Skip to content

Commit

Permalink
WIP unwinding support
Browse files Browse the repository at this point in the history
  • Loading branch information
Amanieu committed Nov 1, 2016
1 parent 01d9b8a commit 5c0a00b
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 38 deletions.
2 changes: 1 addition & 1 deletion src/arch/aarch64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ use arch::StackPointer;

pub const STACK_ALIGNMENT: usize = 16;

pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe extern "C" fn(usize, StackPointer)) -> StackPointer {
pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe fn(usize, StackPointer)) -> StackPointer {
#[cfg(not(target_vendor = "apple"))]
#[naked]
unsafe extern "C" fn trampoline_1() {
Expand Down
10 changes: 5 additions & 5 deletions src/arch/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ mod tests {

#[test]
fn context() {
unsafe extern "C" fn adder(arg: usize, stack_ptr: StackPointer) {
unsafe fn adder(arg: usize, stack_ptr: StackPointer) {
println!("it's alive! arg: {}", arg);
let (arg, stack_ptr) = arch::swap(arg + 1, stack_ptr);
println!("still alive! arg: {}", arg);
Expand All @@ -69,7 +69,7 @@ mod tests {

#[test]
fn context_simd() {
unsafe extern "C" fn permuter(arg: usize, stack_ptr: StackPointer) {
unsafe fn permuter(arg: usize, stack_ptr: StackPointer) {
// This will crash if the stack is not aligned properly.
let x = simd::i32x4::splat(arg as i32);
let y = x * x;
Expand All @@ -92,7 +92,7 @@ mod tests {
}
}

unsafe extern "C" fn do_panic(arg: usize, stack_ptr: StackPointer) {
unsafe fn do_panic(arg: usize, stack_ptr: StackPointer) {
match arg {
0 => panic!("arg=0"),
1 => {
Expand Down Expand Up @@ -128,7 +128,7 @@ mod tests {

#[test]
fn ret() {
unsafe extern "C" fn ret2(_: usize, _: StackPointer) {}
unsafe fn ret2(_: usize, _: StackPointer) {}

unsafe {
let stack = OsStack::new(4 << 20).unwrap();
Expand All @@ -141,7 +141,7 @@ mod tests {

#[bench]
fn swap(b: &mut test::Bencher) {
unsafe extern "C" fn loopback(mut arg: usize, mut stack_ptr: StackPointer) {
unsafe fn loopback(mut arg: usize, mut stack_ptr: StackPointer) {
// This deliberately does not ignore arg, to measure the time it takes
// to move the return value between registers.
loop {
Expand Down
2 changes: 1 addition & 1 deletion src/arch/or1k.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use arch::StackPointer;

pub const STACK_ALIGNMENT: usize = 4;

pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe extern "C" fn(usize, StackPointer)) -> StackPointer {
pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe fn(usize, StackPointer)) -> StackPointer {
#[naked]
unsafe extern "C" fn trampoline_1() {
asm!(
Expand Down
2 changes: 1 addition & 1 deletion src/arch/x86.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use arch::StackPointer;

pub const STACK_ALIGNMENT: usize = 16;

pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe extern "C" fn(usize, StackPointer)) -> StackPointer {
pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe fn(usize, StackPointer)) -> StackPointer {
#[cfg(not(target_vendor = "apple"))]
#[naked]
unsafe extern "C" fn trampoline_1() {
Expand Down
98 changes: 81 additions & 17 deletions src/arch/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,11 @@
use core::mem;
use stack;
use arch::StackPointer;
use unwind;

pub const STACK_ALIGNMENT: usize = 16;

pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe extern "C" fn(usize, StackPointer)) -> StackPointer {
pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe fn(usize, StackPointer)) -> StackPointer {
#[cfg(not(target_vendor = "apple"))]
#[naked]
unsafe extern "C" fn trampoline_1() {
Expand Down Expand Up @@ -127,12 +128,10 @@ pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe extern "C" fn(u
# trampoline_2.
nop
# Call the provided function.
callq *16(%rsp)
# Clear the stack pointer. We can't call into this context any more once
# the function has returned.
xorq %rsi, %rsi
# Call unwind_wrapper with the provided function and the CFA address.
movq %rsp, %rdx
movq 16(%rsp), %rcx
call ${0:c}
# Restore the stack pointer of the parent context. No CFI adjustments
# are needed since we have the same stack frame as trampoline_1.
Expand All @@ -143,14 +142,27 @@ pub unsafe fn init<Stack: stack::Stack>(stack: &Stack, f: unsafe extern "C" fn(u
.cfi_adjust_cfa_offset -8
.cfi_restore %rbp
# If the returned value is nonzero, trigger an unwind in the parent
# context with the given exception object.
movq %rax, %rdi
testq %rax, %rax
jnz ${1:c}
# Clear the stack pointer. We can't call into this context any more once
# the function has returned.
xorq %rsi, %rsi
# Return into the parent context. Use `pop` and `jmp` instead of a `ret`
# to avoid return address mispredictions (~8ns per `ret` on Ivy Bridge).
popq %rax
popq %rcx
.cfi_adjust_cfa_offset -8
.cfi_register %rip, %rax
jmpq *%rax
.cfi_register %rip, %rcx
jmpq *%rcx
"#
: : : : "volatile")
:
: "s" (unwind::unwind_wrapper as usize)
"s" (unwind::start_unwind as usize)
: : "volatile")
}

// We set up the stack in a somewhat special way so that to the unwinder it
Expand Down Expand Up @@ -213,10 +225,10 @@ pub unsafe fn swap_link<Stack: stack::Stack>(arg: usize, new_sp: StackPointer,
# Return into the new context. Use `pop` and `jmp` instead of a `ret`
# to avoid return address mispredictions (~8ns per `ret` on Ivy Bridge).
popq %rax
popq %rcx
.cfi_adjust_cfa_offset -8
.cfi_register %rip, %rax
jmpq *%rax
.cfi_register %rip, %rcx
jmpq *%rcx
"#
: : : : "volatile")
}
Expand Down Expand Up @@ -267,10 +279,10 @@ pub unsafe fn swap(arg: usize, new_sp: StackPointer) -> (usize, StackPointer) {
popq %rbp
.cfi_adjust_cfa_offset -8
.cfi_restore %rbp
popq %rax
popq %rcx
.cfi_adjust_cfa_offset -8
.cfi_register %rip, %rax
jmpq *%rax
.cfi_register %rip, %rcx
jmpq *%rcx
"#
: : : : "volatile")
}
Expand All @@ -297,3 +309,55 @@ pub unsafe fn swap(arg: usize, new_sp: StackPointer) -> (usize, StackPointer) {
: "volatile", "alignstack");
(ret, mem::transmute(ret_sp))
}


#[inline(always)]
pub unsafe fn unwind<Stack: stack::Stack>(new_sp: StackPointer, new_stack: &Stack) {
// Address of the topmost CFA stack slot.
let new_cfa = StackPointer::stack_base(new_stack).offset(-4);

// Argument to pass to start_unwind, based on the CFA slot address.
let arg = unwind::unwind_arg(new_cfa);

// This is identical to swap_link, except that it doesn't set %rsi and jumps
// (tail call) to start_unwind instead of returning into the target context.
#[naked]
unsafe extern "C" fn trampoline() {
asm!(
r#"
pushq %rbp
.cfi_adjust_cfa_offset 8
.cfi_rel_offset %rbp, 0
movq %rsp, (%rcx)
movq %rdx, %rsp
popq %rbp
.cfi_adjust_cfa_offset -8
.cfi_restore %rbp
# Jump to the start_unwind function, which will force a stack unwind in
# the target context. This will eventually return to our caller through
# the stack link.
jmp ${0:c}
"#
: : "s" (unwind::start_unwind as usize) : : "volatile")
}

asm!(
r#"
call ${0:c}
"#
:
: "s" (trampoline as usize)
"{rdi}" (arg)
"{rdx}" (new_sp.0)
"{rcx}" (new_cfa)
: "rax", "rbx", "rcx", "rdx", "rsi", "rdi", /*"rbp", "rsp",*/
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
"mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7",
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
"xmm16", "xmm17", "xmm18", "xmm19", "xmm20", "xmm21", "xmm22", "xmm23",
"xmm24", "xmm25", "xmm26", "xmm27", "xmm28", "xmm29", "xmm30", "xmm31",
"cc", "dirflag", "fpsr", "flags", "memory"
: "volatile", "alignstack");
}
45 changes: 36 additions & 9 deletions src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ impl<'a, Input, Output, Stack> Generator<'a, Input, Output, Stack>
/// See also the [contract](../trait.Stack.html) that needs to be fulfilled by `stack`.
pub unsafe fn unsafe_new<F>(stack: Stack, f: F) -> Generator<'a, Input, Output, Stack>
where F: FnOnce(&Yielder<Input, Output>, Input) + Send + 'a {
unsafe extern "C" fn generator_wrapper<Input, Output, Stack, F>(env: usize, stack_ptr: StackPointer)
unsafe fn generator_wrapper<Input, Output, Stack, F>(env: usize, stack_ptr: StackPointer)
where Stack: stack::Stack, F: FnOnce(&Yielder<Input, Output>, Input) {
// Retrieve our environment from the callee and return control to it.
let f: F = decode_usize(env);
Expand Down Expand Up @@ -200,26 +200,38 @@ impl<'a, Input, Output, Stack> Generator<'a, Input, Output, Stack>
/// Extracts the stack from a generator when the generator function has returned.
/// If the generator function has not returned
/// (i.e. `self.state() == State::Runnable`), panics.
pub fn unwrap(self) -> Stack {
match self.state() {
State::Runnable => panic!("Argh! Bastard! Don't touch that!"),
State::Unavailable => self.stack
pub fn unwrap(mut self) -> Stack {
unsafe {
self.stack_ptr.map(|stack_ptr| arch::unwind(stack_ptr, &self.stack));

// We can't just return self.stack since Generator has a Drop impl
let stack = ptr::read(&self.stack);
ptr::drop_in_place(&mut self.stack_id);
mem::forget(self);
stack
}
}
}

impl<'a, Input, Output, Stack> Drop for Generator<'a, Input, Output, Stack>
where Input: 'a, Output: 'a, Stack: stack::Stack {
fn drop(&mut self) {
self.stack_ptr.map(|stack_ptr| unsafe { arch::unwind(stack_ptr, &self.stack) });
}
}

/// Yielder is an interface provided to every generator through which it
/// returns a value.
#[derive(Debug)]
pub struct Yielder<Input, Output> {
stack_ptr: Cell<StackPointer>,
stack_ptr: Cell<Option<StackPointer>>,
phantom: PhantomData<(*const Input, *mut Output)>
}

impl<Input, Output> Yielder<Input, Output> {
fn new(stack_ptr: StackPointer) -> Yielder<Input, Output> {
Yielder {
stack_ptr: Cell::new(stack_ptr),
stack_ptr: Cell::new(Some(stack_ptr)),
phantom: PhantomData
}
}
Expand All @@ -229,9 +241,24 @@ impl<Input, Output> Yielder<Input, Output> {
#[inline(always)]
pub fn suspend(&self, item: Output) -> Input {
unsafe {
struct PanicGuard<'a>(&'a Cell<Option<StackPointer>>);
impl<'a> Drop for PanicGuard<'a> {
fn drop(&mut self) {
self.0.set(None);
}
}

let stack_ptr = self.stack_ptr.get().expect("attempted to yield while unwinding");
let item2 = NoDrop { inner: item };
let (data, stack_ptr) = arch::swap(encode_usize(&item2), self.stack_ptr.get());
self.stack_ptr.set(stack_ptr);

// Use a PanicGuard to set self.stack_ptr to None if unwinding occurs. This
// is necessary to guarantee safety in case someone tries to call yield
// while we are unwinding since there is nowhere to yield to.
let guard = PanicGuard(&self.stack_ptr);
let (data, stack_ptr) = arch::swap(encode_usize(&item2), stack_ptr);
mem::forget(guard);

self.stack_ptr.set(Some(stack_ptr));
decode_usize(data)
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub const STACK_ALIGNMENT: usize = arch::STACK_ALIGNMENT;

mod debug;

mod unwind;

pub mod generator;

mod stack;
4 changes: 2 additions & 2 deletions src/stack/os/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@ impl OsStack {
pub fn new(size: usize) -> Result<OsStack, IoError> {
let page_size = sys::page_size();

// Stacks have to be at least one page long.
let len = if size == 0 { page_size } else { size };
// Stacks have to be at least 16KB to support unwinding.
let len = if size == 0 { 16384 } else { size };

// Round the length one page size up, using the fact that the page size
// is a power of two.
Expand Down
75 changes: 75 additions & 0 deletions src/unwind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// This file is part of libfringe, a low-level green threading library.
// Copyright (c) Amanieu d'Antras <amanieu@gmail.com>,
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
// http://opensource.org/licenses/MIT>, at your option. This file may not be
// copied, modified, or distributed except according to those terms.
extern crate std;

use self::std::panic;
use self::std::boxed::Box;
use core::any::Any;
use arch::StackPointer;

// Marker object that is passed through the stack unwinding
struct UnwindMarker {
// We use the CFA address as an identifier so that nested generators are handled
// correctly. When unwinding, we will want to continue through any number of
// nested generators until we reach the one identified with a matching CFA address.
cfa_addr: *mut usize,
}
unsafe impl Send for UnwindMarker {}

// Whether the current platform support unwinding across multiple stacks.
fn have_cross_stack_unwind() -> bool {
// - Windows uses SEH for unwinding instead of libunwind. While it may be
// possible to munge it so support cross-stack unwinding, we stay conservative
// for now.
// - iOS on ARM uses setjmp/longjmp instead of DWARF-2 unwinding, which needs
// to be explicitly saved/restored when switching contexts.
!(cfg!(windows) || cfg!(all(target_os = "ios", target_arch = "arm")))
}

// Wrapper around the root function of a generator which handles unwinding.
pub unsafe extern "C" fn unwind_wrapper(arg: usize, sp: StackPointer, cfa_addr: *mut usize,
f: unsafe fn(usize, StackPointer)) -> Option<Box<Box<Any + Send>>> {
// Catch any attempts to unwind out of the context.
match panic::catch_unwind(move || f(arg, sp)) {
Ok(_) => None,
Err(err) => {
// If the unwinding is due to an UnwindMarker, check whether it is intended
// for us by comparing the CFA of the caller with ours. If it is the same
// then we can swallow the exception and return to the caller normally.
if let Some(marker) = err.downcast_ref::<UnwindMarker>() {
if marker.cfa_addr == cfa_addr {
return None;
}
}

// Otherwise, propagate the panic to the parent context.
if have_cross_stack_unwind() {
panic::resume_unwind(err)
} else {
// The assembly code will call start_unwind in the parent context and
// pass it this Box as parameter.
Some(Box::new(err))
}
}
}
}

// Called by asm to start unwinding in the current context with the given
// exception object.
pub unsafe extern "C" fn start_unwind(panic: Box<Box<Any + Send>>) -> ! {
// Use resume_unwind instead of panic! to avoid printing a message.
panic::resume_unwind(*panic)
}

// Get the initial argument to pass to start_unwind, keyed to the address of the
// CFA slot on the generator stack that is going to be unwound.
pub fn unwind_arg(cfa_addr: *mut usize) -> usize {
let marker = UnwindMarker {
cfa_addr: cfa_addr
};
Box::into_raw(Box::new(Box::new(marker) as Box<Any + Send>)) as usize
}
Loading

0 comments on commit 5c0a00b

Please sign in to comment.