wasmtime_fiber/stackswitch/
x86_64.rs1use wasmtime_asm_macros::asm_func;
9
10asm_func!(
12 wasmtime_versioned_export_macros::versioned_stringify_ident!(wasmtime_fiber_switch),
13 "
14 // We're switching to arbitrary code somewhere else, so pessimistically
15 // assume that all callee-save register are clobbered. This means we need
16 // to save/restore all of them.
17 //
18 // Note that this order for saving is important since we use CFI directives
19 // below to point to where all the saved registers are.
20 push rbp
21 push rbx
22 push r12
23 push r13
24 push r14
25 push r15
26
27 // Load pointer that we're going to resume at and store where we're going
28 // to get resumed from. This is in accordance with the diagram at the top
29 // of unix.rs.
30 mov rax, -0x10[rdi]
31 mov -0x10[rdi], rsp
32
33 // Swap stacks and restore all our callee-saved registers
34 mov rsp, rax
35 pop r15
36 pop r14
37 pop r13
38 pop r12
39 pop rbx
40 pop rbp
41 ret
42 ",
43);
44
45#[rustfmt::skip]
51asm_func!(
52 wasmtime_versioned_export_macros::versioned_stringify_ident!(wasmtime_fiber_init),
53 "
54 // Here we're going to set up a stack frame as expected by
55 // `wasmtime_fiber_switch`. The values we store here will get restored into
56 // registers by that function and the `wasmtime_fiber_start` function will
57 // take over and understands which values are in which registers.
58 //
59 // The first 16 bytes of stack are reserved for metadata, so we start
60 // storing values beneath that.
61 lea rax, {start}[rip]
62 mov -0x18[rdi], rax
63 mov -0x20[rdi], rdi // loaded into rbp during switch
64 mov -0x28[rdi], rsi // loaded into rbx during switch
65 mov -0x30[rdi], rdx // loaded into r12 during switch
66
67 // And then we specify the stack pointer resumption should begin at. Our
68 // `wasmtime_fiber_switch` function consumes 6 registers plus a return
69 // pointer, and the top 16 bytes are reserved, so that's:
70 //
71 // (6 + 1) * 16 + 16 = 0x48
72 lea rax, -0x48[rdi]
73 mov -0x10[rdi], rax
74 ret
75 ",
76 start = sym super::wasmtime_fiber_start,
77);
78
79asm_func!(
92 wasmtime_versioned_export_macros::versioned_stringify_ident!(wasmtime_fiber_start),
93 "
94 // Use the `simple` directive on the startproc here which indicates that
95 // some default settings for the platform are omitted, since this
96 // function is so nonstandard
97 .cfi_startproc simple
98 .cfi_def_cfa_offset 0
99
100 // This is where things get special, we're specifying a custom dwarf
101 // expression for how to calculate the CFA. The goal here is that we
102 // need to load the parent's stack pointer just before the call it made
103 // into `wasmtime_fiber_switch`. Note that the CFA value changes over
104 // time as well because a fiber may be resumed multiple times from
105 // different points on the original stack. This means that our custom
106 // CFA directive involves `DW_OP_deref`, which loads data from memory.
107 //
108 // The expression we're encoding here is that the CFA, the stack pointer
109 // of whatever called into `wasmtime_fiber_start`, is:
110 //
111 // *$rsp + 0x38
112 //
113 // $rsp is the stack pointer of `wasmtime_fiber_start` at the time the
114 // next instruction after the `.cfi_escape` is executed. Our $rsp at the
115 // start of this function is 16 bytes below the top of the stack (0xAff0
116 // in the diagram in unix.rs). The $rsp to resume at is stored at that
117 // location, so we dereference the stack pointer to load it.
118 //
119 // After dereferencing, though, we have the $rsp value for
120 // `wasmtime_fiber_switch` itself. That's a weird function which sort of
121 // and sort of doesn't exist on the stack. We want to point to the
122 // caller of `wasmtime_fiber_switch`, so to do that we need to skip the
123 // stack space reserved by `wasmtime_fiber_switch`, which is the 6 saved
124 // registers plus the return address of the caller's `call` instruction.
125 // Hence we offset another 0x38 bytes.
126 .cfi_escape 0x0f, /* DW_CFA_def_cfa_expression */ \
127 4, /* the byte length of this expression */ \
128 0x57, /* DW_OP_reg7 (rsp) */ \
129 0x06, /* DW_OP_deref */ \
130 0x23, 0x38 /* DW_OP_plus_uconst 0x38 */
131
132 // And now after we've indicated where our CFA is for our parent
133 // function, we can define that where all of the saved registers are
134 // located. This uses standard `.cfi` directives which indicate that
135 // these registers are all stored relative to the CFA. Note that this
136 // order is kept in sync with the above register spills in
137 // `wasmtime_fiber_switch`.
138 .cfi_rel_offset rip, -8
139 .cfi_rel_offset rbp, -16
140 .cfi_rel_offset rbx, -24
141 .cfi_rel_offset r12, -32
142 .cfi_rel_offset r13, -40
143 .cfi_rel_offset r14, -48
144 .cfi_rel_offset r15, -56
145
146 // The body of this function is pretty similar. All our parameters are
147 // already loaded into registers by the switch function. The
148 // `wasmtime_fiber_init` routine arranged the various values to be
149 // materialized into the registers used here. Our job is to then move
150 // the values into the ABI-defined registers and call the entry-point.
151 // Note that `call` is used here to leave this frame on the stack so we
152 // can use the dwarf info here for unwinding. The trailing `ud2` is just
153 // for safety.
154 mov rdi, r12
155 mov rsi, rbp
156 call rbx
157 ud2
158 .cfi_endproc
159 ",
160);