Skip to content

Commit dde6a37

Browse files
committed
InstructionDeleter rewrite
Clarify the API. Make it suitable for use everywhere in the compiler. We should try to standardize on it and allow it to do the OSSA fixup more often. Add InstructionDeleter::updatingIterator() factory so we never normally need to use InstModCallbacks. Fix bugs in which notifyWillBeDeleted() was being called on invalid SIL. The bugs are easily exposed just by removing copy_value side effects, but that will be in the follow-up commit. Call notifyWillBeDeleted() only when identifying new dead instructions that the client may not know about. Give the client control over force-deleting instructions. When doing its own lifetime fixups, the client may force-delete a set of related instructions. Invoking callbacks for these force-deleted instructions is wrong. TODO: partial_apply support is only partial. I disabled the buggy cases. This should be easy to fix but requires designing some InstructionDeleter test cases.
1 parent ef73f89 commit dde6a37

File tree

4 files changed

+264
-206
lines changed

4 files changed

+264
-206
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/SILOptimizer/Utils/InstOptUtils.h

Lines changed: 127 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -59,39 +59,53 @@ class InstModCallbacks {
5959
///
6060
/// NOTE: It is assumed that this operation will never invalidate instruction
6161
/// iterators.
62+
///
63+
/// This can have compile-time implications and should be avoided
64+
/// whenever possible in favor of more structured optimization passes.
6265
std::function<void(Operand *use, SILValue newValue)> setUseValueFunc;
6366

6467
/// A function that takes in an instruction and deletes the inst.
6568
///
66-
/// Default implementation is instToDelete->eraseFromParent();
69+
/// This is used to invalidate dangling instruction pointers. The SIL will be
70+
/// invalid when it is invoked. The callback is only allowed to inspect the
71+
/// inline fields of \p instToDelete and iterate over the results. It is not
72+
/// allowed to dereference operands or iterate uses.
73+
///
74+
/// See comments for notifyWillBeDeletedFunc.
75+
///
76+
/// The default implementation is:
77+
///
78+
/// instToDelete->eraseFromParent();
79+
///
80+
/// The reason this callback is reponsible for deleting the instruction is to
81+
/// interoperate more easily with
82+
/// CanonicalizeInstruction::killInstruction(). This allows updates to choose
83+
/// whether to happen before or after deleting the instruction and possibly
84+
/// keep it around as a zombie object. All implementations must at least
85+
/// immediately remove all references to the instruction, including the parent
86+
/// block list.
6787
///
68-
/// NOTE: The reason why we have deleteInstFunc and notifyWillBeDeletedFunc is
69-
/// InstModCallback supports 2 stage deletion where a callee passed
70-
/// InstModCallback is allowed to drop all references to the instruction
71-
/// before calling deleteInstFunc. In contrast, notifyWillBeDeletedFunc
72-
/// assumes that the IR is in a good form before being called so that the
73-
/// caller can via the callback gather state about the instruction that will
74-
/// be deleted. As an example, see InstructionDeleter::deleteInstruction() in
75-
/// InstOptUtils.cpp.
88+
/// TODO: When zombie instructions are implemented at the module level, we
89+
/// should move the eraseFromParent() functionality out of the callback.
7690
std::function<void(SILInstruction *instToDelete)> deleteInstFunc;
7791

78-
/// If non-null, called before an instruction is deleted or has its references
79-
/// dropped. If null, no-op.
92+
/// If non-null, called before a salient instruction is deleted or has its
93+
/// references dropped. If null, no-op.
8094
///
81-
/// NOTE: The reason why we have deleteInstFunc and notifyWillBeDeletedFunc is
82-
/// InstModCallback supports 2 stage deletion where a callee passed
83-
/// InstModCallback is allowed to drop all references to the instruction
84-
/// before calling deleteInstFunc. In contrast, notifyWillBeDeletedFunc
85-
/// assumes that the IR is in a good form before being called so that the
86-
/// caller can via the callback gather state about the instruction that will
87-
/// be deleted. As an example, see InstructionDeleter::deleteInstruction() in
88-
/// InstOptUtils.cpp.
95+
/// This can be used to respond to dead instructions that will be deleted in
96+
/// the future. Unlike deleteInstFunc, the SIL will be in a valid
97+
/// state. However, arbitrary SIL transformations may happen between this
98+
/// invocation and actual instruction deletion.
8999
///
90-
/// NOTE: This is called in InstModCallback::deleteInst() if one does not use
91-
/// a default bool argument to disable the notification. In general that
92-
/// should only be done when one is writing custom handling and is performing
93-
/// the notification ones self. It is assumed that the notification will be
94-
/// called with a valid instruction.
100+
/// This callback is not guaranteed to be called for every deleted
101+
/// instruction. It cannot be used to invalidate dangling pointers. It is only
102+
/// called for "salient" instructions that likely create additional
103+
/// optimization opportunities when deleted. If a dead def-use chain is
104+
/// deleted, notification only occurs for the initial def.
105+
///
106+
/// This is used in rare circumstances to update an optimization worklist. It
107+
/// should be avoided whenever possible in favor of more structured
108+
/// optimization passes.
95109
std::function<void(SILInstruction *instThatWillBeDeleted)>
96110
notifyWillBeDeletedFunc;
97111

@@ -265,6 +279,9 @@ void recursivelyDeleteTriviallyDeadInstructions(
265279
SILInstruction *inst, bool force = false,
266280
InstModCallbacks callbacks = InstModCallbacks());
267281

282+
/// True if this instruction can be deleted if all its uses can also be deleted.
283+
bool isInstructionTriviallyDeletable(SILInstruction *inst);
284+
268285
/// Perform a fast local check to see if the instruction is dead.
269286
///
270287
/// This routine only examines the state of the instruction at hand.
@@ -279,11 +296,6 @@ bool isIntermediateRelease(SILInstruction *inst, EpilogueARCFunctionInfo *erfi);
279296
void collectUsesOfValue(SILValue V,
280297
llvm::SmallPtrSetImpl<SILInstruction *> &Insts);
281298

282-
/// Recursively erase all of the uses of the instruction (but not the
283-
/// instruction itself)
284-
void eraseUsesOfInstruction(SILInstruction *inst,
285-
InstModCallbacks callbacks = InstModCallbacks());
286-
287299
/// Recursively erase all of the uses of the value (but not the
288300
/// value itself)
289301
void eraseUsesOfValue(SILValue value);
@@ -375,7 +387,42 @@ bool tryCheckedCastBrJumpThreading(
375387
/// cleaning up any dead code resulting from deleting those instructions. Use
376388
/// this utility instead of
377389
/// \c recursivelyDeleteTriviallyDeadInstruction.
390+
///
391+
/// This is designed to be used with a single 'onDelete' callback, which is
392+
/// invoked consistently just before deleting each instruction. It's usually
393+
/// used to avoid iterator invalidation (see the updatingIterator() factory
394+
/// method). The other InstModCallbacks should generally be handled at a higher
395+
/// level, and avoided altogether if possible. The following two are supported
396+
/// for flexibility:
397+
///
398+
/// callbacks.createdNewInst() is invoked incrementally when it fixes lifetimes
399+
/// while deleting a set of instructions, but the SIL may still be invalid
400+
/// relative to the new instruction.
401+
///
402+
/// callbacks.notifyWillBeDeletedFunc() is invoked when a dead instruction is
403+
/// first recognized and was not already passed in by the client. During the
404+
/// callback, the to-be-deleted instruction has valid SIL. It's operands and
405+
/// uses can be inspected and cached. It will be deleted later during
406+
/// cleanupDeadInstructions().
407+
///
408+
/// Note that the forceDelete* APIs only invoke notifyWillBeDeletedFunc() when
409+
/// an operand's definition will become dead after force-deleting the specified
410+
/// instruction. Some clients force-delete related instructions one at a
411+
/// time. They may not force-deleted. It's the client's responsiblity to invoke
412+
/// notifyWillBeDeletedFunc() on those explicitly deleted instructions if
413+
/// needed.
414+
///
378415
class InstructionDeleter {
416+
public:
417+
static InstructionDeleter updatingIterator(SILBasicBlock::iterator &iter) {
418+
auto onDelete = InstModCallbacks().onDelete([&](SILInstruction *deadInst) {
419+
if (deadInst->getIterator() == iter)
420+
++iter;
421+
deadInst->eraseFromParent();
422+
});
423+
return InstructionDeleter(onDelete);
424+
}
425+
379426
private:
380427
/// A set vector of instructions that are found to be dead. The ordering of
381428
/// instructions in this set is important as when a dead instruction is
@@ -384,20 +431,33 @@ class InstructionDeleter {
384431
SmallSetVector<SILInstruction *, 8> deadInstructions;
385432

386433
/// Callbacks used when adding/deleting instructions.
387-
InstModCallbacks instModCallbacks;
434+
InstModCallbacks callbacks;
388435

389436
public:
390-
InstructionDeleter() : deadInstructions(), instModCallbacks() {}
391-
InstructionDeleter(InstModCallbacks inputCallbacks)
392-
: deadInstructions(), instModCallbacks(inputCallbacks) {}
437+
InstructionDeleter() : deadInstructions(), callbacks() {}
438+
InstructionDeleter(InstModCallbacks callbacks)
439+
: deadInstructions(), callbacks(callbacks) {}
440+
441+
InstModCallbacks &getCallbacks() { return callbacks; }
393442

394443
/// If the instruction \p inst is dead, record it so that it can be cleaned
395444
/// up.
396-
void trackIfDead(SILInstruction *inst);
445+
///
446+
/// Calls callbacks.notifyWillBeDeleted().
447+
bool trackIfDead(SILInstruction *inst);
448+
449+
/// Force track this instruction as dead. Used to enable the deletion of a
450+
/// bunch of instructions at the same time.
451+
///
452+
/// Calls callbacks.notifyWillBeDeleted().
453+
void forceTrackAsDead(SILInstruction *inst);
397454

398-
/// If the instruction \p inst is dead, delete it immediately and record
399-
/// its operands so that they can be cleaned up later.
400-
void deleteIfDead(SILInstruction *inst);
455+
/// If the instruction \p inst is dead, delete it immediately along with its
456+
/// destroys and scope-ending uses, and record its operands so that they can
457+
/// be cleaned up later.
458+
///
459+
/// Calls callbacks.notifyWillBeDeleted().
460+
bool deleteIfDead(SILInstruction *inst);
401461

402462
/// Delete the instruction \p inst and record instructions that may become
403463
/// dead because of the removal of \c inst. This function will add necessary
@@ -411,10 +471,8 @@ class InstructionDeleter {
411471
/// \pre the instruction to be deleted must not have any use other than
412472
/// incidental uses.
413473
///
414-
/// \p callback is called on each deleted instruction before deleting any
415-
/// instructions. This way, the SIL is valid in the callback. However, the
416-
/// callback cannot be used to update instruction iterators since other
417-
/// instructions to be deleted remain in the instruction list.
474+
/// callbacks.notifyWillBeDeleted will not be called for \p inst but will be
475+
/// called for any other instructions that become dead as a result.
418476
void forceDeleteAndFixLifetimes(SILInstruction *inst);
419477

420478
/// Delete the instruction \p inst and record instructions that may become
@@ -429,11 +487,19 @@ class InstructionDeleter {
429487
///
430488
/// \pre the instruction to be deleted must not have any use other than
431489
/// incidental uses.
490+
///
491+
/// callbacks.notifyWillBeDeleted will not be called for \p inst but will be
492+
/// called for any other instructions that become dead as a result.
432493
void forceDelete(SILInstruction *inst);
433494

434-
/// Force track this instruction as dead. Used to enable the deletion of a
435-
/// bunch of instructions at the same time.
436-
void forceTrackAsDead(SILInstruction *inst);
495+
/// Recursively delete all of the uses of the instruction before deleting the
496+
/// instruction itself. Does not fix lifetimes.
497+
///
498+
/// callbacks.notifyWillBeDeleted will not be called for \p inst but will
499+
/// be called for any other instructions that become dead as a result.
500+
void forceDeleteWithUsers(SILInstruction *inst) {
501+
deleteWithUses(inst, /*fixLifetimes*/ false, /*forceDeleteUsers*/ true);
502+
}
437503

438504
/// Clean up dead instructions that are tracked by this instance and all
439505
/// instructions that transitively become dead.
@@ -442,19 +508,30 @@ class InstructionDeleter {
442508
/// under or over releases). Note that if \c forceDelete call leaves the
443509
/// function body in an inconsistent state, it needs to be made consistent
444510
/// before this method is invoked.
445-
void cleanUpDeadInstructions();
511+
///
512+
/// callbacks.notifyWillBeDeletedFunc will only be called for instructions
513+
/// that become dead during cleanup but were not already tracked.
514+
void cleanupDeadInstructions();
446515

447-
/// Recursively visit users of \c inst (including \c inst)and delete
448-
/// instructions that are dead (including \c inst).
516+
/// Recursively visit users of \p inst and delete instructions that are dead
517+
/// including \p inst.
518+
///
519+
/// callbacks.notifyWillBeDeletedFunc will be called for any dead
520+
/// instructions.
449521
void recursivelyDeleteUsersIfDead(SILInstruction *inst);
450522

451-
/// Recursively visit users of \c inst (including \c inst)and force delete
452-
/// them. Also, destroy the consumed operands of the deleted instructions
523+
/// Recursively visit users of \p inst and force delete them including \p
524+
/// inst. Also, destroy the consumed operands of the deleted instructions
453525
/// whenever necessary.
526+
///
527+
/// callbacks.notifyWillBeDeletedFunc will not be called for \p inst or its
528+
/// users but will be called for any other instructions that become dead as a
529+
/// result.
454530
void recursivelyForceDeleteUsersAndFixLifetimes(SILInstruction *inst);
455531

456532
private:
457-
void deleteInstruction(SILInstruction *inst, bool fixOperandLifetimes);
533+
void deleteWithUses(SILInstruction *inst, bool fixLifetimes,
534+
bool forceDeleteUsers = false);
458535
};
459536

460537
/// If \c inst is dead, delete it and recursively eliminate all code that

lib/SIL/IR/SILInstruction.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,13 +145,15 @@ void SILInstruction::dropAllReferences() {
145145
OpE = PossiblyDeadOps.end(); OpI != OpE; ++OpI) {
146146
OpI->drop();
147147
}
148+
dropNonOperandReferences();
149+
}
148150

151+
void SILInstruction::dropNonOperandReferences() {
149152
if (auto *termInst = dyn_cast<TermInst>(this)) {
150153
for (SILSuccessor &succ : termInst->getSuccessors()) {
151154
succ = nullptr;
152155
}
153156
}
154-
155157
// If we have a function ref inst, we need to especially drop its function
156158
// argument so that it gets a proper ref decrement.
157159
if (auto *FRI = dyn_cast<FunctionRefBaseInst>(this)) {

0 commit comments

Comments
 (0)