Skip to content

Commit 742ad76

Browse files
authored
Merge pull request #35481 from atrick/fix-rewrite-borrows
Fix -canonical-ossa-rewrite-borrows but leave it disabled.
2 parents 4879700 + f509d67 commit 742ad76

File tree

5 files changed

+459
-50
lines changed

5 files changed

+459
-50
lines changed

include/swift/SIL/OwnershipUtils.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,9 @@ struct BorrowingOperand {
217217
/// over a region of code instead of just for a single instruction, visit
218218
/// those uses.
219219
///
220+
/// Returns true if all visitor invocations returns true. Exits early if a
221+
/// visitor returns false.
222+
///
220223
/// Example: An apply performs an instantaneous recursive borrow of a
221224
/// guaranteed value but a begin_apply borrows the value over the entire
222225
/// region of code corresponding to the coroutine.

include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,8 @@ class CanonicalizeOSSALifetime {
196196

197197
DominanceAnalysis *dominanceAnalysis;
198198

199+
DeadEndBlocks *deBlocks;
200+
199201
/// Current copied def for which this state describes the liveness.
200202
SILValue currentDef;
201203

@@ -237,9 +239,10 @@ class CanonicalizeOSSALifetime {
237239
public:
238240
CanonicalizeOSSALifetime(bool pruneDebug,
239241
NonLocalAccessBlockAnalysis *accessBlockAnalysis,
240-
DominanceAnalysis *dominanceAnalysis)
242+
DominanceAnalysis *dominanceAnalysis,
243+
DeadEndBlocks *deBlocks)
241244
: pruneDebug(pruneDebug), accessBlockAnalysis(accessBlockAnalysis),
242-
dominanceAnalysis(dominanceAnalysis) {}
245+
dominanceAnalysis(dominanceAnalysis), deBlocks(deBlocks) {}
243246

244247
SILValue getCurrentDef() const { return currentDef; }
245248

@@ -293,7 +296,7 @@ class CanonicalizeOSSALifetime {
293296

294297
bool computeBorrowLiveness();
295298

296-
void consolidateBorrowScope();
299+
bool consolidateBorrowScope();
297300

298301
bool computeCanonicalLiveness();
299302

lib/SILOptimizer/Transforms/CopyPropagation.cpp

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,11 @@
2626
#define DEBUG_TYPE "copy-propagation"
2727

2828
#include "swift/SIL/BasicBlockUtils.h"
29+
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
2930
#include "swift/SILOptimizer/PassManager/Passes.h"
3031
#include "swift/SILOptimizer/PassManager/Transforms.h"
3132
#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h"
33+
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
3234

3335
using namespace swift;
3436

@@ -49,11 +51,26 @@ class CopyPropagation : public SILFunctionTransform {
4951
};
5052
} // end anonymous namespace
5153

54+
static bool isCopyDead(CopyValueInst *copy, bool pruneDebug) {
55+
for (Operand *use : copy->getUses()) {
56+
auto *user = use->getUser();
57+
if (isa<DestroyValueInst>(user)) {
58+
continue;
59+
}
60+
if (pruneDebug && isa<DebugValueInst>(user)) {
61+
continue;
62+
}
63+
return false;
64+
}
65+
return true;
66+
}
67+
5268
/// Top-level pass driver.
5369
void CopyPropagation::run() {
5470
auto *f = getFunction();
5571
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
5672
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
73+
auto *deBlocksAnalysis = getAnalysis<DeadEndBlocksAnalysis>();
5774

5875
// Debug label for unit testing.
5976
LLVM_DEBUG(llvm::dbgs() << "*** CopyPropagation: " << f->getName() << "\n");
@@ -73,23 +90,39 @@ void CopyPropagation::run() {
7390
}
7491
// Perform copy propgation for each copied value.
7592
CanonicalizeOSSALifetime canonicalizer(pruneDebug, accessBlockAnalysis,
76-
dominanceAnalysis);
93+
dominanceAnalysis,
94+
deBlocksAnalysis->get(f));
95+
// Cleanup dead copies. If getCanonicalCopiedDef returns a copy (because the
96+
// copy's source operand is unrecgonized), then the copy is itself treated
97+
// like a def and may be dead after canonicalization.
98+
llvm::SmallVector<CopyValueInst *, 4> deadCopies;
7799
for (auto &def : copiedDefs) {
100+
// Canonicalized this def.
78101
canonicalizer.canonicalizeValueLifetime(def);
102+
103+
if (auto *copy = dyn_cast<CopyValueInst>(def)) {
104+
if (isCopyDead(copy, pruneDebug)) {
105+
deadCopies.push_back(copy);
106+
}
107+
}
108+
// Canonicalize any new outer copy.
79109
if (SILValue outerCopy = canonicalizer.createdOuterCopy()) {
80110
SILValue outerDef = canonicalizer.getCanonicalCopiedDef(outerCopy);
81111
canonicalizer.canonicalizeValueLifetime(outerDef);
82112
}
83113
// TODO: also canonicalize any lifetime.persistentCopies like separate owned
84114
// live ranges.
85115
}
86-
if (canonicalizer.hasChanged()) {
116+
if (canonicalizer.hasChanged() || !deadCopies.empty()) {
117+
InstructionDeleter deleter;
118+
for (auto *copy : deadCopies) {
119+
deleter.recursivelyDeleteUsersIfDead(copy);
120+
}
87121
// Preserves NonLocalAccessBlockAnalysis.
88122
accessBlockAnalysis->lockInvalidation();
89123
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
90124
accessBlockAnalysis->unlockInvalidation();
91-
DeadEndBlocks deBlocks(f);
92-
f->verifyOwnership(&deBlocks);
125+
f->verifyOwnership(deBlocksAnalysis->get(f));
93126
}
94127
}
95128

lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp

Lines changed: 129 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
#include "swift/SIL/OwnershipUtils.h"
4646
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
4747
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
48+
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
4849
#include "llvm/ADT/Statistic.h"
4950

5051
using namespace swift;
@@ -73,8 +74,19 @@ SILValue CanonicalizeOSSALifetime::getCanonicalCopiedDef(SILValue v) {
7374
case BorrowedValueKind::Invalid:
7475
llvm_unreachable("Using invalid case?!");
7576
case BorrowedValueKind::SILFunctionArgument:
76-
case BorrowedValueKind::BeginBorrow:
7777
return def;
78+
case BorrowedValueKind::BeginBorrow: {
79+
bool localScope = true;
80+
// TODO: visitLocalScopeEndingUses should have an early exit.
81+
borrowedVal.visitLocalScopeEndingUses([&](Operand *endBorrow) {
82+
if (endBorrow->getUser()->getParent() != def->getParentBlock())
83+
localScope = false;
84+
});
85+
if (localScope) {
86+
return def;
87+
}
88+
break;
89+
}
7890
case BorrowedValueKind::LoadBorrow:
7991
case BorrowedValueKind::Phi:
8092
break;
@@ -87,6 +99,20 @@ SILValue CanonicalizeOSSALifetime::getCanonicalCopiedDef(SILValue v) {
8799
return v;
88100
}
89101

102+
/// The lifetime extends beyond given consuming use. Copy the value.
103+
static void copyLiveUse(Operand *use) {
104+
SILInstruction *user = use->getUser();
105+
SILBuilderWithScope B(user->getIterator());
106+
107+
auto loc = RegularLocation::getAutoGeneratedLocation(
108+
user->getLoc().getSourceLoc());
109+
auto *copy = B.createCopyValue(loc, use->get());
110+
use->set(copy);
111+
112+
++NumCopiesGenerated;
113+
LLVM_DEBUG(llvm::dbgs() << " Copying at last use " << *copy);
114+
}
115+
90116
//===----------------------------------------------------------------------===//
91117
// MARK: Rewrite borrow scopes
92118
//===----------------------------------------------------------------------===//
@@ -121,12 +147,11 @@ bool CanonicalizeOSSALifetime::computeBorrowLiveness() {
121147
liveness.updateForUse(use->getUser(), /*lifetimeEnding*/ true);
122148
});
123149

124-
// TODO: Remove this check. Canonicalize multi-block borrow scopes only after
125-
// consolidateBorrowScope can handle persistentCopies, otherwise we may end up
126-
// generating more dynamic copies than the non-canonical form.
127-
if (liveness.numLiveBlocks() > 1) {
128-
return false;
129-
}
150+
// TODO: Fix getCanonicalCopiedDef to allow multi-block borrows and remove
151+
// this assert. This should only be done once consolidateBorrowScope can
152+
// handle persistentCopies, otherwise we may end up generating more dynamic
153+
// copies than the non-canonical form.
154+
assert(liveness.numLiveBlocks() == 1);
130155
return true;
131156
}
132157

@@ -153,15 +178,26 @@ static CopyValueInst *createOuterCopy(BeginBorrowInst *beginBorrow) {
153178
// TODO: Canonicalize multi-block borrow scopes, load_borrow scope, and phi
154179
// borrow scopes by adding one copy per block to persistentCopies for
155180
// each block that dominates an outer use.
156-
void CanonicalizeOSSALifetime::consolidateBorrowScope() {
181+
bool CanonicalizeOSSALifetime::consolidateBorrowScope() {
157182
if (isa<SILFunctionArgument>(currentDef)) {
158-
return;
183+
return true;
159184
}
160185
// Gather all outer uses before rewriting any to avoid scanning any basic
161186
// block more than once.
162187
SmallVector<Operand *, 8> outerUses;
163188
llvm::SmallPtrSet<SILInstruction *, 8> outerUseInsts;
189+
auto isUserInLiveOutBlock = [&](SILInstruction *user) {
190+
// TODO: enable isUserInLiveOutBlock once we support multi-block borrows
191+
// return (liveness.getBlockLiveness(user->getParent())
192+
// == PrunedLiveBlocks::LiveOut);
193+
return false;
194+
};
164195
auto recordOuterUse = [&](Operand *use) {
196+
// If the user's block is LiveOut, then it is definitely within the borrow
197+
// scope, so there's no need to record it.
198+
if (isUserInLiveOutBlock(use->getUser())) {
199+
return;
200+
}
165201
outerUses.push_back(use);
166202
outerUseInsts.insert(use->getUser());
167203
};
@@ -177,12 +213,10 @@ void CanonicalizeOSSALifetime::consolidateBorrowScope() {
177213
defUseWorklist.insert(copy);
178214
continue;
179215
}
180-
// debug_value uses are handled like normal uses here. They should be
181-
// stripped later if required when handling outerCopy or persistentCopies.
182-
if (liveness.getBlockLiveness(user->getParent())
183-
== PrunedLiveBlocks::LiveOut) {
184-
continue;
185-
}
216+
// Note: debug_value uses are handled like normal uses here. They should
217+
// be stripped later if required when handling outerCopy or
218+
// persistentCopies.
219+
186220
switch (use->getOperandOwnership()) {
187221
case OperandOwnership::NonUse:
188222
break;
@@ -198,41 +232,102 @@ void CanonicalizeOSSALifetime::consolidateBorrowScope() {
198232

199233
case OperandOwnership::ForwardingUnowned:
200234
case OperandOwnership::PointerEscape:
235+
return false;
236+
201237
case OperandOwnership::InstantaneousUse:
202238
case OperandOwnership::UnownedInstantaneousUse:
203239
case OperandOwnership::BitwiseEscape:
204240
case OperandOwnership::ForwardingConsume:
205241
case OperandOwnership::DestroyingConsume:
242+
recordOuterUse(use);
243+
break;
206244
case OperandOwnership::Borrow:
245+
BorrowingOperand borrowOper(use);
246+
if (borrowOper.kind == BorrowingOperandKind::Invalid) {
247+
return false;
248+
}
207249
recordOuterUse(use);
250+
// For borrows, record the scope-ending instructions in addition to the
251+
// borrow instruction as an outer use point.
252+
borrowOper.visitLocalEndScopeUses([&](Operand *endBorrow) {
253+
if (!isUserInLiveOutBlock(endBorrow->getUser())) {
254+
outerUseInsts.insert(endBorrow->getUser());
255+
}
256+
return true;
257+
});
208258
break;
209259
}
210260
}
211261
} // end def-use traversal
212262

213-
// Remove outer uses that occur before the end of the borrow scope.
214263
auto *beginBorrow = cast<BeginBorrowInst>(currentDef);
215-
BorrowedValue::get(beginBorrow).visitLocalScopeEndingUses([&](Operand *use) {
216-
// Forward iterate until we find the end of the borrow scope.
217-
auto *endScope = use->getUser();
218-
for (auto instIter = beginBorrow->getIterator(),
219-
endIter = endScope->getIterator();
220-
instIter != endIter; ++instIter) {
221-
outerUseInsts.erase(&*instIter);
222-
}
223-
});
264+
SmallVector<SILInstruction *, 1> scopeEndingInst;
265+
BorrowedValue::get(beginBorrow)
266+
.getLocalScopeEndingInstructions(scopeEndingInst);
267+
assert(scopeEndingInst.size() == 1 && "expected single-block borrow");
268+
// Remove outer uses that occur before the end of the borrow scope by
269+
// forward iterating from begin_borrow to end_borrow.
270+
for (auto instIter = beginBorrow->getIterator(),
271+
endIter = scopeEndingInst[0]->getIterator();
272+
instIter != endIter; ++instIter) {
273+
outerUseInsts.erase(&*instIter);
274+
}
224275
if (outerUseInsts.empty()) {
225-
return;
276+
return true;
226277
}
227-
// Rewrite the outer uses.
278+
// Rewrite the outer uses and record lifetime-ending uses.
279+
SmallVector<Operand *, 4> consumingUses;
280+
SmallPtrSet<SILInstruction *, 4> unclaimedConsumingUsers;
228281
this->outerCopy = createOuterCopy(beginBorrow);
229282
for (Operand *use : outerUses) {
230283
if (!outerUseInsts.count(use->getUser())) {
231-
continue;
284+
// The immediate use is within this borrow scope.
285+
BorrowingOperand borrowOper(use);
286+
if (borrowOper.kind == BorrowingOperandKind::Invalid) {
287+
continue;
288+
}
289+
// For sub-borrows also check that the scope-ending instructions are
290+
// within the scope.
291+
if (borrowOper.visitLocalEndScopeUses([&](Operand *endBorrow) {
292+
return !outerUseInsts.count(endBorrow->getUser());
293+
})) {
294+
continue;
295+
}
232296
}
233297
LLVM_DEBUG(llvm::dbgs() << " Use of outer copy " << *use->getUser());
234298
use->set(outerCopy);
299+
if (use->isLifetimeEnding()) {
300+
consumingUses.push_back(use);
301+
unclaimedConsumingUsers.insert(use->getUser());
302+
}
303+
}
304+
// Insert a destroy on the outer copy's lifetime frontier, or claim an
305+
// existing consume.
306+
ValueLifetimeAnalysis lifetimeAnalysis(outerCopy, outerUseInsts);
307+
ValueLifetimeAnalysis::Frontier frontier;
308+
bool result = lifetimeAnalysis.computeFrontier(
309+
frontier, ValueLifetimeAnalysis::DontModifyCFG, deBlocks);
310+
assert(result);
311+
while (!frontier.empty()) {
312+
auto *insertPt = frontier.pop_back_val();
313+
if (unclaimedConsumingUsers.erase(&*std::prev(insertPt->getIterator()))) {
314+
continue;
315+
}
316+
SILBuilderWithScope(insertPt).createDestroyValue(insertPt->getLoc(),
317+
outerCopy);
235318
}
319+
// Add copies for consuming users of outerCopy.
320+
for (auto *use : consumingUses) {
321+
// If the user is still in the unclaimedConsumingUsers set, then it does not
322+
// end the outer copy's lifetime and therefore requires a copy. Only one
323+
// operand can be claimed as ending the lifetime, so return its user to the
324+
// unclaimedConsumingUsers set after skipping the first copy.
325+
auto iterAndInserted = unclaimedConsumingUsers.insert(use->getUser());
326+
if (!iterAndInserted.second) {
327+
copyLiveUse(use);
328+
}
329+
}
330+
return true;
236331
}
237332

238333
//===----------------------------------------------------------------------===//
@@ -650,20 +745,6 @@ void CanonicalizeOSSALifetime::findOrInsertDestroys() {
650745
// MARK: Step 3. Rewrite copies and destroys
651746
//===----------------------------------------------------------------------===//
652747

653-
/// The lifetime extends beyond given consuming use. Copy the value.
654-
static void copyLiveUse(Operand *use) {
655-
SILInstruction *user = use->getUser();
656-
SILBuilderWithScope B(user->getIterator());
657-
658-
auto loc = RegularLocation::getAutoGeneratedLocation(
659-
user->getLoc().getSourceLoc());
660-
auto *copy = B.createCopyValue(loc, use->get());
661-
use->set(copy);
662-
663-
++NumCopiesGenerated;
664-
LLVM_DEBUG(llvm::dbgs() << " Copying at last use " << *copy);
665-
}
666-
667748
/// Revisit the def-use chain of currentDef. Mark unneeded original
668749
/// copies and destroys for deletion. Insert new copies for interior uses that
669750
/// require ownership of the used operand.
@@ -774,6 +855,8 @@ void CanonicalizeOSSALifetime::rewriteCopies() {
774855
//===----------------------------------------------------------------------===//
775856

776857
bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) {
858+
LLVM_DEBUG(llvm::dbgs() << " Canonicalizing: " << def);
859+
777860
switch (def.getOwnershipKind()) {
778861
case OwnershipKind::None:
779862
case OwnershipKind::Unowned:
@@ -787,7 +870,10 @@ bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def) {
787870
}
788871
// Set outerCopy and persistentCopies and rewrite uses
789872
// outside the scope.
790-
consolidateBorrowScope();
873+
if (!consolidateBorrowScope()) {
874+
clearLiveness();
875+
return false;
876+
}
791877
// Invalidate book-keeping before deleting instructions.
792878
clearLiveness();
793879
// Rewrite copies and delete extra destroys within the scope.

0 commit comments

Comments
 (0)