use std::error::Error;
use std::fmt::{self, Display};
use std::io;
use std::mem::MaybeUninit;
use std::os::raw::c_void;
use std::ptr;
#[cfg_attr(unix, path = "unix.rs")]
#[cfg_attr(windows, path = "windows.rs")]
pub mod sys;
pub use sys::overflow;
const ALIGN: usize = std::mem::size_of::<StackBoxHeader>();
const HEADER_SIZE: usize = std::mem::size_of::<StackBoxHeader>() / std::mem::size_of::<usize>();
struct StackBoxHeader {
stack: Stack,
data_size: usize,
need_drop: usize,
}
pub struct StackBox<T> {
ptr: ptr::NonNull<T>,
}
impl<T> StackBox<T> {
fn new_uninit(stack: &mut Stack, need_drop: usize) -> MaybeUninit<Self> {
let _ = stack as *mut Stack;
let offset = unsafe { &mut *stack.get_offset() };
let layout = std::alloc::Layout::new::<T>();
let align = std::cmp::max(layout.align(), ALIGN);
let size = ((layout.size() + align - 1) & !(align - 1)) / std::mem::size_of::<usize>();
let u_align = align / std::mem::size_of::<usize>();
let pad_size = u_align - (*offset + size) % u_align;
let data_size = size + pad_size;
*offset += data_size;
let ptr = unsafe { ptr::NonNull::new_unchecked(stack.end() as *mut T) };
*offset += HEADER_SIZE;
unsafe {
let mut header = ptr::NonNull::new_unchecked(stack.end() as *mut StackBoxHeader);
let header = header.as_mut();
header.data_size = data_size;
header.need_drop = need_drop;
header.stack = stack.shadow_clone();
MaybeUninit::new(StackBox { ptr })
}
}
fn get_header(&self) -> &StackBoxHeader {
unsafe {
let header = (self.ptr.as_ptr() as *mut usize).offset(0 - HEADER_SIZE as isize);
&*(header as *const StackBoxHeader)
}
}
pub(crate) unsafe fn init(&mut self, data: T) {
ptr::write(self.ptr.as_ptr(), data);
}
pub(crate) fn as_ptr(&self) -> *mut T {
self.ptr.as_ptr()
}
#[inline]
pub(crate) unsafe fn from_raw(raw: *mut T) -> Self {
StackBox {
ptr: ptr::NonNull::new_unchecked(raw),
}
}
}
pub struct Func {
data: *mut (),
size: usize,
offset: *mut usize,
func: fn(*mut ()),
drop: fn(*mut ()),
}
impl Func {
pub fn call_once(mut self) {
let data = self.data;
self.data = ptr::null_mut();
(self.func)(data);
}
}
impl Drop for Func {
fn drop(&mut self) {
if !self.data.is_null() {
(self.drop)(self.data);
}
unsafe { *self.offset -= self.size };
}
}
impl<F: FnOnce()> StackBox<F> {
fn call_once(data: *mut ()) {
unsafe {
let data = data as *mut F;
let f = data.read();
f();
}
}
fn drop_inner(data: *mut ()) {
unsafe {
let data = data as *mut F;
ptr::drop_in_place(data);
}
}
pub(crate) fn new_fn_once(stack: &mut Stack, data: F) -> Func {
unsafe {
let mut d = Self::new_uninit(stack, 0);
(*d.as_mut_ptr()).init(data);
let d = d.assume_init();
let header = d.get_header();
let f = Func {
data: d.ptr.as_ptr() as *mut (),
size: header.data_size + HEADER_SIZE,
offset: stack.get_offset(),
func: Self::call_once,
drop: Self::drop_inner,
};
std::mem::forget(d);
f
}
}
}
impl<T> std::ops::Deref for StackBox<T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { self.ptr.as_ref() }
}
}
impl<T> std::ops::DerefMut for StackBox<T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.ptr.as_mut() }
}
}
impl<T> Drop for StackBox<T> {
fn drop(&mut self) {
let header = self.get_header();
unsafe {
*header.stack.get_offset() -= header.data_size + HEADER_SIZE;
ptr::drop_in_place(self.ptr.as_ptr());
if header.need_drop != 0 {
header.stack.drop_stack();
}
}
}
}
#[derive(Debug)]
pub enum StackError {
ExceedsMaximumSize(usize),
IoError(io::Error),
}
impl Display for StackError {
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
match *self {
StackError::ExceedsMaximumSize(size) => write!(
fmt,
"Requested more than max size of {size} bytes for a stack"
),
StackError::IoError(ref e) => e.fmt(fmt),
}
}
}
impl Error for StackError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match *self {
StackError::ExceedsMaximumSize(_) => None,
StackError::IoError(ref e) => Some(e),
}
}
}
#[derive(Debug)]
pub struct SysStack {
top: *mut c_void,
bottom: *mut c_void,
}
impl SysStack {
#[inline]
pub unsafe fn new(top: *mut c_void, bottom: *mut c_void) -> SysStack {
debug_assert!(top >= bottom);
SysStack { top, bottom }
}
#[inline]
pub fn top(&self) -> *mut c_void {
self.top
}
#[inline]
pub fn bottom(&self) -> *mut c_void {
self.bottom
}
#[inline]
pub fn len(&self) -> usize {
self.top as usize - self.bottom as usize
}
#[inline]
pub fn min_size() -> usize {
sys::min_stack_size()
}
fn allocate(mut size: usize, protected: bool) -> Result<SysStack, StackError> {
let page_size = sys::page_size();
let min_stack_size = sys::min_stack_size();
let max_stack_size = sys::max_stack_size();
let add_shift = i32::from(protected);
let add = page_size << add_shift;
if size < min_stack_size {
size = min_stack_size;
}
size = (size - 1) & !(page_size.overflowing_sub(1).0);
if let Some(size) = size.checked_add(add) {
if size <= max_stack_size {
let mut ret = unsafe { sys::allocate_stack(size) };
if protected {
if let Ok(stack) = ret {
ret = unsafe { sys::protect_stack(&stack) };
}
}
return ret.map_err(StackError::IoError);
}
}
Err(StackError::ExceedsMaximumSize(max_stack_size - add))
}
}
unsafe impl Send for SysStack {}
pub struct Stack {
buf: SysStack,
}
impl Stack {
pub fn new(size: usize) -> Stack {
let track = (size & 1) != 0;
let bytes = usize::max(size * std::mem::size_of::<usize>(), SysStack::min_size());
let buf = SysStack::allocate(bytes, true).expect("failed to alloc sys stack");
let stk = Stack { buf };
let count = if track {
stk.size()
} else {
8
};
unsafe {
let buf = stk.buf.bottom as *mut usize;
ptr::write_bytes(buf, 0xEE, count);
}
let offset = stk.get_offset();
unsafe { *offset = 1 };
stk
}
pub fn get_used_size(&self) -> usize {
let mut offset: usize = 0;
unsafe {
let mut magic: usize = 0xEE;
ptr::write_bytes(&mut magic, 0xEE, 1);
let mut ptr = self.buf.bottom as *mut usize;
while *ptr == magic {
offset += 1;
ptr = ptr.offset(1);
}
}
let cap = self.size();
cap - offset
}
#[inline]
pub fn size(&self) -> usize {
self.buf.len() / std::mem::size_of::<usize>()
}
pub fn end(&self) -> *mut usize {
let offset = self.get_offset();
unsafe { (self.buf.top as *mut usize).offset(0 - *offset as isize) }
}
#[allow(dead_code)]
pub fn begin(&self) -> *mut usize {
self.buf.bottom as *mut _
}
pub fn alloc_uninit_box<T>(&mut self) -> MaybeUninit<StackBox<T>> {
StackBox::<T>::new_uninit(self, 1)
}
fn get_offset(&self) -> *mut usize {
unsafe { (self.buf.top as *mut usize).offset(-1) }
}
fn drop_stack(&self) {
if self.buf.len() == 0 {
return;
}
let page_size = sys::page_size();
let guard = (self.buf.bottom as usize - page_size) as *mut c_void;
let size_with_guard = self.buf.len() + page_size;
unsafe {
sys::deallocate_stack(guard, size_with_guard);
}
}
fn shadow_clone(&self) -> Self {
Stack {
buf: SysStack {
top: self.buf.top,
bottom: self.buf.bottom,
},
}
}
}
impl fmt::Debug for Stack {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let offset = self.get_offset();
write!(f, "Stack<{:?}, Offset={}>", self.buf, unsafe { *offset })
}
}