use std::cell::Cell;
#[derive(Debug, Clone, Copy)]
pub(crate) struct StackBounds {
pub(crate) low: usize,
pub(crate) high: usize,
}
thread_local! {
static CACHED: Cell<Option<StackBounds>> = const { Cell::new(None) };
}
#[inline]
pub(crate) fn current_stack_bounds() -> Option<StackBounds> {
if let Ok(cached) = CACHED.try_with(|c| c.get()) {
if let Some(b) = cached {
return Some(b);
}
let fresh = query_os();
if let Some(b) = fresh {
let _ = CACHED.try_with(|c| c.set(Some(b)));
}
fresh
} else {
query_os()
}
}
#[cfg(target_os = "windows")]
fn query_os() -> Option<StackBounds> {
extern "system" {
fn GetCurrentThreadStackLimits(low_limit: *mut usize, high_limit: *mut usize);
}
let mut low: usize = 0;
let mut high: usize = 0;
unsafe {
GetCurrentThreadStackLimits(&mut low, &mut high);
}
if low == 0 || high == 0 || high <= low {
None
} else {
Some(StackBounds { low, high })
}
}
#[cfg(target_os = "linux")]
fn query_os() -> Option<StackBounds> {
#[repr(C, align(16))]
struct PthreadAttr([u8; 64]);
extern "C" {
fn pthread_self() -> usize;
fn pthread_getattr_np(thread: usize, attr: *mut PthreadAttr) -> i32;
fn pthread_attr_destroy(attr: *mut PthreadAttr) -> i32;
fn pthread_attr_getstack(
attr: *const PthreadAttr,
stackaddr: *mut *mut core::ffi::c_void,
stacksize: *mut usize,
) -> i32;
}
let mut attr = PthreadAttr([0u8; 64]);
let mut stackaddr: *mut core::ffi::c_void = core::ptr::null_mut();
let mut stacksize: usize = 0;
unsafe {
let tid = pthread_self();
if pthread_getattr_np(tid, &mut attr) != 0 {
return None;
}
let rc = pthread_attr_getstack(&attr, &mut stackaddr, &mut stacksize);
let _ = pthread_attr_destroy(&mut attr);
if rc != 0 {
return None;
}
}
let low = stackaddr as usize;
let high = low.checked_add(stacksize)?;
if low == 0 || stacksize == 0 {
None
} else {
Some(StackBounds { low, high })
}
}
#[cfg(any(
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
))]
fn query_os() -> Option<StackBounds> {
extern "C" {
fn pthread_self() -> usize;
fn pthread_get_stackaddr_np(thread: usize) -> *mut core::ffi::c_void;
fn pthread_get_stacksize_np(thread: usize) -> usize;
}
let (addr_top, size) = unsafe {
let tid = pthread_self();
(
pthread_get_stackaddr_np(tid) as usize,
pthread_get_stacksize_np(tid),
)
};
if addr_top == 0 || size == 0 {
return None;
}
let low = addr_top.checked_sub(size)?;
Some(StackBounds {
low,
high: addr_top,
})
}
#[cfg(not(any(
target_os = "windows",
target_os = "linux",
target_os = "macos",
target_os = "ios",
target_os = "freebsd",
target_os = "openbsd",
target_os = "netbsd",
target_os = "dragonfly"
)))]
fn query_os() -> Option<StackBounds> {
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn bounds_are_sane_on_host() {
let b = current_stack_bounds().expect("host OS should be supported");
assert!(b.low < b.high);
assert!(b.high - b.low >= 64 * 1024);
}
#[test]
fn cached_after_first_call() {
let a = current_stack_bounds();
let b = current_stack_bounds();
assert_eq!(a.map(|x| (x.low, x.high)), b.map(|x| (x.low, x.high)));
}
}