mod common;
use std::{
collections::{BTreeMap, BTreeSet},
sync::Mutex,
};
use analyssa::{
events::EventLog,
host::{DirtySet, SsaStore},
ir::{function::SsaFunction, ops::SsaOp, variable::SsaVarId},
passes::{
AlgebraicSimplificationPass, BlockMergingPass, ControlFlowSimplificationPass,
CopyPropagationPass, DeadCodeEliminationPass, DeadMethodEliminationPass,
GlobalValueNumberingPass, JumpThreadingPass, LicmPass, LoopCanonicalizationPass,
OpaquePredicatePass, ReassociationPass, StrengthReductionPass, ValueRangePropagationPass,
},
scheduling::{ModificationScope, PassScheduler, SsaPass, SsaPassHost},
testing::{self, MockTarget},
world::World,
Error, PipelineConfig, Result,
};
use common::assert_valid_full;
fn pass_name(pass: &dyn SsaPass<MockTarget, MockHost>) -> &'static str {
pass.name()
}
fn pass_scope(pass: &dyn SsaPass<MockTarget, MockHost>) -> ModificationScope {
pass.modification_scope()
}
fn pass_repairs_ssa(pass: &dyn SsaPass<MockTarget, MockHost>) -> bool {
pass.repairs_ssa()
}
fn pass_is_global(pass: &dyn SsaPass<MockTarget, MockHost>) -> bool {
pass.is_global()
}
fn some_or_abort<T>(value: Option<T>) -> T {
value.unwrap_or_else(|| std::process::abort())
}
fn result_or_abort<T>(result: Result<T>) -> T {
result.unwrap_or_else(|_| std::process::abort())
}
fn err_or_abort<T>(result: Result<T>) -> Error {
match result {
Ok(_) => std::process::abort(),
Err(error) => error,
}
}
#[derive(Default)]
struct MockHost {
ssa: Mutex<BTreeMap<u32, SsaFunction<MockTarget>>>,
dirty: Mutex<BTreeSet<u32>>,
processed: Mutex<BTreeSet<u32>>,
events: EventLog<MockTarget>,
}
impl World<MockTarget> for MockHost {
fn all_methods(&self) -> Vec<u32> {
self.iter_methods()
}
fn entry_points(&self) -> Vec<u32> {
self.iter_methods()
}
fn callees(&self, _method: &u32) -> Vec<u32> {
Vec::new()
}
fn is_dead(&self, _method: &u32) -> bool {
false
}
fn mark_dead(&self, _method: &u32) {}
}
impl SsaStore<MockTarget> for MockHost {
fn contains(&self, method: &u32) -> bool {
self.ssa
.lock()
.unwrap_or_else(|_| std::process::abort())
.contains_key(method)
}
fn take_ssa(&self, method: &u32) -> Option<SsaFunction<MockTarget>> {
self.ssa
.lock()
.unwrap_or_else(|_| std::process::abort())
.remove(method)
}
fn insert_ssa(&self, method: u32, ssa: SsaFunction<MockTarget>) {
self.ssa
.lock()
.unwrap_or_else(|_| std::process::abort())
.insert(method, ssa);
}
fn clone_ssa(&self, method: &u32) -> Option<SsaFunction<MockTarget>> {
self.ssa
.lock()
.unwrap_or_else(|_| std::process::abort())
.get(method)
.cloned()
}
fn iter_methods(&self) -> Vec<u32> {
self.ssa
.lock()
.unwrap_or_else(|_| std::process::abort())
.keys()
.copied()
.collect()
}
}
impl DirtySet<MockTarget> for MockHost {
fn mark_dirty(&self, method: &u32) {
self.dirty
.lock()
.unwrap_or_else(|_| std::process::abort())
.insert(*method);
}
fn is_dirty(&self, method: &u32) -> bool {
self.dirty
.lock()
.unwrap_or_else(|_| std::process::abort())
.contains(method)
}
fn dirty_snapshot(&self) -> Vec<u32> {
self.dirty
.lock()
.unwrap_or_else(|_| std::process::abort())
.iter()
.copied()
.collect()
}
fn clear_dirty_for(&self, method: &u32) {
self.dirty
.lock()
.unwrap_or_else(|_| std::process::abort())
.remove(method);
}
fn mark_processed(&self, method: &u32) {
self.processed
.lock()
.unwrap_or_else(|_| std::process::abort())
.insert(*method);
}
fn is_processed(&self, method: &u32) -> bool {
self.processed
.lock()
.unwrap_or_else(|_| std::process::abort())
.contains(method)
}
}
impl SsaPassHost<MockTarget> for MockHost {
fn events(&self) -> &EventLog<MockTarget> {
&self.events
}
}
struct BreakingPass;
impl SsaPass<MockTarget, MockHost> for BreakingPass {
fn name(&self) -> &'static str {
"breaking"
}
fn run_on_method(
&self,
ssa: &mut SsaFunction<MockTarget>,
_method: &u32,
_host: &MockHost,
) -> Result<bool> {
ssa.replace_instruction_op(
0,
1,
SsaOp::Return {
value: Some(SsaVarId::from_index(99)),
},
);
Ok(true)
}
fn modification_scope(&self) -> ModificationScope {
ModificationScope::InstructionsOnly
}
}
struct FailingPass;
impl SsaPass<MockTarget, MockHost> for FailingPass {
fn name(&self) -> &'static str {
"failing"
}
fn run_on_method(
&self,
_ssa: &mut SsaFunction<MockTarget>,
_method: &u32,
_host: &MockHost,
) -> Result<bool> {
Err(Error::new("pass failed"))
}
}
#[test]
fn default_pipeline_config_registers_all_builtin_passes() {
let config = PipelineConfig::default();
let scheduler = PassScheduler::<MockTarget, MockHost>::new(config);
assert_eq!(scheduler.normalize_count(), 3);
assert_eq!(scheduler.pass_count(), 11);
let without_global = PipelineConfig {
include_dead_method_elimination: false,
..PipelineConfig::default()
};
let scheduler = PassScheduler::<MockTarget, MockHost>::new(without_global);
assert_eq!(scheduler.normalize_count(), 3);
assert_eq!(scheduler.pass_count(), 10);
}
#[test]
fn verify_hard_reports_invalid_pass_output_and_rolls_back() {
let host = MockHost::default();
let method = 1u32;
let original = testing::const_i32_return(7);
host.insert_ssa(method, original.clone());
let config = PipelineConfig {
include_dead_method_elimination: false,
verify_hard: true,
max_iterations: 1,
..PipelineConfig::default()
};
let mut scheduler = PassScheduler::<MockTarget, MockHost>::new(config);
scheduler.add_at_layer(Box::new(BreakingPass), 0);
let error = err_or_abort(scheduler.run_pipeline(&host));
assert!(error.to_string().contains("breaking"));
assert!(error.to_string().contains("invalid SSA"));
let stored = some_or_abort(host.clone_ssa(&method));
assert_eq!(format!("{stored}"), format!("{original}"));
}
#[test]
fn scheduler_propagates_pass_errors() {
let host = MockHost::default();
let method = 1u32;
host.insert_ssa(method, testing::const_i32_return(7));
let config = PipelineConfig {
include_dead_method_elimination: false,
max_iterations: 1,
..PipelineConfig::default()
};
let mut scheduler = PassScheduler::<MockTarget, MockHost>::new(config);
scheduler.add_at_layer(Box::new(FailingPass), 0);
let error = err_or_abort(scheduler.run_pipeline(&host));
assert!(error.to_string().contains("failing"));
assert!(error.to_string().contains("pass failed"));
}
#[test]
fn default_scheduler_verify_hard_handles_mixed_builder_fixtures() {
let host = MockHost::default();
host.insert_ssa(1, testing::scalar_rewrite_fixture());
host.insert_ssa(2, testing::diamond_phi_fixture());
host.insert_ssa(3, testing::memory_effect_fixture());
host.insert_ssa(4, testing::native_effect_fixture());
host.insert_ssa(5, testing::vector_simd_fixture());
let config = PipelineConfig {
include_dead_method_elimination: false,
verify_hard: true,
max_iterations: 2,
max_phase_iterations: 3,
..PipelineConfig::default()
};
let mut scheduler = PassScheduler::<MockTarget, MockHost>::new(config);
let changes = result_or_abort(scheduler.run_pipeline(&host));
assert!(changes > 0, "mixed fixtures should expose scheduler work");
for method in host.iter_methods() {
let ssa = some_or_abort(host.clone_ssa(&method));
assert_valid_full(&ssa, &format!("method {method} after scheduler"));
}
}
#[test]
fn built_in_pass_wrappers_report_expected_metadata() {
let instructions_only = ModificationScope::InstructionsOnly;
let uses_only = ModificationScope::UsesOnly;
let cfg = ModificationScope::CfgModifying;
let algebraic = AlgebraicSimplificationPass::new();
assert_eq!(pass_name(&algebraic), "algebraic-simplification");
assert_eq!(pass_scope(&algebraic), instructions_only);
assert!(pass_repairs_ssa(&algebraic));
assert!(!pass_is_global(&algebraic));
let blockmerge = BlockMergingPass::new(3);
assert_eq!(pass_name(&blockmerge), "block-merging");
assert_eq!(pass_scope(&blockmerge), cfg);
assert!(pass_repairs_ssa(&blockmerge));
assert_eq!(blockmerge.max_iterations, 3);
let controlflow = ControlFlowSimplificationPass::new(4);
assert_eq!(pass_name(&controlflow), "control-flow-simplification");
assert_eq!(pass_scope(&controlflow), cfg);
assert!(pass_repairs_ssa(&controlflow));
assert_eq!(controlflow.max_iterations, 4);
let copying = CopyPropagationPass::new(5);
assert_eq!(pass_name(©ing), "copy-propagation");
assert_eq!(pass_scope(©ing), instructions_only);
assert!(pass_repairs_ssa(©ing));
assert_eq!(copying.max_iterations, 5);
let dce = DeadCodeEliminationPass::new(6);
assert_eq!(pass_name(&dce), "dead-code-elimination");
assert_eq!(pass_scope(&dce), instructions_only);
assert_eq!(dce.max_iterations, 6);
let global_dce = DeadMethodEliminationPass;
assert_eq!(pass_name(&global_dce), "dead-method-elimination");
assert!(pass_is_global(&global_dce));
let gvn = GlobalValueNumberingPass::new();
assert_eq!(pass_name(&gvn), "global-value-numbering");
assert_eq!(pass_scope(&gvn), uses_only);
let licm = LicmPass::new();
assert_eq!(pass_name(&licm), "licm");
assert_eq!(pass_scope(&licm), cfg);
assert!(pass_repairs_ssa(&licm));
let loopcanon = LoopCanonicalizationPass::new();
assert_eq!(pass_name(&loopcanon), "loop-canonicalization");
assert_eq!(pass_scope(&loopcanon), cfg);
assert!(pass_repairs_ssa(&loopcanon));
let predicates = OpaquePredicatePass::<MockTarget>::new();
assert_eq!(pass_name(&predicates), "opaque-predicate");
assert_eq!(pass_scope(&predicates), cfg);
assert!(pass_repairs_ssa(&predicates));
let ranges = ValueRangePropagationPass::new(7);
assert_eq!(pass_name(&ranges), "value-range-propagation");
assert_eq!(pass_scope(&ranges), cfg);
assert!(pass_repairs_ssa(&ranges));
assert_eq!(ranges.max_iterations, 7);
let reassociation = ReassociationPass::new();
assert_eq!(pass_name(&reassociation), "reassociation");
assert_eq!(pass_scope(&reassociation), instructions_only);
assert!(pass_repairs_ssa(&reassociation));
let strength = StrengthReductionPass;
assert_eq!(pass_name(&strength), "strength-reduction");
assert_eq!(pass_scope(&strength), instructions_only);
assert!(pass_repairs_ssa(&strength));
let threading = JumpThreadingPass::new();
assert_eq!(pass_name(&threading), "jump-threading");
assert_eq!(pass_scope(&threading), cfg);
assert!(pass_repairs_ssa(&threading));
}