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

@@ -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));
}
}