#[inline]
pub(crate) fn maybe_grow<R>(red_zone: usize, stack_size: usize, f: impl FnOnce() -> R) -> R {
match remaining() {
Some(left) if left >= red_zone => f(),
_ => stacker::grow(stack_size, f),
}
}
#[cfg(not(any(target_os = "android", target_os = "ios", test)))]
#[inline]
fn remaining() -> Option<usize> {
stacker::remaining_stack()
}
#[cfg(any(target_os = "android", target_os = "ios", test))]
#[inline]
fn remaining() -> Option<usize> {
stacker::remaining_stack().or_else(|| host_limit().map(|limit| stack_pointer().saturating_sub(limit)))
}
#[cfg(any(target_os = "android", target_os = "ios", test))]
#[inline(always)]
fn stack_pointer() -> usize {
let marker = 0u8;
std::hint::black_box(&marker) as *const u8 as usize
}
#[cfg(any(target_os = "android", target_os = "ios", test))]
fn host_limit() -> Option<usize> {
use std::cell::Cell;
thread_local! {
static LIMIT: Cell<Option<Option<usize>>> = const { Cell::new(None) };
}
LIMIT.with(|cell| {
if let Some(known) = cell.get() {
return known;
}
let probed = probe();
cell.set(Some(probed));
probed
})
}
#[cfg(any(target_os = "android", all(test, target_os = "linux")))]
fn probe() -> Option<usize> {
unsafe {
let mut attr = std::mem::MaybeUninit::<libc::pthread_attr_t>::zeroed();
if libc::pthread_getattr_np(libc::pthread_self(), attr.as_mut_ptr()) != 0 {
return None;
}
let mut addr = std::ptr::null_mut();
let mut size = 0;
let mut guard = 0;
let ok = libc::pthread_attr_getstack(attr.as_ptr(), &mut addr, &mut size) == 0
&& libc::pthread_attr_getguardsize(attr.as_ptr(), &mut guard) == 0;
libc::pthread_attr_destroy(attr.as_mut_ptr());
ok.then(|| addr as usize + guard)
}
}
#[cfg(any(target_os = "ios", all(test, target_vendor = "apple")))]
fn probe() -> Option<usize> {
unsafe {
let me = libc::pthread_self();
let top = libc::pthread_get_stackaddr_np(me) as usize;
Some(top - libc::pthread_get_stacksize_np(me))
}
}
#[cfg(all(test, not(any(target_os = "android", target_os = "linux", target_vendor = "apple"))))]
fn probe() -> Option<usize> {
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_host_probe_brackets_the_current_frame() {
let Some(limit) = host_limit() else { return };
let sp = stack_pointer();
assert!(sp > limit, "the frame lies above the limit");
if let Some(stacker_says) = stacker::remaining_stack() {
assert!(stacker_says.abs_diff(sp - limit) < 64 * 1024);
}
}
#[test]
fn a_roomy_thread_runs_on_its_own_stack() {
let sp = stack_pointer();
let inner = maybe_grow(64 * 1024, 1024 * 1024, stack_pointer);
assert!(sp.abs_diff(inner) < 64 * 1024, "no segment switch");
}
#[test]
fn a_tight_thread_grows_and_the_segment_reports_itself() {
let sp = stack_pointer();
let (inner, left) = maybe_grow(usize::MAX / 2, 1024 * 1024, || (stack_pointer(), remaining()));
assert!(sp.abs_diff(inner) > 64 * 1024, "ran on a segment");
let left = left.expect("stacker knows its own segment");
assert!(left > 512 * 1024 && left <= 1024 * 1024 + 64 * 1024);
}
}