Coverage Report

Created: 2026-08-08 08:01

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}