Skip to content

Commit 37eadd5

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 7e4779e commit 37eadd5

File tree

2 files changed

+400
-10
lines changed

2 files changed

+400
-10
lines changed

lib/SILOptimizer/Mandatory/SILGenCleanup.cpp

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

116233
LLVM_DEBUG(llvm::dbgs() << "Completing lifetimes in " << function->getName()
117234
<< "\n");
118235

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

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

131292
template <typename Range>
132-
bool SILGenCleanup::completeLifetimesInRange(
133-
Range const &range, OSSALifetimeCompletion &completion) {
293+
bool SILGenCleanup::completeLifetimesInRange(Range const &range,
294+
OSSALifetimeCompletion &completion,
295+
BasicBlockSet &completed) {
134296
bool changed = false;
135297
for (auto *block : range) {
298+
if (!completed.insert(block))
299+
continue;
136300
LLVM_DEBUG(llvm::dbgs()
137301
<< "Completing lifetimes in bb" << block->getDebugID() << "\n");
138302
for (SILInstruction &inst : reverse(*block)) {

0 commit comments

Comments
 (0)