Skip to content

Commit fe40cfa

Browse files
committed
Implement defining host functions at the Config level.
This commit introduces defining host functions at the `Config` rather than with `Func` tied to a `Store`. The intention here is to enable a host to define all of the functions once with a `Config` and then use a `Linker` (or directly with `Store::get_host_func`) to use the functions when instantiating a module. This should help improve the performance of use cases where a `Store` is short-lived and redefining the functions at every module instantiation is a noticeable performance hit. This commit adds `add_to_config` to the code generation for Wasmtime's `Wasi` type. The new method adds the WASI functions to the given config as host functions. This commit adds context functions to `Store`: `get` to get a context of a particular type and `set` to set the context on the store. For safety, `set` cannot replace an existing context value of the same type. `Wasi::set_context` was added to set the WASI context for a `Store` when using `Wasi::add_to_config`.
1 parent f8cc824 commit fe40cfa

22 files changed

Lines changed: 1620 additions & 456 deletions

File tree

crates/c-api/src/func.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ pub struct wasm_func_t {
1717
wasmtime_c_api_macros::declare_ref!(wasm_func_t);
1818

1919
#[repr(C)]
20-
pub struct wasmtime_caller_t<'a> {
21-
caller: Caller<'a>,
20+
pub struct wasmtime_caller_t {
21+
caller: Caller,
2222
}
2323

2424
pub type wasm_func_callback_t = extern "C" fn(
@@ -85,7 +85,7 @@ impl From<Func> for wasm_func_t {
8585
fn create_function(
8686
store: &wasm_store_t,
8787
ty: &wasm_functype_t,
88-
func: impl Fn(Caller<'_>, *const wasm_val_vec_t, *mut wasm_val_vec_t) -> Option<Box<wasm_trap_t>>
88+
func: impl Fn(Caller, *const wasm_val_vec_t, *mut wasm_val_vec_t) -> Option<Box<wasm_trap_t>>
8989
+ 'static,
9090
) -> Box<wasm_func_t> {
9191
let store = &store.store;

crates/runtime/src/instance/allocator.rs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -581,9 +581,6 @@ unsafe impl InstanceAllocator for OnDemandInstanceAllocator {
581581
&self,
582582
mut req: InstanceAllocationRequest,
583583
) -> Result<InstanceHandle, InstantiationError> {
584-
debug_assert!(!req.externref_activations_table.is_null());
585-
debug_assert!(!req.stack_map_registry.is_null());
586-
587584
let memories = self.create_memories(&req.module)?;
588585
let tables = Self::create_tables(&req.module);
589586

crates/runtime/src/traphandlers.rs

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//! WebAssembly trap handling, which is built on top of the lower-level
22
//! signalhandling mechanisms.
33
4-
use crate::VMContext;
4+
use crate::VMInterrupts;
55
use backtrace::Backtrace;
66
use std::any::Any;
77
use std::cell::Cell;
@@ -445,19 +445,15 @@ impl Trap {
445445
/// returning them as a `Result`.
446446
///
447447
/// Highly unsafe since `closure` won't have any dtors run.
448-
pub unsafe fn catch_traps<F>(
449-
vmctx: *mut VMContext,
450-
trap_info: &impl TrapInfo,
451-
mut closure: F,
452-
) -> Result<(), Trap>
448+
pub unsafe fn catch_traps<F>(trap_info: &impl TrapInfo, mut closure: F) -> Result<(), Trap>
453449
where
454450
F: FnMut(),
455451
{
456452
// Ensure that we have our sigaltstack installed.
457453
#[cfg(unix)]
458454
setup_unix_sigaltstack()?;
459455

460-
return CallThreadState::new(vmctx, trap_info).with(|cx| {
456+
return CallThreadState::new(trap_info).with(|cx| {
461457
RegisterSetjmp(
462458
cx.jmp_buf.as_ptr(),
463459
call_closure::<F>,
@@ -493,7 +489,6 @@ pub fn out_of_gas() {
493489
pub struct CallThreadState<'a> {
494490
unwind: Cell<UnwindReason>,
495491
jmp_buf: Cell<*const u8>,
496-
vmctx: *mut VMContext,
497492
handling_trap: Cell<bool>,
498493
trap_info: &'a (dyn TrapInfo + 'a),
499494
}
@@ -526,6 +521,9 @@ pub unsafe trait TrapInfo {
526521
///
527522
/// This function may return, and it may also `raise_lib_trap`.
528523
fn out_of_gas(&self);
524+
525+
/// Returns the VM interrupts to use for interrupting Wasm code.
526+
fn interrupts(&self) -> &VMInterrupts;
529527
}
530528

531529
enum UnwindReason {
@@ -537,10 +535,9 @@ enum UnwindReason {
537535
}
538536

539537
impl<'a> CallThreadState<'a> {
540-
fn new(vmctx: *mut VMContext, trap_info: &'a (dyn TrapInfo + 'a)) -> CallThreadState<'a> {
538+
fn new(trap_info: &'a (dyn TrapInfo + 'a)) -> CallThreadState<'a> {
541539
CallThreadState {
542540
unwind: Cell::new(UnwindReason::None),
543-
vmctx,
544541
jmp_buf: Cell::new(ptr::null()),
545542
handling_trap: Cell::new(false),
546543
trap_info,
@@ -562,10 +559,9 @@ impl<'a> CallThreadState<'a> {
562559
UnwindReason::LibTrap(trap) => Err(trap),
563560
UnwindReason::JitTrap { backtrace, pc } => {
564561
debug_assert_eq!(ret, 0);
565-
let maybe_interrupted = unsafe {
566-
let interrupts = (*self.vmctx).instance().interrupts();
567-
(**interrupts).stack_limit.load(SeqCst) == wasmtime_environ::INTERRUPTED
568-
};
562+
let interrupts = self.trap_info.interrupts();
563+
let maybe_interrupted =
564+
interrupts.stack_limit.load(SeqCst) == wasmtime_environ::INTERRUPTED;
569565
Err(Trap::Jit {
570566
pc,
571567
backtrace,
@@ -622,7 +618,7 @@ impl<'a> CallThreadState<'a> {
622618
// (a million bytes) the slop shouldn't matter too much.
623619
let wasm_stack_limit = psm::stack_pointer() as usize - self.trap_info.max_wasm_stack();
624620

625-
let interrupts = unsafe { &**(&*self.vmctx).instance().interrupts() };
621+
let interrupts = self.trap_info.interrupts();
626622
let reset_stack_limit = match interrupts.stack_limit.compare_exchange(
627623
usize::max_value(),
628624
wasm_stack_limit,

crates/wasi/src/lib.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
use std::cell::RefCell;
1111
use std::rc::Rc;
1212
pub use wasi_common::{Error, WasiCtx, WasiCtxBuilder, WasiDir, WasiFile};
13-
use wasmtime::{Linker, Store};
13+
use wasmtime::{Config, Linker, Store};
1414

1515
/// An instantiated instance of all available wasi exports. Presently includes
1616
/// both the "preview1" snapshot and the "unstable" (preview0) snapshot.
@@ -34,6 +34,18 @@ impl Wasi {
3434
self.preview_0.add_to_linker(linker)?;
3535
Ok(())
3636
}
37+
pub fn add_to_config(config: &mut Config) {
38+
snapshots::preview_1::Wasi::add_to_config(config);
39+
snapshots::preview_0::Wasi::add_to_config(config);
40+
}
41+
pub fn set_context(store: &Store, context: WasiCtx) -> Result<(), WasiCtx> {
42+
store.set(Rc::new(RefCell::new(context))).map_err(|ctx| {
43+
match std::rc::Rc::try_unwrap(ctx) {
44+
Ok(ctx) => ctx.into_inner(),
45+
Err(_) => unreachable!(),
46+
}
47+
})
48+
}
3749
}
3850

3951
pub mod snapshots {

crates/wasmtime/src/config.rs

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,15 @@
11
use crate::memory::MemoryCreator;
22
use crate::trampoline::MemoryCreatorProxy;
3+
use crate::{func::HostFunc, Caller, FuncType, IntoFunc, Trap, Val, WasmRet, WasmTy};
34
use anyhow::{bail, Result};
45
use std::cmp;
6+
use std::collections::HashMap;
57
use std::convert::TryFrom;
68
use std::fmt;
9+
use std::future::Future;
710
#[cfg(feature = "cache")]
811
use std::path::Path;
12+
use std::pin::Pin;
913
use std::sync::Arc;
1014
use wasmparser::WasmFeatures;
1115
#[cfg(feature = "cache")]
@@ -266,6 +270,88 @@ impl Default for InstanceAllocationStrategy {
266270
}
267271
}
268272

273+
/// This type is used for storing host functions in a `Config`.
274+
///
275+
/// The module and function names are interned for more compact storage.
276+
#[derive(Clone)]
277+
struct HostFuncMap {
278+
index_map: HashMap<Arc<str>, usize>,
279+
strings: Vec<Arc<str>>,
280+
funcs: HashMap<(usize, usize), Arc<HostFunc>>,
281+
}
282+
283+
impl HostFuncMap {
284+
fn new() -> Self {
285+
Self {
286+
index_map: HashMap::new(),
287+
strings: Vec::new(),
288+
funcs: HashMap::new(),
289+
}
290+
}
291+
292+
fn insert(&mut self, module: &str, name: &str, func: HostFunc) {
293+
let key = (self.intern_str(module), self.intern_str(name));
294+
self.funcs.insert(key, Arc::new(func));
295+
}
296+
297+
fn get(&self, module: &str, name: &str) -> Option<&HostFunc> {
298+
let key = (
299+
self.index_map.get(module).cloned()?,
300+
self.index_map.get(name).cloned()?,
301+
);
302+
self.funcs.get(&key).map(AsRef::as_ref)
303+
}
304+
305+
fn intern_str(&mut self, string: &str) -> usize {
306+
if let Some(idx) = self.index_map.get(string) {
307+
return *idx;
308+
}
309+
let string: Arc<str> = string.into();
310+
let idx = self.strings.len();
311+
self.strings.push(string.clone());
312+
self.index_map.insert(string, idx);
313+
idx
314+
}
315+
}
316+
317+
macro_rules! generate_wrap_async_host_func {
318+
($num:tt $($args:ident)*) => (paste::paste!{
319+
/// Same as [`Config::wrap_host_func`], except the closure asynchronously produces
320+
/// its result. For more information see the [`Func`](crate::Func) documentation.
321+
///
322+
/// # Panics
323+
///
324+
/// A panic will occur if the store associated with the instance that calls this host
325+
/// function is not asynchronous (see [`Store::new_async`](crate::Store::new_async)).
326+
#[allow(non_snake_case)]
327+
#[cfg(feature = "async")]
328+
#[cfg_attr(nightlydoc, doc(cfg(feature = "async")))]
329+
pub fn [<wrap_host_func $num _async>]<$($args,)* R>(
330+
&mut self,
331+
module: &str,
332+
name: &str,
333+
func: impl Fn(Caller, $($args),*) -> Box<dyn Future<Output = R>> + Send + Sync + 'static,
334+
)
335+
where
336+
$($args: WasmTy,)*
337+
R: WasmRet,
338+
{
339+
self.host_funcs.insert(
340+
module,
341+
name,
342+
HostFunc::wrap(&self.default_instance_allocator, move |caller: Caller, $($args: $args),*| {
343+
let store = caller.store().clone();
344+
let mut future = Pin::from(func(caller, $($args),*));
345+
match store.block_on(future.as_mut()) {
346+
Ok(ret) => ret.into_result(),
347+
Err(e) => Err(e),
348+
}
349+
})
350+
);
351+
}
352+
})
353+
}
354+
269355
/// Global configuration options used to create an [`Engine`](crate::Engine)
270356
/// and customize its behavior.
271357
///
@@ -292,6 +378,7 @@ pub struct Config {
292378
pub(crate) max_memories: usize,
293379
#[cfg(feature = "async")]
294380
pub(crate) async_stack_size: usize,
381+
host_funcs: HostFuncMap,
295382
}
296383

297384
impl Config {
@@ -344,6 +431,7 @@ impl Config {
344431
max_memories: 10_000,
345432
#[cfg(feature = "async")]
346433
async_stack_size: 2 << 20,
434+
host_funcs: HostFuncMap::new(),
347435
};
348436
ret.wasm_backtrace_details(WasmBacktraceDetails::Environment);
349437
return ret;
@@ -1062,6 +1150,59 @@ impl Config {
10621150
self
10631151
}
10641152

1153+
/// Defines a host function for the [`Config`] for the given callback.
1154+
///
1155+
/// Use [`Store::get_host_func`](crate::Store::get_host_func) to get a [`Func`](crate::Func) representing the function.
1156+
///
1157+
/// Note that the implementation of `func` must adhere to the `ty`
1158+
/// signature given, error or traps may occur if it does not respect the
1159+
/// `ty` signature.
1160+
///
1161+
/// Additionally note that this is quite a dynamic function since signatures
1162+
/// are not statically known. For performance reasons, it's recommended
1163+
/// to use [`Config::wrap_host_func`] if you can because with statically known
1164+
/// signatures the engine can optimize the implementation much more.
1165+
///
1166+
/// The callback must be `Send` and `Sync` as it is shared between all engines created
1167+
/// from the `Config`. For more relaxed bounds, use [`Func::new`](crate::Func::new) to define the function.
1168+
pub fn define_host_func(
1169+
&mut self,
1170+
module: &str,
1171+
name: &str,
1172+
ty: FuncType,
1173+
func: impl Fn(Caller, &[Val], &mut [Val]) -> Result<(), Trap> + Send + Sync + 'static,
1174+
) {
1175+
self.host_funcs
1176+
.insert(module, name, HostFunc::new(self, ty, func));
1177+
}
1178+
1179+
/// Defines a host function for the [`Config`] from the given Rust closure.
1180+
///
1181+
/// Use [`Store::get_host_func`](crate::Store::get_host_func) to get a [`Func`](crate::Func) representing the function.
1182+
///
1183+
/// See [`Func::wrap`](crate::Func::wrap) for information about accepted parameter and result types for the closure.
1184+
///
1185+
/// The closure must be `Send` and `Sync` as it is shared between all engines created
1186+
/// from the `Config`. For more relaxed bounds, use [`Func::wrap`](crate::Func::wrap) to wrap the closure.
1187+
pub fn wrap_host_func<Params, Results>(
1188+
&mut self,
1189+
module: &str,
1190+
name: &str,
1191+
func: impl IntoFunc<Params, Results> + Send + Sync,
1192+
) {
1193+
self.host_funcs.insert(
1194+
module,
1195+
name,
1196+
HostFunc::wrap(&self.default_instance_allocator, func),
1197+
);
1198+
}
1199+
1200+
for_each_function_signature!(generate_wrap_async_host_func);
1201+
1202+
pub(crate) fn get_host_func(&self, module: &str, name: &str) -> Option<&HostFunc> {
1203+
self.host_funcs.get(module, name)
1204+
}
1205+
10651206
pub(crate) fn target_isa(&self) -> Box<dyn TargetIsa> {
10661207
self.isa_flags
10671208
.clone()

0 commit comments

Comments
 (0)