|
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