Skip to content

Commit

Permalink
auto merge of rust-lang#11762 : alexcrichton/rust/guard_pages, r=alex…
Browse files Browse the repository at this point in the history
…crichton

Rebasing of the previous PRs, I believe I've found the problems.
  • Loading branch information
bors committed Jan 26, 2014
2 parents e36032e + 8c43ce6 commit 838b5a4
Show file tree
Hide file tree
Showing 9 changed files with 284 additions and 107 deletions.
6 changes: 3 additions & 3 deletions src/libgreen/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,9 @@ use std::libc::c_void;
use std::uint;
use std::cast::{transmute, transmute_mut_unsafe,
transmute_region, transmute_mut_region};
use stack::Stack;
use std::unstable::stack;

use stack::StackSegment;

// FIXME #7761: Registers is boxed so that it is 16-byte aligned, for storing
// SSE regs. It would be marginally better not to do this. In C++ we
// use an attribute on a struct.
Expand All @@ -41,7 +40,7 @@ impl Context {
}

/// Create a new context that will resume execution by running proc()
pub fn new(start: proc(), stack: &mut StackSegment) -> Context {
pub fn new(start: proc(), stack: &mut Stack) -> Context {
// The C-ABI function that is the task entry point
//
// Note that this function is a little sketchy. We're taking a
Expand Down Expand Up @@ -79,6 +78,7 @@ impl Context {
// be passed to the spawn function. Another unfortunate
// allocation
let start = ~start;

initialize_call_frame(&mut *regs,
task_start_wrapper as *c_void,
unsafe { transmute(&*start) },
Expand Down
10 changes: 5 additions & 5 deletions src/libgreen/coroutine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
use std::rt::env;

use context::Context;
use stack::{StackPool, StackSegment};
use stack::{StackPool, Stack};

/// A coroutine is nothing more than a (register context, stack) pair.
pub struct Coroutine {
Expand All @@ -24,7 +24,7 @@ pub struct Coroutine {
///
/// Servo needs this to be public in order to tell SpiderMonkey
/// about the stack bounds.
current_stack_segment: StackSegment,
current_stack_segment: Stack,

/// Always valid if the task is alive and not running.
saved_context: Context
Expand All @@ -39,7 +39,7 @@ impl Coroutine {
Some(size) => size,
None => env::min_stack()
};
let mut stack = stack_pool.take_segment(stack_size);
let mut stack = stack_pool.take_stack(stack_size);
let initial_context = Context::new(start, &mut stack);
Coroutine {
current_stack_segment: stack,
Expand All @@ -49,14 +49,14 @@ impl Coroutine {

pub fn empty() -> Coroutine {
Coroutine {
current_stack_segment: StackSegment::new(0),
current_stack_segment: unsafe { Stack::dummy_stack() },
saved_context: Context::empty()
}
}

/// Destroy coroutine and try to reuse std::stack segment.
pub fn recycle(self, stack_pool: &mut StackPool) {
let Coroutine { current_stack_segment, .. } = self;
stack_pool.give_segment(current_stack_segment);
stack_pool.give_stack(current_stack_segment);
}
}
141 changes: 112 additions & 29 deletions src/libgreen/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,46 +8,113 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.

use std::vec;
use std::libc::{c_uint, uintptr_t};
use std::rt::env::max_cached_stacks;
use std::os::{errno, page_size, MemoryMap, MapReadable, MapWritable,
MapNonStandardFlags, MapVirtual};
use std::libc;

pub struct StackSegment {
priv buf: ~[u8],
priv valgrind_id: c_uint
/// A task's stack. The name "Stack" is a vestige of segmented stacks.
pub struct Stack {
priv buf: MemoryMap,
priv min_size: uint,
priv valgrind_id: libc::c_uint,
}

impl StackSegment {
pub fn new(size: uint) -> StackSegment {
unsafe {
// Crate a block of uninitialized values
let mut stack = vec::with_capacity(size);
stack.set_len(size);
// Try to use MAP_STACK on platforms that support it (it's what we're doing
// anyway), but some platforms don't support it at all. For example, it appears
// that there's a bug in freebsd that MAP_STACK implies MAP_FIXED (so it always
// fails): http://lists.freebsd.org/pipermail/freebsd-bugs/2011-July/044840.html
#[cfg(not(windows), not(target_os = "freebsd"))]
static STACK_FLAGS: libc::c_int = libc::MAP_STACK | libc::MAP_PRIVATE |
libc::MAP_ANON;
#[cfg(target_os = "freebsd")]
static STACK_FLAGS: libc::c_int = libc::MAP_PRIVATE | libc::MAP_ANON;
#[cfg(windows)]
static STACK_FLAGS: libc::c_int = 0;

let mut stk = StackSegment {
buf: stack,
valgrind_id: 0
};
impl Stack {
/// Allocate a new stack of `size`. If size = 0, this will fail. Use
/// `dummy_stack` if you want a zero-sized stack.
pub fn new(size: uint) -> Stack {
// Map in a stack. Eventually we might be able to handle stack
// allocation failure, which would fail to spawn the task. But there's
// not many sensible things to do on OOM. Failure seems fine (and is
// what the old stack allocation did).
let stack = match MemoryMap::new(size, [MapReadable, MapWritable,
MapNonStandardFlags(STACK_FLAGS)]) {
Ok(map) => map,
Err(e) => fail!("mmap for stack of size {} failed: {}", size, e)
};

// XXX: Using the FFI to call a C macro. Slow
stk.valgrind_id = rust_valgrind_stack_register(stk.start(), stk.end());
return stk;
// Change the last page to be inaccessible. This is to provide safety;
// when an FFI function overflows it will (hopefully) hit this guard
// page. It isn't guaranteed, but that's why FFI is unsafe. buf.data is
// guaranteed to be aligned properly.
if !protect_last_page(&stack) {
fail!("Could not memory-protect guard page. stack={:?}, errno={}",
stack, errno());
}

let mut stk = Stack {
buf: stack,
min_size: size,
valgrind_id: 0
};

// XXX: Using the FFI to call a C macro. Slow
stk.valgrind_id = unsafe {
rust_valgrind_stack_register(stk.start(), stk.end())
};
return stk;
}

/// Create a 0-length stack which starts (and ends) at 0.
pub unsafe fn dummy_stack() -> Stack {
Stack {
buf: MemoryMap { data: 0 as *mut u8, len: 0, kind: MapVirtual },
min_size: 0,
valgrind_id: 0
}
}

/// Point to the low end of the allocated stack
pub fn start(&self) -> *uint {
self.buf.as_ptr() as *uint
self.buf.data as *uint
}

/// Point one word beyond the high end of the allocated stack
/// Point one uint beyond the high end of the allocated stack
pub fn end(&self) -> *uint {
unsafe {
self.buf.as_ptr().offset(self.buf.len() as int) as *uint
self.buf.data.offset(self.buf.len as int) as *uint
}
}
}

impl Drop for StackSegment {
#[cfg(unix)]
fn protect_last_page(stack: &MemoryMap) -> bool {
unsafe {
// This may seem backwards: the start of the segment is the last page?
// Yes! The stack grows from higher addresses (the end of the allocated
// block) to lower addresses (the start of the allocated block).
let last_page = stack.data as *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 {
// see above
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
}
}

impl Drop for Stack {
fn drop(&mut self) {
unsafe {
// XXX: Using the FFI to call a C macro. Slow
Expand All @@ -56,20 +123,36 @@ impl Drop for StackSegment {
}
}

pub struct StackPool(());
pub struct StackPool {
// Ideally this would be some datastructure that preserved ordering on
// Stack.min_size.
priv stacks: ~[Stack],
}

impl StackPool {
pub fn new() -> StackPool { StackPool(()) }
pub fn new() -> StackPool {
StackPool {
stacks: ~[],
}
}

pub fn take_segment(&self, min_size: uint) -> StackSegment {
StackSegment::new(min_size)
pub fn take_stack(&mut self, min_size: uint) -> Stack {
// Ideally this would be a binary search
match self.stacks.iter().position(|s| s.min_size < min_size) {
Some(idx) => self.stacks.swap_remove(idx),
None => Stack::new(min_size)
}
}

pub fn give_segment(&self, _stack: StackSegment) {
pub fn give_stack(&mut self, stack: Stack) {
if self.stacks.len() <= max_cached_stacks() {
self.stacks.push(stack)
}
}
}

extern {
fn rust_valgrind_stack_register(start: *uintptr_t, end: *uintptr_t) -> c_uint;
fn rust_valgrind_stack_deregister(id: c_uint);
fn rust_valgrind_stack_register(start: *libc::uintptr_t,
end: *libc::uintptr_t) -> libc::c_uint;
fn rust_valgrind_stack_deregister(id: libc::c_uint);
}
1 change: 1 addition & 0 deletions src/libstd/libc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2863,6 +2863,7 @@ pub mod consts {
pub static MAP_PRIVATE : c_int = 0x0002;
pub static MAP_FIXED : c_int = 0x0010;
pub static MAP_ANON : c_int = 0x1000;
pub static MAP_STACK : c_int = 0;

pub static MAP_FAILED : *c_void = -1 as *c_void;

Expand Down
Loading

0 comments on commit 838b5a4

Please sign in to comment.