16
16
#include " swift/SIL/BranchPropagatedUser.h"
17
17
#include " swift/SIL/MemAccessUtils.h"
18
18
#include " swift/SIL/OwnershipUtils.h"
19
+ #include " swift/SILOptimizer/Utils/Local.h"
19
20
#include " swift/SIL/SILArgument.h"
20
21
#include " swift/SIL/SILBuilder.h"
21
22
#include " swift/SIL/SILInstruction.h"
@@ -121,8 +122,23 @@ static bool isConsumed(
121
122
122
123
namespace {
123
124
125
+ // / A two stage visitor that optimizes ownership instructions and eliminates any
126
+ // / trivially dead code that results after optimization. The two stages are used
127
+ // / to avoid iterator invalidation. Specifically:
128
+ // /
129
+ // / 1. We first process the CFG instruction by instruction, only eliminating
130
+ // / instructions that are guaranteed to be dominated by the visited
131
+ // / instrution. While we do that, we add the operands of any instruction that we
132
+ // / successfully optimize to the worklist. NOTE: We do not process arguments
133
+ // / here to get SSA guarantees around dominance.
134
+ // /
135
+ // / 2. Once we have processed the CFG and done some initial optimization, we
136
+ // / enter phase 2 where we process the worklist. Here we are allowed to process
137
+ // / arbitrary values and instructions, removing things that we are erasing from
138
+ // / the worklist before we delete them.
124
139
struct SemanticARCOptVisitor
125
140
: SILInstructionVisitor<SemanticARCOptVisitor, bool > {
141
+ SmallSetVector<SILValue, 32 > worklist;
126
142
SILFunction &F;
127
143
Optional<DeadEndBlocks> TheDeadEndBlocks;
128
144
@@ -133,17 +149,100 @@ struct SemanticARCOptVisitor
133
149
TheDeadEndBlocks.emplace (&F);
134
150
return *TheDeadEndBlocks;
135
151
}
136
-
152
+
153
+ // / Given a single value instruction, RAUW it with newValue, add newValue to
154
+ // / the worklist, and then call eraseInstruction on i.
155
+ void eraseAndRAUWSingleValueInstruction (SingleValueInstruction *i, SILValue newValue) {
156
+ worklist.insert (newValue);
157
+ i->replaceAllUsesWith (newValue);
158
+ eraseInstruction (i);
159
+ }
160
+
161
+ // / Add all operands of i to the worklist and then call eraseInstruction on
162
+ // / i. Assumes that the instruction doesnt have users.
163
+ void eraseInstructionAndAddOptsToWorklist (SILInstruction *i) {
164
+ // Then copy all operands into the worklist for future processing.
165
+ for (SILValue v : i->getOperandValues ()) {
166
+ worklist.insert (v);
167
+ }
168
+ eraseInstruction (i);
169
+ }
170
+
171
+ // / Remove all results of the given instruction from the worklist and then
172
+ // / erase the instruction. Assumes that the instruction does not have any
173
+ // / users left.
174
+ void eraseInstruction (SILInstruction *i) {
175
+ // Remove all SILValues of the instruction from the worklist and then erase
176
+ // the instruction.
177
+ for (SILValue result : i->getResults ()) {
178
+ worklist.remove (result);
179
+ }
180
+ i->eraseFromParent ();
181
+ }
182
+
183
+ // / The default visitor.
137
184
bool visitSILInstruction (SILInstruction *i) { return false ; }
138
185
bool visitCopyValueInst (CopyValueInst *cvi);
139
186
bool visitBeginBorrowInst (BeginBorrowInst *bbi);
140
187
bool visitLoadInst (LoadInst *li);
141
188
142
189
bool isWrittenTo (LoadInst *li);
190
+
191
+ bool processWorklist ();
192
+
193
+ bool performGuaranteedCopyValueOptimization (CopyValueInst *cvi);
194
+ bool eliminateDeadLiveRangeCopyValue (CopyValueInst *cvi);
143
195
};
144
196
145
197
} // end anonymous namespace
146
198
199
+ bool SemanticARCOptVisitor::processWorklist () {
200
+ // NOTE: The madeChange here is not strictly necessary since we only have
201
+ // items added to the worklist today if we have already made /some/ sort of
202
+ // change. That being said, I think there is a low cost to including this here
203
+ // and makes the algorithm more correct, visually and in the face of potential
204
+ // refactoring.
205
+ bool madeChange = false ;
206
+
207
+ while (!worklist.empty ()) {
208
+ SILValue next = worklist.pop_back_val ();
209
+
210
+ // First check if this is an instruction that is trivially dead. This can
211
+ // occur if we eliminate rr traffic resulting in dead projections and the
212
+ // like.
213
+ //
214
+ // If we delete, we first add all of our deleted instructions operands to
215
+ // the worklist and then remove all results (since we are going to delete
216
+ // the instruction).
217
+ if (auto *defInst = next->getDefiningInstruction ()) {
218
+ if (isInstructionTriviallyDead (defInst)) {
219
+ madeChange = true ;
220
+ recursivelyDeleteTriviallyDeadInstructions (
221
+ defInst, true /* force*/ ,
222
+ [&](SILInstruction *i) {
223
+ for (SILValue operand : i->getOperandValues ()) {
224
+ worklist.insert (operand);
225
+ }
226
+ for (SILValue result : i->getResults ()) {
227
+ worklist.remove (result);
228
+ }
229
+ ++NumEliminatedInsts;
230
+ });
231
+ continue ;
232
+ }
233
+ }
234
+
235
+ // Otherwise, if we have a single value instruction (to be expanded later
236
+ // perhaps), try to visit that value recursively.
237
+ if (auto *svi = dyn_cast<SingleValueInstruction>(next)) {
238
+ madeChange |= visit (svi);
239
+ continue ;
240
+ }
241
+ }
242
+
243
+ return madeChange;
244
+ }
245
+
147
246
bool SemanticARCOptVisitor::visitBeginBorrowInst (BeginBorrowInst *bbi) {
148
247
auto kind = bbi->getOperand ().getOwnershipKind ();
149
248
SmallVector<EndBorrowInst *, 16 > endBorrows;
@@ -167,11 +266,11 @@ bool SemanticARCOptVisitor::visitBeginBorrowInst(BeginBorrowInst *bbi) {
167
266
// begin borrow and end borrows.
168
267
while (!endBorrows.empty ()) {
169
268
auto *ebi = endBorrows.pop_back_val ();
170
- ebi-> eraseFromParent ( );
269
+ eraseInstruction (ebi );
171
270
++NumEliminatedInsts;
172
271
}
173
- bbi-> replaceAllUsesWith (bbi-> getOperand ());
174
- bbi-> eraseFromParent ( );
272
+
273
+ eraseAndRAUWSingleValueInstruction ( bbi, bbi-> getOperand () );
175
274
++NumEliminatedInsts;
176
275
return true ;
177
276
}
@@ -216,7 +315,7 @@ static bool canHandleOperand(SILValue operand, SmallVectorImpl<SILValue> &out) {
216
315
// are within the borrow scope.
217
316
//
218
317
// TODO: This needs a better name.
219
- static bool performGuaranteedCopyValueOptimization (CopyValueInst *cvi) {
318
+ bool SemanticARCOptVisitor:: performGuaranteedCopyValueOptimization (CopyValueInst *cvi) {
220
319
SmallVector<SILValue, 16 > borrowIntroducers;
221
320
222
321
// Whitelist the operands that we know how to support and make sure
@@ -250,11 +349,11 @@ static bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi) {
250
349
// within the guaranteed value scope. First delete the destroys/copies.
251
350
while (!destroys.empty ()) {
252
351
auto *dvi = destroys.pop_back_val ();
253
- dvi-> eraseFromParent ( );
352
+ eraseInstruction (dvi );
254
353
++NumEliminatedInsts;
255
354
}
256
- cvi-> replaceAllUsesWith (cvi-> getOperand ());
257
- cvi-> eraseFromParent ( );
355
+
356
+ eraseAndRAUWSingleValueInstruction ( cvi, cvi-> getOperand () );
258
357
259
358
// Then change all of our guaranteed forwarding insts to have guaranteed
260
359
// ownership kind instead of what ever they previously had (ignoring trivial
@@ -302,12 +401,12 @@ static bool performGuaranteedCopyValueOptimization(CopyValueInst *cvi) {
302
401
303
402
// / If cvi only has destroy value users, then cvi is a dead live range. Lets
304
403
// / eliminate all such dead live ranges.
305
- static bool eliminateDeadLiveRangeCopyValue (CopyValueInst *cvi) {
404
+ bool SemanticARCOptVisitor:: eliminateDeadLiveRangeCopyValue (CopyValueInst *cvi) {
306
405
// See if we are lucky and have a simple case.
307
406
if (auto *op = cvi->getSingleUse ()) {
308
407
if (auto *dvi = dyn_cast<DestroyValueInst>(op->getUser ())) {
309
- dvi-> eraseFromParent ( );
310
- cvi-> eraseFromParent ( );
408
+ eraseInstruction (dvi );
409
+ eraseInstructionAndAddOptsToWorklist (cvi );
311
410
NumEliminatedInsts += 2 ;
312
411
return true ;
313
412
}
@@ -331,10 +430,10 @@ static bool eliminateDeadLiveRangeCopyValue(CopyValueInst *cvi) {
331
430
332
431
// Now that we have a truly dead live range copy value, eliminate it!
333
432
while (!destroys.empty ()) {
334
- destroys.pop_back_val ()-> eraseFromParent ( );
433
+ eraseInstruction ( destroys.pop_back_val ());
335
434
++NumEliminatedInsts;
336
435
}
337
- cvi-> eraseFromParent ( );
436
+ eraseInstructionAndAddOptsToWorklist (cvi );
338
437
++NumEliminatedInsts;
339
438
return true ;
340
439
}
@@ -513,12 +612,11 @@ bool SemanticARCOptVisitor::visitLoadInst(LoadInst *li) {
513
612
while (!destroyValues.empty ()) {
514
613
auto *dvi = destroyValues.pop_back_val ();
515
614
SILBuilderWithScope (dvi).createEndBorrow (dvi->getLoc (), lbi);
516
- dvi-> eraseFromParent ( );
615
+ eraseInstruction (dvi );
517
616
++NumEliminatedInsts;
518
617
}
519
618
520
- li->replaceAllUsesWith (lbi);
521
- li->eraseFromParent ();
619
+ eraseAndRAUWSingleValueInstruction (li, lbi);
522
620
++NumEliminatedInsts;
523
621
++NumLoadCopyConvertedToLoadBorrow;
524
622
return true ;
@@ -542,10 +640,11 @@ struct SemanticARCOpts : SILFunctionTransform {
542
640
" Can not perform semantic arc optimization unless ownership "
543
641
" verification is enabled" );
544
642
545
- // Iterate over all of the arguments, performing small peephole
546
- // ARC optimizations.
547
- //
548
- // FIXME: Should we iterate or use a RPOT order here?
643
+ // Iterate over all of the arguments, performing small peephole ARC
644
+ // optimizations. We assume that the visitor will add any instructions we
645
+ // need to recursively to the visitor's worklist. Also, note that we assume
646
+ // that we do not look through /any/ sil block arguments here since our
647
+ // iteration here is only valid up to SSA.
549
648
bool madeChange = false ;
550
649
551
650
SemanticARCOptVisitor visitor (f);
@@ -584,6 +683,15 @@ struct SemanticARCOpts : SILFunctionTransform {
584
683
madeChange |= visitor.visit (&*ii);
585
684
}
586
685
686
+ // Finally drain the worklist on the visitor and process until we reach the
687
+ // fixpoint and thus do not have any further work to do.
688
+ //
689
+ // NOTE: At this point madeChange has already been set to true if we have
690
+ // anything in the worklist, so technically we do not need to do this. But I
691
+ // would rather represent this state to future proof the pass to be
692
+ // "visually" correct.
693
+ madeChange |= visitor.processWorklist ();
694
+
587
695
if (madeChange) {
588
696
invalidateAnalysis (SILAnalysis::InvalidationKind::Instructions);
589
697
}
0 commit comments