Skip to content

Fix missing transition adapters for shared Wasm R2R unboxing stubs - #133516

Merged
lewing merged 3 commits into
dotnet:mainfrom
lewing:lewing-investigate-issue-133491
Sep 10, 2026
Merged

lewing merged 3 commits into
dotnet:mainfrom
lewing:lewing-investigate-issue-133491

Conversation

@lewing

@lewing lewing commented Sep 9, 2026 •

Copy link
Copy Markdown
Member

Fixes browser-Wasm CoreCLR ReadyToRun failures introduced after #133218 while preserving shared U, UG, and UM unboxing stubs and almost all of their binary-size benefit.

Corrected root-cause analysis

The initial version of this PR replaced U/UG with per-method managed thunks based on an ABI-mismatch hypothesis. That approach and its portable-prestub changes have been removed. The revised patch does not change the shared-stub calling conventions or lookup keys.

NESM inspection stopped immediately before the failing Test23 call_indirect: the call expected five i32 parameters, but the selected table slot was zero (a null function reference). The expected parameter count alone did not establish a U versus UG mismatch. The observed delay-load fixup resolved to a non-unboxing shared target requiring a generic context, whose portable entrypoint lacked an R2R-to-interpreter adapter such as IvTiS1p.

Replacing a compiled per-method unboxing thunk with a structural assembly stub lost dependencies previously introduced by compiling the thunk's managed body. There are two distinct transitions:

  • Interpreter -> unboxing stub: needs an M adapter for the full managed unboxing signature, for example MS56Tp.
  • Unboxing stub -> target: needs an I adapter for the target signature, including its generic context where applicable, for example IS56Tip or IvTiS1p, so its portable entrypoint is callable before the target's native body is published.

Structural sharing also allows the runtime to find a shared stub for a generic instantiation Crossgen2 never compiled. A matching Wasm function shape does not imply that the exact managed transition cookie exists: different struct sizes can share the same structural stub but require different interpreter adapters. The actual GitHub_19361 execution exposed this case with a missing MS56Tp cookie.

Changes

  • Add a per-target dependency node retaining the shared stub, compiled target, interpreter-to-R2R adapter for the unboxing signature, and R2R-to-interpreter adapter for the target signature.
  • Keep these dependencies per managed target rather than on the structurally shared node, so signatures with the same Wasm shape but different managed layouts retain their distinct adapters.
  • Before publishing a shared unboxing stub, require the exact incoming interpreter cookie and callable code in the target portable entrypoint. An installed R2R-to-interpreter adapter counts as callable code; this does not require a precompiled native target body.
  • If either transition is unavailable, use the existing interpreted IL-stub fallback. R2R callers independently root their call-signature I adapters through the existing call-site recording machinery.
  • Extend the existing Wasm generic-dispatch tests with a 56-byte struct return alongside the 16-byte case, checking distinct adapters while retaining the original shared-stub assertions.

Evidence and validation

Browser-Wasm Release CoreCLR under Node.js 26.4.0, with explicitly regenerated and Wasm-validated CoreLib, LINQ, and test images:

Runtime case Actual execution evidence Result
LoaderClassloaderGenerics, DisplayName~genrecur.dll Test23 OK, matching Passed test line Exit 100
Regressions, DisplayName~genrecur.dll Test23 OK, matching Passed test line Exit 100
Regression_NoOptimize_r_1, Repro.Program.TestEntryPoint Starting stress loop, Result: Completed Normally, matching Passed test line Exit 100

The earlier GitHub_19361 name filter matched no test; earlier success claims using that filter were invalid. The results above use the actual fully qualified method name and confirm that the stress loop executed. Earlier stale CoreLib images were also replaced explicitly rather than relying on layout generation to refresh them.

Additional validation:

  • Browser Release runtime and corerun build: succeeded.
  • Wasm ILCompiler.ReadyToRun.Tests: 75 passed, 37 target-inapplicable skips.
  • Host macOS arm64 generic-dispatch tests: 3 passed, 2 Wasm-only skips.
  • Negative dependency checks: removing only I(target) fails on missing IS16Tip; removing only M(unboxing) fails on missing MS56Tp. Restoring both passes.

The full CI outer-loop matrix and a browser-hosted run have not been rerun locally.

Binary size

Optimized browser System.Private.CoreLib images using --optimize --generate-unboxing-stubs in the local comparison:

Variant Bytes
Earlier fully shared prototype 26,991,640
Revised shared stubs with transition dependencies 26,993,234
Superseded per-method U/UG fallback 27,387,567

The revised image adds 1,594 bytes over the earlier fully shared prototype and saves 394,333 bytes compared with the superseded fallback, retaining approximately 99.6% of the size reduction in that comparison. This supersedes the initial description's claim that the fix gives back nearly the entire saving.

Fixes #133491

Note

This pull request description and investigation summary were generated with GitHub Copilot.

Restore managed U and UG unboxing thunks where interface and implementation ABIs differ, retain shared UM stubs, and publish precompiled unboxing code through portable entrypoints.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing
lewing requested a review from davidwrighton September 9, 2026 18:22
@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 commented Sep 9, 2026 •

Copy link
Copy Markdown
Member Author

This is obviously not ideal and the value over reverting the original pr is questionable. I'll try to fix the sharing model to fit the abi in the meantime

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/crossgen-contrib
See info in area-owners.md if you want to be subscribed.

@lewing lewing mentioned this pull request Sep 9, 2026
6 tasks
Root the target R2R-to-interpreter adapter per managed signature. Keep shared U, UG, and UM stubs, but use the existing interpreted fallback when an exact incoming cookie or callable target adapter is unavailable. Remove the superseded managed-thunk fallback and cover both adapter dependencies.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@lewing lewing changed the title Fix browser Wasm R2R unboxing stub ABI handling Fix missing transition adapters for shared Wasm R2R unboxing stubs Sep 9, 2026
@lewing

lewing commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

This is obviously not ideal and the value over reverting the original pr is questionable. I'll try to fix the sharing model to fit the abi in the meantime

particular objection is no longer true

@lewing
lewing marked this pull request as ready for review September 9, 2026 21:47
Copilot AI lite review requested due to automatic review settings September 9, 2026 21:47
Resolve GetUnboxingStub overlap with dotnet#133461 by retaining its STANDARD_VM_CONTRACT and void-pointer lookup while returning the target entrypoint checked by the adapter guard.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

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

It changes CoreCLR VM behavior and R2R dependency rooting in a Wasm-specific path where subtle signature/entrypoint invariants can regress without a full human review and broader validation.

Review tier: Lite
Findings: None

What changed in this PR

This PR addresses browser-Wasm CoreCLR ReadyToRun failures involving shared unboxing stubs by ensuring the runtime only uses a shared unboxing stub when the necessary interpreter↔R2R transition thunks (and a callable portable entrypoint for the target) are available, otherwise falling back to the existing interpreted IL-stub path. It also updates Crossgen2 dependency rooting so those transition thunks are retained per target method, and extends Wasm R2R tests to cover a larger struct-return case.

Changes:

  • Add runtime gating in GetUnboxingStub to require a matching interpreter-to-R2R thunk for the unboxing signature and callable code in the target portable entrypoint before returning a shared stub.
  • Introduce a per-target dependency node (WasmUnboxingStubTargetNode) so Crossgen2 roots the shared stub, the compiled target, and both transition thunk kinds (M* and I*) per managed target.
  • Extend Wasm generic-dispatch test coverage with a 56-byte struct return and validate the expected thunk keys are present in the produced R2R image.
File Description
src/​coreclr/​vm/​wasm/​helpers.cpp Adds runtime checks to avoid publishing shared unboxing stub usage when required transition thunks / callable target entrypoints are missing.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRunCodegenNodeFactory.cs Switches Wasm unboxing-stub rooting to a per-target node cache that pulls in both transition thunk dependencies.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRun/​WasmUnboxingStubNode.cs Adds WasmUnboxingStubTargetNode to express per-target dependency rooting for stub + target + transition thunks.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun.Tests/​TestCases/​VirtualMethodGenerics/​NonGVM.cs Adds a new 56-byte struct-return interface dispatch case to exercise distinct adapter rooting with shared stubs.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun.Tests/​TestCases/​R2RTestSuites.cs Extends Wasm validation to assert presence of the expected M* and I* thunk keys for 16-byte and 56-byte struct return scenarios.

Copilot AI review requested due to automatic review settings September 9, 2026 21:57

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

It changes low-level Wasm VM + Crossgen2 dependency behavior for R2R/interpreter transitions and needs expert confirmation and full CI matrix validation.

Review tier: Lite
Findings: None

@lewing
lewing enabled auto-merge (squash) September 9, 2026 22:34
@lewing

lewing commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

This is approved it just needed a post approval conflict resolution so it needs another approval

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[ci-scan] Test failure: browser-wasm R2R interpreter-to-R2R thunk traps with null function or function signature mismatch

4 participants