peepmatic: Be generic over the operator type

This lets us avoid the cost of `cranelift_codegen::ir::Opcode` to
`peepmatic_runtime::Operator` conversion overhead, and paves the way for
allowing Peepmatic to support non-clif optimizations (e.g. vcode optimizations).

Rather than defining our own `peepmatic::Operator` type like we used to, now the
whole `peepmatic` crate is effectively generic over a `TOperator` type
parameter. For the Cranelift integration, we use `cranelift_codegen::ir::Opcode`
as the concrete type for our `TOperator` type parameter. For testing, we also
define a `TestOperator` type, so that we can test Peepmatic code without
building all of Cranelift, and we can keep them somewhat isolated from each
other.

The methods that `peepmatic::Operator` had are now translated into trait bounds
on the `TOperator` type. These traits need to be shared between all of
`peepmatic`, `peepmatic-runtime`, and `cranelift-codegen`'s Peepmatic
integration. Therefore, these new traits live in a new crate:
`peepmatic-traits`. This crate acts as a header file of sorts for shared
trait/type/macro definitions.

Additionally, the `peepmatic-runtime` crate no longer depends on the
`peepmatic-macro` procedural macro crate, which should lead to faster build
times for Cranelift when it is using pre-built peephole optimizers.
This commit is contained in:
Nick Fitzgerald
2020-06-30 11:50:10 -07:00
parent ae95ad8733
commit ee5982fd16
46 changed files with 1945 additions and 1387 deletions

View File

@@ -1,6 +1,6 @@
[package]
name = "peepmatic-automata"
version = "0.2.0"
version = "0.66.0"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
edition = "2018"
license = "Apache-2.0 WITH LLVM-exception"

View File

@@ -1,6 +1,6 @@
[package]
name = "peepmatic-fuzzing"
version = "0.2.0"
version = "0.66.0"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
edition = "2018"
publish = false
@@ -17,6 +17,8 @@ peepmatic = { path = "../.." }
peepmatic-automata = { path = "../automata", features = ["serde"] }
peepmatic-runtime = { path = "../runtime", features = ["construct"] }
peepmatic-test = { path = "../test" }
peepmatic-test-operator = { path = "../test-operator" }
peepmatic-traits = { path = "../traits" }
rand = { version = "0.7.3", features = ["small_rng"] }
serde = "1.0.106"
wast = "15.0.0"

View File

@@ -1,6 +1,7 @@
//! Fuzz testing utilities related to AST pattern matching.
use peepmatic_runtime::PeepholeOptimizations;
use peepmatic_test_operator::TestOperator;
use std::path::Path;
use std::str;
@@ -19,18 +20,18 @@ pub fn compile(data: &[u8]) {
Ok(s) => s,
};
let opt = match peepmatic::compile_str(source, Path::new("fuzz")) {
let opt = match peepmatic::compile_str::<TestOperator>(source, Path::new("fuzz")) {
Err(_) => return,
Ok(o) => o,
};
// Should be able to serialize and deserialize the peephole optimizer.
let opt_bytes = bincode::serialize(&opt).expect("should serialize peephole optimizations OK");
let _: PeepholeOptimizations =
let _: PeepholeOptimizations<TestOperator> =
bincode::deserialize(&opt_bytes).expect("should deserialize peephole optimizations OK");
// Compiling the same source text again should be deterministic.
let opt2 = peepmatic::compile_str(source, Path::new("fuzz"))
let opt2 = peepmatic::compile_str::<TestOperator>(source, Path::new("fuzz"))
.expect("should be able to compile source text again, if it compiled OK the first time");
let opt2_bytes =
bincode::serialize(&opt2).expect("should serialize second peephole optimizations OK");

View File

@@ -6,12 +6,13 @@ use peepmatic::{
};
use peepmatic_runtime::{
cc::ConditionCode,
operator::TypingContext as TypingContextTrait,
part::Constant,
r#type::BitWidth,
r#type::{Kind, Type},
};
use peepmatic_test::{Program, TestIsa};
use peepmatic_test_operator::TestOperator;
use peepmatic_traits::{TypingContext as TypingContextTrait, TypingRules};
use std::collections::{BTreeMap, HashMap};
use std::path::Path;
use std::str;
@@ -37,7 +38,7 @@ pub fn interp(data: &[u8]) {
// Okay, we know it compiles and verifies alright, so (re)parse the AST.
let buf = wast::parser::ParseBuffer::new(&source).unwrap();
let ast = wast::parser::parse::<Optimizations>(&buf).unwrap();
let ast = wast::parser::parse::<Optimizations<TestOperator>>(&buf).unwrap();
// And we need access to the assigned types, so re-verify it as well.
peepmatic::verify(&ast).unwrap();
@@ -87,7 +88,7 @@ pub fn interp(data: &[u8]) {
// Generate this operation's immediates.
let mut imm_tys = vec![];
op.operator
.immediate_types(&mut TypingContext, op.span(), &mut imm_tys);
.immediate_types((), &mut TypingContext, &mut imm_tys);
let imms: Vec<_> = op
.operands
.iter()
@@ -121,7 +122,7 @@ pub fn interp(data: &[u8]) {
// this operation's arguments.
let mut arg_tys = vec![];
op.operator
.param_types(&mut TypingContext, op.span(), &mut arg_tys);
.parameter_types((), &mut TypingContext, &mut arg_tys);
let args: Vec<_> = op
.operands
.iter()
@@ -165,7 +166,7 @@ pub fn interp(data: &[u8]) {
})
.collect();
let ty = match op.operator.result_type(&mut TypingContext, op.span()) {
let ty = match op.operator.result_type((), &mut TypingContext) {
TypeOrConditionCode::Type(ty) => ty,
TypeOrConditionCode::ConditionCode => {
unreachable!("condition codes cannot be operation results")
@@ -206,41 +207,42 @@ enum TypeOrConditionCode {
struct TypingContext;
impl<'a> TypingContextTrait<'a> for TypingContext {
type Span = ();
type TypeVariable = TypeOrConditionCode;
fn cc(&mut self, _: wast::Span) -> Self::TypeVariable {
fn cc(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::ConditionCode
}
fn bNN(&mut self, _: wast::Span) -> Self::TypeVariable {
fn bNN(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::Type(Type::b1())
}
fn iNN(&mut self, _: wast::Span) -> Self::TypeVariable {
fn iNN(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::Type(Type::i32())
}
fn iMM(&mut self, _: wast::Span) -> Self::TypeVariable {
fn iMM(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::Type(Type::i32())
}
fn cpu_flags(&mut self, _: wast::Span) -> Self::TypeVariable {
fn cpu_flags(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::Type(Type::cpu_flags())
}
fn b1(&mut self, _: wast::Span) -> Self::TypeVariable {
fn b1(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::Type(Type::b1())
}
fn void(&mut self, _: wast::Span) -> Self::TypeVariable {
fn void(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::Type(Type::void())
}
fn bool_or_int(&mut self, _: wast::Span) -> Self::TypeVariable {
fn bool_or_int(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::Type(Type::b1())
}
fn any_t(&mut self, _: wast::Span) -> Self::TypeVariable {
fn any_t(&mut self, _: ()) -> Self::TypeVariable {
TypeOrConditionCode::Type(Type::i32())
}
}

View File

@@ -1,6 +1,7 @@
//! Utilities for fuzzing our DSL's parser.
use peepmatic::Optimizations;
use peepmatic_test_operator::TestOperator;
use std::str;
/// Attempt to parse the given string as if it were a snippet of our DSL.
@@ -15,7 +16,7 @@ pub fn parse(data: &[u8]) {
Err(_) => return,
};
let _ = wast::parser::parse::<Optimizations>(&buf);
let _ = wast::parser::parse::<Optimizations<TestOperator>>(&buf);
}
#[cfg(test)]

View File

@@ -1,6 +1,6 @@
[package]
name = "peepmatic-macro"
version = "0.2.0"
version = "0.66.0"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
edition = "2018"
license = "Apache-2.0 WITH LLVM-exception"

View File

@@ -9,8 +9,8 @@ pub fn derive_child_nodes(input: &DeriveInput) -> Result<impl quote::ToTokens> {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
Ok(quote! {
impl #impl_generics ChildNodes<'a, 'a> for #name #ty_generics #where_clause {
fn child_nodes(&'a self, children: &mut impl Extend<DynAstRef<'a>>) {
impl #impl_generics ChildNodes<'a, 'a, TOperator> for #name #ty_generics #where_clause {
fn child_nodes(&'a self, children: &mut impl Extend<DynAstRef<'a, TOperator>>) {
#children
}
}
@@ -103,7 +103,12 @@ fn get_child_nodes(data: &syn::Data) -> Result<impl quote::ToTokens> {
fn add_trait_bounds(mut generics: Generics) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(type_param) = param {
type_param.bounds.push(parse_quote!(ChildNodes<'a, 'a>));
if type_param.ident == "TOperator" {
continue;
}
type_param
.bounds
.push(parse_quote!(ChildNodes<'a, 'a, TOperator>));
}
}
generics

View File

@@ -13,7 +13,7 @@ pub fn derive_into_dyn_ast_ref(input: &DeriveInput) -> Result<impl quote::ToToke
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
Ok(quote! {
impl #impl_generics From<&'a #ty #ty_generics> for DynAstRef<'a> #where_clause {
impl #impl_generics From<&'a #ty #ty_generics> for DynAstRef<'a, TOperator> #where_clause {
#[inline]
fn from(x: &'a #ty #ty_generics) -> Self {
Self::#ty(x)

View File

@@ -11,14 +11,8 @@ use syn::{parse_macro_input, Ident, Result};
mod child_nodes;
mod into_dyn_ast_ref;
mod operator;
mod span;
#[proc_macro_derive(PeepmaticOperator, attributes(peepmatic))]
pub fn operator(input: TokenStream) -> TokenStream {
operator::derive_operator(input)
}
#[proc_macro_derive(Ast, attributes(peepmatic))]
pub fn derive_ast(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);

View File

@@ -1,325 +0,0 @@
//! Implementation of the `#[peepmatic]` macro for the `Operator` AST node.
use crate::proc_macro::TokenStream;
use crate::PeepmaticOpts;
use proc_macro2::{Ident, Span};
use quote::quote;
use syn::DeriveInput;
use syn::Error;
use syn::{parse_macro_input, Result};
pub fn derive_operator(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let variants = match get_enum_variants(&input) {
Ok(v) => v,
Err(e) => return e.to_compile_error().into(),
};
let arity = match create_arity(&variants) {
Ok(a) => a,
Err(e) => return e.to_compile_error().into(),
};
let num_operators = variants.len();
let type_methods = create_type_methods(&variants);
let parse_impl = create_parse_impl(&input.ident, &variants);
let display_impl = create_display_impl(&input.ident, &variants);
let try_from_u32_impl = create_try_from_u32_impl(&input.ident, &variants);
let ident = &input.ident;
let expanded = quote! {
impl #ident {
#arity
#type_methods
/// Get the total number of different operators.
pub const fn num_operators() -> usize {
#num_operators
}
}
#display_impl
#try_from_u32_impl
#parse_impl
};
// eprintln!("{}", expanded);
TokenStream::from(expanded)
}
fn get_enum_variants(input: &DeriveInput) -> Result<Vec<OperatorVariant>> {
let en = match &input.data {
syn::Data::Enum(en) => en,
syn::Data::Struct(_) => {
panic!("can only put #[peepmatic] on an enum; found it on a struct")
}
syn::Data::Union(_) => panic!("can only put #[peepmatic] on an enum; found it on a union"),
};
en.variants
.iter()
.cloned()
.map(|mut variant| {
Ok(OperatorVariant {
opts: PeepmaticOpts::from_attrs(&mut variant.attrs)?,
syn: variant,
})
})
.collect()
}
struct OperatorVariant {
syn: syn::Variant,
opts: PeepmaticOpts,
}
fn create_arity(variants: &[OperatorVariant]) -> Result<impl quote::ToTokens> {
let mut imm_arities = vec![];
let mut params_arities = vec![];
for v in variants {
let variant = &v.syn.ident;
let imm_arity = v.opts.immediates.len();
if imm_arity > std::u8::MAX as usize {
return Err(Error::new(
v.opts.immediates_paren.span,
"cannot have more than u8::MAX immediates",
));
}
let imm_arity = imm_arity as u8;
imm_arities.push(quote! {
Self::#variant => #imm_arity,
});
let params_arity = v.opts.params.len();
if params_arity > std::u8::MAX as usize {
return Err(Error::new(
v.opts.params_paren.span,
"cannot have more than u8::MAX params",
));
}
let params_arity = params_arity as u8;
params_arities.push(quote! {
Self::#variant => #params_arity,
});
}
Ok(quote! {
/// Get the number of immediates that this operator has.
pub fn immediates_arity(&self) -> u8 {
match *self {
#( #imm_arities )*
}
}
/// Get the number of parameters that this operator takes.
pub fn params_arity(&self) -> u8 {
match *self {
#( #params_arities )*
}
}
})
}
fn create_type_methods(variants: &[OperatorVariant]) -> impl quote::ToTokens {
let mut result_types = vec![];
let mut imm_types = vec![];
let mut param_types = vec![];
for v in variants {
let variant = &v.syn.ident;
let result_ty = v.opts.result.as_ref().unwrap_or_else(|| {
panic!(
"must define #[peepmatic(result(..))] on operator `{}`",
variant
)
});
result_types.push(quote! {
Self::#variant => {
context.#result_ty(span)
}
});
let imm_tys = match &v.opts.immediates[..] {
[] => quote! {},
[ty, rest @ ..] => {
let rest = rest.iter().map(|ty| {
quote! { .chain(::std::iter::once(context.#ty(span))) }
});
quote! {
types.extend(::std::iter::once(context.#ty(span))#( #rest )*);
}
}
};
imm_types.push(quote! {
Self::#variant => {
#imm_tys
}
});
let param_tys = match &v.opts.params[..] {
[] => quote! {},
[ty, rest @ ..] => {
let rest = rest.iter().map(|ty| {
quote! { .chain(::std::iter::once(context.#ty(span))) }
});
quote! {
types.extend(::std::iter::once(context.#ty(span))#( #rest )*);
}
}
};
param_types.push(quote! {
Self::#variant => {
#param_tys
}
});
}
quote! {
/// Get the result type of this operator.
#[cfg(feature = "construct")]
pub fn result_type<'a, C>(
&self,
context: &mut C,
span: wast::Span,
) -> C::TypeVariable
where
C: 'a + TypingContext<'a>,
{
match *self {
#( #result_types )*
}
}
/// Get the immediate types of this operator.
#[cfg(feature = "construct")]
pub fn immediate_types<'a, C>(
&self,
context: &mut C,
span: wast::Span,
types: &mut impl Extend<C::TypeVariable>,
)
where
C: 'a + TypingContext<'a>,
{
match *self {
#( #imm_types )*
}
}
/// Get the parameter types of this operator.
#[cfg(feature = "construct")]
pub fn param_types<'a, C>(
&self,
context: &mut C,
span: wast::Span,
types: &mut impl Extend<C::TypeVariable>,
)
where
C: 'a + TypingContext<'a>,
{
match *self {
#( #param_types )*
}
}
}
}
fn snake_case(s: &str) -> String {
let mut t = String::with_capacity(s.len() + 1);
for (i, ch) in s.chars().enumerate() {
if i != 0 && ch.is_uppercase() {
t.push('_');
}
t.extend(ch.to_lowercase());
}
t
}
fn create_parse_impl(ident: &syn::Ident, variants: &[OperatorVariant]) -> impl quote::ToTokens {
let token_defs = variants.iter().map(|v| {
let tok = snake_case(&v.syn.ident.to_string());
let tok = Ident::new(&tok, Span::call_site());
quote! {
wast::custom_keyword!(#tok);
}
});
let parses = variants.iter().map(|v| {
let tok = snake_case(&v.syn.ident.to_string());
let tok = Ident::new(&tok, Span::call_site());
let ident = &v.syn.ident;
quote! {
if p.peek::<#tok>() {
p.parse::<#tok>()?;
return Ok(Self::#ident);
}
}
});
let expected = format!("expected {}", ident);
quote! {
#[cfg(feature = "construct")]
impl<'a> wast::parser::Parse<'a> for #ident {
fn parse(p: wast::parser::Parser<'a>) -> wast::parser::Result<Self> {
#( #token_defs )*
#( #parses )*
Err(p.error(#expected))
}
}
}
}
fn create_display_impl(ident: &syn::Ident, variants: &[OperatorVariant]) -> impl quote::ToTokens {
let displays = variants.iter().map(|v| {
let variant = &v.syn.ident;
let snake = snake_case(&v.syn.ident.to_string());
quote! {
Self::#variant => write!(f, #snake),
}
});
quote! {
impl std::fmt::Display for #ident {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
#( #displays )*
}
}
}
}
}
fn create_try_from_u32_impl(
ident: &syn::Ident,
variants: &[OperatorVariant],
) -> impl quote::ToTokens {
let matches = variants.iter().map(|v| {
let variant = &v.syn.ident;
quote! {
x if Self::#variant as u32 == x => Ok(Self::#variant),
}
});
let error_msg = format!("value is not an `{}`", ident);
quote! {
impl std::convert::TryFrom<u32> for #ident {
type Error = &'static str;
fn try_from(value: u32) -> Result<Self, Self::Error> {
match value {
#( #matches )*
_ => Err(#error_msg)
}
}
}
}
}

View File

@@ -45,6 +45,9 @@ pub fn derive_span(input: &DeriveInput) -> Result<impl quote::ToTokens> {
fn add_span_trait_bounds(mut generics: Generics) -> Generics {
for param in &mut generics.params {
if let GenericParam::Type(ref mut type_param) = *param {
if type_param.ident == "TOperator" {
continue;
}
type_param.bounds.push(parse_quote!(Span));
}
}

View File

@@ -1,6 +1,6 @@
[package]
name = "peepmatic-runtime"
version = "0.2.0"
version = "0.66.0"
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
edition = "2018"
license = "Apache-2.0 WITH LLVM-exception"
@@ -12,13 +12,14 @@ description = "Runtime support for peepmatic peephole optimizers"
bincode = "1.2.1"
bumpalo = "3.2.0"
log = "0.4.8"
peepmatic-automata = { version = "0.2.0", path = "../automata", features = ["serde"] }
peepmatic-macro = { version = "0.2.0", path = "../macro" }
peepmatic-automata = { version = "0.66.0", path = "../automata", features = ["serde"] }
peepmatic-traits = { version = "0.66.0", path = "../traits" }
serde = { version = "1.0.105", features = ["derive"] }
thiserror = "1.0.15"
wast = { version = "15.0.0", optional = true }
[dev-dependencies]
peepmatic-test-operator = { version = "0.66.0", path = "../test-operator" }
serde_test = "1.0.114"
[features]

View File

@@ -1,10 +1,11 @@
//! 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;
use std::hash::Hash;
use std::num::NonZeroU32;
/// A trait for interfacing with actual instruction sequences.
///
@@ -32,6 +33,9 @@ pub unsafe trait InstructionSet<'a> {
/// implementation.
type Context;
/// An operator.
type Operator: 'static + Copy + Debug + Eq + Hash + Into<NonZeroU32>;
/// An instruction (or identifier for an instruction).
type Instruction: Copy + Debug + Eq;
@@ -64,10 +68,12 @@ pub unsafe trait InstructionSet<'a> {
/// 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>;
/// If the instruction isn't supported, then `None` should be returned.
fn operator(
&self,
context: &mut Self::Context,
instr: Self::Instruction,
) -> Option<Self::Operator>;
/// Make a unary instruction.
///
@@ -76,7 +82,7 @@ pub unsafe trait InstructionSet<'a> {
&self,
context: &mut Self::Context,
root: Self::Instruction,
operator: Operator,
operator: Self::Operator,
r#type: Type,
a: Part<Self::Instruction>,
) -> Self::Instruction;
@@ -92,7 +98,7 @@ pub unsafe trait InstructionSet<'a> {
&self,
context: &mut Self::Context,
root: Self::Instruction,
operator: Operator,
operator: Self::Operator,
r#type: Type,
a: Part<Self::Instruction>,
b: Part<Self::Instruction>,
@@ -108,7 +114,7 @@ pub unsafe trait InstructionSet<'a> {
&self,
context: &mut Self::Context,
root: Self::Instruction,
operator: Operator,
operator: Self::Operator,
r#type: Type,
a: Part<Self::Instruction>,
b: Part<Self::Instruction>,

View File

@@ -22,12 +22,12 @@ 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 mod unquote;
pub use error::{Error, Result};
pub use optimizations::PeepholeOptimizations;

View File

@@ -7,17 +7,22 @@
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 crate::unquote::UnquoteOperator;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::hash::Hash;
use std::num::NonZeroU32;
/// A set of linear optimizations.
#[derive(Debug)]
pub struct Optimizations {
pub struct Optimizations<TOperator>
where
TOperator: 'static + Copy + Debug + Eq + Hash,
{
/// The linear optimizations.
pub optimizations: Vec<Optimization>,
pub optimizations: Vec<Optimization<TOperator>>,
/// The de-duplicated paths referenced by these optimizations.
pub paths: PathInterner,
@@ -28,9 +33,12 @@ pub struct Optimizations {
/// A linearized optimization.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Optimization {
pub struct Optimization<TOperator>
where
TOperator: 'static + Copy + Debug + Eq + Hash,
{
/// The chain of increments for this optimization.
pub increments: Vec<Increment>,
pub increments: Vec<Increment<TOperator>>,
}
/// Match any value.
@@ -63,7 +71,10 @@ pub fn bool_to_match_result(b: bool) -> MatchResult {
/// basically become a state and a transition edge out of that state in the
/// final automata.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Increment {
pub struct Increment<TOperator>
where
TOperator: 'static + Copy + Debug + Eq + Hash,
{
/// The matching operation to perform.
pub operation: MatchOp,
@@ -74,7 +85,7 @@ pub struct Increment {
/// Actions to perform, given that the operation resulted in the expected
/// value.
pub actions: Vec<Action>,
pub actions: Vec<Action<TOperator>>,
}
/// A matching operation to be performed on some Cranelift instruction as part
@@ -163,7 +174,7 @@ pub struct RhsId(pub u16);
/// When evaluating actions, the `i^th` action implicitly defines the
/// `RhsId(i)`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Action {
pub enum Action<TOperator> {
/// Reuse something from the left-hand side.
GetLhs {
/// The path to the instruction or value.
@@ -215,13 +226,13 @@ pub enum Action {
/// The type of this instruction's result.
r#type: Type,
/// The operator for this instruction.
operator: Operator,
operator: TOperator,
},
/// Make a binary instruction.
MakeBinaryInst {
/// The opcode for this instruction.
operator: Operator,
operator: TOperator,
/// The type of this instruction's result.
r#type: Type,
/// The operands for this instruction.
@@ -231,7 +242,7 @@ pub enum Action {
/// Make a ternary instruction.
MakeTernaryInst {
/// The opcode for this instruction.
operator: Operator,
operator: TOperator,
/// The type of this instruction's result.
r#type: Type,
/// The operands for this instruction.
@@ -242,6 +253,7 @@ pub enum Action {
#[cfg(test)]
mod tests {
use super::*;
use peepmatic_test_operator::TestOperator;
// These types all end up in the automaton, so we should take care that they
// are small and don't fill up the data cache (or take up too much
@@ -259,6 +271,6 @@ mod tests {
#[test]
fn action_size() {
assert_eq!(std::mem::size_of::<Action>(), 16);
assert_eq!(std::mem::size_of::<Action<TestOperator>>(), 16);
}
}

View File

@@ -1,306 +0,0 @@
//! 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))]
// NB: We convert `Operator`s into `NonZeroU32`s with unchecked casts;
// memory safety relies on `Operator` starting at `1` and no variant ever
// being zero.
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,
/// `bnot`
#[peepmatic(params(iNN), result(iNN))]
Bnot,
/// `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,
/// Compile-time `isub` of two constant values.
#[peepmatic(params(iNN, iNN), result(iNN))]
Isub,
/// 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;
}

View File

@@ -8,6 +8,8 @@ use crate::optimizer::PeepholeOptimizer;
use crate::paths::PathInterner;
use peepmatic_automata::Automaton;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
use std::hash::Hash;
#[cfg(feature = "construct")]
use std::fs;
@@ -19,7 +21,10 @@ use std::path::Path;
/// 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 {
pub struct PeepholeOptimizations<TOperator>
where
TOperator: 'static + Copy + Debug + Eq + Hash,
{
/// The instruction paths referenced by the peephole optimizations.
pub paths: PathInterner,
@@ -29,12 +34,18 @@ pub struct PeepholeOptimizations {
/// The underlying automata for matching optimizations' left-hand sides, and
/// building up the corresponding right-hand side.
pub automata: Automaton<MatchResult, MatchOp, Box<[Action]>>,
pub automata: Automaton<MatchResult, MatchOp, Box<[Action<TOperator>]>>,
}
impl PeepholeOptimizations {
impl<TOperator> PeepholeOptimizations<TOperator>
where
TOperator: 'static + Copy + Debug + Eq + Hash,
{
/// Deserialize a `PeepholeOptimizations` from bytes.
pub fn deserialize(serialized: &[u8]) -> Result<Self> {
pub fn deserialize<'a>(serialized: &'a [u8]) -> Result<Self>
where
TOperator: serde::Deserialize<'a>,
{
let peep_opt: Self = bincode::deserialize(serialized)?;
Ok(peep_opt)
}
@@ -43,12 +54,20 @@ impl PeepholeOptimizations {
///
/// Requires that the `"construct"` cargo feature is enabled.
#[cfg(feature = "construct")]
pub fn serialize_to_file(&self, path: &Path) -> Result<()> {
pub fn serialize_to_file(&self, path: &Path) -> Result<()>
where
TOperator: serde::Serialize,
{
let file = fs::File::create(path)?;
bincode::serialize_into(file, self)?;
Ok(())
}
}
impl<TOperator> PeepholeOptimizations<TOperator>
where
TOperator: 'static + Copy + Debug + Eq + Hash,
{
/// Create a new peephole optimizer instance from this set of peephole
/// optimizations.
///
@@ -58,9 +77,13 @@ impl PeepholeOptimizations {
/// 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>
pub fn optimizer<'peep, 'ctx, TInstructionSet>(
&'peep self,
instr_set: TInstructionSet,
) -> PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
where
I: InstructionSet<'ctx>,
TInstructionSet: InstructionSet<'ctx, Operator = TOperator>,
TOperator: Into<std::num::NonZeroU32>,
{
PeepholeOptimizer {
peep_opt: self,

View File

@@ -2,10 +2,10 @@
use crate::instruction_set::InstructionSet;
use crate::linear::{bool_to_match_result, Action, Else, MatchOp, MatchResult};
use crate::operator::UnquoteOperator;
use crate::optimizations::PeepholeOptimizations;
use crate::part::{Constant, Part};
use crate::r#type::{BitWidth, Type};
use crate::unquote::UnquoteOperator;
use peepmatic_automata::State;
use std::convert::TryFrom;
use std::fmt::{self, Debug};
@@ -21,20 +21,20 @@ use std::num::NonZeroU32;
/// 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>
pub struct PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
where
I: InstructionSet<'ctx>,
TInstructionSet: InstructionSet<'ctx>,
{
pub(crate) peep_opt: &'peep PeepholeOptimizations,
pub(crate) instr_set: I,
pub(crate) right_hand_sides: Vec<Part<I::Instruction>>,
pub(crate) actions: Vec<Action>,
pub(crate) peep_opt: &'peep PeepholeOptimizations<TInstructionSet::Operator>,
pub(crate) instr_set: TInstructionSet,
pub(crate) right_hand_sides: Vec<Part<TInstructionSet::Instruction>>,
pub(crate) actions: Vec<Action<TInstructionSet::Operator>>,
pub(crate) backtracking_states: Vec<(State, usize)>,
}
impl<'peep, 'ctx, I> Debug for PeepholeOptimizer<'peep, 'ctx, I>
impl<'peep, 'ctx, TInstructionSet> Debug for PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
where
I: InstructionSet<'ctx>,
TInstructionSet: InstructionSet<'ctx>,
{
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let PeepholeOptimizer {
@@ -54,9 +54,9 @@ where
}
}
impl<'peep, 'ctx, I> PeepholeOptimizer<'peep, 'ctx, I>
impl<'peep, 'ctx, TInstructionSet> PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
where
I: InstructionSet<'ctx>,
TInstructionSet: InstructionSet<'ctx>,
{
fn eval_unquote_1(&self, operator: UnquoteOperator, a: Constant) -> Constant {
use Constant::*;
@@ -107,7 +107,11 @@ where
}
}
fn eval_actions(&mut self, context: &mut I::Context, root: I::Instruction) {
fn eval_actions(
&mut self,
context: &mut TInstructionSet::Context,
root: TInstructionSet::Instruction,
) {
let mut actions = mem::replace(&mut self.actions, vec![]);
for action in actions.drain(..) {
@@ -272,8 +276,8 @@ where
fn eval_match_op(
&mut self,
context: &mut I::Context,
root: I::Instruction,
context: &mut TInstructionSet::Context,
root: TInstructionSet::Instruction,
match_op: MatchOp,
) -> MatchResult {
use crate::linear::MatchOp::*;
@@ -288,13 +292,7 @@ where
.ok_or(Else)?;
let inst = part.as_instruction().ok_or(Else)?;
let op = self.instr_set.operator(context, inst).ok_or(Else)?;
let op = op as u32;
debug_assert!(
op != 0,
"`Operator` doesn't have any variant represented
with zero"
);
Ok(unsafe { NonZeroU32::new_unchecked(op as u32) })
Ok(op.into())
}
IsConst { path } => {
let path = self.peep_opt.paths.lookup(path);
@@ -477,9 +475,9 @@ where
/// untouched and `None` is returned.
pub fn apply_one(
&mut self,
context: &mut I::Context,
root: I::Instruction,
) -> Option<I::Instruction> {
context: &mut TInstructionSet::Context,
root: TInstructionSet::Instruction,
) -> Option<TInstructionSet::Instruction> {
log::trace!("PeepholeOptimizer::apply_one");
self.backtracking_states.clear();
@@ -566,7 +564,11 @@ where
/// 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) {
pub fn apply_all(
&mut self,
context: &mut TInstructionSet::Context,
mut inst: TInstructionSet::Instruction,
) {
loop {
if let Some(new_inst) = self.apply_one(context, inst) {
inst = new_inst;

View File

@@ -0,0 +1,44 @@
//! Unquote operator definition.
peepmatic_traits::define_operator! {
/// 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.
#[allow(missing_docs)]
UnquoteOperator {
band => Band {
parameters(iNN, iNN);
result(iNN);
}
bor => Bor {
parameters(iNN, iNN);
result(iNN);
}
bxor => Bxor {
parameters(iNN, iNN);
result(iNN);
}
iadd => Iadd {
parameters(iNN, iNN);
result(iNN);
}
imul => Imul {
parameters(iNN, iNN);
result(iNN);
}
isub => Isub {
parameters(iNN, iNN);
result(iNN);
}
log2 => Log2 {
parameters(iNN);
result(iNN);
}
neg => Neg {
parameters(iNN);
result(iNN);
}
}
parse_cfg(feature = "construct");
}

View File

@@ -0,0 +1,12 @@
[package]
name = "peepmatic-test-operator"
version = "0.66.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]
peepmatic-traits = { version = "0.66.0", path = "../traits" }
serde = { version = "1.0.105", features = ["derive"] }
wast = "15.0.0"

View File

@@ -0,0 +1,219 @@
//! This crate defines `TestOperator`: a `TOperator` type for usage in tests.
//!
//! This allows us to write Peepmatic-specific tests that do not depend on
//! building all of Cranelift.
peepmatic_traits::define_operator! {
/// A `TOperator` type for use inside tests.
TestOperator {
adjust_sp_down => AdjustSpDown {
parameters(iNN);
result(void);
}
adjust_sp_down_imm => AdjustSpDownImm {
immediates(iNN);
result(void);
}
band => Band {
parameters(iNN, iNN);
result(iNN);
}
band_imm => BandImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
bconst => Bconst {
immediates(b1);
result(bNN);
}
bint => Bint {
parameters(bNN);
result(iNN);
}
bor => Bor {
parameters(iNN, iNN);
result(iNN);
}
bor_imm => BorImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
brnz => Brnz {
parameters(bool_or_int);
result(void);
}
brz => Brz {
parameters(bool_or_int);
result(void);
}
bxor => Bxor {
parameters(iNN, iNN);
result(iNN);
}
bxor_imm => BxorImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
iadd => Iadd {
parameters(iNN, iNN);
result(iNN);
}
iadd_imm => IaddImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
icmp => Icmp {
immediates(cc);
parameters(iNN, iNN);
result(b1);
}
icmp_imm => IcmpImm {
immediates(cc, iNN);
parameters(iNN);
result(b1);
}
iconst => Iconst {
immediates(iNN);
result(iNN);
}
ifcmp => Ifcmp {
parameters(iNN, iNN);
result(cpu_flags);
}
ifcmp_imm => IfcmpImm {
immediates(iNN);
parameters(iNN);
result(cpu_flags);
}
imul => Imul {
parameters(iNN, iNN);
result(iNN);
}
imul_imm => ImulImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
ireduce => Ireduce {
parameters(iNN);
result(iMM);
is_reduce(true);
}
irsub_imm => IrsubImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
ishl => Ishl {
parameters(iNN, iNN);
result(iNN);
}
ishl_imm => IshlImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
isub => Isub {
parameters(iNN, iNN);
result(iNN);
}
rotl => Rotl {
parameters(iNN, iNN);
result(iNN);
}
rotl_imm => RotlImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
rotr => Rotr {
parameters(iNN, iNN);
result(iNN);
}
rotr_imm => RotrImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
sdiv => Sdiv {
parameters(iNN, iNN);
result(iNN);
}
sdiv_imm => SdivImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
select => Select {
parameters(bool_or_int, any_t, any_t);
result(any_t);
}
sextend => Sextend {
parameters(iNN);
result(iMM);
is_extend(true);
}
srem => Srem {
parameters(iNN, iNN);
result(iNN);
}
srem_imm => SremImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
sshr => Sshr {
parameters(iNN, iNN);
result(iNN);
}
sshr_imm => SshrImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
trapnz => Trapnz {
parameters(bool_or_int);
result(void);
}
trapz => Trapz {
parameters(bool_or_int);
result(void);
}
udiv => Udiv {
parameters(iNN, iNN);
result(iNN);
}
udiv_imm => UdivImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
uextend => Uextend {
parameters(iNN);
result(iMM);
is_extend(true);
}
urem => Urem {
parameters(iNN, iNN);
result(iNN);
}
urem_imm => UremImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
ushr => Ushr {
parameters(iNN, iNN);
result(iNN);
}
ushr_imm => UshrImm {
immediates(iNN);
parameters(iNN);
result(iNN);
}
}
}

View File

@@ -12,3 +12,5 @@ env_logger = "0.7.1"
log = "0.4.8"
peepmatic = { path = "../.." }
peepmatic-runtime = { path = "../runtime" }
peepmatic-test-operator = { path = "../test-operator" }
peepmatic-traits = { path = "../traits" }

View File

@@ -5,11 +5,12 @@
use peepmatic_runtime::{
cc::ConditionCode,
instruction_set::InstructionSet,
operator::Operator,
part::{Constant, Part},
paths::Path,
r#type::{BitWidth, Kind, Type},
};
use peepmatic_test_operator::TestOperator;
use peepmatic_traits::TypingRules;
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::convert::TryFrom;
@@ -19,7 +20,7 @@ pub struct Instruction(pub usize);
#[derive(Debug)]
pub struct InstructionData {
pub operator: Operator,
pub operator: TestOperator,
pub r#type: Type,
pub immediates: Vec<Immediate>,
pub arguments: Vec<Instruction>,
@@ -174,7 +175,7 @@ impl Program {
pub fn new_instruction(
&mut self,
operator: Operator,
operator: TestOperator,
r#type: Type,
immediates: Vec<Immediate>,
arguments: Vec<Instruction>,
@@ -188,11 +189,11 @@ impl Program {
immediates.len(),
);
assert_eq!(
operator.params_arity() as usize,
operator.parameters_arity() as usize,
arguments.len(),
"wrong number of arguments for {:?}: expected {}, found {}",
operator,
operator.params_arity(),
operator.parameters_arity(),
arguments.len(),
);
@@ -222,7 +223,7 @@ impl Program {
assert!(!root_bit_width.is_polymorphic());
match c {
Constant::Bool(_, bit_width) => self.new_instruction(
Operator::Bconst,
TestOperator::Bconst,
if bit_width.is_polymorphic() {
Type {
kind: Kind::Bool,
@@ -238,7 +239,7 @@ impl Program {
vec![],
),
Constant::Int(_, bit_width) => self.new_instruction(
Operator::Iconst,
TestOperator::Iconst,
if bit_width.is_polymorphic() {
Type {
kind: Kind::Int,
@@ -259,12 +260,12 @@ impl Program {
fn instruction_to_constant(&mut self, inst: Instruction) -> Option<Constant> {
match self.data(inst) {
InstructionData {
operator: Operator::Iconst,
operator: TestOperator::Iconst,
immediates,
..
} => Some(immediates[0].unwrap_constant()),
InstructionData {
operator: Operator::Bconst,
operator: TestOperator::Bconst,
immediates,
..
} => Some(immediates[0].unwrap_constant()),
@@ -310,6 +311,8 @@ pub struct TestIsa {
// Unsafe because we must ensure that `instruction_result_bit_width` never
// returns zero.
unsafe impl<'a> InstructionSet<'a> for TestIsa {
type Operator = TestOperator;
type Context = Program;
type Instruction = Instruction;
@@ -360,7 +363,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
Some(part)
}
fn operator(&self, program: &mut Program, instr: Instruction) -> Option<Operator> {
fn operator(&self, program: &mut Program, instr: Instruction) -> Option<TestOperator> {
log::debug!("operator({:?})", instr);
let data = program.data(instr);
Some(data.operator)
@@ -370,7 +373,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
&self,
program: &mut Program,
root: Instruction,
operator: Operator,
operator: TestOperator,
r#type: Type,
a: Part<Instruction>,
) -> Instruction {
@@ -383,11 +386,11 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
let (imms, args) = match operator.immediates_arity() {
0 => {
assert_eq!(operator.params_arity(), 1);
assert_eq!(operator.parameters_arity(), 1);
(vec![], vec![program.part_to_instruction(root, a).unwrap()])
}
1 => {
assert_eq!(operator.params_arity(), 0);
assert_eq!(operator.parameters_arity(), 0);
(vec![program.part_to_immediate(a).unwrap()], vec![])
}
_ => unreachable!(),
@@ -399,7 +402,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
&self,
program: &mut Program,
root: Instruction,
operator: Operator,
operator: TestOperator,
r#type: Type,
a: Part<Instruction>,
b: Part<Instruction>,
@@ -414,7 +417,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
let (imms, args) = match operator.immediates_arity() {
0 => {
assert_eq!(operator.params_arity(), 2);
assert_eq!(operator.parameters_arity(), 2);
(
vec![],
vec![
@@ -424,14 +427,14 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
)
}
1 => {
assert_eq!(operator.params_arity(), 1);
assert_eq!(operator.parameters_arity(), 1);
(
vec![program.part_to_immediate(a).unwrap()],
vec![program.part_to_instruction(root, b).unwrap()],
)
}
2 => {
assert_eq!(operator.params_arity(), 0);
assert_eq!(operator.parameters_arity(), 0);
(
vec![
program.part_to_immediate(a).unwrap(),
@@ -449,7 +452,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
&self,
program: &mut Program,
root: Instruction,
operator: Operator,
operator: TestOperator,
r#type: Type,
a: Part<Instruction>,
b: Part<Instruction>,
@@ -465,7 +468,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
);
let (imms, args) = match operator.immediates_arity() {
0 => {
assert_eq!(operator.params_arity(), 3);
assert_eq!(operator.parameters_arity(), 3);
(
vec![],
vec![
@@ -476,7 +479,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
)
}
1 => {
assert_eq!(operator.params_arity(), 2);
assert_eq!(operator.parameters_arity(), 2);
(
vec![program.part_to_immediate(a).unwrap()],
vec![
@@ -486,7 +489,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
)
}
2 => {
assert_eq!(operator.params_arity(), 1);
assert_eq!(operator.parameters_arity(), 1);
(
vec![
program.part_to_immediate(a).unwrap(),
@@ -496,7 +499,7 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
)
}
3 => {
assert_eq!(operator.params_arity(), 0);
assert_eq!(operator.parameters_arity(), 0);
(
vec![
program.part_to_immediate(a).unwrap(),

View File

@@ -1,10 +1,10 @@
use peepmatic_runtime::{
cc::ConditionCode,
operator::Operator,
part::Constant,
r#type::{BitWidth, Type},
};
use peepmatic_test::*;
use peepmatic_test_operator::TestOperator;
const TEST_ISA: TestIsa = TestIsa {
native_word_size_in_bits: 32,
@@ -26,13 +26,13 @@ fn opcode() {
let mut program = Program::default();
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let zero = program.r#const(Constant::Int(0, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let add = program.new_instruction(Operator::Iadd, Type::i32(), vec![], vec![five, zero]);
let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, zero]);
let new = optimizer.apply_one(&mut program, add);
let new = new.expect("optimization should have applied");
assert!(program.structurally_eq(new, five));
let add = program.new_instruction(Operator::Iadd, Type::i32(), vec![], vec![five, five]);
let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, five]);
let replacement = optimizer.apply_one(&mut program, add);
assert!(replacement.is_none());
}
@@ -45,10 +45,10 @@ fn constant() {
let mut program = Program::default();
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let zero = program.r#const(Constant::Int(0, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let add = program.new_instruction(Operator::Iadd, Type::i32(), vec![], vec![five, zero]);
let add = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![five, zero]);
let expected = program.new_instruction(
Operator::IaddImm,
TestOperator::IaddImm,
Type::i32(),
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
vec![zero],
@@ -58,8 +58,8 @@ fn constant() {
let new = new.expect("optimization should have applied");
assert!(program.structurally_eq(new, expected));
let mul = program.new_instruction(Operator::Imul, Type::i32(), vec![], vec![five, zero]);
let add = program.new_instruction(Operator::Imul, Type::i32(), vec![], vec![mul, five]);
let mul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, zero]);
let add = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![mul, five]);
let replacement = optimizer.apply_one(&mut program, add);
assert!(replacement.is_none());
}
@@ -71,7 +71,7 @@ fn boolean() {
let mut program = Program::default();
let t = program.r#const(Constant::Bool(true, BitWidth::One), BitWidth::One);
let bint = program.new_instruction(Operator::Bint, Type::i1(), vec![], vec![t]);
let bint = program.new_instruction(TestOperator::Bint, Type::i1(), vec![], vec![t]);
let one = program.r#const(Constant::Int(1, BitWidth::One), BitWidth::ThirtyTwo);
let new = optimizer.apply_one(&mut program, bint);
@@ -79,7 +79,7 @@ fn boolean() {
assert!(program.structurally_eq(new, one));
let f = program.r#const(Constant::Bool(false, BitWidth::One), BitWidth::One);
let bint = program.new_instruction(Operator::Bint, Type::i1(), vec![], vec![f]);
let bint = program.new_instruction(TestOperator::Bint, Type::i1(), vec![], vec![f]);
let replacement = optimizer.apply_one(&mut program, bint);
assert!(replacement.is_none());
}
@@ -92,7 +92,7 @@ fn condition_codes() {
let mut program = Program::default();
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::One);
let icmp_eq = program.new_instruction(
Operator::Icmp,
TestOperator::Icmp,
Type::b1(),
vec![ConditionCode::Eq.into()],
vec![five, five],
@@ -104,7 +104,7 @@ fn condition_codes() {
assert!(program.structurally_eq(new, t));
let icmp_ne = program.new_instruction(
Operator::Icmp,
TestOperator::Icmp,
Type::b1(),
vec![ConditionCode::Ne.into()],
vec![five, five],
@@ -128,17 +128,17 @@ fn is_power_of_two() {
let mut program = Program::default();
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let imul = program.new_instruction(Operator::Imul, Type::i32(), vec![], vec![five, two]);
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
let one = program.r#const(Constant::Int(1, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let ishl = program.new_instruction(Operator::Ishl, Type::i32(), vec![], vec![five, one]);
let ishl = program.new_instruction(TestOperator::Ishl, Type::i32(), vec![], vec![five, one]);
let new = optimizer.apply_one(&mut program, imul);
let new = new.expect("optimization should have applied");
assert!(program.structurally_eq(new, ishl));
let three = program.r#const(Constant::Int(3, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let imul = program.new_instruction(Operator::Imul, Type::i32(), vec![], vec![five, three]);
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, three]);
let replacement = optimizer.apply_one(&mut program, imul);
assert!(replacement.is_none());
@@ -159,10 +159,10 @@ fn bit_width() {
let mut program = Program::default();
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let imul = program.new_instruction(Operator::Imul, Type::i32(), vec![], vec![five, two]);
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
let imul_imm = program.new_instruction(
Operator::ImulImm,
TestOperator::ImulImm,
Type::i32(),
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
vec![two],
@@ -174,7 +174,7 @@ fn bit_width() {
let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour);
let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour);
let imul = program.new_instruction(Operator::Imul, Type::i32(), vec![], vec![five, two]);
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
let replacement = optimizer.apply_one(&mut program, imul);
assert!(replacement.is_none());
@@ -195,10 +195,10 @@ fn fits_in_native_word() {
let mut program = Program::default();
let five = program.r#const(Constant::Int(5, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let two = program.r#const(Constant::Int(2, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let imul = program.new_instruction(Operator::Imul, Type::i32(), vec![], vec![five, two]);
let imul = program.new_instruction(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
let imul_imm = program.new_instruction(
Operator::ImulImm,
TestOperator::ImulImm,
Type::i32(),
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
vec![two],
@@ -210,7 +210,7 @@ fn fits_in_native_word() {
let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour);
let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour);
let imul = program.new_instruction(Operator::Imul, Type::i64(), vec![], vec![five, two]);
let imul = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![five, two]);
let replacement = optimizer.apply_one(&mut program, imul);
assert!(replacement.is_none());
@@ -230,10 +230,10 @@ fn unquote_neg() {
let mut program = Program::default();
let five = program.r#const(Constant::Int(5, BitWidth::SixtyFour), BitWidth::SixtyFour);
let two = program.r#const(Constant::Int(2, BitWidth::SixtyFour), BitWidth::SixtyFour);
let isub = program.new_instruction(Operator::Isub, Type::i64(), vec![], vec![five, two]);
let isub = program.new_instruction(TestOperator::Isub, Type::i64(), vec![], vec![five, two]);
let iadd_imm = program.new_instruction(
Operator::IaddImm,
TestOperator::IaddImm,
Type::i64(),
vec![Constant::Int(-2 as _, BitWidth::SixtyFour).into()],
vec![five],
@@ -276,13 +276,13 @@ fn subsumption() {
log::debug!("(iadd (iadd (iadd w x) y) z) => (iadd (iadd w x) (iadd y z))");
let iadd = program.new_instruction(Operator::Iadd, Type::i64(), vec![], vec![w, x]);
let iadd = program.new_instruction(Operator::Iadd, Type::i64(), vec![], vec![iadd, y]);
let iadd = program.new_instruction(Operator::Iadd, Type::i64(), vec![], vec![iadd, z]);
let expected_lhs = program.new_instruction(Operator::Iadd, Type::i64(), vec![], vec![w, x]);
let expected_rhs = program.new_instruction(Operator::Iadd, Type::i64(), vec![], vec![y, z]);
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]);
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![iadd, y]);
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![iadd, z]);
let expected_lhs = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]);
let expected_rhs = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![y, z]);
let expected = program.new_instruction(
Operator::Iadd,
TestOperator::Iadd,
Type::i64(),
vec![],
vec![expected_lhs, expected_rhs],
@@ -294,17 +294,17 @@ fn subsumption() {
log::debug!("(iadd w x) => y");
let iadd = program.new_instruction(Operator::Iadd, Type::i64(), vec![], vec![w, x]);
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![w, x]);
let new = optimizer.apply_one(&mut program, iadd);
let new = new.expect("optimization should have applied");
assert!(program.structurally_eq(new, y));
log::debug!("(iadd x (iadd y z)) => (iadd_imm x (iadd y z))");
let iadd_y_z = program.new_instruction(Operator::Iadd, Type::i64(), vec![], vec![y, z]);
let iadd = program.new_instruction(Operator::Iadd, Type::i64(), vec![], vec![x, iadd_y_z]);
let iadd_y_z = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![y, z]);
let iadd = program.new_instruction(TestOperator::Iadd, Type::i64(), vec![], vec![x, iadd_y_z]);
let iadd_imm = program.new_instruction(
Operator::IaddImm,
TestOperator::IaddImm,
Type::i64(),
vec![Constant::Int(22, BitWidth::SixtyFour).into()],
vec![iadd_y_z],
@@ -316,19 +316,19 @@ fn subsumption() {
log::debug!("(iadd (imul_imm x 1) (imul_imm x 1)) => (ishl_imm 1 (imul_imm x 1))");
let imul_imm = program.new_instruction(
Operator::ImulImm,
TestOperator::ImulImm,
Type::i64(),
vec![Constant::Int(1, BitWidth::SixtyFour).into()],
vec![x],
);
let iadd = program.new_instruction(
Operator::Iadd,
TestOperator::Iadd,
Type::i64(),
vec![],
vec![imul_imm, imul_imm],
);
let ishl_imm = program.new_instruction(
Operator::IshlImm,
TestOperator::IshlImm,
Type::i64(),
vec![Constant::Int(1, BitWidth::SixtyFour).into()],
vec![imul_imm],
@@ -339,10 +339,10 @@ fn subsumption() {
log::debug!("(iadd (imul w x) (imul y z)) does not match any optimization.");
let imul_w_x = program.new_instruction(Operator::Imul, Type::i64(), vec![], vec![w, x]);
let imul_y_z = program.new_instruction(Operator::Imul, Type::i64(), vec![], vec![y, z]);
let imul_w_x = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![w, x]);
let imul_y_z = program.new_instruction(TestOperator::Imul, Type::i64(), vec![], vec![y, z]);
let iadd = program.new_instruction(
Operator::Iadd,
TestOperator::Iadd,
Type::i64(),
vec![],
vec![imul_w_x, imul_y_z],
@@ -363,9 +363,9 @@ fn polymorphic_bit_widths() {
let x = program.r#const(Constant::Int(42, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let y = program.r#const(Constant::Int(420, BitWidth::ThirtyTwo), BitWidth::ThirtyTwo);
let iadd = program.new_instruction(Operator::Iadd, Type::i32(), vec![], vec![x, y]);
let iadd = program.new_instruction(TestOperator::Iadd, Type::i32(), vec![], vec![x, y]);
let iadd_imm = program.new_instruction(
Operator::IaddImm,
TestOperator::IaddImm,
Type::i32(),
vec![Constant::Int(42, BitWidth::ThirtyTwo).into()],
vec![y],
@@ -379,9 +379,9 @@ fn polymorphic_bit_widths() {
let x = program.r#const(Constant::Int(42, BitWidth::Sixteen), BitWidth::Sixteen);
let y = program.r#const(Constant::Int(420, BitWidth::Sixteen), BitWidth::Sixteen);
let iadd = program.new_instruction(Operator::Iadd, Type::i16(), vec![], vec![x, y]);
let iadd = program.new_instruction(TestOperator::Iadd, Type::i16(), vec![], vec![x, y]);
let iadd_imm = program.new_instruction(
Operator::IaddImm,
TestOperator::IaddImm,
Type::i16(),
vec![Constant::Int(42, BitWidth::Sixteen).into()],
vec![y],

View File

@@ -0,0 +1,9 @@
[package]
name = "peepmatic-traits"
version = "0.66.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]

View File

@@ -0,0 +1,26 @@
//! Shared traits, types, and macros for Peepmatic.
//!
//! This crate is used both at build time when constructing peephole optimizers
//! (i.e. in the `peepmatic` crate), and at run time when using pre-built
//! peephole optimizers (i.e. in the `peepmatic-runtime` crate and in
//! Cranelift's Peepmatic integration at `cranelift/codegen/src/peepmatic.rs`).
//!
//! This crate is similar to a header file: it should generally only contain
//! trait/type/macro definitions, not any code.
#![deny(missing_docs)]
#![deny(missing_debug_implementations)]
#[macro_use]
mod operator;
pub use operator::*;
mod typing;
pub use typing::*;
/// Raise a panic about an unsupported operation.
#[cold]
#[inline(never)]
pub fn unsupported(msg: &str) -> ! {
panic!("unsupported: {}", msg)
}

View File

@@ -0,0 +1,317 @@
/// Define a `wast::parser::Parse` implementation for an operator type.
#[macro_export]
macro_rules! define_parse_impl_for_operator {
(
$operator:ident {
$(
$keyword:ident => $variant:ident;
)*
}
) => {
impl<'a> wast::parser::Parse<'a> for $operator {
fn parse(p: wast::parser::Parser<'a>) -> wast::parser::Result<$operator> {
/// Token definitions for our `Opcode` keywords.
mod tok {
$(
wast::custom_keyword!($keyword);
)*
}
// Peek at the next token, and if it is the variant's
// keyword, then consume it with `parse`, and finally return
// the `Opcode` variant.
$(
if p.peek::<tok::$keyword>() {
p.parse::<tok::$keyword>()?;
return Ok(Self::$variant);
}
)*
// If none of the keywords matched, then we get a parse error.
Err(p.error(concat!("expected `", stringify!($operator), "`")))
}
}
}
}
/// Define a `peepmatic_traits::TypingRules` implementation for the given
/// operator type.
#[macro_export]
macro_rules! define_typing_rules_impl_for_operator {
(
$operator:ident {
$(
$variant:ident {
$( immediates( $($immediate:ident),* ); )?
$( parameters( $($parameter:ident),* ); )?
result( $result:ident );
$( is_reduce($is_reduce:expr); )?
$( is_extend($is_extend:expr); )?
}
)*
}
) => {
impl $crate::TypingRules for $operator {
fn result_type<'a, C>(
&self,
span: C::Span,
typing_context: &mut C,
) -> C::TypeVariable
where
C: $crate::TypingContext<'a> {
match self {
$(
Self::$variant => typing_context.$result(span),
)*
#[allow(dead_code)]
_ => $crate::unsupported("no typing rules defined for variant"),
}
}
fn immediates_arity(&self) -> u8 {
match self {
$(
Self::$variant => $crate::define_typing_rules_impl_for_operator!(
@arity;
$( $( $immediate, )* )?
),
)*
#[allow(dead_code)]
_ => $crate::unsupported("no typing rules defined for variant"),
}
}
fn immediate_types<'a, C>(
&self,
span: C::Span,
typing_context: &mut C,
types: &mut impl Extend<C::TypeVariable>,
)
where
C: $crate::TypingContext<'a>
{
match self {
$(
Self::$variant => types.extend(
None.into_iter()
$(
$(
.chain(Some(typing_context.$immediate(span)))
)*
)?
),
)*
#[allow(dead_code)]
_ => $crate::unsupported("no typing rules defined for variant"),
}
}
fn parameters_arity(&self) -> u8 {
match self {
$(
Self::$variant => $crate::define_typing_rules_impl_for_operator!(
@arity;
$( $( $parameter, )* )?
),
)*
#[allow(dead_code)]
_ => $crate::unsupported("no typing rules defined for variant"),
}
}
fn parameter_types<'a, C>(
&self,
span: C::Span,
typing_context: &mut C,
types: &mut impl Extend<C::TypeVariable>,
)
where
C: $crate::TypingContext<'a>
{
match self {
$(
Self::$variant => types.extend(
None.into_iter()
$(
$(
.chain(Some(typing_context.$parameter(span)))
)*
)?
),
)*
#[allow(dead_code)]
_ => $crate::unsupported("no typing rules defined for variant"),
}
}
fn is_reduce(&self) -> bool {
match self {
$(
Self::$variant if false $( || $is_reduce )? => false $( || $is_reduce )?,
)*
_ => false,
}
}
fn is_extend(&self) -> bool {
match self {
$(
Self::$variant if false $( || $is_extend )? => false $( || $is_extend )?,
)*
_ => false,
}
}
}
};
// Base case: zero arity.
(
@arity;
) => {
0
};
// Recursive case: count one for the head and add that to the arity of the
// rest.
(
@arity;
$head:ident,
$( $rest:ident, )*
) => {
1 + $crate::define_typing_rules_impl_for_operator!(
@arity;
$( $rest, )*
)
}
}
/// Define both a `wast::parser::Parse` implementation and a
/// `peepmatic_traits::TypingRules` implementation for the given operator type.
#[macro_export]
macro_rules! define_parse_and_typing_rules_for_operator {
(
$operator:ident {
$(
$keyword:ident => $variant:ident {
$( immediates( $($immediate:ident),* ); )?
$( parameters( $($parameter:ident),* ); )?
result( $result:ident );
$( is_reduce($is_reduce:expr); )?
$( is_extend($is_extend:expr); )?
}
)*
}
$( parse_cfg($parse_cfg:meta); )?
) => {
$( #[cfg($parse_cfg)] )?
$crate::define_parse_impl_for_operator! {
$operator {
$(
$keyword => $variant;
)*
}
}
$crate::define_typing_rules_impl_for_operator! {
$operator {
$(
$variant {
$( immediates( $($immediate),* ); )?
$( parameters( $($parameter),* ); )?
result( $result );
$( is_reduce($is_reduce); )?
$( is_extend($is_extend); )?
}
)*
}
}
}
}
/// Define an operator type, as well as its parsing and typing rules.
#[macro_export]
macro_rules! define_operator {
(
$( #[$attr:meta] )*
$operator:ident {
$(
$keywrord:ident => $variant:ident {
$( immediates( $($immediate:ident),* ); )?
$( parameters( $($parameter:ident),* ); )?
result( $result:ident );
$( is_reduce($is_reduce:expr); )?
$( is_extend($is_extend:expr); )?
}
)*
}
$( parse_cfg($parse_cfg:meta); )?
) => {
$( #[$attr] )*
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[repr(u32)]
pub enum $operator {
$(
$variant,
)*
}
impl From<$operator> for u32 {
#[inline]
fn from(x: $operator) -> u32 {
x as u32
}
}
impl From<$operator> for core::num::NonZeroU32 {
#[inline]
fn from(x: $operator) -> core::num::NonZeroU32 {
let x: u32 = x.into();
core::num::NonZeroU32::new(x.checked_add(1).unwrap()).unwrap()
}
}
impl core::convert::TryFrom<u32> for $operator {
type Error = ();
#[inline]
fn try_from(x: u32) -> Result<Self, ()> {
match x {
$(
x if x == Self::$variant.into() => Ok(Self::$variant),
)*
_ => Err(())
}
}
}
impl core::convert::TryFrom<core::num::NonZeroU32> for $operator {
type Error = ();
#[inline]
fn try_from(x: core::num::NonZeroU32) -> Result<Self, ()> {
let x = x.get().checked_sub(1).ok_or(())?;
Self::try_from(x)
}
}
$crate::define_parse_and_typing_rules_for_operator! {
$operator {
$(
$keywrord => $variant {
$( immediates( $($immediate),* ); )?
$( parameters( $($parameter),* ); )?
result( $result );
$( is_reduce($is_reduce); )?
$( is_extend($is_extend); )?
}
)*
}
$( parse_cfg($parse_cfg); )?
}
}
}

View File

@@ -0,0 +1,97 @@
/// 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`.
pub trait TypingContext<'a> {
/// A source span.
type Span: Copy;
/// A type variable.
type TypeVariable;
/// Create a condition code type.
fn cc(&mut self, span: Self::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: Self::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: Self::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: Self::Span) -> Self::TypeVariable;
/// Create the CPU flags type variable.
fn cpu_flags(&mut self, span: Self::Span) -> Self::TypeVariable;
/// Create a boolean type of size one bit.
fn b1(&mut self, span: Self::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: Self::Span) -> Self::TypeVariable;
/// Create a type variable that may be either a boolean or an integer.
fn bool_or_int(&mut self, span: Self::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: Self::Span) -> Self::TypeVariable;
}
/// The typing rules for a `TOperator` type.
///
/// This trait describes the types of immediates, parameters, and results of an
/// operator type, as well as their arity.
pub trait TypingRules {
/// Get the result type of this operator.
fn result_type<'a, C>(&self, span: C::Span, typing_context: &mut C) -> C::TypeVariable
where
C: TypingContext<'a>;
/// Get the number of immediates this operator has.
fn immediates_arity(&self) -> u8;
/// Get the types of this operator's immediates.
fn immediate_types<'a, C>(
&self,
span: C::Span,
typing_context: &mut C,
types: &mut impl Extend<C::TypeVariable>,
) where
C: TypingContext<'a>;
/// Get the number of parameters this operator has.
fn parameters_arity(&self) -> u8;
/// Get the types of this operator's parameters.
fn parameter_types<'a, C>(
&self,
span: C::Span,
typing_context: &mut C,
types: &mut impl Extend<C::TypeVariable>,
) where
C: TypingContext<'a>;
/// Is this a bit width reducing instruction?
///
/// E.g. Cranelift's `ireduce` instruction.
fn is_reduce(&self) -> bool;
/// Is this a bit width extending instruction?
///
/// E.g. Cranelift's `uextend` and `sextend` instructions.
fn is_extend(&self) -> bool;
}