Skip to content

Commit 956243c

Browse files
committed
Teach #isolation to respect the flow-sensitive nature of actor initializers
Actor initializers have a flow-sensitive property where they are isolated to the actor being initialized only after the actor instance itself is fully-initialized. However, this behavior was not being reflected in the expansion of `#isolation`, which was always expanding to `self`, even before `self` is fully formed. This led to a source compatibility issue with code that used the async for..in loop within an actor initializer *prior* to the point where the actor was fully initialized, because the type checker is introducing the `#isolation` (SE-0421) but Definite Initialization properly rejects the use of `self` before it is initialized. Address this issue by delaying the expansion of `#isolation` until after the actor is fully initialized. In SILGen, we introduce a new builtin for this case (and *just* this case) called `flowSensitiveSelfIsolation`, which takes in `self` as its argument and produces an `(any Actor)?`. Definite initialization does not treat this as a use of `self`. Rather, it tracks these builtins and replaces them either with `self` (if it is fully-initialized at this point) or `nil` (if it is not fully-initialized at this point), mirroring the flow-sensitive isolation semantics described in SE-0327. Fixes rdar://127080037.
1 parent 1aaba1f commit 956243c

File tree

11 files changed

+181
-1
lines changed

11 files changed

+181
-1
lines changed

include/swift/AST/Builtins.def

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -899,6 +899,14 @@ BUILTIN_MISC_OPERATION(StartAsyncLetWithLocalBuffer, "startAsyncLetWithLocalBuff
899899
/// This is only supported under the task-to-thread concurrency model.
900900
BUILTIN_MISC_OPERATION(TaskRunInline, "taskRunInline", "", Special)
901901

902+
/// flowSensitiveSelfIsolation<T: Actor>(_ actor: T) -> T?
903+
///
904+
/// Used only in actor initializers, this builtin lowers to either 'actor'
905+
/// (wrapped in an optional) or 'nil' depending on whether 'self' has been
906+
/// initialized at this point. 'actor' is always an alias for the 'self'
907+
/// being initialized.
908+
BUILTIN_MISC_OPERATION(FlowSensitiveSelfIsolation, "flowSensitiveSelfIsolation", "", Special)
909+
902910
/// endAsyncLet(): (Builtin.RawPointer) -> Void
903911
///
904912
/// DEPRECATED. The swift_asyncLet_finish intrinsic and endAsyncLetLifetime

lib/AST/Builtins.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2089,6 +2089,16 @@ static ValueDecl *getHopToActor(ASTContext &ctx, Identifier id) {
20892089
return builder.build(id);
20902090
}
20912091

2092+
static ValueDecl *getFlowSensitiveSelfIsolation(ASTContext &ctx, Identifier id) {
2093+
BuiltinFunctionBuilder builder(ctx);
2094+
return getBuiltinFunction(ctx, id, _thin,
2095+
_generics(_unrestricted,
2096+
_conformsToDefaults(0),
2097+
_conformsTo(_typeparam(0), _actor)),
2098+
_parameters(_typeparam(0)),
2099+
_optional(_existential(_actor)));
2100+
}
2101+
20922102
static ValueDecl *getDistributedActorAsAnyActor(ASTContext &ctx, Identifier id) {
20932103
BuiltinFunctionBuilder builder(ctx);
20942104
auto *distributedActorProto = ctx.getProtocol(KnownProtocolKind::DistributedActor);
@@ -3191,6 +3201,9 @@ ValueDecl *swift::getBuiltinValueDecl(ASTContext &Context, Identifier Id) {
31913201
case BuiltinValueKind::HopToActor:
31923202
return getHopToActor(Context, Id);
31933203

3204+
case BuiltinValueKind::FlowSensitiveSelfIsolation:
3205+
return getFlowSensitiveSelfIsolation(Context, Id);
3206+
31943207
case BuiltinValueKind::AutoDiffCreateLinearMapContextWithType:
31953208
return getAutoDiffCreateLinearMapContext(Context, Id);
31963209

lib/SIL/IR/OperandOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -906,6 +906,7 @@ BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, EndAsyncLetLifetime)
906906
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, CreateTaskGroup)
907907
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, CreateTaskGroupWithFlags)
908908
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, DestroyTaskGroup)
909+
BUILTIN_OPERAND_OWNERSHIP(InstantaneousUse, FlowSensitiveSelfIsolation)
909910

910911
BUILTIN_OPERAND_OWNERSHIP(ForwardingConsume, COWBufferForReading)
911912

lib/SIL/IR/ValueOwnership.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,7 @@ CONSTANT_OWNERSHIP_BUILTIN(None, CreateTaskGroup)
630630
CONSTANT_OWNERSHIP_BUILTIN(None, CreateTaskGroupWithFlags)
631631
CONSTANT_OWNERSHIP_BUILTIN(None, DestroyTaskGroup)
632632
CONSTANT_OWNERSHIP_BUILTIN(None, TaskRunInline)
633+
CONSTANT_OWNERSHIP_BUILTIN(Owned, FlowSensitiveSelfIsolation)
633634
CONSTANT_OWNERSHIP_BUILTIN(None, GetEnumTag)
634635
CONSTANT_OWNERSHIP_BUILTIN(None, InjectEnumTag)
635636
CONSTANT_OWNERSHIP_BUILTIN(Owned, DistributedActorAsAnyActor)

lib/SILGen/SILGenExpr.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6743,6 +6743,38 @@ RValue RValueEmitter::visitMacroExpansionExpr(MacroExpansionExpr *E,
67436743

67446744
RValue RValueEmitter::visitCurrentContextIsolationExpr(
67456745
CurrentContextIsolationExpr *E, SGFContext C) {
6746+
// If we are in an actor initializer that is isolated to, the current context
6747+
// isolation flow-sensitive: before 'self' has been initialized, it will be
6748+
// nil. After 'self' has been initialized, it will be 'self'. Introduce a
6749+
// custom builtin that Definite Initialization will rewrite appropriately.
6750+
if (auto ctor = dyn_cast_or_null<ConstructorDecl>(
6751+
SGF.F.getDeclRef().getDecl())) {
6752+
auto isolation = getActorIsolation(ctor);
6753+
if (ctor->isDesignatedInit() &&
6754+
isolation == ActorIsolation::ActorInstance &&
6755+
isolation.getActorInstance() == ctor->getImplicitSelfDecl()) {
6756+
ASTContext &ctx = SGF.getASTContext();
6757+
auto builtinName = ctx.getIdentifier(
6758+
getBuiltinName(BuiltinValueKind::FlowSensitiveSelfIsolation));
6759+
SILType resultTy = SGF.getLoweredType(E->getType());
6760+
6761+
auto injection = cast<InjectIntoOptionalExpr>(E->getActor());
6762+
auto erasure = cast<ErasureExpr>(injection->getSubExpr());
6763+
auto conformance = erasure->getConformances()[0];
6764+
SGF.SGM.useConformance(conformance);
6765+
6766+
auto origActorExpr = erasure->getSubExpr();
6767+
auto actorProto = ctx.getProtocol(KnownProtocolKind::Actor);
6768+
SubstitutionMap subs = SubstitutionMap::getProtocolSubstitutions(
6769+
actorProto, origActorExpr->getType(), conformance);
6770+
auto origActor = SGF.maybeEmitValueOfLocalVarDecl(
6771+
ctor->getImplicitSelfDecl(), AccessKind::Read).getValue();
6772+
auto call = SGF.B.createBuiltin(E, builtinName, resultTy, subs, origActor);
6773+
return RValue(SGF, E,
6774+
ManagedValue::forForwardedRValue(SGF, call));
6775+
}
6776+
}
6777+
67466778
return visit(E->getActor(), C);
67476779
}
67486780

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1709,6 +1709,17 @@ void ElementUseCollector::collectClassSelfUses(
17091709
Kind = DIUseKind::Escape;
17101710
}
17111711

1712+
// Track flow-sensitive 'self' isolation builtins separately, because they
1713+
// aren't really uses of 'self' until after DI, once we've decided whether
1714+
// they have a fully-formed 'self' to use.
1715+
if (auto builtin = dyn_cast<BuiltinInst>(User)) {
1716+
if (auto builtinKind = builtin->getBuiltinKind()) {
1717+
if (*builtinKind == BuiltinValueKind::FlowSensitiveSelfIsolation) {
1718+
Kind = DIUseKind::FlowSensitiveSelfIsolation;
1719+
}
1720+
}
1721+
}
1722+
17121723
trackUse(DIMemoryUse(User, Kind, 0, TheMemory.getNumElements()));
17131724
}
17141725
}

lib/SILOptimizer/Mandatory/DIMemoryUseCollector.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,12 @@ enum DIUseKind {
298298
LoadForTypeOfSelf,
299299

300300
/// This instruction is a value_metatype on the address of 'self'.
301-
TypeOfSelf
301+
TypeOfSelf,
302+
303+
/// This instruction is the builtin for flow-sensitive current isolation
304+
/// within an actor initializer. It will be replaced with either a copy of
305+
/// its argument (injected into an (any Actor)?) or nil.
306+
FlowSensitiveSelfIsolation,
302307
};
303308

304309
/// This struct represents a single classified access to the memory object

lib/SILOptimizer/Mandatory/DefiniteInitialization.cpp

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,6 +491,7 @@ namespace {
491491
void handleTypeOfSelfUse(DIMemoryUse &Use);
492492
void handleInOutUse(const DIMemoryUse &Use);
493493
void handleEscapeUse(const DIMemoryUse &Use);
494+
void handleFlowSensitiveActorIsolationUse(const DIMemoryUse &Use);
494495

495496
bool diagnoseReturnWithoutInitializingStoredProperties(
496497
const SILInstruction *Inst, SILLocation loc, const DIMemoryUse &Use);
@@ -565,6 +566,7 @@ LifetimeChecker::LifetimeChecker(const DIMemoryObjectInfo &TheMemory,
565566
case DIUseKind::LoadForTypeOfSelf:
566567
case DIUseKind::TypeOfSelf:
567568
case DIUseKind::Escape:
569+
case DIUseKind::FlowSensitiveSelfIsolation:
568570
continue;
569571
case DIUseKind::Assign:
570572
case DIUseKind::Set:
@@ -1160,6 +1162,10 @@ void LifetimeChecker::doIt() {
11601162
case DIUseKind::BadExplicitStore:
11611163
diagnoseBadExplicitStore(Inst);
11621164
break;
1165+
1166+
case DIUseKind::FlowSensitiveSelfIsolation:
1167+
handleFlowSensitiveActorIsolationUse(Use);
1168+
break;
11631169
}
11641170
}
11651171

@@ -1344,6 +1350,47 @@ void LifetimeChecker::handleTypeOfSelfUse(DIMemoryUse &Use) {
13441350
}
13451351
}
13461352

1353+
void LifetimeChecker::handleFlowSensitiveActorIsolationUse(
1354+
const DIMemoryUse &Use) {
1355+
bool IsSuperInitComplete, FailedSelfUse;
1356+
1357+
ASTContext &ctx = F.getASTContext();
1358+
auto builtinInst = cast<BuiltinInst>(Use.Inst);
1359+
SILBuilderWithScope B(builtinInst);
1360+
SILValue replacement;
1361+
SILType optExistentialType = builtinInst->getType();
1362+
SILLocation loc = builtinInst->getLoc();
1363+
if (isInitializedAtUse(Use, &IsSuperInitComplete, &FailedSelfUse)) {
1364+
// 'self' is initialized, so replace this builtin with an injection of the
1365+
// argument into (any Actor)?.
1366+
1367+
// Create a copy of the actor argument, which we intentionally did not
1368+
// copy in SILGen.
1369+
SILValue actor = B.createCopyValue(loc, builtinInst->getArguments()[0]);
1370+
1371+
// Inject 'self' into 'any Actor'.
1372+
ProtocolConformanceRef conformances[1] = {
1373+
builtinInst->getSubstitutions().getConformances()[0]
1374+
};
1375+
SILType existentialType = optExistentialType.getOptionalObjectType();
1376+
SILValue existentialBox = B.createInitExistentialRef(
1377+
loc, existentialType, actor->getType().getASTType(), actor,
1378+
ctx.AllocateCopy(conformances));
1379+
1380+
// Then, wrap it in an optional.
1381+
replacement = B.createEnum(
1382+
loc, existentialBox, ctx.getOptionalSomeDecl(), optExistentialType);
1383+
} else {
1384+
// 'self' is not initialized yet, so use 'nil'.
1385+
replacement = B.createEnum(
1386+
loc, SILValue(), ctx.getOptionalNoneDecl(), optExistentialType);
1387+
}
1388+
1389+
// Introduce the replacement.
1390+
InstModCallbacks callbacks;
1391+
replaceAllUsesAndErase(builtinInst, replacement, callbacks);
1392+
}
1393+
13471394
void LifetimeChecker::emitSelfConsumedDiagnostic(SILInstruction *Inst) {
13481395
if (!shouldEmitError(Inst))
13491396
return;

lib/SILOptimizer/Transforms/AccessEnforcementReleaseSinking.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,7 @@ static bool isBarrier(SILInstruction *inst) {
159159
case BuiltinValueKind::GetEnumTag:
160160
case BuiltinValueKind::InjectEnumTag:
161161
case BuiltinValueKind::ExtractFunctionIsolation:
162+
case BuiltinValueKind::FlowSensitiveSelfIsolation:
162163
case BuiltinValueKind::AddressOfRawLayout:
163164
return false;
164165

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 | %FileCheck %s
2+
// REQUIRES: concurrency
3+
4+
@available(SwiftStdlib 5.1, *)
5+
func f(isolatedTo actor: isolated (any Actor)?) async -> Int { 0 }
6+
7+
@available(SwiftStdlib 5.1, *)
8+
actor A {
9+
let number: Int
10+
11+
// CHECK-LABEL: sil hidden [available 10.15] [ossa] @$s4test1ACACyYacfc : $@convention(method) @async (@sil_isolated @owned A) -> @owned A
12+
init() async {
13+
// First use of #isolation.
14+
// CHECK: [[ISOLATION_1:%.*]] = builtin "flowSensitiveSelfIsolation"<A>
15+
// CHECK: [[F_1:%.*]] = function_ref @$s4test1f10isolatedToSiScA_pSgYi_tYaF
16+
// CHECK-NEXT: [[F_RESULT:%.*]] = apply [[F_1]]([[ISOLATION_1]])
17+
18+
// Assignment to "number" of the result.
19+
// CHECK: [[NUMBER:%.*]] = ref_element_addr {{%.*}} : $A, #A.number
20+
// CHECK: assign [[F_RESULT]] to [[NUMBER]]
21+
self.number = await f(isolatedTo: #isolation)
22+
23+
// Second use of #isolation.
24+
// CHECK: [[ISOLATION_2:%.*]] = builtin "flowSensitiveSelfIsolation"<A>
25+
// CHECK: [[F_2:%.*]] = function_ref @$s4test1f10isolatedToSiScA_pSgYi_tYaF
26+
// CHECK-NEXT: apply [[F_2]]([[ISOLATION_2]])
27+
_ = await f(isolatedTo: #isolation)
28+
}
29+
}

0 commit comments

Comments
 (0)