Skip to content

Commit ed8b1b2

Browse files
authored
Merge pull request swiftlang#36970 from atrick/destructure-conversion
CanonicalizeOSSA: Destructure canonicalization and conslidate borrow scope support for destructure
2 parents ce7bdab + 340feb4 commit ed8b1b2

File tree

6 files changed

+636
-174
lines changed

6 files changed

+636
-174
lines changed

include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h

Lines changed: 80 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,18 @@
103103

104104
namespace swift {
105105

106+
/// Convert this struct_extract into a copy+destructure. Return the destructured
107+
/// result or invalid SILValue. The caller must delete the extract and its
108+
/// now-dead copy use.
109+
///
110+
// If a copied-def is a struct-extract, attempt a destructure conversion
111+
// %extract = struct_extract %... : $TypeWithSingleOwnershipValue
112+
// %copy = copy_value %extract : $OwnershipValue
113+
// To:
114+
// %copy = copy_value %extract : $TypeWithSingleOwnershipValue
115+
// (%extracted,...) = destructure %copy : $TypeWithSingleOwnershipValue
116+
SILValue convertExtractToDestructure(StructExtractInst *extract);
117+
106118
/// Information about consumes on the extended-lifetime boundary. Consuming uses
107119
/// within the lifetime are not included--they will consume a copy after
108120
/// rewriting. For borrowed def values, the consumes do not include the end of
@@ -201,6 +213,50 @@ class CanonicalOSSAConsumeInfo {
201213
SWIFT_ASSERT_ONLY_DECL(void dump() const LLVM_ATTRIBUTE_USED);
202214
};
203215

216+
// Worklist of pointer-like things that have an invalid default value. Avoid
217+
// revisiting nodes--suitable for DAGs, but pops finished nodes without
218+
// preserving them in the vector.
219+
//
220+
// The primary API has two methods: intialize() and pop(). Others are provided
221+
// for flexibility.
222+
//
223+
// TODO: make this a better utility.
224+
template <typename T, unsigned SmallSize> struct PtrWorklist {
225+
SmallPtrSet<T, SmallSize> ptrVisited;
226+
SmallVector<T, SmallSize> ptrVector;
227+
228+
PtrWorklist() = default;
229+
230+
PtrWorklist(const PtrWorklist &) = delete;
231+
232+
void initialize(T t) {
233+
clear();
234+
insert(t);
235+
}
236+
237+
template <typename R> void initializeRange(R &&range) {
238+
clear();
239+
ptrVisited.insert(range.begin(), range.end());
240+
ptrVector.append(range.begin(), range.end());
241+
}
242+
243+
T pop() { return empty() ? T() : ptrVector.pop_back_val(); }
244+
245+
bool empty() const { return ptrVector.empty(); }
246+
247+
unsigned size() const { return ptrVector.size(); }
248+
249+
void clear() {
250+
ptrVector.clear();
251+
ptrVisited.clear();
252+
}
253+
254+
void insert(T t) {
255+
if (ptrVisited.insert(t).second)
256+
ptrVector.push_back(t);
257+
}
258+
};
259+
204260
/// Canonicalize OSSA lifetimes.
205261
///
206262
/// Allows the allocation of analysis state to be reused across calls to
@@ -215,6 +271,10 @@ class CanonicalizeOSSALifetime {
215271
/// liveness may be pruned during canonicalization.
216272
bool pruneDebugMode;
217273

274+
/// If true, borrows scopes will be canonicalized allowing copies of
275+
/// guaranteed values to be eliminated.
276+
bool canonicalizeBorrowMode;
277+
218278
/// If true, then new destroy_value instructions will be poison.
219279
bool poisonRefsMode;
220280

@@ -225,8 +285,6 @@ class CanonicalizeOSSALifetime {
225285

226286
DominanceAnalysis *dominanceAnalysis;
227287

228-
DeadEndBlocks *deBlocks;
229-
230288
/// Current copied def for which this state describes the liveness.
231289
SILValue currentDef;
232290

@@ -250,11 +308,11 @@ class CanonicalizeOSSALifetime {
250308
/// outisde the pruned liveness at the time it is discovered.
251309
llvm::SmallPtrSet<DebugValueInst *, 8> debugValues;
252310

253-
/// Reuse a general worklist for def-use traversal.
254-
SmallSetVector<SILValue, 8> defUseWorklist;
311+
/// Reuse a general visited set for def-use traversal.
312+
PtrWorklist<SILValue, 8> defUseWorklist;
255313

256314
/// Reuse a general worklist for CFG traversal.
257-
SmallSetVector<SILBasicBlock *, 8> blockWorklist;
315+
PtrWorklist<SILBasicBlock *, 8> blockWorklist;
258316

259317
/// Pruned liveness for the extended live range including copies. For this
260318
/// purpose, only consuming instructions are considered "lifetime
@@ -276,13 +334,15 @@ class CanonicalizeOSSALifetime {
276334
CanonicalOSSAConsumeInfo consumes;
277335

278336
public:
279-
CanonicalizeOSSALifetime(bool pruneDebugMode, bool poisonRefsMode,
337+
CanonicalizeOSSALifetime(bool pruneDebugMode, bool canonicalizeBorrowMode,
338+
bool poisonRefsMode,
280339
NonLocalAccessBlockAnalysis *accessBlockAnalysis,
281-
DominanceAnalysis *dominanceAnalysis,
282-
DeadEndBlocks *deBlocks)
283-
: pruneDebugMode(pruneDebugMode), poisonRefsMode(poisonRefsMode),
284-
accessBlockAnalysis(accessBlockAnalysis),
285-
dominanceAnalysis(dominanceAnalysis), deBlocks(deBlocks) {}
340+
DominanceAnalysis *dominanceAnalysis)
341+
: pruneDebugMode(pruneDebugMode),
342+
canonicalizeBorrowMode(canonicalizeBorrowMode),
343+
poisonRefsMode(poisonRefsMode),
344+
accessBlockAnalysis(accessBlockAnalysis),
345+
dominanceAnalysis(dominanceAnalysis) {}
286346

287347
SILValue getCurrentDef() const { return currentDef; }
288348

@@ -339,6 +399,15 @@ class CanonicalizeOSSALifetime {
339399

340400
bool consolidateBorrowScope();
341401

402+
bool findBorrowScopeUses(llvm::SmallPtrSetImpl<SILInstruction *> &useInsts);
403+
404+
void filterOuterBorrowUseInsts(
405+
llvm::SmallPtrSetImpl<SILInstruction *> &outerUseInsts);
406+
407+
void rewriteOuterBorrowUsesAndFindConsumes(
408+
SILValue incomingValue,
409+
llvm::SmallPtrSetImpl<SILInstruction *> &outerUseInsts);
410+
342411
bool computeCanonicalLiveness();
343412

344413
bool endsAccessOverlappingPrunedBoundary(SILInstruction *inst);

lib/SILOptimizer/Transforms/CopyPropagation.cpp

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

2828
#include "swift/SIL/BasicBlockUtils.h"
29+
#include "swift/SIL/SILUndef.h"
2930
#include "swift/SILOptimizer/Analysis/DeadEndBlocksAnalysis.h"
3031
#include "swift/SILOptimizer/PassManager/Passes.h"
3132
#include "swift/SILOptimizer/PassManager/Transforms.h"
@@ -34,6 +35,12 @@
3435

3536
using namespace swift;
3637

38+
// This only applies to -O copy-propagation.
39+
llvm::cl::opt<bool>
40+
EnableRewriteBorrows("canonical-ossa-rewrite-borrows",
41+
llvm::cl::init(false),
42+
llvm::cl::desc("Enable rewriting borrow scopes"));
43+
3744
//===----------------------------------------------------------------------===//
3845
// CopyPropagation: Top-Level Function Transform.
3946
//===----------------------------------------------------------------------===//
@@ -44,13 +51,17 @@ class CopyPropagation : public SILFunctionTransform {
4451
bool pruneDebug;
4552
/// True of all values should be canonicalized.
4653
bool canonicalizeAll;
54+
/// If true, then borrow scopes will be canonicalized, allowing copies of
55+
/// guaranteed values to be optimized. Does *not* shrink the borrow scope.
56+
bool canonicalizeBorrows;
4757
/// If true, then new destroy_value instructions will be poison.
4858
bool poisonRefs;
4959

5060
public:
51-
CopyPropagation(bool pruneDebug, bool canonicalizeAll, bool poisonRefs)
52-
: pruneDebug(pruneDebug), canonicalizeAll(canonicalizeAll),
53-
poisonRefs(poisonRefs) {}
61+
CopyPropagation(bool pruneDebug, bool canonicalizeAll,
62+
bool canonicalizeBorrows, bool poisonRefs)
63+
: pruneDebug(pruneDebug), canonicalizeAll(canonicalizeAll),
64+
canonicalizeBorrows(canonicalizeBorrows), poisonRefs(poisonRefs) {}
5465

5566
/// The entry point to this function transformation.
5667
void run() override;
@@ -71,12 +82,18 @@ static bool isCopyDead(CopyValueInst *copy, bool pruneDebug) {
7182
return true;
7283
}
7384

85+
static bool isBorrowDead(BeginBorrowInst *borrow) {
86+
return llvm::all_of(borrow->getUses(), [](Operand *use) {
87+
SILInstruction *user = use->getUser();
88+
return isa<EndBorrowInst>(user) || user->isDebugInstruction();
89+
});
90+
}
91+
7492
/// Top-level pass driver.
7593
void CopyPropagation::run() {
7694
auto *f = getFunction();
7795
auto *accessBlockAnalysis = getAnalysis<NonLocalAccessBlockAnalysis>();
7896
auto *dominanceAnalysis = getAnalysis<DominanceAnalysis>();
79-
auto *deBlocksAnalysis = getAnalysis<DeadEndBlocksAnalysis>();
8097

8198
// Debug label for unit testing.
8299
LLVM_DEBUG(llvm::dbgs() << "*** CopyPropagation: " << f->getName() << "\n");
@@ -100,15 +117,57 @@ void CopyPropagation::run() {
100117
}
101118
}
102119
}
120+
// Push copy_value instructions above their struct_extract operands by
121+
// inserting destructures.
122+
//
123+
// copiedDefs be be modified, but it never shrinks
124+
for (unsigned idx = 0; idx < copiedDefs.size(); ++idx) {
125+
SILValue def = copiedDefs[idx];
126+
auto *copy = dyn_cast<CopyValueInst>(def);
127+
if (!copy)
128+
continue;
129+
130+
auto *extract = dyn_cast<StructExtractInst>(copy->getOperand());
131+
if (!extract
132+
|| SILValue(extract).getOwnershipKind() != OwnershipKind::Guaranteed)
133+
continue;
134+
135+
// Bail-out if we won't rewrite borrows because that currently regresses
136+
// Breadcrumbs.MutatedUTF16ToIdx.Mixed/Breadcrumbs.MutatedIdxToUTF16.Mixed.
137+
// Also, mandatory copyprop does not need to rewrite destructures.
138+
if (!canonicalizeBorrows)
139+
continue;
140+
141+
if (SILValue destructuredResult = convertExtractToDestructure(extract)) {
142+
// Remove to-be-deleted instructions from copiedDeds. The extract cannot
143+
// be in the copiedDefs set since getCanonicalCopiedDef does not allow a
144+
// guaranteed projection to be a canonical def.
145+
copiedDefs.remove(copy);
146+
--idx; // point back to the current element, which was erased.
147+
148+
// TODO: unfortunately SetVector has no element replacement.
149+
copiedDefs.insert(destructuredResult);
150+
151+
auto *destructure = cast<DestructureStructInst>(
152+
destructuredResult.getDefiningInstruction());
153+
auto *newCopy = cast<CopyValueInst>(destructure->getOperand());
154+
copiedDefs.insert(
155+
CanonicalizeOSSALifetime::getCanonicalCopiedDef(newCopy));
156+
157+
LLVM_DEBUG(llvm::dbgs() << "Destructure Conversion:\n"
158+
<< *extract << " to " << *destructure);
159+
// Delete both the copy and the extract.
160+
InstructionDeleter().recursivelyDeleteUsersIfDead(extract);
161+
}
162+
}
103163
// Perform copy propgation for each copied value.
104-
CanonicalizeOSSALifetime canonicalizer(pruneDebug, poisonRefs,
105-
accessBlockAnalysis,
106-
dominanceAnalysis,
107-
deBlocksAnalysis->get(f));
164+
CanonicalizeOSSALifetime canonicalizer(pruneDebug, canonicalizeBorrows,
165+
poisonRefs, accessBlockAnalysis,
166+
dominanceAnalysis);
108167
// Cleanup dead copies. If getCanonicalCopiedDef returns a copy (because the
109168
// copy's source operand is unrecgonized), then the copy is itself treated
110169
// like a def and may be dead after canonicalization.
111-
llvm::SmallVector<CopyValueInst *, 4> deadCopies;
170+
llvm::SmallVector<SILInstruction *, 4> deadCopies;
112171
for (auto &def : copiedDefs) {
113172
// Canonicalized this def.
114173
canonicalizer.canonicalizeValueLifetime(def);
@@ -118,6 +177,14 @@ void CopyPropagation::run() {
118177
deadCopies.push_back(copy);
119178
}
120179
}
180+
// Dead borrow scopes must be removed as uses before canonicalizing the
181+
// outer copy.
182+
if (auto *borrow = dyn_cast<BeginBorrowInst>(def)) {
183+
if (isBorrowDead(borrow)) {
184+
borrow->setOperand(SILUndef::get(borrow->getType(), *f));
185+
deadCopies.push_back(borrow);
186+
}
187+
}
121188
// Canonicalize any new outer copy.
122189
if (SILValue outerCopy = canonicalizer.createdOuterCopy()) {
123190
SILValue outerDef = canonicalizer.getCanonicalCopiedDef(outerCopy);
@@ -128,26 +195,29 @@ void CopyPropagation::run() {
128195
}
129196
if (canonicalizer.hasChanged() || !deadCopies.empty()) {
130197
InstructionDeleter deleter;
131-
for (auto *copy : deadCopies) {
132-
deleter.recursivelyDeleteUsersIfDead(copy);
198+
for (auto *copyOrBorrow : deadCopies) {
199+
deleter.recursivelyDeleteUsersIfDead(copyOrBorrow);
133200
}
134201
// Preserves NonLocalAccessBlockAnalysis.
135202
accessBlockAnalysis->lockInvalidation();
136203
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
137204
accessBlockAnalysis->unlockInvalidation();
138205
if (f->getModule().getOptions().VerifySILOwnership) {
206+
auto *deBlocksAnalysis = getAnalysis<DeadEndBlocksAnalysis>();
139207
f->verifyOwnership(deBlocksAnalysis->get(f));
140208
}
141209
}
142210
}
143211

144212
SILTransform *swift::createMandatoryCopyPropagation() {
145213
return new CopyPropagation(/*pruneDebug*/ true, /*canonicalizeAll*/ true,
214+
/*canonicalizeBorrows*/ false,
146215
/*poisonRefs*/ true);
147216
}
148217

149218
SILTransform *swift::createCopyPropagation() {
150219
return new CopyPropagation(/*pruneDebug*/ true, /*canonicalizeAll*/ false,
220+
/*canonicalizeBorrows*/ EnableRewriteBorrows,
151221
/*poisonRefs*/ false);
152222
}
153223

0 commit comments

Comments
 (0)