1 //! The stack layout is expected to look like so:
2 //!
3 //!
4 //! ```text
5 //! 0xB000 +-----------------------+   <- top of stack (TOS)
6 //!        | saved RIP             |
7 //! 0xAff8 +-----------------------+
8 //!        | saved RBP             |
9 //! 0xAff0 +-----------------------+
10 //!        | saved RSP             |
11 //! 0xAfe8 +-----------------------+   <- beginning of "control context",
12 //!        | args_capacity         |
13 //! 0xAfe0 +-----------------------+
14 //!        | args buffer, size:    |
15 //!        | (16 * args_capacity)  |
16 //! 0xAfc0 +-----------------------+   <- below: beginning of usable stack space
17 //!        |                       |      (16-byte aligned)
18 //!        |                       |
19 //!        ~        ...            ~   <- actual native stack space to use
20 //!        |                       |
21 //! 0x1000 +-----------------------+
22 //!        |  guard page           |   <- (not currently enabled)
23 //! 0x0000 +-----------------------+
24 //! ```
25 //!
26 //! The "control context" indicates how to resume a computation. The layout is
27 //! determined by Cranelift's stack_switch instruction, which reads and writes
28 //! these fields. The fields are used as follows, where we distinguish two
29 //! cases:
30 //!
31 //! 1.
32 //! If the continuation is currently active (i.e., running directly, or ancestor
33 //! of the running continuation), it stores the PC, RSP, and RBP of the *parent*
34 //! of the running continuation.
35 //!
36 //! 2.
37 //! If the picture shows a suspended computation, the fields store the PC, RSP,
38 //! and RBP at the time of the suspension.
39 //!
40 //! Note that this design ensures that external tools can construct backtraces
41 //! in the presence of stack switching by using frame pointers only: The
42 //! wasmtime_continuation_start trampoline uses the address of the RBP field in the
43 //! control context (0xAff0 above) as its frame pointer. This means that when
44 //! passing the wasmtime_continuation_start frame while doing frame pointer walking,
45 //! the parent of that frame is the last frame in the parent of this
46 //! continuation.
47 //!
48 //! Wasmtime's own mechanism for constructing backtraces also relies on frame
49 //! pointer chains. However, it understands continuations and does not rely on
50 //! the trickery outlined here to go from the frames in one continuation to the
51 //! parent.
52 //!
53 //! The args buffer is used as follows: It is used by the array calling
54 //! trampoline to read and store the arguments and return values of the function
55 //! running inside the continuation. If this function has m parameters and n
56 //! return values, then args_capacity is defined as max(m, n) and the size of
57 //! the args buffer is args_capacity * 16 bytes. The start address (0xAfc0 in
58 //! the example above, thus assuming args_capacity = 2) is saved as the `data`
59 //! field of the VMContRef's `args` object.
60 
61 #![allow(unused_macros)]
62 
63 use core::ptr::NonNull;
64 use std::io;
65 use std::ops::Range;
66 use std::ptr;
67 
68 use crate::runtime::vm::stack_switching::VMHostArray;
69 use crate::runtime::vm::{VMContext, VMFuncRef, ValRaw};
70 
71 #[derive(Debug, PartialEq, Eq)]
72 pub enum Allocator {
73     Mmap,
74     Custom,
75 }
76 
77 #[derive(Debug)]
78 #[repr(C)]
79 pub struct VMContinuationStack {
80     // The top of the stack; for stacks allocated by the fiber implementation itself,
81     // the base address of the allocation will be `top.sub(len.unwrap())`
82     top: *mut u8,
83     // The length of the stack
84     len: usize,
85     // allocation strategy
86     allocator: Allocator,
87 }
88 
89 impl VMContinuationStack {
90     pub fn new(size: usize) -> io::Result<Self> {
91         // Round up our stack size request to the nearest multiple of the
92         // page size.
93         let page_size = rustix::param::page_size();
94         let size = if size == 0 {
95             page_size
96         } else {
97             size.next_multiple_of(page_size)
98         };
99 
100         unsafe {
101             // Add in one page for a guard page and then ask for some memory.
102             let mmap_len = size + page_size;
103             let mmap = rustix::mm::mmap_anonymous(
104                 ptr::null_mut(),
105                 mmap_len,
106                 rustix::mm::ProtFlags::empty(),
107                 rustix::mm::MapFlags::PRIVATE,
108             )?;
109 
110             rustix::mm::mprotect(
111                 mmap.cast::<u8>().add(page_size).cast(),
112                 size,
113                 rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
114             )?;
115 
116             Ok(Self {
117                 top: mmap.cast::<u8>().add(mmap_len),
118                 len: mmap_len,
119                 allocator: Allocator::Mmap,
120             })
121         }
122     }
123 
124     pub fn unallocated() -> Self {
125         Self {
126             top: std::ptr::null_mut(),
127             len: 0,
128             allocator: Allocator::Custom,
129         }
130     }
131 
132     pub fn is_unallocated(&self) -> bool {
133         debug_assert_eq!(self.len == 0, self.top == std::ptr::null_mut());
134         self.len == 0
135     }
136 
137     #[allow(clippy::missing_safety_doc)]
138     pub unsafe fn from_raw_parts(
139         base: *mut u8,
140         _guard_size: usize,
141         len: usize,
142     ) -> io::Result<Self> {
143         Ok(Self {
144             top: base.add(len),
145             len,
146             allocator: Allocator::Custom,
147         })
148     }
149 
150     pub fn is_from_raw_parts(&self) -> bool {
151         self.allocator == Allocator::Custom
152     }
153 
154     pub fn top(&self) -> Option<*mut u8> {
155         Some(self.top)
156     }
157 
158     pub fn range(&self) -> Option<Range<usize>> {
159         let base = unsafe { self.top.sub(self.len).addr() };
160         Some(base..base + self.len)
161     }
162 
163     pub fn control_context_instruction_pointer(&self) -> usize {
164         // See picture at top of this file:
165         // RIP is stored 8 bytes below top of stack.
166         unsafe {
167             let ptr = self.top.sub(8).cast::<usize>();
168             *ptr
169         }
170     }
171 
172     pub fn control_context_frame_pointer(&self) -> usize {
173         // See picture at top of this file:
174         // RBP is stored 16 bytes below top of stack.
175         unsafe {
176             let ptr = self.top.sub(16).cast::<usize>();
177             *ptr
178         }
179     }
180 
181     pub fn control_context_stack_pointer(&self) -> usize {
182         // See picture at top of this file:
183         // RSP is stored 24 bytes below top of stack.
184         unsafe {
185             let ptr = self.top.sub(24).cast::<usize>();
186             *ptr
187         }
188     }
189 
190     /// This function installs the launchpad for the computation to run on the
191     /// fiber, such that executing a `stack_switch` instruction on the stack
192     /// actually runs the desired computation.
193     ///
194     /// Concretely, switching to the stack prepared by this function
195     /// causes that we enter `wasmtime_continuation_start`, which then in turn
196     /// calls `fiber_start` with  the following arguments:
197     /// TOS, func_ref, caller_vmctx, args_ptr, args_capacity
198     ///
199     /// Note that at this point we also allocate the args buffer
200     /// (see picture at the top of this file).
201     /// We define `args_capacity` as the max of parameter and return value count.
202     /// Then the size s of the actual buffer size is calculated as follows:
203     /// s = size_of(ValRaw) * `args_capacity`,
204     ///
205     /// Note that this value is used below, and we may have s = 0.
206     ///
207     /// The layout of the VMContinuationStack near the top of stack (TOS)
208     /// *after* running this function is as follows:
209     ///
210     ///
211     ///  Offset from    |
212     ///       TOS       | Contents
213     ///  ---------------|-------------------------------------------------------
214     ///       -0x08     | address of wasmtime_continuation_start function (future PC)
215     ///       -0x10     | TOS - 0x10 (future RBP)
216     ///       -0x18     | TOS - 0x40 - s (future RSP)
217     ///       -0x20     | args_capacity
218     ///
219     ///
220     /// The data stored behind the args buffer is as follows:
221     ///
222     ///  Offset from    |
223     ///       TOS       | Contents
224     ///  ---------------|-------------------------------------------------------
225     ///       -0x28 - s | func_ref
226     ///       -0x30 - s | caller_vmctx
227     ///       -0x38 - s | args (of type *mut ArrayRef<ValRaw>)
228     ///       -0x40 - s | return_value_count
229     pub fn initialize(
230         &self,
231         func_ref: *const VMFuncRef,
232         caller_vmctx: *mut VMContext,
233         args: *mut VMHostArray<ValRaw>,
234         parameter_count: u32,
235         return_value_count: u32,
236     ) {
237         let tos = self.top;
238 
239         unsafe {
240             let store = |tos_neg_offset, value| {
241                 let target = tos.sub(tos_neg_offset).cast::<usize>();
242                 target.write(value)
243             };
244 
245             let args_ref = &mut *args;
246             let args_capacity = std::cmp::max(parameter_count, return_value_count);
247             // The args object must currently be empty.
248             debug_assert_eq!(args_ref.capacity, 0);
249             debug_assert_eq!(args_ref.length, 0);
250 
251             let args_data_size =
252                 usize::try_from(args_capacity).unwrap() * std::mem::size_of::<ValRaw>();
253             let args_data_ptr = if args_capacity == 0 {
254                 ptr::null_mut()
255             } else {
256                 tos.sub(0x20 + args_data_size)
257             };
258 
259             args_ref.capacity = args_capacity;
260             args_ref.data = args_data_ptr.cast::<ValRaw>();
261 
262             let to_store = [
263                 // Data near top of stack:
264                 (0x08, wasmtime_continuation_start as usize),
265                 (0x10, tos.sub(0x10).addr()),
266                 (0x18, tos.sub(0x40 + args_data_size).addr()),
267                 (0x20, usize::try_from(args_capacity).unwrap()),
268                 // Data after the args buffer:
269                 (0x28 + args_data_size, func_ref.addr()),
270                 (0x30 + args_data_size, caller_vmctx.addr()),
271                 (0x38 + args_data_size, args.addr()),
272                 (
273                     0x40 + args_data_size,
274                     usize::try_from(return_value_count).unwrap(),
275                 ),
276             ];
277 
278             for (offset, data) in to_store {
279                 store(offset, data);
280             }
281         }
282     }
283 }
284 
285 impl Drop for VMContinuationStack {
286     fn drop(&mut self) {
287         unsafe {
288             match self.allocator {
289                 Allocator::Mmap => {
290                     let ret = rustix::mm::munmap(self.top.sub(self.len) as _, self.len);
291                     debug_assert!(ret.is_ok());
292                 }
293                 Allocator::Custom => {} // It's the creator's responsibility to reclaim the memory.
294             }
295         }
296     }
297 }
298 
299 unsafe extern "C" {
300     #[allow(dead_code)] // only used in inline assembly for some platforms
301     fn wasmtime_continuation_start();
302 }
303 
304 /// This function is responsible for actually running a wasm function inside a
305 /// continuation. It is only ever called from `wasmtime_continuation_start`.
306 unsafe extern "C" fn fiber_start(
307     func_ref: *const VMFuncRef,
308     caller_vmctx: *mut VMContext,
309     args: *mut VMHostArray<ValRaw>,
310     return_value_count: u32,
311 ) {
312     unsafe {
313         let func_ref = func_ref.as_ref().expect("Non-null function reference");
314         let caller_vmxtx = NonNull::new_unchecked(caller_vmctx);
315         let args = &mut *args;
316         let params_and_returns: NonNull<[ValRaw]> = if args.capacity == 0 {
317             NonNull::from(&[])
318         } else {
319             std::slice::from_raw_parts_mut(args.data, usize::try_from(args.capacity).unwrap())
320                 .into()
321         };
322 
323         // NOTE(frank-emrich) The usage of the `caller_vmctx` is probably not
324         // 100% correct here. Currently, we determine the "caller" vmctx when
325         // initilizing the fiber stack/continuation (i.e. as part of
326         // `cont.new`). However, we may subsequenly `resume` the continuation
327         // from a different Wasm instance. The way to fix this would be to make
328         // the currently active `VMContext` an additional parameter of
329         // `wasmtime_continuation_switch` and pipe it through to this point. However,
330         // since the caller vmctx is only really used to access stuff in the
331         // underlying `Store`, it's fine to be slightly sloppy about the exact
332         // value we set.
333         //
334         // TODO(dhil): we are ignoring the boolean return value
335         // here... we probably shouldn't.
336         func_ref.array_call(None, caller_vmxtx, params_and_returns);
337 
338         // The array call trampoline should have just written
339         // `return_value_count` values to the `args` buffer. Let's reflect that
340         // in its length field, to make various bounds checks happy.
341         args.length = return_value_count;
342 
343         // Note that after this function returns, wasmtime_continuation_start
344         // will switch back to the parent stack.
345     }
346 }
347 
348 cfg_if::cfg_if! {
349     if #[cfg(target_arch = "x86_64")] {
350         mod x86_64;
351     } else {
352         // Note that this shoul be unreachable: In stack.rs, we currently select
353         // the module defined in the current file only if we are on unix AND
354         // x86_64.
355         compile_error!("the stack switching feature is not supported on this CPU architecture");
356     }
357 }
358