#![allow(improper_ctypes)]
#[macro_use]
extern crate cfg_if;
extern crate libc;
#[cfg(windows)]
extern crate winapi;
use std::cell::Cell;
#[inline(always)]
pub fn maybe_grow<R, F: FnOnce() -> R>(
red_zone: usize,
stack_size: usize,
f: F,
) -> R {
if cfg!(unsupported) {
return f();
}
if remaining_stack().map_or(false, |remaining| remaining >= red_zone) {
f()
} else {
grow(stack_size, f)
}
}
extern {
fn __stacker_stack_pointer() -> usize;
}
#[inline(always)]
pub fn remaining_stack() -> Option<usize> {
get_stack_limit().map(|limit| unsafe { __stacker_stack_pointer() - limit })
}
#[inline(never)]
pub fn grow<R, F: FnOnce() -> R>(stack_size: usize, f: F) -> R {
let mut f = Some(f);
let mut ret = None;
_grow(stack_size, &mut || {
ret = Some(f.take().unwrap()());
});
ret.unwrap()
}
thread_local! {
static STACK_LIMIT: Cell<Option<usize>> = Cell::new(unsafe {
guess_os_stack_limit()
})
}
#[inline(always)]
fn get_stack_limit() -> Option<usize> {
STACK_LIMIT.with(|s| s.get())
}
#[cfg(not(unsupported))]
fn set_stack_limit(l: Option<usize>) {
STACK_LIMIT.with(|s| s.set(l))
}
cfg_if! {
if #[cfg(unsupported)] {
fn _grow(stack_size: usize, f: &mut FnMut()) {
drop(stack_size);
f();
}
} else if #[cfg(not(windows))] {
extern {
fn __stacker_switch_stacks(dataptr: *mut u8,
fnptr: *const u8,
new_stack: usize);
fn getpagesize() -> libc::c_int;
}
struct StackSwitch {
map: *mut libc::c_void,
stack_size: usize,
old_stack_limit: Option<usize>,
}
impl Drop for StackSwitch {
fn drop(&mut self) {
unsafe {
libc::munmap(self.map, self.stack_size);
}
set_stack_limit(self.old_stack_limit);
}
}
fn _grow(stack_size: usize, mut f: &mut FnMut()) {
let page_size = unsafe { getpagesize() } as usize;
let rem = stack_size % page_size;
let stack_size = if rem == 0 {
stack_size
} else {
stack_size.checked_add(page_size - rem)
.expect("stack size calculation overflowed")
};
let stack_size = std::cmp::max(stack_size, page_size);
let stack_size = stack_size.checked_add(page_size)
.expect("stack size calculation overflowed");
let map = unsafe {
libc::mmap(std::ptr::null_mut(),
stack_size,
libc::PROT_NONE,
libc::MAP_PRIVATE |
libc::MAP_ANON,
0,
0)
};
if map == -1isize as _ {
panic!("unable to allocate stack")
}
let _switch = StackSwitch {
map,
stack_size,
old_stack_limit: get_stack_limit(),
};
let result = unsafe {
libc::mprotect((map as usize + page_size) as *mut libc::c_void,
stack_size - page_size,
libc::PROT_READ | libc::PROT_WRITE)
};
if result == -1 {
panic!("unable to set stack permissions")
}
let stack_low = map as usize;
set_stack_limit(Some(stack_low));
let offset = if cfg!(target_pointer_width = "32") {
12
} else {
0
};
extern fn doit(f: &mut &mut FnMut()) {
f();
}
unsafe {
__stacker_switch_stacks(&mut f as *mut &mut FnMut() as *mut u8,
doit as usize as *const _,
stack_low + stack_size - offset);
}
}
}
}
cfg_if! {
if #[cfg(windows)] {
use std::ptr;
use std::io;
use winapi::shared::basetsd::*;
use winapi::shared::minwindef::{LPVOID, BOOL};
use winapi::shared::ntdef::*;
use winapi::um::fibersapi::*;
use winapi::um::memoryapi::*;
use winapi::um::processthreadsapi::*;
use winapi::um::winbase::*;
extern {
fn __stacker_get_current_fiber() -> PVOID;
}
struct FiberInfo<'a> {
callback: &'a mut FnMut(),
result: Option<std::thread::Result<()>>,
parent_fiber: LPVOID,
}
unsafe extern "system" fn fiber_proc(info: LPVOID) {
let info = &mut *(info as *mut FiberInfo);
let old_stack_limit = get_stack_limit();
set_stack_limit(guess_os_stack_limit());
info.result = Some(std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
(info.callback)();
})));
set_stack_limit(old_stack_limit);
SwitchToFiber(info.parent_fiber);
return;
}
fn _grow(stack_size: usize, callback: &mut FnMut()) {
unsafe {
let was_fiber = IsThreadAFiber() == TRUE as BOOL;
let mut info = FiberInfo {
callback,
result: None,
parent_fiber: {
if was_fiber {
__stacker_get_current_fiber()
} else {
ConvertThreadToFiber(ptr::null_mut())
}
},
};
if info.parent_fiber.is_null() {
panic!("unable to convert thread to fiber: {}", io::Error::last_os_error());
}
let fiber = CreateFiber(
stack_size as SIZE_T,
Some(fiber_proc),
&mut info as *mut FiberInfo as *mut _,
);
if fiber.is_null() {
panic!("unable to allocate fiber: {}", io::Error::last_os_error());
}
SwitchToFiber(fiber);
DeleteFiber(fiber);
if !was_fiber {
if ConvertFiberToThread() == 0 {
panic!("unable to convert back to thread: {}", io::Error::last_os_error());
}
}
if let Err(payload) = info.result.unwrap() {
std::panic::resume_unwind(payload);
}
}
}
#[inline(always)]
fn get_thread_stack_guarantee() -> usize {
let min_guarantee = if cfg!(target_pointer_width = "32") {
0x1000
} else {
0x2000
};
let mut stack_guarantee = 0;
unsafe {
SetThreadStackGuarantee(&mut stack_guarantee)
};
std::cmp::max(stack_guarantee, min_guarantee) as usize + 0x1000
}
#[inline(always)]
unsafe fn guess_os_stack_limit() -> Option<usize> {
let mut mi = std::mem::zeroed();
VirtualQuery(
__stacker_stack_pointer() as *const _,
&mut mi,
std::mem::size_of_val(&mi) as SIZE_T,
);
Some(mi.AllocationBase as usize + get_thread_stack_guarantee() + 0x1000)
}
} else if #[cfg(target_os = "linux")] {
use std::mem;
unsafe fn guess_os_stack_limit() -> Option<usize> {
let mut attr: libc::pthread_attr_t = mem::zeroed();
assert_eq!(libc::pthread_attr_init(&mut attr), 0);
assert_eq!(libc::pthread_getattr_np(libc::pthread_self(),
&mut attr), 0);
let mut stackaddr = 0 as *mut _;
let mut stacksize = 0;
assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr,
&mut stacksize), 0);
assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
Some(stackaddr as usize)
}
} else if #[cfg(target_os = "macos")] {
unsafe fn guess_os_stack_limit() -> Option<usize> {
Some(libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize -
libc::pthread_get_stacksize_np(libc::pthread_self()) as usize)
}
} else {
unsafe fn guess_os_stack_limit() -> Option<usize> {
None
}
}
}