1 // A WORD OF CAUTION
2 //
3 // This entire file basically needs to be kept in sync with itself. It's not
4 // really possible to modify just one bit of this file without understanding
5 // all the other bits. Documentation tries to reference various bits here and
6 // there but try to make sure to read over everything before tweaking things!
7 
8 use core::arch::naked_asm;
9 
10 #[unsafe(naked)]
11 pub(crate) unsafe extern "C" fn wasmtime_fiber_switch(top_of_stack: *mut u8 /* rdi */) {
12     naked_asm!(
13         "
14         // We're switching to arbitrary code somewhere else, so pessimistically
15         // assume that all callee-save register are clobbered. This means we need
16         // to save/restore all of them.
17         //
18         // Note that this order for saving is important since we use CFI directives
19         // below to point to where all the saved registers are.
20         push rbp
21         push rbx
22         push r12
23         push r13
24         push r14
25         push r15
26 
27         // Load pointer that we're going to resume at and store where we're going
28         // to get resumed from. This is in accordance with the diagram at the top
29         // of unix.rs.
30         mov rax, -0x10[rdi]
31         mov -0x10[rdi], rsp
32 
33         // Swap stacks and restore all our callee-saved registers
34         mov rsp, rax
35         pop r15
36         pop r14
37         pop r13
38         pop r12
39         pop rbx
40         pop rbp
41         ret
42     ",
43     );
44 }
45 
46 #[unsafe(naked)]
47 pub(crate) unsafe extern "C" fn wasmtime_fiber_init(
48     top_of_stack: *mut u8,                        // rdi
49     entry_point: extern "C" fn(*mut u8, *mut u8), // rsi
50     entry_arg0: *mut u8,                          // rdx
51 ) {
52     naked_asm!(
53         "
54         // Here we're going to set up a stack frame as expected by
55         // `wasmtime_fiber_switch`. The values we store here will get restored into
56         // registers by that function and the `wasmtime_fiber_start` function will
57         // take over and understands which values are in which registers.
58         //
59         // The first 16 bytes of stack are reserved for metadata, so we start
60         // storing values beneath that.
61         lea rax, {start}[rip]
62         mov -0x18[rdi], rax
63         mov -0x20[rdi], rdi   // loaded into rbp during switch
64         mov -0x28[rdi], rsi   // loaded into rbx during switch
65         mov -0x30[rdi], rdx   // loaded into r12 during switch
66 
67         // And then we specify the stack pointer resumption should begin at. Our
68         // `wasmtime_fiber_switch` function consumes 6 registers plus a return
69         // pointer, and the top 16 bytes are reserved, so that's:
70         //
71         //	(6 + 1) * 16 + 16 = 0x48
72         lea rax, -0x48[rdi]
73         mov -0x10[rdi], rax
74         ret
75         ",
76         start = sym wasmtime_fiber_start,
77     );
78 }
79 
80 // This is a pretty special function that has no real signature. Its use is to
81 // be the "base" function of all fibers. This entrypoint is used in
82 // `wasmtime_fiber_init` to bootstrap the execution of a new fiber.
83 //
84 // We also use this function as a persistent frame on the stack to emit dwarf
85 // information to unwind into the caller. This allows us to unwind from the
86 // fiber's stack back to the main stack that the fiber was called from. We use
87 // special dwarf directives here to do so since this is a pretty nonstandard
88 // function.
89 //
90 // If you're curious a decent introduction to CFI things and unwinding is at
91 // https://www.imperialviolet.org/2017/01/18/cfi.html
92 #[unsafe(naked)]
93 unsafe extern "C" fn wasmtime_fiber_start() -> ! {
94     naked_asm!(
95         "
96         // Use the `simple` directive on the startproc here which indicates that
97         // some default settings for the platform are omitted, since this
98         // function is so nonstandard
99         .cfi_startproc simple
100         .cfi_def_cfa_offset 0
101 
102         // This is where things get special, we're specifying a custom dwarf
103         // expression for how to calculate the CFA. The goal here is that we
104         // need to load the parent's stack pointer just before the call it made
105         // into `wasmtime_fiber_switch`. Note that the CFA value changes over
106         // time as well because a fiber may be resumed multiple times from
107         // different points on the original stack. This means that our custom
108         // CFA directive involves `DW_OP_deref`, which loads data from memory.
109         //
110         // The expression we're encoding here is that the CFA, the stack pointer
111         // of whatever called into `wasmtime_fiber_start`, is:
112         //
113         //        *$rsp + 0x38
114         //
115         // $rsp is the stack pointer of `wasmtime_fiber_start` at the time the
116         // next instruction after the `.cfi_escape` is executed. Our $rsp at the
117         // start of this function is 16 bytes below the top of the stack (0xAff0
118         // in the diagram in unix.rs). The $rsp to resume at is stored at that
119         // location, so we dereference the stack pointer to load it.
120         //
121         // After dereferencing, though, we have the $rsp value for
122         // `wasmtime_fiber_switch` itself. That's a weird function which sort of
123         // and sort of doesn't exist on the stack.  We want to point to the
124         // caller of `wasmtime_fiber_switch`, so to do that we need to skip the
125         // stack space reserved by `wasmtime_fiber_switch`, which is the 6 saved
126         // registers plus the return address of the caller's `call` instruction.
127         // Hence we offset another 0x38 bytes.
128         .cfi_escape 0x0f, /* DW_CFA_def_cfa_expression */ \
129             4,            /* the byte length of this expression */ \
130             0x57,         /* DW_OP_reg7 (rsp) */ \
131             0x06,         /* DW_OP_deref */ \
132             0x23, 0x38    /* DW_OP_plus_uconst 0x38 */
133 
134         // And now after we've indicated where our CFA is for our parent
135         // function, we can define that where all of the saved registers are
136         // located. This uses standard `.cfi` directives which indicate that
137         // these registers are all stored relative to the CFA. Note that this
138         // order is kept in sync with the above register spills in
139         // `wasmtime_fiber_switch`.
140         .cfi_rel_offset rip, -8
141         .cfi_rel_offset rbp, -16
142         .cfi_rel_offset rbx, -24
143         .cfi_rel_offset r12, -32
144         .cfi_rel_offset r13, -40
145         .cfi_rel_offset r14, -48
146         .cfi_rel_offset r15, -56
147 
148         // The body of this function is pretty similar. All our parameters are
149         // already loaded into registers by the switch function. The
150         // `wasmtime_fiber_init` routine arranged the various values to be
151         // materialized into the registers used here. Our job is to then move
152         // the values into the ABI-defined registers and call the entry-point.
153         // Note that `call` is used here to leave this frame on the stack so we
154         // can use the dwarf info here for unwinding. The trailing `ud2` is just
155         // for safety.
156         mov rdi, r12
157         mov rsi, rbp
158         call rbx
159         ud2
160         .cfi_endproc
161         ",
162     );
163 }
164