Skip to content

Commit 0806751

Browse files
committed
[SILGen] Emit dynamic actor isolation checks for closures
For synchronous isolated closures passed to unsafe APIs (the ones that have not been fully concurrency checked) emit an expected executor check in prolog that would make sure that they are always used in the expected context. Resolves: rdar://133415157
1 parent 44d83ae commit 0806751

File tree

5 files changed

+288
-14
lines changed

5 files changed

+288
-14
lines changed

lib/SILGen/SILGenConcurrency.cpp

Lines changed: 54 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
using namespace swift;
2626
using namespace Lowering;
2727

28+
static void loadExpectedExecutorForLocalVar(SILGenFunction &SGF, VarDecl *var) {
29+
auto loc = RegularLocation::getAutoGeneratedLocation(SGF.F.getLocation());
30+
Type actorType = var->getTypeInContext();
31+
RValue actorInstanceRV =
32+
SGF.emitRValueForDecl(loc, var, actorType, AccessSemantics::Ordinary);
33+
ManagedValue actorInstance = std::move(actorInstanceRV).getScalarValue();
34+
SGF.ExpectedExecutor = SGF.emitLoadActorExecutor(loc, actorInstance);
35+
}
36+
2837
void SILGenFunction::emitExpectedExecutor() {
2938
// Whether the given declaration context is nested within an actor's
3039
// destructor.
@@ -80,17 +89,6 @@ void SILGenFunction::emitExpectedExecutor() {
8089
// which are allowed to be available on OSes where concurrency is not
8190
// available. rdar://106827064
8291

83-
// Local function to load the expected executor from a local actor
84-
auto loadExpectedExecutorForLocalVar = [&](VarDecl *var) {
85-
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
86-
Type actorType = var->getTypeInContext();
87-
RValue actorInstanceRV = emitRValueForDecl(
88-
loc, var, actorType, AccessSemantics::Ordinary);
89-
ManagedValue actorInstance =
90-
std::move(actorInstanceRV).getScalarValue();
91-
ExpectedExecutor = emitLoadActorExecutor(loc, actorInstance);
92-
};
93-
9492
if (auto *funcDecl =
9593
dyn_cast_or_null<AbstractFunctionDecl>(FunctionDC->getAsDecl())) {
9694
auto actorIsolation = getActorIsolation(funcDecl);
@@ -112,7 +110,7 @@ void SILGenFunction::emitExpectedExecutor() {
112110
(wantDataRaceChecks && funcDecl->isLocalCapture())) {
113111
auto loweredCaptures = SGM.Types.getLoweredLocalCaptures(SILDeclRef(funcDecl));
114112
if (auto isolatedParam = loweredCaptures.getIsolatedParamCapture()) {
115-
loadExpectedExecutorForLocalVar(isolatedParam);
113+
loadExpectedExecutorForLocalVar(*this, isolatedParam);
116114
} else {
117115
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
118116
ManagedValue actorArg;
@@ -131,7 +129,7 @@ void SILGenFunction::emitExpectedExecutor() {
131129
actorIsolation.getActorInstanceParameter() - 1;
132130
auto param = funcDecl->getParameters()->get(isolatedParamIdx);
133131
assert(param->isIsolated());
134-
loadExpectedExecutorForLocalVar(param);
132+
loadExpectedExecutorForLocalVar(*this, param);
135133
}
136134
}
137135
}
@@ -159,7 +157,8 @@ void SILGenFunction::emitExpectedExecutor() {
159157

160158
case ActorIsolation::ActorInstance: {
161159
if (wantExecutor) {
162-
loadExpectedExecutorForLocalVar(actorIsolation.getActorInstance());
160+
loadExpectedExecutorForLocalVar(*this,
161+
actorIsolation.getActorInstance());
163162
}
164163
break;
165164
}
@@ -610,6 +609,47 @@ static bool isCheckExpectedExecutorIntrinsicAvailable(SILGenModule &SGM) {
610609
return true;
611610
}
612611

612+
void SILGenFunction::emitExpectedExecutorPreconditionForClosure() {
613+
auto closure = dyn_cast_or_null<ClosureExpr>(FunctionDC);
614+
if (!closure)
615+
return;
616+
617+
auto &ctx = closure->getASTContext();
618+
if (!ctx.LangOpts.isDynamicActorIsolationCheckingEnabled())
619+
return;
620+
621+
// Asynchronous closures would hop to the expected executor.
622+
if (F.isAsync() || !closure->requiresDynamicIsolationChecking())
623+
return;
624+
625+
auto actorIsolation = closure->getActorIsolation();
626+
switch (actorIsolation) {
627+
case ActorIsolation::Unspecified:
628+
case ActorIsolation::Nonisolated:
629+
case ActorIsolation::NonisolatedUnsafe:
630+
break;
631+
632+
case ActorIsolation::Erased:
633+
llvm_unreachable("closure cannot have erased isolation");
634+
635+
case ActorIsolation::ActorInstance: {
636+
loadExpectedExecutorForLocalVar(*this, actorIsolation.getActorInstance());
637+
break;
638+
}
639+
640+
case ActorIsolation::GlobalActor:
641+
ExpectedExecutor =
642+
emitLoadGlobalActorExecutor(actorIsolation.getGlobalActor());
643+
break;
644+
}
645+
646+
if (ExpectedExecutor) {
647+
emitPreconditionCheckExpectedExecutor(
648+
RegularLocation::getAutoGeneratedLocation(F.getLocation()),
649+
ExpectedExecutor);
650+
}
651+
}
652+
613653
void SILGenFunction::emitPreconditionCheckExpectedExecutor(
614654
SILLocation loc, ActorIsolation isolation,
615655
std::optional<ManagedValue> actorSelf) {

lib/SILGen/SILGenFunction.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1186,6 +1186,10 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
11861186
void emitPreconditionCheckExpectedExecutor(
11871187
SILLocation loc, SILValue executor);
11881188

1189+
/// Emit a precondition check to ensure that the closure is executing in
1190+
/// the expected isolation context.
1191+
void emitExpectedExecutorPreconditionForClosure();
1192+
11891193
/// Gets a reference to the current executor for the task.
11901194
/// \returns a value of type Builtin.Executor
11911195
SILValue emitGetCurrentExecutor(SILLocation loc);

lib/SILGen/SILGenProlog.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,6 +1381,7 @@ void SILGenFunction::emitProlog(
13811381
}
13821382

13831383
emitExpectedExecutor();
1384+
emitExpectedExecutorPreconditionForClosure();
13841385

13851386
// IMPORTANT: This block should be the last one in `emitProlog`,
13861387
// since it terminates BB and no instructions should be insterted after it.
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
// RUN: %empty-directory(%t/src)
2+
// RUN: split-file %s %t/src
3+
4+
/// Build the library API
5+
// RUN: %target-swift-frontend -emit-module %t/src/API.swift \
6+
// RUN: -module-name API -swift-version 5 -enable-library-evolution \
7+
// RUN: -emit-module-path %t/API.swiftmodule \
8+
// RUN: -emit-module-interface-path %t/API.swiftinterface
9+
10+
// Build client with module
11+
// RUN: %target-swift-emit-silgen \
12+
// RUN: -I %t \
13+
// RUN: -disable-availability-checking \
14+
// RUN: -module-name Client \
15+
// RUN: -swift-version 6 \
16+
// RUN: %t/src/Client.swift -verify | %FileCheck %s
17+
18+
//--- API.swift
19+
public func compute(_: (() -> Void)?) {}
20+
21+
public func computeSendable(_: @escaping @Sendable () -> Void) {}
22+
23+
public func computeSending(_: sending @escaping () -> Void) {}
24+
25+
//--- Client.swift
26+
import API
27+
28+
func localCompute(_: (() -> Void)?) {}
29+
30+
func forceIsolation(isolation: isolated (any Actor)?) {}
31+
32+
// CHECK-LABEL: sil private [ossa] @$s6Client17test_global_actoryyFyycfU_ : $@convention(thin) () -> ()
33+
// CHECK: [[EXPECTED_EXECUTOR:%.*]] = extract_executor {{.*}} : $MainActor
34+
// CHECK: [[CHECK_EXECUTOR_FN:%.*]] = function_ref @$ss22_checkExpectedExecutor14_filenameStart01_D6Length01_D7IsASCII5_line9_executoryBp_BwBi1_BwBetF : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, Builtin.Word, Builtin.Executor) -> ()
35+
// CHECK: %11 = apply [[CHECK_EXECUTOR_FN]]({{.*}}, [[EXPECTED_EXECUTOR]]) : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, Builtin.Word, Builtin.Executor) -> ()
36+
// CHECK: } // end sil function '$s6Client17test_global_actoryyFyycfU_'
37+
@MainActor
38+
func test_global_actor() {
39+
compute {
40+
}
41+
}
42+
43+
// CHECK-LABEL: sil private [ossa] @$s6Client13test_isolated9isolationyScMYi_tFyycfU_ : $@convention(thin) (@sil_isolated @guaranteed MainActor) -> ()
44+
// CHECK: [[EXPECTED_EXECUTOR:%.*]] = extract_executor {{.*}} : $MainActor
45+
// CHECK: [[CHECK_EXECUTOR_FN:%.*]] = function_ref @$ss22_checkExpectedExecutor14_filenameStart01_D6Length01_D7IsASCII5_line9_executoryBp_BwBi1_BwBetF : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, Builtin.Word, Builtin.Executor) -> ()
46+
// CHECK: %11 = apply [[CHECK_EXECUTOR_FN]]({{.*}}, [[EXPECTED_EXECUTOR]]) : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, Builtin.Word, Builtin.Executor) -> ()
47+
// CHECK: } // end sil function '$s6Client13test_isolated9isolationyScMYi_tFyycfU_'
48+
func test_isolated(isolation: isolated MainActor) {
49+
compute {
50+
forceIsolation(isolation: isolation)
51+
}
52+
}
53+
54+
// CHECK-LABEL: sil private [ossa] @$s6Client55test_concurrency_checked_global_isolation_has_no_checksyyFyycfU_ : $@convention(thin) () -> ()
55+
// CHECK-NOT: function_ref @$ss22_checkExpectedExecutor14_filenameStart01_D6Length01_D7IsASCII5_line9_executoryBp_BwBi1_BwBetF
56+
// CHECK: } // end sil function '$s6Client55test_concurrency_checked_global_isolation_has_no_checksyyFyycfU_'
57+
@MainActor
58+
func test_concurrency_checked_global_isolation_has_no_checks() {
59+
localCompute {
60+
}
61+
}
62+
63+
// CHECK-LABEL: sil private [ossa] @$s6Client47test_concurrency_checked_isolated_has_no_checks9isolationyScMYi_tFyycfU_ : $@convention(thin) (@sil_isolated @guaranteed MainActor) -> ()
64+
// CHECK-NOT: function_ref @$ss22_checkExpectedExecutor14_filenameStart01_D6Length01_D7IsASCII5_line9_executoryBp_BwBi1_BwBetF
65+
// CHECK: } // end sil function '$s6Client47test_concurrency_checked_isolated_has_no_checks9isolationyScMYi_tFyycfU_'
66+
func test_concurrency_checked_isolated_has_no_checks(isolation: isolated MainActor) {
67+
localCompute {
68+
forceIsolation(isolation: isolation)
69+
}
70+
}
71+
72+
// CHECK-LABEL: sil private [ossa] @$s6Client24test_direct_closure_callyyFyyXEfU_ : $@convention(thin) () -> ()
73+
// CHECK-NOT: function_ref @$ss22_checkExpectedExecutor14_filenameStart01_D6Length01_D7IsASCII5_line9_executoryBp_BwBi1_BwBetF
74+
// CHECK: [[INNER_CLOSURE_REF:%.*]] = function_ref @$s6Client24test_direct_closure_callyyFyyXEfU_yycfU_ : $@convention(thin) () -> ()
75+
// CHECK-NEXT: [[INNER_CLOSURE:%.*]] = thin_to_thick_function [[INNER_CLOSURE_REF]] : $@convention(thin) () -> () to $@callee_guaranteed () -> ()
76+
// CHECK-NEXT: [[OPT_INNER_CLOSURE:%.*]] = enum $Optional<@callee_guaranteed () -> ()>, #Optional.some!enumelt, [[INNER_CLOSURE]] : $@callee_guaranteed () -> ()
77+
// CHECK: [[COMPUTE_FN:%.*]] = function_ref @$s3API7computeyyyycSgF : $@convention(thin) (@guaranteed Optional<@callee_guaranteed () -> ()>) -> ()
78+
// CHECK-NEXT: apply [[COMPUTE_FN]]([[OPT_INNER_CLOSURE]]) : $@convention(thin) (@guaranteed Optional<@callee_guaranteed () -> ()>) -> ()
79+
// CHECK: } // end sil function '$s6Client24test_direct_closure_callyyFyyXEfU_'
80+
81+
// CHECK-LABEL: sil private [ossa] @$s6Client24test_direct_closure_callyyFyyXEfU_yycfU_ : $@convention(thin) () -> ()
82+
// CHECK: [[EXPECTED_EXECUTOR:%.*]] = extract_executor {{.*}} : $MainActor
83+
// CHECK: [[CHECK_EXECUTOR_FN:%.*]] = function_ref @$ss22_checkExpectedExecutor14_filenameStart01_D6Length01_D7IsASCII5_line9_executoryBp_BwBi1_BwBetF : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, Builtin.Word, Builtin.Executor) -> ()
84+
// CHECK: %11 = apply [[CHECK_EXECUTOR_FN]]({{.*}}, [[EXPECTED_EXECUTOR]]) : $@convention(thin) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, Builtin.Word, Builtin.Executor) -> ()
85+
// CHECK: } // end sil function '$s6Client24test_direct_closure_callyyFyyXEfU_yycfU_'
86+
@MainActor
87+
func test_direct_closure_call() {
88+
{
89+
compute {
90+
}
91+
}()
92+
}
93+
94+
@MainActor
95+
func test_global_actor_sendable_and_sending_closures() {
96+
// CHECK-LABEL: sil private [ossa] @$s6Client47test_global_actor_sendable_and_sending_closuresyyFyyYbcfU_ : $@convention(thin) @Sendable () -> ()
97+
// CHECK-NOT: function_ref @$ss22_checkExpectedExecutor14_filenameStart01_D6Length01_D7IsASCII5_line9_executoryBp_BwBi1_BwBetF
98+
// CHECK: } // end sil function '$s6Client47test_global_actor_sendable_and_sending_closuresyyFyyYbcfU_'
99+
computeSendable {
100+
}
101+
102+
// CHECK-LABEL: sil private [ossa] @$s6Client47test_global_actor_sendable_and_sending_closuresyyFyycfU0_ : $@convention(thin) () -> ()
103+
// CHECK-NOT: function_ref @$ss22_checkExpectedExecutor14_filenameStart01_D6Length01_D7IsASCII5_line9_executoryBp_BwBi1_BwBetF
104+
// CHECK: } // end sil function '$s6Client47test_global_actor_sendable_and_sending_closuresyyFyycfU0_'
105+
computeSending {
106+
forceIsolation(isolation: #isolation)
107+
}
108+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// RUN: %empty-directory(%t/src)
2+
// RUN: split-file %s %t/src
3+
4+
// RUN: %target-build-swift %t/src/Interface.swift -emit-module -emit-library \
5+
// RUN: -target %target-cpu-apple-macosx10.15 -swift-version 5 \
6+
// RUN: -enable-library-evolution \
7+
// RUN: -module-name Interface \
8+
// RUN: -o %t/%target-library-name(Interface) \
9+
// RUN: -emit-module-interface-path %t/Interface.swiftinterface
10+
11+
// RUN: %target-build-swift -target %target-cpu-apple-macosx10.15 -swift-version 5 -Xfrontend -enable-upcoming-feature -Xfrontend DynamicActorIsolation -I %t -L %t -lInterface %t/src/Crash1.swift -o %t/crash1.out
12+
// RUN: %target-codesign %t/crash1.out
13+
// RUN: not --crash env SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=legacy SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=2 %target-run %t/crash1.out 2>&1 | %FileCheck %t/src/Crash1.swift --check-prefix=LEGACY_CHECK
14+
// RUN: not --crash env SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6 SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=2 %target-run %t/crash1.out 2>&1 | %FileCheck %t/src/Crash1.swift --check-prefix=SWIFT6_CHECK --dump-input=always
15+
16+
// RUN: %target-build-swift -target %target-cpu-apple-macosx10.15 -swift-version 6 -I %t -L %t -lInterface %t/src/Crash2.swift -o %t/crash2.out
17+
// RUN: %target-codesign %t/crash2.out
18+
// RUN: not --crash env SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=legacy SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=2 %target-run %t/crash2.out 2>&1 | %FileCheck %t/src/Crash2.swift --check-prefix=LEGACY_CHECK
19+
// RUN: not --crash env SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6 SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=2 %target-run %t/crash2.out 2>&1 | %FileCheck %t/src/Crash2.swift --check-prefix=SWIFT6_CHECK --dump-input=always
20+
21+
// RUN: %target-build-swift -target %target-cpu-apple-macosx10.15 -swift-version 6 -I %t -L %t -lInterface %t/src/Crash3.swift -o %t/crash3.out
22+
// RUN: %target-codesign %t/crash3.out
23+
// RUN: not --crash env SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=legacy SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=2 %target-run %t/crash3.out 2>&1 | %FileCheck %t/src/Crash3.swift --check-prefix=LEGACY_CHECK
24+
// RUN: not --crash env SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6 SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=2 %target-run %t/crash3.out 2>&1 | %FileCheck %t/src/Crash3.swift --check-prefix=SWIFT6_CHECK --dump-input=always
25+
26+
// REQUIRES: asserts
27+
// REQUIRES: concurrency
28+
// REQUIRES: concurrency_runtime
29+
// REQUIRES: executable_test
30+
// REQUIRES: OS=macosx
31+
32+
// rdar://123810657
33+
// UNSUPPORTED: back_deployment_runtime
34+
35+
//--- Interface.swift
36+
import Dispatch
37+
38+
public func runTest(_ fn: @escaping () -> Void) async {
39+
await Task.detached {
40+
fn()
41+
}.value
42+
}
43+
44+
public func syncRunTest(_ fn: @escaping () -> Void) {
45+
let sem = DispatchSemaphore(value: 0)
46+
47+
Task.detached {
48+
fn()
49+
sem.signal()
50+
}
51+
52+
sem.wait()
53+
}
54+
55+
//--- crash1.swift
56+
import Interface
57+
58+
@globalActor
59+
actor MyActor {
60+
static let shared = MyActor()
61+
}
62+
63+
func forceIsolation(isolation: isolated (any Actor)?) {}
64+
65+
@MyActor
66+
func test() async {
67+
await runTest { forceIsolation(isolation: #isolation) }
68+
}
69+
70+
await test()
71+
72+
print("OK")
73+
74+
// LEGACY_CHECK: data race detected: actor-isolated function at crash1/Crash1.swift:12 was not called on the same actor
75+
76+
// Crash without good message, since via 'dispatch_assert_queue'
77+
// SWIFT6_CHECK-NOT: OK
78+
79+
//--- crash2.swift
80+
import Interface
81+
82+
@globalActor
83+
actor MyActor {
84+
static let shared = MyActor()
85+
}
86+
87+
@MyActor
88+
func test() async {
89+
syncRunTest { }
90+
}
91+
92+
await test()
93+
94+
print("OK")
95+
96+
// LEGACY_CHECK: data race detected: actor-isolated function at crash2/Crash2.swift:10 was not called on the same actor
97+
98+
// Crash without good message, since via 'dispatch_assert_queue'
99+
// SWIFT6_CHECK-NOT: OK
100+
101+
//--- crash3.swift
102+
import Interface
103+
104+
actor MyActor {
105+
}
106+
107+
func forceIsolation(isolation: isolated MyActor) {
108+
}
109+
110+
func test(isolation: isolated MyActor) async {
111+
syncRunTest { forceIsolation(isolation: isolation) }
112+
}
113+
114+
await test(isolation: MyActor())
115+
116+
print("OK")
117+
118+
// LEGACY_CHECK: data race detected: actor-isolated function at crash3/Crash3.swift:10 was not called on the same actor
119+
120+
// Crash without good message, since via 'dispatch_assert_queue'
121+
// SWIFT6_CHECK-NOT: OK

0 commit comments

Comments
 (0)