Skip to content

Commit 6004e29

Browse files
committed
Introduce a builtin and API for getting the local actor from a distributed one
1 parent 3d00fa3 commit 6004e29

File tree

15 files changed

+238
-6
lines changed

15 files changed

+238
-6
lines changed

include/swift/AST/Builtins.def

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1083,6 +1083,12 @@ BUILTIN_MISC_OPERATION_WITH_SILGEN(GetEnumTag, "getEnumTag", "", Special)
10831083
/// tag's payload is already initialized with the given source.
10841084
BUILTIN_MISC_OPERATION_WITH_SILGEN(InjectEnumTag, "injectEnumTag", "", Special)
10851085

1086+
/// distributedActorAsAnyActor: <DA: DistributedActor>(_: DA) -> any Actor
1087+
///
1088+
/// For a given distributed actor that is known to be local, extract an
1089+
/// `any Actor` existential that refers to the local actor.
1090+
BUILTIN_MISC_OPERATION_WITH_SILGEN(DistributedActorAsAnyActor, "distributedActorAsAnyActor", "n", Special)
1091+
10861092
#undef BUILTIN_MISC_OPERATION_WITH_SILGEN
10871093

10881094
#undef BUILTIN_MISC_OPERATION

include/swift/SIL/FormalLinkage.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,9 @@ SILLinkage
5252
getLinkageForProtocolConformance(const RootProtocolConformance *C,
5353
ForDefinition_t definition);
5454

55+
/// Whether the given conformance is synthesized by the implementation.
56+
bool isSynthesizedConformance(const RootProtocolConformance *root);
57+
5558
} // end swift namespace
5659

5760
#endif

lib/AST/Builtins.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1975,6 +1975,19 @@ static ValueDecl *getHopToActor(ASTContext &ctx, Identifier id) {
19751975
return builder.build(id);
19761976
}
19771977

1978+
static ValueDecl *getDistributedActorAsAnyActor(ASTContext &ctx, Identifier id) {
1979+
BuiltinFunctionBuilder builder(ctx);
1980+
auto *distributedActorProto = ctx.getProtocol(KnownProtocolKind::DistributedActor);
1981+
auto *actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
1982+
1983+
// Create type parameters and add conformance constraints.
1984+
auto actorParam = makeGenericParam();
1985+
builder.addParameter(actorParam);
1986+
builder.addConformanceRequirement(actorParam, distributedActorProto);
1987+
builder.setResult(makeConcrete(actorProto->getDeclaredExistentialType()));
1988+
return builder.build(id);
1989+
}
1990+
19781991
static ValueDecl *getPackLength(ASTContext &ctx, Identifier id) {
19791992
BuiltinFunctionBuilder builder(ctx, /* genericParamCount */ 1,
19801993
/* anyObject */ false,
@@ -3055,6 +3068,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
30553068

30563069
case BuiltinValueKind::InjectEnumTag:
30573070
return getInjectEnumTag(Context, Id);
3071+
3072+
case BuiltinValueKind::DistributedActorAsAnyActor:
3073+
return getDistributedActorAsAnyActor(Context, Id);
30583074
}
30593075

30603076
llvm_unreachable("bad builtin value!");

lib/AST/ProtocolConformance.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,18 @@ bool NormalProtocolConformance::isRetroactive() const {
321321
}
322322

323323
bool NormalProtocolConformance::isSynthesizedNonUnique() const {
324+
// Synthesized conformance of DistributedActor to Actor.
325+
if (getProtocol()->isSpecificProtocol(KnownProtocolKind::Actor)) {
326+
if (auto fromProto = getDeclContext()->getSelfProtocolDecl()) {
327+
if (fromProto->isSpecificProtocol(KnownProtocolKind::DistributedActor))
328+
return true;
329+
}
330+
}
331+
332+
// Check if the conformance was synthesized by the ClangImporter.
324333
if (auto *file = dyn_cast<FileUnit>(getDeclContext()->getModuleScopeContext()))
325334
return file->getKind() == FileUnitKind::ClangModule;
335+
326336
return false;
327337
}
328338

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,7 @@ BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, TypePtrAuthDiscriminator)
880880
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, TargetOSVersionAtLeast)
881881
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, GetEnumTag)
882882
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, InjectEnumTag)
883+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, DistributedActorAsAnyActor)
883884
OperandOwnership OperandOwnershipBuiltinClassifier::visitCopy(BuiltinInst *bi,
884885
StringRef) {
885886
if (bi->getFunction()->getConventions().useLoweredAddresses()) {

lib/SIL/IR/SIL.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,19 @@ SILLinkage swift::getSILLinkage(FormalLinkage linkage,
7474
llvm_unreachable("bad formal linkage");
7575
}
7676

77+
bool swift::isSynthesizedConformance(const RootProtocolConformance *root) {
78+
if (auto normal = dyn_cast<NormalProtocolConformance>(root))
79+
return normal->isSynthesizedNonUnique();
80+
81+
return false;
82+
}
83+
84+
7785
SILLinkage
7886
swift::getLinkageForProtocolConformance(const RootProtocolConformance *C,
7987
ForDefinition_t definition) {
80-
// If the conformance was synthesized by the ClangImporter, give it
81-
// shared linkage.
82-
if (isa<ClangModuleUnit>(C->getDeclContext()->getModuleScopeContext()))
88+
// If the conformance was synthesized, give it shared linkage.
89+
if (isSynthesizedConformance(C))
8390
return SILLinkage::Shared;
8491

8592
auto typeDecl = C->getDeclContext()->getSelfNominalTypeDecl();

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,6 +621,7 @@ CONSTANT_OWNERSHIP_BUILTIN(None, TaskRunInline)
621621
CONSTANT_OWNERSHIP_BUILTIN(None, Copy)
622622
CONSTANT_OWNERSHIP_BUILTIN(None, GetEnumTag)
623623
CONSTANT_OWNERSHIP_BUILTIN(None, InjectEnumTag)
624+
CONSTANT_OWNERSHIP_BUILTIN(Owned, DistributedActorAsAnyActor)
624625

625626
#undef CONSTANT_OWNERSHIP_BUILTIN
626627

lib/SILGen/SILGenBuiltin.cpp

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/FileUnit.h"
2626
#include "swift/AST/GenericEnvironment.h"
2727
#include "swift/AST/Module.h"
28+
#include "swift/AST/ProtocolConformance.h"
2829
#include "swift/AST/ReferenceCounting.h"
2930
#include "swift/SIL/SILArgument.h"
3031
#include "swift/SIL/SILUndef.h"
@@ -1886,6 +1887,63 @@ static ManagedValue emitBuiltinInjectEnumTag(SILGenFunction &SGF, SILLocation lo
18861887
return ManagedValue::forObjectRValueWithoutOwnership(bi);
18871888
}
18881889

1890+
/// Find the extension on DistributedActor that defines __actorUnownedExecutor.
1891+
static ExtensionDecl *findDistributedActorAsActorExtension(
1892+
ProtocolDecl *distributedActorProto) {
1893+
ASTContext &ctx = distributedActorProto->getASTContext();
1894+
1895+
auto name = ctx.getIdentifier("__actorUnownedExecutor");
1896+
auto results = distributedActorProto->lookupDirect(
1897+
name, SourceLoc(),
1898+
NominalTypeDecl::LookupDirectFlags::IncludeAttrImplements);
1899+
for (auto result : results) {
1900+
if (auto var = dyn_cast<VarDecl>(result)) {
1901+
return dyn_cast<ExtensionDecl>(var->getDeclContext());
1902+
}
1903+
}
1904+
1905+
return nullptr;
1906+
}
1907+
1908+
static ManagedValue emitBuiltinDistributedActorAsAnyActor(
1909+
SILGenFunction &SGF, SILLocation loc, SubstitutionMap subs,
1910+
ArrayRef<ManagedValue> args, SGFContext C) {
1911+
auto &ctx = SGF.getASTContext();
1912+
auto distributedActor = args[0];
1913+
1914+
auto builtinDecl = cast<FuncDecl>(getBuiltinValueDecl(
1915+
ctx, ctx.getIdentifier("distributedActorAsAnyActor")));
1916+
auto genericSignature = builtinDecl->getGenericSignature();
1917+
auto genericParam = genericSignature.getGenericParams()[0];
1918+
1919+
auto distributedActorProto = ctx.getProtocol(KnownProtocolKind::DistributedActor);
1920+
auto ext = findDistributedActorAsActorExtension(distributedActorProto);
1921+
1922+
// Conformance of DistributedActor to Actor.
1923+
auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
1924+
CanType distributedActorType = distributedActor.getType().getASTType();
1925+
RootProtocolConformance *daAsActorConformance = ctx.getNormalConformance(
1926+
Type(genericParam), actorProto, SourceLoc(), ext,
1927+
ProtocolConformanceState::Incomplete, /*isUnchecked=*/false,
1928+
/*isPreconcurrency=*/false);
1929+
ProtocolConformanceRef conformance(
1930+
actorProto,
1931+
ctx.getSpecializedConformance(distributedActorType, daAsActorConformance,
1932+
subs));
1933+
ProtocolConformanceRef conformances[1] = { conformance };
1934+
1935+
// Erase the distributed actor instance into an `any Actor` existential with
1936+
// the special conformance.
1937+
auto &distributedActorTL = SGF.getTypeLowering(distributedActorType);
1938+
auto &anyActorTL = SGF.getTypeLowering(actorProto->getDeclaredExistentialType());
1939+
return SGF.emitExistentialErasure(
1940+
loc, distributedActorType, distributedActorTL, anyActorTL,
1941+
ctx.AllocateCopy(conformances),
1942+
C, [&distributedActor](SGFContext) {
1943+
return distributedActor;
1944+
});
1945+
}
1946+
18891947
llvm::Optional<SpecializedEmitter>
18901948
SpecializedEmitter::forDecl(SILGenModule &SGM, SILDeclRef function) {
18911949
// Only consider standalone declarations in the Builtin module.

lib/SILGen/SILGenLazyConformance.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "swift/AST/ProtocolConformance.h"
1717
#include "swift/AST/ProtocolConformanceRef.h"
1818
#include "swift/ClangImporter/ClangModule.h"
19+
#include "swift/SIL/FormalLinkage.h"
1920
#include "swift/SIL/SILInstruction.h"
2021
#include "swift/SIL/SILVisitor.h"
2122

@@ -64,7 +65,7 @@ void SILGenModule::useConformance(ProtocolConformanceRef conformanceRef) {
6465
// If this conformance was not synthesized by the ClangImporter, we're not
6566
// going to be emitting it lazily either, so we can avoid doing anything
6667
// below.
67-
if (!isa<ClangModuleUnit>(normal->getDeclContext()->getModuleScopeContext()))
68+
if (!isSynthesizedConformance(normal))
6869
return;
6970

7071
// If we already emitted this witness table, we don't need to track the fact

lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,7 @@ static bool isBarrier(SILInstruction *inst) {
209209
case BuiltinValueKind::AutoDiffAllocateSubcontextWithType:
210210
case BuiltinValueKind::AddressOfBorrowOpaque:
211211
case BuiltinValueKind::UnprotectedAddressOfBorrowOpaque:
212+
case BuiltinValueKind::DistributedActorAsAnyActor:
212213
return true;
213214
}
214215
}

lib/Sema/TypeCheckAttr.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3886,7 +3886,9 @@ void AttributeChecker::visitImplementsAttr(ImplementsAttr *attr) {
38863886
// conforms to the specified protocol.
38873887
NominalTypeDecl *NTD = DC->getSelfNominalTypeDecl();
38883888
if (auto *OtherPD = dyn_cast<ProtocolDecl>(NTD)) {
3889-
if (!OtherPD->inheritsFrom(PD)) {
3889+
if (!OtherPD->inheritsFrom(PD) &&
3890+
!(OtherPD->isSpecificProtocol(KnownProtocolKind::DistributedActor) ||
3891+
PD->isSpecificProtocol(KnownProtocolKind::Actor))) {
38903892
diagnose(attr->getLocation(),
38913893
diag::implements_attr_protocol_not_conformed_to, NTD, PD)
38923894
.highlight(attr->getProtocolTypeRepr()->getSourceRange());

lib/Serialization/Deserialization.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -971,7 +971,10 @@ ProtocolConformanceDeserializer::readNormalProtocolConformance(
971971
uint64_t offset = conformanceEntry;
972972
conformanceEntry = conformance;
973973

974-
dc->getSelfNominalTypeDecl()->registerProtocolConformance(conformance);
974+
// Note: the DistributedActor -> Actor pseudo-conformance can be deserialized
975+
// but must not be registered, so don't register it here.
976+
if (!dc->getSelfProtocolDecl())
977+
dc->getSelfNominalTypeDecl()->registerProtocolConformance(conformance);
975978

976979
// If the conformance is complete, we're done.
977980
if (conformance->isComplete())

stdlib/public/Distributed/DistributedActor.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,28 @@ extension DistributedActor {
373373
}
374374
}
375375

376+
/// Supports the operation to produce an any Actor instance from a local
377+
/// distributed actor
378+
@available(SwiftStdlib 5.7, *)
379+
extension DistributedActor {
380+
@_alwaysEmitIntoClient
381+
@_implements(Actor, unownedExecutor)
382+
public nonisolated var __actorUnownedExecutor: UnownedSerialExecutor {
383+
if #available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *) {
384+
return unownedExecutor
385+
} else {
386+
fatalError("FIXME: Implement default actor executor on older platforms")
387+
}
388+
}
389+
390+
/// For a local distributed actor, produce an instance of the underlying
391+
/// \c Actor.
392+
@backDeployed(before: SwiftStdlib 5.11)
393+
public var asLocalActor: any Actor {
394+
Builtin.distributedActorAsAnyActor(self)
395+
}
396+
}
397+
376398
/******************************************************************************/
377399
/************************* Runtime Functions **********************************/
378400
/******************************************************************************/
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems %S/../Inputs/FakeDistributedActorSystems.swift
3+
// RUN: %target-build-swift -module-name main %import-libdispatch -j2 -parse-as-library -Xfrontend -disable-availability-checking -I %t %s %S/../Inputs/FakeDistributedActorSystems.swift -g -o %t/a.out -enable-experimental-feature BuiltinModule
4+
// RUN: %target-codesign %t/a.out
5+
// RUN: %target-run %t/a.out | %FileCheck %s --color
6+
7+
// REQUIRES: executable_test
8+
// REQUIRES: concurrency
9+
// REQUIRES: distributed
10+
// REQUIRES: libdispatch
11+
12+
// rdar://76038845
13+
// UNSUPPORTED: use_os_stdlib
14+
// UNSUPPORTED: back_deployment_runtime
15+
16+
// FIXME(distributed): Distributed actors currently have some issues on windows rdar://82593574
17+
// UNSUPPORTED: OS=windows-msvc
18+
19+
import Builtin // FIXME: Part of a hack to get the protocol conformance defined properly
20+
import Dispatch
21+
import Distributed
22+
import FakeDistributedActorSystems
23+
24+
typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem
25+
26+
extension DistributedActor {
27+
func f() {
28+
// FIXME: Part of a hack that forces the protocol conformance for DistributedActor -> Actor to be defined
29+
Builtin.distributedActorAsAnyActor(self)
30+
}
31+
}
32+
33+
distributed actor Worker {
34+
var counter: Int = 0
35+
36+
distributed func test() async {
37+
print("Starting on distributed actor \(self)")
38+
counter = counter + 1
39+
let result = await asLocalActor.doSomethingAsync {
40+
incrementCounter()
41+
return "\(counter)"
42+
}
43+
print("Distributed actor received \(result)")
44+
}
45+
46+
func incrementCounter() {
47+
counter += 1
48+
}
49+
}
50+
51+
extension Actor {
52+
func doSomethingAsync(body: () async -> String) async -> String {
53+
print("Executing on local actor \(self)")
54+
let result = await body()
55+
print("Got \(result) back")
56+
return result + "!"
57+
}
58+
}
59+
60+
@main struct Main {
61+
static func main() async throws {
62+
let worker = Worker(actorSystem: DefaultDistributedActorSystem())
63+
64+
// CHECK: Starting on distributed actor [[PTR:%.*]]
65+
// CHECK: Executing on local actor [[PTR]]
66+
// CHECK: Got 2 back
67+
// CHECK: Distributed actor received 2!
68+
try await worker.test()
69+
70+
print("OK") // CHECK: OK
71+
}
72+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %target-swift-frontend -disable-availability-checking %s -parse-as-library -parse-stdlib -emit-sil -o - | %FileCheck %s -check-prefix=CHECK-SIL
2+
3+
// RUN: %target-swift-frontend -disable-availability-checking %s -parse-as-library -parse-stdlib -emit-ir -o - | %FileCheck %s -check-prefix=CHECK-IR
4+
5+
// UNSUPPORTED: back_deploy_concurrency
6+
// REQUIRES: concurrency
7+
// REQUIRES: distributed
8+
9+
import _Concurrency
10+
import Distributed
11+
12+
// CHECK-IR: @"$sxScA11DistributedMc" = linkonce_odr hidden constant
13+
// CHECK-IR-SAME: i32 196808
14+
// CHECK-IR-SAME: $sxScA11DistributedScA15unownedExecutorScevgTW
15+
16+
// CHECK-SIL-LABEL: sil hidden @$s021distributed_actor_to_B011getAnyActor0aF0ScA_pxYi_t11Distributed0gF0RzlF : $@convention(thin) <τ_0_0 where τ_0_0 : DistributedActor> (@guaranteed τ_0_0) -> @owned any Actor
17+
func getAnyActor(distributedActor: isolated some DistributedActor) -> any Actor {
18+
// CHECK-SIL: [[EXISTENTIAL:%.*]] = init_existential_ref %0 : $τ_0_0 : $τ_0_0, $any Actor
19+
// CHECK-SIL-NEXT: strong_retain [[EXISTENTIAL]] : $any Actor
20+
// CHECK-SIL-NEXT: return [[EXISTENTIAL]] : $any Actor
21+
return Builtin.distributedActorAsAnyActor(distributedActor)
22+
}
23+
24+
// CHECK-IR-LABEL: define {{.*}} @"$s021distributed_actor_to_B011getAnyActor0aF0ScA_pxYi_t11Distributed0gF0RzlF
25+
// CHECK-IR: call ptr @swift_getWitnessTable(ptr @"$sxScA11DistributedMc", ptr %"some DistributedActor", ptr undef)
26+
27+
// CHECK-SIL-LABEL: sil_witness_table shared <Self where Self : DistributedActor> T: Actor module Distributed {
28+
// CHECK-SIL-NEXT: method #Actor.unownedExecutor!getter: <Self where Self : Actor> (Self) -> () -> UnownedSerialExecutor : @$sxScA11DistributedScA15unownedExecutorScevgTW
29+
// CHECK-SIL-NEXT: }

0 commit comments

Comments
 (0)