Skip to content

Commit 38c89bf

Browse files
authored
Merge pull request swiftlang#63491 from ktoso/wip-custom-executor-not-directly-on-actor
[CustomExecutors] unownedExecutor can be declared not directly on actor
2 parents c644be4 + 6b95ff7 commit 38c89bf

File tree

6 files changed

+214
-27
lines changed

6 files changed

+214
-27
lines changed

include/swift/Runtime/Concurrency.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,13 @@ void swift_task_enqueueGlobalWithDeadline(long long sec, long long nsec,
729729
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
730730
void swift_task_enqueueMainExecutor(Job *job);
731731

732+
/// Return true if the caller is running in a Task on the passed Executor.
733+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
734+
bool swift_task_isOnExecutor(
735+
HeapObject * executor,
736+
const Metadata *selfType,
737+
const SerialExecutorWitnessTable *wtable);
738+
732739
#if SWIFT_CONCURRENCY_ENABLE_DISPATCH
733740

734741
/// Enqueue the given job on the main executor.
@@ -766,6 +773,17 @@ SWIFT_CC(swift) void (*swift_task_enqueueGlobalWithDeadline_hook)(
766773
int clock, Job *job,
767774
swift_task_enqueueGlobalWithDeadline_original original);
768775

776+
typedef SWIFT_CC(swift) bool (*swift_task_isOnExecutor_original)(
777+
HeapObject *executor,
778+
const Metadata *selfType,
779+
const SerialExecutorWitnessTable *wtable);
780+
SWIFT_EXPORT_FROM(swift_Concurrency)
781+
SWIFT_CC(swift) bool (*swift_task_isOnExecutor_hook)(
782+
HeapObject *executor,
783+
const Metadata *selfType,
784+
const SerialExecutorWitnessTable *wtable,
785+
swift_task_isOnExecutor_original original);
786+
769787
/// A hook to take over main executor enqueueing.
770788
typedef SWIFT_CC(swift) void (*swift_task_enqueueMainExecutor_original)(
771789
Job *job);

lib/AST/Decl.cpp

Lines changed: 15 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -9365,38 +9365,26 @@ Type TypeBase::getSwiftNewtypeUnderlyingType() {
93659365
}
93669366

93679367
const VarDecl *ClassDecl::getUnownedExecutorProperty() const {
9368-
auto &ctx = getASTContext();
9368+
auto &C = getASTContext();
93699369

9370-
auto hasUnownedSerialExecutorType = [&](VarDecl *property) {
9371-
if (auto type = property->getInterfaceType())
9372-
if (auto td = type->getAnyNominal())
9373-
if (td == ctx.getUnownedSerialExecutorDecl())
9374-
return true;
9375-
return false;
9376-
};
9370+
if (!isAnyActor())
9371+
return nullptr;
93779372

9378-
VarDecl *candidate = nullptr;
9379-
for (auto member: getMembers()) {
9380-
// Instance properties called unownedExecutor.
9381-
if (auto property = dyn_cast<VarDecl>(member)) {
9382-
if (property->getName() == ctx.Id_unownedExecutor &&
9383-
!property->isStatic()) {
9384-
if (!candidate) {
9385-
candidate = property;
9386-
continue;
9387-
}
9373+
llvm::SmallVector<ValueDecl *, 2> results;
9374+
this->lookupQualified(getSelfNominalTypeDecl(),
9375+
DeclNameRef(C.Id_unownedExecutor),
9376+
NL_ProtocolMembers,
9377+
results);
93889378

9389-
bool oldHasRightType = hasUnownedSerialExecutorType(candidate);
9390-
if (oldHasRightType == hasUnownedSerialExecutorType(property)) {
9391-
// just ignore the new property, we should diagnose this eventually
9392-
} else if (!oldHasRightType) {
9393-
candidate = property;
9394-
}
9395-
}
9396-
}
9379+
for (auto candidate: results) {
9380+
if (isa<ProtocolDecl>(candidate->getDeclContext()))
9381+
continue;
9382+
9383+
if (VarDecl *var = dyn_cast<VarDecl>(candidate))
9384+
return var;
93979385
}
93989386

9399-
return candidate;
9387+
return nullptr;
94009388
}
94019389

94029390
bool ClassDecl::isRootDefaultActor() const {

stdlib/public/Concurrency/Executor.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,18 @@ public struct UnownedSerialExecutor: Sendable {
6969
}
7070
}
7171

72+
/// Checks if the current task is running on the expected executor.
73+
///
74+
/// Generally, Swift programs should be constructed such that it is statically
75+
/// known that a specific executor is used, for example by using global actors or
76+
/// custom executors. However, in some APIs it may be useful to provide an
77+
/// additional runtime check for this, especially when moving towards Swift
78+
/// concurrency from other runtimes which frequently use such assertions.
79+
/// - Parameter executor: The expected executor.
80+
@available(SwiftStdlib 5.9, *)
81+
@_silgen_name("swift_task_isOnExecutor")
82+
public func _taskIsOnExecutor<Executor: SerialExecutor>(_ executor: Executor) -> Bool
83+
7284
// Used by the concurrency runtime
7385
@available(SwiftStdlib 5.1, *)
7486
@_silgen_name("_swift_task_enqueueOnExecutor")

stdlib/public/Concurrency/GlobalExecutor.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ void (*swift::swift_task_enqueueGlobalWithDeadline_hook)(
7979
int clock, Job *job,
8080
swift_task_enqueueGlobalWithDeadline_original original) = nullptr;
8181

82+
SWIFT_CC(swift)
83+
bool (*swift::swift_task_isOnExecutor_hook)(
84+
HeapObject *executor,
85+
const Metadata *selfType,
86+
const SerialExecutorWitnessTable *wtable,
87+
swift_task_isOnExecutor_original original) = nullptr;
88+
8289
SWIFT_CC(swift)
8390
void (*swift::swift_task_enqueueMainExecutor_hook)(
8491
Job *job, swift_task_enqueueMainExecutor_original original) = nullptr;
@@ -125,6 +132,28 @@ void swift::swift_task_enqueueGlobalWithDeadline(
125132
swift_task_enqueueGlobalWithDeadlineImpl(sec, nsec, tsec, tnsec, clock, job);
126133
}
127134

135+
SWIFT_CC(swift)
136+
static bool swift_task_isOnExecutorImpl(HeapObject *executor,
137+
const Metadata *selfType,
138+
const SerialExecutorWitnessTable *wtable) {
139+
auto executorRef = ExecutorRef::forOrdinary(executor, wtable);
140+
return swift_task_isCurrentExecutor(executorRef);
141+
}
142+
143+
bool swift::swift_task_isOnExecutor(HeapObject *executor,
144+
const Metadata *selfType,
145+
const SerialExecutorWitnessTable *wtable) {
146+
if (swift_task_isOnExecutor_hook)
147+
return swift_task_isOnExecutor_hook(
148+
executor, selfType, wtable, swift_task_isOnExecutorImpl);
149+
else
150+
return swift_task_isOnExecutorImpl(executor, selfType, wtable);
151+
}
152+
153+
/*****************************************************************************/
154+
/****************************** MAIN EXECUTOR *******************************/
155+
/*****************************************************************************/
156+
128157
void swift::swift_task_enqueueMainExecutor(Job *job) {
129158
concurrency::trace::job_enqueue_main_executor(job);
130159
if (swift_task_enqueueMainExecutor_hook)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// RUN: %target-run-simple-swift( -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+
11+
actor Simple {
12+
var count = 0
13+
func report() {
14+
print("simple.count == \(count)")
15+
count += 1
16+
}
17+
}
18+
19+
actor Custom {
20+
var count = 0
21+
let simple = Simple()
22+
23+
func report() async {
24+
print("custom.count == \(count)")
25+
count += 1
26+
27+
await simple.report()
28+
}
29+
}
30+
31+
@available(SwiftStdlib 5.1, *)
32+
@main struct Main {
33+
static func main() async {
34+
print("begin")
35+
let actor = Custom()
36+
await actor.report()
37+
await actor.report()
38+
await actor.report()
39+
print("end")
40+
}
41+
}
42+
43+
// CHECK: begin
44+
// CHECK-NEXT: custom.count == 0
45+
// CHECK-NEXT: simple.count == 0
46+
// CHECK-NEXT: custom.count == 1
47+
// CHECK-NEXT: simple.count == 1
48+
// CHECK-NEXT: custom.count == 2
49+
// CHECK-NEXT: simple.count == 2
50+
// CHECK-NEXT: end
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
// RUN: %target-run-simple-swift( -Xfrontend -disable-availability-checking %import-libdispatch -parse-as-library) | %FileCheck %s
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: executable_test
5+
// REQUIRES: libdispatch
6+
// UNSUPPORTED: freestanding
7+
8+
// UNSUPPORTED: back_deployment_runtime
9+
// REQUIRES: concurrency_runtime
10+
11+
import Dispatch
12+
13+
func checkIfMainQueue(expectedAnswer expected: Bool) {
14+
dispatchPrecondition(condition: expected ? .onQueue(DispatchQueue.main)
15+
: .notOnQueue(DispatchQueue.main))
16+
}
17+
18+
protocol WithSpecifiedExecutor: Actor {
19+
nonisolated var executor: SpecifiedExecutor { get }
20+
}
21+
22+
protocol SpecifiedExecutor: SerialExecutor {}
23+
24+
extension WithSpecifiedExecutor {
25+
/// Establishes the WithSpecifiedExecutorExecutor as the serial
26+
/// executor that will coordinate execution for the actor.
27+
nonisolated var unownedExecutor: UnownedSerialExecutor {
28+
executor.asUnownedSerialExecutor()
29+
}
30+
}
31+
32+
final class InlineExecutor: SpecifiedExecutor, Swift.CustomStringConvertible {
33+
let name: String
34+
35+
init(_ name: String) {
36+
self.name = name
37+
}
38+
39+
public func enqueue(_ job: UnownedJob) {
40+
print("\(self): enqueue")
41+
job._runSynchronously(on: self.asUnownedSerialExecutor())
42+
print("\(self): after run")
43+
}
44+
45+
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
46+
return UnownedSerialExecutor(ordinary: self)
47+
}
48+
49+
var description: Swift.String {
50+
"InlineExecutor(\(name))"
51+
}
52+
}
53+
54+
actor MyActor: WithSpecifiedExecutor {
55+
56+
nonisolated let executor: SpecifiedExecutor
57+
58+
// Note that we don't have to provide the unownedExecutor in the actor itself.
59+
// We obtain it from the extension on `WithSpecifiedExecutor`.
60+
61+
init(executor: SpecifiedExecutor) {
62+
self.executor = executor
63+
}
64+
65+
func test(expectedExecutor: some SerialExecutor) {
66+
precondition(_taskIsOnExecutor(expectedExecutor), "Expected to be on: \(expectedExecutor)")
67+
checkIfMainQueue(expectedAnswer: true)
68+
print("\(Self.self): on executor \(expectedExecutor)")
69+
}
70+
}
71+
72+
@main struct Main {
73+
static func main() async {
74+
print("begin")
75+
let one = InlineExecutor("one")
76+
let actor = MyActor(executor: one)
77+
await actor.test(expectedExecutor: one)
78+
await actor.test(expectedExecutor: one)
79+
await actor.test(expectedExecutor: one)
80+
print("end")
81+
}
82+
}
83+
84+
// CHECK: begin
85+
// CHECK-NEXT: InlineExecutor(one): enqueue
86+
// CHECK-NEXT: MyActor: on executor InlineExecutor(one)
87+
// CHECK-NEXT: MyActor: on executor InlineExecutor(one)
88+
// CHECK-NEXT: MyActor: on executor InlineExecutor(one)
89+
// CHECK-NEXT: InlineExecutor(one): after run
90+
// CHECK-NEXT: end

0 commit comments

Comments
 (0)