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