Skip to content

[Executors] Make move to ExecutorJob binary compatible; deprecate Job #65455

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -3879,8 +3879,13 @@ 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.
/// Get the move-only `enqueue(ExecutorJob)` protocol requirement function on the `Executor` protocol.
AbstractFunctionDecl *getExecutorOwnedEnqueueFunction() const;
/// This method should be deprecated and removed
/// Get the move-only `enqueue(Job)` protocol requirement function on the `Executor` protocol.
AbstractFunctionDecl *getExecutorLegacyOwnedEnqueueFunction() const;
/// Get the move-only `enqueue(UnownedJob)` protocol requirement function on the `Executor` protocol.
AbstractFunctionDecl *getExecutorLegacyUnownedEnqueueFunction() const;

/// Collect the set of protocols to which this type should implicitly
/// conform, such as AnyObject (for classes).
Expand Down
6 changes: 5 additions & 1 deletion include/swift/AST/DiagnosticsSema.def
Original file line number Diff line number Diff line change
Expand Up @@ -6517,7 +6517,11 @@ WARNING(hashvalue_implementation,Deprecation,

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",
"conform type %0 to 'Executor' by implementing 'func enqueue(ExecutorJob)' instead",
(Type))
WARNING(executor_enqueue_deprecated_owned_job_implementation,Deprecation,
"'Executor.enqueue(Job)' is deprecated as a protocol requirement; "
"conform type %0 to 'Executor' by implementing 'func enqueue(ExecutorJob)' instead",
(Type))

//------------------------------------------------------------------------------
Expand Down
86 changes: 84 additions & 2 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5318,6 +5318,48 @@ VarDecl *NominalTypeDecl::getGlobalActorInstance() const {
AbstractFunctionDecl *
NominalTypeDecl::getExecutorOwnedEnqueueFunction() const {
auto &C = getASTContext();
StructDecl *executorJobDecl = C.getExecutorJobDecl();
if (!executorJobDecl)
return nullptr;

auto proto = dyn_cast<ProtocolDecl>(this);
if (!proto)
return nullptr;

llvm::SmallVector<ValueDecl *, 2> 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<ProtocolDecl>(candidate->getDeclContext()))
continue;

if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
auto params = funcDecl->getParameters();

if (params->size() != 1)
continue;

if ((params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned ||
params->get(0)->getSpecifier() == ParamSpecifier::Consuming) &&
params->get(0)->getInterfaceType()->isEqual(executorJobDecl->getDeclaredInterfaceType())) {
return funcDecl;
}
}
}

return nullptr;
}

AbstractFunctionDecl *
NominalTypeDecl::getExecutorLegacyOwnedEnqueueFunction() const {
auto &C = getASTContext();
StructDecl *legacyJobDecl = C.getJobDecl();
if (!legacyJobDecl)
return nullptr;

auto proto = dyn_cast<ProtocolDecl>(this);
if (!proto)
Expand All @@ -5335,11 +5377,51 @@ NominalTypeDecl::getExecutorOwnedEnqueueFunction() const {
continue;

if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
if (funcDecl->getParameters()->size() != 1)
auto params = funcDecl->getParameters();

if (params->size() != 1)
continue;

if ((params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned ||
params->get(0)->getSpecifier() == ParamSpecifier::Consuming) &&
params->get(0)->getType()->isEqual(legacyJobDecl->getDeclaredInterfaceType())) {
return funcDecl;
}
}
}

return nullptr;
}

AbstractFunctionDecl *
NominalTypeDecl::getExecutorLegacyUnownedEnqueueFunction() const {
auto &C = getASTContext();
StructDecl *unownedJobDecl = C.getUnownedJobDecl();
if (!unownedJobDecl)
return nullptr;

auto proto = dyn_cast<ProtocolDecl>(this);
if (!proto)
return nullptr;

llvm::SmallVector<ValueDecl *, 2> 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<ProtocolDecl>(candidate->getDeclContext()))
continue;

if (auto *funcDecl = dyn_cast<AbstractFunctionDecl>(candidate)) {
auto params = funcDecl->getParameters();
if (params->get(0)->getSpecifier() == ParamSpecifier::LegacyOwned) { // TODO: make this Consuming

if (params->size() != 1)
continue;

if (params->get(0)->getType()->isEqual(unownedJobDecl->getDeclaredInterfaceType())) {
return funcDecl;
}
}
Expand Down
95 changes: 59 additions & 36 deletions lib/Sema/TypeCheckConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1269,8 +1269,9 @@ void swift::tryDiagnoseExecutorConformance(ASTContext &C,
// enqueue(_:)
auto enqueueDeclName = DeclName(C, DeclBaseName(C.Id_enqueue), { Identifier() });

FuncDecl *unownedEnqueueRequirement = nullptr;
FuncDecl *moveOnlyEnqueueRequirement = nullptr;
FuncDecl *legacyMoveOnlyEnqueueRequirement = nullptr; // TODO: preferably we'd want to remove handling of `enqueue(Job)` when able to
FuncDecl *unownedEnqueueRequirement = nullptr;
for (auto req: proto->getProtocolRequirements()) {
auto *funcDecl = dyn_cast<FuncDecl>(req);
if (!funcDecl)
Expand All @@ -1282,61 +1283,83 @@ void swift::tryDiagnoseExecutorConformance(ASTContext &C,
// look for the first parameter being a Job or UnownedJob
if (funcDecl->getParameters()->size() != 1)
continue;

if (auto param = funcDecl->getParameters()->front()) {
StructDecl *executorJobDecl = C.getExecutorJobDecl();
StructDecl *legacyJobDecl = C.getJobDecl();
StructDecl *unownedJobDecl = C.getUnownedJobDecl();
StructDecl *jobDecl = nullptr;
if (auto executorJobDecl = C.getExecutorJobDecl()) {
jobDecl = executorJobDecl;
} else if (auto plainJobDecl = C.getJobDecl()) {
// old standard library, before we introduced the `typealias Job = ExecutorJob`
jobDecl = plainJobDecl;
}

if (jobDecl &&
param->getType()->isEqual(jobDecl->getDeclaredInterfaceType())) {
if (executorJobDecl && param->getType()->isEqual(executorJobDecl->getDeclaredInterfaceType())) {
assert(moveOnlyEnqueueRequirement == nullptr);
moveOnlyEnqueueRequirement = funcDecl;
} else if (unownedJobDecl &&
param->getType()->isEqual(unownedJobDecl->getDeclaredInterfaceType())) {
} else if (legacyJobDecl && param->getType()->isEqual(legacyJobDecl->getDeclaredInterfaceType())) {
assert(legacyMoveOnlyEnqueueRequirement == nullptr);
legacyMoveOnlyEnqueueRequirement = funcDecl;
} else if (unownedJobDecl && param->getType()->isEqual(unownedJobDecl->getDeclaredInterfaceType())) {
assert(unownedEnqueueRequirement == nullptr);
unownedEnqueueRequirement = funcDecl;
}
}

// if we found both, we're done here and break out of the loop
if (unownedEnqueueRequirement && moveOnlyEnqueueRequirement)
// if we found all potential requirements, we're done here and break out of the loop
if (unownedEnqueueRequirement &&
moveOnlyEnqueueRequirement &&
legacyMoveOnlyEnqueueRequirement)
break; // we're done looking for the requirements
}

auto conformance = module->lookupConformance(nominalTy, proto);
auto concreteConformance = conformance.getConcrete();
assert(unownedEnqueueRequirement && "could not find the enqueue(UnownedJob) requirement, which should be always there");

// try to find at least a single implementations of enqueue(_:)
ConcreteDeclRef unownedEnqueueWitness = concreteConformance->getWitnessDeclRef(unownedEnqueueRequirement);
ValueDecl *unownedEnqueueWitnessDecl = unownedEnqueueWitness.getDecl();
ValueDecl *moveOnlyEnqueueWitnessDecl = nullptr;
ValueDecl *legacyMoveOnlyEnqueueWitnessDecl = nullptr;

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 (moveOnlyEnqueueRequirement) {
moveOnlyEnqueueWitnessDecl = concreteConformance->getWitnessDeclRef(
moveOnlyEnqueueRequirement).getDecl();
}
if (legacyMoveOnlyEnqueueRequirement) {
legacyMoveOnlyEnqueueWitnessDecl = concreteConformance->getWitnessDeclRef(
legacyMoveOnlyEnqueueRequirement).getDecl();
}

if (auto unownedEnqueueDecl = unownedEnqueueWitness.getDecl()) {
if (moveOnlyEnqueueRequirement) {
ConcreteDeclRef moveOnlyEnqueueWitness = concreteConformance->getWitnessDeclRef(moveOnlyEnqueueRequirement);
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);
}
}
// --- Diagnose warnings and errors

// Old UnownedJob based impl is present, warn about it suggesting the new protocol requirement.
if (unownedEnqueueWitnessDecl && unownedEnqueueWitnessDecl->getLoc().isValid()) {
diags.diagnose(unownedEnqueueWitnessDecl->getLoc(), diag::executor_enqueue_unowned_implementation, nominalTy);
}
// Old Job based impl is present, warn about it suggesting the new protocol requirement.
if (legacyMoveOnlyEnqueueWitnessDecl && legacyMoveOnlyEnqueueWitnessDecl->getLoc().isValid()) {
diags.diagnose(legacyMoveOnlyEnqueueWitnessDecl->getLoc(), diag::executor_enqueue_deprecated_owned_job_implementation, nominalTy);
}

if ((!unownedEnqueueWitnessDecl || unownedEnqueueWitnessDecl->getLoc().isInvalid()) &&
(!moveOnlyEnqueueWitnessDecl || moveOnlyEnqueueWitnessDecl->getLoc().isInvalid()) &&
(!legacyMoveOnlyEnqueueWitnessDecl || legacyMoveOnlyEnqueueWitnessDecl->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.
//
// If we're running against an SDK that does not have the ExecutorJob enqueue function,
// try to diagnose using the next-best one available.
auto missingRequirement = C.getExecutorDecl()->getExecutorOwnedEnqueueFunction();
if (!missingRequirement)
missingRequirement = C.getExecutorDecl()->getExecutorLegacyOwnedEnqueueFunction();
if (!missingRequirement)
missingRequirement = C.getExecutorDecl()->getExecutorLegacyUnownedEnqueueFunction();

if (missingRequirement) {
nominal->diagnose(diag::type_does_not_conform, nominalTy, proto->getDeclaredInterfaceType());
missingRequirement->diagnose(diag::no_witnesses,
getProtocolRequirementKind(missingRequirement),
missingRequirement->getName(),
missingRequirement->getParameters()->get(0)->getInterfaceType(),
/*AddFixIt=*/true);
return;
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions stdlib/public/Concurrency/Executor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ public protocol Executor: AnyObject, Sendable {
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
func enqueue(_ job: UnownedJob)

#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
@available(*, deprecated, message: "Use enqueue(ExecutorJob) instead")
func enqueue(_ job: __owned Job)
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY

#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@available(SwiftStdlib 5.9, *)
func enqueue(_ job: __owned ExecutorJob)
Expand All @@ -46,6 +52,17 @@ public protocol SerialExecutor: Executor {
#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
// work-scheduling operation.
@_nonoverride
@available(macOS 9999, iOS 9999, watchOS 9999, tvOS 9999, *)
@available(*, deprecated, message: "Use enqueue(ExecutorJob) instead")
func enqueue(_ job: __owned Job)
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY

#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
Expand Down Expand Up @@ -92,6 +109,10 @@ extension Executor {
public func enqueue(_ job: __owned ExecutorJob) {
self.enqueue(UnownedJob(job))
}

public func enqueue(_ job: __owned Job) {
self.enqueue(UnownedJob(job))
}
}
#endif // !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY

Expand Down
Loading