Skip to content

[6.0🍒][Concurrency] Improvements to the concurrency model for stored properties of globally-isolated Sendable value types and globally-isolated function types. #73146

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
4 changes: 1 addition & 3 deletions include/swift/AST/Types.h
Original file line number Diff line number Diff line change
Expand Up @@ -3634,9 +3634,7 @@ class AnyFunctionType : public TypeBase {
return getExtInfo().isNoEscape();
}

bool isSendable() const {
return getExtInfo().isSendable();
}
bool isSendable() const;

bool isAsync() const { return getExtInfo().isAsync(); }

Expand Down
3 changes: 3 additions & 0 deletions include/swift/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,9 @@ EXPERIMENTAL_FEATURE(CImplementation, true)
// Enable the stdlib @DebugDescription macro.
EXPERIMENTAL_FEATURE(DebugDescriptionMacro, true)

// Enable usability improvements for global-actor-isolated types.
EXPERIMENTAL_FEATURE(GlobalActorIsolatedTypesUsability, false)

#undef EXPERIMENTAL_FEATURE_EXCLUDED_FROM_MODULE_INTERFACE
#undef EXPERIMENTAL_FEATURE
#undef UPCOMING_FEATURE
Expand Down
4 changes: 4 additions & 0 deletions lib/AST/FeatureSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -702,6 +702,10 @@ UNINTERESTING_FEATURE(CImplementation)

UNINTERESTING_FEATURE(DebugDescriptionMacro)

static bool usesFeatureGlobalActorIsolatedTypesUsability(Decl *decl) {
return false;
}

// ----------------------------------------------------------------------------
// MARK: - FeatureSet
// ----------------------------------------------------------------------------
Expand Down
9 changes: 9 additions & 0 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3895,6 +3895,15 @@ Type AnyFunctionType::getThrownError() const {
}
}

bool AnyFunctionType::isSendable() const {
auto &ctx = getASTContext();
if (ctx.LangOpts.hasFeature(Feature::GlobalActorIsolatedTypesUsability)) {
// Global-actor-isolated function types are implicitly Sendable.
return getExtInfo().isSendable() || getIsolation().isGlobalActor();
}
return getExtInfo().isSendable();
}

Type AnyFunctionType::getGlobalActor() const {
switch (getKind()) {
case TypeKind::Function:
Expand Down
36 changes: 28 additions & 8 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -503,9 +503,26 @@ static bool varIsSafeAcrossActors(const ModuleDecl *fromModule,
VarDecl *var,
const ActorIsolation &varIsolation,
ActorReferenceResult::Options &options) {
// must be immutable
if (!var->isLet())

bool accessWithinModule =
(fromModule == var->getDeclContext()->getParentModule());

if (!var->isLet()) {
ASTContext &ctx = var->getASTContext();
if (ctx.LangOpts.hasFeature(Feature::GlobalActorIsolatedTypesUsability)) {
// A mutable storage of a value type accessed from within the module is
// okay.
if (dyn_cast_or_null<StructDecl>(var->getDeclContext()->getAsDecl()) &&
!var->isStatic() &&
var->hasStorage() &&
var->getTypeInContext()->isSendableType() &&
accessWithinModule) {
return true;
}
}
// Otherwise, must be immutable.
return false;
}

switch (varIsolation) {
case ActorIsolation::Nonisolated:
Expand Down Expand Up @@ -538,9 +555,6 @@ static bool varIsSafeAcrossActors(const ModuleDecl *fromModule,
return false;
}

bool accessWithinModule =
(fromModule == var->getDeclContext()->getParentModule());

// If the type is not 'Sendable', it's unsafe
if (!var->getTypeInContext()->isSendableType()) {
// Compiler versions <= 5.10 treated this variable as nonisolated,
Expand Down Expand Up @@ -4181,15 +4195,21 @@ bool ActorIsolationChecker::mayExecuteConcurrentlyWith(
if (useIsolation == defIsolation)
return false;

auto &ctx = useContext->getASTContext();
bool regionIsolationEnabled =
ctx.LangOpts.hasFeature(Feature::RegionBasedIsolation);

// Globally-isolated closures may never be executed concurrently.
if (ctx.LangOpts.hasFeature(Feature::GlobalActorIsolatedTypesUsability) &&
regionIsolationEnabled && useIsolation.isGlobalActor())
return false;

// If the local function is not Sendable, its isolation differs
// from that of the context, and both contexts are actor isolated,
// then capturing non-Sendable values allows the closure to stash
// those values into actor isolated state. The original context
// may also stash those values into isolated state, enabling concurrent
// access later on.
auto &ctx = useContext->getASTContext();
bool regionIsolationEnabled =
ctx.LangOpts.hasFeature(Feature::RegionBasedIsolation);
isolatedStateMayEscape =
(!regionIsolationEnabled &&
useIsolation.isActorIsolated() && defIsolation.isActorIsolated());
Expand Down
8 changes: 4 additions & 4 deletions test/Concurrency/actor_isolation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

// RUN: %target-swift-frontend -emit-module -emit-module-path %t/OtherActors.swiftmodule -module-name OtherActors %S/Inputs/OtherActors.swift -disable-availability-checking

// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify %s
// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify -enable-upcoming-feature RegionBasedIsolation %s
// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify -enable-experimental-feature GlobalActorIsolatedTypesUsability %s
// RUN: %target-swift-frontend -I %t -disable-availability-checking -strict-concurrency=complete -parse-as-library -emit-sil -o /dev/null -verify -enable-upcoming-feature RegionBasedIsolation -enable-experimental-feature GlobalActorIsolatedTypesUsability %s

// REQUIRES: concurrency
// REQUIRES: asserts
Expand Down Expand Up @@ -156,7 +156,7 @@ func checkIsolationValueType(_ formance: InferredFromConformance,
_ anno: NoGlobalActorValueType) async {
// these still do need an await in Swift 5
_ = await ext.point // expected-warning {{non-sendable type 'Point' in implicitly asynchronous access to main actor-isolated property 'point' cannot cross actor boundary}}
_ = await formance.counter
_ = formance.counter
_ = await anno.point // expected-warning {{non-sendable type 'Point' in implicitly asynchronous access to global actor 'SomeGlobalActor'-isolated property 'point' cannot cross actor boundary}}
// expected-warning@-1 {{non-sendable type 'NoGlobalActorValueType' passed in implicitly asynchronous call to global actor 'SomeGlobalActor'-isolated property 'point' cannot cross actor boundary}}
_ = anno.counter // expected-warning {{non-sendable type 'NoGlobalActorValueType' passed in call to main actor-isolated property 'counter' cannot cross actor boundary}}
Expand Down Expand Up @@ -559,7 +559,7 @@ extension MyActor {

func testBadImplicitGlobalActorClosureCall() async {
{ @MainActor in }() // expected-error{{expression is 'async' but is not marked with 'await'}}
// expected-note@-1{{calls function of type '@MainActor () -> ()' from outside of its actor context are implicitly asynchronous}}
// expected-note@-1{{calls function of type '@MainActor @Sendable () -> ()' from outside of its actor context are implicitly asynchronous}}
}


Expand Down
12 changes: 6 additions & 6 deletions test/Concurrency/actor_isolation_swift6.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// RUN: %target-swift-frontend -disable-availability-checking -swift-version 6 -emit-sil -o /dev/null -verify %s
// RUN: %target-swift-frontend -disable-availability-checking -swift-version 6 -emit-sil -o /dev/null -verify -enable-experimental-feature GlobalActorIsolatedTypesUsability %s

// REQUIRES: concurrency
// REQUIRES: asserts

final class ImmutablePoint: Sendable {
let x : Int = 0
Expand Down Expand Up @@ -55,13 +56,12 @@ func checkIsolationValueType(_ formance: InferredFromConformance,
_ = await ext.point // expected-warning {{no 'async' operations occur within 'await' expression}}
_ = await formance.counter // expected-warning {{no 'async' operations occur within 'await' expression}}
_ = await anno.counter // expected-warning {{no 'async' operations occur within 'await' expression}}

// We could extend the 'nonisolated within the module' rule to vars
// value types types if the property type is 'Sendable'.
// this does not need an await, since the property is 'Sendable' and of a
// value type
_ = anno.point
// expected-error@-1 {{expression is 'async' but is not marked with 'await'}}
// expected-note@-2 {{property access is 'async'}}
_ = await anno.point
// expected-warning@-1 {{no 'async' operations occur within 'await' expression}}

// these do need await, regardless of reference or value type
_ = await (formance as any MainCounter).counter
Expand Down
39 changes: 27 additions & 12 deletions test/Concurrency/derived_conformances_nonisolated.swift
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// RUN: %target-swift-frontend -disable-availability-checking -strict-concurrency=complete -parse-as-library %s -emit-sil -o /dev/null -verify
// RUN: %target-swift-frontend -disable-availability-checking -strict-concurrency=complete -parse-as-library %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation
// RUN: %target-swift-frontend -disable-availability-checking -strict-concurrency=complete -parse-as-library %s -emit-sil -o /dev/null -verify -enable-experimental-feature GlobalActorIsolatedTypesUsability
// RUN: %target-swift-frontend -disable-availability-checking -strict-concurrency=complete -parse-as-library %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation -enable-experimental-feature GlobalActorIsolatedTypesUsability

// REQUIRES: concurrency
// REQUIRES: asserts
Expand All @@ -10,14 +10,32 @@ struct X1: Equatable, Hashable, Codable {
let y: String
}

// expected-error@+5 3{{main actor-isolated property 'y' can not be referenced from a non-isolated context}}
// expected-note@+4{{in static method '==' for derived conformance to 'Equatable'}}
// expected-error@+3{{main actor-isolated property 'y' can not be referenced from a non-isolated context}}
// expected-note@+2{{in static method '==' for derived conformance to 'Equatable'}}
// okay
@MainActor
struct X2: Equatable, Hashable, Codable {
let x: Int
var y: String // expected-note 4 {{property declared here}}
var y: String
}

class NonSendable {
let x: Int

init(x: Int) {
self.x = x
}
}

extension NonSendable: Equatable {
static func == (lhs: NonSendable, rhs: NonSendable) -> Bool {
return lhs.x == rhs.x
}
}

// expected-warning@+3 2{{main actor-isolated property 'x' can not be referenced from a non-isolated context}}
// expected-note@+2 2{{in static method '==' for derived conformance to 'Equatable'}}
@MainActor
struct X2NonSendable: Equatable {
let x: NonSendable // expected-note 2 {{property declared here}}
}

@MainActor
Expand All @@ -26,12 +44,9 @@ enum X3: Hashable, Comparable, Codable {
case b(Int)
}

// expected-warning@+5{{main actor-isolated property 'y' can not be referenced from a non-isolated context}}
// expected-note@+4{{in static method '==' for derived conformance to 'Equatable'}}
// expected-warning@+3{{main actor-isolated property 'y' can not be referenced from a non-isolated context}}
// expected-note@+2{{in static method '==' for derived conformance to 'Equatable'}}
// okay
@preconcurrency @MainActor
struct X4: Equatable {
let x: Int
var y: String // expected-note 2 {{property declared here}}
var y: String
}
35 changes: 17 additions & 18 deletions test/Concurrency/global_actor_inference.swift
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
// RUN: %empty-directory(%t)

// RUN: %target-swift-frontend -emit-module -emit-module-path %t/other_global_actor_inference.swiftmodule -module-name other_global_actor_inference -strict-concurrency=complete %S/Inputs/other_global_actor_inference.swift
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -verify-additional-prefix minimal-targeted-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=targeted -verify-additional-prefix minimal-targeted-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -verify-additional-prefix complete-tns-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation -verify-additional-prefix complete-tns-
// RUN: %target-swift-frontend -emit-module -emit-module-path %t/other_global_actor_inference.swiftmodule -module-name other_global_actor_inference -strict-concurrency=complete %S/Inputs/other_global_actor_inference.swift -enable-experimental-feature GlobalActorIsolatedTypesUsability
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -verify-additional-prefix minimal-targeted- -enable-experimental-feature GlobalActorIsolatedTypesUsability
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=targeted -verify-additional-prefix minimal-targeted- -enable-experimental-feature GlobalActorIsolatedTypesUsability
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -verify-additional-prefix complete-tns- -enable-experimental-feature GlobalActorIsolatedTypesUsability
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-upcoming-feature RegionBasedIsolation -verify-additional-prefix complete-tns- -enable-experimental-feature GlobalActorIsolatedTypesUsability

// REQUIRES: concurrency
// REQUIRES: asserts
Expand Down Expand Up @@ -285,14 +285,14 @@ func barSync() {

@OtherGlobalActor
struct Observed {
var thing: Int = 0 { // expected-note {{property declared here}}
var thing: Int = 0 {
didSet {}
willSet {}
}
}

func checkObserved(_ o: Observed) { // expected-note {{add '@OtherGlobalActor' to make global function 'checkObserved' part of global actor 'OtherGlobalActor'}}
_ = o.thing // expected-error {{global actor 'OtherGlobalActor'-isolated property 'thing' can not be referenced from a non-isolated context}}
func checkObserved(_ o: Observed) {
_ = o.thing // okay
}

// ----------------------------------------------------------------------
Expand Down Expand Up @@ -376,13 +376,13 @@ actor WrapperActor<Wrapped: Sendable> {

struct HasWrapperOnActor {
@WrapperOnActor var synced: Int = 0
// expected-note@-1 3{{property declared here}}
// expected-note@-1 2{{property declared here}}

// expected-note@+1 3{{to make instance method 'testErrors()'}}
// expected-note@+1 2{{to make instance method 'testErrors()'}}
func testErrors() {
_ = synced // expected-error{{main actor-isolated property 'synced' can not be referenced from a non-isolated context}}
_ = $synced // expected-error{{global actor 'SomeGlobalActor'-isolated property '$synced' can not be referenced from a non-isolated context}}
_ = _synced // expected-error{{global actor 'OtherGlobalActor'-isolated property '_synced' can not be referenced from a non-isolated context}}
_ = _synced // okay
}

@MainActor mutating func testOnMain() {
Expand Down Expand Up @@ -566,22 +566,21 @@ struct HasWrapperOnUnsafeActor {
// expected-complete-tns-warning@-2 {{default initializer for 'HasWrapperOnUnsafeActor' cannot be both nonisolated and global actor 'OtherGlobalActor'-isolated; this is an error in the Swift 6 language mode}}

@WrapperOnUnsafeActor var synced: Int = 0 // expected-complete-tns-note 2 {{initializer for property '_synced' is global actor 'OtherGlobalActor'-isolated}}
// expected-note @-1 3{{property declared here}}
// expected-complete-tns-note @-2 3{{property declared here}}
// expected-note @-1 2{{property declared here}}
// expected-complete-tns-note @-2 2{{property declared here}}

func testUnsafeOkay() {
// expected-complete-tns-note @-1 {{add '@OtherGlobalActor' to make instance method 'testUnsafeOkay()' part of global actor 'OtherGlobalActor'}}
// expected-complete-tns-note @-2 {{add '@SomeGlobalActor' to make instance method 'testUnsafeOkay()' part of global actor 'SomeGlobalActor'}}
// expected-complete-tns-note @-3 {{add '@MainActor' to make instance method 'testUnsafeOkay()' part of global actor 'MainActor'}}
// expected-complete-tns-note @-1 {{add '@SomeGlobalActor' to make instance method 'testUnsafeOkay()' part of global actor 'SomeGlobalActor'}}
// expected-complete-tns-note @-2 {{add '@MainActor' to make instance method 'testUnsafeOkay()' part of global actor 'MainActor'}}
_ = synced // expected-complete-tns-warning {{main actor-isolated property 'synced' can not be referenced from a non-isolated context}}
_ = $synced // expected-complete-tns-warning {{global actor 'SomeGlobalActor'-isolated property '$synced' can not be referenced from a non-isolated context}}
_ = _synced // expected-complete-tns-warning {{global actor 'OtherGlobalActor'-isolated property '_synced' can not be referenced from a non-isolated context}}
_ = _synced // okay
}

nonisolated func testErrors() {
_ = synced // expected-warning{{main actor-isolated property 'synced' can not be referenced from a non-isolated context}}
_ = $synced // expected-warning{{global actor 'SomeGlobalActor'-isolated property '$synced' can not be referenced from a non-isolated context}}
_ = _synced // expected-warning{{global actor 'OtherGlobalActor'-isolated property '_synced' can not be referenced from a non-isolated context}}
_ = _synced // okay
}

@MainActor mutating func testOnMain() {
Expand Down
21 changes: 21 additions & 0 deletions test/Concurrency/global_actor_sendable_fn_type_inference.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %target-typecheck-verify-swift -strict-concurrency=complete -disable-availability-checking -enable-upcoming-feature RegionBasedIsolation -enable-experimental-feature GlobalActorIsolatedTypesUsability

// REQUIRES: concurrency
// REQUIRES: asserts

func inferSendableFunctionType() {
let closure: @MainActor () -> Void = {}

Task {
await closure() // okay
}
}

class NonSendable {}

func allowNonSendableCaptures() {
let nonSendable = NonSendable()
let _: @MainActor () -> Void = {
let _ = nonSendable // okay
}
}
8 changes: 3 additions & 5 deletions test/Concurrency/sendable_keypaths.swift
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// RUN: %target-typecheck-verify-swift -enable-upcoming-feature InferSendableFromCaptures -strict-concurrency=complete
// RUN: %target-typecheck-verify-swift -enable-upcoming-feature InferSendableFromCaptures -strict-concurrency=complete -enable-experimental-feature GlobalActorIsolatedTypesUsability

// REQUIRES: concurrency
// REQUIRES: asserts
Expand Down Expand Up @@ -147,8 +147,7 @@ func testGlobalActorIsolatedReferences() {
subscript(v: Int) -> Bool { false }
}

let dataKP = \Isolated.data
// expected-warning@-1 {{cannot form key path to main actor-isolated property 'data'; this is an error in the Swift 6 language mode}}
let dataKP = \Isolated.data // Ok
let subscriptKP = \Isolated.[42]
// expected-warning@-1 {{cannot form key path to main actor-isolated subscript 'subscript(_:)'; this is an error in the Swift 6 language mode}}

Expand All @@ -158,8 +157,7 @@ func testGlobalActorIsolatedReferences() {
// expected-warning@-1 {{type 'KeyPath<Isolated, Bool>' does not conform to the 'Sendable' protocol}}

func testNonIsolated() {
_ = \Isolated.data
// expected-warning@-1 {{cannot form key path to main actor-isolated property 'data'; this is an error in the Swift 6 language mode}}
_ = \Isolated.data // Ok
}

@MainActor func testIsolated() {
Expand Down