/src/wasmtime/crates/fiber/src/stackswitch/x86_64.rs
Line | Count | Source |
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 | 516k | pub(crate) unsafe extern "C" fn wasmtime_fiber_switch(top_of_stack: *mut u8) { |
12 | 516k | unsafe { wasmtime_fiber_switch_(top_of_stack) } |
13 | 516k | } |
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 | 118k | pub(crate) unsafe fn wasmtime_fiber_init( |
52 | 118k | top_of_stack: *mut u8, |
53 | 118k | entry_point: extern "C" fn(*mut u8, *mut u8) -> *mut u8, |
54 | 118k | entry_arg0: *mut u8, |
55 | 118k | ) { |
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 | 118k | unsafe { |
73 | 118k | let initial_stack = top_of_stack.cast::<InitialStack>().sub(1); |
74 | 118k | initial_stack.write(InitialStack { |
75 | 118k | r13: wasmtime_fiber_switch_ as *mut u8, |
76 | 118k | r12: entry_arg0, |
77 | 118k | rbx: entry_point as *mut u8, |
78 | 118k | rbp: top_of_stack, |
79 | 118k | return_address: wasmtime_fiber_start as *mut u8, |
80 | 118k | last_sp: initial_stack.cast(), |
81 | 118k | ..InitialStack::default() |
82 | 118k | }); |
83 | 118k | } |
84 | 118k | } |
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)] |
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 | | } |