Skip to content

Commit 617a027

Browse files
authored
Merge pull request #37208 from gottesmm/canonicalize-ossa-lifetimes-sil-combine
[sil-combine] Use canonicalizeOSSALifetimes to eliminate unnecessary copies inserted due to RAUWing
2 parents 5b897f6 + e670d2b commit 617a027

File tree

6 files changed

+206
-68
lines changed

6 files changed

+206
-68
lines changed

include/swift/SIL/SILInstructionWorklist.h

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,30 @@ class SILInstructionWorklist : SILInstructionWorklistBase {
128128
}
129129
}
130130

131+
/// All operands of \p instruction to the worklist when performing 2 stage
132+
/// instruction deletion. Meant to be used right before deleting an
133+
/// instruction in callbacks like InstModCallback::onNotifyWillBeDeleted().
134+
void addOperandsToWorklist(SILInstruction &instruction) {
135+
assert(!instruction.hasUsesOfAnyResult() &&
136+
"Cannot erase instruction that is used!");
137+
138+
// Make sure that we reprocess all operands now that we reduced their
139+
// use counts.
140+
if (instruction.getNumOperands() < 8) {
141+
for (auto &operand : instruction.getAllOperands()) {
142+
if (auto *operandInstruction =
143+
operand.get()->getDefiningInstruction()) {
144+
withDebugStream([&](llvm::raw_ostream &stream,
145+
StringRef loggingName) {
146+
stream << loggingName << ": add op " << *operandInstruction << '\n'
147+
<< " from erased inst to worklist\n";
148+
});
149+
add(operandInstruction);
150+
}
151+
}
152+
}
153+
}
154+
131155
/// When an instruction has been simplified, add all of its users to the
132156
/// worklist, since additional simplifications of its users may have been
133157
/// exposed.
@@ -296,29 +320,15 @@ class SILInstructionWorklist : SILInstructionWorklistBase {
296320
}
297321

298322
void eraseSingleInstFromFunction(SILInstruction &instruction,
299-
bool addOperandsToWorklist) {
323+
bool shouldAddOperandsToWorklist) {
300324
withDebugStream([&](llvm::raw_ostream &stream, StringRef loggingName) {
301325
stream << loggingName << ": ERASE " << instruction << '\n';
302326
});
303327

304-
assert(!instruction.hasUsesOfAnyResult() &&
305-
"Cannot erase instruction that is used!");
328+
// If we are asked to add operands to the worklist, do so now.
329+
if (shouldAddOperandsToWorklist)
330+
addOperandsToWorklist(instruction);
306331

307-
// Make sure that we reprocess all operands now that we reduced their
308-
// use counts.
309-
if (instruction.getNumOperands() < 8 && addOperandsToWorklist) {
310-
for (auto &operand : instruction.getAllOperands()) {
311-
if (auto *operandInstruction =
312-
operand.get()->getDefiningInstruction()) {
313-
withDebugStream([&](llvm::raw_ostream &stream,
314-
StringRef loggingName) {
315-
stream << loggingName << ": add op " << *operandInstruction << '\n'
316-
<< " from erased inst to worklist\n";
317-
});
318-
add(operandInstruction);
319-
}
320-
}
321-
}
322332
erase(&instruction);
323333
instruction.eraseFromParent();
324334
}

include/swift/SILOptimizer/Utils/CanonicalOSSALifetime.h

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -398,6 +398,10 @@ class CanonicalizeOSSALifetime {
398398
/// lifetime, call this API again on the value defined by the new copy.
399399
bool canonicalizeValueLifetime(SILValue def);
400400

401+
/// Return the inst mod callbacks struct used by this CanonicalizeOSSALifetime
402+
/// to pass to other APIs that need to compose with CanonicalizeOSSALifetime.
403+
InstModCallbacks getInstModCallbacks() const { return instModCallbacks; }
404+
401405
protected:
402406
void recordDebugValue(DebugValueInst *dvi) {
403407
debugValues.insert(dvi);
@@ -438,6 +442,29 @@ class CanonicalizeOSSALifetime {
438442
void injectPoison();
439443
};
440444

445+
/// Canonicalize the passed in set of defs, eliminating in one batch any that
446+
/// are not needed given the canonical lifetime of the various underlying owned
447+
/// value introducers.
448+
///
449+
/// On success, returns the invalidation kind that the caller must use to
450+
/// invalidate analyses. Currently it will only ever return
451+
/// SILAnalysis::InvalidationKind::Instructions or None.
452+
///
453+
/// NOTE: This routine is guaranteed to not invalidate
454+
/// NonLocalAccessBlockAnalysis, so callers should lock it before invalidating
455+
/// instructions. E.x.:
456+
///
457+
/// accessBlockAnalysis->lockInvalidation();
458+
/// pass->invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
459+
/// accessBlockAnalysis->unlockInvalidation();
460+
///
461+
/// NOTE: We assume that all \p inputDefs is a set (that is it has no duplicate
462+
/// elements) and that all values have been generated by running a copy through
463+
/// CanonicalizeOSSALifetime::getCanonicalCopiedDef(copy)).
464+
SILAnalysis::InvalidationKind
465+
canonicalizeOSSALifetimes(CanonicalizeOSSALifetime &canonicalizeLifetime,
466+
ArrayRef<SILValue> inputDefs);
467+
441468
} // end namespace swift
442469

443470
#endif

lib/SILOptimizer/SILCombiner/SILCombine.cpp

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@
2121
#define DEBUG_TYPE "sil-combine"
2222

2323
#include "SILCombiner.h"
24+
#include "swift/SIL/BasicBlockDatastructures.h"
2425
#include "swift/SIL/DebugUtils.h"
2526
#include "swift/SIL/SILBuilder.h"
2627
#include "swift/SIL/SILVisitor.h"
27-
#include "swift/SIL/BasicBlockDatastructures.h"
2828
#include "swift/SILOptimizer/Analysis/AliasAnalysis.h"
29+
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
30+
#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h"
2931
#include "swift/SILOptimizer/Analysis/SimplifyInstruction.h"
3032
#include "swift/SILOptimizer/PassManager/Passes.h"
3133
#include "swift/SILOptimizer/PassManager/Transforms.h"
34+
#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h"
3235
#include "swift/SILOptimizer/Utils/CanonicalizeInstruction.h"
3336
#include "swift/SILOptimizer/Utils/DebugOptUtils.h"
3437
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
@@ -299,10 +302,39 @@ bool SILCombiner::doOneIteration(SILFunction &F, unsigned Iteration) {
299302
}
300303

301304
// Our tracking list has been accumulating instructions created by the
302-
// SILBuilder during this iteration. Go through the tracking list and add
303-
// its contents to the worklist and then clear said list in preparation for
304-
// the next iteration.
305+
// SILBuilder during this iteration. In order to finish this round of
306+
// SILCombine, go through the tracking list and add its contents to the
307+
// worklist and then clear said list in preparation for the next
308+
// iteration. We canonicalize any copies that we created in order to
309+
// eliminate unnecessary copies introduced by RAUWing when ownership is
310+
// enabled.
311+
//
312+
// NOTE: It is ok if copy propagation results in MadeChanges being set to
313+
// true. This is because we only add elements to the tracking list if we
314+
// actually made a change to the IR, so MadeChanges should already be true
315+
// at this point.
305316
auto &TrackingList = *Builder.getTrackingList();
317+
if (TrackingList.size() && Builder.hasOwnership()) {
318+
SmallSetVector<SILValue, 16> defsToCanonicalize;
319+
for (auto *trackedInst : TrackingList) {
320+
if (auto *cvi = dyn_cast<CopyValueInst>(trackedInst)) {
321+
defsToCanonicalize.insert(
322+
CanonicalizeOSSALifetime::getCanonicalCopiedDef(cvi));
323+
}
324+
}
325+
if (defsToCanonicalize.size()) {
326+
CanonicalizeOSSALifetime canonicalizer(
327+
false /*prune debug*/, false /*canonicalize borrows*/,
328+
false /*poison refs*/, NLABA, DA, instModCallbacks);
329+
auto analysisInvalidation = canonicalizeOSSALifetimes(
330+
canonicalizer, defsToCanonicalize.getArrayRef());
331+
if (bool(analysisInvalidation)) {
332+
NLABA->lockInvalidation();
333+
parentTransform->invalidateAnalysis(analysisInvalidation);
334+
NLABA->unlockInvalidation();
335+
}
336+
}
337+
}
306338
for (SILInstruction *I : TrackingList) {
307339
LLVM_DEBUG(llvm::dbgs() << "SC: add " << *I
308340
<< " from tracking list to worklist\n");
@@ -359,12 +391,13 @@ class SILCombine : public SILFunctionTransform {
359391
auto *DA = PM->getAnalysis<DominanceAnalysis>();
360392
auto *PCA = PM->getAnalysis<ProtocolConformanceAnalysis>();
361393
auto *CHA = PM->getAnalysis<ClassHierarchyAnalysis>();
394+
auto *NLABA = PM->getAnalysis<NonLocalAccessBlockAnalysis>();
362395

363396
SILOptFunctionBuilder FuncBuilder(*this);
364397
// Create a SILBuilder with a tracking list for newly added
365398
// instructions, which we will periodically move to our worklist.
366399
SILBuilder B(*getFunction(), &TrackingList);
367-
SILCombiner Combiner(FuncBuilder, B, AA, DA, PCA, CHA,
400+
SILCombiner Combiner(this, FuncBuilder, B, AA, DA, PCA, CHA, NLABA,
368401
getOptions().RemoveRuntimeAsserts);
369402
bool Changed = Combiner.runOnFunction(*getFunction());
370403
assert(TrackingList.empty() &&

lib/SILOptimizer/SILCombiner/SILCombiner.h

Lines changed: 44 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "swift/SIL/SILValue.h"
3030
#include "swift/SIL/SILVisitor.h"
3131
#include "swift/SILOptimizer/Analysis/ClassHierarchyAnalysis.h"
32+
#include "swift/SILOptimizer/Analysis/NonLocalAccessBlockAnalysis.h"
3233
#include "swift/SILOptimizer/Analysis/ProtocolConformanceAnalysis.h"
3334
#include "swift/SILOptimizer/Utils/CastOptimizer.h"
3435
#include "swift/SILOptimizer/Utils/Existential.h"
@@ -46,6 +47,7 @@ class AliasAnalysis;
4647
/// the worklist.
4748
class SILCombiner :
4849
public SILInstructionVisitor<SILCombiner, SILInstruction *> {
50+
SILFunctionTransform *parentTransform;
4951

5052
AliasAnalysis *AA;
5153

@@ -59,6 +61,10 @@ class SILCombiner :
5961
/// conforming class.
6062
ClassHierarchyAnalysis *CHA;
6163

64+
/// Non local access block analysis that we use when canonicalize object
65+
/// lifetimes in OSSA.
66+
NonLocalAccessBlockAnalysis *NLABA;
67+
6268
/// Worklist containing all of the instructions primed for simplification.
6369
SmallSILInstructionWorklist<256> Worklist;
6470

@@ -97,39 +103,47 @@ class SILCombiner :
97103
OwnershipFixupContext ownershipFixupContext;
98104

99105
public:
100-
SILCombiner(SILOptFunctionBuilder &FuncBuilder, SILBuilder &B,
106+
SILCombiner(SILFunctionTransform *parentTransform,
107+
SILOptFunctionBuilder &FuncBuilder, SILBuilder &B,
101108
AliasAnalysis *AA, DominanceAnalysis *DA,
102109
ProtocolConformanceAnalysis *PCA, ClassHierarchyAnalysis *CHA,
103-
bool removeCondFails)
104-
: AA(AA), DA(DA), PCA(PCA), CHA(CHA), Worklist("SC"),
105-
deadEndBlocks(&B.getFunction()), MadeChange(false),
106-
RemoveCondFails(removeCondFails), Iteration(0), Builder(B),
107-
CastOpt(
108-
FuncBuilder, nullptr /*SILBuilderContext*/,
109-
/* ReplaceValueUsesAction */
110-
[&](SILValue Original, SILValue Replacement) {
111-
replaceValueUsesWith(Original, Replacement);
112-
},
113-
/* ReplaceInstUsesAction */
114-
[&](SingleValueInstruction *I, ValueBase *V) {
115-
replaceInstUsesWith(*I, V);
116-
},
117-
/* EraseAction */
118-
[&](SILInstruction *I) { eraseInstFromFunction(*I); }),
119-
instModCallbacks(),
120-
deBlocks(&B.getFunction()),
110+
NonLocalAccessBlockAnalysis *NLABA, bool removeCondFails)
111+
: parentTransform(parentTransform), AA(AA), DA(DA), PCA(PCA), CHA(CHA),
112+
NLABA(NLABA), Worklist("SC"), deadEndBlocks(&B.getFunction()),
113+
MadeChange(false), RemoveCondFails(removeCondFails), Iteration(0),
114+
Builder(B), CastOpt(
115+
FuncBuilder, nullptr /*SILBuilderContext*/,
116+
/* ReplaceValueUsesAction */
117+
[&](SILValue Original, SILValue Replacement) {
118+
replaceValueUsesWith(Original, Replacement);
119+
},
120+
/* ReplaceInstUsesAction */
121+
[&](SingleValueInstruction *I, ValueBase *V) {
122+
replaceInstUsesWith(*I, V);
123+
},
124+
/* EraseAction */
125+
[&](SILInstruction *I) { eraseInstFromFunction(*I); }),
126+
instModCallbacks(), deBlocks(&B.getFunction()),
121127
ownershipFixupContext(instModCallbacks, deBlocks) {
122-
instModCallbacks = InstModCallbacks()
123-
.onDelete([&](SILInstruction *instToDelete) {
124-
eraseInstFromFunction(*instToDelete);
125-
})
126-
.onCreateNewInst([&](SILInstruction *newlyCreatedInst) {
127-
Worklist.add(newlyCreatedInst);
128-
})
129-
.onSetUseValue([&](Operand *use, SILValue newValue) {
130-
use->set(newValue);
131-
Worklist.add(use->getUser());
132-
});
128+
instModCallbacks =
129+
InstModCallbacks()
130+
.onDelete([&](SILInstruction *instToDelete) {
131+
// We allow for users in SILCombine to perform 2 stage deletion,
132+
// so we need to split the erasing of instructions from adding
133+
// operands to the worklist.
134+
eraseInstFromFunction(
135+
*instToDelete, false /*do not add operands to the worklist*/);
136+
})
137+
.onNotifyWillBeDeleted([&](SILInstruction *instThatWillBeDeleted) {
138+
Worklist.addOperandsToWorklist(*instThatWillBeDeleted);
139+
})
140+
.onCreateNewInst([&](SILInstruction *newlyCreatedInst) {
141+
Worklist.add(newlyCreatedInst);
142+
})
143+
.onSetUseValue([&](Operand *use, SILValue newValue) {
144+
use->set(newValue);
145+
Worklist.add(use->getUser());
146+
});
133147
}
134148

135149
bool runOnFunction(SILFunction &F);

lib/SILOptimizer/Utils/CanonicalOSSALifetime.cpp

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,8 @@ void CanonicalizeOSSALifetime::rewriteCopies() {
11041104
// Remove the leftover copy_value and destroy_value instructions.
11051105
if (!instsToDelete.empty()) {
11061106
recursivelyDeleteTriviallyDeadInstructions(instsToDelete.takeVector(),
1107-
/*force=*/true);
1107+
/*force=*/true,
1108+
instModCallbacks);
11081109
setChanged();
11091110
}
11101111
}
@@ -1220,3 +1221,64 @@ SWIFT_ASSERT_ONLY_DECL(
12201221
llvm::dbgs() << " " << *blockAndInst.getSecond();
12211222
}
12221223
})
1224+
1225+
//===----------------------------------------------------------------------===//
1226+
// MARK: Canonicalize Lifetimes Utility
1227+
//===----------------------------------------------------------------------===//
1228+
1229+
SILAnalysis::InvalidationKind
1230+
swift::canonicalizeOSSALifetimes(CanonicalizeOSSALifetime &canonicalizer,
1231+
ArrayRef<SILValue> copiedDefs) {
1232+
auto isCopyDead = [](CopyValueInst *copy, bool pruneDebug) -> bool {
1233+
for (Operand *use : copy->getUses()) {
1234+
auto *user = use->getUser();
1235+
if (isa<DestroyValueInst>(user)) {
1236+
continue;
1237+
}
1238+
if (pruneDebug && isa<DebugValueInst>(user)) {
1239+
continue;
1240+
}
1241+
return false;
1242+
}
1243+
return true;
1244+
};
1245+
1246+
// Cleanup dead copies. If getCanonicalCopiedDef returns a copy (because the
1247+
// copy's source operand is unrecgonized), then the copy is itself treated
1248+
// like a def and may be dead after canonicalization.
1249+
SmallVector<SILInstruction *, 4> deadCopies;
1250+
for (auto def : copiedDefs) {
1251+
// Canonicalized this def. If we did not perform any canonicalization, just
1252+
// continue.
1253+
if (!canonicalizer.canonicalizeValueLifetime(def))
1254+
continue;
1255+
1256+
// Otherwise, see if we have a dead copy.
1257+
if (auto *copy = dyn_cast<CopyValueInst>(def)) {
1258+
if (isCopyDead(copy, false /*prune debug*/)) {
1259+
deadCopies.push_back(copy);
1260+
}
1261+
}
1262+
1263+
// Canonicalize any new outer copy.
1264+
if (SILValue outerCopy = canonicalizer.createdOuterCopy()) {
1265+
SILValue outerDef = canonicalizer.getCanonicalCopiedDef(outerCopy);
1266+
canonicalizer.canonicalizeValueLifetime(outerDef);
1267+
}
1268+
1269+
// TODO: also canonicalize any lifetime.persistentCopies like separate owned
1270+
// live ranges.
1271+
}
1272+
1273+
if (!canonicalizer.hasChanged() && deadCopies.empty()) {
1274+
return SILAnalysis::InvalidationKind::Nothing;
1275+
}
1276+
1277+
// We use our canonicalizers inst mod callbacks.
1278+
InstructionDeleter deleter(canonicalizer.getInstModCallbacks());
1279+
for (auto *copy : deadCopies) {
1280+
deleter.recursivelyDeleteUsersIfDead(copy);
1281+
}
1282+
1283+
return SILAnalysis::InvalidationKind::Instructions;
1284+
}

0 commit comments

Comments
 (0)