Skip to content

Commit 33818ff

Browse files
authored
Teach witness thunks to hop to the actor when needed (#59456)
* Teach witness thunks to hop to the actor when needed. When a synchronous, actor-isolated declaration witnesses an asynchronous, not-similarly-isolated requirement, emit an actor hop within the witness thunk to ensure that we properly enter the context of the actor. Fixes #58517 / rdar://92881539. (cherry picked from commit 25a7988) * Enter actor isolation for a distributed witness via the distributed thunk We cannot hop to a distributed actor directly, so call through the distributed thunk instead. (cherry picked from commit 840b7ea)
1 parent 215fe8c commit 33818ff

13 files changed

+154
-32
lines changed

include/swift/AST/ProtocolConformance.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,6 +601,9 @@ class NormalProtocolConformance : public RootProtocolConformance,
601601
/// Set the witness for the given requirement.
602602
void setWitness(ValueDecl *requirement, Witness witness) const;
603603

604+
/// Override the witness for a given requirement.
605+
void overrideWitness(ValueDecl *requirement, Witness newWitness);
606+
604607
/// Retrieve the protocol conformances that satisfy the requirements of the
605608
/// protocol, which line up with the conformance constraints in the
606609
/// protocol's requirement signature.

include/swift/AST/Witness.h

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ class Witness {
9797
/// The derivative generic signature, when the requirement is a derivative
9898
/// function.
9999
GenericSignature derivativeGenSig;
100+
Optional<ActorIsolation> enterIsolation;
100101
};
101102

102103
llvm::PointerUnion<ValueDecl *, StoredWitness *> storage;
@@ -125,10 +126,12 @@ class Witness {
125126
///
126127
/// Deserialized witnesses do not have a synthetic environment.
127128
static Witness forDeserialized(ValueDecl *decl,
128-
SubstitutionMap substitutions) {
129+
SubstitutionMap substitutions,
130+
Optional<ActorIsolation> enterIsolation) {
129131
// TODO: It's probably a good idea to have a separate 'deserialized' bit.
130132
return Witness(
131-
decl, substitutions, nullptr, SubstitutionMap(), CanGenericSignature());
133+
decl, substitutions, nullptr, SubstitutionMap(), CanGenericSignature(),
134+
enterIsolation);
132135
}
133136

134137
/// Create a witness that requires substitutions.
@@ -145,11 +148,15 @@ class Witness {
145148
///
146149
/// \param derivativeGenSig The derivative generic signature, when the
147150
/// requirement is a derivative function.
151+
///
152+
/// \param enterIsolation The actor isolation that the witness thunk will
153+
/// need to hop to before calling the witness.
148154
Witness(ValueDecl *decl,
149155
SubstitutionMap substitutions,
150156
GenericEnvironment *syntheticEnv,
151157
SubstitutionMap reqToSyntheticEnvSubs,
152-
GenericSignature derivativeGenSig);
158+
GenericSignature derivativeGenSig,
159+
Optional<ActorIsolation> enterIsolation);
153160

154161
/// Retrieve the witness declaration reference, which includes the
155162
/// substitutions needed to use the witness from the synthetic environment
@@ -198,6 +205,17 @@ class Witness {
198205
return GenericSignature();
199206
}
200207

208+
Optional<ActorIsolation> getEnterIsolation() const {
209+
if (auto *storedWitness = storage.dyn_cast<StoredWitness *>())
210+
return storedWitness->enterIsolation;
211+
212+
return None;
213+
}
214+
215+
/// Retrieve a copy of the witness with an actor isolation that the
216+
/// witness thunk will need to hop to.
217+
Witness withEnterIsolation(ActorIsolation enterIsolation) const;
218+
201219
SWIFT_DEBUG_DUMP;
202220

203221
void dump(llvm::raw_ostream &out) const;

lib/AST/ProtocolConformance.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,10 @@ using namespace swift;
4444
Witness::Witness(ValueDecl *decl, SubstitutionMap substitutions,
4545
GenericEnvironment *syntheticEnv,
4646
SubstitutionMap reqToSynthesizedEnvSubs,
47-
GenericSignature derivativeGenSig) {
47+
GenericSignature derivativeGenSig,
48+
Optional<ActorIsolation> enterIsolation) {
4849
if (!syntheticEnv && substitutions.empty() &&
49-
reqToSynthesizedEnvSubs.empty()) {
50+
reqToSynthesizedEnvSubs.empty() && !enterIsolation) {
5051
storage = decl;
5152
return;
5253
}
@@ -56,11 +57,17 @@ Witness::Witness(ValueDecl *decl, SubstitutionMap substitutions,
5657
auto storedMem = ctx.Allocate(sizeof(StoredWitness), alignof(StoredWitness));
5758
auto stored = new (storedMem) StoredWitness{declRef, syntheticEnv,
5859
reqToSynthesizedEnvSubs,
59-
derivativeGenSig};
60+
derivativeGenSig, enterIsolation};
6061

6162
storage = stored;
6263
}
6364

65+
Witness Witness::withEnterIsolation(ActorIsolation enterIsolation) const {
66+
return Witness(getDecl(), getSubstitutions(), getSyntheticEnvironment(),
67+
getRequirementToSyntheticSubs(),
68+
getDerivativeGenericSignature(), enterIsolation);
69+
}
70+
6471
void Witness::dump() const { dump(llvm::errs()); }
6572

6673
void Witness::dump(llvm::raw_ostream &out) const {
@@ -902,7 +909,7 @@ NormalProtocolConformance::getWitnessUncached(ValueDecl *requirement) const {
902909

903910
Witness SelfProtocolConformance::getWitness(ValueDecl *requirement) const {
904911
return Witness(requirement, SubstitutionMap(), nullptr, SubstitutionMap(),
905-
GenericSignature());
912+
GenericSignature(), None);
906913
}
907914

908915
ConcreteDeclRef
@@ -941,6 +948,12 @@ void NormalProtocolConformance::setWitness(ValueDecl *requirement,
941948
Mapping[requirement] = witness;
942949
}
943950

951+
void NormalProtocolConformance::overrideWitness(ValueDecl *requirement,
952+
Witness witness) {
953+
assert(Mapping.count(requirement) == 1 && "Witness not known");
954+
Mapping[requirement] = witness;
955+
}
956+
944957
SpecializedProtocolConformance::SpecializedProtocolConformance(
945958
Type conformingType,
946959
ProtocolConformance *genericConformance,

lib/SILGen/SILGenFunction.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -756,7 +756,8 @@ class LLVM_LIBRARY_VISIBILITY SILGenFunction
756756
SILDeclRef witness,
757757
SubstitutionMap witnessSubs,
758758
IsFreeFunctionWitness_t isFree,
759-
bool isSelfConformance);
759+
bool isSelfConformance,
760+
Optional<ActorIsolation> enterIsolation);
760761

761762
/// Generates subscript arguments for keypath. This function handles lowering
762763
/// of all index expressions including default arguments.

lib/SILGen/SILGenPoly.cpp

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4407,14 +4407,16 @@ emitOpenExistentialInSelfConformance(SILGenFunction &SGF, SILLocation loc,
44074407
: AccessKind::Read);
44084408
}
44094409

4410-
void SILGenFunction::emitProtocolWitness(AbstractionPattern reqtOrigTy,
4411-
CanAnyFunctionType reqtSubstTy,
4412-
SILDeclRef requirement,
4413-
SubstitutionMap reqtSubs,
4414-
SILDeclRef witness,
4415-
SubstitutionMap witnessSubs,
4416-
IsFreeFunctionWitness_t isFree,
4417-
bool isSelfConformance) {
4410+
void SILGenFunction::emitProtocolWitness(
4411+
AbstractionPattern reqtOrigTy,
4412+
CanAnyFunctionType reqtSubstTy,
4413+
SILDeclRef requirement,
4414+
SubstitutionMap reqtSubs,
4415+
SILDeclRef witness,
4416+
SubstitutionMap witnessSubs,
4417+
IsFreeFunctionWitness_t isFree,
4418+
bool isSelfConformance,
4419+
Optional<ActorIsolation> enterIsolation) {
44184420
// FIXME: Disable checks that the protocol witness carries debug info.
44194421
// Should we carry debug info for witnesses?
44204422
F.setBare(IsBare);
@@ -4434,6 +4436,34 @@ void SILGenFunction::emitProtocolWitness(AbstractionPattern reqtOrigTy,
44344436
SmallVector<ManagedValue, 8> origParams;
44354437
collectThunkParams(loc, origParams);
44364438

4439+
// If we are supposed to enter the actor, do so now.
4440+
if (enterIsolation) {
4441+
if (enterIsolation->isDistributedActor()) {
4442+
// For a distributed actor, call through the distributed thunk.
4443+
witness = witness.asDistributed();
4444+
} else {
4445+
// For a non-distributed actor, hop to the actor.
4446+
Optional<ManagedValue> actorSelf;
4447+
4448+
// For an instance actor, get the actor 'self'.
4449+
if (*enterIsolation == ActorIsolation::ActorInstance) {
4450+
auto actorSelfVal = origParams.back();
4451+
4452+
if (actorSelfVal.getType().isAddress()) {
4453+
auto &actorSelfTL = getTypeLowering(actorSelfVal.getType());
4454+
if (!actorSelfTL.isAddressOnly()) {
4455+
actorSelfVal = emitManagedLoad(
4456+
*this, loc, actorSelfVal, actorSelfTL);
4457+
}
4458+
}
4459+
4460+
actorSelf = actorSelfVal;
4461+
}
4462+
4463+
emitHopToTargetActor(loc, enterIsolation, actorSelf);
4464+
}
4465+
}
4466+
44374467
// Get the type of the witness.
44384468
auto witnessInfo = getConstantInfo(getTypeExpansionContext(), witness);
44394469
CanAnyFunctionType witnessSubstTy = witnessInfo.LoweredType;

lib/SILGen/SILGenType.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -812,7 +812,8 @@ SILFunction *SILGenModule::emitProtocolWitness(
812812

813813
SGF.emitProtocolWitness(AbstractionPattern(reqtOrigTy), reqtSubstTy,
814814
requirement, reqtSubMap, witnessRef,
815-
witnessSubs, isFree, /*isSelfConformance*/ false);
815+
witnessSubs, isFree, /*isSelfConformance*/ false,
816+
witness.getEnterIsolation());
816817

817818
emitLazyConformancesForFunction(f);
818819
return f;
@@ -884,7 +885,8 @@ static SILFunction *emitSelfConformanceWitness(SILGenModule &SGM,
884885

885886
SGF.emitProtocolWitness(AbstractionPattern(reqtOrigTy), reqtSubstTy,
886887
requirement, reqtSubs, requirement,
887-
witnessSubs, isFree, /*isSelfConformance*/ true);
888+
witnessSubs, isFree, /*isSelfConformance*/ true,
889+
None);
888890

889891
SGM.emitLazyConformancesForFunction(f);
890892

lib/Sema/TypeCheckProtocol.cpp

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ swift::Witness RequirementMatch::getWitness(ASTContext &ctx) const {
102102
auto syntheticEnv = ReqEnv->getSyntheticEnvironment();
103103
return swift::Witness(this->Witness, WitnessSubstitutions,
104104
syntheticEnv, ReqEnv->getRequirementToSyntheticMap(),
105-
DerivativeGenSig);
105+
DerivativeGenSig, None);
106106
}
107107

108108
AssociatedTypeDecl *
@@ -2932,7 +2932,7 @@ static bool hasExplicitGlobalActorAttr(ValueDecl *decl) {
29322932
return !globalActorAttr->first->isImplicit();
29332933
}
29342934

2935-
bool ConformanceChecker::checkActorIsolation(
2935+
Optional<ActorIsolation> ConformanceChecker::checkActorIsolation(
29362936
ValueDecl *requirement, ValueDecl *witness) {
29372937
/// Retrieve a concrete witness for Sendable checking.
29382938
auto getConcreteWitness = [&] {
@@ -2972,12 +2972,12 @@ bool ConformanceChecker::checkActorIsolation(
29722972
}
29732973

29742974
// Otherwise, we're done.
2975-
return false;
2975+
return None;
29762976

29772977
case ActorReferenceResult::ExitsActorToNonisolated:
29782978
diagnoseNonSendableTypesInReference(
29792979
getConcreteWitness(), DC, loc, SendableCheckReason::Conformance);
2980-
return false;
2980+
return None;
29812981

29822982
case ActorReferenceResult::EntersActor:
29832983
// Handled below.
@@ -3056,12 +3056,16 @@ bool ConformanceChecker::checkActorIsolation(
30563056
// that is explicitly marked nonisolated.
30573057
if (isa<ConstructorDecl>(witness) &&
30583058
witness->getAttrs().hasAttribute<NonisolatedAttr>())
3059-
return false;
3059+
return None;
30603060

30613061
diagnoseNonSendableTypesInReference(
30623062
getConcreteWitness(), DC, loc, SendableCheckReason::Conformance);
30633063

3064-
return false;
3064+
if (refResult.isolation.isActorIsolated() && isAsyncDecl(requirement) &&
3065+
!isAsyncDecl(witness))
3066+
return refResult.isolation;
3067+
3068+
return None;
30653069
}
30663070

30673071
// Limit the behavior of the diagnostic based on context.
@@ -3080,7 +3084,6 @@ bool ConformanceChecker::checkActorIsolation(
30803084

30813085
// Complain that this witness cannot conform to the requirement due to
30823086
// actor isolation.
3083-
bool isError = (behavior == DiagnosticBehavior::Unspecified);
30843087
witness->diagnose(diag::actor_isolated_witness,
30853088
isDistributed && !isDistributedDecl(witness),
30863089
refResult.isolation, witness->getDescriptiveKind(),
@@ -3168,7 +3171,7 @@ bool ConformanceChecker::checkActorIsolation(
31683171
requirement->diagnose(diag::decl_declared_here, requirement->getName());
31693172
}
31703173

3171-
return isError;
3174+
return None;
31723175
}
31733176

31743177
bool ConformanceChecker::checkObjCTypeErasedGenerics(
@@ -5071,8 +5074,13 @@ void ConformanceChecker::resolveValueWitnesses() {
50715074

50725075
auto &C = witness->getASTContext();
50735076

5074-
if (checkActorIsolation(requirement, witness)) {
5075-
return;
5077+
// Check actor isolation. If we need to enter into the actor's
5078+
// isolation within the witness thunk, record that.
5079+
if (auto enteringIsolation = checkActorIsolation(requirement, witness)) {
5080+
Conformance->overrideWitness(
5081+
requirement,
5082+
Conformance->getWitnessUncached(requirement)
5083+
.withEnterIsolation(*enteringIsolation));
50765084
}
50775085

50785086
// Objective-C checking for @objc requirements.

lib/Sema/TypeCheckProtocol.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -788,8 +788,11 @@ class ConformanceChecker : public WitnessChecker {
788788

789789
/// Check that the witness and requirement have compatible actor contexts.
790790
///
791-
/// \returns true if an error occurred, false otherwise.
792-
bool checkActorIsolation(ValueDecl *requirement, ValueDecl *witness);
791+
/// \returns the isolation that needs to be enforced to invoke the witness
792+
/// from the requirement, used when entering an actor-isolated synchronous
793+
/// witness from an asynchronous requirement.
794+
Optional<ActorIsolation>
795+
checkActorIsolation(ValueDecl *requirement, ValueDecl *witness);
793796

794797
/// Record a type witness.
795798
///

lib/Serialization/Deserialization.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6964,14 +6964,22 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
69646964
fatal(witnessSubstitutions.takeError());
69656965
}
69666966

6967+
// Determine whether we need to enter the actor isolation of the witness.
6968+
Optional<ActorIsolation> enterIsolation;
6969+
if (*rawIDIter++) {
6970+
enterIsolation = getActorIsolation(witness);
6971+
}
6972+
69676973
// Handle opaque witnesses that couldn't be deserialized.
69686974
if (isOpaque) {
69696975
trySetOpaqueWitness();
69706976
continue;
69716977
}
69726978

69736979
// Set the witness.
6974-
trySetWitness(Witness::forDeserialized(witness, witnessSubstitutions.get()));
6980+
trySetWitness(
6981+
Witness::forDeserialized(
6982+
witness, witnessSubstitutions.get(), enterIsolation));
69756983
}
69766984
assert(rawIDIter <= rawIDs.end() && "read too much");
69776985

lib/Serialization/ModuleFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5656
/// describe what change you made. The content of this comment isn't important;
5757
/// it just ensures a conflict if two people change the module format.
5858
/// Don't worry about adhering to the 80-column limit for this line.
59-
const uint16_t SWIFTMODULE_VERSION_MINOR = 690; // cast ownership serialization
59+
const uint16_t SWIFTMODULE_VERSION_MINOR = 691; // witness enter isolation
6060

6161
/// A standard hash seed used for all string hashes in a serialized module.
6262
///

lib/Serialization/Serialization.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1592,6 +1592,7 @@ void Serializer::writeLocalNormalProtocolConformance(
15921592
subs = subs.mapReplacementTypesOutOfContext();
15931593

15941594
data.push_back(addSubstitutionMapRef(subs));
1595+
data.push_back(witness.getEnterIsolation().hasValue() ? 1 : 0);
15951596
});
15961597

15971598
unsigned abbrCode

test/SILGen/distributed_thunk.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,16 @@ extension DA {
1717

1818
distributed func f() { }
1919
}
20+
21+
protocol ServerProto {
22+
func doSomething() async throws
23+
}
24+
25+
extension DA: ServerProto {
26+
// CHECK-LABEL: sil private [transparent] [thunk] [ossa] @$s17distributed_thunk2DACAA11ServerProtoA2aDP11doSomethingyyYaKFTW : $@convention(witness_method: ServerProto) @async (@in_guaranteed DA) -> @error Error
27+
// CHECK-NOT: hop_to_executor
28+
// CHECK-NOT: return
29+
// CHECK: function_ref @$s17distributed_thunk2DAC11doSomethingyyFTE
30+
// CHECK: return
31+
distributed func doSomething() { }
32+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// RUN: %target-swift-frontend -emit-silgen %s -module-name test -swift-version 5 | %FileCheck --enable-var-scope %s --implicit-check-not 'hop_to_executor {{%[0-9]+}}'
2+
// REQUIRES: concurrency
3+
4+
@available(SwiftStdlib 5.1, *)
5+
protocol TestableAsync {
6+
func test() async
7+
}
8+
9+
@available(SwiftStdlib 5.1, *)
10+
actor TestActor : TestableAsync {
11+
// CHECK-LABEL: sil private [transparent] [thunk] [ossa] @$s4test9TestActorCAA13TestableAsyncA2aDPAAyyYaFTW : $@convention(witness_method: TestableAsync) @async (@in_guaranteed TestActor) -> ()
12+
// CHECK: [[ACTOR_INSTANCE:%.*]] = load_borrow %0 : $*TestActor
13+
// CHECK-NEXT: hop_to_executor [[ACTOR_INSTANCE]] : $TestActor
14+
func test() { }
15+
}
16+
17+
@available(SwiftStdlib 5.1, *)
18+
@MainActor class TestClass : TestableAsync {
19+
// CHECK-LABEL: sil private [transparent] [thunk] [ossa] @$s4test9TestClassCAA13TestableAsyncA2aDPAAyyYaFTW : $@convention(witness_method: TestableAsync) @async (@in_guaranteed TestClass) -> ()
20+
// CHECK: hop_to_executor {{%.*}} : $MainActor
21+
func test() { }
22+
}

0 commit comments

Comments
 (0)