Skip to content

Magical vanishing tuple conformances #68405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions include/swift/AST/ProtocolConformance.h
Original file line number Diff line number Diff line change
Expand Up @@ -299,23 +299,23 @@ class alignas(1 << DeclAlignInBits) ProtocolConformance
/// be satisfied.
ArrayRef<Requirement> 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;
Expand Down
7 changes: 7 additions & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<BuiltinTupleDecl>(generic->getDeclContext()->getSelfNominalTypeDecl())) {
assert(type->is<TupleType>() && "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,
Expand Down
101 changes: 77 additions & 24 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -918,63 +919,104 @@ 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 {
InFlightSubstitution IFS(subs, conformances, options);
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<BuiltinTupleDecl>(generic->getDeclContext()->getSelfNominalTypeDecl()))
return false;

auto replacementTypes = substitutions.getReplacementTypes();
assert(replacementTypes.size() == 1);
auto packType = replacementTypes[0]->castTo<PackType>();

return (packType->getNumElements() == 1 &&
!packType->getElementTypes()[0]->is<PackExpansionType>());
}

/// 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<ProtocolConformance *>(this);

switch (getKind()) {
case ProtocolConformanceKind::Normal: {
auto origType = getType();
if (!origType->hasTypeParameter() &&
!origType->hasArchetype())
return const_cast<ProtocolConformance *>(this);
return ProtocolConformanceRef(mutableThis);

auto substType = origType.subst(IFS);
if (substType->isEqual(origType))
return const_cast<ProtocolConformance *>(this);
return ProtocolConformanceRef(mutableThis);

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

auto *mutableThis = const_cast<ProtocolConformance *>(this);
return substType->getASTContext()
.getSpecializedConformance(substType,
cast<NormalProtocolConformance>(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<ProtocolConformance *>(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<ProtocolConformance *>(this);
return ProtocolConformanceRef(mutableThis);

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

return substType->getASTContext()
auto *concrete = substType->getASTContext()
.getBuiltinConformance(substType, getProtocol(), kind);
return ProtocolConformanceRef(concrete);
}

case ProtocolConformanceKind::Self:
return const_cast<ProtocolConformance*>(this);
return ProtocolConformanceRef(mutableThis);

case ProtocolConformanceKind::Inherited: {
// Substitute the base.
auto inheritedConformance
Expand All @@ -983,31 +1025,42 @@ ProtocolConformance::subst(InFlightSubstitution &IFS) const {
auto origType = getType();
if (!origType->hasTypeParameter() &&
!origType->hasArchetype()) {
return const_cast<ProtocolConformance *>(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<SpecializedProtocolConformance>(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");
Expand Down
5 changes: 2 additions & 3 deletions lib/AST/ProtocolConformanceRef.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -126,15 +126,14 @@ 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<ArchetypeType>())
return archetypeType->getInterfaceType();
return type;
},
MakeAbstractConformanceForGenericType(),
SubstFlags::PreservePackExpansionLevel);
return ProtocolConformanceRef(concrete);
} else if (isPack()) {
return getPack()->subst(
[](SubstitutableType *type) -> Type {
Expand Down
5 changes: 3 additions & 2 deletions lib/AST/Type.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<PackExpansionType>() &&
!(tuple->getNumElements() == 1 &&
Expand Down
2 changes: 1 addition & 1 deletion lib/IRGen/GenProto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
9 changes: 9 additions & 0 deletions lib/SILOptimizer/Utils/Devirtualize.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1146,6 +1146,15 @@ static bool canDevirtualizeWitnessMethod(ApplySite applySite, bool isMandatory)

auto *wmi = cast<WitnessMethodInst>(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<TupleType>(wmi->getLookupType())) {
if (tupleType->containsPackExpansionType() &&
tupleType->getNumScalarElements() <= 1) {
return false;
}
}

std::tie(f, wt) = applySite.getModule().lookUpFunctionInWitnessTable(
wmi->getConformance(), wmi->getMember(), SILModule::LinkingMode::LinkAll);

Expand Down
62 changes: 62 additions & 0 deletions test/SILOptimizer/tuple-conformances.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// RUN: %target-swift-frontend -emit-sil %s -enable-experimental-feature TupleConformances | %FileCheck %s

public typealias Tuple<each T> = (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: P>(t: T.Type) {
}

@_transparent
public func transparentCallee<T: P>(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) <each T where repeat each T : P> (@thin (repeat each T).Type) -> () {
@_transparent public func transparentCaller<each T: P>(tuple: (repeat each T).Type) {
callee(t: tuple)

// 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) -> ()
// 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]]<Int>({{.*}}) : $@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) <each τ_0_0 where repeat each τ_0_0 : P> (@thin (repeat each τ_0_0).Type) -> ()
// CHECK: apply [[FN]]<Pack{Int}>({{.*}}) : $@convention(method) <each τ_0_0 where repeat each τ_0_0 : P> (@thin (repeat each τ_0_0).Type) -> ()