Repository: bytecodealliance/wasmtime
Pull request #8806 (2024-06-14 17:35:46)

Const-propagate some offsets in `VMOffsets`
Before this commit all offsets to all fields in `VMOffsets` were stored as fields within `VMOffset` itself. All of the fields at the start of `VMOffsets`, however, are statically known given the pointer size. Notably this means that the use of `HostPtr` in the runtime still was forcing a dynamic lookup of these static offsets.

This commit refactors this to reflect all static offsets based solely on the pointer size in the `PtrSize` trait, removing all the fields from `VMOffsets`. All the dynamically sized fields, however, remain in `VMOffsets`.

<!--
Please make sure you include the following information:

- If this work has been discussed elsewhere, please include a link to that
  conversation. If it was discussed in an issue, just mention "issue #...".

- Explain why this change is needed. If the details are in an issue already,
  this can be brief.

Our development process is documented in the Wasmtime book:
https://docs.wasmtime.dev/contributing-development-process.html

Please ensure all communication follows the code of conduct:
https://github.com/bytecodealliance/wasmtime/blob/main/CODE_OF_CONDUCT.md
-->



Patch:
diff --git a/crates/cranelift/src/compiler.rs b/crates/cranelift/src/compiler.rs
index a4ed9a0c4..bb44575bd 100644
--- a/crates/cranelift/src/compiler.rs
+++ b/crates/cranelift/src/compiler.rs
@@ -193,9 +193,7 @@ impl wasmtime_environ::Compiler for Compiler {
             .create_global_value(ir::GlobalValueData::VMContext);
         let interrupts_ptr = context.func.create_global_value(ir::GlobalValueData::Load {
             base: vmctx,
-            offset: i32::try_from(func_env.offsets.vmctx_runtime_limits())
-                .unwrap()
-                .into(),
+            offset: i32::from(func_env.offsets.ptr.vmctx_runtime_limits()).into(),
             global_type: isa.pointer_type(),
             flags: MemFlags::trusted().with_readonly(),
         });
@@ -282,12 +280,12 @@ impl wasmtime_environ::Compiler for Compiler {
         // what we are assuming with our offsets below.
         debug_assert_vmctx_kind(isa, &mut builder, vmctx, wasmtime_environ::VMCONTEXT_MAGIC);
         let offsets = VMOffsets::new(isa.pointer_bytes(), &translation.module);
-        let vm_runtime_limits_offset = offsets.vmctx_runtime_limits();
+        let vm_runtime_limits_offset = offsets.ptr.vmctx_runtime_limits();
         save_last_wasm_entry_sp(
             &mut builder,
             pointer_type,
             &offsets.ptr,
-            vm_runtime_limits_offset,
+            vm_runtime_limits_offset.into(),
             vmctx,
         );
 
diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs
index 8adf82454..0dbffa4b4 100644
--- a/crates/cranelift/src/func_environ.rs
+++ b/crates/cranelift/src/func_environ.rs
@@ -287,7 +287,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
         let pointer_type = self.pointer_type();
         let vmctx = self.vmctx(builder.func);
         let base = builder.ins().global_value(pointer_type, vmctx);
-        let offset = i32::try_from(self.offsets.vmctx_runtime_limits()).unwrap();
+        let offset = i32::from(self.offsets.ptr.vmctx_runtime_limits());
         debug_assert!(self.vmruntime_limits_ptr.is_reserved_value());
         self.vmruntime_limits_ptr =
             builder
@@ -580,7 +580,7 @@ impl<'module_environment> FuncEnvironment<'module_environment> {
         let vmctx = self.vmctx(builder.func);
         let pointer_type = self.pointer_type();
         let base = builder.ins().global_value(pointer_type, vmctx);
-        let offset = i32::try_from(self.offsets.vmctx_epoch_ptr()).unwrap();
+        let offset = i32::try_from(self.offsets.ptr.vmctx_epoch_ptr()).unwrap();
         let epoch_ptr = builder
             .ins()
             .load(pointer_type, ir::MemFlags::trusted(), base, offset);
@@ -1518,7 +1518,7 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
             pointer_type,
             mem_flags,
             base,
-            i32::try_from(self.env.offsets.vmctx_type_ids_array()).unwrap(),
+            i32::from(self.env.offsets.ptr.vmctx_type_ids_array()),
         );
         let sig_index = self.env.module.types[ty_index];
         let offset =
diff --git a/crates/cranelift/src/gc/enabled.rs b/crates/cranelift/src/gc/enabled.rs
index 00aefbb1e..e610e57a2 100644
--- a/crates/cranelift/src/gc/enabled.rs
+++ b/crates/cranelift/src/gc/enabled.rs
@@ -5,7 +5,7 @@ use cranelift_frontend::FunctionBuilder;
 use cranelift_wasm::{
     TargetEnvironment, WasmHeapTopType, WasmHeapType, WasmRefType, WasmResult, WasmValType,
 };
-use wasmtime_environ::{I31_DISCRIMINANT, NON_NULL_NON_I31_MASK};
+use wasmtime_environ::{PtrSize, I31_DISCRIMINANT, NON_NULL_NON_I31_MASK};
 
 /// Get the default GC compiler.
 pub fn gc_compiler(_func_env: &FuncEnvironment<'_>) -> Box<dyn GcCompiler> {
@@ -97,11 +97,11 @@ impl FuncEnvironment<'_> {
         let vmctx = self.vmctx(builder.func);
         let vmctx = builder.ins().global_value(ptr_ty, vmctx);
 
-        let base_offset = self.offsets.vmctx_gc_heap_base();
-        let base_offset = i32::try_from(base_offset).unwrap();
+        let base_offset = self.offsets.ptr.vmctx_gc_heap_base();
+        let base_offset = i32::from(base_offset);
 
-        let bound_offset = self.offsets.vmctx_gc_heap_bound();
-        let bound_offset = i32::try_from(bound_offset).unwrap();
+        let bound_offset = self.offsets.ptr.vmctx_gc_heap_bound();
+        let bound_offset = i32::from(bound_offset);
 
         let base = builder.ins().load(ptr_ty, flags, vmctx, base_offset);
         let bound = builder.ins().load(ptr_ty, flags, vmctx, bound_offset);
@@ -289,7 +289,7 @@ impl DrcCompiler {
             ptr_ty,
             ir::MemFlags::trusted(),
             vmctx,
-            i32::try_from(func_env.offsets.vmctx_gc_heap_data()).unwrap(),
+            i32::from(func_env.offsets.ptr.vmctx_gc_heap_data()),
         );
         let next = builder.ins().load(
             ptr_ty,
diff --git a/crates/environ/src/vmoffsets.rs b/crates/environ/src/vmoffsets.rs
index 569e3e5ff..d913f2d62 100644
--- a/crates/environ/src/vmoffsets.rs
+++ b/crates/environ/src/vmoffsets.rs
@@ -7,8 +7,9 @@
 //      magic: u32,
 //      _padding: u32, // (On 64-bit systems)
 //      runtime_limits: *const VMRuntimeLimits,
-//      builtins: *mut VMBuiltinFunctionsArray,
+//      builtin_functions: *mut VMBuiltinFunctionsArray,
 //      callee: *mut VMFunctionBody,
+//      epoch_ptr: *mut AtomicU64,
 //      gc_heap_base: *mut u8,
 //      gc_heap_bound: *mut u8,
 //      gc_heap_data: *mut T, // Collector-specific pointer
@@ -77,16 +78,6 @@ pub struct VMOffsets<P> {
     pub num_call_indirect_caches: u32,
 
     // precalculated offsets of various member fields
-    magic: u32,
-    runtime_limits: u32,
-    builtin_functions: u32,
-    callee: u32,
-    epoch_ptr: u32,
-    gc_heap_base: u32,
-    gc_heap_bound: u32,
-    gc_heap_data: u32,
-    store: u32,
-    type_ids: u32,
     imported_functions: u32,
     imported_tables: u32,
     imported_memories: u32,
@@ -244,6 +235,81 @@ pub trait PtrSize {
     fn size_of_vmcall_indirect_cache(&self) -> u8 {
         2 * self.size()
     }
+
+    /// Return the offset to the `magic` value in this `VMContext`.
+    #[inline]
+    fn vmctx_magic(&self) -> u8 {
+        // This is required by the implementation of `VMContext::instance` and
+        // `VMContext::instance_mut`. If this value changes then those locations
+        // need to be updated.
+        0
+    }
+
+    /// Return the offset to the `VMRuntimeLimits` structure
+    #[inline]
+    fn vmctx_runtime_limits(&self) -> u8 {
+        self.vmctx_magic() + self.size()
+    }
+
+    /// Return the offset to the `VMBuiltinFunctionsArray` structure
+    #[inline]
+    fn vmctx_builtin_functions(&self) -> u8 {
+        self.vmctx_runtime_limits() + self.size()
+    }
+
+    /// Return the offset to the `callee` member in this `VMContext`.
+    #[inline]
+    fn vmctx_callee(&self) -> u8 {
+        self.vmctx_builtin_functions() + self.size()
+    }
+
+    /// Return the offset to the `*const AtomicU64` epoch-counter
+    /// pointer.
+    #[inline]
+    fn vmctx_epoch_ptr(&self) -> u8 {
+        self.vmctx_callee() + self.size()
+    }
+
+    /// Return the offset to the GC heap base in this `VMContext`.
+    #[inline]
+    fn vmctx_gc_heap_base(&self) -> u8 {
+        self.vmctx_epoch_ptr() + self.size()
+    }
+
+    /// Return the offset to the GC heap bound in this `VMContext`.
+    #[inline]
+    fn vmctx_gc_heap_bound(&self) -> u8 {
+        self.vmctx_gc_heap_base() + self.size()
+    }
+
+    /// Return the offset to the `*mut T` collector-specific data.
+    ///
+    /// This is a pointer that different collectors can use however they see
+    /// fit.
+    #[inline]
+    fn vmctx_gc_heap_data(&self) -> u8 {
+        self.vmctx_gc_heap_bound() + self.size()
+    }
+
+    /// The offset of the `*const dyn Store` member.
+    #[inline]
+    fn vmctx_store(&self) -> u8 {
+        self.vmctx_gc_heap_data() + self.size()
+    }
+
+    /// The offset of the `type_ids` array pointer.
+    #[inline]
+    fn vmctx_type_ids_array(&self) -> u8 {
+        self.vmctx_store() + 2 * self.size()
+    }
+
+    /// The end of statically known offsets in `VMContext`.
+    ///
+    /// Data after this is dynamically sized.
+    #[inline]
+    fn vmctx_dynamic_data_start(&self) -> u8 {
+        self.vmctx_type_ids_array() + self.size()
+    }
 }
 
 /// Type representing the size of a pointer for the current compilation host
@@ -366,8 +432,11 @@ impl<P: PtrSize> VMOffsets<P> {
                     let $name = last - $name;
                     last = tmp;
                 )*
-                assert_eq!(last, 0);
-                IntoIterator::into_iter([$(($desc, $name),)*])
+                assert_ne!(last, 0);
+                IntoIterator::into_iter([
+                    $(($desc, $name),)*
+                    ("static vmctx data", last),
+                ])
             }};
         }
 
@@ -382,16 +451,6 @@ impl<P: PtrSize> VMOffsets<P> {
             imported_memories: "imported memories",
             imported_tables: "imported tables",
             imported_functions: "imported functions",
-            type_ids: "module types",
-            store: "jit store state",
-            gc_heap_data: "GC implementation-specific data",
-            gc_heap_bound: "GC heap bound",
-            gc_heap_base: "GC heap base",
-            epoch_ptr: "jit current epoch state",
-            callee: "callee function pointer",
-            builtin_functions: "jit builtin functions state",
-            runtime_limits: "jit runtime limits state",
-            magic: "magic value",
         }
     }
 }
@@ -410,16 +469,6 @@ impl<P: PtrSize> From<VMOffsetsFields<P>> for VMOffsets<P> {
             num_defined_globals: fields.num_defined_globals,
             num_escaped_funcs: fields.num_escaped_funcs,
             num_call_indirect_caches: fields.num_call_indirect_caches,
-            magic: 0,
-            runtime_limits: 0,
-            builtin_functions: 0,
-            callee: 0,
-            epoch_ptr: 0,
-            gc_heap_base: 0,
-            gc_heap_bound: 0,
-            gc_heap_data: 0,
-            store: 0,
-            type_ids: 0,
             imported_functions: 0,
             imported_tables: 0,
             imported_memories: 0,
@@ -447,7 +496,7 @@ impl<P: PtrSize> From<VMOffsetsFields<P>> for VMOffsets<P> {
             count.checked_mul(u32::from(size)).unwrap()
         }
 
-        let mut next_field_offset = 0;
+        let mut next_field_offset = u32::from(ret.ptr.vmctx_dynamic_data_start());
 
         macro_rules! fields {
             (size($field:ident) = $size:expr, $($rest:tt)*) => {
@@ -463,17 +512,6 @@ impl<P: PtrSize> From<VMOffsetsFields<P>> for VMOffsets<P> {
         }
 
         fields! {
-            size(magic) = 4u32,
-            align(u32::from(ret.ptr.size())),
-            size(runtime_limits) = ret.ptr.size(),
-            size(builtin_functions) = ret.ptr.size(),
-            size(callee) = ret.ptr.size(),
-            size(epoch_ptr) = ret.ptr.size(),
-            size(gc_heap_base) = ret.ptr.size(),
-            size(gc_heap_bound) = ret.ptr.size(),
-            size(gc_heap_data) = ret.ptr.size(),
-            size(store) = ret.ptr.size() * 2,
-            size(type_ids) = ret.ptr.size(),
             size(imported_functions)
                 = cmul(ret.num_imported_functions, ret.size_of_vmfunction_import()),
             size(imported_tables)
@@ -503,11 +541,6 @@ impl<P: PtrSize> From<VMOffsetsFields<P>> for VMOffsets<P> {
 
         ret.size = next_field_offset;
 
-        // This is required by the implementation of `VMContext::instance` and
-        // `VMContext::instance_mut`. If this value changes then those locations
-        // need to be updated.
-        assert_eq!(ret.magic, 0);
-
         return ret;
     }
 }
@@ -640,63 +673,6 @@ impl<P: PtrSize> VMOffsets<P> {
 
 /// Offsets for `VMContext`.
 impl<P: PtrSize> VMOffsets<P> {
-    /// Return the offset to the `magic` value in this `VMContext`.
-    #[inline]
-    pub fn vmctx_magic(&self) -> u32 {
-        self.magic
-    }
-
-    /// Return the offset to the `VMRuntimeLimits` structure
-    #[inline]
-    pub fn vmctx_runtime_limits(&self) -> u32 {
-        self.runtime_limits
-    }
-
-    /// Return the offset to the `callee` member in this `VMContext`.
-    pub fn vmctx_callee(&self) -> u32 {
-        self.callee
-    }
-
-    /// Return the offset to the `*const AtomicU64` epoch-counter
-    /// pointer.
-    #[inline]
-    pub fn vmctx_epoch_ptr(&self) -> u32 {
-        self.epoch_ptr
-    }
-
-    /// Return the offset to the GC heap base in this `VMContext`.
-    #[inline]
-    pub fn vmctx_gc_heap_base(&self) -> u32 {
-        self.gc_heap_base
-    }
-
-    /// Return the offset to the GC heap bound in this `VMContext`.
-    #[inline]
-    pub fn vmctx_gc_heap_bound(&self) -> u32 {
-        self.gc_heap_bound
-    }
-
-    /// Return the offset to the `*mut T` collector-specific data.
-    ///
-    /// This is a pointer that different collectors can use however they see
-    /// fit.
-    #[inline]
-    pub fn vmctx_gc_heap_data(&self) -> u32 {
-        self.gc_heap_data
-    }
-
-    /// The offset of the `*const dyn Store` member.
-    #[inline]
-    pub fn vmctx_store(&self) -> u32 {
-        self.store
-    }
-
-    /// The offset of the `type_ids` array pointer.
-    #[inline]
-    pub fn vmctx_type_ids_array(&self) -> u32 {
-        self.type_ids
-    }
-
     /// The offset of the `tables` array.
     #[inline]
     pub fn vmctx_imported_functions_begin(&self) -> u32 {
@@ -751,12 +727,6 @@ impl<P: PtrSize> VMOffsets<P> {
         self.defined_func_refs
     }
 
-    /// The offset of the builtin functions array.
-    #[inline]
-    pub fn vmctx_builtin_functions(&self) -> u32 {
-        self.builtin_functions
-    }
-
     /// The offset of the `call_indirect_caches` array.
     #[inline]
     pub fn vmctx_call_indirec_caches_begin(&self) -> u32 {
diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs
index 2beb9b16d..e4e35ebb6 100644
--- a/crates/wasmtime/src/runtime/func.rs
+++ b/crates/wasmtime/src/runtime/func.rs
@@ -1704,6 +1704,8 @@ pub unsafe trait WasmRet {
 
     #[doc(hidden)]
     fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType;
+    #[doc(hidden)]
+    fn may_gc() -> bool;
 
     // Utilities used to convert an instance of this type to a `Result`
     // explicitly, used when wrapping async functions which always bottom-out
@@ -1735,6 +1737,10 @@ where
         <Self as WasmTy>::store(self, store, ptr.get_unchecked_mut(0))
     }
 
+    fn may_gc() -> bool {
+        T::may_gc()
+    }
+
     fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
         FuncType::new(engine, params, Some(<Self as WasmTy>::valtype()))
     }
@@ -1769,6 +1775,10 @@ where
         self.and_then(|val| val.store(store, ptr))
     }
 
+    fn may_gc() -> bool {
+        T::may_gc()
+    }
+
     fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
         T::func_type(engine, params)
     }
@@ -1814,6 +1824,11 @@ macro_rules! impl_wasm_host_results {
                 Ok(())
             }
 
+            #[doc(hidden)]
+            fn may_gc() -> bool {
+                $( $t::may_gc() || )* false
+            }
+
             fn func_type(engine: &Engine, params: impl Iterator<Item = ValType>) -> FuncType {
                 FuncType::new(
                     engine,
@@ -1937,6 +1952,9 @@ pub unsafe trait WasmTyList {
     // valid for this given type.
     #[doc(hidden)]
     unsafe fn load(store: &mut AutoAssertNoGc<'_>, values: &mut [MaybeUninit<ValRaw>]) -> Self;
+
+    #[doc(hidden)]
+    fn may_gc() -> bool;
 }
 
 macro_rules! impl_wasm_ty_list {
@@ -1959,6 +1977,10 @@ macro_rules! impl_wasm_ty_list {
                     $args::load(_store, ptr)
                 },)*)
             }
+
+            fn may_gc() -> bool {
+                $( $args::may_gc() || )* false
+            }
         }
     });
 }
@@ -2220,7 +2242,11 @@ impl HostContext {
                     break 'ret R::fallible_from_error(trap);
                 }
 
-                let mut store = AutoAssertNoGc::new(caller.store.0);
+                let mut store = if P::may_gc() {
+                    AutoAssertNoGc::new(caller.store.0)
+                } else {
+                    unsafe { AutoAssertNoGc::disabled(caller.store.0) }
+                };
                 let params = P::load(&mut store, args);
                 let _ = &mut store;
                 drop(store);
@@ -2235,7 +2261,11 @@ impl HostContext {
             if !ret.compatible_with_store(caller.store.0) {
                 bail!("host function attempted to return cross-`Store` value to Wasm")
             } else {
-                let mut store = AutoAssertNoGc::new(&mut **caller.store.0);
+                let mut store = if R::may_gc() {
+                    AutoAssertNoGc::new(caller.store.0)
+                } else {
+                    unsafe { AutoAssertNoGc::disabled(caller.store.0) }
+                };
                 let ret = ret.store(&mut store, args)?;
                 Ok(ret)
             }
diff --git a/crates/wasmtime/src/runtime/func/typed.rs b/crates/wasmtime/src/runtime/func/typed.rs
index f30b9e7a8..f021909ae 100644
--- a/crates/wasmtime/src/runtime/func/typed.rs
+++ b/crates/wasmtime/src/runtime/func/typed.rs
@@ -315,6 +315,14 @@ pub unsafe trait WasmTy: Send {
     #[doc(hidden)]
     fn valtype() -> ValType;
 
+    #[doc(hidden)]
+    fn may_gc() -> bool {
+        match Self::valtype() {
+            ValType::Ref(_) => true,
+            ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => false,
+        }
+    }
+
     // Dynamic checks that this value is being used with the correct store
     // context.
     #[doc(hidden)]
diff --git a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
index 9b465787e..74fe5be87 100644
--- a/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
+++ b/crates/wasmtime/src/runtime/gc/enabled/rooting.rs
@@ -407,9 +407,7 @@ impl RootSet {
     /// Calls to `{enter,exit}_lifo_scope` must happen in a strict LIFO order.
     #[inline]
     pub(crate) fn enter_lifo_scope(&self) -> usize {
-        let len = self.lifo_roots.len();
-        log::debug!("Entering GC root set LIFO scope: {len}");
-        len
+        self.lifo_roots.len()
     }
 
     /// Exit a LIFO rooting scope.
@@ -420,7 +418,6 @@ impl RootSet {
     /// Calls to `{enter,exit}_lifo_scope` must happen in a strict LIFO order.
     #[inline]
     pub(crate) fn exit_lifo_scope(&mut self, gc_store: &mut GcStore, scope: usize) {
-        log::debug!("Exiting GC root set LIFO scope: {scope}");
         debug_assert!(self.lifo_roots.len() >= scope);
 
         // If we actually have roots to unroot, call an out-of-line slow path.
diff --git a/crates/wasmtime/src/runtime/store.rs b/crates/wasmtime/src/runtime/store.rs
index f2e2bf116..16eb424d2 100644
--- a/crates/wasmtime/src/runtime/store.rs
+++ b/crates/wasmtime/src/runtime/store.rs
@@ -420,6 +420,29 @@ impl<'a> AutoAssertNoGc<'a> {
 
         AutoAssertNoGc { store, entered }
     }
+
+    /// Creates an `AutoAssertNoGc` value which is forcibly "not entered" and
+    /// disables checks for no GC happening for the duration of this value.
+    ///
+    /// This is used when it is statically otherwise known that a GC doesn't
+    /// happen for the various types involved.
+    ///
+    /// # Unsafety
+    ///
+    /// This method is `unsafe` as it does not provide the same safety
+    /// guarantees as `AutoAssertNoGc::new`. It must be guaranteed by the
+    /// caller that a GC doesn't happen.
+    #[inline]
+    pub unsafe fn disabled(store: &'a mut StoreOpaque) -> Self {
+        if cfg!(debug_assertions) {
+            AutoAssertNoGc::new(store)
+        } else {
+            AutoAssertNoGc {
+                store,
+                entered: false,
+            }
+        }
+    }
 }
 
 impl core::ops::Deref for AutoAssertNoGc<'_> {
diff --git a/crates/wasmtime/src/runtime/vm/instance.rs b/crates/wasmtime/src/runtime/vm/instance.rs
index 80fc138e4..fe3fbe6a2 100644
--- a/crates/wasmtime/src/runtime/vm/instance.rs
+++ b/crates/wasmtime/src/runtime/vm/instance.rs
@@ -27,9 +27,9 @@ use sptr::Strict;
 use wasmtime_environ::{
     packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex,
     DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex,
-    HostPtr, MemoryIndex, MemoryPlan, Module, ModuleInternedTypeIndex, PrimaryMap, TableIndex,
-    TableInitialValue, TableSegmentElements, Trap, VMOffsets, VMSharedTypeIndex, WasmHeapTopType,
-    VMCONTEXT_MAGIC,
+    HostPtr, MemoryIndex, MemoryPlan, Module, ModuleInternedTypeIndex, PrimaryMap, PtrSize,
+    TableIndex, TableInitialValue, TableSegmentElements, Trap, VMOffsets, VMSharedTypeIndex,
+    WasmHeapTopType, VMCONTEXT_MAGIC,
 };
 #[cfg(feature = "wmemcheck")]
 use wasmtime_wmemcheck::Wmemcheck;
@@ -246,16 +246,16 @@ impl Instance {
     ///
     /// This method is unsafe because the `offset` must be within bounds of the
     /// `VMContext` object trailing this instance.
-    unsafe fn vmctx_plus_offset<T>(&self, offset: u32) -> *const T {
+    unsafe fn vmctx_plus_offset<T>(&self, offset: impl Into<u32>) -> *const T {
         self.vmctx()
-            .byte_add(usize::try_from(offset).unwrap())
+            .byte_add(usize::try_from(offset.into()).unwrap())
             .cast()
     }
 
     /// Dual of `vmctx_plus_offset`, but for mutability.
-    unsafe fn vmctx_plus_offset_mut<T>(&mut self, offset: u32) -> *mut T {
+    unsafe fn vmctx_plus_offset_mut<T>(&mut self, offset: impl Into<u32>) -> *mut T {
         self.vmctx()
-            .byte_add(usize::try_from(offset).unwrap())
+            .byte_add(usize::try_from(offset.into()).unwrap())
             .cast()
     }
 
@@ -420,27 +420,27 @@ impl Instance {
     /// Return a pointer to the interrupts structure
     #[inline]
     pub fn runtime_limits(&mut self) -> *mut *const VMRuntimeLimits {
-        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_runtime_limits()) }
+        unsafe { self.vmctx_plus_offset_mut(self.offsets().ptr.vmctx_runtime_limits()) }
     }
 
     /// Return a pointer to the global epoch counter used by this instance.
     pub fn epoch_ptr(&mut self) -> *mut *const AtomicU64 {
-        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_epoch_ptr()) }
+        unsafe { self.vmctx_plus_offset_mut(self.offsets().ptr.vmctx_epoch_ptr()) }
     }
 
     /// Return a pointer to the GC heap base pointer.
     pub fn gc_heap_base(&mut self) -> *mut *mut u8 {
-        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_gc_heap_base()) }
+        unsafe { self.vmctx_plus_offset_mut(self.offsets().ptr.vmctx_gc_heap_base()) }
     }
 
     /// Return a pointer to the GC heap bound.
     pub fn gc_heap_bound(&mut self) -> *mut usize {
-        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_gc_heap_bound()) }
+        unsafe { self.vmctx_plus_offset_mut(self.offsets().ptr.vmctx_gc_heap_bound()) }
     }
 
     /// Return a pointer to the collector-specific heap data.
     pub fn gc_heap_data(&mut self) -> *mut *mut u8 {
-        unsafe { self.vmctx_plus_offset_mut(self.offsets().vmctx_gc_heap_data()) }
+        unsafe { self.vmctx_plus_offset_mut(self.offsets().ptr.vmctx_gc_heap_data()) }
     }
 
     /// Gets a pointer to this instance's `Store` which was originally
@@ -456,14 +456,14 @@ impl Instance {
     #[inline]
     pub fn store(&self) -> *mut dyn Store {
         let ptr =
-            unsafe { *self.vmctx_plus_offset::<*mut dyn Store>(self.offsets().vmctx_store()) };
+            unsafe { *self.vmctx_plus_offset::<*mut dyn Store>(self.offsets().ptr.vmctx_store()) };
         debug_assert!(!ptr.is_null());
         ptr
     }
 
     pub(crate) unsafe fn set_store(&mut self, store: Option<*mut dyn Store>) {
         if let Some(store) = store {
-            *self.vmctx_plus_offset_mut(self.offsets().vmctx_store()) = store;
+            *self.vmctx_plus_offset_mut(self.offsets().ptr.vmctx_store()) = store;
             *self.runtime_limits() = (*store).vmruntime_limits();
             *self.epoch_ptr() = (*store).epoch_ptr();
             self.set_gc_heap((*store).maybe_gc_store());
@@ -472,7 +472,7 @@ impl Instance {
                 mem::size_of::<*mut dyn Store>(),
                 mem::size_of::<[*mut (); 2]>()
             );
-            *self.vmctx_plus_offset_mut::<[*mut (); 2]>(self.offsets().vmctx_store()) =
+            *self.vmctx_plus_offset_mut::<[*mut (); 2]>(self.offsets().ptr.vmctx_store()) =
                 [ptr::null_mut(), ptr::null_mut()];
             *self.runtime_limits() = ptr::null_mut();
             *self.epoch_ptr() = ptr::null_mut();
@@ -493,7 +493,7 @@ impl Instance {
     }
 
     pub(crate) unsafe fn set_callee(&mut self, callee: Option<NonNull<VMFunctionBody>>) {
-        *self.vmctx_plus_offset_mut(self.offsets().vmctx_callee()) =
+        *self.vmctx_plus_offset_mut(self.offsets().ptr.vmctx_callee()) =
             callee.map_or(ptr::null_mut(), |c| c.as_ptr());
     }
 
@@ -717,7 +717,7 @@ impl Instance {
     ) {
         let type_index = unsafe {
             let base: *const VMSharedTypeIndex =
-                *self.vmctx_plus_offset_mut(self.offsets().vmctx_type_ids_array());
+                *self.vmctx_plus_offset_mut(self.offsets().ptr.vmctx_type_ids_array());
             *base.add(sig.index())
         };
 
@@ -1180,16 +1180,16 @@ impl Instance {
     ) {
         assert!(ptr::eq(module, self.module().as_ref()));
 
-        *self.vmctx_plus_offset_mut(offsets.vmctx_magic()) = VMCONTEXT_MAGIC;
+        *self.vmctx_plus_offset_mut(offsets.ptr.vmctx_magic()) = VMCONTEXT_MAGIC;
         self.set_callee(None);
         self.set_store(store.as_raw());
 
         // Initialize shared types
         let types = self.runtime_info.type_ids();
-        *self.vmctx_plus_offset_mut(offsets.vmctx_type_ids_array()) = types.as_ptr();
+        *self.vmctx_plus_offset_mut(offsets.ptr.vmctx_type_ids_array()) = types.as_ptr();
 
         // Initialize the built-in functions
-        *self.vmctx_plus_offset_mut(offsets.vmctx_builtin_functions()) =
+        *self.vmctx_plus_offset_mut(offsets.ptr.vmctx_builtin_functions()) =
             &VMBuiltinFunctionsArray::INIT;
 
         // Initialize the imports
diff --git a/crates/wasmtime/src/runtime/vm/vmcontext.rs b/crates/wasmtime/src/runtime/vm/vmcontext.rs
index 6530399ae..d9da984eb 100644
--- a/crates/wasmtime/src/runtime/vm/vmcontext.rs
+++ b/crates/wasmtime/src/runtime/vm/vmcontext.rs
@@ -840,16 +840,6 @@ mod test_vmruntime_limits {
     use std::mem::size_of;
     use wasmtime_environ::{Module, PtrSize, VMOffsets};
 
-    #[test]
-    fn vmctx_runtime_limits_offset() {
-        let module = Module::new();
-        let offsets = VMOffsets::new(size_of::<*mut u8>() as u8, &module);
-        assert_eq!(
-            offsets.vmctx_runtime_limits(),
-            u32::from(offsets.ptr.vmcontext_runtime_limits())
-        );
-    }
-
     #[test]
     fn field_offsets() {
         let module = Module::new();
diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs
index 07d60ed69..4752bdddc 100644
--- a/winch/codegen/src/codegen/mod.rs
+++ b/winch/codegen/src/codegen/mod.rs
@@ -328,13 +328,13 @@ where
             .as_u32()
             .checked_mul(sig_index_bytes.into())
             .unwrap();
-        let signatures_base_offset = self.env.vmoffsets.vmctx_type_ids_array();
+        let signatures_base_offset = self.env.vmoffsets.ptr.vmctx_type_ids_array();
         let scratch = <M::ABI as ABI>::scratch_reg();
         let funcref_sig_offset = self.env.vmoffsets.ptr.vm_func_ref_type_index();
 
         // Load the signatures address into the scratch register.
         self.masm.load(
-            self.masm.address_at_vmctx(signatures_base_offset),
+            self.masm.address_at_vmctx(signatures_base_offset.into()),
             scratch,
             ptr_size,
         );


Test patch:
diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs
index e6f51e5ac..d3d4424d7 100644
--- a/tests/all/pooling_allocator.rs
+++ b/tests/all/pooling_allocator.rs
@@ -648,6 +648,7 @@ fn instance_too_large() -> Result<()> {
 configured maximum of 16 bytes; breakdown of allocation requirement:
 
  * 72.73% - 256 bytes - instance state management
+ * 25.00% - 88 bytes - static vmctx data
 "
     } else {
         "\
@@ -655,7 +656,7 @@ instance allocation for this module requires 256 bytes which exceeds the \
 configured maximum of 16 bytes; breakdown of allocation requirement:
 
  * 62.50% - 160 bytes - instance state management
- * 6.25% - 16 bytes - jit store state
+ * 34.38% - 88 bytes - static vmctx data
 "
     };
     match Module::new(&engine, "(module)") {