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 iself (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 descibed 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 @ {:p}", contref); 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 /// Universal control effect. This structure encodes return signal, resume 548 /// signal, suspension signal, and the handler to suspend to in a single variant 549 /// type. This instance is used at runtime. There is a codegen counterpart in 550 /// `cranelift/src/stack-switching/control_effect.rs`. 551 #[derive(Clone, Copy, Debug, Eq, PartialEq)] 552 #[repr(u32)] 553 #[allow(dead_code)] 554 pub enum ControlEffect { 555 /// Used to signal that a continuation has returned and control switches 556 /// back to the parent. 557 Return = wasmtime_environ::CONTROL_EFFECT_RETURN_DISCRIMINANT, 558 /// Used to signal to a continuation that it is being resumed. 559 Resume = wasmtime_environ::CONTROL_EFFECT_RESUME_DISCRIMINANT, 560 /// Used to signal that a continuation has invoked a `suspend` instruction. 561 Suspend { 562 /// The index of the handler to be used in the parent continuation to 563 /// switch back to. 564 handler_index: u32, 565 } = wasmtime_environ::CONTROL_EFFECT_SUSPEND_DISCRIMINANT, 566 /// Used to signal that a continuation has invoked a `suspend` instruction. 567 Switch = wasmtime_environ::CONTROL_EFFECT_SWITCH_DISCRIMINANT, 568 } 569 570 #[cfg(test)] 571 mod tests { 572 use core::mem::{offset_of, size_of}; 573 574 use wasmtime_environ::{HostPtr, Module, PtrSize, VMOffsets}; 575 576 use super::*; 577 578 #[test] 579 fn null_pointer_optimization() { 580 // The Rust spec does not technically guarantee that the null pointer 581 // optimization applies to a struct containing a `NonNull`. 582 assert_eq!(size_of::<Option<VMContObj>>(), size_of::<VMContObj>()); 583 } 584 585 #[test] 586 fn check_vm_stack_limits_offsets() { 587 let module = Module::new(); 588 let offsets = VMOffsets::new(HostPtr, &module); 589 assert_eq!( 590 offset_of!(VMStackLimits, stack_limit), 591 usize::from(offsets.ptr.vmstack_limits_stack_limit()) 592 ); 593 assert_eq!( 594 offset_of!(VMStackLimits, last_wasm_entry_fp), 595 usize::from(offsets.ptr.vmstack_limits_last_wasm_entry_fp()) 596 ); 597 } 598 599 #[test] 600 fn check_vm_common_stack_information_offsets() { 601 let module = Module::new(); 602 let offsets = VMOffsets::new(HostPtr, &module); 603 assert_eq!( 604 size_of::<VMCommonStackInformation>(), 605 usize::from(offsets.ptr.size_of_vmcommon_stack_information()) 606 ); 607 assert_eq!( 608 offset_of!(VMCommonStackInformation, limits), 609 usize::from(offsets.ptr.vmcommon_stack_information_limits()) 610 ); 611 assert_eq!( 612 offset_of!(VMCommonStackInformation, state), 613 usize::from(offsets.ptr.vmcommon_stack_information_state()) 614 ); 615 assert_eq!( 616 offset_of!(VMCommonStackInformation, handlers), 617 usize::from(offsets.ptr.vmcommon_stack_information_handlers()) 618 ); 619 assert_eq!( 620 offset_of!(VMCommonStackInformation, first_switch_handler_index), 621 usize::from( 622 offsets 623 .ptr 624 .vmcommon_stack_information_first_switch_handler_index() 625 ) 626 ); 627 } 628 629 #[test] 630 fn check_vm_array_offsets() { 631 // Note that the type parameter has no influence on the size and offsets. 632 let module = Module::new(); 633 let offsets = VMOffsets::new(HostPtr, &module); 634 assert_eq!( 635 size_of::<VMHostArray<()>>(), 636 usize::from(offsets.ptr.size_of_vmhostarray()) 637 ); 638 assert_eq!( 639 offset_of!(VMHostArray<()>, length), 640 usize::from(offsets.ptr.vmhostarray_length()) 641 ); 642 assert_eq!( 643 offset_of!(VMHostArray<()>, capacity), 644 usize::from(offsets.ptr.vmhostarray_capacity()) 645 ); 646 assert_eq!( 647 offset_of!(VMHostArray<()>, data), 648 usize::from(offsets.ptr.vmhostarray_data()) 649 ); 650 } 651 652 #[test] 653 fn check_vm_contref_offsets() { 654 let module = Module::new(); 655 let offsets = VMOffsets::new(HostPtr, &module); 656 assert_eq!( 657 offset_of!(VMContRef, common_stack_information), 658 usize::from(offsets.ptr.vmcontref_common_stack_information()) 659 ); 660 assert_eq!( 661 offset_of!(VMContRef, parent_chain), 662 usize::from(offsets.ptr.vmcontref_parent_chain()) 663 ); 664 assert_eq!( 665 offset_of!(VMContRef, last_ancestor), 666 usize::from(offsets.ptr.vmcontref_last_ancestor()) 667 ); 668 // Some 32-bit platforms need this to be 8-byte aligned, some don't. 669 // So we need to make sure it always is, without padding. 670 assert_eq!(u8::vmcontref_revision(&4) % 8, 0); 671 assert_eq!(u8::vmcontref_revision(&8) % 8, 0); 672 assert_eq!( 673 offset_of!(VMContRef, revision), 674 usize::from(offsets.ptr.vmcontref_revision()) 675 ); 676 assert_eq!( 677 offset_of!(VMContRef, stack), 678 usize::from(offsets.ptr.vmcontref_stack()) 679 ); 680 assert_eq!( 681 offset_of!(VMContRef, args), 682 usize::from(offsets.ptr.vmcontref_args()) 683 ); 684 assert_eq!( 685 offset_of!(VMContRef, values), 686 usize::from(offsets.ptr.vmcontref_values()) 687 ); 688 } 689 690 #[test] 691 fn check_vm_stack_chain_offsets() { 692 let module = Module::new(); 693 let offsets = VMOffsets::new(HostPtr, &module); 694 assert_eq!( 695 size_of::<VMStackChain>(), 696 usize::from(offsets.ptr.size_of_vmstack_chain()) 697 ); 698 } 699 } 700