Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bytecodealliance
GitHub Repository: bytecodealliance/wasmtime
Path: blob/main/crates/fiber/src/stackswitch/x86_64.rs
1692 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 wasmtime_asm_macros::asm_func;
9
10
// fn(top_of_stack(rdi): *mut u8)
11
asm_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
// fn(
46
// top_of_stack(rdi): *mut u8,
47
// entry_point(rsi): extern fn(*mut u8, *mut u8),
48
// entry_arg0(rdx): *mut u8,
49
// )
50
#[rustfmt::skip]
51
asm_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
79
// This is a pretty special function that has no real signature. Its use is to
80
// be the "base" function of all fibers. This entrypoint is used in
81
// `wasmtime_fiber_init` to bootstrap the execution of a new fiber.
82
//
83
// We also use this function as a persistent frame on the stack to emit dwarf
84
// information to unwind into the caller. This allows us to unwind from the
85
// fiber's stack back to the main stack that the fiber was called from. We use
86
// special dwarf directives here to do so since this is a pretty nonstandard
87
// function.
88
//
89
// If you're curious a decent introduction to CFI things and unwinding is at
90
// https://www.imperialviolet.org/2017/01/18/cfi.html
91
asm_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
);
161
162