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