@@ -1649,6 +1649,112 @@ PhaseStatus Compiler::fgWasmControlFlow()
16491649 return PhaseStatus::MODIFIED_EVERYTHING;
16501650}
16511651
1652+ PhaseStatus Compiler::WasmSpillRefs ()
1653+ {
1654+ bool anyChanges = false ;
1655+
1656+ size_t highWaterMark = 0 ;
1657+ jitstd::vector<GenTree*> defs (getAllocator (CMK_WasmSpillRefs));
1658+
1659+ for (BasicBlock* const block : Blocks ())
1660+ {
1661+ // LIR edges cannot span blocks, so we can safely clear the list of live values per-block
1662+ defs.clear ();
1663+
1664+ for (GenTree* tree : LIR::AsRange (block))
1665+ {
1666+ if (tree->IsCall ())
1667+ {
1668+ highWaterMark = std::max (highWaterMark, defs.size ());
1669+
1670+ if (defs.size ())
1671+ {
1672+ JITDUMP (" Spilling %d live ref(s) for call\n " , defs.size ());
1673+ DISPNODE (tree);
1674+ for (GenTree* def : defs)
1675+ {
1676+ JITDUMP (" " );
1677+ DISPNODE (def);
1678+ GenTreeUnOp* spill = gtNewOperNode (GT_WASM_SPILL_REF, def->TypeGet (), def);
1679+ LIR::Use use;
1680+ noway_assert (LIR::AsRange (block).TryGetUse (def, &use));
1681+ use.ReplaceWith (spill);
1682+ LIR::AsRange (block).InsertAfter (def, spill);
1683+ anyChanges = true ;
1684+ }
1685+
1686+ defs.clear ();
1687+ }
1688+ }
1689+
1690+ // FIXME: Should this happen before the spilling of the live defs list?
1691+ // I think the answer is no, because live defs being passed as arguments to the current call
1692+ // are not guaranteed to ever end up in memory where the GC can see them unless we spill
1693+ // them. If we can somehow guarantee that all callees will spill their ref parameters
1694+ // immediately, we could do this before the block above.
1695+ // Remove used nodes from defs list, they're no longer meaningfully 'live'.
1696+ tree->VisitOperands ([&defs](GenTree* op) {
1697+ if (!op->IsValue ())
1698+ return GenTree::VisitResult::Continue;
1699+ if (!op->TypeIs (TYP_REF, TYP_BYREF))
1700+ return GenTree::VisitResult::Continue;
1701+
1702+ for (size_t i = defs.size (); i > 0 ; i--)
1703+ {
1704+ if (op == defs[i - 1 ])
1705+ {
1706+ defs[i - 1 ] = defs[defs.size () - 1 ];
1707+ defs.erase (defs.begin () + (defs.size () - 1 ), defs.end ());
1708+ break ;
1709+ }
1710+ }
1711+
1712+ return GenTree::VisitResult::Continue;
1713+ });
1714+
1715+ if (tree->IsValue () && tree->TypeIs (TYP_REF, TYP_BYREF) && !tree->OperIs (GT_WASM_SPILL_REF))
1716+ {
1717+ // TODO: Can we skip this for GT_LCL_VAR when it lives in memory? Or is it possible
1718+ // that the LCL_VAR has been modified since it was loaded onto the Wasm stack?
1719+ defs.push_back (tree);
1720+ }
1721+ }
1722+ }
1723+
1724+ JITDUMP (" High water mark for refs was %d\n " , highWaterMark);
1725+ if (highWaterMark == 0 )
1726+ return PhaseStatus::MODIFIED_NOTHING;
1727+
1728+ m_wasmSpillSlots = new (this , CMK_WasmSpillRefs) jitstd::vector<unsigned >(highWaterMark + 1 , 0 , getAllocator (CMK_WasmSpillRefs));
1729+
1730+ // Allocate a temporary wasm local to use as a scratch slot during spills
1731+ {
1732+ const unsigned varNum = lvaGrabTemp (false DEBUGARG (" WasmSpillRefs splash zone" ));
1733+ LclVarDsc* const varDsc = lvaGetDesc (varNum);
1734+ // HACK: Make this TYP_I_IMPL because if we make it a REF or BYREF that may block enregistration
1735+ varDsc->lvType = TYP_I_IMPL;
1736+ varDsc->lvHasExplicitInit = true ;
1737+ varDsc->lvImplicitlyReferenced = true ;
1738+ // If we don't make this var tracked, regalloc will crash when allocating a register for it
1739+ varDsc->lvTracked = true ;
1740+ m_wasmSpillSlots->at (0 ) = varNum;
1741+ }
1742+
1743+ // Allocate N temporary refs to act as GC-visible storage for all spills that occur during execution
1744+ for (size_t i = 0 ; i < highWaterMark; i++)
1745+ {
1746+ const unsigned varNum = lvaGrabTemp (false DEBUGARG (" WasmSpillRefs spill slot" ));
1747+ LclVarDsc* const varDsc = lvaGetDesc (varNum);
1748+ varDsc->lvType = TYP_BYREF;
1749+ varDsc->lvPinned = true ;
1750+ varDsc->lvImplicitlyReferenced = true ;
1751+ lvaSetVarDoNotEnregister (varNum, DoNotEnregisterReason::WasmGCVisibility);
1752+ m_wasmSpillSlots->at (i + 1 ) = varNum;
1753+ }
1754+
1755+ return PhaseStatus::MODIFIED_EVERYTHING;
1756+ }
1757+
16521758#ifdef DEBUG
16531759
16541760// ------------------------------------------------------------------------
0 commit comments