From b45d89ba7140a95d0341488cef4be97360d2a9a9 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sat, 20 Apr 2024 21:26:32 +0200 Subject: [PATCH 01/11] add benchmarks for wasmi::Linker setup --- crates/wasmi/benches/benches.rs | 75 ++++++++++++++++++++++++++++++++- 1 file changed, 74 insertions(+), 1 deletion(-) diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index 65f075a653..fd4f8614a8 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -16,6 +16,7 @@ use wasmi::{ Engine, Extern, Func, + FuncType, Linker, Memory, Module, @@ -63,6 +64,16 @@ criterion_group!( bench_overhead_call_untyped_0, bench_overhead_call_untyped_16, ); +criterion_group!( + name = bench_linker; + config = Criterion::default() + .sample_size(10) + .measurement_time(Duration::from_millis(1000)) + .warm_up_time(Duration::from_millis(1000)); + targets = + bench_linker_setup_same, + bench_linker_setup_unique, +); criterion_group! { name = bench_execute; config = Criterion::default() @@ -96,7 +107,8 @@ criterion_main!( bench_translate, bench_instantiate, bench_execute, - bench_overhead + bench_overhead, + bench_linker, ); const WASM_KERNEL: &str = @@ -244,6 +256,67 @@ fn bench_instantiate_wasm_kernel(c: &mut Criterion) { }); } +fn bench_linker_setup_same(c: &mut Criterion) { + let len_funcs = 50; + let bench_id = format!("linker/setup/same/{len_funcs}"); + let func_names: Vec = (0..len_funcs).into_iter().map(|i| i.to_string()).collect(); + c.bench_function(&bench_id, |b| { + b.iter(|| { + let engine = Engine::default(); + let mut linker = >::new(&engine); + for func_name in &func_names { + linker.func_wrap("env", func_name, || ()).unwrap(); + } + }) + }); +} + +fn bench_linker_setup_unique(c: &mut Criterion) { + let len_funcs = 50; + let bench_id = format!("linker/setup/unique/{len_funcs}"); + let types = [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ValueType::FuncRef, + ValueType::ExternRef, + ]; + let funcs: Vec<(String, FuncType)> = (0..len_funcs) + .into_iter() + .map(|i| { + let func_name = i.to_string(); + let (len_params, len_results) = if i % 2 == 0 { + ((i / (types.len() * 2)) + 1, 0) + } else { + (0, (i / (types.len() * 2)) + 1) + }; + let chosen_type = types[i % 4]; + let func_type = FuncType::new( + vec![chosen_type; len_params], + vec![chosen_type; len_results], + ); + (func_name, func_type) + }) + .collect(); + c.bench_function(&bench_id, |b| { + b.iter(|| { + let engine = Engine::default(); + let mut linker = >::new(&engine); + for (func_name, func_type) in &funcs { + linker + .func_new( + "env", + func_name, + func_type.clone(), + move |_caller, _params, _results| Ok(()), + ) + .unwrap(); + } + }) + }); +} + #[allow(dead_code)] fn bench_instantiate_contract(c: &mut Criterion, name: &str, path: &str) { let bench_id = format!("instantiate/{name}"); From 4d34b25419f4634c300100fba0828eb1f9370c0f Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sat, 20 Apr 2024 22:06:32 +0200 Subject: [PATCH 02/11] move setup routines into bencher call --- crates/wasmi/benches/benches.rs | 52 ++++++++++++++++----------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index fd4f8614a8..0b1b9405ef 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -259,8 +259,8 @@ fn bench_instantiate_wasm_kernel(c: &mut Criterion) { fn bench_linker_setup_same(c: &mut Criterion) { let len_funcs = 50; let bench_id = format!("linker/setup/same/{len_funcs}"); - let func_names: Vec = (0..len_funcs).into_iter().map(|i| i.to_string()).collect(); c.bench_function(&bench_id, |b| { + let func_names: Vec = (0..len_funcs).into_iter().map(|i| i.to_string()).collect(); b.iter(|| { let engine = Engine::default(); let mut linker = >::new(&engine); @@ -274,32 +274,32 @@ fn bench_linker_setup_same(c: &mut Criterion) { fn bench_linker_setup_unique(c: &mut Criterion) { let len_funcs = 50; let bench_id = format!("linker/setup/unique/{len_funcs}"); - let types = [ - ValueType::I32, - ValueType::I64, - ValueType::F32, - ValueType::F64, - ValueType::FuncRef, - ValueType::ExternRef, - ]; - let funcs: Vec<(String, FuncType)> = (0..len_funcs) - .into_iter() - .map(|i| { - let func_name = i.to_string(); - let (len_params, len_results) = if i % 2 == 0 { - ((i / (types.len() * 2)) + 1, 0) - } else { - (0, (i / (types.len() * 2)) + 1) - }; - let chosen_type = types[i % 4]; - let func_type = FuncType::new( - vec![chosen_type; len_params], - vec![chosen_type; len_results], - ); - (func_name, func_type) - }) - .collect(); c.bench_function(&bench_id, |b| { + let types = [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ValueType::FuncRef, + ValueType::ExternRef, + ]; + let funcs: Vec<(String, FuncType)> = (0..len_funcs) + .into_iter() + .map(|i| { + let func_name = i.to_string(); + let (len_params, len_results) = if i % 2 == 0 { + ((i / (types.len() * 2)) + 1, 0) + } else { + (0, (i / (types.len() * 2)) + 1) + }; + let chosen_type = types[i % 4]; + let func_type = FuncType::new( + vec![chosen_type; len_params], + vec![chosen_type; len_results], + ); + (func_name, func_type) + }) + .collect(); b.iter(|| { let engine = Engine::default(); let mut linker = >::new(&engine); From 75b17069081f35c13f6a74a2ee6c8f1db4c21a2f Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sat, 20 Apr 2024 23:56:02 +0200 Subject: [PATCH 03/11] implement LenOrder and LenOrderStr string wrappers According to our benchmarks those string type wrappers and their custom Ord implementation improve Linker performance by roughly 50%. This is probably because the Rust compiler is able to inline the `memcmp` routines whereas with `Arc` and `str` it uses `memcmp` calls which are inefficient for small strings. --- crates/wasmi/src/linker.rs | 89 +++++++++++++++++++++++++++++++++++--- 1 file changed, 83 insertions(+), 6 deletions(-) diff --git a/crates/wasmi/src/linker.rs b/crates/wasmi/src/linker.rs index 7fbe9e954e..4dba97dfad 100644 --- a/crates/wasmi/src/linker.rs +++ b/crates/wasmi/src/linker.rs @@ -19,8 +19,10 @@ use crate::{ Value, }; use core::{ - fmt, - fmt::{Debug, Display}, + borrow::Borrow, + cmp::Ordering, + fmt::{self, Debug, Display}, + mem, num::NonZeroUsize, ops::Deref, }; @@ -245,12 +247,87 @@ impl Symbol { } } +/// An `Arc` that defines its own (more efficient) [`Ord`]. +#[derive(Debug, Clone, PartialEq, Eq)] +#[repr(transparent)] +pub struct LenOrder(Arc); + +impl Ord for LenOrder { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + self.as_str().cmp(other.as_str()) + } +} + +impl PartialOrd for LenOrder { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl LenOrder { + pub fn as_str(&self) -> &LenOrderStr { + (&*self.0).into() + } +} + +/// A `str` that defines its own (more efficient) [`Ord`]. +#[derive(Debug, Eq, PartialEq)] +#[repr(transparent)] +pub struct LenOrderStr(str); + +impl<'a> From<&'a str> for &'a LenOrderStr { + #[inline] + fn from(value: &'a str) -> Self { + // Safety: This operation is safe because + // + // - we preserve the lifetime `'a` + // - the `LenOrderStr` type is a `str` newtype wrapper and `#[repr(transparent)` + unsafe { mem::transmute(value) } + } +} + +impl Borrow for LenOrder { + #[inline] + fn borrow(&self) -> &LenOrderStr { + (&*self.0).into() + } +} + +impl PartialOrd for LenOrderStr { + #[inline] + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for LenOrderStr { + #[inline] + fn cmp(&self, other: &Self) -> Ordering { + let lhs = self.0.as_bytes(); + let rhs = other.0.as_bytes(); + match lhs.len().cmp(&rhs.len()) { + Ordering::Equal => { + for (l, r) in lhs.iter().zip(rhs) { + match l.cmp(r) { + Ordering::Equal => (), + ordering => return ordering, + } + } + Ordering::Equal + } + ordering => ordering, + } + } +} + /// A string interner. /// /// Efficiently interns strings and distributes symbols. #[derive(Debug, Default, Clone)] pub struct StringInterner { - string2idx: BTreeMap, Symbol>, + string2idx: BTreeMap, strings: Vec>, } @@ -262,12 +339,12 @@ impl StringInterner { /// Returns the symbol of the string and interns it if necessary. pub fn get_or_intern(&mut self, string: &str) -> Symbol { - match self.string2idx.get(string) { + match self.string2idx.get(<&LenOrderStr>::from(string)) { Some(symbol) => *symbol, None => { let symbol = self.next_symbol(); let rc_string: Arc = Arc::from(string); - self.string2idx.insert(rc_string.clone(), symbol); + self.string2idx.insert(LenOrder(rc_string.clone()), symbol); self.strings.push(rc_string); symbol } @@ -276,7 +353,7 @@ impl StringInterner { /// Returns the symbol for the string if interned. pub fn get(&self, string: &str) -> Option { - self.string2idx.get(string).copied() + self.string2idx.get(<&LenOrderStr>::from(string)).copied() } /// Resolves the symbol to the underlying string. From f099e3cac2b90fff4f914cc8477cfff2af4f1a5f Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 21 Apr 2024 11:40:48 +0200 Subject: [PATCH 04/11] apply clippy suggestions in benches --- crates/wasmi/benches/benches.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index 0b1b9405ef..a24e359539 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -260,7 +260,7 @@ fn bench_linker_setup_same(c: &mut Criterion) { let len_funcs = 50; let bench_id = format!("linker/setup/same/{len_funcs}"); c.bench_function(&bench_id, |b| { - let func_names: Vec = (0..len_funcs).into_iter().map(|i| i.to_string()).collect(); + let func_names: Vec = (0..len_funcs).map(|i| format!("{i}")).collect(); b.iter(|| { let engine = Engine::default(); let mut linker = >::new(&engine); @@ -284,9 +284,8 @@ fn bench_linker_setup_unique(c: &mut Criterion) { ValueType::ExternRef, ]; let funcs: Vec<(String, FuncType)> = (0..len_funcs) - .into_iter() .map(|i| { - let func_name = i.to_string(); + let func_name = format!("{i}"); let (len_params, len_results) = if i % 2 == 0 { ((i / (types.len() * 2)) + 1, 0) } else { From d0159b5f7d3130d6199c5fa16c3bbd68df1b3590 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 21 Apr 2024 11:41:08 +0200 Subject: [PATCH 05/11] only use inline-memcmp for short strings --- crates/wasmi/src/linker.rs | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/wasmi/src/linker.rs b/crates/wasmi/src/linker.rs index 4dba97dfad..f549a90400 100644 --- a/crates/wasmi/src/linker.rs +++ b/crates/wasmi/src/linker.rs @@ -309,13 +309,17 @@ impl Ord for LenOrderStr { let rhs = other.0.as_bytes(); match lhs.len().cmp(&rhs.len()) { Ordering::Equal => { - for (l, r) in lhs.iter().zip(rhs) { - match l.cmp(r) { - Ordering::Equal => (), - ordering => return ordering, + if lhs.len() < 8 { + for (l, r) in lhs.iter().zip(rhs) { + match l.cmp(r) { + Ordering::Equal => (), + ordering => return ordering, + } } + Ordering::Equal + } else { + lhs.cmp(rhs) } - Ordering::Equal } ordering => ordering, } From 2965e9f4d9c00494b5afe235a7e95e632400318b Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 21 Apr 2024 12:01:56 +0200 Subject: [PATCH 06/11] use NonZeroU32 instead of NonZeroUsize in Symbol --- crates/wasmi/src/linker.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/crates/wasmi/src/linker.rs b/crates/wasmi/src/linker.rs index f549a90400..16f38e5dc2 100644 --- a/crates/wasmi/src/linker.rs +++ b/crates/wasmi/src/linker.rs @@ -23,7 +23,7 @@ use core::{ cmp::Ordering, fmt::{self, Debug, Display}, mem, - num::NonZeroUsize, + num::NonZeroU32, ops::Deref, }; use std::{ @@ -222,12 +222,12 @@ impl Display for LinkerError { /// /// # Dev. Note /// -/// Internally we use [`NonZeroUsize`] so that `Option` can +/// Internally we use [`NonZeroU32`] so that `Option` can /// be space optimized easily by the compiler. This is important since /// in [`ImportKey`] we are making extensive use of `Option`. #[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] #[repr(transparent)] -pub struct Symbol(NonZeroUsize); +pub struct Symbol(NonZeroU32); impl Symbol { /// Creates a new symbol. @@ -236,14 +236,17 @@ impl Symbol { /// /// If the `value` is equal to `usize::MAX`. pub fn from_usize(value: usize) -> Self { - NonZeroUsize::new(value.wrapping_add(1)) + let Ok(value) = u32::try_from(value) else { + panic!("encountered invalid symvol value: {value}"); + }; + NonZeroU32::new(value.wrapping_add(1)) .map(Symbol) .expect("encountered invalid symbol value") } /// Returns the underlying `usize` value of the [`Symbol`]. pub fn into_usize(self) -> usize { - self.0.get().wrapping_sub(1) + self.0.get().wrapping_sub(1) as usize } } From 5be3b35d993dd1c1dc3471387fb3b90e5a83b0ee Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 21 Apr 2024 12:23:21 +0200 Subject: [PATCH 07/11] refactor ImportKey and Symbol The new implementation optimizes the Ord impl of ImportKey since ImportKeys are compared more often than their fields are queried during Linker setup. --- crates/wasmi/src/linker.rs | 83 +++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 28 deletions(-) diff --git a/crates/wasmi/src/linker.rs b/crates/wasmi/src/linker.rs index 16f38e5dc2..2eb2e27f31 100644 --- a/crates/wasmi/src/linker.rs +++ b/crates/wasmi/src/linker.rs @@ -23,7 +23,6 @@ use core::{ cmp::Ordering, fmt::{self, Debug, Display}, mem, - num::NonZeroU32, ops::Deref, }; use std::{ @@ -219,15 +218,9 @@ impl Display for LinkerError { /// Comparing symbols for equality is equal to comparing their respective /// interned strings for equality given that both symbol are coming from /// the same string interner instance. -/// -/// # Dev. Note -/// -/// Internally we use [`NonZeroU32`] so that `Option` can -/// be space optimized easily by the compiler. This is important since -/// in [`ImportKey`] we are making extensive use of `Option`. #[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] #[repr(transparent)] -pub struct Symbol(NonZeroU32); +pub struct Symbol(u32); impl Symbol { /// Creates a new symbol. @@ -235,18 +228,24 @@ impl Symbol { /// # Panics /// /// If the `value` is equal to `usize::MAX`. + #[inline] pub fn from_usize(value: usize) -> Self { let Ok(value) = u32::try_from(value) else { - panic!("encountered invalid symvol value: {value}"); + panic!("encountered invalid symbol value: {value}"); }; - NonZeroU32::new(value.wrapping_add(1)) - .map(Symbol) - .expect("encountered invalid symbol value") + Self(value) } /// Returns the underlying `usize` value of the [`Symbol`]. + #[inline] pub fn into_usize(self) -> usize { - self.0.get().wrapping_sub(1) as usize + self.0 as usize + } + + /// Returns the underlying `u32` value of the [`Symbol`]. + #[inline] + pub fn into_u32(self) -> u32 { + self.0 } } @@ -370,12 +369,43 @@ impl StringInterner { } /// Wasm import keys. -#[derive(Debug, Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +#[derive(Copy, Clone, PartialOrd, Ord, PartialEq, Eq)] +#[repr(transparent)] struct ImportKey { - /// The name of the module for the definition. - module: Symbol, - /// The name of the definition within the module scope. - name: Symbol, + /// Merged module and name symbols. + /// + /// Merging allows for a faster `Ord` implementation. + module_and_name: u64, +} + +impl Debug for ImportKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ImportKey") + .field("module", &self.module()) + .field("name", &self.name()) + .finish() + } +} + +impl ImportKey { + /// Creates a new [`ImportKey`] from the given `module` and `name` symbols. + #[inline] + pub fn new(module: Symbol, name: Symbol) -> Self { + let module_and_name = u64::from(module.into_u32()) << 32 | u64::from(name.into_u32()); + Self { module_and_name } + } + + /// Returns the `module` [`Symbol`] of the [`ImportKey`]. + #[inline] + pub fn module(&self) -> Symbol { + Symbol((self.module_and_name >> 32) as u32) + } + + /// Returns the `name` [`Symbol`] of the [`ImportKey`]. + #[inline] + pub fn name(&self) -> Symbol { + Symbol(self.module_and_name as u32) + } } /// A [`Linker`] definition. @@ -641,16 +671,16 @@ impl Linker { /// Returns the import key for the module name and item name. fn import_key(&mut self, module: &str, name: &str) -> ImportKey { - ImportKey { - module: self.strings.get_or_intern(module), - name: self.strings.get_or_intern(name), - } + ImportKey::new( + self.strings.get_or_intern(module), + self.strings.get_or_intern(name), + ) } /// Resolves the module and item name of the import key if any. fn resolve_import_key(&self, key: ImportKey) -> Option<(&str, &str)> { - let module_name = self.strings.resolve(key.module)?; - let item_name = self.strings.resolve(key.name)?; + let module_name = self.strings.resolve(key.module())?; + let item_name = self.strings.resolve(key.name())?; Some((module_name, item_name)) } @@ -712,10 +742,7 @@ impl Linker { context.as_context().store.engine(), self.engine() )); - let key = ImportKey { - module: self.strings.get(module)?, - name: self.strings.get(name)?, - }; + let key = ImportKey::new(self.strings.get(module)?, self.strings.get(name)?); self.definitions.get(&key) } From 21effcd90fae37aa66ab88a7598912bf79af839e Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 21 Apr 2024 12:52:10 +0200 Subject: [PATCH 08/11] use FuncType instead of DedupFuncType in HostFuncTrampolineEntity This allows us to create new HostFuncTrampolineEntities without an Engine and thus allow for a HostApi abstraction later to further optimize Linker setup. Also conveniently this allow us to get rid of tons of custom Debug implementations to resolve the no longer existing DedupFuncTypes. --- crates/wasmi/src/func/mod.rs | 28 +++++------ crates/wasmi/src/linker.rs | 90 ++++-------------------------------- 2 files changed, 24 insertions(+), 94 deletions(-) diff --git a/crates/wasmi/src/func/mod.rs b/crates/wasmi/src/func/mod.rs index 0eec774d48..2b7a931acd 100644 --- a/crates/wasmi/src/func/mod.rs +++ b/crates/wasmi/src/func/mod.rs @@ -22,7 +22,7 @@ use super::{ StoreContext, Stored, }; -use crate::{engine::ResumableCall, Engine, Error, Value}; +use crate::{engine::ResumableCall, Error, Value}; use core::{fmt, fmt::Debug, num::NonZeroU32}; use std::{boxed::Box, sync::Arc}; use wasmi_arena::ArenaIndex; @@ -175,7 +175,7 @@ impl WasmFuncEntity { /// A host function instance. pub struct HostFuncTrampolineEntity { /// The type of the associated host function. - ty: DedupFuncType, + ty: FuncType, /// The trampoline of the associated host function. trampoline: TrampolineEntity, } @@ -183,7 +183,7 @@ pub struct HostFuncTrampolineEntity { impl Clone for HostFuncTrampolineEntity { fn clone(&self) -> Self { Self { - ty: self.ty, + ty: self.ty.clone(), trampoline: self.trampoline.clone(), } } @@ -198,7 +198,7 @@ impl Debug for HostFuncTrampolineEntity { impl HostFuncTrampolineEntity { /// Creates a new host function trampoline from the given dynamically typed closure. pub fn new( - engine: &Engine, + // engine: &Engine, ty: FuncType, func: impl Fn(Caller<'_, T>, &[Value], &mut [Value]) -> Result<(), Error> + Send @@ -224,19 +224,19 @@ impl HostFuncTrampolineEntity { func(caller, params, results)?; Ok(func_results.encode_results_from_slice(results).unwrap()) }); - let ty = engine.alloc_func_type(ty.clone()); + // let ty = engine.alloc_func_type(ty.clone()); Self { ty, trampoline } } /// Creates a new host function trampoline from the given statically typed closure. - pub fn wrap(engine: &Engine, func: impl IntoFunc) -> Self { - let (signature, trampoline) = func.into_func(); - let ty = engine.alloc_func_type(signature); + pub fn wrap(func: impl IntoFunc) -> Self { + let (ty, trampoline) = func.into_func(); + // let ty = engine.alloc_func_type(signature); Self { ty, trampoline } } - /// Returns the signature of the host function. - pub fn ty_dedup(&self) -> &DedupFuncType { + /// Returns the [`FuncType`] of the host function. + pub fn func_type(&self) -> &FuncType { &self.ty } @@ -340,8 +340,8 @@ impl Func { + 'static, ) -> Self { let engine = ctx.as_context().store.engine(); - let host_func = HostFuncTrampolineEntity::new(engine, ty, func); - let ty_dedup = *host_func.ty_dedup(); + let host_func = HostFuncTrampolineEntity::new(ty, func); + let ty_dedup = engine.alloc_func_type(host_func.func_type().clone()); let trampoline = host_func.trampoline().clone(); let func = ctx.as_context_mut().store.alloc_trampoline(trampoline); ctx.as_context_mut() @@ -356,8 +356,8 @@ impl Func { func: impl IntoFunc, ) -> Self { let engine = ctx.as_context().store.engine(); - let host_func = HostFuncTrampolineEntity::wrap(engine, func); - let ty_dedup = *host_func.ty_dedup(); + let host_func = HostFuncTrampolineEntity::wrap(func); + let ty_dedup = engine.alloc_func_type(host_func.func_type().clone()); let trampoline = host_func.trampoline().clone(); let func = ctx.as_context_mut().store.alloc_trampoline(trampoline); ctx.as_context_mut() diff --git a/crates/wasmi/src/linker.rs b/crates/wasmi/src/linker.rs index 2eb2e27f31..adceec7210 100644 --- a/crates/wasmi/src/linker.rs +++ b/crates/wasmi/src/linker.rs @@ -409,6 +409,7 @@ impl ImportKey { } /// A [`Linker`] definition. +#[derive(Debug)] enum Definition { /// An external item from an [`Instance`](crate::Instance). Extern(Extern), @@ -440,14 +441,7 @@ impl Definition { pub fn ty(&self, ctx: impl AsContext) -> ExternType { match self { Definition::Extern(item) => item.ty(ctx), - Definition::HostFunc(host_func) => { - let func_type = ctx - .as_context() - .store - .engine() - .resolve_func_type(host_func.ty_dedup(), FuncType::clone); - ExternType::Func(func_type) - } + Definition::HostFunc(host_func) => ExternType::Func(host_func.func_type().clone()), } } @@ -469,8 +463,11 @@ impl Definition { .as_context_mut() .store .alloc_trampoline(host_func.trampoline().clone()); - let ty_dedup = host_func.ty_dedup(); - let entity = HostFuncEntity::new(*ty_dedup, trampoline); + let ty_dedup = ctx + .as_context() + .engine() + .alloc_func_type(host_func.func_type().clone()); + let entity = HostFuncEntity::new(ty_dedup, trampoline); let func = ctx .as_context_mut() .store @@ -483,66 +480,8 @@ impl Definition { } } -/// [`Debug`]-wrapper for the definitions of a [`Linker`]. -pub struct DebugDefinitions<'a, T> { - /// The [`Engine`] of the [`Linker`]. - engine: &'a Engine, - /// The definitions of the [`Linker`]. - definitions: &'a BTreeMap>, -} - -impl<'a, T> DebugDefinitions<'a, T> { - /// Create a new [`Debug`]-wrapper for the [`Linker`] definitions. - fn new(linker: &'a Linker) -> Self { - Self { - engine: linker.engine(), - definitions: &linker.definitions, - } - } -} - -impl<'a, T> Debug for DebugDefinitions<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut map = f.debug_map(); - for (name, definition) in self.definitions { - match definition { - Definition::Extern(definition) => { - map.entry(name, definition); - } - Definition::HostFunc(definition) => { - map.entry(name, &DebugHostFuncEntity::new(self.engine, definition)); - } - } - } - map.finish() - } -} - -/// [`Debug`]-wrapper for [`HostFuncTrampolineEntity`] in the [`Linker`]. -pub struct DebugHostFuncEntity<'a, T> { - /// The [`Engine`] of the [`Linker`]. - engine: &'a Engine, - /// The host function to be [`Debug`] formatted. - host_func: &'a HostFuncTrampolineEntity, -} - -impl<'a, T> DebugHostFuncEntity<'a, T> { - /// Create a new [`Debug`]-wrapper for the [`HostFuncTrampolineEntity`]. - fn new(engine: &'a Engine, host_func: &'a HostFuncTrampolineEntity) -> Self { - Self { engine, host_func } - } -} - -impl<'a, T> Debug for DebugHostFuncEntity<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.engine - .resolve_func_type(self.host_func.ty_dedup(), |func_type| { - f.debug_struct("HostFunc").field("ty", func_type).finish() - }) - } -} - /// A linker used to define module imports and instantiate module instances. +#[derive(Debug)] pub struct Linker { /// The underlying [`Engine`] for the [`Linker`]. /// @@ -557,15 +496,6 @@ pub struct Linker { definitions: BTreeMap>, } -impl Debug for Linker { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.debug_struct("Linker") - .field("strings", &self.strings) - .field("definitions", &DebugDefinitions::new(self)) - .finish() - } -} - impl Clone for Linker { fn clone(&self) -> Linker { Self { @@ -630,7 +560,7 @@ impl Linker { + Sync + 'static, ) -> Result<&mut Self, LinkerError> { - let func = HostFuncTrampolineEntity::new(&self.engine, ty, func); + let func = HostFuncTrampolineEntity::new(ty, func); let key = self.import_key(module, name); self.insert(key, Definition::HostFunc(func))?; Ok(self) @@ -663,7 +593,7 @@ impl Linker { name: &str, func: impl IntoFunc, ) -> Result<&mut Self, LinkerError> { - let func = HostFuncTrampolineEntity::wrap(&self.engine, func); + let func = HostFuncTrampolineEntity::wrap(func); let key = self.import_key(module, name); self.insert(key, Definition::HostFunc(func))?; Ok(self) From c61abc4c8196f0709fd596e56f504e2915d56549 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 21 Apr 2024 13:17:50 +0200 Subject: [PATCH 09/11] add LinkerBuilder abstraction and benchmarks --- crates/wasmi/benches/benches.rs | 124 ++++++++++++++++++++++++++++++ crates/wasmi/src/lib.rs | 2 +- crates/wasmi/src/linker.rs | 131 +++++++++++++++++++++++++++++++- 3 files changed, 255 insertions(+), 2 deletions(-) diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index a24e359539..9893d3d676 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -72,7 +72,11 @@ criterion_group!( .warm_up_time(Duration::from_millis(1000)); targets = bench_linker_setup_same, + bench_linker_build_finish_same, + bench_linker_build_construct_same, bench_linker_setup_unique, + bench_linker_build_finish_unique, + bench_linker_build_construct_unique, ); criterion_group! { name = bench_execute; @@ -256,6 +260,36 @@ fn bench_instantiate_wasm_kernel(c: &mut Criterion) { }); } +fn bench_linker_build_finish_same(c: &mut Criterion) { + let len_funcs = 50; + let bench_id = format!("linker/build/finish/same/{len_funcs}"); + c.bench_function(&bench_id, |b| { + let func_names: Vec = (0..len_funcs).map(|i| format!("{i}")).collect(); + let mut builder = >::build(); + for func_name in &func_names { + builder.func_wrap("env", func_name, || ()).unwrap(); + } + b.iter(|| { + let engine = Engine::default(); + _ = builder.finish(&engine); + }) + }); +} + +fn bench_linker_build_construct_same(c: &mut Criterion) { + let len_funcs = 50; + let bench_id = format!("linker/build/construct/same/{len_funcs}"); + c.bench_function(&bench_id, |b| { + let func_names: Vec = (0..len_funcs).map(|i| format!("{i}")).collect(); + b.iter(|| { + let mut builder = >::build(); + for func_name in &func_names { + builder.func_wrap("env", func_name, || ()).unwrap(); + } + }) + }); +} + fn bench_linker_setup_same(c: &mut Criterion) { let len_funcs = 50; let bench_id = format!("linker/setup/same/{len_funcs}"); @@ -316,6 +350,96 @@ fn bench_linker_setup_unique(c: &mut Criterion) { }); } +fn bench_linker_build_finish_unique(c: &mut Criterion) { + let len_funcs = 50; + let bench_id = format!("linker/build/finish/unique/{len_funcs}"); + c.bench_function(&bench_id, |b| { + let types = [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ValueType::FuncRef, + ValueType::ExternRef, + ]; + let funcs: Vec<(String, FuncType)> = (0..len_funcs) + .map(|i| { + let func_name = format!("{i}"); + let (len_params, len_results) = if i % 2 == 0 { + ((i / (types.len() * 2)) + 1, 0) + } else { + (0, (i / (types.len() * 2)) + 1) + }; + let chosen_type = types[i % 4]; + let func_type = FuncType::new( + vec![chosen_type; len_params], + vec![chosen_type; len_results], + ); + (func_name, func_type) + }) + .collect(); + let mut builder = >::build(); + for (func_name, func_type) in &funcs { + builder + .func_new( + "env", + func_name, + func_type.clone(), + move |_caller, _params, _results| Ok(()), + ) + .unwrap(); + } + b.iter(|| { + let engine = Engine::default(); + _ = builder.finish(&engine); + }) + }); +} + +fn bench_linker_build_construct_unique(c: &mut Criterion) { + let len_funcs = 50; + let bench_id = format!("linker/build/construct/unique/{len_funcs}"); + c.bench_function(&bench_id, |b| { + let types = [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ValueType::FuncRef, + ValueType::ExternRef, + ]; + let funcs: Vec<(String, FuncType)> = (0..len_funcs) + .map(|i| { + let func_name = format!("{i}"); + let (len_params, len_results) = if i % 2 == 0 { + ((i / (types.len() * 2)) + 1, 0) + } else { + (0, (i / (types.len() * 2)) + 1) + }; + let chosen_type = types[i % 4]; + let func_type = FuncType::new( + vec![chosen_type; len_params], + vec![chosen_type; len_results], + ); + (func_name, func_type) + }) + .collect(); + b.iter(|| { + let mut builder = >::build(); + for (func_name, func_type) in &funcs { + builder + .func_new( + "env", + func_name, + func_type.clone(), + move |_caller, _params, _results| Ok(()), + ) + .unwrap(); + } + }) + }); +} + #[allow(dead_code)] fn bench_instantiate_contract(c: &mut Criterion, name: &str, path: &str) { let bench_id = format!("instantiate/{name}"); diff --git a/crates/wasmi/src/lib.rs b/crates/wasmi/src/lib.rs index 75395fd1a9..2a3feec928 100644 --- a/crates/wasmi/src/lib.rs +++ b/crates/wasmi/src/lib.rs @@ -151,7 +151,7 @@ pub use self::{ global::{Global, GlobalType, Mutability}, instance::{Export, ExportsIter, Extern, ExternType, Instance}, limits::{ResourceLimiter, StoreLimits, StoreLimitsBuilder}, - linker::Linker, + linker::{Linker, LinkerBuilder}, memory::{Memory, MemoryType}, module::{ ExportType, diff --git a/crates/wasmi/src/linker.rs b/crates/wasmi/src/linker.rs index adceec7210..5f3217fda5 100644 --- a/crates/wasmi/src/linker.rs +++ b/crates/wasmi/src/linker.rs @@ -513,7 +513,7 @@ impl Default for Linker { } impl Linker { - /// Creates a new linker. + /// Creates a new [`Linker`]. pub fn new(engine: &Engine) -> Self { Self { engine: engine.clone(), @@ -522,6 +522,14 @@ impl Linker { } } + /// Creates a new [`LinkerBuilder`] to construct a [`Linker`]. + pub fn build() -> LinkerBuilder { + LinkerBuilder { + strings: StringInterner::default(), + definitions: BTreeMap::default(), + } + } + /// Returns the underlying [`Engine`] of the [`Linker`]. pub fn engine(&self) -> &Engine { &self.engine @@ -893,3 +901,124 @@ mod tests { assert_eq!(wasm_get_b.call(&mut store, ()).unwrap(), 200); } } + +/// A linker used to define module imports and instantiate module instances. +#[derive(Debug)] +pub struct LinkerBuilder { + /// Allows to efficiently store strings and deduplicate them.. + strings: StringInterner, + /// Stores the definitions given their names. + definitions: BTreeMap>, +} + +impl Clone for LinkerBuilder { + fn clone(&self) -> Self { + Self { + strings: self.strings.clone(), + definitions: self.definitions.clone(), + } + } +} + +impl LinkerBuilder { + /// Finishes construction of the [`Linker`] by attaching an [`Engine`]. + pub fn finish(&self, engine: &Engine) -> Linker { + Linker { + engine: engine.clone(), + strings: self.strings.clone(), + definitions: self.definitions.clone(), + } + } + + /// Returns the import key for the module name and item name. + fn import_key(&mut self, module: &str, name: &str) -> ImportKey { + ImportKey::new( + self.strings.get_or_intern(module), + self.strings.get_or_intern(name), + ) + } + + /// Resolves the module and item name of the import key if any. + fn resolve_import_key(&self, key: ImportKey) -> Option<(&str, &str)> { + let module_name = self.strings.resolve(key.module())?; + let item_name = self.strings.resolve(key.name())?; + Some((module_name, item_name)) + } + + /// Inserts the extern item under the import key. + /// + /// # Errors + /// + /// If there already is a definition for the import key for this [`Linker`]. + fn insert(&mut self, key: ImportKey, item: Definition) -> Result<(), LinkerError> { + match self.definitions.entry(key) { + Entry::Occupied(_) => { + let (module_name, field_name) = self + .resolve_import_key(key) + .unwrap_or_else(|| panic!("encountered missing import names for key {key:?}")); + let import_name = ImportName::new(module_name, field_name); + return Err(LinkerError::DuplicateDefinition { import_name }); + } + Entry::Vacant(v) => { + v.insert(item); + } + } + Ok(()) + } + + /// Creates a new named [`Func::new`]-style host [`Func`] for this [`Linker`]. + /// + /// For more information see [`Linker::func_wrap`]. + /// + /// # Errors + /// + /// If there already is a definition under the same name for this [`Linker`]. + pub fn func_new( + &mut self, + module: &str, + name: &str, + ty: FuncType, + func: impl Fn(Caller<'_, T>, &[Value], &mut [Value]) -> Result<(), Error> + + Send + + Sync + + 'static, + ) -> Result<&mut Self, LinkerError> { + let func = HostFuncTrampolineEntity::new(ty, func); + let key = self.import_key(module, name); + self.insert(key, Definition::HostFunc(func))?; + Ok(self) + } + + /// Creates a new named [`Func::new`]-style host [`Func`] for this [`Linker`]. + /// + /// For information how to use this API see [`Func::wrap`]. + /// + /// This method creates a host function for this [`Linker`] under the given name. + /// It is distinct in its ability to create a [`Store`] independent + /// host function. Host functions defined this way can be used to instantiate + /// instances in multiple different [`Store`] entities. + /// + /// The same applies to other [`Linker`] methods to define new [`Func`] instances + /// such as [`Linker::func_new`]. + /// + /// In a concurrently running program, this means that these host functions + /// could be called concurrently if different [`Store`] entities are executing on + /// different threads. + /// + /// # Errors + /// + /// If there already is a definition under the same name for this [`Linker`]. + /// + /// [`Store`]: crate::Store + pub fn func_wrap( + &mut self, + module: &str, + name: &str, + func: impl IntoFunc, + ) -> Result<&mut Self, LinkerError> { + let func = HostFuncTrampolineEntity::wrap(func); + let key = self.import_key(module, name); + self.insert(key, Definition::HostFunc(func))?; + Ok(self) + } +} From 45f0ed9fb8d5459d20e2f186b024471520b61ba2 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 21 Apr 2024 15:12:48 +0200 Subject: [PATCH 10/11] remove code duplication in benchmarks --- crates/wasmi/benches/benches.rs | 103 ++++++++++---------------------- 1 file changed, 31 insertions(+), 72 deletions(-) diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index 9893d3d676..c16c964b5b 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -305,34 +305,39 @@ fn bench_linker_setup_same(c: &mut Criterion) { }); } +/// Generates `count` host functions with different signatures. +fn generate_unique_host_functions(count: usize) -> Vec<(String, FuncType)> { + let types = [ + ValueType::I32, + ValueType::I64, + ValueType::F32, + ValueType::F64, + ValueType::FuncRef, + ValueType::ExternRef, + ]; + (0..count) + .map(|i| { + let func_name = format!("{i}"); + let (len_params, len_results) = if i % 2 == 0 { + ((i / (types.len() * 2)) + 1, 0) + } else { + (0, (i / (types.len() * 2)) + 1) + }; + let chosen_type = types[i % 4]; + let func_type = FuncType::new( + vec![chosen_type; len_params], + vec![chosen_type; len_results], + ); + (func_name, func_type) + }) + .collect() +} + fn bench_linker_setup_unique(c: &mut Criterion) { let len_funcs = 50; let bench_id = format!("linker/setup/unique/{len_funcs}"); c.bench_function(&bench_id, |b| { - let types = [ - ValueType::I32, - ValueType::I64, - ValueType::F32, - ValueType::F64, - ValueType::FuncRef, - ValueType::ExternRef, - ]; - let funcs: Vec<(String, FuncType)> = (0..len_funcs) - .map(|i| { - let func_name = format!("{i}"); - let (len_params, len_results) = if i % 2 == 0 { - ((i / (types.len() * 2)) + 1, 0) - } else { - (0, (i / (types.len() * 2)) + 1) - }; - let chosen_type = types[i % 4]; - let func_type = FuncType::new( - vec![chosen_type; len_params], - vec![chosen_type; len_results], - ); - (func_name, func_type) - }) - .collect(); + let funcs = generate_unique_host_functions(len_funcs); b.iter(|| { let engine = Engine::default(); let mut linker = >::new(&engine); @@ -354,30 +359,7 @@ fn bench_linker_build_finish_unique(c: &mut Criterion) { let len_funcs = 50; let bench_id = format!("linker/build/finish/unique/{len_funcs}"); c.bench_function(&bench_id, |b| { - let types = [ - ValueType::I32, - ValueType::I64, - ValueType::F32, - ValueType::F64, - ValueType::FuncRef, - ValueType::ExternRef, - ]; - let funcs: Vec<(String, FuncType)> = (0..len_funcs) - .map(|i| { - let func_name = format!("{i}"); - let (len_params, len_results) = if i % 2 == 0 { - ((i / (types.len() * 2)) + 1, 0) - } else { - (0, (i / (types.len() * 2)) + 1) - }; - let chosen_type = types[i % 4]; - let func_type = FuncType::new( - vec![chosen_type; len_params], - vec![chosen_type; len_results], - ); - (func_name, func_type) - }) - .collect(); + let funcs = generate_unique_host_functions(len_funcs); let mut builder = >::build(); for (func_name, func_type) in &funcs { builder @@ -400,30 +382,7 @@ fn bench_linker_build_construct_unique(c: &mut Criterion) { let len_funcs = 50; let bench_id = format!("linker/build/construct/unique/{len_funcs}"); c.bench_function(&bench_id, |b| { - let types = [ - ValueType::I32, - ValueType::I64, - ValueType::F32, - ValueType::F64, - ValueType::FuncRef, - ValueType::ExternRef, - ]; - let funcs: Vec<(String, FuncType)> = (0..len_funcs) - .map(|i| { - let func_name = format!("{i}"); - let (len_params, len_results) = if i % 2 == 0 { - ((i / (types.len() * 2)) + 1, 0) - } else { - (0, (i / (types.len() * 2)) + 1) - }; - let chosen_type = types[i % 4]; - let func_type = FuncType::new( - vec![chosen_type; len_params], - vec![chosen_type; len_results], - ); - (func_name, func_type) - }) - .collect(); + let funcs = generate_unique_host_functions(len_funcs); b.iter(|| { let mut builder = >::build(); for (func_name, func_type) in &funcs { From 92aea8d28781b490b97303a2016419d042caa3fc Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Sun, 21 Apr 2024 15:40:14 +0200 Subject: [PATCH 11/11] resolve code duplication in linker.rs --- crates/wasmi/src/linker.rs | 377 +++++++++++++++++++++---------------- 1 file changed, 212 insertions(+), 165 deletions(-) diff --git a/crates/wasmi/src/linker.rs b/crates/wasmi/src/linker.rs index 5f3217fda5..a106765850 100644 --- a/crates/wasmi/src/linker.rs +++ b/crates/wasmi/src/linker.rs @@ -490,18 +490,15 @@ pub struct Linker { /// Primarily required to define [`Linker`] owned host functions // using [`Linker::func_wrap`] and [`Linker::func_new`]. TODO: implement methods engine: Engine, - /// Allows to efficiently store strings and deduplicate them.. - strings: StringInterner, - /// Stores the definitions given their names. - definitions: BTreeMap>, + /// Inner linker implementation details. + inner: LinkerInner, } impl Clone for Linker { fn clone(&self) -> Linker { Self { engine: self.engine.clone(), - strings: self.strings.clone(), - definitions: self.definitions.clone(), + inner: self.inner.clone(), } } } @@ -517,16 +514,14 @@ impl Linker { pub fn new(engine: &Engine) -> Self { Self { engine: engine.clone(), - strings: StringInterner::default(), - definitions: BTreeMap::default(), + inner: LinkerInner::default(), } } /// Creates a new [`LinkerBuilder`] to construct a [`Linker`]. pub fn build() -> LinkerBuilder { LinkerBuilder { - strings: StringInterner::default(), - definitions: BTreeMap::default(), + inner: LinkerInner::default(), } } @@ -546,8 +541,8 @@ impl Linker { name: &str, item: impl Into, ) -> Result<&mut Self, LinkerError> { - let key = self.import_key(module, name); - self.insert(key, Definition::Extern(item.into()))?; + let key = self.inner.import_key(module, name); + self.inner.insert(key, Definition::Extern(item.into()))?; Ok(self) } @@ -569,8 +564,8 @@ impl Linker { + 'static, ) -> Result<&mut Self, LinkerError> { let func = HostFuncTrampolineEntity::new(ty, func); - let key = self.import_key(module, name); - self.insert(key, Definition::HostFunc(func))?; + let key = self.inner.import_key(module, name); + self.inner.insert(key, Definition::HostFunc(func))?; Ok(self) } @@ -602,47 +597,11 @@ impl Linker { func: impl IntoFunc, ) -> Result<&mut Self, LinkerError> { let func = HostFuncTrampolineEntity::wrap(func); - let key = self.import_key(module, name); - self.insert(key, Definition::HostFunc(func))?; + let key = self.inner.import_key(module, name); + self.inner.insert(key, Definition::HostFunc(func))?; Ok(self) } - /// Returns the import key for the module name and item name. - fn import_key(&mut self, module: &str, name: &str) -> ImportKey { - ImportKey::new( - self.strings.get_or_intern(module), - self.strings.get_or_intern(name), - ) - } - - /// Resolves the module and item name of the import key if any. - fn resolve_import_key(&self, key: ImportKey) -> Option<(&str, &str)> { - let module_name = self.strings.resolve(key.module())?; - let item_name = self.strings.resolve(key.name())?; - Some((module_name, item_name)) - } - - /// Inserts the extern item under the import key. - /// - /// # Errors - /// - /// If there already is a definition for the import key for this [`Linker`]. - fn insert(&mut self, key: ImportKey, item: Definition) -> Result<(), LinkerError> { - match self.definitions.entry(key) { - Entry::Occupied(_) => { - let (module_name, field_name) = self - .resolve_import_key(key) - .unwrap_or_else(|| panic!("encountered missing import names for key {key:?}")); - let import_name = ImportName::new(module_name, field_name); - return Err(LinkerError::DuplicateDefinition { import_name }); - } - Entry::Vacant(v) => { - v.insert(item); - } - } - Ok(()) - } - /// Looks up a defined [`Extern`] by name in this [`Linker`]. /// /// - Returns `None` if this name was not previously defined in this [`Linker`]. @@ -680,8 +639,7 @@ impl Linker { context.as_context().store.engine(), self.engine() )); - let key = ImportKey::new(self.strings.get(module)?, self.strings.get(name)?); - self.definitions.get(&key) + self.inner.get_definition(module, name) } /// Instantiates the given [`Module`] using the definitions in the [`Linker`]. @@ -794,142 +752,111 @@ impl Linker { } } -#[cfg(test)] -mod tests { - use wasmi_core::ValueType; - - use super::*; - use crate::Store; +/// A linker used to define module imports and instantiate module instances. +#[derive(Debug)] +pub struct LinkerBuilder { + /// Internal linker implementation details. + inner: LinkerInner, +} - struct HostState { - a: i32, - b: i64, +impl Clone for LinkerBuilder { + fn clone(&self) -> Self { + Self { + inner: self.inner.clone(), + } } +} - #[test] - fn linker_funcs_work() { - let engine = Engine::default(); - let mut linker = >::new(&engine); - linker - .func_new( - "host", - "get_a", - FuncType::new([], [ValueType::I32]), - |ctx: Caller, _params: &[Value], results: &mut [Value]| { - results[0] = Value::from(ctx.data().a); - Ok(()) - }, - ) - .unwrap(); - linker - .func_new( - "host", - "set_a", - FuncType::new([ValueType::I32], []), - |mut ctx: Caller, params: &[Value], _results: &mut [Value]| { - ctx.data_mut().a = params[0].i32().unwrap(); - Ok(()) - }, - ) - .unwrap(); - linker - .func_wrap("host", "get_b", |ctx: Caller| ctx.data().b) - .unwrap(); - linker - .func_wrap("host", "set_b", |mut ctx: Caller, value: i64| { - ctx.data_mut().b = value - }) - .unwrap(); - let a_init = 42; - let b_init = 77; - let mut store = >::new( - &engine, - HostState { - a: a_init, - b: b_init, - }, - ); - let wat = r#" - (module - (import "host" "get_a" (func $host_get_a (result i32))) - (import "host" "set_a" (func $host_set_a (param i32))) - (import "host" "get_b" (func $host_get_b (result i64))) - (import "host" "set_b" (func $host_set_b (param i64))) - - (func (export "wasm_get_a") (result i32) - (call $host_get_a) - ) - (func (export "wasm_set_a") (param $param i32) - (call $host_set_a (local.get $param)) - ) - - (func (export "wasm_get_b") (result i64) - (call $host_get_b) - ) - (func (export "wasm_set_b") (param $param i64) - (call $host_set_b (local.get $param)) - ) - ) - "#; - let wasm = wat::parse_str(wat).unwrap(); - let module = Module::new(&engine, &mut &wasm[..]).unwrap(); - let instance = linker - .instantiate(&mut store, &module) - .unwrap() - .start(&mut store) - .unwrap(); - - let wasm_get_a = instance - .get_typed_func::<(), i32>(&store, "wasm_get_a") - .unwrap(); - let wasm_set_a = instance - .get_typed_func::(&store, "wasm_set_a") - .unwrap(); - let wasm_get_b = instance - .get_typed_func::<(), i64>(&store, "wasm_get_b") - .unwrap(); - let wasm_set_b = instance - .get_typed_func::(&store, "wasm_set_b") - .unwrap(); +impl LinkerBuilder { + /// Finishes construction of the [`Linker`] by attaching an [`Engine`]. + pub fn finish(&self, engine: &Engine) -> Linker { + Linker { + engine: engine.clone(), + inner: self.inner.clone(), + } + } - assert_eq!(wasm_get_a.call(&mut store, ()).unwrap(), a_init); - wasm_set_a.call(&mut store, 100).unwrap(); - assert_eq!(wasm_get_a.call(&mut store, ()).unwrap(), 100); + /// Creates a new named [`Func::new`]-style host [`Func`] for this [`Linker`]. + /// + /// For more information see [`Linker::func_wrap`]. + /// + /// # Errors + /// + /// If there already is a definition under the same name for this [`Linker`]. + pub fn func_new( + &mut self, + module: &str, + name: &str, + ty: FuncType, + func: impl Fn(Caller<'_, T>, &[Value], &mut [Value]) -> Result<(), Error> + + Send + + Sync + + 'static, + ) -> Result<&mut Self, LinkerError> { + self.inner.func_new(module, name, ty, func)?; + Ok(self) + } - assert_eq!(wasm_get_b.call(&mut store, ()).unwrap(), b_init); - wasm_set_b.call(&mut store, 200).unwrap(); - assert_eq!(wasm_get_b.call(&mut store, ()).unwrap(), 200); + /// Creates a new named [`Func::new`]-style host [`Func`] for this [`Linker`]. + /// + /// For information how to use this API see [`Func::wrap`]. + /// + /// This method creates a host function for this [`Linker`] under the given name. + /// It is distinct in its ability to create a [`Store`] independent + /// host function. Host functions defined this way can be used to instantiate + /// instances in multiple different [`Store`] entities. + /// + /// The same applies to other [`Linker`] methods to define new [`Func`] instances + /// such as [`Linker::func_new`]. + /// + /// In a concurrently running program, this means that these host functions + /// could be called concurrently if different [`Store`] entities are executing on + /// different threads. + /// + /// # Errors + /// + /// If there already is a definition under the same name for this [`Linker`]. + /// + /// [`Store`]: crate::Store + pub fn func_wrap( + &mut self, + module: &str, + name: &str, + func: impl IntoFunc, + ) -> Result<&mut Self, LinkerError> { + self.inner.func_wrap(module, name, func)?; + Ok(self) } } -/// A linker used to define module imports and instantiate module instances. +/// Internal [`Linker`] implementation. #[derive(Debug)] -pub struct LinkerBuilder { +pub struct LinkerInner { /// Allows to efficiently store strings and deduplicate them.. strings: StringInterner, /// Stores the definitions given their names. definitions: BTreeMap>, } -impl Clone for LinkerBuilder { - fn clone(&self) -> Self { +impl Default for LinkerInner { + fn default() -> Self { Self { - strings: self.strings.clone(), - definitions: self.definitions.clone(), + strings: StringInterner::default(), + definitions: BTreeMap::default(), } } } -impl LinkerBuilder { - /// Finishes construction of the [`Linker`] by attaching an [`Engine`]. - pub fn finish(&self, engine: &Engine) -> Linker { - Linker { - engine: engine.clone(), +impl Clone for LinkerInner { + fn clone(&self) -> Self { + Self { strings: self.strings.clone(), definitions: self.definitions.clone(), } } +} +impl LinkerInner { /// Returns the import key for the module name and item name. fn import_key(&mut self, module: &str, name: &str) -> ImportKey { ImportKey::new( @@ -1021,4 +948,124 @@ impl LinkerBuilder { self.insert(key, Definition::HostFunc(func))?; Ok(self) } + + /// Looks up a [`Definition`] by name in this [`Linker`]. + /// + /// Returns `None` if this name was not previously defined in this [`Linker`]. + /// + /// # Panics + /// + /// If the [`Engine`] of this [`Linker`] and the [`Engine`] of `context` are not the same. + fn get_definition(&self, module: &str, name: &str) -> Option<&Definition> { + let key = ImportKey::new(self.strings.get(module)?, self.strings.get(name)?); + self.definitions.get(&key) + } +} + +#[cfg(test)] +mod tests { + use wasmi_core::ValueType; + + use super::*; + use crate::Store; + + struct HostState { + a: i32, + b: i64, + } + + #[test] + fn linker_funcs_work() { + let engine = Engine::default(); + let mut linker = >::new(&engine); + linker + .func_new( + "host", + "get_a", + FuncType::new([], [ValueType::I32]), + |ctx: Caller, _params: &[Value], results: &mut [Value]| { + results[0] = Value::from(ctx.data().a); + Ok(()) + }, + ) + .unwrap(); + linker + .func_new( + "host", + "set_a", + FuncType::new([ValueType::I32], []), + |mut ctx: Caller, params: &[Value], _results: &mut [Value]| { + ctx.data_mut().a = params[0].i32().unwrap(); + Ok(()) + }, + ) + .unwrap(); + linker + .func_wrap("host", "get_b", |ctx: Caller| ctx.data().b) + .unwrap(); + linker + .func_wrap("host", "set_b", |mut ctx: Caller, value: i64| { + ctx.data_mut().b = value + }) + .unwrap(); + let a_init = 42; + let b_init = 77; + let mut store = >::new( + &engine, + HostState { + a: a_init, + b: b_init, + }, + ); + let wat = r#" + (module + (import "host" "get_a" (func $host_get_a (result i32))) + (import "host" "set_a" (func $host_set_a (param i32))) + (import "host" "get_b" (func $host_get_b (result i64))) + (import "host" "set_b" (func $host_set_b (param i64))) + + (func (export "wasm_get_a") (result i32) + (call $host_get_a) + ) + (func (export "wasm_set_a") (param $param i32) + (call $host_set_a (local.get $param)) + ) + + (func (export "wasm_get_b") (result i64) + (call $host_get_b) + ) + (func (export "wasm_set_b") (param $param i64) + (call $host_set_b (local.get $param)) + ) + ) + "#; + let wasm = wat::parse_str(wat).unwrap(); + let module = Module::new(&engine, &mut &wasm[..]).unwrap(); + let instance = linker + .instantiate(&mut store, &module) + .unwrap() + .start(&mut store) + .unwrap(); + + let wasm_get_a = instance + .get_typed_func::<(), i32>(&store, "wasm_get_a") + .unwrap(); + let wasm_set_a = instance + .get_typed_func::(&store, "wasm_set_a") + .unwrap(); + let wasm_get_b = instance + .get_typed_func::<(), i64>(&store, "wasm_get_b") + .unwrap(); + let wasm_set_b = instance + .get_typed_func::(&store, "wasm_set_b") + .unwrap(); + + assert_eq!(wasm_get_a.call(&mut store, ()).unwrap(), a_init); + wasm_set_a.call(&mut store, 100).unwrap(); + assert_eq!(wasm_get_a.call(&mut store, ()).unwrap(), 100); + + assert_eq!(wasm_get_b.call(&mut store, ()).unwrap(), b_init); + wasm_set_b.call(&mut store, 200).unwrap(); + assert_eq!(wasm_get_b.call(&mut store, ()).unwrap(), 200); + } }