Skip to content

Commit 670c4c1

Browse files
committed
[Concurrency] Warn about "old" Executor.enqueue impl and prefer new
move new enqueue method to be named enqueue and not enqueueOwned
1 parent 4bf4d82 commit 670c4c1

20 files changed

+327
-54
lines changed

include/swift/AST/Decl.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3856,6 +3856,9 @@ class NominalTypeDecl : public GenericTypeDecl, public IterableDeclContext {
38563856
/// Find the 'RemoteCallArgument(label:name:value:)' initializer function.
38573857
ConstructorDecl *getDistributedRemoteCallArgumentInitFunction() const;
38583858

3859+
/// Get the move-only `enqueue(Job)` protocol requirement function on the `Executor` protocol.
3860+
AbstractFunctionDecl *getExecutorOwnedEnqueueFunction() const;
3861+
38593862
/// Collect the set of protocols to which this type should implicitly
38603863
/// conform, such as AnyObject (for classes).
38613864
void getImplicitProtocols(SmallVectorImpl<ProtocolDecl *> &protocols);

include/swift/AST/DiagnosticsSema.def

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6408,6 +6408,11 @@ WARNING(hashvalue_implementation,Deprecation,
64086408
"conform type %0 to 'Hashable' by implementing 'hash(into:)' instead",
64096409
(Type))
64106410

6411+
WARNING(executor_enqueue_unowned_implementation,Deprecation,
6412+
"'Executor.enqueue(UnownedJob)' is deprecated as a protocol requirement; "
6413+
"conform type %0 to 'Executor' by implementing 'func enqueue(Job)' instead",
6414+
(Type))
6415+
64116416
//------------------------------------------------------------------------------
64126417
// MARK: property wrapper diagnostics
64136418
//------------------------------------------------------------------------------

include/swift/AST/KnownProtocols.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ PROTOCOL(SIMDScalar)
7676
PROTOCOL(BinaryInteger)
7777
PROTOCOL(FixedWidthInteger)
7878
PROTOCOL(RangeReplaceableCollection)
79+
PROTOCOL(Executor)
7980
PROTOCOL(SerialExecutor)
8081
PROTOCOL(GlobalActor)
8182

include/swift/AST/KnownSDKTypes.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ KNOWN_SDK_TYPE_DECL(ObjectiveC, ObjCBool, StructDecl, 0)
3939
// standardized
4040
KNOWN_SDK_TYPE_DECL(Concurrency, UnsafeContinuation, NominalTypeDecl, 2)
4141
KNOWN_SDK_TYPE_DECL(Concurrency, MainActor, NominalTypeDecl, 0)
42+
KNOWN_SDK_TYPE_DECL(Concurrency, Job, StructDecl, 0)
43+
KNOWN_SDK_TYPE_DECL(Concurrency, UnownedJob, StructDecl, 0)
44+
KNOWN_SDK_TYPE_DECL(Concurrency, Executor, NominalTypeDecl, 0)
45+
KNOWN_SDK_TYPE_DECL(Concurrency, SerialExecutor, NominalTypeDecl, 0)
4246
KNOWN_SDK_TYPE_DECL(Concurrency, UnownedSerialExecutor, NominalTypeDecl, 0)
4347

4448
KNOWN_SDK_TYPE_DECL(Concurrency, TaskLocal, ClassDecl, 1)

lib/AST/ASTContext.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1120,6 +1120,7 @@ ProtocolDecl *ASTContext::getProtocol(KnownProtocolKind kind) const {
11201120
case KnownProtocolKind::GlobalActor:
11211121
case KnownProtocolKind::AsyncSequence:
11221122
case KnownProtocolKind::AsyncIteratorProtocol:
1123+
case KnownProtocolKind::Executor:
11231124
case KnownProtocolKind::SerialExecutor:
11241125
M = getLoadedModule(Id_Concurrency);
11251126
break;

lib/AST/Decl.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5242,6 +5242,42 @@ VarDecl *NominalTypeDecl::getGlobalActorInstance() const {
52425242
nullptr);
52435243
}
52445244

5245+
AbstractFunctionDecl *
5246+
NominalTypeDecl::getExecutorOwnedEnqueueFunction() const {
5247+
auto &C = getASTContext();
5248+
5249+
auto proto = dyn_cast<ProtocolDecl>(this);
5250+
if (!proto)
5251+
return nullptr;
5252+
5253+
llvm::SmallVector<ValueDecl *, 2> results;
5254+
lookupQualified(getSelfNominalTypeDecl(),
5255+
DeclNameRef(C.Id_enqueue),
5256+
NL_ProtocolMembers,
5257+
results);
5258+
5259+
for (auto candidate: results) {
5260+
// we're specifically looking for the Executor protocol requirement
5261+
if (!isa<ProtocolDecl>(candidate->getDeclContext()))
5262+
continue;
5263+
5264+
fprintf(stderr, "[%s:%d](%s) candidate\n", __FILE_NAME__, __LINE__, __FUNCTION__);
5265+
candidate->dump();
5266+
5267+
if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
5268+
if (funcDecl->getParameters()->size() != 1)
5269+
continue;
5270+
5271+
auto params = funcDecl->getParameters();
5272+
if (params->get(0)->getSpecifier() == ParamSpecifier::Owned) {
5273+
return funcDecl;
5274+
}
5275+
}
5276+
}
5277+
5278+
return nullptr;
5279+
}
5280+
52455281
ClassDecl::ClassDecl(SourceLoc ClassLoc, Identifier Name, SourceLoc NameLoc,
52465282
ArrayRef<InheritedEntry> Inherited,
52475283
GenericParamList *GenericParams, DeclContext *Parent,

lib/IRGen/GenMeta.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6178,6 +6178,7 @@ SpecialProtocol irgen::getSpecialProtocolID(ProtocolDecl *P) {
61786178
case KnownProtocolKind::CxxSequence:
61796179
case KnownProtocolKind::UnsafeCxxInputIterator:
61806180
case KnownProtocolKind::UnsafeCxxRandomAccessIterator:
6181+
case KnownProtocolKind::Executor:
61816182
case KnownProtocolKind::SerialExecutor:
61826183
case KnownProtocolKind::Sendable:
61836184
case KnownProtocolKind::UnsafeSendable:

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1225,6 +1225,79 @@ void swift::diagnoseMissingExplicitSendable(NominalTypeDecl *nominal) {
12251225
}
12261226
}
12271227

1228+
void swift::tryDiagnoseExecutorConformance(ASTContext &C,
1229+
const NominalTypeDecl *nominal,
1230+
ProtocolDecl *proto) {
1231+
assert(proto->isSpecificProtocol(KnownProtocolKind::Executor) ||
1232+
proto->isSpecificProtocol(KnownProtocolKind::SerialExecutor));
1233+
1234+
auto &diags = C.Diags;
1235+
auto module = nominal->getParentModule();
1236+
Type nominalTy = nominal->getDeclaredInterfaceType();
1237+
1238+
// enqueue(_: UnownedJob)
1239+
auto enqueueDeclName = DeclName(C, DeclBaseName(C.Id_enqueue), { Identifier() });
1240+
1241+
FuncDecl *unownedEnqueueRequirement = nullptr;
1242+
FuncDecl *moveOnlyEnqueueRequirement = nullptr;
1243+
for (auto req: proto->getProtocolRequirements()) {
1244+
auto *funcDecl = dyn_cast<FuncDecl>(req);
1245+
if (!funcDecl)
1246+
continue;
1247+
1248+
if (funcDecl->getName() != enqueueDeclName)
1249+
continue;
1250+
1251+
1252+
// look for the first parameter being a Job or UnownedJob
1253+
if (funcDecl->getParameters()->size() != 1)
1254+
continue;
1255+
if (auto param = funcDecl->getParameters()->front()) {
1256+
if (param->getType()->isEqual(C.getJobDecl()->getDeclaredInterfaceType())) {
1257+
assert(moveOnlyEnqueueRequirement == nullptr);
1258+
moveOnlyEnqueueRequirement = funcDecl;
1259+
} else if (param->getType()->isEqual(C.getUnownedJobDecl()->getDeclaredInterfaceType())) {
1260+
assert(unownedEnqueueRequirement == nullptr);
1261+
unownedEnqueueRequirement = funcDecl;
1262+
}
1263+
}
1264+
1265+
// if we found both, we're done here and break out of the loop
1266+
if (unownedEnqueueRequirement && moveOnlyEnqueueRequirement)
1267+
break; // we're done looking for the requirements
1268+
}
1269+
1270+
1271+
auto conformance = module->lookupConformance(nominalTy, proto);
1272+
auto concreteConformance = conformance.getConcrete();
1273+
auto unownedEnqueueWitness = concreteConformance->getWitnessDeclRef(unownedEnqueueRequirement);
1274+
auto moveOnlyEnqueueWitness = concreteConformance->getWitnessDeclRef(moveOnlyEnqueueRequirement);
1275+
1276+
if (auto enqueueUnownedDecl = unownedEnqueueWitness.getDecl()) {
1277+
// Old UnownedJob based impl is present, warn about it suggesting the new protocol requirement.
1278+
if (enqueueUnownedDecl->getLoc().isValid()) {
1279+
diags.diagnose(enqueueUnownedDecl->getLoc(), diag::executor_enqueue_unowned_implementation, nominalTy);
1280+
}
1281+
}
1282+
1283+
if (auto unownedEnqueueDecl = unownedEnqueueWitness.getDecl()) {
1284+
if (auto moveOnlyEnqueueDecl = moveOnlyEnqueueWitness.getDecl()) {
1285+
if (unownedEnqueueDecl && unownedEnqueueDecl->getLoc().isInvalid() &&
1286+
moveOnlyEnqueueDecl && moveOnlyEnqueueDecl->getLoc().isInvalid()) {
1287+
// Neither old nor new implementation have been found, but we provide default impls for them
1288+
// that are mutually recursive, so we must error and suggest implementing the right requirement.
1289+
auto ownedRequirement = C.getExecutorDecl()->getExecutorOwnedEnqueueFunction();
1290+
nominal->diagnose(diag::type_does_not_conform, nominalTy, proto->getDeclaredInterfaceType());
1291+
ownedRequirement->diagnose(diag::no_witnesses,
1292+
getProtocolRequirementKind(ownedRequirement),
1293+
ownedRequirement->getName(),
1294+
proto->getDeclaredInterfaceType(),
1295+
/*AddFixIt=*/true);
1296+
}
1297+
}
1298+
}
1299+
}
1300+
12281301
/// Determine whether this is the main actor type.
12291302
/// FIXME: the diagnostics engine has a copy of this.
12301303
static bool isMainActor(Type type) {

lib/Sema/TypeCheckConcurrency.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,6 +280,9 @@ void diagnoseMissingSendableConformance(
280280
/// state whether it conforms to Sendable, provide a diagnostic.
281281
void diagnoseMissingExplicitSendable(NominalTypeDecl *nominal);
282282

283+
/// Warn about deprecated `Executor.enqueue` implementations.
284+
void tryDiagnoseExecutorConformance(ASTContext &C, const NominalTypeDecl *nominal, ProtocolDecl *proto);
285+
283286
/// How the Sendable check should be performed.
284287
enum class SendableCheck {
285288
/// Sendable conformance was explicitly stated and should be

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6496,6 +6496,8 @@ void TypeChecker::checkConformancesInContext(IterableDeclContext *idc) {
64966496
} else if (proto->isSpecificProtocol(
64976497
KnownProtocolKind::UnsafeSendable)) {
64986498
sendableConformanceIsUnchecked = true;
6499+
} else if (proto->isSpecificProtocol(KnownProtocolKind::Executor)) {
6500+
tryDiagnoseExecutorConformance(Context, nominal, proto);
64996501
}
65006502
}
65016503

stdlib/public/BackDeployConcurrency/Actor.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,9 +285,14 @@ JobPriority swift::swift_task_getCurrentThreadPriority() {
285285
SWIFT_CC(swift)
286286
static bool swift_task_isCurrentExecutorImpl(ExecutorRef executor) {
287287
if (auto currentTracking = ExecutorTrackingInfo::current()) {
288+
fprintf(stderr, "[%s:%d](%s) current executor = %p, main:%s, default:%s\n", __FILE_NAME__, __LINE__, __FUNCTION__,
289+
currentTracking->getActiveExecutor().getIdentity(),
290+
currentTracking->getActiveExecutor().isMainExecutor() ? "y" : "n",
291+
currentTracking->getActiveExecutor().isDefaultActor() ? "y" : "n");
288292
return currentTracking->getActiveExecutor() == executor;
289293
}
290294

295+
fprintf(stderr, "[%s:%d](%s) no executor found in tracking\n", __FILE_NAME__, __LINE__, __FUNCTION__);
291296
return executor.isMainExecutor() && isExecutingOnMainThread();
292297
}
293298

stdlib/public/BackDeployConcurrency/Executor.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,10 @@ internal final class DispatchQueueShim: @unchecked Sendable, SerialExecutor {
117117
_enqueueOnDispatchQueue(job, queue: self)
118118
}
119119

120+
func enqueue(_ job: __owned Job) {
121+
_enqueueOnDispatchQueue(job, queue: self)
122+
}
123+
120124
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
121125
return UnownedSerialExecutor(ordinary: self)
122126
}

stdlib/public/Concurrency/Executor.swift

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,28 +15,31 @@ 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")
18+
@available(*, deprecated, message: "Implement 'enqueue(_: __owned Job)' instead")
2319
func enqueue(_ job: UnownedJob)
2420

2521
@available(SwiftStdlib 5.9, *)
26-
func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way
22+
func enqueue(_ job: __owned Job)
2723
}
2824

2925
/// A service that executes jobs.
3026
@available(SwiftStdlib 5.1, *)
3127
public protocol SerialExecutor: Executor {
32-
28+
// This requirement is repeated here as a non-override so that we
29+
// get a redundant witness-table entry for it. This allows us to
30+
// avoid drilling down to the base conformance just for the basic
31+
// work-scheduling operation.
3332
@_nonoverride
34-
@available(*, deprecated, message: "Implement enqueueJob instead")
33+
@available(*, deprecated, message: "Implement 'enqueue_: __owned Job) instead")
3534
func enqueue(_ job: UnownedJob)
3635

36+
// This requirement is repeated here as a non-override so that we
37+
// get a redundant witness-table entry for it. This allows us to
38+
// avoid drilling down to the base conformance just for the basic
39+
// work-scheduling operation.
3740
@_nonoverride
3841
@available(SwiftStdlib 5.9, *)
39-
func enqueueJob(_ job: __owned Job) // FIXME: figure out how to introduce in compatible way
42+
func enqueue(_ job: __owned Job)
4043

4144
/// Convert this executor value to the optimized form of borrowed
4245
/// executor references.
@@ -45,14 +48,22 @@ public protocol SerialExecutor: Executor {
4548

4649
@available(SwiftStdlib 5.9, *)
4750
extension Executor {
48-
public func enqueue(_ job: UnownedJob) { // FIXME: this is bad; how could we deploy this nicely
51+
public func enqueue(_ job: UnownedJob) {
4952
fatalError("Please implement \(Self.self).enqueueJob(_:)")
5053
}
51-
public func enqueueJob(_ job: __owned Job) {
54+
55+
public func enqueue(_ job: __owned Job) {
5256
self.enqueue(UnownedJob(job))
5357
}
5458
}
5559

60+
@available(SwiftStdlib 5.9, *)
61+
extension SerialExecutor {
62+
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
63+
UnownedSerialExecutor(ordinary: self)
64+
}
65+
}
66+
5667
/// An unowned reference to a serial executor (a `SerialExecutor`
5768
/// value).
5869
///
@@ -124,7 +135,7 @@ internal func _getJobTaskId(_ job: UnownedJob) -> UInt64
124135
internal func _enqueueOnExecutor<E>(job unownedJob: UnownedJob, executor: E)
125136
where E: SerialExecutor {
126137
if #available(SwiftStdlib 5.9, *) {
127-
executor.enqueueJob(Job(context: unownedJob.context))
138+
executor.enqueue(Job(context: unownedJob.context))
128139
} else {
129140
executor.enqueue(unownedJob)
130141
}

0 commit comments

Comments
 (0)