Skip to content

[Concurrency] Distributed actor's unownedExecutor should be optional #64499

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 7 commits into from
Mar 21, 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
1 change: 1 addition & 0 deletions include/swift/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -4533,6 +4533,7 @@ class ClassDecl final : public NominalTypeDecl {

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

/// Is this the NSObject class type?
bool isNSObject() const;
Expand Down
2 changes: 2 additions & 0 deletions include/swift/AST/KnownIdentifiers.def
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ IDENTIFIER(className)
IDENTIFIER(_defaultActorInitialize)
IDENTIFIER(_defaultActorDestroy)
IDENTIFIER(unownedExecutor)
IDENTIFIER(localUnownedExecutor)
IDENTIFIER(_unwrapLocalUnownedExecutor)

IDENTIFIER_(ErrorType)
IDENTIFIER(Code)
Expand Down
1 change: 1 addition & 0 deletions include/swift/AST/KnownSDKDecls.def
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
#endif

KNOWN_SDK_FUNC_DECL(Distributed, IsRemoteDistributedActor, "__isRemoteActor")
KNOWN_SDK_FUNC_DECL(Distributed, IsLocalDistributedActor, "__isLocalActor")

#undef KNOWN_SDK_FUNC_DECL

1 change: 1 addition & 0 deletions include/swift/SIL/SILFunction.h
Original file line number Diff line number Diff line change
Expand Up @@ -1273,6 +1273,7 @@ class SILFunction
const SILBasicBlock *getEntryBlock() const { return &front(); }

SILBasicBlock *createBasicBlock();
SILBasicBlock *createBasicBlock(llvm::StringRef debugName);
SILBasicBlock *createBasicBlockAfter(SILBasicBlock *afterBB);
SILBasicBlock *createBasicBlockBefore(SILBasicBlock *beforeBB);

Expand Down
23 changes: 23 additions & 0 deletions lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9625,6 +9625,29 @@ const VarDecl *ClassDecl::getUnownedExecutorProperty() const {
return nullptr;
}

const VarDecl *ClassDecl::getLocalUnownedExecutorProperty() const {
auto &C = getASTContext();

if (!isDistributedActor())
return nullptr;

llvm::SmallVector<ValueDecl *, 2> results;
this->lookupQualified(getSelfNominalTypeDecl(),
DeclNameRef(C.Id_localUnownedExecutor),
NL_ProtocolMembers,
results);

for (auto candidate: results) {
if (isa<ProtocolDecl>(candidate->getDeclContext()))
continue;

if (VarDecl *var = dyn_cast<VarDecl>(candidate))
return var;
}

return nullptr;
}

bool ClassDecl::isRootDefaultActor() const {
return isRootDefaultActor(getModuleContext(), ResilienceExpansion::Maximal);
}
Expand Down
7 changes: 7 additions & 0 deletions lib/SIL/IR/SILFunction.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -533,6 +533,13 @@ SILBasicBlock *SILFunction::createBasicBlock() {
return newBlock;
}

SILBasicBlock *SILFunction::createBasicBlock(llvm::StringRef debugName) {
SILBasicBlock *newBlock = new (getModule()) SILBasicBlock(this);
newBlock->setDebugName(debugName);
BlockList.push_back(newBlock);
return newBlock;
}

SILBasicBlock *SILFunction::createBasicBlockAfter(SILBasicBlock *afterBB) {
SILBasicBlock *newBlock = new (getModule()) SILBasicBlock(this);
BlockList.insertAfter(afterBB->getIterator(), newBlock);
Expand Down
55 changes: 51 additions & 4 deletions lib/SILOptimizer/Mandatory/LowerHopToActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,28 @@ static AccessorDecl *getUnownedExecutorGetter(ASTContext &ctx,
return nullptr;
}

static AccessorDecl *getUnwrapLocalUnownedExecutorGetter(ASTContext &ctx,
ProtocolDecl *actorProtocol) {
for (auto member: actorProtocol->getAllMembers()) { // FIXME: remove this, just go to the extension
if (auto var = dyn_cast<VarDecl>(member)) {
if (var->getName() == ctx.Id__unwrapLocalUnownedExecutor)
return var->getAccessor(AccessorKind::Get);
}
}

for (auto extension: actorProtocol->getExtensions()) {
for (auto member: extension->getAllMembers()) {
if (auto var = dyn_cast<VarDecl>(member)) {
if (var->getName() == ctx.Id__unwrapLocalUnownedExecutor) {
return var->getAccessor(AccessorKind::Get);
}
}
}
}

return nullptr;
}

SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
SILLocation loc, SILValue actor,
bool makeOptional) {
Expand Down Expand Up @@ -186,11 +208,36 @@ SILValue LowerHopToActor::emitGetExecutor(SILBuilderWithScope &B,
unmarkedExecutor =
B.createBuiltin(loc, builtinName, resultType, subs, {actor});

// Otherwise, go through Actor.unownedExecutor.
// Otherwise, go through Actor.unownedExecutor.
} else if (actorType->isDistributedActor()) {
auto actorKind = KnownProtocolKind::DistributedActor;
auto actorProtocol = ctx.getProtocol(actorKind);
auto req = getUnwrapLocalUnownedExecutorGetter(ctx, actorProtocol);
assert(req && "Distributed library broken");
SILDeclRef fn(req, SILDeclRef::Kind::Func);

auto actorConf = module->lookupConformance(actorType, actorProtocol);
assert(actorConf &&
"hop_to_executor with distributed actor that doesn't conform to DistributedActor");

auto subs = SubstitutionMap::get(req->getGenericSignature(),
{actorType}, {actorConf});
auto fnType = F->getModule().Types.getConstantFunctionType(*F, fn);

auto witness =
B.createWitnessMethod(loc, actorType, actorConf, fn,
SILType::getPrimitiveObjectType(fnType));
auto witnessCall = B.createApply(loc, witness, subs, {actor});

// The protocol requirement returns an Optional<UnownedSerialExecutor>;
// extract the Builtin.Executor from it.
auto executorDecl = ctx.getUnownedSerialExecutorDecl();
auto executorProps = executorDecl->getStoredProperties();
assert(executorProps.size() == 1);
unmarkedExecutor =
B.createStructExtract(loc, witnessCall, executorProps[0]);
} else {
auto actorKind = actorType->isDistributedActor() ?
KnownProtocolKind::DistributedActor :
KnownProtocolKind::Actor;
auto actorKind = KnownProtocolKind::Actor;
auto actorProtocol = ctx.getProtocol(actorKind);
auto req = getUnownedExecutorGetter(ctx, actorProtocol);
assert(req && "Concurrency library broken");
Expand Down
89 changes: 62 additions & 27 deletions lib/Sema/DerivedConformanceDistributedActor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -621,10 +621,13 @@ static Expr *constructDistributedUnownedSerialExecutor(ASTContext &ctx,
}

static std::pair<BraceStmt *, bool>
deriveBodyDistributedActor_unownedExecutor(AbstractFunctionDecl *getter, void *) {
// var unownedExecutor: UnownedSerialExecutor {
deriveBodyDistributedActor_localUnownedExecutor(AbstractFunctionDecl *getter, void *) {
// var localUnownedExecutor: UnownedSerialExecutor? {
// get {
// return Builtin.buildDefaultActorExecutorRef(self)
// guard __isLocalActor(self) else {
// return nil
// }
// return Optional(Builtin.buildDefaultActorExecutorRef(self))
// }
// }
ASTContext &ctx = getter->getASTContext();
Expand All @@ -641,33 +644,53 @@ deriveBodyDistributedActor_unownedExecutor(AbstractFunctionDecl *getter, void *)
Expr *selfArg = DerivedConformance::createSelfDeclRef(getter);
selfArg->setType(selfType);

// Prepare the builtin call, we'll use it after the guard, but want to take the type
// of its return type earlier, so we prepare it here.

// The builtin call gives us a Builtin.Executor.
auto builtinCall =
DerivedConformance::createBuiltinCall(ctx,
BuiltinValueKind::BuildDefaultActorExecutorRef,
{selfType}, {}, {selfArg});

// Turn that into an UnownedSerialExecutor.
auto initCall = constructDistributedUnownedSerialExecutor(ctx, builtinCall);
if (!initCall) return failure();

auto ret = new (ctx) ReturnStmt(SourceLoc(), initCall, /*implicit*/ true);
// guard __isLocalActor(self) else {
// return nil
// }
auto isLocalActorDecl = ctx.getIsLocalDistributedActor();
DeclRefExpr *isLocalActorExpr =
new (ctx) DeclRefExpr(ConcreteDeclRef(isLocalActorDecl), DeclNameLoc(), /*implicit=*/true,
AccessSemantics::Ordinary,
FunctionType::get({AnyFunctionType::Param(ctx.getAnyObjectType())},
ctx.getBoolType()));
Expr *selfForIsLocalArg = DerivedConformance::createSelfDeclRef(getter);
selfForIsLocalArg->setType(selfType);
auto *argListForIsLocal =
ArgumentList::forImplicitSingle(ctx, Identifier(),
ErasureExpr::create(ctx, selfForIsLocalArg, ctx.getAnyObjectType(), {}, {}));
CallExpr *isLocalActorCall = CallExpr::createImplicit(ctx, isLocalActorExpr, argListForIsLocal);
isLocalActorCall->setType(ctx.getBoolType());
isLocalActorCall->setThrows(false);
auto returnNilIfRemoteStmt = DerivedConformance::returnNilIfFalseGuardTypeChecked(
ctx, isLocalActorCall, /*optionalWrappedType=*/initCall->getType());


// Finalize preparing the unowned executor for returning.
auto wrappedCall = new (ctx) InjectIntoOptionalExpr(initCall, initCall->getType()->wrapInOptionalType());

auto ret = new (ctx) ReturnStmt(SourceLoc(), wrappedCall, /*implicit*/ true);

auto body = BraceStmt::create(
ctx, SourceLoc(), { ret }, SourceLoc(), /*implicit=*/true);
ctx, SourceLoc(), { returnNilIfRemoteStmt, ret }, SourceLoc(), /*implicit=*/true);
return { body, /*isTypeChecked=*/true };
}

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

if (auto classDecl = dyn_cast<ClassDecl>(derived.Nominal)) {
if (auto existing = classDecl->getUnownedExecutorProperty()) {
return const_cast<VarDecl*>(existing);
}
}

// Retrieve the types and declarations we'll need to form this operation.
auto executorDecl = ctx.getUnownedSerialExecutorDecl();
if (!executorDecl) {
Expand All @@ -676,16 +699,28 @@ static ValueDecl *deriveDistributedActor_unownedExecutor(DerivedConformance &der
return nullptr;
}
Type executorType = executorDecl->getDeclaredInterfaceType();
Type optionalExecutorType = executorType->wrapInOptionalType();

if (auto classDecl = dyn_cast<ClassDecl>(derived.Nominal)) {
if (auto existing = classDecl->getLocalUnownedExecutorProperty()) {
if (existing->getInterfaceType()->isEqual(optionalExecutorType)) {
return const_cast<VarDecl *>(existing);
} else {
// bad type, should be diagnosed elsewhere
return nullptr;
}
}
}

auto propertyPair = derived.declareDerivedProperty(
DerivedConformance::SynthesizedIntroducer::Var, ctx.Id_unownedExecutor,
executorType, executorType,
DerivedConformance::SynthesizedIntroducer::Var, ctx.Id_localUnownedExecutor,
optionalExecutorType, optionalExecutorType,
/*static*/ false, /*final*/ false);
auto property = propertyPair.first;
property->setSynthesized(true);
property->getAttrs().add(new (ctx) SemanticsAttr(SEMANTICS_DEFAULT_ACTOR,
SourceLoc(), SourceRange(),
/*implicit*/ true));
/*implicit*/ true));
property->getAttrs().add(new (ctx) NonisolatedAttr(/*IsImplicit=*/true));

// Make the property implicitly final.
Expand All @@ -703,8 +738,8 @@ static ValueDecl *deriveDistributedActor_unownedExecutor(DerivedConformance &der
property, asAvailableAs, ctx);

auto getter =
derived.addGetterToReadOnlyDerivedProperty(property, executorType);
getter->setBodySynthesizer(deriveBodyDistributedActor_unownedExecutor);
derived.addGetterToReadOnlyDerivedProperty(property, optionalExecutorType);
getter->setBodySynthesizer(deriveBodyDistributedActor_localUnownedExecutor);

// IMPORTANT: MUST BE AFTER [id, actorSystem].
if (auto id = derived.Nominal->getDistributedActorIDProperty()) {
Expand Down Expand Up @@ -747,24 +782,24 @@ static void assertRequiredSynthesizedPropertyOrder(DerivedConformance &derived,
if (auto id = Nominal->getDistributedActorIDProperty()) {
if (auto system = Nominal->getDistributedActorSystemProperty()) {
if (auto classDecl = dyn_cast<ClassDecl>(derived.Nominal)) {
if (auto unownedExecutor = classDecl->getUnownedExecutorProperty()) {
int idIdx, actorSystemIdx, unownedExecutorIdx = 0;
if (auto localUnownedExecutor = classDecl->getLocalUnownedExecutorProperty()) {
int idIdx, actorSystemIdx, localUnownedExecutorIdx = 0;
int idx = 0;
for (auto member: Nominal->getMembers()) {
if (auto binding = dyn_cast<PatternBindingDecl>(member)) {
if (binding->getSingleVar()->getName() == Context.Id_id) {
idIdx = idx;
} else if (binding->getSingleVar()->getName() == Context.Id_actorSystem) {
actorSystemIdx = idx;
} else if (binding->getSingleVar()->getName() == Context.Id_unownedExecutor) {
unownedExecutorIdx = idx;
} else if (binding->getSingleVar()->getName() == Context.Id_localUnownedExecutor) {
localUnownedExecutorIdx = idx;
}
idx += 1;
}
}
if (idIdx + actorSystemIdx + unownedExecutorIdx >= 0 + 1 + 2) {
if (idIdx + actorSystemIdx + localUnownedExecutorIdx >= 0 + 1 + 2) {
// we have found all the necessary fields, let's assert their order
assert(idIdx < actorSystemIdx < unownedExecutorIdx && "order of fields MUST be exact.");
assert(idIdx < actorSystemIdx < localUnownedExecutorIdx && "order of fields MUST be exact.");
}
}
}
Expand All @@ -786,8 +821,8 @@ ValueDecl *DerivedConformance::deriveDistributedActor(ValueDecl *requirement) {
derivedValue = deriveDistributedActor_id(*this);
} else if (var->getName() == Context.Id_actorSystem) {
derivedValue = deriveDistributedActor_actorSystem(*this);
} else if (var->getName() == Context.Id_unownedExecutor) {
derivedValue = deriveDistributedActor_unownedExecutor(*this);
} else if (var->getName() == Context.Id_localUnownedExecutor) {
derivedValue = deriveDistributedActor_localUnownedExecutor(*this);
}

assertRequiredSynthesizedPropertyOrder(*this, derivedValue);
Expand Down
41 changes: 36 additions & 5 deletions lib/Sema/DerivedConformances.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,7 @@ ValueDecl *DerivedConformance::getDerivableRequirement(NominalTypeDecl *nominal,

// Actor.unownedExecutor
if (name.isSimpleName(ctx.Id_unownedExecutor)) {
if (nominal->isDistributedActor()) {
return getRequirement(KnownProtocolKind::DistributedActor);
} else {
return getRequirement(KnownProtocolKind::Actor);
}
return getRequirement(KnownProtocolKind::Actor);
}

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

// DistributedActor.localUnownedExecutor
if (name.isSimpleName(ctx.Id_localUnownedExecutor)) {
return getRequirement(KnownProtocolKind::DistributedActor);
}

return nullptr;
}

Expand Down Expand Up @@ -674,6 +675,36 @@ GuardStmt *DerivedConformance::returnFalseIfNotEqualGuard(ASTContext &C,
auto falseExpr = new (C) BooleanLiteralExpr(false, SourceLoc(), true);
return returnIfNotEqualGuard(C, lhsExpr, rhsExpr, falseExpr);
}
/// Returns a generated guard statement that checks whether the given expr is true.
/// If it is false, the else block for the guard returns `nil`.
/// \p C The AST context.
/// \p testExpr The expression that should be tested.
/// \p baseType The wrapped type of the to-be-returned Optional<Wrapped>.
GuardStmt *DerivedConformance::returnNilIfFalseGuardTypeChecked(ASTContext &C,
Expr *testExpr,
Type optionalWrappedType) {
auto nilExpr = new (C) NilLiteralExpr(SourceLoc(), /*implicit=*/true);
nilExpr->setType(optionalWrappedType->wrapInOptionalType());

SmallVector<StmtConditionElement, 1> conditions;
SmallVector<ASTNode, 1> statements;

auto returnStmt = new (C) ReturnStmt(SourceLoc(), nilExpr);
statements.push_back(returnStmt);

// Next, generate the condition being checked.
// auto cmpFuncExpr = new (C) UnresolvedDeclRefExpr(
// DeclNameRef(C.Id_EqualsOperator), DeclRefKind::BinaryOperator,
// DeclNameLoc());
// auto *cmpExpr = BinaryExpr::create(C, lhsExpr, cmpFuncExpr, rhsExpr,
// /*implicit*/ true);
conditions.emplace_back(testExpr);

// Build and return the complete guard statement.
// guard lhs == rhs else { return lhs < rhs }
auto body = BraceStmt::create(C, SourceLoc(), statements, SourceLoc());
return new (C) GuardStmt(SourceLoc(), C.AllocateCopy(conditions), body);
}
/// Returns a generated guard statement that checks whether the given lhs and
/// rhs expressions are equal. If not equal, the else block for the guard
/// returns lhs < rhs.
Expand Down
6 changes: 6 additions & 0 deletions lib/Sema/DerivedConformances.h
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,12 @@ class DerivedConformance {
// return false
static GuardStmt *returnFalseIfNotEqualGuard(ASTContext &C, Expr *lhsExpr,
Expr *rhsExpr);

// Return `nil` is the `testExp` is `false`.
static GuardStmt *returnNilIfFalseGuardTypeChecked(ASTContext &C,
Expr *testExpr,
Type optionalWrappedType);

// return lhs < rhs
static GuardStmt *
returnComparisonIfNotEqualGuard(ASTContext &C, Expr *lhsExpr, Expr *rhsExpr);
Expand Down
Loading