Skip to content

Commit 15e5a26

Browse files
committed
[SGC] Complete unreachable-reachable lifetimes.
For a function to have complete lifetimes, the lifetime of every def in the function which is reachable by walking backwards from an unreachable instruction must be completed. Previously, every def in every block which appears in the post-order of the function's blocks is completed. This was not enough. Here, even defs in "unreachable blocks"--i.e. those which can't be reached by a forwards walk from function entry--are be completed. Such blocks are discovered by backwards non-repeating backwards walks from unreachable instructions. rdar://141197164
1 parent 12cc668 commit 15e5a26

File tree

2 files changed

+225
-8
lines changed

2 files changed

+225
-8
lines changed

lib/SILOptimizer/Mandatory/SILGenCleanup.cpp

+141-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
#include "swift/Basic/Assertions.h"
2020
#include "swift/Basic/Defer.h"
21+
#include "swift/SIL/BasicBlockBits.h"
2122
#include "swift/SIL/BasicBlockUtils.h"
2223
#include "swift/SIL/OSSALifetimeCompletion.h"
2324
#include "swift/SIL/PrettyStackTrace.h"
@@ -26,8 +27,10 @@
2627
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
2728
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
2829
#include "swift/SILOptimizer/PassManager/Transforms.h"
30+
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
2931
#include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h"
3032
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
33+
#include "llvm/ADT/PostOrderIterator.h"
3134

3235
using namespace swift;
3336

@@ -106,33 +109,163 @@ struct SILGenCleanup : SILModuleTransform {
106109
bool completeOSSALifetimes(SILFunction *function);
107110
template <typename Range>
108111
bool completeLifetimesInRange(Range const &range,
109-
OSSALifetimeCompletion &completion);
112+
OSSALifetimeCompletion &completion,
113+
BasicBlockSet &completed);
110114
};
111115

116+
/// Progress made so far on a walk beginning at an unreachable-terminated block.
117+
struct Walk {
118+
SILBasicBlock *current;
119+
SmallPtrSet<SILBasicBlock *, 16> seen;
120+
};
121+
122+
/// Populate `roots` with the last blocks that are discovered via backwards
123+
/// walks along any non-repeating paths starting at the ends in `walks`.
124+
void collectReachableRoots(SILFunction *function, SmallVectorImpl<Walk> &walks,
125+
StackList<SILBasicBlock *> &roots) {
126+
assert(!walks.empty());
127+
assert(roots.empty());
128+
129+
// Always include the entry block as a root. Currently SILGen will emit
130+
// consumes in unreachable blocks of values defined in reachable blocks (e.g.
131+
// test/SILGen/unreachable_code.swift:testUnreachableCatchClause).
132+
// TODO: [fix_silgen_destroy_unreachable] Fix SILGen not to emit such
133+
// destroys and don't add the entry
134+
// block to roots here.
135+
roots.push_back(function->getEntryBlock());
136+
137+
StackList<SILBasicBlock *> backedges(function);
138+
139+
BasicBlockSet visited(function);
140+
while (!walks.empty()) {
141+
auto walk = walks.pop_back_val();
142+
143+
SILBasicBlock *current = walk.current;
144+
145+
while (auto *block = current) {
146+
current = nullptr;
147+
bool previouslyVisited = visited.insert(block);
148+
149+
if (!walk.seen.insert(block).second) {
150+
// This block was seen on _this_ path.
151+
backedges.push_back(block);
152+
continue;
153+
}
154+
155+
if (!previouslyVisited) {
156+
// This block was seen on another path. Stop walking.
157+
continue;
158+
}
159+
160+
// No predecessors? Found a root.
161+
if (block->pred_empty()) {
162+
if (block == function->getEntryBlock()) {
163+
// TODO: [fix_silgen_destroy_unreachable] Remove this condition.
164+
continue;
165+
}
166+
roots.push_back(block);
167+
continue;
168+
}
169+
170+
// At least one predecessor. Continue walking this path from the first
171+
// predecessor and add all the others to "ends" for subsequent walking.
172+
173+
for (auto pair : llvm::enumerate(block->getPredecessorBlocks())) {
174+
auto *predecessor = pair.value();
175+
if (pair.index() == 0) {
176+
// Continue this walk from `predecessor`.
177+
current = predecessor;
178+
} else {
179+
// Clone this walk to another that continues from `predecessor`.
180+
walks.push_back({predecessor, walk.seen});
181+
}
182+
}
183+
}
184+
}
185+
186+
// Only complete starting from backedges after completing starting from all
187+
// nodes without predecessors. This ensures that completion is done with
188+
// respect to the post-order based at a node without predecessors rather than
189+
// one based at a backedge when both a node without predecessor and a
190+
// backedge are discovered from the same end.
191+
for (auto *backedge : backedges) {
192+
roots.push_back(backedge);
193+
}
194+
}
195+
112196
bool SILGenCleanup::completeOSSALifetimes(SILFunction *function) {
113197
if (!getModule()->getOptions().OSSACompleteLifetimes)
114198
return false;
115199

116200
LLVM_DEBUG(llvm::dbgs() << "Completing lifetimes in " << function->getName()
117201
<< "\n");
118202

119-
bool changed = false;
203+
// First, collect all blocks terminated in unreachable. Enables bailing out
204+
// if there are none.
205+
SmallVector<Walk, 32> walks;
206+
function->visitUnreachableTerminatedBlocks([&walks](auto &block) {
207+
walks.push_back({&block, {}});
208+
});
209+
210+
if (walks.empty()) {
211+
// There are no unreachable-terminated blocks, so there are no lifetimes to
212+
// complete. (SILGen may emit incomplete lifetimes, but not underconsumed
213+
// lifetimes.)
214+
return false;
215+
}
216+
217+
// Lifetimes must be completed in unreachable blocks that are reachable via
218+
// backwards walk from unreachable instructions. First, check whether there
219+
// are any unreachable blocks.
220+
ReachableBlocks reachableBlocks(function);
221+
reachableBlocks.compute();
222+
StackList<SILBasicBlock *> roots(function);
223+
if (!reachableBlocks.hasUnreachableBlocks()) {
224+
// There are no blocks that are unreachable from the entry block. Thus,
225+
// every block will be completed when completing the post-order of the
226+
// entry block.
227+
roots.push_back(function->getEntryBlock());
228+
} else {
229+
// There are unreachable blocks. Determine the roots that can be reached
230+
// when walking from the unreachable blocks.
231+
collectReachableRoots(function, walks, roots);
232+
}
120233

121-
// Lifetimes must be completed inside out (bottom-up in the CFG).
122-
PostOrderFunctionInfo *postOrder =
123-
getAnalysis<PostOrderAnalysis>()->get(function);
234+
bool changed = false;
124235
DeadEndBlocks *deb = getAnalysis<DeadEndBlocksAnalysis>()->get(function);
125236
OSSALifetimeCompletion completion(function, /*DomInfo*/ nullptr, *deb);
126-
changed |= completeLifetimesInRange(postOrder->getPostOrder(), completion);
237+
BasicBlockSet completed(function);
238+
for (auto *root : roots) {
239+
if (root == function->getEntryBlock()) {
240+
assert(!completed.contains(root));
241+
// When completing from the entry block, prefer the PostOrderAnalysis so
242+
// the result is cached.
243+
PostOrderFunctionInfo *postOrder =
244+
getAnalysis<PostOrderAnalysis>()->get(function);
245+
changed |= completeLifetimesInRange(postOrder->getPostOrder(), completion,
246+
completed);
247+
}
248+
if (completed.contains(root)) {
249+
// This block has already been completed in some other post-order
250+
// traversal. Thus the entire post-order rooted at it has already been
251+
// completed.
252+
continue;
253+
}
254+
changed |= completeLifetimesInRange(
255+
make_range(po_begin(root), po_end(root)), completion, completed);
256+
}
127257
function->verifyOwnership(/*deadEndBlocks=*/nullptr);
128258
return changed;
129259
}
130260

131261
template <typename Range>
132-
bool SILGenCleanup::completeLifetimesInRange(
133-
Range const &range, OSSALifetimeCompletion &completion) {
262+
bool SILGenCleanup::completeLifetimesInRange(Range const &range,
263+
OSSALifetimeCompletion &completion,
264+
BasicBlockSet &completed) {
134265
bool changed = false;
135266
for (auto *block : range) {
267+
if (!completed.insert(block))
268+
continue;
136269
LLVM_DEBUG(llvm::dbgs()
137270
<< "Completing lifetimes in bb" << block->getDebugID() << "\n");
138271
for (SILInstruction &inst : reverse(*block)) {

test/SILOptimizer/silgen_cleanup_complete_ossa.sil

+84
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,87 @@ exit:
141141
die:
142142
unreachable
143143
}
144+
145+
// CHECK-LABEL: sil [ossa] @unreachable_def : {{.*}} {
146+
// CHECK: bb2:
147+
// CHECK-NEXT: [[DEF:%[^,]+]] = apply
148+
// CHECK-NEXT: br bb3
149+
// CHECK: bb3:
150+
// CHECK-NEXT: destroy_value [dead_end] [[DEF]]
151+
// CHECK: unreachable
152+
// CHECK-LABEL: } // end sil function 'unreachable_def'
153+
sil [ossa] @unreachable_def : $@convention(thin) () -> () {
154+
entry:
155+
br exit
156+
157+
exit:
158+
%retval = tuple ()
159+
return %retval : $()
160+
161+
nowhere:
162+
%def = apply undef() : $@convention(thin) () -> (@owned C)
163+
br die
164+
165+
die:
166+
unreachable
167+
}
168+
169+
// CHECK-LABEL: sil [ossa] @unreachable_def_2 : {{.*}} {
170+
// CHECK: {{bb[0-9]+}}([[C:%[^,]+]] :
171+
// CHECK: cond_br undef, [[LEFT:bb[0-9]+]], [[RIGHT:bb[0-9]+]]
172+
// CHECK: [[LEFT]]:
173+
// CHECK-NEXT: destroy_value [dead_end] [[C]]
174+
// CHECK-NEXT: unreachable
175+
// CHECK: [[RIGHT]]:
176+
// CHECK-NEXT: destroy_value [dead_end] [[C]]
177+
// CHECK: unreachable
178+
// CHECK-LABEL: } // end sil function 'unreachable_def_2'
179+
sil [ossa] @unreachable_def_2 : $@convention(thin) () -> () {
180+
entry:
181+
%t = tuple ()
182+
return %t : $()
183+
184+
not(%c : @owned $C):
185+
cond_br undef, left, right
186+
187+
left:
188+
unreachable
189+
190+
right:
191+
unreachable
192+
}
193+
194+
// CHECK-LABEL: sil [ossa] @unreachable_def_3 : {{.*}} {
195+
// CHECK: {{bb[0-9]+}}([[C:%[^,]+]] :
196+
// CHECK: cond_br
197+
// CHECK: cond_br undef, [[LEFT:bb[0-9]+]], [[RIGHT:bb[0-9]+]]
198+
// CHECK: [[LEFT]]:
199+
// CHECK-NEXT: destroy_value [dead_end] [[C]]
200+
// CHECK-NEXT: unreachable
201+
// CHECK: [[RIGHT]]:
202+
// CHECK-NEXT: destroy_value [dead_end] [[C]]
203+
// CHECK: unreachable
204+
// CHECK-LABEL: } // end sil function 'unreachable_def_3'
205+
sil [ossa] @unreachable_def_3 : $@convention(thin) () -> () {
206+
entry:
207+
%t = tuple ()
208+
return %t : $()
209+
210+
header(%c : @owned $C):
211+
br body
212+
213+
body:
214+
cond_br undef, die, backedge
215+
216+
backedge:
217+
br header(%c)
218+
219+
die:
220+
cond_br undef, left, right
221+
222+
left:
223+
unreachable
224+
225+
right:
226+
unreachable
227+
}

0 commit comments

Comments
 (0)