Skip to content

thumb-2: br/br_if out of an if is not taken when its value operand is a block that branches (labels.wast) — silent wrong value, exit 0 #930

Description

@avrabe

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions