15
15
#include " swift/Basic/Defer.h"
16
16
#include " swift/SIL/DebugUtils.h"
17
17
#include " swift/SIL/InstructionUtils.h"
18
+ #include " swift/SIL/PrunedLiveness.h"
18
19
#include " swift/SIL/SILArgument.h"
19
20
#include " swift/SIL/SILBuilder.h"
20
21
#include " swift/SIL/SILInstruction.h"
22
+ #include " swift/SIL/SILValue.h"
21
23
#include " swift/SIL/BasicBlockDatastructures.h"
22
24
#include " swift/SILOptimizer/PassManager/Passes.h"
23
25
#include " swift/SILOptimizer/PassManager/Transforms.h"
@@ -712,7 +714,171 @@ static SILValue tryRewriteToPartialApplyStack(
712
714
// lifetime ends.
713
715
SmallVector<SILInstruction *, 4 > lifetimeEnds;
714
716
collectStackClosureLifetimeEnds (lifetimeEnds, closureOp);
717
+
718
+ // For noncopyable address-only captures, see if we can eliminate the copy
719
+ // that SILGen emitted to allow the original partial_apply to take ownership.
720
+ // We do this here because otherwise the move checker will see the copy as an
721
+ // attempt to consume the value, which we don't want.
722
+ SmallVector<SILBasicBlock *, 8 > discoveredBlocks;
723
+ SSAPrunedLiveness closureLiveness (cvt->getFunction (), &discoveredBlocks);
724
+ closureLiveness.initializeDef (closureOp);
725
+
726
+ SmallSetVector<SILValue, 4 > borrowedOriginals;
727
+
728
+ for (unsigned i : indices (newPA->getArgumentOperands ())) {
729
+ auto &arg = newPA->getArgumentOperands ()[i];
730
+ SILValue copy = arg.get ();
731
+ // The temporary should be a local stack allocation.
732
+ LLVM_DEBUG (llvm::dbgs () << " considering whether to eliminate copy of capture\n " ;
733
+ copy->printInContext (llvm::dbgs ());
734
+ llvm::dbgs () << " \n " );
735
+
736
+ auto stack = dyn_cast<AllocStackInst>(copy);
737
+ if (!stack) {
738
+ LLVM_DEBUG (llvm::dbgs () << " -- not an alloc_stack\n " );
739
+ continue ;
740
+ }
741
+
742
+ // This would be a nice optimization to attempt for all types, but for now,
743
+ // limit the effect to move-only types.
744
+ if (!copy->getType ().isMoveOnly ()) {
745
+ LLVM_DEBUG (llvm::dbgs () << " -- not move-only\n " );
746
+ continue ;
747
+ }
748
+
749
+ // Is the capture a borrow?
750
+ auto paramIndex = newPA
751
+ ->getArgumentIndexForOperandIndex (i + newPA->getArgumentOperandNumber ())
752
+ .getValue ();
753
+ if (!newPA->getOrigCalleeType ()->getParameters ()[paramIndex]
754
+ .isIndirectInGuaranteed ()) {
755
+ LLVM_DEBUG (llvm::dbgs () << " -- not an in_guaranteed parameter\n " ;
756
+ newPA->getOrigCalleeType ()->getParameters ()[paramIndex]
757
+ .print (llvm::dbgs ());
758
+ llvm::dbgs () << " \n " );
759
+ continue ;
760
+ }
715
761
762
+ // It needs to have been initialized by copying from somewhere else.
763
+ CopyAddrInst *initialization = nullptr ;
764
+ MarkDependenceInst *markDep = nullptr ;
765
+ for (auto *use : stack->getUses ()) {
766
+ // Since we removed the `dealloc_stack`s from the capture arguments,
767
+ // the only uses of this stack slot should be the initialization, the
768
+ // partial application, and possibly a mark_dependence from the
769
+ // buffer to the partial application.
770
+ if (use->getUser () == newPA) {
771
+ continue ;
772
+ }
773
+ if (auto mark = dyn_cast<MarkDependenceInst>(use->getUser ())) {
774
+ // If we're marking dependence of the current partial_apply on this
775
+ // stack slot, that's fine.
776
+ if (mark->getValue () != newPA
777
+ || mark->getBase () != stack) {
778
+ LLVM_DEBUG (llvm::dbgs () << " -- had unexpected mark_dependence use\n " ;
779
+ use->getUser ()->print (llvm::dbgs ());
780
+ llvm::dbgs () << " \n " );
781
+
782
+ break ;
783
+ }
784
+ markDep = mark;
785
+ continue ;
786
+ }
787
+
788
+ // If we saw more than just the initialization, this isn't a pattern we
789
+ // recognize.
790
+ if (initialization) {
791
+ LLVM_DEBUG (llvm::dbgs () << " -- had non-initialization, non-partial-apply use\n " ;
792
+ use->getUser ()->print (llvm::dbgs ());
793
+ llvm::dbgs () << " \n " );
794
+
795
+ initialization = nullptr ;
796
+ break ;
797
+ }
798
+ if (auto possibleInit = dyn_cast<CopyAddrInst>(use->getUser ())) {
799
+ // Should copy the source and initialize the destination.
800
+ if (possibleInit->isTakeOfSrc ()
801
+ || !possibleInit->isInitializationOfDest ()) {
802
+ LLVM_DEBUG (llvm::dbgs () << " -- had non-initialization, non-partial-apply use\n " ;
803
+ use->getUser ()->print (llvm::dbgs ());
804
+ llvm::dbgs () << " \n " );
805
+
806
+ break ;
807
+ }
808
+ // This is the initialization if there are no other uses.
809
+ initialization = possibleInit;
810
+ continue ;
811
+ }
812
+ LLVM_DEBUG (llvm::dbgs () << " -- unrecognized use\n " );
813
+ break ;
814
+ }
815
+ if (!initialization) {
816
+ LLVM_DEBUG (llvm::dbgs () << " -- failed to find single initializing use\n " );
817
+ continue ;
818
+ }
819
+
820
+ // The source should have no writes in the duration of the partial_apply's
821
+ // liveness.
822
+ auto orig = initialization->getSrc ();
823
+ LLVM_DEBUG (llvm::dbgs () << " ++ found original:\n " ;
824
+ orig->print (llvm::dbgs ());
825
+ llvm::dbgs () << " \n " );
826
+
827
+ bool origIsUnusedDuringClosureLifetime = true ;
828
+
829
+ class OrigUnusedDuringClosureLifetimeWalker final
830
+ : public TransitiveAddressWalker
831
+ {
832
+ SSAPrunedLiveness &closureLiveness;
833
+ bool &origIsUnusedDuringClosureLifetime;
834
+ public:
835
+ OrigUnusedDuringClosureLifetimeWalker (SSAPrunedLiveness &closureLiveness,
836
+ bool &origIsUnusedDuringClosureLifetime)
837
+ : closureLiveness(closureLiveness),
838
+ origIsUnusedDuringClosureLifetime (origIsUnusedDuringClosureLifetime)
839
+ {}
840
+
841
+ virtual bool visitUse (Operand *origUse) override {
842
+ LLVM_DEBUG (llvm::dbgs () << " looking at use\n " ;
843
+ origUse->getUser ()->printInContext (llvm::dbgs ());
844
+ llvm::dbgs () << " \n " );
845
+
846
+ // If the user doesn't write to memory, then it's harmless.
847
+ if (!origUse->getUser ()->mayWriteToMemory ()) {
848
+ return true ;
849
+ }
850
+ if (closureLiveness.isWithinBoundary (origUse->getUser ())) {
851
+ origIsUnusedDuringClosureLifetime = false ;
852
+ LLVM_DEBUG (llvm::dbgs () << " -- original has other possibly writing use during closure lifetime\n " ;
853
+ origUse->getUser ()->print (llvm::dbgs ());
854
+ llvm::dbgs () << " \n " );
855
+ return false ;
856
+ }
857
+ return true ;
858
+ }
859
+ };
860
+
861
+ OrigUnusedDuringClosureLifetimeWalker origUseWalker (closureLiveness,
862
+ origIsUnusedDuringClosureLifetime);
863
+ auto walkResult = std::move(origUseWalker).walk(orig);
864
+
865
+ if (walkResult == AddressUseKind::Unknown
866
+ || !origIsUnusedDuringClosureLifetime) {
867
+ continue ;
868
+ }
869
+
870
+ // OK, we can use the original. Eliminate the copy and replace it with the
871
+ // original.
872
+ LLVM_DEBUG (llvm::dbgs() << " ++ replacing with original!\n " );
873
+ arg.set(orig);
874
+ if (markDep) {
875
+ markDep->setBase (orig);
876
+ }
877
+ initialization->eraseFromParent ();
878
+ stack->eraseFromParent ();
879
+ borrowedOriginals.insert(orig);
880
+ }
881
+
716
882
/* DEBUG
717
883
llvm::errs() << "=== found lifetime ends for\n";
718
884
closureOp->dump();
@@ -724,7 +890,11 @@ static SILValue tryRewriteToPartialApplyStack(
724
890
*/
725
891
SILBuilderWithScope builder (std::next (destroy->getIterator ()));
726
892
insertDestroyOfCapturedArguments (newPA, builder,
727
- [&](SILValue arg) -> bool { return true ; },
893
+ [&](SILValue arg) -> bool {
894
+ // Don't need to destroy if we borrowed
895
+ // in place .
896
+ return !borrowedOriginals.count (arg);
897
+ },
728
898
newPA->getLoc ());
729
899
}
730
900
/* DEBUG
@@ -750,7 +920,12 @@ static SILValue tryRewriteToPartialApplyStack(
750
920
return closureOp;
751
921
752
922
insertDeallocOfCapturedArguments (
753
- newPA, dominanceAnalysis->get (closureUser->getFunction ()));
923
+ newPA, dominanceAnalysis->get (closureUser->getFunction ()),
924
+ [&](SILValue arg) -> bool {
925
+ // Don't need to destroy if we borrowed
926
+ // in place.
927
+ return !borrowedOriginals.count (arg);
928
+ });
754
929
755
930
return closureOp;
756
931
}
0 commit comments