[Wasm RyuJIT] Spill live ref/byref values to pinned stack slots at calls#129059
[Wasm RyuJIT] Spill live ref/byref values to pinned stack slots at calls#129059kg wants to merge 6 commits into
Conversation
|
Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch |
There was a problem hiding this comment.
Pull request overview
This PR introduces a Wasm-specific mechanism intended to make ref/byref values GC-visible across call sites by injecting a new IR node (GT_WASM_SPILL_REF) and a new Wasm phase (WasmSpillRefs) that inserts these nodes and allocates pinned stack spill slots used during Wasm codegen.
Changes:
- Add
GT_WASM_SPILL_REFnode kind and operand iteration support. - Add
Compiler::WasmSpillRefsphase to insert spill nodes around calls and allocate spill locals. - Extend Wasm codegen/regalloc to track a spill index and (temporarily) force-enregister a scratch “splash zone” local.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/jit/regallocwasm.cpp | Forces the “splash zone” local to be treated as a reg candidate. |
| src/coreclr/jit/gtlist.h | Adds new Wasm node WASM_SPILL_REF. |
| src/coreclr/jit/gentree.cpp | Updates operand-edge iterator to treat GT_WASM_SPILL_REF as unary. |
| src/coreclr/jit/fgwasm.cpp | Implements Compiler::WasmSpillRefs to insert spill nodes and allocate spill locals. |
| src/coreclr/jit/compphases.h | Adds PHASE_WASM_SPILL_REFS. |
| src/coreclr/jit/compmemkind.h | Adds WasmSpillRefs memory kind. |
| src/coreclr/jit/compiler.h | Adds m_wasmSpillSlots field and WasmSpillRefs declaration. |
| src/coreclr/jit/compiler.cpp | Wires WasmSpillRefs into the Wasm compilation pipeline. |
| src/coreclr/jit/codegenwasm.cpp | Emits Wasm for GT_WASM_SPILL_REF and resets spill index at calls. |
| src/coreclr/jit/codegenlinear.cpp | Resets spill index at block boundaries on Wasm. |
| src/coreclr/jit/codegen.h | Adds wasmSpillRefIndex state. |
AndyAyersMS
left a comment
There was a problem hiding this comment.
Generally looks good.
I'm curious how this intersects/overlaps with LSRA's spill temp mechanism. Not saying we should use that here, but it likely serves a similar purpose.
| if (!op->TypeIs(TYP_REF, TYP_BYREF)) | ||
| return GenTree::VisitResult::Continue; | ||
|
|
||
| for (size_t i = defs.size(); i > 0; i--) |
There was a problem hiding this comment.
Probably worth commenting what this is doing (removing active defs once we find their use, and keeping the defs collection compact).
|
Still working on this, going to try moving to STORE_LCL_VAR/LCL_VAR like Jakob suggested. |
| varDsc->lvType = TYP_BYREF; | ||
| varDsc->lvPinned = true; | ||
| varDsc->lvImplicitlyReferenced = true; | ||
| varDsc->lvMustInit = true; | ||
| lvaSetVarDoNotEnregister(varNum, DoNotEnregisterReason::WasmGCVisibility); |
There was a problem hiding this comment.
It's true that this could be a problem, but I don't know if we need to do anything about it other than add a TODO-WASM comment for now
| GenTreeUnOp* spill = gtNewOperNode(GT_WASM_SPILL_REF, def->TypeGet(), def); | ||
| LIR::Use use; | ||
| noway_assert(LIR::AsRange(block).TryGetUse(def, &use)); | ||
| use.ReplaceWith(spill); | ||
| LIR::AsRange(block).InsertAfter(def, spill); | ||
| if (def->gtLIRFlags & LIR::Flags::MultiplyUsed) | ||
| { | ||
| JITDUMP("Transferring multiply-used flag from [%06u] to [%06u] for spill\n", Compiler::dspTreeID(def), Compiler::dspTreeID(spill)); | ||
| def->gtLIRFlags &= ~LIR::Flags::MultiplyUsed; | ||
| spill->gtLIRFlags |= LIR::Flags::MultiplyUsed; | ||
| } | ||
| anyChanges = true; |
There was a problem hiding this comment.
Is this the right way to do it? @AndyAyersMS @jakobbotsch
…ill node Remove the splash zone concept
|
Updated to use STORE_LCL_VAR and LCL_VAR instead, removing the splash zone var. |
| GenTreeLclVar *spill = gtNewStoreLclVarNode(spillSlot, def); | ||
| GenTreeLclVar *reload = gtNewLclVarNode(spillSlot); | ||
| LIR::Use use; | ||
| noway_assert(LIR::AsRange(block).TryGetUse(def, &use)); | ||
| use.ReplaceWith(reload); | ||
| LIR::AsRange(block).InsertAfter(def, spill); | ||
| LIR::AsRange(block).InsertAfter(spill, reload); | ||
| if (def->gtLIRFlags & LIR::Flags::MultiplyUsed) | ||
| { | ||
| JITDUMP("Transferring multiply-used flag from [%06u] to [%06u] for spill\n", Compiler::dspTreeID(def), Compiler::dspTreeID(reload)); | ||
| def->gtLIRFlags &= ~LIR::Flags::MultiplyUsed; | ||
| reload->gtLIRFlags |= LIR::Flags::MultiplyUsed; | ||
| } |
| LclVarDsc* const varDsc = lvaGetDesc(spillSlot); | ||
| varDsc->lvType = TYP_BYREF; | ||
| varDsc->lvPinned = true; | ||
| varDsc->lvImplicitlyReferenced = true; | ||
| varDsc->lvMustInit = true; |
There was a problem hiding this comment.
Isn't it fine to store a REF in a pinned BYREF? Or no?
There was a problem hiding this comment.
Storing is fine, it's the reloading that is a bit iffy, we have to trust the thing stored there was really a ref. In your case we know this but the IR doesn't.
I would probably just keep two pools for now like copilot suggests. Possibly reporting refs as refs is a bit cheaper for GC.
No description provided.