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.
394 lines
14 KiB
Rust
394 lines
14 KiB
Rust
use peepmatic_runtime::{
|
|
cc::ConditionCode,
|
|
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,
|
|
};
|
|
|
|
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(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(TestOperator::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(TestOperator::Iadd, Type::i32(), vec![], vec![five, zero]);
|
|
|
|
let expected = program.new_instruction(
|
|
TestOperator::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(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());
|
|
}
|
|
|
|
#[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(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);
|
|
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(TestOperator::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(
|
|
TestOperator::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(
|
|
TestOperator::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(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(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(TestOperator::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(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
|
|
|
|
let imul_imm = program.new_instruction(
|
|
TestOperator::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(TestOperator::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(TestOperator::Imul, Type::i32(), vec![], vec![five, two]);
|
|
|
|
let imul_imm = program.new_instruction(
|
|
TestOperator::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(TestOperator::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(TestOperator::Isub, Type::i64(), vec![], vec![five, two]);
|
|
|
|
let iadd_imm = program.new_instruction(
|
|
TestOperator::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(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(
|
|
TestOperator::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(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(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(
|
|
TestOperator::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(
|
|
TestOperator::ImulImm,
|
|
Type::i64(),
|
|
vec![Constant::Int(1, BitWidth::SixtyFour).into()],
|
|
vec![x],
|
|
);
|
|
let iadd = program.new_instruction(
|
|
TestOperator::Iadd,
|
|
Type::i64(),
|
|
vec![],
|
|
vec![imul_imm, imul_imm],
|
|
);
|
|
let ishl_imm = program.new_instruction(
|
|
TestOperator::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(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(
|
|
TestOperator::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(TestOperator::Iadd, Type::i32(), vec![], vec![x, y]);
|
|
let iadd_imm = program.new_instruction(
|
|
TestOperator::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(TestOperator::Iadd, Type::i16(), vec![], vec![x, y]);
|
|
let iadd_imm = program.new_instruction(
|
|
TestOperator::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));
|
|
}
|