Skip to content

Commit 463b298

Browse files
committed
RequirementMachine: New way to tie off type witness recursion
If two equivalence classes have the same concrete type and the concrete type does not have any concrete substitutions, they are essentially equivalent. Take advantage of this by introducing same-type requirements to existing type witnesses where possible.
1 parent c32ec95 commit 463b298

File tree

3 files changed

+204
-38
lines changed

3 files changed

+204
-38
lines changed

lib/AST/RequirementMachine/EquivalenceClassMap.cpp

Lines changed: 135 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,7 @@ EquivalenceClassMap::getOrCreateEquivalenceClass(const MutableTerm &key) {
565565

566566
void EquivalenceClassMap::clear() {
567567
Map.clear();
568+
ConcreteTypeInDomainMap.clear();
568569
}
569570

570571
/// Record a protocol conformance, layout or superclass constraint on the given
@@ -578,6 +579,37 @@ void EquivalenceClassMap::addProperty(
578579
inducedRules, DebugConcreteUnification);
579580
}
580581

582+
/// For each fully-concrete type, find the shortest term having that concrete type.
583+
/// This is later used by computeConstraintTermForTypeWitness().
584+
void EquivalenceClassMap::computeConcreteTypeInDomainMap() {
585+
for (const auto &equivClass : Map) {
586+
if (!equivClass->isConcreteType())
587+
continue;
588+
589+
auto concreteType = equivClass->ConcreteType->getConcreteType();
590+
if (concreteType->hasTypeParameter())
591+
continue;
592+
593+
assert(equivClass->ConcreteType->getSubstitutions().empty());
594+
595+
auto domain = equivClass->Key.getRootProtocols();
596+
auto concreteTypeKey = std::make_pair(concreteType, domain);
597+
598+
auto found = ConcreteTypeInDomainMap.find(concreteTypeKey);
599+
if (found != ConcreteTypeInDomainMap.end()) {
600+
const auto &otherTerm = found->second;
601+
assert(equivClass->Key.compare(otherTerm, Protos) > 0 &&
602+
"Out-of-order keys?");
603+
continue;
604+
}
605+
606+
auto inserted = ConcreteTypeInDomainMap.insert(
607+
std::make_pair(concreteTypeKey, equivClass->Key));
608+
assert(inserted.second);
609+
(void) inserted;
610+
}
611+
}
612+
581613
void EquivalenceClassMap::concretizeNestedTypesFromConcreteParents(
582614
SmallVectorImpl<std::pair<MutableTerm, MutableTerm>> &inducedRules) const {
583615
for (const auto &equivClass : Map) {
@@ -709,44 +741,13 @@ void EquivalenceClassMap::concretizeNestedTypesFromConcreteParent(
709741
<< " of " << concreteType << " is " << typeWitness << "\n";
710742
}
711743

712-
auto nestedType = Atom::forAssociatedType(proto, assocType->getName(),
713-
Context);
714-
715744
MutableTerm subjectType = key;
716-
subjectType.add(nestedType);
745+
subjectType.add(Atom::forAssociatedType(proto, assocType->getName(),
746+
Context));
717747

718-
MutableTerm constraintType;
719-
720-
if (concreteType == typeWitness) {
721-
if (DebugConcretizeNestedTypes) {
722-
llvm::dbgs() << "^^ Type witness is the same as the concrete type\n";
723-
}
724-
725-
// Add a rule T.[P:A] => T.
726-
constraintType = key;
727-
728-
} else if (typeWitness->isTypeParameter()) {
729-
// The type witness is a type parameter of the form τ_0_n.X.Y...Z,
730-
// where 'n' is an index into the substitution array.
731-
//
732-
// Add a rule T => S.X.Y...Z, where S is the nth substitution term.
733-
constraintType = getRelativeTermForType(typeWitness, substitutions,
734-
Context);
735-
736-
} else {
737-
// The type witness is a concrete type.
738-
constraintType = subjectType;
739-
740-
SmallVector<Term, 3> result;
741-
auto typeWitnessSchema =
742-
remapConcreteSubstitutionSchema(typeWitness, substitutions,
743-
Context, result);
744-
745-
// Add a rule T.[P:A].[concrete: Foo.A] => T.[P:A].
746-
constraintType.add(
747-
Atom::forConcreteType(
748-
typeWitnessSchema, result, Context));
749-
}
748+
MutableTerm constraintType = computeConstraintTermForTypeWitness(
749+
key, concreteType, typeWitness, subjectType,
750+
substitutions);
750751

751752
inducedRules.emplace_back(subjectType, constraintType);
752753
if (DebugConcretizeNestedTypes) {
@@ -757,6 +758,97 @@ void EquivalenceClassMap::concretizeNestedTypesFromConcreteParent(
757758
}
758759
}
759760

761+
/// Given the key of an equivalence class known to have \p concreteType,
762+
/// together with a \p typeWitness from a conformance on that concrete
763+
/// type, return the right hand side of a rewrite rule to relate
764+
/// \p subjectType with a term representing the type witness.
765+
///
766+
/// Suppose the key is T and the subject type is T.[P:A].
767+
///
768+
/// If the type witness is an abstract type U, this produces a rewrite
769+
/// rule
770+
///
771+
/// T.[P:A] => U
772+
///
773+
/// If the type witness is a concrete type Foo, this produces a rewrite
774+
/// rule
775+
///
776+
/// T.[P:A].[concrete: Foo] => T.[P:A]
777+
///
778+
/// However, this also tries to tie off recursion first, using two
779+
/// heuristics:
780+
///
781+
/// 1) If the type witness is the same exact type as the concrete type,
782+
/// we instead produce a rewrite rule:
783+
///
784+
/// T.[P:A] => T
785+
///
786+
/// 2) If the type witness is fully concrete and we've already seen a
787+
/// shorter term V in the same domain with the same concrete type,
788+
/// we produce a rewrite rule:
789+
///
790+
/// T.[P:A] => V
791+
MutableTerm EquivalenceClassMap::computeConstraintTermForTypeWitness(
792+
const MutableTerm &key,
793+
CanType concreteType,
794+
CanType typeWitness,
795+
const MutableTerm &subjectType,
796+
ArrayRef<Term> substitutions) const {
797+
if (!typeWitness->hasTypeParameter()) {
798+
// Check if we have a shorter representative we can use.
799+
auto domain = key.getRootProtocols();
800+
auto concreteTypeKey = std::make_pair(typeWitness, domain);
801+
802+
auto found = ConcreteTypeInDomainMap.find(concreteTypeKey);
803+
if (found != ConcreteTypeInDomainMap.end()) {
804+
if (found->second != subjectType) {
805+
if (DebugConcretizeNestedTypes) {
806+
llvm::dbgs() << "^^ Type witness can re-use equivalence class of "
807+
<< found->second << "\n";
808+
}
809+
return found->second;
810+
}
811+
}
812+
}
813+
814+
if (concreteType == typeWitness) {
815+
// FIXME: This is wrong for the superclass case!
816+
//
817+
// FIXME: ConcreteTypeInDomainMap should support substitutions so
818+
// that we can remove this.
819+
820+
if (DebugConcretizeNestedTypes) {
821+
llvm::dbgs() << "^^ Type witness is the same as the concrete type\n";
822+
}
823+
824+
// Add a rule T.[P:A] => T.
825+
return key;
826+
}
827+
828+
if (typeWitness->isTypeParameter()) {
829+
// The type witness is a type parameter of the form τ_0_n.X.Y...Z,
830+
// where 'n' is an index into the substitution array.
831+
//
832+
// Add a rule T => S.X.Y...Z, where S is the nth substitution term.
833+
return getRelativeTermForType(typeWitness, substitutions, Context);
834+
}
835+
836+
// The type witness is a concrete type.
837+
MutableTerm constraintType = subjectType;
838+
839+
SmallVector<Term, 3> result;
840+
auto typeWitnessSchema =
841+
remapConcreteSubstitutionSchema(typeWitness, substitutions,
842+
Context, result);
843+
844+
// Add a rule T.[P:A].[concrete: Foo.A] => T.[P:A].
845+
constraintType.add(
846+
Atom::forConcreteType(
847+
typeWitnessSchema, result, Context));
848+
849+
return constraintType;
850+
}
851+
760852
void EquivalenceClassMap::dump(llvm::raw_ostream &out) const {
761853
out << "Equivalence class map: {\n";
762854
for (const auto &equivClass : Map) {
@@ -827,8 +919,13 @@ RewriteSystem::buildEquivalenceClassMap(EquivalenceClassMap &map,
827919
map.addProperty(pair.first, pair.second, inducedRules);
828920
}
829921

830-
// We also need to merge concrete type rules with conformance rules, by
831-
// concretizing the associated type witnesses of the concrete type.
922+
// We collect equivalence classes with fully concrete types so that we can
923+
// re-use them to tie off recursion in the next step.
924+
map.computeConcreteTypeInDomainMap();
925+
926+
// Now, we merge concrete type rules with conformance rules, by adding
927+
// relations between associated type members of type parameters with
928+
// the concrete type witnesses in the concrete type's conformance.
832929
map.concretizeNestedTypesFromConcreteParents(inducedRules);
833930

834931
// Some of the induced rules might be trivial; only count the induced rules

lib/AST/RequirementMachine/EquivalenceClassMap.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ class EquivalenceClass {
111111
class EquivalenceClassMap {
112112
RewriteContext &Context;
113113
std::vector<std::unique_ptr<EquivalenceClass>> Map;
114+
115+
using ConcreteTypeInDomain = std::pair<CanType, ArrayRef<const ProtocolDecl *>>;
116+
llvm::DenseMap<ConcreteTypeInDomain, MutableTerm> ConcreteTypeInDomainMap;
117+
114118
const ProtocolGraph &Protos;
115119
unsigned DebugConcreteUnification : 1;
116120
unsigned DebugConcretizeNestedTypes : 1;
@@ -138,6 +142,8 @@ class EquivalenceClassMap {
138142
void clear();
139143
void addProperty(const MutableTerm &key, Atom property,
140144
SmallVectorImpl<std::pair<MutableTerm, MutableTerm>> &inducedRules);
145+
146+
void computeConcreteTypeInDomainMap();
141147
void concretizeNestedTypesFromConcreteParents(
142148
SmallVectorImpl<std::pair<MutableTerm, MutableTerm>> &inducedRules) const;
143149

@@ -148,6 +154,13 @@ class EquivalenceClassMap {
148154
ArrayRef<Term> substitutions,
149155
ArrayRef<const ProtocolDecl *> conformsTo,
150156
SmallVectorImpl<std::pair<MutableTerm, MutableTerm>> &inducedRules) const;
157+
158+
MutableTerm computeConstraintTermForTypeWitness(
159+
const MutableTerm &key,
160+
CanType concreteType,
161+
CanType typeWitness,
162+
const MutableTerm &subjectType,
163+
ArrayRef<Term> substitutions) const;
151164
};
152165

153166
} // end namespace rewriting
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// RUN: %target-typecheck-verify-swift -enable-requirement-machine -debug-requirement-machine 2>&1 | %FileCheck %s
2+
3+
protocol P1 {
4+
associatedtype A : P1
5+
associatedtype B : P1
6+
}
7+
8+
struct S1 : P1 {
9+
typealias A = S1
10+
typealias B = S2
11+
}
12+
13+
struct S2 : P1 {
14+
typealias A = S2
15+
typealias B = S1
16+
}
17+
18+
protocol P2 {
19+
associatedtype A where A == S1
20+
associatedtype B where B == S2
21+
}
22+
23+
struct G<T : P1 & P2> {}
24+
25+
// T.A and T.B become concrete, which produces the following series of
26+
// concretized nested types:
27+
//
28+
// T.A.[concrete: S1]
29+
// T.B.[concrete: S2]
30+
// T.A.A.[concrete: S1]
31+
// T.A.B.[concrete: S2]
32+
// T.B.A.[concrete: S2]
33+
// T.B.B.[concrete: S1]
34+
// ...
35+
//
36+
// This would normally go on forever, but since S1 and S2 are not generic,
37+
// we solve this by merging the repeated types with T.A or T.B:
38+
//
39+
// T.A.A => T.A
40+
// T.A.B => T.B
41+
// T.B.A => T.B
42+
// T.B.B => T.A
43+
// ...
44+
45+
// CHECK-LABEL: Requirement machine for <τ_0_0 where τ_0_0 : P1, τ_0_0 : P2>
46+
// CHECK-LABEL: Rewrite system: {
47+
// CHECK: - τ_0_0.[P1&P2:A].[P1:A] => τ_0_0.[P1&P2:A]
48+
// CHECK: - τ_0_0.[P1&P2:A].[P1:B] => τ_0_0.[P1&P2:B]
49+
// CHECK: - τ_0_0.[P1&P2:B].[P1:A] => τ_0_0.[P1&P2:B]
50+
// CHECK: - τ_0_0.[P1&P2:B].[P1:B] => τ_0_0.[P1&P2:A]
51+
// CHECK: }
52+
// CHECK-LABEL: Equivalence class map: {
53+
// CHECK: τ_0_0.[P1&P2:A] => { conforms_to: [P1] concrete_type: [concrete: S1] }
54+
// CHECK: τ_0_0.[P1&P2:B] => { conforms_to: [P1] concrete_type: [concrete: S2] }
55+
// CHECK: }
56+
// CHECK: }

0 commit comments

Comments
 (0)