Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions artifacts/verified-codegen-roadmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -747,8 +747,14 @@ artifacts:
(frozen-safe, no codegen touched): `scripts/repro/i64_param_518.wat` +
`i64_param_518_differential.py` — AAPCS-correct arg placement (even-aligned
i64 pairs: `(i32,i64)` → R0,R2:R3), i64-aware result readback (R0|R1<<32),
runs BOTH paths vs wasmtime. Measured: 6/6 i64-param functions diverge on
each path; the i32 control (`t_i32`) stays correct on both. The script is a
runs BOTH paths vs wasmtime. Measured: 10/10 i64-param functions diverge on
each path; the i32 control (`t_i32`) stays correct on both. The fixture
covers the FULL AAPCS i64-param register-assignment matrix the fix's mapping
logic must get right — not just the single leading `(i64)` (R0:R1) case but
the multi-param shapes the sequential `index_to_reg(i)` mapping botches:
`(i64,i64)` (p1 = even-aligned R2:R3, mis-mapped to R1 → computes p0.lo+p0.hi
instead of p0+p1), `(i64,i32)` (p1=R2 ignored), and reading ONLY the second
i64 param (sequential reads R1, and the const clobbers the real p1.lo=R2). The script is a
stable characterization (exit 0 = "bug reproduces as the root cause
documents"); the gated fix flips its `EXPECT_MISCOMPILE→False` and it becomes
the correctness gate. BOUNDED FIX SPEC for the gated step (NOT this tick):
Expand Down
13 changes: 13 additions & 0 deletions scripts/repro/i64_param_518.wat
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,19 @@
;; i64 param NOT at index 0 (after an i32) — exercises pair-shift past a scalar
(func (export "t_mixed") (param i32 i64) (result i64)
(i64.add (local.get 1) (i64.extend_i32_u (local.get 0))))
;; --- AAPCS i64-param register-assignment matrix (the fix's hardest logic) ---
;; TWO i64 params: p0=R0:R1, p1=R2:R3 (consecutive even-aligned pairs). The
;; sequential `index_to_reg(i)` mapping puts p1 at R1 (wrong) and computes
;; p0.lo+p0.hi instead of p0+p1.
(func (export "t_ii_add") (param i64 i64) (result i64)
(i64.add (local.get 0) (local.get 1)))
;; i64 then i32: p0=R0:R1, p1=R2 (no even-align padding needed for the i32).
(func (export "t_i64_i32") (param i64 i32) (result i64)
(i64.add (local.get 0) (i64.extend_i32_u (local.get 1))))
;; read ONLY the SECOND i64 param (p1=R2:R3) + const: sequential mapping reads
;; p1 from R1 (wrong) AND the const clobbers R2 (the real p1.lo).
(func (export "t_snd_i64") (param i64 i64) (result i64)
(i64.add (local.get 1) (i64.const 9)))
;; control: i32 param binop must stay correct
(func (export "t_i32") (param i32) (result i32)
(i32.add (local.get 0) (i32.const 5))))
36 changes: 25 additions & 11 deletions scripts/repro/i64_param_518_differential.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,18 @@
("t_sub", [(100,)], "i64"),
("t_or", [(0x8,)], "i64"),
("t_mixed", [(3, 10)], "i32,i64"),
# AAPCS i64-param register-assignment matrix — the even-aligned-pair vs
# sequential-index distinction the fix's mapping logic must get right.
("t_ii_add", [(7, 5)], "i64,i64"), # p0=R0:R1, p1=R2:R3
("t_ii_add", [(0xFFFFFFFF, 1)], "i64,i64"),
("t_i64_i32", [(10, 3)], "i64,i32"), # p0=R0:R1, p1=R2
("t_snd_i64", [(100, 40)], "i64,i64"), # reads only p1=R2:R3
("t_i32", [(7,)], "i32"),
]
# functions whose result is i64 (read R0:R1) vs i32 (read R0 only)
I64_RESULT = {"t_add", "t_sub", "t_or", "t_mixed"}
I64_PARAM_FNS = {"t_add", "t_sub", "t_or", "t_mixed"}
I64_RESULT = {"t_add", "t_sub", "t_or", "t_mixed",
"t_ii_add", "t_i64_i32", "t_snd_i64"}
I64_PARAM_FNS = I64_RESULT


def wasmtime_run(fn, args):
Expand Down Expand Up @@ -161,38 +168,45 @@ def run_path(label, relocatable):
code, base, syms = load(out)
print(f"\n=== {label} path ===")
divergences = 0
control_ok = True
for fn, args, sig in CASES:
faddr = syms.get(fn)
if faddr is None:
print(f" {fn}: SYMBOL MISSING")
divergences += 1
if fn not in I64_PARAM_FNS:
control_ok = False
continue
exp = wasmtime_run(fn, args[0])
got = unicorn_run(code, base, faddr, args, sig, fn in I64_RESULT)
match = isinstance(got, int) and got == exp
flag = "ok " if match else "BUG"
if not match:
divergences += 1
if fn not in I64_PARAM_FNS: # an i32-control regression is a real fail
control_ok = False
print(f" [{flag}] {fn}{args[0]} -> {got} (wasmtime {exp})"
+ ("" if match else f" [MISCOMPILE: off by hi/param]"))
return divergences
+ ("" if match else " [MISCOMPILE: off by hi/param]"))
return divergences, control_ok


def main():
opt_div = run_path("OPTIMIZED (--target cortex-m4)", relocatable=False)
rel_div = run_path("DIRECT (--relocatable)", relocatable=True)
opt_div, opt_ctrl = run_path("OPTIMIZED (--target cortex-m4)", relocatable=False)
rel_div, rel_ctrl = run_path("DIRECT (--relocatable)", relocatable=True)

print("\n--- characterization verdict ---")
# The i32 control must ALWAYS match; only i64-param fns are allowed to diverge
# while the bug is live. So a "healthy" buggy state = both paths diverge on
# >=1 i64-param fn AND neither diverges on t_i32.
# >=1 i64-param fn AND neither breaks the i32 control.
if EXPECT_MISCOMPILE:
ok = opt_div > 0 and rel_div > 0
print(f"opt divergences={opt_div} direct divergences={rel_div}")
ok = opt_div > 0 and rel_div > 0 and opt_ctrl and rel_ctrl
print(f"opt divergences={opt_div} (control_ok={opt_ctrl}) "
f"direct divergences={rel_div} (control_ok={rel_ctrl})")
if ok:
print("RESULT: PASS — #518 reproduces on BOTH paths exactly as the "
"root-cause documents (i64 param dropped/high-half clobbered; "
"i32 control correct). Oracle ready for the gated fix.")
"root-cause documents (i64 param dropped/high-half clobbered "
"across the full single/dual/even-aligned matrix; i32 control "
"correct). Oracle ready for the gated fix.")
sys.exit(0)
print("RESULT: FAIL — reality diverged from the documented #518 root "
"cause (a path no longer miscompiles, or the i32 control broke). "
Expand Down
Loading