Skip to content

Commit dcefd70

Browse files
committed
RequirementMachine: Homotopy reduction propagates Rule::isExplicit() bit
If a rewrite loop contains two rules in empty context and one of them is explicit, propagate the explicit bit to the other rule if the original rule was found to be redundant. This means that if we begin with an unresolved rule like [P].X.[Q] => [P].X [explicit] And then completion added the resolved rule [P:X].[Q] => [P:X] Then the new rule will become explicit once the original rule becomes redundant.
1 parent 8ab1c1f commit dcefd70

File tree

2 files changed

+79
-8
lines changed

2 files changed

+79
-8
lines changed

lib/AST/RequirementMachine/HomotopyReduction.cpp

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,22 +10,32 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212
//
13-
// A confluent rewrite system together with a set of rewrite loops that generate
14-
// the homotopy relation on rewrite paths is known as a 'coherent presentation'.
13+
//
14+
// This file implements the algorithm for computing a minimal set of rules from
15+
// a confluent rewrite system. A minimal set of rules is:
16+
//
17+
// 1) Large enough that computing the confluent completion produces the original
18+
// rewrite system;
19+
//
20+
// 2) Small enough that no further rules can be deleted without changing the
21+
// resulting confluent rewrite system.
22+
//
23+
// Redundant rules that are not part of the minimal set are redundant are
24+
// detected by analyzing the set of rewrite loops computed by the completion
25+
// procedure.
1526
//
1627
// If a rewrite rule appears exactly once in a loop and without context, the
1728
// loop witnesses a redundancy; the rewrite rule is equivalent to traveling
1829
// around the loop "in the other direction". This rewrite rule and the
19-
// corresponding loop can be deleted from the coherent presentation via a
20-
// Tietze transformation.
30+
// corresponding rewrite loop can be deleted.
2131
//
2232
// Any occurrence of the rule in the remaining loops is replaced with the
2333
// alternate definition obtained by splitting the loop that witnessed the
2434
// redundancy. After substitution, every loop is normalized to a cyclically
2535
// reduced left-canonical form. The loop witnessing the redundancy normalizes
2636
// to the empty loop and is deleted.
2737
//
28-
// Iterating this process eventually produces a minimal presentation.
38+
// Iterating this process eventually produces a minimal set of rewrite rules.
2939
//
3040
// For a description of the general algorithm, see "A Homotopical Completion
3141
// Procedure with Applications to Coherence of Monoids",
@@ -102,6 +112,58 @@ RewriteLoop::findRulesAppearingOnceInEmptyContext(
102112
return result;
103113
}
104114

115+
/// If a rewrite loop contains an explicit rule in empty context, propagate the
116+
/// explicit bit to all other rules appearing in empty context within the same
117+
/// loop.
118+
///
119+
/// When computing generating conformances we prefer to eliminate non-explicit
120+
/// rules, as a heuristic to ensure that minimized conformance requirements
121+
/// remain in the same protocol as originally written, in cases where they can
122+
/// be moved between protocols.
123+
///
124+
/// However, conformance rules can also be written in a non-canonical way.
125+
///
126+
/// Most conformance requirements are non-canonical, since the original
127+
/// requirements use unresolved types. For example, a requirement 'Self.X.Y : Q'
128+
/// inside a protocol P will lower to a rewrite rule
129+
///
130+
/// [P].X.Y.[Q] => [P].X.Y
131+
///
132+
/// Completion will then add a new rule that looks something like this, using
133+
/// associated type symbols:
134+
///
135+
/// [P:X].[P2:Y].[Q] => [P:X].[P2:Y]
136+
///
137+
/// Furthermore, if [P:X].[P2:Y] simplies to some other term, such as [P:Z],
138+
/// there will be yet another rule added by completion:
139+
///
140+
/// [P:Z].[Q] => [P:Z]
141+
///
142+
/// The new rules are related to the original rule via rewrite loops where
143+
/// both rules appear in empty context. This algorithm will propagate the
144+
/// explicit bit from the original rule to the canonical rule.
145+
void RewriteSystem::propagateExplicitBits() {
146+
for (const auto &loop : Loops) {
147+
SmallVector<unsigned, 1> rulesInEmptyContext =
148+
loop.findRulesAppearingOnceInEmptyContext(*this);
149+
150+
bool sawExplicitRule = false;
151+
152+
for (unsigned ruleID : rulesInEmptyContext) {
153+
const auto &rule = getRule(ruleID);
154+
if (rule.isExplicit())
155+
sawExplicitRule = true;
156+
}
157+
if (sawExplicitRule) {
158+
for (unsigned ruleID : rulesInEmptyContext) {
159+
auto &rule = getRule(ruleID);
160+
if (!rule.isPermanent() && !rule.isExplicit())
161+
rule.markExplicit();
162+
}
163+
}
164+
}
165+
}
166+
105167
/// Given a rewrite rule which appears exactly once in a loop
106168
/// without context, return a new definition for this rewrite rule.
107169
/// The new definition is the path obtained by deleting the
@@ -579,7 +641,9 @@ findRuleToDelete(const llvm::DenseSet<unsigned> *redundantConformances,
579641
replacementPath = loop.Path.splitCycleAtRule(ruleID);
580642

581643
loop.markDeleted();
582-
getRule(ruleID).markRedundant();
644+
645+
auto &rule = getRule(ruleID);
646+
rule.markRedundant();
583647

584648
return ruleID;
585649
}
@@ -687,6 +751,8 @@ void RewriteSystem::minimizeRewriteSystem() {
687751
// Check invariants before homotopy reduction.
688752
verifyRewriteLoops();
689753

754+
propagateExplicitBits();
755+
690756
// First pass: Eliminate all redundant rules that are not conformance rules.
691757
performHomotopyReduction(/*redundantConformances=*/nullptr);
692758

@@ -803,8 +869,11 @@ void RewriteSystem::verifyMinimizedRules() const {
803869
if (rule.isRedundant())
804870
continue;
805871

806-
// Simplified rules should be redundant.
807-
if (rule.isSimplified()) {
872+
// Simplified rules should be redundant, unless they're protocol conformance
873+
// rules, which unfortunately might no be redundant, because we try to keep
874+
// them in the original protocol definition for compatibility with the
875+
// GenericSignatureBuilder's minimization algorithm.
876+
if (rule.isSimplified() && !rule.isProtocolConformanceRule()) {
808877
llvm::errs() << "Simplified rule is not redundant: " << rule << "\n\n";
809878
dump(llvm::errs());
810879
abort();

lib/AST/RequirementMachine/RewriteSystem.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ class RewriteSystem final {
322322
///
323323
//////////////////////////////////////////////////////////////////////////////
324324

325+
void propagateExplicitBits();
326+
325327
bool
326328
isCandidateForDeletion(unsigned ruleID,
327329
const llvm::DenseSet<unsigned> *redundantConformances) const;

0 commit comments

Comments
 (0)