[NFC] Extract executor-handling stuff into SILGenConcurrency.cpp

This commit is contained in:
John McCall
2024-02-15 20:02:53 -05:00
parent a42c5bd76e
commit c9dac8d0b3
5 changed files with 397 additions and 367 deletions

View File

@@ -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