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