A discarded value was being stored over the return address
edn.flan crashed by jumping into .rodata, several statements after the mistake, and the assembly at the jump read correctly. Item 15 said this is how hand-encoding fails, and it is: the crash and the cause were in different functions. The cause is one line of design. A form whose value is thrown away was handed the sink, and the sink was spelled as an address — rbp+0. That is the saved rbp, and rbp+8 is the return address, so a non-void form in statement position stored its value straight over both. A 16-byte slice did it in one rep movsb. The sink is now compared by identity and never used as an address: anything with a value that is handed it gets a frame temporary instead, reclaimed immediately. The point is not the temporary, it is that the store has somewhere legal to go. edn.flan matches the LLVM build now — 60 lines of a hand-written EDN reader, unions, options, nested collections and all.
This commit is contained in:
parent
fcdaa105af
commit
58b1f49cf2
23
lib/x86.ml
23
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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user