On thumb-2, a br/br_if that exits an enclosing block from inside an if is not taken
when the branch's value operand is itself a block that branches. Control falls through to
the enclosing block's tail value instead. synth compile exits 0 with no warning.
Found by executing the official WebAssembly testsuite across backends (context: #928): these
are labels.wast br_if2 and br, both assert_return … (i32.const 1).
Verified on v0.55.0 (clone HEAD 97bd6db), thumb-2 under qemu-system-arm; aarch64 and
wasmtime agree with each other and disagree with thumb-2.
Minimal repro
(module
(func (export "t") (result i32)
(block $l0 (result i32)
(if (i32.const 1)
(then
(drop
(br_if $l0
(block $l1 (result i32) (br $l1 (i32.const 1))) ;; value operand branches
(i32.const 1))))) ;; condition is 1
(i32.const 0)))) ;; fallthrough
|
result |
| wasmtime |
1 |
| aarch64 |
1 |
| thumb-2 (cortex-m3) |
0 |
synth compile v.wat -b arm -t cortex-m3 --all-exports --relocatable → exit 0, no warning.
Deterministic across repeated runs.
The condition is a literal 1, so br_if must branch to $l0 with value 1; getting 0
means the branch was not taken and the block's tail (i32.const 0) was produced instead.
The br form fails the same way (labels.wast/br):
(block $l0 (result i32)
(if (i32.const 1)
(then (br $l0 (block $l1 (result i32) (br $l1 (i32.const 1)))))
(else (block (drop (block $l1 (result i32) (br $l1 (i32.const 1)))))))
(i32.const 1))
It is the combination, not any one piece
Each ingredient alone is lowered correctly on thumb-2 (all verified, all match wasmtime):
| shape |
thumb-2 |
br_if $l0 with a plain constant value, no enclosing if |
✓ 1 |
br_if $l0 with a plain constant value, inside if/then |
✓ 1 |
br_if $l0 whose value is a block-with-br, no enclosing if |
✓ 1 |
(block $l1 (result i32) (br $l1 (i32.const 1))) on its own |
✓ 1 |
| all of the above combined (repro above) |
✗ 0 |
So it needs an inner block that branches supplying the value of a branch that itself exits
an if. That is consistent with the nested-branch value being materialised into the wrong
place, or the outer branch's target/condition being clobbered while the inner block's exit is
lowered — but I have not gone further than narrowing the shape.
Impact
Silent wrong value on synth's flagship target, in ordinary structured control flow — this
shape is what an optimising producer emits for if (c) return f(); …. Nothing is observable
on target: exit 0, no diagnostic, plausible-looking result.
labels.wast is in the official testsuite, so this is a genuine spec-conformance failure and
would be caught by a value-checking WAST gate — which is exactly what #928 is about. The
aarch64 backend gets both cases right.
On thumb-2, a
br/br_ifthat exits an enclosing block from inside anifis not takenwhen the branch's value operand is itself a block that branches. Control falls through to
the enclosing block's tail value instead.
synth compileexits 0 with no warning.Found by executing the official WebAssembly testsuite across backends (context: #928): these
are
labels.wastbr_if2andbr, bothassert_return … (i32.const 1).Verified on v0.55.0 (clone HEAD 97bd6db), thumb-2 under
qemu-system-arm; aarch64 andwasmtime agree with each other and disagree with thumb-2.
Minimal repro
synth compile v.wat -b arm -t cortex-m3 --all-exports --relocatable→ exit 0, no warning.Deterministic across repeated runs.
The condition is a literal
1, sobr_ifmust branch to$l0with value 1; getting0means the branch was not taken and the block's tail
(i32.const 0)was produced instead.The
brform fails the same way (labels.wast/br):It is the combination, not any one piece
Each ingredient alone is lowered correctly on thumb-2 (all verified, all match wasmtime):
br_if $l0with a plain constant value, no enclosingifbr_if $l0with a plain constant value, insideif/thenbr_if $l0whose value is a block-with-br, no enclosingif(block $l1 (result i32) (br $l1 (i32.const 1)))on its ownSo it needs an inner block that branches supplying the value of a branch that itself exits
an
if. That is consistent with the nested-branch value being materialised into the wrongplace, or the outer branch's target/condition being clobbered while the inner block's exit is
lowered — but I have not gone further than narrowing the shape.
Impact
Silent wrong value on synth's flagship target, in ordinary structured control flow — this
shape is what an optimising producer emits for
if (c) return f(); …. Nothing is observableon target: exit 0, no diagnostic, plausible-looking result.
labels.wastis in the official testsuite, so this is a genuine spec-conformance failure andwould be caught by a value-checking WAST gate — which is exactly what #928 is about. The
aarch64 backend gets both cases right.