1//! How a task's reads are recorded and deduplicated into the edge list of its node.
23use rustc_data_structures::sync::Lock;
4use rustc_index::IndexVec;
56use super::DepNodeIndex;
78/// How many reads fit in [`TaskReads::Small`]'s inline buffer.
9pub(crate) const SMALL_READS_MAX: usize = 16;
1011/// The reads recorded by one task so far, deduplicated and in first-read order.
12#[derive(#[automatically_derived]
impl ::core::fmt::Debug for TaskReads {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
match self {
TaskReads::Small { len: __self_0, buf: __self_1 } =>
::core::fmt::Formatter::debug_struct_field2_finish(f, "Small",
"len", __self_0, "buf", &__self_1),
TaskReads::Recorded(__self_0) =>
::core::fmt::Formatter::debug_tuple_field1_finish(f,
"Recorded", &__self_0),
}
}
}Debug)]
13pub(crate) enum TaskReads {
14/// The first few reads, deduplicated by a linear scan. Most tasks never outgrow this.
15Small { len: u8, buf: [DepNodeIndex; SMALL_READS_MAX] },
1617/// The reads of a task that outgrew the inline buffer.
18Recorded(ReadsRecorder),
19}
2021impl TaskReads {
22#[inline]
23pub(crate) fn new() -> Self {
24 TaskReads::Small { len: 0, buf: [DepNodeIndex::ZERO; SMALL_READS_MAX] }
25 }
2627/// Records a read and returns whether it was new for the task. The pool provides a
28 /// recorder when the task outgrows the inline buffer.
29#[inline]
30pub(crate) fn insert(&mut self, index: DepNodeIndex, pool: &Lock<Vec<ReadsRecorder>>) -> bool {
31match self {
32 TaskReads::Recorded(recorder) => recorder.insert(index),
33 TaskReads::Small { len, buf } => {
34let n = usize::from(*len);
35if buf[..n].contains(&index) {
36false
37} else if n < SMALL_READS_MAX {
38buf[n] = index;
39*len += 1;
40true
41} else {
42// The inline buffer is full: move the reads into a pooled recorder.
43let seed = *buf;
44let mut recorder = pool.lock().pop().unwrap_or_default();
45recorder.clear();
46recorder.seed(&seed);
47recorder.insert(index);
48*self = TaskReads::Recorded(recorder);
49true
50}
51 }
52 }
53 }
5455/// The task's deduplicated reads, in first-read order.
56#[inline]
57pub(crate) fn edges(&self) -> &[DepNodeIndex] {
58match self {
59 TaskReads::Small { len, buf } => &buf[..usize::from(*len)],
60 TaskReads::Recorded(recorder) => &recorder.reads,
61 }
62 }
63}
6465/// Records the reads of a task with many reads.
66///
67/// Recorders are pooled globally, reusing the read list and epoch filter allocations
68/// across tasks.
69#[derive(#[automatically_derived]
impl ::core::fmt::Debug for ReadsRecorder {
#[inline]
fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result {
::core::fmt::Formatter::debug_struct_field3_finish(f, "ReadsRecorder",
"reads", &self.reads, "epochs", &self.epochs, "epoch",
&&self.epoch)
}
}Debug, #[automatically_derived]
impl ::core::default::Default for ReadsRecorder {
#[inline]
fn default() -> ReadsRecorder {
ReadsRecorder {
reads: ::core::default::Default::default(),
epochs: ::core::default::Default::default(),
epoch: ::core::default::Default::default(),
}
}
}Default)]
70pub(crate) struct ReadsRecorder {
71/// The deduplicated reads, in first-read order.
72reads: Vec<DepNodeIndex>,
73/// Seen-before filter: a slot counts as seen only while it holds `epoch`.
74epochs: IndexVec<DepNodeIndex, u8>,
75 epoch: u8,
76}
7778impl ReadsRecorder {
79/// Seeds a fresh recorder with reads that are already known to be distinct.
80fn seed(&mut self, reads: &[DepNodeIndex]) {
81if true {
if !self.reads.is_empty() {
::core::panicking::panic("assertion failed: self.reads.is_empty()")
};
};debug_assert!(self.reads.is_empty());
82let epoch = self.epoch;
83for &index in reads {
84*self.slot(index) = epoch;
85 }
86self.reads.extend_from_slice(reads);
87 }
8889/// Records a read of `index` and returns whether it was new for the current task.
90#[inline]
91fn insert(&mut self, index: DepNodeIndex) -> bool {
92let epoch = self.epoch;
93let slot = self.slot(index);
94if *slot == epoch {
95false
96} else {
97*slot = epoch;
98self.reads.push(index);
99true
100}
101 }
102103/// The epoch slot for `index`, growing the filter (with slack) to cover it.
104#[inline]
105fn slot(&mut self, index: DepNodeIndex) -> &mut u8 {
106let index = index.as_usize();
107if index >= self.epochs.raw.len() {
108// New indices keep showing up all session, so growing to exactly
109 // `index + 1` (e.g. `ensure_contains_elem`) would resize again and
110 // again. The power-of-two slack makes resizes rare, and starting
111 // at 64 skips the smallest sizes.
112self.epochs.raw.resize((index + 1).next_power_of_two().max(64), 0);
113 }
114&mut self.epochs.raw[index]
115 }
116117/// Prepares the recorder for a new task, keeping the backing allocations. Bumping the
118 /// epoch invalidates every slot at once; on wraparound the slots are zeroed.
119fn clear(&mut self) {
120self.reads.clear();
121self.epoch = self.epoch.wrapping_add(1);
122if self.epoch == 0 {
123self.epochs.raw.fill(0);
124self.epoch = 1;
125 }
126 }
127128/// Returns a finished task's recorder to the pool. The cap keeps deeply nested tasks
129 /// from growing the pool without bound.
130#[inline]
131pub(crate) fn release(self, pool: &Lock<Vec<ReadsRecorder>>) {
132const MAX_POOLED_RECORDERS: usize = 4;
133let mut pool = pool.lock();
134if pool.len() < MAX_POOLED_RECORDERS {
135pool.push(self);
136 }
137 }
138}