1use std::{
2 cell::UnsafeCell,
3 collections::{HashMap, HashSet, VecDeque},
4 rc::Rc,
5};
6
7use crate::{decoder::*, perf::PerfCounter};
8use petgraph::visit::EdgeRef;
9
10struct PostOrder {
12 index_to_order: Vec<usize>,
14 order_to_index: Vec<usize>,
16}
17
18struct DominatedNodes {
19 first_dom_node_index: Vec<usize>,
20 dominated_nodes: Vec<usize>,
21}
22
23struct Retaining {
24 nodes: Vec<usize>,
25 edges: Vec<usize>,
26 first_retainer: Vec<usize>,
27 first_edge: Vec<usize>,
28}
29
30#[derive(Clone)]
31pub struct ClassGroup {
32 pub index: usize,
33 pub self_size: u64,
34 pub retained_size: u64,
35 pub nodes: Vec<usize>,
36}
37
38#[cfg(target_arch = "wasm32")]
39use wasm_bindgen::prelude::*;
40
41#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
42pub struct Graph {
43 pub(crate) inner: Rc<PetGraph>,
51 pub root_index: usize,
52 strings: Rc<Vec<String>>,
53 dominators: UnsafeCell<Option<Vec<usize>>>,
54 retained_sizes: UnsafeCell<Option<Vec<u64>>>,
55 flags: UnsafeCell<Option<Flags>>,
56 retainers: UnsafeCell<Option<Retaining>>,
57 post_order: UnsafeCell<Option<PostOrder>>,
58 dominated_nodes: UnsafeCell<Option<DominatedNodes>>,
59 class_groups: UnsafeCell<Option<Vec<ClassGroup>>>,
60}
61
62mod flag {
63 pub const CAN_BE_QUERIED: u8 = 1 << 0;
64 pub const DETACHED_DOM_TREE_NODE: u8 = 1 << 1;
65 pub const PAGE_OBJECT: u8 = 1 << 2;
66}
67
68struct Flags(Vec<u8>);
69
70impl Flags {
71 pub fn test(&self, index: usize, flag: u8) -> bool {
72 (self.0[index] & flag) != 0
73 }
74}
75
76impl Graph {
77 pub(crate) fn new(inner: PetGraph, root_index: usize, strings: Rc<Vec<String>>) -> Self {
78 Self {
79 inner: Rc::new(inner),
80 root_index,
81 strings,
82 dominators: UnsafeCell::new(None),
83 retained_sizes: UnsafeCell::new(None),
84 retainers: UnsafeCell::new(None),
85 post_order: UnsafeCell::new(None),
86 flags: UnsafeCell::new(None),
87 dominated_nodes: UnsafeCell::new(None),
88 class_groups: UnsafeCell::new(None),
89 }
90 }
91
92 pub fn nodes(&self) -> &[petgraph::graph::Node<Node>] {
94 self.inner.raw_nodes()
95 }
96
97 pub fn get_node(&self, index: usize) -> Option<&Node> {
99 self.inner.raw_nodes().get(index).map(|n| &n.weight)
100 }
101
102 pub fn get_indexed_string(&self, index: usize) -> Option<&str> {
105 self.strings.get(index).map(|s| s.as_str())
106 }
107
108 pub(crate) fn graph(&self) -> &PetGraph {
109 &self.inner
110 }
111
112 pub fn iter(&self) -> NodeIterator<'_> {
114 NodeIterator {
115 graph: self.graph(),
116 index: 0,
117 }
118 }
119
120 pub fn children(&self, parent: usize) -> Vec<usize> {
122 let graph = self.graph();
123
124 graph
125 .neighbors(petgraph::graph::NodeIndex::new(parent))
126 .map(|n| n.index())
127 .collect()
128 }
129
130 pub fn retained_size(&self, node_index: usize) -> u64 {
134 let retained_sizes = unsafe { &mut *self.retained_sizes.get() };
135
136 if retained_sizes.is_none() {
137 let graph = self.graph();
138 let post_order = &self.get_post_order().order_to_index;
139 let dom = self.get_dominators();
140 let _perf = PerfCounter::new("build_retained_sizes");
141
142 let mut rs = Vec::with_capacity(graph.node_count());
143 for n in self.graph().raw_nodes() {
144 rs.push(n.weight.self_size);
145 }
146
147 for i in post_order.iter() {
148 if *i != self.root_index {
149 rs[dom[*i]] += rs[*i];
150 }
151 }
152
153 *retained_sizes = Some(rs);
154 }
155
156 retained_sizes.as_ref().unwrap()[node_index]
157 }
158
159 fn get_dominated_nodes(&self) -> &DominatedNodes {
160 let dominated_nodes_o = unsafe { &mut *self.dominated_nodes.get() };
161
162 if dominated_nodes_o.is_none() {
163 let dominators_tree = self.get_dominators();
164 let _perf = PerfCounter::new("build_dominated_nodes");
165 let graph = self.graph();
166 let mut dominated_nodes = vec![0; graph.node_count()];
167 let mut first_dom_node_index = vec![0; graph.node_count() + 1];
168
169 let range = match self.root_index {
170 0 => 1..graph.node_count(),
171 i if i == graph.node_count() - 1 => 0..graph.node_count() - 1,
172 i => panic!("expected root index to be first or last, was {}", i),
173 };
174
175 for node_index in range.clone() {
177 first_dom_node_index[dominators_tree[node_index]] += 1;
178 }
179
180 let mut first_dominated_node_index = 0;
181 #[allow(clippy::needless_range_loop)]
182 for i in 0..graph.node_count() {
183 let dominated_count = first_dom_node_index[i];
184 if i < graph.node_count() - 1 {
185 dominated_nodes[first_dominated_node_index] = dominated_count;
186 }
187 first_dom_node_index[i] = first_dominated_node_index;
188 first_dominated_node_index += dominated_count;
189 }
190 first_dom_node_index[graph.node_count()] = dominated_nodes.len() - 1;
191
192 for node_index in range {
193 let dominator_ordinal = dominators_tree[node_index];
194 let mut dominated_ref_index = first_dom_node_index[dominator_ordinal];
195 dominated_nodes[dominated_ref_index] -= 1;
196 dominated_ref_index += dominated_nodes[dominated_ref_index];
197 dominated_nodes[dominated_ref_index] = node_index;
198 }
199
200 *dominated_nodes_o = Some(DominatedNodes {
201 dominated_nodes,
202 first_dom_node_index,
203 });
204 }
205
206 dominated_nodes_o.as_ref().unwrap()
207 }
208
209 pub fn get_class_groups(&self, no_retained: bool) -> &[ClassGroup] {
212 let class_groups = unsafe { &mut *self.class_groups.get() };
213
214 if class_groups.is_none() {
215 let nodes = self.nodes();
216 let mut groups = HashMap::new();
217 for (index, node) in nodes.iter().enumerate() {
218 let name = node.weight.class_name();
219 let group = groups.entry(name).or_insert(ClassGroup {
220 index,
221 self_size: 0,
222 retained_size: 0,
223 nodes: vec![],
224 });
225
226 group.self_size += node.weight.self_size;
227 group.nodes.push(index);
228 }
229
230 if !no_retained {
231 let dominators = self.get_dominated_nodes();
232 let _perf = PerfCounter::new("build_class_groups");
233 let mut queue = VecDeque::new();
234 let mut sizes = vec![-1];
235 let mut classes = vec![];
236 queue.push_back(self.root_index);
237
238 let mut seen_groups: HashSet<&str> = HashSet::new();
239 while let Some(node_index) = queue.pop_back() {
240 let node = &nodes[node_index];
241 let name = node.weight.class_name();
242 let seen = seen_groups.contains(name);
243
244 let dominated_index_from = dominators.first_dom_node_index[node_index];
245 let dominated_index_to = dominators.first_dom_node_index[node_index + 1];
246
247 if !seen
248 && (node.weight.self_size != 0
249 || matches!(node.weight.typ, NodeType::Native))
250 {
251 let group = groups.get_mut(name).unwrap();
253 group.retained_size += self.retained_size(node_index);
254 if dominated_index_from != dominated_index_to {
255 seen_groups.insert(name);
256 sizes.push(queue.len() as isize);
257 classes.push(name);
258 }
259 }
260
261 for i in dominated_index_from..dominated_index_to {
262 queue.push_back(dominators.dominated_nodes[i]);
263 }
264
265 let l = queue.len();
266 while sizes.last().copied() == Some(l as isize) {
267 sizes.pop();
268 seen_groups.remove(classes.pop().unwrap());
269 }
270 }
271 }
272
273 let mut groups: Vec<_> = groups.into_values().collect();
274 groups.sort_by_key(|g| std::cmp::Reverse(g.retained_size));
275 *class_groups = Some(groups);
276 }
277
278 class_groups.as_ref().unwrap()
279 }
280
281 fn get_retainers(&self) -> &Retaining {
282 let retaining = unsafe { &mut *self.retainers.get() };
283
284 if retaining.is_none() {
285 let _perf = PerfCounter::new("build_retainers");
286 let graph = self.graph();
287 let mut r = Retaining {
288 edges: vec![0; graph.edge_count()],
289 nodes: vec![0; graph.edge_count()],
290 first_retainer: vec![0; graph.node_count() + 1],
291 first_edge: vec![0; graph.node_count() + 1],
292 };
293
294 r.first_edge[graph.node_count()] = graph.edge_count();
295 let mut edge_index = 0;
296 for (i, node) in graph.raw_nodes().iter().enumerate() {
297 r.first_edge[i] = edge_index;
298 edge_index += node.weight.edge_count;
299 }
300
301 for edge in graph.raw_edges() {
302 r.first_retainer[edge.target().index()] += 1;
303 }
304
305 let mut first_unused_retainer_slot = 0;
306 for i in 0..graph.node_count() {
307 let retainers_count = r.first_retainer[i];
308 r.first_retainer[i] = first_unused_retainer_slot;
309 r.nodes[first_unused_retainer_slot] = retainers_count;
310 first_unused_retainer_slot += retainers_count;
311 }
312 r.first_retainer[graph.node_count()] = r.nodes.len();
313
314 for node in graph.node_indices() {
315 for edge in graph.edges(node) {
316 let first_retainer_slot_index = r.first_retainer[edge.target().index()];
317 r.nodes[first_retainer_slot_index] -= 1;
318 let next_unused_retainer_slot_index =
319 first_retainer_slot_index + r.nodes[first_retainer_slot_index];
320 r.nodes[next_unused_retainer_slot_index] = node.index();
321 r.edges[next_unused_retainer_slot_index] = edge.id().index();
322 }
323 }
324
325 *retaining = Some(r);
326 }
327
328 retaining.as_ref().unwrap()
329 }
330
331 fn get_dominators(&self) -> &Vec<usize> {
332 let dominators = unsafe { &mut *self.dominators.get() };
333 if dominators.is_none() {
334 *dominators = Some(self.build_dominators());
335 }
336
337 dominators.as_ref().unwrap()
338 }
339
340 pub(crate) fn is_essential_edge(&self, index: usize, typ: &EdgeType) -> bool {
341 if let EdgeType::Weak = typ {
342 return false;
343 }
344 if let EdgeType::Shortcut = typ {
345 return index == self.root_index;
346 }
347
348 true
349 }
350
351 fn get_flags(&self) -> &Flags {
353 let flags = unsafe { &mut *self.flags.get() };
354
355 if flags.is_none() {
356 let _perf = PerfCounter::new("build_flags");
357 let mut f = vec![0; self.graph().node_count()];
358 self.mark_detached_dom_tree_nodes(&mut f);
359 self.mark_queriable_heap_objects(&mut f);
360 self.mark_page_owned_nodes(&mut f);
361 *flags = Some(Flags(f));
362 }
363
364 flags.as_ref().unwrap()
365 }
366
367 fn mark_detached_dom_tree_nodes(&self, flags: &mut [u8]) {
368 for (index, node) in self.graph().raw_nodes().iter().enumerate() {
369 if let NodeType::Native = node.weight.typ {
370 if node.weight.name().starts_with("Detached ") {
371 flags[index] |= flag::DETACHED_DOM_TREE_NODE;
372 }
373 }
374 }
375 }
376
377 fn mark_queriable_heap_objects(&self, flags: &mut [u8]) {
378 let graph = self.graph();
379 let mut list = VecDeque::new();
380 let retained = self.get_retainers();
381
382 while let Some(node_index) = list.pop_front() {
383 if flags[node_index] & flag::CAN_BE_QUERIED != 0 {
384 continue;
385 }
386 flags[node_index] |= flag::CAN_BE_QUERIED;
387 let begin_edge = retained.first_edge[node_index];
388 let end_edge = retained.first_edge[node_index + 1];
389 for edge in graph.raw_edges()[begin_edge..end_edge].iter() {
390 if flags[edge.target().index()] & flag::CAN_BE_QUERIED != 0 {
391 continue;
392 }
393 let typ = edge.weight.typ;
394 if matches!(
395 typ,
396 EdgeType::Hidden | EdgeType::Internal | EdgeType::Invisible | EdgeType::Weak
397 ) {
398 continue;
399 }
400 list.push_back(edge.target().index());
401 }
402 }
403 }
404
405 fn mark_page_owned_nodes(&self, flags: &mut [u8]) {
406 let retainers = self.get_retainers();
407 let graph = self.graph();
408 let node_count = graph.raw_nodes().len();
409 let mut nodes_to_visit = vec![0; node_count];
410 let mut nodes_to_visit_length = 0;
411
412 for edge_index in
414 retainers.first_edge[self.root_index]..retainers.first_edge[self.root_index + 1]
415 {
416 let edge = &graph.raw_edges()[edge_index];
417 if edge.weight.typ == EdgeType::Element {
418 if !graph[edge.target()].is_document_dom_trees_root() {
419 continue;
420 }
421 } else if edge.weight.typ != EdgeType::Shortcut {
422 continue;
423 }
424
425 nodes_to_visit[nodes_to_visit_length] = edge.target().index();
426 flags[edge.target().index()] |= flag::PAGE_OBJECT;
427 nodes_to_visit_length += 1;
428 }
429
430 while nodes_to_visit_length > 0 {
432 let node_ordinal = nodes_to_visit[nodes_to_visit_length - 1];
433 nodes_to_visit_length -= 1;
434
435 let edge_begin = retainers.first_edge[node_ordinal];
436 let edge_end = retainers.first_edge[node_ordinal + 1];
437 for edge in graph.raw_edges()[edge_begin..edge_end].iter() {
438 let child_index = edge.target().index();
439 if flags[child_index] & flag::PAGE_OBJECT != 0 {
440 continue;
441 }
442 if edge.weight.typ == EdgeType::Weak {
443 continue;
444 }
445 nodes_to_visit[nodes_to_visit_length] = child_index;
446 flags[child_index] |= flag::PAGE_OBJECT;
447 nodes_to_visit_length += 1;
448 }
449 }
450 }
451
452 fn get_post_order(&self) -> &PostOrder {
454 let post_order = unsafe { &mut *self.post_order.get() };
455 if post_order.is_none() {
456 *post_order = Some(self.build_post_order());
457 }
458
459 post_order.as_ref().unwrap()
460 }
461
462 fn build_post_order(&self) -> PostOrder {
463 let retainers = self.get_retainers();
464 let _perf = PerfCounter::new("build_post_order");
465 let graph = self.graph();
466 let node_count = graph.raw_nodes().len();
467 let flags = self.get_flags();
468 let mut stack_nodes = vec![0; node_count];
469 let mut stack_current_edge = vec![0; node_count];
470 let mut order_to_index = vec![0; node_count];
471 let mut index_to_order = vec![0; node_count];
472 let mut visited = vec![false; node_count];
473 let mut post_order_index = 0;
474
475 let mut stack_top = 0;
476 stack_nodes[0] = self.root_index;
477 stack_current_edge[0] = retainers.first_edge[self.root_index];
478 visited[self.root_index] = true;
479
480 let mut iteration = 0;
481 loop {
482 iteration += 1;
483 loop {
484 let node_index = stack_nodes[stack_top];
485 let edge_index = stack_current_edge[stack_top];
486 let edges_end = retainers.first_edge[node_index + 1];
487
488 if edge_index < edges_end {
489 stack_current_edge[stack_top] += 1;
490 let edge = &graph.raw_edges()[edge_index];
491 if !self.is_essential_edge(node_index, &edge.weight.typ) {
492 continue;
493 }
494 let child_node_index = edge.target().index();
495 if visited[child_node_index] {
496 continue;
497 }
498
499 if node_index != self.root_index
500 && flags.test(child_node_index, flag::PAGE_OBJECT)
501 && !flags.test(node_index, flag::PAGE_OBJECT)
502 {
503 continue;
504 }
505
506 stack_top += 1;
507 stack_nodes[stack_top] = child_node_index;
508 stack_current_edge[stack_top] = retainers.first_edge[child_node_index];
509 visited[child_node_index] = true;
510 } else {
511 index_to_order[node_index] = post_order_index;
512 order_to_index[post_order_index] = node_index;
513 post_order_index += 1;
514
515 if stack_top == 0 {
516 break;
517 }
518
519 stack_top -= 1;
520 }
521 }
522
523 if post_order_index == node_count || iteration > 1 {
524 break;
525 }
526
527 post_order_index -= 1;
528 stack_top = 0;
529 stack_nodes[0] = self.root_index;
530 stack_current_edge[0] = retainers.first_edge[self.root_index + 1];
531
532 for (i, did_visit) in visited.iter_mut().enumerate() {
533 if *did_visit || !self.has_only_weak_retainers(i) {
534 continue;
535 }
536
537 stack_top += 1;
538 stack_nodes[stack_top] = i;
539 stack_current_edge[stack_top] = retainers.first_edge[i];
540 *did_visit = true;
541 }
542 }
543
544 if post_order_index != node_count {
545 post_order_index -= 1;
546 for i in 0..node_count {
547 if visited[i] {
548 continue;
549 }
550 index_to_order[i] = post_order_index;
551 order_to_index[post_order_index] = i;
552 post_order_index += 1;
553 }
554 index_to_order[self.root_index] = post_order_index;
555 order_to_index[post_order_index] = self.root_index;
556 }
557
558 PostOrder {
559 index_to_order,
560 order_to_index,
561 }
562 }
563
564 fn build_dominators(&self) -> Vec<usize> {
565 let post_order = self.get_post_order();
566 let graph = self.graph();
567
568 let flags = self.get_flags();
569 let retaining = self.get_retainers();
570 let nodes_count = self.nodes().len();
571 let root_post_ordered_index = nodes_count - 1;
572 let no_entry = nodes_count;
573 let mut dominators = vec![no_entry; nodes_count];
574 dominators[root_post_ordered_index] = root_post_ordered_index;
575 let _perf = PerfCounter::new("build_dominators");
576
577 let mut affected: Vec<u8> = vec![0; nodes_count];
580
581 {
583 let begin_index = retaining.first_edge[self.root_index];
584 let end_index = retaining.first_edge[self.root_index + 1];
585 for edge in graph.raw_edges()[begin_index..end_index].iter() {
586 if !self.is_essential_edge(self.root_index, &edge.weight.typ) {
587 continue;
588 }
589
590 let index = post_order.index_to_order[edge.target().index()];
591 affected[index] = 1;
592 }
593 }
594
595 let mut changed = true;
596 while changed {
597 changed = false;
598 for i in 0..root_post_ordered_index {
599 let post_order_index = root_post_ordered_index - (i + 1);
600 if affected[post_order_index] == 0 {
601 continue;
602 }
603 affected[post_order_index] = 0;
604 if dominators[post_order_index] == root_post_ordered_index {
605 continue;
606 }
607 let node_index = post_order.order_to_index[post_order_index];
608 let mut new_dominator_index = no_entry;
609 let begin_retainer_index = retaining.first_retainer[node_index];
610 let end_retainer_index = retaining.first_retainer[node_index + 1];
611 let mut orphan_node = true;
612 for retainer_index in begin_retainer_index..end_retainer_index {
613 let retainer_edge_index = retaining.edges[retainer_index];
614 let retainer_edge_type = &graph.raw_edges()[retainer_edge_index].weight.typ;
615 let retainer_node_index = retaining.nodes[retainer_index];
616 if !self.is_essential_edge(retainer_node_index, retainer_edge_type) {
617 continue;
618 }
619 orphan_node = false;
620 if retainer_node_index != self.root_index
621 && flags.test(node_index, flag::PAGE_OBJECT)
622 && !flags.test(retainer_node_index, flag::PAGE_OBJECT)
623 {
624 continue;
625 }
626 let mut retainer_post_order_index =
627 post_order.index_to_order[retainer_node_index];
628 if dominators[retainer_post_order_index] != no_entry {
629 if new_dominator_index == no_entry {
630 new_dominator_index = retainer_post_order_index;
631 } else {
632 while retainer_post_order_index != new_dominator_index {
633 while retainer_post_order_index < new_dominator_index {
634 retainer_post_order_index =
635 dominators[retainer_post_order_index];
636 }
637 while new_dominator_index < retainer_post_order_index {
638 new_dominator_index = dominators[new_dominator_index];
639 }
640 }
641 }
642 if new_dominator_index == root_post_ordered_index {
643 break;
644 }
645 }
646 }
647 if orphan_node {
648 new_dominator_index = root_post_ordered_index;
649 }
650 if new_dominator_index != no_entry
651 && dominators[post_order_index] != new_dominator_index
652 {
653 dominators[post_order_index] = new_dominator_index;
654 let node_index = post_order.order_to_index[post_order_index];
655 changed = true;
656 let begin_index = retaining.first_edge[node_index];
657 let end_index = retaining.first_edge[node_index + 1];
658 for edge in graph.raw_edges()[begin_index..end_index].iter() {
659 affected[post_order.index_to_order[edge.target().index()]] = 1;
660 }
661 }
662 }
663 }
664
665 let mut dominators_tree = vec![0; nodes_count];
666 for (post_order_index, dominated_by) in dominators.into_iter().enumerate() {
667 let node_index = post_order.order_to_index[post_order_index];
668 dominators_tree[node_index] = post_order
671 .order_to_index
672 .get(dominated_by)
673 .copied()
674 .unwrap_or(self.root_index);
675 }
676
677 dominators_tree
678 }
679
680 fn has_only_weak_retainers(&self, node_index: usize) -> bool {
681 let ret = self.get_retainers();
682 let graph = self.graph();
683 for retainer in ret.first_retainer[node_index]..ret.first_retainer[node_index + 1] {
684 if let Some(e) = graph.edge_weight(petgraph::graph::EdgeIndex::new(ret.edges[retainer]))
685 {
686 if !matches!(e.typ, EdgeType::Weak | EdgeType::Shortcut) {
687 return false;
688 }
689 }
690 }
691
692 true
693 }
694}
695
696pub struct NodeIterator<'graph> {
697 graph: &'graph PetGraph,
698 index: usize,
699}
700
701impl<'graph> Iterator for NodeIterator<'graph> {
702 type Item = &'graph Node;
703
704 fn next(&mut self) -> Option<Self::Item> {
705 let n = self.graph.raw_nodes().get(self.index).map(|n| &n.weight);
706 self.index += 1;
707 n
708 }
709}
710
711#[cfg(test)]
712mod tests {
713 use super::*;
714 use std::fs;
715
716 #[test]
717 fn test_retained_sizes() {
718 let contents = fs::read("test/basic.heapsnapshot").unwrap();
719 let graph = decode_slice(&contents).expect("expect no errors");
720
721 let mut groups = graph.get_class_groups(false).to_vec();
722 groups.sort_by_key(|g| std::cmp::Reverse(g.retained_size));
723
724 let mut actual = String::new();
725 for group in groups.iter().take(10) {
726 actual.push_str(&format!(
727 "{} {} {}\n",
728 group.retained_size,
729 group.nodes.len(),
730 graph.get_node(group.index).unwrap().class_name()
731 ));
732 }
733
734 assert_eq!(
735 actual,
736 "3413384 18983 (compiled code)
7372347408 13774 (string)
7382301824 6714 (closure)
7391555120 1186 (array)
7401396568 811 Object
7411266788 757 system / Context
742985128 12345 (system)
743651840 29 Map
744340240 322 BuiltinModule
745325872 3 global
746"
747 );
748 }
749}