Skip to content

Support panicking and make unsupported platforms a nop #5

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 12 commits into from
Closed
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "stacker"
version = "0.1.3"
version = "0.1.4"
authors = ["Alex Crichton <alex@alexcrichton.com>"]
build = "build.rs"
license = "MIT/Apache-2.0"
Expand Down
7 changes: 2 additions & 5 deletions build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,6 @@ fn main() {
cfg.define("APPLE", None);
} else if target.contains("windows") {
cfg.define("WINDOWS", None);
} else {
panic!("\n\nusing currently unsupported target triple with \
stacker: {}\n\n", target);
}

if target.starts_with("x86_64") {
Expand All @@ -26,8 +23,8 @@ fn main() {
cfg.file(if msvc {"src/arch/i686.asm"} else {"src/arch/i686.S"});
cfg.define("X86", None);
} else {
panic!("\n\nusing currently unsupported target triple with \
stacker: {}\n\n", target);
println!("cargo:rustc-cfg=fallback");
return;
}

cfg.include("src/arch").compile("libstacker.a");
Expand Down
16 changes: 16 additions & 0 deletions src/arch/fallback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#[no_mangle]
extern fn __stacker_stack_pointer() -> usize {
panic!("not supported")
}

#[no_mangle]
unsafe extern fn __stacker_switch_stacks(
_new_stack: usize,
_fnptr: unsafe fn(&mut &mut FnMut()),
_dataptr: &mut &mut FnMut(),
) {
panic!("not supported")
}

#[no_mangle]
extern fn __stacker_black_box(_: *const u8) {}
72 changes: 42 additions & 30 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@
//! // guaranteed to have at least 32K of stack
//! });
//! ```
//!
//! # Platform support
//!
//! Only Windows, MacOS and Linux are supported. Other platforms don't do anything
//! and will overflow your stack.

#![allow(improper_ctypes)]

Expand All @@ -30,6 +35,10 @@ extern crate libc;

use std::cell::Cell;

#[cfg(fallback)]
#[path="arch/fallback.rs"]
mod fallback;

extern {
fn __stacker_stack_pointer() -> usize;
fn __stacker_switch_stacks(new_stack: usize,
Expand All @@ -38,17 +47,17 @@ extern {
}

thread_local! {
static STACK_LIMIT: Cell<usize> = Cell::new(unsafe {
static STACK_LIMIT: Cell<Option<usize>> = Cell::new(unsafe {
guess_os_stack_limit()
})
}

fn get_stack_limit() -> usize {
fn get_stack_limit() -> Option<usize> {
STACK_LIMIT.with(|s| s.get())
}

fn set_stack_limit(l: usize) {
STACK_LIMIT.with(|s| s.set(l))
STACK_LIMIT.with(|s| s.set(Some(l)))
}

/// Grows the call stack if necessary.
Expand All @@ -60,49 +69,52 @@ fn set_stack_limit(l: usize) {
///
/// The closure `f` is guaranteed to run on a stack with at least `red_zone`
/// bytes, and it will be run on the current stack if there's space available.
pub fn maybe_grow<R, F: FnOnce() -> R>(red_zone: usize,
stack_size: usize,
f: F) -> R {
if remaining_stack() >= red_zone {
f()
pub fn maybe_grow<R, F: FnOnce() -> R>(red_zone: usize, stack_size: usize, f: F) -> R {
if let Some(remaining_stack_bytes) = remaining_stack() {
if remaining_stack_bytes >= red_zone {
f()
} else {
grow_the_stack(stack_size, f, remaining_stack_bytes)
}
} else {
grow_the_stack(stack_size, f)
f()
}
}

/// Queries the amount of remaining stack as interpreted by this library.
///
/// This function will return the amount of stack space left which will be used
/// to determine whether a stack switch should be made or not.
pub fn remaining_stack() -> usize {
unsafe {
__stacker_stack_pointer() - get_stack_limit()
}
pub fn remaining_stack() -> Option<usize> {
get_stack_limit().map(|limit| unsafe {
__stacker_stack_pointer() - limit
})
}

#[inline(never)]
fn grow_the_stack<R, F: FnOnce() -> R>(stack_size: usize, f: F) -> R {
fn grow_the_stack<R, F: FnOnce() -> R>(stack_size: usize, f: F, remaining_stack_bytes: usize) -> R {
let mut f = Some(f);
let mut ret = None;
unsafe {
_grow_the_stack(stack_size, &mut || {
ret = Some(f.take().unwrap()());
_grow_the_stack(stack_size, remaining_stack_bytes, &mut || {
let f: F = f.take().unwrap();
ret = Some(std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)));
});
}
ret.unwrap()
match ret.unwrap() {
Ok(ret) => ret,
Err(payload) => std::panic::resume_unwind(payload),
}
}

unsafe fn _grow_the_stack(stack_size: usize, mut f: &mut FnMut()) {
unsafe fn _grow_the_stack(stack_size: usize, old_limit: usize, mut f: &mut FnMut()) {
// Align to 16-bytes (see below for why)
let stack_size = (stack_size + 15) / 16 * 16;

// Allocate some new stack for oureslves
let mut stack = Vec::<u8>::with_capacity(stack_size);
let new_limit = stack.as_ptr() as usize + 32 * 1024;

// Save off the old stack limits
let old_limit = get_stack_limit();

// Prepare stack limits for the stack switch
set_stack_limit(new_limit);

Expand Down Expand Up @@ -135,7 +147,7 @@ cfg_if! {
//
// https://github.com/adobe/webkit/blob/0441266/Source/WTF/wtf
// /StackBounds.cpp
unsafe fn guess_os_stack_limit() -> usize {
unsafe fn guess_os_stack_limit() -> Option<usize> {
#[cfg(target_pointer_width = "32")]
extern {
#[link_name = "__stacker_get_tib_32"]
Expand All @@ -151,12 +163,12 @@ cfg_if! {
// the struct layout of the 32-bit TIB. It looks like the struct
// layout of the 64-bit TIB is also the same for getting the stack
// limit: http://doxygen.reactos.org/d3/db0/structNT__TIB64.html
*get_tib_address().offset(2)
Some(*get_tib_address().offset(2))
}
} else if #[cfg(target_os = "linux")] {
use std::mem;

unsafe fn guess_os_stack_limit() -> usize {
unsafe fn guess_os_stack_limit() -> Option<usize> {
let mut attr: libc::pthread_attr_t = mem::zeroed();
assert_eq!(libc::pthread_attr_init(&mut attr), 0);
assert_eq!(libc::pthread_getattr_np(libc::pthread_self(),
Expand All @@ -166,18 +178,18 @@ cfg_if! {
assert_eq!(libc::pthread_attr_getstack(&attr, &mut stackaddr,
&mut stacksize), 0);
assert_eq!(libc::pthread_attr_destroy(&mut attr), 0);
stackaddr as usize
Some(stackaddr as usize)
}
} else if #[cfg(target_os = "macos")] {
use libc::{c_void, pthread_t, size_t};

unsafe fn guess_os_stack_limit() -> usize {
libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize -
libc::pthread_get_stacksize_np(libc::pthread_self()) as usize
unsafe fn guess_os_stack_limit() -> Option<usize> {
Some(libc::pthread_get_stackaddr_np(libc::pthread_self()) as usize -
libc::pthread_get_stacksize_np(libc::pthread_self()) as usize)
}
} else {
unsafe fn guess_os_stack_limit() -> usize {
panic!("cannot guess the stack limit on this platform");
unsafe fn guess_os_stack_limit() -> Option<usize> {
None
}
}
}
27 changes: 27 additions & 0 deletions tests/panic_handling.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
extern crate stacker;

const RED_ZONE: usize = 100*1024; // 100k
const STACK_PER_RECURSION: usize = 1 * 1024 * 1024; // 1MB

pub fn ensure_sufficient_stack<R, F: FnOnce() -> R + std::panic::UnwindSafe>(
f: F
) -> R {
stacker::maybe_grow(RED_ZONE, STACK_PER_RECURSION, f)
}

#[inline(never)]
fn recurse(n: usize) {
let x = [42u8; 50000];
if n == 0 {
panic!("an inconvenient time");
} else {
ensure_sufficient_stack(|| recurse(n - 1));
}
drop(x);
}

#[test]
#[should_panic]
fn foo() {
recurse(10000);
}