Skip to main content

rkyv/ser/allocator/
alloc.rs

1use core::{
2    alloc::Layout,
3    marker::PhantomData,
4    mem::{align_of, size_of, ManuallyDrop},
5    ptr::{slice_from_raw_parts_mut, NonNull},
6};
7
8use crate::{
9    alloc::alloc::{alloc, dealloc, handle_alloc_error},
10    ser::Allocator,
11};
12
13struct Block {
14    next_ptr: NonNull<Block>,
15    next_size: usize,
16}
17
18impl Block {
19    fn alloc(size: usize) -> NonNull<Self> {
20        debug_assert!(size >= size_of::<Self>());
21        let layout = Layout::from_size_align(size, align_of::<Self>()).unwrap();
22        let ptr = unsafe { alloc(layout).cast::<Self>() };
23        let Some(ptr) = NonNull::new(ptr) else {
24            handle_alloc_error(layout)
25        };
26
27        unsafe {
28            ptr.as_ptr().write(Self {
29                next_ptr: ptr,
30                next_size: layout.size(),
31            });
32        }
33
34        ptr
35    }
36
37    unsafe fn dealloc(ptr: NonNull<Self>, size: usize) {
38        let layout = unsafe {
39            Layout::from_size_align_unchecked(size, align_of::<Self>())
40        };
41        unsafe {
42            dealloc(ptr.as_ptr().cast(), layout);
43        }
44    }
45
46    /// # Safety
47    ///
48    /// `tail_ptr` and `new_ptr` must point to valid `Block`s and `new_ptr` must
49    /// be the only block in its loop.
50    unsafe fn push_next(
51        mut tail_ptr: NonNull<Self>,
52        mut new_ptr: NonNull<Self>,
53    ) {
54        let tail = unsafe { tail_ptr.as_mut() };
55        let new = unsafe { new_ptr.as_mut() };
56
57        debug_assert!(new.next_ptr == new_ptr);
58
59        let head = tail.next_ptr;
60        let head_cap = tail.next_size;
61        tail.next_ptr = new_ptr;
62        tail.next_size = new.next_size;
63        new.next_ptr = head;
64        new.next_size = head_cap;
65    }
66}
67
68/// An arena allocator for allocations.
69///
70/// Reusing the same arena for multiple serializations will reduce the number of
71/// global allocations, which can save a considerable amount of time.
72pub struct Arena {
73    head_ptr: NonNull<Block>,
74}
75
76// SAFETY: Arena is safe to send to other threads
77unsafe impl Send for Arena {}
78
79impl Drop for Arena {
80    fn drop(&mut self) {
81        self.shrink();
82        let head_size = unsafe { self.head_ptr.as_ref().next_size };
83        unsafe {
84            Block::dealloc(self.head_ptr, head_size);
85        }
86    }
87}
88
89impl Arena {
90    /// The default capacity for arenas.
91    pub const DEFAULT_CAPACITY: usize = 1024;
92
93    /// Creates a new `Arena` with the default capacity.
94    pub fn new() -> Self {
95        Self::with_capacity(Self::DEFAULT_CAPACITY)
96    }
97
98    /// Creates a new `Arena` with at least the requested capacity.
99    pub fn with_capacity(cap: usize) -> Self {
100        let head_size = cap
101            .checked_add(size_of::<Block>())
102            .and_then(|s| s.checked_next_power_of_two())
103            .expect("Arena capacity overflow");
104        let head_ptr = Block::alloc(head_size);
105        Self { head_ptr }
106    }
107
108    /// Cleans up allocated blocks which are no longer in use.
109    ///
110    /// The arena is automatically shrunk by [`acquire`](Self::acquire).
111    pub fn shrink(&mut self) -> usize {
112        let (mut current_ptr, mut current_size) = {
113            let head = unsafe { self.head_ptr.as_ref() };
114            (head.next_ptr, head.next_size)
115        };
116
117        loop {
118            let current = unsafe { current_ptr.as_mut() };
119
120            if current.next_ptr == current_ptr {
121                // There was only one block in the loop. No deallocating needed.
122                break;
123            }
124
125            let next_ptr = current.next_ptr;
126            let next_size = current.next_size;
127
128            if next_ptr == self.head_ptr {
129                // End of the loop. Free the head block.
130                unsafe {
131                    Block::dealloc(next_ptr, next_size);
132                }
133
134                // Loop the head back on itself.
135                current.next_ptr = current_ptr;
136                current.next_size = current_size;
137                self.head_ptr = current_ptr;
138
139                break;
140            }
141
142            unsafe {
143                Block::dealloc(current_ptr, current_size);
144            }
145
146            current_ptr = next_ptr;
147            current_size = next_size;
148        }
149
150        current_size - size_of::<Block>()
151    }
152
153    /// Returns the available capacity of the arena.
154    pub fn capacity(&self) -> usize {
155        let mut current_ptr = self.head_ptr;
156        loop {
157            let current = unsafe { current_ptr.as_ref() };
158            if current.next_ptr == self.head_ptr {
159                break current.next_size - size_of::<Block>();
160            }
161            current_ptr = current.next_ptr;
162        }
163    }
164
165    /// Acquires a handle to the arena.
166    ///
167    /// The returned handle has exclusive allocation rights in the arena.
168    pub fn acquire(&mut self) -> ArenaHandle<'_> {
169        self.shrink();
170
171        ArenaHandle {
172            tail_ptr: self.head_ptr,
173            tail_size: unsafe { self.head_ptr.as_ref().next_size },
174            used: size_of::<Block>(),
175            _phantom: PhantomData,
176        }
177    }
178
179    /// Consumes the `Arena`, returning a raw pointer.
180    pub fn into_raw(self) -> NonNull<()> {
181        let this = ManuallyDrop::new(self);
182        this.head_ptr.cast()
183    }
184
185    /// Constructs an arena from a raw pointer.
186    ///
187    /// # Safety
188    ///
189    /// `raw` must have been returned from `into_raw`. `from_raw` takes
190    /// ownership over the pointer, and so `from_raw` must not be called on the
191    /// same pointer more than once.
192    pub unsafe fn from_raw(raw: NonNull<()>) -> Self {
193        Self {
194            head_ptr: raw.cast(),
195        }
196    }
197}
198
199impl Default for Arena {
200    fn default() -> Self {
201        Self::new()
202    }
203}
204
205/// A handle which can allocate within an arena.
206pub struct ArenaHandle<'a> {
207    tail_ptr: NonNull<Block>,
208    tail_size: usize,
209    used: usize,
210    _phantom: PhantomData<&'a mut Arena>,
211}
212
213// SAFETY: ArenaHandle is safe to send to other threads
214unsafe impl Send for ArenaHandle<'_> {}
215
216unsafe impl<E> Allocator<E> for ArenaHandle<'_> {
217    unsafe fn push_alloc(
218        &mut self,
219        layout: Layout,
220    ) -> Result<NonNull<[u8]>, E> {
221        let pos = self.tail_ptr.as_ptr() as usize + self.used;
222        let pad = 0usize.wrapping_sub(pos) % layout.align();
223        if pad + layout.size() <= self.tail_size - self.used {
224            self.used += pad;
225        } else {
226            // Allocation request is too large, allocate a new block
227            let size = usize::max(
228                2 * self.tail_size,
229                (size_of::<Block>() + layout.size() + layout.align())
230                    .next_power_of_two(),
231            );
232            let next = Block::alloc(size);
233            unsafe {
234                Block::push_next(self.tail_ptr, next);
235            }
236            self.tail_ptr = next;
237            self.tail_size = size;
238            let pos = self.tail_ptr.as_ptr() as usize + size_of::<Block>();
239            let pad = 0usize.wrapping_sub(pos) % layout.align();
240            self.used = size_of::<Block>() + pad;
241        }
242
243        // SAFETY: `self.used` is always less than the length of the allocated
244        // block that `tail_ptr` points to.
245        let ptr = unsafe { self.tail_ptr.as_ptr().cast::<u8>().add(self.used) };
246        let slice_ptr = slice_from_raw_parts_mut(ptr, layout.size());
247        // SAFETY: `slice_ptr` is guaranteed not to be null because it is offset
248        // from `self.tail_ptr` which is always non-null.
249        let result = unsafe { NonNull::new_unchecked(slice_ptr) };
250        self.used += layout.size();
251        Ok(result)
252    }
253
254    unsafe fn pop_alloc(
255        &mut self,
256        ptr: NonNull<u8>,
257        _: Layout,
258    ) -> Result<(), E> {
259        // If the popped allocation was in the current tail block, then we can
260        // reduce the amount of used space.
261        let start = self.tail_ptr.as_ptr() as usize;
262        let end = start + self.tail_size;
263        let pos = ptr.as_ptr() as usize;
264        if (start..end).contains(&pos) {
265            self.used = pos - start;
266        }
267
268        Ok(())
269    }
270}
271
272#[cfg(test)]
273mod tests {
274    use core::alloc::Layout;
275
276    use rancor::{Panic, ResultExt};
277
278    use crate::{
279        alloc::{string::ToString, vec},
280        api::high::to_bytes_in_with_alloc,
281        ser::{allocator::Arena, Allocator},
282        util::AlignedVec,
283    };
284
285    #[test]
286    fn reuse_arena() {
287        let mut arena = Arena::with_capacity(2);
288
289        let value = vec![
290            "hello".to_string(),
291            "world".to_string(),
292            "foo".to_string(),
293            "bar".to_string(),
294            "baz".to_string(),
295        ];
296
297        for _ in 0..10 {
298            to_bytes_in_with_alloc::<_, _, Panic>(
299                &value,
300                AlignedVec::<16>::new(),
301                arena.acquire(),
302            )
303            .unwrap();
304        }
305    }
306
307    #[test]
308    fn pop_non_tail() {
309        let mut arena = Arena::new();
310        let mut handle = arena.acquire();
311
312        let layout =
313            Layout::from_size_align(Arena::DEFAULT_CAPACITY, 1).unwrap();
314
315        unsafe {
316            let a =
317                Allocator::<Panic>::push_alloc(&mut handle, layout).always_ok();
318            let b =
319                Allocator::<Panic>::push_alloc(&mut handle, layout).always_ok();
320            Allocator::<Panic>::pop_alloc(&mut handle, b.cast(), layout)
321                .always_ok();
322            Allocator::<Panic>::pop_alloc(&mut handle, a.cast(), layout)
323                .always_ok();
324        }
325    }
326}