Skip to content

Commit 954cb6f

Browse files
authored
Merge pull request #64740 from ktoso/pick-cleanup-hop-to-distributed-custom-executor
2 parents 6fd7229 + ff96c5d commit 954cb6f

13 files changed

+228
-43
lines changed

include/swift/AST/ASTContext.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -891,6 +891,9 @@ class ASTContext final {
891891
/// Get the back-deployed availability for concurrency.
892892
AvailabilityContext getBackDeployedConcurrencyAvailability();
893893

894+
/// The the availability since when distributed actors are able to have custom executors.
895+
AvailabilityContext getConcurrencyDistributedActorWithCustomExecutorAvailability();
896+
894897
/// Get the runtime availability of support for differentiation.
895898
AvailabilityContext getDifferentiationAvailability();
896899

@@ -934,6 +937,14 @@ class ASTContext final {
934937
/// compiler for the target platform.
935938
AvailabilityContext getSwift57Availability();
936939

940+
/// Get the runtime availability of features introduced in the Swift 5.8
941+
/// compiler for the target platform.
942+
AvailabilityContext getSwift58Availability();
943+
944+
/// Get the runtime availability of features introduced in the Swift 5.9
945+
/// compiler for the target platform.
946+
AvailabilityContext getSwift59Availability();
947+
937948
// Note: Update this function if you add a new getSwiftXYAvailability above.
938949
/// Get the runtime availability for a particular version of Swift (5.0+).
939950
AvailabilityContext

include/swift/AST/Availability.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -329,6 +329,11 @@ class AvailabilityContext {
329329
bool isAvailableAsSPI() const {
330330
return SPI && *SPI;
331331
}
332+
333+
/// Returns a representation of this range as a string for debugging purposes.
334+
std::string getAsString() const {
335+
return "AvailabilityContext(" + OSVersion.getAsString() + (isAvailableAsSPI() ? ", spi" : "") + ")";
336+
}
332337
};
333338

334339

include/swift/AST/KnownSDKDecls.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121

2222
KNOWN_SDK_FUNC_DECL(Distributed, IsRemoteDistributedActor, "__isRemoteActor")
2323
KNOWN_SDK_FUNC_DECL(Distributed, IsLocalDistributedActor, "__isLocalActor")
24+
KNOWN_SDK_FUNC_DECL(Distributed, GetUnwrapLocalDistributedActorUnownedExecutor, "_getUnwrapLocalDistributedActorUnownedExecutor")
2425

2526
#undef KNOWN_SDK_FUNC_DECL
2627

lib/AST/Availability.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -489,6 +489,10 @@ AvailabilityContext ASTContext::getBackDeployedConcurrencyAvailability() {
489489
return getSwift51Availability();
490490
}
491491

492+
AvailabilityContext ASTContext::getConcurrencyDistributedActorWithCustomExecutorAvailability() {
493+
return getSwift59Availability();
494+
}
495+
492496
AvailabilityContext ASTContext::getDifferentiationAvailability() {
493497
return getSwiftFutureAvailability();
494498
}
@@ -642,6 +646,28 @@ AvailabilityContext ASTContext::getSwift57Availability() {
642646
}
643647
}
644648

649+
AvailabilityContext ASTContext::getSwift58Availability() {
650+
auto target = LangOpts.Target;
651+
652+
if (target.isMacOSX()) {
653+
return AvailabilityContext(
654+
VersionRange::allGTE(llvm::VersionTuple(13, 3, 0)));
655+
} else if (target.isiOS()) {
656+
return AvailabilityContext(
657+
VersionRange::allGTE(llvm::VersionTuple(16, 4, 0)));
658+
} else if (target.isWatchOS()) {
659+
return AvailabilityContext(
660+
VersionRange::allGTE(llvm::VersionTuple(9, 4, 0)));
661+
} else {
662+
return AvailabilityContext::alwaysAvailable();
663+
}
664+
}
665+
666+
AvailabilityContext ASTContext::getSwift59Availability() {
667+
// TODO: Update Availability impl when Swift 5.9 is released
668+
return getSwiftFutureAvailability();
669+
}
670+
645671
AvailabilityContext ASTContext::getSwiftFutureAvailability() {
646672
auto target = LangOpts.Target;
647673

@@ -671,6 +697,8 @@ ASTContext::getSwift5PlusAvailability(llvm::VersionTuple swiftVersion) {
671697
case 5: return getSwift55Availability();
672698
case 6: return getSwift56Availability();
673699
case 7: return getSwift57Availability();
700+
case 8: return getSwift58Availability();
701+
case 9: return getSwift59Availability();
674702
default: break;
675703
}
676704
}

lib/SILOptimizer/Mandatory/LowerHopToActor.cpp

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#include "swift/SIL/SILFunction.h"
1616
#include "swift/SIL/Dominance.h"
1717
#include "swift/SILOptimizer/Analysis/DominanceAnalysis.h"
18+
#include "swift/SILOptimizer/Utils/SILOptFunctionBuilder.h"
1819
#include "swift/SILOptimizer/PassManager/Transforms.h"
1920
#include "llvm/ADT/ScopedHashTable.h"
2021

@@ -49,19 +50,26 @@ namespace {
4950
class LowerHopToActor {
5051
SILFunction *F;
5152
DominanceInfo *Dominance;
53+
SILOptFunctionBuilder &functionBuilder;
5254

5355
/// A map from an actor value to the executor we've derived for it.
5456
llvm::ScopedHashTable<SILValue, SILValue> ExecutorForActor;
5557

5658
bool processHop(HopToExecutorInst *hop);
5759
bool processExtract(ExtractExecutorInst *extract);
5860

59-
SILValue emitGetExecutor(SILBuilderWithScope &B, SILLocation loc,
61+
SILValue emitGetExecutor(SILBuilderWithScope &B,
62+
SILLocation loc,
6063
SILValue actor, bool makeOptional);
6164

6265
public:
63-
LowerHopToActor(SILFunction *f, DominanceInfo *dominance)
64-
: F(f), Dominance(dominance) { }
66+
LowerHopToActor(SILFunction *f,
67+
SILOptFunctionBuilder &FunctionBuilder,
68+
DominanceInfo *dominance)
69+
: F(f),
70+
Dominance(dominance),
71+
functionBuilder(FunctionBuilder)
72+
{ }
6573

6674
/// The entry point to the transformation.
6775
bool run();
@@ -154,28 +162,6 @@ static AccessorDecl *getUnownedExecutorGetter(ASTContext &ctx,
154162
return nullptr;
155163
}
156164

157-
static AccessorDecl *getUnwrapLocalUnownedExecutorGetter(ASTContext &ctx,
158-
ProtocolDecl *actorProtocol) {
159-
for (auto member: actorProtocol->getAllMembers()) { // FIXME: remove this, just go to the extension
160-
if (auto var = dyn_cast<VarDecl>(member)) {
161-
if (var->getName() == ctx.Id__unwrapLocalUnownedExecutor)
162-
return var->getAccessor(AccessorKind::Get);
163-
}
164-
}
165-
166-
for (auto extension: actorProtocol->getExtensions()) {
167-
for (auto member: extension->getAllMembers()) {
168-
if (auto var = dyn_cast<VarDecl>(member)) {
169-
if (var->getName() == ctx.Id__unwrapLocalUnownedExecutor) {
170-
return var->getAccessor(AccessorKind::Get);
171-
}
172-
}
173-
}
174-
}
175-
176-
return nullptr;
177-
}
178-
179165
SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
180166
SILLocation loc, SILValue actor,
181167
bool makeOptional) {
@@ -199,6 +185,9 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
199185
// If the actor type is a default actor, go ahead and devirtualize here.
200186
auto module = F->getModule().getSwiftModule();
201187
SILValue unmarkedExecutor;
188+
189+
// Determine if the actor is a "default actor" in which case we'll build a default
190+
// actor executor ref inline, rather than calling out to the user-provided executor function.
202191
if (isDefaultActorType(actorType, module, F->getResilienceExpansion())) {
203192
auto builtinName = ctx.getIdentifier(
204193
getBuiltinName(BuiltinValueKind::BuildDefaultActorExecutorRef));
@@ -212,7 +201,7 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
212201
} else if (actorType->isDistributedActor()) {
213202
auto actorKind = KnownProtocolKind::DistributedActor;
214203
auto actorProtocol = ctx.getProtocol(actorKind);
215-
auto req = getUnwrapLocalUnownedExecutorGetter(ctx, actorProtocol);
204+
auto req = ctx.getGetUnwrapLocalDistributedActorUnownedExecutor();
216205
assert(req && "Distributed library broken");
217206
SILDeclRef fn(req, SILDeclRef::Kind::Func);
218207

@@ -222,12 +211,19 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
222211

223212
auto subs = SubstitutionMap::get(req->getGenericSignature(),
224213
{actorType}, {actorConf});
225-
auto fnType = F->getModule().Types.getConstantFunctionType(*F, fn);
226214

227-
auto witness =
228-
B.createWitnessMethod(loc, actorType, actorConf, fn,
229-
SILType::getPrimitiveObjectType(fnType));
230-
auto witnessCall = B.createApply(loc, witness, subs, {actor});
215+
// Find the unwrap function
216+
FuncDecl *funcDecl = ctx.getGetUnwrapLocalDistributedActorUnownedExecutor();
217+
assert(funcDecl);
218+
auto funcDeclRef = SILDeclRef(funcDecl, SILDeclRef::Kind::Func);
219+
220+
SILFunction *unwrapExecutorFun =
221+
functionBuilder.getOrCreateFunction(
222+
loc, funcDeclRef, ForDefinition_t::NotForDefinition);
223+
assert(unwrapExecutorFun && "no sil function!");
224+
auto funcRef =
225+
B.createFunctionRef(loc, unwrapExecutorFun);
226+
auto witnessCall = B.createApply(loc, funcRef, subs, {actor});
231227

232228
// The protocol requirement returns an Optional<UnownedSerialExecutor>;
233229
// extract the Builtin.Executor from it.
@@ -286,7 +282,8 @@ class LowerHopToActorPass : public SILFunctionTransform {
286282
void run() override {
287283
auto fn = getFunction();
288284
auto domTree = getAnalysis<DominanceAnalysis>()->get(fn);
289-
LowerHopToActor pass(getFunction(), domTree);
285+
auto functionBuilder = SILOptFunctionBuilder(*this);
286+
LowerHopToActor pass(getFunction(), functionBuilder, domTree);
290287
if (pass.run())
291288
invalidateAnalysis(SILAnalysis::InvalidationKind::Instructions);
292289
}

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,27 @@ bool IsDefaultActorRequest::evaluate(
182182
if (!classDecl->isActor())
183183
return false;
184184

185+
// Distributed actors were not able to have custom executors until Swift 5.9,
186+
// so in order to avoid wrongly treating a resilient distributed actor from another
187+
// module as not-default we need to handle this case explicitly.
188+
if (classDecl->isDistributedActor()) {
189+
ASTContext &ctx = classDecl->getASTContext();
190+
auto customExecutorAvailability =
191+
ctx.getConcurrencyDistributedActorWithCustomExecutorAvailability();
192+
193+
auto actorAvailability = TypeChecker::overApproximateAvailabilityAtLocation(
194+
classDecl->getStartLoc(),
195+
classDecl);
196+
197+
if (!actorAvailability.isContainedIn(customExecutorAvailability)) {
198+
// Any 'distributed actor' declared with availability lower than the
199+
// introduction of custom executors for distributed actors, must be treated as default actor,
200+
// even if it were to declared the unowned executor property, as older compilers
201+
// do not have the the logic to handle that case.
202+
return true;
203+
}
204+
}
205+
185206
// If the class is resilient from the perspective of the module
186207
// module, it's not a default actor.
187208
if (classDecl->isForeign() || classDecl->isResilient(M, expansion))

stdlib/public/Distributed/DistributedActor.swift

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -278,19 +278,15 @@ public protocol DistributedActor: AnyActor, Identifiable, Hashable
278278
/// - Parameter system: `system` which should be used to resolve the `identity`, and be associated with the returned actor
279279
static func resolve(id: ID, using system: ActorSystem) throws -> Self
280280

281-
// FIXME: figure out how to remove this so LowerHopToActor can call the extension method directly on the protocol
282-
@available(SwiftStdlib 5.9, *)
283-
var _unwrapLocalUnownedExecutor: UnownedSerialExecutor { get }
284281
}
285282

286-
287283
@available(SwiftStdlib 5.9, *)
288-
extension DistributedActor {
289-
290-
@available(SwiftStdlib 5.9, *)
291-
public var _unwrapLocalUnownedExecutor: UnownedSerialExecutor {
292-
self.localUnownedExecutor!
284+
public func _getUnwrapLocalDistributedActorUnownedExecutor(_ actor: some DistributedActor) -> UnownedSerialExecutor {
285+
guard let executor = actor.localUnownedExecutor else {
286+
fatalError("Expected distributed actor executor to be not nil!")
293287
}
288+
289+
return executor
294290
}
295291

296292
// ==== Hashable conformance ---------------------------------------------------

test/Distributed/Runtime/distributed_actor_assume_executor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ func check(actor: MainDistributedFriend) {
3939
checkAssumeMainActor(actor: actor)
4040
}
4141

42+
@available(SwiftStdlib 5.9, *)
4243
distributed actor MainDistributedFriend {
4344
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
4445
print("get unowned executor")
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/../Inputs/FakeDistributedActorSystems.swift
3+
// RUN: %target-build-swift -Xfrontend -disable-availability-checking -parse-as-library -I %t %s %S/../Inputs/FakeDistributedActorSystems.swift -o %t/a.out
4+
// RUN: %target-codesign %t/a.out
5+
// RUN: %target-run %t/a.out
6+
7+
// REQUIRES: executable_test
8+
// REQUIRES: concurrency
9+
// REQUIRES: distributed
10+
// REQUIRES: concurrency_runtime
11+
// UNSUPPORTED: back_deployment_runtime
12+
13+
// UNSUPPORTED: back_deploy_concurrency
14+
// UNSUPPORTED: use_os_stdlib
15+
// UNSUPPORTED: freestanding
16+
17+
import StdlibUnittest
18+
import Distributed
19+
import FakeDistributedActorSystems
20+
21+
@available(SwiftStdlib 5.7, *)
22+
typealias DefaultDistributedActorSystem = LocalTestingDistributedActorSystem
23+
24+
@available(SwiftStdlib 5.7, *)
25+
distributed actor FiveSevenActor_NothingExecutor {
26+
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
27+
print("get unowned executor")
28+
return MainActor.sharedUnownedExecutor
29+
}
30+
31+
distributed func test(x: Int) async throws {
32+
print("executed: \(#function)")
33+
defer {
34+
print("done executed: \(#function)")
35+
}
36+
assumeOnMainActorExecutor {
37+
// ignore
38+
}
39+
}
40+
}
41+
42+
@available(SwiftStdlib 5.9, *)
43+
distributed actor FiveNineActor_NothingExecutor {
44+
// @available(SwiftStdlib 5.9, *) // because of `localUnownedExecutor`
45+
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
46+
print("get unowned executor")
47+
return MainActor.sharedUnownedExecutor
48+
}
49+
50+
distributed func test(x: Int) async throws {
51+
print("executed: \(#function)")
52+
defer {
53+
print("done executed: \(#function)")
54+
}
55+
assumeOnMainActorExecutor {
56+
// ignore
57+
}
58+
}
59+
}
60+
61+
@available(SwiftStdlib 5.7, *)
62+
distributed actor FiveSevenActor_FiveNineExecutor {
63+
@available(SwiftStdlib 5.9, *)
64+
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
65+
print("get unowned executor")
66+
return MainActor.sharedUnownedExecutor
67+
}
68+
69+
distributed func test(x: Int) async throws {
70+
print("executed: \(#function)")
71+
defer {
72+
print("done executed: \(#function)")
73+
}
74+
assumeOnMainActorExecutor {
75+
// ignore
76+
}
77+
}
78+
}
79+
80+
@main struct Main {
81+
static func main() async {
82+
if #available(SwiftStdlib 5.9, *) {
83+
let tests = TestSuite("DistributedActorExecutorAvailability")
84+
85+
let system = LocalTestingDistributedActorSystem()
86+
87+
#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
88+
tests.test("5.7 actor, no availability executor property => no custom executor") {
89+
expectCrashLater(withMessage: "Fatal error: Incorrect actor executor assumption; Expected 'MainActor' executor.")
90+
try! await FiveSevenActor_NothingExecutor(actorSystem: system).test(x: 42)
91+
}
92+
93+
tests.test("5.9 actor, no availability executor property => custom executor") {
94+
try! await FiveNineActor_NothingExecutor(actorSystem: system).test(x: 42)
95+
}
96+
97+
tests.test("5.7 actor, 5.9 executor property => no custom executor") {
98+
expectCrashLater(withMessage: "Fatal error: Incorrect actor executor assumption; Expected 'MainActor' executor.")
99+
try! await FiveSevenActor_FiveNineExecutor(actorSystem: system).test(x: 42)
100+
}
101+
#else
102+
// On non-apple platforms the SDK comes with the toolchains,
103+
// so the feature works because we're executing in a 5.9 context already,
104+
// which otherwise could not have been compiled
105+
tests.test("non apple platform: 5.7 actor, no availability executor property => no custom executor") {
106+
try! await FiveSevenActor_NothingExecutor(actorSystem: system).test(x: 42)
107+
}
108+
109+
tests.test("non apple platform: 5.9 actor, no availability executor property => custom executor") {
110+
try! await FiveNineActor_NothingExecutor(actorSystem: system).test(x: 42)
111+
}
112+
113+
tests.test("non apple platform: 5.7 actor, 5.9 executor property => no custom executor") {
114+
try! await FiveSevenActor_FiveNineExecutor(actorSystem: system).test(x: 42)
115+
}
116+
#endif
117+
118+
await runAllTestsAsync()
119+
}
120+
}
121+
}

0 commit comments

Comments
 (0)