[Wasm Ryujit]: implement runtime-async codegen for R2R Wasm - #131167
Conversation
Adds Wasm R2R support for runtime-async methods. Key design decisions: * The continuation is returned by a Wasm global. The continuation global is referenced through the newly introduced CORINFO_WASM_WELLKNOWN_GLOBALS. * Per-method async resume info is emitted into read-only data. The info `Resume` field points to code, so is emitted as a relocatable function-table index, requiring a new fixup type (function index in data). * AsyncResumptionStub target calls are routed through MethodEntrypoint on Wasm (no direct code-pointer call exists). We may need to revisit this some day to ensure the right IL version is being invoked. * The async continuation is encoded via a new 'a' signature marker. The interp<->R2R thunks and the delay-load import thunk explicitly forward the continuation as needed. There is perhaps too much ceremony in the code to get correct argument ordering. * The Wasm shadow stack pointer is a caller-established local, not continuation state, so it is excluded from async save/restore in JIT codegen. Adding a new Wasm global requires creating a new JIT Guid. Fixes dotnet#130952. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452
|
Azure Pipelines: Successfully started running 7 pipeline(s). 9 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
|
@davidwrighton PTAL Simple R2R'd async test case works on top of interpreted SPC. Haven't tested much else yet. |
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull request overview
This PR extends the Wasm ReadyToRun (R2R) pipeline to support runtime-async codegen by introducing an asyncContinuation well-known Wasm global, updating signature encoding to include an async marker ('a'), and adding the necessary relocations and thunk plumbing across the JIT/EE/ILCompiler/tooling/host to carry async resume metadata and continuation values.
Changes:
- Add an
asyncContinuationentry toCORINFO_WASM_WELLKNOWN_GLOBALSand plumb it through the JIT/EE interface, SuperPMI replay, r2rdump, object writer, and the Wasm host (libCorerun.js). - Implement Wasm JIT codegen for async nodes (e.g.,
GT_ASYNC_CONTINUATION,GT_RETURN_SUSPEND,GT_ASYNC_RESUME_INFO,GT_RECORD_ASYNC_RESUME) and add a Wasm-specific reloc to store function-table indices in data. - Update signature encoding/decoding and R2R thunk generation to recognize/forward the async marker (
'a') and adjust interpreter ↔ R2R call boundaries accordingly.
Reviewed changes
Copilot reviewed 39 out of 39 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| src/tasks/WasmAppBuilder/coreclr/SignatureMapper.cs | Updates signature-token parsing docs to include the new async marker token. |
| src/tasks/WasmAppBuilder/coreclr/InterpToNativeGenerator.cs | Strips the 'a' marker when generating interp→native call helper signatures / mappings. |
| src/coreclr/vm/wasmasynccontinuation.h | Adds Wasm-only C-callable accessors for reading/writing the shared async continuation global. |
| src/coreclr/vm/wasm/helpers.cpp | Updates interpreter→native invocation to optionally pass/handle an async continuation out parameter. |
| src/coreclr/vm/prestub.cpp | Writes/reads the async continuation via the shared global on Wasm at interpreter boundaries. |
| src/coreclr/vm/jitinterface.cpp | Adjusts R2R dynamic info resumption stub entry point resolution for Wasm function-table indices. |
| src/coreclr/vm/interpexec.cpp | Ensures calli paths only pass the continuation slot to async callees under portable entrypoints. |
| src/coreclr/tools/superpmi/superpmi-shared/methodcontext.cpp | Records/replays the new asyncContinuation well-known global for SuperPMI Wasm contexts. |
| src/coreclr/tools/superpmi/superpmi-shared/agnostic.h | Extends agnostic Wasm well-known globals struct with asyncContinuation. |
| src/coreclr/tools/r2rdump/WasmDisassembler.cs | Annotates global.get/set disassembly with well-known global names including asyncContinuation. |
| src/coreclr/tools/r2rdump/R2RDump.csproj | Links WasmGlobalImports.cs into r2rdump for consistent global index naming. |
| src/coreclr/tools/Common/JitInterface/WasmLowering.cs | Adds 'a' signature marker emission/parsing for async calls and adjusts roundtrip validation. |
| src/coreclr/tools/Common/JitInterface/CorInfoTypes.cs | Adds WASM_TABLE_INDEX_I32 reloc and the asyncContinuation well-known global field. |
| src/coreclr/tools/Common/JitInterface/CorInfoImpl.cs | Plumbs asyncContinuation global and maps the new reloc kind into object-writer relocation types. |
| src/coreclr/tools/Common/Compiler/ObjectWriter/WasmObjectWriter.cs | Improves relocation error messages and centralizes global indices via WasmGlobalImports. |
| src/coreclr/tools/Common/Compiler/ObjectWriter/WasmGlobalImports.cs | New shared constants for imported Wasm global indices (including async continuation). |
| src/coreclr/tools/Common/Compiler/DependencyAnalysis/WasmWellKnownGlobalSymbolNode.cs | Adds symbol name for __async_continuation. |
| src/coreclr/tools/Common/Compiler/DependencyAnalysis/NodeFactory.Wasm.cs | Adds node factory caching for the new well-known global symbol. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/JitInterface/CorInfoImpl.ReadyToRun.cs | Avoids direct-call optimization for async resumption stubs on Wasm (routes through indirection). |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/ILCompiler.ReadyToRun.csproj | Links WasmGlobalImports.cs into ILCompiler.ReadyToRun. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmR2RToInterpreterThunkNode.cs | Forwards async continuation across R2R→interpreter thunks (including generic-context ordering special-case). |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmInterpreterToR2RThunkNode.cs | Adds async-aware interp→R2R thunk signature and forwards the continuation via the shared global. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/WasmImportThunk.cs | Updates import thunk emission to store/forward the async continuation value as needed. |
| src/coreclr/tools/aot/ILCompiler.ReadyToRun/Compiler/DependencyAnalysis/ReadyToRun/ResumptionStubEntryPointSignature.cs | Emits Wasm-specific relocation for resumption stub entry points (table index in data). |
| src/coreclr/jit/lowerwasm.cpp | Forces managed calls to be treated as indirect on Wasm (no “in-range” direct-call assumption). |
| src/coreclr/jit/instrswasm.h | Adds i32.const_dataoffs pseudo-instruction for data-section offset constants. |
| src/coreclr/jit/emitwasm.h | Declares emitDataOffsetConstant helper. |
| src/coreclr/jit/emitwasm.cpp | Implements emitDataOffsetConstant and adds IF_DATAOFFS emission/output/disasm support. |
| src/coreclr/jit/emitfmtswasm.h | Adds IF_DATAOFFS instruction format. |
| src/coreclr/jit/emit.cpp | Stores async resume Resume as a Wasm table index in data and records WASM_TABLE_INDEX_I32 relocations. |
| src/coreclr/jit/codegenwasm.cpp | Implements Wasm codegen for async continuation/global handling and async resume info addressing. |
| src/coreclr/jit/codegenlinear.cpp | Adjusts Wasm #ifdef factoring so async resume recording helpers are available where needed. |
| src/coreclr/jit/codegencommon.cpp | Clears async continuation (global on Wasm) on normal returns from async methods. |
| src/coreclr/jit/codegen.h | Adds Wasm-only helpers to store/clear the async continuation global. |
| src/coreclr/jit/async.cpp | Excludes the Wasm shadow stack pointer local from async save/restore sets. |
| src/coreclr/inc/jiteeversionguid.h | Bumps the JIT/EE interface GUID due to interface shape changes. |
| src/coreclr/inc/corinfo.h | Adds new reloc kind and extends CORINFO_WASM_WELLKNOWN_GLOBALS with asyncContinuation. |
| src/coreclr/hosts/corerun/wasm/libCorerun.js | Creates/imports a shared WebAssembly.Global for async continuation and exposes C-callable accessors. |
| docs/design/coreclr/botr/readytorun-format.md | Updates docs to reflect 'a' async continuation marker in Wasm signature strings. |
|
Going to revise things so the async return global is "owned" by the runtime so hosts don't need to define / export accessors. |
Make the runtime-async continuation global owned and exported by the runtime module (an inline-asm wasm global in helpers.cpp) and imported by every webcil, instead of a host-JS-created global with JS accessors. This defines RuntimeAsync_Load/StoreAsyncContinuation in the runtime itself, so browserhost's dotnet.native.wasm links (it was failing with undefined symbols, breaking the browser/wasi CoreCLR CI legs). corerun now wires webcils to wasmExports.__async_continuation. Also addresses PR review: * InvokeCalliStub keeps the 3-arg interpreter cookie and reads the continuation from the shared global after the call, instead of casting to a 4-arg cookie the generated CallFunc helpers never provide. * WasmLowering.RaiseSignature derives the generic-context hidden-pointer char from PointerSize (i32/i64) instead of hard-coding 'i'. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452
The async continuation accessors carry an Object* bit-pattern through an i32 wasm global. Use uint32_t instead of int32_t so the pointer->integer round-trip stays bit-preserving for references with the high bit set (wasm32 memory can exceed 2 GB), avoiding implementation-defined uintptr_t->int32_t conversions. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452
…expand, drop Wasi optimistic-ISA disable Rebased for the v2 cleanup. Keeps runtime-async=on (now unconditional in main + dotnet#131167 provides wasm async R2R codegen), so no async-off gate. corerun.cpp R2R host does NOT define the RuntimeAsync accessors (helpers.cpp from dotnet#131167 now owns __async_continuation + the accessors).
…lobal wiring dotnet#131167 makes the runtime own + export __async_continuation (browser wires it via libCorerun.js). The standalone wasi corerun needs an explicit --export so --gc-sections keeps it and the offline merge can wire each composite's webcil.asyncContinuation import to this same runtime global.
…expand, drop Wasi optimistic-ISA disable Rebased for the v2 cleanup. Keeps runtime-async=on (now unconditional in main + dotnet#131167 provides wasm async R2R codegen), so no async-off gate. corerun.cpp R2R host does NOT define the RuntimeAsync accessors (helpers.cpp from dotnet#131167 now owns __async_continuation + the accessors).
…lobal wiring dotnet#131167 makes the runtime own + export __async_continuation (browser wires it via libCorerun.js). The standalone wasi corerun needs an explicit --export so --gc-sections keeps it and the offline merge can wire each composite's webcil.asyncContinuation import to this same runtime global.
The R2R-to-interpreter thunk re-published the async continuation to the shared global from the transition-block arg slot after the call. On wasm that slot is never updated with the callee's result (the register area is unused; the runtime writes the continuation straight to the global in ExecuteInterpretedMethod), so the thunk overwrote the correct value with the stale inbound arg - breaking suspensions that cross an R2R-to-interpreter async call. Drop the post-call store; the runtime is the authoritative publisher. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452
- Instead of embedding a table index in the resume info, use a new R2R fixup to place the right PortableEntryPoint into the location - Tweak InterpreterToR2RThunk generation - Always use the standard MehtodSignature for InterpreterToR2RThunks - Build the ArgIterator based on having an async continuation - Always pass 0 as the async continuation parameter. The only case where a non-zero value is passed is on resumptions, and those always pass through the R2R'd resumption stub, not the interpter to R2R thunk
Fix runtime async on wasm
# Conflicts: # src/coreclr/inc/jiteeversionguid.h # src/coreclr/inc/readytorun.h # src/coreclr/jit/emitwasm.h # src/coreclr/nativeaot/Runtime/inc/ModuleHeaders.h # src/coreclr/tools/Common/Internal/Runtime/ModuleHeaders.cs
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452
davidwrighton
left a comment
There was a problem hiding this comment.
My concerns have been addressed, although I think that @jakobbotsch's comments about making the clr-abi change simpler have merit.
- clr-abi.md: compact wasm continuation-return table row (jakobbotsch) - derive generic-context char from PointerSize for wasm64 (3 thunk nodes) - fix misleading roundtrip assert message and WasmGlobalImports doc reference Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452
|
I have to respin CI for a new Guid, so will fix the doc/comment/diagnostic issues above too. |
|
@davidwrighton need a re-approval |
Adds Wasm R2R support for runtime-async methods.
The continuation is returned by a Wasm global. The continuation global is referenced through the recently introduced
CORINFO_WASM_WELLKNOWN_GLOBALS.Per-method async resume info is emitted into read-only data. The info
Resumefield points to code, so is emitted as a relocatable function-table index, requiring a new fixup type (function index in data).AsyncResumptionStub target calls are routed through MethodEntrypoint on Wasm (no direct code-pointer call exists). We may need to revisit this some day to ensure the right IL version is being invoked.
The async continuation is encoded via a new 'a' signature marker. The interp<->R2R thunks and the delay-load import thunk explicitly forward the continuation as needed. There is perhaps too much ceremony in the code to get correct argument ordering.
The Wasm shadow stack pointer is a caller-established local, not continuation state, so it is excluded from async save/restore in JIT codegen.
Adding a new Wasm global requires creating a new JIT Guid.
Fixes #130952.
Copilot-Session: 760f2e57-f8d3-478b-a9cb-de67d71c9452