-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathSILGenCleanup.cpp
366 lines (326 loc) · 13.5 KB
/
SILGenCleanup.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
//===--- SILGenCleanup.cpp ------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2019 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
///
/// Perform peephole-style "cleanup" to aid SIL diagnostic passes.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "silgen-cleanup"
#include "swift/Basic/Assertions.h"
#include "swift/Basic/Defer.h"
#include "swift/SIL/BasicBlockBits.h"
#include "swift/SIL/BasicBlockDatastructures.h"
#include "swift/SIL/BasicBlockUtils.h"
#include "swift/SIL/OSSALifetimeCompletion.h"
#include "swift/SIL/PrettyStackTrace.h"
#include "swift/SIL/PrunedLiveness.h"
#include "swift/SIL/SILInstruction.h"
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/BasicBlockOptUtils.h"
#include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "llvm/ADT/PostOrderIterator.h"
using namespace swift;
// Define a CanonicalizeInstruction subclass for use in SILGenCleanup.
struct SILGenCanonicalize final : CanonicalizeInstruction {
bool changed = false;
llvm::SmallPtrSet<SILInstruction *, 16> deadOperands;
SILGenCanonicalize(DeadEndBlocks &deadEndBlocks)
: CanonicalizeInstruction(DEBUG_TYPE, deadEndBlocks) {}
void notifyNewInstruction(SILInstruction *) override { changed = true; }
// Just delete the given 'inst' and record its operands. The callback isn't
// allowed to mutate any other instructions.
void killInstruction(SILInstruction *inst) override {
deadOperands.erase(inst);
for (auto &operand : inst->getAllOperands()) {
if (auto *operInst = operand.get()->getDefiningInstruction())
deadOperands.insert(operInst);
}
inst->eraseFromParent();
changed = true;
}
void notifyHasNewUsers(SILValue) override { changed = true; }
/// Delete trivially dead instructions in non-deterministic order.
///
/// We either have that nextII is endII or if nextII is not endII then endII
/// is nextII->getParent()->end().
SILBasicBlock::iterator deleteDeadOperands(SILBasicBlock::iterator nextII,
SILBasicBlock::iterator endII) {
auto callbacks = InstModCallbacks().onDelete([&](SILInstruction *deadInst) {
LLVM_DEBUG(llvm::dbgs() << "Trivially dead: " << *deadInst);
// If nextII is the instruction we are going to delete, move nextII past
// it.
if (deadInst->getIterator() == nextII)
++nextII;
// Then remove the instruction from the set and delete it.
deadOperands.erase(deadInst);
deadInst->eraseFromParent();
});
while (!deadOperands.empty()) {
SILInstruction *deadOperInst = *deadOperands.begin();
// Make sure at least the first instruction is removed from the set.
deadOperands.erase(deadOperInst);
// Then delete this instruction/everything else that we can.
eliminateDeadInstruction(deadOperInst, callbacks);
}
return nextII;
}
};
//===----------------------------------------------------------------------===//
// SILGenCleanup: Top-Level Module Transform
//===----------------------------------------------------------------------===//
namespace {
// SILGenCleanup must run on all functions that will be seen by any analysis
// used by diagnostics before transforming the function that requires the
// analysis. e.g. Closures need to be cleaned up before the closure's parent can
// be diagnosed.
//
// TODO: This pass can be converted to a function transform if the mandatory
// pipeline runs in bottom-up closure order.
struct SILGenCleanup : SILModuleTransform {
void run() override;
bool completeOSSALifetimes(SILFunction *function);
template <typename Range>
bool completeLifetimesInRange(Range const &range,
OSSALifetimeCompletion &completion,
BasicBlockSet &completed);
};
// Iterate over `iterator` until finding a block in `include` and not in
// `exclude`.
SILBasicBlock *
findFirstBlock(SILFunction *function, SILFunction::iterator &iterator,
llvm::function_ref<bool(SILBasicBlock *)> include,
llvm::function_ref<bool(SILBasicBlock *)> exclude) {
while (iterator != function->end()) {
auto *block = &*iterator;
iterator = std::next(iterator);
if (!include(block))
continue;
if (exclude(block))
continue;
return block;
}
return nullptr;
}
// Walk backward from `from` following first predecessors until finding the
// first already-reached block.
SILBasicBlock *findFirstLoop(SILFunction *function, SILBasicBlock *from) {
BasicBlockSet path(function);
auto *current = from;
while (auto *block = current) {
current = nullptr;
if (!path.insert(block)) {
return block;
}
if (block->pred_empty()) {
return nullptr;
}
current = *block->getPredecessorBlocks().begin();
}
llvm_unreachable("finished function-exiting loop!?");
}
/// Populate `roots` with the last blocks that are discovered via backwards
/// walks along any non-repeating paths starting at the ends in `backward`.
void collectReachableRoots(SILFunction *function, BasicBlockWorklist &backward,
StackList<SILBasicBlock *> &roots) {
assert(!backward.empty());
assert(roots.empty());
// Always include the entry block as a root. Currently SILGen will emit
// consumes in unreachable blocks of values defined in reachable blocks (e.g.
// test/SILGen/unreachable_code.swift:testUnreachableCatchClause).
// TODO: [fix_silgen_destroy_unreachable] Fix SILGen not to emit such
// destroys and don't add the entry
// block to roots here.
roots.push_back(function->getEntryBlock());
// First, find all blocks backwards-reachable from dead-end blocks.
while (auto *block = backward.pop()) {
for (auto *predecessor : block->getPredecessorBlocks()) {
backward.pushIfNotVisited(predecessor);
}
}
// Simple case: unpredecessored blocks.
//
// Every unpredecessored block reachable from some dead-end block is a root.
for (auto &block : *function) {
if (&block == function->getEntryBlock()) {
// TODO: [fix_silgen_destroy_unreachable] Remove this condition.
continue;
}
if (!block.pred_empty())
continue;
if (!backward.isVisited(&block))
continue;
roots.push_back(&block);
}
// Complex case: unreachable loops.
//
// Iteratively (the first time, these are the roots discovered in "Simple
// case" above), determine which blocks are forward-reachable from roots.
// Then, look for a block that is backwards-reachable from dead-end blocks
// but not forwards-reachable from roots so far discovered. If one is found,
// it is forwards-reachable from an unreachable loop. Walk backwards from
// that block to find a representative block in the loop. Add that
// representative block to roots and iterate. If no such block is found, all
// roots have been found.
BasicBlockWorklist forward(function);
for (auto *root : roots) {
forward.push(root);
}
bool changed = false;
auto iterator = function->begin();
do {
changed = false;
// Propagate forward-reachability from roots discovered so far.
while (auto *block = forward.pop()) {
for (auto *successor : block->getSuccessorBlocks()) {
forward.pushIfNotVisited(successor);
}
}
// Any block in `backward` but not in `forward` is forward-reachable from an
// unreachable loop.
if (auto *target = findFirstBlock(
function, iterator, /*include=*/
[&backward](auto *block) { return backward.isVisited(block); },
/*exclude=*/
[&forward](auto *block) { return forward.isVisited(block); })) {
// Find the first unreachable loop it's forwards-reachable from.
auto *loop = findFirstLoop(function, target);
ASSERT(loop);
forward.push(loop);
roots.push_back(loop);
changed = true;
}
} while (changed);
}
bool SILGenCleanup::completeOSSALifetimes(SILFunction *function) {
if (!getModule()->getOptions().OSSACompleteLifetimes)
return false;
LLVM_DEBUG(llvm::dbgs() << "Completing lifetimes in " << function->getName()
<< "\n");
BasicBlockWorklist deadends(function);
DeadEndBlocks *deba = getAnalysis<DeadEndBlocksAnalysis>()->get(function);
for (auto &block : *function) {
if (deba->isDeadEnd(&block))
deadends.push(&block);
}
if (deadends.empty()) {
// There are no dead-end blocks, so there are no lifetimes to complete.
// (SILGen may emit incomplete lifetimes, but not underconsumed lifetimes.)
return false;
}
// Lifetimes must be completed in unreachable blocks that are reachable via
// backwards walk from dead-end blocks. First, check whether there are any
// unreachable blocks.
ReachableBlocks reachableBlocks(function);
reachableBlocks.compute();
StackList<SILBasicBlock *> roots(function);
if (!reachableBlocks.hasUnreachableBlocks()) {
// There are no blocks that are unreachable from the entry block. Thus
// every block will be completed when completing the post-order of the
// entry block.
roots.push_back(function->getEntryBlock());
} else {
// There are unreachable blocks. Determine the roots that can be reached
// when walking from the unreachable blocks.
collectReachableRoots(function, deadends, roots);
}
bool changed = false;
OSSALifetimeCompletion completion(
function, /*DomInfo*/ nullptr, *deba,
OSSALifetimeCompletion::ExtendTrivialVariable);
BasicBlockSet completed(function);
for (auto *root : roots) {
if (root == function->getEntryBlock()) {
assert(!completed.contains(root));
// When completing from the entry block, prefer the PostOrderAnalysis so
// the result is cached.
PostOrderFunctionInfo *postOrder =
getAnalysis<PostOrderAnalysis>()->get(function);
changed |= completeLifetimesInRange(postOrder->getPostOrder(), completion,
completed);
}
if (completed.contains(root)) {
// This block has already been completed in some other post-order
// traversal. Thus the entire post-order rooted at it has already been
// completed.
continue;
}
changed |= completeLifetimesInRange(
make_range(po_begin(root), po_end(root)), completion, completed);
}
function->verifyOwnership(/*deadEndBlocks=*/nullptr);
return changed;
}
template <typename Range>
bool SILGenCleanup::completeLifetimesInRange(Range const &range,
OSSALifetimeCompletion &completion,
BasicBlockSet &completed) {
bool changed = false;
for (auto *block : range) {
if (!completed.insert(block))
continue;
LLVM_DEBUG(llvm::dbgs()
<< "Completing lifetimes in bb" << block->getDebugID() << "\n");
for (SILInstruction &inst : reverse(*block)) {
for (auto result : inst.getResults()) {
LLVM_DEBUG(llvm::dbgs() << "completing " << result << "\n");
if (completion.completeOSSALifetime(
result, OSSALifetimeCompletion::Boundary::Availability) ==
LifetimeCompletion::WasCompleted) {
LLVM_DEBUG(llvm::dbgs() << "\tcompleted!\n");
changed = true;
}
}
}
for (SILArgument *arg : block->getArguments()) {
LLVM_DEBUG(llvm::dbgs() << "completing " << *arg << "\n");
assert(!arg->isReborrow() && "reborrows not legal at this SIL stage");
if (completion.completeOSSALifetime(
arg, OSSALifetimeCompletion::Boundary::Availability) ==
LifetimeCompletion::WasCompleted) {
LLVM_DEBUG(llvm::dbgs() << "\tcompleted!\n");
changed = true;
}
}
}
return changed;
}
void SILGenCleanup::run() {
auto &module = *getModule();
for (auto &function : module) {
if (!function.isDefinition())
continue;
PrettyStackTraceSILFunction stackTrace("silgen cleanup", &function);
LLVM_DEBUG(llvm::dbgs()
<< "\nRunning SILGenCleanup on " << function.getName() << "\n");
bool changed = completeOSSALifetimes(&function);
DeadEndBlocks deadEndBlocks(&function);
SILGenCanonicalize sgCanonicalize(deadEndBlocks);
// Iterate over all blocks even if they aren't reachable. No phi-less
// dataflow cycles should have been created yet, and these transformations
// are simple enough they shouldn't be affected by cycles.
for (auto &bb : function) {
for (auto ii = bb.begin(), ie = bb.end(); ii != ie;) {
ii = sgCanonicalize.canonicalize(&*ii);
ii = sgCanonicalize.deleteDeadOperands(ii, ie);
}
}
changed |= sgCanonicalize.changed;
if (changed) {
auto invalidKind = SILAnalysis::InvalidationKind::Instructions;
invalidateAnalysis(&function, invalidKind);
}
}
}
} // end anonymous namespace
SILTransform *swift::createSILGenCleanup() { return new SILGenCleanup(); }