89
89
90
90
#define DEBUG_TYPE " ssa-destroy-hoisting"
91
91
92
+ #include " swift/AST/Type.h"
92
93
#include " swift/Basic/GraphNodeWorklist.h"
93
94
#include " swift/Basic/SmallPtrSetVector.h"
94
95
#include " swift/SIL/BasicBlockDatastructures.h"
@@ -496,6 +497,9 @@ bool DeinitBarriers::DestroyReachability::checkReachablePhiBarrier(
496
497
// / object.
497
498
class HoistDestroys {
498
499
SILValue storageRoot;
500
+ SILFunction *function;
501
+ SILModule &module ;
502
+ TypeExpansionContext typeExpansionContext;
499
503
bool ignoreDeinitBarriers;
500
504
SmallPtrSetImpl<SILInstruction *> &remainingDestroyAddrs;
501
505
InstructionDeleter &deleter;
@@ -509,7 +513,9 @@ class HoistDestroys {
509
513
HoistDestroys (SILValue storageRoot, bool ignoreDeinitBarriers,
510
514
SmallPtrSetImpl<SILInstruction *> &remainingDestroyAddrs,
511
515
InstructionDeleter &deleter)
512
- : storageRoot(storageRoot), ignoreDeinitBarriers(ignoreDeinitBarriers),
516
+ : storageRoot(storageRoot), function(storageRoot->getFunction ()),
517
+ module(function->getModule ()), typeExpansionContext(*function),
518
+ ignoreDeinitBarriers(ignoreDeinitBarriers),
513
519
remainingDestroyAddrs(remainingDestroyAddrs), deleter(deleter),
514
520
destroyMergeBlocks(getFunction()) {}
515
521
@@ -518,11 +524,20 @@ class HoistDestroys {
518
524
protected:
519
525
SILFunction *getFunction () const { return storageRoot->getFunction (); }
520
526
521
- bool foldBarrier (SILInstruction *barrier, SILValue accessScope);
527
+ bool foldBarrier (SILInstruction *barrier, const AccessStorage &storage,
528
+ const DeinitBarriers &deinitBarriers);
522
529
523
- bool foldBarrier (SILInstruction *barrier, const KnownStorageUses &knownUses,
530
+ bool foldBarrier (SILInstruction *barrier, const AccessStorage &storage,
531
+ const KnownStorageUses &knownUses,
524
532
const DeinitBarriers &deinitBarriers);
525
533
534
+ bool checkFoldingBarrier (SILInstruction *instruction,
535
+ SmallVectorImpl<LoadInst *> &loads,
536
+ SmallVectorImpl<CopyAddrInst *> &copies,
537
+ SmallPtrSetImpl<AccessPath::PathNode> &leaves,
538
+ const AccessStorage &storage,
539
+ const DeinitBarriers &deinitBarriers);
540
+
526
541
void insertDestroy (SILInstruction *barrier, SILInstruction *insertBefore,
527
542
const KnownStorageUses &knownUses);
528
543
@@ -531,7 +546,8 @@ class HoistDestroys {
531
546
532
547
void createSuccessorDestroys (SILBasicBlock *barrierBlock);
533
548
534
- bool rewriteDestroys (const KnownStorageUses &knownUses,
549
+ bool rewriteDestroys (const AccessStorage &storage,
550
+ const KnownStorageUses &knownUses,
535
551
const DeinitBarriers &deinitBarriers);
536
552
537
553
void mergeDestroys (SILBasicBlock *mergeBlock);
@@ -553,16 +569,17 @@ bool HoistDestroys::perform() {
553
569
deinitBarriers.compute ();
554
570
555
571
// No SIL changes happen before rewriting.
556
- return rewriteDestroys (knownUses, deinitBarriers);
572
+ return rewriteDestroys (storage, knownUses, deinitBarriers);
557
573
}
558
574
559
- bool HoistDestroys::rewriteDestroys (const KnownStorageUses &knownUses,
575
+ bool HoistDestroys::rewriteDestroys (const AccessStorage &storage,
576
+ const KnownStorageUses &knownUses,
560
577
const DeinitBarriers &deinitBarriers) {
561
578
// Place a new destroy after each barrier instruction.
562
579
for (SILInstruction *barrier : deinitBarriers.barriers ) {
563
580
auto *barrierBlock = barrier->getParent ();
564
581
if (barrier != barrierBlock->getTerminator ()) {
565
- if (!foldBarrier (barrier, knownUses, deinitBarriers))
582
+ if (!foldBarrier (barrier, storage, knownUses, deinitBarriers))
566
583
insertDestroy (barrier, barrier->getNextInstruction (), knownUses);
567
584
continue ;
568
585
}
@@ -610,30 +627,234 @@ bool HoistDestroys::rewriteDestroys(const KnownStorageUses &knownUses,
610
627
return deleter.hadCallbackInvocation ();
611
628
}
612
629
613
- bool HoistDestroys::foldBarrier (SILInstruction *barrier, SILValue storageRoot) {
614
- if (auto *load = dyn_cast<LoadInst>(barrier)) {
615
- if (stripAccessMarkers (load->getOperand ()) ==
616
- stripAccessMarkers (storageRoot)) {
617
- if (load->getOwnershipQualifier () == LoadOwnershipQualifier::Copy) {
618
- load->setOwnershipQualifier (LoadOwnershipQualifier::Take);
630
+ // / Try to fold the destroy_addr with the specified barrier, or a backwards
631
+ // / sequence of instructions that it begins.
632
+ // /
633
+ // / Do the following kinds of folds:
634
+ // /
635
+ // / - loads:
636
+ // / given: load [copy] %addr
637
+ // / destroy_addr %addr
638
+ // / yield: load [take]
639
+ // / - copy_addrs:
640
+ // / given: copy_addr %addr to ...
641
+ // / destroy_addr %addr
642
+ // / yield: copy_addr [take] %addr
643
+ // /
644
+ // / Additionally, generalize this to subobjects. If there is a sequence of
645
+ // / copy_addrs and loads that covers all the subobjects of %addr. Given
646
+ // / projections %subobject_1 and %subobject_2 out of %addr which fully cover all
647
+ // / the non-trivial fields of the recursive type-tree of %addr, fold
648
+ // /
649
+ // / load [copy] %subobject_1
650
+ // / copy_addr %subobject_2 to ...
651
+ // / destroy_addr %addr
652
+ // /
653
+ // / into
654
+ // /
655
+ // / load [take] %subobject_1
656
+ // / copy_addr [take] %subobject_2 to ...
657
+ // /
658
+ // / so long as all the loads and copy_addrs occur within the same block.
659
+ bool HoistDestroys::foldBarrier (SILInstruction *barrier,
660
+ const AccessStorage &storage,
661
+ const DeinitBarriers &deinitBarriers) {
662
+
663
+ // The load [copy]s which will be folded into load [take]s if folding is
664
+ // possible.
665
+ llvm::SmallVector<LoadInst *, 4 > loads;
666
+ // The copy_addrs which will be folded into copy_addr [take]s if folding is
667
+ // possible.
668
+ llvm::SmallVector<CopyAddrInst *, 4 > copies;
669
+
670
+ // The non-trivial storage leaves of the root storage all of which must be
671
+ // destroyed exactly once in the sequence of instructions prior to the
672
+ // destroy_addr in order for folding to occur.
673
+ llvm::SmallPtrSet<AccessPath::PathNode, 16 > leaves;
674
+
675
+ visitProductLeafAccessPathNodes (storageRoot, typeExpansionContext, module ,
676
+ [&](AccessPath::PathNode node, SILType ty) {
677
+ if (ty.isTrivial (*function))
678
+ return ;
679
+ leaves.insert (node);
680
+ });
681
+
682
+ for (auto *instruction = barrier; instruction != nullptr ;
683
+ instruction = instruction->getPreviousInstruction ()) {
684
+ if (checkFoldingBarrier (instruction, loads, copies, leaves, storage,
685
+ deinitBarriers))
686
+ return false ;
687
+
688
+ // If we have load [copy]s or copy_addrs of projections out of the root
689
+ // storage that cover all non-trivial product leaves, then we can fold!
690
+ //
691
+ // Stop looking for instructions to fold.
692
+ if (leaves.empty ())
693
+ break ;
694
+ }
695
+
696
+ if (!leaves.empty ())
697
+ return false ;
698
+
699
+ for (auto *load : loads) {
700
+ assert (load->getOwnershipQualifier () == LoadOwnershipQualifier::Copy);
701
+ load->setOwnershipQualifier (LoadOwnershipQualifier::Take);
702
+ }
703
+ for (auto *copy : copies) {
704
+ assert (!copy->isTakeOfSrc ());
705
+ copy->setIsTakeOfSrc (IsTake);
706
+ }
707
+
708
+ return true ;
709
+ }
710
+
711
+ // / Whether the specified instruction is a barrier to folding.
712
+ // /
713
+ // / TODO: This is a bit more conservative that it needs to be in a couple of
714
+ // / ways:
715
+ // /
716
+ // / (1) even if we've already seen a leaf, we could still fold, in certain
717
+ // / cases, we should be able to fold anyway. For example, given projections
718
+ // / %p1 and %p2 of some root storage %a, in the following scenario:
719
+ // /
720
+ // / %p1 = <PROJECT> %a
721
+ // / %p2 = <PROJECT> %a
722
+ // / %v1 = load [copy] %p1
723
+ // / %v2_1 = load [copy] %p2
724
+ // / %v2_1 = load [copy] %p2
725
+ // / destroy_addr %a
726
+ // /
727
+ // / we could fold destroy_addr %a into the first load [copy] %p2 and the
728
+ // / load [copy] %p1:
729
+ // /
730
+ // / %v1 = load [take] %p1
731
+ // / %v2_1 = load [copy] %p2
732
+ // / %v2_2 = load [take] %p1
733
+ // /
734
+ // / And indeed we can do that for loads from a subprojection %p2_sub of
735
+ // / %p2; the following
736
+ // /
737
+ // / %v1 = load [copy] %p1
738
+ // / %v2_sub = load [copy] %p2_sub
739
+ // / %v2 = load [copy] %p2
740
+ // /
741
+ // / could be folded to
742
+ // /
743
+ // / %v1 = load [take] %p1
744
+ // / %v2_sub = load [copy] %p2_sub
745
+ // / %v2 = load [take] %p2
746
+ // /
747
+ // / (2) We should be able to continue folding over a load [trivial] so long as
748
+ // / the instructions that we're folding with don't destroy an aggregate that
749
+ // / contains the projection which is the target of the load [trivial]. For
750
+ // / example, given
751
+ // /
752
+ // / %addr = alloc_stack %(X, I)
753
+ // / %x_addr = tuple_element_addr %addr : $*(X, I), 0
754
+ // / %i_addr = tuple_element_addr %addr : $*(X, I), 1
755
+ // / %x = load [copy] %x_addr : $*X
756
+ // / %i = load [trivial] %i_addr : $*I
757
+ // / destroy_addr %addr
758
+ // /
759
+ // / we should be able to fold the destroy_addr of the tuple with the load [copy]
760
+ // / and ignore the load [trivial].
761
+ // /
762
+ // / Doing this is complicated by the fact that we can't ignore the load
763
+ // / [trivial] if the load [copy] is of the whole tuple. If we have instead
764
+ // /
765
+ // / %addr = alloc_stack %(X, I)
766
+ // / %x_addr = tuple_element_addr %addr : $*(X, I), 0
767
+ // / %i_addr = tuple_element_addr %addr : $*(X, I), 1
768
+ // / %x = load [copy] %addr : $*(X, I)
769
+ // / %i = load [trivial] %i_addr : $*I
770
+ // / destroy_addr %addr
771
+ // /
772
+ // / then we cannot fold. If we did, we would end up with invalid SIL:
773
+ // /
774
+ // / %x = load [take] %addr
775
+ // / %i = load [trivial] %i_addr
776
+ bool HoistDestroys::checkFoldingBarrier (
777
+ SILInstruction *instruction, SmallVectorImpl<LoadInst *> &loads,
778
+ SmallVectorImpl<CopyAddrInst *> &copies,
779
+ SmallPtrSetImpl<AccessPath::PathNode> &leaves, const AccessStorage &storage,
780
+ const DeinitBarriers &deinitBarriers) {
781
+ // The address of a projection out of the root storage which would be
782
+ // folded if folding is possible.
783
+ //
784
+ // If no such address is found, we need to check whether the instruction
785
+ // is a barrier.
786
+ SILValue address;
787
+ if (auto *load = dyn_cast<LoadInst>(instruction)) {
788
+ auto loadee = load->getOperand ();
789
+ auto relativeAccessStorage = RelativeAccessStorageWithBase::compute (loadee);
790
+ if (relativeAccessStorage.getStorage ().hasIdenticalStorage (storage)) {
791
+ // If the access path from the loaded address to its root storage involves
792
+ // a (layout non-equivalent) typecast--a load [take] of the casted address
793
+ // would not be equivalent to a load [copy] followed by a destroy_addr of
794
+ // the corresponding uncast projection--the truncated portion might have
795
+ // refcounted components.
796
+ if (relativeAccessStorage.cast == AccessStorageCast::Type)
619
797
return true ;
798
+ if (load->getOwnershipQualifier () == LoadOwnershipQualifier::Copy) {
799
+ address = loadee;
800
+ loads.push_back (load);
620
801
} else {
621
- assert (load-> getOperand () ->getType ().isTrivial (*load->getFunction ()));
622
- return false ;
802
+ assert (loadee ->getType ().isTrivial (*load->getFunction ()));
803
+ return true ;
623
804
}
624
805
}
806
+ } else if (auto *copy = dyn_cast<CopyAddrInst>(instruction)) {
807
+ auto source = copy->getSrc ();
808
+ auto relativeAccessStorage = RelativeAccessStorageWithBase::compute (source);
809
+ if (relativeAccessStorage.getStorage ().hasIdenticalStorage (storage)) {
810
+ // If the access path from the copy_addr'd address to its root storage
811
+ // involves a (layout non-equivalent) typecast--a copy_addr [take] of the
812
+ // casted address would not be equivalent to a copy_addr followed by a
813
+ // destroy_addr of the corresponding uncast projection--the truncated
814
+ // portion might have refcounted components.
815
+ if (relativeAccessStorage.cast == AccessStorageCast::Type)
816
+ return true ;
817
+ address = source;
818
+ copies.push_back (copy);
819
+ }
625
820
}
626
- if (auto *copy = dyn_cast<CopyAddrInst>(barrier)) {
627
- if (stripAccessMarkers (copy->getSrc ()) == stripAccessMarkers (storageRoot)) {
628
- assert (!copy->isTakeOfSrc ());
629
- copy->setIsTakeOfSrc (IsTake);
821
+ if (address) {
822
+ // We found a relevant instruction that is operating on a projection out
823
+ // of the root storage which would be folded if folding were possible.
824
+ // Find its nontrivial product leaves and remove them from the set of
825
+ // leaves of the root storage which we're wating to see.
826
+ bool alreadySawLeaf = false ;
827
+ visitProductLeafAccessPathNodes (address, typeExpansionContext, module ,
828
+ [&](AccessPath::PathNode node, SILType ty) {
829
+ if (ty.isTrivial (*function))
830
+ return ;
831
+ bool erased = leaves.erase (node);
832
+ alreadySawLeaf =
833
+ alreadySawLeaf || !erased;
834
+ });
835
+ if (alreadySawLeaf) {
836
+ // We saw this non-trivial product leaf already. That means there are
837
+ // multiple load [copy]s or copy_addrs of at least one product leaf
838
+ // before (walking backwards from the hoisting point) there are
839
+ // instructions that load or copy from all the non-trivial leaves.
840
+ // Give up on folding.
630
841
return true ;
631
842
}
843
+ } else if (deinitBarriers.isBarrier (instruction)) {
844
+ // We didn't find an instruction that was both
845
+ // - relevant (i.e. a copy_addr or a load [take])
846
+ // - operating on a projection of the root storage
847
+ // Additionally:
848
+ // - we can't ignore whether it's a barrier
849
+ // - and it IS a barrier.
850
+ // We can't fold.
851
+ return true ;
632
852
}
633
853
return false ;
634
854
}
635
855
636
856
bool HoistDestroys::foldBarrier (SILInstruction *barrier,
857
+ const AccessStorage &storage,
637
858
const KnownStorageUses &knownUses,
638
859
const DeinitBarriers &deinitBarriers) {
639
860
if (auto *eai = dyn_cast<EndAccessInst>(barrier)) {
@@ -645,13 +866,13 @@ bool HoistDestroys::foldBarrier(SILInstruction *barrier,
645
866
while ((instruction = instruction->getPreviousInstruction ())) {
646
867
if (instruction == bai)
647
868
return false ;
648
- if (foldBarrier (instruction, storageRoot ))
869
+ if (foldBarrier (instruction, storage, deinitBarriers ))
649
870
return true ;
650
871
if (deinitBarriers.isBarrier (instruction))
651
872
return false ;
652
873
}
653
874
}
654
- return foldBarrier (barrier, storageRoot );
875
+ return foldBarrier (barrier, storage, deinitBarriers );
655
876
}
656
877
657
878
// \p barrier may be null if the destroy is at function entry.
@@ -729,6 +950,12 @@ bool hoistDestroys(SILValue root, bool ignoreDeinitBarriers,
729
950
// The algorithm assumes no critical edges.
730
951
assert (function->hasOwnership () && " requires OSSA" );
731
952
953
+ // If lexical lifetimes aren't enabled, then deinit barriers aren't respected.
954
+ auto &module = function->getModule ();
955
+ auto enableLexicalLifetimes =
956
+ module .getASTContext ().SILOpts .supportsLexicalLifetimes (module );
957
+ ignoreDeinitBarriers = ignoreDeinitBarriers || !enableLexicalLifetimes;
958
+
732
959
return HoistDestroys (root, ignoreDeinitBarriers, remainingDestroyAddrs,
733
960
deleter)
734
961
.perform ();
@@ -842,8 +1069,7 @@ void SSADestroyHoisting::run() {
842
1069
// We assume that the function is in reverse post order so visiting the
843
1070
// blocks and pushing begin_access as we see them and then popping them off
844
1071
// the end will result in hoisting inner begin_access' destroy_addrs first.
845
- while (!bais.empty ()) {
846
- auto *bai = bais.pop_back_val ();
1072
+ for (auto *bai : llvm::reverse (bais)) {
847
1073
changed |= hoistDestroys (bai, /* ignoreDeinitBarriers=*/ true ,
848
1074
remainingDestroyAddrs, deleter);
849
1075
}
0 commit comments