Skip to content

[semantic-arc-opts] Eliminate dead trivial instructions /after/ the m… #27094

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/swift/Basic/BlotSetVector.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ class BlotSetVector {
iterator end() { return vector.end(); }
const_iterator begin() const { return vector.begin(); }
const_iterator end() const { return vector.end(); }

ArrayRef<Optional<ValueT>> getArray() const { return vector; }

llvm::iterator_range<const_iterator> getRange() const {
return {begin(), end()};
}
Expand Down
47 changes: 30 additions & 17 deletions lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,18 @@ namespace {
/// the worklist before we delete them.
struct SemanticARCOptVisitor
: SILInstructionVisitor<SemanticARCOptVisitor, bool> {
/// Our main worklist. We use this after an initial run through.
SmallBlotSetVector<SILValue, 32> worklist;

/// A secondary work list that we use to store dead trivial instructions to
/// delete after we are done processing the worklist.
SmallBlotSetVector<SILInstruction *, 32> deadTrivialInsts;

SILFunction &F;
Optional<DeadEndBlocks> TheDeadEndBlocks;

explicit SemanticARCOptVisitor(SILFunction &F) : F(F) {}

DeadEndBlocks &getDeadEndBlocks() {
if (!TheDeadEndBlocks)
TheDeadEndBlocks.emplace(&F);
Expand All @@ -167,7 +173,7 @@ struct SemanticARCOptVisitor

/// Add all operands of i to the worklist and then call eraseInstruction on
/// i. Assumes that the instruction doesnt have users.
void eraseInstructionAndAddOptsToWorklist(SILInstruction *i) {
void eraseInstructionAndAddOperandsToWorklist(SILInstruction *i) {
// Then copy all operands into the worklist for future processing.
for (SILValue v : i->getOperandValues()) {
worklist.insert(v);
Expand All @@ -184,6 +190,7 @@ struct SemanticARCOptVisitor
for (SILValue result : i->getResults()) {
worklist.erase(result);
}
deadTrivialInsts.erase(i);
i->eraseFromParent();
}

Expand Down Expand Up @@ -227,18 +234,7 @@ bool SemanticARCOptVisitor::processWorklist() {
// the instruction).
if (auto *defInst = next->getDefiningInstruction()) {
if (isInstructionTriviallyDead(defInst)) {
madeChange = true;
recursivelyDeleteTriviallyDeadInstructions(
defInst, true/*force*/,
[&](SILInstruction *i) {
for (SILValue operand : i->getOperandValues()) {
worklist.insert(operand);
}
for (SILValue result : i->getResults()) {
worklist.erase(result);
}
++NumEliminatedInsts;
});
deadTrivialInsts.insert(defInst);
continue;
}
}
Expand All @@ -251,6 +247,23 @@ bool SemanticARCOptVisitor::processWorklist() {
}
}

// Then eliminate the rest of the dead trivial insts.
//
// NOTE: We do not need to touch the worklist here since it is guaranteed to
// be empty due to the loop above. We enforce this programatically with the
// assert.
assert(worklist.empty() && "Expected drained worklist so we don't have to "
"remove dead insts form it");
while (!deadTrivialInsts.empty()) {
auto val = deadTrivialInsts.pop_back_val();
if (!val)
continue;
recursivelyDeleteTriviallyDeadInstructions(
*val, true /*force*/,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Out of curiosity, why is force=true appropriate here? Also, why was it previously necessary to add operands and results to the worklist but is no longer?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why I put force here is b/c if I don't put force it just does the isTriviallyDead check again.

The reason why I removed the operand/result thing is I find with the test case in question that recursivelyDeleteTriviallyDeadInstructions can re-add the struct_extract from the test case to the worklist as a result of an instruction.

I just avoided all of the problems by doing it later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason why I put force here is b/c if I don't put force it just does the isTriviallyDead check again.

Anytime we violate the spirit of the API like this there should be a comment explaining the cleverness.

[&](SILInstruction *i) { deadTrivialInsts.erase(i); });
madeChange = true;
}

return madeChange;
}

Expand Down Expand Up @@ -504,7 +517,7 @@ bool SemanticARCOptVisitor::eliminateDeadLiveRangeCopyValue(CopyValueInst *cvi)
if (auto *op = cvi->getSingleUse()) {
if (auto *dvi = dyn_cast<DestroyValueInst>(op->getUser())) {
eraseInstruction(dvi);
eraseInstructionAndAddOptsToWorklist(cvi);
eraseInstructionAndAddOperandsToWorklist(cvi);
NumEliminatedInsts += 2;
return true;
}
Expand All @@ -531,7 +544,7 @@ bool SemanticARCOptVisitor::eliminateDeadLiveRangeCopyValue(CopyValueInst *cvi)
eraseInstruction(destroys.pop_back_val());
++NumEliminatedInsts;
}
eraseInstructionAndAddOptsToWorklist(cvi);
eraseInstructionAndAddOperandsToWorklist(cvi);
++NumEliminatedInsts;
return true;
}
Expand Down
18 changes: 18 additions & 0 deletions test/SILOptimizer/semantic-arc-opts.sil
Original file line number Diff line number Diff line change
Expand Up @@ -838,3 +838,21 @@ bb0(%0 : @guaranteed $NativeObjectPair):
apply %func(%4) : $@convention(thin) (@guaranteed Builtin.NativeObject) -> MyNever
unreachable
}

// Just make sure that we do not crash on this. We should be able to eliminate
// everything here.
//
// CHECK-LABEL: sil [ossa] @copy_value_with_debug_user : $@convention(thin) (@guaranteed NativeObjectPair) -> () {
// CHECK: bb0
// CHECK-NEXT: tuple
// CHECK-NEXT: return
// CHECK-NEXT: } // end sil function 'copy_value_with_debug_user'
sil [ossa] @copy_value_with_debug_user : $@convention(thin) (@guaranteed NativeObjectPair) -> () {
bb0(%0 : @guaranteed $NativeObjectPair):
%1 = struct_extract %0 : $NativeObjectPair, #NativeObjectPair.obj1
%2 = copy_value %1 : $Builtin.NativeObject
debug_value %2 : $Builtin.NativeObject, let, name "myField"
destroy_value %2 : $Builtin.NativeObject
%9999 = tuple()
return %9999 : $()
}