From 90c9bec225d3d8feaf406e404069ebaa13175ef3 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 13 Mar 2023 14:39:30 -0700 Subject: [PATCH] wasmtime: Option to return default values for unknown imports (#6010) Similar to the `--trap-unknown-imports` option, which defines unknown function imports with functions that trap when called, this new `--default-values-unknown-imports` option defines unknown function imports with a function that returns the default values for the result types (either zero or null depending on the value type). --- crates/wasmtime/src/linker.rs | 58 ++++++++++++++++++++++++++++++++++- src/commands/run.rs | 11 +++++++ tests/all/linker.rs | 34 ++++++++++++++++++++ 3 files changed, 102 insertions(+), 1 deletion(-) diff --git a/crates/wasmtime/src/linker.rs b/crates/wasmtime/src/linker.rs index 0cda89000d..70cc04a39e 100644 --- a/crates/wasmtime/src/linker.rs +++ b/crates/wasmtime/src/linker.rs @@ -3,7 +3,7 @@ use crate::instance::InstancePre; use crate::store::StoreOpaque; use crate::{ AsContext, AsContextMut, Caller, Engine, Extern, ExternType, Func, FuncType, ImportType, - Instance, IntoFunc, Module, StoreContextMut, Val, ValRaw, + Instance, IntoFunc, Module, StoreContextMut, Val, ValRaw, ValType, }; use anyhow::{bail, Context, Result}; use log::warn; @@ -289,6 +289,62 @@ impl Linker { Ok(()) } + /// Implement any function imports of the [`Module`] with a function that + /// ignores its arguments and returns default values. + /// + /// Default values are either zero or null, depending on the value type. + /// + /// This method can be used to allow unknown imports from command modules. + /// + /// # Example + /// + /// ``` + /// # use wasmtime::*; + /// # fn main() -> anyhow::Result<()> { + /// # let engine = Engine::default(); + /// # let module = Module::new(&engine, "(module (import \"unknown\" \"import\" (func)))")?; + /// # let mut store = Store::new(&engine, ()); + /// let mut linker = Linker::new(&engine); + /// linker.define_unknown_imports_as_default_values(&module)?; + /// linker.instantiate(&mut store, &module)?; + /// # Ok(()) + /// # } + /// ``` + #[cfg(compiler)] + #[cfg_attr(nightlydoc, doc(cfg(feature = "cranelift")))] // see build.rs + pub fn define_unknown_imports_as_default_values( + &mut self, + module: &Module, + ) -> anyhow::Result<()> { + for import in module.imports() { + if let Err(import_err) = self._get_by_import(&import) { + if let ExternType::Func(func_ty) = import_err.ty() { + let result_tys: Vec<_> = func_ty.results().collect(); + self.func_new( + import.module(), + import.name(), + func_ty, + move |_caller, _args, results| { + for (result, ty) in results.iter_mut().zip(&result_tys) { + *result = match ty { + ValType::I32 => Val::I32(0), + ValType::I64 => Val::I64(0), + ValType::F32 => Val::F32(0.0_f32.to_bits()), + ValType::F64 => Val::F64(0.0_f64.to_bits()), + ValType::V128 => Val::V128(0), + ValType::FuncRef => Val::FuncRef(None), + ValType::ExternRef => Val::ExternRef(None), + }; + } + Ok(()) + }, + )?; + } + } + } + Ok(()) + } + /// Defines a new item in this [`Linker`]. /// /// This method will add a new definition, by name, to this instance of diff --git a/src/commands/run.rs b/src/commands/run.rs index cea3ef7410..9fef95d04c 100644 --- a/src/commands/run.rs +++ b/src/commands/run.rs @@ -90,6 +90,11 @@ pub struct RunCommand { #[clap(long = "trap-unknown-imports")] trap_unknown_imports: bool, + /// Allow the main module to import unknown functions, using an + /// implementation that returns default values, when running commands. + #[clap(long = "default-values-unknown-imports")] + default_values_unknown_imports: bool, + /// Allow executing precompiled WebAssembly modules as `*.cwasm` files. /// /// Note that this option is not safe to pass if the module being passed in @@ -322,6 +327,12 @@ impl RunCommand { if self.trap_unknown_imports { linker.define_unknown_imports_as_traps(&module)?; } + + // ...or as default values. + if self.default_values_unknown_imports { + linker.define_unknown_imports_as_default_values(&module)?; + } + // Use "" as a default module name. linker .module(&mut *store, "", &module) diff --git a/tests/all/linker.rs b/tests/all/linker.rs index 4048f6894e..7fe580b4c3 100644 --- a/tests/all/linker.rs +++ b/tests/all/linker.rs @@ -390,3 +390,37 @@ fn test_trapping_unknown_import() -> Result<()> { Ok(()) } + +#[test] +fn test_default_value_unknown_import() -> Result<()> { + const WAT: &str = r#" + (module + (import "unknown" "func" (func $unknown_func (result i64 f32 externref))) + (func (export "run") (result i64 f32 externref) + call $unknown_func + ) + ) + "#; + + let mut store = Store::<()>::default(); + let module = Module::new(store.engine(), WAT).expect("failed to create module"); + let mut linker = Linker::new(store.engine()); + + linker.define_unknown_imports_as_default_values(&module)?; + let instance = linker.instantiate(&mut store, &module)?; + + // "run" calls an import function which will not be defined, so it should + // return default values. + let run_func = instance + .get_func(&mut store, "run") + .expect("expected a run func in the module"); + + let mut results = vec![Val::I32(1), Val::I32(2), Val::I32(3)]; + run_func.call(&mut store, &[], &mut results)?; + + assert_eq!(results[0].i64(), Some(0)); + assert_eq!(results[1].f32(), Some(0.0)); + assert!(results[2].externref().unwrap().is_none()); + + Ok(()) +}