Skip to main content

cadmpeg_core/decode/
arena.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Append-only byte store with stable slice addresses.
3
4use std::cell::RefCell;
5use std::ptr::NonNull;
6
7/// Owns stable byte buffers allocated during a decode.
8#[derive(Debug, Default)]
9pub struct DecodeArena {
10    buffers: RefCell<Vec<OwnedBuffer>>,
11}
12
13impl DecodeArena {
14    /// Creates an empty arena.
15    pub fn new() -> Self {
16        Self::default()
17    }
18
19    /// Stores `bytes` and returns a borrow valid for the arena's lifetime.
20    ///
21    /// The returned slice is stable: later `alloc` calls never invalidate it.
22    pub fn alloc(&self, bytes: Box<[u8]>) -> &[u8] {
23        let mut buffers = self.buffers.borrow_mut();
24        let buffer = OwnedBuffer(NonNull::from(Box::leak(bytes)));
25        let pointer = buffer.0;
26        buffers.push(buffer);
27        // SAFETY: the arena owns every buffer until it is dropped, and no
28        // method mutates a buffer. The borrow cannot outlive the arena.
29        unsafe { pointer.as_ref() }
30    }
31}
32
33/// Owns one allocation without imposing a movable Box's unique-borrow tag.
34#[derive(Debug)]
35struct OwnedBuffer(NonNull<[u8]>);
36
37// SAFETY: the allocation is uniquely owned, contains only bytes, and has no
38// mutation API. Shared arena borrows cannot cross threads because RefCell
39// makes DecodeArena !Sync.
40unsafe impl Send for OwnedBuffer {}
41
42impl Drop for OwnedBuffer {
43    fn drop(&mut self) {
44        // SAFETY: this pointer came from exactly one Box::leak and is released
45        // exactly once. DecodeArena owns it and outlives every borrowed slice.
46        unsafe { drop(Box::from_raw(self.0.as_ptr())) };
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::DecodeArena;
53
54    fn buffer(index: usize) -> Box<[u8]> {
55        let len = (index % 7) + 1;
56        vec![u8::try_from(index).expect("test index fits u8"); len].into_boxed_slice()
57    }
58
59    fn check(index: usize, slice: &[u8]) {
60        assert_eq!(slice.len(), (index % 7) + 1, "length of buffer {index}");
61        assert!(
62            slice
63                .iter()
64                .all(|&byte| byte == u8::try_from(index).expect("test index fits u8")),
65            "contents of buffer {index}",
66        );
67    }
68
69    #[test]
70    fn empty_buffer_borrows_survive_later_allocations() {
71        let arena = DecodeArena::new();
72        let empty = arena.alloc(Vec::new().into_boxed_slice());
73        let present = arena.alloc(vec![7].into_boxed_slice());
74        assert!(empty.is_empty());
75        assert_eq!(present, &[7]);
76    }
77
78    #[test]
79    fn an_owned_arena_can_move_between_threads() {
80        let arena = DecodeArena::new();
81        {
82            arena.alloc(vec![7].into_boxed_slice());
83        }
84        let arena = std::thread::spawn(move || {
85            {
86                assert_eq!(arena.alloc(vec![9].into_boxed_slice()), &[9]);
87            }
88            arena
89        })
90        .join()
91        .expect("the receiving thread retains and reads the arena");
92        assert_eq!(arena.alloc(vec![11].into_boxed_slice()), &[11]);
93    }
94
95    /// Alloc while earlier borrows stay live: each iteration allocates, then
96    /// re-reads every prior slice.
97    #[test]
98    fn interleaved_alloc_and_read_across_many_buffers() {
99        let arena = DecodeArena::new();
100        let mut borrows: Vec<&[u8]> = Vec::new();
101        for index in 0..256usize {
102            borrows.push(arena.alloc(buffer(index)));
103            for (i, slice) in borrows.iter().enumerate() {
104                check(i, slice);
105            }
106        }
107    }
108}