Skip to content

Commit ffbc65c

Browse files
authored
Merge pull request #68405 from slavapestov/magical-vanishing-tuple-conformances
Magical vanishing tuple conformances
2 parents 80e43fb + bd2a020 commit ffbc65c

File tree

8 files changed

+170
-39
lines changed

8 files changed

+170
-39
lines changed

include/swift/AST/ProtocolConformance.h

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -299,23 +299,23 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
299299
/// be satisfied.
300300
ArrayRef<Requirement> getConditionalRequirements() const;
301301

302-
/// Substitute the conforming type and produce a ProtocolConformance that
302+
/// Substitute the conforming type and produce a ProtocolConformanceRef that
303303
/// applies to the substituted type.
304-
ProtocolConformance *subst(SubstitutionMap subMap,
305-
SubstOptions options = llvm::None) const;
304+
ProtocolConformanceRef subst(SubstitutionMap subMap,
305+
SubstOptions options = llvm::None) const;
306306

307-
/// Substitute the conforming type and produce a ProtocolConformance that
307+
/// Substitute the conforming type and produce a ProtocolConformanceRef that
308308
/// applies to the substituted type.
309-
ProtocolConformance *subst(TypeSubstitutionFn subs,
310-
LookupConformanceFn conformances,
311-
SubstOptions options = llvm::None) const;
309+
ProtocolConformanceRef subst(TypeSubstitutionFn subs,
310+
LookupConformanceFn conformances,
311+
SubstOptions options = llvm::None) const;
312312

313-
/// Substitute the conforming type and produce a ProtocolConformance that
313+
/// Substitute the conforming type and produce a ProtocolConformanceRef that
314314
/// applies to the substituted type.
315315
///
316316
/// This function should generally not be used outside of the substitution
317317
/// subsystem.
318-
ProtocolConformance *subst(InFlightSubstitution &IFS) const;
318+
ProtocolConformanceRef subst(InFlightSubstitution &IFS) const;
319319

320320
SWIFT_DEBUG_DUMP;
321321
void dump(llvm::raw_ostream &out, unsigned indent = 0) const;

lib/AST/ASTContext.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2731,6 +2731,13 @@ ASTContext::getSpecializedConformance(Type type,
27312731
if (auto result = specializedConformances.FindNodeOrInsertPos(id, insertPos))
27322732
return result;
27332733

2734+
// Vanishing tuple conformances must be handled by the caller.
2735+
if (isa<BuiltinTupleDecl>(generic->getDeclContext()->getSelfNominalTypeDecl())) {
2736+
assert(type->is<TupleType>() && "Vanishing tuple substitution is not "
2737+
"here. Did you mean to use ProtocolConformanceRef::subst() "
2738+
"instead?");
2739+
}
2740+
27342741
// Build a new specialized conformance.
27352742
auto result
27362743
= new (*this, arena) SpecializedProtocolConformance(type, generic,

lib/AST/ProtocolConformance.cpp

Lines changed: 77 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "swift/AST/InFlightSubstitution.h"
2626
#include "swift/AST/LazyResolver.h"
2727
#include "swift/AST/Module.h"
28+
#include "swift/AST/PackConformance.h"
2829
#include "swift/AST/TypeCheckRequests.h"
2930
#include "swift/AST/Types.h"
3031
#include "swift/Basic/Statistic.h"
@@ -918,63 +919,104 @@ bool ProtocolConformance::isVisibleFrom(const DeclContext *dc) const {
918919
return true;
919920
}
920921

921-
ProtocolConformance *
922+
ProtocolConformanceRef
922923
ProtocolConformance::subst(SubstitutionMap subMap,
923924
SubstOptions options) const {
924925
InFlightSubstitutionViaSubMap IFS(subMap, options);
925926
return subst(IFS);
926927
}
927928

928-
ProtocolConformance *
929+
ProtocolConformanceRef
929930
ProtocolConformance::subst(TypeSubstitutionFn subs,
930931
LookupConformanceFn conformances,
931932
SubstOptions options) const {
932933
InFlightSubstitution IFS(subs, conformances, options);
933934
return subst(IFS);
934935
}
935936

936-
ProtocolConformance *
937+
/// Check if the replacement is a one-element pack with a scalar type.
938+
static bool isVanishingTupleConformance(
939+
RootProtocolConformance *generic,
940+
SubstitutionMap substitutions) {
941+
if (!isa<BuiltinTupleDecl>(generic->getDeclContext()->getSelfNominalTypeDecl()))
942+
return false;
943+
944+
auto replacementTypes = substitutions.getReplacementTypes();
945+
assert(replacementTypes.size() == 1);
946+
auto packType = replacementTypes[0]->castTo<PackType>();
947+
948+
return (packType->getNumElements() == 1 &&
949+
!packType->getElementTypes()[0]->is<PackExpansionType>());
950+
}
951+
952+
/// Don't form a tuple conformance if the substituted type is unwrapped
953+
/// from a one-element tuple.
954+
///
955+
/// That is, [(repeat each T): P] ⊗ {each T := Pack{U};
956+
/// [each T: P]: Pack{ [U: P] }}
957+
/// => [U: P]
958+
static ProtocolConformanceRef unwrapVanishingTupleConformance(
959+
SubstitutionMap substitutions) {
960+
auto conformances = substitutions.getConformances();
961+
assert(conformances.size() == 1);
962+
assert(conformances[0].isPack());
963+
auto packConformance = conformances[0].getPack();
964+
965+
assert(packConformance->getPatternConformances().size() == 1);
966+
return packConformance->getPatternConformances()[0];
967+
}
968+
969+
ProtocolConformanceRef
937970
ProtocolConformance::subst(InFlightSubstitution &IFS) const {
971+
auto *mutableThis = const_cast<ProtocolConformance *>(this);
972+
938973
switch (getKind()) {
939974
case ProtocolConformanceKind::Normal: {
940975
auto origType = getType();
941976
if (!origType->hasTypeParameter() &&
942977
!origType->hasArchetype())
943-
return const_cast<ProtocolConformance *>(this);
978+
return ProtocolConformanceRef(mutableThis);
944979

945980
auto substType = origType.subst(IFS);
946981
if (substType->isEqual(origType))
947-
return const_cast<ProtocolConformance *>(this);
982+
return ProtocolConformanceRef(mutableThis);
948983

984+
auto *generic = cast<NormalProtocolConformance>(mutableThis);
949985
auto subMap = SubstitutionMap::get(getGenericSignature(), IFS);
950986

951-
auto *mutableThis = const_cast<ProtocolConformance *>(this);
952-
return substType->getASTContext()
953-
.getSpecializedConformance(substType,
954-
cast<NormalProtocolConformance>(mutableThis),
955-
subMap);
987+
if (isVanishingTupleConformance(generic, subMap))
988+
return unwrapVanishingTupleConformance(subMap);
989+
990+
auto &ctx = substType->getASTContext();
991+
auto *concrete = ctx.getSpecializedConformance(substType, generic, subMap);
992+
993+
return ProtocolConformanceRef(concrete);
956994
}
995+
957996
case ProtocolConformanceKind::Builtin: {
958997
auto origType = getType();
959998
if (!origType->hasTypeParameter() &&
960999
!origType->hasArchetype())
961-
return const_cast<ProtocolConformance *>(this);
1000+
return ProtocolConformanceRef(mutableThis);
9621001

9631002
auto substType = origType.subst(IFS);
9641003

9651004
// We do an exact pointer equality check because subst() can
9661005
// change sugar.
9671006
if (substType.getPointer() == origType.getPointer())
968-
return const_cast<ProtocolConformance *>(this);
1007+
return ProtocolConformanceRef(mutableThis);
9691008

9701009
auto kind = cast<BuiltinProtocolConformance>(this)
9711010
->getBuiltinConformanceKind();
9721011

973-
return substType->getASTContext()
1012+
auto *concrete = substType->getASTContext()
9741013
.getBuiltinConformance(substType, getProtocol(), kind);
1014+
return ProtocolConformanceRef(concrete);
9751015
}
1016+
9761017
case ProtocolConformanceKind::Self:
977-
return const_cast<ProtocolConformance*>(this);
1018+
return ProtocolConformanceRef(mutableThis);
1019+
9781020
case ProtocolConformanceKind::Inherited: {
9791021
// Substitute the base.
9801022
auto inheritedConformance
@@ -983,31 +1025,42 @@ ProtocolConformance::subst(InFlightSubstitution &IFS) const {
9831025
auto origType = getType();
9841026
if (!origType->hasTypeParameter() &&
9851027
!origType->hasArchetype()) {
986-
return const_cast<ProtocolConformance *>(this);
1028+
return ProtocolConformanceRef(mutableThis);
9871029
}
9881030

9891031
auto origBaseType = inheritedConformance->getType();
9901032
if (origBaseType->hasTypeParameter() ||
9911033
origBaseType->hasArchetype()) {
9921034
// Substitute into the superclass.
993-
inheritedConformance = inheritedConformance->subst(IFS);
1035+
auto substConformance = inheritedConformance->subst(IFS);
1036+
if (!substConformance.isConcrete())
1037+
return substConformance;
1038+
1039+
inheritedConformance = substConformance.getConcrete();
9941040
}
9951041

9961042
auto substType = origType.subst(IFS);
997-
return substType->getASTContext()
1043+
auto *concrete = substType->getASTContext()
9981044
.getInheritedConformance(substType, inheritedConformance);
1045+
return ProtocolConformanceRef(concrete);
9991046
}
1047+
10001048
case ProtocolConformanceKind::Specialized: {
10011049
// Substitute the substitutions in the specialized conformance.
10021050
auto spec = cast<SpecializedProtocolConformance>(this);
1003-
auto genericConformance = spec->getGenericConformance();
1004-
auto subMap = spec->getSubstitutionMap();
10051051

1006-
auto origType = getType();
1007-
auto substType = origType.subst(IFS);
1008-
return substType->getASTContext()
1009-
.getSpecializedConformance(substType, genericConformance,
1010-
subMap.subst(IFS));
1052+
auto *generic = spec->getGenericConformance();
1053+
auto subMap = spec->getSubstitutionMap().subst(IFS);
1054+
1055+
if (isVanishingTupleConformance(generic, subMap))
1056+
return unwrapVanishingTupleConformance(subMap);
1057+
1058+
auto substType = spec->getType().subst(IFS);
1059+
1060+
auto &ctx = substType->getASTContext();
1061+
auto *concrete = ctx.getSpecializedConformance(substType, generic, subMap);
1062+
1063+
return ProtocolConformanceRef(concrete);
10111064
}
10121065
}
10131066
llvm_unreachable("bad ProtocolConformanceKind");

lib/AST/ProtocolConformanceRef.cpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ ProtocolConformanceRef::subst(Type origType, InFlightSubstitution &IFS) const {
8787
return *this;
8888

8989
if (isConcrete())
90-
return ProtocolConformanceRef(getConcrete()->subst(IFS));
90+
return getConcrete()->subst(IFS);
9191
if (isPack())
9292
return getPack()->subst(IFS);
9393

@@ -126,15 +126,14 @@ ProtocolConformanceRef::subst(Type origType, InFlightSubstitution &IFS) const {
126126

127127
ProtocolConformanceRef ProtocolConformanceRef::mapConformanceOutOfContext() const {
128128
if (isConcrete()) {
129-
auto *concrete = getConcrete()->subst(
129+
return getConcrete()->subst(
130130
[](SubstitutableType *type) -> Type {
131131
if (auto *archetypeType = type->getAs<ArchetypeType>())
132132
return archetypeType->getInterfaceType();
133133
return type;
134134
},
135135
MakeAbstractConformanceForGenericType(),
136136
SubstFlags::PreservePackExpansionLevel);
137-
return ProtocolConformanceRef(concrete);
138137
} else if (isPack()) {
139138
return getPack()->subst(
140139
[](SubstitutableType *type) -> Type {

lib/AST/Type.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4765,8 +4765,9 @@ case TypeKind::Id:
47654765
if (!anyChanged)
47664766
return *this;
47674767

4768-
// If the transform would yield a singleton tuple, and we didn't
4769-
// start with one, flatten to produce the element type.
4768+
// Handle vanishing tuples -- If the transform would yield a singleton
4769+
// tuple, and we didn't start with one, flatten to produce the
4770+
// element type.
47704771
if (elements.size() == 1 &&
47714772
!elements[0].getType()->is<PackExpansionType>() &&
47724773
!(tuple->getNumElements() == 1 &&

lib/IRGen/GenProto.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1190,7 +1190,7 @@ getWitnessTableLazyAccessFunction(IRGenModule &IGM,
11901190
static const ProtocolConformance *
11911191
mapConformanceIntoContext(const RootProtocolConformance *conf) {
11921192
if (auto *genericEnv = conf->getDeclContext()->getGenericEnvironmentOfContext())
1193-
return conf->subst(genericEnv->getForwardingSubstitutionMap());
1193+
return conf->subst(genericEnv->getForwardingSubstitutionMap()).getConcrete();
11941194
return conf;
11951195
}
11961196

lib/SILOptimizer/Utils/Devirtualize.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1146,6 +1146,15 @@ static bool canDevirtualizeWitnessMethod(ApplySite applySite, bool isMandatory)
11461146

11471147
auto *wmi = cast<WitnessMethodInst>(applySite.getCallee());
11481148

1149+
// Handle vanishing tuples: don't devirtualize a call to a tuple conformance
1150+
// if the lookup type can possibly be unwrapped after substitution.
1151+
if (auto tupleType = dyn_cast<TupleType>(wmi->getLookupType())) {
1152+
if (tupleType->containsPackExpansionType() &&
1153+
tupleType->getNumScalarElements() <= 1) {
1154+
return false;
1155+
}
1156+
}
1157+
11491158
std::tie(f, wt) = applySite.getModule().lookUpFunctionInWitnessTable(
11501159
wmi->getConformance(), wmi->getMember(), SILModule::LinkingMode::LinkAll);
11511160

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// RUN: %target-swift-frontend -emit-sil %s -enable-experimental-feature TupleConformances | %FileCheck %s
2+
3+
public typealias Tuple<each T> = (repeat each T)
4+
5+
public protocol P {
6+
static func protocolMethod()
7+
}
8+
9+
extension Tuple: P where repeat each T: P {
10+
public static func protocolMethod() {}
11+
}
12+
13+
extension Int: P {
14+
public static func protocolMethod() {}
15+
}
16+
17+
public func callee<T: P>(t: T.Type) {
18+
}
19+
20+
@_transparent
21+
public func transparentCallee<T: P>(t: T.Type) {
22+
t.protocolMethod()
23+
}
24+
25+
// Make sure we don't devirtualize the call when we inline transparentCallee()
26+
// into transparentCaller(), because we might unwrap the tuple conformance
27+
// later.
28+
29+
// CHECK-LABEL: sil [transparent] @$s4main17transparentCaller5tupleyxxQp_tm_tRvzAA1PRzlF : $@convention(thin) <each T where repeat each T : P> (@thin (repeat each T).Type) -> () {
30+
@_transparent public func transparentCaller<each T: P>(tuple: (repeat each T).Type) {
31+
callee(t: tuple)
32+
33+
// CHECK: [[FN:%.*]] = witness_method $(repeat each T), #P.protocolMethod : <Self where Self : P> (Self.Type) -> () -> () : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@thick τ_0_0.Type) -> ()
34+
// CHECK: apply [[FN:%.*]]<(repeat each T)>({{.*}})
35+
36+
transparentCallee(t: tuple)
37+
38+
// FIXME: This one is wrong in the AST
39+
tuple.protocolMethod()
40+
}
41+
42+
// Inlining transparentCaller() into caller() exercises the code path to unwrap
43+
// a one-element tuple conformance.
44+
45+
public func caller() {
46+
transparentCaller(tuple: Int.self)
47+
}
48+
49+
// CHECK-LABEL: sil @$s4main6calleryyF : $@convention(thin) () -> () {
50+
// CHECK: [[FN:%.*]] = function_ref @$s4main6callee1tyxm_tAA1PRzlF : $@convention(thin) <τ_0_0 where τ_0_0 : P> (@thick τ_0_0.Type) -> ()
51+
// CHECK: apply [[FN]]<Int>({{.*}}) : $@convention(thin) <τ_0_0 where τ_0_0 : P> (@thick τ_0_0.Type) -> ()
52+
53+
// Make sure the witness method call in transparentCallee() was devirtualized to Int.protocolMethod().
54+
55+
// CHECK: [[FN:%.*]] = function_ref @$sSi4mainE14protocolMethodyyFZ : $@convention(method) (@thin Int.Type) -> () // user: %6
56+
// CHECK: apply [[FN]]({{.*}}) : $@convention(method) (@thin Int.Type) -> ()
57+
58+
// FIXME: This is the `tuple.protocolMethod()` in transparentCaller(). It should
59+
// also refer to Int.protocolMethod()!
60+
61+
// CHECK: [[FN:%.*]] = function_ref @$sBT4mainRvzAA1PRzlE14protocolMethodyyFZ : $@convention(method) <each τ_0_0 where repeat each τ_0_0 : P> (@thin (repeat each τ_0_0).Type) -> ()
62+
// CHECK: apply [[FN]]<Pack{Int}>({{.*}}) : $@convention(method) <each τ_0_0 where repeat each τ_0_0 : P> (@thin (repeat each τ_0_0).Type) -> ()

0 commit comments

Comments
 (0)