Skip to content

Commit 351c94d

Browse files
committed
SIL: Distinguish "compatible convention" and "compatible representation" function conversions.
We want to be able to use different representations for function types with otherwise compatible calling conventions. Distinguish these concepts in the `checkForABIDifferences` SIL APIs, so that we correctly handle representation-only conversions, which can be handled by `convert_function`, from full reabstractions, making sure to note that the representation-only case is not transitive for function arguments, since a function that takes a function with a representation change needs a thunk to change the argument's representation.
1 parent 0926d23 commit 351c94d

File tree

6 files changed

+158
-28
lines changed

6 files changed

+158
-28
lines changed

include/swift/SIL/TypeLowering.h

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -965,10 +965,27 @@ class TypeConverter {
965965
#endif
966966

967967
enum class ABIDifference : uint8_t {
968-
// No ABI differences, function can be trivially bitcast to result type.
969-
Trivial,
968+
// Types have compatible calling conventions and representations, so can
969+
// be trivially bitcast.
970+
CompatibleRepresentation,
971+
972+
// No convention differences, function can be cast via `convert_function`
973+
// without a thunk.
974+
//
975+
// There may still be a representation difference between values of the
976+
// compared function types. This means that, if two function types
977+
// have a matching argument or return of function type with
978+
// `SameCallingConvention`, then the outer function types may not themselves
979+
// have the `SameCallingConvention` because they need a thunk to convert
980+
// the inner function value representation.
981+
CompatibleCallingConvention,
982+
970983
// Representation difference requires thin-to-thick conversion.
971-
ThinToThick,
984+
CompatibleRepresentation_ThinToThick,
985+
// Function types have the `SameCallingConvention` but additionally need
986+
// a thin-to-thick conversion.
987+
CompatibleCallingConvention_ThinToThick,
988+
972989
// Non-trivial difference requires thunk.
973990
NeedsThunk
974991
};

lib/SIL/TypeLowering.cpp

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2440,14 +2440,14 @@ TypeConverter::checkForABIDifferences(SILModule &M,
24402440
// If the types are identical and there was no optionality change,
24412441
// we're done.
24422442
if (type1 == type2 && !optionalityChange)
2443-
return ABIDifference::Trivial;
2443+
return ABIDifference::CompatibleRepresentation;
24442444

24452445
// Classes, class-constrained archetypes, and pure-ObjC existential types
24462446
// all have single retainable pointer representation; optionality change
24472447
// is allowed.
24482448
if (type1.getASTType()->satisfiesClassConstraint() &&
24492449
type2.getASTType()->satisfiesClassConstraint())
2450-
return ABIDifference::Trivial;
2450+
return ABIDifference::CompatibleRepresentation;
24512451

24522452
// Function parameters are ABI compatible if their differences are
24532453
// trivial.
@@ -2470,7 +2470,7 @@ TypeConverter::checkForABIDifferences(SILModule &M,
24702470
if (meta1->getRepresentation() == meta2->getRepresentation() &&
24712471
(!optionalityChange ||
24722472
meta1->getRepresentation() == MetatypeRepresentation::Thick))
2473-
return ABIDifference::Trivial;
2473+
return ABIDifference::CompatibleRepresentation;
24742474
}
24752475
}
24762476

@@ -2483,7 +2483,7 @@ TypeConverter::checkForABIDifferences(SILModule &M,
24832483
if (auto meta2 = type2.getAs<ExistentialMetatypeType>()) {
24842484
if (meta1->getRepresentation() == meta2->getRepresentation() &&
24852485
meta1->getRepresentation() == MetatypeRepresentation::ObjC)
2486-
return ABIDifference::Trivial;
2486+
return ABIDifference::CompatibleRepresentation;
24872487
}
24882488
}
24892489

@@ -2498,12 +2498,12 @@ TypeConverter::checkForABIDifferences(SILModule &M,
24982498
if (checkForABIDifferences(M,
24992499
type1.getTupleElementType(i),
25002500
type2.getTupleElementType(i))
2501-
!= ABIDifference::Trivial)
2501+
!= ABIDifference::CompatibleRepresentation)
25022502
return ABIDifference::NeedsThunk;
25032503
}
25042504

25052505
// Tuple lengths and elements match
2506-
return ABIDifference::Trivial;
2506+
return ABIDifference::CompatibleRepresentation;
25072507
}
25082508
}
25092509
}
@@ -2517,10 +2517,19 @@ TypeConverter::ABIDifference
25172517
TypeConverter::checkFunctionForABIDifferences(SILModule &M,
25182518
SILFunctionType *fnTy1,
25192519
SILFunctionType *fnTy2) {
2520+
// For now, only differentiate representation from calling convention when
2521+
// staging in substituted function types.
2522+
//
2523+
// We might still want to conditionalize this behavior even after we commit
2524+
// substituted function types, to avoid bloating
2525+
// IR for platforms that don't differentiate function type representations.
2526+
bool DifferentFunctionTypesHaveDifferentRepresentation
2527+
= Context.LangOpts.EnableSubstSILFunctionTypesForFunctionValues;
2528+
25202529
// Fast path -- if both functions were unwrapped from a CanSILFunctionType,
25212530
// we might have pointer equality here.
25222531
if (fnTy1 == fnTy2)
2523-
return ABIDifference::Trivial;
2532+
return ABIDifference::CompatibleRepresentation;
25242533

25252534
if (fnTy1->getParameters().size() != fnTy2->getParameters().size())
25262535
return ABIDifference::NeedsThunk;
@@ -2548,7 +2557,7 @@ TypeConverter::checkFunctionForABIDifferences(SILModule &M,
25482557
result1.getSILStorageType(M, fnTy1),
25492558
result2.getSILStorageType(M, fnTy2),
25502559
/*thunk iuos*/ fnTy1->getLanguage() == SILFunctionLanguage::Swift)
2551-
!= ABIDifference::Trivial)
2560+
!= ABIDifference::CompatibleRepresentation)
25522561
return ABIDifference::NeedsThunk;
25532562
}
25542563

@@ -2563,7 +2572,7 @@ TypeConverter::checkFunctionForABIDifferences(SILModule &M,
25632572
yield1.getSILStorageType(M, fnTy1),
25642573
yield2.getSILStorageType(M, fnTy2),
25652574
/*thunk iuos*/ fnTy1->getLanguage() == SILFunctionLanguage::Swift)
2566-
!= ABIDifference::Trivial)
2575+
!= ABIDifference::CompatibleRepresentation)
25672576
return ABIDifference::NeedsThunk;
25682577
}
25692578

@@ -2580,7 +2589,7 @@ TypeConverter::checkFunctionForABIDifferences(SILModule &M,
25802589
error1.getSILStorageType(M, fnTy1),
25812590
error2.getSILStorageType(M, fnTy2),
25822591
/*thunk iuos*/ fnTy1->getLanguage() == SILFunctionLanguage::Swift)
2583-
!= ABIDifference::Trivial)
2592+
!= ABIDifference::CompatibleRepresentation)
25842593
return ABIDifference::NeedsThunk;
25852594
}
25862595

@@ -2592,26 +2601,34 @@ TypeConverter::checkFunctionForABIDifferences(SILModule &M,
25922601

25932602
// Parameters are contravariant and our relation is not symmetric, so
25942603
// make sure to flip the relation around.
2595-
std::swap(param1, param2);
2596-
25972604
if (checkForABIDifferences(M,
2598-
param1.getSILStorageType(M, fnTy1),
25992605
param2.getSILStorageType(M, fnTy2),
2606+
param1.getSILStorageType(M, fnTy1),
26002607
/*thunk iuos*/ fnTy1->getLanguage() == SILFunctionLanguage::Swift)
2601-
!= ABIDifference::Trivial)
2608+
!= ABIDifference::CompatibleRepresentation)
26022609
return ABIDifference::NeedsThunk;
26032610
}
26042611

26052612
auto rep1 = fnTy1->getRepresentation(), rep2 = fnTy2->getRepresentation();
26062613
if (rep1 != rep2) {
26072614
if (rep1 == SILFunctionTypeRepresentation::Thin &&
2608-
rep2 == SILFunctionTypeRepresentation::Thick)
2609-
return ABIDifference::ThinToThick;
2615+
rep2 == SILFunctionTypeRepresentation::Thick) {
2616+
if (DifferentFunctionTypesHaveDifferentRepresentation) {
2617+
// FIXME: check whether the representations are compatible modulo
2618+
// context
2619+
return ABIDifference::CompatibleCallingConvention_ThinToThick;
2620+
} else {
2621+
return ABIDifference::CompatibleRepresentation_ThinToThick;
2622+
}
2623+
}
26102624

26112625
return ABIDifference::NeedsThunk;
26122626
}
26132627

2614-
return ABIDifference::Trivial;
2628+
if (DifferentFunctionTypesHaveDifferentRepresentation)
2629+
return ABIDifference::CompatibleCallingConvention;
2630+
else
2631+
return ABIDifference::CompatibleRepresentation;
26152632
}
26162633

26172634
CanSILBoxType

lib/SILGen/SILGenExpr.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1463,7 +1463,8 @@ static ManagedValue convertCFunctionSignature(SILGenFunction &SGF,
14631463
// ABI-compatible, since we can't emit a thunk.
14641464
switch (SGF.SGM.Types.checkForABIDifferences(SGF.SGM.M,
14651465
loweredResultTy, loweredDestTy)){
1466-
case TypeConverter::ABIDifference::Trivial:
1466+
case TypeConverter::ABIDifference::CompatibleRepresentation:
1467+
case TypeConverter::ABIDifference::CompatibleCallingConvention:
14671468
result = fnEmitter();
14681469
assert(result.getType() == loweredResultTy);
14691470

@@ -1482,7 +1483,8 @@ static ManagedValue convertCFunctionSignature(SILGenFunction &SGF,
14821483
result = SGF.emitUndef(loweredDestTy);
14831484
break;
14841485

1485-
case TypeConverter::ABIDifference::ThinToThick:
1486+
case TypeConverter::ABIDifference::CompatibleCallingConvention_ThinToThick:
1487+
case TypeConverter::ABIDifference::CompatibleRepresentation_ThinToThick:
14861488
llvm_unreachable("Cannot have thin to thick conversion here");
14871489
}
14881490

lib/SILGen/SILGenPoly.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ ManagedValue Transform::transform(ManagedValue v,
404404
// If the conversion is trivial, just cast.
405405
if (SGF.SGM.Types.checkForABIDifferences(SGF.SGM.M,
406406
v.getType(), loweredResultTy)
407-
== TypeConverter::ABIDifference::Trivial) {
407+
== TypeConverter::ABIDifference::CompatibleRepresentation) {
408408
if (v.getType().isAddress())
409409
return SGF.B.createUncheckedAddrCast(Loc, v, loweredResultTy);
410410
return SGF.B.createUncheckedBitCast(Loc, v, loweredResultTy);

lib/SILGen/SILGenType.cpp

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,24 @@ SILGenModule::emitVTableMethod(ClassDecl *theClass,
125125
// The override member type is semantically a subtype of the base
126126
// member type. If the override is ABI compatible, we do not need
127127
// a thunk.
128-
if (doesNotHaveGenericRequirementDifference && !baseLessVisibleThanDerived &&
129-
M.Types.checkFunctionForABIDifferences(M,
130-
derivedInfo.SILFnType,
131-
overrideInfo.SILFnType) ==
132-
TypeConverter::ABIDifference::Trivial)
128+
bool compatibleCallingConvention;
129+
switch (M.Types.checkFunctionForABIDifferences(M,
130+
derivedInfo.SILFnType,
131+
overrideInfo.SILFnType)) {
132+
case TypeConverter::ABIDifference::CompatibleCallingConvention:
133+
case TypeConverter::ABIDifference::CompatibleRepresentation:
134+
compatibleCallingConvention = true;
135+
break;
136+
case TypeConverter::ABIDifference::NeedsThunk:
137+
compatibleCallingConvention = false;
138+
break;
139+
case TypeConverter::ABIDifference::CompatibleCallingConvention_ThinToThick:
140+
case TypeConverter::ABIDifference::CompatibleRepresentation_ThinToThick:
141+
llvm_unreachable("shouldn't be thick methods");
142+
}
143+
if (doesNotHaveGenericRequirementDifference
144+
&& !baseLessVisibleThanDerived
145+
&& compatibleCallingConvention)
133146
return SILVTable::Entry(base, implFn, implKind);
134147

135148
// Generate the thunk name.
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
// RUN: %target-swift-frontend -emit-silgen -disable-availability-checking -module-name main -enable-subst-sil-function-types-for-function-values %s | %FileCheck %s
2+
3+
func generic<T, U>(_ f: @escaping (T) -> U) -> (T) -> U { return f }
4+
5+
// CHECK-LABEL: sil {{.*}}4main{{.*}}11sameGeneric
6+
func sameGeneric<X, Y, Z>(_: X, _: Y, _ f: @escaping (Z) -> Z) -> (Z) -> Z {
7+
// CHECK: bb0({{.*}}, [[F:%[0-9]+]] : @guaranteed $@callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <Z, Z>
8+
// Similarly generic types should be directly substitutable
9+
// CHECK: [[GENERIC:%.*]] = function_ref @{{.*}}4main{{.*}}7generic
10+
// CHECK: [[RET:%.*]] = apply [[GENERIC]]<Z, Z>([[F]])
11+
// CHECK: return [[RET]]
12+
return generic(f)
13+
}
14+
15+
// CHECK-LABEL: sil {{.*}}4main{{.*}}16concreteIndirect
16+
func concreteIndirect(_ f: @escaping (Any) -> Any) -> (Any) -> Any {
17+
// CHECK: bb0([[F:%[0-9]+]] : @guaranteed $@callee_guaranteed (@in_guaranteed Any) -> @out Any)
18+
// Any is passed indirectly, but is a concrete type, so we need to convert
19+
// to the generic abstraction level
20+
// CHECK: [[F2:%.*]] = copy_value [[F]]
21+
// CHECK: [[GENERIC_F:%.*]] = convert_function [[F2]] : {{.*}} to $@callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <Any, Any>
22+
// CHECK: [[GENERIC:%.*]] = function_ref @{{.*}}4main{{.*}}7generic
23+
// CHECK: [[GENERIC_RET:%.*]] = apply [[GENERIC]]<Any, Any>([[GENERIC_F]])
24+
// CHECK: [[RET:%.*]] = convert_function [[GENERIC_RET]] : {{.*}}
25+
// CHECK: return [[RET]]
26+
return generic(f)
27+
}
28+
29+
// CHECK-LABEL: sil {{.*}}4main{{.*}}14concreteDirect
30+
func concreteDirect(_ f: @escaping (Int) -> String) -> (Int) -> String {
31+
// CHECK: bb0([[F:%[0-9]+]] : @guaranteed $@callee_guaranteed (Int) -> @owned String):
32+
// Int and String are passed and returned directly, so we need both
33+
// thunking and conversion to the substituted form
34+
// CHECK: [[F2:%.*]] = copy_value [[F]]
35+
// CHECK: [[REABSTRACT_F:%.*]] = partial_apply {{.*}}([[F2]])
36+
// CHECK: [[GENERIC_F:%.*]] = convert_function [[REABSTRACT_F]] : {{.*}} to $@callee_guaranteed <τ_0_0, τ_0_1> in (@in_guaranteed τ_0_0) -> @out τ_0_1 for <Int, String>
37+
// CHECK: [[GENERIC:%.*]] = function_ref @{{.*}}4main{{.*}}7generic
38+
// CHECK: [[GENERIC_RET:%.*]] = apply [[GENERIC]]<Int, String>([[GENERIC_F]])
39+
// CHECK: [[REABSTRACT_RET:%.*]] = convert_function [[GENERIC_RET]] : {{.*}}
40+
// CHECK: [[RET:%.*]] = partial_apply {{.*}}([[REABSTRACT_RET]])
41+
// CHECK: return [[RET]]
42+
43+
return generic(f)
44+
}
45+
46+
func genericTakesFunction<T, U>(
47+
_ f: @escaping ((T) -> U) -> (T) -> U
48+
) -> ((T) -> U) -> (T) -> U { return f }
49+
50+
func sameGenericTakesFunction<T>(
51+
_ f: @escaping ((T) -> T) -> (T) -> T
52+
) -> ((T) -> T) -> (T) -> T {
53+
return genericTakesFunction(f)
54+
}
55+
56+
// CHECK-LABEL: sil {{.*}}4main29concreteIndirectTakesFunction
57+
func concreteIndirectTakesFunction(
58+
_ f: @escaping ((Any) -> Any) -> (Any) -> Any
59+
) -> ((Any) -> Any) -> (Any) -> Any {
60+
// CHECK: bb0([[F:%[0-9]+]] : @guaranteed $@callee_guaranteed
61+
62+
// Calling convention matches callee, but the representation of the argument
63+
// to `f` needs to change, so we still have to thunk
64+
// CHECK: [[F2:%.*]] = copy_value [[F]]
65+
// CHECK: [[REABSTRACT_F:%.*]] = partial_apply {{.*}}([[F2]])
66+
// CHECK: [[GENERIC_F:%.*]] = convert_function [[REABSTRACT_F]]
67+
// CHECK: [[GENERIC:%.*]] = function_ref @{{.*}}4main{{.*}}20genericTakesFunction
68+
// CHECK: [[GENERIC_RET:%.*]] = apply [[GENERIC]]<Any, Any>([[GENERIC_F]])
69+
// CHECK: [[REABSTRACT_RET:%.*]] = convert_function [[GENERIC_RET]] : {{.*}}
70+
// CHECK: [[RET:%.*]] = partial_apply {{.*}}([[REABSTRACT_RET]])
71+
// CHECK: return [[RET]]
72+
return genericTakesFunction(f)
73+
}
74+
75+
func concreteDirectTakesFunction(
76+
_ f: @escaping ((Int) -> String) -> (Int) -> String
77+
) -> ((Int) -> String) -> (Int) -> String {
78+
// Int and String are passed and returned directly, so we need both
79+
// thunking and conversion to the substituted form
80+
return genericTakesFunction(f)
81+
}

0 commit comments

Comments
 (0)