wasmtime_internal_fiber/stackswitch/
x86_64.rs

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
8use core::arch::naked_asm;
9
10#[inline(never)] // FIXME(rust-lang/rust#148307)
11pub(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 /* 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#[unsafe(naked)]
52pub(crate) unsafe extern "C" fn wasmtime_fiber_init(
53    top_of_stack: *mut u8,                        // rdi
54    entry_point: extern "C" fn(*mut u8, *mut u8), // rsi
55    entry_arg0: *mut u8,                          // rdx
56) {
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// 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)]
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}