peepmatic: Be generic over the operator type
This lets us avoid the cost of `cranelift_codegen::ir::Opcode` to `peepmatic_runtime::Operator` conversion overhead, and paves the way for allowing Peepmatic to support non-clif optimizations (e.g. vcode optimizations). Rather than defining our own `peepmatic::Operator` type like we used to, now the whole `peepmatic` crate is effectively generic over a `TOperator` type parameter. For the Cranelift integration, we use `cranelift_codegen::ir::Opcode` as the concrete type for our `TOperator` type parameter. For testing, we also define a `TestOperator` type, so that we can test Peepmatic code without building all of Cranelift, and we can keep them somewhat isolated from each other. The methods that `peepmatic::Operator` had are now translated into trait bounds on the `TOperator` type. These traits need to be shared between all of `peepmatic`, `peepmatic-runtime`, and `cranelift-codegen`'s Peepmatic integration. Therefore, these new traits live in a new crate: `peepmatic-traits`. This crate acts as a header file of sorts for shared trait/type/macro definitions. Additionally, the `peepmatic-runtime` crate no longer depends on the `peepmatic-macro` procedural macro crate, which should lead to faster build times for Cranelift when it is using pre-built peephole optimizers.
This commit is contained in:
@@ -22,8 +22,8 @@
|
||||
|
||||
use peepmatic_macro::Ast;
|
||||
use peepmatic_runtime::{
|
||||
operator::{Operator, UnquoteOperator},
|
||||
r#type::{BitWidth, Type},
|
||||
unquote::UnquoteOperator,
|
||||
};
|
||||
use std::cell::Cell;
|
||||
use std::hash::{Hash, Hasher};
|
||||
@@ -32,58 +32,58 @@ use wast::Id;
|
||||
|
||||
/// A reference to any AST node.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum DynAstRef<'a> {
|
||||
pub enum DynAstRef<'a, TOperator> {
|
||||
/// A reference to an `Optimizations`.
|
||||
Optimizations(&'a Optimizations<'a>),
|
||||
Optimizations(&'a Optimizations<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Optimization`.
|
||||
Optimization(&'a Optimization<'a>),
|
||||
Optimization(&'a Optimization<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Lhs`.
|
||||
Lhs(&'a Lhs<'a>),
|
||||
Lhs(&'a Lhs<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Rhs`.
|
||||
Rhs(&'a Rhs<'a>),
|
||||
Rhs(&'a Rhs<'a, TOperator>),
|
||||
|
||||
/// A reference to a `Pattern`.
|
||||
Pattern(&'a Pattern<'a>),
|
||||
Pattern(&'a Pattern<'a, TOperator>),
|
||||
|
||||
/// A reference to a `Precondition`.
|
||||
Precondition(&'a Precondition<'a>),
|
||||
Precondition(&'a Precondition<'a, TOperator>),
|
||||
|
||||
/// A reference to a `ConstraintOperand`.
|
||||
ConstraintOperand(&'a ConstraintOperand<'a>),
|
||||
ConstraintOperand(&'a ConstraintOperand<'a, TOperator>),
|
||||
|
||||
/// A reference to a `ValueLiteral`.
|
||||
ValueLiteral(&'a ValueLiteral<'a>),
|
||||
ValueLiteral(&'a ValueLiteral<'a, TOperator>),
|
||||
|
||||
/// A reference to a `Constant`.
|
||||
Constant(&'a Constant<'a>),
|
||||
Constant(&'a Constant<'a, TOperator>),
|
||||
|
||||
/// A reference to a `PatternOperation`.
|
||||
PatternOperation(&'a Operation<'a, Pattern<'a>>),
|
||||
PatternOperation(&'a Operation<'a, TOperator, Pattern<'a, TOperator>>),
|
||||
|
||||
/// A reference to a `Variable`.
|
||||
Variable(&'a Variable<'a>),
|
||||
Variable(&'a Variable<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Integer`.
|
||||
Integer(&'a Integer<'a>),
|
||||
Integer(&'a Integer<'a, TOperator>),
|
||||
|
||||
/// A reference to a `Boolean`.
|
||||
Boolean(&'a Boolean<'a>),
|
||||
Boolean(&'a Boolean<'a, TOperator>),
|
||||
|
||||
/// A reference to a `ConditionCode`.
|
||||
ConditionCode(&'a ConditionCode<'a>),
|
||||
ConditionCode(&'a ConditionCode<'a, TOperator>),
|
||||
|
||||
/// A reference to an `Unquote`.
|
||||
Unquote(&'a Unquote<'a>),
|
||||
Unquote(&'a Unquote<'a, TOperator>),
|
||||
|
||||
/// A reference to an `RhsOperation`.
|
||||
RhsOperation(&'a Operation<'a, Rhs<'a>>),
|
||||
RhsOperation(&'a Operation<'a, TOperator, Rhs<'a, TOperator>>),
|
||||
}
|
||||
|
||||
impl<'a, 'b> ChildNodes<'a, 'b> for DynAstRef<'a> {
|
||||
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a>>) {
|
||||
impl<'a, 'b, TOperator> ChildNodes<'a, 'b, TOperator> for DynAstRef<'a, TOperator> {
|
||||
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a, TOperator>>) {
|
||||
match self {
|
||||
Self::Optimizations(x) => x.child_nodes(sink),
|
||||
Self::Optimization(x) => x.child_nodes(sink),
|
||||
@@ -118,23 +118,28 @@ impl<'a, 'b> ChildNodes<'a, 'b> for DynAstRef<'a> {
|
||||
/// This trait is blanked implemented for everything that does those three
|
||||
/// things, and in practice those three thrings are all implemented by the
|
||||
/// `derive(Ast)` macro.
|
||||
pub trait Ast<'a>: 'a + ChildNodes<'a, 'a> + Span
|
||||
pub trait Ast<'a, TOperator>: 'a + ChildNodes<'a, 'a, TOperator> + Span
|
||||
where
|
||||
DynAstRef<'a>: From<&'a Self>,
|
||||
DynAstRef<'a, TOperator>: From<&'a Self>,
|
||||
TOperator: 'a,
|
||||
{
|
||||
}
|
||||
|
||||
impl<'a, T> Ast<'a> for T
|
||||
impl<'a, T, TOperator> Ast<'a, TOperator> for T
|
||||
where
|
||||
T: 'a + ?Sized + ChildNodes<'a, 'a> + Span,
|
||||
DynAstRef<'a>: From<&'a Self>,
|
||||
T: 'a + ?Sized + ChildNodes<'a, 'a, TOperator> + Span,
|
||||
DynAstRef<'a, TOperator>: From<&'a Self>,
|
||||
TOperator: 'a,
|
||||
{
|
||||
}
|
||||
|
||||
/// Enumerate the child AST nodes of a given node.
|
||||
pub trait ChildNodes<'a, 'b> {
|
||||
pub trait ChildNodes<'a, 'b, TOperator>
|
||||
where
|
||||
TOperator: 'a,
|
||||
{
|
||||
/// Get each of this AST node's children, in order.
|
||||
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a>>);
|
||||
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a, TOperator>>);
|
||||
}
|
||||
|
||||
/// A trait for getting the span where an AST node was defined.
|
||||
@@ -147,30 +152,30 @@ pub trait Span {
|
||||
///
|
||||
/// This is the root AST node.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Optimizations<'a> {
|
||||
pub struct Optimizations<'a, TOperator> {
|
||||
/// Where these `Optimizations` were defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The optimizations.
|
||||
#[peepmatic(flatten)]
|
||||
pub optimizations: Vec<Optimization<'a>>,
|
||||
pub optimizations: Vec<Optimization<'a, TOperator>>,
|
||||
}
|
||||
|
||||
/// A complete optimization: a left-hand side to match against and a right-hand
|
||||
/// side replacement.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Optimization<'a> {
|
||||
pub struct Optimization<'a, TOperator> {
|
||||
/// Where this `Optimization` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// The left-hand side that matches when this optimization applies.
|
||||
pub lhs: Lhs<'a>,
|
||||
pub lhs: Lhs<'a, TOperator>,
|
||||
|
||||
/// The new sequence of instructions to replace an old sequence that matches
|
||||
/// the left-hand side with.
|
||||
pub rhs: Rhs<'a>,
|
||||
pub rhs: Rhs<'a, TOperator>,
|
||||
}
|
||||
|
||||
/// A left-hand side describes what is required for a particular optimization to
|
||||
@@ -180,58 +185,58 @@ pub struct Optimization<'a> {
|
||||
/// candidate instruction sequences, and zero or more preconditions that add
|
||||
/// additional constraints upon instruction sequences matched by the pattern.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Lhs<'a> {
|
||||
pub struct Lhs<'a, TOperator> {
|
||||
/// Where this `Lhs` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
|
||||
/// A pattern that describes sequences of instructions to match.
|
||||
pub pattern: Pattern<'a>,
|
||||
pub pattern: Pattern<'a, TOperator>,
|
||||
|
||||
/// Additional constraints that a match must satisfy in addition to
|
||||
/// structually matching the pattern, e.g. some constant must be a power of
|
||||
/// two.
|
||||
#[peepmatic(flatten)]
|
||||
pub preconditions: Vec<Precondition<'a>>,
|
||||
pub preconditions: Vec<Precondition<'a, TOperator>>,
|
||||
}
|
||||
|
||||
/// A structural pattern, potentially with wildcard variables for matching whole
|
||||
/// subtrees.
|
||||
#[derive(Debug, Ast)]
|
||||
pub enum Pattern<'a> {
|
||||
pub enum Pattern<'a, TOperator> {
|
||||
/// A specific value. These are written as `1234` or `0x1234` or `true` or
|
||||
/// `false`.
|
||||
ValueLiteral(ValueLiteral<'a>),
|
||||
ValueLiteral(ValueLiteral<'a, TOperator>),
|
||||
|
||||
/// A constant that matches any constant value. This subsumes value
|
||||
/// patterns. These are upper-case identifiers like `$C`.
|
||||
Constant(Constant<'a>),
|
||||
Constant(Constant<'a, TOperator>),
|
||||
|
||||
/// An operation pattern with zero or more operand patterns. These are
|
||||
/// s-expressions like `(iadd $x $y)`.
|
||||
Operation(Operation<'a, Pattern<'a>>),
|
||||
Operation(Operation<'a, TOperator, Pattern<'a, TOperator>>),
|
||||
|
||||
/// A variable that matches any kind of subexpression. This subsumes all
|
||||
/// other patterns. These are lower-case identifiers like `$x`.
|
||||
Variable(Variable<'a>),
|
||||
Variable(Variable<'a, TOperator>),
|
||||
}
|
||||
|
||||
/// An integer or boolean value literal.
|
||||
#[derive(Debug, Ast)]
|
||||
pub enum ValueLiteral<'a> {
|
||||
pub enum ValueLiteral<'a, TOperator> {
|
||||
/// An integer value.
|
||||
Integer(Integer<'a>),
|
||||
Integer(Integer<'a, TOperator>),
|
||||
|
||||
/// A boolean value: `true` or `false`.
|
||||
Boolean(Boolean<'a>),
|
||||
Boolean(Boolean<'a, TOperator>),
|
||||
|
||||
/// A condition code: `eq`, `ne`, etc...
|
||||
ConditionCode(ConditionCode<'a>),
|
||||
ConditionCode(ConditionCode<'a, TOperator>),
|
||||
}
|
||||
|
||||
/// An integer literal.
|
||||
#[derive(Debug, PartialEq, Eq, Ast)]
|
||||
pub struct Integer<'a> {
|
||||
pub struct Integer<'a, TOperator> {
|
||||
/// Where this `Integer` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
@@ -255,10 +260,10 @@ pub struct Integer<'a> {
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a ()>,
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
impl Hash for Integer<'_> {
|
||||
impl<TOperator> Hash for Integer<'_, TOperator> {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
@@ -278,7 +283,7 @@ impl Hash for Integer<'_> {
|
||||
|
||||
/// A boolean literal.
|
||||
#[derive(Debug, PartialEq, Eq, Ast)]
|
||||
pub struct Boolean<'a> {
|
||||
pub struct Boolean<'a, TOperator> {
|
||||
/// Where this `Boolean` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
@@ -299,10 +304,10 @@ pub struct Boolean<'a> {
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a ()>,
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
impl Hash for Boolean<'_> {
|
||||
impl<TOperator> Hash for Boolean<'_, TOperator> {
|
||||
fn hash<H>(&self, state: &mut H)
|
||||
where
|
||||
H: Hasher,
|
||||
@@ -322,7 +327,7 @@ impl Hash for Boolean<'_> {
|
||||
|
||||
/// A condition code.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct ConditionCode<'a> {
|
||||
pub struct ConditionCode<'a, TOperator> {
|
||||
/// Where this `ConditionCode` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
@@ -333,7 +338,7 @@ pub struct ConditionCode<'a> {
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a ()>,
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
/// A symbolic constant.
|
||||
@@ -341,7 +346,7 @@ pub struct ConditionCode<'a> {
|
||||
/// These are identifiers containing uppercase letters: `$C`, `$MY-CONST`,
|
||||
/// `$CONSTANT1`.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Constant<'a> {
|
||||
pub struct Constant<'a, TOperator> {
|
||||
/// Where this `Constant` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
@@ -349,6 +354,10 @@ pub struct Constant<'a> {
|
||||
/// This constant's identifier.
|
||||
#[peepmatic(skip_child)]
|
||||
pub id: Id<'a>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
/// A variable that matches any subtree.
|
||||
@@ -357,7 +366,7 @@ pub struct Constant<'a> {
|
||||
/// being the same as each other occurrence as well, e.g. `(iadd $x $x)` matches
|
||||
/// `(iadd 5 5)` but not `(iadd 1 2)`.
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Variable<'a> {
|
||||
pub struct Variable<'a, TOperator> {
|
||||
/// Where this `Variable` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
@@ -365,15 +374,20 @@ pub struct Variable<'a> {
|
||||
/// This variable's identifier.
|
||||
#[peepmatic(skip_child)]
|
||||
pub id: Id<'a>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
/// An operation with an operator, and operands of type `T`.
|
||||
#[derive(Debug, Ast)]
|
||||
#[peepmatic(no_into_dyn_node)]
|
||||
pub struct Operation<'a, T>
|
||||
pub struct Operation<'a, TOperator, TOperand>
|
||||
where
|
||||
T: 'a + Ast<'a>,
|
||||
DynAstRef<'a>: From<&'a T>,
|
||||
TOperator: 'a,
|
||||
TOperand: 'a + Ast<'a, TOperator>,
|
||||
DynAstRef<'a, TOperator>: From<&'a TOperand>,
|
||||
{
|
||||
/// The span where this operation was written.
|
||||
#[peepmatic(skip_child)]
|
||||
@@ -381,7 +395,7 @@ where
|
||||
|
||||
/// The operator for this operation, e.g. `imul` or `iadd`.
|
||||
#[peepmatic(skip_child)]
|
||||
pub operator: Operator,
|
||||
pub operator: TOperator,
|
||||
|
||||
/// An optional ascribed or inferred type for the operator.
|
||||
#[peepmatic(skip_child)]
|
||||
@@ -393,23 +407,27 @@ where
|
||||
/// the operands. When `Operation is used in a right-hand side replacement,
|
||||
/// these are the sub-replacements for the operands.
|
||||
#[peepmatic(flatten)]
|
||||
pub operands: Vec<T>,
|
||||
pub operands: Vec<TOperand>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a ()>,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Operation<'a, Pattern<'a>>> for DynAstRef<'a> {
|
||||
impl<'a, TOperator> From<&'a Operation<'a, TOperator, Pattern<'a, TOperator>>>
|
||||
for DynAstRef<'a, TOperator>
|
||||
{
|
||||
#[inline]
|
||||
fn from(o: &'a Operation<'a, Pattern<'a>>) -> DynAstRef<'a> {
|
||||
fn from(o: &'a Operation<'a, TOperator, Pattern<'a, TOperator>>) -> DynAstRef<'a, TOperator> {
|
||||
DynAstRef::PatternOperation(o)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a Operation<'a, Rhs<'a>>> for DynAstRef<'a> {
|
||||
impl<'a, TOperator> From<&'a Operation<'a, TOperator, Rhs<'a, TOperator>>>
|
||||
for DynAstRef<'a, TOperator>
|
||||
{
|
||||
#[inline]
|
||||
fn from(o: &'a Operation<'a, Rhs<'a>>) -> DynAstRef<'a> {
|
||||
fn from(o: &'a Operation<'a, TOperator, Rhs<'a, TOperator>>) -> DynAstRef<'a, TOperator> {
|
||||
DynAstRef::RhsOperation(o)
|
||||
}
|
||||
}
|
||||
@@ -417,7 +435,7 @@ impl<'a> From<&'a Operation<'a, Rhs<'a>>> for DynAstRef<'a> {
|
||||
/// A precondition adds additional constraints to a pattern, such as "$C must be
|
||||
/// a power of two".
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Precondition<'a> {
|
||||
pub struct Precondition<'a, TOperator> {
|
||||
/// Where this `Precondition` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
@@ -428,7 +446,11 @@ pub struct Precondition<'a> {
|
||||
|
||||
/// The operands of the constraint.
|
||||
#[peepmatic(flatten)]
|
||||
pub operands: Vec<ConstraintOperand<'a>>,
|
||||
pub operands: Vec<ConstraintOperand<'a, TOperator>>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
/// Contraint operators.
|
||||
@@ -446,40 +468,40 @@ pub enum Constraint {
|
||||
|
||||
/// An operand of a precondition's constraint.
|
||||
#[derive(Debug, Ast)]
|
||||
pub enum ConstraintOperand<'a> {
|
||||
pub enum ConstraintOperand<'a, TOperator> {
|
||||
/// A value literal operand.
|
||||
ValueLiteral(ValueLiteral<'a>),
|
||||
ValueLiteral(ValueLiteral<'a, TOperator>),
|
||||
|
||||
/// A constant operand.
|
||||
Constant(Constant<'a>),
|
||||
Constant(Constant<'a, TOperator>),
|
||||
|
||||
/// A variable operand.
|
||||
Variable(Variable<'a>),
|
||||
Variable(Variable<'a, TOperator>),
|
||||
}
|
||||
|
||||
/// The right-hand side of an optimization that contains the instructions to
|
||||
/// replace any matched left-hand side with.
|
||||
#[derive(Debug, Ast)]
|
||||
pub enum Rhs<'a> {
|
||||
pub enum Rhs<'a, TOperator> {
|
||||
/// A value literal right-hand side.
|
||||
ValueLiteral(ValueLiteral<'a>),
|
||||
ValueLiteral(ValueLiteral<'a, TOperator>),
|
||||
|
||||
/// A constant right-hand side (the constant must have been matched and
|
||||
/// bound in the left-hand side's pattern).
|
||||
Constant(Constant<'a>),
|
||||
Constant(Constant<'a, TOperator>),
|
||||
|
||||
/// A variable right-hand side (the variable must have been matched and
|
||||
/// bound in the left-hand side's pattern).
|
||||
Variable(Variable<'a>),
|
||||
Variable(Variable<'a, TOperator>),
|
||||
|
||||
/// An unquote expression that is evaluated while replacing the left-hand
|
||||
/// side with the right-hand side. The result of the evaluation is used in
|
||||
/// the replacement.
|
||||
Unquote(Unquote<'a>),
|
||||
Unquote(Unquote<'a, TOperator>),
|
||||
|
||||
/// A compound right-hand side consisting of an operation and subsequent
|
||||
/// right-hand side operands.
|
||||
Operation(Operation<'a, Rhs<'a>>),
|
||||
Operation(Operation<'a, TOperator, Rhs<'a, TOperator>>),
|
||||
}
|
||||
|
||||
/// An unquote operation.
|
||||
@@ -493,7 +515,7 @@ pub enum Rhs<'a> {
|
||||
/// instructions that match its left-hand side with the compile-time result of
|
||||
/// `log2($C)` (the left-hand side must match and bind the constant `$C`).
|
||||
#[derive(Debug, Ast)]
|
||||
pub struct Unquote<'a> {
|
||||
pub struct Unquote<'a, TOperator> {
|
||||
/// Where this `Unquote` was defined.
|
||||
#[peepmatic(skip_child)]
|
||||
pub span: wast::Span,
|
||||
@@ -504,5 +526,9 @@ pub struct Unquote<'a> {
|
||||
|
||||
/// The operands for this unquote operation.
|
||||
#[peepmatic(flatten)]
|
||||
pub operands: Vec<Rhs<'a>>,
|
||||
pub operands: Vec<Rhs<'a, TOperator>>,
|
||||
|
||||
#[allow(missing_docs)]
|
||||
#[peepmatic(skip_child)]
|
||||
pub marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
@@ -2,14 +2,20 @@
|
||||
|
||||
use peepmatic_automata::{Automaton, Builder};
|
||||
use peepmatic_runtime::linear;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Construct an automaton from a set of linear optimizations.
|
||||
pub fn automatize(
|
||||
opts: &linear::Optimizations,
|
||||
) -> Automaton<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> {
|
||||
pub fn automatize<TOperator>(
|
||||
opts: &linear::Optimizations<TOperator>,
|
||||
) -> Automaton<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
debug_assert!(crate::linear_passes::is_sorted_lexicographically(opts));
|
||||
|
||||
let mut builder = Builder::<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>>::new();
|
||||
let mut builder =
|
||||
Builder::<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>::new();
|
||||
|
||||
for opt in &opts.optimizations {
|
||||
let mut insertion = builder.insert();
|
||||
|
||||
@@ -7,17 +7,21 @@ use peepmatic_runtime::{
|
||||
cc::ConditionCode,
|
||||
integer_interner::{IntegerId, IntegerInterner},
|
||||
linear,
|
||||
operator::Operator,
|
||||
paths::{PathId, PathInterner},
|
||||
};
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt::Debug;
|
||||
use std::io::{self, Write};
|
||||
use std::num::NonZeroU16;
|
||||
use std::num::{NonZeroU16, NonZeroU32};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PeepholeDotFmt<'a>(pub(crate) &'a PathInterner, pub(crate) &'a IntegerInterner);
|
||||
|
||||
impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for PeepholeDotFmt<'_> {
|
||||
impl<TOperator> DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>
|
||||
for PeepholeDotFmt<'_>
|
||||
where
|
||||
TOperator: Debug + TryFrom<NonZeroU32>,
|
||||
{
|
||||
fn fmt_transition(
|
||||
&self,
|
||||
w: &mut impl Write,
|
||||
@@ -26,22 +30,23 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
|
||||
_to: Option<&linear::MatchOp>,
|
||||
) -> io::Result<()> {
|
||||
let from = from.expect("we should have match op for every state");
|
||||
if let Some(x) = input.ok().map(|x| x.get()) {
|
||||
if let Some(x) = input.ok() {
|
||||
match from {
|
||||
linear::MatchOp::Opcode { .. } => {
|
||||
let opcode =
|
||||
Operator::try_from(x).expect("we shouldn't generate non-opcode edges");
|
||||
write!(w, "{}", opcode)
|
||||
let opcode = TOperator::try_from(x)
|
||||
.map_err(|_| ())
|
||||
.expect("we shouldn't generate non-opcode edges");
|
||||
write!(w, "{:?}", opcode)
|
||||
}
|
||||
linear::MatchOp::ConditionCode { .. } => {
|
||||
let cc =
|
||||
ConditionCode::try_from(x).expect("we shouldn't generate non-CC edges");
|
||||
let cc = ConditionCode::try_from(x.get())
|
||||
.expect("we shouldn't generate non-CC edges");
|
||||
write!(w, "{}", cc)
|
||||
}
|
||||
linear::MatchOp::IntegerValue { .. } => {
|
||||
let x = self
|
||||
.1
|
||||
.lookup(IntegerId(NonZeroU16::new(x.try_into().unwrap()).unwrap()));
|
||||
let x = self.1.lookup(IntegerId(
|
||||
NonZeroU16::new(x.get().try_into().unwrap()).unwrap(),
|
||||
));
|
||||
write!(w, "{}", x)
|
||||
}
|
||||
_ => write!(w, "Ok({})", x),
|
||||
@@ -73,7 +78,11 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
|
||||
writeln!(w, "</font>")
|
||||
}
|
||||
|
||||
fn fmt_output(&self, w: &mut impl Write, actions: &Box<[linear::Action]>) -> io::Result<()> {
|
||||
fn fmt_output(
|
||||
&self,
|
||||
w: &mut impl Write,
|
||||
actions: &Box<[linear::Action<TOperator>]>,
|
||||
) -> io::Result<()> {
|
||||
use linear::Action::*;
|
||||
|
||||
if actions.is_empty() {
|
||||
@@ -88,11 +97,11 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
|
||||
match a {
|
||||
GetLhs { path } => write!(w, "get-lhs @ {}<br/>", p(path))?,
|
||||
UnaryUnquote { operator, operand } => {
|
||||
write!(w, "eval {} $rhs{}<br/>", operator, operand.0)?
|
||||
write!(w, "eval {:?} $rhs{}<br/>", operator, operand.0)?
|
||||
}
|
||||
BinaryUnquote { operator, operands } => write!(
|
||||
w,
|
||||
"eval {} $rhs{}, $rhs{}<br/>",
|
||||
"eval {:?} $rhs{}, $rhs{}<br/>",
|
||||
operator, operands[0].0, operands[1].0,
|
||||
)?,
|
||||
MakeIntegerConst {
|
||||
@@ -108,14 +117,14 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
|
||||
operand,
|
||||
operator,
|
||||
r#type: _,
|
||||
} => write!(w, "make {} $rhs{}<br/>", operator, operand.0,)?,
|
||||
} => write!(w, "make {:?} $rhs{}<br/>", operator, operand.0,)?,
|
||||
MakeBinaryInst {
|
||||
operator,
|
||||
operands,
|
||||
r#type: _,
|
||||
} => write!(
|
||||
w,
|
||||
"make {} $rhs{}, $rhs{}<br/>",
|
||||
"make {:?} $rhs{}, $rhs{}<br/>",
|
||||
operator, operands[0].0, operands[1].0,
|
||||
)?,
|
||||
MakeTernaryInst {
|
||||
@@ -124,7 +133,7 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
|
||||
r#type: _,
|
||||
} => write!(
|
||||
w,
|
||||
"make {} $rhs{}, $rhs{}, $rhs{}<br/>",
|
||||
"make {:?} $rhs{}, $rhs{}, $rhs{}<br/>",
|
||||
operator, operands[0].0, operands[1].0, operands[2].0,
|
||||
)?,
|
||||
}
|
||||
|
||||
@@ -23,20 +23,25 @@ pub use self::{
|
||||
};
|
||||
|
||||
use peepmatic_runtime::PeepholeOptimizations;
|
||||
use peepmatic_traits::TypingRules;
|
||||
use std::convert::TryFrom;
|
||||
use std::fmt::Debug;
|
||||
use std::fs;
|
||||
use std::hash::Hash;
|
||||
use std::num::NonZeroU32;
|
||||
use std::path::Path;
|
||||
|
||||
/// Compile the given DSL file into a compact peephole optimizations automaton!
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// ```ignore
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let peep_opts = peepmatic::compile_file(Path::new(
|
||||
/// "path/to/optimizations.peepmatic"
|
||||
/// ))?;
|
||||
/// let peep_opts = peepmatic::compile_file::<cranelift_codegen::ir::Opcode>(
|
||||
/// Path::new("path/to/optimizations.peepmatic")
|
||||
/// )?;
|
||||
///
|
||||
/// // Use the peephole optimizations or serialize them into bytes here...
|
||||
/// # Ok(())
|
||||
@@ -49,9 +54,19 @@ use std::path::Path;
|
||||
/// `PEEPMATIC_DOT` environment variable to a file path. A [GraphViz
|
||||
/// Dot]((https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf)) file showing the
|
||||
/// peephole optimizer's automaton will be written to that file path.
|
||||
pub fn compile_file(filename: &Path) -> anyhow::Result<PeepholeOptimizations> {
|
||||
pub fn compile_file<TOperator>(filename: &Path) -> anyhow::Result<PeepholeOptimizations<TOperator>>
|
||||
where
|
||||
TOperator: Copy
|
||||
+ Debug
|
||||
+ Eq
|
||||
+ Hash
|
||||
+ for<'a> wast::parser::Parse<'a>
|
||||
+ TypingRules
|
||||
+ Into<NonZeroU32>
|
||||
+ TryFrom<NonZeroU32>,
|
||||
{
|
||||
let source = fs::read_to_string(filename)?;
|
||||
compile_str(&source, filename)
|
||||
compile_str::<TOperator>(&source, filename)
|
||||
}
|
||||
|
||||
/// Compile the given DSL source text down into a compact peephole optimizations
|
||||
@@ -64,11 +79,11 @@ pub fn compile_file(filename: &Path) -> anyhow::Result<PeepholeOptimizations> {
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```no_run
|
||||
/// ```ignore
|
||||
/// # fn main() -> anyhow::Result<()> {
|
||||
/// use std::path::Path;
|
||||
///
|
||||
/// let peep_opts = peepmatic::compile_str(
|
||||
/// let peep_opts = peepmatic::compile_str::<cranelift_codegen::ir::Opcode>(
|
||||
/// "
|
||||
/// (=> (iadd $x 0) $x)
|
||||
/// (=> (imul $x 0) 0)
|
||||
@@ -88,14 +103,27 @@ pub fn compile_file(filename: &Path) -> anyhow::Result<PeepholeOptimizations> {
|
||||
/// `PEEPMATIC_DOT` environment variable to a file path. A [GraphViz
|
||||
/// Dot]((https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf)) file showing the
|
||||
/// peephole optimizer's automaton will be written to that file path.
|
||||
pub fn compile_str(source: &str, filename: &Path) -> anyhow::Result<PeepholeOptimizations> {
|
||||
pub fn compile_str<TOperator>(
|
||||
source: &str,
|
||||
filename: &Path,
|
||||
) -> anyhow::Result<PeepholeOptimizations<TOperator>>
|
||||
where
|
||||
TOperator: Copy
|
||||
+ Debug
|
||||
+ Eq
|
||||
+ Hash
|
||||
+ for<'a> wast::parser::Parse<'a>
|
||||
+ TypingRules
|
||||
+ Into<NonZeroU32>
|
||||
+ TryFrom<NonZeroU32>,
|
||||
{
|
||||
let buf = wast::parser::ParseBuffer::new(source).map_err(|mut e| {
|
||||
e.set_path(filename);
|
||||
e.set_text(source);
|
||||
e
|
||||
})?;
|
||||
|
||||
let opts = wast::parser::parse::<Optimizations>(&buf).map_err(|mut e| {
|
||||
let opts = wast::parser::parse::<Optimizations<'_, TOperator>>(&buf).map_err(|mut e| {
|
||||
e.set_path(filename);
|
||||
e.set_text(source);
|
||||
e
|
||||
@@ -137,9 +165,10 @@ pub fn compile_str(source: &str, filename: &Path) -> anyhow::Result<PeepholeOpti
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
fn assert_compiles(path: &str) {
|
||||
match compile_file(Path::new(path)) {
|
||||
match compile_file::<TestOperator>(Path::new(path)) {
|
||||
Ok(_) => return,
|
||||
Err(e) => {
|
||||
eprintln!("error: {}", e);
|
||||
|
||||
@@ -5,6 +5,8 @@ use peepmatic_runtime::{
|
||||
paths::{PathId, PathInterner},
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// Sort a set of optimizations from least to most general.
|
||||
///
|
||||
@@ -25,7 +27,10 @@ use std::cmp::Ordering;
|
||||
///
|
||||
/// and we are matching `(imul 4 (..))`, then we want to apply the second
|
||||
/// optimization, because it is more specific than the first.
|
||||
pub fn sort_least_to_most_general(opts: &mut linear::Optimizations) {
|
||||
pub fn sort_least_to_most_general<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
let linear::Optimizations {
|
||||
ref mut optimizations,
|
||||
ref paths,
|
||||
@@ -41,7 +46,10 @@ pub fn sort_least_to_most_general(opts: &mut linear::Optimizations) {
|
||||
/// Sort the linear optimizations lexicographically.
|
||||
///
|
||||
/// This sort order is required for automata construction.
|
||||
pub fn sort_lexicographically(opts: &mut linear::Optimizations) {
|
||||
pub fn sort_lexicographically<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
let linear::Optimizations {
|
||||
ref mut optimizations,
|
||||
ref paths,
|
||||
@@ -53,12 +61,15 @@ pub fn sort_lexicographically(opts: &mut linear::Optimizations) {
|
||||
.sort_by(|a, b| compare_optimizations(paths, a, b, |a_len, b_len| a_len.cmp(&b_len)));
|
||||
}
|
||||
|
||||
fn compare_optimizations(
|
||||
fn compare_optimizations<TOperator>(
|
||||
paths: &PathInterner,
|
||||
a: &linear::Optimization,
|
||||
b: &linear::Optimization,
|
||||
a: &linear::Optimization<TOperator>,
|
||||
b: &linear::Optimization<TOperator>,
|
||||
compare_lengths: impl Fn(usize, usize) -> Ordering,
|
||||
) -> Ordering {
|
||||
) -> Ordering
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
for (a, b) in a.increments.iter().zip(b.increments.iter()) {
|
||||
let c = compare_match_op_generality(paths, a.operation, b.operation);
|
||||
if c != Ordering::Equal {
|
||||
@@ -79,11 +90,14 @@ fn compare_optimizations(
|
||||
compare_lengths(a.increments.len(), b.increments.len())
|
||||
}
|
||||
|
||||
fn compare_optimization_generality(
|
||||
fn compare_optimization_generality<TOperator>(
|
||||
paths: &PathInterner,
|
||||
a: &linear::Optimization,
|
||||
b: &linear::Optimization,
|
||||
) -> Ordering {
|
||||
a: &linear::Optimization<TOperator>,
|
||||
b: &linear::Optimization<TOperator>,
|
||||
) -> Ordering
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
compare_optimizations(paths, a, b, |a_len, b_len| {
|
||||
// If they shared equivalent prefixes, then compare lengths and invert the
|
||||
// result because longer patterns are less general than shorter patterns.
|
||||
@@ -158,14 +172,22 @@ fn compare_paths(paths: &PathInterner, a: PathId, b: PathId) -> Ordering {
|
||||
}
|
||||
|
||||
/// Are the given optimizations sorted from least to most general?
|
||||
pub(crate) fn is_sorted_by_generality(opts: &linear::Optimizations) -> bool {
|
||||
pub(crate) fn is_sorted_by_generality<TOperator>(opts: &linear::Optimizations<TOperator>) -> bool
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
opts.optimizations
|
||||
.windows(2)
|
||||
.all(|w| compare_optimization_generality(&opts.paths, &w[0], &w[1]) <= Ordering::Equal)
|
||||
}
|
||||
|
||||
/// Are the given optimizations sorted lexicographically?
|
||||
pub(crate) fn is_sorted_lexicographically(opts: &linear::Optimizations) -> bool {
|
||||
pub(crate) fn is_sorted_lexicographically<TOperator>(
|
||||
opts: &linear::Optimizations<TOperator>,
|
||||
) -> bool
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
opts.optimizations.windows(2).all(|w| {
|
||||
compare_optimizations(&opts.paths, &w[0], &w[1], |a_len, b_len| a_len.cmp(&b_len))
|
||||
<= Ordering::Equal
|
||||
@@ -207,7 +229,10 @@ pub(crate) fn is_sorted_lexicographically(opts: &linear::Optimizations) -> bool
|
||||
/// opcode @ 0 --iadd-->
|
||||
/// opcode @ 0 --(else)--> is-const? @ 0 --true-->
|
||||
/// ```
|
||||
pub fn match_in_same_order(opts: &mut linear::Optimizations) {
|
||||
pub fn match_in_same_order<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
assert!(!opts.optimizations.is_empty());
|
||||
|
||||
let mut prefix = vec![];
|
||||
@@ -267,7 +292,10 @@ pub fn match_in_same_order(opts: &mut linear::Optimizations) {
|
||||
/// existence completely. So we just emit nop match operations for all variable
|
||||
/// patterns, and then in this post-processing pass, we fuse them and their
|
||||
/// actions with their preceding increment.
|
||||
pub fn remove_unnecessary_nops(opts: &mut linear::Optimizations) {
|
||||
pub fn remove_unnecessary_nops<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
for opt in &mut opts.optimizations {
|
||||
if opt.increments.len() < 2 {
|
||||
debug_assert!(!opt.increments.is_empty());
|
||||
@@ -289,9 +317,9 @@ mod tests {
|
||||
use crate::ast::*;
|
||||
use peepmatic_runtime::{
|
||||
linear::{bool_to_match_result, Else, MatchOp::*, MatchResult},
|
||||
operator::Operator,
|
||||
paths::*,
|
||||
};
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
use std::num::NonZeroU32;
|
||||
|
||||
#[test]
|
||||
@@ -306,7 +334,7 @@ mod tests {
|
||||
fn $test_name() {
|
||||
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
|
||||
|
||||
let opts = match wast::parser::parse::<Optimizations>(&buf) {
|
||||
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(std::path::Path::new(stringify!($test_name)));
|
||||
@@ -383,7 +411,7 @@ mod tests {
|
||||
fn $test_name() {
|
||||
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
|
||||
|
||||
let opts = match wast::parser::parse::<Optimizations>(&buf) {
|
||||
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(std::path::Path::new(stringify!($test_name)));
|
||||
@@ -468,31 +496,19 @@ mod tests {
|
||||
",
|
||||
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(
|
||||
Opcode { path: p(&[0, 1]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0, 1]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IntegerValue { path: p(&[0, 1]) }, i(42))
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true)),
|
||||
(
|
||||
@@ -501,10 +517,7 @@ mod tests {
|
||||
)
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true)),
|
||||
(
|
||||
@@ -513,18 +526,12 @@ mod tests {
|
||||
)
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true))
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(
|
||||
Eq {
|
||||
@@ -535,10 +542,7 @@ mod tests {
|
||||
)
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
],
|
||||
@@ -558,50 +562,32 @@ mod tests {
|
||||
",
|
||||
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
|
||||
(IntegerValue { path: p(&[0, 0]) }, i(2)),
|
||||
(Nop, Err(Else))
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
|
||||
(IntegerValue { path: p(&[0, 0]) }, i(1)),
|
||||
(Nop, Err(Else))
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IntegerValue { path: p(&[0, 1]) }, i(2))
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IntegerValue { path: p(&[0, 1]) }, i(1))
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(IntegerValue { path: p(&[0, 0]) }, i(0)),
|
||||
(Nop, Err(Else))
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
||||
(Nop, Err(Else)),
|
||||
(IntegerValue { path: p(&[0, 1]) }, i(0))
|
||||
]
|
||||
@@ -619,14 +605,8 @@ mod tests {
|
||||
",
|
||||
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
|
||||
),
|
||||
(
|
||||
Opcode { path: p(&[0, 0]) },
|
||||
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
|
||||
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
(
|
||||
@@ -638,14 +618,8 @@ mod tests {
|
||||
),
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
|
||||
),
|
||||
(
|
||||
Opcode { path: p(&[0, 0]) },
|
||||
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
|
||||
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
(
|
||||
@@ -670,14 +644,8 @@ mod tests {
|
||||
",
|
||||
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
|
||||
),
|
||||
(
|
||||
Opcode { path: p(&[0, 0]) },
|
||||
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
|
||||
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
(
|
||||
@@ -689,14 +657,8 @@ mod tests {
|
||||
),
|
||||
],
|
||||
vec![
|
||||
(
|
||||
Opcode { path: p(&[0]) },
|
||||
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
|
||||
),
|
||||
(
|
||||
Opcode { path: p(&[0, 0]) },
|
||||
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
|
||||
),
|
||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
|
||||
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
|
||||
(Nop, Err(Else)),
|
||||
(Nop, Err(Else)),
|
||||
(
|
||||
|
||||
@@ -93,11 +93,17 @@ use peepmatic_runtime::{
|
||||
};
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryInto;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::marker::PhantomData;
|
||||
use std::num::NonZeroU32;
|
||||
use wast::Id;
|
||||
|
||||
/// Translate the given AST optimizations into linear optimizations.
|
||||
pub fn linearize(opts: &Optimizations) -> linear::Optimizations {
|
||||
pub fn linearize<TOperator>(opts: &Optimizations<TOperator>) -> linear::Optimizations<TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||
{
|
||||
let mut optimizations = vec![];
|
||||
let mut paths = PathInterner::new();
|
||||
let mut integers = IntegerInterner::new();
|
||||
@@ -113,12 +119,15 @@ pub fn linearize(opts: &Optimizations) -> linear::Optimizations {
|
||||
}
|
||||
|
||||
/// Translate an AST optimization into a linear optimization!
|
||||
fn linearize_optimization(
|
||||
fn linearize_optimization<TOperator>(
|
||||
paths: &mut PathInterner,
|
||||
integers: &mut IntegerInterner,
|
||||
opt: &Optimization,
|
||||
) -> linear::Optimization {
|
||||
let mut increments: Vec<linear::Increment> = vec![];
|
||||
opt: &Optimization<TOperator>,
|
||||
) -> linear::Optimization<TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||
{
|
||||
let mut increments: Vec<linear::Increment<_>> = vec![];
|
||||
|
||||
let mut lhs_id_to_path = LhsIdToPath::new();
|
||||
|
||||
@@ -175,20 +184,26 @@ fn linearize_optimization(
|
||||
///
|
||||
/// Does not maintain any extra state about the traversal, such as where in the
|
||||
/// tree each yielded node comes from.
|
||||
struct RhsPostOrder<'a> {
|
||||
dfs: Dfs<'a>,
|
||||
struct RhsPostOrder<'a, TOperator> {
|
||||
dfs: Dfs<'a, TOperator>,
|
||||
}
|
||||
|
||||
impl<'a> RhsPostOrder<'a> {
|
||||
fn new(rhs: &'a Rhs<'a>) -> Self {
|
||||
impl<'a, TOperator> RhsPostOrder<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
fn new(rhs: &'a Rhs<'a, TOperator>) -> Self {
|
||||
Self { dfs: Dfs::new(rhs) }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for RhsPostOrder<'a> {
|
||||
type Item = &'a Rhs<'a>;
|
||||
impl<'a, TOperator> Iterator for RhsPostOrder<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy,
|
||||
{
|
||||
type Item = &'a Rhs<'a, TOperator>;
|
||||
|
||||
fn next(&mut self) -> Option<&'a Rhs<'a>> {
|
||||
fn next(&mut self) -> Option<&'a Rhs<'a, TOperator>> {
|
||||
use crate::traversals::TraversalEvent as TE;
|
||||
loop {
|
||||
match self.dfs.next()? {
|
||||
@@ -203,14 +218,17 @@ impl<'a> Iterator for RhsPostOrder<'a> {
|
||||
///
|
||||
/// Keeps track of the path to each pattern, and yields it along side the
|
||||
/// pattern AST node.
|
||||
struct PatternPreOrder<'a> {
|
||||
struct PatternPreOrder<'a, TOperator> {
|
||||
last_child: Option<u8>,
|
||||
path: Vec<u8>,
|
||||
dfs: Dfs<'a>,
|
||||
dfs: Dfs<'a, TOperator>,
|
||||
}
|
||||
|
||||
impl<'a> PatternPreOrder<'a> {
|
||||
fn new(pattern: &'a Pattern<'a>) -> Self {
|
||||
impl<'a, TOperator> PatternPreOrder<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
fn new(pattern: &'a Pattern<'a, TOperator>) -> Self {
|
||||
Self {
|
||||
last_child: None,
|
||||
path: vec![],
|
||||
@@ -218,7 +236,7 @@ impl<'a> PatternPreOrder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn next(&mut self, paths: &mut PathInterner) -> Option<(PathId, &'a Pattern<'a>)> {
|
||||
fn next(&mut self, paths: &mut PathInterner) -> Option<(PathId, &'a Pattern<'a, TOperator>)> {
|
||||
use crate::traversals::TraversalEvent as TE;
|
||||
loop {
|
||||
match self.dfs.next()? {
|
||||
@@ -252,15 +270,17 @@ impl<'a> PatternPreOrder<'a> {
|
||||
|
||||
/// A map from left-hand side identifiers to the path in the left-hand side
|
||||
/// where they first occurred.
|
||||
struct LhsIdToPath<'a> {
|
||||
struct LhsIdToPath<'a, TOperator> {
|
||||
id_to_path: BTreeMap<&'a str, PathId>,
|
||||
_marker: PhantomData<&'a TOperator>,
|
||||
}
|
||||
|
||||
impl<'a> LhsIdToPath<'a> {
|
||||
impl<'a, TOperator> LhsIdToPath<'a, TOperator> {
|
||||
/// Construct a new, empty `LhsIdToPath`.
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
id_to_path: Default::default(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,7 +300,7 @@ impl<'a> LhsIdToPath<'a> {
|
||||
}
|
||||
|
||||
/// Remember the path to any LHS ids used in the given pattern.
|
||||
fn remember_path_to_pattern_ids(&mut self, pattern: &'a Pattern<'a>, path: PathId) {
|
||||
fn remember_path_to_pattern_ids(&mut self, pattern: &'a Pattern<'a, TOperator>, path: PathId) {
|
||||
match pattern {
|
||||
// If this is the first time we've seen an identifier defined on the
|
||||
// left-hand side, remember it.
|
||||
@@ -294,10 +314,10 @@ impl<'a> LhsIdToPath<'a> {
|
||||
|
||||
/// An `RhsBuilder` emits the actions for building the right-hand side
|
||||
/// instructions.
|
||||
struct RhsBuilder<'a> {
|
||||
struct RhsBuilder<'a, TOperator> {
|
||||
// We do a post order traversal of the RHS because an RHS instruction cannot
|
||||
// be created until after all of its operands are created.
|
||||
rhs_post_order: RhsPostOrder<'a>,
|
||||
rhs_post_order: RhsPostOrder<'a, TOperator>,
|
||||
|
||||
// A map from a right-hand side's span to its `linear::RhsId`. This is used
|
||||
// by RHS-construction actions to reference operands. In practice the
|
||||
@@ -306,9 +326,12 @@ struct RhsBuilder<'a> {
|
||||
rhs_span_to_id: BTreeMap<wast::Span, linear::RhsId>,
|
||||
}
|
||||
|
||||
impl<'a> RhsBuilder<'a> {
|
||||
impl<'a, TOperator> RhsBuilder<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Create a new builder for the given right-hand side.
|
||||
fn new(rhs: &'a Rhs<'a>) -> Self {
|
||||
fn new(rhs: &'a Rhs<'a, TOperator>) -> Self {
|
||||
let rhs_post_order = RhsPostOrder::new(rhs);
|
||||
let rhs_span_to_id = Default::default();
|
||||
Self {
|
||||
@@ -323,7 +346,7 @@ impl<'a> RhsBuilder<'a> {
|
||||
///
|
||||
/// Panics if we haven't already emitted the action for building this RHS's
|
||||
/// instruction.
|
||||
fn get_rhs_id(&self, rhs: &Rhs) -> linear::RhsId {
|
||||
fn get_rhs_id(&self, rhs: &Rhs<TOperator>) -> linear::RhsId {
|
||||
self.rhs_span_to_id[&rhs.span()]
|
||||
}
|
||||
|
||||
@@ -335,8 +358,8 @@ impl<'a> RhsBuilder<'a> {
|
||||
fn add_rhs_build_actions(
|
||||
&mut self,
|
||||
integers: &mut IntegerInterner,
|
||||
lhs_id_to_path: &LhsIdToPath,
|
||||
actions: &mut Vec<linear::Action>,
|
||||
lhs_id_to_path: &LhsIdToPath<TOperator>,
|
||||
actions: &mut Vec<linear::Action<TOperator>>,
|
||||
) {
|
||||
while let Some(rhs) = self.rhs_post_order.next() {
|
||||
actions.push(self.rhs_to_linear_action(integers, lhs_id_to_path, rhs));
|
||||
@@ -348,9 +371,9 @@ impl<'a> RhsBuilder<'a> {
|
||||
fn rhs_to_linear_action(
|
||||
&self,
|
||||
integers: &mut IntegerInterner,
|
||||
lhs_id_to_path: &LhsIdToPath,
|
||||
rhs: &Rhs,
|
||||
) -> linear::Action {
|
||||
lhs_id_to_path: &LhsIdToPath<TOperator>,
|
||||
rhs: &Rhs<TOperator>,
|
||||
) -> linear::Action<TOperator> {
|
||||
match rhs {
|
||||
Rhs::ValueLiteral(ValueLiteral::Integer(i)) => linear::Action::MakeIntegerConst {
|
||||
value: integers.intern(i.value as u64),
|
||||
@@ -425,9 +448,15 @@ impl<'a> RhsBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Precondition<'_> {
|
||||
impl<TOperator> Precondition<'_, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||
{
|
||||
/// Convert this precondition into a `linear::Increment`.
|
||||
fn to_linear_increment(&self, lhs_id_to_path: &LhsIdToPath) -> linear::Increment {
|
||||
fn to_linear_increment(
|
||||
&self,
|
||||
lhs_id_to_path: &LhsIdToPath<TOperator>,
|
||||
) -> linear::Increment<TOperator> {
|
||||
match self.constraint {
|
||||
Constraint::IsPowerOfTwo => {
|
||||
let id = match &self.operands[0] {
|
||||
@@ -484,7 +513,10 @@ impl Precondition<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Pattern<'_> {
|
||||
impl<TOperator> Pattern<'_, TOperator>
|
||||
where
|
||||
TOperator: Copy,
|
||||
{
|
||||
/// Convert this pattern into its linear match operation and the expected
|
||||
/// result of that operation.
|
||||
///
|
||||
@@ -493,9 +525,12 @@ impl Pattern<'_> {
|
||||
fn to_linear_match_op(
|
||||
&self,
|
||||
integers: &mut IntegerInterner,
|
||||
lhs_id_to_path: &LhsIdToPath,
|
||||
lhs_id_to_path: &LhsIdToPath<TOperator>,
|
||||
path: PathId,
|
||||
) -> (linear::MatchOp, linear::MatchResult) {
|
||||
) -> (linear::MatchOp, linear::MatchResult)
|
||||
where
|
||||
TOperator: Into<NonZeroU32>,
|
||||
{
|
||||
match self {
|
||||
Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => (
|
||||
linear::MatchOp::IntegerValue { path },
|
||||
@@ -543,9 +578,7 @@ impl Pattern<'_> {
|
||||
}
|
||||
}
|
||||
Pattern::Operation(op) => {
|
||||
let op = op.operator as u32;
|
||||
debug_assert!(op != 0, "no `Operator` variants are zero");
|
||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(op) });
|
||||
let expected = Ok(op.operator.into());
|
||||
(linear::MatchOp::Opcode { path }, expected)
|
||||
}
|
||||
}
|
||||
@@ -558,9 +591,9 @@ mod tests {
|
||||
use peepmatic_runtime::{
|
||||
integer_interner::IntegerId,
|
||||
linear::{bool_to_match_result, Action::*, Else, MatchOp::*},
|
||||
operator::Operator,
|
||||
r#type::{BitWidth, Kind, Type},
|
||||
};
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
macro_rules! linearizes_to {
|
||||
($name:ident, $source:expr, $make_expected:expr $(,)* ) => {
|
||||
@@ -568,7 +601,7 @@ mod tests {
|
||||
fn $name() {
|
||||
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
|
||||
|
||||
let opts = match wast::parser::parse::<Optimizations>(&buf) {
|
||||
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(std::path::Path::new(stringify!($name)));
|
||||
@@ -602,7 +635,7 @@ mod tests {
|
||||
let make_expected: fn(
|
||||
&mut dyn FnMut(&[u8]) -> PathId,
|
||||
&mut dyn FnMut(u64) -> IntegerId,
|
||||
) -> Vec<linear::Increment> = $make_expected;
|
||||
) -> Vec<linear::Increment<TestOperator>> = $make_expected;
|
||||
let expected = make_expected(&mut p, &mut i);
|
||||
dbg!(&expected);
|
||||
|
||||
@@ -624,12 +657,12 @@ mod tests {
|
||||
|p, i| vec![
|
||||
linear::Increment {
|
||||
operation: Opcode { path: p(&[0]) },
|
||||
expected: Ok(NonZeroU32::new(Operator::Imul as _).unwrap()),
|
||||
expected: Ok(TestOperator::Imul.into()),
|
||||
actions: vec![
|
||||
GetLhs { path: p(&[0, 0]) },
|
||||
GetLhs { path: p(&[0, 1]) },
|
||||
MakeBinaryInst {
|
||||
operator: Operator::Ishl,
|
||||
operator: TestOperator::Ishl,
|
||||
r#type: Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::Polymorphic,
|
||||
@@ -702,11 +735,11 @@ mod tests {
|
||||
|p, i| vec![
|
||||
linear::Increment {
|
||||
operation: Opcode { path: p(&[0]) },
|
||||
expected: Ok(NonZeroU32::new(Operator::Iconst as _).unwrap()),
|
||||
expected: Ok(TestOperator::Iconst.into()),
|
||||
actions: vec![
|
||||
GetLhs { path: p(&[0, 0]) },
|
||||
MakeUnaryInst {
|
||||
operator: Operator::Iconst,
|
||||
operator: TestOperator::Iconst,
|
||||
r#type: Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::Polymorphic,
|
||||
@@ -729,14 +762,14 @@ mod tests {
|
||||
|p, i| vec![
|
||||
linear::Increment {
|
||||
operation: Opcode { path: p(&[0]) },
|
||||
expected: Ok(NonZeroU32::new(Operator::Bor as _).unwrap()),
|
||||
expected: Ok(TestOperator::Bor.into()),
|
||||
actions: vec![
|
||||
GetLhs { path: p(&[0, 0]) },
|
||||
GetLhs {
|
||||
path: p(&[0, 1, 1]),
|
||||
},
|
||||
MakeBinaryInst {
|
||||
operator: Operator::Bor,
|
||||
operator: TestOperator::Bor,
|
||||
r#type: Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: BitWidth::Polymorphic,
|
||||
@@ -752,7 +785,7 @@ mod tests {
|
||||
},
|
||||
linear::Increment {
|
||||
operation: Opcode { path: p(&[0, 1]) },
|
||||
expected: Ok(NonZeroU32::new(Operator::Bor as _).unwrap()),
|
||||
expected: Ok(TestOperator::Bor.into()),
|
||||
actions: vec![],
|
||||
},
|
||||
linear::Increment {
|
||||
@@ -791,7 +824,7 @@ mod tests {
|
||||
|p, i| vec![
|
||||
linear::Increment {
|
||||
operation: Opcode { path: p(&[0]) },
|
||||
expected: Ok(NonZeroU32::new(Operator::Ireduce as _).unwrap()),
|
||||
expected: Ok(TestOperator::Ireduce.into()),
|
||||
actions: vec![MakeIntegerConst {
|
||||
value: i(0),
|
||||
bit_width: BitWidth::ThirtyTwo,
|
||||
|
||||
@@ -84,7 +84,10 @@ mod tok {
|
||||
custom_keyword!(nof);
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Optimizations<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Optimizations<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
let mut optimizations = vec![];
|
||||
@@ -98,7 +101,10 @@ impl<'a> Parse<'a> for Optimizations<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Optimization<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Optimization<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
p.parens(|p| {
|
||||
@@ -110,7 +116,10 @@ impl<'a> Parse<'a> for Optimization<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Lhs<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Lhs<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
let mut preconditions = vec![];
|
||||
@@ -139,30 +148,36 @@ impl<'a> Parse<'a> for Lhs<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Pattern<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Pattern<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if p.peek::<ValueLiteral>() {
|
||||
if p.peek::<ValueLiteral<TOperator>>() {
|
||||
return Ok(Pattern::ValueLiteral(p.parse()?));
|
||||
}
|
||||
if p.peek::<Constant>() {
|
||||
if p.peek::<Constant<TOperator>>() {
|
||||
return Ok(Pattern::Constant(p.parse()?));
|
||||
}
|
||||
if p.peek::<Operation<Self>>() {
|
||||
if p.peek::<Operation<TOperator, Self>>() {
|
||||
return Ok(Pattern::Operation(p.parse()?));
|
||||
}
|
||||
if p.peek::<Variable>() {
|
||||
if p.peek::<Variable<TOperator>>() {
|
||||
return Ok(Pattern::Variable(p.parse()?));
|
||||
}
|
||||
Err(p.error("expected a left-hand side pattern"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for Pattern<'a> {
|
||||
impl<'a, TOperator> Peek for Pattern<'a, TOperator>
|
||||
where
|
||||
TOperator: 'a,
|
||||
{
|
||||
fn peek(c: Cursor) -> bool {
|
||||
ValueLiteral::peek(c)
|
||||
|| Constant::peek(c)
|
||||
|| Variable::peek(c)
|
||||
|| Operation::<Self>::peek(c)
|
||||
ValueLiteral::<TOperator>::peek(c)
|
||||
|| Constant::<TOperator>::peek(c)
|
||||
|| Variable::<TOperator>::peek(c)
|
||||
|| Operation::<TOperator, Self>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
@@ -170,24 +185,26 @@ impl<'a> Peek for Pattern<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for ValueLiteral<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for ValueLiteral<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if let Ok(b) = p.parse::<Boolean>() {
|
||||
if let Ok(b) = p.parse::<Boolean<TOperator>>() {
|
||||
return Ok(ValueLiteral::Boolean(b));
|
||||
}
|
||||
if let Ok(i) = p.parse::<Integer>() {
|
||||
if let Ok(i) = p.parse::<Integer<TOperator>>() {
|
||||
return Ok(ValueLiteral::Integer(i));
|
||||
}
|
||||
if let Ok(cc) = p.parse::<ConditionCode>() {
|
||||
if let Ok(cc) = p.parse::<ConditionCode<TOperator>>() {
|
||||
return Ok(ValueLiteral::ConditionCode(cc));
|
||||
}
|
||||
Err(p.error("expected an integer or boolean or condition code literal"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for ValueLiteral<'a> {
|
||||
impl<'a, TOperator> Peek for ValueLiteral<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
c.integer().is_some() || Boolean::peek(c) || ConditionCode::peek(c)
|
||||
c.integer().is_some()
|
||||
|| Boolean::<TOperator>::peek(c)
|
||||
|| ConditionCode::<TOperator>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
@@ -195,7 +212,7 @@ impl<'a> Peek for ValueLiteral<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Integer<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Integer<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
p.step(|c| {
|
||||
@@ -221,7 +238,7 @@ impl<'a> Parse<'a> for Integer<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Boolean<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Boolean<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
if p.parse::<tok::r#true>().is_ok() {
|
||||
@@ -244,7 +261,7 @@ impl<'a> Parse<'a> for Boolean<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for Boolean<'a> {
|
||||
impl<'a, TOperator> Peek for Boolean<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
<tok::r#true as Peek>::peek(c) || <tok::r#false as Peek>::peek(c)
|
||||
}
|
||||
@@ -254,7 +271,7 @@ impl<'a> Peek for Boolean<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for ConditionCode<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for ConditionCode<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
|
||||
@@ -292,7 +309,7 @@ impl<'a> Parse<'a> for ConditionCode<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for ConditionCode<'a> {
|
||||
impl<'a, TOperator> Peek for ConditionCode<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
macro_rules! peek_cc {
|
||||
( $( $token:ident, )* ) => {
|
||||
@@ -321,7 +338,7 @@ impl<'a> Peek for ConditionCode<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Constant<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Constant<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
let id = Id::parse(p)?;
|
||||
@@ -330,7 +347,11 @@ impl<'a> Parse<'a> for Constant<'a> {
|
||||
.chars()
|
||||
.all(|c| !c.is_alphabetic() || c.is_uppercase())
|
||||
{
|
||||
Ok(Constant { span, id })
|
||||
Ok(Constant {
|
||||
span,
|
||||
id,
|
||||
marker: PhantomData,
|
||||
})
|
||||
} else {
|
||||
let upper = id
|
||||
.name()
|
||||
@@ -345,7 +366,7 @@ impl<'a> Parse<'a> for Constant<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for Constant<'a> {
|
||||
impl<'a, TOperator> Peek for Constant<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
if let Some((id, _rest)) = c.id() {
|
||||
id.chars().all(|c| !c.is_alphabetic() || c.is_uppercase())
|
||||
@@ -359,7 +380,7 @@ impl<'a> Peek for Constant<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Variable<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Variable<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
let id = Id::parse(p)?;
|
||||
@@ -368,7 +389,11 @@ impl<'a> Parse<'a> for Variable<'a> {
|
||||
.chars()
|
||||
.all(|c| !c.is_alphabetic() || c.is_lowercase())
|
||||
{
|
||||
Ok(Variable { span, id })
|
||||
Ok(Variable {
|
||||
span,
|
||||
id,
|
||||
marker: PhantomData,
|
||||
})
|
||||
} else {
|
||||
let lower = id
|
||||
.name()
|
||||
@@ -383,7 +408,7 @@ impl<'a> Parse<'a> for Variable<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for Variable<'a> {
|
||||
impl<'a, TOperator> Peek for Variable<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
if let Some((id, _rest)) = c.id() {
|
||||
id.chars().all(|c| !c.is_alphabetic() || c.is_lowercase())
|
||||
@@ -397,10 +422,11 @@ impl<'a> Peek for Variable<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Parse<'a> for Operation<'a, T>
|
||||
impl<'a, TOperator, TOperand> Parse<'a> for Operation<'a, TOperator, TOperand>
|
||||
where
|
||||
T: 'a + Ast<'a> + Peek + Parse<'a>,
|
||||
DynAstRef<'a>: From<&'a T>,
|
||||
TOperator: Parse<'a>,
|
||||
TOperand: 'a + Ast<'a, TOperator> + Peek + Parse<'a>,
|
||||
DynAstRef<'a, TOperator>: From<&'a TOperand>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
@@ -417,7 +443,7 @@ where
|
||||
});
|
||||
|
||||
let mut operands = vec![];
|
||||
while p.peek::<T>() {
|
||||
while p.peek::<TOperand>() {
|
||||
operands.push(p.parse()?);
|
||||
}
|
||||
Ok(Operation {
|
||||
@@ -431,10 +457,10 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T> Peek for Operation<'a, T>
|
||||
impl<'a, TOperator, TOperand> Peek for Operation<'a, TOperator, TOperand>
|
||||
where
|
||||
T: 'a + Ast<'a>,
|
||||
DynAstRef<'a>: From<&'a T>,
|
||||
TOperand: 'a + Ast<'a, TOperator>,
|
||||
DynAstRef<'a, TOperator>: From<&'a TOperand>,
|
||||
{
|
||||
fn peek(c: Cursor) -> bool {
|
||||
wast::LParen::peek(c)
|
||||
@@ -445,19 +471,20 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Precondition<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Precondition<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
p.parens(|p| {
|
||||
let constraint = p.parse()?;
|
||||
let mut operands = vec![];
|
||||
while p.peek::<ConstraintOperand>() {
|
||||
while p.peek::<ConstraintOperand<TOperator>>() {
|
||||
operands.push(p.parse()?);
|
||||
}
|
||||
Ok(Precondition {
|
||||
span,
|
||||
constraint,
|
||||
operands,
|
||||
marker: PhantomData,
|
||||
})
|
||||
})
|
||||
}
|
||||
@@ -481,24 +508,26 @@ impl<'a> Parse<'a> for Constraint {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for ConstraintOperand<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for ConstraintOperand<'a, TOperator> {
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if p.peek::<ValueLiteral>() {
|
||||
if p.peek::<ValueLiteral<TOperator>>() {
|
||||
return Ok(ConstraintOperand::ValueLiteral(p.parse()?));
|
||||
}
|
||||
if p.peek::<Constant>() {
|
||||
if p.peek::<Constant<TOperator>>() {
|
||||
return Ok(ConstraintOperand::Constant(p.parse()?));
|
||||
}
|
||||
if p.peek::<Variable>() {
|
||||
if p.peek::<Variable<TOperator>>() {
|
||||
return Ok(ConstraintOperand::Variable(p.parse()?));
|
||||
}
|
||||
Err(p.error("expected an operand for precondition constraint"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for ConstraintOperand<'a> {
|
||||
impl<'a, TOperator> Peek for ConstraintOperand<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
ValueLiteral::peek(c) || Constant::peek(c) || Variable::peek(c)
|
||||
ValueLiteral::<TOperator>::peek(c)
|
||||
|| Constant::<TOperator>::peek(c)
|
||||
|| Variable::<TOperator>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
@@ -506,34 +535,40 @@ impl<'a> Peek for ConstraintOperand<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Rhs<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Rhs<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
if p.peek::<ValueLiteral>() {
|
||||
if p.peek::<ValueLiteral<TOperator>>() {
|
||||
return Ok(Rhs::ValueLiteral(p.parse()?));
|
||||
}
|
||||
if p.peek::<Constant>() {
|
||||
if p.peek::<Constant<TOperator>>() {
|
||||
return Ok(Rhs::Constant(p.parse()?));
|
||||
}
|
||||
if p.peek::<Variable>() {
|
||||
if p.peek::<Variable<TOperator>>() {
|
||||
return Ok(Rhs::Variable(p.parse()?));
|
||||
}
|
||||
if p.peek::<Unquote>() {
|
||||
if p.peek::<Unquote<TOperator>>() {
|
||||
return Ok(Rhs::Unquote(p.parse()?));
|
||||
}
|
||||
if p.peek::<Operation<Self>>() {
|
||||
if p.peek::<Operation<TOperator, Self>>() {
|
||||
return Ok(Rhs::Operation(p.parse()?));
|
||||
}
|
||||
Err(p.error("expected a right-hand side replacement"))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for Rhs<'a> {
|
||||
impl<'a, TOperator> Peek for Rhs<'a, TOperator>
|
||||
where
|
||||
TOperator: 'a,
|
||||
{
|
||||
fn peek(c: Cursor) -> bool {
|
||||
ValueLiteral::peek(c)
|
||||
|| Constant::peek(c)
|
||||
|| Variable::peek(c)
|
||||
|| Unquote::peek(c)
|
||||
|| Operation::<Self>::peek(c)
|
||||
ValueLiteral::<TOperator>::peek(c)
|
||||
|| Constant::<TOperator>::peek(c)
|
||||
|| Variable::<TOperator>::peek(c)
|
||||
|| Unquote::<TOperator>::peek(c)
|
||||
|| Operation::<TOperator, Self>::peek(c)
|
||||
}
|
||||
|
||||
fn display() -> &'static str {
|
||||
@@ -541,26 +576,30 @@ impl<'a> Peek for Rhs<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Parse<'a> for Unquote<'a> {
|
||||
impl<'a, TOperator> Parse<'a> for Unquote<'a, TOperator>
|
||||
where
|
||||
TOperator: Parse<'a>,
|
||||
{
|
||||
fn parse(p: Parser<'a>) -> ParseResult<Self> {
|
||||
let span = p.cur_span();
|
||||
p.parse::<tok::dollar>()?;
|
||||
p.parens(|p| {
|
||||
let operator = p.parse()?;
|
||||
let mut operands = vec![];
|
||||
while p.peek::<Rhs>() {
|
||||
while p.peek::<Rhs<TOperator>>() {
|
||||
operands.push(p.parse()?);
|
||||
}
|
||||
Ok(Unquote {
|
||||
span,
|
||||
operator,
|
||||
operands,
|
||||
marker: PhantomData,
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Peek for Unquote<'a> {
|
||||
impl<'a, TOperator> Peek for Unquote<'a, TOperator> {
|
||||
fn peek(c: Cursor) -> bool {
|
||||
tok::dollar::peek(c)
|
||||
}
|
||||
@@ -573,7 +612,7 @@ impl<'a> Peek for Unquote<'a> {
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
use peepmatic_runtime::operator::Operator;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
macro_rules! test_parse {
|
||||
(
|
||||
@@ -623,7 +662,7 @@ mod test {
|
||||
}
|
||||
|
||||
test_parse! {
|
||||
parse_boolean<Boolean> {
|
||||
parse_boolean<Boolean<TestOperator>> {
|
||||
ok {
|
||||
"true",
|
||||
"false",
|
||||
@@ -641,7 +680,7 @@ mod test {
|
||||
"falsezzz",
|
||||
}
|
||||
}
|
||||
parse_cc<ConditionCode> {
|
||||
parse_cc<ConditionCode<TestOperator>> {
|
||||
ok {
|
||||
"eq",
|
||||
"ne",
|
||||
@@ -661,7 +700,7 @@ mod test {
|
||||
"neq",
|
||||
}
|
||||
}
|
||||
parse_constant<Constant> {
|
||||
parse_constant<Constant<TestOperator>> {
|
||||
ok {
|
||||
"$C",
|
||||
"$C1",
|
||||
@@ -693,7 +732,7 @@ mod test {
|
||||
"imul",
|
||||
}
|
||||
}
|
||||
parse_constraint_operand<ConstraintOperand> {
|
||||
parse_constraint_operand<ConstraintOperand<TestOperator>> {
|
||||
ok {
|
||||
"1234",
|
||||
"true",
|
||||
@@ -707,7 +746,7 @@ mod test {
|
||||
"(iadd 1 2)",
|
||||
}
|
||||
}
|
||||
parse_integer<Integer> {
|
||||
parse_integer<Integer<TestOperator>> {
|
||||
ok {
|
||||
"0",
|
||||
"1",
|
||||
@@ -746,7 +785,7 @@ mod test {
|
||||
"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||
}
|
||||
}
|
||||
parse_lhs<Lhs> {
|
||||
parse_lhs<Lhs<TestOperator>> {
|
||||
ok {
|
||||
"(when (imul $C1 $C2) (is-power-of-two $C1) (is-power-of-two $C2))",
|
||||
"(when (imul $x $C) (is-power-of-two $C))",
|
||||
@@ -762,7 +801,7 @@ mod test {
|
||||
"abc",
|
||||
}
|
||||
}
|
||||
parse_operation_pattern<Operation<Pattern>> {
|
||||
parse_operation_pattern<Operation<TestOperator, Pattern<TestOperator>>> {
|
||||
ok {
|
||||
"(iadd)",
|
||||
"(iadd 1)",
|
||||
@@ -779,7 +818,7 @@ mod test {
|
||||
"(ishl $x $(log2 $C))",
|
||||
}
|
||||
}
|
||||
parse_operation_rhs<Operation<Rhs>> {
|
||||
parse_operation_rhs<Operation<TestOperator, Rhs<TestOperator>>> {
|
||||
ok {
|
||||
"(iadd)",
|
||||
"(iadd 1)",
|
||||
@@ -793,7 +832,7 @@ mod test {
|
||||
"$CONST",
|
||||
}
|
||||
}
|
||||
parse_operator<Operator> {
|
||||
parse_operator<TestOperator> {
|
||||
ok {
|
||||
"bor",
|
||||
"iadd",
|
||||
@@ -812,7 +851,7 @@ mod test {
|
||||
"iadd{i32}",
|
||||
}
|
||||
}
|
||||
parse_optimization<Optimization> {
|
||||
parse_optimization<Optimization<TestOperator>> {
|
||||
ok {
|
||||
"(=> (when (iadd $x $C) (is-power-of-two $C) (is-power-of-two $C)) (iadd $C $x))",
|
||||
"(=> (when (iadd $x $C)) (iadd $C $x))",
|
||||
@@ -825,7 +864,7 @@ mod test {
|
||||
"(=> () ())",
|
||||
}
|
||||
}
|
||||
parse_optimizations<Optimizations> {
|
||||
parse_optimizations<Optimizations<TestOperator>> {
|
||||
ok {
|
||||
"",
|
||||
r#"
|
||||
@@ -844,7 +883,7 @@ mod test {
|
||||
"#,
|
||||
}
|
||||
}
|
||||
parse_pattern<Pattern> {
|
||||
parse_pattern<Pattern<TestOperator>> {
|
||||
ok {
|
||||
"1234",
|
||||
"$C",
|
||||
@@ -857,7 +896,7 @@ mod test {
|
||||
"abc",
|
||||
}
|
||||
}
|
||||
parse_precondition<Precondition> {
|
||||
parse_precondition<Precondition<TestOperator>> {
|
||||
ok {
|
||||
"(is-power-of-two)",
|
||||
"(is-power-of-two $C)",
|
||||
@@ -871,7 +910,7 @@ mod test {
|
||||
"$CONST",
|
||||
}
|
||||
}
|
||||
parse_rhs<Rhs> {
|
||||
parse_rhs<Rhs<TestOperator>> {
|
||||
ok {
|
||||
"5",
|
||||
"$C",
|
||||
@@ -884,7 +923,7 @@ mod test {
|
||||
"()",
|
||||
}
|
||||
}
|
||||
parse_unquote<Unquote> {
|
||||
parse_unquote<Unquote<TestOperator>> {
|
||||
ok {
|
||||
"$(log2)",
|
||||
"$(log2 $C)",
|
||||
@@ -899,7 +938,7 @@ mod test {
|
||||
"$()",
|
||||
}
|
||||
}
|
||||
parse_value_literal<ValueLiteral> {
|
||||
parse_value_literal<ValueLiteral<TestOperator>> {
|
||||
ok {
|
||||
"12345",
|
||||
"true",
|
||||
@@ -911,7 +950,7 @@ mod test {
|
||||
"12.34",
|
||||
}
|
||||
}
|
||||
parse_variable<Variable> {
|
||||
parse_variable<Variable<TestOperator>> {
|
||||
ok {
|
||||
"$v",
|
||||
"$v1",
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
//! Traversals over the AST.
|
||||
|
||||
use crate::ast::*;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
|
||||
/// A low-level DFS traversal event: either entering or exiting the traversal of
|
||||
/// an AST node.
|
||||
@@ -32,13 +34,16 @@ pub enum TraversalEvent {
|
||||
/// the AST, because the `new` constructor takes anything that can convert into
|
||||
/// a `DynAstRef`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dfs<'a> {
|
||||
stack: Vec<(TraversalEvent, DynAstRef<'a>)>,
|
||||
pub struct Dfs<'a, TOperator> {
|
||||
stack: Vec<(TraversalEvent, DynAstRef<'a, TOperator>)>,
|
||||
}
|
||||
|
||||
impl<'a> Dfs<'a> {
|
||||
impl<'a, TOperator> Dfs<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
/// Construct a new `Dfs` traversal starting at the given `start` AST node.
|
||||
pub fn new(start: impl Into<DynAstRef<'a>>) -> Self {
|
||||
pub fn new(start: impl Into<DynAstRef<'a, TOperator>>) -> Self {
|
||||
let start = start.into();
|
||||
Dfs {
|
||||
stack: vec![
|
||||
@@ -49,15 +54,18 @@ impl<'a> Dfs<'a> {
|
||||
}
|
||||
|
||||
/// Peek at the next traversal event and AST node pair, if any.
|
||||
pub fn peek(&self) -> Option<(TraversalEvent, DynAstRef<'a>)> {
|
||||
self.stack.last().cloned()
|
||||
pub fn peek(&self) -> Option<(TraversalEvent, DynAstRef<'a, TOperator>)> {
|
||||
self.stack.last().copied()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Iterator for Dfs<'a> {
|
||||
type Item = (TraversalEvent, DynAstRef<'a>);
|
||||
impl<'a, TOperator> Iterator for Dfs<'a, TOperator>
|
||||
where
|
||||
TOperator: Copy,
|
||||
{
|
||||
type Item = (TraversalEvent, DynAstRef<'a, TOperator>);
|
||||
|
||||
fn next(&mut self) -> Option<(TraversalEvent, DynAstRef<'a>)> {
|
||||
fn next(&mut self) -> Option<(TraversalEvent, DynAstRef<'a, TOperator>)> {
|
||||
let (event, node) = self.stack.pop()?;
|
||||
if let TraversalEvent::Enter = event {
|
||||
let mut enqueue_children = EnqueueChildren(self);
|
||||
@@ -65,15 +73,16 @@ impl<'a> Iterator for Dfs<'a> {
|
||||
}
|
||||
return Some((event, node));
|
||||
|
||||
struct EnqueueChildren<'a, 'b>(&'b mut Dfs<'a>)
|
||||
struct EnqueueChildren<'a, 'b, TOperator>(&'b mut Dfs<'a, TOperator>)
|
||||
where
|
||||
'a: 'b;
|
||||
|
||||
impl<'a, 'b> Extend<DynAstRef<'a>> for EnqueueChildren<'a, 'b>
|
||||
impl<'a, 'b, TOperator> Extend<DynAstRef<'a, TOperator>> for EnqueueChildren<'a, 'b, TOperator>
|
||||
where
|
||||
'a: 'b,
|
||||
TOperator: Copy,
|
||||
{
|
||||
fn extend<T: IntoIterator<Item = DynAstRef<'a>>>(&mut self, iter: T) {
|
||||
fn extend<T: IntoIterator<Item = DynAstRef<'a, TOperator>>>(&mut self, iter: T) {
|
||||
let iter = iter.into_iter();
|
||||
|
||||
let (min, max) = iter.size_hint();
|
||||
@@ -97,6 +106,7 @@ impl<'a> Iterator for Dfs<'a> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
use DynAstRef::*;
|
||||
|
||||
#[test]
|
||||
@@ -107,7 +117,7 @@ mod tests {
|
||||
(ishl $x $(log2 $C)))
|
||||
";
|
||||
let buf = wast::parser::ParseBuffer::new(input).expect("input should lex OK");
|
||||
let ast = match wast::parser::parse::<crate::ast::Optimizations>(&buf) {
|
||||
let ast = match wast::parser::parse::<crate::ast::Optimizations<TestOperator>>(&buf) {
|
||||
Ok(ast) => ast,
|
||||
Err(e) => panic!("expected to parse OK, got error:\n\n{}", e),
|
||||
};
|
||||
|
||||
@@ -14,14 +14,13 @@
|
||||
|
||||
use crate::ast::{Span as _, *};
|
||||
use crate::traversals::{Dfs, TraversalEvent};
|
||||
use peepmatic_runtime::{
|
||||
operator::{Operator, TypingContext as TypingContextTrait},
|
||||
r#type::{BitWidth, Kind, Type},
|
||||
};
|
||||
use peepmatic_runtime::r#type::{BitWidth, Kind, Type};
|
||||
use peepmatic_traits::{TypingContext as TypingContextTrait, TypingRules};
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::fmt;
|
||||
use std::fmt::Debug;
|
||||
use std::hash::Hash;
|
||||
use std::iter;
|
||||
use std::mem;
|
||||
@@ -94,7 +93,10 @@ impl VerifyError {
|
||||
pub type VerifyResult<T> = Result<T, VerifyError>;
|
||||
|
||||
/// Verify and type check a set of optimizations.
|
||||
pub fn verify(opts: &Optimizations) -> VerifyResult<()> {
|
||||
pub fn verify<TOperator>(opts: &Optimizations<TOperator>) -> VerifyResult<()>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + TypingRules,
|
||||
{
|
||||
if opts.optimizations.is_empty() {
|
||||
return Err(anyhow::anyhow!("no optimizations").into());
|
||||
}
|
||||
@@ -113,7 +115,10 @@ pub fn verify(opts: &Optimizations) -> VerifyResult<()> {
|
||||
/// If there were duplicates, then it would be nondeterministic which one we
|
||||
/// applied and would make automata construction more difficult. It is better to
|
||||
/// check for duplicates and reject them if found.
|
||||
fn verify_unique_left_hand_sides(opts: &Optimizations) -> VerifyResult<()> {
|
||||
fn verify_unique_left_hand_sides<TOperator>(opts: &Optimizations<TOperator>) -> VerifyResult<()>
|
||||
where
|
||||
TOperator: Copy + Eq + Debug + Hash,
|
||||
{
|
||||
let mut lefts = HashMap::new();
|
||||
for opt in &opts.optimizations {
|
||||
let canon_lhs = canonicalized_lhs_key(&opt.lhs);
|
||||
@@ -146,7 +151,10 @@ fn verify_unique_left_hand_sides(opts: &Optimizations) -> VerifyResult<()> {
|
||||
///
|
||||
/// This function creates an opaque, canonicalized hash key for left-hand sides
|
||||
/// that sees through identifier renaming.
|
||||
fn canonicalized_lhs_key(lhs: &Lhs) -> impl Hash + Eq {
|
||||
fn canonicalized_lhs_key<TOperator>(lhs: &Lhs<TOperator>) -> impl Hash + Eq
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash,
|
||||
{
|
||||
let mut var_to_canon = HashMap::new();
|
||||
let mut const_to_canon = HashMap::new();
|
||||
let mut canonicalized = vec![];
|
||||
@@ -183,19 +191,20 @@ fn canonicalized_lhs_key(lhs: &Lhs) -> impl Hash + Eq {
|
||||
return canonicalized;
|
||||
|
||||
#[derive(Hash, PartialEq, Eq)]
|
||||
enum CanonicalBit {
|
||||
enum CanonicalBit<TOperator> {
|
||||
Var(u32),
|
||||
Const(u32),
|
||||
Integer(i64),
|
||||
Boolean(bool),
|
||||
ConditionCode(peepmatic_runtime::cc::ConditionCode),
|
||||
Operation(Operator, Option<Type>),
|
||||
Operation(TOperator, Option<Type>),
|
||||
Precondition(Constraint),
|
||||
Other(&'static str),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct TypingContext<'a> {
|
||||
#[derive(Debug)]
|
||||
struct TypingContext<'a, TOperator> {
|
||||
z3: &'a z3::Context,
|
||||
type_kind_sort: z3::DatatypeSort<'a>,
|
||||
solver: z3::Solver<'a>,
|
||||
@@ -218,12 +227,15 @@ pub(crate) struct TypingContext<'a> {
|
||||
// Keep track of AST nodes that need to have their types assigned to
|
||||
// them. For these AST nodes, we know what bit width to use when
|
||||
// interpreting peephole optimization actions.
|
||||
boolean_literals: Vec<(&'a Boolean<'a>, TypeVar<'a>)>,
|
||||
integer_literals: Vec<(&'a Integer<'a>, TypeVar<'a>)>,
|
||||
rhs_operations: Vec<(&'a Operation<'a, Rhs<'a>>, TypeVar<'a>)>,
|
||||
boolean_literals: Vec<(&'a Boolean<'a, TOperator>, TypeVar<'a>)>,
|
||||
integer_literals: Vec<(&'a Integer<'a, TOperator>, TypeVar<'a>)>,
|
||||
rhs_operations: Vec<(
|
||||
&'a Operation<'a, TOperator, Rhs<'a, TOperator>>,
|
||||
TypeVar<'a>,
|
||||
)>,
|
||||
}
|
||||
|
||||
impl<'a> TypingContext<'a> {
|
||||
impl<'a, TOperator> TypingContext<'a, TOperator> {
|
||||
fn new(z3: &'a z3::Context) -> Self {
|
||||
let type_kind_sort = z3::DatatypeBuilder::new(z3)
|
||||
.variant("int", &[])
|
||||
@@ -301,51 +313,55 @@ impl<'a> TypingContext<'a> {
|
||||
// and similar refer to the same type variables.
|
||||
fn enter_operation_scope<'b>(
|
||||
&'b mut self,
|
||||
) -> impl DerefMut<Target = TypingContext<'a>> + Drop + 'b {
|
||||
) -> impl DerefMut<Target = TypingContext<'a, TOperator>> + Drop + 'b {
|
||||
assert!(self.operation_scope.is_empty());
|
||||
return Scope(self);
|
||||
|
||||
struct Scope<'a, 'b>(&'b mut TypingContext<'a>)
|
||||
struct Scope<'a, 'b, TOperator>(&'b mut TypingContext<'a, TOperator>)
|
||||
where
|
||||
'a: 'b;
|
||||
|
||||
impl<'a, 'b> Deref for Scope<'a, 'b>
|
||||
impl<'a, 'b, TOperator> Deref for Scope<'a, 'b, TOperator>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
type Target = TypingContext<'a>;
|
||||
fn deref(&self) -> &TypingContext<'a> {
|
||||
type Target = TypingContext<'a, TOperator>;
|
||||
fn deref(&self) -> &TypingContext<'a, TOperator> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> DerefMut for Scope<'a, 'b>
|
||||
impl<'a, 'b, TOperator> DerefMut for Scope<'a, 'b, TOperator>
|
||||
where
|
||||
'a: 'b,
|
||||
{
|
||||
fn deref_mut(&mut self) -> &mut TypingContext<'a> {
|
||||
fn deref_mut(&mut self) -> &mut TypingContext<'a, TOperator> {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Scope<'_, '_> {
|
||||
impl<TOperator> Drop for Scope<'_, '_, TOperator> {
|
||||
fn drop(&mut self) {
|
||||
self.0.operation_scope.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remember_boolean_literal(&mut self, b: &'a Boolean<'a>, ty: TypeVar<'a>) {
|
||||
fn remember_boolean_literal(&mut self, b: &'a Boolean<'a, TOperator>, ty: TypeVar<'a>) {
|
||||
self.assert_is_bool(b.span, &ty);
|
||||
self.boolean_literals.push((b, ty));
|
||||
}
|
||||
|
||||
fn remember_integer_literal(&mut self, i: &'a Integer<'a>, ty: TypeVar<'a>) {
|
||||
fn remember_integer_literal(&mut self, i: &'a Integer<'a, TOperator>, ty: TypeVar<'a>) {
|
||||
self.assert_is_integer(i.span, &ty);
|
||||
self.integer_literals.push((i, ty));
|
||||
}
|
||||
|
||||
fn remember_rhs_operation(&mut self, op: &'a Operation<'a, Rhs<'a>>, ty: TypeVar<'a>) {
|
||||
fn remember_rhs_operation(
|
||||
&mut self,
|
||||
op: &'a Operation<'a, TOperator, Rhs<'a, TOperator>>,
|
||||
ty: TypeVar<'a>,
|
||||
) {
|
||||
self.rhs_operations.push((op, ty));
|
||||
}
|
||||
|
||||
@@ -638,7 +654,8 @@ impl<'a> TypingContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> TypingContextTrait<'a> for TypingContext<'a> {
|
||||
impl<'a, TOperator> TypingContextTrait<'a> for TypingContext<'a, TOperator> {
|
||||
type Span = Span;
|
||||
type TypeVariable = TypeVar<'a>;
|
||||
|
||||
fn cc(&mut self, span: Span) -> TypeVar<'a> {
|
||||
@@ -737,13 +754,19 @@ impl<'a> TypingContextTrait<'a> for TypingContext<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct TypeVar<'a> {
|
||||
#[derive(Clone, Debug)]
|
||||
struct TypeVar<'a> {
|
||||
kind: z3::ast::Datatype<'a>,
|
||||
width: z3::ast::BV<'a>,
|
||||
}
|
||||
|
||||
fn verify_optimization(z3: &z3::Context, opt: &Optimization) -> VerifyResult<()> {
|
||||
fn verify_optimization<TOperator>(
|
||||
z3: &z3::Context,
|
||||
opt: &Optimization<TOperator>,
|
||||
) -> VerifyResult<()>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + TypingRules,
|
||||
{
|
||||
let mut context = TypingContext::new(z3);
|
||||
collect_type_constraints(&mut context, opt)?;
|
||||
context.type_check(opt.span)?;
|
||||
@@ -755,10 +778,13 @@ fn verify_optimization(z3: &z3::Context, opt: &Optimization) -> VerifyResult<()>
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect_type_constraints<'a>(
|
||||
context: &mut TypingContext<'a>,
|
||||
opt: &'a Optimization<'a>,
|
||||
) -> VerifyResult<()> {
|
||||
fn collect_type_constraints<'a, TOperator>(
|
||||
context: &mut TypingContext<'a, TOperator>,
|
||||
opt: &'a Optimization<'a, TOperator>,
|
||||
) -> VerifyResult<()>
|
||||
where
|
||||
TOperator: Copy + Debug + Eq + Hash + TypingRules,
|
||||
{
|
||||
use crate::traversals::TraversalEvent as TE;
|
||||
|
||||
let lhs_ty = context.new_type_var();
|
||||
@@ -780,8 +806,22 @@ fn collect_type_constraints<'a>(
|
||||
// Build up the type constraints for the left-hand side.
|
||||
for (event, node) in Dfs::new(&opt.lhs) {
|
||||
match (event, node) {
|
||||
(TE::Enter, DynAstRef::Pattern(Pattern::Constant(Constant { id, span })))
|
||||
| (TE::Enter, DynAstRef::Pattern(Pattern::Variable(Variable { id, span }))) => {
|
||||
(
|
||||
TE::Enter,
|
||||
DynAstRef::Pattern(Pattern::Constant(Constant {
|
||||
id,
|
||||
span,
|
||||
marker: _,
|
||||
})),
|
||||
)
|
||||
| (
|
||||
TE::Enter,
|
||||
DynAstRef::Pattern(Pattern::Variable(Variable {
|
||||
id,
|
||||
span,
|
||||
marker: _,
|
||||
})),
|
||||
) => {
|
||||
let id = context.get_or_create_type_var_for_id(*id);
|
||||
context.assert_type_eq(*span, expected_types.last().unwrap(), &id, None);
|
||||
}
|
||||
@@ -805,11 +845,11 @@ fn collect_type_constraints<'a>(
|
||||
let mut operand_types = vec![];
|
||||
{
|
||||
let mut scope = context.enter_operation_scope();
|
||||
result_ty = op.operator.result_type(&mut *scope, op.span);
|
||||
result_ty = op.operator.result_type(op.span, &mut *scope);
|
||||
op.operator
|
||||
.immediate_types(&mut *scope, op.span, &mut operand_types);
|
||||
.immediate_types(op.span, &mut *scope, &mut operand_types);
|
||||
op.operator
|
||||
.param_types(&mut *scope, op.span, &mut operand_types);
|
||||
.parameter_types(op.span, &mut *scope, &mut operand_types);
|
||||
}
|
||||
|
||||
if op.operands.len() != operand_types.len() {
|
||||
@@ -841,29 +881,22 @@ fn collect_type_constraints<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
match op.operator {
|
||||
Operator::Ireduce | Operator::Uextend | Operator::Sextend => {
|
||||
if op.r#type.get().is_none() {
|
||||
return Err(WastError::new(
|
||||
op.span,
|
||||
"`ireduce`, `sextend`, and `uextend` require an ascribed type, \
|
||||
like `(sextend{i64} ...)`"
|
||||
.into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
if (op.operator.is_reduce() || op.operator.is_extend()) && op.r#type.get().is_none()
|
||||
{
|
||||
return Err(WastError::new(
|
||||
op.span,
|
||||
"`ireduce`, `sextend`, and `uextend` require an ascribed type, \
|
||||
like `(sextend{i64} ...)`"
|
||||
.into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
match op.operator {
|
||||
Operator::Uextend | Operator::Sextend => {
|
||||
context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]);
|
||||
}
|
||||
Operator::Ireduce => {
|
||||
context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]);
|
||||
}
|
||||
_ => {}
|
||||
if op.operator.is_extend() {
|
||||
context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]);
|
||||
}
|
||||
if op.operator.is_reduce() {
|
||||
context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]);
|
||||
}
|
||||
|
||||
if let Some(ty) = op.r#type.get() {
|
||||
@@ -916,8 +949,22 @@ fn collect_type_constraints<'a>(
|
||||
let ty = expected_types.last().unwrap();
|
||||
context.assert_is_cc(cc.span, ty);
|
||||
}
|
||||
(TE::Enter, DynAstRef::Rhs(Rhs::Constant(Constant { span, id })))
|
||||
| (TE::Enter, DynAstRef::Rhs(Rhs::Variable(Variable { span, id }))) => {
|
||||
(
|
||||
TE::Enter,
|
||||
DynAstRef::Rhs(Rhs::Constant(Constant {
|
||||
span,
|
||||
id,
|
||||
marker: _,
|
||||
})),
|
||||
)
|
||||
| (
|
||||
TE::Enter,
|
||||
DynAstRef::Rhs(Rhs::Variable(Variable {
|
||||
span,
|
||||
id,
|
||||
marker: _,
|
||||
})),
|
||||
) => {
|
||||
let id_ty = context.get_type_var_for_id(*id)?;
|
||||
context.assert_type_eq(*span, expected_types.last().unwrap(), &id_ty, None);
|
||||
}
|
||||
@@ -926,11 +973,11 @@ fn collect_type_constraints<'a>(
|
||||
let mut operand_types = vec![];
|
||||
{
|
||||
let mut scope = context.enter_operation_scope();
|
||||
result_ty = op.operator.result_type(&mut *scope, op.span);
|
||||
result_ty = op.operator.result_type(op.span, &mut *scope);
|
||||
op.operator
|
||||
.immediate_types(&mut *scope, op.span, &mut operand_types);
|
||||
.immediate_types(op.span, &mut *scope, &mut operand_types);
|
||||
op.operator
|
||||
.param_types(&mut *scope, op.span, &mut operand_types);
|
||||
.parameter_types(op.span, &mut *scope, &mut operand_types);
|
||||
}
|
||||
|
||||
if op.operands.len() != operand_types.len() {
|
||||
@@ -965,29 +1012,22 @@ fn collect_type_constraints<'a>(
|
||||
}
|
||||
}
|
||||
|
||||
match op.operator {
|
||||
Operator::Ireduce | Operator::Uextend | Operator::Sextend => {
|
||||
if op.r#type.get().is_none() {
|
||||
return Err(WastError::new(
|
||||
op.span,
|
||||
"`ireduce`, `sextend`, and `uextend` require an ascribed type, \
|
||||
like `(sextend{i64} ...)`"
|
||||
.into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
if (op.operator.is_reduce() || op.operator.is_extend()) && op.r#type.get().is_none()
|
||||
{
|
||||
return Err(WastError::new(
|
||||
op.span,
|
||||
"`ireduce`, `sextend`, and `uextend` require an ascribed type, \
|
||||
like `(sextend{i64} ...)`"
|
||||
.into(),
|
||||
)
|
||||
.into());
|
||||
}
|
||||
|
||||
match op.operator {
|
||||
Operator::Uextend | Operator::Sextend => {
|
||||
context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]);
|
||||
}
|
||||
Operator::Ireduce => {
|
||||
context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]);
|
||||
}
|
||||
_ => {}
|
||||
if op.operator.is_extend() {
|
||||
context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]);
|
||||
}
|
||||
if op.operator.is_reduce() {
|
||||
context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]);
|
||||
}
|
||||
|
||||
if let Some(ty) = op.r#type.get() {
|
||||
@@ -1017,11 +1057,11 @@ fn collect_type_constraints<'a>(
|
||||
let mut operand_types = vec![];
|
||||
{
|
||||
let mut scope = context.enter_operation_scope();
|
||||
result_ty = unq.operator.result_type(&mut *scope, unq.span);
|
||||
result_ty = unq.operator.result_type(unq.span, &mut *scope);
|
||||
unq.operator
|
||||
.immediate_types(&mut *scope, unq.span, &mut operand_types);
|
||||
.immediate_types(unq.span, &mut *scope, &mut operand_types);
|
||||
unq.operator
|
||||
.param_types(&mut *scope, unq.span, &mut operand_types);
|
||||
.parameter_types(unq.span, &mut *scope, &mut operand_types);
|
||||
}
|
||||
|
||||
if unq.operands.len() != operand_types.len() {
|
||||
@@ -1068,9 +1108,9 @@ fn collect_type_constraints<'a>(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn type_constrain_precondition<'a>(
|
||||
context: &mut TypingContext<'a>,
|
||||
pre: &Precondition<'a>,
|
||||
fn type_constrain_precondition<'a, TOperator>(
|
||||
context: &mut TypingContext<'a, TOperator>,
|
||||
pre: &Precondition<'a, TOperator>,
|
||||
) -> VerifyResult<()> {
|
||||
match pre.constraint {
|
||||
Constraint::BitWidth => {
|
||||
@@ -1182,13 +1222,14 @@ fn type_constrain_precondition<'a>(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use peepmatic_test_operator::TestOperator;
|
||||
|
||||
macro_rules! verify_ok {
|
||||
($name:ident, $src:expr) => {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let buf = wast::parser::ParseBuffer::new($src).expect("should lex OK");
|
||||
let opts = match wast::parser::parse::<Optimizations>(&buf) {
|
||||
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(Path::new(stringify!($name)));
|
||||
@@ -1215,7 +1256,7 @@ mod tests {
|
||||
#[test]
|
||||
fn $name() {
|
||||
let buf = wast::parser::ParseBuffer::new($src).expect("should lex OK");
|
||||
let opts = match wast::parser::parse::<Optimizations>(&buf) {
|
||||
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
|
||||
Ok(opts) => opts,
|
||||
Err(mut e) => {
|
||||
e.set_path(Path::new(stringify!($name)));
|
||||
|
||||
Reference in New Issue
Block a user