Skip to content

[Concurrency] Implement isIsolatingCurrentContext requirement and mode #79788

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 1 commit into from
Mar 11, 2025
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
9 changes: 7 additions & 2 deletions include/swift/Runtime/Bincompat.h
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,20 @@ bool useLegacySwiftObjCHashing();
/// - `swift_task_isCurrentExecutorImpl` does not invoke `checkIsolated`
/// - logging a warning on concurrency violation is allowed
///
/// New behavior:
/// Swift 6.0 behavior:
/// - always fatal error in `swift_task_reportUnexpectedExecutor`
/// - `swift_task_isCurrentExecutorImpl` will crash when it would have returned
/// false
/// - `swift_task_isCurrentExecutorImpl` does invoke `checkIsolated` when other
/// checks failed
///
/// Swift 6.2 behavior:
/// - `swift_task_isCurrentExecutorImpl` will attempt to call the *non-crashing*
/// `isIsolatingCurrentContext` and return its result
/// - if not available, it will invoke the the *crashing* 'checkIsolated'
///
/// This can be overridden by using `SWIFT_UNEXPECTED_EXECUTOR_LOG_LEVEL=1`
/// or `SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash|nocrash`
/// or `SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=crash|nocrash|swift6|swift6.2`
SWIFT_RUNTIME_STDLIB_SPI
bool swift_bincompat_useLegacyNonCrashingExecutorChecks();

Expand Down
17 changes: 15 additions & 2 deletions include/swift/Runtime/Concurrency.h
Original file line number Diff line number Diff line change
Expand Up @@ -773,8 +773,8 @@ void swift_task_enqueue(Job *job, SerialExecutorRef executor);
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
void swift_task_enqueueGlobal(Job *job);

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

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

/// Invoke an executor's `isIsolatingCurrentContext` implementation;
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_isIsolatingCurrentContext(SerialExecutorRef executor);

/// Invoke a Swift executor's `isIsolatingCurrentContext` implementation; returns
/// `true` if it invoked the Swift implementation, `false` otherwise.
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
bool swift_task_invokeSwiftIsIsolatingCurrentContext(SerialExecutorRef executor);

/// A count in nanoseconds.
using JobDelay = unsigned long long;

Expand Down Expand Up @@ -1037,6 +1046,10 @@ enum swift_task_is_current_executor_flag : uint64_t {

/// The routine should assert on failure.
Assert = 0x8,

/// The routine should use 'isIsolatingCurrentContext' function on the
/// 'expected' executor instead of `checkIsolated`.
HasIsIsolatingCurrentContext = 0x10,
};

SWIFT_EXPORT_FROM(swift_Concurrency)
Expand Down
6 changes: 5 additions & 1 deletion include/swift/Runtime/ConcurrencyHooks.def
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,11 @@ SWIFT_CONCURRENCY_HOOK(void, swift_task_enqueueGlobalWithDeadline,
long long tnsec,
int clock, Job *job);

SWIFT_CONCURRENCY_HOOK(void, swift_task_checkIsolated, SerialExecutorRef executor);
SWIFT_CONCURRENCY_HOOK(void, swift_task_checkIsolated,
SerialExecutorRef executor);

SWIFT_CONCURRENCY_HOOK(bool, swift_task_isIsolatingCurrentContext,
SerialExecutorRef executor);

SWIFT_CONCURRENCY_HOOK(bool, swift_task_isOnExecutor,
HeapObject *executor,
Expand Down
162 changes: 133 additions & 29 deletions stdlib/public/Concurrency/Actor.cpp

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion stdlib/public/Concurrency/ConcurrencyHooks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ swift::swift_task_enqueueGlobalWithDeadline(

SWIFT_CC(swift) static void
swift_task_checkIsolatedOrig(SerialExecutorRef executor) {
swift_task_checkIsolatedImpl(*reinterpret_cast<SwiftExecutorRef *>(&executor));}
swift_task_checkIsolatedImpl(*reinterpret_cast<SwiftExecutorRef *>(&executor));
}

void
swift::swift_task_checkIsolated(SerialExecutorRef executor) {
Expand All @@ -110,6 +111,20 @@ swift::swift_task_checkIsolated(SerialExecutorRef executor) {
swift_task_checkIsolatedOrig(executor);
}

SWIFT_CC(swift) static bool
swift_task_isIsolatingCurrentContextOrig(SerialExecutorRef executor) {
return swift_task_isIsolatingCurrentContextImpl(
*reinterpret_cast<SwiftExecutorRef *>(&executor));
}

bool
swift::swift_task_isIsolatingCurrentContext(SerialExecutorRef executor) {
if (SWIFT_UNLIKELY(swift_task_isIsolatingCurrentContext_hook))
return swift_task_isIsolatingCurrentContext_hook(executor, swift_task_isIsolatingCurrentContextOrig);
else
return swift_task_isIsolatingCurrentContextOrig(executor);
}

// Implemented in Swift because we need to obtain the user-defined flags on the executor ref.
//
// We could inline this with effort, though.
Expand Down
5 changes: 5 additions & 0 deletions stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,11 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
swift_executor_invokeSwiftCheckIsolated(executor);
}

SWIFT_CC(swift)
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
}

/// Insert a job into the cooperative global queue with a delay.
SWIFT_CC(swift)
void swift_task_enqueueGlobalWithDelayImpl(SwiftJobDelay delay,
Expand Down
6 changes: 6 additions & 0 deletions stdlib/public/Concurrency/DispatchGlobalExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,12 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
swift_Concurrency_fatalError(0, "Incorrect actor executor assumption");
}

SWIFT_CC(swift)
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
}


SWIFT_CC(swift)
SwiftExecutorRef swift_task_getMainExecutorImpl() {
return swift_executor_ordinary(
Expand Down
20 changes: 20 additions & 0 deletions stdlib/public/Concurrency/Executor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ public protocol SerialExecutor: Executor {
@available(SwiftStdlib 6.0, *)
func checkIsolated()

@available(SwiftStdlib 6.2, *)
func isIsolatingCurrentContext() -> Bool

}

@available(SwiftStdlib 6.0, *)
Expand All @@ -210,6 +213,16 @@ extension SerialExecutor {
}
}

@available(SwiftStdlib 6.2, *)
extension SerialExecutor {

@available(SwiftStdlib 6.2, *)
public func isIsolatingCurrentContext() -> Bool {
self.checkIsolated()
return true
}
}

/// An executor that may be used as preferred executor by a task.
///
/// ### Impact of setting a task executor preference
Expand Down Expand Up @@ -467,6 +480,13 @@ internal func _task_serialExecutor_checkIsolated<E>(executor: E)
executor.checkIsolated()
}

@available(SwiftStdlib 6.2, *)
@_silgen_name("_task_serialExecutor_isIsolatingCurrentContext")
internal func _task_serialExecutor_isIsolatingCurrentContext<E>(executor: E) -> Bool
where E: SerialExecutor {
return executor.isIsolatingCurrentContext()
}

/// Obtain the executor ref by calling the executor's `asUnownedSerialExecutor()`.
/// The obtained executor ref will have all the user-defined flags set on the executor.
@available(SwiftStdlib 5.9, *)
Expand Down
11 changes: 11 additions & 0 deletions stdlib/public/Concurrency/ExecutorImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,14 @@ swift_executor_invokeSwiftCheckIsolated(SwiftExecutorRef executor) {
return _swift_task_invokeSwiftCheckIsolated_c(executor);
}

/// Check if the current context is isolated by the specified executor.
static inline bool
swift_executor_invokeSwiftIsIsolatingCurrentContext(SwiftExecutorRef executor) {
extern bool _swift_task_invokeSwiftIsIsolatingCurrentContext_c(SwiftExecutorRef executor);

return _swift_task_invokeSwiftIsIsolatingCurrentContext_c(executor);
}

/// Execute the specified job while running on the specified executor.
static inline void swift_job_run(SwiftJob *job, SwiftExecutorRef executor) {
extern void _swift_job_run_c(SwiftJob *job, SwiftExecutorRef executor);
Expand Down Expand Up @@ -276,6 +284,9 @@ SWIFT_CC(swift) void swift_task_enqueueMainExecutorImpl(SwiftJob *job);
/// Assert that the specified executor is the current executor.
SWIFT_CC(swift) void swift_task_checkIsolatedImpl(SwiftExecutorRef executor);

/// Check if the specified executor isolates the current context.
SWIFT_CC(swift) bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor);

/// Get a reference to the main executor.
SWIFT_CC(swift) SwiftExecutorRef swift_task_getMainExecutorImpl(void);

Expand Down
31 changes: 27 additions & 4 deletions stdlib/public/Concurrency/GlobalExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,10 @@
using namespace swift;

extern "C" SWIFT_CC(swift)
void _task_serialExecutor_checkIsolated(
HeapObject *executor,
const Metadata *selfType,
const SerialExecutorWitnessTable *wtable);
void _task_serialExecutor_checkIsolated(
HeapObject *executor,
const Metadata *selfType,
const SerialExecutorWitnessTable *wtable);

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


extern "C" SWIFT_CC(swift)
bool _task_serialExecutor_isIsolatingCurrentContext(
HeapObject *executor,
const Metadata *selfType,
const SerialExecutorWitnessTable *wtable);

SWIFT_CC(swift)
bool swift::swift_task_invokeSwiftIsIsolatingCurrentContext(SerialExecutorRef executor)
{
if (!executor.hasSerialExecutorWitnessTable())
return false;

return _task_serialExecutor_isIsolatingCurrentContext(
executor.getIdentity(), swift_getObjectType(executor.getIdentity()),
executor.getSerialExecutorWitnessTable());
}

extern "C" bool _swift_task_invokeSwiftIsIsolatingCurrentContext_c(SwiftExecutorRef executor)
{
return swift_task_invokeSwiftIsIsolatingCurrentContext(*reinterpret_cast<SerialExecutorRef *>(&executor));
}

extern "C" void _swift_job_run_c(SwiftJob *job, SwiftExecutorRef executor)
{
swift_job_run(reinterpret_cast<Job *>(job),
Expand Down
5 changes: 5 additions & 0 deletions stdlib/public/Concurrency/NonDispatchGlobalExecutor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,11 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
swift_executor_invokeSwiftCheckIsolated(executor);
}

SWIFT_CC(swift)
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
}

SWIFT_CC(swift)
SwiftExecutorRef swift_task_getMainExecutorImpl() {
return swift_executor_generic();
Expand Down
6 changes: 5 additions & 1 deletion stdlib/public/runtime/EnvironmentVariables.def
Original file line number Diff line number Diff line change
Expand Up @@ -124,9 +124,13 @@ VARIABLE(SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE, string, "",
"isolation is not the expected one. Some old code may rely on the "
"non-crashing behavior. This flag enables temporarily restoring the "
"legacy 'nocrash' behavior until adopting code has been adjusted. "
"It is possible to force the use of Swift 6.2's "
"isIsolatingCurrentContext instead of checkIsolated by passing "
" the 'swift6.2' configuration value. "
"Legal values are: "
" 'legacy' (Legacy behavior), "
" 'swift6' (Swift 6.0+ behavior)")
" 'swift6' (Swift 6.0-6.1 behavior)"
" 'swift6.2' (Swift 6.2 behavior)")

VARIABLE(SWIFT_DUMP_ACCESSIBLE_FUNCTIONS, bool, false,
"Dump a listing of all 'AccessibleFunctionRecord's upon first access. "
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// RUN: %empty-directory(%t)
// RUN: %target-build-swift %import-libdispatch -parse-as-library %s -o %t/a.out
// RUN: %target-codesign %t/a.out
// RUN: %env-SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE=swift6.2 %target-run %t/a.out

// REQUIRES: executable_test
// REQUIRES: concurrency
// REQUIRES: concurrency_runtime

// REQUIRES: libdispatch

// UNSUPPORTED: back_deployment_runtime
// UNSUPPORTED: back_deploy_concurrency
// UNSUPPORTED: use_os_stdlib
// UNSUPPORTED: freestanding

@available(SwiftStdlib 6.2, *)
final class NaiveQueueExecutor: SerialExecutor {
init() {}

func enqueue(_ job: consuming ExecutorJob) {
job.runSynchronously(on: self.asUnownedSerialExecutor())
}

func asUnownedSerialExecutor() -> UnownedSerialExecutor {
UnownedSerialExecutor(ordinary: self)
}

func checkIsolated() {
fatalError("Should not call this: checkIsolated")
}

func isIsolatingCurrentContext() -> Bool {
print("pretend it is ok: isIsolatingCurrentContext")
return true
}
}

@available(SwiftStdlib 6.2, *)
actor ActorOnNaiveQueueExecutor {
let executor: NaiveQueueExecutor

init() {
self.executor = NaiveQueueExecutor()
}

nonisolated var unownedExecutor: UnownedSerialExecutor {
self.executor.asUnownedSerialExecutor()
}

nonisolated func checkPreconditionIsolated() async {
print("Before preconditionIsolated")
self.preconditionIsolated()
print("After preconditionIsolated")

print("Before assumeIsolated")
self.assumeIsolated { iso in
print("Inside assumeIsolated")
}
print("After assumeIsolated")
}
}

@main struct Main {
static func main() async {
if #available(SwiftStdlib 6.2, *) {
let actor = ActorOnNaiveQueueExecutor()
await actor.checkPreconditionIsolated()
// CHECK: Before preconditionIsolated
// CHECK-NOT: Should not call this: checkIsolated
// CHECK-NEXT: pretend it is ok: isIsolatingCurrentContext
// CHECK-NEXT: After preconditionIsolated

// CHECK-NEXT: Before assumeIsolated
// CHECK-NOT: Should not call this: checkIsolated
// CHECK-NEXT: pretend it is ok: isIsolatingCurrentContext
// CHECK-NEXT: After assumeIsolated
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// will be able to have this behavior, however new apps will not. We use the
// overrides to test the logic for legacy code remains functional.
//
// 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
// 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

// REQUIRES: executable_test
// REQUIRES: concurrency
Expand Down
7 changes: 7 additions & 0 deletions test/abi/macOS/arm64/concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,13 @@ Added: _$ss27ThrowingDiscardingTaskGroupV05startC28SynchronouslyUnlessCancelled4

Added: _swift_task_startSynchronously

Added: _swift_task_invokeSwiftIsIsolatingCurrentContext
Added: _swift_task_isIsolatingCurrentContext
Added: _swift_task_isIsolatingCurrentContext_hook
Added: _$sScf25isIsolatingCurrentContextSbyFTj
Added: _$sScf25isIsolatingCurrentContextSbyFTq
Added: _$sScfsE25isIsolatingCurrentContextSbyF

// add callee-allocated coro entrypoints
// TODO: CoroutineAccessors: several of these symbols should be in swiftCore
Added: __swift_coro_malloc_allocator
Expand Down
7 changes: 7 additions & 0 deletions test/abi/macOS/x86_64/concurrency.swift
Original file line number Diff line number Diff line change
Expand Up @@ -401,6 +401,13 @@ Added: _$ss27ThrowingDiscardingTaskGroupV05startC28SynchronouslyUnlessCancelled4

Added: _swift_task_startSynchronously

Added: _swift_task_invokeSwiftIsIsolatingCurrentContext
Added: _swift_task_isIsolatingCurrentContext
Added: _swift_task_isIsolatingCurrentContext_hook
Added: _$sScf25isIsolatingCurrentContextSbyFTj
Added: _$sScf25isIsolatingCurrentContextSbyFTq
Added: _$sScfsE25isIsolatingCurrentContextSbyF

// add callee-allocated coro entrypoints
// TODO: CoroutineAccessors: several of these symbols should be in swiftCore
Added: __swift_coro_malloc_allocator
Expand Down
6 changes: 6 additions & 0 deletions test/embedded/Inputs/executor.c
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,12 @@ void swift_task_checkIsolatedImpl(SwiftExecutorRef executor) {
swift_executor_invokeSwiftCheckIsolated(executor);
}

/// Check if the specified executor is the current executor.
SWIFT_CC(swift)
bool swift_task_isIsolatingCurrentContextImpl(SwiftExecutorRef executor) {
return swift_executor_invokeSwiftIsIsolatingCurrentContext(executor);
}

/// Get a reference to the main executor.
SWIFT_CC(swift)
SwiftExecutorRef swift_task_getMainExecutorImpl() {
Expand Down
Loading