Skip to content

Commit 6d13bf1

Browse files
committed
SIL Optimizer: handle dead-end infinite loops in StackNesting
Fixes a verifier crash caused by a not properly nested alloc-dealloc pair rdar://109204178
1 parent 8a611bf commit 6d13bf1

File tree

3 files changed

+59
-9
lines changed

3 files changed

+59
-9
lines changed

include/swift/SILOptimizer/Utils/StackNesting.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class StackNesting {
7777

7878
/// Used in the setup function to walk over the CFG.
7979
bool visited = false;
80+
81+
/// True for dead-end blocks, i.e. blocks from which there is no path to
82+
/// a function exit, e.g. blocks which end with `unreachable` or an
83+
/// infinite loop.
84+
bool isDeadEnd = false;
8085
};
8186

8287
/// Data stored for each stack location (= allocation).

lib/SILOptimizer/Utils/StackNesting.cpp

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -82,20 +82,38 @@ bool StackNesting::solve() {
8282
bool isNested = false;
8383
BitVector Bits(StackLocs.size());
8484

85+
StackList<SILBasicBlock *> deadEndWorklist(BlockInfos.getFunction());
86+
8587
// Initialize all bit fields to 1s, expect 0s for the entry block.
8688
bool initVal = false;
8789
for (auto bd : BlockInfos) {
8890
bd.data.AliveStackLocsAtEntry.resize(StackLocs.size(), initVal);
8991
initVal = true;
92+
93+
bd.data.isDeadEnd = !bd.block.getTerminator()->isFunctionExiting();
94+
if (!bd.data.isDeadEnd)
95+
deadEndWorklist.push_back(&bd.block);
96+
}
97+
98+
// Calculate the isDeadEnd block flags.
99+
while (!deadEndWorklist.empty()) {
100+
SILBasicBlock *b = deadEndWorklist.pop_back_val();
101+
for (SILBasicBlock *pred : b->getPredecessorBlocks()) {
102+
BlockInfo &bi = BlockInfos[pred];
103+
if (bi.isDeadEnd) {
104+
bi.isDeadEnd = false;
105+
deadEndWorklist.push_back(pred);
106+
}
107+
}
90108
}
91109

92110
// First step: do a forward dataflow analysis to get the live stack locations
93111
// at the block exits.
94-
// This is necessary to get the live locations at blocks which end in
95-
// unreachable instructions (otherwise the backward data flow would be
96-
// sufficient). The special thing about unreachable-blocks is that it's
97-
// okay to have alive locations at that point, i.e. locations which are never
98-
// dealloced. We cannot get such locations with a purly backward dataflow.
112+
// This is necessary to get the live locations at dead-end blocks (otherwise
113+
// the backward data flow would be sufficient).
114+
// The special thing about dead-end blocks is that it's okay to have alive
115+
// locations at that point (e.g. at an `unreachable`) i.e. locations which are
116+
// never dealloced. We cannot get such locations with a purly backward dataflow.
99117
do {
100118
changed = false;
101119

@@ -124,7 +142,7 @@ bool StackNesting::solve() {
124142
do {
125143
changed = false;
126144

127-
for (auto bd : llvm::reverse(BlockInfos)) {
145+
for (auto bd : llvm::reverse(BlockInfos)) {
128146
// Collect the alive-bits (at the block exit) from the successor blocks.
129147
for (SILBasicBlock *SuccBB : bd.block.getSuccessorBlocks()) {
130148
bd.data.AliveStackLocsAtExit |= BlockInfos[SuccBB].AliveStackLocsAtEntry;
@@ -134,14 +152,18 @@ bool StackNesting::solve() {
134152
&& Bits.any())
135153
&& "stack location is missing dealloc");
136154

137-
if (isa<UnreachableInst>(bd.block.getTerminator())) {
138-
// We treat unreachable as an implicit deallocation for all locations
139-
// which are still alive at this point.
155+
if (bd.data.isDeadEnd) {
156+
// We treat `unreachable` as an implicit deallocation for all locations
157+
// which are still alive at this point. The same is true for dead-end
158+
// CFG regions due to an infinite loop.
140159
for (int BitNr = Bits.find_first(); BitNr >= 0;
141160
BitNr = Bits.find_next(BitNr)) {
142161
// For each alive location extend the lifetime of all locations which
143162
// are alive at the allocation point. This is the same as we do for
144163
// a "real" deallocation instruction (see below).
164+
// In dead-end CFG regions we have to do that for all blocks (because
165+
// of potential infinite loops), whereas in "normal" CFG regions it's
166+
// sufficient to do it at deallocation instructions.
145167
Bits |= StackLocs[BitNr].AliveLocs;
146168
}
147169
bd.data.AliveStackLocsAtExit = Bits;
@@ -336,6 +358,8 @@ StackNesting::Changes StackNesting::fixNesting(SILFunction *F) {
336358
void StackNesting::dump() const {
337359
for (auto bd : BlockInfos) {
338360
llvm::dbgs() << "Block " << bd.block.getDebugID();
361+
if (bd.data.isDeadEnd)
362+
llvm::dbgs() << "(deadend)";
339363
llvm::dbgs() << ": entry-bits=";
340364
dumpBits(bd.data.AliveStackLocsAtEntry);
341365
llvm::dbgs() << ": exit-bits=";

test/SILOptimizer/stack_promotion.sil

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,3 +1156,24 @@ bb0(%0 : $UnownedLink):
11561156
%r = tuple ()
11571157
return %r : $()
11581158
}
1159+
1160+
// CHECK-LABEL: sil @dont_crash_with_wrong_stacknesting_with_infinite_loop
1161+
// CHECK: alloc_ref [stack] $XX
1162+
// CHECK-LABEL: } // end sil function 'dont_crash_with_wrong_stacknesting_with_infinite_loop'
1163+
sil @dont_crash_with_wrong_stacknesting_with_infinite_loop : $@convention(thin) () -> () {
1164+
bb0:
1165+
%0 = alloc_stack $Int
1166+
%1 = alloc_ref $XX
1167+
dealloc_stack %0 : $*Int
1168+
cond_br undef, bb1, bb2
1169+
bb1:
1170+
strong_release %1 : $XX
1171+
%4 = tuple ()
1172+
return %4 : $()
1173+
1174+
bb2:
1175+
br bb3
1176+
bb3:
1177+
br bb3
1178+
}
1179+

0 commit comments

Comments
 (0)