Skip to main content

rustc_middle/dep_graph/
edges.rs

1//! How a task's reads are recorded and deduplicated into the edge list of its node.
2
3use rustc_data_structures::sync::Lock;
4use rustc_index::IndexVec;
5
6use super::DepNodeIndex;
7
8/// How many reads fit in [`TaskReads::Small`]'s inline buffer.
9pub(crate) const SMALL_READS_MAX: usize = 16;
10
11/// 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.
15    Small { len: u8, buf: [DepNodeIndex; SMALL_READS_MAX] },
16
17    /// The reads of a task that outgrew the inline buffer.
18    Recorded(ReadsRecorder),
19}
20
21impl TaskReads {
22    #[inline]
23    pub(crate) fn new() -> Self {
24        TaskReads::Small { len: 0, buf: [DepNodeIndex::ZERO; SMALL_READS_MAX] }
25    }
26
27    /// 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]
30    pub(crate) fn insert(&mut self, index: DepNodeIndex, pool: &Lock<Vec<ReadsRecorder>>) -> bool {
31        match self {
32            TaskReads::Recorded(recorder) => recorder.insert(index),
33            TaskReads::Small { len, buf } => {
34                let n = usize::from(*len);
35                if buf[..n].contains(&index) {
36                    false
37                } else if n < SMALL_READS_MAX {
38                    buf[n] = index;
39                    *len += 1;
40                    true
41                } else {
42                    // The inline buffer is full: move the reads into a pooled recorder.
43                    let seed = *buf;
44                    let mut recorder = pool.lock().pop().unwrap_or_default();
45                    recorder.clear();
46                    recorder.seed(&seed);
47                    recorder.insert(index);
48                    *self = TaskReads::Recorded(recorder);
49                    true
50                }
51            }
52        }
53    }
54
55    /// The task's deduplicated reads, in first-read order.
56    #[inline]
57    pub(crate) fn edges(&self) -> &[DepNodeIndex] {
58        match self {
59            TaskReads::Small { len, buf } => &buf[..usize::from(*len)],
60            TaskReads::Recorded(recorder) => &recorder.reads,
61        }
62    }
63}
64
65/// 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.
72    reads: Vec<DepNodeIndex>,
73    /// Seen-before filter: a slot counts as seen only while it holds `epoch`.
74    epochs: IndexVec<DepNodeIndex, u8>,
75    epoch: u8,
76}
77
78impl ReadsRecorder {
79    /// Seeds a fresh recorder with reads that are already known to be distinct.
80    fn seed(&mut self, reads: &[DepNodeIndex]) {
81        if true {
    if !self.reads.is_empty() {
        ::core::panicking::panic("assertion failed: self.reads.is_empty()")
    };
};debug_assert!(self.reads.is_empty());
82        let epoch = self.epoch;
83        for &index in reads {
84            *self.slot(index) = epoch;
85        }
86        self.reads.extend_from_slice(reads);
87    }
88
89    /// Records a read of `index` and returns whether it was new for the current task.
90    #[inline]
91    fn insert(&mut self, index: DepNodeIndex) -> bool {
92        let epoch = self.epoch;
93        let slot = self.slot(index);
94        if *slot == epoch {
95            false
96        } else {
97            *slot = epoch;
98            self.reads.push(index);
99            true
100        }
101    }
102
103    /// The epoch slot for `index`, growing the filter (with slack) to cover it.
104    #[inline]
105    fn slot(&mut self, index: DepNodeIndex) -> &mut u8 {
106        let index = index.as_usize();
107        if 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.
112            self.epochs.raw.resize((index + 1).next_power_of_two().max(64), 0);
113        }
114        &mut self.epochs.raw[index]
115    }
116
117    /// 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.
119    fn clear(&mut self) {
120        self.reads.clear();
121        self.epoch = self.epoch.wrapping_add(1);
122        if self.epoch == 0 {
123            self.epochs.raw.fill(0);
124            self.epoch = 1;
125        }
126    }
127
128    /// Returns a finished task's recorder to the pool. The cap keeps deeply nested tasks
129    /// from growing the pool without bound.
130    #[inline]
131    pub(crate) fn release(self, pool: &Lock<Vec<ReadsRecorder>>) {
132        const MAX_POOLED_RECORDERS: usize = 4;
133        let mut pool = pool.lock();
134        if pool.len() < MAX_POOLED_RECORDERS {
135            pool.push(self);
136        }
137    }
138}