peepmatic: Introduce the peepmatic-test crate
This crate provides testing utilities for `peepmatic`, and a test-only instruction set we can use to check that various optimizations do or don't apply.
This commit is contained in:
14
cranelift/peepmatic/crates/test/Cargo.toml
Normal file
14
cranelift/peepmatic/crates/test/Cargo.toml
Normal file
@@ -0,0 +1,14 @@
|
||||
[package]
|
||||
name = "peepmatic-test"
|
||||
version = "0.1.0"
|
||||
authors = ["Nick Fitzgerald <fitzgen@gmail.com>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.7.1"
|
||||
log = "0.4.8"
|
||||
peepmatic = { path = "../.." }
|
||||
peepmatic-runtime = { path = "../runtime" }
|
||||
531
cranelift/peepmatic/crates/test/src/lib.rs
Normal file
531
cranelift/peepmatic/crates/test/src/lib.rs
Normal file
@@ -0,0 +1,531 @@
|
||||
//! Testing utilities and a testing-only instruction set for `peepmatic`.
|
||||
|
||||
#![deny(missing_debug_implementations)]
|
||||
|
||||
use peepmatic_runtime::{
|
||||
cc::ConditionCode,
|
||||
instruction_set::InstructionSet,
|
||||
operator::Operator,
|
||||
part::{Constant, Part},
|
||||
paths::Path,
|
||||
r#type::{BitWidth, Kind, Type},
|
||||
};
|
||||
use std::cell::RefCell;
|
||||
use std::collections::BTreeMap;
|
||||
use std::convert::TryFrom;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
|
||||
pub struct Instruction(pub usize);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct InstructionData {
|
||||
pub operator: Operator,
|
||||
pub r#type: Type,
|
||||
pub immediates: Vec<Immediate>,
|
||||
pub arguments: Vec<Instruction>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Immediate {
|
||||
Constant(Constant),
|
||||
ConditionCode(ConditionCode),
|
||||
}
|
||||
|
||||
impl Immediate {
|
||||
fn unwrap_constant(&self) -> Constant {
|
||||
match *self {
|
||||
Immediate::Constant(c) => c,
|
||||
_ => panic!("not a constant"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Constant> for Immediate {
|
||||
fn from(c: Constant) -> Immediate {
|
||||
Immediate::Constant(c)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ConditionCode> for Immediate {
|
||||
fn from(cc: ConditionCode) -> Immediate {
|
||||
Immediate::ConditionCode(cc)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Immediate> for Part<Instruction> {
|
||||
fn from(imm: Immediate) -> Part<Instruction> {
|
||||
match imm {
|
||||
Immediate::Constant(c) => Part::Constant(c),
|
||||
Immediate::ConditionCode(cc) => Part::ConditionCode(cc),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Part<Instruction>> for Immediate {
|
||||
type Error = &'static str;
|
||||
|
||||
fn try_from(part: Part<Instruction>) -> Result<Immediate, Self::Error> {
|
||||
match part {
|
||||
Part::Constant(c) => Ok(Immediate::Constant(c)),
|
||||
Part::ConditionCode(c) => Ok(Immediate::ConditionCode(c)),
|
||||
Part::Instruction(_) => Err("instruction parts cannot be converted into immediates"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Program {
|
||||
instr_counter: usize,
|
||||
instruction_data: BTreeMap<Instruction, InstructionData>,
|
||||
replacements: RefCell<BTreeMap<Instruction, Instruction>>,
|
||||
}
|
||||
|
||||
impl Program {
|
||||
/// Are `a` and `b` structurally equivalent, even if they use different
|
||||
/// `Instruction`s for various arguments?
|
||||
pub fn structurally_eq(&mut self, a: Instruction, b: Instruction) -> bool {
|
||||
macro_rules! ensure_eq {
|
||||
($a:expr, $b:expr) => {{
|
||||
let a = &$a;
|
||||
let b = &$b;
|
||||
if a != b {
|
||||
log::debug!(
|
||||
"{} != {} ({:?} != {:?})",
|
||||
stringify!($a),
|
||||
stringify!($b),
|
||||
a,
|
||||
b
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
let a = self.resolve(a);
|
||||
let b = self.resolve(b);
|
||||
if a == b {
|
||||
return true;
|
||||
}
|
||||
|
||||
let a = self.data(a);
|
||||
let b = self.data(b);
|
||||
log::debug!("structurally_eq({:?}, {:?})", a, b);
|
||||
|
||||
ensure_eq!(a.operator, b.operator);
|
||||
ensure_eq!(a.r#type, b.r#type);
|
||||
ensure_eq!(a.immediates, b.immediates);
|
||||
ensure_eq!(a.arguments.len(), b.arguments.len());
|
||||
a.arguments
|
||||
.clone()
|
||||
.into_iter()
|
||||
.zip(b.arguments.clone().into_iter())
|
||||
.all(|(a, b)| self.structurally_eq(a, b))
|
||||
}
|
||||
|
||||
pub fn instructions(&self) -> impl Iterator<Item = (Instruction, &InstructionData)> {
|
||||
self.instruction_data.iter().map(|(k, v)| (*k, v))
|
||||
}
|
||||
|
||||
pub fn replace_instruction(&mut self, old: Instruction, new: Instruction) {
|
||||
log::debug!("replacing {:?} with {:?}", old, new);
|
||||
|
||||
let old = self.resolve(old);
|
||||
let new = self.resolve(new);
|
||||
if old == new {
|
||||
return;
|
||||
}
|
||||
|
||||
let mut replacements = self.replacements.borrow_mut();
|
||||
let existing_replacement = replacements.insert(old, new);
|
||||
assert!(existing_replacement.is_none());
|
||||
|
||||
let old_data = self.instruction_data.remove(&old);
|
||||
assert!(old_data.is_some());
|
||||
}
|
||||
|
||||
pub fn resolve(&self, inst: Instruction) -> Instruction {
|
||||
let mut replacements = self.replacements.borrow_mut();
|
||||
let mut replacements_followed = 0;
|
||||
let mut resolved = inst;
|
||||
while let Some(i) = replacements.get(&resolved).cloned() {
|
||||
log::trace!("resolving replaced instruction: {:?} -> {:?}", resolved, i);
|
||||
replacements_followed += 1;
|
||||
assert!(
|
||||
replacements_followed <= replacements.len(),
|
||||
"cyclic replacements"
|
||||
);
|
||||
|
||||
resolved = i;
|
||||
continue;
|
||||
}
|
||||
|
||||
if inst != resolved {
|
||||
let old_replacement = replacements.insert(inst, resolved);
|
||||
assert!(old_replacement.is_some());
|
||||
}
|
||||
|
||||
resolved
|
||||
}
|
||||
|
||||
pub fn data(&self, inst: Instruction) -> &InstructionData {
|
||||
let inst = self.resolve(inst);
|
||||
&self.instruction_data[&inst]
|
||||
}
|
||||
|
||||
pub fn new_instruction(
|
||||
&mut self,
|
||||
operator: Operator,
|
||||
r#type: Type,
|
||||
immediates: Vec<Immediate>,
|
||||
arguments: Vec<Instruction>,
|
||||
) -> Instruction {
|
||||
assert_eq!(
|
||||
operator.immediates_arity() as usize,
|
||||
immediates.len(),
|
||||
"wrong number of immediates for {:?}: expected {}, found {}",
|
||||
operator,
|
||||
operator.immediates_arity(),
|
||||
immediates.len(),
|
||||
);
|
||||
assert_eq!(
|
||||
operator.params_arity() as usize,
|
||||
arguments.len(),
|
||||
"wrong number of arguments for {:?}: expected {}, found {}",
|
||||
operator,
|
||||
operator.params_arity(),
|
||||
arguments.len(),
|
||||
);
|
||||
|
||||
assert!(!r#type.bit_width.is_polymorphic());
|
||||
assert!(immediates.iter().all(|imm| match imm {
|
||||
Immediate::Constant(Constant::Bool(_, w))
|
||||
| Immediate::Constant(Constant::Int(_, w)) => !w.is_polymorphic(),
|
||||
Immediate::ConditionCode(_) => true,
|
||||
}));
|
||||
|
||||
let inst = Instruction(self.instr_counter);
|
||||
self.instr_counter += 1;
|
||||
|
||||
let data = InstructionData {
|
||||
operator,
|
||||
r#type,
|
||||
immediates,
|
||||
arguments,
|
||||
};
|
||||
|
||||
log::trace!("new instruction: {:?} = {:?}", inst, data);
|
||||
self.instruction_data.insert(inst, data);
|
||||
inst
|
||||
}
|
||||
|
||||
pub fn r#const(&mut self, c: Constant, root_bit_width: BitWidth) -> Instruction {
|
||||
assert!(!root_bit_width.is_polymorphic());
|
||||
match c {
|
||||
Constant::Bool(_, bit_width) => self.new_instruction(
|
||||
Operator::Bconst,
|
||||
if bit_width.is_polymorphic() {
|
||||
Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width: root_bit_width,
|
||||
}
|
||||
} else {
|
||||
Type {
|
||||
kind: Kind::Bool,
|
||||
bit_width,
|
||||
}
|
||||
},
|
||||
vec![c.into()],
|
||||
vec![],
|
||||
),
|
||||
Constant::Int(_, bit_width) => self.new_instruction(
|
||||
Operator::Iconst,
|
||||
if bit_width.is_polymorphic() {
|
||||
Type {
|
||||
kind: Kind::Int,
|
||||
bit_width: root_bit_width,
|
||||
}
|
||||
} else {
|
||||
Type {
|
||||
kind: Kind::Int,
|
||||
bit_width,
|
||||
}
|
||||
},
|
||||
vec![c.into()],
|
||||
vec![],
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn instruction_to_constant(&mut self, inst: Instruction) -> Option<Constant> {
|
||||
match self.data(inst) {
|
||||
InstructionData {
|
||||
operator: Operator::Iconst,
|
||||
immediates,
|
||||
..
|
||||
} => Some(immediates[0].unwrap_constant()),
|
||||
InstructionData {
|
||||
operator: Operator::Bconst,
|
||||
immediates,
|
||||
..
|
||||
} => Some(immediates[0].unwrap_constant()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn part_to_immediate(&mut self, part: Part<Instruction>) -> Result<Immediate, &'static str> {
|
||||
match part {
|
||||
Part::Instruction(i) => self
|
||||
.instruction_to_constant(i)
|
||||
.map(|c| c.into())
|
||||
.ok_or("non-constant instructions cannot be converted into immediates"),
|
||||
Part::Constant(c) => Ok(c.into()),
|
||||
Part::ConditionCode(cc) => Ok(Immediate::ConditionCode(cc)),
|
||||
}
|
||||
}
|
||||
|
||||
fn part_to_instruction(
|
||||
&mut self,
|
||||
root: Instruction,
|
||||
part: Part<Instruction>,
|
||||
) -> Result<Instruction, &'static str> {
|
||||
match part {
|
||||
Part::Instruction(inst) => {
|
||||
let inst = self.resolve(inst);
|
||||
Ok(inst)
|
||||
}
|
||||
Part::Constant(c) => {
|
||||
let root_width = self.data(root).r#type.bit_width;
|
||||
Ok(self.r#const(c, root_width))
|
||||
}
|
||||
Part::ConditionCode(_) => Err("condition codes cannot be converted into instructions"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct TestIsa {
|
||||
pub native_word_size_in_bits: u8,
|
||||
}
|
||||
|
||||
impl<'a> InstructionSet<'a> for TestIsa {
|
||||
type Context = Program;
|
||||
|
||||
type Instruction = Instruction;
|
||||
|
||||
fn replace_instruction(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
old: Instruction,
|
||||
new: Part<Instruction>,
|
||||
) -> Instruction {
|
||||
log::debug!("replace_instruction({:?}, {:?})", old, new);
|
||||
let new = program.part_to_instruction(old, new).unwrap();
|
||||
program.replace_instruction(old, new);
|
||||
new
|
||||
}
|
||||
|
||||
fn get_part_at_path(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
root: Instruction,
|
||||
path: Path,
|
||||
) -> Option<Part<Instruction>> {
|
||||
log::debug!("get_part_at_path({:?})", path);
|
||||
|
||||
assert!(!path.0.is_empty());
|
||||
assert_eq!(path.0[0], 0);
|
||||
|
||||
let mut part = Part::Instruction(root);
|
||||
for p in &path.0[1..] {
|
||||
if let Part::Instruction(inst) = part {
|
||||
let data = program.data(inst);
|
||||
let p = *p as usize;
|
||||
|
||||
if p < data.immediates.len() {
|
||||
part = data.immediates[p].into();
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some(inst) = data.arguments.get(p - data.immediates.len()).copied() {
|
||||
part = Part::Instruction(inst);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
return None;
|
||||
}
|
||||
|
||||
Some(part)
|
||||
}
|
||||
|
||||
fn operator(&self, program: &mut Program, instr: Instruction) -> Option<Operator> {
|
||||
log::debug!("operator({:?})", instr);
|
||||
let data = program.data(instr);
|
||||
Some(data.operator)
|
||||
}
|
||||
|
||||
fn make_inst_1(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
root: Instruction,
|
||||
operator: Operator,
|
||||
r#type: Type,
|
||||
a: Part<Instruction>,
|
||||
) -> Instruction {
|
||||
log::debug!(
|
||||
"make_inst_1(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n)",
|
||||
operator,
|
||||
r#type,
|
||||
a,
|
||||
);
|
||||
|
||||
let (imms, args) = match operator.immediates_arity() {
|
||||
0 => {
|
||||
assert_eq!(operator.params_arity(), 1);
|
||||
(vec![], vec![program.part_to_instruction(root, a).unwrap()])
|
||||
}
|
||||
1 => {
|
||||
assert_eq!(operator.params_arity(), 0);
|
||||
(vec![program.part_to_immediate(a).unwrap()], vec![])
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
program.new_instruction(operator, r#type, imms, args)
|
||||
}
|
||||
|
||||
fn make_inst_2(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
root: Instruction,
|
||||
operator: Operator,
|
||||
r#type: Type,
|
||||
a: Part<Instruction>,
|
||||
b: Part<Instruction>,
|
||||
) -> Instruction {
|
||||
log::debug!(
|
||||
"make_inst_2(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n\tb = {:?},\n)",
|
||||
operator,
|
||||
r#type,
|
||||
a,
|
||||
b,
|
||||
);
|
||||
|
||||
let (imms, args) = match operator.immediates_arity() {
|
||||
0 => {
|
||||
assert_eq!(operator.params_arity(), 2);
|
||||
(
|
||||
vec![],
|
||||
vec![
|
||||
program.part_to_instruction(root, a).unwrap(),
|
||||
program.part_to_instruction(root, b).unwrap(),
|
||||
],
|
||||
)
|
||||
}
|
||||
1 => {
|
||||
assert_eq!(operator.params_arity(), 1);
|
||||
(
|
||||
vec![program.part_to_immediate(a).unwrap()],
|
||||
vec![program.part_to_instruction(root, b).unwrap()],
|
||||
)
|
||||
}
|
||||
2 => {
|
||||
assert_eq!(operator.params_arity(), 0);
|
||||
(
|
||||
vec![
|
||||
program.part_to_immediate(a).unwrap(),
|
||||
program.part_to_immediate(b).unwrap(),
|
||||
],
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
program.new_instruction(operator, r#type, imms, args)
|
||||
}
|
||||
|
||||
fn make_inst_3(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
root: Instruction,
|
||||
operator: Operator,
|
||||
r#type: Type,
|
||||
a: Part<Instruction>,
|
||||
b: Part<Instruction>,
|
||||
c: Part<Instruction>,
|
||||
) -> Instruction {
|
||||
log::debug!(
|
||||
"make_inst_3(\n\toperator = {:?},\n\ttype = {},\n\ta = {:?},\n\tb = {:?},\n\tc = {:?},\n)",
|
||||
operator,
|
||||
r#type,
|
||||
a,
|
||||
b,
|
||||
c,
|
||||
);
|
||||
let (imms, args) = match operator.immediates_arity() {
|
||||
0 => {
|
||||
assert_eq!(operator.params_arity(), 3);
|
||||
(
|
||||
vec![],
|
||||
vec![
|
||||
program.part_to_instruction(root, a).unwrap(),
|
||||
program.part_to_instruction(root, b).unwrap(),
|
||||
program.part_to_instruction(root, c).unwrap(),
|
||||
],
|
||||
)
|
||||
}
|
||||
1 => {
|
||||
assert_eq!(operator.params_arity(), 2);
|
||||
(
|
||||
vec![program.part_to_immediate(a).unwrap()],
|
||||
vec![
|
||||
program.part_to_instruction(root, b).unwrap(),
|
||||
program.part_to_instruction(root, c).unwrap(),
|
||||
],
|
||||
)
|
||||
}
|
||||
2 => {
|
||||
assert_eq!(operator.params_arity(), 1);
|
||||
(
|
||||
vec![
|
||||
program.part_to_immediate(a).unwrap(),
|
||||
program.part_to_immediate(b).unwrap(),
|
||||
],
|
||||
vec![program.part_to_instruction(root, c).unwrap()],
|
||||
)
|
||||
}
|
||||
3 => {
|
||||
assert_eq!(operator.params_arity(), 0);
|
||||
(
|
||||
vec![
|
||||
program.part_to_immediate(a).unwrap(),
|
||||
program.part_to_immediate(b).unwrap(),
|
||||
program.part_to_immediate(c).unwrap(),
|
||||
],
|
||||
vec![],
|
||||
)
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
program.new_instruction(operator, r#type, imms, args)
|
||||
}
|
||||
|
||||
fn instruction_to_constant(
|
||||
&self,
|
||||
program: &mut Program,
|
||||
inst: Instruction,
|
||||
) -> Option<Constant> {
|
||||
log::debug!("instruction_to_constant({:?})", inst);
|
||||
program.instruction_to_constant(inst)
|
||||
}
|
||||
|
||||
fn instruction_result_bit_width(&self, program: &mut Program, inst: Instruction) -> u8 {
|
||||
log::debug!("instruction_result_bit_width({:?})", inst);
|
||||
let ty = program.data(inst).r#type;
|
||||
ty.bit_width.fixed_width().unwrap()
|
||||
}
|
||||
|
||||
fn native_word_size_in_bits(&self, _program: &mut Program) -> u8 {
|
||||
log::debug!("native_word_size_in_bits");
|
||||
self.native_word_size_in_bits
|
||||
}
|
||||
}
|
||||
393
cranelift/peepmatic/crates/test/tests/tests.rs
Normal file
393
cranelift/peepmatic/crates/test/tests/tests.rs
Normal file
@@ -0,0 +1,393 @@
|
||||
use peepmatic_runtime::{
|
||||
cc::ConditionCode,
|
||||
operator::Operator,
|
||||
part::Constant,
|
||||
r#type::{BitWidth, Type},
|
||||
};
|
||||
use peepmatic_test::*;
|
||||
|
||||
const TEST_ISA: TestIsa = TestIsa {
|
||||
native_word_size_in_bits: 32,
|
||||
};
|
||||
|
||||
macro_rules! optimizer {
|
||||
($opts:ident, $source:expr) => {{
|
||||
let _ = env_logger::try_init();
|
||||
$opts = peepmatic::compile_str($source, std::path::Path::new("peepmatic-test")).unwrap();
|
||||
$opts.optimizer(TEST_ISA)
|
||||
}};
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opcode() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (iadd $x 0) $x)");
|
||||
|
||||
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 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 replacement = optimizer.apply_one(&mut program, add);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (iadd $C $x) (iadd_imm $C $x))");
|
||||
|
||||
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 expected = program.new_instruction(
|
||||
Operator::IaddImm,
|
||||
Type::i32(),
|
||||
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
|
||||
vec![zero],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, add);
|
||||
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 replacement = optimizer.apply_one(&mut program, add);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boolean() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (bint true) 1)");
|
||||
|
||||
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 one = program.r#const(Constant::Int(1, BitWidth::One), BitWidth::ThirtyTwo);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, bint);
|
||||
let new = new.expect("optimization should have applied");
|
||||
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 replacement = optimizer.apply_one(&mut program, bint);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn condition_codes() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (icmp eq $x $x) true)");
|
||||
|
||||
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,
|
||||
Type::b1(),
|
||||
vec![ConditionCode::Eq.into()],
|
||||
vec![five, five],
|
||||
);
|
||||
let t = program.r#const(Constant::Bool(true, BitWidth::One), BitWidth::One);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, icmp_eq);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, t));
|
||||
|
||||
let icmp_ne = program.new_instruction(
|
||||
Operator::Icmp,
|
||||
Type::b1(),
|
||||
vec![ConditionCode::Ne.into()],
|
||||
vec![five, five],
|
||||
);
|
||||
let replacement = optimizer.apply_one(&mut program, icmp_ne);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn is_power_of_two() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
(=> (when (imul $x $C)
|
||||
(is-power-of-two $C))
|
||||
(ishl $x $(log2 $C)))
|
||||
"
|
||||
);
|
||||
|
||||
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 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 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 replacement = optimizer.apply_one(&mut program, imul);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bit_width() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
(=> (when (imul $C $x)
|
||||
(bit-width $C 32))
|
||||
(imul_imm $C $x))
|
||||
"
|
||||
);
|
||||
|
||||
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_imm = program.new_instruction(
|
||||
Operator::ImulImm,
|
||||
Type::i32(),
|
||||
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
|
||||
vec![two],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, imul);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, imul_imm));
|
||||
|
||||
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 replacement = optimizer.apply_one(&mut program, imul);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn fits_in_native_word() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
(=> (when (imul $C $x)
|
||||
(fits-in-native-word $C))
|
||||
(imul_imm $C $x))
|
||||
"
|
||||
);
|
||||
|
||||
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_imm = program.new_instruction(
|
||||
Operator::ImulImm,
|
||||
Type::i32(),
|
||||
vec![Constant::Int(5, BitWidth::ThirtyTwo).into()],
|
||||
vec![two],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, imul);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, imul_imm));
|
||||
|
||||
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 replacement = optimizer.apply_one(&mut program, imul);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unquote_neg() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
(=> (isub $x $C)
|
||||
(iadd_imm $(neg $C) $x))
|
||||
"
|
||||
);
|
||||
|
||||
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 iadd_imm = program.new_instruction(
|
||||
Operator::IaddImm,
|
||||
Type::i64(),
|
||||
vec![Constant::Int(-2 as _, BitWidth::SixtyFour).into()],
|
||||
vec![five],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, isub);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, iadd_imm));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subsumption() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(
|
||||
opts,
|
||||
"
|
||||
;; NB: the following optimizations are ordered from least to most general, so
|
||||
;; the first applicable optimization should be the one that is applied.
|
||||
|
||||
(=> (iadd (iadd (iadd $w $x) $y) $z)
|
||||
(iadd (iadd $w $x) (iadd $y $z)))
|
||||
|
||||
(=> (iadd $C1 $C2)
|
||||
$(iadd $C1 $C2))
|
||||
|
||||
(=> (iadd $C $x)
|
||||
(iadd_imm $C $x))
|
||||
|
||||
(=> (iadd $x $x)
|
||||
(ishl_imm 1 $x))
|
||||
"
|
||||
);
|
||||
|
||||
let mut program = Program::default();
|
||||
|
||||
let w = program.r#const(Constant::Int(11, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let x = program.r#const(Constant::Int(22, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let y = program.r#const(Constant::Int(33, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
let z = program.r#const(Constant::Int(44, BitWidth::SixtyFour), BitWidth::SixtyFour);
|
||||
|
||||
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 expected = program.new_instruction(
|
||||
Operator::Iadd,
|
||||
Type::i64(),
|
||||
vec![],
|
||||
vec![expected_lhs, expected_rhs],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, expected));
|
||||
|
||||
log::debug!("(iadd w x) => y");
|
||||
|
||||
let iadd = program.new_instruction(Operator::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_imm = program.new_instruction(
|
||||
Operator::IaddImm,
|
||||
Type::i64(),
|
||||
vec![Constant::Int(22, BitWidth::SixtyFour).into()],
|
||||
vec![iadd_y_z],
|
||||
);
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, iadd_imm));
|
||||
|
||||
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,
|
||||
Type::i64(),
|
||||
vec![Constant::Int(1, BitWidth::SixtyFour).into()],
|
||||
vec![x],
|
||||
);
|
||||
let iadd = program.new_instruction(
|
||||
Operator::Iadd,
|
||||
Type::i64(),
|
||||
vec![],
|
||||
vec![imul_imm, imul_imm],
|
||||
);
|
||||
let ishl_imm = program.new_instruction(
|
||||
Operator::IshlImm,
|
||||
Type::i64(),
|
||||
vec![Constant::Int(1, BitWidth::SixtyFour).into()],
|
||||
vec![imul_imm],
|
||||
);
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, ishl_imm));
|
||||
|
||||
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 iadd = program.new_instruction(
|
||||
Operator::Iadd,
|
||||
Type::i64(),
|
||||
vec![],
|
||||
vec![imul_w_x, imul_y_z],
|
||||
);
|
||||
|
||||
let replacement = optimizer.apply_one(&mut program, iadd);
|
||||
assert!(replacement.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn polymorphic_bit_widths() {
|
||||
let opts;
|
||||
let mut optimizer = optimizer!(opts, "(=> (iadd $C $x) (iadd_imm $C $x))");
|
||||
|
||||
let mut program = Program::default();
|
||||
|
||||
// Applies to 32 bit adds.
|
||||
|
||||
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_imm = program.new_instruction(
|
||||
Operator::IaddImm,
|
||||
Type::i32(),
|
||||
vec![Constant::Int(42, BitWidth::ThirtyTwo).into()],
|
||||
vec![y],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, iadd_imm));
|
||||
|
||||
// Applies to 16 bit adds.
|
||||
|
||||
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_imm = program.new_instruction(
|
||||
Operator::IaddImm,
|
||||
Type::i16(),
|
||||
vec![Constant::Int(42, BitWidth::Sixteen).into()],
|
||||
vec![y],
|
||||
);
|
||||
|
||||
let new = optimizer.apply_one(&mut program, iadd);
|
||||
let new = new.expect("optimization should have applied");
|
||||
assert!(program.structurally_eq(new, iadd_imm));
|
||||
}
|
||||
Reference in New Issue
Block a user