use alloc::vec::Vec;
use core::fmt;
use ir_lang::{Builder, FuncId, Module};
use pass_lang::{Outcome, Pass, PassError, PassManager};
use crate::analysis::size;
use crate::passes::{copy_prop, dce, gvn, licm, sccp, simplify_cfg};
use crate::{Budget, OptError, Stats};
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[non_exhaustive]
pub enum PassKind {
SimplifyCfg,
Sccp,
CopyProp,
Gvn,
Licm,
Dce,
}
impl PassKind {
pub const ALL: [PassKind; 6] = [
PassKind::SimplifyCfg,
PassKind::Sccp,
PassKind::CopyProp,
PassKind::Gvn,
PassKind::Licm,
PassKind::Dce,
];
#[must_use]
pub const fn name(self) -> &'static str {
match self {
PassKind::SimplifyCfg => "simplify-cfg",
PassKind::Sccp => "sccp",
PassKind::CopyProp => "copy-prop",
PassKind::Gvn => "gvn",
PassKind::Licm => "licm",
PassKind::Dce => "dce",
}
}
#[must_use]
pub fn from_name(name: &str) -> Option<PassKind> {
PassKind::ALL.into_iter().find(|p| p.name() == name)
}
}
impl fmt::Display for PassKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.name())
}
}
pub const DEFAULT_PIPELINE: &[PassKind] = &[
PassKind::SimplifyCfg,
PassKind::Sccp,
PassKind::CopyProp,
PassKind::Gvn,
PassKind::Licm,
PassKind::Dce,
PassKind::SimplifyCfg,
];
pub const DEFAULT_MAX_ITERATIONS: usize = 4;
pub const DEFAULT_STEPS_PER_UNIT: u64 = 256;
fn default_budget(func: &ir_lang::Function) -> u64 {
let units = (func.inst_count() + func.block_count() + func.value_count()) as u64;
units
.saturating_mul(DEFAULT_STEPS_PER_UNIT)
.saturating_add(1 << 16)
}
fn execute(b: &mut Builder<'_>, pass: PassKind, budget: &mut Budget) -> Result<bool, OptError> {
b.resolve_aliases();
let r = match pass {
PassKind::SimplifyCfg => simplify_cfg::run(b, budget),
PassKind::Sccp => sccp::run(b, budget),
PassKind::CopyProp => copy_prop::run(b, budget),
PassKind::Gvn => gvn::run(b, budget),
PassKind::Licm => licm::run(b, budget),
PassKind::Dce => dce::run(b, budget),
};
r.map_err(|error| OptError::Build {
func: b.id(),
pass,
error,
})
}
pub fn run_pass(
b: &mut Builder<'_>,
pass: PassKind,
budget: &mut Budget,
) -> Result<bool, OptError> {
b.validate().map_err(|error| OptError::InvalidInput {
func: b.id(),
error,
})?;
let changed = execute(b, pass, budget)?;
if changed && cfg!(debug_assertions) {
b.validate().map_err(|error| OptError::InvalidOutput {
func: b.id(),
pass,
error,
})?;
}
Ok(changed)
}
struct Session<'m> {
module: &'m mut Module,
func: FuncId,
budget: Budget,
validate: bool,
compact: bool,
failure: Option<OptError>,
stats: Stats,
}
fn maybe_compact(b: &mut Builder<'_>) {
let func = b.func();
let (insts, blocks) = size(func);
if func.inst_count() > 2 * insts + 64 || func.block_count() > 2 * blocks + 64 {
let _ = b.compact();
}
}
struct Step(PassKind);
impl Pass<Session<'_>> for Step {
fn name(&self) -> &'static str {
self.0.name()
}
fn run(&mut self, s: &mut Session<'_>) -> Result<Outcome, PassError> {
let pass = self.0;
let func = s.func;
let result = match s.module.edit(func) {
Ok(mut b) => execute(&mut b, pass, &mut s.budget).and_then(|changed| {
if changed && s.validate {
b.validate()
.map_err(|error| OptError::InvalidOutput { func, pass, error })?;
}
if changed && s.compact {
maybe_compact(&mut b);
}
Ok(changed)
}),
Err(e) => Err(OptError::Module(e)),
};
match result {
Ok(changed) => {
s.stats.record(pass, changed);
Ok(Outcome::from_changed(changed))
}
Err(e) => {
s.failure = Some(e);
Err(PassError::new("see the session's typed error"))
}
}
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Optimizer {
passes: Vec<PassKind>,
max_iterations: usize,
budget: Option<u64>,
validate: bool,
compact: bool,
}
impl Default for Optimizer {
fn default() -> Self {
Optimizer::new()
}
}
impl Optimizer {
#[must_use]
pub fn new() -> Optimizer {
Optimizer {
passes: DEFAULT_PIPELINE.to_vec(),
max_iterations: DEFAULT_MAX_ITERATIONS,
budget: None,
validate: cfg!(debug_assertions),
compact: true,
}
}
#[must_use]
pub fn passes(mut self, passes: &[PassKind]) -> Optimizer {
self.passes = passes.to_vec();
self
}
#[must_use]
pub fn max_iterations(mut self, n: usize) -> Optimizer {
self.max_iterations = n;
self
}
#[must_use]
pub fn budget(mut self, steps: u64) -> Optimizer {
self.budget = Some(steps);
self
}
#[must_use]
pub fn validate(mut self, on: bool) -> Optimizer {
self.validate = on;
self
}
#[must_use]
pub fn compact(mut self, on: bool) -> Optimizer {
self.compact = on;
self
}
pub fn run(&self, module: &mut Module) -> Result<Stats, OptError> {
let funcs: Vec<FuncId> = module
.functions()
.filter(|&f| module.function(f).is_some())
.collect();
self.run_on(module, &funcs)
}
pub fn run_function(&self, module: &mut Module, func: FuncId) -> Result<Stats, OptError> {
if module.function(func).is_none() {
let _ = module.edit(func)?;
}
self.run_on(module, &[func])
}
fn run_on(&self, module: &mut Module, funcs: &[FuncId]) -> Result<Stats, OptError> {
let mut total = Stats::default();
let first = match funcs.first() {
Some(&f) => f,
None => return Ok(total),
};
let mut pm: PassManager<Session<'_>> = PassManager::new();
for &p in &self.passes {
let _ = pm.add(Step(p));
}
let mut s = Session {
module,
func: first,
budget: Budget::unlimited(),
validate: self.validate,
compact: self.compact,
failure: None,
stats: Stats::default(),
};
for &f in funcs {
s.module
.validate_function(f)
.map_err(|error| OptError::InvalidInput { func: f, error })?;
let Some(body) = s.module.function(f) else {
continue;
};
let (insts, blocks) = size(body);
let steps = self.budget.unwrap_or_else(|| default_budget(body));
s.func = f;
s.budget = Budget::new(steps);
s.stats = Stats::default();
let report = match pm.run_to_fixpoint(&mut s, self.max_iterations) {
Ok(r) => r,
Err(e) => {
return Err(s.failure.take().unwrap_or(OptError::Build {
func: f,
pass: PassKind::from_name(e.pass()).unwrap_or(PassKind::Dce),
error: ir_lang::BuildError::UnknownBlock {
block: ir_lang::Block::from_u32(u32::MAX),
},
}));
}
};
if self.compact && report.changes() > 0 {
let _ = s.module.edit(f)?.compact();
}
let (insts_after, blocks_after) = s.module.function(f).map_or((0, 0), size);
let mut st = core::mem::take(&mut s.stats);
st.functions = 1;
st.insts_before = insts;
st.blocks_before = blocks;
st.insts_after = insts_after;
st.blocks_after = blocks_after;
st.iterations = report.iterations();
st.converged = usize::from(report.converged());
st.budget_exhausted = usize::from(s.budget.is_exhausted());
total.merge(&st);
}
Ok(total)
}
}