1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
//! ## Overview
//!
//! This crate provides a safe and efficient abstraction for context switching between different stacks, in the form of [coroutines]. A coroutine is a function that can be paused and resumed, yielding values to the caller. A coroutine can suspend itself from any point in its call stack. In addition to receiving yielded values from a coroutine, you can also pass data into the coroutine each time it is resumed.
//!
//! [coroutines]: https://en.wikipedia.org/wiki/Coroutine
//!
//! ## Example
//!
//! ```rust
//! use corosensei::{Coroutine, CoroutineResult};
//!
//! fn main() {
//!     println!("[main] creating coroutine");
//!
//!     let mut coroutine = Coroutine::new(|yielder, input| {
//!         println!("[coroutine] coroutine started with input {}", input);
//!         for i in 0..5 {
//!             println!("[coroutine] yielding {}", i);
//!             let input = yielder.suspend(i);
//!             println!("[coroutine] got {} from parent", input)
//!         }
//!         println!("[coroutine] exiting coroutine");
//!     });
//!
//!     let mut counter = 100;
//!     loop {
//!         println!("[main] resuming coroutine with argument {}", counter);
//!         match coroutine.resume(counter) {
//!             CoroutineResult::Yield(i) => println!("[main] got {:?} from coroutine", i),
//!             CoroutineResult::Return(()) => break,
//!         }
//!
//!         counter += 1;
//!     }
//!
//!     println!("[main] exiting");
//! }
//! ```
//!
//! #### Output
//!
//! ```text
//! [main] creating coroutine
//! [main] resuming coroutine with argument 100
//! [coroutine] coroutine started with input 100
//! [coroutine] yielding 0
//! [main] got 0 from coroutine
//! [main] resuming coroutine with argument 101
//! [coroutine] got 101 from parent
//! [coroutine] yielding 1
//! [main] got 1 from coroutine
//! [main] resuming coroutine with argument 102
//! [coroutine] got 102 from parent
//! [coroutine] yielding 2
//! [main] got 2 from coroutine
//! [main] resuming coroutine with argument 103
//! [coroutine] got 103 from parent
//! [coroutine] yielding 3
//! [main] got 3 from coroutine
//! [main] resuming coroutine with argument 104
//! [coroutine] got 104 from parent
//! [coroutine] yielding 4
//! [main] got 4 from coroutine
//! [main] resuming coroutine with argument 105
//! [coroutine] got 105 from parent
//! [coroutine] exiting coroutine
//! [main] exiting
//! ```
//!
//! ## Supported targets
//!
//! This crate currently supports the following targets:
//!
//! |             | ELF (Linux, BSD, bare metal, etc) | Darwin (macOS, iOS, etc) | Windows |
//! |-------------|-----------------------------------|--------------------------|---------|
//! | x86_64      | ✅                                | ✅                       | ✅      |
//! | x86         | ✅                                | ✅                       | ⚠️*      |
//! | AArch64     | ✅                                | ✅                       | ❌      |
//! | ARM         | ✅                                | ❌                       | ❌      |
//! | RISC-V      | ✅                                | ❌                       | ❌      |
//! | LoongArch64 | ✅                                | ❌                       | ❌      |
//!
//! *\* Linked backtraces are not supported on x86 Windows.*
//!
//! Feel free to open an issue if your target is not supported yet.
//!
//! ## Features
//!
//! #### Panic propagation
//!
//! If a panic occurs in a coroutine then the panic will unwind through the coroutine stack and then continue to unwind out of the caller which last resumed it. Once this has happened, the coroutine is considered complete and can no longer be resumed.
//!
//! ```rust
//! use std::panic::{catch_unwind, AssertUnwindSafe};
//!
//! use corosensei::Coroutine;
//!
//! fn main() {
//!     println!("[main] creating coroutine");
//!
//!     let mut coroutine = Coroutine::new(|yielder, ()| {
//!         println!("[coroutine] yielding 42");
//!         yielder.suspend(42);
//!
//!         println!("[coroutine] panicking");
//!         panic!("foobar");
//!     });
//!
//!     println!("[main] resuming coroutine");
//!     let result = catch_unwind(AssertUnwindSafe(|| coroutine.resume(())));
//!     println!(
//!         "[main] got value {} from coroutine",
//!         result.unwrap().as_yield().unwrap()
//!     );
//!
//!     println!("[main] resuming coroutine");
//!     let result = catch_unwind(AssertUnwindSafe(|| coroutine.resume(())));
//!     println!(
//!         "[main] caught panic \"{}\" from coroutine",
//!         result.unwrap_err().downcast_ref::<&'static str>().unwrap()
//!     );
//!
//!     println!("[main] exiting");
//! }
//! ```
//!
//! ```text
//! [main] creating coroutine
//! [main] resuming coroutine
//! [coroutine] yielding 42
//! [main] got value 42 from coroutine
//! [main] resuming coroutine
//! [coroutine] panicking
//! thread 'main' panicked at 'foobar', examples/panic.rs:13:9
//! note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
//! [main] caught panic "foobar" from coroutine
//! [main] exiting
//! ```
//!
//! #### Linked backtraces
//!
//! Backtraces taken from within a coroutine will continue into the parent stack from the point where they were the coroutine was last resumed from. This is a significant help towards debugging issues with code in coroutines.
//!
//! Notice how the backtrace in this example shows that the coroutine was last resumed from `sub_function`:
//!
//! ```rust
//! use backtrace::Backtrace;
//! use corosensei::Coroutine;
//!
//! #[inline(never)]
//! fn sub_function(coroutine: &mut Coroutine<(), (), ()>) {
//!     coroutine.resume(());
//! }
//!
//! fn main() {
//!     let mut coroutine = Coroutine::new(move |_, ()| {
//!         let trace = Backtrace::new();
//!         println!("{:?}", trace);
//!     });
//!
//!     sub_function(&mut coroutine);
//! }
//! ```
//!
//! ```text
//!    0: backtrace::main::{{closure}}
//!              at examples/backtrace.rs:11:21
//!       corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::with_stack::coroutine_func::{{closure}}
//!              at src/coroutine.rs:174:62
//!       corosensei::unwind::catch_unwind_at_root
//!              at src/unwind.rs:43:16
//!    1: corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::with_stack::coroutine_func
//!              at src/coroutine.rs:174:30
//!    2: stack_init_trampoline
//!    3: corosensei::arch::x86_64::switch_and_link
//!              at src/arch/x86_64.rs:302:5
//!       corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::resume_inner
//!              at src/coroutine.rs:256:13
//!       corosensei::coroutine::ScopedCoroutine<Input,Yield,Return,Stack>::resume
//!              at src/coroutine.rs:235:19
//!       backtrace::sub_function
//!              at examples/backtrace.rs:6:5
//!    4: backtrace::main
//!              at examples/backtrace.rs:15:5
//! ```
//!
//! Linked backtraces use the platform's standard unwinding metadata and work with debuggers, profilers and many other tools.
//!
//! #### Cleanup on drop
//!
//! If a coroutine is dropped while it is suspended then the coroutine's stack will be safely unwound using the same mechanism as panics, which will drop any local variables on the stack. This is necessary to maintain a safe API: the coroutine's stack cannot be freed or reused while there are still live objects on it.
//!
//! If this cleanup is not desired then a coroutine can forcibly be marked as completed using the unsafe `force_reset` function.
//!
//! #### Trap handling
//!
//! For advanced use cases, this crate provides the ability to recover from traps (e.g. segmentation fault, stack overflow) that occur in a coroutine. This API is unsafe and should be used from within a signal/exception handler: it will help set up the return from the signal/exception handler so that the coroutine will exit immediately with the given return value and return control to its parent.
//!
//! This is particularly useful for running JIT-compiled code with a fixed stack limit: stack overflows can be caught by a signal/exception handler and easily recovered from.
//!
//! ## Cargo features
//!
//! This crate is compatible with `#![no_std]` when all Cargo features are disabled.
//!
//! The following Cargo feature flags are available on this crate:
//!
//! #### `default-stack` (Enabled by default)
//!
//! This feature provides a `DefaultStack` implementation which can be used by the `Coroutine` type. This stack is allocated with a guard page using OS APIs and requires `std`.
//!
//! If this feature is disabled then you must implement your own stack type which implements the `Stack` trait.
//!
//! #### `unwind` (Enabled by default)
//!
//! This feature adds support for:
//! - unwinding panics in a coroutine back out to its caller.
//! - forcibly unwinding a suspended coroutine via `force_unwind` or when the coroutine is dropped.
//!
//! Note that if a coroutine is dropped while suspended (i.e. it has been resumed at least once but has not returned yet) when this feature is disabled then the program will abort.
//!
//! Requires `std`.
//!
//! #### `asm-unwind`
//!
//! This feature uses the `asm_unwind` nightly feature to allow panics to unwind directly through the inline assembly used in this crate, which can improve performance since it doesn't need to be passed across stack boundaries as a `Result`.
//!
//! Implies `unwind`.

#![no_std]
#![cfg_attr(feature = "asm-unwind", feature(asm_unwind, c_unwind, asm_sym))]
#![warn(missing_docs)]

// Must come first because it defines macros used by other modules.
mod unwind;

mod arch;
mod coroutine;
pub mod stack;
pub mod trap;
mod util;

pub use coroutine::*;

#[cfg(test)]
mod tests;