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:
Nick Fitzgerald
2020-06-30 11:50:10 -07:00
parent ae95ad8733
commit ee5982fd16
46 changed files with 1945 additions and 1387 deletions

View File

@@ -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>,
}

View File

@@ -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();

View File

@@ -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,
)?,
}

View File

@@ -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);

View File

@@ -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)),
(

View File

@@ -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,

View File

@@ -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",

View File

@@ -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),
};

View File

@@ -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)));