Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fiber/src/stackswitch/x86_64.rs
3063 views
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
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)]
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
pub(crate) unsafe fn wasmtime_fiber_init(
52
top_of_stack: *mut u8,
53
entry_point: extern "C" fn(*mut u8, *mut u8),
54
entry_arg0: *mut u8,
55
) {
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
unsafe {
73
let initial_stack = top_of_stack.cast::<InitialStack>().sub(1);
74
initial_stack.write(InitialStack {
75
r12: entry_arg0,
76
rbx: entry_point as *mut u8,
77
rbp: top_of_stack,
78
return_address: wasmtime_fiber_start as *mut u8,
79
last_sp: initial_stack.cast(),
80
..InitialStack::default()
81
});
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)]
98
unsafe 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
}
169
170