use alloc::boxed::Box;
use core::any::Any;
use super::view::FactView;
use super::{Demand, FactProducer, FactTable};
use crate::pass::{AnalysisId, MAX_ANALYSES};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct GroupDesc {
pub id: AnalysisId,
pub name: &'static str,
pub stratum: u8,
pub depends: Demand,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum StrataError {
DuplicateId { id: AnalysisId },
UnregisteredDependency {
group: AnalysisId,
dependency: AnalysisId,
},
NotStrictlyLower {
group: AnalysisId,
dependency: AnalysisId,
},
}
pub const fn check_strata(groups: &[GroupDesc]) -> Result<(), StrataError> {
let mut i = 0;
while let Some((earlier, [group, ..])) = groups.split_at_checked(i) {
let group = *group;
let mut seen = earlier;
while let [previous, tail @ ..] = seen {
if previous.id.index() == group.id.index() {
return Err(StrataError::DuplicateId { id: group.id });
}
seen = tail;
}
let mut index = 0;
while index < MAX_ANALYSES {
let dependency = AnalysisId::new(index);
if group.depends.contains(dependency) {
match stratum_of(groups, dependency) {
None => {
return Err(StrataError::UnregisteredDependency {
group: group.id,
dependency,
});
}
Some(stratum) if stratum >= group.stratum => {
return Err(StrataError::NotStrictlyLower {
group: group.id,
dependency,
});
}
Some(_) => {}
}
}
index += 1;
}
i += 1;
}
Ok(())
}
const fn stratum_of(groups: &[GroupDesc], id: AnalysisId) -> Option<u8> {
let mut rest = groups;
while let [group, tail @ ..] = rest {
if group.id.index() == id.index() {
return Some(group.stratum);
}
rest = tail;
}
None
}
pub(crate) type ErasedTable = Box<dyn Any + Send + Sync>;
pub struct ProducerEntry<A: ?Sized> {
pub desc: GroupDesc,
pub(crate) run: fn(&A, &FactView<'_>) -> ErasedTable,
pub(crate) eq: fn(&(dyn Any + Send + Sync), &(dyn Any + Send + Sync)) -> bool,
}
impl<A: ?Sized> Clone for ProducerEntry<A> {
fn clone(&self) -> Self {
*self
}
}
impl<A: ?Sized> Copy for ProducerEntry<A> {}
impl<A: ?Sized> core::fmt::Debug for ProducerEntry<A> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("ProducerEntry")
.field("desc", &self.desc)
.finish_non_exhaustive()
}
}
impl<A: ?Sized> ProducerEntry<A> {
#[must_use]
pub const fn of<G: FactProducer<A>>() -> Self {
Self {
desc: G::DESC,
run: run_erased::<A, G>,
eq: eq_erased::<A, G>,
}
}
}
fn run_erased<A: ?Sized, G: FactProducer<A>>(artifact: &A, inputs: &FactView<'_>) -> ErasedTable {
Box::new(G::produce(artifact, inputs))
}
fn eq_erased<A: ?Sized, G: FactProducer<A>>(
left: &(dyn Any + Send + Sync),
right: &(dyn Any + Send + Sync),
) -> bool {
match (
left.downcast_ref::<FactTable<G>>(),
right.downcast_ref::<FactTable<G>>(),
) {
(Some(left), Some(right)) => left == right,
_ => false,
}
}
pub struct FactRegistry<A: ?Sized + 'static> {
producers: &'static [ProducerEntry<A>],
}
impl<A: ?Sized + 'static> FactRegistry<A> {
#[must_use]
pub const fn new(producers: &'static [ProducerEntry<A>]) -> Self {
assert!(
producers.len() <= MAX_ANALYSES as usize,
"a registry holds at most MAX_ANALYSES groups"
);
let mut descs = [GroupDesc {
id: AnalysisId::new(0),
name: "",
stratum: 0,
depends: Demand::NONE,
}; MAX_ANALYSES as usize];
let mut slots: &mut [GroupDesc] = &mut descs;
let mut entries = producers;
while let ([slot, slots_tail @ ..], [entry, entries_tail @ ..]) = (slots, entries) {
*slot = entry.desc;
slots = slots_tail;
entries = entries_tail;
}
let registered = match descs.split_at_checked(producers.len()) {
Some((registered, _)) => registered,
None => &descs,
};
let strata = check_strata(registered);
assert!(
!matches!(strata, Err(StrataError::DuplicateId { .. })),
"fact registry: two producers register the same group id"
);
assert!(
!matches!(strata, Err(StrataError::UnregisteredDependency { .. })),
"fact registry: a group depends on a group no producer computes"
);
assert!(
!matches!(strata, Err(StrataError::NotStrictlyLower { .. })),
"fact registry: a group depends on a group in its own or a higher stratum \
(a demand cycle is unrepresentable)"
);
Self { producers }
}
#[must_use]
pub const fn producers(&self) -> &'static [ProducerEntry<A>] {
self.producers
}
#[must_use]
pub fn entry(&self, id: AnalysisId) -> Option<&'static ProducerEntry<A>> {
self.producers.iter().find(|entry| entry.desc.id == id)
}
#[must_use]
pub fn registered(&self) -> Demand {
self.producers
.iter()
.fold(Demand::NONE, |set, entry| set.with(entry.desc.id))
}
#[must_use]
pub fn closure(&self, demand: Demand) -> Demand {
let mut closed = demand;
loop {
let next = closed.iter().fold(closed, |set, id| match self.entry(id) {
Some(entry) => set.union(entry.desc.depends),
None => set,
});
if next == closed {
return closed;
}
closed = next;
}
}
}