diff --git a/include/swift/Runtime/Bincompat.h b/include/swift/Runtime/Bincompat.h index 8e5845e2f96d7..38367682682d1 100644 --- a/include/swift/Runtime/Bincompat.h +++ b/include/swift/Runtime/Bincompat.h @@ -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(); diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index 8d6b67f2cfbef..55995b0926f0b 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -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); @@ -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; @@ -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) diff --git a/include/swift/Runtime/ConcurrencyHooks.def b/include/swift/Runtime/ConcurrencyHooks.def index 6bcc4c5bec38c..960753e4411ff 100644 --- a/include/swift/Runtime/ConcurrencyHooks.def +++ b/include/swift/Runtime/ConcurrencyHooks.def @@ -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, diff --git a/stdlib/public/Concurrency/Actor.cpp b/stdlib/public/Concurrency/Actor.cpp index 5a8a803b69e6d..d9a493590563e 100644 --- a/stdlib/public/Concurrency/Actor.cpp +++ b/stdlib/public/Concurrency/Actor.cpp @@ -353,13 +353,15 @@ enum IsCurrentExecutorCheckMode : unsigned { // Shimming call to Swift runtime because Swift Embedded does not have // these symbols defined. -bool __swift_bincompat_useLegacyNonCrashingExecutorChecks() { +swift_task_is_current_executor_flag +__swift_bincompat_useLegacyNonCrashingExecutorChecks() { #if !SWIFT_CONCURRENCY_EMBEDDED - return swift::runtime::bincompat:: - swift_bincompat_useLegacyNonCrashingExecutorChecks(); -#else - return false; + if (swift::runtime::bincompat:: + swift_bincompat_useLegacyNonCrashingExecutorChecks()) { + return swift_task_is_current_executor_flag::None; + } #endif + return swift_task_is_current_executor_flag::Assert; } // Shimming call to Swift runtime because Swift Embedded does not have @@ -376,22 +378,46 @@ const char *__swift_runtime_env_useLegacyNonCrashingExecutorChecks() { // Done this way because of the interaction with the initial value of // 'unexpectedExecutorLogLevel' -bool swift_bincompat_useLegacyNonCrashingExecutorChecks() { - bool legacyMode = __swift_bincompat_useLegacyNonCrashingExecutorChecks(); - +swift_task_is_current_executor_flag swift_bincompat_useLegacyNonCrashingExecutorChecks() { + swift_task_is_current_executor_flag options = + __swift_bincompat_useLegacyNonCrashingExecutorChecks(); // Potentially, override the platform detected mode, primarily used in tests. if (const char *modeStr = __swift_runtime_env_useLegacyNonCrashingExecutorChecks()) { + + if (strlen(modeStr) == 0) { + return swift_task_is_current_executor_flag::None; + } + if (strcmp(modeStr, "nocrash") == 0 || strcmp(modeStr, "legacy") == 0) { - return true; + // Since we're in nocrash/legacy mode: + // Remove the assert option which is what would cause the "crash" mode + options = swift_task_is_current_executor_flag( + options & ~swift_task_is_current_executor_flag::Assert); + SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => NONE; options = %d", + modeStr, options); + } else if (strcmp(modeStr, "swift6.2") == 0) { + options = swift_task_is_current_executor_flag( + options | swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext); + // When we're using the isIsolatingCurrentContext we don't want to use crashing APIs, + // so disable it explicitly. + options = swift_task_is_current_executor_flag( + options & ~swift_task_is_current_executor_flag::Assert); + SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => 6.2; options = %d", modeStr, options); } else if (strcmp(modeStr, "crash") == 0 || strcmp(modeStr, "swift6") == 0) { - return false; // don't use the legacy mode - } // else, just use the platform detected mode + options = swift_task_is_current_executor_flag( + options | swift_task_is_current_executor_flag::Assert); + SWIFT_TASK_DEBUG_LOG("executor checking: SWIFT_IS_CURRENT_EXECUTOR_LEGACY_MODE_OVERRIDE = %s => 6.9; options = %d", modeStr, options); + } +// else { // else, just use the platform detected mode +// assert(strlen(modeStr) > 0 && "why is it empty!"); +// } } // no override, use the default mode - return legacyMode; + SWIFT_TASK_DEBUG_LOG("executor checking: final options = %d", options); + return options; } // Implemented in Swift to avoid some annoying hard-coding about @@ -419,6 +445,13 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl( swift_task_is_current_executor_flag flags) { auto options = SwiftTaskIsCurrentExecutorOptions(flags); auto current = ExecutorTrackingInfo::current(); + SWIFT_TASK_DEBUG_LOG("executor checking: current task %p", current); +#ifndef NDEBUG + if (options.contains(swift_task_is_current_executor_flag::Assert)) + SWIFT_TASK_DEBUG_LOG("executor checking: active option = Assert (%d)", flags); + if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext)) + SWIFT_TASK_DEBUG_LOG("executor checking: active option = HasIsIsolatingCurrentContext (%d)", flags); +#endif if (!current) { // We have no current executor, i.e. we are running "outside" of Swift @@ -428,15 +461,30 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl( // Special handling the main executor by detecting the main thread. if (expectedExecutor.isMainExecutor() && isExecutingOnMainThread()) { + SWIFT_TASK_DEBUG_LOG("executor checking: expected is main executor & current thread is main thread => pass", nullptr); return true; } // We cannot use 'complexEquality' as it requires two executor instances, // and we do not have a 'current' executor here. + if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext)) { + SWIFT_TASK_DEBUG_LOG("executor checking mode option: HasIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext", + expectedExecutor.getIdentity()); + // The executor has the most recent 'isIsolatingCurrentContext' API + // so available so we prefer calling that to 'checkIsolated'. + auto result = swift_task_isIsolatingCurrentContext(expectedExecutor); + + SWIFT_TASK_DEBUG_LOG("executor checking mode option: HasIsIsolatingCurrentContext; invoke (%p).isIsolatingCurrentContext => %s", + expectedExecutor.getIdentity(), result ? "pass" : "fail"); + return result; + } + // Otherwise, as last resort, let the expected executor check using // external means, as it may "know" this thread is managed by it etc. if (options.contains(swift_task_is_current_executor_flag::Assert)) { + SWIFT_TASK_DEBUG_LOG("executor checking mode option: Assert; invoke (%p).expectedExecutor", + expectedExecutor.getIdentity()); swift_task_checkIsolated(expectedExecutor); // will crash if not same context // checkIsolated did not crash, so we are on the right executor, after all! @@ -448,16 +496,23 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl( } SerialExecutorRef currentExecutor = current->getActiveExecutor(); + SWIFT_TASK_DEBUG_LOG("executor checking: current executor %p %s", + currentExecutor.getIdentity(), currentExecutor.getIdentityDebugName()); // Fast-path: the executor is exactly the same memory address; // We assume executors do not come-and-go appearing under the same address, // and treat pointer equality of executors as good enough to assume the executor. if (currentExecutor == expectedExecutor) { + SWIFT_TASK_DEBUG_LOG("executor checking: current executor %p, equal to expected executor => pass", + currentExecutor.getIdentity()); return true; } // Fast-path, specialize the common case of comparing two main executors. if (currentExecutor.isMainExecutor() && expectedExecutor.isMainExecutor()) { + SWIFT_TASK_DEBUG_LOG("executor checking: current executor %p is main executor, and expected executor (%p) is main executor => pass", + currentExecutor.getIdentity(), + expectedExecutor.getIdentity()); return true; } @@ -475,8 +530,16 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl( // or confirm we actually are on the main queue; or the custom expected // executor has a chance to implement a similar queue check. if (!options.contains(swift_task_is_current_executor_flag::Assert)) { - if ((expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor()) || - (!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor())) { + if ((expectedExecutor.isMainExecutor() && !currentExecutor.isMainExecutor())) { + SWIFT_TASK_DEBUG_LOG("executor checking: expected executor %p%s is main executor, and current executor %p%s is NOT => fail", + expectedExecutor.getIdentity(), expectedExecutor.getIdentityDebugName(), + currentExecutor.getIdentity(), currentExecutor.getIdentityDebugName()); + return false; + } + if ((!expectedExecutor.isMainExecutor() && currentExecutor.isMainExecutor())) { + SWIFT_TASK_DEBUG_LOG("executor checking: expected executor %p%s is NOT main executor, and current executor %p%s is => fail", + expectedExecutor.getIdentity(), expectedExecutor.getIdentityDebugName(), + currentExecutor.getIdentity(), currentExecutor.getIdentityDebugName()); return false; } } @@ -499,6 +562,8 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl( currentExecutor.getSerialExecutorWitnessTable()), reinterpret_cast( expectedExecutor.getSerialExecutorWitnessTable()))) { + SWIFT_TASK_DEBUG_LOG("executor checking: can check isComplexEquality (%p, and %p)", + expectedExecutor.getIdentity(), currentExecutor.getIdentity()); auto isSameExclusiveExecutionContextResult = _task_serialExecutor_isSameExclusiveExecutionContext( @@ -510,11 +575,25 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl( // it and return; if it was false, we need to give checkIsolated another // chance to check. if (isSameExclusiveExecutionContextResult) { + SWIFT_TASK_DEBUG_LOG("executor checking: isComplexEquality (%p, and %p) is true => pass", + expectedExecutor.getIdentity(), currentExecutor.getIdentity()); return true; } // else, we must give 'checkIsolated' a last chance to verify isolation } } + if (options.contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext)) { + // We can invoke the 'isIsolatingCurrentContext' function. + SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext", + expectedExecutor.getIdentity()); + + bool checkResult = swift_task_isIsolatingCurrentContext(expectedExecutor); + + SWIFT_TASK_DEBUG_LOG("executor checking: can call (%p).isIsolatingCurrentContext => %p", + expectedExecutor.getIdentity(), checkResult ? "pass" : "fail"); + return checkResult; + } + // This provides a last-resort check by giving the expected SerialExecutor the // chance to perform a check using some external knowledge if perhaps we are, // after all, on this executor, but the Swift concurrency runtime was just not @@ -536,9 +615,13 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl( // synchronous, and will not cause suspensions, as that would require the // presence of a Task. if (options.contains(swift_task_is_current_executor_flag::Assert)) { + SWIFT_TASK_DEBUG_LOG("executor checking: call (%p).checkIsolated", + expectedExecutor.getIdentity()); swift_task_checkIsolated(expectedExecutor); // will crash if not same context // The checkIsolated call did not crash, so we are on the right executor. + SWIFT_TASK_DEBUG_LOG("executor checking: call (%p).checkIsolated passed => pass", + expectedExecutor.getIdentity()); return true; } @@ -550,12 +633,30 @@ static bool swift_task_isCurrentExecutorWithFlagsImpl( // Check override of executor checking mode. static void swift_task_setDefaultExecutorCheckingFlags(void *context) { - bool useLegacyMode = swift_bincompat_useLegacyNonCrashingExecutorChecks(); - auto checkMode = static_cast(context); - if (!useLegacyMode) { - *checkMode = swift_task_is_current_executor_flag( - *checkMode | swift_task_is_current_executor_flag::Assert); - } + SWIFT_TASK_DEBUG_LOG("executor checking: swift_task_setDefaultExecutorCheckingFlags = %d", nullptr); + auto useLegacyMode = swift_bincompat_useLegacyNonCrashingExecutorChecks(); + SWIFT_TASK_DEBUG_LOG("executor checking: use legacy mode = %d", useLegacyMode); + auto *options = static_cast(context); + if (useLegacyMode) { + *options = useLegacyMode; + } +// else { +// *options = swift_task_is_current_executor_flag( +// *options | swift_task_is_current_executor_flag::Assert); +// SWIFT_TASK_DEBUG_LOG("executor checking: ADD ASSERT -> %d", *options); +// } + + // FIXME: remove this + *options = swift_task_is_current_executor_flag( + *options | swift_task_is_current_executor_flag::None); + + SWIFT_TASK_DEBUG_LOG("executor checking: resulting options = %d", *options); + SWIFT_TASK_DEBUG_LOG("executor checking: option Assert = %d", + SwiftTaskIsCurrentExecutorOptions(*options) + .contains(swift_task_is_current_executor_flag::Assert)); + SWIFT_TASK_DEBUG_LOG("executor checking: option HasIsIsolatingCurrentContext = %d", + SwiftTaskIsCurrentExecutorOptions(*options) + .contains(swift_task_is_current_executor_flag::HasIsIsolatingCurrentContext)); } SWIFT_CC(swift) @@ -600,11 +701,9 @@ static void checkUnexpectedExecutorLogLevel(void *context) { long level = strtol(levelStr, nullptr, 0); if (level >= 0 && level < 3) { - if (swift_bincompat_useLegacyNonCrashingExecutorChecks()) { - // legacy mode permits doing nothing or just logging, since the method - // used to perform the check itself is not going to crash: - unexpectedExecutorLogLevel = level; - } else { + auto flag = SwiftTaskIsCurrentExecutorOptions( + swift_bincompat_useLegacyNonCrashingExecutorChecks()); + if (flag.contains(swift_task_is_current_executor_flag::Assert)) { // We are in swift6/crash mode of isCurrentExecutor which means that // rather than returning false, that method will always CRASH when an // executor mismatch is discovered. @@ -614,7 +713,13 @@ static void checkUnexpectedExecutorLogLevel(void *context) { // the crash would happen before logging or "ignoring", but this should // help avoid confusing situations like "I thought it should log" when // debugging the runtime. + SWIFT_TASK_DEBUG_LOG("executor checking: crash mode, unexpectedExecutorLogLevel = %d", level); unexpectedExecutorLogLevel = 2; + } else { + // legacy mode permits doing nothing or just logging, since the method + // used to perform the check itself is not going to crash: + SWIFT_TASK_DEBUG_LOG("executor checking: legacy mode, unexpectedExecutorLogLevel = %d", level); + unexpectedExecutorLogLevel = level; } } #endif // SWIFT_STDLIB_HAS_ENVIRON @@ -624,6 +729,7 @@ SWIFT_CC(swift) void swift::swift_task_reportUnexpectedExecutor( const unsigned char *file, uintptr_t fileLength, bool fileIsASCII, uintptr_t line, SerialExecutorRef executor) { + SWIFT_TASK_DEBUG_LOG("CHECKING swift_task_reportUnexpectedExecutor %s", ""); // Make sure we have an appropriate log level. static swift::once_t logLevelToken; swift::once(logLevelToken, checkUnexpectedExecutorLogLevel, nullptr); @@ -2296,8 +2402,7 @@ static void swift_task_switchImpl(SWIFT_ASYNC_CONTEXT AsyncContext *resumeContex : TaskExecutorRef::undefined()); auto newTaskExecutor = task->getPreferredTaskExecutor(); SWIFT_TASK_DEBUG_LOG("Task %p trying to switch executors: executor %p%s to " - "new serial executor: %p%s; task executor: from %p%s to %p%s" - "%s", + "new serial executor: %p%s; task executor: from %p%s to %p%s", task, currentExecutor.getIdentity(), currentExecutor.getIdentityDebugName(), @@ -2306,8 +2411,7 @@ static void swift_task_switchImpl(SWIFT_ASYNC_CONTEXT AsyncContext *resumeContex currentTaskExecutor.getIdentity(), currentTaskExecutor.isDefined() ? "" : " (undefined)", newTaskExecutor.getIdentity(), - newTaskExecutor.isDefined() ? "" : " (undefined)", - trackingInfo->isSynchronousStart() ? "[synchronous start]" : ""); + newTaskExecutor.isDefined() ? "" : " (undefined)"); // If the current executor is compatible with running the new executor, // we can just immediately continue running with the resume function diff --git a/stdlib/public/Concurrency/ConcurrencyHooks.cpp b/stdlib/public/Concurrency/ConcurrencyHooks.cpp index af303adb8005c..26fd5c8159954 100644 --- a/stdlib/public/Concurrency/ConcurrencyHooks.cpp +++ b/stdlib/public/Concurrency/ConcurrencyHooks.cpp @@ -100,7 +100,8 @@ swift::swift_task_enqueueGlobalWithDeadline( SWIFT_CC(swift) static void swift_task_checkIsolatedOrig(SerialExecutorRef executor) { - swift_task_checkIsolatedImpl(*reinterpret_cast(&executor));} + swift_task_checkIsolatedImpl(*reinterpret_cast(&executor)); +} void swift::swift_task_checkIsolated(SerialExecutorRef executor) { @@ -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(&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. diff --git a/stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp b/stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp index 7c2a5e9623b69..7cc503e836c25 100644 --- a/stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp +++ b/stdlib/public/Concurrency/CooperativeGlobalExecutor.cpp @@ -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, diff --git a/stdlib/public/Concurrency/DispatchGlobalExecutor.cpp b/stdlib/public/Concurrency/DispatchGlobalExecutor.cpp index 88b5c2abc18ac..58fa5b09ab524 100644 --- a/stdlib/public/Concurrency/DispatchGlobalExecutor.cpp +++ b/stdlib/public/Concurrency/DispatchGlobalExecutor.cpp @@ -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( diff --git a/stdlib/public/Concurrency/Executor.swift b/stdlib/public/Concurrency/Executor.swift index a6195139d6108..c4ff1a319dc58 100644 --- a/stdlib/public/Concurrency/Executor.swift +++ b/stdlib/public/Concurrency/Executor.swift @@ -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, *) @@ -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 @@ -467,6 +480,13 @@ internal func _task_serialExecutor_checkIsolated(executor: E) executor.checkIsolated() } +@available(SwiftStdlib 6.2, *) +@_silgen_name("_task_serialExecutor_isIsolatingCurrentContext") +internal func _task_serialExecutor_isIsolatingCurrentContext(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, *) diff --git a/stdlib/public/Concurrency/ExecutorImpl.h b/stdlib/public/Concurrency/ExecutorImpl.h index 82bd412c68d50..fe461743cb10d 100644 --- a/stdlib/public/Concurrency/ExecutorImpl.h +++ b/stdlib/public/Concurrency/ExecutorImpl.h @@ -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); @@ -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); diff --git a/stdlib/public/Concurrency/GlobalExecutor.cpp b/stdlib/public/Concurrency/GlobalExecutor.cpp index c97f5ed312843..833abb1a02f15 100644 --- a/stdlib/public/Concurrency/GlobalExecutor.cpp +++ b/stdlib/public/Concurrency/GlobalExecutor.cpp @@ -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) @@ -86,6 +86,29 @@ extern "C" bool _swift_task_invokeSwiftCheckIsolated_c(SwiftExecutorRef executor return swift_task_invokeSwiftCheckIsolated(*reinterpret_cast(&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(&executor)); +} + extern "C" void _swift_job_run_c(SwiftJob *job, SwiftExecutorRef executor) { swift_job_run(reinterpret_cast(job), diff --git a/stdlib/public/Concurrency/NonDispatchGlobalExecutor.cpp b/stdlib/public/Concurrency/NonDispatchGlobalExecutor.cpp index 236dc867ab436..d2f4439b67cae 100644 --- a/stdlib/public/Concurrency/NonDispatchGlobalExecutor.cpp +++ b/stdlib/public/Concurrency/NonDispatchGlobalExecutor.cpp @@ -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(); diff --git a/stdlib/public/runtime/EnvironmentVariables.def b/stdlib/public/runtime/EnvironmentVariables.def index d40d8e2c62866..76da3fe6e3d49 100644 --- a/stdlib/public/runtime/EnvironmentVariables.def +++ b/stdlib/public/runtime/EnvironmentVariables.def @@ -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. " diff --git a/test/Concurrency/Runtime/actor_assert_precondition_executor_isIsolatingCurrentContext_swift6_mode.swift b/test/Concurrency/Runtime/actor_assert_precondition_executor_isIsolatingCurrentContext_swift6_mode.swift new file mode 100644 index 0000000000000..99208b6201de6 --- /dev/null +++ b/test/Concurrency/Runtime/actor_assert_precondition_executor_isIsolatingCurrentContext_swift6_mode.swift @@ -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 + } + } +} diff --git a/test/Concurrency/Runtime/data_race_detection_legacy_warning.swift b/test/Concurrency/Runtime/data_race_detection_legacy_warning.swift index beeb40748fc8a..3c2c85fff9f65 100644 --- a/test/Concurrency/Runtime/data_race_detection_legacy_warning.swift +++ b/test/Concurrency/Runtime/data_race_detection_legacy_warning.swift @@ -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 diff --git a/test/abi/macOS/arm64/concurrency.swift b/test/abi/macOS/arm64/concurrency.swift index 8d69298c2fd12..2d88c8fa34574 100644 --- a/test/abi/macOS/arm64/concurrency.swift +++ b/test/abi/macOS/arm64/concurrency.swift @@ -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 diff --git a/test/abi/macOS/x86_64/concurrency.swift b/test/abi/macOS/x86_64/concurrency.swift index aab62f7e6dfae..197cc715d18fa 100644 --- a/test/abi/macOS/x86_64/concurrency.swift +++ b/test/abi/macOS/x86_64/concurrency.swift @@ -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 diff --git a/test/embedded/Inputs/executor.c b/test/embedded/Inputs/executor.c index 793d3f0018496..7032b3af833d3 100644 --- a/test/embedded/Inputs/executor.c +++ b/test/embedded/Inputs/executor.c @@ -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() { diff --git a/unittests/runtime/CompatibilityOverrideConcurrency.cpp b/unittests/runtime/CompatibilityOverrideConcurrency.cpp index 265c96cffbe6c..b2fa56b6e0b05 100644 --- a/unittests/runtime/CompatibilityOverrideConcurrency.cpp +++ b/unittests/runtime/CompatibilityOverrideConcurrency.cpp @@ -91,6 +91,14 @@ swift_task_checkIsolated_override(SerialExecutorRef executor, Ran = true; } +SWIFT_CC(swift) +static bool +swift_task_isIsolatingCurrentContext_override(SerialExecutorRef executor, + swift_task_isIsolatingCurrentContext_original original) { + Ran = true; + return true; +} + SWIFT_CC(swift) static void swift_task_enqueueGlobalWithDelay_override( unsigned long long delay, Job *job, @@ -144,6 +152,8 @@ class CompatibilityOverrideConcurrencyTest : public ::testing::Test { swift_task_enqueueMainExecutor_override; swift_task_checkIsolated_hook = swift_task_checkIsolated_override; + swift_task_isIsolatingCurrentContext_hook = + swift_task_isIsolatingCurrentContext_override; #ifdef RUN_ASYNC_MAIN_DRAIN_QUEUE_TEST swift_task_asyncMainDrainQueue_hook = swift_task_asyncMainDrainQueue_override_fn; @@ -201,6 +211,11 @@ TEST_F(CompatibilityOverrideConcurrencyTest, swift_task_checkIsolated(SerialExecutorRef::generic()); } +TEST_F(CompatibilityOverrideConcurrencyTest, + test_swift_task_isIsolatingCurrentContext) { + swift_task_isIsolatingCurrentContext(SerialExecutorRef::generic()); +} + TEST_F(CompatibilityOverrideConcurrencyTest, test_swift_task_enqueueMainExecutor) { swift_task_enqueueMainExecutor(&fakeJob);