Add more examples to README.
This also renames some functions for a mild increase in backtrace clarity.
This commit is contained in:
parent
f34ddc6805
commit
7d5075edc2
73
README.md
73
README.md
@ -25,6 +25,26 @@ It also provides the necessary low-level building blocks:
|
||||
libfringe emphasizes safety and correctness, and goes to great lengths to never
|
||||
violate the platform ABI.
|
||||
|
||||
## Usage example
|
||||
|
||||
```rust
|
||||
extern crate fringe;
|
||||
|
||||
use fringe::{OsStack, Generator};
|
||||
|
||||
fn main() {
|
||||
let stack = OsStack::new(1 << 16).unwrap();
|
||||
let mut gen = Generator::new(stack, move |yielder, ()| {
|
||||
for i in 1..4 { yielder.generate(i) }
|
||||
});
|
||||
|
||||
println!("{:?}", gen.resume(())); // Some(1)
|
||||
println!("{:?}", gen.resume(())); // Some(2)
|
||||
println!("{:?}", gen.resume(())); // Some(3)
|
||||
println!("{:?}", gen.resume(())); // None
|
||||
}
|
||||
```
|
||||
|
||||
## Performance
|
||||
|
||||
libfringe does context switches in 3ns flat on x86 and x86_64!
|
||||
@ -33,6 +53,59 @@ libfringe does context switches in 3ns flat on x86 and x86_64!
|
||||
test swap ... bench: 6 ns/iter (+/- 0)
|
||||
```
|
||||
|
||||
## Debuggability
|
||||
|
||||
Uniquely among libraries implementing context switching, libfringe ensures that the call stack
|
||||
does not abruptly end at the boundary of a generator. Let's consider this buggy code:
|
||||
|
||||
```rust
|
||||
extern crate fringe;
|
||||
|
||||
use fringe::{OsStack, Generator};
|
||||
|
||||
fn main() {
|
||||
let stack = OsStack::new(1 << 16).unwrap();
|
||||
let mut gen = Generator::new(stack, move |yielder, mut index| {
|
||||
let values = [1, 2, 3];
|
||||
loop { index = yielder.generate(values[index]) }
|
||||
});
|
||||
|
||||
println!("{:?}", gen.resume(5));
|
||||
}
|
||||
```
|
||||
|
||||
It crashes with the following backtrace (redacted for clarity):
|
||||
|
||||
```
|
||||
thread 'main' panicked at 'assertion failed: index < self.len()', ../src/libcore/slice.rs:531
|
||||
stack backtrace:
|
||||
[... core::panicking internals ...]
|
||||
9: 0x559ee50f677b - core::panicking::panic::hbfac80217e56ecbe
|
||||
10: 0x559ee50b6b4c - core::slice::_<impl core..ops..Index<usize> for [T]>::index::hcb117ddcc7cf2f33
|
||||
at .../src/libcore/slice.rs:21
|
||||
11: 0x559ee50b7288 - crash_test::main::_{{closure}}::hc7da249d76d51364
|
||||
at .../crash_test.rs:9
|
||||
12: 0x559ee50b6f23 - _<fringe..generator..Generator<Input, Output, Stack>>::unsafe_new::generator_wrapper::ha2da172d4f041d38
|
||||
at .../libfringe/src/generator.rs:94
|
||||
13: 0x559ee50b76d3 - fringe::arch::imp::init::trampoline_2::hdb11eb4bdafcdeb9
|
||||
at .../libfringe/src/arch/x86_64.rs:71
|
||||
14: 0x559ee50b76c4 - fringe::arch::imp::init::trampoline_1::h6b071b2a8ea6aab3
|
||||
at .../libfringe/src/arch/x86_64.rs:43
|
||||
15: 0x559ee50b7098 - _<fringe..generator..Generator<Input, Output, Stack>>::resume::h8d2b90d386543e29
|
||||
at .../libfringe/src/arch/x86_64.rs:131
|
||||
at .../libfringe/src/context.rs:52
|
||||
at .../libfringe/src/generator.rs:129
|
||||
16: 0x559ee50b71c8 - crash_test::main::hfc5e04bc99de7a6a
|
||||
at .../crash_test.rs:12
|
||||
[... standard library startup internals ...]
|
||||
```
|
||||
|
||||
Similarly, debuggers, profilers, and all other tools using the DWARF debug information have
|
||||
full insight into the call stacks.
|
||||
|
||||
Note that the stack should be deep enough for the panic machinery to store its state—at any point
|
||||
there should be at least 8 KiB of free stack space, or panicking will result in a segfault.
|
||||
|
||||
## Limitations
|
||||
|
||||
The only architectures currently supported are x86 and x86_64.
|
||||
|
@ -35,7 +35,7 @@ pub struct StackPointer(*mut usize);
|
||||
|
||||
pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackPointer {
|
||||
#[naked]
|
||||
unsafe extern "C" fn init_trampoline_1() {
|
||||
unsafe extern "C" fn trampoline_1() {
|
||||
asm!(
|
||||
r#"
|
||||
# gdb has a hardcoded check that rejects backtraces where frame addresses
|
||||
@ -59,11 +59,11 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackP
|
||||
.Lend:
|
||||
.size __morestack, .Lend-__morestack
|
||||
"#
|
||||
: : "s" (init_trampoline_2 as usize) : "memory" : "volatile")
|
||||
: : "s" (trampoline_2 as usize) : "memory" : "volatile")
|
||||
}
|
||||
|
||||
#[naked]
|
||||
unsafe extern "C" fn init_trampoline_2() {
|
||||
unsafe extern "C" fn trampoline_2() {
|
||||
asm!(
|
||||
r#"
|
||||
# Set up the second part of our DWARF CFI.
|
||||
@ -87,7 +87,7 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackP
|
||||
let mut sp = StackPointer(stack.top() as *mut usize);
|
||||
push(&mut sp, 0xdead0cfa); // CFA slot
|
||||
push(&mut sp, f as usize); // function
|
||||
push(&mut sp, init_trampoline_1 as usize);
|
||||
push(&mut sp, trampoline_1 as usize);
|
||||
push(&mut sp, 0xdeadbbbb); // saved %ebp
|
||||
sp
|
||||
}
|
||||
@ -99,7 +99,7 @@ pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer,
|
||||
let new_cfa = (new_stack.top() as *mut usize).offset(-1);
|
||||
|
||||
#[naked]
|
||||
unsafe extern "C" fn swap_trampoline() {
|
||||
unsafe extern "C" fn trampoline() {
|
||||
asm!(
|
||||
r#"
|
||||
# Save frame pointer explicitly; the unwinder uses it to find CFA of
|
||||
@ -135,7 +135,7 @@ pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer,
|
||||
call ${1:c}
|
||||
"#
|
||||
: "={eax}" (ret)
|
||||
: "s" (swap_trampoline as usize)
|
||||
: "s" (trampoline as usize)
|
||||
"{eax}" (arg)
|
||||
"{esi}" (old_sp)
|
||||
"{edx}" (new_sp)
|
||||
|
@ -39,7 +39,7 @@ pub struct StackPointer(*mut usize);
|
||||
|
||||
pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackPointer {
|
||||
#[naked]
|
||||
unsafe extern "C" fn init_trampoline_1() {
|
||||
unsafe extern "C" fn trampoline_1() {
|
||||
asm!(
|
||||
r#"
|
||||
# gdb has a hardcoded check that rejects backtraces where frame addresses
|
||||
@ -63,11 +63,11 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackP
|
||||
.Lend:
|
||||
.size __morestack, .Lend-__morestack
|
||||
"#
|
||||
: : "s" (init_trampoline_2 as usize) : "memory" : "volatile")
|
||||
: : "s" (trampoline_2 as usize) : "memory" : "volatile")
|
||||
}
|
||||
|
||||
#[naked]
|
||||
unsafe extern "C" fn init_trampoline_2() {
|
||||
unsafe extern "C" fn trampoline_2() {
|
||||
asm!(
|
||||
r#"
|
||||
# Set up the second part of our DWARF CFI.
|
||||
@ -89,7 +89,7 @@ pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackP
|
||||
push(&mut sp, 0xdeaddeaddead0cfa); // CFA slot
|
||||
push(&mut sp, 0 as usize); // alignment
|
||||
push(&mut sp, f as usize); // function
|
||||
push(&mut sp, init_trampoline_1 as usize);
|
||||
push(&mut sp, trampoline_1 as usize);
|
||||
push(&mut sp, 0xdeaddeaddeadbbbb); // saved %rbp
|
||||
sp
|
||||
}
|
||||
@ -101,7 +101,7 @@ pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer,
|
||||
let new_cfa = (new_stack.top() as *mut usize).offset(-1);
|
||||
|
||||
#[naked]
|
||||
unsafe extern "C" fn swap_trampoline() {
|
||||
unsafe extern "C" fn trampoline() {
|
||||
asm!(
|
||||
r#"
|
||||
# Save frame pointer explicitly; the unwinder uses it to find CFA of
|
||||
@ -137,7 +137,7 @@ pub unsafe fn swap(arg: usize, old_sp: &mut StackPointer, new_sp: &StackPointer,
|
||||
call ${1:c}
|
||||
"#
|
||||
: "={rdi}" (ret)
|
||||
: "s" (swap_trampoline as usize)
|
||||
: "s" (trampoline as usize)
|
||||
"{rdi}" (arg)
|
||||
"{rsi}" (old_sp)
|
||||
"{rdx}" (new_sp)
|
||||
|
Loading…
Reference in New Issue
Block a user