baryl 0.0.2

Public SDK for Baryl, a full-system emulation and introspection engine
//! A fixed-length owned array, allocated out of a pool region.

use std::{
    alloc::{Allocator, Layout},
    ops::{Deref, DerefMut},
    ptr::{self, NonNull},
    slice::{from_raw_parts, from_raw_parts_mut},
};

use zerocopy::FromZeros;

use crate::abi::SandboxSafe;

/// `len` elements of `T`, allocated once out of `A` and freed when dropped.
///
/// `Box<[T]>`, not `Vec<T>`: the length is chosen at construction and there is
/// no spare capacity, no `push`, and no way to grow. Deref gives you `&[T]` and
/// `&mut [T]`, so indexing, iteration and slicing all work as usual.
///
/// The allocator is stored inside, not borrowed: one built in one binary may be
/// dropped in another, and it frees correctly. `#[repr(C)]`, so both agree on
/// where the fields are.
///
/// All-zeroes is a valid empty `BVec`, which is what freshly claimed pool bytes
/// already are — a zeroed region needs no construction pass.
///
/// # Examples
///
/// ```ignore
/// // 4096 counters in the checkpoint heap, zeroed, restored with the run.
/// let heap: SbxAlloc = ctl.subs.alloc.ck_heap();
/// let Some(mut hits): Option<BVec<u64, SbxAlloc>> = BVec::try_new_in(heap, 4096) else {
///     return;  // heap full
/// };
///
/// assert_eq!(hits.len(), 4096);
/// assert_eq!(hits[0], 0);
/// hits[7] += 1;
/// ```
#[repr(C)]
pub struct BVec<T, A: Allocator> {
    alloc: A,
    ptr: *mut T,
    len: u64,
}

impl<T: FromZeros, A: Allocator> BVec<T, A> {
    /// `len` zeroed elements out of `alloc`.
    ///
    /// `None` when the allocator has no room, or when `len` elements of `T`
    /// would overflow a layout. `len` of 0 allocates nothing and succeeds,
    /// giving an array that derefs to `&[]`.
    pub fn try_new_in(alloc: A, len: usize) -> Option<Self> {
        if len == 0 {
            return Some(Self {
                alloc: alloc,
                ptr: ptr::null_mut(),
                len: 0,
            });
        }
        let layout = Layout::array::<T>(len).ok()?;
        let ptr = alloc.allocate_zeroed(layout).ok()?;
        Some(Self {
            alloc: alloc,
            ptr: ptr.as_ptr().cast(),
            len: len as u64,
        })
    }
}

impl<T, A: Allocator> Deref for BVec<T, A> {
    type Target = [T];
    fn deref(&self) -> &[T] {
        match NonNull::new(self.ptr) {
            // SAFETY: `try_new_in` allocated `len` elements at `ptr` and nothing
            // hands out a `BVec` it did not make.
            Some(p) => unsafe { from_raw_parts(p.as_ptr().cast_const(), self.len as usize) },
            None => &[],
        }
    }
}

impl<T, A: Allocator> DerefMut for BVec<T, A> {
    fn deref_mut(&mut self) -> &mut [T] {
        match NonNull::new(self.ptr) {
            // SAFETY: see `deref`; `&mut self` is what makes the aliasing unique.
            Some(p) => unsafe { from_raw_parts_mut(p.as_ptr(), self.len as usize) },
            None => &mut [],
        }
    }
}

impl<T, A: Allocator> Drop for BVec<T, A> {
    /// Returns the block to the allocator. The elements themselves are not
    /// dropped — anything stored here is plain data by construction.
    fn drop(&mut self) {
        let (Ok(layout), Some(p)) = (
            Layout::array::<T>(self.len as usize),
            NonNull::new(self.ptr.cast::<u8>()),
        ) else {
            return;
        };
        unsafe { self.alloc.deallocate(p, layout) };
    }
}

// SAFETY: the storage is a pool allocation and `A` is a pool VA, so every
// pointer inside targets region-managed memory.
unsafe impl<T: SandboxSafe, A: Allocator + SandboxSafe> SandboxSafe for BVec<T, A> {}