use core::alloc::{GlobalAlloc, Layout};
use core::sync::atomic::{AtomicUsize, Ordering};
pub const FALLBACK_HEAP_BASE: usize = 8 * 1024 * 1024;
#[cfg_attr(not(any(target_arch = "wasm32", test)), allow(dead_code))]
const WASM_PAGE: usize = 65536;
#[cfg_attr(not(any(target_arch = "wasm32", test)), allow(dead_code))]
#[inline]
fn heap_base_from_total_len(total_len: usize) -> usize {
let off = crate::imp::core::datasection::DIGS_DATA_OFFSET as usize;
let end = off + total_len;
let page = WASM_PAGE;
end.div_ceil(page) * page
}
pub struct BumpAllocator {
next: AtomicUsize,
}
unsafe impl Sync for BumpAllocator {}
impl BumpAllocator {
pub const fn new() -> Self {
BumpAllocator {
next: AtomicUsize::new(0), }
}
#[cfg(target_arch = "wasm32")]
fn resolve_heap_base() -> usize {
use crate::imp::core::datasection::DIGS_DATA_OFFSET;
const HEADER_LEN: usize = 9; const ROW_LEN: usize = 10; unsafe {
let base = DIGS_DATA_OFFSET as *const u8;
let header = core::slice::from_raw_parts(base, HEADER_LEN);
if &header[0..4] != b"DIGS" || header[4] != 1 {
return FALLBACK_HEAP_BASE;
}
let count = u32::from_be_bytes([header[5], header[6], header[7], header[8]]) as usize;
let table_len = match count
.checked_mul(ROW_LEN)
.and_then(|t| t.checked_add(HEADER_LEN))
{
Some(n) => n,
None => return FALLBACK_HEAP_BASE,
};
let rows = core::slice::from_raw_parts(base, table_len);
let mut total_len = table_len;
for i in 0..count {
let p = HEADER_LEN + i * ROW_LEN;
let offset =
u32::from_be_bytes([rows[p + 2], rows[p + 3], rows[p + 4], rows[p + 5]])
as usize;
let len = u32::from_be_bytes([rows[p + 6], rows[p + 7], rows[p + 8], rows[p + 9]])
as usize;
match offset.checked_add(len) {
Some(end) if end > total_len => total_len = end,
Some(_) => {}
None => return FALLBACK_HEAP_BASE,
}
}
heap_base_from_total_len(total_len)
}
}
#[cfg(not(target_arch = "wasm32"))]
fn resolve_heap_base() -> usize {
FALLBACK_HEAP_BASE
}
#[cfg(target_arch = "wasm32")]
fn ensure_memory(end: usize) -> bool {
let have_pages = core::arch::wasm32::memory_size(0);
let have_bytes = have_pages * WASM_PAGE;
if end <= have_bytes {
return true;
}
let need_pages = (end - have_bytes).div_ceil(WASM_PAGE);
core::arch::wasm32::memory_grow(0, need_pages) != usize::MAX
}
#[cfg(not(target_arch = "wasm32"))]
fn ensure_memory(_end: usize) -> bool {
true
}
pub fn bump(&self, layout: Layout) -> *mut u8 {
let align = layout.align().max(1);
let size = layout.size();
loop {
let mut cur = self.next.load(Ordering::Relaxed);
if cur == 0 {
let base = Self::resolve_heap_base();
match self
.next
.compare_exchange(0, base, Ordering::SeqCst, Ordering::Relaxed)
{
Ok(_) => cur = base,
Err(observed) => cur = observed,
}
}
let aligned = (cur + align - 1) & !(align - 1);
let end = match aligned.checked_add(size) {
Some(e) => e,
None => return core::ptr::null_mut(),
};
if self
.next
.compare_exchange(cur, end, Ordering::SeqCst, Ordering::Relaxed)
.is_ok()
{
if !Self::ensure_memory(end) {
return core::ptr::null_mut();
}
return aligned as *mut u8;
}
}
}
}
impl Default for BumpAllocator {
fn default() -> Self {
Self::new()
}
}
unsafe impl GlobalAlloc for BumpAllocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
self.bump(layout)
}
unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {}
}
#[cfg(all(target_arch = "wasm32", not(feature = "std")))]
#[global_allocator]
static ALLOC: BumpAllocator = BumpAllocator::new();
#[cfg(all(test, not(target_arch = "wasm32")))]
mod tests {
use super::*;
use core::alloc::Layout;
#[test]
fn fallback_base_is_8mib_without_a_digs_header() {
let a = BumpAllocator::new();
let p1 = a.bump(Layout::from_size_align(64, 8).unwrap());
assert!(!p1.is_null());
assert!((p1 as usize) >= FALLBACK_HEAP_BASE);
assert!(FALLBACK_HEAP_BASE > crate::imp::core::datasection::DIGS_DATA_OFFSET as usize);
}
#[test]
fn bump_returns_distinct_aligned_pointers() {
let a = BumpAllocator::new();
let p1 = a.bump(Layout::from_size_align(64, 8).unwrap()) as usize;
let p2 = a.bump(Layout::from_size_align(64, 8).unwrap()) as usize;
assert_ne!(p1, p2);
assert!(p2 >= p1 + 64);
assert_eq!(p1 % 8, 0);
assert_eq!(p2 % 8, 0);
}
#[test]
fn heap_base_from_header_sits_above_the_blob() {
let off = crate::imp::core::datasection::DIGS_DATA_OFFSET as usize;
let total_len = 10 * 1024 * 1024; let base = heap_base_from_total_len(total_len);
assert!(base >= off + total_len);
assert_eq!(base % 65536, 0);
}
}