]> git.lizzy.rs Git - rust.git/blob - src/libgreen/stack.rs
Add a few more derivings to AST types
[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::atomics;
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 #[cfg(not(windows), not(target_os = "freebsd"))]
29 static STACK_FLAGS: libc::c_int = libc::MAP_STACK | libc::MAP_PRIVATE |
30                                   libc::MAP_ANON;
31 #[cfg(target_os = "freebsd")]
32 static STACK_FLAGS: libc::c_int = libc::MAP_PRIVATE | libc::MAP_ANON;
33 #[cfg(windows)]
34 static STACK_FLAGS: libc::c_int = 0;
35
36 impl Stack {
37     /// Allocate a new stack of `size`. If size = 0, this will fail. Use
38     /// `dummy_stack` if you want a zero-sized stack.
39     pub fn new(size: uint) -> Stack {
40         // Map in a stack. Eventually we might be able to handle stack
41         // allocation failure, which would fail to spawn the task. But there's
42         // not many sensible things to do on OOM.  Failure seems fine (and is
43         // what the old stack allocation did).
44         let stack = match MemoryMap::new(size, [MapReadable, MapWritable,
45                                          MapNonStandardFlags(STACK_FLAGS)]) {
46             Ok(map) => map,
47             Err(e) => fail!("mmap for stack of size {} failed: {}", size, e)
48         };
49
50         // Change the last page to be inaccessible. This is to provide safety;
51         // when an FFI function overflows it will (hopefully) hit this guard
52         // page. It isn't guaranteed, but that's why FFI is unsafe. buf.data is
53         // guaranteed to be aligned properly.
54         if !protect_last_page(&stack) {
55             fail!("Could not memory-protect guard page. stack={}, errno={}",
56                   stack.data(), errno());
57         }
58
59         let mut stk = Stack {
60             buf: Some(stack),
61             min_size: size,
62             valgrind_id: 0
63         };
64
65         // FIXME: Using the FFI to call a C macro. Slow
66         stk.valgrind_id = unsafe {
67             rust_valgrind_stack_register(stk.start(), stk.end())
68         };
69         return stk;
70     }
71
72     /// Create a 0-length stack which starts (and ends) at 0.
73     pub unsafe fn dummy_stack() -> Stack {
74         Stack {
75             buf: None,
76             min_size: 0,
77             valgrind_id: 0
78         }
79     }
80
81     /// Point to the low end of the allocated stack
82     pub fn start(&self) -> *const uint {
83         self.buf.as_ref().map(|m| m.data() as *const uint)
84             .unwrap_or(ptr::null())
85     }
86
87     /// Point one uint beyond the high end of the allocated stack
88     pub fn end(&self) -> *const uint {
89         self.buf.as_ref().map(|buf| unsafe {
90             buf.data().offset(buf.len() as int) as *const uint
91         }).unwrap_or(ptr::null())
92     }
93 }
94
95 #[cfg(unix)]
96 fn protect_last_page(stack: &MemoryMap) -> bool {
97     unsafe {
98         // This may seem backwards: the start of the segment is the last page?
99         // Yes! The stack grows from higher addresses (the end of the allocated
100         // block) to lower addresses (the start of the allocated block).
101         let last_page = stack.data() as *mut libc::c_void;
102         libc::mprotect(last_page, page_size() as libc::size_t,
103                        libc::PROT_NONE) != -1
104     }
105 }
106
107 #[cfg(windows)]
108 fn protect_last_page(stack: &MemoryMap) -> bool {
109     unsafe {
110         // see above
111         let last_page = stack.data() as *mut libc::c_void;
112         let mut old_prot: libc::DWORD = 0;
113         libc::VirtualProtect(last_page, page_size() as libc::SIZE_T,
114                              libc::PAGE_NOACCESS,
115                              &mut old_prot as libc::LPDWORD) != 0
116     }
117 }
118
119 impl Drop for Stack {
120     fn drop(&mut self) {
121         unsafe {
122             // FIXME: Using the FFI to call a C macro. Slow
123             rust_valgrind_stack_deregister(self.valgrind_id);
124         }
125     }
126 }
127
128 pub struct StackPool {
129     // Ideally this would be some data structure that preserved ordering on
130     // Stack.min_size.
131     stacks: Vec<Stack>,
132 }
133
134 impl StackPool {
135     pub fn new() -> StackPool {
136         StackPool {
137             stacks: vec![],
138         }
139     }
140
141     pub fn take_stack(&mut self, min_size: uint) -> Stack {
142         // Ideally this would be a binary search
143         match self.stacks.iter().position(|s| min_size <= s.min_size) {
144             Some(idx) => self.stacks.swap_remove(idx).unwrap(),
145             None => Stack::new(min_size)
146         }
147     }
148
149     pub fn give_stack(&mut self, stack: Stack) {
150         if self.stacks.len() <= max_cached_stacks() {
151             self.stacks.push(stack)
152         }
153     }
154 }
155
156 fn max_cached_stacks() -> uint {
157     static mut AMT: atomics::AtomicUint = atomics::INIT_ATOMIC_UINT;
158     match unsafe { AMT.load(atomics::SeqCst) } {
159         0 => {}
160         n => return n - 1,
161     }
162     let amt = getenv("RUST_MAX_CACHED_STACKS").and_then(|s| from_str(s.as_slice()));
163     // This default corresponds to 20M of cache per scheduler (at the
164     // default size).
165     let amt = amt.unwrap_or(10);
166     // 0 is our sentinel value, so ensure that we'll never see 0 after
167     // initialization has run
168     unsafe { AMT.store(amt + 1, atomics::SeqCst); }
169     return amt;
170 }
171
172 extern {
173     fn rust_valgrind_stack_register(start: *const libc::uintptr_t,
174                                     end: *const libc::uintptr_t) -> libc::c_uint;
175     fn rust_valgrind_stack_deregister(id: libc::c_uint);
176 }
177
178 #[cfg(test)]
179 mod tests {
180     use super::StackPool;
181
182     #[test]
183     fn stack_pool_caches() {
184         let mut p = StackPool::new();
185         let s = p.take_stack(10);
186         p.give_stack(s);
187         let s = p.take_stack(4);
188         assert_eq!(s.min_size, 10);
189         p.give_stack(s);
190         let s = p.take_stack(14);
191         assert_eq!(s.min_size, 14);
192         p.give_stack(s);
193     }
194
195     #[test]
196     fn stack_pool_caches_exact() {
197         let mut p = StackPool::new();
198         let mut s = p.take_stack(10);
199         s.valgrind_id = 100;
200         p.give_stack(s);
201
202         let s = p.take_stack(10);
203         assert_eq!(s.min_size, 10);
204         assert_eq!(s.valgrind_id, 100);
205     }
206 }