use alloc::string::ToString;
use alloc::vec::Vec;
use const_interpreter_loop::run_const;
use function_ref::FunctionRef;
use interpreter_loop::run;
use locals::Locals;
use store::DataInst;
use value_stack::Stack;
use crate::core::reader::types::export::{Export, ExportDesc};
use crate::core::reader::types::FuncType;
use crate::core::reader::WasmReader;
use crate::execution::assert_validated::UnwrapValidatedExt;
use crate::execution::hooks::{EmptyHookSet, HookSet};
use crate::execution::store::{FuncInst, GlobalInst, MemInst, Store};
use crate::execution::value::Value;
use crate::validation::code::read_declared_locals;
use crate::value::InteropValueList;
use crate::{RuntimeError, ValType, ValidationInfo};
pub(crate) mod assert_validated;
pub mod const_interpreter_loop;
pub mod function_ref;
pub mod hooks;
mod interpreter_loop;
pub(crate) mod locals;
pub(crate) mod store;
pub mod value;
pub mod value_stack;
pub const DEFAULT_MODULE: &str = "__interpreter_default__";
pub struct RuntimeInstance<'b, H = EmptyHookSet>
where
H: HookSet,
{
pub wasm_bytecode: &'b [u8],
types: Vec<FuncType>,
exports: Vec<Export>,
pub store: Store,
pub hook_set: H,
}
impl<'b> RuntimeInstance<'b, EmptyHookSet> {
pub fn new(validation_info: &'_ ValidationInfo<'b>) -> Result<Self, RuntimeError> {
Self::new_with_hooks(DEFAULT_MODULE, validation_info, EmptyHookSet)
}
pub fn new_named(
module_name: &str,
validation_info: &'_ ValidationInfo<'b>,
) -> Result<Self, RuntimeError> {
Self::new_with_hooks(module_name, validation_info, EmptyHookSet)
}
}
impl<'b, H> RuntimeInstance<'b, H>
where
H: HookSet,
{
pub fn new_with_hooks(
module_name: &str,
validation_info: &'_ ValidationInfo<'b>,
hook_set: H,
) -> Result<Self, RuntimeError> {
trace!("Starting instantiation of bytecode");
let store = Self::init_store(validation_info);
let mut instance = RuntimeInstance {
wasm_bytecode: validation_info.wasm,
types: validation_info.types.clone(),
exports: validation_info.exports.clone(),
store,
hook_set,
};
if let Some(start) = validation_info.start {
let start_fn = FunctionRef {
module_name: module_name.to_string(),
function_name: "start".to_string(),
module_index: 0,
function_index: start,
exported: false,
};
instance.invoke::<(), ()>(&start_fn, ())?;
}
Ok(instance)
}
pub fn get_function_by_name(
&self,
module_name: &str,
function_name: &str,
) -> Result<FunctionRef, RuntimeError> {
let (module_idx, func_idx) = self.get_indicies(module_name, function_name)?;
Ok(FunctionRef {
module_name: module_name.to_string(),
function_name: function_name.to_string(),
module_index: module_idx,
function_index: func_idx,
exported: true,
})
}
pub fn get_function_by_index(
&self,
module_idx: usize,
function_idx: usize,
) -> Result<FunctionRef, RuntimeError> {
let function_name = self
.exports
.iter()
.find(|export| match &export.desc {
ExportDesc::FuncIdx(idx) => *idx == function_idx,
_ => false,
})
.map(|export| export.name.clone())
.ok_or(RuntimeError::FunctionNotFound)?;
Ok(FunctionRef {
module_name: DEFAULT_MODULE.to_string(),
function_name,
module_index: module_idx,
function_index: function_idx,
exported: true,
})
}
#[allow(clippy::result_unit_err)]
pub fn add_module(
&mut self,
_module_name: &str,
_validation_info: &'_ ValidationInfo<'b>,
) -> Result<(), ()> {
todo!("Implement module linking");
}
pub fn invoke<Param: InteropValueList, Returns: InteropValueList>(
&mut self,
function_ref: &FunctionRef,
params: Param,
) -> Result<Returns, RuntimeError> {
let (_module_idx, func_idx) = self.verify_function_ref(function_ref)?;
let func_inst = self.store.funcs.get(func_idx).expect("valid FuncIdx");
let func_ty = self.types.get(func_inst.ty).unwrap_validated();
if func_ty.params.valtypes != Param::TYS {
panic!("Invalid `Param` generics");
}
if func_ty.returns.valtypes != Returns::TYS {
panic!("Invalid `Returns` generics");
}
let mut stack = Stack::new();
let locals = Locals::new(
params.into_values().into_iter(),
func_inst.locals.iter().cloned(),
);
stack.push_stackframe(func_idx, func_ty, locals, usize::MAX);
run(
self.wasm_bytecode,
&self.types,
&mut self.store,
&mut stack,
EmptyHookSet,
)?;
let return_values = Returns::TYS
.iter()
.map(|ty| stack.pop_value(*ty))
.collect::<Vec<Value>>();
let reversed_values = return_values.into_iter().rev();
let ret: Returns = Returns::from_values(reversed_values);
debug!("Successfully invoked function");
Ok(ret)
}
pub fn invoke_dynamic(
&mut self,
function_ref: &FunctionRef,
params: Vec<Value>,
ret_types: &[ValType],
) -> Result<Vec<Value>, RuntimeError> {
let (_module_idx, func_idx) = self.verify_function_ref(function_ref)?;
let func_inst = self.store.funcs.get(func_idx).expect("valid FuncIdx");
let func_ty = self.types.get(func_inst.ty).unwrap_validated();
let param_types = params.iter().map(|v| v.to_ty()).collect::<Vec<_>>();
if func_ty.params.valtypes != param_types {
panic!("Invalid parameters for function");
}
if func_ty.returns.valtypes != ret_types {
panic!("Invalid return types for function");
}
let mut stack = Stack::new();
let locals = Locals::new(params.into_iter(), func_inst.locals.iter().cloned());
stack.push_stackframe(func_idx, func_ty, locals, 0);
run(
self.wasm_bytecode,
&self.types,
&mut self.store,
&mut stack,
EmptyHookSet,
)?;
let func_inst = self.store.funcs.get(func_idx).expect("valid FuncIdx");
let func_ty = self.types.get(func_inst.ty).unwrap_validated();
let return_values = func_ty
.returns
.valtypes
.iter()
.map(|ty| stack.pop_value(*ty))
.collect::<Vec<Value>>();
let reversed_values = return_values.into_iter().rev();
let ret = reversed_values.collect();
debug!("Successfully invoked function");
Ok(ret)
}
fn get_indicies(
&self,
_module_name: &str,
function_name: &str,
) -> Result<(usize, usize), RuntimeError> {
let func_idx = self
.exports
.iter()
.find_map(|export| {
if export.name == function_name {
match export.desc {
ExportDesc::FuncIdx(func_idx) => Some(func_idx),
_ => None,
}
} else {
None
}
})
.ok_or(RuntimeError::FunctionNotFound)?;
Ok((0, func_idx))
}
fn verify_function_ref(
&self,
function_ref: &FunctionRef,
) -> Result<(usize, usize), RuntimeError> {
if function_ref.exported {
let (module_idx, func_idx) =
self.get_indicies(&function_ref.module_name, &function_ref.function_name)?;
if module_idx != function_ref.module_index || func_idx != function_ref.function_index {
return Err(RuntimeError::FunctionNotFound);
}
Ok((module_idx, func_idx))
} else {
let (module_idx, func_idx) = (function_ref.module_index, function_ref.function_index);
self.store
.funcs
.get(func_idx)
.ok_or(RuntimeError::FunctionNotFound)?;
Ok((module_idx, func_idx))
}
}
fn init_store(validation_info: &ValidationInfo) -> Store {
let function_instances: Vec<FuncInst> = {
let mut wasm_reader = WasmReader::new(validation_info.wasm);
let functions = validation_info.functions.iter();
let func_blocks = validation_info.func_blocks.iter();
functions
.zip(func_blocks)
.map(|(ty, func)| {
wasm_reader
.move_start_to(*func)
.expect("function index to be in the bounds of the WASM binary");
let (locals, bytes_read) = wasm_reader
.measure_num_read_bytes(read_declared_locals)
.unwrap_validated();
let code_expr = wasm_reader
.make_span(func.len() - bytes_read)
.expect("TODO remove this expect");
FuncInst {
ty: *ty,
locals,
code_expr,
}
})
.collect()
};
let mut memory_instances: Vec<MemInst> = validation_info
.memories
.iter()
.map(|ty| MemInst::new(*ty))
.collect();
let data_sections: Vec<DataInst> = validation_info
.data
.iter()
.map(|d| {
use crate::core::reader::types::data::DataMode;
if let DataMode::Active(active_data) = d.mode.clone() {
let mem_idx = active_data.memory_idx;
if mem_idx != 0 {
todo!("Active data has memory_idx different than 0");
}
assert!(memory_instances.len() > mem_idx);
let value = {
let mut wasm = WasmReader::new(validation_info.wasm);
wasm.move_start_to(active_data.offset).unwrap_validated();
let mut stack = Stack::new();
run_const(wasm, &mut stack, ());
let value = stack.peek_unknown_value();
if value.is_none() {
panic!("No value on the stack for data segment offset");
}
value.unwrap()
};
let offset: u32 = match value {
Value::I32(val) => val,
Value::I64(val) => {
if val > u32::MAX as u64 {
panic!("i64 value for data segment offset is out of reach")
}
val as u32
}
_ => unimplemented!(),
};
let mem_inst = memory_instances.get_mut(mem_idx).unwrap();
let len = mem_inst.data.len();
if offset as usize + d.init.len() > len {
panic!("Active data writing in memory, out of bounds");
}
let data = mem_inst
.data
.get_mut(offset as usize..offset as usize + d.init.len())
.unwrap();
data.copy_from_slice(&d.init);
}
DataInst {
data: d.init.clone(),
}
})
.collect();
let global_instances: Vec<GlobalInst> = validation_info
.globals
.iter()
.map({
let mut stack = Stack::new();
move |global| {
let mut wasm = WasmReader::new(validation_info.wasm);
wasm.move_start_to(global.init_expr).unwrap_validated();
run_const(wasm, &mut stack, ());
let value = stack.pop_value(global.ty.ty);
GlobalInst {
global: *global,
value,
}
}
})
.collect();
Store {
funcs: function_instances,
mems: memory_instances,
globals: global_instances,
data: data_sections,
}
}
}