use core::alloc::Layout;
use core::ffi::c_void;
use core::ptr::null_mut;
use static_assertions::*;
const fn calc_alloc_size(size: usize, align: usize) -> usize {
let shift_storage_size = core::mem::size_of::<usize>();
let alloc_size = size + (align - 1);
if shift_storage_size % align == 0 {
(alloc_size + shift_storage_size) as usize
} else {
let aligned = ((shift_storage_size / align) + 1) * align;
(alloc_size + aligned) as usize
}
}
const fn calc_shift_for_align(addr: u64, align: usize) -> usize {
let shift_storage_size = core::mem::size_of::<usize>() as u64;
let align = align as u64;
let shift = align - addr % align;
if shift >= shift_storage_size {
shift as usize
} else {
let needed = shift_storage_size - shift;
if needed % align == 0 {
(shift + needed) as usize
} else {
let aligned_needed = ((needed + align) / align) * align;
(shift + aligned_needed) as usize
}
}
}
pub struct Allocator {
sys: Option<&'static craydate_sys::playdate_sys>,
}
impl Allocator {
pub const fn new() -> Allocator {
Allocator::tests();
Allocator { sys: None }
}
pub fn set_system_ptr(&mut self, sys: &'static craydate_sys::playdate_sys) {
self.sys = Some(sys)
}
fn alloc_fn(&self, ptr: *mut u8, size: usize) -> *mut u8 {
let sys = self.sys.unwrap();
let realloc = sys.realloc.unwrap();
unsafe { realloc(ptr as *mut c_void, size as u64) as *mut u8 }
}
fn write_shift_behind_ptr(ptr: *mut u8, shift: usize) {
unsafe {
core::ptr::write_unaligned(ptr.sub(core::mem::size_of::<usize>()) as *mut usize, shift)
}
}
fn read_shift_behind_ptr(ptr: *mut u8) -> usize {
unsafe { core::ptr::read_unaligned(ptr.sub(core::mem::size_of::<usize>()) as *mut usize) }
}
const fn tests() {
const _STORAGE: usize = core::mem::size_of::<usize>();
const_assert!(_STORAGE == 4 || _STORAGE == 8);
const_assert_eq!(calc_alloc_size(1, 1), _STORAGE + 1);
const_assert!(_STORAGE != 4 || (calc_alloc_size(3, 2) == (2 * 2) + 3 + (2 - 1)));
const_assert!(_STORAGE != 8 || (calc_alloc_size(3, 2) == (2 * 4) + 3 + (2 - 1)));
const_assert_eq!(calc_alloc_size(5, 11), (11 * 1) + 5 + (11 - 1));
const_assert_eq!(calc_alloc_size(1, 4), _STORAGE + 1 + (4 - 1));
const_assert_eq!(calc_alloc_size(2, 4), _STORAGE + 2 + (4 - 1));
const_assert_eq!(calc_alloc_size(5, 4), _STORAGE + 5 + (4 - 1));
const_assert!(_STORAGE != 4 || (calc_alloc_size(5, 5) == (5 * 1) + 5 + (5 - 1)));
const_assert!(_STORAGE != 8 || (calc_alloc_size(5, 5) == (5 * 2) + 5 + (5 - 1)));
const_assert_eq!(calc_alloc_size(5, 20), (20 * 1) + 5 + (20 - 1));
const_assert!(_STORAGE != 4 || (calc_alloc_size(5, 3) == (3 * 2) + 5 + (3 - 1)));
const_assert!(_STORAGE != 8 || (calc_alloc_size(5, 3) == (3 * 3) + 5 + (3 - 1)));
const_assert!(calc_shift_for_align(0, 1) <= calc_alloc_size(1000, 1) - 1000);
const_assert!(calc_shift_for_align(1, 1) <= calc_alloc_size(1000, 1) - 1000);
const_assert!(calc_shift_for_align(2, 1) <= calc_alloc_size(1000, 1) - 1000);
const_assert!(calc_shift_for_align(3, 1) <= calc_alloc_size(1000, 1) - 1000);
const_assert!(calc_shift_for_align(0, 4) <= calc_alloc_size(1000, 4) - 1000);
const_assert!(calc_shift_for_align(1, 4) <= calc_alloc_size(1000, 4) - 1000);
const_assert!(calc_shift_for_align(2, 4) <= calc_alloc_size(1000, 4) - 1000);
const_assert!(calc_shift_for_align(3, 4) <= calc_alloc_size(1000, 4) - 1000);
const_assert!(calc_shift_for_align(4, 4) <= calc_alloc_size(1000, 4) - 1000);
const_assert!(calc_shift_for_align(5, 4) <= calc_alloc_size(1000, 4) - 1000);
const_assert!(calc_shift_for_align(0, 1000) <= calc_alloc_size(1000, 1000) - 1000);
const_assert!(calc_shift_for_align(1, 1000) <= calc_alloc_size(1000, 1000) - 1000);
const_assert!(calc_shift_for_align(2, 1000) <= calc_alloc_size(1000, 1000) - 1000);
const_assert!(calc_shift_for_align(3, 1000) <= calc_alloc_size(1000, 1000) - 1000);
const_assert!(calc_shift_for_align(999, 1000) <= calc_alloc_size(1000, 1000) - 1000);
const_assert!(calc_shift_for_align(1000, 1000) <= calc_alloc_size(1000, 1000) - 1000);
const_assert!(calc_shift_for_align(1001, 1000) <= calc_alloc_size(1000, 1000) - 1000);
const_assert!(calc_shift_for_align(0, 8) <= calc_alloc_size(3, 8) - 3);
const_assert!(calc_shift_for_align(1, 8) <= calc_alloc_size(3, 8) - 3);
const_assert!(calc_shift_for_align(7, 8) <= calc_alloc_size(3, 8) - 3);
const_assert!(calc_shift_for_align(8, 8) <= calc_alloc_size(3, 8) - 3);
const_assert!(calc_shift_for_align(9, 8) <= calc_alloc_size(3, 8) - 3);
const_assert!(calc_shift_for_align(0, 3) <= calc_alloc_size(100, 3) - 100);
const_assert!(calc_shift_for_align(1, 3) <= calc_alloc_size(100, 3) - 100);
const_assert!(calc_shift_for_align(2, 3) <= calc_alloc_size(100, 3) - 100);
const_assert!(calc_shift_for_align(3, 3) <= calc_alloc_size(100, 3) - 100);
const_assert!(calc_shift_for_align(4, 3) <= calc_alloc_size(100, 3) - 100);
const_assert!(calc_shift_for_align(0, 3) <= calc_alloc_size(9, 3) - 9);
const_assert!(calc_shift_for_align(1, 3) <= calc_alloc_size(9, 3) - 9);
const_assert!(calc_shift_for_align(2, 3) <= calc_alloc_size(9, 3) - 9);
const_assert!(calc_shift_for_align(3, 3) <= calc_alloc_size(9, 3) - 9);
const_assert!(calc_shift_for_align(4, 3) <= calc_alloc_size(9, 3) - 9);
const_assert!(calc_shift_for_align(8, 3) <= calc_alloc_size(9, 3) - 9);
const_assert!(calc_shift_for_align(9, 3) <= calc_alloc_size(9, 3) - 9);
const_assert!(calc_shift_for_align(10, 3) <= calc_alloc_size(9, 3) - 9);
}
}
#[cfg(not(doc))]
unsafe impl core::alloc::GlobalAlloc for Allocator {
unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
let size = calc_alloc_size(layout.size(), layout.align());
let ptr = self.alloc_fn(null_mut(), size) as *mut u8;
let shift = calc_shift_for_align(ptr as u64, layout.align());
assert!(layout.size() + shift <= size);
assert_eq!(ptr.add(shift) as usize % layout.align(), 0);
let ptr = ptr.add(shift);
Self::write_shift_behind_ptr(ptr, shift);
ptr
}
unsafe fn dealloc(&self, ptr: *mut u8, _layout: Layout) {
let shift = core::ptr::read_unaligned(ptr.sub(core::mem::size_of::<usize>()) as *mut usize);
self.alloc_fn(ptr.sub(shift), 0);
}
unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
let old_shift = Self::read_shift_behind_ptr(ptr);
let size = calc_alloc_size(new_size, layout.align());
let ptr = self.alloc_fn(ptr.sub(old_shift), size);
let new_shift = calc_shift_for_align(ptr as u64, layout.align());
assert!(layout.size() + new_shift < size);
assert_eq!(ptr.add(new_shift) as usize % layout.align(), 0);
let ptr = ptr.add(new_shift);
Self::write_shift_behind_ptr(ptr, new_shift);
ptr
}
}