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