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
// Copyright 2016 coroutine-rs Developers // // Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or // http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or // http://opensource.org/licenses/MIT>, at your option. This file may not be // copied, modified, or distributed except according to those terms. use std::fmt; use std::os::raw::c_void; use stack::Stack; // Requires cdecl calling convention on x86, which is the default for "C" blocks. extern "C" { /// Creates a new `Context` ontop of some stack. /// /// # Arguments /// * `sp` - A pointer to the bottom of the stack. /// * `size` - The size of the stack. /// * `f` - A function to be invoked on the first call to jump_fcontext(this, _). #[inline(never)] fn make_fcontext(sp: *mut c_void, size: usize, f: ContextFn) -> &'static c_void; /// Yields the execution to another `Context`. /// /// # Arguments /// * `to` - A pointer to the `Context` with whom we swap execution. /// * `p` - An arbitrary argument that will be set as the `data` field /// of the `Transfer` object passed to the other Context. #[inline(never)] fn jump_fcontext(to: &'static c_void, p: usize) -> Transfer; /// Yields the execution to another `Context` and executes a function "ontop" of it's stack. /// /// # Arguments /// * `to` - A pointer to the `Context` with whom we swap execution. /// * `p` - An arbitrary argument that will be set as the `data` field /// of the `Transfer` object passed to the other Context. /// * `f` - A function to be invoked on `to` before returning. #[inline(never)] fn ontop_fcontext(to: &'static c_void, p: usize, f: ResumeOntopFn) -> Transfer; } /// Functions of this signature are used as the entry point for a new `Context`. pub type ContextFn = extern "C" fn(t: Transfer) -> !; /// Functions of this signature are used as the callback while resuming ontop of a `Context`. pub type ResumeOntopFn = extern "C" fn(t: Transfer) -> Transfer; /// A `Context` stores a `ContextFn`'s state of execution, for it to be resumed later. /// /// If we have 2 or more `Context` instances, we can thus easily "freeze" the /// current state of execution and explicitely switch to another `Context`. /// This `Context` is then resumed exactly where it left of and /// can in turn "freeze" and switch to another `Context`. /// /// # Examples /// /// See [examples/basic.rs](https://github.com/zonyitoo/context-rs/blob/master/examples/basic.rs) // The reference is using 'static because we can't possibly imply the // lifetime of the Context instances returned by resume() anyways. #[repr(C)] pub struct Context(&'static c_void); // NOTE: Rustc is kinda dumb and introduces a overhead of up to 500% compared to the asm methods // if we don't explicitely inline them or use LTO (e.g.: 3ns/iter VS. 18ns/iter on i7 3770). impl Context { /// Creates a new `Context` prepared to execute `f` at the beginning of `stack`. /// /// `f` is not executed until the first call to `resume()`. /// /// It is unsafe because it only takes a reference of `Stack`. You have to make sure the /// `Stack` lives longer than the generated `Context`. #[inline(always)] pub unsafe fn new(stack: &Stack, f: ContextFn) -> Context { Context(make_fcontext(stack.top(), stack.len(), f)) } /// Yields the execution to another `Context`. /// /// The exact behaviour of this method is implementation defined, but the general mechanism is: /// The current state of execution is preserved somewhere and the previously saved state /// in the `Context` pointed to by `self` is restored and executed next. /// /// This behaviour is similiar in spirit to regular function calls with the difference /// that the call to `resume()` only returns when someone resumes the caller in turn. /// /// The returned `Transfer` struct contains the previously active `Context` and /// the `data` argument used to resume the current one. /// /// It is unsafe because it is your responsibility to make sure that all data that constructed in /// this context have to be dropped properly when the last context is dropped. #[inline(always)] pub unsafe fn resume(self, data: usize) -> Transfer { jump_fcontext(self.0, data) } /// Yields the execution to another `Context` and executes a function "ontop" of it's stack. /// /// This method identical to `resume()` with a minor difference: /// /// The argument `f` is executed right after the targeted `Context`, pointed to by `self`, /// is woken up, but before it returns from it's call to `resume()`. /// `f` now gets passed the `Transfer` struct which would normally be returned by `resume()` /// and is allowed to inspect and modify it. The `Transfer` struct `f` returns is then /// finally the one returned by `resume()` in the targeted `Context`. /// /// This behaviour can be used to either execute additional code or *map* the `Transfer` struct /// to another one before it's returned, without the targeted `Context` giving it's consent. /// For instance it can be used to unwind the stack of an unfinished `Context`, /// by calling this method with a function that panics, or to deallocate the own stack, /// by deferring the actual deallocation until we jumped to another, safe `Context`. /// /// It is unsafe because it is your responsibility to make sure that all data that constructed in /// this context have to be dropped properly when the last context is dropped. #[inline(always)] pub unsafe fn resume_ontop(self, data: usize, f: ResumeOntopFn) -> Transfer { ontop_fcontext(self.0, data, f) } } impl fmt::Debug for Context { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Context({:p})", self.0) } } /// Contains the previously active `Context` and the `data` passed to resume the current one and /// is used as the return value by `Context::resume()` and `Context::resume_ontop()` #[repr(C)] #[derive(Debug)] pub struct Transfer { /// The previously executed `Context` which yielded to resume the current one. pub context: Context, /// The `data` which was passed to `Context::resume()` or /// `Context::resume_ontop()` to resume the current `Context`. pub data: usize, } impl Transfer { /// Returns a new `Transfer` struct with the members set to their respective arguments. #[inline(always)] pub fn new(context: Context, data: usize) -> Transfer { Transfer { context: context, data: data, } } } #[cfg(test)] mod tests { use std::mem; use std::os::raw::c_void; use stack::ProtectedFixedSizeStack; use super::*; #[test] fn type_sizes() { assert_eq!(mem::size_of::<Context>(), mem::size_of::<usize>()); assert_eq!(mem::size_of::<Context>(), mem::size_of::<*const c_void>()); } #[cfg(feature = "nightly")] #[test] fn stack_alignment() { #[allow(non_camel_case_types)] #[repr(simd)] struct u8x16(u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8, u8); extern "C" fn context_function(t: Transfer) -> ! { // If we do not use an array in combination with mem::uninitialized(), // Rust will still generate SSE/NEON operations for the assignment // and make the test crash with a segmentation fault due to misalignment. let data: [u8x16; 1] = unsafe { mem::uninitialized() }; let addr = &data as *const _ as usize; unsafe { t.context.resume(addr % mem::align_of::<u8x16>()) }; unreachable!(); } let stack = ProtectedFixedSizeStack::default(); let mut t = Transfer::new(unsafe { Context::new(&stack, context_function) }, 0); t = unsafe { t.context.resume(0) }; assert_eq!(t.data, 0); } #[test] fn number_generator() { extern "C" fn context_function(mut t: Transfer) -> ! { for i in 0usize.. { assert_eq!(t.data, i); t = unsafe { t.context.resume(i) }; } unreachable!(); } let stack = ProtectedFixedSizeStack::default(); let mut t = Transfer::new(unsafe { Context::new(&stack, context_function) }, 0); for i in 0..10usize { t = unsafe { t.context.resume(i) }; assert_eq!(t.data, i); if t.data == 9 { break; } } } #[test] fn resume_ontop() { extern "C" fn resume(t: Transfer) -> ! { assert_eq!(t.data, 0); unsafe { t.context.resume_ontop(1, resume_ontop) }; unreachable!(); } extern "C" fn resume_ontop(mut t: Transfer) -> Transfer { assert_eq!(t.data, 1); t.data = 123; t } let stack = ProtectedFixedSizeStack::default(); let mut t = Transfer::new(unsafe { Context::new(&stack, resume) }, 0); t = unsafe { t.context.resume(0) }; assert_eq!(t.data, 123); } }