Skip to content

Commit 054c6af

Browse files
committed
[Concurrency] Move-only Job, in addition to UnownedJob
1 parent 934b0dd commit 054c6af

File tree

11 files changed

+202
-11
lines changed

11 files changed

+202
-11
lines changed

include/swift/ABI/Task.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ class alignas(2 * alignof(void*)) Job :
131131
return Flags.getPriority();
132132
}
133133

134+
uint32_t getJobId() const {
135+
return Id;
136+
}
137+
134138
/// Given that we've fully established the job context in the current
135139
/// thread, actually start running this job. To establish the context
136140
/// correctly, call swift_job_run or runJobInExecutorContext.

include/swift/Runtime/Concurrency.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -741,6 +741,11 @@ bool swift_task_isOnExecutor(
741741
const Metadata *selfType,
742742
const SerialExecutorWitnessTable *wtable);
743743

744+
/// Return the 64bit TaskID (if the job is an AsyncTask),
745+
/// or the 32bits of the job Id otherwise.
746+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
747+
uint64_t swift_task_getJobTaskId(Job *job);
748+
744749
#if SWIFT_CONCURRENCY_ENABLE_DISPATCH
745750

746751
/// Enqueue the given job on the main executor.

stdlib/cmake/modules/AddSwiftStdlib.cmake

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1801,7 +1801,7 @@ function(add_swift_target_library name)
18011801
if (SWIFTLIB_IS_STDLIB)
18021802
list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-warn-implicit-overrides")
18031803
list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-ossa-modules")
1804-
list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-lexical-lifetimes=false")
1804+
list(APPEND SWIFTLIB_SWIFT_COMPILE_FLAGS "-Xfrontend;-enable-lexical-lifetimes=true") # FIXME: undo this
18051805
endif()
18061806

18071807
if(NOT SWIFT_BUILD_RUNTIME_WITH_HOST_COMPILER AND NOT BUILD_STANDALONE AND

stdlib/public/BackDeployConcurrency/Executor.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ public struct UnownedSerialExecutor: Sendable {
7474
@_silgen_name("_swift_task_enqueueOnExecutor")
7575
internal func _enqueueOnExecutor<E>(job: UnownedJob, executor: E)
7676
where E: SerialExecutor {
77+
// TODO: something to do here?
7778
executor.enqueue(job)
7879
}
7980

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
148148
${SWIFT_STANDARD_LIBRARY_SWIFT_FLAGS}
149149
-parse-stdlib
150150
-Xfrontend -enable-experimental-concurrency
151+
-Xfrontend -enable-experimental-move-only # FIXME: REMOVE THIS
151152
${SWIFT_RUNTIME_CONCURRENCY_SWIFT_FLAGS}
152153
${swift_concurrency_options}
153154
INSTALL_IN_COMPONENT ${swift_concurrency_install_component}

stdlib/public/Concurrency/Executor.swift

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,44 @@ import Swift
1515
/// A service that can execute jobs.
1616
@available(SwiftStdlib 5.1, *)
1717
public protocol Executor: AnyObject, Sendable {
18+
// This requirement is repeated here as a non-override so that we
19+
// get a redundant witness-table entry for it. This allows us to
20+
// avoid drilling down to the base conformance just for the basic
21+
// work-scheduling operation.
22+
@available(*, deprecated, message: "Implement enqueueJob instead")
1823
func enqueue(_ job: UnownedJob)
24+
25+
@available(SwiftStdlib 5.9, *)
26+
func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way
1927
}
2028

2129
/// A service that executes jobs.
2230
@available(SwiftStdlib 5.1, *)
2331
public protocol SerialExecutor: Executor {
24-
// This requirement is repeated here as a non-override so that we
25-
// get a redundant witness-table entry for it. This allows us to
26-
// avoid drilling down to the base conformance just for the basic
27-
// work-scheduling operation.
32+
2833
@_nonoverride
34+
@available(*, deprecated, message: "Implement enqueueJob instead")
2935
func enqueue(_ job: UnownedJob)
3036

37+
@_nonoverride
38+
@available(SwiftStdlib 5.9, *)
39+
func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way
40+
3141
/// Convert this executor value to the optimized form of borrowed
3242
/// executor references.
3343
func asUnownedSerialExecutor() -> UnownedSerialExecutor
3444
}
3545

46+
@available(SwiftStdlib 5.9, *)
47+
extension Executor {
48+
public func enqueue(_ job: UnownedJob) { // FIXME: this is bad; how could we deploy this nicely
49+
fatalError("Please implement \(Self.self).enqueueJob(_:)")
50+
}
51+
public func enqueueJob(_ job: __owned Job) {
52+
self.enqueue(UnownedJob(job))
53+
}
54+
}
55+
3656
/// An unowned reference to a serial executor (a `SerialExecutor`
3757
/// value).
3858
///
@@ -81,12 +101,26 @@ public struct UnownedSerialExecutor: Sendable {
81101
@_silgen_name("swift_task_isOnExecutor")
82102
public func _taskIsOnExecutor<Executor: SerialExecutor>(_ executor: Executor) -> Bool
83103

104+
/// Primarily a debug utility.
105+
///
106+
/// If the passed in Job is a Task, returns the complete 64bit TaskId,
107+
/// otherwise returns only the job's 32bit Id.
108+
///
109+
/// - Returns: the Id stored in this Job or Task, for purposes of debug printing
110+
@available(SwiftStdlib 5.9, *)
111+
@_silgen_name("swift_task_getJobTaskId")
112+
internal func _getJobTaskId(_ job: UnownedJob) -> UInt64
113+
84114
// Used by the concurrency runtime
85115
@available(SwiftStdlib 5.1, *)
86116
@_silgen_name("_swift_task_enqueueOnExecutor")
87-
internal func _enqueueOnExecutor<E>(job: UnownedJob, executor: E)
117+
internal func _enqueueOnExecutor<E>(job unownedJob: UnownedJob, executor: E)
88118
where E: SerialExecutor {
89-
executor.enqueue(job)
119+
if #available(SwiftStdlib 5.9, *) {
120+
executor.enqueueJob(Job(context: unownedJob.context))
121+
} else {
122+
executor.enqueue(unownedJob)
123+
}
90124
}
91125

92126
@available(SwiftStdlib 5.1, *)

stdlib/public/Concurrency/GlobalExecutor.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,17 @@ bool swift::swift_task_isOnExecutor(HeapObject *executor,
150150
return swift_task_isOnExecutorImpl(executor, selfType, wtable);
151151
}
152152

153+
uint64_t swift::swift_task_getJobTaskId(Job *job) {
154+
if (auto task = dyn_cast<AsyncTask>(job)) {
155+
// TaskID is actually:
156+
// 32bits of Job's Id
157+
// + 32bits stored in the AsyncTask
158+
return task->getTaskId();
159+
} else {
160+
return job->getJobId();
161+
}
162+
}
163+
153164
/*****************************************************************************/
154165
/****************************** MAIN EXECUTOR *******************************/
155166
/*****************************************************************************/

stdlib/public/Concurrency/PartialAsyncTask.swift

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,18 @@ internal func _swiftJobRun(_ job: UnownedJob,
2626
@available(SwiftStdlib 5.1, *)
2727
@frozen
2828
public struct UnownedJob: Sendable {
29-
private var context: Builtin.Job
29+
internal var context: Builtin.Job
30+
31+
@usableFromInline
32+
internal init(context: Builtin.Job) {
33+
self.context = context
34+
}
35+
36+
@available(SwiftStdlib 5.9, *)
37+
@usableFromInline
38+
internal init(_ job: __owned Job) {
39+
self.context = job.context
40+
}
3041

3142
@available(SwiftStdlib 5.9, *)
3243
public var priority: Priority {
@@ -41,7 +52,7 @@ public struct UnownedJob: Sendable {
4152
}
4253
}
4354

44-
@available(SwiftStdlib 5.1, *)
55+
@available(SwiftStdlib 5.9, *)
4556
extension UnownedJob {
4657
public struct Priority {
4758
public typealias RawValue = UInt8
@@ -55,6 +66,58 @@ extension UnownedJob {
5566
}
5667
}
5768

69+
/// A unit of scheduleable work.
70+
///
71+
/// Unless you're implementing a scheduler,
72+
/// you don't generally interact with jobs directly.
73+
@available(SwiftStdlib 5.9, *)
74+
@frozen
75+
@_moveOnly
76+
public struct Job: Sendable {
77+
internal var context: Builtin.Job
78+
79+
@usableFromInline
80+
internal init(context: __owned Builtin.Job) {
81+
self.context = context
82+
}
83+
84+
public var priority: UnownedJob.Priority {
85+
// let raw = _swift_concurrency_jobPriority(UnownedJob(context: self.context)) // TODO: do we also surface this or the base priority?
86+
let raw = _taskCurrentPriority(context as! Builtin.NativeObject)
87+
return UnownedJob.Priority(rawValue: raw)
88+
}
89+
90+
// TODO: move only types cannot conform to protocols, so we can't conform to CustomStringConvertible;
91+
// we can still offer a description to be called explicitly though.
92+
public var description: String {
93+
"\(Self.self)(\(_getJobTaskId(UnownedJob(context: self.context)))"
94+
}
95+
}
96+
97+
@available(SwiftStdlib 5.9, *)
98+
extension UnownedSerialExecutor {
99+
/// Run the job synchronously.
100+
///
101+
/// This operation consumes the job.
102+
@_alwaysEmitIntoClient
103+
@inlinable
104+
public func runJobSynchronously(_ job: __owned Job) {
105+
UnownedJob(job)._runSynchronously(on: self)
106+
}
107+
}
108+
109+
@available(SwiftStdlib 5.9, *)
110+
extension SerialExecutor {
111+
/// Run the job synchronously.
112+
///
113+
/// This operation consumes the job.
114+
@_alwaysEmitIntoClient
115+
@inlinable
116+
public func runJobSynchronously(_ job: __owned Job) {
117+
UnownedJob(job)._runSynchronously(on: self.asUnownedSerialExecutor())
118+
}
119+
}
120+
58121
/// A mechanism to interface
59122
/// between synchronous and asynchronous code,
60123
/// without correctness checking.
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: executable_test
5+
// UNSUPPORTED: freestanding
6+
7+
// UNSUPPORTED: back_deployment_runtime
8+
// REQUIRES: concurrency_runtime
9+
10+
final class InlineExecutor: SerialExecutor, CustomStringConvertible {
11+
12+
public func enqueueJob(_ job: __owned Job) {
13+
print("\(self): enqueue (job: \(job.description))")
14+
runJobSynchronously(job)
15+
print("\(self): after run")
16+
}
17+
18+
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
19+
return UnownedSerialExecutor(ordinary: self)
20+
}
21+
22+
var description: Swift.String {
23+
"InlineExecutor()"
24+
}
25+
}
26+
27+
let inlineExecutor = InlineExecutor()
28+
29+
actor Custom {
30+
var count = 0
31+
32+
nonisolated var unownedExecutor: UnownedSerialExecutor {
33+
print("custom unownedExecutor")
34+
return inlineExecutor.asUnownedSerialExecutor()
35+
}
36+
37+
func report() async {
38+
print("custom.count == \(count)")
39+
count += 1
40+
}
41+
}
42+
43+
@available(SwiftStdlib 5.1, *)
44+
@main struct Main {
45+
static func main() async {
46+
print("begin")
47+
let actor = Custom()
48+
await actor.report()
49+
await actor.report()
50+
await actor.report()
51+
print("end")
52+
}
53+
}
54+
55+
// CHECK: begin
56+
// CHECK-NEXT: custom unownedExecutor
57+
// CHECK-NEXT: custom.count == 0
58+
// CHECK-NEXT: custom unownedExecutor
59+
// CHECK-NEXT: custom.count == 1
60+
// CHECK-NEXT: custom unownedExecutor
61+
// CHECK-NEXT: custom.count == 2
62+
// CHECK-NEXT: end

test/Concurrency/Runtime/custom_executors_priority.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ actor Custom {
3535
}
3636
}
3737

38-
@available(SwiftStdlib 5.1, *)
3938
@main struct Main {
4039
static func main() async {
4140
print("begin")
@@ -46,6 +45,11 @@ actor Custom {
4645
await Task() {
4746
await actor.report()
4847
}.value
48+
await Task(priority: .low) {
49+
print("P: \(Task.currentPriority)")
50+
print("B: \(Task.currentBasePriority)")
51+
await actor.report()
52+
}.value
4953
print("end")
5054
}
5155
}

test/Concurrency/Runtime/custom_executors_protocol.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s
1+
// RUN: %target-run-simple-swift( -Xfrontend -enable-experimental-move-only -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s
22

33
// REQUIRES: concurrency
44
// REQUIRES: executable_test
@@ -42,6 +42,12 @@ final class InlineExecutor: SpecifiedExecutor, Swift.CustomStringConvertible {
4242
print("\(self): after run")
4343
}
4444

45+
public func enqueueJob(_ job: __owned Job) {
46+
print("\(self): enqueue")
47+
runJobSynchronously(job)
48+
print("\(self): after run")
49+
}
50+
4551
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
4652
return UnownedSerialExecutor(ordinary: self)
4753
}

0 commit comments

Comments
 (0)