|
10 | 10 | // |
11 | 11 | //===----------------------------------------------------------------------===// |
12 | 12 | // |
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. |
15 | 26 | // |
16 | 27 | // If a rewrite rule appears exactly once in a loop and without context, the |
17 | 28 | // loop witnesses a redundancy; the rewrite rule is equivalent to traveling |
18 | 29 | // 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. |
21 | 31 | // |
22 | 32 | // Any occurrence of the rule in the remaining loops is replaced with the |
23 | 33 | // alternate definition obtained by splitting the loop that witnessed the |
24 | 34 | // redundancy. After substitution, every loop is normalized to a cyclically |
25 | 35 | // reduced left-canonical form. The loop witnessing the redundancy normalizes |
26 | 36 | // to the empty loop and is deleted. |
27 | 37 | // |
28 | | -// Iterating this process eventually produces a minimal presentation. |
| 38 | +// Iterating this process eventually produces a minimal set of rewrite rules. |
29 | 39 | // |
30 | 40 | // For a description of the general algorithm, see "A Homotopical Completion |
31 | 41 | // Procedure with Applications to Coherence of Monoids", |
@@ -102,6 +112,58 @@ RewriteLoop::findRulesAppearingOnceInEmptyContext( |
102 | 112 | return result; |
103 | 113 | } |
104 | 114 |
|
| 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 | + |
105 | 167 | /// Given a rewrite rule which appears exactly once in a loop |
106 | 168 | /// without context, return a new definition for this rewrite rule. |
107 | 169 | /// The new definition is the path obtained by deleting the |
@@ -579,7 +641,9 @@ findRuleToDelete(const llvm::DenseSet<unsigned> *redundantConformances, |
579 | 641 | replacementPath = loop.Path.splitCycleAtRule(ruleID); |
580 | 642 |
|
581 | 643 | loop.markDeleted(); |
582 | | - getRule(ruleID).markRedundant(); |
| 644 | + |
| 645 | + auto &rule = getRule(ruleID); |
| 646 | + rule.markRedundant(); |
583 | 647 |
|
584 | 648 | return ruleID; |
585 | 649 | } |
@@ -687,6 +751,8 @@ void RewriteSystem::minimizeRewriteSystem() { |
687 | 751 | // Check invariants before homotopy reduction. |
688 | 752 | verifyRewriteLoops(); |
689 | 753 |
|
| 754 | + propagateExplicitBits(); |
| 755 | + |
690 | 756 | // First pass: Eliminate all redundant rules that are not conformance rules. |
691 | 757 | performHomotopyReduction(/*redundantConformances=*/nullptr); |
692 | 758 |
|
@@ -803,8 +869,11 @@ void RewriteSystem::verifyMinimizedRules() const { |
803 | 869 | if (rule.isRedundant()) |
804 | 870 | continue; |
805 | 871 |
|
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()) { |
808 | 877 | llvm::errs() << "Simplified rule is not redundant: " << rule << "\n\n"; |
809 | 878 | dump(llvm::errs()); |
810 | 879 | abort(); |
|
0 commit comments