peepmatic: Do not use paths in linear IR
Rather than using paths from the root instruction to the instruction we are matching against or checking if it is constant or whatever, use temporary variables. When we successfully match an instruction's opcode, we simultaneously define these temporaries for the instruction's operands. This is similar to how open-coding these matches in Rust would use `match` expressions with pattern matching to bind the operands to variables at the same time. This saves about 1.8% of instructions retired when Peepmatic is enabled.
This commit is contained in:
@@ -14,14 +14,13 @@ use peepmatic_runtime::{
|
|||||||
cc::ConditionCode,
|
cc::ConditionCode,
|
||||||
instruction_set::InstructionSet,
|
instruction_set::InstructionSet,
|
||||||
part::{Constant, Part},
|
part::{Constant, Part},
|
||||||
paths::Path,
|
|
||||||
r#type::{BitWidth, Kind, Type},
|
r#type::{BitWidth, Kind, Type},
|
||||||
PeepholeOptimizations, PeepholeOptimizer,
|
PeepholeOptimizations, PeepholeOptimizer,
|
||||||
};
|
};
|
||||||
use peepmatic_traits::TypingRules;
|
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::boxed::Box;
|
use std::boxed::Box;
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
|
use std::iter;
|
||||||
use std::ptr;
|
use std::ptr;
|
||||||
use std::sync::atomic::{AtomicPtr, Ordering};
|
use std::sync::atomic::{AtomicPtr, Ordering};
|
||||||
|
|
||||||
@@ -573,35 +572,6 @@ fn intcc_to_peepmatic(cc: IntCC) -> ConditionCode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_immediate(dfg: &DataFlowGraph, inst: Inst, i: usize) -> Part<ValueOrInst> {
|
|
||||||
return match dfg[inst] {
|
|
||||||
InstructionData::BinaryImm64 { imm, .. } if i == 0 => imm.into(),
|
|
||||||
InstructionData::BranchIcmp { cond, .. } if i == 0 => intcc_to_peepmatic(cond).into(),
|
|
||||||
InstructionData::BranchInt { cond, .. } if i == 0 => intcc_to_peepmatic(cond).into(),
|
|
||||||
InstructionData::IntCompare { cond, .. } if i == 0 => intcc_to_peepmatic(cond).into(),
|
|
||||||
InstructionData::IntCompareImm { cond, .. } if i == 0 => intcc_to_peepmatic(cond).into(),
|
|
||||||
InstructionData::IntCompareImm { imm, .. } if i == 1 => imm.into(),
|
|
||||||
InstructionData::IntCond { cond, .. } if i == 0 => intcc_to_peepmatic(cond).into(),
|
|
||||||
InstructionData::IntCondTrap { cond, .. } if i == 0 => intcc_to_peepmatic(cond).into(),
|
|
||||||
InstructionData::IntSelect { cond, .. } if i == 0 => intcc_to_peepmatic(cond).into(),
|
|
||||||
InstructionData::UnaryBool { imm, .. } if i == 0 => {
|
|
||||||
Constant::Bool(imm, BitWidth::Polymorphic).into()
|
|
||||||
}
|
|
||||||
InstructionData::UnaryImm { imm, .. } if i == 0 => imm.into(),
|
|
||||||
ref otherwise => unsupported(otherwise),
|
|
||||||
};
|
|
||||||
|
|
||||||
#[inline(never)]
|
|
||||||
#[cold]
|
|
||||||
fn unsupported(data: &InstructionData) -> ! {
|
|
||||||
panic!("unsupported instruction data: {:?}", data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_argument(dfg: &DataFlowGraph, inst: Inst, i: usize) -> Option<Value> {
|
|
||||||
dfg.inst_args(inst).get(i).copied()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn peepmatic_ty_to_ir_ty(ty: Type, dfg: &DataFlowGraph, root: Inst) -> types::Type {
|
fn peepmatic_ty_to_ir_ty(ty: Type, dfg: &DataFlowGraph, root: Inst) -> types::Type {
|
||||||
match (ty.kind, bit_width(dfg, ty.bit_width, root)) {
|
match (ty.kind, bit_width(dfg, ty.bit_width, root)) {
|
||||||
(Kind::Int, 8) => types::I8,
|
(Kind::Int, 8) => types::I8,
|
||||||
@@ -681,39 +651,290 @@ unsafe impl<'a, 'b> InstructionSet<'b> for &'a dyn TargetIsa {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_part_at_path(
|
fn operator<E>(
|
||||||
&self,
|
&self,
|
||||||
pos: &mut FuncCursor<'b>,
|
pos: &mut FuncCursor<'b>,
|
||||||
root: ValueOrInst,
|
value_or_inst: ValueOrInst,
|
||||||
path: Path,
|
operands: &mut E,
|
||||||
) -> Option<Part<ValueOrInst>> {
|
) -> Option<Opcode>
|
||||||
// The root is path [0].
|
where
|
||||||
debug_assert!(!path.0.is_empty());
|
E: Extend<Part<Self::Instruction>>,
|
||||||
debug_assert_eq!(path.0[0], 0);
|
{
|
||||||
|
let inst = value_or_inst.resolve_inst(&pos.func.dfg)?;
|
||||||
let mut part = Part::Instruction(root);
|
Some(match pos.func.dfg[inst] {
|
||||||
for p in path.0[1..].iter().copied() {
|
InstructionData::Binary {
|
||||||
let inst = part.as_instruction()?.resolve_inst(&pos.func.dfg)?;
|
opcode: opcode @ Opcode::Band,
|
||||||
let operator = pos.func.dfg[inst].opcode();
|
args,
|
||||||
|
}
|
||||||
if p < operator.immediates_arity() {
|
| InstructionData::Binary {
|
||||||
part = get_immediate(&pos.func.dfg, inst, p as usize);
|
opcode: opcode @ Opcode::Bor,
|
||||||
continue;
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Bxor,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Iadd,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Ifcmp,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Imul,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Ishl,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Isub,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Rotl,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Rotr,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Sdiv,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Srem,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Sshr,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Udiv,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Urem,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Ushr,
|
||||||
|
args,
|
||||||
|
} => {
|
||||||
|
operands.extend(args.iter().map(|v| Part::Instruction((*v).into())));
|
||||||
|
opcode
|
||||||
}
|
}
|
||||||
|
|
||||||
let arg = p - operator.immediates_arity();
|
InstructionData::BinaryImm64 {
|
||||||
let arg = arg as usize;
|
opcode: opcode @ Opcode::BandImm,
|
||||||
let value = get_argument(&pos.func.dfg, inst, arg)?;
|
imm,
|
||||||
part = Part::Instruction(value.into());
|
arg,
|
||||||
}
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::BorImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::BxorImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::IaddImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::IfcmpImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::ImulImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::IrsubImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::IshlImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::RotlImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::RotrImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::SdivImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::SremImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::SshrImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::UdivImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::UremImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::UshrImm,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
} => {
|
||||||
|
operands.extend(
|
||||||
|
iter::once(imm.into()).chain(iter::once(Part::Instruction(arg.into()))),
|
||||||
|
);
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
|
||||||
log::trace!("get_part_at_path({:?}) = {:?}", path, part);
|
InstructionData::Branch {
|
||||||
Some(part)
|
opcode: opcode @ Opcode::Brnz,
|
||||||
}
|
ref args,
|
||||||
|
destination: _,
|
||||||
|
}
|
||||||
|
| InstructionData::Branch {
|
||||||
|
opcode: opcode @ Opcode::Brz,
|
||||||
|
ref args,
|
||||||
|
destination: _,
|
||||||
|
} => {
|
||||||
|
operands.extend(
|
||||||
|
args.as_slice(&pos.func.dfg.value_lists)
|
||||||
|
.iter()
|
||||||
|
.map(|v| Part::Instruction((*v).into()))
|
||||||
|
// NB: Peepmatic only knows about the condition, not any
|
||||||
|
// of the arguments to the block, which are special
|
||||||
|
// cased elsewhere, if/when we actually replace the
|
||||||
|
// instruction.
|
||||||
|
.take(1),
|
||||||
|
);
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
|
||||||
fn operator(&self, pos: &mut FuncCursor<'b>, value_or_inst: ValueOrInst) -> Option<Opcode> {
|
InstructionData::CondTrap {
|
||||||
let inst = value_or_inst.resolve_inst(&pos.func.dfg)?;
|
opcode: opcode @ Opcode::Trapnz,
|
||||||
Some(pos.func.dfg[inst].opcode())
|
arg,
|
||||||
|
code: _,
|
||||||
|
}
|
||||||
|
| InstructionData::CondTrap {
|
||||||
|
opcode: opcode @ Opcode::Trapz,
|
||||||
|
arg,
|
||||||
|
code: _,
|
||||||
|
} => {
|
||||||
|
operands.extend(iter::once(Part::Instruction(arg.into())));
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::IntCompare {
|
||||||
|
opcode: opcode @ Opcode::Icmp,
|
||||||
|
cond,
|
||||||
|
args,
|
||||||
|
} => {
|
||||||
|
operands.extend(
|
||||||
|
iter::once(intcc_to_peepmatic(cond).into())
|
||||||
|
.chain(args.iter().map(|v| Part::Instruction((*v).into()))),
|
||||||
|
);
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::IntCompareImm {
|
||||||
|
opcode: opcode @ Opcode::IcmpImm,
|
||||||
|
cond,
|
||||||
|
imm,
|
||||||
|
arg,
|
||||||
|
} => {
|
||||||
|
operands.extend(
|
||||||
|
iter::once(intcc_to_peepmatic(cond).into())
|
||||||
|
.chain(iter::once(Part::Constant(imm.into())))
|
||||||
|
.chain(iter::once(Part::Instruction(arg.into()))),
|
||||||
|
);
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::Ternary {
|
||||||
|
opcode: opcode @ Opcode::Select,
|
||||||
|
ref args,
|
||||||
|
} => {
|
||||||
|
operands.extend(args.iter().map(|v| Part::Instruction((*v).into())));
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::Unary {
|
||||||
|
opcode: opcode @ Opcode::AdjustSpDown,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::Unary {
|
||||||
|
opcode: opcode @ Opcode::Bint,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::Unary {
|
||||||
|
opcode: opcode @ Opcode::Ireduce,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::Unary {
|
||||||
|
opcode: opcode @ Opcode::Sextend,
|
||||||
|
arg,
|
||||||
|
}
|
||||||
|
| InstructionData::Unary {
|
||||||
|
opcode: opcode @ Opcode::Uextend,
|
||||||
|
arg,
|
||||||
|
} => {
|
||||||
|
operands.extend(iter::once(Part::Instruction(arg.into())));
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::UnaryBool { opcode, imm } => {
|
||||||
|
operands.extend(iter::once(Part::Constant(Constant::Bool(
|
||||||
|
imm,
|
||||||
|
BitWidth::Polymorphic,
|
||||||
|
))));
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::UnaryImm {
|
||||||
|
opcode: opcode @ Opcode::AdjustSpDownImm,
|
||||||
|
imm,
|
||||||
|
}
|
||||||
|
| InstructionData::UnaryImm {
|
||||||
|
opcode: opcode @ Opcode::Iconst,
|
||||||
|
imm,
|
||||||
|
} => {
|
||||||
|
operands.extend(iter::once(imm.into()));
|
||||||
|
opcode
|
||||||
|
}
|
||||||
|
ref otherwise => {
|
||||||
|
log::trace!("Not supported by Peepmatic: {:?}", otherwise);
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_inst_1(
|
fn make_inst_1(
|
||||||
|
|||||||
Binary file not shown.
@@ -1,7 +1,6 @@
|
|||||||
//! Interfacing with actual instructions.
|
//! Interfacing with actual instructions.
|
||||||
|
|
||||||
use crate::part::{Constant, Part};
|
use crate::part::{Constant, Part};
|
||||||
use crate::paths::Path;
|
|
||||||
use crate::r#type::Type;
|
use crate::r#type::Type;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
@@ -54,26 +53,22 @@ pub unsafe trait InstructionSet<'a> {
|
|||||||
new: Part<Self::Instruction>,
|
new: Part<Self::Instruction>,
|
||||||
) -> Self::Instruction;
|
) -> Self::Instruction;
|
||||||
|
|
||||||
/// Get the instruction, constant, or condition code at the given path.
|
|
||||||
///
|
|
||||||
/// If there is no such entity at the given path (e.g. we run into a
|
|
||||||
/// function parameter and can't traverse the path any further) then `None`
|
|
||||||
/// should be returned.
|
|
||||||
fn get_part_at_path(
|
|
||||||
&self,
|
|
||||||
context: &mut Self::Context,
|
|
||||||
root: Self::Instruction,
|
|
||||||
path: Path,
|
|
||||||
) -> Option<Part<Self::Instruction>>;
|
|
||||||
|
|
||||||
/// Get the given instruction's operator.
|
/// Get the given instruction's operator.
|
||||||
///
|
///
|
||||||
/// If the instruction isn't supported, then `None` should be returned.
|
/// If the instruction isn't supported, then `None` should be returned.
|
||||||
fn operator(
|
///
|
||||||
|
/// Additionally, if `Some` is returned, then the instruction's operands
|
||||||
|
/// must be pushed in order into `operands`. E.g. calling this method on
|
||||||
|
/// `(iadd $x $y)` would return `Some(iadd)` and extend `operands` with
|
||||||
|
/// `[$x, $y]`.
|
||||||
|
fn operator<E>(
|
||||||
&self,
|
&self,
|
||||||
context: &mut Self::Context,
|
context: &mut Self::Context,
|
||||||
instr: Self::Instruction,
|
instr: Self::Instruction,
|
||||||
) -> Option<Self::Operator>;
|
operands: &mut E,
|
||||||
|
) -> Option<Self::Operator>
|
||||||
|
where
|
||||||
|
E: Extend<Part<Self::Instruction>>;
|
||||||
|
|
||||||
/// Make a unary instruction.
|
/// Make a unary instruction.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ pub mod linear;
|
|||||||
pub mod optimizations;
|
pub mod optimizations;
|
||||||
pub mod optimizer;
|
pub mod optimizer;
|
||||||
pub mod part;
|
pub mod part;
|
||||||
pub mod paths;
|
|
||||||
pub mod r#type;
|
pub mod r#type;
|
||||||
pub mod unquote;
|
pub mod unquote;
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@
|
|||||||
|
|
||||||
use crate::cc::ConditionCode;
|
use crate::cc::ConditionCode;
|
||||||
use crate::integer_interner::{IntegerId, IntegerInterner};
|
use crate::integer_interner::{IntegerId, IntegerInterner};
|
||||||
use crate::paths::{PathId, PathInterner};
|
|
||||||
use crate::r#type::{BitWidth, Type};
|
use crate::r#type::{BitWidth, Type};
|
||||||
use crate::unquote::UnquoteOperator;
|
use crate::unquote::UnquoteOperator;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -24,9 +23,6 @@ where
|
|||||||
/// The linear optimizations.
|
/// The linear optimizations.
|
||||||
pub optimizations: Vec<Optimization<TOperator>>,
|
pub optimizations: Vec<Optimization<TOperator>>,
|
||||||
|
|
||||||
/// The de-duplicated paths referenced by these optimizations.
|
|
||||||
pub paths: PathInterner,
|
|
||||||
|
|
||||||
/// The integer literals referenced by these optimizations.
|
/// The integer literals referenced by these optimizations.
|
||||||
pub integers: IntegerInterner,
|
pub integers: IntegerInterner,
|
||||||
}
|
}
|
||||||
@@ -87,79 +83,54 @@ pub struct Match {
|
|||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||||
pub enum MatchOp {
|
pub enum MatchOp {
|
||||||
/// Switch on the opcode of an instruction.
|
/// Switch on the opcode of an instruction.
|
||||||
Opcode {
|
///
|
||||||
/// The path to the instruction whose opcode we're switching on.
|
/// Upon successfully matching an instruction's opcode, bind each of its
|
||||||
path: PathId,
|
/// operands to a LHS temporary.
|
||||||
},
|
Opcode(LhsId),
|
||||||
|
|
||||||
/// Does an instruction have a constant value?
|
/// Does an instruction have a constant value?
|
||||||
IsConst {
|
IsConst(LhsId),
|
||||||
/// The path to the instruction (or immediate) that we're checking
|
|
||||||
/// whether it is constant or not.
|
|
||||||
path: PathId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Is the constant value a power of two?
|
/// Is the constant value a power of two?
|
||||||
IsPowerOfTwo {
|
IsPowerOfTwo(LhsId),
|
||||||
/// The path to the instruction (or immediate) that we are checking
|
|
||||||
/// whether it is a constant power of two or not.
|
|
||||||
path: PathId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Switch on the bit width of a value.
|
/// Switch on the bit width of a value.
|
||||||
BitWidth {
|
BitWidth(LhsId),
|
||||||
/// The path to the instruction (or immediate) whose result's bit width
|
|
||||||
/// we are checking.
|
|
||||||
path: PathId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Does the value fit in our target architecture's native word size?
|
/// Does the value fit in our target architecture's native word size?
|
||||||
FitsInNativeWord {
|
FitsInNativeWord(LhsId),
|
||||||
/// The path to the instruction (or immediate) whose result we are
|
|
||||||
/// checking whether it fits in a native word or not.
|
|
||||||
path: PathId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Are the instructions (or immediates) at the given paths the same?
|
/// Are the instructions (or immediates) the same?
|
||||||
Eq {
|
Eq(LhsId, LhsId),
|
||||||
/// The path to the first instruction (or immediate).
|
|
||||||
path_a: PathId,
|
|
||||||
/// The path to the second instruction (or immediate).
|
|
||||||
path_b: PathId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Switch on the constant integer value of an instruction.
|
/// Switch on the constant integer value of an instruction.
|
||||||
IntegerValue {
|
IntegerValue(LhsId),
|
||||||
/// The path to the instruction.
|
|
||||||
path: PathId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Switch on the constant boolean value of an instruction.
|
/// Switch on the constant boolean value of an instruction.
|
||||||
BooleanValue {
|
BooleanValue(LhsId),
|
||||||
/// The path to the instruction.
|
|
||||||
path: PathId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Switch on a condition code.
|
/// Switch on a condition code.
|
||||||
ConditionCode {
|
ConditionCode(LhsId),
|
||||||
/// The path to the condition code.
|
|
||||||
path: PathId,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// No operation. Always evaluates to `None`.
|
/// No operation. Always evaluates to `Else`.
|
||||||
///
|
///
|
||||||
/// Exceedingly rare in real optimizations; nonetheless required to support
|
/// Never appears in real optimizations; nonetheless required to support
|
||||||
/// corner cases of the DSL, such as a LHS pattern that is nothing but a
|
/// corner cases of the DSL, such as a LHS pattern that is nothing but a
|
||||||
/// variable pattern.
|
/// variable.
|
||||||
Nop,
|
Nop,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A canonicalized identifier for a left-hand side value that was bound in a
|
/// A canonicalized identifier for a left-hand side value that was bound in a
|
||||||
/// pattern.
|
/// pattern.
|
||||||
|
///
|
||||||
|
/// These are defined in a pre-order traversal of the LHS pattern by successful
|
||||||
|
/// `MatchOp::Opcode` matches.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Serialize, Deserialize)]
|
||||||
pub struct LhsId(pub u16);
|
pub struct LhsId(pub u16);
|
||||||
|
|
||||||
/// A canonicalized identifier for a right-hand side value.
|
/// A canonicalized identifier for a right-hand side value.
|
||||||
|
///
|
||||||
|
/// These are defined by RHS actions.
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
||||||
pub struct RhsId(pub u16);
|
pub struct RhsId(pub u16);
|
||||||
|
|
||||||
@@ -171,8 +142,8 @@ pub struct RhsId(pub u16);
|
|||||||
pub enum Action<TOperator> {
|
pub enum Action<TOperator> {
|
||||||
/// Reuse something from the left-hand side.
|
/// Reuse something from the left-hand side.
|
||||||
GetLhs {
|
GetLhs {
|
||||||
/// The path to the instruction or value.
|
/// The left-hand side instruction or value.
|
||||||
path: PathId,
|
lhs: LhsId,
|
||||||
},
|
},
|
||||||
|
|
||||||
/// Perform compile-time evaluation.
|
/// Perform compile-time evaluation.
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use crate::instruction_set::InstructionSet;
|
|||||||
use crate::integer_interner::IntegerInterner;
|
use crate::integer_interner::IntegerInterner;
|
||||||
use crate::linear::{Action, MatchOp, MatchResult};
|
use crate::linear::{Action, MatchOp, MatchResult};
|
||||||
use crate::optimizer::PeepholeOptimizer;
|
use crate::optimizer::PeepholeOptimizer;
|
||||||
use crate::paths::PathInterner;
|
|
||||||
use peepmatic_automata::Automaton;
|
use peepmatic_automata::Automaton;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -25,9 +24,6 @@ pub struct PeepholeOptimizations<TOperator>
|
|||||||
where
|
where
|
||||||
TOperator: 'static + Copy + Debug + Eq + Hash,
|
TOperator: 'static + Copy + Debug + Eq + Hash,
|
||||||
{
|
{
|
||||||
/// The instruction paths referenced by the peephole optimizations.
|
|
||||||
pub paths: PathInterner,
|
|
||||||
|
|
||||||
/// Not all integers we're matching on fit in the `u32` that we use as the
|
/// Not all integers we're matching on fit in the `u32` that we use as the
|
||||||
/// result of match operations. So we intern them and refer to them by id.
|
/// result of match operations. So we intern them and refer to them by id.
|
||||||
pub integers: IntegerInterner,
|
pub integers: IntegerInterner,
|
||||||
@@ -88,6 +84,7 @@ where
|
|||||||
PeepholeOptimizer {
|
PeepholeOptimizer {
|
||||||
peep_opt: self,
|
peep_opt: self,
|
||||||
instr_set,
|
instr_set,
|
||||||
|
left_hand_sides: vec![],
|
||||||
right_hand_sides: vec![],
|
right_hand_sides: vec![],
|
||||||
actions: vec![],
|
actions: vec![],
|
||||||
backtracking_states: vec![],
|
backtracking_states: vec![],
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ where
|
|||||||
{
|
{
|
||||||
pub(crate) peep_opt: &'peep PeepholeOptimizations<TInstructionSet::Operator>,
|
pub(crate) peep_opt: &'peep PeepholeOptimizations<TInstructionSet::Operator>,
|
||||||
pub(crate) instr_set: TInstructionSet,
|
pub(crate) instr_set: TInstructionSet,
|
||||||
|
pub(crate) left_hand_sides: Vec<Part<TInstructionSet::Instruction>>,
|
||||||
pub(crate) right_hand_sides: Vec<Part<TInstructionSet::Instruction>>,
|
pub(crate) right_hand_sides: Vec<Part<TInstructionSet::Instruction>>,
|
||||||
pub(crate) actions: Vec<Action<TInstructionSet::Operator>>,
|
pub(crate) actions: Vec<Action<TInstructionSet::Operator>>,
|
||||||
pub(crate) backtracking_states: Vec<(State, usize)>,
|
pub(crate) backtracking_states: Vec<(State, usize, usize)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'peep, 'ctx, TInstructionSet> Debug for PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
|
impl<'peep, 'ctx, TInstructionSet> Debug for PeepholeOptimizer<'peep, 'ctx, TInstructionSet>
|
||||||
@@ -40,6 +41,7 @@ where
|
|||||||
let PeepholeOptimizer {
|
let PeepholeOptimizer {
|
||||||
peep_opt,
|
peep_opt,
|
||||||
instr_set: _,
|
instr_set: _,
|
||||||
|
left_hand_sides,
|
||||||
right_hand_sides,
|
right_hand_sides,
|
||||||
actions,
|
actions,
|
||||||
backtracking_states,
|
backtracking_states,
|
||||||
@@ -47,6 +49,7 @@ where
|
|||||||
f.debug_struct("PeepholeOptimizer")
|
f.debug_struct("PeepholeOptimizer")
|
||||||
.field("peep_opt", peep_opt)
|
.field("peep_opt", peep_opt)
|
||||||
.field("instr_set", &"_")
|
.field("instr_set", &"_")
|
||||||
|
.field("left_hand_sides", left_hand_sides)
|
||||||
.field("right_hand_sides", right_hand_sides)
|
.field("right_hand_sides", right_hand_sides)
|
||||||
.field("actions", actions)
|
.field("actions", actions)
|
||||||
.field("backtracking_states", backtracking_states)
|
.field("backtracking_states", backtracking_states)
|
||||||
@@ -117,12 +120,8 @@ where
|
|||||||
for action in actions.drain(..) {
|
for action in actions.drain(..) {
|
||||||
log::trace!("Evaluating action: {:?}", action);
|
log::trace!("Evaluating action: {:?}", action);
|
||||||
match action {
|
match action {
|
||||||
Action::GetLhs { path } => {
|
Action::GetLhs { lhs } => {
|
||||||
let path = self.peep_opt.paths.lookup(path);
|
let lhs = self.left_hand_sides[lhs.0 as usize];
|
||||||
let lhs = self
|
|
||||||
.instr_set
|
|
||||||
.get_part_at_path(context, root, path)
|
|
||||||
.expect("should always get part at path OK by the time it is bound");
|
|
||||||
self.right_hand_sides.push(lhs);
|
self.right_hand_sides.push(lhs);
|
||||||
}
|
}
|
||||||
Action::UnaryUnquote { operator, operand } => {
|
Action::UnaryUnquote { operator, operand } => {
|
||||||
@@ -284,22 +283,17 @@ where
|
|||||||
|
|
||||||
log::trace!("Evaluating match operation: {:?}", match_op);
|
log::trace!("Evaluating match operation: {:?}", match_op);
|
||||||
let result: MatchResult = (|| match match_op {
|
let result: MatchResult = (|| match match_op {
|
||||||
Opcode { path } => {
|
Opcode(id) => {
|
||||||
let path = self.peep_opt.paths.lookup(path);
|
let part = self.left_hand_sides[id.0 as usize];
|
||||||
let part = self
|
|
||||||
.instr_set
|
|
||||||
.get_part_at_path(context, root, path)
|
|
||||||
.ok_or(Else)?;
|
|
||||||
let inst = part.as_instruction().ok_or(Else)?;
|
let inst = part.as_instruction().ok_or(Else)?;
|
||||||
let op = self.instr_set.operator(context, inst).ok_or(Else)?;
|
let op = self
|
||||||
|
.instr_set
|
||||||
|
.operator(context, inst, &mut self.left_hand_sides)
|
||||||
|
.ok_or(Else)?;
|
||||||
Ok(op.into())
|
Ok(op.into())
|
||||||
}
|
}
|
||||||
IsConst { path } => {
|
IsConst(id) => {
|
||||||
let path = self.peep_opt.paths.lookup(path);
|
let part = self.left_hand_sides[id.0 as usize];
|
||||||
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()
|
||||||
@@ -308,12 +302,8 @@ where
|
|||||||
};
|
};
|
||||||
bool_to_match_result(is_const)
|
bool_to_match_result(is_const)
|
||||||
}
|
}
|
||||||
IsPowerOfTwo { path } => {
|
IsPowerOfTwo(id) => {
|
||||||
let path = self.peep_opt.paths.lookup(path);
|
let part = self.left_hand_sides[id.0 as usize];
|
||||||
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 is_pow2 = c.as_int().unwrap().is_power_of_two();
|
let is_pow2 = c.as_int().unwrap().is_power_of_two();
|
||||||
@@ -327,18 +317,11 @@ where
|
|||||||
let is_pow2 = c.as_int().unwrap().is_power_of_two();
|
let is_pow2 = c.as_int().unwrap().is_power_of_two();
|
||||||
bool_to_match_result(is_pow2)
|
bool_to_match_result(is_pow2)
|
||||||
}
|
}
|
||||||
Part::ConditionCode(_) => unreachable!(
|
Part::ConditionCode(_) => unreachable!("IsPowerOfTwo on a condition code"),
|
||||||
"IsPowerOfTwo on a condition
|
|
||||||
code"
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BitWidth { path } => {
|
BitWidth(id) => {
|
||||||
let path = self.peep_opt.paths.lookup(path);
|
let part = self.left_hand_sides[id.0 as usize];
|
||||||
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)) => {
|
||||||
@@ -355,15 +338,11 @@ where
|
|||||||
);
|
);
|
||||||
Ok(unsafe { NonZeroU32::new_unchecked(bit_width as u32) })
|
Ok(unsafe { NonZeroU32::new_unchecked(bit_width as u32) })
|
||||||
}
|
}
|
||||||
FitsInNativeWord { path } => {
|
FitsInNativeWord(id) => {
|
||||||
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 part = self.left_hand_sides[id.0 as usize];
|
||||||
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);
|
||||||
@@ -378,17 +357,9 @@ where
|
|||||||
};
|
};
|
||||||
bool_to_match_result(fits)
|
bool_to_match_result(fits)
|
||||||
}
|
}
|
||||||
Eq { path_a, path_b } => {
|
Eq(a, b) => {
|
||||||
let path_a = self.peep_opt.paths.lookup(path_a);
|
let part_a = self.left_hand_sides[a.0 as usize];
|
||||||
let part_a = self
|
let part_b = self.left_hand_sides[b.0 as usize];
|
||||||
.instr_set
|
|
||||||
.get_part_at_path(context, root, path_a)
|
|
||||||
.ok_or(Else)?;
|
|
||||||
let path_b = self.peep_opt.paths.lookup(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)) => {
|
||||||
@@ -401,12 +372,8 @@ where
|
|||||||
};
|
};
|
||||||
bool_to_match_result(eq)
|
bool_to_match_result(eq)
|
||||||
}
|
}
|
||||||
IntegerValue { path } => {
|
IntegerValue(id) => {
|
||||||
let path = self.peep_opt.paths.lookup(path);
|
let part = self.left_hand_sides[id.0 as usize];
|
||||||
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().ok_or(Else)?;
|
let x = c.as_int().ok_or(Else)?;
|
||||||
@@ -425,12 +392,8 @@ where
|
|||||||
Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
|
Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BooleanValue { path } => {
|
BooleanValue(id) => {
|
||||||
let path = self.peep_opt.paths.lookup(path);
|
let part = self.left_hand_sides[id.0 as usize];
|
||||||
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 b = c.as_bool().ok_or(Else)?;
|
let b = c.as_bool().ok_or(Else)?;
|
||||||
@@ -447,12 +410,8 @@ where
|
|||||||
Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
|
Part::ConditionCode(_) => unreachable!("IntegerValue on condition code"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ConditionCode { path } => {
|
ConditionCode(id) => {
|
||||||
let path = self.peep_opt.paths.lookup(path);
|
let part = self.left_hand_sides[id.0 as usize];
|
||||||
let part = self
|
|
||||||
.instr_set
|
|
||||||
.get_part_at_path(context, root, path)
|
|
||||||
.ok_or(Else)?;
|
|
||||||
let cc = part.as_condition_code().ok_or(Else)?;
|
let cc = part.as_condition_code().ok_or(Else)?;
|
||||||
let cc = cc as u32;
|
let cc = cc as u32;
|
||||||
debug_assert!(cc != 0);
|
debug_assert!(cc != 0);
|
||||||
@@ -483,12 +442,20 @@ where
|
|||||||
self.backtracking_states.clear();
|
self.backtracking_states.clear();
|
||||||
self.actions.clear();
|
self.actions.clear();
|
||||||
self.right_hand_sides.clear();
|
self.right_hand_sides.clear();
|
||||||
|
self.left_hand_sides.clear();
|
||||||
|
|
||||||
|
// `LhsId(0)` is always the root.
|
||||||
|
self.left_hand_sides.push(Part::Instruction(root));
|
||||||
|
|
||||||
let mut r#final = None;
|
let mut r#final = None;
|
||||||
|
|
||||||
let mut query = self.peep_opt.automata.query();
|
let mut query = self.peep_opt.automata.query();
|
||||||
loop {
|
loop {
|
||||||
log::trace!("Current state: {:?}", query.current_state());
|
log::trace!("Current state: {:?}", query.current_state());
|
||||||
|
log::trace!(
|
||||||
|
"self.left_hand_sides = {:#?}",
|
||||||
|
self.left_hand_sides.iter().enumerate().collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
|
||||||
if query.is_in_final_state() {
|
if query.is_in_final_state() {
|
||||||
// If we're in a final state (which means an optimization is
|
// If we're in a final state (which means an optimization is
|
||||||
@@ -507,8 +474,11 @@ where
|
|||||||
// 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 `Else` transition.
|
// then try taking the `Else` transition.
|
||||||
if query.has_transition_on(&Err(Else)) {
|
if query.has_transition_on(&Err(Else)) {
|
||||||
self.backtracking_states
|
self.backtracking_states.push((
|
||||||
.push((query.current_state(), self.actions.len()));
|
query.current_state(),
|
||||||
|
self.actions.len(),
|
||||||
|
self.left_hand_sides.len(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let match_op = match query.current_state_data() {
|
let match_op = match query.current_state_data() {
|
||||||
@@ -522,9 +492,10 @@ where
|
|||||||
actions
|
actions
|
||||||
} else if r#final.is_some() {
|
} else if r#final.is_some() {
|
||||||
break;
|
break;
|
||||||
} else if let Some((state, actions_len)) = self.backtracking_states.pop() {
|
} else if let Some((state, actions_len, lhs_len)) = self.backtracking_states.pop() {
|
||||||
query.go_to_state(state);
|
query.go_to_state(state);
|
||||||
self.actions.truncate(actions_len);
|
self.actions.truncate(actions_len);
|
||||||
|
self.left_hand_sides.truncate(lhs_len);
|
||||||
query
|
query
|
||||||
.next(&Err(Else))
|
.next(&Err(Else))
|
||||||
.expect("backtracking states always have `Else` transitions")
|
.expect("backtracking states always have `Else` transitions")
|
||||||
|
|||||||
@@ -1,242 +0,0 @@
|
|||||||
//! Representing paths through the dataflow graph.
|
|
||||||
//!
|
|
||||||
//! Paths are relative from a *root* instruction, which is the instruction we
|
|
||||||
//! are determining which, if any, optimizations apply.
|
|
||||||
//!
|
|
||||||
//! Paths are series of indices through each instruction's children as we
|
|
||||||
//! traverse down the graph from the root. Children are immediates followed by
|
|
||||||
//! arguments: `[imm0, imm1, ..., immN, arg0, arg1, ..., argN]`.
|
|
||||||
//!
|
|
||||||
//! ## Examples
|
|
||||||
//!
|
|
||||||
//! * `[0]` is the path to the root.
|
|
||||||
//! * `[0, 0]` is the path to the root's first child.
|
|
||||||
//! * `[0, 1]` is the path to the root's second child.
|
|
||||||
//! * `[0, 1, 0]` is the path to the root's second child's first child.
|
|
||||||
//!
|
|
||||||
//! ## Interning
|
|
||||||
//!
|
|
||||||
//! To avoid extra allocations, de-duplicate paths, and reference them via a
|
|
||||||
//! fixed-length value, we intern paths inside a `PathInterner` and then
|
|
||||||
//! reference them via `PathId`.
|
|
||||||
|
|
||||||
// TODO: Make `[]` the path to the root, and get rid of this redundant leading
|
|
||||||
// zero that is currently in every single path.
|
|
||||||
|
|
||||||
use serde::de::{Deserializer, SeqAccess, Visitor};
|
|
||||||
use serde::ser::{SerializeSeq, Serializer};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::convert::TryInto;
|
|
||||||
use std::fmt;
|
|
||||||
use std::hash::{Hash, Hasher};
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
|
|
||||||
/// A path through the data-flow graph from the root instruction.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub struct Path<'a>(pub &'a [u8]);
|
|
||||||
|
|
||||||
impl Path<'_> {
|
|
||||||
/// Construct a new path through the data-flow graph from the root
|
|
||||||
/// instruction.
|
|
||||||
pub fn new(path: &impl AsRef<[u8]>) -> Path {
|
|
||||||
Path(path.as_ref())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An identifier for an interned path.
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct PathId(u16);
|
|
||||||
|
|
||||||
/// An interner and de-duplicator for `Path`s.
|
|
||||||
///
|
|
||||||
/// Can be serialized and deserialized while maintaining the same id to interned
|
|
||||||
/// path mapping.
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct PathInterner {
|
|
||||||
/// A map from a path (whose owned data is inside `arena`) to the canonical
|
|
||||||
/// `PathId` we assigned it when interning it.
|
|
||||||
map: HashMap<UnsafePath, PathId>,
|
|
||||||
|
|
||||||
/// A map from a `PathId` index to an unsafe, self-borrowed path pointing
|
|
||||||
/// into `arena`. It is safe to given these out as safe `Path`s, as long as
|
|
||||||
/// the lifetime is not longer than this `PathInterner`'s lifetime.
|
|
||||||
paths: Vec<UnsafePath>,
|
|
||||||
|
|
||||||
/// Bump allocation arena for path data. The bump arena ensures that these
|
|
||||||
/// allocations never move, and are therefore safe for self-references.
|
|
||||||
arena: bumpalo::Bump,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathInterner {
|
|
||||||
/// Construct a new, empty `PathInterner`.
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self::default()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Intern a path into this `PathInterner`, returning its canonical
|
|
||||||
/// `PathId`.
|
|
||||||
///
|
|
||||||
/// If we've already interned this path before, then the existing id we
|
|
||||||
/// already assigned to it is returned. If we've never seen this path
|
|
||||||
/// before, then it is copied into this `PathInterner` and a new id is
|
|
||||||
/// assigned to it.
|
|
||||||
#[inline]
|
|
||||||
pub fn intern<'a>(&mut self, path: Path<'a>) -> PathId {
|
|
||||||
let unsafe_path = unsafe { UnsafePath::from_path(&path) };
|
|
||||||
if let Some(id) = self.map.get(&unsafe_path) {
|
|
||||||
return *id;
|
|
||||||
}
|
|
||||||
self.intern_new(path)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(never)]
|
|
||||||
fn intern_new<'a>(&mut self, path: Path<'a>) -> PathId {
|
|
||||||
let id: u16 = self
|
|
||||||
.paths
|
|
||||||
.len()
|
|
||||||
.try_into()
|
|
||||||
.expect("too many paths interned");
|
|
||||||
let id = PathId(id);
|
|
||||||
|
|
||||||
let our_path = self.arena.alloc_slice_copy(&path.0);
|
|
||||||
let unsafe_path = unsafe { UnsafePath::from_slice(&our_path) };
|
|
||||||
|
|
||||||
self.paths.push(unsafe_path.clone());
|
|
||||||
let old = self.map.insert(unsafe_path, id);
|
|
||||||
|
|
||||||
debug_assert!(old.is_none());
|
|
||||||
debug_assert_eq!(self.lookup(id), path);
|
|
||||||
debug_assert_eq!(self.intern(path), id);
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Lookup a previously interned path by id.
|
|
||||||
#[inline]
|
|
||||||
pub fn lookup<'a>(&'a self, id: PathId) -> Path<'a> {
|
|
||||||
let unsafe_path = self
|
|
||||||
.paths
|
|
||||||
.get(id.0 as usize)
|
|
||||||
.unwrap_or_else(|| Self::lookup_failure());
|
|
||||||
unsafe { unsafe_path.as_path() }
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline(never)]
|
|
||||||
fn lookup_failure() -> ! {
|
|
||||||
panic!(
|
|
||||||
"no path for the given id; this can only happen when mixing `PathId`s with different \
|
|
||||||
`PathInterner`s"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for PathInterner {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
let mut seq = serializer.serialize_seq(Some(self.paths.len()))?;
|
|
||||||
for p in &self.paths {
|
|
||||||
let p = unsafe { p.as_path() };
|
|
||||||
seq.serialize_element(&p)?;
|
|
||||||
}
|
|
||||||
seq.end()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for PathInterner {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
deserializer.deserialize_seq(PathInternerVisitor {
|
|
||||||
marker: PhantomData,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct PathInternerVisitor {
|
|
||||||
marker: PhantomData<fn() -> PathInterner>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Visitor<'de> for PathInternerVisitor {
|
|
||||||
type Value = PathInterner;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(formatter, "a `peepmatic_runtime::paths::PathInterner`")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_seq<M>(self, mut access: M) -> Result<Self::Value, M::Error>
|
|
||||||
where
|
|
||||||
M: SeqAccess<'de>,
|
|
||||||
{
|
|
||||||
const DEFAULT_CAPACITY: usize = 16;
|
|
||||||
let capacity = access.size_hint().unwrap_or(DEFAULT_CAPACITY);
|
|
||||||
|
|
||||||
let mut interner = PathInterner {
|
|
||||||
map: HashMap::with_capacity(capacity),
|
|
||||||
paths: Vec::with_capacity(capacity),
|
|
||||||
arena: bumpalo::Bump::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
while let Some(path) = access.next_element::<Path>()? {
|
|
||||||
interner.intern(path);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(interner)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// An unsafe, unchecked borrow of a path. Not for use outside of
|
|
||||||
/// `PathInterner`!
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
struct UnsafePath {
|
|
||||||
ptr: *const u8,
|
|
||||||
len: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for UnsafePath {
|
|
||||||
fn eq(&self, rhs: &UnsafePath) -> bool {
|
|
||||||
unsafe { self.as_slice() == rhs.as_slice() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for UnsafePath {}
|
|
||||||
|
|
||||||
impl Hash for UnsafePath {
|
|
||||||
fn hash<H>(&self, hasher: &mut H)
|
|
||||||
where
|
|
||||||
H: Hasher,
|
|
||||||
{
|
|
||||||
unsafe { self.as_slice().hash(hasher) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Safety: callers must ensure that the constructed values won't have unsafe
|
|
||||||
/// usages of `PartialEq`, `Eq`, or `Hash`.
|
|
||||||
impl UnsafePath {
|
|
||||||
unsafe fn from_path(p: &Path) -> Self {
|
|
||||||
Self::from_slice(&p.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn from_slice(s: &[u8]) -> Self {
|
|
||||||
UnsafePath {
|
|
||||||
ptr: s.as_ptr(),
|
|
||||||
len: s.len(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Safety: callers must ensure that `'a` does not outlive the lifetime of the
|
|
||||||
/// underlying data.
|
|
||||||
impl UnsafePath {
|
|
||||||
unsafe fn as_slice<'a>(&self) -> &'a [u8] {
|
|
||||||
std::slice::from_raw_parts(self.ptr, self.len)
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe fn as_path<'a>(&self) -> Path<'a> {
|
|
||||||
Path(self.as_slice())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ use peepmatic_runtime::{
|
|||||||
cc::ConditionCode,
|
cc::ConditionCode,
|
||||||
instruction_set::InstructionSet,
|
instruction_set::InstructionSet,
|
||||||
part::{Constant, Part},
|
part::{Constant, Part},
|
||||||
paths::Path,
|
|
||||||
r#type::{BitWidth, Kind, Type},
|
r#type::{BitWidth, Kind, Type},
|
||||||
};
|
};
|
||||||
use peepmatic_test_operator::TestOperator;
|
use peepmatic_test_operator::TestOperator;
|
||||||
@@ -329,43 +328,23 @@ unsafe impl<'a> InstructionSet<'a> for TestIsa {
|
|||||||
new
|
new
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_part_at_path(
|
fn operator<E>(
|
||||||
&self,
|
&self,
|
||||||
program: &mut Program,
|
program: &mut Program,
|
||||||
root: Instruction,
|
instr: Self::Instruction,
|
||||||
path: Path,
|
operands: &mut E,
|
||||||
) -> Option<Part<Instruction>> {
|
) -> Option<Self::Operator>
|
||||||
log::debug!("get_part_at_path({:?})", path);
|
where
|
||||||
|
E: Extend<Part<Instruction>>,
|
||||||
assert!(!path.0.is_empty());
|
{
|
||||||
assert_eq!(path.0[0], 0);
|
|
||||||
|
|
||||||
let mut part = Part::Instruction(root);
|
|
||||||
for p in &path.0[1..] {
|
|
||||||
if let Part::Instruction(inst) = part {
|
|
||||||
let data = program.data(inst);
|
|
||||||
let p = *p as usize;
|
|
||||||
|
|
||||||
if p < data.immediates.len() {
|
|
||||||
part = data.immediates[p].into();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(inst) = data.arguments.get(p - data.immediates.len()).copied() {
|
|
||||||
part = Part::Instruction(inst);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(part)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operator(&self, program: &mut Program, instr: Instruction) -> Option<TestOperator> {
|
|
||||||
log::debug!("operator({:?})", instr);
|
log::debug!("operator({:?})", instr);
|
||||||
let data = program.data(instr);
|
let data = program.data(instr);
|
||||||
|
operands.extend(
|
||||||
|
data.immediates
|
||||||
|
.iter()
|
||||||
|
.map(|imm| Part::from(*imm))
|
||||||
|
.chain(data.arguments.iter().map(|inst| Part::Instruction(*inst))),
|
||||||
|
);
|
||||||
Some(data.operator)
|
Some(data.operator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ use peepmatic_runtime::{
|
|||||||
cc::ConditionCode,
|
cc::ConditionCode,
|
||||||
integer_interner::{IntegerId, IntegerInterner},
|
integer_interner::{IntegerId, IntegerInterner},
|
||||||
linear,
|
linear,
|
||||||
paths::{PathId, PathInterner},
|
|
||||||
};
|
};
|
||||||
use std::convert::{TryFrom, TryInto};
|
use std::convert::{TryFrom, TryInto};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -15,7 +14,7 @@ use std::io::{self, Write};
|
|||||||
use std::num::{NonZeroU16, NonZeroU32};
|
use std::num::{NonZeroU16, 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 IntegerInterner);
|
||||||
|
|
||||||
impl<TOperator> DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>
|
impl<TOperator> DotFmt<linear::MatchResult, linear::MatchOp, Box<[linear::Action<TOperator>]>>
|
||||||
for PeepholeDotFmt<'_>
|
for PeepholeDotFmt<'_>
|
||||||
@@ -44,7 +43,7 @@ where
|
|||||||
write!(w, "{}", cc)
|
write!(w, "{}", cc)
|
||||||
}
|
}
|
||||||
linear::MatchOp::IntegerValue { .. } => {
|
linear::MatchOp::IntegerValue { .. } => {
|
||||||
let x = self.1.lookup(IntegerId(
|
let x = self.0.lookup(IntegerId(
|
||||||
NonZeroU16::new(x.get().try_into().unwrap()).unwrap(),
|
NonZeroU16::new(x.get().try_into().unwrap()).unwrap(),
|
||||||
));
|
));
|
||||||
write!(w, "{}", x)
|
write!(w, "{}", x)
|
||||||
@@ -61,17 +60,16 @@ where
|
|||||||
|
|
||||||
write!(w, r#"<font face="monospace">"#)?;
|
write!(w, r#"<font face="monospace">"#)?;
|
||||||
|
|
||||||
let p = p(self.0);
|
|
||||||
match op {
|
match op {
|
||||||
Opcode { path } => write!(w, "opcode @ {}", p(path))?,
|
Opcode(id) => write!(w, "opcode $lhs{}", id.0)?,
|
||||||
IsConst { path } => write!(w, "is-const? @ {}", p(path))?,
|
IsConst(id) => write!(w, "is-const? $lhs{}", id.0)?,
|
||||||
IsPowerOfTwo { path } => write!(w, "is-power-of-two? @ {}", p(path))?,
|
IsPowerOfTwo(id) => write!(w, "is-power-of-two? $lhs{}", id.0)?,
|
||||||
BitWidth { path } => write!(w, "bit-width @ {}", p(path))?,
|
BitWidth(id) => write!(w, "bit-width $lhs{}", id.0)?,
|
||||||
FitsInNativeWord { path } => write!(w, "fits-in-native-word @ {}", p(path))?,
|
FitsInNativeWord(id) => write!(w, "fits-in-native-word $lhs{}", id.0)?,
|
||||||
Eq { path_a, path_b } => write!(w, "{} == {}", p(path_a), p(path_b))?,
|
Eq(a, b) => write!(w, "$lhs{} == $lhs{}", a.0, b.0)?,
|
||||||
IntegerValue { path } => write!(w, "integer-value @ {}", p(path))?,
|
IntegerValue(id) => write!(w, "integer-value $lhs{}", id.0)?,
|
||||||
BooleanValue { path } => write!(w, "boolean-value @ {}", p(path))?,
|
BooleanValue(id) => write!(w, "boolean-value $lhs{}", id.0)?,
|
||||||
ConditionCode { path } => write!(w, "condition-code @ {}", p(path))?,
|
ConditionCode(id) => write!(w, "condition-code $lhs{}", id.0)?,
|
||||||
Nop => write!(w, "nop")?,
|
Nop => write!(w, "nop")?,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,11 +89,9 @@ where
|
|||||||
|
|
||||||
write!(w, r#"<font face="monospace">"#)?;
|
write!(w, r#"<font face="monospace">"#)?;
|
||||||
|
|
||||||
let p = p(self.0);
|
|
||||||
|
|
||||||
for a in actions.iter() {
|
for a in actions.iter() {
|
||||||
match a {
|
match a {
|
||||||
GetLhs { path } => write!(w, "get-lhs @ {}<br/>", p(path))?,
|
GetLhs { lhs } => write!(w, "get-lhs $lhs{}<br/>", lhs.0)?,
|
||||||
UnaryUnquote { operator, operand } => {
|
UnaryUnquote { operator, operand } => {
|
||||||
write!(w, "eval {:?} $rhs{}<br/>", operator, operand.0)?
|
write!(w, "eval {:?} $rhs{}<br/>", operator, operand.0)?
|
||||||
}
|
}
|
||||||
@@ -107,7 +103,7 @@ where
|
|||||||
MakeIntegerConst {
|
MakeIntegerConst {
|
||||||
value,
|
value,
|
||||||
bit_width: _,
|
bit_width: _,
|
||||||
} => write!(w, "make {}<br/>", self.1.lookup(*value))?,
|
} => write!(w, "make {}<br/>", self.0.lookup(*value))?,
|
||||||
MakeBooleanConst {
|
MakeBooleanConst {
|
||||||
value,
|
value,
|
||||||
bit_width: _,
|
bit_width: _,
|
||||||
@@ -142,13 +138,3 @@ where
|
|||||||
writeln!(w, "</font>")
|
writeln!(w, "</font>")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn p<'a>(paths: &'a PathInterner) -> impl Fn(&PathId) -> String + 'a {
|
|
||||||
move |path: &PathId| {
|
|
||||||
let mut s = vec![];
|
|
||||||
for b in paths.lookup(*path).0 {
|
|
||||||
s.push(b.to_string());
|
|
||||||
}
|
|
||||||
s.join(".")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -142,11 +142,10 @@ where
|
|||||||
sort_lexicographically(&mut opts);
|
sort_lexicographically(&mut opts);
|
||||||
|
|
||||||
let automata = automatize(&opts);
|
let automata = automatize(&opts);
|
||||||
let paths = opts.paths;
|
|
||||||
let integers = opts.integers;
|
let integers = opts.integers;
|
||||||
|
|
||||||
if let Ok(path) = std::env::var("PEEPMATIC_DOT") {
|
if let Ok(path) = std::env::var("PEEPMATIC_DOT") {
|
||||||
let f = dot_fmt::PeepholeDotFmt(&paths, &integers);
|
let f = dot_fmt::PeepholeDotFmt(&integers);
|
||||||
if let Err(e) = automata.write_dot_file(&f, &path) {
|
if let Err(e) = automata.write_dot_file(&f, &path) {
|
||||||
panic!(
|
panic!(
|
||||||
"failed to write GraphViz Dot file to PEEPMATIC_DOT={}; error: {}",
|
"failed to write GraphViz Dot file to PEEPMATIC_DOT={}; error: {}",
|
||||||
@@ -155,11 +154,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(PeepholeOptimizations {
|
Ok(PeepholeOptimizations { integers, automata })
|
||||||
paths,
|
|
||||||
integers,
|
|
||||||
automata,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,9 +1,6 @@
|
|||||||
//! Passes over the linear IR.
|
//! Passes over the linear IR.
|
||||||
|
|
||||||
use peepmatic_runtime::{
|
use peepmatic_runtime::linear;
|
||||||
linear,
|
|
||||||
paths::{PathId, PathInterner},
|
|
||||||
};
|
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
@@ -33,13 +30,12 @@ where
|
|||||||
{
|
{
|
||||||
let linear::Optimizations {
|
let linear::Optimizations {
|
||||||
ref mut optimizations,
|
ref mut optimizations,
|
||||||
ref paths,
|
|
||||||
..
|
..
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
// NB: we *cannot* use an unstable sort here, because we want deterministic
|
// NB: we *cannot* use an unstable sort here, because we want deterministic
|
||||||
// compilation of optimizations to automata.
|
// compilation of optimizations to automata.
|
||||||
optimizations.sort_by(|a, b| compare_optimization_generality(paths, a, b));
|
optimizations.sort_by(compare_optimization_generality);
|
||||||
debug_assert!(is_sorted_by_generality(opts));
|
debug_assert!(is_sorted_by_generality(opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -52,17 +48,14 @@ where
|
|||||||
{
|
{
|
||||||
let linear::Optimizations {
|
let linear::Optimizations {
|
||||||
ref mut optimizations,
|
ref mut optimizations,
|
||||||
ref paths,
|
|
||||||
..
|
..
|
||||||
} = opts;
|
} = opts;
|
||||||
|
|
||||||
// NB: we *cannot* use an unstable sort here, same as above.
|
// NB: we *cannot* use an unstable sort here, same as above.
|
||||||
optimizations
|
optimizations.sort_by(|a, b| compare_optimizations(a, b, |a_len, b_len| a_len.cmp(&b_len)));
|
||||||
.sort_by(|a, b| compare_optimizations(paths, a, b, |a_len, b_len| a_len.cmp(&b_len)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_optimizations<TOperator>(
|
fn compare_optimizations<TOperator>(
|
||||||
paths: &PathInterner,
|
|
||||||
a: &linear::Optimization<TOperator>,
|
a: &linear::Optimization<TOperator>,
|
||||||
b: &linear::Optimization<TOperator>,
|
b: &linear::Optimization<TOperator>,
|
||||||
compare_lengths: impl Fn(usize, usize) -> Ordering,
|
compare_lengths: impl Fn(usize, usize) -> Ordering,
|
||||||
@@ -71,7 +64,7 @@ where
|
|||||||
TOperator: Copy + Debug + Eq + Hash,
|
TOperator: Copy + Debug + Eq + Hash,
|
||||||
{
|
{
|
||||||
for (a, b) in a.matches.iter().zip(b.matches.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(a.operation, b.operation);
|
||||||
if c != Ordering::Equal {
|
if c != Ordering::Equal {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -91,86 +84,62 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn compare_optimization_generality<TOperator>(
|
fn compare_optimization_generality<TOperator>(
|
||||||
paths: &PathInterner,
|
|
||||||
a: &linear::Optimization<TOperator>,
|
a: &linear::Optimization<TOperator>,
|
||||||
b: &linear::Optimization<TOperator>,
|
b: &linear::Optimization<TOperator>,
|
||||||
) -> Ordering
|
) -> Ordering
|
||||||
where
|
where
|
||||||
TOperator: Copy + Debug + Eq + Hash,
|
TOperator: Copy + Debug + Eq + Hash,
|
||||||
{
|
{
|
||||||
compare_optimizations(paths, a, b, |a_len, b_len| {
|
compare_optimizations(a, b, |a_len, b_len| {
|
||||||
// If they shared equivalent prefixes, then compare lengths and invert the
|
// If they shared equivalent prefixes, then compare lengths and invert the
|
||||||
// result because longer patterns are less general than shorter patterns.
|
// result because longer patterns are less general than shorter patterns.
|
||||||
a_len.cmp(&b_len).reverse()
|
a_len.cmp(&b_len).reverse()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_match_op_generality(
|
fn compare_match_op_generality(a: linear::MatchOp, b: linear::MatchOp) -> Ordering {
|
||||||
paths: &PathInterner,
|
|
||||||
a: linear::MatchOp,
|
|
||||||
b: linear::MatchOp,
|
|
||||||
) -> Ordering {
|
|
||||||
use linear::MatchOp::*;
|
use linear::MatchOp::*;
|
||||||
match (a, b) {
|
match (a, b) {
|
||||||
(Opcode { path: a }, Opcode { path: b }) => compare_paths(paths, a, b),
|
(Opcode(a), Opcode(b)) => a.cmp(&b),
|
||||||
(Opcode { .. }, _) => Ordering::Less,
|
(Opcode(_), _) => Ordering::Less,
|
||||||
(_, Opcode { .. }) => Ordering::Greater,
|
(_, Opcode(_)) => Ordering::Greater,
|
||||||
|
|
||||||
(IntegerValue { path: a }, IntegerValue { path: b }) => compare_paths(paths, a, b),
|
(IntegerValue(a), IntegerValue(b)) => a.cmp(&b),
|
||||||
(IntegerValue { .. }, _) => Ordering::Less,
|
(IntegerValue(_), _) => Ordering::Less,
|
||||||
(_, IntegerValue { .. }) => Ordering::Greater,
|
(_, IntegerValue(_)) => Ordering::Greater,
|
||||||
|
|
||||||
(BooleanValue { path: a }, BooleanValue { path: b }) => compare_paths(paths, a, b),
|
(BooleanValue(a), BooleanValue(b)) => a.cmp(&b),
|
||||||
(BooleanValue { .. }, _) => Ordering::Less,
|
(BooleanValue(_), _) => Ordering::Less,
|
||||||
(_, BooleanValue { .. }) => Ordering::Greater,
|
(_, BooleanValue(_)) => Ordering::Greater,
|
||||||
|
|
||||||
(ConditionCode { path: a }, ConditionCode { path: b }) => compare_paths(paths, a, b),
|
(ConditionCode(a), ConditionCode(b)) => a.cmp(&b),
|
||||||
(ConditionCode { .. }, _) => Ordering::Less,
|
(ConditionCode(_), _) => Ordering::Less,
|
||||||
(_, ConditionCode { .. }) => Ordering::Greater,
|
(_, ConditionCode(_)) => Ordering::Greater,
|
||||||
|
|
||||||
(IsConst { path: a }, IsConst { path: b }) => compare_paths(paths, a, b),
|
(IsConst(a), IsConst(b)) => a.cmp(&b),
|
||||||
(IsConst { .. }, _) => Ordering::Less,
|
(IsConst(_), _) => Ordering::Less,
|
||||||
(_, IsConst { .. }) => Ordering::Greater,
|
(_, IsConst(_)) => Ordering::Greater,
|
||||||
|
|
||||||
(
|
(Eq(a1, b1), Eq(a2, b2)) => a1.cmp(&a2).then(b1.cmp(&b2)),
|
||||||
Eq {
|
(Eq(..), _) => Ordering::Less,
|
||||||
path_a: pa1,
|
(_, Eq(..)) => Ordering::Greater,
|
||||||
path_b: pb1,
|
|
||||||
},
|
|
||||||
Eq {
|
|
||||||
path_a: pa2,
|
|
||||||
path_b: pb2,
|
|
||||||
},
|
|
||||||
) => compare_paths(paths, pa1, pa2).then(compare_paths(paths, pb1, pb2)),
|
|
||||||
(Eq { .. }, _) => Ordering::Less,
|
|
||||||
(_, Eq { .. }) => Ordering::Greater,
|
|
||||||
|
|
||||||
(IsPowerOfTwo { path: a }, IsPowerOfTwo { path: b }) => compare_paths(paths, a, b),
|
(IsPowerOfTwo(a), IsPowerOfTwo(b)) => a.cmp(&b),
|
||||||
(IsPowerOfTwo { .. }, _) => Ordering::Less,
|
(IsPowerOfTwo(_), _) => Ordering::Less,
|
||||||
(_, IsPowerOfTwo { .. }) => Ordering::Greater,
|
(_, IsPowerOfTwo(_)) => Ordering::Greater,
|
||||||
|
|
||||||
(BitWidth { path: a }, BitWidth { path: b }) => compare_paths(paths, a, b),
|
(BitWidth(a), BitWidth(b)) => a.cmp(&b),
|
||||||
(BitWidth { .. }, _) => Ordering::Less,
|
(BitWidth(_), _) => Ordering::Less,
|
||||||
(_, BitWidth { .. }) => Ordering::Greater,
|
(_, BitWidth(_)) => Ordering::Greater,
|
||||||
|
|
||||||
(FitsInNativeWord { path: a }, FitsInNativeWord { path: b }) => compare_paths(paths, a, b),
|
(FitsInNativeWord(a), FitsInNativeWord(b)) => a.cmp(&b),
|
||||||
(FitsInNativeWord { .. }, _) => Ordering::Less,
|
(FitsInNativeWord(_), _) => Ordering::Less,
|
||||||
(_, FitsInNativeWord { .. }) => Ordering::Greater,
|
(_, FitsInNativeWord(_)) => Ordering::Greater,
|
||||||
|
|
||||||
(Nop, Nop) => Ordering::Equal,
|
(Nop, Nop) => Ordering::Equal,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_paths(paths: &PathInterner, a: PathId, b: PathId) -> Ordering {
|
|
||||||
if a == b {
|
|
||||||
Ordering::Equal
|
|
||||||
} else {
|
|
||||||
let a = paths.lookup(a);
|
|
||||||
let b = paths.lookup(b);
|
|
||||||
a.0.cmp(&b.0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Are the given optimizations sorted from least to most general?
|
/// Are the given optimizations sorted from least to most general?
|
||||||
pub(crate) fn is_sorted_by_generality<TOperator>(opts: &linear::Optimizations<TOperator>) -> bool
|
pub(crate) fn is_sorted_by_generality<TOperator>(opts: &linear::Optimizations<TOperator>) -> bool
|
||||||
where
|
where
|
||||||
@@ -178,7 +147,7 @@ where
|
|||||||
{
|
{
|
||||||
opts.optimizations
|
opts.optimizations
|
||||||
.windows(2)
|
.windows(2)
|
||||||
.all(|w| compare_optimization_generality(&opts.paths, &w[0], &w[1]) <= Ordering::Equal)
|
.all(|w| compare_optimization_generality(&w[0], &w[1]) <= Ordering::Equal)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Are the given optimizations sorted lexicographically?
|
/// Are the given optimizations sorted lexicographically?
|
||||||
@@ -189,8 +158,7 @@ where
|
|||||||
TOperator: Copy + Debug + Eq + Hash,
|
TOperator: Copy + Debug + Eq + Hash,
|
||||||
{
|
{
|
||||||
opts.optimizations.windows(2).all(|w| {
|
opts.optimizations.windows(2).all(|w| {
|
||||||
compare_optimizations(&opts.paths, &w[0], &w[1], |a_len, b_len| a_len.cmp(&b_len))
|
compare_optimizations(&w[0], &w[1], |a_len, b_len| a_len.cmp(&b_len)) <= Ordering::Equal
|
||||||
<= Ordering::Equal
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,10 +277,7 @@ where
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use peepmatic_runtime::{
|
use peepmatic_runtime::linear::{bool_to_match_result, Else, LhsId, MatchOp::*, MatchResult};
|
||||||
linear::{bool_to_match_result, Else, MatchOp::*, MatchResult},
|
|
||||||
paths::*,
|
|
||||||
};
|
|
||||||
use peepmatic_test_operator::TestOperator;
|
use peepmatic_test_operator::TestOperator;
|
||||||
use std::num::NonZeroU32;
|
use std::num::NonZeroU32;
|
||||||
|
|
||||||
@@ -374,7 +339,6 @@ mod tests {
|
|||||||
eprintln!("after = {:#?}", before);
|
eprintln!("after = {:#?}", before);
|
||||||
|
|
||||||
let linear::Optimizations {
|
let linear::Optimizations {
|
||||||
mut paths,
|
|
||||||
mut integers,
|
mut integers,
|
||||||
optimizations,
|
optimizations,
|
||||||
} = opts;
|
} = opts;
|
||||||
@@ -389,9 +353,8 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut p = |p: &[u8]| paths.intern(Path::new(&p));
|
|
||||||
let mut i = |i: u64| Ok(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 i);
|
||||||
|
|
||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
@@ -452,7 +415,6 @@ mod tests {
|
|||||||
eprintln!("after = {:#?}", before);
|
eprintln!("after = {:#?}", before);
|
||||||
|
|
||||||
let linear::Optimizations {
|
let linear::Optimizations {
|
||||||
mut paths,
|
|
||||||
mut integers,
|
mut integers,
|
||||||
optimizations,
|
optimizations,
|
||||||
} = opts;
|
} = opts;
|
||||||
@@ -467,9 +429,8 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut p = |p: &[u8]| paths.intern(Path::new(&p));
|
|
||||||
let mut i = |i: u64| Ok(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 i);
|
||||||
|
|
||||||
assert_eq!(expected, actual);
|
assert_eq!(expected, actual);
|
||||||
}
|
}
|
||||||
@@ -488,55 +449,43 @@ 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) -> MatchResult| vec![
|
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(Opcode { path: p(&[0, 1]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(2)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(IntegerValue { path: p(&[0, 1]) }, i(42))
|
(IntegerValue(LhsId(2)), i(42))
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true)),
|
(IsConst(LhsId(2)), bool_to_match_result(true)),
|
||||||
(
|
(IsPowerOfTwo(LhsId(2)), bool_to_match_result(true))
|
||||||
IsPowerOfTwo { path: p(&[0, 1]) },
|
|
||||||
bool_to_match_result(true)
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true)),
|
(IsConst(LhsId(2)), bool_to_match_result(true)),
|
||||||
(
|
(BitWidth(LhsId(1)), Ok(NonZeroU32::new(32).unwrap()))
|
||||||
BitWidth { path: p(&[0, 0]) },
|
|
||||||
Ok(NonZeroU32::new(32).unwrap())
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(IsConst { path: p(&[0, 1]) }, bool_to_match_result(true))
|
(IsConst(LhsId(2)), bool_to_match_result(true))
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(
|
(Eq(LhsId(2), LhsId(1)), bool_to_match_result(true))
|
||||||
Eq {
|
|
||||||
path_a: p(&[0, 1]),
|
|
||||||
path_b: p(&[0, 0]),
|
|
||||||
},
|
|
||||||
bool_to_match_result(true)
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
],
|
],
|
||||||
@@ -554,36 +503,36 @@ 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) -> MatchResult| vec![
|
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
|
||||||
(IntegerValue { path: p(&[0, 0]) }, i(2)),
|
(IntegerValue(LhsId(1)), i(2)),
|
||||||
(Nop, Err(Else))
|
(Nop, Err(Else))
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
|
||||||
(IntegerValue { path: p(&[0, 0]) }, i(1)),
|
(IntegerValue(LhsId(1)), i(1)),
|
||||||
(Nop, Err(Else))
|
(Nop, Err(Else))
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(IntegerValue { path: p(&[0, 1]) }, i(2))
|
(IntegerValue(LhsId(2)), i(2))
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Imul.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Imul.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(IntegerValue { path: p(&[0, 1]) }, i(1))
|
(IntegerValue(LhsId(2)), i(1))
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(IntegerValue { path: p(&[0, 0]) }, i(0)),
|
(IntegerValue(LhsId(1)), i(0)),
|
||||||
(Nop, Err(Else))
|
(Nop, Err(Else))
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Iadd.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Iadd.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(IntegerValue { path: p(&[0, 1]) }, i(0))
|
(IntegerValue(LhsId(2)), i(0))
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -597,32 +546,20 @@ mod tests {
|
|||||||
(=> (bor (bor $x $y) $y)
|
(=> (bor (bor $x $y) $y)
|
||||||
(bor $x $y))
|
(bor $x $y))
|
||||||
",
|
",
|
||||||
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
|
||||||
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
|
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
|
(Eq(LhsId(3), LhsId(2)), bool_to_match_result(true)),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(
|
|
||||||
Eq {
|
|
||||||
path_a: p(&[0, 1]),
|
|
||||||
path_b: p(&[0, 0, 0]),
|
|
||||||
},
|
|
||||||
bool_to_match_result(true)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
|
||||||
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
|
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(
|
(Eq(LhsId(4), LhsId(2)), bool_to_match_result(true)),
|
||||||
Eq {
|
|
||||||
path_a: p(&[0, 1]),
|
|
||||||
path_b: p(&[0, 0, 1]),
|
|
||||||
},
|
|
||||||
bool_to_match_result(true)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -636,39 +573,21 @@ mod tests {
|
|||||||
(=> (bor (bor $x $y) $y)
|
(=> (bor (bor $x $y) $y)
|
||||||
(bor $x $y))
|
(bor $x $y))
|
||||||
",
|
",
|
||||||
|p: &mut dyn FnMut(&[u8]) -> PathId, i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
|i: &mut dyn FnMut(u64) -> MatchResult| vec![
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
|
||||||
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
|
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
|
(Eq(LhsId(3), LhsId(2)), bool_to_match_result(true)),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(
|
|
||||||
Eq {
|
|
||||||
path_a: p(&[0, 1]),
|
|
||||||
path_b: p(&[0, 0, 0]),
|
|
||||||
},
|
|
||||||
bool_to_match_result(true)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
(Opcode { path: p(&[0]) }, Ok(TestOperator::Bor.into())),
|
(Opcode(LhsId(0)), Ok(TestOperator::Bor.into())),
|
||||||
(Opcode { path: p(&[0, 0]) }, Ok(TestOperator::Bor.into())),
|
(Opcode(LhsId(1)), Ok(TestOperator::Bor.into())),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
|
(Eq(LhsId(3), LhsId(2)), Err(Else)),
|
||||||
(Nop, Err(Else)),
|
(Nop, Err(Else)),
|
||||||
(
|
(Eq(LhsId(4), LhsId(2)), bool_to_match_result(true)),
|
||||||
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)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -49,14 +49,8 @@
|
|||||||
//!
|
//!
|
||||||
//! 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 match chain as
|
//! * Don't match on a subtree until we know it exists. That is, match on
|
||||||
//! they can be. This means the tail has fewer side effects, and is therefore
|
//! parents before matching on children.
|
||||||
//! more likely to be share-able with other optimizations in the automata that
|
|
||||||
//! we build.
|
|
||||||
//!
|
|
||||||
//! * RHS actions cannot reference matches from the LHS until they've been
|
|
||||||
//! defined. And finally, an RHS operation's operands must be defined before
|
|
||||||
//! the RHS operation itself. In general, definitions must come before uses!
|
|
||||||
//!
|
//!
|
||||||
//! * Shorter match 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
|
||||||
@@ -76,13 +70,15 @@
|
|||||||
//! precondition.
|
//! precondition.
|
||||||
//!
|
//!
|
||||||
//! Within matching the pattern structure, we emit matching operations in a
|
//! Within matching the pattern structure, we emit matching operations in a
|
||||||
//! pre-order traversal of the pattern. This ensures that we've already matched
|
//! breadth-first traversal of the pattern. This ensures that we've already
|
||||||
//! an operation before we consider its operands, and therefore we already know
|
//! matched an operation before we consider its operands, and therefore we
|
||||||
//! the operands exist. See `PatternPreOrder` for details.
|
//! already know the operands exist. It also lets us fuse "what opcode does this
|
||||||
|
//! instruction have?" and "define temporary variables for this instruction's
|
||||||
|
//! operands" into a single operation. See `PatternBfs` for details.
|
||||||
//!
|
//!
|
||||||
//! As we define the match operations for a pattern, we remember the path where
|
//! As we define the match operations for a pattern, we remember the path where
|
||||||
//! each LHS id first occurred. These will later be reused when building the RHS
|
//! each LHS id first occurred. These will later be reused when building the RHS
|
||||||
//! actions. See `LhsIdToPath` for details.
|
//! actions. See `LhsCanonicalizer` for details.
|
||||||
//!
|
//!
|
||||||
//! After we've generated the match operations and expected result of those
|
//! After we've generated the match operations and expected result of those
|
||||||
//! match operations, then we generate the right-hand side actions. The
|
//! match operations, then we generate the right-hand side actions. The
|
||||||
@@ -94,12 +90,8 @@
|
|||||||
//! linear optimization translation function.
|
//! linear optimization translation function.
|
||||||
|
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
use crate::traversals::Dfs;
|
use crate::traversals::{Bfs, Dfs};
|
||||||
use peepmatic_runtime::{
|
use peepmatic_runtime::{integer_interner::IntegerInterner, linear};
|
||||||
integer_interner::IntegerInterner,
|
|
||||||
linear,
|
|
||||||
paths::{Path, PathId, PathInterner},
|
|
||||||
};
|
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
@@ -114,22 +106,19 @@ where
|
|||||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||||
{
|
{
|
||||||
let mut optimizations = vec![];
|
let mut optimizations = vec![];
|
||||||
let mut paths = PathInterner::new();
|
|
||||||
let mut integers = IntegerInterner::new();
|
let mut integers = IntegerInterner::new();
|
||||||
for opt in &opts.optimizations {
|
for opt in &opts.optimizations {
|
||||||
let lin_opt = linearize_optimization(&mut paths, &mut integers, opt);
|
let lin_opt = linearize_optimization(&mut integers, opt);
|
||||||
optimizations.push(lin_opt);
|
optimizations.push(lin_opt);
|
||||||
}
|
}
|
||||||
linear::Optimizations {
|
linear::Optimizations {
|
||||||
optimizations,
|
optimizations,
|
||||||
paths,
|
|
||||||
integers,
|
integers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Translate an AST optimization into a linear optimization!
|
/// Translate an AST optimization into a linear optimization!
|
||||||
fn linearize_optimization<TOperator>(
|
fn linearize_optimization<TOperator>(
|
||||||
paths: &mut PathInterner,
|
|
||||||
integers: &mut IntegerInterner,
|
integers: &mut IntegerInterner,
|
||||||
opt: &Optimization<TOperator>,
|
opt: &Optimization<TOperator>,
|
||||||
) -> linear::Optimization<TOperator>
|
) -> linear::Optimization<TOperator>
|
||||||
@@ -138,22 +127,21 @@ where
|
|||||||
{
|
{
|
||||||
let mut matches: Vec<linear::Match> = vec![];
|
let mut matches: Vec<linear::Match> = vec![];
|
||||||
|
|
||||||
let mut lhs_id_to_path = LhsIdToPath::new();
|
let mut lhs_canonicalizer = LhsCanonicalizer::new();
|
||||||
|
|
||||||
// We do a pre-order traversal of the LHS because we don't know whether a
|
// We do a breadth-first traversal of the LHS because we don't know whether
|
||||||
// child actually exists to match on until we've matched its parent, and we
|
// a child actually exists to match on until we've matched its parent, and
|
||||||
// don't want to emit matching operations on things that might not exist!
|
// we don't want to emit matching operations on things that might not exist!
|
||||||
let mut patterns = PatternPreOrder::new(&opt.lhs.pattern);
|
for (id, pattern) in PatternBfs::new(&opt.lhs.pattern) {
|
||||||
while let Some((path, pattern)) = patterns.next(paths) {
|
|
||||||
// Create the matching parts of an `Match` for this part of the
|
// Create the matching parts of an `Match` for this part of the
|
||||||
// pattern.
|
// 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_canonicalizer, id);
|
||||||
matches.push(linear::Match {
|
matches.push(linear::Match {
|
||||||
operation,
|
operation,
|
||||||
expected,
|
expected,
|
||||||
});
|
});
|
||||||
|
|
||||||
lhs_id_to_path.remember_path_to_pattern_ids(pattern, path);
|
lhs_canonicalizer.remember_linear_id(pattern, id);
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -165,7 +153,7 @@ where
|
|||||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) });
|
let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) });
|
||||||
|
|
||||||
matches.push(linear::Match {
|
matches.push(linear::Match {
|
||||||
operation: linear::MatchOp::BitWidth { path },
|
operation: linear::MatchOp::BitWidth(id),
|
||||||
expected,
|
expected,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -175,7 +163,7 @@ where
|
|||||||
// Now that we've added all the matches for the LHS pattern, add the
|
// Now that we've added all the matches for the LHS pattern, add the
|
||||||
// matches for its preconditions.
|
// matches for its preconditions.
|
||||||
for pre in &opt.lhs.preconditions {
|
for pre in &opt.lhs.preconditions {
|
||||||
matches.push(pre.to_linear_match(&lhs_id_to_path));
|
matches.push(pre.to_linear_match(&lhs_canonicalizer));
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(!matches.is_empty());
|
assert!(!matches.is_empty());
|
||||||
@@ -183,7 +171,7 @@ where
|
|||||||
// Finally, generate the RHS-building actions and attach them to the first match.
|
// 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);
|
||||||
let mut actions = vec![];
|
let mut actions = vec![];
|
||||||
rhs_builder.add_rhs_build_actions(integers, &lhs_id_to_path, &mut actions);
|
rhs_builder.add_rhs_build_actions(integers, &lhs_canonicalizer, &mut actions);
|
||||||
|
|
||||||
linear::Optimization { matches, actions }
|
linear::Optimization { matches, actions }
|
||||||
}
|
}
|
||||||
@@ -222,55 +210,46 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A pre-order, depth-first traversal of left-hand side patterns.
|
/// A breadth-first traversal of left-hand side patterns.
|
||||||
///
|
///
|
||||||
/// Keeps track of the path to each pattern, and yields it along side the
|
/// Keeps track of the `LhsId` of each pattern, and yields it along side the
|
||||||
/// pattern AST node.
|
/// pattern AST node.
|
||||||
struct PatternPreOrder<'a, TOperator> {
|
///
|
||||||
last_child: Option<u8>,
|
/// We use a breadth-first traversal because we fuse "which opcode is this?" and
|
||||||
path: Vec<u8>,
|
/// "assign operands to temporaries" into a single linear match operation. A
|
||||||
dfs: Dfs<'a, TOperator>,
|
/// breadth-first traversal aligns with "match this opcode, and on success bind
|
||||||
|
/// all of its operands to temporaries". Fusing these operations into one is
|
||||||
|
/// important for attaining similar performance as an open-coded Rust `match`
|
||||||
|
/// expression, which would also fuse these operations via pattern matching.
|
||||||
|
struct PatternBfs<'a, TOperator> {
|
||||||
|
next_id: u16,
|
||||||
|
bfs: Bfs<'a, TOperator>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, TOperator> PatternPreOrder<'a, TOperator>
|
impl<'a, TOperator> PatternBfs<'a, TOperator>
|
||||||
where
|
where
|
||||||
TOperator: Copy + Debug + Eq + Hash,
|
TOperator: Copy + Debug + Eq + Hash,
|
||||||
{
|
{
|
||||||
fn new(pattern: &'a Pattern<'a, TOperator>) -> Self {
|
fn new(pattern: &'a Pattern<'a, TOperator>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
last_child: None,
|
next_id: 0,
|
||||||
path: vec![],
|
bfs: Bfs::new(pattern),
|
||||||
dfs: Dfs::new(pattern),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn next(&mut self, paths: &mut PathInterner) -> Option<(PathId, &'a Pattern<'a, TOperator>)> {
|
impl<'a, TOperator> Iterator for PatternBfs<'a, TOperator>
|
||||||
use crate::traversals::TraversalEvent as TE;
|
where
|
||||||
|
TOperator: 'a + Copy + Debug + Eq + Hash,
|
||||||
|
{
|
||||||
|
type Item = (linear::LhsId, &'a Pattern<'a, TOperator>);
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
loop {
|
loop {
|
||||||
match self.dfs.next()? {
|
if let DynAstRef::Pattern(pattern) = self.bfs.next()? {
|
||||||
(TE::Enter, DynAstRef::Pattern(pattern)) => {
|
let id = linear::LhsId(self.next_id);
|
||||||
let last_child = self.last_child.take();
|
self.next_id = self.next_id.checked_add(1).unwrap();
|
||||||
self.path.push(match last_child {
|
return Some((id, pattern));
|
||||||
None => 0,
|
|
||||||
Some(c) => {
|
|
||||||
assert!(
|
|
||||||
c < std::u8::MAX,
|
|
||||||
"operators must have less than or equal u8::MAX arity"
|
|
||||||
);
|
|
||||||
c + 1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let path = paths.intern(Path(&self.path));
|
|
||||||
return Some((path, pattern));
|
|
||||||
}
|
|
||||||
(TE::Exit, DynAstRef::Pattern(_)) => {
|
|
||||||
self.last_child = Some(
|
|
||||||
self.path
|
|
||||||
.pop()
|
|
||||||
.expect("should always have a non-empty path during traversal"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -278,42 +257,37 @@ where
|
|||||||
|
|
||||||
/// A map from left-hand side identifiers to the path in the left-hand side
|
/// A map from left-hand side identifiers to the path in the left-hand side
|
||||||
/// where they first occurred.
|
/// where they first occurred.
|
||||||
struct LhsIdToPath<'a, TOperator> {
|
struct LhsCanonicalizer<'a, TOperator> {
|
||||||
id_to_path: BTreeMap<&'a str, PathId>,
|
id_to_linear: BTreeMap<&'a str, linear::LhsId>,
|
||||||
_marker: PhantomData<&'a TOperator>,
|
_marker: PhantomData<&'a TOperator>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, TOperator> LhsIdToPath<'a, TOperator> {
|
impl<'a, TOperator> LhsCanonicalizer<'a, TOperator> {
|
||||||
/// Construct a new, empty `LhsIdToPath`.
|
/// Construct a new, empty `LhsCanonicalizer`.
|
||||||
fn new() -> Self {
|
fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
id_to_path: Default::default(),
|
id_to_linear: Default::default(),
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Have we already seen the given identifier?
|
/// Get the canonical `linear::LhsId` for the given variable, if any.
|
||||||
fn get_first_occurrence(&self, id: &Id) -> Option<PathId> {
|
fn get(&self, id: &Id) -> Option<linear::LhsId> {
|
||||||
self.id_to_path.get(id.name()).copied()
|
self.id_to_linear.get(id.name()).copied()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the path within the left-hand side pattern where we first saw the
|
/// Remember the canonical `linear::LhsId` for any variables or constants
|
||||||
/// given AST id.
|
/// used in the given pattern.
|
||||||
///
|
fn remember_linear_id(
|
||||||
/// ## Panics
|
&mut self,
|
||||||
///
|
pattern: &'a Pattern<'a, TOperator>,
|
||||||
/// Panics if the given AST id has not already been canonicalized.
|
linear_id: linear::LhsId,
|
||||||
fn unwrap_first_occurrence(&self, id: &Id) -> PathId {
|
) {
|
||||||
self.id_to_path[id.name()]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Remember the path to any LHS ids used in the given pattern.
|
|
||||||
fn remember_path_to_pattern_ids(&mut self, pattern: &'a Pattern<'a, TOperator>, path: PathId) {
|
|
||||||
match pattern {
|
match pattern {
|
||||||
// If this is the first time we've seen an identifier defined on the
|
// If this is the first time we've seen an identifier defined on the
|
||||||
// left-hand side, remember it.
|
// left-hand side, remember it.
|
||||||
Pattern::Variable(Variable { id, .. }) | Pattern::Constant(Constant { id, .. }) => {
|
Pattern::Variable(Variable { id, .. }) | Pattern::Constant(Constant { id, .. }) => {
|
||||||
self.id_to_path.entry(id.name()).or_insert(path);
|
self.id_to_linear.entry(id.name()).or_insert(linear_id);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -366,11 +340,11 @@ where
|
|||||||
fn add_rhs_build_actions(
|
fn add_rhs_build_actions(
|
||||||
&mut self,
|
&mut self,
|
||||||
integers: &mut IntegerInterner,
|
integers: &mut IntegerInterner,
|
||||||
lhs_id_to_path: &LhsIdToPath<TOperator>,
|
lhs_canonicalizer: &LhsCanonicalizer<TOperator>,
|
||||||
actions: &mut Vec<linear::Action<TOperator>>,
|
actions: &mut Vec<linear::Action<TOperator>>,
|
||||||
) {
|
) {
|
||||||
while let Some(rhs) = self.rhs_post_order.next() {
|
while let Some(rhs) = self.rhs_post_order.next() {
|
||||||
actions.push(self.rhs_to_linear_action(integers, lhs_id_to_path, rhs));
|
actions.push(self.rhs_to_linear_action(integers, lhs_canonicalizer, rhs));
|
||||||
let id = linear::RhsId(self.rhs_span_to_id.len().try_into().unwrap());
|
let id = linear::RhsId(self.rhs_span_to_id.len().try_into().unwrap());
|
||||||
self.rhs_span_to_id.insert(rhs.span(), id);
|
self.rhs_span_to_id.insert(rhs.span(), id);
|
||||||
}
|
}
|
||||||
@@ -379,7 +353,7 @@ where
|
|||||||
fn rhs_to_linear_action(
|
fn rhs_to_linear_action(
|
||||||
&self,
|
&self,
|
||||||
integers: &mut IntegerInterner,
|
integers: &mut IntegerInterner,
|
||||||
lhs_id_to_path: &LhsIdToPath<TOperator>,
|
lhs_canonicalizer: &LhsCanonicalizer<TOperator>,
|
||||||
rhs: &Rhs<TOperator>,
|
rhs: &Rhs<TOperator>,
|
||||||
) -> linear::Action<TOperator> {
|
) -> linear::Action<TOperator> {
|
||||||
match rhs {
|
match rhs {
|
||||||
@@ -401,8 +375,8 @@ where
|
|||||||
linear::Action::MakeConditionCode { cc: *cc }
|
linear::Action::MakeConditionCode { cc: *cc }
|
||||||
}
|
}
|
||||||
Rhs::Variable(Variable { id, .. }) | Rhs::Constant(Constant { id, .. }) => {
|
Rhs::Variable(Variable { id, .. }) | Rhs::Constant(Constant { id, .. }) => {
|
||||||
let path = lhs_id_to_path.unwrap_first_occurrence(id);
|
let lhs = lhs_canonicalizer.get(id).unwrap();
|
||||||
linear::Action::GetLhs { path }
|
linear::Action::GetLhs { lhs }
|
||||||
}
|
}
|
||||||
Rhs::Unquote(unq) => match unq.operands.len() {
|
Rhs::Unquote(unq) => match unq.operands.len() {
|
||||||
1 => linear::Action::UnaryUnquote {
|
1 => linear::Action::UnaryUnquote {
|
||||||
@@ -461,16 +435,16 @@ where
|
|||||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||||
{
|
{
|
||||||
/// Convert this precondition into a `linear::Match`.
|
/// Convert this precondition into a `linear::Match`.
|
||||||
fn to_linear_match(&self, lhs_id_to_path: &LhsIdToPath<TOperator>) -> linear::Match {
|
fn to_linear_match(&self, lhs_canonicalizer: &LhsCanonicalizer<TOperator>) -> linear::Match {
|
||||||
match self.constraint {
|
match self.constraint {
|
||||||
Constraint::IsPowerOfTwo => {
|
Constraint::IsPowerOfTwo => {
|
||||||
let id = match &self.operands[0] {
|
let id = match &self.operands[0] {
|
||||||
ConstraintOperand::Constant(Constant { id, .. }) => id,
|
ConstraintOperand::Constant(Constant { id, .. }) => id,
|
||||||
_ => unreachable!("checked in verification"),
|
_ => unreachable!("checked in verification"),
|
||||||
};
|
};
|
||||||
let path = lhs_id_to_path.unwrap_first_occurrence(&id);
|
let id = lhs_canonicalizer.get(&id).unwrap();
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: linear::MatchOp::IsPowerOfTwo { path },
|
operation: linear::MatchOp::IsPowerOfTwo(id),
|
||||||
expected: linear::bool_to_match_result(true),
|
expected: linear::bool_to_match_result(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -480,7 +454,7 @@ where
|
|||||||
| ConstraintOperand::Variable(Variable { id, .. }) => id,
|
| ConstraintOperand::Variable(Variable { id, .. }) => id,
|
||||||
_ => unreachable!("checked in verification"),
|
_ => unreachable!("checked in verification"),
|
||||||
};
|
};
|
||||||
let path = lhs_id_to_path.unwrap_first_occurrence(&id);
|
let id = lhs_canonicalizer.get(&id).unwrap();
|
||||||
|
|
||||||
let width = match &self.operands[1] {
|
let width = match &self.operands[1] {
|
||||||
ConstraintOperand::ValueLiteral(ValueLiteral::Integer(Integer {
|
ConstraintOperand::ValueLiteral(ValueLiteral::Integer(Integer {
|
||||||
@@ -495,7 +469,7 @@ where
|
|||||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) });
|
let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) });
|
||||||
|
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: linear::MatchOp::BitWidth { path },
|
operation: linear::MatchOp::BitWidth(id),
|
||||||
expected,
|
expected,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -505,9 +479,9 @@ where
|
|||||||
| ConstraintOperand::Variable(Variable { id, .. }) => id,
|
| ConstraintOperand::Variable(Variable { id, .. }) => id,
|
||||||
_ => unreachable!("checked in verification"),
|
_ => unreachable!("checked in verification"),
|
||||||
};
|
};
|
||||||
let path = lhs_id_to_path.unwrap_first_occurrence(&id);
|
let id = lhs_canonicalizer.get(&id).unwrap();
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: linear::MatchOp::FitsInNativeWord { path },
|
operation: linear::MatchOp::FitsInNativeWord(id),
|
||||||
expected: linear::bool_to_match_result(true),
|
expected: linear::bool_to_match_result(true),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -527,52 +501,46 @@ where
|
|||||||
fn to_linear_match_op(
|
fn to_linear_match_op(
|
||||||
&self,
|
&self,
|
||||||
integers: &mut IntegerInterner,
|
integers: &mut IntegerInterner,
|
||||||
lhs_id_to_path: &LhsIdToPath<TOperator>,
|
lhs_canonicalizer: &LhsCanonicalizer<TOperator>,
|
||||||
path: PathId,
|
linear_lhs_id: linear::LhsId,
|
||||||
) -> (linear::MatchOp, linear::MatchResult)
|
) -> (linear::MatchOp, linear::MatchResult)
|
||||||
where
|
where
|
||||||
TOperator: Into<NonZeroU32>,
|
TOperator: Into<NonZeroU32>,
|
||||||
{
|
{
|
||||||
match self {
|
match self {
|
||||||
Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => (
|
Pattern::ValueLiteral(ValueLiteral::Integer(Integer { value, .. })) => (
|
||||||
linear::MatchOp::IntegerValue { path },
|
linear::MatchOp::IntegerValue(linear_lhs_id),
|
||||||
Ok(integers.intern(*value as u64).into()),
|
Ok(integers.intern(*value as u64).into()),
|
||||||
),
|
),
|
||||||
Pattern::ValueLiteral(ValueLiteral::Boolean(Boolean { value, .. })) => (
|
Pattern::ValueLiteral(ValueLiteral::Boolean(Boolean { value, .. })) => (
|
||||||
linear::MatchOp::BooleanValue { path },
|
linear::MatchOp::BooleanValue(linear_lhs_id),
|
||||||
linear::bool_to_match_result(*value),
|
linear::bool_to_match_result(*value),
|
||||||
),
|
),
|
||||||
Pattern::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => {
|
Pattern::ValueLiteral(ValueLiteral::ConditionCode(ConditionCode { cc, .. })) => {
|
||||||
let cc = *cc as u32;
|
let cc = *cc as u32;
|
||||||
debug_assert!(cc != 0, "no `ConditionCode` variants are zero");
|
debug_assert!(cc != 0, "no `ConditionCode` variants are zero");
|
||||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(cc) });
|
let expected = Ok(unsafe { NonZeroU32::new_unchecked(cc) });
|
||||||
(linear::MatchOp::ConditionCode { path }, expected)
|
(linear::MatchOp::ConditionCode(linear_lhs_id), 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(linear_lhs_id2) = lhs_canonicalizer.get(id) {
|
||||||
debug_assert!(path != path_b);
|
debug_assert!(linear_lhs_id != linear_lhs_id2);
|
||||||
(
|
(
|
||||||
linear::MatchOp::Eq {
|
linear::MatchOp::Eq(linear_lhs_id, linear_lhs_id2),
|
||||||
path_a: path,
|
|
||||||
path_b,
|
|
||||||
},
|
|
||||||
linear::bool_to_match_result(true),
|
linear::bool_to_match_result(true),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
(
|
(
|
||||||
linear::MatchOp::IsConst { path },
|
linear::MatchOp::IsConst(linear_lhs_id),
|
||||||
linear::bool_to_match_result(true),
|
linear::bool_to_match_result(true),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Pattern::Variable(Variable { id, .. }) => {
|
Pattern::Variable(Variable { id, .. }) => {
|
||||||
if let Some(path_b) = lhs_id_to_path.get_first_occurrence(id) {
|
if let Some(linear_lhs_id2) = lhs_canonicalizer.get(id) {
|
||||||
debug_assert!(path != path_b);
|
debug_assert!(linear_lhs_id != linear_lhs_id2);
|
||||||
(
|
(
|
||||||
linear::MatchOp::Eq {
|
linear::MatchOp::Eq(linear_lhs_id, linear_lhs_id2),
|
||||||
path_a: path,
|
|
||||||
path_b,
|
|
||||||
},
|
|
||||||
linear::bool_to_match_result(true),
|
linear::bool_to_match_result(true),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -581,7 +549,7 @@ where
|
|||||||
}
|
}
|
||||||
Pattern::Operation(op) => {
|
Pattern::Operation(op) => {
|
||||||
let expected = Ok(op.operator.into());
|
let expected = Ok(op.operator.into());
|
||||||
(linear::MatchOp::Opcode { path }, expected)
|
(linear::MatchOp::Opcode(linear_lhs_id), expected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -592,7 +560,7 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use peepmatic_runtime::{
|
use peepmatic_runtime::{
|
||||||
integer_interner::IntegerId,
|
integer_interner::IntegerId,
|
||||||
linear::{bool_to_match_result, Action::*, Else, MatchOp::*},
|
linear::{bool_to_match_result, Action::*, Else, LhsId, MatchOp::*, RhsId},
|
||||||
r#type::{BitWidth, Kind, Type},
|
r#type::{BitWidth, Kind, Type},
|
||||||
unquote::UnquoteOperator,
|
unquote::UnquoteOperator,
|
||||||
};
|
};
|
||||||
@@ -628,20 +596,16 @@ mod tests {
|
|||||||
panic!("should verify OK")
|
panic!("should verify OK")
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut paths = PathInterner::new();
|
|
||||||
let mut p = |p: &[u8]| paths.intern(Path::new(&p));
|
|
||||||
|
|
||||||
let mut integers = IntegerInterner::new();
|
let mut integers = IntegerInterner::new();
|
||||||
let mut i = |i: u64| integers.intern(i);
|
let mut i = |i: u64| integers.intern(i);
|
||||||
|
|
||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
let make_expected: fn(
|
let make_expected: fn(
|
||||||
&mut dyn FnMut(&[u8]) -> PathId,
|
|
||||||
&mut dyn FnMut(u64) -> IntegerId,
|
&mut dyn FnMut(u64) -> IntegerId,
|
||||||
) -> (Vec<linear::Match>, Vec<linear::Action<_>>) = $make_expected;
|
) -> (Vec<linear::Match>, Vec<linear::Action<_>>) = $make_expected;
|
||||||
|
|
||||||
let expected = make_expected(&mut p, &mut i);
|
let expected = make_expected(&mut i);
|
||||||
let actual = linearize_optimization(&mut paths, &mut integers, &opts.optimizations[0]);
|
let actual = linearize_optimization(&mut integers, &opts.optimizations[0]);
|
||||||
assert_eq!(expected.0, actual.matches);
|
assert_eq!(expected.0, actual.matches);
|
||||||
assert_eq!(expected.1, actual.actions);
|
assert_eq!(expected.1, actual.actions);
|
||||||
}
|
}
|
||||||
@@ -655,10 +619,10 @@ mod tests {
|
|||||||
(is-power-of-two $C))
|
(is-power-of-two $C))
|
||||||
(ishl $x $(log2 $C)))
|
(ishl $x $(log2 $C)))
|
||||||
",
|
",
|
||||||
|p, i| (
|
|i| (
|
||||||
vec![
|
vec![
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: Opcode { path: p(&[0]) },
|
operation: Opcode(LhsId(0)),
|
||||||
expected: Ok(TestOperator::Imul.into()),
|
expected: Ok(TestOperator::Imul.into()),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
@@ -666,20 +630,20 @@ mod tests {
|
|||||||
expected: Err(Else),
|
expected: Err(Else),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: IsConst { path: p(&[0, 1]) },
|
operation: IsConst(LhsId(2)),
|
||||||
expected: bool_to_match_result(true),
|
expected: bool_to_match_result(true),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: IsPowerOfTwo { path: p(&[0, 1]) },
|
operation: IsPowerOfTwo(LhsId(2)),
|
||||||
expected: bool_to_match_result(true),
|
expected: bool_to_match_result(true),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
GetLhs { path: p(&[0, 0]) },
|
GetLhs { lhs: LhsId(1) },
|
||||||
GetLhs { path: p(&[0, 1]) },
|
GetLhs { lhs: LhsId(2) },
|
||||||
UnaryUnquote {
|
UnaryUnquote {
|
||||||
operator: UnquoteOperator::Log2,
|
operator: UnquoteOperator::Log2,
|
||||||
operand: linear::RhsId(1)
|
operand: RhsId(1)
|
||||||
},
|
},
|
||||||
MakeBinaryInst {
|
MakeBinaryInst {
|
||||||
operator: TestOperator::Ishl,
|
operator: TestOperator::Ishl,
|
||||||
@@ -687,31 +651,31 @@ mod tests {
|
|||||||
kind: Kind::Int,
|
kind: Kind::Int,
|
||||||
bit_width: BitWidth::Polymorphic
|
bit_width: BitWidth::Polymorphic
|
||||||
},
|
},
|
||||||
operands: [linear::RhsId(0), linear::RhsId(2)]
|
operands: [RhsId(0), RhsId(2)]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
linearizes_to!(variable_pattern_id_optimization, "(=> $x $x)", |p, i| (
|
linearizes_to!(variable_pattern_id_optimization, "(=> $x $x)", |i| (
|
||||||
vec![linear::Match {
|
vec![linear::Match {
|
||||||
operation: Nop,
|
operation: Nop,
|
||||||
expected: Err(Else),
|
expected: Err(Else),
|
||||||
}],
|
}],
|
||||||
vec![GetLhs { path: p(&[0]) }],
|
vec![GetLhs { lhs: LhsId(0) }],
|
||||||
));
|
));
|
||||||
|
|
||||||
linearizes_to!(constant_pattern_id_optimization, "(=> $C $C)", |p, i| (
|
linearizes_to!(constant_pattern_id_optimization, "(=> $C $C)", |i| (
|
||||||
vec![linear::Match {
|
vec![linear::Match {
|
||||||
operation: IsConst { path: p(&[0]) },
|
operation: IsConst(LhsId(0)),
|
||||||
expected: bool_to_match_result(true),
|
expected: bool_to_match_result(true),
|
||||||
}],
|
}],
|
||||||
vec![GetLhs { path: p(&[0]) }],
|
vec![GetLhs { lhs: LhsId(0) }],
|
||||||
));
|
));
|
||||||
|
|
||||||
linearizes_to!(boolean_literal_id_optimization, "(=> true true)", |p, i| (
|
linearizes_to!(boolean_literal_id_optimization, "(=> true true)", |i| (
|
||||||
vec![linear::Match {
|
vec![linear::Match {
|
||||||
operation: BooleanValue { path: p(&[0]) },
|
operation: BooleanValue(LhsId(0)),
|
||||||
expected: bool_to_match_result(true),
|
expected: bool_to_match_result(true),
|
||||||
}],
|
}],
|
||||||
vec![MakeBooleanConst {
|
vec![MakeBooleanConst {
|
||||||
@@ -720,9 +684,9 @@ mod tests {
|
|||||||
}],
|
}],
|
||||||
));
|
));
|
||||||
|
|
||||||
linearizes_to!(number_literal_id_optimization, "(=> 5 5)", |p, i| (
|
linearizes_to!(number_literal_id_optimization, "(=> 5 5)", |i| (
|
||||||
vec![linear::Match {
|
vec![linear::Match {
|
||||||
operation: IntegerValue { path: p(&[0]) },
|
operation: IntegerValue(LhsId(0)),
|
||||||
expected: Ok(i(5).into()),
|
expected: Ok(i(5).into()),
|
||||||
}],
|
}],
|
||||||
vec![MakeIntegerConst {
|
vec![MakeIntegerConst {
|
||||||
@@ -734,26 +698,26 @@ mod tests {
|
|||||||
linearizes_to!(
|
linearizes_to!(
|
||||||
operation_id_optimization,
|
operation_id_optimization,
|
||||||
"(=> (iconst $C) (iconst $C))",
|
"(=> (iconst $C) (iconst $C))",
|
||||||
|p, i| (
|
|i| (
|
||||||
vec![
|
vec![
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: Opcode { path: p(&[0]) },
|
operation: Opcode(LhsId(0)),
|
||||||
expected: Ok(TestOperator::Iconst.into()),
|
expected: Ok(TestOperator::Iconst.into()),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: IsConst { path: p(&[0, 0]) },
|
operation: IsConst(LhsId(1)),
|
||||||
expected: bool_to_match_result(true),
|
expected: bool_to_match_result(true),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
GetLhs { path: p(&[0, 0]) },
|
GetLhs { lhs: LhsId(1) },
|
||||||
MakeUnaryInst {
|
MakeUnaryInst {
|
||||||
operator: TestOperator::Iconst,
|
operator: TestOperator::Iconst,
|
||||||
r#type: Type {
|
r#type: Type {
|
||||||
kind: Kind::Int,
|
kind: Kind::Int,
|
||||||
bit_width: BitWidth::Polymorphic,
|
bit_width: BitWidth::Polymorphic,
|
||||||
},
|
},
|
||||||
operand: linear::RhsId(0),
|
operand: RhsId(0),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -762,10 +726,10 @@ mod tests {
|
|||||||
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| (
|
|i| (
|
||||||
vec![
|
vec![
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: Opcode { path: p(&[0]) },
|
operation: Opcode(LhsId(0)),
|
||||||
expected: Ok(TestOperator::Bor.into()),
|
expected: Ok(TestOperator::Bor.into()),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
@@ -773,14 +737,11 @@ mod tests {
|
|||||||
expected: Err(Else),
|
expected: Err(Else),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: Opcode { path: p(&[0, 1]) },
|
operation: Opcode(LhsId(2)),
|
||||||
expected: Ok(TestOperator::Bor.into()),
|
expected: Ok(TestOperator::Bor.into()),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: Eq {
|
operation: Eq(LhsId(3), LhsId(1)),
|
||||||
path_a: p(&[0, 1, 0]),
|
|
||||||
path_b: p(&[0, 0]),
|
|
||||||
},
|
|
||||||
expected: bool_to_match_result(true),
|
expected: bool_to_match_result(true),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
@@ -789,17 +750,15 @@ mod tests {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
vec![
|
vec![
|
||||||
GetLhs { path: p(&[0, 0]) },
|
GetLhs { lhs: LhsId(1) },
|
||||||
GetLhs {
|
GetLhs { lhs: LhsId(4) },
|
||||||
path: p(&[0, 1, 1]),
|
|
||||||
},
|
|
||||||
MakeBinaryInst {
|
MakeBinaryInst {
|
||||||
operator: TestOperator::Bor,
|
operator: TestOperator::Bor,
|
||||||
r#type: Type {
|
r#type: Type {
|
||||||
kind: Kind::Int,
|
kind: Kind::Int,
|
||||||
bit_width: BitWidth::Polymorphic,
|
bit_width: BitWidth::Polymorphic,
|
||||||
},
|
},
|
||||||
operands: [linear::RhsId(0), linear::RhsId(1)],
|
operands: [RhsId(0), RhsId(1)],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
@@ -809,9 +768,9 @@ mod tests {
|
|||||||
large_integers,
|
large_integers,
|
||||||
// u64::MAX
|
// u64::MAX
|
||||||
"(=> 18446744073709551615 0)",
|
"(=> 18446744073709551615 0)",
|
||||||
|p, i| (
|
|i| (
|
||||||
vec![linear::Match {
|
vec![linear::Match {
|
||||||
operation: IntegerValue { path: p(&[0]) },
|
operation: IntegerValue(LhsId(0)),
|
||||||
expected: Ok(i(std::u64::MAX).into()),
|
expected: Ok(i(std::u64::MAX).into()),
|
||||||
}],
|
}],
|
||||||
vec![MakeIntegerConst {
|
vec![MakeIntegerConst {
|
||||||
@@ -824,14 +783,14 @@ mod tests {
|
|||||||
linearizes_to!(
|
linearizes_to!(
|
||||||
ireduce_with_type_ascription,
|
ireduce_with_type_ascription,
|
||||||
"(=> (ireduce{i32} $x) 0)",
|
"(=> (ireduce{i32} $x) 0)",
|
||||||
|p, i| (
|
|i| (
|
||||||
vec![
|
vec![
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: Opcode { path: p(&[0]) },
|
operation: Opcode(LhsId(0)),
|
||||||
expected: Ok(TestOperator::Ireduce.into()),
|
expected: Ok(TestOperator::Ireduce.into()),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
operation: linear::MatchOp::BitWidth { path: p(&[0]) },
|
operation: linear::MatchOp::BitWidth(LhsId(0)),
|
||||||
expected: Ok(NonZeroU32::new(32).unwrap()),
|
expected: Ok(NonZeroU32::new(32).unwrap()),
|
||||||
},
|
},
|
||||||
linear::Match {
|
linear::Match {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
//! Traversals over the AST.
|
//! Traversals over the AST.
|
||||||
|
|
||||||
use crate::ast::*;
|
use crate::ast::*;
|
||||||
|
use std::collections::VecDeque;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
|
|
||||||
@@ -103,6 +104,44 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A breadth-first traversal of an AST
|
||||||
|
///
|
||||||
|
/// This implementation is not recursive, and exposes an `Iterator` interface
|
||||||
|
/// that yields `DynAstRef` items.
|
||||||
|
///
|
||||||
|
/// The traversal can walk a whole set of `Optimization`s or just a subtree of
|
||||||
|
/// the AST, because the `new` constructor takes anything that can convert into
|
||||||
|
/// a `DynAstRef`.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Bfs<'a, TOperator> {
|
||||||
|
queue: VecDeque<DynAstRef<'a, TOperator>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, TOperator> Bfs<'a, TOperator>
|
||||||
|
where
|
||||||
|
TOperator: Copy + Debug + Eq + Hash,
|
||||||
|
{
|
||||||
|
/// Construct a new `Bfs` traversal starting at the given `start` AST node.
|
||||||
|
pub fn new(start: impl Into<DynAstRef<'a, TOperator>>) -> Self {
|
||||||
|
let mut queue = VecDeque::with_capacity(16);
|
||||||
|
queue.push_back(start.into());
|
||||||
|
Bfs { queue }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, TOperator> Iterator for Bfs<'a, TOperator>
|
||||||
|
where
|
||||||
|
TOperator: Copy + Debug + Eq + Hash,
|
||||||
|
{
|
||||||
|
type Item = DynAstRef<'a, TOperator>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
let node = self.queue.pop_front()?;
|
||||||
|
node.child_nodes(&mut self.queue);
|
||||||
|
Some(node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
Reference in New Issue
Block a user