use std::sync::{
atomic::{AtomicBool, Ordering},
Arc,
};
use log::debug;
use rayon::prelude::*;
use crate::{
compiler::{context::CompilerContext, pass::SsaPass},
CilObject, Result,
};
#[allow(clippy::struct_field_names)]
pub struct PassScheduler {
max_iterations: usize,
stable_iterations: usize,
max_phase_iterations: usize,
pub structure: Vec<Box<dyn SsaPass>>,
pub value: Vec<Box<dyn SsaPass>>,
pub simplify: Vec<Box<dyn SsaPass>>,
pub inline: Vec<Box<dyn SsaPass>>,
pub normalize: Vec<Box<dyn SsaPass>>,
}
impl Default for PassScheduler {
fn default() -> Self {
Self::new(5, 2, 15)
}
}
impl PassScheduler {
#[must_use]
pub fn new(
max_iterations: usize,
stable_iterations: usize,
max_phase_iterations: usize,
) -> Self {
Self {
max_iterations,
stable_iterations,
max_phase_iterations,
structure: Vec::new(),
value: Vec::new(),
simplify: Vec::new(),
inline: Vec::new(),
normalize: Vec::new(),
}
}
fn normalize_to_fixpoint(
ctx: &CompilerContext,
passes: &mut [Box<dyn SsaPass>],
max_phase_iterations: usize,
assembly: &Arc<CilObject>,
) -> Result<bool> {
let mut any_changed = false;
for _ in 0..max_phase_iterations {
let changed = Self::run_passes_once(ctx, passes, assembly)?;
if !changed {
break;
}
any_changed = true;
}
Ok(any_changed)
}
fn phase_to_fixpoint(
ctx: &CompilerContext,
phase_passes: &mut [Box<dyn SsaPass>],
normalize_passes: &mut [Box<dyn SsaPass>],
max_phase_iterations: usize,
assembly: &Arc<CilObject>,
) -> Result<bool> {
if phase_passes.is_empty() {
return Ok(false);
}
let mut phase_changed = false;
for _ in 0..max_phase_iterations {
let pass_changed = Self::run_passes_once(ctx, phase_passes, assembly)?;
if !pass_changed {
break;
}
phase_changed = true;
if !normalize_passes.is_empty() {
Self::normalize_to_fixpoint(ctx, normalize_passes, max_phase_iterations, assembly)?;
}
}
Ok(phase_changed)
}
fn run_passes_once(
ctx: &CompilerContext,
passes: &mut [Box<dyn SsaPass>],
assembly: &Arc<CilObject>,
) -> Result<bool> {
let any_changed = AtomicBool::new(false);
for pass in passes.iter_mut() {
pass.initialize(ctx)?;
}
for pass in passes.iter() {
if pass.is_global() && pass.run_global(ctx, assembly)? {
any_changed.store(true, Ordering::Relaxed);
}
}
let method_order: Vec<_> = {
let topo = ctx.methods_reverse_topological();
if topo.is_empty() {
ctx.all_methods().collect()
} else {
topo
}
};
let methods_with_ssa: Vec<_> = method_order
.into_iter()
.filter(|token| ctx.ssa_functions.contains_key(token))
.collect();
for pass in passes.iter() {
if pass.is_global() {
continue;
}
methods_with_ssa.par_iter().for_each(|&method_token| {
if !pass.should_run(method_token, ctx) {
return;
}
let Some((_, mut ssa)) = ctx.ssa_functions.remove(&method_token) else {
return;
};
let result = pass.run_on_method(&mut ssa, method_token, ctx, assembly);
if let Ok(true) = result {
ssa.rebuild_ssa();
}
ctx.ssa_functions.insert(method_token, ssa);
if let Ok(true) = result {
any_changed.store(true, Ordering::Relaxed);
ctx.processed_methods.insert(method_token);
}
});
}
for pass in passes.iter_mut() {
pass.finalize(ctx)?;
}
Ok(any_changed.load(Ordering::Relaxed))
}
pub fn run_pipeline(
&mut self,
ctx: &CompilerContext,
assembly: &Arc<CilObject>,
) -> Result<usize> {
let mut stable_count = 0;
let mut iterations = 0;
let max_phase = self.max_phase_iterations;
let max_iterations = self.max_iterations;
let stable_iterations = self.stable_iterations;
for iteration in 0..max_iterations {
iterations = iteration + 1;
debug!("Pipeline iteration {}/{}", iterations, max_iterations);
let mut iteration_changed = false;
if Self::phase_to_fixpoint(
ctx,
&mut self.structure,
&mut self.normalize,
max_phase,
assembly,
)? {
iteration_changed = true;
}
if Self::phase_to_fixpoint(
ctx,
&mut self.value,
&mut self.normalize,
max_phase,
assembly,
)? {
iteration_changed = true;
}
if Self::phase_to_fixpoint(
ctx,
&mut self.simplify,
&mut self.normalize,
max_phase,
assembly,
)? {
iteration_changed = true;
}
if Self::phase_to_fixpoint(
ctx,
&mut self.inline,
&mut self.normalize,
max_phase,
assembly,
)? {
iteration_changed = true;
}
if iteration == 0 && !iteration_changed && !self.normalize.is_empty() {
iteration_changed =
Self::normalize_to_fixpoint(ctx, &mut self.normalize, max_phase, assembly)?;
}
if iteration_changed {
stable_count = 0;
} else {
stable_count += 1;
if stable_count >= stable_iterations {
debug!("Pipeline stable after {} iterations", iterations);
break;
}
}
}
Ok(iterations)
}
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use crate::{
analysis::SsaFunction,
compiler::{context::CompilerContext, pass::SsaPass, EventKind, PassScheduler},
metadata::token::Token,
CilObject, Result,
};
struct TestPass {
name: &'static str,
changes_to_make: usize,
}
impl TestPass {
fn new(name: &'static str, changes: usize) -> Self {
Self {
name,
changes_to_make: changes,
}
}
}
impl SsaPass for TestPass {
fn name(&self) -> &'static str {
self.name
}
fn run_on_method(
&self,
_ssa: &mut SsaFunction,
method_token: Token,
ctx: &CompilerContext,
_assembly: &Arc<CilObject>,
) -> Result<bool> {
for i in 0..self.changes_to_make {
ctx.events
.record(EventKind::ConstantFolded)
.at(method_token, i)
.message("test");
}
Ok(self.changes_to_make > 0)
}
}
#[test]
fn test_scheduler_iteration_limits() {
let scheduler = PassScheduler::new(10, 3, 5);
assert_eq!(scheduler.max_iterations, 10);
assert_eq!(scheduler.stable_iterations, 3);
assert_eq!(scheduler.max_phase_iterations, 5);
}
#[test]
fn test_default_scheduler() {
let scheduler = PassScheduler::default();
assert_eq!(scheduler.max_iterations, 5);
assert_eq!(scheduler.stable_iterations, 2);
assert_eq!(scheduler.max_phase_iterations, 15);
}
#[test]
fn test_pass_names() {
let passes: Vec<Box<dyn SsaPass>> = vec![
Box::new(TestPass::new("pass1", 0)),
Box::new(TestPass::new("pass2", 0)),
];
assert_eq!(passes.len(), 2);
assert_eq!(passes[0].name(), "pass1");
assert_eq!(passes[1].name(), "pass2");
}
}