Adjust PortableEntryPoint logic to actually handle having Wasm R2R native entrypoints#126901
Adjust PortableEntryPoint logic to actually handle having Wasm R2R native entrypoints#126901davidwrighton wants to merge 5 commits intodotnet:mainfrom
Conversation
davidwrighton
commented
Apr 14, 2026
- This includes some initial work where there is a predefined set of new thunks generated into the coreclr codebase. I expect that we'll build some sort of hardcoded list here for invokes to/from R2R code, and then have R2R supplement the list with extra cases.
- Adjust PortableEntryPoint calling convention to match the WASM version
- Update PortableEntryPoint::HasNativeEntryPoint to indicate that the NativeEntryPoint is the intentional target
- Update the calls from R2R into interpreter to capture the stack argument into the TransitionBlock
- Implement correct signature mapping for PortableEntryPoints which are FCalls
- Update interp to managed helpers to pass a stack pointer which informs the stack walker to stop walking
- Put the callers frame pointer into the TransitionBlock. To do so, I've added a new m_StackPointer field to the TransitionBlock
- Make the ExecuteInterpretedMethodWtihArgs_PortableEntryPoint_Complex function into functionally being the Prestub when called from R2R.
- Add m_StackPointer to TransitionBlock, and put the callers stack pointer in there.
- Update helper calls in interpexec to setup the stack pointer argument correctly
…entrypoints - This includes some initial work where there is a predefined set of new thunks generated into the coreclr codebase. I expect that we'll build some sort of hardcoded list here for invokes to/from R2R code, and then have R2R supplement the list with extra cases. - Adjust PortableEntryPoint calling convention to match the WASM version - Update PortableEntryPoint::HasNativeEntryPoint to indicate that the NativeEntryPoint is the intentional target - Update the calls from R2R into interpreter to capture the stack argument into the TransitionBlock - Implement correct signature mapping for PortableEntryPoints which are FCalls - Update interp to managed helpers to pass a stack pointer which informs the stack walker to stop walking - Put the callers frame pointer into the TransitionBlock. The current implementation drops it into the m_ReturnAddress field which is a bit dodgy, but we can fix this later when we actually work on the stack walker - Make the ExecuteInterpretedMehtodWtihArgs_PortableEntryPoint_Complex function into functionally being the Prestub when called from R2R.
…inter in there. - Update helper calls in interpexec to setup the stack pointer argument correctly
|
Tagging subscribers to this area: @agocke |
There was a problem hiding this comment.
Pull request overview
This PR updates CoreCLR’s WASM PortableEntryPoint and interpreter interop to support scenarios where R2R code calls through PortableEntryPoints that may ultimately target native entrypoints, including new thunk/signature handling and stack-walk termination plumbing.
Changes:
- Extend signature generation and thunk tables to distinguish PortableEntryPoint calls (via a
'p'suffix) and to include InternalCall/FCall shapes. - Add/adjust WASM-side thunk implementations and runtime lookup/caching for R2R→interpreter and interpreter→native dispatch.
- Update PortableEntryPoint state tracking (preferring interpreter vs native) and propagate a stack-walk termination/stack pointer through
TransitionBlockand helper call paths.
Reviewed changes
Copilot reviewed 23 out of 23 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs | Adds optional inclusion of this in computed signatures. |
| src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs | Switches from icall table generation to InternalCall signature collection + pregenerated signatures; writes cache as UTF-8. |
| src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs | Generates distinct _PE call thunks for signatures ending in 'p' (PortableEntryPoint context). |
| src/tasks/WasmAppBuilder/coreclr/InternalCallSignatureCollector.cs | New collector that scans for MethodImplAttributes.InternalCall and emits 'p'-suffixed signatures. |
| src/coreclr/vm/wasm/helpers.cpp | Adds hardcoded R2R→interpreter thunks, PortableEntryPoint thunk cache, and signature key support for 'p' suffix. |
| src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp | Updates generated interpreter→native thunk implementations for PortableEntryPoint calls (*_PE) and signature table entries. |
| src/coreclr/vm/prestub.cpp | Adds PortableEntryPoint-aware interpreted execution path that can run prestub work and dispatch to compiled code. |
| src/coreclr/vm/precode_portable.hpp | Adds PrefersInterpreterEntryPoint flag and accessors. |
| src/coreclr/vm/precode_portable.cpp | Updates HasNativeEntryPoint/SetActualCode semantics around “prefer interpreter” state. |
| src/coreclr/vm/method.cpp | Initializes PortableEntryPoints with an optional R2R→interpreter thunk and marks them as preferring interpreter initially. |
| src/coreclr/vm/jithelpers.cpp | Updates WASM helper wrappers to pass stack-walk termination/TransitionBlock info into _Impl variants. |
| src/coreclr/vm/interpexec.h | Exposes InvokeManagedMethod prototype for PortableEntryPoint prestub dispatch use. |
| src/coreclr/vm/interpexec.cpp | Adds macros/typedefs to pass PortableEntryPoint stack/context args through helper calls; adjusts PortableEntryPoint native-entrypoint gating. |
| src/coreclr/vm/ilstubcache.cpp | Moves SetTemporaryEntryPoint later in IL stub creation sequence. |
| src/coreclr/vm/gccover.cpp | Updates GCFrame construction to use GC flags instead of a bool. |
| src/coreclr/vm/frames.h | Changes GCFrame API from BOOL maybeInterior to UINT gcFlags and updates protection macros accordingly. |
| src/coreclr/vm/frames.cpp | Implements GCFrame behavior using gcFlags and asserts flag expectations. |
| src/coreclr/vm/fcall.h | Adjusts WASM FCALL/HCALL signatures to include hidden stack/context parameters. |
| src/coreclr/vm/excep.cpp | Records interpreter stack pointer from TransitionBlock for WASM exception context. |
| src/coreclr/vm/eetwain.cpp | Ensures WASM TransitionBlock has stack pointer set for funclet calls. |
| src/coreclr/vm/callingconvention.h | Adds m_StackPointer overlay in WASM TransitionBlock. |
| src/coreclr/vm/callhelpers.h | Introduces TERMINATE_R2R_STACK_WALK sentinel for WASM stack walking termination. |
| src/coreclr/runtime/portable/AllocFast.cpp | Updates an internal FCALL-to-FCALL call site to pass the new WASM hidden parameters explicitly. |
…ng the address of an aligned local. - Fix the stack arg passed to R2R functions to always be 16 byte aligned - Fix missing Call_System_Private_CoreLib_System_Environment_CallEntryPoint_I32_I32_I32_I32_I32_RetVoid thunk
There was a problem hiding this comment.
Pull request overview
This PR updates the CoreCLR WASM PortableEntryPoint calling convention and thunk/signature infrastructure to support ReadyToRun (R2R) native entrypoints while preserving correct interpreter dispatch and stack-walking behavior.
Changes:
- Extends WASM signature generation to account for PortableEntryPoint context (
'p') and InternalCall/FCall signature shapes. - Adds R2R-to-interpreter thunk support, including capturing the caller stack pointer into
TransitionBlockand using it to terminate/limit stack walking. - Adjusts PortableEntryPoint state management and prestub-like behavior to correctly resolve interpreter vs native (R2R) targets.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs | Adds optional inclusion of this to signature mapping for instance methods. |
| src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs | Replaces icall table generation with InternalCall signature collection and adds pregenerated signature shapes. |
| src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs | Generates distinct call thunks for PortableEntryPoint calls (extra hidden params + stack-walk termination). |
| src/tasks/WasmAppBuilder/coreclr/InternalCallSignatureCollector.cs | New scanner to collect InternalCall signatures for thunk generation. |
| src/coreclr/vm/wasm/helpers.cpp | Adds R2R→interpreter thunk table/cache and updates managed calli invocation to carry PE context. |
| src/coreclr/vm/wasm/callhelpers-reverse.cpp | Updates reverse-thunk exports and signature naming adjustments. |
| src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp | Updates generated thunk table to include PE-aware signatures and call patterns. |
| src/coreclr/vm/prestub.cpp | Adds PortableEntryPoint “complex” path that behaves like prestub when invoked from R2R. |
| src/coreclr/vm/precode_portable.hpp | Adds PrefersInterpreterEntryPoint flag/accessors and updates PortableEntryPoint API surface. |
| src/coreclr/vm/precode_portable.cpp | Updates native-entrypoint detection and allows “upgrade” from interpreter-preferred to actual code. |
| src/coreclr/vm/method.cpp | Initializes PortableEntryPoint with an R2R→interpreter thunk when available and sets interpreter-preferred state. |
| src/coreclr/vm/jithelpers.cpp | WASM FCALL wrappers now pass stack pointer / transition info to exception helpers. |
| src/coreclr/vm/interpexec.h | Exposes InvokeManagedMethod for prestub/R2R dispatch paths. |
| src/coreclr/vm/interpexec.cpp | Adjusts helper calling patterns and PortableEntryPoint selection logic; threads stack-pointer through transition blocks. |
| src/coreclr/vm/ilstubcache.cpp | Moves SetTemporaryEntryPoint later in IL stub creation sequence. |
| src/coreclr/vm/gccover.cpp | Updates GCFrame usage to new ctor parameterization (gcFlags). |
| src/coreclr/vm/frames.h | Changes GCFrame API from maybeInterior to gcFlags and updates GCPROTECT macros. |
| src/coreclr/vm/frames.cpp | Implements GCFrame gcFlags handling and enforces interior-flag expectations. |
| src/coreclr/vm/fcall.h | Updates FCALL/HCALL macro signatures for WASM to include hidden stack/context args. |
| src/coreclr/vm/excep.cpp | Plumbs interpreter stack pointer from TransitionBlock into exception frame context. |
| src/coreclr/vm/eetwain.cpp | Ensures WASM transition blocks set stack pointer consistently for funclet calls. |
| src/coreclr/vm/callingconvention.h | Extends WASM TransitionBlock to carry m_StackPointer. |
| src/coreclr/vm/callhelpers.h | Adds TERMINATE_R2R_STACK_WALK sentinel for WASM stack-walk termination. |
| src/coreclr/runtime/portable/AllocFast.cpp | Updates a FCALL wrapper to supply explicit hidden args under new WASM calling convention. |
| class PortableEntryPoint final | ||
| { | ||
| public: // static | ||
| // Returns true if the NativeEntryPoint of a given PortableEntryPoint is set to an exact target address (instead of a thunk or other auxiliary code), and that target is the provided address. |
|
|
||
| foreach (var nestedType in type.GetNestedTypes()) | ||
| ScanType(nestedType); |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates CoreCLR WASM PortableEntryPoint plumbing to support ReadyToRun (R2R) native entrypoints and correct calling conventions/signature handling across interpreter ↔ native transitions (including FCalls), with additional stack-walk/TransitionBlock support.
Changes:
- Extend signature generation/mapping to support PortableEntryPoint context (
p) and InternalCall (FCall) signatures; update thunk generation accordingly. - Add initial hardcoded R2R→interpreter PortableEntryPoint thunk set and associated lookup/caching paths; adjust prestub-style dispatch for PortableEntryPoint calls.
- Update runtime structures/paths for WASM stack-walk termination and TransitionBlock stack pointer propagation; refactor GCFrame to use GC flag bitmasks.
Reviewed changes
Copilot reviewed 24 out of 24 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs | Adds optional inclusion of this in computed signatures. |
| src/tasks/WasmAppBuilder/coreclr/ManagedToNativeGenerator.cs | Switches from icall table generation to InternalCall signature collection; adds pregenerated signature list. |
| src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs | Generates distinct PE thunks (extra hidden params + stack-walk sentinel) and maps ...p signatures. |
| src/tasks/WasmAppBuilder/coreclr/InternalCallSignatureCollector.cs | New collector for InternalCall method signatures (for PE thunk generation). |
| src/coreclr/vm/wasm/helpers.cpp | Adds PE thunk table + lookup; appends p to default-callconv signature keys; updates managed calli cookie invocation shape; adds thunk lookup for R2R→interp. |
| src/coreclr/vm/wasm/callhelpers-interp-to-managed.cpp | Updates generated thunk implementations + table entries for ...p (PortableEntryPoint) signatures. |
| src/coreclr/vm/wasm/callhelpers-reverse.cpp | Updates generated reverse thunks and some signatures/entry mappings. |
| src/coreclr/vm/prestub.cpp | Adds PortableEntryPoint “complex” path that behaves prestub-like and can dispatch to R2R via InvokeManagedMethod. |
| src/coreclr/vm/precode_portable.hpp | Adds PrefersInterpreterEntryPoint flag API to PortableEntryPoint. |
| src/coreclr/vm/precode_portable.cpp | Updates HasNativeEntryPoint semantics and SetActualCode behavior with PrefersInterpreterEntryPoint. |
| src/coreclr/vm/method.cpp | Uses R2R→interp thunk when available and sets PrefersInterpreterEntryPoint on new PortableEntryPoints. |
| src/coreclr/vm/jithelpers.cpp | Updates WASM IL_Throw/IL_Rethrow/IL_ThrowExact wrappers to incorporate TransitionBlock/stack pointer logic. |
| src/coreclr/vm/interpexec.h | Exposes InvokeManagedMethod declaration for new prestub path usage. |
| src/coreclr/vm/interpexec.cpp | Adds helper-call wrappers for WASM portable-entrypoint calling convention; updates PortableEntryPoint/native-entrypoint selection logic. |
| src/coreclr/vm/callingconvention.h | Extends WASM TransitionBlock storage with a unioned stack pointer field. |
| src/coreclr/vm/callhelpers.h | Introduces TERMINATE_R2R_STACK_WALK sentinel for WASM stack walking. |
| src/coreclr/vm/frames.h | Updates GCFrame ctor signature (bool → flags) and adjusts GCPROTECT macros. |
| src/coreclr/vm/frames.cpp | Implements GCFrame flag-based scanning behavior. |
| src/coreclr/vm/gccover.cpp | Updates GCFrame usage to pass GC flags. |
| src/coreclr/vm/ilstubcache.cpp | Moves SetTemporaryEntryPoint call later in IL stub creation. |
| src/coreclr/vm/fcall.h | Adjusts FCALL/HCALL signature macros for TARGET_WASM to include hidden params. |
| src/coreclr/vm/excep.cpp | Plumbs TransitionBlock stack pointer into SoftwareExceptionFrame context on WASM. |
| src/coreclr/vm/eetwain.cpp | Initializes TransitionBlock stack pointer for WASM funclet path. |
| src/coreclr/runtime/portable/AllocFast.cpp | Updates RhpNewPtrArrayFast to match new WASM FCALL signature shape. |
|
|
||
| foreach (var nestedType in type.GetNestedTypes()) | ||
| ScanType(nestedType); |
| block.m_ReturnAddress = 0; | ||
| block.m_StackPointer = callersStackPointer; | ||
|
|
||
| IL_Throw_Impl(0, obj, (callersStackPointer == 0 || *(int*)callersStackPointer == TERMINATE_R2R_STACK_WALK) ? NULL : &block, 0); |
| if (m_gcFlags != 0) | ||
| { | ||
| PromoteCarefully(fn, pRefs + i, sc, GC_CALL_INTERIOR | CHECK_APP_DOMAIN); | ||
| _ASSERTE(m_gcFlags & GC_CALL_INTERIOR); | ||
| PromoteCarefully(fn, pRefs + i, sc, m_gcFlags | CHECK_APP_DOMAIN); | ||
| } |
|
|
||
| // This is a lock free write. It can either be NULL or was already set to the same value. | ||
| _ASSERTE(!portableEntryPoint->HasNativeCode() || portableEntryPoint->_pActualCode == (void*)PCODEToPINSTR(actualCode)); | ||
| // This is a lock free write. It can either be NULL, was already set to the same value, or be the interpreter entrypoint. |
| block.m_ReturnAddress = 0; | ||
| block.m_StackPointer = callersStackPointer; | ||
|
|
||
| IL_Rethrow_Impl(0, (callersStackPointer == 0 || *(int*)callersStackPointer == TERMINATE_R2R_STACK_WALK) ? NULL : &block, 0); |
| #ifdef TARGET_WASM | ||
| // A sentinel value to indicate to the stack walker that this frame is NOT R2R generated managed code, | ||
| // and it should look for the next Frame in the Frame chain to make further progress. | ||
| #define TERMINATE_R2R_STACK_WALK 1 |
| block.m_ReturnAddress = 0; | ||
| block.m_StackPointer = callersStackPointer; | ||
|
|
||
| IL_ThrowExact_Impl(0, obj, (callersStackPointer == 0 || *(int*)callersStackPointer == TERMINATE_R2R_STACK_WALK) ? NULL : &block, 0); |
| public void ScanAssembly(Assembly asm) | ||
| { | ||
| foreach (Type type in asm.GetTypes()) | ||
| ScanType(type); | ||
| } |