diff --git a/cranelift/peepmatic/crates/runtime/Cargo.toml b/cranelift/peepmatic/crates/runtime/Cargo.toml new file mode 100644 index 0000000000..31c4655b25 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "peepmatic-runtime" +version = "0.1.0" +authors = ["Nick Fitzgerald "] +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"] diff --git a/cranelift/peepmatic/crates/runtime/src/cc.rs b/cranelift/peepmatic/crates/runtime/src/cc.rs new file mode 100644 index 0000000000..69f44db2ae --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/cc.rs @@ -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 for ConditionCode { + type Error = &'static str; + + fn try_from(x: u32) -> Result { + 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"), + } + } +} diff --git a/cranelift/peepmatic/crates/runtime/src/error.rs b/cranelift/peepmatic/crates/runtime/src/error.rs new file mode 100644 index 0000000000..3453316238 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/error.rs @@ -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 = std::result::Result; + +/// Errors that `peepmatic_runtime` may generate. +#[derive(Debug, Error)] +#[error(transparent)] +pub struct Error { + #[from] + inner: Box, +} + +#[derive(Debug, Error)] +enum ErrorInner { + #[error(transparent)] + Io(#[from] io::Error), + + #[error(transparent)] + Bincode(#[from] bincode::Error), +} + +impl From for Error { + fn from(e: io::Error) -> Error { + let e: ErrorInner = e.into(); + e.into() + } +} + +impl From for Error { + fn from(e: bincode::Error) -> Error { + let e: ErrorInner = e.into(); + e.into() + } +} + +impl From for Error { + fn from(e: ErrorInner) -> Error { + Box::new(e).into() + } +} diff --git a/cranelift/peepmatic/crates/runtime/src/instruction_set.rs b/cranelift/peepmatic/crates/runtime/src/instruction_set.rs new file mode 100644 index 0000000000..1a501d4ce4 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/instruction_set.rs @@ -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; + + /// 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>; + + /// 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; + + /// 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; + + /// 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, + b: Part, + ) -> 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, + b: Part, + c: Part, + ) -> 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; + + /// 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; +} diff --git a/cranelift/peepmatic/crates/runtime/src/integer_interner.rs b/cranelift/peepmatic/crates/runtime/src/integer_interner.rs new file mode 100644 index 0000000000..93dd2e7183 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/integer_interner.rs @@ -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, + values: Vec, +} + +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) -> 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) -> Option { + 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 for u32 { + #[inline] + fn from(id: IntegerId) -> u32 { + id.0 + } +} diff --git a/cranelift/peepmatic/crates/runtime/src/lib.rs b/cranelift/peepmatic/crates/runtime/src/lib.rs new file mode 100755 index 0000000000..ae45c42f2b --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/lib.rs @@ -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; diff --git a/cranelift/peepmatic/crates/runtime/src/linear.rs b/cranelift/peepmatic/crates/runtime/src/linear.rs new file mode 100644 index 0000000000..006f18e0e5 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/linear.rs @@ -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, + + /// 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, +} + +/// 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, + + /// Actions to perform, given that the operation resulted in the expected + /// value. + pub actions: Vec, +} + +/// 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], + }, +} diff --git a/cranelift/peepmatic/crates/runtime/src/operator.rs b/cranelift/peepmatic/crates/runtime/src/operator.rs new file mode 100644 index 0000000000..35e17a89f7 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/operator.rs @@ -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; +} diff --git a/cranelift/peepmatic/crates/runtime/src/optimizations.rs b/cranelift/peepmatic/crates/runtime/src/optimizations.rs new file mode 100644 index 0000000000..29dfa7f388 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/optimizations.rs @@ -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, MatchOp, Vec>, +} + +impl PeepholeOptimizations { + /// Deserialize a `PeepholeOptimizations` from bytes. + pub fn deserialize(serialized: &[u8]) -> Result { + 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![], + } + } +} diff --git a/cranelift/peepmatic/crates/runtime/src/optimizer.rs b/cranelift/peepmatic/crates/runtime/src/optimizer.rs new file mode 100644 index 0000000000..535bb56219 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/optimizer.rs @@ -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>, + pub(crate) right_hand_sides: Vec>, + pub(crate) actions: Vec, + 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 { + 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 { + 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; + } + } + } +} diff --git a/cranelift/peepmatic/crates/runtime/src/part.rs b/cranelift/peepmatic/crates/runtime/src/part.rs new file mode 100644 index 0000000000..6190b8e980 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/part.rs @@ -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 +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 From for Part +where + I: Copy + Debug + Eq, +{ + #[inline] + fn from(c: Constant) -> Part { + Part::Constant(c) + } +} + +impl From for Part +where + I: Copy + Debug + Eq, +{ + #[inline] + fn from(c: ConditionCode) -> Part { + 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 { + 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 { + 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 Part +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; + } +} diff --git a/cranelift/peepmatic/crates/runtime/src/paths.rs b/cranelift/peepmatic/crates/runtime/src/paths.rs new file mode 100644 index 0000000000..a30aa81886 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/paths.rs @@ -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, + + // 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, + + // 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(&self, serializer: S) -> Result + 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(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + deserializer.deserialize_seq(PathInternerVisitor { + marker: PhantomData, + }) + } +} + +struct PathInternerVisitor { + marker: PhantomData 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(self, mut access: M) -> Result + 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::()? { + 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(&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()) + } +} diff --git a/cranelift/peepmatic/crates/runtime/src/type.rs b/cranelift/peepmatic/crates/runtime/src/type.rs new file mode 100644 index 0000000000..e4a08c9851 --- /dev/null +++ b/cranelift/peepmatic/crates/runtime/src/type.rs @@ -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 for BitWidth { + type Error = &'static str; + + #[inline] + fn try_from(x: u8) -> Result { + 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 { + 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 { + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Bool, + bit_width: BitWidth::One, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Bool, + bit_width: BitWidth::Eight, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Bool, + bit_width: BitWidth::Sixteen, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Bool, + bit_width: BitWidth::ThirtyTwo, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Bool, + bit_width: BitWidth::SixtyFour, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Bool, + bit_width: BitWidth::OneTwentyEight, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Int, + bit_width: BitWidth::One, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Int, + bit_width: BitWidth::Eight, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Int, + bit_width: BitWidth::Sixteen, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Int, + bit_width: BitWidth::ThirtyTwo, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Int, + bit_width: BitWidth::SixtyFour, + }); + } + if p.peek::() { + p.parse::()?; + return Ok(Type { + kind: Kind::Int, + bit_width: BitWidth::OneTwentyEight, + }); + } + Err(p.error("expected an ascribed type")) + } +}