Skip to content

Commit 224305c

Browse files
committed
WIP unwinding support
1 parent 0003fd6 commit 224305c

File tree

6 files changed

+121
-12
lines changed

6 files changed

+121
-12
lines changed

src/arch/x86_64.rs

Lines changed: 68 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
use core::mem;
5050
use stack::Stack;
5151
use arch::StackPointer;
52+
use unwind;
5253

5354
pub const STACK_ALIGNMENT: usize = 16;
5455

@@ -127,8 +128,9 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize, StackPointer))
127128
# trampoline_2.
128129
nop
129130
130-
# Call the provided function.
131-
callq *16(%rsp)
131+
# Call unwind_wrapper with the provided function.
132+
movq 16(%rsp), %rdx
133+
call ${0:c}
132134
133135
# Clear the stack pointer. We can't call into this context any more once
134136
# the function has returned.
@@ -150,7 +152,7 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize, StackPointer))
150152
.cfi_register %rip, %rax
151153
jmpq *%rax
152154
"#
153-
: : : : "volatile")
155+
: : "s" (unwind::unwind_wrapper as usize) : : "volatile")
154156
}
155157

156158
// We set up the stack in a somewhat special way so that to the unwinder it
@@ -315,3 +317,66 @@ pub unsafe fn swap(arg: usize, new_sp: StackPointer) -> (usize, StackPointer) {
315317
: "volatile", "alignstack");
316318
(ret, mem::transmute(ret_sp))
317319
}
320+
321+
322+
#[inline(always)]
323+
pub unsafe fn unwind(new_sp: StackPointer, new_stack: &Stack) {
324+
// Address of the topmost CFA stack slot.
325+
let new_cfa = (new_stack.base() as *mut usize).offset(-4);
326+
327+
#[naked]
328+
unsafe extern "C" fn trampoline() {
329+
asm!(
330+
r#"
331+
# Save frame pointer explicitly; the unwinder uses it to find CFA of
332+
# the caller, and so it has to have the correct value immediately after
333+
# the call instruction that invoked the trampoline.
334+
pushq %rbp
335+
.cfi_adjust_cfa_offset 8
336+
.cfi_rel_offset %rbp, 0
337+
338+
# Link the call stacks together by writing the current stack bottom
339+
# address to the CFA slot in the new stack.
340+
movq %rsp, (%rcx)
341+
342+
# Load stack pointer of the new context.
343+
movq %rdx, %rsp
344+
345+
# Restore frame pointer of the new context.
346+
popq %rbp
347+
.cfi_adjust_cfa_offset -8
348+
.cfi_restore %rbp
349+
350+
# Jump to the unwind function, which will force a stack unwind in the
351+
# target context. This will eventually return to our caller through the
352+
# stack link.
353+
jmp ${0:c}
354+
"#
355+
: : "s" (unwind::force_unwind as usize) : : "volatile")
356+
}
357+
358+
asm!(
359+
r#"
360+
# Push instruction pointer of the old context and switch to
361+
# the new context.
362+
call ${0:c}
363+
"#
364+
:
365+
: "s" (trampoline as usize)
366+
"{rdx}" (new_sp.0)
367+
"{rcx}" (new_cfa)
368+
: "rax", "rbx", "rcx", "rdx", "rsi", "rdi", /*"rbp", "rsp",*/
369+
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
370+
"mm0", "mm1", "mm2", "mm3", "mm4", "mm5", "mm6", "mm7",
371+
"xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
372+
"xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15",
373+
"xmm16", "xmm17", "xmm18", "xmm19", "xmm20", "xmm21", "xmm22", "xmm23",
374+
"xmm24", "xmm25", "xmm26", "xmm27", "xmm28", "xmm29", "xmm30", "xmm31",
375+
"cc", "dirflag", "fpsr", "flags", "memory"
376+
// Ideally, we would set the LLVM "noredzone" attribute on this function
377+
// (and it would be propagated to the call site). Unfortunately, rustc
378+
// provides no such functionality. Fortunately, by a lucky coincidence,
379+
// the "alignstack" LLVM inline assembly option does exactly the same
380+
// thing on x86_64.
381+
: "volatile", "alignstack");
382+
}

src/generator.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,14 +176,26 @@ impl<'a, Input, Output, Stack> Generator<'a, Input, Output, Stack>
176176
/// Extracts the stack from a generator when the generator function has returned.
177177
/// If the generator function has not returned
178178
/// (i.e. `self.state() == State::Runnable`), panics.
179-
pub fn unwrap(self) -> Stack {
180-
match self.state() {
181-
State::Runnable => panic!("Argh! Bastard! Don't touch that!"),
182-
State::Unavailable => self.stack
179+
pub fn unwrap(mut self) -> Stack {
180+
unsafe {
181+
self.stack_ptr.map(|stack_ptr| arch::unwind(stack_ptr, &self.stack));
182+
183+
// We can't just return self.stack since Generator has a Drop impl
184+
let stack = ptr::read(&self.stack);
185+
ptr::drop_in_place(&mut self.stack_id);
186+
mem::forget(self);
187+
stack
183188
}
184189
}
185190
}
186191

192+
impl<Input, Output, Stack> Drop for Generator<Input, Output, Stack>
193+
where Input: Send, Output: Send, Stack: stack::Stack {
194+
fn drop(&mut self) {
195+
self.stack_ptr.map(|stack_ptr| unsafe { arch::unwind(stack_ptr, &self.stack) });
196+
}
197+
}
198+
187199
/// Yielder is an interface provided to every generator through which it
188200
/// returns a value.
189201
#[derive(Debug)]

src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@
2929
//! * a stack allocator based on anonymous memory mappings with guard pages,
3030
//! [OsStack](struct.OsStack.html).
3131
32-
#[cfg(test)]
32+
//#[cfg(test)]
3333
#[macro_use]
3434
extern crate std;
3535

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

5252
mod debug;
5353

54+
mod unwind;
55+
5456
mod stack;
5557
mod slice_stack;
5658
pub mod generator;

src/os/mod.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,8 @@ impl Stack {
2828
pub fn new(size: usize) -> Result<Stack, IoError> {
2929
let page_size = sys::page_size();
3030

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

3434
// Round the length one page size up, using the fact that the page size
3535
// is a power of two.

src/unwind.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// This file is part of libfringe, a low-level green threading library.
2+
// Copyright (c) Amanieu d'Antras <[email protected]>,
3+
// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or
4+
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or
5+
// http://opensource.org/licenses/MIT>, at your option. This file may not be
6+
// copied, modified, or distributed except according to those terms.
7+
8+
use std::panic;
9+
use arch::StackPointer;
10+
use std::boxed::Box;
11+
12+
struct UnwindMarker;
13+
14+
pub unsafe extern "C" fn unwind_wrapper(arg: usize, sp: StackPointer,
15+
f: unsafe extern "C" fn(usize, StackPointer)) {
16+
match panic::catch_unwind(move || f(arg, sp)) {
17+
Ok(_) => (),
18+
Err(err) => {
19+
if !err.is::<UnwindMarker>() {
20+
// Propagate the panic to the parent context if it wasn't generated by us
21+
panic::resume_unwind(err)
22+
}
23+
}
24+
}
25+
}
26+
27+
pub unsafe extern "C" fn force_unwind() -> ! {
28+
// Use resume_unwind instead of panic! to avoid printing a message
29+
panic::resume_unwind(Box::new(UnwindMarker));
30+
}

tests/generator.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ fn panic_safety() {
6868

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

7878
#[test]
7979
fn with_owned_stack() {
80-
let stack = OwnedStack::new(1024);
80+
let stack = OwnedStack::new(16384);
8181
let mut add_one = unsafe { Generator::unsafe_new(stack, add_one_fn) };
8282
assert_eq!(add_one.resume(1), Some(2));
8383
assert_eq!(add_one.resume(2), Some(3));

0 commit comments

Comments
 (0)