163d482c8SFrank Emrich //! The stack layout is expected to look like so:
263d482c8SFrank Emrich //!
363d482c8SFrank Emrich //!
463d482c8SFrank Emrich //! ```text
563d482c8SFrank Emrich //! 0xB000 +-----------------------+   <- top of stack (TOS)
663d482c8SFrank Emrich //!        | saved RIP             |
763d482c8SFrank Emrich //! 0xAff8 +-----------------------+
863d482c8SFrank Emrich //!        | saved RBP             |
963d482c8SFrank Emrich //! 0xAff0 +-----------------------+
1063d482c8SFrank Emrich //!        | saved RSP             |
1163d482c8SFrank Emrich //! 0xAfe8 +-----------------------+   <- beginning of "control context",
1263d482c8SFrank Emrich //!        | args_capacity         |
1363d482c8SFrank Emrich //! 0xAfe0 +-----------------------+
1463d482c8SFrank Emrich //!        | args buffer, size:    |
1563d482c8SFrank Emrich //!        | (16 * args_capacity)  |
1663d482c8SFrank Emrich //! 0xAfc0 +-----------------------+   <- below: beginning of usable stack space
1763d482c8SFrank Emrich //!        |                       |      (16-byte aligned)
1863d482c8SFrank Emrich //!        |                       |
1963d482c8SFrank Emrich //!        ~        ...            ~   <- actual native stack space to use
2063d482c8SFrank Emrich //!        |                       |
2163d482c8SFrank Emrich //! 0x1000 +-----------------------+
2263d482c8SFrank Emrich //!        |  guard page           |   <- (not currently enabled)
2363d482c8SFrank Emrich //! 0x0000 +-----------------------+
2463d482c8SFrank Emrich //! ```
2563d482c8SFrank Emrich //!
2663d482c8SFrank Emrich //! The "control context" indicates how to resume a computation. The layout is
2763d482c8SFrank Emrich //! determined by Cranelift's stack_switch instruction, which reads and writes
2863d482c8SFrank Emrich //! these fields. The fields are used as follows, where we distinguish two
2963d482c8SFrank Emrich //! cases:
3063d482c8SFrank Emrich //!
3163d482c8SFrank Emrich //! 1.
3263d482c8SFrank Emrich //! If the continuation is currently active (i.e., running directly, or ancestor
3363d482c8SFrank Emrich //! of the running continuation), it stores the PC, RSP, and RBP of the *parent*
3463d482c8SFrank Emrich //! of the running continuation.
3563d482c8SFrank Emrich //!
3663d482c8SFrank Emrich //! 2.
3763d482c8SFrank Emrich //! If the picture shows a suspended computation, the fields store the PC, RSP,
3863d482c8SFrank Emrich //! and RBP at the time of the suspension.
3963d482c8SFrank Emrich //!
4063d482c8SFrank Emrich //! Note that this design ensures that external tools can construct backtraces
4163d482c8SFrank Emrich //! in the presence of stack switching by using frame pointers only: The
4263d482c8SFrank Emrich //! wasmtime_continuation_start trampoline uses the address of the RBP field in the
4363d482c8SFrank Emrich //! control context (0xAff0 above) as its frame pointer. This means that when
4463d482c8SFrank Emrich //! passing the wasmtime_continuation_start frame while doing frame pointer walking,
4563d482c8SFrank Emrich //! the parent of that frame is the last frame in the parent of this
4663d482c8SFrank Emrich //! continuation.
4763d482c8SFrank Emrich //!
4863d482c8SFrank Emrich //! Wasmtime's own mechanism for constructing backtraces also relies on frame
4963d482c8SFrank Emrich //! pointer chains. However, it understands continuations and does not rely on
5063d482c8SFrank Emrich //! the trickery outlined here to go from the frames in one continuation to the
5163d482c8SFrank Emrich //! parent.
5263d482c8SFrank Emrich //!
5363d482c8SFrank Emrich //! The args buffer is used as follows: It is used by the array calling
5463d482c8SFrank Emrich //! trampoline to read and store the arguments and return values of the function
5563d482c8SFrank Emrich //! running inside the continuation. If this function has m parameters and n
5663d482c8SFrank Emrich //! return values, then args_capacity is defined as max(m, n) and the size of
5763d482c8SFrank Emrich //! the args buffer is args_capacity * 16 bytes. The start address (0xAfc0 in
5863d482c8SFrank Emrich //! the example above, thus assuming args_capacity = 2) is saved as the `data`
5963d482c8SFrank Emrich //! field of the VMContRef's `args` object.
6063d482c8SFrank Emrich 
6163d482c8SFrank Emrich use core::ptr::NonNull;
6263d482c8SFrank Emrich use std::io;
6363d482c8SFrank Emrich use std::ops::Range;
6463d482c8SFrank Emrich use std::ptr;
6563d482c8SFrank Emrich 
6663d482c8SFrank Emrich use crate::runtime::vm::stack_switching::VMHostArray;
678392736dSAlex Crichton use crate::runtime::vm::{VMContext, VMFuncRef, ValRaw};
6863d482c8SFrank Emrich 
6963d482c8SFrank Emrich #[derive(Debug, PartialEq, Eq)]
7063d482c8SFrank Emrich pub enum Allocator {
7163d482c8SFrank Emrich     Mmap,
7263d482c8SFrank Emrich     Custom,
7363d482c8SFrank Emrich }
7463d482c8SFrank Emrich 
7563d482c8SFrank Emrich #[derive(Debug)]
7663d482c8SFrank Emrich #[repr(C)]
7763d482c8SFrank Emrich pub struct VMContinuationStack {
7863d482c8SFrank Emrich     // The top of the stack; for stacks allocated by the fiber implementation itself,
7963d482c8SFrank Emrich     // the base address of the allocation will be `top.sub(len.unwrap())`
8063d482c8SFrank Emrich     top: *mut u8,
8163d482c8SFrank Emrich     // The length of the stack
8263d482c8SFrank Emrich     len: usize,
8363d482c8SFrank Emrich     // allocation strategy
8463d482c8SFrank Emrich     allocator: Allocator,
8563d482c8SFrank Emrich }
8663d482c8SFrank Emrich 
8763d482c8SFrank Emrich impl VMContinuationStack {
new(size: usize) -> io::Result<Self>8863d482c8SFrank Emrich     pub fn new(size: usize) -> io::Result<Self> {
8963d482c8SFrank Emrich         // Round up our stack size request to the nearest multiple of the
9063d482c8SFrank Emrich         // page size.
9163d482c8SFrank Emrich         let page_size = rustix::param::page_size();
9263d482c8SFrank Emrich         let size = if size == 0 {
9363d482c8SFrank Emrich             page_size
9463d482c8SFrank Emrich         } else {
9563d482c8SFrank Emrich             size.next_multiple_of(page_size)
9663d482c8SFrank Emrich         };
9763d482c8SFrank Emrich 
9863d482c8SFrank Emrich         unsafe {
9963d482c8SFrank Emrich             // Add in one page for a guard page and then ask for some memory.
10063d482c8SFrank Emrich             let mmap_len = size + page_size;
10163d482c8SFrank Emrich             let mmap = rustix::mm::mmap_anonymous(
10263d482c8SFrank Emrich                 ptr::null_mut(),
10363d482c8SFrank Emrich                 mmap_len,
10463d482c8SFrank Emrich                 rustix::mm::ProtFlags::empty(),
10563d482c8SFrank Emrich                 rustix::mm::MapFlags::PRIVATE,
10663d482c8SFrank Emrich             )?;
10763d482c8SFrank Emrich 
10863d482c8SFrank Emrich             rustix::mm::mprotect(
10963d482c8SFrank Emrich                 mmap.cast::<u8>().add(page_size).cast(),
11063d482c8SFrank Emrich                 size,
11163d482c8SFrank Emrich                 rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
11263d482c8SFrank Emrich             )?;
11363d482c8SFrank Emrich 
11463d482c8SFrank Emrich             Ok(Self {
11563d482c8SFrank Emrich                 top: mmap.cast::<u8>().add(mmap_len),
11663d482c8SFrank Emrich                 len: mmap_len,
11763d482c8SFrank Emrich                 allocator: Allocator::Mmap,
11863d482c8SFrank Emrich             })
11963d482c8SFrank Emrich         }
12063d482c8SFrank Emrich     }
12163d482c8SFrank Emrich 
unallocated() -> Self12263d482c8SFrank Emrich     pub fn unallocated() -> Self {
12363d482c8SFrank Emrich         Self {
12463d482c8SFrank Emrich             top: std::ptr::null_mut(),
12563d482c8SFrank Emrich             len: 0,
12663d482c8SFrank Emrich             allocator: Allocator::Custom,
12763d482c8SFrank Emrich         }
12863d482c8SFrank Emrich     }
12963d482c8SFrank Emrich 
is_unallocated(&self) -> bool13063d482c8SFrank Emrich     pub fn is_unallocated(&self) -> bool {
13163d482c8SFrank Emrich         debug_assert_eq!(self.len == 0, self.top == std::ptr::null_mut());
13263d482c8SFrank Emrich         self.len == 0
13363d482c8SFrank Emrich     }
13463d482c8SFrank Emrich 
from_raw_parts( base: *mut u8, _guard_size: usize, len: usize, ) -> io::Result<Self>13563d482c8SFrank Emrich     pub unsafe fn from_raw_parts(
13663d482c8SFrank Emrich         base: *mut u8,
13763d482c8SFrank Emrich         _guard_size: usize,
13863d482c8SFrank Emrich         len: usize,
13963d482c8SFrank Emrich     ) -> io::Result<Self> {
14063d482c8SFrank Emrich         Ok(Self {
14135786823SAlex Crichton             top: unsafe { base.add(len) },
14263d482c8SFrank Emrich             len,
14363d482c8SFrank Emrich             allocator: Allocator::Custom,
14463d482c8SFrank Emrich         })
14563d482c8SFrank Emrich     }
14663d482c8SFrank Emrich 
is_from_raw_parts(&self) -> bool14763d482c8SFrank Emrich     pub fn is_from_raw_parts(&self) -> bool {
14863d482c8SFrank Emrich         self.allocator == Allocator::Custom
14963d482c8SFrank Emrich     }
15063d482c8SFrank Emrich 
top(&self) -> Option<*mut u8>15163d482c8SFrank Emrich     pub fn top(&self) -> Option<*mut u8> {
15263d482c8SFrank Emrich         Some(self.top)
15363d482c8SFrank Emrich     }
15463d482c8SFrank Emrich 
range(&self) -> Option<Range<usize>>15563d482c8SFrank Emrich     pub fn range(&self) -> Option<Range<usize>> {
15663d482c8SFrank Emrich         let base = unsafe { self.top.sub(self.len).addr() };
15763d482c8SFrank Emrich         Some(base..base + self.len)
15863d482c8SFrank Emrich     }
15963d482c8SFrank Emrich 
control_context_instruction_pointer(&self) -> usize16063d482c8SFrank Emrich     pub fn control_context_instruction_pointer(&self) -> usize {
16163d482c8SFrank Emrich         // See picture at top of this file:
16263d482c8SFrank Emrich         // RIP is stored 8 bytes below top of stack.
16363d482c8SFrank Emrich         unsafe {
16463d482c8SFrank Emrich             let ptr = self.top.sub(8).cast::<usize>();
16563d482c8SFrank Emrich             *ptr
16663d482c8SFrank Emrich         }
16763d482c8SFrank Emrich     }
16863d482c8SFrank Emrich 
control_context_frame_pointer(&self) -> usize16963d482c8SFrank Emrich     pub fn control_context_frame_pointer(&self) -> usize {
17063d482c8SFrank Emrich         // See picture at top of this file:
17163d482c8SFrank Emrich         // RBP is stored 16 bytes below top of stack.
17263d482c8SFrank Emrich         unsafe {
17363d482c8SFrank Emrich             let ptr = self.top.sub(16).cast::<usize>();
17463d482c8SFrank Emrich             *ptr
17563d482c8SFrank Emrich         }
17663d482c8SFrank Emrich     }
17763d482c8SFrank Emrich 
control_context_stack_pointer(&self) -> usize17863d482c8SFrank Emrich     pub fn control_context_stack_pointer(&self) -> usize {
17963d482c8SFrank Emrich         // See picture at top of this file:
18063d482c8SFrank Emrich         // RSP is stored 24 bytes below top of stack.
18163d482c8SFrank Emrich         unsafe {
18263d482c8SFrank Emrich             let ptr = self.top.sub(24).cast::<usize>();
18363d482c8SFrank Emrich             *ptr
18463d482c8SFrank Emrich         }
18563d482c8SFrank Emrich     }
18663d482c8SFrank Emrich 
18763d482c8SFrank Emrich     /// This function installs the launchpad for the computation to run on the
18863d482c8SFrank Emrich     /// fiber, such that executing a `stack_switch` instruction on the stack
18963d482c8SFrank Emrich     /// actually runs the desired computation.
19063d482c8SFrank Emrich     ///
19163d482c8SFrank Emrich     /// Concretely, switching to the stack prepared by this function
19263d482c8SFrank Emrich     /// causes that we enter `wasmtime_continuation_start`, which then in turn
19363d482c8SFrank Emrich     /// calls `fiber_start` with  the following arguments:
19463d482c8SFrank Emrich     /// TOS, func_ref, caller_vmctx, args_ptr, args_capacity
19563d482c8SFrank Emrich     ///
19663d482c8SFrank Emrich     /// Note that at this point we also allocate the args buffer
19763d482c8SFrank Emrich     /// (see picture at the top of this file).
19863d482c8SFrank Emrich     /// We define `args_capacity` as the max of parameter and return value count.
19963d482c8SFrank Emrich     /// Then the size s of the actual buffer size is calculated as follows:
20063d482c8SFrank Emrich     /// s = size_of(ValRaw) * `args_capacity`,
20163d482c8SFrank Emrich     ///
20263d482c8SFrank Emrich     /// Note that this value is used below, and we may have s = 0.
20363d482c8SFrank Emrich     ///
20463d482c8SFrank Emrich     /// The layout of the VMContinuationStack near the top of stack (TOS)
20563d482c8SFrank Emrich     /// *after* running this function is as follows:
20663d482c8SFrank Emrich     ///
20763d482c8SFrank Emrich     ///
20863d482c8SFrank Emrich     ///  Offset from    |
20963d482c8SFrank Emrich     ///       TOS       | Contents
21063d482c8SFrank Emrich     ///  ---------------|-------------------------------------------------------
21163d482c8SFrank Emrich     ///       -0x08     | address of wasmtime_continuation_start function (future PC)
21263d482c8SFrank Emrich     ///       -0x10     | TOS - 0x10 (future RBP)
21363d482c8SFrank Emrich     ///       -0x18     | TOS - 0x40 - s (future RSP)
21463d482c8SFrank Emrich     ///       -0x20     | args_capacity
21563d482c8SFrank Emrich     ///
21663d482c8SFrank Emrich     ///
21763d482c8SFrank Emrich     /// The data stored behind the args buffer is as follows:
21863d482c8SFrank Emrich     ///
21963d482c8SFrank Emrich     ///  Offset from    |
22063d482c8SFrank Emrich     ///       TOS       | Contents
22163d482c8SFrank Emrich     ///  ---------------|-------------------------------------------------------
22263d482c8SFrank Emrich     ///       -0x28 - s | func_ref
22363d482c8SFrank Emrich     ///       -0x30 - s | caller_vmctx
22463d482c8SFrank Emrich     ///       -0x38 - s | args (of type *mut ArrayRef<ValRaw>)
22563d482c8SFrank Emrich     ///       -0x40 - s | return_value_count
initialize( &self, func_ref: *const VMFuncRef, caller_vmctx: *mut VMContext, args: *mut VMHostArray<ValRaw>, parameter_count: u32, return_value_count: u32, )22663d482c8SFrank Emrich     pub fn initialize(
22763d482c8SFrank Emrich         &self,
22863d482c8SFrank Emrich         func_ref: *const VMFuncRef,
22963d482c8SFrank Emrich         caller_vmctx: *mut VMContext,
23063d482c8SFrank Emrich         args: *mut VMHostArray<ValRaw>,
23163d482c8SFrank Emrich         parameter_count: u32,
23263d482c8SFrank Emrich         return_value_count: u32,
23363d482c8SFrank Emrich     ) {
23463d482c8SFrank Emrich         let tos = self.top;
23563d482c8SFrank Emrich 
23663d482c8SFrank Emrich         unsafe {
23763d482c8SFrank Emrich             let store = |tos_neg_offset, value| {
23863d482c8SFrank Emrich                 let target = tos.sub(tos_neg_offset).cast::<usize>();
23963d482c8SFrank Emrich                 target.write(value)
24063d482c8SFrank Emrich             };
24163d482c8SFrank Emrich 
24263d482c8SFrank Emrich             let args_ref = &mut *args;
24363d482c8SFrank Emrich             let args_capacity = std::cmp::max(parameter_count, return_value_count);
24463d482c8SFrank Emrich             // The args object must currently be empty.
24563d482c8SFrank Emrich             debug_assert_eq!(args_ref.capacity, 0);
24663d482c8SFrank Emrich             debug_assert_eq!(args_ref.length, 0);
24763d482c8SFrank Emrich 
24863d482c8SFrank Emrich             let args_data_size =
24963d482c8SFrank Emrich                 usize::try_from(args_capacity).unwrap() * std::mem::size_of::<ValRaw>();
25063d482c8SFrank Emrich             let args_data_ptr = if args_capacity == 0 {
25163d482c8SFrank Emrich                 ptr::null_mut()
25263d482c8SFrank Emrich             } else {
25363d482c8SFrank Emrich                 tos.sub(0x20 + args_data_size)
25463d482c8SFrank Emrich             };
25563d482c8SFrank Emrich 
25663d482c8SFrank Emrich             args_ref.capacity = args_capacity;
25763d482c8SFrank Emrich             args_ref.data = args_data_ptr.cast::<ValRaw>();
25863d482c8SFrank Emrich 
25963d482c8SFrank Emrich             let to_store = [
26063d482c8SFrank Emrich                 // Data near top of stack:
261*fe12d384SAlex Crichton                 (0x08, wasmtime_continuation_start_address().addr()),
26263d482c8SFrank Emrich                 (0x10, tos.sub(0x10).addr()),
26363d482c8SFrank Emrich                 (0x18, tos.sub(0x40 + args_data_size).addr()),
26463d482c8SFrank Emrich                 (0x20, usize::try_from(args_capacity).unwrap()),
26563d482c8SFrank Emrich                 // Data after the args buffer:
26663d482c8SFrank Emrich                 (0x28 + args_data_size, func_ref.addr()),
26763d482c8SFrank Emrich                 (0x30 + args_data_size, caller_vmctx.addr()),
26863d482c8SFrank Emrich                 (0x38 + args_data_size, args.addr()),
26963d482c8SFrank Emrich                 (
27063d482c8SFrank Emrich                     0x40 + args_data_size,
27163d482c8SFrank Emrich                     usize::try_from(return_value_count).unwrap(),
27263d482c8SFrank Emrich                 ),
27363d482c8SFrank Emrich             ];
27463d482c8SFrank Emrich 
27563d482c8SFrank Emrich             for (offset, data) in to_store {
27663d482c8SFrank Emrich                 store(offset, data);
27763d482c8SFrank Emrich             }
27863d482c8SFrank Emrich         }
27963d482c8SFrank Emrich     }
28063d482c8SFrank Emrich }
28163d482c8SFrank Emrich 
28263d482c8SFrank Emrich impl Drop for VMContinuationStack {
drop(&mut self)28363d482c8SFrank Emrich     fn drop(&mut self) {
28463d482c8SFrank Emrich         unsafe {
28563d482c8SFrank Emrich             match self.allocator {
28663d482c8SFrank Emrich                 Allocator::Mmap => {
28763d482c8SFrank Emrich                     let ret = rustix::mm::munmap(self.top.sub(self.len) as _, self.len);
28863d482c8SFrank Emrich                     debug_assert!(ret.is_ok());
28963d482c8SFrank Emrich                 }
29063d482c8SFrank Emrich                 Allocator::Custom => {} // It's the creator's responsibility to reclaim the memory.
29163d482c8SFrank Emrich             }
29263d482c8SFrank Emrich         }
29363d482c8SFrank Emrich     }
29463d482c8SFrank Emrich }
29563d482c8SFrank Emrich 
29663d482c8SFrank Emrich /// This function is responsible for actually running a wasm function inside a
29763d482c8SFrank Emrich /// continuation. It is only ever called from `wasmtime_continuation_start`.
fiber_start( func_ref: *mut VMFuncRef, caller_vmctx: *mut VMContext, args: *mut VMHostArray<ValRaw>, return_value_count: u32, )29863d482c8SFrank Emrich unsafe extern "C" fn fiber_start(
299078bc37bSAlex Crichton     func_ref: *mut VMFuncRef,
30063d482c8SFrank Emrich     caller_vmctx: *mut VMContext,
30163d482c8SFrank Emrich     args: *mut VMHostArray<ValRaw>,
30263d482c8SFrank Emrich     return_value_count: u32,
30363d482c8SFrank Emrich ) {
30463d482c8SFrank Emrich     unsafe {
305078bc37bSAlex Crichton         let func_ref = NonNull::new(func_ref).unwrap();
3068392736dSAlex Crichton         let caller_vmxtx = NonNull::new_unchecked(caller_vmctx);
30763d482c8SFrank Emrich         let args = &mut *args;
30863d482c8SFrank Emrich         let params_and_returns: NonNull<[ValRaw]> = if args.capacity == 0 {
30963d482c8SFrank Emrich             NonNull::from(&[])
31063d482c8SFrank Emrich         } else {
31163d482c8SFrank Emrich             std::slice::from_raw_parts_mut(args.data, usize::try_from(args.capacity).unwrap())
31263d482c8SFrank Emrich                 .into()
31363d482c8SFrank Emrich         };
31463d482c8SFrank Emrich 
31563d482c8SFrank Emrich         // NOTE(frank-emrich) The usage of the `caller_vmctx` is probably not
31663d482c8SFrank Emrich         // 100% correct here. Currently, we determine the "caller" vmctx when
317a7b5a912Sleopardracer         // initializing the fiber stack/continuation (i.e. as part of
31863d482c8SFrank Emrich         // `cont.new`). However, we may subsequenly `resume` the continuation
31963d482c8SFrank Emrich         // from a different Wasm instance. The way to fix this would be to make
32063d482c8SFrank Emrich         // the currently active `VMContext` an additional parameter of
32163d482c8SFrank Emrich         // `wasmtime_continuation_switch` and pipe it through to this point. However,
32263d482c8SFrank Emrich         // since the caller vmctx is only really used to access stuff in the
32363d482c8SFrank Emrich         // underlying `Store`, it's fine to be slightly sloppy about the exact
32463d482c8SFrank Emrich         // value we set.
32563d482c8SFrank Emrich         //
32663d482c8SFrank Emrich         // TODO(dhil): we are ignoring the boolean return value
32763d482c8SFrank Emrich         // here... we probably shouldn't.
328078bc37bSAlex Crichton         VMFuncRef::array_call(func_ref, None, caller_vmxtx, params_and_returns);
32963d482c8SFrank Emrich 
33063d482c8SFrank Emrich         // The array call trampoline should have just written
33163d482c8SFrank Emrich         // `return_value_count` values to the `args` buffer. Let's reflect that
33263d482c8SFrank Emrich         // in its length field, to make various bounds checks happy.
33363d482c8SFrank Emrich         args.length = return_value_count;
33463d482c8SFrank Emrich 
33563d482c8SFrank Emrich         // Note that after this function returns, wasmtime_continuation_start
33663d482c8SFrank Emrich         // will switch back to the parent stack.
33763d482c8SFrank Emrich     }
33863d482c8SFrank Emrich }
33963d482c8SFrank Emrich 
34063d482c8SFrank Emrich cfg_if::cfg_if! {
34163d482c8SFrank Emrich     if #[cfg(target_arch = "x86_64")] {
34263d482c8SFrank Emrich         mod x86_64;
3433e9eca8bSAlex Crichton         use x86_64::*;
34463d482c8SFrank Emrich     } else {
345b221fca7SJoel Dice         // Note that this should be unreachable: In stack.rs, we currently select
34663d482c8SFrank Emrich         // the module defined in the current file only if we are on unix AND
34763d482c8SFrank Emrich         // x86_64.
34863d482c8SFrank Emrich         compile_error!("the stack switching feature is not supported on this CPU architecture");
34963d482c8SFrank Emrich     }
35063d482c8SFrank Emrich }
351