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)
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)]
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 
51 pub(crate) unsafe fn wasmtime_fiber_init(
52     top_of_stack: *mut u8,
53     entry_point: extern "C" fn(*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             r12: entry_arg0,
76             rbx: entry_point as *mut u8,
77             rbp: top_of_stack,
78             return_address: wasmtime_fiber_start as *mut u8,
79             last_sp: initial_stack.cast(),
80             ..InitialStack::default()
81         });
82     }
83 }
84 
85 // This is a pretty special function that has no real signature. Its use is to
86 // be the "base" function of all fibers. This entrypoint is used in
87 // `wasmtime_fiber_init` to bootstrap the execution of a new fiber.
88 //
89 // We also use this function as a persistent frame on the stack to emit dwarf
90 // information to unwind into the caller. This allows us to unwind from the
91 // fiber's stack back to the main stack that the fiber was called from. We use
92 // special dwarf directives here to do so since this is a pretty nonstandard
93 // function.
94 //
95 // If you're curious a decent introduction to CFI things and unwinding is at
96 // https://www.imperialviolet.org/2017/01/18/cfi.html
97 #[unsafe(naked)]
98 unsafe extern "C" fn wasmtime_fiber_start() -> ! {
99     naked_asm!(
100         "
101         // Use the `simple` directive on the startproc here which indicates that
102         // some default settings for the platform are omitted, since this
103         // function is so nonstandard
104         .cfi_startproc simple
105         .cfi_def_cfa_offset 0
106 
107         // This is where things get special, we're specifying a custom dwarf
108         // expression for how to calculate the CFA. The goal here is that we
109         // need to load the parent's stack pointer just before the call it made
110         // into `wasmtime_fiber_switch`. Note that the CFA value changes over
111         // time as well because a fiber may be resumed multiple times from
112         // different points on the original stack. This means that our custom
113         // CFA directive involves `DW_OP_deref`, which loads data from memory.
114         //
115         // The expression we're encoding here is that the CFA, the stack pointer
116         // of whatever called into `wasmtime_fiber_start`, is:
117         //
118         //        *$rsp + 0x38
119         //
120         // $rsp is the stack pointer of `wasmtime_fiber_start` at the time the
121         // next instruction after the `.cfi_escape` is executed. Our $rsp at the
122         // start of this function is 16 bytes below the top of the stack (0xAff0
123         // in the diagram in unix.rs). The $rsp to resume at is stored at that
124         // location, so we dereference the stack pointer to load it.
125         //
126         // After dereferencing, though, we have the $rsp value for
127         // `wasmtime_fiber_switch` itself. That's a weird function which sort of
128         // and sort of doesn't exist on the stack.  We want to point to the
129         // caller of `wasmtime_fiber_switch`, so to do that we need to skip the
130         // stack space reserved by `wasmtime_fiber_switch`, which is the 6 saved
131         // registers plus the return address of the caller's `call` instruction.
132         // Hence we offset another 0x38 bytes.
133         .cfi_escape 0x0f, /* DW_CFA_def_cfa_expression */ \
134             4,            /* the byte length of this expression */ \
135             0x57,         /* DW_OP_reg7 (rsp) */ \
136             0x06,         /* DW_OP_deref */ \
137             0x23, 0x38    /* DW_OP_plus_uconst 0x38 */
138 
139         // And now after we've indicated where our CFA is for our parent
140         // function, we can define that where all of the saved registers are
141         // located. This uses standard `.cfi` directives which indicate that
142         // these registers are all stored relative to the CFA. Note that this
143         // order is kept in sync with the above register spills in
144         // `wasmtime_fiber_switch`.
145         .cfi_rel_offset rip, -8
146         .cfi_rel_offset rbp, -16
147         .cfi_rel_offset rbx, -24
148         .cfi_rel_offset r12, -32
149         .cfi_rel_offset r13, -40
150         .cfi_rel_offset r14, -48
151         .cfi_rel_offset r15, -56
152 
153         // The body of this function is pretty similar. All our parameters are
154         // already loaded into registers by the switch function. The
155         // `wasmtime_fiber_init` routine arranged the various values to be
156         // materialized into the registers used here. Our job is to then move
157         // the values into the ABI-defined registers and call the entry-point.
158         // Note that `call` is used here to leave this frame on the stack so we
159         // can use the dwarf info here for unwinding. The trailing `ud2` is just
160         // for safety.
161         mov rdi, r12
162         mov rsi, rbp
163         call rbx
164         ud2
165         .cfi_endproc
166         ",
167     );
168 }
169