use crate::Allocator;
#[cfg(target_arch = "wasm32")]
use core::arch::wasm32 as wasm;
#[cfg(target_arch = "wasm64")]
use core::arch::wasm64 as wasm;
use core::ptr;
use core::sync::atomic::{AtomicBool, Ordering};
#[cfg(target_os = "unknown")]
extern "C" {
static __heap_base: u8;
static __heap_end: u8;
}
static PREEXISTING_USED: AtomicBool = AtomicBool::new(false);
#[cfg(target_os = "unknown")]
fn preexisting_chunk_from_linker(size: usize) -> Option<(usize, usize)> {
let heap_base = unsafe { &__heap_base as *const u8 as usize };
let heap_end = unsafe { &__heap_end as *const u8 as usize };
if heap_base == 0 || heap_end <= heap_base {
return None;
}
let len = heap_end - heap_base;
if len < size {
return None;
}
Some((heap_base, len))
}
#[cfg(not(target_os = "unknown"))]
fn preexisting_chunk_from_linker(_size: usize) -> Option<(usize, usize)> {
None
}
fn try_donate_preexisting(
state: &AtomicBool,
chunk: Option<(usize, usize)>,
) -> Option<(usize, usize)> {
if state.swap(true, Ordering::Relaxed) {
return None;
}
chunk
}
fn alloc_via_grow(size: usize, page_size: usize) -> (*mut u8, usize, u32) {
let pages = size.div_ceil(page_size);
let prev = wasm::memory_grow(0, pages);
if prev == usize::max_value() {
return (ptr::null_mut(), 0, 0);
}
let prev_page = prev * page_size;
let base_ptr = prev_page as *mut u8;
let size = pages * page_size;
if prev_page.wrapping_add(size) == 0 {
return (base_ptr, size - 16, 0);
}
(base_ptr, size, 0)
}
pub struct System {
_priv: (),
}
impl System {
pub const fn new() -> System {
System { _priv: () }
}
}
unsafe impl Allocator for System {
fn alloc(&self, size: usize) -> (*mut u8, usize, u32) {
let page_size = self.page_size();
if size != 0 {
let chunk = preexisting_chunk_from_linker(size);
if let Some((base, len)) = try_donate_preexisting(&PREEXISTING_USED, chunk) {
return (base as *mut u8, len, 0);
}
}
alloc_via_grow(size, page_size)
}
fn remap(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize, _can_move: bool) -> *mut u8 {
ptr::null_mut()
}
fn free_part(&self, _ptr: *mut u8, _oldsize: usize, _newsize: usize) -> bool {
false
}
fn free(&self, _ptr: *mut u8, _size: usize) -> bool {
false
}
fn can_release_part(&self, _flags: u32) -> bool {
false
}
fn allocates_zeros(&self) -> bool {
true
}
fn page_size(&self) -> usize {
64 * 1024
}
}
#[cfg(test)]
mod tests {
use super::try_donate_preexisting;
use core::sync::atomic::{AtomicBool, Ordering};
fn legacy_grow_only(
size: usize,
page_size: usize,
grow_result: usize,
) -> Option<(usize, usize)> {
let pages = size.div_ceil(page_size);
if grow_result == usize::MAX {
return None;
}
Some((grow_result * page_size, pages * page_size))
}
#[test]
fn uses_preexisting_memory_when_growth_fails() {
let page_size = 64 * 1024;
let chunk = (page_size, page_size * 3);
let state = AtomicBool::new(false);
let new_behavior = try_donate_preexisting(&state, Some(chunk));
let legacy_behavior = legacy_grow_only(16, page_size, usize::MAX);
assert_eq!(new_behavior, Some((page_size, page_size * 3)));
assert_eq!(legacy_behavior, None);
assert!(state.load(Ordering::Relaxed));
}
#[test]
fn one_chunk_or_never_disables_after_failure() {
let state = AtomicBool::new(false);
let first = None;
assert_eq!(first, None);
assert_eq!(try_donate_preexisting(&state, first), None);
assert!(state.load(Ordering::Relaxed));
let second = Some((64 * 1024, 64 * 1024));
assert_eq!(try_donate_preexisting(&state, second), None);
}
#[test]
fn one_chunk_donates_only_once() {
let state = AtomicBool::new(false);
assert_eq!(
try_donate_preexisting(&state, Some((64 * 1024, 128 * 1024))),
Some((64 * 1024, 128 * 1024))
);
assert!(state.load(Ordering::Relaxed));
assert_eq!(
try_donate_preexisting(&state, Some((64 * 1024, 64 * 1024))),
None
);
}
}
#[cfg(feature = "global")]
pub fn acquire_global_lock() {
assert!(!cfg!(target_feature = "atomics"));
}
#[cfg(feature = "global")]
pub fn release_global_lock() {
assert!(!cfg!(target_feature = "atomics"));
}
#[allow(missing_docs)]
#[cfg(feature = "global")]
pub unsafe fn enable_alloc_after_fork() {
assert!(!cfg!(target_feature = "atomics"));
}