Repository: bytecodealliance/wasmtime
Pull request #5346 (2022-11-29 19:26:57)

Tidy up some internals of instance allocation
This is a series of commits in preparation for eventually more closely integrating components with the pooling instance allocator. None of these are directly related to the end goal but were cleanups I noticed while reading through existing code and figure out how best to structure things. The main benefits of this commit are:

* Simplification of `ModuleRuntimeInfo` by coalescing some methods and removing extraneous ones.
* Some dead code elimination
* Removal of the computation of `VMOffsets` on each instantiation, now performed only once during module construction.

github-actions[bot]: #### Subscribe to Label Action

cc @peterhuene

<details>
This issue or pull request has been labeled: "wasmtime:api"

Thus the following users have been cc'd because of the following labels:

* peterhuene: wasmtime:api

To subscribe or unsubscribe from this label, edit the <code>.github/subscribe-to-label.json</code> configuration file.

[Learn more.](https://github.com/bytecodealliance/subscribe-to-label-action)
</details>

Patch:
diff --git a/crates/environ/src/vmoffsets.rs b/crates/environ/src/vmoffsets.rs
index de131eab4..520787010 100644
--- a/crates/environ/src/vmoffsets.rs
+++ b/crates/environ/src/vmoffsets.rs
@@ -31,11 +31,6 @@ use cranelift_entity::packed_option::ReservedValue;
 use std::convert::TryFrom;
 use wasmtime_types::OwnedMemoryIndex;
 
-/// Sentinel value indicating that wasm has been interrupted.
-// Note that this has a bit of an odd definition. See the `insert_stack_check`
-// function in `cranelift/codegen/src/isa/x86/abi.rs` for more information
-pub const INTERRUPTED: usize = usize::max_value() - 32 * 1024;
-
 #[cfg(target_pointer_width = "32")]
 fn cast_to_u32(sz: usize) -> u32 {
     u32::try_from(sz).unwrap()
diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs
index 52f7297b3..db3137d32 100644
--- a/crates/jit/src/instantiate.rs
+++ b/crates/jit/src/instantiate.rs
@@ -20,8 +20,7 @@ use wasmtime_environ::{
     PrimaryMap, SignatureIndex, StackMapInformation, Tunables, WasmFunctionInfo,
 };
 use wasmtime_runtime::{
-    CompiledModuleId, CompiledModuleIdAllocator, GdbJitImageRegistration, MmapVec, VMFunctionBody,
-    VMTrampoline,
+    CompiledModuleId, CompiledModuleIdAllocator, GdbJitImageRegistration, MmapVec, VMTrampoline,
 };
 
 /// Secondary in-memory results of compilation.
@@ -482,19 +481,22 @@ impl CompiledModule {
         Arc::get_mut(&mut self.module)
     }
 
-    /// Returns the map of all finished JIT functions compiled for this module
+    /// Returns an iterator over all functions defined within this module with
+    /// their index and their body in memory.
     #[inline]
     pub fn finished_functions(
         &self,
-    ) -> impl ExactSizeIterator<Item = (DefinedFuncIndex, *const [VMFunctionBody])> + '_ {
-        let text = self.text();
-        self.funcs.iter().map(move |(i, (_, loc))| {
-            let func = &text[loc.start as usize..][..loc.length as usize];
-            (
-                i,
-                std::ptr::slice_from_raw_parts(func.as_ptr().cast::<VMFunctionBody>(), func.len()),
-            )
-        })
+    ) -> impl ExactSizeIterator<Item = (DefinedFuncIndex, &[u8])> + '_ {
+        self.funcs
+            .iter()
+            .map(move |(i, _)| (i, self.finished_function(i)))
+    }
+
+    /// Returns the body of the function that `index` points to.
+    #[inline]
+    pub fn finished_function(&self, index: DefinedFuncIndex) -> &[u8] {
+        let (_, loc) = &self.funcs[index];
+        &self.text()[loc.start as usize..][..loc.length as usize]
     }
 
     /// Returns the per-signature trampolines for this module.
@@ -517,9 +519,7 @@ impl CompiledModule {
     ///
     /// The iterator returned iterates over the span of the compiled function in
     /// memory with the stack maps associated with those bytes.
-    pub fn stack_maps(
-        &self,
-    ) -> impl Iterator<Item = (*const [VMFunctionBody], &[StackMapInformation])> {
+    pub fn stack_maps(&self) -> impl Iterator<Item = (&[u8], &[StackMapInformation])> {
         self.finished_functions()
             .map(|(_, f)| f)
             .zip(self.funcs.values().map(|f| &f.0.stack_maps[..]))
diff --git a/crates/jit/src/profiling/jitdump_linux.rs b/crates/jit/src/profiling/jitdump_linux.rs
index 8ff1c3a11..1336bf215 100644
--- a/crates/jit/src/profiling/jitdump_linux.rs
+++ b/crates/jit/src/profiling/jitdump_linux.rs
@@ -83,7 +83,8 @@ impl State {
         let tid = pid; // ThreadId does appear to track underlying thread. Using PID.
 
         for (idx, func) in module.finished_functions() {
-            let (addr, len) = unsafe { ((*func).as_ptr().cast::<u8>(), (*func).len()) };
+            let addr = func.as_ptr();
+            let len = func.len();
             if let Some(img) = &dbg_image {
                 if let Err(err) = self.dump_from_debug_image(img, "wasm", addr, len, pid, tid) {
                     println!(
diff --git a/crates/jit/src/profiling/vtune.rs b/crates/jit/src/profiling/vtune.rs
index 3e6b6415a..b99511110 100644
--- a/crates/jit/src/profiling/vtune.rs
+++ b/crates/jit/src/profiling/vtune.rs
@@ -93,7 +93,8 @@ impl State {
             .unwrap_or_else(|| format!("wasm_module_{}", global_module_id));
 
         for (idx, func) in module.finished_functions() {
-            let (addr, len) = unsafe { ((*func).as_ptr().cast::<u8>(), (*func).len()) };
+            let addr = func.as_ptr();
+            let len = func.len();
             let method_name = super::debug_name(module, idx);
             log::trace!(
                 "new function {:?}::{:?} @ {:?}\n",
diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs
index 9e4f88ecb..84acc6294 100644
--- a/crates/runtime/src/instance.rs
+++ b/crates/runtime/src/instance.rs
@@ -13,7 +13,7 @@ use crate::vmcontext::{
 };
 use crate::{
     ExportFunction, ExportGlobal, ExportMemory, ExportTable, Imports, ModuleRuntimeInfo, Store,
-    VMFunctionBody,
+    VMFunctionBody, VMSharedSignatureIndex,
 };
 use anyhow::Error;
 use memoffset::offset_of;
@@ -61,9 +61,6 @@ pub(crate) struct Instance {
     /// functions, lazy initialization state, etc.
     runtime_info: Arc<dyn ModuleRuntimeInfo>,
 
-    /// Offsets in the `vmctx` region, precomputed from the `module` above.
-    offsets: VMOffsets<HostPtr>,
-
     /// WebAssembly linear memory data.
     ///
     /// This is where all runtime information about defined linear memories in
@@ -105,13 +102,12 @@ impl Instance {
     unsafe fn new_at(
         ptr: *mut Instance,
         alloc_size: usize,
-        offsets: VMOffsets<HostPtr>,
         req: InstanceAllocationRequest,
         memories: PrimaryMap<DefinedMemoryIndex, Memory>,
         tables: PrimaryMap<DefinedTableIndex, Table>,
     ) {
         // The allocation must be *at least* the size required of `Instance`.
-        assert!(alloc_size >= Self::alloc_layout(&offsets).size());
+        assert!(alloc_size >= Self::alloc_layout(req.runtime_info.offsets()).size());
 
         let module = req.runtime_info.module();
         let dropped_elements = EntitySet::with_capacity(module.passive_elements.len());
@@ -121,7 +117,6 @@ impl Instance {
             ptr,
             Instance {
                 runtime_info: req.runtime_info.clone(),
-                offsets,
                 memories,
                 tables,
                 dropped_elements,
@@ -133,7 +128,7 @@ impl Instance {
             },
         );
 
-        (*ptr).initialize_vmctx(module, req.store, req.imports);
+        (*ptr).initialize_vmctx(module, req.runtime_info.offsets(), req.store, req.imports);
     }
 
     /// Helper function to access various locations offset from our `*mut
@@ -148,24 +143,28 @@ impl Instance {
         self.runtime_info.module()
     }
 
+    fn offsets(&self) -> &VMOffsets<HostPtr> {
+        self.runtime_info.offsets()
+    }
+
     /// Return the indexed `VMFunctionImport`.
     fn imported_function(&self, index: FuncIndex) -> &VMFunctionImport {
-        unsafe { &*self.vmctx_plus_offset(self.offsets.vmctx_vmfunction_import(index)) }
+        unsafe { &*self.vmctx_plus_offset(self.offsets().vmctx_vmfunction_import(index)) }
     }
 
     /// Return the index `VMTableImport`.
     fn imported_table(&self, index: TableIndex) -> &VMTableImport {
-        unsafe { &*self.vmctx_plus_offset(self.offsets.vmctx_vmtable_import(index)) }
+        unsafe { &*self.vmctx_plus_offset(self.offsets().vmctx_vmtable_import(index)) }
     }
 
     /// Return the indexed `VMMemoryImport`.
     fn imported_memory(&self, index: MemoryIndex) -> &VMMemoryImport {
-        unsafe { &*self.vmctx_plus_offset(self.offsets.vmctx_vmmemory_import(index)) }
+        unsafe { &*self.vmctx_plus_offset(self.offsets().vmctx_vmmemory_import(index)) }
     }
 
     /// Return the indexed `VMGlobalImport`.
     fn imported_global(&self, index: GlobalIndex) -> &VMGlobalImport {
-        unsafe { &*self.vmctx_plus_offset(self.offsets.vmctx_vmglobal_import(index)) }
+        unsafe { &*self.vmctx_plus_offset(self.offsets().vmctx_vmglobal_import(index)) }
     }
 
     /// Return the indexed `VMTableDefinition`.
@@ -183,7 +182,7 @@ impl Instance {
 
     /// Return the indexed `VMTableDefinition`.
     fn table_ptr(&self, index: DefinedTableIndex) -> *mut VMTableDefinition {
-        unsafe { self.vmctx_plus_offset(self.offsets.vmctx_vmtable_definition(index)) }
+        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_vmtable_definition(index)) }
     }
 
     /// Get a locally defined or imported memory.
@@ -221,7 +220,7 @@ impl Instance {
 
     /// Return the indexed `VMMemoryDefinition`.
     fn memory_ptr(&self, index: DefinedMemoryIndex) -> *mut VMMemoryDefinition {
-        unsafe { *self.vmctx_plus_offset(self.offsets.vmctx_vmmemory_pointer(index)) }
+        unsafe { *self.vmctx_plus_offset(self.offsets().vmctx_vmmemory_pointer(index)) }
     }
 
     /// Return the indexed `VMGlobalDefinition`.
@@ -231,7 +230,7 @@ impl Instance {
 
     /// Return the indexed `VMGlobalDefinition`.
     fn global_ptr(&self, index: DefinedGlobalIndex) -> *mut VMGlobalDefinition {
-        unsafe { self.vmctx_plus_offset(self.offsets.vmctx_vmglobal_definition(index)) }
+        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_vmglobal_definition(index)) }
     }
 
     /// Get a raw pointer to the global at the given index regardless whether it
@@ -251,17 +250,17 @@ impl Instance {
 
     /// Return a pointer to the interrupts structure
     pub fn runtime_limits(&self) -> *mut *const VMRuntimeLimits {
-        unsafe { self.vmctx_plus_offset(self.offsets.vmctx_runtime_limits()) }
+        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_runtime_limits()) }
     }
 
     /// Return a pointer to the global epoch counter used by this instance.
     pub fn epoch_ptr(&self) -> *mut *const AtomicU64 {
-        unsafe { self.vmctx_plus_offset(self.offsets.vmctx_epoch_ptr()) }
+        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_epoch_ptr()) }
     }
 
     /// Return a pointer to the `VMExternRefActivationsTable`.
     pub fn externref_activations_table(&self) -> *mut *mut VMExternRefActivationsTable {
-        unsafe { self.vmctx_plus_offset(self.offsets.vmctx_externref_activations_table()) }
+        unsafe { self.vmctx_plus_offset(self.offsets().vmctx_externref_activations_table()) }
     }
 
     /// Gets a pointer to this instance's `Store` which was originally
@@ -276,14 +275,15 @@ impl Instance {
     /// store).
     #[inline]
     pub fn store(&self) -> *mut dyn Store {
-        let ptr = unsafe { *self.vmctx_plus_offset::<*mut dyn Store>(self.offsets.vmctx_store()) };
+        let ptr =
+            unsafe { *self.vmctx_plus_offset::<*mut dyn Store>(self.offsets().vmctx_store()) };
         assert!(!ptr.is_null());
         ptr
     }
 
     pub unsafe fn set_store(&mut self, store: Option<*mut dyn Store>) {
         if let Some(store) = store {
-            *self.vmctx_plus_offset(self.offsets.vmctx_store()) = store;
+            *self.vmctx_plus_offset(self.offsets().vmctx_store()) = store;
             *self.runtime_limits() = (*store).vmruntime_limits();
             *self.epoch_ptr() = (*store).epoch_ptr();
             *self.externref_activations_table() = (*store).externref_activations_table().0;
@@ -292,7 +292,7 @@ impl Instance {
                 mem::size_of::<*mut dyn Store>(),
                 mem::size_of::<[*mut (); 2]>()
             );
-            *self.vmctx_plus_offset::<[*mut (); 2]>(self.offsets.vmctx_store()) =
+            *self.vmctx_plus_offset::<[*mut (); 2]>(self.offsets().vmctx_store()) =
                 [ptr::null_mut(), ptr::null_mut()];
 
             *self.runtime_limits() = ptr::null_mut();
@@ -302,7 +302,7 @@ impl Instance {
     }
 
     pub(crate) unsafe fn set_callee(&mut self, callee: Option<NonNull<VMFunctionBody>>) {
-        *self.vmctx_plus_offset(self.offsets.vmctx_callee()) =
+        *self.vmctx_plus_offset(self.offsets().vmctx_callee()) =
             callee.map_or(ptr::null_mut(), |c| c.as_ptr());
     }
 
@@ -499,13 +499,15 @@ impl Instance {
         sig: SignatureIndex,
         into: *mut VMCallerCheckedAnyfunc,
     ) {
-        let type_index = self.runtime_info.signature(sig);
+        let type_index = unsafe {
+            let base: *const VMSharedSignatureIndex =
+                *self.vmctx_plus_offset(self.offsets().vmctx_signature_ids_array());
+            *base.add(sig.index())
+        };
 
         let (func_ptr, vmctx) = if let Some(def_index) = self.module().defined_func_index(index) {
             (
-                (self.runtime_info.image_base()
-                    + self.runtime_info.function_loc(def_index).start as usize)
-                    as *mut _,
+                self.runtime_info.function(def_index),
                 VMOpaqueContext::from_vmcontext(self.vmctx_ptr()),
             )
         } else {
@@ -569,7 +571,7 @@ impl Instance {
             let sig = func.signature;
             let anyfunc: *mut VMCallerCheckedAnyfunc = self
                 .vmctx_plus_offset::<VMCallerCheckedAnyfunc>(
-                    self.offsets.vmctx_anyfunc(func.anyfunc),
+                    self.offsets().vmctx_anyfunc(func.anyfunc),
                 );
             self.construct_anyfunc(index, sig, anyfunc);
 
@@ -898,44 +900,49 @@ impl Instance {
     /// The `VMContext` memory is assumed to be uninitialized; any field
     /// that we need in a certain state will be explicitly written by this
     /// function.
-    unsafe fn initialize_vmctx(&mut self, module: &Module, store: StorePtr, imports: Imports) {
+    unsafe fn initialize_vmctx(
+        &mut self,
+        module: &Module,
+        offsets: &VMOffsets<HostPtr>,
+        store: StorePtr,
+        imports: Imports,
+    ) {
         assert!(std::ptr::eq(module, self.module().as_ref()));
 
-        *self.vmctx_plus_offset(self.offsets.vmctx_magic()) = VMCONTEXT_MAGIC;
+        *self.vmctx_plus_offset(offsets.vmctx_magic()) = VMCONTEXT_MAGIC;
         self.set_callee(None);
         self.set_store(store.as_raw());
 
         // Initialize shared signatures
         let signatures = self.runtime_info.signature_ids();
-        *self.vmctx_plus_offset(self.offsets.vmctx_signature_ids_array()) = signatures.as_ptr();
+        *self.vmctx_plus_offset(offsets.vmctx_signature_ids_array()) = signatures.as_ptr();
 
         // Initialize the built-in functions
-        *self.vmctx_plus_offset(self.offsets.vmctx_builtin_functions()) =
-            &VMBuiltinFunctionsArray::INIT;
+        *self.vmctx_plus_offset(offsets.vmctx_builtin_functions()) = &VMBuiltinFunctionsArray::INIT;
 
         // Initialize the imports
         debug_assert_eq!(imports.functions.len(), module.num_imported_funcs);
         ptr::copy_nonoverlapping(
             imports.functions.as_ptr(),
-            self.vmctx_plus_offset(self.offsets.vmctx_imported_functions_begin()),
+            self.vmctx_plus_offset(offsets.vmctx_imported_functions_begin()),
             imports.functions.len(),
         );
         debug_assert_eq!(imports.tables.len(), module.num_imported_tables);
         ptr::copy_nonoverlapping(
             imports.tables.as_ptr(),
-            self.vmctx_plus_offset(self.offsets.vmctx_imported_tables_begin()),
+            self.vmctx_plus_offset(offsets.vmctx_imported_tables_begin()),
             imports.tables.len(),
         );
         debug_assert_eq!(imports.memories.len(), module.num_imported_memories);
         ptr::copy_nonoverlapping(
             imports.memories.as_ptr(),
-            self.vmctx_plus_offset(self.offsets.vmctx_imported_memories_begin()),
+            self.vmctx_plus_offset(offsets.vmctx_imported_memories_begin()),
             imports.memories.len(),
         );
         debug_assert_eq!(imports.globals.len(), module.num_imported_globals);
         ptr::copy_nonoverlapping(
             imports.globals.as_ptr(),
-            self.vmctx_plus_offset(self.offsets.vmctx_imported_globals_begin()),
+            self.vmctx_plus_offset(offsets.vmctx_imported_globals_begin()),
             imports.globals.len(),
         );
 
@@ -946,7 +953,7 @@ impl Instance {
         // any state now.
 
         // Initialize the defined tables
-        let mut ptr = self.vmctx_plus_offset(self.offsets.vmctx_tables_begin());
+        let mut ptr = self.vmctx_plus_offset(offsets.vmctx_tables_begin());
         for i in 0..module.table_plans.len() - module.num_imported_tables {
             ptr::write(ptr, self.tables[DefinedTableIndex::new(i)].vmtable());
             ptr = ptr.add(1);
@@ -957,8 +964,8 @@ impl Instance {
         // time. Entries in `defined_memories` hold a pointer to a definition
         // (all memories) whereas the `owned_memories` hold the actual
         // definitions of memories owned (not shared) in the module.
-        let mut ptr = self.vmctx_plus_offset(self.offsets.vmctx_memories_begin());
-        let mut owned_ptr = self.vmctx_plus_offset(self.offsets.vmctx_owned_memories_begin());
+        let mut ptr = self.vmctx_plus_offset(offsets.vmctx_memories_begin());
+        let mut owned_ptr = self.vmctx_plus_offset(offsets.vmctx_owned_memories_begin());
         for i in 0..module.memory_plans.len() - module.num_imported_memories {
             let defined_memory_index = DefinedMemoryIndex::new(i);
             let memory_index = module.memory_index(defined_memory_index);
diff --git a/crates/runtime/src/instance/allocator.rs b/crates/runtime/src/instance/allocator.rs
index 9ea6d9501..e8f8e75f0 100644
--- a/crates/runtime/src/instance/allocator.rs
+++ b/crates/runtime/src/instance/allocator.rs
@@ -92,18 +92,11 @@ impl StorePtr {
 /// This trait is unsafe as it requires knowledge of Wasmtime's runtime internals to implement correctly.
 pub unsafe trait InstanceAllocator: Send + Sync {
     /// Validates that a module is supported by the allocator.
-    fn validate(&self, module: &Module) -> Result<()> {
-        drop(module);
+    fn validate(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()> {
+        drop((module, offsets));
         Ok(())
     }
 
-    /// Adjusts the tunables prior to creation of any JIT compiler.
-    ///
-    /// This method allows the instance allocator control over tunables passed to a `wasmtime_jit::Compiler`.
-    fn adjust_tunables(&self, tunables: &mut wasmtime_environ::Tunables) {
-        drop(tunables);
-    }
-
     /// Allocates an instance for the given allocation request.
     ///
     /// # Safety
@@ -464,11 +457,9 @@ pub unsafe fn allocate_single_memory_instance(
     let mut memories = PrimaryMap::default();
     memories.push(memory);
     let tables = PrimaryMap::default();
-    let module = req.runtime_info.module();
-    let offsets = VMOffsets::new(HostPtr, module);
-    let layout = Instance::alloc_layout(&offsets);
+    let layout = Instance::alloc_layout(req.runtime_info.offsets());
     let instance = alloc::alloc(layout) as *mut Instance;
-    Instance::new_at(instance, layout.size(), offsets, req, memories, tables);
+    Instance::new_at(instance, layout.size(), req, memories, tables);
     Ok(InstanceHandle { instance })
 }
 
@@ -476,7 +467,7 @@ pub unsafe fn allocate_single_memory_instance(
 ///
 /// See [`InstanceAllocator::deallocate()`] for more details.
 pub unsafe fn deallocate(handle: &InstanceHandle) {
-    let layout = Instance::alloc_layout(&handle.instance().offsets);
+    let layout = Instance::alloc_layout(handle.instance().offsets());
     ptr::drop_in_place(handle.instance);
     alloc::dealloc(handle.instance.cast(), layout);
 }
@@ -485,12 +476,10 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
     unsafe fn allocate(&self, mut req: InstanceAllocationRequest) -> Result<InstanceHandle> {
         let memories = self.create_memories(&mut req.store, &req.runtime_info)?;
         let tables = Self::create_tables(&mut req.store, &req.runtime_info)?;
-        let module = req.runtime_info.module();
-        let offsets = VMOffsets::new(HostPtr, module);
-        let layout = Instance::alloc_layout(&offsets);
+        let layout = Instance::alloc_layout(req.runtime_info.offsets());
         let instance_ptr = alloc::alloc(layout) as *mut Instance;
 
-        Instance::new_at(instance_ptr, layout.size(), offsets, req, memories, tables);
+        Instance::new_at(instance_ptr, layout.size(), req, memories, tables);
 
         Ok(InstanceHandle {
             instance: instance_ptr,
diff --git a/crates/runtime/src/instance/allocator/pooling.rs b/crates/runtime/src/instance/allocator/pooling.rs
index b40ab8b2a..efd5b57fc 100644
--- a/crates/runtime/src/instance/allocator/pooling.rs
+++ b/crates/runtime/src/instance/allocator/pooling.rs
@@ -169,7 +169,7 @@ impl InstancePool {
         // If this fails then it's a configuration error at the `Engine` level
         // from when this pooling allocator was created and that needs updating
         // if this is to succeed.
-        let offsets = self.validate_instance_size(module)?;
+        self.validate_instance_size(req.runtime_info.offsets())?;
 
         let mut memories =
             PrimaryMap::with_capacity(module.memory_plans.len() - module.num_imported_memories);
@@ -192,14 +192,7 @@ impl InstancePool {
 
         let instance_ptr = self.instance(instance_index) as _;
 
-        Instance::new_at(
-            instance_ptr,
-            self.instance_size,
-            offsets,
-            req,
-            memories,
-            tables,
-        );
+        Instance::new_at(instance_ptr, self.instance_size, req, memories, tables);
 
         Ok(InstanceHandle {
             instance: instance_ptr,
@@ -485,11 +478,10 @@ impl InstancePool {
         Ok(())
     }
 
-    fn validate_instance_size(&self, module: &Module) -> Result<VMOffsets<HostPtr>> {
-        let offsets = VMOffsets::new(HostPtr, module);
-        let layout = Instance::alloc_layout(&offsets);
+    fn validate_instance_size(&self, offsets: &VMOffsets<HostPtr>) -> Result<()> {
+        let layout = Instance::alloc_layout(offsets);
         if layout.size() <= self.instance_size {
-            return Ok(offsets);
+            return Ok(());
         }
 
         // If this `module` exceeds the allocation size allotted to it then an
@@ -1078,17 +1070,10 @@ impl PoolingInstanceAllocator {
 }
 
 unsafe impl InstanceAllocator for PoolingInstanceAllocator {
-    fn validate(&self, module: &Module) -> Result<()> {
+    fn validate(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()> {
         self.instances.validate_memory_plans(module)?;
         self.instances.validate_table_plans(module)?;
-
-        // Note that this check is not 100% accurate for cross-compiled systems
-        // where the pointer size may change since this check is often performed
-        // at compile time instead of runtime. Given that Wasmtime is almost
-        // always on a 64-bit platform though this is generally ok, and
-        // otherwise this check also happens during instantiation to
-        // double-check at that point.
-        self.instances.validate_instance_size(module)?;
+        self.instances.validate_instance_size(offsets)?;
 
         Ok(())
     }
@@ -1145,26 +1130,22 @@ unsafe impl InstanceAllocator for PoolingInstanceAllocator {
 #[cfg(test)]
 mod test {
     use super::*;
-    use crate::{CompiledModuleId, Imports, MemoryImage, StorePtr, VMSharedSignatureIndex};
+    use crate::{
+        CompiledModuleId, Imports, MemoryImage, StorePtr, VMFunctionBody, VMSharedSignatureIndex,
+    };
     use std::sync::Arc;
-    use wasmtime_environ::{DefinedFuncIndex, DefinedMemoryIndex, FunctionLoc, SignatureIndex};
+    use wasmtime_environ::{DefinedFuncIndex, DefinedMemoryIndex};
 
     pub(crate) fn empty_runtime_info(
         module: Arc<wasmtime_environ::Module>,
     ) -> Arc<dyn ModuleRuntimeInfo> {
-        struct RuntimeInfo(Arc<wasmtime_environ::Module>);
+        struct RuntimeInfo(Arc<wasmtime_environ::Module>, VMOffsets<HostPtr>);
 
         impl ModuleRuntimeInfo for RuntimeInfo {
             fn module(&self) -> &Arc<wasmtime_environ::Module> {
                 &self.0
             }
-            fn image_base(&self) -> usize {
-                0
-            }
-            fn function_loc(&self, _: DefinedFuncIndex) -> &FunctionLoc {
-                unimplemented!()
-            }
-            fn signature(&self, _: SignatureIndex) -> VMSharedSignatureIndex {
+            fn function(&self, _: DefinedFuncIndex) -> *mut VMFunctionBody {
                 unimplemented!()
             }
             fn memory_image(
@@ -1183,9 +1164,13 @@ mod test {
             fn signature_ids(&self) -> &[VMSharedSignatureIndex] {
                 &[]
             }
+            fn offsets(&self) -> &VMOffsets<HostPtr> {
+                &self.1
+            }
         }
 
-        Arc::new(RuntimeInfo(module))
+        let offsets = VMOffsets::new(HostPtr, &module);
+        Arc::new(RuntimeInfo(module, offsets))
     }
 
     #[cfg(target_pointer_width = "64")]
diff --git a/crates/runtime/src/lib.rs b/crates/runtime/src/lib.rs
index 373081b1a..d74363d13 100644
--- a/crates/runtime/src/lib.rs
+++ b/crates/runtime/src/lib.rs
@@ -23,10 +23,7 @@
 use anyhow::Error;
 use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
 use std::sync::Arc;
-use wasmtime_environ::DefinedFuncIndex;
-use wasmtime_environ::DefinedMemoryIndex;
-use wasmtime_environ::FunctionLoc;
-use wasmtime_environ::SignatureIndex;
+use wasmtime_environ::{DefinedFuncIndex, DefinedMemoryIndex, HostPtr, VMOffsets};
 
 #[macro_use]
 mod trampolines;
@@ -172,15 +169,8 @@ pub trait ModuleRuntimeInfo: Send + Sync + 'static {
     /// The underlying Module.
     fn module(&self) -> &Arc<wasmtime_environ::Module>;
 
-    /// The signatures.
-    fn signature(&self, index: SignatureIndex) -> VMSharedSignatureIndex;
-
-    /// The base address of where JIT functions are located.
-    fn image_base(&self) -> usize;
-
-    /// Descriptors about each compiled function, such as the offset from
-    /// `image_base`.
-    fn function_loc(&self, func_index: DefinedFuncIndex) -> &FunctionLoc;
+    /// Returns the address, in memory, that the function `index` resides at.
+    fn function(&self, index: DefinedFuncIndex) -> *mut VMFunctionBody;
 
     /// Returns the `MemoryImage` structure used for copy-on-write
     /// initialization of the memory, if it's applicable.
@@ -198,6 +188,9 @@ pub trait ModuleRuntimeInfo: Send + Sync + 'static {
     /// Returns an array, indexed by `SignatureIndex` of all
     /// `VMSharedSignatureIndex` entries corresponding to the `SignatureIndex`.
     fn signature_ids(&self) -> &[VMSharedSignatureIndex];
+
+    /// Offset information for the current host.
+    fn offsets(&self) -> &VMOffsets<HostPtr>;
 }
 
 /// Returns the host OS page size, in bytes.
diff --git a/crates/runtime/src/traphandlers/backtrace.rs b/crates/runtime/src/traphandlers/backtrace.rs
index b18d8b96c..0efed892b 100644
--- a/crates/runtime/src/traphandlers/backtrace.rs
+++ b/crates/runtime/src/traphandlers/backtrace.rs
@@ -74,6 +74,11 @@ impl Frame {
 }
 
 impl Backtrace {
+    /// Returns an empty backtrace
+    pub fn empty() -> Backtrace {
+        Backtrace(Vec::new())
+    }
+
     /// Capture the current Wasm stack in a backtrace.
     pub fn new() -> Backtrace {
         tls::with(|state| match state {
diff --git a/crates/wasmtime/src/engine.rs b/crates/wasmtime/src/engine.rs
index 9c4a67f25..65fb029f3 100644
--- a/crates/wasmtime/src/engine.rs
+++ b/crates/wasmtime/src/engine.rs
@@ -86,9 +86,9 @@ impl Engine {
 
         #[cfg(compiler)]
         let compiler = config.build_compiler()?;
+        drop(&mut config); // silence warnings without `cfg(compiler)`
 
         let allocator = config.build_allocator()?;
-        allocator.adjust_tunables(&mut config.tunables);
         let profiler = config.build_profiler()?;
 
         Ok(Engine {
diff --git a/crates/wasmtime/src/module.rs b/crates/wasmtime/src/module.rs
index 97efc56a0..d52832195 100644
--- a/crates/wasmtime/src/module.rs
+++ b/crates/wasmtime/src/module.rs
@@ -14,12 +14,13 @@ use std::path::Path;
 use std::sync::Arc;
 use wasmparser::{Parser, ValidPayload, Validator};
 use wasmtime_environ::{
-    DefinedFuncIndex, DefinedMemoryIndex, FunctionLoc, ModuleEnvironment, ModuleTranslation,
-    ModuleTypes, ObjectKind, PrimaryMap, SignatureIndex, WasmFunctionInfo,
+    DefinedFuncIndex, DefinedMemoryIndex, HostPtr, ModuleEnvironment, ModuleTranslation,
+    ModuleTypes, ObjectKind, PrimaryMap, VMOffsets, WasmFunctionInfo,
 };
 use wasmtime_jit::{CodeMemory, CompiledModule, CompiledModuleInfo};
 use wasmtime_runtime::{
-    CompiledModuleId, MemoryImage, MmapVec, ModuleMemoryImages, VMSharedSignatureIndex,
+    CompiledModuleId, MemoryImage, MmapVec, ModuleMemoryImages, VMFunctionBody,
+    VMSharedSignatureIndex,
 };
 
 mod registry;
@@ -123,6 +124,9 @@ struct ModuleInner {
 
     /// Flag indicating whether this module can be serialized or not.
     serializable: bool,
+
+    /// Runtime offset information for `VMContext`.
+    offsets: VMOffsets<HostPtr>,
 }
 
 impl Module {
@@ -664,7 +668,8 @@ impl Module {
         )?;
 
         // Validate the module can be used with the current allocator
-        engine.allocator().validate(module.module())?;
+        let offsets = VMOffsets::new(HostPtr, module.module());
+        engine.allocator().validate(module.module(), &offsets)?;
 
         Ok(Self {
             inner: Arc::new(ModuleInner {
@@ -673,6 +678,7 @@ impl Module {
                 memory_images: OnceCell::new(),
                 module,
                 serializable,
+                offsets,
             }),
         })
     }
@@ -1098,16 +1104,12 @@ impl wasmtime_runtime::ModuleRuntimeInfo for ModuleInner {
         self.module.module()
     }
 
-    fn signature(&self, index: SignatureIndex) -> VMSharedSignatureIndex {
-        self.code.signatures().as_module_map()[index]
-    }
-
-    fn image_base(&self) -> usize {
-        self.module.text().as_ptr() as usize
-    }
-
-    fn function_loc(&self, index: DefinedFuncIndex) -> &FunctionLoc {
-        self.module.func_loc(index)
+    fn function(&self, index: DefinedFuncIndex) -> *mut VMFunctionBody {
+        self.module
+            .finished_function(index)
+            .as_ptr()
+            .cast::<VMFunctionBody>()
+            .cast_mut()
     }
 
     fn memory_image(&self, memory: DefinedMemoryIndex) -> Result<Option<&Arc<MemoryImage>>> {
@@ -1126,6 +1128,10 @@ impl wasmtime_runtime::ModuleRuntimeInfo for ModuleInner {
     fn signature_ids(&self) -> &[VMSharedSignatureIndex] {
         self.code.signatures().as_module_map().values().as_slice()
     }
+
+    fn offsets(&self) -> &VMOffsets<HostPtr> {
+        &self.offsets
+    }
 }
 
 impl wasmtime_runtime::ModuleInfo for ModuleInner {
@@ -1160,26 +1166,22 @@ impl wasmtime_runtime::ModuleInfo for ModuleInner {
 /// default-callee instance).
 pub(crate) struct BareModuleInfo {
     module: Arc<wasmtime_environ::Module>,
-    image_base: usize,
-    one_signature: Option<(SignatureIndex, VMSharedSignatureIndex)>,
+    one_signature: Option<VMSharedSignatureIndex>,
+    offsets: VMOffsets<HostPtr>,
 }
 
 impl BareModuleInfo {
     pub(crate) fn empty(module: Arc<wasmtime_environ::Module>) -> Self {
-        BareModuleInfo {
-            module,
-            image_base: 0,
-            one_signature: None,
-        }
+        BareModuleInfo::maybe_imported_func(module, None)
     }
 
     pub(crate) fn maybe_imported_func(
         module: Arc<wasmtime_environ::Module>,
-        one_signature: Option<(SignatureIndex, VMSharedSignatureIndex)>,
+        one_signature: Option<VMSharedSignatureIndex>,
     ) -> Self {
         BareModuleInfo {
+            offsets: VMOffsets::new(HostPtr, &module),
             module,
-            image_base: 0,
             one_signature,
         }
     }
@@ -1194,19 +1196,7 @@ impl wasmtime_runtime::ModuleRuntimeInfo for BareModuleInfo {
         &self.module
     }
 
-    fn signature(&self, index: SignatureIndex) -> VMSharedSignatureIndex {
-        let (signature_id, signature) = self
-            .one_signature
-            .expect("Signature for one function should be present if queried");
-        assert_eq!(index, signature_id);
-        signature
-    }
-
-    fn image_base(&self) -> usize {
-        self.image_base
-    }
-
-    fn function_loc(&self, _index: DefinedFuncIndex) -> &FunctionLoc {
+    fn function(&self, _index: DefinedFuncIndex) -> *mut VMFunctionBody {
         unreachable!()
     }
 
@@ -1224,10 +1214,14 @@ impl wasmtime_runtime::ModuleRuntimeInfo for BareModuleInfo {
 
     fn signature_ids(&self) -> &[VMSharedSignatureIndex] {
         match &self.one_signature {
-            Some((_, id)) => std::slice::from_ref(id),
+            Some(id) => std::slice::from_ref(id),
             None => &[],
         }
     }
+
+    fn offsets(&self) -> &VMOffsets<HostPtr> {
+        &self.offsets
+    }
 }
 
 /// Helper method to construct a `ModuleMemoryImages` for an associated
diff --git a/crates/wasmtime/src/module/registry.rs b/crates/wasmtime/src/module/registry.rs
index 21bae8c5a..7784d4cb1 100644
--- a/crates/wasmtime/src/module/registry.rs
+++ b/crates/wasmtime/src/module/registry.rs
@@ -161,7 +161,7 @@ impl LoadedCode {
             // functions.
             None => return,
         };
-        let start = unsafe { (*func).as_ptr() as usize };
+        let start = func.as_ptr() as usize;
 
         match self.modules.entry(start) {
             // This module is already present, and it should be the same as
@@ -279,9 +279,9 @@ fn test_frame_info() -> Result<(), anyhow::Error> {
     Instance::new(&mut store, &module, &[])?;
 
     for (i, alloc) in module.compiled_module().finished_functions() {
-        let (start, end) = unsafe {
-            let ptr = (*alloc).as_ptr();
-            let len = (*alloc).len();
+        let (start, end) = {
+            let ptr = alloc.as_ptr();
+            let len = alloc.len();
             (ptr as usize, ptr as usize + len)
         };
         for pc in start..end {
diff --git a/crates/wasmtime/src/trampoline.rs b/crates/wasmtime/src/trampoline.rs
index 46c9fa202..59e62cb97 100644
--- a/crates/wasmtime/src/trampoline.rs
+++ b/crates/wasmtime/src/trampoline.rs
@@ -17,7 +17,7 @@ use crate::{GlobalType, MemoryType, TableType, Val};
 use anyhow::Result;
 use std::any::Any;
 use std::sync::Arc;
-use wasmtime_environ::{GlobalIndex, MemoryIndex, Module, SignatureIndex, TableIndex};
+use wasmtime_environ::{GlobalIndex, MemoryIndex, Module, TableIndex};
 use wasmtime_runtime::{
     Imports, InstanceAllocationRequest, InstanceAllocator, OnDemandInstanceAllocator, SharedMemory,
     StorePtr, VMFunctionImport, VMSharedSignatureIndex,
@@ -28,7 +28,7 @@ fn create_handle(
     store: &mut StoreOpaque,
     host_state: Box<dyn Any + Send + Sync>,
     func_imports: &[VMFunctionImport],
-    one_signature: Option<(SignatureIndex, VMSharedSignatureIndex)>,
+    one_signature: Option<VMSharedSignatureIndex>,
 ) -> Result<InstanceId> {
     let mut imports = Imports::default();
     imports.functions = func_imports;
diff --git a/crates/wasmtime/src/trampoline/global.rs b/crates/wasmtime/src/trampoline/global.rs
index ccc00c142..e84601bd8 100644
--- a/crates/wasmtime/src/trampoline/global.rs
+++ b/crates/wasmtime/src/trampoline/global.rs
@@ -39,8 +39,8 @@ pub fn create_global(store: &mut StoreOpaque, gt: &GlobalType, val: Val) -> Resu
                 // our global with a `ref.func` to grab that imported function.
                 let f = f.caller_checked_anyfunc(store);
                 let f = unsafe { f.as_ref() };
-                let sig_id = SignatureIndex::from_u32(u32::max_value() - 1);
-                one_signature = Some((sig_id, f.type_index));
+                let sig_id = SignatureIndex::from_u32(0);
+                one_signature = Some(f.type_index);
                 module.types.push(ModuleType::Function(sig_id));
                 let func_index = module.push_escaped_function(sig_id, AnyfuncIndex::from_u32(0));
                 module.num_imported_funcs = 1;
diff --git a/crates/wasmtime/src/trap.rs b/crates/wasmtime/src/trap.rs
index 8c1d21aa4..f16f19aed 100644
--- a/crates/wasmtime/src/trap.rs
+++ b/crates/wasmtime/src/trap.rs
@@ -1,5 +1,5 @@
 use crate::store::StoreOpaque;
-use crate::Module;
+use crate::{AsContext, Module};
 use anyhow::Error;
 use std::fmt;
 use wasmtime_environ::{EntityRef, FilePos};
@@ -112,7 +112,7 @@ pub(crate) fn from_runtime_box(
     };
     match backtrace {
         Some(bt) => {
-            let bt = WasmBacktrace::new(store, bt, pc);
+            let bt = WasmBacktrace::from_captured(store, bt, pc);
             if bt.wasm_trace.is_empty() {
                 error
             } else {
@@ -183,7 +183,79 @@ pub struct WasmBacktrace {
 }
 
 impl WasmBacktrace {
-    fn new(
+    /// Captures a trace of the WebAssembly frames on the stack for the
+    /// provided store.
+    ///
+    /// This will return a [`WasmBacktrace`] which holds captured
+    /// [`FrameInfo`]s for each frame of WebAssembly on the call stack of the
+    /// current thread. If no WebAssembly is on the stack then the returned
+    /// backtrace will have no frames in it.
+    ///
+    /// Note that this function will respect the [`Config::wasm_backtrace`]
+    /// configuration option and will return an empty backtrace if that is
+    /// disabled. To always capture a backtrace use the
+    /// [`WasmBacktrace::force_capture`] method.
+    ///
+    /// Also note that this function will only capture frames from the
+    /// specified `store` on the stack, ignoring frames from other stores if
+    /// present.
+    ///
+    /// [`Config::wasm_backtrace`]: crate::Config::wasm_backtrace
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// # use wasmtime::*;
+    /// # use anyhow::Result;
+    /// # fn main() -> Result<()> {
+    /// let engine = Engine::default();
+    /// let module = Module::new(
+    ///     &engine,
+    ///     r#"
+    ///         (module
+    ///             (import "" "" (func $host))
+    ///             (func $foo (export "f") call $bar)
+    ///             (func $bar call $host)
+    ///         )
+    ///     "#,
+    /// )?;
+    ///
+    /// let mut store = Store::new(&engine, ());
+    /// let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
+    ///     let trace = WasmBacktrace::capture(&cx);
+    ///     println!("{trace:?}");
+    /// });
+    /// let instance = Instance::new(&mut store, &module, &[func.into()])?;
+    /// let func = instance.get_typed_func::<(), ()>(&mut store, "f")?;
+    /// func.call(&mut store, ())?;
+    /// # Ok(())
+    /// # }
+    /// ```
+    pub fn capture(store: impl AsContext) -> WasmBacktrace {
+        let store = store.as_context();
+        if store.engine().config().wasm_backtrace {
+            Self::force_capture(store)
+        } else {
+            WasmBacktrace {
+                wasm_trace: Vec::new(),
+                hint_wasm_backtrace_details_env: false,
+                runtime_trace: wasmtime_runtime::Backtrace::empty(),
+            }
+        }
+    }
+
+    /// Unconditionally captures a trace of the WebAssembly frames on the stack
+    /// for the provided store.
+    ///
+    /// Same as [`WasmBacktrace::capture`] except that it disregards the
+    /// [`Config::wasm_backtrace`](crate::Config::wasm_backtrace) setting and
+    /// always captures a backtrace.
+    pub fn force_capture(store: impl AsContext) -> WasmBacktrace {
+        let store = store.as_context();
+        Self::from_captured(store.0, wasmtime_runtime::Backtrace::new(), None)
+    }
+
+    fn from_captured(
         store: &StoreOpaque,
         runtime_trace: wasmtime_runtime::Backtrace,
         trap_pc: Option<usize>,


Test patch:
diff --git a/tests/all/pooling_allocator.rs b/tests/all/pooling_allocator.rs
index 0354a9c74..dcce9bfb3 100644
--- a/tests/all/pooling_allocator.rs
+++ b/tests/all/pooling_allocator.rs
@@ -646,10 +646,11 @@ fn instance_too_large() -> Result<()> {
 
     let engine = Engine::new(&config)?;
     let expected = "\
-instance allocation for this module requires 336 bytes which exceeds the \
+instance allocation for this module requires 224 bytes which exceeds the \
 configured maximum of 16 bytes; breakdown of allocation requirement:
 
- * 76.19% - 256 bytes - instance state management
+ * 64.29% - 144 bytes - instance state management
+ * 7.14% - 16 bytes - jit store state
 ";
     match Module::new(&engine, "(module)") {
         Ok(_) => panic!("should have failed to compile"),
@@ -663,11 +664,11 @@ configured maximum of 16 bytes; breakdown of allocation requirement:
     lots_of_globals.push_str(")");
 
     let expected = "\
-instance allocation for this module requires 1936 bytes which exceeds the \
+instance allocation for this module requires 1824 bytes which exceeds the \
 configured maximum of 16 bytes; breakdown of allocation requirement:
 
- * 13.22% - 256 bytes - instance state management
- * 82.64% - 1600 bytes - defined globals
+ * 7.89% - 144 bytes - instance state management
+ * 87.72% - 1600 bytes - defined globals
 ";
     match Module::new(&engine, &lots_of_globals) {
         Ok(_) => panic!("should have failed to compile"),
diff --git a/tests/all/traps.rs b/tests/all/traps.rs
index f1776c875..0922f8442 100644
--- a/tests/all/traps.rs
+++ b/tests/all/traps.rs
@@ -1099,3 +1099,64 @@ async fn sync_then_async_trap() -> Result<()> {
 
     Ok(())
 }
+
+#[test]
+fn standalone_backtrace() -> Result<()> {
+    let engine = Engine::default();
+    let mut store = Store::new(&engine, ());
+    let trace = WasmBacktrace::capture(&store);
+    assert!(trace.frames().is_empty());
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (import "" "" (func $host))
+                (func $foo (export "f") call $bar)
+                (func $bar call $host)
+            )
+        "#,
+    )?;
+    let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
+        let trace = WasmBacktrace::capture(&cx);
+        assert_eq!(trace.frames().len(), 2);
+        let frame1 = &trace.frames()[0];
+        let frame2 = &trace.frames()[1];
+        assert_eq!(frame1.func_index(), 2);
+        assert_eq!(frame1.func_name(), Some("bar"));
+        assert_eq!(frame2.func_index(), 1);
+        assert_eq!(frame2.func_name(), Some("foo"));
+    });
+    let instance = Instance::new(&mut store, &module, &[func.into()])?;
+    let f = instance.get_typed_func::<(), ()>(&mut store, "f")?;
+    f.call(&mut store, ())?;
+    Ok(())
+}
+
+#[test]
+#[allow(deprecated)]
+fn standalone_backtrace_disabled() -> Result<()> {
+    let mut config = Config::new();
+    config.wasm_backtrace(false);
+    let engine = Engine::new(&config)?;
+    let mut store = Store::new(&engine, ());
+    let module = Module::new(
+        &engine,
+        r#"
+            (module
+                (import "" "" (func $host))
+                (func $foo (export "f") call $bar)
+                (func $bar call $host)
+            )
+        "#,
+    )?;
+    let func = Func::wrap(&mut store, |cx: Caller<'_, ()>| {
+        let trace = WasmBacktrace::capture(&cx);
+        assert_eq!(trace.frames().len(), 0);
+        let trace = WasmBacktrace::force_capture(&cx);
+        assert_eq!(trace.frames().len(), 2);
+    });
+    let instance = Instance::new(&mut store, &module, &[func.into()])?;
+    let f = instance.get_typed_func::<(), ()>(&mut store, "f")?;
+    f.call(&mut store, ())?;
+    Ok(())
+}