Skip to content

Commit c9dac8d

Browse files
committed
[NFC] Extract executor-handling stuff into SILGenConcurrency.cpp
1 parent a42c5bd commit c9dac8d

5 files changed

+397
-367
lines changed

lib/SILGen/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ add_swift_host_library(swiftSILGen STATIC
1515
SILGenBridging.cpp
1616
SILGenBuilder.cpp
1717
SILGenBuiltin.cpp
18+
SILGenConcurrency.cpp
1819
SILGenConstructor.cpp
1920
SILGenConvert.cpp
2021
SILGenDecl.cpp

lib/SILGen/SILGenConcurrency.cpp

+391
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,391 @@
1+
//===--- SILGenConcurrency.cpp - Concurrency-specific SILGen --------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2024 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#include "ArgumentSource.h"
14+
#include "ExecutorBreadcrumb.h"
15+
#include "RValue.h"
16+
#include "Scope.h"
17+
#include "swift/AST/ASTContext.h"
18+
#include "swift/Basic/Range.h"
19+
20+
using namespace swift;
21+
using namespace Lowering;
22+
23+
void SILGenFunction::emitExpectedExecutor() {
24+
// Whether the given declaration context is nested within an actor's
25+
// destructor.
26+
auto isInActorDestructor = [](DeclContext *dc) {
27+
while (!dc->isModuleScopeContext() && !dc->isTypeContext()) {
28+
if (auto destructor = dyn_cast<DestructorDecl>(dc)) {
29+
switch (getActorIsolation(destructor)) {
30+
case ActorIsolation::ActorInstance:
31+
return true;
32+
33+
case ActorIsolation::GlobalActor:
34+
// Global-actor-isolated types should likely have deinits that
35+
// are not themselves actor-isolated, yet still have access to
36+
// the instance properties of the class.
37+
return false;
38+
39+
case ActorIsolation::Nonisolated:
40+
case ActorIsolation::NonisolatedUnsafe:
41+
case ActorIsolation::Unspecified:
42+
return false;
43+
44+
case ActorIsolation::Erased:
45+
llvm_unreachable("deinit cannot have erased isolation");
46+
}
47+
}
48+
49+
dc = dc->getParent();
50+
}
51+
52+
return false;
53+
};
54+
55+
// Initialize ExpectedExecutor if:
56+
// - this function is async or
57+
// - this function is sync and isolated to an actor, and we want to
58+
// dynamically check that we're on the right executor.
59+
//
60+
// Actor destructors are isolated in the sense that we now have a
61+
// unique reference to the actor, but we probably aren't running on
62+
// the actor's executor, so we cannot safely do this check.
63+
//
64+
// Defer bodies are always called synchronously within their enclosing
65+
// function, so the check is unnecessary; in addition, we cannot
66+
// necessarily perform the check because the defer may not have
67+
// captured the isolated parameter of the enclosing function.
68+
bool wantDataRaceChecks = getOptions().EnableActorDataRaceChecks &&
69+
!F.isAsync() &&
70+
!isInActorDestructor(FunctionDC) &&
71+
!F.isDefer();
72+
73+
// FIXME: Avoid loading and checking the expected executor if concurrency is
74+
// unavailable. This is specifically relevant for MainActor isolated contexts,
75+
// which are allowed to be available on OSes where concurrency is not
76+
// available. rdar://106827064
77+
78+
// Local function to load the expected executor from a local actor
79+
auto loadExpectedExecutorForLocalVar = [&](VarDecl *var) {
80+
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
81+
Type actorType = var->getTypeInContext();
82+
RValue actorInstanceRV = emitRValueForDecl(
83+
loc, var, actorType, AccessSemantics::Ordinary);
84+
ManagedValue actorInstance =
85+
std::move(actorInstanceRV).getScalarValue();
86+
ExpectedExecutor = emitLoadActorExecutor(loc, actorInstance);
87+
};
88+
89+
if (auto *funcDecl =
90+
dyn_cast_or_null<AbstractFunctionDecl>(FunctionDC->getAsDecl())) {
91+
auto actorIsolation = getActorIsolation(funcDecl);
92+
switch (actorIsolation.getKind()) {
93+
case ActorIsolation::Unspecified:
94+
case ActorIsolation::Nonisolated:
95+
case ActorIsolation::NonisolatedUnsafe:
96+
break;
97+
98+
case ActorIsolation::Erased:
99+
llvm_unreachable("method cannot have erased isolation");
100+
101+
case ActorIsolation::ActorInstance: {
102+
// Only produce an executor for actor-isolated functions that are async
103+
// or are local functions. The former require a hop, while the latter
104+
// are prone to dynamic data races in code that does not enforce Sendable
105+
// completely.
106+
if (F.isAsync() ||
107+
(wantDataRaceChecks && funcDecl->isLocalCapture())) {
108+
if (auto isolatedParam = funcDecl->getCaptureInfo()
109+
.getIsolatedParamCapture()) {
110+
loadExpectedExecutorForLocalVar(isolatedParam);
111+
} else {
112+
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
113+
ManagedValue actorArg;
114+
if (actorIsolation.getActorInstanceParameter() == 0) {
115+
ManagedValue selfArg;
116+
if (F.getSelfArgument()->getOwnershipKind() ==
117+
OwnershipKind::Guaranteed) {
118+
selfArg = ManagedValue::forBorrowedRValue(F.getSelfArgument());
119+
} else {
120+
selfArg =
121+
ManagedValue::forUnmanagedOwnedValue(F.getSelfArgument());
122+
}
123+
ExpectedExecutor = emitLoadActorExecutor(loc, selfArg);
124+
} else {
125+
unsigned isolatedParamIdx =
126+
actorIsolation.getActorInstanceParameter() - 1;
127+
auto param = funcDecl->getParameters()->get(isolatedParamIdx);
128+
assert(param->isIsolated());
129+
loadExpectedExecutorForLocalVar(param);
130+
}
131+
}
132+
}
133+
break;
134+
}
135+
136+
case ActorIsolation::GlobalActor:
137+
if (F.isAsync() || wantDataRaceChecks) {
138+
ExpectedExecutor =
139+
emitLoadGlobalActorExecutor(actorIsolation.getGlobalActor());
140+
}
141+
break;
142+
}
143+
} else if (auto *closureExpr = dyn_cast<AbstractClosureExpr>(FunctionDC)) {
144+
bool wantExecutor = F.isAsync() || wantDataRaceChecks;
145+
auto actorIsolation = closureExpr->getActorIsolation();
146+
switch (actorIsolation.getKind()) {
147+
case ActorIsolation::Unspecified:
148+
case ActorIsolation::Nonisolated:
149+
case ActorIsolation::NonisolatedUnsafe:
150+
break;
151+
152+
case ActorIsolation::Erased:
153+
llvm_unreachable("closure cannot have erased isolation");
154+
155+
case ActorIsolation::ActorInstance: {
156+
if (wantExecutor) {
157+
loadExpectedExecutorForLocalVar(actorIsolation.getActorInstance());
158+
}
159+
break;
160+
}
161+
162+
case ActorIsolation::GlobalActor:
163+
if (wantExecutor) {
164+
ExpectedExecutor =
165+
emitLoadGlobalActorExecutor(actorIsolation.getGlobalActor());
166+
break;
167+
}
168+
}
169+
}
170+
171+
// In async functions, the generic executor is our expected executor
172+
// if we don't have any sort of isolation.
173+
if (!ExpectedExecutor && F.isAsync() && !unsafelyInheritsExecutor()) {
174+
ExpectedExecutor = emitGenericExecutor(
175+
RegularLocation::getAutoGeneratedLocation(F.getLocation()));
176+
}
177+
178+
// Jump to the expected executor.
179+
if (ExpectedExecutor) {
180+
if (F.isAsync()) {
181+
// For an async function, hop to the executor.
182+
B.createHopToExecutor(
183+
RegularLocation::getDebugOnlyLocation(F.getLocation(), getModule()),
184+
ExpectedExecutor,
185+
/*mandatory*/ false);
186+
} else {
187+
// For a synchronous function, check that we're on the same executor.
188+
// Note: if we "know" that the code is completely Sendable-safe, this
189+
// is unnecessary. The type checker will need to make this determination.
190+
emitPreconditionCheckExpectedExecutor(
191+
RegularLocation::getAutoGeneratedLocation(F.getLocation()),
192+
ExpectedExecutor);
193+
}
194+
}
195+
}
196+
197+
void SILGenFunction::emitConstructorPrologActorHop(
198+
SILLocation loc, llvm::Optional<ActorIsolation> maybeIso) {
199+
loc = loc.asAutoGenerated();
200+
if (maybeIso) {
201+
if (auto executor = emitExecutor(loc, *maybeIso, llvm::None)) {
202+
ExpectedExecutor = *executor;
203+
}
204+
}
205+
206+
if (!ExpectedExecutor)
207+
ExpectedExecutor = emitGenericExecutor(loc);
208+
209+
B.createHopToExecutor(loc, ExpectedExecutor, /*mandatory*/ false);
210+
}
211+
212+
void SILGenFunction::emitPrologGlobalActorHop(SILLocation loc,
213+
Type globalActor) {
214+
ExpectedExecutor = emitLoadGlobalActorExecutor(globalActor);
215+
B.createHopToExecutor(RegularLocation::getDebugOnlyLocation(loc, getModule()),
216+
ExpectedExecutor, /*mandatory*/ false);
217+
}
218+
219+
220+
SILValue SILGenFunction::emitMainExecutor(SILLocation loc) {
221+
auto &ctx = getASTContext();
222+
auto builtinName = ctx.getIdentifier(
223+
getBuiltinName(BuiltinValueKind::BuildMainActorExecutorRef));
224+
auto resultType = SILType::getPrimitiveObjectType(ctx.TheExecutorType);
225+
226+
return B.createBuiltin(loc, builtinName, resultType, {}, {});
227+
}
228+
229+
SILValue SILGenFunction::emitGenericExecutor(SILLocation loc) {
230+
// The generic executor is encoded as the nil value of
231+
// llvm::Optional<Builtin.SerialExecutor>.
232+
auto ty = SILType::getOptionalType(
233+
SILType::getPrimitiveObjectType(
234+
getASTContext().TheExecutorType));
235+
return B.createOptionalNone(loc, ty);
236+
}
237+
238+
SILValue SILGenFunction::emitLoadGlobalActorExecutor(Type globalActor) {
239+
CanType actorType = globalActor->getCanonicalType();
240+
NominalTypeDecl *nominal = actorType->getNominalOrBoundGenericNominal();
241+
VarDecl *sharedInstanceDecl = nominal->getGlobalActorInstance();
242+
assert(sharedInstanceDecl && "no shared actor field in global actor");
243+
SubstitutionMap subs =
244+
actorType->getContextSubstitutionMap(SGM.SwiftModule, nominal);
245+
SILLocation loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
246+
Type instanceType =
247+
actorType->getTypeOfMember(SGM.SwiftModule, sharedInstanceDecl);
248+
249+
auto metaRepr =
250+
nominal->isResilient(SGM.SwiftModule, F.getResilienceExpansion())
251+
? MetatypeRepresentation::Thick
252+
: MetatypeRepresentation::Thin;
253+
254+
CanType actorMetaType = CanMetatypeType::get(actorType, metaRepr);
255+
ManagedValue actorMetaTypeValue =
256+
ManagedValue::forObjectRValueWithoutOwnership(B.createMetatype(
257+
loc, SILType::getPrimitiveObjectType(actorMetaType)));
258+
259+
RValue actorInstanceRV = emitRValueForStorageLoad(loc, actorMetaTypeValue,
260+
actorMetaType, /*isSuper*/ false, sharedInstanceDecl, PreparedArguments(),
261+
subs, AccessSemantics::Ordinary, instanceType, SGFContext());
262+
ManagedValue actorInstance = std::move(actorInstanceRV).getScalarValue();
263+
return emitLoadActorExecutor(loc, actorInstance);
264+
}
265+
266+
SILValue SILGenFunction::emitLoadActorExecutor(SILLocation loc,
267+
ManagedValue actor) {
268+
SILValue actorV;
269+
if (isInFormalEvaluationScope())
270+
actorV = actor.formalAccessBorrow(*this, loc).getValue();
271+
else
272+
actorV = actor.borrow(*this, loc).getValue();
273+
274+
// For now, we just want to emit a hop_to_executor directly to the
275+
// actor; LowerHopToActor will add the emission logic necessary later.
276+
return actorV;
277+
}
278+
279+
ExecutorBreadcrumb
280+
SILGenFunction::emitHopToTargetActor(SILLocation loc,
281+
llvm::Optional<ActorIsolation> maybeIso,
282+
llvm::Optional<ManagedValue> maybeSelf) {
283+
if (!maybeIso)
284+
return ExecutorBreadcrumb();
285+
286+
if (auto executor = emitExecutor(loc, *maybeIso, maybeSelf)) {
287+
return emitHopToTargetExecutor(loc, *executor);
288+
} else {
289+
return ExecutorBreadcrumb();
290+
}
291+
}
292+
293+
ExecutorBreadcrumb SILGenFunction::emitHopToTargetExecutor(
294+
SILLocation loc, SILValue executor) {
295+
// Record that we need to hop back to the current executor.
296+
auto breadcrumb = ExecutorBreadcrumb(true);
297+
B.createHopToExecutor(RegularLocation::getDebugOnlyLocation(loc, getModule()),
298+
executor, /*mandatory*/ false);
299+
return breadcrumb;
300+
}
301+
302+
llvm::Optional<SILValue>
303+
SILGenFunction::emitExecutor(SILLocation loc, ActorIsolation isolation,
304+
llvm::Optional<ManagedValue> maybeSelf) {
305+
switch (isolation.getKind()) {
306+
case ActorIsolation::Unspecified:
307+
case ActorIsolation::Nonisolated:
308+
case ActorIsolation::NonisolatedUnsafe:
309+
return llvm::None;
310+
311+
case ActorIsolation::Erased:
312+
llvm_unreachable("executor emission for erased isolation is unimplemented");
313+
314+
case ActorIsolation::ActorInstance: {
315+
// "self" here means the actor instance's "self" value.
316+
assert(maybeSelf.has_value() && "actor-instance but no self provided?");
317+
auto self = maybeSelf.value();
318+
return emitLoadActorExecutor(loc, self);
319+
}
320+
321+
case ActorIsolation::GlobalActor:
322+
return emitLoadGlobalActorExecutor(isolation.getGlobalActor());
323+
}
324+
llvm_unreachable("covered switch");
325+
}
326+
327+
void SILGenFunction::emitHopToActorValue(SILLocation loc, ManagedValue actor) {
328+
// TODO: can the type system enforce this async requirement?
329+
if (!F.isAsync()) {
330+
llvm::report_fatal_error("Builtin.hopToActor must be in an async function");
331+
}
332+
auto isolation =
333+
getActorIsolationOfContext(FunctionDC, [](AbstractClosureExpr *CE) {
334+
return CE->getActorIsolation();
335+
});
336+
if (isolation != ActorIsolation::Nonisolated &&
337+
isolation != ActorIsolation::NonisolatedUnsafe &&
338+
isolation != ActorIsolation::Unspecified) {
339+
// TODO: Explicit hop with no hop-back should only be allowed in nonisolated
340+
// async functions. But it needs work for any closure passed to
341+
// Task.detached, which currently has unspecified isolation.
342+
llvm::report_fatal_error(
343+
"Builtin.hopToActor must be in an actor-independent function");
344+
}
345+
SILValue executor = emitLoadActorExecutor(loc, actor);
346+
B.createHopToExecutor(RegularLocation::getDebugOnlyLocation(loc, getModule()),
347+
executor, /*mandatory*/ true);
348+
}
349+
350+
void SILGenFunction::emitPreconditionCheckExpectedExecutor(
351+
SILLocation loc, SILValue executorOrActor) {
352+
auto checkExecutor = SGM.getCheckExpectedExecutor();
353+
if (!checkExecutor)
354+
return;
355+
356+
// We don't want the debugger to step into these.
357+
loc.markAutoGenerated();
358+
359+
// Get the executor.
360+
SILValue executor = B.createExtractExecutor(loc, executorOrActor);
361+
362+
// Call the library function that performs the checking.
363+
auto args = emitSourceLocationArgs(loc.getSourceLoc(), loc);
364+
365+
emitApplyOfLibraryIntrinsic(
366+
loc, checkExecutor, SubstitutionMap(),
367+
{args.filenameStartPointer, args.filenameLength, args.filenameIsAscii,
368+
args.line, ManagedValue::forObjectRValueWithoutOwnership(executor)},
369+
SGFContext());
370+
}
371+
372+
bool SILGenFunction::unsafelyInheritsExecutor() {
373+
if (auto fn = dyn_cast<AbstractFunctionDecl>(FunctionDC))
374+
return fn->getAttrs().hasAttribute<UnsafeInheritExecutorAttr>();
375+
return false;
376+
}
377+
378+
void ExecutorBreadcrumb::emit(SILGenFunction &SGF, SILLocation loc) {
379+
if (mustReturnToExecutor) {
380+
assert(SGF.ExpectedExecutor || SGF.unsafelyInheritsExecutor());
381+
if (auto executor = SGF.ExpectedExecutor)
382+
SGF.B.createHopToExecutor(
383+
RegularLocation::getDebugOnlyLocation(loc, SGF.getModule()), executor,
384+
/*mandatory*/ false);
385+
}
386+
}
387+
388+
SILValue SILGenFunction::emitGetCurrentExecutor(SILLocation loc) {
389+
assert(ExpectedExecutor && "prolog failed to set up expected executor?");
390+
return ExpectedExecutor;
391+
}

0 commit comments

Comments
 (0)