Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion crates/wasmtime/src/runtime/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<VMOpaqueContext>,
core::num::NonZero<usize>,
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<VMFuncRef> {
self.store.assert_belongs_to(store.id());
Expand Down
89 changes: 89 additions & 0 deletions tests/all/func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down
Loading