Skip to content

Commit 7f9d427

Browse files
committed
add stack spilling tests to ssa stack shuffler tests
1 parent 56644a2 commit 7f9d427

6 files changed

Lines changed: 163 additions & 1 deletion

File tree

test/libyul/ssa/StackShufflerTest.cpp

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ std::string_view constexpr parserKeyInitialStack {"initial"};
5050
std::string_view constexpr parserKeyStackTop {"targetStackTop"};
5151
std::string_view constexpr parserKeyTailSet {"targetStackTailSet"};
5252
std::string_view constexpr parserKeyStackSize {"targetStackSize"};
53+
std::string_view constexpr parserKeyAllowSpilling {"allowSpilling"};
54+
std::string_view constexpr parserKeyInitialSpilled {"initialSpilledSet"};
5355

5456
using Liveness = LivenessAnalysis::LivenessData;
5557
using 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),
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Seeds the spill set with v1 from the start. Even though v1 is in the tail set and not on the stack,
2+
// the shuffler treats it as freely generatable and finishes admissible without retries.
3+
allowSpilling: true
4+
initialSpilledSet: {v1}
5+
initial: [v3, v4, v5]
6+
targetStackTop: [v3, v4, v5]
7+
targetStackTailSet: {v1}
8+
targetStackSize: 3
9+
// ----
10+
// | 0 1 2
11+
// +---------------------
12+
// (initial)| v3 v4 v5
13+
// +---------------------
14+
// (target)| v3 v4 v5
15+
// Status: Admissible
16+
// Spilled: {v1}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// v1 is needed both as an arg AND in the liveOut tail set, but appears only once on the stack at the top.
2+
// The deep tail slots (depth 17 = offset 0) are beyond swap range, so v1 cannot be duplicated into the tail.
3+
// With allowSpilling, the shuffler reports StackTooDeep with culprit v1, the test driver adds v1 to the spill
4+
// set and retries; v1 is now freely generatable so the second iteration succeeds.
5+
allowSpilling: true
6+
initial: [JUNK, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v1]
7+
targetStackTop: [v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v1]
8+
targetStackTailSet: {v1}
9+
targetStackSize: 18
10+
// ----
11+
// | 0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
12+
// +------- +-----------------------------------------------------------------------------------------------------------------------
13+
// (initial)| * | v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v1
14+
// +------- +-----------------------------------------------------------------------------------------------------------------------
15+
// (target)| {v1} | v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v1
16+
// Status: Admissible
17+
// Spilled: {v1}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// Same setup as spill_arg_in_liveout but without allowSpilling: confirms the default behaviour is
2+
// preserved and the shuffler reports StackTooDeep without retrying.
3+
initial: [JUNK, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v1]
4+
targetStackTop: [v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v1]
5+
targetStackTailSet: {v1}
6+
targetStackSize: 18
7+
allowSpilling: false
8+
// ----
9+
// | 0 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
10+
// +------- +-----------------------------------------------------------------------------------------------------------------------
11+
// (initial)| * | v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v1
12+
// +------- +-----------------------------------------------------------------------------------------------------------------------
13+
// (target)| {v1} | v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v1
14+
// Status: StackTooDeep (culprit: v1)
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Every target arg is also in the tail set, but the tail region has zero room (tail size = 0).
2+
// Each fix-tail iteration reports the deepest still-needed value as StackTooDeep; with allowSpilling
3+
// the driver spills it and retries. After every value has been spilled, the stack is admissible
4+
// because the spilled values can be regenerated freely.
5+
allowSpilling: true
6+
initial: [v1, v2, v3, v4, v5, v6, v7, v8]
7+
targetStackTop: [v1, v2, v3, v4, v5, v6, v7, v8]
8+
targetStackTailSet: {v1, v2, v3, v4, v5, v6, v7, v8}
9+
targetStackSize: 8
10+
// ----
11+
// | 0 1 2 3 4 5 6 7
12+
// +--------------------------------------------------------
13+
// (initial)| v1 v2 v3 v4 v5 v6 v7 v8
14+
// +--------------------------------------------------------
15+
// (target)| v1 v2 v3 v4 v5 v6 v7 v8
16+
// Status: Admissible
17+
// Spilled: {v1, v2, v3, v4, v5, v6, v7, v8}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
allowSpilling: true
2+
initial: [JUNK, v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20]
3+
targetStackTop: [v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, v13, v14, v15, v16, v17, v18, v19, v20]
4+
// ----
5+
// | 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | 20
6+
// +-------------------------------------------------------------------------------------------------------------------------------------------- +-------
7+
// (initial)| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 | v20
8+
// SWAP1| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v20 | v19
9+
// SWAP2| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v19 v20 | v18
10+
// SWAP3| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v18 v19 v20 | v17
11+
// SWAP4| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v17 v18 v19 v20 | v16
12+
// SWAP5| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v16 v17 v18 v19 v20 | v15
13+
// SWAP6| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v15 v16 v17 v18 v19 v20 | v14
14+
// SWAP7| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v14 v15 v16 v17 v18 v19 v20 | v13
15+
// SWAP8| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v13 v14 v15 v16 v17 v18 v19 v20 | v12
16+
// SWAP9| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v12 v13 v14 v15 v16 v17 v18 v19 v20 | v11
17+
// SWAP10| * v1 v2 v3 v4 v5 v6 v7 v8 v9 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 | v10
18+
// SWAP11| * v1 v2 v3 v4 v5 v6 v7 v8 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 | v9
19+
// SWAP12| * v1 v2 v3 v4 v5 v6 v7 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 | v8
20+
// SWAP13| * v1 v2 v3 v4 v5 v6 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 | v7
21+
// SWAP14| * v1 v2 v3 v4 v5 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 | v6
22+
// SWAP15| * v1 v2 v3 v4 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 | v5
23+
// SWAP16| * v1 v2 v3 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 | v4
24+
// POP| * v1 v2 v3 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20
25+
// POP| * v1 v2 v3 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19
26+
// SWAP15| * v1 v2 v19 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v3
27+
// SWAP16| * v1 v3 v19 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v2
28+
// PUSH v4| * v1 v3 v19 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v2 v4
29+
// SWAP16| * v1 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v2 v19
30+
// SWAP1| * v1 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v2
31+
// POP| * v1 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19
32+
// PUSH v2| * v1 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v2
33+
// POP| * v1 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19
34+
// PUSH v2| * v1 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v2
35+
// POP| * v1 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19
36+
// PUSH v2| * v1 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v2
37+
// ...|
38+
// +-------------------------------------------------------------------------------------------------------------------------------------------- +-------
39+
// (target)| v1 v2 v3 v4 v5 v6 v7 v8 v9 v10 v11 v12 v13 v14 v15 v16 v17 v18 v19 v20 |
40+
// Status: MaxIterationsReached
41+
// Spilled: {v1, v2, v4, v20}

0 commit comments

Comments
 (0)