Skip to content

Commit 25a7988

Browse files
committed
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.
1 parent 774a94f commit 25a7988

File tree

12 files changed

+132
-32
lines changed

12 files changed

+132
-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: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4411,14 +4411,16 @@ emitOpenExistentialInSelfConformance(SILGenFunction &SGF, SILLocation loc,
44114411
: AccessKind::Read);
44124412
}
44134413

4414-
void SILGenFunction::emitProtocolWitness(AbstractionPattern reqtOrigTy,
4415-
CanAnyFunctionType reqtSubstTy,
4416-
SILDeclRef requirement,
4417-
SubstitutionMap reqtSubs,
4418-
SILDeclRef witness,
4419-
SubstitutionMap witnessSubs,
4420-
IsFreeFunctionWitness_t isFree,
4421-
bool isSelfConformance) {
4414+
void SILGenFunction::emitProtocolWitness(
4415+
AbstractionPattern reqtOrigTy,
4416+
CanAnyFunctionType reqtSubstTy,
4417+
SILDeclRef requirement,
4418+
SubstitutionMap reqtSubs,
4419+
SILDeclRef witness,
4420+
SubstitutionMap witnessSubs,
4421+
IsFreeFunctionWitness_t isFree,
4422+
bool isSelfConformance,
4423+
Optional<ActorIsolation> enterIsolation) {
44224424
// FIXME: Disable checks that the protocol witness carries debug info.
44234425
// Should we carry debug info for witnesses?
44244426
F.setBare(IsBare);
@@ -4479,6 +4481,25 @@ void SILGenFunction::emitProtocolWitness(AbstractionPattern reqtOrigTy,
44794481
witnessUnsubstTy->getSelfParameter());
44804482
}
44814483

4484+
// If we are supposed to hop to the actor, do so now.
4485+
if (enterIsolation) {
4486+
Optional<ManagedValue> actorSelf;
4487+
if (*enterIsolation == ActorIsolation::ActorInstance) {
4488+
auto actorSelfVal = origParams.back();
4489+
4490+
if (actorSelfVal.getType().isAddress()) {
4491+
auto &actorSelfTL = getTypeLowering(actorSelfVal.getType());
4492+
if (!actorSelfTL.isAddressOnly()) {
4493+
actorSelfVal = emitManagedLoad(*this, loc, actorSelfVal, actorSelfTL);
4494+
}
4495+
}
4496+
4497+
actorSelf = actorSelfVal;
4498+
}
4499+
4500+
emitHopToTargetActor(loc, enterIsolation, actorSelf);
4501+
}
4502+
44824503
// For static C++ methods and constructors, we need to drop the (metatype)
44834504
// "self" param. The "native" SIL representation will look like this:
44844505
// @convention(method) (@thin Foo.Type) -> () but the "actual" SIL function

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 *
@@ -2953,7 +2953,7 @@ static bool hasExplicitGlobalActorAttr(ValueDecl *decl) {
29532953
return !globalActorAttr->first->isImplicit();
29542954
}
29552955

2956-
bool ConformanceChecker::checkActorIsolation(
2956+
Optional<ActorIsolation> ConformanceChecker::checkActorIsolation(
29572957
ValueDecl *requirement, ValueDecl *witness) {
29582958
/// Retrieve a concrete witness for Sendable checking.
29592959
auto getConcreteWitness = [&] {
@@ -2993,12 +2993,12 @@ bool ConformanceChecker::checkActorIsolation(
29932993
}
29942994

29952995
// Otherwise, we're done.
2996-
return false;
2996+
return None;
29972997

29982998
case ActorReferenceResult::ExitsActorToNonisolated:
29992999
diagnoseNonSendableTypesInReference(
30003000
getConcreteWitness(), DC, loc, SendableCheckReason::Conformance);
3001-
return false;
3001+
return None;
30023002

30033003
case ActorReferenceResult::EntersActor:
30043004
// Handled below.
@@ -3077,12 +3077,16 @@ bool ConformanceChecker::checkActorIsolation(
30773077
// that is explicitly marked nonisolated.
30783078
if (isa<ConstructorDecl>(witness) &&
30793079
witness->getAttrs().hasAttribute<NonisolatedAttr>())
3080-
return false;
3080+
return None;
30813081

30823082
diagnoseNonSendableTypesInReference(
30833083
getConcreteWitness(), DC, loc, SendableCheckReason::Conformance);
30843084

3085-
return false;
3085+
if (refResult.isolation.isActorIsolated() && isAsyncDecl(requirement) &&
3086+
!isAsyncDecl(witness))
3087+
return refResult.isolation;
3088+
3089+
return None;
30863090
}
30873091

30883092
// Limit the behavior of the diagnostic based on context.
@@ -3101,7 +3105,6 @@ bool ConformanceChecker::checkActorIsolation(
31013105

31023106
// Complain that this witness cannot conform to the requirement due to
31033107
// actor isolation.
3104-
bool isError = (behavior == DiagnosticBehavior::Unspecified);
31053108
witness->diagnose(diag::actor_isolated_witness,
31063109
isDistributed && !isDistributedDecl(witness),
31073110
refResult.isolation, witness->getDescriptiveKind(),
@@ -3189,7 +3192,7 @@ bool ConformanceChecker::checkActorIsolation(
31893192
requirement->diagnose(diag::decl_declared_here, requirement->getName());
31903193
}
31913194

3192-
return isError;
3195+
return None;
31933196
}
31943197

31953198
bool ConformanceChecker::checkObjCTypeErasedGenerics(
@@ -5092,8 +5095,13 @@ void ConformanceChecker::resolveValueWitnesses() {
50925095

50935096
auto &C = witness->getASTContext();
50945097

5095-
if (checkActorIsolation(requirement, witness)) {
5096-
return;
5098+
// Check actor isolation. If we need to enter into the actor's
5099+
// isolation within the witness thunk, record that.
5100+
if (auto enteringIsolation = checkActorIsolation(requirement, witness)) {
5101+
Conformance->overrideWitness(
5102+
requirement,
5103+
Conformance->getWitnessUncached(requirement)
5104+
.withEnterIsolation(*enteringIsolation));
50975105
}
50985106

50995107
// 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
@@ -6936,14 +6936,22 @@ void ModuleFile::finishNormalConformance(NormalProtocolConformance *conformance,
69366936
fatal(witnessSubstitutions.takeError());
69376937
}
69386938

6939+
// Determine whether we need to enter the actor isolation of the witness.
6940+
Optional<ActorIsolation> enterIsolation;
6941+
if (*rawIDIter++) {
6942+
enterIsolation = getActorIsolation(witness);
6943+
}
6944+
69396945
// Handle opaque witnesses that couldn't be deserialized.
69406946
if (isOpaque) {
69416947
trySetOpaqueWitness();
69426948
continue;
69436949
}
69446950

69456951
// Set the witness.
6946-
trySetWitness(Witness::forDeserialized(witness, witnessSubstitutions.get()));
6952+
trySetWitness(
6953+
Witness::forDeserialized(
6954+
witness, witnessSubstitutions.get(), enterIsolation));
69476955
}
69486956
assert(rawIDIter <= rawIDs.end() && "read too much");
69496957

lib/Serialization/ModuleFormat.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ const uint16_t SWIFTMODULE_VERSION_MAJOR = 0;
5858
/// describe what change you made. The content of this comment isn't important;
5959
/// it just ensures a conflict if two people change the module format.
6060
/// Don't worry about adhering to the 80-column limit for this line.
61-
const uint16_t SWIFTMODULE_VERSION_MINOR = 692; // move only type wrapper
61+
const uint16_t SWIFTMODULE_VERSION_MINOR = 693; // witness enter isolation
6262

6363
/// A standard hash seed used for all string hashes in a serialized module.
6464
///

lib/Serialization/Serialization.cpp

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

15951595
data.push_back(addSubstitutionMapRef(subs));
1596+
data.push_back(witness.getEnterIsolation().hasValue() ? 1 : 0);
15961597
});
15971598

15981599
unsigned abbrCode
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)