wasmtime_internal_fiber/stackswitch/
x86_64.rs1use core::arch::naked_asm;
9
10#[inline(never)] 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)]
16unsafe extern "C" fn wasmtime_fiber_switch_(top_of_stack: *mut u8 ) {
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#[unsafe(naked)]
52pub(crate) unsafe extern "C" fn wasmtime_fiber_init(
53 top_of_stack: *mut u8, entry_point: extern "C" fn(*mut u8, *mut u8), entry_arg0: *mut u8, ) {
57 naked_asm!(
58 "
59 // Here we're going to set up a stack frame as expected by
60 // `wasmtime_fiber_switch`. The values we store here will get restored into
61 // registers by that function and the `wasmtime_fiber_start` function will
62 // take over and understands which values are in which registers.
63 //
64 // The first 16 bytes of stack are reserved for metadata, so we start
65 // storing values beneath that.
66 lea rax, {start}[rip]
67 mov -0x18[rdi], rax
68 mov -0x20[rdi], rdi // loaded into rbp during switch
69 mov -0x28[rdi], rsi // loaded into rbx during switch
70 mov -0x30[rdi], rdx // loaded into r12 during switch
71
72 // And then we specify the stack pointer resumption should begin at. Our
73 // `wasmtime_fiber_switch` function consumes 6 registers plus a return
74 // pointer, and the top 16 bytes are reserved, so that's:
75 //
76 // (6 + 1) * 16 + 16 = 0x48
77 lea rax, -0x48[rdi]
78 mov -0x10[rdi], rax
79 ret
80 ",
81 start = sym wasmtime_fiber_start,
82 );
83}
84
85#[unsafe(naked)]
98unsafe 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}