Skip to content

Fix Wasm R2R exceptions escaping filters - #133932

Merged
lewing merged 10 commits into
dotnet:mainfrom
lewing:lewing-fix-issue-133468
Sep 17, 2026
Merged

lewing merged 10 commits into
dotnet:mainfrom
lewing:lewing-fix-issue-133468

Conversation

@lewing

@lewing lewing commented Sep 15, 2026 •

Copy link
Copy Markdown
Member

Summary

  • fix WASM ReadyToRun filter-funclet classification by mapping the executable filter entry to its containing funclet
  • use the existing cross-platform native-marker path to propagate exceptions thrown from filters
  • preserve executable FilterOffset values and the existing collided-unwind fail-fast invariant
  • re-enable all three runtime tests quarantined under [wasm][R2R] b68872 collides with first-pass ExInfo #133468

Testing

  • ./build.sh clr+libs+host
  • ./build.sh clr
  • ./build.sh clr -os browser -arch wasm -c Checked /p:HostConfiguration=Release
  • browser WASM R2R b68872: exit 100
  • browser WASM R2R throwinfilter_d: exit 100
  • browser WASM R2R GitHub_4044: exit 100
  • browser WASM R2R Runtime_88168: exit 100
  • browser WASM R2R GitHub_22820: exit 100
  • browser WASM diagnostic-ip-stacktrace, with and without a test R2R component: exit 100
  • native osx-arm64 Debug b68872: exit 100
  • native collided-unwind regression GitHub_121578: passed

Resolves #133468

Note

This pull request description was authored with GitHub Copilot.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 15, 2026 06:07
@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 15, 2026
@lewing
lewing requested a review from janvorli September 15, 2026 06:08
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 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 added the arch-wasm WebAssembly architecture label Sep 15, 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.

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

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.

Copilot review overview

🟡 Changes recommended

The exception-handling condition must be restricted to filter funclets to preserve collided-unwind fail-fast behavior.

Get a fresh assessment by requesting another Copilot review.

Review tier: Lite
Findings: 1 High severity

Open (1)
What changed in this PR

Fixes WASM ReadyToRun exception-filter propagation and re-enables three quarantined tests.

Changes:

  • Adds WASM R2R filter-boundary handling.
  • Removes obsolete quarantines and an unused test dependency.
File Description
src/​tests/​JIT/​Regression/​CLR-x86-JIT/​V1-M12-Beta2/​b68872/​b68872.ilproj Removes an obsolete test-library reference.
src/​tests/​JIT/​Regression/​CLR-x86-JIT/​V1-M12-Beta2/​b68872/​b68872.il Removes WASM R2R quarantine metadata.
src/​tests/​JIT/​Regression_3/​GitHub_4044/​GitHub_4044.cs Removes the WASM R2R quarantine.
src/​tests/​JIT/​Methodical/​eh/​basics/​throwinfilter.il Removes the WASM R2R quarantine.
src/​coreclr/​vm/​exceptionhandling.cpp Adjusts WASM R2R filter-boundary exception handling.

Comment thread src/coreclr/vm/exceptionhandling.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 15, 2026 06:54

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.

Copilot review overview

🔵 Needs a closer look

The exception-handling changes require final human review.

Review tier: Lite
Findings: None

Resolved since last review (1)

@AndyAyersMS AndyAyersMS added area-VM-coreclr and removed area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI labels Sep 15, 2026
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

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

Comment thread src/coreclr/vm/exceptionhandling.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 15, 2026 15:26
@lewing lewing changed the title Fix WASM R2R exceptions escaping filters Fix Wasm R2R exceptions escaping filters Sep 15, 2026

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.

Copilot review overview

🔵 Needs a closer look

The cDAC WASM filter-offset handling remains inconsistent and needs to be fixed with coverage.

Review tier: Lite
Findings: None

@janvorli

Copy link
Copy Markdown
Member

The existing check failed because ReadyToRunJitManager::IsFilterFunclet fell back to offset arithmetic on WASM: the R2R funclet starts at relative offset 0xc, while the EH filter entry is at 0xe after the synthetic funclet prolog, so it returned false.

I wonder if this offset discrepancy isn't actually what should be fixed. Why are those two offsets different?

@lewing

lewing commented Sep 15, 2026

Copy link
Copy Markdown
Member Author

I think that is the design? cc @AndyAyersMS @davidwrighton

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing and others added 2 commits September 16, 2026 11:42
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 16, 2026 16:58
@lewing

lewing commented Sep 16, 2026 •

Copy link
Copy Markdown
Member Author

@AndyAyersMS @janvorli the offset change is restored and the GC change fixes the test that regressed as a result. I am not sure if there is a better approach there too?

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.

Copilot review overview

🔵 Needs a closer look

CoreCLR exception handling and WASM R2R changes warrant final human review.

Review effort: Lite
Findings: None

@AndyAyersMS

Copy link
Copy Markdown
Member

That change seems a bit odd too, I don't think the filter funclet parent frame should ever be at a non-interruptible location (they should all be sitting at a call).

Maybe there is something else inhibiting parent frame reporting?

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 16, 2026 20:03
@lewing

lewing commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

You are right that conceptually the filter parent should be sitting at a call. I traced why it is still treated as non-reportable on Wasm.

The recovered parent context is the original exception context and carries ExecutionAborted. For partially interruptible GC info, the decoder intentionally ignores ordinary safe-point live states for an aborted frame and will only report from an interruptible range. More importantly, the current Wasm JIT does not emit either form for this method:

NumSafePoints: 0
NumInterruptibleRanges: 0
NumUntracked: 5

This is not just the usual optimization that drops a boring call site late in encoding. The Wasm emitter does not call emitRecordGCcall, so there is no call descriptor to retain. This matches the current Wasm GC-info model from #126932 and #129100: tracked lifetimes/call-site GC states were deferred, all stack GC variables are emitted as zero-initialized untracked slots, and no tracked GC slots are encoded.

I mutation-tested two deeper alternatives:

  1. Clearing ExecutionAborted for the known filter parent makes the failing merged runners pass, and the parent reports the expected exception and disposable-object slots. But it cannot be made safe generally because hardware-fault filters may have a genuinely non-safe parent PC.
  2. Retaining otherwise-empty call sites in gcencode.cpp had no effect: the emitted method still had zero safe points because Wasm never creates the call descriptors upstream.

So the current scoped behavior is intentional: for an R2R filter, report the always-live, zero-initialized untracked slots from the filter frame rather than suppressing them in expectation that the aborted parent will report. Implementing the normal parent-reporting model would require broader Wasm GC-info work to emit precise call-site metadata.

The exact A/B matrix was:

  • filter classification + standard suppression: Runtime_88168, GitHub_22820, and nested Methodical filter runners fail with missing-root corruption
  • classification disabled: those pass, but throwinfilter hits the original first-pass collision assertion
  • approved fgwasm classification + filter untracked reporting: all exact merged runners, focused filter tests, async R2R/non-R2R, and native collided-unwind regression pass

I updated the code comment to describe the missing call-site GC metadata rather than merely saying the parent is non-interruptible.

Note

This response was authored with GitHub Copilot.

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.

Copilot review overview

🔵 Needs a closer look

Low-level WASM JIT, exception-unwind, and GC handling changes warrant final human review.

Review effort: Lite
Findings: None

@AndyAyersMS

AndyAyersMS commented Sep 16, 2026 •

Copy link
Copy Markdown
Member

an R2R filter, report the always-live, zero-initialized untracked slots from the filter frame rather than suppressing them in expectation that the aborted parent will report.

Ok, that makes more sense. Since Wasm only has untracked lifetimes there is no per-call site info to report. I suppose we could still report all the call sites to make the main method look like it is in fact interruptible (not sure how much this costs). We have the same (only untracked lifetime) setup for Tier0 and perhaps we've avoided this problem there by reporting call sites.

Also note the filter proper doesn't have any untracked GC slots to report, they are all shared with the parent frame (which is why duplication is possible).

@AndyAyersMS

Copy link
Copy Markdown
Member

Ah apparently for native targets we force methods with EH to be fully interruptible, and hence the parent frame does the untracked reporting and we suppress reporting in the filter.

But for Wasm we don't do this forcing, fully interruptible makes no sense... so then the parent doesn't report, and we have to report in the filter.

So from what I can tell your fix looks good.

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

LGTM w/comment nit...

Comment thread src/coreclr/vm/eetwain.cpp Outdated
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 16, 2026 22:17
@lewing

lewing commented Sep 16, 2026

Copy link
Copy Markdown
Member Author

@AndyAyersMS comment fixed, please reapprove when you have a chance

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.

Copilot review overview

🔵 Needs a closer look

WASM exception handling and runtime GC reporting changes warrant final human review.

Review effort: Lite
Findings: None

@lewing
lewing merged commit d642f9c into dotnet:main Sep 17, 2026
140 of 143 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 12.0-preview1 milestone Sep 18, 2026
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Sep 18, 2026
## Summary

- recognize the implicit WASM R2R boundary between a filter funclet and
its VM caller
- propagate exceptions thrown by filters back to the caller that applies
filter exception semantics
- preserve the existing collided-unwind fail-fast invariant for
non-WASM-R2R cases
- re-enable all three runtime tests quarantined under dotnet#133468

## Testing

- `./build.sh clr+libs+host`
- `./build.sh clr`
- `./build.sh clr -os browser -arch wasm -c Checked
/p:HostConfiguration=Release`
- browser WASM R2R `b68872`: exit 100
- browser WASM R2R `throwinfilter_d`: exit 100
- browser WASM R2R `GitHub_4044`: exit 100
- native osx-arm64 Debug `b68872`: exit 100
- native collided-unwind regression `GitHub_121578`: passed

Resolves dotnet#133468

> [!NOTE]
> This pull request description was authored with GitHub Copilot.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing added a commit that referenced this pull request Sep 26, 2026
## Summary

Fix cDAC live resolution of WebAssembly ReadyToRun virtual IPs by
matching the runtime's existing lookup model:

- expose `ExecutionManager::s_pVirtualIPRangeList` and
`VirtualIPRangeSection` through the data descriptor;
- resolve encoded virtual IPs through that intrusive list using cycle
detection and a 65,536-node per-lookup reader resource budget, with no
map fallback when an encoded VIP is absent;
- mask the WebAssembly funclet flag from `RUNTIME_FUNCTION.BeginAddress`
for ordering and address arithmetic while preserving funclet identity;
- keep the virtual code base (`MinVirtualIP`) separate from the
loaded-image base used for unwind, debug, GC, exception, and thunk RVA
reads;
- handle the actual WASM descriptor shape, where hot/cold metadata and
delay-load thunk metadata are absent;
- classify WASM filter funclets by mapping the executable filter entry
to its containing runtime function.

## Root cause

The model added in #130988 was already false when that PR merged. On
`TARGET_WASM`, ReadyToRun modules are not added to `RangeSectionMap`;
`ReadyToRunInfo::RegisterVirtualIPRange` registers them in
`ExecutionManager::s_pVirtualIPRangeList`, and native `FindCodeRange`
checks that list first.

The prior unit test synthesized a `RangeSectionMap` entry with an
address that did not satisfy native `IsVirtualIP`, so it validated a
mock-only model rather than the live runtime layout. This is a
test-model gap, not a reviewer fault. The prior review explicitly noted
that the WebAssembly specifics had not been run locally and should be
added to cDAC CI:
#130988 (review).

## Blast radius and scope

This affects ReadyToRun code on all CoreCLR WebAssembly hosts, including
browser and WASI. Interpreter code is unaffected.

The list lookup, descriptor feature gating, funclet masking, and
image-base separation are inseparable: exposing the list alone would
still throw while reading absent WASM fields, or could return the wrong
method or read RVA data from the synthetic virtual address space.

This PR is independent of #133086 and intentionally excludes variable
producer/decoder work. #133890 depends on this PR for correct shared
code lookup and function identity.

On WASM, `FilterOffset` is the executable filter entry and can follow a
synthetic funclet prolog. cDAC now mirrors the corrected native
classification in #133932 by resolving that entry to its containing
runtime function before comparing funclet starts. The PRs remain
independent; #133917 does not depend on changing the producer offset.

## Validation

- `./build.sh clr+libs+host`
- `PATH="/opt/homebrew/bin:$PATH" ./build.sh -os browser -c Debug
-subset clr+libs`
- cDAC UnitTests: **3162 passed**
- cDAC DataGeneratorTests: **46 passed**
- cDAC UsageTests: **4 passed**
- generated contract documentation check: **up to date**
- focused ExecutionManager / RuntimeFunction / WasmR2R tests: **221
passed**

The durable tests cover:

- captured/live-shaped VIP `0x80010109`, exact `MethodDesc`, module, and
runtime-function index;
- the actual WASM descriptor shape: 8-byte `RUNTIME_FUNCTION` records
with no `EndAddress`, and absent hot/cold and delay-load thunk fields;
- start/end boundaries and adjacent ranges;
- encoded VIP absent from the list with no `RangeSectionMap` fallback;
- self-cycle, two-node cycle, inverted range, null module, and
overlapping ambiguity;
- unrelated partially registered nodes not blocking initialized ranges,
while an uninitialized candidate fails closed;
- valid 1,024/1,025-node lists, exact 65,536-node budget success, and
budget+1 fail-closed behavior even when the head matches, with a read
counter proving the extra node is never dereferenced;
- root/funclet resolution with a flagged funclet entry that breaks raw
ordering;
- exact loaded-image debug, unwind, GC, and exception-clause reads while
entrypoint lookup uses `MinVirtualIP`;
- filter-funclet classification where `FilterOffset` follows the flagged
funclet start but resolves to the same containing runtime function;
- missing list capability and unchanged ordinary architecture behavior.

Mutation proofs were applied, confirmed in source, run red, restored,
and rerun green:

1. Removing the VIP-list branch fails the captured `0x80010109` test at
the exact code-block assertion.
2. Using raw `BeginAddress` fails the funclet identity test.
3. Using `startVIP` as the loaded-image base fails the GC/unwind test
with a read at `0x80010081` instead of the loaded image.
4. Raising the reader budget from 65,536 to 65,537 makes the budget+1
test fail at its read-boundary assertion (highest node index 65,536
instead of 65,535); restoring the budget returns the suite to green.
5. Replacing WASM filter-entry containing-function resolution with raw
`FilterOffset == funcletStartOffset` comparison makes the filter
regression fail with expected `true` and actual `false`.

The finite list cutoff is an intentional diagnostic-reader resource
policy, not a native registration limit or a claim that an over-budget
list is corrupt.

> [!NOTE]
> This pull request description was generated with GitHub Copilot.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-VM-coreclr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[wasm][R2R] b68872 collides with first-pass ExInfo

4 participants