Skip to content

Properly erase closure isolation to @isolated(any) #71904

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 2 commits into from
Feb 27, 2024
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
10 changes: 10 additions & 0 deletions lib/SIL/IR/SILFunctionType.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1900,6 +1900,16 @@ lowerCaptureContextParameters(TypeConverter &TC, SILDeclRef function,
TypeExpansionContext expansion,
SmallVectorImpl<SILParameterInfo> &inputs) {

// If the function is a closure being converted to an @isolated(any) type,
// add the implicit isolation parameter.
if (auto closureInfo = TC.getClosureTypeInfo(function)) {
if (closureInfo->ExpectedLoweredType->hasErasedIsolation()) {
auto isolationTy = SILType::getOpaqueIsolationType(TC.Context);
inputs.push_back({isolationTy.getASTType(),
ParameterConvention::Direct_Guaranteed});
}
}

// NB: The generic signature may be elided from the lowered function type
// if the function is in a fully-specialized context, but we still need to
// canonicalize references to the generic parameters that may appear in
Expand Down
1 change: 0 additions & 1 deletion lib/SILGen/Cleanup.h
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,6 @@ class LLVM_LIBRARY_VISIBILITY CleanupManager {
/// we can only reap the cleanup stack up to the innermost depth
/// that we've handed out as a Scope.
Scope *innermostScope = nullptr;
FormalEvaluationScope *innermostFormalScope = nullptr;

void popTopDeadCleanups();
void emitCleanups(CleanupsDepth depth, CleanupLocation l,
Expand Down
189 changes: 149 additions & 40 deletions lib/SILGen/SILGenConcurrency.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,11 @@ SILValue SILGenFunction::emitGenericExecutor(SILLocation loc) {
return B.createOptionalNone(loc, ty);
}

ManagedValue SILGenFunction::emitNonIsolatedIsolation(SILLocation loc) {
return B.createManagedOptionalNone(loc,
SILType::getOpaqueIsolationType(getASTContext()));
}

SILValue SILGenFunction::emitLoadGlobalActorExecutor(Type globalActor) {
auto loc = RegularLocation::getAutoGeneratedLocation(F.getLocation());
auto actor = emitLoadOfGlobalActorShared(loc, globalActor->getCanonicalType());
Expand Down Expand Up @@ -268,8 +273,71 @@ SILGenFunction::emitLoadOfGlobalActorShared(SILLocation loc, CanType actorType)
return actorInstance;
}

ManagedValue
SILGenFunction::emitGlobalActorIsolation(SILLocation loc,
CanType globalActorType) {
// GlobalActor.shared returns Self, so this should be a value of
// GlobalActor type.
auto actor = emitLoadOfGlobalActorShared(loc, globalActorType);

// Since it's just a normal actor instance, we can use the normal path.
return emitActorInstanceIsolation(loc, actor, globalActorType);
}

/// Given a value of some non-optional actor type, convert it to
/// non-optional `any Actor` type.
static ManagedValue
emitNonOptionalActorInstanceIsolation(SILGenFunction &SGF, SILLocation loc,
ManagedValue actor, CanType actorType,
SILType anyActorTy) {
// If we have an `any Actor` already, we're done.
if (actor.getType() == anyActorTy)
return actor;

CanType anyActorType = anyActorTy.getASTType();
return SGF.emitTransformExistential(loc, actor, actorType, anyActorType);
}

ManagedValue
SILGenFunction::emitActorInstanceIsolation(SILLocation loc, ManagedValue actor,
CanType actorType) {
// $Optional<any Actor>
auto optionalAnyActorTy = SILType::getOpaqueIsolationType(getASTContext());
// Optional<any Actor> as a formal type (it's invariant to lowering)
auto optionalAnyActorType = optionalAnyActorTy.getASTType();

// If we started with an Optional<any Actor>, we're done.
if (actorType == optionalAnyActorType) {
return actor;
}

// Otherwise, if we have an optional value, we need to transform the payload.
auto actorObjectType = actorType.getOptionalObjectType();
if (actorObjectType) {
return emitOptionalToOptional(loc, actor, optionalAnyActorTy,
[&](SILGenFunction &SGF, SILLocation loc, ManagedValue actorObject,
SILType anyActorTy, SGFContext C) {
return emitNonOptionalActorInstanceIsolation(*this, loc, actorObject,
actorObjectType, anyActorTy);
});
}

// Otherwise, transform the non-optional value we have, then inject that
// into Optional.
SILType anyActorTy = optionalAnyActorTy.getOptionalObjectType();
ManagedValue anyActor =
emitNonOptionalActorInstanceIsolation(*this, loc, actor, actorType,
anyActorTy);

// Inject into `Optional`.
auto result = B.createOptionalSome(loc, anyActor);
return result;
}

SILValue SILGenFunction::emitLoadActorExecutor(SILLocation loc,
ManagedValue actor) {
// FIXME: Checking for whether we're in a formal evaluation scope
// like this doesn't seem like a good pattern.
SILValue actorV;
if (isInFormalEvaluationScope())
actorV = actor.formalAccessBorrow(*this, loc).getValue();
Expand All @@ -291,23 +359,18 @@ SILValue SILGenFunction::emitLoadErasedExecutor(SILLocation loc,
ManagedValue
SILGenFunction::emitLoadErasedIsolation(SILLocation loc,
ManagedValue fn) {
if (isInFormalEvaluationScope())
fn = fn.formalAccessBorrow(*this, loc);
else
fn = fn.borrow(*this, loc);
fn = fn.borrow(*this, loc);

// This expects a borrowed function and returns a borrowed (any Actor)?.
auto actor = B.createFunctionExtractIsolation(loc, fn.getValue());

return ManagedValue::forBorrowedObjectRValue(actor);
}

/// The ownership of the value returned here is mixed; callers that need
/// an owned value must call ensurePlusOne.
ManagedValue
SILGenFunction::emitLoadOfFunctionIsolation(SILLocation loc,
FunctionTypeIsolation isolation,
ManagedValue fn) {
SILGenFunction::emitFunctionTypeIsolation(SILLocation loc,
FunctionTypeIsolation isolation,
ManagedValue fn) {
switch (isolation.getKind()) {

// Parameter-isolated functions don't have a specific actor they're isolated
Expand All @@ -318,50 +381,96 @@ SILGenFunction::emitLoadOfFunctionIsolation(SILLocation loc,

// Emit nonisolated by simply emitting Optional.none in the result type.
case FunctionTypeIsolation::Kind::NonIsolated:
return B.createManagedOptionalNone(loc,
SILType::getOpaqueIsolationType(getASTContext()));
return emitNonIsolatedIsolation(loc);

// Emit global actor isolation by loading .shared from the global actor,
// erasing it into `any Actor`, and injecting that into Optional.
case FunctionTypeIsolation::Kind::GlobalActor: {
auto concreteActorType =
isolation.getGlobalActorType()->getCanonicalType();
case FunctionTypeIsolation::Kind::GlobalActor:
return emitGlobalActorIsolation(loc,
isolation.getGlobalActorType()->getCanonicalType());

// GlobalActor.shared returns Self, so this should be a value of the
// actor type.
auto actor = emitLoadOfGlobalActorShared(loc, concreteActorType);
// Emit @isolated(any) isolation by loading the actor reference from the
// function.
case FunctionTypeIsolation::Kind::Erased: {
Scope scope(*this, CleanupLocation(loc));
auto value = emitLoadErasedIsolation(loc, fn).copy(*this, loc);
return scope.popPreservingValue(value);
}
}

auto optionalAnyActorTy = SILType::getOpaqueIsolationType(getASTContext());
auto anyActorTy = optionalAnyActorTy.getOptionalObjectType();
assert(anyActorTy);
llvm_unreachable("bad kind");
}

static ActorIsolation getClosureIsolationInfo(SILDeclRef constant) {
if (auto closure = constant.getAbstractClosureExpr()) {
return closure->getActorIsolation();
}
auto func = constant.getAbstractFunctionDecl();
assert(func && "unexpected closure constant");
return getActorIsolation(func);
}

ArrayRef<ProtocolConformanceRef> conformances =
SGM.SwiftModule->collectExistentialConformances(concreteActorType,
anyActorTy.getASTType());
static ManagedValue emitLoadOfCaptureIsolation(SILGenFunction &SGF,
SILLocation loc,
VarDecl *isolatedCapture,
SILDeclRef constant,
ArrayRef<ManagedValue> captureArgs) {
auto &TC = SGF.SGM.Types;
auto captureInfo = TC.getLoweredLocalCaptures(constant);

auto isolatedVarType =
isolatedCapture->getInterfaceType()->getCanonicalType();

// Capture arguments are 1-1 with the lowered capture info.
auto captures = captureInfo.getCaptures();
for (auto i : indices(captures)) {
const auto &capture = captures[i];
if (capture.isDynamicSelfMetadata()) continue;
auto capturedVar = capture.getDecl();
if (capturedVar != isolatedCapture) continue;

// Captured actor references should always be captured as constants.
assert(TC.getDeclCaptureKind(capture,
TC.getCaptureTypeExpansionContext(constant))
== CaptureKind::Constant);

auto value = captureArgs[i].copy(SGF, loc);
return SGF.emitActorInstanceIsolation(loc, value, isolatedVarType);
}

for (auto conf: conformances)
SGM.useConformance(conf);
// The capture not being a lowered capture can happen in global code.
auto value = SGF.emitRValueForDecl(loc, isolatedCapture, isolatedVarType,
AccessSemantics::Ordinary)
.getAsSingleValue(SGF, loc);
return SGF.emitActorInstanceIsolation(loc, value, isolatedVarType);
}

// Erase to `any Actor`.
assert(anyActorTy.getPreferredExistentialRepresentation(concreteActorType)
== ExistentialRepresentation::Class);
auto erasedActor = B.createInitExistentialRef(loc, anyActorTy,
concreteActorType,
actor, conformances);
ManagedValue
SILGenFunction::emitClosureIsolation(SILLocation loc, SILDeclRef constant,
ArrayRef<ManagedValue> captures) {
auto isolation = getClosureIsolationInfo(constant);
switch (isolation) {
case ActorIsolation::Unspecified:
case ActorIsolation::Nonisolated:
case ActorIsolation::NonisolatedUnsafe:
return emitNonIsolatedIsolation(loc);

// Inject into `Optional`.
auto result = B.createOptionalSome(loc, erasedActor);
case ActorIsolation::Erased:
llvm_unreachable("closures cannot directly have erased isolation");

return result;
}
case ActorIsolation::GlobalActor:
return emitGlobalActorIsolation(loc,
isolation.getGlobalActor()->getCanonicalType());

// Emit @isolated(any) isolation by loading the actor reference from the
// function.
case FunctionTypeIsolation::Kind::Erased:
return emitLoadErasedIsolation(loc, fn);
case ActorIsolation::ActorInstance: {
// This should always be a capture. That's not expressed super-cleanly
// in ActorIsolation, unfortunately.
assert(isolation.getActorInstanceParameter() == 0);
auto capture = isolation.getActorInstance();
assert(capture);
return emitLoadOfCaptureIsolation(*this, loc, capture, constant, captures);
}
}

llvm_unreachable("bad kind");
}

Expand Down
7 changes: 3 additions & 4 deletions lib/SILGen/SILGenEpilog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,7 @@ using namespace Lowering;

void SILGenFunction::prepareEpilog(
DeclContext *DC, std::optional<Type> directResultType,
std::optional<Type> errorType, CleanupLocation CleanupL,
std::optional<AbstractionPattern> origClosureType) {
std::optional<Type> errorType, CleanupLocation CleanupL) {
auto *epilogBB = createBasicBlock();

// If we have any direct results, receive them via BB arguments.
Expand Down Expand Up @@ -66,8 +65,8 @@ void SILGenFunction::prepareEpilog(

if (errorType) {
auto genericSig = DC->getGenericSignatureOfContext();
AbstractionPattern origErrorType = origClosureType
? *origClosureType->getFunctionThrownErrorType()
AbstractionPattern origErrorType = TypeContext
? *TypeContext->OrigType.getFunctionThrownErrorType()
: AbstractionPattern(genericSig.getCanonicalSignature(),
(*errorType)->getCanonicalType());

Expand Down
Loading