use std::collections::BTreeMap;
use crate::types::{MemAddr, PluginId, Size};
const PAGE_BITS: u32 = 12;
const PAGE_SIZE: usize = 1 << PAGE_BITS;
#[inline]
fn page_of(addr: u64) -> u64 {
addr >> PAGE_BITS
}
#[inline]
fn offset_in_page(addr: u64) -> usize {
(addr & ((1u64 << PAGE_BITS) - 1)) as usize
}
#[derive(Debug)]
pub enum MemoryError {
OutOfBounds(MemAddr, Size, Size),
DoubleFree(MemAddr),
UseAfterFree(MemAddr),
NotAllocated(MemAddr),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LinearMemory {
pub(crate) pages: BTreeMap<u64, Box<[u8; PAGE_SIZE]>>,
pub(crate) bounds: Size,
}
impl LinearMemory {
pub fn empty(size: Size) -> Self {
LinearMemory {
pages: BTreeMap::new(),
bounds: size,
}
}
pub fn addr_in_bounds(&self, addr: MemAddr, len: Size) -> bool {
match addr.checked_add(len) {
Some(end) => end <= self.bounds,
None => false,
}
}
pub fn read(&self, addr: MemAddr) -> u8 {
match self.pages.get(&page_of(addr)) {
Some(page) => page[offset_in_page(addr)],
None => 0,
}
}
pub fn write(&self, addr: MemAddr, val: u8) -> Self {
let mut new = self.clone();
new.write_mut(addr, val);
new
}
pub fn write_mut(&mut self, addr: MemAddr, val: u8) {
let pg = page_of(addr);
let off = offset_in_page(addr);
let page = self
.pages
.entry(pg)
.or_insert_with(|| Box::new([0u8; PAGE_SIZE]));
page[off] = val;
}
#[inline]
pub fn bounds(&self) -> Size {
self.bounds
}
#[inline]
pub fn written_bytes(&self) -> usize {
self.pages.len() * PAGE_SIZE
}
#[inline]
pub fn is_empty(&self) -> bool {
self.pages.is_empty()
}
pub fn read_range(&self, addr: MemAddr, len: Size) -> Result<Vec<u8>, MemoryError> {
if !self.addr_in_bounds(addr, len) {
return Err(MemoryError::OutOfBounds(addr, len, self.bounds));
}
let mut result = Vec::with_capacity(len as usize);
let mut pos = addr;
let end = addr + len;
while pos < end {
let pg = page_of(pos);
let off = offset_in_page(pos);
let page_remaining = PAGE_SIZE - off;
let needed = (end - pos) as usize;
let chunk = page_remaining.min(needed);
match self.pages.get(&pg) {
Some(page) => result.extend_from_slice(&page[off..off + chunk]),
None => result.resize(result.len() + chunk, 0),
}
pos += chunk as u64;
}
Ok(result)
}
pub fn write_range(&self, addr: MemAddr, data: &[u8]) -> Result<Self, MemoryError> {
let len = data.len() as Size;
if !self.addr_in_bounds(addr, len) {
return Err(MemoryError::OutOfBounds(addr, len, self.bounds));
}
let mut new = self.clone();
new.write_range_mut(addr, data);
Ok(new)
}
pub fn write_range_mut(&mut self, addr: MemAddr, data: &[u8]) {
let mut pos = addr;
let mut src_off = 0usize;
while src_off < data.len() {
let pg = page_of(pos);
let off = offset_in_page(pos);
let page_remaining = PAGE_SIZE - off;
let remaining_data = data.len() - src_off;
let chunk = page_remaining.min(remaining_data);
let page = self
.pages
.entry(pg)
.or_insert_with(|| Box::new([0u8; PAGE_SIZE]));
page[off..off + chunk].copy_from_slice(&data[src_off..src_off + chunk]);
pos += chunk as u64;
src_off += chunk;
}
}
}
impl Default for LinearMemory {
fn default() -> Self {
LinearMemory::empty(0)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub enum ResourceStatus {
#[default]
Unallocated,
Allocated {
owner: PluginId,
},
Freed,
}
impl ResourceStatus {
#[inline]
pub fn is_allocated(&self) -> bool {
matches!(self, ResourceStatus::Allocated { .. })
}
#[inline]
pub fn is_freed(&self) -> bool {
matches!(self, ResourceStatus::Freed)
}
#[inline]
pub fn is_unallocated(&self) -> bool {
matches!(self, ResourceStatus::Unallocated)
}
#[inline]
pub fn owner(&self) -> Option<PluginId> {
match self {
ResourceStatus::Allocated { owner } => Some(*owner),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MetaState {
pub(crate) resources: BTreeMap<MemAddr, ResourceStatus>,
pub(crate) freed_set: Vec<MemAddr>,
}
impl MetaState {
pub fn empty() -> Self {
MetaState {
resources: BTreeMap::new(),
freed_set: Vec::new(),
}
}
fn is_freed_addr(&self, addr: MemAddr) -> bool {
self.freed_set.binary_search(&addr).is_ok()
}
fn insert_freed_addr(freed_set: &mut Vec<MemAddr>, addr: MemAddr) {
match freed_set.binary_search(&addr) {
Ok(_) => {} Err(pos) => freed_set.insert(pos, addr),
}
}
pub fn is_live(&self, addr: MemAddr) -> bool {
match self.resources.get(&addr) {
Some(ResourceStatus::Allocated { .. }) => !self.is_freed_addr(addr),
_ => false,
}
}
pub fn alloc(&self, addr: MemAddr, owner: PluginId) -> Self {
let mut new_resources = self.resources.clone();
new_resources.insert(addr, ResourceStatus::Allocated { owner });
MetaState {
resources: new_resources,
freed_set: self.freed_set.clone(),
}
}
pub fn free(&self, addr: MemAddr) -> Self {
let mut new_freed_set = self.freed_set.clone();
Self::insert_freed_addr(&mut new_freed_set, addr);
MetaState {
resources: self.resources.clone(),
freed_set: new_freed_set,
}
}
pub fn alloc_mut(&mut self, addr: MemAddr, owner: PluginId) {
self.resources
.insert(addr, ResourceStatus::Allocated { owner });
}
pub fn free_mut(&mut self, addr: MemAddr) -> Result<(), MemoryError> {
if self.is_freed_addr(addr) {
return Err(MemoryError::DoubleFree(addr));
}
match self.resources.get_mut(&addr) {
Some(status @ ResourceStatus::Allocated { .. }) => {
*status = ResourceStatus::Freed;
Self::insert_freed_addr(&mut self.freed_set, addr);
Ok(())
}
Some(ResourceStatus::Freed) => Err(MemoryError::DoubleFree(addr)),
_ => Err(MemoryError::NotAllocated(addr)),
}
}
pub fn allocated_count(&self) -> usize {
self.resources.values().filter(|s| s.is_allocated()).count()
}
#[inline]
pub fn freed_count(&self) -> usize {
self.freed_set.len()
}
#[inline]
pub fn resource_count(&self) -> usize {
self.resources.len()
}
#[inline]
pub fn is_freed(&self, addr: MemAddr) -> bool {
self.freed_set.binary_search(&addr).is_ok()
}
#[inline]
pub fn get_status(&self, addr: MemAddr) -> Option<&ResourceStatus> {
self.resources.get(&addr)
}
}
impl Default for MetaState {
fn default() -> Self {
MetaState::empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linear_memory_empty() {
let mem = LinearMemory::empty(1024);
assert_eq!(mem.bounds(), 1024);
assert!(mem.is_empty());
}
#[test]
fn test_linear_memory_read_uninitialized() {
let mem = LinearMemory::empty(1024);
assert_eq!(mem.read(0), 0);
assert_eq!(mem.read(100), 0);
assert_eq!(mem.read(1023), 0);
}
#[test]
fn test_linear_memory_write_read() {
let mem = LinearMemory::empty(1024);
let mem = mem.write(100, 42);
assert_eq!(mem.read(100), 42);
assert_eq!(mem.read(99), 0);
assert_eq!(mem.read(101), 0);
}
#[test]
fn test_linear_memory_bounds_check() {
let mem = LinearMemory::empty(1024);
assert!(mem.addr_in_bounds(0, 1024));
assert!(mem.addr_in_bounds(1023, 1));
assert!(!mem.addr_in_bounds(1024, 1));
assert!(!mem.addr_in_bounds(0, 1025));
assert!(!mem.addr_in_bounds(u64::MAX, 1));
}
#[test]
fn test_linear_memory_read_range() {
let mem = LinearMemory::empty(1024);
let mem = mem.write(10, 1);
let mem = mem.write(11, 2);
let mem = mem.write(12, 3);
let result = mem.read_range(10, 3);
assert!(result.is_ok());
assert_eq!(result.ok(), Some(vec![1, 2, 3]));
}
#[test]
fn test_linear_memory_read_range_out_of_bounds() {
let mem = LinearMemory::empty(100);
let result = mem.read_range(90, 20);
assert!(matches!(result, Err(MemoryError::OutOfBounds(_, _, _))));
}
#[test]
fn test_resource_status() {
let unalloc = ResourceStatus::Unallocated;
assert!(unalloc.is_unallocated());
assert!(!unalloc.is_allocated());
assert!(!unalloc.is_freed());
assert_eq!(unalloc.owner(), None);
let alloc = ResourceStatus::Allocated { owner: 42 };
assert!(!alloc.is_unallocated());
assert!(alloc.is_allocated());
assert!(!alloc.is_freed());
assert_eq!(alloc.owner(), Some(42));
let freed = ResourceStatus::Freed;
assert!(!freed.is_unallocated());
assert!(!freed.is_allocated());
assert!(freed.is_freed());
assert_eq!(freed.owner(), None);
}
#[test]
fn test_meta_state_empty() {
let ms = MetaState::empty();
assert_eq!(ms.allocated_count(), 0);
assert_eq!(ms.freed_count(), 0);
assert!(!ms.is_live(0));
}
#[test]
fn test_meta_state_alloc_makes_live() {
let ms = MetaState::empty();
let ms = ms.alloc(100, 1);
assert!(ms.is_live(100));
assert_eq!(ms.allocated_count(), 1);
}
#[test]
fn test_meta_state_free_makes_not_live() {
let ms = MetaState::empty();
let ms = ms.alloc(100, 1);
assert!(ms.is_live(100));
let ms = ms.free(100);
assert!(!ms.is_live(100));
assert!(ms.is_freed(100));
}
#[test]
fn test_meta_state_alloc_preserves_live() {
let ms = MetaState::empty();
let ms = ms.alloc(100, 1);
let ms = ms.alloc(200, 2);
assert!(ms.is_live(100));
assert!(ms.is_live(200));
}
#[test]
fn test_meta_state_free_preserves_live() {
let ms = MetaState::empty();
let ms = ms.alloc(100, 1);
let ms = ms.alloc(200, 2);
let ms = ms.free(100);
assert!(!ms.is_live(100));
assert!(ms.is_live(200));
}
#[test]
fn test_meta_state_double_free_detection() {
let mut ms = MetaState::empty();
ms.alloc_mut(100, 1);
assert!(ms.free_mut(100).is_ok());
let result = ms.free_mut(100);
assert!(matches!(result, Err(MemoryError::DoubleFree(100))));
}
#[test]
fn test_linear_memory_page_spanning_write_read() {
let mem = LinearMemory::empty(8192);
let boundary = PAGE_SIZE as u64 - 2; let data = vec![0xAA, 0xBB, 0xCC, 0xDD]; let mem = mem.write_range(boundary, &data).unwrap();
let read_back = mem.read_range(boundary, 4).unwrap();
assert_eq!(read_back, data);
assert_eq!(mem.read(boundary), 0xAA);
assert_eq!(mem.read(boundary + 1), 0xBB);
assert_eq!(mem.read(boundary + 2), 0xCC); assert_eq!(mem.read(boundary + 3), 0xDD);
}
}