diff --git a/include/swift/AST/ProtocolConformance.h b/include/swift/AST/ProtocolConformance.h index 4b383d9ab02a6..87841f93aaf81 100644 --- a/include/swift/AST/ProtocolConformance.h +++ b/include/swift/AST/ProtocolConformance.h @@ -299,23 +299,23 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance /// be satisfied. ArrayRef getConditionalRequirements() const; - /// Substitute the conforming type and produce a ProtocolConformance that + /// Substitute the conforming type and produce a ProtocolConformanceRef that /// applies to the substituted type. - ProtocolConformance *subst(SubstitutionMap subMap, - SubstOptions options = llvm::None) const; + ProtocolConformanceRef subst(SubstitutionMap subMap, + SubstOptions options = llvm::None) const; - /// Substitute the conforming type and produce a ProtocolConformance that + /// Substitute the conforming type and produce a ProtocolConformanceRef that /// applies to the substituted type. - ProtocolConformance *subst(TypeSubstitutionFn subs, - LookupConformanceFn conformances, - SubstOptions options = llvm::None) const; + ProtocolConformanceRef subst(TypeSubstitutionFn subs, + LookupConformanceFn conformances, + SubstOptions options = llvm::None) const; - /// Substitute the conforming type and produce a ProtocolConformance that + /// Substitute the conforming type and produce a ProtocolConformanceRef that /// applies to the substituted type. /// /// This function should generally not be used outside of the substitution /// subsystem. - ProtocolConformance *subst(InFlightSubstitution &IFS) const; + ProtocolConformanceRef subst(InFlightSubstitution &IFS) const; SWIFT_DEBUG_DUMP; void dump(llvm::raw_ostream &out, unsigned indent = 0) const; diff --git a/lib/AST/ASTContext.cpp b/lib/AST/ASTContext.cpp index 3f61efdfada59..3da6840db33c8 100644 --- a/lib/AST/ASTContext.cpp +++ b/lib/AST/ASTContext.cpp @@ -2731,6 +2731,13 @@ ASTContext::getSpecializedConformance(Type type, if (auto result = specializedConformances.FindNodeOrInsertPos(id, insertPos)) return result; + // Vanishing tuple conformances must be handled by the caller. + if (isa(generic->getDeclContext()->getSelfNominalTypeDecl())) { + assert(type->is() && "Vanishing tuple substitution is not " + "here. Did you mean to use ProtocolConformanceRef::subst() " + "instead?"); + } + // Build a new specialized conformance. auto result = new (*this, arena) SpecializedProtocolConformance(type, generic, diff --git a/lib/AST/ProtocolConformance.cpp b/lib/AST/ProtocolConformance.cpp index 696a4555c8357..db19b39b5552a 100644 --- a/lib/AST/ProtocolConformance.cpp +++ b/lib/AST/ProtocolConformance.cpp @@ -25,6 +25,7 @@ #include "swift/AST/InFlightSubstitution.h" #include "swift/AST/LazyResolver.h" #include "swift/AST/Module.h" +#include "swift/AST/PackConformance.h" #include "swift/AST/TypeCheckRequests.h" #include "swift/AST/Types.h" #include "swift/Basic/Statistic.h" @@ -918,14 +919,14 @@ bool ProtocolConformance::isVisibleFrom(const DeclContext *dc) const { return true; } -ProtocolConformance * +ProtocolConformanceRef ProtocolConformance::subst(SubstitutionMap subMap, SubstOptions options) const { InFlightSubstitutionViaSubMap IFS(subMap, options); return subst(IFS); } -ProtocolConformance * +ProtocolConformanceRef ProtocolConformance::subst(TypeSubstitutionFn subs, LookupConformanceFn conformances, SubstOptions options) const { @@ -933,48 +934,89 @@ ProtocolConformance::subst(TypeSubstitutionFn subs, return subst(IFS); } -ProtocolConformance * +/// Check if the replacement is a one-element pack with a scalar type. +static bool isVanishingTupleConformance( + RootProtocolConformance *generic, + SubstitutionMap substitutions) { + if (!isa(generic->getDeclContext()->getSelfNominalTypeDecl())) + return false; + + auto replacementTypes = substitutions.getReplacementTypes(); + assert(replacementTypes.size() == 1); + auto packType = replacementTypes[0]->castTo(); + + return (packType->getNumElements() == 1 && + !packType->getElementTypes()[0]->is()); +} + +/// Don't form a tuple conformance if the substituted type is unwrapped +/// from a one-element tuple. +/// +/// That is, [(repeat each T): P] ⊗ {each T := Pack{U}; +/// [each T: P]: Pack{ [U: P] }} +/// => [U: P] +static ProtocolConformanceRef unwrapVanishingTupleConformance( + SubstitutionMap substitutions) { + auto conformances = substitutions.getConformances(); + assert(conformances.size() == 1); + assert(conformances[0].isPack()); + auto packConformance = conformances[0].getPack(); + + assert(packConformance->getPatternConformances().size() == 1); + return packConformance->getPatternConformances()[0]; +} + +ProtocolConformanceRef ProtocolConformance::subst(InFlightSubstitution &IFS) const { + auto *mutableThis = const_cast(this); + switch (getKind()) { case ProtocolConformanceKind::Normal: { auto origType = getType(); if (!origType->hasTypeParameter() && !origType->hasArchetype()) - return const_cast(this); + return ProtocolConformanceRef(mutableThis); auto substType = origType.subst(IFS); if (substType->isEqual(origType)) - return const_cast(this); + return ProtocolConformanceRef(mutableThis); + auto *generic = cast(mutableThis); auto subMap = SubstitutionMap::get(getGenericSignature(), IFS); - auto *mutableThis = const_cast(this); - return substType->getASTContext() - .getSpecializedConformance(substType, - cast(mutableThis), - subMap); + if (isVanishingTupleConformance(generic, subMap)) + return unwrapVanishingTupleConformance(subMap); + + auto &ctx = substType->getASTContext(); + auto *concrete = ctx.getSpecializedConformance(substType, generic, subMap); + + return ProtocolConformanceRef(concrete); } + case ProtocolConformanceKind::Builtin: { auto origType = getType(); if (!origType->hasTypeParameter() && !origType->hasArchetype()) - return const_cast(this); + return ProtocolConformanceRef(mutableThis); auto substType = origType.subst(IFS); // We do an exact pointer equality check because subst() can // change sugar. if (substType.getPointer() == origType.getPointer()) - return const_cast(this); + return ProtocolConformanceRef(mutableThis); auto kind = cast(this) ->getBuiltinConformanceKind(); - return substType->getASTContext() + auto *concrete = substType->getASTContext() .getBuiltinConformance(substType, getProtocol(), kind); + return ProtocolConformanceRef(concrete); } + case ProtocolConformanceKind::Self: - return const_cast(this); + return ProtocolConformanceRef(mutableThis); + case ProtocolConformanceKind::Inherited: { // Substitute the base. auto inheritedConformance @@ -983,31 +1025,42 @@ ProtocolConformance::subst(InFlightSubstitution &IFS) const { auto origType = getType(); if (!origType->hasTypeParameter() && !origType->hasArchetype()) { - return const_cast(this); + return ProtocolConformanceRef(mutableThis); } auto origBaseType = inheritedConformance->getType(); if (origBaseType->hasTypeParameter() || origBaseType->hasArchetype()) { // Substitute into the superclass. - inheritedConformance = inheritedConformance->subst(IFS); + auto substConformance = inheritedConformance->subst(IFS); + if (!substConformance.isConcrete()) + return substConformance; + + inheritedConformance = substConformance.getConcrete(); } auto substType = origType.subst(IFS); - return substType->getASTContext() + auto *concrete = substType->getASTContext() .getInheritedConformance(substType, inheritedConformance); + return ProtocolConformanceRef(concrete); } + case ProtocolConformanceKind::Specialized: { // Substitute the substitutions in the specialized conformance. auto spec = cast(this); - auto genericConformance = spec->getGenericConformance(); - auto subMap = spec->getSubstitutionMap(); - auto origType = getType(); - auto substType = origType.subst(IFS); - return substType->getASTContext() - .getSpecializedConformance(substType, genericConformance, - subMap.subst(IFS)); + auto *generic = spec->getGenericConformance(); + auto subMap = spec->getSubstitutionMap().subst(IFS); + + if (isVanishingTupleConformance(generic, subMap)) + return unwrapVanishingTupleConformance(subMap); + + auto substType = spec->getType().subst(IFS); + + auto &ctx = substType->getASTContext(); + auto *concrete = ctx.getSpecializedConformance(substType, generic, subMap); + + return ProtocolConformanceRef(concrete); } } llvm_unreachable("bad ProtocolConformanceKind"); diff --git a/lib/AST/ProtocolConformanceRef.cpp b/lib/AST/ProtocolConformanceRef.cpp index dfd5279c0ca84..079004512fbd9 100644 --- a/lib/AST/ProtocolConformanceRef.cpp +++ b/lib/AST/ProtocolConformanceRef.cpp @@ -87,7 +87,7 @@ ProtocolConformanceRef::subst(Type origType, InFlightSubstitution &IFS) const { return *this; if (isConcrete()) - return ProtocolConformanceRef(getConcrete()->subst(IFS)); + return getConcrete()->subst(IFS); if (isPack()) return getPack()->subst(IFS); @@ -126,7 +126,7 @@ ProtocolConformanceRef::subst(Type origType, InFlightSubstitution &IFS) const { ProtocolConformanceRef ProtocolConformanceRef::mapConformanceOutOfContext() const { if (isConcrete()) { - auto *concrete = getConcrete()->subst( + return getConcrete()->subst( [](SubstitutableType *type) -> Type { if (auto *archetypeType = type->getAs()) return archetypeType->getInterfaceType(); @@ -134,7 +134,6 @@ ProtocolConformanceRef ProtocolConformanceRef::mapConformanceOutOfContext() cons }, MakeAbstractConformanceForGenericType(), SubstFlags::PreservePackExpansionLevel); - return ProtocolConformanceRef(concrete); } else if (isPack()) { return getPack()->subst( [](SubstitutableType *type) -> Type { diff --git a/lib/AST/Type.cpp b/lib/AST/Type.cpp index d0e42c274251a..0c3fe0e51f19d 100644 --- a/lib/AST/Type.cpp +++ b/lib/AST/Type.cpp @@ -4765,8 +4765,9 @@ case TypeKind::Id: if (!anyChanged) return *this; - // If the transform would yield a singleton tuple, and we didn't - // start with one, flatten to produce the element type. + // Handle vanishing tuples -- If the transform would yield a singleton + // tuple, and we didn't start with one, flatten to produce the + // element type. if (elements.size() == 1 && !elements[0].getType()->is() && !(tuple->getNumElements() == 1 && diff --git a/lib/IRGen/GenProto.cpp b/lib/IRGen/GenProto.cpp index 96d7ae25b8402..a688220e3d86d 100644 --- a/lib/IRGen/GenProto.cpp +++ b/lib/IRGen/GenProto.cpp @@ -1190,7 +1190,7 @@ getWitnessTableLazyAccessFunction(IRGenModule &IGM, static const ProtocolConformance * mapConformanceIntoContext(const RootProtocolConformance *conf) { if (auto *genericEnv = conf->getDeclContext()->getGenericEnvironmentOfContext()) - return conf->subst(genericEnv->getForwardingSubstitutionMap()); + return conf->subst(genericEnv->getForwardingSubstitutionMap()).getConcrete(); return conf; } diff --git a/lib/SILOptimizer/Utils/Devirtualize.cpp b/lib/SILOptimizer/Utils/Devirtualize.cpp index 066d91f08b384..f07b3a43e35b3 100644 --- a/lib/SILOptimizer/Utils/Devirtualize.cpp +++ b/lib/SILOptimizer/Utils/Devirtualize.cpp @@ -1146,6 +1146,15 @@ static bool canDevirtualizeWitnessMethod(ApplySite applySite, bool isMandatory) auto *wmi = cast(applySite.getCallee()); + // Handle vanishing tuples: don't devirtualize a call to a tuple conformance + // if the lookup type can possibly be unwrapped after substitution. + if (auto tupleType = dyn_cast(wmi->getLookupType())) { + if (tupleType->containsPackExpansionType() && + tupleType->getNumScalarElements() <= 1) { + return false; + } + } + std::tie(f, wt) = applySite.getModule().lookUpFunctionInWitnessTable( wmi->getConformance(), wmi->getMember(), SILModule::LinkingMode::LinkAll); diff --git a/test/SILOptimizer/tuple-conformances.swift b/test/SILOptimizer/tuple-conformances.swift new file mode 100644 index 0000000000000..3a1d3f3fad4fd --- /dev/null +++ b/test/SILOptimizer/tuple-conformances.swift @@ -0,0 +1,62 @@ +// RUN: %target-swift-frontend -emit-sil %s -enable-experimental-feature TupleConformances | %FileCheck %s + +public typealias Tuple = (repeat each T) + +public protocol P { + static func protocolMethod() +} + +extension Tuple: P where repeat each T: P { + public static func protocolMethod() {} +} + +extension Int: P { + public static func protocolMethod() {} +} + +public func callee(t: T.Type) { +} + +@_transparent +public func transparentCallee(t: T.Type) { + t.protocolMethod() +} + +// Make sure we don't devirtualize the call when we inline transparentCallee() +// into transparentCaller(), because we might unwrap the tuple conformance +// later. + +// CHECK-LABEL: sil [transparent] @$s4main17transparentCaller5tupleyxxQp_tm_tRvzAA1PRzlF : $@convention(thin) (@thin (repeat each T).Type) -> () { +@_transparent public func transparentCaller(tuple: (repeat each T).Type) { + callee(t: tuple) + +// CHECK: [[FN:%.*]] = witness_method $(repeat each T), #P.protocolMethod : (Self.Type) -> () -> () : $@convention(witness_method: P) <τ_0_0 where τ_0_0 : P> (@thick τ_0_0.Type) -> () +// CHECK: apply [[FN:%.*]]<(repeat each T)>({{.*}}) + + transparentCallee(t: tuple) + +// FIXME: This one is wrong in the AST + tuple.protocolMethod() +} + +// Inlining transparentCaller() into caller() exercises the code path to unwrap +// a one-element tuple conformance. + +public func caller() { + transparentCaller(tuple: Int.self) +} + +// CHECK-LABEL: sil @$s4main6calleryyF : $@convention(thin) () -> () { +// CHECK: [[FN:%.*]] = function_ref @$s4main6callee1tyxm_tAA1PRzlF : $@convention(thin) <τ_0_0 where τ_0_0 : P> (@thick τ_0_0.Type) -> () +// CHECK: apply [[FN]]({{.*}}) : $@convention(thin) <τ_0_0 where τ_0_0 : P> (@thick τ_0_0.Type) -> () + +// Make sure the witness method call in transparentCallee() was devirtualized to Int.protocolMethod(). + +// CHECK: [[FN:%.*]] = function_ref @$sSi4mainE14protocolMethodyyFZ : $@convention(method) (@thin Int.Type) -> () // user: %6 +// CHECK: apply [[FN]]({{.*}}) : $@convention(method) (@thin Int.Type) -> () + +// FIXME: This is the `tuple.protocolMethod()` in transparentCaller(). It should +// also refer to Int.protocolMethod()! + +// CHECK: [[FN:%.*]] = function_ref @$sBT4mainRvzAA1PRzlE14protocolMethodyyFZ : $@convention(method) (@thin (repeat each τ_0_0).Type) -> () +// CHECK: apply [[FN]]({{.*}}) : $@convention(method) (@thin (repeat each τ_0_0).Type) -> ()