Skip to main content

pathland_core/
arena.rs

1//! Bump arena for variable-length data in linear memory.
2//!
3//! Every entry is self-describing: `[u32 byteLength][bytes...]`. Opcodes
4//! reference entries by their byte offset (`arenaRef`), so the entry length is
5//! read directly from the arena and every opcode stays 16 bytes.
6
7use crate::memory::header;
8
9/// Errors produced by the arena.
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ArenaError {
12    /// Not enough remaining bytes for the allocation.
13    OutOfSpace,
14    /// The arena offset is out of bounds or points to an invalid entry.
15    InvalidOffset,
16}
17
18/// Allocate `bytes` into the arena region, returning its byte offset.
19///
20/// The cursor lives in the header block (`OFF_ARENA_CURSOR`).
21#[inline]
22pub(crate) fn alloc(data: &mut [u8], header: &mut [u8], bytes: &[u8]) -> Result<u32, ArenaError> {
23    alloc_at(data, header, crate::memory::OFF_ARENA_CURSOR, bytes)
24}
25
26/// Allocate `bytes` into the host → guest (event) arena region, returning its
27/// byte offset. The cursor lives in the header block
28/// (`OFF_EVENT_ARENA_CURSOR`, host-owned) — the host writes event string
29/// payloads here so the guest can resolve them over the shared ring.
30#[inline]
31pub(crate) fn alloc_event(
32    data: &mut [u8],
33    header: &mut [u8],
34    bytes: &[u8],
35) -> Result<u32, ArenaError> {
36    alloc_at(data, header, crate::memory::OFF_EVENT_ARENA_CURSOR, bytes)
37}
38
39/// Allocate into any arena region, using the given header cursor offset.
40#[inline]
41pub(crate) fn alloc_at(
42    data: &mut [u8],
43    header: &mut [u8],
44    cursor_off: usize,
45    bytes: &[u8],
46) -> Result<u32, ArenaError> {
47    let cursor = header::get_u32(header, cursor_off) as usize;
48    let len = bytes.len();
49    let needed = 4 + len;
50    if cursor + needed > data.len() {
51        return Err(ArenaError::OutOfSpace);
52    }
53    data[cursor..cursor + 4].copy_from_slice(&(len as u32).to_le_bytes());
54    data[cursor + 4..cursor + needed].copy_from_slice(bytes);
55    header::set_u32(header, cursor_off, (cursor + needed) as u32);
56    Ok(cursor as u32)
57}
58
59/// Allocate a string, returning its arena offset.
60#[inline]
61pub(crate) fn alloc_str(
62    data: &mut [u8],
63    header: &mut [u8],
64    s: &str,
65) -> Result<u32, ArenaError> {
66    alloc(data, header, s.as_bytes())
67}
68
69/// Read the bytes of the entry at `offset` from an arena region.
70#[inline]
71pub(crate) fn get(data: &[u8], offset: u32) -> Result<&[u8], ArenaError> {
72    let off = offset as usize;
73    if off + 4 > data.len() {
74        return Err(ArenaError::InvalidOffset);
75    }
76    let len = u32::from_le_bytes(data[off..off + 4].try_into().unwrap()) as usize;
77    if off + 4 + len > data.len() {
78        return Err(ArenaError::InvalidOffset);
79    }
80    Ok(&data[off + 4..off + 4 + len])
81}
82
83/// Read the string at `offset` (UTF-8 validated).
84#[inline]
85pub(crate) fn get_str(data: &[u8], offset: u32) -> Result<&str, ArenaError> {
86    let bytes = get(data, offset)?;
87    core::str::from_utf8(bytes).map_err(|_| ArenaError::InvalidOffset)
88}
89
90#[cfg(test)]
91mod tests {
92    use alloc::vec;
93    use super::*;
94    use crate::memory::{header, MemoryLayout};
95
96    #[test]
97    fn alloc_and_get_round_trip() {
98        let layout = MemoryLayout::default();
99        let mut buf = vec![0u8; layout.total_bytes()];
100        crate::init_memory(&mut buf, &layout);
101        let (head, rest) = buf.split_at_mut(crate::memory::HEADER_SIZE);
102        let (_, rest) = rest.split_at_mut(layout.ring_bytes() + layout.event_ring_bytes());
103        let (arena_data, _event_arena) = rest.split_at_mut(layout.arena_bytes);
104        let off = alloc_str(arena_data, head, "Hello").unwrap();
105        assert_eq!(get(arena_data, off).unwrap(), b"Hello");
106        assert_eq!(get_str(arena_data, off).unwrap(), "Hello");
107        assert_eq!(header::get_u32(head, crate::memory::OFF_ARENA_CURSOR), 4 + 5);
108    }
109
110    #[test]
111    fn multiple_allocs_are_contiguous() {
112        let layout = MemoryLayout::default();
113        let mut buf = vec![0u8; layout.total_bytes()];
114        crate::init_memory(&mut buf, &layout);
115        let (head, rest) = buf.split_at_mut(crate::memory::HEADER_SIZE);
116        let (_, rest) = rest.split_at_mut(layout.ring_bytes() + layout.event_ring_bytes());
117        let (arena_data, _event_arena) = rest.split_at_mut(layout.arena_bytes);
118        let o1 = alloc_str(arena_data, head, "ab").unwrap();
119        let o2 = alloc_str(arena_data, head, "cdef").unwrap();
120        assert!(o2 > o1);
121        assert_eq!(get(arena_data, o1).unwrap(), b"ab");
122        assert_eq!(get(arena_data, o2).unwrap(), b"cdef");
123    }
124
125    #[test]
126    fn out_of_space_is_reported() {
127        let layout = MemoryLayout::default();
128        let mut buf = vec![0u8; layout.total_bytes()];
129        crate::init_memory(&mut buf, &layout);
130        let (head, rest) = buf.split_at_mut(crate::memory::HEADER_SIZE);
131        let (_, rest) = rest.split_at_mut(layout.ring_bytes() + layout.event_ring_bytes());
132        let (arena_data, _event_arena) = rest.split_at_mut(layout.arena_bytes);
133        assert_eq!(
134            alloc(arena_data, head, &[0u8; 1024 * 1024]).unwrap_err(),
135            ArenaError::OutOfSpace
136        );
137    }
138}