Implement RFC 11: Redesigning Wasmtime's APIs (#2897)

Implement Wasmtime's new API as designed by RFC 11. This is quite a large commit which has had lots of discussion externally, so for more information it's best to read the RFC thread and the PR thread.
This commit is contained in:
Alex Crichton
2021-06-03 09:10:53 -05:00
committed by GitHub
parent a5a28b1c5b
commit 7a1b7cdf92
233 changed files with 13349 additions and 11997 deletions

View File

@@ -15,8 +15,6 @@ pub mod dummy;
use arbitrary::Arbitrary;
use dummy::dummy_linker;
use log::debug;
use std::cell::Cell;
use std::rc::Rc;
use std::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
@@ -44,9 +42,9 @@ fn log_wasm(wasm: &[u8]) {
}
}
fn create_store(engine: &Engine) -> Store {
Store::new_with_limits(
&engine,
fn create_store(engine: &Engine) -> Store<()> {
let mut store = Store::new(&engine, ());
store.limiter(
StoreLimitsBuilder::new()
// The limits here are chosen based on the default "maximum type size"
// configured in wasm-smith, which is 1000. This means that instances
@@ -56,7 +54,8 @@ fn create_store(engine: &Engine) -> Store {
.tables(1100)
.memories(1100)
.build(),
)
);
store
}
/// Methods of timing out execution of a WebAssembly module
@@ -110,7 +109,7 @@ pub fn instantiate_with_config(
_ => false,
});
let engine = Engine::new(&config).unwrap();
let store = create_store(&engine);
let mut store = create_store(&engine);
let mut timeout_state = SignalOnDrop::default();
match timeout {
@@ -137,9 +136,9 @@ pub fn instantiate_with_config(
Err(_) if !known_valid => return,
Err(e) => panic!("failed to compile module: {:?}", e),
};
let linker = dummy_linker(&store, &module);
let linker = dummy_linker(&mut store, &module);
match linker.instantiate(&module) {
match linker.instantiate(&mut store, &module) {
Ok(_) => {}
Err(e) => {
let string = e.to_string();
@@ -218,7 +217,7 @@ pub fn differential_execution(
config.wasm_module_linking(false);
let engine = Engine::new(&config).unwrap();
let store = create_store(&engine);
let mut store = create_store(&engine);
let module = Module::new(&engine, &wasm).unwrap();
@@ -227,13 +226,13 @@ pub fn differential_execution(
// in and with what values. Like the results of exported functions,
// calls to imports should also yield the same values for each
// configuration, and we should assert that.
let linker = dummy_linker(&store, &module);
let linker = dummy_linker(&mut store, &module);
// Don't unwrap this: there can be instantiation-/link-time errors that
// aren't caught during validation or compilation. For example, an imported
// table might not have room for an element segment that we want to
// initialize into it.
let instance = match linker.instantiate(&module) {
let instance = match linker.instantiate(&mut store, &module) {
Ok(instance) => instance,
Err(e) => {
eprintln!(
@@ -244,30 +243,36 @@ pub fn differential_execution(
}
};
for (name, f) in instance.exports().filter_map(|e| {
let name = e.name();
e.into_func().map(|f| (name, f))
}) {
let exports = instance
.exports(&mut store)
.filter_map(|e| {
let name = e.name().to_string();
e.into_func().map(|f| (name, f))
})
.collect::<Vec<_>>();
for (name, f) in exports {
// Always call the hang limit initializer first, so that we don't
// infinite loop when calling another export.
init_hang_limit(&instance);
init_hang_limit(&mut store, instance);
let ty = f.ty();
let ty = f.ty(&store);
let params = dummy::dummy_values(ty.params());
let this_result = f.call(&params).map_err(|e| e.downcast::<Trap>().unwrap());
let this_result = f
.call(&mut store, &params)
.map_err(|e| e.downcast::<Trap>().unwrap());
let existing_result = export_func_results
.entry(name.to_string())
.or_insert_with(|| this_result.clone());
assert_same_export_func_result(&existing_result, &this_result, name);
assert_same_export_func_result(&existing_result, &this_result, &name);
}
}
fn init_hang_limit(instance: &Instance) {
match instance.get_export("hangLimitInitializer") {
fn init_hang_limit(store: &mut Store<()>, instance: Instance) {
match instance.get_export(&mut *store, "hangLimitInitializer") {
None => return,
Some(Extern::Func(f)) => {
f.call(&[])
f.call(store, &[])
.expect("initializing the hang limit should not fail");
}
Some(_) => panic!("unexpected hangLimitInitializer export"),
@@ -332,7 +337,7 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
let mut config: Option<Config> = None;
let mut engine: Option<Engine> = None;
let mut store: Option<Store> = None;
let mut store: Option<Store<()>> = None;
let mut modules: HashMap<usize, Module> = Default::default();
let mut instances: HashMap<usize, Instance> = Default::default();
@@ -390,14 +395,14 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
None => continue,
};
let store = store.as_ref().unwrap();
let store = store.as_mut().unwrap();
let linker = dummy_linker(store, module);
// Don't unwrap this: there can be instantiation-/link-time errors that
// aren't caught during validation or compilation. For example, an imported
// table might not have room for an element segment that we want to
// initialize into it.
if let Ok(instance) = linker.instantiate(&module) {
if let Ok(instance) = linker.instantiate(store, &module) {
instances.insert(id, instance);
}
}
@@ -421,9 +426,10 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
continue;
}
};
let store = store.as_mut().unwrap();
let funcs = instance
.exports()
.exports(&mut *store)
.filter_map(|e| match e.into_extern() {
Extern::Func(f) => Some(f.clone()),
_ => None,
@@ -436,9 +442,9 @@ pub fn make_api_calls(api: crate::generators::api::ApiCalls) {
let nth = nth % funcs.len();
let f = &funcs[nth];
let ty = f.ty();
let ty = f.ty(&store);
let params = dummy::dummy_values(ty.params());
let _ = f.call(&params);
let _ = f.call(store, &params);
}
}
}
@@ -454,7 +460,7 @@ pub fn spectest(fuzz_config: crate::generators::Config, test: crate::generators:
config.wasm_reference_types(false);
config.wasm_bulk_memory(false);
config.wasm_module_linking(false);
let store = create_store(&Engine::new(&config).unwrap());
let mut store = create_store(&Engine::new(&config).unwrap());
if fuzz_config.consume_fuel {
store.add_fuel(u64::max_value()).unwrap();
}
@@ -472,13 +478,13 @@ pub fn table_ops(
) {
let _ = env_logger::try_init();
let num_dropped = Rc::new(Cell::new(0));
let num_dropped = Arc::new(AtomicUsize::new(0));
{
let mut config = fuzz_config.to_wasmtime();
config.wasm_reference_types(true);
let engine = Engine::new(&config).unwrap();
let store = create_store(&engine);
let mut store = create_store(&engine);
if fuzz_config.consume_fuel {
store.add_fuel(u64::max_value()).unwrap();
}
@@ -494,31 +500,30 @@ pub fn table_ops(
// test case.
const MAX_GCS: usize = 5;
let num_gcs = Cell::new(0);
let gc = Func::wrap(&store, move |caller: Caller| {
if num_gcs.get() < MAX_GCS {
caller.store().gc();
num_gcs.set(num_gcs.get() + 1);
let num_gcs = AtomicUsize::new(0);
let gc = Func::wrap(&mut store, move |mut caller: Caller<'_, ()>| {
if num_gcs.fetch_add(1, SeqCst) < MAX_GCS {
caller.gc();
}
});
let instance = Instance::new(&store, &module, &[gc.into()]).unwrap();
let run = instance.get_func("run").unwrap();
let instance = Instance::new(&mut store, &module, &[gc.into()]).unwrap();
let run = instance.get_func(&mut store, "run").unwrap();
let args: Vec<_> = (0..ops.num_params())
.map(|_| Val::ExternRef(Some(ExternRef::new(CountDrops(num_dropped.clone())))))
.collect();
let _ = run.call(&args);
let _ = run.call(&mut store, &args);
}
assert_eq!(num_dropped.get(), ops.num_params());
assert_eq!(num_dropped.load(SeqCst), ops.num_params() as usize);
return;
struct CountDrops(Rc<Cell<u8>>);
struct CountDrops(Arc<AtomicUsize>);
impl Drop for CountDrops {
fn drop(&mut self) {
self.0.set(self.0.get().checked_add(1).unwrap());
self.0.fetch_add(1, SeqCst);
}
}
}
@@ -593,13 +598,13 @@ pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Con
let mut wasmtime_config = config.to_wasmtime();
wasmtime_config.cranelift_nan_canonicalization(true);
let wasmtime_engine = Engine::new(&wasmtime_config).unwrap();
let wasmtime_store = create_store(&wasmtime_engine);
let mut wasmtime_store = create_store(&wasmtime_engine);
if config.consume_fuel {
wasmtime_store.add_fuel(u64::max_value()).unwrap();
}
let wasmtime_module =
Module::new(&wasmtime_engine, &wasm).expect("Wasmtime can compile module");
let wasmtime_instance = Instance::new(&wasmtime_store, &wasmtime_module, &[])
let wasmtime_instance = Instance::new(&mut wasmtime_store, &wasmtime_module, &[])
.expect("Wasmtime can instantiate module");
// Introspect wasmtime module to find name of an exported function and of an
@@ -628,12 +633,12 @@ pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Con
let wasmi_val = wasmi::FuncInstance::invoke(&wasmi_main, &[], &mut wasmi::NopExternals);
let wasmtime_mem = wasmtime_instance
.get_memory(&memory_name[..])
.get_memory(&mut wasmtime_store, &memory_name[..])
.expect("memory export is present");
let wasmtime_main = wasmtime_instance
.get_func(&func_name[..])
.get_func(&mut wasmtime_store, &func_name[..])
.expect("function export is present");
let wasmtime_vals = wasmtime_main.call(&[]);
let wasmtime_vals = wasmtime_main.call(&mut wasmtime_store, &[]);
let wasmtime_val = wasmtime_vals.map(|v| v.iter().next().cloned());
debug!(
@@ -665,18 +670,18 @@ pub fn differential_wasmi_execution(wasm: &[u8], config: &crate::generators::Con
}
}
if wasmi_mem.current_size().0 != wasmtime_mem.size() as usize {
if wasmi_mem.current_size().0 != wasmtime_mem.size(&wasmtime_store) as usize {
show_wat();
panic!("resulting memories are not the same size");
}
// Wasmi memory may be stored non-contiguously; copy it out to a contiguous chunk.
let mut wasmi_buf: Vec<u8> = vec![0; wasmtime_mem.data_size()];
let mut wasmi_buf: Vec<u8> = vec![0; wasmtime_mem.data_size(&wasmtime_store)];
wasmi_mem
.get_into(0, &mut wasmi_buf[..])
.expect("can access wasmi memory");
let wasmtime_slice = unsafe { wasmtime_mem.data_unchecked() };
let wasmtime_slice = wasmtime_mem.data(&wasmtime_store);
if wasmi_buf.len() >= 64 {
debug!("-> First 64 bytes of wasmi heap: {:?}", &wasmi_buf[0..64]);

View File

@@ -4,8 +4,8 @@ use std::fmt::Write;
use wasmtime::*;
/// Create a set of dummy functions/globals/etc for the given imports.
pub fn dummy_linker<'module>(store: &Store, module: &Module) -> Linker {
let mut linker = Linker::new(store);
pub fn dummy_linker<'module>(store: &mut Store<()>, module: &Module) -> Linker<()> {
let mut linker = Linker::new(store.engine());
linker.allow_shadowing(true);
for import in module.imports() {
match import.name() {
@@ -34,19 +34,19 @@ pub fn dummy_linker<'module>(store: &Store, module: &Module) -> Linker {
}
/// Construct a dummy `Extern` from its type signature
pub fn dummy_extern(store: &Store, ty: ExternType) -> Extern {
pub fn dummy_extern(store: &mut Store<()>, ty: ExternType) -> Extern {
match 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)),
ExternType::Module(module_ty) => Extern::Module(dummy_module(store.engine(), module_ty)),
}
}
/// Construct a dummy function for the given function type
pub fn dummy_func(store: &Store, ty: FuncType) -> Func {
pub fn dummy_func(store: &mut 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);
@@ -74,19 +74,19 @@ pub fn dummy_values(val_tys: impl IntoIterator<Item = ValType>) -> Vec<Val> {
}
/// Construct a dummy global for the given global type.
pub fn dummy_global(store: &Store, ty: GlobalType) -> Global {
pub fn dummy_global(store: &mut 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 {
pub fn dummy_table(store: &mut 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 {
pub fn dummy_memory(store: &mut Store<()>, ty: MemoryType) -> Memory {
Memory::new(store, ty).unwrap()
}
@@ -94,7 +94,7 @@ pub fn dummy_memory(store: &Store, ty: MemoryType) -> Memory {
///
/// 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 {
pub fn dummy_instance(store: &mut Store<()>, ty: InstanceType) -> Instance {
let mut wat = WatGenerator::new();
for ty in ty.exports() {
wat.export(&ty);
@@ -106,7 +106,7 @@ pub fn dummy_instance(store: &Store, ty: InstanceType) -> Instance {
/// 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 {
pub fn dummy_module(engine: &Engine, ty: ModuleType) -> Module {
let mut wat = WatGenerator::new();
for ty in ty.imports() {
wat.import(&ty);
@@ -114,7 +114,7 @@ pub fn dummy_module(store: &Store, ty: ModuleType) -> Module {
for ty in ty.exports() {
wat.export(&ty);
}
Module::new(store.engine(), &wat.finish()).unwrap()
Module::new(engine, &wat.finish()).unwrap()
}
struct WatGenerator {
@@ -378,53 +378,57 @@ mod tests {
use super::*;
use std::collections::HashSet;
fn store() -> Store {
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)
Store::new(&engine, ())
}
#[test]
fn dummy_table_import() {
let store = store();
let mut store = store();
let table = dummy_table(
&store,
&mut store,
TableType::new(ValType::ExternRef, Limits::at_least(10)),
);
assert_eq!(table.size(), 10);
assert_eq!(table.size(&store), 10);
for i in 0..10 {
assert!(table.get(i).unwrap().unwrap_externref().is_none());
assert!(table
.get(&mut store, 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);
let mut store = store();
let global = dummy_global(&mut store, GlobalType::new(ValType::I32, Mutability::Const));
assert_eq!(*global.ty(&store).content(), ValType::I32);
assert_eq!(global.ty(&store).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);
let mut store = store();
let memory = dummy_memory(&mut store, MemoryType::new(Limits::at_least(1)));
assert_eq!(memory.size(&store), 1);
}
#[test]
fn dummy_function_import() {
let store = store();
let mut 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);
let func = dummy_func(&mut store, func_ty.clone());
assert_eq!(func.ty(&store), func_ty);
}
#[test]
fn dummy_instance_import() {
let store = store();
let mut store = store();
let mut instance_ty = InstanceType::new();
@@ -464,7 +468,7 @@ mod tests {
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 instance = dummy_instance(&mut store, instance_ty.clone());
let mut expected_exports = vec![
"func0",
@@ -482,7 +486,7 @@ mod tests {
]
.into_iter()
.collect::<HashSet<_>>();
for exp in instance.ty().exports() {
for exp in instance.ty(&store).exports() {
let was_expected = expected_exports.remove(exp.name());
assert!(was_expected);
}
@@ -564,7 +568,7 @@ mod tests {
module_ty.add_named_import("instance1", None, InstanceType::new().into());
// Create the module.
let module = dummy_module(&store, module_ty);
let module = dummy_module(store.engine(), module_ty);
// Check that we have the expected exports.
assert!(module.get_export("func0").is_some());