mirror of
https://github.com/apple/swift.git
synced 2025-12-21 12:14:44 +01:00
[NFC] Extract executor-handling stuff into SILGenConcurrency.cpp
This commit is contained in:
@@ -1264,178 +1264,7 @@ void SILGenFunction::emitProlog(
|
||||
capture, ++ArgNo);
|
||||
}
|
||||
|
||||
// Whether the given declaration context is nested within an actor's
|
||||
// destructor.
|
||||
auto isInActorDestructor = [](DeclContext *dc) {
|
||||
while (!dc->isModuleScopeContext() && !dc->isTypeContext()) {
|
||||
if (auto destructor = dyn_cast<DestructorDecl>(dc)) {
|
||||
switch (getActorIsolation(destructor)) {
|
||||
case ActorIsolation::ActorInstance:
|
||||
return true;
|
||||
|
||||
case ActorIsolation::GlobalActor:
|
||||
// Global-actor-isolated types should likely have deinits that
|
||||
// are not themselves actor-isolated, yet still have access to
|
||||
// the instance properties of the class.
|
||||
return false;
|
||||
|
||||
case ActorIsolation::Nonisolated:
|
||||
case ActorIsolation::NonisolatedUnsafe:
|
||||
case ActorIsolation::Unspecified:
|
||||
return false;
|
||||
|
||||
case ActorIsolation::Erased:
|
||||
llvm_unreachable("deinit cannot have erased isolation");
|
||||
}
|
||||
}
|
||||
|
||||
dc = dc->getParent();
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// Initialize ExpectedExecutor if:
|
||||
// - this function is async or
|
||||
// - this function is sync and isolated to an actor, and we want to
|
||||
// dynamically check that we're on the right executor.
|
||||
//
|
||||
// Actor destructors are isolated in the sense that we now have a
|
||||
// unique reference to the actor, but we probably aren't running on
|
||||
// the actor's executor, so we cannot safely do this check.
|
||||
//
|
||||
// Defer bodies are always called synchronously within their enclosing
|
||||
// function, so the check is unnecessary; in addition, we cannot
|
||||
// necessarily perform the check because the defer may not have
|
||||
// captured the isolated parameter of the enclosing function.
|
||||
bool wantDataRaceChecks = getOptions().EnableActorDataRaceChecks &&
|
||||
!F.isAsync() &&
|
||||
!isInActorDestructor(FunctionDC) &&
|
||||
!F.isDefer();
|
||||
|
||||
// FIXME: Avoid loading and checking the expected executor if concurrency is
|
||||
// unavailable. This is specifically relevant for MainActor isolated contexts,
|
||||
// which are allowed to be available on OSes where concurrency is not
|
||||
// available. rdar://106827064
|
||||
|
||||
// Local function to load the expected executor from a local actor
|
||||
auto loadExpectedExecutorForLocalVar = [&](VarDecl *var) {
|
||||
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
|
||||
Type actorType = var->getTypeInContext();
|
||||
RValue actorInstanceRV = emitRValueForDecl(
|
||||
loc, var, actorType, AccessSemantics::Ordinary);
|
||||
ManagedValue actorInstance =
|
||||
std::move(actorInstanceRV).getScalarValue();
|
||||
ExpectedExecutor = emitLoadActorExecutor(loc, actorInstance);
|
||||
};
|
||||
|
||||
if (auto *funcDecl =
|
||||
dyn_cast_or_null<AbstractFunctionDecl>(FunctionDC->getAsDecl())) {
|
||||
auto actorIsolation = getActorIsolation(funcDecl);
|
||||
switch (actorIsolation.getKind()) {
|
||||
case ActorIsolation::Unspecified:
|
||||
case ActorIsolation::Nonisolated:
|
||||
case ActorIsolation::NonisolatedUnsafe:
|
||||
break;
|
||||
|
||||
case ActorIsolation::Erased:
|
||||
llvm_unreachable("method cannot have erased isolation");
|
||||
|
||||
case ActorIsolation::ActorInstance: {
|
||||
// Only produce an executor for actor-isolated functions that are async
|
||||
// or are local functions. The former require a hop, while the latter
|
||||
// are prone to dynamic data races in code that does not enforce Sendable
|
||||
// completely.
|
||||
if (F.isAsync() ||
|
||||
(wantDataRaceChecks && funcDecl->isLocalCapture())) {
|
||||
if (auto isolatedParam = funcDecl->getCaptureInfo()
|
||||
.getIsolatedParamCapture()) {
|
||||
loadExpectedExecutorForLocalVar(isolatedParam);
|
||||
} else {
|
||||
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
|
||||
ManagedValue actorArg;
|
||||
if (actorIsolation.getActorInstanceParameter() == 0) {
|
||||
assert(selfParam && "no self parameter for ActorInstance isolation");
|
||||
ManagedValue selfArg;
|
||||
if (F.getSelfArgument()->getOwnershipKind() ==
|
||||
OwnershipKind::Guaranteed) {
|
||||
selfArg = ManagedValue::forBorrowedRValue(F.getSelfArgument());
|
||||
} else {
|
||||
selfArg =
|
||||
ManagedValue::forUnmanagedOwnedValue(F.getSelfArgument());
|
||||
}
|
||||
ExpectedExecutor = emitLoadActorExecutor(loc, selfArg);
|
||||
} else {
|
||||
unsigned isolatedParamIdx =
|
||||
actorIsolation.getActorInstanceParameter() - 1;
|
||||
auto param = funcDecl->getParameters()->get(isolatedParamIdx);
|
||||
assert(param->isIsolated());
|
||||
loadExpectedExecutorForLocalVar(param);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ActorIsolation::GlobalActor:
|
||||
if (F.isAsync() || wantDataRaceChecks) {
|
||||
ExpectedExecutor =
|
||||
emitLoadGlobalActorExecutor(actorIsolation.getGlobalActor());
|
||||
}
|
||||
break;
|
||||
}
|
||||
} else if (auto *closureExpr = dyn_cast<AbstractClosureExpr>(FunctionDC)) {
|
||||
bool wantExecutor = F.isAsync() || wantDataRaceChecks;
|
||||
auto actorIsolation = closureExpr->getActorIsolation();
|
||||
switch (actorIsolation.getKind()) {
|
||||
case ActorIsolation::Unspecified:
|
||||
case ActorIsolation::Nonisolated:
|
||||
case ActorIsolation::NonisolatedUnsafe:
|
||||
break;
|
||||
|
||||
case ActorIsolation::Erased:
|
||||
llvm_unreachable("closure cannot have erased isolation");
|
||||
|
||||
case ActorIsolation::ActorInstance: {
|
||||
if (wantExecutor) {
|
||||
loadExpectedExecutorForLocalVar(actorIsolation.getActorInstance());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ActorIsolation::GlobalActor:
|
||||
if (wantExecutor) {
|
||||
ExpectedExecutor =
|
||||
emitLoadGlobalActorExecutor(actorIsolation.getGlobalActor());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// In async functions, the generic executor is our expected executor
|
||||
// if we don't have any sort of isolation.
|
||||
if (!ExpectedExecutor && F.isAsync() && !unsafelyInheritsExecutor()) {
|
||||
ExpectedExecutor = emitGenericExecutor(
|
||||
RegularLocation::getAutoGeneratedLocation(F.getLocation()));
|
||||
}
|
||||
|
||||
// Jump to the expected executor.
|
||||
if (ExpectedExecutor) {
|
||||
if (F.isAsync()) {
|
||||
// For an async function, hop to the executor.
|
||||
B.createHopToExecutor(
|
||||
RegularLocation::getDebugOnlyLocation(F.getLocation(), getModule()),
|
||||
ExpectedExecutor,
|
||||
/*mandatory*/ false);
|
||||
} else {
|
||||
// For a synchronous function, check that we're on the same executor.
|
||||
// Note: if we "know" that the code is completely Sendable-safe, this
|
||||
// is unnecessary. The type checker will need to make this determination.
|
||||
emitPreconditionCheckExpectedExecutor(
|
||||
RegularLocation::getAutoGeneratedLocation(F.getLocation()),
|
||||
ExpectedExecutor);
|
||||
}
|
||||
}
|
||||
emitExpectedExecutor();
|
||||
|
||||
// IMPORTANT: This block should be the last one in `emitProlog`,
|
||||
// since it terminates BB and no instructions should be insterted after it.
|
||||
@@ -1453,186 +1282,6 @@ void SILGenFunction::emitProlog(
|
||||
}
|
||||
}
|
||||
|
||||
SILValue SILGenFunction::emitMainExecutor(SILLocation loc) {
|
||||
auto &ctx = getASTContext();
|
||||
auto builtinName = ctx.getIdentifier(
|
||||
getBuiltinName(BuiltinValueKind::BuildMainActorExecutorRef));
|
||||
auto resultType = SILType::getPrimitiveObjectType(ctx.TheExecutorType);
|
||||
|
||||
return B.createBuiltin(loc, builtinName, resultType, {}, {});
|
||||
}
|
||||
|
||||
SILValue SILGenFunction::emitGenericExecutor(SILLocation loc) {
|
||||
// The generic executor is encoded as the nil value of
|
||||
// llvm::Optional<Builtin.SerialExecutor>.
|
||||
auto ty = SILType::getOptionalType(
|
||||
SILType::getPrimitiveObjectType(
|
||||
getASTContext().TheExecutorType));
|
||||
return B.createOptionalNone(loc, ty);
|
||||
}
|
||||
|
||||
void SILGenFunction::emitPrologGlobalActorHop(SILLocation loc,
|
||||
Type globalActor) {
|
||||
ExpectedExecutor = emitLoadGlobalActorExecutor(globalActor);
|
||||
B.createHopToExecutor(RegularLocation::getDebugOnlyLocation(loc, getModule()),
|
||||
ExpectedExecutor, /*mandatory*/ false);
|
||||
}
|
||||
|
||||
SILValue SILGenFunction::emitLoadGlobalActorExecutor(Type globalActor) {
|
||||
CanType actorType = globalActor->getCanonicalType();
|
||||
NominalTypeDecl *nominal = actorType->getNominalOrBoundGenericNominal();
|
||||
VarDecl *sharedInstanceDecl = nominal->getGlobalActorInstance();
|
||||
assert(sharedInstanceDecl && "no shared actor field in global actor");
|
||||
SubstitutionMap subs =
|
||||
actorType->getContextSubstitutionMap(SGM.SwiftModule, nominal);
|
||||
SILLocation loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
|
||||
Type instanceType =
|
||||
actorType->getTypeOfMember(SGM.SwiftModule, sharedInstanceDecl);
|
||||
|
||||
auto metaRepr =
|
||||
nominal->isResilient(SGM.SwiftModule, F.getResilienceExpansion())
|
||||
? MetatypeRepresentation::Thick
|
||||
: MetatypeRepresentation::Thin;
|
||||
|
||||
CanType actorMetaType = CanMetatypeType::get(actorType, metaRepr);
|
||||
ManagedValue actorMetaTypeValue =
|
||||
ManagedValue::forObjectRValueWithoutOwnership(B.createMetatype(
|
||||
loc, SILType::getPrimitiveObjectType(actorMetaType)));
|
||||
|
||||
RValue actorInstanceRV = emitRValueForStorageLoad(loc, actorMetaTypeValue,
|
||||
actorMetaType, /*isSuper*/ false, sharedInstanceDecl, PreparedArguments(),
|
||||
subs, AccessSemantics::Ordinary, instanceType, SGFContext());
|
||||
ManagedValue actorInstance = std::move(actorInstanceRV).getScalarValue();
|
||||
return emitLoadActorExecutor(loc, actorInstance);
|
||||
}
|
||||
|
||||
SILValue SILGenFunction::emitLoadActorExecutor(SILLocation loc,
|
||||
ManagedValue actor) {
|
||||
SILValue actorV;
|
||||
if (isInFormalEvaluationScope())
|
||||
actorV = actor.formalAccessBorrow(*this, loc).getValue();
|
||||
else
|
||||
actorV = actor.borrow(*this, loc).getValue();
|
||||
|
||||
// For now, we just want to emit a hop_to_executor directly to the
|
||||
// actor; LowerHopToActor will add the emission logic necessary later.
|
||||
return actorV;
|
||||
}
|
||||
|
||||
ExecutorBreadcrumb
|
||||
SILGenFunction::emitHopToTargetActor(SILLocation loc,
|
||||
llvm::Optional<ActorIsolation> maybeIso,
|
||||
llvm::Optional<ManagedValue> maybeSelf) {
|
||||
if (!maybeIso)
|
||||
return ExecutorBreadcrumb();
|
||||
|
||||
if (auto executor = emitExecutor(loc, *maybeIso, maybeSelf)) {
|
||||
return emitHopToTargetExecutor(loc, *executor);
|
||||
} else {
|
||||
return ExecutorBreadcrumb();
|
||||
}
|
||||
}
|
||||
|
||||
ExecutorBreadcrumb SILGenFunction::emitHopToTargetExecutor(
|
||||
SILLocation loc, SILValue executor) {
|
||||
// Record that we need to hop back to the current executor.
|
||||
auto breadcrumb = ExecutorBreadcrumb(true);
|
||||
B.createHopToExecutor(RegularLocation::getDebugOnlyLocation(loc, getModule()),
|
||||
executor, /*mandatory*/ false);
|
||||
return breadcrumb;
|
||||
}
|
||||
|
||||
llvm::Optional<SILValue>
|
||||
SILGenFunction::emitExecutor(SILLocation loc, ActorIsolation isolation,
|
||||
llvm::Optional<ManagedValue> maybeSelf) {
|
||||
switch (isolation.getKind()) {
|
||||
case ActorIsolation::Unspecified:
|
||||
case ActorIsolation::Nonisolated:
|
||||
case ActorIsolation::NonisolatedUnsafe:
|
||||
return llvm::None;
|
||||
|
||||
case ActorIsolation::Erased:
|
||||
llvm_unreachable("executor emission for erased isolation is unimplemented");
|
||||
|
||||
case ActorIsolation::ActorInstance: {
|
||||
// "self" here means the actor instance's "self" value.
|
||||
assert(maybeSelf.has_value() && "actor-instance but no self provided?");
|
||||
auto self = maybeSelf.value();
|
||||
return emitLoadActorExecutor(loc, self);
|
||||
}
|
||||
|
||||
case ActorIsolation::GlobalActor:
|
||||
return emitLoadGlobalActorExecutor(isolation.getGlobalActor());
|
||||
}
|
||||
llvm_unreachable("covered switch");
|
||||
}
|
||||
|
||||
void SILGenFunction::emitHopToActorValue(SILLocation loc, ManagedValue actor) {
|
||||
// TODO: can the type system enforce this async requirement?
|
||||
if (!F.isAsync()) {
|
||||
llvm::report_fatal_error("Builtin.hopToActor must be in an async function");
|
||||
}
|
||||
auto isolation =
|
||||
getActorIsolationOfContext(FunctionDC, [](AbstractClosureExpr *CE) {
|
||||
return CE->getActorIsolation();
|
||||
});
|
||||
if (isolation != ActorIsolation::Nonisolated &&
|
||||
isolation != ActorIsolation::NonisolatedUnsafe &&
|
||||
isolation != ActorIsolation::Unspecified) {
|
||||
// TODO: Explicit hop with no hop-back should only be allowed in nonisolated
|
||||
// async functions. But it needs work for any closure passed to
|
||||
// Task.detached, which currently has unspecified isolation.
|
||||
llvm::report_fatal_error(
|
||||
"Builtin.hopToActor must be in an actor-independent function");
|
||||
}
|
||||
SILValue executor = emitLoadActorExecutor(loc, actor);
|
||||
B.createHopToExecutor(RegularLocation::getDebugOnlyLocation(loc, getModule()),
|
||||
executor, /*mandatory*/ true);
|
||||
}
|
||||
|
||||
void SILGenFunction::emitPreconditionCheckExpectedExecutor(
|
||||
SILLocation loc, SILValue executorOrActor) {
|
||||
auto checkExecutor = SGM.getCheckExpectedExecutor();
|
||||
if (!checkExecutor)
|
||||
return;
|
||||
|
||||
// We don't want the debugger to step into these.
|
||||
loc.markAutoGenerated();
|
||||
|
||||
// Get the executor.
|
||||
SILValue executor = B.createExtractExecutor(loc, executorOrActor);
|
||||
|
||||
// Call the library function that performs the checking.
|
||||
auto args = emitSourceLocationArgs(loc.getSourceLoc(), loc);
|
||||
|
||||
emitApplyOfLibraryIntrinsic(
|
||||
loc, checkExecutor, SubstitutionMap(),
|
||||
{args.filenameStartPointer, args.filenameLength, args.filenameIsAscii,
|
||||
args.line, ManagedValue::forObjectRValueWithoutOwnership(executor)},
|
||||
SGFContext());
|
||||
}
|
||||
|
||||
bool SILGenFunction::unsafelyInheritsExecutor() {
|
||||
if (auto fn = dyn_cast<AbstractFunctionDecl>(FunctionDC))
|
||||
return fn->getAttrs().hasAttribute<UnsafeInheritExecutorAttr>();
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExecutorBreadcrumb::emit(SILGenFunction &SGF, SILLocation loc) {
|
||||
if (mustReturnToExecutor) {
|
||||
assert(SGF.ExpectedExecutor || SGF.unsafelyInheritsExecutor());
|
||||
if (auto executor = SGF.ExpectedExecutor)
|
||||
SGF.B.createHopToExecutor(
|
||||
RegularLocation::getDebugOnlyLocation(loc, SGF.getModule()), executor,
|
||||
/*mandatory*/ false);
|
||||
}
|
||||
}
|
||||
|
||||
SILValue SILGenFunction::emitGetCurrentExecutor(SILLocation loc) {
|
||||
assert(ExpectedExecutor && "prolog failed to set up expected executor?");
|
||||
return ExpectedExecutor;
|
||||
}
|
||||
|
||||
static void emitIndirectPackParameter(SILGenFunction &SGF,
|
||||
PackType *resultType,
|
||||
CanTupleEltTypeArrayRef
|
||||
|
||||
Reference in New Issue
Block a user