Skip to content

JIT: make Wasm frames that call a finally unwindable - #134979

Merged
lewing merged 3 commits into
mainfrom
lewing-wasm-r2r-finally-throw-eh
Oct 1, 2026
Merged

lewing merged 3 commits into
mainfrom
lewing-wasm-r2r-finally-throw-eh

Conversation

@lewing

@lewing lewing commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

On Wasm, genCallFinally calls the finally funclet but did not mark the calling function as needing an unwindable frame. genCallInstruction and genEmitHelperCall both call ensureCurrentFuncIsUnwindable(); genCallFinally was the only call emitter that did not.

When the callfinally is a method's only call, the prolog never stores the R2R function table index at $fp[0]. If the finally throws, unwinding out of the funclet reads a stale value from that slot. GetWasmVirtualIPFromFunctionTableIndex returns 0, the walk treats the caller as non-R2R, and the exception is reported as unhandled instead of reaching the caller's catch. A GC stack walk while the finally runs goes through the same broken frame.

This hits b51875, where the finally never returns. It also hits a finally that returns but is not cloned onto the normal path, for example under MinOpts.

Change

genCallFinally now calls ensureCurrentFuncIsUnwindable(). The prolog is generated after block codegen, so the flag takes effect. fgWasmVirtualIP already creates the function-index local for any method with a BBJ_CALLFINALLY. Funclets that call a finally were already unwindable.

No new test: b51875 and the JIT/Methodical runners already cover this under browser-wasm R2R (see below).

Validation

browser-wasm, Checked, src/tests/run.sh wasm checked --runcrossgen2tests, comparing the JIT with and without this fix (the JIT sources otherwise match):

Tests Without fix With fix
--tree=JIT/Regression --runner-filter=Regression_8 (b51875) Unhandled exception. System.Exception, runner aborts 13/13 pass, including b51875.AA.TestEntryPoint
--tree=JIT/Methodical (6 runners) All 6 runners crash within their first 4 tests (corrupted state after the failed unwind) All 6 complete: 1968 total, 1924 passed, 22 failed, 22 skipped

The 22 JIT/Methodical failures are out-of-process explicit/* tests (refloc_*, refarg_box_f8, rotate_u2), which fail on an assertion in the out-of-process runner. There is no baseline for them because every runner crashed before reaching them without the fix. This change only affects codegen for calls to a finally.

JIT disassembly confirms the main method prolog now stores the function index.

Not run: Regression_PdbOnly_r_2, which contains the other copy of b51875.

Resolves #134975

Note

This PR description was generated with GitHub Copilot.

genCallFinally emits a call to the finally funclet but did not mark the
calling function as needing an unwindable frame. When the callfinally was
the method's only call, the prolog never stored the function table index
at $fp[0]. An exception thrown from the finally then unwound into a frame
with a stale index, the runtime saw no R2R caller, and the exception was
reported as unhandled instead of reaching the caller's catch.

Fixes b51875 under browser-wasm R2R. Also affects returning finallys that
are not cloned (for example in MinOpts).

Fixes #134975

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@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 Sep 30, 2026
@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.

@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.

@lewing

lewing commented Sep 30, 2026

Copy link
Copy Markdown
Member Author

cc @dotnet/wasm-contrib

@lewing lewing added the arch-wasm WebAssembly architecture label Sep 30, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

b51875 and the JIT/Methodical tests already cover this under Wasm R2R.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing
lewing requested a review from jkotas September 30, 2026 20:51
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing
lewing requested a review from steveisok September 30, 2026 20:53
@lewing
lewing enabled auto-merge (squash) September 30, 2026 21:49
@lewing
lewing merged commit 9997fdf into main Oct 1, 2026
136 of 138 checks passed
@lewing
lewing deleted the lewing-wasm-r2r-finally-throw-eh branch October 1, 2026 03:27
lewing added a commit that referenced this pull request Oct 1, 2026
These JIT regression tests were quarantined for wasm ReadyToRun in
#134960 against #134975 (an exception thrown from a `finally` was
reported as unhandled instead of reaching the caller's `catch`). #134979
("JIT: make Wasm frames that call a finally unwindable") fixed that and
closed #134975, so this removes the three `ActiveIssue` attributes:

- `JIT/Regression/CLR-x86-JIT/V1-M12-Beta2/b51875/b51875.cs` (runner
`Regression_8`)
- `JIT/Regression/CLR-x86-JIT/V1-M12-Beta2/b51875/Desktop/b51875.cs`
(runner `Regression_PdbOnly_r_2`)
- `JIT/Regression/CLR-x86-JIT/V1-M12-Beta2/b77707/b77707.cs` (runner
`Regression_PdbOnly_r_2`)

## Local validation (macOS arm64, branch includes 9997fdf)

```bash
./build.sh -s clr+libs -os browser -c checked -lc Release
src/tests/build.sh checked -arch wasm -os browser -priority1 -p:HostConfiguration=Release \
  -test:JIT/Regression/Regression_8.csproj -test:JIT/Regression/Regression_PdbOnly_r_2.csproj
src/tests/build.sh copynativeonly checked -arch wasm -os browser -priority1 -p:HostConfiguration=Release
src/tests/build.sh generatelayoutonly crossgen2 checked -arch wasm -os browser -priority1 -p:HostConfiguration=Release
# per runner, with CORE_ROOT set and DOTNET_TieredCompilation=0:
RunCrossGen2=1 bash ./<Runner>.sh   # R2R mode
bash ./<Runner>.sh                  # interpreter mode
```

All builds passed. Results from `<Runner>.testResults.xml`:

| Runner | Mode | b51875 | b77707 | Pass / Fail / Skip |
|---|---|---|---|---|
| Regression_8 | R2R (crossgen2) | Pass | n/a | 13 / 0 / 0 |
| Regression_8 | Interpreter | Pass | n/a | 13 / 0 / 0 |
| Regression_PdbOnly_r_2 | R2R (crossgen2) | Pass | Pass | 122 / 0 / 1 |
| Regression_PdbOnly_r_2 | Interpreter | Pass | Pass | 122 / 0 / 1 |

`runtime-coreclr outerloop` has no PR trigger, so it has to be started
manually with `/azp run runtime-coreclr outerloop`.

> [!NOTE]
> This PR was generated with GitHub Copilot assistance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture 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] Exception thrown from a finally reached by leave is reported as unhandled instead of reaching the caller's catch (b51875, b77707)

4 participants