Skip to content

Commit f722ccd

Browse files
committed
restrict uses of 'self' in certain actor initializers
A uniqueness rule for 'self' is enforced throughout the following types of actor initializers: 1. synchronous 2. global-actor isolated This means that 'self' cannot be used for anything, even after 'self' is fully initialized, other than for access to stored properties. Method calls are considered escaping uses of 'self', since it is passed implicitly to the method of computed property when invoked. Also, an actor's convenience init now treats self as nonisolated. This provides a way to perform follow-up work after self is fully initialized, when creating the actor from a synchronous context. Resolves rdar://78790369
1 parent d3d87e8 commit f722ccd

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
@@ -2143,12 +2143,14 @@ namespace {
21432143
}
21442144

21452145
/// Check whether we are in an actor's initializer or deinitializer.
2146-
static bool isActorInitOrDeInitContext(const DeclContext *dc) {
2146+
/// \returns nullptr iff we are not in such a declaration. Otherwise,
2147+
/// returns a pointer to the declaration.
2148+
static AbstractFunctionDecl const* isActorInitOrDeInitContext(const DeclContext *dc) {
21472149
while (true) {
21482150
// Non-Sendable closures are considered part of the enclosing context.
21492151
if (auto closure = dyn_cast<AbstractClosureExpr>(dc)) {
21502152
if (isSendableClosure(closure, /*forActorIsolation=*/false))
2151-
return false;
2153+
return nullptr;
21522154

21532155
dc = dc->getParent();
21542156
continue;
@@ -2158,26 +2160,33 @@ namespace {
21582160
// If this is an initializer or deinitializer of an actor, we're done.
21592161
if ((isa<ConstructorDecl>(func) || isa<DestructorDecl>(func)) &&
21602162
getSelfActorDecl(dc->getParent()))
2161-
return true;
2163+
return func;
21622164

21632165
// Non-Sendable local functions are considered part of the enclosing
21642166
// context.
21652167
if (func->getDeclContext()->isLocalContext()) {
21662168
if (auto fnType =
21672169
func->getInterfaceType()->getAs<AnyFunctionType>()) {
21682170
if (fnType->isSendable())
2169-
return false;
2171+
return nullptr;
21702172

21712173
dc = dc->getParent();
21722174
continue;
21732175
}
21742176
}
21752177
}
21762178

2177-
return false;
2179+
return nullptr;
21782180
}
21792181
}
21802182

2183+
static bool isConvenienceInit(AbstractFunctionDecl const* fn) {
2184+
if (auto ctor = dyn_cast_or_null<ConstructorDecl>(fn))
2185+
return ctor->isConvenienceInit();
2186+
2187+
return false;
2188+
}
2189+
21812190
/// Check a reference to a local or global.
21822191
bool checkNonMemberReference(
21832192
ConcreteDeclRef valueRef, SourceLoc loc, DeclRefExpr *declRefExpr) {
@@ -2251,9 +2260,10 @@ namespace {
22512260
bool continueToCheckingLocalIsolation = false;
22522261
// Must reference distributed actor-isolated state on 'self'.
22532262
//
2254-
// FIXME: For now, be loose about access to "self" in actor
2255-
// initializers/deinitializers. We'll want to tighten this up once
2256-
// we decide exactly how the model should go.
2263+
// FIXME(78484431): For now, be loose about access to "self" in actor
2264+
// initializers/deinitializers for distributed actors.
2265+
// We'll want to tighten this up once we decide exactly
2266+
// how the model should go.
22572267
auto isolatedActor = getIsolatedActor(base);
22582268
if (!isolatedActor &&
22592269
!(isolatedActor.isActorSelf() &&
@@ -2311,14 +2321,12 @@ namespace {
23112321
if (isolatedActor)
23122322
return false;
23132323

2314-
// An instance member of an actor can be referenced from an initializer
2315-
// or deinitializer.
2316-
// FIXME: We likely want to narrow this down to only stored properties,
2317-
// but this matches existing behavior.
2318-
if (isolatedActor.isActorSelf() &&
2319-
member->isInstanceMember() &&
2320-
isActorInitOrDeInitContext(getDeclContext()))
2321-
return false;
2324+
// An instance member of an actor can be referenced from an actor's
2325+
// designated initializer or deinitializer.
2326+
if (isolatedActor.isActorSelf() && member->isInstanceMember())
2327+
if (auto fn = isActorInitOrDeInitContext(getDeclContext()))
2328+
if (!isConvenienceInit(fn))
2329+
return false;
23222330

23232331
// An escaping partial application of something that is part of
23242332
// the actor's isolated state is never permitted.
@@ -2936,9 +2944,11 @@ static Optional<MemberIsolationPropagation> getMemberIsolationPropagation(
29362944
case DeclKind::PatternBinding:
29372945
case DeclKind::EnumCase:
29382946
case DeclKind::EnumElement:
2939-
case DeclKind::Constructor:
29402947
return MemberIsolationPropagation::GlobalActor;
29412948

2949+
case DeclKind::Constructor:
2950+
return MemberIsolationPropagation::AnyIsolation;
2951+
29422952
case DeclKind::Func:
29432953
case DeclKind::Accessor:
29442954
case DeclKind::Subscript:
@@ -3021,6 +3031,13 @@ ActorIsolation ActorIsolationRequest::evaluate(
30213031
}
30223032
}
30233033

3034+
// An actor's convenience init is assumed to be actor-independent.
3035+
if (auto nominal = value->getDeclContext()->getSelfNominalTypeDecl())
3036+
if (nominal->isActor())
3037+
if (auto ctor = dyn_cast<ConstructorDecl>(value))
3038+
if (ctor->isConvenienceInit())
3039+
defaultIsolation = ActorIsolation::forIndependent();
3040+
30243041
// Function used when returning an inferred isolation.
30253042
auto inferredIsolation = [&](
30263043
ActorIsolation inferred, bool onlyGlobal = false) {
@@ -3180,10 +3197,6 @@ bool HasIsolatedSelfRequest::evaluate(
31803197

31813198
switch (*memberIsolation) {
31823199
case MemberIsolationPropagation::GlobalActor:
3183-
// Treat constructors as being actor-isolated... for now.
3184-
if (isa<ConstructorDecl>(value))
3185-
break;
3186-
31873200
return false;
31883201

31893202
case MemberIsolationPropagation::AnyIsolation:
@@ -3221,6 +3234,13 @@ bool HasIsolatedSelfRequest::evaluate(
32213234
}
32223235
}
32233236

3237+
// In an actor's convenience init, self is not isolated.
3238+
if (auto ctor = dyn_cast<ConstructorDecl>(value)) {
3239+
if (ctor->isConvenienceInit()) {
3240+
return false;
3241+
}
3242+
}
3243+
32243244
return true;
32253245
}
32263246

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)