Skip to content

Commit 67ff2ae

Browse files
committed
[semantic-arc-opts] Change to always use the worklist.
Previously we were: 1. Doing a linear scan, performing certain optimizations, and setting up a worklist for future processing. 2. Draining the worklist of changed instructions until we reached a fix point. The key thing here is that for (1) to be safe, we would need to not perform any optimizations on block arguments since there was an ssumption that we would only perform SSA-like optimizations. I am going to be adding such optimizations soon, so it makes sense to just convert the initial traversal to non-destructively seed the worklist and eliminate that initial optimization pass. This should be NFC.
1 parent d6e30b5 commit 67ff2ae

File tree

2 files changed

+116
-80
lines changed

2 files changed

+116
-80
lines changed

lib/SILOptimizer/Mandatory/SemanticARCOpts.cpp

Lines changed: 107 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "swift/Basic/STLExtras.h"
1616
#include "swift/SIL/BasicBlockUtils.h"
1717
#include "swift/SIL/BranchPropagatedUser.h"
18+
#include "swift/SIL/DebugUtils.h"
1819
#include "swift/SIL/MemAccessUtils.h"
1920
#include "swift/SIL/OwnershipUtils.h"
2021
#include "swift/SIL/SILArgument.h"
@@ -148,9 +149,12 @@ struct SemanticARCOptVisitor
148149
/// Our main worklist. We use this after an initial run through.
149150
SmallBlotSetVector<SILValue, 32> worklist;
150151

151-
/// A secondary work list that we use to store dead trivial instructions to
152-
/// delete after we are done processing the worklist.
153-
SmallBlotSetVector<SILInstruction *, 32> deadTrivialInsts;
152+
/// A set of values that we have visited since the last mutation. We use this
153+
/// to ensure that we do not visit values twice without mutating.
154+
///
155+
/// This is specifically to ensure that we do not go into an infinite loop
156+
/// when visiting phi nodes.
157+
SmallBlotSetVector<SILValue, 16> visitedSinceLastMutation;
154158

155159
SILFunction &F;
156160
Optional<DeadEndBlocks> TheDeadEndBlocks;
@@ -167,8 +171,13 @@ struct SemanticARCOptVisitor
167171
/// the worklist, and then call eraseInstruction on i.
168172
void eraseAndRAUWSingleValueInstruction(SingleValueInstruction *i, SILValue newValue) {
169173
worklist.insert(newValue);
174+
for (auto *use : i->getUses()) {
175+
for (SILValue result : use->getUser()->getResults()) {
176+
worklist.insert(result);
177+
}
178+
}
170179
i->replaceAllUsesWith(newValue);
171-
eraseInstruction(i);
180+
eraseInstructionAndAddOperandsToWorklist(i);
172181
}
173182

174183
/// Add all operands of i to the worklist and then call eraseInstruction on
@@ -189,16 +198,86 @@ struct SemanticARCOptVisitor
189198
// the instruction.
190199
for (SILValue result : i->getResults()) {
191200
worklist.erase(result);
201+
visitedSinceLastMutation.erase(result);
192202
}
193-
deadTrivialInsts.erase(i);
194203
i->eraseFromParent();
204+
205+
// Add everything else from visitedSinceLastMutation to the worklist.
206+
for (auto opt : visitedSinceLastMutation) {
207+
if (!opt.hasValue()) {
208+
continue;
209+
}
210+
worklist.insert(*opt);
211+
}
212+
visitedSinceLastMutation.clear();
195213
}
196214

197215
/// The default visitor.
198-
bool visitSILInstruction(SILInstruction *i) { return false; }
216+
bool visitSILInstruction(SILInstruction *i) {
217+
assert(!isGuaranteedForwardingInst(i) &&
218+
"Should have forwarding visitor for all ownership forwarding "
219+
"instructions");
220+
return false;
221+
}
222+
199223
bool visitCopyValueInst(CopyValueInst *cvi);
200224
bool visitBeginBorrowInst(BeginBorrowInst *bbi);
201225
bool visitLoadInst(LoadInst *li);
226+
static bool shouldVisitInst(SILInstruction *i) {
227+
switch (i->getKind()) {
228+
default:
229+
return false;
230+
case SILInstructionKind::CopyValueInst:
231+
case SILInstructionKind::BeginBorrowInst:
232+
case SILInstructionKind::LoadInst:
233+
return true;
234+
}
235+
}
236+
237+
#define FORWARDING_INST(NAME) \
238+
bool visit##NAME##Inst(NAME##Inst *cls) { \
239+
for (SILValue v : cls->getResults()) { \
240+
worklist.insert(v); \
241+
} \
242+
return false; \
243+
}
244+
FORWARDING_INST(Tuple)
245+
FORWARDING_INST(Struct)
246+
FORWARDING_INST(Enum)
247+
FORWARDING_INST(OpenExistentialRef)
248+
FORWARDING_INST(Upcast)
249+
FORWARDING_INST(UncheckedRefCast)
250+
FORWARDING_INST(ConvertFunction)
251+
FORWARDING_INST(RefToBridgeObject)
252+
FORWARDING_INST(BridgeObjectToRef)
253+
FORWARDING_INST(UnconditionalCheckedCast)
254+
FORWARDING_INST(UncheckedEnumData)
255+
FORWARDING_INST(MarkUninitialized)
256+
FORWARDING_INST(SelectEnum)
257+
FORWARDING_INST(DestructureStruct)
258+
FORWARDING_INST(DestructureTuple)
259+
FORWARDING_INST(TupleExtract)
260+
FORWARDING_INST(StructExtract)
261+
FORWARDING_INST(OpenExistentialValue)
262+
FORWARDING_INST(OpenExistentialBoxValue)
263+
FORWARDING_INST(MarkDependence)
264+
#undef FORWARDING_INST
265+
266+
#define FORWARDING_TERM(NAME) \
267+
bool visit##NAME##Inst(NAME##Inst *cls) { \
268+
for (auto succValues : cls->getSuccessorBlockArguments()) { \
269+
for (SILValue v : succValues) { \
270+
worklist.insert(v); \
271+
} \
272+
} \
273+
return false; \
274+
}
275+
276+
FORWARDING_TERM(SwitchEnum)
277+
FORWARDING_TERM(CheckedCastBranch)
278+
FORWARDING_TERM(Branch)
279+
FORWARDING_TERM(CondBranch)
280+
#undef FORWARDING_TERM
202281

203282
bool isWrittenTo(LoadInst *li);
204283

@@ -225,6 +304,14 @@ bool SemanticARCOptVisitor::processWorklist() {
225304
if (!next)
226305
continue;
227306

307+
// First check if this is a value that we have visited since the last time
308+
// we erased an instruction. If we have visited it, skip it. Every time we
309+
// modify something, we should be deleting an instruction, so we have not
310+
// found any further information.
311+
if (!visitedSinceLastMutation.insert(next).second) {
312+
continue;
313+
}
314+
228315
// First check if this is an instruction that is trivially dead. This can
229316
// occur if we eliminate rr traffic resulting in dead projections and the
230317
// like.
@@ -234,7 +321,8 @@ bool SemanticARCOptVisitor::processWorklist() {
234321
// the instruction).
235322
if (auto *defInst = next->getDefiningInstruction()) {
236323
if (isInstructionTriviallyDead(defInst)) {
237-
deadTrivialInsts.insert(defInst);
324+
deleteAllDebugUses(defInst);
325+
eraseInstruction(defInst);
238326
continue;
239327
}
240328
}
@@ -247,23 +335,6 @@ bool SemanticARCOptVisitor::processWorklist() {
247335
}
248336
}
249337

250-
// Then eliminate the rest of the dead trivial insts.
251-
//
252-
// NOTE: We do not need to touch the worklist here since it is guaranteed to
253-
// be empty due to the loop above. We enforce this programatically with the
254-
// assert.
255-
assert(worklist.empty() && "Expected drained worklist so we don't have to "
256-
"remove dead insts form it");
257-
while (!deadTrivialInsts.empty()) {
258-
auto val = deadTrivialInsts.pop_back_val();
259-
if (!val)
260-
continue;
261-
recursivelyDeleteTriviallyDeadInstructions(
262-
*val, true /*force*/,
263-
[&](SILInstruction *i) { deadTrivialInsts.erase(i); });
264-
madeChange = true;
265-
}
266-
267338
return madeChange;
268339
}
269340

@@ -795,59 +866,24 @@ struct SemanticARCOpts : SILFunctionTransform {
795866
"Can not perform semantic arc optimization unless ownership "
796867
"verification is enabled");
797868

798-
// Iterate over all of the arguments, performing small peephole ARC
799-
// optimizations. We assume that the visitor will add any instructions we
800-
// need to recursively to the visitor's worklist. Also, note that we assume
801-
// that we do not look through /any/ sil block arguments here since our
802-
// iteration here is only valid up to SSA.
803-
bool madeChange = false;
804-
805869
SemanticARCOptVisitor visitor(f);
806-
807-
for (auto &bb : f) {
808-
auto ii = bb.rend();
809-
auto start = bb.rbegin();
810-
811-
// If the bb is empty, continue.
812-
if (start == ii)
813-
continue;
814870

815-
// Go to the first instruction to process.
816-
--ii;
817-
818-
// Then until we process the first instruction of the block...
819-
while (ii != start) {
820-
// Move the iterator before ii.
821-
auto tmp = std::next(ii);
822-
823-
// Then try to optimize. If we succeeded, then we deleted
824-
// ii. Move ii from the next value back onto the instruction
825-
// after ii's old value in the block instruction list and then
826-
// process that.
827-
if (visitor.visit(&*ii)) {
828-
madeChange = true;
829-
ii = std::prev(tmp);
830-
continue;
871+
// Add all the results of all instructions that we want to visit to the
872+
// worklist.
873+
for (auto &block : f) {
874+
for (auto &inst : block) {
875+
if (SemanticARCOptVisitor::shouldVisitInst(&inst)) {
876+
for (SILValue v : inst.getResults()) {
877+
visitor.worklist.insert(v);
878+
}
831879
}
832-
833-
// Otherwise, we didn't delete ii. Just visit the next instruction.
834-
--ii;
835880
}
836-
837-
// Finally visit the first instruction of the block.
838-
madeChange |= visitor.visit(&*ii);
839881
}
840882

841-
// Finally drain the worklist on the visitor and process until we reach the
842-
// fixpoint and thus do not have any further work to do.
843-
//
844-
// NOTE: At this point madeChange has already been set to true if we have
845-
// anything in the worklist, so technically we do not need to do this. But I
846-
// would rather represent this state to future proof the pass to be
847-
// "visually" correct.
848-
madeChange |= visitor.processWorklist();
849-
850-
if (madeChange) {
883+
// Then process the worklist. We only destroy instructions, so invalidate
884+
// that. Once we modify the ownership of block arguments, we will need to
885+
// perhaps invalidate branches as well.
886+
if (visitor.processWorklist()) {
851887
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
852888
}
853889
}

test/SILOptimizer/outliner.swift

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ public class MyGizmo {
3737
// CHECK: [[FUN:%.*]] = function_ref @$sSo5GizmoC12modifyString_10withNumber0D6FoobarSSSgAF_SiypSgtFToTembnnnb_
3838
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned String, Int, Optional<AnyObject>, Gizmo) -> @owned Optional<String>
3939
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned String, Int, Optional<AnyObject>, Gizmo) -> @owned Optional<String>
40-
// CHECK: [[FUN:%.*]] = function_ref @$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembnn_
41-
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned Array<String>, Gizmo) -> @owned Optional<AnyObject>
42-
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@owned Array<String>, Gizmo) -> @owned Optional<AnyObject>
40+
// CHECK: [[FUN:%.*]] = function_ref @$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembgnn_
41+
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@guaranteed Array<String>, Gizmo) -> @owned Optional<AnyObject>
42+
// CHECK: apply [[FUN]]({{.*}}) : $@convention(thin) (@guaranteed Array<String>, Gizmo) -> @owned Optional<AnyObject>
4343
// CHECK: return
4444
// CHECK: } // end sil function '$s8outliner13testOutliningyyF'
4545
public func testOutlining() {
@@ -142,17 +142,17 @@ public func testOutlining() {
142142
// CHECK: return %21 : $Optional<String>
143143
// CHECK: } // end sil function '$sSo5GizmoC12modifyString_10withNumber0D6FoobarSSSgAF_SiypSgtFToTembnnnb_'
144144

145-
// CHECK-LABEL: sil shared [noinline] @$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembnn_ : $@convention(thin) (@owned Array<String>, Gizmo) -> @owned Optional<AnyObject> {
145+
// CHECK-LABEL: sil shared [noinline] @$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembgnn_ : $@convention(thin) (@guaranteed Array<String>, Gizmo) -> @owned Optional<AnyObject> {
146146
// CHECK: bb0(%0 : $Array<String>, %1 : $Gizmo):
147147
// CHECK: %2 = objc_method %1 : $Gizmo, #Gizmo.doSomething!1.foreign : (Gizmo) -> ([String]?) -> Any?
148148
// CHECK: %3 = function_ref @$sSa10FoundationE19_bridgeToObjectiveCSo7NSArrayCyF : $@convention(method) <{{.*}}> (@guaranteed Array<{{.*}}>) -> @owned NSArray
149149
// CHECK: %4 = apply %3<String>(%0) : $@convention(method) <{{.*}}> (@guaranteed Array<{{.*}}>) -> @owned NSArray
150-
// CHECK: release_value %0 : $Array<String>
151-
// CHECK: %6 = enum $Optional<NSArray>, #Optional.some!enumelt.1, %4 : $NSArray
152-
// CHECK: %7 = apply %2(%6, %1) : $@convention(objc_method) (Optional<NSArray>, Gizmo) -> @autoreleased Optional<AnyObject>
150+
// CHECK-NOT: release_value
151+
// CHECK: %5 = enum $Optional<NSArray>, #Optional.some!enumelt.1, %4 : $NSArray
152+
// CHECK: %6 = apply %2(%5, %1) : $@convention(objc_method) (Optional<NSArray>, Gizmo) -> @autoreleased Optional<AnyObject>
153153
// CHECK: strong_release %4 : $NSArray
154-
// CHECK: return %7 : $Optional<AnyObject>
155-
// CHECK: } // end sil function '$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembnn_'
154+
// CHECK: return %6 : $Optional<AnyObject>
155+
// CHECK: } // end sil function '$sSo5GizmoC11doSomethingyypSgSaySSGSgFToTembgnn_'
156156

157157
public func dontCrash<T: Proto>(x : Gizmo2<T>) {
158158
let s = x.doSomething()

0 commit comments

Comments
 (0)