Skip to content

Commit dd168d8

Browse files
committed
[LexicalDestroyHoisting] Added.
The new utility, to be run as part of copy propagation, hoists destroy_values of owned lexical values up to deinit barriers. It is heavily based on the rewritten ShrinkBorrowScope.
1 parent c3a81f0 commit dd168d8

File tree

4 files changed

+831
-0
lines changed

4 files changed

+831
-0
lines changed

include/swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,10 @@ MoveValueInst *foldDestroysOfCopiedLexicalBorrow(BeginBorrowInst *bbi,
155155
DominanceInfo &dominanceTree,
156156
InstructionDeleter &deleter);
157157

158+
bool hoistDestroysOfOwnedLexicalValue(SILValue const value,
159+
SILFunction &function,
160+
InstructionDeleter &deleter);
161+
158162
} // namespace swift
159163

160164
#endif // SWIFT_SILOPTIMIZER_UTILS_CANONICALIZEBORROWSCOPES_H

lib/SILOptimizer/Utils/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ target_sources(swiftSILOptimizer PRIVATE
1919
InstOptUtils.cpp
2020
KeyPathProjector.cpp
2121
LexicalDestroyFolding.cpp
22+
LexicalDestroyHoisting.cpp
2223
LoadStoreOptUtils.cpp
2324
LoopUtils.cpp
2425
OptimizerStatsUtils.cpp
Lines changed: 363 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,363 @@
1+
//=-- LexicalDestroyHoisting.cpp - Hoist destroy_values to deinit barriers. -=//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2022 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
/// Hoist destroys of owned lexical values (owned arguments and the results of
13+
/// move_value [lexical] instructions) up to deinit barriers.
14+
//===----------------------------------------------------------------------===//
15+
16+
#include "swift/AST/Builtins.h"
17+
#include "swift/SIL/MemAccessUtils.h"
18+
#include "swift/SIL/OwnershipUtils.h"
19+
#include "swift/SIL/SILBasicBlock.h"
20+
#include "swift/SIL/SILInstruction.h"
21+
#include "swift/SIL/SILValue.h"
22+
#include "swift/SILOptimizer/Analysis/Reachability.h"
23+
#include "swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h"
24+
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
25+
#include "swift/SILOptimizer/Utils/InstructionDeleter.h"
26+
#include "llvm/ADT/STLExtras.h"
27+
28+
#define DEBUG_TYPE "copy-propagation"
29+
30+
using namespace swift;
31+
32+
//===----------------------------------------------------------------------===//
33+
// MARK: LexicalDestroyHoisting
34+
//===----------------------------------------------------------------------===//
35+
36+
namespace LexicalDestroyHoisting {
37+
38+
/// The environment within which to hoist.
39+
struct Context final {
40+
/// The owned lexical value whose destroys are to be hoisted.
41+
SILValue const &value;
42+
43+
/// value->getDefiningInstruction()
44+
SILInstruction *const definition;
45+
46+
SILFunction &function;
47+
48+
InstructionDeleter &deleter;
49+
50+
Context(SILValue const &value, SILFunction &function,
51+
InstructionDeleter &deleter)
52+
: value(value), definition(value->getDefiningInstruction()),
53+
function(function), deleter(deleter) {
54+
assert(value->isLexical());
55+
assert(value->getOwnershipKind() == OwnershipKind::Owned);
56+
}
57+
Context(Context const &) = delete;
58+
Context &operator=(Context const &) = delete;
59+
};
60+
61+
/// How %value gets used.
62+
struct Usage final {
63+
/// Instructions which are users of the simple (i.e. not reborrowed) value.
64+
SmallPtrSet<SILInstruction *, 16> users;
65+
// The instructions from which the hoisting starts, the destroy_values.
66+
llvm::SmallSetVector<SILInstruction *, 4> ends;
67+
68+
Usage(){};
69+
Usage(Usage const &) = delete;
70+
Usage &operator=(Usage const &) = delete;
71+
};
72+
73+
/// Identify users and destroy_values of %value.
74+
///
75+
/// returns true if all uses were found
76+
/// false otherwise
77+
bool findUsage(Context const &context, Usage &usage) {
78+
SmallVector<Operand *, 16> uses;
79+
if (!findUsesOfSimpleValue(context.value, &uses)) {
80+
// If the value escapes, don't hoist.
81+
return false;
82+
}
83+
for (auto *use : uses) {
84+
// Add the destroy_values to the collection of ends so we can seed the data
85+
// flow and determine whether any were reused. They aren't uses over which
86+
// we can't hoist though.
87+
if (isa<DestroyValueInst>(use->getUser())) {
88+
usage.ends.insert(use->getUser());
89+
} else {
90+
usage.users.insert(use->getUser());
91+
}
92+
}
93+
return true;
94+
}
95+
96+
/// How destroy_value hoisting is obstructed.
97+
struct DeinitBarriers final {
98+
/// Blocks up to "before the beginning" of which hoisting was able to proceed.
99+
BasicBlockSetVector hoistingReachesBeginBlocks;
100+
101+
/// Blocks to "after the end" of which hoisting was able to proceed.
102+
BasicBlockSet hoistingReachesEndBlocks;
103+
104+
/// Instructions above which destroy_values cannot be hoisted.
105+
SmallVector<SILInstruction *, 4> barriers;
106+
107+
/// Blocks one of whose phis is a barrier and consequently out of which
108+
/// destroy_values cannot be hoisted.
109+
SmallVector<SILBasicBlock *, 4> phiBarriers;
110+
111+
DeinitBarriers(Context &context)
112+
: hoistingReachesBeginBlocks(&context.function),
113+
hoistingReachesEndBlocks(&context.function) {}
114+
DeinitBarriers(DeinitBarriers const &) = delete;
115+
DeinitBarriers &operator=(DeinitBarriers const &) = delete;
116+
};
117+
118+
/// Works backwards from the current location of destroy_values to the earliest
119+
/// place they can be hoisted to.
120+
///
121+
/// Implements BackwardReachability::BlockReachability.
122+
class DataFlow final {
123+
Context const &context;
124+
Usage const &uses;
125+
DeinitBarriers &result;
126+
127+
enum class Classification { Barrier, Other };
128+
129+
BackwardReachability<DataFlow> reachability;
130+
131+
public:
132+
DataFlow(Context const &context, Usage const &uses, DeinitBarriers &result)
133+
: context(context), uses(uses), result(result),
134+
reachability(&context.function, *this) {
135+
// Seed reachability with the scope ending uses from which the backwards
136+
// data flow will begin.
137+
for (auto *end : uses.ends) {
138+
reachability.initLastUse(end);
139+
}
140+
}
141+
DataFlow(DataFlow const &) = delete;
142+
DataFlow &operator=(DataFlow const &) = delete;
143+
144+
void run() { reachability.solveBackward(); }
145+
146+
private:
147+
friend class BackwardReachability<DataFlow>;
148+
149+
bool hasReachableBegin(SILBasicBlock *block) {
150+
return result.hoistingReachesBeginBlocks.contains(block);
151+
}
152+
153+
void markReachableBegin(SILBasicBlock *block) {
154+
result.hoistingReachesBeginBlocks.insert(block);
155+
}
156+
157+
void markReachableEnd(SILBasicBlock *block) {
158+
result.hoistingReachesEndBlocks.insert(block);
159+
}
160+
161+
Classification classifyInstruction(SILInstruction *);
162+
163+
bool classificationIsBarrier(Classification);
164+
165+
void visitedInstruction(SILInstruction *, Classification);
166+
167+
bool checkReachableBarrier(SILInstruction *);
168+
169+
bool checkReachablePhiBarrier(SILBasicBlock *);
170+
};
171+
172+
DataFlow::Classification
173+
DataFlow::classifyInstruction(SILInstruction *instruction) {
174+
if (instruction == context.definition) {
175+
return Classification::Barrier;
176+
}
177+
if (uses.users.contains(instruction)) {
178+
return Classification::Barrier;
179+
}
180+
if (isDeinitBarrier(instruction)) {
181+
return Classification::Barrier;
182+
}
183+
return Classification::Other;
184+
}
185+
186+
bool DataFlow::classificationIsBarrier(Classification classification) {
187+
switch (classification) {
188+
case Classification::Barrier:
189+
return true;
190+
case Classification::Other:
191+
return false;
192+
}
193+
llvm_unreachable("exhaustive switch not exhaustive?!");
194+
}
195+
196+
void DataFlow::visitedInstruction(SILInstruction *instruction,
197+
Classification classification) {
198+
assert(classifyInstruction(instruction) == classification);
199+
switch (classification) {
200+
case Classification::Barrier:
201+
result.barriers.push_back(instruction);
202+
return;
203+
case Classification::Other:
204+
return;
205+
}
206+
llvm_unreachable("exhaustive switch not exhaustive?!");
207+
}
208+
209+
bool DataFlow::checkReachableBarrier(SILInstruction *instruction) {
210+
auto classification = classifyInstruction(instruction);
211+
visitedInstruction(instruction, classification);
212+
return classificationIsBarrier(classification);
213+
}
214+
215+
bool DataFlow::checkReachablePhiBarrier(SILBasicBlock *block) {
216+
assert(llvm::all_of(block->getArguments(),
217+
[&](auto argument) { return PhiValue(argument); }));
218+
219+
bool isBarrier =
220+
llvm::any_of(block->getPredecessorBlocks(), [&](auto *predecessor) {
221+
return classificationIsBarrier(
222+
classifyInstruction(predecessor->getTerminator()));
223+
});
224+
if (isBarrier) {
225+
result.phiBarriers.push_back(block);
226+
}
227+
return isBarrier;
228+
}
229+
230+
/// Hoist the destroy_values of %value.
231+
class Rewriter final {
232+
Context &context;
233+
Usage const &uses;
234+
DeinitBarriers const &barriers;
235+
236+
/// The destroy_value instructions for this owned lexical value that existed
237+
/// before LexicalDestroyHoisting ran and which were not modified.
238+
llvm::SmallPtrSet<SILInstruction *, 8> reusedDestroyValueInsts;
239+
240+
public:
241+
Rewriter(Context &context, Usage const &uses, DeinitBarriers const &barriers)
242+
: context(context), uses(uses), barriers(barriers) {}
243+
Rewriter(Rewriter const &) = delete;
244+
Rewriter &operator=(Rewriter const &) = delete;
245+
246+
bool run();
247+
248+
private:
249+
bool createDestroyValue(SILInstruction *insertionPoint);
250+
};
251+
252+
bool Rewriter::run() {
253+
bool madeChange = false;
254+
255+
// Add destroy_values for phi barrier boundaries.
256+
//
257+
// A block is a phi barrier iff any of its predecessors' terminators get
258+
// classified as barriers.
259+
for (auto *block : barriers.phiBarriers) {
260+
madeChange |= createDestroyValue(&block->front());
261+
}
262+
263+
// Add destroy_values for barrier boundaries.
264+
//
265+
// Insert destroy_values after every non-terminator barrier.
266+
//
267+
// For terminator barriers, add destroy_values at the beginning of the
268+
// successor blocks. In order to reach a terminator and classify it as a
269+
// barrier, all of a block P's successors B had reachable beginnings. If any
270+
// of them didn't, then BackwardReachability::meetOverSuccessors would never
271+
// have returned true for P, so none of its instructions would ever have been
272+
// classified (except for via checkReachablePhiBarrier, which doesn't record
273+
// terminator barriers).
274+
for (auto instruction : barriers.barriers) {
275+
if (auto *terminator = dyn_cast<TermInst>(instruction)) {
276+
auto successors = terminator->getParentBlock()->getSuccessorBlocks();
277+
// In order for the instruction to have been classified as a barrier,
278+
// reachability would have had to reach the block containing it.
279+
assert(barriers.hoistingReachesEndBlocks.contains(
280+
terminator->getParentBlock()));
281+
for (auto *successor : successors) {
282+
madeChange |= createDestroyValue(&successor->front());
283+
}
284+
} else {
285+
auto *next = instruction->getNextInstruction();
286+
assert(next);
287+
madeChange |= createDestroyValue(next);
288+
}
289+
}
290+
291+
// Add destroy_values for control-flow boundaries.
292+
//
293+
// Insert destroy_values at the beginning of blocks which were preceded by a
294+
// control flow branch (and which, thanks to the lack of critical edges,
295+
// don't have multiple predecessors) whose end was not reachable (because
296+
// reachability was not able to make it to the top of some other successor).
297+
//
298+
// In other words, a control flow boundary is the target edge from a block B
299+
// to its single predecessor P not all of whose successors S in succ(P) had
300+
// reachable beginnings. We witness that fact about P's successors by way of
301+
// P not having a reachable end--see BackwardReachability::meetOverSuccessors.
302+
//
303+
// control-flow-boundary(B) := beginning-reachable(B) && !end-reachable(P)
304+
for (auto *block : barriers.hoistingReachesBeginBlocks) {
305+
if (auto *predecessor = block->getSinglePredecessorBlock()) {
306+
if (!barriers.hoistingReachesEndBlocks.contains(predecessor)) {
307+
madeChange |= createDestroyValue(&block->front());
308+
}
309+
}
310+
}
311+
312+
if (madeChange) {
313+
// Remove all the original destroy_values instructions.
314+
for (auto *end : uses.ends) {
315+
if (reusedDestroyValueInsts.contains(end)) {
316+
continue;
317+
}
318+
context.deleter.forceDelete(end);
319+
}
320+
}
321+
322+
return madeChange;
323+
}
324+
325+
bool Rewriter::createDestroyValue(SILInstruction *insertionPoint) {
326+
if (auto *ebi = dyn_cast<DestroyValueInst>(insertionPoint)) {
327+
if (uses.ends.contains(insertionPoint)) {
328+
reusedDestroyValueInsts.insert(insertionPoint);
329+
return false;
330+
}
331+
}
332+
auto builder = SILBuilderWithScope(insertionPoint);
333+
builder.createDestroyValue(
334+
RegularLocation::getAutoGeneratedLocation(insertionPoint->getLoc()),
335+
context.value);
336+
return true;
337+
}
338+
339+
bool run(Context &context) {
340+
Usage usage;
341+
if (!findUsage(context, usage))
342+
return false;
343+
344+
DeinitBarriers barriers(context);
345+
DataFlow flow(context, usage, barriers);
346+
flow.run();
347+
348+
Rewriter rewriter(context, usage, barriers);
349+
350+
return rewriter.run();
351+
}
352+
} // end namespace LexicalDestroyHoisting
353+
354+
bool swift::hoistDestroysOfOwnedLexicalValue(SILValue const value,
355+
SILFunction &function,
356+
InstructionDeleter &deleter) {
357+
if (!value->isLexical())
358+
return false;
359+
if (value->getOwnershipKind() != OwnershipKind::Owned)
360+
return false;
361+
LexicalDestroyHoisting::Context context(value, function, deleter);
362+
return LexicalDestroyHoisting::run(context);
363+
}

0 commit comments

Comments
 (0)