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
|
libfringe emphasizes safety and correctness, and goes to great lengths to never
|
||||||
violate the platform ABI.
|
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
|
## Performance
|
||||||
|
|
||||||
libfringe does context switches in 3ns flat on x86 and x86_64!
|
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)
|
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
|
## Limitations
|
||||||
|
|
||||||
The only architectures currently supported are x86 and x86_64.
|
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 {
|
pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackPointer {
|
||||||
#[naked]
|
#[naked]
|
||||||
unsafe extern "C" fn init_trampoline_1() {
|
unsafe extern "C" fn trampoline_1() {
|
||||||
asm!(
|
asm!(
|
||||||
r#"
|
r#"
|
||||||
# gdb has a hardcoded check that rejects backtraces where frame addresses
|
# 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:
|
.Lend:
|
||||||
.size __morestack, .Lend-__morestack
|
.size __morestack, .Lend-__morestack
|
||||||
"#
|
"#
|
||||||
: : "s" (init_trampoline_2 as usize) : "memory" : "volatile")
|
: : "s" (trampoline_2 as usize) : "memory" : "volatile")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[naked]
|
#[naked]
|
||||||
unsafe extern "C" fn init_trampoline_2() {
|
unsafe extern "C" fn trampoline_2() {
|
||||||
asm!(
|
asm!(
|
||||||
r#"
|
r#"
|
||||||
# Set up the second part of our DWARF CFI.
|
# 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);
|
let mut sp = StackPointer(stack.top() as *mut usize);
|
||||||
push(&mut sp, 0xdead0cfa); // CFA slot
|
push(&mut sp, 0xdead0cfa); // CFA slot
|
||||||
push(&mut sp, f as usize); // function
|
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
|
push(&mut sp, 0xdeadbbbb); // saved %ebp
|
||||||
sp
|
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);
|
let new_cfa = (new_stack.top() as *mut usize).offset(-1);
|
||||||
|
|
||||||
#[naked]
|
#[naked]
|
||||||
unsafe extern "C" fn swap_trampoline() {
|
unsafe extern "C" fn trampoline() {
|
||||||
asm!(
|
asm!(
|
||||||
r#"
|
r#"
|
||||||
# Save frame pointer explicitly; the unwinder uses it to find CFA of
|
# 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}
|
call ${1:c}
|
||||||
"#
|
"#
|
||||||
: "={eax}" (ret)
|
: "={eax}" (ret)
|
||||||
: "s" (swap_trampoline as usize)
|
: "s" (trampoline as usize)
|
||||||
"{eax}" (arg)
|
"{eax}" (arg)
|
||||||
"{esi}" (old_sp)
|
"{esi}" (old_sp)
|
||||||
"{edx}" (new_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 {
|
pub unsafe fn init(stack: &Stack, f: unsafe extern "C" fn(usize) -> !) -> StackPointer {
|
||||||
#[naked]
|
#[naked]
|
||||||
unsafe extern "C" fn init_trampoline_1() {
|
unsafe extern "C" fn trampoline_1() {
|
||||||
asm!(
|
asm!(
|
||||||
r#"
|
r#"
|
||||||
# gdb has a hardcoded check that rejects backtraces where frame addresses
|
# 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:
|
.Lend:
|
||||||
.size __morestack, .Lend-__morestack
|
.size __morestack, .Lend-__morestack
|
||||||
"#
|
"#
|
||||||
: : "s" (init_trampoline_2 as usize) : "memory" : "volatile")
|
: : "s" (trampoline_2 as usize) : "memory" : "volatile")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[naked]
|
#[naked]
|
||||||
unsafe extern "C" fn init_trampoline_2() {
|
unsafe extern "C" fn trampoline_2() {
|
||||||
asm!(
|
asm!(
|
||||||
r#"
|
r#"
|
||||||
# Set up the second part of our DWARF CFI.
|
# 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, 0xdeaddeaddead0cfa); // CFA slot
|
||||||
push(&mut sp, 0 as usize); // alignment
|
push(&mut sp, 0 as usize); // alignment
|
||||||
push(&mut sp, f as usize); // function
|
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
|
push(&mut sp, 0xdeaddeaddeadbbbb); // saved %rbp
|
||||||
sp
|
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);
|
let new_cfa = (new_stack.top() as *mut usize).offset(-1);
|
||||||
|
|
||||||
#[naked]
|
#[naked]
|
||||||
unsafe extern "C" fn swap_trampoline() {
|
unsafe extern "C" fn trampoline() {
|
||||||
asm!(
|
asm!(
|
||||||
r#"
|
r#"
|
||||||
# Save frame pointer explicitly; the unwinder uses it to find CFA of
|
# 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}
|
call ${1:c}
|
||||||
"#
|
"#
|
||||||
: "={rdi}" (ret)
|
: "={rdi}" (ret)
|
||||||
: "s" (swap_trampoline as usize)
|
: "s" (trampoline as usize)
|
||||||
"{rdi}" (arg)
|
"{rdi}" (arg)
|
||||||
"{rsi}" (old_sp)
|
"{rsi}" (old_sp)
|
||||||
"{rdx}" (new_sp)
|
"{rdx}" (new_sp)
|
||||||
|
|
Loading…
Reference in New Issue