Skip to content

Commit b666fc6

Browse files
authored
[Executors] Remote distributed actors get "crash on enqueue" default executor (#64969)
1 parent 8d709c4 commit b666fc6

23 files changed

+255
-213
lines changed

include/swift/AST/Decl.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4535,7 +4535,6 @@ class ClassDecl final : public NominalTypeDecl {
45354535

45364536
/// Fetch this class's unownedExecutor property, if it has one.
45374537
const VarDecl *getUnownedExecutorProperty() const;
4538-
const VarDecl *getLocalUnownedExecutorProperty() const;
45394538

45404539
/// Is this the NSObject class type?
45414540
bool isNSObject() const;

include/swift/AST/KnownIdentifiers.def

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -231,8 +231,6 @@ IDENTIFIER(className)
231231
IDENTIFIER(_defaultActorInitialize)
232232
IDENTIFIER(_defaultActorDestroy)
233233
IDENTIFIER(unownedExecutor)
234-
IDENTIFIER(localUnownedExecutor)
235-
IDENTIFIER(_unwrapLocalUnownedExecutor)
236234

237235
IDENTIFIER_(ErrorType)
238236
IDENTIFIER(Code)

include/swift/AST/KnownSDKDecls.def

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
KNOWN_SDK_FUNC_DECL(Distributed, IsRemoteDistributedActor, "__isRemoteActor")
2323
KNOWN_SDK_FUNC_DECL(Distributed, IsLocalDistributedActor, "__isLocalActor")
24-
KNOWN_SDK_FUNC_DECL(Distributed, GetUnwrapLocalDistributedActorUnownedExecutor, "_getUnwrapLocalDistributedActorUnownedExecutor")
24+
KNOWN_SDK_FUNC_DECL(Distributed, BuildDefaultDistributedRemoteActorUnownedExecutor, "buildDefaultDistributedRemoteActorExecutor")
2525

2626
#undef KNOWN_SDK_FUNC_DECL
2727

lib/AST/Decl.cpp

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9648,29 +9648,6 @@ const VarDecl *ClassDecl::getUnownedExecutorProperty() const {
96489648
return nullptr;
96499649
}
96509650

9651-
const VarDecl *ClassDecl::getLocalUnownedExecutorProperty() const {
9652-
auto &C = getASTContext();
9653-
9654-
if (!isDistributedActor())
9655-
return nullptr;
9656-
9657-
llvm::SmallVector<ValueDecl *, 2> results;
9658-
this->lookupQualified(getSelfNominalTypeDecl(),
9659-
DeclNameRef(C.Id_localUnownedExecutor),
9660-
NL_ProtocolMembers,
9661-
results);
9662-
9663-
for (auto candidate: results) {
9664-
if (isa<ProtocolDecl>(candidate->getDeclContext()))
9665-
continue;
9666-
9667-
if (VarDecl *var = dyn_cast<VarDecl>(candidate))
9668-
return var;
9669-
}
9670-
9671-
return nullptr;
9672-
}
9673-
96749651
bool ClassDecl::isRootDefaultActor() const {
96759652
return isRootDefaultActor(getModuleContext(), ResilienceExpansion::Maximal);
96769653
}

lib/SILOptimizer/Mandatory/LowerHopToActor.cpp

Lines changed: 4 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -197,43 +197,11 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
197197
unmarkedExecutor =
198198
B.createBuiltin(loc, builtinName, resultType, subs, {actor});
199199

200-
// Otherwise, go through Actor.unownedExecutor.
201-
} else if (actorType->isDistributedActor()) {
202-
auto actorKind = KnownProtocolKind::DistributedActor;
203-
auto actorProtocol = ctx.getProtocol(actorKind);
204-
auto req = ctx.getGetUnwrapLocalDistributedActorUnownedExecutor();
205-
assert(req && "Distributed library broken");
206-
SILDeclRef fn(req, SILDeclRef::Kind::Func);
207-
208-
auto actorConf = module->lookupConformance(actorType, actorProtocol);
209-
assert(actorConf &&
210-
"hop_to_executor with distributed actor that doesn't conform to DistributedActor");
211-
212-
auto subs = SubstitutionMap::get(req->getGenericSignature(),
213-
{actorType}, {actorConf});
214-
215-
// Find the unwrap function
216-
FuncDecl *funcDecl = ctx.getGetUnwrapLocalDistributedActorUnownedExecutor();
217-
assert(funcDecl);
218-
auto funcDeclRef = SILDeclRef(funcDecl, SILDeclRef::Kind::Func);
219-
220-
SILFunction *unwrapExecutorFun =
221-
functionBuilder.getOrCreateFunction(
222-
loc, funcDeclRef, ForDefinition_t::NotForDefinition);
223-
assert(unwrapExecutorFun && "no sil function!");
224-
auto funcRef =
225-
B.createFunctionRef(loc, unwrapExecutorFun);
226-
auto witnessCall = B.createApply(loc, funcRef, subs, {actor});
227-
228-
// The protocol requirement returns an Optional<UnownedSerialExecutor>;
229-
// extract the Builtin.Executor from it.
230-
auto executorDecl = ctx.getUnownedSerialExecutorDecl();
231-
auto executorProps = executorDecl->getStoredProperties();
232-
assert(executorProps.size() == 1);
233-
unmarkedExecutor =
234-
B.createStructExtract(loc, witnessCall, executorProps[0]);
200+
// Otherwise, go through (Distributed)Actor.unownedExecutor.
235201
} else {
236-
auto actorKind = KnownProtocolKind::Actor;
202+
auto actorKind = actorType->isDistributedActor() ?
203+
KnownProtocolKind::DistributedActor :
204+
KnownProtocolKind::Actor;
237205
auto actorProtocol = ctx.getProtocol(actorKind);
238206
auto req = getUnownedExecutorGetter(ctx, actorProtocol);
239207
assert(req && "Concurrency library broken");

lib/Sema/DerivedConformanceDistributedActor.cpp

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -621,13 +621,13 @@ static Expr *constructDistributedUnownedSerialExecutor(ASTContext &ctx,
621621
}
622622

623623
static std::pair<BraceStmt *, bool>
624-
deriveBodyDistributedActor_localUnownedExecutor(AbstractFunctionDecl *getter, void *) {
625-
// var localUnownedExecutor: UnownedSerialExecutor? {
624+
deriveBodyDistributedActor_unownedExecutor(AbstractFunctionDecl *getter, void *) {
625+
// var unownedExecutor: UnownedSerialExecutor {
626626
// get {
627627
// guard __isLocalActor(self) else {
628-
// return nil
628+
// return buildDefaultDistributedRemoteActorExecutor(self)
629629
// }
630-
// return Optional(Builtin.buildDefaultActorExecutorRef(self))
630+
// return Builtin.buildDefaultActorExecutorRef(self)
631631
// }
632632
// }
633633
ASTContext &ctx = getter->getASTContext();
@@ -657,7 +657,7 @@ deriveBodyDistributedActor_localUnownedExecutor(AbstractFunctionDecl *getter, vo
657657
if (!initCall) return failure();
658658

659659
// guard __isLocalActor(self) else {
660-
// return nil
660+
// return buildDefaultDistributedRemoteActorExecutor(self)
661661
// }
662662
auto isLocalActorDecl = ctx.getIsLocalDistributedActor();
663663
DeclRefExpr *isLocalActorExpr =
@@ -673,22 +673,71 @@ deriveBodyDistributedActor_localUnownedExecutor(AbstractFunctionDecl *getter, vo
673673
CallExpr *isLocalActorCall = CallExpr::createImplicit(ctx, isLocalActorExpr, argListForIsLocal);
674674
isLocalActorCall->setType(ctx.getBoolType());
675675
isLocalActorCall->setThrows(false);
676-
auto returnNilIfRemoteStmt = DerivedConformance::returnNilIfFalseGuardTypeChecked(
677-
ctx, isLocalActorCall, /*optionalWrappedType=*/initCall->getType());
678676

677+
GuardStmt* guardElseRemoteReturnExec;
678+
{
679+
// Find the buildDefaultDistributedRemoteActorExecutor method
680+
auto buildRemoteExecutorDecl =
681+
ctx.getBuildDefaultDistributedRemoteActorUnownedExecutor();
682+
assert(buildRemoteExecutorDecl && "cannot find buildDefaultDistributedRemoteActorExecutor");
683+
auto substitutions = SubstitutionMap::get(
684+
buildRemoteExecutorDecl->getGenericSignature(),
685+
[&](SubstitutableType *dependentType) {
686+
if (auto gp = dyn_cast<GenericTypeParamType>(dependentType)) {
687+
if (gp->getDepth() == 0 && gp->getIndex() == 0) {
688+
return getter->getImplicitSelfDecl()->getType();
689+
}
690+
}
679691

680-
// Finalize preparing the unowned executor for returning.
681-
auto wrappedCall = new (ctx) InjectIntoOptionalExpr(initCall, initCall->getType()->wrapInOptionalType());
692+
return Type();
693+
},
694+
LookUpConformanceInModule(getter->getParentModule())
695+
);
696+
DeclRefExpr *buildRemoteExecutorExpr =
697+
new (ctx) DeclRefExpr(
698+
ConcreteDeclRef(buildRemoteExecutorDecl, substitutions),
699+
DeclNameLoc(),/*implicit=*/true,
700+
AccessSemantics::Ordinary);
701+
buildRemoteExecutorExpr->setType(
702+
buildRemoteExecutorDecl->getInterfaceType()
703+
.subst(substitutions)
704+
);
705+
706+
Expr *selfForBuildRemoteExecutor = DerivedConformance::createSelfDeclRef(getter);
707+
selfForBuildRemoteExecutor->setType(selfType);
708+
auto *argListForBuildRemoteExecutor =
709+
ArgumentList::forImplicitCallTo(buildRemoteExecutorDecl->getParameters(),
710+
/*argExprs=*/{selfForBuildRemoteExecutor}, ctx);
711+
CallExpr *buildRemoteExecutorCall = CallExpr::createImplicit(ctx, buildRemoteExecutorExpr,
712+
argListForBuildRemoteExecutor);
713+
buildRemoteExecutorCall->setType(ctx.getUnownedSerialExecutorType());
714+
buildRemoteExecutorCall->setThrows(false);
715+
716+
SmallVector<ASTNode, 1> statements = {
717+
new(ctx) ReturnStmt(SourceLoc(), buildRemoteExecutorCall)
718+
};
719+
720+
SmallVector<StmtConditionElement, 1> conditions = {
721+
isLocalActorCall
722+
};
723+
724+
// Build and return the complete guard statement.
725+
guardElseRemoteReturnExec =
726+
new(ctx) GuardStmt(SourceLoc(), ctx.AllocateCopy(conditions),
727+
BraceStmt::create(ctx, SourceLoc(), statements, SourceLoc()));
728+
}
682729

683-
auto ret = new (ctx) ReturnStmt(SourceLoc(), wrappedCall, /*implicit*/ true);
730+
// Finalize preparing the unowned executor for returning.
731+
// auto wrappedCall = new (ctx) InjectIntoOptionalExpr(initCall, initCall->getType());
732+
auto returnDefaultExec = new (ctx) ReturnStmt(SourceLoc(), initCall, /*implicit=*/true);
684733

685734
auto body = BraceStmt::create(
686-
ctx, SourceLoc(), { returnNilIfRemoteStmt, ret }, SourceLoc(), /*implicit=*/true);
735+
ctx, SourceLoc(), { guardElseRemoteReturnExec, returnDefaultExec }, SourceLoc(), /*implicit=*/true);
687736
return { body, /*isTypeChecked=*/true };
688737
}
689738

690-
/// Derive the declaration of DistributedActor's localUnownedExecutor property.
691-
static ValueDecl *deriveDistributedActor_localUnownedExecutor(DerivedConformance &derived) {
739+
/// Derive the declaration of DistributedActor's unownedExecutor property.
740+
static ValueDecl *deriveDistributedActor_unownedExecutor(DerivedConformance &derived) {
692741
ASTContext &ctx = derived.Context;
693742

694743
// Retrieve the types and declarations we'll need to form this operation.
@@ -699,11 +748,10 @@ static ValueDecl *deriveDistributedActor_localUnownedExecutor(DerivedConformance
699748
return nullptr;
700749
}
701750
Type executorType = executorDecl->getDeclaredInterfaceType();
702-
Type optionalExecutorType = executorType->wrapInOptionalType();
703751

704752
if (auto classDecl = dyn_cast<ClassDecl>(derived.Nominal)) {
705-
if (auto existing = classDecl->getLocalUnownedExecutorProperty()) {
706-
if (existing->getInterfaceType()->isEqual(optionalExecutorType)) {
753+
if (auto existing = classDecl->getUnownedExecutorProperty()) {
754+
if (existing->getInterfaceType()->isEqual(executorType)) {
707755
return const_cast<VarDecl *>(existing);
708756
} else {
709757
// bad type, should be diagnosed elsewhere
@@ -713,8 +761,8 @@ static ValueDecl *deriveDistributedActor_localUnownedExecutor(DerivedConformance
713761
}
714762

715763
auto propertyPair = derived.declareDerivedProperty(
716-
DerivedConformance::SynthesizedIntroducer::Var, ctx.Id_localUnownedExecutor,
717-
optionalExecutorType, optionalExecutorType,
764+
DerivedConformance::SynthesizedIntroducer::Var, ctx.Id_unownedExecutor,
765+
executorType, executorType,
718766
/*static*/ false, /*final*/ false);
719767
auto property = propertyPair.first;
720768
property->setSynthesized(true);
@@ -738,8 +786,8 @@ static ValueDecl *deriveDistributedActor_localUnownedExecutor(DerivedConformance
738786
property, asAvailableAs, ctx);
739787

740788
auto getter =
741-
derived.addGetterToReadOnlyDerivedProperty(property, optionalExecutorType);
742-
getter->setBodySynthesizer(deriveBodyDistributedActor_localUnownedExecutor);
789+
derived.addGetterToReadOnlyDerivedProperty(property, executorType);
790+
getter->setBodySynthesizer(deriveBodyDistributedActor_unownedExecutor);
743791

744792
// IMPORTANT: MUST BE AFTER [id, actorSystem].
745793
if (auto id = derived.Nominal->getDistributedActorIDProperty()) {
@@ -782,24 +830,24 @@ static void assertRequiredSynthesizedPropertyOrder(DerivedConformance &derived,
782830
if (auto id = Nominal->getDistributedActorIDProperty()) {
783831
if (auto system = Nominal->getDistributedActorSystemProperty()) {
784832
if (auto classDecl = dyn_cast<ClassDecl>(derived.Nominal)) {
785-
if (auto localUnownedExecutor = classDecl->getLocalUnownedExecutorProperty()) {
786-
int idIdx, actorSystemIdx, localUnownedExecutorIdx = 0;
833+
if (auto unownedExecutor = classDecl->getUnownedExecutorProperty()) {
834+
int idIdx, actorSystemIdx, unownedExecutorIdx = 0;
787835
int idx = 0;
788836
for (auto member: Nominal->getMembers()) {
789837
if (auto binding = dyn_cast<PatternBindingDecl>(member)) {
790838
if (binding->getSingleVar()->getName() == Context.Id_id) {
791839
idIdx = idx;
792840
} else if (binding->getSingleVar()->getName() == Context.Id_actorSystem) {
793841
actorSystemIdx = idx;
794-
} else if (binding->getSingleVar()->getName() == Context.Id_localUnownedExecutor) {
795-
localUnownedExecutorIdx = idx;
842+
} else if (binding->getSingleVar()->getName() == Context.Id_unownedExecutor) {
843+
unownedExecutorIdx = idx;
796844
}
797845
idx += 1;
798846
}
799847
}
800-
if (idIdx + actorSystemIdx + localUnownedExecutorIdx >= 0 + 1 + 2) {
848+
if (idIdx + actorSystemIdx + unownedExecutorIdx >= 0 + 1 + 2) {
801849
// we have found all the necessary fields, let's assert their order
802-
assert(idIdx < actorSystemIdx < localUnownedExecutorIdx && "order of fields MUST be exact.");
850+
assert(idIdx < actorSystemIdx < unownedExecutorIdx && "order of fields MUST be exact.");
803851
}
804852
}
805853
}
@@ -821,8 +869,8 @@ ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) {
821869
derivedValue = deriveDistributedActor_id(*this);
822870
} else if (var->getName() == Context.Id_actorSystem) {
823871
derivedValue = deriveDistributedActor_actorSystem(*this);
824-
} else if (var->getName() == Context.Id_localUnownedExecutor) {
825-
derivedValue = deriveDistributedActor_localUnownedExecutor(*this);
872+
} else if (var->getName() == Context.Id_unownedExecutor) {
873+
derivedValue = deriveDistributedActor_unownedExecutor(*this);
826874
}
827875

828876
assertRequiredSynthesizedPropertyOrder(*this, derivedValue);

lib/Sema/DerivedConformances.cpp

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,11 @@ ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal,
335335

336336
// Actor.unownedExecutor
337337
if (name.isSimpleName(ctx.Id_unownedExecutor)) {
338-
return getRequirement(KnownProtocolKind::Actor);
338+
if (nominal->isDistributedActor()) {
339+
return getRequirement(KnownProtocolKind::DistributedActor);
340+
} else {
341+
return getRequirement(KnownProtocolKind::Actor);
342+
}
339343
}
340344

341345
// DistributedActor.id
@@ -346,11 +350,6 @@ ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal,
346350
if (name.isSimpleName(ctx.Id_actorSystem))
347351
return getRequirement(KnownProtocolKind::DistributedActor);
348352

349-
// DistributedActor.localUnownedExecutor
350-
if (name.isSimpleName(ctx.Id_localUnownedExecutor)) {
351-
return getRequirement(KnownProtocolKind::DistributedActor);
352-
}
353-
354353
return nullptr;
355354
}
356355

@@ -693,11 +692,6 @@ GuardStmt *DerivedConformance::returnNilIfFalseGuardTypeChecked(ASTContext &C,
693692
statements.push_back(returnStmt);
694693

695694
// Next, generate the condition being checked.
696-
// auto cmpFuncExpr = new (C) UnresolvedDeclRefExpr(
697-
// DeclNameRef(C.Id_EqualsOperator), DeclRefKind::BinaryOperator,
698-
// DeclNameLoc());
699-
// auto *cmpExpr = BinaryExpr::create(C, lhsExpr, cmpFuncExpr, rhsExpr,
700-
// /*implicit*/ true);
701695
conditions.emplace_back(testExpr);
702696

703697
// Build and return the complete guard statement.

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -221,16 +221,6 @@ bool IsDefaultActorRequest::evaluate(
221221
executorProperty->getAttrs().hasSemanticsAttr(SEMANTICS_DEFAULT_ACTOR);
222222
}
223223

224-
// Maybe it was a distributed actor, let's double-check it's localUnownedExecutor property.
225-
// If we synthesized that one with appropriate semantics we may still be a default actor.
226-
if (!isDefaultActor && classDecl->isDistributedActor()) {
227-
if (auto localExecutorProperty = classDecl->getLocalUnownedExecutorProperty()) {
228-
foundExecutorPropertyImpl = true;
229-
isDefaultActor = isDefaultActor ||
230-
localExecutorProperty->getAttrs().hasSemanticsAttr(SEMANTICS_DEFAULT_ACTOR);
231-
}
232-
}
233-
234224
// Only if we found one of the executor properties, do we return the status of default or not,
235225
// based on the findings of the semantics attribute of that located property.
236226
if (foundExecutorPropertyImpl) {

lib/Sema/TypeCheckDeclOverride.cpp

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2039,7 +2039,6 @@ static bool checkSingleOverride(ValueDecl *override, ValueDecl *base) {
20392039
cast<ClassDecl>(prop->getDeclContext())->isActor() &&
20402040
!prop->isStatic() &&
20412041
prop->getName() == ctx.Id_unownedExecutor &&
2042-
prop->getName() == ctx.Id_localUnownedExecutor &&
20432042
prop->getInterfaceType()->getAnyNominal() == ctx.getUnownedSerialExecutorDecl());
20442043
};
20452044

stdlib/public/Concurrency/DiscardingTaskGroup.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ extension DiscardingTaskGroup: Sendable { }
407407
/// out of the `withThrowingDiscardingTaskGroup` method when it returns.
408408
///
409409
/// ```
410-
/// try await withThrowingDiscardingTaskGroup() { group in
410+
/// try await withThrowingDiscardingTaskGroup { group in
411411
/// group.addTask { try boom(1) }
412412
/// group.addTask { try boom(2, after: .seconds(5)) }
413413
/// group.addTask { try boom(3, after: .seconds(5)) }
@@ -490,7 +490,7 @@ public func withThrowingDiscardingTaskGroup<GroupResult>(
490490
/// A throwing discarding task group becomes cancelled in one of the following ways:
491491
///
492492
/// - when ``cancelAll()`` is invoked on it,
493-
/// - when an error is thrown out of the `withThrowingDiscardingTaskGroup(...) { }` closure,
493+
/// - when an error is thrown out of the `withThrowingDiscardingTaskGroup { ... }` closure,
494494
/// - when the ``Task`` running this task group is cancelled.
495495
///
496496
/// But also, and uniquely in *discarding* task groups:
@@ -598,7 +598,7 @@ public struct ThrowingDiscardingTaskGroup<Failure: Error> {
598598

599599
/// A Boolean value that indicates whether the group has any remaining tasks.
600600
///
601-
/// At the start of the body of a `withThrowingDiscardingTaskGroup(of:returning:body:)` call,
601+
/// At the start of the body of a `withThrowingDiscardingTaskGroup(returning:body:)` call,
602602
/// the task group is always empty.
603603
///
604604
/// It's guaranteed to be empty when returning from that body

stdlib/public/Distributed/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ add_swift_target_library(swiftDistributed ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS
1919
DistributedActor.swift
2020
DistributedActorSystem.swift
2121
DistributedAssertions.swift
22+
DistributedDefaultExecutor.swift
2223
DistributedMetadata.swift
2324
LocalTestingDistributedActorSystem.swift
2425

0 commit comments

Comments
 (0)