Skip to content

Remove ProtocolConformanceRef::getInherited() #11724

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
5 changes: 5 additions & 0 deletions include/swift/AST/ASTContext.h
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,11 @@ class ASTContext {
/// like `<T>`.
CanGenericSignature getSingleGenericParameterSignature() const;

/// Retrieve a generic signature with a single type parameter conforming
/// to the given existential type.
CanGenericSignature getExistentialSignature(CanType existential,
ModuleDecl *mod);

/// Whether our effective Swift version is in the Swift 3 family.
bool isSwiftVersion3() const { return LangOpts.isSwiftVersion3(); }

Expand Down
5 changes: 0 additions & 5 deletions include/swift/AST/ProtocolConformanceRef.h
Original file line number Diff line number Diff line change
Expand Up @@ -86,11 +86,6 @@ class ProtocolConformanceRef {
/// Return the protocol requirement.
ProtocolDecl *getRequirement() const;

/// Get the inherited conformance corresponding to the given protocol.
/// Returns `this` if `parent` is already the same as the protocol this
/// conformance represents.
ProtocolConformanceRef getInherited(ProtocolDecl *parent) const;

/// Apply a substitution to the conforming type.
ProtocolConformanceRef subst(Type origType,
TypeSubstitutionFn subs,
Expand Down
32 changes: 32 additions & 0 deletions lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,9 @@ FOR_KNOWN_FOUNDATION_TYPES(CACHE_FOUNDATION_DECL)
/// The single-parameter generic signature with no constraints, <T>.
CanGenericSignature SingleGenericParameterSignature;

/// The existential signature <T : P> for each P.
llvm::DenseMap<CanType, CanGenericSignature> ExistentialSignatures;

/// \brief Structure that captures data that is segregated into different
/// arenas.
struct Arena {
Expand Down Expand Up @@ -4509,6 +4512,35 @@ CanGenericSignature ASTContext::getSingleGenericParameterSignature() const {
return canonicalSig;
}

CanGenericSignature ASTContext::getExistentialSignature(CanType existential,
ModuleDecl *mod) {
auto found = Impl.ExistentialSignatures.find(existential);
if (found != Impl.ExistentialSignatures.end())
return found->second;

assert(existential.isExistentialType());

GenericSignatureBuilder builder(*this, LookUpConformanceInModule(mod));

auto genericParam = GenericTypeParamType::get(0, 0, *this);
builder.addGenericParameter(genericParam);

Requirement requirement(RequirementKind::Conformance, genericParam,
existential);
auto source =
GenericSignatureBuilder::FloatingRequirementSource::forAbstract();
builder.addRequirement(requirement, source, nullptr);

CanGenericSignature genericSig(builder.computeGenericSignature(SourceLoc()));

auto result = Impl.ExistentialSignatures.insert(
std::make_pair(existential, genericSig));
assert(result.second);
(void) result;

return genericSig;
}

SILLayout *SILLayout::get(ASTContext &C,
CanGenericSignature Generics,
ArrayRef<SILField> Fields) {
Expand Down
24 changes: 0 additions & 24 deletions lib/AST/ProtocolConformance.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -77,30 +77,6 @@ ProtocolDecl *ProtocolConformanceRef::getRequirement() const {
}
}

ProtocolConformanceRef
ProtocolConformanceRef::getInherited(ProtocolDecl *parent) const {
assert((getRequirement() == parent ||
getRequirement()->inheritsFrom(parent)) &&
"not a parent of this protocol");

if (parent == getRequirement())
return *this;

// For an abstract requirement, simply produce a new abstract requirement
// for the parent.
if (isAbstract()) {
return ProtocolConformanceRef(parent);
}

// Navigate concrete conformances.
if (isConcrete()) {
return ProtocolConformanceRef(
getConcrete()->getInheritedConformance(parent));
}

llvm_unreachable("unhandled ProtocolConformanceRef");
}

ProtocolConformanceRef
ProtocolConformanceRef::subst(Type origType,
TypeSubstitutionFn subs,
Expand Down
9 changes: 3 additions & 6 deletions lib/IRGen/GenProto.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2347,9 +2347,7 @@ llvm::Value *irgen::emitWitnessTableRef(IRGenFunction &IGF,
// more concrete than we're expecting.
// TODO: make a best effort to devirtualize, maybe?
auto concreteConformance = conformance.getConcrete();
if (concreteConformance->getProtocol() != proto) {
concreteConformance = concreteConformance->getInheritedConformance(proto);
}
assert(concreteConformance->getProtocol() == proto);

// Check immediately for an existing cache entry.
auto wtable = IGF.tryGetLocalTypeData(
Expand Down Expand Up @@ -2823,11 +2821,10 @@ irgen::emitWitnessMethodValue(IRGenFunction &IGF,
CanSILFunctionType fnType) {
auto fn = cast<AbstractFunctionDecl>(member.getDecl());
auto fnProto = cast<ProtocolDecl>(fn->getDeclContext());
conformance = conformance.getInherited(fnProto);

assert(conformance.getRequirement() == fnProto);

// Find the witness table.
// FIXME conformance for concrete type
llvm::Value *wtable = emitWitnessTableRef(IGF, baseTy, baseMetadataCache,
conformance);

Expand Down
20 changes: 14 additions & 6 deletions lib/SIL/SILInstructions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1572,19 +1572,27 @@ DynamicMethodBranchInst::create(SILDebugLocation Loc, SILValue Operand,
DynamicMethodBranchInst(Loc, Operand, Member, HasMethodBB, NoMethodBB);
}

/// Create a witness method, creating a witness table declaration if we don't
/// have a witness table for it. Later on if someone wants the real definition,
/// lookUpWitnessTable will deserialize it for us if we can.
/// Create a witness method call of a protocol requirement, passing in a lookup
/// type and conformance.
///
/// This is following the same model of how we deal with SILFunctions in
/// function_ref. There we always just create a declaration and then later
/// deserialize the actual function definition if we need to.
/// At runtime, the witness is looked up in the conformance of the lookup type
/// to the protocol.
///
/// The lookup type is usually an archetype, but it will be concrete if the
/// witness_method instruction is inside a function body that was specialized.
///
/// The conformance must exactly match the requirement; the caller must handle
/// the case where the requirement is defined in a base protocol that is
/// refined by the conforming protocol.
WitnessMethodInst *
WitnessMethodInst::create(SILDebugLocation Loc, CanType LookupType,
ProtocolConformanceRef Conformance, SILDeclRef Member,
SILType Ty, SILFunction *F,
SILOpenedArchetypesState &OpenedArchetypes,
bool Volatile) {
assert(cast<ProtocolDecl>(Member.getDecl()->getDeclContext())
== Conformance.getRequirement());

SILModule &Mod = F->getModule();
SmallVector<SILValue, 8> TypeDependentOperands;
collectTypeDependentOperands(TypeDependentOperands, OpenedArchetypes, *F,
Expand Down
78 changes: 41 additions & 37 deletions lib/SILOptimizer/SILCombiner/SILCombinerApplyVisitors.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -710,7 +710,8 @@ SILCombiner::createApplyWithConcreteType(FullApplySite AI,
[&](CanType origTy, Type substTy,
ProtocolType *proto) -> Optional<ProtocolConformanceRef> {
if (substTy->isEqual(ConcreteType)) {
return Conformance.getInherited(proto->getDecl());
assert(proto->getDecl() == Conformance.getRequirement());
return Conformance;
}
return ProtocolConformanceRef(proto->getDecl());
});
Expand Down Expand Up @@ -752,69 +753,70 @@ SILCombiner::createApplyWithConcreteType(FullApplySite AI,

/// Derive a concrete type of self and conformance from the init_existential
/// instruction.
static Optional<std::tuple<ProtocolConformanceRef, CanType, SILValue>>
getConformanceAndConcreteType(FullApplySite AI,
static Optional<std::tuple<ProtocolConformanceRef, CanType, SILValue, SILValue>>
getConformanceAndConcreteType(ASTContext &Ctx,
FullApplySite AI,
SILInstruction *InitExistential,
ProtocolDecl *Protocol,
SILValue &NewSelf,
ArrayRef<ProtocolConformanceRef> &Conformances) {
// Try to derive the concrete type of self from the found init_existential.
CanType ConcreteType;
// The existential type result of the found init_existential.
CanType ExistentialType;
SILValue ConcreteTypeDef;
SILValue NewSelf;

// FIXME: Factor this out. All we really need here is the ExistentialSig
// below, which should be stored directly in the SILInstruction.
if (auto IE = dyn_cast<InitExistentialAddrInst>(InitExistential)) {
Conformances = IE->getConformances();
ConcreteType = IE->getFormalConcreteType();
NewSelf = IE;
ExistentialType = IE->getOperand()->getType().getSwiftRValueType();
} else if (auto IER = dyn_cast<InitExistentialRefInst>(InitExistential)) {
Conformances = IER->getConformances();
ConcreteType = IER->getFormalConcreteType();
NewSelf = IER->getOperand();
ExistentialType = IER->getType().getSwiftRValueType();
} else if (auto IEM = dyn_cast<InitExistentialMetatypeInst>(InitExistential)){
Conformances = IEM->getConformances();
NewSelf = IEM->getOperand();
ConcreteType = NewSelf->getType().getSwiftRValueType();

auto ExType = IEM->getType().getSwiftRValueType();
while (auto ExMetatype = dyn_cast<ExistentialMetatypeType>(ExType)) {
ExType = ExMetatype.getInstanceType();
ExistentialType = IEM->getType().getSwiftRValueType();
while (auto InstanceType = dyn_cast<ExistentialMetatypeType>(ExistentialType)) {
ExistentialType = InstanceType.getInstanceType();
ConcreteType = cast<MetatypeType>(ConcreteType).getInstanceType();
}
} else {
return None;
}

// Construct a substitution map from the existential type's generic
// parameter to the concrete type.
auto ExistentialSig = Ctx.getExistentialSignature(ExistentialType,
AI.getModule().getSwiftModule());

Substitution ConcreteSub(ConcreteType, Conformances);
auto SubMap = ExistentialSig->getSubstitutionMap({&ConcreteSub, 1});

// If the requirement is in a base protocol that is refined by the
// conforming protocol, fish out the exact conformance for the base
// protocol using the substitution map.
auto ExactConformance = SubMap.lookupConformance(
CanType(ExistentialSig->getGenericParams()[0]),
Protocol);

// If the concrete type is another existential, we're "forwarding" an
// opened existential type, so we must keep track of the original
// defining instruction.
if (ConcreteType->isOpenedExistential()) {
assert(!InitExistential->getTypeDependentOperands().empty() &&
"init_existential is supposed to have a typedef operand");
ConcreteTypeDef = InitExistential->getTypeDependentOperands()[0].get();
}

// Find the conformance for the protocol we're interested in.
for (auto Conformance : Conformances) {
auto Requirement = Conformance.getRequirement();
if (Requirement == Protocol) {
return std::make_tuple(Conformance, ConcreteType, ConcreteTypeDef);
}
// If Requirement != Protocol, then the abstract conformance cannot be
// used as is and we need to create a proper conformance.
// FIXME: We can handle only direct inheritance at the moment due to some
// limitations of the init_existential_* instructions representation.
// Once these instructions start using generic signatures instead of
// conformances lists, it should be fairly easy to support the indirect
// inheritance here by something like:
// Substitution Sub(ConcreteType, Conformances);
// IE->getGenericSignature()
// ->getSubstitutionMap({Sub}).lookupConformance(GP00, Protocol);
auto InheritedProtocols = Requirement->getInheritedProtocols();
if (std::find(InheritedProtocols.begin(), InheritedProtocols.end(),
Protocol) == InheritedProtocols.end())
return None;
// Requirement is directly inherited from Protocol.
return std::make_tuple(Conformance.getInherited(Protocol), ConcreteType,
ConcreteTypeDef);
}

llvm_unreachable("couldn't find matching conformance in substitution?");
return std::make_tuple(*ExactConformance, ConcreteType,
ConcreteTypeDef, NewSelf);
}

/// Propagate information about a concrete type from init_existential_addr
Expand All @@ -827,6 +829,8 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI,
ProtocolDecl *Protocol,
llvm::function_ref<void(CanType , ProtocolConformanceRef)> Propagate) {

ASTContext &Ctx = Builder.getASTContext();

// Get the self argument.
SILValue Self;
if (auto *Apply = dyn_cast<ApplyInst>(AI)) {
Expand All @@ -851,16 +855,16 @@ SILCombiner::propagateConcreteTypeOfInitExistential(FullApplySite AI,
// Try to derive the concrete type of self and a related conformance from
// the found init_existential.
ArrayRef<ProtocolConformanceRef> Conformances;
auto NewSelf = SILValue();
auto ConformanceAndConcreteType =
getConformanceAndConcreteType(AI, InitExistential,
Protocol, NewSelf, Conformances);
getConformanceAndConcreteType(Ctx, AI, InitExistential,
Protocol, Conformances);
if (!ConformanceAndConcreteType)
return nullptr;

ProtocolConformanceRef Conformance = std::get<0>(*ConformanceAndConcreteType);
CanType ConcreteType = std::get<1>(*ConformanceAndConcreteType);
SILValue ConcreteTypeDef = std::get<2>(*ConformanceAndConcreteType);
SILValue NewSelf = std::get<3>(*ConformanceAndConcreteType);

SILOpenedArchetypesTracker *OldOpenedArchetypesTracker =
Builder.getOpenedArchetypesTracker();
Expand Down
40 changes: 40 additions & 0 deletions lib/Sema/TypeCheckProtocol.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,9 +69,49 @@ namespace {
/// generic requirements. See the \c Witness class for more information about
/// this synthetic environment.
class RequirementEnvironment {
/// A generic signature that combines the generic parameters of the
/// concrete conforming type with the generic parameters of the
/// requirement.
///
///
/// For example, if you have:
///
/// protocol P { func f<T>(_: T) }
/// struct S<A, B> : P { func f<T>(_: T) }
///
/// The requirement and witness signatures are, respectively:
///
/// <Self : P, T>
/// <A, B, T>
///
/// The synthetic signature in this case is just the witness signature.
///
/// It may be that the witness is more generic than the requirement,
/// for example:
///
/// protocol P { func f(_: Int) }
/// struct S<A, B> : P { func f<T>(_: T) { } }
///
/// Here, the requirement signature and witness signatures are:
///
/// <Self : P>
/// <A, B, T>
///
/// The synthetic signature is just:
///
/// <A, B>
///
/// The witness thunk emitted by SILGen uses the synthetic signature.
/// Therefore one invariant we preserve is that the witness thunk is
/// ABI compatible with the requirement's function type.
GenericSignature *syntheticSignature = nullptr;
GenericEnvironment *syntheticEnvironment = nullptr;

/// The generic signature of the protocol requirement member.
GenericSignature *reqSig = nullptr;

/// A substitution map mapping the requirement signature to the
/// generic parameters of the synthetic signature.
SubstitutionMap reqToSyntheticEnvMap;

public:
Expand Down
7 changes: 4 additions & 3 deletions test/SILOptimizer/devirt_protocol_method_invocations.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,12 @@ public func testInheritedConformance() {
(S() as QQQ).f()
}

// Test that a witness_method instructions is not devirtualized yet, because
// it uses indirect inheritance.
// Test that a witness_method instruction using an indirectly-inherited conformance
// is devirtualized.
//
// This test used to crash the compiler because it uses inherited conformances.
// CHECK-LABEL: sil @_T034devirt_protocol_method_invocations34testIndirectlyInheritedConformanceyyF : $@convention(thin) () -> ()
// CHECK: witness_method
// CHECK-NOT: witness_method
// CHECK: apply
// CHECK: // end sil function '_T034devirt_protocol_method_invocations34testIndirectlyInheritedConformanceyyF'
public func testIndirectlyInheritedConformance() {
Expand Down