Skip to content

Commit c2b8565

Browse files
committed
[Concurrency] Implicitly synthesize actor queue storage and enqueue.
When an actor class has its `enqueue(partialTask:)` implicitly synthesized, also synthesize a stored property for the actor's queue. The type of the property is defined by the _Concurrency library (`_DefaultActorQueue`), and it will be initialized with a call to `_defaultActorQueueCreate` (also provided by the _Concurrency library). Also synthesize the body of the implicitly-generated `enqueue(partialTask:)`, which will be a call to `_defaultActorQueueEnqueuePartialTask(actor:queue:partialTask:)`. Together, all of these allow us to experiment with the form of the queue and the queue operation without affecting the type checker. When `enqueue(partialTask:)` is not implicitly synthesized, the queue storage is not synthesized either. In such cases, the user has taken over the execution of tasks for the actor, rather than using the default implementation.
1 parent 8f3c912 commit c2b8565

File tree

7 files changed

+306
-11
lines changed

7 files changed

+306
-11
lines changed

include/swift/AST/DiagnosticsSema.def

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4195,9 +4195,9 @@ ERROR(actorisolated_not_actor_instance_member,none,
41954195
"'@actorIsolated' can only be applied to instance members of actors",
41964196
())
41974197

4198-
ERROR(partial_task_type_missing,none,
4199-
"missing 'PartialAsyncTask' type, probably because the '_Concurrency' "
4200-
"module was not imported", ())
4198+
ERROR(concurrency_lib_missing,none,
4199+
"missing '%0' declaration, probably because the '_Concurrency' "
4200+
"module was not imported", (StringRef))
42014201
ERROR(enqueue_partial_task_not_in_context,none,
42024202
"'enqueue(partialTask:)' can only be implemented in the definition of "
42034203
"actor class %0", (Type))

include/swift/AST/KnownIdentifiers.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ IDENTIFIER(withKeywordArguments)
141141
IDENTIFIER(wrapped)
142142
IDENTIFIER(wrappedValue)
143143
IDENTIFIER(wrapperValue)
144+
IDENTIFIER_WITH_NAME(actorStorage, "$__actor_storage")
144145

145146
// Kinds of layout constraints
146147
IDENTIFIER_WITH_NAME(UnknownLayout, "_UnknownLayout")

lib/Sema/DerivedConformanceActor.cpp

Lines changed: 196 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
//===----------------------------------------------------------------------===//
1616
#include "DerivedConformances.h"
1717
#include "TypeChecker.h"
18+
#include "TypeCheckConcurrency.h"
19+
#include "swift/AST/NameLookupRequests.h"
1820
#include "swift/AST/ParameterList.h"
1921

2022
using namespace swift;
@@ -46,49 +48,237 @@ static Type getPartialAsyncTaskType(ASTContext &ctx) {
4648
return Type();
4749
}
4850

51+
/// Look for the default actor queue type.
52+
static Type getDefaultActorQueueType(DeclContext *dc, SourceLoc loc) {
53+
ASTContext &ctx = dc->getASTContext();
54+
UnqualifiedLookupOptions options;
55+
options |= UnqualifiedLookupFlags::TypeLookup;
56+
auto desc = UnqualifiedLookupDescriptor(
57+
DeclNameRef(ctx.getIdentifier("_DefaultActorQueue")), dc, loc, options);
58+
auto lookup =
59+
evaluateOrDefault(ctx.evaluator, UnqualifiedLookupRequest{desc}, {});
60+
for (const auto &result : lookup) {
61+
if (auto typeDecl = dyn_cast<TypeDecl>(result.getValueDecl()))
62+
return typeDecl->getDeclaredInterfaceType();
63+
}
64+
65+
return Type();
66+
}
67+
68+
/// Look for the initialization function for the default actor storage.
69+
static FuncDecl *getDefaultActorQueueCreate(DeclContext *dc, SourceLoc loc) {
70+
ASTContext &ctx = dc->getASTContext();
71+
auto desc = UnqualifiedLookupDescriptor(
72+
DeclNameRef(ctx.getIdentifier("_defaultActorQueueCreate")), dc, loc,
73+
UnqualifiedLookupOptions());
74+
auto lookup =
75+
evaluateOrDefault(ctx.evaluator, UnqualifiedLookupRequest{desc}, {});
76+
for (const auto &result : lookup) {
77+
// FIXME: Validate this further, because we're assuming the exact type.
78+
if (auto func = dyn_cast<FuncDecl>(result.getValueDecl()))
79+
return func;
80+
}
81+
82+
return nullptr;
83+
}
84+
85+
/// Look for the default enqueue operation.
86+
static FuncDecl *getDefaultActorQueueEnqueue(DeclContext *dc, SourceLoc loc) {
87+
ASTContext &ctx = dc->getASTContext();
88+
auto desc = UnqualifiedLookupDescriptor(
89+
DeclNameRef(ctx.getIdentifier("_defaultActorQueueEnqueuePartialTask")),
90+
dc, loc, UnqualifiedLookupOptions());
91+
auto lookup =
92+
evaluateOrDefault(ctx.evaluator, UnqualifiedLookupRequest{desc}, {});
93+
for (const auto &result : lookup) {
94+
// FIXME: Validate this further, because we're assuming the exact type.
95+
if (auto func = dyn_cast<FuncDecl>(result.getValueDecl()))
96+
return func;
97+
}
98+
99+
return nullptr;
100+
}
101+
49102
static std::pair<BraceStmt *, bool>
50103
deriveBodyActor_enqueuePartialTask(
51104
AbstractFunctionDecl *enqueuePartialTask, void *) {
105+
// func enqueue(partialTask: PartialAsyncTask) {
106+
// _defaultActorQueueEnqueuePartialTask(
107+
// actor: self, queue: &self.$__actor_storage, partialTask: partialTask)
108+
// }
52109
ASTContext &ctx = enqueuePartialTask->getASTContext();
53110

54-
// FIXME: Call into runtime API to enqueue the task, once we figure out
55-
// what that runtime API should look like.
111+
// Dig out the $__actor_storage property.
112+
auto classDecl = enqueuePartialTask->getDeclContext()->getSelfClassDecl();
113+
VarDecl *storageVar = nullptr;
114+
for (auto decl : classDecl->lookupDirect(ctx.Id_actorStorage)) {
115+
storageVar = dyn_cast<VarDecl>(decl);
116+
if (storageVar)
117+
break;
118+
}
119+
120+
// Produce an empty brace statement on failure.
121+
auto failure = [&]() -> std::pair<BraceStmt *, bool> {
122+
auto body = BraceStmt::create(
123+
ctx, SourceLoc(), { }, SourceLoc(), /*implicit=*/true);
124+
return { body, /*isTypeChecked=*/true };
125+
};
126+
127+
if (!storageVar) {
128+
classDecl->diagnose(
129+
diag::concurrency_lib_missing, ctx.Id_actorStorage.str());
130+
return failure();
131+
}
132+
133+
// Call into the runtime to enqueue the task.
134+
auto fn = getDefaultActorQueueEnqueue(classDecl, classDecl->getLoc());
135+
if (!fn) {
136+
classDecl->diagnose(
137+
diag::concurrency_lib_missing, "_defaultActorQueueEnqueuePartialTask");
138+
return failure();
139+
}
140+
141+
// Reference to _defaultActorQueueEnqueuePartialTask.
142+
auto fnRef = new (ctx) DeclRefExpr(fn, DeclNameLoc(), /*Implicit=*/true);
143+
fnRef->setType(fn->getInterfaceType());
144+
145+
// self argument to the function.
146+
auto selfDecl = enqueuePartialTask->getImplicitSelfDecl();
147+
Type selfType = enqueuePartialTask->mapTypeIntoContext(
148+
selfDecl->getValueInterfaceType());
149+
Expr *selfArg = new (ctx) DeclRefExpr(
150+
selfDecl, DeclNameLoc(), /*Implicit=*/true, AccessSemantics::Ordinary,
151+
selfType);
152+
selfArg = ErasureExpr::create(ctx, selfArg, ctx.getAnyObjectType(), { });
153+
selfArg->setImplicit();
154+
155+
// Address of the actor storage.
156+
auto module = classDecl->getModuleContext();
157+
Expr *selfBase = new (ctx) DeclRefExpr(
158+
selfDecl, DeclNameLoc(), /*Implicit=*/true, AccessSemantics::Ordinary,
159+
selfType);
160+
SubstitutionMap storageVarSubs = classDecl->getDeclaredTypeInContext()
161+
->getMemberSubstitutionMap(module, storageVar);
162+
ConcreteDeclRef storageVarDeclRef(storageVar, storageVarSubs);
163+
Type storageVarType = classDecl->mapTypeIntoContext(
164+
storageVar->getValueInterfaceType());
165+
Type storageVarRefType = LValueType::get(storageVarType);
166+
Expr *storageVarRefExpr = new (ctx) MemberRefExpr(
167+
selfBase, SourceLoc(), storageVarDeclRef, DeclNameLoc(),
168+
/*Implicit=*/true);
169+
storageVarRefExpr->setType(storageVarRefType);
170+
storageVarRefExpr = new (ctx) InOutExpr(
171+
SourceLoc(), storageVarRefExpr, storageVarType, /*isImplicit=*/true);
172+
173+
// The partial asynchronous task.
174+
auto partialTaskParam = enqueuePartialTask->getParameters()->get(0);
175+
Expr *partialTask = new (ctx) DeclRefExpr(
176+
partialTaskParam, DeclNameLoc(), /*Implicit=*/true,
177+
AccessSemantics::Ordinary,
178+
enqueuePartialTask->mapTypeIntoContext(
179+
partialTaskParam->getValueInterfaceType()));
180+
181+
// Form the call itself.
182+
auto call = CallExpr::createImplicit(
183+
ctx, fnRef, { selfArg, storageVarRefExpr, partialTask },
184+
{ ctx.getIdentifier("actor"), ctx.getIdentifier("queue"),
185+
ctx.Id_partialTask });
186+
call->setType(fn->getResultInterfaceType());
187+
call->setThrows(false);
56188

57189
auto body = BraceStmt::create(
58-
ctx, SourceLoc(), { }, SourceLoc(), /*implicit=*/true);
190+
ctx, SourceLoc(), { call }, SourceLoc(), /*implicit=*/true);
59191
return { body, /*isTypeChecked=*/true };
60192
}
61193

62194
/// Derive the declaration of Actor's enqueue(partialTask:).
63195
static ValueDecl *deriveActor_enqueuePartialTask(DerivedConformance &derived) {
64196
ASTContext &ctx = derived.Context;
65197

198+
// Retrieve the types and declarations we'll need to form this operation.
66199
Type partialTaskType = getPartialAsyncTaskType(ctx);
67200
if (!partialTaskType) {
68-
derived.Nominal->diagnose(diag::partial_task_type_missing);
201+
derived.Nominal->diagnose(
202+
diag::concurrency_lib_missing, ctx.Id_PartialAsyncTask.str());
69203
return nullptr;
70204
}
71205

72206
auto parentDC = derived.getConformanceContext();
207+
Type defaultActorQueueType = getDefaultActorQueueType(
208+
parentDC, derived.ConformanceDecl->getLoc());
209+
if (!defaultActorQueueType) {
210+
derived.Nominal->diagnose(
211+
diag::concurrency_lib_missing, "_DefaultActorQueue");
212+
return nullptr;
213+
}
214+
215+
auto actorStorageCreateFn = getDefaultActorQueueCreate(
216+
parentDC, derived.ConformanceDecl->getLoc());
217+
if (!actorStorageCreateFn) {
218+
derived.Nominal->diagnose(
219+
diag::concurrency_lib_missing, "_defaultActorQueueCreate");
220+
return nullptr;
221+
}
222+
223+
// Partial task parameter to enqueue(partialTask:).
73224
auto partialTaskParamDecl = new (ctx) ParamDecl(
74225
SourceLoc(), SourceLoc(), ctx.Id_partialTask,
75226
SourceLoc(), ctx.Id_partialTask, parentDC);
76227
partialTaskParamDecl->setInterfaceType(partialTaskType);
77228
partialTaskParamDecl->setSpecifier(ParamSpecifier::Default);
78229

230+
// enqueue(partialTask:) method.
79231
ParameterList *params = ParameterList::createWithoutLoc(partialTaskParamDecl);
80232
auto func = FuncDecl::createImplicit(
81233
ctx, StaticSpellingKind::None, getEnqueuePartialTaskName(ctx),
82234
SourceLoc(), /*Async=*/false, /*Throws=*/false, /*GenericParams=*/nullptr,
83235
params, TupleType::getEmpty(ctx), parentDC);
84236
func->copyFormalAccessFrom(derived.Nominal);
85237
func->setBodySynthesizer(deriveBodyActor_enqueuePartialTask);
238+
func->setSynthesized();
86239

87240
// FIXME: This function should be "actor-unsafe", not "actor-independent", but
88241
// the latter is all we have at the moment.
89242
func->getAttrs().add(new (ctx) ActorIndependentAttr(/*IsImplicit=*/true));
90243

91-
derived.addMembersToConformanceContext({func});
244+
// Actor storage property and its initialization.
245+
auto actorStorage = new (ctx) VarDecl(
246+
/*isStatic=*/false, VarDecl::Introducer::Var, SourceLoc(),
247+
ctx.Id_actorStorage, parentDC);
248+
actorStorage->setInterfaceType(defaultActorQueueType);
249+
actorStorage->setImplicit();
250+
actorStorage->setAccess(AccessLevel::Private);
251+
actorStorage->getAttrs().add(new (ctx) FinalAttr(/*Implicit=*/true));
252+
253+
// Pattern binding to initialize the actor storage.
254+
Pattern *actorStoragePattern = NamedPattern::createImplicit(
255+
ctx, actorStorage);
256+
actorStoragePattern = TypedPattern::createImplicit(
257+
ctx, actorStoragePattern, defaultActorQueueType);
258+
259+
// Initialization expression.
260+
// FIXME: We want the equivalent of type(of: self) here, but we cannot refer
261+
// to self, so for now we use the static type instead.
262+
Type nominalType = derived.Nominal->getDeclaredTypeInContext();
263+
Expr *metatypeArg = TypeExpr::createImplicit(nominalType, ctx);
264+
Type anyObjectMetatype = ExistentialMetatypeType::get(ctx.getAnyObjectType());
265+
metatypeArg = ErasureExpr::create(ctx, metatypeArg, anyObjectMetatype, { });
266+
Expr *actorStorageCreateFnRef = new (ctx) DeclRefExpr(
267+
actorStorageCreateFn, DeclNameLoc(), /*Implicit=*/true);
268+
actorStorageCreateFnRef->setType(actorStorageCreateFn->getInterfaceType());
269+
270+
auto actorStorageInit = CallExpr::createImplicit(
271+
ctx, actorStorageCreateFnRef, { metatypeArg}, { Identifier() });
272+
actorStorageInit->setType(actorStorageCreateFn->getResultInterfaceType());
273+
actorStorageInit->setThrows(false);
274+
275+
auto actorStoragePatternBinding = PatternBindingDecl::createImplicit(
276+
ctx, StaticSpellingKind::None, actorStoragePattern, actorStorageInit,
277+
parentDC);
278+
actorStoragePatternBinding->setInitializerChecked(0);
279+
280+
derived.addMembersToConformanceContext(
281+
{ func, actorStorage, actorStoragePatternBinding });
92282
return func;
93283
}
94284

@@ -97,7 +287,7 @@ ValueDecl *DerivedConformance::deriveActor(ValueDecl *requirement) {
97287
if (!func)
98288
return nullptr;
99289

100-
if (func->getName() == getEnqueuePartialTaskName(Context))
290+
if (isEnqueuePartialTask(Context, func->getName()))
101291
return deriveActor_enqueuePartialTask(*this);
102292

103293
return nullptr;

lib/Sema/TypeCheckStorage.cpp

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
#include "swift/AST/ParameterList.h"
3131
#include "swift/AST/Pattern.h"
3232
#include "swift/AST/PropertyWrappers.h"
33+
#include "swift/AST/ProtocolConformance.h"
3334
#include "swift/AST/SourceFile.h"
3435
#include "swift/AST/TypeCheckRequests.h"
3536
#include "swift/AST/Types.h"
@@ -113,6 +114,21 @@ static void computeLoweredStoredProperties(NominalTypeDecl *decl) {
113114
if (var->hasAttachedPropertyWrapper())
114115
(void) var->getPropertyWrapperBackingProperty();
115116
}
117+
118+
// If this is an actor class, check conformance to the Actor protocol to
119+
// ensure that the actor storage will get created (if needed).
120+
if (auto classDecl = dyn_cast<ClassDecl>(decl)) {
121+
if (classDecl->isActor()) {
122+
ASTContext &ctx = decl->getASTContext();
123+
if (auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor)) {
124+
SmallVector<ProtocolConformance *, 1> conformances;
125+
classDecl->lookupConformance(
126+
decl->getModuleContext(), actorProto, conformances);
127+
for (auto conformance : conformances)
128+
TypeChecker::checkConformance(conformance->getRootNormalConformance());
129+
}
130+
}
131+
}
116132
}
117133

118134
ArrayRef<VarDecl *>
@@ -128,10 +144,16 @@ StoredPropertiesRequest::evaluate(Evaluator &evaluator,
128144
if (isa<SourceFile>(decl->getModuleScopeContext()))
129145
computeLoweredStoredProperties(decl);
130146

147+
ASTContext &ctx = decl->getASTContext();
131148
for (auto *member : decl->getMembers()) {
132149
if (auto *var = dyn_cast<VarDecl>(member))
133-
if (!var->isStatic() && var->hasStorage())
134-
results.push_back(var);
150+
if (!var->isStatic() && var->hasStorage()) {
151+
// Actor storage always goes at the beginning.
152+
if (var->getName() == ctx.Id_actorStorage)
153+
results.insert(results.begin(), var);
154+
else
155+
results.push_back(var);
156+
}
135157
}
136158

137159
return decl->getASTContext().AllocateCopy(results);

stdlib/public/Concurrency/Actor.swift

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,33 @@ public protocol Actor: AnyObject {
2323
/// Enqueue a new partial task that will be executed in the actor's context.
2424
func enqueue(partialTask: PartialAsyncTask)
2525
}
26+
27+
/// A native actor queue, which schedules partial tasks onto a serial queue.
28+
public struct _NativeActorQueue {
29+
// TODO: This is just a stub for now
30+
}
31+
32+
/// The default type to be used for an actor's queue when an actor does not
33+
/// provide its own implementation of `enqueue(partialTask:)`.
34+
public typealias _DefaultActorQueue = _NativeActorQueue
35+
36+
/// Called to create a new default actor queue instance for a class of the given
37+
/// type. The implementation will call this within the actor's initializer to
38+
/// initialize the actor queue.
39+
public func _defaultActorQueueCreate(
40+
_ actorClass: AnyObject.Type
41+
) -> _DefaultActorQueue {
42+
_DefaultActorQueue()
43+
}
44+
45+
/// Called by the synthesized implementation of enqueue(partialTask:).
46+
///
47+
/// The implementation is provided with the address of the synthesized instance
48+
/// property for the actor queue, so that it need not be at a fixed offset.
49+
public func _defaultActorQueueEnqueuePartialTask(
50+
actor: AnyObject,
51+
queue: inout _DefaultActorQueue,
52+
partialTask: PartialAsyncTask
53+
) {
54+
// TODO: Implement queueing.
55+
}

0 commit comments

Comments
 (0)