Skip to content

Commit 5a73063

Browse files
committed
[semantic-arc-opts] Eliminate all copy_value from guaranteed arguments that have all uses that can accept values with guaranteed ownership.
This takes advantage of my restricting Semantic ARC Opts to run only on the stdlib since we know it passes the ownership verifier. Using that I reimplemented this optimization in a more robust, elegant, general way. Specifically, we now will eliminate any SILGen copy_value on a guaranteed argument all of whose uses are either destroy_values or instructions that can accept guaranteed parameters. To be clear: This means that the value must be consumed in the same function only be destroy_value. Since we know that the lifetime of the guaranteed parameter will be larger than any owned value created/destroyed in the body, we do not need to check that the copy_value's destroy_value set is joint post-dominated by the set of end_borrows. That will have to be added to turn this into a general optimization.
1 parent 6462473 commit 5a73063

File tree

2 files changed

+243
-118
lines changed

2 files changed

+243
-118
lines changed

lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp

Lines changed: 165 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
#include "swift/SIL/OwnershipUtils.h"
1616
#include "swift/SIL/SILArgument.h"
1717
#include "swift/SIL/SILInstruction.h"
18+
#include "swift/SIL/SILVisitor.h"
19+
#include "swift/Basic/STLExtras.h"
1820
#include "swift/SILOptimizer/Analysis/PostOrderAnalysis.h"
1921
#include "swift/SILOptimizer/PassManager/Passes.h"
2022
#include "swift/SILOptimizer/PassManager/Transforms.h"
@@ -26,123 +28,143 @@ using namespace swift;
2628

2729
STATISTIC(NumEliminatedInsts, "number of removed instructions");
2830

29-
static bool optimizeGuaranteedArgument(SILArgument *Arg,
30-
OwnershipChecker &Checker) {
31-
bool MadeChange = false;
31+
namespace {
3232

33-
// Gather all copy_value users of Arg.
34-
llvm::SmallVector<CopyValueInst *, 4> Worklist;
35-
for (auto *Op : Arg->getUses()) {
36-
if (auto *CVI = dyn_cast<CopyValueInst>(Op->getUser())) {
37-
Worklist.push_back(CVI);
38-
}
39-
}
33+
struct SemanticARCOptVisitor
34+
: SILInstructionVisitor<SemanticARCOptVisitor, bool> {
35+
bool visitSILInstruction(SILInstruction *i) { return false; }
36+
bool visitCopyValueInst(CopyValueInst *cvi);
37+
bool visitBeginBorrowInst(BeginBorrowInst *bbi);
38+
};
39+
40+
} // end anonymous namespace
4041

41-
// Then until we run out of copies...
42-
while (!Worklist.empty()) {
43-
auto *CVI = Worklist.pop_back_val();
44-
45-
// Quickly see if copy has only one use and that use is a destroy_value. In
46-
// such a case, we can always eliminate both the copy and the destroy.
47-
if (auto *Op = CVI->getSingleUse()) {
48-
if (auto *DVI = dyn_cast<DestroyValueInst>(Op->getUser())) {
49-
DVI->eraseFromParent();
50-
CVI->eraseFromParent();
51-
NumEliminatedInsts += 2;
42+
bool SemanticARCOptVisitor::visitBeginBorrowInst(BeginBorrowInst *bbi) {
43+
auto kind = bbi->getOperand().getOwnershipKind();
44+
SmallVector<EndBorrowInst *, 16> endBorrows;
45+
for (auto *op : bbi->getUses()) {
46+
auto *user = op->getUser();
47+
switch (user->getKind()) {
48+
case SILInstructionKind::EndBorrowInst:
49+
endBorrows.push_back(cast<EndBorrowInst>(user));
50+
break;
51+
default:
52+
// Make sure that this operand can accept our arguments kind.
53+
auto map = op->getOwnershipKindMap();
54+
if (map.canAcceptKind(kind))
5255
continue;
53-
}
56+
return false;
5457
}
58+
}
5559

56-
// Ok, now run the checker on the copy value. If it fails, then we just
57-
// continue.
58-
if (!Checker.checkValue(CVI))
59-
continue;
60-
61-
// Otherwise, lets do a quick check on what the checker thinks the lifetime
62-
// ending and non-lifetime ending users. To be conservative, we bail unless
63-
// each lifetime ending use is a destroy_value and if each non-lifetime
64-
// ending use is one of the following instructions:
65-
//
66-
// 1. copy_value.
67-
// 2. begin_borrow.
68-
// 3. end_borrow.
69-
if (!all_of(Checker.lifetimeEndingUsers, [](SILInstruction *I) -> bool {
70-
return isa<DestroyValueInst>(I);
71-
}))
72-
continue;
73-
74-
// Extra copy values that we should visit recursively.
75-
llvm::SmallVector<CopyValueInst *, 8> NewCopyInsts;
76-
llvm::SmallVector<SILInstruction *, 8> NewBorrowInsts;
77-
if (!all_of(Checker.regularUsers, [&](SILInstruction *I) -> bool {
78-
if (auto *CVI = dyn_cast<CopyValueInst>(I)) {
79-
NewCopyInsts.push_back(CVI);
80-
return true;
81-
}
82-
83-
if (!isa<BeginBorrowInst>(I) && !isa<EndBorrowInst>(I))
84-
return false;
85-
86-
NewBorrowInsts.push_back(I);
87-
return true;
88-
}))
89-
continue;
90-
91-
// Ok! we can remove the copy_value, destroy_values!
92-
MadeChange = true;
93-
CVI->replaceAllUsesWith(CVI->getOperand());
94-
CVI->eraseFromParent();
60+
// At this point, we know that the begin_borrow's operand can be
61+
// used as an argument to all non-end borrow uses. Eliminate the
62+
// begin borrow and end borrows.
63+
while (!endBorrows.empty()) {
64+
auto *ebi = endBorrows.pop_back_val();
65+
ebi->eraseFromParent();
9566
++NumEliminatedInsts;
67+
}
68+
bbi->replaceAllUsesWith(bbi->getOperand());
69+
bbi->eraseFromParent();
70+
++NumEliminatedInsts;
71+
return true;
72+
}
9673

97-
while (!Checker.lifetimeEndingUsers.empty()) {
98-
Checker.lifetimeEndingUsers.pop_back_val()->eraseFromParent();
99-
++NumEliminatedInsts;
100-
}
101-
102-
// Then add the copy_values that were users of our original copy value to
103-
// the worklist.
104-
while (!NewCopyInsts.empty()) {
105-
Worklist.push_back(NewCopyInsts.pop_back_val());
106-
}
107-
108-
// Then remove any begin/end borrow that we found. These are unneeded since
109-
// the lifetime guarantee from the argument exists above and beyond said
110-
// scope.
111-
while (!NewBorrowInsts.empty()) {
112-
SILInstruction *I = NewBorrowInsts.pop_back_val();
113-
if (auto *BBI = dyn_cast<BeginBorrowInst>(I)) {
114-
// Any copy_value that is used by the begin borrow is added to the
115-
// worklist.
116-
for (auto *BBIUse : BBI->getUses()) {
117-
if (auto *BBIUseCopyValue =
118-
dyn_cast<CopyValueInst>(BBIUse->getUser())) {
119-
Worklist.push_back(BBIUseCopyValue);
120-
}
121-
}
74+
/// TODO: Add support for begin_borrow, load_borrow.
75+
static bool canHandleOperand(SILValue operand) {
76+
if (auto *arg = dyn_cast<SILFunctionArgument>(operand)) {
77+
return arg->getOwnershipKind() == ValueOwnershipKind::Guaranteed;
78+
}
12279

123-
// First go through and eliminate all end borrows.
124-
SmallVector<EndBorrowInst *, 4> endBorrows;
125-
copy(BBI->getEndBorrows(), std::back_inserter(endBorrows));
126-
while (!endBorrows.empty()) {
127-
endBorrows.pop_back_val()->eraseFromParent();
128-
++NumEliminatedInsts;
129-
}
80+
return false;
81+
}
13082

131-
// Then eliminate BBI itself.
132-
BBI->replaceAllUsesWith(BBI->getOperand());
133-
BBI->eraseFromParent();
134-
++NumEliminatedInsts;
83+
static bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi) {
84+
// Whitelist the operands that we know how to support and make sure
85+
// our operand is actually guaranteed.
86+
if (!canHandleOperand(cvi->getOperand()))
87+
return false;
88+
89+
// Then go over all of our uses. Find our destroying instructions
90+
// and make sure all of them are destroy_value. For our
91+
// non-destroying instructions, make sure that they accept a
92+
// guaranteed value. After that, make sure that our destroys are
93+
// within the lifetime of our borrowed values.
94+
SmallVector<DestroyValueInst *, 16> destroys;
95+
for (auto *op : cvi->getUses()) {
96+
// We know that a copy_value produces an @owned value. Look
97+
// through all of our uses and classify them as either
98+
// invalidating or not invalidating. Make sure that all of the
99+
// invalidating ones are destroy_value since otherwise the
100+
// live_range is not complete.
101+
auto map = op->getOwnershipKindMap();
102+
auto constraint = map.getLifetimeConstraint(ValueOwnershipKind::Owned);
103+
switch (constraint) {
104+
case UseLifetimeConstraint::MustBeInvalidated:
105+
// And we have a destroy_value, track it and continue.
106+
if (auto *dvi = dyn_cast<DestroyValueInst>(op->getUser())) {
107+
destroys.push_back(dvi);
135108
continue;
136109
}
110+
// Otherwise, we found a non-destroy value invalidating owned
111+
// user... This is not an unnecessary live range.
112+
return false;
113+
case UseLifetimeConstraint::MustBeLive:
114+
// Ok, this constraint can take something owned as live. Lets
115+
// see if it can also take something that is guaranteed. If it
116+
// can not, then we bail.
117+
if (!map.canAcceptKind(ValueOwnershipKind::Guaranteed)) {
118+
return false;
119+
}
137120

138-
// This is not necessary, but it does add a check.
139-
auto *EBI = cast<EndBorrowInst>(I);
140-
EBI->eraseFromParent();
141-
++NumEliminatedInsts;
121+
// Otherwise, continue.
122+
continue;
142123
}
143124
}
144125

145-
return MadeChange;
126+
// If we reached this point, then we know that all of our users can
127+
// accept a guaranteed value and our owned value is destroyed only
128+
// by destroy_value. Check if all of our destroys are joint
129+
// post-dominated by the end_borrow set. If they do not, then the
130+
// copy_value is lifetime extending the guaranteed value, we can not
131+
// eliminate it.
132+
//
133+
// TODO: When we support begin_borrow/load_borrow a linear linfetime
134+
// check will be needed here.
135+
assert(isa<SILFunctionArgument>(cvi->getOperand()) &&
136+
"This code must be updated for begin_borrow/load_borrow?!");
137+
138+
// Otherwise, we know that our copy_value/destroy_values are all
139+
// completely within the guaranteed value scope.
140+
while (!destroys.empty()) {
141+
auto *dvi = destroys.pop_back_val();
142+
dvi->eraseFromParent();
143+
++NumEliminatedInsts;
144+
}
145+
cvi->replaceAllUsesWith(cvi->getOperand());
146+
cvi->eraseFromParent();
147+
++NumEliminatedInsts;
148+
return true;
149+
}
150+
151+
bool SemanticARCOptVisitor::visitCopyValueInst(CopyValueInst *cvi) {
152+
// If our copy value inst has a single destroy value user, eliminate
153+
// it.
154+
if (auto *op = cvi->getSingleUse()) {
155+
if (auto *dvi = dyn_cast<DestroyValueInst>(op->getUser())) {
156+
dvi->eraseFromParent();
157+
cvi->eraseFromParent();
158+
NumEliminatedInsts += 2;
159+
return true;
160+
}
161+
}
162+
163+
// Then try to perform the guaranteed copy value optimization.
164+
if (performGuaranteedCopyValueOptimization(cvi))
165+
return true;
166+
167+
return false;
146168
}
147169

148170
//===----------------------------------------------------------------------===//
@@ -156,31 +178,56 @@ namespace {
156178
// configuration.
157179
struct SemanticARCOpts : SILFunctionTransform {
158180
void run() override {
159-
bool MadeChange = false;
160-
SILFunction *F = getFunction();
161-
if (!F->getModule().isStdlibModule()) {
181+
SILFunction &f = *getFunction();
182+
// Do not run the semantic arc opts unless we are running on the
183+
// standard library. This is because this is the only module that
184+
// passes the ownership verifier.
185+
if (!f.getModule().isStdlibModule())
162186
return;
163-
}
164187

165-
DeadEndBlocks DEBlocks(F);
166-
OwnershipChecker Checker{{}, {}, {}, {}, F->getModule(), DEBlocks};
167-
168-
// First as a special case, handle guaranteed SIL function arguments.
188+
// Iterate over all of the arguments, performing small peephole
189+
// ARC optimizations.
169190
//
170-
// The reason that this is special is that we do not need to consider the
171-
// end of the borrow scope since the end of the function is the end of the
172-
// borrow scope.
173-
for (auto *Arg : F->getArguments()) {
174-
if (Arg->getOwnershipKind() != ValueOwnershipKind::Guaranteed)
191+
// FIXME: Should we iterate or use a RPOT order here?
192+
bool madeChange = false;
193+
for (auto &bb : f) {
194+
auto ii = bb.rend();
195+
auto start = bb.rbegin();
196+
197+
// If the bb is empty, continue.
198+
if (start == ii)
175199
continue;
176-
MadeChange |= optimizeGuaranteedArgument(Arg, Checker);
200+
201+
// Go to the first instruction to process.
202+
--ii;
203+
204+
// Then until we process the first instruction of the block...
205+
while (ii != start) {
206+
// Move the iterator before ii.
207+
auto tmp = std::next(ii);
208+
209+
// Then try to optimize. If we succeeded, then we deleted
210+
// ii. Move ii from the next value back onto the instruction
211+
// after ii's old value in the block instruction list and then
212+
// process that.
213+
if (SemanticARCOptVisitor().visit(&*ii)) {
214+
madeChange = true;
215+
ii = std::prev(tmp);
216+
continue;
217+
}
218+
219+
// Otherwise, we didn't delete ii. Just visit the next instruction.
220+
--ii;
221+
}
222+
223+
// Finally visit the first instruction of the block.
224+
madeChange |= SemanticARCOptVisitor().visit(&*ii);
177225
}
178226

179-
if (MadeChange) {
227+
if (madeChange) {
180228
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
181229
}
182230
}
183-
184231
};
185232

186233
} // end anonymous namespace

0 commit comments

Comments
 (0)