Add support for inline pinvokes to Wasm Ryujit#130384
Conversation
- 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.
There was a problem hiding this comment.
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
InlinedCallFrameregister 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_PInvokeEndand adjusts fixup handling so Wasm usesREADYTORUN_FIXUP_PInvokeTarget(not indirect). - Gates
READYTORUN_FIXUP_IndirectPInvokeTargethandling in the runtime behindHAS_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) |
| 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); | ||
| } |
| ::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; |
| 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(); | ||
| } |
| 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)); |
| extern "C" void JIT_PInvokeEnd(void* sp, InlinedCallFrame* pFrame, PCODE pep) | ||
| { | ||
| Thread* pThread = (Thread*)pFrame->m_pThread; |
| // 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. |
| extern "C" __attribute__((naked)) void JIT_PInvokeBegin(void* sp, InlinedCallFrame* pFrame, PCODE pep) | ||
| { |
|
Tagging subscribers to 'arch-wasm': @lewing, @pavelsavara |
|
Locally I have these ai-supplied fixes on top of this. Think they were also called out above.
|
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>
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>
…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>
…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>
DRAFT: since this is mostly untested at the moment.