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