Skip to content

Commit e9c5542

Browse files
authored
Merge pull request #83177 from gottesmm/rdar155905383-finished
[concurrency] Add fixes from rjmccall's review on 6.2 to main
2 parents b4b9311 + 0da2238 commit e9c5542

File tree

8 files changed

+383
-47
lines changed

8 files changed

+383
-47
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

lib/SILOptimizer/Mandatory/OptimizeHopToExecutor.cpp

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,11 +342,12 @@ void OptimizeHopToExecutor::updateNeedExecutor(int &needExecutor,
342342
// 6.2, since caller isolation inheriting functions will no longer be
343343
// considered suspension points, we will be able to sink this code into needs
344344
// executor.
345-
if (auto isolation = inst->getFunction()->getActorIsolation();
346-
isolation && isolation->isCallerIsolationInheriting() &&
347-
isa<ReturnInst>(inst)) {
348-
needExecutor = BlockState::ExecutorNeeded;
349-
return;
345+
if (isa<ReturnInst>(inst)) {
346+
if (auto isolation = inst->getFunction()->getActorIsolation();
347+
isolation && isolation->isCallerIsolationInheriting()) {
348+
needExecutor = BlockState::ExecutorNeeded;
349+
return;
350+
}
350351
}
351352

352353
if (isSuspensionPoint(inst)) {

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)