]> git.lizzy.rs Git - rust.git/blob - src/libgreen/stack.rs
return &mut T from the arenas, not &T
[rust.git] / src / libgreen / stack.rs
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.
4 //
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.
10
11 use std::ptr;
12 use std::sync::atomic;
13 use std::os::{errno, page_size, MemoryMap, MapReadable, MapWritable,
14               MapNonStandardFlags, getenv};
15 use libc;
16
17 /// A task's stack. The name "Stack" is a vestige of segmented stacks.
18 pub struct Stack {
19     buf: Option<MemoryMap>,
20     min_size: uint,
21     valgrind_id: libc::c_uint,
22 }
23
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 // fails): http://lists.freebsd.org/pipermail/freebsd-bugs/2011-July/044840.html
28 //
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 |
33                                   libc::MAP_ANON;
34 #[cfg(any(target_os = "freebsd", target_os = "dragonfly"))]
35 static STACK_FLAGS: libc::c_int = libc::MAP_PRIVATE | libc::MAP_ANON;
36 #[cfg(windows)]
37 static STACK_FLAGS: libc::c_int = 0;
38
39 impl Stack {
40     /// Allocate a new stack of `size`. If size = 0, this will fail. 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)]) {
49             Ok(map) => map,
50             Err(e) => fail!("mmap for stack of size {} failed: {}", size, e)
51         };
52
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             fail!("Could not memory-protect guard page. stack={}, errno={}",
59                   stack.data(), errno());
60         }
61
62         let mut stk = Stack {
63             buf: Some(stack),
64             min_size: size,
65             valgrind_id: 0
66         };
67
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)
72         };
73         return stk;
74     }
75
76     /// Create a 0-length stack which starts (and ends) at 0.
77     pub unsafe fn dummy_stack() -> Stack {
78         Stack {
79             buf: None,
80             min_size: 0,
81             valgrind_id: 0
82         }
83     }
84
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
88     }
89
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())
94     }
95
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())
101     }
102 }
103
104 #[cfg(unix)]
105 fn protect_last_page(stack: &MemoryMap) -> bool {
106     unsafe {
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
113     }
114 }
115
116 #[cfg(windows)]
117 fn protect_last_page(stack: &MemoryMap) -> bool {
118     unsafe {
119         // see above
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,
123                              libc::PAGE_NOACCESS,
124                              &mut old_prot as libc::LPDWORD) != 0
125     }
126 }
127
128 impl Drop for Stack {
129     fn drop(&mut self) {
130         unsafe {
131             // FIXME: Using the FFI to call a C macro. Slow
132             rust_valgrind_stack_deregister(self.valgrind_id);
133         }
134     }
135 }
136
137 pub struct StackPool {
138     // Ideally this would be some data structure that preserved ordering on
139     // Stack.min_size.
140     stacks: Vec<Stack>,
141 }
142
143 impl StackPool {
144     pub fn new() -> StackPool {
145         StackPool {
146             stacks: vec![],
147         }
148     }
149
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)
155         }
156     }
157
158     pub fn give_stack(&mut self, stack: Stack) {
159         if self.stacks.len() <= max_cached_stacks() {
160             self.stacks.push(stack)
161         }
162     }
163 }
164
165 fn max_cached_stacks() -> uint {
166     static AMT: atomic::AtomicUint = atomic::INIT_ATOMIC_UINT;
167     match AMT.load(atomic::SeqCst) {
168         0 => {}
169         n => return n - 1,
170     }
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
173     // default size).
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);
178     return amt;
179 }
180
181 extern {
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);
185 }
186
187 #[cfg(test)]
188 mod tests {
189     use super::StackPool;
190
191     #[test]
192     fn stack_pool_caches() {
193         let mut p = StackPool::new();
194         let s = p.take_stack(10);
195         p.give_stack(s);
196         let s = p.take_stack(4);
197         assert_eq!(s.min_size, 10);
198         p.give_stack(s);
199         let s = p.take_stack(14);
200         assert_eq!(s.min_size, 14);
201         p.give_stack(s);
202     }
203
204     #[test]
205     fn stack_pool_caches_exact() {
206         let mut p = StackPool::new();
207         let mut s = p.take_stack(10);
208         s.valgrind_id = 100;
209         p.give_stack(s);
210
211         let s = p.take_stack(10);
212         assert_eq!(s.min_size, 10);
213         assert_eq!(s.valgrind_id, 100);
214     }
215 }