use super::{
run::{ParsingMode, RunnerConfig},
TestDescriptor,
TestError,
TestProfile,
TestSpan,
};
use anyhow::Result;
use std::collections::HashMap;
use wasmi::{
Engine,
Extern,
Func,
Global,
Instance,
Linker,
Memory,
MemoryType,
Module,
Mutability,
Store,
Table,
TableType,
Val,
};
use wasmi_core::{ValType, F32, F64};
use wast::token::{Id, Span};
#[derive(Debug)]
pub struct TestContext<'a> {
engine: Engine,
runner_config: RunnerConfig,
linker: Linker<()>,
store: Store<()>,
modules: Vec<Module>,
instances: HashMap<String, Instance>,
last_instance: Option<Instance>,
profile: TestProfile,
results: Vec<Val>,
descriptor: &'a TestDescriptor,
}
impl<'a> TestContext<'a> {
pub fn new(descriptor: &'a TestDescriptor, runner_config: RunnerConfig) -> Self {
let engine = Engine::new(&runner_config.config);
let mut linker = Linker::new(&engine);
let mut store = Store::new(&engine, ());
_ = store.set_fuel(1_000_000_000);
let default_memory = Memory::new(&mut store, MemoryType::new(1, Some(2)).unwrap()).unwrap();
let default_table = Table::new(
&mut store,
TableType::new(ValType::FuncRef, 10, Some(20)),
Val::default(ValType::FuncRef),
)
.unwrap();
let global_i32 = Global::new(&mut store, Val::I32(666), Mutability::Const);
let global_i64 = Global::new(&mut store, Val::I64(666), Mutability::Const);
let global_f32 = Global::new(&mut store, Val::F32(666.0.into()), Mutability::Const);
let global_f64 = Global::new(&mut store, Val::F64(666.0.into()), Mutability::Const);
let print = Func::wrap(&mut store, || {
println!("print");
});
let print_i32 = Func::wrap(&mut store, |value: i32| {
println!("print: {value}");
});
let print_i64 = Func::wrap(&mut store, |value: i64| {
println!("print: {value}");
});
let print_f32 = Func::wrap(&mut store, |value: F32| {
println!("print: {value:?}");
});
let print_f64 = Func::wrap(&mut store, |value: F64| {
println!("print: {value:?}");
});
let print_i32_f32 = Func::wrap(&mut store, |v0: i32, v1: F32| {
println!("print: {v0:?} {v1:?}");
});
let print_f64_f64 = Func::wrap(&mut store, |v0: F64, v1: F64| {
println!("print: {v0:?} {v1:?}");
});
linker.define("spectest", "memory", default_memory).unwrap();
linker.define("spectest", "table", default_table).unwrap();
linker.define("spectest", "global_i32", global_i32).unwrap();
linker.define("spectest", "global_i64", global_i64).unwrap();
linker.define("spectest", "global_f32", global_f32).unwrap();
linker.define("spectest", "global_f64", global_f64).unwrap();
linker.define("spectest", "print", print).unwrap();
linker.define("spectest", "print_i32", print_i32).unwrap();
linker.define("spectest", "print_i64", print_i64).unwrap();
linker.define("spectest", "print_f32", print_f32).unwrap();
linker.define("spectest", "print_f64", print_f64).unwrap();
linker
.define("spectest", "print_i32_f32", print_i32_f32)
.unwrap();
linker
.define("spectest", "print_f64_f64", print_f64_f64)
.unwrap();
TestContext {
engine,
runner_config,
linker,
store,
modules: Vec::new(),
instances: HashMap::new(),
last_instance: None,
profile: TestProfile::default(),
results: Vec::new(),
descriptor,
}
}
}
impl TestContext<'_> {
fn test_path(&self) -> &str {
self.descriptor.path()
}
pub fn spanned(&self, span: Span) -> TestSpan {
self.descriptor.spanned(span)
}
fn engine(&self) -> &Engine {
&self.engine
}
pub fn store(&self) -> &Store<()> {
&self.store
}
pub fn store_mut(&mut self) -> &mut Store<()> {
&mut self.store
}
pub fn profile(&mut self) -> &mut TestProfile {
&mut self.profile
}
pub fn compile_and_instantiate(
&mut self,
mut module: wast::core::Module,
) -> Result<Instance, TestError> {
let module_name = module.id.map(|id| id.name());
let wasm = module.encode().unwrap_or_else(|error| {
panic!(
"encountered unexpected failure to encode `.wast` module into `.wasm`:{}: {}",
self.test_path(),
error
)
});
let module = match self.runner_config.mode {
ParsingMode::Buffered => Module::new(self.engine(), &wasm[..])?,
ParsingMode::Streaming => Module::new_streaming(self.engine(), &wasm[..])?,
};
let instance_pre = self.linker.instantiate(&mut self.store, &module)?;
let instance = instance_pre.start(&mut self.store)?;
self.modules.push(module);
if let Some(module_name) = module_name {
self.instances.insert(module_name.to_string(), instance);
for export in instance.exports(&self.store) {
self.linker
.define(module_name, export.name(), export.into_extern())?;
}
}
self.last_instance = Some(instance);
Ok(instance)
}
pub fn instance_by_name(&self, name: &str) -> Result<Instance, TestError> {
self.instances
.get(name)
.copied()
.ok_or_else(|| TestError::InstanceNotRegistered {
name: name.to_owned(),
})
}
pub fn instance_by_name_or_last(&self, name: Option<&str>) -> Result<Instance, TestError> {
name.map(|name| self.instance_by_name(name))
.unwrap_or_else(|| self.last_instance.ok_or(TestError::NoModuleInstancesFound))
}
pub fn register_instance(&mut self, name: &str, instance: Instance) {
if self.instances.contains_key(name) {
return;
}
self.instances.insert(name.to_string(), instance);
for export in instance.exports(&self.store) {
self.linker
.define(name, export.name(), export.clone().into_extern())
.unwrap_or_else(|error| {
let field_name = export.name();
let export = export.clone().into_extern();
panic!(
"failed to define export {name}::{field_name}: \
{export:?}: {error}",
)
});
}
self.last_instance = Some(instance);
}
pub fn invoke(
&mut self,
module_name: Option<&str>,
func_name: &str,
args: &[Val],
) -> Result<&[Val], TestError> {
let instance = self.instance_by_name_or_last(module_name)?;
let func = instance
.get_export(&self.store, func_name)
.and_then(Extern::into_func)
.ok_or_else(|| TestError::FuncNotFound {
module_name: module_name.map(|name| name.to_string()),
func_name: func_name.to_string(),
})?;
let len_results = func.ty(&self.store).results().len();
self.results.clear();
self.results.resize(len_results, Val::I32(0));
func.call(&mut self.store, args, &mut self.results)?;
Ok(&self.results)
}
pub fn get_global(&self, module_name: Option<Id>, global_name: &str) -> Result<Val, TestError> {
let module_name = module_name.map(|id| id.name());
let instance = self.instance_by_name_or_last(module_name)?;
let global = instance
.get_export(&self.store, global_name)
.and_then(Extern::into_global)
.ok_or_else(|| TestError::GlobalNotFound {
module_name: module_name.map(|name| name.to_string()),
global_name: global_name.to_string(),
})?;
let value = global.get(&self.store);
Ok(value)
}
}