Skip to content

Commit 1815aea

Browse files
committed
std: Introduce an unstable::stack module
This module will be used to manage the OS-specific TLS registers used to specify the bounds of the current rust stack (useful in 1:1 and M:N)
1 parent cab44fb commit 1815aea

File tree

2 files changed

+276
-2
lines changed

2 files changed

+276
-2
lines changed

src/libstd/rt/thread.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,9 @@ static DEFAULT_STACK_SIZE: libc::size_t = 1024 * 1024;
4141
// and invoke it.
4242
#[no_split_stack]
4343
extern fn thread_start(main: *libc::c_void) -> imp::rust_thread_return {
44-
use rt::context;
44+
use unstable::stack;
4545
unsafe {
46-
context::record_stack_bounds(0, uint::max_value);
46+
stack::record_stack_bounds(0, uint::max_value);
4747
let f: ~proc() = cast::transmute(main);
4848
(*f)();
4949
cast::transmute(0 as imp::rust_thread_return)

src/libstd/unstable/stack.rs

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
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+
//! Rust stack-limit management
12+
//!
13+
//! Currently Rust uses a segmented-stack-like scheme in order to detect stack
14+
//! overflow for rust tasks. In this scheme, the prologue of all functions are
15+
//! preceded with a check to see whether the current stack limits are being
16+
//! exceeded.
17+
//!
18+
//! This module provides the functionality necessary in order to manage these
19+
//! stack limits (which are stored in platform-specific locations). The
20+
//! functions here are used at the borders of the task lifetime in order to
21+
//! manage these limits.
22+
//!
23+
//! This function is an unstable module because this scheme for stack overflow
24+
//! detection is not guaranteed to continue in the future. Usage of this module
25+
//! is discouraged unless absolutely necessary.
26+
27+
use rt::task::Task;
28+
use option::None;
29+
use rt::local::Local;
30+
use unstable::intrinsics;
31+
32+
static RED_ZONE: uint = 20 * 1024;
33+
34+
/// This function is invoked from rust's current __morestack function. Segmented
35+
/// stacks are currently not enabled as segmented stacks, but rather one giant
36+
/// stack segment. This means that whenever we run out of stack, we want to
37+
/// truly consider it to be stack overflow rather than allocating a new stack.
38+
#[no_mangle] // - this is called from C code
39+
#[no_split_stack] // - it would be sad for this function to trigger __morestack
40+
#[doc(hidden)] // - Function must be `pub` to get exported, but it's
41+
// irrelevant for documentation purposes.
42+
#[cfg(not(test))] // in testing, use the original libstd's version
43+
pub extern "C" fn rust_stack_exhausted() {
44+
45+
unsafe {
46+
// We're calling this function because the stack just ran out. We need
47+
// to call some other rust functions, but if we invoke the functions
48+
// right now it'll just trigger this handler being called again. In
49+
// order to alleviate this, we move the stack limit to be inside of the
50+
// red zone that was allocated for exactly this reason.
51+
let limit = get_sp_limit();
52+
record_sp_limit(limit - RED_ZONE / 2);
53+
54+
// This probably isn't the best course of action. Ideally one would want
55+
// to unwind the stack here instead of just aborting the entire process.
56+
// This is a tricky problem, however. There's a few things which need to
57+
// be considered:
58+
//
59+
// 1. We're here because of a stack overflow, yet unwinding will run
60+
// destructors and hence arbitrary code. What if that code overflows
61+
// the stack? One possibility is to use the above allocation of an
62+
// extra 10k to hope that we don't hit the limit, and if we do then
63+
// abort the whole program. Not the best, but kind of hard to deal
64+
// with unless we want to switch stacks.
65+
//
66+
// 2. LLVM will optimize functions based on whether they can unwind or
67+
// not. It will flag functions with 'nounwind' if it believes that
68+
// the function cannot trigger unwinding, but if we do unwind on
69+
// stack overflow then it means that we could unwind in any function
70+
// anywhere. We would have to make sure that LLVM only places the
71+
// nounwind flag on functions which don't call any other functions.
72+
//
73+
// 3. The function that overflowed may have owned arguments. These
74+
// arguments need to have their destructors run, but we haven't even
75+
// begun executing the function yet, so unwinding will not run the
76+
// any landing pads for these functions. If this is ignored, then
77+
// the arguments will just be leaked.
78+
//
79+
// Exactly what to do here is a very delicate topic, and is possibly
80+
// still up in the air for what exactly to do. Some relevant issues:
81+
//
82+
// #3555 - out-of-stack failure leaks arguments
83+
// #3695 - should there be a stack limit?
84+
// #9855 - possible strategies which could be taken
85+
// #9854 - unwinding on windows through __morestack has never worked
86+
// #2361 - possible implementation of not using landing pads
87+
88+
let mut task = Local::borrow(None::<Task>);
89+
let n = task.get().name.as_ref()
90+
.map(|n| n.as_slice()).unwrap_or("<unnamed>");
91+
92+
// See the message below for why this is not emitted to the
93+
// task's logger. This has the additional conundrum of the
94+
// logger may not be initialized just yet, meaning that an FFI
95+
// call would happen to initialized it (calling out to libuv),
96+
// and the FFI call needs 2MB of stack when we just ran out.
97+
println!("task '{}' has overflowed its stack", n);
98+
99+
intrinsics::abort();
100+
}
101+
}
102+
103+
#[inline(always)]
104+
pub unsafe fn record_stack_bounds(stack_lo: uint, stack_hi: uint) {
105+
// When the old runtime had segmented stacks, it used a calculation that was
106+
// "limit + RED_ZONE + FUDGE". The red zone was for things like dynamic
107+
// symbol resolution, llvm function calls, etc. In theory this red zone
108+
// value is 0, but it matters far less when we have gigantic stacks because
109+
// we don't need to be so exact about our stack budget. The "fudge factor"
110+
// was because LLVM doesn't emit a stack check for functions < 256 bytes in
111+
// size. Again though, we have giant stacks, so we round all these
112+
// calculations up to the nice round number of 20k.
113+
record_sp_limit(stack_lo + RED_ZONE);
114+
115+
return target_record_stack_bounds(stack_lo, stack_hi);
116+
117+
#[cfg(not(windows))] #[cfg(not(target_arch = "x86_64"))] #[inline(always)]
118+
unsafe fn target_record_stack_bounds(_stack_lo: uint, _stack_hi: uint) {}
119+
#[cfg(windows, target_arch = "x86_64")] #[inline(always)]
120+
unsafe fn target_record_stack_bounds(stack_lo: uint, stack_hi: uint) {
121+
// Windows compiles C functions which may check the stack bounds. This
122+
// means that if we want to perform valid FFI on windows, then we need
123+
// to ensure that the stack bounds are what they truly are for this
124+
// task. More info can be found at:
125+
// https://github.com/mozilla/rust/issues/3445#issuecomment-26114839
126+
//
127+
// stack range is at TIB: %gs:0x08 (top) and %gs:0x10 (bottom)
128+
asm!("mov $0, %gs:0x08" :: "r"(stack_hi) :: "volatile");
129+
asm!("mov $0, %gs:0x10" :: "r"(stack_lo) :: "volatile");
130+
}
131+
}
132+
133+
/// Records the current limit of the stack as specified by `end`.
134+
///
135+
/// This is stored in an OS-dependent location, likely inside of the thread
136+
/// local storage. The location that the limit is stored is a pre-ordained
137+
/// location because it's where LLVM has emitted code to check.
138+
///
139+
/// Note that this cannot be called under normal circumstances. This function is
140+
/// changing the stack limit, so upon returning any further function calls will
141+
/// possibly be triggering the morestack logic if you're not careful.
142+
///
143+
/// Also note that this and all of the inside functions are all flagged as
144+
/// "inline(always)" because they're messing around with the stack limits. This
145+
/// would be unfortunate for the functions themselves to trigger a morestack
146+
/// invocation (if they were an actual function call).
147+
#[inline(always)]
148+
pub unsafe fn record_sp_limit(limit: uint) {
149+
return target_record_sp_limit(limit);
150+
151+
// x86-64
152+
#[cfg(target_arch = "x86_64", target_os = "macos")] #[inline(always)]
153+
unsafe fn target_record_sp_limit(limit: uint) {
154+
asm!("movq $$0x60+90*8, %rsi
155+
movq $0, %gs:(%rsi)" :: "r"(limit) : "rsi" : "volatile")
156+
}
157+
#[cfg(target_arch = "x86_64", target_os = "linux")] #[inline(always)]
158+
unsafe fn target_record_sp_limit(limit: uint) {
159+
asm!("movq $0, %fs:112" :: "r"(limit) :: "volatile")
160+
}
161+
#[cfg(target_arch = "x86_64", target_os = "win32")] #[inline(always)]
162+
unsafe fn target_record_sp_limit(limit: uint) {
163+
// see: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
164+
// store this inside of the "arbitrary data slot", but double the size
165+
// because this is 64 bit instead of 32 bit
166+
asm!("movq $0, %gs:0x28" :: "r"(limit) :: "volatile")
167+
}
168+
#[cfg(target_arch = "x86_64", target_os = "freebsd")] #[inline(always)]
169+
unsafe fn target_record_sp_limit(limit: uint) {
170+
asm!("movq $0, %fs:24" :: "r"(limit) :: "volatile")
171+
}
172+
173+
// x86
174+
#[cfg(target_arch = "x86", target_os = "macos")] #[inline(always)]
175+
unsafe fn target_record_sp_limit(limit: uint) {
176+
asm!("movl $$0x48+90*4, %eax
177+
movl $0, %gs:(%eax)" :: "r"(limit) : "eax" : "volatile")
178+
}
179+
#[cfg(target_arch = "x86", target_os = "linux")]
180+
#[cfg(target_arch = "x86", target_os = "freebsd")] #[inline(always)]
181+
unsafe fn target_record_sp_limit(limit: uint) {
182+
asm!("movl $0, %gs:48" :: "r"(limit) :: "volatile")
183+
}
184+
#[cfg(target_arch = "x86", target_os = "win32")] #[inline(always)]
185+
unsafe fn target_record_sp_limit(limit: uint) {
186+
// see: http://en.wikipedia.org/wiki/Win32_Thread_Information_Block
187+
// store this inside of the "arbitrary data slot"
188+
asm!("movl $0, %fs:0x14" :: "r"(limit) :: "volatile")
189+
}
190+
191+
// mips, arm - Some brave soul can port these to inline asm, but it's over
192+
// my head personally
193+
#[cfg(target_arch = "mips")]
194+
#[cfg(target_arch = "arm")] #[inline(always)]
195+
unsafe fn target_record_sp_limit(limit: uint) {
196+
return record_sp_limit(limit as *c_void);
197+
extern {
198+
fn record_sp_limit(limit: *c_void);
199+
}
200+
}
201+
}
202+
203+
/// The counterpart of the function above, this function will fetch the current
204+
/// stack limit stored in TLS.
205+
///
206+
/// Note that all of these functions are meant to be exact counterparts of their
207+
/// brethren above, except that the operands are reversed.
208+
///
209+
/// As with the setter, this function does not have a __morestack header and can
210+
/// therefore be called in a "we're out of stack" situation.
211+
#[inline(always)]
212+
pub unsafe fn get_sp_limit() -> uint {
213+
return target_get_sp_limit();
214+
215+
// x86-64
216+
#[cfg(target_arch = "x86_64", target_os = "macos")] #[inline(always)]
217+
unsafe fn target_get_sp_limit() -> uint {
218+
let limit;
219+
asm!("movq $$0x60+90*8, %rsi
220+
movq %gs:(%rsi), $0" : "=r"(limit) :: "rsi" : "volatile");
221+
return limit;
222+
}
223+
#[cfg(target_arch = "x86_64", target_os = "linux")] #[inline(always)]
224+
unsafe fn target_get_sp_limit() -> uint {
225+
let limit;
226+
asm!("movq %fs:112, $0" : "=r"(limit) ::: "volatile");
227+
return limit;
228+
}
229+
#[cfg(target_arch = "x86_64", target_os = "win32")] #[inline(always)]
230+
unsafe fn target_get_sp_limit() -> uint {
231+
let limit;
232+
asm!("movq %gs:0x28, $0" : "=r"(limit) ::: "volatile");
233+
return limit;
234+
}
235+
#[cfg(target_arch = "x86_64", target_os = "freebsd")] #[inline(always)]
236+
unsafe fn target_get_sp_limit() -> uint {
237+
let limit;
238+
asm!("movq %fs:24, $0" : "=r"(limit) ::: "volatile");
239+
return limit;
240+
}
241+
242+
// x86
243+
#[cfg(target_arch = "x86", target_os = "macos")] #[inline(always)]
244+
unsafe fn target_get_sp_limit() -> uint {
245+
let limit;
246+
asm!("movl $$0x48+90*4, %eax
247+
movl %gs:(%eax), $0" : "=r"(limit) :: "eax" : "volatile");
248+
return limit;
249+
}
250+
#[cfg(target_arch = "x86", target_os = "linux")]
251+
#[cfg(target_arch = "x86", target_os = "freebsd")] #[inline(always)]
252+
unsafe fn target_get_sp_limit() -> uint {
253+
let limit;
254+
asm!("movl %gs:48, $0" : "=r"(limit) ::: "volatile");
255+
return limit;
256+
}
257+
#[cfg(target_arch = "x86", target_os = "win32")] #[inline(always)]
258+
unsafe fn target_get_sp_limit() -> uint {
259+
let limit;
260+
asm!("movl %fs:0x14, $0" : "=r"(limit) ::: "volatile");
261+
return limit;
262+
}
263+
264+
// mips, arm - Some brave soul can port these to inline asm, but it's over
265+
// my head personally
266+
#[cfg(target_arch = "mips")]
267+
#[cfg(target_arch = "arm")] #[inline(always)]
268+
unsafe fn target_get_sp_limit() -> uint {
269+
return get_sp_limit() as uint;
270+
extern {
271+
fn get_sp_limit() -> *c_void;
272+
}
273+
}
274+
}

0 commit comments

Comments
 (0)