use std::cell::RefCell;
use std::rc::Rc;
use crate::nan_preserving_float::{F32, F64};
use crate::value::{ExtendTo, Integer, LittleEndianConvert, Number, Value, WrapTo};
use crate::wasm::{
Function, InitExpr, Instruction, Module, ResizableLimits, TableType, ValueType, PAGE_SIZE,
};
use crate::Breakpoints;
pub const VALUE_STACK_LIMIT: usize = 1024 * 1024;
pub const LABEL_STACK_LIMIT: usize = 64 * 1024;
pub const FUNCTION_STACK_LIMIT: usize = 1024;
pub const MEMORY_MAX_PAGES: u32 = 65_536;
#[derive(Debug, Fail)]
pub enum InitError {
#[fail(display = "Initalizer contains global.get which requires imports (unimplemented)")]
GlobalGetUnimplemented,
#[fail(
display = "Initializer type mismatch. Expected \"{}\", found \"{}\"",
expected, found
)]
MismatchedType {
expected: ValueType,
found: ValueType,
},
#[fail(
display = "Offset expr has invalid type. Expected \"i32\", found \"{}\"",
_0
)]
OffsetInvalidType(ValueType),
}
#[derive(Clone, Debug, Fail)]
pub enum Trap {
#[fail(display = "Reached unreachable")]
ReachedUnreachable,
#[fail(display = "Unknown instruction \"{}\"", _0)]
UnknownInstruction(Instruction),
#[fail(display = "Pop from empty stack")]
PopFromEmptyStack,
#[fail(display = "Tried to access function frame but there was none")]
NoFunctionFrame,
#[fail(display = "Execution finished")]
ExecutionFinished,
#[fail(display = "Type error. Expected \"{}\", found \"{}\"", expected, found)]
TypeError {
expected: ValueType,
found: ValueType,
},
#[fail(display = "Division by zero")]
DivisionByZero,
#[fail(display = "Signed integer overflow")]
SignedIntegerOverflow,
#[fail(display = "Invalid conversion to integer")]
InvalidConversionToInt,
#[fail(display = "No table present")]
NoTable,
#[fail(display = "Indirect callee absent (no table or invalid table index)")]
IndirectCalleeAbsent,
#[fail(display = "Indirect call type mismatch")]
IndirectCallTypeMismatch,
#[fail(display = "No function with index {}", _0)]
NoFunctionWithIndex(u32),
#[fail(display = "No start function")]
NoStartFunction,
#[fail(display = "Reached breakpoint {}", _0)]
BreakpointReached(u32),
#[fail(display = "Reached watchpoint {}", _0)]
WatchpointReached(u32),
#[fail(display = "Invalid branch index")]
InvalidBranchIndex,
#[fail(display = "Out of range memory access at address {:#08x}", _0)]
MemoryAccessOutOfRange(u32),
#[fail(display = "Tried to call unsupported imported function: {}", _0)]
UnsupportedCallToImportedFunction(u32),
#[fail(display = "Value stack overflow")]
ValueStackOverflow,
#[fail(display = "Label stack overflow")]
LabelStackOverflow,
#[fail(display = "Function stack overflow")]
FunctionStackOverflow,
#[fail(display = "WASI process exited with exitcode {}", _0)]
WasiExit(u32),
}
pub type VMResult<T> = Result<T, Trap>;
#[derive(Default, Copy, Clone, PartialEq, Eq, Hash)]
pub struct CodePosition {
pub func_index: u32,
pub instr_index: u32,
}
impl CodePosition {
pub fn new(func_index: u32, instr_index: u32) -> Self {
CodePosition {
func_index,
instr_index,
}
}
}
impl std::fmt::Display for CodePosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}:{}", self.func_index, self.instr_index)
}
}
#[derive(Debug)]
pub enum Label {
Bound(u32),
Unbound,
Return,
}
pub struct FunctionFrame {
pub ret_addr: CodePosition,
pub locals: Vec<Value>,
}
pub struct Memory {
data: Vec<u8>,
limits: ResizableLimits,
}
impl Memory {
fn new() -> Memory {
Memory {
data: Vec::new(),
limits: ResizableLimits::new(0, None),
}
}
fn from_module(module: &Module) -> Result<Self, InitError> {
if let Some(memory) = module.memories().get(0) {
let limits = *memory.limits();
let mut data = vec![0; (limits.initial() * PAGE_SIZE) as usize];
for data_segment in module.data_entries() {
if data_segment.index() == 0 {
let offset = eval_init_expr(data_segment.offset())?;
let offset = match offset.to::<u32>() {
Some(val) => val as usize,
None => return Err(InitError::OffsetInvalidType(offset.value_type())),
};
let len = data_segment.value().len();
if offset + len > data.len() {
data.resize(offset + len, 0);
}
data[offset..offset + len].copy_from_slice(data_segment.value());
}
}
return Ok(Memory { data, limits });
}
Ok(Self::new())
}
pub fn page_count(&self) -> u32 {
(self.data.len() as u32 / PAGE_SIZE)
}
fn grow(&mut self, delta: u32) -> u32 {
let page_count = self.page_count();
if let Some(max) = self.limits.maximum() {
if page_count + delta > max {
return -1i32 as u32;
}
} else if page_count + delta > MEMORY_MAX_PAGES {
return -1i32 as u32;
}
self.data
.resize(((page_count + delta) * PAGE_SIZE) as usize, 0);
page_count
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn load<T: LittleEndianConvert>(&self, address: u32) -> VMResult<T> {
let size = core::mem::size_of::<T>();
let address = address as usize;
let bytes = self
.data
.get(address..address + size)
.ok_or_else(|| Trap::MemoryAccessOutOfRange((address + size) as u32))?;
Ok(T::from_little_endian(bytes))
}
pub fn store<T: LittleEndianConvert>(&mut self, address: u32, value: T) -> VMResult<()> {
let size = core::mem::size_of::<T>();
let address = address as usize;
let bytes = self
.data
.get_mut(address..address + size)
.ok_or_else(|| Trap::MemoryAccessOutOfRange((address + size) as u32))?;
value.to_little_endian(bytes);
Ok(())
}
}
#[derive(Clone)]
pub enum TableElement {
Null,
Func(u32),
}
impl Default for TableElement {
fn default() -> Self {
TableElement::Null
}
}
pub struct Table {
elements: Vec<TableElement>,
}
impl Table {
fn new(table_type: TableType) -> Self {
let elements = vec![TableElement::Null; table_type.limits().initial() as usize];
Table { elements }
}
fn get(&self, index: u32) -> TableElement {
self.elements
.get(index as usize)
.cloned()
.unwrap_or_default()
}
fn from_module(module: &Module) -> Result<Option<Table>, InitError> {
if let Some(default_table_type) = module.tables().get(0) {
let mut table = Table::new(*default_table_type);
for entry in module.element_entries() {
if entry.index() == 0 {
let offset = eval_init_expr(entry.offset())?;
let offset = match offset.to::<u32>() {
Some(val) => val as usize,
None => return Err(InitError::OffsetInvalidType(offset.value_type())),
};
for (i, val) in entry.members().iter().enumerate() {
table.elements[offset + i] = TableElement::Func(*val);
}
}
}
return Ok(Some(table));
}
Ok(None)
}
}
fn eval_init_expr(init_expr: &InitExpr) -> Result<Value, InitError> {
let val = match init_expr {
InitExpr::Const(val) => *val,
InitExpr::Global(_) => return Err(InitError::GlobalGetUnimplemented),
};
Ok(val)
}
pub struct VM {
module: Rc<Module>,
memory: Memory,
table: Option<Table>,
ip: CodePosition,
globals: Vec<Value>,
value_stack: Vec<Value>,
label_stack: Vec<Label>,
function_stack: Vec<FunctionFrame>,
trap: Option<Trap>,
breakpoints: Rc<RefCell<Breakpoints>>,
}
impl VM {
pub fn new(module: Rc<Module>, breakpoints: Rc<RefCell<Breakpoints>>) -> Result<VM, InitError> {
let mut globals = Vec::with_capacity(module.globals().len());
for global in module.globals() {
let val = eval_init_expr(global.init_expr())?;
if val.value_type() != global.value_type() {
return Err(InitError::MismatchedType {
expected: global.value_type(),
found: val.value_type(),
});
}
globals.push(val);
}
let memory = Memory::from_module(&module)?;
let table = Table::from_module(&module)?;
Ok(VM {
module,
memory,
table,
ip: CodePosition::default(),
globals,
value_stack: Vec::new(),
label_stack: Vec::new(),
function_stack: Vec::new(),
trap: None,
breakpoints,
})
}
pub fn value_stack(&self) -> &[Value] {
&self.value_stack
}
pub fn value_stack_mut(&mut self) -> &mut Vec<Value> {
&mut self.value_stack
}
pub fn function_stack(&self) -> &[FunctionFrame] {
&self.function_stack
}
pub fn label_stack(&self) -> &[Label] {
&self.label_stack
}
pub fn trap(&self) -> Option<&Trap> {
self.trap.as_ref()
}
pub fn ip(&self) -> CodePosition {
self.ip
}
pub fn globals(&self) -> &[Value] {
&self.globals
}
pub fn globals_mut(&mut self) -> &mut [Value] {
&mut self.globals
}
pub fn memory(&self) -> &Memory {
&self.memory
}
pub fn memory_mut(&mut self) -> &mut Memory {
&mut self.memory
}
pub(crate) fn push(&mut self, val: Value) -> VMResult<()> {
if self.value_stack.len() >= VALUE_STACK_LIMIT {
return Err(Trap::ValueStackOverflow);
}
self.value_stack.push(val);
Ok(())
}
pub(crate) fn pop(&mut self) -> VMResult<Value> {
self.value_stack.pop().ok_or(Trap::PopFromEmptyStack)
}
pub(crate) fn pop_as<T: Number>(&mut self) -> VMResult<T> {
let val = self.pop()?;
val.to::<T>().ok_or_else(|| Trap::TypeError {
expected: val.value_type(),
found: T::value_type(),
})
}
pub fn locals(&self) -> VMResult<&[Value]> {
if let Some(frame) = self.function_stack.last() {
return Ok(&frame.locals);
}
Err(Trap::NoFunctionFrame)
}
pub fn locals_mut(&mut self) -> VMResult<&mut [Value]> {
if let Some(frame) = self.function_stack.last_mut() {
return Ok(&mut frame.locals);
}
Err(Trap::NoFunctionFrame)
}
fn curr_func(&self) -> VMResult<&Function> {
self.module
.get_func(self.ip.func_index)
.ok_or_else(|| Trap::NoFunctionWithIndex(self.ip.func_index))
}
fn default_table(&self) -> VMResult<&Table> {
self.table.as_ref().ok_or(Trap::NoTable)
}
fn branch(&mut self, mut index: u32) -> VMResult<()> {
self.label_stack
.truncate(self.label_stack.len() - index as usize);
match self.label_stack.last().unwrap() {
Label::Bound(target) => self.ip.instr_index = *target,
Label::Unbound => {
index += 1;
loop {
let curr_code = self.curr_func()?.instructions();
match curr_code[self.ip.instr_index as usize] {
Instruction::Block(_) => index += 1,
Instruction::Loop(_) => index += 1,
Instruction::If(_) => index += 1,
Instruction::End => index -= 1,
_ => (),
}
if index == 0 {
break;
}
self.ip.instr_index += 1;
}
}
_ => return Err(Trap::InvalidBranchIndex),
}
Ok(())
}
fn branch_else(&mut self) -> VMResult<()> {
let mut index = 1;
loop {
let curr_code = self.curr_func()?.instructions();
match curr_code[self.ip.instr_index as usize] {
Instruction::Block(_) => index += 1,
Instruction::Loop(_) => index += 1,
Instruction::If(_) => index += 1,
Instruction::Else => {
if index == 1 {
self.ip.instr_index += 1;
break;
}
}
Instruction::End => index -= 1,
_ => (),
}
if index == 0 {
break;
}
self.ip.instr_index += 1;
}
Ok(())
}
fn perform_load<T: Number + LittleEndianConvert>(&mut self, offset: u32) -> VMResult<()> {
let address = self.pop_as::<u32>()? + offset;
self.push(self.memory.load::<T>(address)?.into())?;
let size = core::mem::size_of::<T>() as u32;
if let Some(break_index) = self.breakpoints.borrow().find_memory(address, size, false) {
return Err(Trap::WatchpointReached(break_index));
}
Ok(())
}
fn perform_load_extend<T: LittleEndianConvert, U: Number>(
&mut self,
offset: u32,
) -> VMResult<()>
where
T: ExtendTo<U>,
{
let address = self.pop_as::<u32>()? + offset;
let val: T = self.memory.load(address)?;
let val: U = val.extend_to();
self.push(val.into())?;
let size = core::mem::size_of::<T>() as u32;
if let Some(break_index) = self.breakpoints.borrow().find_memory(address, size, false) {
return Err(Trap::WatchpointReached(break_index));
}
Ok(())
}
fn perform_store<T: Number + LittleEndianConvert>(&mut self, offset: u32) -> VMResult<()> {
let value = self.pop_as::<T>()?;
let address = self.pop_as::<u32>()? + offset;
self.memory.store(address, value)?;
let size = core::mem::size_of::<T>() as u32;
if let Some(break_index) = self.breakpoints.borrow().find_memory(address, size, true) {
return Err(Trap::WatchpointReached(break_index));
}
Ok(())
}
fn perform_store_wrap<T: LittleEndianConvert, U: Number>(&mut self, offset: u32) -> VMResult<()>
where
U: WrapTo<T>,
{
let value: U = self.pop_as()?;
let value: T = value.wrap_to();
let address = self.pop_as::<u32>()? + offset;
self.memory.store(address, value)?;
let size = core::mem::size_of::<T>() as u32;
if let Some(break_index) = self.breakpoints.borrow().find_memory(address, size, true) {
return Err(Trap::WatchpointReached(break_index));
}
Ok(())
}
fn unop<T: Number, R: Number, F: Fn(T) -> R>(&mut self, fun: F) -> VMResult<()> {
let val: T = self.pop_as()?;
self.push(fun(val).into())?;
Ok(())
}
fn unop_try<T: Number, R: Number, F: Fn(T) -> VMResult<R>>(&mut self, fun: F) -> VMResult<()> {
let val: T = self.pop_as()?;
self.push(fun(val)?.into())?;
Ok(())
}
fn binop<T: Number, R: Number, F: Fn(T, T) -> R>(&mut self, fun: F) -> VMResult<()> {
let b: T = self.pop_as()?;
let a: T = self.pop_as()?;
self.push(fun(a, b).into())?;
Ok(())
}
fn binop_try<T: Number, R: Number, F: Fn(T, T) -> VMResult<R>>(
&mut self,
fun: F,
) -> VMResult<()> {
let b: T = self.pop_as()?;
let a: T = self.pop_as()?;
self.push(fun(a, b)?.into())?;
Ok(())
}
fn call(&mut self, index: u32) -> VMResult<()> {
let func = self
.module
.get_func(index)
.ok_or_else(|| Trap::NoFunctionWithIndex(index))?;
let params_count = func.func_type().params().len();
let mut locals = Vec::new();
for _ in 0..params_count {
locals.push(self.pop()?);
}
locals.reverse();
for local_type in self.module.get_func(index).unwrap().locals() {
locals.push(Value::default(*local_type));
}
if self.label_stack.len() >= LABEL_STACK_LIMIT {
return Err(Trap::LabelStackOverflow);
}
self.label_stack.push(Label::Return);
if self.function_stack.len() >= FUNCTION_STACK_LIMIT {
return Err(Trap::FunctionStackOverflow);
}
self.function_stack.push(FunctionFrame {
ret_addr: self.ip,
locals,
});
self.ip = CodePosition {
func_index: index,
instr_index: 0,
};
Ok(())
}
pub fn start(&mut self) -> VMResult<()> {
if let Some(start_function) = self.module.start_func() {
self.run_func_paused(start_function, &[])
} else {
Err(Trap::NoStartFunction)
}
}
fn run_func_paused(&mut self, index: u32, args: &[Value]) -> VMResult<()> {
self.function_stack.clear();
self.label_stack.clear();
self.value_stack.clear();
self.trap = None;
self.ip = CodePosition::default();
for arg in args {
self.push(*arg)?
}
self.call(index)
}
pub fn run(&mut self) -> Trap {
if let Some(start_function) = self.module.start_func() {
self.run_func(start_function, &[])
} else {
Trap::NoStartFunction
}
}
pub fn run_func(&mut self, index: u32, args: &[Value]) -> Trap {
if let Err(trap) = self.run_func_paused(index, args) {
return trap;
}
if let Some(index) = self.breakpoints.borrow().find_code(self.ip) {
return Trap::BreakpointReached(index);
}
self.continue_execution()
}
pub fn continue_execution(&mut self) -> Trap {
loop {
if let Err(trap) = self.execute_step() {
return trap;
}
}
}
pub fn execute_step_over(&mut self) -> VMResult<()> {
let curr_frame_index = self.function_stack.len();
loop {
self.execute_step()?;
if curr_frame_index >= self.function_stack.len() {
return Ok(());
}
}
}
pub fn execute_step_out(&mut self) -> VMResult<()> {
let curr_frame_index = self.function_stack.len();
loop {
self.execute_step()?;
if curr_frame_index - 1 == self.function_stack.len() {
return Ok(());
}
}
}
pub fn execute_step(&mut self) -> VMResult<()> {
if let Some(trap) = &self.trap {
return Err(trap.to_owned());
}
if let Err(trap) = self.execute_step_internal() {
match trap {
Trap::BreakpointReached(_) | Trap::WatchpointReached(_) => return Err(trap),
_ => {
self.trap = Some(trap.clone());
return Err(trap);
}
}
}
Ok(())
}
#[allow(clippy::float_cmp, clippy::redundant_closure)]
fn execute_step_internal(&mut self) -> VMResult<()> {
let func = self.module.get_func(self.ip.func_index).unwrap();
if func.is_imported() {
if let Some(wasi_func) = func.wasi_function() {
wasi_func.handle(self)?;
loop {
if let Some(Label::Return) = self.label_stack.pop() {
if !self.label_stack.is_empty() {
let frame = self.function_stack.pop().unwrap();
self.ip = frame.ret_addr;
}
break;
}
}
return Ok(());
}
return Err(Trap::UnsupportedCallToImportedFunction(self.ip.func_index));
}
let instr = func.instructions()[self.ip.instr_index as usize].clone();
self.ip.instr_index += 1;
match instr {
Instruction::Unreachable => return Err(Trap::ReachedUnreachable),
Instruction::Nop => (),
Instruction::Block(_) => self.label_stack.push(Label::Unbound),
Instruction::Loop(_) => self.label_stack.push(Label::Bound(self.ip.instr_index)),
Instruction::If(_) => {
self.label_stack.push(Label::Unbound);
if self.pop_as::<u32>()? == 0 {
self.branch_else()?;
}
}
Instruction::Else => self.branch(0)?,
Instruction::End => {
if let Some(Label::Return) = self.label_stack.pop() {
if !self.label_stack.is_empty() {
let frame = self.function_stack.pop().unwrap();
self.ip = frame.ret_addr;
}
}
}
Instruction::Br(index) => self.branch(index)?,
Instruction::BrIf(index) => {
if self.pop_as::<u32>()? != 0 {
self.branch(index)?;
}
}
Instruction::BrTable(table_data) => {
let index = self.pop_as::<u32>()?;
let depth = table_data
.table
.get(index as usize)
.unwrap_or(&table_data.default);
self.branch(*depth)?;
}
Instruction::Return => loop {
if let Some(Label::Return) = self.label_stack.pop() {
if !self.label_stack.is_empty() {
let frame = self.function_stack.pop().unwrap();
self.ip = frame.ret_addr;
}
break;
}
},
Instruction::Call(index) => self.call(index)?,
Instruction::CallIndirect(signature, _) => {
let callee = self.pop_as::<u32>()?;
let func_index = match self.default_table()?.get(callee) {
TableElement::Func(func_index) => func_index,
_ => return Err(Trap::IndirectCalleeAbsent),
};
let func = self
.module
.get_func(func_index)
.ok_or_else(|| Trap::NoFunctionWithIndex(func_index))?;
if func.func_type().type_ref() != signature {
return Err(Trap::IndirectCallTypeMismatch);
}
self.call(func_index)?;
}
Instruction::Drop => {
self.pop()?;
}
Instruction::Select => {
let cond: u32 = self.pop_as()?;
let val2 = self.pop()?;
let val1 = self.pop()?;
if cond != 0 {
self.push(val1)?;
} else {
self.push(val2)?;
}
}
Instruction::GetLocal(index) => {
let val = self.locals_mut()?[index as usize];
self.push(val)?;
}
Instruction::SetLocal(index) => {
let val = self.pop()?;
self.locals_mut()?[index as usize] = val;
}
Instruction::TeeLocal(index) => {
let val = self.value_stack.last().ok_or(Trap::PopFromEmptyStack)?;
self.locals_mut()?[index as usize] = *val;
}
Instruction::GetGlobal(index) => {
let val = self.globals[index as usize];
self.push(val)?;
if let Some(break_index) = self.breakpoints.borrow().find_global(index, false) {
return Err(Trap::WatchpointReached(break_index));
}
}
Instruction::SetGlobal(index) => {
let val = self.pop()?;
self.globals[index as usize] = val;
if let Some(break_index) = self.breakpoints.borrow().find_global(index, true) {
return Err(Trap::WatchpointReached(break_index));
}
}
Instruction::I32Load(_flag, offset) => self.perform_load::<u32>(offset)?,
Instruction::I64Load(_flag, offset) => self.perform_load::<u64>(offset)?,
Instruction::F32Load(_flag, offset) => self.perform_load::<F32>(offset)?,
Instruction::F64Load(_flag, offset) => self.perform_load::<F64>(offset)?,
Instruction::I32Load8S(_flag, offset) => self.perform_load_extend::<i8, u32>(offset)?,
Instruction::I32Load8U(_flag, offset) => self.perform_load_extend::<u8, u32>(offset)?,
Instruction::I32Load16S(_flag, offset) => {
self.perform_load_extend::<i16, u32>(offset)?
}
Instruction::I32Load16U(_flag, offset) => {
self.perform_load_extend::<u16, u32>(offset)?
}
Instruction::I64Load8S(_flag, offset) => self.perform_load_extend::<i8, u64>(offset)?,
Instruction::I64Load8U(_flag, offset) => self.perform_load_extend::<u8, u64>(offset)?,
Instruction::I64Load16S(_flag, offset) => {
self.perform_load_extend::<i16, u64>(offset)?
}
Instruction::I64Load16U(_flag, offset) => {
self.perform_load_extend::<u16, u64>(offset)?
}
Instruction::I64Load32S(_flag, offset) => {
self.perform_load_extend::<i32, u64>(offset)?
}
Instruction::I64Load32U(_flag, offset) => {
self.perform_load_extend::<u32, u64>(offset)?
}
Instruction::I32Store(_flag, offset) => self.perform_store::<u32>(offset)?,
Instruction::I64Store(_flag, offset) => self.perform_store::<u64>(offset)?,
Instruction::F32Store(_flag, offset) => self.perform_store::<F32>(offset)?,
Instruction::F64Store(_flag, offset) => self.perform_store::<F64>(offset)?,
Instruction::I32Store8(_flag, offset) => self.perform_store_wrap::<u8, u32>(offset)?,
Instruction::I32Store16(_flag, offset) => {
self.perform_store_wrap::<u16, u32>(offset)?
}
Instruction::I64Store8(_flag, offset) => self.perform_store_wrap::<u8, u64>(offset)?,
Instruction::I64Store16(_flag, offset) => {
self.perform_store_wrap::<u16, u64>(offset)?
}
Instruction::I64Store32(_flag, offset) => {
self.perform_store_wrap::<u32, u64>(offset)?
}
Instruction::CurrentMemory(_) => self.push(Value::I32(self.memory.page_count()))?,
Instruction::GrowMemory(_) => {
let delta = self.pop_as::<u32>()?;
let result = self.memory.grow(delta);
self.push(Value::I32(result))?;
}
Instruction::I32Const(val) => self.push(Value::I32(val as u32))?,
Instruction::I64Const(val) => self.push(Value::I64(val as u64))?,
Instruction::F32Const(val) => self.push(Value::F32(F32::from_bits(val)))?,
Instruction::F64Const(val) => self.push(Value::F64(F64::from_bits(val)))?,
Instruction::I32Eqz => self.unop(|x: u32| bool_val(x == 0))?,
Instruction::I32Eq => self.binop(|a: u32, b: u32| bool_val(a == b))?,
Instruction::I32Ne => self.binop(|a: u32, b: u32| bool_val(a != b))?,
Instruction::I32LtS => self.binop(|a: u32, b: u32| bool_val(a as i32 == b as i32))?,
Instruction::I32LtU => self.binop(|a: u32, b: u32| bool_val(a == b))?,
Instruction::I32GtS => self.binop(|a: u32, b: u32| bool_val(a as i32 > b as i32))?,
Instruction::I32GtU => self.binop(|a: u32, b: u32| bool_val(a > b))?,
Instruction::I32LeS => self.binop(|a: u32, b: u32| bool_val(a as i32 <= b as i32))?,
Instruction::I32LeU => self.binop(|a: u32, b: u32| bool_val(a <= b))?,
Instruction::I32GeS => self.binop(|a: u32, b: u32| bool_val(a as i32 >= b as i32))?,
Instruction::I32GeU => self.binop(|a: u32, b: u32| bool_val(a >= b))?,
Instruction::I64Eqz => self.unop(|x: u64| bool_val(x == 0))?,
Instruction::I64Eq => self.binop(|a: u64, b: u64| bool_val(a == b))?,
Instruction::I64Ne => self.binop(|a: u64, b: u64| bool_val(a != b))?,
Instruction::I64LtS => self.binop(|a: u64, b: u64| bool_val(a as i64 == b as i64))?,
Instruction::I64LtU => self.binop(|a: u64, b: u64| bool_val(a == b))?,
Instruction::I64GtS => self.binop(|a: u64, b: u64| bool_val(a as i64 > b as i64))?,
Instruction::I64GtU => self.binop(|a: u64, b: u64| bool_val(a > b))?,
Instruction::I64LeS => self.binop(|a: u64, b: u64| bool_val(a as i64 <= b as i64))?,
Instruction::I64LeU => self.binop(|a: u64, b: u64| bool_val(a <= b))?,
Instruction::I64GeS => self.binop(|a: u64, b: u64| bool_val(a as i64 >= b as i64))?,
Instruction::I64GeU => self.binop(|a: u64, b: u64| bool_val(a >= b))?,
Instruction::F32Eq => self.binop(|a: F32, b: F32| bool_val(a == b))?,
Instruction::F32Ne => self.binop(|a: F32, b: F32| bool_val(a != b))?,
Instruction::F32Lt => self.binop(|a: F32, b: F32| bool_val(a < b))?,
Instruction::F32Gt => self.binop(|a: F32, b: F32| bool_val(a > b))?,
Instruction::F32Le => self.binop(|a: F32, b: F32| bool_val(a <= b))?,
Instruction::F32Ge => self.binop(|a: F32, b: F32| bool_val(a >= b))?,
Instruction::F64Eq => self.binop(|a: F64, b: F64| bool_val(a == b))?,
Instruction::F64Ne => self.binop(|a: F64, b: F64| bool_val(a != b))?,
Instruction::F64Lt => self.binop(|a: F64, b: F64| bool_val(a < b))?,
Instruction::F64Gt => self.binop(|a: F64, b: F64| bool_val(a > b))?,
Instruction::F64Le => self.binop(|a: F64, b: F64| bool_val(a <= b))?,
Instruction::F64Ge => self.binop(|a: F64, b: F64| bool_val(a >= b))?,
Instruction::I32Clz => self.unop(|x: u32| x.leading_zeros())?,
Instruction::I32Ctz => self.unop(|x: u32| x.trailing_zeros())?,
Instruction::I32Popcnt => self.unop(|x: u32| x.count_ones())?,
Instruction::I32Add => self.binop(|a: u32, b: u32| a.wrapping_add(b))?,
Instruction::I32Sub => self.binop(|a: u32, b: u32| a.wrapping_sub(b))?,
Instruction::I32Mul => self.binop(|a: u32, b: u32| a.wrapping_mul(b))?,
Instruction::I32DivS => {
self.binop_try(|a: u32, b: u32| Ok((a as i32).div(b as i32)? as u32))?
}
Instruction::I32DivU => self.binop_try(|a: u32, b: u32| a.div(b))?,
Instruction::I32RemS => {
self.binop_try(|a: u32, b: u32| Ok((a as i32).rem(b as i32)? as u32))?
}
Instruction::I32RemU => self.binop_try(|a: u32, b: u32| a.rem(b))?,
Instruction::I32And => self.binop(|a: u32, b: u32| a & b)?,
Instruction::I32Or => self.binop(|a: u32, b: u32| a | b)?,
Instruction::I32Xor => self.binop(|a: u32, b: u32| a ^ b)?,
Instruction::I32Shl => self.binop(|a: u32, b: u32| a.wrapping_shl(b))?,
Instruction::I32ShrS => self.binop(|a: u32, b: u32| (a as i32 >> b) as u32)?,
Instruction::I32ShrU => self.binop(|a: u32, b: u32| a >> b)?,
Instruction::I32Rotl => self.binop(|a: u32, b: u32| a.rotate_left(b))?,
Instruction::I32Rotr => self.binop(|a: u32, b: u32| a.rotate_right(b))?,
Instruction::I64Clz => self.unop(|x: u64| x.leading_zeros())?,
Instruction::I64Ctz => self.unop(|x: u64| x.trailing_zeros())?,
Instruction::I64Popcnt => self.unop(|x: u64| x.count_ones())?,
Instruction::I64Add => self.binop(|a: u64, b: u64| a.wrapping_add(b))?,
Instruction::I64Sub => self.binop(|a: u64, b: u64| a.wrapping_sub(b))?,
Instruction::I64Mul => self.binop(|a: u64, b: u64| a.wrapping_mul(b))?,
Instruction::I64DivS => {
self.binop_try(|a: u64, b: u64| Ok((a as i64).div(b as i64)? as u64))?
}
Instruction::I64DivU => self.binop_try(|a: u64, b: u64| a.div(b))?,
Instruction::I64RemS => {
self.binop_try(|a: u64, b: u64| Ok((a as i64).rem(b as i64)? as u64))?
}
Instruction::I64RemU => self.binop_try(|a: u64, b: u64| a.rem(b))?,
Instruction::I64And => self.binop(|a: u64, b: u64| a & b)?,
Instruction::I64Or => self.binop(|a: u64, b: u64| a | b)?,
Instruction::I64Xor => self.binop(|a: u64, b: u64| a ^ b)?,
Instruction::I64Shl => self.binop(|a: u64, b: u64| a.wrapping_shl(b as u32))?,
Instruction::I64ShrS => self.binop(|a: u64, b: u64| (a as i64 >> b) as u64)?,
Instruction::I64ShrU => self.binop(|a: u64, b: u64| a >> b)?,
Instruction::I64Rotl => self.binop(|a: u64, b: u64| a.rotate_left(b as u32))?,
Instruction::I64Rotr => self.binop(|a: u64, b: u64| a.rotate_right(b as u32))?,
Instruction::F32Abs => self.unop(|x: F32| x.abs())?,
Instruction::F32Neg => self.unop(|x: F32| -x)?,
Instruction::F32Ceil => self.unop(|x: F32| x.ceil())?,
Instruction::F32Floor => self.unop(|x: F32| x.floor())?,
Instruction::F32Trunc => self.unop(|x: F32| x.trunc())?,
Instruction::F32Nearest => self.unop(|x: F32| x.nearest())?,
Instruction::F32Sqrt => self.unop(|x: F32| x.sqrt())?,
Instruction::F32Add => self.binop(|a: F32, b: F32| a + b)?,
Instruction::F32Sub => self.binop(|a: F32, b: F32| a - b)?,
Instruction::F32Mul => self.binop(|a: F32, b: F32| a * b)?,
Instruction::F32Div => self.binop(|a: F32, b: F32| a / b)?,
Instruction::F32Min => self.binop(|a: F32, b: F32| a.min(b))?,
Instruction::F32Max => self.binop(|a: F32, b: F32| a.max(b))?,
Instruction::F32Copysign => self.binop(|a: F32, b: F32| a.copysign(b))?,
Instruction::F64Abs => self.unop(|x: F64| x.abs())?,
Instruction::F64Neg => self.unop(|x: F64| -x)?,
Instruction::F64Ceil => self.unop(|x: F64| x.ceil())?,
Instruction::F64Floor => self.unop(|x: F64| x.floor())?,
Instruction::F64Trunc => self.unop(|x: F64| x.trunc())?,
Instruction::F64Nearest => self.unop(|x: F64| x.nearest())?,
Instruction::F64Sqrt => self.unop(|x: F64| x.sqrt())?,
Instruction::F64Add => self.binop(|a: F64, b: F64| a + b)?,
Instruction::F64Sub => self.binop(|a: F64, b: F64| a - b)?,
Instruction::F64Mul => self.binop(|a: F64, b: F64| a * b)?,
Instruction::F64Div => self.binop(|a: F64, b: F64| a / b)?,
Instruction::F64Min => self.binop(|a: F64, b: F64| a.min(b))?,
Instruction::F64Max => self.binop(|a: F64, b: F64| a.max(b))?,
Instruction::F64Copysign => self.binop(|a: F64, b: F64| a.copysign(b))?,
Instruction::I32WrapI64 => self.unop(|x: u64| x as u32)?,
Instruction::I32TruncSF32 => self.unop_try(|x: F32| {
Ok(x.trunc_to_i32().ok_or(Trap::InvalidConversionToInt)? as u32)
})?,
Instruction::I32TruncUF32 => {
self.unop_try(|x: F32| x.trunc_to_u32().ok_or(Trap::InvalidConversionToInt))?
}
Instruction::I32TruncSF64 => self.unop_try(|x: F64| {
Ok(x.trunc_to_i32().ok_or(Trap::InvalidConversionToInt)? as u32)
})?,
Instruction::I32TruncUF64 => {
self.unop_try(|x: F64| x.trunc_to_u32().ok_or(Trap::InvalidConversionToInt))?
}
Instruction::I64ExtendSI32 => self.unop(|x: u32| -> u64 { (x as i32).extend_to() })?,
Instruction::I64ExtendUI32 => self.unop(|x: u32| -> u64 { x.extend_to() })?,
Instruction::I64TruncSF32 => self.unop_try(|x: F32| {
Ok(x.trunc_to_i64().ok_or(Trap::InvalidConversionToInt)? as u64)
})?,
Instruction::I64TruncUF32 => {
self.unop_try(|x: F32| x.trunc_to_u64().ok_or(Trap::InvalidConversionToInt))?
}
Instruction::I64TruncSF64 => self.unop_try(|x: F64| {
Ok(x.trunc_to_i64().ok_or(Trap::InvalidConversionToInt)? as u64)
})?,
Instruction::I64TruncUF64 => {
self.unop_try(|x: F64| x.trunc_to_u64().ok_or(Trap::InvalidConversionToInt))?
}
Instruction::F32ConvertSI32 => self.unop(|x: u32| x as i32 as f32)?,
Instruction::F32ConvertUI32 => self.unop(|x: u32| x as f32)?,
Instruction::F32ConvertSI64 => self.unop(|x: u64| x as i64 as f32)?,
Instruction::F32ConvertUI64 => self.unop(|x: u64| x as f32)?,
Instruction::F32DemoteF64 => self.unop(|x: f64| x as f32)?,
Instruction::F64ConvertSI32 => self.unop(|x: u32| f64::from(x as i32))?,
Instruction::F64ConvertUI32 => self.unop(|x: u32| f64::from(x))?,
Instruction::F64ConvertSI64 => self.unop(|x: u64| x as i64 as f64)?,
Instruction::F64ConvertUI64 => self.unop(|x: u64| x as f64)?,
Instruction::F64PromoteF32 => self.unop(|x: f32| f64::from(x))?,
Instruction::I32ReinterpretF32 => self.unop(|x: F32| x.to_bits())?,
Instruction::I64ReinterpretF64 => self.unop(|x: F64| x.to_bits())?,
Instruction::F32ReinterpretI32 => self.unop(F32::from_bits)?,
Instruction::F64ReinterpretI64 => self.unop(F64::from_bits)?,
}
if self.label_stack.is_empty() {
return Err(Trap::ExecutionFinished);
}
if let Some(index) = self.breakpoints.borrow().find_code(self.ip) {
return Err(Trap::BreakpointReached(index));
}
Ok(())
}
}
#[allow(clippy::match_bool)]
fn bool_val(val: bool) -> u32 {
match val {
true => 1,
false => 0,
}
}