Skip to main content

hermes_support/
deque.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 crate::HeapSize;
9
10/// Append-only deque which ensures the elements pushed into it never move.
11/// Allocates chunks in doubling capacities.
12#[derive(Debug)]
13pub struct Deque<T> {
14    storage: Vec<Vec<T>>,
15
16    /// Capacity at which to allocate the next chunk.
17    /// Doubles every chunk until reaching [`MAX_CHUNK_CAPACITY`].
18    next_chunk_capacity: usize,
19}
20
21/// Minimum chunk capacity in the deque.
22/// May be made configurable in the future.
23const MIN_CHUNK_CAPACITY: usize = 1 << 10;
24
25/// Maximum chunk capacity in the deque.
26/// May be made configurable in the future.
27const MAX_CHUNK_CAPACITY: usize = MIN_CHUNK_CAPACITY * (1 << 10);
28
29impl<T> Default for Deque<T> {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl<T> Deque<T> {
36    pub fn new() -> Self {
37        let mut result = Self {
38            storage: Default::default(),
39            next_chunk_capacity: MIN_CHUNK_CAPACITY,
40        };
41        result.new_chunk();
42        result
43    }
44
45    /// Append an element to the deque and return a reference to it.
46    /// The element will not move after it is allocated.
47    pub fn push(&mut self, val: T) -> &T {
48        let chunk = self.storage.last().unwrap();
49        if chunk.len() >= chunk.capacity() {
50            self.new_chunk();
51        }
52        let chunk = self.storage.last_mut().unwrap();
53        debug_assert!(
54            chunk.len() < chunk.capacity(),
55            "Invalid attempt to expand a chunk"
56        );
57        chunk.push(val);
58        chunk.last().unwrap()
59    }
60
61    /// Return the number of elements that have been appended to the deque.
62    pub fn len(&self) -> usize {
63        let mut result = 0;
64        for chunk in &self.storage {
65            result += chunk.len();
66        }
67        result
68    }
69
70    pub fn is_empty(&self) -> bool {
71        self.storage.is_empty()
72    }
73
74    /// Iterator over every element of the deque.
75    pub fn iter(&self) -> impl Iterator<Item = &T> {
76        self.storage.iter().flatten()
77    }
78
79    /// Iterator over every element of the deque.
80    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut T> {
81        self.storage.iter_mut().flatten()
82    }
83
84    /// Truncate the deque to `len` elements, dropping every element at
85    /// index >= `len` and freeing fully-vacated trailing chunks. Surviving
86    /// elements never move (only trailing elements/chunks are dropped), so
87    /// references to them remain valid. Used by the AST arena's
88    /// `AllocationScope` (bump-allocator save/restore semantics, mirroring
89    /// the C++ `BumpPtrAllocator::pushScope`/`popScope`,
90    /// hermes/Support/Allocator.h:500).
91    pub fn truncate(&mut self, len: usize) {
92        debug_assert!(len <= self.len(), "truncate beyond deque length");
93        let mut remaining = len;
94        let mut keep = 0usize; // number of chunks to keep
95        for chunk in &mut self.storage {
96            keep += 1;
97            if remaining < chunk.len() {
98                chunk.truncate(remaining);
99                break;
100            }
101            remaining -= chunk.len();
102            if remaining == 0 {
103                break;
104            }
105        }
106        // Always keep at least one chunk: `push` assumes storage is
107        // non-empty (deque.rs `new()` pre-creates chunk 0).
108        self.storage.truncate(keep.max(1));
109    }
110
111    /// Iterate over the elements starting at `index`. Positions by chunk
112    /// arithmetic (a handful of chunk-boundary comparisons; skipped
113    /// elements are not walked), so iterating a suffix is O(suffix).
114    /// An `index` at or past `len()` yields an empty iterator.
115    pub fn iter_from(&self, index: usize) -> impl Iterator<Item = &T> {
116        let mut skip = index;
117        let mut start_chunk = self.storage.len();
118        for (i, chunk) in self.storage.iter().enumerate() {
119            if skip < chunk.len() {
120                start_chunk = i;
121                break;
122            }
123            skip -= chunk.len();
124        }
125        self.storage[start_chunk..]
126            .iter()
127            .enumerate()
128            .flat_map(move |(i, chunk)| {
129                let s = if i == 0 { skip } else { 0 };
130                chunk[s..].iter()
131            })
132    }
133
134    /// Allocate a new chunk in the node storage.
135    fn new_chunk(&mut self) {
136        let capacity = self.next_chunk_capacity;
137        self.storage.push(Vec::with_capacity(capacity));
138
139        // Double the capacity if there's room.
140        if capacity < MAX_CHUNK_CAPACITY {
141            self.next_chunk_capacity = capacity * 2;
142        }
143    }
144}
145
146impl<T> HeapSize for Deque<T> {
147    fn heap_size(&self) -> usize {
148        let mut result = 0;
149        for chunk in &self.storage {
150            result += chunk.heap_size();
151        }
152        result
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn append() {
162        let mut d = Deque::new();
163        d.push(1);
164        d.push(2);
165        assert_eq!(d.iter().count(), 2);
166    }
167
168    #[test]
169    fn multi_chunks() {
170        let mut d = Deque::<usize>::new();
171        let count = MIN_CHUNK_CAPACITY * 2;
172        let mut addr = 0usize;
173        for i in 0..count {
174            let elem = d.push(i);
175            if i == 1000 {
176                addr = elem as *const usize as usize;
177            }
178        }
179        assert_eq!(d.iter().count(), count);
180        // The element at index 1000 must not have moved (stable addresses):
181        // re-fetch it through the iterator and confirm address + value are unchanged.
182        let again = d.iter().nth(1000).unwrap();
183        assert_eq!(again as *const usize as usize, addr);
184        assert_eq!(*again, 1000);
185    }
186
187    #[test]
188    fn truncate_within_and_across_chunks() {
189        // 2500 elements spans chunk 0 (1024) and chunk 1 (2048 capacity).
190        let mut d = Deque::new();
191        for i in 0..2500usize {
192            d.push(i);
193        }
194        assert_eq!(d.len(), 2500);
195        // Truncate within chunk 1.
196        d.truncate(1500);
197        assert_eq!(d.len(), 1500);
198        assert_eq!(d.iter().copied().last(), Some(1499));
199        // Survivors intact and re-push works.
200        assert_eq!(d.iter().nth(1023).copied(), Some(1023));
201        d.push(9999);
202        assert_eq!(d.len(), 1501);
203        assert_eq!(d.iter().copied().last(), Some(9999));
204        // Truncate dropping the whole trailing chunk.
205        d.truncate(500);
206        assert_eq!(d.len(), 500);
207        // Truncate to zero leaves a usable deque.
208        d.truncate(0);
209        assert_eq!(d.len(), 0);
210        d.push(1);
211        assert_eq!(d.len(), 1);
212        // Truncate to exactly the current length is a no-op.
213        d.truncate(1);
214        assert_eq!(d.len(), 1);
215    }
216
217    #[test]
218    fn iter_from_positions_correctly() {
219        let mut d = Deque::new();
220        for i in 0..2500usize {
221            d.push(i);
222        }
223        // Mid-chunk-1 start.
224        let v: Vec<usize> = d.iter_from(1030).copied().take(3).collect();
225        assert_eq!(v, vec![1030, 1031, 1032]);
226        // Exactly at a chunk boundary.
227        assert_eq!(d.iter_from(1024).copied().next(), Some(1024));
228        // From zero == full iteration.
229        assert_eq!(d.iter_from(0).count(), 2500);
230        // From len() and beyond: empty.
231        assert_eq!(d.iter_from(2500).count(), 0);
232        assert_eq!(d.iter_from(9999).count(), 0);
233    }
234}