1 //! This module contains the runtime components of the implementation of the
2 //! stack switching proposal.
3 
4 mod stack;
5 
6 use core::{marker::PhantomPinned, ptr::NonNull};
7 
8 pub use stack::*;
9 
10 /// A continuation object is a handle to a continuation reference
11 /// (i.e. an actual stack). A continuation object only be consumed
12 /// once. The linearity is checked dynamically in the generated code
13 /// by comparing the revision witness embedded in the pointer to the
14 /// actual revision counter on the continuation reference.
15 ///
16 /// In the optimized implementation, the continuation logically
17 /// represented by a VMContObj not only encompasses the pointed-to
18 /// VMContRef, but also all of its parents:
19 ///
20 /// ```text
21 ///
22 ///                     +----------------+
23 ///                 +-->|   VMContRef    |
24 ///                 |   +----------------+
25 ///                 |            ^
26 ///                 |            | parent
27 ///                 |            |
28 ///                 |   +----------------+
29 ///                 |   |   VMContRef    |
30 ///                 |   +----------------+
31 ///                 |            ^
32 ///                 |            | parent
33 ///  last ancestor  |            |
34 ///                 |   +----------------+
35 ///                 +---|   VMContRef    |    <--  VMContObj
36 ///                     +----------------+
37 /// ```
38 ///
39 /// For performance reasons, the VMContRef at the bottom of this chain
40 /// (i.e., the one pointed to by the VMContObj) has a pointer to the
41 /// other end of the chain (i.e., its last ancestor).
42 #[repr(C)]
43 #[derive(Debug, Clone, Copy)]
44 pub struct VMContObj {
45     pub contref: NonNull<VMContRef>,
46     pub revision: usize,
47 }
48 
49 impl VMContObj {
new(contref: NonNull<VMContRef>, revision: usize) -> Self50     pub fn new(contref: NonNull<VMContRef>, revision: usize) -> Self {
51         Self { contref, revision }
52     }
53 
54     /// Construction a VMContinuationObject from a pointer and revision
55     ///
56     /// The `contref` pointer may be null in which case None will be returned.
57     ///
58     /// # Safety
59     ///
60     /// Behavior will be undefined if a pointer to data that is not a
61     /// VMContRef is provided.
from_raw_parts(contref: *mut u8, revision: usize) -> Option<Self>62     pub unsafe fn from_raw_parts(contref: *mut u8, revision: usize) -> Option<Self> {
63         NonNull::new(contref.cast::<VMContRef>()).map(|contref| Self::new(contref, revision))
64     }
65 }
66 
67 unsafe impl Send for VMContObj {}
68 unsafe impl Sync for VMContObj {}
69 
70 /// This type is used to save (and subsequently restore) a subset of the data in
71 /// `VMStoreContext`. See documentation of `VMStackChain` for the exact uses.
72 #[repr(C)]
73 #[derive(Debug, Default, Clone)]
74 pub struct VMStackLimits {
75     /// Saved version of `stack_limit` field of `VMStoreContext`
76     pub stack_limit: usize,
77     /// Saved version of `last_wasm_entry_fp` field of `VMStoreContext`
78     pub last_wasm_entry_fp: usize,
79 }
80 
81 /// This type represents "common" information that we need to save both for the
82 /// initial stack and each continuation.
83 #[repr(C)]
84 #[derive(Debug, Clone)]
85 pub struct VMCommonStackInformation {
86     /// Saves subset of `VMStoreContext` for this stack. See documentation of
87     /// `VMStackChain` for the exact uses.
88     pub limits: VMStackLimits,
89     /// For the initial stack, this field must only have one of the following values:
90     /// - Running
91     /// - Parent
92     pub state: VMStackState,
93 
94     /// Only in use when state is `Parent`. Otherwise, the list must be empty.
95     ///
96     /// Represents the handlers that this stack installed when resume-ing a
97     /// continuation.
98     ///
99     /// Note that for any resume instruction, we can re-order the handler
100     /// clauses without changing behavior such that all the suspend handlers
101     /// come first, followed by all the switch handler (while maintaining the
102     /// original ordering within the two groups).
103     /// Thus, we assume that the given resume instruction has the following
104     /// shape:
105     ///
106     /// (resume $ct
107     ///   (on $tag_0 $block_0) ... (on $tag_{n-1} $block_{n-1})
108     ///   (on $tag_n switch) ... (on $tag_m switch)
109     /// )
110     ///
111     /// On resume, the handler list is then filled with m + 1 (i.e., one per
112     /// handler clause) entries such that the i-th entry, using 0-based
113     /// indexing, is the identifier of $tag_i (represented as *mut
114     /// VMTagDefinition).
115     /// Further, `first_switch_handler_index` (see below) is set to n (i.e., the
116     /// 0-based index of the first switch handler).
117     ///
118     /// Note that the actual data buffer (i.e., the one `handler.data` points
119     /// to) is always allocated on the stack that this `CommonStackInformation`
120     /// struct describes.
121     pub handlers: VMHandlerList,
122 
123     /// Only used when state is `Parent`. See documentation of `handlers` above.
124     pub first_switch_handler_index: u32,
125 }
126 
127 impl VMCommonStackInformation {
128     /// Default value with state set to `Running`
running_default() -> Self129     pub fn running_default() -> Self {
130         Self {
131             limits: VMStackLimits::default(),
132             state: VMStackState::Running,
133             handlers: VMHandlerList::empty(),
134             first_switch_handler_index: 0,
135         }
136     }
137 }
138 
139 impl VMStackLimits {
140     /// Default value, but uses the given value for `stack_limit`.
with_stack_limit(stack_limit: usize) -> Self141     pub fn with_stack_limit(stack_limit: usize) -> Self {
142         Self {
143             stack_limit,
144             ..Default::default()
145         }
146     }
147 }
148 
149 #[repr(C)]
150 #[derive(Debug, Clone)]
151 /// Reference to a stack-allocated buffer ("array"), storing data of some type
152 /// `T`.
153 pub struct VMHostArray<T> {
154     /// Number of currently occupied slots.
155     pub length: u32,
156     /// Number of slots in the data buffer. Note that this is *not* the size of
157     /// the buffer in bytes!
158     pub capacity: u32,
159     /// The actual data buffer
160     pub data: *mut T,
161 }
162 
163 impl<T> VMHostArray<T> {
164     /// Creates empty `Array`
empty() -> Self165     pub fn empty() -> Self {
166         Self {
167             length: 0,
168             capacity: 0,
169             data: core::ptr::null_mut(),
170         }
171     }
172 
173     /// Makes `Array` empty.
clear(&mut self)174     pub fn clear(&mut self) {
175         *self = Self::empty();
176     }
177 }
178 
179 /// Type used for passing payloads to and from continuations. The actual type
180 /// argument should be wasmtime::runtime::vm::vmcontext::ValRaw, but we don't
181 /// have access to that here.
182 pub type VMPayloads = VMHostArray<u128>;
183 
184 /// Type for a list of handlers, represented by the handled tag. Thus, the
185 /// stored data is actually `*mut VMTagDefinition`, but we don't have access to
186 /// that here.
187 pub type VMHandlerList = VMHostArray<*mut u8>;
188 
189 /// The main type representing a continuation.
190 #[repr(C)]
191 pub struct VMContRef {
192     /// The `CommonStackInformation` of this continuation's stack.
193     pub common_stack_information: VMCommonStackInformation,
194 
195     /// The parent of this continuation, which may be another continuation, the
196     /// initial stack, or absent (in case of a suspended continuation).
197     pub parent_chain: VMStackChain,
198 
199     /// Only used if `common_stack_information.state` is `Suspended` or `Fresh`. In
200     /// that case, this points to the end of the stack chain (i.e., the
201     /// continuation in the parent chain whose own `parent_chain` field is
202     /// `VMStackChain::Absent`).
203     /// Note that this may be a pointer to itself (if the state is `Fresh`, this is always the case).
204     pub last_ancestor: *mut VMContRef,
205 
206     /// Revision counter.
207     pub revision: usize,
208 
209     /// The underlying stack.
210     pub stack: VMContinuationStack,
211 
212     /// Used to store only
213     /// 1. The arguments to the function passed to cont.new
214     /// 2. The return values of that function
215     ///
216     /// Note that the actual data buffer (i.e., the one `args.data` points
217     /// to) is always allocated on this continuation's stack.
218     pub args: VMPayloads,
219 
220     /// Once a continuation has been suspended (using suspend or switch),
221     /// this buffer is used to pass payloads to and from the continuation.
222     /// More concretely, it is used to
223     /// - Pass payloads from a suspend instruction to the corresponding handler.
224     /// - Pass payloads to a continuation using cont.bind or resume
225     /// - Pass payloads to the continuation being switched to when using switch.
226     ///
227     /// Note that the actual data buffer (i.e., the one `values.data` points
228     /// to) is always allocated on this continuation's stack.
229     pub values: VMPayloads,
230 
231     /// Tell the compiler that this structure has potential self-references
232     /// through the `last_ancestor` pointer.
233     _marker: core::marker::PhantomPinned,
234 }
235 
236 impl VMContRef {
fiber_stack(&self) -> &VMContinuationStack237     pub fn fiber_stack(&self) -> &VMContinuationStack {
238         &self.stack
239     }
240 
detach_stack(&mut self) -> VMContinuationStack241     pub fn detach_stack(&mut self) -> VMContinuationStack {
242         core::mem::replace(&mut self.stack, VMContinuationStack::unallocated())
243     }
244 
245     /// This is effectively a `Default` implementation, without calling it
246     /// so. Used to create `VMContRef`s when initializing pooling allocator.
empty() -> Self247     pub fn empty() -> Self {
248         let limits = VMStackLimits::with_stack_limit(Default::default());
249         let state = VMStackState::Fresh;
250         let handlers = VMHandlerList::empty();
251         let common_stack_information = VMCommonStackInformation {
252             limits,
253             state,
254             handlers,
255             first_switch_handler_index: 0,
256         };
257         let parent_chain = VMStackChain::Absent;
258         let last_ancestor = core::ptr::null_mut();
259         let stack = VMContinuationStack::unallocated();
260         let args = VMPayloads::empty();
261         let values = VMPayloads::empty();
262         let revision = 0;
263         let _marker = PhantomPinned;
264 
265         Self {
266             common_stack_information,
267             parent_chain,
268             last_ancestor,
269             stack,
270             args,
271             values,
272             revision,
273             _marker,
274         }
275     }
276 }
277 
278 impl Drop for VMContRef {
drop(&mut self)279     fn drop(&mut self) {
280         // Note that continuation references do not own their parents, and we
281         // don't drop them here.
282 
283         // We would like to enforce the invariant that any continuation that
284         // was created for a cont.new (rather than, say, just living in a
285         // pool and never being touched), either ran to completion or was
286         // cancelled. But failing to do so should yield a custom error,
287         // instead of panicking here.
288     }
289 }
290 
291 // These are required so the WasmFX pooling allocator can store a Vec of
292 // `VMContRef`s.
293 unsafe impl Send for VMContRef {}
294 unsafe impl Sync for VMContRef {}
295 
296 /// Implements `cont.new` instructions (i.e., creation of continuations).
297 #[cfg(feature = "stack-switching")]
298 #[inline(always)]
cont_new( store: &mut dyn crate::vm::VMStore, instance: crate::store::InstanceId, func: *mut u8, param_count: u32, result_count: u32, ) -> crate::Result<*mut VMContRef>299 pub fn cont_new(
300     store: &mut dyn crate::vm::VMStore,
301     instance: crate::store::InstanceId,
302     func: *mut u8,
303     param_count: u32,
304     result_count: u32,
305 ) -> crate::Result<*mut VMContRef> {
306     let instance = store.instance_mut(instance);
307     let caller_vmctx = instance.vmctx();
308 
309     let stack_size = store.engine().config().async_stack_size;
310 
311     let contref = store.allocate_continuation()?;
312     let contref = unsafe { contref.as_mut().unwrap() };
313 
314     let tsp = contref.stack.top().unwrap();
315     contref.parent_chain = VMStackChain::Absent;
316     // The continuation is fresh, which is a special case of being suspended.
317     // Thus we need to set the correct end of the continuation chain: itself.
318     contref.last_ancestor = contref;
319 
320     // The initialization function will allocate the actual args/return value buffer and
321     // update this object (if needed).
322     let contref_args_ptr = &mut contref.args as *mut _ as *mut VMHostArray<crate::ValRaw>;
323 
324     contref.stack.initialize(
325         func.cast::<crate::vm::VMFuncRef>(),
326         caller_vmctx.as_ptr(),
327         contref_args_ptr,
328         param_count,
329         result_count,
330     );
331 
332     // Now that the initial stack pointer was set by the initialization
333     // function, use it to determine stack limit.
334     let stack_pointer = contref.stack.control_context_stack_pointer();
335     // Same caveat regarding stack_limit here as described in
336     // `wasmtime::runtime::func::EntryStoreContext::enter_wasm`.
337     let wasm_stack_limit = core::cmp::max(
338         stack_pointer - store.engine().config().max_wasm_stack,
339         tsp as usize - stack_size,
340     );
341     let limits = VMStackLimits::with_stack_limit(wasm_stack_limit);
342     let csi = &mut contref.common_stack_information;
343     csi.state = VMStackState::Fresh;
344     csi.limits = limits;
345 
346     log::trace!("Created contref @ {contref:p}");
347     Ok(contref)
348 }
349 
350 /// This type represents a linked lists ("chain") of stacks, where the a
351 /// node's successor denotes its parent.
352 /// Additionally, a `CommonStackInformation` object is associated with
353 /// each stack in the list.
354 /// Here, a "stack" is one of the following:
355 /// - A continuation (i.e., created with cont.new).
356 /// - The initial stack. This is the stack that we were on when entering
357 ///   Wasm (i.e., when executing
358 ///   `crate::runtime::func::invoke_wasm_and_catch_traps`).
359 ///   This stack never has a parent.
360 ///   In terms of the memory allocation that this stack resides on, it will
361 ///   usually be the main stack, but doesn't have to: If we are running
362 ///   inside a continuation while executing a host call, which in turn
363 ///   re-renters Wasm, the initial stack is actually the stack of that
364 ///   continuation.
365 ///
366 /// Note that the linked list character of `VMStackChain` arises from the fact
367 /// that `VMStackChain::Continuation` variants have a pointer to a
368 /// `VMContRef`, which in turn has a `parent_chain` value of type
369 /// `VMStackChain`. This is how the stack chain reflects the parent-child
370 /// relationships between continuations/stacks. This also shows how the
371 /// initial stack (mentioned above) cannot have a parent.
372 ///
373 /// There are generally two uses of `VMStackChain`:
374 ///
375 /// 1. The `stack_chain` field in the `StoreOpaque` contains such a
376 /// chain of stacks, where the head of the list denotes the stack that is
377 /// currently executing (either a continuation or the initial stack). Note
378 /// that in this case, the linked list must contain 0 or more `Continuation`
379 /// elements, followed by a final `InitialStack` element. In particular,
380 /// this list always ends with `InitialStack` and never contains an `Absent`
381 /// variant.
382 ///
383 /// 2. When a continuation is suspended, its chain of parents eventually
384 /// ends with an `Absent` variant in its `parent_chain` field. Note that a
385 /// suspended continuation never appears in the stack chain in the
386 /// VMContext!
387 ///
388 ///
389 /// As mentioned before, each stack in a `VMStackChain` has a corresponding
390 /// `CommonStackInformation` object. For continuations, this is stored in
391 /// the `common_stack_information` field of the corresponding `VMContRef`.
392 /// For the initial stack, the `InitialStack` variant contains a pointer to
393 /// a `CommonStackInformation`. The latter will be allocated allocated on
394 /// the stack frame that executed by `invoke_wasm_and_catch_traps`.
395 ///
396 /// The following invariants hold for these `VMStackLimits` objects,
397 /// and the data in `VMStoreContext`.
398 ///
399 /// Currently executing stack: For the currently executing stack (i.e., the
400 /// stack that is at the head of the store's `stack_chain` list), the
401 /// associated `VMStackLimits` object contains stale/undefined data. Instead,
402 /// the live data describing the limits for the currently executing stack is
403 /// always maintained in `VMStoreContext`. Note that as a general rule
404 /// independently from any execution of continuations, the `last_wasm_exit*`
405 /// fields in the `VMStoreContext` contain undefined values while executing
406 /// wasm.
407 ///
408 /// Parents of currently executing stack: For stacks that appear in the tail
409 /// of the store's `stack_chain` list (i.e., stacks that are not currently
410 /// executing themselves, but are an ancestor of the currently executing
411 /// stack), we have the following: All the fields in the stack's
412 /// `VMStackLimits` are valid, describing the stack's stack limit, and
413 /// pointers where executing for that stack entered and exited WASM.
414 ///
415 /// Suspended continuations: For suspended continuations (including their
416 /// ancestors), we have the following. Note that the initial stack can never
417 /// be in this state. The `stack_limit` and `last_enter_wasm_sp` fields of
418 /// the corresponding `VMStackLimits` object contain valid data, while the
419 /// `last_exit_wasm_*` fields contain arbitrary values. There is only one
420 /// exception to this: Note that a continuation that has been created with
421 /// cont.new, but never been resumed so far, is considered "suspended".
422 /// However, its `last_enter_wasm_sp` field contains undefined data. This is
423 /// justified, because when resume-ing a continuation for the first time, a
424 /// native-to-wasm trampoline is called, which sets up the
425 /// `last_wasm_entry_sp` in the `VMStoreContext` with the correct value,
426 /// thus restoring the necessary invariant.
427 #[derive(Debug, Clone, PartialEq)]
428 #[repr(usize, C)]
429 pub enum VMStackChain {
430     /// For suspended continuations, denotes the end of their chain of
431     /// ancestors.
432     Absent = wasmtime_environ::STACK_CHAIN_ABSENT_DISCRIMINANT,
433     /// Represents the initial stack (i.e., where we entered Wasm from the
434     /// host by executing
435     /// `crate::runtime::func::invoke_wasm_and_catch_traps`). Therefore, it
436     /// does not have a parent. The `CommonStackInformation` that this
437     /// variant points to is stored in the stack frame of
438     /// `invoke_wasm_and_catch_traps`.
439     InitialStack(*mut VMCommonStackInformation) =
440         wasmtime_environ::STACK_CHAIN_INITIAL_STACK_DISCRIMINANT,
441     /// Represents a continuation's stack.
442     Continuation(*mut VMContRef) = wasmtime_environ::STACK_CHAIN_CONTINUATION_DISCRIMINANT,
443 }
444 
445 impl VMStackChain {
446     /// Indicates if `self` is a `InitialStack` variant.
is_initial_stack(&self) -> bool447     pub fn is_initial_stack(&self) -> bool {
448         matches!(self, VMStackChain::InitialStack(_))
449     }
450 
451     /// Returns an iterator over the continuations in this chain.
452     /// We don't implement `IntoIterator` because our iterator is unsafe, so at
453     /// least this gives us some way of indicating this, even though the actual
454     /// unsafety lies in the `next` function.
455     ///
456     /// # Safety
457     ///
458     /// This function is not unsafe per see, but it returns an object
459     /// whose usage is unsafe.
into_continuation_iter(self) -> ContinuationIterator460     pub unsafe fn into_continuation_iter(self) -> ContinuationIterator {
461         ContinuationIterator(self)
462     }
463 
464     /// Returns an iterator over the stack limits in this chain.
465     /// We don't implement `IntoIterator` because our iterator is unsafe, so at
466     /// least this gives us some way of indicating this, even though the actual
467     /// unsafety lies in the `next` function.
468     ///
469     /// # Safety
470     ///
471     /// This function is not unsafe per see, but it returns an object
472     /// whose usage is unsafe.
into_stack_limits_iter(self) -> StackLimitsIterator473     pub unsafe fn into_stack_limits_iter(self) -> StackLimitsIterator {
474         StackLimitsIterator(self)
475     }
476 }
477 
478 /// Iterator for Continuations in a stack chain.
479 pub struct ContinuationIterator(VMStackChain);
480 
481 /// Iterator for VMStackLimits in a stack chain.
482 pub struct StackLimitsIterator(VMStackChain);
483 
484 impl Iterator for ContinuationIterator {
485     type Item = *mut VMContRef;
486 
next(&mut self) -> Option<Self::Item>487     fn next(&mut self) -> Option<Self::Item> {
488         match self.0 {
489             VMStackChain::Absent | VMStackChain::InitialStack(_) => None,
490             VMStackChain::Continuation(ptr) => {
491                 let continuation = unsafe { ptr.as_mut().unwrap() };
492                 self.0 = continuation.parent_chain.clone();
493                 Some(ptr)
494             }
495         }
496     }
497 }
498 
499 impl Iterator for StackLimitsIterator {
500     type Item = *mut VMStackLimits;
501 
next(&mut self) -> Option<Self::Item>502     fn next(&mut self) -> Option<Self::Item> {
503         match self.0 {
504             VMStackChain::Absent => None,
505             VMStackChain::InitialStack(csi) => {
506                 let stack_limits = unsafe { &mut (*csi).limits } as *mut VMStackLimits;
507                 self.0 = VMStackChain::Absent;
508                 Some(stack_limits)
509             }
510             VMStackChain::Continuation(ptr) => {
511                 let continuation = unsafe { ptr.as_mut().unwrap() };
512                 let stack_limits =
513                     (&mut continuation.common_stack_information.limits) as *mut VMStackLimits;
514                 self.0 = continuation.parent_chain.clone();
515                 Some(stack_limits)
516             }
517         }
518     }
519 }
520 
521 /// Encodes the life cycle of a `VMContRef`.
522 #[derive(Debug, Clone, Copy, PartialEq)]
523 #[repr(u32)]
524 pub enum VMStackState {
525     /// The `VMContRef` has been created, but neither `resume` or `switch` has ever been
526     /// called on it. During this stage, we may add arguments using `cont.bind`.
527     Fresh = wasmtime_environ::STACK_STATE_FRESH_DISCRIMINANT,
528     /// The continuation is running, meaning that it is the one currently
529     /// executing code.
530     Running = wasmtime_environ::STACK_STATE_RUNNING_DISCRIMINANT,
531     /// The continuation is suspended because it executed a resume instruction
532     /// that has not finished yet. In other words, it became the parent of
533     /// another continuation (which may itself be `Running`, a `Parent`, or
534     /// `Suspended`).
535     Parent = wasmtime_environ::STACK_STATE_PARENT_DISCRIMINANT,
536     /// The continuation was suspended by a `suspend` or `switch` instruction.
537     Suspended = wasmtime_environ::STACK_STATE_SUSPENDED_DISCRIMINANT,
538     /// The function originally passed to `cont.new` has returned normally.
539     /// Note that there is no guarantee that a VMContRef will ever
540     /// reach this status, as it may stay suspended until being dropped.
541     Returned = wasmtime_environ::STACK_STATE_RETURNED_DISCRIMINANT,
542 }
543 
544 #[cfg(test)]
545 mod tests {
546     use core::mem::{offset_of, size_of};
547 
548     use wasmtime_environ::{HostPtr, Module, PtrSize, StaticModuleIndex, VMOffsets};
549 
550     use super::*;
551 
552     #[test]
null_pointer_optimization()553     fn null_pointer_optimization() {
554         // The Rust spec does not technically guarantee that the null pointer
555         // optimization applies to a struct containing a `NonNull`.
556         assert_eq!(size_of::<Option<VMContObj>>(), size_of::<VMContObj>());
557     }
558 
559     #[test]
check_vm_stack_limits_offsets()560     fn check_vm_stack_limits_offsets() {
561         let module = Module::new(StaticModuleIndex::from_u32(0));
562         let offsets = VMOffsets::new(HostPtr, &module);
563         assert_eq!(
564             offset_of!(VMStackLimits, stack_limit),
565             usize::from(offsets.ptr.vmstack_limits_stack_limit())
566         );
567         assert_eq!(
568             offset_of!(VMStackLimits, last_wasm_entry_fp),
569             usize::from(offsets.ptr.vmstack_limits_last_wasm_entry_fp())
570         );
571     }
572 
573     #[test]
check_vm_common_stack_information_offsets()574     fn check_vm_common_stack_information_offsets() {
575         let module = Module::new(StaticModuleIndex::from_u32(0));
576         let offsets = VMOffsets::new(HostPtr, &module);
577         assert_eq!(
578             size_of::<VMCommonStackInformation>(),
579             usize::from(offsets.ptr.size_of_vmcommon_stack_information())
580         );
581         assert_eq!(
582             offset_of!(VMCommonStackInformation, limits),
583             usize::from(offsets.ptr.vmcommon_stack_information_limits())
584         );
585         assert_eq!(
586             offset_of!(VMCommonStackInformation, state),
587             usize::from(offsets.ptr.vmcommon_stack_information_state())
588         );
589         assert_eq!(
590             offset_of!(VMCommonStackInformation, handlers),
591             usize::from(offsets.ptr.vmcommon_stack_information_handlers())
592         );
593         assert_eq!(
594             offset_of!(VMCommonStackInformation, first_switch_handler_index),
595             usize::from(
596                 offsets
597                     .ptr
598                     .vmcommon_stack_information_first_switch_handler_index()
599             )
600         );
601     }
602 
603     #[test]
check_vm_array_offsets()604     fn check_vm_array_offsets() {
605         // Note that the type parameter has no influence on the size and offsets.
606         let module = Module::new(StaticModuleIndex::from_u32(0));
607         let offsets = VMOffsets::new(HostPtr, &module);
608         assert_eq!(
609             size_of::<VMHostArray<()>>(),
610             usize::from(offsets.ptr.size_of_vmhostarray())
611         );
612         assert_eq!(
613             offset_of!(VMHostArray<()>, length),
614             usize::from(offsets.ptr.vmhostarray_length())
615         );
616         assert_eq!(
617             offset_of!(VMHostArray<()>, capacity),
618             usize::from(offsets.ptr.vmhostarray_capacity())
619         );
620         assert_eq!(
621             offset_of!(VMHostArray<()>, data),
622             usize::from(offsets.ptr.vmhostarray_data())
623         );
624     }
625 
626     #[test]
check_vm_contobj_offsets()627     fn check_vm_contobj_offsets() {
628         let module = Module::new(StaticModuleIndex::from_u32(0));
629         let offsets = VMOffsets::new(HostPtr, &module);
630         assert_eq!(
631             offset_of!(VMContObj, contref),
632             usize::from(offsets.ptr.vmcontobj_contref())
633         );
634         assert_eq!(
635             offset_of!(VMContObj, revision),
636             usize::from(offsets.ptr.vmcontobj_revision())
637         );
638         assert_eq!(
639             size_of::<VMContObj>(),
640             usize::from(offsets.ptr.size_of_vmcontobj())
641         )
642     }
643 
644     #[test]
check_vm_contref_offsets()645     fn check_vm_contref_offsets() {
646         let module = Module::new(StaticModuleIndex::from_u32(0));
647         let offsets = VMOffsets::new(HostPtr, &module);
648         assert_eq!(
649             offset_of!(VMContRef, common_stack_information),
650             usize::from(offsets.ptr.vmcontref_common_stack_information())
651         );
652         assert_eq!(
653             offset_of!(VMContRef, parent_chain),
654             usize::from(offsets.ptr.vmcontref_parent_chain())
655         );
656         assert_eq!(
657             offset_of!(VMContRef, last_ancestor),
658             usize::from(offsets.ptr.vmcontref_last_ancestor())
659         );
660         // Some 32-bit platforms need this to be 8-byte aligned, some don't.
661         // So we need to make sure it always is, without padding.
662         assert_eq!(u8::vmcontref_revision(&4) % 8, 0);
663         assert_eq!(u8::vmcontref_revision(&8) % 8, 0);
664         assert_eq!(
665             offset_of!(VMContRef, revision),
666             usize::from(offsets.ptr.vmcontref_revision())
667         );
668         assert_eq!(
669             offset_of!(VMContRef, stack),
670             usize::from(offsets.ptr.vmcontref_stack())
671         );
672         assert_eq!(
673             offset_of!(VMContRef, args),
674             usize::from(offsets.ptr.vmcontref_args())
675         );
676         assert_eq!(
677             offset_of!(VMContRef, values),
678             usize::from(offsets.ptr.vmcontref_values())
679         );
680     }
681 
682     #[test]
check_vm_stack_chain_offsets()683     fn check_vm_stack_chain_offsets() {
684         let module = Module::new(StaticModuleIndex::from_u32(0));
685         let offsets = VMOffsets::new(HostPtr, &module);
686         assert_eq!(
687             size_of::<VMStackChain>(),
688             usize::from(offsets.ptr.size_of_vmstack_chain())
689         );
690     }
691 }
692