Description
(module
(tag $tag (param i32))
(func $foo
(local $temp i32)
(try
(do
(nop)
)
(catch $tag
(call $call
(block (result i32)
(local.set $temp
(pop i32)
)
(call $nop)
(local.get $temp)
)
)
)
)
)
(func $call (param i32))
(func $nop)
)
In this IR the pop exists in a place that Binaryen accepts but wasm does not: we use a pop to get the catch value, but we are inside a block so the wasm does not validate, and a VM will error on something like this:
CompileError: WebAssembly.instantiate(): Compiling function #0 failed: not enough arguments on the stack for local.set (need 1, got 0) @+43)
That is with just translating that wat into a binary, bin/wasm-opt a.wat -all -o a.out.wasm
. However, if we add a --roundtrip
in that command, then the IR becomes this:
(catch $tag$0
(local.set $temp
(pop i32)
)
(call $call
(block $label$3 (result i32)
(call $nop)
(local.get $temp)
)
)
)
Note how the pop was moved into a proper position. So it seems like we have some logic in the binary reading code that handles this somehow, and so using --roundtrip
is a workaround, at least, so this is probably not urgent.
I noticed this in wasm GC that had this:
(catch $tag$0
(throw $tag$0
(ref.cast
(pop (ref null $type$16))
(global.get $global$1)
)
)
)
That cast can be statically removed, leading to:
(catch $tag$0
(throw $tag$0
(block (result (ref null $type$16))
(local.set $4
(pop (ref null $type$16))
)
(drop
(global.get $global$1)
)
(local.get $4)
)
)
)
And that is how the problem can occur.
This does not seem specific to wasm GC, but perhaps we have more peephole opts that end up emitting small blocks, which increase the risk.