Skip to content

Commit 14ac4be

Browse files
committed
SILOptimizer: Fix invariant violation in getWitnessMethodSubstitutions() with class witness methods
Witness thunks where the conforming type is a class and the witness is in a protocol extension have an extra generic parameter constrained to the class type that is passed as the 'Self' parameter for the protocol extension method. This means the substitution map for the devirtualized call must be assembled from three sources: - The 'Self' substitution - The generic parameters of the concrete conforming type, if any - The generic parameters of the protocol requirement, if any This was previously done by making two calls to combineSubstitutionMaps(), the first call combined the first two maps and the second call combined the result of the first call with the third map. Unfortunately, both calls were made with the generic signature of the witness thunk, and the result of combining the first two substitution maps does not provide sufficient replacements for all generic parameters and conformance requirements in the witness thunk's signature. This was apparently fine with the GenericSignatureBuilder, but the Requirement Machine flags the missing generic parameters in assert builds. Fixes #59193.
1 parent 64b8624 commit 14ac4be

File tree

2 files changed

+112
-33
lines changed

2 files changed

+112
-33
lines changed

lib/SILOptimizer/Utils/Devirtualize.cpp

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -900,6 +900,8 @@ getWitnessMethodSubstitutions(
900900
bool isSelfAbstract,
901901
ClassDecl *classWitness) {
902902

903+
auto &ctx = mod->getASTContext();
904+
903905
if (witnessThunkSig.isNull())
904906
return SubstitutionMap();
905907

@@ -909,46 +911,100 @@ getWitnessMethodSubstitutions(
909911
assert(!conformanceRef.isAbstract());
910912
auto conformance = conformanceRef.getConcrete();
911913

914+
auto selfType = conformance->getProtocol()->getSelfInterfaceType();
915+
912916
// If `Self` maps to a bound generic type, this gives us the
913917
// substitutions for the concrete type's generic parameters.
914918
auto baseSubMap = conformance->getSubstitutions(mod);
915919

916920
unsigned baseDepth = 0;
917921
auto *rootConformance = conformance->getRootConformance();
918-
if (auto witnessSig = rootConformance->getGenericSignature())
919-
baseDepth = witnessSig.getGenericParams().back()->getDepth() + 1;
920-
921-
// If the witness has a class-constrained 'Self' generic parameter,
922-
// we have to build a new substitution map that shifts all generic
923-
// parameters down by one.
924-
if (classWitness != nullptr) {
925-
auto *proto = conformance->getProtocol();
926-
auto selfType = proto->getSelfInterfaceType();
927-
928-
auto selfSubMap = SubstitutionMap::getProtocolSubstitutions(
929-
proto, selfType.subst(origSubMap), conformanceRef);
930-
if (baseSubMap.empty()) {
931-
assert(baseDepth == 0);
932-
baseSubMap = selfSubMap;
933-
} else {
934-
baseSubMap = SubstitutionMap::combineSubstitutionMaps(
935-
selfSubMap,
936-
baseSubMap,
937-
CombineSubstitutionMaps::AtDepth,
938-
/*firstDepth=*/1,
939-
/*secondDepth=*/0,
940-
witnessThunkSig);
941-
}
942-
baseDepth += 1;
943-
}
922+
if (auto conformingTypeSig = rootConformance->getGenericSignature())
923+
baseDepth = conformingTypeSig.getGenericParams().back()->getDepth() + 1;
944924

945-
return SubstitutionMap::combineSubstitutionMaps(
946-
baseSubMap,
947-
origSubMap,
948-
CombineSubstitutionMaps::AtDepth,
949-
/*firstDepth=*/baseDepth,
950-
/*secondDepth=*/1,
951-
witnessThunkSig);
925+
// witnessThunkSig begins with the optional class 'Self', followed by the
926+
// generic parameters of the concrete conforming type, followed by the
927+
// generic parameters of the protocol requirement, if any.
928+
//
929+
// - The 'Self' parameter is replaced with the conforming type.
930+
// - The conforming type's generic parameters are replaced by the
931+
// conformance substitutions.
932+
// - The protocol requirement's generic parameters are replaced from the
933+
// substitution map at the call site.
934+
return SubstitutionMap::get(
935+
witnessThunkSig,
936+
[&](SubstitutableType *type) {
937+
auto *paramType = type->castTo<GenericTypeParamType>();
938+
unsigned depth = paramType->getDepth();
939+
940+
if (classWitness != nullptr) {
941+
if (depth == 0) {
942+
assert(paramType->getIndex() == 0);
943+
return selfType.subst(origSubMap);
944+
}
945+
946+
--depth;
947+
}
948+
949+
if (depth < baseDepth) {
950+
paramType = GenericTypeParamType::get(
951+
paramType->isTypeSequence(),
952+
depth, paramType->getIndex(), ctx);
953+
954+
return Type(paramType).subst(baseSubMap);
955+
}
956+
957+
depth = depth - baseDepth + 1;
958+
959+
paramType = GenericTypeParamType::get(
960+
paramType->isTypeSequence(),
961+
depth, paramType->getIndex(), ctx);
962+
return Type(paramType).subst(origSubMap);
963+
},
964+
[&](CanType type, Type substType, ProtocolDecl *proto) {
965+
auto *paramType = type->getRootGenericParam();
966+
unsigned depth = paramType->getDepth();
967+
968+
if (classWitness != nullptr) {
969+
if (depth == 0) {
970+
assert(type->isEqual(paramType));
971+
assert(paramType->getIndex() == 0);
972+
return conformanceRef;
973+
}
974+
975+
--depth;
976+
}
977+
978+
if (depth < baseDepth) {
979+
type = CanType(type.transform([&](Type t) -> Type {
980+
if (t->isEqual(paramType)) {
981+
return GenericTypeParamType::get(
982+
paramType->isTypeSequence(),
983+
depth, paramType->getIndex(), ctx);
984+
}
985+
986+
assert(!t->is<GenericTypeParamType>());
987+
return t;
988+
}));
989+
990+
return baseSubMap.lookupConformance(type, proto);
991+
}
992+
993+
depth = depth - baseDepth + 1;
994+
995+
type = CanType(type.transform([&](Type t) -> Type {
996+
if (t->isEqual(paramType)) {
997+
return GenericTypeParamType::get(
998+
paramType->isTypeSequence(),
999+
depth, paramType->getIndex(), ctx);
1000+
}
1001+
1002+
assert(!t->is<GenericTypeParamType>());
1003+
return t;
1004+
}));
1005+
1006+
return origSubMap.lookupConformance(type, proto);
1007+
});
9521008
}
9531009

9541010
SubstitutionMap
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// RUN: %target-swift-frontend -emit-sil -O %s | %FileCheck %s
2+
3+
public protocol P {}
4+
5+
public protocol Q {
6+
func foo<T: P>(t: T)
7+
func bar<T: P>(t: T)
8+
}
9+
extension Q {
10+
@inline(__always)
11+
public func foo<T: P>(t: T) {
12+
bar(t: t)
13+
}
14+
15+
@_optimize(none)
16+
public func bar<T: P>(t: T) {}
17+
}
18+
19+
public class C<T>: Q {}
20+
21+
// CHECK-LABEL: sil shared [transparent] [thunk] @$s35devirt_class_witness_method_generic1CCyqd__GAA1QA2aEP3bar1tyqd___tAA1PRd__lFTW : $@convention(witness_method: Q) <τ_0_0><τ_1_0 where τ_0_0 : C<τ_1_0>><τ_2_0 where τ_2_0 : P> (@in_guaranteed τ_2_0, @in_guaranteed τ_0_0) -> () {
22+
// CHECK: [[FN:%.*]] = function_ref @$s35devirt_class_witness_method_generic1QPAAE3bar1tyqd___tAA1PRd__lF : $@convention(method) <τ_0_0 where τ_0_0 : Q><τ_1_0 where τ_1_0 : P> (@in_guaranteed τ_1_0, @in_guaranteed τ_0_0) -> ()
23+
// CHECK: apply [[FN]]<τ_0_0, τ_2_0>(%0, %1) : $@convention(method) <τ_0_0 where τ_0_0 : Q><τ_1_0 where τ_1_0 : P> (@in_guaranteed τ_1_0, @in_guaranteed τ_0_0) -> ()

0 commit comments

Comments
 (0)