Skip to main content

tycho_types/cell/
usage_tree.rs

1use super::cell_impl::VirtualCellWrapper;
2use super::{Cell, CellDescriptor, CellImpl, CellInner, DynCell, HashBytes};
3
4/// Rule for including cells in the usage tree.
5#[derive(Debug, Clone, Copy, Eq, PartialEq)]
6pub enum UsageTreeMode {
7    /// Include cell on load.
8    OnLoad,
9    /// Include cell only when accessing data.
10    OnDataAccess,
11}
12
13/// Usage tree for a family of cells.
14pub struct UsageTree {
15    state: SharedState,
16}
17
18impl UsageTree {
19    /// Creates a usage tree with the specified tracking mode.
20    pub fn new(mode: UsageTreeMode) -> Self {
21        Self {
22            state: UsageTreeState::new(mode),
23        }
24    }
25
26    /// Creates a usage tree with the specified tracking mode
27    /// and a specified starting capacity.
28    pub fn with_mode_and_capacity(mode: UsageTreeMode, capacity: usize) -> Self {
29        Self {
30            state: UsageTreeState::with_mode_and_capacity(mode, capacity),
31        }
32    }
33
34    /// Wraps the specified cell in a usage cell to keep track
35    /// of the data or links being accessed.
36    pub fn track(&self, cell: &Cell) -> Cell {
37        if self.state.mode == UsageTreeMode::OnLoad {
38            self.state.insert(cell);
39        }
40        self.state.wrap(cell.clone())
41    }
42
43    /// Returns `true` if the cell with the specified representation hash
44    /// is present in this usage tree.
45    pub fn contains(&self, repr_hash: &HashBytes) -> bool {
46        self.state.contains(repr_hash)
47    }
48
49    /// Extends the usage tree with subtree tracker.
50    pub fn with_subtrees(self) -> UsageTreeWithSubtrees {
51        UsageTreeWithSubtrees {
52            state: self.state,
53            subtrees: Default::default(),
54        }
55    }
56
57    /// Returns `true` if there are no cells in the usage tree.
58    pub fn is_empty(&self) -> bool {
59        self.state.is_empty()
60    }
61
62    /// Returns the number of cells in the usage tree.
63    pub fn len(&self) -> usize {
64        self.state.len()
65    }
66}
67
68/// Usage tree for a family of cells with subtrees.
69pub struct UsageTreeWithSubtrees {
70    state: SharedState,
71    subtrees: ahash::HashSet<HashBytes>,
72}
73
74impl UsageTreeWithSubtrees {
75    /// Wraps the specified cell in a usage cell to keep track
76    /// of the data or links being accessed.
77    pub fn track(&self, cell: &Cell) -> Cell {
78        if self.state.mode == UsageTreeMode::OnLoad {
79            self.state.insert(cell);
80        }
81        self.state.wrap(cell.clone())
82    }
83
84    /// Returns `true` if the cell with the specified representation hash
85    /// is present in this usage tree.
86    pub fn contains_direct(&self, repr_hash: &HashBytes) -> bool {
87        self.state.as_ref().contains(repr_hash)
88    }
89
90    /// Returns `true` if the subtree root with the specified representation hash
91    /// is present in this usage tree.
92    pub fn contains_subtree(&self, repr_hash: &HashBytes) -> bool {
93        self.subtrees.contains(repr_hash)
94    }
95
96    /// Adds a subtree to the usage tree.
97    /// Returns whether the value was newly inserted.
98    pub fn add_subtree(&mut self, root: &DynCell) -> bool {
99        self.subtrees.insert(*root.repr_hash())
100    }
101}
102
103#[cfg(not(feature = "sync"))]
104use self::rc::{SharedState, UsageCell, UsageTreeState};
105#[cfg(feature = "sync")]
106use self::sync::{SharedState, UsageCell, UsageTreeState};
107
108impl CellImpl for UsageCell {
109    #[inline]
110    fn untrack(self: CellInner<Self>) -> Cell {
111        UsageCell::untrack_impl(self)
112    }
113
114    fn descriptor(&self) -> CellDescriptor {
115        self.cell.descriptor()
116    }
117
118    fn data(&self) -> &[u8] {
119        if self.should_insert() {
120            if let Some(usage_tree) = self.usage_tree.upgrade() {
121                usage_tree.insert(&self.cell);
122            } else {
123                self.set_orphaned();
124            }
125            self.set_inserted();
126        }
127        self.cell.data()
128    }
129
130    fn bit_len(&self) -> u16 {
131        self.cell.bit_len()
132    }
133
134    fn reference(&self, index: u8) -> Option<&DynCell> {
135        if self.is_orphaned() {
136            self.cell.reference(index)
137        } else {
138            Some(self.load_reference(index)?.as_ref())
139        }
140    }
141
142    fn reference_cloned(&self, index: u8) -> Option<Cell> {
143        if self.is_orphaned() {
144            self.cell.reference_cloned(index)
145        } else {
146            let cell = self.load_reference(index)?.clone();
147
148            #[cfg(not(feature = "sync"))]
149            {
150                Some(Cell::from(cell as std::rc::Rc<DynCell>))
151            }
152
153            #[cfg(feature = "sync")]
154            {
155                Some(Cell::from(cell as std::sync::Arc<DynCell>))
156            }
157        }
158    }
159
160    fn reference_repr_hash(&self, index: u8) -> Option<HashBytes> {
161        self.cell.reference_repr_hash(index)
162    }
163
164    fn virtualize(&self) -> &DynCell {
165        VirtualCellWrapper::wrap(self)
166    }
167
168    fn hash(&self, level: u8) -> &HashBytes {
169        self.cell.hash(level)
170    }
171
172    fn depth(&self, level: u8) -> u16 {
173        self.cell.depth(level)
174    }
175}
176
177#[cfg(not(feature = "sync"))]
178mod rc {
179    use std::rc::{Rc, Weak};
180
181    use super::UsageTreeMode;
182    use crate::cell::{Cell, DynCell, HashBytes};
183
184    pub type SharedState = Rc<UsageTreeState>;
185
186    type VisitedCells = std::cell::RefCell<ahash::HashSet<HashBytes>>;
187
188    pub struct UsageTreeState {
189        pub mode: UsageTreeMode,
190        pub visited: VisitedCells,
191    }
192
193    impl UsageTreeState {
194        pub fn new(mode: UsageTreeMode) -> SharedState {
195            Rc::new(Self {
196                mode,
197                visited: Default::default(),
198            })
199        }
200
201        pub fn with_mode_and_capacity(mode: UsageTreeMode, capacity: usize) -> SharedState {
202            Rc::new(Self {
203                mode,
204                visited: std::cell::RefCell::new(ahash::HashSet::with_capacity_and_hasher(
205                    capacity,
206                    Default::default(),
207                )),
208            })
209        }
210
211        pub fn wrap(self: &SharedState, cell: Cell) -> Cell {
212            Cell::from(Rc::new(UsageCell::new(cell, Rc::downgrade(self), self.mode)) as Rc<DynCell>)
213        }
214
215        #[inline]
216        pub fn insert(&self, cell: &Cell) {
217            self.visited.borrow_mut().insert(*cell.repr_hash());
218        }
219
220        #[inline]
221        pub fn contains(&self, repr_hash: &HashBytes) -> bool {
222            self.visited.borrow().contains(repr_hash)
223        }
224
225        #[inline]
226        pub fn is_empty(&self) -> bool {
227            self.visited.borrow().is_empty()
228        }
229
230        #[inline]
231        pub fn len(&self) -> usize {
232            self.visited.borrow().len()
233        }
234    }
235
236    pub struct UsageCell {
237        pub cell: Cell,
238        pub usage_tree: Weak<UsageTreeState>,
239        pub children: std::cell::UnsafeCell<[Option<Rc<Self>>; 4]>,
240        pub inserted: std::cell::Cell<bool>,
241        pub orphaned: std::cell::Cell<bool>,
242        pub mode: UsageTreeMode,
243    }
244
245    impl UsageCell {
246        pub fn new(cell: Cell, usage_tree: Weak<UsageTreeState>, mode: UsageTreeMode) -> Self {
247            Self {
248                cell,
249                usage_tree,
250                children: Default::default(),
251                inserted: std::cell::Cell::new(mode == UsageTreeMode::OnLoad),
252                orphaned: std::cell::Cell::new(false),
253                mode,
254            }
255        }
256
257        pub fn untrack_impl(self: Rc<Self>) -> Cell {
258            self.cell.clone()
259        }
260
261        #[inline]
262        pub fn is_orphaned(&self) -> bool {
263            self.orphaned.get()
264        }
265
266        #[inline]
267        pub fn set_orphaned(&self) {
268            self.orphaned.set(true);
269        }
270
271        #[inline]
272        pub fn should_insert(&self) -> bool {
273            !self.inserted.get()
274        }
275
276        #[inline]
277        pub fn set_inserted(&self) {
278            self.inserted.set(true);
279        }
280
281        pub fn load_reference(&self, index: u8) -> Option<&Rc<Self>> {
282            if index < 4 {
283                let children = unsafe { &mut *self.children.get() };
284                Some(match &mut children[index as usize] {
285                    Some(value) => value,
286                    slot @ None => {
287                        let child = self.cell.as_ref().reference_cloned(index)?;
288                        if self.mode == UsageTreeMode::OnLoad {
289                            if let Some(usage_tree) = self.usage_tree.upgrade() {
290                                usage_tree.insert(&child);
291                            } else {
292                                self.set_orphaned();
293                            }
294                        }
295
296                        slot.insert(Rc::new(UsageCell::new(
297                            child,
298                            self.usage_tree.clone(),
299                            self.mode,
300                        )))
301                    }
302                })
303            } else {
304                None
305            }
306        }
307    }
308}
309
310#[cfg(feature = "sync")]
311mod sync {
312    use std::cell::UnsafeCell;
313    use std::sync::atomic::{AtomicBool, Ordering};
314    use std::sync::{Arc, Once, Weak};
315
316    use super::UsageTreeMode;
317    use crate::cell::{Cell, DynCell, HashBytes};
318
319    pub type SharedState = Arc<UsageTreeState>;
320
321    type VisitedCells = scc::HashSet<HashBytes, ahash::RandomState>;
322
323    pub struct UsageTreeState {
324        pub mode: UsageTreeMode,
325        pub visited: VisitedCells,
326    }
327
328    impl UsageTreeState {
329        pub fn new(mode: UsageTreeMode) -> SharedState {
330            Arc::new(Self {
331                mode,
332                visited: Default::default(),
333            })
334        }
335
336        pub fn with_mode_and_capacity(mode: UsageTreeMode, capacity: usize) -> SharedState {
337            Arc::new(Self {
338                mode,
339                visited: VisitedCells::with_capacity_and_hasher(capacity, Default::default()),
340            })
341        }
342
343        pub fn wrap(self: &SharedState, cell: Cell) -> Cell {
344            Cell::from(
345                Arc::new(UsageCell::new(cell, Arc::downgrade(self), self.mode)) as Arc<DynCell>,
346            )
347        }
348
349        #[inline]
350        pub fn insert(&self, cell: &Cell) {
351            _ = self.visited.insert(*cell.repr_hash());
352        }
353
354        #[inline]
355        pub fn contains(&self, repr_hash: &HashBytes) -> bool {
356            self.visited.contains(repr_hash)
357        }
358
359        #[inline]
360        pub fn is_empty(&self) -> bool {
361            self.visited.is_empty()
362        }
363
364        #[inline]
365        pub fn len(&self) -> usize {
366            self.visited.len()
367        }
368    }
369
370    pub struct UsageCell {
371        pub cell: Cell,
372        pub usage_tree: Weak<UsageTreeState>,
373        // TODO: Compress into one futex with bitset.
374        pub reference_states: [Once; 4],
375        pub reference_data: [UnsafeCell<Option<Arc<Self>>>; 4],
376        pub inserted: AtomicBool,
377        pub orphaned: AtomicBool,
378        pub mode: UsageTreeMode,
379    }
380
381    impl UsageCell {
382        pub fn new(cell: Cell, usage_tree: Weak<UsageTreeState>, mode: UsageTreeMode) -> Self {
383            Self {
384                // NOTE: Untrack loaded cell to prevent its `UsageTree`s from stacking.
385                cell: Cell::untrack(cell),
386                usage_tree,
387                reference_states: [(); 4].map(|_| Once::new()),
388                reference_data: [(); 4].map(|_| UnsafeCell::new(None)),
389                inserted: AtomicBool::new(mode == UsageTreeMode::OnLoad),
390                orphaned: AtomicBool::new(false),
391                mode,
392            }
393        }
394
395        pub fn untrack_impl(self: Arc<Self>) -> Cell {
396            match Arc::try_unwrap(self) {
397                Ok(inner) => inner.cell,
398                Err(this) => this.cell.clone(),
399            }
400        }
401
402        #[inline]
403        pub fn is_orphaned(&self) -> bool {
404            self.orphaned.load(Ordering::Acquire)
405        }
406
407        #[inline]
408        pub fn set_orphaned(&self) {
409            self.orphaned.store(true, Ordering::Release);
410        }
411
412        #[inline]
413        pub fn should_insert(&self) -> bool {
414            !self.inserted.load(Ordering::Acquire)
415        }
416
417        #[inline]
418        pub fn set_inserted(&self) {
419            self.inserted.store(true, Ordering::Release);
420        }
421
422        pub fn load_reference(&self, index: u8) -> Option<&Arc<Self>> {
423            if index < 4 {
424                let mut should_insert = false;
425                self.reference_states[index as usize].call_once_force(|_| {
426                    let Some(child) = self.cell.as_ref().reference_cloned(index) else {
427                        // NOTE: Don't forget to set `None` here if `MaybeUninit` is used.
428                        return;
429                    };
430
431                    should_insert = self.mode == UsageTreeMode::OnLoad;
432
433                    // SAFETY: `UnsafeCell` data is controlled by the `Once` state.
434                    unsafe {
435                        *self.reference_data[index as usize].get() = Some(Arc::new(Self::new(
436                            child,
437                            self.usage_tree.clone(),
438                            self.mode,
439                        )));
440                    };
441                });
442
443                // SAFETY: `UnsafeCell` data is controlled by the `Once` state.
444                let child = unsafe { &*self.reference_data[index as usize].get().cast_const() };
445                if crate::util::unlikely(should_insert)
446                    && let Some(child) = child
447                {
448                    if let Some(usage_tree) = self.usage_tree.upgrade() {
449                        usage_tree.insert(&child.cell);
450                    } else {
451                        self.set_orphaned();
452                    }
453                }
454
455                child.as_ref()
456            } else {
457                None
458            }
459        }
460    }
461
462    // SAFETY: `UnsafeCell` data is controlled by the `Once` state.
463    unsafe impl Send for UsageCell {}
464    unsafe impl Sync for UsageCell {}
465}