use std::collections::VecDeque;
const CHUNK_SIZE: usize = 256; const MEM_SIZE: usize = 1024*1024*1024; const FLAG_BASE: u32 = 0x7800_0000; const DRAM_BASE: u32 = 0x0000_0000;
#[derive(Debug)]
pub enum MemoryError {
ENOMEM,
EDFREE,
}
impl std::fmt::Display for MemoryError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match *self {
MemoryError::ENOMEM => write!(f, "Out of memory"),
MemoryError::EDFREE => write!(f, "Double free")
}
}
}
impl std::convert::From<MemoryError> for &str {
fn from(error: MemoryError) -> Self {
match error {
MemoryError::ENOMEM => "Out of memory",
MemoryError::EDFREE => "Double free",
}
}
}
impl std::convert::From<MemoryError> for String {
fn from(error: MemoryError) -> Self {
let foo: &str = error.into();
foo.to_string()
}
}
impl std::error::Error for MemoryError {}
pub(crate) struct Chunk {
_addr: u32,
_valid: bool,
_dummy: bool,
}
impl std::fmt::Debug for Chunk {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Chunk")
.field("_addr", &format_args!("0x{:08x}", &self._addr))
.field("_valid", &self._valid)
.field("_dummy", &self._dummy)
.finish()
}
}
impl Chunk {
pub fn new(addr: u32) -> Self {
Chunk { _addr: addr, _valid: true, _dummy: false }
}
pub(crate) fn dummy() -> Self {
Chunk { _addr: 0x0, _valid: true, _dummy: true }
}
pub fn addr(&self) -> u32 {
self._addr
}
fn offset(&self) -> u32 {
self._addr / (CHUNK_SIZE as u32)
}
pub fn flag_addr(&self) -> u32 {
let offset = self.offset();
return FLAG_BASE + 4*offset;
}
fn is_valid(&self) -> bool {
self._valid
}
pub(crate) fn is_dummy(&self) -> bool {
self._dummy
}
fn invalidate(&mut self) {
self._valid = false;
}
}
pub(crate) struct MemMan {
top: u32,
total_blocks: usize,
free_blocks: usize,
stack: VecDeque<u32>
}
impl MemMan {
pub fn new() -> Self {
MemMan {
top: DRAM_BASE,
total_blocks: MEM_SIZE / CHUNK_SIZE,
free_blocks: MEM_SIZE / CHUNK_SIZE,
stack: VecDeque::with_capacity(2048)
}
}
fn next(&self) -> Option<u32> {
if self.free_blocks == 0 {
return None;
}
if self.stack.len() > 0 {
return Some(*self.stack.front().unwrap());
} else {
return Some(self.top);
}
}
pub fn alloc_chunk(&mut self) -> Result<Chunk, MemoryError> {
if self.free_blocks == 0 {
return Err(MemoryError::ENOMEM);
}
let chunk: Chunk;
if self.stack.len() > 0 {
chunk = Chunk::new(self.stack.pop_front().unwrap());
} else {
chunk = Chunk::new(self.top);
self.top += CHUNK_SIZE as u32;
}
self.free_blocks -= 1;
Ok(chunk)
}
pub fn free_chunk(&mut self, chunk: &mut Chunk) -> Result<(), MemoryError> {
if chunk.is_dummy() {
chunk.invalidate();
return Ok(())
}
if !chunk.is_valid() {
return Err(MemoryError::EDFREE);
}
self.stack.push_back(chunk.addr());
chunk.invalidate();
self.free_blocks += 1;
Ok(())
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::{MemMan, Chunk, MemoryError, CHUNK_SIZE, DRAM_BASE};
#[test]
fn memman_test_alloc() {
let mut manager = MemMan::new();
assert_eq!(manager.free_blocks, manager.total_blocks);
let mut vec: Vec<Chunk> = Vec::new();
for i in 0..10 {
let chunk = manager.alloc_chunk().unwrap();
assert_eq!(chunk.addr(), DRAM_BASE + i*CHUNK_SIZE as u32);
vec.push(chunk);
}
assert_eq!(manager.free_blocks, manager.total_blocks - vec.len());
}
#[test]
fn memman_test_overalloc() {
let mut manager = MemMan::new();
let mut vec: Vec<Chunk> = Vec::new();
let mut success = false;
for i in 0..u32::MAX {
match manager.alloc_chunk() {
Ok(c) => vec.push(c),
Err(_) => { success = true; break; }
}
}
assert_eq!(success, true);
}
#[test]
fn memman_test_no_double_free() {
let mut manager = MemMan::new();
let mut chunk = manager.alloc_chunk().unwrap();
assert_matches!(manager.free_chunk(&mut chunk), Ok(()));
assert_eq!(manager.free_blocks, manager.total_blocks);
assert_matches!(manager.free_chunk(&mut chunk), Err(MemoryError::EDFREE));
}
#[test]
fn memman_test_reclaim() {
let mut manager = MemMan::new();
let chunk0 = manager.alloc_chunk().unwrap();
let mut chunk1 = manager.alloc_chunk().unwrap();
let chunk2 = manager.alloc_chunk().unwrap();
assert_eq!(chunk0.addr(), DRAM_BASE);
assert_eq!(chunk1.addr(), DRAM_BASE + CHUNK_SIZE as u32);
assert_eq!(chunk2.addr(), DRAM_BASE + 2u32*CHUNK_SIZE as u32);
assert_eq!(manager.next(), Some(DRAM_BASE + 3u32*CHUNK_SIZE as u32));
manager.free_chunk(&mut chunk1);
assert_eq!(manager.next(), Some(DRAM_BASE + CHUNK_SIZE as u32));
}
}