pub(crate) const MAX_INLINE_SIZE: usize = if cfg!(debug_assertions) { 2 * 1024 } else { 16 * 1024 };
pub fn current_thread_committed_stack_bytes() -> Option<usize> {
#[cfg(windows)]
{
win::committed_stack_bytes()
}
#[cfg(not(windows))]
{
None
}
}
#[cfg(windows)]
mod win {
use std::ffi::c_void;
use std::mem::size_of;
use windows_sys::Win32::System::Memory::VirtualQuery;
use windows_sys::Win32::System::Memory::MEMORY_BASIC_INFORMATION;
use windows_sys::Win32::System::Memory::MEM_COMMIT;
use windows_sys::Win32::System::Memory::PAGE_GUARD;
use windows_sys::Win32::System::Threading::GetCurrentThreadStackLimits;
pub(super) fn committed_stack_bytes() -> Option<usize> {
let mut low = 0_usize;
let mut high = 0_usize;
unsafe { GetCurrentThreadStackLimits(&mut low, &mut high) };
let mut address = low;
while address < high {
let mut region = MEMORY_BASIC_INFORMATION::default();
let written = unsafe {
VirtualQuery(
address as *const c_void,
&mut region,
size_of::<MEMORY_BASIC_INFORMATION>(),
)
};
if written == 0 || region.RegionSize == 0 {
return None;
}
let base = region.BaseAddress as usize;
if region.State == MEM_COMMIT && region.Protect & PAGE_GUARD == 0 {
return high.checked_sub(base);
}
address = base.checked_add(region.RegionSize)?;
}
None
}
}
#[cfg(test)]
mod tests {
use super::current_thread_committed_stack_bytes;
#[test]
#[cfg(windows)]
fn committed_stack_grows_with_stack_use() {
let (before, after) = std::thread::Builder::new()
.stack_size(1024 * 1024)
.spawn(|| {
let before = current_thread_committed_stack_bytes().expect("committed stack before use");
(before, use_stack(256 * 1024))
})
.expect("spawn stack test thread")
.join()
.expect("stack test thread");
assert!(before > 0);
assert!(
after >= before + 200 * 1024,
"committed stack {after} did not grow past {before} after using 256 KiB"
);
}
#[cfg(windows)]
#[inline(never)]
fn use_stack(bytes: usize) -> usize {
const CHUNK: usize = 16 * 1024;
let mut frame = [0_u8; CHUNK];
std::hint::black_box(&mut frame);
let committed = if bytes > CHUNK {
use_stack(bytes - CHUNK)
} else {
current_thread_committed_stack_bytes().expect("committed stack after use")
};
std::hint::black_box(&frame);
committed
}
#[test]
#[cfg(not(windows))]
fn other_platforms_do_not_report_a_committed_stack() {
assert_eq!(current_thread_committed_stack_bytes(), None);
}
}