Files
wasmtime/crates/fuzzing/src/oracles/dummy.rs
Peter Huene 54c07d8f16 Implement shared host functions. (#2625)
* 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`.

* Add `Config::define_host_func_async`.

* Make config "async" rather than store.

This commit moves the concept of "async-ness" to `Config` rather than `Store`.

Note: this is a breaking API change for anyone that's already adopted the new
async support in Wasmtime.

Now `Config::new_async` is used to create an "async" config and any `Store`
associated with that config is inherently "async".

This is needed for async shared host functions to have some sanity check during their
execution (async host functions, like "async" `Func`, need to be called with
the "async" variants).

* Update async function tests to smoke async shared host functions.

This commit updates the async function tests to also smoke the shared host
functions, plus `Func::wrap0_async`.

This also changes the "wrap async" method names on `Config` to
`wrap$N_host_func_async` to slightly better match what is on `Func`.

* Move the instance allocator into `Engine`.

This commit moves the instantiated instance allocator from `Config` into
`Engine`.

This makes certain settings in `Config` no longer order-dependent, which is how
`Config` should ideally be.

This also removes the confusing concept of the "default" instance allocator,
instead opting to construct the on-demand instance allocator when needed.

This does alter the semantics of the instance allocator as now each `Engine`
gets its own instance allocator rather than sharing a single one between all
engines created from a configuration.

* Make `Engine::new` return `Result`.

This is a breaking API change for anyone using `Engine::new`.

As creating the pooling instance allocator may fail (likely cause is not enough
memory for the provided limits), instead of panicking when creating an
`Engine`, `Engine::new` now returns a `Result`.

* Remove `Config::new_async`.

This commit removes `Config::new_async` in favor of treating "async support" as
any other setting on `Config`.

The setting is `Config::async_support`.

* Remove order dependency when defining async host functions in `Config`.

This commit removes the order dependency where async support must be enabled on
the `Config` prior to defining async host functions.

The check is now delayed to when an `Engine` is created from the config.

* Update WASI example to use shared `Wasi::add_to_config`.

This commit updates the WASI example to use `Wasi::add_to_config`.

As only a single store and instance are used in the example, it has no semantic
difference from the previous example, but the intention is to steer users
towards defining WASI on the config and only using `Wasi::add_to_linker` when
more explicit scoping of the WASI context is required.
2021-03-11 10:14:03 -06:00

581 lines
20 KiB
Rust

//! Dummy implementations of things that a Wasm module can import.
use std::fmt::Write;
use wasmtime::*;
/// Create a set of dummy functions/globals/etc for the given imports.
pub fn dummy_imports<'module>(
store: &Store,
import_tys: impl Iterator<Item = ImportType<'module>>,
) -> Vec<Extern> {
import_tys
.map(|imp| match imp.ty() {
ExternType::Func(func_ty) => Extern::Func(dummy_func(&store, func_ty)),
ExternType::Global(global_ty) => Extern::Global(dummy_global(&store, global_ty)),
ExternType::Table(table_ty) => Extern::Table(dummy_table(&store, table_ty)),
ExternType::Memory(mem_ty) => Extern::Memory(dummy_memory(&store, mem_ty)),
ExternType::Instance(instance_ty) => {
Extern::Instance(dummy_instance(&store, instance_ty))
}
ExternType::Module(module_ty) => Extern::Module(dummy_module(&store, module_ty)),
})
.collect()
}
/// Construct a dummy function for the given function type
pub fn dummy_func(store: &Store, ty: FuncType) -> Func {
Func::new(store, ty.clone(), move |_, _, results| {
for (ret_ty, result) in ty.results().zip(results) {
*result = dummy_value(ret_ty);
}
Ok(())
})
}
/// Construct a dummy value for the given value type.
pub fn dummy_value(val_ty: ValType) -> Val {
match val_ty {
ValType::I32 => Val::I32(0),
ValType::I64 => Val::I64(0),
ValType::F32 => Val::F32(0),
ValType::F64 => Val::F64(0),
ValType::V128 => Val::V128(0),
ValType::ExternRef => Val::ExternRef(None),
ValType::FuncRef => Val::FuncRef(None),
}
}
/// Construct a sequence of dummy values for the given types.
pub fn dummy_values(val_tys: impl IntoIterator<Item = ValType>) -> Vec<Val> {
val_tys.into_iter().map(dummy_value).collect()
}
/// Construct a dummy global for the given global type.
pub fn dummy_global(store: &Store, ty: GlobalType) -> Global {
let val = dummy_value(ty.content().clone());
Global::new(store, ty, val).unwrap()
}
/// Construct a dummy table for the given table type.
pub fn dummy_table(store: &Store, ty: TableType) -> Table {
let init_val = dummy_value(ty.element().clone());
Table::new(store, ty, init_val).unwrap()
}
/// Construct a dummy memory for the given memory type.
pub fn dummy_memory(store: &Store, ty: MemoryType) -> Memory {
Memory::new(store, ty)
}
/// Construct a dummy instance for the given instance type.
///
/// This is done by using the expected type to generate a module on-the-fly
/// which we the instantiate.
pub fn dummy_instance(store: &Store, ty: InstanceType) -> Instance {
let mut wat = WatGenerator::new();
for ty in ty.exports() {
wat.export(&ty);
}
let module = Module::new(store.engine(), &wat.finish()).unwrap();
Instance::new(store, &module, &[]).unwrap()
}
/// Construct a dummy module for the given module type.
///
/// This is done by using the expected type to generate a module on-the-fly.
pub fn dummy_module(store: &Store, ty: ModuleType) -> Module {
let mut wat = WatGenerator::new();
for ty in ty.imports() {
wat.import(&ty);
}
for ty in ty.exports() {
wat.export(&ty);
}
Module::new(store.engine(), &wat.finish()).unwrap()
}
struct WatGenerator {
tmp: usize,
dst: String,
}
impl WatGenerator {
fn new() -> WatGenerator {
WatGenerator {
tmp: 0,
dst: String::from("(module\n"),
}
}
fn finish(mut self) -> String {
self.dst.push_str(")\n");
self.dst
}
fn import(&mut self, ty: &ImportType<'_>) {
write!(self.dst, "(import ").unwrap();
self.str(ty.module());
write!(self.dst, " ").unwrap();
if let Some(field) = ty.name() {
self.str(field);
write!(self.dst, " ").unwrap();
}
self.item_ty(&ty.ty());
writeln!(self.dst, ")").unwrap();
}
fn item_ty(&mut self, ty: &ExternType) {
match ty {
ExternType::Memory(mem) => {
write!(
self.dst,
"(memory {} {})",
mem.limits().min(),
match mem.limits().max() {
Some(max) => max.to_string(),
None => String::new(),
}
)
.unwrap();
}
ExternType::Table(table) => {
write!(
self.dst,
"(table {} {} {})",
table.limits().min(),
match table.limits().max() {
Some(max) => max.to_string(),
None => String::new(),
},
wat_ty(table.element()),
)
.unwrap();
}
ExternType::Global(ty) => {
if ty.mutability() == Mutability::Const {
write!(self.dst, "(global {})", wat_ty(ty.content())).unwrap();
} else {
write!(self.dst, "(global (mut {}))", wat_ty(ty.content())).unwrap();
}
}
ExternType::Func(ty) => {
write!(self.dst, "(func ").unwrap();
self.func_sig(ty);
write!(self.dst, ")").unwrap();
}
ExternType::Instance(ty) => {
writeln!(self.dst, "(instance").unwrap();
for ty in ty.exports() {
write!(self.dst, "(export ").unwrap();
self.str(ty.name());
write!(self.dst, " ").unwrap();
self.item_ty(&ty.ty());
writeln!(self.dst, ")").unwrap();
}
write!(self.dst, ")").unwrap();
}
ExternType::Module(ty) => {
writeln!(self.dst, "(module").unwrap();
for ty in ty.imports() {
self.import(&ty);
writeln!(self.dst, "").unwrap();
}
for ty in ty.exports() {
write!(self.dst, "(export ").unwrap();
self.str(ty.name());
write!(self.dst, " ").unwrap();
self.item_ty(&ty.ty());
writeln!(self.dst, ")").unwrap();
}
write!(self.dst, ")").unwrap();
}
}
}
fn export(&mut self, ty: &ExportType<'_>) {
let wat_name = format!("item{}", self.tmp);
self.tmp += 1;
let item_ty = ty.ty();
self.item(&wat_name, &item_ty);
write!(self.dst, "(export ").unwrap();
self.str(ty.name());
write!(self.dst, " (").unwrap();
match item_ty {
ExternType::Memory(_) => write!(self.dst, "memory").unwrap(),
ExternType::Global(_) => write!(self.dst, "global").unwrap(),
ExternType::Func(_) => write!(self.dst, "func").unwrap(),
ExternType::Instance(_) => write!(self.dst, "instance").unwrap(),
ExternType::Table(_) => write!(self.dst, "table").unwrap(),
ExternType::Module(_) => write!(self.dst, "module").unwrap(),
}
writeln!(self.dst, " ${}))", wat_name).unwrap();
}
fn item(&mut self, name: &str, ty: &ExternType) {
match ty {
ExternType::Memory(mem) => {
write!(
self.dst,
"(memory ${} {} {})\n",
name,
mem.limits().min(),
match mem.limits().max() {
Some(max) => max.to_string(),
None => String::new(),
}
)
.unwrap();
}
ExternType::Table(table) => {
write!(
self.dst,
"(table ${} {} {} {})\n",
name,
table.limits().min(),
match table.limits().max() {
Some(max) => max.to_string(),
None => String::new(),
},
wat_ty(table.element()),
)
.unwrap();
}
ExternType::Global(ty) => {
write!(self.dst, "(global ${} ", name).unwrap();
if ty.mutability() == Mutability::Var {
write!(self.dst, "(mut ").unwrap();
}
write!(self.dst, "{}", wat_ty(ty.content())).unwrap();
if ty.mutability() == Mutability::Var {
write!(self.dst, ")").unwrap();
}
write!(self.dst, " (").unwrap();
self.value(ty.content());
writeln!(self.dst, "))").unwrap();
}
ExternType::Func(ty) => {
write!(self.dst, "(func ${} ", name).unwrap();
self.func_sig(ty);
for ty in ty.results() {
writeln!(self.dst, "").unwrap();
self.value(&ty);
}
writeln!(self.dst, ")").unwrap();
}
ExternType::Module(ty) => {
writeln!(self.dst, "(module ${}", name).unwrap();
for ty in ty.imports() {
self.import(&ty);
}
for ty in ty.exports() {
self.export(&ty);
}
self.dst.push_str(")\n");
}
ExternType::Instance(ty) => {
writeln!(self.dst, "(module ${}_module", name).unwrap();
for ty in ty.exports() {
self.export(&ty);
}
self.dst.push_str(")\n");
writeln!(self.dst, "(instance ${} (instantiate ${0}_module))", name).unwrap();
}
}
}
fn func_sig(&mut self, ty: &FuncType) {
write!(self.dst, "(param ").unwrap();
for ty in ty.params() {
write!(self.dst, "{} ", wat_ty(&ty)).unwrap();
}
write!(self.dst, ") (result ").unwrap();
for ty in ty.results() {
write!(self.dst, "{} ", wat_ty(&ty)).unwrap();
}
write!(self.dst, ")").unwrap();
}
fn value(&mut self, ty: &ValType) {
match ty {
ValType::I32 => write!(self.dst, "i32.const 0").unwrap(),
ValType::I64 => write!(self.dst, "i64.const 0").unwrap(),
ValType::F32 => write!(self.dst, "f32.const 0").unwrap(),
ValType::F64 => write!(self.dst, "f64.const 0").unwrap(),
ValType::V128 => write!(self.dst, "v128.const i32x4 0 0 0 0").unwrap(),
ValType::ExternRef => write!(self.dst, "ref.null extern").unwrap(),
ValType::FuncRef => write!(self.dst, "ref.null func").unwrap(),
}
}
fn str(&mut self, name: &str) {
let mut bytes = [0; 4];
self.dst.push_str("\"");
for c in name.chars() {
let v = c as u32;
if v >= 0x20 && v < 0x7f && c != '"' && c != '\\' && v < 0xff {
self.dst.push(c);
} else {
for byte in c.encode_utf8(&mut bytes).as_bytes() {
self.hex_byte(*byte);
}
}
}
self.dst.push_str("\"");
}
fn hex_byte(&mut self, byte: u8) {
fn to_hex(b: u8) -> char {
if b < 10 {
(b'0' + b) as char
} else {
(b'a' + b - 10) as char
}
}
self.dst.push('\\');
self.dst.push(to_hex((byte >> 4) & 0xf));
self.dst.push(to_hex(byte & 0xf));
}
}
fn wat_ty(ty: &ValType) -> &'static str {
match ty {
ValType::I32 => "i32",
ValType::I64 => "i64",
ValType::F32 => "f32",
ValType::F64 => "f64",
ValType::V128 => "v128",
ValType::ExternRef => "externref",
ValType::FuncRef => "funcref",
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::collections::HashSet;
fn store() -> Store {
let mut config = Config::default();
config.wasm_module_linking(true);
config.wasm_multi_memory(true);
let engine = wasmtime::Engine::new(&config).unwrap();
Store::new(&engine)
}
#[test]
fn dummy_table_import() {
let store = store();
let table = dummy_table(
&store,
TableType::new(ValType::ExternRef, Limits::at_least(10)),
);
assert_eq!(table.size(), 10);
for i in 0..10 {
assert!(table.get(i).unwrap().unwrap_externref().is_none());
}
}
#[test]
fn dummy_global_import() {
let store = store();
let global = dummy_global(&store, GlobalType::new(ValType::I32, Mutability::Const));
assert_eq!(global.val_type(), ValType::I32);
assert_eq!(global.mutability(), Mutability::Const);
}
#[test]
fn dummy_memory_import() {
let store = store();
let memory = dummy_memory(&store, MemoryType::new(Limits::at_least(1)));
assert_eq!(memory.size(), 1);
}
#[test]
fn dummy_function_import() {
let store = store();
let func_ty = FuncType::new(vec![ValType::I32], vec![ValType::I64]);
let func = dummy_func(&store, func_ty.clone());
assert_eq!(func.ty(), func_ty);
}
#[test]
fn dummy_instance_import() {
let store = store();
let mut instance_ty = InstanceType::new();
// Functions.
instance_ty.add_named_export("func0", FuncType::new(vec![ValType::I32], vec![]).into());
instance_ty.add_named_export("func1", FuncType::new(vec![], vec![ValType::I64]).into());
// Globals.
instance_ty.add_named_export(
"global0",
GlobalType::new(ValType::I32, Mutability::Const).into(),
);
instance_ty.add_named_export(
"global1",
GlobalType::new(ValType::I64, Mutability::Var).into(),
);
// Tables.
instance_ty.add_named_export(
"table0",
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
instance_ty.add_named_export(
"table1",
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
// Memories.
instance_ty.add_named_export("memory0", MemoryType::new(Limits::at_least(1)).into());
instance_ty.add_named_export("memory1", MemoryType::new(Limits::at_least(1)).into());
// Modules.
instance_ty.add_named_export("module0", ModuleType::new().into());
instance_ty.add_named_export("module1", ModuleType::new().into());
// Instances.
instance_ty.add_named_export("instance0", InstanceType::new().into());
instance_ty.add_named_export("instance1", InstanceType::new().into());
let instance = dummy_instance(&store, instance_ty.clone());
let mut expected_exports = vec![
"func0",
"func1",
"global0",
"global1",
"table0",
"table1",
"memory0",
"memory1",
"module0",
"module1",
"instance0",
"instance1",
]
.into_iter()
.collect::<HashSet<_>>();
for exp in instance.ty().exports() {
let was_expected = expected_exports.remove(exp.name());
assert!(was_expected);
}
assert!(expected_exports.is_empty());
}
#[test]
fn dummy_module_import() {
let store = store();
let mut module_ty = ModuleType::new();
// Multiple exported and imported functions.
module_ty.add_named_export("func0", FuncType::new(vec![ValType::I32], vec![]).into());
module_ty.add_named_export("func1", FuncType::new(vec![], vec![ValType::I64]).into());
module_ty.add_named_import(
"func2",
None,
FuncType::new(vec![ValType::I64], vec![]).into(),
);
module_ty.add_named_import(
"func3",
None,
FuncType::new(vec![], vec![ValType::I32]).into(),
);
// Multiple exported and imported globals.
module_ty.add_named_export(
"global0",
GlobalType::new(ValType::I32, Mutability::Const).into(),
);
module_ty.add_named_export(
"global1",
GlobalType::new(ValType::I64, Mutability::Var).into(),
);
module_ty.add_named_import(
"global2",
None,
GlobalType::new(ValType::I32, Mutability::Var).into(),
);
module_ty.add_named_import(
"global3",
None,
GlobalType::new(ValType::I64, Mutability::Const).into(),
);
// Multiple exported and imported tables.
module_ty.add_named_export(
"table0",
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
module_ty.add_named_export(
"table1",
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
module_ty.add_named_import(
"table2",
None,
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
module_ty.add_named_import(
"table3",
None,
TableType::new(ValType::ExternRef, Limits::at_least(1)).into(),
);
// Multiple exported and imported memories.
module_ty.add_named_export("memory0", MemoryType::new(Limits::at_least(1)).into());
module_ty.add_named_export("memory1", MemoryType::new(Limits::at_least(1)).into());
module_ty.add_named_import("memory2", None, MemoryType::new(Limits::at_least(1)).into());
module_ty.add_named_import("memory3", None, MemoryType::new(Limits::at_least(1)).into());
// An exported and an imported module.
module_ty.add_named_export("module0", ModuleType::new().into());
module_ty.add_named_import("module1", None, ModuleType::new().into());
// An exported and an imported instance.
module_ty.add_named_export("instance0", InstanceType::new().into());
module_ty.add_named_import("instance1", None, InstanceType::new().into());
// Create the module.
let module = dummy_module(&store, module_ty);
// Check that we have the expected exports.
assert!(module.get_export("func0").is_some());
assert!(module.get_export("func1").is_some());
assert!(module.get_export("global0").is_some());
assert!(module.get_export("global1").is_some());
assert!(module.get_export("table0").is_some());
assert!(module.get_export("table1").is_some());
assert!(module.get_export("memory0").is_some());
assert!(module.get_export("memory1").is_some());
assert!(module.get_export("instance0").is_some());
assert!(module.get_export("module0").is_some());
// Check that we have the exported imports.
let mut expected_imports = vec![
"func2",
"func3",
"global2",
"global3",
"table2",
"table3",
"memory2",
"memory3",
"instance1",
"module1",
]
.into_iter()
.collect::<HashSet<_>>();
for imp in module.imports() {
assert!(imp.name().is_none());
let was_expected = expected_imports.remove(imp.module());
assert!(was_expected);
}
assert!(expected_imports.is_empty());
}
}