Skip to content

Commit aeae636

Browse files
committed
[Concurrency] Swift interface for custom main and global executors.
Reorganise the Concurrency code so that it's possible to completely implement executors (both main and global) in Swift. Provide API to choose the desired executors for your application. Also make `Task.Sleep` wait using the current executor, not the global executor, and expose APIs on `Clock` to allow for conversion between time bases. rdar://141348916
1 parent 80050bb commit aeae636

34 files changed

+1890
-370
lines changed

include/swift/Runtime/Concurrency.h

+23
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,29 @@ SWIFT_EXPORT_FROM(swift_Concurrency)
149149
CoroAllocator *const _swift_coro_malloc_allocator;
150150
// }} TODO: CoroutineAccessors
151151

152+
/// Deallocate memory in a task.
153+
///
154+
/// The pointer provided must be the last pointer allocated on
155+
/// this task that has not yet been deallocated; that is, memory
156+
/// must be allocated and deallocated in a strict stack discipline.
157+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
158+
void swift_task_dealloc(void *ptr);
159+
160+
/// Allocate memory in a job.
161+
///
162+
/// All allocations will be rounded to a multiple of MAX_ALIGNMENT;
163+
/// if the job does not support allocation, this will return NULL.
164+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
165+
void *swift_job_allocate(Job *job, size_t size);
166+
167+
/// Deallocate memory in a job.
168+
///
169+
/// The pointer provided must be the last pointer allocated on
170+
/// this task that has not yet been deallocated; that is, memory
171+
/// must be allocated and deallocated in a strict stack discipline.
172+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
173+
void swift_job_deallocate(Job *job, void *ptr);
174+
152175
/// Cancel a task and all of its child tasks.
153176
///
154177
/// This can be called from any thread.

stdlib/public/Concurrency/Actor.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "../CompatibilityOverride/CompatibilityOverride.h"
2626
#include "swift/ABI/Actor.h"
2727
#include "swift/ABI/Task.h"
28+
#include "ExecutorBridge.h"
2829
#include "TaskPrivate.h"
2930
#include "swift/Basic/HeaderFooterLayout.h"
3031
#include "swift/Basic/PriorityQueue.h"
@@ -289,6 +290,40 @@ static SerialExecutorRef swift_task_getCurrentExecutorImpl() {
289290
return result;
290291
}
291292

293+
#pragma clang diagnostic push
294+
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
295+
296+
extern "C" SWIFT_CC(swift)
297+
SerialExecutorRef _swift_getActiveExecutor() {
298+
auto currentTracking = ExecutorTrackingInfo::current();
299+
if (currentTracking) {
300+
SerialExecutorRef executor = currentTracking->getActiveExecutor();
301+
// This might be an actor, in which case return nil ("generic")
302+
if (executor.isDefaultActor())
303+
return SerialExecutorRef::generic();
304+
return executor;
305+
}
306+
return swift_getMainExecutor();
307+
}
308+
309+
extern "C" SWIFT_CC(swift)
310+
TaskExecutorRef _swift_getCurrentTaskExecutor() {
311+
auto currentTracking = ExecutorTrackingInfo::current();
312+
if (currentTracking)
313+
return currentTracking->getTaskExecutor();
314+
return TaskExecutorRef::undefined();
315+
}
316+
317+
extern "C" SWIFT_CC(swift)
318+
TaskExecutorRef _swift_getPreferredTaskExecutor() {
319+
AsyncTask *task = swift_task_getCurrent();
320+
if (!task)
321+
return TaskExecutorRef::undefined();
322+
return task->getPreferredTaskExecutor();
323+
}
324+
325+
#pragma clang diagnostic pop
326+
292327
/// Determine whether we are currently executing on the main thread
293328
/// independently of whether we know that we are on the main actor.
294329
static bool isExecutingOnMainThread() {
+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if !$Embedded && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS))
14+
15+
import Swift
16+
17+
internal import Darwin
18+
19+
// .. Dynamic binding ..........................................................
20+
21+
enum CoreFoundation {
22+
static let path =
23+
"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
24+
25+
static let handle = dlopen(path, RTLD_NOLOAD)
26+
27+
static var isPresent: Bool { return handle != nil }
28+
29+
static func symbol<T>(_ name: String) -> T {
30+
guard let result = dlsym(handle, name) else {
31+
fatalError("Unable to look up \(name) in CoreFoundation")
32+
}
33+
return unsafeBitCast(result, to: T.self)
34+
}
35+
36+
static let CFRunLoopRun: @convention(c) () -> () =
37+
symbol("CFRunLoopRun")
38+
static let CFRunLoopGetMain: @convention(c) () -> OpaquePointer =
39+
symbol("CFRunLoopGetMain")
40+
static let CFRunLoopStop: @convention(c) (OpaquePointer) -> () =
41+
symbol("CFRunLoopStop")
42+
}
43+
44+
// .. Main Executor ............................................................
45+
46+
@available(SwiftStdlib 6.2, *)
47+
public final class CFMainExecutor: DispatchMainExecutor, @unchecked Sendable {
48+
49+
override public func run() throws {
50+
CoreFoundation.CFRunLoopRun()
51+
}
52+
53+
override public func stop() {
54+
CoreFoundation.CFRunLoopStop(CoreFoundation.CFRunLoopGetMain())
55+
}
56+
57+
}
58+
59+
// .. Task Executor ............................................................
60+
61+
@available(SwiftStdlib 6.2, *)
62+
public final class CFTaskExecutor: DispatchTaskExecutor, @unchecked Sendable {
63+
64+
}
65+
66+
#endif

stdlib/public/Concurrency/CMakeLists.txt

+20-16
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ set(SWIFT_RUNTIME_CONCURRENCY_C_SOURCES
7979
ConcurrencyHooks.cpp
8080
EmbeddedSupport.cpp
8181
Error.cpp
82+
ExecutorBridge.cpp
8283
ExecutorChecks.cpp
8384
Setup.cpp
8485
Task.cpp
@@ -92,25 +93,13 @@ set(SWIFT_RUNTIME_CONCURRENCY_C_SOURCES
9293
linker-support/magic-symbols-for-install-name.c
9394
)
9495

95-
if("${SWIFT_CONCURRENCY_GLOBAL_EXECUTOR}" STREQUAL "dispatch")
96-
set(SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES
97-
DispatchGlobalExecutor.cpp
98-
)
99-
elseif("${SWIFT_CONCURRENCY_GLOBAL_EXECUTOR}" STREQUAL "singlethreaded")
100-
set(SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES
101-
CooperativeGlobalExecutor.cpp
102-
)
103-
elseif("${SWIFT_CONCURRENCY_GLOBAL_EXECUTOR}" STREQUAL "hooked" OR
104-
"${SWIFT_CONCURRENCY_GLOBAL_EXECUTOR}" STREQUAL "none")
105-
set(SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES
106-
NonDispatchGlobalExecutor.cpp
96+
set(SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES
97+
DispatchGlobalExecutor.cpp
10798
)
108-
endif()
10999

110100
set(LLVM_OPTIONAL_SOURCES
111101
CooperativeGlobalExecutor.cpp
112102
DispatchGlobalExecutor.cpp
113-
NonDispatchGlobalExecutor.cpp
114103
)
115104

116105
set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
@@ -119,6 +108,7 @@ set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
119108
CheckedContinuation.swift
120109
Errors.swift
121110
Executor.swift
111+
ExecutorBridge.swift
122112
ExecutorAssertions.swift
123113
AsyncCompactMapSequence.swift
124114
AsyncDropFirstSequence.swift
@@ -136,10 +126,10 @@ set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
136126
AsyncThrowingFlatMapSequence.swift
137127
AsyncThrowingMapSequence.swift
138128
AsyncThrowingPrefixWhileSequence.swift
129+
ExecutorJob.swift
139130
GlobalActor.swift
140131
GlobalConcurrentExecutor.swift
141132
MainActor.swift
142-
PartialAsyncTask.swift
143133
SourceCompatibilityShims.swift
144134
Task.swift
145135
Task+PriorityEscalation.swift
@@ -176,12 +166,26 @@ set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
176166
ContinuousClock.swift
177167
SuspendingClock.swift
178168
TaskSleepDuration.swift
169+
DispatchExecutor.swift
170+
CFExecutor.swift
171+
PlatformExecutorDarwin.swift
172+
PlatformExecutorLinux.swift
173+
PlatformExecutorWindows.swift
179174
)
180175

176+
set(SWIFT_RUNTIME_CONCURRENCY_NONEMBEDDED_SWIFT_SOURCES
177+
ExecutorImpl.swift
178+
)
179+
180+
set(SWIFT_RUNTIME_CONCURRENCY_EMBEDDED_SWIFT_SOURCES
181+
PlatformExecutorEmbedded.swift
182+
)
183+
181184
add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB
182185
${SWIFT_RUNTIME_CONCURRENCY_C_SOURCES}
183186
${SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES}
184187
${SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES}
188+
${SWIFT_RUNTIME_CONCURRENCY_NONEMBEDDED_SWIFT_SOURCES}
185189

186190
GYB_SOURCES
187191
Task+startSynchronously.swift.gyb
@@ -278,6 +282,7 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB AND SWIFT_SHOULD_BUILD_EMBEDDED_CONCURRENC
278282

279283
${SWIFT_RUNTIME_CONCURRENCY_C_SOURCES}
280284
${SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES}
285+
${SWIFT_RUNTIME_CONCURRENCY_EMBEDDED_SWIFT_SOURCES}
281286

282287
SWIFT_COMPILE_FLAGS
283288
${extra_swift_compile_flags} -enable-experimental-feature Embedded
@@ -357,4 +362,3 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB AND SWIFT_SHOULD_BUILD_EMBEDDED_CONCURRENC
357362

358363
add_dependencies(embedded-concurrency "copy_executor_impl_header")
359364
endif()
360-

stdlib/public/Concurrency/Clock.swift

+100-8
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@
1111
//===----------------------------------------------------------------------===//
1212
import Swift
1313

14-
/// A mechanism in which to measure time, and delay work until a given point
14+
/// A mechanism in which to measure time, and delay work until a given point
1515
/// in time.
1616
///
17-
/// Types that conform to the `Clock` protocol define a concept of "now" which
17+
/// Types that conform to the `Clock` protocol define a concept of "now" which
1818
/// is the specific instant in time that property is accessed. Any pair of calls
1919
/// to the `now` property may have a minimum duration between them - this
2020
/// minimum resolution is exposed by the `minimumResolution` property to inform
21-
/// any user of the type the expected granularity of accuracy.
21+
/// any user of the type the expected granularity of accuracy.
2222
///
2323
/// One of the primary uses for clocks is to schedule task sleeping. This method
2424
/// resumes the calling task after a given deadline has been met or passed with
25-
/// a given tolerance value. The tolerance is expected as a leeway around the
26-
/// deadline. The clock may reschedule tasks within the tolerance to ensure
25+
/// a given tolerance value. The tolerance is expected as a leeway around the
26+
/// deadline. The clock may reschedule tasks within the tolerance to ensure
2727
/// efficient execution of resumptions by reducing potential operating system
2828
/// wake-ups. If no tolerance is specified (i.e. nil is passed in) the sleep
29-
/// function is expected to schedule with a default tolerance strategy.
29+
/// function is expected to schedule with a default tolerance strategy.
3030
///
31-
/// For more information about specific clocks see `ContinuousClock` and
31+
/// For more information about specific clocks see `ContinuousClock` and
3232
/// `SuspendingClock`.
3333
@available(SwiftStdlib 5.7, *)
3434
public protocol Clock<Duration>: Sendable {
@@ -41,8 +41,56 @@ public protocol Clock<Duration>: Sendable {
4141
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
4242
func sleep(until deadline: Instant, tolerance: Instant.Duration?) async throws
4343
#endif
44-
}
4544

45+
#if !$Embedded
46+
/// Choose which Dispatch clock to use with DispatchExecutor
47+
///
48+
/// This controls which Dispatch clock is used to enqueue delayed jobs
49+
/// when using this Clock.
50+
@available(SwiftStdlib 6.2, *)
51+
var dispatchClockID: DispatchClockID { get }
52+
#endif
53+
54+
/// Convert a Clock-specific Duration to a Swift Duration
55+
///
56+
/// Some clocks may define `C.Duration` to be something other than a
57+
/// `Swift.Duration`, but that makes it tricky to convert timestamps
58+
/// between clocks, which is something we want to be able to support.
59+
/// This method will convert whatever `C.Duration` is to a `Swift.Duration`.
60+
///
61+
/// Parameters:
62+
///
63+
/// - from duration: The `Duration` to convert
64+
///
65+
/// Returns: A `Swift.Duration` representing the equivalent duration, or
66+
/// `nil` if this function is not supported.
67+
@available(SwiftStdlib 6.2, *)
68+
func convert(from duration: Duration) -> Swift.Duration?
69+
70+
/// Convert a Swift Duration to a Clock-specific Duration
71+
///
72+
/// Parameters:
73+
///
74+
/// - from duration: The `Swift.Duration` to convert.
75+
///
76+
/// Returns: A `Duration` representing the equivalent duration, or
77+
/// `nil` if this function is not supported.
78+
@available(SwiftStdlib 6.2, *)
79+
func convert(from duration: Swift.Duration) -> Duration?
80+
81+
/// Convert an `Instant` from some other clock's `Instant`
82+
///
83+
/// Parameters:
84+
///
85+
/// - instant: The instant to convert.
86+
// - from clock: The clock to convert from.
87+
///
88+
/// Returns: An `Instant` representing the equivalent instant, or
89+
/// `nil` if this function is not supported.
90+
@available(SwiftStdlib 6.2, *)
91+
func convert<OtherClock: Clock>(instant: OtherClock.Instant,
92+
from clock: OtherClock) -> Instant?
93+
}
4694

4795
@available(SwiftStdlib 5.7, *)
4896
extension Clock {
@@ -88,6 +136,50 @@ extension Clock {
88136
}
89137
}
90138

139+
@available(SwiftStdlib 6.2, *)
140+
extension Clock {
141+
#if !$Embedded
142+
public var dispatchClockID: DispatchClockID {
143+
return .suspending
144+
}
145+
#endif
146+
147+
// For compatibility, return `nil` if this is not implemented
148+
public func convert(from duration: Duration) -> Swift.Duration? {
149+
return nil
150+
}
151+
152+
public func convert(from duration: Swift.Duration) -> Duration? {
153+
return nil
154+
}
155+
156+
public func convert<OtherClock: Clock>(instant: OtherClock.Instant,
157+
from clock: OtherClock) -> Instant? {
158+
let ourNow = now
159+
let otherNow = clock.now
160+
let otherDuration = otherNow.duration(to: instant)
161+
162+
// Convert to `Swift.Duration`
163+
guard let duration = clock.convert(from: otherDuration) else {
164+
return nil
165+
}
166+
167+
// Convert from `Swift.Duration`
168+
guard let ourDuration = convert(from: duration) else {
169+
return nil
170+
}
171+
172+
return ourNow.advanced(by: ourDuration)
173+
}
174+
}
175+
176+
@available(SwiftStdlib 6.2, *)
177+
extension Clock where Duration == Swift.Duration {
178+
public func convert(from duration: Duration) -> Duration? {
179+
return duration
180+
}
181+
}
182+
91183
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
92184
@available(SwiftStdlib 5.7, *)
93185
extension Clock {

0 commit comments

Comments
 (0)