Summary
On the optimized (non-relocatable) path, synth compile silently miscompiles br_table: the selector is never read and all arms execute in fall-through sequence. No skip, no warning, exit 0 — a silent wrong-code, violating the #180/#185 "unlowerable ⇒ Err, never silently continue" contract. Same family as #483 (closed) / #500 (open) optimized-path control-flow lowering, but a distinct and more severe symptom (the whole dispatch is elided, not a mis-placed target).
Verified on synth 0.16.0. The --relocatable path lowers the identical module correctly — so gust and the shipped falcon firmware (both --relocatable) are unaffected; this is an optimized-path-only hazard.
Minimal repro
brtable2.wat:
(module (memory (export "memory") 1)
(func (export "dispatch") (param i32)
(block (block (block (block
(br_table 0 1 2 3 (local.get 0)))
(i32.store (i32.const 0)(i32.const 10))(return))
(i32.store (i32.const 0)(i32.const 20))(return))
(i32.store (i32.const 0)(i32.const 30))(return))
(i32.store (i32.const 0)(i32.const 40))))
Oracle (wasmtime): dispatch(0..3) → mem[0] = 10,20,30,40 (one store, selected).
synth compile brtable2.wat -t cortex-m3 --cortex-m (optimized path):
dispatch:
movs r4,#0 ; movs r5,#10 ; ... str [fp] ; case 0 body
... str #20 [fp] ; case 1 body
... str #30 [fp] ; case 2 body
... str #40 [fp] ; case 3 body
pop {pc}
No cmp, no branch, the param (local.get 0) is never loaded → every input yields mem[0]=40. Reproduced across 3 br_table shapes (sequential targets, shared-tail targets, value-returning arms).
synth compile brtable2.wat -t cortex-m3 --relocatable --all-exports (shipped path) — CORRECT:
dispatch:
cmp r0,#0 ; beq case0
cmp r0,#1 ; beq case1
cmp r0,#2 ; beq case2
b default
case0: movw r2,#10 ; str [fp] ; pop
...
Why it matters / scope
- The one
br_table in the gust kernel (kiln_async::task::TaskTable::transition) lowers correctly because gust dissolves with --relocatable — confirmed in the dissolved object (selector read, cmp-chain to distinct targets). So no shipped artifact is wrong today.
- But it's a silent miscompile of a core control-flow primitive on the optimized path; anything that moves to that path (or a future default flip) would be wrong with no diagnostic.
Surfaced while measuring the gust 1.81× codegen gap (gale COMPARE.md); the perf side of the gust hot path is already #390/#428. This is the correctness side. Repro files attached on request.
Summary
On the optimized (non-relocatable) path,
synth compilesilently miscompilesbr_table: the selector is never read and all arms execute in fall-through sequence. No skip, no warning, exit 0 — a silent wrong-code, violating the #180/#185 "unlowerable ⇒ Err, never silently continue" contract. Same family as #483 (closed) / #500 (open) optimized-path control-flow lowering, but a distinct and more severe symptom (the whole dispatch is elided, not a mis-placed target).Verified on synth 0.16.0. The
--relocatablepath lowers the identical module correctly — so gust and the shipped falcon firmware (both--relocatable) are unaffected; this is an optimized-path-only hazard.Minimal repro
brtable2.wat:Oracle (wasmtime):
dispatch(0..3)→ mem[0] =10,20,30,40(one store, selected).synth compile brtable2.wat -t cortex-m3 --cortex-m(optimized path):No
cmp, no branch, the param (local.get 0) is never loaded → every input yields mem[0]=40. Reproduced across 3 br_table shapes (sequential targets, shared-tail targets, value-returning arms).synth compile brtable2.wat -t cortex-m3 --relocatable --all-exports(shipped path) — CORRECT:Why it matters / scope
br_tablein the gust kernel (kiln_async::task::TaskTable::transition) lowers correctly because gust dissolves with--relocatable— confirmed in the dissolved object (selector read,cmp-chain to distinct targets). So no shipped artifact is wrong today.Surfaced while measuring the gust 1.81× codegen gap (gale
COMPARE.md); the perf side of the gust hot path is already #390/#428. This is the correctness side. Repro files attached on request.