use std::{
fmt,
hash::{Hash, Hasher},
};
use laddu_expr::ExprGraph;
use crate::{CompileResult, cost::OptimizationCost};
use super::{CanonicalCsePass, CostGatePass, OptimizationPass, OptimizationPipeline, RewritePass};
const DEFAULT_MAX_ITERATIONS: usize = 16;
impl OptimizationPipeline {
pub(crate) fn normalization_analysis_passes() -> Self {
Self::new()
.with_pass(RewritePass::simplify())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::normalize_add_mul())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::combine_like_terms())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::factor_common_products())
.with_pass(RewritePass::normalize_add_mul())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::exponential())
.with_pass(RewritePass::simplify())
.with_max_iterations(DEFAULT_MAX_ITERATIONS)
}
pub(crate) fn normalization_target_lowering_passes() -> Self {
Self::new().with_pass(CostGatePass::new(Self::norm_sqr_expansion_candidate()))
}
pub fn new() -> Self {
Self {
passes: Vec::new(),
max_iterations: 1,
}
}
pub fn with_default_passes() -> Self {
Self::new()
.with_pass(RewritePass::simplify())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::normalize_add_mul())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::combine_like_terms())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::factor_common_products())
.with_pass(RewritePass::normalize_add_mul())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::exponential())
.with_pass(RewritePass::simplify())
.with_pass(CostGatePass::new(Self::norm_sqr_expansion_candidate()))
.with_max_iterations(DEFAULT_MAX_ITERATIONS)
}
pub fn norm_sqr_expansion_candidate() -> Self {
Self::new()
.with_pass(RewritePass::norm_sqr_expansion())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::conjugation())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::normalize_add_mul())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::combine_like_terms())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::factor_common_products())
.with_pass(RewritePass::normalize_add_mul())
.with_pass(CanonicalCsePass)
.with_pass(RewritePass::exponential())
.with_pass(RewritePass::simplify())
.with_max_iterations(8)
}
pub fn add_pass(&mut self, pass: impl OptimizationPass + 'static) {
self.passes.push(Box::new(pass));
}
pub fn with_pass(mut self, pass: impl OptimizationPass + 'static) -> Self {
self.add_pass(pass);
self
}
pub fn set_max_iterations(&mut self, max_iterations: usize) {
self.max_iterations = max_iterations.max(1);
}
pub fn with_max_iterations(mut self, max_iterations: usize) -> Self {
self.set_max_iterations(max_iterations);
self
}
pub fn max_iterations(&self) -> usize {
self.max_iterations
}
pub fn run(&self, mut graph: ExprGraph) -> CompileResult<ExprGraph> {
for _ in 0..self.max_iterations {
let previous = graph_fingerprint(&graph);
for pass in &self.passes {
graph = pass.run(graph)?;
}
if previous == graph_fingerprint(&graph) {
break;
}
}
Ok(graph)
}
pub fn is_empty(&self) -> bool {
self.passes.is_empty()
}
}
impl Default for OptimizationPipeline {
fn default() -> Self {
Self::with_default_passes()
}
}
impl fmt::Debug for OptimizationPipeline {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("OptimizationPipeline")
.field("passes", &self.passes.len())
.field("max_iterations", &self.max_iterations)
.finish()
}
}
pub(super) fn graph_fingerprint(graph: &ExprGraph) -> u64 {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
graph.root().index().hash(&mut hasher);
graph.nodes().len().hash(&mut hasher);
for node in graph.nodes() {
node.structural_key().hash(&mut hasher);
}
hasher.finish()
}
impl CostGatePass {
pub fn new(candidate: OptimizationPipeline) -> Self {
Self::named("cost-gate", candidate)
}
pub fn named(name: &'static str, candidate: OptimizationPipeline) -> Self {
Self { name, candidate }
}
}
impl fmt::Debug for CostGatePass {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("CostGatePass")
.field("name", &self.name)
.field("candidate", &self.candidate)
.finish()
}
}
impl OptimizationPass for CostGatePass {
fn name(&self) -> &'static str {
self.name
}
fn run(&self, graph: ExprGraph) -> CompileResult<ExprGraph> {
let original_cost = OptimizationCost::analyze(&graph);
let candidate = self.candidate.run(graph.clone())?;
let candidate_cost = OptimizationCost::analyze(&candidate);
if candidate_cost.is_better_than(&original_cost) {
Ok(candidate)
} else {
Ok(graph)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pass_names(pipeline: &OptimizationPipeline) -> Vec<&'static str> {
pipeline.passes.iter().map(|pass| pass.name()).collect()
}
#[test]
fn built_in_pipeline_order_is_stable() {
assert_eq!(
pass_names(&OptimizationPipeline::with_default_passes()),
[
"simplify",
"canonical-cse",
"normalize-add-mul",
"canonical-cse",
"combine-like-terms",
"canonical-cse",
"factor-common-products",
"normalize-add-mul",
"canonical-cse",
"exponential",
"simplify",
"cost-gate",
]
);
assert_eq!(
pass_names(&OptimizationPipeline::normalization_analysis_passes()),
[
"simplify",
"canonical-cse",
"normalize-add-mul",
"canonical-cse",
"combine-like-terms",
"canonical-cse",
"factor-common-products",
"normalize-add-mul",
"canonical-cse",
"exponential",
"simplify",
]
);
assert_eq!(
pass_names(&OptimizationPipeline::norm_sqr_expansion_candidate()),
[
"norm-sqr-expansion",
"canonical-cse",
"conjugation",
"canonical-cse",
"normalize-add-mul",
"canonical-cse",
"combine-like-terms",
"canonical-cse",
"factor-common-products",
"normalize-add-mul",
"canonical-cse",
"exponential",
"simplify",
]
);
}
}