Filed by the PulseEngine challenge harness (synth 137b773, aarch64 backend). Confirmed by disassembling the (now-valid, post-#546) ELF64/EM_AARCH64 output.
Defect: -b aarch64 SILENTLY miscompiles f32 ops instead of rejecting them
milestone-1 correctly rejects most unimplemented ops with an explicit error, e.g.:
$ synth compile i32shl.wat -b aarch64 ...
Error: aarch64 selector: unsupported wasm op for aarch64 milestone-1: I32Shl
(same honest failure for I32DivS, I64Add, I64Mul, I32Load.) But f32 ops compile "successfully" and emit wrong code:
| input |
emitted A64 |
correct? |
f32.add(a,b) |
mov w0, w1 ; ret |
❌ returns the 2nd operand, no add |
f32.sub(a,b) |
mov w0, w1 ; ret |
❌ |
f32.mul(a,b) |
mov w0, w1 ; ret |
❌ |
f32.const 2.5 |
ret |
❌ constant never materialized |
No FP instruction (fadd/fmul/fmov/scvtf) is ever emitted, and f32 args live in s0/s1 (not w0/w1) under AAPCS64 anyway. A program doing f32 arithmetic compiled with -b aarch64 gets silently wrong results — strictly worse than the honest "unsupported milestone-1" error the other ops produce.
Repro
printf '(module (func (export "f") (param f32 f32)(result f32)(f32.add (local.get 0)(local.get 1))))\n' > f.wat
synth compile f.wat -b aarch64 -n f -o f.o
llvm-objdump -d f.o # -> add? no: mov w0, w1 ; ret
Root cause
The op stream handed to the aarch64 backend has the f32 op dropped:
INFO WASM operations: [LocalGet(0), LocalGet(1), End] # F32Add is gone
So F32Add never reaches the selector's guard at crates/synth-backend-aarch64/src/selector.rs:97 (other => Err("unsupported wasm op...")) that would otherwise reject it. The End handler then just does mov w0, <top-of-stack=w1>; ret.
Fix
Ensure f32/f64 ops are NOT silently dropped before the aarch64 selector — either implement them (fadd/fsub/fmul on s/d regs, args in v0..), or let them fall through to the milestone-1 unsupported error so the failure is honest. Silent wrong-code must never be the outcome.
Related observation (not claiming a bug here)
The same op stream [LocalGet, LocalGet, End] (F32Add dropped) is logged for the ARM backend on the --relocatable/direct-selector path. The optimized ARM path may handle f32 separately (VFP), but the direct-selector path is worth checking for the same silent drop.
Note
Regression probe added: harness/synth_aarch64_f32_probe.sh (asserts f32 ops either error or emit an FP instruction — never silently return an operand).
Filed by the PulseEngine challenge harness (synth
137b773, aarch64 backend). Confirmed by disassembling the (now-valid, post-#546) ELF64/EM_AARCH64 output.Defect:
-b aarch64SILENTLY miscompiles f32 ops instead of rejecting themmilestone-1 correctly rejects most unimplemented ops with an explicit error, e.g.:
(same honest failure for
I32DivS,I64Add,I64Mul,I32Load.) But f32 ops compile "successfully" and emit wrong code:f32.add(a,b)mov w0, w1 ; retf32.sub(a,b)mov w0, w1 ; retf32.mul(a,b)mov w0, w1 ; retf32.const 2.5retNo FP instruction (
fadd/fmul/fmov/scvtf) is ever emitted, and f32 args live ins0/s1(notw0/w1) under AAPCS64 anyway. A program doing f32 arithmetic compiled with-b aarch64gets silently wrong results — strictly worse than the honest "unsupported milestone-1" error the other ops produce.Repro
Root cause
The op stream handed to the aarch64 backend has the f32 op dropped:
So
F32Addnever reaches the selector's guard atcrates/synth-backend-aarch64/src/selector.rs:97(other => Err("unsupported wasm op...")) that would otherwise reject it. TheEndhandler then just doesmov w0, <top-of-stack=w1>; ret.Fix
Ensure f32/f64 ops are NOT silently dropped before the aarch64 selector — either implement them (fadd/fsub/fmul on
s/dregs, args inv0..), or let them fall through to the milestone-1unsupportederror so the failure is honest. Silent wrong-code must never be the outcome.Related observation (not claiming a bug here)
The same op stream
[LocalGet, LocalGet, End](F32Add dropped) is logged for the ARM backend on the--relocatable/direct-selector path. The optimized ARM path may handle f32 separately (VFP), but the direct-selector path is worth checking for the same silent drop.Note
Regression probe added:
harness/synth_aarch64_f32_probe.sh(asserts f32 ops either error or emit an FP instruction — never silently return an operand).