use std::ptr;
use std::sync::atomic;
use std::env::{self, page_size};
use std::fmt;
use libc;
use mmap::{MemoryMap, MapOption};
pub struct Stack {
buf: Option<MemoryMap>,
min_size: usize,
}
impl fmt::Debug for Stack {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
try!(write!(f, "Stack {} buf: ", "{"));
match self.buf {
Some(ref map) => try!(write!(f, "Some({:#x}), ", map.data() as libc::uintptr_t)),
None => try!(write!(f, "None, ")),
}
write!(f, "min_size: {:?} {}", self.min_size, "}")
}
}
#[cfg(all(not(windows), not(target_os = "freebsd"), not(target_os = "dragonfly")))]
static STACK_FLAGS: libc::c_int = libc::MAP_STACK | libc::MAP_PRIVATE | libc::MAP_ANON;
#[cfg(any(target_os = "freebsd",
target_os = "dragonfly"))]
static STACK_FLAGS: libc::c_isize = libc::MAP_PRIVATE | libc::MAP_ANON;
#[cfg(windows)]
static STACK_FLAGS: libc::c_isize = 0;
impl Stack {
pub fn new(size: usize) -> Stack {
let stack = match MemoryMap::new(size, &[MapOption::MapReadable,
MapOption::MapWritable,
MapOption::MapNonStandardFlags(STACK_FLAGS)]) {
Ok(map) => map,
Err(e) => panic!("mmap for stack of size {} failed: {}", size, e)
};
if !protect_last_page(&stack) {
panic!("Could not memory-protect guard page. stack={:?}",
stack.data());
}
Stack {
buf: Some(stack),
min_size: size,
}
}
pub unsafe fn dummy_stack() -> Stack {
Stack {
buf: None,
min_size: 0,
}
}
pub fn guard(&self) -> *const usize {
(self.start() as usize + page_size()) as *const usize
}
pub fn start(&self) -> *const usize {
self.buf.as_ref()
.map(|m| m.data() as *const usize)
.unwrap_or(ptr::null())
}
pub fn end(&self) -> *const usize {
self.buf
.as_ref()
.map(|buf| unsafe {
buf.data().offset(buf.len() as isize) as *const usize
})
.unwrap_or(ptr::null())
}
}
#[cfg(unix)]
fn protect_last_page(stack: &MemoryMap) -> bool {
unsafe {
let last_page = stack.data() as *mut libc::c_void;
libc::mprotect(last_page, page_size() as libc::size_t,
libc::PROT_NONE) != -1
}
}
#[cfg(windows)]
fn protect_last_page(stack: &MemoryMap) -> bool {
unsafe {
let last_page = stack.data() as *mut libc::c_void;
let mut old_prot: libc::DWORD = 0;
libc::VirtualProtect(last_page, page_size() as libc::SIZE_T,
libc::PAGE_NOACCESS,
&mut old_prot as libc::LPDWORD) != 0
}
}
#[derive(Debug)]
pub struct StackPool {
stacks: Vec<Stack>,
}
impl StackPool {
pub fn new() -> StackPool {
StackPool {
stacks: vec![],
}
}
pub fn take_stack(&mut self, min_size: usize) -> Stack {
match self.stacks.iter().position(|s| min_size <= s.min_size) {
Some(idx) => self.stacks.swap_remove(idx),
None => Stack::new(min_size)
}
}
pub fn give_stack(&mut self, stack: Stack) {
if self.stacks.len() <= max_cached_stacks() {
self.stacks.push(stack)
}
}
}
fn max_cached_stacks() -> usize {
static mut AMT: atomic::AtomicUsize = atomic::ATOMIC_USIZE_INIT;
match unsafe { AMT.load(atomic::Ordering::SeqCst) } {
0 => {}
n => return n - 1,
}
let amt = env::var("RUST_MAX_CACHED_STACKS").ok().and_then(|s| s.parse().ok());
let amt = amt.unwrap_or(10);
unsafe { AMT.store(amt + 1, atomic::Ordering::SeqCst); }
return amt;
}
#[cfg(test)]
mod tests {
use super::StackPool;
#[test]
fn stack_pool_caches() {
let mut p = StackPool::new();
let s = p.take_stack(10);
p.give_stack(s);
let s = p.take_stack(4);
assert_eq!(s.min_size, 10);
p.give_stack(s);
let s = p.take_stack(14);
assert_eq!(s.min_size, 14);
p.give_stack(s);
}
#[test]
fn stack_pool_caches_exact() {
let mut p = StackPool::new();
let s = p.take_stack(10);
p.give_stack(s);
let s = p.take_stack(10);
assert_eq!(s.min_size, 10);
}
}