From cbe136b762d9cb800c499bfa790227bc336cf97e Mon Sep 17 00:00:00 2001 From: whitequark Date: Sat, 16 Jul 2016 01:22:41 +0000 Subject: [PATCH] Completely rework fringe::Context and fringe::arch. The new design concerns itself with one thing and exactly one thing: passing values back and forth with an extern "C" function. This allows to simplify fringe::arch into a single primitive, swap. Close #21 --- Cargo.toml | 13 ++- README.md | 9 +-- benches/context_new.rs | 41 ---------- benches/swap.rs | 29 ++++--- benches/{kernel_swap.rs => syscall.rs} | 5 +- src/arch/common.rs | 34 -------- src/arch/mod.rs | 22 +++--- src/arch/x86.rs | 105 +++++++++++++++++++++++++ src/arch/x86/.syntastic_asm_config | 1 - src/arch/x86/init.s | 40 ---------- src/arch/x86/mod.rs | 33 -------- src/arch/x86/swap.s | 37 --------- src/arch/x86_64.rs | 92 ++++++++++++++++++++++ src/arch/x86_64/.syntastic_asm_config | 1 - src/arch/x86_64/init.s | 41 ---------- src/arch/x86_64/mod.rs | 36 --------- src/arch/x86_64/swap.s | 44 ----------- src/arch/x86_common.rs | 35 --------- src/context.rs | 54 ++++++------- src/debug/mod.rs | 2 +- src/debug/valgrind.rs | 2 +- src/lib.rs | 13 +-- src/os/mod.rs | 4 +- src/stack.rs | 4 +- tests/basic.rs | 26 ------ tests/context.rs | 60 ++++++++++++++ tests/fpe.rs | 23 +++--- 27 files changed, 339 insertions(+), 467 deletions(-) delete mode 100644 benches/context_new.rs rename benches/{kernel_swap.rs => syscall.rs} (87%) delete mode 100644 src/arch/common.rs create mode 100644 src/arch/x86.rs delete mode 100644 src/arch/x86/.syntastic_asm_config delete mode 100644 src/arch/x86/init.s delete mode 100644 src/arch/x86/mod.rs delete mode 100644 src/arch/x86/swap.s create mode 100644 src/arch/x86_64.rs delete mode 100644 src/arch/x86_64/.syntastic_asm_config delete mode 100644 src/arch/x86_64/init.s delete mode 100644 src/arch/x86_64/mod.rs delete mode 100644 src/arch/x86_64/swap.s delete mode 100644 src/arch/x86_common.rs delete mode 100644 tests/basic.rs create mode 100644 tests/context.rs diff --git a/Cargo.toml b/Cargo.toml index 833eb30..9f1c2fb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,9 +15,16 @@ optional = true git = "https://github.com/edef1c/libvalgrind" rev = "9ef793e9549aabfd2d969615180b69d29ce28d88" -[dependencies.void] -default-features = false -version = "1" +[dev-dependencies] +simd = "0.1" [features] default = ["valgrind"] + +# These apply only to tests within this library; assembly at -O0 is completely +# unreadable, so use -O1. +[profile.dev] +opt-level = 1 + +[profile.test] +opt-level = 1 diff --git a/README.md b/README.md index eaee2f0..91e2cf8 100644 --- a/README.md +++ b/README.md @@ -44,17 +44,10 @@ git = "https://github.com/edef1c/libfringe.git" ### Feature flags [Cargo's feature flags]: http://doc.crates.io/manifest.html#the-[features]-section - libfringe provides several optional features through [Cargo's feature flags]. + libfringe provides some optional features through [Cargo's feature flags]. Currently, all of them are enabled by default. #### `valgrind` [Valgrind]: http://valgrind.org [Valgrind] integration. libfringe will register context stacks with Valgrind. - -#### `os` - - [Built-in stack allocator]: https://edef1c.github.io/libfringe/fringe/struct.OsStack.html - [Built-in stack allocator] using your your operating system's anonymous memory mapping facility. - Currently only available for Unix. - diff --git a/benches/context_new.rs b/benches/context_new.rs deleted file mode 100644 index 28a2f11..0000000 --- a/benches/context_new.rs +++ /dev/null @@ -1,41 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. -#![feature(test)] -extern crate test; -extern crate fringe; -use fringe::Context; - -static mut ctx_slot: *mut Context<'static, SliceStack<'static>> = 0 as *mut Context<_>; -static mut stack_buf: [u8; 1024] = [0; 1024]; - -#[bench] -fn context_new(b: &mut test::Bencher) { - b.iter(|| unsafe { - let stack = SliceStack(&mut stack_buf); - - let mut ctx = Context::new(stack, move || { - let ctx_ptr = ctx_slot; - loop { - Context::swap(ctx_ptr, ctx_ptr); - } - }); - - ctx_slot = &mut ctx; - - Context::swap(ctx_slot, ctx_slot); - }) -} - -struct SliceStack<'a>(&'a mut [u8]); -impl<'a> fringe::Stack for SliceStack<'a> { - fn top(&mut self) -> *mut u8 { - unsafe { - self.0.as_mut_ptr().offset(self.0.len() as isize) - } - } - - fn limit(&self) -> *const u8 { - self.0.as_ptr() - } -} diff --git a/benches/swap.rs b/benches/swap.rs index cd6d84d..170213a 100644 --- a/benches/swap.rs +++ b/benches/swap.rs @@ -1,31 +1,30 @@ // This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef +// Copyright (c) edef , +// whitequark // See the LICENSE file included in this distribution. #![feature(test)] -#![cfg(feature = "os")] extern crate test; extern crate fringe; + use fringe::Context; -static mut ctx_slot: *mut Context<'static, fringe::OsStack> = 0 as *mut Context<_>; +static mut ctx_slot: *mut Context = 0 as *mut Context<_>; #[bench] fn swap(b: &mut test::Bencher) { + unsafe extern "C" fn loopback(mut arg: usize) -> ! { + // This deliberately does not ignore arg, to measure the time it takes + // to move the return value between registers. + let ctx_ptr = ctx_slot; + loop { arg = Context::swap(ctx_ptr, ctx_ptr, arg) } + } + unsafe { let stack = fringe::OsStack::new(4 << 20).unwrap(); - - let mut ctx = Context::new(stack, move || { - let ctx_ptr = ctx_slot; - loop { - Context::swap(ctx_ptr, ctx_ptr); - } - }); + let mut ctx = Context::new(stack, loopback); + ctx_slot = &mut ctx; let ctx_ptr = &mut ctx; - ctx_slot = ctx_ptr; - - Context::swap(ctx_ptr, ctx_ptr); - - b.iter(|| Context::swap(ctx_ptr, ctx_ptr)); + b.iter(|| Context::swap(ctx_ptr, ctx_ptr, 0)); } } diff --git a/benches/kernel_swap.rs b/benches/syscall.rs similarity index 87% rename from benches/kernel_swap.rs rename to benches/syscall.rs index 6c2aaf8..3275b80 100644 --- a/benches/kernel_swap.rs +++ b/benches/syscall.rs @@ -4,11 +4,10 @@ #![cfg(target_os = "linux")] #![feature(asm, test)] extern crate test; -use test::Bencher; #[cfg(target_arch = "x86_64")] #[bench] -fn kernel_swap(b: &mut Bencher) { +fn syscall(b: &mut test::Bencher) { b.iter(|| unsafe { asm!("movq $$102, %rax\n\ syscall" @@ -21,7 +20,7 @@ fn kernel_swap(b: &mut Bencher) { #[cfg(target_arch = "x86")] #[bench] -fn kernel_swap(b: &mut Bencher) { +fn syscall(b: &mut test::Bencher) { b.iter(|| unsafe { asm!("mov $$24, %eax\n\ int $$0x80" diff --git a/src/arch/common.rs b/src/arch/common.rs deleted file mode 100644 index af12ef7..0000000 --- a/src/arch/common.rs +++ /dev/null @@ -1,34 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. -use core::mem::{size_of, align_of}; -use core::cmp::max; -use core::ptr; - -use void::{self, Void}; - -use super::imp::STACK_ALIGN; - -pub unsafe extern "C" fn rust_trampoline(f: *const F) -> ! - where F: FnOnce() -> Void { - void::unreachable(ptr::read(f)()) -} - -pub unsafe fn push(spp: &mut *mut usize, value: T) -> *mut T { - let mut sp = *spp as *mut T; - sp = offset_mut(sp, -1); - sp = align_down_mut(sp, max(align_of::(), STACK_ALIGN)); - ptr::write(sp, value); // does not attempt to drop old value - *spp = sp as *mut usize; - sp -} - -pub fn align_down_mut(sp: *mut T, n: usize) -> *mut T { - let sp = (sp as usize) & !(n - 1); - sp as *mut T -} - -// ptr::offset_mut is positive ints only -pub fn offset_mut(ptr: *mut T, count: isize) -> *mut T { - (ptr as isize + count * (size_of::() as isize)) as *mut T -} diff --git a/src/arch/mod.rs b/src/arch/mod.rs index ebcb04a..3d7fe4e 100644 --- a/src/arch/mod.rs +++ b/src/arch/mod.rs @@ -1,16 +1,18 @@ // This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef +// Copyright (c) edef , +// whitequark // See the LICENSE file included in this distribution. -pub use self::imp::Registers; +pub use self::imp::*; -unsafe impl Send for Registers {} - -mod common; - -#[cfg(target_arch = "x86_64")] -#[path = "x86_64/mod.rs"] -mod imp; +// rust-lang/rust#25544 +// #[cfg_attr(target_arch = "x86", path = "x86.rs")] +// #[cfg_attr(target_arch = "x86_64", path = "x86_64.rs")] +// mod imp; #[cfg(target_arch = "x86")] -#[path = "x86/mod.rs"] +#[path = "x86.rs"] +mod imp; + +#[cfg(target_arch = "x86_64")] +#[path = "x86_64.rs"] mod imp; diff --git a/src/arch/x86.rs b/src/arch/x86.rs new file mode 100644 index 0000000..5514f4d --- /dev/null +++ b/src/arch/x86.rs @@ -0,0 +1,105 @@ +// This file is part of libfringe, a low-level green threading library. +// Copyright (c) edef , +// whitequark +// See the LICENSE file included in this distribution. + +//! To understand the code in this file, keep in mind this fact: +//! * i686 SysV C ABI requires the stack to be aligned at function entry, +//! so that `%esp+4` is a multiple of 16. Aligned operands are a requirement +//! of SIMD instructions, and making this the responsibility of the caller +//! avoids having to maintain a frame pointer, which is necessary when +//! a function has to realign the stack from an unknown state. +//! * i686 SysV C ABI passes the first argument on the stack. This is +//! unfortunate, because unlike every other architecture we can't reuse +//! `swap` for the initial call, and so we use a trampoline. +use stack::Stack; + +#[derive(Debug)] +pub struct StackPointer(*mut usize); + +impl StackPointer { + unsafe fn new(stack: &Stack) -> StackPointer { + StackPointer(stack.top() as *mut usize) + } + + unsafe fn push(&mut self, val: usize) { + self.0 = self.0.offset(-1); + *self.0 = val + } +} + +pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackPointer { + let g: usize; + asm!( + r#" + # Push address of the trampoline. + call 1f + + # Pop function. + popl %ebx + # Push argument. + pushl %eax + # Call it. + call *%ebx + + 1: + # Pop address of the trampoline. + popl %eax + "# + : "={eax}" (g) + : + : "memory" + : "volatile" + ); + + let mut sp = StackPointer::new(stack); + sp.push(0); // alignment + sp.push(0); // alignment + sp.push(0); // alignment + sp.push(f as usize); // function + sp.push(g as usize); // trampoline + sp +} + +#[inline(always)] +pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer) -> usize { + let ret: usize; + asm!( + r#" + # Save frame pointer explicitly; LLVM doesn't spill it even if it is + # marked as clobbered. + pushl %ebp + # Push instruction pointer of the old context and switch to + # the new context. + call 1f + # Restore frame pointer. + popl %ebp + # Continue executing old context. + jmp 2f + + 1: + # Remember stack pointer of the old context, in case %rdx==%rsi. + movl %esp, %ebx + # Load stack pointer of the new context. + movl (%edi), %esp + # Save stack pointer of the old context. + movl %ebx, (%esi) + + # Pop instruction pointer of the new context (placed onto stack by + # the call above) and jump there; don't use `ret` to avoid return + # address mispredictions (~8ns on Ivy Bridge). + popl %ebx + jmpl *%ebx + 2: + "# + : "={eax}" (ret) + : "{eax}" (arg) + "{esi}" (old_sp) + "{edi}" (new_sp) + : "eax", "ebx", "ecx", "edx", "esi", "edi", //"ebp", "esp", + "mmx0", "mmx1", "mmx2", "mmx3", "mmx4", "mmx5", "mmx6", "mmx7", + "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", + "cc", "fpsr", "flags", "memory" + : "volatile"); + ret +} diff --git a/src/arch/x86/.syntastic_asm_config b/src/arch/x86/.syntastic_asm_config deleted file mode 100644 index 8c7133a..0000000 --- a/src/arch/x86/.syntastic_asm_config +++ /dev/null @@ -1 +0,0 @@ ---32 diff --git a/src/arch/x86/init.s b/src/arch/x86/init.s deleted file mode 100644 index 27e4088..0000000 --- a/src/arch/x86/init.s +++ /dev/null @@ -1,40 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. - -//! initialise a new context -//! arguments: -//! * eax: stack pointer -//! * ebx: function pointer -//! * ecx: data pointer -//! -//! return values: -//! * eax: new stack pointer - -// switch to the fresh stack -xchg %esp, %eax - -// save the data pointer and the function pointer, respectively -pushl %ecx -pushl %ebx - -// save the return address, control flow continues at label 1 -call 1f -// we arrive here once this context is reactivated (see swap.s) - -// restore the function pointer (the data pointer is the first argument, which lives at the top of the stack) -popl %eax - -// initialise the frame pointer -movl $$0, %ebp - -// call the function pointer with the data pointer (top of the stack is the first argument) -call *%eax - -// crash if it ever returns -ud2 - -1: - // save our neatly-setup new stack - xchg %esp, %eax - // back into Rust-land we go diff --git a/src/arch/x86/mod.rs b/src/arch/x86/mod.rs deleted file mode 100644 index 7441bdc..0000000 --- a/src/arch/x86/mod.rs +++ /dev/null @@ -1,33 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. -pub use self::common::*; - -macro_rules! init { - ($sp:expr, $f_ptr:expr, $tramp:expr) => { - asm!(include_str!("x86/init.s") - : "={eax}"($sp) - : "{eax}" ($sp), - "{ebx}" ($tramp), - "{ecx}" ($f_ptr) - : - : "volatile") - }; -} - -macro_rules! swap { - ($out_spp:expr, $in_spp:expr) => { - asm!(include_str!("x86/swap.s") - : - : "{eax}" ($out_spp), - "{ebx}" ($in_spp) - : "eax", "ebx", "ecx", "edx", "esi", "edi", //"ebp", "esp", - "mmx0", "mmx1", "mmx2", "mmx3", "mmx4", "mmx5", "mmx6", "mmx7", - "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7", - "cc", "fpsr", "eflags" - : "volatile") - }; -} - -#[path = "../x86_common.rs"] -mod common; diff --git a/src/arch/x86/swap.s b/src/arch/x86/swap.s deleted file mode 100644 index eed2b42..0000000 --- a/src/arch/x86/swap.s +++ /dev/null @@ -1,37 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. - -//! switch to a new context -//! arguments: -//! * eax: stack pointer out pointer -//! * ebx: stack pointer in pointer - -// save the frame pointer -pushl %ebp - -// save the return address to the stack, control flow continues at label 1 -call 1f -// we arrive here once this context is reactivated - -// restore the frame pointer -popl %ebp - -// and we merrily go on our way, back into Rust-land -jmp 2f - -1: - // retrieve the new stack pointer - movl (%eax), %edx - // save the old stack pointer - movl %esp, (%ebx) - // switch to the new stack pointer - movl %edx, %esp - - // jump into the new context (return to the call point) - // doing this instead of a straight `ret` is 8ns slower, - // presumably because the branch predictor tries to be clever about it - popl %eax - jmpl *%eax - -2: diff --git a/src/arch/x86_64.rs b/src/arch/x86_64.rs new file mode 100644 index 0000000..f2c8ee4 --- /dev/null +++ b/src/arch/x86_64.rs @@ -0,0 +1,92 @@ +// This file is part of libfringe, a low-level green threading library. +// Copyright (c) edef , +// whitequark +// See the LICENSE file included in this distribution. + +//! To understand the code in this file, keep in mind these two facts: +//! * x86_64 SysV C ABI has a "red zone": 128 bytes under the top of the stack +//! that is defined to be unmolested by signal handlers, interrupts, etc. +//! Leaf functions can use the red zone without adjusting rsp or rbp. +//! * x86_64 SysV C ABI requires the stack to be aligned at function entry, +//! so that (%rsp+8) is a multiple of 16. Aligned operands are a requirement +//! of SIMD instructions, and making this the responsibility of the caller +//! avoids having to maintain a frame pointer, which is necessary when +//! a function has to realign the stack from an unknown state. +//! * x86_64 SysV C ABI passes the first argument in %rdi. We also use %rdi +//! to pass a value while swapping context; this is an arbitrary choice +//! (we clobber all registers and could use any of them) but this allows us +//! to reuse the swap function to perform the initial call. + +use stack::Stack; + +#[derive(Debug)] +pub struct StackPointer(*mut usize); + +impl StackPointer { + unsafe fn new(stack: &Stack) -> StackPointer { + StackPointer(stack.top() as *mut usize) + } + + unsafe fn push(&mut self, val: usize) { + self.0 = self.0.offset(-1); + *self.0 = val + } +} + +pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackPointer { + let mut sp = StackPointer::new(stack); + sp.push(0); // alignment + sp.push(f as usize); + sp +} + +#[inline(always)] +pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer) -> usize { + let ret: usize; + asm!( + r#" + # Save frame pointer explicitly; LLVM doesn't spill it even if it is + # marked as clobbered. + pushq %rbp + # Push instruction pointer of the old context and switch to + # the new context. + call 1f + # Restore frame pointer. + popq %rbp + # Continue executing old context. + jmp 2f + + 1: + # Remember stack pointer of the old context, in case %rdx==%rsi. + movq %rsp, %rax + # Load stack pointer of the new context. + movq (%rdx), %rsp + # Save stack pointer of the old context. + movq %rax, (%rsi) + + # Pop instruction pointer of the new context (placed onto stack by + # the call above) and jump there; don't use `ret` to avoid return + # address mispredictions (~8ns on Ivy Bridge). + popq %rax + jmpq *%rax + 2: + "# + : "={rdi}" (ret) + : "{rdi}" (arg) + "{rsi}" (old_sp) + "{rdx}" (new_sp) + : "rax", "rbx", "rcx", "rdx", "rsi", "rdi", //"rbp", "rsp", + "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", + "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", "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"); + ret +} diff --git a/src/arch/x86_64/.syntastic_asm_config b/src/arch/x86_64/.syntastic_asm_config deleted file mode 100644 index efdaccc..0000000 --- a/src/arch/x86_64/.syntastic_asm_config +++ /dev/null @@ -1 +0,0 @@ ---64 diff --git a/src/arch/x86_64/init.s b/src/arch/x86_64/init.s deleted file mode 100644 index 54e76ce..0000000 --- a/src/arch/x86_64/init.s +++ /dev/null @@ -1,41 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. - -//! initialise a new context -//! arguments: -//! * rdi: stack pointer -//! * rsi: function pointer -//! * rdx: data pointer -//! -//! return values: -//! * rdi: new stack pointer - -// switch to the fresh stack -xchg %rsp, %rdi - -// save the function pointer the data pointer, respectively -pushq %rsi -pushq %rdx - -// save the return address, control flow continues at label 1 -call 1f -// we arrive here once this context is reactivated (see swap.s) - -// restore the data pointer and the function pointer, respectively -popq %rdi -popq %rax - -// initialise the frame pointer -movq $$0, %rbp - -// call the function pointer with the data pointer (rdi is the first argument) -call *%rax - -// crash if it ever returns -ud2 - -1: - // save our neatly-setup new stack - xchg %rsp, %rdi - // back into Rust-land we go diff --git a/src/arch/x86_64/mod.rs b/src/arch/x86_64/mod.rs deleted file mode 100644 index 0128691..0000000 --- a/src/arch/x86_64/mod.rs +++ /dev/null @@ -1,36 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. -pub use self::common::*; - -macro_rules! init { - ($sp:expr, $f_ptr:expr, $tramp:expr) => { - asm!(include_str!("x86_64/init.s") - : "={rdi}"($sp) - : "{rdi}" ($sp), - "{rsi}" ($tramp), - "{rdx}" ($f_ptr) - : - : "volatile"); - } -} - -macro_rules! swap { - ($out_spp:expr, $in_spp:expr) => { - asm!(include_str!("x86_64/swap.s") - : - : "{rdi}" ($out_spp) - "{rsi}" ($in_spp) - : "rax", "rbx", "rcx", "rdx", "rsi", "rdi", //"rbp", "rsp", - "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15", - "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", "fpsr", "eflags" - : "volatile"); - } -} - -#[path = "../x86_common.rs"] -mod common; diff --git a/src/arch/x86_64/swap.s b/src/arch/x86_64/swap.s deleted file mode 100644 index cffa170..0000000 --- a/src/arch/x86_64/swap.s +++ /dev/null @@ -1,44 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. - -//! switch to a new context -//! arguments: -//! * rdi: stack pointer out pointer -//! * rsi: stack pointer in pointer - -// make sure we leave the red zone alone -sub $$128, %rsp - -// save the frame pointer -pushq %rbp - -// save the return address to the stack, control flow continues at label 1 -call 1f -// we arrive here once this context is reactivated - -// restore the frame pointer -popq %rbp - -// give back the red zone -add $$128, %rsp - -// and we merrily go on our way, back into Rust-land -jmp 2f - -1: - // retrieve the new stack pointer - movq (%rsi), %rax - // save the old stack pointer - movq %rsp, (%rdi) - // switch to the new stack pointer - movq %rax, %rsp - - // jump into the new context (return to the call point) - // doing this instead of a straight `ret` is 8ns faster, - // presumably because the branch predictor tries - // to be clever about it otherwise - popq %rax - jmpq *%rax - -2: diff --git a/src/arch/x86_common.rs b/src/arch/x86_common.rs deleted file mode 100644 index de1f6b5..0000000 --- a/src/arch/x86_common.rs +++ /dev/null @@ -1,35 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) 2015, edef -// See the LICENSE file included in this distribution. -use void::Void; - -use stack::Stack; -use arch::common::{push, rust_trampoline}; - -pub const STACK_ALIGN: usize = 16; - -#[derive(Debug)] -pub struct Registers { - stack_pointer: *mut usize -} - -impl Registers { - #[inline] - pub unsafe fn new(stack: &mut S, f: F) -> Registers - where S: Stack, F: FnOnce() -> Void - { - let mut sp = stack.top() as *mut usize; - let f_ptr = push(&mut sp, f); - - init!(sp, f_ptr, rust_trampoline:: as unsafe extern "C" fn(*const F) -> !); - - Registers { - stack_pointer: sp, - } - } - - #[inline(always)] - pub unsafe fn swap(out_regs: *mut Registers, in_regs: *const Registers) { - swap!(&mut (*out_regs).stack_pointer, &(*in_regs).stack_pointer); - } -} diff --git a/src/context.rs b/src/context.rs index 0c38e2c..98609d8 100644 --- a/src/context.rs +++ b/src/context.rs @@ -1,13 +1,10 @@ // This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef +// Copyright (c) edef , +// whitequark // See the LICENSE file included in this distribution. -use core::marker::PhantomData; - -use void::Void; - -use arch::Registers; use stack; -use debug::StackId; +use debug; +use arch; /// Context is the heart of libfringe. /// A context represents a saved thread of execution, along with a stack. @@ -17,44 +14,41 @@ use debug::StackId; /// Every operation is unsafe, because libfringe can't make any guarantees /// about the state of the context. #[derive(Debug)] -pub struct Context<'a, Stack: stack::Stack> { - regs: Registers, - _stack_id: StackId, - stack: Stack, - _ref: PhantomData<&'a ()> +pub struct Context { + stack: Stack, + stack_id: debug::StackId, + stack_ptr: arch::StackPointer } -unsafe impl<'a, Stack> Send for Context<'a, Stack> +unsafe impl Send for Context where Stack: stack::Stack + Send {} -impl<'a, Stack> Context<'a, Stack> where Stack: stack::Stack { - /// Create a new Context. When it is swapped into, - /// it will call the passed closure. - #[inline] - pub unsafe fn new(mut stack: Stack, f: F) -> Context<'a, Stack> - where F: FnOnce() -> Void + Send + 'a { - let stack_id = StackId::register(&mut stack); - let regs = Registers::new(&mut stack, f); +impl Context where Stack: stack::Stack { + /// Create a new Context. When it is swapped into, it will call + /// `f(arg)`, where `arg` is the argument passed to `swap`. + pub unsafe fn new(stack: Stack, f: unsafe extern "C" fn(usize) -> !) -> Context { + let stack_id = debug::StackId::register(&stack); + let stack_ptr = arch::init(&stack, f); Context { - regs: regs, - _stack_id: stack_id, - stack: stack, - _ref: PhantomData + stack: stack, + stack_id: stack_id, + stack_ptr: stack_ptr } } /// Unwrap the context, returning the stack it contained. - #[inline] pub unsafe fn unwrap(self) -> Stack { self.stack } } -impl<'i, InStack> Context<'i, InStack> where InStack: stack::Stack { +impl Context where OldStack: stack::Stack { /// Switch to in_ctx, saving the current thread of execution to out_ctx. #[inline(always)] - pub unsafe fn swap<'o, OutStack>(out_ctx: *mut Context<'o, OutStack>, in_ctx: *const Context<'i, InStack>) - where OutStack: stack::Stack { - Registers::swap(&mut (*out_ctx).regs, &(*in_ctx).regs) + pub unsafe fn swap(old_ctx: *mut Context, + new_ctx: *const Context, + arg: usize) -> usize + where NewStack: stack::Stack { + arch::swap(arg, &mut (*old_ctx).stack_ptr, &(*new_ctx).stack_ptr) } } diff --git a/src/debug/mod.rs b/src/debug/mod.rs index ba0d703..4fe48d7 100644 --- a/src/debug/mod.rs +++ b/src/debug/mod.rs @@ -14,7 +14,7 @@ mod imp { pub struct StackId; /// No-op since no valgrind impl StackId { - pub fn register(_stack: &mut Stack) -> StackId { + pub fn register(_stack: &Stack) -> StackId { StackId } } diff --git a/src/debug/valgrind.rs b/src/debug/valgrind.rs index 707c598..59dff44 100644 --- a/src/debug/valgrind.rs +++ b/src/debug/valgrind.rs @@ -11,7 +11,7 @@ pub struct StackId(self::valgrind::Value); impl StackId { #[inline(always)] - pub fn register(stack: &mut Stack) -> StackId { + pub fn register(stack: &Stack) -> StackId { StackId(stack_register(stack.limit(), stack.top())) } } diff --git a/src/lib.rs b/src/lib.rs index 8bc64e4..e986317 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,26 +7,17 @@ //! libfringe is a low-level green threading library. //! It provides only a context-swapping mechanism. -#[cfg(test)] -#[macro_use] -extern crate std; - -extern crate void; - pub use context::Context; pub use stack::Stack; -#[cfg(feature = "os")] +#[cfg(any(unix, windows))] pub use os::Stack as OsStack; mod context; mod stack; -#[cfg(feature = "os")] +#[cfg(any(unix, windows))] mod os; mod arch; mod debug; - -#[cfg(not(test))] -mod std { pub use core::*; } diff --git a/src/os/mod.rs b/src/os/mod.rs index ce0eb2d..56ef6bf 100644 --- a/src/os/mod.rs +++ b/src/os/mod.rs @@ -47,13 +47,13 @@ impl Stack { } impl stack::Stack for Stack { - fn top(&mut self) -> *mut u8 { + fn top(&self) -> *mut u8 { unsafe { self.ptr.offset(self.len as isize) } } - fn limit(&self) -> *const u8 { + fn limit(&self) -> *mut u8 { unsafe { self.ptr.offset(sys::page_size() as isize) } diff --git a/src/stack.rs b/src/stack.rs index 1f92ca2..116542d 100644 --- a/src/stack.rs +++ b/src/stack.rs @@ -8,9 +8,9 @@ pub trait Stack { /// Returns the top of the stack. /// On all modern architectures, the stack grows downwards, /// so this is the highest address. - fn top(&mut self) -> *mut u8; + fn top(&self) -> *mut u8; /// Returns the bottom of the stack. /// On all modern architectures, the stack grows downwards, /// so this is the lowest address. - fn limit(&self) -> *const u8; + fn limit(&self) -> *mut u8; } diff --git a/tests/basic.rs b/tests/basic.rs deleted file mode 100644 index cad24a9..0000000 --- a/tests/basic.rs +++ /dev/null @@ -1,26 +0,0 @@ -// This file is part of libfringe, a low-level green threading library. -// Copyright (c) edef -// See the LICENSE file included in this distribution. -#![feature(thread_local)] -extern crate fringe; -use fringe::Context; - -#[thread_local] -static mut ctx_slot: *mut Context<'static, fringe::OsStack> = 0 as *mut Context<_>; - -#[test] -fn main() { - unsafe { - let stack = fringe::OsStack::new(4 << 20).unwrap(); - - let mut ctx = Context::new(stack, move || { - println!("it's alive!"); - Context::swap(ctx_slot, ctx_slot); - panic!("Do not come back!") - }); - - ctx_slot = &mut ctx; - - Context::swap(ctx_slot, ctx_slot); - } -} diff --git a/tests/context.rs b/tests/context.rs new file mode 100644 index 0000000..b922448 --- /dev/null +++ b/tests/context.rs @@ -0,0 +1,60 @@ +// This file is part of libfringe, a low-level green threading library. +// Copyright (c) edef , +// whitequark +// See the LICENSE file included in this distribution. +#![feature(thread_local)] +extern crate simd; +extern crate fringe; + +use fringe::Context; + +#[thread_local] +static mut ctx_slot: *mut Context = 0 as *mut Context<_>; + +#[test] +fn context() { + unsafe extern "C" fn adder(arg: usize) -> ! { + println!("it's alive! arg: {}", arg); + let arg = Context::swap(ctx_slot, ctx_slot, arg + 1); + println!("still alive! arg: {}", arg); + Context::swap(ctx_slot, ctx_slot, arg + 1); + panic!("i should be dead"); + } + + unsafe { + let stack = fringe::OsStack::new(4 << 20).unwrap(); + let mut ctx = Context::new(stack, adder); + ctx_slot = &mut ctx; + + let ret = Context::swap(ctx_slot, ctx_slot, 10); + assert_eq!(ret, 11); + let ret = Context::swap(ctx_slot, ctx_slot, 50); + assert_eq!(ret, 51); + } +} + +#[test] +fn simd() { + unsafe extern "C" fn permuter(arg: usize) -> ! { + // This will crash if the stack is not aligned properly. + let x = simd::i32x4::splat(arg as i32); + let y = x * x; + println!("simd result: {:?}", y); + Context::swap(ctx_slot, ctx_slot, 0); + // And try again after a context switch. + let x = simd::i32x4::splat(arg as i32); + let y = x * x; + println!("simd result: {:?}", y); + Context::swap(ctx_slot, ctx_slot, 0); + panic!("i should be dead"); + } + + unsafe { + let stack = fringe::OsStack::new(4 << 20).unwrap(); + let mut ctx = Context::new(stack, permuter); + ctx_slot = &mut ctx; + + Context::swap(ctx_slot, ctx_slot, 10); + Context::swap(ctx_slot, ctx_slot, 20); + } +} diff --git a/tests/fpe.rs b/tests/fpe.rs index 90e5868..ec440c0 100644 --- a/tests/fpe.rs +++ b/tests/fpe.rs @@ -12,7 +12,7 @@ use fringe::Context; use test::black_box; #[thread_local] -static mut ctx_slot: *mut Context<'static, fringe::OsStack> = 0 as *mut Context<_>; +static mut ctx_slot: *mut Context = 0 as *mut Context<_>; const FE_DIVBYZERO: i32 = 0x4; extern { @@ -22,21 +22,20 @@ extern { #[test] #[ignore] fn fpe() { + unsafe extern "C" fn universe_destroyer(_arg: usize) -> ! { + loop { + println!("{:?}", 1.0/black_box(0.0)); + Context::swap(ctx_slot, ctx_slot, 0); + } + } + unsafe { let stack = fringe::OsStack::new(4 << 20).unwrap(); - - let mut ctx = Context::new(stack, move || { - println!("it's alive!"); - loop { - println!("{:?}", 1.0/black_box(0.0)); - Context::swap(ctx_slot, ctx_slot); - } - }); - + let mut ctx = Context::new(stack, universe_destroyer); ctx_slot = &mut ctx; - Context::swap(ctx_slot, ctx_slot); + Context::swap(ctx_slot, ctx_slot, 0); feenableexcept(FE_DIVBYZERO); - Context::swap(ctx_slot, ctx_slot); + Context::swap(ctx_slot, ctx_slot, 0); } }