lift-core 0.4.4

LIFT compiler framework core: unified SSA intermediate representation (IR) for AI and quantum — types, values, operations, blocks, regions, verifier
Documentation
use crate::context::Context;
use std::collections::HashMap;

#[derive(Debug, Clone, PartialEq)]
pub enum PassResult {
    Unchanged,
    Changed,
    RolledBack,
    Error(String),
}

impl PassResult {
    pub fn changed(&self) -> bool {
        matches!(self, PassResult::Changed)
    }

    pub fn rolled_back() -> Self {
        PassResult::RolledBack
    }
}

pub trait Pass: std::fmt::Debug {
    fn name(&self) -> &str;
    fn run(&self, ctx: &mut Context, cache: &mut AnalysisCache) -> PassResult;
    fn invalidates(&self) -> Vec<&str> {
        Vec::new()
    }
}

#[derive(Debug, Default)]
pub struct AnalysisCache {
    entries: HashMap<String, Box<dyn std::any::Any>>,
}

impl AnalysisCache {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn insert<T: 'static>(&mut self, key: &str, value: T) {
        self.entries.insert(key.to_string(), Box::new(value));
    }

    pub fn get<T: 'static>(&self, key: &str) -> Option<&T> {
        self.entries.get(key)?.downcast_ref::<T>()
    }

    pub fn invalidate(&mut self, keys: Vec<&str>) {
        for key in keys {
            self.entries.remove(key);
        }
    }

    pub fn clear(&mut self) {
        self.entries.clear();
    }
}

#[derive(Debug)]
pub struct PassManager {
    passes: Vec<Box<dyn Pass>>,
    cache: AnalysisCache,
}

impl PassManager {
    pub fn new() -> Self {
        Self {
            passes: Vec::new(),
            cache: AnalysisCache::new(),
        }
    }

    pub fn add_pass(&mut self, pass: Box<dyn Pass>) {
        self.passes.push(pass);
    }

    pub fn run_all(&mut self, ctx: &mut Context) -> Vec<(String, PassResult)> {
        let mut results = Vec::new();

        for pass in &self.passes {
            let name = pass.name().to_string();
            let snapshot = ctx.snapshot();
            let result = pass.run(ctx, &mut self.cache);

            if result.changed() {
                self.cache.invalidate(pass.invalidates());
            }

            results.push((name, result));
            let _ = snapshot;
        }

        results
    }

    pub fn num_passes(&self) -> usize {
        self.passes.len()
    }
}

impl Default for PassManager {
    fn default() -> Self {
        Self::new()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[derive(Debug)]
    struct NoOpPass;

    impl Pass for NoOpPass {
        fn name(&self) -> &str {
            "no-op"
        }
        fn run(&self, _ctx: &mut Context, _cache: &mut AnalysisCache) -> PassResult {
            PassResult::Unchanged
        }
    }

    #[test]
    fn test_pass_manager_empty() {
        let mut pm = PassManager::new();
        let mut ctx = Context::new();
        let results = pm.run_all(&mut ctx);
        assert!(results.is_empty());
    }

    #[test]
    fn test_pass_manager_noop() {
        let mut pm = PassManager::new();
        pm.add_pass(Box::new(NoOpPass));
        let mut ctx = Context::new();
        let results = pm.run_all(&mut ctx);
        assert_eq!(results.len(), 1);
        assert_eq!(results[0].1, PassResult::Unchanged);
    }
}