Skip to content

Commit 6f0e70a

Browse files
committed
Unify 'distributed' checking for accesses to distributed actors.
Unify the logic for checking and marking accesses as async/throws/uses-distributed-thunk to reduce the overall amount of code, clarify the isolated/local/potentially-remote cases, and consistently set these options. There are tiny semantic improvements here where, e.g., one can asynchronously use non-distributed functions, properties, and subscripts if we're in a context where we know the actor is not remote.
1 parent 147b2e9 commit 6f0e70a

File tree

2 files changed

+99
-135
lines changed

2 files changed

+99
-135
lines changed

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 94 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -1559,23 +1559,62 @@ namespace {
15591559
FoundAsync, // successfully marked an implicitly-async operation
15601560
NotFound, // fail: no valid implicitly-async operation was found
15611561
SyncContext, // fail: a valid implicitly-async op, but in sync context
1562-
NotSendable // fail: valid op and context, but not Sendable
1562+
NotSendable, // fail: valid op and context, but not Sendable
1563+
NotDistributed, // fail: non-distributed declaration in distributed actor
15631564
};
15641565

1566+
/// Determine whether we can access the given declaration that is
1567+
/// isolated to a distributed actor from a location that is potentially not
1568+
/// local to this process.
1569+
///
1570+
/// \returns the (setThrows, isDistributedThunk) bits to implicitly
1571+
/// mark the access/call with on success, or emits an error and returns
1572+
/// \c None.
1573+
Optional<std::pair<bool, bool>>
1574+
checkDistributedAccess(SourceLoc declLoc, ValueDecl *decl,
1575+
Expr *context) {
1576+
// Cannot reference properties or subscripts of distributed actors.
1577+
if (isPropOrSubscript(decl)) {
1578+
ctx.Diags.diagnose(
1579+
declLoc, diag::distributed_actor_isolated_non_self_reference,
1580+
decl->getDescriptiveKind(), decl->getName());
1581+
noteIsolatedActorMember(decl, context);
1582+
return None;
1583+
}
1584+
1585+
// Check that we have a distributed function.
1586+
auto func = dyn_cast<AbstractFunctionDecl>(decl);
1587+
if (!func || !func->isDistributed()) {
1588+
ctx.Diags.diagnose(declLoc,
1589+
diag::distributed_actor_isolated_method)
1590+
.fixItInsert(decl->getAttributeInsertionLoc(true), "distributed ");
1591+
1592+
noteIsolatedActorMember(decl, context);
1593+
return None;
1594+
}
1595+
1596+
return std::make_pair(!func->hasThrows(), true);
1597+
}
1598+
15651599
/// Attempts to identify and mark a valid cross-actor use of a synchronous
15661600
/// actor-isolated member (e.g., sync function application, property access)
15671601
AsyncMarkingResult tryMarkImplicitlyAsync(SourceLoc declLoc,
15681602
ConcreteDeclRef concDeclRef,
15691603
Expr* context,
1570-
ImplicitActorHopTarget target) {
1604+
ImplicitActorHopTarget target,
1605+
bool isDistributed) {
15711606
ValueDecl *decl = concDeclRef.getDecl();
15721607
AsyncMarkingResult result = AsyncMarkingResult::NotFound;
1608+
bool isAsyncCall = false;
15731609

15741610
// is it an access to a property?
15751611
if (isPropOrSubscript(decl)) {
1612+
// Cannot reference properties or subscripts of distributed actors.
1613+
if (isDistributed && !checkDistributedAccess(declLoc, decl, context))
1614+
return AsyncMarkingResult::NotDistributed;
1615+
15761616
if (auto declRef = dyn_cast_or_null<DeclRefExpr>(context)) {
15771617
if (usageEnv(declRef) == VarRefUseEnv::Read) {
1578-
15791618
if (!isInAsynchronousContext())
15801619
return AsyncMarkingResult::SyncContext;
15811620

@@ -1601,9 +1640,7 @@ namespace {
16011640
if (!isInAsynchronousContext())
16021641
return AsyncMarkingResult::SyncContext;
16031642

1604-
markNearestCallAsImplicitly(/*setAsync=*/target);
1605-
result = AsyncMarkingResult::FoundAsync;
1606-
1643+
isAsyncCall = true;
16071644
} else if (!applyStack.empty()) {
16081645
// Check our applyStack metadata from the traversal.
16091646
// Our goal is to identify whether the actor reference appears
@@ -1620,13 +1657,33 @@ namespace {
16201657
return AsyncMarkingResult::SyncContext;
16211658

16221659
// then this ValueDecl appears as the called value of the ApplyExpr.
1623-
markNearestCallAsImplicitly(/*setAsync=*/target);
1624-
result = AsyncMarkingResult::FoundAsync;
1660+
isAsyncCall = true;
16251661
}
16261662
}
16271663
}
16281664

1665+
// Set up an implicit async call.
1666+
if (isAsyncCall) {
1667+
// If we're calling to a distributed actor, make sure the function
1668+
// is actually 'distributed'.
1669+
bool setThrows = false;
1670+
bool usesDistributedThunk = false;
1671+
if (isDistributed) {
1672+
if (auto access = checkDistributedAccess(declLoc, decl, context))
1673+
std::tie(setThrows, usesDistributedThunk) = *access;
1674+
else
1675+
return AsyncMarkingResult::NotDistributed;
1676+
}
1677+
1678+
// Mark call as implicitly 'async', and also potentially as
1679+
// throwing and using a distributed thunk.
1680+
markNearestCallAsImplicitly(
1681+
/*setAsync=*/target, setThrows, usesDistributedThunk);
1682+
result = AsyncMarkingResult::FoundAsync;
1683+
}
1684+
16291685
if (result == AsyncMarkingResult::FoundAsync) {
1686+
16301687
// Check for non-sendable types.
16311688
bool problemFound =
16321689
diagnoseNonSendableTypesInReference(
@@ -1639,56 +1696,6 @@ namespace {
16391696
return result;
16401697
}
16411698

1642-
enum ThrowsMarkingResult {
1643-
FoundThrows,
1644-
NotFound
1645-
};
1646-
1647-
ThrowsMarkingResult tryMarkImplicitlyThrows(SourceLoc declLoc,
1648-
ConcreteDeclRef concDeclRef,
1649-
Expr* context) {
1650-
ValueDecl *decl = concDeclRef.getDecl();
1651-
ThrowsMarkingResult result = ThrowsMarkingResult::NotFound;
1652-
1653-
if (isa_and_nonnull<SelfApplyExpr>(context)) {
1654-
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
1655-
if (func->isDistributed() && !func->hasThrows()) {
1656-
// A distributed function is implicitly throwing if called from
1657-
// outside of the actor.
1658-
//
1659-
// If it already is throwing, no need to mark it implicitly so.
1660-
markNearestCallAsImplicitly(
1661-
/*setAsync=*/None, /*setThrows=*/true);
1662-
result = ThrowsMarkingResult::FoundThrows;
1663-
}
1664-
}
1665-
} else if (!applyStack.empty()) {
1666-
// Check our applyStack metadata from the traversal.
1667-
// Our goal is to identify whether the actor reference appears
1668-
// as the called value of the enclosing ApplyExpr. We cannot simply
1669-
// inspect Parent here because of expressions like (callee)()
1670-
// and the fact that the reference may be just an argument to an apply
1671-
ApplyExpr *apply = applyStack.back();
1672-
Expr *fn = apply->getFn()->getValueProvidingExpr();
1673-
if (auto memberRef = findMemberReference(fn)) {
1674-
auto concDecl = memberRef->first;
1675-
if (decl == concDecl.getDecl() && !apply->implicitlyThrows()) {
1676-
1677-
if (auto func = dyn_cast<AbstractFunctionDecl>(decl)) {
1678-
if (func->isDistributed() && !func->hasThrows()) {
1679-
// then this ValueDecl appears as the called value of the ApplyExpr.
1680-
markNearestCallAsImplicitly(
1681-
/*setAsync=*/None, /*setThrows=*/true);
1682-
result = ThrowsMarkingResult::FoundThrows;
1683-
}
1684-
}
1685-
}
1686-
}
1687-
}
1688-
1689-
return result;
1690-
}
1691-
16921699
/// Check actor isolation for a particular application.
16931700
bool checkApply(ApplyExpr *apply) {
16941701
auto fnExprType = apply->getFn()->getType();
@@ -1850,7 +1857,8 @@ namespace {
18501857
// Call is implicitly asynchronous.
18511858
auto result = tryMarkImplicitlyAsync(
18521859
loc, valueRef, context,
1853-
ImplicitActorHopTarget::forGlobalActor(globalActor));
1860+
ImplicitActorHopTarget::forGlobalActor(globalActor),
1861+
/*FIXME if we get global distributed actors*/false);
18541862
if (result == AsyncMarkingResult::FoundAsync)
18551863
return false;
18561864

@@ -2197,39 +2205,21 @@ namespace {
21972205
if (isolatedActor)
21982206
return false;
21992207

2208+
// If we have a distributed actor that might be remote, check that
2209+
// we are referencing a properly-distributed member.
22002210
bool performDistributedChecks =
22012211
isolation.getActorType()->isDistributedActor() &&
2202-
!(isolatedActor.isActorSelf() &&
2203-
member->isInstanceMember() &&
2204-
isActorInitOrDeInitContext(getDeclContext())
2205-
);
2206-
2212+
!isolatedActor.isPotentiallyIsolated &&
2213+
!isa<ConstructorDecl>(member) &&
2214+
!isActorInitOrDeInitContext(getDeclContext());
22072215
if (performDistributedChecks) {
2208-
if (auto func = dyn_cast<FuncDecl>(member)) {
2209-
if (func->isStatic()) {
2210-
// no additional checks for static functions
2211-
} else if (func->isDistributed()) {
2212-
tryMarkImplicitlyThrows(memberLoc, memberRef, context);
2213-
markNearestCallAsImplicitly(/*setAsync*/None, /*setThrows*/false,
2214-
/*setDistributedThunk*/true);
2215-
2216-
} else {
2217-
// neither static or distributed, apply full distributed isolation
2218-
ctx.Diags.diagnose(memberLoc, diag::distributed_actor_isolated_method)
2219-
.fixItInsert(member->getAttributeInsertionLoc(true), "distributed ");
2220-
noteIsolatedActorMember(member, context);
2221-
return true;
2222-
}
2223-
} // end FuncDecl
2224-
2225-
if (isPropOrSubscript(member)) {
2226-
ctx.Diags.diagnose(
2227-
memberLoc, diag::distributed_actor_isolated_non_self_reference,
2228-
member->getDescriptiveKind(),
2229-
member->getName());
2230-
noteIsolatedActorMember(member, context);
2231-
return true;
2232-
} // end VarDecl
2216+
if (auto access = checkDistributedAccess(memberLoc, member, context)){
2217+
// This is a distributed access, so mark it as throwing or
2218+
// using a distributed thunk as appropriate.
2219+
markNearestCallAsImplicitly(None, access->first, access->second);
2220+
} else {
2221+
return true;
2222+
}
22332223
}
22342224

22352225
return diagnoseNonSendableTypesInReference(
@@ -2261,55 +2251,27 @@ namespace {
22612251
return true;
22622252
}
22632253

2264-
2265-
// Distributed actor properties cannot be accessed cross-actor.
2266-
if (isolation.getActorType()->isDistributedActor()) {
2267-
if (auto func = dyn_cast<AbstractFunctionDecl>(member)) {
2268-
if (!func->isDistributed()) {
2269-
ctx.Diags.diagnose(memberLoc,
2270-
diag::distributed_actor_isolated_method);
2271-
noteIsolatedActorMember(member, context);
2272-
return true;
2273-
} else {
2274-
// it is some other member and since we only allow distributed
2275-
// funcs this definitely is distributed-actor-isolated
2276-
if (!func->isDistributed()) {
2277-
ctx.Diags.diagnose(
2278-
memberLoc,
2279-
diag::distributed_actor_isolated_non_self_reference,
2280-
member->getDescriptiveKind(), member->getName());
2281-
noteIsolatedActorMember(member, context);
2282-
return true;
2283-
}
2284-
}
2285-
}
2286-
2287-
// It wasn't a distributed func, so ban the access
2288-
if (isPropOrSubscript(member)) {
2289-
ctx.Diags.diagnose(
2290-
memberLoc, diag::distributed_actor_isolated_non_self_reference,
2291-
member->getDescriptiveKind(), member->getName());
2292-
noteIsolatedActorMember(member, context);
2293-
return true;
2294-
}
2295-
}
2296-
22972254
// Try implicit asynchronous access.
2255+
bool isDistributed = isolation.getActorType()->isDistributedActor() &&
2256+
!isolatedActor.isPotentiallyIsolated;
22982257
auto implicitAsyncResult = tryMarkImplicitlyAsync(
22992258
memberLoc, memberRef, context,
2300-
ImplicitActorHopTarget::forInstanceSelf());
2301-
if (isolation.getActorType()->isDistributedActor() &&
2302-
!isolatedActor.isPotentiallyIsolated) {
2303-
tryMarkImplicitlyThrows(memberLoc, memberRef, context);
2304-
markNearestCallAsImplicitly(/*setAsync*/None, /*setThrows*/false,
2305-
/*setDistributedThunk*/true);
2306-
}
2259+
ImplicitActorHopTarget::forInstanceSelf(),
2260+
isDistributed);
23072261

2308-
if (implicitAsyncResult == AsyncMarkingResult::FoundAsync)
2309-
return false; // no problems
2310-
else if (implicitAsyncResult == AsyncMarkingResult::NotSendable)
2262+
switch (implicitAsyncResult) {
2263+
case AsyncMarkingResult::FoundAsync:
2264+
return false;
2265+
2266+
case AsyncMarkingResult::NotSendable:
2267+
case AsyncMarkingResult::NotDistributed:
23112268
return true;
23122269

2270+
case AsyncMarkingResult::NotFound:
2271+
case AsyncMarkingResult::SyncContext:
2272+
// Diagnose below.
2273+
break;
2274+
}
23132275

23142276
// Complain about access outside of the isolation domain.
23152277
auto useKind = static_cast<unsigned>(

test/Distributed/distributed_actor_isolation_and_tasks.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ distributed actor Philosopher {
1414
let log: Logger
1515
// expected-note@-1{{distributed actor state is only available within the actor instance}}
1616
var variable = 12
17-
var variable_fromDetach = 12 // expected-note{{distributed actor state is only available within the actor instance}}
17+
var variable_fromDetach = 12
1818
let INITIALIZED: Int
1919
let outside: Int = 1
2020

@@ -47,7 +47,9 @@ distributed actor Philosopher {
4747
// because we KNOW this is a local call -- and there is no transport in
4848
// between that will throw.
4949
_ = await self.dist() // notice lack of 'try' even though 'distributed func'
50-
_ = self.variable_fromDetach // expected-error{{distributed actor-isolated property 'variable_fromDetach' can only be referenced inside the distributed actor}}
50+
_ = self.variable_fromDetach // expected-error{{expression is 'async' but is not marked with 'await'}}
51+
// expected-note@-1{{property access is 'async'}}
52+
_ = await self.variable_fromDetach // okay, we know we're on the local node
5153
}
5254
}
5355
}
@@ -62,4 +64,4 @@ func test_outside(transport: ActorTransport) async throws {
6264

6365
func test_outside_isolated(phil: isolated Philosopher) async throws {
6466
phil.log.info("works on isolated")
65-
}
67+
}

0 commit comments

Comments
 (0)