pub(super) struct CudaArena {
base: u64,
size: usize,
high_water: usize,
live_regions: Vec<(usize, usize)>,
}
impl CudaArena {
pub(super) fn new(base: u64, size: usize) -> Self {
Self {
base,
size,
high_water: 0,
live_regions: Vec::new(),
}
}
fn align_up(n: usize) -> usize {
const ALIGN: usize = 256;
(n + ALIGN - 1) & !(ALIGN - 1)
}
pub(super) fn allocate(&mut self, size_bytes: usize) -> crate::error::Result<u64> {
let aligned = Self::align_up(size_bytes.max(1));
if self.high_water + aligned > self.size {
return Err(crate::error::Error::OutOfMemory { size: size_bytes });
}
let offset = self.high_water;
self.high_water += aligned;
self.live_regions.push((offset, aligned));
Ok(self.base + offset as u64)
}
pub(super) fn deallocate(&mut self, ptr: u64) {
let offset = match ptr.checked_sub(self.base) {
Some(o) => o as usize,
None => return, };
if let Some(idx) = self
.live_regions
.iter()
.rposition(|(off, _)| *off == offset)
{
self.live_regions.remove(idx);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn arena_bump_pointer_logic_no_gpu() {
let base: u64 = 0x1_0000_0000;
let arena_size: usize = 4096;
let mut arena = CudaArena::new(base, arena_size);
let p0 = arena.allocate(100).expect("alloc 100 bytes");
assert_eq!(p0, base, "first alloc must start at base");
assert_eq!(arena.high_water, 256);
assert_eq!(arena.live_regions.len(), 1);
let p1 = arena.allocate(512).expect("alloc 512 bytes");
assert_eq!(
p1,
base + 256,
"second alloc starts after first (256 bytes)"
);
assert_eq!(arena.high_water, 768);
assert_eq!(arena.live_regions.len(), 2);
arena.deallocate(p1);
assert_eq!(
arena.high_water, 768,
"deallocate must NOT rewind high_water (monotone arena)"
);
assert_eq!(
arena.live_regions.len(),
1,
"p1 tracking entry removed; p0 remains"
);
let p2 = arena.allocate(256).expect("alloc p2");
assert_eq!(p2, base + 768, "p2 starts at high_water after p1 was freed");
assert_eq!(arena.high_water, 1024);
arena.deallocate(p0);
assert_eq!(
arena.high_water, 1024,
"non-topmost dealloc must NOT rewind high_water"
);
assert_eq!(
arena.live_regions.len(),
1,
"p0 tracking entry removed; p2 remains"
);
arena.deallocate(p2);
assert_eq!(
arena.high_water, 1024,
"even topmost dealloc must NOT rewind high_water (monotone policy)"
);
assert_eq!(arena.live_regions.len(), 0, "all tracking entries removed");
let remaining = arena_size - arena.high_water;
let _ = arena.allocate(remaining).expect("fill remaining space");
let oom = arena.allocate(1);
assert!(
oom.is_err(),
"allocation beyond arena capacity must return OOM"
);
}
}