1 // Copyright 2013 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
12 use std::sync::atomic;
13 use std::os::{errno, page_size, MemoryMap, MapReadable, MapWritable,
14 MapNonStandardFlags, getenv};
17 /// A task's stack. The name "Stack" is a vestige of segmented stacks.
19 buf: Option<MemoryMap>,
21 valgrind_id: libc::c_uint,
24 // Try to use MAP_STACK on platforms that support it (it's what we're doing
25 // anyway), but some platforms don't support it at all. For example, it appears
26 // that there's a bug in freebsd that MAP_STACK implies MAP_FIXED (so it always
27 // panics): http://lists.freebsd.org/pipermail/freebsd-bugs/2011-July/044840.html
29 // DragonFly BSD also seems to suffer from the same problem. When MAP_STACK is
30 // used, it returns the same `ptr` multiple times.
31 #[cfg(not(any(windows, target_os = "freebsd", target_os = "dragonfly")))]
32 static STACK_FLAGS: libc::c_int = libc::MAP_STACK | libc::MAP_PRIVATE |
34 #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
35 static STACK_FLAGS: libc::c_int = libc::MAP_PRIVATE | libc::MAP_ANON;
37 static STACK_FLAGS: libc::c_int = 0;
40 /// Allocate a new stack of `size`. If size = 0, this will panic. Use
41 /// `dummy_stack` if you want a zero-sized stack.
42 pub fn new(size: uint) -> Stack {
43 // Map in a stack. Eventually we might be able to handle stack
44 // allocation failure, which would fail to spawn the task. But there's
45 // not many sensible things to do on OOM. Failure seems fine (and is
46 // what the old stack allocation did).
47 let stack = match MemoryMap::new(size, [MapReadable, MapWritable,
48 MapNonStandardFlags(STACK_FLAGS)]) {
50 Err(e) => panic!("mmap for stack of size {} failed: {}", size, e)
53 // Change the last page to be inaccessible. This is to provide safety;
54 // when an FFI function overflows it will (hopefully) hit this guard
55 // page. It isn't guaranteed, but that's why FFI is unsafe. buf.data is
56 // guaranteed to be aligned properly.
57 if !protect_last_page(&stack) {
58 panic!("Could not memory-protect guard page. stack={}, errno={}",
59 stack.data(), errno());
68 // FIXME: Using the FFI to call a C macro. Slow
69 stk.valgrind_id = unsafe {
70 rust_valgrind_stack_register(stk.start() as *const libc::uintptr_t,
71 stk.end() as *const libc::uintptr_t)
76 /// Create a 0-length stack which starts (and ends) at 0.
77 pub unsafe fn dummy_stack() -> Stack {
85 /// Point to the last writable byte of the stack
86 pub fn guard(&self) -> *const uint {
87 (self.start() as uint + page_size()) as *const uint
90 /// Point to the low end of the allocated stack
91 pub fn start(&self) -> *const uint {
92 self.buf.as_ref().map(|m| m.data() as *const uint)
93 .unwrap_or(ptr::null())
96 /// Point one uint beyond the high end of the allocated stack
97 pub fn end(&self) -> *const uint {
98 self.buf.as_ref().map(|buf| unsafe {
99 buf.data().offset(buf.len() as int) as *const uint
100 }).unwrap_or(ptr::null())
105 fn protect_last_page(stack: &MemoryMap) -> bool {
107 // This may seem backwards: the start of the segment is the last page?
108 // Yes! The stack grows from higher addresses (the end of the allocated
109 // block) to lower addresses (the start of the allocated block).
110 let last_page = stack.data() as *mut libc::c_void;
111 libc::mprotect(last_page, page_size() as libc::size_t,
112 libc::PROT_NONE) != -1
117 fn protect_last_page(stack: &MemoryMap) -> bool {
120 let last_page = stack.data() as *mut libc::c_void;
121 let mut old_prot: libc::DWORD = 0;
122 libc::VirtualProtect(last_page, page_size() as libc::SIZE_T,
124 &mut old_prot as libc::LPDWORD) != 0
128 impl Drop for Stack {
131 // FIXME: Using the FFI to call a C macro. Slow
132 rust_valgrind_stack_deregister(self.valgrind_id);
137 pub struct StackPool {
138 // Ideally this would be some data structure that preserved ordering on
144 pub fn new() -> StackPool {
150 pub fn take_stack(&mut self, min_size: uint) -> Stack {
151 // Ideally this would be a binary search
152 match self.stacks.iter().position(|s| min_size <= s.min_size) {
153 Some(idx) => self.stacks.swap_remove(idx).unwrap(),
154 None => Stack::new(min_size)
158 pub fn give_stack(&mut self, stack: Stack) {
159 if self.stacks.len() <= max_cached_stacks() {
160 self.stacks.push(stack)
165 fn max_cached_stacks() -> uint {
166 static AMT: atomic::AtomicUint = atomic::INIT_ATOMIC_UINT;
167 match AMT.load(atomic::SeqCst) {
171 let amt = getenv("RUST_MAX_CACHED_STACKS").and_then(|s| from_str(s.as_slice()));
172 // This default corresponds to 20M of cache per scheduler (at the
174 let amt = amt.unwrap_or(10);
175 // 0 is our sentinel value, so ensure that we'll never see 0 after
176 // initialization has run
177 AMT.store(amt + 1, atomic::SeqCst);
182 fn rust_valgrind_stack_register(start: *const libc::uintptr_t,
183 end: *const libc::uintptr_t) -> libc::c_uint;
184 fn rust_valgrind_stack_deregister(id: libc::c_uint);
189 use super::StackPool;
192 fn stack_pool_caches() {
193 let mut p = StackPool::new();
194 let s = p.take_stack(10);
196 let s = p.take_stack(4);
197 assert_eq!(s.min_size, 10);
199 let s = p.take_stack(14);
200 assert_eq!(s.min_size, 14);
205 fn stack_pool_caches_exact() {
206 let mut p = StackPool::new();
207 let mut s = p.take_stack(10);
211 let s = p.take_stack(10);
212 assert_eq!(s.min_size, 10);
213 assert_eq!(s.valgrind_id, 100);