Skip to main content

dag/set/
hints.rs

1/*
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8use std::cmp;
9use std::fmt;
10use std::sync::atomic::AtomicU32;
11use std::sync::atomic::AtomicU64;
12use std::sync::atomic::Ordering::Acquire;
13use std::sync::atomic::Ordering::Relaxed;
14use std::sync::atomic::Ordering::Release;
15use std::sync::Arc;
16
17use bitflags::bitflags;
18
19use crate::ops::DagAlgorithm;
20use crate::ops::IdConvert;
21use crate::Id;
22use crate::VerLink;
23
24bitflags! {
25    #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Clone, Copy)]
26    pub struct Flags: u32 {
27        /// Full. A full Set & other set X with compatible Dag results in X.
28        const FULL = 0x1;
29
30        /// Empty (also implies ID_DESC | ID_ASC | TOPO_DESC).
31        const EMPTY = 0x2;
32
33        /// Sorted by Id. Descending (head -> root).
34        const ID_DESC = 0x4;
35
36        /// Sorted by Id. Ascending (root -> head).
37        const ID_ASC = 0x8;
38
39        /// Sorted topologically. Descending (head -> root).
40        const TOPO_DESC = 0x10;
41
42        /// Minimal Id is known.
43        const HAS_MIN_ID = 0x20;
44
45        /// Maximum Id is known.
46        const HAS_MAX_ID = 0x40;
47
48        /// A "filter" set. It provides "contains" but iteration is inefficient.
49        const FILTER = 0x80;
50
51        /// The set contains ancestors. If X in set, any ancestor of X is also in set.
52        const ANCESTORS = 0x100;
53    }
54}
55
56/// Optimation hints.
57#[derive(Default)]
58pub struct Hints {
59    // Atomic is used for interior mutability.
60    flags: AtomicU32,
61    min_id: AtomicU64,
62    max_id: AtomicU64,
63    id_map: IdMapSnapshot,
64    dag: DagSnapshot,
65}
66
67impl Hints {
68    pub fn new_with_idmap_dag(
69        id_map: impl Into<IdMapSnapshot>,
70        dag: impl Into<DagSnapshot>,
71    ) -> Self {
72        Self {
73            id_map: id_map.into(),
74            dag: dag.into(),
75            ..Default::default()
76        }
77    }
78
79    pub fn new_inherit_idmap_dag(hints: &Self) -> Self {
80        Self::new_with_idmap_dag(hints, hints)
81    }
82
83    /// Attempt to inherit properties (IdMap and Dag snapshots) from a list of
84    /// hints. The returned hints have IdMap and Dag set to be compatible with
85    /// all other hints in the list (or set to be None if that's not possible).
86    pub fn union(hints_list: &[&Self]) -> Self {
87        let default = Self::default();
88        // Find the id_map that is compatible with all other id_maps.
89        debug_assert!(default.id_map().is_none());
90        let id_map = hints_list
91            .iter()
92            .fold(Some(&default), |opt_a, b| {
93                opt_a.and_then(
94                    |a| match a.id_map_version().partial_cmp(&b.id_map_version()) {
95                        None => None, // Incompatible sets
96                        Some(cmp::Ordering::Equal) | Some(cmp::Ordering::Greater) => Some(a),
97                        Some(cmp::Ordering::Less) => Some(b),
98                    },
99                )
100            })
101            .and_then(|a| a.id_map());
102        // Find the dag that is compatible with all other dags.
103        debug_assert!(default.dag().is_none());
104        let dag = hints_list
105            .iter()
106            .fold(Some(&default), |opt_a, b| {
107                opt_a.and_then(|a| match a.dag_version().partial_cmp(&b.dag_version()) {
108                    None => None,
109                    Some(cmp::Ordering::Equal) | Some(cmp::Ordering::Greater) => Some(a),
110                    Some(cmp::Ordering::Less) => Some(b),
111                })
112            })
113            .and_then(|a| a.dag());
114        Self {
115            id_map: IdMapSnapshot(id_map),
116            dag: DagSnapshot(dag),
117            ..Self::default()
118        }
119    }
120
121    pub fn flags(&self) -> Flags {
122        let flags = self.flags.load(Relaxed);
123        Flags::from_bits_truncate(flags)
124    }
125
126    pub fn contains(&self, flags: Flags) -> bool {
127        self.flags().contains(flags)
128    }
129
130    pub fn min_id(&self) -> Option<Id> {
131        if self.contains(Flags::HAS_MIN_ID) {
132            Some(Id(self.min_id.load(Acquire)))
133        } else {
134            None
135        }
136    }
137
138    pub fn max_id(&self) -> Option<Id> {
139        if self.contains(Flags::HAS_MAX_ID) {
140            Some(Id(self.max_id.load(Acquire)))
141        } else {
142            None
143        }
144    }
145
146    pub fn update_flags_with(&self, func: impl Fn(Flags) -> Flags) -> &Self {
147        let mut flags = func(self.flags());
148        // Automatically add "derived" flags.
149        if flags.contains(Flags::EMPTY) {
150            flags.insert(Flags::ID_ASC | Flags::ID_DESC | Flags::TOPO_DESC | Flags::ANCESTORS);
151        }
152        if flags.contains(Flags::FULL) {
153            flags.insert(Flags::ANCESTORS);
154        }
155        self.flags.store(flags.bits(), Relaxed);
156        self
157    }
158
159    pub fn add_flags(&self, flags: Flags) -> &Self {
160        self.update_flags_with(|f| f | flags);
161        self
162    }
163
164    pub fn remove_flags(&self, flags: Flags) -> &Self {
165        self.update_flags_with(|f| f - flags);
166        self
167    }
168
169    pub fn set_min_id(&self, min_id: Id) -> &Self {
170        self.min_id.store(min_id.0, Release);
171        self.add_flags(Flags::HAS_MIN_ID);
172        self
173    }
174
175    pub fn set_max_id(&self, max_id: Id) -> &Self {
176        self.max_id.store(max_id.0, Release);
177        self.add_flags(Flags::HAS_MAX_ID);
178        self
179    }
180
181    pub fn inherit_flags_min_max_id(&self, other: &Hints) -> &Self {
182        self.update_flags_with(|_| other.flags());
183        if let Some(id) = other.min_id() {
184            self.set_min_id(id);
185        }
186        if let Some(id) = other.max_id() {
187            self.set_max_id(id);
188        }
189        self
190    }
191
192    pub fn dag(&self) -> Option<Arc<dyn DagAlgorithm + Send + Sync>> {
193        self.dag.0.clone()
194    }
195
196    pub fn id_map(&self) -> Option<Arc<dyn IdConvert + Send + Sync>> {
197        self.id_map.0.clone()
198    }
199
200    /// The `VerLink` of the Dag. `None` if there is no Dag associated.
201    pub fn dag_version(&self) -> Option<&VerLink> {
202        self.dag.0.as_ref().map(|d| d.dag_version())
203    }
204
205    /// The `VerLink` of the IdMap. `None` if there is no IdMap associated.
206    pub fn id_map_version(&self) -> Option<&VerLink> {
207        self.id_map.0.as_ref().map(|d| d.map_version())
208    }
209}
210
211#[derive(Clone, Default)]
212pub struct DagSnapshot(Option<Arc<dyn DagAlgorithm + Send + Sync>>);
213
214impl From<&Hints> for DagSnapshot {
215    fn from(hints: &Hints) -> Self {
216        hints.dag.clone()
217    }
218}
219
220impl From<Arc<dyn DagAlgorithm + Send + Sync>> for DagSnapshot {
221    fn from(dag: Arc<dyn DagAlgorithm + Send + Sync>) -> Self {
222        DagSnapshot(Some(dag))
223    }
224}
225
226#[derive(Clone, Default)]
227pub struct IdMapSnapshot(Option<Arc<dyn IdConvert + Send + Sync>>);
228
229impl From<&Hints> for IdMapSnapshot {
230    fn from(hints: &Hints) -> Self {
231        hints.id_map.clone()
232    }
233}
234
235impl From<Arc<dyn IdConvert + Send + Sync>> for IdMapSnapshot {
236    fn from(dag: Arc<dyn IdConvert + Send + Sync>) -> Self {
237        IdMapSnapshot(Some(dag))
238    }
239}
240
241#[derive(Clone, Default)]
242pub struct DagVersion<'a>(Option<&'a VerLink>);
243
244impl<'a> From<&'a Hints> for DagVersion<'a> {
245    fn from(hints: &'a Hints) -> Self {
246        Self(match &hints.dag.0 {
247            Some(d) => Some(d.dag_version()),
248            None => None,
249        })
250    }
251}
252
253impl<'a> From<&'a VerLink> for DagVersion<'a> {
254    fn from(version: &'a VerLink) -> Self {
255        Self(Some(version))
256    }
257}
258
259#[derive(Clone, Default)]
260pub struct IdMapVersion<'a>(Option<&'a VerLink>);
261
262impl<'a> From<&'a Hints> for IdMapVersion<'a> {
263    fn from(hints: &'a Hints) -> Self {
264        Self(match &hints.id_map.0 {
265            Some(m) => Some(m.map_version()),
266            None => None,
267        })
268    }
269}
270
271impl<'a> From<&'a VerLink> for IdMapVersion<'a> {
272    fn from(version: &'a VerLink) -> Self {
273        Self(Some(version))
274    }
275}
276
277impl Clone for Hints {
278    fn clone(&self) -> Self {
279        Self {
280            flags: AtomicU32::new(self.flags.load(Acquire)),
281            min_id: AtomicU64::new(self.min_id.load(Acquire)),
282            max_id: AtomicU64::new(self.max_id.load(Acquire)),
283            id_map: self.id_map.clone(),
284            dag: self.dag.clone(),
285        }
286    }
287}
288
289impl fmt::Debug for Hints {
290    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
291        write!(
292            f,
293            "Hints({:?}",
294            self.flags() - (Flags::HAS_MIN_ID | Flags::HAS_MAX_ID)
295        )?;
296        match (self.min_id(), self.max_id()) {
297            (Some(min), Some(max)) => write!(f, ", {}..={}", min.0, max.0)?,
298            (Some(min), None) => write!(f, ", {}..", min.0)?,
299            (None, Some(max)) => write!(f, ", ..={}", max.0)?,
300            (None, None) => {}
301        }
302        write!(f, ")")?;
303        Ok(())
304    }
305}
306
307#[cfg(test)]
308#[test]
309fn test_incompatilbe_union() {
310    use crate::tests::dummy_dag::DummyDag;
311    let dag1 = DummyDag::new();
312    let dag2 = DummyDag::new();
313
314    let mut hints1 = Hints::default();
315    hints1.dag = DagSnapshot(Some(dag1.dag_snapshot().unwrap()));
316
317    let mut hints2 = Hints::default();
318    hints2.dag = DagSnapshot(Some(dag2.dag_snapshot().unwrap()));
319
320    assert_eq!(
321        Hints::union(&[&hints1, &hints1]).dag_version(),
322        hints1.dag_version()
323    );
324
325    assert_eq!(Hints::union(&[&hints1, &hints2]).dag_version(), None);
326    assert_eq!(Hints::union(&[&hints2, &hints1]).dag_version(), None);
327    assert_eq!(
328        Hints::union(&[&hints2, &hints1, &hints2]).dag_version(),
329        None
330    );
331}