@@ -50,6 +50,8 @@ std::string_view constexpr parserKeyInitialStack {"initial"};
5050std::string_view constexpr parserKeyStackTop {" targetStackTop" };
5151std::string_view constexpr parserKeyTailSet {" targetStackTailSet" };
5252std::string_view constexpr parserKeyStackSize {" targetStackSize" };
53+ std::string_view constexpr parserKeyAllowSpilling {" allowSpilling" };
54+ std::string_view constexpr parserKeyInitialSpilled {" initialSpilledSet" };
5355
5456using Liveness = LivenessAnalysis::LivenessData;
5557using Slot = StackSlot;
@@ -178,6 +180,8 @@ struct ShuffleTestInput
178180 std::optional<TestStack::Data> targetStackTop;
179181 Liveness targetStackTailSet{};
180182 std::optional<size_t > targetStackSize;
183+ bool allowSpilling = false ;
184+ SpilledVariables initialSpilledSet{};
181185
182186 bool valid () const
183187 {
@@ -236,6 +240,18 @@ struct ShuffleTestInput
236240 else
237241 throw std::runtime_error (fmt::format (" Couldn't parse targetStackSize: {}" , value));
238242 }
243+ else if (key == parserKeyAllowSpilling)
244+ {
245+ if (value == " true" )
246+ result.allowSpilling = true ;
247+ else if (value == " false" )
248+ result.allowSpilling = false ;
249+ else
250+ throw std::runtime_error (fmt::format (" Couldn't parse allowSpilling: {}" , value));
251+ }
252+ else if (key == parserKeyInitialSpilled)
253+ for (auto const & [valueId, _]: parseLiveness (value))
254+ result.initialSpilledSet .spill (valueId);
239255
240256 }
241257
@@ -475,6 +491,34 @@ explicitly provided.)";
475491 auto stackData = *testConfig.initial ;
476492 std::ostringstream oss;
477493 StackShufflerResult shuffleResult;
494+ SpilledVariables spillSet = testConfig.initialSpilledSet ;
495+
496+ // First, when spilling is allowed, run the shuffler repeatedly without recording to determine
497+ // the final spill set. Each iteration starts from the initial stack and adds the culprit of a
498+ // recoverable StackTooDeep to the spill set.
499+ if (testConfig.allowSpilling )
500+ while (true )
501+ {
502+ auto scratch = *testConfig.initial ;
503+ TestStack stack (scratch, {});
504+ auto const result = StackShuffler<StackManipulationCallbacks>::shuffle (
505+ stack,
506+ *testConfig.targetStackTop ,
507+ testConfig.targetStackTailSet ,
508+ *testConfig.targetStackSize ,
509+ &spillSet
510+ );
511+ if (
512+ result.status != StackShufflerResult::Status::StackTooDeep ||
513+ !result.culprit .isValueID () ||
514+ result.culprit .isLiteralValueID () ||
515+ spillSet.isSpilled (result.culprit .valueID ())
516+ )
517+ break ;
518+ spillSet.spill (result.culprit .valueID ());
519+ }
520+
521+ // Final shuffle with the (possibly pre-populated) spill set, recording the trace.
478522 {
479523 TraceRecorder trace (oss, *testConfig.targetStackTop , testConfig.targetStackTailSet , *testConfig.targetStackSize );
480524 trace.record (" (initial)" , *testConfig.initial );
@@ -486,7 +530,8 @@ explicitly provided.)";
486530 stack,
487531 *testConfig.targetStackTop ,
488532 testConfig.targetStackTailSet ,
489- *testConfig.targetStackSize
533+ *testConfig.targetStackSize ,
534+ &spillSet
490535 );
491536 if (shuffleResult.status == StackShufflerResult::Status::MaxIterationsReached)
492537 trace.truncate (30 );
@@ -506,6 +551,16 @@ explicitly provided.)";
506551 case StackShufflerResult::Status::Continue:
507552 yulAssert (false , " Unexpected Continue status from shuffle()" );
508553 }
554+ if (testConfig.allowSpilling )
555+ oss << fmt::format (
556+ " Spilled: {{{}}}\n " ,
557+ fmt::join (
558+ spillSet.spilledValues () | ranges::views::transform (
559+ [](auto const & id) { return slotToString (StackSlot::makeValueID (id)); }
560+ ),
561+ " , "
562+ )
563+ );
509564 // check stack data
510565 if (shuffleResult.status == StackShufflerResult::Status::Admissible)
511566 {
@@ -514,6 +569,8 @@ explicitly provided.)";
514569 yulAssert (stackData.size () == *testConfig.targetStackSize );
515570 for (const auto & valueID: testConfig.targetStackTailSet | ranges::views::keys)
516571 {
572+ if (spillSet.isSpilled (valueID))
573+ continue ;
517574 auto const findIt = ranges::find (
518575 stackData.begin (),
519576 stackData.begin () + static_cast <std::ptrdiff_t >(tailSize),
0 commit comments