Skip to content

Commit 368d4c4

Browse files
committed
[Concurrency] Distributed actor's unownedExecutor should be optional
1 parent b123204 commit 368d4c4

13 files changed

+469
-22
lines changed

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ IDENTIFIER(className)
231231
IDENTIFIER(_defaultActorInitialize)
232232
IDENTIFIER(_defaultActorDestroy)
233233
IDENTIFIER(unownedExecutor)
234+
IDENTIFIER(localUnownedExecutor)
234235

235236
IDENTIFIER_(ErrorType)
236237
IDENTIFIER(Code)

include/swift/AST/KnownSDKDecls.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#endif
2121

2222
KNOWN_SDK_FUNC_DECL(Distributed, IsRemoteDistributedActor, "__isRemoteActor")
23+
KNOWN_SDK_FUNC_DECL(Distributed, IsLocalDistributedActor, "__isLocalActor")
2324

2425
#undef KNOWN_SDK_FUNC_DECL
2526

lib/SILOptimizer/Mandatory/LowerHopToActor.cpp

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
186186
unmarkedExecutor =
187187
B.createBuiltin(loc, builtinName, resultType, subs, {actor});
188188

189-
// Otherwise, go through Actor.unownedExecutor.
189+
// Otherwise, go through (Distributed)Actor.unownedExecutor.
190190
} else {
191191
auto actorKind = actorType->isDistributedActor() ?
192192
KnownProtocolKind::DistributedActor :
@@ -214,6 +214,8 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
214214
auto executorDecl = ctx.getUnownedSerialExecutorDecl();
215215
auto executorProps = executorDecl->getStoredProperties();
216216
assert(executorProps.size() == 1);
217+
fprintf(stderr, "[%s:%d](%s) executorProps = \n", __FILE_NAME__, __LINE__, __FUNCTION__);
218+
executorProps[0]->dump();
217219
unmarkedExecutor =
218220
B.createStructExtract(loc, witnessCall, executorProps[0]);
219221
}

lib/Sema/DerivedConformanceDistributedActor.cpp

Lines changed: 120 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -620,9 +620,79 @@ static Expr *constructDistributedUnownedSerialExecutor(ASTContext &ctx,
620620
return nullptr;
621621
}
622622

623+
//static Expr *constructOptionalSome(ASTContext &ctx,
624+
// Expr *arg) {
625+
// auto optionalDecl = ctx.getOptionalDecl();
626+
// if (!optionalDecl) return nullptr;
627+
//
628+
// for (auto member: optionalDecl->getAllMembers()) {
629+
// auto ctor = dyn_cast<ConstructorDecl>(member);
630+
// if (!ctor) continue;
631+
//
632+
// // Find the init(_:) which initialized as `some`
633+
// auto params = ctor->getParameters();
634+
// if (params->size() != 1 ||
635+
// params->get(0)->getArgumentName() != Identifier())
636+
// continue;
637+
//
638+
// ctor->dump();
639+
//
640+
//
641+
//// Type optionalType = optionalDecl->getDeclaredInterfaceType();
642+
//// fprintf(stderr, "[%s:%d](%s) optionalDecl\n", __FILE_NAME__, __LINE__, __FUNCTION__);
643+
//// optionalType.dump();
644+
//
645+
// Type optionalType = BoundGenericEnumType::get(optionalDecl, Type(), {arg->getType()});
646+
// fprintf(stderr, "[%s:%d](%s) better optionalType\n", __FILE_NAME__, __LINE__, __FUNCTION__);
647+
// optionalType->dump();
648+
//
649+
// Type ctorType = ctor->getInterfaceType();
650+
// fprintf(stderr, "[%s:%d](%s) ctor type\n", __FILE_NAME__, __LINE__, __FUNCTION__);
651+
// ctorType->dump();
652+
//
653+
// // We have the right initializer. Build a reference to it of type:
654+
// // (Optional.Type)
655+
// // -> (T) -> T
656+
// auto initRef = new (ctx) DeclRefExpr(ctor, DeclNameLoc(), /*implicit*/true,
657+
// AccessSemantics::Ordinary,
658+
// ctorType);
659+
//
660+
// // Apply the initializer to the metatype, building an expression of type:
661+
// // (Builtin.Executor) -> Optional<UnownedSerialExecutor>
662+
// // auto metatypeRef = TypeExpr::createImplicit(optionalType, ctx);
663+
// auto metatypeRef = TypeExpr::createImplicit(optionalType, ctx);
664+
// GenericIdentTypeRepr::create(ctx, DeclNameLoc(optionalDecl->getNameLoc()),
665+
// DeclNameRef(optionalDecl->getBaseName()),
666+
// {arg->getType()}, SourceRange());
667+
////
668+
//// auto ctorAppliedType = new (ctx) TypeRepr(
669+
//// GenericIdentTypeRepr::create(ctx, DeclNameLoc(), )
670+
//// );
671+
//
672+
//// TypeExpr::createForSpecializedDecl(optionalType.get, {arg->getType()}, SourceRange(), ctx);
673+
//// Type ctorAppliedType = ctorType->getAs<FunctionType>()->getResult();
674+
// auto selfApply = ConstructorRefCallExpr::create(ctx, initRef, metatypeRef
675+
//// ,
676+
//// ctorAppliedType
677+
// );
678+
// selfApply->setImplicit(true);
679+
// selfApply->setThrows(false);
680+
//
681+
// // Call the constructor, building an expression of type
682+
// // Optional<T>.
683+
// auto *argList = ArgumentList::forImplicitUnlabeled(ctx, {arg});
684+
// auto call = CallExpr::createImplicit(ctx, selfApply, argList);
685+
// call->setType(optionalType);
686+
// call->setThrows(false);
687+
// return call;
688+
// }
689+
//
690+
// assert(false);
691+
//}
692+
623693
static std::pair<BraceStmt *, bool>
624694
deriveBodyDistributedActor_unownedExecutor(AbstractFunctionDecl *getter, void *) {
625-
// var unownedExecutor: UnownedSerialExecutor {
695+
// var unownedExecutor: UnownedSerialExecutor? {
626696
// get {
627697
// return Builtin.buildDefaultActorExecutorRef(self)
628698
// }
@@ -641,33 +711,58 @@ deriveBodyDistributedActor_unownedExecutor(AbstractFunctionDecl *getter, void *)
641711
Expr *selfArg = DerivedConformance::createSelfDeclRef(getter);
642712
selfArg->setType(selfType);
643713

714+
// Prepare the builtin call, we'll use it after the guard, but want to take the type
715+
// of its return type earlier, so we prepare it here.
716+
644717
// The builtin call gives us a Builtin.Executor.
645718
auto builtinCall =
646719
DerivedConformance::createBuiltinCall(ctx,
647720
BuiltinValueKind::BuildDefaultActorExecutorRef,
648721
{selfType}, {}, {selfArg});
649-
650722
// Turn that into an UnownedSerialExecutor.
651723
auto initCall = constructDistributedUnownedSerialExecutor(ctx, builtinCall);
652724
if (!initCall) return failure();
653725

654-
auto ret = new (ctx) ReturnStmt(SourceLoc(), initCall, /*implicit*/ true);
726+
// guard __isLocalActor(self) else {
727+
// return nil
728+
// }
729+
auto isLocalActorDecl = ctx.getIsLocalDistributedActor();
730+
DeclRefExpr *isLocalActorExpr =
731+
new (ctx) DeclRefExpr(ConcreteDeclRef(isLocalActorDecl), DeclNameLoc(), /*implicit=*/true,
732+
AccessSemantics::Ordinary,
733+
FunctionType::get({AnyFunctionType::Param(ctx.getAnyObjectType())},
734+
ctx.getBoolType()));
735+
Expr *selfForIsLocalArg = DerivedConformance::createSelfDeclRef(getter);
736+
selfForIsLocalArg->setType(selfType);
737+
auto *argListForIsLocal =
738+
ArgumentList::forImplicitSingle(ctx, Identifier(),
739+
ErasureExpr::create(ctx, selfForIsLocalArg, ctx.getAnyObjectType(), {}, {}));
740+
CallExpr *isLocalActorCall = CallExpr::createImplicit(ctx, isLocalActorExpr, argListForIsLocal);
741+
isLocalActorCall->setType(ctx.getBoolType());
742+
isLocalActorCall->setThrows(false);
743+
auto returnNilIfRemoteStmt = DerivedConformance::returnNilIfFalseGuardTypeChecked(
744+
ctx, isLocalActorCall, /*optionalWrappedType=*/initCall->getType());
745+
746+
747+
// Finalize preparing the unowned executor for returning.
748+
auto wrappedCall = new (ctx) InjectIntoOptionalExpr(initCall, initCall->getType()->wrapInOptionalType());
749+
750+
auto ret = new (ctx) ReturnStmt(SourceLoc(), wrappedCall, /*implicit*/ true);
655751

656752
auto body = BraceStmt::create(
657-
ctx, SourceLoc(), { ret }, SourceLoc(), /*implicit=*/true);
753+
ctx, SourceLoc(), { returnNilIfRemoteStmt, ret }, SourceLoc(), /*implicit=*/true);
754+
755+
fprintf(stderr, "[%s:%d](%s) ================================================================================================\n", __FILE_NAME__, __LINE__, __FUNCTION__);
756+
body->dump();
757+
fprintf(stderr, "[%s:%d](%s) ================================================================================================\n", __FILE_NAME__, __LINE__, __FUNCTION__);
758+
658759
return { body, /*isTypeChecked=*/true };
659760
}
660761

661762
/// Derive the declaration of DistributedActor's unownedExecutor property.
662763
static ValueDecl *deriveDistributedActor_unownedExecutor(DerivedConformance &derived) {
663764
ASTContext &ctx = derived.Context;
664765

665-
if (auto classDecl = dyn_cast<ClassDecl>(derived.Nominal)) {
666-
if (auto existing = classDecl->getUnownedExecutorProperty()) {
667-
return const_cast<VarDecl*>(existing);
668-
}
669-
}
670-
671766
// Retrieve the types and declarations we'll need to form this operation.
672767
auto executorDecl = ctx.getUnownedSerialExecutorDecl();
673768
if (!executorDecl) {
@@ -676,16 +771,28 @@ static ValueDecl *deriveDistributedActor_unownedExecutor(DerivedConformance &der
676771
return nullptr;
677772
}
678773
Type executorType = executorDecl->getDeclaredInterfaceType();
774+
Type optionalExecutorType = executorType->wrapInOptionalType();
775+
776+
if (auto classDecl = dyn_cast<ClassDecl>(derived.Nominal)) {
777+
if (auto existing = classDecl->getUnownedExecutorProperty()) {
778+
if (existing->getInterfaceType()->isEqual(optionalExecutorType)) {
779+
return const_cast<VarDecl *>(existing);
780+
} else {
781+
// bad type, should be diagnosed elsewhere
782+
return nullptr;
783+
}
784+
}
785+
}
679786

680787
auto propertyPair = derived.declareDerivedProperty(
681788
DerivedConformance::SynthesizedIntroducer::Var, ctx.Id_unownedExecutor,
682-
executorType, executorType,
789+
optionalExecutorType, optionalExecutorType,
683790
/*static*/ false, /*final*/ false);
684791
auto property = propertyPair.first;
685792
property->setSynthesized(true);
686793
property->getAttrs().add(new (ctx) SemanticsAttr(SEMANTICS_DEFAULT_ACTOR,
687794
SourceLoc(), SourceRange(),
688-
/*implicit*/ true));
795+
/*implicit*/ true));
689796
property->getAttrs().add(new (ctx) NonisolatedAttr(/*IsImplicit=*/true));
690797

691798
// Make the property implicitly final.
@@ -703,7 +810,7 @@ static ValueDecl *deriveDistributedActor_unownedExecutor(DerivedConformance &der
703810
property, asAvailableAs, ctx);
704811

705812
auto getter =
706-
derived.addGetterToReadOnlyDerivedProperty(property, executorType);
813+
derived.addGetterToReadOnlyDerivedProperty(property, optionalExecutorType);
707814
getter->setBodySynthesizer(deriveBodyDistributedActor_unownedExecutor);
708815

709816
// IMPORTANT: MUST BE AFTER [id, actorSystem].

lib/Sema/DerivedConformances.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,37 @@ GuardStmt *DerivedConformance::returnFalseIfNotEqualGuard(ASTContext &C,
674674
auto falseExpr = new (C) BooleanLiteralExpr(false, SourceLoc(), true);
675675
return returnIfNotEqualGuard(C, lhsExpr, rhsExpr, falseExpr);
676676
}
677+
/// Returns a generated guard statement that checks whether the given expr is true.
678+
/// If it is false, the else block for the guard returns `nil`.
679+
/// \p C The AST context.
680+
/// \p testExpr The expression that should be tested.
681+
/// \p baseType The wrapped type of the to-be-returned Optional<Wrapped>.
682+
GuardStmt *DerivedConformance::returnNilIfFalseGuardTypeChecked(ASTContext &C,
683+
Expr *testExpr,
684+
Type optionalWrappedType) {
685+
auto trueExpr = new (C) BooleanLiteralExpr(true, SourceLoc(), /*implicit=*/true);
686+
auto nilExpr = new (C) NilLiteralExpr(SourceLoc(), /*implicit=*/true);
687+
nilExpr->setType(optionalWrappedType->wrapInOptionalType());
688+
689+
SmallVector<StmtConditionElement, 1> conditions;
690+
SmallVector<ASTNode, 1> statements;
691+
692+
auto returnStmt = new (C) ReturnStmt(SourceLoc(), nilExpr);
693+
statements.push_back(returnStmt);
694+
695+
// 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);
701+
conditions.emplace_back(testExpr);
702+
703+
// Build and return the complete guard statement.
704+
// guard lhs == rhs else { return lhs < rhs }
705+
auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc());
706+
return new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conditions), body);
707+
}
677708
/// Returns a generated guard statement that checks whether the given lhs and
678709
/// rhs expressions are equal. If not equal, the else block for the guard
679710
/// returns lhs < rhs.

lib/Sema/DerivedConformances.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,12 @@ class DerivedConformance {
409409
// return false
410410
static GuardStmt *returnFalseIfNotEqualGuard(ASTContext &C, Expr *lhsExpr,
411411
Expr *rhsExpr);
412+
413+
// Return `nil` is the `testExp` is `false`.
414+
static GuardStmt *returnNilIfFalseGuardTypeChecked(ASTContext &C,
415+
Expr *testExpr,
416+
Type optionalWrappedType);
417+
412418
// return lhs < rhs
413419
static GuardStmt *
414420
returnComparisonIfNotEqualGuard(ASTContext &C, Expr *lhsExpr, Expr *rhsExpr);

stdlib/public/Concurrency/Executor.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ public struct UnownedSerialExecutor: Sendable {
101101
@usableFromInline
102102
internal var executor: Builtin.Executor
103103

104-
@_spi(ConcurrencyExecutors)
104+
/// SPI: Do not use. Cannot be marked @_spi, since we need to use it from Distributed module
105+
/// which needs to reach for this from an @_transparent function which prevents @_spi use.
105106
@available(SwiftStdlib 5.9, *)
106107
public var _executor: Builtin.Executor {
107108
self.executor

stdlib/public/Distributed/DistributedActor.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,8 +254,11 @@ public protocol DistributedActor: AnyActor, Identifiable, Hashable
254254
/// eliminated, and rearranged with other work, and they may even
255255
/// be introduced when not strictly required. Visible side effects
256256
/// are therefore strongly discouraged within this property.
257+
// @available(SwiftStdlib 5.9, *)
258+
// nonisolated var localUnownedExecutor: UnownedSerialExecutor? { get }
259+
257260
@available(SwiftStdlib 5.9, *)
258-
nonisolated var unownedExecutor: UnownedSerialExecutor { get }
261+
nonisolated var unownedExecutor: UnownedSerialExecutor? { get }
259262

260263
/// Resolves the passed in `id` against the `system`, returning
261264
/// either a local or remote actor reference.

0 commit comments

Comments
 (0)