Skip to content

Commit 16ae354

Browse files
authored
Merge pull request #37669 from atrick/fix-callbacks
InstructionDeleter rewrite and migrate passes
2 parents a2d5678 + 0407a4e commit 16ae354

36 files changed

+1013
-565
lines changed

include/swift/SIL/SILInstruction.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,9 @@ class SILInstruction : public llvm::ilist_node<SILInstruction> {
469469
/// Drops all uses that belong to this instruction.
470470
void dropAllReferences();
471471

472+
/// Drops all references that aren't represented by operands.
473+
void dropNonOperandReferences();
474+
472475
/// Replace all uses of all results of this instruction with undef.
473476
void replaceAllUsesOfAllResultsWithUndef();
474477

include/swift/SIL/SILInstructionWorklist.h

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,13 +128,15 @@ 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().
131+
/// Add operands of \p instruction to the worklist. Meant to be used once it
132+
/// is certain that \p instruction will be deleted but may have operands that
133+
/// are still alive. With fewer uses, the operand definition may be
134+
/// optimizable.
135+
///
136+
/// \p instruction may still have uses because this is called before
137+
/// InstructionDeleter begins deleting it and some instructions are deleted at
138+
/// the same time as their uses.
134139
void addOperandsToWorklist(SILInstruction &instruction) {
135-
assert(!instruction.hasUsesOfAnyResult() &&
136-
"Cannot erase instruction that is used!");
137-
138140
// Make sure that we reprocess all operands now that we reduced their
139141
// use counts.
140142
if (instruction.getNumOperands() < 8) {

include/swift/SIL/SILValue.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,9 @@ class Operand {
10371037
/// guaranteed scope.
10381038
bool isLifetimeEnding() const;
10391039

1040+
/// Returns true if this ends the lifetime of an owned operand.
1041+
bool isConsuming() const;
1042+
10401043
SILBasicBlock *getParentBlock() const;
10411044
SILFunction *getParentFunction() const;
10421045

include/swift/SILOptimizer/Analysis/SimplifyInstruction.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
namespace swift {
2626

2727
class SILInstruction;
28-
class InstModCallbacks;
28+
struct InstModCallbacks;
2929

3030
/// Replace an instruction with a simplified result and erase it. If the
3131
/// instruction initiates a scope, do not replace the end of its scope; it will

include/swift/SILOptimizer/Utils/CFGOptUtils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ namespace swift {
3636
class DominanceInfo;
3737
class SILLoop;
3838
class SILLoopInfo;
39-
class InstModCallbacks;
39+
struct InstModCallbacks;
4040

4141
/// Adds a new argument to an edge between a branch and a destination
4242
/// block. Allows for user injected callbacks via \p callbacks.
Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,245 @@
1+
//===--- InstModCallbacks.h - intruction modification callbacks -*- C++ -*-===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
///
13+
/// InstModCallbacks: callbacks for instruction modification.
14+
///
15+
/// Callbacks are generally problematic because a pass cannot anticipate the
16+
/// state that SIL will be in when lower-level utilties invoke the
17+
/// callback. This creates implicit coupling across the layers of SIL utilities.
18+
///
19+
/// Alternatives:
20+
///
21+
/// For an Analyses that caches SILInstruction pointers, check
22+
/// SILInstruction::isDeleted() upon retrieval, and use the PassManager's
23+
/// analysis invalidation mechanism to clear the pointers at the end of each
24+
/// pass. The pointers remain valid in the "deleted" state until the end the
25+
/// pass.
26+
///
27+
/// For iterating over instructions during instruction creation and deletion,
28+
/// use an UpdatingInstructionIterator provided by the InstructionDeleter
29+
/// object:
30+
///
31+
/// for (SILInstruction *inst : deleter.updatingRange(bb)) ...
32+
///
33+
/// Make sure the pass uses the same deleter object for all deletion within the
34+
/// iterator scope.
35+
///
36+
/// To defer instruction deletion so that deletions happen in bulk at a
37+
/// convenient point in the pass, use InstructionDeleter::trackIfDead() and
38+
/// cleanupDeadInstructions().
39+
///
40+
/// To determine whether multiple layers of utilities made any change to the
41+
/// SIL, have each utility report whether it made a change.
42+
///
43+
/// For uses that don't fall into the categories above, restructure the pass so
44+
/// that low-level operations on individual instructions don't require
45+
/// callbacks. The SILCombine worklist is currently the main client of
46+
/// callbacks. It's possible to work around this by running more complex
47+
/// utilities as a separate SILCombine subpass in between draining the worklist
48+
/// so those utilities do not require callbacks.
49+
///
50+
//===----------------------------------------------------------------------===//
51+
52+
#include "swift/SIL/SILInstruction.h"
53+
#include <functional>
54+
55+
#ifndef SWIFT_SILOPTIMIZER_UTILS_INSTMODCALLBACKS_H
56+
#define SWIFT_SILOPTIMIZER_UTILS_INSTMODCALLBACKS_H
57+
58+
namespace swift {
59+
60+
/// A structure containing callbacks that are called when an instruction is
61+
/// removed or added.
62+
///
63+
/// PERFORMANCE NOTES: This code can be used in loops, so we want to make sure
64+
/// to not have overhead when the user does not specify a callback. To do that
65+
/// instead of defining a "default" std::function, we represent the "default"
66+
/// functions as nullptr. Then, in the helper function trampoline that actually
67+
/// gets called, we check if we have a nullptr and if we do, we perform the
68+
/// default operation inline. What is nice about this from a perf perspective is
69+
/// that in a loop this property should predict well since you have a single
70+
/// branch that is going to go the same way everytime.
71+
struct InstModCallbacks {
72+
/// A function that is called to notify that a new function was created.
73+
///
74+
/// Default implementation is a no-op, but we still mark madeChange.
75+
std::function<void(SILInstruction *newlyCreatedInst)> createdNewInstFunc;
76+
77+
/// A function sets the value in \p use to be \p newValue.
78+
///
79+
/// Default implementation just calls use->set(newValue).
80+
///
81+
/// NOTE: It is assumed that this operation will never invalidate instruction
82+
/// iterators.
83+
///
84+
/// This can have compile-time implications and should be avoided
85+
/// whenever possible in favor of more structured optimization passes.
86+
std::function<void(Operand *use, SILValue newValue)> setUseValueFunc;
87+
88+
/// A function that takes in an instruction and deletes the inst.
89+
///
90+
/// This is used to invalidate dangling instruction pointers. The SIL will be
91+
/// invalid when it is invoked. The callback is only allowed to inspect the
92+
/// inline fields of \p instToDelete and iterate over the results. It is not
93+
/// allowed to dereference operands or iterate uses.
94+
///
95+
/// See comments for notifyWillBeDeletedFunc.
96+
///
97+
/// The default implementation is:
98+
///
99+
/// instToDelete->eraseFromParent();
100+
///
101+
/// The reason this callback is reponsible for deleting the instruction is to
102+
/// interoperate more easily with
103+
/// CanonicalizeInstruction::killInstruction(). This allows updates to choose
104+
/// whether to happen before or after deleting the instruction and possibly
105+
/// keep it around as a zombie object. All implementations must at least
106+
/// immediately remove all references to the instruction, including the parent
107+
/// block list.
108+
///
109+
/// TODO: Now that instructions deletion can be delayed via
110+
/// SILModule::scheduleForDeletion(); there's no longer a good use case for
111+
/// calling eraseFromParent() within this callback. Rewrite all clients
112+
/// without doing the instruction deletion within the callback.
113+
std::function<void(SILInstruction *instToDelete)> deleteInstFunc;
114+
115+
/// If non-null, called before a salient instruction is deleted or has its
116+
/// references dropped. If null, no-op.
117+
///
118+
/// This can be used to respond to dead instructions that will be deleted in
119+
/// the future. Unlike deleteInstFunc, the SIL will be in a valid
120+
/// state. However, arbitrary SIL transformations may happen between this
121+
/// invocation and actual instruction deletion.
122+
///
123+
/// This callback is not guaranteed to be called for every deleted
124+
/// instruction. It cannot be used to invalidate dangling pointers. It is only
125+
/// called for "salient" instructions that likely create additional
126+
/// optimization opportunities when deleted. If a dead def-use chain is
127+
/// deleted, notification only occurs for the initial def.
128+
///
129+
/// This is used in rare circumstances to update an optimization worklist. It
130+
/// should be avoided whenever possible in favor of more structured
131+
/// optimization passes.
132+
std::function<void(SILInstruction *instThatWillBeDeleted)>
133+
notifyWillBeDeletedFunc;
134+
135+
/// A boolean that tracks if any of our callbacks were ever called.
136+
bool wereAnyCallbacksInvoked = false;
137+
138+
InstModCallbacks() = default;
139+
~InstModCallbacks() = default;
140+
InstModCallbacks(const InstModCallbacks &) = default;
141+
142+
/// Return a copy of self with deleteInstFunc set to \p newDeleteInstFunc.
143+
LLVM_ATTRIBUTE_UNUSED InstModCallbacks
144+
onDelete(decltype(deleteInstFunc) newDeleteInstFunc) const {
145+
InstModCallbacks result = *this;
146+
result.deleteInstFunc = newDeleteInstFunc;
147+
return result;
148+
}
149+
150+
/// Return a copy of self with createdNewInstFunc set to \p
151+
/// newCreatedNewInstFunc.
152+
LLVM_ATTRIBUTE_UNUSED InstModCallbacks
153+
onCreateNewInst(decltype(createdNewInstFunc) newCreatedNewInstFunc) const {
154+
InstModCallbacks result = *this;
155+
result.createdNewInstFunc = newCreatedNewInstFunc;
156+
return result;
157+
}
158+
159+
/// Return a copy of self with setUseValueFunc set to \p newSetUseValueFunc.
160+
LLVM_ATTRIBUTE_UNUSED InstModCallbacks
161+
onSetUseValue(decltype(setUseValueFunc) newSetUseValueFunc) const {
162+
InstModCallbacks result = *this;
163+
result.setUseValueFunc = newSetUseValueFunc;
164+
return result;
165+
}
166+
167+
/// Return a copy of self with notifyWillBeDeletedFunc set to \p
168+
/// newNotifyWillBeDeletedFunc.
169+
LLVM_ATTRIBUTE_UNUSED
170+
InstModCallbacks onNotifyWillBeDeleted(
171+
decltype(notifyWillBeDeletedFunc) newNotifyWillBeDeletedFunc) const {
172+
InstModCallbacks result = *this;
173+
result.notifyWillBeDeletedFunc = newNotifyWillBeDeletedFunc;
174+
return result;
175+
}
176+
177+
void deleteInst(SILInstruction *instToDelete,
178+
bool notifyWhenDeleting = true) {
179+
wereAnyCallbacksInvoked = true;
180+
if (notifyWhenDeleting && notifyWillBeDeletedFunc)
181+
notifyWillBeDeletedFunc(instToDelete);
182+
if (deleteInstFunc)
183+
return deleteInstFunc(instToDelete);
184+
instToDelete->eraseFromParent();
185+
}
186+
187+
void createdNewInst(SILInstruction *newlyCreatedInst) {
188+
wereAnyCallbacksInvoked = true;
189+
if (createdNewInstFunc)
190+
createdNewInstFunc(newlyCreatedInst);
191+
}
192+
193+
void setUseValue(Operand *use, SILValue newValue) {
194+
wereAnyCallbacksInvoked = true;
195+
if (setUseValueFunc)
196+
return setUseValueFunc(use, newValue);
197+
use->set(newValue);
198+
}
199+
200+
/// Notify via our callbacks that an instruction will be deleted/have its
201+
/// operands dropped.
202+
///
203+
/// DISCUSSION: Since we do not delete instructions in any specific order, we
204+
/// drop all references of the instructions before we call deleteInst. Thus
205+
/// one can not in deleteInst look at operands. Certain parts of the optimizer
206+
/// rely on this ability, so we preserve it.
207+
void notifyWillBeDeleted(SILInstruction *instThatWillBeDeleted) {
208+
wereAnyCallbacksInvoked = true;
209+
if (notifyWillBeDeletedFunc)
210+
return notifyWillBeDeletedFunc(instThatWillBeDeleted);
211+
}
212+
213+
void replaceValueUsesWith(SILValue oldValue, SILValue newValue) {
214+
wereAnyCallbacksInvoked = true;
215+
216+
// If setUseValueFunc is not set, just call RAUW directly. RAUW in this case
217+
// is equivalent to what we do below. We just enable better
218+
// performance. This ensures that the default InstModCallback is really
219+
// fast.
220+
if (!setUseValueFunc)
221+
return oldValue->replaceAllUsesWith(newValue);
222+
223+
while (!oldValue->use_empty()) {
224+
auto *use = *oldValue->use_begin();
225+
setUseValue(use, newValue);
226+
}
227+
}
228+
229+
void eraseAndRAUWSingleValueInst(SingleValueInstruction *oldInst,
230+
SILValue newValue) {
231+
wereAnyCallbacksInvoked = true;
232+
replaceValueUsesWith(oldInst, newValue);
233+
deleteInst(oldInst);
234+
}
235+
236+
bool hadCallbackInvocation() const { return wereAnyCallbacksInvoked; }
237+
238+
/// Set \p wereAnyCallbacksInvoked to false. Useful if one wants to reuse an
239+
/// InstModCallback in between iterations.
240+
void resetHadCallbackInvocation() { wereAnyCallbacksInvoked = false; }
241+
};
242+
243+
} // end namespace swift
244+
245+
#endif

0 commit comments

Comments
 (0)