Skip to content

Commit 2d69bc8

Browse files
committed
respect nonisolated on an async actor initializer
Previously, we were ignoring the explicit `nonisolated` attribute on an actor initializer. This only has a noticable effect if the initializer is `async`: an actor hop is being emitted even though isolation was explicitly not requested. While we internally treat the isolation of an unadorned actor initializer as if it were `nonisolated`, if the programmer explicitly requests it, then it should be honored in an async init too. Thus, this patch tells definite initialization to skip the insertion of the hop, instead treating a `nonisolated` initializer as though it has the escaping-use restriction. resolves rdar://84164173
1 parent a1daee5 commit 2d69bc8

File tree

6 files changed

+144
-50
lines changed

6 files changed

+144
-50
lines changed

include/swift/AST/ActorIsolation.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,8 @@ class ActorIsolation {
106106
operator Kind() const { return getKind(); }
107107

108108
bool isUnspecified() const { return kind == Unspecified; }
109+
110+
bool isIndependent() const { return kind == Independent; }
109111

110112
NominalTypeDecl *getActor() const {
111113
assert(getKind() == ActorInstance || getKind() == DistributedActorInstance);

include/swift/AST/DiagnosticsSIL.def

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,12 @@ 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",
171+
ERROR(self_disallowed_nonisolated_actor_init,none,
172+
"%select{|this use of }0actor 'self' cannot %2 %select{from|in}0 "
173+
"%select{a non-isolated, designated|a global-actor isolated}1 initializer",
174+
(bool, bool, StringRef))
175+
ERROR(self_disallowed_plain_actor_init,none,
176+
"%select{|this use of }0actor 'self' can only %1 %select{from|in}0 an async initializer",
179177
(bool, StringRef))
180178
NOTE(actor_convenience_init,none,
181179
"convenience initializers allow non-isolated use of 'self' once "

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 84 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,10 @@ namespace {
388388

389389
enum class ActorInitKind {
390390
None, // not an actor init
391-
Plain, // synchronous, not isolated to global-actor
392-
PlainAsync, // asynchronous, not isolated to global-actor
393-
GlobalActorIsolated // isolated to global-actor (sync or async).
391+
Plain, // synchronous initializer
392+
PlainAsync, // asynchronous initializer
393+
GlobalActorIsolated, // isolated to global-actor (any async-ness)
394+
NonIsolated // explicitly 'nonisolated' (any async-ness)
394395
};
395396

396397
/// LifetimeChecker - This is the main heavy lifting for definite
@@ -509,10 +510,18 @@ namespace {
509510
/// written out. Otherwise, the None initializer kind will be written out.
510511
bool isRestrictedActorInitSelf(ActorInitKind *kind = nullptr) const;
511512

513+
/// Emits a diagnostic to flag an illegal use of self in an actor init.
514+
/// The message is tuned based on the input arguments.
515+
/// \param ProblemDesc fills in the blank for "cannot ___ in an actor init"
516+
/// \param suggestConvenienceInit if true, will emit a note about
517+
/// convenience inits
518+
/// \param specifyThisUse if true, precedes the message with "this use of"
519+
/// to be more precise about the problem.
512520
void reportIllegalUseForActorInit(const DIMemoryUse &Use,
513521
ActorInitKind ActorKind,
514522
StringRef ProblemDesc,
515-
bool suggestConvenienceInit) const;
523+
bool suggestConvenienceInit,
524+
bool specifyThisUse) const;
516525

517526
void handleLoadUseFailureForActorInit(const DIMemoryUse &Use,
518527
ActorInitKind ActorKind) const;
@@ -1450,7 +1459,8 @@ void LifetimeChecker::handleInOutUse(const DIMemoryUse &Use) {
14501459
// 'self' cannot be passed 'inout' from some kinds of actor initializers.
14511460
if (isRestrictedActorInitSelf(&ActorKind))
14521461
reportIllegalUseForActorInit(Use, ActorKind, "be passed 'inout'",
1453-
/*suggestConvenienceInit=*/false);
1462+
/*suggestConvenienceInit=*/false,
1463+
/*specifyThisUse=*/false);
14541464

14551465
// One additional check: 'let' properties may never be passed inout, because
14561466
// they are only allowed to have their initial value set, not a subsequent
@@ -1626,7 +1636,8 @@ void LifetimeChecker::handleEscapeUse(const DIMemoryUse &Use) {
16261636
// no escaping uses of 'self' are allowed in restricted actor inits.
16271637
if (isRestrictedActorInitSelf(&ActorKind))
16281638
reportIllegalUseForActorInit(Use, ActorKind, "be captured by a closure",
1629-
/*suggestConvenienceInit=*/true);
1639+
/*suggestConvenienceInit=*/true,
1640+
/*specifyThisUse=*/false);
16301641

16311642
return;
16321643
}
@@ -2018,16 +2029,39 @@ bool LifetimeChecker::isRestrictedActorInitSelf(ActorInitKind *kind) const {
20182029
*kind = k;
20192030
return isRestricted;
20202031
};
2032+
2033+
// Determines whether the constructor was explicitly marked as `nonisolated`
2034+
auto isExplicitlyNonIsolated = [] (ConstructorDecl const* ctor) -> bool {
2035+
if (auto *attr = ctor->getAttrs().getAttribute<NonisolatedAttr>())
2036+
return !attr->isImplicit();
2037+
return false;
2038+
};
20212039

2022-
// Currently: being synchronous, or global-actor isolated, means the actor's
2023-
// self is restricted within the init.
2040+
// Currently: being synchronous, or having an explicitly specified isolation,
2041+
// means the actor's self is restricted within the init.
20242042
if (auto *ctor = TheMemory.getActorInitSelf()) {
2025-
if (getActorIsolation(ctor).isGlobalActor()) // global-actor isolated?
2026-
return result(ActorInitKind::GlobalActorIsolated, true);
2027-
else if (!ctor->hasAsync()) // synchronous?
2028-
return result(ActorInitKind::Plain, true);
2029-
else
2030-
return result(ActorInitKind::PlainAsync, false);
2043+
auto isolation = getActorIsolation(ctor);
2044+
switch (isolation.getKind()) {
2045+
case ActorIsolation::Unspecified:
2046+
llvm_unreachable("actor decl with unspecified isolation?");
2047+
2048+
case ActorIsolation::Independent:
2049+
if (isExplicitlyNonIsolated(ctor))
2050+
return result(ActorInitKind::NonIsolated, true);
2051+
2052+
llvm_unreachable("implicitly nonisolated actor init?");
2053+
2054+
case ActorIsolation::ActorInstance:
2055+
case ActorIsolation::DistributedActorInstance:
2056+
if (ctor->hasAsync())
2057+
return result(ActorInitKind::PlainAsync, false);
2058+
else
2059+
return result(ActorInitKind::Plain, true);
2060+
2061+
case ActorIsolation::GlobalActor:
2062+
case ActorIsolation::GlobalActorUnsafe:
2063+
return result(ActorInitKind::GlobalActorIsolated, true);
2064+
};
20312065
}
20322066

20332067
return result(ActorInitKind::None, false);
@@ -2037,25 +2071,47 @@ void LifetimeChecker::reportIllegalUseForActorInit(
20372071
const DIMemoryUse &Use,
20382072
ActorInitKind ActorKind,
20392073
StringRef ProblemDesc,
2040-
bool suggestConvenienceInit) const {
2074+
bool suggestConvenienceInit,
2075+
bool specifyThisUse) const {
2076+
2077+
ConstructorDecl *asyncNonIsoCtor = nullptr;
2078+
20412079
switch(ActorKind) {
20422080
case ActorInitKind::None:
20432081
case ActorInitKind::PlainAsync:
20442082
llvm::report_fatal_error("this actor init is never problematic!");
20452083

20462084
case ActorInitKind::Plain:
2047-
diagnose(Module, Use.Inst->getLoc(), diag::self_disallowed_actor_init,
2048-
false, ProblemDesc)
2085+
diagnose(Module, Use.Inst->getLoc(), diag::self_disallowed_plain_actor_init,
2086+
specifyThisUse, ProblemDesc)
20492087
.warnUntilSwiftVersion(6);
20502088
break;
20512089

2052-
case ActorInitKind::GlobalActorIsolated:
2053-
diagnose(Module, Use.Inst->getLoc(), diag::self_disallowed_actor_init,
2054-
true, ProblemDesc)
2055-
.warnUntilSwiftVersion(6);
2090+
case ActorInitKind::NonIsolated:
2091+
if (auto *ctor = TheMemory.getActorInitSelf())
2092+
if (ctor->hasAsync())
2093+
asyncNonIsoCtor = ctor;
2094+
2095+
LLVM_FALLTHROUGH;
2096+
2097+
case ActorInitKind::GlobalActorIsolated: {
2098+
bool isGlobalInstead = ActorKind == ActorInitKind::GlobalActorIsolated;
2099+
auto diag = diagnose(Module, Use.Inst->getLoc(),
2100+
diag::self_disallowed_nonisolated_actor_init, specifyThisUse,
2101+
isGlobalInstead, ProblemDesc);
2102+
2103+
// Emit a fix-it to remove `nonisolated`.
2104+
// For an async init, it would fix their program.
2105+
if (asyncNonIsoCtor) {
2106+
auto attr = asyncNonIsoCtor->getAttrs().getAttribute<NonisolatedAttr>();
2107+
diag.fixItRemove(attr->getRange());
2108+
}
2109+
2110+
diag.warnUntilSwiftVersion(6);
20562111
break;
20572112
}
2058-
2113+
};
2114+
20592115
if (suggestConvenienceInit)
20602116
diagnose(Module, Use.Inst->getLoc(), diag::actor_convenience_init);
20612117
}
@@ -2088,28 +2144,15 @@ void LifetimeChecker::handleLoadUseFailureForActorInit(
20882144
return;
20892145

20902146
// Everything else is disallowed!
2091-
switch(ActorKind) {
2092-
case ActorInitKind::None:
2093-
case ActorInitKind::PlainAsync:
2094-
llvm::report_fatal_error("this actor init is never problematic!");
2095-
2096-
case ActorInitKind::Plain:
2097-
diagnose(Module, Use.Inst->getLoc(), diag::self_use_actor_init, false)
2098-
.warnUntilSwiftVersion(6);
2099-
break;
2100-
2101-
case ActorInitKind::GlobalActorIsolated:
2102-
diagnose(Module, Use.Inst->getLoc(), diag::self_use_actor_init, true)
2103-
.warnUntilSwiftVersion(6);
2104-
break;
2105-
}
2106-
2147+
21072148
// We cannot easily determine which argument in the call the use of 'self'
21082149
// appears in. If we could, then we could determine whether the callee
21092150
// is 'isolated' to that parameter, in order to avoid suggesting a convenience
2110-
// init in those cases. Thus, the phrasing of the note should be informative.
2111-
if (isa<ApplyInst>(Inst))
2112-
diagnose(Module, Use.Inst->getLoc(), diag::actor_convenience_init);
2151+
// init in those cases. For now, just always suggest it if it's an apply.
2152+
bool suggestConvenience = isa<ApplyInst>(Inst);
2153+
2154+
reportIllegalUseForActorInit(Use, ActorKind, "appear", suggestConvenience,
2155+
/*specifyThisUse=*/true);
21132156
}
21142157

21152158
/// Check and diagnose various failures when a load use is not fully

test/Concurrency/actor_definite_init.swift

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,4 +403,25 @@ actor EscapeArtist {
403403

404404
func isolatedMethod() { x += 1 }
405405
nonisolated func nonisolated() {}
406-
}
406+
}
407+
408+
@available(SwiftStdlib 5.5, *)
409+
actor Ahmad {
410+
func f() {}
411+
412+
nonisolated init(v1: Void) {
413+
Task.detached { await self.f() } // expected-warning {{actor 'self' cannot be captured by a closure from a non-isolated, designated initializer}}
414+
// expected-note@-1 {{convenience initializers allow non-isolated use of 'self' once initialized}}
415+
416+
f() // expected-warning {{this use of actor 'self' cannot appear in a non-isolated, designated initializer}}
417+
// expected-note@-1 {{convenience initializers allow non-isolated use of 'self' once initialized}}
418+
}
419+
420+
nonisolated init(v2: Void) async {
421+
Task.detached { await self.f() } // expected-warning {{actor 'self' cannot be captured by a closure from a non-isolated, designated initializer}} {{3-15=}}
422+
// expected-note@-1 {{convenience initializers allow non-isolated use of 'self' once initialized}}
423+
424+
f() // expected-warning {{this use of actor 'self' cannot appear in a non-isolated, designated initializer}}
425+
// expected-note@-1 {{convenience initializers allow non-isolated use of 'self' once initialized}}
426+
}
427+
}

test/Concurrency/actor_definite_init_swift6.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -402,3 +402,24 @@ actor EscapeArtist {
402402
func isolatedMethod() { x += 1 }
403403
nonisolated func nonisolated() {}
404404
}
405+
406+
@available(SwiftStdlib 5.5, *)
407+
actor Ahmad {
408+
func f() {}
409+
410+
nonisolated init(v1: Void) {
411+
Task.detached { await self.f() } // expected-error {{actor 'self' cannot be captured by a closure from a non-isolated, designated initializer}}
412+
// expected-note@-1 {{convenience initializers allow non-isolated use of 'self' once initialized}}
413+
414+
f() // expected-error {{this use of actor 'self' cannot appear in a non-isolated, designated initializer}}
415+
// expected-note@-1 {{convenience initializers allow non-isolated use of 'self' once initialized}}
416+
}
417+
418+
nonisolated init(v2: Void) async {
419+
Task.detached { await self.f() } // expected-error {{actor 'self' cannot be captured by a closure from a non-isolated, designated initializer}} {{3-15=}}
420+
// expected-note@-1 {{convenience initializers allow non-isolated use of 'self' once initialized}}
421+
422+
f() // expected-error {{this use of actor 'self' cannot appear in a non-isolated, designated initializer}}
423+
// expected-note@-1 {{convenience initializers allow non-isolated use of 'self' once initialized}}
424+
}
425+
}

test/SILOptimizer/definite_init_actor.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,3 +243,12 @@ actor SomeActor {
243243
// CHECK: } // end sil function '$s4test9SomeActorCACyYacfc'
244244
init() async {}
245245
}
246+
247+
actor Ahmad {
248+
var x: Int = 0
249+
250+
// CHECK-LABEL: sil hidden @$s4test5AhmadCACyYacfc : $@convention(method) @async (@owned Ahmad) -> @owned Ahmad {
251+
// CHECK: store {{%[0-9]+}} to {{%[0-9]+}} : $*Int
252+
// CHECK: } // end sil function '$s4test5AhmadCACyYacfc'
253+
nonisolated init() async {} // no hop should appear here because of explicit nonisolated marking.
254+
}

0 commit comments

Comments
 (0)