-
Notifications
You must be signed in to change notification settings - Fork 10.5k
/
Copy pathCanonicalOSSALifetime.cpp
1063 lines (996 loc) · 39.6 KB
/
CanonicalOSSALifetime.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
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//===--- CanonicalOSSALifetime.cpp - Canonicalize OSSA value lifetimes ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2020 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
//
//===----------------------------------------------------------------------===//
///
/// This top-level API rewrites the extended lifetime of a SILValue:
///
/// bool CanonicalizeOSSALifetime::canonicalizeValueLifetime(SILValue def)
///
/// Each time it's called on a single OSSA value, `def`, it performs three
/// steps:
///
/// 1. Compute "pruned" liveness of def and its copies, ignoring original
/// destroys. Initializes `liveness`.
///
/// 2. Find `def`s final destroy points based on its pruned
/// liveness. Initializes `consumes` and inserts new destroy_value
/// instructions.
///
/// 3. Rewrite `def`s original copies and destroys, inserting new copies where
/// needed. Deletes original copies and destroys and inserts new copies.
///
/// See CanonicalOSSALifetime.h for examples.
///
/// TODO: Enable canonical-ossa-rewrite-borrows to rewrite single-block borrows.
/// Add support for multi-block borrows, load_borrow, and phi by using
/// persistentCopies.
///
/// TODO: If all client passes can maintain block numbers, then the
/// SmallDenseMaps/SetVectors can be replaced with bitsets/sparsesets.
///
//===----------------------------------------------------------------------===//
#define DEBUG_TYPE "copy-propagation"
#include "swift/SILOptimizer/Utils/CanonicalOSSALifetime.h"
#include "swift/SIL/InstructionUtils.h"
#include "swift/SIL/OwnershipUtils.h"
#include "swift/SILOptimizer/Utils/CFGOptUtils.h"
#include "swift/SILOptimizer/Utils/InstOptUtils.h"
#include "swift/SILOptimizer/Utils/ValueLifetime.h"
#include "llvm/ADT/Statistic.h"
using namespace swift;
using llvm::SmallSetVector;
STATISTIC(NumCopiesEliminated, "number of copy_value instructions removed");
STATISTIC(NumDestroysEliminated,
"number of destroy_value instructions removed");
STATISTIC(NumCopiesGenerated, "number of copy_value instructions created");
STATISTIC(NumDestroysGenerated, "number of destroy_value instructions created");
STATISTIC(NumUnknownUsers, "number of functions with unknown users");
/// This use-def walk must be consistent with the def-use walks performed
/// within the canonicalizeValueLifetime() implementation.
SILValue CanonicalizeOSSALifetime::getCanonicalCopiedDef(SILValue v) {
while (auto *copy = dyn_cast<CopyValueInst>(v)) {
auto def = copy->getOperand();
if (def.getOwnershipKind() == OwnershipKind::Owned) {
v = def;
continue;
}
if (auto borrowedVal = BorrowedValue(def)) {
// Any def's that aren't filtered out here must be handled by
// computeBorrowLiveness.
switch (borrowedVal.kind) {
case BorrowedValueKind::Invalid:
llvm_unreachable("Using invalid case?!");
case BorrowedValueKind::SILFunctionArgument:
return def;
case BorrowedValueKind::BeginBorrow: {
// TODO: Remove this call to visitLocalScopeEndingUses and the
// same-block check once computeBorrowLiveness supports multiple blocks.
auto *defBB = def->getParentBlock();
if (borrowedVal.visitLocalScopeEndingUses(
[&](Operand *endBorrow) {
return endBorrow->getUser()->getParent() == defBB;
})) {
return def;
}
break;
}
case BorrowedValueKind::LoadBorrow:
case BorrowedValueKind::Phi:
break;
}
}
// This guaranteed value cannot be handled, treat the copy as an owned
// live range def instead.
return copy;
}
return v;
}
/// The lifetime extends beyond given consuming use. Copy the value.
///
/// This can set the operand value, but cannot invalidate the use iterator.
static void copyLiveUse(Operand *use) {
SILInstruction *user = use->getUser();
SILBuilderWithScope B(user->getIterator());
auto loc = RegularLocation::getAutoGeneratedLocation(user->getLoc());
auto *copy = B.createCopyValue(loc, use->get());
use->set(copy);
++NumCopiesGenerated;
LLVM_DEBUG(llvm::dbgs() << " Copying at last use " << *copy);
}
//===----------------------------------------------------------------------===//
// MARK: Rewrite borrow scopes
//===----------------------------------------------------------------------===//
llvm::cl::opt<bool>
EnableRewriteBorrows("canonical-ossa-rewrite-borrows", llvm::cl::init(false),
llvm::cl::desc("Enable rewriting borrow scopes"));
bool CanonicalizeOSSALifetime::computeBorrowLiveness() {
auto borrowedVal = BorrowedValue(currentDef);
if (!borrowedVal) {
return false;
}
switch (borrowedVal.kind) {
case BorrowedValueKind::Invalid:
llvm_unreachable("Used invalid");
case BorrowedValueKind::SILFunctionArgument:
// For efficiency, function arguments skip liveness.
return true;
case BorrowedValueKind::LoadBorrow:
case BorrowedValueKind::Phi:
// TODO: Canonicalize load_borrow scope and phi once consolidateBorrowScope
// can handle persistentCopies.
return false;
case BorrowedValueKind::BeginBorrow:
break;
}
if (!EnableRewriteBorrows) {
return false;
}
// Note that there is no need to look through any reborrows. The reborrowed
// value is considered a separate lifetime for canonicalization. Any copies of
// the reborrowed value will not be rewritten when canonicalizing the current
// borrow scope because they are "hidden" behind the reborrow.
borrowedVal.visitLocalScopeEndingUses([this](Operand *use) {
liveness.updateForUse(use->getUser(), /*lifetimeEnding*/ true);
return true;
});
// TODO: Fix getCanonicalCopiedDef to allow multi-block borrows and remove
// this assert. This should only be done once consolidateBorrowScope can
// handle persistentCopies, otherwise we may end up generating more dynamic
// copies than the non-canonical form.
assert(liveness.numLiveBlocks() == 1);
return true;
}
// Create a copy for outer uses of the borrow scope introduced by
// currentDef. This copy should only be used by outer uses in the same block
// as the borrow scope.
//
// To use an existing outer copy, we could find its earliest consume. But the
// new copy will immediately canonicalized and a canonical begin_borrow scope
// have no outside uses of its first block.
static CopyValueInst *createOuterCopy(BeginBorrowInst *beginBorrow) {
SILBuilderWithScope B(beginBorrow);
auto loc = RegularLocation::getAutoGeneratedLocation(beginBorrow->getLoc());
auto *copy = B.createCopyValue(loc, beginBorrow->getOperand());
++NumCopiesGenerated;
LLVM_DEBUG(llvm::dbgs() << " Outer copy " << *copy);
return copy;
}
// If this succeeds, then all uses of the borrowed value outside the borrow
// scope will be rewritten to use an outer copy, and all remaining uses of the
// borrowed value will be confined to the borrow scope.
//
// TODO: Canonicalize multi-block borrow scopes, load_borrow scope, and phi
// borrow scopes by adding one copy per block to persistentCopies for
// each block that dominates an outer use.
bool CanonicalizeOSSALifetime::consolidateBorrowScope() {
if (isa<SILFunctionArgument>(currentDef)) {
return true;
}
// Gather all outer uses before rewriting any to avoid scanning any basic
// block more than once.
SmallVector<Operand *, 8> outerUses;
llvm::SmallPtrSet<SILInstruction *, 8> outerUseInsts;
auto isUserInLiveOutBlock = [&](SILInstruction *user) {
// TODO: enable isUserInLiveOutBlock once we support multi-block borrows
// return (liveness.getBlockLiveness(user->getParent())
// == PrunedLiveBlocks::LiveOut);
return false;
};
auto recordOuterUse = [&](Operand *use) {
// If the user's block is LiveOut, then it is definitely within the borrow
// scope, so there's no need to record it.
if (isUserInLiveOutBlock(use->getUser())) {
return;
}
outerUses.push_back(use);
outerUseInsts.insert(use->getUser());
};
// getCanonicalCopiedDef ensures that if currentDef is a guaranteed value,
// then it is a borrow scope introducer.
assert(BorrowedValue(currentDef).isLocalScope());
// This def-use traversal is similar to
// findExtendedTransitiveGuaranteedUses(), however, to cover the canonical
// lifetime, it looks through copies. It also considered uses within the
// introduced borrow scope itself (instead of simply visiting the scope-ending
// uses). It does not, however, look into nested borrow scopes uses, since
// nested scopes are canonicalized independently.
defUseWorklist.clear();
defUseWorklist.insert(currentDef);
while (!defUseWorklist.empty()) {
SILValue value = defUseWorklist.pop_back_val();
for (Operand *use : value->getUses()) {
auto *user = use->getUser();
// Recurse through copies.
if (auto *copy = dyn_cast<CopyValueInst>(user)) {
defUseWorklist.insert(copy);
continue;
}
// Note: debug_value uses are handled like normal uses here. They should
// be stripped later if required when handling outerCopy or
// persistentCopies.
switch (use->getOperandOwnership()) {
case OperandOwnership::NonUse:
break;
case OperandOwnership::TrivialUse:
llvm_unreachable("this operand cannot handle ownership");
case OperandOwnership::InteriorPointer:
case OperandOwnership::ForwardingBorrow:
case OperandOwnership::EndBorrow:
case OperandOwnership::Reborrow:
// Ignore uses that must be within the borrow scope.
// Rewriting does not look through reborrowed values--it considers them
// part of a separate lifetime.
break;
case OperandOwnership::ForwardingUnowned:
case OperandOwnership::PointerEscape:
// Pointer escapes are only allowed if they use the guaranteed value,
// which means that the escaped value must be confied to the current
// borrow scope.
if (use->get().getOwnershipKind() != OwnershipKind::Guaranteed)
return false;
break;
case OperandOwnership::InstantaneousUse:
case OperandOwnership::UnownedInstantaneousUse:
case OperandOwnership::BitwiseEscape:
case OperandOwnership::ForwardingConsume:
case OperandOwnership::DestroyingConsume:
recordOuterUse(use);
break;
case OperandOwnership::Borrow:
BorrowingOperand borrowOper(use);
assert(borrowOper && "BorrowingOperand must handle OperandOwnership");
recordOuterUse(use);
// For borrows, record the scope-ending instructions in addition to the
// borrow instruction outer use points. The logic below to check whether
// a borrow scope is an outer use must visit the same set of uses.
borrowOper.visitExtendedScopeEndingUses([&](Operand *endBorrow) {
if (!isUserInLiveOutBlock(endBorrow->getUser())) {
outerUseInsts.insert(endBorrow->getUser());
}
return true;
});
break;
}
} // end switch OperandOwnership
} // end def-use traversal
auto *beginBorrow = cast<BeginBorrowInst>(currentDef);
SmallVector<SILInstruction *, 1> scopeEndingInst;
BorrowedValue(beginBorrow).getLocalScopeEndingInstructions(scopeEndingInst);
assert(scopeEndingInst.size() == 1 && "expected single-block borrow");
// Remove outer uses that occur before the end of the borrow scope by
// forward iterating from begin_borrow to end_borrow.
for (auto instIter = beginBorrow->getIterator(),
endIter = scopeEndingInst[0]->getIterator();
instIter != endIter; ++instIter) {
outerUseInsts.erase(&*instIter);
}
if (outerUseInsts.empty()) {
return true;
}
// Rewrite the outer uses and record lifetime-ending uses.
SmallVector<Operand *, 4> consumingUses;
SmallPtrSet<SILInstruction *, 4> unclaimedConsumingUsers;
this->outerCopy = createOuterCopy(beginBorrow);
for (Operand *use : outerUses) {
if (!outerUseInsts.count(use->getUser())) {
// The immediate use is within this borrow scope.
BorrowingOperand borrowOper(use);
if (borrowOper.kind == BorrowingOperandKind::Invalid) {
continue;
}
// For sub-borrows also check that the scope-ending instructions are
// within the scope.
if (borrowOper.visitExtendedScopeEndingUses([&](Operand *endBorrow) {
return !outerUseInsts.count(endBorrow->getUser());
})) {
continue;
}
}
LLVM_DEBUG(llvm::dbgs() << " Use of outer copy " << *use->getUser());
use->set(outerCopy);
if (use->isLifetimeEnding()) {
consumingUses.push_back(use);
unclaimedConsumingUsers.insert(use->getUser());
}
}
// Insert a destroy on the outer copy's lifetime frontier, or claim an
// existing consume.
ValueLifetimeAnalysis lifetimeAnalysis(outerCopy, outerUseInsts);
ValueLifetimeAnalysis::Frontier frontier;
bool result = lifetimeAnalysis.computeFrontier(
frontier, ValueLifetimeAnalysis::DontModifyCFG, deBlocks);
assert(result);
while (!frontier.empty()) {
auto *insertPt = frontier.pop_back_val();
if (unclaimedConsumingUsers.erase(&*std::prev(insertPt->getIterator()))) {
continue;
}
SILBuilderWithScope(insertPt).createDestroyValue(insertPt->getLoc(),
outerCopy);
}
// Add copies for consuming users of outerCopy.
for (auto *use : consumingUses) {
// If the user is still in the unclaimedConsumingUsers set, then it does not
// end the outer copy's lifetime and therefore requires a copy. Only one
// operand can be claimed as ending the lifetime, so return its user to the
// unclaimedConsumingUsers set after skipping the first copy.
auto iterAndInserted = unclaimedConsumingUsers.insert(use->getUser());
if (!iterAndInserted.second) {
copyLiveUse(use);
}
}
return true;
}
//===----------------------------------------------------------------------===//
// MARK: Step 1. Compute pruned liveness
//===----------------------------------------------------------------------===//
bool CanonicalizeOSSALifetime::computeCanonicalLiveness() {
defUseWorklist.clear();
defUseWorklist.insert(currentDef);
while (!defUseWorklist.empty()) {
SILValue value = defUseWorklist.pop_back_val();
for (Operand *use : value->getUses()) {
auto *user = use->getUser();
// Recurse through copies.
if (auto *copy = dyn_cast<CopyValueInst>(user)) {
defUseWorklist.insert(copy);
continue;
}
// Handle debug_value instructions separately.
if (pruneDebugMode) {
if (auto *dvi = dyn_cast<DebugValueInst>(user)) {
// Only instructions potentially outside current pruned liveness are
// interesting.
if (liveness.getBlockLiveness(dvi->getParent())
!= PrunedLiveBlocks::LiveOut) {
recordDebugValue(dvi);
}
continue;
}
}
switch (use->getOperandOwnership()) {
case OperandOwnership::NonUse:
break;
case OperandOwnership::TrivialUse:
llvm_unreachable("this operand cannot handle ownership");
// Conservatively treat a conversion to an unowned value as a pointer
// escape. Is it legal to canonicalize ForwardingUnowned?
case OperandOwnership::ForwardingUnowned:
case OperandOwnership::PointerEscape:
return false;
case OperandOwnership::InstantaneousUse:
case OperandOwnership::UnownedInstantaneousUse:
case OperandOwnership::BitwiseEscape:
liveness.updateForUse(user, /*lifetimeEnding*/ false);
break;
case OperandOwnership::ForwardingConsume:
recordConsumingUse(use);
liveness.updateForUse(user, /*lifetimeEnding*/ true);
break;
case OperandOwnership::DestroyingConsume:
// destroy_value does not force pruned liveness (but store etc. does).
if (!isa<DestroyValueInst>(user)) {
liveness.updateForUse(user, /*lifetimeEnding*/ true);
}
recordConsumingUse(use);
break;
case OperandOwnership::Borrow:
if (!liveness.updateForBorrowingOperand(use))
return false;
break;
case OperandOwnership::InteriorPointer:
case OperandOwnership::ForwardingBorrow:
case OperandOwnership::EndBorrow:
case OperandOwnership::Reborrow:
llvm_unreachable("operand kind cannot take an owned value");
}
}
}
return true;
}
// Return true if \p inst is an end_access whose access scope overlaps the end
// of the pruned live range. This means that a hoisted destroy might execute
// within the access scope which previously executed outside the access scope.
//
// Not overlapping (ignored):
//
// %def
// use %def // pruned liveness ends here
// begin_access // access scope unrelated to def
// end_access
//
// Overlapping (must extend pruned liveness):
//
// %def
// begin_access // access scope unrelated to def
// use %def // pruned liveness ends here
// end_access
//
// Overlapping (must extend pruned liveness):
//
// begin_access // access scope unrelated to def
// %def
// use %def // pruned liveness ends here
// end_access
//
bool CanonicalizeOSSALifetime::
endsAccessOverlappingPrunedBoundary(SILInstruction *inst) {
if (isa<EndUnpairedAccessInst>(inst)) {
return true;
}
auto *endAccess = dyn_cast<EndAccessInst>(inst);
if (!endAccess) {
return false;
}
auto *beginAccess = endAccess->getBeginAccess();
SILBasicBlock *beginBB = beginAccess->getParent();
switch (liveness.getBlockLiveness(beginBB)) {
case PrunedLiveBlocks::LiveOut:
// Found partial overlap of the form:
// currentDef
// beginAccess
// br...
// bb...
// use
// endAccess
return true;
case PrunedLiveBlocks::LiveWithin:
// Check for partial overlap of this form where beginAccess and the last use
// are in the same block:
// currentDef
// beginAccess
// use
// endAccess
if (std::find_if(std::next(beginAccess->getIterator()), beginBB->end(),
[this](SILInstruction &nextInst) {
return liveness.isInterestingUser(&nextInst)
!= PrunedLiveness::NonUser;
}) != beginBB->end()) {
// An interesting use after the beginAccess means overlap.
return true;
}
return false;
case PrunedLiveBlocks::Dead:
// Check for partial overlap of this form where beginAccess and currentDef
// are in different blocks:
// beginAccess
// br...
// bb...
// currentDef
// endAccess
//
// Since beginAccess is not within the canonical live range, its access
// scope overlaps only if there is a path from beginAccess to currentDef
// that does not pass through endAccess. endAccess is dominated by
// both currentDef and begin_access. Therefore, such a path only exists if
// beginAccess dominates currentDef.
DominanceInfo *domInfo = dominanceAnalysis->get(inst->getFunction());
return domInfo->properlyDominates(beginAccess->getParent(),
getCurrentDef()->getParentBlock());
}
}
// Find all overlapping access scopes and extend pruned liveness to cover them:
//
// This may also unnecessarily, but conservatively extend liveness over some
// originally overlapping access, such as:
//
// begin_access // access scope unrelated to def
// %def
// use %def
// destroy %def
// end_access
//
// Or:
//
// %def
// begin_access // access scope unrelated to def
// use %def
// destroy %def
// end_access
//
// To minimize unnecessary lifetime extension, only search for end_access
// within dead blocks that are backward reachable from an original destroy.
//
// Note that lifetime extension is iterative because adding a new liveness use
// may create new overlapping access scopes. This can happen because there is no
// guarantee of strict stack discpline across unrelated access. For example:
//
// %def
// begin_access A
// use %def // Initial pruned lifetime boundary
// begin_access B
// end_access A // Lifetime boundary after first extension
// end_access B // Lifetime boundary after second extension
// destroy %def
//
// If the lifetime extension did not iterate, then def would be destroyed within
// B's access scope when originally it was destroyed outside that scope.
void CanonicalizeOSSALifetime::extendLivenessThroughOverlappingAccess() {
this->accessBlocks = accessBlockAnalysis->get(getCurrentDef()->getFunction());
// Visit each original consuming use or destroy as the starting point for a
// backward CFG traversal. This traversal must only visit blocks within the
// original extended lifetime.
bool changed = true;
while (changed) {
changed = false;
blockWorklist.clear();
blockWorklist.insert(consumingBlocks.begin(), consumingBlocks.end());
// This worklist is also a visited set, so we never pop the entries.
for (unsigned blockIdx = 0; blockIdx < blockWorklist.size(); ++blockIdx) {
SILBasicBlock *bb = blockWorklist[blockIdx];
auto blockLiveness = liveness.getBlockLiveness(bb);
// Ignore blocks within pruned liveness.
if (blockLiveness == PrunedLiveBlocks::LiveOut) {
continue;
}
if (blockLiveness == PrunedLiveBlocks::Dead) {
// Continue searching upward to find the pruned liveness boundary.
for (auto *predBB : bb->getPredecessorBlocks()) {
blockWorklist.insert(predBB);
}
// Otherwise, ignore dead blocks with no nonlocal end_access.
if (!accessBlocks->containsNonLocalEndAccess(bb)) {
continue;
}
}
bool blockHasUse = (blockLiveness == PrunedLiveBlocks::LiveWithin);
// Find the latest partially overlapping access scope, if one exists:
// use %def // pruned liveness ends here
// end_access
for (auto &inst : llvm::reverse(*bb)) {
// Stop at the latest use. An earlier end_access does not overlap.
if (blockHasUse && liveness.isInterestingUser(&inst)) {
break;
}
if (endsAccessOverlappingPrunedBoundary(&inst)) {
liveness.updateForUse(&inst, /*lifetimeEnding*/ false);
changed = true;
break;
}
}
// If liveness changed, might as well restart CFG traversal.
if (changed) {
break;
}
}
}
}
//===----------------------------------------------------------------------===//
// MARK: Step 2. Find the destroy points of the current def based on the pruned
// liveness computed in Step 1.
//===----------------------------------------------------------------------===//
/// The liveness boundary is at a CFG edge `predBB` -> `succBB`, meaning that
/// `currentDef` is live out of at least one other `predBB` successor.
///
/// Create and record a final destroy_value at the beginning of `succBB`
/// (assuming no critical edges).
void CanonicalizeOSSALifetime::insertDestroyOnCFGEdge(
SILBasicBlock *predBB, SILBasicBlock *succBB, bool needsPoison) {
assert(succBB->getSinglePredecessorBlock() == predBB
&& "value is live-out on another predBB successor: critical edge?");
auto pos = succBB->begin();
// TODO: to improve debugability, consider advancing the poison position ahead
// of operations that are known not to access weak references.
SILBuilderWithScope builder(pos);
auto loc = RegularLocation::getAutoGeneratedLocation(pos->getLoc());
auto *di = builder.createDestroyValue(loc, currentDef, needsPoison);
consumes.recordFinalConsume(di);
++NumDestroysGenerated;
LLVM_DEBUG(llvm::dbgs() << " Destroy on edge " << *di);
}
/// This liveness boundary is within a basic block at the given position.
///
/// Create a final destroy, immediately after `pos`.
static void insertDestroyAtInst(SILBasicBlock::iterator pos,
DestroyValueInst *existingDestroy,
SILValue def, bool needsPoison,
CanonicalOSSAConsumeInfo &consumes) {
if (existingDestroy) {
for (; pos != existingDestroy->getIterator(); ++pos) {
if (auto *debugVal = dyn_cast<DebugValueInst>(&*pos)) {
consumes.popDebugAfterConsume(debugVal);
}
}
consumes.recordFinalConsume(existingDestroy);
// Avoid resetting poison just in case this runs twice.
if (needsPoison) {
existingDestroy->setPoisonRefs(true);
}
return;
}
SILBuilderWithScope builder(pos);
auto loc = RegularLocation::getAutoGeneratedLocation((*pos).getLoc());
auto *di = builder.createDestroyValue(loc, def, needsPoison);
consumes.recordFinalConsume(di);
++NumDestroysGenerated;
LLVM_DEBUG(llvm::dbgs() << " Destroy at last use " << *di);
}
// The pruned liveness boundary is within the given basic block. Find the
// block's last use. If the last use consumes the value, record it as a
// destroy. Otherwise, insert a new destroy_value.
//
// TODO: This has become quite a hack. Instead, the final liveness boundary
// should be returned in a data structure along with summary information about
// each block. Then any special logic for handling existing destroys, debug
// values, and poison should be applied to that block summary which can provide
// the input to rewriteCopies.
void CanonicalizeOSSALifetime::findOrInsertDestroyInBlock(SILBasicBlock *bb) {
auto *defInst = currentDef->getDefiningInstruction();
DestroyValueInst *existingDestroy = nullptr;
bool existingDestroyNeedsPoison = false;
// needsPoison is true as soon as an existingDestroy is seen, but sticky.
bool needsPoison = false;
auto instIter = bb->getTerminator()->getIterator();
while (true) {
auto *inst = &*instIter;
if (pruneDebugMode) {
if (auto *dvi = dyn_cast<DebugValueInst>(inst)) {
if (debugValues.erase(dvi))
consumes.recordDebugAfterConsume(dvi);
}
}
switch (liveness.isInterestingUser(inst)) {
case PrunedLiveness::NonUser:
break;
case PrunedLiveness::NonLifetimeEndingUse:
// Insert a destroy after this non-consuming use.
if (isa<TermInst>(inst)) {
for (auto &succ : bb->getSuccessors()) {
insertDestroyOnCFGEdge(bb, succ, poisonRefsMode);
setChanged();
}
} else {
if (existingDestroy) {
needsPoison = existingDestroyNeedsPoison;
}
insertDestroyAtInst(std::next(instIter), existingDestroy, currentDef,
needsPoison, consumes);
setChanged();
}
return;
case PrunedLiveness::LifetimeEndingUse:
if (!needsPoison) {
// This use becomes a final consume.
consumes.recordFinalConsume(inst);
return;
}
// If a destroy needs poison, simply make it so.
auto *destroy = dyn_cast<DestroyValueInst>(inst);
// Reuse an existing one if we need to.
if (!destroy) {
destroy = existingDestroy;
needsPoison = existingDestroyNeedsPoison;
}
if (destroy) {
destroy->setPoisonRefs(needsPoison);
consumes.recordFinalConsume(destroy);
return;
}
// Put this in the set of final consumes that need poison injection.
consumes.recordNeedsPoison(inst);
return;
}
// This is not a potential last user. Keep scanning.
// Allow lifetimes to be artificially extended up to the next call. The goal
// is to prevent repeated destroy rewriting without inhibiting optimization.
if (ApplySite::isa(inst)) {
existingDestroy = nullptr;
} else if (!existingDestroy) {
if (auto *destroy = dyn_cast<DestroyValueInst>(inst)) {
auto destroyDef = CanonicalizeOSSALifetime::getCanonicalCopiedDef(
destroy->getOperand());
if (destroyDef == currentDef) {
existingDestroy = destroy;
// If this destroy is reused, it is not poisoned.
existingDestroyNeedsPoison = needsPoison;
if (poisonRefsMode) {
// Any earlier consume will be poisoned.
needsPoison = true;
}
}
}
}
if (instIter == bb->begin()) {
assert(cast<SILArgument>(currentDef)->getParent() == bb);
if (existingDestroy) {
needsPoison = existingDestroyNeedsPoison;
}
insertDestroyAtInst(instIter, existingDestroy, currentDef, needsPoison,
consumes);
setChanged();
return;
}
--instIter;
// If the original def is reached, this is a dead live range. Insert a
// destroy immediately after the def.
if (&*instIter == defInst) {
if (existingDestroy) {
needsPoison = existingDestroyNeedsPoison;
}
insertDestroyAtInst(std::next(instIter), existingDestroy, currentDef,
needsPoison, consumes);
setChanged();
return;
}
}
}
/// Populate `consumes` with the final destroy points once copies are
/// eliminated. This only applies to owned values.
///
/// Observations:
/// - currentDef must be postdominated by some subset of its
/// consuming uses, including destroys on all return paths.
/// - The postdominating consumes cannot be within nested loops.
/// - Any blocks in nested loops are now marked LiveOut.
void CanonicalizeOSSALifetime::findOrInsertDestroys() {
this->accessBlocks = accessBlockAnalysis->get(getCurrentDef()->getFunction());
// Visit each original consuming use or destroy as the starting point for a
// backward CFG traversal.
blockWorklist.clear();
blockWorklist.insert(consumingBlocks.begin(), consumingBlocks.end());
// This worklist is also a visited set, so we never pop the entries.
for (unsigned blockIdx = 0; blockIdx < blockWorklist.size(); ++blockIdx) {
// Process each block that has not been visited and is not LiveOut.
SILBasicBlock *bb = blockWorklist[blockIdx];
switch (liveness.getBlockLiveness(bb)) {
case PrunedLiveBlocks::LiveOut:
// A lifetimeEndBlock may be determined to be LiveOut after analyzing the
// extended It is irrelevent for finding the boundary.
break;
case PrunedLiveBlocks::LiveWithin: {
// The liveness boundary is inside this block. Insert a final destroy
// inside the block if it doesn't already have one.
findOrInsertDestroyInBlock(bb);
break;
}
case PrunedLiveBlocks::Dead:
// Continue searching upward to find the pruned liveness boundary.
for (auto *predBB : bb->getPredecessorBlocks()) {
if (liveness.getBlockLiveness(predBB) == PrunedLiveBlocks::LiveOut) {
insertDestroyOnCFGEdge(predBB, bb, poisonRefsMode);
setChanged();
} else {
if (poisonRefsMode) {
remnantLiveOutBlocks.insert(predBB);
}
blockWorklist.insert(predBB);
}
}
break;
}
}
}
//===----------------------------------------------------------------------===//
// MARK: Step 3. Rewrite copies and destroys
//===----------------------------------------------------------------------===//
/// Revisit the def-use chain of currentDef. Mark unneeded original
/// copies and destroys for deletion. Insert new copies for interior uses that
/// require ownership of the used operand.
void CanonicalizeOSSALifetime::rewriteCopies() {
bool isOwned = currentDef.getOwnershipKind() == OwnershipKind::Owned;
assert((!isOwned || !consumes.hasPersistentCopies())
&& "persistent copies use borrowed values");
SmallSetVector<SILInstruction *, 8> instsToDelete;
defUseWorklist.clear();
// Visit each operand in the def-use chain.
//
// Return true if the operand can use the current definition. Return false if
// it requires a copy.
auto visitUse = [&](Operand *use) {
auto *user = use->getUser();
// Recurse through copies.
if (auto *copy = dyn_cast<CopyValueInst>(user)) {
if (!consumes.isPersistentCopy(copy)) {
defUseWorklist.insert(copy);
return true;
}
}
if (auto *destroy = dyn_cast<DestroyValueInst>(user)) {
// If this destroy was marked as a final destroy, ignore it; otherwise,
// delete it.
if (!consumes.claimConsume(destroy)) {
instsToDelete.insert(destroy);
LLVM_DEBUG(llvm::dbgs() << " Removing " << *destroy);
++NumDestroysEliminated;
}
// If another destroy is reachable on this path, then poison this
// destroy. It will already be poisoned if it was inserted on a CFG edge
// or another destroy occurs later in the same block.
if (!destroy->poisonRefs()
&& remnantLiveOutBlocks.count(destroy->getParent())) {
destroy->setPoisonRefs(true);
}
return true;
}
// Nonconsuming uses do not need copies and cannot be marked as destroys.
// A lifetime-ending use here must be a consume because EndBorrow/Reborrow
// uses have been filtered out.
if (!use->isLifetimeEnding())
return true;
// If this use was not marked as a final destroy *or* this is not the first
// consumed operand we visited, then it needs a copy.
if (!consumes.claimConsume(user))
return false;
// Final consumes that need poison, also need a copy.
if (consumes.needsPoison(user))
return false;
// If another destroy is reachable on this path, then this consume needs
// poison even though findOrInsertDestroyInBlock didn't know that.
if (remnantLiveOutBlocks.count(user->getParent())) {
consumes.recordNeedsPoison(user);
return false;
}
return true;
};
// Perform a def-use traversal, visiting each use operand.
for (auto useIter = currentDef->use_begin(), endIter = currentDef->use_end();
useIter != endIter;) {
Operand *use = *useIter++;
// A direct lifetime-ending use of a guaranteed value (EndBorrow or
// Reborrow), never needs a copy.
if (!isOwned && use->isLifetimeEnding()) {
continue;
}
if (!visitUse(use)) {
copyLiveUse(use);
setChanged();
}
}
while (!defUseWorklist.empty()) {
CopyValueInst *srcCopy = cast<CopyValueInst>(defUseWorklist.pop_back_val());
// Recurse through copies while replacing their uses.
Operand *reusedCopyOp = nullptr;
for (auto useIter = srcCopy->use_begin(); useIter != srcCopy->use_end();) {
Operand *use = *useIter++;
if (!visitUse(use)) {
if (!reusedCopyOp && srcCopy->getParent() == use->getParentBlock()) {
reusedCopyOp = use;
} else {
copyLiveUse(use);
setChanged();
}
}
}
if (!(reusedCopyOp && srcCopy->hasOneUse())) {
setChanged();
srcCopy->replaceAllUsesWith(srcCopy->getOperand());
if (reusedCopyOp) {
reusedCopyOp->set(srcCopy);
} else {
if (instsToDelete.insert(srcCopy)) {
LLVM_DEBUG(llvm::dbgs() << " Removing " << *srcCopy);
++NumCopiesEliminated;
}
}
}
}
assert(!consumes.hasUnclaimedConsumes());
// Insert destroys wherever poison is needed. This may recover more debug
// values, erasing them from the debugValues set.
injectPoison();
// Add any debug_values from Dead blocks into the debugAfterConsume set.
for (auto *dvi : debugValues) {
if (liveness.getBlockLiveness(dvi->getParent()) == PrunedLiveBlocks::Dead) {
consumes.recordDebugAfterConsume(dvi);
}
}
// Remove any dead, non-recovered debug_values.
for (auto *dvi : consumes.getDebugInstsAfterConsume()) {
LLVM_DEBUG(llvm::dbgs() << " Removing debug_value: " << *dvi);
dvi->eraseFromParent();
}
// Remove the leftover copy_value and destroy_value instructions.
if (!instsToDelete.empty()) {
recursivelyDeleteTriviallyDeadInstructions(instsToDelete.takeVector(),
/*force=*/true);
setChanged();
}
}
/// Insert poison destroys after each consume that needs poison.
void CanonicalizeOSSALifetime::injectPoison() {
for (SILInstruction *consume : consumes.getNeedsPoisonConsumes()) {
auto createDestroy = [&](SILBuilder &builder) {
auto loc = RegularLocation::getAutoGeneratedLocation(
builder.getInsertionPoint()->getLoc());
auto *di = builder.createDestroyValue(loc, currentDef,
/*needsPoison*/ true);
++NumDestroysGenerated;
LLVM_DEBUG(llvm::dbgs() << " Destroy at last use " << *di);
};
if (auto *branch = dyn_cast<BranchInst>(consume)) {
// At a phi, destroy the value before branching.
//
// A copy must have been created for the branch operand during
// rewriteCopies (but there's no easy way to assert that here).
SILBuilderWithScope builder(branch);
createDestroy(builder);
} else {
// For normal instructions and non-phi block terminators, destroy the
// value after the operation.
SILBuilderWithScope::insertAfter(consume, createDestroy);
}
}
}
//===----------------------------------------------------------------------===//
// MARK: Top-Level API
//===----------------------------------------------------------------------===//
/// True if \p def should be canonicalized in poisonRefs mode.
///
/// Currently, we only handle owned values because, for now, the goal is to
/// shorten lifetimes to mimic -O behavior, not to remove copies.
///
/// IRGen must also have support for injecting poison into values of this type.
bool shouldCanonicalizeWithPoison(SILValue def) {
assert(def->getType().isObject() && "only SIL values are canonicalized");
auto objectTy = def->getType().unwrapOptionalType();
// TODO: Handle structs, enums, and tuples.
//
// TODO: Functions are not currently handled because of closure lifetime
// bugs. Noescape closures don't always have a dependence.
return objectTy.isAnyClassReferenceType();
}