Skip to content

Commit 8fd1780

Browse files
committed
[SGC] Complete deadend-reachable lifetimes.
For a function to have complete lifetimes, the lifetime of every def in the function which is backwards-reachable from a dead-end block 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: defs in "unreachable blocks" (i.e. those which aren't forwards-reachable from the function entry) must be completed too, and such blocks do not appear in the function's post-order. Here, defs in unreachable blocks are be completed too. Such defs must also be completed in relative post-order. To do this, roots for the non-entry post-orders must be found. rdar://141197164
1 parent 5b1fd77 commit 8fd1780

File tree

2 files changed

+403
-10
lines changed

2 files changed

+403
-10
lines changed

lib/SILOptimizer/Mandatory/SILGenCleanup.cpp

+177-10
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
#include "swift/Basic/Assertions.h"
2020
#include "swift/Basic/Defer.h"
21+
#include "swift/SIL/BasicBlockBits.h"
22+
#include "swift/SIL/BasicBlockDatastructures.h"
2123
#include "swift/SIL/BasicBlockUtils.h"
2224
#include "swift/SIL/OSSALifetimeCompletion.h"
2325
#include "swift/SIL/PrettyStackTrace.h"
@@ -26,8 +28,10 @@
2628
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
2729
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
2830
#include "swift/SILOptimizer/PassManager/Transforms.h"
31+
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
2932
#include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h"
3033
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
34+
#include "llvm/ADT/PostOrderIterator.h"
3135

3236
using namespace swift;
3337

@@ -106,33 +110,196 @@ struct SILGenCleanup : SILModuleTransform {
106110
bool completeOSSALifetimes(SILFunction *function);
107111
template <typename Range>
108112
bool completeLifetimesInRange(Range const &range,
109-
OSSALifetimeCompletion &completion);
113+
OSSALifetimeCompletion &completion,
114+
BasicBlockSet &completed);
110115
};
111116

117+
// Iterate over `iterator` until finding a block in `include` and not in
118+
// `exclude`.
119+
SILBasicBlock *
120+
findFirstBlock(SILFunction *function, SILFunction::iterator &iterator,
121+
llvm::function_ref<bool(SILBasicBlock *)> include,
122+
llvm::function_ref<bool(SILBasicBlock *)> exclude) {
123+
while (iterator != function->end()) {
124+
auto *block = &*iterator;
125+
iterator = std::next(iterator);
126+
if (!include(block))
127+
continue;
128+
if (exclude(block))
129+
continue;
130+
return block;
131+
}
132+
return nullptr;
133+
}
134+
135+
// Walk backward from `from` following first predecessors until finding the
136+
// first already-reached block.
137+
SILBasicBlock *findFirstLoop(SILFunction *function, SILBasicBlock *from) {
138+
BasicBlockSet path(function);
139+
auto *current = from;
140+
while (auto *block = current) {
141+
current = nullptr;
142+
if (!path.insert(block)) {
143+
return block;
144+
}
145+
if (block->pred_empty()) {
146+
return nullptr;
147+
}
148+
current = *block->getPredecessorBlocks().begin();
149+
}
150+
llvm_unreachable("finished function-exiting loop!?");
151+
}
152+
153+
/// Populate `roots` with the last blocks that are discovered via backwards
154+
/// walks along any non-repeating paths starting at the ends in `backward`.
155+
void collectReachableRoots(SILFunction *function, BasicBlockWorklist &backward,
156+
StackList<SILBasicBlock *> &roots) {
157+
assert(!backward.empty());
158+
assert(roots.empty());
159+
160+
// Always include the entry block as a root. Currently SILGen will emit
161+
// consumes in unreachable blocks of values defined in reachable blocks (e.g.
162+
// test/SILGen/unreachable_code.swift:testUnreachableCatchClause).
163+
// TODO: [fix_silgen_destroy_unreachable] Fix SILGen not to emit such
164+
// destroys and don't add the entry
165+
// block to roots here.
166+
roots.push_back(function->getEntryBlock());
167+
168+
// First, find all blocks backwards-reachable from dead-end blocks.
169+
while (auto *block = backward.pop()) {
170+
for (auto *predecessor : block->getPredecessorBlocks()) {
171+
backward.pushIfNotVisited(predecessor);
172+
}
173+
}
174+
175+
// Simple case: unpredecessored blocks.
176+
//
177+
// Every unpredecessored block reachable from some dead-end block is a root.
178+
for (auto &block : *function) {
179+
if (&block == function->getEntryBlock()) {
180+
// TODO: [fix_silgen_destroy_unreachable] Remove this condition.
181+
continue;
182+
}
183+
if (!block.pred_empty())
184+
continue;
185+
if (!backward.isVisited(&block))
186+
continue;
187+
roots.push_back(&block);
188+
}
189+
190+
// Complex case: unreachable loops.
191+
//
192+
// Iteratively (the first time, these are the roots discovered in "Simple
193+
// case" above), determine which blocks are forward-reachable from roots.
194+
// Then, look for a block that is backwards-reachable from dead-end blocks
195+
// but not forwards-reachable from roots so far discovered. If one is found,
196+
// it is forwards-reachable from an unreachable loop. Walk backwards from
197+
// that block to find a representative block in the loop. Add that
198+
// representative block to roots and iterate. If no such block is found, all
199+
// roots have been found.
200+
BasicBlockWorklist forward(function);
201+
for (auto *root : roots) {
202+
forward.push(root);
203+
}
204+
bool changed = false;
205+
auto iterator = function->begin();
206+
do {
207+
changed = false;
208+
209+
// Propagate forward-reachability from roots discovered so far.
210+
while (auto *block = forward.pop()) {
211+
for (auto *successor : block->getSuccessorBlocks()) {
212+
forward.pushIfNotVisited(successor);
213+
}
214+
}
215+
// Any block in `backward` but not in `forward` is forward-reachable from an
216+
// unreachable loop.
217+
if (auto *target = findFirstBlock(
218+
function, iterator, /*include=*/
219+
[&backward](auto *block) { return backward.isVisited(block); },
220+
/*exclude=*/
221+
[&forward](auto *block) { return forward.isVisited(block); })) {
222+
// Find the first unreachable loop it's forwards-reachable from.
223+
auto *loop = findFirstLoop(function, target);
224+
ASSERT(loop);
225+
forward.push(loop);
226+
roots.push_back(loop);
227+
changed = true;
228+
}
229+
} while (changed);
230+
}
231+
112232
bool SILGenCleanup::completeOSSALifetimes(SILFunction *function) {
113233
if (!getModule()->getOptions().OSSACompleteLifetimes)
114234
return false;
115235

116236
LLVM_DEBUG(llvm::dbgs() << "Completing lifetimes in " << function->getName()
117237
<< "\n");
118238

119-
bool changed = false;
239+
BasicBlockWorklist deadends(function);
240+
DeadEndBlocks *deba = getAnalysis<DeadEndBlocksAnalysis>()->get(function);
241+
for (auto &block : *function) {
242+
if (deba->isDeadEnd(&block))
243+
deadends.push(&block);
244+
}
120245

121-
// Lifetimes must be completed inside out (bottom-up in the CFG).
122-
PostOrderFunctionInfo *postOrder =
123-
getAnalysis<PostOrderAnalysis>()->get(function);
124-
DeadEndBlocks *deb = getAnalysis<DeadEndBlocksAnalysis>()->get(function);
125-
OSSALifetimeCompletion completion(function, /*DomInfo*/ nullptr, *deb);
126-
changed |= completeLifetimesInRange(postOrder->getPostOrder(), completion);
246+
if (deadends.empty()) {
247+
// There are no dead-end blocks, so there are no lifetimes to complete.
248+
// (SILGen may emit incomplete lifetimes, but not underconsumed lifetimes.)
249+
return false;
250+
}
251+
252+
// Lifetimes must be completed in unreachable blocks that are reachable via
253+
// backwards walk from dead-end blocks. First, check whether there are any
254+
// unreachable blocks.
255+
ReachableBlocks reachableBlocks(function);
256+
reachableBlocks.compute();
257+
StackList<SILBasicBlock *> roots(function);
258+
if (!reachableBlocks.hasUnreachableBlocks()) {
259+
// There are no blocks that are unreachable from the entry block. Thus
260+
// every block will be completed when completing the post-order of the
261+
// entry block.
262+
roots.push_back(function->getEntryBlock());
263+
} else {
264+
// There are unreachable blocks. Determine the roots that can be reached
265+
// when walking from the unreachable blocks.
266+
collectReachableRoots(function, deadends, roots);
267+
}
268+
269+
bool changed = false;
270+
OSSALifetimeCompletion completion(function, /*DomInfo*/ nullptr, *deba);
271+
BasicBlockSet completed(function);
272+
for (auto *root : roots) {
273+
if (root == function->getEntryBlock()) {
274+
assert(!completed.contains(root));
275+
// When completing from the entry block, prefer the PostOrderAnalysis so
276+
// the result is cached.
277+
PostOrderFunctionInfo *postOrder =
278+
getAnalysis<PostOrderAnalysis>()->get(function);
279+
changed |= completeLifetimesInRange(postOrder->getPostOrder(), completion,
280+
completed);
281+
}
282+
if (completed.contains(root)) {
283+
// This block has already been completed in some other post-order
284+
// traversal. Thus the entire post-order rooted at it has already been
285+
// completed.
286+
continue;
287+
}
288+
changed |= completeLifetimesInRange(
289+
make_range(po_begin(root), po_end(root)), completion, completed);
290+
}
127291
function->verifyOwnership(/*deadEndBlocks=*/nullptr);
128292
return changed;
129293
}
130294

131295
template <typename Range>
132-
bool SILGenCleanup::completeLifetimesInRange(
133-
Range const &range, OSSALifetimeCompletion &completion) {
296+
bool SILGenCleanup::completeLifetimesInRange(Range const &range,
297+
OSSALifetimeCompletion &completion,
298+
BasicBlockSet &completed) {
134299
bool changed = false;
135300
for (auto *block : range) {
301+
if (!completed.insert(block))
302+
continue;
136303
LLVM_DEBUG(llvm::dbgs()
137304
<< "Completing lifetimes in bb" << block->getDebugID() << "\n");
138305
for (SILInstruction &inst : reverse(*block)) {

0 commit comments

Comments
 (0)