[Wasm RyuJIT] Spill live ref/byref values to pinned stack slots at calls#129059
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.
|
Still working on this, going to try moving to STORE_LCL_VAR/LCL_VAR like Jakob suggested. |
|
Updated to use STORE_LCL_VAR and LCL_VAR instead, removing the splash zone var. |
|
I had to come up with a different test program to get failures without this PR's changes - the previous test program was only failing because of some other bugs that AndyA has since fixed. public class C {
public string S = "";
public byte[]? Trash;
[MethodImpl(MethodImplOptions.NoInlining)]
public void AppendToS (string value) {
Trash = new byte[32768];
S += value;
}
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static int Main () {
Console.WriteLine("Hello world!");
string s = "";
for (int i = 0; i < 1000; i++) {
C c = new C();
for (int j = 0; j < 2000; j++)
c.AppendToS(j.ToString());
s = c.S;
}
Console.WriteLine(s);
return 42;
}With the spilling pass disabled this asserts inside the GC during execution, i.e. |
AndyAyersMS
left a comment
There was a problem hiding this comment.
Think this is close, though we probably still need slot zeroing.
Another option would be to try and report these slots as tracked, since we know their lifetimes, but that can happen later.
Interested to see the code size impact.
AndyAyersMS
left a comment
There was a problem hiding this comment.
LGTM
@jakobbotsch you want to take another look?
jakobbotsch
left a comment
There was a problem hiding this comment.
In general I would have preferred to keep the slot reuse and pinning optimization separate if a simpler "just create a new local for the LIR edge" would have sufficed for correctness.
That aside this looks good to me, I left mainly nits as comments. Feel free to ignore them.
Resolve conflict in compiler.h: keep WasmSpillSlot struct added by this branch alongside the blank line added on main. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Had to fix a merge conflict. |
|
@jakobbotsch can you re-approve (new repo policy) |
|
/ba-g persistent timeouts in some arm64 legs |
GC refs/byrefs can end up temporarily living on the Wasm shadow stack - where the GC can't see them - across calls, which creates a GC hole. This PR adds a pass that 'spills' these live refs/byrefs to dedicated pinned locals so that the GC won't move or collect those refs.