forked from M-Labs/libfringe
Clarify the Windows situation.
Also, remove the Windows OsStack implementation. It will not ever be useful in libfringe, as even if we add support for Windows using fibers, the OS allocates stacks for fibers and we needn't do it manually.
This commit is contained in:
parent
a5d3430e63
commit
302ceef10a
76
README.md
76
README.md
|
@ -8,43 +8,42 @@
|
||||||
|
|
||||||
# libfringe
|
# 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
|
without relying on kernel services. It can be used in hosted environments
|
||||||
(using `std`) as well as on bare metal (using `core`).
|
(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,
|
* an implementation of internal iterators, also known as generators,
|
||||||
[Generator](https://edef1c.github.io/libfringe/fringe/generator/struct.Generator.html).
|
[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,
|
* a trait that can be implemented by stack allocators,
|
||||||
[Stack](https://edef1c.github.io/libfringe/fringe/struct.Stack.html);
|
[Stack](https://edef1c.github.io/libfringe/fringe/struct.Stack.html);
|
||||||
* a stack allocator based on anonymous memory mappings with guard pages,
|
* a stack allocator based on anonymous memory mappings with guard pages,
|
||||||
[OsStack](https://edef1c.github.io/libfringe/fringe/struct.OsStack.html).
|
[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
|
## Performance
|
||||||
|
|
||||||
libfringe does context switches in 2.5ns flat on x86_64!
|
libfringe does context switches in 3ns flat on x86 and x86_64!
|
||||||
```
|
|
||||||
test swap ... bench: 5 ns/iter (+/- 1)
|
|
||||||
```
|
|
||||||
|
|
||||||
…and on x86:
|
|
||||||
|
|
||||||
```
|
```
|
||||||
test swap ... bench: 5 ns/iter (+/- 1)
|
test swap ... bench: 6 ns/iter (+/- 0)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
|
|
||||||
libfringe currently doesn't work on anything but x86 and x86_64,
|
The only architectures currently supported are x86 and x86_64.
|
||||||
and is untested on anything but Linux.
|
Windows is not supported (see [explanation](#windows-compatibility) below).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
libfringe is a [Cargo](https://crates.io) package.
|
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.
|
It's not stable software yet, so you'll have to use it as a git dependency.
|
||||||
Add this to your `Cargo.toml`:
|
Add this to your `Cargo.toml`:
|
||||||
|
|
||||||
```toml
|
```toml
|
||||||
[dependencies.fringe]
|
[dependencies.fringe]
|
||||||
git = "https://github.com/edef1c/libfringe.git"
|
git = "https://github.com/edef1c/libfringe.git"
|
||||||
|
@ -60,3 +59,56 @@ git = "https://github.com/edef1c/libfringe.git"
|
||||||
|
|
||||||
[Valgrind]: http://valgrind.org
|
[Valgrind]: http://valgrind.org
|
||||||
[Valgrind] integration. libfringe will register context stacks with Valgrind.
|
[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
|
||||||
|
|
10
src/lib.rs
10
src/lib.rs
|
@ -7,16 +7,16 @@
|
||||||
#![cfg_attr(target_arch = "x86_64", feature(naked_functions, core_intrinsics))]
|
#![cfg_attr(target_arch = "x86_64", feature(naked_functions, core_intrinsics))]
|
||||||
#![no_std]
|
#![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
|
//! without relying on kernel services. It can be used in hosted environments
|
||||||
//! (using `std`) as well as on bare metal (using `core`).
|
//! (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,
|
//! * an implementation of internal iterators, also known as generators,
|
||||||
//! [Generator](generator/struct.Generator.html).
|
//! [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,
|
//! * a trait that can be implemented by stack allocators,
|
||||||
//! [Stack](struct.Stack.html);
|
//! [Stack](struct.Stack.html);
|
||||||
|
@ -31,7 +31,7 @@ pub use stack::Stack;
|
||||||
pub use stack::GuardedStack;
|
pub use stack::GuardedStack;
|
||||||
pub use generator::Generator;
|
pub use generator::Generator;
|
||||||
|
|
||||||
#[cfg(any(unix, windows))]
|
#[cfg(unix)]
|
||||||
pub use os::Stack as OsStack;
|
pub use os::Stack as OsStack;
|
||||||
|
|
||||||
mod arch;
|
mod arch;
|
||||||
|
@ -41,5 +41,5 @@ mod stack;
|
||||||
mod context;
|
mod context;
|
||||||
pub mod generator;
|
pub mod generator;
|
||||||
|
|
||||||
#[cfg(any(unix, windows))]
|
#[cfg(unix)]
|
||||||
mod os;
|
mod os;
|
||||||
|
|
|
@ -3,20 +3,13 @@
|
||||||
// See the LICENSE file included in this distribution.
|
// See the LICENSE file included in this distribution.
|
||||||
extern crate std;
|
extern crate std;
|
||||||
extern crate libc;
|
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::std::io::Error as IoError;
|
||||||
use self::libc::{c_void, c_int, size_t};
|
use self::libc::{c_void, c_int, size_t};
|
||||||
use self::libc::{mmap, mprotect, munmap};
|
use self::libc::{mmap, mprotect, munmap};
|
||||||
use self::libc::MAP_FAILED;
|
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 GUARD_PROT: c_int = libc::PROT_NONE;
|
||||||
const STACK_PROT: c_int = libc::PROT_READ
|
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())
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,27 +0,0 @@
|
||||||
// This file is part of libfringe, a low-level green threading library.
|
|
||||||
// Copyright (c) edef <edef@edef.eu>
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
// This file is part of libfringe, a low-level green threading library.
|
|
||||||
// Copyright (c) edef <edef@edef.eu>
|
|
||||||
// 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(())
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue