Merge pull request #2257 from fitzgen/peepmatic-no-paths-in-linear-ir
Peepmatic: Do not use paths in linear IR
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 mut part = Part::Instruction(root);
|
|
||||||
for p in path.0[1..].iter().copied() {
|
|
||||||
let inst = part.as_instruction()?.resolve_inst(&pos.func.dfg)?;
|
|
||||||
let operator = pos.func.dfg[inst].opcode();
|
|
||||||
|
|
||||||
if p < operator.immediates_arity() {
|
|
||||||
part = get_immediate(&pos.func.dfg, inst, p as usize);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let arg = p - operator.immediates_arity();
|
|
||||||
let arg = arg as usize;
|
|
||||||
let value = get_argument(&pos.func.dfg, inst, arg)?;
|
|
||||||
part = Part::Instruction(value.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
log::trace!("get_part_at_path({:?}) = {:?}", path, part);
|
|
||||||
Some(part)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn operator(&self, pos: &mut FuncCursor<'b>, value_or_inst: ValueOrInst) -> Option<Opcode> {
|
|
||||||
let inst = value_or_inst.resolve_inst(&pos.func.dfg)?;
|
let inst = value_or_inst.resolve_inst(&pos.func.dfg)?;
|
||||||
Some(pos.func.dfg[inst].opcode())
|
Some(match pos.func.dfg[inst] {
|
||||||
|
InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Band,
|
||||||
|
args,
|
||||||
|
}
|
||||||
|
| InstructionData::Binary {
|
||||||
|
opcode: opcode @ Opcode::Bor,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::BinaryImm64 {
|
||||||
|
opcode: opcode @ Opcode::BandImm,
|
||||||
|
imm,
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::Branch {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
InstructionData::CondTrap {
|
||||||
|
opcode: opcode @ Opcode::Trapnz,
|
||||||
|
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.
@@ -54,11 +54,6 @@ superoptimizer like [Souper][]. Eventually, `peepmatic` should have a verifier
|
|||||||
that ensures that the DSL's optimizations are sound, similar to what [Alive][]
|
that ensures that the DSL's optimizations are sound, similar to what [Alive][]
|
||||||
does for LLVM optimizations.
|
does for LLVM optimizations.
|
||||||
|
|
||||||
Currently, `peepmatic` is targeting peephole optimizers that operate on
|
|
||||||
Cranelift's clif intermediate representation. The intended next target is
|
|
||||||
Cranelift's new backend's "vcode" intermediate representation. Supporting
|
|
||||||
non-Cranelift targets is not a goal.
|
|
||||||
|
|
||||||
[Cranelift]: https://github.com/bytecodealliance/wasmtime/tree/main/cranelift#readme
|
[Cranelift]: https://github.com/bytecodealliance/wasmtime/tree/main/cranelift#readme
|
||||||
[Souper]: https://github.com/google/souper
|
[Souper]: https://github.com/google/souper
|
||||||
[Alive]: https://github.com/AliveToolkit/alive2
|
[Alive]: https://github.com/AliveToolkit/alive2
|
||||||
|
|||||||
@@ -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,
|
||||||
}
|
}
|
||||||
@@ -37,8 +33,13 @@ pub struct Optimization<TOperator>
|
|||||||
where
|
where
|
||||||
TOperator: 'static + Copy + Debug + Eq + Hash,
|
TOperator: 'static + Copy + Debug + Eq + Hash,
|
||||||
{
|
{
|
||||||
/// The chain of increments for this optimization.
|
/// The chain of match operations and expected results for this
|
||||||
pub increments: Vec<Increment<TOperator>>,
|
/// optimization.
|
||||||
|
pub matches: Vec<Match>,
|
||||||
|
|
||||||
|
/// Actions to perform, given that the operation resulted in the expected
|
||||||
|
/// value.
|
||||||
|
pub actions: Vec<Action<TOperator>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Match any value.
|
/// Match any value.
|
||||||
@@ -61,31 +62,20 @@ pub fn bool_to_match_result(b: bool) -> MatchResult {
|
|||||||
unsafe { Ok(NonZeroU32::new_unchecked(b + 1)) }
|
unsafe { Ok(NonZeroU32::new_unchecked(b + 1)) }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A partial match of an optimization's LHS and partial construction of its
|
/// A partial match of an optimization's LHS.
|
||||||
/// RHS.
|
|
||||||
///
|
///
|
||||||
/// An increment is a matching operation, the expected result from that
|
/// An match is composed of a matching operation and the expected result of that
|
||||||
/// operation to continue to the next increment, and the actions to take to
|
/// operation. Each match will basically become a state and a transition edge
|
||||||
/// build up the LHS scope and RHS instructions given that we got the expected
|
/// out of that state in the final automata.
|
||||||
/// result from this increment's matching operation. Each increment will
|
|
||||||
/// basically become a state and a transition edge out of that state in the
|
|
||||||
/// final automata.
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Increment<TOperator>
|
pub struct Match {
|
||||||
where
|
|
||||||
TOperator: 'static + Copy + Debug + Eq + Hash,
|
|
||||||
{
|
|
||||||
/// The matching operation to perform.
|
/// The matching operation to perform.
|
||||||
pub operation: MatchOp,
|
pub operation: MatchOp,
|
||||||
|
|
||||||
/// The expected result of our matching operation, that enables us to
|
/// The expected result of our matching operation, that enables us to
|
||||||
/// continue to the next increment, or `Else` for "don't care"
|
/// continue to the next match, or `Else` for "don't care" wildcard-style
|
||||||
/// wildcard-style matching.
|
/// matching.
|
||||||
pub expected: MatchResult,
|
pub expected: MatchResult,
|
||||||
|
|
||||||
/// Actions to perform, given that the operation resulted in the expected
|
|
||||||
/// value.
|
|
||||||
pub actions: Vec<Action<TOperator>>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A matching operation to be performed on some Cranelift instruction as part
|
/// A matching operation to be performed on some Cranelift instruction as part
|
||||||
@@ -93,79 +83,54 @@ where
|
|||||||
#[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);
|
||||||
|
|
||||||
@@ -177,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 220 KiB After Width: | Height: | Size: 252 KiB |
@@ -19,16 +19,23 @@ where
|
|||||||
|
|
||||||
for opt in &opts.optimizations {
|
for opt in &opts.optimizations {
|
||||||
let mut insertion = builder.insert();
|
let mut insertion = builder.insert();
|
||||||
for inc in &opt.increments {
|
let mut is_first = true;
|
||||||
// Ensure that this state's associated data is this increment's
|
for m in &opt.matches {
|
||||||
// match operation.
|
// Ensure that this state's associated data is this match's
|
||||||
|
// operation.
|
||||||
if let Some(op) = insertion.get_state_data() {
|
if let Some(op) = insertion.get_state_data() {
|
||||||
assert_eq!(*op, inc.operation);
|
assert_eq!(*op, m.operation);
|
||||||
} else {
|
} else {
|
||||||
insertion.set_state_data(inc.operation);
|
insertion.set_state_data(m.operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
insertion.next(inc.expected, inc.actions.clone().into_boxed_slice());
|
let actions = if is_first {
|
||||||
|
is_first = false;
|
||||||
|
opt.actions.clone().into_boxed_slice()
|
||||||
|
} else {
|
||||||
|
vec![].into_boxed_slice()
|
||||||
|
};
|
||||||
|
insertion.next(m.expected, actions);
|
||||||
}
|
}
|
||||||
insertion.finish();
|
insertion.finish();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
@@ -70,8 +63,8 @@ fn compare_optimizations<TOperator>(
|
|||||||
where
|
where
|
||||||
TOperator: Copy + Debug + Eq + Hash,
|
TOperator: Copy + Debug + Eq + Hash,
|
||||||
{
|
{
|
||||||
for (a, b) in a.increments.iter().zip(b.increments.iter()) {
|
for (a, b) in a.matches.iter().zip(b.matches.iter()) {
|
||||||
let c = compare_match_op_generality(paths, a.operation, b.operation);
|
let c = compare_match_op_generality(a.operation, b.operation);
|
||||||
if c != Ordering::Equal {
|
if c != Ordering::Equal {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
@@ -87,90 +80,66 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
compare_lengths(a.increments.len(), b.increments.len())
|
compare_lengths(a.matches.len(), b.matches.len())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_optimization_generality<TOperator>(
|
fn compare_optimization_generality<TOperator>(
|
||||||
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
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,28 +206,27 @@ where
|
|||||||
let mut prefix = vec![];
|
let mut prefix = vec![];
|
||||||
|
|
||||||
for opt in &mut opts.optimizations {
|
for opt in &mut opts.optimizations {
|
||||||
assert!(!opt.increments.is_empty());
|
assert!(!opt.matches.is_empty());
|
||||||
|
|
||||||
let mut old_increments = opt.increments.iter().peekable();
|
let mut old_matches = opt.matches.iter().peekable();
|
||||||
let mut new_increments = vec![];
|
let mut new_matches = vec![];
|
||||||
|
|
||||||
for (last_op, last_expected) in &prefix {
|
for (last_op, last_expected) in &prefix {
|
||||||
match old_increments.peek() {
|
match old_matches.peek() {
|
||||||
None => {
|
None => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
Some(inc) if *last_op == inc.operation => {
|
Some(inc) if *last_op == inc.operation => {
|
||||||
let inc = old_increments.next().unwrap();
|
let inc = old_matches.next().unwrap();
|
||||||
new_increments.push(inc.clone());
|
new_matches.push(inc.clone());
|
||||||
if inc.expected != *last_expected {
|
if inc.expected != *last_expected {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
new_increments.push(linear::Increment {
|
new_matches.push(linear::Match {
|
||||||
operation: *last_op,
|
operation: *last_op,
|
||||||
expected: Err(linear::Else),
|
expected: Err(linear::Else),
|
||||||
actions: vec![],
|
|
||||||
});
|
});
|
||||||
if last_expected.is_ok() {
|
if last_expected.is_ok() {
|
||||||
break;
|
break;
|
||||||
@@ -268,16 +235,12 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
new_increments.extend(old_increments.cloned());
|
new_matches.extend(old_matches.cloned());
|
||||||
assert!(new_increments.len() >= opt.increments.len());
|
assert!(new_matches.len() >= opt.matches.len());
|
||||||
opt.increments = new_increments;
|
opt.matches = new_matches;
|
||||||
|
|
||||||
prefix.clear();
|
prefix.clear();
|
||||||
prefix.extend(
|
prefix.extend(opt.matches.iter().map(|inc| (inc.operation, inc.expected)));
|
||||||
opt.increments
|
|
||||||
.iter()
|
|
||||||
.map(|inc| (inc.operation, inc.expected)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Should still be sorted after this pass.
|
// Should still be sorted after this pass.
|
||||||
@@ -291,21 +254,20 @@ where
|
|||||||
/// for the DSL's edge-cases than it is to try and statically eliminate their
|
/// for the DSL's edge-cases than it is to try and statically eliminate their
|
||||||
/// existence completely. So we just emit nop match operations for all variable
|
/// existence completely. So we just emit nop match operations for all variable
|
||||||
/// patterns, and then in this post-processing pass, we fuse them and their
|
/// patterns, and then in this post-processing pass, we fuse them and their
|
||||||
/// actions with their preceding increment.
|
/// actions with their preceding match.
|
||||||
pub fn remove_unnecessary_nops<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
pub fn remove_unnecessary_nops<TOperator>(opts: &mut linear::Optimizations<TOperator>)
|
||||||
where
|
where
|
||||||
TOperator: Copy + Debug + Eq + Hash,
|
TOperator: Copy + Debug + Eq + Hash,
|
||||||
{
|
{
|
||||||
for opt in &mut opts.optimizations {
|
for opt in &mut opts.optimizations {
|
||||||
if opt.increments.len() < 2 {
|
if opt.matches.len() < 2 {
|
||||||
debug_assert!(!opt.increments.is_empty());
|
debug_assert!(!opt.matches.is_empty());
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for i in (1..opt.increments.len()).rev() {
|
for i in (1..opt.matches.len()).rev() {
|
||||||
if let linear::MatchOp::Nop = opt.increments[i].operation {
|
if let linear::MatchOp::Nop = opt.matches[i].operation {
|
||||||
let nop = opt.increments.remove(i);
|
opt.matches.remove(i);
|
||||||
opt.increments[i - 1].actions.extend(nop.actions);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -315,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;
|
||||||
|
|
||||||
@@ -357,7 +316,7 @@ mod tests {
|
|||||||
.optimizations
|
.optimizations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| {
|
.map(|o| {
|
||||||
o.increments
|
o.matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -371,7 +330,7 @@ mod tests {
|
|||||||
.optimizations
|
.optimizations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| {
|
.map(|o| {
|
||||||
o.increments
|
o.matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -380,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;
|
||||||
@@ -388,16 +346,15 @@ mod tests {
|
|||||||
let actual: Vec<Vec<_>> = optimizations
|
let actual: Vec<Vec<_>> = optimizations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| {
|
.map(|o| {
|
||||||
o.increments
|
o.matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| (i.operation, i.expected))
|
.map(|i| (i.operation, i.expected))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.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);
|
||||||
}
|
}
|
||||||
@@ -435,7 +392,7 @@ mod tests {
|
|||||||
.optimizations
|
.optimizations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| {
|
.map(|o| {
|
||||||
o.increments
|
o.matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -449,7 +406,7 @@ mod tests {
|
|||||||
.optimizations
|
.optimizations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| {
|
.map(|o| {
|
||||||
o.increments
|
o.matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
.map(|i| format!("{:?} == {:?}", i.operation, i.expected))
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
@@ -458,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;
|
||||||
@@ -466,16 +422,15 @@ mod tests {
|
|||||||
let actual: Vec<Vec<_>> = optimizations
|
let actual: Vec<Vec<_>> = optimizations
|
||||||
.iter()
|
.iter()
|
||||||
.map(|o| {
|
.map(|o| {
|
||||||
o.increments
|
o.matches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|i| (i.operation, i.expected))
|
.map(|i| (i.operation, i.expected))
|
||||||
.collect()
|
.collect()
|
||||||
})
|
})
|
||||||
.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);
|
||||||
}
|
}
|
||||||
@@ -494,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)),
|
||||||
],
|
],
|
||||||
@@ -560,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))
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -603,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)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
@@ -642,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)
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -18,46 +18,49 @@
|
|||||||
//! (ishl $x $(log2 C)))
|
//! (ishl $x $(log2 C)))
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Then we should get the following linear chain of "increments":
|
//! Then the left-hand side becomes the following linear chain of "matches":
|
||||||
//!
|
//!
|
||||||
//! ```ignore
|
//! ```ignore
|
||||||
//! [
|
//! [
|
||||||
//! // ( Match Operation, Expected Value, Actions )
|
//! // ( Match Operation, Expected Value )
|
||||||
//! ( Opcode@0, imul, [$x = GetLhs@0.0, $C = GetLhs@0.1, ...] ),
|
//! ( Opcode@0, imul ),
|
||||||
//! ( IsConst(C), true, [] ),
|
//! ( IsConst(C), true ),
|
||||||
//! ( IsPowerOfTwo(C), true, [] ),
|
//! ( IsPowerOfTwo(C), true ),
|
||||||
//! ]
|
//! ]
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! Each increment will essentially become a state and a transition out of that
|
//! And the right-hand side becomes this linear chain of "actions":
|
||||||
//! state in the final automata, along with the actions to perform when taking
|
//!
|
||||||
//! that transition. The actions record the scope of matches from the left-hand
|
//! ```ignore
|
||||||
//! side and also incrementally build the right-hand side's instructions. (Note
|
//! [
|
||||||
//! that we've elided the actions that build up the optimization's right-hand
|
//! $rhs0 = get lhs @ 0.0 // $x
|
||||||
//! side in this example.)
|
//! $rhs1 = get lhs @ 0.1 // $C
|
||||||
|
//! $rhs2 = eval log2 $rhs1
|
||||||
|
//! $rhs3 = make ishl $rhs0, $rhs2
|
||||||
|
//! ]
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Each match will essentially become a state and a transition out of that
|
||||||
|
//! state in the final automata. The actions record the scope of matches from
|
||||||
|
//! the left-hand side and also incrementally build the right-hand side's
|
||||||
|
//! instructions.
|
||||||
//!
|
//!
|
||||||
//! ## General Principles
|
//! ## General Principles
|
||||||
//!
|
//!
|
||||||
//! Here are the general principles that linearization should adhere to:
|
//! Here are the general principles that linearization should adhere to:
|
||||||
//!
|
//!
|
||||||
//! * Actions should be pushed as early in the optimization's increment chain as
|
//! * 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
|
//! * Shorter match chains are better! This means fewer tests when matching
|
||||||
//! defined. And finally, an RHS operation's operands must be defined before
|
|
||||||
//! the RHS operation itself. In general, definitions must come before uses!
|
|
||||||
//!
|
|
||||||
//! * Shorter increment chains are better! This means fewer tests when matching
|
|
||||||
//! left-hand sides, and a more-compact, more-cache-friendly automata, and
|
//! left-hand sides, and a more-compact, more-cache-friendly automata, and
|
||||||
//! ultimately, a faster automata.
|
//! ultimately, a faster automata.
|
||||||
//!
|
//!
|
||||||
//! * An increment's match operation should be a switch rather than a predicate
|
//! * An match operation should be a switch rather than a predicate that returns
|
||||||
//! that returns a boolean. For example, we switch on an instruction's opcode,
|
//! a boolean. For example, we switch on an instruction's opcode, rather than
|
||||||
//! rather than ask whether this operation is an `imul`. This allows for more
|
//! ask whether this operation is an `imul`. This allows for more prefix
|
||||||
//! prefix sharing in the automata, which (again) makes it more compact and
|
//! sharing in the automata, which (again) makes it more compact and more
|
||||||
//! more cache friendly.
|
//! cache friendly.
|
||||||
//!
|
//!
|
||||||
//! ## Implementation Overview
|
//! ## Implementation Overview
|
||||||
//!
|
//!
|
||||||
@@ -67,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
|
||||||
@@ -85,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;
|
||||||
@@ -105,79 +106,74 @@ 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>
|
||||||
where
|
where
|
||||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||||
{
|
{
|
||||||
let mut increments: Vec<linear::Increment<_>> = vec![];
|
let mut matches: Vec<linear::Match> = vec![];
|
||||||
|
|
||||||
let mut lhs_id_to_path = LhsIdToPath::new();
|
let mut lhs_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 `Increment` for this part of the
|
// pattern.
|
||||||
// pattern, without any actions yet.
|
let (operation, expected) = pattern.to_linear_match_op(integers, &lhs_canonicalizer, id);
|
||||||
let (operation, expected) = pattern.to_linear_match_op(integers, &lhs_id_to_path, path);
|
matches.push(linear::Match {
|
||||||
increments.push(linear::Increment {
|
|
||||||
operation,
|
operation,
|
||||||
expected,
|
expected,
|
||||||
actions: vec![],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
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.
|
||||||
// When there is such a type ascription in the pattern, insert another
|
// When there is such a type ascription in the pattern, insert another
|
||||||
// increment that checks the instruction-being-matched's bit width.
|
// match that checks the instruction-being-matched's bit width.
|
||||||
if let Pattern::Operation(Operation { r#type, .. }) = pattern {
|
if let Pattern::Operation(Operation { r#type, .. }) = pattern {
|
||||||
if let Some(w) = r#type.get().and_then(|ty| ty.bit_width.fixed_width()) {
|
if let Some(w) = r#type.get().and_then(|ty| ty.bit_width.fixed_width()) {
|
||||||
debug_assert!(w != 0, "All fixed-width bit widths are non-zero");
|
debug_assert!(w != 0, "All fixed-width bit widths are non-zero");
|
||||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) });
|
let expected = Ok(unsafe { NonZeroU32::new_unchecked(w as u32) });
|
||||||
|
|
||||||
increments.push(linear::Increment {
|
matches.push(linear::Match {
|
||||||
operation: linear::MatchOp::BitWidth { path },
|
operation: linear::MatchOp::BitWidth(id),
|
||||||
expected,
|
expected,
|
||||||
actions: vec![],
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we've added all the increments for the LHS pattern, add the
|
// Now that we've added all the matches for the LHS pattern, add the
|
||||||
// increments for its preconditions.
|
// matches for its preconditions.
|
||||||
for pre in &opt.lhs.preconditions {
|
for pre in &opt.lhs.preconditions {
|
||||||
increments.push(pre.to_linear_increment(&lhs_id_to_path));
|
matches.push(pre.to_linear_match(&lhs_canonicalizer));
|
||||||
}
|
}
|
||||||
|
|
||||||
assert!(!increments.is_empty());
|
assert!(!matches.is_empty());
|
||||||
|
|
||||||
// Finally, generate the RHS-building actions and attach them to the first increment.
|
// Finally, generate the RHS-building actions and attach them to the first match.
|
||||||
let mut rhs_builder = RhsBuilder::new(&opt.rhs);
|
let mut rhs_builder = RhsBuilder::new(&opt.rhs);
|
||||||
rhs_builder.add_rhs_build_actions(integers, &lhs_id_to_path, &mut increments[0].actions);
|
let mut actions = vec![];
|
||||||
|
rhs_builder.add_rhs_build_actions(integers, &lhs_canonicalizer, &mut actions);
|
||||||
|
|
||||||
linear::Optimization { increments }
|
linear::Optimization { matches, actions }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A post-order, depth-first traversal of right-hand sides.
|
/// A post-order, depth-first traversal of right-hand sides.
|
||||||
@@ -214,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"),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -270,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);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -358,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);
|
||||||
}
|
}
|
||||||
@@ -371,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 {
|
||||||
@@ -393,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 {
|
||||||
@@ -452,22 +434,18 @@ impl<TOperator> Precondition<'_, TOperator>
|
|||||||
where
|
where
|
||||||
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
TOperator: Copy + Debug + Eq + Hash + Into<NonZeroU32>,
|
||||||
{
|
{
|
||||||
/// Convert this precondition into a `linear::Increment`.
|
/// Convert this precondition into a `linear::Match`.
|
||||||
fn to_linear_increment(
|
fn to_linear_match(&self, lhs_canonicalizer: &LhsCanonicalizer<TOperator>) -> linear::Match {
|
||||||
&self,
|
|
||||||
lhs_id_to_path: &LhsIdToPath<TOperator>,
|
|
||||||
) -> linear::Increment<TOperator> {
|
|
||||||
match self.constraint {
|
match self.constraint {
|
||||||
Constraint::IsPowerOfTwo => {
|
Constraint::IsPowerOfTwo => {
|
||||||
let id = match &self.operands[0] {
|
let id = match &self.operands[0] {
|
||||||
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::Increment {
|
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),
|
||||||
actions: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Constraint::BitWidth => {
|
Constraint::BitWidth => {
|
||||||
@@ -476,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 {
|
||||||
@@ -490,10 +468,9 @@ where
|
|||||||
assert!((width as u8).is_power_of_two());
|
assert!((width as u8).is_power_of_two());
|
||||||
let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) });
|
let expected = Ok(unsafe { NonZeroU32::new_unchecked(width as u32) });
|
||||||
|
|
||||||
linear::Increment {
|
linear::Match {
|
||||||
operation: linear::MatchOp::BitWidth { path },
|
operation: linear::MatchOp::BitWidth(id),
|
||||||
expected,
|
expected,
|
||||||
actions: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Constraint::FitsInNativeWord => {
|
Constraint::FitsInNativeWord => {
|
||||||
@@ -502,11 +479,10 @@ 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::Increment {
|
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),
|
||||||
actions: vec![],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -525,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 {
|
||||||
@@ -579,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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -590,8 +560,9 @@ 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,
|
||||||
};
|
};
|
||||||
use peepmatic_test_operator::TestOperator;
|
use peepmatic_test_operator::TestOperator;
|
||||||
|
|
||||||
@@ -625,24 +596,18 @@ 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::Increment<TestOperator>> = $make_expected;
|
) -> (Vec<linear::Match>, Vec<linear::Action<_>>) = $make_expected;
|
||||||
let expected = make_expected(&mut p, &mut i);
|
|
||||||
dbg!(&expected);
|
|
||||||
|
|
||||||
let actual = linearize_optimization(&mut paths, &mut integers, &opts.optimizations[0]);
|
let expected = make_expected(&mut i);
|
||||||
dbg!(&actual.increments);
|
let actual = linearize_optimization(&mut integers, &opts.optimizations[0]);
|
||||||
|
assert_eq!(expected.0, actual.matches);
|
||||||
assert_eq!(expected, actual.increments);
|
assert_eq!(expected.1, actual.actions);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -652,194 +617,191 @@ mod tests {
|
|||||||
"
|
"
|
||||||
(=> (when (imul $x $C)
|
(=> (when (imul $x $C)
|
||||||
(is-power-of-two $C))
|
(is-power-of-two $C))
|
||||||
(ishl $x $C))
|
(ishl $x $(log2 $C)))
|
||||||
",
|
",
|
||||||
|p, i| vec![
|
|i| (
|
||||||
linear::Increment {
|
vec![
|
||||||
operation: Opcode { path: p(&[0]) },
|
linear::Match {
|
||||||
|
operation: Opcode(LhsId(0)),
|
||||||
expected: Ok(TestOperator::Imul.into()),
|
expected: Ok(TestOperator::Imul.into()),
|
||||||
actions: vec![
|
},
|
||||||
GetLhs { path: p(&[0, 0]) },
|
linear::Match {
|
||||||
GetLhs { path: p(&[0, 1]) },
|
operation: Nop,
|
||||||
|
expected: Err(Else),
|
||||||
|
},
|
||||||
|
linear::Match {
|
||||||
|
operation: IsConst(LhsId(2)),
|
||||||
|
expected: bool_to_match_result(true),
|
||||||
|
},
|
||||||
|
linear::Match {
|
||||||
|
operation: IsPowerOfTwo(LhsId(2)),
|
||||||
|
expected: bool_to_match_result(true),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
GetLhs { lhs: LhsId(1) },
|
||||||
|
GetLhs { lhs: LhsId(2) },
|
||||||
|
UnaryUnquote {
|
||||||
|
operator: UnquoteOperator::Log2,
|
||||||
|
operand: RhsId(1)
|
||||||
|
},
|
||||||
MakeBinaryInst {
|
MakeBinaryInst {
|
||||||
operator: TestOperator::Ishl,
|
operator: TestOperator::Ishl,
|
||||||
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)],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
linear::Increment {
|
|
||||||
operation: Nop,
|
|
||||||
expected: Err(Else),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
linear::Increment {
|
|
||||||
operation: IsConst { path: p(&[0, 1]) },
|
|
||||||
expected: bool_to_match_result(true),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
linear::Increment {
|
|
||||||
operation: IsPowerOfTwo { path: p(&[0, 1]) },
|
|
||||||
expected: bool_to_match_result(true),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
},
|
||||||
|
operands: [RhsId(0), RhsId(2)]
|
||||||
|
}
|
||||||
],
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
linearizes_to!(variable_pattern_id_optimization, "(=> $x $x)", |p, i| vec![
|
linearizes_to!(variable_pattern_id_optimization, "(=> $x $x)", |i| (
|
||||||
linear::Increment {
|
vec![linear::Match {
|
||||||
operation: Nop,
|
operation: Nop,
|
||||||
expected: Err(Else),
|
expected: Err(Else),
|
||||||
actions: vec![GetLhs { path: p(&[0]) }],
|
}],
|
||||||
}
|
vec![GetLhs { lhs: LhsId(0) }],
|
||||||
]);
|
));
|
||||||
|
|
||||||
linearizes_to!(constant_pattern_id_optimization, "(=> $C $C)", |p, i| vec![
|
linearizes_to!(constant_pattern_id_optimization, "(=> $C $C)", |i| (
|
||||||
linear::Increment {
|
vec![linear::Match {
|
||||||
operation: IsConst { path: p(&[0]) },
|
operation: IsConst(LhsId(0)),
|
||||||
expected: bool_to_match_result(true),
|
expected: bool_to_match_result(true),
|
||||||
actions: vec![GetLhs { path: p(&[0]) }],
|
}],
|
||||||
}
|
vec![GetLhs { lhs: LhsId(0) }],
|
||||||
]);
|
));
|
||||||
|
|
||||||
linearizes_to!(
|
linearizes_to!(boolean_literal_id_optimization, "(=> true true)", |i| (
|
||||||
boolean_literal_id_optimization,
|
vec![linear::Match {
|
||||||
"(=> true true)",
|
operation: BooleanValue(LhsId(0)),
|
||||||
|p, i| vec![linear::Increment {
|
|
||||||
operation: BooleanValue { path: p(&[0]) },
|
|
||||||
expected: bool_to_match_result(true),
|
expected: bool_to_match_result(true),
|
||||||
actions: vec![MakeBooleanConst {
|
}],
|
||||||
|
vec![MakeBooleanConst {
|
||||||
value: true,
|
value: true,
|
||||||
bit_width: BitWidth::Polymorphic,
|
bit_width: BitWidth::Polymorphic,
|
||||||
}],
|
}],
|
||||||
}]
|
));
|
||||||
);
|
|
||||||
|
|
||||||
linearizes_to!(number_literal_id_optimization, "(=> 5 5)", |p, i| vec![
|
linearizes_to!(number_literal_id_optimization, "(=> 5 5)", |i| (
|
||||||
linear::Increment {
|
vec![linear::Match {
|
||||||
operation: IntegerValue { path: p(&[0]) },
|
operation: IntegerValue(LhsId(0)),
|
||||||
expected: Ok(i(5).into()),
|
expected: Ok(i(5).into()),
|
||||||
actions: vec![MakeIntegerConst {
|
}],
|
||||||
|
vec![MakeIntegerConst {
|
||||||
value: i(5),
|
value: i(5),
|
||||||
bit_width: BitWidth::Polymorphic,
|
bit_width: BitWidth::Polymorphic,
|
||||||
}],
|
}],
|
||||||
}
|
));
|
||||||
]);
|
|
||||||
|
|
||||||
linearizes_to!(
|
linearizes_to!(
|
||||||
operation_id_optimization,
|
operation_id_optimization,
|
||||||
"(=> (iconst $C) (iconst $C))",
|
"(=> (iconst $C) (iconst $C))",
|
||||||
|p, i| vec![
|
|i| (
|
||||||
linear::Increment {
|
vec![
|
||||||
operation: Opcode { path: p(&[0]) },
|
linear::Match {
|
||||||
|
operation: Opcode(LhsId(0)),
|
||||||
expected: Ok(TestOperator::Iconst.into()),
|
expected: Ok(TestOperator::Iconst.into()),
|
||||||
actions: vec![
|
},
|
||||||
GetLhs { path: p(&[0, 0]) },
|
linear::Match {
|
||||||
|
operation: IsConst(LhsId(1)),
|
||||||
|
expected: bool_to_match_result(true),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
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),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
linear::Increment {
|
|
||||||
operation: IsConst { path: p(&[0, 0]) },
|
|
||||||
expected: bool_to_match_result(true),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
linearizes_to!(
|
linearizes_to!(
|
||||||
redundant_bor,
|
redundant_bor,
|
||||||
"(=> (bor $x (bor $x $y)) (bor $x $y))",
|
"(=> (bor $x (bor $x $y)) (bor $x $y))",
|
||||||
|p, i| vec![
|
|i| (
|
||||||
linear::Increment {
|
vec![
|
||||||
operation: Opcode { path: p(&[0]) },
|
linear::Match {
|
||||||
|
operation: Opcode(LhsId(0)),
|
||||||
expected: Ok(TestOperator::Bor.into()),
|
expected: Ok(TestOperator::Bor.into()),
|
||||||
actions: vec![
|
|
||||||
GetLhs { path: p(&[0, 0]) },
|
|
||||||
GetLhs {
|
|
||||||
path: p(&[0, 1, 1]),
|
|
||||||
},
|
},
|
||||||
|
linear::Match {
|
||||||
|
operation: Nop,
|
||||||
|
expected: Err(Else),
|
||||||
|
},
|
||||||
|
linear::Match {
|
||||||
|
operation: Opcode(LhsId(2)),
|
||||||
|
expected: Ok(TestOperator::Bor.into()),
|
||||||
|
},
|
||||||
|
linear::Match {
|
||||||
|
operation: Eq(LhsId(3), LhsId(1)),
|
||||||
|
expected: bool_to_match_result(true),
|
||||||
|
},
|
||||||
|
linear::Match {
|
||||||
|
operation: Nop,
|
||||||
|
expected: Err(Else),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![
|
||||||
|
GetLhs { lhs: LhsId(1) },
|
||||||
|
GetLhs { lhs: LhsId(4) },
|
||||||
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)],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
linear::Increment {
|
|
||||||
operation: Nop,
|
|
||||||
expected: Err(Else),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
linear::Increment {
|
|
||||||
operation: Opcode { path: p(&[0, 1]) },
|
|
||||||
expected: Ok(TestOperator::Bor.into()),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
linear::Increment {
|
|
||||||
operation: Eq {
|
|
||||||
path_a: p(&[0, 1, 0]),
|
|
||||||
path_b: p(&[0, 0]),
|
|
||||||
},
|
|
||||||
expected: bool_to_match_result(true),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
linear::Increment {
|
|
||||||
operation: Nop,
|
|
||||||
expected: Err(Else),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
linearizes_to!(
|
linearizes_to!(
|
||||||
large_integers,
|
large_integers,
|
||||||
// u64::MAX
|
// u64::MAX
|
||||||
"(=> 18446744073709551615 0)",
|
"(=> 18446744073709551615 0)",
|
||||||
|p, i| vec![linear::Increment {
|
|i| (
|
||||||
operation: IntegerValue { path: p(&[0]) },
|
vec![linear::Match {
|
||||||
|
operation: IntegerValue(LhsId(0)),
|
||||||
expected: Ok(i(std::u64::MAX).into()),
|
expected: Ok(i(std::u64::MAX).into()),
|
||||||
actions: vec![MakeIntegerConst {
|
}],
|
||||||
|
vec![MakeIntegerConst {
|
||||||
value: i(0),
|
value: i(0),
|
||||||
bit_width: BitWidth::Polymorphic,
|
bit_width: BitWidth::Polymorphic,
|
||||||
}],
|
}],
|
||||||
}]
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
linearizes_to!(
|
linearizes_to!(
|
||||||
ireduce_with_type_ascription,
|
ireduce_with_type_ascription,
|
||||||
"(=> (ireduce{i32} $x) 0)",
|
"(=> (ireduce{i32} $x) 0)",
|
||||||
|p, i| vec![
|
|i| (
|
||||||
linear::Increment {
|
vec![
|
||||||
operation: Opcode { path: p(&[0]) },
|
linear::Match {
|
||||||
|
operation: Opcode(LhsId(0)),
|
||||||
expected: Ok(TestOperator::Ireduce.into()),
|
expected: Ok(TestOperator::Ireduce.into()),
|
||||||
actions: vec![MakeIntegerConst {
|
},
|
||||||
|
linear::Match {
|
||||||
|
operation: linear::MatchOp::BitWidth(LhsId(0)),
|
||||||
|
expected: Ok(NonZeroU32::new(32).unwrap()),
|
||||||
|
},
|
||||||
|
linear::Match {
|
||||||
|
operation: Nop,
|
||||||
|
expected: Err(Else),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
vec![MakeIntegerConst {
|
||||||
value: i(0),
|
value: i(0),
|
||||||
bit_width: BitWidth::ThirtyTwo,
|
bit_width: BitWidth::ThirtyTwo,
|
||||||
}],
|
}],
|
||||||
},
|
),
|
||||||
linear::Increment {
|
|
||||||
operation: linear::MatchOp::BitWidth { path: p(&[0]) },
|
|
||||||
expected: Ok(NonZeroU32::new(32).unwrap()),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
linear::Increment {
|
|
||||||
operation: Nop,
|
|
||||||
expected: Err(Else),
|
|
||||||
actions: vec![],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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