Repository navigation
[wasm] crossgen2 ships an invalid branch depth (~0) for EH funclets whose branch target isn't on the wasm control-flow stack #131252
Description
Description
When crossgen2 compiles managed methods to WebAssembly (wasm32) R2R, CodeGen::findTargetDepth can fail to locate a branch's target block in the active wasm control-flow stack. In that case it falls through to a checked-only assert and then return ~0;. Because the assert is compiled out of release/optimized crossgen2, the sentinel ~0 (0xFFFFFFFF) is emitted as the branch depth of a br / br_if / br_table instruction, producing an invalid wasm module. Validators reject it — V8 reports invalid branch depth: 4294967295.
Relevant source (src/coreclr/jit/codegenwasm.cpp); line numbers are relative to main and will drift:
unsigned CodeGen::findTargetDepth(BasicBlock* targetBlock) // codegenwasm.cpp:517
{
// ... searches wasmControlFlowStack for targetBlock ...
#ifdef DEBUG
JITDUMP("Could not find " FMT_BB "[%u]%s in active control stack\n", ...);
// ... dumps the current stack ...
#endif
assert(!"Can't find target in control stack"); // codegenwasm.cpp:568 — DEBUG-only
return ~0; // codegenwasm.cpp:570 — release ships 0xFFFFFFFF
}The two callers feed this depth straight into emitted branch instructions:
codegenwasm.cpp:1120—br_tablecase-target depth (BBJ_SWITCH).codegenwasm.cpp:3969—findTargetDepth(tgtBlock) + wasmExtraControlFlowDepthfor an emittedbr/br_if.
So a ~0 return does not merely misbehave in one spot — it is written into the module's branch encoding.
Observed on the exception-handling funclet System.Threading.Tasks.Parallel.Invoke (mangled Invoke_0, funclet 1) during a full framework wasm R2R crossgen. It is not SIMD-related (the funclet contains no 0xfd-prefixed opcodes).
This is a distinct root cause from the terminal-end drop fixed in #131251 (that fix addresses funclet end-emission; this is funclet branch-target resolution), but it is the same family of defect: an EH-funclet wasm-codegen gap guarded only by a checked-only assert, so in release the malformed wasm ships silently instead of failing the build.
Reproduction Steps
- Build a wasm R2R crossgen2 (release) and a
browser-wasmframework pack. - Crossgen a framework assembly (or composite) containing
System.Threading.Tasks.Parallelfor wasm:crossgen2 -O --targetarch:wasm --targetos:browser \ --codegenopt:JitWasmNyiToR2RUnsupported=1 \ -r:<every other framework dll> \ -o:out.wasm <assembly containing System.Threading.Tasks.Parallel>.dll - Validate the emitted module:
wasm-tools validate out.wasm # or load it in V8 / a browser
The malformed function is the Parallel.Invoke EH funclet; V8 rejects the whole module with invalid branch depth: 4294967295.
Expected behavior
crossgen2 emits a valid wasm module: every emitted br / br_if / br_table carries a real, in-range control-stack depth for its target block. If a target genuinely cannot be resolved on the control-flow stack, crossgen2 should fail the compilation (or mark the method R2R-unsupported) rather than silently emit a ~0 branch depth into shipping wasm.
Actual behavior
findTargetDepth fails to find the target block on the wasm control-flow stack for certain EH-funclet shapes. In release the DEBUG assert is gone, so it returns ~0 and that value is encoded as the branch depth. The resulting module fails validation:
invalid branch depth: 4294967295
(V8; wasm-tools/other validators reject it equivalently.)
Regression?
Not a regression from a shipping release — wasm R2R (wasm32 crossgen2) is in-development. This is within the same wasm EH-funclet codegen area as #129335, #129449, and #131251.
Known Workarounds
None at the module level once emitted — unlike the terminal-end drop (#131251), the missing/incorrect data is a branch-target depth that cannot be repaired by appending a byte. Avoiding the affected funclet shape (e.g. excluding the offending method from R2R) sidesteps it.
Configuration
- .NET 11 (
main), CoreCLR wasm R2R (crossgen2), targetwasm32/--targetos:browser. - Host: macOS/arm64; reproduced against a
browser-wasmframework pack. - Not specific to SIMD codegen (the funclet has no SIMD opcodes).
Other information
- Root cause locus (relative to
main, subject to drift):CodeGen::findTargetDepth(src/coreclr/jit/codegenwasm.cpp:517); DEBUG assertassert(!"Can't find target in control stack")at:568; release fallbackreturn ~0;at:570. Callers at:1120(br_table) and:3969(br/br_if). To locate it independent of line drift, search for the assert stringCan't find target in control stack. - Related: [wasm] Emit terminal
endfor EH funclets ending in a no-return call #131251 (sibling wasm EH-funclet defect — terminalenddropped; different root cause, fixed separately), Wasm / R2R: malformed wasm (missing function-body end opcode) for try / throwing-finally pattern in b51875 #129335 and Wasm R2R: emit trailing end after retless BBJ_CALLFINALLY #129449 (prior wasm EH-funclet codegen work in the same area). - Suggested direction: either (a) resolve the target correctly for this funclet's control-flow shape so it is present on the stack when the branch is emitted, or (b) make the "target not found" case a hard compilation failure in all build flavors (not just checked) so malformed wasm can never ship.
Note
This issue was authored with the help of GitHub Copilot.
Activity
dotnet-policy-service commentedon Jul 23, 2026
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.
dotnet-policy-service commentedon Jul 23, 2026
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.
lewing commentedon Jul 23, 2026
Very likely fixed by #131279, which addresses the underlying wasm block-layout defect.
Root cause:
FgWasm::VisitWasmSuccsattaches a throw helper to a try side-entry via the purely-lexicalbbInTryRegionsenclosing-try match (added in #130945) without checking that the helper and the side-entry live in the same function region (funclet). That pulls a main-method throw helper into a handler funclet's RPO, which both drops the funclet's terminalendand miscomputes branch-target depths — the ~0 depth reported here. #131279 gates that attach on same-funclet ownership.Evidence:
System.Threading.Tasks.Parallel(the module in this report) is invalid on baseline crossgen and validates with #131279, in a 194-assembly standalone survey with 0 regressions (details on the PR). Suggest closing this as resolved-by #131279 once it merges; happy to keep it open until then to confirm on the exactParallel.Invokerepro.Note
This comment was authored with the assistance of GitHub Copilot.
[wasm] Restrict enclosing-try throw-helper attach to the same funclet (…
[wasm] Restrict enclosing-try throw-helper attach to the same funclet (…