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:
26
cranelift/peepmatic/crates/traits/src/lib.rs
Normal file
26
cranelift/peepmatic/crates/traits/src/lib.rs
Normal 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)
|
||||
}
|
||||
317
cranelift/peepmatic/crates/traits/src/operator.rs
Normal file
317
cranelift/peepmatic/crates/traits/src/operator.rs
Normal 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); )?
|
||||
}
|
||||
}
|
||||
}
|
||||
97
cranelift/peepmatic/crates/traits/src/typing.rs
Normal file
97
cranelift/peepmatic/crates/traits/src/typing.rs
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user