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