Skip to main content

cadmpeg_ir/decode/
arena.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Append-only byte store with stable slice addresses.
3
4use std::cell::RefCell;
5
6/// Owns stable byte buffers allocated during a decode.
7#[derive(Debug, Default)]
8pub struct DecodeArena {
9    buffers: RefCell<Vec<Box<[u8]>>>,
10}
11
12impl DecodeArena {
13    /// Creates an empty arena.
14    pub fn new() -> Self {
15        Self::default()
16    }
17
18    /// Stores `bytes` and returns a borrow valid for the arena's lifetime.
19    ///
20    /// The returned slice is stable: later `alloc` calls never invalidate it.
21    pub fn alloc(&self, bytes: Box<[u8]>) -> &[u8] {
22        let mut buffers = self.buffers.borrow_mut();
23        buffers.push(bytes);
24        let slice: &[u8] = buffers.last().expect("a buffer was just pushed").as_ref();
25        // SAFETY: `slice` points into the heap allocation owned by the box we
26        // just pushed. That allocation is not freed or moved until the arena
27        // is dropped, and the arena outlives every borrow of `&self`, so the
28        // pointer is valid for the returned lifetime. The bytes are never
29        // mutated after being stored (the arena exposes no mutation), so
30        // aliasing `&[u8]` borrows do not conflict.
31        //
32        unsafe { std::slice::from_raw_parts(slice.as_ptr(), slice.len()) }
33    }
34}
35
36#[cfg(test)]
37mod tests {
38    use super::DecodeArena;
39
40    fn buffer(index: usize) -> Box<[u8]> {
41        let len = (index % 7) + 1;
42        vec![index as u8; len].into_boxed_slice()
43    }
44
45    fn check(index: usize, slice: &[u8]) {
46        assert_eq!(slice.len(), (index % 7) + 1, "length of buffer {index}");
47        assert!(
48            slice.iter().all(|&byte| byte == index as u8),
49            "contents of buffer {index}",
50        );
51    }
52
53    /// Interleave allocation and reads so every earlier borrow is read *after*
54    /// a later `alloc` has re-entered `borrow_mut`. Each `alloc` retags
55    /// `&mut Vec<Box<[u8]>>` and, across 256 pushes, reallocates the outer `Vec`
56    /// many times over; reading every slice handed out so far, on every
57    /// iteration, forces the aliasing model to validate each earlier pointer
58    /// against the later retag. This is the alloc-while-earlier-borrows-live
59    /// case a raw-pointer rework would need to satisfy.
60    ///
61    /// It subsumes the narrower scenarios that do not add coverage: outer-`Vec`
62    /// regrowth (the reallocations here relocate the box *pointers* while the
63    /// boxed bytes the borrows address never move) and read order (a shared read
64    /// never mutates the borrow stack or tree, so revalidating every held
65    /// pointer each iteration already covers any order). Neither could fail
66    /// where this test passes.
67    #[test]
68    fn interleaved_alloc_and_read_across_many_buffers() {
69        let arena = DecodeArena::new();
70        let mut borrows: Vec<&[u8]> = Vec::new();
71        for index in 0..256usize {
72            borrows.push(arena.alloc(buffer(index)));
73            for (i, slice) in borrows.iter().enumerate() {
74                check(i, slice);
75            }
76        }
77    }
78}