Skip to content

Commit 1113bde

Browse files
authored
Merge pull request #19690 from gottesmm/pr-20fe767974f279f36f9ca7bfbec1133a41f07747
[semantic-arc-opts] Eliminate all copy_value from guaranteed argument…
2 parents d32677e + 099cae5 commit 1113bde

File tree

4 files changed

+284
-119
lines changed

4 files changed

+284
-119
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,12 @@ bool isGuaranteedForwardingValueKind(SILNodeKind kind);
114114

115115
bool isGuaranteedForwardingValue(SILValue value);
116116

117+
bool isOwnershipForwardingInst(SILInstruction *i);
118+
117119
bool isGuaranteedForwardingInst(SILInstruction *i);
118120

119-
bool isOwnershipForwardingInst(SILInstruction *i);
121+
bool getUnderlyingBorrowIntroducers(SILValue inputValue,
122+
SmallVectorImpl<SILValue> &out);
120123

121124
} // namespace swift
122125

lib/SIL/OwnershipUtils.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#include "swift/SIL/OwnershipUtils.h"
14+
#include "swift/SIL/SILArgument.h"
1415
#include "swift/SIL/SILInstruction.h"
1516

1617
using namespace swift;
@@ -75,3 +76,38 @@ bool swift::isGuaranteedForwardingInst(SILInstruction *i) {
7576
bool swift::isOwnershipForwardingInst(SILInstruction *i) {
7677
return isOwnershipForwardingValueKind(SILNodeKind(i->getKind()));
7778
}
79+
80+
bool swift::getUnderlyingBorrowIntroducers(SILValue inputValue,
81+
SmallVectorImpl<SILValue> &out) {
82+
SmallVector<SILValue, 32> worklist;
83+
worklist.emplace_back(inputValue);
84+
85+
while (!worklist.empty()) {
86+
SILValue v = worklist.pop_back_val();
87+
88+
// First check if v is an introducer. If so, stash it and continue.
89+
if (isa<SILFunctionArgument>(v) || isa<LoadBorrowInst>(v) ||
90+
isa<BeginBorrowInst>(v)) {
91+
out.push_back(v);
92+
continue;
93+
}
94+
95+
// Otherwise if v is an ownership forwarding value, add its defining
96+
// instruction
97+
if (isGuaranteedForwardingValue(v)) {
98+
auto *i = v->getDefiningInstruction();
99+
assert(i);
100+
transform(i->getAllOperands(), std::back_inserter(worklist),
101+
[](const Operand &op) -> SILValue { return op.get(); });
102+
continue;
103+
}
104+
105+
// If v produces any ownership, then we can ignore it. Otherwise, we need to
106+
// return false since this is an introducer we do not understand.
107+
if (v.getOwnershipKind() != ValueOwnershipKind::Any &&
108+
v.getOwnershipKind() != ValueOwnershipKind::Trivial)
109+
return false;
110+
}
111+
112+
return true;
113+
}

lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp

Lines changed: 166 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111
//===----------------------------------------------------------------------===//
1212

1313
#define DEBUG_TYPE "sil-semantic-arc-opts"
14+
#include "swift/Basic/STLExtras.h"
1415
#include "swift/SIL/BasicBlockUtils.h"
1516
#include "swift/SIL/OwnershipUtils.h"
1617
#include "swift/SIL/SILArgument.h"
1718
#include "swift/SIL/SILInstruction.h"
19+
#include "swift/SIL/SILVisitor.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,144 @@ 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+
static bool canHandleOperand(SILValue operand, SmallVectorImpl<SILValue> &out) {
75+
if (!getUnderlyingBorrowIntroducers(operand, out))
76+
return false;
12277

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-
}
78+
/// TODO: Add support for begin_borrow, load_borrow.
79+
return all_of(out, [](SILValue v) { return isa<SILFunctionArgument>(v); });
80+
}
13081

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

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

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

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

165-
DeadEndBlocks DEBlocks(F);
166-
OwnershipChecker Checker{{}, {}, {}, {}, F->getModule(), DEBlocks};
167-
168-
// First as a special case, handle guaranteed SIL function arguments.
189+
// Iterate over all of the arguments, performing small peephole
190+
// ARC optimizations.
169191
//
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)
192+
// FIXME: Should we iterate or use a RPOT order here?
193+
bool madeChange = false;
194+
for (auto &bb : f) {
195+
auto ii = bb.rend();
196+
auto start = bb.rbegin();
197+
198+
// If the bb is empty, continue.
199+
if (start == ii)
175200
continue;
176-
MadeChange |= optimizeGuaranteedArgument(Arg, Checker);
201+
202+
// Go to the first instruction to process.
203+
--ii;
204+
205+
// Then until we process the first instruction of the block...
206+
while (ii != start) {
207+
// Move the iterator before ii.
208+
auto tmp = std::next(ii);
209+
210+
// Then try to optimize. If we succeeded, then we deleted
211+
// ii. Move ii from the next value back onto the instruction
212+
// after ii's old value in the block instruction list and then
213+
// process that.
214+
if (SemanticARCOptVisitor().visit(&*ii)) {
215+
madeChange = true;
216+
ii = std::prev(tmp);
217+
continue;
218+
}
219+
220+
// Otherwise, we didn't delete ii. Just visit the next instruction.
221+
--ii;
222+
}
223+
224+
// Finally visit the first instruction of the block.
225+
madeChange |= SemanticARCOptVisitor().visit(&*ii);
177226
}
178227

179-
if (MadeChange) {
228+
if (madeChange) {
180229
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
181230
}
182231
}
183-
184232
};
185233

186234
} // end anonymous namespace

0 commit comments

Comments
 (0)