peepmatic: Simplify linear IR

This commit splits "increments" in two; they previously contained both the
linearized left- and right-hand sides. But only the first increment ever had any
actions, so it was confusing (and space wasting) that all increments had an
"actions" vector. No more!

This commit separates the linearized left-hand side ("matches") from the
linearized right-hand side ("actions").
This commit is contained in:
Nick Fitzgerald
2020-09-22 16:06:07 -07:00
parent 3f0d789830
commit 447c3e71a6
4 changed files with 254 additions and 256 deletions

View File

@@ -37,8 +37,13 @@ pub struct Optimization<TOperator>
where where
TOperator: 'static + Copy + Debug + Eq + Hash, TOperator: 'static + Copy + Debug + Eq + Hash,
{ {
/// The chain of increments for this optimization. /// The chain of match operations and expected results for this
pub increments: Vec<Increment<TOperator>>, /// optimization.
pub matches: Vec<Match>,
/// Actions to perform, given that the operation resulted in the expected
/// value.
pub actions: Vec<Action<TOperator>>,
} }
/// Match any value. /// Match any value.
@@ -61,31 +66,20 @@ pub fn bool_to_match_result(b: bool) -> MatchResult {
unsafe { Ok(NonZeroU32::new_unchecked(b + 1)) } unsafe { Ok(NonZeroU32::new_unchecked(b + 1)) }
} }
/// A partial match of an optimization's LHS and partial construction of its /// A partial match of an optimization's LHS.
/// RHS.
/// ///
/// An increment is a matching operation, the expected result from that /// An match is composed of a matching operation and the expected result of that
/// operation to continue to the next increment, and the actions to take to /// operation. Each match will basically become a state and a transition edge
/// build up the LHS scope and RHS instructions given that we got the expected /// out of that state in the final automata.
/// result from this increment's matching operation. Each increment will
/// basically become a state and a transition edge out of that state in the
/// final automata.
#[derive(Clone, Debug, PartialEq, Eq)] #[derive(Clone, Debug, PartialEq, Eq)]
pub struct Increment<TOperator> pub struct Match {
where
TOperator: 'static + Copy + Debug + Eq + Hash,
{
/// The matching operation to perform. /// The matching operation to perform.
pub operation: MatchOp, pub operation: MatchOp,
/// The expected result of our matching operation, that enables us to /// The expected result of our matching operation, that enables us to
/// continue to the next increment, or `Else` for "don't care" /// continue to the next match, or `Else` for "don't care" wildcard-style
/// wildcard-style matching. /// matching.
pub expected: MatchResult, pub expected: MatchResult,
/// Actions to perform, given that the operation resulted in the expected
/// value.
pub actions: Vec<Action<TOperator>>,
} }
/// A matching operation to be performed on some Cranelift instruction as part /// A matching operation to be performed on some Cranelift instruction as part

View File

@@ -19,16 +19,23 @@ where
for opt in &opts.optimizations { for opt in &opts.optimizations {
let mut insertion = builder.insert(); let mut insertion = builder.insert();
for inc in &opt.increments { let mut is_first = true;
// Ensure that this state's associated data is this increment's for m in &opt.matches {
// match operation. // Ensure that this state's associated data is this match's
// operation.
if let Some(op) = insertion.get_state_data() { if let Some(op) = insertion.get_state_data() {
assert_eq!(*op, inc.operation); assert_eq!(*op, m.operation);
} else { } else {
insertion.set_state_data(inc.operation); insertion.set_state_data(m.operation);
} }
insertion.next(inc.expected, inc.actions.clone().into_boxed_slice()); let actions = if is_first {
is_first = false;
opt.actions.clone().into_boxed_slice()
} else {
vec![].into_boxed_slice()
};
insertion.next(m.expected, actions);
} }
insertion.finish(); insertion.finish();
} }

View File

@@ -70,7 +70,7 @@ fn compare_optimizations<TOperator>(
where where
TOperator: Copy + Debug + Eq + Hash, TOperator: Copy + Debug + Eq + Hash,
{ {
for (a, b) in a.increments.iter().zip(b.increments.iter()) { for (a, b) in a.matches.iter().zip(b.matches.iter()) {
let c = compare_match_op_generality(paths, a.operation, b.operation); let c = compare_match_op_generality(paths, a.operation, b.operation);
if c != Ordering::Equal { if c != Ordering::Equal {
return c; return c;
@@ -87,7 +87,7 @@ where
} }
} }
compare_lengths(a.increments.len(), b.increments.len()) compare_lengths(a.matches.len(), b.matches.len())
} }
fn compare_optimization_generality<TOperator>( fn compare_optimization_generality<TOperator>(
@@ -238,28 +238,27 @@ where
let mut prefix = vec![]; let mut prefix = vec![];
for opt in &mut opts.optimizations { for opt in &mut opts.optimizations {
assert!(!opt.increments.is_empty()); assert!(!opt.matches.is_empty());
let mut old_increments = opt.increments.iter().peekable(); let mut old_matches = opt.matches.iter().peekable();
let mut new_increments = vec![]; let mut new_matches = vec![];
for (last_op, last_expected) in &prefix { for (last_op, last_expected) in &prefix {
match old_increments.peek() { match old_matches.peek() {
None => { None => {
break; break;
} }
Some(inc) if *last_op == inc.operation => { Some(inc) if *last_op == inc.operation => {
let inc = old_increments.next().unwrap(); let inc = old_matches.next().unwrap();
new_increments.push(inc.clone()); new_matches.push(inc.clone());
if inc.expected != *last_expected { if inc.expected != *last_expected {
break; break;
} }
} }
Some(_) => { Some(_) => {
new_increments.push(linear::Increment { new_matches.push(linear::Match {
operation: *last_op, operation: *last_op,
expected: Err(linear::Else), expected: Err(linear::Else),
actions: vec![],
}); });
if last_expected.is_ok() { if last_expected.is_ok() {
break; break;
@@ -268,16 +267,12 @@ where
} }
} }
new_increments.extend(old_increments.cloned()); new_matches.extend(old_matches.cloned());
assert!(new_increments.len() >= opt.increments.len()); assert!(new_matches.len() >= opt.matches.len());
opt.increments = new_increments; opt.matches = new_matches;
prefix.clear(); prefix.clear();
prefix.extend( prefix.extend(opt.matches.iter().map(|inc| (inc.operation, inc.expected)));
opt.increments
.iter()
.map(|inc| (inc.operation, inc.expected)),
);
} }
// Should still be sorted after this pass. // Should still be sorted after this pass.
@@ -291,21 +286,20 @@ where
/// for the DSL's edge-cases than it is to try and statically eliminate their /// for the DSL's edge-cases than it is to try and statically eliminate their
/// existence completely. So we just emit nop match operations for all variable /// 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 /// patterns, and then in this post-processing pass, we fuse them and their
/// actions with their preceding increment. /// actions with their preceding match.
pub fn remove_unnecessary_nops<TOperator>(opts: &mut linear::Optimizations<TOperator>) pub fn remove_unnecessary_nops<TOperator>(opts: &mut linear::Optimizations<TOperator>)
where where
TOperator: Copy + Debug + Eq + Hash, TOperator: Copy + Debug + Eq + Hash,
{ {
for opt in &mut opts.optimizations { for opt in &mut opts.optimizations {
if opt.increments.len() < 2 { if opt.matches.len() < 2 {
debug_assert!(!opt.increments.is_empty()); debug_assert!(!opt.matches.is_empty());
continue; continue;
} }
for i in (1..opt.increments.len()).rev() { for i in (1..opt.matches.len()).rev() {
if let linear::MatchOp::Nop = opt.increments[i].operation { if let linear::MatchOp::Nop = opt.matches[i].operation {
let nop = opt.increments.remove(i); opt.matches.remove(i);
opt.increments[i - 1].actions.extend(nop.actions);
} }
} }
} }
@@ -357,7 +351,7 @@ mod tests {
.optimizations .optimizations
.iter() .iter()
.map(|o| { .map(|o| {
o.increments o.matches
.iter() .iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected)) .map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -371,7 +365,7 @@ mod tests {
.optimizations .optimizations
.iter() .iter()
.map(|o| { .map(|o| {
o.increments o.matches
.iter() .iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected)) .map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -388,7 +382,7 @@ mod tests {
let actual: Vec<Vec<_>> = optimizations let actual: Vec<Vec<_>> = optimizations
.iter() .iter()
.map(|o| { .map(|o| {
o.increments o.matches
.iter() .iter()
.map(|i| (i.operation, i.expected)) .map(|i| (i.operation, i.expected))
.collect() .collect()
@@ -435,7 +429,7 @@ mod tests {
.optimizations .optimizations
.iter() .iter()
.map(|o| { .map(|o| {
o.increments o.matches
.iter() .iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected)) .map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -449,7 +443,7 @@ mod tests {
.optimizations .optimizations
.iter() .iter()
.map(|o| { .map(|o| {
o.increments o.matches
.iter() .iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected)) .map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>() .collect::<Vec<_>>()
@@ -466,7 +460,7 @@ mod tests {
let actual: Vec<Vec<_>> = optimizations let actual: Vec<Vec<_>> = optimizations
.iter() .iter()
.map(|o| { .map(|o| {
o.increments o.matches
.iter() .iter()
.map(|i| (i.operation, i.expected)) .map(|i| (i.operation, i.expected))
.collect() .collect()

View File

@@ -18,29 +18,38 @@
//! (ishl $x $(log2 C))) //! (ishl $x $(log2 C)))
//! ``` //! ```
//! //!
//! Then we should get the following linear chain of "increments": //! Then the left-hand side becomes the following linear chain of "matches":
//! //!
//! ```ignore //! ```ignore
//! [ //! [
//! // ( Match Operation, Expected Value, Actions ) //! // ( Match Operation, Expected Value )
//! ( Opcode@0, imul, [$x = GetLhs@0.0, $C = GetLhs@0.1, ...] ), //! ( Opcode@0, imul ),
//! ( IsConst(C), true, [] ), //! ( IsConst(C), true ),
//! ( IsPowerOfTwo(C), true, [] ), //! ( IsPowerOfTwo(C), true ),
//! ] //! ]
//! ``` //! ```
//! //!
//! Each increment will essentially become a state and a transition out of that //! And the right-hand side becomes this linear chain of "actions":
//! state in the final automata, along with the actions to perform when taking //!
//! that transition. The actions record the scope of matches from the left-hand //! ```ignore
//! side and also incrementally build the right-hand side's instructions. (Note //! [
//! that we've elided the actions that build up the optimization's right-hand //! $rhs0 = get lhs @ 0.0 // $x
//! side in this example.) //! $rhs1 = get lhs @ 0.1 // $C
//! $rhs2 = eval log2 $rhs1
//! $rhs3 = make ishl $rhs0, $rhs2
//! ]
//! ```
//!
//! Each match will essentially become a state and a transition out of that
//! state in the final automata. The actions record the scope of matches from
//! the left-hand side and also incrementally build the right-hand side's
//! instructions.
//! //!
//! ## General Principles //! ## General Principles
//! //!
//! Here are the general principles that linearization should adhere to: //! Here are the general principles that linearization should adhere to:
//! //!
//! * Actions should be pushed as early in the optimization's increment chain as //! * Actions should be pushed as early in the optimization's match chain as
//! they can be. This means the tail has fewer side effects, and is therefore //! they can be. This means the tail has fewer side effects, and is therefore
//! more likely to be share-able with other optimizations in the automata that //! more likely to be share-able with other optimizations in the automata that
//! we build. //! we build.
@@ -49,15 +58,15 @@
//! defined. And finally, an RHS operation's operands must be defined before //! defined. And finally, an RHS operation's operands must be defined before
//! the RHS operation itself. In general, definitions must come before uses! //! the RHS operation itself. In general, definitions must come before uses!
//! //!
//! * Shorter increment chains are better! This means fewer tests when matching //! * Shorter match chains are better! This means fewer tests when matching
//! left-hand sides, and a more-compact, more-cache-friendly automata, and //! left-hand sides, and a more-compact, more-cache-friendly automata, and
//! ultimately, a faster automata. //! ultimately, a faster automata.
//! //!
//! * An increment's match operation should be a switch rather than a predicate //! * An match operation should be a switch rather than a predicate that returns
//! that returns a boolean. For example, we switch on an instruction's opcode, //! a boolean. For example, we switch on an instruction's opcode, rather than
//! rather than ask whether this operation is an `imul`. This allows for more //! ask whether this operation is an `imul`. This allows for more prefix
//! prefix sharing in the automata, which (again) makes it more compact and //! sharing in the automata, which (again) makes it more compact and more
//! more cache friendly. //! cache friendly.
//! //!
//! ## Implementation Overview //! ## Implementation Overview
//! //!
@@ -127,7 +136,7 @@ fn linearize_optimization<TOperator>(
where where
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>, TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
{ {
let mut increments: Vec<linear::Increment<_>> = vec![]; let mut matches: Vec<linear::Match> = vec![];
let mut lhs_id_to_path = LhsIdToPath::new(); let mut lhs_id_to_path = LhsIdToPath::new();
@@ -136,13 +145,12 @@ where
// don't want to emit matching operations on things that might not exist! // don't want to emit matching operations on things that might not exist!
let mut patterns = PatternPreOrder::new(&opt.lhs.pattern); let mut patterns = PatternPreOrder::new(&opt.lhs.pattern);
while let Some((path, pattern)) = patterns.next(paths) { while let Some((path, pattern)) = patterns.next(paths) {
// Create the matching parts of an `Increment` for this part of the // Create the matching parts of an `Match` for this part of the
// pattern, without any actions yet. // pattern.
let (operation, expected) = pattern.to_linear_match_op(integers, &lhs_id_to_path, path); let (operation, expected) = pattern.to_linear_match_op(integers, &lhs_id_to_path, path);
increments.push(linear::Increment { matches.push(linear::Match {
operation, operation,
expected, expected,
actions: vec![],
}); });
lhs_id_to_path.remember_path_to_pattern_ids(pattern, path); lhs_id_to_path.remember_path_to_pattern_ids(pattern, path);
@@ -150,34 +158,34 @@ where
// Some operations require type ascriptions for us to infer the correct // Some operations require type ascriptions for us to infer the correct
// bit width of their results: `ireduce`, `sextend`, `uextend`, etc. // bit width of their results: `ireduce`, `sextend`, `uextend`, etc.
// When there is such a type ascription in the pattern, insert another // When there is such a type ascription in the pattern, insert another
// increment that checks the instruction-being-matched's bit width. // match that checks the instruction-being-matched's bit width.
if let Pattern::Operation(Operation { r#type, .. }) = pattern { if let Pattern::Operation(Operation { r#type, .. }) = pattern {
if let Some(w) = r#type.get().and_then(|ty| ty.bit_width.fixed_width()) { if let Some(w) = r#type.get().and_then(|ty| ty.bit_width.fixed_width()) {
debug_assert!(w != 0, "All fixed-width bit widths are non-zero"); debug_assert!(w != 0, "All fixed-width bit widths are non-zero");
let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) }); let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) });
increments.push(linear::Increment { matches.push(linear::Match {
operation: linear::MatchOp::BitWidth { path }, operation: linear::MatchOp::BitWidth { path },
expected, expected,
actions: vec![],
}); });
} }
} }
} }
// Now that we've added all the increments for the LHS pattern, add the // Now that we've added all the matches for the LHS pattern, add the
// increments for its preconditions. // matches for its preconditions.
for pre in &opt.lhs.preconditions { for pre in &opt.lhs.preconditions {
increments.push(pre.to_linear_increment(&lhs_id_to_path)); matches.push(pre.to_linear_match(&lhs_id_to_path));
} }
assert!(!increments.is_empty()); assert!(!matches.is_empty());
// Finally, generate the RHS-building actions and attach them to the first increment. // Finally, generate the RHS-building actions and attach them to the first match.
let mut rhs_builder = RhsBuilder::new(&opt.rhs); let mut rhs_builder = RhsBuilder::new(&opt.rhs);
rhs_builder.add_rhs_build_actions(integers, &lhs_id_to_path, &mut increments[0].actions); let mut actions = vec![];
rhs_builder.add_rhs_build_actions(integers, &lhs_id_to_path, &mut actions);
linear::Optimization { increments } linear::Optimization { matches, actions }
} }
/// A post-order, depth-first traversal of right-hand sides. /// A post-order, depth-first traversal of right-hand sides.
@@ -452,11 +460,8 @@ impl<TOperator> Precondition<'_, TOperator>
where where
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>, TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
{ {
/// Convert this precondition into a `linear::Increment`. /// Convert this precondition into a `linear::Match`.
fn to_linear_increment( fn to_linear_match(&self, lhs_id_to_path: &LhsIdToPath<TOperator>) -> linear::Match {
&self,
lhs_id_to_path: &LhsIdToPath<TOperator>,
) -> linear::Increment<TOperator> {
match self.constraint { match self.constraint {
Constraint::IsPowerOfTwo => { Constraint::IsPowerOfTwo => {
let id = match &self.operands[0] { let id = match &self.operands[0] {
@@ -464,10 +469,9 @@ where
_ => unreachable!("checked in verification"), _ => unreachable!("checked in verification"),
}; };
let path = lhs_id_to_path.unwrap_first_occurrence(&id); let path = lhs_id_to_path.unwrap_first_occurrence(&id);
linear::Increment { linear::Match {
operation: linear::MatchOp::IsPowerOfTwo { path }, operation: linear::MatchOp::IsPowerOfTwo { path },
expected: linear::bool_to_match_result(true), expected: linear::bool_to_match_result(true),
actions: vec![],
} }
} }
Constraint::BitWidth => { Constraint::BitWidth => {
@@ -490,10 +494,9 @@ where
assert!((width as u8).is_power_of_two()); assert!((width as u8).is_power_of_two());
let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) }); let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) });
linear::Increment { linear::Match {
operation: linear::MatchOp::BitWidth { path }, operation: linear::MatchOp::BitWidth { path },
expected, expected,
actions: vec![],
} }
} }
Constraint::FitsInNativeWord => { Constraint::FitsInNativeWord => {
@@ -503,10 +506,9 @@ where
_ => unreachable!("checked in verification"), _ => unreachable!("checked in verification"),
}; };
let path = lhs_id_to_path.unwrap_first_occurrence(&id); let path = lhs_id_to_path.unwrap_first_occurrence(&id);
linear::Increment { linear::Match {
operation: linear::MatchOp::FitsInNativeWord { path }, operation: linear::MatchOp::FitsInNativeWord { path },
expected: linear::bool_to_match_result(true), expected: linear::bool_to_match_result(true),
actions: vec![],
} }
} }
} }
@@ -592,6 +594,7 @@ mod tests {
integer_interner::IntegerId, integer_interner::IntegerId,
linear::{bool_to_match_result, Action::*, Else, MatchOp::*}, linear::{bool_to_match_result, Action::*, Else, MatchOp::*},
r#type::{BitWidth, Kind, Type}, r#type::{BitWidth, Kind, Type},
unquote::UnquoteOperator,
}; };
use peepmatic_test_operator::TestOperator; use peepmatic_test_operator::TestOperator;
@@ -635,14 +638,12 @@ mod tests {
let make_expected: fn( let make_expected: fn(
&mut dyn FnMut(&[u8]) -> PathId, &mut dyn FnMut(&[u8]) -> PathId,
&mut dyn FnMut(u64) -> IntegerId, &mut dyn FnMut(u64) -> IntegerId,
) -> Vec<linear::Increment<TestOperator>> = $make_expected; ) -> (Vec<linear::Match>, Vec<linear::Action<_>>) = $make_expected;
let expected = make_expected(&mut p, &mut i); let expected = make_expected(&mut p, &mut i);
dbg!(&expected);
let actual = linearize_optimization(&mut paths, &mut integers, &opts.optimizations[0]); let actual = linearize_optimization(&mut paths, &mut integers, &opts.optimizations[0]);
dbg!(&actual.increments); assert_eq!(expected.0, actual.matches);
assert_eq!(expected.1, actual.actions);
assert_eq!(expected, actual.increments);
} }
}; };
} }
@@ -652,194 +653,196 @@ mod tests {
" "
(=> (when (imul $x $C) (=> (when (imul $x $C)
(is-power-of-two $C)) (is-power-of-two $C))
(ishl $x $C)) (ishl $x $(log2 $C)))
", ",
|p, i| vec![ |p, i| (
linear::Increment { vec![
operation: Opcode { path: p(&[0]) }, linear::Match {
expected: Ok(TestOperator::Imul.into()), operation: Opcode { path: p(&[0]) },
actions: vec![ expected: Ok(TestOperator::Imul.into()),
GetLhs { path: p(&[0, 0]) }, },
GetLhs { path: p(&[0, 1]) }, linear::Match {
MakeBinaryInst { operation: Nop,
operator: TestOperator::Ishl, expected: Err(Else),
r#type: Type { },
kind: Kind::Int, linear::Match {
bit_width: BitWidth::Polymorphic, operation: IsConst { path: p(&[0, 1]) },
}, expected: bool_to_match_result(true),
operands: [linear::RhsId(0), linear::RhsId(1)], },
linear::Match {
operation: IsPowerOfTwo { path: p(&[0, 1]) },
expected: bool_to_match_result(true),
},
],
vec![
GetLhs { path: p(&[0, 0]) },
GetLhs { path: p(&[0, 1]) },
UnaryUnquote {
operator: UnquoteOperator::Log2,
operand: linear::RhsId(1)
},
MakeBinaryInst {
operator: TestOperator::Ishl,
r#type: Type {
kind: Kind::Int,
bit_width: BitWidth::Polymorphic
}, },
], operands: [linear::RhsId(0), linear::RhsId(2)]
}, }
linear::Increment { ],
operation: Nop, ),
expected: Err(Else),
actions: vec![],
},
linear::Increment {
operation: IsConst { path: p(&[0, 1]) },
expected: bool_to_match_result(true),
actions: vec![],
},
linear::Increment {
operation: IsPowerOfTwo { path: p(&[0, 1]) },
expected: bool_to_match_result(true),
actions: vec![],
},
],
); );
linearizes_to!(variable_pattern_id_optimization, "(=> $x $x)", |p, i| vec![ linearizes_to!(variable_pattern_id_optimization, "(=> $x $x)", |p, i| (
linear::Increment { vec![linear::Match {
operation: Nop, operation: Nop,
expected: Err(Else), expected: Err(Else),
actions: vec![GetLhs { path: p(&[0]) }], }],
} vec![GetLhs { path: p(&[0]) }],
]); ));
linearizes_to!(constant_pattern_id_optimization, "(=> $C $C)", |p, i| vec![ linearizes_to!(constant_pattern_id_optimization, "(=> $C $C)", |p, i| (
linear::Increment { vec![linear::Match {
operation: IsConst { path: p(&[0]) }, operation: IsConst { path: p(&[0]) },
expected: bool_to_match_result(true), expected: bool_to_match_result(true),
actions: vec![GetLhs { path: p(&[0]) }], }],
} vec![GetLhs { path: p(&[0]) }],
]); ));
linearizes_to!( linearizes_to!(boolean_literal_id_optimization, "(=> true true)", |p, i| (
boolean_literal_id_optimization, vec![linear::Match {
"(=> true true)",
|p, i| vec![linear::Increment {
operation: BooleanValue { path: p(&[0]) }, operation: BooleanValue { path: p(&[0]) },
expected: bool_to_match_result(true), expected: bool_to_match_result(true),
actions: vec![MakeBooleanConst { }],
value: true, vec![MakeBooleanConst {
bit_width: BitWidth::Polymorphic, value: true,
}], bit_width: BitWidth::Polymorphic,
}] }],
); ));
linearizes_to!(number_literal_id_optimization, "(=> 5 5)", |p, i| vec![ linearizes_to!(number_literal_id_optimization, "(=> 5 5)", |p, i| (
linear::Increment { vec![linear::Match {
operation: IntegerValue { path: p(&[0]) }, operation: IntegerValue { path: p(&[0]) },
expected: Ok(i(5).into()), expected: Ok(i(5).into()),
actions: vec![MakeIntegerConst { }],
value: i(5), vec![MakeIntegerConst {
bit_width: BitWidth::Polymorphic, value: i(5),
}], bit_width: BitWidth::Polymorphic,
} }],
]); ));
linearizes_to!( linearizes_to!(
operation_id_optimization, operation_id_optimization,
"(=> (iconst $C) (iconst $C))", "(=> (iconst $C) (iconst $C))",
|p, i| vec![ |p, i| (
linear::Increment { vec![
operation: Opcode { path: p(&[0]) }, linear::Match {
expected: Ok(TestOperator::Iconst.into()), operation: Opcode { path: p(&[0]) },
actions: vec![ expected: Ok(TestOperator::Iconst.into()),
GetLhs { path: p(&[0, 0]) }, },
MakeUnaryInst { linear::Match {
operator: TestOperator::Iconst, operation: IsConst { path: p(&[0, 0]) },
r#type: Type { expected: bool_to_match_result(true),
kind: Kind::Int, },
bit_width: BitWidth::Polymorphic, ],
}, vec![
operand: linear::RhsId(0), GetLhs { path: p(&[0, 0]) },
MakeUnaryInst {
operator: TestOperator::Iconst,
r#type: Type {
kind: Kind::Int,
bit_width: BitWidth::Polymorphic,
}, },
], operand: linear::RhsId(0),
}, },
linear::Increment { ],
operation: IsConst { path: p(&[0, 0]) }, ),
expected: bool_to_match_result(true),
actions: vec![],
},
]
); );
linearizes_to!( linearizes_to!(
redundant_bor, redundant_bor,
"(=> (bor $x (bor $x $y)) (bor $x $y))", "(=> (bor $x (bor $x $y)) (bor $x $y))",
|p, i| vec![ |p, i| (
linear::Increment { vec![
operation: Opcode { path: p(&[0]) }, linear::Match {
expected: Ok(TestOperator::Bor.into()), operation: Opcode { path: p(&[0]) },
actions: vec![ expected: Ok(TestOperator::Bor.into()),
GetLhs { path: p(&[0, 0]) },
GetLhs {
path: p(&[0, 1, 1]),
},
MakeBinaryInst {
operator: TestOperator::Bor,
r#type: Type {
kind: Kind::Int,
bit_width: BitWidth::Polymorphic,
},
operands: [linear::RhsId(0), linear::RhsId(1)],
},
],
},
linear::Increment {
operation: Nop,
expected: Err(Else),
actions: vec![],
},
linear::Increment {
operation: Opcode { path: p(&[0, 1]) },
expected: Ok(TestOperator::Bor.into()),
actions: vec![],
},
linear::Increment {
operation: Eq {
path_a: p(&[0, 1, 0]),
path_b: p(&[0, 0]),
}, },
expected: bool_to_match_result(true), linear::Match {
actions: vec![], operation: Nop,
}, expected: Err(Else),
linear::Increment { },
operation: Nop, linear::Match {
expected: Err(Else), operation: Opcode { path: p(&[0, 1]) },
actions: vec![], expected: Ok(TestOperator::Bor.into()),
}, },
] linear::Match {
operation: Eq {
path_a: p(&[0, 1, 0]),
path_b: p(&[0, 0]),
},
expected: bool_to_match_result(true),
},
linear::Match {
operation: Nop,
expected: Err(Else),
},
],
vec![
GetLhs { path: p(&[0, 0]) },
GetLhs {
path: p(&[0, 1, 1]),
},
MakeBinaryInst {
operator: TestOperator::Bor,
r#type: Type {
kind: Kind::Int,
bit_width: BitWidth::Polymorphic,
},
operands: [linear::RhsId(0), linear::RhsId(1)],
},
],
),
); );
linearizes_to!( linearizes_to!(
large_integers, large_integers,
// u64::MAX // u64::MAX
"(=> 18446744073709551615 0)", "(=> 18446744073709551615 0)",
|p, i| vec![linear::Increment { |p, i| (
operation: IntegerValue { path: p(&[0]) }, vec![linear::Match {
expected: Ok(i(std::u64::MAX).into()), operation: IntegerValue { path: p(&[0]) },
actions: vec![MakeIntegerConst { expected: Ok(i(std::u64::MAX).into()),
}],
vec![MakeIntegerConst {
value: i(0), value: i(0),
bit_width: BitWidth::Polymorphic, bit_width: BitWidth::Polymorphic,
}], }],
}] ),
); );
linearizes_to!( linearizes_to!(
ireduce_with_type_ascription, ireduce_with_type_ascription,
"(=> (ireduce{i32} $x) 0)", "(=> (ireduce{i32} $x) 0)",
|p, i| vec![ |p, i| (
linear::Increment { vec![
operation: Opcode { path: p(&[0]) }, linear::Match {
expected: Ok(TestOperator::Ireduce.into()), operation: Opcode { path: p(&[0]) },
actions: vec![MakeIntegerConst { expected: Ok(TestOperator::Ireduce.into()),
value: i(0), },
bit_width: BitWidth::ThirtyTwo, linear::Match {
}], operation: linear::MatchOp::BitWidth { path: p(&[0]) },
}, expected: Ok(NonZeroU32::new(32).unwrap()),
linear::Increment { },
operation: linear::MatchOp::BitWidth { path: p(&[0]) }, linear::Match {
expected: Ok(NonZeroU32::new(32).unwrap()), operation: Nop,
actions: vec![], expected: Err(Else),
}, },
linear::Increment { ],
operation: Nop, vec![MakeIntegerConst {
expected: Err(Else), value: i(0),
actions: vec![], bit_width: BitWidth::ThirtyTwo,
}, }],
] ),
); );
} }