Skip to content

Commit e056c63

Browse files
authored
[Concurrency] Implement isIsolatingCurrentContext requirement and mode (#79788)
1 parent 1e88bc6 commit e056c63

18 files changed

+371
-41
lines changed

include/swift/Runtime/Bincompat.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,15 +67,20 @@ bool useLegacySwiftObjCHashing();
6767
/// - `swift_task_isCurrentExecutorImpl` does not invoke `checkIsolated`
6868
/// - logging a warning on concurrency violation is allowed
6969
///
70-
/// New behavior:
70+
/// Swift 6.0 behavior:
7171
/// - always fatal error in `swift_task_reportUnexpectedExecutor`
7272
/// - `swift_task_isCurrentExecutorImpl` will crash when it would have returned
7373
/// false
7474
/// - `swift_task_isCurrentExecutorImpl` does invoke `checkIsolated` when other
7575
/// checks failed
7676
///
77+
/// Swift 6.2 behavior:
78+
/// - `swift_task_isCurrentExecutorImpl` will attempt to call the *non-crashing*
79+
/// `isIsolatingCurrentContext` and return its result
80+
/// - if not available, it will invoke the the *crashing* 'checkIsolated'
81+
///
7782
/// This can be overridden by using `SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1`
78-
/// or `SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash|nocrash`
83+
/// or `SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash|nocrash|swift6|swift6.2`
7984
SWIFT_RUNTIME_STDLIB_SPI
8085
bool swift_bincompat_useLegacyNonCrashingExecutorChecks();
8186

include/swift/Runtime/Concurrency.h

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -773,8 +773,8 @@ void swift_task_enqueue(Job *job, SerialExecutorRef executor);
773773
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
774774
void swift_task_enqueueGlobal(Job *job);
775775

776-
/// Invoke an executor's `checkIsolated` or otherwise equivalent API,
777-
/// that will crash if the current executor is NOT the passed executor.
776+
/// Invoke an executor's `checkIsolated` implementation;
777+
/// It will crash if the current executor is NOT the passed executor.
778778
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
779779
void swift_task_checkIsolated(SerialExecutorRef executor);
780780

@@ -785,6 +785,15 @@ void swift_task_checkIsolated(SerialExecutorRef executor);
785785
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
786786
bool swift_task_invokeSwiftCheckIsolated(SerialExecutorRef executor);
787787

788+
/// Invoke an executor's `isIsolatingCurrentContext` implementation;
789+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
790+
bool swift_task_isIsolatingCurrentContext(SerialExecutorRef executor);
791+
792+
/// Invoke a Swift executor's `isIsolatingCurrentContext` implementation; returns
793+
/// `true` if it invoked the Swift implementation, `false` otherwise.
794+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
795+
bool swift_task_invokeSwiftIsIsolatingCurrentContext(SerialExecutorRef executor);
796+
788797
/// A count in nanoseconds.
789798
using JobDelay = unsigned long long;
790799

@@ -1037,6 +1046,10 @@ enum swift_task_is_current_executor_flag : uint64_t {
10371046

10381047
/// The routine should assert on failure.
10391048
Assert = 0x8,
1049+
1050+
/// The routine should use 'isIsolatingCurrentContext' function on the
1051+
/// 'expected' executor instead of `checkIsolated`.
1052+
HasIsIsolatingCurrentContext = 0x10,
10401053
};
10411054

10421055
SWIFT_EXPORT_FROM(swift_Concurrency)

include/swift/Runtime/ConcurrencyHooks.def

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,11 @@ SWIFT_CONCURRENCY_HOOK(void, swift_task_enqueueGlobalWithDeadline,
4444
long long tnsec,
4545
int clock, Job *job);
4646

47-
SWIFT_CONCURRENCY_HOOK(void, swift_task_checkIsolated, SerialExecutorRef executor);
47+
SWIFT_CONCURRENCY_HOOK(void, swift_task_checkIsolated,
48+
SerialExecutorRef executor);
49+
50+
SWIFT_CONCURRENCY_HOOK(bool, swift_task_isIsolatingCurrentContext,
51+
SerialExecutorRef executor);
4852

4953
SWIFT_CONCURRENCY_HOOK(bool, swift_task_isOnExecutor,
5054
HeapObject *executor,

stdlib/public/Concurrency/Actor.cpp

Lines changed: 133 additions & 29 deletions
Large diffs are not rendered by default.

stdlib/public/Concurrency/ConcurrencyHooks.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,8 @@ swift::swift_task_enqueueGlobalWithDeadline(
100100

101101
SWIFT_CC(swift) static void
102102
swift_task_checkIsolatedOrig(SerialExecutorRef executor) {
103-
swift_task_checkIsolatedImpl(*reinterpret_cast<SwiftExecutorRef *>(&executor));}
103+
swift_task_checkIsolatedImpl(*reinterpret_cast<SwiftExecutorRef *>(&executor));
104+
}
104105

105106
void
106107
swift::swift_task_checkIsolated(SerialExecutorRef executor) {
@@ -110,6 +111,20 @@ swift::swift_task_checkIsolated(SerialExecutorRef executor) {
110111
swift_task_checkIsolatedOrig(executor);
111112
}
112113

114+
SWIFT_CC(swift) static bool
115+
swift_task_isIsolatingCurrentContextOrig(SerialExecutorRef executor) {
116+
return swift_task_isIsolatingCurrentContextImpl(
117+
*reinterpret_cast<SwiftExecutorRef *>(&executor));
118+
}
119+
120+
bool
121+
swift::swift_task_isIsolatingCurrentContext(SerialExecutorRef executor) {
122+
if (SWIFT_UNLIKELY(swift_task_isIsolatingCurrentContext_hook))
123+
return swift_task_isIsolatingCurrentContext_hook(executor, swift_task_isIsolatingCurrentContextOrig);
124+
else
125+
return swift_task_isIsolatingCurrentContextOrig(executor);
126+
}
127+
113128
// Implemented in Swift because we need to obtain the user-defined flags on the executor ref.
114129
//
115130
// We could inline this with effort, though.

stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,11 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
154154
swift_executor_invokeSwiftCheckIsolated(executor);
155155
}
156156

157+
SWIFT_CC(swift)
158+
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
159+
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
160+
}
161+
157162
/// Insert a job into the cooperative global queue with a delay.
158163
SWIFT_CC(swift)
159164
void swift_task_enqueueGlobalWithDelayImpl(SwiftJobDelay delay,

stdlib/public/Concurrency/DispatchGlobalExecutor.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,12 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
448448
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption");
449449
}
450450

451+
SWIFT_CC(swift)
452+
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
453+
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
454+
}
455+
456+
451457
SWIFT_CC(swift)
452458
SwiftExecutorRef swift_task_getMainExecutorImpl() {
453459
return swift_executor_ordinary(

stdlib/public/Concurrency/Executor.swift

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,9 @@ public protocol SerialExecutor: Executor {
195195
@available(SwiftStdlib 6.0, *)
196196
func checkIsolated()
197197

198+
@available(SwiftStdlib 6.2, *)
199+
func isIsolatingCurrentContext() -> Bool
200+
198201
}
199202

200203
@available(SwiftStdlib 6.0, *)
@@ -210,6 +213,16 @@ extension SerialExecutor {
210213
}
211214
}
212215

216+
@available(SwiftStdlib 6.2, *)
217+
extension SerialExecutor {
218+
219+
@available(SwiftStdlib 6.2, *)
220+
public func isIsolatingCurrentContext() -> Bool {
221+
self.checkIsolated()
222+
return true
223+
}
224+
}
225+
213226
/// An executor that may be used as preferred executor by a task.
214227
///
215228
/// ### Impact of setting a task executor preference
@@ -467,6 +480,13 @@ internal func _task_serialExecutor_checkIsolated<E>(executor: E)
467480
executor.checkIsolated()
468481
}
469482

483+
@available(SwiftStdlib 6.2, *)
484+
@_silgen_name("_task_serialExecutor_isIsolatingCurrentContext")
485+
internal func _task_serialExecutor_isIsolatingCurrentContext<E>(executor: E) -> Bool
486+
where E: SerialExecutor {
487+
return executor.isIsolatingCurrentContext()
488+
}
489+
470490
/// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`.
471491
/// The obtained executor ref will have all the user-defined flags set on the executor.
472492
@available(SwiftStdlib 5.9, *)

stdlib/public/Concurrency/ExecutorImpl.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,14 @@ swift_executor_invokeSwiftCheckIsolated(SwiftExecutorRef executor) {
221221
return _swift_task_invokeSwiftCheckIsolated_c(executor);
222222
}
223223

224+
/// Check if the current context is isolated by the specified executor.
225+
static inline bool
226+
swift_executor_invokeSwiftIsIsolatingCurrentContext(SwiftExecutorRef executor) {
227+
extern bool _swift_task_invokeSwiftIsIsolatingCurrentContext_c(SwiftExecutorRef executor);
228+
229+
return _swift_task_invokeSwiftIsIsolatingCurrentContext_c(executor);
230+
}
231+
224232
/// Execute the specified job while running on the specified executor.
225233
static inline void swift_job_run(SwiftJob *job, SwiftExecutorRef executor) {
226234
extern void _swift_job_run_c(SwiftJob *job, SwiftExecutorRef executor);
@@ -276,6 +284,9 @@ SWIFT_CC(swift) void swift_task_enqueueMainExecutorImpl(SwiftJob *job);
276284
/// Assert that the specified executor is the current executor.
277285
SWIFT_CC(swift) void swift_task_checkIsolatedImpl(SwiftExecutorRef executor);
278286

287+
/// Check if the specified executor isolates the current context.
288+
SWIFT_CC(swift) bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor);
289+
279290
/// Get a reference to the main executor.
280291
SWIFT_CC(swift) SwiftExecutorRef swift_task_getMainExecutorImpl(void);
281292

stdlib/public/Concurrency/GlobalExecutor.cpp

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,10 @@
6363
using namespace swift;
6464

6565
extern "C" SWIFT_CC(swift)
66-
void _task_serialExecutor_checkIsolated(
67-
HeapObject *executor,
68-
const Metadata *selfType,
69-
const SerialExecutorWitnessTable *wtable);
66+
void _task_serialExecutor_checkIsolated(
67+
HeapObject *executor,
68+
const Metadata *selfType,
69+
const SerialExecutorWitnessTable *wtable);
7070

7171
SWIFT_CC(swift)
7272
bool swift::swift_task_invokeSwiftCheckIsolated(SerialExecutorRef executor)
@@ -86,6 +86,29 @@ extern "C" bool _swift_task_invokeSwiftCheckIsolated_c(SwiftExecutorRef executor
8686
return swift_task_invokeSwiftCheckIsolated(*reinterpret_cast<SerialExecutorRef *>(&executor));
8787
}
8888

89+
90+
extern "C" SWIFT_CC(swift)
91+
bool _task_serialExecutor_isIsolatingCurrentContext(
92+
HeapObject *executor,
93+
const Metadata *selfType,
94+
const SerialExecutorWitnessTable *wtable);
95+
96+
SWIFT_CC(swift)
97+
bool swift::swift_task_invokeSwiftIsIsolatingCurrentContext(SerialExecutorRef executor)
98+
{
99+
if (!executor.hasSerialExecutorWitnessTable())
100+
return false;
101+
102+
return _task_serialExecutor_isIsolatingCurrentContext(
103+
executor.getIdentity(), swift_getObjectType(executor.getIdentity()),
104+
executor.getSerialExecutorWitnessTable());
105+
}
106+
107+
extern "C" bool _swift_task_invokeSwiftIsIsolatingCurrentContext_c(SwiftExecutorRef executor)
108+
{
109+
return swift_task_invokeSwiftIsIsolatingCurrentContext(*reinterpret_cast<SerialExecutorRef *>(&executor));
110+
}
111+
89112
extern "C" void _swift_job_run_c(SwiftJob *job, SwiftExecutorRef executor)
90113
{
91114
swift_job_run(reinterpret_cast<Job *>(job),

stdlib/public/Concurrency/NonDispatchGlobalExecutor.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,11 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
7979
swift_executor_invokeSwiftCheckIsolated(executor);
8080
}
8181

82+
SWIFT_CC(swift)
83+
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
84+
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
85+
}
86+
8287
SWIFT_CC(swift)
8388
SwiftExecutorRef swift_task_getMainExecutorImpl() {
8489
return swift_executor_generic();

stdlib/public/runtime/EnvironmentVariables.def

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,9 +124,13 @@ VARIABLE(SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE, string, "",
124124
"isolation is not the expected one. Some old code may rely on the "
125125
"non-crashing behavior. This flag enables temporarily restoring the "
126126
"legacy 'nocrash' behavior until adopting code has been adjusted. "
127+
"It is possible to force the use of Swift 6.2's "
128+
"isIsolatingCurrentContext instead of checkIsolated by passing "
129+
" the 'swift6.2' configuration value. "
127130
"Legal values are: "
128131
" 'legacy' (Legacy behavior), "
129-
" 'swift6' (Swift 6.0+ behavior)")
132+
" 'swift6' (Swift 6.0-6.1 behavior)"
133+
" 'swift6.2' (Swift 6.2 behavior)")
130134

131135
VARIABLE(SWIFT_DUMP_ACCESSIBLE_FUNCTIONS, bool, false,
132136
"Dump a listing of all 'AccessibleFunctionRecord's upon first access. "
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %import-libdispatch -parse-as-library %s -o %t/a.out
3+
// RUN: %target-codesign %t/a.out
4+
// RUN: %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6.2 %target-run %t/a.out
5+
6+
// REQUIRES: executable_test
7+
// REQUIRES: concurrency
8+
// REQUIRES: concurrency_runtime
9+
10+
// REQUIRES: libdispatch
11+
12+
// UNSUPPORTED: back_deployment_runtime
13+
// UNSUPPORTED: back_deploy_concurrency
14+
// UNSUPPORTED: use_os_stdlib
15+
// UNSUPPORTED: freestanding
16+
17+
@available(SwiftStdlib 6.2, *)
18+
final class NaiveQueueExecutor: SerialExecutor {
19+
init() {}
20+
21+
func enqueue(_ job: consuming ExecutorJob) {
22+
job.runSynchronously(on: self.asUnownedSerialExecutor())
23+
}
24+
25+
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
26+
UnownedSerialExecutor(ordinary: self)
27+
}
28+
29+
func checkIsolated() {
30+
fatalError("Should not call this: checkIsolated")
31+
}
32+
33+
func isIsolatingCurrentContext() -> Bool {
34+
print("pretend it is ok: isIsolatingCurrentContext")
35+
return true
36+
}
37+
}
38+
39+
@available(SwiftStdlib 6.2, *)
40+
actor ActorOnNaiveQueueExecutor {
41+
let executor: NaiveQueueExecutor
42+
43+
init() {
44+
self.executor = NaiveQueueExecutor()
45+
}
46+
47+
nonisolated var unownedExecutor: UnownedSerialExecutor {
48+
self.executor.asUnownedSerialExecutor()
49+
}
50+
51+
nonisolated func checkPreconditionIsolated() async {
52+
print("Before preconditionIsolated")
53+
self.preconditionIsolated()
54+
print("After preconditionIsolated")
55+
56+
print("Before assumeIsolated")
57+
self.assumeIsolated { iso in
58+
print("Inside assumeIsolated")
59+
}
60+
print("After assumeIsolated")
61+
}
62+
}
63+
64+
@main struct Main {
65+
static func main() async {
66+
if #available(SwiftStdlib 6.2, *) {
67+
let actor = ActorOnNaiveQueueExecutor()
68+
await actor.checkPreconditionIsolated()
69+
// CHECK: Before preconditionIsolated
70+
// CHECK-NOT: Should not call this: checkIsolated
71+
// CHECK-NEXT: pretend it is ok: isIsolatingCurrentContext
72+
// CHECK-NEXT: After preconditionIsolated
73+
74+
// CHECK-NEXT: Before assumeIsolated
75+
// CHECK-NOT: Should not call this: checkIsolated
76+
// CHECK-NEXT: pretend it is ok: isIsolatingCurrentContext
77+
// CHECK-NEXT: After assumeIsolated
78+
}
79+
}
80+
}

test/Concurrency/Runtime/data_race_detection_legacy_warning.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// will be able to have this behavior, however new apps will not. We use the
77
// overrides to test the logic for legacy code remains functional.
88
//
9-
// RUN: %env-SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1 %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=legacy %target-run %t/a.out 2>&1 | %FileCheck %s
9+
// RUN: %env-SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1 %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=legacy %target-run %t/a.out 2>&1 | %FileCheck %s --dump-input=always
1010

1111
// REQUIRES: executable_test
1212
// REQUIRES: concurrency

test/abi/macOS/arm64/concurrency.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,13 @@ Added: _$ss27ThrowingDiscardingTaskGroupV05startC28SynchronouslyUnlessCancelled4
401401

402402
Added: _swift_task_startSynchronously
403403

404+
Added: _swift_task_invokeSwiftIsIsolatingCurrentContext
405+
Added: _swift_task_isIsolatingCurrentContext
406+
Added: _swift_task_isIsolatingCurrentContext_hook
407+
Added: _$sScf25isIsolatingCurrentContextSbyFTj
408+
Added: _$sScf25isIsolatingCurrentContextSbyFTq
409+
Added: _$sScfsE25isIsolatingCurrentContextSbyF
410+
404411
// add callee-allocated coro entrypoints
405412
// TODO: CoroutineAccessors: several of these symbols should be in swiftCore
406413
Added: __swift_coro_malloc_allocator

test/abi/macOS/x86_64/concurrency.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,6 +401,13 @@ Added: _$ss27ThrowingDiscardingTaskGroupV05startC28SynchronouslyUnlessCancelled4
401401

402402
Added: _swift_task_startSynchronously
403403

404+
Added: _swift_task_invokeSwiftIsIsolatingCurrentContext
405+
Added: _swift_task_isIsolatingCurrentContext
406+
Added: _swift_task_isIsolatingCurrentContext_hook
407+
Added: _$sScf25isIsolatingCurrentContextSbyFTj
408+
Added: _$sScf25isIsolatingCurrentContextSbyFTq
409+
Added: _$sScfsE25isIsolatingCurrentContextSbyF
410+
404411
// add callee-allocated coro entrypoints
405412
// TODO: CoroutineAccessors: several of these symbols should be in swiftCore
406413
Added: __swift_coro_malloc_allocator

test/embedded/Inputs/executor.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,12 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
306306
swift_executor_invokeSwiftCheckIsolated(executor);
307307
}
308308

309+
/// Check if the specified executor is the current executor.
310+
SWIFT_CC(swift)
311+
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
312+
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
313+
}
314+
309315
/// Get a reference to the main executor.
310316
SWIFT_CC(swift)
311317
SwiftExecutorRef swift_task_getMainExecutorImpl() {

0 commit comments

Comments
 (0)