#![allow(unsafe_code)]
pub const WASM_PAGE_SIZE: usize = 65_536;
#[unsafe(no_mangle)]
pub extern "C" fn oxigdal_alloc(size: usize) -> *mut u8 {
if size == 0 {
return std::ptr::null_mut();
}
let align_units = size.div_ceil(8);
let mut buf: Vec<u64> = Vec::with_capacity(align_units);
let ptr = buf.as_mut_ptr().cast::<u8>();
std::mem::forget(buf);
ptr
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn oxigdal_dealloc(ptr: *mut u8, size: usize) {
if ptr.is_null() || size == 0 {
return;
}
let align_units = size.div_ceil(8);
unsafe {
drop(Vec::from_raw_parts(ptr.cast::<u64>(), 0, align_units));
}
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct WasmMemoryStats {
pub allocated_bytes: usize,
pub allocation_count: u64,
pub deallocation_count: u64,
}
pub struct WasmBumpAllocator {
buf: Vec<u8>,
offset: usize,
}
impl WasmBumpAllocator {
pub fn new(capacity: usize) -> Self {
Self {
buf: vec![0u8; capacity],
offset: 0,
}
}
pub fn alloc(&mut self, size: usize, align: usize) -> Option<&mut [u8]> {
if align == 0 || size == 0 {
return None;
}
let aligned_offset = (self.offset + align - 1) & !(align - 1);
let end = aligned_offset.checked_add(size)?;
if end > self.buf.len() {
return None;
}
self.offset = end;
Some(&mut self.buf[aligned_offset..end])
}
pub fn reset(&mut self) {
self.offset = 0;
}
pub fn used(&self) -> usize {
self.offset
}
pub fn remaining(&self) -> usize {
self.buf.len().saturating_sub(self.offset)
}
pub fn capacity(&self) -> usize {
self.buf.len()
}
pub fn is_empty(&self) -> bool {
self.offset == 0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bump_new_capacity() {
let a = WasmBumpAllocator::new(1024);
assert_eq!(a.capacity(), 1024);
assert_eq!(a.used(), 0);
assert_eq!(a.remaining(), 1024);
assert!(a.is_empty());
}
#[test]
fn bump_alloc_basic() {
let mut a = WasmBumpAllocator::new(256);
let slice = a.alloc(64, 8).expect("allocation should succeed");
assert_eq!(slice.len(), 64);
assert_eq!(a.used(), 64);
assert_eq!(a.remaining(), 192);
}
#[test]
fn bump_alloc_exhausted() {
let mut a = WasmBumpAllocator::new(8);
assert!(a.alloc(16, 1).is_none());
}
#[test]
fn bump_reset_reuse() {
let mut a = WasmBumpAllocator::new(64);
a.alloc(32, 4).expect("first alloc");
assert_eq!(a.used(), 32);
a.reset();
assert_eq!(a.used(), 0);
assert!(a.is_empty());
a.alloc(64, 1).expect("second alloc after reset");
assert_eq!(a.used(), 64);
}
#[test]
fn bump_used_remaining_track() {
let mut a = WasmBumpAllocator::new(100);
a.alloc(10, 1).expect("alloc 10");
assert_eq!(a.used(), 10);
assert_eq!(a.remaining(), 90);
a.alloc(40, 1).expect("alloc 40");
assert_eq!(a.used(), 50);
assert_eq!(a.remaining(), 50);
}
#[test]
fn alloc_dealloc_roundtrip() {
let ptr = oxigdal_alloc(128);
assert!(!ptr.is_null());
unsafe { oxigdal_dealloc(ptr, 128) };
}
#[test]
fn alloc_zero_returns_null() {
let ptr = oxigdal_alloc(0);
assert!(ptr.is_null());
}
}