Ensure code is ready for calls from R2R Wasm logic - #130386
#130386Ensure code is ready for calls from R2R Wasm logic#130386davidwrighton merged 2 commits intodotnet:maindotnet/runtime:mainfrom davidwrighton:EnsureCodeIsReadyForCallsFromR2Rdavidwrighton/runtime:EnsureCodeIsReadyForCallsFromR2RCopy head branch name to clipboard
Conversation
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>
|
Tagging subscribers to this area: @agocke |
There was a problem hiding this comment.
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
RunClassInitExhave a portable entry point that’s callable from R2R on portable-entrypoint targets. - Ensure default constructors invoked via
CallDefaultConstructorlikewise 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. |
|
Independently hit what looks like this exact class in a WASI static-composite CoreLib R2R prototype: Note This comment was generated with the assistance of GitHub Copilot. |
|
/ba-g unrelated infra issues |
|
@lewing, this shouldn't be necessary for that case. That is likely a different bug. |
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>
# [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>
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.