Skip to content

[wasm] Fix R2R codegen trap on async try/return/catch normal-completion branch#131287

Merged
lewing merged 3 commits into
mainfrom
lewing-bug-c-wasm-catch-resume-fix
Jul 23, 2026
Merged

[wasm] Fix R2R codegen trap on async try/return/catch normal-completion branch#131287
lewing merged 3 commits into
mainfrom
lewing-bug-c-wasm-catch-resume-fix

Conversation

@lewing

@lewing lewing commented Jul 23, 2026

Copy link
Copy Markdown
Member

Fixes #131285

Problem

On WebAssembly R2R (crossgen), an async method with a try { ...; return; } catch { } where the try completes normally by branching out early (a return after an await) generates a valid wasm module that traps at runtime with RuntimeError: unreachable on the normal-completion path.

This is distinct from #131279 (dropped terminal endinvalid module) and is not a reopening of #129335 — here the module is well-formed and validates; it simply traps at runtime.

Root cause

In Compiler::fgWasmControlFlow(), a non-contiguous forward branch that exits a try/catch region started its enclosing wasm 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. The normal-completion branch then resolves to depth 0 and lands on the outer try's trailing validation unreachable (emitted right after its end by genEmitStartBlock) instead of the continuation.

Fix

Walk the enclosing try regions outward and start the Block at the outermost escaped catch-try's begin, so the Block encloses those trys' ends and the branch resolves to the continuation (br 0br 1). For a single-level try/catch the outermost escaped region is also the innermost, so behavior there is unchanged.

Test

Adds src/tests/JIT/Regression/JitBlue/Runtime_131285, a sync [Fact] that drives an async Task MoveNext through the normal-completion return path. The csproj sets <AlwaysUseCrossGen2 Condition="'$(TargetOS)' == 'browser'">true</AlwaysUseCrossGen2> so the browser leg is actually compiled to wasm R2R and run under node — without it a plain browser leg runs the test as ordinary JIT and silently false-passes.

Validation

  • Runtime A/B on the exact CLRTest node command: unfixed JIT traps (exit=1), fixed JIT passes (exit=100).
  • Composite-mode crossgen A/B: unfixed emits trapping br 0, fixed emits br 1.
  • 194-assembly sweep: 0 new invalid modules, 185 byte-identical; single-level try/catch provably unaffected.

Note

This pull request was generated with the assistance of GitHub Copilot.

lewing and others added 2 commits July 23, 2026 14:12
An async `try { ...; return; } catch {}` MoveNext compiled to WebAssembly
R2R (crossgen) produced a valid module that trapped at runtime
(RuntimeError: unreachable) on the normal-completion path.

In Compiler::fgWasmControlFlow(), a non-contiguous forward branch that
exits a try/catch region started its enclosing Block at the innermost
try's begin. When that branch also escapes an outer catch-try sharing the
same end cursor, the Block ended up nested inside the outer try, so the
`br` resolved to depth 0 and landed on the outer try's trailing
validation `unreachable` (emitted after its `end`) instead of the
continuation.

Walk the enclosing try regions outward and start 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
is unchanged there.

Adds a JitBlue regression test that forces the browser leg through
crossgen wasm R2R via AlwaysUseCrossGen2.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 964b4f25-1c41-44d6-81fb-a6a75ee7d3e4
Fills in the placeholder Runtime_NNNNN name now that the tracking issue
(#131285) is filed: renames the folder, the .cs/.csproj,
and the test class.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 964b4f25-1c41-44d6-81fb-a6a75ee7d3e4
Copilot AI review requested due to automatic review settings July 23, 2026 19:17
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Jul 23, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 6 pipeline(s).
10 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

Tightens the explanatory comment on the outermost-escaped-catch-try Block
placement to match the terser JIT comment style. No code change.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 964b4f25-1c41-44d6-81fb-a6a75ee7d3e4

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts wasm control-flow interval construction in fgWasmControlFlow() so forward branches that exit multiple enclosing try/catch regions create their enclosing Block interval starting at the outermost escaped catch-try’s try begin, avoiding mis-targeted branches that can land on the post-end validation unreachable and trap at runtime in wasm R2R async EH shapes.

Changes:

  • Update wasm interval selection logic to walk outward across enclosing EH regions and choose a safer Block start for non-contiguous forward branches that exit catch-try regions.
  • Add a wasm R2R-focused regression test that forces CrossGen2 on the browser leg to ensure the problematic codegen path is actually exercised.
  • Introduce a dedicated JitBlue regression test project for the repro.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/coreclr/jit/fgwasm.cpp Walks enclosing EH regions to pick the outermost escaped catch-try begin as the Block interval start for affected forward branches.
src/tests/JIT/Regression/JitBlue/Runtime_131285/Runtime_131285.csproj Forces CrossGen2 only on TargetOS=browser to ensure wasm R2R coverage for the regression.
src/tests/JIT/Regression/JitBlue/Runtime_131285/Runtime_131285.cs Adds an async try/return/catch repro driven synchronously to validate normal-completion behavior under wasm R2R.

Copilot AI review requested due to automatic review settings July 23, 2026 19:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 3 out of 3 changed files in this pull request and generated no new comments.

@lewing
lewing marked this pull request as draft July 23, 2026 19:35
@lewing
lewing marked this pull request as ready for review July 23, 2026 19:38
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 6 pipeline(s).
10 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

@AndyAyersMS AndyAyersMS left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's probably a more elegant way to do this, but this fix seems reasonable enough.

@lewing
lewing merged commit 304d1cc into main Jul 23, 2026
125 of 134 checks passed
@lewing
lewing deleted the lewing-bug-c-wasm-catch-resume-fix branch July 23, 2026 22:56
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-rc1 milestone Jul 24, 2026
@am11

am11 commented Jul 24, 2026

Copy link
Copy Markdown
Member
/__w/1/s/src/tests/JIT/Regression/JitBlue/Runtime_131285/Runtime_131285.csproj(7,59): error MSB4025: The project file could not be loaded. An XML comment cannot contain '--', and '-' cannot be the last character. Line 7, position 59. [/__w/1/s/src/tests/build.proj]
/__w/1/s/src/tests/JIT/Regression/JitBlue/Runtime_131285/Runtime_131285.csproj(7,59): error MSB4025: The project file could not be loaded. An XML comment cannot contain '--', and '-' cannot be the last character. Line 7, position 59. [/__w/1/s/src/tests/build.proj]
/__w/1/s/src/tests/Directory.Build.targets(508,5): error MSB4181: The "MSBuild" task returned false but did not log an error. [/__w/1/s/src/tests/JIT/Regression/Regression_2.csproj] [/__w/1/s/src/tests/build.proj]
/__w/1/s/src/tests/Common/dir.traversal.targets(25,5): error : (No message specified) [/__w/1/s/src/tests/build.proj] [/__w/1/s/src/tests/build.proj]
/__w/1/s/src/tests/build.proj(266,5): error MSB3073: The command ""/__w/1/s/dotnet.sh" msbuild /__w/1/s/src/tests/build.proj /t:Build "/p:TargetArchitecture=x64" "/p:Configuration=Checked" "/p:LibrariesConfiguration=Release" "/p:TasksConfiguration=Checked" "/p:TargetOS=AnyOS" "/p:ToolsOS=" "/p:PackageOS=" "/p:RuntimeFlavor=coreclr" "/p:RuntimeVariant=" "/p:CLRTestBuildAllTargets=allTargets" "/p:UseCodeFlowEnforcement=" "/p:__TestGroupToBuild=3" "/p:__SkipRestorePackages=1" /nodeReuse:false /maxcpucount "/p:UseMonoRuntime=false" /bl:/__w/1/s/artifacts//log/Checked/InnerManagedTestBuild.3.binlog "/p:CrossBuild=true" /p:ContinuousIntegrationBuild=true "/p:DevTeamProvisioning=-"" exited with code 1.
    6 Warning(s)
    5 Error(s)

<!-- The bug is wasm R2R (crossgen) codegen. On non-browser legs this runs as
ordinary JIT/interp and passes trivially (expected). On the browser leg,
AlwaysUseCrossGen2 forces crossgen (exports RunCrossGen2=1) so the test is
actually compiled to wasm R2R and run under node -- otherwise a plain

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
actually compiled to wasm R2R and run under node -- otherwise a plain
actually compiled to wasm R2R and run under node; otherwise a plain

jakobbotsch pushed a commit that referenced this pull request Jul 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[wasm] R2R codegen: async try/return/catch MoveNext traps at runtime (RuntimeError: unreachable) on normal-completion branch

4 participants