use crate::chunkvec::ChunkU32;
use crate::cvec::CompressedU32;
use crate::report::UnreachableGarbageRoot;
use std::collections::HashMap;
pub const GARBAGE_ROOTS_CAP: usize = 10;
pub const GARBAGE_ROOT_DEPTH: usize = 6;
pub const GARBAGE_ROOT_FAN: usize = 8;
#[derive(Debug, Default, Clone)]
pub struct UnreachableRetained {
pub retained_by_class: HashMap<u32, u64>,
pub total: u64,
pub garbage_roots: Vec<UnreachableGarbageRoot>,
}
pub fn compute_unreachable_retained(
n: usize,
dfn: &[u32],
fwd_off: &[u32],
fwd_tgt: &ChunkU32,
shallow_c: &CompressedU32,
class_idx_c: &CompressedU32,
class_count: usize,
class_obj_class_idx: &HashMap<u32, u32>,
class_names: &[String],
) -> std::io::Result<Option<UnreachableRetained>> {
let undef = u32::MAX;
let mut orig: Vec<u32> = Vec::new(); for (node, &d) in dfn.iter().take(n).enumerate() {
if d == undef {
orig.push(node as u32);
}
}
let u = orig.len();
if u == 0 {
return Ok(None);
}
let dense_of = |node: u32| -> Option<u32> { orig.binary_search(&node).ok().map(|i| i as u32) };
let mut u_shallow = vec![0u32; u];
let mut total: u64 = 0;
{
let mut di_cursor = 0usize;
let mut node: usize = 0;
shallow_c.for_each_u32(|s| {
if di_cursor < u && orig[di_cursor] as usize == node {
u_shallow[di_cursor] = s;
total += s as u64;
di_cursor += 1;
}
node += 1;
})?;
}
let mut u_class = vec![undef; u];
{
let mut di_cursor = 0usize;
let mut node: usize = 0;
class_idx_c.for_each_u32(|c| {
if di_cursor < u && orig[di_cursor] as usize == node {
u_class[di_cursor] = c;
di_cursor += 1;
}
node += 1;
})?;
}
let mut sub_off = vec![0u32; u + 1];
for di in 0..u {
let node = orig[di] as usize;
let lo = fwd_off[node] as usize;
let hi = fwd_off[node + 1] as usize;
let mut deg = 0u32;
for pos in lo..hi {
let tgt = fwd_tgt.get(pos);
if (tgt as usize) < n && dfn[tgt as usize] == undef {
deg += 1;
}
}
sub_off[di + 1] = deg;
}
for i in 0..u {
sub_off[i + 1] += sub_off[i];
}
let total_edges = sub_off[u] as usize;
let mut sub_tgt = vec![0u32; total_edges];
{
let mut cursor = sub_off.clone();
for di in 0..u {
let node = orig[di] as usize;
let lo = fwd_off[node] as usize;
let hi = fwd_off[node + 1] as usize;
for pos in lo..hi {
let tgt = fwd_tgt.get(pos);
if (tgt as usize) < n && dfn[tgt as usize] == undef {
if let Some(dt) = dense_of(tgt) {
sub_tgt[cursor[di] as usize] = dt;
cursor[di] += 1;
}
}
}
}
}
let mut has_pred = crate::bitset::Bitset::with_len(u);
for &t in &sub_tgt {
has_pred.set(t as usize);
}
let root = u as u32;
let idom = compute_idom_chk(u, &sub_off, &sub_tgt, &has_pred, root);
let (dc_off, dc_tgt) = build_children_csr(u, &idom, root);
let retained = fold_retained(u, &u_shallow, &dc_off, &dc_tgt, root);
let mut retained_by_class: HashMap<u32, u64> = HashMap::new();
let has_same = same_class_ancestor(u, &dc_off, &dc_tgt, &u_class, class_count, root);
for di in 0..u {
if has_same.get(di) {
continue;
}
let node = orig[di];
let repr = class_obj_class_idx.get(&node).copied();
let ci = match repr {
Some(c) if (c as usize) < class_count => c,
_ => {
let c = u_class[di];
if (c as usize) < class_count {
c
} else {
continue;
}
}
};
*retained_by_class.entry(ci).or_insert(0) += retained[di];
}
Ok(Some(UnreachableRetained {
retained_by_class,
total,
garbage_roots: build_garbage_root_trees(
u,
&idom,
&has_pred,
&retained,
&dc_off,
&dc_tgt,
&u_class,
class_names,
root,
),
}))
}
fn build_garbage_root_trees(
u: usize,
idom: &[u32],
has_pred: &crate::bitset::Bitset,
retained: &[u64],
dc_off: &[u32],
dc_tgt: &[u32],
u_class: &[u32],
class_names: &[String],
root: u32,
) -> Vec<UnreachableGarbageRoot> {
let mut subtree_objects = vec![1u64; u]; {
let mut queue: std::collections::VecDeque<u32> = (0..u as u32)
.filter(|&n| !has_pred.get(n as usize))
.collect();
let mut bfs_order: Vec<u32> = Vec::with_capacity(u);
while let Some(node) = queue.pop_front() {
bfs_order.push(node);
for pos in dc_off[node as usize]..dc_off[node as usize + 1] {
queue.push_back(dc_tgt[pos as usize]);
}
}
for &node in bfs_order.iter().rev() {
let parent = idom[node as usize];
if parent != root && parent != u32::MAX && (parent as usize) < u {
subtree_objects[parent as usize] += subtree_objects[node as usize];
}
}
}
let make_node = |di: u32, depth: usize| -> UnreachableGarbageRoot {
let _ = (di, depth); UnreachableGarbageRoot::default()
};
let _ = make_node;
fn build_node(
di: u32,
depth: usize,
dc_off: &[u32],
dc_tgt: &[u32],
retained: &[u64],
subtree_objects: &[u64],
u_class: &[u32],
class_names: &[String],
) -> UnreachableGarbageRoot {
let ci = u_class[di as usize] as usize;
let pretty_class = if ci < class_names.len() {
crate::report::pretty_class_name(&class_names[ci])
} else {
"<unknown>".to_string()
};
let children = if depth + 1 < GARBAGE_ROOT_DEPTH {
let lo = dc_off[di as usize] as usize;
let hi = dc_off[di as usize + 1] as usize;
let mut kids: Vec<u32> = dc_tgt[lo..hi].to_vec();
kids.sort_unstable_by(|&a, &b| retained[b as usize].cmp(&retained[a as usize]));
kids.truncate(GARBAGE_ROOT_FAN);
kids.iter()
.map(|&child| {
build_node(
child,
depth + 1,
dc_off,
dc_tgt,
retained,
subtree_objects,
u_class,
class_names,
)
})
.collect()
} else {
vec![]
};
UnreachableGarbageRoot {
pretty_class,
retained: retained[di as usize],
objects: subtree_objects[di as usize],
children,
}
}
let mut roots: Vec<u32> = (0..u as u32)
.filter(|&n| !has_pred.get(n as usize))
.collect();
roots.sort_unstable_by(|&a, &b| retained[b as usize].cmp(&retained[a as usize]));
roots.truncate(GARBAGE_ROOTS_CAP);
roots
.iter()
.map(|&di| {
build_node(
di,
0,
dc_off,
dc_tgt,
retained,
&subtree_objects,
u_class,
class_names,
)
})
.collect()
}
fn compute_idom_chk(
u: usize,
sub_off: &[u32],
sub_tgt: &[u32],
has_pred: &crate::bitset::Bitset,
root: u32,
) -> Vec<u32> {
let undef = u32::MAX;
let mut pred_off = vec![0u32; u + 1];
for &t in sub_tgt {
pred_off[t as usize + 1] += 1;
}
for i in 0..u {
pred_off[i + 1] += pred_off[i];
}
let mut pred = vec![0u32; pred_off[u] as usize];
{
let mut cursor = pred_off.clone();
for s in 0..u {
for pos in sub_off[s]..sub_off[s + 1] {
let t = sub_tgt[pos as usize] as usize;
pred[cursor[t] as usize] = s as u32;
cursor[t] += 1;
}
}
}
let mut order: Vec<u32> = Vec::with_capacity(u);
let mut visited = crate::bitset::Bitset::with_len(u);
let mut stack: Vec<(u32, u32)> = Vec::new(); for start in 0..u {
if has_pred.get(start) || visited.get(start) {
continue;
}
visited.set(start);
stack.push((start as u32, sub_off[start]));
while let Some(&mut (node, ref mut cur)) = stack.last_mut() {
if *cur < sub_off[node as usize + 1] {
let child = sub_tgt[*cur as usize];
*cur += 1;
if !visited.get(child as usize) {
visited.set(child as usize);
stack.push((child, sub_off[child as usize]));
}
} else {
order.push(node);
stack.pop();
}
}
}
for node in 0..u as u32 {
if !visited.get(node as usize) {
order.push(node);
}
}
order.reverse();
let mut rpo_num = vec![undef; u + 1];
rpo_num[root as usize] = 0;
for (i, &node) in order.iter().enumerate() {
rpo_num[node as usize] = i as u32 + 1;
}
let mut idom = vec![undef; u + 1];
idom[root as usize] = root;
for node in 0..u {
if !has_pred.get(node) {
idom[node] = root;
}
}
let intersect = |mut a: u32, mut b: u32, idom: &[u32], rpo_num: &[u32]| -> u32 {
while a != b {
while rpo_num[a as usize] > rpo_num[b as usize] {
a = idom[a as usize];
}
while rpo_num[b as usize] > rpo_num[a as usize] {
b = idom[b as usize];
}
}
a
};
let mut changed = true;
while changed {
changed = false;
for &node in &order {
if !has_pred.get(node as usize) {
continue; }
let ni = node as usize;
let mut new_idom = undef;
for pos in pred_off[ni]..pred_off[ni + 1] {
let p = pred[pos as usize];
if idom[p as usize] == undef {
continue; }
new_idom = if new_idom == undef {
p
} else {
intersect(p, new_idom, &idom, &rpo_num)
};
}
if new_idom != undef && idom[ni] != new_idom {
idom[ni] = new_idom;
changed = true;
}
}
}
idom
}
fn build_children_csr(u: usize, idom: &[u32], root: u32) -> (Vec<u32>, Vec<u32>) {
let mut off = vec![0u32; u + 1];
for node in 0..u {
let p = idom[node];
if p == u32::MAX || p == root || p == node as u32 {
continue;
}
off[p as usize + 1] += 1;
}
for i in 0..u {
off[i + 1] += off[i];
}
let mut tgt = vec![0u32; off[u] as usize];
let mut cursor = off.clone();
for node in 0..u {
let p = idom[node];
if p == u32::MAX || p == root || p == node as u32 {
continue;
}
tgt[cursor[p as usize] as usize] = node as u32;
cursor[p as usize] += 1;
}
(off, tgt)
}
fn fold_retained(
u: usize,
shallow: &[u32],
dc_off: &[u32],
dc_tgt: &[u32],
_root: u32,
) -> Vec<u64> {
let mut retained: Vec<u64> = shallow.iter().map(|&s| s as u64).collect();
let mut visited = crate::bitset::Bitset::with_len(u);
let mut stack: Vec<(u32, u32)> = Vec::new();
let mut is_child = crate::bitset::Bitset::with_len(u);
for &c in dc_tgt {
is_child.set(c as usize);
}
for start in 0..u {
if is_child.get(start) || visited.get(start) {
continue;
}
visited.set(start);
stack.push((start as u32, dc_off[start]));
while let Some(&mut (node, ref mut cur)) = stack.last_mut() {
if *cur < dc_off[node as usize + 1] {
let child = dc_tgt[*cur as usize];
*cur += 1;
if !visited.get(child as usize) {
visited.set(child as usize);
stack.push((child, dc_off[child as usize]));
}
} else {
stack.pop();
if let Some(&(parent, _)) = stack.last() {
retained[parent as usize] += retained[node as usize];
}
}
}
}
retained
}
fn same_class_ancestor(
u: usize,
dc_off: &[u32],
dc_tgt: &[u32],
class: &[u32],
class_count: usize,
_root: u32,
) -> crate::bitset::Bitset {
let undef = u32::MAX;
let mut has_same = crate::bitset::Bitset::with_len(u);
let mut on_path: Vec<u32> = vec![0u32; class_count]; let mut is_child = crate::bitset::Bitset::with_len(u);
for &c in dc_tgt {
is_child.set(c as usize);
}
let mut stack: Vec<(u32, u32, u32)> = Vec::new(); for start in 0..u {
if is_child.get(start) {
continue;
}
push_frame(
&mut stack,
start as u32,
dc_off,
class,
class_count,
&mut on_path,
&mut has_same,
);
while let Some(&mut (node, ref mut cur, _)) = stack.last_mut() {
if *cur < dc_off[node as usize + 1] {
let child = dc_tgt[*cur as usize];
*cur += 1;
push_frame(
&mut stack,
child,
dc_off,
class,
class_count,
&mut on_path,
&mut has_same,
);
} else {
let (node, _, saved) = stack.pop().unwrap();
let c = class[node as usize];
if (c as usize) < class_count {
on_path[c as usize] = saved;
}
let _ = undef;
}
}
}
has_same
}
#[allow(clippy::too_many_arguments)]
fn push_frame(
stack: &mut Vec<(u32, u32, u32)>,
node: u32,
dc_off: &[u32],
class: &[u32],
class_count: usize,
on_path: &mut [u32],
has_same: &mut crate::bitset::Bitset,
) {
let depth = stack.len() as u32 + 1;
let c = class[node as usize];
let mut saved = 0u32;
if (c as usize) < class_count {
saved = on_path[c as usize];
if saved > 0 {
has_same.set(node as usize);
}
on_path[c as usize] = depth;
}
stack.push((node, dc_off[node as usize], saved));
}
#[cfg(test)]
mod tests {
use super::*;
fn cvec(v: &[u32]) -> CompressedU32 {
CompressedU32::compress(v, crate::cvec::Codec::None).unwrap()
}
#[test]
fn chain_forest_retained() {
let n = 5;
let dfn = vec![0u32, 1, u32::MAX, u32::MAX, u32::MAX, 0 ];
let fwd_off = vec![0u32, 0, 0, 1, 2, 2]; let fwd_tgt = ChunkU32::from_vec(vec![3u32, 4u32]);
let shallow = cvec(&[0, 0, 10, 20, 30]);
let class_idx = cvec(&[0, 0, 1, 2, 3]);
let r = compute_unreachable_retained(
n,
&dfn,
&fwd_off,
&fwd_tgt,
&shallow,
&class_idx,
4,
&HashMap::new(),
&[],
)
.unwrap()
.unwrap();
assert_eq!(r.total, 60);
assert_eq!(r.retained_by_class.get(&1).copied(), Some(60));
assert_eq!(r.retained_by_class.get(&2).copied(), Some(50));
assert_eq!(r.retained_by_class.get(&3).copied(), Some(30));
}
#[test]
fn disconnected_forest() {
let n = 9;
let mut dfn = vec![0u32; n + 1];
for d in dfn.iter_mut().take(5) {
*d = 0; }
for d in dfn.iter_mut().take(9).skip(5) {
*d = u32::MAX; }
let fwd_off = vec![0u32, 0, 0, 0, 0, 0, 1, 1, 2, 2]; let fwd_tgt = ChunkU32::from_vec(vec![6u32, 8u32]);
let shallow = cvec(&[0, 0, 0, 0, 0, 10, 10, 10, 10]);
let class_idx = cvec(&[0, 0, 0, 0, 0, 1, 1, 1, 1]);
let r = compute_unreachable_retained(
n,
&dfn,
&fwd_off,
&fwd_tgt,
&shallow,
&class_idx,
2,
&HashMap::new(),
&[],
)
.unwrap()
.unwrap();
assert_eq!(r.total, 40);
assert_eq!(r.retained_by_class.get(&1).copied(), Some(40));
}
#[test]
fn no_unreachable_returns_none() {
let n = 2;
let dfn = vec![0u32, 1, 0];
let fwd_off = vec![0u32, 0, 0];
let fwd_tgt = ChunkU32::from_vec(vec![]);
let shallow = cvec(&[10, 20]);
let class_idx = cvec(&[0, 0]);
let r = compute_unreachable_retained(
n,
&dfn,
&fwd_off,
&fwd_tgt,
&shallow,
&class_idx,
1,
&HashMap::new(),
&[],
)
.unwrap();
assert!(r.is_none());
}
}