Skip to content

Commit e6ff771

Browse files
committed
GSB: Rewrite getConformanceAccessPath(), again
The new approach is to not look at RequirementSources at all. Instead, we exhaustively enumerate all conformance access paths, beginning from the root conformance requirements in the signature, then doing all conformance requirements from those protocols' requirement signatures, and so on. We enumerate conformance access paths in breadth first order by length until we find the one we want. The results are memoized. This fixes a regression with another change I'm working on. The test case does not fail with this PR alone, but I'm adding it now anyway.
1 parent 1263da1 commit e6ff771

File tree

5 files changed

+165
-149
lines changed

5 files changed

+165
-149
lines changed

include/swift/AST/GenericSignature.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class ConformanceAccessPath {
8080
ConformanceAccessPath(ArrayRef<Entry> path) : path(path) {}
8181

8282
friend class GenericSignatureImpl;
83+
friend class GenericSignatureBuilder;
8384

8485
public:
8586
typedef const Entry *const_iterator;
@@ -88,6 +89,8 @@ class ConformanceAccessPath {
8889
const_iterator begin() const { return path.begin(); }
8990
const_iterator end() const { return path.end(); }
9091

92+
const Entry &back() const { return path.back(); }
93+
9194
void print(raw_ostream &OS) const;
9295

9396
SWIFT_DEBUG_DUMP;

include/swift/AST/GenericSignatureBuilder.h

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,6 @@ class GenericSignatureBuilder {
277277
/// Cached nested-type information, which contains the best declaration
278278
/// for a given name.
279279
llvm::SmallDenseMap<Identifier, CachedNestedType> nestedTypeNameCache;
280-
281-
/// Cached access paths.
282-
llvm::SmallDenseMap<const ProtocolDecl *, ConformanceAccessPath, 8>
283-
conformanceAccessPathCache;
284280
};
285281

286282
friend class RequirementSource;
@@ -786,6 +782,22 @@ class GenericSignatureBuilder {
786782
Type getCanonicalTypeInContext(Type type,
787783
TypeArrayView<GenericTypeParamType> genericParams);
788784

785+
/// Retrieve the conformance access path used to extract the conformance of
786+
/// interface \c type to the given \c protocol.
787+
///
788+
/// \param type The interface type whose conformance access path is to be
789+
/// queried.
790+
/// \param protocol A protocol to which \c type conforms.
791+
///
792+
/// \returns the conformance access path that starts at a requirement of
793+
/// this generic signature and ends at the conformance that makes \c type
794+
/// conform to \c protocol.
795+
///
796+
/// \seealso ConformanceAccessPath
797+
ConformanceAccessPath getConformanceAccessPath(Type type,
798+
ProtocolDecl *protocol,
799+
GenericSignature sig);
800+
789801
/// Verify the correctness of the given generic signature.
790802
///
791803
/// This routine will test that the given generic signature is both minimal

lib/AST/GenericSignature.cpp

Lines changed: 2 additions & 145 deletions
Original file line numberDiff line numberDiff line change
@@ -576,154 +576,11 @@ CanGenericSignature::getGenericParams() const{
576576
return {base, params.size()};
577577
}
578578

579-
namespace {
580-
typedef GenericSignatureBuilder::RequirementSource RequirementSource;
581-
582-
template<typename T>
583-
using GSBConstraint = GenericSignatureBuilder::Constraint<T>;
584-
} // end anonymous namespace
585-
586-
/// Determine whether there is a conformance of the given
587-
/// subject type to the given protocol within the given set of explicit
588-
/// requirements.
589-
static bool hasConformanceInSignature(ArrayRef<Requirement> requirements,
590-
Type subjectType,
591-
ProtocolDecl *proto) {
592-
// Make sure this requirement exists in the requirement signature.
593-
for (const auto &req: requirements) {
594-
if (req.getKind() == RequirementKind::Conformance &&
595-
req.getFirstType()->isEqual(subjectType) &&
596-
req.getProtocolDecl() == proto) {
597-
return true;
598-
}
599-
}
600-
601-
return false;
602-
}
603-
604579
ConformanceAccessPath
605580
GenericSignatureImpl::getConformanceAccessPath(Type type,
606581
ProtocolDecl *protocol) const {
607-
assert(type->isTypeParameter() && "not a type parameter");
608-
609-
// Look up the equivalence class for this type.
610-
auto &builder = *getGenericSignatureBuilder();
611-
auto equivClass =
612-
builder.resolveEquivalenceClass(
613-
type,
614-
ArchetypeResolutionKind::CompleteWellFormed);
615-
616-
assert(!equivClass->concreteType &&
617-
"Concrete types don't have conformance access paths");
618-
619-
auto cached = equivClass->conformanceAccessPathCache.find(protocol);
620-
if (cached != equivClass->conformanceAccessPathCache.end())
621-
return cached->second;
622-
623-
// Dig out the conformance of this type to the given protocol, because we
624-
// want its requirement source.
625-
auto conforms = equivClass->conformsTo.find(protocol);
626-
assert(conforms != equivClass->conformsTo.end());
627-
628-
auto rootType = equivClass->getAnchor(builder, { });
629-
if (hasConformanceInSignature(getRequirements(), rootType, protocol)) {
630-
ConformanceAccessPath::Entry root(rootType, protocol);
631-
ArrayRef<ConformanceAccessPath::Entry> path(root);
632-
633-
ConformanceAccessPath result(builder.getASTContext().AllocateCopy(path));
634-
equivClass->conformanceAccessPathCache.insert({protocol, result});
635-
636-
return result;
637-
}
638-
639-
// This conformance comes from a derived source.
640-
//
641-
// To recover this the conformance, we recursively recover the conformance
642-
// of the shortest parent type to the parent protocol first.
643-
Type shortestParentType;
644-
Type shortestSubjectType;
645-
ProtocolDecl *shortestParentProto = nullptr;
646-
647-
auto isShortestPath = [&](Type parentType,
648-
Type subjectType,
649-
ProtocolDecl *parentProto) -> bool {
650-
if (!shortestParentType)
651-
return true;
652-
653-
int cmpParentTypes = compareDependentTypes(parentType, shortestParentType);
654-
if (cmpParentTypes != 0)
655-
return cmpParentTypes < 0;
656-
657-
int cmpSubjectTypes = compareDependentTypes(subjectType, shortestSubjectType);
658-
if (cmpSubjectTypes != 0)
659-
return cmpSubjectTypes < 0;
660-
661-
int cmpParentProtos = TypeDecl::compare(parentProto, shortestParentProto);
662-
return cmpParentProtos < 0;
663-
};
664-
665-
auto recordShortestParentType = [&](Type parentType,
666-
Type subjectType,
667-
ProtocolDecl *parentProto) {
668-
if (isShortestPath(parentType, subjectType, parentProto)) {
669-
shortestParentType = parentType;
670-
shortestSubjectType = subjectType;
671-
shortestParentProto = parentProto;
672-
}
673-
};
674-
675-
for (auto constraint : conforms->second) {
676-
auto *source = constraint.source;
677-
678-
switch (source->kind) {
679-
case RequirementSource::Explicit:
680-
case RequirementSource::Inferred:
681-
// This is not a derived source, so it contributes nothing to the
682-
// "shortest parent type" computation.
683-
break;
684-
685-
case RequirementSource::ProtocolRequirement:
686-
case RequirementSource::InferredProtocolRequirement: {
687-
assert(source->parent->kind != RequirementSource::RequirementSignatureSelf);
688-
689-
// If we have a derived conformance requirement like T[.P].X : Q, we can
690-
// recursively compute the conformance access path for T : P, and append
691-
// the path element (Self.X : Q).
692-
auto parentType = source->parent->getAffectedType()->getCanonicalType();
693-
auto subjectType = source->getStoredType()->getCanonicalType();
694-
auto *parentProto = source->getProtocolDecl();
695-
696-
// We might have multiple candidate parent types and protocols for the
697-
// recursive step, so pick the shortest one.
698-
recordShortestParentType(parentType, subjectType, parentProto);
699-
700-
break;
701-
}
702-
703-
default:
704-
// There should be no other way of testifying to a conformance on a
705-
// dependent type.
706-
llvm_unreachable("Bad requirement source for conformance on dependent type");
707-
}
708-
}
709-
710-
assert(shortestParentType);
711-
712-
SmallVector<ConformanceAccessPath::Entry, 2> path;
713-
714-
auto parentPath = getConformanceAccessPath(
715-
shortestParentType, shortestParentProto);
716-
for (auto entry : parentPath)
717-
path.push_back(entry);
718-
719-
// Then, we add the subject type from the parent protocol's requirement
720-
// signature.
721-
path.emplace_back(shortestSubjectType, protocol);
722-
723-
ConformanceAccessPath result(builder.getASTContext().AllocateCopy(path));
724-
equivClass->conformanceAccessPathCache.insert({protocol, result});
725-
726-
return result;
582+
return getGenericSignatureBuilder()->getConformanceAccessPath(
583+
type, protocol, this);
727584
}
728585

729586
unsigned GenericParamKey::findIndexIn(

lib/AST/GenericSignatureBuilder.cpp

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,12 @@ struct GenericSignatureBuilder::Implementation {
744744

745745
llvm::DenseSet<ExplicitRequirement> ExplicitConformancesImpliedByConcrete;
746746

747+
llvm::DenseMap<std::pair<CanType, ProtocolDecl *>,
748+
ConformanceAccessPath> ConformanceAccessPaths;
749+
750+
std::vector<std::pair<CanType, ConformanceAccessPath>>
751+
CurrentConformanceAccessPaths;
752+
747753
#ifndef NDEBUG
748754
/// Whether we've already computed redundant requiremnts.
749755
bool computedRedundantRequirements = false;
@@ -4125,6 +4131,123 @@ Type GenericSignatureBuilder::getCanonicalTypeInContext(Type type,
41254131
});
41264132
}
41274133

4134+
ConformanceAccessPath
4135+
GenericSignatureBuilder::getConformanceAccessPath(Type type,
4136+
ProtocolDecl *protocol,
4137+
GenericSignature sig) {
4138+
auto canType = getCanonicalTypeInContext(type, { })->getCanonicalType();
4139+
assert(canType->isTypeParameter());
4140+
4141+
// Check if we've already cached the result before doing anything else.
4142+
auto found = Impl->ConformanceAccessPaths.find(
4143+
std::make_pair(canType, protocol));
4144+
if (found != Impl->ConformanceAccessPaths.end()) {
4145+
return found->second;
4146+
}
4147+
4148+
FrontendStatsTracer(Context.Stats, "get-conformance-access-path");
4149+
4150+
// If this is the first time we're asked to look up a conformance access path,
4151+
// visit all of the root conformance requirements in our generic signature and
4152+
// add them to the buffer.
4153+
if (Impl->ConformanceAccessPaths.empty()) {
4154+
for (const auto &req : sig->getRequirements()) {
4155+
// We only care about conformance requirements.
4156+
if (req.getKind() != RequirementKind::Conformance)
4157+
continue;
4158+
4159+
auto rootType = req.getFirstType()->getCanonicalType();
4160+
auto *rootProto = req.getProtocolDecl();
4161+
4162+
ConformanceAccessPath::Entry root(rootType, rootProto);
4163+
ArrayRef<ConformanceAccessPath::Entry> path(root);
4164+
ConformanceAccessPath result(Context.AllocateCopy(path));
4165+
4166+
// Add the path to the buffer.
4167+
Impl->CurrentConformanceAccessPaths.emplace_back(rootType, result);
4168+
4169+
// Add the path to the map.
4170+
auto key = std::make_pair(rootType, rootProto);
4171+
auto inserted = Impl->ConformanceAccessPaths.insert(
4172+
std::make_pair(key, result));
4173+
assert(inserted.second);
4174+
(void) inserted;
4175+
}
4176+
}
4177+
4178+
// We keep going until we find the path we are looking for.
4179+
while (true) {
4180+
auto found = Impl->ConformanceAccessPaths.find(
4181+
std::make_pair(canType, protocol));
4182+
if (found != Impl->ConformanceAccessPaths.end()) {
4183+
return found->second;
4184+
}
4185+
4186+
assert(Impl->CurrentConformanceAccessPaths.size() > 0);
4187+
4188+
// Refill the buffer.
4189+
std::vector<std::pair<CanType, ConformanceAccessPath>> morePaths;
4190+
4191+
// From each path in the buffer, compute all paths of length plus one.
4192+
for (const auto &pair : Impl->CurrentConformanceAccessPaths) {
4193+
const auto &lastElt = pair.second.back();
4194+
auto *lastProto = lastElt.second;
4195+
4196+
// A copy of the current path, populated as needed.
4197+
SmallVector<ConformanceAccessPath::Entry, 4> entries;
4198+
4199+
for (const auto &req : lastProto->getRequirementSignature()) {
4200+
// We only care about conformance requirements.
4201+
if (req.getKind() != RequirementKind::Conformance)
4202+
continue;
4203+
4204+
auto nextSubjectType = req.getFirstType()->getCanonicalType();
4205+
auto *nextProto = req.getProtocolDecl();
4206+
4207+
// Compute the canonical anchor for this conformance requirement.
4208+
auto nextType = replaceSelfWithType(pair.first, nextSubjectType);
4209+
auto nextCanType = getCanonicalTypeInContext(nextType, { })
4210+
->getCanonicalType();
4211+
4212+
// Skip "derived via concrete" sources.
4213+
if (!nextCanType->isTypeParameter())
4214+
continue;
4215+
4216+
// Check if we already have a conformance access path for this anchor.
4217+
auto key = std::make_pair(nextCanType, nextProto);
4218+
4219+
// If we've already seen a path for this conformance, skip it and
4220+
// don't add it to the buffer.
4221+
if (Impl->ConformanceAccessPaths.count(key))
4222+
continue;
4223+
4224+
if (entries.empty()) {
4225+
// Fill our temporary vector.
4226+
entries.insert(entries.begin(),
4227+
pair.second.begin(),
4228+
pair.second.end());
4229+
}
4230+
4231+
// Add the next entry.
4232+
entries.emplace_back(nextSubjectType, nextProto);
4233+
ConformanceAccessPath result = Context.AllocateCopy(entries);
4234+
entries.pop_back();
4235+
4236+
// Add the path to the buffer.
4237+
morePaths.emplace_back(nextCanType, result);
4238+
4239+
// Add the path to the map.
4240+
auto inserted = Impl->ConformanceAccessPaths.insert(
4241+
std::make_pair(key, result));
4242+
assert(inserted.second);
4243+
(void) inserted;
4244+
}
4245+
}
4246+
4247+
std::swap(morePaths, Impl->CurrentConformanceAccessPaths);
4248+
}
4249+
}
4250+
41284251
TypeArrayView<GenericTypeParamType>
41294252
GenericSignatureBuilder::getGenericParams() const {
41304253
return TypeArrayView<GenericTypeParamType>(Impl->GenericParams);
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// RUN: %target-swift-frontend -typecheck -debug-generic-signatures %s 2>&1 | %FileCheck %s
2+
// RUN: %target-swift-frontend -emit-ir -verify %s
3+
4+
// Reduced from swift-futures project in the source compatibility suite.
5+
6+
public protocol FutureProtocol: FutureConvertible where FutureType == Self {
7+
associatedtype Output
8+
}
9+
10+
public protocol FutureConvertible {
11+
associatedtype FutureType: FutureProtocol
12+
}
13+
14+
func takesFuture<T : FutureProtocol>(_: T.Type) {}
15+
16+
public struct FutureHolder<T : FutureProtocol> {
17+
// CHECK-LABEL: Generic signature: <T, U where T == U.FutureType, U : FutureConvertible>
18+
init<U : FutureConvertible>(_: U) where U.FutureType == T {
19+
takesFuture(T.self)
20+
}
21+
}

0 commit comments

Comments
 (0)