Skip to content

Commit f2b0885

Browse files
committed
[Concurrency] Implement more precise actor isolation for isolated argument
values. Teach ActorIsolation that a `nil` isolated argument is statically nonisolated, and a reference to GlobalActor.shared statically has global actor isolation. This change also models arbitrary actor instance isolation using VarDecls when possible, which allows comparing two ActorIsolation values that may represent different actor instances. Previously, ActorIsolation was modeled only by storing the nominal actor type and the parameter index, so the actor isolation value for two different actors was considered to be equal. Now, the nominal actor type is only used for isolated `self` in cases where there is no implicit self parameter decl, such as for stored properties.
1 parent f732661 commit f2b0885

File tree

5 files changed

+145
-39
lines changed

5 files changed

+145
-39
lines changed

include/swift/AST/ActorIsolation.h

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -106,9 +106,7 @@ class ActorIsolation {
106106
return ActorIsolation(unsafe ? NonisolatedUnsafe : Nonisolated, nullptr);
107107
}
108108

109-
static ActorIsolation forActorInstanceSelf(NominalTypeDecl *actor) {
110-
return ActorIsolation(ActorInstance, actor, 0);
111-
}
109+
static ActorIsolation forActorInstanceSelf(ValueDecl *decl);
112110

113111
static ActorIsolation forActorInstanceParameter(NominalTypeDecl *actor,
114112
unsigned parameterIndex) {
@@ -121,9 +119,7 @@ class ActorIsolation {
121119
}
122120

123121
static ActorIsolation forActorInstanceParameter(Expr *actor,
124-
unsigned parameterIndex) {
125-
return ActorIsolation(ActorInstance, actor, parameterIndex + 1);
126-
}
122+
unsigned parameterIndex);
127123

128124
static ActorIsolation forActorInstanceCapture(VarDecl *capturedActor) {
129125
return ActorIsolation(ActorInstance, capturedActor, 0);
@@ -227,27 +223,12 @@ class ActorIsolation {
227223
/// Substitute into types within the actor isolation.
228224
ActorIsolation subst(SubstitutionMap subs) const;
229225

226+
static bool isEqual(const ActorIsolation &lhs,
227+
const ActorIsolation &rhs);
228+
230229
friend bool operator==(const ActorIsolation &lhs,
231230
const ActorIsolation &rhs) {
232-
if (lhs.isGlobalActor() && rhs.isGlobalActor())
233-
return areTypesEqual(lhs.globalActor, rhs.globalActor);
234-
235-
if (lhs.getKind() != rhs.getKind())
236-
return false;
237-
238-
switch (lhs.getKind()) {
239-
case Nonisolated:
240-
case NonisolatedUnsafe:
241-
case Unspecified:
242-
return true;
243-
244-
case ActorInstance:
245-
return (lhs.getActor() == rhs.getActor() &&
246-
lhs.parameterIndex == rhs.parameterIndex);
247-
248-
case GlobalActor:
249-
llvm_unreachable("Global actors handled above");
250-
}
231+
return ActorIsolation::isEqual(lhs, rhs);
251232
}
252233

253234
friend bool operator!=(const ActorIsolation &lhs,

lib/AST/Decl.cpp

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11233,6 +11233,56 @@ ActorIsolation::ActorIsolation(Kind kind, Expr *actor,
1123311233
: actorInstance(actor), kind(kind), isolatedByPreconcurrency(false),
1123411234
silParsed(false), parameterIndex(parameterIndex) {}
1123511235

11236+
ActorIsolation
11237+
ActorIsolation::forActorInstanceParameter(Expr *actor,
11238+
unsigned parameterIndex) {
11239+
auto &ctx = actor->getType()->getASTContext();
11240+
11241+
if (auto *isolation = dyn_cast<CurrentContextIsolationExpr>(actor))
11242+
actor = isolation->getActor();
11243+
11244+
// An isolated value of `nil` is statically nonisolated.
11245+
// FIXME: Also allow 'Optional.none'
11246+
if (dyn_cast<NilLiteralExpr>(actor))
11247+
return ActorIsolation::forNonisolated(/*unsafe*/false);
11248+
11249+
// An isolated value of `<global actor type>.shared` is statically
11250+
// global actor isolated.
11251+
if (auto *memberRef = dyn_cast<MemberRefExpr>(actor)) {
11252+
// Check that the member declaration witnesses the `shared`
11253+
// requirement of the `GlobalActor` protocol.
11254+
auto declRef = memberRef->getDecl();
11255+
auto baseType =
11256+
memberRef->getBase()->getType()->getMetatypeInstanceType();
11257+
if (auto globalActor = ctx.getProtocol(KnownProtocolKind::GlobalActor)) {
11258+
auto *dc = declRef.getDecl()->getDeclContext();
11259+
auto *module = dc->getParentModule();
11260+
auto conformance = module->checkConformance(baseType, globalActor);
11261+
if (conformance &&
11262+
conformance.getWitnessByName(baseType, ctx.Id_shared) == declRef) {
11263+
return ActorIsolation::forGlobalActor(baseType);
11264+
}
11265+
}
11266+
}
11267+
11268+
return ActorIsolation(ActorInstance, actor, parameterIndex + 1);
11269+
}
11270+
11271+
ActorIsolation
11272+
ActorIsolation::forActorInstanceSelf(ValueDecl *decl) {
11273+
if (auto *fn = dyn_cast<AbstractFunctionDecl>(decl))
11274+
return ActorIsolation(ActorInstance, fn->getImplicitSelfDecl(), 0);
11275+
11276+
if (auto *storage = dyn_cast<AbstractStorageDecl>(decl)) {
11277+
if (auto *fn = storage->getAccessor(AccessorKind::Get)) {
11278+
return ActorIsolation(ActorInstance, fn->getImplicitSelfDecl(), 0);
11279+
}
11280+
}
11281+
11282+
auto *dc = decl->getDeclContext();
11283+
return ActorIsolation(ActorInstance, dc->getSelfNominalTypeDecl(), 0);
11284+
}
11285+
1123611286
NominalTypeDecl *ActorIsolation::getActor() const {
1123711287
assert(getKind() == ActorInstance);
1123811288

@@ -11295,6 +11345,39 @@ bool ActorIsolation::isDistributedActor() const {
1129511345
return getKind() == ActorInstance && getActor()->isDistributedActor();
1129611346
}
1129711347

11348+
bool ActorIsolation::isEqual(const ActorIsolation &lhs,
11349+
const ActorIsolation &rhs) {
11350+
if (lhs.getKind() != rhs.getKind())
11351+
return false;
11352+
11353+
switch (lhs.getKind()) {
11354+
case Nonisolated:
11355+
case NonisolatedUnsafe:
11356+
case Unspecified:
11357+
return true;
11358+
11359+
case ActorInstance: {
11360+
auto *lhsActor = lhs.getActorInstance();
11361+
auto *rhsActor = rhs.getActorInstance();
11362+
if (lhsActor && rhsActor) {
11363+
// FIXME: This won't work for arbitrary isolated parameter captures.
11364+
if ((lhsActor->isSelfParameter() && rhsActor->isSelfParamCapture()) ||
11365+
(lhsActor->isSelfParamCapture() && rhsActor->isSelfParameter())) {
11366+
return true;
11367+
}
11368+
}
11369+
11370+
// The parameter index doesn't matter; only the actor instance
11371+
// values must be equal.
11372+
return (lhs.getActor() == rhs.getActor() &&
11373+
lhs.actorInstance == rhs.actorInstance);
11374+
}
11375+
11376+
case GlobalActor:
11377+
return areTypesEqual(lhs.globalActor, rhs.globalActor);
11378+
}
11379+
}
11380+
1129811381
BuiltinTupleDecl::BuiltinTupleDecl(Identifier Name, DeclContext *Parent)
1129911382
: NominalTypeDecl(DeclKind::BuiltinTuple, Parent, Name, SourceLoc(),
1130011383
ArrayRef<InheritedEntry>(), nullptr) {}

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,8 @@ bool swift::usesFlowSensitiveIsolation(AbstractFunctionDecl const *fn) {
141141
return true;
142142

143143
// construct an isolation corresponding to the type.
144-
auto actorTypeIso = ActorIsolation::forActorInstanceSelf(nominal);
144+
auto actorTypeIso = ActorIsolation::forActorInstanceSelf(
145+
const_cast<AbstractFunctionDecl *>(fn));
145146

146147
return requiresFlowIsolation(actorTypeIso, cast<ConstructorDecl>(fn));
147148
}
@@ -3295,15 +3296,15 @@ namespace {
32953296
KnownProtocolKind::Actor);
32963297
}
32973298

3298-
auto origArg = const_cast<Expr *>(arg->findOriginalValue());
3299+
auto calleeIsolation = ActorIsolation::forActorInstanceParameter(
3300+
const_cast<Expr *>(arg->findOriginalValue()), paramIdx);
32993301

3300-
// FIXME: Also allow 'Optional.none'
3301-
if (dyn_cast<NilLiteralExpr>(origArg)) {
3302-
mayExitToNonisolated = true;
3303-
} else {
3304-
unsatisfiedIsolation =
3305-
ActorIsolation::forActorInstanceParameter(origArg, paramIdx);
3306-
mayExitToNonisolated = false;
3302+
if (getContextIsolation() != calleeIsolation) {
3303+
if (calleeIsolation.isNonisolated()) {
3304+
mayExitToNonisolated = true;
3305+
} else {
3306+
unsatisfiedIsolation = calleeIsolation;
3307+
}
33073308
}
33083309

33093310
if (!fnType->getExtInfo().isAsync())
@@ -4661,7 +4662,7 @@ ActorIsolation ActorIsolationRequest::evaluate(
46614662
if (evaluateOrDefault(evaluator, HasIsolatedSelfRequest{value}, false)) {
46624663
auto actor = value->getDeclContext()->getSelfNominalTypeDecl();
46634664
assert(actor && "could not find the actor that 'self' is isolated to");
4664-
return ActorIsolation::forActorInstanceSelf(actor);
4665+
return ActorIsolation::forActorInstanceSelf(value);
46654666
}
46664667

46674668
// If this declaration has an isolated parameter, it's isolated to that
@@ -6092,7 +6093,7 @@ static ActorIsolation getActorIsolationForReference(
60926093
// as needing to enter the actor.
60936094
if (auto nominal = ctor->getDeclContext()->getSelfNominalTypeDecl()) {
60946095
if (nominal->isAnyActor())
6095-
return ActorIsolation::forActorInstanceSelf(nominal);
6096+
return ActorIsolation::forActorInstanceSelf(decl);
60966097
}
60976098

60986099
// Fall through to treat initializers like any other declaration.
@@ -6108,7 +6109,7 @@ static ActorIsolation getActorIsolationForReference(
61086109
declIsolation.isNonisolated()) {
61096110
if (auto nominal = var->getDeclContext()->getSelfNominalTypeDecl()) {
61106111
if (nominal->isAnyActor())
6111-
return ActorIsolation::forActorInstanceSelf(nominal);
6112+
return ActorIsolation::forActorInstanceSelf(decl);
61126113

61136114
auto nominalIsolation = getActorIsolation(nominal);
61146115
if (nominalIsolation.isGlobalActor())

test/Concurrency/isolated_parameters.swift

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,7 @@ func isolated_generic_ok_1<T: Actor>(_ t: isolated T) {}
363363

364364

365365
class NotSendable {} // expected-complete-note 5 {{class 'NotSendable' does not conform to the 'Sendable' protocol}}
366+
// expected-note@-1 {{class 'NotSendable' does not conform to the 'Sendable' protocol}}
366367

367368
func optionalIsolated(_ ns: NotSendable, to actor: isolated (any Actor)?) async {}
368369
func optionalIsolatedSync(_ ns: NotSendable, to actor: isolated (any Actor)?) {}
@@ -392,7 +393,7 @@ nonisolated func callFromNonisolated(ns: NotSendable) async {
392393
let myActor = A()
393394

394395
await optionalIsolated(ns, to: myActor)
395-
// expected-complete-warning@-1 {{passing argument of non-sendable type 'NotSendable' into actor-isolated context may introduce data races}}
396+
// expected-complete-warning@-1 {{passing argument of non-sendable type 'NotSendable' outside of main actor-isolated context may introduce data races}}
396397

397398
optionalIsolatedSync(ns, to: myActor)
398399
// expected-error@-1 {{expression is 'async' but is not marked with 'await'}}
@@ -416,3 +417,43 @@ actor A2 {
416417
return self
417418
}
418419
}
420+
421+
func testNonSendableCaptures(ns: NotSendable, a: isolated MyActor) {
422+
Task {
423+
_ = a
424+
_ = ns
425+
}
426+
427+
// FIXME: The `a` in the capture list and `isolated a` are the same,
428+
// but the actor isolation checker doesn't know that.
429+
Task { [a] in
430+
_ = a
431+
_ = ns // expected-warning {{capture of 'ns' with non-sendable type 'NotSendable' in a `@Sendable` closure}}
432+
}
433+
}
434+
435+
436+
@globalActor actor MyGlobal {
437+
static let shared = MyGlobal()
438+
}
439+
440+
func sync(isolatedTo actor: isolated (any Actor)?) {}
441+
442+
func preciseIsolated(a: isolated MyActor) async {
443+
sync(isolatedTo: a)
444+
sync(isolatedTo: nil) // okay from anywhere
445+
446+
Task { @MainActor in
447+
sync(isolatedTo: MainActor.shared)
448+
sync(isolatedTo: nil) // okay from anywhere
449+
}
450+
451+
Task { @MyGlobal in
452+
sync(isolatedTo: MyGlobal.shared)
453+
sync(isolatedTo: nil) // okay from anywhere
454+
}
455+
456+
Task.detached {
457+
sync(isolatedTo: nil) // okay from anywhere
458+
}
459+
}

test/Concurrency/optional_isolated_parameters.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,5 +23,5 @@ func optionalIsolated(to actor: isolated (any Actor)?) {
2323
// CHECK: isolated to Swift.MainActor
2424
// CHECK: isolated to MyActor
2525
optionalIsolated(to: nil)
26-
await optionalIsolated(to: MainActor.shared)
26+
optionalIsolated(to: MainActor.shared)
2727
await optionalIsolated(to: MyActor())

0 commit comments

Comments
 (0)