Skip to content

Commit 740cd7b

Browse files
committed
[Distributed] Implement distributed actor semantics in call checking
The move to perform all call checking that can cross concurrency domains into one place dropped the specific logic for distributed actor calls. Introduce that logic, cleaning it up to consistently use the "known to be local" semantics needed for distributed actors.
1 parent d78a5ed commit 740cd7b

File tree

4 files changed

+45
-50
lines changed

4 files changed

+45
-50
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 39 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2415,41 +2415,6 @@ namespace {
24152415
return ReferencedActor(var, isPotentiallyIsolated, ReferencedActor::NonIsolatedParameter);
24162416
}
24172417

2418-
VarDecl *findReferencedBaseSelf(Expr *expr) {
2419-
if (auto selfVar = getReferencedParamOrCapture(expr))
2420-
if (selfVar->isSelfParameter() || selfVar->isSelfParamCapture())
2421-
return selfVar;
2422-
2423-
// Look through identity expressions and implicit conversions.
2424-
Expr *prior;
2425-
do {
2426-
prior = expr;
2427-
2428-
expr = expr->getSemanticsProvidingExpr();
2429-
2430-
if (auto conversion = dyn_cast<ImplicitConversionExpr>(expr))
2431-
expr = conversion->getSubExpr();
2432-
if (auto fnConv = dyn_cast<FunctionConversionExpr>(expr))
2433-
expr = fnConv->getSubExpr();
2434-
} while (prior != expr);
2435-
2436-
if (auto call = dyn_cast<DotSyntaxCallExpr>(expr)) {
2437-
for (auto arg : *call->getArgs()) {
2438-
if (auto declRef = dyn_cast<DeclRefExpr>(arg.getExpr())) {
2439-
if (auto var = dyn_cast<VarDecl>(declRef->getDecl())) {
2440-
if (var->isSelfParameter()) {
2441-
return var;
2442-
}
2443-
}
2444-
}
2445-
}
2446-
}
2447-
2448-
2449-
// Not a self reference.
2450-
return nullptr;
2451-
}
2452-
24532418
/// Note that the given actor member is isolated.
24542419
/// @param context is allowed to be null if no context is appropriate.
24552420
void noteIsolatedActorMember(ValueDecl const* decl, Expr *context) {
@@ -2590,13 +2555,17 @@ namespace {
25902555
Optional<std::pair<bool, bool>>
25912556
checkDistributedAccess(SourceLoc declLoc, ValueDecl *decl,
25922557
Expr *context) {
2593-
// If base of the call is 'local' we permit skip distributed checks.
2594-
if (auto baseSelf = findReferencedBaseSelf(context)) {
2595-
if (baseSelf->getAttrs().hasAttribute<KnownToBeLocalAttr>()) {
2558+
// If the actor itself is, we're not doing any distributed access.
2559+
if (getIsolatedActor(context).isKnownToBeLocal()) {
25962560
return std::make_pair(
25972561
/*setThrows=*/false,
25982562
/*isDistributedThunk=*/false);
2599-
}
2563+
}
2564+
2565+
// If there is no declaration, it can't possibly be distributed.
2566+
if (!decl) {
2567+
ctx.Diags.diagnose(declLoc, diag::distributed_actor_isolated_method);
2568+
return None;
26002569
}
26012570

26022571
// Check that we have a distributed function or computed property.
@@ -2627,6 +2596,8 @@ namespace {
26272596
}
26282597
}
26292598

2599+
// FIXME: Subscript?
2600+
26302601
// This is either non-distributed variable, subscript, or something else.
26312602
ctx.Diags.diagnose(declLoc,
26322603
diag::distributed_actor_isolated_non_self_reference,
@@ -2787,6 +2758,7 @@ namespace {
27872758
// Determine from the callee whether actor isolation is unsatisfied.
27882759
Optional<ActorIsolation> unsatisfiedIsolation;
27892760
bool mayExitToNonisolated = true;
2761+
Expr *argForIsolatedParam = nullptr;
27902762
auto calleeDecl = apply->getCalledValue(/*skipFunctionConversions=*/true);
27912763
if (Type globalActor = fnType->getGlobalActor()) {
27922764
// If the function type is global-actor-qualified, determine whether
@@ -2798,7 +2770,7 @@ namespace {
27982770
}
27992771

28002772
mayExitToNonisolated = false;
2801-
} else if (auto selfApplyFn = dyn_cast<SelfApplyExpr>(
2773+
} else if (auto *selfApplyFn = dyn_cast<SelfApplyExpr>(
28022774
apply->getFn()->getValueProvidingExpr())) {
28032775
// If we're calling a member function, check whether the function
28042776
// itself is isolated.
@@ -2825,6 +2797,7 @@ namespace {
28252797
callOptions = result.options;
28262798
mayExitToNonisolated = false;
28272799
calleeDecl = memberRef->first.getDecl();
2800+
argForIsolatedParam = selfApplyFn->getBase();
28282801
}
28292802
} else if (calleeDecl &&
28302803
calleeDecl->getAttrs()
@@ -2846,6 +2819,7 @@ namespace {
28462819
continue;
28472820

28482821
auto *arg = args->getExpr(paramIdx);
2822+
argForIsolatedParam = arg;
28492823
if (getIsolatedActor(arg))
28502824
continue;
28512825

@@ -2861,6 +2835,10 @@ namespace {
28612835

28622836
unsatisfiedIsolation =
28632837
ActorIsolation::forActorInstanceParameter(nominal, paramIdx);
2838+
2839+
if (!fnType->getExtInfo().isAsync())
2840+
callOptions |= ActorReferenceResult::Flags::AsyncPromotion;
2841+
28642842
break;
28652843
}
28662844

@@ -2877,7 +2855,8 @@ namespace {
28772855
bool requiresAsync =
28782856
callOptions.contains(ActorReferenceResult::Flags::AsyncPromotion);
28792857

2880-
// If we are not in an asynchronous context, complain.
2858+
// If we need to mark the call as implicitly asynchronous, make sure
2859+
// we're in an asynchronous context.
28812860
if (requiresAsync && !getDeclContext()->isAsyncContext()) {
28822861
if (calleeDecl) {
28832862
ctx.Diags.diagnose(
@@ -2905,9 +2884,25 @@ namespace {
29052884
return true;
29062885
}
29072886

2908-
// If needed, mark as implicitly async.
2909-
if (requiresAsync) {
2910-
apply->setImplicitlyAsync(*unsatisfiedIsolation);
2887+
// If the actor we're hopping to is distributed, we might also need
2888+
// to mark the call as throwing and/or using the distributed thunk.
2889+
// FIXME: ActorReferenceResult has this information, too.
2890+
bool setThrows = false;
2891+
bool usesDistributedThunk = false;
2892+
if (unsatisfiedIsolation->isDistributedActor() &&
2893+
!(calleeDecl && isa<ConstructorDecl>(calleeDecl))) {
2894+
auto distributedAccess = checkDistributedAccess(
2895+
apply->getFn()->getLoc(), calleeDecl, argForIsolatedParam);
2896+
if (!distributedAccess)
2897+
return true;
2898+
2899+
std::tie(setThrows, usesDistributedThunk) = *distributedAccess;
2900+
}
2901+
2902+
// Mark as implicitly async/throws/distributed thunk as needed.
2903+
if (requiresAsync || setThrows || usesDistributedThunk) {
2904+
markNearestCallAsImplicitly(
2905+
unsatisfiedIsolation, setThrows, usesDistributedThunk);
29112906
}
29122907

29132908
// Check for sendability of the parameter types.

test/ClangImporter/objc_async.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -165,14 +165,14 @@ actor MySubclassCheckingSwiftAttributes : ProtocolWithSwiftAttributes {
165165
func syncMethod() { } // expected-note 2{{calls to instance method 'syncMethod()' from outside of its actor context are implicitly asynchronous}}
166166

167167
nonisolated func independentMethod() {
168-
syncMethod() // expected-error{{ctor-isolated instance method 'syncMethod()' can not be referenced from a non-isolated context}}
168+
syncMethod() // expected-error{{call to actor-isolated instance method 'syncMethod()' in a synchronous nonisolated context}}
169169
}
170170

171171
nonisolated func nonisolatedMethod() {
172172
}
173173

174174
@MainActor func mainActorMethod() {
175-
syncMethod() // expected-error{{actor-isolated instance method 'syncMethod()' can not be referenced from the main actor}}
175+
syncMethod() // expected-error{{call to actor-isolated instance method 'syncMethod()' in a synchronous main actor-isolated context}}
176176
}
177177

178178
@MainActor func uiActorMethod() { }

test/Distributed/distributed_actor_isolation.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -207,10 +207,10 @@ distributed actor DijonMustard {
207207

208208
convenience init(conv: FakeActorSystem) { // expected-warning {{initializers in actors are not marked with 'convenience'; this is an error in Swift 6}}{{3-15=}}
209209
self.init(system: conv)
210-
self.f() // expected-error {{actor-isolated instance method 'f()' can not be referenced from a non-isolated context}}
210+
self.f() // expected-error {{call to actor-isolated instance method 'f()' in a synchronous nonisolated context}}
211211
}
212212

213-
func f() {} // expected-note {{distributed actor-isolated instance method 'f()' declared here}}
213+
func f() {} // expected-note {{calls to instance method 'f()' from outside of its actor context are implicitly asynchronous}}
214214

215215
nonisolated init(conv2: FakeActorSystem) { // expected-warning {{'nonisolated' on an actor's synchronous initializer is invalid; this is an error in Swift 6}} {{3-15=}}
216216
self.init(system: conv2)

test/Sema/moveonly_sendable.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ func processFiles(_ a: A, _ anotherFile: borrowing FileDescriptor) async {
4949
await a.takeMaybeFile(.available(anotherFile))
5050
_ = A(.available(anotherFile))
5151

52-
let ns = await a.getRef() // expected-warning {{non-sendable type 'NotSendableMO' returned by implicitly asynchronous call to actor-isolated instance method 'getRef()' cannot cross actor boundary}}
53-
await takeNotSendable(ns) // expected-warning {{non-sendable type 'NotSendableMO' exiting main actor-isolated context in call to non-isolated global function 'takeNotSendable' cannot cross actor boundary}}
52+
let ns = await a.getRef() // expected-warning {{non-sendable type 'NotSendableMO' returned by call to actor-isolated function cannot cross actor boundary}}
53+
await takeNotSendable(ns) // expected-warning {{passing argument of non-sendable type 'NotSendableMO' outside of main actor-isolated context may introduce data races}}
5454

5555
switch (await a.giveFileDescriptor()) {
5656
case let .available(fd):

0 commit comments

Comments
 (0)