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: crate::store::InstanceId,
306     func: *mut u8,
307     param_count: u32,
308     result_count: u32,
309 ) -> anyhow::Result<*mut VMContRef> {
310     let instance = store.instance_mut(instance);
311     let caller_vmctx = instance.vmctx();
312 
313     let stack_size = store.engine().config().async_stack_size;
314 
315     let contref = store.allocate_continuation()?;
316     let contref = unsafe { contref.as_mut().unwrap() };
317 
318     let tsp = contref.stack.top().unwrap();
319     contref.parent_chain = VMStackChain::Absent;
320     // The continuation is fresh, which is a special case of being suspended.
321     // Thus we need to set the correct end of the continuation chain: itself.
322     contref.last_ancestor = contref;
323 
324     // The initialization function will allocate the actual args/return value buffer and
325     // update this object (if needed).
326     let contref_args_ptr = &mut contref.args as *mut _ as *mut VMHostArray<crate::ValRaw>;
327 
328     contref.stack.initialize(
329         func.cast::<crate::vm::VMFuncRef>(),
330         caller_vmctx.as_ptr(),
331         contref_args_ptr,
332         param_count,
333         result_count,
334     );
335 
336     // Now that the initial stack pointer was set by the initialization
337     // function, use it to determine stack limit.
338     let stack_pointer = contref.stack.control_context_stack_pointer();
339     // Same caveat regarding stack_limit here as described in
340     // `wasmtime::runtime::func::EntryStoreContext::enter_wasm`.
341     let wasm_stack_limit = core::cmp::max(
342         stack_pointer - store.engine().config().max_wasm_stack,
343         tsp as usize - stack_size,
344     );
345     let limits = VMStackLimits::with_stack_limit(wasm_stack_limit);
346     let csi = &mut contref.common_stack_information;
347     csi.state = VMStackState::Fresh;
348     csi.limits = limits;
349 
350     log::trace!("Created contref @ {contref:p}");
351     Ok(contref)
352 }
353 
354 /// This type represents a linked lists ("chain") of stacks, where the a
355 /// node's successor denotes its parent.
356 /// Additionally, a `CommonStackInformation` object is associated with
357 /// each stack in the list.
358 /// Here, a "stack" is one of the following:
359 /// - A continuation (i.e., created with cont.new).
360 /// - The initial stack. This is the stack that we were on when entering
361 ///   Wasm (i.e., when executing
362 ///   `crate::runtime::func::invoke_wasm_and_catch_traps`).
363 ///   This stack never has a parent.
364 ///   In terms of the memory allocation that this stack resides on, it will
365 ///   usually be the main stack, but doesn't have to: If we are running
366 ///   inside a continuation while executing a host call, which in turn
367 ///   re-renters Wasm, the initial stack is actually the stack of that
368 ///   continuation.
369 ///
370 /// Note that the linked list character of `VMStackChain` arises from the fact
371 /// that `VMStackChain::Continuation` variants have a pointer to a
372 /// `VMContRef`, which in turn has a `parent_chain` value of type
373 /// `VMStackChain`. This is how the stack chain reflects the parent-child
374 /// relationships between continuations/stacks. This also shows how the
375 /// initial stack (mentioned above) cannot have a parent.
376 ///
377 /// There are generally two uses of `VMStackChain`:
378 ///
379 /// 1. The `stack_chain` field in the `StoreOpaque` contains such a
380 /// chain of stacks, where the head of the list denotes the stack that is
381 /// currently executing (either a continuation or the initial stack). Note
382 /// that in this case, the linked list must contain 0 or more `Continuation`
383 /// elements, followed by a final `InitialStack` element. In particular,
384 /// this list always ends with `InitialStack` and never contains an `Absent`
385 /// variant.
386 ///
387 /// 2. When a continuation is suspended, its chain of parents eventually
388 /// ends with an `Absent` variant in its `parent_chain` field. Note that a
389 /// suspended continuation never appears in the stack chain in the
390 /// VMContext!
391 ///
392 ///
393 /// As mentioned before, each stack in a `VMStackChain` has a corresponding
394 /// `CommonStackInformation` object. For continuations, this is stored in
395 /// the `common_stack_information` field of the corresponding `VMContRef`.
396 /// For the initial stack, the `InitialStack` variant contains a pointer to
397 /// a `CommonStackInformation`. The latter will be allocated allocated on
398 /// the stack frame that executed by `invoke_wasm_and_catch_traps`.
399 ///
400 /// The following invariants hold for these `VMStackLimits` objects,
401 /// and the data in `VMStoreContext`.
402 ///
403 /// Currently executing stack: For the currently executing stack (i.e., the
404 /// stack that is at the head of the store's `stack_chain` list), the
405 /// associated `VMStackLimits` object contains stale/undefined data. Instead,
406 /// the live data describing the limits for the currently executing stack is
407 /// always maintained in `VMStoreContext`. Note that as a general rule
408 /// independently from any execution of continuations, the `last_wasm_exit*`
409 /// fields in the `VMStoreContext` contain undefined values while executing
410 /// wasm.
411 ///
412 /// Parents of currently executing stack: For stacks that appear in the tail
413 /// of the store's `stack_chain` list (i.e., stacks that are not currently
414 /// executing themselves, but are an ancestor of the currently executing
415 /// stack), we have the following: All the fields in the stack's
416 /// `VMStackLimits` are valid, describing the stack's stack limit, and
417 /// pointers where executing for that stack entered and exited WASM.
418 ///
419 /// Suspended continuations: For suspended continuations (including their
420 /// ancestors), we have the following. Note that the initial stack can never
421 /// be in this state. The `stack_limit` and `last_enter_wasm_sp` fields of
422 /// the corresponding `VMStackLimits` object contain valid data, while the
423 /// `last_exit_wasm_*` fields contain arbitrary values. There is only one
424 /// exception to this: Note that a continuation that has been created with
425 /// cont.new, but never been resumed so far, is considered "suspended".
426 /// However, its `last_enter_wasm_sp` field contains undefined data. This is
427 /// justified, because when resume-ing a continuation for the first time, a
428 /// native-to-wasm trampoline is called, which sets up the
429 /// `last_wasm_entry_sp` in the `VMStoreContext` with the correct value,
430 /// thus restoring the necessary invariant.
431 #[derive(Debug, Clone, PartialEq)]
432 #[repr(usize, C)]
433 pub enum VMStackChain {
434     /// For suspended continuations, denotes the end of their chain of
435     /// ancestors.
436     Absent = wasmtime_environ::STACK_CHAIN_ABSENT_DISCRIMINANT,
437     /// Represents the initial stack (i.e., where we entered Wasm from the
438     /// host by executing
439     /// `crate::runtime::func::invoke_wasm_and_catch_traps`). Therefore, it
440     /// does not have a parent. The `CommonStackInformation` that this
441     /// variant points to is stored in the stack frame of
442     /// `invoke_wasm_and_catch_traps`.
443     InitialStack(*mut VMCommonStackInformation) =
444         wasmtime_environ::STACK_CHAIN_INITIAL_STACK_DISCRIMINANT,
445     /// Represents a continuation's stack.
446     Continuation(*mut VMContRef) = wasmtime_environ::STACK_CHAIN_CONTINUATION_DISCRIMINANT,
447 }
448 
449 impl VMStackChain {
450     /// Indicates if `self` is a `InitialStack` variant.
451     pub fn is_initial_stack(&self) -> bool {
452         matches!(self, VMStackChain::InitialStack(_))
453     }
454 
455     /// Returns an iterator over the continuations in this chain.
456     /// We don't implement `IntoIterator` because our iterator is unsafe, so at
457     /// least this gives us some way of indicating this, even though the actual
458     /// unsafety lies in the `next` function.
459     ///
460     /// # Safety
461     ///
462     /// This function is not unsafe per see, but it returns an object
463     /// whose usage is unsafe.
464     pub unsafe fn into_continuation_iter(self) -> ContinuationIterator {
465         ContinuationIterator(self)
466     }
467 
468     /// Returns an iterator over the stack limits in this chain.
469     /// We don't implement `IntoIterator` because our iterator is unsafe, so at
470     /// least this gives us some way of indicating this, even though the actual
471     /// unsafety lies in the `next` function.
472     ///
473     /// # Safety
474     ///
475     /// This function is not unsafe per see, but it returns an object
476     /// whose usage is unsafe.
477     pub unsafe fn into_stack_limits_iter(self) -> StackLimitsIterator {
478         StackLimitsIterator(self)
479     }
480 }
481 
482 /// Iterator for Continuations in a stack chain.
483 pub struct ContinuationIterator(VMStackChain);
484 
485 /// Iterator for VMStackLimits in a stack chain.
486 pub struct StackLimitsIterator(VMStackChain);
487 
488 impl Iterator for ContinuationIterator {
489     type Item = *mut VMContRef;
490 
491     fn next(&mut self) -> Option<Self::Item> {
492         match self.0 {
493             VMStackChain::Absent | VMStackChain::InitialStack(_) => None,
494             VMStackChain::Continuation(ptr) => {
495                 let continuation = unsafe { ptr.as_mut().unwrap() };
496                 self.0 = continuation.parent_chain.clone();
497                 Some(ptr)
498             }
499         }
500     }
501 }
502 
503 impl Iterator for StackLimitsIterator {
504     type Item = *mut VMStackLimits;
505 
506     fn next(&mut self) -> Option<Self::Item> {
507         match self.0 {
508             VMStackChain::Absent => None,
509             VMStackChain::InitialStack(csi) => {
510                 let stack_limits = unsafe { &mut (*csi).limits } as *mut VMStackLimits;
511                 self.0 = VMStackChain::Absent;
512                 Some(stack_limits)
513             }
514             VMStackChain::Continuation(ptr) => {
515                 let continuation = unsafe { ptr.as_mut().unwrap() };
516                 let stack_limits =
517                     (&mut continuation.common_stack_information.limits) as *mut VMStackLimits;
518                 self.0 = continuation.parent_chain.clone();
519                 Some(stack_limits)
520             }
521         }
522     }
523 }
524 
525 /// Encodes the life cycle of a `VMContRef`.
526 #[derive(Debug, Clone, Copy, PartialEq)]
527 #[repr(u32)]
528 pub enum VMStackState {
529     /// The `VMContRef` has been created, but neither `resume` or `switch` has ever been
530     /// called on it. During this stage, we may add arguments using `cont.bind`.
531     Fresh = wasmtime_environ::STACK_STATE_FRESH_DISCRIMINANT,
532     /// The continuation is running, meaning that it is the one currently
533     /// executing code.
534     Running = wasmtime_environ::STACK_STATE_RUNNING_DISCRIMINANT,
535     /// The continuation is suspended because it executed a resume instruction
536     /// that has not finished yet. In other words, it became the parent of
537     /// another continuation (which may itself be `Running`, a `Parent`, or
538     /// `Suspended`).
539     Parent = wasmtime_environ::STACK_STATE_PARENT_DISCRIMINANT,
540     /// The continuation was suspended by a `suspend` or `switch` instruction.
541     Suspended = wasmtime_environ::STACK_STATE_SUSPENDED_DISCRIMINANT,
542     /// The function originally passed to `cont.new` has returned normally.
543     /// Note that there is no guarantee that a VMContRef will ever
544     /// reach this status, as it may stay suspended until being dropped.
545     Returned = wasmtime_environ::STACK_STATE_RETURNED_DISCRIMINANT,
546 }
547 
548 #[cfg(test)]
549 mod tests {
550     use core::mem::{offset_of, size_of};
551 
552     use wasmtime_environ::{HostPtr, Module, PtrSize, VMOffsets};
553 
554     use super::*;
555 
556     #[test]
557     fn null_pointer_optimization() {
558         // The Rust spec does not technically guarantee that the null pointer
559         // optimization applies to a struct containing a `NonNull`.
560         assert_eq!(size_of::<Option<VMContObj>>(), size_of::<VMContObj>());
561     }
562 
563     #[test]
564     fn check_vm_stack_limits_offsets() {
565         let module = Module::new();
566         let offsets = VMOffsets::new(HostPtr, &module);
567         assert_eq!(
568             offset_of!(VMStackLimits, stack_limit),
569             usize::from(offsets.ptr.vmstack_limits_stack_limit())
570         );
571         assert_eq!(
572             offset_of!(VMStackLimits, last_wasm_entry_fp),
573             usize::from(offsets.ptr.vmstack_limits_last_wasm_entry_fp())
574         );
575     }
576 
577     #[test]
578     fn check_vm_common_stack_information_offsets() {
579         let module = Module::new();
580         let offsets = VMOffsets::new(HostPtr, &module);
581         assert_eq!(
582             size_of::<VMCommonStackInformation>(),
583             usize::from(offsets.ptr.size_of_vmcommon_stack_information())
584         );
585         assert_eq!(
586             offset_of!(VMCommonStackInformation, limits),
587             usize::from(offsets.ptr.vmcommon_stack_information_limits())
588         );
589         assert_eq!(
590             offset_of!(VMCommonStackInformation, state),
591             usize::from(offsets.ptr.vmcommon_stack_information_state())
592         );
593         assert_eq!(
594             offset_of!(VMCommonStackInformation, handlers),
595             usize::from(offsets.ptr.vmcommon_stack_information_handlers())
596         );
597         assert_eq!(
598             offset_of!(VMCommonStackInformation, first_switch_handler_index),
599             usize::from(
600                 offsets
601                     .ptr
602                     .vmcommon_stack_information_first_switch_handler_index()
603             )
604         );
605     }
606 
607     #[test]
608     fn check_vm_array_offsets() {
609         // Note that the type parameter has no influence on the size and offsets.
610         let module = Module::new();
611         let offsets = VMOffsets::new(HostPtr, &module);
612         assert_eq!(
613             size_of::<VMHostArray<()>>(),
614             usize::from(offsets.ptr.size_of_vmhostarray())
615         );
616         assert_eq!(
617             offset_of!(VMHostArray<()>, length),
618             usize::from(offsets.ptr.vmhostarray_length())
619         );
620         assert_eq!(
621             offset_of!(VMHostArray<()>, capacity),
622             usize::from(offsets.ptr.vmhostarray_capacity())
623         );
624         assert_eq!(
625             offset_of!(VMHostArray<()>, data),
626             usize::from(offsets.ptr.vmhostarray_data())
627         );
628     }
629 
630     #[test]
631     fn check_vm_contref_offsets() {
632         let module = Module::new();
633         let offsets = VMOffsets::new(HostPtr, &module);
634         assert_eq!(
635             offset_of!(VMContRef, common_stack_information),
636             usize::from(offsets.ptr.vmcontref_common_stack_information())
637         );
638         assert_eq!(
639             offset_of!(VMContRef, parent_chain),
640             usize::from(offsets.ptr.vmcontref_parent_chain())
641         );
642         assert_eq!(
643             offset_of!(VMContRef, last_ancestor),
644             usize::from(offsets.ptr.vmcontref_last_ancestor())
645         );
646         // Some 32-bit platforms need this to be 8-byte aligned, some don't.
647         // So we need to make sure it always is, without padding.
648         assert_eq!(u8::vmcontref_revision(&4) % 8, 0);
649         assert_eq!(u8::vmcontref_revision(&8) % 8, 0);
650         assert_eq!(
651             offset_of!(VMContRef, revision),
652             usize::from(offsets.ptr.vmcontref_revision())
653         );
654         assert_eq!(
655             offset_of!(VMContRef, stack),
656             usize::from(offsets.ptr.vmcontref_stack())
657         );
658         assert_eq!(
659             offset_of!(VMContRef, args),
660             usize::from(offsets.ptr.vmcontref_args())
661         );
662         assert_eq!(
663             offset_of!(VMContRef, values),
664             usize::from(offsets.ptr.vmcontref_values())
665         );
666     }
667 
668     #[test]
669     fn check_vm_stack_chain_offsets() {
670         let module = Module::new();
671         let offsets = VMOffsets::new(HostPtr, &module);
672         assert_eq!(
673             size_of::<VMStackChain>(),
674             usize::from(offsets.ptr.size_of_vmstack_chain())
675         );
676     }
677 }
678