mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
301 lines
8.8 KiB
C++
301 lines
8.8 KiB
C++
//===--- NormalizeRewritePath.cpp - Canonical form of a rewrite path ------===//
|
|
//
|
|
// This source file is part of the Swift.org open source project
|
|
//
|
|
// Copyright (c) 2021 Apple Inc. and the Swift project authors
|
|
// Licensed under Apache License v2.0 with Runtime Library Exception
|
|
//
|
|
// See https://swift.org/LICENSE.txt for license information
|
|
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Algorithm for reducing a rewrite path to left-canonical form:
|
|
//
|
|
// - Adjacent steps that are inverses of each other cancel out. for example
|
|
// these two steps will be eliminated:
|
|
//
|
|
// (A => B) ⊗ (B => A)
|
|
//
|
|
// - Interchange law moves rewrites "to the left", for example
|
|
//
|
|
// X.(U => V) ⊗ (X => Y).V
|
|
//
|
|
// becomes
|
|
//
|
|
// (X => Y).U ⊗ Y.(U => V)
|
|
//
|
|
// These two transformations are iterated until fixed point to produce a
|
|
// equivalent rewrite path that is simpler.
|
|
//
|
|
// From "Homotopy reduction systems for monoid presentations",
|
|
// https://www.sciencedirect.com/science/article/pii/S0022404997000959
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "RewriteLoop.h"
|
|
#include "RewriteSystem.h"
|
|
#include "swift/Basic/Assertions.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include <utility>
|
|
|
|
using namespace swift;
|
|
using namespace rewriting;
|
|
|
|
/// Returns true if this rewrite step is an inverse of \p other
|
|
/// (and vice versa).
|
|
bool RewriteStep::isInverseOf(const RewriteStep &other) const {
|
|
if (Kind != other.Kind)
|
|
return false;
|
|
|
|
if (StartOffset != other.StartOffset)
|
|
return false;
|
|
|
|
if (Inverse != !other.Inverse)
|
|
return false;
|
|
|
|
switch (Kind) {
|
|
case RewriteStep::Rule:
|
|
return Arg == other.Arg;
|
|
|
|
case RewriteStep::Relation:
|
|
return Arg == other.Arg;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
ASSERT(EndOffset == other.EndOffset && "Bad whiskering?");
|
|
return true;
|
|
}
|
|
|
|
bool RewriteStep::maybeSwapRewriteSteps(RewriteStep &other,
|
|
const RewriteSystem &system) {
|
|
if (Kind != other.Kind)
|
|
return false;
|
|
|
|
// Two rewrite steps are _orthogonal_ if they rewrite disjoint subterms
|
|
// in context. Orthogonal rewrite steps commute, so we can canonicalize
|
|
// a path by placing the left-most step first.
|
|
//
|
|
// Eg, A.U.B.(X => Y).C ⊗ A.(U => V).B.Y == A.(U => V).B.X ⊗ A.V.B.(X => Y).
|
|
//
|
|
// Or, in diagram form. We want to turn this:
|
|
//
|
|
// ----- time ----->
|
|
// +---------+---------+
|
|
// | A | A |
|
|
// +---------+---------+
|
|
// | U | U ==> V |
|
|
// +---------+---------+
|
|
// | B | B |
|
|
// +---------+---------+
|
|
// | X ==> Y | Y |
|
|
// +---------+---------+
|
|
// | C | C |
|
|
// +---------+---------+
|
|
//
|
|
// Into this:
|
|
//
|
|
// +---------+---------+
|
|
// | A | A |
|
|
// +---------+---------+
|
|
// | U ==> V | V |
|
|
// +---------+---------+
|
|
// | B | B |
|
|
// +---------+---------+
|
|
// | X | X ==> Y |
|
|
// +---------+---------+
|
|
// | C | C |
|
|
// +---------+---------+
|
|
//
|
|
// Note that
|
|
//
|
|
// StartOffset == |A|+|U|+|B|
|
|
// EndOffset = |C|
|
|
//
|
|
// other.StartOffset = |A|
|
|
// other.EndOffset = |B|+|Y|+|C|
|
|
//
|
|
// After interchange, we adjust:
|
|
//
|
|
// StartOffset = |A|
|
|
// EndOffset = |B|+|X|+|C|
|
|
//
|
|
// other.StartOffset = |A|+|V|+|B|
|
|
// other.EndOffset = |C|
|
|
|
|
if (Kind == RewriteStep::Rule) {
|
|
const auto &rule = system.getRule(Arg);
|
|
auto lhs = (Inverse ? rule.getRHS() : rule.getLHS());
|
|
auto rhs = (Inverse ? rule.getLHS() : rule.getRHS());
|
|
|
|
const auto &otherRule = system.getRule(other.Arg);
|
|
auto otherLHS = (other.Inverse ? otherRule.getRHS() : otherRule.getLHS());
|
|
auto otherRHS = (other.Inverse ? otherRule.getLHS() : otherRule.getRHS());
|
|
|
|
if (StartOffset < other.StartOffset + otherLHS.size())
|
|
return false;
|
|
|
|
std::swap(*this, other);
|
|
EndOffset += (lhs.size() - rhs.size());
|
|
other.StartOffset += (otherRHS.size() - otherLHS.size());
|
|
|
|
return true;
|
|
} else if (Kind == RewriteStep::Relation) {
|
|
const auto &relation = system.getRelation(Arg);
|
|
auto lhs = (Inverse ? relation.second : relation.first);
|
|
auto rhs = (Inverse ? relation.first : relation.second);
|
|
|
|
const auto &otherRelation = system.getRelation(other.Arg);
|
|
auto otherLHS = (other.Inverse ? otherRelation.second : otherRelation.first);
|
|
auto otherRHS = (other.Inverse ? otherRelation.first : otherRelation.second);
|
|
|
|
if (StartOffset < other.StartOffset + otherLHS.size())
|
|
return false;
|
|
|
|
std::swap(*this, other);
|
|
EndOffset += (lhs.size() - rhs.size());
|
|
other.StartOffset += (otherRHS.size() - otherLHS.size());
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Cancels out adjacent rewrite steps that are inverses of each other.
|
|
/// This does not change either endpoint of the path, and the path does
|
|
/// not necessarily need to be a loop.
|
|
bool RewritePath::computeFreelyReducedForm() {
|
|
unsigned j = 0;
|
|
|
|
for (unsigned i = 0, e = Steps.size(); i < e; ++i) {
|
|
// Pre-condition:
|
|
// - Steps in the range [0, j-1] are freely reduced.
|
|
// - Steps in the range [i, e-1] remain to be considered.
|
|
if (j > 0) {
|
|
// If Steps[j-1] and Steps[i] are inverses of each other,
|
|
// discard both Steps[j-1] and Steps[i].
|
|
const auto &otherStep = Steps[j - 1];
|
|
const auto &step = Steps[i];
|
|
|
|
if (otherStep.isInverseOf(step)) {
|
|
--j;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Post-condition:
|
|
// - Steps in the range [0, j] are freely reduced.
|
|
// - Steps in the range [i+1, e-1] remain to be considered.
|
|
Steps[j] = Steps[i];
|
|
++j;
|
|
}
|
|
|
|
if (j == Steps.size())
|
|
return false;
|
|
|
|
Steps.erase(Steps.begin() + j, Steps.end());
|
|
return true;
|
|
}
|
|
|
|
/// Apply the interchange rule until fixed point (see maybeSwapRewriteSteps()).
|
|
bool RewritePath::computeLeftCanonicalForm(const RewriteSystem &system) {
|
|
bool changed = false;
|
|
|
|
for (unsigned i = 1, e = Steps.size(); i < e; ++i) {
|
|
// Pre-condition: steps in the range [0, i-1] are in left-canonical
|
|
// normal form.
|
|
//
|
|
// If Steps[i] can be swapped with Steps[i-1], swap them, and check
|
|
// if Steps[i-1] (the old Steps[i]) can be swapped with Steps[i-2],
|
|
// etc.
|
|
for (unsigned j = i; j >= 1; --j) {
|
|
auto &prevStep = Steps[j - 1];
|
|
auto &step = Steps[j];
|
|
|
|
if (!prevStep.maybeSwapRewriteSteps(step, system))
|
|
break;
|
|
|
|
changed = true;
|
|
}
|
|
|
|
// Post-condition: steps in the range [0, i] are in left-canonical
|
|
// normal form.
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/// Compute freely-reduced left-canonical normal form of a path.
|
|
bool RewritePath::computeNormalForm(const RewriteSystem &system) {
|
|
// Note that computeFreelyReducedForm() and computeLeftCanonicalForm()
|
|
// are both idempotent, but their composition is not.
|
|
|
|
// Begin by freely reducing the path.
|
|
bool changed = computeFreelyReducedForm();
|
|
|
|
// Then, bring it into left canonical form.
|
|
while (computeLeftCanonicalForm(system)) {
|
|
changed = true;
|
|
|
|
// If it was not already in left-canonical form, freely reduce it
|
|
// again.
|
|
if (!computeFreelyReducedForm()) {
|
|
// If it was already freely reduced, then we're done, because it
|
|
// is freely reduced *and* in left-canonical form.
|
|
break;
|
|
}
|
|
|
|
// Otherwise, perform another round, since freely reducing may have
|
|
// opened up new opportunities for left-canonicalization.
|
|
}
|
|
|
|
return changed;
|
|
}
|
|
|
|
/// Given a path that is a loop around the given basepoint, cancels out
|
|
/// pairs of terms from the ends that are inverses of each other, applying
|
|
/// the corresponding translation to the basepoint.
|
|
///
|
|
/// For example, consider this loop with basepoint 'X':
|
|
///
|
|
/// (X => Y.A) * (Y.A => Y.B) * Y.(B => A) * (Y.A => X)
|
|
///
|
|
/// The first step is the inverse of the last step, so the cyclic
|
|
/// reduction is the loop (Y.A => Y.B) * Y.(B => A), with a new
|
|
/// basepoint 'Y.A'.
|
|
bool RewritePath::computeCyclicallyReducedForm(MutableTerm &basepoint,
|
|
const RewriteSystem &system) {
|
|
RewritePathEvaluator evaluator(basepoint);
|
|
unsigned count = 0;
|
|
|
|
while (2 * count + 1 < size()) {
|
|
auto left = Steps[count];
|
|
auto right = Steps[Steps.size() - count - 1];
|
|
if (!left.isInverseOf(right))
|
|
break;
|
|
|
|
// Update the basepoint by applying the first step in the path.
|
|
evaluator.apply(left, system);
|
|
|
|
++count;
|
|
}
|
|
|
|
std::rotate(Steps.begin(), Steps.begin() + count, Steps.end() - count);
|
|
Steps.erase(Steps.end() - 2 * count, Steps.end());
|
|
|
|
basepoint = evaluator.getCurrentTerm();
|
|
return count > 0;
|
|
}
|
|
|
|
/// Compute cyclically-reduced left-canonical normal form of a loop.
|
|
void RewriteLoop::computeNormalForm(const RewriteSystem &system) {
|
|
bool changed = Path.computeNormalForm(system);
|
|
changed |= Path.computeCyclicallyReducedForm(Basepoint, system);
|
|
|
|
if (changed)
|
|
markDirty();
|
|
}
|