Skip to content

Commit 787c35f

Browse files
committed
SILOptimizer: correctly handle unreachable blocks in StackNesting.
Instead of some special treatment of unreachable blocks, model unreachable as implicitly deallocating all alive stack locations at that point. This requires an additional forward-dataflow pass. But it now correctly models the problem and fixes a compiler crash. rdar://problem/47402694
1 parent dd0cddf commit 787c35f

File tree

4 files changed

+120
-45
lines changed

4 files changed

+120
-45
lines changed

include/swift/SILOptimizer/Utils/StackNesting.h

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,12 +84,8 @@ class StackNesting {
8484
/// The bit-set of alive stack locations at the block entry.
8585
BitVector AliveStackLocsAtEntry;
8686

87-
/// True if there is a path from this block to a function-exit.
88-
///
89-
/// In other words: this block does not end in an unreachable-instruction.
90-
/// This flag is only used for verifying that the lifetime of a stack
91-
/// location does not end at the end of a block.
92-
bool ExitReachable = false;
87+
/// The bit-set of alive stack locations at the block exit.
88+
BitVector AliveStackLocsAtExit;
9389

9490
BlockInfo(SILBasicBlock *Block) : Block(Block) { }
9591
};
@@ -165,6 +161,19 @@ class StackNesting {
165161
SILInstruction *InsertionPoint,
166162
Optional<SILLocation> Location);
167163

164+
/// Reeturns the location bit number for a stack allocation instruction.
165+
int bitNumberForAlloc(SILInstruction *AllocInst) {
166+
assert(AllocInst->isAllocatingStack());
167+
return StackLoc2BitNumbers[cast<SingleValueInstruction>(AllocInst)];
168+
}
169+
170+
/// Reeturns the location bit number for a stack deallocation instruction.
171+
int bitNumberForDealloc(SILInstruction *DeallocInst) {
172+
assert(DeallocInst->isDeallocatingStack());
173+
auto *AllocInst = cast<SingleValueInstruction>(DeallocInst->getOperand(0));
174+
return bitNumberForAlloc(AllocInst);
175+
}
176+
168177
/// Modifies the SIL to end up with a correct stack nesting.
169178
///
170179
/// Returns the status of what changes were made.

lib/SILOptimizer/Utils/StackNesting.cpp

Lines changed: 67 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,6 @@ void StackNesting::setup(SILFunction *F) {
134134
}
135135
}
136136
}
137-
if (BI->Block->getTerminator()->isFunctionExiting())
138-
BI->ExitReachable = true;
139-
140137
for (auto *SuccBB : BI->Block->getSuccessorBlocks()) {
141138
BlockInfo *&SuccBI = BlockMapping[SuccBB];
142139
if (!SuccBI) {
@@ -165,33 +162,78 @@ bool StackNesting::solve() {
165162
bool isNested = false;
166163
BitVector Bits(StackLocs.size());
167164

168-
// Iterate until we reach a fixed-point.
165+
// Initialize all bit fields to 1s, expect 0s for the entry block.
166+
bool initVal = false;
167+
for (BlockInfo &BI : BlockInfos) {
168+
BI.AliveStackLocsAtEntry.resize(StackLocs.size(), initVal);
169+
initVal = false;
170+
}
171+
172+
// First step: do a forward dataflow analysis to get the live stack locations
173+
// at the block exits.
174+
// This is necessary to get the live locations at blocks which end in
175+
// unreachable instructions (otherwise the backward data flow would be
176+
// sufficient). The special thing about unreachable-blocks is that it's
177+
// okay to have alive locations at that point, i.e. locations which are never
178+
// dealloced. We cannot get such locations with a purly backward dataflow.
179+
do {
180+
changed = false;
181+
182+
for (BlockInfo &BI : BlockInfos) {
183+
Bits = BI.AliveStackLocsAtEntry;
184+
for (SILInstruction *StackInst : BI.StackInsts) {
185+
if (StackInst->isAllocatingStack()) {
186+
Bits.set(bitNumberForAlloc(StackInst));
187+
} else if (StackInst->isDeallocatingStack()) {
188+
Bits.reset(bitNumberForDealloc(StackInst));
189+
}
190+
}
191+
if (Bits != BI.AliveStackLocsAtExit) {
192+
BI.AliveStackLocsAtExit = Bits;
193+
assert(!(BI.Block->getTerminator()->isFunctionExiting() && Bits.any())
194+
&& "stack location is missing dealloc");
195+
changed = true;
196+
}
197+
// Merge the bits into the successors.
198+
for (BlockInfo *SuccBI : BI.Successors) {
199+
SuccBI->AliveStackLocsAtEntry &= Bits;
200+
}
201+
}
202+
} while (changed);
203+
204+
// Second step: do a backward dataflow analysis to extend the lifetimes of
205+
// no properly nested allocations.
169206
do {
170207
changed = false;
171208

172209
// It's a backward dataflow problem.
173210
for (BlockInfo &BI : reversed(BlockInfos)) {
174211
// Collect the alive-bits (at the block exit) from the successor blocks.
175-
Bits.reset();
176212
for (BlockInfo *SuccBI : BI.Successors) {
177-
Bits |= SuccBI->AliveStackLocsAtEntry;
178-
179-
// Also get the ExitReachable flag from the successor blocks.
180-
if (!BI.ExitReachable && SuccBI->ExitReachable) {
181-
BI.ExitReachable = true;
182-
changed = true;
213+
BI.AliveStackLocsAtExit |= SuccBI->AliveStackLocsAtEntry;
214+
}
215+
Bits = BI.AliveStackLocsAtExit;
216+
217+
if (isa<UnreachableInst>(BI.Block->getTerminator())) {
218+
// We treat unreachable as an implicit deallocation for all locations
219+
// which are still alive at this point.
220+
for (int BitNr = Bits.find_first(); BitNr >= 0;
221+
BitNr = Bits.find_next(BitNr)) {
222+
// For each alive location extend the lifetime of all locations which
223+
// are alive at the allocation point. This is the same as we do for
224+
// a "real" deallocation instruction (see below).
225+
Bits |= StackLocs[BitNr].AliveLocs;
183226
}
227+
BI.AliveStackLocsAtExit = Bits;
184228
}
185229
for (SILInstruction *StackInst : reversed(BI.StackInsts)) {
186230
if (StackInst->isAllocatingStack()) {
187-
auto AllocInst = cast<SingleValueInstruction>(StackInst);
188-
int BitNr = StackLoc2BitNumbers[AllocInst];
231+
int BitNr = bitNumberForAlloc(StackInst);
189232
if (Bits != StackLocs[BitNr].AliveLocs) {
190233
// More locations are alive around the StackInst's location.
191234
// Update the AlivaLocs bitset, which contains all those alive
192235
// locations.
193-
assert((Bits.test(BitNr) || (!BI.ExitReachable && !Bits.any()))
194-
&& "no dealloc found for alloc stack");
236+
assert(Bits.test(BitNr) && "no dealloc found for alloc stack");
195237
StackLocs[BitNr].AliveLocs = Bits;
196238
changed = true;
197239
isNested = true;
@@ -203,10 +245,7 @@ bool StackNesting::solve() {
203245
// A stack deallocation begins the lifetime of its location (in
204246
// reverse order). And it also begins the lifetime of all other
205247
// locations which are alive at the allocation point.
206-
auto *AllocInst =
207-
cast<SingleValueInstruction>(StackInst->getOperand(0));
208-
int BitNr = StackLoc2BitNumbers[AllocInst];
209-
Bits |= StackLocs[BitNr].AliveLocs;
248+
Bits |= StackLocs[bitNumberForDealloc(StackInst)].AliveLocs;
210249
}
211250
}
212251
if (Bits != BI.AliveStackLocsAtEntry) {
@@ -271,10 +310,7 @@ StackNesting::Changes StackNesting::adaptDeallocs() {
271310
// the same order as in solve().
272311
for (const BlockInfo &BI : reversed(BlockInfos)) {
273312
// Collect the alive-bits (at the block exit) from the successor blocks.
274-
Bits.reset();
275-
for (BlockInfo *SuccBI : BI.Successors) {
276-
Bits |= SuccBI->AliveStackLocsAtEntry;
277-
}
313+
Bits = BI.AliveStackLocsAtExit;
278314

279315
// Insert deallocations at block boundaries.
280316
// This can be necessary for unreachable blocks. Example:
@@ -293,12 +329,6 @@ StackNesting::Changes StackNesting::adaptDeallocs() {
293329
SuccIdx < NumSuccs; ++ SuccIdx) {
294330
BlockInfo *SuccBI = BI.Successors[SuccIdx];
295331

296-
// It's acceptable to not deallocate alive locations in unreachable
297-
// blocks - as long as the nesting is not violated. So if there are no
298-
// alive locations at the unreachable successor block, we can ignore it.
299-
if (!SuccBI->ExitReachable && !SuccBI->AliveStackLocsAtEntry.any())
300-
continue;
301-
302332
if (SuccBI->AliveStackLocsAtEntry == Bits)
303333
continue;
304334

@@ -320,16 +350,14 @@ StackNesting::Changes StackNesting::adaptDeallocs() {
320350
for (SILInstruction *StackInst : reversed(BI.StackInsts)) {
321351
if (StackInst->isAllocatingStack()) {
322352
// For allocations we just update the bit-set.
323-
auto AllocInst = cast<SingleValueInstruction>(StackInst);
324-
int BitNr = StackLoc2BitNumbers.lookup(AllocInst);
353+
int BitNr = bitNumberForAlloc(StackInst);
325354
assert(Bits == StackLocs[BitNr].AliveLocs &&
326355
"dataflow didn't converge");
327356
Bits.reset(BitNr);
328357
} else if (StackInst->isDeallocatingStack()) {
329358
// Handle deallocations.
330-
auto *AllocInst = cast<SingleValueInstruction>(StackInst->getOperand(0));
331359
SILLocation Loc = StackInst->getLoc();
332-
int BitNr = StackLoc2BitNumbers.lookup(AllocInst);
360+
int BitNr = bitNumberForDealloc(StackInst);
333361
SILInstruction *InsertionPoint = &*std::next(StackInst->getIterator());
334362
if (Bits.test(BitNr)) {
335363
// The location of StackInst is alive after StackInst. So we have to
@@ -372,17 +400,18 @@ void StackNesting::dump() const {
372400
continue;
373401

374402
llvm::dbgs() << "Block " << BI.Block->getDebugID();
375-
if (!BI.ExitReachable)
376-
llvm::dbgs() << " (unreachable exit)";
377-
llvm::dbgs() << ": bits=";
403+
llvm::dbgs() << ": entry-bits=";
378404
dumpBits(BI.AliveStackLocsAtEntry);
405+
llvm::dbgs() << ": exit-bits=";
406+
dumpBits(BI.AliveStackLocsAtExit);
407+
llvm::dbgs() << '\n';
379408
for (SILInstruction *StackInst : BI.StackInsts) {
380409
if (StackInst->isAllocatingStack()) {
381410
auto AllocInst = cast<SingleValueInstruction>(StackInst);
382411
int BitNr = StackLoc2BitNumbers.lookup(AllocInst);
383412
llvm::dbgs() << " alloc #" << BitNr << ": alive=";
384413
dumpBits(StackLocs[BitNr].AliveLocs);
385-
llvm::dbgs() << " " << *StackInst;
414+
llvm::dbgs() << ", " << *StackInst;
386415
} else if (StackInst->isDeallocatingStack()) {
387416
auto *AllocInst = cast<SingleValueInstruction>(StackInst->getOperand(0));
388417
int BitNr = StackLoc2BitNumbers.lookup(AllocInst);
@@ -405,6 +434,5 @@ void StackNesting::dumpBits(const BitVector &Bits) {
405434
llvm::dbgs() << separator << Bit;
406435
separator = ",";
407436
}
408-
llvm::dbgs() << ">\n";
437+
llvm::dbgs() << '>';
409438
}
410-

test/SILOptimizer/allocbox_to_stack.sil

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,8 @@ bb0(%0 : $Int):
819819
// CHECK-NEXT: [[STACK1:%[0-9]+]] = alloc_stack $Bool
820820
// CHECK-NEXT: [[BOX:%[0-9]+]] = alloc_stack $Int
821821
// CHECK: bb1:
822+
// CHECK-NEXT: dealloc_stack [[BOX]]
823+
// CHECK-NEXT: dealloc_stack [[STACK1]]
822824
// CHECK-NEXT: [[STACK2:%[0-9]+]] = alloc_stack $Bool
823825
// CHECK-NEXT: dealloc_stack [[STACK2]]
824826
// CHECK-NEXT: unreachable
@@ -952,6 +954,7 @@ bb2:
952954
// CHECK: bb0(%0 : $Int):
953955
// CHECK-NEXT: [[BOX:%[0-9]+]] = alloc_stack $Int
954956
// CHECK: bb1:
957+
// CHECK-NEXT: dealloc_stack [[BOX]]
955958
// CHECK-NEXT: {{.*}} = alloc_stack $Bool
956959
// CHECK-NEXT: {{.*}} = alloc_stack $Bool
957960
// CHECK-NEXT: unreachable
@@ -979,6 +982,38 @@ bb2:
979982
return %r : $()
980983
}
981984

985+
// CHECK-LABEL: sil @nesting_and_unreachable6
986+
// CHECK: bb0(%0 : $Int):
987+
// CHECK-NEXT: [[BOX:%[0-9]+]] = alloc_stack $Int
988+
// CHECK: bb1:
989+
// CHECK-NEXT: dealloc_stack [[BOX]]
990+
// CHECK-NEXT: br bb3
991+
// CHECK: bb2:
992+
// CHECK-NEXT: alloc_stack
993+
// CHECK-NEXT: unreachable
994+
// CHECK: bb3:
995+
// CHECK-NEXT: return
996+
sil @nesting_and_unreachable6 : $@convention(thin) (Int) -> Int {
997+
bb0(%0 : $Int):
998+
%1 = alloc_box ${ var Int }
999+
%1a = project_box %1 : ${ var Int }, 0
1000+
store %0 to %1a : $*Int
1001+
%3 = load %1a : $*Int
1002+
cond_br undef, bb1, bb2
1003+
1004+
bb1:
1005+
strong_release %1 : ${ var Int }
1006+
br bb3
1007+
1008+
bb2:
1009+
%241 = alloc_stack $Int
1010+
strong_release %1 : ${ var Int }
1011+
unreachable
1012+
1013+
bb3:
1014+
return %3 : $Int
1015+
}
1016+
9821017
// CHECK-LABEL: sil @nesting_and_unreachable_critical_edge
9831018
// CHECK: bb0(%0 : $Int):
9841019
// CHECK-NEXT: [[BOX:%[0-9]+]] = alloc_stack $Int

test/SILOptimizer/allocbox_to_stack_ownership.sil

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -814,6 +814,8 @@ bb0(%0 : $Int):
814814
// CHECK-NEXT: [[STACK1:%[0-9]+]] = alloc_stack $Bool
815815
// CHECK-NEXT: [[BOX:%[0-9]+]] = alloc_stack $Int
816816
// CHECK: bb1:
817+
// CHECK-NEXT: dealloc_stack [[BOX]]
818+
// CHECK-NEXT: dealloc_stack [[STACK1]]
817819
// CHECK-NEXT: [[STACK2:%[0-9]+]] = alloc_stack $Bool
818820
// CHECK-NEXT: dealloc_stack [[STACK2]]
819821
// CHECK-NEXT: unreachable
@@ -947,6 +949,7 @@ bb2:
947949
// CHECK: bb0(%0 : $Int):
948950
// CHECK-NEXT: [[BOX:%[0-9]+]] = alloc_stack $Int
949951
// CHECK: bb1:
952+
// CHECK-NEXT: dealloc_stack [[BOX]]
950953
// CHECK-NEXT: {{.*}} = alloc_stack $Bool
951954
// CHECK-NEXT: {{.*}} = alloc_stack $Bool
952955
// CHECK-NEXT: unreachable

0 commit comments

Comments
 (0)