Skip to content

[Executors] Ensure we treat DA older than 5.9 always as DefaultActor #64800

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,9 @@ class ASTContext final {
/// Get the back-deployed availability for concurrency.
AvailabilityContext getBackDeployedConcurrencyAvailability();

/// The the availability since when distributed actors are able to have custom executors.
AvailabilityContext getConcurrencyDistributedActorWithCustomExecutorAvailability();

/// Get the runtime availability of support for differentiation.
AvailabilityContext getDifferentiationAvailability();

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

/// Get the runtime availability of features introduced in the Swift 5.8
/// compiler for the target platform.
AvailabilityContext getSwift58Availability();

/// Get the runtime availability of features introduced in the Swift 5.9
/// compiler for the target platform.
AvailabilityContext getSwift59Availability();

// Note: Update this function if you add a new getSwiftXYAvailability above.
/// Get the runtime availability for a particular version of Swift (5.0+).
AvailabilityContext
Expand Down
5 changes: 5 additions & 0 deletions include/swift/AST/Availability.h
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,11 @@ class AvailabilityContext {
bool isAvailableAsSPI() const {
return SPI && *SPI;
}

/// Returns a representation of this range as a string for debugging purposes.
std::string getAsString() const {
return "AvailabilityContext(" + OSVersion.getAsString() + (isAvailableAsSPI() ? ", spi" : "") + ")";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor: run git-clang-format

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll follow up separately if that's fine? That'd just be a linebreak here AFAICS.

-    return "AvailabilityContext(" + OSVersion.getAsString() + (isAvailableAsSPI() ? ", spi" : "") + ")";
+    return "AvailabilityContext(" + OSVersion.getAsString() +
+           (isAvailableAsSPI() ? ", spi" : "") + ")";

// The problem with clang-format is that it is not being applied consistently in this project, so applying it adds all kinds of noise and "this commit does not look like the rest of the file or method at all now..."

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow up is fine with me! I find the clang-format output is pretty consistent with the style of most of the files I have worked in in the project and it is documented in https://github.com/apple/swift/blob/main/docs/HowToGuides/FirstPullRequest.md as the primary way of enforcing the project code style. In particular, long lines stick out a lot - I think the majority of the codebase adheres to the 80 char limit

Copy link
Contributor Author

@ktoso ktoso Apr 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll follow up about my specific changes, but the reality is that files are not well formatted:

 22 files changed, 65 insertions(+), 65 deletions(-)

if running clang-format on files this PR touched, out of that my changes would be...

 5 files changed, 22 insertions(+), 15 deletions(-)

but yeah, I'll add a git hook to git-clang-format my commits and I'll do the promised follow up now as well: #64886

}
};


Expand Down
28 changes: 28 additions & 0 deletions lib/AST/Availability.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -489,6 +489,10 @@ AvailabilityContext ASTContext::getBackDeployedConcurrencyAvailability() {
return getSwift51Availability();
}

AvailabilityContext ASTContext::getConcurrencyDistributedActorWithCustomExecutorAvailability() {
return getSwift59Availability();
}

AvailabilityContext ASTContext::getDifferentiationAvailability() {
return getSwiftFutureAvailability();
}
Expand Down Expand Up @@ -642,6 +646,28 @@ AvailabilityContext ASTContext::getSwift57Availability() {
}
}

AvailabilityContext ASTContext::getSwift58Availability() {
auto target = LangOpts.Target;

if (target.isMacOSX()) {
return AvailabilityContext(
VersionRange::allGTE(llvm::VersionTuple(13, 3, 0)));
} else if (target.isiOS()) {
return AvailabilityContext(
VersionRange::allGTE(llvm::VersionTuple(16, 4, 0)));
} else if (target.isWatchOS()) {
return AvailabilityContext(
VersionRange::allGTE(llvm::VersionTuple(9, 4, 0)));
} else {
return AvailabilityContext::alwaysAvailable();
}
}

AvailabilityContext ASTContext::getSwift59Availability() {
// TODO: Update Availability impl when Swift 5.9 is released
return getSwiftFutureAvailability();
}

AvailabilityContext ASTContext::getSwiftFutureAvailability() {
auto target = LangOpts.Target;

Expand Down Expand Up @@ -671,6 +697,8 @@ ASTContext::getSwift5PlusAvailability(llvm::VersionTuple swiftVersion) {
case 5: return getSwift55Availability();
case 6: return getSwift56Availability();
case 7: return getSwift57Availability();
case 8: return getSwift58Availability();
case 9: return getSwift59Availability();
default: break;
}
}
Expand Down
3 changes: 3 additions & 0 deletions lib/SILOptimizer/Mandatory/LowerHopToActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,9 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
// If the actor type is a default actor, go ahead and devirtualize here.
auto module = F->getModule().getSwiftModule();
SILValue unmarkedExecutor;

// Determine if the actor is a "default actor" in which case we'll build a default
// actor executor ref inline, rather than calling out to the user-provided executor function.
if (isDefaultActorType(actorType, module, F->getResilienceExpansion())) {
auto builtinName = ctx.getIdentifier(
getBuiltinName(BuiltinValueKind::BuildDefaultActorExecutorRef));
Expand Down
21 changes: 21 additions & 0 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,27 @@ bool IsDefaultActorRequest::evaluate(
if (!classDecl->isActor())
return false;

// Distributed actors were not able to have custom executors until Swift 5.9,
// so in order to avoid wrongly treating a resilient distributed actor from another
// module as not-default we need to handle this case explicitly.
if (classDecl->isDistributedActor()) {
ASTContext &ctx = classDecl->getASTContext();
auto customExecutorAvailability =
ctx.getConcurrencyDistributedActorWithCustomExecutorAvailability();

auto actorAvailability = TypeChecker::overApproximateAvailabilityAtLocation(
classDecl->getStartLoc(),
classDecl);

if (!actorAvailability.isContainedIn(customExecutorAvailability)) {
// Any 'distributed actor' declared with availability lower than the
// introduction of custom executors for distributed actors, must be treated as default actor,
// even if it were to declared the unowned executor property, as older compilers
// do not have the the logic to handle that case.
return true;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the core of the change; we ensure we treat distributed actors which "could not have been non-default" always as default.

}
}

// If the class is resilient from the perspective of the module
// module, it's not a default actor.
if (classDecl->isForeign() || classDecl->isResilient(M, expansion))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func check(actor: MainDistributedFriend) {
checkAssumeMainActor(actor: actor)
}

@available(SwiftStdlib 5.9, *)
distributed actor MainDistributedFriend {
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
print("get unowned executor")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/../Inputs/FakeDistributedActorSystems.swift
// RUN: %target-build-swift -Xfrontend -disable-availability-checking -parse-as-library -I %t %s %S/../Inputs/FakeDistributedActorSystems.swift -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %target-run %t/a.out

// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: distributed
// REQUIRES: concurrency_runtime
// UNSUPPORTED: back_deployment_runtime

// UNSUPPORTED: back_deploy_concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: freestanding

import StdlibUnittest
import Distributed
import FakeDistributedActorSystems

@available(SwiftStdlib 5.7, *)
typealias DefaultDistributedActorSystem = LocalTestingDistributedActorSystem

@available(SwiftStdlib 5.7, *)
distributed actor FiveSevenActor_NothingExecutor {
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
print("get unowned executor")
return MainActor.sharedUnownedExecutor
}

distributed func test(x: Int) async throws {
print("executed: \(#function)")
defer {
print("done executed: \(#function)")
}
assumeOnMainActorExecutor {
// ignore
}
}
}

@available(SwiftStdlib 5.9, *)
distributed actor FiveNineActor_NothingExecutor {
// @available(SwiftStdlib 5.9, *) // because of `localUnownedExecutor`
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
print("get unowned executor")
return MainActor.sharedUnownedExecutor
}

distributed func test(x: Int) async throws {
print("executed: \(#function)")
defer {
print("done executed: \(#function)")
}
assumeOnMainActorExecutor {
// ignore
}
}
}

@available(SwiftStdlib 5.7, *)
distributed actor FiveSevenActor_FiveNineExecutor {
@available(SwiftStdlib 5.9, *)
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
print("get unowned executor")
return MainActor.sharedUnownedExecutor
}

distributed func test(x: Int) async throws {
print("executed: \(#function)")
defer {
print("done executed: \(#function)")
}
assumeOnMainActorExecutor {
// ignore
}
}
}

@main struct Main {
static func main() async {
if #available(SwiftStdlib 5.9, *) {
let tests = TestSuite("DistributedActorExecutorAvailability")

let system = LocalTestingDistributedActorSystem()

#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS)
tests.test("5.7 actor, no availability executor property => no custom executor") {
expectCrashLater(withMessage: "Fatal error: Incorrect actor executor assumption; Expected 'MainActor' executor.")
try! await FiveSevenActor_NothingExecutor(actorSystem: system).test(x: 42)
}

tests.test("5.9 actor, no availability executor property => custom executor") {
try! await FiveNineActor_NothingExecutor(actorSystem: system).test(x: 42)
}

tests.test("5.7 actor, 5.9 executor property => no custom executor") {
expectCrashLater(withMessage: "Fatal error: Incorrect actor executor assumption; Expected 'MainActor' executor.")
try! await FiveSevenActor_FiveNineExecutor(actorSystem: system).test(x: 42)
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This case I was unsure about, but I think this way to handle it makes sense; the entire actor has to be 5.9 to be treated like being able to have a custom executor, not just the property.

#else
// On non-apple platforms the SDK comes with the toolchains,
// so the feature works because we're executing in a 5.9 context already,
// which otherwise could not have been compiled
tests.test("non apple platform: 5.7 actor, no availability executor property => no custom executor") {
try! await FiveSevenActor_NothingExecutor(actorSystem: system).test(x: 42)
}

tests.test("non apple platform: 5.9 actor, no availability executor property => custom executor") {
try! await FiveNineActor_NothingExecutor(actorSystem: system).test(x: 42)
}

tests.test("non apple platform: 5.7 actor, 5.9 executor property => no custom executor") {
try! await FiveSevenActor_FiveNineExecutor(actorSystem: system).test(x: 42)
}
#endif

await runAllTestsAsync()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import FakeDistributedActorSystems

typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem

@available(SwiftStdlib 5.9, *) // because conforming to the protocol... that has this field in 5.9?
distributed actor Worker {
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
print("get unowned executor")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import FakeDistributedActorSystems

typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem

@available(SwiftStdlib 5.9, *)
distributed actor Worker {
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
print("get unowned 'local' executor via ID")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
// RUN: %empty-directory(%t)
// RUN: %target-swift-frontend-emit-module -emit-module-path %t/FakeDistributedActorSystems.swiftmodule -module-name FakeDistributedActorSystems -disable-availability-checking %S/../Inputs/FakeDistributedActorSystems.swift
// RUN: %target-swift-frontend -module-name default_deinit -primary-file %s -emit-sil -verify -disable-availability-checking -I %t | %FileCheck %s --enable-var-scope --dump-input=fail
// RUN: %target-swift-frontend -module-name default_deinit -primary-file %s -emit-sil -verify -disable-availability-checking -I %t | %FileCheck %s --enable-var-scope
// REQUIRES: concurrency
// REQUIRES: distributed

Expand All @@ -13,13 +13,14 @@ typealias DefaultDistributedActorSystem = FakeRoundtripActorSystem

// ==== ----------------------------------------------------------------------------------------------------------------

@available(SwiftStdlib 5.9, *)
distributed actor MyDistActor {
nonisolated var localUnownedExecutor: UnownedSerialExecutor? {
return MainActor.sharedUnownedExecutor
}

// // MyDistActor.init(actorSystem:)
// CHECK: sil hidden @$s14default_deinit11MyDistActorC11actorSystemAC015FakeDistributedE7Systems0h9RoundtripeG0C_tcfc : $@convention(method) (@owned FakeRoundtripActorSystem, @owned MyDistActor) -> @owned MyDistActor
// CHECK: sil hidden{{.*}} @$s14default_deinit11MyDistActorC11actorSystemAC015FakeDistributedE7Systems0h9RoundtripeG0C_tcfc : $@convention(method) (@owned FakeRoundtripActorSystem, @owned MyDistActor) -> @owned MyDistActor
// CHECK-NOT: {{%[0-9]+}} = builtin "initializeDefaultActor"(%1 : $MyDistActor) : $()
// CHECK: [[ACTOR_INSTANCE:%[0-9]+]] = builtin "initializeNonDefaultDistributedActor"(%1 : $MyDistActor) : $()
}
Expand Down
1 change: 1 addition & 0 deletions utils/availability-macros.def
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ SwiftStdlib 5.6:macOS 12.3, iOS 15.4, watchOS 8.5, tvOS 15.4
SwiftStdlib 5.7:macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0
SwiftStdlib 5.8:macOS 13.3, iOS 16.4, watchOS 9.4, tvOS 16.4
SwiftStdlib 5.9:macOS 9999, iOS 9999, watchOS 9999, tvOS 9999
# TODO: Also update ASTContext::getSwift59Availability when 5.9 is released

# Local Variables:
# mode: conf-unix
Expand Down