use crate::{
analysis::Validator,
mir::{Function, Module},
transform::{
AdcePass, CfgSimplifyPass, CheckElimPass, CsePass, DcePass, FrameSlotPromotionPass,
FunctionDcePass, GvnPass, IndVarSimplifyPass, InlinePass, InstSimplifyPass,
JumpThreadingPass, LicmPass, LoadPrePass, LoopCanonicalizePass, MemoryDsePass, PrePass,
PureEvalPass, SccpTransformPass, StorageDsePass, StorageLoadCsePass,
StorageScalarPromotionPass,
},
};
use solar_data_structures::map::FxHashMap;
use std::any::{Any, TypeId};
type PassFactory = fn() -> Box<dyn ModulePass>;
#[derive(Clone, Copy, Debug)]
pub struct PassInfo {
pub name: &'static str,
pub description: &'static str,
make_pass: PassFactory,
}
impl PassInfo {
const fn new(name: &'static str, description: &'static str, make_pass: PassFactory) -> Self {
Self { name, description, make_pass }
}
fn make_pass(&self) -> Box<dyn ModulePass> {
(self.make_pass)()
}
}
macro_rules! declare_passes {
($(
$(#[doc = $description:literal])+
$vis:vis const $const_name:ident -> $name:literal = $pass:expr;
)+) => {
$(
$(#[doc = $description])+
$vis const $const_name: PassInfo = PassInfo::new(
$name,
concat!($($description, "\n"),+).trim_ascii(),
|| Box::new($pass),
);
)+
};
}
declare_passes! {
pub const INLINE_PASS -> "inline" = InlinePass;
pub const FUNCTION_DCE_PASS -> "function-dce" = FunctionDcePass;
pub const SCCP_PASS -> "sccp" = SccpTransformPass;
pub const PURE_EVAL_PASS -> "pure-eval" = PureEvalPass;
pub const INST_SIMPLIFY_PASS -> "inst-simplify" = InstSimplifyPass;
pub const CSE_PASS -> "cse" = CsePass;
pub const PRE_PASS -> "pre" = PrePass;
pub const GVN_PASS -> "gvn" = GvnPass;
pub const STORAGE_LOAD_CSE_PASS -> "storage-load-cse" = StorageLoadCsePass;
pub const STORAGE_DSE_PASS -> "storage-dse" = StorageDsePass;
pub const LOAD_PRE_PASS -> "load-pre" = LoadPrePass;
pub const LOOP_CANONICALIZE_PASS -> "loop-canonicalize" = LoopCanonicalizePass;
pub const INDVAR_SIMPLIFY_PASS -> "indvar-simplify" = IndVarSimplifyPass;
pub const STORAGE_PROMOTION_PASS -> "storage-promotion" = StorageScalarPromotionPass;
pub const LICM_PASS -> "licm" = LicmPass;
pub const CHECK_ELIM_PASS -> "check-elim" = CheckElimPass;
pub const JUMP_THREADING_PASS -> "jump-threading" = JumpThreadingPass;
pub const CFG_SIMPLIFY_PASS -> "cfg-simplify" = CfgSimplifyPass;
pub const FRAME_SLOT_PROMOTION_PASS -> "frame-slot-promotion" = FrameSlotPromotionPass;
pub const MEMORY_DSE_PASS -> "memory-dse" = MemoryDsePass;
pub const DCE_PASS -> "dce" = DcePass;
pub const ADCE_PASS -> "adce" = AdcePass;
}
pub const PASS_REGISTRY: &[PassInfo] = &[
INLINE_PASS,
FUNCTION_DCE_PASS,
ADCE_PASS,
DCE_PASS,
INST_SIMPLIFY_PASS,
CSE_PASS,
GVN_PASS,
PRE_PASS,
STORAGE_LOAD_CSE_PASS,
STORAGE_DSE_PASS,
LOAD_PRE_PASS,
LOOP_CANONICALIZE_PASS,
INDVAR_SIMPLIFY_PASS,
SCCP_PASS,
PURE_EVAL_PASS,
LICM_PASS,
CHECK_ELIM_PASS,
CFG_SIMPLIFY_PASS,
JUMP_THREADING_PASS,
FRAME_SLOT_PROMOTION_PASS,
MEMORY_DSE_PASS,
STORAGE_PROMOTION_PASS,
];
pub fn lookup_pass(name: &str) -> Option<&'static PassInfo> {
PASS_REGISTRY.iter().find(|pass| pass.name == name)
}
pub const DEFAULT_PIPELINE: &[PassInfo] = &[
INLINE_PASS,
FUNCTION_DCE_PASS,
SCCP_PASS,
PURE_EVAL_PASS,
INST_SIMPLIFY_PASS,
CSE_PASS,
GVN_PASS,
PRE_PASS,
STORAGE_LOAD_CSE_PASS,
STORAGE_DSE_PASS,
LOAD_PRE_PASS,
FRAME_SLOT_PROMOTION_PASS,
LOOP_CANONICALIZE_PASS,
INDVAR_SIMPLIFY_PASS,
STORAGE_PROMOTION_PASS,
LICM_PASS,
CHECK_ELIM_PASS,
JUMP_THREADING_PASS,
CFG_SIMPLIFY_PASS,
MEMORY_DSE_PASS,
ADCE_PASS,
DCE_PASS,
];
pub const DEFAULT_CLEANUP_PIPELINE: &[PassInfo] = &[
SCCP_PASS,
PURE_EVAL_PASS,
INST_SIMPLIFY_PASS,
CSE_PASS,
GVN_PASS,
PRE_PASS,
STORAGE_LOAD_CSE_PASS,
STORAGE_DSE_PASS,
LOAD_PRE_PASS,
CHECK_ELIM_PASS,
JUMP_THREADING_PASS,
CFG_SIMPLIFY_PASS,
FRAME_SLOT_PROMOTION_PASS,
MEMORY_DSE_PASS,
ADCE_PASS,
DCE_PASS,
];
const DEFAULT_CLEANUP_MAX_ROUNDS: usize = 3;
#[derive(Clone, Copy, Debug)]
pub struct PipelineOptions {
pub print_after_each: bool,
pub validate_after_each: bool,
}
impl Default for PipelineOptions {
fn default() -> Self {
Self { print_after_each: false, validate_after_each: cfg!(debug_assertions) }
}
}
pub fn run_pass(module: &mut Module, pass: &PassInfo) -> bool {
run_pass_with_options(module, pass, PipelineOptions::default())
}
fn run_pass_with_options(module: &mut Module, pass: &PassInfo, options: PipelineOptions) -> bool {
let mut pm = PassManager::new();
pm.set_validate_after_each(options.validate_after_each);
pm.add_pass(pass.make_pass());
pm.run(module).1
}
pub fn run_pipeline(module: &mut Module, passes: &[PassInfo]) -> bool {
let mut changed = false;
for pass in passes {
changed |= run_pass(module, pass);
}
changed
}
pub fn run_pipeline_with_options(
module: &mut Module,
passes: &[PassInfo],
options: PipelineOptions,
) -> bool {
let mut changed = false;
for pass in passes {
changed |= run_pass_with_options(module, pass, options);
if options.print_after_each {
println!("// === {} (after {}) ===", module.name, pass.name);
print!("{}", module.to_text());
}
}
changed
}
pub fn run_default_pipeline(module: &mut Module) -> bool {
run_default_pipeline_with_options(module, PipelineOptions::default())
}
pub fn run_default_pipeline_with_options(module: &mut Module, options: PipelineOptions) -> bool {
let mut changed = run_pipeline_with_options(module, DEFAULT_PIPELINE, options);
changed |=
run_cleanup_pipeline_to_fixpoint(module, DEFAULT_CLEANUP_PIPELINE, options, "cleanup");
changed
}
fn run_cleanup_pipeline_to_fixpoint(
module: &mut Module,
passes: &[PassInfo],
options: PipelineOptions,
label: &str,
) -> bool {
let mut changed = false;
for round in 1..=DEFAULT_CLEANUP_MAX_ROUNDS {
let mut round_changed = false;
for pass in passes {
let pass_changed = run_pass_with_options(module, pass, options);
round_changed |= pass_changed;
if options.print_after_each {
println!("// === {} (after {label}-{round}:{}) ===", module.name, pass.name);
print!("{}", module.to_text());
}
}
if !round_changed {
break;
}
changed = true;
}
changed
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct AnalysisKey(TypeId);
impl AnalysisKey {
pub fn of<T: 'static>() -> Self {
Self(TypeId::of::<T>())
}
}
pub trait AnalysisPass {
type Result: 'static;
fn name(&self) -> &str;
fn run(&self, func: &Function) -> Self::Result;
}
pub trait ModulePass {
fn name(&self) -> &str;
fn run(&mut self, module: &mut Module) -> bool;
}
pub trait FunctionPass {
fn name(&self) -> &str;
fn run_on_function(&mut self, func: &mut Function) -> bool;
}
impl<T: FunctionPass> ModulePass for T {
fn name(&self) -> &str {
FunctionPass::name(self)
}
fn run(&mut self, module: &mut Module) -> bool {
let mut changed = false;
for func in module.functions.iter_mut().filter(|func| !func.blocks.is_empty()) {
changed |= self.run_on_function(func);
}
changed
}
}
#[derive(Default)]
pub struct AnalysisManager {
results: FxHashMap<AnalysisKey, Box<dyn Any>>,
}
impl AnalysisManager {
pub fn new() -> Self {
Self::default()
}
pub fn get<T: 'static>(&self) -> Option<&T> {
let key = AnalysisKey::of::<T>();
self.results.get(&key)?.downcast_ref::<T>()
}
pub fn insert<T: 'static>(&mut self, result: T) {
let key = AnalysisKey::of::<T>();
self.results.insert(key, Box::new(result));
}
pub fn get_or_compute<A: AnalysisPass>(&mut self, analysis: &A, func: &Function) -> &A::Result {
let key = AnalysisKey::of::<A::Result>();
self.results.entry(key).or_insert_with(|| {
let result = analysis.run(func);
Box::new(result)
});
self.results[&key].downcast_ref::<A::Result>().unwrap()
}
pub fn invalidate_all(&mut self) {
self.results.clear();
}
pub fn invalidate<T: 'static>(&mut self) {
let key = AnalysisKey::of::<T>();
self.results.remove(&key);
}
}
pub struct PassManager {
passes: Vec<Box<dyn ModulePass>>,
validate_after_each: bool,
}
impl Default for PassManager {
fn default() -> Self {
Self { passes: Vec::new(), validate_after_each: cfg!(debug_assertions) }
}
}
impl PassManager {
pub fn new() -> Self {
Self::default()
}
pub fn add_pass(&mut self, pass: Box<dyn ModulePass>) {
self.passes.push(pass);
}
pub const fn set_validate_after_each(&mut self, enabled: bool) {
self.validate_after_each = enabled;
}
pub fn run(&mut self, module: &mut Module) -> (AnalysisManager, bool) {
let mut am = AnalysisManager::new();
let mut changed = false;
for pass in &mut self.passes {
let pass_name = pass.name().to_string();
if pass.run(module) {
changed = true;
am.invalidate_all();
}
if self.validate_after_each {
validate_module_after_pass(module, &pass_name);
}
}
(am, changed)
}
}
fn validate_module_after_pass(module: &Module, pass_name: &str) {
let errors = Validator::validate_module(module);
if errors.is_empty() {
return;
}
let mut message = format!("MIR validation failed after `{pass_name}`");
for error in errors {
message.push_str("\n ");
message.push_str(&error.to_string());
}
panic!("{message}");
}
pub struct LivenessAnalysis;
impl AnalysisPass for LivenessAnalysis {
type Result = crate::analysis::Liveness;
fn name(&self) -> &str {
"liveness"
}
fn run(&self, func: &Function) -> Self::Result {
crate::analysis::Liveness::compute(func)
}
}