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:
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user