peepmatic: Introduce the peepmatic-runtime crate
The `peepmatic-runtime` crate contains everything required to use a `peepmatic`-generated peephole optimizer. In short: build times and code size. If you are just using a peephole optimizer, you shouldn't need the functions to construct it from scratch from the DSL (and the implied code size and compilation time), let alone even build it at all. You should just deserialize an already-built peephole optimizer, and then use it. That's all that is contained here in this crate.
This commit is contained in:
24
cranelift/peepmatic/crates/runtime/Cargo.toml
Normal file
24
cranelift/peepmatic/crates/runtime/Cargo.toml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
[package]
|
||||||
|
name = "peepmatic-runtime"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bincode = "1.2.1"
|
||||||
|
bumpalo = "3.2.0"
|
||||||
|
log = "0.4.8"
|
||||||
|
peepmatic-automata = { version = "0.1.0", path = "../automata", features = ["serde"] }
|
||||||
|
peepmatic-macro = { version = "0.1.0", path = "../macro" }
|
||||||
|
serde = { version = "1.0.105", features = ["derive"] }
|
||||||
|
thiserror = "1.0.15"
|
||||||
|
wast = { version = "13.0.0", optional = true }
|
||||||
|
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Enable support for a few extra methods that are required by the `peepmatic`
|
||||||
|
# crate when constructing peephole optimizers, but are not needed when simply
|
||||||
|
# using already-constructed peephole optimizers.
|
||||||
|
construct = ["wast"]
|
||||||
90
cranelift/peepmatic/crates/runtime/src/cc.rs
Normal file
90
cranelift/peepmatic/crates/runtime/src/cc.rs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
//! Condition codes.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// A condition code.
|
||||||
|
///
|
||||||
|
/// This is a special kind of immediate for `icmp` instructions that dictate
|
||||||
|
/// which parts of the comparison result we care about.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum ConditionCode {
|
||||||
|
/// Equal.
|
||||||
|
Eq = 1,
|
||||||
|
|
||||||
|
/// Not equal.
|
||||||
|
Ne,
|
||||||
|
|
||||||
|
/// Signed less than.
|
||||||
|
Slt,
|
||||||
|
|
||||||
|
/// Unsigned less than.
|
||||||
|
Ult,
|
||||||
|
|
||||||
|
/// Signed greater than or equal.
|
||||||
|
Sge,
|
||||||
|
|
||||||
|
/// Unsigned greater than or equal.
|
||||||
|
Uge,
|
||||||
|
|
||||||
|
/// Signed greater than.
|
||||||
|
Sgt,
|
||||||
|
|
||||||
|
/// Unsigned greater than.
|
||||||
|
Ugt,
|
||||||
|
|
||||||
|
/// Signed less than or equal.
|
||||||
|
Sle,
|
||||||
|
|
||||||
|
/// Unsigned less than or equal.
|
||||||
|
Ule,
|
||||||
|
|
||||||
|
/// Overflow.
|
||||||
|
Of,
|
||||||
|
|
||||||
|
/// No overflow.
|
||||||
|
Nof,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u32> for ConditionCode {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
fn try_from(x: u32) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match x {
|
||||||
|
x if Self::Eq as u32 == x => Self::Eq,
|
||||||
|
x if Self::Ne as u32 == x => Self::Ne,
|
||||||
|
x if Self::Slt as u32 == x => Self::Slt,
|
||||||
|
x if Self::Ult as u32 == x => Self::Ult,
|
||||||
|
x if Self::Sge as u32 == x => Self::Sge,
|
||||||
|
x if Self::Uge as u32 == x => Self::Uge,
|
||||||
|
x if Self::Sgt as u32 == x => Self::Sgt,
|
||||||
|
x if Self::Ugt as u32 == x => Self::Ugt,
|
||||||
|
x if Self::Sle as u32 == x => Self::Sle,
|
||||||
|
x if Self::Ule as u32 == x => Self::Ule,
|
||||||
|
x if Self::Of as u32 == x => Self::Of,
|
||||||
|
x if Self::Nof as u32 == x => Self::Nof,
|
||||||
|
_ => return Err("not a valid condition code value"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ConditionCode {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Eq => write!(f, "eq"),
|
||||||
|
Self::Ne => write!(f, "ne"),
|
||||||
|
Self::Slt => write!(f, "slt"),
|
||||||
|
Self::Ult => write!(f, "ult"),
|
||||||
|
Self::Sge => write!(f, "sge"),
|
||||||
|
Self::Uge => write!(f, "uge"),
|
||||||
|
Self::Sgt => write!(f, "sgt"),
|
||||||
|
Self::Ugt => write!(f, "ugt"),
|
||||||
|
Self::Sle => write!(f, "sle"),
|
||||||
|
Self::Ule => write!(f, "ule"),
|
||||||
|
Self::Of => write!(f, "of"),
|
||||||
|
Self::Nof => write!(f, "nof"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
44
cranelift/peepmatic/crates/runtime/src/error.rs
Normal file
44
cranelift/peepmatic/crates/runtime/src/error.rs
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//! `Error` and `Result` types for this crate.
|
||||||
|
|
||||||
|
use std::io;
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
/// A result type containing `Ok(T)` or `Err(peepmatic_runtime::Error)`.
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
/// Errors that `peepmatic_runtime` may generate.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[error(transparent)]
|
||||||
|
pub struct Error {
|
||||||
|
#[from]
|
||||||
|
inner: Box<ErrorInner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
enum ErrorInner {
|
||||||
|
#[error(transparent)]
|
||||||
|
Io(#[from] io::Error),
|
||||||
|
|
||||||
|
#[error(transparent)]
|
||||||
|
Bincode(#[from] bincode::Error),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<io::Error> for Error {
|
||||||
|
fn from(e: io::Error) -> Error {
|
||||||
|
let e: ErrorInner = e.into();
|
||||||
|
e.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<bincode::Error> for Error {
|
||||||
|
fn from(e: bincode::Error) -> Error {
|
||||||
|
let e: ErrorInner = e.into();
|
||||||
|
e.into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ErrorInner> for Error {
|
||||||
|
fn from(e: ErrorInner) -> Error {
|
||||||
|
Box::new(e).into()
|
||||||
|
}
|
||||||
|
}
|
||||||
136
cranelift/peepmatic/crates/runtime/src/instruction_set.rs
Normal file
136
cranelift/peepmatic/crates/runtime/src/instruction_set.rs
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
//! Interfacing with actual instructions.
|
||||||
|
|
||||||
|
use crate::operator::Operator;
|
||||||
|
use crate::part::{Constant, Part};
|
||||||
|
use crate::paths::Path;
|
||||||
|
use crate::r#type::Type;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// A trait for interfacing with actual instruction sequences.
|
||||||
|
///
|
||||||
|
/// This trait enables both:
|
||||||
|
///
|
||||||
|
/// * `peepmatic-runtime` to be used by `cranelift-codegen` without a circular
|
||||||
|
/// dependency from `peepmatic-runtime` to `cranelift-codegen` to get access
|
||||||
|
/// to Cranelift's IR types, and
|
||||||
|
///
|
||||||
|
/// * enables us to write local tests that exercise peephole optimizers on a
|
||||||
|
/// simple, testing-only instruction set without pulling in all of Cranelift.
|
||||||
|
///
|
||||||
|
/// Finally, this should also make the task of adding support for Cranelift's
|
||||||
|
/// new `MachInst` and vcode backend easier, since all that needs to be done is
|
||||||
|
/// "just" implementing this trait. (And probably add/modify some
|
||||||
|
/// `peepmatic_runtime::operation::Operation`s as well).
|
||||||
|
pub trait InstructionSet<'a> {
|
||||||
|
/// Mutable context passed into all trait methods. Can be whatever you want!
|
||||||
|
///
|
||||||
|
/// In practice, this is a `FuncCursor` for `cranelift-codegen`'s trait
|
||||||
|
/// implementation.
|
||||||
|
type Context;
|
||||||
|
|
||||||
|
/// An instruction (or identifier for an instruction).
|
||||||
|
type Instruction: Copy + Debug + Eq;
|
||||||
|
|
||||||
|
/// Replace the `old` instruction with `new`.
|
||||||
|
///
|
||||||
|
/// `new` is either a `Part::Instruction` or a constant `Part::Boolean` or
|
||||||
|
/// `Part::Integer`. In the former case, it can directly replace `old`. In
|
||||||
|
/// the latter case, implementations of this trait should transparently
|
||||||
|
/// create an `iconst` or `bconst` instruction to wrap the given constant.
|
||||||
|
///
|
||||||
|
/// `new` will never be `Part::ConditionCode`.
|
||||||
|
fn replace_instruction(
|
||||||
|
&self,
|
||||||
|
context: &mut Self::Context,
|
||||||
|
old: Self::Instruction,
|
||||||
|
new: Part<Self::Instruction>,
|
||||||
|
) -> Self::Instruction;
|
||||||
|
|
||||||
|
/// Get the instruction, constant, or condition code at the given path.
|
||||||
|
///
|
||||||
|
/// If there is no such entity at the given path (e.g. we run into a
|
||||||
|
/// function parameter and can't traverse the path any further) then `None`
|
||||||
|
/// should be returned.
|
||||||
|
fn get_part_at_path(
|
||||||
|
&self,
|
||||||
|
context: &mut Self::Context,
|
||||||
|
root: Self::Instruction,
|
||||||
|
path: Path,
|
||||||
|
) -> Option<Part<Self::Instruction>>;
|
||||||
|
|
||||||
|
/// Get the given instruction's operator.
|
||||||
|
///
|
||||||
|
/// If the instruction's opcode does not have an associated
|
||||||
|
/// `peepmatic_runtime::operator::Operator` variant (i.e. that instruction
|
||||||
|
/// isn't supported by `peepmatic` yet) then `None` should be returned.
|
||||||
|
fn operator(&self, context: &mut Self::Context, instr: Self::Instruction) -> Option<Operator>;
|
||||||
|
|
||||||
|
/// Make a unary instruction.
|
||||||
|
///
|
||||||
|
/// If the type is not given, then it should be inferred.
|
||||||
|
fn make_inst_1(
|
||||||
|
&self,
|
||||||
|
context: &mut Self::Context,
|
||||||
|
root: Self::Instruction,
|
||||||
|
operator: Operator,
|
||||||
|
r#type: Type,
|
||||||
|
a: Part<Self::Instruction>,
|
||||||
|
) -> Self::Instruction;
|
||||||
|
|
||||||
|
/// Make a binary instruction.
|
||||||
|
///
|
||||||
|
/// Operands are given as immediates first and arguments following
|
||||||
|
/// them. Condition codes are treated as immediates. So if we are creating
|
||||||
|
/// an `iadd_imm` instruction, then `a` will be the constant integer
|
||||||
|
/// immediate and `b` will be the instruction whose result is the dynamic
|
||||||
|
/// argument.
|
||||||
|
fn make_inst_2(
|
||||||
|
&self,
|
||||||
|
context: &mut Self::Context,
|
||||||
|
root: Self::Instruction,
|
||||||
|
operator: Operator,
|
||||||
|
r#type: Type,
|
||||||
|
a: Part<Self::Instruction>,
|
||||||
|
b: Part<Self::Instruction>,
|
||||||
|
) -> Self::Instruction;
|
||||||
|
|
||||||
|
/// Make a ternary instruction.
|
||||||
|
///
|
||||||
|
/// Operands are given as immediates first and arguments following
|
||||||
|
/// them. Condition codes are treated as immediates. So if we are creating
|
||||||
|
/// an `icmp` instruction, then `a` will be the condition code, and `b` and
|
||||||
|
/// `c` will be instructions whose results are the dynamic arguments.
|
||||||
|
fn make_inst_3(
|
||||||
|
&self,
|
||||||
|
context: &mut Self::Context,
|
||||||
|
root: Self::Instruction,
|
||||||
|
operator: Operator,
|
||||||
|
r#type: Type,
|
||||||
|
a: Part<Self::Instruction>,
|
||||||
|
b: Part<Self::Instruction>,
|
||||||
|
c: Part<Self::Instruction>,
|
||||||
|
) -> Self::Instruction;
|
||||||
|
|
||||||
|
/// Try to resolve the given instruction into a constant value.
|
||||||
|
///
|
||||||
|
/// If we can tell that the instruction returns a constant value, then
|
||||||
|
/// return that constant value as either a `Part::Boolean` or
|
||||||
|
/// `Part::Integer`. Otherwise, return `None`.
|
||||||
|
fn instruction_to_constant(
|
||||||
|
&self,
|
||||||
|
context: &mut Self::Context,
|
||||||
|
inst: Self::Instruction,
|
||||||
|
) -> Option<Constant>;
|
||||||
|
|
||||||
|
/// Get the bit width of the given instruction's result.
|
||||||
|
///
|
||||||
|
/// Must be one of 1, 8, 16, 32, 64, or 128.
|
||||||
|
fn instruction_result_bit_width(
|
||||||
|
&self,
|
||||||
|
context: &mut Self::Context,
|
||||||
|
inst: Self::Instruction,
|
||||||
|
) -> u8;
|
||||||
|
|
||||||
|
/// Get the size of a native word in bits.
|
||||||
|
fn native_word_size_in_bits(&self, context: &mut Self::Context) -> u8;
|
||||||
|
}
|
||||||
71
cranelift/peepmatic/crates/runtime/src/integer_interner.rs
Normal file
71
cranelift/peepmatic/crates/runtime/src/integer_interner.rs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
//! Interner for (potentially large) integer values.
|
||||||
|
//!
|
||||||
|
//! We support matching on integers that can be represented by `u64`, but only
|
||||||
|
//! support automata results that fit in a `u32`. So we intern the (relatively
|
||||||
|
//! few compared to the full range of `u64`) integers we are matching against
|
||||||
|
//! here and then reference them by `IntegerId`.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
|
/// An identifier for an interned integer.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct IntegerId(#[doc(hidden)] pub u32);
|
||||||
|
|
||||||
|
/// An interner for integer values.
|
||||||
|
#[derive(Debug, Default, Serialize, Deserialize)]
|
||||||
|
pub struct IntegerInterner {
|
||||||
|
// Note: we use `BTreeMap`s for deterministic serialization.
|
||||||
|
map: BTreeMap<u64, IntegerId>,
|
||||||
|
values: Vec<u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl IntegerInterner {
|
||||||
|
/// Construct a new `IntegerInterner`.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intern a value into this `IntegerInterner`, returning its canonical
|
||||||
|
/// `IntegerId`.
|
||||||
|
#[inline]
|
||||||
|
pub fn intern(&mut self, value: impl Into<u64>) -> IntegerId {
|
||||||
|
debug_assert_eq!(self.map.len(), self.values.len());
|
||||||
|
|
||||||
|
let value = value.into();
|
||||||
|
|
||||||
|
if let Some(id) = self.map.get(&value) {
|
||||||
|
return *id;
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = IntegerId(self.values.len().try_into().unwrap());
|
||||||
|
|
||||||
|
self.values.push(value);
|
||||||
|
self.map.insert(value, id);
|
||||||
|
debug_assert_eq!(self.map.len(), self.values.len());
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the id of an already-interned integer, or `None` if it has not been
|
||||||
|
/// interned.
|
||||||
|
pub fn already_interned(&self, value: impl Into<u64>) -> Option<IntegerId> {
|
||||||
|
let value = value.into();
|
||||||
|
self.map.get(&value).copied()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookup a previously interned integer by id.
|
||||||
|
#[inline]
|
||||||
|
pub fn lookup(&self, id: IntegerId) -> u64 {
|
||||||
|
self.values[id.0 as usize]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<IntegerId> for u32 {
|
||||||
|
#[inline]
|
||||||
|
fn from(id: IntegerId) -> u32 {
|
||||||
|
id.0
|
||||||
|
}
|
||||||
|
}
|
||||||
34
cranelift/peepmatic/crates/runtime/src/lib.rs
Executable file
34
cranelift/peepmatic/crates/runtime/src/lib.rs
Executable file
@@ -0,0 +1,34 @@
|
|||||||
|
//! Runtime support for `peepmatic`'s peephole optimizers.
|
||||||
|
//!
|
||||||
|
//! This crate contains everything required to use a `peepmatic`-generated
|
||||||
|
//! peephole optimizer.
|
||||||
|
//!
|
||||||
|
//! ## Why is this a different crate from `peepmatic`?
|
||||||
|
//!
|
||||||
|
//! In short: build times and code size.
|
||||||
|
//!
|
||||||
|
//! If you are just using a peephole optimizer, you shouldn't need the functions
|
||||||
|
//! to construct it from scratch from the DSL (and the implied code size and
|
||||||
|
//! compilation time), let alone even build it at all. You should just
|
||||||
|
//! deserialize an already-built peephole optimizer, and then use it.
|
||||||
|
//!
|
||||||
|
//! That's all that is contained here in this crate.
|
||||||
|
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![deny(missing_debug_implementations)]
|
||||||
|
|
||||||
|
pub mod cc;
|
||||||
|
pub mod error;
|
||||||
|
pub mod instruction_set;
|
||||||
|
pub mod integer_interner;
|
||||||
|
pub mod linear;
|
||||||
|
pub mod operator;
|
||||||
|
pub mod optimizations;
|
||||||
|
pub mod optimizer;
|
||||||
|
pub mod part;
|
||||||
|
pub mod paths;
|
||||||
|
pub mod r#type;
|
||||||
|
|
||||||
|
pub use error::{Error, Result};
|
||||||
|
pub use optimizations::PeepholeOptimizations;
|
||||||
|
pub use optimizer::PeepholeOptimizer;
|
||||||
219
cranelift/peepmatic/crates/runtime/src/linear.rs
Normal file
219
cranelift/peepmatic/crates/runtime/src/linear.rs
Normal file
@@ -0,0 +1,219 @@
|
|||||||
|
//! A linear IR for optimizations.
|
||||||
|
//!
|
||||||
|
//! This IR is designed such that it should be easy to combine multiple linear
|
||||||
|
//! optimizations into a single automata.
|
||||||
|
//!
|
||||||
|
//! See also `src/linearize.rs` for the AST to linear IR translation pass.
|
||||||
|
|
||||||
|
use crate::cc::ConditionCode;
|
||||||
|
use crate::integer_interner::{IntegerId, IntegerInterner};
|
||||||
|
use crate::operator::{Operator, UnquoteOperator};
|
||||||
|
use crate::paths::{PathId, PathInterner};
|
||||||
|
use crate::r#type::{BitWidth, Type};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// A set of linear optimizations.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Optimizations {
|
||||||
|
/// The linear optimizations.
|
||||||
|
pub optimizations: Vec<Optimization>,
|
||||||
|
|
||||||
|
/// The de-duplicated paths referenced by these optimizations.
|
||||||
|
pub paths: PathInterner,
|
||||||
|
|
||||||
|
/// The integer literals referenced by these optimizations.
|
||||||
|
pub integers: IntegerInterner,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A linearized optimization.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Optimization {
|
||||||
|
/// The chain of increments for this optimization.
|
||||||
|
pub increments: Vec<Increment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An increment is a matching operation, the expected result from that
|
||||||
|
/// operation to continue to the next increment, and the actions to take to
|
||||||
|
/// build up the LHS scope and RHS instructions given that we got the expected
|
||||||
|
/// result from this increment's matching operation. Each increment will
|
||||||
|
/// basically become a state and a transition edge out of that state in the
|
||||||
|
/// final automata.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct Increment {
|
||||||
|
/// The matching operation to perform.
|
||||||
|
pub operation: MatchOp,
|
||||||
|
|
||||||
|
/// The expected result of our matching operation, that enables us to
|
||||||
|
/// continue to the next increment. `None` is used for wildcard-style "else"
|
||||||
|
/// transitions.
|
||||||
|
pub expected: Option<u32>,
|
||||||
|
|
||||||
|
/// Actions to perform, given that the operation resulted in the expected
|
||||||
|
/// value.
|
||||||
|
pub actions: Vec<Action>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A matching operation to be performed on some Cranelift instruction as part
|
||||||
|
/// of determining whether an optimization is applicable.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
|
pub enum MatchOp {
|
||||||
|
/// Switch on the opcode of an instruction.
|
||||||
|
Opcode {
|
||||||
|
/// The path to the instruction whose opcode we're switching on.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Does an instruction have a constant value?
|
||||||
|
IsConst {
|
||||||
|
/// The path to the instruction (or immediate) that we're checking
|
||||||
|
/// whether it is constant or not.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Is the constant value a power of two?
|
||||||
|
IsPowerOfTwo {
|
||||||
|
/// The path to the instruction (or immediate) that we are checking
|
||||||
|
/// whether it is a constant power of two or not.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Switch on the bit width of a value.
|
||||||
|
BitWidth {
|
||||||
|
/// The path to the instruction (or immediate) whose result's bit width
|
||||||
|
/// we are checking.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Does the value fit in our target architecture's native word size?
|
||||||
|
FitsInNativeWord {
|
||||||
|
/// The path to the instruction (or immediate) whose result we are
|
||||||
|
/// checking whether it fits in a native word or not.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Are the instructions (or immediates) at the given paths the same?
|
||||||
|
Eq {
|
||||||
|
/// The path to the first instruction (or immediate).
|
||||||
|
path_a: PathId,
|
||||||
|
/// The path to the second instruction (or immediate).
|
||||||
|
path_b: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Switch on the constant integer value of an instruction.
|
||||||
|
IntegerValue {
|
||||||
|
/// The path to the instruction.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Switch on the constant boolean value of an instruction.
|
||||||
|
BooleanValue {
|
||||||
|
/// The path to the instruction.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Switch on a condition code.
|
||||||
|
ConditionCode {
|
||||||
|
/// The path to the condition code.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// No operation. Always evaluates to `None`.
|
||||||
|
///
|
||||||
|
/// Exceedingly rare in real optimizations; nonetheless required to support
|
||||||
|
/// corner cases of the DSL, such as a LHS pattern that is nothing but a
|
||||||
|
/// variable pattern.
|
||||||
|
Nop,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A canonicalized identifier for a left-hand side value that was bound in a
|
||||||
|
/// pattern.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
|
pub struct LhsId(pub u32);
|
||||||
|
|
||||||
|
/// A canonicalized identifier for a right-hand side value.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct RhsId(pub u32);
|
||||||
|
|
||||||
|
/// An action to perform when transitioning between states in the automata.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum Action {
|
||||||
|
/// Implicitly define the n^th built up RHS instruction as something from
|
||||||
|
/// the left-hand side.
|
||||||
|
GetLhs {
|
||||||
|
/// The path to the instruction or value.
|
||||||
|
path: PathId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Implicitly define the n^th RHS instruction as the result of the
|
||||||
|
/// compile-time evaluation off this unquote operation.
|
||||||
|
UnaryUnquote {
|
||||||
|
/// The unquote operator.
|
||||||
|
operator: UnquoteOperator,
|
||||||
|
/// The constant operand to this unquote.
|
||||||
|
operand: RhsId,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Implicitly define the n^th RHS instruction as the result of the
|
||||||
|
/// compile-time evaluation off this unquote operation.
|
||||||
|
BinaryUnquote {
|
||||||
|
/// The unquote operator.
|
||||||
|
operator: UnquoteOperator,
|
||||||
|
/// The constant operands to this unquote.
|
||||||
|
operands: [RhsId; 2],
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Implicitly define the n^th RHS as an integer constant.
|
||||||
|
MakeIntegerConst {
|
||||||
|
/// The constant integer value.
|
||||||
|
value: IntegerId,
|
||||||
|
/// The bit width of this constant.
|
||||||
|
bit_width: BitWidth,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Implicitly define the n^th RHS as a boolean constant.
|
||||||
|
MakeBooleanConst {
|
||||||
|
/// The constant boolean value.
|
||||||
|
value: bool,
|
||||||
|
/// The bit width of this constant.
|
||||||
|
bit_width: BitWidth,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Implicitly defint the n^th RHS as a condition code.
|
||||||
|
MakeConditionCode {
|
||||||
|
/// The condition code.
|
||||||
|
cc: ConditionCode,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Implicitly define the n^th RHS instruction by making a unary
|
||||||
|
/// instruction.
|
||||||
|
MakeUnaryInst {
|
||||||
|
/// The operand for this instruction.
|
||||||
|
operand: RhsId,
|
||||||
|
/// The type of this instruction's result.
|
||||||
|
r#type: Type,
|
||||||
|
/// The operator for this instruction.
|
||||||
|
operator: Operator,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Implicitly define the n^th RHS instruction by making a binary
|
||||||
|
/// instruction.
|
||||||
|
MakeBinaryInst {
|
||||||
|
/// The opcode for this instruction.
|
||||||
|
operator: Operator,
|
||||||
|
/// The type of this instruction's result.
|
||||||
|
r#type: Type,
|
||||||
|
/// The operands for this instruction.
|
||||||
|
operands: [RhsId; 2],
|
||||||
|
},
|
||||||
|
|
||||||
|
/// Implicitly define the n^th RHS instruction by making a ternary
|
||||||
|
/// instruction.
|
||||||
|
MakeTernaryInst {
|
||||||
|
/// The opcode for this instruction.
|
||||||
|
operator: Operator,
|
||||||
|
/// The type of this instruction's result.
|
||||||
|
r#type: Type,
|
||||||
|
/// The operands for this instruction.
|
||||||
|
operands: [RhsId; 3],
|
||||||
|
},
|
||||||
|
}
|
||||||
295
cranelift/peepmatic/crates/runtime/src/operator.rs
Normal file
295
cranelift/peepmatic/crates/runtime/src/operator.rs
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
//! Operator definitions.
|
||||||
|
|
||||||
|
use peepmatic_macro::PeepmaticOperator;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
/// An operator.
|
||||||
|
///
|
||||||
|
/// These are a subset of Cranelift IR's operators.
|
||||||
|
///
|
||||||
|
/// ## Caveats for Branching and Trapping Operators
|
||||||
|
///
|
||||||
|
/// Branching operators are not fully modeled: we do not represent their label
|
||||||
|
/// and jump arguments. It is up to the interpreter doing the instruction
|
||||||
|
/// replacement to recognize when we are replacing one branch with another, and
|
||||||
|
/// copy over the extra information.
|
||||||
|
///
|
||||||
|
/// Affected operations: `brz`, `brnz`, `trapz`, `trapnz`.
|
||||||
|
#[derive(PeepmaticOperator, Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum Operator {
|
||||||
|
/// `adjust_sp_down`
|
||||||
|
#[peepmatic(params(iNN), result(void))]
|
||||||
|
AdjustSpDown = 1,
|
||||||
|
|
||||||
|
/// `adjust_sp_down_imm`
|
||||||
|
#[peepmatic(immediates(iNN), result(void))]
|
||||||
|
AdjustSpDownImm,
|
||||||
|
|
||||||
|
/// `band`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Band,
|
||||||
|
|
||||||
|
/// `band_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
BandImm,
|
||||||
|
|
||||||
|
/// `bconst`
|
||||||
|
#[peepmatic(immediates(b1), result(bNN))]
|
||||||
|
Bconst,
|
||||||
|
|
||||||
|
/// `bint`
|
||||||
|
#[peepmatic(params(bNN), result(iNN))]
|
||||||
|
Bint,
|
||||||
|
|
||||||
|
/// `bor`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Bor,
|
||||||
|
|
||||||
|
/// `bor_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
BorImm,
|
||||||
|
|
||||||
|
/// `brnz`
|
||||||
|
#[peepmatic(params(bool_or_int), result(void))]
|
||||||
|
Brnz,
|
||||||
|
|
||||||
|
/// `brz`
|
||||||
|
#[peepmatic(params(bool_or_int), result(void))]
|
||||||
|
Brz,
|
||||||
|
|
||||||
|
/// `bxor`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Bxor,
|
||||||
|
|
||||||
|
/// `bxor_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
BxorImm,
|
||||||
|
|
||||||
|
/// `iadd`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Iadd,
|
||||||
|
|
||||||
|
/// `iadd_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
IaddImm,
|
||||||
|
|
||||||
|
/// `icmp`
|
||||||
|
#[peepmatic(immediates(cc), params(iNN, iNN), result(b1))]
|
||||||
|
Icmp,
|
||||||
|
|
||||||
|
/// `icmp_imm`
|
||||||
|
#[peepmatic(immediates(cc, iNN), params(iNN), result(b1))]
|
||||||
|
IcmpImm,
|
||||||
|
|
||||||
|
/// `iconst`
|
||||||
|
#[peepmatic(immediates(iNN), result(iNN))]
|
||||||
|
Iconst,
|
||||||
|
|
||||||
|
/// `ifcmp`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(cpu_flags))]
|
||||||
|
Ifcmp,
|
||||||
|
|
||||||
|
/// `ifcmp_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(cpu_flags))]
|
||||||
|
IfcmpImm,
|
||||||
|
|
||||||
|
/// `imul`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Imul,
|
||||||
|
|
||||||
|
/// `imul_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
ImulImm,
|
||||||
|
|
||||||
|
/// `ireduce`
|
||||||
|
#[peepmatic(params(iNN), result(iMM))]
|
||||||
|
Ireduce,
|
||||||
|
|
||||||
|
/// `irsub_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
IrsubImm,
|
||||||
|
|
||||||
|
/// `ishl`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Ishl,
|
||||||
|
|
||||||
|
/// `ishl_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
IshlImm,
|
||||||
|
|
||||||
|
/// `isub`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Isub,
|
||||||
|
|
||||||
|
/// `rotl`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Rotl,
|
||||||
|
|
||||||
|
/// `rotl_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
RotlImm,
|
||||||
|
|
||||||
|
/// `rotr`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Rotr,
|
||||||
|
|
||||||
|
/// `rotr_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
RotrImm,
|
||||||
|
|
||||||
|
/// `sdiv`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Sdiv,
|
||||||
|
|
||||||
|
/// `sdiv_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
SdivImm,
|
||||||
|
|
||||||
|
/// `select`
|
||||||
|
#[peepmatic(params(bool_or_int, any_t, any_t), result(any_t))]
|
||||||
|
Select,
|
||||||
|
|
||||||
|
/// `sextend`
|
||||||
|
#[peepmatic(params(iNN), result(iMM))]
|
||||||
|
Sextend,
|
||||||
|
|
||||||
|
/// `srem`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Srem,
|
||||||
|
|
||||||
|
/// `srem_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
SremImm,
|
||||||
|
|
||||||
|
/// `sshr`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Sshr,
|
||||||
|
|
||||||
|
/// `sshr_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
SshrImm,
|
||||||
|
|
||||||
|
/// `trapnz`
|
||||||
|
#[peepmatic(params(bool_or_int), result(void))]
|
||||||
|
Trapnz,
|
||||||
|
|
||||||
|
/// `trapz`
|
||||||
|
#[peepmatic(params(bool_or_int), result(void))]
|
||||||
|
Trapz,
|
||||||
|
|
||||||
|
/// `udiv`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Udiv,
|
||||||
|
|
||||||
|
/// `udiv_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
UdivImm,
|
||||||
|
|
||||||
|
/// `uextend`
|
||||||
|
#[peepmatic(params(iNN), result(iMM))]
|
||||||
|
Uextend,
|
||||||
|
|
||||||
|
/// `urem`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Urem,
|
||||||
|
|
||||||
|
/// `urem_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
UremImm,
|
||||||
|
|
||||||
|
/// `ushr`
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Ushr,
|
||||||
|
|
||||||
|
/// `ushr_imm`
|
||||||
|
#[peepmatic(immediates(iNN), params(iNN), result(iNN))]
|
||||||
|
UshrImm,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compile-time unquote operators.
|
||||||
|
///
|
||||||
|
/// These are used in the right-hand side to perform compile-time evaluation of
|
||||||
|
/// constants matched on the left-hand side.
|
||||||
|
#[derive(PeepmaticOperator, Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum UnquoteOperator {
|
||||||
|
/// Compile-time `band` of two constant values.
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Band,
|
||||||
|
|
||||||
|
/// Compile-time `bor` of two constant values.
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Bor,
|
||||||
|
|
||||||
|
/// Compile-time `bxor` of two constant values.
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Bxor,
|
||||||
|
|
||||||
|
/// Compile-time `iadd` of two constant values.
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Iadd,
|
||||||
|
|
||||||
|
/// Compile-time `imul` of two constant values.
|
||||||
|
#[peepmatic(params(iNN, iNN), result(iNN))]
|
||||||
|
Imul,
|
||||||
|
|
||||||
|
/// Take the base-2 log of a power of two integer.
|
||||||
|
#[peepmatic(params(iNN), result(iNN))]
|
||||||
|
Log2,
|
||||||
|
|
||||||
|
/// Wrapping negation of an integer.
|
||||||
|
#[peepmatic(params(iNN), result(iNN))]
|
||||||
|
Neg,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A trait to represent a typing context.
|
||||||
|
///
|
||||||
|
/// This is used by the macro-generated operator methods that create the type
|
||||||
|
/// variables for their immediates, parameters, and results. This trait is
|
||||||
|
/// implemented by the concrete typing context in `peepmatic/src/verify.rs`.
|
||||||
|
#[cfg(feature = "construct")]
|
||||||
|
pub trait TypingContext<'a> {
|
||||||
|
/// A type variable.
|
||||||
|
type TypeVariable;
|
||||||
|
|
||||||
|
/// Create a condition code type.
|
||||||
|
fn cc(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
|
||||||
|
/// Create a boolean type with a polymorphic bit width.
|
||||||
|
///
|
||||||
|
/// Each use of `bNN` by the same operator refers to the same type variable.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn bNN(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
|
||||||
|
/// Create an integer type with a polymorphic bit width.
|
||||||
|
///
|
||||||
|
/// Each use of `iNN` by the same operator refers to the same type variable.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn iNN(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
|
||||||
|
/// Create an integer type with a polymorphic bit width.
|
||||||
|
///
|
||||||
|
/// Each use of `iMM` by the same operator refers to the same type variable.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn iMM(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
|
||||||
|
/// Create the CPU flags type variable.
|
||||||
|
fn cpu_flags(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
|
||||||
|
/// Create a boolean type of size one bit.
|
||||||
|
fn b1(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
|
||||||
|
/// Create the void type, used as the result of operators that branch away,
|
||||||
|
/// or do not return anything.
|
||||||
|
fn void(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
|
||||||
|
/// Create a type variable that may be either a boolean or an integer.
|
||||||
|
fn bool_or_int(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
|
||||||
|
/// Create a type variable that can be any type T.
|
||||||
|
///
|
||||||
|
/// Each use of `any_t` by the same operator refers to the same type
|
||||||
|
/// variable.
|
||||||
|
fn any_t(&mut self, span: wast::Span) -> Self::TypeVariable;
|
||||||
|
}
|
||||||
74
cranelift/peepmatic/crates/runtime/src/optimizations.rs
Normal file
74
cranelift/peepmatic/crates/runtime/src/optimizations.rs
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
//! Compiled peephole optimizations.
|
||||||
|
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::instruction_set::InstructionSet;
|
||||||
|
use crate::integer_interner::IntegerInterner;
|
||||||
|
use crate::linear::{Action, MatchOp};
|
||||||
|
use crate::optimizer::PeepholeOptimizer;
|
||||||
|
use crate::paths::PathInterner;
|
||||||
|
use peepmatic_automata::Automaton;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[cfg(feature = "construct")]
|
||||||
|
use std::fs;
|
||||||
|
#[cfg(feature = "construct")]
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
/// A compiled set of peephole optimizations.
|
||||||
|
///
|
||||||
|
/// This is the compilation result of the `peepmatic` crate, after its taken a
|
||||||
|
/// bunch of optimizations written in the DSL and lowered and combined them.
|
||||||
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
|
pub struct PeepholeOptimizations {
|
||||||
|
/// The instruction paths referenced by the peephole optimizations.
|
||||||
|
pub paths: PathInterner,
|
||||||
|
|
||||||
|
/// Not all integers we're matching on fit in the `u32` that we use as the
|
||||||
|
/// result of match operations. So we intern them and refer to them by id.
|
||||||
|
pub integers: IntegerInterner,
|
||||||
|
|
||||||
|
/// The underlying automata for matching optimizations' left-hand sides, and
|
||||||
|
/// building up the corresponding right-hand side.
|
||||||
|
pub automata: Automaton<Option<u32>, MatchOp, Vec<Action>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PeepholeOptimizations {
|
||||||
|
/// Deserialize a `PeepholeOptimizations` from bytes.
|
||||||
|
pub fn deserialize(serialized: &[u8]) -> Result<Self> {
|
||||||
|
let peep_opt: Self = bincode::deserialize(serialized)?;
|
||||||
|
Ok(peep_opt)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Serialize these peephole optimizations out to the file at the given path.
|
||||||
|
///
|
||||||
|
/// Requires that the `"construct"` cargo feature is enabled.
|
||||||
|
#[cfg(feature = "construct")]
|
||||||
|
pub fn serialize_to_file(&self, path: &Path) -> Result<()> {
|
||||||
|
let file = fs::File::create(path)?;
|
||||||
|
bincode::serialize_into(file, self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a new peephole optimizer instance from this set of peephole
|
||||||
|
/// optimizations.
|
||||||
|
///
|
||||||
|
/// The peephole optimizer instance can be used to apply these peephole
|
||||||
|
/// optimizations. When checking multiple instructions for whether they can
|
||||||
|
/// be optimized, it is more performant to reuse a single peephole optimizer
|
||||||
|
/// instance, rather than create a new one for each instruction. Reusing the
|
||||||
|
/// peephole optimizer instance allows the reuse of a few internal
|
||||||
|
/// allocations.
|
||||||
|
pub fn optimizer<'peep, 'ctx, I>(&'peep self, instr_set: I) -> PeepholeOptimizer<'peep, 'ctx, I>
|
||||||
|
where
|
||||||
|
I: InstructionSet<'ctx>,
|
||||||
|
{
|
||||||
|
PeepholeOptimizer {
|
||||||
|
peep_opt: self,
|
||||||
|
instr_set,
|
||||||
|
left_hand_sides: vec![],
|
||||||
|
right_hand_sides: vec![],
|
||||||
|
actions: vec![],
|
||||||
|
backtracking_states: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
512
cranelift/peepmatic/crates/runtime/src/optimizer.rs
Normal file
512
cranelift/peepmatic/crates/runtime/src/optimizer.rs
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
//! An optimizer for a set of peephole optimizations.
|
||||||
|
|
||||||
|
use crate::instruction_set::InstructionSet;
|
||||||
|
use crate::linear::{Action, MatchOp};
|
||||||
|
use crate::operator::UnquoteOperator;
|
||||||
|
use crate::optimizations::PeepholeOptimizations;
|
||||||
|
use crate::part::{Constant, Part};
|
||||||
|
use crate::r#type::{BitWidth, Type};
|
||||||
|
use peepmatic_automata::State;
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt::{self, Debug};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
/// A peephole optimizer instance that can apply a set of peephole
|
||||||
|
/// optimizations to instructions.
|
||||||
|
///
|
||||||
|
/// These are created from a set of peephole optimizations with the
|
||||||
|
/// [`PeepholeOptimizer::instance`][crate::PeepholeOptimizer::instance] method.
|
||||||
|
///
|
||||||
|
/// Reusing an instance when applying peephole optimizations to different
|
||||||
|
/// instruction sequences means that you reuse internal allocations that are
|
||||||
|
/// used to match left-hand sides and build up right-hand sides.
|
||||||
|
pub struct PeepholeOptimizer<'peep, 'ctx, I>
|
||||||
|
where
|
||||||
|
I: InstructionSet<'ctx>,
|
||||||
|
{
|
||||||
|
pub(crate) peep_opt: &'peep PeepholeOptimizations,
|
||||||
|
pub(crate) instr_set: I,
|
||||||
|
pub(crate) left_hand_sides: Vec<Part<I::Instruction>>,
|
||||||
|
pub(crate) right_hand_sides: Vec<Part<I::Instruction>>,
|
||||||
|
pub(crate) actions: Vec<Action>,
|
||||||
|
pub(crate) backtracking_states: Vec<(State, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'peep, 'ctx, I> Debug for PeepholeOptimizer<'peep, 'ctx, I>
|
||||||
|
where
|
||||||
|
I: InstructionSet<'ctx>,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
let PeepholeOptimizer {
|
||||||
|
peep_opt,
|
||||||
|
instr_set: _,
|
||||||
|
left_hand_sides,
|
||||||
|
right_hand_sides,
|
||||||
|
actions,
|
||||||
|
backtracking_states,
|
||||||
|
} = self;
|
||||||
|
f.debug_struct("PeepholeOptimizer")
|
||||||
|
.field("peep_opt", peep_opt)
|
||||||
|
.field("instr_set", &"_")
|
||||||
|
.field("left_hand_sides", left_hand_sides)
|
||||||
|
.field("right_hand_sides", right_hand_sides)
|
||||||
|
.field("actions", actions)
|
||||||
|
.field("backtracking_states", backtracking_states)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'peep, 'ctx, I> PeepholeOptimizer<'peep, 'ctx, I>
|
||||||
|
where
|
||||||
|
I: InstructionSet<'ctx>,
|
||||||
|
{
|
||||||
|
fn eval_unquote_1(&self, operator: UnquoteOperator, a: Constant) -> Constant {
|
||||||
|
use Constant::*;
|
||||||
|
|
||||||
|
macro_rules! map_int {
|
||||||
|
( $c:expr , | $x:ident | $e:expr ) => {
|
||||||
|
match $c {
|
||||||
|
Int($x, w) => Int($e, w),
|
||||||
|
Bool(..) => panic!("not an integer"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match operator {
|
||||||
|
UnquoteOperator::Log2 => map_int!(a, |x| x.trailing_zeros() as _),
|
||||||
|
UnquoteOperator::Neg => map_int!(a, |x| x.wrapping_neg()),
|
||||||
|
UnquoteOperator::Band
|
||||||
|
| UnquoteOperator::Bor
|
||||||
|
| UnquoteOperator::Bxor
|
||||||
|
| UnquoteOperator::Iadd
|
||||||
|
| UnquoteOperator::Imul => unreachable!("not a unary unquote operator: {:?}", operator),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_unquote_2(&self, operator: UnquoteOperator, a: Constant, b: Constant) -> Constant {
|
||||||
|
use Constant::*;
|
||||||
|
|
||||||
|
macro_rules! fold_ints {
|
||||||
|
( $c1:expr , $c2:expr , | $x:ident , $y:ident | $e:expr ) => {
|
||||||
|
match ($c1, $c2) {
|
||||||
|
(Int($x, w1), Int($y, w2)) if w1 == w2 => Int($e, w1),
|
||||||
|
_ => panic!("not two integers of the same width"),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
match operator {
|
||||||
|
UnquoteOperator::Band => fold_ints!(a, b, |x, y| x & y),
|
||||||
|
UnquoteOperator::Bor => fold_ints!(a, b, |x, y| x | y),
|
||||||
|
UnquoteOperator::Bxor => fold_ints!(a, b, |x, y| x ^ y),
|
||||||
|
UnquoteOperator::Iadd => fold_ints!(a, b, |x, y| x.wrapping_add(y)),
|
||||||
|
UnquoteOperator::Imul => fold_ints!(a, b, |x, y| x.wrapping_mul(y)),
|
||||||
|
UnquoteOperator::Log2 | UnquoteOperator::Neg => {
|
||||||
|
unreachable!("not a binary unquote operator: {:?}", operator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_actions(&mut self, context: &mut I::Context, root: I::Instruction) {
|
||||||
|
let mut actions = mem::replace(&mut self.actions, vec![]);
|
||||||
|
|
||||||
|
for action in actions.drain(..) {
|
||||||
|
log::trace!("Evaluating action: {:?}", action);
|
||||||
|
match action {
|
||||||
|
Action::GetLhs { path } => {
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let lhs = self
|
||||||
|
.instr_set
|
||||||
|
.get_part_at_path(context, root, path)
|
||||||
|
.expect("should always get part at path OK by the time it is bound");
|
||||||
|
self.right_hand_sides.push(lhs);
|
||||||
|
}
|
||||||
|
Action::UnaryUnquote { operator, operand } => {
|
||||||
|
let operand = self.right_hand_sides[operand.0 as usize];
|
||||||
|
let operand = match operand {
|
||||||
|
Part::Instruction(i) => self
|
||||||
|
.instr_set
|
||||||
|
.instruction_to_constant(context, i)
|
||||||
|
.expect("cannot convert instruction to constant for unquote operand"),
|
||||||
|
Part::Constant(c) => c,
|
||||||
|
Part::ConditionCode(_) => {
|
||||||
|
panic!("cannot use a condition code as an unquote operand")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let result = self.eval_unquote_1(operator, operand);
|
||||||
|
self.right_hand_sides.push(result.into());
|
||||||
|
}
|
||||||
|
Action::BinaryUnquote { operator, operands } => {
|
||||||
|
let a = self.right_hand_sides[operands[0].0 as usize];
|
||||||
|
let a = match a {
|
||||||
|
Part::Instruction(i) => self
|
||||||
|
.instr_set
|
||||||
|
.instruction_to_constant(context, i)
|
||||||
|
.expect("cannot convert instruction to constant for unquote operand"),
|
||||||
|
Part::Constant(c) => c,
|
||||||
|
Part::ConditionCode(_) => {
|
||||||
|
panic!("cannot use a condition code as an unquote operand")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let b = self.right_hand_sides[operands[1].0 as usize];
|
||||||
|
let b = match b {
|
||||||
|
Part::Instruction(i) => self
|
||||||
|
.instr_set
|
||||||
|
.instruction_to_constant(context, i)
|
||||||
|
.expect("cannot convert instruction to constant for unquote operand"),
|
||||||
|
Part::Constant(c) => c,
|
||||||
|
Part::ConditionCode(_) => {
|
||||||
|
panic!("cannot use a condition code as an unquote operand")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let result = self.eval_unquote_2(operator, a, b);
|
||||||
|
self.right_hand_sides.push(result.into());
|
||||||
|
}
|
||||||
|
Action::MakeIntegerConst {
|
||||||
|
value,
|
||||||
|
mut bit_width,
|
||||||
|
} => {
|
||||||
|
let value = self.peep_opt.integers.lookup(value);
|
||||||
|
if bit_width.is_polymorphic() {
|
||||||
|
bit_width = BitWidth::try_from(
|
||||||
|
self.instr_set.instruction_result_bit_width(context, root),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
self.right_hand_sides
|
||||||
|
.push(Constant::Int(value, bit_width).into());
|
||||||
|
}
|
||||||
|
Action::MakeBooleanConst {
|
||||||
|
value,
|
||||||
|
mut bit_width,
|
||||||
|
} => {
|
||||||
|
if bit_width.is_polymorphic() {
|
||||||
|
bit_width = BitWidth::try_from(
|
||||||
|
self.instr_set.instruction_result_bit_width(context, root),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
self.right_hand_sides
|
||||||
|
.push(Constant::Bool(value, bit_width).into());
|
||||||
|
}
|
||||||
|
Action::MakeConditionCode { cc } => {
|
||||||
|
self.right_hand_sides.push(Part::ConditionCode(cc));
|
||||||
|
}
|
||||||
|
Action::MakeUnaryInst {
|
||||||
|
operator,
|
||||||
|
r#type:
|
||||||
|
Type {
|
||||||
|
kind,
|
||||||
|
mut bit_width,
|
||||||
|
},
|
||||||
|
operand,
|
||||||
|
} => {
|
||||||
|
if bit_width.is_polymorphic() {
|
||||||
|
bit_width = BitWidth::try_from(
|
||||||
|
self.instr_set.instruction_result_bit_width(context, root),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let ty = Type { kind, bit_width };
|
||||||
|
let operand = self.right_hand_sides[operand.0 as usize];
|
||||||
|
let inst = self
|
||||||
|
.instr_set
|
||||||
|
.make_inst_1(context, root, operator, ty, operand);
|
||||||
|
self.right_hand_sides.push(Part::Instruction(inst));
|
||||||
|
}
|
||||||
|
Action::MakeBinaryInst {
|
||||||
|
operator,
|
||||||
|
r#type:
|
||||||
|
Type {
|
||||||
|
kind,
|
||||||
|
mut bit_width,
|
||||||
|
},
|
||||||
|
operands,
|
||||||
|
} => {
|
||||||
|
if bit_width.is_polymorphic() {
|
||||||
|
bit_width = BitWidth::try_from(
|
||||||
|
self.instr_set.instruction_result_bit_width(context, root),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let ty = Type { kind, bit_width };
|
||||||
|
let a = self.right_hand_sides[operands[0].0 as usize];
|
||||||
|
let b = self.right_hand_sides[operands[1].0 as usize];
|
||||||
|
let inst = self
|
||||||
|
.instr_set
|
||||||
|
.make_inst_2(context, root, operator, ty, a, b);
|
||||||
|
self.right_hand_sides.push(Part::Instruction(inst));
|
||||||
|
}
|
||||||
|
Action::MakeTernaryInst {
|
||||||
|
operator,
|
||||||
|
r#type:
|
||||||
|
Type {
|
||||||
|
kind,
|
||||||
|
mut bit_width,
|
||||||
|
},
|
||||||
|
operands,
|
||||||
|
} => {
|
||||||
|
if bit_width.is_polymorphic() {
|
||||||
|
bit_width = BitWidth::try_from(
|
||||||
|
self.instr_set.instruction_result_bit_width(context, root),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
let ty = Type { kind, bit_width };
|
||||||
|
let a = self.right_hand_sides[operands[0].0 as usize];
|
||||||
|
let b = self.right_hand_sides[operands[1].0 as usize];
|
||||||
|
let c = self.right_hand_sides[operands[2].0 as usize];
|
||||||
|
let inst = self
|
||||||
|
.instr_set
|
||||||
|
.make_inst_3(context, root, operator, ty, a, b, c);
|
||||||
|
self.right_hand_sides.push(Part::Instruction(inst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reuse the heap elements allocation.
|
||||||
|
self.actions = actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn eval_match_op(
|
||||||
|
&mut self,
|
||||||
|
context: &mut I::Context,
|
||||||
|
root: I::Instruction,
|
||||||
|
match_op: MatchOp,
|
||||||
|
) -> Option<u32> {
|
||||||
|
use crate::linear::MatchOp::*;
|
||||||
|
|
||||||
|
log::trace!("Evaluating match operation: {:?}", match_op);
|
||||||
|
let result = match match_op {
|
||||||
|
Opcode { path } => {
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let part = self.instr_set.get_part_at_path(context, root, path)?;
|
||||||
|
let inst = part.as_instruction()?;
|
||||||
|
self.instr_set.operator(context, inst).map(|op| op as u32)
|
||||||
|
}
|
||||||
|
IsConst { path } => {
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let part = self.instr_set.get_part_at_path(context, root, path)?;
|
||||||
|
let is_const = match part {
|
||||||
|
Part::Instruction(i) => {
|
||||||
|
self.instr_set.instruction_to_constant(context, i).is_some()
|
||||||
|
}
|
||||||
|
Part::ConditionCode(_) | Part::Constant(_) => true,
|
||||||
|
};
|
||||||
|
Some(is_const as u32)
|
||||||
|
}
|
||||||
|
IsPowerOfTwo { path } => {
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let part = self.instr_set.get_part_at_path(context, root, path)?;
|
||||||
|
match part {
|
||||||
|
Part::Constant(c) => Some(c.as_int().unwrap().is_power_of_two() as u32),
|
||||||
|
Part::Instruction(i) => {
|
||||||
|
let c = self.instr_set.instruction_to_constant(context, i)?;
|
||||||
|
Some(c.as_int().unwrap().is_power_of_two() as u32)
|
||||||
|
}
|
||||||
|
Part::ConditionCode(_) => panic!("IsPowerOfTwo on a condition code"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BitWidth { path } => {
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let part = self.instr_set.get_part_at_path(context, root, path)?;
|
||||||
|
let bit_width = match part {
|
||||||
|
Part::Instruction(i) => self.instr_set.instruction_result_bit_width(context, i),
|
||||||
|
Part::Constant(Constant::Int(_, w)) | Part::Constant(Constant::Bool(_, w)) => {
|
||||||
|
w.fixed_width().unwrap_or_else(|| {
|
||||||
|
self.instr_set.instruction_result_bit_width(context, root)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
Part::ConditionCode(_) => panic!("BitWidth on condition code"),
|
||||||
|
};
|
||||||
|
Some(bit_width as u32)
|
||||||
|
}
|
||||||
|
FitsInNativeWord { path } => {
|
||||||
|
let native_word_size = self.instr_set.native_word_size_in_bits(context);
|
||||||
|
debug_assert!(native_word_size.is_power_of_two());
|
||||||
|
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let part = self.instr_set.get_part_at_path(context, root, path)?;
|
||||||
|
let fits = match part {
|
||||||
|
Part::Instruction(i) => {
|
||||||
|
let size = self.instr_set.instruction_result_bit_width(context, i);
|
||||||
|
size <= native_word_size
|
||||||
|
}
|
||||||
|
Part::Constant(c) => {
|
||||||
|
let root_width = self.instr_set.instruction_result_bit_width(context, root);
|
||||||
|
let size = c.bit_width(root_width);
|
||||||
|
size <= native_word_size
|
||||||
|
}
|
||||||
|
Part::ConditionCode(_) => panic!("FitsInNativeWord on condition code"),
|
||||||
|
};
|
||||||
|
Some(fits as u32)
|
||||||
|
}
|
||||||
|
Eq { path_a, path_b } => {
|
||||||
|
let path_a = self.peep_opt.paths.lookup(path_a);
|
||||||
|
let part_a = self.instr_set.get_part_at_path(context, root, path_a)?;
|
||||||
|
let path_b = self.peep_opt.paths.lookup(path_b);
|
||||||
|
let part_b = self.instr_set.get_part_at_path(context, root, path_b)?;
|
||||||
|
let eq = match (part_a, part_b) {
|
||||||
|
(Part::Instruction(inst), Part::Constant(c1))
|
||||||
|
| (Part::Constant(c1), Part::Instruction(inst)) => {
|
||||||
|
match self.instr_set.instruction_to_constant(context, inst) {
|
||||||
|
Some(c2) => c1 == c2,
|
||||||
|
None => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(a, b) => a == b,
|
||||||
|
};
|
||||||
|
Some(eq as _)
|
||||||
|
}
|
||||||
|
IntegerValue { path } => {
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let part = self.instr_set.get_part_at_path(context, root, path)?;
|
||||||
|
match part {
|
||||||
|
Part::Constant(c) => {
|
||||||
|
let x = c.as_int()?;
|
||||||
|
self.peep_opt.integers.already_interned(x).map(|id| id.0)
|
||||||
|
}
|
||||||
|
Part::Instruction(i) => {
|
||||||
|
let c = self.instr_set.instruction_to_constant(context, i)?;
|
||||||
|
let x = c.as_int()?;
|
||||||
|
self.peep_opt.integers.already_interned(x).map(|id| id.0)
|
||||||
|
}
|
||||||
|
Part::ConditionCode(_) => panic!("IntegerValue on condition code"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
BooleanValue { path } => {
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let part = self.instr_set.get_part_at_path(context, root, path)?;
|
||||||
|
match part {
|
||||||
|
Part::Constant(c) => c.as_bool().map(|b| b as u32),
|
||||||
|
Part::Instruction(i) => {
|
||||||
|
let c = self.instr_set.instruction_to_constant(context, i)?;
|
||||||
|
c.as_bool().map(|b| b as u32)
|
||||||
|
}
|
||||||
|
Part::ConditionCode(_) => panic!("IntegerValue on condition code"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ConditionCode { path } => {
|
||||||
|
let path = self.peep_opt.paths.lookup(path);
|
||||||
|
let part = self.instr_set.get_part_at_path(context, root, path)?;
|
||||||
|
part.as_condition_code().map(|cc| cc as u32)
|
||||||
|
}
|
||||||
|
MatchOp::Nop => None,
|
||||||
|
};
|
||||||
|
log::trace!("Evaluated match operation: {:?} = {:?}", match_op, result);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to apply a single peephole optimization to the given root
|
||||||
|
/// instruction.
|
||||||
|
///
|
||||||
|
/// If an optimization is applied, then the `root` is replaced with the
|
||||||
|
/// optimization's right-hand side, and the root of the right-hand side is
|
||||||
|
/// returned as `Some`.
|
||||||
|
///
|
||||||
|
/// If no optimization's left-hand side matches `root`, then `root` is left
|
||||||
|
/// untouched and `None` is returned.
|
||||||
|
pub fn apply_one(
|
||||||
|
&mut self,
|
||||||
|
context: &mut I::Context,
|
||||||
|
root: I::Instruction,
|
||||||
|
) -> Option<I::Instruction> {
|
||||||
|
log::trace!("PeepholeOptimizer::apply_one");
|
||||||
|
|
||||||
|
self.backtracking_states.clear();
|
||||||
|
self.actions.clear();
|
||||||
|
self.left_hand_sides.clear();
|
||||||
|
self.right_hand_sides.clear();
|
||||||
|
|
||||||
|
let mut r#final = None;
|
||||||
|
|
||||||
|
let mut query = self.peep_opt.automata.query();
|
||||||
|
loop {
|
||||||
|
log::trace!("Current state: {:?}", query.current_state());
|
||||||
|
|
||||||
|
if query.is_in_final_state() {
|
||||||
|
// If we're in a final state (which means an optimization is
|
||||||
|
// applicable) then record that fact, but keep going. We don't
|
||||||
|
// want to stop yet, because we might discover another,
|
||||||
|
// more-specific optimization that is also applicable if we keep
|
||||||
|
// going. And we always want to apply the most specific
|
||||||
|
// optimization that matches.
|
||||||
|
log::trace!("Found a match at state {:?}", query.current_state());
|
||||||
|
r#final = Some((query.current_state(), self.actions.len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Anything following a `None` transition doesn't care about the
|
||||||
|
// result of this match operation, so if we partially follow the
|
||||||
|
// current non-`None` path, but don't ultimately find a matching
|
||||||
|
// optimization, we want to be able to backtrack to this state and
|
||||||
|
// then try taking the `None` transition.
|
||||||
|
if query.has_transition_on(&None) {
|
||||||
|
self.backtracking_states
|
||||||
|
.push((query.current_state(), self.actions.len()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let match_op = match query.current_state_data() {
|
||||||
|
None => break,
|
||||||
|
Some(op) => op,
|
||||||
|
};
|
||||||
|
|
||||||
|
let input = self.eval_match_op(context, root, *match_op);
|
||||||
|
|
||||||
|
let actions = if let Some(actions) = query.next(&input) {
|
||||||
|
actions
|
||||||
|
} else if r#final.is_some() {
|
||||||
|
break;
|
||||||
|
} else if let Some((state, actions_len)) = self.backtracking_states.pop() {
|
||||||
|
query.go_to_state(state);
|
||||||
|
self.actions.truncate(actions_len);
|
||||||
|
query
|
||||||
|
.next(&None)
|
||||||
|
.expect("backtracking states always have `None` transitions")
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.actions.extend(actions.iter().copied());
|
||||||
|
}
|
||||||
|
|
||||||
|
// If `final` is none, then we didn't encounter any final states, so
|
||||||
|
// there are no applicable optimizations.
|
||||||
|
let (final_state, actions_len) = match r#final {
|
||||||
|
Some(f) => f,
|
||||||
|
None => {
|
||||||
|
log::trace!("No optimizations matched");
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Go to the last final state we saw, reset the LHS and RHS to how
|
||||||
|
// they were at the time we saw the final state, and process the
|
||||||
|
// final actions.
|
||||||
|
self.actions.truncate(actions_len);
|
||||||
|
query.go_to_state(final_state);
|
||||||
|
let final_actions = query.finish().expect("should be in a final state");
|
||||||
|
self.actions.extend(final_actions.iter().copied());
|
||||||
|
self.eval_actions(context, root);
|
||||||
|
|
||||||
|
// And finally, the root of the RHS for this optimization is the
|
||||||
|
// last entry in `self.right_hand_sides`, so replace the old root
|
||||||
|
// instruction with this one!
|
||||||
|
let result = self.right_hand_sides.pop().unwrap();
|
||||||
|
let new_root = self.instr_set.replace_instruction(context, root, result);
|
||||||
|
Some(new_root)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Keep applying peephole optimizations to the given instruction until none
|
||||||
|
/// can be applied anymore.
|
||||||
|
pub fn apply_all(&mut self, context: &mut I::Context, mut inst: I::Instruction) {
|
||||||
|
loop {
|
||||||
|
if let Some(new_inst) = self.apply_one(context, inst) {
|
||||||
|
inst = new_inst;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
128
cranelift/peepmatic/crates/runtime/src/part.rs
Normal file
128
cranelift/peepmatic/crates/runtime/src/part.rs
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
//! Parts of instructions.
|
||||||
|
|
||||||
|
use crate::cc::ConditionCode;
|
||||||
|
use crate::r#type::BitWidth;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
/// A constant value.
|
||||||
|
///
|
||||||
|
/// Whether an integer is interpreted as signed or unsigned depends on the
|
||||||
|
/// operations applied to it.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Constant {
|
||||||
|
/// A boolean of the given width.
|
||||||
|
Bool(bool, BitWidth),
|
||||||
|
|
||||||
|
/// An integer constant of the given width,
|
||||||
|
Int(u64, BitWidth),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A part of an instruction, or a whole instruction itself.
|
||||||
|
///
|
||||||
|
/// These are the different values that can be matched in an optimization's
|
||||||
|
/// left-hand side and then built up in its right-hand side.
|
||||||
|
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub enum Part<I>
|
||||||
|
where
|
||||||
|
I: Copy + Debug + Eq,
|
||||||
|
{
|
||||||
|
/// An instruction (or result of an instruction).
|
||||||
|
Instruction(I),
|
||||||
|
|
||||||
|
/// A constant value.
|
||||||
|
Constant(Constant),
|
||||||
|
|
||||||
|
/// A condition code.
|
||||||
|
ConditionCode(ConditionCode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> From<Constant> for Part<I>
|
||||||
|
where
|
||||||
|
I: Copy + Debug + Eq,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn from(c: Constant) -> Part<I> {
|
||||||
|
Part::Constant(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> From<ConditionCode> for Part<I>
|
||||||
|
where
|
||||||
|
I: Copy + Debug + Eq,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn from(c: ConditionCode) -> Part<I> {
|
||||||
|
Part::ConditionCode(c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! accessors {
|
||||||
|
( $( $variant:ident , $result:ty , $getter:ident , $is:ident , $unwrap:ident ; )* ) => {
|
||||||
|
$(
|
||||||
|
#[inline]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub fn $getter(&self) -> Option<$result> {
|
||||||
|
match *self {
|
||||||
|
Self::$variant(x, ..) => Some(x),
|
||||||
|
_ => None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub fn $is(&self) -> bool {
|
||||||
|
self.$getter().is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
pub fn $unwrap(&self) -> $result {
|
||||||
|
self.$getter().expect(concat!("failed to unwrap `", stringify!($variant), "`"))
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Constant {
|
||||||
|
/// If this is any kind of integer constant, get it as a 64-bit unsigned
|
||||||
|
/// integer.
|
||||||
|
pub fn as_int(&self) -> Option<u64> {
|
||||||
|
match *self {
|
||||||
|
Constant::Bool(..) => None,
|
||||||
|
Constant::Int(x, _) => Some(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If this is any kind of boolean constant, get its value.
|
||||||
|
pub fn as_bool(&self) -> Option<bool> {
|
||||||
|
match *self {
|
||||||
|
Constant::Bool(b, _) => Some(b),
|
||||||
|
Constant::Int(..) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The number of bits required to represent this constant value's type.
|
||||||
|
pub fn bit_width(&self, root_width: u8) -> u8 {
|
||||||
|
match *self {
|
||||||
|
Constant::Bool(_, w) | Constant::Int(_, w) => {
|
||||||
|
if let Some(w) = w.fixed_width() {
|
||||||
|
w
|
||||||
|
} else {
|
||||||
|
debug_assert!(w.is_polymorphic());
|
||||||
|
root_width
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I> Part<I>
|
||||||
|
where
|
||||||
|
I: Copy + Debug + Eq,
|
||||||
|
{
|
||||||
|
accessors! {
|
||||||
|
Instruction, I, as_instruction, is_instruction, unwrap_instruction;
|
||||||
|
Constant, Constant, as_constant, is_constant, unwrap_constant;
|
||||||
|
ConditionCode, ConditionCode, as_condition_code, is_condition_code, unwrap_condition_code;
|
||||||
|
}
|
||||||
|
}
|
||||||
242
cranelift/peepmatic/crates/runtime/src/paths.rs
Normal file
242
cranelift/peepmatic/crates/runtime/src/paths.rs
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
//! Representing paths through the dataflow graph.
|
||||||
|
//!
|
||||||
|
//! Paths are relative from a *root* instruction, which is the instruction we
|
||||||
|
//! are determining which, if any, optimizations apply.
|
||||||
|
//!
|
||||||
|
//! Paths are series of indices through each instruction's children as we
|
||||||
|
//! traverse down the graph from the root. Children are immediates followed by
|
||||||
|
//! arguments: `[imm0, imm1, ..., immN, arg0, arg1, ..., argN]`.
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! * `[0]` is the path to the root.
|
||||||
|
//! * `[0, 0]` is the path to the root's first child.
|
||||||
|
//! * `[0, 1]` is the path to the root's second child.
|
||||||
|
//! * `[0, 1, 0]` is the path to the root's second child's first child.
|
||||||
|
//!
|
||||||
|
//! ## Interning
|
||||||
|
//!
|
||||||
|
//! To avoid extra allocations, de-duplicate paths, and reference them via a
|
||||||
|
//! fixed-length value, we intern paths inside a `PathInterner` and then
|
||||||
|
//! reference them via `PathId`.
|
||||||
|
|
||||||
|
// TODO: Make `[]` the path to the root, and get rid of this redundant leading
|
||||||
|
// zero that is currently in every single path.
|
||||||
|
|
||||||
|
use serde::de::{Deserializer, SeqAccess, Visitor};
|
||||||
|
use serde::ser::{SerializeSeq, Serializer};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::convert::TryInto;
|
||||||
|
use std::fmt;
|
||||||
|
use std::hash::{Hash, Hasher};
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
|
/// A path through the data-flow graph from the root instruction.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
pub struct Path<'a>(pub &'a [u8]);
|
||||||
|
|
||||||
|
impl Path<'_> {
|
||||||
|
/// Construct a new path through the data-flow graph from the root
|
||||||
|
/// instruction.
|
||||||
|
pub fn new(path: &impl AsRef<[u8]>) -> Path {
|
||||||
|
Path(path.as_ref())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An identifier for an interned path.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct PathId(u32);
|
||||||
|
|
||||||
|
/// An interner and de-duplicator for `Path`s.
|
||||||
|
///
|
||||||
|
/// Can be serialized and deserialized while maintaining the same id to interned
|
||||||
|
/// path mapping.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct PathInterner {
|
||||||
|
// A map from a path (whose owned data is inside `arena`) to the canonical
|
||||||
|
// `PathId` we assigned it when interning it.
|
||||||
|
map: HashMap<UnsafePath, PathId>,
|
||||||
|
|
||||||
|
// A map from a `PathId` index to an unsafe, self-borrowed path pointing
|
||||||
|
// into `arena`. It is safe to given these out as safe `Path`s, as long as
|
||||||
|
// the lifetime is not longer than this `PathInterner`'s lifetime.
|
||||||
|
paths: Vec<UnsafePath>,
|
||||||
|
|
||||||
|
// Bump allocation arena for path data. The bump arena ensures that these
|
||||||
|
// allocations never move, and are therefore safe for self-references.
|
||||||
|
arena: bumpalo::Bump,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PathInterner {
|
||||||
|
/// Construct a new, empty `PathInterner`.
|
||||||
|
#[inline]
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Intern a path into this `PathInterner`, returning its canonical
|
||||||
|
/// `PathId`.
|
||||||
|
///
|
||||||
|
/// If we've already interned this path before, then the existing id we
|
||||||
|
/// already assigned to it is returned. If we've never seen this path
|
||||||
|
/// before, then it is copied into this `PathInterner` and a new id is
|
||||||
|
/// assigned to it.
|
||||||
|
#[inline]
|
||||||
|
pub fn intern<'a>(&mut self, path: Path<'a>) -> PathId {
|
||||||
|
let unsafe_path = unsafe { UnsafePath::from_path(&path) };
|
||||||
|
if let Some(id) = self.map.get(&unsafe_path) {
|
||||||
|
return *id;
|
||||||
|
}
|
||||||
|
self.intern_new(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn intern_new<'a>(&mut self, path: Path<'a>) -> PathId {
|
||||||
|
let id: u32 = self
|
||||||
|
.paths
|
||||||
|
.len()
|
||||||
|
.try_into()
|
||||||
|
.expect("too many paths interned");
|
||||||
|
let id = PathId(id);
|
||||||
|
|
||||||
|
let our_path = self.arena.alloc_slice_copy(&path.0);
|
||||||
|
let unsafe_path = unsafe { UnsafePath::from_slice(&our_path) };
|
||||||
|
|
||||||
|
self.paths.push(unsafe_path.clone());
|
||||||
|
let old = self.map.insert(unsafe_path, id);
|
||||||
|
|
||||||
|
debug_assert!(old.is_none());
|
||||||
|
debug_assert_eq!(self.lookup(id), path);
|
||||||
|
debug_assert_eq!(self.intern(path), id);
|
||||||
|
|
||||||
|
id
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lookup a previously interned path by id.
|
||||||
|
#[inline]
|
||||||
|
pub fn lookup<'a>(&'a self, id: PathId) -> Path<'a> {
|
||||||
|
let unsafe_path = self
|
||||||
|
.paths
|
||||||
|
.get(id.0 as usize)
|
||||||
|
.unwrap_or_else(|| Self::lookup_failure());
|
||||||
|
unsafe { unsafe_path.as_path() }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline(never)]
|
||||||
|
fn lookup_failure() -> ! {
|
||||||
|
panic!(
|
||||||
|
"no path for the given id; this can only happen when mixing `PathId`s with different \
|
||||||
|
`PathInterner`s"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for PathInterner {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
let mut seq = serializer.serialize_seq(Some(self.paths.len()))?;
|
||||||
|
for p in &self.paths {
|
||||||
|
let p = unsafe { p.as_path() };
|
||||||
|
seq.serialize_element(&p)?;
|
||||||
|
}
|
||||||
|
seq.end()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for PathInterner {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
deserializer.deserialize_seq(PathInternerVisitor {
|
||||||
|
marker: PhantomData,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PathInternerVisitor {
|
||||||
|
marker: PhantomData<fn() -> PathInterner>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Visitor<'de> for PathInternerVisitor {
|
||||||
|
type Value = PathInterner;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(formatter, "a `peepmatic_runtime::paths::PathInterner`")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
||||||
|
where
|
||||||
|
M: SeqAccess<'de>,
|
||||||
|
{
|
||||||
|
const DEFAULT_CAPACITY: usize = 16;
|
||||||
|
let capacity = access.size_hint().unwrap_or(DEFAULT_CAPACITY);
|
||||||
|
|
||||||
|
let mut interner = PathInterner {
|
||||||
|
map: HashMap::with_capacity(capacity),
|
||||||
|
paths: Vec::with_capacity(capacity),
|
||||||
|
arena: bumpalo::Bump::new(),
|
||||||
|
};
|
||||||
|
|
||||||
|
while let Some(path) = access.next_element::<Path>()? {
|
||||||
|
interner.intern(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(interner)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An unsafe, unchecked borrow of a path. Not for use outside of
|
||||||
|
/// `PathInterner`!
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct UnsafePath {
|
||||||
|
ptr: *const u8,
|
||||||
|
len: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for UnsafePath {
|
||||||
|
fn eq(&self, rhs: &UnsafePath) -> bool {
|
||||||
|
unsafe { self.as_slice() == rhs.as_slice() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for UnsafePath {}
|
||||||
|
|
||||||
|
impl Hash for UnsafePath {
|
||||||
|
fn hash<H>(&self, hasher: &mut H)
|
||||||
|
where
|
||||||
|
H: Hasher,
|
||||||
|
{
|
||||||
|
unsafe { self.as_slice().hash(hasher) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety: callers must ensure that the constructed values won't have unsafe
|
||||||
|
/// usages of `PartialEq`, `Eq`, or `Hash`.
|
||||||
|
impl UnsafePath {
|
||||||
|
unsafe fn from_path(p: &Path) -> Self {
|
||||||
|
Self::from_slice(&p.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn from_slice(s: &[u8]) -> Self {
|
||||||
|
UnsafePath {
|
||||||
|
ptr: s.as_ptr(),
|
||||||
|
len: s.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safety: callers must ensure that `'a` does not outlive the lifetime of the
|
||||||
|
/// underlying data.
|
||||||
|
impl UnsafePath {
|
||||||
|
unsafe fn as_slice<'a>(&self) -> &'a [u8] {
|
||||||
|
std::slice::from_raw_parts(self.ptr, self.len)
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn as_path<'a>(&self) -> Path<'a> {
|
||||||
|
Path(self.as_slice())
|
||||||
|
}
|
||||||
|
}
|
||||||
262
cranelift/peepmatic/crates/runtime/src/type.rs
Normal file
262
cranelift/peepmatic/crates/runtime/src/type.rs
Normal file
@@ -0,0 +1,262 @@
|
|||||||
|
//! Types.
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
/// A bit width of a type.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
#[repr(u8)]
|
||||||
|
pub enum BitWidth {
|
||||||
|
/// Polymorphic over bit width, with the same width as the root of the
|
||||||
|
/// optimization's LHS and RHS.
|
||||||
|
Polymorphic = 0,
|
||||||
|
|
||||||
|
/// A fixed bit width of 1.
|
||||||
|
One = 1,
|
||||||
|
|
||||||
|
/// A fixed bit width of 8.
|
||||||
|
Eight = 8,
|
||||||
|
|
||||||
|
/// A fixed bit width of 16.
|
||||||
|
Sixteen = 16,
|
||||||
|
|
||||||
|
/// A fixed bit width of 32.
|
||||||
|
ThirtyTwo = 32,
|
||||||
|
|
||||||
|
/// A fixed bit width of 64.
|
||||||
|
SixtyFour = 64,
|
||||||
|
|
||||||
|
/// A fixed bit width of 128.
|
||||||
|
OneTwentyEight = 128,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The kind of type we are looking at: either an integer kind or boolean kind.
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub enum Kind {
|
||||||
|
/// Integer kind.
|
||||||
|
Int,
|
||||||
|
|
||||||
|
/// Boolean kind.
|
||||||
|
Bool,
|
||||||
|
|
||||||
|
/// CPU flags kind.
|
||||||
|
CpuFlags,
|
||||||
|
|
||||||
|
/// Void kind.
|
||||||
|
Void,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A type a value or the result of an operation.
|
||||||
|
#[allow(missing_docs)]
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
|
pub struct Type {
|
||||||
|
/// This type's kind.
|
||||||
|
pub kind: Kind,
|
||||||
|
|
||||||
|
/// This type's bit width.
|
||||||
|
pub bit_width: BitWidth,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TryFrom<u8> for BitWidth {
|
||||||
|
type Error = &'static str;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn try_from(x: u8) -> Result<Self, Self::Error> {
|
||||||
|
Ok(match x {
|
||||||
|
1 => Self::One,
|
||||||
|
8 => Self::Eight,
|
||||||
|
16 => Self::Sixteen,
|
||||||
|
32 => Self::ThirtyTwo,
|
||||||
|
64 => Self::SixtyFour,
|
||||||
|
128 => Self::OneTwentyEight,
|
||||||
|
_ => return Err("not a valid bit width"),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Type {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self.kind {
|
||||||
|
Kind::CpuFlags => return write!(f, "cpu-flags"),
|
||||||
|
Kind::Void => return write!(f, "void"),
|
||||||
|
Kind::Int => write!(f, "i")?,
|
||||||
|
Kind::Bool => write!(f, "b")?,
|
||||||
|
}
|
||||||
|
match self.bit_width {
|
||||||
|
BitWidth::Polymorphic => write!(f, "NN"),
|
||||||
|
otherwise => write!(f, "{}", otherwise as u8),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BitWidth {
|
||||||
|
/// Is this a polymorphic bit width?
|
||||||
|
pub fn is_polymorphic(&self) -> bool {
|
||||||
|
matches!(self, BitWidth::Polymorphic)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get this width in bits, unless this is a polymorphic bit width.
|
||||||
|
pub fn fixed_width(&self) -> Option<u8> {
|
||||||
|
if self.is_polymorphic() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(*self as u8)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! type_ctors {
|
||||||
|
( $( $( #[$attr:meta] )* $name:ident ( $kind:ident , $width:ident ) ; )* ) => {
|
||||||
|
$(
|
||||||
|
$( #[$attr] )*
|
||||||
|
pub const fn $name() -> Self {
|
||||||
|
Type {
|
||||||
|
kind: Kind::$kind,
|
||||||
|
bit_width: BitWidth::$width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Type {
|
||||||
|
type_ctors! {
|
||||||
|
/// Get the `i1` type.
|
||||||
|
i1(Int, One);
|
||||||
|
/// Get the `i8` type.
|
||||||
|
i8(Int, Eight);
|
||||||
|
/// Get the `i16` type.
|
||||||
|
i16(Int, Sixteen);
|
||||||
|
/// Get the `i32` type.
|
||||||
|
i32(Int, ThirtyTwo);
|
||||||
|
/// Get the `i64` type.
|
||||||
|
i64(Int, SixtyFour);
|
||||||
|
/// Get the `i128` type.
|
||||||
|
i128(Int, OneTwentyEight);
|
||||||
|
/// Get the `b1` type.
|
||||||
|
b1(Bool, One);
|
||||||
|
/// Get the `b8` type.
|
||||||
|
b8(Bool, Eight);
|
||||||
|
/// Get the `b16` type.
|
||||||
|
b16(Bool, Sixteen);
|
||||||
|
/// Get the `b32` type.
|
||||||
|
b32(Bool, ThirtyTwo);
|
||||||
|
/// Get the `b64` type.
|
||||||
|
b64(Bool, SixtyFour);
|
||||||
|
/// Get the `b128` type.
|
||||||
|
b128(Bool, OneTwentyEight);
|
||||||
|
/// Get the CPU flags type.
|
||||||
|
cpu_flags(CpuFlags, One);
|
||||||
|
/// Get the void type.
|
||||||
|
void(Void, One);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "construct")]
|
||||||
|
mod tok {
|
||||||
|
use wast::custom_keyword;
|
||||||
|
custom_keyword!(b1);
|
||||||
|
custom_keyword!(b8);
|
||||||
|
custom_keyword!(b16);
|
||||||
|
custom_keyword!(b32);
|
||||||
|
custom_keyword!(b64);
|
||||||
|
custom_keyword!(b128);
|
||||||
|
custom_keyword!(i1);
|
||||||
|
custom_keyword!(i8);
|
||||||
|
custom_keyword!(i16);
|
||||||
|
custom_keyword!(i32);
|
||||||
|
custom_keyword!(i64);
|
||||||
|
custom_keyword!(i128);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "construct")]
|
||||||
|
impl<'a> wast::parser::Parse<'a> for Type {
|
||||||
|
fn parse(p: wast::parser::Parser<'a>) -> wast::parser::Result<Self> {
|
||||||
|
if p.peek::<tok::b1>() {
|
||||||
|
p.parse::<tok::b1>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Bool,
|
||||||
|
bit_width: BitWidth::One,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::b8>() {
|
||||||
|
p.parse::<tok::b8>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Bool,
|
||||||
|
bit_width: BitWidth::Eight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::b16>() {
|
||||||
|
p.parse::<tok::b16>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Bool,
|
||||||
|
bit_width: BitWidth::Sixteen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::b32>() {
|
||||||
|
p.parse::<tok::b32>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Bool,
|
||||||
|
bit_width: BitWidth::ThirtyTwo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::b64>() {
|
||||||
|
p.parse::<tok::b64>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Bool,
|
||||||
|
bit_width: BitWidth::SixtyFour,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::b128>() {
|
||||||
|
p.parse::<tok::b128>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Bool,
|
||||||
|
bit_width: BitWidth::OneTwentyEight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::i1>() {
|
||||||
|
p.parse::<tok::i1>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Int,
|
||||||
|
bit_width: BitWidth::One,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::i8>() {
|
||||||
|
p.parse::<tok::i8>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Int,
|
||||||
|
bit_width: BitWidth::Eight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::i16>() {
|
||||||
|
p.parse::<tok::i16>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Int,
|
||||||
|
bit_width: BitWidth::Sixteen,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::i32>() {
|
||||||
|
p.parse::<tok::i32>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Int,
|
||||||
|
bit_width: BitWidth::ThirtyTwo,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::i64>() {
|
||||||
|
p.parse::<tok::i64>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Int,
|
||||||
|
bit_width: BitWidth::SixtyFour,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if p.peek::<tok::i128>() {
|
||||||
|
p.parse::<tok::i128>()?;
|
||||||
|
return Ok(Type {
|
||||||
|
kind: Kind::Int,
|
||||||
|
bit_width: BitWidth::OneTwentyEight,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Err(p.error("expected an ascribed type"))
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user