Skip to content

Commit 1a9ebbd

Browse files
committed
SIL: Type lowering for function values with substituted SIL function types.
After converting a function type to its corresponding SIL type by our usual rules, extract the substituted generic signature and common interface type for all concrete function types that the function could be called as. We don't currently have any use cases for distinguishing types at finer granularity than generic class-constrained/generic unconstrained/not-generic, so to reduce the amount of conversion noise in SIL, generate the most general generic signature possible, by extracting every position with a dependent generic parameter or associated type thereof into a separate generic parameter in the substituted signature, discarding all constraints except for `AnyObject` layout constraints. This way, if `foo<T>(x: (T, T) -> ())` calls `bar<T, U>(x: (T, U) -> ())` and forwards `x` along, the two closures that only vary in genericity don't need a conversion even with substituted function types, because they'll both have the same abstract lowering `<A, B> (A, B) -> ()` with different substitutions.
1 parent 15f216e commit 1a9ebbd

File tree

3 files changed

+233
-2
lines changed

3 files changed

+233
-2
lines changed

lib/SIL/SILFunctionType.cpp

Lines changed: 113 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1059,11 +1059,122 @@ static CanSILFunctionType getSILFunctionType(
10591059
.withIsPseudogeneric(pseudogeneric)
10601060
.withNoEscape(extInfo.isNoEscape());
10611061

1062+
// Extract the abstract generic calling convention of the function into a
1063+
// substituted generic signature for the function type.
1064+
//
1065+
// This signature only needs to consider the general calling convention,
1066+
// so it can reduce away protocol and base class constraints aside from
1067+
// `AnyObject`. We wanto similar-shaped generic function types to remain
1068+
// canonically equivalent, like `(T, U) -> ()`, `(T, T) -> ()`,
1069+
// `(U, T) -> ()` or `(T, T.A) -> ()` when given substitutions that produce
1070+
// the same function types, so we also introduce a new generic argument for
1071+
// each position where we see a dependent type, and canonicalize the order in
1072+
// which we see independent generic arguments.
1073+
//
1074+
bool impliedSignature = false;
1075+
SubstitutionMap substitutions;
1076+
if (TC.Context.LangOpts.EnableSubstSILFunctionTypesForFunctionValues
1077+
// We don't currently use substituted function types for generic function
1078+
// type lowering, though we should for generic methods on classes and
1079+
// protocols.
1080+
&& !genericSig) {
1081+
class SubstFunctionTypeCollector {
1082+
public:
1083+
TypeConverter &TC;
1084+
const bool mapReplacementsOutOfContext;
1085+
1086+
SmallVector<GenericTypeParamType *, 4> substGenericParams;
1087+
SmallVector<Requirement, 4> substRequirements;
1088+
SmallVector<Type, 4> substReplacements;
1089+
1090+
SubstFunctionTypeCollector(TypeConverter &TC,
1091+
bool mapReplacementsOutOfContext)
1092+
: TC(TC), mapReplacementsOutOfContext(mapReplacementsOutOfContext) {}
1093+
SubstFunctionTypeCollector(const SubstFunctionTypeCollector &) = delete;
1094+
1095+
// TypeSubstitutionFn
1096+
Type operator()(SubstitutableType *t) {
1097+
ArchetypeType *archetype = dyn_cast<ArchetypeType>(t);
1098+
if (!archetype)
1099+
archetype = TC.getCurGenericContext()->getGenericEnvironment()
1100+
->mapTypeIntoContext(t)
1101+
->castTo<ArchetypeType>();
1102+
1103+
// Replace every dependent type we see with a fresh type variable in
1104+
// the substituted signature.
1105+
auto paramIndex = substGenericParams.size();
1106+
auto param = CanGenericTypeParamType::get(0, paramIndex, TC.Context);
1107+
1108+
substGenericParams.push_back(param);
1109+
Type replacementTy = t;
1110+
if (mapReplacementsOutOfContext) {
1111+
replacementTy = t->mapTypeOutOfContext();
1112+
}
1113+
substReplacements.push_back(replacementTy);
1114+
1115+
// Preserve the AnyObject constraint, if any, on the archetype in the
1116+
// generic signature.
1117+
if (archetype->requiresClass()) {
1118+
substRequirements.push_back(Requirement(RequirementKind::Layout,param,
1119+
LayoutConstraint::getLayoutConstraint(LayoutConstraintKind::Class)));
1120+
}
1121+
1122+
return param;
1123+
}
1124+
1125+
// LookupConformanceFn
1126+
Optional<ProtocolConformanceRef> operator()(CanType dependentType,
1127+
Type conformingReplacementType,
1128+
ProtocolDecl *conformedProtocol) {
1129+
// We should only substitute with other dependent types, so that an
1130+
// abstract conformance ref is sufficient.
1131+
assert(conformingReplacementType->isTypeParameter());
1132+
return ProtocolConformanceRef(conformedProtocol);
1133+
}
1134+
};
1135+
1136+
SubstFunctionTypeCollector collector(TC,
1137+
substFnInterfaceType->hasTypeParameter());
1138+
auto contextSig = TC.getCurGenericContext();
1139+
1140+
auto collectSubstitutions = [&](CanType t) -> CanType {
1141+
if (t->hasTypeParameter()) {
1142+
t = contextSig->getGenericEnvironment()
1143+
->mapTypeIntoContext(t)
1144+
->getCanonicalType(contextSig);
1145+
}
1146+
1147+
return CanType(t.subst(collector, collector));
1148+
};
1149+
1150+
for (auto &input : inputs) {
1151+
input = input.getWithInterfaceType(
1152+
collectSubstitutions(input.getInterfaceType()));
1153+
}
1154+
for (auto &yield : yields) {
1155+
yield = yield.getWithInterfaceType(
1156+
collectSubstitutions(yield.getInterfaceType()));
1157+
}
1158+
for (auto &result : results) {
1159+
result = result.getWithInterfaceType(
1160+
collectSubstitutions(result.getInterfaceType()));
1161+
}
1162+
1163+
if (!collector.substGenericParams.empty()) {
1164+
genericSig = GenericSignature::get(collector.substGenericParams,
1165+
collector.substRequirements)
1166+
->getCanonicalSignature();
1167+
substitutions = SubstitutionMap::get(genericSig,
1168+
collector.substReplacements,
1169+
ArrayRef<ProtocolConformanceRef>());
1170+
impliedSignature = true;
1171+
}
1172+
}
10621173

10631174
return SILFunctionType::get(genericSig, silExtInfo, coroutineKind,
10641175
calleeConvention, inputs, yields,
10651176
results, errorResult,
1066-
SubstitutionMap(), false,
1177+
substitutions, impliedSignature,
10671178
TC.Context, witnessMethodConformance);
10681179
}
10691180

@@ -3056,7 +3167,7 @@ SILFunctionType::withSubstitutions(SubstitutionMap subs) const {
30563167
getExtInfo(), getCoroutineKind(),
30573168
getCalleeConvention(),
30583169
getParameters(), getYields(), getResults(),
3059-
getErrorResult(),
3170+
getOptionalErrorResult(),
30603171
subs, isGenericSignatureImplied(),
30613172
const_cast<SILFunctionType*>(this)->getASTContext());
30623173
}

lib/SIL/TypeLowering.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1547,6 +1547,8 @@ CanType TypeConverter::computeLoweredRValueType(AbstractionPattern origType,
15471547
// - types are turned into their unbridged equivalents, depending
15481548
// on the abstract CC
15491549
// - ownership conventions are deduced
1550+
// - a minimal substituted generic signature is extracted to represent
1551+
// possible ABI-compatible substitutions
15501552
if (auto substFnType = dyn_cast<AnyFunctionType>(substType)) {
15511553
// If the formal type uses a C convention, it is not formally
15521554
// abstractable, and it may be subject to implicit bridging.
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
// RUN: %target-swift-emit-silgen -enable-subst-sil-function-types-for-function-values %s | %FileCheck %s
2+
3+
4+
// Similarly-abstract generic signatures should share an unsubstituted type
5+
// even in different generic contexts
6+
7+
// CHECK-LABEL: sil {{.*}}1a{{.*}} : $@convention(thin) <T, U> (@noescape @callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <T, U>) -> ()
8+
func a<T, U>(_ x: (T) -> U) {}
9+
10+
// CHECK-LABEL: sil {{.*}}1b{{.*}} : $@convention(thin) <T, U> (@noescape @callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <U, T>) -> ()
11+
func b<T, U>(_ x: (U) -> T) {}
12+
13+
// CHECK-LABEL: sil {{.*}}1c{{.*}} : $@convention(thin) <T, U, V> (@noescape @callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <V, T>, @in_guaranteed U) -> ()
14+
func c<T, U, V>(_ x: (V) -> T, _: U) {}
15+
16+
// CHECK-LABEL: sil {{.*}}003Hca{{.*}} : $@convention(thin) <T> (@noescape @callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <T, T>) -> ()
17+
func ç<T>(_ x: (T) -> T) {}
18+
19+
20+
// ...including unconstrained associated types
21+
22+
protocol P {
23+
associatedtype A
24+
}
25+
26+
// CHECK-LABEL: sil {{.*}}1d{{.*}} : $@convention(thin) <T where T : P> (@noescape @callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <T, T.A>) -> ()
27+
func d<T: P>(_ x: (T) -> T.A) {}
28+
29+
// CHECK-LABEL: sil {{.*}}1e{{.*}} : $@convention(thin) <T where T : P> (@noescape @callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <T.A, T>) -> ()
30+
func e<T: P>(_ x: (T.A) -> T) {}
31+
32+
33+
// Preserve class constraints, because they're less abstract for layout and
34+
// calling convention purposes than unconstrained types
35+
36+
// CHECK-LABEL: sil {{.*}}1f{{.*}} : $@convention(thin) <T, U where T : AnyObject> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject> in (@guaranteed τ_0_0) -> @out τ_0_1 for <T, U>) -> ()
37+
func f<T: AnyObject, U>(_ x: (T) -> U) {}
38+
39+
// CHECK-LABEL: sil {{.*}}1g{{.*}} : $@convention(thin) <T, U where T : AnyObject> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_1 : AnyObject> in (@in_guaranteed τ_0_0) -> @owned τ_0_1 for <U, T>) -> ()
40+
func g<T: AnyObject, U>(_ x: (U) -> T) {}
41+
42+
// CHECK-LABEL: sil {{.*}}1h{{.*}} : $@convention(thin) <T, U where T : AnyObject, U : AnyObject> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject, τ_0_1 : AnyObject> in (@guaranteed τ_0_0) -> @owned τ_0_1 for <T, U>) -> ()
43+
func h<T: AnyObject, U: AnyObject>(_ x: (T) -> U) {}
44+
45+
// CHECK-LABEL: sil {{.*}}1i{{.*}} : $@convention(thin) <T, U where T : AnyObject, U : AnyObject> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject, τ_0_1 : AnyObject> in (@guaranteed τ_0_0) -> @owned τ_0_1 for <U, T>) -> ()
46+
func i<T: AnyObject, U: AnyObject>(_ x: (U) -> T) {}
47+
48+
49+
// Indirect class constraints
50+
51+
protocol PC: AnyObject { }
52+
53+
// CHECK-LABEL: sil {{.*}}1j{{.*}} : $@convention(thin) <T, U where T : PC> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject> in (@guaranteed τ_0_0) -> @out τ_0_1 for <T, U>) -> ()
54+
func j<T: PC, U>(_ x: (T) -> U) {}
55+
56+
// CHECK-LABEL: sil {{.*}}1k{{.*}} : $@convention(thin) <T, U where T : PC> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_1 : AnyObject> in (@in_guaranteed τ_0_0) -> @owned τ_0_1 for <U, T>) -> ()
57+
func k<T: PC, U>(_ x: (U) -> T) {}
58+
59+
// CHECK-LABEL: sil {{.*}}1l{{.*}} : $@convention(thin) <T, U where T : PC, U : PC> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject, τ_0_1 : AnyObject> in (@guaranteed τ_0_0) -> @owned τ_0_1 for <T, U>) -> ()
60+
func l<T: PC, U: PC>(_ x: (T) -> U) {}
61+
62+
// CHECK-LABEL: sil {{.*}}1m{{.*}} : $@convention(thin) <T, U where T : PC, U : PC> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject, τ_0_1 : AnyObject> in (@guaranteed τ_0_0) -> @owned τ_0_1 for <U, T>) -> ()
63+
func m<T: PC, U: PC>(_ x: (U) -> T) {}
64+
65+
// CHECK-LABEL: sil {{.*}}1n{{.*}} : $@convention(thin) <T where T : P, T : PC> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject> in (@guaranteed τ_0_0) -> @out τ_0_1 for <T, T.A>) -> ()
66+
func n<T: P & PC>(_ x: (T) -> T.A) {}
67+
68+
69+
// Superclass constraints
70+
71+
class Base {}
72+
73+
func o<T: Base, U> (_ x: (T) -> U) {}
74+
75+
76+
// Indirect constraints by associated type or protocol
77+
78+
protocol PCAO: AnyObject {
79+
associatedtype A
80+
}
81+
82+
protocol POAC {
83+
associatedtype A: AnyObject
84+
}
85+
86+
protocol PCAC: AnyObject {
87+
associatedtype A: AnyObject
88+
}
89+
90+
// CHECK-LABEL: sil {{.*}}1p{{.*}} : $@convention(thin) <T where T : PCAO> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject> in (@guaranteed τ_0_0) -> @out τ_0_1 for <T, T.A>) -> ()
91+
func p<T: PCAO> (_ x: (T) -> T.A) {}
92+
// CHECK-LABEL: sil {{.*}}1q{{.*}} : $@convention(thin) <T where T : POAC> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_1 : AnyObject> in (@in_guaranteed τ_0_0) -> @owned τ_0_1 for <T, T.A>) -> ()
93+
func q<T: POAC> (_ x: (T) -> T.A) {}
94+
// CHECK-LABEL: sil {{.*}}1r{{.*}} : $@convention(thin) <T where T : PCAC> (@noescape @callee_guaranteed <τ_0_0, τ_0_1 where τ_0_0 : AnyObject, τ_0_1 : AnyObject> in (@guaranteed τ_0_0) -> @owned τ_0_1 for <T, T.A>) -> ()
95+
func r<T: PCAC> (_ x: (T) -> T.A) {}
96+
97+
98+
// Structural positions
99+
100+
struct S<T, U> {}
101+
102+
// CHECK-LABEL: sil {{.*}}1t{{.*}} : $@convention(thin) <T, U where U : AnyObject> (@noescape @callee_guaranteed <τ_0_0, τ_0_1, τ_0_2, τ_0_3 where τ_0_1 : AnyObject, τ_0_3 : AnyObject> in (S<τ_0_0, τ_0_1>) -> (@out τ_0_2, @owned τ_0_3) for <T, U, T, U>) -> ()
103+
func t<T, U: AnyObject>(_: (S<T, U>) -> (T, U)) {}
104+
105+
// CHECK-LABEL: sil {{.*}}1u{{.*}} : $@convention(thin) <T> (@noescape @callee_guaranteed <τ_0_0, τ_0_1, τ_0_2> in (S<τ_0_0, τ_0_1>) -> @out τ_0_2 for <T, T, T>) -> ()
106+
func u<T>(_: (S<T, T>) -> T) {}
107+
108+
109+
class C<T, U> {}
110+
111+
// CHECK-LABEL: sil {{.*}}1v{{.*}} : $@convention(thin) <T, U where U : AnyObject> (@noescape @callee_guaranteed <τ_0_0, τ_0_1, τ_0_2, τ_0_3 where τ_0_1 : AnyObject, τ_0_3 : AnyObject> in (@guaranteed C<τ_0_0, τ_0_1>) -> (@out τ_0_2, @owned τ_0_3) for <T, U, T, U>) -> ()
112+
func v<T, U: AnyObject>(_: (C<T, U>) -> (T, U)) {}
113+
114+
// CHECK-LABEL: sil {{.*}}1w{{.*}} : $@convention(thin) <T> (@noescape @callee_guaranteed <τ_0_0, τ_0_1, τ_0_2> in (@guaranteed C<τ_0_0, τ_0_1>) -> @out τ_0_2 for <T, T, T>) -> ()
115+
func w<T>(_: (C<T, T>) -> T) {}
116+
117+
// CHECK-LABEL: sil {{.*}}1x{{.*}} : $@convention(thin) <T, U, V where V : C<T, U>> (@noescape @callee_guaranteed <τ_0_0 where τ_0_0 : AnyObject> in (@guaranteed τ_0_0) -> () for <V>) -> ()
118+
func x<T, U, V: C<T, U>>(_: (V) -> Void) {}

0 commit comments

Comments
 (0)