Skip to content

Commit 7379780

Browse files
authored
[Distributed] introduce static resolve func, and fix static isolation on dist actors (swiftlang#38530)
1 parent 99028d1 commit 7379780

12 files changed

+179
-32
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4436,8 +4436,8 @@ NOTE(actor_isolated_sync_func,none,
44364436
"implicitly asynchronous",
44374437
(DescriptiveDeclKind, DeclName))
44384438
NOTE(distributed_actor_isolated_method_note,none,
4439-
"only 'distributed' functions can be called from outside the distributed actor",
4440-
())
4439+
"only 'distributed' functions can be called from outside the distributed actor", // TODO: improve error message
4440+
())
44414441
ERROR(distributed_actor_isolated_method,none,
44424442
"only 'distributed' functions can be called from outside the distributed actor", // TODO: improve error message to be more like 'non-distributed' ... defined here
44434443
())

include/swift/Runtime/Concurrency.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,9 +579,19 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
579579
void swift_defaultActor_deallocateResilient(HeapObject *actor);
580580

581581
/// Initialize the runtime storage for a distributed remote actor.
582+
// TODO: this may end up being removed as we move to the "proxy creation" below
582583
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
583584
void swift_distributedActor_remote_initialize(DefaultActor *actor);
584585

586+
/// Create a proxy object that will serve as remote distributed actor instance.
587+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
588+
OpaqueValue* swift_distributedActor_remote_create(
589+
/* +1 */OpaqueValue *identity,
590+
/* +1 */OpaqueValue *transport
591+
// metadata for identity
592+
// metadata for transport
593+
);
594+
585595
/// Destroy the runtime storage for a default actor.
586596
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
587597
void swift_distributedActor_destroy(DefaultActor *actor);

include/swift/Runtime/RuntimeFunctions.def

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1671,6 +1671,16 @@ FUNCTION(DistributedActorInitializeRemote,
16711671
ARGS(RefCountedPtrTy), // TODO also address and transport?
16721672
ATTRS(NoUnwind))
16731673

1674+
// OpaqueValue* swift_distributedActor_remote_create(
1675+
// OpaqueValue *identity,
1676+
// OpaqueValue *transport);
1677+
FUNCTION(DistributedActorRemoteCreate,
1678+
swift_distributedActor_remote_create, SwiftCC,
1679+
ConcurrencyAvailability,
1680+
RETURNS(OpaquePtrTy), // TODO: is this the right type here?
1681+
ARGS(OpaquePtrTy, OpaquePtrTy),
1682+
ATTRS(NoUnwind))
1683+
16741684
// void swift_distributedActor_destroy(DefaultActor *actor); // TODO: ProxyActor *proxy?
16751685
FUNCTION(DistributedActorDestroy,
16761686
swift_distributedActor_destroy, SwiftCC,

lib/Sema/CodeSynthesisDistributedActor.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ createDistributedActor_init_local(ClassDecl *classDecl,
231231
}
232232

233233
// ==== Distributed Actor: Resolve Initializer ---------------------------------
234+
// TODO: remove resolve initializer in favor of resolve static function
234235

235236
/// Synthesizes the body for
236237
///

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,7 @@ ActorIsolationRestriction ActorIsolationRestriction::forDeclaration(
385385
if (func->isAsyncContext())
386386
isAccessibleAcrossActors = true;
387387

388+
// FIXME: move diagnosis out of this function entirely (!)
388389
if (func->isDistributed()) {
389390
if (auto classDecl = dyn_cast<ClassDecl>(decl->getDeclContext())) {
390391
if (!classDecl->isDistributedActor()) {
@@ -2273,9 +2274,11 @@ namespace {
22732274
if (dyn_cast<ConstructorDecl>(member))
22742275
return false;
22752276

2276-
// While the member may be unrestricted, perhaps
2277+
// While the member may be unrestricted, perhaps it is in a
2278+
// distributed actor, in which case we need to diagnose it.
22772279
if (auto classDecl = dyn_cast<ClassDecl>(member->getDeclContext())) {
22782280
if (classDecl->isDistributedActor()) {
2281+
fprintf(stderr, "[%s:%d] (%s) HERE\n", __FILE__, __LINE__, __FUNCTION__);
22792282
ctx.Diags.diagnose(memberLoc, diag::distributed_actor_isolated_method);
22802283
noteIsolatedActorMember(member, context);
22812284
return true;
@@ -2312,32 +2315,43 @@ namespace {
23122315
!(isolatedActor.isActorSelf() &&
23132316
member->isInstanceMember() &&
23142317
isActorInitOrDeInitContext(getDeclContext()))) {
2315-
// invocation on not-'self', is only okey if this is a distributed func
2318+
// cross actor invocation is only ok for a distributed or static func
23162319
if (auto func = dyn_cast<FuncDecl>(member)) {
2317-
if (!func->isDistributed()) {
2320+
if (func->isStatic()) {
2321+
// FIXME: rather, never end up as distributed actor self isolated
2322+
// at all for static funcs
2323+
continueToCheckingLocalIsolation = true;
2324+
} else if (func->isDistributed()) {
2325+
tryMarkImplicitlyAsync(memberLoc, memberRef, context);
2326+
tryMarkImplicitlyThrows(memberLoc, memberRef, context);
2327+
2328+
// distributed func reference, that passes all checks, great!
2329+
continueToCheckingLocalIsolation = true;
2330+
} else {
2331+
// the func is neither static or distributed
23182332
ctx.Diags.diagnose(memberLoc, diag::distributed_actor_isolated_method);
23192333
// TODO: offer a fixit to add 'distributed' on the member; how to test fixits? See also https://github.com/apple/swift/pull/35930/files
23202334
noteIsolatedActorMember(member, context);
23212335
return true;
23222336
}
23232337

2324-
assert(func->isDistributed());
2325-
tryMarkImplicitlyAsync(memberLoc, memberRef, context);
2326-
tryMarkImplicitlyThrows(memberLoc, memberRef, context);
2327-
2328-
// distributed func reference, that passes all checks, great!
2329-
continueToCheckingLocalIsolation = true;
23302338
} // end FuncDecl
23312339

23322340
if (!continueToCheckingLocalIsolation) {
23332341
// it wasn't a function (including a distributed function),
23342342
// so we need to perform some more checks
23352343
if (auto var = dyn_cast<VarDecl>(member)) {
2344+
// TODO: we want to remove _distributedActorIndependent in favor of nonisolated
2345+
//
23362346
// @_distributedActorIndependent decls are accessible always,
23372347
// regardless of distributed actor-isolation; e.g. actorAddress
23382348
if (member->getAttrs().hasAttribute<DistributedActorIndependentAttr>())
23392349
return false;
23402350

2351+
// nonisolated decls are accessible always
2352+
if (member->getAttrs().hasAttribute<DistributedActorIndependentAttr>())
2353+
return false;
2354+
23412355
// otherwise, no other properties are accessible on a distributed actor
23422356
if (!continueToCheckingLocalIsolation) {
23432357
ctx.Diags.diagnose(
@@ -2347,13 +2361,16 @@ namespace {
23472361
noteIsolatedActorMember(member, context);
23482362
return true;
23492363
}
2350-
}
2364+
} // end VarDecl
23512365

23522366
// TODO: would have to also consider subscripts and other things
23532367
}
23542368
} // end !isolatedActor
23552369

2356-
return false;
2370+
if (!continueToCheckingLocalIsolation)
2371+
return false;
2372+
2373+
LLVM_FALLTHROUGH;
23572374
}
23582375

23592376
case ActorIsolationRestriction::ActorSelf: {
@@ -2428,9 +2445,10 @@ namespace {
24282445
case ActorIsolationRestriction::Unsafe:
24292446
// This case is hit when passing actor state inout to functions in some
24302447
// cases. The error is emitted by diagnoseInOutArg.
2431-
2432-
if (auto classDecl = dyn_cast<ClassDecl>(member->getDeclContext())) {
2433-
if (classDecl->isDistributedActor()) {
2448+
auto classDecl = dyn_cast<ClassDecl>(member->getDeclContext());
2449+
if (classDecl && classDecl->isDistributedActor()) {
2450+
auto funcDecl = dyn_cast<AbstractFunctionDecl>(member);
2451+
if (funcDecl && !funcDecl->isStatic()) {
24342452
member->diagnose(diag::distributed_actor_isolated_method);
24352453
return true;
24362454
}
@@ -3164,7 +3182,8 @@ ActorIsolation ActorIsolationRequest::evaluate(
31643182
}
31653183

31663184
if (auto nominal = value->getDeclContext()->getSelfNominalTypeDecl()) {
3167-
if (nominal->isDistributedActor()) {
3185+
/// Unless the function is static, it is isolated to the dist actor
3186+
if (nominal->isDistributedActor() && !func->isStatic()) {
31683187
defaultIsolation = ActorIsolation::forDistributedActorInstance(nominal);
31693188
}
31703189
}

stdlib/public/Concurrency/Actor.cpp

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,13 +1756,19 @@ void swift::swift_defaultActor_deallocateResilient(HeapObject *actor) {
17561756
metadata->getInstanceAlignMask());
17571757
}
17581758

1759-
// TODO: most likely where we'd need to create the "proxy instance" instead?
1760-
void swift::swift_distributedActor_remote_initialize(DefaultActor *_actor) { // FIXME: !!!!! remove distributed C++ impl not needed?
1759+
// TODO: most likely where we'd need to create the "proxy instance" instead? (most likely remove this and use swift_distributedActor_remote_create instead)
1760+
void swift::swift_distributedActor_remote_initialize(DefaultActor *_actor) { // FIXME: remove distributed C++ impl not needed?
17611761
auto actor = asImpl(_actor);
17621762
actor->initialize(/*remote=*/true);
17631763
}
17641764

1765-
void swift::swift_distributedActor_destroy(DefaultActor *_actor) { // FIXME: !!!!! remove distributed C++ impl not needed?
1765+
// TODO: missing implementation of creating a proxy for the remote actor
1766+
OpaqueValue* swift::swift_distributedActor_remote_create(OpaqueValue *identity,
1767+
OpaqueValue *transport) {
1768+
assert(false && "swift_distributedActor_remote_create is not implemented yet!");
1769+
}
1770+
1771+
void swift::swift_distributedActor_destroy(DefaultActor *_actor) { // FIXME: remove distributed C++ impl not needed?
17661772
// TODO: need to resign the address before we destroy:
17671773
// something like: actor.transport.resignIdentity(actor.address)
17681774

stdlib/public/Distributed/ActorTransport.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public protocol ActorTransport: Sendable {
4848
///
4949
/// Detecting liveness of such remote actors shall be offered / by transport libraries
5050
/// by other means, such as "watching an actor for termination" or similar.
51-
func resolve<Act>(_ identity: Act.ID, as actorType: Act.Type) throws -> ActorResolved<Act>
51+
func resolve<Act>(_ identity: AnyActorIdentity, as actorType: Act.Type) throws -> ActorResolved<Act>
5252
where Act: DistributedActor
5353

5454
// ==== ---------------------------------------------------------------------

stdlib/public/Distributed/DistributedActor.swift

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,23 @@ public protocol DistributedActor: AnyActor, Identifiable, Hashable, Codable {
4646
/// associated with.
4747
init(transport: ActorTransport)
4848

49-
/// Resolves the passed in `address` against the `transport`,
50-
/// returning either a local or remote actor reference.
49+
@available(*, deprecated, renamed: "SomeDistributedActor.resolve(_:using:)")
50+
init(resolve id: AnyActorIdentity, using transport: ActorTransport) throws
51+
52+
/// Resolves the passed in `identity` against the `transport`, returning
53+
/// either a local or remote actor reference.
5154
///
52-
/// The transport will be asked to `resolve` the address and return either
53-
/// a local instance or determine that a proxy instance should be created
54-
/// for this address. A proxy actor will forward all invocations through
55+
/// The transport will be asked to `resolve` the identity and return either
56+
/// a local instance or request a proxy to be created for this identity.
57+
///
58+
/// A remote distributed actor reference will forward all invocations through
5559
/// the transport, allowing it to take over the remote messaging with the
5660
/// remote actor instance.
5761
///
58-
/// - Parameter address: the address to resolve, and produce an instance or proxy for.
59-
/// - Parameter transport: transport which should be used to resolve the `address`.
60-
// FIXME: replace with static func resolve(_:using)
61-
// TODO: make this <ID>(resolve id: ID, ...)
62-
init(resolve id: AnyActorIdentity, using transport: ActorTransport) throws
62+
/// - Parameter identity: identity uniquely identifying a, potentially remote, actor in the system
63+
/// - Parameter transport: `transport` which should be used to resolve the `identity`, and be associated with the returned actor
64+
static func resolve<Identity>(_ identity: Identity, using transport: ActorTransport)
65+
throws -> Self where Identity: ActorIdentity
6366

6467
/// The `ActorTransport` associated with this actor.
6568
/// It is immutable and equal to the transport passed in the local/resolve
@@ -83,6 +86,23 @@ public protocol DistributedActor: AnyActor, Identifiable, Hashable, Codable {
8386
nonisolated var id: AnyActorIdentity { get }
8487
}
8588

89+
@available(SwiftStdlib 5.5, *)
90+
extension DistributedActor {
91+
92+
public static func resolve<Identity>(_ identity: Identity, using transport: ActorTransport)
93+
throws -> Self where Identity: ActorIdentity {
94+
switch try transport.resolve(AnyActorIdentity(identity), as: Self.self) {
95+
case .resolved(let instance):
96+
return instance
97+
98+
case .makeProxy:
99+
// FIXME: this needs actual implementation of distributedActorRemoteCreate
100+
let remote: Any = distributedActorRemoteCreate(identity: identity, transport: transport)
101+
return remote as! Self
102+
}
103+
}
104+
}
105+
86106
// ==== Hashable conformance ---------------------------------------------------
87107

88108
@available(SwiftStdlib 5.5, *)
@@ -220,6 +240,9 @@ func __isLocalActor(_ actor: AnyObject) -> Bool {
220240
@_silgen_name("swift_distributedActor_remote_initialize")
221241
func _distributedActorRemoteInitialize(_ actor: AnyObject)
222242

243+
@_silgen_name("swift_distributedActor_remote_create")
244+
func distributedActorRemoteCreate(identity: Any, transport: Any) -> Any // TODO: make it typed
245+
223246
/// Called to destroy the default actor instance in an actor.
224247
/// The implementation will call this within the actor's deinit.
225248
///

test/Concurrency/actor_isolation.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -853,6 +853,29 @@ actor MyActorP: P {
853853
func h() { g() }
854854
}
855855

856+
@available(SwiftStdlib 5.5, *)
857+
protocol SP {
858+
static func s()
859+
}
860+
861+
@available(SwiftStdlib 5.5, *)
862+
actor ASP: SP {
863+
static func s() { }
864+
}
865+
866+
@available(SwiftStdlib 5.5, *)
867+
protocol SPD {
868+
static func sd()
869+
}
870+
@available(SwiftStdlib 5.5, *)
871+
extension SPD {
872+
static func sd() { }
873+
}
874+
875+
@available(SwiftStdlib 5.5, *)
876+
actor ASPD: SPD {
877+
}
878+
856879
@available(SwiftStdlib 5.5, *)
857880
func testCrossActorProtocol<T: P>(t: T) async {
858881
await t.f()
@@ -863,6 +886,8 @@ func testCrossActorProtocol<T: P>(t: T) async {
863886
t.g()
864887
// expected-error@-1{{expression is 'async' but is not marked with 'await'}}{{3-3=await }}
865888
// expected-note@-2{{calls to instance method 'g()' from outside of its actor context are implicitly asynchronous}}
889+
ASP.s()
890+
ASPD.sd()
866891
}
867892

868893
// ----------------------------------------------------------------------

test/Distributed/distributed_actor_initialization.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ distributed actor OK1 {
1313
// ok, since all fields are initialized, the constructors can be synthesized
1414
}
1515

16-
// TODO: test all the FIXITs in this file (!!!)
16+
// TODO: test all the FIXITs in this file
1717

1818
@available(SwiftStdlib 5.5, *)
1919
distributed actor Bad1 {

test/Distributed/distributed_actor_isolation.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,17 @@ distributed actor DistributedActor_1 {
8989
fatalError()
9090
}
9191

92+
static func staticFunc() -> String { "" } // ok
93+
94+
// TODO: should be able to handle a static, global actor isolated function as well
95+
// @MainActor
96+
// static func staticMainActorFunc() -> String { "" } // ok
97+
98+
static distributed func staticDistributedFunc() -> String {
99+
// expected-error@-1{{'distributed' functions cannot be 'static'}}{10-21=}
100+
fatalError()
101+
}
102+
92103
func test_inside() async throws {
93104
_ = self.name
94105
_ = self.computedMutable
@@ -126,12 +137,31 @@ func test_outside(
126137
_ = distributed.id
127138
_ = distributed.actorTransport
128139

140+
// ==== static functions
141+
_ = distributed.staticFunc() // expected-error{{static member 'staticFunc' cannot be used on instance of type 'DistributedActor_1'}}
142+
_ = DistributedActor_1.staticFunc()
143+
129144
// ==== non-distributed functions
130145
_ = await distributed.hello() // expected-error{{only 'distributed' functions can be called from outside the distributed actor}}
131146
_ = await distributed.helloAsync() // expected-error{{only 'distributed' functions can be called from outside the distributed actor}}
132147
_ = try await distributed.helloAsyncThrows() // expected-error{{only 'distributed' functions can be called from outside the distributed actor}}
133148
}
134149

150+
// ==== Protocols and static (non isolated functions)
151+
152+
@available(SwiftStdlib 5.5, *)
153+
protocol P {
154+
static func hello() -> String
155+
}
156+
@available(SwiftStdlib 5.5, *)
157+
extension P {
158+
static func hello() -> String { "" }
159+
}
160+
161+
@available(SwiftStdlib 5.5, *)
162+
distributed actor ALL: P {
163+
}
164+
135165
// ==== Codable parameters and return types ------------------------------------
136166

137167
@available(SwiftStdlib 5.5, *)
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %target-typecheck-verify-swift -enable-experimental-distributed
2+
// REQUIRES: concurrency
3+
// REQUIRES: distributed
4+
5+
import _Distributed
6+
7+
@available(SwiftStdlib 5.5, *)
8+
distributed actor Capybara { }
9+
10+
//@available(SwiftStdlib 5.5, *)
11+
//protocol Wheeker: DistributedActor { }
12+
//@available(SwiftStdlib 5.5, *)
13+
//distributed actor GuineaPing: Wheeker { }
14+
15+
@available(SwiftStdlib 5.5, *)
16+
func test<Identity: ActorIdentity>(identity: Identity, transport: ActorTransport) async throws {
17+
let _: Capybara = try Capybara.resolve(identity, using: transport)
18+
19+
// TODO: implement resolve being able to be called on a distributed actor protocol
20+
// (yes, normally it is not allowed to call such function... so we need to
21+
// discuss and figure out how we want to expose the resolve of a protocol)
22+
// let c: Wheeker = try Wheeker.resolve(.init(identity), using: transport)
23+
}

0 commit comments

Comments
 (0)