Skip to content

Commit 347ffb3

Browse files
authored
Merge pull request #22120 from eeckstein/fix-stacknesting
SILOptimizer: correctly handle unreachable blocks in StackNesting.
2 parents f965d96 + 787c35f commit 347ffb3

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)