Skip to content

Add support for inline pinvokes to Wasm Ryujit#130384

Draft
davidwrighton wants to merge 1 commit into
dotnet:mainfrom
davidwrighton:AddInlinedPInvokesToWasmRyujit
Draft

Add support for inline pinvokes to Wasm Ryujit#130384
davidwrighton wants to merge 1 commit into
dotnet:mainfrom
davidwrighton:AddInlinedPInvokesToWasmRyujit

Conversation

@davidwrighton

Copy link
Copy Markdown
Member
  • They should always be READYTORUN_FIXUP_PInvokeTarget instead of READYTORUN_FIXUP_IndirectPInvokeTarget
  • There is a debug version of JIT_PinvokeEnd which validates that sp == the __stack_pointer global. I believe our codegen will maintain this invariant, so we should be ok to skip resetting the __stack_pointer global before calls to the C++ implementation of JIT_PInvokeEnd
  • One detail which is no longer true after this work is merged is that the __stack_pointer global will be set to increasingly unpredictable values during execution. This is not a new phenomena, as it was happening during EH flow, but now it will happen in normal execution flow. Since the interpreter to R2R thunks will reset the stack before we return to any emscripten compiled code, we should be ok with this.

DRAFT: since this is mostly untested at the moment.

- They should always be READYTORUN_FIXUP_PInvokeTarget instead of READYTORUN_FIXUP_IndirectPInvokeTarget
- There is a debug version of JIT_PinvokeEnd which validates that sp == the __stack_pointer global. I believe our codegen will maintain this invariant, so we should be ok to skip resetting the __stack_pointer global before calls to the C++ implementation of JIT_PInvokeEnd
- One detail which is no longer true after this work is merged is that the __stack_pointer global will be set to increasingly unpredictable values during execution. This is not a new phenomena, as it was happening during EH flow, but now it will happen in normal execution flow. Since the interpreter to R2R thunks will reset the stack before we return to any emscripten compiled code, we should be ok with this.

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 PR changes the Wasm/CoreCLR and ReadyToRun toolchain to enable (or move toward enabling) inlined P/Invoke sequences in Wasm RyuJIT / R2R scenarios, primarily by adjusting how P/Invoke targets are represented in R2R fixups and how the runtime establishes stackwalk state for inlined P/Invokes on Wasm.

Changes:

  • Updates Wasm InlinedCallFrame register display logic to derive IP/SP/FP from an R2R stack pointer in the “inlined P/Invoke” case.
  • Introduces Wasm implementations/wrappers for JIT_PInvokeBegin/JIT_PInvokeEnd and adjusts fixup handling so Wasm uses READYTORUN_FIXUP_PInvokeTarget (not indirect).
  • Gates READYTORUN_FIXUP_IndirectPInvokeTarget handling in the runtime behind HAS_PINVOKE_IMPORT_PRECODE.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 9 comments.

File Description
src/coreclr/vm/wasm/helpers.cpp Adds Wasm-specific inlined P/Invoke frame setup and stackwalk register-display logic; introduces Wasm JIT_PInvokeBegin/End implementations/wrappers.
src/coreclr/vm/jitinterface.cpp Wraps READYTORUN_FIXUP_IndirectPInvokeTarget runtime fixup support with HAS_PINVOKE_IMPORT_PRECODE.
src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs Forces Wasm ReadyToRun compilations to use direct P/Invoke target fixups (and adds explanatory comments).


#define WASM_STRINGIFY_HELPER(value) #value
#define WASM_STRINGIFY(value) WASM_STRINGIFY_HELPER(value)
#define INLINED_PINVOKE_FROM_R2R 0

pRD->pCurrentContext->InterpreterSP = *(DWORD *)&m_pCallSiteSP;
pRD->pCurrentContext->InterpreterFP = *(DWORD *)&m_pCalleeSavedFP;
if (m_CallerReturnAddress == INLINED_PINVOKE_FROM_R2R)
Comment on lines +499 to +503
pRD->pCurrentContext->InterpreterSP = m_pCallSiteSP;
pRD->pCurrentContext->InterpreterIP = GetWasmVirtualIPFromStackPointer(m_pCallSiteSP);
_ASSERTE(pRD->pCurrentContext->InterpreterIP != 0); // We should be in RyuJit compiled code here
pRD->pCurrentContext->InterpreterFP = GetWasmFramePointerFromStackPointer(m_pCallSiteSP, pRD->pCurrentContext->InterpreterIP);
}
Comment on lines +708 to +712
::new ((void*)pFrame) InlinedCallFrame();
pFrame->m_pCallSiteSP = sp;
pFrame->m_pCallerReturnAddress = INLINED_PINVOKE_FROM_R2R; // When this is tru the UpdateRegisters function will do all work based on m_pCallerReturnAddress
pFrame->m_pCalleeSavedFP = 0;
pFrame->m_pThread = pThread;
Comment on lines +734 to +742
EXTERN_C void* JIT_PInvokeEndImpl(TADDR sp, TADDR stack_pointer_global_value, InlinedCallFrame* pFrame)
{
_ASSERTE(sp == stack_pointer_global_value);
Thread* pThread = (Thread*)pFrame->m_pThread;

// Transition back to cooperative GC mode and unlink the frame.
pThread->DisablePreemptiveGC();
pFrame->Pop();
}
Comment on lines +746 to +750
asm("local.get 0\n" /* sp */
"global.set __stack_pointer\n" /* __stack_pointer = sp before any native code runs */
"local.get 1\n" /* pFrame */
"call %0\n"
"return" ::"i"(JIT_PInvokeEndImpl));
Comment on lines +753 to +755
extern "C" void JIT_PInvokeEnd(void* sp, InlinedCallFrame* pFrame, PCODE pep)
{
Thread* pThread = (Thread*)pFrame->m_pThread;
Comment on lines +3226 to +3228
// Suppress GC transition pinvokes are called directly, since we can't do a gc transition at this point.
// On Wasm, we also call directly, as the runtime doesn't generate stubs for the pinvoke calls which can produce errors. Instead
// the error is produced as we fixup the method in the first place.
Comment on lines +722 to 723
extern "C" __attribute__((naked)) void JIT_PInvokeBegin(void* sp, InlinedCallFrame* pFrame, PCODE pep)
{
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara
See info in area-owners.md if you want to be subscribed.

@AndyAyersMS

Copy link
Copy Markdown
Member

Locally I have these ai-supplied fixes on top of this. Think they were also called out above.

  1. Rebase glue for  InlinedCallFrame::UpdateRegDisplay_Impl  — adapts Add support for inline pinvokes to Wasm Ryujit #130384's  INLINED_PINVOKE_FROM_R2R  handling to main's current  InlinedCallFrame  field layout: uses  m_pCallerReturnAddress  (dac_cast to  TADDR ) instead of the PR's  m_CallerReturnAddress , and casts  m_pCallSiteSP  into a local.

  2. DEBUG  JIT_PInvokeEnd  fix — the debug variant is supposed to validate that R2R codegen kept  sp == __stack_pointer  across the call, but the naked asm was doing  global.set __stack_pointer  (resetting it, like Begin) and passing the wrong arg. Fixed to  global.get  so it reads the current global and passes it as arg2 for the  _ASSERTE(sp == stack_pointer_global_value)  check; also changed  JIT_PInvokeEndImpl  return type from  void*  to  void .

lewing added a commit to lewing/runtime that referenced this pull request Jul 13, 2026
David's dotnet#130384 draft was written against pre-dotnet#130363 main. Adapt to build:
- dac_cast<TADDR>(m_pCallSiteSP): m_pCallSiteSP is PTR_VOID, helpers take TADDR
- GetWasmFramePointerFromStackPointer is now 2-arg (sp, controlPC) on main (dotnet#130363),
  pass (PCODE)InterpreterIP
- (typo m_CallerReturnAddress -> m_pCallerReturnAddress fixed during cherry-pick resolve)

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing added a commit to lewing/runtime that referenced this pull request Jul 13, 2026
Rebaselines the WASI R2R prototype onto current main (67 commits, now includes
dotnet#130446 param-homing merged upstream 07-12). 4 trivial conflicts resolved in favor
of origin/main (jiteeversionguid.h GUID token; wasi PAL stubs/file/map comment+null-check
polish that landed via dotnet#130051 squash). dotnet#130446 de-duplicated cleanly (our cherry-pick
== main's merged change). Prototype-only delta preserved: dotnet#130384 (inline pinvokes,
still draft upstream), GAP1 (crossgen2 Program.cs +Wasi optimistic-ISA), static-compose
corerun glue + JIT-EE WasmWellKnownGlobalSymbol additions.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing added a commit to lewing/runtime that referenced this pull request Jul 13, 2026
…arity

Cherry-pick InitHelpers.cs from AndyAyersMS:next-blocker-spc — InitClass and
InitInstantiatedClass return void* (null) instead of void, so the compiled wasm
call_indirect signature matches the interp's value-returning P_ helper convention.
Fixes the class-init helper arity mismatch (Console.cctor trap) that the rebaselined
corelib-R2R composite hit at func 61854 after Gates 1+2 (dotnet#130384/dotnet#130446).

Prototype-only; for the future WASI R2R PR.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
lewing added a commit to lewing/runtime that referenced this pull request Jul 13, 2026
…tes #1,#3-#7)

Merges Andy's complete 'get hello world working' set on top of our branch (which
already had gate #2 InitHelpers). Adds:
 #1 CallEntryPoint: assembly.cpp RunMainInternal EnsurePortableEntryPointIsCallableFromR2R
 #3 disable wasm SIMD (compiler.cpp, InstructionSetHelpers.cs, codeman.cpp)
 #4 WasmBase LZC/TZC + PackedSimd NYI (hwintrinsic*.{cpp,h})
 #5 interp IsSupported fold (interpreter/compiler.cpp)
 #6 thread-static base FCall (Thread.CoreCLR.cs, comsynchronizable.*, ecalllist.h)
 #7 String-ctor PEP (prestub.cpp, wasm/helpers.cpp)
One conflict in wasm/helpers.cpp (dotnet#130384 overlap) resolved to Andy's canonical version.
NOTE: gate #3 disables SIMD — prototype exploration only, not a shippable path.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-VM-coreclr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants