Skip to content

Directly call same-image managed R2R helpers - #134555

Open
davidwrighton wants to merge 8 commits into
dotnet:mainfrom
davidwrighton:wasm-direct-managed-helpers
Open

davidwrighton wants to merge 8 commits into
dotnet:mainfrom
davidwrighton:wasm-direct-managed-helpers

Conversation

@davidwrighton

@davidwrighton davidwrighton commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Summary

Allow ReadyToRun code to call managed JIT-helper bodies directly when the helper can be compiled portably into the same binary.

Crossgen2 probe-compiles managed JIT helpers before compiling ordinary methods. Helpers that compile successfully without processor-feature fixups can call one another directly and are exposed to ordinary methods as local compiled bodies. Helpers that require runtime JIT or processor-specific dispatch continue to use the existing helper-cell path. The JIT treats an IAT_VALUE lookup as a direct call; on Wasm it passes zero for the portable-entrypoint argument rather than loading and calling through the PEP cell.

Direct calls bypass normal method-entrypoint resolution, so the compiler emits a METHOD_ENTRY_READYTORUN fixup in the ordinary Eager import section. CoreCLR processes eager imports at FILE_ACTIVE, immediately before Activate(), with method-entry fixups ordered after all other eager work. JIT-helper roots are also identified explicitly so throw-only managed helpers are emitted instead of being removed by FunctionJustThrows.

The new fixup updates the R2R format to version 31.0. This is a major version bump because runtimes implementing 30.x and earlier do not understand the new fixup.

Performance

Measured ArrayDeAbstraction.foreach_member_array_via_interface_property_in_loop on CoreCLR Wasm using a retail runtime and libraries built from this branch. Values are medians in ns/op; lower is better.

Scenario Before After Change
Individual R2R 36,032 30,857.62 -14.4%
Composite R2R 34,143 28,088.91 -17.7%

Individual R2R benefits from direct helper calls within the CoreLib image. Composite R2R additionally permits the benchmark and CoreLib helpers to share one Wasm module, eliminating the helper-cell load and call_indirect for GetGCStaticBase and VirtualFunctionPointer.

A fresh five-second sampled profile was generated from an unmodified final composite image. It collected 18,491 attributed samples:

Symbol Aggregated self-time
VirtualFunctionPointer 39.51%
get_Current 27.71%
GetGCStaticBase 9.49%
Benchmark method 8.86%
MoveNext 2.71%

An earlier description incorrectly said GetGCStaticBase fell from 10.49% to 3.10%. The 3.10% result came from a separate manual WAT experiment and is not attributable to the checked-in changes, so that comparison has been removed.

The interpreted path is not changed by this optimization. Five repeated interpreted runs had a median of medians of 88,718 ns/op and a 17.2% run-to-run spread; the previously observed +2.5% difference is therefore not evidence of a regression.

Wasm image size

Measured with a controlled same-worktree A/B against merged upstream/main (5b82fc4ae86): preserve the upstream image, apply only this PR's source diff, and rerun the same browser-Wasm Release build.

Layout Upstream PR Difference
Standalone System.Private.CoreLib.wasm 28,498,561 bytes 27,355,419 bytes -1,143,142 bytes (-4.01%)
Composite composite.wasm 25,676,102 bytes 24,421,328 bytes -1,254,774 bytes (-4.89%)
Complete composite layout 31,587,642 bytes 30,332,868 bytes -1,254,774 bytes (-3.97%)

The complete composite layout includes composite.wasm and the System.Private.CoreLib.wasm, System.Linq.wasm, and benchmark forwarding headers. wasm-objdump -h attributes nearly all of the reduction to the Wasm Code section.

Validation

  • ./build.sh -os browser -c Release -subset clr+libs
  • ./build.sh clr+libs+host -rc release -lc release
  • Interpreted, individual-R2R, and composite-R2R Wasm startup and focused benchmark runs
  • Fresh composite V8 sampled profile with no manual WAT edits
  • Native Linux build and testhost construction
  • R2RDump verification of R2R 31.0 and METHOD_ENTRY_READYTORUN helper entries ordered last in the ordinary eager section
  • Targeted CoreLib crossgen2 validation: Wasm compiled all 70 managed helpers directly; ARM compiled 69 directly and used the helper-cell fallback for SpanHelpers.Fill

Note

This pull request description was generated with GitHub Copilot.

davidwrighton and others added 2 commits September 23, 2026 19:03
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@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.

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

R2R version compatibility and non-local helper fallback issues remain unresolved, along with requested documentation and regression coverage.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity · 1 Low severity

Open (2)
What changed in this PR

This PR enables ReadyToRun code to directly call same-image managed JIT helpers, with eager activation fixups and WebAssembly support.

Changes:

  • Adds METHOD_ENTRY_READYTORUN and R2R format version 29.3.
  • Resolves and roots local managed helpers, including throw-only helpers.
  • Updates activation sequencing and Wasm direct-call lowering.

Outstanding issues include R2R version compatibility, a helper fallback bug, missing format documentation, and requested regression coverage.

File Summary
src/​coreclr/​vm/​nativeimage.h Tracks activation fixups.
src/​coreclr/​vm/​nativeimage.cpp Initializes activation state.
src/​coreclr/​vm/​jitinterface.cpp Handles method-entry fixups; regression coverage is requested.
src/​coreclr/​vm/​ceeload.h Declares activation fixup APIs.
src/​coreclr/​vm/​ceeload.cpp Processes eager and activation fixups.
src/​coreclr/​vm/​assembly.hpp Declares activation-phase handling.
src/​coreclr/​vm/​assembly.cpp Runs activation fixups before activation.
src/​coreclr/​tools/​Common/​JitInterface/​CorInfoImpl.cs Caches helper methods.
src/​coreclr/​tools/​Common/​Internal/​Runtime/​ReadyToRunConstants.cs Adds R2R flags and fixup kind.
src/​coreclr/​tools/​Common/​Internal/​Runtime/​ModuleHeaders.cs Updates the R2R version.
src/​coreclr/​tools/​aot/​ILCompiler.RyuJit/​JitInterface/​CorInfoImpl.RyuJit.cs Updates the helper lookup contract.
src/​coreclr/​tools/​aot/​ILCompiler.Reflection.ReadyToRun/​ReadyToRunSignature.cs Decodes the new fixup.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​JitInterface/​CorInfoImpl.ReadyToRun.cs Resolves local helpers; fallback output handling requires correction.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​ReadyToRunJitHelperRootProvider.cs Roots helper methods.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​ReadyToRunCodegenCompilation.cs Tracks helper compilation.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​IRootingServiceProvider.cs Extends root-provider metadata.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRunSymbolNodeFactory.cs Creates activation imports.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRunCodegenNodeFactory.cs Emits activation sections.
src/​coreclr/​tools/​aot/​ILCompiler.ReadyToRun/​Compiler/​DependencyAnalysis/​ReadyToRun/​MethodWithGCInfo.cs Marks JIT helpers.
src/​coreclr/​nativeaot/​Runtime/​inc/​ModuleHeaders.h Updates the NativeAOT R2R version.
src/​coreclr/​jit/​lowerwasm.cpp Supports direct Wasm calls and PEP handling.
src/​coreclr/​jit/​lower.h Declares Wasm lowering helpers.
src/​coreclr/​jit/​lower.cpp Implements direct Wasm call lowering.
src/​coreclr/​inc/​readytorun.h Defines R2R 29.3 additions; compatibility and format documentation issues remain.

Comment thread src/coreclr/inc/readytorun.h Outdated
Preserve upstream's R2R 29.3 BulkWriteBarrierSmall update and use a major version bump for the incompatible EagerActivation import-section flag.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 23, 2026 22:47

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

Two critical runtime issues and missing R2R format documentation remain unresolved.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity

Open (2)
Resolved since last review (2)

Comment thread src/coreclr/vm/ceeload.cpp Outdated
Comment thread src/coreclr/tools/Common/Internal/Runtime/ReadyToRunConstants.cs Outdated
lewing added a commit that referenced this pull request Sep 25, 2026
The relocation only arises with #134555, where a same-image
managed helper is returned as IAT_VALUE but genEmitHelperCall still loads the
PEP through an indirection cell. Encoding it as a function index would load
from imageBase + funcIndex, so the fix belongs in the JIT. On main no code
path emits it for composite images.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
davidwrighton and others added 2 commits September 25, 2026 18:17
Run all eager imports immediately before module activation and order managed helper method-entry fixups after ordinary eager imports. Keep direct managed helper selection restricted to Wasm so non-Wasm crossgen builds do not reference methods that require runtime JIT.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compile managed JIT helpers optimistically without publishing output, exclude helpers that fail or require processor feature fixups, and then compile the stable helper set before ordinary methods. This enables direct same-image helper calls across targets while retaining helper-cell fallback for unsupported methods.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@davidwrighton
davidwrighton enabled auto-merge (squash) September 25, 2026 18:56
davidwrighton and others added 2 commits September 25, 2026 19:13
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <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

🟡 Changes recommended

The helper-entry cache can retain stale direct-call eligibility; format documentation and end-to-end coverage are also requested.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)
Resolved since last review (2)

Comment thread src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs
Co-authored-by: Copilot <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

Optional helper fallback and profiler behavior require resolution before approval.

Review effort: Lite
Findings: None

Resolved since last review (1)

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

JIT changes look good.

Comment thread src/coreclr/jit/lower.cpp
LowerPEPCall(call);
if (call->gtDirectCallAddress != nullptr)
{
AddWasmPortableEntryPointArg(call, m_compiler->gtNewIconNode(0, TYP_I_IMPL));

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.

This arg is just to fulfill the signature demand?

Comment on lines +81 to +83
// R2R Version 31 adds READYTORUN_FIXUP_MethodEntry_ReadyToRun for initializing a
// method's ReadyToRun entry point and fixups.
// R2R 31 is not backward compatible with 30.x or earlier.

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
// R2R Version 31 adds READYTORUN_FIXUP_MethodEntry_ReadyToRun for initializing a
// method's ReadyToRun entry point and fixups.
// R2R 31 is not backward compatible with 30.x or earlier.
// R2R Version 31 adds READYTORUN_FIXUP_MethodEntry_ReadyToRun

R2R 31 is not backward compatible with 30.x or earlier.

I am not sure what this is trying to say.

Also, the description can be shorter.

Eager = 0x0001, // Section at module load time.
PCode = 0x0004, // Section contains pointers to code
None = 0x0000,
Eager = 0x0001, // Section before module activation.

@jkotas jkotas Sep 26, 2026 •

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
Eager = 0x0001, // Section before module activation.
Eager = 0x0001, // Fixups must be resolved at module load time before any code in the module runs.

Section before module activation.

Does not make sense

break;

case FILE_ACTIVE:
EagerFixups();

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.

I do not think it can be together with Activate (ie running module constructors). It would break if there are cycles between module constructors.

We should move the whole FILE_LOAD_EAGER_FIXUPS phase.

ReadyToRunFixupKind.MethodEntry_ReadyToRun,
method,
isInstantiatingStub: false),
sortLast: true);

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.

Why is sortLast: true needed here?

This branch has not been deployed

No deployments
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.

4 participants