use peepmatic_runtime::linear;
use std::cmp::Ordering;
use std::fmt::Debug;
use std::hash::Hash;
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,
..
} = opts;
optimizations.sort_by(compare_optimization_generality);
debug_assert!(is_sorted_by_generality(opts));
}
pub fn sort_lexicographically<TOperator>(opts: &mut linear::Optimizations<TOperator>)
where
TOperator: Copy + Debug + Eq + Hash,
{
let linear::Optimizations {
ref mut optimizations,
..
} = opts;
optimizations.sort_by(|a, b| compare_optimizations(a, b, |a_len, b_len| a_len.cmp(&b_len)));
}
fn compare_optimizations<TOperator>(
a: &linear::Optimization<TOperator>,
b: &linear::Optimization<TOperator>,
compare_lengths: impl Fn(usize, usize) -> Ordering,
) -> Ordering
where
TOperator: Copy + Debug + Eq + Hash,
{
for (a, b) in a.matches.iter().zip(b.matches.iter()) {
let c = compare_match_op_generality(a.operation, b.operation);
if c != Ordering::Equal {
return c;
}
let c = match (a.expected, b.expected) {
(Ok(a), Ok(b)) => a.cmp(&b).reverse(),
(Err(_), Ok(_)) => Ordering::Greater,
(Ok(_), Err(_)) => Ordering::Less,
(Err(linear::Else), Err(linear::Else)) => Ordering::Equal,
};
if c != Ordering::Equal {
return c;
}
}
compare_lengths(a.matches.len(), b.matches.len())
}
fn compare_optimization_generality<TOperator>(
a: &linear::Optimization<TOperator>,
b: &linear::Optimization<TOperator>,
) -> Ordering
where
TOperator: Copy + Debug + Eq + Hash,
{
compare_optimizations(a, b, |a_len, b_len| {
a_len.cmp(&b_len).reverse()
})
}
fn compare_match_op_generality(a: linear::MatchOp, b: linear::MatchOp) -> Ordering {
use linear::MatchOp::*;
match (a, b) {
(Opcode(a), Opcode(b)) => a.cmp(&b),
(Opcode(_), _) => Ordering::Less,
(_, Opcode(_)) => Ordering::Greater,
(IntegerValue(a), IntegerValue(b)) => a.cmp(&b),
(IntegerValue(_), _) => Ordering::Less,
(_, IntegerValue(_)) => Ordering::Greater,
(BooleanValue(a), BooleanValue(b)) => a.cmp(&b),
(BooleanValue(_), _) => Ordering::Less,
(_, BooleanValue(_)) => Ordering::Greater,
(ConditionCode(a), ConditionCode(b)) => a.cmp(&b),
(ConditionCode(_), _) => Ordering::Less,
(_, ConditionCode(_)) => Ordering::Greater,
(IsConst(a), IsConst(b)) => a.cmp(&b),
(IsConst(_), _) => Ordering::Less,
(_, IsConst(_)) => Ordering::Greater,
(Eq(a1, b1), Eq(a2, b2)) => a1.cmp(&a2).then(b1.cmp(&b2)),
(Eq(..), _) => Ordering::Less,
(_, Eq(..)) => Ordering::Greater,
(IsPowerOfTwo(a), IsPowerOfTwo(b)) => a.cmp(&b),
(IsPowerOfTwo(_), _) => Ordering::Less,
(_, IsPowerOfTwo(_)) => Ordering::Greater,
(BitWidth(a), BitWidth(b)) => a.cmp(&b),
(BitWidth(_), _) => Ordering::Less,
(_, BitWidth(_)) => Ordering::Greater,
(FitsInNativeWord(a), FitsInNativeWord(b)) => a.cmp(&b),
(FitsInNativeWord(_), _) => Ordering::Less,
(_, FitsInNativeWord(_)) => Ordering::Greater,
(Nop, Nop) => Ordering::Equal,
}
}
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(&w[0], &w[1]) <= Ordering::Equal)
}
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(&w[0], &w[1], |a_len, b_len| a_len.cmp(&b_len)) <= Ordering::Equal
})
}
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![];
for opt in &mut opts.optimizations {
assert!(!opt.matches.is_empty());
let mut old_matches = opt.matches.iter().peekable();
let mut new_matches = vec![];
for (last_op, last_expected) in &prefix {
match old_matches.peek() {
None => {
break;
}
Some(inc) if *last_op == inc.operation => {
let inc = old_matches.next().unwrap();
new_matches.push(inc.clone());
if inc.expected != *last_expected {
break;
}
}
Some(_) => {
new_matches.push(linear::Match {
operation: *last_op,
expected: Err(linear::Else),
});
if last_expected.is_ok() {
break;
}
}
}
}
new_matches.extend(old_matches.cloned());
assert!(new_matches.len() >= opt.matches.len());
opt.matches = new_matches;
prefix.clear();
prefix.extend(opt.matches.iter().map(|inc| (inc.operation, inc.expected)));
}
debug_assert!(is_sorted_by_generality(&opts));
}
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.matches.len() < 2 {
debug_assert!(!opt.matches.is_empty());
continue;
}
for i in (1..opt.matches.len()).rev() {
if let linear::MatchOp::Nop = opt.matches[i].operation {
opt.matches.remove(i);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ast::*;
use peepmatic_runtime::linear::{bool_to_match_result, Else, LhsId, MatchOp::*, MatchResult};
use peepmatic_test_operator::TestOperator;
use std::num::NonZeroU32;
#[test]
fn ok_non_zero_less_than_err_else() {
assert!(Ok(NonZeroU32::new(1).unwrap()) < Err(Else));
}
macro_rules! sorts_to {
($test_name:ident, $source:expr, $make_expected:expr) => {
#[test]
#[allow(unused_variables)]
fn $test_name() {
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
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)));
e.set_text($source);
eprintln!("{}", e);
panic!("should parse OK")
}
};
if let Err(mut e) = crate::verify(&opts) {
e.set_path(std::path::Path::new(stringify!($test_name)));
e.set_text($source);
eprintln!("{}", e);
panic!("should verify OK")
}
let mut opts = crate::linearize(&opts);
let before = opts
.optimizations
.iter()
.map(|o| {
o.matches
.iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
eprintln!("before = {:#?}", before);
sort_least_to_most_general(&mut opts);
let after = opts
.optimizations
.iter()
.map(|o| {
o.matches
.iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
eprintln!("after = {:#?}", before);
let linear::Optimizations {
mut integers,
optimizations,
} = opts;
let actual: Vec<Vec<_>> = optimizations
.iter()
.map(|o| {
o.matches
.iter()
.map(|i| (i.operation, i.expected))
.collect()
})
.collect();
let mut i = |i: u64| Ok(integers.intern(i).into());
let expected = $make_expected(&mut i);
assert_eq!(expected, actual);
}
};
}
macro_rules! match_in_same_order {
($test_name:ident, $source:expr, $make_expected:expr) => {
#[test]
#[allow(unused_variables)]
fn $test_name() {
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
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)));
e.set_text($source);
eprintln!("{}", e);
panic!("should parse OK")
}
};
if let Err(mut e) = crate::verify(&opts) {
e.set_path(std::path::Path::new(stringify!($test_name)));
e.set_text($source);
eprintln!("{}", e);
panic!("should verify OK")
}
let mut opts = crate::linearize(&opts);
sort_least_to_most_general(&mut opts);
let before = opts
.optimizations
.iter()
.map(|o| {
o.matches
.iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
eprintln!("before = {:#?}", before);
match_in_same_order(&mut opts);
let after = opts
.optimizations
.iter()
.map(|o| {
o.matches
.iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
eprintln!("after = {:#?}", before);
let linear::Optimizations {
mut integers,
optimizations,
} = opts;
let actual: Vec<Vec<_>> = optimizations
.iter()
.map(|o| {
o.matches
.iter()
.map(|i| (i.operation, i.expected))
.collect()
})
.collect();
let mut i = |i: u64| Ok(integers.intern(i).into());
let expected = $make_expected(&mut i);
assert_eq!(expected, actual);
}
};
}
sorts_to!(
test_sort_least_to_most_general,
"
(=> $x 0)
(=> (iadd $x $y) 0)
(=> (iadd $x $x) 0)
(=> (iadd $x $C) 0)
(=> (when (iadd $x $C) (is-power-of-two $C)) 0)
(=> (when (iadd $x $C) (bit-width $x 32)) 0)
(=> (iadd $x 42) 0)
(=> (iadd $x (iadd $y $z)) 0)
",
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(Opcode(LhsId(2)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IntegerValue(LhsId(2)), i(42))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IsConst(LhsId(2)), bool_to_match_result(true)),
(IsPowerOfTwo(LhsId(2)), bool_to_match_result(true))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IsConst(LhsId(2)), bool_to_match_result(true)),
(BitWidth(LhsId(1)), Ok(NonZeroU32::new(32).unwrap()))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IsConst(LhsId(2)), bool_to_match_result(true))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(Eq(LhsId(2), LhsId(1)), bool_to_match_result(true))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
],
vec![(Nop, Err(Else))]
]
);
sorts_to!(
expected_edges_are_sorted,
"
(=> (iadd 0 $x) $x)
(=> (iadd $x 0) $x)
(=> (imul 1 $x) $x)
(=> (imul $x 1) $x)
(=> (imul 2 $x) (ishl $x 1))
(=> (imul $x 2) (ishl $x 1))
",
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
(IntegerValue(LhsId(1)), i(2)),
(Nop, Err(Else))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
(IntegerValue(LhsId(1)), i(1)),
(Nop, Err(Else))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
(Nop, Err(Else)),
(IntegerValue(LhsId(2)), i(2))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
(Nop, Err(Else)),
(IntegerValue(LhsId(2)), i(1))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(IntegerValue(LhsId(1)), i(0)),
(Nop, Err(Else))
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
(Nop, Err(Else)),
(IntegerValue(LhsId(2)), i(0))
]
]
);
sorts_to!(
sort_redundant_bor,
"
(=> (bor (bor $x $y) $x)
(bor $x $y))
(=> (bor (bor $x $y) $y)
(bor $x $y))
",
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
(Nop, Err(Else)),
(Eq(LhsId(3), LhsId(2)), bool_to_match_result(true)),
(Nop, Err(Else)),
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
(Nop, Err(Else)),
(Nop, Err(Else)),
(Eq(LhsId(4), LhsId(2)), bool_to_match_result(true)),
],
]
);
match_in_same_order!(
match_in_same_order_redundant_bor,
"
(=> (bor (bor $x $y) $x)
(bor $x $y))
(=> (bor (bor $x $y) $y)
(bor $x $y))
",
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
(Nop, Err(Else)),
(Eq(LhsId(3), LhsId(2)), bool_to_match_result(true)),
(Nop, Err(Else)),
],
vec![
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
(Nop, Err(Else)),
(Eq(LhsId(3), LhsId(2)), Err(Else)),
(Nop, Err(Else)),
(Eq(LhsId(4), LhsId(2)), bool_to_match_result(true)),
],
]
);
}