use alloc::rc::Rc;
use core::{
any::{Any, TypeId},
cell::RefCell,
};
use smallvec::SmallVec;
use super::{PassInstrumentor, PassTarget};
use crate::{FxHashMap, Op, Operation, OperationRef, Report, any::AsAny};
pub trait Analysis: Default + AsAny {
type Target: ?Sized + PassTarget;
#[inline]
fn analysis_id(&self) -> TypeId {
TypeId::of::<Self>()
}
fn as_any(&self) -> &dyn Any;
fn as_any_rc(self: Rc<Self>) -> Rc<dyn Any>;
fn name(&self) -> &'static str;
fn analyze(
&mut self,
op: &Self::Target,
analysis_manager: AnalysisManager,
) -> Result<(), Report>;
fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool;
}
pub trait OperationAnalysis {
fn analysis_id(&self) -> TypeId;
fn as_any(&self) -> &dyn Any;
fn as_any_rc(self: Rc<Self>) -> Rc<dyn Any>;
fn name(&self) -> &'static str;
fn analyze(&mut self, op: &OperationRef, am: AnalysisManager) -> Result<(), Report>;
fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool;
}
impl dyn OperationAnalysis {
#[inline]
pub fn downcast<T: 'static>(self: Rc<Self>) -> Option<Rc<T>> {
self.as_any_rc().downcast::<T>().ok()
}
}
impl<A> OperationAnalysis for A
where
A: Analysis,
{
#[inline]
fn analysis_id(&self) -> TypeId {
<A as Analysis>::analysis_id(self)
}
#[inline]
fn as_any(&self) -> &dyn Any {
<A as Analysis>::as_any(self)
}
#[inline]
fn as_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
<A as Analysis>::as_any_rc(self)
}
#[inline]
fn name(&self) -> &'static str {
<A as Analysis>::name(self)
}
#[inline]
fn analyze(&mut self, op: &OperationRef, am: AnalysisManager) -> Result<(), Report> {
let op = <<A as Analysis>::Target as PassTarget>::into_target(op);
<A as Analysis>::analyze(self, &op, am)
}
#[inline]
fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool {
<A as Analysis>::invalidate(self, preserved_analyses)
}
}
#[derive(Default)]
pub struct PreservedAnalyses {
preserved: SmallVec<[TypeId; 8]>,
}
impl PreservedAnalyses {
pub fn preserve_all(&mut self) {
self.insert(AllAnalyses::TYPE_ID);
}
pub fn preserve<A: 'static>(&mut self) {
self.insert(TypeId::of::<A>());
}
pub fn preserve_raw(&mut self, id: TypeId) {
self.insert(id);
}
pub fn is_preserved<A: 'static>(&self) -> bool {
self.preserved.contains(&TypeId::of::<A>()) || self.is_all()
}
pub fn is_preserved_raw(&self, ty: &TypeId) -> bool {
self.preserved.contains(ty) || self.is_all()
}
pub fn unpreserve<A: 'static>(&mut self) {
self.remove(&AllAnalyses::TYPE_ID);
self.remove(&TypeId::of::<A>());
}
pub fn unpreserve_raw(&mut self, ty: &TypeId) {
self.remove(&AllAnalyses::TYPE_ID);
self.remove(ty)
}
pub fn is_all(&self) -> bool {
self.preserved.contains(&AllAnalyses::TYPE_ID)
}
pub fn is_none(&self) -> bool {
self.preserved.is_empty()
}
fn insert(&mut self, id: TypeId) {
match self.preserved.binary_search_by_key(&id, |probe| *probe) {
Ok(index) => {
self.preserved[index] = id;
}
Err(index) => {
self.preserved.insert(index, id);
}
}
}
fn remove(&mut self, id: &TypeId) {
if let Ok(index) = self.preserved.binary_search_by_key(&id, |probe| probe) {
self.preserved.remove(index);
}
}
}
pub struct AllAnalyses;
impl AllAnalyses {
const TYPE_ID: TypeId = TypeId::of::<AllAnalyses>();
}
#[repr(transparent)]
struct AnalysisWrapper<A> {
analysis: A,
}
impl<A: Analysis> AnalysisWrapper<A> {
fn new(op: &<A as Analysis>::Target, am: AnalysisManager) -> Result<Self, Report> {
let mut analysis = A::default();
analysis.analyze(op, am)?;
Ok(Self { analysis })
}
}
impl<A: Default> Default for AnalysisWrapper<A> {
fn default() -> Self {
Self {
analysis: Default::default(),
}
}
}
impl<A: Analysis> Analysis for AnalysisWrapper<A> {
type Target = <A as Analysis>::Target;
#[inline]
fn analysis_id(&self) -> TypeId {
self.analysis.analysis_id()
}
#[inline]
fn as_any(&self) -> &dyn Any {
<A as Analysis>::as_any(&self.analysis)
}
#[inline]
fn as_any_rc(self: Rc<Self>) -> Rc<dyn Any> {
let ptr = Rc::into_raw(self);
<A as Analysis>::as_any_rc(unsafe { Rc::<A>::from_raw(ptr.cast()) })
}
#[inline]
fn name(&self) -> &'static str {
<A as Analysis>::name(&self.analysis)
}
#[inline]
fn analyze(&mut self, op: &Self::Target, am: AnalysisManager) -> Result<(), Report> {
self.analysis.analyze(op, am)
}
fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) -> bool {
let invalidated = self.analysis.invalidate(preserved_analyses);
if invalidated {
preserved_analyses.unpreserve::<A>();
}
invalidated
}
}
#[derive(Clone)]
#[repr(transparent)]
pub struct AnalysisManager {
analyses: Rc<NestedAnalysisMap>,
}
impl AnalysisManager {
pub fn new(op: OperationRef, instrumentor: Option<Rc<PassInstrumentor>>) -> Self {
Self {
analyses: Rc::new(NestedAnalysisMap::new(op, instrumentor)),
}
}
pub fn get_cached_parent_analysis<A>(&self, parent: OperationRef) -> Option<Rc<A>>
where
A: Analysis,
{
let mut current_parent = self.analyses.parent();
while let Some(parent_am) = current_parent.take() {
if parent_am.get_operation() == parent {
return parent_am.analyses().get_cached::<A>();
}
current_parent = parent_am.parent();
}
None
}
pub fn get_analysis<A>(&self) -> Result<Rc<A>, Report>
where
A: Analysis<Target = Operation>,
{
self.get_analysis_for::<A, Operation>()
}
pub fn get_analysis_for<A, O>(&self) -> Result<Rc<A>, Report>
where
A: Analysis<Target = O>,
O: 'static,
{
let op = {
let analysis_map = self.analyses.analyses.borrow();
let cached = analysis_map.get_cached_for::<A, O>();
if let Some(cached) = cached {
return Ok(cached);
}
analysis_map.ir
};
let pi = self.pass_instrumentor();
let am = self.clone();
let ir = <O as PassTarget>::into_target(&op);
let analysis = AnalysisMap::compute_analysis_for::<A, O>(pi, &*ir, &op, am)?;
self.analyses
.analyses
.borrow_mut()
.analyses
.insert(TypeId::of::<A>(), Rc::clone(&analysis) as Rc<dyn OperationAnalysis>);
Ok(analysis)
}
pub fn get_cached_analysis<A>(&self) -> Option<Rc<A>>
where
A: Analysis,
{
self.analyses.analyses().get_cached::<A>()
}
pub fn get_child_analysis<A>(&self, op: OperationRef) -> Result<Rc<A>, Report>
where
A: Analysis<Target = Operation>,
{
self.clone().nest(op).get_analysis::<A>()
}
pub fn get_child_analysis_for<A, O>(&self, op: &O) -> Result<Rc<A>, Report>
where
A: Analysis<Target = O>,
O: Op,
{
self.clone().nest(op.as_operation_ref()).get_analysis_for::<A, O>()
}
pub fn get_cached_child_analysis<A>(&self, child: &OperationRef) -> Option<Rc<A>>
where
A: Analysis,
{
assert!(child.borrow().parent_op().unwrap() == self.analyses.get_operation());
let child_analyses = self.analyses.child_analyses.borrow();
let child_analyses = child_analyses.get(child)?;
let child_analyses = child_analyses.analyses.borrow();
child_analyses.get_cached::<A>()
}
pub fn nest(&self, op: OperationRef) -> AnalysisManager {
let current_op = self.analyses.get_operation();
assert!(
current_op.borrow().is_proper_ancestor_of(&op.borrow()),
"expected valid descendant op"
);
if current_op == op.borrow().parent_op().expect("expected `op` to have a parent") {
return self.nest_immediate(op);
}
let mut ancestors = SmallVec::<[OperationRef; 4]>::default();
let mut next_op = op;
while next_op != current_op {
ancestors.push(next_op);
next_op = next_op.borrow().parent_op().unwrap();
}
let mut manager = self.clone();
while let Some(op) = ancestors.pop() {
manager = manager.nest_immediate(op);
}
manager
}
fn nest_immediate(&self, op: OperationRef) -> AnalysisManager {
use hashbrown::hash_map::Entry;
assert!(
Some(self.analyses.get_operation()) == op.borrow().parent_op(),
"expected immediate child operation"
);
let parent = self.analyses.clone();
let mut child_analyses = self.analyses.child_analyses.borrow_mut();
match child_analyses.entry(op) {
Entry::Vacant(entry) => {
let analyses = entry.insert(Rc::new(parent.nest(op)));
AnalysisManager {
analyses: Rc::clone(analyses),
}
}
Entry::Occupied(entry) => AnalysisManager {
analyses: Rc::clone(entry.get()),
},
}
}
#[inline]
pub fn invalidate(&self, preserved_analyses: &mut PreservedAnalyses) {
Rc::clone(&self.analyses).invalidate(preserved_analyses)
}
#[inline]
pub fn clear(&mut self) {
self.analyses.clear();
}
#[inline]
pub fn defer_clear(&self) -> ResetAnalysesOnDrop {
ResetAnalysesOnDrop {
analyses: self.analyses.clone(),
}
}
#[inline]
pub fn pass_instrumentor(&self) -> Option<Rc<PassInstrumentor>> {
self.analyses.pass_instrumentor()
}
}
#[must_use]
#[doc(hidden)]
pub struct ResetAnalysesOnDrop {
analyses: Rc<NestedAnalysisMap>,
}
impl Drop for ResetAnalysesOnDrop {
fn drop(&mut self) {
self.analyses.clear()
}
}
struct NestedAnalysisMap {
parent: Option<Rc<NestedAnalysisMap>>,
instrumentor: Option<Rc<PassInstrumentor>>,
analyses: RefCell<AnalysisMap>,
child_analyses: RefCell<FxHashMap<OperationRef, Rc<NestedAnalysisMap>>>,
}
impl NestedAnalysisMap {
pub fn new(op: OperationRef, instrumentor: Option<Rc<PassInstrumentor>>) -> Self {
Self {
parent: None,
instrumentor,
analyses: RefCell::new(AnalysisMap::new(op)),
child_analyses: Default::default(),
}
}
pub fn nest(self: Rc<Self>, op: OperationRef) -> Self {
let instrumentor = self.instrumentor.clone();
Self {
parent: Some(self),
instrumentor,
analyses: RefCell::new(AnalysisMap::new(op)),
child_analyses: Default::default(),
}
}
pub fn parent(&self) -> Option<Rc<NestedAnalysisMap>> {
self.parent.clone()
}
pub fn pass_instrumentor(&self) -> Option<Rc<PassInstrumentor>> {
self.instrumentor.clone()
}
#[inline]
pub fn get_operation(&self) -> OperationRef {
self.analyses.borrow().get_operation()
}
fn analyses(&self) -> core::cell::Ref<'_, AnalysisMap> {
self.analyses.borrow()
}
pub fn invalidate(self: Rc<Self>, preserved_analyses: &mut PreservedAnalyses) {
if preserved_analyses.is_all() {
return;
}
self.analyses.borrow_mut().invalidate(preserved_analyses);
if preserved_analyses.is_none() {
self.child_analyses.borrow_mut().clear();
}
let mut to_invalidate = SmallVec::<[Rc<NestedAnalysisMap>; 8]>::from_iter([self]);
while let Some(map) = to_invalidate.pop() {
map.child_analyses.borrow_mut().retain(|_op, nested_analysis_map| {
Rc::clone(nested_analysis_map).invalidate(preserved_analyses);
if nested_analysis_map.child_analyses.borrow().is_empty() {
false
} else {
to_invalidate.push(Rc::clone(nested_analysis_map));
true
}
});
}
}
pub fn clear(&self) {
self.child_analyses.borrow_mut().clear();
self.analyses.borrow_mut().clear();
}
}
struct AnalysisMap {
analyses: FxHashMap<TypeId, Rc<dyn OperationAnalysis>>,
ir: OperationRef,
}
impl AnalysisMap {
pub fn new(ir: OperationRef) -> Self {
Self {
analyses: Default::default(),
ir,
}
}
pub fn get_cached<A>(&self) -> Option<Rc<A>>
where
A: Analysis,
{
self.analyses.get(&TypeId::of::<A>()).cloned().and_then(|a| a.downcast::<A>())
}
pub fn get_cached_for<A, O>(&self) -> Option<Rc<A>>
where
A: Analysis<Target = O>,
O: 'static,
{
self.analyses.get(&TypeId::of::<A>()).cloned().and_then(|a| a.downcast::<A>())
}
fn compute_analysis_for<A, O>(
pi: Option<Rc<PassInstrumentor>>,
ir: &O,
op: &OperationRef,
am: AnalysisManager,
) -> Result<Rc<A>, Report>
where
A: Analysis<Target = O>,
{
let id = TypeId::of::<A>();
if let Some(pi) = pi.as_deref() {
pi.run_before_analysis(core::any::type_name::<A>(), &id, op);
}
let analysis = Self::construct_analysis::<A, O>(am, ir)?;
if let Some(pi) = pi.as_deref() {
pi.run_after_analysis(core::any::type_name::<A>(), &id, op);
}
Ok(analysis.downcast::<A>().unwrap())
}
fn construct_analysis<A, O>(
am: AnalysisManager,
op: &O,
) -> Result<Rc<dyn OperationAnalysis>, Report>
where
A: Analysis<Target = O>,
{
AnalysisWrapper::<A>::new(op, am)
.map(|analysis| Rc::new(analysis) as Rc<dyn OperationAnalysis>)
}
pub fn get_operation(&self) -> OperationRef {
self.ir
}
pub fn clear(&mut self) {
self.analyses.clear();
}
pub fn invalidate(&mut self, preserved_analyses: &mut PreservedAnalyses) {
self.analyses.retain(|_, a| !a.invalidate(preserved_analyses));
}
}
#[cfg(test)]
mod tests {
use super::*;
struct DummyAnalysis;
#[test]
fn preserved_analyses_no_duplicates() {
let mut preserved = PreservedAnalyses::default();
preserved.preserve::<DummyAnalysis>();
preserved.preserve::<DummyAnalysis>();
preserved.preserve::<DummyAnalysis>();
assert_eq!(preserved.preserved.len(), 1);
assert!(preserved.is_preserved::<DummyAnalysis>());
preserved.unpreserve::<DummyAnalysis>();
assert!(!preserved.is_preserved::<DummyAnalysis>());
assert!(preserved.preserved.is_empty());
}
}