| Issue |
208409
|
| Summary |
[WebAssembly] Assertion error when compiling code with coroutines
|
| Labels |
|
| Assignees |
|
| Reporter |
RobinTF
|
Disclaimer: This report was created with a lot of help from AI. I tried my best to confirm every claim in this report, but when it comes to LLVM terminology (which I'm not familiar with) I have to trust it.
When a function's unwind edges form an irreducible cycle that includes EH pads, `WebAssemblyFixIrreducibleControlFlow` rewrites the CFG in a way that violates the invariants the wasm EH lowering passes rely on. The same 40-line, verifier-valid IR module crashes `llc` in both EH modes, at every optimization level (-O0/-O1/-O2), for wasm32 and wasm64, on both `-emscripten` and `-unknown` OS triples:
- **Standard (exnref) EH** (`-wasm-use-legacy-eh=0`):
```
llc: .../WebAssemblyUtilities.cpp:150: llvm::MachineInstr* llvm::WebAssembly::findCatch(llvm::MachineBasicBlock*):
Assertion `EHPad->isEHPad()' failed.
...
2. Running pass 'WebAssembly Late Prepare Exception' on function '@crash'
```
- **Legacy EH** (current default):
```
llc: .../WebAssemblyCFGStackify.cpp:517: void {anonymous}::WebAssemblyCFGStackify::placeTryMarker(llvm::MachineBasicBlock&):
Assertion `!explicitlyBranchesTo(Pred, &MBB) && "Explicit branch to an EH pad!"' failed.
```
On release (no-assert) builds (e.g. the emsdk 6.0.2 toolchain) the minimal module below happens to compile without visible failure, but on real-world inputs of the same shape (the `boost::asio` coroutine code this was reduced from) the violated invariants surface as:
- new EH: segfault in `WebAssemblyLateEHPrepare::runOnMachineFunction` (reproduced with the 9.7 MB pre-reduction IR of the same function), and
- legacy EH: silent emission of a spec-invalid module. Both `wasm-opt` and V8 reject the linked output with `rethrow not targeting catch or catch-all`.
Reproduces on `main` (`777ec1fff4502f6913818de5970a1c060a966877`, 2026-07-08) and on 23.0.0git `787619a4072e0eb7887357d5d284e86c17548aed` (emsdk 6.0.2).
## Reproduce
```
llc -exception-model=wasm -wasm-enable-eh -wasm-use-legacy-eh=0 minimal.ll -o /dev/null # findCatch assert
llc -exception-model=wasm -wasm-enable-eh minimal.ll -o /dev/null # CFGStackify assert
```
```llvm
target datalayout = "e-m:e-p:64:64-p10:8:8-p20:8:8-i64:64-i128:128-f128:64-n32:64-S128-ni:1:10:20"
target triple = "wasm64-unknown-emscripten"
declare i32 @__gxx_wasm_personality_v0(...)
declare ptr @llvm.wasm.get.exception(token)
declare i32 @llvm.wasm.get.ehselector(token)
declare i32 @f(ptr)
declare void @g(ptr)
declare i1 @h(i32)
define void @crash() #0 personality ptr @__gxx_wasm_personality_v0 {
entry:
%0 = invoke i32 @f(ptr null)
to label %cont unwind label %cleanup.outer
cont:
invoke void @g(ptr null)
to label %exit2 unwind label %catch.dispatch
cleanup.outer: ; unwinds INTO the catchswitch below
%pad = cleanuppad within none []
cleanupret from %pad unwind label %catch.dispatch
catch.dispatch:
%cs = catchswitch within none [label %catch.start] unwind label %cleanup.final
catch.start:
%cp = catchpad within %cs [ptr null, ptr null, ptr null]
%exn = tail call ptr @llvm.wasm.get.exception(token %cp)
%sel = tail call i32 @llvm.wasm.get.ehselector(token %cp)
catchret from %cp to label %after.catch
after.catch: ; unwind edge goes BACK to cleanup.outer (cycle)
%1 = invoke i1 @h(i32 0)
to label %exit1 unwind label %cleanup.outer
exit1:
ret void
exit2:
ret void
cleanup.final:
%pad2 = cleanuppad within none []
ret void
}
attributes #0 = { "target-features"="+exception-handling" }
```
## Analysis
The unwind edges form a loop with two distinct entries, i.e. an irreducible cycle whose members include EH pads:
```
entry --invoke @f--> unwinds to %cleanup.outer (entry 1)
cont --invoke @g--> unwinds to %catch.dispatch (entry 2)
%cleanup.outer --cleanupret--> unwinds to %catch.dispatch
%catch.start --catchret--> %after.catch
%after.catch --invoke @h--> unwinds back to %cleanup.outer (closes the cycle)
```
`WebAssemblyFixIrreducibleControlFlow` "fixes" this by routing all entries of the loop `{cleanup.outer, catch.start, ...}` through a new `BR_TABLE` dispatch block with a discriminator register.
MIR right before `wasm-late-eh-prepare` (`-print-before=wasm-late-eh-prepare`, excerpt):
```
bb.2.cleanup.outer (landing-pad, ehscope-entry):
; predecessors: %bb.8
CLEANUPRET %bb.10, implicit-def dead $arguments <-- MBB operand is NOT an EH pad
bb.8:
; predecessors: %bb.9, %bb.10, %bb.11, %bb.3
BR_TABLE_I32 %11:i32, %bb.2, %bb.4, %bb.4 <-- explicit branches to EH pads
bb.2/bb.4 (both landing-pads)
bb.10:
; predecessors: %bb.5
%11:i32 = CONST_I32 0
BR %bb.8
```
This breaks two invariants:
1. `CLEANUPRET`/`RETHROW`'s MBB operand must reference an EH pad. `WebAssemblyLateEHPrepare::replaceFuncletReturns` turns the `CLEANUPRET` into `RETHROW %bb.10`, and `addCatchRefsAndThrowRefs` then calls `WebAssembly::findCatch(bb.10)` → `assert(EHPad->isEHPad())` fires (WebAssemblyUtilities.cpp:150). In no-assert builds the subsequent iteration over a non-EH-pad block leads to the segfault.
2. Nothing may branch explicitly to an EH pad. The new `BR_TABLE` does exactly that, which trips `placeTryMarker`'s `!explicitlyBranchesTo(Pred, &MBB)` assertion in CFGStackify under legacy EH. On the larger original input, the no-assert emsdk compiler proceeds past the violated invariant and emits `rethrow` instructions whose label doesn't target a `catch`, i.e. a module both Binaryen and V8 reject:
```
[wasm-validator error in function N] unexpected false: all rethrow targets must be valid, on (rethrow $label7)
CompileError: WebAssembly.instantiate(): ... rethrow not targeting catch or catch-all
```
## In the wild
Reduced with `llvm-reduce` from the coroutine resume function of a `boost::asio`/`boost::beast` C++20 awaitable [Live code here](https://github.com/ad-freiburg/qlever/blob/e8506158eda09ae7c30ec845ce2442790e25b038/test/HttpTest.cpp), compiled with emscripten `-fwasm-exceptions` (emsdk 6.0.2, wasm64, `-pthread`).
Possibly related to #126916.
## Environment
- LLVM main `777ec1fff4502f6913818de5970a1c060a966877` (2026-07-08), x86_64 Linux host, `-DLLVM_TARGETS_TO_BUILD=WebAssembly`
- Also: clang 23.0.0git `787619a4072e0eb7887357d5d284e86c17548aed` as shipped in emsdk 6.0.2
_______________________________________________
llvm-bugs mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/llvm-bugs