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
// All architectures expose a similar api. Here I just want to take some time explaining the general
// idea behind all of them.
//
// At the core of the implementation there are 3 functions:
// * `init(stack: Stack, f: unsafe extern "C" fn(usize, *mut  usize))`
// * `swap_and_link_stacks(arg: usize, new_sp: *mut usize, sp: *mut usize) -> (usize, *mut usize)`
// * `swap(arg: usize, new_sp: *mut usize, sp: *mut usize) -> (usize, *mut usize)`
//
// ### init
// `init` takes a **stack** and a **pointer to a function**. It will prepare the stack so it is ready
// to be switched to. Once we switch to it the function we set up here will be called.
//
// Unix and Windows operating systems require different stack setups. Here is an illustration on how
// the stacks look after the call to `init`:
// ```
//      +                  +
//      |     .......      |
//      |                  |
//      |Deallocation stack|
//      +------------------+
//      |Stack limit       |
//      +------------------+
//      |Stack base        |        +                  +
//      +------------------+        |                  |
// +----+Stack frame ptr   |        |                  |
// |    +------------------+        |    .........     |
// |    |Trampoline 2 ptr  |        |                  |
// |    +------------------+   +----+Stack frame ptr   |
// +---->Caller frame      |   |    +------------------+
//      +------------------+   |    |Trampoline 2 ptr  |
//      |Trampoline 1 ptr  |   |    +------------------+
//      +------------------+   +---->Caller frame      |
//      |Function ptr      |        +------------------+
//      +------------------+        |Trampoline 1 ptr  |
//      |Alignment         |        +------------------+
//      +------------------+        |Function ptr      |
//      |                  |        +------------------+
//      |Home address space|        |Alignment         |
//      +------------------+        +------------------+
//
//            Windows                      Unix
// ```
// Windows needs to preserve some extra information across context switches, like the stack base, top
// and deallocation values. If they are not present Windows will not know how to grow the stack.
// The [Boost.Context](https://www.boost.org/doc/libs/1_61_0/libs/context/doc/html/context/overview.html)
// library also preserves some other information, like the current
// [Fiber](https://docs.microsoft.com/en-us/windows/win32/procthread/fibers) data, but I don't expect
// anyone to use switcheroo and Windows Fibers in the same app.
//
// The **Caller frame** value will be filled in by the `swap_and_link_stacks` function to link the 2
// stacks from different contexts. At this point of time we can't know from where we are jumping to
// the stack.
//
// ### swap_and_link_stacks
// This function is really similar to `swap`, but it's expected to be the first one called when jumping
// to a new stack. It will write the **Caller frame** data inside the new stack, basically linking them
// together. Once this data exists on the new stack we don't need to call it anymore and can switch
// stacks with just the `swap` function.
//
// The swap functions will:
// 1. Preserve the frame pointer and instruction pointer of the current context.
//    On Windows, deallocation stack, stack limit and base stack are also preserved.
// 2. Change the stack pointer to the new stack.
// 3. Pop the frame pointer and instruction pointer from the new stack.
// 4. Jump to the instruction.
//
// Notice that the instruction pointer points to a cryptic **Trampoline 2** function and not to the
// passed in **Function**. Trampoline 1 and 2 contain some extra assembler information so that it's
// possible to re-create a backtrace across contexts if we panic inside the new context.

#[cfg(all(target_family = "unix", target_arch = "x86_64"))]
mod unix_x64;
#[cfg(all(target_family = "unix", target_arch = "x86_64"))]
pub use self::unix_x64::*;

#[cfg(all(target_family = "unix", target_arch = "aarch64"))]
mod unix_aarch64;
#[cfg(all(target_family = "unix", target_arch = "aarch64"))]
pub use self::unix_aarch64::*;

#[cfg(all(target_family = "windows", target_arch = "x86_64"))]
mod windows_x64;
#[cfg(all(target_family = "windows", target_arch = "x86_64"))]
pub use self::windows_x64::*;