@@ -45,36 +45,66 @@ void assertLayoutCompatibility(StackData const& _layout1, StackData const& _layo
4545void CodeTransform::run
4646(
4747 AbstractAssembly& _assembly,
48+ ControlFlowGraphs& _controlFlowGraphs,
4849 ControlFlowGraphsLiveness const & _controlFlowLiveness,
4950 BuiltinContext& _builtinContext
5051)
5152{
53+ yulAssert (&_controlFlowLiveness.controlFlowGraphs .get () == &_controlFlowGraphs);
5254 yulAssert (!_controlFlowLiveness.cfgLiveness .empty ());
53- ControlFlowGraphs const & controlFlowGraphs = _controlFlowLiveness.controlFlowGraphs .get ();
54- yulAssert (controlFlowGraphs.functionGraphs .size () == _controlFlowLiveness.cfgLiveness .size ());
55- FunctionLabels const functionLabels = registerFunctionLabels (_assembly, controlFlowGraphs);
56- CallGraph const callGraph (controlFlowGraphs);
57-
58- for (std::size_t functionIndex = 0 ; functionIndex < controlFlowGraphs.functionGraphs .size (); ++functionIndex)
55+ yulAssert (_controlFlowGraphs.functionGraphs .size () == _controlFlowLiveness.cfgLiveness .size ());
56+ FunctionLabels const functionLabels = registerFunctionLabels (_assembly, _controlFlowGraphs);
57+ CallGraph const callGraph (_controlFlowGraphs);
58+
59+ std::size_t const numCFGs = _controlFlowGraphs.functionGraphs .size ();
60+ std::vector<CallSites> callSitesPerCFG;
61+ std::vector<SSACFGStackLayout> layouts;
62+ std::vector<spill::SpillSet> spillSetsPerCFG;
63+ callSitesPerCFG.reserve (numCFGs);
64+ layouts.reserve (numCFGs);
65+ spillSetsPerCFG.reserve (numCFGs);
66+
67+ for (std::size_t functionIndex = 0 ; functionIndex < numCFGs; ++functionIndex)
5968 {
60- std::unique_ptr<SSACFG > const & functionGraphPtr = controlFlowGraphs .functionGraphs [functionIndex];
69+ std::unique_ptr<SSACFG > const & functionGraphPtr = _controlFlowGraphs .functionGraphs [functionIndex];
6170 yulAssert (functionGraphPtr);
6271 SSACFG const & cfg = *functionGraphPtr;
63- auto const callSites = gatherCallSites (cfg);
6472 auto const & liveness = _controlFlowLiveness.cfgLiveness [functionIndex];
6573 yulAssert (liveness);
6674 auto const graphID = static_cast <ControlFlowGraphs::FunctionGraphID>(functionIndex);
75+ callSitesPerCFG.push_back (gatherCallSites (cfg));
6776 bool const spillingAllowed = !callGraph.isRecursive (graphID);
68- auto const stackLayoutGeneratorResult = StackLayoutGenerator::generate (*liveness, callSites, graphID, spillingAllowed);
77+ auto [layout, spillSet] = StackLayoutGenerator::generate (*liveness, callSitesPerCFG.back (), graphID, spillingAllowed);
78+ layouts.push_back (std::move (layout));
79+ spillSetsPerCFG.push_back (std::move (spillSet));
80+ }
81+
82+ for (std::size_t functionIndex = 0 ; functionIndex < numCFGs; ++functionIndex)
83+ spillSetsPerCFG[functionIndex].closeUnderReachabilityConstraints (
84+ *_controlFlowGraphs.functionGraphs [functionIndex],
85+ layouts[functionIndex]
86+ );
87+
88+ // build up global addressing based on the spill sets
89+ spill::MemoryAddressing const addressing (_controlFlowGraphs, spillSetsPerCFG);
90+
91+ for (std::size_t functionIndex = 0 ; functionIndex < _controlFlowGraphs.functionGraphs .size (); ++functionIndex)
92+ {
93+ SSACFG const & cfg = *_controlFlowGraphs.functionGraphs [functionIndex];
94+ auto const graphID = static_cast <ControlFlowGraphs::FunctionGraphID>(functionIndex);
95+ auto const & liveness = _controlFlowLiveness.cfgLiveness [functionIndex];
96+ yulAssert (liveness);
6997 CodeTransform transform (
7098 _assembly,
7199 _builtinContext,
72- controlFlowGraphs ,
100+ _controlFlowGraphs ,
73101 functionLabels,
74- callSites ,
102+ callSitesPerCFG[functionIndex] ,
75103 cfg,
76- stackLayoutGeneratorResult.layout ,
77- graphID
104+ layouts[functionIndex],
105+ spillSetsPerCFG[functionIndex],
106+ graphID,
107+ addressing
78108 );
79109 transform (cfg.entry );
80110 }
@@ -120,7 +150,9 @@ CodeTransform::CodeTransform(
120150 CallSites const & _callSites,
121151 SSACFG const & _cfg,
122152 SSACFGStackLayout const & _stackLayout,
123- ControlFlowGraphs::FunctionGraphID _graphID
153+ spill::SpillSet const & _spillSet,
154+ ControlFlowGraphs::FunctionGraphID _graphID,
155+ spill::MemoryAddressing const & _addressing
124156):
125157 m_assembly(_assembly),
126158 m_builtinContext(_builtinContext),
@@ -129,6 +161,7 @@ CodeTransform::CodeTransform(
129161 m_callSites(_callSites),
130162 m_cfg(_cfg),
131163 m_stackLayout(_stackLayout),
164+ m_spillSet(_spillSet),
132165 m_graphID(_graphID),
133166 m_blockIsTransformed(_cfg.numBlocks(), false),
134167 m_blockLabels([this ] {
@@ -138,11 +171,17 @@ CodeTransform::CodeTransform(
138171 blockLabels.push_back (m_assembly.newLabelId ());
139172 return blockLabels;
140173 }()),
174+ m_spillEmitter([&]() -> std::optional<spill::Emitter> {
175+ if (_spillSet.numSpilled () > 0 )
176+ return spill::Emitter (_addressing, _graphID, _assembly);
177+ return std::nullopt ;
178+ }()),
141179 m_assemblyCallbacks{
142180 .cfg = &_cfg,
143181 .assembly = &_assembly,
144182 .callSites = &_callSites,
145- .returnLabels = &m_returnLabels
183+ .returnLabels = &m_returnLabels,
184+ .spillEmitter = m_spillEmitter ? &*m_spillEmitter : nullptr
146185 },
147186 m_stackData ([&]
148187 {
@@ -167,6 +206,11 @@ CodeTransform::CodeTransform(
167206 for (auto const & arg: m_cfg.arguments | ranges::views::reverse)
168207 expectedStackTop.push_back (StackSlot::makeValue (_cfg, arg));
169208 assertLayoutCompatibility (m_stack.data (), expectedStackTop);
209+
210+ // Spilled function args need an `mstore` at function entry so later `mload`s see a populated slot
211+ if (m_spillEmitter && isFunctionGraph)
212+ for (InstId const argId: m_cfg.arguments )
213+ spillStore (argId);
170214}
171215
172216void CodeTransform::operator ()(SSACFG ::BlockId const _blockId)
@@ -184,18 +228,28 @@ void CodeTransform::operator()(SSACFG::BlockId const _blockId)
184228 auto const & block = m_cfg.block (_blockId);
185229
186230 std::size_t operationIndex = 0 ;
187- m_cfg.forEachOperation (block, [&](InstId const instId, SSACFG ::Inst const &) {
188- yulAssert (operationIndex < blockLayout->operationIn .size ());
189- auto const & operationInLayout = blockLayout->operationIn [operationIndex];
190- (*this )(instId, operationInLayout);
191- ++operationIndex;
192- });
231+
232+ // Iterate every Inst in the block in scheduled order. Only Operations advance codegen;
233+ // Phis are otherwise pure stack assertions (already materialized on the block's stackIn).
234+ for (InstId const instId: block.instructions )
235+ {
236+ SSACFG ::Inst const & inst = m_cfg.inst (instId);
237+ if (inst.isPhi ())
238+ // this is a no-op for not spilled phis
239+ spillStore (instId);
240+ if (inst.isOperation ())
241+ {
242+ yulAssert (operationIndex < blockLayout->operationIn .size ());
243+ (*this )(instId, blockLayout->operationIn [operationIndex]);
244+ ++operationIndex;
245+ }
246+ }
193247 yulAssert (operationIndex == blockLayout->operationIn .size ());
194248
195249 // Shuffle to the block's exit layout before dispatching the exit.
196250 // This ensures the condition is on top for ConditionalJump, phi pre-images are
197251 // in the right positions for jumps, and return values are accessible for FunctionReturn.
198- auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle (m_stack, blockLayout->exitIn );
252+ auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle (m_stack, blockLayout->exitIn , &m_spillSet );
199253 yulAssert (shuffleResult.status == StackShufflerResult::Status::Admissible);
200254
201255 // handle the block exit
@@ -221,7 +275,7 @@ void CodeTransform::operator()(InstId _instId, StackData const& _operationInputL
221275
222276 // prepare stack for operation
223277 {
224- auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle (m_stack, _operationInputLayout);
278+ auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle (m_stack, _operationInputLayout, &m_spillSet );
225279 yulAssert (shuffleResult.status == StackShufflerResult::Status::Admissible);
226280 }
227281
@@ -322,6 +376,11 @@ void CodeTransform::operator()(InstId _instId, StackData const& _operationInputL
322376 for (InstId const id: m_cfg.outputsOf (_instId))
323377 m_stack.push <false >(StackSlot::makeValue (m_cfg, id));
324378
379+ // Each output the layout decided to spill gets its `mstore` here
380+ if (m_spillEmitter)
381+ for (InstId const outputId: m_cfg.outputsOf (_instId))
382+ spillStore (outputId);
383+
325384 yulAssert (m_stack.size () == baseHeight + numOutputs);
326385 for (auto const & [stackEntry, output]: ranges::views::zip (
327386 m_stack.data () | ranges::views::take_last (numOutputs),
@@ -334,6 +393,32 @@ void CodeTransform::operator()(InstId _instId, StackData const& _operationInputL
334393 );
335394}
336395
396+ void CodeTransform::spillStore (InstId const _value)
397+ {
398+ if (!m_spillEmitter || !m_spillSet.isSpilled (_value))
399+ return ;
400+
401+ // Bring `_value` to the stack top so that the `mstore` below consumes it
402+ StackData target = m_stack.data ();
403+ target.push_back (StackSlot::makeValue (m_cfg, _value));
404+ // addr(_value) is the slot THIS `mstore` populates, so we have to exclude it from the spill set to
405+ // prevent the shuffler from just `mload`ing it back
406+ spill::SpillSet const spillSetExcludingSpillee = m_spillSet.without (_value);
407+ auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle (m_stack, target, &spillSetExcludingSpillee);
408+ yulAssert (
409+ shuffleResult.status == StackShufflerResult::Status::Admissible,
410+ fmt::format (
411+ " shuffler failed to bring spilled value {} to top (status={})" ,
412+ _value,
413+ static_cast <int >(shuffleResult.status )
414+ )
415+ );
416+
417+ m_spillEmitter->emitStore (_value);
418+ // `mstore` consumed the value; the address it pushed never persists on the symbolic stack
419+ m_stack.pop <false >();
420+ }
421+
337422void CodeTransform::operator ()(SSACFG ::BlockId const &, SSACFG ::BasicBlock::MainExit const &)
338423{
339424 yulAssert (static_cast <int >(m_stack.size ()) == m_assembly.stackHeight ());
@@ -446,7 +531,7 @@ void CodeTransform::prepareBlockExitStack(StackData const& _target, PhiInverse c
446531 auto const pulledBackTarget = stackPreImage (m_cfg, _target, _phiInverse);
447532 // shuffle to target
448533 {
449- auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle (m_stack, pulledBackTarget);
534+ auto const shuffleResult = StackShuffler<AssemblyCallbacks>::shuffle (m_stack, pulledBackTarget, &m_spillSet );
450535 yulAssert (shuffleResult.status == StackShufflerResult::Status::Admissible);
451536 }
452537 // check that shuffling was successful
0 commit comments