Skip to content

Optimized path silently miscompiles br_table — selector elided, all arms fall through (#483/#500 family) #507

Description

@avrabe

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions