Skip to content

Commit c2ba06c

Browse files
committed
Make the actor transport an associated type of the DistributedActor protocol.
Eliminate the required use of existentials in distributed actors by introducing the `Transport` associated type into the `DistributedActor` protocol. Each distributed actor has a known (concrete) actor transport type, reducing storage requirements and transport dynamism when it isn't needed. Distributed actors can manually specify their `Transport` associated type or pick up a default by looking for a type named `DefaultActorTransport`. A library that vends an actor transport can make create a public typealias `DefaultActorTransport` referring to its transport, so importing that library and defining a distributed actor will use that library's transport. Introduce a type-erased `AnyActorTransport` type to provide an explicitly dynamic actor transport. This is still an important option, e.g., for cases where one wants to be able to dynamically change the transport for testing or different kinds of deployment. For now, we default to this transport in the library (via `DefaultActorTransport`), but we may very well want to eliminate this because it will be ambiguous with client libraries that vend their own `DefaultActorTransport`.
1 parent d682049 commit c2ba06c

28 files changed

+254
-113
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4417,6 +4417,8 @@ ERROR(actor_protocol_illegal_inheritance,none,
44174417
ERROR(distributed_actor_protocol_illegal_inheritance,none,
44184418
"non-distributed actor type %0 cannot conform to the 'DistributedActor' protocol",
44194419
(DeclName))
4420+
ERROR(broken_distributed_actor_requirement,none,
4421+
"DistributedActor protocol is broken: unexpected requirement", ())
44204422

44214423
ERROR(unowned_executor_outside_actor,none,
44224424
"'unownedExecutor' can only be implemented within the main "

include/swift/AST/KnownIdentifiers.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ IDENTIFIER(decode)
6565
IDENTIFIER(decodeIfPresent)
6666
IDENTIFIER(Decoder)
6767
IDENTIFIER(decoder)
68+
IDENTIFIER(DefaultActorTransport)
6869
IDENTIFIER_(Differentiation)
6970
IDENTIFIER_WITH_NAME(PatternMatchVar, "$match")
7071
IDENTIFIER(dynamicallyCall)
@@ -141,6 +142,7 @@ IDENTIFIER_WITH_NAME(SwiftObject, "_TtCs12_SwiftObject")
141142
IDENTIFIER(SwiftNativeNSObject)
142143
IDENTIFIER(to)
143144
IDENTIFIER(toRaw)
145+
IDENTIFIER(Transport)
144146
IDENTIFIER(Type)
145147
IDENTIFIER(type)
146148
IDENTIFIER(typeMismatch)

lib/SILGen/SILGenDistributed.cpp

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -45,22 +45,45 @@ static VarDecl* lookupProperty(NominalTypeDecl *ty, DeclName name) {
4545
return dyn_cast<VarDecl>(refs.front());
4646
}
4747

48+
/// Emit a reference to a specific stored property of the actor.
49+
static SILValue emitActorPropertyReference(
50+
SILGenFunction &SGF, SILLocation loc, SILValue actorSelf,
51+
VarDecl *property) {
52+
Type formalType = SGF.F.mapTypeIntoContext(property->getInterfaceType());
53+
SILType loweredType = SGF.getLoweredType(formalType).getAddressType();
54+
#if false
55+
if (!loweredType.isAddress()) {
56+
loweredType = SILType::getPrimitiveAddressType(
57+
formalType->getCanonicalType());
58+
}
59+
#endif
60+
return SGF.B.createRefElementAddr(loc, actorSelf, property, loweredType);
61+
}
62+
4863
/// Perform an initializing store to the given property using the value
4964
/// \param actorSelf the value representing `self` for the actor instance.
5065
/// \param prop the property to be initialized.
5166
/// \param value the value to use when initializing the property.
5267
static void initializeProperty(SILGenFunction &SGF, SILLocation loc,
5368
SILValue actorSelf,
5469
VarDecl* prop, SILValue value) {
55-
CanType propType = SGF.F.mapTypeIntoContext(prop->getInterfaceType())
56-
->getCanonicalType();
57-
SILType loweredPropType = SGF.getLoweredType(propType);
58-
auto fieldAddr = SGF.B.createRefElementAddr(loc, actorSelf, prop, loweredPropType);
70+
Type formalType = SGF.F.mapTypeIntoContext(prop->getInterfaceType());
71+
SILType loweredType = SGF.getLoweredType(formalType);
72+
73+
auto fieldAddr = emitActorPropertyReference(SGF, loc, actorSelf, prop);
5974

60-
if (fieldAddr->getType().isAddress())
75+
if (loweredType.isAddressOnly(SGF.F)) {
6176
SGF.B.createCopyAddr(loc, value, fieldAddr, IsNotTake, IsInitialization);
62-
else
63-
SGF.B.emitStoreValueOperation(loc, value, fieldAddr, StoreOwnershipQualifier::Init);
77+
} else {
78+
if (value->getType().isAddress()) {
79+
value = SGF.B.createTrivialLoadOr(
80+
loc, value, LoadOwnershipQualifier::Copy);
81+
}
82+
83+
value = SGF.B.emitCopyValueOperation(loc, value);
84+
SGF.B.emitStoreValueOperation(
85+
loc, value, fieldAddr, StoreOwnershipQualifier::Init);
86+
}
6487
}
6588

6689
/******************************************************************************/
@@ -355,26 +378,20 @@ void SILGenFunction::emitResignIdentityCall(SILLocation loc,
355378
FormalEvaluationScope scope(*this);
356379

357380
// ==== locate: self.id
358-
auto *idVarDeclRef = lookupProperty(actorDecl, ctx.Id_id);
359-
CanType idType = F.mapTypeIntoContext(
360-
idVarDeclRef->getInterfaceType())->getCanonicalType();
361-
auto idRef = B.createRefElementAddr(loc, actorSelf, idVarDeclRef,
362-
getLoweredType(idType));
381+
auto idRef = emitActorPropertyReference(
382+
*this, loc, actorSelf.getValue(), lookupProperty(actorDecl, ctx.Id_id));
363383

364384
// ==== locate: self.actorTransport
365-
auto transportVarDeclRef = lookupProperty(actorDecl, ctx.Id_actorTransport);
366-
CanType transportType = F.mapTypeIntoContext(
367-
transportVarDeclRef->getInterfaceType())->getCanonicalType();
368-
auto transportRef =
369-
B.createRefElementAddr(loc, actorSelf, transportVarDeclRef,
370-
getLoweredType(transportType));
385+
auto transportRef = emitActorPropertyReference(
386+
*this, loc, actorSelf.getValue(),
387+
lookupProperty(actorDecl, ctx.Id_actorTransport));
371388

372389
// Perform the call.
373390
emitActorTransportWitnessCall(
374391
B, loc, ctx.Id_resignIdentity,
375-
transportRef.getValue(),
392+
transportRef,
376393
SILType(),
377-
{ idRef.getValue() });
394+
{ idRef });
378395
}
379396

380397
void

lib/SILOptimizer/Utils/DistributedActor.cpp

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ void emitActorTransportWitnessCall(
131131
if (methodSILFnTy->getSelfParameter().isFormalIndirect() &&
132132
!transport->getType().isAddress()) {
133133
auto buf = B.createAllocStack(loc, transport->getType(), None);
134+
transport = B.emitCopyValueOperation(loc, transport);
134135
B.emitStoreValueOperation(
135136
loc, transport, buf, StoreOwnershipQualifier::Init);
136137
temporaryTransportBuffer = SILValue(buf);
@@ -169,18 +170,12 @@ void emitActorTransportWitnessCall(
169170
// If we had to create a buffer to pass the transport
170171
if (temporaryTransportBuffer) {
171172
emitCleanup([&](SILBuilder & builder) {
173+
auto value = builder.emitLoadValueOperation(
174+
loc, *temporaryTransportBuffer, LoadOwnershipQualifier::Take);
175+
builder.emitDestroyValueOperation(loc, value);
172176
builder.createDeallocStack(loc, *temporaryTransportBuffer);
173177
});
174178
}
175-
176-
// If we opened an existential, then destroy the existential.
177-
#if false
178-
if (openedExistential) {
179-
emitCleanup([&](SILBuilder & builder) {
180-
builder.emitDestroyAddr(loc, transport);
181-
});
182-
}
183-
#endif
184179
}
185180

186181
void emitActorReadyCall(SILBuilder &B, SILLocation loc, SILValue actor,

lib/Sema/DerivedConformanceDistributedActor.cpp

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,8 @@ static FuncDecl *deriveDistributedActor_resolve(DerivedConformance &derived) {
6161
return param;
6262
};
6363

64-
Type addressType = C.getAnyActorIdentityDecl()->getDeclaredInterfaceType();
65-
Type transportType = getDistributedActorTransportType(decl);
64+
auto addressType = C.getAnyActorIdentityDecl()->getDeclaredInterfaceType();
65+
auto transportType = getDistributedActorTransportType(decl);
6666

6767
// (_ identity: AnyActorIdentity, using transport: ActorTransport)
6868
auto *params = ParameterList::create(
@@ -133,10 +133,11 @@ static ValueDecl *deriveDistributedActor_actorTransport(
133133

134134
// ```
135135
// nonisolated
136-
// let actorTransport: ActorTransport
136+
// let actorTransport: Transport
137137
// ```
138138
// (no need for @actorIndependent because it is an immutable let)
139-
Type propertyType = getDistributedActorTransportType(derived.Nominal);
139+
auto propertyType = getDistributedActorTransportType(derived.Nominal);
140+
140141
VarDecl *propDecl;
141142
PatternBindingDecl *pbDecl;
142143
std::tie(propDecl, pbDecl) = derived.declareDerivedProperty(
@@ -154,6 +155,36 @@ static ValueDecl *deriveDistributedActor_actorTransport(
154155
return propDecl;
155156
}
156157

158+
static Type deriveDistributedActor_Transport(
159+
DerivedConformance &derived) {
160+
assert(derived.Nominal->isDistributedActor());
161+
auto &C = derived.Context;
162+
163+
// Look for a type DefaultActorTransport within the parent context.
164+
auto defaultTransportLookup = TypeChecker::lookupUnqualified(
165+
derived.getConformanceContext()->getModuleScopeContext(),
166+
DeclNameRef(C.Id_DefaultActorTransport),
167+
derived.ConformanceDecl->getLoc());
168+
TypeDecl *defaultTransportTypeDecl = nullptr;
169+
for (const auto &found : defaultTransportLookup) {
170+
if (auto foundType = dyn_cast_or_null<TypeDecl>(found.getValueDecl())) {
171+
if (defaultTransportTypeDecl) {
172+
// Note: ambiguity, for now just fail.
173+
return nullptr;
174+
}
175+
176+
defaultTransportTypeDecl = foundType;
177+
continue;
178+
}
179+
}
180+
181+
// There is no default, so fail to synthesize.
182+
if (!defaultTransportTypeDecl)
183+
return nullptr;
184+
185+
// Return the default transport type.
186+
return defaultTransportTypeDecl->getDeclaredInterfaceType();
187+
}
157188
// ==== ------------------------------------------------------------------------
158189

159190
ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) {
@@ -174,3 +205,17 @@ ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) {
174205

175206
return nullptr;
176207
}
208+
209+
std::pair<Type, TypeDecl *> DerivedConformance::deriveDistributedActor(
210+
AssociatedTypeDecl *assocType) {
211+
if (!canDeriveDistributedActor(Nominal, cast<DeclContext>(ConformanceDecl)))
212+
return std::make_pair(Type(), nullptr);
213+
214+
if (assocType->getName() == Context.Id_Transport) {
215+
return std::make_pair(deriveDistributedActor_Transport(*this), nullptr);
216+
}
217+
218+
Context.Diags.diagnose(assocType->getLoc(),
219+
diag::broken_distributed_actor_requirement);
220+
return std::make_pair(Type(), nullptr);
221+
}

lib/Sema/DerivedConformances.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,12 @@ class DerivedConformance {
311311
/// \returns the derived member, which will also be added to the type.
312312
ValueDecl *deriveDistributedActor(ValueDecl *requirement);
313313

314+
/// Derive a DistributedActor associated type for a distributed actor.
315+
///
316+
/// \returns the derived type member, which will also be added to the type.
317+
std::pair<Type, TypeDecl *> deriveDistributedActor(
318+
AssociatedTypeDecl *assocType);
319+
314320
/// Determine if \c Actor can be derived for the given type.
315321
static bool canDeriveActor(DeclContext *DC, NominalTypeDecl *NTD);
316322

lib/Sema/TypeCheckDistributed.cpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -184,20 +184,15 @@ void swift::checkDistributedActorConstructor(const ClassDecl *decl, ConstructorD
184184
if (!ctor->isDesignatedInit())
185185
return;
186186

187-
// === Designated initializers must accept exactly one ActorTransport
188-
auto &C = ctor->getASTContext();
189-
auto module = ctor->getParentModule();
190-
187+
// === Designated initializers must accept exactly one actor transport that
188+
// matches the actor transport type of the actor.
191189
SmallVector<ParamDecl*, 2> transportParams;
192190
int transportParamsCount = 0;
193-
auto protocolDecl = C.getProtocol(KnownProtocolKind::ActorTransport);
194-
auto protocolTy = protocolDecl->getDeclaredInterfaceType();
195-
191+
Type transportTy = ctor->mapTypeIntoContext(
192+
getDistributedActorTransportType(const_cast<ClassDecl *>(decl)));
196193
for (auto param : *ctor->getParameters()) {
197194
auto paramTy = ctor->mapTypeIntoContext(param->getInterfaceType());
198-
auto conformance = TypeChecker::conformsToProtocol(paramTy, protocolDecl, module);
199-
200-
if (paramTy->isEqual(protocolTy) || !conformance.isInvalid()) {
195+
if (paramTy->isEqual(transportTy)) {
201196
transportParamsCount += 1;
202197
transportParams.push_back(param);
203198
}
@@ -252,9 +247,13 @@ Type swift::getDistributedActorTransportType(NominalTypeDecl *actor) {
252247
assert(actor->isDistributedActor());
253248
auto &ctx = actor->getASTContext();
254249

255-
auto protocol = ctx.getProtocol(KnownProtocolKind::ActorTransport);
250+
auto protocol = ctx.getProtocol(KnownProtocolKind::DistributedActor);
256251
if (!protocol)
257252
return ErrorType::get(ctx);
258253

259-
return protocol->getDeclaredInterfaceType();
254+
// Dig out the actor transport type.
255+
auto module = actor->getParentModule();
256+
Type selfType = actor->getSelfInterfaceType();
257+
auto conformance = module->lookupConformance(selfType, protocol);
258+
return conformance.getTypeWitnessByName(selfType, ctx.Id_Transport);
260259
}

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6601,6 +6601,8 @@ TypeChecker::deriveTypeWitness(DeclContext *DC,
66016601
return std::make_pair(derived.deriveCaseIterable(AssocType), nullptr);
66026602
case KnownProtocolKind::Differentiable:
66036603
return derived.deriveDifferentiable(AssocType);
6604+
case KnownProtocolKind::DistributedActor:
6605+
return derived.deriveDistributedActor(AssocType);
66046606
default:
66056607
return std::make_pair(nullptr, nullptr);
66066608
}

stdlib/public/Distributed/ActorTransport.swift

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,47 @@ public protocol ActorTransport: Sendable {
6767
func assignIdentity<Act>(_ actorType: Act.Type) -> AnyActorIdentity
6868
where Act: DistributedActor
6969

70-
func actorReady<Act>(_ actor: Act) where Act: DistributedActor
70+
func actorReady<Act>(_ actor: Act)
71+
where Act: DistributedActor
7172

7273
/// Called during actor deinit/destroy.
7374
func resignIdentity(_ id: AnyActorIdentity)
75+
}
76+
77+
@available(SwiftStdlib 5.6, *)
78+
public struct AnyActorTransport: ActorTransport {
79+
let transport: ActorTransport
80+
81+
public init<Transport: ActorTransport>(_ transport: Transport) {
82+
self.transport = transport
83+
}
84+
85+
public func decodeIdentity(from decoder: Decoder) throws -> AnyActorIdentity {
86+
return try transport.decodeIdentity(from: decoder)
87+
}
7488

89+
public func resolve<Act>(
90+
_ identity: AnyActorIdentity, as actorType: Act.Type
91+
) throws -> Act? where Act: DistributedActor {
92+
return try transport.resolve(identity, as: actorType)
93+
}
94+
95+
public func assignIdentity<Act>(_ actorType: Act.Type) -> AnyActorIdentity
96+
where Act: DistributedActor {
97+
return transport.assignIdentity(actorType)
98+
}
99+
100+
public func actorReady<Act>(_ actor: Act)
101+
where Act: DistributedActor {
102+
transport.actorReady(actor)
103+
}
104+
105+
/// Called during actor deinit/destroy.
106+
public func resignIdentity(_ id: AnyActorIdentity) {
107+
transport.resignIdentity(id)
108+
}
75109
}
110+
111+
/// Use the existential wrapper as the default actor transport.
112+
@available(SwiftStdlib 5.6, *)
113+
public typealias DefaultActorTransport = AnyActorTransport

stdlib/public/Distributed/DistributedActor.swift

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@ public protocol AnyActor: Sendable, AnyObject {}
3636
@available(SwiftStdlib 5.6, *)
3737
public protocol DistributedActor:
3838
AnyActor, Sendable, Identifiable, Hashable, Codable {
39+
/// The type of transport used to communicate with actors of this type.
40+
associatedtype Transport: ActorTransport
41+
3942
/// Resolves the passed in `identity` against the `transport`, returning
4043
/// either a local or remote actor reference.
4144
///
@@ -52,7 +55,7 @@ public protocol DistributedActor:
5255
// We want to move to accepting a generic or existential identity here
5356
// static func resolve<Identity>(_ identity: Identity, using transport: ActorTransport)
5457
// throws -> Self where Identity: ActorIdentity
55-
static func resolve(_ identity: AnyActorIdentity, using transport: ActorTransport)
58+
static func resolve(_ identity: AnyActorIdentity, using transport: Transport)
5659
throws -> Self
5760

5861
/// The `ActorTransport` associated with this actor.
@@ -61,7 +64,7 @@ public protocol DistributedActor:
6164
///
6265
/// Conformance to this requirement is synthesized automatically for any
6366
/// `distributed actor` declaration.
64-
nonisolated var actorTransport: ActorTransport { get } // TODO: rename to `transport`?
67+
nonisolated var actorTransport: Transport { get } // TODO: rename to `transport`?
6568

6669
/// Logical identity of this distributed actor.
6770
///
@@ -100,9 +103,9 @@ extension CodingUserInfoKey {
100103
@available(SwiftStdlib 5.6, *)
101104
extension DistributedActor {
102105
nonisolated public init(from decoder: Decoder) throws {
103-
guard let transport = decoder.userInfo[.actorTransportKey] as? ActorTransport else {
106+
guard let transport = decoder.userInfo[.actorTransportKey] as? Transport else {
104107
throw DistributedActorCodingError(message:
105-
"Missing ActorTransport (for key .actorTransportKey) " +
108+
"Missing Transport (for key .actorTransportKey) " +
106109
"in Decoder.userInfo, while decoding \(Self.self).")
107110
}
108111

test/Distributed/Runtime/distributed_actor_decode.swift

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ struct FakeTransport: ActorTransport {
6666
}
6767
}
6868

69+
@available(SwiftStdlib 5.5, *)
70+
typealias DefaultActorTransport = FakeTransport
71+
6972
// ==== Test Coding ------------------------------------------------------------
7073

7174
class TestEncoder: Encoder {

0 commit comments

Comments
 (0)