Skip to content

Commit

Permalink
WIP unwinding support
Browse files Browse the repository at this point in the history
  • Loading branch information
Amanieu committed Sep 16, 2016
1 parent 0003fd6 commit 58a2fb6
Show file tree
Hide file tree
Showing 6 changed files with 121 additions and 12 deletions.
71 changes: 68 additions & 3 deletions src/arch/x86_64.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
use core::mem;
use stack::Stack;
use arch::StackPointer;
use unwind;

pub const STACK_ALIGNMENT: usize = 16;

Expand Down Expand Up @@ -127,8 +128,9 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize, StackPointer))
# trampoline_2.
nop
# Call the provided function.
callq *16(%rsp)
# Call unwind_wrapper with the provided function.
movq 16(%rsp), %rdx
call ${0:c}
# Clear the stack pointer. We can't call into this context any more once
# the function has returned.
Expand All @@ -150,7 +152,7 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize, StackPointer))
.cfi_register %rip, %rax
jmpq *%rax
"#
: : : : "volatile")
: : "s" (unwind::unwind_wrapper as usize) : : "volatile")
}

// We set up the stack in a somewhat special way so that to the unwinder it
Expand Down Expand Up @@ -315,3 +317,66 @@ pub unsafe fn swap(arg: usize, new_sp: StackPointer) -> (usize, StackPointer) {
: "volatile", "alignstack");
(ret, mem::transmute(ret_sp))
}


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

#[naked]
unsafe extern "C" fn trampoline() {
asm!(
r#"
# Save frame pointer explicitly; the unwinder uses it to find CFA of
# the caller, and so it has to have the correct value immediately after
# the call instruction that invoked the trampoline.
pushq %rbp
.cfi_adjust_cfa_offset 8
.cfi_rel_offset %rbp, 0
# Link the call stacks together by writing the current stack bottom
# address to the CFA slot in the new stack.
movq %rsp, (%rcx)
# Load stack pointer of the new context.
movq %rdx, %rsp
# Restore frame pointer of the new context.
popq %rbp
.cfi_adjust_cfa_offset -8
.cfi_restore %rbp
# Jump to the 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::force_unwind as usize) : : "volatile")
}

asm!(
r#"
# Push instruction pointer of the old context and switch to
# the new context.
call ${0:c}
"#
:
: "s" (trampoline as usize)
"{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"
// Ideally, we would set the LLVM "noredzone" attribute on this function
// (and it would be propagated to the call site). Unfortunately, rustc
// provides no such functionality. Fortunately, by a lucky coincidence,
// the "alignstack" LLVM inline assembly option does exactly the same
// thing on x86_64.
: "volatile", "alignstack");
}
20 changes: 16 additions & 4 deletions src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -176,14 +176,26 @@ 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: Send + 'a, Output: Send + '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)]
Expand Down
4 changes: 3 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
//! * a stack allocator based on anonymous memory mappings with guard pages,
//! [OsStack](struct.OsStack.html).

#[cfg(test)]
//#[cfg(test)]
#[macro_use]
extern crate std;

Expand All @@ -51,6 +51,8 @@ pub const STACK_ALIGNMENT: usize = arch::STACK_ALIGNMENT;

mod debug;

mod unwind;

mod stack;
mod slice_stack;
pub mod generator;
Expand Down
4 changes: 2 additions & 2 deletions src/os/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ impl Stack {
pub fn new(size: usize) -> Result<Stack, 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
30 changes: 30 additions & 0 deletions src/unwind.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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.

use std::panic;
use arch::StackPointer;
use std::boxed::Box;

struct UnwindMarker;

pub unsafe extern "C" fn unwind_wrapper(arg: usize, sp: StackPointer,
f: unsafe extern "C" fn(usize, StackPointer)) {
match panic::catch_unwind(move || f(arg, sp)) {
Ok(_) => (),
Err(err) => {
if !err.is::<UnwindMarker>() {
// Propagate the panic to the parent context if it wasn't generated by us
panic::resume_unwind(err)
}
}
}
}

pub unsafe extern "C" fn force_unwind() -> ! {
// Use resume_unwind instead of panic! to avoid printing a message
panic::resume_unwind(Box::new(UnwindMarker));
}
4 changes: 2 additions & 2 deletions tests/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ fn panic_safety() {

#[test]
fn with_slice_stack() {
let mut memory = [0; 1024];
let mut memory = [0; 16384];
let stack = SliceStack(&mut memory);
let mut add_one = unsafe { Generator::unsafe_new(stack, add_one_fn) };
assert_eq!(add_one.resume(1), Some(2));
Expand All @@ -77,7 +77,7 @@ fn with_slice_stack() {

#[test]
fn with_owned_stack() {
let stack = OwnedStack::new(1024);
let stack = OwnedStack::new(16384);
let mut add_one = unsafe { Generator::unsafe_new(stack, add_one_fn) };
assert_eq!(add_one.resume(1), Some(2));
assert_eq!(add_one.resume(2), Some(3));
Expand Down

0 comments on commit 58a2fb6

Please sign in to comment.