Skip to content

Commit 4a8cb7a

Browse files
committed
Move InstModCallbacks into its own header.
Required to break circular dependence when introducing UpdatingInstructionIterator. Also, this is a lot of detail that doesn't belong in the general InstOptUtils APIs. It's not something people should ever reach for.
1 parent da6b136 commit 4a8cb7a

File tree

2 files changed

+247
-186
lines changed

2 files changed

+247
-186
lines changed
Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
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+
class 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+
public:
139+
InstModCallbacks() = default;
140+
~InstModCallbacks() = default;
141+
InstModCallbacks(const InstModCallbacks &) = default;
142+
143+
/// Return a copy of self with deleteInstFunc set to \p newDeleteInstFunc.
144+
LLVM_ATTRIBUTE_UNUSED InstModCallbacks
145+
onDelete(decltype(deleteInstFunc) newDeleteInstFunc) const {
146+
InstModCallbacks result = *this;
147+
result.deleteInstFunc = newDeleteInstFunc;
148+
return result;
149+
}
150+
151+
/// Return a copy of self with createdNewInstFunc set to \p
152+
/// newCreatedNewInstFunc.
153+
LLVM_ATTRIBUTE_UNUSED InstModCallbacks
154+
onCreateNewInst(decltype(createdNewInstFunc) newCreatedNewInstFunc) const {
155+
InstModCallbacks result = *this;
156+
result.createdNewInstFunc = newCreatedNewInstFunc;
157+
return result;
158+
}
159+
160+
/// Return a copy of self with setUseValueFunc set to \p newSetUseValueFunc.
161+
LLVM_ATTRIBUTE_UNUSED InstModCallbacks
162+
onSetUseValue(decltype(setUseValueFunc) newSetUseValueFunc) const {
163+
InstModCallbacks result = *this;
164+
result.setUseValueFunc = newSetUseValueFunc;
165+
return result;
166+
}
167+
168+
/// Return a copy of self with notifyWillBeDeletedFunc set to \p
169+
/// newNotifyWillBeDeletedFunc.
170+
LLVM_ATTRIBUTE_UNUSED
171+
InstModCallbacks onNotifyWillBeDeleted(
172+
decltype(notifyWillBeDeletedFunc) newNotifyWillBeDeletedFunc) const {
173+
InstModCallbacks result = *this;
174+
result.notifyWillBeDeletedFunc = newNotifyWillBeDeletedFunc;
175+
return result;
176+
}
177+
178+
void deleteInst(SILInstruction *instToDelete,
179+
bool notifyWhenDeleting = true) {
180+
wereAnyCallbacksInvoked = true;
181+
if (notifyWhenDeleting && notifyWillBeDeletedFunc)
182+
notifyWillBeDeletedFunc(instToDelete);
183+
if (deleteInstFunc)
184+
return deleteInstFunc(instToDelete);
185+
instToDelete->eraseFromParent();
186+
}
187+
188+
void createdNewInst(SILInstruction *newlyCreatedInst) {
189+
wereAnyCallbacksInvoked = true;
190+
if (createdNewInstFunc)
191+
createdNewInstFunc(newlyCreatedInst);
192+
}
193+
194+
void setUseValue(Operand *use, SILValue newValue) {
195+
wereAnyCallbacksInvoked = true;
196+
if (setUseValueFunc)
197+
return setUseValueFunc(use, newValue);
198+
use->set(newValue);
199+
}
200+
201+
/// Notify via our callbacks that an instruction will be deleted/have its
202+
/// operands dropped.
203+
///
204+
/// DISCUSSION: Since we do not delete instructions in any specific order, we
205+
/// drop all references of the instructions before we call deleteInst. Thus
206+
/// one can not in deleteInst look at operands. Certain parts of the optimizer
207+
/// rely on this ability, so we preserve it.
208+
void notifyWillBeDeleted(SILInstruction *instThatWillBeDeleted) {
209+
wereAnyCallbacksInvoked = true;
210+
if (notifyWillBeDeletedFunc)
211+
return notifyWillBeDeletedFunc(instThatWillBeDeleted);
212+
}
213+
214+
void replaceValueUsesWith(SILValue oldValue, SILValue newValue) {
215+
wereAnyCallbacksInvoked = true;
216+
217+
// If setUseValueFunc is not set, just call RAUW directly. RAUW in this case
218+
// is equivalent to what we do below. We just enable better
219+
// performance. This ensures that the default InstModCallback is really
220+
// fast.
221+
if (!setUseValueFunc)
222+
return oldValue->replaceAllUsesWith(newValue);
223+
224+
while (!oldValue->use_empty()) {
225+
auto *use = *oldValue->use_begin();
226+
setUseValue(use, newValue);
227+
}
228+
}
229+
230+
void eraseAndRAUWSingleValueInst(SingleValueInstruction *oldInst,
231+
SILValue newValue) {
232+
wereAnyCallbacksInvoked = true;
233+
replaceValueUsesWith(oldInst, newValue);
234+
deleteInst(oldInst);
235+
}
236+
237+
bool hadCallbackInvocation() const { return wereAnyCallbacksInvoked; }
238+
239+
/// Set \p wereAnyCallbacksInvoked to false. Useful if one wants to reuse an
240+
/// InstModCallback in between iterations.
241+
void resetHadCallbackInvocation() { wereAnyCallbacksInvoked = false; }
242+
};
243+
244+
} // end namespace swift
245+
246+
#endif

0 commit comments

Comments
 (0)