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.
155 lines
5.3 KiB
Rust
155 lines
5.3 KiB
Rust
//! Formatting a peephole optimizer's automata for GraphViz Dot.
|
|
//!
|
|
//! See also `crates/automata/src/dot.rs`.
|
|
|
|
use peepmatic_automata::dot::DotFmt;
|
|
use peepmatic_runtime::{
|
|
cc::ConditionCode,
|
|
integer_interner::{IntegerId, IntegerInterner},
|
|
linear,
|
|
paths::{PathId, PathInterner},
|
|
};
|
|
use std::convert::{TryFrom, TryInto};
|
|
use std::fmt::Debug;
|
|
use std::io::{self, Write};
|
|
use std::num::{NonZeroU16, NonZeroU32};
|
|
|
|
#[derive(Debug)]
|
|
pub(crate) struct PeepholeDotFmt<'a>(pub(crate) &'a PathInterner, pub(crate) &'a IntegerInterner);
|
|
|
|
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,
|
|
from: Option<&linear::MatchOp>,
|
|
input: &linear::MatchResult,
|
|
_to: Option<&linear::MatchOp>,
|
|
) -> io::Result<()> {
|
|
let from = from.expect("we should have match op for every state");
|
|
if let Some(x) = input.ok() {
|
|
match from {
|
|
linear::MatchOp::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.get())
|
|
.expect("we shouldn't generate non-CC edges");
|
|
write!(w, "{}", cc)
|
|
}
|
|
linear::MatchOp::IntegerValue { .. } => {
|
|
let x = self.1.lookup(IntegerId(
|
|
NonZeroU16::new(x.get().try_into().unwrap()).unwrap(),
|
|
));
|
|
write!(w, "{}", x)
|
|
}
|
|
_ => write!(w, "Ok({})", x),
|
|
}
|
|
} else {
|
|
write!(w, "(else)")
|
|
}
|
|
}
|
|
|
|
fn fmt_state(&self, w: &mut impl Write, op: &linear::MatchOp) -> io::Result<()> {
|
|
use linear::MatchOp::*;
|
|
|
|
write!(w, r#"<font face="monospace">"#)?;
|
|
|
|
let p = p(self.0);
|
|
match op {
|
|
Opcode { path } => write!(w, "opcode @ {}", p(path))?,
|
|
IsConst { path } => write!(w, "is-const? @ {}", p(path))?,
|
|
IsPowerOfTwo { path } => write!(w, "is-power-of-two? @ {}", p(path))?,
|
|
BitWidth { path } => write!(w, "bit-width @ {}", p(path))?,
|
|
FitsInNativeWord { path } => write!(w, "fits-in-native-word @ {}", p(path))?,
|
|
Eq { path_a, path_b } => write!(w, "{} == {}", p(path_a), p(path_b))?,
|
|
IntegerValue { path } => write!(w, "integer-value @ {}", p(path))?,
|
|
BooleanValue { path } => write!(w, "boolean-value @ {}", p(path))?,
|
|
ConditionCode { path } => write!(w, "condition-code @ {}", p(path))?,
|
|
Nop => write!(w, "nop")?,
|
|
}
|
|
|
|
writeln!(w, "</font>")
|
|
}
|
|
|
|
fn fmt_output(
|
|
&self,
|
|
w: &mut impl Write,
|
|
actions: &Box<[linear::Action<TOperator>]>,
|
|
) -> io::Result<()> {
|
|
use linear::Action::*;
|
|
|
|
if actions.is_empty() {
|
|
return writeln!(w, "(no output)");
|
|
}
|
|
|
|
write!(w, r#"<font face="monospace">"#)?;
|
|
|
|
let p = p(self.0);
|
|
|
|
for a in actions.iter() {
|
|
match a {
|
|
GetLhs { path } => write!(w, "get-lhs @ {}<br/>", p(path))?,
|
|
UnaryUnquote { operator, operand } => {
|
|
write!(w, "eval {:?} $rhs{}<br/>", operator, operand.0)?
|
|
}
|
|
BinaryUnquote { operator, operands } => write!(
|
|
w,
|
|
"eval {:?} $rhs{}, $rhs{}<br/>",
|
|
operator, operands[0].0, operands[1].0,
|
|
)?,
|
|
MakeIntegerConst {
|
|
value,
|
|
bit_width: _,
|
|
} => write!(w, "make {}<br/>", self.1.lookup(*value))?,
|
|
MakeBooleanConst {
|
|
value,
|
|
bit_width: _,
|
|
} => write!(w, "make {}<br/>", value)?,
|
|
MakeConditionCode { cc } => write!(w, "{}<br/>", cc)?,
|
|
MakeUnaryInst {
|
|
operand,
|
|
operator,
|
|
r#type: _,
|
|
} => write!(w, "make {:?} $rhs{}<br/>", operator, operand.0,)?,
|
|
MakeBinaryInst {
|
|
operator,
|
|
operands,
|
|
r#type: _,
|
|
} => write!(
|
|
w,
|
|
"make {:?} $rhs{}, $rhs{}<br/>",
|
|
operator, operands[0].0, operands[1].0,
|
|
)?,
|
|
MakeTernaryInst {
|
|
operator,
|
|
operands,
|
|
r#type: _,
|
|
} => write!(
|
|
w,
|
|
"make {:?} $rhs{}, $rhs{}, $rhs{}<br/>",
|
|
operator, operands[0].0, operands[1].0, operands[2].0,
|
|
)?,
|
|
}
|
|
}
|
|
|
|
writeln!(w, "</font>")
|
|
}
|
|
}
|
|
|
|
fn p<'a>(paths: &'a PathInterner) -> impl Fn(&PathId) -> String + 'a {
|
|
move |path: &PathId| {
|
|
let mut s = vec![];
|
|
for b in paths.lookup(*path).0 {
|
|
s.push(b.to_string());
|
|
}
|
|
s.join(".")
|
|
}
|
|
}
|