Skip to content

[wasm] Fix caller identification across R2R prestub transitions - #133707

Merged
lewing merged 1 commit into
dotnet:mainfrom
lewing:lewing-wasm-caller-stackwalk
Sep 11, 2026
Merged

lewing merged 1 commit into
dotnet:mainfrom
lewing:lewing-wasm-caller-stackwalk

Conversation

@lewing

@lewing lewing commented Sep 11, 2026

Copy link
Copy Markdown
Member

Fixes #133617.

Problem

The Wasm portable-entrypoint slow path retains a PrestubMethodFrame while invoking a resolved R2R body. Function-only stack walks can report both the real managed activation and that transition frame. MethodBase.GetCurrentMethod can therefore accept its own prestub frame as the caller.

A Checked-runtime capture confirmed this: the stack marker was 0x4fe71c; the real GetCurrentMethod body at SP 0x4fe710 did not pass the caller-marker comparison, but its explicit PrestubMethodFrame at SP 0x4fe720 did. The result was GetCurrentMethod instead of the calling method. This also reproduces without trimming.

#133610 addressed the duplicate in GetStackFramesCallback, but caller-sensitive reflection uses a different callback and did not benefit from that filter.

Change

  • Mark completed non-FCall Wasm prestub transitions as not representing an additional managed activation.
  • Filter those frames centrally for FUNCTIONSONLY walks, covering both ordinary and skipped explicit frames.
  • Retain the frame, MethodDesc, argument-root reporting, and physical unwind/exception lifetime. Popping early or clearing the MethodDesc would lose GC protection and the unwind bridge across interpreter-to-R2R transitions.
  • Replace [wasm] Fix duplicate R2R stack trace frames #133610's StackTrace-specific adjacency filter. Filtering by lifecycle rather than matching method identities preserves genuine recursion.
  • Extend the existing github60486 regression with direct/inlineable/generic caller identity, assembly lookup, recursive frame counts, and compacting-GC object/byref/struct argument coverage. Its original DIM frame-sequence and exception-stack checks remain intact.

Native FCalls retain their existing representation. Non-Wasm filtering behavior is unchanged.

Validation

CoreCLR runtime regression

Built browser-Wasm and macOS arm64 Checked runtimes with Release libraries. The expanded Loader/classloader/DefaultInterfaceMethods/regressions/github60486 passes with fixed Wasm R2R, the interpreter, and native arm64. The identical final regression assembly fails against the saved original Wasm runtime: expected ValidateCurrentMethod, actual GetCurrentMethod.

These fixed runs include the original regression checks from #133610, with its debugdebugger.cpp filter removed.

Original browser library lane

Validated the exact patch in an isolated checkout of #133656 at 282d12dfe220a1f34d0bb81a85239cab60c779ce, which supplies the browser R2R publishing/test prerequisites. Built matched original/fixed CoreCLR Release products locally and ran Chromium 153. Temporarily enabled only the nine #133617-quarantined methods, preserving every original assertion and unrelated exclusion; all annotations were restored afterward. This PR contains no library-test quarantine changes.

Coverage Original Fixed
Cold trimmed direct GetCurrentMethod 1 failed 1 passed
Cold trimmed inlineable GetCurrentMethod 1 failed 1 passed
Trimmed targeted batch 7 passed, 1 failed 8 passed
Untrimmed R2R targeted batch 8 passed, 1 failed 9 passed
Full trimmed System.Runtime 77,396 passed, 1 failed, 175 skipped 77,397 passed, 0 failed, 175 skipped

The affected-class fixed run also passed: 23 passed, 3 existing skips. CrossAssembly2 remains excluded when trimmed by dotnet/linker#2078 and passes untrimmed; it is not counted as a trimmed pass.

Main test IL and main R2R images are byte-identical across each original/fixed pair; skip sets are identical. Served assets were verified against their respective rebuilt packs. This is a matched-product A/B, not a native-module-only swap: CoreLib R2R image bytes also differ.

Existing reflection compatibility controls

System.Reflection.Tests caller-assembly theory, real cross-assembly delegate invocation, static-constructor caller lookup, executing-assembly lookup, and reflected first/second invocation all pass: six cases on each original/fixed, trimmed/untrimmed browser R2R configuration. Fresh-process delegate and reflective-invocation probes also pass in all four configurations. All existing annotations remained intact. These are compatibility controls, not failing-before reproductions.

Full browser reflection suites have identical original/fixed results:

  • Untrimmed: 1,750 passed, 1 failed, 23 skipped.
  • Trimmed: 1,747 passed, 2 failed, 23 skipped.

Existing failures are GetEntryAssembly (browser host expectation versus R2R expectation) and, trimmed only, AssemblyGetForwardedTypesLoadFailure (underlying trimming cause not isolated). Failure signatures and skip sets match before/after; neither was suppressed.

Native arm64 Checked reflection control: six focused cases passed; full suite 1,776 passed, 2 platform skips, zero failures.

Remaining scope

Draft for review of the frame-lifecycle distinction and FCall exception. Dedicated FCall cold/warm stack-identity coverage, a broader CoreCLR GC-stress campaign, and other browsers have not been run. No performance claim is made.

Note

This change and pull request description were developed with GitHub Copilot assistance.

Exclude completed non-FCall prestub activations from function-only stack walks while retaining their GC roots and physical unwind state. Replace the StackTrace-specific duplicate filter and extend caller, recursion, and GC regression coverage.

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

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

@lewing
lewing requested a lite review from Copilot September 11, 2026 17:42
@lewing
lewing marked this pull request as ready for review September 11, 2026 17:43
@lewing lewing added the arch-wasm WebAssembly architecture label Sep 11, 2026
@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.

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

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

Add cold/warm caller-identity or stack-trace coverage for native and managed FCall cases.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Review tier: Lite
Findings: 1 Medium severity

New issues introduced by this change (1)
Severity Finding
Medium severity src/​coreclr/​vm/​prestub.cpp — Cover the FCall prestub exception View comment
What changed in this PR

Fixes Wasm CoreCLR caller identification across retained R2R prestub transitions.

Changes:

  • Marks completed non-FCall prestub transitions as non-managed activations.
  • Filters these frames from function-only stack walks.
  • Expands regression coverage for caller identity, recursion, exceptions, and GC-sensitive arguments.
  • Removes the obsolete debugger-specific duplicate-frame filter.

Review note: FCall-specific lifecycle behavior lacks direct regression coverage.

File Summary
src/​tests/​Loader/​classloader/​DefaultInterfaceMethods/​regressions/​github60486.cs Expands caller-identity and GC-root regression coverage.
src/​coreclr/​vm/​stackwalk.cpp Centrally filters completed transition frames.
src/​coreclr/​vm/​prestub.cpp Marks completed non-FCall Wasm prestub transitions.
src/​coreclr/​vm/​frames.h Adds prestub lifecycle state and activation attributes.
src/​coreclr/​vm/​debugdebugger.cpp Removes the obsolete adjacency-based filter.

Comment thread src/coreclr/vm/prestub.cpp
@lewing

lewing commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

/ba-g failures are #133747

@lewing
lewing merged commit a7fda7e into dotnet:main Sep 11, 2026
124 of 126 checks passed
Comment thread src/coreclr/vm/prestub.cpp
Comment thread src/coreclr/vm/prestub.cpp
lewing added a commit to lewing/runtime that referenced this pull request Sep 12, 2026
Integrate main at a7fda7e, including dotnet#133707, without rewriting the draft branch history.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing added a commit to lewing/runtime that referenced this pull request Sep 12, 2026
Remove the nine dotnet#133617 ActiveIssue annotations now that dotnet#133707 has landed. Preserve every original GetCurrentMethod call and assertion and retain unrelated platform and trimming exclusions.

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 Sep 14, 2026
lewing pushed a commit that referenced this pull request Sep 14, 2026
## Summary

This fixes #133798

Fix the two AOT failures exposed by the expanded `github60486`
regression in #133707 and observed in CI for
#133774.

- Preserve callers of the stack-walk implementation of
`MethodBase.GetCurrentMethod` during Mono LLVM inlining. The LLVM-only
intrinsic already prevents inlining; the stack-walk path needs the same
protection. The test's inlineable helper remains unannotated.
- Allow unrelated NativeAOT stack frames to lack reflection metadata. A
local diagnostic run identified `TestEntryPoint` as the frame returning
null from `GetMethod`; all four recursive frames returned valid method
metadata. Keep the exact four-frame assertion and all existing DIM,
exception-stack, caller-identity, and GC-root checks.

Related caller-inlining report: #60334. Its iOS
configuration was not tested here.

## Validation

Built Checked CoreCLR/NativeAOT and Release Mono with LLVM from a clean
baseline in the same worktree.

| Windows x64 configuration | Before | After |
|---|---|---|
| NativeAOT Checked | Reported null-reference failure | Pass |
| Mono LLVM AOT Release | Expected `GetCurrentMethodInlineable`, actual
`ValidateCurrentMethod` | Pass |
| CoreCLR Checked | Pass | Pass |
| Mono MiniJIT Release | Pass | Pass |

Each run executes the full standalone `github60486` regression. Mono
LLVM AOT comparisons use identical test IL, and runtime logging confirms
the fixed AOT image is loaded. LLVM IR adds `noinline` to the inlineable
and generic wrappers; the optimized caller retains the helper call
instead of inlining it. No performance claim is made.

The Windows Mono LLVM AOT reproduction matches the Linux CI failure
signature. Linux and browser Wasm were not run locally. The Windows x64
JIT formatting check completed successfully.

> [!NOTE]
> This change and pull request description were developed with GitHub
Copilot assistance.

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

Follow up on review feedback received after #133707 merged:

- Mark every completed portable prestub transition as no longer
representing an additional managed activation, including FCalls.
- Scope the lifecycle state and function-only stack-walk filtering to
`FEATURE_PORTABLE_ENTRYPOINTS` instead of `TARGET_WASM`.
- Add cold/warm coverage for a string constructor backed by a managed
FCall. The test verifies that the first invocation does not expose an
extra constructor frame that disappears after the entrypoint is
published.

The prestub frame remains active for argument rooting and physical
unwinding while the resolved R2R body runs.

## Validation

Built browser-wasm CoreCLR Checked with Release libraries.

The focused `github60486` regression was compiled to ReadyToRun and run
against identical test output:

| Runtime | Cold constructor frames | Warm constructor frames | Result |
|---|---:|---:|---|
| Before | 1 | 0 | Failed |
| After | 0 | 0 | Passed |

The full regression also passes with `DOTNET_ReadyToRun=0`, and a native
SDK-runtime smoke run passes.

This intentionally does not address the separate Mono
aggressive-inlining concern raised on #133775; that requires validation
in both MiniJIT and LLVM configurations.

> [!NOTE]
> This change and pull request description were developed with GitHub
Copilot assistance.

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing added a commit to lewing/runtime that referenced this pull request Sep 17, 2026
Remove the nine dotnet#133617 ActiveIssue annotations now that dotnet#133707 has landed. Preserve every original GetCurrentMethod call and assertion and retain unrelated platform and trimming exclusions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Sep 18, 2026
)

## Summary

This fixes dotnet#133798

Fix the two AOT failures exposed by the expanded `github60486`
regression in dotnet#133707 and observed in CI for
dotnet#133774.

- Preserve callers of the stack-walk implementation of
`MethodBase.GetCurrentMethod` during Mono LLVM inlining. The LLVM-only
intrinsic already prevents inlining; the stack-walk path needs the same
protection. The test's inlineable helper remains unannotated.
- Allow unrelated NativeAOT stack frames to lack reflection metadata. A
local diagnostic run identified `TestEntryPoint` as the frame returning
null from `GetMethod`; all four recursive frames returned valid method
metadata. Keep the exact four-frame assertion and all existing DIM,
exception-stack, caller-identity, and GC-root checks.

Related caller-inlining report: dotnet#60334. Its iOS
configuration was not tested here.

## Validation

Built Checked CoreCLR/NativeAOT and Release Mono with LLVM from a clean
baseline in the same worktree.

| Windows x64 configuration | Before | After |
|---|---|---|
| NativeAOT Checked | Reported null-reference failure | Pass |
| Mono LLVM AOT Release | Expected `GetCurrentMethodInlineable`, actual
`ValidateCurrentMethod` | Pass |
| CoreCLR Checked | Pass | Pass |
| Mono MiniJIT Release | Pass | Pass |

Each run executes the full standalone `github60486` regression. Mono
LLVM AOT comparisons use identical test IL, and runtime logging confirms
the fixed AOT image is loaded. LLVM IR adds `noinline` to the inlineable
and generic wrappers; the optimized caller retains the helper call
instead of inlining it. No performance claim is made.

The Windows Mono LLVM AOT reproduction matches the Linux CI failure
signature. Linux and browser Wasm were not run locally. The Windows x64
JIT formatting check completed successfully.

> [!NOTE]
> This change and pull request description were developed with GitHub
Copilot assistance.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
jtschuster pushed a commit to jtschuster/runtime that referenced this pull request Sep 18, 2026
## Summary

Follow up on review feedback received after dotnet#133707 merged:

- Mark every completed portable prestub transition as no longer
representing an additional managed activation, including FCalls.
- Scope the lifecycle state and function-only stack-walk filtering to
`FEATURE_PORTABLE_ENTRYPOINTS` instead of `TARGET_WASM`.
- Add cold/warm coverage for a string constructor backed by a managed
FCall. The test verifies that the first invocation does not expose an
extra constructor frame that disappears after the entrypoint is
published.

The prestub frame remains active for argument rooting and physical
unwinding while the resolved R2R body runs.

## Validation

Built browser-wasm CoreCLR Checked with Release libraries.

The focused `github60486` regression was compiled to ReadyToRun and run
against identical test output:

| Runtime | Cold constructor frames | Warm constructor frames | Result |
|---|---:|---:|---|
| Before | 1 | 0 | Failed |
| After | 0 | 0 | Passed |

The full regression also passes with `DOTNET_ReadyToRun=0`, and a native
SDK-runtime smoke run passes.

This intentionally does not address the separate Mono
aggressive-inlining concern raised on dotnet#133775; that requires validation
in both MiniJIT and LLVM configurations.

> [!NOTE]
> This change and pull request description were developed with GitHub
Copilot assistance.

---------

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][CoreCLR][R2R] MethodBase.GetCurrentMethod returns itself instead of its caller

4 participants