Skip to content

Commit 0da2238

Browse files
committed
[silgen] Make sure that thunks that convert to/from nonisolated(nonsending) handle hopping correctly.
Specifically: 1. When we convert a function to nonisolated(nonsending), we need to make sure that in the thunk we hop upon return since nonisolated(nonsending) functions are assumed to preserve the caller's isolation. 2. When we convert a function from nonisolated(nonsending), we need to make sure that in the thunk we hop onto the actor that we are passing in as the isolated parameter of the nonisolated(nonsending) function. This ensures that the nonisolated(nonsending) function can assume that it is already on its isolated parameter's actor at function entry. rdar://155905383
1 parent 320f170 commit 0da2238

File tree

7 files changed

+377
-42
lines changed

7 files changed

+377
-42
lines changed

lib/SILGen/SILGenConcurrency.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,35 @@ SILGenFunction::emitHopToTargetActor(SILLocation loc,
680680
}
681681
}
682682

683+
namespace {
684+
685+
class HopToActorCleanup : public Cleanup {
686+
SILValue value;
687+
688+
public:
689+
HopToActorCleanup(SILValue value) : value(value) {}
690+
691+
void emit(SILGenFunction &SGF, CleanupLocation l,
692+
ForUnwind_t forUnwind) override {
693+
SGF.B.createHopToExecutor(l, value, false /*mandatory*/);
694+
}
695+
696+
void dump(SILGenFunction &) const override {
697+
#ifndef NDEBUG
698+
llvm::errs() << "HopToExecutorCleanup\n"
699+
<< "State:" << getState() << "\n"
700+
<< "Value:" << value << "\n";
701+
#endif
702+
}
703+
};
704+
} // end anonymous namespace
705+
706+
CleanupHandle SILGenFunction::emitScopedHopToTargetActor(SILLocation loc,
707+
SILValue actor) {
708+
Cleanups.pushCleanup<HopToActorCleanup>(actor);
709+
return Cleanups.getTopCleanup();
710+
}
711+
683712
ExecutorBreadcrumb SILGenFunction::emitHopToTargetExecutor(
684713
SILLocation loc, SILValue executor) {
685714
// Record that we need to hop back to the current executor.

lib/SILGen/SILGenFunction.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1333,6 +1333,12 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
13331333
emitHopToTargetActor(SILLocation loc, std::optional<ActorIsolation> actorIso,
13341334
std::optional<ManagedValue> actorSelf);
13351335

1336+
/// Emit a cleanup for a hop back to a target actor.
1337+
///
1338+
/// Used to ensure that along error paths and normal scope exit paths we hop
1339+
/// back automatically.
1340+
CleanupHandle emitScopedHopToTargetActor(SILLocation loc, SILValue actor);
1341+
13361342
/// Emit a hop to the target executor, returning a breadcrumb with enough
13371343
/// enough information to hop back.
13381344
///

lib/SILGen/SILGenPoly.cpp

Lines changed: 104 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5517,7 +5517,7 @@ static void buildThunkBody(SILGenFunction &SGF, SILLocation loc,
55175517
// don't need to do anything. But if the input is synchronous and the
55185518
// executor is asynchronous, we need to treat this like any other call
55195519
// to a synchronous function from an asynchronous context.
5520-
bool hopToIsolatedParameter = false;
5520+
bool hopToIsolatedParameterForSync = false;
55215521
if (outputSubstType->isAsync() && !inputSubstType->isAsync()) {
55225522
auto inputIsolation = inputSubstType->getIsolation();
55235523
switch (inputIsolation.getKind()) {
@@ -5534,7 +5534,7 @@ static void buildThunkBody(SILGenFunction &SGF, SILLocation loc,
55345534
// For a function with parameter isolation, we'll have to dig the
55355535
// argument out after translation but before making the call.
55365536
case FunctionTypeIsolation::Kind::Parameter:
5537-
hopToIsolatedParameter = true;
5537+
hopToIsolatedParameterForSync = true;
55385538
break;
55395539

55405540
// For a function with global-actor isolation, hop to the appropriate
@@ -5603,44 +5603,49 @@ static void buildThunkBody(SILGenFunction &SGF, SILLocation loc,
56035603
argValues.push_back(*innerIndirectErrorAddr);
56045604
}
56055605

5606-
// If we need to jump to an isolated parameter, do so before the call.
5607-
if (hopToIsolatedParameter) {
5606+
// If we need to jump to an isolated parameter for a synchronous function, do
5607+
// so before the call.
5608+
if (hopToIsolatedParameterForSync) {
56085609
auto formalIsolatedIndex = getIsolatedParamIndex(inputSubstType);
56095610
auto isolatedIndex = inputOrigType.getLoweredParamIndex(formalIsolatedIndex);
56105611
SGF.B.createHopToExecutor(loc, args[isolatedIndex].getValue(),
56115612
/*mandatory*/false);
56125613
}
56135614

5614-
// If the input function has caller isolation, we need to fill in that
5615-
// argument with the formal isolation of the output function.
5615+
// If the input function has caller isolation (and thus is async), we need to
5616+
// fill in that argument with the formal isolation of the output function and
5617+
// hop to it so that it is able to assume that it does not need to hop in its
5618+
// prologue.
56165619
if (options.contains(ThunkGenFlag::CalleeHasImplicitIsolatedParam)) {
5617-
auto outputIsolation = outputSubstType->getIsolation();
5618-
switch (outputIsolation.getKind()) {
5619-
case FunctionTypeIsolation::Kind::NonIsolated:
5620-
case FunctionTypeIsolation::Kind::Erased:
5621-
// Converting a caller-isolated function to @isolated(any) makes
5622-
// it @concurrent. In either case, emit a nil actor reference.
5623-
argValues.push_back(SGF.emitNonIsolatedIsolation(loc).getValue());
5624-
break;
5625-
case FunctionTypeIsolation::Kind::GlobalActor: {
5626-
auto globalActor =
5627-
outputIsolation.getGlobalActorType()->getCanonicalType();
5628-
argValues.push_back(
5629-
SGF.emitGlobalActorIsolation(loc, globalActor).getValue());
5630-
break;
5631-
}
5632-
case FunctionTypeIsolation::Kind::NonIsolatedCaller: {
5633-
argValues.push_back(implicitIsolationParam.getValue());
5634-
break;
5635-
}
5636-
case FunctionTypeIsolation::Kind::Parameter:
5637-
// This would require a conversion from a type with caller
5638-
// isolation to a type with parameter isolation, which is not
5639-
// currently allowed and probably won't ever be. Anyway, to
5640-
// implement it, we'd need to borrow the isolated parameter
5641-
// and wrap it up as an `Optional<any Actor>`.
5642-
llvm_unreachable("Should never see this");
5643-
}
5620+
auto forwardedIsolationValue = [&]() -> SILValue {
5621+
auto outputIsolation = outputSubstType->getIsolation();
5622+
switch (outputIsolation.getKind()) {
5623+
case FunctionTypeIsolation::Kind::NonIsolated:
5624+
case FunctionTypeIsolation::Kind::Erased:
5625+
// Converting a caller-isolated function to @isolated(any) makes
5626+
// it @concurrent. In either case, emit a nil actor reference.
5627+
return SGF.emitNonIsolatedIsolation(loc).getValue();
5628+
case FunctionTypeIsolation::Kind::GlobalActor: {
5629+
auto globalActor =
5630+
outputIsolation.getGlobalActorType()->getCanonicalType();
5631+
return SGF.emitGlobalActorIsolation(loc, globalActor).getValue();
5632+
}
5633+
case FunctionTypeIsolation::Kind::NonIsolatedCaller: {
5634+
return implicitIsolationParam.getValue();
5635+
}
5636+
case FunctionTypeIsolation::Kind::Parameter:
5637+
// This would require a conversion from a type with caller
5638+
// isolation to a type with parameter isolation, which is not
5639+
// currently allowed and probably won't ever be. Anyway, to
5640+
// implement it, we'd need to borrow the isolated parameter
5641+
// and wrap it up as an `Optional<any Actor>`.
5642+
llvm::report_fatal_error("Should never see this?!");
5643+
}
5644+
}();
5645+
5646+
assert(forwardedIsolationValue);
5647+
SGF.B.createHopToExecutor(loc, forwardedIsolationValue, false);
5648+
argValues.push_back(forwardedIsolationValue);
56445649
}
56455650

56465651
// Add the rest of the arguments.
@@ -5649,6 +5654,16 @@ static void buildThunkBody(SILGenFunction &SGF, SILLocation loc,
56495654
auto fun = fnType->isCalleeGuaranteed() ? fnValue.borrow(SGF, loc).getValue()
56505655
: fnValue.forward(SGF);
56515656

5657+
// If our thunk is a caller isolated function, we need to insert a hop to
5658+
// return to our isolation so that our caller can assume that we preserve
5659+
// isolation.
5660+
if (auto isolatedArg = llvm::cast_or_null<SILFunctionArgument>(
5661+
SGF.F.maybeGetIsolatedArgument());
5662+
isolatedArg && isolatedArg->getKnownParameterInfo().hasOption(
5663+
SILParameterInfo::ImplicitLeading)) {
5664+
SGF.emitScopedHopToTargetActor(loc, isolatedArg);
5665+
}
5666+
56525667
SILValue innerResult =
56535668
SGF.emitApplyWithRethrow(loc, fun,
56545669
/*substFnType*/ fnValue.getType(),
@@ -7080,6 +7095,15 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
70807095
if (options.contains(ThunkGenFlag::CalleeHasImplicitIsolatedParam)) {
70817096
auto baseIsolation =
70827097
swift::getActorIsolation(base.getAbstractFunctionDecl());
7098+
std::optional<ActorIsolation> derivedIsolationCache;
7099+
auto getDerivedIsolation = [&]() -> ActorIsolation {
7100+
if (!derivedIsolationCache) {
7101+
derivedIsolationCache =
7102+
swift::getActorIsolation(derived.getAbstractFunctionDecl());
7103+
}
7104+
return *derivedIsolationCache;
7105+
};
7106+
70837107
switch (baseIsolation) {
70847108
case ActorIsolation::Unspecified:
70857109
case ActorIsolation::Nonisolated:
@@ -7096,8 +7120,7 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
70967120
}
70977121
case ActorIsolation::ActorInstance:
70987122
case ActorIsolation::CallerIsolationInheriting: {
7099-
auto derivedIsolation =
7100-
swift::getActorIsolation(derived.getAbstractFunctionDecl());
7123+
auto derivedIsolation = getDerivedIsolation();
71017124
switch (derivedIsolation) {
71027125
case ActorIsolation::Unspecified:
71037126
case ActorIsolation::Nonisolated:
@@ -7124,6 +7147,14 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
71247147
break;
71257148
}
71267149
}
7150+
7151+
// If our derived isolation is caller isolation inheriting and our base
7152+
// isn't, we need to insert a hop so that derived can assume that it does
7153+
// not have to hop in its prologue.
7154+
if (!baseIsolation.isCallerIsolationInheriting() &&
7155+
getDerivedIsolation().isCallerIsolationInheriting()) {
7156+
B.createHopToExecutor(loc, args.back(), false /*mandatory*/);
7157+
}
71277158
}
71287159

71297160
// Then, the arguments.
@@ -7142,6 +7173,15 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
71427173
derivedRef = B.createFunctionRefFor(loc, implFn);
71437174
}
71447175

7176+
// Check if our base was caller isolation inheriting. In such a case, we need
7177+
// to insert a hop back to our original actor.
7178+
if (auto isolatedArg = llvm::dyn_cast_or_null<SILFunctionArgument>(
7179+
F.maybeGetIsolatedArgument());
7180+
isolatedArg && isolatedArg->getKnownParameterInfo().hasOption(
7181+
SILParameterInfo::ImplicitLeading)) {
7182+
emitScopedHopToTargetActor(loc, {isolatedArg});
7183+
}
7184+
71457185
SILValue result;
71467186

71477187
switch (coroutineKind) {
@@ -7192,7 +7232,7 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
71927232
result = B.createTuple(loc, {});
71937233
break;
71947234
}
7195-
7235+
71967236
scope.pop();
71977237
B.createReturn(loc, result);
71987238

@@ -7560,6 +7600,14 @@ void SILGenFunction::emitProtocolWitness(
75607600
if (options.contains(ThunkGenFlag::CalleeHasImplicitIsolatedParam)) {
75617601
auto reqtIsolation =
75627602
swift::getActorIsolation(requirement.getAbstractFunctionDecl());
7603+
std::optional<ActorIsolation> witnessIsolationCache;
7604+
auto getWitnessIsolation = [&]() -> ActorIsolation {
7605+
if (!witnessIsolationCache) {
7606+
witnessIsolationCache =
7607+
swift::getActorIsolation(witness.getAbstractFunctionDecl());
7608+
}
7609+
return *witnessIsolationCache;
7610+
};
75637611
switch (reqtIsolation) {
75647612
case ActorIsolation::Unspecified:
75657613
case ActorIsolation::Nonisolated:
@@ -7576,8 +7624,7 @@ void SILGenFunction::emitProtocolWitness(
75767624
}
75777625
case ActorIsolation::ActorInstance:
75787626
case ActorIsolation::CallerIsolationInheriting: {
7579-
auto witnessIsolation =
7580-
swift::getActorIsolation(witness.getAbstractFunctionDecl());
7627+
auto witnessIsolation = getWitnessIsolation();
75817628
switch (witnessIsolation) {
75827629
case ActorIsolation::Unspecified:
75837630
case ActorIsolation::Nonisolated:
@@ -7604,6 +7651,25 @@ void SILGenFunction::emitProtocolWitness(
76047651
break;
76057652
}
76067653
}
7654+
7655+
// If our reqtIsolation was not caller isolation inheriting, but our witness
7656+
// isolation is caller isolation inheriting, hop onto the reqtIsolation so
7657+
// that it is safe for our witness to assume that it is already on its
7658+
// actor.
7659+
if (!reqtIsolation.isCallerIsolationInheriting() &&
7660+
getWitnessIsolation().isCallerIsolationInheriting()) {
7661+
B.createHopToExecutor(loc, args.back(), false /*mandatory*/);
7662+
}
7663+
}
7664+
7665+
// Check if our caller has an implicit isolated param. If so, we need to
7666+
// insert a hop at the end of the function to ensure that our caller can
7667+
// assume that we are going to be on its isolation.
7668+
if (auto isolatedArg =
7669+
cast_or_null<SILFunctionArgument>(F.maybeGetIsolatedArgument());
7670+
isolatedArg && isolatedArg->getKnownParameterInfo().hasOption(
7671+
SILParameterInfo::ImplicitLeading)) {
7672+
emitScopedHopToTargetActor(loc, SILValue(isolatedArg));
76077673
}
76087674

76097675
// - the rest of the arguments

test/Concurrency/attr_execution/classes_silgen.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66

77
// We should only produce thunks when going to/from nonisolated(nonsending)
88
// since that is the only thing that makes a true ABI change since we have an
9-
// extra parameter. For isolation purposes, we can rely on async functions
10-
// hopping in their prolog and not need a thunk for the purposes of isolation changing.
9+
// extra parameter.
1110

1211
// NOTE: We use implicit-check-not to make sure we do not create any other
1312
// thunks beyond the ones we pattern match.
@@ -31,6 +30,7 @@ class AllConcurrent : SuperKlass {
3130
// CHECK: bb0([[ACTOR:%.*]] : @guaranteed $Optional<any Actor>, [[PARAM:%.*]] : @guaranteed $AllConcurrent):
3231
// CHECK: [[FUNC:%.*]] = function_ref @$s21attr_execution_silgen13AllConcurrentC10callerTestyyYaF : $@convention(method) @async (@guaranteed AllConcurrent) -> ()
3332
// CHECK: apply [[FUNC]]([[PARAM]])
33+
// CHECK: hop_to_executor [[ACTOR]]
3434
// CHECK: } // end sil function '$s21attr_execution_silgen13AllConcurrentC10callerTestyyYaFAA10SuperKlassCADyyYaFTV'
3535
@concurrent override func callerTest() async {}
3636
@concurrent override func concurrentTest() async {}
@@ -44,6 +44,7 @@ class AllNonIsolatedUnsafe : SuperKlass {
4444
// CHECK-NEXT: sil private [thunk] [ossa] @$s21attr_execution_silgen20AllNonIsolatedUnsafeC14concurrentTestyyYaFAA10SuperKlassCADyyYaFTV : $@convention(method) @async (@guaranteed AllNonIsolatedUnsafe) -> () {
4545
// CHECK: bb0([[ARG:%.*]] : @guaranteed
4646
// CHECK: [[ACTOR:%.*]] = enum $Optional<any Actor>, #Optional.none!enumelt
47+
// CHECK: hop_to_executor [[ACTOR]]
4748
// CHECK: [[FUNC:%.*]] = function_ref @$s21attr_execution_silgen20AllNonIsolatedUnsafeC14concurrentTestyyYaF : $@convention(method) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>, @guaranteed AllNonIsolatedUnsafe) -> ()
4849
// CHECK: apply [[FUNC]]([[ACTOR]], [[ARG]])
4950
// CHECK: } // end sil function '$s21attr_execution_silgen20AllNonIsolatedUnsafeC14concurrentTestyyYaFAA10SuperKlassCADyyYaFTV'
@@ -55,6 +56,7 @@ class AllNonIsolatedUnsafe : SuperKlass {
5556
// CHECK: [[ACTOR:%.*]] = apply {{%.*}}({{%.*}}) : $@convention(method) (@thick MainActor.Type) -> @owned MainActor
5657
// CHECK: [[ACTOR_E:%.*]] = init_existential_ref [[ACTOR]]
5758
// CHECK: [[ACTOR_E_OPT:%.*]] = enum $Optional<any Actor>, #Optional.some!enumelt, [[ACTOR_E]]
59+
// CHECK: hop_to_executor [[ACTOR_E_OPT]]
5860
// CHECK: [[FUNC:%.*]] = function_ref @$s21attr_execution_silgen20AllNonIsolatedUnsafeC13mainActorTestyyYaF : $@convention(method) @async (@sil_isolated @sil_implicit_leading_param @guaranteed Optional<any Actor>, @guaranteed AllNonIsolatedUnsafe) -> ()
5961
// CHECK: apply [[FUNC]]([[ACTOR_E_OPT]], [[ARG]])
6062
// CHECK: } // end sil function '$s21attr_execution_silgen20AllNonIsolatedUnsafeC13mainActorTestyyYaFAA10SuperKlassCADyyYaFTV'

0 commit comments

Comments
 (0)