-
Notifications
You must be signed in to change notification settings - Fork 10.5k
/
Copy pathDestroyHoisting.cpp
803 lines (705 loc) · 28.9 KB
/
DestroyHoisting.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
//===--- DestroyHoisting.cpp - Hoisting of address destroys ---------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "sil-destroy-hoisting"
#include "swift/SIL/MemoryLocations.h"
#include "swift/SIL/BitDataflow.h"
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
#include "swift/SILOptimizer/PassManager/Transforms.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "llvm/Support/Debug.h"
using namespace swift;
namespace {
//===----------------------------------------------------------------------===//
// DestroyHoisting
//===----------------------------------------------------------------------===//
/// Hoist destroy_addr instructions up the control flow.
///
/// This has two benefits:
///
/// * Combine a destroy_addr with a preceding copy. For example, replace
/// /code
/// %v = load [copy] %a
/// ...
/// destroy_addr %a
/// /endcode
/// with
/// /code
/// %v = load [take] %a
/// ...
/// /endcode
/// * Shorten the lifetime of memory-values. This can avoid copy-on-write
/// operations. Specifically, optimize the enum payload in-place modification
/// pattern:
/// /code
/// // inside a mutating enum member function
/// switch self {
/// case .A(var cow_container)
/// cow_container.modify()
/// self = .A(cow_container)
/// ...
/// }
/// /endcode
/// DestroyHoisting moves the destroy-part of the self assignment up so that
/// the cow_container is only single-referenced and will not trigger a copy-
/// on-write operation when calling modify().
///
/// Note that this optimization does _not_ try to split a destroy_addr of a
/// struct/tuple into destroys of its sub-locations. Also, vice versa, it does
/// not try to combine multiple destroy_addr of sub-locations into a single
/// destroy_addr of the aggregate.
class DestroyHoisting {
using Bits = MemoryLocations::Bits;
using BlockState = BitDataflow::BlockState;
SILFunction *function;
MemoryLocations locations;
/// A cache of generated address projection instructions. The key is the
/// location index.
llvm::DenseMap<unsigned, SingleValueInstruction *> addressProjections;
DominanceAnalysis *DA;
DominanceInfo *domTree = nullptr;
bool madeChanges = false;
void expandStores(BitDataflow &dataFlow);
void initDataflow(BitDataflow &dataFlow);
void initDataflowInBlock(SILBasicBlock *block, BlockState &state);
bool canIgnoreUnreachableBlock(SILBasicBlock *block,
BitDataflow &dataFlow);
int getDestroyedLoc(SILInstruction *I) {
if (auto *DAI = dyn_cast<DestroyAddrInst>(I))
return locations.getLocationIdx(DAI->getOperand());
return -1;
}
void getUsedLocationsOfAddr(Bits &bits, SILValue addr);
void getUsedLocationsOfOperands(Bits &bits, SILInstruction *I);
void getUsedLocationsOfInst(Bits &bits, SILInstruction *Inst);
void moveDestroys(BitDataflow &dataFlow);
bool locationOverlaps(const MemoryLocations::Location *loc,
const Bits &destroys);
void moveDestroysInBlock(SILBasicBlock *block, Bits &activeDestroys,
SmallVectorImpl<SILInstruction *> &toRemove);
void insertDestroys(Bits &toInsert, Bits &aliveDestroys,
SmallVectorImpl<SILInstruction *> &removeList,
SILInstruction *afterInst, SILBasicBlock *inBlock);
void insertDestroy(int locIdx, Bits &insertBits, SILInstruction *atInst);
bool combineWith(SILInstruction *inst, unsigned locIdx);
SILValue createAddress(unsigned locIdx, SILBuilder &builder);
void tailMerging(BitDataflow &dataFlow);
bool tailMergingInBlock(SILBasicBlock *block, BitDataflow &dataFlow);
public:
DestroyHoisting(SILFunction *function, DominanceAnalysis *DA) :
function(function),
// We currently don't handle enum and existential projections, because they
// cannot be re-created easily. We could support this in future.
locations(/*handleNonTrivialProjections*/ false,
/*handleTrivialLocations*/ false),
DA(DA) {}
bool hoistDestroys();
};
static bool isExpansionEnabler(SILInstruction *I) {
return isa<SwitchEnumInst>(I);
}
// A pre-pass which expands
// store %s to [assign] %a
// to
// destroy_addr %a
// store %s to [init] %a
//
// This is not a good thing in general. If we would do it unconditionally, it
// would result in some benchmark regressions. Therefore we only expand stores
// where we expect that we can move the destroys to or across a switch_enum.
// This enables the switch-enum optimization (see above).
// TODO: investigate the benchmark regressions and enable store-expansion more
// aggressively.
void DestroyHoisting::expandStores(BitDataflow &dataFlow) {
Bits usedLocs(locations.getNumLocations());
// Initialize the dataflow, which tells us which destroy_addr instructions are
// reachable through a switch_enum, without a use of the location in between.
// The gen-sets are initialized at all expansion-enabler instructions
// (currently only switch_enum instructions).
// The kill-sets are initialized with the used locations, because we would not
// move a destroy across a use.
bool expansionEnablerFound = false;
for (auto bs : dataFlow) {
bs.data.entrySet.reset();
bs.data.genSet.reset();
bs.data.killSet.reset();
bs.data.exitSet.reset();
for (SILInstruction &I : bs.block) {
if (isExpansionEnabler(&I)) {
expansionEnablerFound = true;
bs.data.genSet.set();
bs.data.killSet.reset();
}
usedLocs.reset();
getUsedLocationsOfInst(usedLocs, &I);
bs.data.genSet.reset(usedLocs);
bs.data.killSet |= usedLocs;
}
}
if (!expansionEnablerFound)
return;
// Solve the dataflow, which tells us if a destroy_addr is reachable from
// a expansion-enabler on _any_ path (therefore "Union" and not "Intersect")
// without hitting a use of that location.
dataFlow.solveForwardWithUnion();
Bits activeLocs(locations.getNumLocations());
for (auto bs : dataFlow) {
activeLocs = bs.data.entrySet;
for (SILInstruction &I : bs.block) {
if (isExpansionEnabler(&I)) {
// Set all bits: an expansion-enabler enables expansion for all
// locations.
activeLocs.set();
}
if (auto *SI = dyn_cast<StoreInst>(&I)) {
if (SI->getOwnershipQualifier() == StoreOwnershipQualifier::Assign) {
int locIdx = locations.getLocationIdx(SI->getDest());
if (locIdx >= 0 && activeLocs.test(locIdx)) {
// Expand the store.
SILBuilder builder(SI);
builder.createDestroyAddr(SI->getLoc(), SI->getDest());
SI->setOwnershipQualifier(StoreOwnershipQualifier::Init);
madeChanges = true;
}
}
}
// Clear the bits of used locations.
usedLocs.reset();
getUsedLocationsOfInst(usedLocs, &I);
activeLocs.reset(usedLocs);
}
}
}
// Initialize the dataflow for moving destroys up the control flow.
void DestroyHoisting::initDataflow(BitDataflow &dataFlow) {
for (auto bs : dataFlow) {
bs.data.genSet.reset();
bs.data.killSet.reset();
if (bs.data.isInInfiniteLoop()) {
// Ignore blocks which are in an infinite loop and prevent any destroy
// hoisting across such block borders.
bs.data.entrySet.reset();
bs.data.exitSet.reset();
continue;
}
bs.data.entrySet.set();
if (isa<UnreachableInst>(bs.block.getTerminator())) {
if (canIgnoreUnreachableBlock(&bs.block, dataFlow)) {
bs.data.exitSet.set();
} else {
bs.data.exitSet.reset();
}
} else if (bs.block.getTerminator()->isFunctionExiting()) {
bs.data.exitSet.reset();
} else {
bs.data.exitSet.set();
}
initDataflowInBlock(&bs.block, bs.data);
}
}
// Initialize the gen- and kill-sets.
// It's a backward dataflow problem. A bit <n> in the genSet means: we have seen
// a destroy_addr of location <n> (when walking backwards).
// As we are not trying to split or combine destroy_addr instructions, a
// destroy_addr just sets its "self" bit and not the location's subLocations
// set (this is different compared to the dataflow in MemoryLifetimeVerifier).
void DestroyHoisting::initDataflowInBlock(SILBasicBlock *block,
BlockState &state) {
Bits usedLocs(state.entrySet.size());
for (SILInstruction &I : llvm::reverse(*block)) {
usedLocs.reset();
getUsedLocationsOfInst(usedLocs, &I);
state.genSet.reset(usedLocs);
state.killSet |= usedLocs;
// For terminators clear the bits in the exit set instead in the genSet,
// because we cannot insert destroy_addrs after the terminator in the block.
if (isa<TermInst>(&I))
state.exitSet.reset(usedLocs);
int destroyedLoc = getDestroyedLoc(&I);
if (destroyedLoc >= 0) {
state.killSet.reset(destroyedLoc);
state.genSet.set(destroyedLoc);
}
}
}
// Handling blocks which eventually end up in an unreachable is surprisingly
// complicated, because without special treatment it would block destroy-
// hoisting in the main control flow. Example:
//
// bb1:
// cond_br %error_condition, bb2, bb3
// bb2:
// // some unrelated error processing, which doesn't use any locations
// unreachable
// bb3:
// // continue with main control flow
// destroy_addr %some_location
//
// We have to initialize the exit-set of bb2 with zeroes. Otherwise we would
// insert wrong destroys in case there are any location-uses in the unreachable-
// block (or sub-graph).
// But having a zero-set at the bb2-entry would block hoisting of the
// destroy_addr from bb3 into bb1.
// Therefore we special case the common scenario of a single block with
// unreachable which does not touch any of our memory locations. We can just
// ignore those blocks.
bool DestroyHoisting::canIgnoreUnreachableBlock(SILBasicBlock *block,
BitDataflow &dataFlow) {
assert(isa<UnreachableInst>(block->getTerminator()));
// Is it a single unreachable-block (i.e. it has a single predecessor from
// which there is a path to the function exit)?
SILBasicBlock *singlePred = block->getSinglePredecessorBlock();
if (!singlePred)
return false;
if (!dataFlow[singlePred].exitReachable())
return false;
// Check if none of the locations are touched in the unreachable-block.
for (SILInstruction &I : *block) {
if (isa<EndBorrowInst>(&I))
return false;
for (Operand &op : I.getAllOperands()) {
if (locations.getLocation(op.get()))
return false;
}
}
return true;
}
void DestroyHoisting::getUsedLocationsOfAddr(Bits &bits, SILValue addr) {
if (auto *loc = locations.getLocation(addr)) {
// destroy_addr locations are "killed" by parent _and_ sublocations. In
// other words: a destroy_addr must not be moved across an instruction which
// uses the location itself, an aggregate containing the location, or a
// sub-field of the location.
bits |= loc->selfAndParents;
bits |= loc->subLocations;
}
}
void DestroyHoisting::getUsedLocationsOfOperands(Bits &bits, SILInstruction *I) {
for (Operand &op : I->getAllOperands()) {
getUsedLocationsOfAddr(bits, op.get());
}
}
// NOTE: All instructions handled in
// MemoryLocations::analyzeLocationUsesRecursively should also be handled
// explicitly here.
// Set all bits of locations which instruction \p I is using.
// It's including parent and sub-locations (see comment in
// getUsedLocationsOfAddr).
void DestroyHoisting::getUsedLocationsOfInst(Bits &bits, SILInstruction *I) {
switch (I->getKind()) {
case SILInstructionKind::EndBorrowInst: {
auto op = cast<EndBorrowInst>(I)->getOperand();
if (auto *LBI = dyn_cast<LoadBorrowInst>(op)) {
getUsedLocationsOfAddr(bits, LBI->getOperand());
}
break;
}
case SILInstructionKind::EndApplyInst:
// Operands passed to begin_apply are alive throughout an end_apply ...
getUsedLocationsOfOperands(bits, cast<EndApplyInst>(I)->getBeginApply());
break;
case SILInstructionKind::AbortApplyInst:
// ... or abort_apply.
getUsedLocationsOfOperands(bits, cast<AbortApplyInst>(I)->getBeginApply());
break;
case SILInstructionKind::EndAccessInst:
getUsedLocationsOfOperands(bits,
cast<EndAccessInst>(I)->getBeginAccess());
break;
// These instructions do not access the memory location for read/write
case SILInstructionKind::StructElementAddrInst:
case SILInstructionKind::TupleElementAddrInst:
case SILInstructionKind::WitnessMethodInst:
case SILInstructionKind::OpenExistentialAddrInst:
break;
case SILInstructionKind::InitExistentialAddrInst:
case SILInstructionKind::InitEnumDataAddrInst:
case SILInstructionKind::UncheckedTakeEnumDataAddrInst:
case SILInstructionKind::SelectEnumAddrInst:
case SILInstructionKind::ExistentialMetatypeInst:
case SILInstructionKind::ValueMetatypeInst:
case SILInstructionKind::IsUniqueInst:
case SILInstructionKind::FixLifetimeInst:
case SILInstructionKind::LoadInst:
case SILInstructionKind::StoreInst:
case SILInstructionKind::StoreBorrowInst:
case SILInstructionKind::CopyAddrInst:
case SILInstructionKind::InjectEnumAddrInst:
case SILInstructionKind::UncheckedRefCastAddrInst:
case SILInstructionKind::UnconditionalCheckedCastAddrInst:
case SILInstructionKind::CheckedCastAddrBranchInst:
case SILInstructionKind::PartialApplyInst:
case SILInstructionKind::ApplyInst:
case SILInstructionKind::TryApplyInst:
case SILInstructionKind::YieldInst:
case SILInstructionKind::SwitchEnumAddrInst:
getUsedLocationsOfOperands(bits, I);
break;
case SILInstructionKind::DebugValueInst:
case SILInstructionKind::DestroyAddrInst:
// destroy_addr and debug_value are handled specially.
break;
default:
break;
}
}
static void processRemoveList(SmallVectorImpl<SILInstruction *> &toRemove) {
for (SILInstruction *I : toRemove) {
I->eraseFromParent();
}
toRemove.clear();
}
// Do the actual moving of destroy_addr instructions.
void DestroyHoisting::moveDestroys(BitDataflow &dataFlow) {
// Don't eagerly delete destroy_addr instructions, instead put them into this
// list. When we are about to "move" a destroy_addr just over some sideeffect-
// free instructions, we'll keep it at the current location.
llvm::SmallVector<SILInstruction *, 8> toRemove;
Bits activeDestroys(locations.getNumLocations());
for (auto bs : dataFlow) {
// Is it an unreachable-block we can ignore?
if (isa<UnreachableInst>(bs.block.getTerminator()) && bs.data.exitSet.any())
continue;
// Ignore blocks which are in an infinite loop.
if (bs.data.isInInfiniteLoop())
continue;
// Do the inner-block processing.
activeDestroys = bs.data.exitSet;
moveDestroysInBlock(&bs.block, activeDestroys, toRemove);
assert(activeDestroys == bs.data.entrySet);
if (bs.block.pred_empty()) {
// The function entry block: insert all destroys which are still active.
insertDestroys(activeDestroys, activeDestroys, toRemove,
nullptr, &bs.block);
} else if (SILBasicBlock *pred = bs.block.getSinglePredecessorBlock()) {
// Insert destroys which are active at the entry of this block, but not
// on another successor block of the predecessor.
Bits usedLocs = activeDestroys;
usedLocs.reset(dataFlow[pred].exitSet);
insertDestroys(usedLocs, activeDestroys, toRemove, nullptr, &bs.block);
}
// Note that this condition relies on not having critical edges in the CFG.
assert(std::all_of(bs.block.getPredecessorBlocks().begin(),
bs.block.getPredecessorBlocks().end(),
[&](SILBasicBlock *P) {
return activeDestroys == dataFlow[P].exitSet;
}));
// Delete all destroy_addr and debug_value which are scheduled for
// removal.
processRemoveList(toRemove);
}
}
bool DestroyHoisting::locationOverlaps(const MemoryLocations::Location *loc,
const Bits &destroys) {
// We cannot just check if 'loc->subLocations' has any common bits with
// 'destroys', because subLocations don't include the "self" bit if all sub
// locations cover the whole location.
for (int subIdx = loc->subLocations.find_first(); subIdx >= 0;
subIdx = loc->subLocations.find_next(subIdx)) {
if (destroys.anyCommon(locations.getLocation(subIdx)->selfAndParents))
return true;
}
return false;
}
void DestroyHoisting::moveDestroysInBlock(
SILBasicBlock *block, Bits &activeDestroys,
SmallVectorImpl<SILInstruction *> &toRemove) {
Bits usedLocs(locations.getNumLocations());
assert(toRemove.empty());
for (SILInstruction &I : llvm::reverse(*block)) {
usedLocs.reset();
getUsedLocationsOfInst(usedLocs, &I);
usedLocs &= activeDestroys;
insertDestroys(usedLocs, activeDestroys, toRemove, &I, block);
int destroyedLoc = getDestroyedLoc(&I);
if (destroyedLoc >= 0) {
activeDestroys.set(destroyedLoc);
toRemove.push_back(&I);
} else if (auto *DV = DebugValueInst::hasAddrVal(&I)) {
// debug_value w/ address value does not count as real use of a location.
// If we are moving a destroy_addr above a debug_value, just delete that
// debug_value.
auto *dvaLoc = locations.getLocation(DV->getOperand());
if (dvaLoc && locationOverlaps(dvaLoc, activeDestroys))
toRemove.push_back(DV);
} else if (I.mayHaveSideEffects()) {
// Delete all destroy_addr and debug_value which are scheduled for
// removal.
processRemoveList(toRemove);
}
}
}
// Insert destroy_addr which are in \p toInsert. Also update \p activeDestroys.
// But exclude instruction from \p removeList (see below).
// If \p afterInst is null, insert the destroys at the begin of \p inBlock.
void DestroyHoisting::insertDestroys(Bits &toInsert, Bits &activeDestroys,
SmallVectorImpl<SILInstruction *> &removeList,
SILInstruction *afterInst, SILBasicBlock *inBlock) {
if (toInsert.none())
return;
// The removeList contains destroy_addr (and debug_value) instructions
// which we want to delete, but we didn't see any side-effect instructions
// since then. There is no value in moving a destroy_addr over side-effect-
// free instructions (it could even trigger creating redundant address
// projections).
// Therefore we remove all such destroy_addr from removeList, i.e. we will
// keep them at their original locations and will not move them.
Bits keepDestroyedLocs(toInsert.size());
auto end = std::remove_if(removeList.begin(), removeList.end(),
[&](SILInstruction *I){
int destroyedLoc = getDestroyedLoc(I);
if (destroyedLoc >= 0 && toInsert.test(destroyedLoc)) {
keepDestroyedLocs.set(destroyedLoc);
toInsert.reset(destroyedLoc);
activeDestroys.reset(destroyedLoc);
return true;
}
if (auto *DV = DebugValueInst::hasAddrVal(I)) {
// Also keep debug_value instructions, located before a
// destroy_addr which we won't move.
auto *dvaLoc = locations.getLocation(DV->getOperand());
if (dvaLoc && dvaLoc->selfAndParents.anyCommon(keepDestroyedLocs))
return true;
}
return false;
});
removeList.erase(end, removeList.end());
if (toInsert.none())
return;
SILInstruction *insertionPoint =
(afterInst ? &*std::next(afterInst->getIterator()) : &*inBlock->begin());
SILBuilder builder(insertionPoint);
SILLocation loc = CleanupLocation(insertionPoint->getLoc());
// Insert destroy_addr instructions for all bits in toInsert.
for (int locIdx = toInsert.find_first(); locIdx >= 0;
locIdx = toInsert.find_next(locIdx)) {
if (!combineWith(afterInst, locIdx)) {
SILValue addr = createAddress(locIdx, builder);
builder.createDestroyAddr(loc, addr);
}
activeDestroys.reset(locIdx);
}
madeChanges = true;
}
// Instead of inserting a destroy_addr, try to combine it with \p inst, in case
// \p inst is a load [copy] or a non taking copy_addr.
// We can just convert the load/copy_addr to a taking load/copy_addr.
bool DestroyHoisting::combineWith(SILInstruction *inst, unsigned locIdx) {
if (!inst)
return false;
switch (inst->getKind()) {
case SILInstructionKind::LoadInst: {
auto *LI = cast<LoadInst>(inst);
int srcIdx = locations.getLocationIdx(LI->getOperand());
if (srcIdx == (int)locIdx) {
assert(LI->getOwnershipQualifier() == LoadOwnershipQualifier::Copy);
LI->setOwnershipQualifier(LoadOwnershipQualifier::Take);
return true;
}
break;
}
case SILInstructionKind::CopyAddrInst: {
auto *CAI = cast<CopyAddrInst>(inst);
int srcIdx = locations.getLocationIdx(CAI->getSrc());
if (srcIdx == (int)locIdx) {
assert(!CAI->isTakeOfSrc());
CAI->setIsTakeOfSrc(IsTake);
return true;
}
break;
}
default:
break;
}
return false;
}
// Create an address projection for the sub-location \p locIdx, in case the
// representativeValue is a begin_access or does not dominate the insertion
// point of \p builder.
SILValue DestroyHoisting::createAddress(unsigned locIdx, SILBuilder &builder) {
auto *loc = locations.getLocation(locIdx);
if (loc->parentIdx < 0)
return loc->representativeValue;
assert(!isa<BeginAccessInst>(loc->representativeValue) &&
"only a root location can be a begin_access");
if (!domTree)
domTree = DA->get(function);
SILInstruction *ip = &*builder.getInsertionPoint();
SingleValueInstruction *&cachedProj = addressProjections[locIdx];
if (cachedProj && domTree->properlyDominates(cachedProj, ip))
return cachedProj;
auto *projInst = cast<SingleValueInstruction>(loc->representativeValue);
if (domTree->properlyDominates(projInst, ip)) {
cachedProj = projInst;
return projInst;
}
// Recursively create (or get) the address of the parent location.
SILValue baseAddr = createAddress(loc->parentIdx, builder);
// Insert the new projection instruction right below the parent address.
// This ensures that it dominates the builder's insertion point.
SILBuilder projBuilder(baseAddr->getParentBlock()->begin());
if (auto *addrInst = dyn_cast<SingleValueInstruction>(baseAddr))
projBuilder.setInsertionPoint(std::next(addrInst->getIterator()));
SingleValueInstruction *newProj;
if (auto *SEA = dyn_cast<StructElementAddrInst>(projInst)) {
newProj = projBuilder.createStructElementAddr(SEA->getLoc(), baseAddr,
SEA->getField(), SEA->getType());
} else {
auto *TEA = dyn_cast<TupleElementAddrInst>(projInst);
newProj = projBuilder.createTupleElementAddr(TEA->getLoc(), baseAddr,
TEA->getFieldIndex(), TEA->getType());
}
assert(domTree->properlyDominates(newProj, ip) &&
"new projection does not dominate insert point");
// We need to remember the new projection instruction because in tailMerging
// we might call locations.getLocationIdx() on such a new instruction.
locations.registerProjection(newProj, locIdx);
cachedProj = newProj;
return newProj;
}
// Perform a simple tail merging optimization: at control flow merges, move
// identical destroy_addr instructions down to the successor block.
// The hoisting optimization can create such situations, e.g.
// \code
// bb1:
// // use of location %a
// br bb3
// bb2:
// br bb3
// bb3:
// destroy_addr %a // will be hoisted (duplicated) into bb2 and bb2
// \endcode
// This is mainly a code size reduction optimization.
void DestroyHoisting::tailMerging(BitDataflow &dataFlow) {
// TODO: we could do a worklist algorithm here instead of iterating through
// all the function blocks.
bool changed = false;
do {
changed = false;
for (SILBasicBlock &block : *function) {
changed |= tailMergingInBlock(&block, dataFlow);
}
} while (changed);
}
bool DestroyHoisting::tailMergingInBlock(SILBasicBlock *block,
BitDataflow &dataFlow) {
if (block->pred_empty() || block->getSinglePredecessorBlock())
return false;
BlockState &state = dataFlow[block];
// Only if the entry set of the block has some bit sets, it's even possible
// that hoisting has moved destroy_addr up to the predecessor blocks.
if (state.entrySet.empty())
return false;
Bits canHoist = state.entrySet;
Bits destroysInPred(canHoist.size());
Bits killedLocs(canHoist.size());
// Collect all common destroy_addr of the predecessor blocks.
for (SILBasicBlock *pred : block->getPredecessorBlocks()) {
destroysInPred.reset();
killedLocs.reset();
for (SILInstruction &I : llvm::reverse(*pred)) {
int destroyedLoc = getDestroyedLoc(&I);
if (destroyedLoc >= 0 && !killedLocs.test(destroyedLoc))
destroysInPred.set(destroyedLoc);
getUsedLocationsOfInst(killedLocs, &I);
if (!canHoist.test(killedLocs))
break;
}
canHoist &= destroysInPred;
}
if (canHoist.none())
return false;
// Create the common destroy_addr at the block entry.
SILBuilder builder(&*block->begin());
SILLocation loc = RegularLocation(block->begin()->getLoc());
for (int locIdx = canHoist.find_first(); locIdx >= 0;
locIdx = canHoist.find_next(locIdx)) {
SILValue addr = createAddress(locIdx, builder);
builder.createDestroyAddr(loc, addr);
}
// Remove the common destroy_addr from the predecessor blocks.
for (SILBasicBlock *pred : block->getPredecessorBlocks()) {
destroysInPred = canHoist;
SILInstruction *I = pred->getTerminator();
while (destroysInPred.any()) {
assert(I && "not all destroys fround in predecessor block");
SILInstruction *nextI = (I == &pred->front() ?
nullptr : &*std::prev(I->getIterator()));
int destroyedLoc = getDestroyedLoc(I);
if (destroyedLoc >= 0 && destroysInPred.test(destroyedLoc)) {
destroysInPred.reset(destroyedLoc);
I->eraseFromParent();
}
I = nextI;
}
}
return true;
}
bool DestroyHoisting::hoistDestroys() {
locations.analyzeLocations(function);
if (locations.getNumLocations() > 0) {
BitDataflow dataFlow(function, locations.getNumLocations());
dataFlow.exitReachableAnalysis();
// Step 1: pre-processing: expand store instructions
expandStores(dataFlow);
// Step 2: hoist destroy_addr instructions.
// (reuse dataFlow to avoid re-allocating all the data structures)
initDataflow(dataFlow);
dataFlow.solveBackwardWithIntersect();
moveDestroys(dataFlow);
// Step 3: post-processing: tail merge inserted destroy_addr instructions.
tailMerging(dataFlow);
addressProjections.clear();
}
// Finally: handle alloc_stack locations within blocks (no need for store-
// expansion and tail-merging for such locations).
locations.handleSingleBlockLocations([this](SILBasicBlock *block) {
llvm::SmallVector<SILInstruction *, 8> toRemove;
Bits activeDestroys(locations.getNumLocations());
moveDestroysInBlock(block, activeDestroys, toRemove);
addressProjections.clear();
assert(activeDestroys.none());
assert(toRemove.empty());
});
return madeChanges;
}
//===----------------------------------------------------------------------===//
// DestroyHoistingPass
//===----------------------------------------------------------------------===//
class DestroyHoistingPass : public SILFunctionTransform {
public:
DestroyHoistingPass() {}
/// The entry point to the transformation.
void run() override {
SILFunction *F = getFunction();
if (!F->shouldOptimize())
return;
if (!F->hasOwnership())
return;
// If we are not supposed to perform ossa optimizations, bail.
if (!F->getModule().getOptions().EnableOSSAOptimizations)
return;
LLVM_DEBUG(llvm::dbgs() << "*** DestroyHoisting on function: "
<< F->getName() << " ***\n");
DominanceAnalysis *DA = PM->getAnalysis<DominanceAnalysis>();
DestroyHoisting CM(F, DA);
bool InstChanged = CM.hoistDestroys();
if (InstChanged) {
// We moved instructions.
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
}
}
};
} // end anonymous namespace
SILTransform *swift::createDestroyHoisting() {
return new DestroyHoistingPass();
}