Skip to content

Commit 7d2d91a

Browse files
authored
Merge pull request #70257 from hborla/isolated-default-values-upcoming-flag
[SE-0411] Promote `IsolatedDefaultValues` from an experimental feature to an upcoming feature.
2 parents ae1fe78 + 97ee101 commit 7d2d91a

14 files changed

+220
-61
lines changed

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,28 @@
55
66
## Swift 5.11
77

8+
* [SE-0411][]:
9+
10+
Default value expressions can now have the same isolation as the enclosing
11+
function or the corresponding stored property:
12+
13+
```swift
14+
@MainActor
15+
func requiresMainActor() -> Int { ... }
16+
17+
class C {
18+
@MainActor
19+
var x: Int = requiresMainActor()
20+
}
21+
22+
@MainActor func defaultArg(value: Int = requiresMainActor()) { ... }
23+
```
24+
25+
For isolated default values of stored properties, the implicit initialization
26+
only happens in the body of an `init` with the same isolation. This closes
27+
an important data-race safety hole where global-actor-isolated default values
28+
could inadvertently run synchronously from outside the actor.
29+
830
* [SE-0413][]:
931

1032
Functions can now specify the type of error that they throw as part of the
@@ -9904,6 +9926,7 @@ using the `.dynamicType` member to retrieve the type of an expression should mig
99049926
[SE-0394]: https://github.com/apple/swift-evolution/blob/main/proposals/0394-swiftpm-expression-macros.md
99059927
[SE-0397]: https://github.com/apple/swift-evolution/blob/main/proposals/0397-freestanding-declaration-macros.md
99069928
[SE-0407]: https://github.com/apple/swift-evolution/blob/main/proposals/0407-member-macro-conformances.md
9929+
[SE-0411]: https://github.com/apple/swift-evolution/blob/main/proposals/0411-isolated-default-values.md
99079930
[SE-0413]: https://github.com/apple/swift-evolution/blob/main/proposals/0413-typed-throws.md
99089931
[#64927]: <https://github.com/apple/swift/issues/64927>
99099932
[#42697]: <https://github.com/apple/swift/issues/42697>

include/swift/Basic/Features.def

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ UPCOMING_FEATURE(DeprecateApplicationMain, 383, 6)
121121
UPCOMING_FEATURE(ImportObjcForwardDeclarations, 384, 6)
122122
UPCOMING_FEATURE(DisableOutwardActorInference, 401, 6)
123123
UPCOMING_FEATURE(InternalImportsByDefault, 409, 6)
124+
UPCOMING_FEATURE(IsolatedDefaultValues, 411, 6)
124125
UPCOMING_FEATURE(GlobalConcurrency, 412, 6)
125126
UPCOMING_FEATURE(FullTypedThrows, 413, 6)
126127

@@ -219,9 +220,6 @@ EXPERIMENTAL_FEATURE(StrictConcurrency, true)
219220
/// Region Based Isolation testing using the TransferNonSendable pass
220221
EXPERIMENTAL_FEATURE(RegionBasedIsolation, false)
221222

222-
/// Allow default values to require isolation at the call-site.
223-
EXPERIMENTAL_FEATURE(IsolatedDefaultValues, false)
224-
225223
/// Enable extended callbacks (with additional parameters) to be used when the
226224
/// "playground transform" is enabled.
227225
EXPERIMENTAL_FEATURE(PlaygroundExtendedCallbacks, true)

lib/Frontend/CompilerInvocation.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,7 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
880880
if (Opts.hasFeature(Feature::CompleteConcurrency)) {
881881
Opts.StrictConcurrencyLevel = StrictConcurrency::Complete;
882882
Opts.enableFeature(Feature::DisableOutwardActorInference);
883+
Opts.enableFeature(Feature::IsolatedDefaultValues);
883884
Opts.enableFeature(Feature::GlobalConcurrency);
884885
}
885886

@@ -1019,6 +1020,11 @@ static bool ParseLangArgs(LangOptions &Opts, ArgList &Args,
10191020
Opts.StrictConcurrencyLevel = StrictConcurrency::Minimal;
10201021
}
10211022

1023+
// StrictConcurrency::Complete enables all data-race safety features.
1024+
if (Opts.StrictConcurrencyLevel == StrictConcurrency::Complete) {
1025+
Opts.enableFeature(Feature::IsolatedDefaultValues);
1026+
}
1027+
10221028
Opts.WarnImplicitOverrides =
10231029
Args.hasArg(OPT_warn_implicit_overrides);
10241030

lib/SILGen/SILGenApply.cpp

Lines changed: 97 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2718,6 +2718,28 @@ class DelayedArgument {
27182718
return LV().Loc;
27192719
}
27202720

2721+
bool isDefaultArg() const {
2722+
return Kind == DefaultArgument;
2723+
}
2724+
2725+
SILLocation getDefaultArgLoc() const {
2726+
assert(isDefaultArg());
2727+
auto storage = Value.get<DefaultArgumentStorage>(Kind);
2728+
return storage.loc;
2729+
}
2730+
2731+
llvm::Optional<ActorIsolation> getIsolation() const {
2732+
if (!isDefaultArg())
2733+
return llvm::None;
2734+
2735+
auto storage = Value.get<DefaultArgumentStorage>(Kind);
2736+
if (!storage.implicitlyAsync)
2737+
return llvm::None;
2738+
2739+
auto callee = storage.defaultArgsOwner.getDecl();
2740+
return getActorIsolation(callee);
2741+
}
2742+
27212743
void emit(SILGenFunction &SGF, SmallVectorImpl<ManagedValue> &args,
27222744
size_t &argIndex) {
27232745
switch (Kind) {
@@ -2943,6 +2965,31 @@ static void emitDelayedArguments(SILGenFunction &SGF,
29432965
MutableArrayRef<SmallVector<ManagedValue, 4>> args) {
29442966
assert(!delayedArgs.empty());
29452967

2968+
// If any of the delayed arguments are isolated default arguments,
2969+
// argument evaluation happens in the following order:
2970+
//
2971+
// 1. Left-to-right evalution of explicit r-value arguments
2972+
// 2. Left-to-right evaluation of formal access arguments
2973+
// 3. Hop to the callee's isolation domain
2974+
// 4. Left-to-right evaluation of default arguments
2975+
2976+
// So, if any delayed arguments are isolated, all default arguments
2977+
// are collected during the first pass over the delayed arguments,
2978+
// and emitted separately after a hop to the callee's isolation domain.
2979+
2980+
llvm::Optional<ActorIsolation> defaultArgIsolation;
2981+
for (auto &arg : delayedArgs) {
2982+
if (auto isolation = arg.getIsolation()) {
2983+
defaultArgIsolation = isolation;
2984+
break;
2985+
}
2986+
}
2987+
2988+
SmallVector<std::tuple<
2989+
/*delayedArgIt*/decltype(delayedArgs)::iterator,
2990+
/*siteArgsIt*/decltype(args)::iterator,
2991+
/*index*/size_t>, 2> isolatedArgs;
2992+
29462993
SmallVector<std::pair<SILValue, SILLocation>, 4> emittedInoutArgs;
29472994
auto delayedNext = delayedArgs.begin();
29482995

@@ -2951,7 +2998,8 @@ static void emitDelayedArguments(SILGenFunction &SGF,
29512998
// wherever there's a delayed argument to insert.
29522999
//
29533000
// Note that this also begins the formal accesses in evaluation order.
2954-
for (auto &siteArgs : args) {
3001+
for (auto argsIt = args.begin(); argsIt != args.end(); ++argsIt) {
3002+
auto &siteArgs = *argsIt;
29553003
// NB: siteArgs.size() may change during iteration
29563004
for (size_t i = 0; i < siteArgs.size(); ) {
29573005
auto &siteArg = siteArgs[i];
@@ -2964,6 +3012,15 @@ static void emitDelayedArguments(SILGenFunction &SGF,
29643012
assert(delayedNext != delayedArgs.end());
29653013
auto &delayedArg = *delayedNext;
29663014

3015+
if (defaultArgIsolation && delayedArg.isDefaultArg()) {
3016+
isolatedArgs.push_back(std::make_tuple(delayedNext, argsIt, i));
3017+
if (++delayedNext == delayedArgs.end()) {
3018+
goto done;
3019+
} else {
3020+
continue;
3021+
}
3022+
}
3023+
29673024
// Emit the delayed argument and replace it in the arguments array.
29683025
delayedArg.emit(SGF, siteArgs, i);
29693026

@@ -2984,6 +3041,45 @@ static void emitDelayedArguments(SILGenFunction &SGF,
29843041

29853042
done:
29863043

3044+
if (defaultArgIsolation) {
3045+
assert(SGF.F.isAsync());
3046+
assert(!isolatedArgs.empty());
3047+
3048+
auto &firstArg = *std::get<0>(isolatedArgs[0]);
3049+
auto loc = firstArg.getDefaultArgLoc();
3050+
3051+
SILValue executor;
3052+
switch (*defaultArgIsolation) {
3053+
case ActorIsolation::GlobalActor:
3054+
case ActorIsolation::GlobalActorUnsafe:
3055+
executor = SGF.emitLoadGlobalActorExecutor(
3056+
defaultArgIsolation->getGlobalActor());
3057+
break;
3058+
3059+
case ActorIsolation::ActorInstance:
3060+
llvm_unreachable("default arg cannot be actor instance isolated");
3061+
3062+
case ActorIsolation::Unspecified:
3063+
case ActorIsolation::Nonisolated:
3064+
case ActorIsolation::NonisolatedUnsafe:
3065+
llvm_unreachable("Not isolated");
3066+
}
3067+
3068+
// Hop to the target isolation domain once to evaluate all
3069+
// default arguments.
3070+
SGF.emitHopToTargetExecutor(loc, executor);
3071+
3072+
size_t argsEmitted = 0;
3073+
for (auto &isolatedArg : isolatedArgs) {
3074+
auto &delayedArg = *std::get<0>(isolatedArg);
3075+
auto &siteArgs = *std::get<1>(isolatedArg);
3076+
auto argIndex = std::get<2>(isolatedArg) + argsEmitted;
3077+
auto origIndex = argIndex;
3078+
delayedArg.emit(SGF, siteArgs, argIndex);
3079+
argsEmitted += (argIndex - origIndex);
3080+
}
3081+
}
3082+
29873083
// Check to see if we have multiple inout arguments which obviously
29883084
// alias. Note that we could do this in a later SILDiagnostics pass
29893085
// as well: this would be stronger (more equivalences exposed) but

lib/SILGen/SILGenExpr.cpp

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2647,24 +2647,8 @@ SILGenFunction::emitApplyOfDefaultArgGenerator(SILLocation loc,
26472647
emitCaptures(loc, generator, CaptureEmission::ImmediateApplication,
26482648
captures);
26492649

2650-
// The default argument might require the callee's isolation. If so,
2651-
// make sure to emit an actor hop.
2652-
//
2653-
// FIXME: Instead of hopping back and forth for each individual isolated
2654-
// default argument, we should emit one hop for all default arguments if
2655-
// any of them are isolated, and immediately enter the function after.
2656-
llvm::Optional<ActorIsolation> implicitActorHopTarget = llvm::None;
2657-
if (implicitlyAsync) {
2658-
auto *param = getParameterAt(defaultArgsOwner.getDecl(), destIndex);
2659-
auto isolation = param->getInitializerIsolation();
2660-
if (isolation.isActorIsolated()) {
2661-
implicitActorHopTarget = isolation;
2662-
}
2663-
}
2664-
26652650
return emitApply(std::move(resultPtr), std::move(argScope), loc, fnRef, subs,
2666-
captures, calleeTypeInfo, ApplyOptions(), C,
2667-
implicitActorHopTarget);
2651+
captures, calleeTypeInfo, ApplyOptions(), C, llvm::None);
26682652
}
26692653

26702654
RValue SILGenFunction::emitApplyOfStoredPropertyInitializer(

lib/Sema/CodeSynthesis.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,8 @@ static ConstructorDecl *createImplicitConstructor(NominalTypeDecl *decl,
337337
ctor->setSynthesized();
338338
ctor->setAccess(accessLevel);
339339

340-
if (ctx.LangOpts.hasFeature(Feature::IsolatedDefaultValues)) {
340+
if (ctx.LangOpts.hasFeature(Feature::IsolatedDefaultValues) &&
341+
!decl->isActor()) {
341342
// If any of the type's actor-isolated properties:
342343
// 1. Have non-Sendable type, or
343344
// 2. Have an isolated initial value

test/Concurrency/actor_isolation.swift

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -765,15 +765,14 @@ actor LocalFunctionIsolatedActor {
765765
@available(SwiftStdlib 5.1, *)
766766
actor LazyActor {
767767
var v: Int = 0
768-
// expected-note@-1 6 {{property declared here}}
769768

770769
let l: Int = 0
771770

772771
lazy var l11: Int = { v }()
773772
lazy var l12: Int = v
774773
lazy var l13: Int = { self.v }()
775774
lazy var l14: Int = self.v
776-
lazy var l15: Int = { [unowned self] in self.v }() // expected-error{{actor-isolated property 'v' can not be referenced from a non-isolated context}}
775+
lazy var l15: Int = { [unowned self] in self.v }()
777776

778777
lazy var l21: Int = { l }()
779778
lazy var l22: Int = l
@@ -782,15 +781,15 @@ actor LazyActor {
782781
lazy var l25: Int = { [unowned self] in self.l }()
783782

784783
nonisolated lazy var l31: Int = { v }()
785-
// expected-error@-1 {{actor-isolated property 'v' can not be referenced from a non-isolated context}}
784+
// expected-error@-1 {{actor-isolated default value in a nonisolated context}}
786785
nonisolated lazy var l32: Int = v
787-
// expected-error@-1 {{actor-isolated property 'v' can not be referenced from a non-isolated context}}
786+
// expected-error@-1 {{actor-isolated default value in a nonisolated context}}
788787
nonisolated lazy var l33: Int = { self.v }()
789-
// expected-error@-1 {{actor-isolated property 'v' can not be referenced from a non-isolated context}}
788+
// expected-error@-1 {{actor-isolated default value in a nonisolated context}}
790789
nonisolated lazy var l34: Int = self.v
791-
// expected-error@-1 {{actor-isolated property 'v' can not be referenced from a non-isolated context}}
790+
// expected-error@-1 {{actor-isolated default value in a nonisolated context}}
792791
nonisolated lazy var l35: Int = { [unowned self] in self.v }()
793-
// expected-error@-1 {{actor-isolated property 'v' can not be referenced from a non-isolated context}}
792+
// expected-error@-1 {{actor-isolated default value in a nonisolated context}}
794793

795794
nonisolated lazy var l41: Int = { l }()
796795
nonisolated lazy var l42: Int = l

test/Concurrency/global_actor_inference.swift

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
// RUN: %empty-directory(%t)
22

33
// 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
4-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify
5-
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=targeted
4+
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -verify-additional-prefix minimal-targeted-
5+
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=targeted -verify-additional-prefix minimal-targeted-
66
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -verify-additional-prefix complete-tns-
77
// RUN: %target-swift-frontend -I %t -disable-availability-checking %s -emit-sil -o /dev/null -verify -strict-concurrency=complete -enable-experimental-feature RegionBasedIsolation -verify-additional-prefix complete-tns-
88

@@ -437,13 +437,15 @@ actor WrapperActorBad2<Wrapped: Sendable> {
437437
struct WrapperWithMainActorDefaultInit {
438438
var wrappedValue: Int { fatalError() }
439439

440-
@MainActor init() {} // expected-note 2 {{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}
440+
@MainActor init() {} // expected-note {{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}
441+
// expected-minimal-targeted-note@-1 {{calls to initializer 'init()' from outside of its actor context are implicitly asynchronous}}
441442
}
442443

443444
actor ActorWithWrapper {
444445
@WrapperOnActor var synced: Int = 0
445446
// expected-note@-1 3{{property declared here}}
446-
@WrapperWithMainActorDefaultInit var property: Int // expected-error {{call to main actor-isolated initializer 'init()' in a synchronous actor-isolated context}}
447+
@WrapperWithMainActorDefaultInit var property: Int // expected-minimal-targeted-error {{call to main actor-isolated initializer 'init()' in a synchronous actor-isolated context}}
448+
// expected-complete-tns-error@-1 {{main actor-isolated default value in a actor-isolated context}}
447449
func f() {
448450
_ = synced // expected-error{{main actor-isolated property 'synced' can not be referenced on a different actor instance}}
449451
_ = $synced // expected-error{{global actor 'SomeGlobalActor'-isolated property '$synced' can not be referenced on a different actor instance}}
@@ -557,8 +559,9 @@ struct WrapperOnUnsafeActor<Wrapped> {
557559
}
558560
}
559561

562+
// HasWrapperOnUnsafeActor gets an inferred @MainActor attribute.
560563
struct HasWrapperOnUnsafeActor {
561-
@WrapperOnUnsafeActor var synced: Int = 0
564+
@WrapperOnUnsafeActor var synced: Int = 0 // expected-complete-tns-error {{global actor 'OtherGlobalActor'-isolated default value in a main actor-isolated context}}
562565
// expected-note @-1 3{{property declared here}}
563566
// expected-complete-tns-note @-2 3{{property declared here}}
564567

@@ -643,11 +646,11 @@ func acceptAsyncSendableClosureInheriting<T>(@_inheritActorContext _: @Sendable
643646

644647
// defer bodies inherit global actor-ness
645648
@MainActor
646-
var statefulThingy: Bool = false // expected-note {{var declared here}}
649+
var statefulThingy: Bool = false // expected-minimal-targeted-note {{var declared here}}
647650
// expected-complete-tns-error @-1 {{top-level code variables cannot have a global actor}}
648651

649652
@MainActor
650-
func useFooInADefer() -> String { // expected-note {{calls to global function 'useFooInADefer()' from outside of its actor context are implicitly asynchronous}}
653+
func useFooInADefer() -> String { // expected-minimal-targeted-note {{calls to global function 'useFooInADefer()' from outside of its actor context are implicitly asynchronous}}
651654
defer {
652655
statefulThingy = true
653656
}
@@ -677,9 +680,11 @@ class Cutter {
677680

678681
@SomeGlobalActor
679682
class Butter {
680-
var a = useFooInADefer() // expected-error {{call to main actor-isolated global function 'useFooInADefer()' in a synchronous global actor 'SomeGlobalActor'-isolated context}}
683+
var a = useFooInADefer() // expected-minimal-targeted-error {{call to main actor-isolated global function 'useFooInADefer()' in a synchronous global actor 'SomeGlobalActor'-isolated context}}
684+
// expected-complete-tns-error@-1 {{main actor-isolated default value in a global actor 'SomeGlobalActor'-isolated context}}
681685

682-
nonisolated let b = statefulThingy // expected-error {{main actor-isolated var 'statefulThingy' can not be referenced from a non-isolated context}}
686+
nonisolated let b = statefulThingy // expected-minimal-targeted-error {{main actor-isolated var 'statefulThingy' can not be referenced from a non-isolated context}}
687+
// expected-complete-tns-error@-1 {{main actor-isolated default value in a nonisolated context}}
683688

684689
var c: Int = {
685690
return getGlobal7()

0 commit comments

Comments
 (0)