use core::alloc::Layout;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AllocError {
AllocationFailed,
OutOfMemory,
PageTooSmall,
}
impl From<u32> for AllocError {
fn from(value: u32) -> Self {
match value {
1 => AllocError::OutOfMemory,
2 => AllocError::PageTooSmall,
_ => AllocError::AllocationFailed,
}
}
}
impl From<AllocError> for u32 {
fn from(value: AllocError) -> Self {
value as u32
}
}
unsafe extern "C" {
fn __spacewasm_alloc(size: usize, align: usize, err: *mut u32) -> *mut u8;
fn __spacewasm_dealloc(ptr: *mut u8, size: usize, align: usize);
}
#[macro_export]
macro_rules! global_allocator {
($ty: ty, $val:expr) => {
static mut ALLOC_IMPL: $ty = $val;
#[allow(unused_unsafe)]
static mut GLOBAL_ALLOCATOR: *mut $ty = unsafe { &raw mut ALLOC_IMPL };
#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spacewasm_alloc(
size: usize,
align: usize,
err: *mut u32,
) -> *mut u8 {
let layout = match core::alloc::Layout::from_size_align(size, align) {
Ok(l) => l,
Err(_) => {
unsafe {
*err = $crate::AllocError::AllocationFailed.into();
}
return core::ptr::null_mut();
}
};
match unsafe { $crate::Allocator::alloc(&*GLOBAL_ALLOCATOR, layout) } {
Ok(ptr) => ptr,
Err(alloc_err) => {
unsafe {
*err = alloc_err.into();
}
core::ptr::null_mut()
}
}
}
#[unsafe(no_mangle)]
pub unsafe extern "C" fn __spacewasm_dealloc(ptr: *mut u8, size: usize, align: usize) {
let Ok(layout) = core::alloc::Layout::from_size_align(size, align) else {
return;
};
unsafe { $crate::Allocator::dealloc(&*GLOBAL_ALLOCATOR, ptr, layout) }
}
};
}
pub unsafe trait Allocator {
unsafe fn alloc(&self, layout: Layout) -> Result<*mut u8, AllocError>;
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout);
}
unsafe impl<T: Allocator> Allocator for &T {
unsafe fn alloc(&self, layout: Layout) -> Result<*mut u8, AllocError> {
unsafe { (**self).alloc(layout) }
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { (**self).dealloc(ptr, layout) }
}
}
#[derive(Clone, Copy)]
pub struct GlobalAllocator;
unsafe impl Allocator for GlobalAllocator {
unsafe fn alloc(&self, layout: Layout) -> Result<*mut u8, AllocError> {
let mut err: u32 = 0;
let ptr = unsafe { __spacewasm_alloc(layout.size(), layout.align(), &mut err) };
if ptr.is_null() {
Err(err.into())
} else {
Ok(ptr)
}
}
unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
unsafe { __spacewasm_dealloc(ptr, layout.size(), layout.align()) }
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_alloc_error_from_u32() {
assert_eq!(AllocError::from(1u32), AllocError::OutOfMemory);
assert_eq!(AllocError::from(2u32), AllocError::PageTooSmall);
assert_eq!(AllocError::from(0u32), AllocError::AllocationFailed);
assert_eq!(AllocError::from(42u32), AllocError::AllocationFailed);
}
#[test]
fn test_alloc_error_into_u32() {
assert_eq!(u32::from(AllocError::AllocationFailed), 0);
assert_eq!(u32::from(AllocError::OutOfMemory), 1);
assert_eq!(u32::from(AllocError::PageTooSmall), 2);
}
#[test]
fn test_alloc_error_round_trip() {
for err in [
AllocError::AllocationFailed,
AllocError::OutOfMemory,
AllocError::PageTooSmall,
] {
let code: u32 = err.clone().into();
assert_eq!(AllocError::from(code), err);
}
}
#[test]
fn test_reference_allocator_delegates() {
let alloc = GlobalAllocator;
let by_ref = &alloc;
let layout = Layout::from_size_align(32, 8).unwrap();
let ptr = unsafe { Allocator::alloc(&by_ref, layout) }.unwrap();
assert!(!ptr.is_null());
unsafe { Allocator::dealloc(&by_ref, ptr, layout) };
}
#[test]
fn test_global_allocator_alloc_dealloc() {
let alloc = GlobalAllocator;
let layout = Layout::from_size_align(32, 8).unwrap();
let ptr = unsafe { alloc.alloc(layout) }.unwrap();
assert!(!ptr.is_null());
unsafe { alloc.dealloc(ptr, layout) };
}
#[test]
fn test_global_allocator_alloc_error() {
let alloc = GlobalAllocator;
let layout = Layout::from_size_align(0, 1).unwrap();
let result = unsafe { alloc.alloc(layout) };
assert_eq!(result, Err(AllocError::AllocationFailed));
}
}