use std::sync::atomic::{AtomicU8, AtomicUsize, Ordering::Relaxed};
use std::sync::Mutex;
use std::sync::OnceLock;
#[cfg(not(windows))]
use libc::{MAP_ANON, MAP_FAILED, MAP_PRIVATE, PROT_NONE, PROT_READ, PROT_WRITE};
#[cfg(not(windows))]
use super::g::current_g;
use super::g::{casgstatus, readgstatus, Stack, GCOPYSTACK, STACK_GUARD, G};
#[cfg(windows)]
mod win32 {
pub const MEM_COMMIT: u32 = 0x0000_1000;
pub const MEM_RESERVE: u32 = 0x0000_2000;
pub const MEM_RELEASE: u32 = 0x0000_8000;
pub const PAGE_READWRITE: u32 = 0x04;
pub const PAGE_NOACCESS: u32 = 0x01;
#[link(name = "kernel32")]
unsafe extern "system" {
pub fn VirtualAlloc(
lpAddress: *mut u8,
dwSize: usize,
flAllocationType: u32,
flProtect: u32,
) -> *mut u8;
pub fn VirtualFree(
lpAddress: *mut u8,
dwSize: usize,
dwFreeType: u32,
) -> i32;
pub fn VirtualProtect(
lpAddress: *mut u8,
dwSize: usize,
flNewProtect: u32,
lpflOldProtect: *mut u32,
) -> i32;
}
}
#[allow(dead_code)] pub(crate) const STACK_MIN: usize = 2 * 1024;
pub(crate) const STACK_MAX: usize = 1024 * 1024 * 1024;
#[cfg(any(all(windows, debug_assertions), all(target_os = "macos", debug_assertions)))]
pub(crate) const GOROUTINE_STACK_BYTES: usize = 64 * 1024;
#[cfg(all(debug_assertions, not(any(windows, target_os = "macos"))))]
pub(crate) const GOROUTINE_STACK_BYTES: usize = 16 * 1024;
#[cfg(not(debug_assertions))]
pub(crate) const GOROUTINE_STACK_BYTES: usize = 32 * 1024;
pub(crate) const G0_STACK_BYTES: usize = 512 * 1024;
pub(crate) fn page_size() -> usize {
static PAGE_SIZE: OnceLock<usize> = OnceLock::new();
*PAGE_SIZE.get_or_init(|| {
#[cfg(not(windows))]
{
let n = unsafe { libc::sysconf(libc::_SC_PAGESIZE) };
assert!(n > 0, "sysconf(_SC_PAGESIZE) returned {n}");
n as usize
}
#[cfg(windows)]
{ 4096usize }
})
}
pub(crate) unsafe fn stack_alloc_size(size: usize) -> Result<Stack, &'static str> {
debug_assert!(size.is_power_of_two() || size == STACK_MAX,
"stack_alloc_size: size must be a power of two");
let ps = page_size();
let total = size + ps;
#[cfg(not(windows))]
{
let base = unsafe {
libc::mmap(
std::ptr::null_mut(),
total,
PROT_READ | PROT_WRITE,
MAP_ANON | MAP_PRIVATE,
-1,
0,
)
};
if base == MAP_FAILED {
return Err("stack_alloc_size: mmap failed");
}
if unsafe { libc::mprotect(base, ps, PROT_NONE) } != 0 {
unsafe { libc::munmap(base, total) };
return Err("stack_alloc_size: mprotect guard page failed");
}
let base_addr = base as usize;
Ok(Stack { lo: base_addr + ps, hi: base_addr + total })
}
#[cfg(windows)]
{
use win32::*;
let base = unsafe {
VirtualAlloc(
std::ptr::null_mut(),
total,
MEM_COMMIT | MEM_RESERVE,
PAGE_READWRITE,
)
};
if base.is_null() {
return Err("stack_alloc_size: VirtualAlloc failed");
}
let mut old_protect: u32 = 0;
if unsafe { VirtualProtect(base, ps, PAGE_NOACCESS, &mut old_protect) } == 0 {
unsafe { VirtualFree(base, 0, MEM_RELEASE) };
return Err("stack_alloc_size: VirtualProtect guard page failed");
}
let base_addr = base as usize;
Ok(Stack { lo: base_addr + ps, hi: base_addr + total })
}
}
pub(crate) unsafe fn stack_alloc() -> Result<Stack, &'static str> {
unsafe { stack_pool_alloc(GOROUTINE_STACK_BYTES) }
}
pub(crate) unsafe fn g0_stack_alloc() -> Result<Stack, &'static str> {
unsafe { stack_pool_alloc(G0_STACK_BYTES) }
}
pub(crate) unsafe fn stack_free(stack: &Stack) {
let ps = page_size();
let base = (stack.lo - ps) as *mut u8;
#[cfg(not(windows))]
{
let total = (stack.hi - stack.lo) + ps;
unsafe { libc::munmap(base as *mut libc::c_void, total) };
}
#[cfg(windows)]
{
use win32::{MEM_RELEASE, VirtualFree};
unsafe { VirtualFree(base, 0, MEM_RELEASE) };
}
}
const POOL_MIN_CLASS: u32 = 14;
const POOL_MAX_CLASS: u32 = 21;
const NUM_POOL_CLASSES: usize = (POOL_MAX_CLASS - POOL_MIN_CLASS + 1) as usize;
const MAX_POOLED_BYTES: usize = 64 * 1024 * 1024;
static STACK_POOL: [Mutex<Vec<usize>>; NUM_POOL_CLASSES] =
[const { Mutex::new(Vec::new()) }; NUM_POOL_CLASSES];
static POOLED_BYTES: AtomicUsize = AtomicUsize::new(0);
static STACKPOOL_OFF: AtomicU8 = AtomicU8::new(u8::MAX);
#[inline]
fn stackpool_off() -> bool {
let mut v = STACKPOOL_OFF.load(Relaxed);
if v == u8::MAX {
v = match std::env::var("GOLIB_STACKPOOL_OFF") {
Ok(s) if s == "1" => 1,
_ => 0,
};
STACKPOOL_OFF.store(v, Relaxed);
}
v == 1
}
#[inline]
fn pool_class(size: usize) -> Option<usize> {
if !size.is_power_of_two() {
return None;
}
let exp = size.trailing_zeros();
if !(POOL_MIN_CLASS..=POOL_MAX_CLASS).contains(&exp) {
return None;
}
Some((exp - POOL_MIN_CLASS) as usize)
}
pub(crate) unsafe fn stack_pool_alloc(size: usize) -> Result<Stack, &'static str> {
if !stackpool_off() && let Some(cls) = pool_class(size) {
let popped = {
let _pin = super::m::m_lock();
STACK_POOL[cls].lock().unwrap().pop()
};
if let Some(lo) = popped {
POOLED_BYTES.fetch_sub(size, Relaxed);
return Ok(Stack { lo, hi: lo + size });
}
}
unsafe { stack_alloc_size(size) }
}
pub(crate) unsafe fn stack_pool_free(stack: &Stack) {
let size = stack.hi - stack.lo;
if !stackpool_off() && let Some(cls) = pool_class(size) {
let prev = POOLED_BYTES.fetch_add(size, Relaxed);
if prev + size <= MAX_POOLED_BYTES {
let _pin = super::m::m_lock();
STACK_POOL[cls].lock().unwrap().push(stack.lo);
return;
}
POOLED_BYTES.fetch_sub(size, Relaxed);
}
unsafe { stack_free(stack) };
}
#[cfg(not(windows))]
pub(crate) unsafe fn newstack(gp: *mut G) -> isize {
let old_stack = Stack {
lo: unsafe { (*gp).stack.lo },
hi: unsafe { (*gp).stack.hi },
};
let old_size = old_stack.hi - old_stack.lo;
if old_size >= STACK_MAX {
eprintln!("goroutine stack overflow: stack size {old_size} >= STACK_MAX ({STACK_MAX})");
unsafe { libc::abort() };
}
let new_size = (old_size * 2).min(STACK_MAX);
let new_stack = unsafe {
stack_alloc_size(new_size).expect("newstack: failed to allocate new goroutine stack")
};
let delta = unsafe { copystack(gp, &old_stack, &new_stack) };
unsafe {
(*gp).stack = Stack { lo: new_stack.lo, hi: new_stack.hi };
(*gp).stackguard0 = new_stack.lo + STACK_GUARD;
}
unsafe { stack_free(&old_stack) };
delta
}
unsafe fn copystack(gp: *mut G, old_stack: &Stack, new_stack: &Stack) -> isize {
let old_status = unsafe { readgstatus(gp) };
unsafe { casgstatus(gp, old_status, GCOPYSTACK) };
let old_lo = old_stack.lo;
let old_hi = old_stack.hi;
let new_lo = new_stack.lo;
let new_hi = new_stack.hi;
let _old_size = old_hi - old_lo;
let new_size = new_hi - new_lo;
let saved_sp = unsafe { (*gp).sched.sp };
let live_start_old = if saved_sp != 0 && saved_sp >= old_lo && saved_sp < old_hi {
saved_sp
} else {
old_lo };
let live_bytes = old_hi - live_start_old;
let live_start_new = new_hi - live_bytes;
debug_assert!(
new_size >= live_bytes,
"copystack: new stack ({new_size} B) too small for live region ({live_bytes} B)"
);
unsafe {
std::ptr::copy_nonoverlapping(
live_start_old as *const u8,
live_start_new as *mut u8,
live_bytes,
);
}
let delta: isize = new_hi as isize - old_hi as isize;
let old_guard_lo = old_lo.saturating_sub(page_size());
let mut addr = live_start_new;
let word = std::mem::size_of::<usize>();
while addr + word <= new_hi {
let val = unsafe { *(addr as *const usize) };
if val >= old_guard_lo && val < old_hi {
unsafe { *(addr as *mut usize) = ((val as isize) + delta) as usize };
}
addr += word;
}
unsafe {
let sp = (*gp).sched.sp;
if sp >= old_lo && sp < old_hi {
(*gp).sched.sp = ((sp as isize) + delta) as usize;
}
let bp = (*gp).sched.bp;
if bp >= old_lo && bp < old_hi {
(*gp).sched.bp = ((bp as isize) + delta) as usize;
}
let pua = (*gp).park_unlock_arg as usize;
if pua >= old_lo && pua < old_hi {
(*gp).park_unlock_arg = ((pua as isize) + delta) as *mut u8;
}
}
unsafe { casgstatus(gp, GCOPYSTACK, old_status) };
delta
}
#[cfg(not(windows))]
static PREV_SIGSEGV: Mutex<Option<libc::sigaction>> = Mutex::new(None);
#[cfg(not(windows))]
pub(crate) unsafe fn install_sigsegv_handler() {
let mut sa: libc::sigaction = unsafe { std::mem::zeroed() };
sa.sa_sigaction = sigsegv_handler as *const () as usize;
sa.sa_flags = (libc::SA_SIGINFO | libc::SA_ONSTACK | libc::SA_RESTART) as _;
unsafe { libc::sigemptyset(&mut sa.sa_mask) };
let mut old: libc::sigaction = unsafe { std::mem::zeroed() };
let ret = unsafe { libc::sigaction(libc::SIGSEGV, &sa, &mut old) };
assert_eq!(ret, 0, "install_sigsegv_handler: sigaction failed");
*PREV_SIGSEGV.lock().unwrap() = Some(old);
}
#[cfg(not(windows))]
pub(crate) unsafe fn try_grow_stack_from_signal(
fault_addr: usize,
ctx: *mut libc::c_void,
) -> bool {
let gp = current_g();
if gp.is_null() { return false; }
let stack_lo = unsafe { (*gp).stack.lo };
let stack_hi = unsafe { (*gp).stack.hi };
let guard_lo = stack_lo - page_size();
let guard_hi = stack_lo;
if fault_addr < guard_lo || fault_addr >= guard_hi {
return false; }
unsafe { (*gp).sched.sp = 0 };
let old_lo = stack_lo;
let old_hi = stack_hi;
let delta = unsafe { newstack(gp) };
unsafe { update_sp_in_context(ctx, old_lo, old_hi, delta) };
true
}
#[cfg(not(windows))]
unsafe extern "C" fn sigsegv_handler(
sig: libc::c_int,
info: *mut libc::siginfo_t,
ctx: *mut libc::c_void,
) {
let fault_addr = unsafe { (*info).si_addr() } as usize;
if unsafe { try_grow_stack_from_signal(fault_addr, ctx) } {
return; }
{
#[inline(always)]
unsafe fn sig_write(msg: &[u8]) {
unsafe { libc::write(2, msg.as_ptr() as *const libc::c_void, msg.len()) };
}
#[inline(always)]
unsafe fn sig_hex(label: &[u8], val: u64) {
unsafe { sig_write(label) };
const H: &[u8] = b"0123456789abcdef";
let mut buf = [b'0'; 19];
buf[0] = b'0'; buf[1] = b'x';
for i in 0..16usize { buf[17 - i] = H[((val >> (i * 4)) & 0xf) as usize]; }
buf[18] = b'\n';
unsafe { sig_write(&buf) };
}
unsafe {
sig_write(b"[go-lib SIGSEGV] non-stack fault\n");
sig_hex(b"[go-lib SIGSEGV] fault_addr = ", fault_addr as u64);
}
let gp = current_g();
if !gp.is_null() {
unsafe {
sig_hex(b"[go-lib SIGSEGV] g.stack.lo = ", (*gp).stack.lo as u64);
sig_hex(b"[go-lib SIGSEGV] g.stack.hi = ", (*gp).stack.hi as u64);
}
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
if !ctx.is_null() {
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let ss = &(*(*uc).uc_mcontext).__ss;
sig_hex(b"[go-lib SIGSEGV] PC = ", ss.__pc);
sig_hex(b"[go-lib SIGSEGV] LR = ", ss.__lr);
sig_hex(b"[go-lib SIGSEGV] SP = ", ss.__sp);
sig_hex(b"[go-lib SIGSEGV] FP = ", ss.__fp);
sig_hex(b"[go-lib SIGSEGV] x19 = ", ss.__x[19]);
sig_hex(b"[go-lib SIGSEGV] x20 = ", ss.__x[20]);
sig_hex(b"[go-lib SIGSEGV] x21 = ", ss.__x[21]);
sig_hex(b"[go-lib SIGSEGV] x22 = ", ss.__x[22]);
}
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
if !ctx.is_null() {
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let ss = &(*(*uc).uc_mcontext).__ss;
sig_hex(b"[go-lib SIGSEGV] RIP = ", ss.__rip);
sig_hex(b"[go-lib SIGSEGV] RSP = ", ss.__rsp);
}
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
if !ctx.is_null() {
unsafe {
let uc = ctx as *mut libc::ucontext_t;
let gregs = &(*uc).uc_mcontext.gregs;
sig_hex(b"[go-lib SIGSEGV] RIP = ", gregs[libc::REG_RIP as usize] as u64);
sig_hex(b"[go-lib SIGSEGV] RSP = ", gregs[libc::REG_RSP as usize] as u64);
sig_hex(b"[go-lib SIGSEGV] RBP = ", gregs[libc::REG_RBP as usize] as u64);
sig_hex(b"[go-lib SIGSEGV] RBX = ", gregs[libc::REG_RBX as usize] as u64);
sig_hex(b"[go-lib SIGSEGV] R12 = ", gregs[libc::REG_R12 as usize] as u64);
sig_hex(b"[go-lib SIGSEGV] R13 = ", gregs[libc::REG_R13 as usize] as u64);
sig_hex(b"[go-lib SIGSEGV] R14 = ", gregs[libc::REG_R14 as usize] as u64);
sig_hex(b"[go-lib SIGSEGV] R15 = ", gregs[libc::REG_R15 as usize] as u64);
}
}
let mp = super::m::current_m();
if !mp.is_null() {
let g0 = unsafe { (*mp).g0 };
if !g0.is_null() {
unsafe {
sig_hex(b"[go-lib SIGSEGV] g0.stack.lo = ", (*g0).stack.lo as u64);
sig_hex(b"[go-lib SIGSEGV] g0.stack.hi = ", (*g0).stack.hi as u64);
}
}
}
}
let prev = *PREV_SIGSEGV.lock().unwrap();
match prev {
Some(old) if old.sa_sigaction != libc::SIG_DFL
&& old.sa_sigaction != libc::SIG_IGN => {
type SaFn = unsafe extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void);
let f: SaFn = unsafe { std::mem::transmute(old.sa_sigaction) };
unsafe { f(sig, info, ctx) };
}
_ => {
unsafe { libc::raise(libc::SIGSEGV) };
}
}
}
#[cfg(all(not(windows), target_arch = "aarch64"))]
unsafe fn sp_predecrement_at_pc(pc: usize) -> usize {
if pc == 0 { return 0; }
let instr = unsafe { *(pc as *const u32) };
if (instr >> 22) & 0x3FF == 0x2A6 {
let imm7 = (((instr >> 15) & 0x7F) as i32) << 25 >> 25;
if imm7 < 0 { return (-imm7 * 8) as usize; }
}
if (instr >> 22) & 0x3FF == 0x0A6 {
let imm7 = (((instr >> 15) & 0x7F) as i32) << 25 >> 25;
if imm7 < 0 { return (-imm7 * 4) as usize; }
}
if (instr >> 21) & 0x7FF == 0x7C0 && (instr >> 10) & 3 == 3 {
let imm9 = (((instr >> 12) & 0x1FF) as i32) << 23 >> 23;
if imm9 < 0 { return (-imm9) as usize; }
}
0
}
#[cfg(not(windows))]
unsafe fn update_sp_in_context(
ctx: *mut libc::c_void,
old_lo: usize,
old_hi: usize,
delta: isize,
) {
let old_guard_lo = old_lo.saturating_sub(page_size());
#[inline(always)]
fn adj(val: u64, lo: usize, hi: usize, delta: isize) -> u64 {
let v = val as usize;
if v >= lo && v < hi { (v as isize + delta) as u64 } else { val }
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
unsafe {
use libc::{REG_RAX,REG_RBX,REG_RCX,REG_RDX,REG_RSI,REG_RDI,
REG_RBP,REG_RSP,REG_R8,REG_R9,REG_R10,REG_R11,
REG_R12,REG_R13,REG_R14,REG_R15};
let mc = &mut (*(ctx as *mut libc::ucontext_t)).uc_mcontext;
for reg in [REG_RSP, REG_RBP] {
let v = mc.gregs[reg as usize] as u64;
mc.gregs[reg as usize] = adj(v, old_guard_lo, old_hi, delta) as libc::greg_t;
}
for reg in [REG_RBX, REG_R12, REG_R13, REG_R14, REG_R15,
REG_RAX, REG_RCX, REG_RDX, REG_RSI, REG_RDI,
REG_R8, REG_R9, REG_R10, REG_R11] {
let v = mc.gregs[reg as usize] as u64;
mc.gregs[reg as usize] = adj(v, old_guard_lo, old_lo, delta) as libc::greg_t;
}
}
#[cfg(all(target_os = "linux", target_arch = "aarch64"))]
unsafe {
let mc = &mut (*(ctx as *mut libc::ucontext_t)).uc_mcontext;
mc.sp = adj(mc.sp, old_guard_lo, old_hi, delta);
mc.regs[29] = adj(mc.regs[29], old_guard_lo, old_hi, delta);
for i in 0..=28usize {
if i == 29 { continue; }
mc.regs[i] = adj(mc.regs[i], old_guard_lo, old_lo, delta);
}
let correction = sp_predecrement_at_pc(mc.pc as usize) as u64;
if correction != 0 { mc.sp += correction; }
}
#[cfg(all(target_os = "macos", target_arch = "x86_64"))]
unsafe {
let ss = &mut (*(* (ctx as *mut libc::ucontext_t)).uc_mcontext).__ss;
ss.__rsp = adj(ss.__rsp, old_guard_lo, old_hi, delta);
ss.__rbp = adj(ss.__rbp, old_guard_lo, old_hi, delta);
ss.__rbx = adj(ss.__rbx, old_guard_lo, old_lo, delta);
ss.__r12 = adj(ss.__r12, old_guard_lo, old_lo, delta);
ss.__r13 = adj(ss.__r13, old_guard_lo, old_lo, delta);
ss.__r14 = adj(ss.__r14, old_guard_lo, old_lo, delta);
ss.__r15 = adj(ss.__r15, old_guard_lo, old_lo, delta);
ss.__rax = adj(ss.__rax, old_guard_lo, old_lo, delta);
ss.__rcx = adj(ss.__rcx, old_guard_lo, old_lo, delta);
ss.__rdx = adj(ss.__rdx, old_guard_lo, old_lo, delta);
ss.__rsi = adj(ss.__rsi, old_guard_lo, old_lo, delta);
ss.__rdi = adj(ss.__rdi, old_guard_lo, old_lo, delta);
ss.__r8 = adj(ss.__r8, old_guard_lo, old_lo, delta);
ss.__r9 = adj(ss.__r9, old_guard_lo, old_lo, delta);
ss.__r10 = adj(ss.__r10, old_guard_lo, old_lo, delta);
ss.__r11 = adj(ss.__r11, old_guard_lo, old_lo, delta);
}
#[cfg(all(target_os = "macos", target_arch = "aarch64"))]
unsafe {
let ss = &mut (*(*(ctx as *mut libc::ucontext_t)).uc_mcontext).__ss;
ss.__sp = adj(ss.__sp, old_guard_lo, old_hi, delta);
ss.__fp = adj(ss.__fp, old_guard_lo, old_hi, delta);
for i in 0..=28usize {
ss.__x[i] = adj(ss.__x[i], old_guard_lo, old_lo, delta);
}
let correction = sp_predecrement_at_pc(ss.__pc as usize) as u64;
if correction != 0 { ss.__sp += correction; }
}
}
pub(crate) unsafe fn grow_stack_if_needed(gp: *mut G) {
let sp = unsafe { (*gp).sched.sp };
let lo = unsafe { (*gp).stack.lo };
if sp == 0 || sp < lo + STACK_GUARD {
if sp != 0 {
let old_stack = Stack {
lo: unsafe { (*gp).stack.lo },
hi: unsafe { (*gp).stack.hi },
};
let old_size = old_stack.hi - old_stack.lo;
if old_size < STACK_MAX {
let new_size = (old_size * 2).min(STACK_MAX);
let new_stack = unsafe {
stack_pool_alloc(new_size)
.expect("grow_stack_if_needed: allocation failed")
};
let delta = unsafe { copystack(gp, &old_stack, &new_stack) };
unsafe {
(*gp).stack = Stack { lo: new_stack.lo, hi: new_stack.hi };
(*gp).stackguard0 = new_stack.lo + STACK_GUARD;
}
unsafe { stack_pool_free(&old_stack) };
let _ = delta;
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn alloc_write_free() {
unsafe {
let stack = stack_alloc().expect("stack_alloc failed");
let ps = page_size();
assert_eq!(stack.hi - stack.lo, GOROUTINE_STACK_BYTES);
assert_eq!(stack.lo % ps, 0);
assert!(stack.hi > stack.lo);
let top = (stack.hi - 8) as *mut u64;
top.write(0xDEAD_BEEF_CAFE_BABE);
assert_eq!(top.read(), 0xDEAD_BEEF_CAFE_BABE);
stack_free(&stack);
}
}
#[test]
fn page_size_sanity() {
let ps = page_size();
assert!(ps.is_power_of_two());
assert!(ps >= 4096);
println!("page_size = {ps}");
}
#[test]
fn page_size_concurrent() {
let handles: Vec<_> = (0..8)
.map(|_| std::thread::spawn(page_size))
.collect();
let sizes: Vec<_> = handles.into_iter().map(|h| h.join().unwrap()).collect();
assert!(sizes.windows(2).all(|w| w[0] == w[1]));
}
#[test]
fn variable_size_alloc() {
unsafe {
for &size in &[8 * 1024usize, 16 * 1024, 32 * 1024, 64 * 1024] {
let stack = stack_alloc_size(size).unwrap();
assert_eq!(stack.hi - stack.lo, size);
stack_free(&stack);
}
}
}
#[test]
fn pool_class_bounds() {
assert_eq!(pool_class(1 << POOL_MIN_CLASS), Some(0));
assert_eq!(
pool_class(1 << POOL_MAX_CLASS),
Some((POOL_MAX_CLASS - POOL_MIN_CLASS) as usize)
);
assert_eq!(pool_class(1 << (POOL_MIN_CLASS - 1)), None);
assert_eq!(pool_class(1 << (POOL_MAX_CLASS + 1)), None);
assert_eq!(pool_class(48 * 1024), None);
}
#[test]
fn pool_alloc_free_round_trip() {
for &cls in &[POOL_MIN_CLASS, POOL_MAX_CLASS] {
let size = 1usize << cls;
unsafe {
let s1 = stack_pool_alloc(size).unwrap();
assert_eq!(s1.hi - s1.lo, size);
let top = (s1.hi - 8) as *mut u64;
top.write(0xDEAD_BEEF_F00D_BABE);
assert_eq!(top.read(), 0xDEAD_BEEF_F00D_BABE);
stack_pool_free(&s1);
let s2 = stack_pool_alloc(size).unwrap();
assert_eq!(s2.hi - s2.lo, size);
let top2 = (s2.hi - 8) as *mut u64;
top2.write(0x0102_0304_0506_0708);
assert_eq!(top2.read(), 0x0102_0304_0506_0708);
stack_pool_free(&s2);
}
}
}
#[test]
fn pool_ablation_round_trips() {
let size = 8 * 1024usize; unsafe {
let s = stack_pool_alloc(size).unwrap();
assert_eq!(s.hi - s.lo, size);
stack_pool_free(&s);
}
}
}