Skip to content

Commit 06dec05

Browse files
committed
Fix a bunch of bugs with the isolation of local funcs. Since we
use local funcs to implement `defer`, this also fixes several bugs with that feature, such as it breaking in nonisolated functions when a default isolation is in effect in the source file. Change how we compute isolation of local funcs. The rule here is supposed to be that non-`@Sendable` local funcs are isolated the same as their enclosing context. Unlike closure expressions, this is unconditional: in instance-isolated functions, the isolation does not depend on whether `self` is captured. But the computation was wrong: it didn't translate global actor isolation between contexts, it didn't turn parameter isolation into capture isolation, and it fell through for several other kinds of parent isolation, causing the compiler to try to apply default isolation instead. I've extracted the logic from the closure expression path into a common function and used it for both paths. The capture computation logic was forcing a capture of the enclosing isolation in local funcs, but only for async functions. Presumably this was conditional because async functions need the isolation for actor hops, but sync functions don't really need it. However, this was causing crashes with `-enable-actor-data-race-checks`. (I didn't investigate whether it also failed with the similar assertion we do with preconcurrency.) For now, I've switched this to capture the isolated instance unconditionally. If we need to be more conservative by either only capturing when data-race checks are enabled or disabling the checks when the isolation isn't captured, we can look into that. Fix a bug in capture isolation checking. We were ignoring captures of nonisolated declarations in order to implement the rule that permits `nonisolated(unsafe)` variables to be captured in non-sendable closures. This check needs to only apply to variables! The isolation of a local func has nothing to do with its sendability as a capture. That fix exposed a problem where we were being unnecessarily restrictive with generic local func declarations because we didn't consider them to have sendable type. This was true even if the genericity was purely from being declared in a generic context, but it doesn't matter, they ought to be sendable regardless. Finally, fix a handful of bugs where global actor types were not remapped properly in SILGen.
1 parent 00463df commit 06dec05

File tree

10 files changed

+217
-114
lines changed

10 files changed

+217
-114
lines changed

include/swift/AST/ActorIsolation.h

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,8 @@ class ActorIsolation {
154154
explicit ActorIsolation(Kind kind = Unspecified, bool isSILParsed = false)
155155
: pointer(nullptr), kind(kind), isolatedByPreconcurrency(false),
156156
silParsed(isSILParsed), encodedParameterIndex(0) {
157-
assert(kind != ActorInstance);
157+
// SIL's use of this has weaker invariants for now.
158+
assert(kind != ActorInstance || isSILParsed);
158159
}
159160

160161
static ActorIsolation forUnspecified() {
@@ -209,21 +210,14 @@ class ActorIsolation {
209210
static std::optional<ActorIsolation> forSILString(StringRef string) {
210211
auto kind =
211212
llvm::StringSwitch<std::optional<ActorIsolation::Kind>>(string)
212-
.Case("unspecified",
213-
std::optional<ActorIsolation>(ActorIsolation::Unspecified))
214-
.Case("actor_instance",
215-
std::optional<ActorIsolation>(ActorIsolation::ActorInstance))
216-
.Case("nonisolated",
217-
std::optional<ActorIsolation>(ActorIsolation::Nonisolated))
218-
.Case("nonisolated_unsafe", std::optional<ActorIsolation>(
219-
ActorIsolation::NonisolatedUnsafe))
220-
.Case("global_actor",
221-
std::optional<ActorIsolation>(ActorIsolation::GlobalActor))
222-
.Case("global_actor_unsafe",
223-
std::optional<ActorIsolation>(ActorIsolation::GlobalActor))
213+
.Case("unspecified", ActorIsolation::Unspecified)
214+
.Case("actor_instance", ActorIsolation::ActorInstance)
215+
.Case("nonisolated", ActorIsolation::Nonisolated)
216+
.Case("nonisolated_unsafe", ActorIsolation::NonisolatedUnsafe)
217+
.Case("global_actor", ActorIsolation::GlobalActor)
218+
.Case("global_actor_unsafe", ActorIsolation::GlobalActor)
224219
.Case("caller_isolation_inheriting",
225-
std::optional<ActorIsolation>(
226-
ActorIsolation::CallerIsolationInheriting))
220+
ActorIsolation::CallerIsolationInheriting)
227221
.Default(std::nullopt);
228222
if (kind == std::nullopt)
229223
return std::nullopt;

lib/AST/ActorIsolation.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ bool ActorIsolation::isEqual(const ActorIsolation &lhs,
191191
auto *lhsActor = lhs.getActorInstance();
192192
auto *rhsActor = rhs.getActorInstance();
193193
if (lhsActor && rhsActor) {
194+
if (lhsActor == rhsActor)
195+
return true;
196+
194197
// FIXME: This won't work for arbitrary isolated parameter captures.
195198
if ((lhsActor->isSelfParameter() && rhsActor->isSelfParamCapture()) ||
196199
(lhsActor->isSelfParamCapture() && rhsActor->isSelfParameter())) {

lib/AST/ConformanceLookup.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,13 @@ static ProtocolConformanceRef getBuiltinTupleTypeConformance(
269269
return ProtocolConformanceRef::forMissingOrInvalid(type, protocol);
270270
}
271271

272+
// We can end up checking builtin conformances for generic function types
273+
// when e.g. we're checking whether a captured local func declaration is
274+
// sendable. That's fine, we can answer that question in the abstract
275+
// without needing to generally support conformances on generic function
276+
// types.
272277
using EitherFunctionType =
273-
llvm::PointerUnion<const SILFunctionType *, const FunctionType *>;
278+
llvm::PointerUnion<const SILFunctionType *, const AnyFunctionType *>;
274279

275280
/// Whether the given function type conforms to Sendable.
276281
static bool isSendableFunctionType(EitherFunctionType eitherFnTy) {
@@ -288,7 +293,7 @@ static bool isSendableFunctionType(EitherFunctionType eitherFnTy) {
288293
representation = *converted;
289294

290295
} else {
291-
auto functionType = eitherFnTy.get<const FunctionType *>();
296+
auto functionType = eitherFnTy.get<const AnyFunctionType *>();
292297

293298
if (functionType->isSendable())
294299
return true;
@@ -332,7 +337,7 @@ static bool isBitwiseCopyableFunctionType(EitherFunctionType eitherFnTy) {
332337
if (auto silFnTy = eitherFnTy.dyn_cast<const SILFunctionType *>()) {
333338
representation = silFnTy->getRepresentation();
334339
} else {
335-
auto fnTy = eitherFnTy.get<const FunctionType *>();
340+
auto fnTy = eitherFnTy.get<const AnyFunctionType *>();
336341
representation = convertRepresentation(fnTy->getRepresentation());
337342
}
338343

@@ -621,7 +626,7 @@ LookupConformanceInModuleRequest::evaluate(
621626
}
622627

623628
// Function types can conform to protocols.
624-
if (auto functionType = type->getAs<FunctionType>()) {
629+
if (auto functionType = type->getAs<AnyFunctionType>()) {
625630
return getBuiltinFunctionTypeConformance(type, functionType, protocol);
626631
}
627632

lib/SILGen/SILGenConcurrency.cpp

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,8 @@ void SILGenFunction::emitExpectedExecutorProlog() {
195195

196196
case ActorIsolation::GlobalActor:
197197
if (F.isAsync() || wantDataRaceChecks) {
198-
setExpectedExecutorForGlobalActor(*this, actorIsolation.getGlobalActor());
198+
auto globalActorType = F.mapTypeIntoContext(actorIsolation.getGlobalActor());
199+
setExpectedExecutorForGlobalActor(*this, globalActorType);
199200
}
200201
break;
201202
}
@@ -226,7 +227,8 @@ void SILGenFunction::emitExpectedExecutorProlog() {
226227

227228
case ActorIsolation::GlobalActor:
228229
if (wantExecutor) {
229-
setExpectedExecutorForGlobalActor(*this, actorIsolation.getGlobalActor());
230+
auto globalActorType = F.mapTypeIntoContext(actorIsolation.getGlobalActor());
231+
setExpectedExecutorForGlobalActor(*this, globalActorType);
230232
break;
231233
}
232234
}
@@ -573,9 +575,10 @@ SILGenFunction::emitFunctionTypeIsolation(SILLocation loc,
573575

574576
// Emit global actor isolation by loading .shared from the global actor,
575577
// erasing it into `any Actor`, and injecting that into Optional.
576-
case FunctionTypeIsolation::Kind::GlobalActor:
578+
case FunctionTypeIsolation::Kind::GlobalActor: {
577579
return emitGlobalActorIsolation(loc,
578580
isolation.getGlobalActorType()->getCanonicalType());
581+
}
579582

580583
// Emit @isolated(any) isolation by loading the actor reference from the
581584
// function.
@@ -646,9 +649,11 @@ SILGenFunction::emitClosureIsolation(SILLocation loc, SILDeclRef constant,
646649
case ActorIsolation::Erased:
647650
llvm_unreachable("closures cannot directly have erased isolation");
648651

649-
case ActorIsolation::GlobalActor:
650-
return emitGlobalActorIsolation(loc,
651-
isolation.getGlobalActor()->getCanonicalType());
652+
case ActorIsolation::GlobalActor: {
653+
auto globalActorType = F.mapTypeIntoContext(isolation.getGlobalActor())
654+
->getCanonicalType();
655+
return emitGlobalActorIsolation(loc, globalActorType);
656+
}
652657

653658
case ActorIsolation::ActorInstance: {
654659
assert(isolation.isActorInstanceForCapture());
@@ -703,8 +708,10 @@ SILGenFunction::emitExecutor(SILLocation loc, ActorIsolation isolation,
703708
return emitLoadActorExecutor(loc, self);
704709
}
705710

706-
case ActorIsolation::GlobalActor:
707-
return emitLoadGlobalActorExecutor(isolation.getGlobalActor());
711+
case ActorIsolation::GlobalActor: {
712+
auto globalActorType = F.mapTypeIntoContext(isolation.getGlobalActor());
713+
return emitLoadGlobalActorExecutor(globalActorType);
714+
}
708715
}
709716
llvm_unreachable("covered switch");
710717
}

lib/Sema/TypeCheckCaptures.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -762,7 +762,7 @@ CaptureInfo CaptureInfoRequest::evaluate(Evaluator &evaluator,
762762
finder.checkType(type, AFD->getLoc());
763763
}
764764

765-
if (AFD->isLocalCapture() && AFD->hasAsync()) {
765+
if (AFD->isLocalCapture()) {
766766
// If a local function inherits isolation from the enclosing context,
767767
// make sure we capture the isolated parameter, if we haven't already.
768768
auto actorIsolation = getActorIsolation(AFD);

lib/Sema/TypeCheckConcurrency.cpp

Lines changed: 91 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -3045,13 +3045,13 @@ namespace {
30453045
// If the closure won't execute concurrently with the context in
30463046
// which the declaration occurred, it's okay.
30473047
auto decl = capture.getDecl();
3048-
auto isolation = getActorIsolation(decl);
30493048

30503049
// 'nonisolated' local variables are always okay to capture in
30513050
// 'Sendable' closures because they can be accessed from anywhere.
30523051
// Note that only 'nonisolated(unsafe)' can be applied to local
30533052
// variables.
3054-
if (isolation.isNonisolated())
3053+
if (isa<VarDecl>(decl) &&
3054+
getActorIsolation(decl).isNonisolated())
30553055
continue;
30563056

30573057
auto *context = localFunc.getAsDeclContext();
@@ -4833,6 +4833,70 @@ namespace {
48334833
};
48344834
} // end anonymous namespace
48354835

4836+
/// Compute the isolation of a closure or local function from its parent
4837+
/// isolation.
4838+
///
4839+
/// Doesn't apply preconcurrency because it's generally easier for the
4840+
/// caller to do so.
4841+
static ActorIsolation
4842+
computeClosureIsolationFromParent(DeclContext *closure,
4843+
ActorIsolation parentIsolation,
4844+
bool checkIsolatedCapture) {
4845+
// We must have parent isolation determined to get here.
4846+
switch (parentIsolation) {
4847+
case ActorIsolation::CallerIsolationInheriting:
4848+
case ActorIsolation::Nonisolated:
4849+
case ActorIsolation::NonisolatedUnsafe:
4850+
case ActorIsolation::Unspecified:
4851+
return ActorIsolation::forNonisolated(
4852+
parentIsolation == ActorIsolation::NonisolatedUnsafe);
4853+
4854+
case ActorIsolation::Erased:
4855+
llvm_unreachable("context cannot have erased isolation");
4856+
4857+
case ActorIsolation::GlobalActor:
4858+
// This should already be an interface type, so we don't need to remap
4859+
// it between the contexts.
4860+
return ActorIsolation::forGlobalActor(parentIsolation.getGlobalActor());
4861+
4862+
case ActorIsolation::ActorInstance: {
4863+
// In non-@Sendable local functions, we always inherit the enclosing
4864+
// isolation, forcing a capture of it if necessary.
4865+
if (isa<FuncDecl>(closure)) {
4866+
// We should always have a VarDecl in this case, where we got the
4867+
// ActorIsolation from a context; the non-VarDecl cases are only used
4868+
// locally within isolation checking.
4869+
auto actor = parentIsolation.getActorInstance();
4870+
assert(actor);
4871+
return ActorIsolation::forActorInstanceCapture(actor);
4872+
}
4873+
4874+
if (checkIsolatedCapture) {
4875+
auto closureAsFn = AnyFunctionRef::fromFunctionDeclContext(closure);
4876+
if (auto param = closureAsFn.getCaptureInfo().getIsolatedParamCapture())
4877+
return ActorIsolation::forActorInstanceCapture(param);
4878+
4879+
auto *explicitClosure = dyn_cast<ClosureExpr>(closure);
4880+
// @_inheritActorContext(always) forces the isolation capture.
4881+
if (explicitClosure && explicitClosure->alwaysInheritsActorContext()) {
4882+
if (parentIsolation.isActorInstanceIsolated()) {
4883+
if (auto *param = parentIsolation.getActorInstance())
4884+
return ActorIsolation::forActorInstanceCapture(param);
4885+
}
4886+
return parentIsolation;
4887+
}
4888+
} else {
4889+
// If we don't have capture information during code completion, assume
4890+
// that the closure captures the `isolated` parameter from the parent
4891+
// context.
4892+
return parentIsolation;
4893+
}
4894+
4895+
return ActorIsolation::forNonisolated(/*unsafe=*/false);
4896+
}
4897+
}
4898+
}
4899+
48364900
ActorIsolation ActorIsolationChecker::determineClosureIsolation(
48374901
AbstractClosureExpr *closure) const {
48384902
bool preconcurrency = false;
@@ -4889,48 +4953,8 @@ ActorIsolation ActorIsolationChecker::determineClosureIsolation(
48894953
closure->getParent(), getClosureActorIsolation);
48904954
preconcurrency |= parentIsolation.preconcurrency();
48914955

4892-
// We must have parent isolation determined to get here.
4893-
switch (parentIsolation) {
4894-
case ActorIsolation::CallerIsolationInheriting:
4895-
case ActorIsolation::Nonisolated:
4896-
case ActorIsolation::NonisolatedUnsafe:
4897-
case ActorIsolation::Unspecified:
4898-
return ActorIsolation::forNonisolated(
4899-
parentIsolation == ActorIsolation::NonisolatedUnsafe);
4900-
4901-
case ActorIsolation::Erased:
4902-
llvm_unreachable("context cannot have erased isolation");
4903-
4904-
case ActorIsolation::GlobalActor: {
4905-
Type globalActor = closure->mapTypeIntoContext(
4906-
parentIsolation.getGlobalActor()->mapTypeOutOfContext());
4907-
return ActorIsolation::forGlobalActor(globalActor);
4908-
}
4909-
4910-
case ActorIsolation::ActorInstance: {
4911-
if (checkIsolatedCapture) {
4912-
if (auto param = closure->getCaptureInfo().getIsolatedParamCapture())
4913-
return ActorIsolation::forActorInstanceCapture(param);
4914-
4915-
auto *explicitClosure = dyn_cast<ClosureExpr>(closure);
4916-
// @_inheritActorContext(always) forces the isolation capture.
4917-
if (explicitClosure && explicitClosure->alwaysInheritsActorContext()) {
4918-
if (parentIsolation.isActorInstanceIsolated()) {
4919-
if (auto *param = parentIsolation.getActorInstance())
4920-
return ActorIsolation::forActorInstanceCapture(param);
4921-
}
4922-
return parentIsolation;
4923-
}
4924-
} else {
4925-
// If we don't have capture information during code completion, assume
4926-
// that the closure captures the `isolated` parameter from the parent
4927-
// context.
4928-
return parentIsolation;
4929-
}
4930-
4931-
return ActorIsolation::forNonisolated(/*unsafe=*/false);
4932-
}
4933-
}
4956+
return computeClosureIsolationFromParent(closure, parentIsolation,
4957+
checkIsolatedCapture);
49344958
}();
49354959

49364960
// Apply computed preconcurrency.
@@ -5127,7 +5151,8 @@ getIsolationFromAttributes(const Decl *decl, bool shouldDiagnose = true,
51275151
std::optional<ActorIsolation> result;
51285152
if (selfTypeDecl) {
51295153
if (selfTypeDecl->isAnyActor()) {
5130-
result = ActorIsolation::forActorInstanceSelf(selfTypeDecl);
5154+
result = ActorIsolation::forActorInstanceSelf(
5155+
const_cast<AbstractFunctionDecl*>(cast<AbstractFunctionDecl>(decl)));
51315156
} else {
51325157
// If the declaration is in an extension that has one of the isolation
51335158
// attributes, use that.
@@ -6316,50 +6341,37 @@ static InferredActorIsolation computeActorIsolation(Evaluator &evaluator,
63166341
return inferred;
63176342
};
63186343

6344+
// If this is an accessor, use the actor isolation of its storage
6345+
// declaration. All of the logic for FuncDecls below only applies to
6346+
// non-accessor functions.
6347+
if (auto accessor = dyn_cast<AccessorDecl>(value)) {
6348+
return getInferredActorIsolation(accessor->getStorage());
6349+
}
6350+
63196351
// If this is a local function, inherit the actor isolation from its
63206352
// context if it global or was captured.
63216353
if (auto func = dyn_cast<FuncDecl>(value)) {
6322-
if (func->isLocalCapture() && !func->isSendable()) {
6323-
auto *dc = func->getDeclContext();
6354+
auto *dc = func->getDeclContext();
6355+
if (dc->isLocalContext() && !func->isSendable()) {
63246356
llvm::PointerUnion<Decl *, AbstractClosureExpr *> inferenceSource;
63256357
if (auto *closure = dyn_cast<AbstractClosureExpr>(dc)) {
63266358
inferenceSource = closure;
63276359
} else {
63286360
inferenceSource = dc->getAsDecl();
63296361
}
63306362

6331-
switch (auto enclosingIsolation = getActorIsolationOfContext(dc)) {
6332-
case ActorIsolation::Nonisolated:
6333-
case ActorIsolation::CallerIsolationInheriting:
6334-
case ActorIsolation::NonisolatedUnsafe:
6335-
case ActorIsolation::Unspecified:
6336-
// Do nothing.
6337-
break;
6338-
6339-
case ActorIsolation::Erased:
6340-
llvm_unreachable("context cannot have erased isolation");
6341-
6342-
case ActorIsolation::ActorInstance:
6343-
return {
6344-
inferredIsolation(enclosingIsolation),
6345-
IsolationSource(inferenceSource, IsolationSource::LexicalContext)
6346-
};
6347-
6348-
case ActorIsolation::GlobalActor:
6349-
return {
6350-
inferredIsolation(enclosingIsolation),
6351-
IsolationSource(inferenceSource, IsolationSource::LexicalContext)
6352-
};
6353-
}
6363+
auto enclosingIsolation = getActorIsolationOfContext(dc);
6364+
auto isolation =
6365+
computeClosureIsolationFromParent(func, enclosingIsolation,
6366+
/*checkIsolatedCapture*/true)
6367+
.withPreconcurrency(enclosingIsolation.preconcurrency());
6368+
return {
6369+
inferredIsolation(isolation),
6370+
IsolationSource(inferenceSource, IsolationSource::LexicalContext)
6371+
};
63546372
}
63556373
}
63566374

6357-
// If this is an accessor, use the actor isolation of its storage
6358-
// declaration.
6359-
if (auto accessor = dyn_cast<AccessorDecl>(value)) {
6360-
return getInferredActorIsolation(accessor->getStorage());
6361-
}
6362-
63636375
if (auto var = dyn_cast<VarDecl>(value)) {
63646376
auto &ctx = var->getASTContext();
63656377
if (!ctx.LangOpts.isConcurrencyModelTaskToThread() &&
@@ -6601,7 +6613,8 @@ bool HasIsolatedSelfRequest::evaluate(
66016613
}
66026614
}
66036615
if (attrIsolation) {
6604-
return attrIsolation == ActorIsolation::forActorInstanceSelf(selfTypeDecl);
6616+
return attrIsolation->getKind() == ActorIsolation::ActorInstance &&
6617+
attrIsolation->isActorInstanceForSelfParameter();
66056618
}
66066619

66076620
// If this is a variable, check for a property wrapper that alters its
@@ -7861,7 +7874,7 @@ ActorIsolation swift::getActorIsolationForReference(ValueDecl *decl,
78617874
// as needing to enter the actor.
78627875
if (auto nominal = ctor->getDeclContext()->getSelfNominalTypeDecl()) {
78637876
if (nominal->isAnyActor())
7864-
return ActorIsolation::forActorInstanceSelf(decl);
7877+
return ActorIsolation::forActorInstanceSelf(ctor);
78657878
}
78667879

78677880
// Fall through to treat initializers like any other declaration.

0 commit comments

Comments
 (0)