From 7789a0ce646ce268bb8a0e293e60046d76c28cb4 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 30 Jun 2025 17:33:36 -0700 Subject: [PATCH 1/4] [SE-0371] Back-deploy support for main-actor-isolated deinit When targeting a platform that predates the introduction of isolated deinit, make a narrow exception that allows main-actor-isolated deinit to work through a special, inlineable entrypoint that is back-deployed. This implementation 1. Calls into the real implementation when available, otherwise 2. Checks if we're on the main thread, destroying immediately when we are, otherwise 3. Creates a new task on the main actor to handle destruction. This implementation is less efficient than the implementation in the runtime, but allows us to back-deploy this functionality as far back as concurrency goes. Fixes rdar://151029118. --- include/swift/Runtime/RuntimeFunctions.def | 14 +++++++- lib/IRGen/IRGenModule.cpp | 8 +++++ lib/SILGen/SILGen.cpp | 9 +++++ lib/SILGen/SILGen.h | 2 ++ lib/SILGen/SILGenDestructor.cpp | 20 ++++++++++- lib/Sema/TypeCheckAttr.cpp | 18 +++++----- stdlib/public/Concurrency/MainActor.swift | 33 +++++++++++++++++++ .../deinit_isolation_backdeploy.swift | 15 +++++++++ test/Concurrency/nonisolated_deinit.swift | 15 ++++++++- 9 files changed, 123 insertions(+), 11 deletions(-) create mode 100644 test/Concurrency/deinit_isolation_backdeploy.swift diff --git a/include/swift/Runtime/RuntimeFunctions.def b/include/swift/Runtime/RuntimeFunctions.def index 88a2011e5c411..474b6f5506031 100644 --- a/include/swift/Runtime/RuntimeFunctions.def +++ b/include/swift/Runtime/RuntimeFunctions.def @@ -2396,13 +2396,25 @@ FUNCTION(TaskSwitchFunc, // size_t flags); FUNCTION(DeinitOnExecutorFunc, _Concurrency, swift_task_deinitOnExecutor, SwiftCC, - ConcurrencyAvailability, + IsolatedDeinitAvailability, RETURNS(VoidTy), ARGS(Int8PtrTy, Int8PtrTy, ExecutorFirstTy, ExecutorSecondTy, SizeTy), ATTRS(NoUnwind), EFFECT(RuntimeEffect::Concurrency), UNKNOWN_MEMEFFECTS) +// void swift_task_deinitOnExecutorMainActorBackDeploy(void *object, +// DeinitWorkFunction *work, +// SerialExecutorRef newExecutor, +// size_t flags); +FUNCTION(DeinitOnExecutorMainActorBackDeployFunc, + _Concurrency, deinitOnExecutorMainActorBackDeploy, SwiftCC, + ConcurrencyAvailability, + RETURNS(VoidTy), + ARGS(Int8PtrTy, Int8PtrTy, ExecutorFirstTy, ExecutorSecondTy, SizeTy), + ATTRS(NoUnwind), + EFFECT(RuntimeEffect::Concurrency), + UNKNOWN_MEMEFFECTS) // AsyncTask *swift_continuation_init(AsyncContext *continuationContext, // AsyncContinuationFlags); diff --git a/lib/IRGen/IRGenModule.cpp b/lib/IRGen/IRGenModule.cpp index fb05a65c5a236..4985b1ce203d3 100644 --- a/lib/IRGen/IRGenModule.cpp +++ b/lib/IRGen/IRGenModule.cpp @@ -982,6 +982,14 @@ namespace RuntimeConstants { return RuntimeAvailability::AlwaysAvailable; } + RuntimeAvailability IsolatedDeinitAvailability(ASTContext &context) { + auto featureAvailability = context.getIsolatedDeinitAvailability(); + if (!isDeploymentAvailabilityContainedIn(context, featureAvailability)) { + return RuntimeAvailability::ConditionallyAvailable; + } + return RuntimeAvailability::AlwaysAvailable; + } + RuntimeAvailability MultiPayloadEnumTagSinglePayloadAvailability(ASTContext &context) { auto featureAvailability = context.getMultiPayloadEnumTagSinglePayload(); diff --git a/lib/SILGen/SILGen.cpp b/lib/SILGen/SILGen.cpp index 71d59db986025..148a90ffa3f2f 100644 --- a/lib/SILGen/SILGen.cpp +++ b/lib/SILGen/SILGen.cpp @@ -462,6 +462,15 @@ FuncDecl *SILGenModule::getDeinitOnExecutor() { return lookupConcurrencyIntrinsic(getASTContext(), "_deinitOnExecutor"); } +FuncDecl *SILGenModule::getDeinitOnExecutorMainActorBackDeploy() { + auto found = lookupConcurrencyIntrinsic(getASTContext(), + "_deinitOnExecutorMainActorBackDeploy"); + if (found) + return found; + + return getDeinitOnExecutor(); +} + FuncDecl *SILGenModule::getCreateExecutors() { return lookupConcurrencyIntrinsic(getASTContext(), "_createExecutors"); } diff --git a/lib/SILGen/SILGen.h b/lib/SILGen/SILGen.h index 10fc5c358b390..6b75a571c2d89 100644 --- a/lib/SILGen/SILGen.h +++ b/lib/SILGen/SILGen.h @@ -557,6 +557,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenModule : public ASTVisitor { FuncDecl *getSwiftJobRun(); /// Retrieve the _Concurrency._deinitOnExecutor intrinsic. FuncDecl *getDeinitOnExecutor(); + /// Retrieve the _Concurrency._deinitOnExecutorMainActorBackDeploy intrinsic. + FuncDecl *getDeinitOnExecutorMainActorBackDeploy(); // Retrieve the _SwiftConcurrencyShims.exit intrinsic. FuncDecl *getExit(); diff --git a/lib/SILGen/SILGenDestructor.cpp b/lib/SILGen/SILGenDestructor.cpp index e4816d5a3dcf1..0ae90b2db278c 100644 --- a/lib/SILGen/SILGenDestructor.cpp +++ b/lib/SILGen/SILGenDestructor.cpp @@ -339,6 +339,17 @@ void SILGenFunction::emitDeallocatingMoveOnlyDestructor(DestructorDecl *dd) { B.createReturn(loc, emitEmptyTuple(loc)); } +/// Determine whether the availability for the body the given destructor predates the introduction of +/// support for isolated deinit. +static bool availabilityPredatesIsolatedDeinit(DestructorDecl *dd) { + ASTContext &ctx = dd->getASTContext(); + if (ctx.LangOpts.DisableAvailabilityChecking) + return false; + + auto deploymentAvailability = AvailabilityRange::forDeploymentTarget(ctx); + return !deploymentAvailability.isContainedIn(ctx.getIsolatedDeinitAvailability()); +} + void SILGenFunction::emitIsolatingDestructor(DestructorDecl *dd) { MagicFunctionName = DeclName(SGM.M.getASTContext().getIdentifier("deinit")); @@ -372,8 +383,15 @@ void SILGenFunction::emitIsolatingDestructor(DestructorDecl *dd) { executor = B.createExtractExecutor(loc, actor); } + // Determine whether we need the main-actor back-deployment version of + // this function. + bool useMainActorBackDeploy = + ai.isMainActor() && availabilityPredatesIsolatedDeinit(dd); + // Get deinitOnExecutor - FuncDecl *swiftDeinitOnExecutorDecl = SGM.getDeinitOnExecutor(); + FuncDecl *swiftDeinitOnExecutorDecl = + useMainActorBackDeploy ? SGM.getDeinitOnExecutorMainActorBackDeploy() + : SGM.getDeinitOnExecutor(); if (!swiftDeinitOnExecutorDecl) { dd->diagnose(diag::missing_deinit_on_executor_function); return; diff --git a/lib/Sema/TypeCheckAttr.cpp b/lib/Sema/TypeCheckAttr.cpp index 2873f11a02535..123aea0b9ed0e 100644 --- a/lib/Sema/TypeCheckAttr.cpp +++ b/lib/Sema/TypeCheckAttr.cpp @@ -123,15 +123,17 @@ class AttributeChecker : public AttributeVisitor { diagnoseAndRemoveAttr(attr, diag::isolated_deinit_on_value_type); return; } - } - TypeChecker::checkAvailability( - attr->getRange(), C.getIsolatedDeinitAvailability(), - D->getDeclContext(), - [&](AvailabilityDomain domain, AvailabilityRange range) { - return diagnoseAndRemoveAttr( - attr, diag::isolated_deinit_unavailable, domain, range); - }); + if (!getActorIsolation(nominal).isMainActor()) { + TypeChecker::checkAvailability( + attr->getRange(), C.getIsolatedDeinitAvailability(), + D->getDeclContext(), + [&](AvailabilityDomain domain, AvailabilityRange range) { + return diagnoseAndRemoveAttr( + attr, diag::isolated_deinit_unavailable, domain, range); + }); + } + } } } diff --git a/stdlib/public/Concurrency/MainActor.swift b/stdlib/public/Concurrency/MainActor.swift index cddb4f83162b6..3220b966a639e 100644 --- a/stdlib/public/Concurrency/MainActor.swift +++ b/stdlib/public/Concurrency/MainActor.swift @@ -164,4 +164,37 @@ extension MainActor { } #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY + +#if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) +@_silgen_name("pthread_main_np") +@usableFromInline +internal func pthread_main_np() -> CInt + +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +@_silgen_name("swift_task_deinitOnExecutorMainActorBackDeploy") +public func _deinitOnExecutorMainActorBackDeploy( + _ object: __owned AnyObject, + _ work: @convention(thin) (__owned AnyObject) -> Void, + _ executor: Builtin.Executor, + _ flags: Builtin.Word) { + if #available(macOS 15.4, iOS 18.4, watchOS 11.4, tvOS 18.4, visionOS 2.4, *) { + // On new-enough platforms, use the runtime functionality, which allocates + // the task more efficiently. + _deinitOnExecutor(object, work, executor, flags) + } else if pthread_main_np() == 1 { + // Using "main thread" as a proxy for "main actor", immediately destroy + // the object. + work(consume object) + } else { + // Steal the local object so that the reference count stays at 1 even when + // the object is captured. + var stolenObject: AnyObject? = consume object + Task.detached { @MainActor in + work(stolenObject.take()!) + } + } +} +#endif + #endif // !$Embedded diff --git a/test/Concurrency/deinit_isolation_backdeploy.swift b/test/Concurrency/deinit_isolation_backdeploy.swift new file mode 100644 index 0000000000000..99ebe9971228e --- /dev/null +++ b/test/Concurrency/deinit_isolation_backdeploy.swift @@ -0,0 +1,15 @@ +// RUN: %target-swift-frontend -target %target-swift-5.1-abi-triple -parse-as-library -emit-silgen -DSILGEN %s | %FileCheck %s + +// REQUIRES: concurrency +// REQUIRES: OS=macosx + + +@MainActor +class C { + // CHECK-LABEL: sil hidden [ossa] @$s27deinit_isolation_backdeploy1CCfD : $@convention(method) (@owned C) -> () + // CHECK: function_ref @swift_task_deinitOnExecutorMainActorBackDeploy + isolated deinit { } +} + +// Make sure this function is available +// CHECK: sil hidden_external [serialized] [available 12.0.0] @swift_task_deinitOnExecutorMainActorBackDeploy : $@convention(thin) (@owned AnyObject, @convention(thin) (@owned AnyObject) -> (), Builtin.Executor, Builtin.Word) -> () diff --git a/test/Concurrency/nonisolated_deinit.swift b/test/Concurrency/nonisolated_deinit.swift index 57010a7e93aa6..62296d7cbc29d 100644 --- a/test/Concurrency/nonisolated_deinit.swift +++ b/test/Concurrency/nonisolated_deinit.swift @@ -13,11 +13,24 @@ class NotSendable {} } } +@globalActor +actor SomeGlobalActor { + static let shared = SomeGlobalActor() +} + // expected-note@+1{{add '@available' attribute to enclosing class}} -@MainActor class C2 { +@SomeGlobalActor class C2 { var x: Int = 0 isolated deinit { // expected-error{{isolated deinit is only available in macOS 15.4.0 or newer}} print(x) } } + +@MainActor class C3 { + var x: Int = 0 + + isolated deinit { // okay, this back-deploys + print(x) + } +} From 642214d6cb89b83a1459eaa9eee7a4672dd0cbab Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Mon, 30 Jun 2025 20:51:18 -0700 Subject: [PATCH 2/4] Update test --- test/api-digester/stability-concurrency-abi.test | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/api-digester/stability-concurrency-abi.test b/test/api-digester/stability-concurrency-abi.test index c9132ed6298ee..8e8e24505f24f 100644 --- a/test/api-digester/stability-concurrency-abi.test +++ b/test/api-digester/stability-concurrency-abi.test @@ -128,6 +128,8 @@ Func withThrowingTaskGroup(of:returning:body:) has parameter 2 type change from Func withTaskGroup(of:returning:body:) has been renamed to Func withTaskGroup(of:returning:isolation:body:) Func withTaskGroup(of:returning:body:) has mangled name changing from '_Concurrency.withTaskGroup(of: A.Type, returning: B.Type, body: (inout Swift.TaskGroup) async -> B) async -> B' to '_Concurrency.withTaskGroup(of: A.Type, returning: B.Type, isolation: isolated Swift.Optional, body: (inout Swift.TaskGroup) async -> B) async -> B' +Func pthread_main_np() is a new API without '@available' + // *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.) From f73667702283fecd281a022f2d659863a38b2579 Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 1 Jul 2025 08:15:54 -0700 Subject: [PATCH 3/4] Switch to the appropriate C calling convention for pthread_main_np --- Runtimes/Core/Concurrency/CMakeLists.txt | 1 + stdlib/public/Concurrency/CMakeLists.txt | 2 ++ stdlib/public/Concurrency/MainActor.swift | 2 +- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Runtimes/Core/Concurrency/CMakeLists.txt b/Runtimes/Core/Concurrency/CMakeLists.txt index 1cf919497009e..a0ab4203e4534 100644 --- a/Runtimes/Core/Concurrency/CMakeLists.txt +++ b/Runtimes/Core/Concurrency/CMakeLists.txt @@ -113,6 +113,7 @@ target_compile_options(swift_Concurrency PRIVATE # NOTE: do not remove until `IsolatedAny` is on by default in all supported # compilers. "$<$:SHELL:-enable-experimental-feature IsolatedAny>" + "$<$:SHELL:-enable-experimental-feature Extern>" # NOTE: enable the async frame pointer on Darwin to faciliate debugging. $<$,$>:-fswift-async-fp=always> "$<$,$>:SHELL:-Xfrontend -swift-async-frame-pointer=always>" diff --git a/stdlib/public/Concurrency/CMakeLists.txt b/stdlib/public/Concurrency/CMakeLists.txt index 58c6da6a61473..aaffe57efe2e1 100644 --- a/stdlib/public/Concurrency/CMakeLists.txt +++ b/stdlib/public/Concurrency/CMakeLists.txt @@ -68,6 +68,8 @@ list(APPEND SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS list(APPEND SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS "-strict-memory-safety") list(APPEND SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS "-enable-experimental-feature" "AllowUnsafeAttribute") +list(APPEND SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS "-enable-experimental-feature" "Extern") + list(APPEND SWIFT_RUNTIME_CONCURRENCY_C_FLAGS "-D__STDC_WANT_LIB_EXT1__=1") diff --git a/stdlib/public/Concurrency/MainActor.swift b/stdlib/public/Concurrency/MainActor.swift index 3220b966a639e..ffc28e96718a9 100644 --- a/stdlib/public/Concurrency/MainActor.swift +++ b/stdlib/public/Concurrency/MainActor.swift @@ -166,7 +166,7 @@ extension MainActor { #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) -@_silgen_name("pthread_main_np") +@_extern(c, "pthread_main_np") @usableFromInline internal func pthread_main_np() -> CInt From bfb62e12620448381c249d196778f9b81c1414fd Mon Sep 17 00:00:00 2001 From: Doug Gregor Date: Tue, 1 Jul 2025 16:06:05 -0700 Subject: [PATCH 4/4] Don't implement swift_task_deinitOnExecutorMainActorBackDeploy in the task-to-thread model --- stdlib/public/Concurrency/MainActor.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/public/Concurrency/MainActor.swift b/stdlib/public/Concurrency/MainActor.swift index ffc28e96718a9..e60a1c766b428 100644 --- a/stdlib/public/Concurrency/MainActor.swift +++ b/stdlib/public/Concurrency/MainActor.swift @@ -162,8 +162,6 @@ extension MainActor { try assumeIsolated(operation, file: file, line: line) } } -#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) || os(visionOS) @_extern(c, "pthread_main_np") @@ -197,4 +195,6 @@ public func _deinitOnExecutorMainActorBackDeploy( } #endif +#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY + #endif // !$Embedded