Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Ensure code is ready for calls from R2R Wasm logic - #130386

#130386
Merged
davidwrighton merged 2 commits into
dotnet:maindotnet/runtime:mainfrom
davidwrighton:EnsureCodeIsReadyForCallsFromR2Rdavidwrighton/runtime:EnsureCodeIsReadyForCallsFromR2RCopy head branch name to clipboard
Jul 9, 2026
Merged

Ensure code is ready for calls from R2R Wasm logic#130386
davidwrighton merged 2 commits into
dotnet:maindotnet/runtime:mainfrom
davidwrighton:EnsureCodeIsReadyForCallsFromR2Rdavidwrighton/runtime:EnsureCodeIsReadyForCallsFromR2RCopy head branch name to clipboard

Conversation

@davidwrighton

Copy link
Copy Markdown
Member

Running class/default constructors triggered from native code in a R2R'd System.Private.CoreLib requires that the method be prepared for executing a call from Ryujit generated code.

AndyAyersMS and others added 2 commits July 8, 2026 16:03
CallClassConstructor invokes the cctor via a typed call_indirect, but the
slot from GetRestoredSlot can be a temporary precode whose wasm type does
not match. Resolve it with EnsurePortableEntryPointIsCallableFromR2R.

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

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.

Pull request overview

This change updates CoreCLR’s native-to-managed constructor invocation paths to ensure that, on FEATURE_PORTABLE_ENTRYPOINTS targets (Wasm), the callee’s portable entry point is in a state that can be invoked from R2R-generated typed call_indirect sequences (i.e., it has published native code or an appropriately-typed interpreter thunk instead of remaining in an unprepared/temporary state).

Changes:

  • Ensure class constructors invoked via RunClassInitEx have a portable entry point that’s callable from R2R on portable-entrypoint targets.
  • Ensure default constructors invoked via CallDefaultConstructor likewise have a portable entry point prepared for R2R/typed indirect calls on portable-entrypoint targets.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/coreclr/vm/methodtable.cpp Adds a portable-entrypoint preparation step before invoking the class constructor from native code on Wasm portable-entrypoint targets.
src/coreclr/vm/callhelpers.cpp Adds a portable-entrypoint preparation step before invoking the default constructor from native code on Wasm portable-entrypoint targets.

@lewing

lewing commented Jul 9, 2026

Copy link
Copy Markdown
Member

Independently hit what looks like this exact class in a WASI static-composite CoreLib R2R prototype: InitClassSlow's first helper call_indirect (type (i32,i32,i32)->void) resolves to an empty placeholder precode (func (param i32))IndirectCallTypeMismatch. Applying this PR's two sites did not cover it — that call site is neither RunClassInitEx nor CallDefaultConstructor. Does this sound like the same temporary-precode issue, needing an EnsurePortableEntryPointIsCallableFromR2R on the R2R helper-import path too, or is that expected to be handled elsewhere?

Note

This comment was generated with the assistance of GitHub Copilot.

@davidwrighton

Copy link
Copy Markdown
Member Author

/ba-g unrelated infra issues

@davidwrighton

Copy link
Copy Markdown
Member Author

@lewing, this shouldn't be necessary for that case. That is likely a different bug.

@davidwrighton
davidwrighton merged commit 3b4e4ff into dotnet:main Jul 9, 2026
104 of 113 checks passed
@dotnet-milestone-bot dotnet-milestone-bot Bot added this to the 11.0-preview7 milestone Jul 10, 2026
eiriktsarpalis pushed a commit that referenced this pull request Jul 15, 2026
Running class/default constructors triggered from native code in a R2R'd
System.Private.CoreLib requires that the method be prepared for
executing a call from Ryujit generated code.

---------

Co-authored-by: Andy Ayers <andya@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
lewing added a commit that referenced this pull request Jul 22, 2026
# [wasm] Ensure R2R entry-point PEP is callable in RunMain

## Summary

On WebAssembly, the managed program entry point is invoked from
R2R-compiled code:
`System.Environment.CallEntryPoint` performs an **indirect call**
through the entry
point's address. When the entry point resolves to a `PortableEntryPoint`
(PEP) whose
`actualCode` slot has not yet been populated, that indirect
`call_indirect` targets an
uninitialized table slot and the module traps (`uninitialized element`)
— or, when the
condition arises on a cold path, the runtime falls back to interpreter
execution for
the entire process instead of running R2R code.

This adds the missing call to
`MethodDesc::EnsurePortableEntryPointIsCallableFromR2R`
in `RunMainInternal` (right after the entry-point address is
materialized via
`GetSingleCallableAddrOfCode()`), so the entry-point PEP is published to
real code
(native R2R body or a correctly-typed interpreter thunk) before it is
invoked.

```cpp
    pParam->pFD->EnsureActive();
    PCODE entryPoint = pParam->pFD->GetSingleCallableAddrOfCode();
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
    // The entry point is invoked from R2R-compiled code (Environment.CallEntryPoint performs an
    // indirect call through this address), so it must resolve to real code (native R2R or a
    // correctly-typed interpreter thunk) rather than an uninitialized portable entry point.
    MethodDesc::EnsurePortableEntryPointIsCallableFromR2R(entryPoint);
#endif // FEATURE_PORTABLE_ENTRYPOINTS
```

## Motivation

The helper `MethodDesc::EnsurePortableEntryPointIsCallableFromR2R` and
its sibling
call-sites are already on `main` (introduced by #130386 and #130841).
Those PRs cover
finalizer slots, FCall/PInvoke helpers, class constructors, string
constructors, and
prestub paths — but **not** the program entry point invoked by
`RunMain`. As a result,
a ReadyToRun WASM image that dispatches its `Main` through
`Environment.CallEntryPoint`
can hit an uninitialized PEP and either trap or degrade to whole-program
interpreter
execution.

This is the last uncovered publication site on the hello-world startup
path. It is a
6-line addition scoped entirely under `#ifdef
FEATURE_PORTABLE_ENTRYPOINTS`, which is
set only for `CLR_CMAKE_TARGET_ARCH_WASM` (see
`src/coreclr/clrfeatures.cmake`), so it
has **zero effect on non-wasm targets**.

## Validation

Measured on a composite R2R WASI "Hello world" (CoreLib + System.Runtime
+
System.Console, crossgen'd `--composite --targetos:wasi
--targetarch:wasm`, run under
wasmtime).

A robust health signal for "R2R actually executes" (as opposed to the
program merely
surviving via interpreter fallback) is whether two guaranteed-reached
corelib bodies —
`System.String..ctor` and `System.SpanHelpers.Memmove` — execute as R2R,
together with
the absence of `TryPublishR2R FAILED` diagnostics.

| Build | `String..ctor` R2R hits | `Memmove` R2R hits | `TryPublishR2R
FAILED` |

|-------|------------------------:|-------------------:|-----------------------:|
| Without this fix (entry-point PEP uninitialized) | 0 | 0 | 5 |
| **With this fix**                                | 7 | 8 | 0 |

With the fix, the two guaranteed-reached bodies run as native R2R, the
reverse-thunk
fallback diagnostics disappear entirely, and the app prints its output
and exits `42`.
Result independently reproduced across three builds (identical `7` / `8`
/ `0` counts).

Note: exit code alone is **not** a sufficient signal — an image with the
entry-point
PEP uninitialized can still print and exit `42` while executing zero R2R
bodies (pure
interpreter fallback). The `String..ctor` + `Memmove` execution check is
the meaningful
gate.

## Risk

- Scoped to wasm only via `FEATURE_PORTABLE_ENTRYPOINTS`.
- Uses an existing, already-merged helper; no new API surface.
- Mirrors the established pattern of the other
`EnsurePortableEntryPointIsCallableFromR2R`
  call-sites already on `main`.

Original prototype commit: `wasm: ensure R2R entry-point PEP is callable
in RunMain`
(authored by Andy Ayers).

> [!NOTE]
> This PR description was drafted with GitHub Copilot.

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

Morty Proxy This is a proxified and sanitized view of the page, visit original site.