17
17
#include " swift/AST/Expr.h"
18
18
#include " swift/AST/Stmt.h"
19
19
#include " swift/ClangImporter/ClangModule.h"
20
- #include " swift/SIL/BasicBlockData.h"
21
20
#include " swift/SIL/BasicBlockBits.h"
22
- #include " swift/SIL/SILValue .h"
21
+ #include " swift/SIL/BasicBlockData .h"
23
22
#include " swift/SIL/InstructionUtils.h"
23
+ #include " swift/SIL/MemAccessUtils.h"
24
24
#include " swift/SIL/SILArgument.h"
25
25
#include " swift/SIL/SILBuilder.h"
26
+ #include " swift/SIL/SILValue.h"
26
27
#include " swift/SILOptimizer/PassManager/Passes.h"
27
28
#include " swift/SILOptimizer/PassManager/Transforms.h"
28
29
#include " swift/SILOptimizer/Utils/CFGOptUtils.h"
@@ -451,6 +452,21 @@ namespace {
451
452
void doIt ();
452
453
453
454
private:
455
+ // / Injects `hop_to_executor` instructions into the function after
456
+ // / `self` becomes fully initialized, only if the current function
457
+ // / is an actor initializer that requires this, and if TheMemory
458
+ // / corresponds to `self`.
459
+ void injectActorHops ();
460
+
461
+ // / Given an initializing block and the live-in availability of TheMemory,
462
+ // / this function injects a `hop_to_executor` instruction soon after the
463
+ // / first non-load use of TheMemory that fully-initializes it.
464
+ // / An "initializing block" is one that definitely contains a such a
465
+ // / non-load use, e.g., because its live-in set is *not* all-Yes, but its
466
+ // / live-out set is all-Yes.
467
+ void injectActorHopForBlock (SILLocation loc,
468
+ SILBasicBlock *initializingBlock,
469
+ AvailabilitySet const &liveInAvailability);
454
470
455
471
void emitSelfConsumedDiagnostic (SILInstruction *Inst);
456
472
@@ -476,7 +492,6 @@ namespace {
476
492
bool *FailedSelfUse = nullptr ,
477
493
bool *FullyUninitialized = nullptr );
478
494
479
-
480
495
void handleStoreUse (unsigned UseID);
481
496
void handleLoadUse (const DIMemoryUse &Use);
482
497
void handleLoadForTypeOfSelfUse (DIMemoryUse &Use);
@@ -487,12 +502,11 @@ namespace {
487
502
bool diagnoseReturnWithoutInitializingStoredProperties (
488
503
const SILInstruction *Inst, SILLocation loc, const DIMemoryUse &Use);
489
504
490
- // / Returns true iff the use involves 'self' in a restricted kind of
505
+ // / Returns true iff TheMemory involves 'self' in a restricted kind of
491
506
// / actor initializer. If a non-null Kind pointer was passed in,
492
507
// / then the specific kind of restricted actor initializer will be
493
508
// / written out. Otherwise, the None initializer kind will be written out.
494
- bool isRestrictedActorInitSelf (const DIMemoryUse& Use,
495
- ActorInitKind *Kind = nullptr ) const ;
509
+ bool isRestrictedActorInitSelf (ActorInitKind *kind = nullptr ) const ;
496
510
497
511
void reportIllegalUseForActorInit (const DIMemoryUse &Use,
498
512
ActorInitKind ActorKind,
@@ -783,6 +797,196 @@ void LifetimeChecker::diagnoseInitError(const DIMemoryUse &Use,
783
797
diagnose (Module, TheMemory.getLoc (), diag::variable_defined_here, isLet);
784
798
}
785
799
800
+ // / Injects a hop_to_executor instruction after the specified insertion point.
801
+ static void injectHopToExecutorAfter (SILLocation loc,
802
+ SILBasicBlock::iterator insertPt,
803
+ SILValue actor, bool needsBorrow = true ) {
804
+
805
+ LLVM_DEBUG (llvm::dbgs () << " hop-injector: requested insertion after "
806
+ << *insertPt);
807
+
808
+ // While insertAfter can handle terminators, it cannot handle ones that lead
809
+ // to a block with multiple predecessors. I don't expect that a terminator
810
+ // could initialize a stored property at all: a try_apply passed the property
811
+ // as an inout would not be a valid use until _after_ the property has been
812
+ // initialized.
813
+ assert (!isa<TermInst>(*insertPt) && " unexpected hop-inject after terminator" );
814
+
815
+ auto injectAfter = [&](SILInstruction *insertPt) -> void {
816
+ LLVM_DEBUG (llvm::dbgs () << " hop-injector: injecting after " << *insertPt);
817
+ SILBuilderWithScope::insertAfter (insertPt, [&](SILBuilder &b) {
818
+ if (needsBorrow)
819
+ actor = b.createBeginBorrow (loc, actor);
820
+
821
+ b.createHopToExecutor (loc, actor);
822
+
823
+ if (needsBorrow)
824
+ b.createEndBorrow (loc, actor);
825
+ });
826
+ };
827
+
828
+ // ////
829
+ // NOTE: We prefer to inject a hop outside of any access regions, so that
830
+ // the dynamic access-set is empty. This is a best-effort to avoid injecting
831
+ // it inside of a region, but does not account for overlapping accesses, etc.
832
+ // But, I cannot think of a way to create an overlapping access with a stored
833
+ // property when it is first initialized, because it's not valid to pass those
834
+ // inout or capture them in a closure. - kavon
835
+
836
+ SILInstruction *cur = &*insertPt;
837
+ BeginAccessInst *access = nullptr ;
838
+
839
+ // Finds begin_access instructions that need hops placed after its end_access.
840
+ auto getBeginAccess = [](SILValue v) -> BeginAccessInst * {
841
+ return dyn_cast<BeginAccessInst>(getAccessScope (v));
842
+ };
843
+
844
+ // If this insertion-point is after a store-like instruction, look for a
845
+ // begin_access corresponding to the destination.
846
+ if (auto *store = dyn_cast<StoreInst>(cur)) {
847
+ access = getBeginAccess (store->getDest ());
848
+ } else if (auto *assign = dyn_cast<AssignInst>(cur)) {
849
+ access = getBeginAccess (assign->getDest ());
850
+ }
851
+
852
+ // If we found a begin_access, then we need to inject the hop after
853
+ // all of the corresponding end_accesses.
854
+ if (access) {
855
+ for (auto *endAccess : access->getEndAccesses ())
856
+ injectAfter (endAccess);
857
+
858
+ return ;
859
+ }
860
+
861
+ // ////
862
+ // Otherwise, we just put the hop after the original insertion point.
863
+ return injectAfter (cur);
864
+ }
865
+
866
+ void LifetimeChecker::injectActorHopForBlock (
867
+ SILLocation loc, SILBasicBlock *block,
868
+ AvailabilitySet const &liveInAvailability) {
869
+ // Tracks status of each element of TheMemory as we scan through the block,
870
+ // starting with the initial availability at the block's entry-point.
871
+ AvailabilitySet localAvail = liveInAvailability;
872
+
873
+ auto bbi = block->begin (); // our cursor and eventual insertion-point.
874
+ const auto bbe = block->end ();
875
+ for (; bbi != bbe; ++bbi) {
876
+ auto *inst = &*bbi;
877
+
878
+ auto result = NonLoadUses.find (inst);
879
+ if (result == NonLoadUses.end ())
880
+ continue ; // not a possible store
881
+
882
+ // Mark the tuple elements involved in this use as defined.
883
+ for (unsigned use : result->second ) {
884
+ auto const &instUse = Uses[use];
885
+ for (unsigned i = instUse.FirstElement ;
886
+ i < instUse.FirstElement + instUse.NumElements ; ++i)
887
+ localAvail.set (i, DIKind::Yes);
888
+ }
889
+
890
+ // Stop if we found the instruction that initializes TheMemory.
891
+ if (localAvail.isAllYes ())
892
+ break ;
893
+ }
894
+
895
+ // Make sure we found the initializing use of TheMemory.
896
+ assert (bbi != bbe && " this block is not initializing?" );
897
+
898
+ injectHopToExecutorAfter (loc, bbi, TheMemory.getUninitializedValue ());
899
+ }
900
+
901
+ void LifetimeChecker::injectActorHops () {
902
+ auto ctor = TheMemory.getActorInitSelf ();
903
+
904
+ // Must be `self` within an actor's designated initializer.
905
+ if (!ctor)
906
+ return ;
907
+
908
+ // If the initializer has restricted use of `self`, then no hops are needed.
909
+ if (isRestrictedActorInitSelf ())
910
+ return ;
911
+
912
+ // Even if there are no stored properties to initialize, we still need a hop.
913
+ // We insert this directly after the mark_uninitialized instruction, so
914
+ // that it happens as early as `self` is available.`
915
+ if (TheMemory.getNumElements () == 0 ) {
916
+ auto *selfDef = TheMemory.getUninitializedValue ();
917
+ return injectHopToExecutorAfter (ctor, selfDef->getIterator (), selfDef);
918
+ }
919
+
920
+ // Returns true iff a block returns normally from the initializer,
921
+ // which means that it returns `self` in some way (perhaps optional-wrapped).
922
+ auto returnsSelf = [](SILBasicBlock &block) -> bool {
923
+ // This check relies on the fact that failable initializers are emitted by
924
+ // SILGen to perform their return in a fresh block with either:
925
+ // 1. No non-load uses of `self` (e.g., failing case)
926
+ // 2. An all-Yes in-availability. (e.g., success case)
927
+ return block.getTerminator ()->getTermKind () == TermKind::ReturnInst;
928
+ };
929
+
930
+ // ///
931
+ // Step 1: Find initializing blocks, which are blocks that contain a store
932
+ // to TheMemory that fully-initializes it, and build the Map.
933
+
934
+ // We determine whether a block is "initializing" by inspecting the "in" and
935
+ // "out" availability sets of the block. If the block goes from No / Partial
936
+ // "in" to Yes "out", then some instruction in the block caused TheMemory to
937
+ // become fully-initialized, so we record that block and its in-availability
938
+ // to scan the block more precisely later in the next Step.
939
+ for (auto &block : F) {
940
+ auto &info = getBlockInfo (&block);
941
+
942
+ if (!info.HasNonLoadUse ) {
943
+ LLVM_DEBUG (llvm::dbgs ()
944
+ << " hop-injector: rejecting bb" << block.getDebugID ()
945
+ << " b/c no non-load uses.\n " );
946
+ continue ; // could not be an initializing block.
947
+ }
948
+
949
+ // Determine if this `block` is initializing, that is:
950
+ //
951
+ // InAvailability ≡ merge(OutAvailability(predecessors(block)))
952
+ // ≠ Yes
953
+ // AND
954
+ // OutAvailability(block) = Yes OR returnsSelf(block)
955
+ //
956
+ // A block with no predecessors has in-avail of non-Yes.
957
+ // A block with no successors has an out-avail of non-Yes, since
958
+ // availability is not computed for it.
959
+
960
+ auto outSet = info.OutAvailability ;
961
+ if (!outSet.isAllYes () && !returnsSelf (block)) {
962
+ LLVM_DEBUG (llvm::dbgs ()
963
+ << " hop-injector: rejecting bb" << block.getDebugID ()
964
+ << " b/c non-Yes OUT avail\n " );
965
+ continue ; // then this block never sees TheMemory initialized.
966
+ }
967
+
968
+ AvailabilitySet inSet (outSet.size ());
969
+ auto const &predecessors = block.getPredecessorBlocks ();
970
+ for (auto *pred : predecessors)
971
+ inSet.mergeIn (getBlockInfo (pred).OutAvailability );
972
+
973
+ if (inSet.isAllYes ()) {
974
+ LLVM_DEBUG (llvm::dbgs ()
975
+ << " hop-injector: rejecting bb" << block.getDebugID ()
976
+ << " b/c all-Yes IN avail\n " );
977
+ continue ; // then this block always sees TheMemory initialized.
978
+ }
979
+
980
+ LLVM_DEBUG (llvm::dbgs () << " hop-injector: bb" << block.getDebugID ()
981
+ << " is initializing block with in-availability: "
982
+ << inSet << " \n " );
983
+
984
+ // Step 2: Scan the initializing block to find the first non-load use that
985
+ // fully-initializes TheMemory, and insert the hop there.
986
+ injectActorHopForBlock (ctor, &block, inSet);
987
+ }
988
+ }
989
+
786
990
void LifetimeChecker::doIt () {
787
991
// With any escapes tallied up, we can work through all the uses, checking
788
992
// for definitive initialization, promoting loads, rewriting assigns, and
@@ -851,6 +1055,9 @@ void LifetimeChecker::doIt() {
851
1055
// If we emitted an error, there is no reason to proceed with load promotion.
852
1056
if (!EmittedErrorLocs.empty ()) return ;
853
1057
1058
+ // Insert hop_to_executor instructions for actor initializers, if needed.
1059
+ injectActorHops ();
1060
+
854
1061
// If the memory object has nontrivial type, then any destroy/release of the
855
1062
// memory object will destruct the memory. If the memory (or some element
856
1063
// thereof) is not initialized on some path, the bad things happen. Process
@@ -893,7 +1100,7 @@ void LifetimeChecker::handleLoadUse(const DIMemoryUse &Use) {
893
1100
if (!isInitializedAtUse (Use, &IsSuperInitComplete, &FailedSelfUse))
894
1101
return handleLoadUseFailure (Use, IsSuperInitComplete, FailedSelfUse);
895
1102
// Check if it involves 'self' in a restricted actor init.
896
- if (isRestrictedActorInitSelf (Use, &ActorKind))
1103
+ if (isRestrictedActorInitSelf (&ActorKind))
897
1104
return handleLoadUseFailureForActorInit (Use, ActorKind);
898
1105
}
899
1106
@@ -1223,7 +1430,7 @@ void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
1223
1430
}
1224
1431
1225
1432
// 'self' cannot be passed 'inout' from some kinds of actor initializers.
1226
- if (isRestrictedActorInitSelf (Use, &ActorKind))
1433
+ if (isRestrictedActorInitSelf (&ActorKind))
1227
1434
reportIllegalUseForActorInit (Use, ActorKind, " be passed 'inout'" ,
1228
1435
/* suggestConvenienceInit=*/ false );
1229
1436
@@ -1399,7 +1606,7 @@ void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
1399
1606
&FullyUninitialized)) {
1400
1607
1401
1608
// no escaping uses of 'self' are allowed in restricted actor inits.
1402
- if (isRestrictedActorInitSelf (Use, &ActorKind))
1609
+ if (isRestrictedActorInitSelf (&ActorKind))
1403
1610
reportIllegalUseForActorInit (Use, ActorKind, " be captured by a closure" ,
1404
1611
/* suggestConvenienceInit=*/ true );
1405
1612
@@ -1786,18 +1993,17 @@ bool LifetimeChecker::diagnoseReturnWithoutInitializingStoredProperties(
1786
1993
return true ;
1787
1994
}
1788
1995
1789
- bool LifetimeChecker::isRestrictedActorInitSelf (const DIMemoryUse& Use,
1790
- ActorInitKind *Kind) const {
1996
+ bool LifetimeChecker::isRestrictedActorInitSelf (ActorInitKind *kind) const {
1791
1997
1792
1998
auto result = [&](ActorInitKind k, bool isRestricted) -> bool {
1793
- if (Kind )
1794
- *Kind = k;
1999
+ if (kind )
2000
+ *kind = k;
1795
2001
return isRestricted;
1796
2002
};
1797
2003
1798
2004
// Currently: being synchronous, or global-actor isolated, means the actor's
1799
2005
// self is restricted within the init.
1800
- if (auto *ctor = TheMemory.isActorInitSelf ()) {
2006
+ if (auto *ctor = TheMemory.getActorInitSelf ()) {
1801
2007
if (getActorIsolation (ctor).isGlobalActor ()) // global-actor isolated?
1802
2008
return result (ActorInitKind::GlobalActorIsolated, true );
1803
2009
else if (!ctor->hasAsync ()) // synchronous?
0 commit comments