@@ -451,6 +451,21 @@ namespace {
451
451
void doIt ();
452
452
453
453
private:
454
+ // / Injects `hop_to_executor` instructions into the function after
455
+ // / `self` becomes fully initialized, only if the current function
456
+ // / is an actor initializer that requires this, and if TheMemory
457
+ // / corresponds to `self`.
458
+ void injectActorHops ();
459
+
460
+ // / Given an initializing block and the live-in availability of TheMemory,
461
+ // / this function injects a `hop_to_executor` instruction after the
462
+ // / first non-load use of TheMemory that fully-initializes it.
463
+ // / An "initializing block" is one that definitely contains a such a
464
+ // / non-load use, e.g., because its live-in set is *not* all-Yes, but its
465
+ // / live-out set is all-Yes.
466
+ void injectActorHopForBlock (SILLocation loc,
467
+ SILBasicBlock *initializingBlock,
468
+ AvailabilitySet const &liveInAvailability);
454
469
455
470
void emitSelfConsumedDiagnostic (SILInstruction *Inst);
456
471
@@ -476,7 +491,6 @@ namespace {
476
491
bool *FailedSelfUse = nullptr ,
477
492
bool *FullyUninitialized = nullptr );
478
493
479
-
480
494
void handleStoreUse (unsigned UseID);
481
495
void handleLoadUse (const DIMemoryUse &Use);
482
496
void handleLoadForTypeOfSelfUse (DIMemoryUse &Use);
@@ -487,12 +501,11 @@ namespace {
487
501
bool diagnoseReturnWithoutInitializingStoredProperties (
488
502
const SILInstruction *Inst, SILLocation loc, const DIMemoryUse &Use);
489
503
490
- // / Returns true iff the use involves 'self' in a restricted kind of
504
+ // / Returns true iff TheMemory involves 'self' in a restricted kind of
491
505
// / actor initializer. If a non-null Kind pointer was passed in,
492
506
// / then the specific kind of restricted actor initializer will be
493
507
// / written out. Otherwise, the None initializer kind will be written out.
494
- bool isRestrictedActorInitSelf (const DIMemoryUse& Use,
495
- ActorInitKind *Kind = nullptr ) const ;
508
+ bool isRestrictedActorInitSelf (ActorInitKind *kind = nullptr ) const ;
496
509
497
510
void reportIllegalUseForActorInit (const DIMemoryUse &Use,
498
511
ActorInitKind ActorKind,
@@ -783,6 +796,155 @@ void LifetimeChecker::diagnoseInitError(const DIMemoryUse &Use,
783
796
diagnose (Module, TheMemory.getLoc (), diag::variable_defined_here, isLet);
784
797
}
785
798
799
+ // / Injects a hop_to_executor instruction after the specified insertion point.
800
+ static void injectHopToExecutorAfter (SILLocation loc,
801
+ SILBasicBlock::iterator insertPt,
802
+ SILValue actor, bool needsBorrow = true ) {
803
+
804
+ LLVM_DEBUG (llvm::dbgs () << " hop-injector: inserting hop after " << *insertPt);
805
+
806
+ // While insertAfter can handle terminators, it cannot handle ones that lead
807
+ // to a block with multiple predecessors. I don't expect that a terminator
808
+ // could initialize a stored property at all: a try_apply passed the property
809
+ // as an inout would not be a valid use until _after_ the property has been
810
+ // initialized.
811
+ assert (!isa<TermInst>(*insertPt) && " unexpected hop-inject after terminator" );
812
+
813
+ SILBuilderWithScope::insertAfter (&*insertPt, [&](SILBuilder &b) {
814
+ if (needsBorrow)
815
+ actor = b.createBeginBorrow (loc, actor);
816
+
817
+ b.createHopToExecutor (loc, actor, /* mandatory=*/ false );
818
+
819
+ if (needsBorrow)
820
+ b.createEndBorrow (loc, actor);
821
+ });
822
+ }
823
+
824
+ void LifetimeChecker::injectActorHopForBlock (
825
+ SILLocation loc, SILBasicBlock *block,
826
+ AvailabilitySet const &liveInAvailability) {
827
+ // Tracks status of each element of TheMemory as we scan through the block,
828
+ // starting with the initial availability at the block's entry-point.
829
+ AvailabilitySet localAvail = liveInAvailability;
830
+
831
+ auto bbi = block->begin (); // our cursor and eventual insertion-point.
832
+ const auto bbe = block->end ();
833
+ for (; bbi != bbe; ++bbi) {
834
+ auto *inst = &*bbi;
835
+
836
+ auto result = NonLoadUses.find (inst);
837
+ if (result == NonLoadUses.end ())
838
+ continue ; // not a possible store
839
+
840
+ // Mark the tuple elements involved in this use as defined.
841
+ for (unsigned use : result->second ) {
842
+ auto const &instUse = Uses[use];
843
+ for (unsigned i = instUse.FirstElement ;
844
+ i < instUse.FirstElement + instUse.NumElements ; ++i)
845
+ localAvail.set (i, DIKind::Yes);
846
+ }
847
+
848
+ // Stop if we found the instruction that initializes TheMemory.
849
+ if (localAvail.isAllYes ())
850
+ break ;
851
+ }
852
+
853
+ // Make sure we found the initializing use of TheMemory.
854
+ assert (bbi != bbe && " this block is not initializing?" );
855
+
856
+ injectHopToExecutorAfter (loc, bbi, TheMemory.getUninitializedValue ());
857
+ }
858
+
859
+ void LifetimeChecker::injectActorHops () {
860
+ auto ctor = TheMemory.getActorInitSelf ();
861
+
862
+ // Must be `self` within an actor's designated initializer.
863
+ if (!ctor)
864
+ return ;
865
+
866
+ // If the initializer has restricted use of `self`, then no hops are needed.
867
+ if (isRestrictedActorInitSelf ())
868
+ return ;
869
+
870
+ // Even if there are no stored properties to initialize, we still need a hop.
871
+ // We insert this directly after the mark_uninitialized instruction, so
872
+ // that it happens as early as `self` is available.`
873
+ if (TheMemory.getNumElements () == 0 ) {
874
+ auto *selfDef = TheMemory.getUninitializedValue ();
875
+ return injectHopToExecutorAfter (ctor, selfDef->getIterator (), selfDef);
876
+ }
877
+
878
+ // Returns true iff a block returns normally from the initializer,
879
+ // which means that it returns `self` in some way (perhaps optional-wrapped).
880
+ auto returnsSelf = [](SILBasicBlock &block) -> bool {
881
+ // This check relies on the fact that failable initializers are emitted by
882
+ // SILGen to perform their return in a fresh block with either:
883
+ // 1. No non-load uses of `self` (e.g., failing case)
884
+ // 2. An all-Yes in-availability. (e.g., success case)
885
+ return block.getTerminator ()->getTermKind () == TermKind::ReturnInst;
886
+ };
887
+
888
+ // ///
889
+ // Step 1: Find initializing blocks, which are blocks that contain a store
890
+ // to TheMemory that fully-initializes it, and build the Map.
891
+
892
+ // We determine whether a block is "initializing" by inspecting the "in" and
893
+ // "out" availability sets of the block. If the block goes from No / Partial
894
+ // "in" to Yes "out", then some instruction in the block caused TheMemory to
895
+ // become fully-initialized, so we record that block and its in-availability
896
+ // to scan the block more precisely later in the next Step.
897
+ for (auto &block : F) {
898
+ auto &info = getBlockInfo (&block);
899
+
900
+ if (!info.HasNonLoadUse ) {
901
+ LLVM_DEBUG (llvm::dbgs ()
902
+ << " hop-injector: rejecting bb" << block.getDebugID ()
903
+ << " b/c no non-load uses.\n " );
904
+ continue ; // could not be an initializing block.
905
+ }
906
+
907
+ // Determine if this `block` is initializing, that is:
908
+ //
909
+ // InAvailability ≡ merge(OutAvailability(predecessors(block)))
910
+ // ≠ Yes
911
+ // AND
912
+ // OutAvailability(block) = Yes OR returnsSelf(block)
913
+ //
914
+ // A block with no predecessors has in-avail of non-Yes.
915
+ // A block with no successors has an out-avail of non-Yes, since
916
+ // availability is not computed for it.
917
+
918
+ auto outSet = info.OutAvailability ;
919
+ if (!outSet.isAllYes () && !returnsSelf (block)) {
920
+ LLVM_DEBUG (llvm::dbgs ()
921
+ << " hop-injector: rejecting bb" << block.getDebugID ()
922
+ << " b/c non-Yes OUT avail\n " );
923
+ continue ; // then this block never sees TheMemory initialized.
924
+ }
925
+
926
+ AvailabilitySet inSet (outSet.size ());
927
+ auto const &predecessors = block.getPredecessorBlocks ();
928
+ for (const auto &pred : predecessors)
929
+ inSet.mergeIn (getBlockInfo (pred).OutAvailability );
930
+
931
+ if (inSet.isAllYes ()) {
932
+ LLVM_DEBUG (llvm::dbgs ()
933
+ << " hop-injector: rejecting bb" << block.getDebugID ()
934
+ << " b/c all-Yes IN avail\n " );
935
+ continue ; // then this block always sees TheMemory initialized.
936
+ }
937
+
938
+ LLVM_DEBUG (llvm::dbgs () << " hop-injector: bb" << block.getDebugID ()
939
+ << " is initializing block with in-availability: "
940
+ << inSet << " \n " );
941
+
942
+ // Step 2: Scan the initializing block to find the first non-load use that
943
+ // fully-initializes TheMemory, and insert the hop there.
944
+ injectActorHopForBlock (ctor, &block, inSet);
945
+ }
946
+ }
947
+
786
948
void LifetimeChecker::doIt () {
787
949
// With any escapes tallied up, we can work through all the uses, checking
788
950
// for definitive initialization, promoting loads, rewriting assigns, and
@@ -851,6 +1013,9 @@ void LifetimeChecker::doIt() {
851
1013
// If we emitted an error, there is no reason to proceed with load promotion.
852
1014
if (!EmittedErrorLocs.empty ()) return ;
853
1015
1016
+ // Insert hop_to_executor instructions for actor initializers, if needed.
1017
+ injectActorHops ();
1018
+
854
1019
// If the memory object has nontrivial type, then any destroy/release of the
855
1020
// memory object will destruct the memory. If the memory (or some element
856
1021
// thereof) is not initialized on some path, the bad things happen. Process
@@ -893,7 +1058,7 @@ void LifetimeChecker::handleLoadUse(const DIMemoryUse &Use) {
893
1058
if (!isInitializedAtUse (Use, &IsSuperInitComplete, &FailedSelfUse))
894
1059
return handleLoadUseFailure (Use, IsSuperInitComplete, FailedSelfUse);
895
1060
// Check if it involves 'self' in a restricted actor init.
896
- if (isRestrictedActorInitSelf (Use, &ActorKind))
1061
+ if (isRestrictedActorInitSelf (&ActorKind))
897
1062
return handleLoadUseFailureForActorInit (Use, ActorKind);
898
1063
}
899
1064
@@ -1223,7 +1388,7 @@ void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
1223
1388
}
1224
1389
1225
1390
// 'self' cannot be passed 'inout' from some kinds of actor initializers.
1226
- if (isRestrictedActorInitSelf (Use, &ActorKind))
1391
+ if (isRestrictedActorInitSelf (&ActorKind))
1227
1392
reportIllegalUseForActorInit (Use, ActorKind, " be passed 'inout'" ,
1228
1393
/* suggestConvenienceInit=*/ false );
1229
1394
@@ -1399,7 +1564,7 @@ void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
1399
1564
&FullyUninitialized)) {
1400
1565
1401
1566
// no escaping uses of 'self' are allowed in restricted actor inits.
1402
- if (isRestrictedActorInitSelf (Use, &ActorKind))
1567
+ if (isRestrictedActorInitSelf (&ActorKind))
1403
1568
reportIllegalUseForActorInit (Use, ActorKind, " be captured by a closure" ,
1404
1569
/* suggestConvenienceInit=*/ true );
1405
1570
@@ -1786,18 +1951,17 @@ bool LifetimeChecker::diagnoseReturnWithoutInitializingStoredProperties(
1786
1951
return true ;
1787
1952
}
1788
1953
1789
- bool LifetimeChecker::isRestrictedActorInitSelf (const DIMemoryUse& Use,
1790
- ActorInitKind *Kind) const {
1954
+ bool LifetimeChecker::isRestrictedActorInitSelf (ActorInitKind *kind) const {
1791
1955
1792
1956
auto result = [&](ActorInitKind k, bool isRestricted) -> bool {
1793
- if (Kind )
1794
- *Kind = k;
1957
+ if (kind )
1958
+ *kind = k;
1795
1959
return isRestricted;
1796
1960
};
1797
1961
1798
1962
// Currently: being synchronous, or global-actor isolated, means the actor's
1799
1963
// self is restricted within the init.
1800
- if (auto *ctor = TheMemory.isActorInitSelf ()) {
1964
+ if (auto *ctor = TheMemory.getActorInitSelf ()) {
1801
1965
if (getActorIsolation (ctor).isGlobalActor ()) // global-actor isolated?
1802
1966
return result (ActorInitKind::GlobalActorIsolated, true );
1803
1967
else if (!ctor->hasAsync ()) // synchronous?
0 commit comments