Skip to content

Commit 1cf4e99

Browse files
committed
Propagate global actor-ness of functions/closures.
I fixed a bunch of small issues around here that resulted in a bunch of radars being fixed. Specifically: 1. I made it so that we treat function_refs that are from an actor isolated function as actor isolated instead of sendable. 2. I made it so that autoclosures which return global actor isolated functions are treated as producing a global actor isolated function. 3. I made it so that we properly handle SILGen code patterns produced by Sendable GlobalActor isolated things. rdar://125452372 rdar://121954871 rdar://121955895 rdar://122692698
1 parent 77dccac commit 1cf4e99

File tree

9 files changed

+363
-16
lines changed

9 files changed

+363
-16
lines changed

include/swift/SILOptimizer/Utils/PartitionUtils.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ class SILIsolationInfo {
208208
return SILIsolationInfo();
209209
}
210210

211+
static SILIsolationInfo getGlobalActorIsolated(Type globalActorType) {
212+
return getActorIsolated(ActorIsolation::forGlobalActor(globalActorType));
213+
}
214+
211215
static SILIsolationInfo getTaskIsolated(SILValue value) {
212216
return {Kind::Task, value};
213217
}

lib/SILGen/SILGen.cpp

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -725,6 +725,13 @@ SILFunction *SILGenModule::getFunction(SILDeclRef constant,
725725
return IGM.getFunction(constant, NotForDefinition);
726726
});
727727

728+
// If we have global actor isolation for our constant, put the isolation onto
729+
// the function.
730+
if (auto isolation =
731+
getActorIsolationOfContext(constant.getInnermostDeclContext())) {
732+
F->setActorIsolation(isolation);
733+
}
734+
728735
assert(F && "SILFunction should have been defined");
729736

730737
emittedFunctions[constant] = F;
@@ -1217,9 +1224,12 @@ void SILGenModule::preEmitFunction(SILDeclRef constant, SILFunction *F,
12171224
if (F->getLoweredFunctionType()->isPolymorphic())
12181225
F->setGenericEnvironment(Types.getConstantGenericEnvironment(constant));
12191226

1220-
// Set the actor isolation of the function to its innermost decl context.
1221-
F->setActorIsolation(
1222-
getActorIsolationOfContext(constant.getInnermostDeclContext()));
1227+
// If we have global actor isolation for our constant, put the isolation onto
1228+
// the function.
1229+
if (auto isolation =
1230+
getActorIsolationOfContext(constant.getInnermostDeclContext())) {
1231+
F->setActorIsolation(isolation);
1232+
}
12231233

12241234
// Create a debug scope for the function using astNode as source location.
12251235
F->setDebugScope(new (M) SILDebugScope(Loc, F));

lib/SILOptimizer/Analysis/RegionAnalysis.cpp

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1848,7 +1848,7 @@ class PartitionOpTranslator {
18481848
}
18491849

18501850
// If our self parameter was transferring, transfer it. Otherwise, just
1851-
// stick it in the non seld operand values array and run multiassign on
1851+
// stick it in the non self operand values array and run multiassign on
18521852
// it.
18531853
if (fas.hasSelfArgument()) {
18541854
auto &selfOperand = fas.getSelfArgumentOperand();
@@ -1864,6 +1864,12 @@ class PartitionOpTranslator {
18641864
}
18651865
}
18661866

1867+
// Add our callee to non-transferring parameters. This ensures that if it is
1868+
// actor isolated, that propagates into our results. This is especially
1869+
// important since our callee could be dynamically isolated and we cannot
1870+
// know that until we perform dataflow.
1871+
nonTransferringParameters.push_back(fas.getCallee());
1872+
18671873
SmallVector<SILValue, 8> applyResults;
18681874
getApplyResults(*fas, applyResults);
18691875

@@ -3283,12 +3289,59 @@ TrackableValue RegionAnalysisValueMap::getTrackableValue(
32833289
}
32843290
}
32853291

3286-
// Then see if we have a sendable value. By default we assume values are not
3287-
// sendable.
32883292
if (auto *defInst = value.getDefiningInstruction()) {
3289-
// Though these values are technically non-Sendable, we can safely and
3290-
// consistently treat them as Sendable.
3291-
if (isa<ClassMethodInst, FunctionRefInst>(defInst)) {
3293+
// Treat function ref as either actor isolated or sendable.
3294+
if (auto *fri = dyn_cast<FunctionRefInst>(defInst)) {
3295+
auto isolation = fri->getReferencedFunction()->getActorIsolation();
3296+
if (isolation.isActorIsolated()) {
3297+
iter.first->getSecond().mergeIsolationRegionInfo(
3298+
SILIsolationInfo::getActorIsolated(isolation));
3299+
return {iter.first->first, iter.first->second};
3300+
}
3301+
3302+
// Otherwise, lets look at the AST and see if our function ref is from an
3303+
// autoclosure.
3304+
if (auto *autoclosure = fri->getLoc().getAsASTNode<AutoClosureExpr>()) {
3305+
if (auto *funcType = autoclosure->getType()->getAs<AnyFunctionType>()) {
3306+
if (funcType->hasGlobalActor()) {
3307+
if (funcType->hasGlobalActor()) {
3308+
iter.first->getSecond().mergeIsolationRegionInfo(
3309+
SILIsolationInfo::getActorIsolated(
3310+
ActorIsolation::forGlobalActor(
3311+
funcType->getGlobalActor())));
3312+
return {iter.first->first, iter.first->second};
3313+
}
3314+
}
3315+
3316+
if (auto *resultFType =
3317+
funcType->getResult()->getAs<AnyFunctionType>()) {
3318+
if (resultFType->hasGlobalActor()) {
3319+
iter.first->getSecond().mergeIsolationRegionInfo(
3320+
SILIsolationInfo::getActorIsolated(
3321+
ActorIsolation::forGlobalActor(
3322+
resultFType->getGlobalActor())));
3323+
return {iter.first->first, iter.first->second};
3324+
}
3325+
}
3326+
}
3327+
}
3328+
3329+
iter.first->getSecond().addFlag(TrackableValueFlag::isSendable);
3330+
return {iter.first->first, iter.first->second};
3331+
}
3332+
3333+
if (auto *cmi = dyn_cast<ClassMethodInst>(defInst)) {
3334+
if (auto *declRefExpr = cmi->getLoc().getAsASTNode<DeclRefExpr>()) {
3335+
// See if we are actor isolated. If so, treat this as non-Sendable so we
3336+
// propagate actor isolation.
3337+
if (auto isolation = getActorIsolation(declRefExpr->getDecl())) {
3338+
if (isolation.isActorIsolated()) {
3339+
iter.first->getSecond().mergeIsolationRegionInfo(
3340+
SILIsolationInfo::getActorIsolated(isolation));
3341+
return {iter.first->first, iter.first->second};
3342+
}
3343+
}
3344+
}
32923345
iter.first->getSecond().addFlag(TrackableValueFlag::isSendable);
32933346
return {iter.first->first, iter.first->second};
32943347
}
@@ -3379,6 +3432,71 @@ TrackableValue RegionAnalysisValueMap::getTrackableValue(
33793432
}
33803433
}
33813434

3435+
// See if we have a convert function from a Sendable actor isolated function,
3436+
// we want to treat the result of the convert function as being actor isolated
3437+
// so that we cannot escape the value.
3438+
//
3439+
// NOTE: At this point, we already know that cfi's result is not sendable,
3440+
// since we would have exited above already.
3441+
if (auto *cfi = dyn_cast<ConvertFunctionInst>(iter.first->first.getValue())) {
3442+
SILValue operand = cfi->getOperand();
3443+
if (operand->getType().getAs<SILFunctionType>()->isSendable()) {
3444+
SILValue newValue = operand;
3445+
do {
3446+
operand = newValue;
3447+
3448+
newValue = lookThroughOwnershipInsts(operand);
3449+
if (auto *ttfi = dyn_cast<ThinToThickFunctionInst>(newValue)) {
3450+
newValue = ttfi->getOperand();
3451+
}
3452+
3453+
if (auto *cfi = dyn_cast<ConvertFunctionInst>(newValue)) {
3454+
newValue = cfi->getOperand();
3455+
}
3456+
3457+
if (auto *pai = dyn_cast<PartialApplyInst>(newValue)) {
3458+
newValue = pai->getCallee();
3459+
}
3460+
} while (newValue != operand);
3461+
3462+
if (auto *ai = dyn_cast<ApplyInst>(operand)) {
3463+
if (auto *callExpr = ai->getLoc().getAsASTNode<ApplyExpr>()) {
3464+
if (auto *callType = callExpr->getType()->getAs<AnyFunctionType>()) {
3465+
if (callType->hasGlobalActor()) {
3466+
iter.first->getSecond().mergeIsolationRegionInfo(
3467+
SILIsolationInfo::getGlobalActorIsolated(
3468+
callType->getGlobalActor()));
3469+
return {iter.first->first, iter.first->second};
3470+
}
3471+
}
3472+
}
3473+
}
3474+
3475+
if (auto *fri = dyn_cast<FunctionRefInst>(operand)) {
3476+
if (auto actorIsolation =
3477+
fri->getReferencedFunction()->getActorIsolation()) {
3478+
if (actorIsolation.isActorIsolated()) {
3479+
iter.first->getSecond().mergeIsolationRegionInfo(
3480+
SILIsolationInfo::getActorIsolated(actorIsolation));
3481+
return {iter.first->first, iter.first->second};
3482+
}
3483+
}
3484+
3485+
// See if the function ref statically is known to have actor isolation.
3486+
//
3487+
// TODO: We should make it so that the closure constructed has actor
3488+
// isolation.
3489+
if (auto value = tryToTrackValue(fri)) {
3490+
auto isolation = value->getIsolationRegionInfo();
3491+
if (isolation.isActorIsolated()) {
3492+
iter.first->getSecond().mergeIsolationRegionInfo(isolation);
3493+
return {iter.first->first, iter.first->second};
3494+
}
3495+
}
3496+
}
3497+
}
3498+
}
3499+
33823500
return {iter.first->first, iter.first->second};
33833501
}
33843502

lib/SILOptimizer/Mandatory/TransferNonSendable.cpp

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1152,13 +1152,18 @@ class TransferNonTransferrableDiagnosticInferrer {
11521152
bool run();
11531153

11541154
private:
1155-
bool initForIsolatedPartialApply(Operand *op, AbstractClosureExpr *ace);
1155+
/// \p actualCallerIsolation is used to override the caller isolation we use
1156+
/// when emitting the error if the closure would have the incorrect one.
1157+
bool initForIsolatedPartialApply(
1158+
Operand *op, AbstractClosureExpr *ace,
1159+
std::optional<ActorIsolation> actualCallerIsolation = {});
11561160
};
11571161

11581162
} // namespace
11591163

11601164
bool TransferNonTransferrableDiagnosticInferrer::initForIsolatedPartialApply(
1161-
Operand *op, AbstractClosureExpr *ace) {
1165+
Operand *op, AbstractClosureExpr *ace,
1166+
std::optional<ActorIsolation> actualCallerIsolation) {
11621167
SmallVector<std::tuple<CapturedValue, unsigned, ApplyIsolationCrossing>, 8>
11631168
foundCapturedIsolationCrossing;
11641169
ace->getIsolationCrossing(foundCapturedIsolationCrossing);
@@ -1169,8 +1174,15 @@ bool TransferNonTransferrableDiagnosticInferrer::initForIsolatedPartialApply(
11691174
for (auto &p : foundCapturedIsolationCrossing) {
11701175
if (std::get<1>(p) == opIndex) {
11711176
auto loc = RegularLocation(std::get<0>(p).getLoc());
1177+
auto crossing = std::get<2>(p);
1178+
auto declIsolation = crossing.getCallerIsolation();
1179+
auto closureIsolation = crossing.getCalleeIsolation();
1180+
if (!bool(declIsolation) && actualCallerIsolation) {
1181+
declIsolation = *actualCallerIsolation;
1182+
}
11721183
diagnosticEmitter.emitNamedFunctionArgumentClosure(
1173-
loc, std::get<0>(p).getDecl()->getBaseIdentifier(), std::get<2>(p));
1184+
loc, std::get<0>(p).getDecl()->getBaseIdentifier(),
1185+
ApplyIsolationCrossing(declIsolation, closureIsolation));
11741186
return true;
11751187
}
11761188
}
@@ -1220,6 +1232,15 @@ bool TransferNonTransferrableDiagnosticInferrer::run() {
12201232
}
12211233
assert(isolation && "Expected non-null");
12221234

1235+
// Then if we are calling a closure expr. If so, we should use the loc of
1236+
// the closure.
1237+
if (auto *closureExpr =
1238+
dyn_cast<AbstractClosureExpr>(sourceApply->getFn())) {
1239+
initForIsolatedPartialApply(op, closureExpr,
1240+
isolation->getCallerIsolation());
1241+
return true;
1242+
}
1243+
12231244
// See if we can infer a name from the value.
12241245
SmallString<64> resultingName;
12251246
if (auto name = inferNameFromValue(op->get())) {
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
@MainActor public func mainActorFunction() {}

test/Concurrency/isolated_captures.swift

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ class NotSendable {
3939
let ns = NotSendable(x: 0)
4040
MyActor.ns = ns
4141

42-
// expected-region-isolation-warning @+2 {{transferring 'ns' may cause a race}}
43-
// expected-region-isolation-note @+1 {{transferring global actor 'MyActor'-isolated 'ns' to global actor 'YourActor'-isolated callee could cause races between global actor 'YourActor'-isolated and global actor 'MyActor'-isolated uses}}
4442
await { @YourActor in
43+
// expected-region-isolation-warning @+3 {{transferring 'ns' may cause a race}}
44+
// expected-region-isolation-note @+2 {{global actor 'MyActor'-isolated 'ns' is captured by a global actor 'YourActor'-isolated closure. global actor 'YourActor'-isolated uses in closure may race against later global actor 'MyActor'-isolated uses}}
4545
// expected-complete-warning@+1 {{capture of 'ns' with non-sendable type 'NotSendable' in an isolated closure; this is an error in the Swift 6 language mode}}
4646
YourActor.ns = ns
4747
}()
@@ -61,8 +61,9 @@ class NotSendable {
6161
let ns = NotSendable(x: 0)
6262
ns.stash()
6363

64-
// FIXME: Region isolation should diagnose this (https://github.com/apple/swift/issues/71533)
6564
await { @YourActor in
65+
// expected-region-isolation-warning @+3 {{transferring 'ns' may cause a race}}
66+
// expected-region-isolation-note @+2 {{global actor 'MyActor'-isolated 'ns' is captured by a global actor 'YourActor'-isolated closure. global actor 'YourActor'-isolated uses in closure may race against later global actor 'MyActor'-isolated uses}}
6667
// expected-complete-warning@+1 {{capture of 'ns' with non-sendable type 'NotSendable' in an isolated closure; this is an error in the Swift 6 language mode}}
6768
YourActor.ns = ns
6869
}()
@@ -81,8 +82,9 @@ class NotSendable {
8182
@MyActor func exhibitRace3() async {
8283
let ns = NotSendable()
8384

84-
// FIXME: Region isolation should diagnose this (https://github.com/apple/swift/issues/71533)
8585
await { @YourActor in
86+
// expected-region-isolation-warning @+3 {{transferring 'ns' may cause a race}}
87+
// expected-region-isolation-note @+2 {{global actor 'MyActor'-isolated 'ns' is captured by a global actor 'YourActor'-isolated closure. global actor 'YourActor'-isolated uses in closure may race against later global actor 'MyActor'-isolated uses}}
8688
// expected-complete-warning@+1 {{capture of 'ns' with non-sendable type 'NotSendable' in an isolated closure; this is an error in the Swift 6 language mode}}
8789
YourActor.ns = ns
8890
}()

test/Concurrency/transfernonsendable_global_actor.swift

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ func transferToNonIsolated<T>(_ t: T) async {}
2222
@MainActor func transferToMainActor<T>(_ t: T) async {}
2323
@CustomActor func transferToCustomActor<T>(_ t: T) async {}
2424
func useValue<T>(_ t: T) {}
25+
func useValueAsync<T>(_ t: T) async {}
26+
@MainActor func useValueMainActor<T>(_ t: T) {}
27+
@MainActor func mainActorFunction() {}
2528

2629
var booleanFlag: Bool { false }
2730
@MainActor var mainActorIsolatedGlobal = NonSendableKlass()
@@ -221,3 +224,83 @@ struct Clock {
221224
// transferred value 'ns' here.
222225
useValue(ns)
223226
}
227+
228+
@MainActor func synchronousActorIsolatedClosureError() async {
229+
let closure = { @MainActor @Sendable in
230+
MainActor.assertIsolated()
231+
}
232+
233+
let erased: () -> Void = closure
234+
235+
await useValueAsync(erased) // expected-tns-warning {{transferring 'erased' may cause a race}}
236+
// expected-tns-note @-1 {{transferring main actor-isolated 'erased' to nonisolated callee could cause races between nonisolated and main actor-isolated uses}}
237+
// expected-complete-warning @-2 {{passing argument of non-sendable type '() -> Void' outside of main actor-isolated context may introduce data races}}
238+
// expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
239+
}
240+
241+
@MainActor func synchronousActorIsolatedFunctionError() async {
242+
let erased: () -> Void = mainActorFunction
243+
244+
await useValueAsync(erased) // expected-tns-warning {{transferring 'erased' may cause a race}}
245+
// expected-tns-note @-1 {{transferring main actor-isolated 'erased' to nonisolated callee could cause races between nonisolated and main actor-isolated uses}}
246+
// expected-complete-warning @-2 {{passing argument of non-sendable type '() -> Void' outside of main actor-isolated context may introduce data races}}
247+
// expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
248+
}
249+
250+
@MainActor func synchronousActorIsolatedGenericFunctionError<T>(_ t: T) async {
251+
let erased: (T) -> Void = useValueMainActor
252+
253+
await useValueAsync(erased) // expected-tns-warning {{transferring 'erased' may cause a race}}
254+
// expected-tns-note @-1 {{transferring main actor-isolated 'erased' to nonisolated callee could cause races between nonisolated and main actor-isolated uses}}
255+
// expected-complete-warning @-2 {{passing argument of non-sendable type '(T) -> Void' outside of main actor-isolated context may introduce data races}}
256+
// expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
257+
}
258+
259+
@MainActor func synchronousActorIsolatedClassMethodError() async {
260+
@MainActor class Test {
261+
func foo() {}
262+
}
263+
264+
let t = Test()
265+
let erased: () -> Void = t.foo
266+
267+
await useValueAsync(erased) // expected-tns-warning {{transferring 'erased' may cause a race}}
268+
// expected-tns-note @-1 {{transferring main actor-isolated 'erased' to nonisolated callee could cause races between nonisolated and main actor-isolated uses}}
269+
// expected-complete-warning @-2 {{passing argument of non-sendable type '() -> Void' outside of main actor-isolated context may introduce data races}}
270+
// expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
271+
}
272+
273+
@MainActor func synchronousActorIsolatedFinalClassMethodError() async {
274+
@MainActor final class Test {
275+
func foo() {}
276+
}
277+
278+
let t = Test()
279+
let erased: () -> Void = t.foo
280+
281+
await useValueAsync(erased) // expected-tns-warning {{transferring 'erased' may cause a race}}
282+
// expected-tns-note @-1 {{transferring main actor-isolated 'erased' to nonisolated callee could cause races between nonisolated and main actor-isolated uses}}
283+
// expected-complete-warning @-2 {{passing argument of non-sendable type '() -> Void' outside of main actor-isolated context may introduce data races}}
284+
// expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
285+
}
286+
287+
@MainActor func synchronousClosureCapturingGlobalActorIsolatedGlobal() async {
288+
let closure = {
289+
print(mainActorIsolatedGlobal)
290+
}
291+
// Regions: [{(closure), @MainActor}]
292+
await transferToCustomActor(closure) // expected-tns-warning {{transferring 'closure' may cause a race}}
293+
// expected-tns-note @-1 {{transferring main actor-isolated 'closure' to global actor 'CustomActor'-isolated callee could cause races between global actor 'CustomActor'-isolated and main actor-isolated uses}}
294+
// expected-complete-warning @-2 {{passing argument of non-sendable type '() -> ()' into global actor 'CustomActor'-isolated context may introduce data races}}
295+
// expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
296+
}
297+
298+
@MainActor func synchronousClosureCapturingGlobalActorIsolatedFunction() async {
299+
let closure = {
300+
mainActorFunction()
301+
}
302+
await transferToCustomActor(closure) // expected-tns-warning {{transferring 'closure' may cause a race}}
303+
// expected-tns-note @-1 {{transferring main actor-isolated 'closure' to global actor 'CustomActor'-isolated callee could cause races between global actor 'CustomActor'-isolated and main actor-isolated uses}}
304+
// expected-complete-warning @-2 {{passing argument of non-sendable type '() -> ()' into global actor 'CustomActor'-isolated context may introduce data races}}
305+
// expected-complete-note @-3 {{a function type must be marked '@Sendable' to conform to 'Sendable'}}
306+
}

0 commit comments

Comments
 (0)