diff --git a/lib/x86.ml b/lib/x86.ml index acad01b..fd5135d 100644 --- a/lib/x86.ml +++ b/lib/x86.ml @@ -741,12 +741,29 @@ let emit_args f (args : arg list) = (* ── Lowering ────────────────────────────────────────────────────────── *) -(* The destination a void expression is handed and never reads. rbp+0 is the - saved rbp; nothing below stores through a destination it was told was - void, so the address is a name and not a target. *) +(* The destination handed to an expression whose value is thrown away. + + It is one allocated value and it is compared by identity, because it is not + an address and must never be used as one: rbp+0 is the saved rbp and rbp+8 + is the return address, so a 16-byte slice stored "into the sink" overwrites + both and the function returns into whatever the first two words of the + value happened to be. That is not hypothetical — it is how [edn.flan] + failed, by jumping into .rodata several statements after the real mistake, + and the mistake was a form of non-void type written in statement position. + + So [lower] refuses the sink for anything that has a value, and spends a + frame temporary on it instead. The temporary is reclaimed at once; the + point is that the store has somewhere legal to go. *) let sink = Lf 0 let rec lower f (e : Tast.expr) (dst : loc) : unit = + if dst == sink && not (is_void e.Tast.ty) then + scoped f (fun () -> + let o = tmp f e.Tast.ty in + lower f e (Lf o)) + else lower_at f e dst + +and lower_at f (e : Tast.expr) (dst : loc) : unit = let t = e.Tast.ty in match e.Tast.e with | Tast.Int (n, _) -> imm_into f ~reg:rax n; store_loc f ~reg:rax dst t