rspirv/sr/
storage.rs

1use std::{fmt, marker::PhantomData};
2
3/// An unique index in the storage array that a token points to.
4///
5/// This type is independent of `spirv::Word`. `spirv::Word` is used in data
6/// representation. It holds a SPIR-V and refers to that instruction. In
7/// structured representation, we use Token to refer to an SPIR-V instruction.
8/// Index is an implementation detail to Token.
9type Index = u32;
10
11/// A strongly typed reference to a SPIR-V element.
12pub struct Token<T> {
13    index: Index,
14    marker: PhantomData<T>,
15}
16
17impl<T> Clone for Token<T> {
18    fn clone(&self) -> Self {
19        *self
20    }
21}
22impl<T> Copy for Token<T> {}
23impl<T> PartialEq for Token<T> {
24    fn eq(&self, other: &Self) -> bool {
25        self.index == other.index
26    }
27}
28impl<T> Eq for Token<T> {}
29impl<T> fmt::Debug for Token<T> {
30    fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
31        write!(formatter, "Token({})", self.index)
32    }
33}
34
35impl<T> Token<T> {
36    #[cfg(test)]
37    pub const DUMMY: Self = Token {
38        index: !0,
39        marker: PhantomData,
40    };
41
42    pub(in crate::sr) fn new(index: Index) -> Self {
43        Token {
44            index,
45            marker: PhantomData,
46        }
47    }
48
49    pub fn index(&self) -> Index {
50        self.index
51    }
52}
53
54/// A structure holding some kind of SPIR-V entity (e.g., type, constant,
55/// instruction, etc.) that can be referenced.
56#[derive(Debug, Default)]
57pub struct Storage<T> {
58    /// Values of this storage.
59    data: Vec<T>,
60}
61
62impl<T> Storage<T> {
63    pub fn new() -> Self {
64        Storage { data: Vec::new() }
65    }
66
67    /// Adds a new value to the storage, returning a typed token.
68    ///
69    /// The value is not linked to any SPIR-V module.
70    pub fn append(&mut self, value: T) -> Token<T> {
71        let index = self.data.len() as Index;
72        self.data.push(value);
73        Token::new(index)
74    }
75
76    /// Adds a value with a check for uniqueness: returns a token pointing to
77    /// an existing element if its value matches the given one, or adds a new
78    /// element otherwise.
79    pub fn fetch_or_append(&mut self, value: T) -> Token<T>
80    where
81        T: PartialEq,
82    {
83        if let Some(index) = self.data.iter().position(|d| d == &value) {
84            Token::new(index as Index)
85        } else {
86            self.append(value)
87        }
88    }
89}
90
91impl<T> std::ops::Index<Token<T>> for Storage<T> {
92    type Output = T;
93    fn index(&self, token: Token<T>) -> &T {
94        &self.data[token.index as usize]
95    }
96}
97
98#[cfg(test)]
99#[allow(clippy::float_cmp)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn append_non_unique() {
105        let mut storage: Storage<f64> = Storage::new();
106        let t1 = storage.append(0.0);
107        let t2 = storage.append(0.0);
108        assert!(t1 != t2);
109        assert!(storage[t1] == storage[t2]);
110    }
111
112    #[test]
113    fn append_unique() {
114        let mut storage: Storage<f64> = Storage::new();
115        let t1 = storage.append(std::f64::NAN);
116        let t2 = storage.append(std::f64::NAN);
117        assert!(t1 != t2);
118        assert!(storage[t1] != storage[t2]);
119    }
120
121    #[test]
122    fn fetch_or_append_non_unique() {
123        let mut storage: Storage<f64> = Storage::new();
124        let t1 = storage.fetch_or_append(0.0);
125        let t2 = storage.fetch_or_append(0.0);
126        assert!(t1 == t2);
127        assert!(storage[t1] == storage[t2])
128    }
129
130    #[test]
131    fn fetch_or_append_unique() {
132        let mut storage: Storage<f64> = Storage::new();
133        let t1 = storage.fetch_or_append(std::f64::NAN);
134        let t2 = storage.fetch_or_append(std::f64::NAN);
135        assert!(t1 != t2);
136        assert!(storage[t1] != storage[t2]);
137    }
138}