From bedbb50b7f65b3a960b023b23c38e4c1f4e83c4c Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Mon, 23 Feb 2026 20:31:28 -0800 Subject: [PATCH] Debugging: add unique-within-store IDs for every entity. When building an introspection API for use by a debugger, we need a way to expose *identity*: that is, to give some way of knowing that a given `Memory`, `Instance`, etc. is *this* one and not *that* one. Our handle types variously have either `Eq` implementations or e.g. `Module::same` for the ones that wrap an `Arc` under-the-covers; but that's not enough to allow a debugger to e.g. build a hashmap for whatever metadata that it might expose via whatever debugging protocol. For maximal generality and flexibility, we should expose the unique IDs that already more-or-less exist: for instances, that is their instance ID directly; for entities owned by instances, we can build a `u64` with the instance ID in the upper 32 bits and the defined-index in the lower 32 bits. IDs for all entities except modules are unique-within-a-Store (and this is all that is needed); IDs for modules happen to reuse the `CompiledModuleId` and so are unique-within-an-Engine. I've opted to name these `debug_index_within_store` to scope the feature and intended use-case clearly, but if there's a desire, I could easily rename them to simply `index`. I shied away from that here because I didn't want to give a notion that these indices are somehow canonical or correspond to some order or other. --- .../wasmtime/src/runtime/externals/global.rs | 18 +++ .../wasmtime/src/runtime/externals/table.rs | 9 ++ crates/wasmtime/src/runtime/externals/tag.rs | 9 ++ crates/wasmtime/src/runtime/instance.rs | 12 ++ crates/wasmtime/src/runtime/memory.rs | 9 ++ crates/wasmtime/src/runtime/module.rs | 9 ++ crates/wasmtime/src/runtime/vm/module_id.rs | 5 + tests/all/debug.rs | 118 ++++++++++++++++++ 8 files changed, 189 insertions(+) diff --git a/crates/wasmtime/src/runtime/externals/global.rs b/crates/wasmtime/src/runtime/externals/global.rs index 8927793fca33..e0a9991d8ad5 100644 --- a/crates/wasmtime/src/runtime/externals/global.rs +++ b/crates/wasmtime/src/runtime/externals/global.rs @@ -397,6 +397,24 @@ impl Global { store.id() == self.store } + /// Returns a stable identifier for this global within its store. + /// + /// This allows distinguishing globals when introspecting them + /// e.g. via debug APIs. + #[cfg(feature = "debug")] + pub fn debug_index_in_store(&self) -> u64 { + match self.kind { + VMGlobalKind::Instance(idx) => u64::from(self.instance) << 32 | u64::from(idx.as_u32()), + VMGlobalKind::Host(idx) => u64::from(u32::MAX) << 32 | u64::from(idx.as_u32()), + #[cfg(feature = "component-model")] + VMGlobalKind::ComponentFlags(idx) => { + u64::from(self.instance) << 32 | u64::from(idx.as_u32()) + } + #[cfg(feature = "component-model")] + VMGlobalKind::TaskMayBlock => u64::from(self.instance) << 32 | u64::from(u32::MAX), + } + } + /// Get a stable hash key for this global. /// /// Even if the same underlying global definition is added to the diff --git a/crates/wasmtime/src/runtime/externals/table.rs b/crates/wasmtime/src/runtime/externals/table.rs index 9e22df7f4ce6..18469b12f8a6 100644 --- a/crates/wasmtime/src/runtime/externals/table.rs +++ b/crates/wasmtime/src/runtime/externals/table.rs @@ -571,6 +571,15 @@ impl Table { store.id() == self.instance.store_id() } + /// Returns a stable identifier for this table within its store. + /// + /// This allows distinguishing tables when introspecting them + /// e.g. via debug APIs. + #[cfg(feature = "debug")] + pub fn debug_index_in_store(&self) -> u64 { + u64::from(self.instance.instance().as_u32()) << 32 | u64::from(self.index.as_u32()) + } + /// Get a stable hash key for this table. /// /// Even if the same underlying table definition is added to the diff --git a/crates/wasmtime/src/runtime/externals/tag.rs b/crates/wasmtime/src/runtime/externals/tag.rs index 11694fa71f0f..a89d372ee9fc 100644 --- a/crates/wasmtime/src/runtime/externals/tag.rs +++ b/crates/wasmtime/src/runtime/externals/tag.rs @@ -60,6 +60,15 @@ impl Tag { store.id() == self.instance.store_id() } + /// Returns a stable identifier for this tag within its store. + /// + /// This allows distinguishing tags when introspecting them + /// e.g. via debug APIs. + #[cfg(feature = "debug")] + pub fn debug_index_in_store(&self) -> u64 { + u64::from(self.instance.instance().as_u32()) << 32 | u64::from(self.index.as_u32()) + } + /// Determines whether this tag is reference equal to the other /// given tag in the given store. /// diff --git a/crates/wasmtime/src/runtime/instance.rs b/crates/wasmtime/src/runtime/instance.rs index da3051c6d0e2..d6074e83ad2f 100644 --- a/crates/wasmtime/src/runtime/instance.rs +++ b/crates/wasmtime/src/runtime/instance.rs @@ -611,6 +611,18 @@ impl Instance { self.id.instance() } + /// Return a unique-within-Store index for this `Instance`. + /// + /// Allows distinguishing instance identities when introspecting + /// the `Store`, e.g. via debug APIs. + /// + /// This index will match the instance's position in the sequence + /// returned by `Store::debug_all_instances()`. + #[cfg(feature = "debug")] + pub fn debug_index_in_store(&self) -> u32 { + self.id.instance().as_u32() + } + /// Get all globals within this instance. /// /// Returns both import and defined globals. diff --git a/crates/wasmtime/src/runtime/memory.rs b/crates/wasmtime/src/runtime/memory.rs index 1a93a583b6dd..7b027bddc6d8 100644 --- a/crates/wasmtime/src/runtime/memory.rs +++ b/crates/wasmtime/src/runtime/memory.rs @@ -671,6 +671,15 @@ impl Memory { store.id() == self.instance.store_id() } + /// Returns a stable identifier for this memory within its store. + /// + /// This allows distinguishing memories when introspecting them + /// e.g. via debug APIs. + #[cfg(feature = "debug")] + pub fn debug_index_in_store(&self) -> u64 { + u64::from(self.instance.instance().as_u32()) << 32 | u64::from(self.index.as_u32()) + } + /// Get a stable hash key for this memory. /// /// Even if the same underlying memory definition is added to the diff --git a/crates/wasmtime/src/runtime/module.rs b/crates/wasmtime/src/runtime/module.rs index 4ac767a17708..989c21c55263 100644 --- a/crates/wasmtime/src/runtime/module.rs +++ b/crates/wasmtime/src/runtime/module.rs @@ -1113,6 +1113,15 @@ impl Module { &self.inner.offsets } + /// Return the unique-within-Engine ID for this module. + /// + /// Allows distinguishing module identities when introspecting + /// modules, e.g. via debug APIs. + #[cfg(feature = "debug")] + pub fn debug_index_in_engine(&self) -> u64 { + self.id().as_u64() + } + /// Return the address, in memory, of the trampoline that allows Wasm to /// call a array function of the given signature. /// diff --git a/crates/wasmtime/src/runtime/vm/module_id.rs b/crates/wasmtime/src/runtime/vm/module_id.rs index be5bc513ecae..04f40e2201e8 100644 --- a/crates/wasmtime/src/runtime/vm/module_id.rs +++ b/crates/wasmtime/src/runtime/vm/module_id.rs @@ -16,4 +16,9 @@ impl CompiledModuleId { // uniqueness. CompiledModuleId(crate::store::StoreId::allocate().as_raw()) } + + /// Returns the inner unique integer contained in this ID. + pub fn as_u64(self) -> u64 { + self.0.get() + } } diff --git a/tests/all/debug.rs b/tests/all/debug.rs index f8ff5262fc69..ffcaf087e8cd 100644 --- a/tests/all/debug.rs +++ b/tests/all/debug.rs @@ -1200,3 +1200,121 @@ fn component_bytecode() -> wasmtime::Result<()> { Ok(()) } + +#[test] +#[cfg_attr(miri, ignore)] +fn debug_ids() -> wasmtime::Result<()> { + let mut config = Config::default(); + config.guest_debug(true); + config.wasm_exceptions(true); + let engine = Engine::new(&config)?; + let mut store = Store::new(&engine, ()); + let module1 = Module::new( + &engine, + r#" + (module + (memory 1 1) + (memory 1 1) + (global (mut i32) (i32.const 0)) + (global (mut i32) (i32.const 1)) + (table 1 1 funcref) + (table 1 1 funcref) + (tag (param i32)) + (tag (param i64))) + "#, + )?; + + let module2 = Module::new( + &engine, + r#" + (module + (memory (export "m") 1 1)) + "#, + )?; + + let instance1 = Instance::new(&mut store, &module1, &[])?; + let instance2 = Instance::new(&mut store, &module2, &[])?; + let instance3 = Instance::new(&mut store, &module1, &[])?; + + assert_ne!( + module1.debug_index_in_engine(), + module2.debug_index_in_engine() + ); + assert_ne!( + instance1.debug_index_in_store(), + instance2.debug_index_in_store() + ); + assert_ne!( + instance1 + .debug_memory(&mut store, 0) + .unwrap() + .debug_index_in_store(), + instance1 + .debug_memory(&mut store, 1) + .unwrap() + .debug_index_in_store() + ); + assert_ne!( + instance1 + .debug_memory(&mut store, 0) + .unwrap() + .debug_index_in_store(), + instance2 + .debug_memory(&mut store, 0) + .unwrap() + .debug_index_in_store() + ); + assert_ne!( + instance1 + .debug_memory(&mut store, 0) + .unwrap() + .debug_index_in_store(), + instance3 + .debug_memory(&mut store, 0) + .unwrap() + .debug_index_in_store() + ); + assert_ne!( + instance1 + .debug_global(&mut store, 0) + .unwrap() + .debug_index_in_store(), + instance3 + .debug_global(&mut store, 0) + .unwrap() + .debug_index_in_store() + ); + assert_ne!( + instance1 + .debug_table(&mut store, 0) + .unwrap() + .debug_index_in_store(), + instance3 + .debug_table(&mut store, 0) + .unwrap() + .debug_index_in_store() + ); + assert_ne!( + instance1 + .debug_tag(&mut store, 0) + .unwrap() + .debug_index_in_store(), + instance3 + .debug_tag(&mut store, 0) + .unwrap() + .debug_index_in_store() + ); + + let m_via_export = instance2 + .get_export(&mut store, "m") + .unwrap() + .into_memory() + .unwrap(); + let m_via_introspection = instance2.debug_memory(&mut store, 0).unwrap(); + assert_eq!( + m_via_export.debug_index_in_store(), + m_via_introspection.debug_index_in_store() + ); + + Ok(()) +}