peepmatic: Make the results of match operations a smaller and more cache friendly

This commit is contained in:
Nick Fitzgerald
2020-05-07 12:15:50 -07:00
parent 9a1f8038b7
commit 469104c4d3
14 changed files with 580 additions and 149 deletions

View File

@@ -422,7 +422,10 @@ fn peepmatic_ty_to_ir_ty(ty: Type, dfg: &DataFlowGraph, root: Inst) -> types::Ty
} }
} }
impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa { // NB: the unsafe contract we must uphold here is that our implementation of
// `instruction_result_bit_width` must always return a valid, non-zero bit
// width.
unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
type Context = FuncCursor<'b>; type Context = FuncCursor<'b>;
type Instruction = ValueOrInst; type Instruction = ValueOrInst;

View File

@@ -12,6 +12,8 @@ use std::fmt;
#[repr(u32)] #[repr(u32)]
pub enum ConditionCode { pub enum ConditionCode {
/// Equal. /// Equal.
// NB: We convert `ConditionCode` into `NonZeroU32`s with unchecked
// conversions; memory safety relies on no variant being zero.
Eq = 1, Eq = 1,
/// Not equal. /// Not equal.

View File

@@ -21,7 +21,11 @@ use std::fmt::Debug;
/// new `MachInst` and vcode backend easier, since all that needs to be done is /// new `MachInst` and vcode backend easier, since all that needs to be done is
/// "just" implementing this trait. (And probably add/modify some /// "just" implementing this trait. (And probably add/modify some
/// `peepmatic_runtime::operation::Operation`s as well). /// `peepmatic_runtime::operation::Operation`s as well).
pub trait InstructionSet<'a> { ///
/// ## Safety
///
/// See doc comment for `instruction_result_bit_width`.
pub unsafe trait InstructionSet<'a> {
/// Mutable context passed into all trait methods. Can be whatever you want! /// Mutable context passed into all trait methods. Can be whatever you want!
/// ///
/// In practice, this is a `FuncCursor` for `cranelift-codegen`'s trait /// In practice, this is a `FuncCursor` for `cranelift-codegen`'s trait
@@ -124,7 +128,10 @@ pub trait InstructionSet<'a> {
/// Get the bit width of the given instruction's result. /// Get the bit width of the given instruction's result.
/// ///
/// Must be one of 1, 8, 16, 32, 64, or 128. /// ## Safety
///
/// There is code that makes memory-safety assumptions that the result is
/// always one of 1, 8, 16, 32, 64, or 128. Implementors must uphold this.
fn instruction_result_bit_width( fn instruction_result_bit_width(
&self, &self,
context: &mut Self::Context, context: &mut Self::Context,

View File

@@ -7,11 +7,11 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::convert::TryInto; use std::num::NonZeroU32;
/// An identifier for an interned integer. /// An identifier for an interned integer.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct IntegerId(#[doc(hidden)] pub u32); pub struct IntegerId(#[doc(hidden)] pub NonZeroU32);
/// An interner for integer values. /// An interner for integer values.
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize)]
@@ -40,7 +40,8 @@ impl IntegerInterner {
return *id; return *id;
} }
let id = IntegerId(self.values.len().try_into().unwrap()); assert!((self.values.len() as u64) < (std::u32::MAX as u64));
let id = IntegerId(unsafe { NonZeroU32::new_unchecked(self.values.len() as u32 + 1) });
self.values.push(value); self.values.push(value);
self.map.insert(value, id); self.map.insert(value, id);
@@ -59,13 +60,21 @@ impl IntegerInterner {
/// Lookup a previously interned integer by id. /// Lookup a previously interned integer by id.
#[inline] #[inline]
pub fn lookup(&self, id: IntegerId) -> u64 { pub fn lookup(&self, id: IntegerId) -> u64 {
self.values[id.0 as usize] let index = id.0.get() as usize - 1;
self.values[index]
} }
} }
impl From<IntegerId> for u32 { impl From<IntegerId> for u32 {
#[inline] #[inline]
fn from(id: IntegerId) -> u32 { fn from(id: IntegerId) -> u32 {
id.0.get()
}
}
impl From<IntegerId> for NonZeroU32 {
#[inline]
fn from(id: IntegerId) -> NonZeroU32 {
id.0 id.0
} }
} }

View File

@@ -11,6 +11,7 @@ use crate::operator::{Operator, UnquoteOperator};
use crate::paths::{PathId, PathInterner}; use crate::paths::{PathId, PathInterner};
use crate::r#type::{BitWidth, Type}; use crate::r#type::{BitWidth, Type};
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::num::NonZeroU32;
/// A set of linear optimizations. /// A set of linear optimizations.
#[derive(Debug)] #[derive(Debug)]
@@ -32,6 +33,26 @@ pub struct Optimization {
pub increments: Vec<Increment>, pub increments: Vec<Increment>,
} }
/// Match any value.
///
/// This can be used to create fallback, wildcard-style transitions between
/// states.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct Else;
/// The result of evaluating a `MatchOp`.
///
/// This is either a specific non-zero `u32`, or a fallback that matches
/// everything.
pub type MatchResult = Result<NonZeroU32, Else>;
/// Convert a boolean to a `MatchResult`.
#[inline]
pub fn bool_to_match_result(b: bool) -> MatchResult {
let b = b as u32;
unsafe { Ok(NonZeroU32::new_unchecked(b + 1)) }
}
/// An increment is a matching operation, the expected result from that /// An increment is a matching operation, the expected result from that
/// operation to continue to the next increment, and the actions to take to /// operation to continue to the next increment, and the actions to take to
/// build up the LHS scope and RHS instructions given that we got the expected /// build up the LHS scope and RHS instructions given that we got the expected
@@ -44,9 +65,9 @@ pub struct Increment {
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. `None` is used for wildcard-style "else" /// continue to the next increment, or `Else` for "don't care"
/// transitions. /// wildcard-style matching.
pub expected: Option<u32>, pub expected: MatchResult,
/// Actions to perform, given that the operation resulted in the expected /// Actions to perform, given that the operation resulted in the expected
/// value. /// value.
@@ -217,3 +238,23 @@ pub enum Action {
operands: [RhsId; 3], operands: [RhsId; 3],
}, },
} }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn match_result_is_4_bytes_in_size() {
assert_eq!(std::mem::size_of::<MatchResult>(), 4);
}
#[test]
fn match_op_is_12_bytes_in_size() {
assert_eq!(std::mem::size_of::<MatchOp>(), 12);
}
#[test]
fn action_is_20_bytes_in_size() {
assert_eq!(std::mem::size_of::<Action>(), 20);
}
}

View File

@@ -20,6 +20,9 @@ use serde::{Deserialize, Serialize};
pub enum Operator { pub enum Operator {
/// `adjust_sp_down` /// `adjust_sp_down`
#[peepmatic(params(iNN), result(void))] #[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, AdjustSpDown = 1,
/// `adjust_sp_down_imm` /// `adjust_sp_down_imm`

View File

@@ -3,7 +3,7 @@
use crate::error::Result; use crate::error::Result;
use crate::instruction_set::InstructionSet; use crate::instruction_set::InstructionSet;
use crate::integer_interner::IntegerInterner; use crate::integer_interner::IntegerInterner;
use crate::linear::{Action, MatchOp}; use crate::linear::{Action, MatchOp, MatchResult};
use crate::optimizer::PeepholeOptimizer; use crate::optimizer::PeepholeOptimizer;
use crate::paths::PathInterner; use crate::paths::PathInterner;
use peepmatic_automata::Automaton; use peepmatic_automata::Automaton;
@@ -29,7 +29,7 @@ pub struct PeepholeOptimizations {
/// The underlying automata for matching optimizations' left-hand sides, and /// The underlying automata for matching optimizations' left-hand sides, and
/// building up the corresponding right-hand side. /// building up the corresponding right-hand side.
pub automata: Automaton<Option<u32>, MatchOp, Vec<Action>>, pub automata: Automaton<MatchResult, MatchOp, Vec<Action>>,
} }
impl PeepholeOptimizations { impl PeepholeOptimizations {

View File

@@ -1,7 +1,7 @@
//! An optimizer for a set of peephole optimizations. //! An optimizer for a set of peephole optimizations.
use crate::instruction_set::InstructionSet; use crate::instruction_set::InstructionSet;
use crate::linear::{Action, MatchOp}; use crate::linear::{bool_to_match_result, Action, Else, MatchOp, MatchResult};
use crate::operator::UnquoteOperator; use crate::operator::UnquoteOperator;
use crate::optimizations::PeepholeOptimizations; use crate::optimizations::PeepholeOptimizations;
use crate::part::{Constant, Part}; use crate::part::{Constant, Part};
@@ -10,6 +10,7 @@ use peepmatic_automata::State;
use std::convert::TryFrom; use std::convert::TryFrom;
use std::fmt::{self, Debug}; use std::fmt::{self, Debug};
use std::mem; use std::mem;
use std::num::NonZeroU32;
/// A peephole optimizer instance that can apply a set of peephole /// A peephole optimizer instance that can apply a set of peephole
/// optimizations to instructions. /// optimizations to instructions.
@@ -275,43 +276,72 @@ where
context: &mut I::Context, context: &mut I::Context,
root: I::Instruction, root: I::Instruction,
match_op: MatchOp, match_op: MatchOp,
) -> Option<u32> { ) -> MatchResult {
use crate::linear::MatchOp::*; use crate::linear::MatchOp::*;
log::trace!("Evaluating match operation: {:?}", match_op); log::trace!("Evaluating match operation: {:?}", match_op);
let result = match match_op { let result: MatchResult = (|| match match_op {
Opcode { path } => { Opcode { path } => {
let path = self.peep_opt.paths.lookup(path); let path = self.peep_opt.paths.lookup(path);
let part = self.instr_set.get_part_at_path(context, root, path)?; let part = self
let inst = part.as_instruction()?; .instr_set
self.instr_set.operator(context, inst).map(|op| op as u32) .get_part_at_path(context, root, path)
.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) })
} }
IsConst { path } => { IsConst { path } => {
let path = self.peep_opt.paths.lookup(path); let path = self.peep_opt.paths.lookup(path);
let part = self.instr_set.get_part_at_path(context, root, path)?; let part = self
.instr_set
.get_part_at_path(context, root, path)
.ok_or(Else)?;
let is_const = match part { let is_const = match part {
Part::Instruction(i) => { Part::Instruction(i) => {
self.instr_set.instruction_to_constant(context, i).is_some() self.instr_set.instruction_to_constant(context, i).is_some()
} }
Part::ConditionCode(_) | Part::Constant(_) => true, Part::ConditionCode(_) | Part::Constant(_) => true,
}; };
Some(is_const as u32) bool_to_match_result(is_const)
} }
IsPowerOfTwo { path } => { IsPowerOfTwo { path } => {
let path = self.peep_opt.paths.lookup(path); let path = self.peep_opt.paths.lookup(path);
let part = self.instr_set.get_part_at_path(context, root, path)?; let part = self
.instr_set
.get_part_at_path(context, root, path)
.ok_or(Else)?;
match part { match part {
Part::Constant(c) => Some(c.as_int().unwrap().is_power_of_two() as u32), Part::Constant(c) => {
Part::Instruction(i) => { let is_pow2 = c.as_int().unwrap().is_power_of_two();
let c = self.instr_set.instruction_to_constant(context, i)?; bool_to_match_result(is_pow2)
Some(c.as_int().unwrap().is_power_of_two() as u32)
} }
Part::ConditionCode(_) => panic!("IsPowerOfTwo on a condition code"), Part::Instruction(i) => {
let c = self
.instr_set
.instruction_to_constant(context, i)
.ok_or(Else)?;
let is_pow2 = c.as_int().unwrap().is_power_of_two();
bool_to_match_result(is_pow2)
}
Part::ConditionCode(_) => unreachable!(
"IsPowerOfTwo on a condition
code"
),
} }
} }
BitWidth { path } => { BitWidth { path } => {
let path = self.peep_opt.paths.lookup(path); let path = self.peep_opt.paths.lookup(path);
let part = self.instr_set.get_part_at_path(context, root, path)?; let part = self
.instr_set
.get_part_at_path(context, root, path)
.ok_or(Else)?;
let bit_width = match part { let bit_width = match part {
Part::Instruction(i) => self.instr_set.instruction_result_bit_width(context, i), Part::Instruction(i) => self.instr_set.instruction_result_bit_width(context, i),
Part::Constant(Constant::Int(_, w)) | Part::Constant(Constant::Bool(_, w)) => { Part::Constant(Constant::Int(_, w)) | Part::Constant(Constant::Bool(_, w)) => {
@@ -321,14 +351,22 @@ where
} }
Part::ConditionCode(_) => panic!("BitWidth on condition code"), Part::ConditionCode(_) => panic!("BitWidth on condition code"),
}; };
Some(bit_width as u32) debug_assert!(
bit_width != 0,
"`InstructionSet` implementors must uphold the contract that \
`instruction_result_bit_width` returns one of 1, 8, 16, 32, 64, or 128"
);
Ok(unsafe { NonZeroU32::new_unchecked(bit_width as u32) })
} }
FitsInNativeWord { path } => { FitsInNativeWord { path } => {
let native_word_size = self.instr_set.native_word_size_in_bits(context); let native_word_size = self.instr_set.native_word_size_in_bits(context);
debug_assert!(native_word_size.is_power_of_two()); debug_assert!(native_word_size.is_power_of_two());
let path = self.peep_opt.paths.lookup(path); let path = self.peep_opt.paths.lookup(path);
let part = self.instr_set.get_part_at_path(context, root, path)?; let part = self
.instr_set
.get_part_at_path(context, root, path)
.ok_or(Else)?;
let fits = match part { let fits = match part {
Part::Instruction(i) => { Part::Instruction(i) => {
let size = self.instr_set.instruction_result_bit_width(context, i); let size = self.instr_set.instruction_result_bit_width(context, i);
@@ -341,13 +379,19 @@ where
} }
Part::ConditionCode(_) => panic!("FitsInNativeWord on condition code"), Part::ConditionCode(_) => panic!("FitsInNativeWord on condition code"),
}; };
Some(fits as u32) bool_to_match_result(fits)
} }
Eq { path_a, path_b } => { Eq { path_a, path_b } => {
let path_a = self.peep_opt.paths.lookup(path_a); let path_a = self.peep_opt.paths.lookup(path_a);
let part_a = self.instr_set.get_part_at_path(context, root, path_a)?; let part_a = self
.instr_set
.get_part_at_path(context, root, path_a)
.ok_or(Else)?;
let path_b = self.peep_opt.paths.lookup(path_b); let path_b = self.peep_opt.paths.lookup(path_b);
let part_b = self.instr_set.get_part_at_path(context, root, path_b)?; let part_b = self
.instr_set
.get_part_at_path(context, root, path_b)
.ok_or(Else)?;
let eq = match (part_a, part_b) { let eq = match (part_a, part_b) {
(Part::Instruction(inst), Part::Constant(c1)) (Part::Instruction(inst), Part::Constant(c1))
| (Part::Constant(c1), Part::Instruction(inst)) => { | (Part::Constant(c1), Part::Instruction(inst)) => {
@@ -358,43 +402,67 @@ where
} }
(a, b) => a == b, (a, b) => a == b,
}; };
Some(eq as _) bool_to_match_result(eq)
} }
IntegerValue { path } => { IntegerValue { path } => {
let path = self.peep_opt.paths.lookup(path); let path = self.peep_opt.paths.lookup(path);
let part = self.instr_set.get_part_at_path(context, root, path)?; let part = self
.instr_set
.get_part_at_path(context, root, path)
.ok_or(Else)?;
match part { match part {
Part::Constant(c) => { Part::Constant(c) => {
let x = c.as_int()?; let x = c.as_int().ok_or(Else)?;
self.peep_opt.integers.already_interned(x).map(|id| id.0) let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?;
Ok(id.0)
} }
Part::Instruction(i) => { Part::Instruction(i) => {
let c = self.instr_set.instruction_to_constant(context, i)?; let c = self
let x = c.as_int()?; .instr_set
self.peep_opt.integers.already_interned(x).map(|id| id.0) .instruction_to_constant(context, i)
.ok_or(Else)?;
let x = c.as_int().ok_or(Else)?;
let id = self.peep_opt.integers.already_interned(x).ok_or(Else)?;
Ok(id.0)
} }
Part::ConditionCode(_) => panic!("IntegerValue on condition code"), Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
} }
} }
BooleanValue { path } => { BooleanValue { path } => {
let path = self.peep_opt.paths.lookup(path); let path = self.peep_opt.paths.lookup(path);
let part = self.instr_set.get_part_at_path(context, root, path)?; let part = self
.instr_set
.get_part_at_path(context, root, path)
.ok_or(Else)?;
match part { match part {
Part::Constant(c) => c.as_bool().map(|b| b as u32), Part::Constant(c) => {
Part::Instruction(i) => { let b = c.as_bool().ok_or(Else)?;
let c = self.instr_set.instruction_to_constant(context, i)?; bool_to_match_result(b)
c.as_bool().map(|b| b as u32)
} }
Part::ConditionCode(_) => panic!("IntegerValue on condition code"), Part::Instruction(i) => {
let c = self
.instr_set
.instruction_to_constant(context, i)
.ok_or(Else)?;
let b = c.as_bool().ok_or(Else)?;
bool_to_match_result(b)
}
Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
} }
} }
ConditionCode { path } => { ConditionCode { path } => {
let path = self.peep_opt.paths.lookup(path); let path = self.peep_opt.paths.lookup(path);
let part = self.instr_set.get_part_at_path(context, root, path)?; let part = self
part.as_condition_code().map(|cc| cc as u32) .instr_set
.get_part_at_path(context, root, path)
.ok_or(Else)?;
let cc = part.as_condition_code().ok_or(Else)?;
let cc = cc as u32;
debug_assert!(cc != 0);
Ok(unsafe { NonZeroU32::new_unchecked(cc) })
} }
MatchOp::Nop => None, MatchOp::Nop => Err(Else),
}; })();
log::trace!("Evaluated match operation: {:?} = {:?}", match_op, result); log::trace!("Evaluated match operation: {:?} = {:?}", match_op, result);
result result
} }
@@ -437,12 +505,12 @@ where
r#final = Some((query.current_state(), self.actions.len())); r#final = Some((query.current_state(), self.actions.len()));
} }
// Anything following a `None` transition doesn't care about the // Anything following a `Else` transition doesn't care about the
// result of this match operation, so if we partially follow the // result of this match operation, so if we partially follow the
// current non-`None` path, but don't ultimately find a matching // current non-`Else` path, but don't ultimately find a matching
// optimization, we want to be able to backtrack to this state and // optimization, we want to be able to backtrack to this state and
// then try taking the `None` transition. // then try taking the `Else` transition.
if query.has_transition_on(&None) { if query.has_transition_on(&Err(Else)) {
self.backtracking_states self.backtracking_states
.push((query.current_state(), self.actions.len())); .push((query.current_state(), self.actions.len()));
} }
@@ -462,8 +530,8 @@ where
query.go_to_state(state); query.go_to_state(state);
self.actions.truncate(actions_len); self.actions.truncate(actions_len);
query query
.next(&None) .next(&Err(Else))
.expect("backtracking states always have `None` transitions") .expect("backtracking states always have `Else` transitions")
} else { } else {
break; break;
}; };

View File

@@ -307,7 +307,9 @@ pub struct TestIsa {
pub native_word_size_in_bits: u8, pub native_word_size_in_bits: u8,
} }
impl<'a> InstructionSet<'a> for TestIsa { // Unsafe because we must ensure that `instruction_result_bit_width` never
// returns zero.
unsafe impl<'a> InstructionSet<'a> for TestIsa {
type Context = Program; type Context = Program;
type Instruction = Instruction; type Instruction = Instruction;
@@ -521,7 +523,9 @@ impl<'a> InstructionSet<'a> for TestIsa {
fn instruction_result_bit_width(&self, program: &mut Program, inst: Instruction) -> u8 { fn instruction_result_bit_width(&self, program: &mut Program, inst: Instruction) -> u8 {
log::debug!("instruction_result_bit_width({:?})", inst); log::debug!("instruction_result_bit_width({:?})", inst);
let ty = program.data(inst).r#type; let ty = program.data(inst).r#type;
ty.bit_width.fixed_width().unwrap() let width = ty.bit_width.fixed_width().unwrap();
assert!(width != 0);
width
} }
fn native_word_size_in_bits(&self, _program: &mut Program) -> u8 { fn native_word_size_in_bits(&self, _program: &mut Program) -> u8 {

View File

@@ -6,10 +6,10 @@ use peepmatic_runtime::linear;
/// Construct an automaton from a set of linear optimizations. /// Construct an automaton from a set of linear optimizations.
pub fn automatize( pub fn automatize(
opts: &linear::Optimizations, opts: &linear::Optimizations,
) -> Automaton<Option<u32>, linear::MatchOp, Vec<linear::Action>> { ) -> Automaton<linear::MatchResult, linear::MatchOp, Vec<linear::Action>> {
debug_assert!(crate::linear_passes::is_sorted_lexicographically(opts)); debug_assert!(crate::linear_passes::is_sorted_lexicographically(opts));
let mut builder = Builder::<Option<u32>, linear::MatchOp, Vec<linear::Action>>::new(); let mut builder = Builder::<linear::MatchResult, linear::MatchOp, Vec<linear::Action>>::new();
for opt in &opts.optimizations { for opt in &opts.optimizations {
let mut insertion = builder.insert(); let mut insertion = builder.insert();

View File

@@ -12,36 +12,37 @@ use peepmatic_runtime::{
}; };
use std::convert::TryFrom; use std::convert::TryFrom;
use std::io::{self, Write}; use std::io::{self, Write};
use std::num::NonZeroU32;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct PeepholeDotFmt<'a>(pub(crate) &'a PathInterner, pub(crate) &'a IntegerInterner); pub(crate) struct PeepholeDotFmt<'a>(pub(crate) &'a PathInterner, pub(crate) &'a IntegerInterner);
impl DotFmt<Option<u32>, linear::MatchOp, Vec<linear::Action>> for PeepholeDotFmt<'_> { impl DotFmt<linear::MatchResult, linear::MatchOp, Vec<linear::Action>> for PeepholeDotFmt<'_> {
fn fmt_transition( fn fmt_transition(
&self, &self,
w: &mut impl Write, w: &mut impl Write,
from: Option<&linear::MatchOp>, from: Option<&linear::MatchOp>,
input: &Option<u32>, input: &linear::MatchResult,
_to: Option<&linear::MatchOp>, _to: Option<&linear::MatchOp>,
) -> io::Result<()> { ) -> io::Result<()> {
let from = from.expect("we should have match op for every state"); let from = from.expect("we should have match op for every state");
if let Some(x) = input { if let Some(x) = input.ok().map(|x| x.get()) {
match from { match from {
linear::MatchOp::Opcode { .. } => { linear::MatchOp::Opcode { .. } => {
let opcode = let opcode =
Operator::try_from(*x).expect("we shouldn't generate non-opcode edges"); Operator::try_from(x).expect("we shouldn't generate non-opcode edges");
write!(w, "{}", opcode) write!(w, "{}", opcode)
} }
linear::MatchOp::ConditionCode { .. } => { linear::MatchOp::ConditionCode { .. } => {
let cc = let cc =
ConditionCode::try_from(*x).expect("we shouldn't generate non-CC edges"); ConditionCode::try_from(x).expect("we shouldn't generate non-CC edges");
write!(w, "{}", cc) write!(w, "{}", cc)
} }
linear::MatchOp::IntegerValue { .. } => { linear::MatchOp::IntegerValue { .. } => {
let x = self.1.lookup(IntegerId(*x)); let x = self.1.lookup(IntegerId(NonZeroU32::new(x).unwrap()));
write!(w, "{}", x) write!(w, "{}", x)
} }
_ => write!(w, "{}", x), _ => write!(w, "Ok({})", x),
} }
} else { } else {
write!(w, "(else)") write!(w, "(else)")

View File

@@ -65,7 +65,12 @@ fn compare_optimizations(
return c; return c;
} }
let c = a.expected.cmp(&b.expected).reverse(); 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 { if c != Ordering::Equal {
return c; return c;
} }
@@ -228,10 +233,10 @@ pub fn match_in_same_order(opts: &mut linear::Optimizations) {
Some(_) => { Some(_) => {
new_increments.push(linear::Increment { new_increments.push(linear::Increment {
operation: *last_op, operation: *last_op,
expected: None, expected: Err(linear::Else),
actions: vec![], actions: vec![],
}); });
if last_expected.is_some() { if last_expected.is_ok() {
break; break;
} }
} }
@@ -282,12 +287,99 @@ pub fn remove_unnecessary_nops(opts: &mut linear::Optimizations) {
mod tests { mod tests {
use super::*; use super::*;
use crate::ast::*; use crate::ast::*;
use linear::MatchOp::*; use peepmatic_runtime::{
use peepmatic_runtime::{operator::Operator, paths::*}; linear::{bool_to_match_result, Else, MatchOp::*, MatchResult},
operator::Operator,
paths::*,
};
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 { macro_rules! sorts_to {
($test_name:ident, $source:expr, $make_expected:expr) => { ($test_name:ident, $source:expr, $make_expected:expr) => {
#[test] #[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>(&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.increments
.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.increments
.iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
eprintln!("after = {:#?}", before);
let linear::Optimizations {
mut paths,
mut integers,
optimizations,
} = opts;
let actual: Vec<Vec<_>> = optimizations
.iter()
.map(|o| {
o.increments
.iter()
.map(|i| (i.operation, i.expected))
.collect()
})
.collect();
let mut p = |p: &[u8]| paths.intern(Path::new(&p));
let mut i = |i: u64| Ok(integers.intern(i).into());
let expected = $make_expected(&mut p, &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() { fn $test_name() {
let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK"); let buf = wast::parser::ParseBuffer::new($source).expect("should lex OK");
@@ -311,6 +403,32 @@ mod tests {
let mut opts = crate::linearize(&opts); let mut opts = crate::linearize(&opts);
sort_least_to_most_general(&mut opts); sort_least_to_most_general(&mut opts);
let before = opts
.optimizations
.iter()
.map(|o| {
o.increments
.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.increments
.iter()
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
.collect::<Vec<_>>()
})
.collect::<Vec<_>>();
eprintln!("after = {:#?}", before);
let linear::Optimizations { let linear::Optimizations {
mut paths, mut paths,
mut integers, mut integers,
@@ -328,7 +446,7 @@ mod tests {
.collect(); .collect();
let mut p = |p: &[u8]| paths.intern(Path::new(&p)); let mut p = |p: &[u8]| paths.intern(Path::new(&p));
let mut i = |i: u64| Some(integers.intern(i).into()); let mut i = |i: u64| Ok(integers.intern(i).into());
let expected = $make_expected(&mut p, &mut i); let expected = $make_expected(&mut p, &mut i);
assert_eq!(expected, actual); assert_eq!(expected, actual);
@@ -348,53 +466,83 @@ mod tests {
(=> (iadd $x 42) 0) (=> (iadd $x 42) 0)
(=> (iadd $x (iadd $y $z)) 0) (=> (iadd $x (iadd $y $z)) 0)
", ",
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> Option<u32>| vec![ |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
(Nop, None), Opcode { path: p(&[0]) },
(Opcode { path: p(&[0, 1]) }, Some(Operator::Iadd as _)), Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
(Nop, None), ),
(Nop, None), (Nop, Err(Else)),
(
Opcode { path: p(&[0, 1]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Nop, Err(Else)),
(Nop, Err(Else)),
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
(Nop, None), Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Nop, Err(Else)),
(IntegerValue { path: p(&[0, 1]) }, i(42)) (IntegerValue { path: p(&[0, 1]) }, i(42))
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
(Nop, None), Opcode { path: p(&[0]) },
(IsConst { path: p(&[0, 1]) }, Some(1)), Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
(IsPowerOfTwo { path: p(&[0, 1]) }, Some(1)) ),
(Nop, Err(Else)),
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true)),
(
IsPowerOfTwo { path: p(&[0, 1]) },
bool_to_match_result(true)
)
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
(Nop, None), Opcode { path: p(&[0]) },
(IsConst { path: p(&[0, 1]) }, Some(1)), Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
(BitWidth { path: p(&[0, 0]) }, Some(32)) ),
(Nop, Err(Else)),
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true)),
(
BitWidth { path: p(&[0, 0]) },
Ok(NonZeroU32::new(32).unwrap())
)
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
(Nop, None), Opcode { path: p(&[0]) },
(IsConst { path: p(&[0, 1]) }, Some(1)) Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Nop, Err(Else)),
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true))
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
(Nop, None), Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Nop, Err(Else)),
( (
Eq { Eq {
path_a: p(&[0, 1]), path_a: p(&[0, 1]),
path_b: p(&[0, 0]), path_b: p(&[0, 0]),
}, },
Some(1) bool_to_match_result(true)
) )
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
(Nop, None), Opcode { path: p(&[0]) },
(Nop, None), Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Nop, Err(Else)),
(Nop, Err(Else)),
], ],
vec![(Nop, None)] vec![(Nop, Err(Else))]
] ]
); );
@@ -408,37 +556,164 @@ mod tests {
(=> (imul 2 $x) (ishl $x 1)) (=> (imul 2 $x) (ishl $x 1))
(=> (imul $x 2) (ishl $x 1)) (=> (imul $x 2) (ishl $x 1))
", ",
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> Option<u32>| vec![ |p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Imul as _)), (
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
),
(IntegerValue { path: p(&[0, 0]) }, i(2)), (IntegerValue { path: p(&[0, 0]) }, i(2)),
(Nop, None) (Nop, Err(Else))
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Imul as _)), (
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
),
(IntegerValue { path: p(&[0, 0]) }, i(1)), (IntegerValue { path: p(&[0, 0]) }, i(1)),
(Nop, None) (Nop, Err(Else))
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Imul as _)), (
(Nop, None), Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
),
(Nop, Err(Else)),
(IntegerValue { path: p(&[0, 1]) }, i(2)) (IntegerValue { path: p(&[0, 1]) }, i(2))
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Imul as _)), (
(Nop, None), Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Imul as _).unwrap())
),
(Nop, Err(Else)),
(IntegerValue { path: p(&[0, 1]) }, i(1)) (IntegerValue { path: p(&[0, 1]) }, i(1))
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(IntegerValue { path: p(&[0, 0]) }, i(0)), (IntegerValue { path: p(&[0, 0]) }, i(0)),
(Nop, None) (Nop, Err(Else))
], ],
vec![ vec![
(Opcode { path: p(&[0]) }, Some(Operator::Iadd as _)), (
(Nop, None), Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Iadd as _).unwrap())
),
(Nop, Err(Else)),
(IntegerValue { path: p(&[0, 1]) }, i(0)) (IntegerValue { path: p(&[0, 1]) }, i(0))
] ]
] ]
); );
sorts_to!(
sort_redundant_bor,
"
(=> (bor (bor $x $y) $x)
(bor $x $y))
(=> (bor (bor $x $y) $y)
(bor $x $y))
",
|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())
),
(Nop, Err(Else)),
(Nop, Err(Else)),
(
Eq {
path_a: p(&[0, 1]),
path_b: p(&[0, 0, 0]),
},
bool_to_match_result(true)
),
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(
Opcode { path: p(&[0, 0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(Nop, Err(Else)),
(Nop, Err(Else)),
(
Eq {
path_a: p(&[0, 1]),
path_b: p(&[0, 0, 1]),
},
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))
",
|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())
),
(Nop, Err(Else)),
(Nop, Err(Else)),
(
Eq {
path_a: p(&[0, 1]),
path_b: p(&[0, 0, 0]),
},
bool_to_match_result(true)
),
],
vec![
(
Opcode { path: p(&[0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(
Opcode { path: p(&[0, 0]) },
Ok(NonZeroU32::new(Operator::Bor as _).unwrap())
),
(Nop, Err(Else)),
(Nop, Err(Else)),
(
Eq {
path_a: p(&[0, 1]),
path_b: p(&[0, 0, 0]),
},
Err(Else),
),
(
Eq {
path_a: p(&[0, 1]),
path_b: p(&[0, 0, 1]),
},
bool_to_match_result(true)
),
],
]
);
} }

View File

@@ -92,6 +92,7 @@ use peepmatic_runtime::{
paths::{Path, PathId, PathInterner}, paths::{Path, PathId, PathInterner},
}; };
use std::collections::BTreeMap; use std::collections::BTreeMap;
use std::num::NonZeroU32;
use wast::Id; use wast::Id;
/// Translate the given AST optimizations into linear optimizations. /// Translate the given AST optimizations into linear optimizations.
@@ -142,9 +143,12 @@ fn linearize_optimization(
// increment that checks the instruction-being-matched's bit width. // increment 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");
let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) });
increments.push(linear::Increment { increments.push(linear::Increment {
operation: linear::MatchOp::BitWidth { path }, operation: linear::MatchOp::BitWidth { path },
expected: Some(w as u32), expected,
actions: vec![], actions: vec![],
}); });
} }
@@ -432,7 +436,7 @@ impl Precondition<'_> {
let path = lhs_id_to_path.unwrap_first_occurrence(&id); let path = lhs_id_to_path.unwrap_first_occurrence(&id);
linear::Increment { linear::Increment {
operation: linear::MatchOp::IsPowerOfTwo { path }, operation: linear::MatchOp::IsPowerOfTwo { path },
expected: Some(1), expected: linear::bool_to_match_result(true),
actions: vec![], actions: vec![],
} }
} }
@@ -451,12 +455,14 @@ impl Precondition<'_> {
})) => *value, })) => *value,
_ => unreachable!("checked in verification"), _ => unreachable!("checked in verification"),
}; };
debug_assert!(width <= 128);
debug_assert!((width as u8).is_power_of_two()); assert!(0 < width && width <= 128);
assert!((width as u8).is_power_of_two());
let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) });
linear::Increment { linear::Increment {
operation: linear::MatchOp::BitWidth { path }, operation: linear::MatchOp::BitWidth { path },
expected: Some(width as u32), expected,
actions: vec![], actions: vec![],
} }
} }
@@ -469,7 +475,7 @@ impl Precondition<'_> {
let path = lhs_id_to_path.unwrap_first_occurrence(&id); let path = lhs_id_to_path.unwrap_first_occurrence(&id);
linear::Increment { linear::Increment {
operation: linear::MatchOp::FitsInNativeWord { path }, operation: linear::MatchOp::FitsInNativeWord { path },
expected: Some(1), expected: linear::bool_to_match_result(true),
actions: vec![], actions: vec![],
} }
} }
@@ -488,17 +494,21 @@ impl Pattern<'_> {
integers: &mut IntegerInterner, integers: &mut IntegerInterner,
lhs_id_to_path: &LhsIdToPath, lhs_id_to_path: &LhsIdToPath,
path: PathId, path: PathId,
) -> (linear::MatchOp, Option<u32>) { ) -> (linear::MatchOp, linear::MatchResult) {
match self { match self {
Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => ( Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => (
linear::MatchOp::IntegerValue { path }, linear::MatchOp::IntegerValue { path },
Some(integers.intern(*value as u64).into()), Ok(integers.intern(*value as u64).into()),
),
Pattern::ValueLiteral(ValueLiteral::Boolean(Boolean { value, .. })) => (
linear::MatchOp::BooleanValue { path },
linear::bool_to_match_result(*value),
), ),
Pattern::ValueLiteral(ValueLiteral::Boolean(Boolean { value, .. })) => {
(linear::MatchOp::BooleanValue { path }, Some(*value as u32))
}
Pattern::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => { Pattern::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => {
(linear::MatchOp::ConditionCode { path }, Some(*cc as u32)) let cc = *cc as u32;
debug_assert!(cc != 0, "no `ConditionCode` variants are zero");
let expected = Ok(unsafe { NonZeroU32::new_unchecked(cc) });
(linear::MatchOp::ConditionCode { path }, expected)
} }
Pattern::Constant(Constant { id, .. }) => { Pattern::Constant(Constant { id, .. }) => {
if let Some(path_b) = lhs_id_to_path.get_first_occurrence(id) { if let Some(path_b) = lhs_id_to_path.get_first_occurrence(id) {
@@ -508,10 +518,13 @@ impl Pattern<'_> {
path_a: path, path_a: path,
path_b, path_b,
}, },
Some(1), linear::bool_to_match_result(true),
) )
} else { } else {
(linear::MatchOp::IsConst { path }, Some(1)) (
linear::MatchOp::IsConst { path },
linear::bool_to_match_result(true),
)
} }
} }
Pattern::Variable(Variable { id, .. }) => { Pattern::Variable(Variable { id, .. }) => {
@@ -522,13 +535,18 @@ impl Pattern<'_> {
path_a: path, path_a: path,
path_b, path_b,
}, },
Some(1), linear::bool_to_match_result(true),
) )
} else { } else {
(linear::MatchOp::Nop, None) (linear::MatchOp::Nop, Err(linear::Else))
} }
} }
Pattern::Operation(op) => (linear::MatchOp::Opcode { path }, Some(op.operator as u32)), 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) });
(linear::MatchOp::Opcode { path }, expected)
}
} }
} }
} }
@@ -538,7 +556,7 @@ mod tests {
use super::*; use super::*;
use peepmatic_runtime::{ use peepmatic_runtime::{
integer_interner::IntegerId, integer_interner::IntegerId,
linear::{Action::*, MatchOp::*}, linear::{bool_to_match_result, Action::*, Else, MatchOp::*},
operator::Operator, operator::Operator,
r#type::{BitWidth, Kind, Type}, r#type::{BitWidth, Kind, Type},
}; };
@@ -603,7 +621,7 @@ mod tests {
increments: vec![ increments: vec![
linear::Increment { linear::Increment {
operation: Opcode { path: p(&[0]) }, operation: Opcode { path: p(&[0]) },
expected: Some(Operator::Imul as _), expected: Ok(NonZeroU32::new(Operator::Imul as _).unwrap()),
actions: vec![ actions: vec![
GetLhs { path: p(&[0, 0]) }, GetLhs { path: p(&[0, 0]) },
GetLhs { path: p(&[0, 1]) }, GetLhs { path: p(&[0, 1]) },
@@ -619,17 +637,17 @@ mod tests {
}, },
linear::Increment { linear::Increment {
operation: Nop, operation: Nop,
expected: None, expected: Err(Else),
actions: vec![], actions: vec![],
}, },
linear::Increment { linear::Increment {
operation: IsConst { path: p(&[0, 1]) }, operation: IsConst { path: p(&[0, 1]) },
expected: Some(1), expected: bool_to_match_result(true),
actions: vec![], actions: vec![],
}, },
linear::Increment { linear::Increment {
operation: IsPowerOfTwo { path: p(&[0, 1]) }, operation: IsPowerOfTwo { path: p(&[0, 1]) },
expected: Some(1), expected: bool_to_match_result(true),
actions: vec![], actions: vec![],
}, },
], ],
@@ -644,7 +662,7 @@ mod tests {
linear::Optimization { linear::Optimization {
increments: vec![linear::Increment { increments: vec![linear::Increment {
operation: Nop, operation: Nop,
expected: None, expected: Err(Else),
actions: vec![GetLhs { path: p(&[0]) }], actions: vec![GetLhs { path: p(&[0]) }],
}], }],
} }
@@ -658,7 +676,7 @@ mod tests {
linear::Optimization { linear::Optimization {
increments: vec![linear::Increment { increments: vec![linear::Increment {
operation: IsConst { path: p(&[0]) }, operation: IsConst { path: p(&[0]) },
expected: Some(1), expected: bool_to_match_result(true),
actions: vec![GetLhs { path: p(&[0]) }], actions: vec![GetLhs { path: p(&[0]) }],
}], }],
} }
@@ -672,7 +690,7 @@ mod tests {
linear::Optimization { linear::Optimization {
increments: vec![linear::Increment { increments: vec![linear::Increment {
operation: BooleanValue { path: p(&[0]) }, operation: BooleanValue { path: p(&[0]) },
expected: Some(1), expected: bool_to_match_result(true),
actions: vec![MakeBooleanConst { actions: vec![MakeBooleanConst {
value: true, value: true,
bit_width: BitWidth::Polymorphic, bit_width: BitWidth::Polymorphic,
@@ -689,7 +707,7 @@ mod tests {
linear::Optimization { linear::Optimization {
increments: vec![linear::Increment { increments: vec![linear::Increment {
operation: IntegerValue { path: p(&[0]) }, operation: IntegerValue { path: p(&[0]) },
expected: Some(i(5).into()), expected: Ok(i(5).into()),
actions: vec![MakeIntegerConst { actions: vec![MakeIntegerConst {
value: i(5), value: i(5),
bit_width: BitWidth::Polymorphic, bit_width: BitWidth::Polymorphic,
@@ -707,7 +725,7 @@ mod tests {
increments: vec![ increments: vec![
linear::Increment { linear::Increment {
operation: Opcode { path: p(&[0]) }, operation: Opcode { path: p(&[0]) },
expected: Some(Operator::Iconst as _), expected: Ok(NonZeroU32::new(Operator::Iconst as _).unwrap()),
actions: vec![ actions: vec![
GetLhs { path: p(&[0, 0]) }, GetLhs { path: p(&[0, 0]) },
MakeUnaryInst { MakeUnaryInst {
@@ -722,7 +740,7 @@ mod tests {
}, },
linear::Increment { linear::Increment {
operation: IsConst { path: p(&[0, 0]) }, operation: IsConst { path: p(&[0, 0]) },
expected: Some(1), expected: bool_to_match_result(true),
actions: vec![], actions: vec![],
}, },
], ],
@@ -738,7 +756,7 @@ mod tests {
increments: vec![ increments: vec![
linear::Increment { linear::Increment {
operation: Opcode { path: p(&[0]) }, operation: Opcode { path: p(&[0]) },
expected: Some(Operator::Bor as _), expected: Ok(NonZeroU32::new(Operator::Bor as _).unwrap()),
actions: vec![ actions: vec![
GetLhs { path: p(&[0, 0]) }, GetLhs { path: p(&[0, 0]) },
GetLhs { GetLhs {
@@ -756,12 +774,12 @@ mod tests {
}, },
linear::Increment { linear::Increment {
operation: Nop, operation: Nop,
expected: None, expected: Err(Else),
actions: vec![], actions: vec![],
}, },
linear::Increment { linear::Increment {
operation: Opcode { path: p(&[0, 1]) }, operation: Opcode { path: p(&[0, 1]) },
expected: Some(Operator::Bor as _), expected: Ok(NonZeroU32::new(Operator::Bor as _).unwrap()),
actions: vec![], actions: vec![],
}, },
linear::Increment { linear::Increment {
@@ -769,12 +787,12 @@ mod tests {
path_a: p(&[0, 1, 0]), path_a: p(&[0, 1, 0]),
path_b: p(&[0, 0]), path_b: p(&[0, 0]),
}, },
expected: Some(1), expected: bool_to_match_result(true),
actions: vec![], actions: vec![],
}, },
linear::Increment { linear::Increment {
operation: Nop, operation: Nop,
expected: None, expected: Err(Else),
actions: vec![], actions: vec![],
}, },
], ],
@@ -790,7 +808,7 @@ mod tests {
linear::Optimization { linear::Optimization {
increments: vec![linear::Increment { increments: vec![linear::Increment {
operation: IntegerValue { path: p(&[0]) }, operation: IntegerValue { path: p(&[0]) },
expected: Some(i(std::u64::MAX).into()), expected: Ok(i(std::u64::MAX).into()),
actions: vec![MakeIntegerConst { actions: vec![MakeIntegerConst {
value: i(0), value: i(0),
bit_width: BitWidth::Polymorphic, bit_width: BitWidth::Polymorphic,
@@ -808,7 +826,7 @@ mod tests {
increments: vec![ increments: vec![
linear::Increment { linear::Increment {
operation: Opcode { path: p(&[0]) }, operation: Opcode { path: p(&[0]) },
expected: Some(Operator::Ireduce as _), expected: Ok(NonZeroU32::new(Operator::Ireduce as _).unwrap()),
actions: vec![MakeIntegerConst { actions: vec![MakeIntegerConst {
value: i(0), value: i(0),
bit_width: BitWidth::ThirtyTwo, bit_width: BitWidth::ThirtyTwo,
@@ -816,12 +834,12 @@ mod tests {
}, },
linear::Increment { linear::Increment {
operation: linear::MatchOp::BitWidth { path: p(&[0]) }, operation: linear::MatchOp::BitWidth { path: p(&[0]) },
expected: Some(32), expected: Ok(NonZeroU32::new(32).unwrap()),
actions: vec![], actions: vec![],
}, },
linear::Increment { linear::Increment {
operation: Nop, operation: Nop,
expected: None, expected: Err(Else),
actions: vec![], actions: vec![],
}, },
], ],