diff --git a/README.md b/README.md index c159f58..94685a0 100644 --- a/README.md +++ b/README.md @@ -8,43 +8,42 @@ # libfringe -libfringe is a library implementing lightweight context switches, +libfringe is a library implementing safe, lightweight context switches, without relying on kernel services. It can be used in hosted environments (using `std`) as well as on bare metal (using `core`). -It provides high-level, safe abstractions: +It provides the following safe abstractions: * an implementation of internal iterators, also known as generators, [Generator](https://edef1c.github.io/libfringe/fringe/generator/struct.Generator.html). -It also provides low-level, unsafe building blocks: +It also provides the necessary low-level building blocks: * a trait that can be implemented by stack allocators, [Stack](https://edef1c.github.io/libfringe/fringe/struct.Stack.html); * a stack allocator based on anonymous memory mappings with guard pages, [OsStack](https://edef1c.github.io/libfringe/fringe/struct.OsStack.html). +libfringe emphasizes safety and correctness, and goes to great lengths to never +violate the platform ABI. + ## Performance - libfringe does context switches in 2.5ns flat on x86_64! -``` -test swap ... bench: 5 ns/iter (+/- 1) -``` - - …and on x86: +libfringe does context switches in 3ns flat on x86 and x86_64! ``` -test swap ... bench: 5 ns/iter (+/- 1) +test swap ... bench: 6 ns/iter (+/- 0) ``` ## Limitations - libfringe currently doesn't work on anything but x86 and x86_64, - and is untested on anything but Linux. +The only architectures currently supported are x86 and x86_64. +Windows is not supported (see [explanation](#windows-compatibility) below). ## Installation - libfringe is a [Cargo](https://crates.io) package. - It's not stable software yet, so you'll have to use it as a git dependency. - Add this to your `Cargo.toml`: +libfringe is a [Cargo](https://crates.io) package. +It's not stable software yet, so you'll have to use it as a git dependency. +Add this to your `Cargo.toml`: + ```toml [dependencies.fringe] git = "https://github.com/edef1c/libfringe.git" @@ -52,11 +51,64 @@ git = "https://github.com/edef1c/libfringe.git" ### Feature flags - [Cargo's feature flags]: http://doc.crates.io/manifest.html#the-[features]-section - libfringe provides some optional features through [Cargo's feature flags]. - Currently, all of them are enabled by default. +[Cargo's feature flags]: http://doc.crates.io/manifest.html#the-[features]-section +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. +[Valgrind]: http://valgrind.org +[Valgrind] integration. libfringe will register context stacks with Valgrind. + +## Internals + +libfringe uses two key implementation techniques. + +### Compiler-assisted register spilling + +Traditionally, libraries implementing context switches in userspace have to spill all callee-saved +registers. On the other hand, libfringe fully inlines calls to every function that eventually +results in a context switch, and uses an inline assembly statement marking every register as +clobbered to implement the context switching itself. + +As a result, only minimal work needs to be performed in the context switching code (LLVM does not +support spilling the frame pointer), which is especially important on architectures with lots +of callee-saved registers. + +### Call stack splicing + +Non-Windows platforms use [DWARF][] for both stack unwinding and debugging. DWARF call frame +information is very generic to be ABI-agnostic—it defines a bytecode that describes the actions +that need to be performed to simulate returning from a function. libfringe uses this bytecode +to specify that, after the generator function has returned, execution continues at the point +where the generator function was resumed the last time. + +[dwarf]: http://dwarfstd.org + +## Windows compatibility + +As was said, libfringe emphasizes following the platform ABI. On Windows, the platform ABI +does not allow moving the stack pointer from the range designated by the OS during thread creation. +Therefore, the technique used by libfringe on *nix platforms is not applicable, and libfringe +does not provide Windows support. + +You might ask, "but what about [mioco][]?" The mioco library uses the [context][] library to +implement context switches, which is little more than a wrapper of [boost::context][boostcontext]. +The boost::context library changes undocumented fields in the [TIB][] during every context switch +to try and work around the restrictions placed by the Windows platform ABI. This has +[failed before][tibfail] and it is bound fail again, breaking existing code that uses +boost::context in unexpected and complicated ways. The authors of libfringe consider this +unacceptable. + +[mioco]: https://github.com/dpc/mioco +[context]: https://github.com/zonyitoo/context-rs +[boostcontext]: http://www.boost.org/doc/libs/1_60_0/libs/context/doc/html/context/overview.html +[TIB]: https://en.wikipedia.org/wiki/Win32_Thread_Information_Block +[tibfail]: https://svn.boost.org/trac/boost/ticket/8544 + +The only supported way to implement user-mode context switching on Windows is to use [fibers][]. +There are no reasons the safe abstractions provided by libfringe could not be implemented on top +of that; it is simply not yet done. This should be straightforward and an implementation is +welcome. + +[fibers]: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682661(v=vs.85).aspx diff --git a/src/lib.rs b/src/lib.rs index 8528ba7..c3c10c3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,16 +7,16 @@ #![cfg_attr(target_arch = "x86_64", feature(naked_functions, core_intrinsics))] #![no_std] -//! libfringe is a library implementing lightweight context switches, +//! libfringe is a library implementing safe, lightweight context switches, //! without relying on kernel services. It can be used in hosted environments //! (using `std`) as well as on bare metal (using `core`). //! -//! It provides high-level, safe abstractions: +//! It provides the following safe abstractions: //! //! * an implementation of internal iterators, also known as generators, //! [Generator](generator/struct.Generator.html). //! -//! It also provides low-level, unsafe building blocks: +//! It also provides the necessary low-level building blocks: //! //! * a trait that can be implemented by stack allocators, //! [Stack](struct.Stack.html); @@ -31,7 +31,7 @@ pub use stack::Stack; pub use stack::GuardedStack; pub use generator::Generator; -#[cfg(any(unix, windows))] +#[cfg(unix)] pub use os::Stack as OsStack; mod arch; @@ -41,5 +41,5 @@ mod stack; mod context; pub mod generator; -#[cfg(any(unix, windows))] +#[cfg(unix)] mod os; diff --git a/src/os/sys/unix.rs b/src/os/sys.rs similarity index 76% rename from src/os/sys/unix.rs rename to src/os/sys.rs index dfd629c..ca25c35 100644 --- a/src/os/sys/unix.rs +++ b/src/os/sys.rs @@ -3,20 +3,13 @@ // See the LICENSE file included in this distribution. extern crate std; extern crate libc; + +use self::std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering}; +use self::std::ptr; use self::std::io::Error as IoError; use self::libc::{c_void, c_int, size_t}; use self::libc::{mmap, mprotect, munmap}; use self::libc::MAP_FAILED; -use super::page_size; - -use core::ptr; - -#[cold] -pub fn sys_page_size() -> usize { - unsafe { - libc::sysconf(libc::_SC_PAGESIZE) as usize - } -} const GUARD_PROT: c_int = libc::PROT_NONE; const STACK_PROT: c_int = libc::PROT_READ @@ -55,3 +48,22 @@ pub unsafe fn unmap_stack(ptr: *mut u8, len: usize) -> Result<(), IoError> { Err(IoError::last_os_error()) } } + +pub fn page_size() -> usize { + #[cold] + pub fn sys_page_size() -> usize { + unsafe { + libc::sysconf(libc::_SC_PAGESIZE) as usize + } + } + + static PAGE_SIZE_CACHE: AtomicUsize = ATOMIC_USIZE_INIT; + match PAGE_SIZE_CACHE.load(Ordering::Relaxed) { + 0 => { + let page_size = sys_page_size(); + PAGE_SIZE_CACHE.store(page_size, Ordering::Relaxed); + page_size + } + page_size => page_size + } +} diff --git a/src/os/sys/mod.rs b/src/os/sys/mod.rs deleted file mode 100644 index 07aa4f4..0000000 --- a/src/os/sys/mod.rs +++ /dev/null @@ -1,27 +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::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering}; - -pub use self::imp::{map_stack, protect_stack, unmap_stack}; -use self::imp::sys_page_size; - -#[cfg(unix)] -#[path = "unix.rs"] -mod imp; - -#[cfg(windows)] -#[path = "windows.rs"] -mod imp; - -static PAGE_SIZE_CACHE: AtomicUsize = ATOMIC_USIZE_INIT; -pub fn page_size() -> usize { - match PAGE_SIZE_CACHE.load(Ordering::Relaxed) { - 0 => { - let page_size = sys_page_size(); - PAGE_SIZE_CACHE.store(page_size, Ordering::Relaxed); - page_size - } - page_size => page_size - } -} diff --git a/src/os/sys/windows.rs b/src/os/sys/windows.rs deleted file mode 100644 index e2ba7ef..0000000 --- a/src/os/sys/windows.rs +++ /dev/null @@ -1,49 +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. -extern crate std; -extern crate winapi; -extern crate kernel32; -use self::std::io::Error as IoError; -use self::std::{mem, ptr}; -use self::winapi::basetsd::SIZE_T; -use self::winapi::minwindef::{DWORD, LPVOID}; -use self::winapi::winnt::{MEM_COMMIT, MEM_RESERVE, MEM_RELEASE, PAGE_READWRITE, PAGE_NOACCESS}; -use super::page_size; - -#[cold] -pub fn sys_page_size() -> usize { - unsafe { - let mut info = mem::zeroed(); - kernel32::GetSystemInfo(&mut info); - info.dwPageSize as usize - } -} - -pub unsafe fn map_stack(len: usize) -> Result<*mut u8, IoError> { - let ptr = kernel32::VirtualAlloc(ptr::null_mut(), len as SIZE_T, - MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - if ptr == ptr::null_mut() { - Err(IoError::last_os_error()) - } else { - Ok(ptr as *mut u8) - } -} - -pub unsafe fn protect_stack(ptr: *mut u8) -> Result<(), IoError> { - let mut old_prot: DWORD = 0; - if kernel32::VirtualProtect(ptr as LPVOID, page_size() as SIZE_T, - PAGE_NOACCESS, &mut old_prot) == 0 { - Err(IoError::last_os_error()) - } else { - Ok(()) - } -} - -pub unsafe fn unmap_stack(ptr: *mut u8, _len: usize) -> Result<(), IoError> { - if kernel32::VirtualFree(ptr as LPVOID, 0, MEM_RELEASE) == 0 { - Err(IoError::last_os_error()) - } else { - Ok(()) - } -}