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