From 640a71dc6fdb25017edb3e4ba98defcec6db1740 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 10 Feb 2023 20:38:39 +0900 Subject: [PATCH 1/9] [Concurrency] Move-only Job, in addition to UnownedJob --- include/swift/ABI/Task.h | 4 ++ include/swift/Runtime/Concurrency.h | 5 ++ stdlib/cmake/modules/AddSwiftStdlib.cmake | 2 +- .../BackDeployConcurrency/Executor.swift | 1 + stdlib/public/Concurrency/CMakeLists.txt | 1 + stdlib/public/Concurrency/Executor.swift | 46 ++++++++++++-- stdlib/public/Concurrency/GlobalExecutor.cpp | 11 ++++ .../public/Concurrency/PartialAsyncTask.swift | 20 +++++- .../custom_executors_moveOnly_job.swift | 62 +++++++++++++++++++ .../Runtime/custom_executors_priority.swift | 6 +- .../Runtime/custom_executors_protocol.swift | 8 ++- 11 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 test/Concurrency/Runtime/custom_executors_moveOnly_job.swift diff --git a/include/swift/ABI/Task.h b/include/swift/ABI/Task.h index 94bd97f60a623..afe7a1b832e34 100644 --- a/include/swift/ABI/Task.h +++ b/include/swift/ABI/Task.h @@ -136,6 +136,10 @@ class alignas(2 * alignof(void*)) Job : return Flags.getPriority(); } + uint32_t getJobId() const { + return Id; + } + /// Given that we've fully established the job context in the current /// thread, actually start running this job. To establish the context /// correctly, call swift_job_run or runJobInExecutorContext. diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index ee8a4bdcc46d8..46faae8b54d73 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -715,6 +715,11 @@ bool swift_task_isOnExecutor( const Metadata *selfType, const SerialExecutorWitnessTable *wtable); +/// Return the 64bit TaskID (if the job is an AsyncTask), +/// or the 32bits of the job Id otherwise. +SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) +uint64_t swift_task_getJobTaskId(Job *job); + #if SWIFT_CONCURRENCY_ENABLE_DISPATCH /// Enqueue the given job on the main executor. diff --git a/stdlib/cmake/modules/AddSwiftStdlib.cmake b/stdlib/cmake/modules/AddSwiftStdlib.cmake index 365b175587abe..cb4f09b30c057 100644 --- a/stdlib/cmake/modules/AddSwiftStdlib.cmake +++ b/stdlib/cmake/modules/AddSwiftStdlib.cmake @@ -1810,7 +1810,7 @@ function(add_swift_target_library name) if (SWIFTLIB_IS_STDLIB) list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-warn-implicit-overrides") list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-ossa-modules") - list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-lexical-lifetimes=false") + list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-lexical-lifetimes=true") # FIXME: undo this endif() if(NOT SWIFT_BUILD_RUNTIME_WITH_HOST_COMPILER AND NOT BUILD_STANDALONE AND diff --git a/stdlib/public/BackDeployConcurrency/Executor.swift b/stdlib/public/BackDeployConcurrency/Executor.swift index 13e2811b83ef5..b7b5b3a751bf2 100644 --- a/stdlib/public/BackDeployConcurrency/Executor.swift +++ b/stdlib/public/BackDeployConcurrency/Executor.swift @@ -74,6 +74,7 @@ public struct UnownedSerialExecutor: Sendable { @_silgen_name("_swift_task_enqueueOnExecutor") internal func _enqueueOnExecutor(job: UnownedJob, executor: E) where E: SerialExecutor { + // TODO: something to do here? executor.enqueue(job) } diff --git a/stdlib/public/Concurrency/CMakeLists.txt b/stdlib/public/Concurrency/CMakeLists.txt index 9f6be0a4c0dd5..7675aa8a1a478 100644 --- a/stdlib/public/Concurrency/CMakeLists.txt +++ b/stdlib/public/Concurrency/CMakeLists.txt @@ -149,6 +149,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I ${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS} -parse-stdlib -Xfrontend -enable-experimental-concurrency + -Xfrontend -enable-experimental-move-only # FIXME: REMOVE THIS -diagnostic-style swift ${SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS} ${swift_concurrency_options} diff --git a/stdlib/public/Concurrency/Executor.swift b/stdlib/public/Concurrency/Executor.swift index 5dfe32b769b41..27f53b90b40d0 100644 --- a/stdlib/public/Concurrency/Executor.swift +++ b/stdlib/public/Concurrency/Executor.swift @@ -15,24 +15,44 @@ import Swift /// A service that can execute jobs. @available(SwiftStdlib 5.1, *) public protocol Executor: AnyObject, Sendable { + // This requirement is repeated here as a non-override so that we + // get a redundant witness-table entry for it. This allows us to + // avoid drilling down to the base conformance just for the basic + // work-scheduling operation. + @available(*, deprecated, message: "Implement enqueueJob instead") func enqueue(_ job: UnownedJob) + + @available(SwiftStdlib 5.9, *) + func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way } /// A service that executes jobs. @available(SwiftStdlib 5.1, *) public protocol SerialExecutor: Executor { - // This requirement is repeated here as a non-override so that we - // get a redundant witness-table entry for it. This allows us to - // avoid drilling down to the base conformance just for the basic - // work-scheduling operation. + @_nonoverride + @available(*, deprecated, message: "Implement enqueueJob instead") func enqueue(_ job: UnownedJob) + @_nonoverride + @available(SwiftStdlib 5.9, *) + func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way + /// Convert this executor value to the optimized form of borrowed /// executor references. func asUnownedSerialExecutor() -> UnownedSerialExecutor } +@available(SwiftStdlib 5.9, *) +extension Executor { + public func enqueue(_ job: UnownedJob) { // FIXME: this is bad; how could we deploy this nicely + fatalError("Please implement \(Self.self).enqueueJob(_:)") + } + public func enqueueJob(_ job: __owned Job) { + self.enqueue(UnownedJob(job)) + } +} + /// An unowned reference to a serial executor (a `SerialExecutor` /// value). /// @@ -88,12 +108,26 @@ public struct UnownedSerialExecutor: Sendable { @_silgen_name("swift_task_isOnExecutor") public func _taskIsOnExecutor(_ executor: Executor) -> Bool +/// Primarily a debug utility. +/// +/// If the passed in Job is a Task, returns the complete 64bit TaskId, +/// otherwise returns only the job's 32bit Id. +/// +/// - Returns: the Id stored in this Job or Task, for purposes of debug printing +@available(SwiftStdlib 5.9, *) +@_silgen_name("swift_task_getJobTaskId") +internal func _getJobTaskId(_ job: UnownedJob) -> UInt64 + // Used by the concurrency runtime @available(SwiftStdlib 5.1, *) @_silgen_name("_swift_task_enqueueOnExecutor") -internal func _enqueueOnExecutor(job: UnownedJob, executor: E) +internal func _enqueueOnExecutor(job unownedJob: UnownedJob, executor: E) where E: SerialExecutor { - executor.enqueue(job) + if #available(SwiftStdlib 5.9, *) { + executor.enqueueJob(Job(context: unownedJob.context)) + } else { + executor.enqueue(unownedJob) + } } @available(SwiftStdlib 5.1, *) diff --git a/stdlib/public/Concurrency/GlobalExecutor.cpp b/stdlib/public/Concurrency/GlobalExecutor.cpp index 52ad68f801c04..f58682b335734 100644 --- a/stdlib/public/Concurrency/GlobalExecutor.cpp +++ b/stdlib/public/Concurrency/GlobalExecutor.cpp @@ -150,6 +150,17 @@ bool swift::swift_task_isOnExecutor(HeapObject *executor, return swift_task_isOnExecutorImpl(executor, selfType, wtable); } +uint64_t swift::swift_task_getJobTaskId(Job *job) { + if (auto task = dyn_cast(job)) { + // TaskID is actually: + // 32bits of Job's Id + // + 32bits stored in the AsyncTask + return task->getTaskId(); + } else { + return job->getJobId(); + } +} + /*****************************************************************************/ /****************************** MAIN EXECUTOR *******************************/ /*****************************************************************************/ diff --git a/stdlib/public/Concurrency/PartialAsyncTask.swift b/stdlib/public/Concurrency/PartialAsyncTask.swift index e7835da0dffbe..c15a61187b024 100644 --- a/stdlib/public/Concurrency/PartialAsyncTask.swift +++ b/stdlib/public/Concurrency/PartialAsyncTask.swift @@ -26,7 +26,18 @@ internal func _swiftJobRun(_ job: UnownedJob, @available(SwiftStdlib 5.1, *) @frozen public struct UnownedJob: Sendable { - private var context: Builtin.Job + internal var context: Builtin.Job + + @usableFromInline + internal init(context: Builtin.Job) { + self.context = context + } + + @available(SwiftStdlib 5.9, *) + @usableFromInline + internal init(_ job: __owned Job) { + self.context = job.context + } /// The priority of this job. @available(SwiftStdlib 5.9, *) @@ -41,6 +52,13 @@ public struct UnownedJob: Sendable { public func _runSynchronously(on executor: UnownedSerialExecutor) { _swiftJobRun(self, executor) } +} + +@available(SwiftStdlib 5.9, *) +extension UnownedJob { + public struct Priority { + public typealias RawValue = UInt8 + public var rawValue: RawValue @_alwaysEmitIntoClient @inlinable diff --git a/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift b/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift new file mode 100644 index 0000000000000..2bf00910e1e87 --- /dev/null +++ b/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift @@ -0,0 +1,62 @@ +// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s + +// REQUIRES: concurrency +// REQUIRES: executable_test +// UNSUPPORTED: freestanding + +// UNSUPPORTED: back_deployment_runtime +// REQUIRES: concurrency_runtime + +final class InlineExecutor: SerialExecutor, CustomStringConvertible { + + public func enqueueJob(_ job: __owned Job) { + print("\(self): enqueue (job: \(job.description))") + runJobSynchronously(job) + print("\(self): after run") + } + + public func asUnownedSerialExecutor() -> UnownedSerialExecutor { + return UnownedSerialExecutor(ordinary: self) + } + + var description: Swift.String { + "InlineExecutor()" + } +} + +let inlineExecutor = InlineExecutor() + +actor Custom { + var count = 0 + + nonisolated var unownedExecutor: UnownedSerialExecutor { + print("custom unownedExecutor") + return inlineExecutor.asUnownedSerialExecutor() + } + + func report() async { + print("custom.count == \(count)") + count += 1 + } +} + +@available(SwiftStdlib 5.1, *) +@main struct Main { + static func main() async { + print("begin") + let actor = Custom() + await actor.report() + await actor.report() + await actor.report() + print("end") + } +} + +// CHECK: begin +// CHECK-NEXT: custom unownedExecutor +// CHECK-NEXT: custom.count == 0 +// CHECK-NEXT: custom unownedExecutor +// CHECK-NEXT: custom.count == 1 +// CHECK-NEXT: custom unownedExecutor +// CHECK-NEXT: custom.count == 2 +// CHECK-NEXT: end diff --git a/test/Concurrency/Runtime/custom_executors_priority.swift b/test/Concurrency/Runtime/custom_executors_priority.swift index fcbafd6dba6d9..940fe32120526 100644 --- a/test/Concurrency/Runtime/custom_executors_priority.swift +++ b/test/Concurrency/Runtime/custom_executors_priority.swift @@ -35,7 +35,6 @@ actor Custom { } } -@available(SwiftStdlib 5.1, *) @main struct Main { static func main() async { print("begin") @@ -46,6 +45,11 @@ actor Custom { await Task() { await actor.report() }.value + await Task(priority: .low) { + print("P: \(Task.currentPriority)") + print("B: \(Task.currentBasePriority)") + await actor.report() + }.value print("end") } } diff --git a/test/Concurrency/Runtime/custom_executors_protocol.swift b/test/Concurrency/Runtime/custom_executors_protocol.swift index 551fa2dc638f9..19a1565ba9be9 100644 --- a/test/Concurrency/Runtime/custom_executors_protocol.swift +++ b/test/Concurrency/Runtime/custom_executors_protocol.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s +// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s // REQUIRES: concurrency // REQUIRES: executable_test @@ -42,6 +42,12 @@ final class InlineExecutor: SpecifiedExecutor, Swift.CustomStringConvertible { print("\(self): after run") } + public func enqueueJob(_ job: __owned Job) { + print("\(self): enqueue") + runJobSynchronously(job) + print("\(self): after run") + } + public func asUnownedSerialExecutor() -> UnownedSerialExecutor { return UnownedSerialExecutor(ordinary: self) } From 672865de875201d5fa9560c38700ad1b9bc7f42a Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Tue, 14 Feb 2023 17:15:50 +0900 Subject: [PATCH 2/9] [Concurrency] Warn about "old" Executor.enqueue impl and prefer new move new enqueue method to be named enqueue and not enqueueOwned --- include/swift/AST/Decl.h | 3 + include/swift/AST/DiagnosticsSema.def | 5 ++ include/swift/AST/KnownProtocols.def | 1 + include/swift/AST/KnownSDKTypes.def | 4 + lib/AST/ASTContext.cpp | 1 + lib/AST/Decl.cpp | 36 +++++++++ lib/IRGen/GenMeta.cpp | 1 + lib/Sema/TypeCheckConcurrency.cpp | 73 ++++++++++++++++++ lib/Sema/TypeCheckConcurrency.h | 3 + lib/Sema/TypeCheckProtocol.cpp | 2 + stdlib/public/BackDeployConcurrency/Actor.cpp | 5 ++ .../BackDeployConcurrency/Executor.swift | 4 + stdlib/public/Concurrency/Executor.swift | 35 ++++++--- .../public/Concurrency/PartialAsyncTask.swift | 75 ++++++++++++++++++- .../Runtime/custom_executors.swift | 2 +- .../Runtime/custom_executors_default.swift | 2 +- .../custom_executors_moveOnly_job.swift | 11 +-- .../Runtime/custom_executors_priority.swift | 12 +-- .../Runtime/custom_executors_protocol.swift | 59 ++++++++++----- .../custom_executor_enqueue_impls.swift | 47 ++++++++++++ 20 files changed, 327 insertions(+), 54 deletions(-) create mode 100644 test/Concurrency/custom_executor_enqueue_impls.swift diff --git a/include/swift/AST/Decl.h b/include/swift/AST/Decl.h index a165cdea94720..1b74d2134f6d9 100644 --- a/include/swift/AST/Decl.h +++ b/include/swift/AST/Decl.h @@ -3856,6 +3856,9 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext { /// Find the 'RemoteCallArgument(label:name:value:)' initializer function. ConstructorDecl *getDistributedRemoteCallArgumentInitFunction() const; + /// Get the move-only `enqueue(Job)` protocol requirement function on the `Executor` protocol. + AbstractFunctionDecl *getExecutorOwnedEnqueueFunction() const; + /// Collect the set of protocols to which this type should implicitly /// conform, such as AnyObject (for classes). void getImplicitProtocols(SmallVectorImpl &protocols); diff --git a/include/swift/AST/DiagnosticsSema.def b/include/swift/AST/DiagnosticsSema.def index 2bd5187b39282..be5d160ecf98d 100644 --- a/include/swift/AST/DiagnosticsSema.def +++ b/include/swift/AST/DiagnosticsSema.def @@ -6408,6 +6408,11 @@ WARNING(hashvalue_implementation,Deprecation, "conform type %0 to 'Hashable' by implementing 'hash(into:)' instead", (Type)) +WARNING(executor_enqueue_unowned_implementation,Deprecation, + "'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; " + "conform type %0 to 'Executor' by implementing 'func enqueue(Job)' instead", + (Type)) + //------------------------------------------------------------------------------ // MARK: property wrapper diagnostics //------------------------------------------------------------------------------ diff --git a/include/swift/AST/KnownProtocols.def b/include/swift/AST/KnownProtocols.def index 1ecd1c395e1b2..8ab4fe5d9799c 100644 --- a/include/swift/AST/KnownProtocols.def +++ b/include/swift/AST/KnownProtocols.def @@ -76,6 +76,7 @@ PROTOCOL(SIMDScalar) PROTOCOL(BinaryInteger) PROTOCOL(FixedWidthInteger) PROTOCOL(RangeReplaceableCollection) +PROTOCOL(Executor) PROTOCOL(SerialExecutor) PROTOCOL(GlobalActor) diff --git a/include/swift/AST/KnownSDKTypes.def b/include/swift/AST/KnownSDKTypes.def index 747f1ad3d6931..f9d0c2a0c24c1 100644 --- a/include/swift/AST/KnownSDKTypes.def +++ b/include/swift/AST/KnownSDKTypes.def @@ -39,6 +39,10 @@ KNOWN_SDK_TYPE_DECL(ObjectiveC, ObjCBool, StructDecl, 0) // standardized KNOWN_SDK_TYPE_DECL(Concurrency, UnsafeContinuation, NominalTypeDecl, 2) KNOWN_SDK_TYPE_DECL(Concurrency, MainActor, NominalTypeDecl, 0) +KNOWN_SDK_TYPE_DECL(Concurrency, Job, StructDecl, 0) +KNOWN_SDK_TYPE_DECL(Concurrency, UnownedJob, StructDecl, 0) +KNOWN_SDK_TYPE_DECL(Concurrency, Executor, NominalTypeDecl, 0) +KNOWN_SDK_TYPE_DECL(Concurrency, SerialExecutor, NominalTypeDecl, 0) KNOWN_SDK_TYPE_DECL(Concurrency, UnownedSerialExecutor, NominalTypeDecl, 0) KNOWN_SDK_TYPE_DECL(Concurrency, TaskLocal, ClassDecl, 1) diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index be8926291d5d9..347a5b0853110 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -1120,6 +1120,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const { case KnownProtocolKind::GlobalActor: case KnownProtocolKind::AsyncSequence: case KnownProtocolKind::AsyncIteratorProtocol: + case KnownProtocolKind::Executor: case KnownProtocolKind::SerialExecutor: M = getLoadedModule(Id_Concurrency); break; diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index d666e93398d91..f762e373152ca 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -5247,6 +5247,42 @@ VarDecl *NominalTypeDecl::getGlobalActorInstance() const { nullptr); } +AbstractFunctionDecl * +NominalTypeDecl::getExecutorOwnedEnqueueFunction() const { + auto &C = getASTContext(); + + auto proto = dyn_cast(this); + if (!proto) + return nullptr; + + llvm::SmallVector results; + lookupQualified(getSelfNominalTypeDecl(), + DeclNameRef(C.Id_enqueue), + NL_ProtocolMembers, + results); + + for (auto candidate: results) { + // we're specifically looking for the Executor protocol requirement + if (!isa(candidate->getDeclContext())) + continue; + + fprintf(stderr, "[%s:%d](%s) candidate\n", __FILE_NAME__, __LINE__, __FUNCTION__); + candidate->dump(); + + if (auto *funcDecl = dyn_cast(candidate)) { + if (funcDecl->getParameters()->size() != 1) + continue; + + auto params = funcDecl->getParameters(); + if (params->get(0)->getSpecifier() == ParamSpecifier::Owned) { + return funcDecl; + } + } + } + + return nullptr; +} + ClassDecl::ClassDecl(SourceLoc ClassLoc, Identifier Name, SourceLoc NameLoc, ArrayRef Inherited, GenericParamList *GenericParams, DeclContext *Parent, diff --git a/lib/IRGen/GenMeta.cpp b/lib/IRGen/GenMeta.cpp index 4848b17cbb4ab..79a97ae070ef1 100644 --- a/lib/IRGen/GenMeta.cpp +++ b/lib/IRGen/GenMeta.cpp @@ -6178,6 +6178,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) { case KnownProtocolKind::CxxSequence: case KnownProtocolKind::UnsafeCxxInputIterator: case KnownProtocolKind::UnsafeCxxRandomAccessIterator: + case KnownProtocolKind::Executor: case KnownProtocolKind::SerialExecutor: case KnownProtocolKind::Sendable: case KnownProtocolKind::UnsafeSendable: diff --git a/lib/Sema/TypeCheckConcurrency.cpp b/lib/Sema/TypeCheckConcurrency.cpp index e9e4fb6ceef0b..f8a8a261a0305 100644 --- a/lib/Sema/TypeCheckConcurrency.cpp +++ b/lib/Sema/TypeCheckConcurrency.cpp @@ -1225,6 +1225,79 @@ void swift::diagnoseMissingExplicitSendable(NominalTypeDecl *nominal) { } } +void swift::tryDiagnoseExecutorConformance(ASTContext &C, + const NominalTypeDecl *nominal, + ProtocolDecl *proto) { + assert(proto->isSpecificProtocol(KnownProtocolKind::Executor) || + proto->isSpecificProtocol(KnownProtocolKind::SerialExecutor)); + + auto &diags = C.Diags; + auto module = nominal->getParentModule(); + Type nominalTy = nominal->getDeclaredInterfaceType(); + + // enqueue(_: UnownedJob) + auto enqueueDeclName = DeclName(C, DeclBaseName(C.Id_enqueue), { Identifier() }); + + FuncDecl *unownedEnqueueRequirement = nullptr; + FuncDecl *moveOnlyEnqueueRequirement = nullptr; + for (auto req: proto->getProtocolRequirements()) { + auto *funcDecl = dyn_cast(req); + if (!funcDecl) + continue; + + if (funcDecl->getName() != enqueueDeclName) + continue; + + + // look for the first parameter being a Job or UnownedJob + if (funcDecl->getParameters()->size() != 1) + continue; + if (auto param = funcDecl->getParameters()->front()) { + if (param->getType()->isEqual(C.getJobDecl()->getDeclaredInterfaceType())) { + assert(moveOnlyEnqueueRequirement == nullptr); + moveOnlyEnqueueRequirement = funcDecl; + } else if (param->getType()->isEqual(C.getUnownedJobDecl()->getDeclaredInterfaceType())) { + assert(unownedEnqueueRequirement == nullptr); + unownedEnqueueRequirement = funcDecl; + } + } + + // if we found both, we're done here and break out of the loop + if (unownedEnqueueRequirement && moveOnlyEnqueueRequirement) + break; // we're done looking for the requirements + } + + + auto conformance = module->lookupConformance(nominalTy, proto); + auto concreteConformance = conformance.getConcrete(); + auto unownedEnqueueWitness = concreteConformance->getWitnessDeclRef(unownedEnqueueRequirement); + auto moveOnlyEnqueueWitness = concreteConformance->getWitnessDeclRef(moveOnlyEnqueueRequirement); + + if (auto enqueueUnownedDecl = unownedEnqueueWitness.getDecl()) { + // Old UnownedJob based impl is present, warn about it suggesting the new protocol requirement. + if (enqueueUnownedDecl->getLoc().isValid()) { + diags.diagnose(enqueueUnownedDecl->getLoc(), diag::executor_enqueue_unowned_implementation, nominalTy); + } + } + + if (auto unownedEnqueueDecl = unownedEnqueueWitness.getDecl()) { + if (auto moveOnlyEnqueueDecl = moveOnlyEnqueueWitness.getDecl()) { + if (unownedEnqueueDecl && unownedEnqueueDecl->getLoc().isInvalid() && + moveOnlyEnqueueDecl && moveOnlyEnqueueDecl->getLoc().isInvalid()) { + // Neither old nor new implementation have been found, but we provide default impls for them + // that are mutually recursive, so we must error and suggest implementing the right requirement. + auto ownedRequirement = C.getExecutorDecl()->getExecutorOwnedEnqueueFunction(); + nominal->diagnose(diag::type_does_not_conform, nominalTy, proto->getDeclaredInterfaceType()); + ownedRequirement->diagnose(diag::no_witnesses, + getProtocolRequirementKind(ownedRequirement), + ownedRequirement->getName(), + proto->getDeclaredInterfaceType(), + /*AddFixIt=*/true); + } + } + } +} + /// Determine whether this is the main actor type. static bool isMainActor(Type type) { if (auto nominal = type->getAnyNominal()) diff --git a/lib/Sema/TypeCheckConcurrency.h b/lib/Sema/TypeCheckConcurrency.h index c7fc5bb55f086..0fb1fdc89847b 100644 --- a/lib/Sema/TypeCheckConcurrency.h +++ b/lib/Sema/TypeCheckConcurrency.h @@ -280,6 +280,9 @@ void diagnoseMissingSendableConformance( /// state whether it conforms to Sendable, provide a diagnostic. void diagnoseMissingExplicitSendable(NominalTypeDecl *nominal); +/// Warn about deprecated `Executor.enqueue` implementations. +void tryDiagnoseExecutorConformance(ASTContext &C, const NominalTypeDecl *nominal, ProtocolDecl *proto); + /// How the Sendable check should be performed. enum class SendableCheck { /// Sendable conformance was explicitly stated and should be diff --git a/lib/Sema/TypeCheckProtocol.cpp b/lib/Sema/TypeCheckProtocol.cpp index 7a87a56eb6ac2..11ed775786ae7 100644 --- a/lib/Sema/TypeCheckProtocol.cpp +++ b/lib/Sema/TypeCheckProtocol.cpp @@ -6496,6 +6496,8 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) { } else if (proto->isSpecificProtocol( KnownProtocolKind::UnsafeSendable)) { sendableConformanceIsUnchecked = true; + } else if (proto->isSpecificProtocol(KnownProtocolKind::Executor)) { + tryDiagnoseExecutorConformance(Context, nominal, proto); } } diff --git a/stdlib/public/BackDeployConcurrency/Actor.cpp b/stdlib/public/BackDeployConcurrency/Actor.cpp index 3db760534eb4b..5dbd5546d3645 100644 --- a/stdlib/public/BackDeployConcurrency/Actor.cpp +++ b/stdlib/public/BackDeployConcurrency/Actor.cpp @@ -285,9 +285,14 @@ JobPriority swift::swift_task_getCurrentThreadPriority() { SWIFT_CC(swift) static bool swift_task_isCurrentExecutorImpl(ExecutorRef executor) { if (auto currentTracking = ExecutorTrackingInfo::current()) { + fprintf(stderr, "[%s:%d](%s) current executor = %p, main:%s, default:%s\n", __FILE_NAME__, __LINE__, __FUNCTION__, + currentTracking->getActiveExecutor().getIdentity(), + currentTracking->getActiveExecutor().isMainExecutor() ? "y" : "n", + currentTracking->getActiveExecutor().isDefaultActor() ? "y" : "n"); return currentTracking->getActiveExecutor() == executor; } + fprintf(stderr, "[%s:%d](%s) no executor found in tracking\n", __FILE_NAME__, __LINE__, __FUNCTION__); return executor.isMainExecutor() && isExecutingOnMainThread(); } diff --git a/stdlib/public/BackDeployConcurrency/Executor.swift b/stdlib/public/BackDeployConcurrency/Executor.swift index b7b5b3a751bf2..84db4bef7ae32 100644 --- a/stdlib/public/BackDeployConcurrency/Executor.swift +++ b/stdlib/public/BackDeployConcurrency/Executor.swift @@ -117,6 +117,10 @@ internal final class DispatchQueueShim: @unchecked Sendable, SerialExecutor { _enqueueOnDispatchQueue(job, queue: self) } + func enqueue(_ job: __owned Job) { + _enqueueOnDispatchQueue(job, queue: self) + } + func asUnownedSerialExecutor() -> UnownedSerialExecutor { return UnownedSerialExecutor(ordinary: self) } diff --git a/stdlib/public/Concurrency/Executor.swift b/stdlib/public/Concurrency/Executor.swift index 27f53b90b40d0..507e33c0f02d1 100644 --- a/stdlib/public/Concurrency/Executor.swift +++ b/stdlib/public/Concurrency/Executor.swift @@ -15,28 +15,31 @@ import Swift /// A service that can execute jobs. @available(SwiftStdlib 5.1, *) public protocol Executor: AnyObject, Sendable { - // This requirement is repeated here as a non-override so that we - // get a redundant witness-table entry for it. This allows us to - // avoid drilling down to the base conformance just for the basic - // work-scheduling operation. - @available(*, deprecated, message: "Implement enqueueJob instead") + @available(*, deprecated, message: "Implement 'enqueue(_: __owned Job)' instead") func enqueue(_ job: UnownedJob) @available(SwiftStdlib 5.9, *) - func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way + func enqueue(_ job: __owned Job) } /// A service that executes jobs. @available(SwiftStdlib 5.1, *) public protocol SerialExecutor: Executor { - + // This requirement is repeated here as a non-override so that we + // get a redundant witness-table entry for it. This allows us to + // avoid drilling down to the base conformance just for the basic + // work-scheduling operation. @_nonoverride - @available(*, deprecated, message: "Implement enqueueJob instead") + @available(*, deprecated, message: "Implement 'enqueue_: __owned Job) instead") func enqueue(_ job: UnownedJob) + // This requirement is repeated here as a non-override so that we + // get a redundant witness-table entry for it. This allows us to + // avoid drilling down to the base conformance just for the basic + // work-scheduling operation. @_nonoverride @available(SwiftStdlib 5.9, *) - func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way + func enqueue(_ job: __owned Job) /// Convert this executor value to the optimized form of borrowed /// executor references. @@ -45,14 +48,22 @@ public protocol SerialExecutor: Executor { @available(SwiftStdlib 5.9, *) extension Executor { - public func enqueue(_ job: UnownedJob) { // FIXME: this is bad; how could we deploy this nicely + public func enqueue(_ job: UnownedJob) { fatalError("Please implement \(Self.self).enqueueJob(_:)") } - public func enqueueJob(_ job: __owned Job) { + + public func enqueue(_ job: __owned Job) { self.enqueue(UnownedJob(job)) } } +@available(SwiftStdlib 5.9, *) +extension SerialExecutor { + public func asUnownedSerialExecutor() -> UnownedSerialExecutor { + UnownedSerialExecutor(ordinary: self) + } +} + /// An unowned reference to a serial executor (a `SerialExecutor` /// value). /// @@ -124,7 +135,7 @@ internal func _getJobTaskId(_ job: UnownedJob) -> UInt64 internal func _enqueueOnExecutor(job unownedJob: UnownedJob, executor: E) where E: SerialExecutor { if #available(SwiftStdlib 5.9, *) { - executor.enqueueJob(Job(context: unownedJob.context)) + executor.enqueue(Job(context: unownedJob.context)) } else { executor.enqueue(unownedJob) } diff --git a/stdlib/public/Concurrency/PartialAsyncTask.swift b/stdlib/public/Concurrency/PartialAsyncTask.swift index c15a61187b024..db46b03bfb5ca 100644 --- a/stdlib/public/Concurrency/PartialAsyncTask.swift +++ b/stdlib/public/Concurrency/PartialAsyncTask.swift @@ -35,7 +35,7 @@ public struct UnownedJob: Sendable { @available(SwiftStdlib 5.9, *) @usableFromInline - internal init(_ job: __owned Job) { + public init(_ job: __owned Job) { self.context = job.context } @@ -48,7 +48,7 @@ public struct UnownedJob: Sendable { @_alwaysEmitIntoClient @inlinable - @available(*, deprecated, renamed: "runSynchronously") + @available(*, deprecated, renamed: "runSynchronously(on:)") public func _runSynchronously(on executor: UnownedSerialExecutor) { _swiftJobRun(self, executor) } @@ -67,6 +67,70 @@ extension UnownedJob { } } +/// A unit of scheduleable work. +/// +/// Unless you're implementing a scheduler, +/// you don't generally interact with jobs directly. +@available(SwiftStdlib 5.9, *) +@frozen +@_moveOnly +public struct Job: Sendable { + internal var context: Builtin.Job + + @usableFromInline + internal init(context: __owned Builtin.Job) { + self.context = context + } + + public var priority: UnownedJob.Priority { + // let raw = _swift_concurrency_jobPriority(UnownedJob(context: self.context)) // TODO: do we also surface this or the base priority? + let raw = _taskCurrentPriority(context as! Builtin.NativeObject) + return UnownedJob.Priority(rawValue: raw) + } + + // TODO: move only types cannot conform to protocols, so we can't conform to CustomStringConvertible; + // we can still offer a description to be called explicitly though. + public var description: String { + let id = _getJobTaskId(UnownedJob(context: self.context)) + /// Tasks are always assigned an unique ID, however some jobs may not have it set, + /// and it appearing as 0 for _different_ jobs may lead to misunderstanding it as + /// being "the same 0 id job", we specifically print 0 (id not set) as nil. + if (id > 0) { + return "\(Self.self)(id: \(id))" + } else { + return "\(Self.self)(id: nil)" + } + } +} + +@available(SwiftStdlib 5.9, *) +extension Job { + + /// Run the job synchronously. + /// + /// This operation consumes the job, preventing it accidental use after it has ben run. + /// + /// Converting a `Job` to an `UnownedJob` and invoking `runSynchronously` on it multiple times is undefined behavior, + /// as a job can only ever be run once, and must not be accessed after it has been run. + @_alwaysEmitIntoClient + @inlinable + __consuming public func runSynchronously(on executor: some SerialExecutor) { + _swiftJobRun(UnownedJob(self), UnownedSerialExecutor(ordinary: executor)) + } + + /// Run the job synchronously. + /// + /// This operation consumes the job, preventing it accidental use after it has ben run. + /// + /// Converting a `Job` to an `UnownedJob` and invoking `runSynchronously` on it multiple times is undefined behavior, + /// as a job can only ever be run once, and must not be accessed after it has been run. + @_alwaysEmitIntoClient + @inlinable + __consuming public func runSynchronously(on executor: UnownedSerialExecutor) { + _swiftJobRun(UnownedJob(self), executor) + } +} + /// The priority of this job. /// /// The executor determines how priority information affects the way tasks are scheduled. @@ -82,8 +146,10 @@ extension UnownedJob { /// Conversions between the two priorities are available as initializers on the respective types. @available(SwiftStdlib 5.9, *) @frozen -public struct JobPriority { +public struct JobPriority: CustomStringConvertible { public typealias RawValue = UInt8 + _swiftJobRun(UnownedJob(self), UnownedSerialExecutor(ordinary: executor)) + } /// The raw priority value. public var rawValue: RawValue @@ -95,6 +161,9 @@ extension TaskPriority { /// /// Most values are directly interchangeable, but this initializer reserves the right to fail for certain values. @available(SwiftStdlib 5.9, *) + @_alwaysEmitIntoClient + @inlinable + __consuming public func runSynchronously(on executor: UnownedSerialExecutor) { public init?(_ p: JobPriority) { // currently we always convert, but we could consider mapping over only recognized values etc. self = .init(rawValue: p.rawValue) diff --git a/test/Concurrency/Runtime/custom_executors.swift b/test/Concurrency/Runtime/custom_executors.swift index 1c398f7c6db63..09e83c8fbc046 100644 --- a/test/Concurrency/Runtime/custom_executors.swift +++ b/test/Concurrency/Runtime/custom_executors.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s +// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s --dump-input=always // REQUIRES: concurrency // REQUIRES: executable_test diff --git a/test/Concurrency/Runtime/custom_executors_default.swift b/test/Concurrency/Runtime/custom_executors_default.swift index f92140b1ffbbc..0d0678814f9ad 100644 --- a/test/Concurrency/Runtime/custom_executors_default.swift +++ b/test/Concurrency/Runtime/custom_executors_default.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s +// RUN: %target-run-simple-swift( -Xfrontend enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s // REQUIRES: concurrency // REQUIRES: executable_test diff --git a/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift b/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift index 2bf00910e1e87..4def1645328da 100644 --- a/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift +++ b/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift @@ -8,15 +8,8 @@ // REQUIRES: concurrency_runtime final class InlineExecutor: SerialExecutor, CustomStringConvertible { - - public func enqueueJob(_ job: __owned Job) { - print("\(self): enqueue (job: \(job.description))") - runJobSynchronously(job) - print("\(self): after run") - } - - public func asUnownedSerialExecutor() -> UnownedSerialExecutor { - return UnownedSerialExecutor(ordinary: self) + public func enqueue(_ job: __owned Job) { + job.runSynchronously(on: self) } var description: Swift.String { diff --git a/test/Concurrency/Runtime/custom_executors_priority.swift b/test/Concurrency/Runtime/custom_executors_priority.swift index 940fe32120526..2e94f2d7dd761 100644 --- a/test/Concurrency/Runtime/custom_executors_priority.swift +++ b/test/Concurrency/Runtime/custom_executors_priority.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s +// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s // REQUIRES: concurrency // REQUIRES: executable_test @@ -8,13 +8,9 @@ // REQUIRES: concurrency_runtime final class InlineExecutor: SerialExecutor { - public func enqueue(_ job: UnownedJob) { + public func enqueue(_ job: __owned Job) { print("\(self): enqueue (priority: \(TaskPriority(job.priority)!))") - job._runSynchronously(on: self.asUnownedSerialExecutor()) - } - - public func asUnownedSerialExecutor() -> UnownedSerialExecutor { - return UnownedSerialExecutor(ordinary: self) + job.runSynchronously(on: self) } } @@ -46,8 +42,6 @@ actor Custom { await actor.report() }.value await Task(priority: .low) { - print("P: \(Task.currentPriority)") - print("B: \(Task.currentBasePriority)") await actor.report() }.value print("end") diff --git a/test/Concurrency/Runtime/custom_executors_protocol.swift b/test/Concurrency/Runtime/custom_executors_protocol.swift index 19a1565ba9be9..3cfaa6bdaa9bd 100644 --- a/test/Concurrency/Runtime/custom_executors_protocol.swift +++ b/test/Concurrency/Runtime/custom_executors_protocol.swift @@ -8,12 +8,7 @@ // UNSUPPORTED: back_deployment_runtime // REQUIRES: concurrency_runtime -import Dispatch - -func checkIfMainQueue(expectedAnswer expected: Bool) { - dispatchPrecondition(condition: expected ? .onQueue(DispatchQueue.main) - : .notOnQueue(DispatchQueue.main)) -} +@preconcurrency import Dispatch protocol WithSpecifiedExecutor: Actor { nonisolated var executor: SpecifiedExecutor { get } @@ -29,7 +24,7 @@ extension WithSpecifiedExecutor { } } -final class InlineExecutor: SpecifiedExecutor, Swift.CustomStringConvertible { +final class InlineExecutor: SpecifiedExecutor, CustomStringConvertible { let name: String init(_ name: String) { @@ -38,22 +33,47 @@ final class InlineExecutor: SpecifiedExecutor, Swift.CustomStringConvertible { public func enqueue(_ job: UnownedJob) { print("\(self): enqueue") - job._runSynchronously(on: self.asUnownedSerialExecutor()) + job.runSynchronously(on: self.asUnownedSerialExecutor()) print("\(self): after run") } - public func enqueueJob(_ job: __owned Job) { + public func enqueue(_ job: __owned Job) { + print("\(self): enqueue") + job.runSynchronously(on: self) + print("\(self): after run") + } + + var description: Swift.String { + "InlineExecutor(\(name))" + } +} + +final class NaiveQueueExecutor: SpecifiedExecutor, CustomStringConvertible { + let queue: DispatchQueue + + init(_ queue: DispatchQueue) { + self.queue = queue + } + + public func enqueue(_ job: UnownedJob) { print("\(self): enqueue") - runJobSynchronously(job) + queue.sync { + job.runSynchronously(on: self) + } print("\(self): after run") } - public func asUnownedSerialExecutor() -> UnownedSerialExecutor { - return UnownedSerialExecutor(ordinary: self) + public func enqueue(_ job: __owned Job) { + print("\(self): enqueue") + let unowned = UnownedJob(job) + queue.sync { + unowned.runSynchronously(on: self) + } + print("\(self): after run") } var description: Swift.String { - "InlineExecutor(\(name))" + "NaiveQueueExecutor(\(queue))" } } @@ -68,9 +88,9 @@ actor MyActor: WithSpecifiedExecutor { self.executor = executor } - func test(expectedExecutor: some SerialExecutor) { + func test(expectedExecutor: some SerialExecutor, expectedQueue: DispatchQueue) { precondition(_taskIsOnExecutor(expectedExecutor), "Expected to be on: \(expectedExecutor)") - checkIfMainQueue(expectedAnswer: true) + dispatchPrecondition(condition: .onQueue(expectedQueue)) print("\(Self.self): on executor \(expectedExecutor)") } } @@ -78,11 +98,12 @@ actor MyActor: WithSpecifiedExecutor { @main struct Main { static func main() async { print("begin") - let one = InlineExecutor("one") + let queue = DispatchQueue(label: "CustomQueue") + let one = NaiveQueueExecutor(queue) let actor = MyActor(executor: one) - await actor.test(expectedExecutor: one) - await actor.test(expectedExecutor: one) - await actor.test(expectedExecutor: one) + await actor.test(expectedExecutor: one, expectedQueue: queue) + await actor.test(expectedExecutor: one, expectedQueue: queue) + await actor.test(expectedExecutor: one, expectedQueue: queue) print("end") } } diff --git a/test/Concurrency/custom_executor_enqueue_impls.swift b/test/Concurrency/custom_executor_enqueue_impls.swift new file mode 100644 index 0000000000000..985a7168a61de --- /dev/null +++ b/test/Concurrency/custom_executor_enqueue_impls.swift @@ -0,0 +1,47 @@ +// RUN: %target-typecheck-verify-swift -enable-experimental-move-only -disable-availability-checking +// REQUIRES: concurrency + +// Such type may be encountered since Swift 5.5 (5.1 backdeployed) if someone implemented the +// not documented, but public Executor types back then already. +// +// We keep support for them, but also log a deprecation warning that they should move to the new signature. +final class OldExecutor: SerialExecutor { + func enqueue(_ job: UnownedJob) {} // expected-warning{{'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; conform type 'OldExecutor' to 'Executor' by implementing 'func enqueue(Job)' instead}} + + func asUnownedSerialExecutor() -> UnownedSerialExecutor { + UnownedSerialExecutor(ordinary: self) + } +} + +/// Implementing both enqueue methods is legal, but somewhat useless -- +/// we call into the "old one"; so the Owned version is not used in such impl. +/// +/// That's why we do log the deprecation warning, people should use the move-only version. +final class BothExecutor: SerialExecutor { + func enqueue(_ job: UnownedJob) {} // expected-warning{{'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; conform type 'BothExecutor' to 'Executor' by implementing 'func enqueue(Job)' instead}} + func enqueue(_ job: __owned Job) {} + + func asUnownedSerialExecutor() -> UnownedSerialExecutor { + UnownedSerialExecutor(ordinary: self) + } +} + +/// Even though we do provide default impls for both enqueue requirements, +/// we manually detect and emit an error if neither of them is implemented. +/// +/// We do so because we implement them recursively, so one of them must be implemented basically. +final class NoneExecutor: SerialExecutor { // expected-error{{type 'NoneExecutor' does not conform to protocol 'Executor'}} + + func asUnownedSerialExecutor() -> UnownedSerialExecutor { + UnownedSerialExecutor(ordinary: self) + } +} + +/// Just implementing the new signature causes no warnings, good. +final class NewExecutor: SerialExecutor { + func enqueue(_ job: __owned Job) {} // no warnings + + func asUnownedSerialExecutor() -> UnownedSerialExecutor { + UnownedSerialExecutor(ordinary: self) + } +} \ No newline at end of file From 8a69b83d08cbcef9546251f190fd830ad8a8e11d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 17 Feb 2023 07:36:18 +0900 Subject: [PATCH 3/9] rebase and cleanup --- lib/AST/Decl.cpp | 5 +- stdlib/cmake/modules/AddSwiftStdlib.cmake | 2 +- stdlib/public/BackDeployConcurrency/Actor.cpp | 5 - .../BackDeployConcurrency/Executor.swift | 1 - stdlib/public/Concurrency/Actor.cpp | 1 + stdlib/public/Concurrency/CMakeLists.txt | 2 +- stdlib/public/Concurrency/Executor.swift | 153 +++++-- .../Concurrency/ExecutorAssertions.swift | 10 +- .../public/Concurrency/PartialAsyncTask.swift | 403 ++++-------------- .../Concurrency/UncheckedContinuation.swift | 304 +++++++++++++ .../Runtime/custom_executors_default.swift | 2 +- .../custom_executors_moveOnly_job.swift | 2 +- .../Runtime/custom_executors_priority.swift | 7 +- .../Runtime/custom_executors_protocol.swift | 40 +- .../stability-concurrency-abi.test | 5 + 15 files changed, 544 insertions(+), 398 deletions(-) create mode 100644 stdlib/public/Concurrency/UncheckedContinuation.swift diff --git a/lib/AST/Decl.cpp b/lib/AST/Decl.cpp index f762e373152ca..843e1ba9428e2 100644 --- a/lib/AST/Decl.cpp +++ b/lib/AST/Decl.cpp @@ -5266,15 +5266,12 @@ NominalTypeDecl::getExecutorOwnedEnqueueFunction() const { if (!isa(candidate->getDeclContext())) continue; - fprintf(stderr, "[%s:%d](%s) candidate\n", __FILE_NAME__, __LINE__, __FUNCTION__); - candidate->dump(); - if (auto *funcDecl = dyn_cast(candidate)) { if (funcDecl->getParameters()->size() != 1) continue; auto params = funcDecl->getParameters(); - if (params->get(0)->getSpecifier() == ParamSpecifier::Owned) { + if (params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned) { // TODO: make this Consuming return funcDecl; } } diff --git a/stdlib/cmake/modules/AddSwiftStdlib.cmake b/stdlib/cmake/modules/AddSwiftStdlib.cmake index cb4f09b30c057..365b175587abe 100644 --- a/stdlib/cmake/modules/AddSwiftStdlib.cmake +++ b/stdlib/cmake/modules/AddSwiftStdlib.cmake @@ -1810,7 +1810,7 @@ function(add_swift_target_library name) if (SWIFTLIB_IS_STDLIB) list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-warn-implicit-overrides") list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-ossa-modules") - list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-lexical-lifetimes=true") # FIXME: undo this + list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-lexical-lifetimes=false") endif() if(NOT SWIFT_BUILD_RUNTIME_WITH_HOST_COMPILER AND NOT BUILD_STANDALONE AND diff --git a/stdlib/public/BackDeployConcurrency/Actor.cpp b/stdlib/public/BackDeployConcurrency/Actor.cpp index 5dbd5546d3645..3db760534eb4b 100644 --- a/stdlib/public/BackDeployConcurrency/Actor.cpp +++ b/stdlib/public/BackDeployConcurrency/Actor.cpp @@ -285,14 +285,9 @@ JobPriority swift::swift_task_getCurrentThreadPriority() { SWIFT_CC(swift) static bool swift_task_isCurrentExecutorImpl(ExecutorRef executor) { if (auto currentTracking = ExecutorTrackingInfo::current()) { - fprintf(stderr, "[%s:%d](%s) current executor = %p, main:%s, default:%s\n", __FILE_NAME__, __LINE__, __FUNCTION__, - currentTracking->getActiveExecutor().getIdentity(), - currentTracking->getActiveExecutor().isMainExecutor() ? "y" : "n", - currentTracking->getActiveExecutor().isDefaultActor() ? "y" : "n"); return currentTracking->getActiveExecutor() == executor; } - fprintf(stderr, "[%s:%d](%s) no executor found in tracking\n", __FILE_NAME__, __LINE__, __FUNCTION__); return executor.isMainExecutor() && isExecutingOnMainThread(); } diff --git a/stdlib/public/BackDeployConcurrency/Executor.swift b/stdlib/public/BackDeployConcurrency/Executor.swift index 84db4bef7ae32..e40013f0f990c 100644 --- a/stdlib/public/BackDeployConcurrency/Executor.swift +++ b/stdlib/public/BackDeployConcurrency/Executor.swift @@ -74,7 +74,6 @@ public struct UnownedSerialExecutor: Sendable { @_silgen_name("_swift_task_enqueueOnExecutor") internal func _enqueueOnExecutor(job: UnownedJob, executor: E) where E: SerialExecutor { - // TODO: something to do here? executor.enqueue(job) } diff --git a/stdlib/public/Concurrency/Actor.cpp b/stdlib/public/Concurrency/Actor.cpp index a58b8b7f94773..c3af38760fb42 100644 --- a/stdlib/public/Concurrency/Actor.cpp +++ b/stdlib/public/Concurrency/Actor.cpp @@ -301,6 +301,7 @@ static bool swift_task_isCurrentExecutorImpl(ExecutorRef executor) { return currentTracking->getActiveExecutor() == executor; } + // TODO(ktoso): checking the "is main thread" is not correct, main executor can be not main thread, relates to rdar://106188692 return executor.isMainExecutor() && isExecutingOnMainThread(); } diff --git a/stdlib/public/Concurrency/CMakeLists.txt b/stdlib/public/Concurrency/CMakeLists.txt index 7675aa8a1a478..b9971b567ffa9 100644 --- a/stdlib/public/Concurrency/CMakeLists.txt +++ b/stdlib/public/Concurrency/CMakeLists.txt @@ -104,6 +104,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I AsyncThrowingFlatMapSequence.swift AsyncThrowingMapSequence.swift AsyncThrowingPrefixWhileSequence.swift + UncheckedContinuation.swift GlobalActor.swift MainActor.swift PartialAsyncTask.swift @@ -149,7 +150,6 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I ${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS} -parse-stdlib -Xfrontend -enable-experimental-concurrency - -Xfrontend -enable-experimental-move-only # FIXME: REMOVE THIS -diagnostic-style swift ${SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS} ${swift_concurrency_options} diff --git a/stdlib/public/Concurrency/Executor.swift b/stdlib/public/Concurrency/Executor.swift index 507e33c0f02d1..7da44ea8eca36 100644 --- a/stdlib/public/Concurrency/Executor.swift +++ b/stdlib/public/Concurrency/Executor.swift @@ -15,7 +15,9 @@ import Swift /// A service that can execute jobs. @available(SwiftStdlib 5.1, *) public protocol Executor: AnyObject, Sendable { + @available(*, deprecated, message: "Implement 'enqueue(_: __owned Job)' instead") + @available(SwiftStdlib 5.1, *) func enqueue(_ job: UnownedJob) @available(SwiftStdlib 5.9, *) @@ -43,13 +45,14 @@ public protocol SerialExecutor: Executor { /// Convert this executor value to the optimized form of borrowed /// executor references. + @available(SwiftStdlib 5.9, *) func asUnownedSerialExecutor() -> UnownedSerialExecutor } @available(SwiftStdlib 5.9, *) extension Executor { public func enqueue(_ job: UnownedJob) { - fatalError("Please implement \(Self.self).enqueueJob(_:)") + self.enqueue(Job(job)) } public func enqueue(_ job: __owned Job) { @@ -59,6 +62,7 @@ extension Executor { @available(SwiftStdlib 5.9, *) extension SerialExecutor { + @available(SwiftStdlib 5.9, *) public func asUnownedSerialExecutor() -> UnownedSerialExecutor { UnownedSerialExecutor(ordinary: self) } @@ -107,18 +111,6 @@ public struct UnownedSerialExecutor: Sendable { } -/// Checks if the current task is running on the expected executor. -/// -/// Generally, Swift programs should be constructed such that it is statically -/// known that a specific executor is used, for example by using global actors or -/// custom executors. However, in some APIs it may be useful to provide an -/// additional runtime check for this, especially when moving towards Swift -/// concurrency from other runtimes which frequently use such assertions. -/// - Parameter executor: The expected executor. -@available(SwiftStdlib 5.9, *) -@_silgen_name("swift_task_isOnExecutor") -public func _taskIsOnExecutor(_ executor: Executor) -> Bool - /// Primarily a debug utility. /// /// If the passed in Job is a Task, returns the complete 64bit TaskId, @@ -135,28 +127,12 @@ internal func _getJobTaskId(_ job: UnownedJob) -> UInt64 internal func _enqueueOnExecutor(job unownedJob: UnownedJob, executor: E) where E: SerialExecutor { if #available(SwiftStdlib 5.9, *) { - executor.enqueue(Job(context: unownedJob.context)) + executor.enqueue(Job(context: unownedJob._context)) } else { executor.enqueue(unownedJob) } } -@available(SwiftStdlib 5.1, *) -@_transparent -public // COMPILER_INTRINSIC -func _checkExpectedExecutor(_filenameStart: Builtin.RawPointer, - _filenameLength: Builtin.Word, - _filenameIsASCII: Builtin.Int1, - _line: Builtin.Word, - _executor: Builtin.Executor) { - if _taskIsCurrentExecutor(_executor) { - return - } - - _reportUnexpectedExecutor( - _filenameStart, _filenameLength, _filenameIsASCII, _line, _executor) -} - #if !SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY // This must take a DispatchQueueShim, not something like AnyObject, // or else SILGen will emit a retain/release in unoptimized builds, @@ -185,3 +161,120 @@ internal final class DispatchQueueShim: @unchecked Sendable, SerialExecutor { } } #endif + +// ==== ----------------------------------------------------------------------- +// - MARK: Executor assertions + +/// Checks if the current task is running on the expected executor. +/// +/// Do note that if multiple actors share the same serial executor, +/// this assertion checks for the executor, not specific actor instance. +/// +/// Generally, Swift programs should be constructed such that it is statically +/// known that a specific executor is used, for example by using global actors or +/// custom executors. However, in some APIs it may be useful to provide an +/// additional runtime check for this, especially when moving towards Swift +/// concurrency from other runtimes which frequently use such assertions. +@available(SwiftStdlib 5.9, *) +public func preconditionOnSerialExecutor( + _ executor: some SerialExecutor, + _ message: @autoclosure () -> String = "", + file: StaticString = #fileID, line: UInt = #line) { + preconditionOnSerialExecutor(executor.asUnownedSerialExecutor(), file: file, line: line) +} + +@available(SwiftStdlib 5.9, *) +public func preconditionOnSerialExecutor( + _ unowned: UnownedSerialExecutor, + _ message: @autoclosure () -> String = "", + file: StaticString = #fileID, line: UInt = #line) { + if _taskIsCurrentExecutor(unowned.executor) { + return + } + + // TODO: log on what executor it was instead of the expected one + let message = "Expected executor \(unowned); \(message())" + preconditionFailure( + message, + file: file, line: line) +} + +/// Same as ``preconditionOnSerialExecutor(_:_:file:line)`` however only in DEBUG mode. +@available(SwiftStdlib 5.9, *) +public func assertOnSerialExecutor( + _ executor: some SerialExecutor, + _ message: @autoclosure () -> String = "", + file: StaticString = #fileID, line: UInt = #line) { + assertOnSerialExecutor(executor.asUnownedSerialExecutor(), file: file, line: line) +} + +@available(SwiftStdlib 5.9, *) +public func assertOnSerialExecutor( + _ unowned: UnownedSerialExecutor, + _ message: @autoclosure () -> String = "", + file: StaticString = #fileID, line: UInt = #line) { + if _isDebugAssertConfiguration() { + if _taskIsCurrentExecutor(unowned.executor) { + return + } + + // TODO: log on what executor it was instead of the expected one + // TODO: fixme use assertion here? + fatalError("Expected executor \(unowned); \(message())", file: file, line: line) + } +} + +/// Checks if the current task is running on the expected executor. +/// +/// Generally, Swift programs should be constructed such that it is statically +/// known that a specific executor is used, for example by using global actors or +/// custom executors. However, in some APIs it may be useful to provide an +/// additional runtime check for this, especially when moving towards Swift +/// concurrency from other runtimes which frequently use such assertions. +/// - Parameter executor: The expected executor. +@available(SwiftStdlib 5.9, *) +@_silgen_name("swift_task_isOnExecutor") +func _taskIsOnExecutor(_ executor: some SerialExecutor) -> Bool + +@available(SwiftStdlib 5.1, *) +@_transparent +public // COMPILER_INTRINSIC +func _checkExpectedExecutor(_filenameStart: Builtin.RawPointer, + _filenameLength: Builtin.Word, + _filenameIsASCII: Builtin.Int1, + _line: Builtin.Word, + _executor: Builtin.Executor) { + if _taskIsCurrentExecutor(_executor) { + return + } + + _reportUnexpectedExecutor( + _filenameStart, _filenameLength, _filenameIsASCII, _line, _executor) +} + +@available(SwiftStdlib 5.9, *) +@_alwaysEmitIntoClient // FIXME: use @backDeploy(before: SwiftStdlib 5.9) +func _checkExpectedExecutor( + _ _executor: Builtin.Executor, + file: String, + line: Int) { + if _taskIsCurrentExecutor(_executor) { + return + } + + file.utf8CString.withUnsafeBufferPointer { (_ bufPtr: UnsafeBufferPointer) in + let fileBasePtr: Builtin.RawPointer = bufPtr.baseAddress!._rawValue + + // string lengths exclude trailing \0 byte, which should be there! + let fileLength: Builtin.Word = (bufPtr.count - 1)._builtinWordValue + + // we're handing it UTF-8 + let falseByte: Int8 = 0 + let fileIsASCII: Builtin.Int1 = Builtin.trunc_Int8_Int1(falseByte._value) + + _reportUnexpectedExecutor( + fileBasePtr, fileLength, fileIsASCII, + line._builtinWordValue, + _executor) + } +} diff --git a/stdlib/public/Concurrency/ExecutorAssertions.swift b/stdlib/public/Concurrency/ExecutorAssertions.swift index cbf54461ec069..3d7e2df1893e1 100644 --- a/stdlib/public/Concurrency/ExecutorAssertions.swift +++ b/stdlib/public/Concurrency/ExecutorAssertions.swift @@ -34,7 +34,7 @@ import SwiftShims /// programming error. /// /// - Parameter executor: the expected current executor -@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9) +@available(SwiftStdlib 5.9, *) public func preconditionTaskOnExecutor( _ executor: some SerialExecutor, @@ -71,7 +71,7 @@ func preconditionTaskOnExecutor( /// programming error. /// /// - Parameter actor: the actor whose serial executor we expect to be the current executor -@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9) +@available(SwiftStdlib 5.9, *) public func preconditionTaskOnActorExecutor( _ actor: some Actor, @@ -108,7 +108,7 @@ func preconditionTaskOnActorExecutor( /// assumption is a serious programming error. /// /// - Parameter executor: the expected current executor -@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9) +@available(SwiftStdlib 5.9, *) public func assertTaskOnExecutor( _ executor: some SerialExecutor, @@ -143,7 +143,7 @@ func assertTaskOnExecutor( /// /// /// - Parameter actor: the actor whose serial executor we expect to be the current executor -@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9) +@available(SwiftStdlib 5.9, *) public func assertTaskOnActorExecutor( _ actor: some Actor, @@ -180,7 +180,7 @@ func assertTaskOnActorExecutor( /// if another actor uses the same serial executor--by using ``MainActor/sharedUnownedExecutor`` /// as its own ``Actor/unownedExecutor``--this check will succeed, as from a concurrency safety /// perspective, the serial executor guarantees mutual exclusion of those two actors. -@available(SwiftStdlib 5.9, *) // FIXME: use @backDeploy(before: SwiftStdlib 5.9) +@available(SwiftStdlib 5.9, *) @_unavailableFromAsync(message: "await the call to the @MainActor closure directly") public func assumeOnMainActorExecutor( diff --git a/stdlib/public/Concurrency/PartialAsyncTask.swift b/stdlib/public/Concurrency/PartialAsyncTask.swift index db46b03bfb5ca..56f866ca80452 100644 --- a/stdlib/public/Concurrency/PartialAsyncTask.swift +++ b/stdlib/public/Concurrency/PartialAsyncTask.swift @@ -13,28 +13,37 @@ import Swift @_implementationOnly import _SwiftConcurrencyShims +// TODO(swift): rename the file to Job.swift eventually, we don't use PartialTask terminology anymore + @available(SwiftStdlib 5.1, *) @_silgen_name("swift_job_run") @usableFromInline internal func _swiftJobRun(_ job: UnownedJob, _ executor: UnownedSerialExecutor) -> () +// ==== ----------------------------------------------------------------------- +// MARK: UnownedJob + /// A unit of scheduleable work. /// /// Unless you're implementing a scheduler, /// you don't generally interact with jobs directly. +/// +/// An `UnownedJob` must be eventually run *exactly once* using ``runSynchronously(on:)``. +/// Not doing so is effectively going to leak and "hang" the work that the job represents (e.g. a ``Task``). @available(SwiftStdlib 5.1, *) @frozen public struct UnownedJob: Sendable { - internal var context: Builtin.Job + private var context: Builtin.Job @usableFromInline + @available(SwiftStdlib 5.9, *) internal init(context: Builtin.Job) { self.context = context } + /// Create an `UnownedJob` whose lifetime must be managed carefully until it is run exactly once. @available(SwiftStdlib 5.9, *) - @usableFromInline public init(_ job: __owned Job) { self.context = job.context } @@ -46,24 +55,50 @@ public struct UnownedJob: Sendable { return JobPriority(rawValue: raw) } + @available(SwiftStdlib 5.9, *) + internal var _context: Builtin.Job { + context + } + + /// Deprecated API to run a job on a specific executor. @_alwaysEmitIntoClient @inlinable - @available(*, deprecated, renamed: "runSynchronously(on:)") + @available(*, deprecated, renamed: "Job.runSynchronously(on:)") public func _runSynchronously(on executor: UnownedSerialExecutor) { - _swiftJobRun(self, executor) + _swiftJobRun(self, executor) } -} - -@available(SwiftStdlib 5.9, *) -extension UnownedJob { - public struct Priority { - public typealias RawValue = UInt8 - public var rawValue: RawValue + /// Run this job on the passed in executor. + /// + /// This operation runs the job on the calling thread and *blocks* until the job completes. + /// The intended use of this method is for an executor to determine when and where it + /// wants to run the job and then call this method on it. + /// + /// The passed in executor reference is used to establish the executor context for the job, + /// and should be the same executor as the one semantically calling the `runSynchronously` method. + /// + /// - Parameter executor: the executor this job will be semantically running on. @_alwaysEmitIntoClient @inlinable public func runSynchronously(on executor: UnownedSerialExecutor) { - _swiftJobRun(self, executor) + _swiftJobRun(self, executor) + } + +} + +@available(SwiftStdlib 5.9, *) +extension UnownedJob: CustomStringConvertible { + @available(SwiftStdlib 5.9, *) + public var description: String { + let id = _getJobTaskId(self) + /// Tasks are always assigned an unique ID, however some jobs may not have it set, + /// and it appearing as 0 for _different_ jobs may lead to misunderstanding it as + /// being "the same 0 id job", we specifically print 0 (id not set) as nil. + if (id > 0) { + return "\(Self.self)(id: \(id))" + } else { + return "\(Self.self)(id: nil)" + } } } @@ -82,10 +117,13 @@ public struct Job: Sendable { self.context = context } - public var priority: UnownedJob.Priority { - // let raw = _swift_concurrency_jobPriority(UnownedJob(context: self.context)) // TODO: do we also surface this or the base priority? - let raw = _taskCurrentPriority(context as! Builtin.NativeObject) - return UnownedJob.Priority(rawValue: raw) + public init(_ job: UnownedJob) { + self.context = job._context + } + + public var priority: JobPriority { + let raw = _swift_concurrency_jobPriority(UnownedJob(context: self.context)) + return JobPriority(rawValue: raw) } // TODO: move only types cannot conform to protocols, so we can't conform to CustomStringConvertible; @@ -106,24 +144,21 @@ public struct Job: Sendable { @available(SwiftStdlib 5.9, *) extension Job { - /// Run the job synchronously. + /// Run this job on the passed in executor. /// - /// This operation consumes the job, preventing it accidental use after it has ben run. + /// This operation runs the job on the calling thread and *blocks* until the job completes. + /// The intended use of this method is for an executor to determine when and where it + /// wants to run the job and then call this method on it. /// - /// Converting a `Job` to an `UnownedJob` and invoking `runSynchronously` on it multiple times is undefined behavior, - /// as a job can only ever be run once, and must not be accessed after it has been run. - @_alwaysEmitIntoClient - @inlinable - __consuming public func runSynchronously(on executor: some SerialExecutor) { - _swiftJobRun(UnownedJob(self), UnownedSerialExecutor(ordinary: executor)) - } - - /// Run the job synchronously. + /// The passed in executor reference is used to establish the executor context for the job, + /// and should be the same executor as the one semantically calling the `runSynchronously` method. /// /// This operation consumes the job, preventing it accidental use after it has ben run. /// - /// Converting a `Job` to an `UnownedJob` and invoking `runSynchronously` on it multiple times is undefined behavior, + /// Converting a `Job` to an ``UnownedJob`` and invoking ``UnownedJob/runSynchronously(_:)` on it multiple times is undefined behavior, /// as a job can only ever be run once, and must not be accessed after it has been run. + /// + /// - Parameter executor: the executor this job will be semantically running on. @_alwaysEmitIntoClient @inlinable __consuming public func runSynchronously(on executor: UnownedSerialExecutor) { @@ -131,6 +166,9 @@ extension Job { } } +// ==== ----------------------------------------------------------------------- +// MARK: JobPriority + /// The priority of this job. /// /// The executor determines how priority information affects the way tasks are scheduled. @@ -145,11 +183,8 @@ extension Job { /// /// Conversions between the two priorities are available as initializers on the respective types. @available(SwiftStdlib 5.9, *) -@frozen -public struct JobPriority: CustomStringConvertible { +public struct JobPriority: Sendable { public typealias RawValue = UInt8 - _swiftJobRun(UnownedJob(self), UnownedSerialExecutor(ordinary: executor)) - } /// The raw priority value. public var rawValue: RawValue @@ -157,307 +192,51 @@ public struct JobPriority: CustomStringConvertible { @available(SwiftStdlib 5.9, *) extension TaskPriority { - /// Convert a job priority to a task priority. + /// Convert this ``UnownedJob/Priority`` to a ``TaskPriority``. /// /// Most values are directly interchangeable, but this initializer reserves the right to fail for certain values. @available(SwiftStdlib 5.9, *) - @_alwaysEmitIntoClient - @inlinable - __consuming public func runSynchronously(on executor: UnownedSerialExecutor) { public init?(_ p: JobPriority) { - // currently we always convert, but we could consider mapping over only recognized values etc. - self = .init(rawValue: p.rawValue) + guard p.rawValue != 0 else { + /// 0 is "undefined" + return nil + } + self = TaskPriority(rawValue: p.rawValue) } } -/// A mechanism to interface -/// between synchronous and asynchronous code, -/// without correctness checking. -/// -/// A *continuation* is an opaque representation of program state. -/// To create a continuation in asynchronous code, -/// call the `withUnsafeContinuation(_:)` or -/// `withUnsafeThrowingContinuation(_:)` function. -/// To resume the asynchronous task, -/// call the `resume(returning:)`, -/// `resume(throwing:)`, -/// `resume(with:)`, -/// or `resume()` method. -/// -/// - Important: You must call a resume method exactly once -/// on every execution path throughout the program. -/// Resuming from a continuation more than once is undefined behavior. -/// Never resuming leaves the task in a suspended state indefinitely, -/// and leaks any associated resources. -/// -/// `CheckedContinuation` performs runtime checks -/// for missing or multiple resume operations. -/// `UnsafeContinuation` avoids enforcing these invariants at runtime -/// because it aims to be a low-overhead mechanism -/// for interfacing Swift tasks with -/// event loops, delegate methods, callbacks, -/// and other non-`async` scheduling mechanisms. -/// However, during development, the ability to verify that the -/// invariants are being upheld in testing is important. -/// Because both types have the same interface, -/// you can replace one with the other in most circumstances, -/// without making other changes. -@available(SwiftStdlib 5.1, *) -@frozen -public struct UnsafeContinuation: Sendable { - @usableFromInline internal var context: Builtin.RawUnsafeContinuation - - @_alwaysEmitIntoClient - internal init(_ context: Builtin.RawUnsafeContinuation) { - self.context = context - } - - /// Resume the task that's awaiting the continuation - /// by returning the given value. - /// - /// - Parameter value: The value to return from the continuation. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(returning value: __owned T) where E == Never { - #if compiler(>=5.5) && $BuiltinContinuation - Builtin.resumeNonThrowingContinuationReturning(context, value) - #else - fatalError("Swift compiler is incompatible with this SDK version") - #endif +@available(SwiftStdlib 5.9, *) +extension JobPriority: Equatable { + public static func == (lhs: JobPriority, rhs: JobPriority) -> Bool { + lhs.rawValue == rhs.rawValue } - /// Resume the task that's awaiting the continuation - /// by returning the given value. - /// - /// - Parameter value: The value to return from the continuation. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(returning value: __owned T) { - #if compiler(>=5.5) && $BuiltinContinuation - Builtin.resumeThrowingContinuationReturning(context, value) - #else - fatalError("Swift compiler is incompatible with this SDK version") - #endif - } - - /// Resume the task that's awaiting the continuation - /// by throwing the given error. - /// - /// - Parameter error: The error to throw from the continuation. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(throwing error: __owned E) { -#if compiler(>=5.5) && $BuiltinContinuation - Builtin.resumeThrowingContinuationThrowing(context, error) -#else - fatalError("Swift compiler is incompatible with this SDK version") -#endif + public static func != (lhs: JobPriority, rhs: JobPriority) -> Bool { + lhs.rawValue != rhs.rawValue } } -@available(SwiftStdlib 5.1, *) -extension UnsafeContinuation { - /// Resume the task that's awaiting the continuation - /// by returning or throwing the given result value. - /// - /// - Parameter result: The result. - /// If it contains a `.success` value, - /// the continuation returns that value; - /// otherwise, it throws the `.error` value. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(with result: Result) where E == Error { - switch result { - case .success(let val): - self.resume(returning: val) - case .failure(let err): - self.resume(throwing: err) - } - } - - /// Resume the task that's awaiting the continuation - /// by returning or throwing the given result value. - /// - /// - Parameter result: The result. - /// If it contains a `.success` value, - /// the continuation returns that value; - /// otherwise, it throws the `.error` value. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(with result: Result) { - switch result { - case .success(let val): - self.resume(returning: val) - case .failure(let err): - self.resume(throwing: err) - } +@available(SwiftStdlib 5.9, *) +extension JobPriority: Comparable { + public static func < (lhs: JobPriority, rhs: JobPriority) -> Bool { + lhs.rawValue < rhs.rawValue } - /// Resume the task that's awaiting the continuation by returning. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume() where T == Void { - self.resume(returning: ()) + public static func <= (lhs: JobPriority, rhs: JobPriority) -> Bool { + lhs.rawValue <= rhs.rawValue } -} -#if _runtime(_ObjC) - -// Intrinsics used by SILGen to resume or fail continuations. -@available(SwiftStdlib 5.1, *) -@_alwaysEmitIntoClient -internal func _resumeUnsafeContinuation( - _ continuation: UnsafeContinuation, - _ value: __owned T -) { - continuation.resume(returning: value) -} - -@available(SwiftStdlib 5.1, *) -@_alwaysEmitIntoClient -internal func _resumeUnsafeThrowingContinuation( - _ continuation: UnsafeContinuation, - _ value: __owned T -) { - continuation.resume(returning: value) -} - -@available(SwiftStdlib 5.1, *) -@_alwaysEmitIntoClient -internal func _resumeUnsafeThrowingContinuationWithError( - _ continuation: UnsafeContinuation, - _ error: __owned Error -) { - continuation.resume(throwing: error) -} - -#endif - - -/// Invokes the passed in closure with a unsafe continuation for the current task. -/// -/// The body of the closure executes synchronously on the calling task, and once it returns -/// the calling task is suspended. It is possible to immediately resume the task, or escape the -/// continuation in order to complete it afterwards, which will them resume suspended task. -/// -/// You must invoke the continuation's `resume` method exactly once. -/// -/// Missing to invoke it (eventually) will cause the calling task to remain suspended -/// indefinitely which will result in the task "hanging" as well as being leaked with -/// no possibility to destroy it. -/// -/// Unlike the "checked" continuation variant, the `UnsafeContinuation` does not -/// detect or diagnose any kind of misuse, so you need to be extra careful to avoid -/// calling `resume` twice or forgetting to call resume before letting go of the -/// continuation object. -/// -/// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. -/// - Returns: The value continuation is resumed with. -/// -/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)` -/// - SeeAlso: `withCheckedContinuation(function:_:)` -/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` -@available(SwiftStdlib 5.1, *) -@_unsafeInheritExecutor -@_alwaysEmitIntoClient -public func withUnsafeContinuation( - _ fn: (UnsafeContinuation) -> Void -) async -> T { - return await Builtin.withUnsafeContinuation { - fn(UnsafeContinuation($0)) + public static func > (lhs: JobPriority, rhs: JobPriority) -> Bool { + lhs.rawValue > rhs.rawValue } -} -/// Invokes the passed in closure with a unsafe continuation for the current task. -/// -/// The body of the closure executes synchronously on the calling task, and once it returns -/// the calling task is suspended. It is possible to immediately resume the task, or escape the -/// continuation in order to complete it afterwards, which will them resume suspended task. -/// -/// If `resume(throwing:)` is called on the continuation, this function throws that error. -/// -/// You must invoke the continuation's `resume` method exactly once. -/// -/// Missing to invoke it (eventually) will cause the calling task to remain suspended -/// indefinitely which will result in the task "hanging" as well as being leaked with -/// no possibility to destroy it. -/// -/// Unlike the "checked" continuation variant, the `UnsafeContinuation` does not -/// detect or diagnose any kind of misuse, so you need to be extra careful to avoid -/// calling `resume` twice or forgetting to call resume before letting go of the -/// continuation object. -/// -/// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. -/// - Returns: The value continuation is resumed with. -/// -/// - SeeAlso: `withUnsafeContinuation(function:_:)` -/// - SeeAlso: `withCheckedContinuation(function:_:)` -/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` -@available(SwiftStdlib 5.1, *) -@_unsafeInheritExecutor -@_alwaysEmitIntoClient -public func withUnsafeThrowingContinuation( - _ fn: (UnsafeContinuation) -> Void -) async throws -> T { - return try await Builtin.withUnsafeThrowingContinuation { - fn(UnsafeContinuation($0)) + public static func >= (lhs: JobPriority, rhs: JobPriority) -> Bool { + lhs.rawValue >= rhs.rawValue } } -/// A hack to mark an SDK that supports swift_continuation_await. -@available(SwiftStdlib 5.1, *) -@_alwaysEmitIntoClient -public func _abiEnableAwaitContinuation() { - fatalError("never use this function") -} +// ==== ----------------------------------------------------------------------- +// MARK: Runtime functions @available(SwiftStdlib 5.9, *) @_silgen_name("swift_concurrency_jobPriority") diff --git a/stdlib/public/Concurrency/UncheckedContinuation.swift b/stdlib/public/Concurrency/UncheckedContinuation.swift new file mode 100644 index 0000000000000..323565106d3e1 --- /dev/null +++ b/stdlib/public/Concurrency/UncheckedContinuation.swift @@ -0,0 +1,304 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2020 - 2021 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Swift +@_implementationOnly import _SwiftConcurrencyShims + + +/// A mechanism to interface +/// between synchronous and asynchronous code, +/// without correctness checking. +/// +/// A *continuation* is an opaque representation of program state. +/// To create a continuation in asynchronous code, +/// call the `withUnsafeContinuation(_:)` or +/// `withUnsafeThrowingContinuation(_:)` function. +/// To resume the asynchronous task, +/// call the `resume(returning:)`, +/// `resume(throwing:)`, +/// `resume(with:)`, +/// or `resume()` method. +/// +/// - Important: You must call a resume method exactly once +/// on every execution path throughout the program. +/// Resuming from a continuation more than once is undefined behavior. +/// Never resuming leaves the task in a suspended state indefinitely, +/// and leaks any associated resources. +/// +/// `CheckedContinuation` performs runtime checks +/// for missing or multiple resume operations. +/// `UnsafeContinuation` avoids enforcing these invariants at runtime +/// because it aims to be a low-overhead mechanism +/// for interfacing Swift tasks with +/// event loops, delegate methods, callbacks, +/// and other non-`async` scheduling mechanisms. +/// However, during development, the ability to verify that the +/// invariants are being upheld in testing is important. +/// Because both types have the same interface, +/// you can replace one with the other in most circumstances, +/// without making other changes. +@available(SwiftStdlib 5.1, *) +@frozen +public struct UnsafeContinuation: Sendable { + @usableFromInline internal var context: Builtin.RawUnsafeContinuation + + @_alwaysEmitIntoClient + internal init(_ context: Builtin.RawUnsafeContinuation) { + self.context = context + } + + /// Resume the task that's awaiting the continuation + /// by returning the given value. + /// + /// - Parameter value: The value to return from the continuation. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(returning value: __owned T) where E == Never { + #if compiler(>=5.5) && $BuiltinContinuation + Builtin.resumeNonThrowingContinuationReturning(context, value) + #else + fatalError("Swift compiler is incompatible with this SDK version") + #endif + } + + /// Resume the task that's awaiting the continuation + /// by returning the given value. + /// + /// - Parameter value: The value to return from the continuation. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(returning value: __owned T) { + #if compiler(>=5.5) && $BuiltinContinuation + Builtin.resumeThrowingContinuationReturning(context, value) + #else + fatalError("Swift compiler is incompatible with this SDK version") + #endif + } + + /// Resume the task that's awaiting the continuation + /// by throwing the given error. + /// + /// - Parameter error: The error to throw from the continuation. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(throwing error: __owned E) { + #if compiler(>=5.5) && $BuiltinContinuation + Builtin.resumeThrowingContinuationThrowing(context, error) + #else + fatalError("Swift compiler is incompatible with this SDK version") + #endif + } +} + +@available(SwiftStdlib 5.1, *) +extension UnsafeContinuation { + /// Resume the task that's awaiting the continuation + /// by returning or throwing the given result value. + /// + /// - Parameter result: The result. + /// If it contains a `.success` value, + /// the continuation returns that value; + /// otherwise, it throws the `.error` value. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(with result: Result) where E == Error { + switch result { + case .success(let val): + self.resume(returning: val) + case .failure(let err): + self.resume(throwing: err) + } + } + + /// Resume the task that's awaiting the continuation + /// by returning or throwing the given result value. + /// + /// - Parameter result: The result. + /// If it contains a `.success` value, + /// the continuation returns that value; + /// otherwise, it throws the `.error` value. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(with result: Result) { + switch result { + case .success(let val): + self.resume(returning: val) + case .failure(let err): + self.resume(throwing: err) + } + } + + /// Resume the task that's awaiting the continuation by returning. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume() where T == Void { + self.resume(returning: ()) + } +} + +#if _runtime(_ObjC) + +// Intrinsics used by SILGen to resume or fail continuations. +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +internal func _resumeUnsafeContinuation( + _ continuation: UnsafeContinuation, + _ value: __owned T +) { + continuation.resume(returning: value) +} + +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +internal func _resumeUnsafeThrowingContinuation( + _ continuation: UnsafeContinuation, + _ value: __owned T +) { + continuation.resume(returning: value) +} + +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +internal func _resumeUnsafeThrowingContinuationWithError( + _ continuation: UnsafeContinuation, + _ error: __owned Error +) { + continuation.resume(throwing: error) +} + +#endif + + +/// Invokes the passed in closure with a unsafe continuation for the current task. +/// +/// The body of the closure executes synchronously on the calling task, and once it returns +/// the calling task is suspended. It is possible to immediately resume the task, or escape the +/// continuation in order to complete it afterwards, which will them resume suspended task. +/// +/// You must invoke the continuation's `resume` method exactly once. +/// +/// Missing to invoke it (eventually) will cause the calling task to remain suspended +/// indefinitely which will result in the task "hanging" as well as being leaked with +/// no possibility to destroy it. +/// +/// Unlike the "checked" continuation variant, the `UnsafeContinuation` does not +/// detect or diagnose any kind of misuse, so you need to be extra careful to avoid +/// calling `resume` twice or forgetting to call resume before letting go of the +/// continuation object. +/// +/// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. +/// - Returns: The value continuation is resumed with. +/// +/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)` +/// - SeeAlso: `withCheckedContinuation(function:_:)` +/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` +@available(SwiftStdlib 5.1, *) +@_unsafeInheritExecutor +@_alwaysEmitIntoClient +public func withUnsafeContinuation( + _ fn: (UnsafeContinuation) -> Void +) async -> T { + return await Builtin.withUnsafeContinuation { + fn(UnsafeContinuation($0)) + } +} + +/// Invokes the passed in closure with a unsafe continuation for the current task. +/// +/// The body of the closure executes synchronously on the calling task, and once it returns +/// the calling task is suspended. It is possible to immediately resume the task, or escape the +/// continuation in order to complete it afterwards, which will them resume suspended task. +/// +/// If `resume(throwing:)` is called on the continuation, this function throws that error. +/// +/// You must invoke the continuation's `resume` method exactly once. +/// +/// Missing to invoke it (eventually) will cause the calling task to remain suspended +/// indefinitely which will result in the task "hanging" as well as being leaked with +/// no possibility to destroy it. +/// +/// Unlike the "checked" continuation variant, the `UnsafeContinuation` does not +/// detect or diagnose any kind of misuse, so you need to be extra careful to avoid +/// calling `resume` twice or forgetting to call resume before letting go of the +/// continuation object. +/// +/// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. +/// - Returns: The value continuation is resumed with. +/// +/// - SeeAlso: `withUnsafeContinuation(function:_:)` +/// - SeeAlso: `withCheckedContinuation(function:_:)` +/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` +@available(SwiftStdlib 5.1, *) +@_unsafeInheritExecutor +@_alwaysEmitIntoClient +public func withUnsafeThrowingContinuation( + _ fn: (UnsafeContinuation) -> Void +) async throws -> T { + return try await Builtin.withUnsafeThrowingContinuation { + fn(UnsafeContinuation($0)) + } +} + +/// A hack to mark an SDK that supports swift_continuation_await. +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +public func _abiEnableAwaitContinuation() { + fatalError("never use this function") +} \ No newline at end of file diff --git a/test/Concurrency/Runtime/custom_executors_default.swift b/test/Concurrency/Runtime/custom_executors_default.swift index 0d0678814f9ad..9f572132fa5e8 100644 --- a/test/Concurrency/Runtime/custom_executors_default.swift +++ b/test/Concurrency/Runtime/custom_executors_default.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift( -Xfrontend enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s +// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s // REQUIRES: concurrency // REQUIRES: executable_test diff --git a/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift b/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift index 4def1645328da..0a722876f5264 100644 --- a/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift +++ b/test/Concurrency/Runtime/custom_executors_moveOnly_job.swift @@ -9,7 +9,7 @@ final class InlineExecutor: SerialExecutor, CustomStringConvertible { public func enqueue(_ job: __owned Job) { - job.runSynchronously(on: self) + job.runSynchronously(on: self.asUnownedSerialExecutor()) } var description: Swift.String { diff --git a/test/Concurrency/Runtime/custom_executors_priority.swift b/test/Concurrency/Runtime/custom_executors_priority.swift index 2e94f2d7dd761..a393374742b78 100644 --- a/test/Concurrency/Runtime/custom_executors_priority.swift +++ b/test/Concurrency/Runtime/custom_executors_priority.swift @@ -1,4 +1,4 @@ -// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s +// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s --dump-input=always // REQUIRES: concurrency // REQUIRES: executable_test @@ -10,7 +10,7 @@ final class InlineExecutor: SerialExecutor { public func enqueue(_ job: __owned Job) { print("\(self): enqueue (priority: \(TaskPriority(job.priority)!))") - job.runSynchronously(on: self) + job.runSynchronously(on: self.asUnownedSerialExecutor()) } } @@ -41,9 +41,6 @@ actor Custom { await Task() { await actor.report() }.value - await Task(priority: .low) { - await actor.report() - }.value print("end") } } diff --git a/test/Concurrency/Runtime/custom_executors_protocol.swift b/test/Concurrency/Runtime/custom_executors_protocol.swift index 3cfaa6bdaa9bd..6d64fd3abac09 100644 --- a/test/Concurrency/Runtime/custom_executors_protocol.swift +++ b/test/Concurrency/Runtime/custom_executors_protocol.swift @@ -24,30 +24,6 @@ extension WithSpecifiedExecutor { } } -final class InlineExecutor: SpecifiedExecutor, CustomStringConvertible { - let name: String - - init(_ name: String) { - self.name = name - } - - public func enqueue(_ job: UnownedJob) { - print("\(self): enqueue") - job.runSynchronously(on: self.asUnownedSerialExecutor()) - print("\(self): after run") - } - - public func enqueue(_ job: __owned Job) { - print("\(self): enqueue") - job.runSynchronously(on: self) - print("\(self): after run") - } - - var description: Swift.String { - "InlineExecutor(\(name))" - } -} - final class NaiveQueueExecutor: SpecifiedExecutor, CustomStringConvertible { let queue: DispatchQueue @@ -58,7 +34,7 @@ final class NaiveQueueExecutor: SpecifiedExecutor, CustomStringConvertible { public func enqueue(_ job: UnownedJob) { print("\(self): enqueue") queue.sync { - job.runSynchronously(on: self) + job.runSynchronously(on: self.asUnownedSerialExecutor()) } print("\(self): after run") } @@ -67,7 +43,7 @@ final class NaiveQueueExecutor: SpecifiedExecutor, CustomStringConvertible { print("\(self): enqueue") let unowned = UnownedJob(job) queue.sync { - unowned.runSynchronously(on: self) + unowned.runSynchronously(on: self.asUnownedSerialExecutor()) } print("\(self): after run") } @@ -89,7 +65,7 @@ actor MyActor: WithSpecifiedExecutor { } func test(expectedExecutor: some SerialExecutor, expectedQueue: DispatchQueue) { - precondition(_taskIsOnExecutor(expectedExecutor), "Expected to be on: \(expectedExecutor)") + // FIXME(waiting on preconditions to merge): preconditionTaskOnExecutor(expectedExecutor, "Expected to be on: \(expectedExecutor)") dispatchPrecondition(condition: .onQueue(expectedQueue)) print("\(Self.self): on executor \(expectedExecutor)") } @@ -109,9 +85,9 @@ actor MyActor: WithSpecifiedExecutor { } // CHECK: begin -// CHECK-NEXT: InlineExecutor(one): enqueue -// CHECK-NEXT: MyActor: on executor InlineExecutor(one) -// CHECK-NEXT: MyActor: on executor InlineExecutor(one) -// CHECK-NEXT: MyActor: on executor InlineExecutor(one) -// CHECK-NEXT: InlineExecutor(one): after run +// CHECK-NEXT: NaiveQueueExecutor(): enqueue +// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor() +// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor() +// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor() +// CHECK-NEXT: NaiveQueueExecutor(): after run // CHECK-NEXT: end diff --git a/test/api-digester/stability-concurrency-abi.test b/test/api-digester/stability-concurrency-abi.test index bcbede5c1298c..a2e244c31a3c9 100644 --- a/test/api-digester/stability-concurrency-abi.test +++ b/test/api-digester/stability-concurrency-abi.test @@ -62,4 +62,9 @@ Func _asyncLet_get_throwing(_:_:) has return type change from Builtin.RawPointer Protocol Actor has added inherited protocol AnyActor Protocol Actor has generic signature change from to Struct CheckedContinuation has removed conformance to UnsafeSendable + +// SerialExecutor gained `enqueue(_:) moveonly `Job`, protocol requirements got default implementations from +Func SerialExecutor.enqueue(_:) has been added as a protocol requirement +// ABI checker seems confused, + // *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.) From a4904135e19e4932649500c97284a6bdc3ea6ec3 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 17 Mar 2023 08:32:56 +0900 Subject: [PATCH 4/9] disable bad abi checker warning --- stdlib/public/Concurrency/Executor.swift | 11 ++++++++--- test/api-digester/stability-concurrency-abi.test | 9 +++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/stdlib/public/Concurrency/Executor.swift b/stdlib/public/Concurrency/Executor.swift index 7da44ea8eca36..64ab35fb18958 100644 --- a/stdlib/public/Concurrency/Executor.swift +++ b/stdlib/public/Concurrency/Executor.swift @@ -16,8 +16,10 @@ import Swift @available(SwiftStdlib 5.1, *) public protocol Executor: AnyObject, Sendable { - @available(*, deprecated, message: "Implement 'enqueue(_: __owned Job)' instead") - @available(SwiftStdlib 5.1, *) + @available(macOS, introduced: 10.15, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") + @available(iOS, introduced: 13.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") + @available(watchOS, introduced: 6.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") + @available(tvOS, introduced: 13.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") func enqueue(_ job: UnownedJob) @available(SwiftStdlib 5.9, *) @@ -32,7 +34,10 @@ public protocol SerialExecutor: Executor { // avoid drilling down to the base conformance just for the basic // work-scheduling operation. @_nonoverride - @available(*, deprecated, message: "Implement 'enqueue_: __owned Job) instead") + @available(macOS, introduced: 10.15, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") + @available(iOS, introduced: 13.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") + @available(watchOS, introduced: 6.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") + @available(tvOS, introduced: 13.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") func enqueue(_ job: UnownedJob) // This requirement is repeated here as a non-override so that we diff --git a/test/api-digester/stability-concurrency-abi.test b/test/api-digester/stability-concurrency-abi.test index a2e244c31a3c9..86047608f6590 100644 --- a/test/api-digester/stability-concurrency-abi.test +++ b/test/api-digester/stability-concurrency-abi.test @@ -63,8 +63,13 @@ Protocol Actor has added inherited protocol AnyActor Protocol Actor has generic signature change from to Struct CheckedContinuation has removed conformance to UnsafeSendable -// SerialExecutor gained `enqueue(_:) moveonly `Job`, protocol requirements got default implementations from +// SerialExecutor gained `enqueue(_: __owned Job)`, protocol requirements got default implementations Func SerialExecutor.enqueue(_:) has been added as a protocol requirement -// ABI checker seems confused, + +// We deprecated `enqueue(_: UnownedJob)` and annotate it as such for all platforms, +// while adding a new overload that accepts `Job` with new availability. +// The ABI checker seems to be confused by this case. +// rdar://106833284 (ABI checker confused with overload getting deprecated) +Func Executor.enqueue(_:) is a new API without @available attribute // *** DO NOT DISABLE OR XFAIL THIS TEST. *** (See comment above.) From 3defe4315841f21a4fa3fb193b97232f37585e81 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 17 Mar 2023 08:53:01 +0900 Subject: [PATCH 5/9] dont touch BackDeployConcurrency --- stdlib/public/BackDeployConcurrency/Executor.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/stdlib/public/BackDeployConcurrency/Executor.swift b/stdlib/public/BackDeployConcurrency/Executor.swift index e40013f0f990c..13e2811b83ef5 100644 --- a/stdlib/public/BackDeployConcurrency/Executor.swift +++ b/stdlib/public/BackDeployConcurrency/Executor.swift @@ -116,10 +116,6 @@ internal final class DispatchQueueShim: @unchecked Sendable, SerialExecutor { _enqueueOnDispatchQueue(job, queue: self) } - func enqueue(_ job: __owned Job) { - _enqueueOnDispatchQueue(job, queue: self) - } - func asUnownedSerialExecutor() -> UnownedSerialExecutor { return UnownedSerialExecutor(ordinary: self) } From f2de5d88d9205dd83766384c5b73b5808113cc1d Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 17 Mar 2023 11:21:23 +0900 Subject: [PATCH 6/9] cleanup --- stdlib/public/Concurrency/CMakeLists.txt | 1 - stdlib/public/Concurrency/Executor.swift | 147 ++------- .../public/Concurrency/PartialAsyncTask.swift | 293 +++++++++++++++++ .../Concurrency/UncheckedContinuation.swift | 304 ------------------ 4 files changed, 322 insertions(+), 423 deletions(-) delete mode 100644 stdlib/public/Concurrency/UncheckedContinuation.swift diff --git a/stdlib/public/Concurrency/CMakeLists.txt b/stdlib/public/Concurrency/CMakeLists.txt index b9971b567ffa9..9f6be0a4c0dd5 100644 --- a/stdlib/public/Concurrency/CMakeLists.txt +++ b/stdlib/public/Concurrency/CMakeLists.txt @@ -104,7 +104,6 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I AsyncThrowingFlatMapSequence.swift AsyncThrowingMapSequence.swift AsyncThrowingPrefixWhileSequence.swift - UncheckedContinuation.swift GlobalActor.swift MainActor.swift PartialAsyncTask.swift diff --git a/stdlib/public/Concurrency/Executor.swift b/stdlib/public/Concurrency/Executor.swift index 64ab35fb18958..17691d853342c 100644 --- a/stdlib/public/Concurrency/Executor.swift +++ b/stdlib/public/Concurrency/Executor.swift @@ -116,6 +116,34 @@ public struct UnownedSerialExecutor: Sendable { } +/// Checks if the current task is running on the expected executor. +/// +/// Generally, Swift programs should be constructed such that it is statically +/// known that a specific executor is used, for example by using global actors or +/// custom executors. However, in some APIs it may be useful to provide an +/// additional runtime check for this, especially when moving towards Swift +/// concurrency from other runtimes which frequently use such assertions. +/// - Parameter executor: The expected executor. +@available(SwiftStdlib 5.9, *) +@_silgen_name("swift_task_isOnExecutor") +public func _taskIsOnExecutor(_ executor: Executor) -> Bool + +@available(SwiftStdlib 5.1, *) +@_transparent +public // COMPILER_INTRINSIC +func _checkExpectedExecutor(_filenameStart: Builtin.RawPointer, + _filenameLength: Builtin.Word, + _filenameIsASCII: Builtin.Int1, + _line: Builtin.Word, + _executor: Builtin.Executor) { + if _taskIsCurrentExecutor(_executor) { + return + } + + _reportUnexpectedExecutor( + _filenameStart, _filenameLength, _filenameIsASCII, _line, _executor) +} + /// Primarily a debug utility. /// /// If the passed in Job is a Task, returns the complete 64bit TaskId, @@ -165,121 +193,4 @@ internal final class DispatchQueueShim: @unchecked Sendable, SerialExecutor { return UnownedSerialExecutor(ordinary: self) } } -#endif - -// ==== ----------------------------------------------------------------------- -// - MARK: Executor assertions - -/// Checks if the current task is running on the expected executor. -/// -/// Do note that if multiple actors share the same serial executor, -/// this assertion checks for the executor, not specific actor instance. -/// -/// Generally, Swift programs should be constructed such that it is statically -/// known that a specific executor is used, for example by using global actors or -/// custom executors. However, in some APIs it may be useful to provide an -/// additional runtime check for this, especially when moving towards Swift -/// concurrency from other runtimes which frequently use such assertions. -@available(SwiftStdlib 5.9, *) -public func preconditionOnSerialExecutor( - _ executor: some SerialExecutor, - _ message: @autoclosure () -> String = "", - file: StaticString = #fileID, line: UInt = #line) { - preconditionOnSerialExecutor(executor.asUnownedSerialExecutor(), file: file, line: line) -} - -@available(SwiftStdlib 5.9, *) -public func preconditionOnSerialExecutor( - _ unowned: UnownedSerialExecutor, - _ message: @autoclosure () -> String = "", - file: StaticString = #fileID, line: UInt = #line) { - if _taskIsCurrentExecutor(unowned.executor) { - return - } - - // TODO: log on what executor it was instead of the expected one - let message = "Expected executor \(unowned); \(message())" - preconditionFailure( - message, - file: file, line: line) -} - -/// Same as ``preconditionOnSerialExecutor(_:_:file:line)`` however only in DEBUG mode. -@available(SwiftStdlib 5.9, *) -public func assertOnSerialExecutor( - _ executor: some SerialExecutor, - _ message: @autoclosure () -> String = "", - file: StaticString = #fileID, line: UInt = #line) { - assertOnSerialExecutor(executor.asUnownedSerialExecutor(), file: file, line: line) -} - -@available(SwiftStdlib 5.9, *) -public func assertOnSerialExecutor( - _ unowned: UnownedSerialExecutor, - _ message: @autoclosure () -> String = "", - file: StaticString = #fileID, line: UInt = #line) { - if _isDebugAssertConfiguration() { - if _taskIsCurrentExecutor(unowned.executor) { - return - } - - // TODO: log on what executor it was instead of the expected one - // TODO: fixme use assertion here? - fatalError("Expected executor \(unowned); \(message())", file: file, line: line) - } -} - -/// Checks if the current task is running on the expected executor. -/// -/// Generally, Swift programs should be constructed such that it is statically -/// known that a specific executor is used, for example by using global actors or -/// custom executors. However, in some APIs it may be useful to provide an -/// additional runtime check for this, especially when moving towards Swift -/// concurrency from other runtimes which frequently use such assertions. -/// - Parameter executor: The expected executor. -@available(SwiftStdlib 5.9, *) -@_silgen_name("swift_task_isOnExecutor") -func _taskIsOnExecutor(_ executor: some SerialExecutor) -> Bool - -@available(SwiftStdlib 5.1, *) -@_transparent -public // COMPILER_INTRINSIC -func _checkExpectedExecutor(_filenameStart: Builtin.RawPointer, - _filenameLength: Builtin.Word, - _filenameIsASCII: Builtin.Int1, - _line: Builtin.Word, - _executor: Builtin.Executor) { - if _taskIsCurrentExecutor(_executor) { - return - } - - _reportUnexpectedExecutor( - _filenameStart, _filenameLength, _filenameIsASCII, _line, _executor) -} - -@available(SwiftStdlib 5.9, *) -@_alwaysEmitIntoClient // FIXME: use @backDeploy(before: SwiftStdlib 5.9) -func _checkExpectedExecutor( - _ _executor: Builtin.Executor, - file: String, - line: Int) { - if _taskIsCurrentExecutor(_executor) { - return - } - - file.utf8CString.withUnsafeBufferPointer { (_ bufPtr: UnsafeBufferPointer) in - let fileBasePtr: Builtin.RawPointer = bufPtr.baseAddress!._rawValue - - // string lengths exclude trailing \0 byte, which should be there! - let fileLength: Builtin.Word = (bufPtr.count - 1)._builtinWordValue - - // we're handing it UTF-8 - let falseByte: Int8 = 0 - let fileIsASCII: Builtin.Int1 = Builtin.trunc_Int8_Int1(falseByte._value) - - _reportUnexpectedExecutor( - fileBasePtr, fileLength, fileIsASCII, - line._builtinWordValue, - _executor) - } -} +#endif // SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY diff --git a/stdlib/public/Concurrency/PartialAsyncTask.swift b/stdlib/public/Concurrency/PartialAsyncTask.swift index 56f866ca80452..93d9a7ac2eb60 100644 --- a/stdlib/public/Concurrency/PartialAsyncTask.swift +++ b/stdlib/public/Concurrency/PartialAsyncTask.swift @@ -183,6 +183,7 @@ extension Job { /// /// Conversions between the two priorities are available as initializers on the respective types. @available(SwiftStdlib 5.9, *) +@frozen public struct JobPriority: Sendable { public typealias RawValue = UInt8 @@ -235,6 +236,298 @@ extension JobPriority: Comparable { } } +// ==== ----------------------------------------------------------------------- +// MARK: UncheckedContinuation + +/// A mechanism to interface +/// between synchronous and asynchronous code, +/// without correctness checking. +/// +/// A *continuation* is an opaque representation of program state. +/// To create a continuation in asynchronous code, +/// call the `withUnsafeContinuation(_:)` or +/// `withUnsafeThrowingContinuation(_:)` function. +/// To resume the asynchronous task, +/// call the `resume(returning:)`, +/// `resume(throwing:)`, +/// `resume(with:)`, +/// or `resume()` method. +/// +/// - Important: You must call a resume method exactly once +/// on every execution path throughout the program. +/// Resuming from a continuation more than once is undefined behavior. +/// Never resuming leaves the task in a suspended state indefinitely, +/// and leaks any associated resources. +/// +/// `CheckedContinuation` performs runtime checks +/// for missing or multiple resume operations. +/// `UnsafeContinuation` avoids enforcing these invariants at runtime +/// because it aims to be a low-overhead mechanism +/// for interfacing Swift tasks with +/// event loops, delegate methods, callbacks, +/// and other non-`async` scheduling mechanisms. +/// However, during development, the ability to verify that the +/// invariants are being upheld in testing is important. +/// Because both types have the same interface, +/// you can replace one with the other in most circumstances, +/// without making other changes. +@available(SwiftStdlib 5.1, *) +@frozen +public struct UnsafeContinuation: Sendable { + @usableFromInline internal var context: Builtin.RawUnsafeContinuation + + @_alwaysEmitIntoClient + internal init(_ context: Builtin.RawUnsafeContinuation) { + self.context = context + } + + /// Resume the task that's awaiting the continuation + /// by returning the given value. + /// + /// - Parameter value: The value to return from the continuation. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(returning value: __owned T) where E == Never { + #if compiler(>=5.5) && $BuiltinContinuation + Builtin.resumeNonThrowingContinuationReturning(context, value) + #else + fatalError("Swift compiler is incompatible with this SDK version") + #endif + } + + /// Resume the task that's awaiting the continuation + /// by returning the given value. + /// + /// - Parameter value: The value to return from the continuation. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(returning value: __owned T) { + #if compiler(>=5.5) && $BuiltinContinuation + Builtin.resumeThrowingContinuationReturning(context, value) + #else + fatalError("Swift compiler is incompatible with this SDK version") + #endif + } + + /// Resume the task that's awaiting the continuation + /// by throwing the given error. + /// + /// - Parameter error: The error to throw from the continuation. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(throwing error: __owned E) { + #if compiler(>=5.5) && $BuiltinContinuation + Builtin.resumeThrowingContinuationThrowing(context, error) + #else + fatalError("Swift compiler is incompatible with this SDK version") + #endif + } +} + +@available(SwiftStdlib 5.1, *) +extension UnsafeContinuation { + /// Resume the task that's awaiting the continuation + /// by returning or throwing the given result value. + /// + /// - Parameter result: The result. + /// If it contains a `.success` value, + /// the continuation returns that value; + /// otherwise, it throws the `.error` value. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(with result: Result) where E == Error { + switch result { + case .success(let val): + self.resume(returning: val) + case .failure(let err): + self.resume(throwing: err) + } + } + + /// Resume the task that's awaiting the continuation + /// by returning or throwing the given result value. + /// + /// - Parameter result: The result. + /// If it contains a `.success` value, + /// the continuation returns that value; + /// otherwise, it throws the `.error` value. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume(with result: Result) { + switch result { + case .success(let val): + self.resume(returning: val) + case .failure(let err): + self.resume(throwing: err) + } + } + + /// Resume the task that's awaiting the continuation by returning. + /// + /// A continuation must be resumed exactly once. + /// If the continuation has already resumed, + /// then calling this method results in undefined behavior. + /// + /// After calling this method, + /// control immediately returns to the caller. + /// The task continues executing + /// when its executor schedules it. + @_alwaysEmitIntoClient + public func resume() where T == Void { + self.resume(returning: ()) + } +} + +#if _runtime(_ObjC) + +// Intrinsics used by SILGen to resume or fail continuations. +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +internal func _resumeUnsafeContinuation( + _ continuation: UnsafeContinuation, + _ value: __owned T +) { + continuation.resume(returning: value) +} + +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +internal func _resumeUnsafeThrowingContinuation( + _ continuation: UnsafeContinuation, + _ value: __owned T +) { + continuation.resume(returning: value) +} + +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +internal func _resumeUnsafeThrowingContinuationWithError( + _ continuation: UnsafeContinuation, + _ error: __owned Error +) { + continuation.resume(throwing: error) +} + +#endif + + +/// Invokes the passed in closure with a unsafe continuation for the current task. +/// +/// The body of the closure executes synchronously on the calling task, and once it returns +/// the calling task is suspended. It is possible to immediately resume the task, or escape the +/// continuation in order to complete it afterwards, which will them resume suspended task. +/// +/// You must invoke the continuation's `resume` method exactly once. +/// +/// Missing to invoke it (eventually) will cause the calling task to remain suspended +/// indefinitely which will result in the task "hanging" as well as being leaked with +/// no possibility to destroy it. +/// +/// Unlike the "checked" continuation variant, the `UnsafeContinuation` does not +/// detect or diagnose any kind of misuse, so you need to be extra careful to avoid +/// calling `resume` twice or forgetting to call resume before letting go of the +/// continuation object. +/// +/// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. +/// - Returns: The value continuation is resumed with. +/// +/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)` +/// - SeeAlso: `withCheckedContinuation(function:_:)` +/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` +@available(SwiftStdlib 5.1, *) +@_unsafeInheritExecutor +@_alwaysEmitIntoClient +public func withUnsafeContinuation( + _ fn: (UnsafeContinuation) -> Void +) async -> T { + return await Builtin.withUnsafeContinuation { + fn(UnsafeContinuation($0)) + } +} + +/// Invokes the passed in closure with a unsafe continuation for the current task. +/// +/// The body of the closure executes synchronously on the calling task, and once it returns +/// the calling task is suspended. It is possible to immediately resume the task, or escape the +/// continuation in order to complete it afterwards, which will them resume suspended task. +/// +/// If `resume(throwing:)` is called on the continuation, this function throws that error. +/// +/// You must invoke the continuation's `resume` method exactly once. +/// +/// Missing to invoke it (eventually) will cause the calling task to remain suspended +/// indefinitely which will result in the task "hanging" as well as being leaked with +/// no possibility to destroy it. +/// +/// Unlike the "checked" continuation variant, the `UnsafeContinuation` does not +/// detect or diagnose any kind of misuse, so you need to be extra careful to avoid +/// calling `resume` twice or forgetting to call resume before letting go of the +/// continuation object. +/// +/// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. +/// - Returns: The value continuation is resumed with. +/// +/// - SeeAlso: `withUnsafeContinuation(function:_:)` +/// - SeeAlso: `withCheckedContinuation(function:_:)` +/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` +@available(SwiftStdlib 5.1, *) +@_unsafeInheritExecutor +@_alwaysEmitIntoClient +public func withUnsafeThrowingContinuation( + _ fn: (UnsafeContinuation) -> Void +) async throws -> T { + return try await Builtin.withUnsafeThrowingContinuation { + fn(UnsafeContinuation($0)) + } +} + +/// A hack to mark an SDK that supports swift_continuation_await. +@available(SwiftStdlib 5.1, *) +@_alwaysEmitIntoClient +public func _abiEnableAwaitContinuation() { + fatalError("never use this function") +} + // ==== ----------------------------------------------------------------------- // MARK: Runtime functions diff --git a/stdlib/public/Concurrency/UncheckedContinuation.swift b/stdlib/public/Concurrency/UncheckedContinuation.swift deleted file mode 100644 index 323565106d3e1..0000000000000 --- a/stdlib/public/Concurrency/UncheckedContinuation.swift +++ /dev/null @@ -1,304 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2020 - 2021 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors -// -//===----------------------------------------------------------------------===// - -import Swift -@_implementationOnly import _SwiftConcurrencyShims - - -/// A mechanism to interface -/// between synchronous and asynchronous code, -/// without correctness checking. -/// -/// A *continuation* is an opaque representation of program state. -/// To create a continuation in asynchronous code, -/// call the `withUnsafeContinuation(_:)` or -/// `withUnsafeThrowingContinuation(_:)` function. -/// To resume the asynchronous task, -/// call the `resume(returning:)`, -/// `resume(throwing:)`, -/// `resume(with:)`, -/// or `resume()` method. -/// -/// - Important: You must call a resume method exactly once -/// on every execution path throughout the program. -/// Resuming from a continuation more than once is undefined behavior. -/// Never resuming leaves the task in a suspended state indefinitely, -/// and leaks any associated resources. -/// -/// `CheckedContinuation` performs runtime checks -/// for missing or multiple resume operations. -/// `UnsafeContinuation` avoids enforcing these invariants at runtime -/// because it aims to be a low-overhead mechanism -/// for interfacing Swift tasks with -/// event loops, delegate methods, callbacks, -/// and other non-`async` scheduling mechanisms. -/// However, during development, the ability to verify that the -/// invariants are being upheld in testing is important. -/// Because both types have the same interface, -/// you can replace one with the other in most circumstances, -/// without making other changes. -@available(SwiftStdlib 5.1, *) -@frozen -public struct UnsafeContinuation: Sendable { - @usableFromInline internal var context: Builtin.RawUnsafeContinuation - - @_alwaysEmitIntoClient - internal init(_ context: Builtin.RawUnsafeContinuation) { - self.context = context - } - - /// Resume the task that's awaiting the continuation - /// by returning the given value. - /// - /// - Parameter value: The value to return from the continuation. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(returning value: __owned T) where E == Never { - #if compiler(>=5.5) && $BuiltinContinuation - Builtin.resumeNonThrowingContinuationReturning(context, value) - #else - fatalError("Swift compiler is incompatible with this SDK version") - #endif - } - - /// Resume the task that's awaiting the continuation - /// by returning the given value. - /// - /// - Parameter value: The value to return from the continuation. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(returning value: __owned T) { - #if compiler(>=5.5) && $BuiltinContinuation - Builtin.resumeThrowingContinuationReturning(context, value) - #else - fatalError("Swift compiler is incompatible with this SDK version") - #endif - } - - /// Resume the task that's awaiting the continuation - /// by throwing the given error. - /// - /// - Parameter error: The error to throw from the continuation. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(throwing error: __owned E) { - #if compiler(>=5.5) && $BuiltinContinuation - Builtin.resumeThrowingContinuationThrowing(context, error) - #else - fatalError("Swift compiler is incompatible with this SDK version") - #endif - } -} - -@available(SwiftStdlib 5.1, *) -extension UnsafeContinuation { - /// Resume the task that's awaiting the continuation - /// by returning or throwing the given result value. - /// - /// - Parameter result: The result. - /// If it contains a `.success` value, - /// the continuation returns that value; - /// otherwise, it throws the `.error` value. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(with result: Result) where E == Error { - switch result { - case .success(let val): - self.resume(returning: val) - case .failure(let err): - self.resume(throwing: err) - } - } - - /// Resume the task that's awaiting the continuation - /// by returning or throwing the given result value. - /// - /// - Parameter result: The result. - /// If it contains a `.success` value, - /// the continuation returns that value; - /// otherwise, it throws the `.error` value. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume(with result: Result) { - switch result { - case .success(let val): - self.resume(returning: val) - case .failure(let err): - self.resume(throwing: err) - } - } - - /// Resume the task that's awaiting the continuation by returning. - /// - /// A continuation must be resumed exactly once. - /// If the continuation has already resumed, - /// then calling this method results in undefined behavior. - /// - /// After calling this method, - /// control immediately returns to the caller. - /// The task continues executing - /// when its executor schedules it. - @_alwaysEmitIntoClient - public func resume() where T == Void { - self.resume(returning: ()) - } -} - -#if _runtime(_ObjC) - -// Intrinsics used by SILGen to resume or fail continuations. -@available(SwiftStdlib 5.1, *) -@_alwaysEmitIntoClient -internal func _resumeUnsafeContinuation( - _ continuation: UnsafeContinuation, - _ value: __owned T -) { - continuation.resume(returning: value) -} - -@available(SwiftStdlib 5.1, *) -@_alwaysEmitIntoClient -internal func _resumeUnsafeThrowingContinuation( - _ continuation: UnsafeContinuation, - _ value: __owned T -) { - continuation.resume(returning: value) -} - -@available(SwiftStdlib 5.1, *) -@_alwaysEmitIntoClient -internal func _resumeUnsafeThrowingContinuationWithError( - _ continuation: UnsafeContinuation, - _ error: __owned Error -) { - continuation.resume(throwing: error) -} - -#endif - - -/// Invokes the passed in closure with a unsafe continuation for the current task. -/// -/// The body of the closure executes synchronously on the calling task, and once it returns -/// the calling task is suspended. It is possible to immediately resume the task, or escape the -/// continuation in order to complete it afterwards, which will them resume suspended task. -/// -/// You must invoke the continuation's `resume` method exactly once. -/// -/// Missing to invoke it (eventually) will cause the calling task to remain suspended -/// indefinitely which will result in the task "hanging" as well as being leaked with -/// no possibility to destroy it. -/// -/// Unlike the "checked" continuation variant, the `UnsafeContinuation` does not -/// detect or diagnose any kind of misuse, so you need to be extra careful to avoid -/// calling `resume` twice or forgetting to call resume before letting go of the -/// continuation object. -/// -/// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. -/// - Returns: The value continuation is resumed with. -/// -/// - SeeAlso: `withUnsafeThrowingContinuation(function:_:)` -/// - SeeAlso: `withCheckedContinuation(function:_:)` -/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` -@available(SwiftStdlib 5.1, *) -@_unsafeInheritExecutor -@_alwaysEmitIntoClient -public func withUnsafeContinuation( - _ fn: (UnsafeContinuation) -> Void -) async -> T { - return await Builtin.withUnsafeContinuation { - fn(UnsafeContinuation($0)) - } -} - -/// Invokes the passed in closure with a unsafe continuation for the current task. -/// -/// The body of the closure executes synchronously on the calling task, and once it returns -/// the calling task is suspended. It is possible to immediately resume the task, or escape the -/// continuation in order to complete it afterwards, which will them resume suspended task. -/// -/// If `resume(throwing:)` is called on the continuation, this function throws that error. -/// -/// You must invoke the continuation's `resume` method exactly once. -/// -/// Missing to invoke it (eventually) will cause the calling task to remain suspended -/// indefinitely which will result in the task "hanging" as well as being leaked with -/// no possibility to destroy it. -/// -/// Unlike the "checked" continuation variant, the `UnsafeContinuation` does not -/// detect or diagnose any kind of misuse, so you need to be extra careful to avoid -/// calling `resume` twice or forgetting to call resume before letting go of the -/// continuation object. -/// -/// - Parameter fn: A closure that takes an `UnsafeContinuation` parameter. -/// - Returns: The value continuation is resumed with. -/// -/// - SeeAlso: `withUnsafeContinuation(function:_:)` -/// - SeeAlso: `withCheckedContinuation(function:_:)` -/// - SeeAlso: `withCheckedThrowingContinuation(function:_:)` -@available(SwiftStdlib 5.1, *) -@_unsafeInheritExecutor -@_alwaysEmitIntoClient -public func withUnsafeThrowingContinuation( - _ fn: (UnsafeContinuation) -> Void -) async throws -> T { - return try await Builtin.withUnsafeThrowingContinuation { - fn(UnsafeContinuation($0)) - } -} - -/// A hack to mark an SDK that supports swift_continuation_await. -@available(SwiftStdlib 5.1, *) -@_alwaysEmitIntoClient -public func _abiEnableAwaitContinuation() { - fatalError("never use this function") -} \ No newline at end of file From 443f95e697749abdc59d44b4563a471f38a5d471 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Fri, 17 Mar 2023 14:02:55 +0900 Subject: [PATCH 7/9] dispatch queue seems to be printed inconsistently; fix test --- .../Runtime/custom_executors_protocol.swift | 29 ++++++++----------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/test/Concurrency/Runtime/custom_executors_protocol.swift b/test/Concurrency/Runtime/custom_executors_protocol.swift index 6d64fd3abac09..81b49fc2e3165 100644 --- a/test/Concurrency/Runtime/custom_executors_protocol.swift +++ b/test/Concurrency/Runtime/custom_executors_protocol.swift @@ -25,20 +25,14 @@ extension WithSpecifiedExecutor { } final class NaiveQueueExecutor: SpecifiedExecutor, CustomStringConvertible { + let name: String let queue: DispatchQueue - init(_ queue: DispatchQueue) { + init(name: String, _ queue: DispatchQueue) { + self.name = name self.queue = queue } - public func enqueue(_ job: UnownedJob) { - print("\(self): enqueue") - queue.sync { - job.runSynchronously(on: self.asUnownedSerialExecutor()) - } - print("\(self): after run") - } - public func enqueue(_ job: __owned Job) { print("\(self): enqueue") let unowned = UnownedJob(job) @@ -49,7 +43,7 @@ final class NaiveQueueExecutor: SpecifiedExecutor, CustomStringConvertible { } var description: Swift.String { - "NaiveQueueExecutor(\(queue))" + "NaiveQueueExecutor(\(name))" } } @@ -74,8 +68,9 @@ actor MyActor: WithSpecifiedExecutor { @main struct Main { static func main() async { print("begin") - let queue = DispatchQueue(label: "CustomQueue") - let one = NaiveQueueExecutor(queue) + let name = "CustomQueue" + let queue = DispatchQueue(label: name) + let one = NaiveQueueExecutor(name: name, queue) let actor = MyActor(executor: one) await actor.test(expectedExecutor: one, expectedQueue: queue) await actor.test(expectedExecutor: one, expectedQueue: queue) @@ -85,9 +80,9 @@ actor MyActor: WithSpecifiedExecutor { } // CHECK: begin -// CHECK-NEXT: NaiveQueueExecutor(): enqueue -// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor() -// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor() -// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor() -// CHECK-NEXT: NaiveQueueExecutor(): after run +// CHECK-NEXT: NaiveQueueExecutor(CustomQueue): enqueue +// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor(CustomQueue) +// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor(CustomQueue) +// CHECK-NEXT: MyActor: on executor NaiveQueueExecutor(CustomQueue) +// CHECK-NEXT: NaiveQueueExecutor(CustomQueue): after run // CHECK-NEXT: end From 614a3263ef0f2a6bb21681e7d783bd96750efca1 Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sat, 18 Mar 2023 08:35:06 +0900 Subject: [PATCH 8/9] fix autocomplete tests now that Job is a possible completion --- test/Sema/typo_correction.swift | 6 +++--- test/Sema/typo_correction_limit.swift | 14 +++++++------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/test/Sema/typo_correction.swift b/test/Sema/typo_correction.swift index bf19e4cd1381f..3686056c70987 100644 --- a/test/Sema/typo_correction.swift +++ b/test/Sema/typo_correction.swift @@ -10,9 +10,9 @@ import NoSuchModule // This is close enough to get typo-correction. func test_short_and_close() { - let foo = 4 // expected-note {{'foo' declared here}} - let bab = fob + 1 - // expected-error@-1 {{cannot find 'fob' in scope; did you mean 'foo'?}} + let plop = 4 // expected-note {{'plop' declared here}} + let bab = plob + 1 + // expected-error@-1 {{cannot find 'plob' in scope}} } // This is not. diff --git a/test/Sema/typo_correction_limit.swift b/test/Sema/typo_correction_limit.swift index 158c4d2e810db..e85c7d308d380 100644 --- a/test/Sema/typo_correction_limit.swift +++ b/test/Sema/typo_correction_limit.swift @@ -2,11 +2,11 @@ // This is close enough to get typo-correction. func test_short_and_close() { - let foo = 4 // expected-note 5 {{'foo' declared here}} - let _ = fob + 1 // expected-error {{cannot find 'fob' in scope; did you mean 'foo'?}} - let _ = fob + 1 // expected-error {{cannot find 'fob' in scope; did you mean 'foo'?}} - let _ = fob + 1 // expected-error {{cannot find 'fob' in scope; did you mean 'foo'?}} - let _ = fob + 1 // expected-error {{cannot find 'fob' in scope; did you mean 'foo'?}} - let _ = fob + 1 // expected-error {{cannot find 'fob' in scope; did you mean 'foo'?}} - let _ = fob + 1 // expected-error {{cannot find 'fob' in scope}} + let boop = 4 // expected-note 5 {{did you mean 'boop'?}} + let _ = bood + 1 // expected-error {{cannot find 'bood' in scope}} + let _ = bood + 1 // expected-error {{cannot find 'bood' in scope}} + let _ = bood + 1 // expected-error {{cannot find 'bood' in scope}} + let _ = bood + 1 // expected-error {{cannot find 'bood' in scope}} + let _ = bood + 1 // expected-error {{cannot find 'bood' in scope}} + let _ = bood + 1 // expected-error {{cannot find 'bood' in scope}} } From f61edfd477acfd34c27caf8b4013f91af1ac5c6b Mon Sep 17 00:00:00 2001 From: Konrad `ktoso` Malawski Date: Sat, 18 Mar 2023 08:49:00 +0900 Subject: [PATCH 9/9] remove moveonly Job APIs from the task-to-thread model --- stdlib/public/Concurrency/Executor.swift | 14 ++++++++++++++ stdlib/public/Concurrency/PartialAsyncTask.swift | 6 +++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/stdlib/public/Concurrency/Executor.swift b/stdlib/public/Concurrency/Executor.swift index 17691d853342c..8a8742f85aaf2 100644 --- a/stdlib/public/Concurrency/Executor.swift +++ b/stdlib/public/Concurrency/Executor.swift @@ -16,14 +16,18 @@ import Swift @available(SwiftStdlib 5.1, *) public protocol Executor: AnyObject, Sendable { + #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(macOS, introduced: 10.15, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") @available(iOS, introduced: 13.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") @available(watchOS, introduced: 6.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") @available(tvOS, introduced: 13.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") + #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY func enqueue(_ job: UnownedJob) + #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.9, *) func enqueue(_ job: __owned Job) + #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY } /// A service that executes jobs. @@ -34,12 +38,15 @@ public protocol SerialExecutor: Executor { // avoid drilling down to the base conformance just for the basic // work-scheduling operation. @_nonoverride + #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(macOS, introduced: 10.15, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") @available(iOS, introduced: 13.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") @available(watchOS, introduced: 6.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") @available(tvOS, introduced: 13.0, deprecated: 9999, message: "Implement 'enqueue(_: __owned Job)' instead") + #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY func enqueue(_ job: UnownedJob) + #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY // This requirement is repeated here as a non-override so that we // get a redundant witness-table entry for it. This allows us to // avoid drilling down to the base conformance just for the basic @@ -47,6 +54,7 @@ public protocol SerialExecutor: Executor { @_nonoverride @available(SwiftStdlib 5.9, *) func enqueue(_ job: __owned Job) + #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY /// Convert this executor value to the optimized form of borrowed /// executor references. @@ -54,6 +62,7 @@ public protocol SerialExecutor: Executor { func asUnownedSerialExecutor() -> UnownedSerialExecutor } +#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.9, *) extension Executor { public func enqueue(_ job: UnownedJob) { @@ -64,6 +73,7 @@ extension Executor { self.enqueue(UnownedJob(job)) } } +#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY @available(SwiftStdlib 5.9, *) extension SerialExecutor { @@ -159,11 +169,15 @@ internal func _getJobTaskId(_ job: UnownedJob) -> UInt64 @_silgen_name("_swift_task_enqueueOnExecutor") internal func _enqueueOnExecutor(job unownedJob: UnownedJob, executor: E) where E: SerialExecutor { + #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY if #available(SwiftStdlib 5.9, *) { executor.enqueue(Job(context: unownedJob._context)) } else { executor.enqueue(unownedJob) } + #else // SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY + executor.enqueue(unownedJob) + #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY } #if !SWIFT_STDLIB_SINGLE_THREADED_CONCURRENCY && !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY diff --git a/stdlib/public/Concurrency/PartialAsyncTask.swift b/stdlib/public/Concurrency/PartialAsyncTask.swift index 93d9a7ac2eb60..f6907f4e401cb 100644 --- a/stdlib/public/Concurrency/PartialAsyncTask.swift +++ b/stdlib/public/Concurrency/PartialAsyncTask.swift @@ -42,11 +42,13 @@ public struct UnownedJob: Sendable { self.context = context } + #if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY /// Create an `UnownedJob` whose lifetime must be managed carefully until it is run exactly once. @available(SwiftStdlib 5.9, *) public init(_ job: __owned Job) { self.context = job.context } + #endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY /// The priority of this job. @available(SwiftStdlib 5.9, *) @@ -102,7 +104,8 @@ extension UnownedJob: CustomStringConvertible { } } -/// A unit of scheduleable work. +#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY +//// A unit of scheduleable work. /// /// Unless you're implementing a scheduler, /// you don't generally interact with jobs directly. @@ -165,6 +168,7 @@ extension Job { _swiftJobRun(UnownedJob(self), executor) } } +#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY // ==== ----------------------------------------------------------------------- // MARK: JobPriority