The ABI probe catches a real misalignment, which is why it exists
Three synthetic Tast functions calling C: eight integers so two go on the stack, and a callee that does a 16-byte aligned spill and answers -1 if it was entered with rsp misaligned. The third calls it from inside a binary operator. The third fails. Alignment at a call site is not a property of the prologue -- it is a property of how much the expression evaluator has pushed, and the evaluator spills the left operand across the right one's evaluation. A call in that right operand runs 8 bytes off. Nothing in the arithmetic tests could see it, because they call nothing that spills a vector register. This is the raylib failure mode exactly, and it is left red for one commit so the record shows the probe found it rather than agreeing with the code.
This commit is contained in:
parent
0fbca40446
commit
faba8a49f8
@ -103,6 +103,49 @@ let run src =
|
||||
close_out oc
|
||||
with Not_found -> ());
|
||||
|
||||
(* ── The SysV boundary ──────────────────────────────────────────────
|
||||
Three synthetic functions, built as Tast by hand rather than written in
|
||||
Flan, because the surface language has no way to spell a call to an
|
||||
arbitrary C symbol with eight arguments. [Tast.Rt] is the node a runtime
|
||||
call already uses and the one a [declare-c] shim lands on, so this is the
|
||||
real path with a made-up callee. *)
|
||||
let loc = Flan.Loc.unknown in
|
||||
let i64 = Flan.Types.Int Flan.Types.I64 in
|
||||
let ex e = { Flan.Tast.e; ty = i64; loc } in
|
||||
let lit n = ex (Flan.Tast.Int (Int64.of_int n, Flan.Types.I64)) in
|
||||
let probe name params body =
|
||||
{ Flan.Tast.name; params; slots = Array.make (List.length params) i64;
|
||||
snames = Array.make (List.length params) None; ret = i64;
|
||||
body = [ body ]; fdefers = []; fparent = None; floc = loc }
|
||||
in
|
||||
let arg0 = ex (Flan.Tast.Local 0) in
|
||||
let probes = [
|
||||
(* Eight integers: six in registers and two on the stack, which is the case
|
||||
a register-only convention gets silently wrong. *)
|
||||
probe "abi-8" [ i64 ]
|
||||
(ex (Flan.Tast.Prim (Flan.Tast.Rt "spike_probe8",
|
||||
[ arg0; lit 2; lit 3; lit 4; lit 5; lit 6; lit 7; lit 8 ])));
|
||||
(* rsp % 16 == 0 at the call. The callee does an aligned 16-byte spill and
|
||||
answers -1 if it was entered misaligned. *)
|
||||
probe "abi-align" [ i64 ]
|
||||
(ex (Flan.Tast.Prim (Flan.Tast.Rt "spike_probe_align", [ arg0 ])));
|
||||
(* The same call, but underneath a binary operator -- so it is evaluated
|
||||
with the left operand spilled on the stack. This is the one that matters:
|
||||
alignment at a call site is not a property of the prologue, it is a
|
||||
property of how much the expression evaluator has pushed. *)
|
||||
probe "abi-align-nested" [ i64 ]
|
||||
(ex (Flan.Tast.Prim (Flan.Tast.Add,
|
||||
[ lit 0;
|
||||
ex (Flan.Tast.Prim (Flan.Tast.Rt "spike_probe_align", [ arg0 ])) ])));
|
||||
] in
|
||||
List.iter
|
||||
(fun (fd : Flan.Tast.fn) ->
|
||||
let code = X86.fn ~resolve fd in
|
||||
let p = jit_alloc page in
|
||||
jit_write p code; jit_protect p page;
|
||||
Hashtbl.replace addrs fd.Flan.Tast.name p)
|
||||
probes;
|
||||
|
||||
print_endline "results:";
|
||||
let at n = Hashtbl.find addrs n in
|
||||
check "spike-add 3 4" (call2 (at "spike-add") 3L 4L) 7L;
|
||||
@ -112,6 +155,9 @@ let run src =
|
||||
check "spike-if 1 2" (call2 (at "spike-if") 1L 2L) 1L;
|
||||
check "spike-if 9 2" (call2 (at "spike-if") 9L 2L) 7L;
|
||||
check "spike-calls 5" (call1 (at "spike-calls") 5L) 656L;
|
||||
check "abi-8 1" (call1 (at "abi-8") 1L) 87654321L;
|
||||
check "abi-align 10" (call1 (at "abi-align") 10L) 13L;
|
||||
check "abi-align-nested 10" (call1 (at "abi-align-nested") 10L) 13L;
|
||||
|
||||
Printf.printf "\n%d checks, %d failures\n" !checks !failures;
|
||||
exit (if !failures = 0 then 0 else 1)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user