use crate::os::{last_os_error, page_size};
use crate::Control;
use core::any::type_name;
use core::cell::RefCell;
use core::num::NonZeroUsize;
use core::ptr::{null_mut, Alignment, NonNull};
use smallvec::SmallVec;
pub enum StackOrientation {
Upwards,
Downwards,
}
impl StackOrientation {
#[cfg(target_arch = "x86_64")]
pub const fn current() -> Self {
Self::Downwards
}
}
#[allow(rustdoc::private_intra_doc_links)]
pub struct Stack {
start: NonNull<u8>,
size: NonZeroUsize,
}
impl Stack {
pub(crate) fn new(min_size: NonZeroUsize, align: Alignment) -> Self {
let page_size = page_size();
debug_assert!(
Control::SIZE <= page_size.get(),
"control record at the bottom of stack must be aligned"
);
let size = min_size
.max(unsafe {
let two = NonZeroUsize::new_unchecked(2);
page_size.unchecked_mul(two)
});
let size = next_multiple_of(size, page_size);
let alloc_size = if align.as_usize() <= page_size.get() {
size
} else {
unsafe { size.unchecked_add(align.as_usize()) }
};
let start = unsafe {
libc::mmap(
null_mut(),
alloc_size.get(),
libc::PROT_READ | libc::PROT_WRITE,
libc::MAP_PRIVATE | libc::MAP_ANONYMOUS | libc::MAP_STACK,
-1,
0,
)
};
assert_ne!(
start,
libc::MAP_FAILED,
"stack mapping creation failed: {}",
last_os_error()
);
let offset = start.align_offset(align.as_usize());
if offset > 0 {
assert_ne!(
unsafe { libc::munmap(start, offset) },
-1,
"failed to trim the front of the stack mapping: {}",
last_os_error()
);
}
let start = unsafe { start.add(offset) };
let tail_start = unsafe { start.byte_add(size.get()) };
let tail_size = alloc_size.get() - offset - size.get();
if tail_size > 0 {
assert_ne!(
unsafe { libc::munmap(tail_start, tail_size) },
-1,
"failed to trim the back of the stack mapping: {}",
last_os_error()
);
}
let guard_start = match StackOrientation::current() {
StackOrientation::Upwards => unsafe { tail_start.byte_sub(page_size.get()) },
StackOrientation::Downwards => start,
};
assert_ne!(
unsafe { libc::mprotect(guard_start, page_size.get(), libc::PROT_NONE) },
-1,
"could not apply memory protection to guard page: {}",
last_os_error()
);
Self {
start: unsafe { NonNull::new_unchecked(start.cast()) },
size,
}
}
pub(crate) const fn control(&self) -> NonNull<Control> {
match StackOrientation::current() {
StackOrientation::Upwards => self.start,
StackOrientation::Downwards => unsafe {
self.start.byte_add(self.size.get() - Control::SIZE)
},
}
.cast()
}
pub(crate) const fn bottom(&self) -> NonNull<u8> {
match StackOrientation::current() {
StackOrientation::Upwards => unsafe { self.start.byte_add(Control::SIZE) },
StackOrientation::Downwards => unsafe {
self.start.byte_add(self.size.get() - Control::SIZE)
},
}
}
pub(crate) fn align_alloc<T>(&self, ptr: NonNull<u8>) -> (NonNull<T>, NonNull<u8>) {
let aligned = align_alloc::<T>(ptr, Alignment::of::<T>());
assert!(
match StackOrientation::current() {
StackOrientation::Upwards => {
let stack_end = unsafe { self.start.byte_add(self.size.get()) };
aligned.wrapping_add(1).cast() <= stack_end.as_ptr()
}
StackOrientation::Downwards => self.start.as_ptr() <= aligned.cast(),
},
"aligned value of type `{}` above {:?} would overflow the stack",
type_name::<T>(),
ptr
);
let aligned = unsafe { NonNull::new_unchecked(aligned) };
let end = match StackOrientation::current() {
StackOrientation::Upwards => unsafe { aligned.add(1) },
StackOrientation::Downwards => aligned,
};
(aligned, end.cast())
}
}
pub(crate) fn align_alloc<T>(ptr: NonNull<u8>, align: Alignment) -> *mut T {
let align_mask = align.as_usize() - 1;
ptr.as_ptr()
.map_addr(|a| match StackOrientation::current() {
StackOrientation::Upwards => (a + align_mask) & !align_mask,
StackOrientation::Downwards => (a & !align_mask) - size_of::<T>(),
})
.cast()
}
impl Drop for Stack {
fn drop(&mut self) {
assert_eq!(
unsafe { libc::munmap(self.start.cast().as_ptr(), self.size.get()) },
0, "failed to remove program stack mapping: {}",
last_os_error()
);
}
}
pub struct StackPool(
SmallVec<Stack, 3>,
);
impl StackPool {
pub const fn new() -> Self {
Self(SmallVec::new())
}
pub(crate) fn take(&mut self, min_size: NonZeroUsize, align: Alignment) -> Stack {
if let Some(ix) = self
.0
.iter()
.position(|s| s.size >= min_size && s.start.is_aligned_to(align.as_usize()))
{
self.0.swap_remove(ix)
} else {
Stack::new(min_size, align)
}
}
pub(crate) fn give(&mut self, stack: Stack) {
self.0.push(stack);
}
#[cfg(not(feature = "std"))]
pub fn give_from<F>(&mut self, coro: Coro<F>) {
assert!(
coro.is_finished(),
"cannot take stack of suspended coroutine"
);
self.0.push(coro.stack);
}
}
#[cfg(feature = "std")]
thread_local! {
pub static COMMON_POOL: RefCell<StackPool> = const { RefCell::new(StackPool::new()) };
}
fn next_multiple_of(lhs: NonZeroUsize, rhs: NonZeroUsize) -> NonZeroUsize {
match lhs.get().next_multiple_of(rhs.get()) {
0 => panic!("least multiple of {rhs} greater than or equal to {lhs} overflows a `usize`"),
r => unsafe { NonZeroUsize::new_unchecked(r) },
}
}