Skip to content

Commit 7b55cbc

Browse files
committed
[sil-optimizer] Make InstructionDeleter and related APIs to use an InstModCallback instead of a notification callback.
I recently have been running into the issue that many of these APIs perform the deletion operation themselves and notify the caller it is going to delete instead of allowing the caller to specify how the instruction is deleted. This causes interesting semantic issues (see the loop in deleteInstruction I simplified) and breaks composition since many parts of the optimizer use InstModCallbacks for this purpose. To fix this, I added a notify will be deleted construct to InstModCallback. In a similar way to the rest of it, if the notify is not set, we do not call any code implying that we should have good predictable performance in loops since we will always skip the function call. I also changed InstModCallback::deleteInst() to notify before deleting so we have a default safe behavior. All previous use sites of this API do not care about being notified and the only new use sites of this API are in InstructionDeleter that perform special notification behavior (it notifies for certain sets of instructions it is going to delete before it deletes any of them). To work around this, I added a bool to deleteInst to control this behavior and defaulted to notifying. This should ensure that all other use sites still compose correctly.
1 parent c15dde4 commit 7b55cbc

File tree

8 files changed

+425
-382
lines changed

8 files changed

+425
-382
lines changed

include/swift/SILOptimizer/Utils/DebugOptUtils.h

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,43 @@ inline void deleteAllDebugUses(SILInstruction *inst,
4343
}
4444
}
4545

46+
/// Erases the instruction \p I from it's parent block and deletes it, including
47+
/// all debug instructions which use \p I.
48+
/// Precondition: The instruction may only have debug instructions as uses.
49+
/// If the iterator \p InstIter references any deleted instruction, it is
50+
/// incremented.
51+
///
52+
/// \p callbacks InstModCallback to use.
53+
///
54+
/// Returns an iterator to the next non-deleted instruction after \p I.
55+
inline SILBasicBlock::iterator
56+
eraseFromParentWithDebugInsts(SILInstruction *inst,
57+
InstModCallbacks callbacks = InstModCallbacks()) {
58+
auto nextII = std::next(inst->getIterator());
59+
60+
auto results = inst->getResults();
61+
bool foundAny;
62+
do {
63+
foundAny = false;
64+
for (auto result : results) {
65+
while (!result->use_empty()) {
66+
foundAny = true;
67+
auto *user = result->use_begin()->getUser();
68+
assert(user->isDebugInstruction());
69+
if (nextII == user->getIterator())
70+
++nextII;
71+
callbacks.deleteInst(user);
72+
}
73+
}
74+
} while (foundAny);
75+
76+
// Just matching what eraseFromParentWithDebugInsts is today.
77+
if (nextII == inst->getIterator())
78+
++nextII;
79+
callbacks.deleteInst(inst, false /*do not notify*/);
80+
return nextII;
81+
}
82+
4683
} // namespace swift
4784

4885
#endif

include/swift/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 256 additions & 232 deletions
Large diffs are not rendered by default.

lib/SILOptimizer/Mandatory/OSLogOptimization.cpp

Lines changed: 31 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===--- OSLogOptimizer.cpp - Optimizes calls to OS Log ===//
1+
//===--- OSLogOptimizer.cpp - Optimizes calls to OS Log -------------------===//
22
//
33
// This source file is part of the Swift.org open source project
44
//
@@ -68,6 +68,8 @@
6868
/// 'substituteConstants' and 'emitCodeForSymbolicValue'. The remaining
6969
/// functions in the file implement the subtasks and utilities needed by the
7070
/// above functions.
71+
///
72+
//===----------------------------------------------------------------------===//
7173

7274
#include "swift/AST/ASTContext.h"
7375
#include "swift/AST/DiagnosticEngine.h"
@@ -1108,8 +1110,6 @@ static bool checkOSLogMessageIsConstant(SingleValueInstruction *osLogMessage,
11081110
return errorDetected;
11091111
}
11101112

1111-
using CallbackTy = llvm::function_ref<void(SILInstruction *)>;
1112-
11131113
/// Return true iff the given address-valued instruction has only stores into
11141114
/// it. This function tests for the conditions under which a call, that was
11151115
/// constant evaluated, that writes into the address-valued instruction can be
@@ -1184,8 +1184,7 @@ static bool hasOnlyStoreUses(SingleValueInstruction *addressInst) {
11841184
/// of begin_apply. This will also fix the lifetimes of the deleted instructions
11851185
/// whenever possible.
11861186
static void forceDeleteAllocStack(SingleValueInstruction *inst,
1187-
InstructionDeleter &deleter,
1188-
CallbackTy callback) {
1187+
InstructionDeleter &deleter) {
11891188
SmallVector<SILInstruction *, 8> users;
11901189
for (Operand *use : inst->getUses())
11911190
users.push_back(use->getUser());
@@ -1194,30 +1193,31 @@ static void forceDeleteAllocStack(SingleValueInstruction *inst,
11941193
if (isIncidentalUse(user))
11951194
continue;
11961195
if (isa<DestroyAddrInst>(user)) {
1197-
deleter.forceDelete(user, callback);
1196+
deleter.forceDelete(user);
11981197
continue;
11991198
}
12001199
if (isa<BeginAccessInst>(user)) {
1201-
forceDeleteAllocStack(cast<BeginAccessInst>(user), deleter, callback);
1200+
forceDeleteAllocStack(cast<BeginAccessInst>(user), deleter);
12021201
continue;
12031202
}
1204-
deleter.forceDeleteAndFixLifetimes(user, callback);
1203+
deleter.forceDeleteAndFixLifetimes(user);
12051204
}
1206-
deleter.forceDelete(inst, callback);
1205+
deleter.forceDelete(inst);
12071206
}
12081207

12091208
/// Delete \c inst , if it is dead, along with its dead users and invoke the
12101209
/// callback whever an instruction is deleted.
1211-
static void deleteInstructionWithUsersAndFixLifetimes(
1212-
SILInstruction *inst, InstructionDeleter &deleter, CallbackTy callback) {
1210+
static void
1211+
deleteInstructionWithUsersAndFixLifetimes(SILInstruction *inst,
1212+
InstructionDeleter &deleter) {
12131213
// If this is an alloc_stack, it can be eliminated as long as it is only
12141214
// stored into or destroyed.
12151215
if (AllocStackInst *allocStack = dyn_cast<AllocStackInst>(inst)) {
12161216
if (hasOnlyStoreUses(allocStack))
1217-
forceDeleteAllocStack(allocStack, deleter, callback);
1217+
forceDeleteAllocStack(allocStack, deleter);
12181218
return;
12191219
}
1220-
deleter.recursivelyDeleteUsersIfDead(inst, callback);
1220+
deleter.recursivelyDeleteUsersIfDead(inst);
12211221
}
12221222

12231223
/// Try to dead-code eliminate the OSLogMessage instance \c oslogMessage passed
@@ -1226,31 +1226,35 @@ static void deleteInstructionWithUsersAndFixLifetimes(
12261226
/// \returns true if elimination is successful and false if it is not successful
12271227
/// and diagnostics is emitted.
12281228
static bool tryEliminateOSLogMessage(SingleValueInstruction *oslogMessage) {
1229-
InstructionDeleter deleter;
12301229
// List of instructions that are possibly dead.
12311230
SmallVector<SILInstruction *, 4> worklist = {oslogMessage};
12321231
// Set of all deleted instructions.
12331232
SmallPtrSet<SILInstruction *, 4> deletedInstructions;
1233+
1234+
auto callbacks =
1235+
InstModCallbacks().onNotifyWillBeDeleted([&](SILInstruction *deadInst) {
1236+
// Add operands of all deleted instructions to the worklist so that
1237+
// they can be recursively deleted if possible.
1238+
for (Operand &operand : deadInst->getAllOperands()) {
1239+
if (SILInstruction *definingInstruction =
1240+
operand.get()->getDefiningInstruction()) {
1241+
if (!deletedInstructions.count(definingInstruction))
1242+
worklist.push_back(definingInstruction);
1243+
}
1244+
}
1245+
(void)deletedInstructions.insert(deadInst);
1246+
});
1247+
InstructionDeleter deleter(callbacks);
1248+
12341249
unsigned startIndex = 0;
12351250
while (startIndex < worklist.size()) {
12361251
SILInstruction *inst = worklist[startIndex++];
12371252
if (deletedInstructions.count(inst))
12381253
continue;
1239-
deleteInstructionWithUsersAndFixLifetimes(
1240-
inst, deleter, [&](SILInstruction *deadInst) {
1241-
// Add operands of all deleted instructions to the worklist so that
1242-
// they can be recursively deleted if possible.
1243-
for (Operand &operand : deadInst->getAllOperands()) {
1244-
if (SILInstruction *definingInstruction =
1245-
operand.get()->getDefiningInstruction()) {
1246-
if (!deletedInstructions.count(definingInstruction))
1247-
worklist.push_back(definingInstruction);
1248-
}
1249-
}
1250-
(void)deletedInstructions.insert(deadInst);
1251-
});
1254+
deleteInstructionWithUsersAndFixLifetimes(inst, deleter);
12521255
}
12531256
deleter.cleanUpDeadInstructions();
1257+
12541258
// If the OSLogMessage instance is not deleted, either we couldn't see the
12551259
// body of the log call or there is a bug in the library implementation.
12561260
// Assuming that the library implementation is correct, it means that either

lib/SILOptimizer/Mandatory/SILGenCleanup.cpp

Lines changed: 14 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -55,50 +55,27 @@ struct SILGenCanonicalize final : CanonicalizeInstruction {
5555
/// is nextII->getParent()->end().
5656
SILBasicBlock::iterator deleteDeadOperands(SILBasicBlock::iterator nextII,
5757
SILBasicBlock::iterator endII) {
58-
// Each iteration, we store the instructions that will be deleted in the
59-
// iteration here and use it to ensure that nextII is moved past /all/
60-
// instructions that we are going to delete no matter the order (since we
61-
// are visiting instructions in non-deterministic order).
62-
SmallPtrSet<SILInstruction *, 16> willBeDeletedInIteration;
58+
auto callbacks = InstModCallbacks().onDelete([&](SILInstruction *deadInst) {
59+
LLVM_DEBUG(llvm::dbgs() << "Trivially dead: " << *deadInst);
60+
61+
// If nextII is the instruction we are going to delete, move nextII past
62+
// it.
63+
if (deadInst->getIterator() == nextII)
64+
++nextII;
65+
66+
// Then remove the instruction from the set and delete it.
67+
deadOperands.erase(deadInst);
68+
deadInst->eraseFromParent();
69+
});
6370

6471
while (!deadOperands.empty()) {
6572
SILInstruction *deadOperInst = *deadOperands.begin();
6673

6774
// Make sure at least the first instruction is removed from the set.
6875
deadOperands.erase(deadOperInst);
6976

70-
// Then add our initial instruction to the will be deleted set.
71-
willBeDeletedInIteration.insert(deadOperInst);
72-
SWIFT_DEFER { willBeDeletedInIteration.clear(); };
73-
74-
eliminateDeadInstruction(deadOperInst, [&](SILInstruction *deadInst) {
75-
LLVM_DEBUG(llvm::dbgs() << "Trivially dead: " << *deadInst);
76-
77-
// Add our instruction to the will be deleted set.
78-
willBeDeletedInIteration.insert(deadInst);
79-
80-
// Then look through /all/ instructions that we are going to delete in
81-
// this iteration until we hit the end of the list. This ensures that in
82-
// a situation like the following:
83-
//
84-
// ```
85-
// (%1, %2) = multi_result_inst %0 (a)
86-
// inst_to_delete %1 (b)
87-
// inst_to_delete %2 (c)
88-
// ```
89-
//
90-
// If nextII is on (b), but we visit (c) before visiting (b), then we
91-
// will end up with nextII on (c) after we are done and then delete
92-
// (c). In contrast by using the set when we process (b) after (c), we
93-
// first see that (b) is nextII [since it is in the set] so move to (c)
94-
// and then see that (c) is in the set as well (since we inserted it
95-
// previously) and skip that.
96-
while (nextII != endII && willBeDeletedInIteration.count(&*nextII))
97-
++nextII;
98-
99-
// Then remove the instruction from the set.
100-
deadOperands.erase(deadInst);
101-
});
77+
// Then delete this instruction/everything else that we can.
78+
eliminateDeadInstruction(deadOperInst, callbacks);
10279
}
10380
return nextII;
10481
}

lib/SILOptimizer/Utils/ConstantFolding.cpp

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1727,6 +1727,13 @@ ConstantFolder::processWorkList() {
17271727
I->eraseFromParent();
17281728
});
17291729

1730+
auto callbacks =
1731+
InstModCallbacks().onDelete([&](SILInstruction *instToDelete) {
1732+
WorkList.remove(instToDelete);
1733+
InvalidateInstructions = true;
1734+
instToDelete->eraseFromParent();
1735+
});
1736+
17301737
// An out parameter array that we use to return new simplified results from
17311738
// constantFoldInstruction.
17321739
SmallVector<SILValue, 8> ConstantFoldedResults;
@@ -1736,8 +1743,6 @@ ConstantFolder::processWorkList() {
17361743

17371744
LLVM_DEBUG(llvm::dbgs() << "Visiting: " << *I);
17381745

1739-
Callback(I);
1740-
17411746
// Replace assert_configuration instructions by their constant value. We
17421747
// want them to be replace even if we can't fully propagate the constant.
17431748
if (AssertConfiguration != SILOptions::DisableReplacement)
@@ -1751,29 +1756,28 @@ ConstantFolder::processWorkList() {
17511756
// Schedule users for constant folding.
17521757
WorkList.insert(AssertConfInt);
17531758
// Delete the call.
1754-
eliminateDeadInstruction(BI);
1755-
1756-
InvalidateInstructions = true;
1759+
eliminateDeadInstruction(BI, callbacks);
17571760
continue;
17581761
}
17591762

17601763
// Kill calls to conditionallyUnreachable if we've folded assert
17611764
// configuration calls.
17621765
if (isApplyOfBuiltin(*BI, BuiltinValueKind::CondUnreachable)) {
17631766
assert(BI->use_empty() && "use of conditionallyUnreachable?!");
1764-
recursivelyDeleteTriviallyDeadInstructions(BI, /*force*/ true);
1767+
recursivelyDeleteTriviallyDeadInstructions(BI, /*force*/ true,
1768+
callbacks);
17651769
InvalidateInstructions = true;
17661770
continue;
17671771
}
17681772
}
1773+
17691774
// Replace a known availability.version semantic call.
17701775
if (isApplyOfKnownAvailability(*I)) {
17711776
if (auto apply = dyn_cast<ApplyInst>(I)) {
17721777
SILBuilderWithScope B(I);
17731778
auto tru = B.createIntegerLiteral(apply->getLoc(), apply->getType(), 1);
17741779
apply->replaceAllUsesWith(tru);
1775-
eliminateDeadInstruction(
1776-
I, [&](SILInstruction *DeadI) { WorkList.remove(DeadI); });
1780+
eliminateDeadInstruction(I, callbacks);
17771781
WorkList.insert(tru);
17781782
InvalidateInstructions = true;
17791783
}
@@ -1822,9 +1826,7 @@ ConstantFolder::processWorkList() {
18221826
if (constantFoldGlobalStringTablePointerBuiltin(cast<BuiltinInst>(I),
18231827
EnableDiagnostics)) {
18241828
// Here, the bulitin instruction got folded, so clean it up.
1825-
eliminateDeadInstruction(
1826-
I, [&](SILInstruction *DeadI) { WorkList.remove(DeadI); });
1827-
InvalidateInstructions = true;
1829+
eliminateDeadInstruction(I, callbacks);
18281830
}
18291831
continue;
18301832
}
@@ -1839,10 +1841,8 @@ ConstantFolder::processWorkList() {
18391841
auto *cfi = builder.createCondFail(I->getLoc(), I->getOperand(0),
18401842
sli->getValue());
18411843
WorkList.insert(cfi);
1842-
recursivelyDeleteTriviallyDeadInstructions(
1843-
I, /*force*/ true,
1844-
[&](SILInstruction *DeadI) { WorkList.remove(DeadI); });
1845-
InvalidateInstructions = true;
1844+
recursivelyDeleteTriviallyDeadInstructions(I, /*force*/ true,
1845+
callbacks);
18461846
}
18471847
}
18481848
continue;
@@ -1851,10 +1851,8 @@ ConstantFolder::processWorkList() {
18511851
if (isApplyOfBuiltin(*I, BuiltinValueKind::IsConcrete)) {
18521852
if (constantFoldIsConcrete(cast<BuiltinInst>(I))) {
18531853
// Here, the bulitin instruction got folded, so clean it up.
1854-
recursivelyDeleteTriviallyDeadInstructions(
1855-
I, /*force*/ true,
1856-
[&](SILInstruction *DeadI) { WorkList.remove(DeadI); });
1857-
InvalidateInstructions = true;
1854+
recursivelyDeleteTriviallyDeadInstructions(I, /*force*/ true,
1855+
callbacks);
18581856
}
18591857
continue;
18601858
}
@@ -1875,7 +1873,7 @@ ConstantFolder::processWorkList() {
18751873
}
18761874

18771875
// Go through all users of the constant and try to fold them.
1878-
InstructionDeleter deleter;
1876+
InstructionDeleter deleter(callbacks);
18791877
for (auto Result : I->getResults()) {
18801878
for (auto *Use : Result->getUses()) {
18811879
SILInstruction *User = Use->getUser();
@@ -2022,12 +2020,10 @@ ConstantFolder::processWorkList() {
20222020
}
20232021
}
20242022
}
2023+
20252024
// Eagerly DCE. We do this after visiting all users to ensure we don't
20262025
// invalidate the uses iterator.
2027-
deleter.cleanUpDeadInstructions([&](SILInstruction *DeadI) {
2028-
WorkList.remove(DeadI);
2029-
InvalidateInstructions = true;
2030-
});
2026+
deleter.cleanUpDeadInstructions();
20312027
}
20322028

20332029
// TODO: refactor this code outside of the method. Passes should not merge

0 commit comments

Comments
 (0)