mod call_graph;
mod control_dep_graph;
mod control_flow_graph;
mod dominator_tree;
mod functions_by_type;
pub use crate::call_graph::CallGraph;
pub use crate::control_dep_graph::ControlDependenceGraph;
pub use crate::control_flow_graph::{CFGNode, ControlFlowGraph};
pub use crate::dominator_tree::{DominatorTree, PostDominatorTree};
pub use crate::functions_by_type::FunctionsByType;
use llvm_ir::{Function, Module};
use log::debug;
use std::cell::{Ref, RefCell};
use std::collections::HashMap;
pub use llvm_ir;
pub struct ModuleAnalysis<'m> {
module: &'m Module,
call_graph: SimpleCache<CallGraph<'m>>,
functions_by_type: SimpleCache<FunctionsByType<'m>>,
fn_analyses: HashMap<&'m str, FunctionAnalysis<'m>>,
}
impl<'m> ModuleAnalysis<'m> {
pub fn new(module: &'m Module) -> Self {
Self {
module,
call_graph: SimpleCache::new(),
functions_by_type: SimpleCache::new(),
fn_analyses: module
.functions
.iter()
.map(|f| (f.name.as_str(), FunctionAnalysis::new(f)))
.collect(),
}
}
pub fn module(&self) -> &'m Module {
self.module
}
pub fn call_graph(&self) -> Ref<CallGraph<'m>> {
self.call_graph.get_or_insert_with(|| {
let functions_by_type = self.functions_by_type();
debug!("computing single-module call graph");
CallGraph::new(std::iter::once(self.module), &functions_by_type)
})
}
pub fn functions_by_type(&self) -> Ref<FunctionsByType<'m>> {
self.functions_by_type.get_or_insert_with(|| {
debug!("computing single-module functions-by-type");
FunctionsByType::new(std::iter::once(self.module))
})
}
pub fn fn_analysis<'s>(&'s self, func_name: &str) -> &'s FunctionAnalysis<'m> {
self.fn_analyses
.get(func_name)
.unwrap_or_else(|| panic!("Function named {:?} not found in the Module", func_name))
}
}
pub struct CrossModuleAnalysis<'m> {
modules: Vec<&'m Module>,
call_graph: SimpleCache<CallGraph<'m>>,
functions_by_type: SimpleCache<FunctionsByType<'m>>,
module_analyses: HashMap<&'m str, ModuleAnalysis<'m>>,
}
impl<'m> CrossModuleAnalysis<'m> {
pub fn new(modules: impl IntoIterator<Item = &'m Module>) -> Self {
let modules: Vec<&'m Module> = modules.into_iter().collect();
let module_analyses = modules
.iter()
.copied()
.map(|m| (m.name.as_str(), ModuleAnalysis::new(m)))
.collect();
Self {
modules,
call_graph: SimpleCache::new(),
functions_by_type: SimpleCache::new(),
module_analyses,
}
}
pub fn modules<'s>(&'s self) -> impl Iterator<Item = &'m Module> + 's {
self.modules.iter().copied()
}
pub fn functions<'s>(&'s self) -> impl Iterator<Item = &'m Function> + 's {
self.modules().map(|m| m.functions.iter()).flatten()
}
pub fn call_graph(&self) -> Ref<CallGraph<'m>> {
self.call_graph.get_or_insert_with(|| {
let functions_by_type = self.functions_by_type();
debug!("computing multi-module call graph");
CallGraph::new(self.modules(), &functions_by_type)
})
}
pub fn functions_by_type(&self) -> Ref<FunctionsByType<'m>> {
self.functions_by_type.get_or_insert_with(|| {
debug!("computing multi-module functions-by-type");
FunctionsByType::new(self.modules())
})
}
pub fn module_analysis<'s>(&'s self, mod_name: &str) -> &'s ModuleAnalysis<'m> {
self.module_analyses.get(mod_name).unwrap_or_else(|| {
panic!(
"Module named {:?} not found in the CrossModuleAnalysis",
mod_name
)
})
}
pub fn get_func_by_name(&self, func_name: &str) -> Option<(&'m Function, &'m Module)> {
let mut retval = None;
for &module in &self.modules {
if let Some(func) = module.get_func_by_name(func_name) {
match retval {
None => retval = Some((func, module)),
Some((_, retmod)) => panic!("Multiple functions found with name {:?}: one in module {:?}, another in module {:?}", func_name, &retmod.name, &module.name),
}
}
}
retval
}
}
pub struct FunctionAnalysis<'m> {
function: &'m Function,
control_flow_graph: SimpleCache<ControlFlowGraph<'m>>,
dominator_tree: SimpleCache<DominatorTree<'m>>,
postdominator_tree: SimpleCache<PostDominatorTree<'m>>,
control_dep_graph: SimpleCache<ControlDependenceGraph<'m>>,
}
impl<'m> FunctionAnalysis<'m> {
pub fn new(function: &'m Function) -> Self {
Self {
function,
control_flow_graph: SimpleCache::new(),
dominator_tree: SimpleCache::new(),
postdominator_tree: SimpleCache::new(),
control_dep_graph: SimpleCache::new(),
}
}
pub fn control_flow_graph(&self) -> Ref<ControlFlowGraph<'m>> {
self.control_flow_graph.get_or_insert_with(|| {
debug!("computing control flow graph for {}", &self.function.name);
ControlFlowGraph::new(self.function)
})
}
pub fn dominator_tree(&self) -> Ref<DominatorTree<'m>> {
self.dominator_tree.get_or_insert_with(|| {
let cfg = self.control_flow_graph();
debug!("computing dominator tree for {}", &self.function.name);
DominatorTree::new(&cfg)
})
}
pub fn postdominator_tree(&self) -> Ref<PostDominatorTree<'m>> {
self.postdominator_tree.get_or_insert_with(|| {
let cfg = self.control_flow_graph();
debug!("computing postdominator tree for {}", &self.function.name);
PostDominatorTree::new(&cfg)
})
}
pub fn control_dependence_graph(&self) -> Ref<ControlDependenceGraph<'m>> {
self.control_dep_graph.get_or_insert_with(|| {
let cfg = self.control_flow_graph();
let postdomtree = self.postdominator_tree();
debug!(
"computing control dependence graph for {}",
&self.function.name
);
ControlDependenceGraph::new(&cfg, &postdomtree)
})
}
}
struct SimpleCache<T> {
data: RefCell<Option<T>>,
}
impl<T> SimpleCache<T> {
fn new() -> Self {
Self {
data: RefCell::new(None),
}
}
fn get_or_insert_with(&self, f: impl FnOnce() -> T) -> Ref<T> {
let need_mutable_borrow = self.data.borrow().is_none();
if need_mutable_borrow {
let old_val = self.data.borrow_mut().replace(f());
debug_assert!(old_val.is_none());
}
Ref::map(self.data.borrow(), |o| {
o.as_ref().expect("should be populated now")
})
}
}