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);
    }
}