Skip to content

GSB: Rewrite getConformanceAccessPath(), again #37296

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
3 changes: 3 additions & 0 deletions include/swift/AST/GenericSignature.h
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ class ConformanceAccessPath {
ConformanceAccessPath(ArrayRef<Entry> path) : path(path) {}

friend class GenericSignatureImpl;
friend class GenericSignatureBuilder;

public:
typedef const Entry *const_iterator;
Expand All @@ -88,6 +89,8 @@ class ConformanceAccessPath {
const_iterator begin() const { return path.begin(); }
const_iterator end() const { return path.end(); }

const Entry &back() const { return path.back(); }

void print(raw_ostream &OS) const;

SWIFT_DEBUG_DUMP;
Expand Down
20 changes: 16 additions & 4 deletions include/swift/AST/GenericSignatureBuilder.h
Original file line number Diff line number Diff line change
Expand Up @@ -277,10 +277,6 @@ 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 Expand Up @@ -786,6 +782,22 @@ class GenericSignatureBuilder {
Type getCanonicalTypeInContext(Type type,
TypeArrayView<GenericTypeParamType> genericParams);

/// Retrieve the conformance access path used to extract the conformance of
/// interface \c type to the given \c protocol.
///
/// \param type The interface type whose conformance access path is to be
/// queried.
/// \param protocol A protocol to which \c type conforms.
///
/// \returns the conformance access path that starts at a requirement of
/// this generic signature and ends at the conformance that makes \c type
/// conform to \c protocol.
///
/// \seealso ConformanceAccessPath
ConformanceAccessPath getConformanceAccessPath(Type type,
ProtocolDecl *protocol,
GenericSignature sig);

/// Verify the correctness of the given generic signature.
///
/// This routine will test that the given generic signature is both minimal
Expand Down
147 changes: 2 additions & 145 deletions lib/AST/GenericSignature.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -576,154 +576,11 @@ CanGenericSignature::getGenericParams() const{
return {base, params.size()};
}

namespace {
typedef GenericSignatureBuilder::RequirementSource RequirementSource;

template<typename T>
using GSBConstraint = GenericSignatureBuilder::Constraint<T>;
} // end anonymous namespace

/// Determine whether there is a conformance of the given
/// subject type to the given protocol within the given set of explicit
/// requirements.
static bool hasConformanceInSignature(ArrayRef<Requirement> requirements,
Type subjectType,
ProtocolDecl *proto) {
// Make sure this requirement exists in the requirement signature.
for (const auto &req: requirements) {
if (req.getKind() == RequirementKind::Conformance &&
req.getFirstType()->isEqual(subjectType) &&
req.getProtocolDecl() == proto) {
return true;
}
}

return false;
}

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

// Look up the equivalence class for this type.
auto &builder = *getGenericSignatureBuilder();
auto equivClass =
builder.resolveEquivalenceClass(
type,
ArchetypeResolutionKind::CompleteWellFormed);

assert(!equivClass->concreteType &&
"Concrete types don't have conformance access paths");

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());

auto rootType = equivClass->getAnchor(builder, { });
if (hasConformanceInSignature(getRequirements(), rootType, protocol)) {
ConformanceAccessPath::Entry root(rootType, protocol);
ArrayRef<ConformanceAccessPath::Entry> path(root);

ConformanceAccessPath result(builder.getASTContext().AllocateCopy(path));
equivClass->conformanceAccessPathCache.insert({protocol, result});

return result;
}

// This conformance comes from a derived source.
//
// To recover this the conformance, we recursively recover the conformance
// of the shortest parent type to the parent protocol first.
Type shortestParentType;
Type shortestSubjectType;
ProtocolDecl *shortestParentProto = nullptr;

auto isShortestPath = [&](Type parentType,
Type subjectType,
ProtocolDecl *parentProto) -> bool {
if (!shortestParentType)
return true;

int cmpParentTypes = compareDependentTypes(parentType, shortestParentType);
if (cmpParentTypes != 0)
return cmpParentTypes < 0;

int cmpSubjectTypes = compareDependentTypes(subjectType, shortestSubjectType);
if (cmpSubjectTypes != 0)
return cmpSubjectTypes < 0;

int cmpParentProtos = TypeDecl::compare(parentProto, shortestParentProto);
return cmpParentProtos < 0;
};

auto recordShortestParentType = [&](Type parentType,
Type subjectType,
ProtocolDecl *parentProto) {
if (isShortestPath(parentType, subjectType, parentProto)) {
shortestParentType = parentType;
shortestSubjectType = subjectType;
shortestParentProto = parentProto;
}
};

for (auto constraint : conforms->second) {
auto *source = constraint.source;

switch (source->kind) {
case RequirementSource::Explicit:
case RequirementSource::Inferred:
// This is not a derived source, so it contributes nothing to the
// "shortest parent type" computation.
break;

case RequirementSource::ProtocolRequirement:
case RequirementSource::InferredProtocolRequirement: {
assert(source->parent->kind != RequirementSource::RequirementSignatureSelf);

// If we have a derived conformance requirement like T[.P].X : Q, we can
// recursively compute the conformance access path for T : P, and append
// the path element (Self.X : Q).
auto parentType = source->parent->getAffectedType()->getCanonicalType();
auto subjectType = source->getStoredType()->getCanonicalType();
auto *parentProto = source->getProtocolDecl();

// We might have multiple candidate parent types and protocols for the
// recursive step, so pick the shortest one.
recordShortestParentType(parentType, subjectType, parentProto);

break;
}

default:
// There should be no other way of testifying to a conformance on a
// dependent type.
llvm_unreachable("Bad requirement source for conformance on dependent type");
}
}

assert(shortestParentType);

SmallVector<ConformanceAccessPath::Entry, 2> path;

auto parentPath = getConformanceAccessPath(
shortestParentType, shortestParentProto);
for (auto entry : parentPath)
path.push_back(entry);

// Then, we add the subject type from the parent protocol's requirement
// signature.
path.emplace_back(shortestSubjectType, protocol);

ConformanceAccessPath result(builder.getASTContext().AllocateCopy(path));
equivClass->conformanceAccessPathCache.insert({protocol, result});

return result;
return getGenericSignatureBuilder()->getConformanceAccessPath(
type, protocol, this);
}

unsigned GenericParamKey::findIndexIn(
Expand Down
123 changes: 123 additions & 0 deletions lib/AST/GenericSignatureBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -744,6 +744,12 @@ struct GenericSignatureBuilder::Implementation {

llvm::DenseSet<ExplicitRequirement> ExplicitConformancesImpliedByConcrete;

llvm::DenseMap<std::pair<CanType, ProtocolDecl *>,
ConformanceAccessPath> ConformanceAccessPaths;

std::vector<std::pair<CanType, ConformanceAccessPath>>
CurrentConformanceAccessPaths;

#ifndef NDEBUG
/// Whether we've already computed redundant requiremnts.
bool computedRedundantRequirements = false;
Expand Down Expand Up @@ -4125,6 +4131,123 @@ Type GenericSignatureBuilder::getCanonicalTypeInContext(Type type,
});
}

ConformanceAccessPath
GenericSignatureBuilder::getConformanceAccessPath(Type type,
ProtocolDecl *protocol,
GenericSignature sig) {
auto canType = getCanonicalTypeInContext(type, { })->getCanonicalType();
assert(canType->isTypeParameter());

// Check if we've already cached the result before doing anything else.
auto found = Impl->ConformanceAccessPaths.find(
std::make_pair(canType, protocol));
if (found != Impl->ConformanceAccessPaths.end()) {
return found->second;
}

FrontendStatsTracer(Context.Stats, "get-conformance-access-path");

// If this is the first time we're asked to look up a conformance access path,
// visit all of the root conformance requirements in our generic signature and
// add them to the buffer.
if (Impl->ConformanceAccessPaths.empty()) {
for (const auto &req : sig->getRequirements()) {
// We only care about conformance requirements.
if (req.getKind() != RequirementKind::Conformance)
continue;

auto rootType = req.getFirstType()->getCanonicalType();
auto *rootProto = req.getProtocolDecl();

ConformanceAccessPath::Entry root(rootType, rootProto);
ArrayRef<ConformanceAccessPath::Entry> path(root);
ConformanceAccessPath result(Context.AllocateCopy(path));

// Add the path to the buffer.
Impl->CurrentConformanceAccessPaths.emplace_back(rootType, result);

// Add the path to the map.
auto key = std::make_pair(rootType, rootProto);
auto inserted = Impl->ConformanceAccessPaths.insert(
std::make_pair(key, result));
assert(inserted.second);
(void) inserted;
}
}

// We keep going until we find the path we are looking for.
while (true) {
auto found = Impl->ConformanceAccessPaths.find(
std::make_pair(canType, protocol));
if (found != Impl->ConformanceAccessPaths.end()) {
return found->second;
}

assert(Impl->CurrentConformanceAccessPaths.size() > 0);

// Refill the buffer.
std::vector<std::pair<CanType, ConformanceAccessPath>> morePaths;

// From each path in the buffer, compute all paths of length plus one.
for (const auto &pair : Impl->CurrentConformanceAccessPaths) {
const auto &lastElt = pair.second.back();
auto *lastProto = lastElt.second;

// A copy of the current path, populated as needed.
SmallVector<ConformanceAccessPath::Entry, 4> entries;

for (const auto &req : lastProto->getRequirementSignature()) {
// We only care about conformance requirements.
if (req.getKind() != RequirementKind::Conformance)
continue;

auto nextSubjectType = req.getFirstType()->getCanonicalType();
auto *nextProto = req.getProtocolDecl();

// Compute the canonical anchor for this conformance requirement.
auto nextType = replaceSelfWithType(pair.first, nextSubjectType);
auto nextCanType = getCanonicalTypeInContext(nextType, { })
->getCanonicalType();

// Skip "derived via concrete" sources.
if (!nextCanType->isTypeParameter())
continue;

// Check if we already have a conformance access path for this anchor.
auto key = std::make_pair(nextCanType, nextProto);

// If we've already seen a path for this conformance, skip it and
// don't add it to the buffer.
if (Impl->ConformanceAccessPaths.count(key))
continue;

if (entries.empty()) {
// Fill our temporary vector.
entries.insert(entries.begin(),
pair.second.begin(),
pair.second.end());
}

// Add the next entry.
entries.emplace_back(nextSubjectType, nextProto);
ConformanceAccessPath result = Context.AllocateCopy(entries);
entries.pop_back();

// Add the path to the buffer.
morePaths.emplace_back(nextCanType, result);

// Add the path to the map.
auto inserted = Impl->ConformanceAccessPaths.insert(
std::make_pair(key, result));
assert(inserted.second);
(void) inserted;
}
}

std::swap(morePaths, Impl->CurrentConformanceAccessPaths);
}
}

TypeArrayView<GenericTypeParamType>
GenericSignatureBuilder::getGenericParams() const {
return TypeArrayView<GenericTypeParamType>(Impl->GenericParams);
Expand Down
21 changes: 21 additions & 0 deletions test/Generics/runaway_conformance_access_path.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures %s 2>&1 | %FileCheck %s
// RUN: %target-swift-frontend -emit-ir -verify %s

// Reduced from swift-futures project in the source compatibility suite.

public protocol FutureProtocol: FutureConvertible where FutureType == Self {
associatedtype Output
}

public protocol FutureConvertible {
associatedtype FutureType: FutureProtocol
}

func takesFuture<T : FutureProtocol>(_: T.Type) {}

public struct FutureHolder<T : FutureProtocol> {
// CHECK-LABEL: Generic signature: <T, U where T == U.FutureType, U : FutureConvertible>
init<U : FutureConvertible>(_: U) where U.FutureType == T {
takesFuture(T.self)
}
}