1+ // ===--- LeftCanonicalForm.cpp - Left canonical form of a rewrite path ----===//
2+ //
3+ // This source file is part of the Swift.org open source project
4+ //
5+ // Copyright (c) 2021 Apple Inc. and the Swift project authors
6+ // Licensed under Apache License v2.0 with Runtime Library Exception
7+ //
8+ // See https://swift.org/LICENSE.txt for license information
9+ // See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+ //
11+ // ===----------------------------------------------------------------------===//
12+ //
13+ // Algorithm for reducing a rewrite path to left-canonical form:
14+ //
15+ // - Adjacent steps that are inverses of each other cancel out. for example
16+ // these two steps will be eliminated:
17+ //
18+ // (A => B) ⊗ (B => A)
19+ //
20+ // - Interchange law moves rewrites "to the left", for example
21+ //
22+ // X.(U => V) ⊗ (X => Y).V
23+ //
24+ // becomes
25+ //
26+ // (X => Y).U ⊗ Y.(U => V)
27+ //
28+ // These two transformations are iterated until fixed point to produce a
29+ // equivalent rewrite path that is simpler.
30+ //
31+ // From "Homotopy reduction systems for monoid presentations",
32+ // https://www.sciencedirect.com/science/article/pii/S0022404997000959
33+ //
34+ // ===----------------------------------------------------------------------===//
35+
36+ #include " RewriteLoop.h"
37+ #include " RewriteSystem.h"
38+ #include " llvm/ADT/SmallVector.h"
39+ #include < utility>
40+
41+ using namespace swift ;
42+ using namespace rewriting ;
43+
44+ // / Returns true if this rewrite step is an inverse of \p other
45+ // / (and vice versa).
46+ bool RewriteStep::isInverseOf (const RewriteStep &other) const {
47+ if (Kind != other.Kind )
48+ return false ;
49+
50+ if (StartOffset != other.StartOffset )
51+ return false ;
52+
53+ if (Inverse != !other.Inverse )
54+ return false ;
55+
56+ switch (Kind) {
57+ case RewriteStep::Rule:
58+ return Arg == other.Arg ;
59+
60+ default :
61+ return false ;
62+ }
63+
64+ assert (EndOffset == other.EndOffset && " Bad whiskering?" );
65+ return true ;
66+ }
67+
68+ bool RewriteStep::maybeSwapRewriteSteps (RewriteStep &other,
69+ const RewriteSystem &system) {
70+ if (Kind != RewriteStep::Rule ||
71+ other.Kind != RewriteStep::Rule)
72+ return false ;
73+
74+ // Two rewrite steps are _orthogonal_ if they rewrite disjoint subterms
75+ // in context. Orthogonal rewrite steps commute, so we can canonicalize
76+ // a path by placing the left-most step first.
77+ //
78+ // Eg, A.U.B.(X => Y).C ⊗ A.(U => V).B.Y == A.(U => V).B.X ⊗ A.V.B.(X => Y).
79+ //
80+ // Or, in diagram form. We want to turn this:
81+ //
82+ // ----- time ----->
83+ // +---------+---------+
84+ // | A | A |
85+ // +---------+---------+
86+ // | U | U ==> V |
87+ // +---------+---------+
88+ // | B | B |
89+ // +---------+---------+
90+ // | X ==> Y | Y |
91+ // +---------+---------+
92+ // | C | C |
93+ // +---------+---------+
94+ //
95+ // Into this:
96+ //
97+ // +---------+---------+
98+ // | A | A |
99+ // +---------+---------+
100+ // | U ==> V | V |
101+ // +---------+---------+
102+ // | B | B |
103+ // +---------+---------+
104+ // | X | X ==> Y |
105+ // +---------+---------+
106+ // | C | C |
107+ // +---------+---------+
108+ //
109+ // Note that
110+ //
111+ // StartOffset == |A|+|U|+|B|
112+ // EndOffset = |C|
113+ //
114+ // other.StartOffset = |A|
115+ // other.EndOffset = |B|+|Y|+|C|
116+ //
117+ // After interchange, we adjust:
118+ //
119+ // StartOffset = |A|
120+ // EndOffset = |B|+|X|+|C|
121+ //
122+ // other.StartOffset = |A|+|V|+|B|
123+ // other.EndOffset = |C|
124+
125+ const auto &rule = system.getRule (Arg);
126+ auto lhs = (Inverse ? rule.getRHS () : rule.getLHS ());
127+ auto rhs = (Inverse ? rule.getLHS () : rule.getRHS ());
128+
129+ const auto &otherRule = system.getRule (other.Arg );
130+ auto otherLHS = (other.Inverse ? otherRule.getRHS () : otherRule.getLHS ());
131+ auto otherRHS = (other.Inverse ? otherRule.getLHS () : otherRule.getRHS ());
132+
133+ if (StartOffset < other.StartOffset + otherLHS.size ())
134+ return false ;
135+
136+ std::swap (*this , other);
137+ EndOffset += (lhs.size () - rhs.size ());
138+ other.StartOffset += (otherRHS.size () - otherLHS.size ());
139+
140+ return true ;
141+ }
142+
143+ // / Cancels out adjacent rewrite steps that are inverses of each other.
144+ // / This does not change either endpoint of the path, and the path does
145+ // / not necessarily need to be a loop.
146+ bool RewritePath::computeFreelyReducedForm () {
147+ SmallVector<RewriteStep, 4 > newSteps;
148+ bool changed = false ;
149+
150+ for (const auto &step : Steps) {
151+ if (!newSteps.empty () &&
152+ newSteps.back ().isInverseOf (step)) {
153+ changed = true ;
154+ newSteps.pop_back ();
155+ continue ;
156+ }
157+
158+ newSteps.push_back (step);
159+ }
160+
161+ if (!changed)
162+ return false ;
163+ std::swap (newSteps, Steps);
164+ return changed;
165+ }
166+
167+ // / Apply the interchange rule until fixed point (see maybeSwapRewriteSteps()).
168+ bool RewritePath::computeLeftCanonicalForm (const RewriteSystem &system) {
169+ bool changed = false ;
170+
171+ for (unsigned i = 1 , e = Steps.size (); i < e; ++i) {
172+ auto &prevStep = Steps[i - 1 ];
173+ auto &step = Steps[i];
174+
175+ if (prevStep.maybeSwapRewriteSteps (step, system))
176+ changed = true ;
177+ }
178+
179+ return changed;
180+ }
181+
182+ // / Compute freely-reduced left-canonical normal form of a path.
183+ void RewritePath::computeNormalForm (const RewriteSystem &system) {
184+ // FIXME: This can be more efficient.
185+ bool changed;
186+ do {
187+ changed = false ;
188+ changed |= computeFreelyReducedForm ();
189+ changed |= computeLeftCanonicalForm (system);
190+ } while (changed);
191+ }
192+
193+ // / Given a path that is a loop around the given basepoint, cancels out
194+ // / pairs of terms from the ends that are inverses of each other, applying
195+ // / the corresponding translation to the basepoint.
196+ // /
197+ // / For example, consider this loop with basepoint 'X':
198+ // /
199+ // / (X => Y.A) * (Y.A => Y.B) * Y.(B => A) * (Y.A => X)
200+ // /
201+ // / The first step is the inverse of the last step, so the cyclic
202+ // / reduction is the loop (Y.A => Y.B) * Y.(B => A), with a new
203+ // / basepoint 'Y.A'.
204+ bool RewritePath::computeCyclicallyReducedForm (MutableTerm &basepoint,
205+ const RewriteSystem &system) {
206+ RewritePathEvaluator evaluator (basepoint);
207+ unsigned count = 0 ;
208+
209+ while (2 * count + 1 < size ()) {
210+ auto left = Steps[count];
211+ auto right = Steps[Steps.size () - count - 1 ];
212+ if (!left.isInverseOf (right))
213+ break ;
214+
215+ // Update the basepoint by applying the first step in the path.
216+ evaluator.apply (left, system);
217+
218+ ++count;
219+ }
220+
221+ std::rotate (Steps.begin (), Steps.begin () + count, Steps.end () - count);
222+ Steps.erase (Steps.end () - 2 * count, Steps.end ());
223+
224+ basepoint = evaluator.getCurrentTerm ();
225+ return count > 0 ;
226+ }
227+
228+ // / Compute cyclically-reduced left-canonical normal form of a loop.
229+ void RewriteLoop::computeNormalForm (const RewriteSystem &system) {
230+ // FIXME: This can be more efficient.
231+ bool changed;
232+ do {
233+ changed = false ;
234+ changed |= Path.computeFreelyReducedForm ();
235+ changed |= Path.computeCyclicallyReducedForm (Basepoint, system);
236+ changed |= Path.computeLeftCanonicalForm (system);
237+
238+ if (changed)
239+ markDirty ();
240+ } while (changed);
241+ }
0 commit comments