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

@@ -163,10 +163,16 @@ jobs:
--package peepmatic-runtime \
--package peepmatic-test
- name: Rebuild Peepmatic-based peephole optimizers and test them
run: cargo test --features 'enable-peepmatic cranelift-codegen/rebuild-peephole-optimizers'
run: |
cargo test \
--features 'enable-peepmatic cranelift-codegen/rebuild-peephole-optimizers' \
peepmatic
working-directory: ./cranelift
- name: Check that peephole optimizers are up to date
- name: Check that built peephole optimizers are up to date
run: git diff --exit-code
- name: Test with Peepmatic-based peephole optimizers
run: cargo test --features 'enable-peepmatic'
working-directory: ./cranelift
# Perform all tests (debug mode) for `wasmtime`. This runs stable/beta/nightly
# channels of Rust as well as macOS/Linux/Windows.

33
Cargo.lock generated
View File

@@ -386,11 +386,13 @@ dependencies = [
"log",
"peepmatic",
"peepmatic-runtime",
"peepmatic-traits",
"regalloc",
"serde",
"smallvec",
"target-lexicon",
"thiserror",
"wast 15.0.0",
]
[[package]]
@@ -1298,20 +1300,23 @@ dependencies = [
"peepmatic-automata",
"peepmatic-macro",
"peepmatic-runtime",
"peepmatic-test-operator",
"peepmatic-traits",
"serde",
"wast 15.0.0",
"z3",
]
[[package]]
name = "peepmatic-automata"
version = "0.2.0"
version = "0.66.0"
dependencies = [
"serde",
]
[[package]]
name = "peepmatic-fuzzing"
version = "0.2.0"
version = "0.66.0"
dependencies = [
"arbitrary",
"bincode",
@@ -1322,6 +1327,8 @@ dependencies = [
"peepmatic-automata",
"peepmatic-runtime",
"peepmatic-test",
"peepmatic-test-operator",
"peepmatic-traits",
"rand 0.7.3",
"serde",
"wast 15.0.0",
@@ -1329,7 +1336,7 @@ dependencies = [
[[package]]
name = "peepmatic-macro"
version = "0.2.0"
version = "0.66.0"
dependencies = [
"proc-macro2",
"quote",
@@ -1338,13 +1345,14 @@ dependencies = [
[[package]]
name = "peepmatic-runtime"
version = "0.2.0"
version = "0.66.0"
dependencies = [
"bincode",
"bumpalo",
"log",
"peepmatic-automata",
"peepmatic-macro",
"peepmatic-test-operator",
"peepmatic-traits",
"serde",
"serde_test",
"thiserror",
@@ -1359,8 +1367,23 @@ dependencies = [
"log",
"peepmatic",
"peepmatic-runtime",
"peepmatic-test-operator",
"peepmatic-traits",
]
[[package]]
name = "peepmatic-test-operator"
version = "0.66.0"
dependencies = [
"peepmatic-traits",
"serde",
"wast 15.0.0",
]
[[package]]
name = "peepmatic-traits"
version = "0.66.0"
[[package]]
name = "plain"
version = "0.2.3"

View File

@@ -26,8 +26,10 @@ smallvec = { version = "1.0.0" }
thiserror = "1.0.4"
byteorder = { version = "1.3.2", default-features = false }
peepmatic = { path = "../peepmatic", optional = true, version = "0.66.0" }
peepmatic-runtime = { path = "../peepmatic/crates/runtime", optional = true, version = "0.2.0" }
regalloc = { version = "0.0.28" }
peepmatic-traits = { path = "../peepmatic/crates/traits", optional = true, version = "0.66.0" }
peepmatic-runtime = { path = "../peepmatic/crates/runtime", optional = true, version = "0.66.0" }
regalloc = "0.0.28"
wast = { version = "15.0.0", optional = true }
# It is a goal of the cranelift-codegen crate to have minimal external dependencies.
# Please don't add any unless they are essential to the task of creating binary
# machine code. Integration tests that need external dependencies can be
@@ -80,10 +82,10 @@ regalloc-snapshot = ["bincode", "regalloc/enable-serde"]
# Recompile our optimizations that are written in the `peepmatic` DSL into a
# compact finite-state transducer automaton.
rebuild-peephole-optimizers = ["peepmatic"]
rebuild-peephole-optimizers = ["peepmatic", "peepmatic-traits", "wast"]
# Enable the use of `peepmatic`-generated peephole optimizers.
enable-peepmatic = ["peepmatic-runtime"]
enable-peepmatic = ["peepmatic-runtime", "peepmatic-traits", "serde"]
[badges]
maintenance = { status = "experimental" }

View File

@@ -407,7 +407,11 @@ fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) {
All instructions from all supported ISAs are present.
"#,
);
fmt.line("#[repr(u16)]");
fmt.line("#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash)]");
fmt.line(
r#"#[cfg_attr(feature = "enable-peepmatic", derive(serde::Serialize, serde::Deserialize))]"#
);
// We explicitly set the discriminant of the first variant to 1, which allows us to take
// advantage of the NonZero optimization, meaning that wrapping enums can use the 0
@@ -589,6 +593,24 @@ fn gen_opcodes(all_inst: &AllInstructions, fmt: &mut Formatter) {
fmt.empty_line();
}
fn gen_try_from(all_inst: &AllInstructions, fmt: &mut Formatter) {
fmt.line("impl core::convert::TryFrom<u16> for Opcode {");
fmt.indent(|fmt| {
fmt.line("type Error = ();");
fmt.line("#[inline]");
fmt.line("fn try_from(x: u16) -> Result<Self, ()> {");
fmt.indent(|fmt| {
fmtln!(fmt, "if 0 < x && x <= {} {{", all_inst.len());
fmt.indent(|fmt| fmt.line("Ok(unsafe { core::mem::transmute(x) })"));
fmt.line("} else {");
fmt.indent(|fmt| fmt.line("Err(())"));
fmt.line("}");
});
fmt.line("}");
});
fmt.line("}");
}
/// Get the value type constraint for an SSA value operand, where
/// `ctrl_typevar` is the controlling type variable.
///
@@ -1147,7 +1169,10 @@ pub(crate) fn generate(
gen_instruction_data_impl(&formats, &mut fmt);
fmt.empty_line();
gen_opcodes(all_inst, &mut fmt);
fmt.empty_line();
gen_type_constraints(all_inst, &mut fmt);
fmt.empty_line();
gen_try_from(all_inst, &mut fmt);
fmt.update_file(opcode_filename, out_dir)?;
// Instruction builder.

View File

@@ -7,7 +7,9 @@
//! directory.
use alloc::vec::Vec;
use core::convert::{TryFrom, TryInto};
use core::fmt::{self, Display, Formatter};
use core::num::NonZeroU32;
use core::ops::{Deref, DerefMut};
use core::str::FromStr;
@@ -69,6 +71,24 @@ impl Opcode {
}
}
impl TryFrom<NonZeroU32> for Opcode {
type Error = ();
#[inline]
fn try_from(x: NonZeroU32) -> Result<Self, ()> {
let x: u16 = x.get().try_into().map_err(|_| ())?;
Self::try_from(x)
}
}
impl From<Opcode> for NonZeroU32 {
#[inline]
fn from(op: Opcode) -> NonZeroU32 {
let x = op as u8;
NonZeroU32::new(x as u32).unwrap()
}
}
// This trait really belongs in cranelift-reader where it is used by the `.clif` file parser, but since
// it critically depends on the `opcode_name()` function which is needed here anyway, it lives in
// this module. This also saves us from running the build script twice to generate code for the two

View File

@@ -13,55 +13,287 @@ use cranelift_codegen_shared::condcodes::IntCC;
use peepmatic_runtime::{
cc::ConditionCode,
instruction_set::InstructionSet,
operator::Operator,
part::{Constant, Part},
paths::Path,
r#type::{BitWidth, Kind, Type},
PeepholeOptimizations, PeepholeOptimizer,
};
use peepmatic_traits::TypingRules;
use std::borrow::Cow;
use std::boxed::Box;
use std::convert::{TryFrom, TryInto};
use std::ptr;
use std::sync::atomic::{AtomicPtr, Ordering};
peepmatic_traits::define_parse_and_typing_rules_for_operator! {
Opcode {
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);
}
}
parse_cfg(feature = "rebuild-peephole-optimizers");
}
/// Code required to rebuild Peepmatic-based peephole optimizers.
///
/// This module is used to scope imports and dependencies that are only required
/// for building peephole optimizers (as opposed to just using pre-built
/// peephole optimizers). This helps ensure that our regular builds using
/// pre-built peephole optimizers stay lean.
#[cfg(feature = "rebuild-peephole-optimizers")]
mod rebuild {
use super::*;
use alloc::vec::Vec;
use std::fs;
use std::path::Path;
/// Rebuild the `preopt.peepmatic` peephole optimizer.
///
/// Saves and overwrites the old `preopt.serialized` build and returns a
/// copy of the result.
pub fn rebuild_preopt() -> Vec<u8> {
let codegen_path = Path::new(include_str!(concat!(
env!("OUT_DIR"),
"/CRANELIFT_CODEGEN_PATH"
)));
let source_path = codegen_path.join("src").join("preopt.peepmatic");
let preopt = peepmatic::compile_file::<Opcode>(&source_path)
.expect("failed to compile `src/preopt.peepmatic`");
let serialized_path = codegen_path.join("src").join("preopt.serialized");
preopt
.serialize_to_file(&serialized_path)
.expect("failed to serialize peephole optimizer to `src/preopt.serialized`");
fs::read(&serialized_path).expect("failed to read `src/preopt.serialized`")
}
}
/// Get the `preopt.peepmatic` peephole optimizer.
pub(crate) fn preopt<'a, 'b>(
isa: &'b dyn TargetIsa,
) -> PeepholeOptimizer<'static, 'a, &'b dyn TargetIsa> {
#[cfg(feature = "rebuild-peephole-optimizers")]
fn get_serialized() -> Cow<'static, [u8]> {
use std::fs;
use std::path::Path;
let codegen_path = Path::new(include_str!(concat!(
env!("OUT_DIR"),
"/CRANELIFT_CODEGEN_PATH"
)));
let source_path = codegen_path.join("src").join("preopt.peepmatic");
println!("cargo:rerun-if-changed={}", source_path.display());
let preopt = peepmatic::compile_file(&source_path)
.expect("failed to compile `src/preopt.peepmatic`");
let serialized_path = codegen_path.join("src").join("preopt.serialized");
preopt
.serialize_to_file(&serialized_path)
.expect("failed to serialize peephole optimizer to `src/preopt.serialized`");
fs::read(&serialized_path)
.expect("failed to read `src/preopt.serialized`")
.into()
rebuild::rebuild_preopt().into()
}
#[cfg(not(feature = "rebuild-peephole-optimizers"))]
fn get_serialized() -> Cow<'static, [u8]> {
static SERIALIZED: &[u8] = include_bytes!("preopt.serialized");
SERIALIZED.into()
}
// Once initialized, this must never be re-assigned. The initialized value
// is semantically "static data" and is intentionally leaked for the whole
// program's lifetime.
static DESERIALIZED: AtomicPtr<PeepholeOptimizations> = AtomicPtr::new(ptr::null_mut());
static DESERIALIZED: AtomicPtr<PeepholeOptimizations<Opcode>> = AtomicPtr::new(ptr::null_mut());
// If `DESERIALIZED` has already been initialized, then just use it.
let ptr = DESERIALIZED.load(Ordering::SeqCst);
@@ -247,70 +479,6 @@ fn part_to_value(pos: &mut FuncCursor, root: Inst, part: Part<ValueOrInst>) -> O
}
}
impl Opcode {
fn to_peepmatic_operator(&self) -> Option<Operator> {
macro_rules! convert {
( $( $op:ident $(,)* )* ) => {
match self {
$( Self::$op => Some(Operator::$op), )*
_ => None,
}
}
}
convert!(
AdjustSpDown,
AdjustSpDownImm,
Band,
BandImm,
Bconst,
Bint,
Bnot,
Bor,
BorImm,
Brnz,
Brz,
Bxor,
BxorImm,
Iadd,
IaddImm,
Icmp,
IcmpImm,
Iconst,
Ifcmp,
IfcmpImm,
Imul,
ImulImm,
Ireduce,
IrsubImm,
Ishl,
IshlImm,
Isub,
Rotl,
RotlImm,
Rotr,
RotrImm,
Sdiv,
SdivImm,
Select,
Sextend,
Srem,
SremImm,
Sshr,
SshrImm,
Trapnz,
Trapz,
Udiv,
UdivImm,
Uextend,
Urem,
UremImm,
Ushr,
UshrImm,
)
}
}
impl TryFrom<Constant> for Imm64 {
type Error = &'static str;
@@ -457,6 +625,8 @@ fn peepmatic_ty_to_ir_ty(ty: Type, dfg: &DataFlowGraph, root: Inst) -> types::Ty
unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
type Context = FuncCursor<'b>;
type Operator = Opcode;
type Instruction = ValueOrInst;
fn replace_instruction(
@@ -524,7 +694,7 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
let mut part = Part::Instruction(root);
for p in path.0[1..].iter().copied() {
let inst = part.as_instruction()?.resolve_inst(&pos.func.dfg)?;
let operator = pos.func.dfg[inst].opcode().to_peepmatic_operator()?;
let operator = pos.func.dfg[inst].opcode();
if p < operator.immediates_arity() {
part = get_immediate(&pos.func.dfg, inst, p as usize);
@@ -541,16 +711,16 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
Some(part)
}
fn operator(&self, pos: &mut FuncCursor<'b>, value_or_inst: ValueOrInst) -> Option<Operator> {
fn operator(&self, pos: &mut FuncCursor<'b>, value_or_inst: ValueOrInst) -> Option<Opcode> {
let inst = value_or_inst.resolve_inst(&pos.func.dfg)?;
pos.func.dfg[inst].opcode().to_peepmatic_operator()
Some(pos.func.dfg[inst].opcode())
}
fn make_inst_1(
&self,
pos: &mut FuncCursor<'b>,
root: ValueOrInst,
operator: Operator,
operator: Opcode,
r#type: Type,
a: Part<ValueOrInst>,
) -> ValueOrInst {
@@ -558,32 +728,32 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
let root = root.resolve_inst(&pos.func.dfg).unwrap();
match operator {
Operator::AdjustSpDown => {
Opcode::AdjustSpDown => {
let a = part_to_value(pos, root, a).unwrap();
pos.ins().adjust_sp_down(a).into()
}
Operator::AdjustSpDownImm => {
Opcode::AdjustSpDownImm => {
let c = a.unwrap_constant();
let imm = Imm64::try_from(c).unwrap();
pos.ins().adjust_sp_down_imm(imm).into()
}
Operator::Bconst => {
Opcode::Bconst => {
let c = a.unwrap_constant();
let val = const_to_value(pos.ins(), c, root);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Bint => {
Opcode::Bint => {
let a = part_to_value(pos, root, a).unwrap();
let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root);
let val = pos.ins().bint(ty, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Bnot => {
Opcode::Bnot => {
let a = part_to_value(pos, root, a).unwrap();
let val = pos.ins().bnot(a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Brnz => {
Opcode::Brnz => {
let a = part_to_value(pos, root, a).unwrap();
// NB: branching instructions must be the root of an
@@ -595,37 +765,37 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
pos.ins().brnz(a, block, &args).into()
}
Operator::Brz => {
Opcode::Brz => {
let a = part_to_value(pos, root, a).unwrap();
// See the comment in the `Operator::Brnz` match argm.
// See the comment in the `Opcode::Brnz` match argm.
let block = pos.func.dfg[root].branch_destination().unwrap();
let args = pos.func.dfg.inst_args(root)[1..].to_vec();
pos.ins().brz(a, block, &args).into()
}
Operator::Iconst => {
Opcode::Iconst => {
let a = a.unwrap_constant();
let val = const_to_value(pos.ins(), a, root);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Ireduce => {
Opcode::Ireduce => {
let a = part_to_value(pos, root, a).unwrap();
let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root);
let val = pos.ins().ireduce(ty, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Sextend => {
Opcode::Sextend => {
let a = part_to_value(pos, root, a).unwrap();
let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root);
let val = pos.ins().sextend(ty, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Trapnz => {
Opcode::Trapnz => {
let a = part_to_value(pos, root, a).unwrap();
// NB: similar to branching instructions (see comment in the
// `Operator::Brnz` match arm) trapping instructions must be the
// `Opcode::Brnz` match arm) trapping instructions must be the
// root of an optimization's right-hand side, and we get the
// trap code from the root of the left-hand side. Peepmatic
// doesn't currently represent trap codes.
@@ -633,13 +803,13 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
pos.ins().trapnz(a, code).into()
}
Operator::Trapz => {
Opcode::Trapz => {
let a = part_to_value(pos, root, a).unwrap();
// See comment in the `Operator::Trapnz` match arm.
// See comment in the `Opcode::Trapnz` match arm.
let code = pos.func.dfg[root].trap_code().unwrap();
pos.ins().trapz(a, code).into()
}
Operator::Uextend => {
Opcode::Uextend => {
let a = part_to_value(pos, root, a).unwrap();
let ty = peepmatic_ty_to_ir_ty(r#type, &pos.func.dfg, root);
let val = pos.ins().uextend(ty, a);
@@ -653,7 +823,7 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
&self,
pos: &mut FuncCursor<'b>,
root: ValueOrInst,
operator: Operator,
operator: Opcode,
_: Type,
a: Part<ValueOrInst>,
b: Part<ValueOrInst>,
@@ -662,193 +832,193 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
let root = root.resolve_inst(&pos.func.dfg).unwrap();
match operator {
Operator::Band => {
Opcode::Band => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().band(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::BandImm => {
Opcode::BandImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().band_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Bor => {
Opcode::Bor => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().bor(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::BorImm => {
Opcode::BorImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().bor_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Bxor => {
Opcode::Bxor => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().bxor(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::BxorImm => {
Opcode::BxorImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().bxor_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Iadd => {
Opcode::Iadd => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().iadd(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::IaddImm => {
Opcode::IaddImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().iadd_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Ifcmp => {
Opcode::Ifcmp => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().ifcmp(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::IfcmpImm => {
Opcode::IfcmpImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().ifcmp_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Imul => {
Opcode::Imul => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().imul(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::ImulImm => {
Opcode::ImulImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().imul_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::IrsubImm => {
Opcode::IrsubImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().irsub_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Ishl => {
Opcode::Ishl => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().ishl(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::IshlImm => {
Opcode::IshlImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().ishl_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Isub => {
Opcode::Isub => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().isub(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Rotl => {
Opcode::Rotl => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().rotl(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::RotlImm => {
Opcode::RotlImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().rotl_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Rotr => {
Opcode::Rotr => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().rotr(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::RotrImm => {
Opcode::RotrImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().rotr_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Sdiv => {
Opcode::Sdiv => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().sdiv(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::SdivImm => {
Opcode::SdivImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().sdiv_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Srem => {
Opcode::Srem => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().srem(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::SremImm => {
Opcode::SremImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().srem_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Sshr => {
Opcode::Sshr => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().sshr(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::SshrImm => {
Opcode::SshrImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().sshr_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Udiv => {
Opcode::Udiv => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().udiv(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::UdivImm => {
Opcode::UdivImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().udiv_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Urem => {
Opcode::Urem => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().urem(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::UremImm => {
Opcode::UremImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().urem_imm(b, a);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Ushr => {
Opcode::Ushr => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().ushr(a, b);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::UshrImm => {
Opcode::UshrImm => {
let a = part_to_imm64(pos, a);
let b = part_to_value(pos, root, b).unwrap();
let val = pos.ins().ushr_imm(b, a);
@@ -862,7 +1032,7 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
&self,
pos: &mut FuncCursor<'b>,
root: ValueOrInst,
operator: Operator,
operator: Opcode,
_: Type,
a: Part<ValueOrInst>,
b: Part<ValueOrInst>,
@@ -872,7 +1042,7 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
let root = root.resolve_inst(&pos.func.dfg).unwrap();
match operator {
Operator::Icmp => {
Opcode::Icmp => {
let cond = a.unwrap_condition_code();
let cond = peepmatic_to_intcc(cond);
let b = part_to_value(pos, root, b).unwrap();
@@ -880,7 +1050,7 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
let val = pos.ins().icmp(cond, b, c);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::IcmpImm => {
Opcode::IcmpImm => {
let cond = a.unwrap_condition_code();
let cond = peepmatic_to_intcc(cond);
let imm = part_to_imm64(pos, b);
@@ -888,7 +1058,7 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
let val = pos.ins().icmp_imm(cond, c, imm);
pos.func.dfg.value_def(val).unwrap_inst().into()
}
Operator::Select => {
Opcode::Select => {
let a = part_to_value(pos, root, a).unwrap();
let b = part_to_value(pos, root, b).unwrap();
let c = part_to_value(pos, root, c).unwrap();

View File

@@ -10,8 +10,13 @@ description = "DSL and compiler for generating peephole optimizers"
[dependencies]
anyhow = "1.0.27"
peepmatic-automata = { version = "0.2.0", path = "crates/automata", features = ["dot"] }
peepmatic-macro = { version = "0.2.0", path = "crates/macro" }
peepmatic-runtime = { version = "0.2.0", path = "crates/runtime", features = ["construct"] }
peepmatic-automata = { version = "0.66.0", path = "crates/automata", features = ["dot"] }
peepmatic-macro = { version = "0.66.0", path = "crates/macro" }
peepmatic-runtime = { version = "0.66.0", path = "crates/runtime", features = ["construct"] }
peepmatic-traits = { version = "0.66.0", path = "crates/traits" }
serde = { version = "1.0.105", features = ["derive"] }
wast = "15.0.0"
z3 = { version = "0.6.0", features = ["static-link-z3"] }
[dev-dependencies]
peepmatic-test-operator = { version = "0.66.0", path = "crates/test-operator" }

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

View File

@@ -22,8 +22,8 @@
use peepmatic_macro::Ast;
use peepmatic_runtime::{
operator::{Operator, UnquoteOperator},
r#type::{BitWidth, Type},
unquote::UnquoteOperator,
};
use std::cell::Cell;
use std::hash::{Hash, Hasher};
@@ -32,58 +32,58 @@ use wast::Id;
/// A reference to any AST node.
#[derive(Debug, Clone, Copy)]
pub enum DynAstRef<'a> {
pub enum DynAstRef<'a, TOperator> {
/// A reference to an `Optimizations`.
Optimizations(&'a Optimizations<'a>),
Optimizations(&'a Optimizations<'a, TOperator>),
/// A reference to an `Optimization`.
Optimization(&'a Optimization<'a>),
Optimization(&'a Optimization<'a, TOperator>),
/// A reference to an `Lhs`.
Lhs(&'a Lhs<'a>),
Lhs(&'a Lhs<'a, TOperator>),
/// A reference to an `Rhs`.
Rhs(&'a Rhs<'a>),
Rhs(&'a Rhs<'a, TOperator>),
/// A reference to a `Pattern`.
Pattern(&'a Pattern<'a>),
Pattern(&'a Pattern<'a, TOperator>),
/// A reference to a `Precondition`.
Precondition(&'a Precondition<'a>),
Precondition(&'a Precondition<'a, TOperator>),
/// A reference to a `ConstraintOperand`.
ConstraintOperand(&'a ConstraintOperand<'a>),
ConstraintOperand(&'a ConstraintOperand<'a, TOperator>),
/// A reference to a `ValueLiteral`.
ValueLiteral(&'a ValueLiteral<'a>),
ValueLiteral(&'a ValueLiteral<'a, TOperator>),
/// A reference to a `Constant`.
Constant(&'a Constant<'a>),
Constant(&'a Constant<'a, TOperator>),
/// A reference to a `PatternOperation`.
PatternOperation(&'a Operation<'a, Pattern<'a>>),
PatternOperation(&'a Operation<'a, TOperator, Pattern<'a, TOperator>>),
/// A reference to a `Variable`.
Variable(&'a Variable<'a>),
Variable(&'a Variable<'a, TOperator>),
/// A reference to an `Integer`.
Integer(&'a Integer<'a>),
Integer(&'a Integer<'a, TOperator>),
/// A reference to a `Boolean`.
Boolean(&'a Boolean<'a>),
Boolean(&'a Boolean<'a, TOperator>),
/// A reference to a `ConditionCode`.
ConditionCode(&'a ConditionCode<'a>),
ConditionCode(&'a ConditionCode<'a, TOperator>),
/// A reference to an `Unquote`.
Unquote(&'a Unquote<'a>),
Unquote(&'a Unquote<'a, TOperator>),
/// A reference to an `RhsOperation`.
RhsOperation(&'a Operation<'a, Rhs<'a>>),
RhsOperation(&'a Operation<'a, TOperator, Rhs<'a, TOperator>>),
}
impl<'a, 'b> ChildNodes<'a, 'b> for DynAstRef<'a> {
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a>>) {
impl<'a, 'b, TOperator> ChildNodes<'a, 'b, TOperator> for DynAstRef<'a, TOperator> {
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a, TOperator>>) {
match self {
Self::Optimizations(x) => x.child_nodes(sink),
Self::Optimization(x) => x.child_nodes(sink),
@@ -118,23 +118,28 @@ impl<'a, 'b> ChildNodes<'a, 'b> for DynAstRef<'a> {
/// This trait is blanked implemented for everything that does those three
/// things, and in practice those three thrings are all implemented by the
/// `derive(Ast)` macro.
pub trait Ast<'a>: 'a + ChildNodes<'a, 'a> + Span
pub trait Ast<'a, TOperator>: 'a + ChildNodes<'a, 'a, TOperator> + Span
where
DynAstRef<'a>: From<&'a Self>,
DynAstRef<'a, TOperator>: From<&'a Self>,
TOperator: 'a,
{
}
impl<'a, T> Ast<'a> for T
impl<'a, T, TOperator> Ast<'a, TOperator> for T
where
T: 'a + ?Sized + ChildNodes<'a, 'a> + Span,
DynAstRef<'a>: From<&'a Self>,
T: 'a + ?Sized + ChildNodes<'a, 'a, TOperator> + Span,
DynAstRef<'a, TOperator>: From<&'a Self>,
TOperator: 'a,
{
}
/// Enumerate the child AST nodes of a given node.
pub trait ChildNodes<'a, 'b> {
pub trait ChildNodes<'a, 'b, TOperator>
where
TOperator: 'a,
{
/// Get each of this AST node's children, in order.
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a>>);
fn child_nodes(&'b self, sink: &mut impl Extend<DynAstRef<'a, TOperator>>);
}
/// A trait for getting the span where an AST node was defined.
@@ -147,30 +152,30 @@ pub trait Span {
///
/// This is the root AST node.
#[derive(Debug, Ast)]
pub struct Optimizations<'a> {
pub struct Optimizations<'a, TOperator> {
/// Where these `Optimizations` were defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
/// The optimizations.
#[peepmatic(flatten)]
pub optimizations: Vec<Optimization<'a>>,
pub optimizations: Vec<Optimization<'a, TOperator>>,
}
/// A complete optimization: a left-hand side to match against and a right-hand
/// side replacement.
#[derive(Debug, Ast)]
pub struct Optimization<'a> {
pub struct Optimization<'a, TOperator> {
/// Where this `Optimization` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
/// The left-hand side that matches when this optimization applies.
pub lhs: Lhs<'a>,
pub lhs: Lhs<'a, TOperator>,
/// The new sequence of instructions to replace an old sequence that matches
/// the left-hand side with.
pub rhs: Rhs<'a>,
pub rhs: Rhs<'a, TOperator>,
}
/// A left-hand side describes what is required for a particular optimization to
@@ -180,58 +185,58 @@ pub struct Optimization<'a> {
/// candidate instruction sequences, and zero or more preconditions that add
/// additional constraints upon instruction sequences matched by the pattern.
#[derive(Debug, Ast)]
pub struct Lhs<'a> {
pub struct Lhs<'a, TOperator> {
/// Where this `Lhs` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
/// A pattern that describes sequences of instructions to match.
pub pattern: Pattern<'a>,
pub pattern: Pattern<'a, TOperator>,
/// Additional constraints that a match must satisfy in addition to
/// structually matching the pattern, e.g. some constant must be a power of
/// two.
#[peepmatic(flatten)]
pub preconditions: Vec<Precondition<'a>>,
pub preconditions: Vec<Precondition<'a, TOperator>>,
}
/// A structural pattern, potentially with wildcard variables for matching whole
/// subtrees.
#[derive(Debug, Ast)]
pub enum Pattern<'a> {
pub enum Pattern<'a, TOperator> {
/// A specific value. These are written as `1234` or `0x1234` or `true` or
/// `false`.
ValueLiteral(ValueLiteral<'a>),
ValueLiteral(ValueLiteral<'a, TOperator>),
/// A constant that matches any constant value. This subsumes value
/// patterns. These are upper-case identifiers like `$C`.
Constant(Constant<'a>),
Constant(Constant<'a, TOperator>),
/// An operation pattern with zero or more operand patterns. These are
/// s-expressions like `(iadd $x $y)`.
Operation(Operation<'a, Pattern<'a>>),
Operation(Operation<'a, TOperator, Pattern<'a, TOperator>>),
/// A variable that matches any kind of subexpression. This subsumes all
/// other patterns. These are lower-case identifiers like `$x`.
Variable(Variable<'a>),
Variable(Variable<'a, TOperator>),
}
/// An integer or boolean value literal.
#[derive(Debug, Ast)]
pub enum ValueLiteral<'a> {
pub enum ValueLiteral<'a, TOperator> {
/// An integer value.
Integer(Integer<'a>),
Integer(Integer<'a, TOperator>),
/// A boolean value: `true` or `false`.
Boolean(Boolean<'a>),
Boolean(Boolean<'a, TOperator>),
/// A condition code: `eq`, `ne`, etc...
ConditionCode(ConditionCode<'a>),
ConditionCode(ConditionCode<'a, TOperator>),
}
/// An integer literal.
#[derive(Debug, PartialEq, Eq, Ast)]
pub struct Integer<'a> {
pub struct Integer<'a, TOperator> {
/// Where this `Integer` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
@@ -255,10 +260,10 @@ pub struct Integer<'a> {
#[allow(missing_docs)]
#[peepmatic(skip_child)]
pub marker: PhantomData<&'a ()>,
pub marker: PhantomData<&'a TOperator>,
}
impl Hash for Integer<'_> {
impl<TOperator> Hash for Integer<'_, TOperator> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
@@ -278,7 +283,7 @@ impl Hash for Integer<'_> {
/// A boolean literal.
#[derive(Debug, PartialEq, Eq, Ast)]
pub struct Boolean<'a> {
pub struct Boolean<'a, TOperator> {
/// Where this `Boolean` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
@@ -299,10 +304,10 @@ pub struct Boolean<'a> {
#[allow(missing_docs)]
#[peepmatic(skip_child)]
pub marker: PhantomData<&'a ()>,
pub marker: PhantomData<&'a TOperator>,
}
impl Hash for Boolean<'_> {
impl<TOperator> Hash for Boolean<'_, TOperator> {
fn hash<H>(&self, state: &mut H)
where
H: Hasher,
@@ -322,7 +327,7 @@ impl Hash for Boolean<'_> {
/// A condition code.
#[derive(Debug, Ast)]
pub struct ConditionCode<'a> {
pub struct ConditionCode<'a, TOperator> {
/// Where this `ConditionCode` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
@@ -333,7 +338,7 @@ pub struct ConditionCode<'a> {
#[allow(missing_docs)]
#[peepmatic(skip_child)]
pub marker: PhantomData<&'a ()>,
pub marker: PhantomData<&'a TOperator>,
}
/// A symbolic constant.
@@ -341,7 +346,7 @@ pub struct ConditionCode<'a> {
/// These are identifiers containing uppercase letters: `$C`, `$MY-CONST`,
/// `$CONSTANT1`.
#[derive(Debug, Ast)]
pub struct Constant<'a> {
pub struct Constant<'a, TOperator> {
/// Where this `Constant` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
@@ -349,6 +354,10 @@ pub struct Constant<'a> {
/// This constant's identifier.
#[peepmatic(skip_child)]
pub id: Id<'a>,
#[allow(missing_docs)]
#[peepmatic(skip_child)]
pub marker: PhantomData<&'a TOperator>,
}
/// A variable that matches any subtree.
@@ -357,7 +366,7 @@ pub struct Constant<'a> {
/// being the same as each other occurrence as well, e.g. `(iadd $x $x)` matches
/// `(iadd 5 5)` but not `(iadd 1 2)`.
#[derive(Debug, Ast)]
pub struct Variable<'a> {
pub struct Variable<'a, TOperator> {
/// Where this `Variable` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
@@ -365,15 +374,20 @@ pub struct Variable<'a> {
/// This variable's identifier.
#[peepmatic(skip_child)]
pub id: Id<'a>,
#[allow(missing_docs)]
#[peepmatic(skip_child)]
pub marker: PhantomData<&'a TOperator>,
}
/// An operation with an operator, and operands of type `T`.
#[derive(Debug, Ast)]
#[peepmatic(no_into_dyn_node)]
pub struct Operation<'a, T>
pub struct Operation<'a, TOperator, TOperand>
where
T: 'a + Ast<'a>,
DynAstRef<'a>: From<&'a T>,
TOperator: 'a,
TOperand: 'a + Ast<'a, TOperator>,
DynAstRef<'a, TOperator>: From<&'a TOperand>,
{
/// The span where this operation was written.
#[peepmatic(skip_child)]
@@ -381,7 +395,7 @@ where
/// The operator for this operation, e.g. `imul` or `iadd`.
#[peepmatic(skip_child)]
pub operator: Operator,
pub operator: TOperator,
/// An optional ascribed or inferred type for the operator.
#[peepmatic(skip_child)]
@@ -393,23 +407,27 @@ where
/// the operands. When `Operation is used in a right-hand side replacement,
/// these are the sub-replacements for the operands.
#[peepmatic(flatten)]
pub operands: Vec<T>,
pub operands: Vec<TOperand>,
#[allow(missing_docs)]
#[peepmatic(skip_child)]
pub marker: PhantomData<&'a ()>,
}
impl<'a> From<&'a Operation<'a, Pattern<'a>>> for DynAstRef<'a> {
impl<'a, TOperator> From<&'a Operation<'a, TOperator, Pattern<'a, TOperator>>>
for DynAstRef<'a, TOperator>
{
#[inline]
fn from(o: &'a Operation<'a, Pattern<'a>>) -> DynAstRef<'a> {
fn from(o: &'a Operation<'a, TOperator, Pattern<'a, TOperator>>) -> DynAstRef<'a, TOperator> {
DynAstRef::PatternOperation(o)
}
}
impl<'a> From<&'a Operation<'a, Rhs<'a>>> for DynAstRef<'a> {
impl<'a, TOperator> From<&'a Operation<'a, TOperator, Rhs<'a, TOperator>>>
for DynAstRef<'a, TOperator>
{
#[inline]
fn from(o: &'a Operation<'a, Rhs<'a>>) -> DynAstRef<'a> {
fn from(o: &'a Operation<'a, TOperator, Rhs<'a, TOperator>>) -> DynAstRef<'a, TOperator> {
DynAstRef::RhsOperation(o)
}
}
@@ -417,7 +435,7 @@ impl<'a> From<&'a Operation<'a, Rhs<'a>>> for DynAstRef<'a> {
/// A precondition adds additional constraints to a pattern, such as "$C must be
/// a power of two".
#[derive(Debug, Ast)]
pub struct Precondition<'a> {
pub struct Precondition<'a, TOperator> {
/// Where this `Precondition` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
@@ -428,7 +446,11 @@ pub struct Precondition<'a> {
/// The operands of the constraint.
#[peepmatic(flatten)]
pub operands: Vec<ConstraintOperand<'a>>,
pub operands: Vec<ConstraintOperand<'a, TOperator>>,
#[allow(missing_docs)]
#[peepmatic(skip_child)]
pub marker: PhantomData<&'a TOperator>,
}
/// Contraint operators.
@@ -446,40 +468,40 @@ pub enum Constraint {
/// An operand of a precondition's constraint.
#[derive(Debug, Ast)]
pub enum ConstraintOperand<'a> {
pub enum ConstraintOperand<'a, TOperator> {
/// A value literal operand.
ValueLiteral(ValueLiteral<'a>),
ValueLiteral(ValueLiteral<'a, TOperator>),
/// A constant operand.
Constant(Constant<'a>),
Constant(Constant<'a, TOperator>),
/// A variable operand.
Variable(Variable<'a>),
Variable(Variable<'a, TOperator>),
}
/// The right-hand side of an optimization that contains the instructions to
/// replace any matched left-hand side with.
#[derive(Debug, Ast)]
pub enum Rhs<'a> {
pub enum Rhs<'a, TOperator> {
/// A value literal right-hand side.
ValueLiteral(ValueLiteral<'a>),
ValueLiteral(ValueLiteral<'a, TOperator>),
/// A constant right-hand side (the constant must have been matched and
/// bound in the left-hand side's pattern).
Constant(Constant<'a>),
Constant(Constant<'a, TOperator>),
/// A variable right-hand side (the variable must have been matched and
/// bound in the left-hand side's pattern).
Variable(Variable<'a>),
Variable(Variable<'a, TOperator>),
/// An unquote expression that is evaluated while replacing the left-hand
/// side with the right-hand side. The result of the evaluation is used in
/// the replacement.
Unquote(Unquote<'a>),
Unquote(Unquote<'a, TOperator>),
/// A compound right-hand side consisting of an operation and subsequent
/// right-hand side operands.
Operation(Operation<'a, Rhs<'a>>),
Operation(Operation<'a, TOperator, Rhs<'a, TOperator>>),
}
/// An unquote operation.
@@ -493,7 +515,7 @@ pub enum Rhs<'a> {
/// instructions that match its left-hand side with the compile-time result of
/// `log2($C)` (the left-hand side must match and bind the constant `$C`).
#[derive(Debug, Ast)]
pub struct Unquote<'a> {
pub struct Unquote<'a, TOperator> {
/// Where this `Unquote` was defined.
#[peepmatic(skip_child)]
pub span: wast::Span,
@@ -504,5 +526,9 @@ pub struct Unquote<'a> {
/// The operands for this unquote operation.
#[peepmatic(flatten)]
pub operands: Vec<Rhs<'a>>,
pub operands: Vec<Rhs<'a, TOperator>>,
#[allow(missing_docs)]
#[peepmatic(skip_child)]
pub marker: PhantomData<&'a TOperator>,
}

View File

@@ -2,14 +2,20 @@
use peepmatic_automata::{Automaton, Builder};
use peepmatic_runtime::linear;
use std::fmt::Debug;
use std::hash::Hash;
/// Construct an automaton from a set of linear optimizations.
pub fn automatize(
opts: &linear::Optimizations,
) -> Automaton<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> {
pub fn automatize<TOperator>(
opts: &linear::Optimizations<TOperator>,
) -> Automaton<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>
where
TOperator: Copy + Debug + Eq + Hash,
{
debug_assert!(crate::linear_passes::is_sorted_lexicographically(opts));
let mut builder = Builder::<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>>::new();
let mut builder =
Builder::<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>::new();
for opt in &opts.optimizations {
let mut insertion = builder.insert();

View File

@@ -7,17 +7,21 @@ use peepmatic_runtime::{
cc::ConditionCode,
integer_interner::{IntegerId, IntegerInterner},
linear,
operator::Operator,
paths::{PathId, PathInterner},
};
use std::convert::{TryFrom, TryInto};
use std::fmt::Debug;
use std::io::{self, Write};
use std::num::NonZeroU16;
use std::num::{NonZeroU16, NonZeroU32};
#[derive(Debug)]
pub(crate) struct PeepholeDotFmt<'a>(pub(crate) &'a PathInterner, pub(crate) &'a IntegerInterner);
impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for PeepholeDotFmt<'_> {
impl<TOperator> DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>
for PeepholeDotFmt<'_>
where
TOperator: Debug + TryFrom<NonZeroU32>,
{
fn fmt_transition(
&self,
w: &mut impl Write,
@@ -26,22 +30,23 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
_to: Option<&linear::MatchOp>,
) -> io::Result<()> {
let from = from.expect("we should have match op for every state");
if let Some(x) = input.ok().map(|x| x.get()) {
if let Some(x) = input.ok() {
match from {
linear::MatchOp::Opcode { .. } => {
let opcode =
Operator::try_from(x).expect("we shouldn't generate non-opcode edges");
write!(w, "{}", opcode)
let opcode = TOperator::try_from(x)
.map_err(|_| ())
.expect("we shouldn't generate non-opcode edges");
write!(w, "{:?}", opcode)
}
linear::MatchOp::ConditionCode { .. } => {
let cc =
ConditionCode::try_from(x).expect("we shouldn't generate non-CC edges");
let cc = ConditionCode::try_from(x.get())
.expect("we shouldn't generate non-CC edges");
write!(w, "{}", cc)
}
linear::MatchOp::IntegerValue { .. } => {
let x = self
.1
.lookup(IntegerId(NonZeroU16::new(x.try_into().unwrap()).unwrap()));
let x = self.1.lookup(IntegerId(
NonZeroU16::new(x.get().try_into().unwrap()).unwrap(),
));
write!(w, "{}", x)
}
_ => write!(w, "Ok({})", x),
@@ -73,7 +78,11 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
writeln!(w, "</font>")
}
fn fmt_output(&self, w: &mut impl Write, actions: &Box<[linear::Action]>) -> io::Result<()> {
fn fmt_output(
&self,
w: &mut impl Write,
actions: &Box<[linear::Action<TOperator>]>,
) -> io::Result<()> {
use linear::Action::*;
if actions.is_empty() {
@@ -88,11 +97,11 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
match a {
GetLhs { path } => write!(w, "get-lhs @ {}<br/>", p(path))?,
UnaryUnquote { operator, operand } => {
write!(w, "eval {} $rhs{}<br/>", operator, operand.0)?
write!(w, "eval {:?} $rhs{}<br/>", operator, operand.0)?
}
BinaryUnquote { operator, operands } => write!(
w,
"eval {} $rhs{}, $rhs{}<br/>",
"eval {:?} $rhs{}, $rhs{}<br/>",
operator, operands[0].0, operands[1].0,
)?,
MakeIntegerConst {
@@ -108,14 +117,14 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
operand,
operator,
r#type: _,
} => write!(w, "make {} $rhs{}<br/>", operator, operand.0,)?,
} => write!(w, "make {:?} $rhs{}<br/>", operator, operand.0,)?,
MakeBinaryInst {
operator,
operands,
r#type: _,
} => write!(
w,
"make {} $rhs{}, $rhs{}<br/>",
"make {:?} $rhs{}, $rhs{}<br/>",
operator, operands[0].0, operands[1].0,
)?,
MakeTernaryInst {
@@ -124,7 +133,7 @@ impl DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action]>> for Pee
r#type: _,
} => write!(
w,
"make {} $rhs{}, $rhs{}, $rhs{}<br/>",
"make {:?} $rhs{}, $rhs{}, $rhs{}<br/>",
operator, operands[0].0, operands[1].0, operands[2].0,
)?,
}

View File

@@ -23,20 +23,25 @@ pub use self::{
};
use peepmatic_runtime::PeepholeOptimizations;
use peepmatic_traits::TypingRules;
use std::convert::TryFrom;
use std::fmt::Debug;
use std::fs;
use std::hash::Hash;
use std::num::NonZeroU32;
use std::path::Path;
/// Compile the given DSL file into a compact peephole optimizations automaton!
///
/// ## Example
///
/// ```no_run
/// ```ignore
/// # fn main() -> anyhow::Result<()> {
/// use std::path::Path;
///
/// let peep_opts = peepmatic::compile_file(Path::new(
/// "path/to/optimizations.peepmatic"
/// ))?;
/// let peep_opts = peepmatic::compile_file::<cranelift_codegen::ir::Opcode>(
/// Path::new("path/to/optimizations.peepmatic")
/// )?;
///
/// // Use the peephole optimizations or serialize them into bytes here...
/// # Ok(())
@@ -49,9 +54,19 @@ use std::path::Path;
/// `PEEPMATIC_DOT` environment variable to a file path. A [GraphViz
/// Dot]((https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf)) file showing the
/// peephole optimizer's automaton will be written to that file path.
pub fn compile_file(filename: &Path) -> anyhow::Result<PeepholeOptimizations> {
pub fn compile_file<TOperator>(filename: &Path) -> anyhow::Result<PeepholeOptimizations<TOperator>>
where
TOperator: Copy
+ Debug
+ Eq
+ Hash
+ for<'a> wast::parser::Parse<'a>
+ TypingRules
+ Into<NonZeroU32>
+ TryFrom<NonZeroU32>,
{
let source = fs::read_to_string(filename)?;
compile_str(&source, filename)
compile_str::<TOperator>(&source, filename)
}
/// Compile the given DSL source text down into a compact peephole optimizations
@@ -64,11 +79,11 @@ pub fn compile_file(filename: &Path) -> anyhow::Result<PeepholeOptimizations> {
///
/// ## Example
///
/// ```no_run
/// ```ignore
/// # fn main() -> anyhow::Result<()> {
/// use std::path::Path;
///
/// let peep_opts = peepmatic::compile_str(
/// let peep_opts = peepmatic::compile_str::<cranelift_codegen::ir::Opcode>(
/// "
/// (=> (iadd $x 0) $x)
/// (=> (imul $x 0) 0)
@@ -88,14 +103,27 @@ pub fn compile_file(filename: &Path) -> anyhow::Result<PeepholeOptimizations> {
/// `PEEPMATIC_DOT` environment variable to a file path. A [GraphViz
/// Dot]((https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf)) file showing the
/// peephole optimizer's automaton will be written to that file path.
pub fn compile_str(source: &str, filename: &Path) -> anyhow::Result<PeepholeOptimizations> {
pub fn compile_str<TOperator>(
source: &str,
filename: &Path,
) -> anyhow::Result<PeepholeOptimizations<TOperator>>
where
TOperator: Copy
+ Debug
+ Eq
+ Hash
+ for<'a> wast::parser::Parse<'a>
+ TypingRules
+ Into<NonZeroU32>
+ TryFrom<NonZeroU32>,
{
let buf = wast::parser::ParseBuffer::new(source).map_err(|mut e| {
e.set_path(filename);
e.set_text(source);
e
})?;
let opts = wast::parser::parse::<Optimizations>(&buf).map_err(|mut e| {
let opts = wast::parser::parse::<Optimizations<'_, TOperator>>(&buf).map_err(|mut e| {
e.set_path(filename);
e.set_text(source);
e
@@ -137,9 +165,10 @@ pub fn compile_str(source: &str, filename: &Path) -> anyhow::Result<PeepholeOpti
#[cfg(test)]
mod tests {
use super::*;
use peepmatic_test_operator::TestOperator;
fn assert_compiles(path: &str) {
match compile_file(Path::new(path)) {
match compile_file::<TestOperator>(Path::new(path)) {
Ok(_) => return,
Err(e) => {
eprintln!("error: {}", e);

View File

@@ -5,6 +5,8 @@ use peepmatic_runtime::{
paths::{PathId, PathInterner},
};
use std::cmp::Ordering;
use std::fmt::Debug;
use std::hash::Hash;
/// Sort a set of optimizations from least to most general.
///
@@ -25,7 +27,10 @@ use std::cmp::Ordering;
///
/// and we are matching `(imul 4 (..))`, then we want to apply the second
/// optimization, because it is more specific than the first.
pub fn sort_least_to_most_general(opts: &mut linear::Optimizations) {
pub fn sort_least_to_most_general<TOperator>(opts: &mut linear::Optimizations<TOperator>)
where
TOperator: Copy + Debug + Eq + Hash,
{
let linear::Optimizations {
ref mut optimizations,
ref paths,
@@ -41,7 +46,10 @@ pub fn sort_least_to_most_general(opts: &mut linear::Optimizations) {
/// Sort the linear optimizations lexicographically.
///
/// This sort order is required for automata construction.
pub fn sort_lexicographically(opts: &mut linear::Optimizations) {
pub fn sort_lexicographically<TOperator>(opts: &mut linear::Optimizations<TOperator>)
where
TOperator: Copy + Debug + Eq + Hash,
{
let linear::Optimizations {
ref mut optimizations,
ref paths,
@@ -53,12 +61,15 @@ pub fn sort_lexicographically(opts: &mut linear::Optimizations) {
.sort_by(|a, b| compare_optimizations(paths, a, b, |a_len, b_len| a_len.cmp(&b_len)));
}
fn compare_optimizations(
fn compare_optimizations<TOperator>(
paths: &PathInterner,
a: &linear::Optimization,
b: &linear::Optimization,
a: &linear::Optimization<TOperator>,
b: &linear::Optimization<TOperator>,
compare_lengths: impl Fn(usize, usize) -> Ordering,
) -> Ordering {
) -> Ordering
where
TOperator: Copy + Debug + Eq + Hash,
{
for (a, b) in a.increments.iter().zip(b.increments.iter()) {
let c = compare_match_op_generality(paths, a.operation, b.operation);
if c != Ordering::Equal {
@@ -79,11 +90,14 @@ fn compare_optimizations(
compare_lengths(a.increments.len(), b.increments.len())
}
fn compare_optimization_generality(
fn compare_optimization_generality<TOperator>(
paths: &PathInterner,
a: &linear::Optimization,
b: &linear::Optimization,
) -> Ordering {
a: &linear::Optimization<TOperator>,
b: &linear::Optimization<TOperator>,
) -> Ordering
where
TOperator: Copy + Debug + Eq + Hash,
{
compare_optimizations(paths, a, b, |a_len, b_len| {
// If they shared equivalent prefixes, then compare lengths and invert the
// result because longer patterns are less general than shorter patterns.
@@ -158,14 +172,22 @@ fn compare_paths(paths: &PathInterner, a: PathId, b: PathId) -> Ordering {
}
/// Are the given optimizations sorted from least to most general?
pub(crate) fn is_sorted_by_generality(opts: &linear::Optimizations) -> bool {
pub(crate) fn is_sorted_by_generality<TOperator>(opts: &linear::Optimizations<TOperator>) -> bool
where
TOperator: Copy + Debug + Eq + Hash,
{
opts.optimizations
.windows(2)
.all(|w| compare_optimization_generality(&opts.paths, &w[0], &w[1]) <= Ordering::Equal)
}
/// Are the given optimizations sorted lexicographically?
pub(crate) fn is_sorted_lexicographically(opts: &linear::Optimizations) -> bool {
pub(crate) fn is_sorted_lexicographically<TOperator>(
opts: &linear::Optimizations<TOperator>,
) -> bool
where
TOperator: Copy + Debug + Eq + Hash,
{
opts.optimizations.windows(2).all(|w| {
compare_optimizations(&opts.paths, &w[0], &w[1], |a_len, b_len| a_len.cmp(&b_len))
<= Ordering::Equal
@@ -207,7 +229,10 @@ pub(crate) fn is_sorted_lexicographically(opts: &linear::Optimizations) -> bool
/// opcode @ 0 --iadd-->
/// opcode @ 0 --(else)--> is-const? @ 0 --true-->
/// ```
pub fn match_in_same_order(opts: &mut linear::Optimizations) {
pub fn match_in_same_order<TOperator>(opts: &mut linear::Optimizations<TOperator>)
where
TOperator: Copy + Debug + Eq + Hash,
{
assert!(!opts.optimizations.is_empty());
let mut prefix = vec![];
@@ -267,7 +292,10 @@ pub fn match_in_same_order(opts: &mut linear::Optimizations) {
/// existence completely. So we just emit nop match operations for all variable
/// patterns, and then in this post-processing pass, we fuse them and their
/// actions with their preceding increment.
pub fn remove_unnecessary_nops(opts: &mut linear::Optimizations) {
pub fn remove_unnecessary_nops<TOperator>(opts: &mut linear::Optimizations<TOperator>)
where
TOperator: Copy + Debug + Eq + Hash,
{
for opt in &mut opts.optimizations {
if opt.increments.len() < 2 {
debug_assert!(!opt.increments.is_empty());
@@ -289,9 +317,9 @@ mod tests {
use crate::ast::*;
use peepmatic_runtime::{
linear::{bool_to_match_result, Else, MatchOp::*, MatchResult},
operator::Operator,
paths::*,
};
use peepmatic_test_operator::TestOperator;
use std::num::NonZeroU32;
#[test]
@@ -306,7 +334,7 @@ mod tests {
fn $test_name() {
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
let opts = match wast::parser::parse::<Optimizations>(&buf) {
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
Ok(opts) => opts,
Err(mut e) => {
e.set_path(std::path::Path::new(stringify!($test_name)));
@@ -383,7 +411,7 @@ mod tests {
fn $test_name() {
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
let opts = match wast::parser::parse::<Optimizations>(&buf) {
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
Ok(opts) => opts,
Err(mut e) => {
e.set_path(std::path::Path::new(stringify!($test_name)));
@@ -468,31 +496,19 @@ mod tests {
",
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(
Opcode { path: p(&[0, 1]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0, 1]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IntegerValue { path: p(&[0, 1]) }, i(42))
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true)),
(
@@ -501,10 +517,7 @@ mod tests {
)
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true)),
(
@@ -513,18 +526,12 @@ mod tests {
)
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true))
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(
Eq {
@@ -535,10 +542,7 @@ mod tests {
)
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
],
@@ -558,50 +562,32 @@ mod tests {
",
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
(IntegerValue { path: p(&[0, 0]) }, i(2)),
(Nop, Err(Else))
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
(IntegerValue { path: p(&[0, 0]) }, i(1)),
(Nop, Err(Else))
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
(Nop, Err(Else)),
(IntegerValue { path: p(&[0, 1]) }, i(2))
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
(Nop, Err(Else)),
(IntegerValue { path: p(&[0, 1]) }, i(1))
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(IntegerValue { path: p(&[0, 0]) }, i(0)),
(Nop, Err(Else))
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IntegerValue { path: p(&[0, 1]) }, i(0))
]
@@ -619,14 +605,8 @@ mod tests {
",
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(
Opcode { path: p(&[0, 0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
(
@@ -638,14 +618,8 @@ mod tests {
),
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(
Opcode { path: p(&[0, 0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
(
@@ -670,14 +644,8 @@ mod tests {
",
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(
Opcode { path: p(&[0, 0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
(
@@ -689,14 +657,8 @@ mod tests {
),
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(
Opcode { path: p(&[0, 0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
(

View File

@@ -93,11 +93,17 @@ use peepmatic_runtime::{
};
use std::collections::BTreeMap;
use std::convert::TryInto;
use std::fmt::Debug;
use std::hash::Hash;
use std::marker::PhantomData;
use std::num::NonZeroU32;
use wast::Id;
/// Translate the given AST optimizations into linear optimizations.
pub fn linearize(opts: &Optimizations) -> linear::Optimizations {
pub fn linearize<TOperator>(opts: &Optimizations<TOperator>) -> linear::Optimizations<TOperator>
where
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
{
let mut optimizations = vec![];
let mut paths = PathInterner::new();
let mut integers = IntegerInterner::new();
@@ -113,12 +119,15 @@ pub fn linearize(opts: &Optimizations) -> linear::Optimizations {
}
/// Translate an AST optimization into a linear optimization!
fn linearize_optimization(
fn linearize_optimization<TOperator>(
paths: &mut PathInterner,
integers: &mut IntegerInterner,
opt: &Optimization,
) -> linear::Optimization {
let mut increments: Vec<linear::Increment> = vec![];
opt: &Optimization<TOperator>,
) -> linear::Optimization<TOperator>
where
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
{
let mut increments: Vec<linear::Increment<_>> = vec![];
let mut lhs_id_to_path = LhsIdToPath::new();
@@ -175,20 +184,26 @@ fn linearize_optimization(
///
/// Does not maintain any extra state about the traversal, such as where in the
/// tree each yielded node comes from.
struct RhsPostOrder<'a> {
dfs: Dfs<'a>,
struct RhsPostOrder<'a, TOperator> {
dfs: Dfs<'a, TOperator>,
}
impl<'a> RhsPostOrder<'a> {
fn new(rhs: &'a Rhs<'a>) -> Self {
impl<'a, TOperator> RhsPostOrder<'a, TOperator>
where
TOperator: Copy + Debug + Eq + Hash,
{
fn new(rhs: &'a Rhs<'a, TOperator>) -> Self {
Self { dfs: Dfs::new(rhs) }
}
}
impl<'a> Iterator for RhsPostOrder<'a> {
type Item = &'a Rhs<'a>;
impl<'a, TOperator> Iterator for RhsPostOrder<'a, TOperator>
where
TOperator: Copy,
{
type Item = &'a Rhs<'a, TOperator>;
fn next(&mut self) -> Option<&'a Rhs<'a>> {
fn next(&mut self) -> Option<&'a Rhs<'a, TOperator>> {
use crate::traversals::TraversalEvent as TE;
loop {
match self.dfs.next()? {
@@ -203,14 +218,17 @@ impl<'a> Iterator for RhsPostOrder<'a> {
///
/// Keeps track of the path to each pattern, and yields it along side the
/// pattern AST node.
struct PatternPreOrder<'a> {
struct PatternPreOrder<'a, TOperator> {
last_child: Option<u8>,
path: Vec<u8>,
dfs: Dfs<'a>,
dfs: Dfs<'a, TOperator>,
}
impl<'a> PatternPreOrder<'a> {
fn new(pattern: &'a Pattern<'a>) -> Self {
impl<'a, TOperator> PatternPreOrder<'a, TOperator>
where
TOperator: Copy + Debug + Eq + Hash,
{
fn new(pattern: &'a Pattern<'a, TOperator>) -> Self {
Self {
last_child: None,
path: vec![],
@@ -218,7 +236,7 @@ impl<'a> PatternPreOrder<'a> {
}
}
fn next(&mut self, paths: &mut PathInterner) -> Option<(PathId, &'a Pattern<'a>)> {
fn next(&mut self, paths: &mut PathInterner) -> Option<(PathId, &'a Pattern<'a, TOperator>)> {
use crate::traversals::TraversalEvent as TE;
loop {
match self.dfs.next()? {
@@ -252,15 +270,17 @@ impl<'a> PatternPreOrder<'a> {
/// A map from left-hand side identifiers to the path in the left-hand side
/// where they first occurred.
struct LhsIdToPath<'a> {
struct LhsIdToPath<'a, TOperator> {
id_to_path: BTreeMap<&'a str, PathId>,
_marker: PhantomData<&'a TOperator>,
}
impl<'a> LhsIdToPath<'a> {
impl<'a, TOperator> LhsIdToPath<'a, TOperator> {
/// Construct a new, empty `LhsIdToPath`.
fn new() -> Self {
Self {
id_to_path: Default::default(),
_marker: PhantomData,
}
}
@@ -280,7 +300,7 @@ impl<'a> LhsIdToPath<'a> {
}
/// Remember the path to any LHS ids used in the given pattern.
fn remember_path_to_pattern_ids(&mut self, pattern: &'a Pattern<'a>, path: PathId) {
fn remember_path_to_pattern_ids(&mut self, pattern: &'a Pattern<'a, TOperator>, path: PathId) {
match pattern {
// If this is the first time we've seen an identifier defined on the
// left-hand side, remember it.
@@ -294,10 +314,10 @@ impl<'a> LhsIdToPath<'a> {
/// An `RhsBuilder` emits the actions for building the right-hand side
/// instructions.
struct RhsBuilder<'a> {
struct RhsBuilder<'a, TOperator> {
// We do a post order traversal of the RHS because an RHS instruction cannot
// be created until after all of its operands are created.
rhs_post_order: RhsPostOrder<'a>,
rhs_post_order: RhsPostOrder<'a, TOperator>,
// A map from a right-hand side's span to its `linear::RhsId`. This is used
// by RHS-construction actions to reference operands. In practice the
@@ -306,9 +326,12 @@ struct RhsBuilder<'a> {
rhs_span_to_id: BTreeMap<wast::Span, linear::RhsId>,
}
impl<'a> RhsBuilder<'a> {
impl<'a, TOperator> RhsBuilder<'a, TOperator>
where
TOperator: Copy + Debug + Eq + Hash,
{
/// Create a new builder for the given right-hand side.
fn new(rhs: &'a Rhs<'a>) -> Self {
fn new(rhs: &'a Rhs<'a, TOperator>) -> Self {
let rhs_post_order = RhsPostOrder::new(rhs);
let rhs_span_to_id = Default::default();
Self {
@@ -323,7 +346,7 @@ impl<'a> RhsBuilder<'a> {
///
/// Panics if we haven't already emitted the action for building this RHS's
/// instruction.
fn get_rhs_id(&self, rhs: &Rhs) -> linear::RhsId {
fn get_rhs_id(&self, rhs: &Rhs<TOperator>) -> linear::RhsId {
self.rhs_span_to_id[&rhs.span()]
}
@@ -335,8 +358,8 @@ impl<'a> RhsBuilder<'a> {
fn add_rhs_build_actions(
&mut self,
integers: &mut IntegerInterner,
lhs_id_to_path: &LhsIdToPath,
actions: &mut Vec<linear::Action>,
lhs_id_to_path: &LhsIdToPath<TOperator>,
actions: &mut Vec<linear::Action<TOperator>>,
) {
while let Some(rhs) = self.rhs_post_order.next() {
actions.push(self.rhs_to_linear_action(integers, lhs_id_to_path, rhs));
@@ -348,9 +371,9 @@ impl<'a> RhsBuilder<'a> {
fn rhs_to_linear_action(
&self,
integers: &mut IntegerInterner,
lhs_id_to_path: &LhsIdToPath,
rhs: &Rhs,
) -> linear::Action {
lhs_id_to_path: &LhsIdToPath<TOperator>,
rhs: &Rhs<TOperator>,
) -> linear::Action<TOperator> {
match rhs {
Rhs::ValueLiteral(ValueLiteral::Integer(i)) => linear::Action::MakeIntegerConst {
value: integers.intern(i.value as u64),
@@ -425,9 +448,15 @@ impl<'a> RhsBuilder<'a> {
}
}
impl Precondition<'_> {
impl<TOperator> Precondition<'_, TOperator>
where
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
{
/// Convert this precondition into a `linear::Increment`.
fn to_linear_increment(&self, lhs_id_to_path: &LhsIdToPath) -> linear::Increment {
fn to_linear_increment(
&self,
lhs_id_to_path: &LhsIdToPath<TOperator>,
) -> linear::Increment<TOperator> {
match self.constraint {
Constraint::IsPowerOfTwo => {
let id = match &self.operands[0] {
@@ -484,7 +513,10 @@ impl Precondition<'_> {
}
}
impl Pattern<'_> {
impl<TOperator> Pattern<'_, TOperator>
where
TOperator: Copy,
{
/// Convert this pattern into its linear match operation and the expected
/// result of that operation.
///
@@ -493,9 +525,12 @@ impl Pattern<'_> {
fn to_linear_match_op(
&self,
integers: &mut IntegerInterner,
lhs_id_to_path: &LhsIdToPath,
lhs_id_to_path: &LhsIdToPath<TOperator>,
path: PathId,
) -> (linear::MatchOp, linear::MatchResult) {
) -> (linear::MatchOp, linear::MatchResult)
where
TOperator: Into<NonZeroU32>,
{
match self {
Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => (
linear::MatchOp::IntegerValue { path },
@@ -543,9 +578,7 @@ impl Pattern<'_> {
}
}
Pattern::Operation(op) => {
let op = op.operator as u32;
debug_assert!(op != 0, "no `Operator` variants are zero");
let expected = Ok(unsafe { NonZeroU32::new_unchecked(op) });
let expected = Ok(op.operator.into());
(linear::MatchOp::Opcode { path }, expected)
}
}
@@ -558,9 +591,9 @@ mod tests {
use peepmatic_runtime::{
integer_interner::IntegerId,
linear::{bool_to_match_result, Action::*, Else, MatchOp::*},
operator::Operator,
r#type::{BitWidth, Kind, Type},
};
use peepmatic_test_operator::TestOperator;
macro_rules! linearizes_to {
($name:ident, $source:expr, $make_expected:expr $(,)* ) => {
@@ -568,7 +601,7 @@ mod tests {
fn $name() {
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
let opts = match wast::parser::parse::<Optimizations>(&buf) {
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
Ok(opts) => opts,
Err(mut e) => {
e.set_path(std::path::Path::new(stringify!($name)));
@@ -602,7 +635,7 @@ mod tests {
let make_expected: fn(
&mut dyn FnMut(&[u8]) -> PathId,
&mut dyn FnMut(u64) -> IntegerId,
) -> Vec<linear::Increment> = $make_expected;
) -> Vec<linear::Increment<TestOperator>> = $make_expected;
let expected = make_expected(&mut p, &mut i);
dbg!(&expected);
@@ -624,12 +657,12 @@ mod tests {
|p, i| vec![
linear::Increment {
operation: Opcode { path: p(&[0]) },
expected: Ok(NonZeroU32::new(Operator::Imul as _).unwrap()),
expected: Ok(TestOperator::Imul.into()),
actions: vec![
GetLhs { path: p(&[0, 0]) },
GetLhs { path: p(&[0, 1]) },
MakeBinaryInst {
operator: Operator::Ishl,
operator: TestOperator::Ishl,
r#type: Type {
kind: Kind::Int,
bit_width: BitWidth::Polymorphic,
@@ -702,11 +735,11 @@ mod tests {
|p, i| vec![
linear::Increment {
operation: Opcode { path: p(&[0]) },
expected: Ok(NonZeroU32::new(Operator::Iconst as _).unwrap()),
expected: Ok(TestOperator::Iconst.into()),
actions: vec![
GetLhs { path: p(&[0, 0]) },
MakeUnaryInst {
operator: Operator::Iconst,
operator: TestOperator::Iconst,
r#type: Type {
kind: Kind::Int,
bit_width: BitWidth::Polymorphic,
@@ -729,14 +762,14 @@ mod tests {
|p, i| vec![
linear::Increment {
operation: Opcode { path: p(&[0]) },
expected: Ok(NonZeroU32::new(Operator::Bor as _).unwrap()),
expected: Ok(TestOperator::Bor.into()),
actions: vec![
GetLhs { path: p(&[0, 0]) },
GetLhs {
path: p(&[0, 1, 1]),
},
MakeBinaryInst {
operator: Operator::Bor,
operator: TestOperator::Bor,
r#type: Type {
kind: Kind::Int,
bit_width: BitWidth::Polymorphic,
@@ -752,7 +785,7 @@ mod tests {
},
linear::Increment {
operation: Opcode { path: p(&[0, 1]) },
expected: Ok(NonZeroU32::new(Operator::Bor as _).unwrap()),
expected: Ok(TestOperator::Bor.into()),
actions: vec![],
},
linear::Increment {
@@ -791,7 +824,7 @@ mod tests {
|p, i| vec![
linear::Increment {
operation: Opcode { path: p(&[0]) },
expected: Ok(NonZeroU32::new(Operator::Ireduce as _).unwrap()),
expected: Ok(TestOperator::Ireduce.into()),
actions: vec![MakeIntegerConst {
value: i(0),
bit_width: BitWidth::ThirtyTwo,

View File

@@ -84,7 +84,10 @@ mod tok {
custom_keyword!(nof);
}
impl<'a> Parse<'a> for Optimizations<'a> {
impl<'a, TOperator> Parse<'a> for Optimizations<'a, TOperator>
where
TOperator: Parse<'a>,
{
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
let mut optimizations = vec![];
@@ -98,7 +101,10 @@ impl<'a> Parse<'a> for Optimizations<'a> {
}
}
impl<'a> Parse<'a> for Optimization<'a> {
impl<'a, TOperator> Parse<'a> for Optimization<'a, TOperator>
where
TOperator: Parse<'a>,
{
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
p.parens(|p| {
@@ -110,7 +116,10 @@ impl<'a> Parse<'a> for Optimization<'a> {
}
}
impl<'a> Parse<'a> for Lhs<'a> {
impl<'a, TOperator> Parse<'a> for Lhs<'a, TOperator>
where
TOperator: Parse<'a>,
{
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
let mut preconditions = vec![];
@@ -139,30 +148,36 @@ impl<'a> Parse<'a> for Lhs<'a> {
}
}
impl<'a> Parse<'a> for Pattern<'a> {
impl<'a, TOperator> Parse<'a> for Pattern<'a, TOperator>
where
TOperator: Parse<'a>,
{
fn parse(p: Parser<'a>) -> ParseResult<Self> {
if p.peek::<ValueLiteral>() {
if p.peek::<ValueLiteral<TOperator>>() {
return Ok(Pattern::ValueLiteral(p.parse()?));
}
if p.peek::<Constant>() {
if p.peek::<Constant<TOperator>>() {
return Ok(Pattern::Constant(p.parse()?));
}
if p.peek::<Operation<Self>>() {
if p.peek::<Operation<TOperator, Self>>() {
return Ok(Pattern::Operation(p.parse()?));
}
if p.peek::<Variable>() {
if p.peek::<Variable<TOperator>>() {
return Ok(Pattern::Variable(p.parse()?));
}
Err(p.error("expected a left-hand side pattern"))
}
}
impl<'a> Peek for Pattern<'a> {
impl<'a, TOperator> Peek for Pattern<'a, TOperator>
where
TOperator: 'a,
{
fn peek(c: Cursor) -> bool {
ValueLiteral::peek(c)
|| Constant::peek(c)
|| Variable::peek(c)
|| Operation::<Self>::peek(c)
ValueLiteral::<TOperator>::peek(c)
|| Constant::<TOperator>::peek(c)
|| Variable::<TOperator>::peek(c)
|| Operation::<TOperator, Self>::peek(c)
}
fn display() -> &'static str {
@@ -170,24 +185,26 @@ impl<'a> Peek for Pattern<'a> {
}
}
impl<'a> Parse<'a> for ValueLiteral<'a> {
impl<'a, TOperator> Parse<'a> for ValueLiteral<'a, TOperator> {
fn parse(p: Parser<'a>) -> ParseResult<Self> {
if let Ok(b) = p.parse::<Boolean>() {
if let Ok(b) = p.parse::<Boolean<TOperator>>() {
return Ok(ValueLiteral::Boolean(b));
}
if let Ok(i) = p.parse::<Integer>() {
if let Ok(i) = p.parse::<Integer<TOperator>>() {
return Ok(ValueLiteral::Integer(i));
}
if let Ok(cc) = p.parse::<ConditionCode>() {
if let Ok(cc) = p.parse::<ConditionCode<TOperator>>() {
return Ok(ValueLiteral::ConditionCode(cc));
}
Err(p.error("expected an integer or boolean or condition code literal"))
}
}
impl<'a> Peek for ValueLiteral<'a> {
impl<'a, TOperator> Peek for ValueLiteral<'a, TOperator> {
fn peek(c: Cursor) -> bool {
c.integer().is_some() || Boolean::peek(c) || ConditionCode::peek(c)
c.integer().is_some()
|| Boolean::<TOperator>::peek(c)
|| ConditionCode::<TOperator>::peek(c)
}
fn display() -> &'static str {
@@ -195,7 +212,7 @@ impl<'a> Peek for ValueLiteral<'a> {
}
}
impl<'a> Parse<'a> for Integer<'a> {
impl<'a, TOperator> Parse<'a> for Integer<'a, TOperator> {
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
p.step(|c| {
@@ -221,7 +238,7 @@ impl<'a> Parse<'a> for Integer<'a> {
}
}
impl<'a> Parse<'a> for Boolean<'a> {
impl<'a, TOperator> Parse<'a> for Boolean<'a, TOperator> {
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
if p.parse::<tok::r#true>().is_ok() {
@@ -244,7 +261,7 @@ impl<'a> Parse<'a> for Boolean<'a> {
}
}
impl<'a> Peek for Boolean<'a> {
impl<'a, TOperator> Peek for Boolean<'a, TOperator> {
fn peek(c: Cursor) -> bool {
<tok::r#true as Peek>::peek(c) || <tok::r#false as Peek>::peek(c)
}
@@ -254,7 +271,7 @@ impl<'a> Peek for Boolean<'a> {
}
}
impl<'a> Parse<'a> for ConditionCode<'a> {
impl<'a, TOperator> Parse<'a> for ConditionCode<'a, TOperator> {
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
@@ -292,7 +309,7 @@ impl<'a> Parse<'a> for ConditionCode<'a> {
}
}
impl<'a> Peek for ConditionCode<'a> {
impl<'a, TOperator> Peek for ConditionCode<'a, TOperator> {
fn peek(c: Cursor) -> bool {
macro_rules! peek_cc {
( $( $token:ident, )* ) => {
@@ -321,7 +338,7 @@ impl<'a> Peek for ConditionCode<'a> {
}
}
impl<'a> Parse<'a> for Constant<'a> {
impl<'a, TOperator> Parse<'a> for Constant<'a, TOperator> {
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
let id = Id::parse(p)?;
@@ -330,7 +347,11 @@ impl<'a> Parse<'a> for Constant<'a> {
.chars()
.all(|c| !c.is_alphabetic() || c.is_uppercase())
{
Ok(Constant { span, id })
Ok(Constant {
span,
id,
marker: PhantomData,
})
} else {
let upper = id
.name()
@@ -345,7 +366,7 @@ impl<'a> Parse<'a> for Constant<'a> {
}
}
impl<'a> Peek for Constant<'a> {
impl<'a, TOperator> Peek for Constant<'a, TOperator> {
fn peek(c: Cursor) -> bool {
if let Some((id, _rest)) = c.id() {
id.chars().all(|c| !c.is_alphabetic() || c.is_uppercase())
@@ -359,7 +380,7 @@ impl<'a> Peek for Constant<'a> {
}
}
impl<'a> Parse<'a> for Variable<'a> {
impl<'a, TOperator> Parse<'a> for Variable<'a, TOperator> {
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
let id = Id::parse(p)?;
@@ -368,7 +389,11 @@ impl<'a> Parse<'a> for Variable<'a> {
.chars()
.all(|c| !c.is_alphabetic() || c.is_lowercase())
{
Ok(Variable { span, id })
Ok(Variable {
span,
id,
marker: PhantomData,
})
} else {
let lower = id
.name()
@@ -383,7 +408,7 @@ impl<'a> Parse<'a> for Variable<'a> {
}
}
impl<'a> Peek for Variable<'a> {
impl<'a, TOperator> Peek for Variable<'a, TOperator> {
fn peek(c: Cursor) -> bool {
if let Some((id, _rest)) = c.id() {
id.chars().all(|c| !c.is_alphabetic() || c.is_lowercase())
@@ -397,10 +422,11 @@ impl<'a> Peek for Variable<'a> {
}
}
impl<'a, T> Parse<'a> for Operation<'a, T>
impl<'a, TOperator, TOperand> Parse<'a> for Operation<'a, TOperator, TOperand>
where
T: 'a + Ast<'a> + Peek + Parse<'a>,
DynAstRef<'a>: From<&'a T>,
TOperator: Parse<'a>,
TOperand: 'a + Ast<'a, TOperator> + Peek + Parse<'a>,
DynAstRef<'a, TOperator>: From<&'a TOperand>,
{
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
@@ -417,7 +443,7 @@ where
});
let mut operands = vec![];
while p.peek::<T>() {
while p.peek::<TOperand>() {
operands.push(p.parse()?);
}
Ok(Operation {
@@ -431,10 +457,10 @@ where
}
}
impl<'a, T> Peek for Operation<'a, T>
impl<'a, TOperator, TOperand> Peek for Operation<'a, TOperator, TOperand>
where
T: 'a + Ast<'a>,
DynAstRef<'a>: From<&'a T>,
TOperand: 'a + Ast<'a, TOperator>,
DynAstRef<'a, TOperator>: From<&'a TOperand>,
{
fn peek(c: Cursor) -> bool {
wast::LParen::peek(c)
@@ -445,19 +471,20 @@ where
}
}
impl<'a> Parse<'a> for Precondition<'a> {
impl<'a, TOperator> Parse<'a> for Precondition<'a, TOperator> {
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
p.parens(|p| {
let constraint = p.parse()?;
let mut operands = vec![];
while p.peek::<ConstraintOperand>() {
while p.peek::<ConstraintOperand<TOperator>>() {
operands.push(p.parse()?);
}
Ok(Precondition {
span,
constraint,
operands,
marker: PhantomData,
})
})
}
@@ -481,24 +508,26 @@ impl<'a> Parse<'a> for Constraint {
}
}
impl<'a> Parse<'a> for ConstraintOperand<'a> {
impl<'a, TOperator> Parse<'a> for ConstraintOperand<'a, TOperator> {
fn parse(p: Parser<'a>) -> ParseResult<Self> {
if p.peek::<ValueLiteral>() {
if p.peek::<ValueLiteral<TOperator>>() {
return Ok(ConstraintOperand::ValueLiteral(p.parse()?));
}
if p.peek::<Constant>() {
if p.peek::<Constant<TOperator>>() {
return Ok(ConstraintOperand::Constant(p.parse()?));
}
if p.peek::<Variable>() {
if p.peek::<Variable<TOperator>>() {
return Ok(ConstraintOperand::Variable(p.parse()?));
}
Err(p.error("expected an operand for precondition constraint"))
}
}
impl<'a> Peek for ConstraintOperand<'a> {
impl<'a, TOperator> Peek for ConstraintOperand<'a, TOperator> {
fn peek(c: Cursor) -> bool {
ValueLiteral::peek(c) || Constant::peek(c) || Variable::peek(c)
ValueLiteral::<TOperator>::peek(c)
|| Constant::<TOperator>::peek(c)
|| Variable::<TOperator>::peek(c)
}
fn display() -> &'static str {
@@ -506,34 +535,40 @@ impl<'a> Peek for ConstraintOperand<'a> {
}
}
impl<'a> Parse<'a> for Rhs<'a> {
impl<'a, TOperator> Parse<'a> for Rhs<'a, TOperator>
where
TOperator: Parse<'a>,
{
fn parse(p: Parser<'a>) -> ParseResult<Self> {
if p.peek::<ValueLiteral>() {
if p.peek::<ValueLiteral<TOperator>>() {
return Ok(Rhs::ValueLiteral(p.parse()?));
}
if p.peek::<Constant>() {
if p.peek::<Constant<TOperator>>() {
return Ok(Rhs::Constant(p.parse()?));
}
if p.peek::<Variable>() {
if p.peek::<Variable<TOperator>>() {
return Ok(Rhs::Variable(p.parse()?));
}
if p.peek::<Unquote>() {
if p.peek::<Unquote<TOperator>>() {
return Ok(Rhs::Unquote(p.parse()?));
}
if p.peek::<Operation<Self>>() {
if p.peek::<Operation<TOperator, Self>>() {
return Ok(Rhs::Operation(p.parse()?));
}
Err(p.error("expected a right-hand side replacement"))
}
}
impl<'a> Peek for Rhs<'a> {
impl<'a, TOperator> Peek for Rhs<'a, TOperator>
where
TOperator: 'a,
{
fn peek(c: Cursor) -> bool {
ValueLiteral::peek(c)
|| Constant::peek(c)
|| Variable::peek(c)
|| Unquote::peek(c)
|| Operation::<Self>::peek(c)
ValueLiteral::<TOperator>::peek(c)
|| Constant::<TOperator>::peek(c)
|| Variable::<TOperator>::peek(c)
|| Unquote::<TOperator>::peek(c)
|| Operation::<TOperator, Self>::peek(c)
}
fn display() -> &'static str {
@@ -541,26 +576,30 @@ impl<'a> Peek for Rhs<'a> {
}
}
impl<'a> Parse<'a> for Unquote<'a> {
impl<'a, TOperator> Parse<'a> for Unquote<'a, TOperator>
where
TOperator: Parse<'a>,
{
fn parse(p: Parser<'a>) -> ParseResult<Self> {
let span = p.cur_span();
p.parse::<tok::dollar>()?;
p.parens(|p| {
let operator = p.parse()?;
let mut operands = vec![];
while p.peek::<Rhs>() {
while p.peek::<Rhs<TOperator>>() {
operands.push(p.parse()?);
}
Ok(Unquote {
span,
operator,
operands,
marker: PhantomData,
})
})
}
}
impl<'a> Peek for Unquote<'a> {
impl<'a, TOperator> Peek for Unquote<'a, TOperator> {
fn peek(c: Cursor) -> bool {
tok::dollar::peek(c)
}
@@ -573,7 +612,7 @@ impl<'a> Peek for Unquote<'a> {
#[cfg(test)]
mod test {
use super::*;
use peepmatic_runtime::operator::Operator;
use peepmatic_test_operator::TestOperator;
macro_rules! test_parse {
(
@@ -623,7 +662,7 @@ mod test {
}
test_parse! {
parse_boolean<Boolean> {
parse_boolean<Boolean<TestOperator>> {
ok {
"true",
"false",
@@ -641,7 +680,7 @@ mod test {
"falsezzz",
}
}
parse_cc<ConditionCode> {
parse_cc<ConditionCode<TestOperator>> {
ok {
"eq",
"ne",
@@ -661,7 +700,7 @@ mod test {
"neq",
}
}
parse_constant<Constant> {
parse_constant<Constant<TestOperator>> {
ok {
"$C",
"$C1",
@@ -693,7 +732,7 @@ mod test {
"imul",
}
}
parse_constraint_operand<ConstraintOperand> {
parse_constraint_operand<ConstraintOperand<TestOperator>> {
ok {
"1234",
"true",
@@ -707,7 +746,7 @@ mod test {
"(iadd 1 2)",
}
}
parse_integer<Integer> {
parse_integer<Integer<TestOperator>> {
ok {
"0",
"1",
@@ -746,7 +785,7 @@ mod test {
"0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
}
}
parse_lhs<Lhs> {
parse_lhs<Lhs<TestOperator>> {
ok {
"(when (imul $C1 $C2) (is-power-of-two $C1) (is-power-of-two $C2))",
"(when (imul $x $C) (is-power-of-two $C))",
@@ -762,7 +801,7 @@ mod test {
"abc",
}
}
parse_operation_pattern<Operation<Pattern>> {
parse_operation_pattern<Operation<TestOperator, Pattern<TestOperator>>> {
ok {
"(iadd)",
"(iadd 1)",
@@ -779,7 +818,7 @@ mod test {
"(ishl $x $(log2 $C))",
}
}
parse_operation_rhs<Operation<Rhs>> {
parse_operation_rhs<Operation<TestOperator, Rhs<TestOperator>>> {
ok {
"(iadd)",
"(iadd 1)",
@@ -793,7 +832,7 @@ mod test {
"$CONST",
}
}
parse_operator<Operator> {
parse_operator<TestOperator> {
ok {
"bor",
"iadd",
@@ -812,7 +851,7 @@ mod test {
"iadd{i32}",
}
}
parse_optimization<Optimization> {
parse_optimization<Optimization<TestOperator>> {
ok {
"(=> (when (iadd $x $C) (is-power-of-two $C) (is-power-of-two $C)) (iadd $C $x))",
"(=> (when (iadd $x $C)) (iadd $C $x))",
@@ -825,7 +864,7 @@ mod test {
"(=> () ())",
}
}
parse_optimizations<Optimizations> {
parse_optimizations<Optimizations<TestOperator>> {
ok {
"",
r#"
@@ -844,7 +883,7 @@ mod test {
"#,
}
}
parse_pattern<Pattern> {
parse_pattern<Pattern<TestOperator>> {
ok {
"1234",
"$C",
@@ -857,7 +896,7 @@ mod test {
"abc",
}
}
parse_precondition<Precondition> {
parse_precondition<Precondition<TestOperator>> {
ok {
"(is-power-of-two)",
"(is-power-of-two $C)",
@@ -871,7 +910,7 @@ mod test {
"$CONST",
}
}
parse_rhs<Rhs> {
parse_rhs<Rhs<TestOperator>> {
ok {
"5",
"$C",
@@ -884,7 +923,7 @@ mod test {
"()",
}
}
parse_unquote<Unquote> {
parse_unquote<Unquote<TestOperator>> {
ok {
"$(log2)",
"$(log2 $C)",
@@ -899,7 +938,7 @@ mod test {
"$()",
}
}
parse_value_literal<ValueLiteral> {
parse_value_literal<ValueLiteral<TestOperator>> {
ok {
"12345",
"true",
@@ -911,7 +950,7 @@ mod test {
"12.34",
}
}
parse_variable<Variable> {
parse_variable<Variable<TestOperator>> {
ok {
"$v",
"$v1",

View File

@@ -1,6 +1,8 @@
//! Traversals over the AST.
use crate::ast::*;
use std::fmt::Debug;
use std::hash::Hash;
/// A low-level DFS traversal event: either entering or exiting the traversal of
/// an AST node.
@@ -32,13 +34,16 @@ pub enum TraversalEvent {
/// the AST, because the `new` constructor takes anything that can convert into
/// a `DynAstRef`.
#[derive(Debug, Clone)]
pub struct Dfs<'a> {
stack: Vec<(TraversalEvent, DynAstRef<'a>)>,
pub struct Dfs<'a, TOperator> {
stack: Vec<(TraversalEvent, DynAstRef<'a, TOperator>)>,
}
impl<'a> Dfs<'a> {
impl<'a, TOperator> Dfs<'a, TOperator>
where
TOperator: Copy + Debug + Eq + Hash,
{
/// Construct a new `Dfs` traversal starting at the given `start` AST node.
pub fn new(start: impl Into<DynAstRef<'a>>) -> Self {
pub fn new(start: impl Into<DynAstRef<'a, TOperator>>) -> Self {
let start = start.into();
Dfs {
stack: vec![
@@ -49,15 +54,18 @@ impl<'a> Dfs<'a> {
}
/// Peek at the next traversal event and AST node pair, if any.
pub fn peek(&self) -> Option<(TraversalEvent, DynAstRef<'a>)> {
self.stack.last().cloned()
pub fn peek(&self) -> Option<(TraversalEvent, DynAstRef<'a, TOperator>)> {
self.stack.last().copied()
}
}
impl<'a> Iterator for Dfs<'a> {
type Item = (TraversalEvent, DynAstRef<'a>);
impl<'a, TOperator> Iterator for Dfs<'a, TOperator>
where
TOperator: Copy,
{
type Item = (TraversalEvent, DynAstRef<'a, TOperator>);
fn next(&mut self) -> Option<(TraversalEvent, DynAstRef<'a>)> {
fn next(&mut self) -> Option<(TraversalEvent, DynAstRef<'a, TOperator>)> {
let (event, node) = self.stack.pop()?;
if let TraversalEvent::Enter = event {
let mut enqueue_children = EnqueueChildren(self);
@@ -65,15 +73,16 @@ impl<'a> Iterator for Dfs<'a> {
}
return Some((event, node));
struct EnqueueChildren<'a, 'b>(&'b mut Dfs<'a>)
struct EnqueueChildren<'a, 'b, TOperator>(&'b mut Dfs<'a, TOperator>)
where
'a: 'b;
impl<'a, 'b> Extend<DynAstRef<'a>> for EnqueueChildren<'a, 'b>
impl<'a, 'b, TOperator> Extend<DynAstRef<'a, TOperator>> for EnqueueChildren<'a, 'b, TOperator>
where
'a: 'b,
TOperator: Copy,
{
fn extend<T: IntoIterator<Item = DynAstRef<'a>>>(&mut self, iter: T) {
fn extend<T: IntoIterator<Item = DynAstRef<'a, TOperator>>>(&mut self, iter: T) {
let iter = iter.into_iter();
let (min, max) = iter.size_hint();
@@ -97,6 +106,7 @@ impl<'a> Iterator for Dfs<'a> {
#[cfg(test)]
mod tests {
use super::*;
use peepmatic_test_operator::TestOperator;
use DynAstRef::*;
#[test]
@@ -107,7 +117,7 @@ mod tests {
(ishl $x $(log2 $C)))
";
let buf = wast::parser::ParseBuffer::new(input).expect("input should lex OK");
let ast = match wast::parser::parse::<crate::ast::Optimizations>(&buf) {
let ast = match wast::parser::parse::<crate::ast::Optimizations<TestOperator>>(&buf) {
Ok(ast) => ast,
Err(e) => panic!("expected to parse OK, got error:\n\n{}", e),
};

View File

@@ -14,14 +14,13 @@
use crate::ast::{Span as _, *};
use crate::traversals::{Dfs, TraversalEvent};
use peepmatic_runtime::{
operator::{Operator, TypingContext as TypingContextTrait},
r#type::{BitWidth, Kind, Type},
};
use peepmatic_runtime::r#type::{BitWidth, Kind, Type};
use peepmatic_traits::{TypingContext as TypingContextTrait, TypingRules};
use std::borrow::Cow;
use std::collections::HashMap;
use std::convert::{TryFrom, TryInto};
use std::fmt;
use std::fmt::Debug;
use std::hash::Hash;
use std::iter;
use std::mem;
@@ -94,7 +93,10 @@ impl VerifyError {
pub type VerifyResult<T> = Result<T, VerifyError>;
/// Verify and type check a set of optimizations.
pub fn verify(opts: &Optimizations) -> VerifyResult<()> {
pub fn verify<TOperator>(opts: &Optimizations<TOperator>) -> VerifyResult<()>
where
TOperator: Copy + Debug + Eq + Hash + TypingRules,
{
if opts.optimizations.is_empty() {
return Err(anyhow::anyhow!("no optimizations").into());
}
@@ -113,7 +115,10 @@ pub fn verify(opts: &Optimizations) -> VerifyResult<()> {
/// If there were duplicates, then it would be nondeterministic which one we
/// applied and would make automata construction more difficult. It is better to
/// check for duplicates and reject them if found.
fn verify_unique_left_hand_sides(opts: &Optimizations) -> VerifyResult<()> {
fn verify_unique_left_hand_sides<TOperator>(opts: &Optimizations<TOperator>) -> VerifyResult<()>
where
TOperator: Copy + Eq + Debug + Hash,
{
let mut lefts = HashMap::new();
for opt in &opts.optimizations {
let canon_lhs = canonicalized_lhs_key(&opt.lhs);
@@ -146,7 +151,10 @@ fn verify_unique_left_hand_sides(opts: &Optimizations) -> VerifyResult<()> {
///
/// This function creates an opaque, canonicalized hash key for left-hand sides
/// that sees through identifier renaming.
fn canonicalized_lhs_key(lhs: &Lhs) -> impl Hash + Eq {
fn canonicalized_lhs_key<TOperator>(lhs: &Lhs<TOperator>) -> impl Hash + Eq
where
TOperator: Copy + Debug + Eq + Hash,
{
let mut var_to_canon = HashMap::new();
let mut const_to_canon = HashMap::new();
let mut canonicalized = vec![];
@@ -183,19 +191,20 @@ fn canonicalized_lhs_key(lhs: &Lhs) -> impl Hash + Eq {
return canonicalized;
#[derive(Hash, PartialEq, Eq)]
enum CanonicalBit {
enum CanonicalBit<TOperator> {
Var(u32),
Const(u32),
Integer(i64),
Boolean(bool),
ConditionCode(peepmatic_runtime::cc::ConditionCode),
Operation(Operator, Option<Type>),
Operation(TOperator, Option<Type>),
Precondition(Constraint),
Other(&'static str),
}
}
pub(crate) struct TypingContext<'a> {
#[derive(Debug)]
struct TypingContext<'a, TOperator> {
z3: &'a z3::Context,
type_kind_sort: z3::DatatypeSort<'a>,
solver: z3::Solver<'a>,
@@ -218,12 +227,15 @@ pub(crate) struct TypingContext<'a> {
// Keep track of AST nodes that need to have their types assigned to
// them. For these AST nodes, we know what bit width to use when
// interpreting peephole optimization actions.
boolean_literals: Vec<(&'a Boolean<'a>, TypeVar<'a>)>,
integer_literals: Vec<(&'a Integer<'a>, TypeVar<'a>)>,
rhs_operations: Vec<(&'a Operation<'a, Rhs<'a>>, TypeVar<'a>)>,
boolean_literals: Vec<(&'a Boolean<'a, TOperator>, TypeVar<'a>)>,
integer_literals: Vec<(&'a Integer<'a, TOperator>, TypeVar<'a>)>,
rhs_operations: Vec<(
&'a Operation<'a, TOperator, Rhs<'a, TOperator>>,
TypeVar<'a>,
)>,
}
impl<'a> TypingContext<'a> {
impl<'a, TOperator> TypingContext<'a, TOperator> {
fn new(z3: &'a z3::Context) -> Self {
let type_kind_sort = z3::DatatypeBuilder::new(z3)
.variant("int", &[])
@@ -301,51 +313,55 @@ impl<'a> TypingContext<'a> {
// and similar refer to the same type variables.
fn enter_operation_scope<'b>(
&'b mut self,
) -> impl DerefMut<Target = TypingContext<'a>> + Drop + 'b {
) -> impl DerefMut<Target = TypingContext<'a, TOperator>> + Drop + 'b {
assert!(self.operation_scope.is_empty());
return Scope(self);
struct Scope<'a, 'b>(&'b mut TypingContext<'a>)
struct Scope<'a, 'b, TOperator>(&'b mut TypingContext<'a, TOperator>)
where
'a: 'b;
impl<'a, 'b> Deref for Scope<'a, 'b>
impl<'a, 'b, TOperator> Deref for Scope<'a, 'b, TOperator>
where
'a: 'b,
{
type Target = TypingContext<'a>;
fn deref(&self) -> &TypingContext<'a> {
type Target = TypingContext<'a, TOperator>;
fn deref(&self) -> &TypingContext<'a, TOperator> {
self.0
}
}
impl<'a, 'b> DerefMut for Scope<'a, 'b>
impl<'a, 'b, TOperator> DerefMut for Scope<'a, 'b, TOperator>
where
'a: 'b,
{
fn deref_mut(&mut self) -> &mut TypingContext<'a> {
fn deref_mut(&mut self) -> &mut TypingContext<'a, TOperator> {
self.0
}
}
impl Drop for Scope<'_, '_> {
impl<TOperator> Drop for Scope<'_, '_, TOperator> {
fn drop(&mut self) {
self.0.operation_scope.clear();
}
}
}
fn remember_boolean_literal(&mut self, b: &'a Boolean<'a>, ty: TypeVar<'a>) {
fn remember_boolean_literal(&mut self, b: &'a Boolean<'a, TOperator>, ty: TypeVar<'a>) {
self.assert_is_bool(b.span, &ty);
self.boolean_literals.push((b, ty));
}
fn remember_integer_literal(&mut self, i: &'a Integer<'a>, ty: TypeVar<'a>) {
fn remember_integer_literal(&mut self, i: &'a Integer<'a, TOperator>, ty: TypeVar<'a>) {
self.assert_is_integer(i.span, &ty);
self.integer_literals.push((i, ty));
}
fn remember_rhs_operation(&mut self, op: &'a Operation<'a, Rhs<'a>>, ty: TypeVar<'a>) {
fn remember_rhs_operation(
&mut self,
op: &'a Operation<'a, TOperator, Rhs<'a, TOperator>>,
ty: TypeVar<'a>,
) {
self.rhs_operations.push((op, ty));
}
@@ -638,7 +654,8 @@ impl<'a> TypingContext<'a> {
}
}
impl<'a> TypingContextTrait<'a> for TypingContext<'a> {
impl<'a, TOperator> TypingContextTrait<'a> for TypingContext<'a, TOperator> {
type Span = Span;
type TypeVariable = TypeVar<'a>;
fn cc(&mut self, span: Span) -> TypeVar<'a> {
@@ -737,13 +754,19 @@ impl<'a> TypingContextTrait<'a> for TypingContext<'a> {
}
}
#[derive(Clone)]
pub(crate) struct TypeVar<'a> {
#[derive(Clone, Debug)]
struct TypeVar<'a> {
kind: z3::ast::Datatype<'a>,
width: z3::ast::BV<'a>,
}
fn verify_optimization(z3: &z3::Context, opt: &Optimization) -> VerifyResult<()> {
fn verify_optimization<TOperator>(
z3: &z3::Context,
opt: &Optimization<TOperator>,
) -> VerifyResult<()>
where
TOperator: Copy + Debug + Eq + Hash + TypingRules,
{
let mut context = TypingContext::new(z3);
collect_type_constraints(&mut context, opt)?;
context.type_check(opt.span)?;
@@ -755,10 +778,13 @@ fn verify_optimization(z3: &z3::Context, opt: &Optimization) -> VerifyResult<()>
Ok(())
}
fn collect_type_constraints<'a>(
context: &mut TypingContext<'a>,
opt: &'a Optimization<'a>,
) -> VerifyResult<()> {
fn collect_type_constraints<'a, TOperator>(
context: &mut TypingContext<'a, TOperator>,
opt: &'a Optimization<'a, TOperator>,
) -> VerifyResult<()>
where
TOperator: Copy + Debug + Eq + Hash + TypingRules,
{
use crate::traversals::TraversalEvent as TE;
let lhs_ty = context.new_type_var();
@@ -780,8 +806,22 @@ fn collect_type_constraints<'a>(
// Build up the type constraints for the left-hand side.
for (event, node) in Dfs::new(&opt.lhs) {
match (event, node) {
(TE::Enter, DynAstRef::Pattern(Pattern::Constant(Constant { id, span })))
| (TE::Enter, DynAstRef::Pattern(Pattern::Variable(Variable { id, span }))) => {
(
TE::Enter,
DynAstRef::Pattern(Pattern::Constant(Constant {
id,
span,
marker: _,
})),
)
| (
TE::Enter,
DynAstRef::Pattern(Pattern::Variable(Variable {
id,
span,
marker: _,
})),
) => {
let id = context.get_or_create_type_var_for_id(*id);
context.assert_type_eq(*span, expected_types.last().unwrap(), &id, None);
}
@@ -805,11 +845,11 @@ fn collect_type_constraints<'a>(
let mut operand_types = vec![];
{
let mut scope = context.enter_operation_scope();
result_ty = op.operator.result_type(&mut *scope, op.span);
result_ty = op.operator.result_type(op.span, &mut *scope);
op.operator
.immediate_types(&mut *scope, op.span, &mut operand_types);
.immediate_types(op.span, &mut *scope, &mut operand_types);
op.operator
.param_types(&mut *scope, op.span, &mut operand_types);
.parameter_types(op.span, &mut *scope, &mut operand_types);
}
if op.operands.len() != operand_types.len() {
@@ -841,29 +881,22 @@ fn collect_type_constraints<'a>(
}
}
match op.operator {
Operator::Ireduce | Operator::Uextend | Operator::Sextend => {
if op.r#type.get().is_none() {
return Err(WastError::new(
op.span,
"`ireduce`, `sextend`, and `uextend` require an ascribed type, \
like `(sextend{i64} ...)`"
.into(),
)
.into());
}
}
_ => {}
if (op.operator.is_reduce() || op.operator.is_extend()) && op.r#type.get().is_none()
{
return Err(WastError::new(
op.span,
"`ireduce`, `sextend`, and `uextend` require an ascribed type, \
like `(sextend{i64} ...)`"
.into(),
)
.into());
}
match op.operator {
Operator::Uextend | Operator::Sextend => {
context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]);
}
Operator::Ireduce => {
context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]);
}
_ => {}
if op.operator.is_extend() {
context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]);
}
if op.operator.is_reduce() {
context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]);
}
if let Some(ty) = op.r#type.get() {
@@ -916,8 +949,22 @@ fn collect_type_constraints<'a>(
let ty = expected_types.last().unwrap();
context.assert_is_cc(cc.span, ty);
}
(TE::Enter, DynAstRef::Rhs(Rhs::Constant(Constant { span, id })))
| (TE::Enter, DynAstRef::Rhs(Rhs::Variable(Variable { span, id }))) => {
(
TE::Enter,
DynAstRef::Rhs(Rhs::Constant(Constant {
span,
id,
marker: _,
})),
)
| (
TE::Enter,
DynAstRef::Rhs(Rhs::Variable(Variable {
span,
id,
marker: _,
})),
) => {
let id_ty = context.get_type_var_for_id(*id)?;
context.assert_type_eq(*span, expected_types.last().unwrap(), &id_ty, None);
}
@@ -926,11 +973,11 @@ fn collect_type_constraints<'a>(
let mut operand_types = vec![];
{
let mut scope = context.enter_operation_scope();
result_ty = op.operator.result_type(&mut *scope, op.span);
result_ty = op.operator.result_type(op.span, &mut *scope);
op.operator
.immediate_types(&mut *scope, op.span, &mut operand_types);
.immediate_types(op.span, &mut *scope, &mut operand_types);
op.operator
.param_types(&mut *scope, op.span, &mut operand_types);
.parameter_types(op.span, &mut *scope, &mut operand_types);
}
if op.operands.len() != operand_types.len() {
@@ -965,29 +1012,22 @@ fn collect_type_constraints<'a>(
}
}
match op.operator {
Operator::Ireduce | Operator::Uextend | Operator::Sextend => {
if op.r#type.get().is_none() {
return Err(WastError::new(
op.span,
"`ireduce`, `sextend`, and `uextend` require an ascribed type, \
like `(sextend{i64} ...)`"
.into(),
)
.into());
}
}
_ => {}
if (op.operator.is_reduce() || op.operator.is_extend()) && op.r#type.get().is_none()
{
return Err(WastError::new(
op.span,
"`ireduce`, `sextend`, and `uextend` require an ascribed type, \
like `(sextend{i64} ...)`"
.into(),
)
.into());
}
match op.operator {
Operator::Uextend | Operator::Sextend => {
context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]);
}
Operator::Ireduce => {
context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]);
}
_ => {}
if op.operator.is_extend() {
context.assert_bit_width_gt(op.span, &result_ty, &operand_types[0]);
}
if op.operator.is_reduce() {
context.assert_bit_width_lt(op.span, &result_ty, &operand_types[0]);
}
if let Some(ty) = op.r#type.get() {
@@ -1017,11 +1057,11 @@ fn collect_type_constraints<'a>(
let mut operand_types = vec![];
{
let mut scope = context.enter_operation_scope();
result_ty = unq.operator.result_type(&mut *scope, unq.span);
result_ty = unq.operator.result_type(unq.span, &mut *scope);
unq.operator
.immediate_types(&mut *scope, unq.span, &mut operand_types);
.immediate_types(unq.span, &mut *scope, &mut operand_types);
unq.operator
.param_types(&mut *scope, unq.span, &mut operand_types);
.parameter_types(unq.span, &mut *scope, &mut operand_types);
}
if unq.operands.len() != operand_types.len() {
@@ -1068,9 +1108,9 @@ fn collect_type_constraints<'a>(
Ok(())
}
fn type_constrain_precondition<'a>(
context: &mut TypingContext<'a>,
pre: &Precondition<'a>,
fn type_constrain_precondition<'a, TOperator>(
context: &mut TypingContext<'a, TOperator>,
pre: &Precondition<'a, TOperator>,
) -> VerifyResult<()> {
match pre.constraint {
Constraint::BitWidth => {
@@ -1182,13 +1222,14 @@ fn type_constrain_precondition<'a>(
#[cfg(test)]
mod tests {
use super::*;
use peepmatic_test_operator::TestOperator;
macro_rules! verify_ok {
($name:ident, $src:expr) => {
#[test]
fn $name() {
let buf = wast::parser::ParseBuffer::new($src).expect("should lex OK");
let opts = match wast::parser::parse::<Optimizations>(&buf) {
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
Ok(opts) => opts,
Err(mut e) => {
e.set_path(Path::new(stringify!($name)));
@@ -1215,7 +1256,7 @@ mod tests {
#[test]
fn $name() {
let buf = wast::parser::ParseBuffer::new($src).expect("should lex OK");
let opts = match wast::parser::parse::<Optimizations>(&buf) {
let opts = match wast::parser::parse::<Optimizations<TestOperator>>(&buf) {
Ok(opts) => opts,
Err(mut e) => {
e.set_path(Path::new(stringify!($name)));

View File

@@ -16,8 +16,10 @@ use std::process::{Command, Stdio};
// note that this list must be topologically sorted by dependencies
const CRATES_TO_PUBLISH: &[&str] = &[
// peepmatic
"peepmatic-traits",
"peepmatic-macro",
"peepmatic-automata",
"peepmatic-test-operator",
"peepmatic-runtime",
"peepmatic",
// cranelift