Skip to content

Commit 735064a

Browse files
authored
Merge pull request #37075 from kavon/actor-initializers
2 parents 4fc2333 + f722ccd commit 735064a

File tree

8 files changed

+596
-25
lines changed

8 files changed

+596
-25
lines changed

include/swift/AST/DiagnosticsSIL.def

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,18 @@ ERROR(variable_defer_use_uninit,none,
168168
ERROR(self_closure_use_uninit,none,
169169
"'self' captured by a closure before all members were initialized", ())
170170

171+
/// false == sync; true == global-actor isolated
172+
ERROR(self_use_actor_init,none,
173+
"this use of actor 'self' %select{can only|cannot}0 appear in "
174+
"%select{an async|a global-actor isolated}0 initializer",
175+
(bool))
176+
ERROR(self_disallowed_actor_init,none,
177+
"actor 'self' %select{can only|cannot}0 %1 from "
178+
"%select{an async|a global-actor isolated}0 initializer",
179+
(bool, StringRef))
180+
181+
182+
171183

172184
ERROR(variable_addrtaken_before_initialized,none,
173185
"address of %select{variable|constant}1 '%0' taken before it is"

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,23 @@ bool DIMemoryObjectInfo::isElementLetProperty(unsigned Element) const {
432432
return false;
433433
}
434434

435+
ConstructorDecl *DIMemoryObjectInfo::isActorInitSelf() const {
436+
// is it 'self'?
437+
if (!MemoryInst->isVar())
438+
if (auto decl =
439+
dyn_cast_or_null<ClassDecl>(getASTType()->getAnyNominal()))
440+
// is it for an actor?
441+
if (decl->isActor() && !decl->isDistributedActor()) // FIXME(78484431) skip distributed actors for now, until their initializers are fixed!
442+
if (auto *silFn = MemoryInst->getFunction())
443+
// is it a designated initializer?
444+
if (auto *ctor = dyn_cast_or_null<ConstructorDecl>(
445+
silFn->getDeclContext()->getAsDecl()))
446+
if (ctor->isDesignatedInit())
447+
return ctor;
448+
449+
return nullptr;
450+
}
451+
435452
//===----------------------------------------------------------------------===//
436453
// DIMemoryUse Implementation
437454
//===----------------------------------------------------------------------===//

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,10 @@ class DIMemoryObjectInfo {
164164
return false;
165165
}
166166

167+
/// Returns the initializer if the memory use is 'self' and appears in an
168+
/// actor's designated initializer. Otherwise, returns nullptr.
169+
ConstructorDecl *isActorInitSelf() const;
170+
167171
/// True if this memory object is the 'self' of a derived class initializer.
168172
bool isDerivedClassSelf() const { return MemoryInst->isDerivedClassSelf(); }
169173

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,13 @@ namespace {
384384

385385
using BlockStates = BasicBlockData<LiveOutBlockState>;
386386

387+
enum class ActorInitKind {
388+
None, // not an actor init
389+
Plain, // synchronous, not isolated to global-actor
390+
PlainAsync, // asynchronous, not isolated to global-actor
391+
GlobalActorIsolated // isolated to global-actor (sync or async).
392+
};
393+
387394
/// LifetimeChecker - This is the main heavy lifting for definite
388395
/// initialization checking of a memory object.
389396
class LifetimeChecker {
@@ -480,6 +487,20 @@ namespace {
480487
bool diagnoseReturnWithoutInitializingStoredProperties(
481488
const SILInstruction *Inst, SILLocation loc, const DIMemoryUse &Use);
482489

490+
/// Returns true iff the use involves 'self' in a restricted kind of
491+
/// actor initializer. If a non-null Kind pointer was passed in,
492+
/// then the specific kind of restricted actor initializer will be
493+
/// written out. Otherwise, the None initializer kind will be written out.
494+
bool isRestrictedActorInitSelf(const DIMemoryUse& Use,
495+
ActorInitKind *Kind = nullptr) const;
496+
497+
void reportIllegalUseForActorInit(const DIMemoryUse &Use,
498+
ActorInitKind ActorKind,
499+
StringRef ProblemDesc) const;
500+
501+
void handleLoadUseFailureForActorInit(const DIMemoryUse &Use,
502+
ActorInitKind ActorKind) const;
503+
483504
void handleLoadUseFailure(const DIMemoryUse &Use,
484505
bool SuperInitDone,
485506
bool FailedSelfUse);
@@ -866,9 +887,13 @@ void LifetimeChecker::doIt() {
866887

867888
void LifetimeChecker::handleLoadUse(const DIMemoryUse &Use) {
868889
bool IsSuperInitComplete, FailedSelfUse;
890+
ActorInitKind ActorKind = ActorInitKind::None;
869891
// If the value is not definitively initialized, emit an error.
870892
if (!isInitializedAtUse(Use, &IsSuperInitComplete, &FailedSelfUse))
871893
return handleLoadUseFailure(Use, IsSuperInitComplete, FailedSelfUse);
894+
// Check if it involves 'self' in a restricted actor init.
895+
if (isRestrictedActorInitSelf(Use, &ActorKind))
896+
return handleLoadUseFailureForActorInit(Use, ActorKind);
872897
}
873898

874899
static void replaceValueMetatypeInstWithMetatypeArgument(
@@ -1177,6 +1202,7 @@ static FullApplySite findApply(SILInstruction *I, bool &isSelfParameter) {
11771202

11781203
void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
11791204
bool IsSuperInitDone, FailedSelfUse;
1205+
ActorInitKind ActorKind = ActorInitKind::None;
11801206

11811207
// inout uses are generally straight-forward: the memory must be initialized
11821208
// before the "address" is passed as an l-value.
@@ -1195,6 +1221,10 @@ void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
11951221
return;
11961222
}
11971223

1224+
// 'self' cannot be passed 'inout' from some kinds of actor initializers.
1225+
if (isRestrictedActorInitSelf(Use, &ActorKind))
1226+
reportIllegalUseForActorInit(Use, ActorKind, "be passed 'inout'");
1227+
11981228
// One additional check: 'let' properties may never be passed inout, because
11991229
// they are only allowed to have their initial value set, not a subsequent
12001230
// overwrite.
@@ -1357,12 +1387,19 @@ static bool isLoadForReturn(SingleValueInstruction *loadInst) {
13571387
}
13581388

13591389
void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
1390+
13601391
// The value must be fully initialized at all escape points. If not, diagnose
13611392
// the error.
13621393
bool SuperInitDone, FailedSelfUse, FullyUninitialized;
1394+
ActorInitKind ActorKind = ActorInitKind::None;
13631395

13641396
if (isInitializedAtUse(Use, &SuperInitDone, &FailedSelfUse,
13651397
&FullyUninitialized)) {
1398+
1399+
// no escaping uses of 'self' are allowed in restricted actor inits.
1400+
if (isRestrictedActorInitSelf(Use, &ActorKind))
1401+
reportIllegalUseForActorInit(Use, ActorKind, "be captured by a closure");
1402+
13661403
return;
13671404
}
13681405

@@ -1746,6 +1783,89 @@ bool LifetimeChecker::diagnoseReturnWithoutInitializingStoredProperties(
17461783
return true;
17471784
}
17481785

1786+
bool LifetimeChecker::isRestrictedActorInitSelf(const DIMemoryUse& Use,
1787+
ActorInitKind *Kind) const {
1788+
1789+
auto result = [&](ActorInitKind k, bool isRestricted) -> bool {
1790+
if (Kind)
1791+
*Kind = k;
1792+
return isRestricted;
1793+
};
1794+
1795+
// Currently: being synchronous, or global-actor isolated, means the actor's
1796+
// self is restricted within the init.
1797+
if (auto *ctor = TheMemory.isActorInitSelf()) {
1798+
if (getActorIsolation(ctor).isGlobalActor()) // global-actor isolated?
1799+
return result(ActorInitKind::GlobalActorIsolated, true);
1800+
else if (!ctor->hasAsync()) // synchronous?
1801+
return result(ActorInitKind::Plain, true);
1802+
else
1803+
return result(ActorInitKind::PlainAsync, false);
1804+
}
1805+
1806+
return result(ActorInitKind::None, false);
1807+
}
1808+
1809+
void LifetimeChecker::reportIllegalUseForActorInit(
1810+
const DIMemoryUse &Use,
1811+
ActorInitKind ActorKind,
1812+
StringRef ProblemDesc) const {
1813+
switch(ActorKind) {
1814+
case ActorInitKind::None:
1815+
case ActorInitKind::PlainAsync:
1816+
llvm::report_fatal_error("this actor init is never problematic!");
1817+
1818+
case ActorInitKind::Plain:
1819+
diagnose(Module, Use.Inst->getLoc(), diag::self_disallowed_actor_init,
1820+
false, ProblemDesc);
1821+
break;
1822+
1823+
case ActorInitKind::GlobalActorIsolated:
1824+
diagnose(Module, Use.Inst->getLoc(), diag::self_disallowed_actor_init,
1825+
true, ProblemDesc);
1826+
break;
1827+
}
1828+
}
1829+
1830+
void LifetimeChecker::handleLoadUseFailureForActorInit(
1831+
const DIMemoryUse &Use,
1832+
ActorInitKind ActorKind) const {
1833+
assert(TheMemory.isAnyInitSelf());
1834+
SILInstruction *Inst = Use.Inst;
1835+
1836+
// While enum instructions have no side-effects, they do wrap-up `self`
1837+
// such that it can escape our analysis. So, enum instruction uses are only
1838+
// acceptable if the enum is part of a specific pattern of usage that is
1839+
// generated for a failable initializer.
1840+
if (auto *enumInst = dyn_cast<EnumInst>(Inst)) {
1841+
if (isFailableInitReturnUseOfEnum(enumInst))
1842+
return;
1843+
1844+
// Otherwise, if the use has no possibility of side-effects, then its OK.
1845+
} else if (!Inst->mayHaveSideEffects()) {
1846+
return;
1847+
1848+
// While loads can have side-effects, we are not concerned by them here.
1849+
} else if (isa<LoadInst>(Inst)) {
1850+
return;
1851+
}
1852+
1853+
// Everything else is disallowed!
1854+
switch(ActorKind) {
1855+
case ActorInitKind::None:
1856+
case ActorInitKind::PlainAsync:
1857+
llvm::report_fatal_error("this actor init is never problematic!");
1858+
1859+
case ActorInitKind::Plain:
1860+
diagnose(Module, Use.Inst->getLoc(), diag::self_use_actor_init, false);
1861+
break;
1862+
1863+
case ActorInitKind::GlobalActorIsolated:
1864+
diagnose(Module, Use.Inst->getLoc(), diag::self_use_actor_init, true);
1865+
break;
1866+
}
1867+
}
1868+
17491869
/// Check and diagnose various failures when a load use is not fully
17501870
/// initialized.
17511871
///

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -2154,12 +2154,14 @@ namespace {
21542154
}
21552155

21562156
/// Check whether we are in an actor's initializer or deinitializer.
2157-
static bool isActorInitOrDeInitContext(const DeclContext *dc) {
2157+
/// \returns nullptr iff we are not in such a declaration. Otherwise,
2158+
/// returns a pointer to the declaration.
2159+
static AbstractFunctionDecl const* isActorInitOrDeInitContext(const DeclContext *dc) {
21582160
while (true) {
21592161
// Non-Sendable closures are considered part of the enclosing context.
21602162
if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
21612163
if (isSendableClosure(closure, /*forActorIsolation=*/false))
2162-
return false;
2164+
return nullptr;
21632165

21642166
dc = dc->getParent();
21652167
continue;
@@ -2169,26 +2171,33 @@ namespace {
21692171
// If this is an initializer or deinitializer of an actor, we're done.
21702172
if ((isa<ConstructorDecl>(func) || isa<DestructorDecl>(func)) &&
21712173
getSelfActorDecl(dc->getParent()))
2172-
return true;
2174+
return func;
21732175

21742176
// Non-Sendable local functions are considered part of the enclosing
21752177
// context.
21762178
if (func->getDeclContext()->isLocalContext()) {
21772179
if (auto fnType =
21782180
func->getInterfaceType()->getAs<AnyFunctionType>()) {
21792181
if (fnType->isSendable())
2180-
return false;
2182+
return nullptr;
21812183

21822184
dc = dc->getParent();
21832185
continue;
21842186
}
21852187
}
21862188
}
21872189

2188-
return false;
2190+
return nullptr;
21892191
}
21902192
}
21912193

2194+
static bool isConvenienceInit(AbstractFunctionDecl const* fn) {
2195+
if (auto ctor = dyn_cast_or_null<ConstructorDecl>(fn))
2196+
return ctor->isConvenienceInit();
2197+
2198+
return false;
2199+
}
2200+
21922201
/// Check a reference to a local or global.
21932202
bool checkNonMemberReference(
21942203
ConcreteDeclRef valueRef, SourceLoc loc, DeclRefExpr *declRefExpr) {
@@ -2262,9 +2271,10 @@ namespace {
22622271
bool continueToCheckingLocalIsolation = false;
22632272
// Must reference distributed actor-isolated state on 'self'.
22642273
//
2265-
// FIXME: For now, be loose about access to "self" in actor
2266-
// initializers/deinitializers. We'll want to tighten this up once
2267-
// we decide exactly how the model should go.
2274+
// FIXME(78484431): For now, be loose about access to "self" in actor
2275+
// initializers/deinitializers for distributed actors.
2276+
// We'll want to tighten this up once we decide exactly
2277+
// how the model should go.
22682278
auto isolatedActor = getIsolatedActor(base);
22692279
if (!isolatedActor &&
22702280
!(isolatedActor.isActorSelf() &&
@@ -2322,14 +2332,12 @@ namespace {
23222332
if (isolatedActor)
23232333
return false;
23242334

2325-
// An instance member of an actor can be referenced from an initializer
2326-
// or deinitializer.
2327-
// FIXME: We likely want to narrow this down to only stored properties,
2328-
// but this matches existing behavior.
2329-
if (isolatedActor.isActorSelf() &&
2330-
member->isInstanceMember() &&
2331-
isActorInitOrDeInitContext(getDeclContext()))
2332-
return false;
2335+
// An instance member of an actor can be referenced from an actor's
2336+
// designated initializer or deinitializer.
2337+
if (isolatedActor.isActorSelf() && member->isInstanceMember())
2338+
if (auto fn = isActorInitOrDeInitContext(getDeclContext()))
2339+
if (!isConvenienceInit(fn))
2340+
return false;
23332341

23342342
// An escaping partial application of something that is part of
23352343
// the actor's isolated state is never permitted.
@@ -2947,9 +2955,11 @@ static Optional<MemberIsolationPropagation> getMemberIsolationPropagation(
29472955
case DeclKind::PatternBinding:
29482956
case DeclKind::EnumCase:
29492957
case DeclKind::EnumElement:
2950-
case DeclKind::Constructor:
29512958
return MemberIsolationPropagation::GlobalActor;
29522959

2960+
case DeclKind::Constructor:
2961+
return MemberIsolationPropagation::AnyIsolation;
2962+
29532963
case DeclKind::Func:
29542964
case DeclKind::Accessor:
29552965
case DeclKind::Subscript:
@@ -3032,6 +3042,13 @@ ActorIsolation ActorIsolationRequest::evaluate(
30323042
}
30333043
}
30343044

3045+
// An actor's convenience init is assumed to be actor-independent.
3046+
if (auto nominal = value->getDeclContext()->getSelfNominalTypeDecl())
3047+
if (nominal->isActor())
3048+
if (auto ctor = dyn_cast<ConstructorDecl>(value))
3049+
if (ctor->isConvenienceInit())
3050+
defaultIsolation = ActorIsolation::forIndependent();
3051+
30353052
// Function used when returning an inferred isolation.
30363053
auto inferredIsolation = [&](
30373054
ActorIsolation inferred, bool onlyGlobal = false) {
@@ -3191,10 +3208,6 @@ bool HasIsolatedSelfRequest::evaluate(
31913208

31923209
switch (*memberIsolation) {
31933210
case MemberIsolationPropagation::GlobalActor:
3194-
// Treat constructors as being actor-isolated... for now.
3195-
if (isa<ConstructorDecl>(value))
3196-
break;
3197-
31983211
return false;
31993212

32003213
case MemberIsolationPropagation::AnyIsolation:
@@ -3232,6 +3245,13 @@ bool HasIsolatedSelfRequest::evaluate(
32323245
}
32333246
}
32343247

3248+
// In an actor's convenience init, self is not isolated.
3249+
if (auto ctor = dyn_cast<ConstructorDecl>(value)) {
3250+
if (ctor->isConvenienceInit()) {
3251+
return false;
3252+
}
3253+
}
3254+
32353255
return true;
32363256
}
32373257

test/Concurrency/Runtime/actor_keypaths.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,14 @@ actor Page {
1717
set { numWordsMem.pointee = newValue }
1818
}
1919

20-
init(_ words : Int) {
20+
private init(withWords words : Int) {
2121
initialNumWords = words
2222
numWordsMem = .allocate(capacity: 1)
2323
numWordsMem.initialize(to: words)
24+
}
25+
26+
convenience init(_ words: Int) {
27+
self.init(withWords: words)
2428
numWords = words
2529
}
2630

0 commit comments

Comments
 (0)