pub const RED_ZONE: usize = if cfg!(debug_assertions) {
4 << 20
} else {
512 << 10
};
#[inline(always)]
pub fn guard<R>(f: impl FnOnce() -> R) -> R {
guard_with(RED_ZONE, f)
}
#[inline(always)]
pub fn guard_with<R>(red_zone: usize, f: impl FnOnce() -> R) -> R {
#[cfg(not(target_family = "wasm"))]
{
native::guard_with(red_zone, f)
}
#[cfg(target_family = "wasm")]
{
let _ = red_zone;
f()
}
}
pub const CHECK_INTERVAL: usize = 16;
#[inline(always)]
pub fn check_due(depth: usize) -> bool {
depth.is_multiple_of(CHECK_INTERVAL)
}
#[doc(hidden)]
pub fn set_segment_size(bytes: usize) {
#[cfg(not(target_family = "wasm"))]
native::SEGMENT.store(bytes, std::sync::atomic::Ordering::Relaxed);
#[cfg(target_family = "wasm")]
let _ = bytes;
}
#[cfg(not(target_family = "wasm"))]
mod native {
use std::cell::Cell;
use std::sync::atomic::{AtomicUsize, Ordering};
use super::RED_ZONE;
pub(super) static SEGMENT: AtomicUsize = AtomicUsize::new(0);
const UNKNOWN: usize = usize::MAX - (1 << 30);
const UNBOUNDED: usize = 0;
thread_local! {
static LIMIT: Cell<usize> = const { Cell::new(UNKNOWN) };
}
#[inline(always)]
fn stack_address() -> usize {
let marker = 0u8;
std::hint::black_box(&marker) as *const u8 as usize
}
#[inline(always)]
pub(super) fn guard_with<R>(red_zone: usize, f: impl FnOnce() -> R) -> R {
let limit = LIMIT.with(Cell::get);
if stack_address() >= limit + red_zone {
f()
} else {
low(red_zone, f)
}
}
#[cold]
#[inline(never)]
fn low<R>(red_zone: usize, f: impl FnOnce() -> R) -> R {
let mut f = Some(f);
let mut result = None;
low_dyn(red_zone, &mut || result = f.take().map(|f| f()));
result.expect("a stack guard's step did not run")
}
fn low_dyn(red_zone: usize, step: &mut dyn FnMut()) {
let Some(left) = stacker::remaining_stack() else {
LIMIT.with(|l| l.set(UNBOUNDED));
return step();
};
let limit = stack_address().saturating_sub(left);
LIMIT.with(|l| l.set(limit));
if left >= red_zone {
return step();
}
let segment = match SEGMENT.load(Ordering::Relaxed) {
0 => 4 * red_zone.max(RED_ZONE),
bytes => bytes + red_zone.saturating_sub(RED_ZONE),
};
stacker::grow(segment, || {
let segment_limit = stacker::remaining_stack()
.map_or(UNBOUNDED, |left| stack_address().saturating_sub(left));
let restore = Restore(limit);
LIMIT.with(|l| l.set(segment_limit));
step();
drop(restore);
})
}
struct Restore(usize);
impl Drop for Restore {
fn drop(&mut self) {
LIMIT.with(|l| l.set(self.0));
}
}
}