You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
On WebAssembly R2R (crossgen), an async method whose try block completes normally by branching out early (a return after an await) inside a try { ... } catch { } generates a valid wasm module that traps at runtime with RuntimeError: unreachable on the normal-completion path.
This is a codegen defect in the CoreCLR wasm JIT control-flow lowering (Compiler::fgWasmControlFlow() in src/coreclr/jit/fgwasm.cpp). The normal-completion branch of the state-machine MoveNext is bound to a wasm Block that ends inside the exception-ref wrapper funclet, so the branch resolves to depth 0 and lands on that region's trailing validation unreachable (emitted right after its end by genEmitStartBlock) instead of the continuation.
Note: this is a separate bug from #131279 (dropped terminal end producing an invalid module) and is not a reopening of the (closed) #129335. Here the module is well-formed and validates; it simply traps at runtime.
Reproduction Steps
Compile the following to wasm R2R (crossgen, TargetOS=browser) and run under node:
A synchronously-completing await (Task.CompletedTask) is deliberate: the trap is pure codegen and does not require a real suspend/resume, and a completed await avoids blocking the single wasm thread. The defect reproduces only under crossgen wasm R2R; all other targets pass trivially (ordinary JIT/interp).
Expected behavior
RunAsync completes normally and the process exits cleanly.
Actual behavior
The wasm module validates but traps at runtime:
RuntimeError: unreachable
at RunAsync MoveNext (wasm-function[2])
Disassembly shows the normal-completion branch emitted as a depth-0 branch (br 0) that lands on the enclosing try's trailing unreachable. The fix flips it to br 1, landing on the continuation.
Regression?
wasm R2R codegen is new/in-development; not a regression from a shipping release.
Reproduced on osx.arm64 host building the browser-wasm R2R leg; runtime trap observed under node --experimental-wasm-exnref.
Other information
Root cause and fix: in fgWasmControlFlow(), a non-contiguous forward branch that exits a try/catch region started its enclosing Block at the innermost try's begin. When the branch also escapes an outer catch-try that shares the same end cursor (the wasm catch-resumption / resume-after-catch shape, where the paired ExnRefWrapper is stretched past the try-end to cover the resumption dispatcher), the Block ends up nested inside that outer try, so the branch resolves to depth 0 → the outer try's trailing unreachable.
The fix walks the enclosing try regions outward and starts the Block at the outermost escaped catch-try's begin, so the Block encloses those trys' ends and the branch resolves to the continuation. For a single-level try/catch the outermost escaped region is also the innermost, so behavior there is unchanged (validated: 194-assembly sweep, 0 new invalid, single-level provably unaffected).
Fix + regression test staged on branch lewing-bug-c-wasm-catch-resume-fix (touches only src/coreclr/jit/fgwasm.cpp and a new src/tests/JIT/Regression/JitBlue/ test that forces the browser leg through crossgen wasm R2R via AlwaysUseCrossGen2).
Note
This issue was generated with the assistance of GitHub Copilot.
Description
On WebAssembly R2R (crossgen), an
asyncmethod whosetryblock completes normally by branching out early (areturnafter anawait) inside atry { ... } catch { }generates a valid wasm module that traps at runtime withRuntimeError: unreachableon the normal-completion path.This is a codegen defect in the CoreCLR wasm JIT control-flow lowering (
Compiler::fgWasmControlFlow()insrc/coreclr/jit/fgwasm.cpp). The normal-completion branch of the state-machineMoveNextis bound to a wasmBlockthat ends inside the exception-ref wrapper funclet, so the branch resolves to depth 0 and lands on that region's trailing validationunreachable(emitted right after itsendbygenEmitStartBlock) instead of the continuation.Note: this is a separate bug from #131279 (dropped terminal
endproducing an invalid module) and is not a reopening of the (closed) #129335. Here the module is well-formed and validates; it simply traps at runtime.Reproduction Steps
Compile the following to wasm R2R (crossgen,
TargetOS=browser) and run under node:A synchronously-completing
await(Task.CompletedTask) is deliberate: the trap is pure codegen and does not require a real suspend/resume, and a completed await avoids blocking the single wasm thread. The defect reproduces only under crossgen wasm R2R; all other targets pass trivially (ordinary JIT/interp).Expected behavior
RunAsynccompletes normally and the process exits cleanly.Actual behavior
The wasm module validates but traps at runtime:
Disassembly shows the normal-completion branch emitted as a depth-0 branch (
br 0) that lands on the enclosing try's trailingunreachable. The fix flips it tobr 1, landing on the continuation.Regression?
wasm R2R codegen is new/in-development; not a regression from a shipping release.
Known Workarounds
None at the source level.
Configuration
CrossGen2OutputFormat=wasm),TargetOS=browser.node --experimental-wasm-exnref.Other information
Root cause and fix: in
fgWasmControlFlow(), a non-contiguous forward branch that exits a try/catch region started its enclosingBlockat the innermost try's begin. When the branch also escapes an outer catch-try that shares the same end cursor (the wasm catch-resumption / resume-after-catch shape, where the pairedExnRefWrapperis stretched past the try-end to cover the resumption dispatcher), theBlockends up nested inside that outer try, so the branch resolves to depth 0 → the outer try's trailingunreachable.The fix walks the enclosing try regions outward and starts the
Blockat the outermost escaped catch-try's begin, so theBlockencloses those trys' ends and the branch resolves to the continuation. For a single-leveltry/catchthe outermost escaped region is also the innermost, so behavior there is unchanged (validated: 194-assembly sweep, 0 new invalid, single-level provably unaffected).Fix + regression test staged on branch
lewing-bug-c-wasm-catch-resume-fix(touches onlysrc/coreclr/jit/fgwasm.cppand a newsrc/tests/JIT/Regression/JitBlue/test that forces the browser leg through crossgen wasm R2R viaAlwaysUseCrossGen2).Note
This issue was generated with the assistance of GitHub Copilot.