1use crate::header::{GcId, GcListKind};
8use crate::list::GcListIter;
9use crate::report::{
10 summarize_gc_object, summarize_gc_objects, FinalFate, FreeCycleStats, GcPhaseStats,
11 GcStatsBundle, ObjectDecision, RestorationWitness, ScanStats, TrialDecision,
12 TrialDeletionStats, VisitedEdge, MAX_EDGE_DETAILS, MAX_OBJECT_DECISIONS,
13 MAX_RESTORATION_WITNESSES,
14};
15use crate::runtime::GcRuntime;
16use crate::value::{EdgeRelation, ValueCell};
17use std::collections::{HashMap, HashSet, VecDeque};
18
19impl GcRuntime {
20 pub fn run_gc_with_stats_bundle(&mut self) -> GcStatsBundle {
22 let live_ids = self.sorted_list_ids(GcListKind::GcObj);
24 let mut ref_count_before = HashMap::with_capacity(live_ids.len());
25 for &id in &live_ids {
26 ref_count_before.insert(id, self.header(id).ref_count);
27 }
28 let all_edges = self.capture_visited_edges(&live_ids);
29 let edges_visited = all_edges.len();
30 debug_assert_eq!(
31 edges_visited,
32 self.gc_edge_count(),
33 "semantic edge capture must match collector trace edge count"
34 );
35 let mut heap_incoming = HashMap::<GcId, usize>::with_capacity(live_ids.len());
36 for edge in &all_edges {
37 *heap_incoming.entry(edge.to_id).or_default() += 1;
38 }
39 let incoming_sum: usize = heap_incoming.values().sum();
40 debug_assert_eq!(
41 incoming_sum, edges_visited,
42 "Σ heapIncomingEdges must equal edgesVisited"
43 );
44
45 self.gc_decref();
47 let candidate_ids = self.sorted_list_ids(GcListKind::Tmp);
48 let candidate_set: HashSet<GcId> = candidate_ids.iter().copied().collect();
49 let mut trial_ref_counts = HashMap::with_capacity(live_ids.len());
50 for &id in &live_ids {
51 let trial_rc = self.header(id).ref_count;
52 let before = ref_count_before[&id];
53 let incoming = heap_incoming.get(&id).copied().unwrap_or(0);
54 debug_assert_eq!(
55 trial_rc,
56 before - incoming as i32,
57 "trialRefCount must equal refCountBefore − heapIncomingEdges"
58 );
59 debug_assert_eq!(
60 candidate_set.contains(&id),
61 trial_rc == 0,
62 "Candidate ⇔ trialRefCount == 0"
63 );
64 trial_ref_counts.insert(id, trial_rc);
65 }
66
67 self.gc_scan();
69 let garbage_candidate_ids = self.sorted_list_ids(GcListKind::Tmp);
70 let garbage_set: HashSet<GcId> = garbage_candidate_ids.iter().copied().collect();
71 let restored_ids = candidate_ids
72 .iter()
73 .copied()
74 .filter(|id| !garbage_set.contains(id))
75 .collect::<Vec<_>>();
76 debug_assert_eq!(
77 restored_ids.len() + garbage_candidate_ids.len(),
78 candidate_ids.len(),
79 "Candidates = Restored + Garbage candidates"
80 );
81 let restored_objects = summarize_gc_objects(self, &restored_ids);
82 let garbage_candidate_objects = summarize_gc_objects(self, &garbage_candidate_ids);
83 let all_witnesses =
84 self.build_restoration_witnesses(&all_edges, &trial_ref_counts, &restored_ids);
85
86 let label_by_id: HashMap<GcId, crate::report::GcObjectSummary> = live_ids
88 .iter()
89 .copied()
90 .map(|id| (id, summarize_gc_object(self, id)))
91 .collect();
92
93 let before_free = self.object_ids().len();
95 self.gc_free_cycles();
96 let freed = before_free.saturating_sub(self.object_ids().len());
97 debug_assert_eq!(freed, garbage_candidate_ids.len(), "Objects freed = Garbage candidates");
98
99 let survivor_ids: Vec<GcId> = live_ids
100 .iter()
101 .copied()
102 .filter(|id| !candidate_set.contains(id))
103 .collect();
104
105 let (object_decisions, omitted_object_decisions, selected_decision_ids) =
106 select_object_decisions(
107 &live_ids,
108 &candidate_set,
109 &survivor_ids,
110 &all_witnesses,
111 &ref_count_before,
112 &heap_incoming,
113 &trial_ref_counts,
114 &garbage_set,
115 );
116
117 let (visited_edges, omitted_edge_details) =
118 select_visited_edges(&all_edges, &candidate_set, &all_witnesses);
119
120 let (restoration_witnesses, omitted_witnesses) =
121 select_restoration_witnesses(&all_witnesses, &selected_decision_ids);
122
123 let mut catalog_ids: HashSet<GcId> = HashSet::new();
124 for decision in &object_decisions {
125 catalog_ids.insert(decision.object_id);
126 }
127 for edge in &visited_edges {
128 catalog_ids.insert(edge.from_id);
129 catalog_ids.insert(edge.to_id);
130 }
131 for witness in &restoration_witnesses {
132 catalog_ids.insert(witness.object_id);
133 catalog_ids.insert(witness.root_id);
134 catalog_ids.insert(witness.predecessor_id);
135 }
136 for object in restored_objects
137 .iter()
138 .chain(garbage_candidate_objects.iter())
139 {
140 catalog_ids.insert(object.id);
141 }
142 let mut catalog_ids = catalog_ids.into_iter().collect::<Vec<_>>();
143 catalog_ids.sort_unstable();
144 let objects = catalog_ids
145 .iter()
146 .map(|&id| {
147 label_by_id
148 .get(&id)
149 .cloned()
150 .unwrap_or_else(|| crate::report::GcObjectSummary {
151 id,
152 kind: crate::value::ValueKind::Other,
153 label: format!("Object#{}", id),
154 })
155 })
156 .collect();
157
158 GcStatsBundle {
159 objects,
160 phases: GcPhaseStats {
161 trial_deletion: TrialDeletionStats {
162 edges_visited,
163 candidates: candidate_ids.len(),
164 object_decisions,
165 visited_edges,
166 omitted_object_decisions,
167 omitted_edge_details,
168 },
169 scan: ScanStats {
170 restored: restored_objects.len(),
171 garbage_candidates: garbage_candidate_objects.len(),
172 restored_objects,
173 garbage_candidate_objects,
174 restoration_witnesses,
175 omitted_witnesses,
176 },
177 free_cycles: FreeCycleStats {
178 freed,
179 },
180 },
181 }
182 }
183
184 fn capture_visited_edges(&self, live_ids: &[GcId]) -> Vec<VisitedEdge> {
185 let mut edges = Vec::new();
186 for &from_id in live_ids {
187 if let Some(cell) = self.object_downcast::<ValueCell>(from_id) {
188 let mut ordinal = 0usize;
189 cell.value.visit_edges(|relation, target| {
190 edges.push((
191 from_id,
192 ordinal,
193 VisitedEdge {
194 from_id,
195 to_id: target.0,
196 relation,
197 },
198 ));
199 ordinal += 1;
200 });
201 } else {
202 let object = self.objects[from_id]
203 .as_ref()
204 .and_then(|entry| entry.object.as_ref())
205 .expect("live GC object must have a payload");
206 let mut ordinal = 0usize;
207 object.trace(&mut |to_id| {
208 edges.push((
209 from_id,
210 ordinal,
211 VisitedEdge {
212 from_id,
213 to_id,
214 relation: EdgeRelation::Unknown,
215 },
216 ));
217 ordinal += 1;
218 });
219 }
220 }
221 edges.sort_by(|left, right| left.0.cmp(&right.0).then(left.1.cmp(&right.1)));
225 edges.into_iter().map(|(_, _, edge)| edge).collect()
226 }
227
228 fn build_restoration_witnesses(
229 &self,
230 edges: &[VisitedEdge],
231 trial_ref_counts: &HashMap<GcId, i32>,
232 restored_ids: &[GcId],
233 ) -> Vec<RestorationWitness> {
234 let mut adjacency: HashMap<GcId, Vec<&VisitedEdge>> = HashMap::new();
235 for edge in edges {
236 adjacency.entry(edge.from_id).or_default().push(edge);
237 }
238 let mut roots: Vec<GcId> = trial_ref_counts
241 .iter()
242 .filter_map(|(&id, &rc)| (rc > 0).then_some(id))
243 .collect();
244 roots.sort_unstable();
245
246 let mut predecessor: HashMap<GcId, (GcId, EdgeRelation, GcId)> = HashMap::new();
248 let mut visited: HashSet<GcId> = HashSet::new();
249 let mut queue: VecDeque<(GcId, GcId)> = VecDeque::new(); for root in roots {
252 if visited.insert(root) {
253 queue.push_back((root, root));
254 }
255 }
256
257 while let Some((node, root)) = queue.pop_front() {
258 let Some(outs) = adjacency.get(&node) else {
259 continue;
260 };
261 for edge in outs {
262 if visited.insert(edge.to_id) {
263 predecessor.insert(edge.to_id, (edge.from_id, edge.relation.clone(), root));
264 queue.push_back((edge.to_id, root));
265 }
266 }
267 }
268
269 let mut witnesses = Vec::new();
270 for &object_id in restored_ids {
271 if let Some((predecessor_id, relation, root_id)) = predecessor.get(&object_id) {
272 witnesses.push(RestorationWitness {
273 object_id,
274 root_id: *root_id,
275 predecessor_id: *predecessor_id,
276 relation: relation.clone(),
277 });
278 }
279 }
280 witnesses.sort_by_key(|witness| witness.object_id);
281 witnesses
282 }
283
284 fn sorted_list_ids(&self, kind: GcListKind) -> Vec<GcId> {
285 let current = match kind {
286 GcListKind::GcObj => self.gc_obj_list.head,
287 GcListKind::Tmp => self.tmp_obj_list.head,
288 GcListKind::ZeroRef => self.gc_zero_ref_count_list.head,
289 };
290 let mut ids = GcListIter {
291 rt: self,
292 current,
293 }
294 .collect::<Vec<_>>();
295 ids.sort_unstable();
296 ids
297 }
298
299 fn gc_edge_count(&self) -> usize {
300 let mut count = 0;
301 for id in (GcListIter {
302 rt: self,
303 current: self.gc_obj_list.head,
304 }) {
305 let object = self.objects[id]
306 .as_ref()
307 .and_then(|entry| entry.object.as_ref())
308 .expect("live GC object must have a payload");
309 object.trace(&mut |_| count += 1);
310 }
311 count
312 }
313}
314
315fn edge_priority(
316 edge: &VisitedEdge,
317 candidates: &HashSet<GcId>,
318 witness_edges: &[(GcId, GcId, EdgeRelation)],
319) -> u8 {
320 if witness_edges.iter().any(|(from, to, relation)| {
321 *from == edge.from_id && *to == edge.to_id && *relation == edge.relation
322 }) {
323 return 0;
324 }
325 let from_c = candidates.contains(&edge.from_id);
326 let to_c = candidates.contains(&edge.to_id);
327 match (from_c, to_c) {
328 (true, true) => 1,
329 (false, true) => 2,
330 (true, false) => 3,
331 (false, false) => 4,
332 }
333}
334
335fn select_visited_edges(
336 all_edges: &[VisitedEdge],
337 candidates: &HashSet<GcId>,
338 witnesses: &[RestorationWitness],
339) -> (Vec<VisitedEdge>, usize) {
340 let witness_edges: Vec<(GcId, GcId, EdgeRelation)> = witnesses
341 .iter()
342 .map(|witness| (witness.predecessor_id, witness.object_id, witness.relation.clone()))
343 .collect();
344
345 let mut ranked = all_edges
346 .iter()
347 .enumerate()
348 .map(|(index, edge)| (edge_priority(edge, candidates, &witness_edges), index))
349 .collect::<Vec<_>>();
350 ranked.sort_by(|left, right| left.0.cmp(&right.0).then(left.1.cmp(&right.1)));
351
352 let selected_indices: HashSet<usize> = ranked
353 .into_iter()
354 .take(MAX_EDGE_DETAILS)
355 .map(|(_, index)| index)
356 .collect();
357 let kept = all_edges
358 .iter()
359 .enumerate()
360 .filter(|&(index, _edge)| selected_indices.contains(&index))
361 .map(|(_index, edge)| edge.clone())
362 .collect::<Vec<_>>();
363 let omitted = all_edges.len().saturating_sub(kept.len());
364 (kept, omitted)
365}
366
367fn witness_chain_ids(
368 witness: &RestorationWitness,
369 by_object: &HashMap<GcId, &RestorationWitness>,
370) -> Option<Vec<GcId>> {
371 let mut ids = vec![witness.object_id, witness.root_id, witness.predecessor_id];
372 let mut current = witness.object_id;
373 let mut seen = HashSet::new();
374 seen.insert(current);
375
376 while let Some(entry) = by_object.get(¤t) {
377 if entry.predecessor_id == entry.root_id {
378 break;
379 }
380 if !seen.insert(entry.predecessor_id) {
381 return None;
382 }
383 ids.push(entry.predecessor_id);
384 current = entry.predecessor_id;
385 if current == witness.root_id {
386 break;
387 }
388 if !by_object.contains_key(¤t) {
389 break;
390 }
391 }
392
393 ids.sort_unstable();
394 ids.dedup();
395 Some(ids)
396}
397
398#[allow(clippy::too_many_arguments)]
399fn select_object_decisions(
400 live_ids: &[GcId],
401 candidates: &HashSet<GcId>,
402 survivor_ids: &[GcId],
403 witnesses: &[RestorationWitness],
404 ref_count_before: &HashMap<GcId, i32>,
405 heap_incoming: &HashMap<GcId, usize>,
406 trial_ref_counts: &HashMap<GcId, i32>,
407 garbage_set: &HashSet<GcId>,
408) -> (Vec<ObjectDecision>, usize, HashSet<GcId>) {
409 let witness_by_object: HashMap<GcId, &RestorationWitness> = witnesses
410 .iter()
411 .map(|witness| (witness.object_id, witness))
412 .collect();
413
414 let mut witness_survivor_ids = HashSet::new();
415 for witness in witnesses {
416 if let Some(ids) = witness_chain_ids(witness, &witness_by_object) {
417 for id in ids {
418 if !candidates.contains(&id) {
419 witness_survivor_ids.insert(id);
420 }
421 }
422 }
423 witness_survivor_ids.insert(witness.root_id);
424 }
425
426 let mut candidate_ids: Vec<GcId> = candidates.iter().copied().collect();
427 candidate_ids.sort_unstable();
428 let mut witness_survivors: Vec<GcId> = witness_survivor_ids.iter().copied().collect();
429 witness_survivors.sort_unstable();
430 let mut other_survivors: Vec<GcId> = survivor_ids
431 .iter()
432 .copied()
433 .filter(|id| !witness_survivor_ids.contains(id))
434 .collect();
435 other_survivors.sort_unstable();
436
437 let mut ordered = Vec::new();
438 ordered.extend(candidate_ids);
439 ordered.extend(witness_survivors);
440 ordered.extend(other_survivors);
441 for &id in live_ids {
442 if !ordered.contains(&id) {
443 ordered.push(id);
444 }
445 }
446
447 let selected: Vec<GcId> = ordered.into_iter().take(MAX_OBJECT_DECISIONS).collect();
448 let selected_set: HashSet<GcId> = selected.iter().copied().collect();
449 let omitted = live_ids.len().saturating_sub(selected.len());
450
451 let mut decisions = selected
452 .iter()
453 .map(|&object_id| {
454 let trial_ref_count = trial_ref_counts[&object_id];
455 let decision = if trial_ref_count == 0 {
456 TrialDecision::Candidate
457 } else {
458 TrialDecision::Survivor
459 };
460 let final_fate =
461 if decision == TrialDecision::Candidate && garbage_set.contains(&object_id) {
462 FinalFate::Freed
463 } else {
464 FinalFate::Retained
465 };
466 ObjectDecision {
467 object_id,
468 ref_count_before: ref_count_before[&object_id],
469 heap_incoming_edges: heap_incoming.get(&object_id).copied().unwrap_or(0),
470 trial_ref_count,
471 decision,
472 final_fate,
473 }
474 })
475 .collect::<Vec<_>>();
476 decisions.sort_by_key(|decision| decision.object_id);
477 (decisions, omitted, selected_set)
478}
479
480fn select_restoration_witnesses(
481 all_witnesses: &[RestorationWitness],
482 selected_decision_ids: &HashSet<GcId>,
483) -> (Vec<RestorationWitness>, usize) {
484 let by_object: HashMap<GcId, &RestorationWitness> = all_witnesses
485 .iter()
486 .map(|witness| (witness.object_id, witness))
487 .collect();
488
489 let mut kept = Vec::new();
490 for witness in all_witnesses {
491 if kept.len() >= MAX_RESTORATION_WITNESSES {
492 break;
493 }
494 let Some(chain) = witness_chain_ids(witness, &by_object) else {
495 continue;
496 };
497 if chain.iter().any(|id| !selected_decision_ids.contains(id)) {
498 continue;
499 }
500 kept.push(witness.clone());
501 }
502 let omitted = all_witnesses.len().saturating_sub(kept.len());
503 (kept, omitted)
504}