Skip to content

Cache ConformanceAccessPaths in GSB::EquivalenceClass for big wins #18597

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 3 commits into from
Aug 14, 2018
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
15 changes: 12 additions & 3 deletions include/swift/AST/GenericSignature.h
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,15 @@ class ConformanceAccessPath {
typedef std::pair<Type, ProtocolDecl *> Entry;

private:
llvm::SmallVector<Entry, 2> path;
ArrayRef<Entry> path;

ConformanceAccessPath(ArrayRef<Entry> path) : path(path) {}

friend class GenericSignature;

public:
typedef llvm::SmallVector<Entry, 2>::const_iterator iterator;
typedef llvm::SmallVector<Entry, 2>::const_iterator const_iterator;
typedef const Entry *const_iterator;
typedef const_iterator iterator;

const_iterator begin() const { return path.begin(); }
const_iterator end() const { return path.end(); }
Expand Down Expand Up @@ -133,6 +135,13 @@ class alignas(1 << TypeAlignInBits) GenericSignature final
/// Retrieve the generic signature builder for the given generic signature.
GenericSignatureBuilder *getGenericSignatureBuilder();

void buildConformanceAccessPath(
SmallVectorImpl<ConformanceAccessPath::Entry> &path,
ArrayRef<Requirement> reqs,
const void /*GenericSignatureBuilder::RequirementSource*/ *source,
ProtocolDecl *conformingProto, Type rootType,
ProtocolDecl *requirementSignatureProto);

friend class ArchetypeType;

public:
Expand Down
6 changes: 5 additions & 1 deletion include/swift/AST/GenericSignatureBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

#include "swift/AST/Decl.h"
#include "swift/AST/DiagnosticEngine.h"
#include "swift/AST/GenericSignature.h"
#include "swift/AST/Identifier.h"
#include "swift/AST/ProtocolConformanceRef.h"
#include "swift/AST/Types.h"
Expand All @@ -45,7 +46,6 @@ namespace swift {
class DeclContext;
class DependentMemberType;
class GenericParamList;
class GenericSignature;
class GenericSignatureBuilder;
class GenericTypeParamType;
class LazyResolver;
Expand Down Expand Up @@ -278,6 +278,10 @@ class GenericSignatureBuilder {
/// Cached nested-type information, which contains the best declaration
/// for a given name.
llvm::SmallDenseMap<Identifier, CachedNestedType> nestedTypeNameCache;

/// Cached access paths.
llvm::SmallDenseMap<const ProtocolDecl *, ConformanceAccessPath, 8>
conformanceAccessPathCache;
};

friend class RequirementSource;
Expand Down
299 changes: 151 additions & 148 deletions lib/AST/GenericSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -817,175 +817,178 @@ getBestRequirementSource(ArrayRef<GSBConstraint<ProtocolDecl *>> constraints) {
return bestSource;
}

ConformanceAccessPath GenericSignature::getConformanceAccessPath(
Type type,
ProtocolDecl *protocol) {
assert(type->isTypeParameter() && "not a type parameter");

// Resolve this type to a potential archetype.
auto &builder = *getGenericSignatureBuilder();
auto equivClass =
builder.resolveEquivalenceClass(
type,
ArchetypeResolutionKind::CompleteWellFormed);

// Dig out the conformance of this type to the given protocol, because we
// want its requirement source.
auto conforms = equivClass->conformsTo.find(protocol);
assert(conforms != equivClass->conformsTo.end());

// Follow the requirement source to form the conformance access path.
typedef GenericSignatureBuilder::RequirementSource RequirementSource;
ConformanceAccessPath path;

// Local function to construct the conformance access path from the
// requirement.
std::function<void(ArrayRef<Requirement>, const RequirementSource *,
ProtocolDecl *, Type, ProtocolDecl *)> buildPath;
buildPath = [&](ArrayRef<Requirement> reqs, const RequirementSource *source,
ProtocolDecl *conformingProto, Type rootType,
ProtocolDecl *requirementSignatureProto) {
// Each protocol requirement is a step along the path.
if (source->isProtocolRequirement()) {
// If we're expanding for a protocol that had no requirement signature
// and have hit the penultimate step, this is the last step
// that would occur in the requirement signature.
Optional<GenericSignatureBuilder> replacementBuilder;
if (!source->parent->parent && requirementSignatureProto) {
// If we have a requirement signature now, we're done.
if (source->usesRequirementSignature) {
Type subjectType = source->getStoredType()->getCanonicalType();
path.path.push_back({subjectType, conformingProto});
return;
}

// The generic signature builder we're using for this protocol
// wasn't built from its own requirement signature, so we can't
// trust it. Make sure we have a requirement signature, then build
// a new generic signature builder.
// FIXME: It would be better if we could replace the canonical generic
// signature builder with the rebuilt one.
if (!requirementSignatureProto->isRequirementSignatureComputed())
requirementSignatureProto->computeRequirementSignature();
assert(requirementSignatureProto->isRequirementSignatureComputed());

replacementBuilder.emplace(getASTContext());
replacementBuilder->addGenericSignature(
requirementSignatureProto->getGenericSignature());
replacementBuilder->processDelayedRequirements();
}

// Follow the rest of the path to derive the conformance into which
// this particular protocol requirement step would look.
auto inProtocol = source->getProtocolDecl();
buildPath(reqs, source->parent, inProtocol, rootType,
requirementSignatureProto);
assert(path.path.back().second == inProtocol &&
"path produces incorrect conformance");

// If this step was computed via the requirement signature, add it
// directly.
void GenericSignature::buildConformanceAccessPath(
SmallVectorImpl<ConformanceAccessPath::Entry> &path,
ArrayRef<Requirement> reqs, const void *opaqueSource,
ProtocolDecl *conformingProto, Type rootType,
ProtocolDecl *requirementSignatureProto) {
auto *source = reinterpret_cast<const RequirementSource *>(opaqueSource);
// Each protocol requirement is a step along the path.
if (source->isProtocolRequirement()) {
// If we're expanding for a protocol that had no requirement signature
// and have hit the penultimate step, this is the last step
// that would occur in the requirement signature.
Optional<GenericSignatureBuilder> replacementBuilder;
if (!source->parent->parent && requirementSignatureProto) {
// If we have a requirement signature now, we're done.
if (source->usesRequirementSignature) {
// Add this step along the path, which involves looking for the
// conformance we want (\c conformingProto) within the protocol
// described by this source.

// Canonicalize the subject type within the protocol's generic
// signature.
Type subjectType = source->getStoredType();
subjectType = inProtocol->getGenericSignature()
->getCanonicalTypeInContext(subjectType);

assert(hasConformanceInSignature(inProtocol->getRequirementSignature(),
subjectType, conformingProto) &&
"missing explicit conformance in requirement signature");

// Record this step.
path.path.push_back({subjectType, conformingProto});
Type subjectType = source->getStoredType()->getCanonicalType();
path.push_back({subjectType, conformingProto});
return;
}

// Get the generic signature builder for the protocol.
// Get a generic signature for the protocol's signature.
auto inProtoSig = inProtocol->getGenericSignature();
auto &inProtoSigBuilder =
replacementBuilder ? *replacementBuilder
: *inProtoSig->getGenericSignatureBuilder();

// Retrieve the stored type, but erase all of the specific associated
// type declarations; we don't want any details of the enclosing context
// to sneak in here.
Type storedType = eraseAssociatedTypes(source->getStoredType());

// Dig out the potential archetype for this stored type.
auto equivClass =
inProtoSigBuilder.resolveEquivalenceClass(
storedType,
ArchetypeResolutionKind::CompleteWellFormed);

// Find the conformance of this potential archetype to the protocol in
// question.
auto conforms = equivClass->conformsTo.find(conformingProto);
assert(conforms != equivClass->conformsTo.end());

// Compute the root type, canonicalizing it w.r.t. the protocol context.
auto conformsSource = getBestRequirementSource(conforms->second);
assert(conformsSource != source || !requirementSignatureProto);
Type localRootType = conformsSource->getRootType();
localRootType = inProtoSig->getCanonicalTypeInContext(localRootType);

// Build the path according to the requirement signature.
buildPath(inProtocol->getRequirementSignature(), conformsSource,
conformingProto, localRootType, inProtocol);

// We're done.
return;
// The generic signature builder we're using for this protocol
// wasn't built from its own requirement signature, so we can't
// trust it. Make sure we have a requirement signature, then build
// a new generic signature builder.
// FIXME: It would be better if we could replace the canonical generic
// signature builder with the rebuilt one.
if (!requirementSignatureProto->isRequirementSignatureComputed())
requirementSignatureProto->computeRequirementSignature();
assert(requirementSignatureProto->isRequirementSignatureComputed());

replacementBuilder.emplace(getASTContext());
replacementBuilder->addGenericSignature(
requirementSignatureProto->getGenericSignature());
replacementBuilder->processDelayedRequirements();
}

// If we have a superclass or concrete requirement, the conformance
// we need is stored in it.
if (source->kind == RequirementSource::Superclass ||
source->kind == RequirementSource::Concrete) {
auto conformance = source->getProtocolConformance();
(void)conformance;
assert(conformance.getRequirement() == conformingProto);
path.path.push_back({source->getAffectedType(), conformingProto});
// Follow the rest of the path to derive the conformance into which
// this particular protocol requirement step would look.
auto inProtocol = source->getProtocolDecl();
buildConformanceAccessPath(path, reqs, source->parent, inProtocol, rootType,
requirementSignatureProto);
assert(path.back().second == inProtocol &&
"path produces incorrect conformance");

// If this step was computed via the requirement signature, add it
// directly.
if (source->usesRequirementSignature) {
// Add this step along the path, which involves looking for the
// conformance we want (\c conformingProto) within the protocol
// described by this source.

// Canonicalize the subject type within the protocol's generic
// signature.
Type subjectType = source->getStoredType();
subjectType = inProtocol->getGenericSignature()
->getCanonicalTypeInContext(subjectType);

assert(hasConformanceInSignature(inProtocol->getRequirementSignature(),
subjectType, conformingProto) &&
"missing explicit conformance in requirement signature");

// Record this step.
path.push_back({subjectType, conformingProto});
return;
}

// If we still have a parent, keep going.
if (source->parent) {
buildPath(reqs, source->parent, conformingProto, rootType,
requirementSignatureProto);
return;
}
// Get the generic signature builder for the protocol.
// Get a generic signature for the protocol's signature.
auto inProtoSig = inProtocol->getGenericSignature();
auto &inProtoSigBuilder =
replacementBuilder ? *replacementBuilder
: *inProtoSig->getGenericSignatureBuilder();

// We are at an explicit or inferred requirement.
assert(source->kind == RequirementSource::Explicit ||
source->kind == RequirementSource::Inferred);
// Retrieve the stored type, but erase all of the specific associated
// type declarations; we don't want any details of the enclosing context
// to sneak in here.
Type storedType = eraseAssociatedTypes(source->getStoredType());

// Skip trivial path elements. These occur when querying a requirement
// signature.
if (!path.path.empty() && conformingProto == path.path.back().second &&
rootType->isEqual(conformingProto->getSelfInterfaceType()))
return;
// Dig out the potential archetype for this stored type.
auto equivClass =
inProtoSigBuilder.resolveEquivalenceClass(
storedType,
ArchetypeResolutionKind::CompleteWellFormed);

assert(hasConformanceInSignature(reqs, rootType, conformingProto) &&
"missing explicit conformance in signature");
// Find the conformance of this potential archetype to the protocol in
// question.
auto conforms = equivClass->conformsTo.find(conformingProto);
assert(conforms != equivClass->conformsTo.end());

// Add the root of the path, which starts at this explicit requirement.
path.path.push_back({rootType, conformingProto});
};
// Compute the root type, canonicalizing it w.r.t. the protocol context.
auto conformsSource = getBestRequirementSource(conforms->second);
assert(conformsSource != source || !requirementSignatureProto);
Type localRootType = conformsSource->getRootType();
localRootType = inProtoSig->getCanonicalTypeInContext(localRootType);

// Build the path according to the requirement signature.
buildConformanceAccessPath(path, inProtocol->getRequirementSignature(),
conformsSource, conformingProto, localRootType,
inProtocol);

// We're done.
return;
}

// If we have a superclass or concrete requirement, the conformance
// we need is stored in it.
if (source->kind == RequirementSource::Superclass ||
source->kind == RequirementSource::Concrete) {
auto conformance = source->getProtocolConformance();
(void)conformance;
assert(conformance.getRequirement() == conformingProto);
path.push_back({source->getAffectedType(), conformingProto});
return;
}

// If we still have a parent, keep going.
if (source->parent) {
buildConformanceAccessPath(path, reqs, source->parent, conformingProto,
rootType, requirementSignatureProto);
return;
}

// We are at an explicit or inferred requirement.
assert(source->kind == RequirementSource::Explicit ||
source->kind == RequirementSource::Inferred);

// Skip trivial path elements. These occur when querying a requirement
// signature.
if (!path.empty() && conformingProto == path.back().second &&
rootType->isEqual(conformingProto->getSelfInterfaceType()))
return;

assert(hasConformanceInSignature(reqs, rootType, conformingProto) &&
"missing explicit conformance in signature");

// Add the root of the path, which starts at this explicit requirement.
path.push_back({rootType, conformingProto});
}

ConformanceAccessPath
GenericSignature::getConformanceAccessPath(Type type, ProtocolDecl *protocol) {
assert(type->isTypeParameter() && "not a type parameter");

// Resolve this type to a potential archetype.
auto &builder = *getGenericSignatureBuilder();
auto equivClass =
builder.resolveEquivalenceClass(
type,
ArchetypeResolutionKind::CompleteWellFormed);

auto cached = equivClass->conformanceAccessPathCache.find(protocol);
if (cached != equivClass->conformanceAccessPathCache.end())
return cached->second;

// Dig out the conformance of this type to the given protocol, because we
// want its requirement source.
auto conforms = equivClass->conformsTo.find(protocol);
assert(conforms != equivClass->conformsTo.end());

// Canonicalize the root type.
auto source = getBestRequirementSource(conforms->second);
Type rootType = source->getRootType()->getCanonicalType(this);

// Build the path.
buildPath(getRequirements(), source, protocol, rootType, nullptr);
SmallVector<ConformanceAccessPath::Entry, 2> path;
buildConformanceAccessPath(path, getRequirements(), source, protocol,
rootType, nullptr);

// Return the path; we're done!
return path;
ConformanceAccessPath result(getASTContext().AllocateCopy(path));
equivClass->conformanceAccessPathCache.insert({protocol, result});
return result;
}

unsigned GenericParamKey::findIndexIn(
Expand Down