Files
swift-mirror/lib/AST/RequirementMachine/RewriteLoop.h
Slava Pestov 2f2249cef1 RequirementMachine: Fix various RewriteSteps to work when re-contextualized
When a rewrite rule is replaced with a path containing ::Adjust, ::Decompose,
::ConcreteConformance or ::SuperclassConformance rewrite steps, the steps
will get a non-zero EndOffset if the original rule appears in a step with a
non-zero EndOffset.

For this reason, these steps must work with a non-zero EndOffset, which
primarily means computing correct offsets into the term being manipulated.
2021-12-08 00:53:35 -05:00

342 lines
10 KiB
C++

//===--- RewriteLoop.h - Identities between rewrite rules -------*- C++ -*-===//
//
// 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
//
//===----------------------------------------------------------------------===//
#ifndef SWIFT_REWRITELOOP_H
#define SWIFT_REWRITELOOP_H
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/DenseMap.h"
#include "Symbol.h"
#include "Term.h"
namespace llvm {
class raw_ostream;
}
namespace swift {
namespace rewriting {
class RewriteSystem;
struct RewritePathEvaluator;
/// Records an evaluation step in a rewrite path.
struct RewriteStep {
enum StepKind : unsigned {
/// Apply a rewrite rule to the term at the top of the A stack.
///
/// Formally, this is a whiskered, oriented rewrite rule. For example,
/// given a rule (X => Y) and the term A.X.B, the application at
/// offset 1 yields A.Y.B.
///
/// This can be represented as A.(X => Y).B.
///
/// Similarly, going in the other direction, if we start from A.Y.B
/// and apply the inverse rule, we get A.(Y => X).B.
///
/// The StartOffset field encodes the offset where to apply the rule.
///
/// The RuleID field encodes the rule to apply.
ApplyRewriteRule,
/// The term at the top of the A stack must be a term ending with a
/// superclass or concrete type symbol.
///
/// If not inverted: prepend the prefix to each substitution.
///
/// If inverted: strip the prefix from each substitution.
///
/// The StartOffset field encodes the length of the prefix.
AdjustConcreteType,
/// Move a term from the A stack to the B stack (if not inverted) or
/// B stack to A stack (if inverted).
Shift,
/// If not inverted: the top of the A stack must be a term ending with a
/// superclass or concrete type symbol. Each concrete substitution in the
/// term is pushed onto the A stack.
///
/// If inverted: pop concrete substitutions from the A stack, which must
/// follow a term ending with a superclass or concrete type symbol. The
/// new substitutions replace the substitutions in that symbol.
///
/// The RuleID field encodes the number of substitutions.
Decompose,
/// If not inverted: the top of the A stack must be a term ending in a
/// concrete type symbol [concrete: C] followed by a protocol symbol [P].
/// These two symbols are combined into a single concrete conformance
/// symbol [concrete: C : P].
///
/// If inverted: the top of the A stack must be a term ending in a
/// concrete conformance symbol [concrete: C : P]. This symbol is replaced
/// with the concrete type symbol [concrete: C] followed by the protocol
/// symbol [P].
ConcreteConformance,
/// If not inverted: the top of the A stack must be a term ending in a
/// superclass symbol [superclass: C] followed by a protocol symbol [P].
/// These two symbols are combined into a single concrete conformance
/// symbol [concrete: C : P].
///
/// If inverted: the top of the A stack must be a term ending in a
/// concrete conformance symbol [concrete: C : P]. This symbol is replaced
/// with the superclass symbol [superclass: C] followed by the protocol
/// symbol [P].
SuperclassConformance
};
/// The rewrite step kind.
StepKind Kind : 3;
/// The size of the left whisker, which is the position within the term where
/// the rule is being applied. In A.(X => Y).B, this is |A|=1.
unsigned StartOffset : 14;
/// The size of the right whisker, which is the length of the remaining suffix
/// after the rule is applied. In A.(X => Y).B, this is |B|=1.
unsigned EndOffset : 14;
/// If Kind is ApplyRewriteRule, the index of the rule in the rewrite system.
///
/// If Kind is AdjustConcreteType, the length of the prefix to add or remove
/// at the beginning of each concrete substitution.
///
/// If Kind is Concrete, the number of substitutions to push or pop.
unsigned RuleID : 15;
/// If false, the step replaces an occurrence of the rule's left hand side
/// with the right hand side. If true, vice versa.
unsigned Inverse : 1;
RewriteStep(StepKind kind, unsigned startOffset, unsigned endOffset,
unsigned ruleID, bool inverse) {
Kind = kind;
StartOffset = startOffset;
assert(StartOffset == startOffset && "Overflow");
EndOffset = endOffset;
assert(EndOffset == endOffset && "Overflow");
RuleID = ruleID;
assert(RuleID == ruleID && "Overflow");
Inverse = inverse;
}
static RewriteStep forRewriteRule(unsigned startOffset, unsigned endOffset,
unsigned ruleID, bool inverse) {
return RewriteStep(ApplyRewriteRule, startOffset, endOffset, ruleID, inverse);
}
static RewriteStep forAdjustment(unsigned offset, unsigned endOffset,
bool inverse) {
return RewriteStep(AdjustConcreteType, /*startOffset=*/0, endOffset,
/*ruleID=*/offset, inverse);
}
static RewriteStep forShift(bool inverse) {
return RewriteStep(Shift, /*startOffset=*/0, /*endOffset=*/0,
/*ruleID=*/0, inverse);
}
static RewriteStep forDecompose(unsigned numSubstitutions, bool inverse) {
return RewriteStep(Decompose, /*startOffset=*/0, /*endOffset=*/0,
/*ruleID=*/numSubstitutions, inverse);
}
static RewriteStep forConcreteConformance(bool inverse) {
return RewriteStep(ConcreteConformance, /*startOffset=*/0, /*endOffset=*/0,
/*ruleID=*/0, inverse);
}
static RewriteStep forSuperclassConformance(bool inverse) {
return RewriteStep(SuperclassConformance, /*startOffset=*/0, /*endOffset=*/0,
/*ruleID=*/0, inverse);
}
bool isInContext() const {
return StartOffset > 0 || EndOffset > 0;
}
void invert() {
Inverse = !Inverse;
}
bool isInverseOf(const RewriteStep &other) const;
bool maybeSwapRewriteSteps(RewriteStep &other,
const RewriteSystem &system);
void dump(llvm::raw_ostream &out,
RewritePathEvaluator &evaluator,
const RewriteSystem &system) const;
};
/// Records a sequence of zero or more rewrite rules applied to a term.
class RewritePath {
SmallVector<RewriteStep, 3> Steps;
public:
bool empty() const {
return Steps.empty();
}
unsigned size() const {
return Steps.size();
}
void add(RewriteStep step) {
Steps.push_back(step);
}
// Horizontal composition of paths.
void append(RewritePath other) {
Steps.append(other.begin(), other.end());
}
void resize(unsigned newSize) {
assert(newSize <= size());
Steps.erase(Steps.begin() + newSize, Steps.end());
}
decltype(Steps)::const_iterator begin() const {
return Steps.begin();
}
decltype(Steps)::const_iterator end() const {
return Steps.end();
}
RewritePath splitCycleAtRule(unsigned ruleID) const;
bool replaceRuleWithPath(unsigned ruleID, const RewritePath &path);
void invert();
void dump(llvm::raw_ostream &out,
MutableTerm term,
const RewriteSystem &system) const;
};
/// Information about protocol conformance rules appearing in a rewrite loop.
///
/// This is the return value of RewriteLoop::findProtocolConformanceRules().
struct ProtocolConformanceRules {
SmallVector<unsigned, 2> RulesInEmptyContext;
SmallVector<std::pair<MutableTerm, unsigned>, 2> RulesInContext;
bool SawIdentityConformance = false;
};
/// A loop (3-cell) that rewrites the basepoint back to the basepoint.
class RewriteLoop {
public:
MutableTerm Basepoint;
RewritePath Path;
private:
bool Deleted;
public:
RewriteLoop(MutableTerm basepoint, RewritePath path)
: Basepoint(basepoint), Path(path), Deleted(false) {}
bool isDeleted() const {
return Deleted;
}
void markDeleted() {
assert(!Deleted);
Deleted = true;
}
bool isInContext(const RewriteSystem &system) const;
llvm::SmallVector<unsigned, 1>
findRulesAppearingOnceInEmptyContext(const RewriteSystem &system) const;
void findProtocolConformanceRules(
llvm::SmallDenseMap<const ProtocolDecl *,
ProtocolConformanceRules, 2> &result,
const RewriteSystem &system) const;
void dump(llvm::raw_ostream &out, const RewriteSystem &system) const;
};
/// Return value of RewritePathEvaluator::applyRewriteRule();
struct AppliedRewriteStep {
Term lhs;
Term rhs;
MutableTerm prefix;
MutableTerm suffix;
};
/// A rewrite path is a list of instructions for a two-stack interpreter.
///
/// - ApplyRewriteRule and AdjustConcreteType manipulate the term at the top of
/// the A stack.
///
/// - Shift moves a term from A to B (if not inverted) or B to A (if inverted).
///
/// - Decompose splits off the substitutions from a superclass or concrete type
/// symbol at the top of the A stack (if not inverted) or assembles a new
/// superclass or concrete type symbol at the top of the A stack
/// (if inverted).
struct RewritePathEvaluator {
SmallVector<MutableTerm, 2> A;
SmallVector<MutableTerm, 2> B;
explicit RewritePathEvaluator(const MutableTerm &term) {
A.push_back(term);
}
void checkA() const;
void checkB() const;
MutableTerm &getCurrentTerm();
/// We're "in context" if we're in the middle of rewriting concrete
/// substitutions.
bool isInContext() const {
assert(A.size() > 0);
return (A.size() > 1 || B.size() > 0);
}
void apply(const RewriteStep &step,
const RewriteSystem &system);
AppliedRewriteStep applyRewriteRule(const RewriteStep &step,
const RewriteSystem &system);
std::pair<MutableTerm, MutableTerm>
applyAdjustment(const RewriteStep &step,
const RewriteSystem &system);
void applyShift(const RewriteStep &step,
const RewriteSystem &system);
void applyDecompose(const RewriteStep &step,
const RewriteSystem &system);
void applyConcreteConformance(const RewriteStep &step,
const RewriteSystem &system);
void dump(llvm::raw_ostream &out) const;
};
} // end namespace rewriting
} // end namespace swift
#endif