Skip to content

Commit d3aee16

Browse files
committed
[silgen-cleanup] Ensure that we move nextII past /all/ instructions we are going to delete no matter order of visitation.
Specifically, given code like the following: ``` (%1, %2) = multi_result_inst %0 (a) inst_to_delete %1 (b) inst_to_delete %2 (c) ``` We would sometimes visit (c) before (b). So if nextII was (b) at that point, we would think that we were safe. Then we process (b), move nextII from (b) -> (c). Then we delete both (b) and (c). =><=. The solution to this is each iteration of the delete loop, we track /all/ instructions that eliminateDeadInstruction is going to eliminate and ensure that after each callback is called we have moved past all visited instructions (even ones we visited previously). This is done using a small set that I clear each iteration. I also re-enabled the dictionary test that exposed this issue when testing -O/-Osize. rdar://71933996
1 parent 28f8d9e commit d3aee16

File tree

3 files changed

+66
-8
lines changed

3 files changed

+66
-8
lines changed

lib/SILOptimizer/Mandatory/SILGenCleanup.cpp

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
#define DEBUG_TYPE "silgen-cleanup"
1818

19+
#include "swift/Basic/Defer.h"
1920
#include "swift/SIL/BasicBlockUtils.h"
2021
#include "swift/SIL/SILInstruction.h"
2122
#include "swift/SILOptimizer/PassManager/Transforms.h"
@@ -48,16 +49,54 @@ struct SILGenCanonicalize final : CanonicalizeInstruction {
4849

4950
void notifyHasNewUsers(SILValue) override { changed = true; }
5051

51-
SILBasicBlock::iterator deleteDeadOperands(SILBasicBlock::iterator nextII) {
52-
// Delete trivially dead instructions in non-determistic order.
52+
/// Delete trivially dead instructions in non-determistic order.
53+
///
54+
/// We either have that nextII is endII or if nextII is not endII then endII
55+
/// is nextII->getParent()->end().
56+
SILBasicBlock::iterator deleteDeadOperands(SILBasicBlock::iterator nextII,
57+
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;
63+
5364
while (!deadOperands.empty()) {
5465
SILInstruction *deadOperInst = *deadOperands.begin();
66+
5567
// Make sure at least the first instruction is removed from the set.
5668
deadOperands.erase(deadOperInst);
69+
70+
// Then add our initial instruction to the will be deleted set.
71+
willBeDeletedInIteration.insert(deadOperInst);
72+
SWIFT_DEFER { willBeDeletedInIteration.clear(); };
73+
5774
eliminateDeadInstruction(deadOperInst, [&](SILInstruction *deadInst) {
5875
LLVM_DEBUG(llvm::dbgs() << "Trivially dead: " << *deadInst);
59-
if (nextII == deadInst->getIterator())
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))
6097
++nextII;
98+
99+
// Then remove the instruction from the set.
61100
deadOperands.erase(deadInst);
62101
});
63102
}
@@ -97,7 +136,7 @@ void SILGenCleanup::run() {
97136
for (auto &bb : function) {
98137
for (auto ii = bb.begin(), ie = bb.end(); ii != ie;) {
99138
ii = sgCanonicalize.canonicalize(&*ii);
100-
ii = sgCanonicalize.deleteDeadOperands(ii);
139+
ii = sgCanonicalize.deleteDeadOperands(ii, ie);
101140
}
102141
}
103142
if (sgCanonicalize.changed) {

test/SILOptimizer/silgen_cleanup.sil

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,26 @@ bb0(%0 : $*X3, %1 : @guaranteed $Builtin.NativeObject):
216216
%12 = tuple ()
217217
return %12 : $()
218218
}
219+
220+
// We used to hit a memory error on this test.
221+
//
222+
// CHECK-LABEL: sil [ossa] @testDestructureTupleNoCrash : $@convention(thin) (@owned (Builtin.NativeObject, Builtin.NativeObject)) -> () {
223+
// CHECK: bb0(
224+
// CHECK-NEXT: destroy_value
225+
// CHECK-NEXT: tuple
226+
// CHECK-NEXT: return
227+
// CHECK: } // end sil function 'testDestructureTupleNoCrash'
228+
sil [ossa] @testDestructureTupleNoCrash : $@convention(thin) (@owned (Builtin.NativeObject, Builtin.NativeObject)) -> () {
229+
bb0(%0 : @owned $(Builtin.NativeObject, Builtin.NativeObject)):
230+
(%1, %2) = destructure_tuple %0 : $(Builtin.NativeObject, Builtin.NativeObject)
231+
debug_value %1 : $Builtin.NativeObject, let, name "key"
232+
debug_value %2 : $Builtin.NativeObject, let, name "value"
233+
%3 = begin_borrow %1 : $Builtin.NativeObject
234+
end_borrow %3 : $Builtin.NativeObject
235+
%4 = begin_borrow %2 : $Builtin.NativeObject
236+
end_borrow %4 : $Builtin.NativeObject
237+
destroy_value %2 : $Builtin.NativeObject
238+
destroy_value %1 : $Builtin.NativeObject
239+
%9999 = tuple()
240+
return %9999 : $()
241+
}

validation-test/stdlib/Dictionary.swift

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
// RUN: %target-codesign %t/Dictionary && %line-directive %t/main.swift -- %target-run %t/Dictionary
88
// REQUIRES: executable_test
99

10-
// rdar71933996
11-
// UNSUPPORTED: swift_test_mode_optimize
12-
// UNSUPPORTED: swift_test_mode_optimize_size
13-
1410
import StdlibUnittest
1511
import StdlibCollectionUnittest
1612

0 commit comments

Comments
 (0)