Skip to content

Commit 82b0ec3

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 48b88b0 commit 82b0ec3

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
@@ -5516,7 +5516,7 @@ static void buildThunkBody(SILGenFunction &SGF, SILLocation loc,
55165516
// don't need to do anything. But if the input is synchronous and the
55175517
// executor is asynchronous, we need to treat this like any other call
55185518
// to a synchronous function from an asynchronous context.
5519-
bool hopToIsolatedParameter = false;
5519+
bool hopToIsolatedParameterForSync = false;
55205520
if (outputSubstType->isAsync() && !inputSubstType->isAsync()) {
55215521
auto inputIsolation = inputSubstType->getIsolation();
55225522
switch (inputIsolation.getKind()) {
@@ -5533,7 +5533,7 @@ static void buildThunkBody(SILGenFunction &SGF, SILLocation loc,
55335533
// For a function with parameter isolation, we'll have to dig the
55345534
// argument out after translation but before making the call.
55355535
case FunctionTypeIsolation::Kind::Parameter:
5536-
hopToIsolatedParameter = true;
5536+
hopToIsolatedParameterForSync = true;
55375537
break;
55385538

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

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

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

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

5656+
// If our thunk is a caller isolated function, we need to insert a hop to
5657+
// return to our isolation so that our caller can assume that we preserve
5658+
// isolation.
5659+
if (auto isolatedArg = llvm::cast_or_null<SILFunctionArgument>(
5660+
SGF.F.maybeGetIsolatedArgument());
5661+
isolatedArg && isolatedArg->getKnownParameterInfo().hasOption(
5662+
SILParameterInfo::ImplicitLeading)) {
5663+
SGF.emitScopedHopToTargetActor(loc, isolatedArg);
5664+
}
5665+
56515666
SILValue innerResult =
56525667
SGF.emitApplyWithRethrow(loc, fun,
56535668
/*substFnType*/ fnValue.getType(),
@@ -7073,6 +7088,15 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
70737088
if (options.contains(ThunkGenFlag::CalleeHasImplicitIsolatedParam)) {
70747089
auto baseIsolation =
70757090
swift::getActorIsolation(base.getAbstractFunctionDecl());
7091+
std::optional<ActorIsolation> derivedIsolationCache;
7092+
auto getDerivedIsolation = [&]() -> ActorIsolation {
7093+
if (!derivedIsolationCache) {
7094+
derivedIsolationCache =
7095+
swift::getActorIsolation(derived.getAbstractFunctionDecl());
7096+
}
7097+
return *derivedIsolationCache;
7098+
};
7099+
70767100
switch (baseIsolation) {
70777101
case ActorIsolation::Unspecified:
70787102
case ActorIsolation::Nonisolated:
@@ -7089,8 +7113,7 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
70897113
}
70907114
case ActorIsolation::ActorInstance:
70917115
case ActorIsolation::CallerIsolationInheriting: {
7092-
auto derivedIsolation =
7093-
swift::getActorIsolation(derived.getAbstractFunctionDecl());
7116+
auto derivedIsolation = getDerivedIsolation();
70947117
switch (derivedIsolation) {
70957118
case ActorIsolation::Unspecified:
70967119
case ActorIsolation::Nonisolated:
@@ -7117,6 +7140,14 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
71177140
break;
71187141
}
71197142
}
7143+
7144+
// If our derived isolation is caller isolation inheriting and our base
7145+
// isn't, we need to insert a hop so that derived can assume that it does
7146+
// not have to hop in its prologue.
7147+
if (!baseIsolation.isCallerIsolationInheriting() &&
7148+
getDerivedIsolation().isCallerIsolationInheriting()) {
7149+
B.createHopToExecutor(loc, args.back(), false /*mandatory*/);
7150+
}
71207151
}
71217152

71227153
// Then, the arguments.
@@ -7135,6 +7166,15 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
71357166
derivedRef = B.createFunctionRefFor(loc, implFn);
71367167
}
71377168

7169+
// Check if our base was caller isolation inheriting. In such a case, we need
7170+
// to insert a hop back to our original actor.
7171+
if (auto isolatedArg = llvm::dyn_cast_or_null<SILFunctionArgument>(
7172+
F.maybeGetIsolatedArgument());
7173+
isolatedArg && isolatedArg->getKnownParameterInfo().hasOption(
7174+
SILParameterInfo::ImplicitLeading)) {
7175+
emitScopedHopToTargetActor(loc, {isolatedArg});
7176+
}
7177+
71387178
SILValue result;
71397179

71407180
switch (coroutineKind) {
@@ -7185,7 +7225,7 @@ SILGenFunction::emitVTableThunk(SILDeclRef base,
71857225
result = B.createTuple(loc, {});
71867226
break;
71877227
}
7188-
7228+
71897229
scope.pop();
71907230
B.createReturn(loc, result);
71917231

@@ -7553,6 +7593,14 @@ void SILGenFunction::emitProtocolWitness(
75537593
if (options.contains(ThunkGenFlag::CalleeHasImplicitIsolatedParam)) {
75547594
auto reqtIsolation =
75557595
swift::getActorIsolation(requirement.getAbstractFunctionDecl());
7596+
std::optional<ActorIsolation> witnessIsolationCache;
7597+
auto getWitnessIsolation = [&]() -> ActorIsolation {
7598+
if (!witnessIsolationCache) {
7599+
witnessIsolationCache =
7600+
swift::getActorIsolation(witness.getAbstractFunctionDecl());
7601+
}
7602+
return *witnessIsolationCache;
7603+
};
75567604
switch (reqtIsolation) {
75577605
case ActorIsolation::Unspecified:
75587606
case ActorIsolation::Nonisolated:
@@ -7569,8 +7617,7 @@ void SILGenFunction::emitProtocolWitness(
75697617
}
75707618
case ActorIsolation::ActorInstance:
75717619
case ActorIsolation::CallerIsolationInheriting: {
7572-
auto witnessIsolation =
7573-
swift::getActorIsolation(witness.getAbstractFunctionDecl());
7620+
auto witnessIsolation = getWitnessIsolation();
75747621
switch (witnessIsolation) {
75757622
case ActorIsolation::Unspecified:
75767623
case ActorIsolation::Nonisolated:
@@ -7597,6 +7644,25 @@ void SILGenFunction::emitProtocolWitness(
75977644
break;
75987645
}
75997646
}
7647+
7648+
// If our reqtIsolation was not caller isolation inheriting, but our witness
7649+
// isolation is caller isolation inheriting, hop onto the reqtIsolation so
7650+
// that it is safe for our witness to assume that it is already on its
7651+
// actor.
7652+
if (!reqtIsolation.isCallerIsolationInheriting() &&
7653+
getWitnessIsolation().isCallerIsolationInheriting()) {
7654+
B.createHopToExecutor(loc, args.back(), false /*mandatory*/);
7655+
}
7656+
}
7657+
7658+
// Check if our caller has an implicit isolated param. If so, we need to
7659+
// insert a hop at the end of the function to ensure that our caller can
7660+
// assume that we are going to be on its isolation.
7661+
if (auto isolatedArg =
7662+
cast_or_null<SILFunctionArgument>(F.maybeGetIsolatedArgument());
7663+
isolatedArg && isolatedArg->getKnownParameterInfo().hasOption(
7664+
SILParameterInfo::ImplicitLeading)) {
7665+
emitScopedHopToTargetActor(loc, SILValue(isolatedArg));
76007666
}
76017667

76027668
// - 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)