use super::align::assert_aligned;
use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit;
#[repr(C)]
pub struct Stack<AlignAs, const CAPACITY: usize> {
_align: [AlignAs; 0],
data: UnsafeCell<[MaybeUninit<u8>; CAPACITY]>,
pub(super) len: Cell<usize>,
}
impl<AlignAs, const CAPACITY: usize> Stack<AlignAs, CAPACITY> {
pub const fn new() -> Self {
Self {
len: Cell::new(0),
_align: [],
data: UnsafeCell::new([MaybeUninit::uninit(); CAPACITY]),
}
}
pub fn try_push(&self, n: usize) -> Option<*mut u8> {
assert_aligned::<AlignAs>(n);
if n == 0 {
return Some(core::ptr::dangling_mut::<AlignAs>().cast());
}
let capacity_left = unsafe { CAPACITY.unchecked_sub(self.len.get()) };
if n > capacity_left {
return None;
}
let ptr = unsafe { self.data.get().byte_add(self.len.get()) };
let ptr: *mut u8 = ptr.cast();
self.len.set(unsafe { self.len.get().unchecked_add(n) });
Some(ptr)
}
pub unsafe fn pop_unchecked(&self, n: usize) {
assert_aligned::<AlignAs>(n);
self.len.set(unsafe { self.len.get().unchecked_sub(n) });
}
pub fn contains_allocated(&self, ptr: *const u8, n: usize) -> bool {
if n == 0 {
return true;
}
CAPACITY.checked_sub(n).is_some_and(|limit| {
ptr.addr().wrapping_sub(self.data.get().addr()) <= limit
})
}
}
#[cfg(test)]
mod test {
use super::*;
use alloc::boxed::Box;
#[test]
#[should_panic]
fn unaligned_push() {
Stack::<u16, 16>::new().try_push(3);
}
#[test]
#[should_panic]
fn unaligned_pop() {
unsafe {
Stack::<u16, 16>::new().pop_unchecked(1);
}
}
#[test]
fn overaligned() {
#[repr(align(256))]
struct Overaligned;
let stack = Stack::<Overaligned, 256>::new();
let ptr = stack.try_push(256).expect("failed to allocate");
assert_eq!(ptr.addr() % 256, 0);
}
#[test]
fn consecutive() {
let stack = Stack::<u8, 256>::new();
assert_eq!(stack.len.get(), 0);
let ptr1 = stack.try_push(5).expect("failed to allocate");
assert_eq!(stack.len.get(), 5);
let ptr2 = stack.try_push(8).expect("failed to allocate");
assert_eq!(stack.len.get(), 13);
let ptr3 = stack.try_push(1).expect("failed to allocate");
assert_eq!(stack.len.get(), 14);
assert_eq!(ptr2.addr() - ptr1.addr(), 5);
assert_eq!(ptr3.addr() - ptr2.addr(), 8);
unsafe { stack.pop_unchecked(1) };
assert_eq!(stack.len.get(), 13);
let ptr4 = stack.try_push(2).expect("failed to allocate");
assert_eq!(ptr3.addr(), ptr4.addr());
}
#[test]
fn too_large() {
let stack = Stack::<u8, 16>::new();
stack.try_push(5);
assert!(stack.try_push(12).is_none(), "allocation fit");
}
#[test]
fn pop_zero() {
let stack = Stack::<u8, 16>::new();
unsafe {
stack.pop_unchecked(0);
}
}
#[test]
fn push_zero() {
let stack = Stack::<u8, 16>::new();
stack.try_push(16).expect("failed to allocate");
stack.try_push(0).expect("failed to allocate");
stack.try_push(0).expect("failed to allocate");
}
#[test]
fn contains_allocated() {
let stack = Stack::<u8, 16>::new();
let ptr = stack.try_push(1).expect("failed to allocate");
assert!(stack.contains_allocated(ptr, 1));
let ptr = stack.try_push(14).expect("failed to allocate");
assert!(stack.contains_allocated(ptr, 14));
let ptr = stack.try_push(1).expect("failed to allocate");
assert!(stack.contains_allocated(ptr, 1));
let ptr = stack.try_push(0).expect("failed to allocate");
assert!(stack.contains_allocated(ptr, 0));
assert!(stack.contains_allocated(core::ptr::null(), 0));
assert!(!stack.contains_allocated(core::ptr::null(), 1));
assert!(!stack.contains_allocated(&*Box::new(1), 1));
}
#[test]
fn unique() {
let stack = Stack::<u8, 256>::new();
let ptr1 = unsafe { &mut *stack.try_push(1).expect("failed to allocate") };
*ptr1 = 1;
let ptr2 = unsafe { &mut *stack.try_push(1).expect("failed to allocate") };
*ptr2 = 2;
assert_eq!(*ptr1, 1);
assert_eq!(*ptr2, 2);
unsafe {
stack.pop_unchecked(1);
}
assert_eq!(*ptr1, 1);
}
}