diff --git a/crates/wasmtime/src/runtime/func.rs b/crates/wasmtime/src/runtime/func.rs index b9520fdfb522..efacd63c146b 100644 --- a/crates/wasmtime/src/runtime/func.rs +++ b/crates/wasmtime/src/runtime/func.rs @@ -3,7 +3,7 @@ use crate::prelude::*; use crate::runtime::vm::{ self, InterpreterRef, SendSyncPtr, StoreBox, VMArrayCallHostFuncContext, VMCommonStackInformation, VMContext, VMFuncRef, VMFunctionImport, VMOpaqueContext, - VMStoreContext, + VMStoreContext, VmPtr, }; use crate::store::{Asyncness, AutoAssertNoGc, InstanceId, StoreId, StoreOpaque}; use crate::type_registry::RegisteredType; @@ -1206,6 +1206,55 @@ impl Func { Ok(()) } + /// Returns whether `a` and `b` refer to the same underlying function + /// within `store`, regardless of how each was reached (e.g. one fetched + /// directly and the other passed as an import to another instance and + /// re-exported from there). + /// + /// # Panics + /// + /// Panics if `a` or `b` are not owned by `store`. + pub fn is_same(store: impl AsContext, a: &Func, b: &Func) -> bool { + let store = store.as_context().0; + a.identity_key_raw(store) == b.identity_key_raw(store) + } + + /// Returns a key that uniquely identifies the underlying function this + /// `Func` refers to within `store`, suitable for use as a `HashMap` key. + /// + /// Two `Func`s produce equal keys if and only if [`Func::is_same`] would + /// return `true` for them. + /// + /// # Panics + /// + /// Panics if this `Func` is not owned by `store`. + pub fn identity_key(&self, store: impl AsContext) -> impl core::hash::Hash + Eq { + self.identity_key_raw(store.as_context().0) + } + + fn identity_key_raw( + &self, + store: &StoreOpaque, + ) -> ( + VmPtr, + core::num::NonZero, + VMSharedTypeIndex, + ) { + // SAFETY: `vm_func_ref` validates that this pointer belongs to + // `store`, which we're borrowing for the duration of this call, so + // dereferencing it here is sound. + let func_ref = unsafe { self.vm_func_ref(store).as_ref() }; + // `vmctx` disambiguates statically-same functions belonging to + // different instances of the same module; `array_call` is always + // present and unique per function; `type_index` is extra insurance + // that we disambiguate by Wasm-level signature too. + ( + func_ref.vmctx, + func_ref.array_call.addr(), + func_ref.type_index, + ) + } + #[inline] pub(crate) fn vm_func_ref(&self, store: &StoreOpaque) -> NonNull { self.store.assert_belongs_to(store.id()); diff --git a/tests/all/func.rs b/tests/all/func.rs index 4c94c67e646a..ee29e50350e3 100644 --- a/tests/all/func.rs +++ b/tests/all/func.rs @@ -759,6 +759,95 @@ fn import_works() -> Result<()> { Ok(()) } +#[test] +fn func_is_same_and_identity_key() -> Result<()> { + use std::collections::hash_map::DefaultHasher; + use std::hash::{Hash, Hasher}; + + fn hash_of(k: impl Hash) -> u64 { + let mut hasher = DefaultHasher::new(); + k.hash(&mut hasher); + hasher.finish() + } + + let mut store = Store::<()>::default(); + let module = Module::new( + store.engine(), + r#" + (module + (table (export "t") 1 1 funcref) + (func (export "f") (result i32) i32.const 0) + (elem (i32.const 0) 0)) + "#, + )?; + let instance = Instance::new(&mut store, &module, &[])?; + + // Fetched by name and by name again: same function. + let f1 = instance.get_func(&mut store, "f").unwrap(); + let f2 = instance.get_func(&mut store, "f").unwrap(); + assert!(Func::is_same(&store, &f1, &f2)); + assert_eq!( + hash_of(f1.identity_key(&store)), + hash_of(f2.identity_key(&store)) + ); + + // Fetched via `exports()`: same function. + let f3 = instance + .exports(&mut store) + .find_map(|e| e.into_func()) + .unwrap(); + assert!(Func::is_same(&store, &f1, &f3)); + + // Fetched through a table: same function. + let t = instance.get_table(&mut store, "t").unwrap(); + let f4 = *t.get(&mut store, 0).unwrap().unwrap_func().unwrap(); + assert!(Func::is_same(&store, &f1, &f4)); + + // Passed as an import to another instance and re-exported: still the + // same function, even though the two `Func`s wrap distinct pointers. + let importer = Module::new( + store.engine(), + r#" + (module + (import "" "f" (func $g (result i32))) + (export "reexported" (func $g))) + "#, + )?; + let importer_instance = Instance::new(&mut store, &importer, &[f1.into()])?; + let f5 = importer_instance + .get_func(&mut store, "reexported") + .unwrap(); + assert!(Func::is_same(&store, &f1, &f5)); + assert_eq!( + hash_of(f1.identity_key(&store)), + hash_of(f5.identity_key(&store)) + ); + + // A different function is not the same. + let other = Func::wrap(&mut store, || {}); + assert!(!Func::is_same(&store, &f1, &other)); + + // Two `Func`s wrapping the same closure *type* (so they share a + // monomorphized trampoline) are still not the same: only their + // individual `vmctx`s (heap-allocated per `wrap` call) distinguish them. + let c = || {}; + let host1 = Func::wrap(&mut store, c); + let host2 = Func::wrap(&mut store, c); + assert!(!Func::is_same(&store, &host1, &host2)); + + // A host function's `identity_key` is stable even though Wasmtime fills + // in its Wasm-calling-convention trampoline lazily, the first time it's + // paired with a module that has one -- so `identity_key` can't rely on + // that trampoline either. + let key_before = hash_of(host1.identity_key(&store)); + let importer_of_host1 = Module::new(store.engine(), r#"(module (import "" "" (func)))"#)?; + Instance::new(&mut store, &importer_of_host1, &[host1.into()])?; + let key_after = hash_of(host1.identity_key(&store)); + assert_eq!(key_before, key_after); + + Ok(()) +} + #[test] #[cfg_attr(miri, ignore)] fn trap_smoke() -> Result<()> {