Skip to content

Commit 290093e

Browse files
committed
Simplify lifetime canonicalization in SILCombine
And fix the way it handles of borrow scopes so we can enable borrow scope rewiting. Make sure SILCombine only does canonicalization that operates on a self-contained single-value-lifetime. It's important to limit SILCombine to transformations where each individual step converges quickly to a more canonical form. Rewriting borrow scopes requires the copy propagation pass to coordinate all the individual transformations. Make canonicalizeLifetimes a SILCombine utility. This moves complexity out of the main loop. SILCombine knows which values it wants to canonicalize and can directly call either canonicalizeValueLifetime or canonicalizeFunctionArgument for each one. Respect the -enable/disable-copy-propagation options.
1 parent 2c6d5af commit 290093e

File tree

2 files changed

+143
-73
lines changed

2 files changed

+143
-73
lines changed

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 102 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include "swift/SILOptimizer/PassManager/PassManager.h"
3434
#include "swift/SILOptimizer/PassManager/Transforms.h"
3535
#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h"
36+
#include "swift/SILOptimizer/Utils/CanonicalizeBorrowScope.h"
3637
#include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h"
3738
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
3839
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
@@ -217,6 +218,88 @@ bool SILCombiner::trySinkOwnedForwardingInst(SingleValueInstruction *svi) {
217218
return true;
218219
}
219220

221+
/// Canonicalize each extended OSSA lifetime that contains an instruction newly
222+
/// created during this SILCombine iteration.
223+
void SILCombiner::canonicalizeOSSALifetimes() {
224+
if (!enableCopyPropagation || !Builder.hasOwnership())
225+
return;
226+
227+
SmallSetVector<SILValue, 16> defsToCanonicalize;
228+
for (auto *trackedInst : *Builder.getTrackingList()) {
229+
if (trackedInst->isDeleted())
230+
continue;
231+
232+
if (auto *cvi = dyn_cast<CopyValueInst>(trackedInst)) {
233+
SILValue def = CanonicalizeOSSALifetime::getCanonicalCopiedDef(cvi);
234+
235+
// getCanonicalCopiedDef returns a copy whenever that the copy's source is
236+
// guaranteed. In that case, find the root of the borrowed lifetime. If it
237+
// is a function argument, then a simple guaranteed canonicalization can
238+
// be performed. Canonicalizing other borrow scopes is not handled by
239+
// SILCombine because it's not a single-lifetime canonicalization.
240+
// Instead, SILCombine treats a copy that uses a borrowed value as a
241+
// separate owned live range. Handling the compensation code across the
242+
// borrow scope boundary requires post processing in a particular order.
243+
// The copy propagation pass knows how to handle that. To avoid complexity
244+
// and ensure fast convergence, rewriting borrow scopes should not be
245+
// combined with other unrelated transformations.
246+
if (auto *copy = dyn_cast<CopyValueInst>(def)) {
247+
if (SILValue borrowDef =
248+
CanonicalizeBorrowScope::getCanonicalBorrowedDef(
249+
copy->getOperand())) {
250+
if (isa<SILFunctionArgument>(borrowDef)) {
251+
def = borrowDef;
252+
}
253+
}
254+
}
255+
defsToCanonicalize.insert(def);
256+
}
257+
}
258+
if (defsToCanonicalize.empty())
259+
return;
260+
261+
// Remove instructions deleted during canonicalization from SILCombine's
262+
// worklist. CanonicalizeOSSALifetime invalidates operands before invoking
263+
// the deletion callback.
264+
//
265+
// Note: a simpler approach would be to drain the Worklist before
266+
// canonicalizing OSSA, then callbacks could be completely removed from
267+
// CanonicalizeOSSALifetime.
268+
auto canonicalizeCallbacks =
269+
InstModCallbacks().onDelete([this](SILInstruction *instToDelete) {
270+
eraseInstFromFunction(*instToDelete,
271+
false /*do not add operands to the worklist*/);
272+
});
273+
InstructionDeleter deleter(std::move(canonicalizeCallbacks));
274+
275+
DominanceInfo *domTree = DA->get(&Builder.getFunction());
276+
CanonicalizeOSSALifetime canonicalizer(
277+
false /*prune debug*/, false /*poison refs*/, NLABA, domTree, deleter);
278+
CanonicalizeBorrowScope borrowCanonicalizer(deleter);
279+
280+
while (!defsToCanonicalize.empty()) {
281+
SILValue def = defsToCanonicalize.pop_back_val();
282+
if (auto functionArg = dyn_cast<SILFunctionArgument>(def)) {
283+
if (!borrowCanonicalizer.canonicalizeFunctionArgument(functionArg))
284+
continue;
285+
} else if (!canonicalizer.canonicalizeValueLifetime(def)) {
286+
continue;
287+
}
288+
MadeChange = true;
289+
290+
// Canonicalization may rewrite many copies and destroys within a single
291+
// extended lifetime, but the extended lifetime of each canonical def is
292+
// non-overlapping. If def was successfully canonicalized, simply add it
293+
// and its users to the SILCombine worklist.
294+
if (auto *inst = def->getDefiningInstruction()) {
295+
Worklist.add(inst);
296+
}
297+
for (auto *use : def->getUses()) {
298+
Worklist.add(use->getUser());
299+
}
300+
}
301+
}
302+
220303
bool SILCombiner::doOneIteration(SILFunction &F, unsigned Iteration) {
221304
MadeChange = false;
222305

@@ -302,50 +385,22 @@ bool SILCombiner::doOneIteration(SILFunction &F, unsigned Iteration) {
302385
MadeChange = true;
303386
}
304387

305-
// Our tracking list has been accumulating instructions created by the
306-
// SILBuilder during this iteration. In order to finish this round of
307-
// SILCombine, go through the tracking list and add its contents to the
308-
// worklist and then clear said list in preparation for the next
309-
// iteration. We canonicalize any copies that we created in order to
310-
// eliminate unnecessary copies introduced by RAUWing when ownership is
311-
// enabled.
312-
//
313-
// NOTE: It is ok if copy propagation results in MadeChanges being set to
314-
// true. This is because we only add elements to the tracking list if we
315-
// actually made a change to the IR, so MadeChanges should already be true
316-
// at this point.
317-
auto &TrackingList = *Builder.getTrackingList();
318-
if (TrackingList.size() && Builder.hasOwnership()) {
319-
SmallSetVector<SILValue, 16> defsToCanonicalize;
320-
for (auto *trackedInst : TrackingList) {
321-
if (!trackedInst->isDeleted()) {
322-
if (auto *cvi = dyn_cast<CopyValueInst>(trackedInst)) {
323-
defsToCanonicalize.insert(
324-
CanonicalizeOSSALifetime::getCanonicalCopiedDef(cvi));
325-
}
326-
}
327-
}
328-
if (defsToCanonicalize.size()) {
329-
CanonicalizeOSSALifetime canonicalizer(
330-
false /*prune debug*/, false /*canonicalize borrows*/,
331-
false /*poison refs*/, NLABA, DA, getInstModCallbacks());
332-
auto analysisInvalidation = canonicalizeOSSALifetimes(
333-
canonicalizer, defsToCanonicalize.getArrayRef());
334-
if (bool(analysisInvalidation)) {
335-
NLABA->lockInvalidation();
336-
parentTransform->invalidateAnalysis(analysisInvalidation);
337-
NLABA->unlockInvalidation();
338-
}
339-
}
340-
}
341-
for (SILInstruction *I : TrackingList) {
388+
// Eliminate copies created that this SILCombine iteration may have
389+
// introduced during OSSA-RAUW.
390+
canonicalizeOSSALifetimes();
391+
392+
// Builder's tracking list has been accumulating instructions created by the
393+
// during this SILCombine iteration. To finish this iteration, go through
394+
// the tracking list and add its contents to the worklist and then clear
395+
// said list in preparation for the next iteration.
396+
for (SILInstruction *I : *Builder.getTrackingList()) {
342397
if (!I->isDeleted()) {
343-
LLVM_DEBUG(llvm::dbgs() << "SC: add " << *I
344-
<< " from tracking list to worklist\n");
398+
LLVM_DEBUG(llvm::dbgs()
399+
<< "SC: add " << *I << " from tracking list to worklist\n");
345400
Worklist.add(I);
346401
}
347402
}
348-
TrackingList.clear();
403+
Builder.getTrackingList()->clear();
349404
}
350405

351406
Worklist.resetChecked();
@@ -448,12 +503,18 @@ class SILCombine : public SILFunctionTransform {
448503
auto *CHA = PM->getAnalysis<ClassHierarchyAnalysis>();
449504
auto *NLABA = PM->getAnalysis<NonLocalAccessBlockAnalysis>();
450505

506+
bool enableCopyPropagation = getOptions().EnableCopyPropagation;
507+
if (getOptions().EnableOSSAModules) {
508+
enableCopyPropagation = !getOptions().DisableCopyPropagation;
509+
}
510+
451511
SILOptFunctionBuilder FuncBuilder(*this);
452512
// Create a SILBuilder with a tracking list for newly added
453513
// instructions, which we will periodically move to our worklist.
454514
SILBuilder B(*getFunction(), &TrackingList);
455515
SILCombiner Combiner(this, FuncBuilder, B, AA, DA, PCA, CHA, NLABA,
456-
getOptions().RemoveRuntimeAsserts);
516+
getOptions().RemoveRuntimeAsserts,
517+
enableCopyPropagation);
457518
bool Changed = Combiner.runOnFunction(*getFunction());
458519
assert(TrackingList.empty() &&
459520
"TrackingList should be fully processed by SILCombiner");

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,9 @@ class SILCombiner :
8686
/// If set to true then the optimizer is free to erase cond_fail instructions.
8787
bool RemoveCondFails;
8888

89+
/// If set to true then copies are canonicalized in OSSA mode.
90+
bool enableCopyPropagation;
91+
8992
/// Set to true if some alloc/dealloc_stack instruction are inserted and at
9093
/// the end of the run stack nesting needs to be corrected.
9194
bool invalidatedStackNesting = false;
@@ -114,43 +117,45 @@ class SILCombiner :
114117
SILOptFunctionBuilder &FuncBuilder, SILBuilder &B,
115118
AliasAnalysis *AA, DominanceAnalysis *DA,
116119
ProtocolConformanceAnalysis *PCA, ClassHierarchyAnalysis *CHA,
117-
NonLocalAccessBlockAnalysis *NLABA, bool removeCondFails)
120+
NonLocalAccessBlockAnalysis *NLABA, bool removeCondFails,
121+
bool enableCopyPropagation)
118122
: parentTransform(parentTransform), AA(AA), DA(DA), PCA(PCA), CHA(CHA),
119123
NLABA(NLABA), Worklist("SC"),
120124
deleter(InstModCallbacks()
121-
.onDelete([&](SILInstruction *instToDelete) {
122-
// We allow for users in SILCombine to perform 2 stage
123-
// deletion, so we need to split the erasing of instructions
124-
// from adding operands to the worklist.
125-
eraseInstFromFunction(*instToDelete,
126-
false /* don't add operands */);
127-
})
128-
.onNotifyWillBeDeleted(
129-
[&](SILInstruction *instThatWillBeDeleted) {
125+
.onDelete([&](SILInstruction *instToDelete) {
126+
// We allow for users in SILCombine to perform 2 stage
127+
// deletion, so we need to split the erasing of
128+
// instructions from adding operands to the worklist.
129+
eraseInstFromFunction(*instToDelete,
130+
false /* don't add operands */);
131+
})
132+
.onNotifyWillBeDeleted(
133+
[&](SILInstruction *instThatWillBeDeleted) {
130134
Worklist.addOperandsToWorklist(
131135
*instThatWillBeDeleted);
132-
})
133-
.onCreateNewInst([&](SILInstruction *newlyCreatedInst) {
134-
Worklist.add(newlyCreatedInst);
135-
})
136-
.onSetUseValue([&](Operand *use, SILValue newValue) {
137-
use->set(newValue);
138-
Worklist.add(use->getUser());
139-
})),
140-
deadEndBlocks(&B.getFunction()),
141-
MadeChange(false), RemoveCondFails(removeCondFails), Iteration(0),
142-
Builder(B), CastOpt(
143-
FuncBuilder, nullptr /*SILBuilderContext*/,
144-
/* ReplaceValueUsesAction */
145-
[&](SILValue Original, SILValue Replacement) {
146-
replaceValueUsesWith(Original, Replacement);
147-
},
148-
/* ReplaceInstUsesAction */
149-
[&](SingleValueInstruction *I, ValueBase *V) {
150-
replaceInstUsesWith(*I, V);
151-
},
152-
/* EraseAction */
153-
[&](SILInstruction *I) { eraseInstFromFunction(*I); }),
136+
})
137+
.onCreateNewInst([&](SILInstruction *newlyCreatedInst) {
138+
Worklist.add(newlyCreatedInst);
139+
})
140+
.onSetUseValue([&](Operand *use, SILValue newValue) {
141+
use->set(newValue);
142+
Worklist.add(use->getUser());
143+
})),
144+
deadEndBlocks(&B.getFunction()), MadeChange(false),
145+
RemoveCondFails(removeCondFails),
146+
enableCopyPropagation(enableCopyPropagation), Iteration(0), Builder(B),
147+
CastOpt(
148+
FuncBuilder, nullptr /*SILBuilderContext*/,
149+
/* ReplaceValueUsesAction */
150+
[&](SILValue Original, SILValue Replacement) {
151+
replaceValueUsesWith(Original, Replacement);
152+
},
153+
/* ReplaceInstUsesAction */
154+
[&](SingleValueInstruction *I, ValueBase *V) {
155+
replaceInstUsesWith(*I, V);
156+
},
157+
/* EraseAction */
158+
[&](SILInstruction *I) { eraseInstFromFunction(*I); }),
154159
deBlocks(&B.getFunction()),
155160
ownershipFixupContext(getInstModCallbacks(), deBlocks),
156161
libswiftPassInvocation(parentTransform->getPassManager(), this) {}
@@ -376,6 +381,10 @@ class SILCombiner :
376381
/// try to visit it.
377382
bool trySinkOwnedForwardingInst(SingleValueInstruction *svi);
378383

384+
/// Apply CanonicalizeOSSALifetime to the extended lifetime of any copy
385+
/// introduced during SILCombine for an owned value.
386+
void canonicalizeOSSALifetimes();
387+
379388
// Optimize concatenation of string literals.
380389
// Constant-fold concatenation of string literals known at compile-time.
381390
SILInstruction *optimizeConcatenationOfStringLiterals(ApplyInst *AI);

0 commit comments

Comments
 (0)