use std::collections::BTreeMap;
use crate::ir::{BufferAccess, BufferDecl, DataType, Expr, Node, Program};
use crate::optimizer::{registered_passes_for_profile, OptimizerProfile, PassScheduler};
pub const RELEASE_MIN_OPTIMIZATION_CASES: usize = 4_096;
#[derive(Debug, Clone)]
pub struct OptimizationCorpusCase {
pub id: String,
pub family: String,
pub program: Program,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct OptimizationFamilyCount {
pub family: String,
pub cases: usize,
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct OptimizationCorpusManifest {
pub schema_version: u32,
pub required_min_cases: usize,
pub generated_cases: usize,
pub verified_cases: usize,
pub optimized_cases: usize,
pub non_converged_cases: usize,
pub total_nodes_before: usize,
pub total_nodes_after: usize,
pub pass_instance_count: usize,
pub changed_pass_instances: usize,
pub families: Vec<OptimizationFamilyCount>,
pub blockers: Vec<String>,
}
#[must_use]
pub fn generate_release_corpus() -> Vec<OptimizationCorpusCase> {
const FAMILIES: [&str; 8] = [
"scalar-algebra",
"strength-reduction",
"fusion-cse",
"dead-code",
"memory-dataflow",
"loop-transform",
"control-flow",
"canonicalization",
];
let mut cases = Vec::with_capacity(RELEASE_MIN_OPTIMIZATION_CASES);
for seed in 0..512u32 {
for family in FAMILIES {
cases.push(OptimizationCorpusCase {
id: format!("foundation.optimizer.{family}.{seed:04}"),
family: family.to_string(),
program: program_for(family, seed),
});
}
}
cases
}
#[must_use]
pub fn manifest_for(cases: &[OptimizationCorpusCase]) -> OptimizationCorpusManifest {
let passes = match registered_passes_for_profile(OptimizerProfile::Release) {
Ok(passes) => passes,
Err(error) => {
return OptimizationCorpusManifest {
schema_version: 2,
required_min_cases: RELEASE_MIN_OPTIMIZATION_CASES,
generated_cases: cases.len(),
verified_cases: 0,
optimized_cases: 0,
non_converged_cases: cases.len(),
total_nodes_before: 0,
total_nodes_after: 0,
pass_instance_count: 0,
changed_pass_instances: 0,
families: family_counts(cases),
blockers: vec![format!(
"registered release pass scheduling failed: {error}"
)],
};
}
};
let scheduler = match PassScheduler::try_with_passes(passes) {
Ok(scheduler) => scheduler
.with_cost_monotone_enforcement(true)
.with_effect_handler_enforcement(true)
.with_linear_type_enforcement(true)
.with_shape_predicate_enforcement(true),
Err(error) => {
return OptimizationCorpusManifest {
schema_version: 2,
required_min_cases: RELEASE_MIN_OPTIMIZATION_CASES,
generated_cases: cases.len(),
verified_cases: 0,
optimized_cases: 0,
non_converged_cases: cases.len(),
total_nodes_before: 0,
total_nodes_after: 0,
pass_instance_count: 0,
changed_pass_instances: 0,
families: family_counts(cases),
blockers: vec![format!("release optimizer construction failed: {error}")],
};
}
};
let mut verified_cases = 0usize;
let mut optimized_cases = 0usize;
let mut non_converged_cases = 0usize;
let mut total_nodes_before = 0usize;
let mut total_nodes_after = 0usize;
let mut pass_instance_count = 0usize;
let mut changed_pass_instances = 0usize;
let mut blockers = Vec::new();
for case in cases {
let nodes_before = case.program.stats().node_count;
total_nodes_before = total_nodes_before.saturating_add(nodes_before);
match scheduler.run_with_metrics(case.program.clone()) {
Ok(report) => {
verified_cases += 1;
let changed = report.passes.iter().filter(|metric| metric.changed).count();
if changed != 0 {
optimized_cases += 1;
}
pass_instance_count = pass_instance_count.saturating_add(report.passes.len());
changed_pass_instances = changed_pass_instances.saturating_add(changed);
total_nodes_after =
total_nodes_after.saturating_add(report.program.stats().node_count);
if let Err(error) = report.program.validate() {
blockers.push(format!(
"case `{}` produced invalid optimized Program: {error}",
case.id
));
}
}
Err(error) => {
non_converged_cases += 1;
blockers.push(format!(
"case `{}` failed canonical semantic optimization: {error}",
case.id
));
}
}
}
OptimizationCorpusManifest {
schema_version: 2,
required_min_cases: RELEASE_MIN_OPTIMIZATION_CASES,
generated_cases: cases.len(),
verified_cases,
optimized_cases,
non_converged_cases,
total_nodes_before,
total_nodes_after,
pass_instance_count,
changed_pass_instances,
families: family_counts(cases),
blockers,
}
}
fn family_counts(cases: &[OptimizationCorpusCase]) -> Vec<OptimizationFamilyCount> {
let mut counts = BTreeMap::<String, usize>::new();
for case in cases {
*counts.entry(case.family.clone()).or_default() += 1;
}
counts
.into_iter()
.map(|(family, cases)| OptimizationFamilyCount { family, cases })
.collect()
}
fn program_for(family: &str, seed: u32) -> Program {
let input = BufferDecl::storage("in", 0, BufferAccess::ReadOnly, DataType::U32).with_count(64);
let scratch =
BufferDecl::storage("scratch", 1, BufferAccess::ReadWrite, DataType::U32).with_count(64);
let output = BufferDecl::output("out", 2, DataType::U32).with_count(64);
let index = Expr::bitand(Expr::gid_x(), Expr::u32(63));
let loaded = Expr::load("in", index.clone());
let literal = Expr::u32(seed & 31);
let body = match family {
"scalar-algebra" => vec![Node::store(
"out",
index,
Expr::add(Expr::mul(Expr::u32(3), Expr::u32(4)), literal),
)],
"strength-reduction" => vec![Node::store(
"out",
index,
Expr::mul(Expr::add(loaded, literal), Expr::u32(8)),
)],
"fusion-cse" => vec![
Node::let_bind("a", Expr::add(loaded.clone(), literal.clone())),
Node::let_bind("b", Expr::add(loaded, literal)),
Node::store("out", index, Expr::add(Expr::var("a"), Expr::var("b"))),
],
"dead-code" => vec![
Node::let_bind("dead", Expr::mul(loaded.clone(), Expr::u32(0))),
Node::store("out", index, Expr::add(loaded, literal)),
],
"memory-dataflow" => vec![
Node::store("scratch", index.clone(), loaded),
Node::store("out", index.clone(), Expr::load("scratch", index)),
],
"loop-transform" => vec![Node::Loop {
var: "i".into(),
from: Expr::u32(0),
to: Expr::u32(1),
body: vec![Node::store("out", index, Expr::add(loaded, literal))],
}],
"control-flow" => vec![Node::if_then_else(
Expr::bool(true),
vec![Node::store(
"out",
index.clone(),
Expr::add(loaded, literal),
)],
vec![Node::store("out", index, Expr::u32(0))],
)],
"canonicalization" => vec![Node::store(
"out",
index,
Expr::add(literal, Expr::add(loaded, Expr::u32(0))),
)],
_ => unreachable!("closed semantic corpus family"),
};
Program::wrapped(vec![input, scratch, output], [64, 1, 1], body)
.with_entry_op_id(format!("foundation.optimizer.{family}"))
}