Skip to content

Commit 6bf4a30

Browse files
authored
Merge pull request #59098 from DougGregor/se-0338-5.7
[SE-0338] Implement Sendable checking for SE-0338
2 parents 18eb9d5 + ee26438 commit 6bf4a30

33 files changed

+1632
-1584
lines changed

CHANGELOG.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,39 @@ _**Note:** This is in reverse chronological order, so newer entries are added to
55

66
## Swift 5.7
77

8+
* [SE-0338][]:
9+
10+
Non-isolated async functions now always execute on the global concurrent pool,
11+
so calling a non-isolated async function from actor-isolated code will leave
12+
the actor. For example:
13+
14+
```swift
15+
class C { }
16+
17+
func f(_: C) async { /* always executes on the global concurrent pool */ }
18+
19+
actor A {
20+
func g(c: C) async {
21+
/* always executes on the actor */
22+
print("on the actor")
23+
24+
await f(c)
25+
}
26+
}
27+
```
28+
29+
Prior to this change, the call from `f` to `g` might have started execution of
30+
`g` on the actor, which could lead to actors being busy longer than strictly
31+
necessary. Now, the non-isolated async function will always hop to the global
32+
cooperative pool, not run on the actor. This can result in a behavior change
33+
for programs that assumed that a non-isolated async function called from a
34+
`@MainActor` context will be executed on the main actor, although such
35+
programs were already technically incorrect.
36+
37+
Additionally, when leaving an actor to execution on the global cooperative
38+
pool, `Sendable` checking will be performed, so the compiler will emit a
39+
diagnostic in the call to `f` if `c` is not of `Sendable` type.
40+
841
* [SE-0353][]:
942

1043
Protocols with primary associated types can now be used in existential types,
@@ -9316,6 +9349,7 @@ Swift 1.0
93169349
[SE-0335]: <https://github.com/apple/swift-evolution/blob/main/proposals/0335-existential-any.md>
93179350
[SE-0336]: <https://github.com/apple/swift-evolution/blob/main/proposals/0336-distributed-actor-isolation.md>
93189351
[SE-0337]: <https://github.com/apple/swift-evolution/blob/main/proposals/0337-support-incremental-migration-to-concurrency-checking.md>
9352+
[SE-0338]: <https://github.com/apple/swift-evolution/blob/main/proposals/0338-clarify-execution-non-actor-async.md>
93199353
[SE-0340]: <https://github.com/apple/swift-evolution/blob/main/proposals/0340-swift-noasync.md>
93209354
[SE-0341]: <https://github.com/apple/swift-evolution/blob/main/proposals/0341-opaque-parameters.md>
93219355
[SE-0343]: <https://github.com/apple/swift-evolution/blob/main/proposals/0343-top-level-concurrency.md>

include/swift/AST/ActorIsolation.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,8 @@ class ActorIsolation {
131131
return getKind() == GlobalActor || getKind() == GlobalActorUnsafe;
132132
}
133133

134+
bool isDistributedActor() const;
135+
134136
Type getGlobalActor() const {
135137
assert(isGlobalActor());
136138
return globalActor;

include/swift/AST/DiagnosticsSema.def

Lines changed: 22 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -4470,14 +4470,11 @@ NOTE(note_add_nonisolated_to_decl,none,
44704470
(DeclName, DescriptiveDeclKind))
44714471
NOTE(note_add_async_and_throws_to_decl,none,
44724472
"mark the protocol requirement %0 '%select{|async|throws|async throws}1' "
4473-
"in order witness it with 'distributed' function declared in distributed actor %2",
4474-
(DeclName, unsigned, DeclName))
4473+
"to allow actor-isolated conformances",
4474+
(DeclName, unsigned))
44754475
NOTE(note_add_distributed_to_decl,none,
4476-
"add 'distributed' to %0 to make this %1 witness the protocol requirement",
4476+
"add 'distributed' to %0 to make this %1 satisfy the protocol requirement",
44774477
(DeclName, DescriptiveDeclKind))
4478-
NOTE(note_distributed_requirement_defined_here,none,
4479-
"distributed instance method requirement %0 declared here",
4480-
(DeclName))
44814478
NOTE(note_add_globalactor_to_function,none,
44824479
"add '@%0' to make %1 %2 part of global actor %3",
44834480
(StringRef, DescriptiveDeclKind, DeclName, Type))
@@ -4558,13 +4555,16 @@ ERROR(override_implicit_unowned_executor,none,
45584555
"cannot override an actor's 'unownedExecutor' property that wasn't "
45594556
"explicitly defined", ())
45604557
ERROR(actor_isolated_non_self_reference,none,
4561-
"actor-isolated %0 %1 can not be "
4558+
"%5 %0 %1 can not be "
45624559
"%select{referenced|mutated|used 'inout'}2 "
4563-
"%select{on a non-isolated actor instance|"
4560+
"%select{from outside the actor|on a different actor instance|"
4561+
"on a non-isolated actor instance|"
45644562
"from a Sendable function|from a Sendable closure|"
45654563
"from an 'async let' initializer|from global actor %4|"
4566-
"from the main actor|from a non-isolated context|from a non-isolated autoclosure}3",
4567-
(DescriptiveDeclKind, DeclName, unsigned, unsigned, Type))
4564+
"from the main actor|from a non-isolated context|"
4565+
"from a non-isolated autoclosure}3",
4566+
(DescriptiveDeclKind, DeclName, unsigned, unsigned, Type,
4567+
ActorIsolation))
45684568
ERROR(distributed_actor_isolated_non_self_reference,none,
45694569
"distributed actor-isolated %0 %1 can not be accessed from a "
45704570
"non-isolated context",
@@ -4579,18 +4579,6 @@ ERROR(actor_isolated_inout_state,none,
45794579
ERROR(actor_isolated_mutating_func,none,
45804580
"cannot call mutating async function %0 on actor-isolated %1 %2",
45814581
(DeclName, DescriptiveDeclKind, DeclName))
4582-
ERROR(global_actor_from_instance_actor_context,none,
4583-
"%0 %1 isolated to global actor %2 can not be %select{referenced|mutated|used 'inout'}4"
4584-
" from actor %3 %select{|in a synchronous context}5",
4585-
(DescriptiveDeclKind, DeclName, Type, DeclName, unsigned, bool))
4586-
ERROR(global_actor_from_other_global_actor_context,none,
4587-
"%0 %1 isolated to global actor %2 can not be %select{referenced|mutated|used 'inout'}4"
4588-
" from different global actor %3 %select{|in a synchronous context}5",
4589-
(DescriptiveDeclKind, DeclName, Type, Type, unsigned, bool))
4590-
ERROR(global_actor_from_nonactor_context,none,
4591-
"%0 %1 isolated to global actor %2 can not be %select{referenced|mutated|used 'inout'}4"
4592-
" from %select{this|a non-isolated}3%select{| synchronous}5 context",
4593-
(DescriptiveDeclKind, DeclName, Type, bool, unsigned, bool))
45944582
ERROR(actor_isolated_call,none,
45954583
"call to %0 function in a synchronous %1 context",
45964584
(ActorIsolation, ActorIsolation))
@@ -4684,19 +4672,9 @@ WARNING(shared_mutable_state_access,none,
46844672
"reference to %0 %1 is not concurrency-safe because it involves "
46854673
"shared mutable state", (DescriptiveDeclKind, DeclName))
46864674
ERROR(actor_isolated_witness,none,
4687-
"actor-isolated %0 %1 cannot be used to satisfy a protocol requirement",
4688-
(DescriptiveDeclKind, DeclName))
4689-
ERROR(distributed_actor_isolated_witness,none,
4690-
"distributed actor-isolated %0 %1 cannot be used to satisfy a protocol requirement",
4691-
(DescriptiveDeclKind, DeclName))
4692-
ERROR(global_actor_isolated_witness,none,
4693-
"%0 %1 isolated to global actor %2 can not satisfy corresponding "
4694-
"requirement from protocol %3",
4695-
(DescriptiveDeclKind, DeclName, Type, Identifier))
4696-
ERROR(global_actor_isolated_requirement_witness_conflict,none,
4697-
"%0 %1 isolated to global actor %2 can not satisfy corresponding "
4698-
"requirement from protocol %3 isolated to global actor %4",
4699-
(DescriptiveDeclKind, DeclName, Type, Identifier, Type))
4675+
"%select{|distributed }0%1 %2 %3 cannot be used to satisfy %4 protocol "
4676+
"requirement",
4677+
(bool, ActorIsolation, DescriptiveDeclKind, DeclName, ActorIsolation))
47004678
ERROR(actor_cannot_conform_to_global_actor_protocol,none,
47014679
"actor %0 cannot conform to global actor isolated protocol %1",
47024680
(Type, Type))
@@ -4708,9 +4686,10 @@ ERROR(isolated_parameter_not_actor,none,
47084686

47094687
WARNING(non_sendable_param_type,none,
47104688
"non-sendable type %0 %select{passed in call to %4 %2 %3|"
4689+
"exiting %4 context in call to non-isolated %2 %3|"
47114690
"passed in implicitly asynchronous call to %4 %2 %3|"
4712-
"in parameter of %4 %2 %3 satisfying non-isolated protocol "
4713-
"requirement|"
4691+
"in parameter of %4 %2 %3 satisfying protocol requirement|"
4692+
"in parameter of %4 overriding %2 %3|"
47144693
"in parameter of %4 '@objc' %2 %3}1 cannot cross actor boundary",
47154694
(Type, unsigned, DescriptiveDeclKind, DeclName, ActorIsolation))
47164695
WARNING(non_sendable_call_param_type,none,
@@ -4719,8 +4698,10 @@ WARNING(non_sendable_call_param_type,none,
47194698
(Type, bool, ActorIsolation))
47204699
WARNING(non_sendable_result_type,none,
47214700
"non-sendable type %0 returned by %select{call to %4 %2 %3|"
4701+
"call from %4 context to non-isolated %2 %3|"
47224702
"implicitly asynchronous call to %4 %2 %3|"
4723-
"%4 %2 %3 satisfying non-isolated protocol requirement|"
4703+
"%4 %2 %3 satisfying protocol requirement|"
4704+
"%4 overriding %2 %3|"
47244705
"%4 '@objc' %2 %3}1 cannot cross actor boundary",
47254706
(Type, unsigned, DescriptiveDeclKind, DeclName, ActorIsolation))
47264707
WARNING(non_sendable_call_result_type,none,
@@ -4730,8 +4711,10 @@ WARNING(non_sendable_call_result_type,none,
47304711
WARNING(non_sendable_property_type,none,
47314712
"non-sendable type %0 in %select{"
47324713
"%select{asynchronous access to %5 %1 %2|"
4714+
"asynchronous access from %5 context to non-isolated %1 %2|"
47334715
"implicitly asynchronous access to %5 %1 %2|"
4734-
"conformance of %5 %1 %2 to non-isolated protocol requirement|"
4716+
"conformance of %5 %1 %2 to protocol requirement|"
4717+
"%5 overriding %1 %2|"
47354718
"%5 '@objc' %1 %2}4|captured local %1 %2}3 cannot "
47364719
"cross %select{actor|task}3 boundary",
47374720
(Type, DescriptiveDeclKind, DeclName, bool, unsigned, ActorIsolation))

lib/AST/Decl.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9375,3 +9375,7 @@ void swift::simple_display(llvm::raw_ostream &out, AnyFunctionRef fn) {
93759375
else
93769376
out << "closure";
93779377
}
9378+
9379+
bool ActorIsolation::isDistributedActor() const {
9380+
return getKind() == ActorInstance && getActor()->isDistributedActor();
9381+
}

lib/Sema/CSApply.cpp

Lines changed: 47 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7852,18 +7852,6 @@ Expr *ExprRewriter::finishApply(ApplyExpr *apply, Type openedType,
78527852
return ctorCall;
78537853
}
78547854

7855-
/// Determine whether this closure should be treated as Sendable.
7856-
static bool isSendableClosure(ConstraintSystem &cs,
7857-
const AbstractClosureExpr *closure) {
7858-
if (auto fnType = cs.getType(const_cast<AbstractClosureExpr *>(closure))
7859-
->getAs<FunctionType>()) {
7860-
if (fnType->isSendable())
7861-
return true;
7862-
}
7863-
7864-
return false;
7865-
}
7866-
78677855
bool ExprRewriter::isDistributedThunk(ConcreteDeclRef ref, Expr *context) {
78687856
auto *FD = dyn_cast_or_null<AbstractFunctionDecl>(ref.getDecl());
78697857
if (!(FD && FD->isInstanceMember() && FD->isDistributed()))
@@ -7887,52 +7875,60 @@ bool ExprRewriter::isDistributedThunk(ConcreteDeclRef ref, Expr *context) {
78877875

78887876
// If this is a method reference on an potentially isolated
78897877
// actor then it cannot be a remote thunk.
7890-
if (isPotentiallyIsolatedActor(actor, [&](ParamDecl *P) {
7891-
return P->isIsolated() ||
7892-
llvm::is_contained(solution.isolatedParams, P);
7893-
}))
7894-
return false;
7895-
7896-
bool isInAsyncLetInitializer = target && target->isAsyncLetInitializer();
7897-
7898-
auto isActorInitOrDeInitContext = [&](const DeclContext *dc) {
7899-
return ::isActorInitOrDeInitContext(
7900-
dc, [&](const AbstractClosureExpr *closure) {
7901-
return isSendableClosure(cs, closure);
7902-
});
7903-
};
7904-
7905-
switch (ActorIsolationRestriction::forDeclaration(ref, dc)) {
7906-
case ActorIsolationRestriction::CrossActorSelf: {
7907-
// Not a thunk if it's used in actor init or de-init.
7908-
if (!isInAsyncLetInitializer && isActorInitOrDeInitContext(dc))
7909-
return false;
7878+
bool isPotentiallyIsolated = isPotentiallyIsolatedActor(
7879+
actor,
7880+
[&](ParamDecl *P) {
7881+
return P->isIsolated() ||
7882+
llvm::is_contained(solution.isolatedParams, P);
7883+
});
7884+
7885+
// Adjust the declaration context to the innermost context that is neither
7886+
// a local function nor a closure, so that the actor reference is checked
7887+
auto referenceDC = dc;
7888+
while (true) {
7889+
switch (referenceDC->getContextKind()) {
7890+
case DeclContextKind::AbstractClosureExpr:
7891+
case DeclContextKind::Initializer:
7892+
case DeclContextKind::SerializedLocal:
7893+
referenceDC = referenceDC->getParent();
7894+
continue;
79107895

7911-
// Here we know that the method could be used across actors
7912-
// and the actor it's used on is non-isolated, which means
7913-
// that it could be a thunk, so we have to be conservative
7914-
// about it.
7915-
return true;
7916-
}
7896+
case DeclContextKind::AbstractFunctionDecl:
7897+
case DeclContextKind::GenericTypeDecl:
7898+
case DeclContextKind::SubscriptDecl:
7899+
if (auto value = dyn_cast<ValueDecl>(referenceDC->getAsDecl())) {
7900+
if (value->isLocalCapture()) {
7901+
referenceDC = referenceDC->getParent();
7902+
continue;
7903+
}
7904+
}
7905+
break;
79177906

7918-
case ActorIsolationRestriction::ActorSelf: {
7919-
// An instance member of an actor can be referenced from an actor's
7920-
// designated initializer or deinitializer.
7921-
if (actor->isActorSelf() && !isInAsyncLetInitializer) {
7922-
if (auto *fn = isActorInitOrDeInitContext(dc)) {
7923-
if (!(isa<ConstructorDecl>(fn) &&
7924-
cast<ConstructorDecl>(fn)->isConvenienceInit()))
7925-
return false;
7926-
}
7907+
case DeclContextKind::EnumElementDecl:
7908+
case DeclContextKind::ExtensionDecl:
7909+
case DeclContextKind::FileUnit:
7910+
case DeclContextKind::Module:
7911+
case DeclContextKind::TopLevelCodeDecl:
7912+
break;
79277913
}
79287914

7929-
// Call on a non-isolated actor in async context requires
7930-
// implicit thunk.
7931-
return isInAsyncLetInitializer || cs.isAsynchronousContext(dc);
7915+
break;
79327916
}
79337917

7934-
default:
7918+
// Create a simple actor reference, assuming that we might be in a
7919+
// non-isolated context but knowing whether it's potentially isolated.
7920+
// We only care about the "distributed" flag.
7921+
ReferencedActor actorRef = ReferencedActor(
7922+
actor, isPotentiallyIsolated, ReferencedActor::NonIsolatedContext);
7923+
auto refResult = ActorReferenceResult::forReference(
7924+
ref, context->getLoc(), referenceDC, None, actorRef);
7925+
switch (refResult) {
7926+
case ActorReferenceResult::ExitsActorToNonisolated:
7927+
case ActorReferenceResult::SameConcurrencyDomain:
79357928
return false;
7929+
7930+
case ActorReferenceResult::EntersActor:
7931+
return refResult.options.contains(ActorReferenceResult::Flags::Distributed);
79367932
}
79377933
}
79387934

0 commit comments

Comments
 (0)