roc/crates/wasm_interp/src/instance.rs

1144 lines
48 KiB
Rust

use bumpalo::{collections::Vec, Bump};
use std::fmt::{self, Write};
use std::iter;
use roc_wasm_module::opcodes::OpCode;
use roc_wasm_module::parse::{Parse, SkipBytes};
use roc_wasm_module::sections::{ImportDesc, MemorySection};
use roc_wasm_module::{ExportType, WasmModule};
use roc_wasm_module::{Value, ValueType};
use crate::call_stack::CallStack;
use crate::value_stack::ValueStack;
pub enum Action {
Continue,
Break,
}
#[derive(Debug)]
pub struct Instance<'a> {
/// Contents of the WebAssembly instance's memory
pub memory: Vec<'a, u8>,
/// Metadata for every currently-active function call
pub call_stack: CallStack<'a>,
/// The WebAssembly stack machine's stack of values
pub value_stack: ValueStack<'a>,
/// Values of any global variables
pub globals: Vec<'a, Value>,
/// Index in the code section of the current instruction
pub program_counter: usize,
/// One entry per nested block. For loops, stores the address of the first instruction.
block_loop_addrs: Vec<'a, Option<u32>>,
/// Outermost block depth for the currently-executing function.
outermost_block: u32,
/// Signature indices (in the TypeSection) of all imported (non-WebAssembly) functions
import_signatures: Vec<'a, u32>,
/// temporary storage for output using the --debug option
debug_string: Option<String>,
}
impl<'a> Instance<'a> {
pub fn new<G>(arena: &'a Bump, memory_pages: u32, program_counter: usize, globals: G) -> Self
where
G: IntoIterator<Item = Value>,
{
let mem_bytes = memory_pages * MemorySection::PAGE_SIZE;
Instance {
memory: Vec::from_iter_in(iter::repeat(0).take(mem_bytes as usize), arena),
call_stack: CallStack::new(arena),
value_stack: ValueStack::new(arena),
globals: Vec::from_iter_in(globals, arena),
program_counter,
block_loop_addrs: Vec::new_in(arena),
outermost_block: 0,
import_signatures: Vec::new_in(arena),
debug_string: Some(String::new()),
}
}
pub fn for_module<'arg, A>(
arena: &'a Bump,
module: &WasmModule<'a>,
start_fn_name: &str,
is_debug_mode: bool,
arg_strings: A,
) -> Result<Self, std::string::String>
where
A: IntoIterator<Item = &'arg str>,
{
let mem_bytes = module.memory.min_bytes().map_err(|e| {
format!(
"Error parsing Memory section at offset {:#x}:\n{}",
e.offset, e.message
)
})?;
let mut memory = Vec::from_iter_in(iter::repeat(0).take(mem_bytes as usize), arena);
module.data.load_into(&mut memory)?;
let globals = module.global.initial_values(arena);
// Gather imported function signatures into a vector, for simpler lookup
let import_signatures = {
let imports_iter = module.import.imports.iter();
let sig_iter = imports_iter.filter_map(|imp| match imp.description {
ImportDesc::Func { signature_index } => Some(signature_index),
_ => None,
});
Vec::from_iter_in(sig_iter, arena)
};
let start_fn_index = {
let mut export_iter = module.export.exports.iter();
export_iter
.find_map(|ex| {
if ex.ty == ExportType::Func && ex.name == start_fn_name {
Some(ex.index)
} else {
None
}
})
.ok_or(format!(
"I couldn't find an exported function '{}' in this WebAssembly module",
start_fn_name
))?
};
let arg_type_bytes = {
let internal_fn_index = start_fn_index as usize - import_signatures.len();
let signature_index = module.function.signatures[internal_fn_index];
module.types.look_up_arg_type_bytes(signature_index)
};
let mut program_counter = {
let internal_fn_index = start_fn_index as usize - module.import.function_count();
let mut cursor = module.code.function_offsets[internal_fn_index] as usize;
let _start_fn_byte_length = u32::parse((), &module.code.bytes, &mut cursor);
cursor
};
let mut value_stack = ValueStack::new(arena);
for (value_str, type_byte) in arg_strings.into_iter().zip(arg_type_bytes.iter().copied()) {
use ValueType::*;
let value = match ValueType::from(type_byte) {
I32 => Value::I32(value_str.parse::<i32>().map_err(|e| e.to_string())?),
I64 => Value::I64(value_str.parse::<i64>().map_err(|e| e.to_string())?),
F32 => Value::F32(value_str.parse::<f32>().map_err(|e| e.to_string())?),
F64 => Value::F64(value_str.parse::<f64>().map_err(|e| e.to_string())?),
};
value_stack.push(value);
}
let mut call_stack = CallStack::new(arena);
call_stack.push_frame(
0, // return_addr
0, // return_block_depth
arg_type_bytes,
&mut value_stack,
&module.code.bytes,
&mut program_counter,
);
let debug_string = if is_debug_mode {
Some(String::new())
} else {
None
};
Ok(Instance {
memory,
call_stack,
value_stack,
globals,
program_counter,
block_loop_addrs: Vec::new_in(arena),
outermost_block: 0,
import_signatures,
debug_string,
})
}
fn fetch_immediate_u32(&mut self, module: &WasmModule<'a>) -> u32 {
let x = u32::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
if let Some(debug_string) = self.debug_string.as_mut() {
write!(debug_string, "{} ", x).unwrap();
}
x
}
fn do_return(&mut self) -> Action {
if let Some((return_addr, block_depth)) = self.call_stack.pop_frame() {
if self.call_stack.is_empty() {
// We just popped the stack frame for the entry function. Terminate the program.
Action::Break
} else {
dbg!(return_addr, block_depth);
self.program_counter = return_addr as usize;
self.outermost_block = block_depth;
Action::Continue
}
} else {
// We should never get here with real programs, but maybe in tests. Terminate the program.
Action::Break
}
}
fn get_load_address(&mut self, module: &WasmModule<'a>) -> u32 {
// Alignment is not used in the execution steps from the spec! Maybe it's just an optimization hint?
// https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions
// Also note: in the text format we can specify the useless `align=` but not the useful `offset=`!
let _alignment = self.fetch_immediate_u32(module);
let offset = self.fetch_immediate_u32(module);
let base_addr = self.value_stack.pop_u32();
base_addr + offset
}
fn get_store_addr_value(&mut self, module: &WasmModule<'a>) -> (usize, Value) {
// Alignment is not used in the execution steps from the spec! Maybe it's just an optimization hint?
// https://webassembly.github.io/spec/core/exec/instructions.html#memory-instructions
// Also note: in the text format we can specify the useless `align=` but not the useful `offset=`!
let _alignment = self.fetch_immediate_u32(module);
let offset = self.fetch_immediate_u32(module);
let value = self.value_stack.pop();
let base_addr = self.value_stack.pop_u32();
let addr = (base_addr + offset) as usize;
(addr, value)
}
fn write_debug<T: fmt::Debug>(&mut self, value: T) {
if let Some(debug_string) = self.debug_string.as_mut() {
std::write!(debug_string, "{:?} ", value).unwrap();
}
}
fn do_break(&mut self, relative_blocks_outward: u32, module: &WasmModule<'a>) {
let block_index = self.block_loop_addrs.len() - 1 - relative_blocks_outward as usize;
match self.block_loop_addrs[block_index] {
Some(addr) => {
self.block_loop_addrs.truncate(block_index + 1);
self.program_counter = addr as usize;
}
None => {
self.break_forward(relative_blocks_outward, module);
}
}
}
// Break to an outer block, going forward in the program
fn break_forward(&mut self, relative_blocks_outward: u32, module: &WasmModule<'a>) {
use OpCode::*;
let mut depth = self.block_loop_addrs.len();
let target_block_depth = depth - (relative_blocks_outward + 1) as usize;
loop {
let skipped_op = OpCode::from(module.code.bytes[self.program_counter]);
OpCode::skip_bytes(&module.code.bytes, &mut self.program_counter).unwrap();
match skipped_op {
BLOCK | LOOP | IF => {
depth += 1;
}
END => {
depth -= 1;
if depth == target_block_depth {
break;
}
}
_ => {}
}
}
self.block_loop_addrs.truncate(target_block_depth);
}
fn do_call(
&mut self,
expected_signature: Option<u32>,
fn_index: usize,
module: &WasmModule<'a>,
) {
let n_imports = self.import_signatures.len();
let signature_index: u32 = if fn_index < n_imports {
self.import_signatures[fn_index]
} else {
module.function.signatures[fn_index - n_imports]
};
if let Some(expected) = expected_signature {
assert_eq!(
expected, signature_index,
"Indirect function call failed. Expected signature {} but found {}",
expected, signature_index,
);
}
let arg_type_bytes = module.types.look_up_arg_type_bytes(signature_index);
let return_addr = self.program_counter as u32;
self.program_counter = module.code.function_offsets[fn_index] as usize;
let return_block_depth = self.outermost_block;
self.outermost_block = self.block_loop_addrs.len() as u32;
let _function_byte_length =
u32::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
self.call_stack.push_frame(
return_addr,
return_block_depth,
arg_type_bytes,
&mut self.value_stack,
&module.code.bytes,
&mut self.program_counter,
);
}
pub fn execute_next_instruction(&mut self, module: &WasmModule<'a>) -> Action {
use OpCode::*;
let file_offset = self.program_counter as u32 + module.code.section_offset;
let op_code = OpCode::from(module.code.bytes[self.program_counter]);
self.program_counter += 1;
if let Some(debug_string) = self.debug_string.as_mut() {
debug_string.clear();
self.write_debug(op_code);
}
let mut action = Action::Continue;
match op_code {
UNREACHABLE => {
unreachable!(
"WebAssembly `unreachable` instruction at file offset {:#x?}.",
file_offset
);
}
NOP => {}
BLOCK => {
self.fetch_immediate_u32(module); // blocktype (ignored)
self.block_loop_addrs.push(None);
}
LOOP => {
self.fetch_immediate_u32(module); // blocktype (ignored)
self.block_loop_addrs
.push(Some(self.program_counter as u32));
}
IF => {
self.fetch_immediate_u32(module); // blocktype (ignored)
let condition = self.value_stack.pop_i32();
self.block_loop_addrs.push(None);
if condition == 0 {
let mut depth = self.block_loop_addrs.len();
loop {
let skipped_op = OpCode::from(module.code.bytes[self.program_counter]);
OpCode::skip_bytes(&module.code.bytes, &mut self.program_counter).unwrap();
match skipped_op {
BLOCK | LOOP | IF => {
depth += 1;
}
END => {
depth -= 1;
}
ELSE => {
if depth == self.block_loop_addrs.len() {
break;
}
}
_ => {}
}
}
}
}
ELSE => {
// We only reach this point when we finish executing the "then" block of an IF statement
// (For a false condition, we would have skipped past the ELSE when we saw the IF)
// We don't want to execute the ELSE block, so we skip it, just like `br 0` would.
self.do_break(0, module);
}
END => {
if self.block_loop_addrs.len() == self.outermost_block as usize {
// implicit RETURN at end of function
action = self.do_return();
} else {
self.block_loop_addrs.pop().unwrap();
}
}
BR => {
let relative_blocks_outward = self.fetch_immediate_u32(module);
self.do_break(relative_blocks_outward, module);
}
BRIF => {
let relative_blocks_outward = self.fetch_immediate_u32(module);
let condition = self.value_stack.pop_i32();
if condition != 0 {
self.do_break(relative_blocks_outward, module);
}
}
BRTABLE => {
let selector = self.value_stack.pop_u32();
let nondefault_condition_count = self.fetch_immediate_u32(module);
let mut selected = None;
for i in 0..nondefault_condition_count {
let rel_blocks = self.fetch_immediate_u32(module);
if i == selector {
selected = Some(rel_blocks);
}
}
let fallback = self.fetch_immediate_u32(module);
let relative_blocks_outward = selected.unwrap_or(fallback);
self.do_break(relative_blocks_outward, module);
}
RETURN => {
action = self.do_return();
}
CALL => {
let fn_index = self.fetch_immediate_u32(module) as usize;
self.do_call(None, fn_index, module);
}
CALLINDIRECT => {
let table_index = self.fetch_immediate_u32(module);
let expected_signature = self.fetch_immediate_u32(module);
let element_index = self.value_stack.pop_u32();
// So far, all compilers seem to be emitting MVP-compatible code. (Rust, Zig, Roc...)
assert_eq!(
table_index, 0,
"Table index {} not supported at file offset {:#x}. This interpreter only supports Wasm MVP.",
table_index, file_offset
);
// Dereference the function pointer (look up the element index in the function table)
let fn_index = module.element.lookup(element_index).unwrap_or_else(|| {
panic!(
"Indirect function call failed. There is no function with element index {}",
element_index
)
});
self.do_call(Some(expected_signature), fn_index as usize, module);
}
DROP => {
self.value_stack.pop();
}
SELECT => {
let c = self.value_stack.pop_i32();
let val2 = self.value_stack.pop();
let val1 = self.value_stack.pop();
assert_eq!(ValueType::from(val1), ValueType::from(val2));
let result = if c != 0 { val1 } else { val2 };
self.value_stack.push(result);
}
GETLOCAL => {
let index = self.fetch_immediate_u32(module);
let value = self.call_stack.get_local(index);
self.value_stack.push(value);
}
SETLOCAL => {
let index = self.fetch_immediate_u32(module);
let value = self.value_stack.pop();
self.call_stack.set_local(index, value);
}
TEELOCAL => {
let index = self.fetch_immediate_u32(module);
let value = self.value_stack.peek();
self.call_stack.set_local(index, value);
}
GETGLOBAL => {
let index = self.fetch_immediate_u32(module);
self.value_stack.push(self.globals[index as usize]);
}
SETGLOBAL => {
let index = self.fetch_immediate_u32(module);
self.globals[index as usize] = self.value_stack.pop();
}
I32LOAD => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 4];
bytes.copy_from_slice(&self.memory[addr..][..4]);
let value = i32::from_le_bytes(bytes);
self.value_stack.push(Value::I32(value));
}
I64LOAD => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 8];
bytes.copy_from_slice(&self.memory[addr..][..8]);
let value = i64::from_le_bytes(bytes);
self.value_stack.push(Value::I64(value));
}
F32LOAD => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 4];
bytes.copy_from_slice(&self.memory[addr..][..4]);
let value = f32::from_le_bytes(bytes);
self.value_stack.push(Value::F32(value));
}
F64LOAD => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 8];
bytes.copy_from_slice(&self.memory[addr..][..8]);
let value = f64::from_le_bytes(bytes);
self.value_stack.push(Value::F64(value));
}
I32LOAD8S => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 1];
bytes.copy_from_slice(&self.memory[addr..][..1]);
let value = i8::from_le_bytes(bytes);
self.value_stack.push(Value::I32(value as i32));
}
I32LOAD8U => {
let addr = self.get_load_address(module) as usize;
let value = self.memory[addr];
self.value_stack.push(Value::I32(value as i32));
}
I32LOAD16S => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 2];
bytes.copy_from_slice(&self.memory[addr..][..2]);
let value = i16::from_le_bytes(bytes);
self.value_stack.push(Value::I32(value as i32));
}
I32LOAD16U => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 2];
bytes.copy_from_slice(&self.memory[addr..][..2]);
let value = u16::from_le_bytes(bytes);
self.value_stack.push(Value::I32(value as i32));
}
I64LOAD8S => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 1];
bytes.copy_from_slice(&self.memory[addr..][..1]);
let value = i8::from_le_bytes(bytes);
self.value_stack.push(Value::I64(value as i64));
}
I64LOAD8U => {
let addr = self.get_load_address(module) as usize;
let value = self.memory[addr];
self.value_stack.push(Value::I64(value as i64));
}
I64LOAD16S => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 2];
bytes.copy_from_slice(&self.memory[addr..][..2]);
let value = i16::from_le_bytes(bytes);
self.value_stack.push(Value::I64(value as i64));
}
I64LOAD16U => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 2];
bytes.copy_from_slice(&self.memory[addr..][..2]);
let value = u16::from_le_bytes(bytes);
self.value_stack.push(Value::I64(value as i64));
}
I64LOAD32S => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 4];
bytes.copy_from_slice(&self.memory[addr..][..4]);
let value = i32::from_le_bytes(bytes);
self.value_stack.push(Value::I64(value as i64));
}
I64LOAD32U => {
let addr = self.get_load_address(module) as usize;
let mut bytes = [0; 4];
bytes.copy_from_slice(&self.memory[addr..][..4]);
let value = u32::from_le_bytes(bytes);
self.value_stack.push(Value::I64(value as i64));
}
I32STORE => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_i32();
let target = &mut self.memory[addr..][..4];
target.copy_from_slice(&unwrapped.to_le_bytes());
}
I64STORE => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_i64();
let target = &mut self.memory[addr..][..8];
target.copy_from_slice(&unwrapped.to_le_bytes());
}
F32STORE => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_f32();
let target = &mut self.memory[addr..][..4];
target.copy_from_slice(&unwrapped.to_le_bytes());
}
F64STORE => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_f64();
let target = &mut self.memory[addr..][..8];
target.copy_from_slice(&unwrapped.to_le_bytes());
}
I32STORE8 => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_i32();
let target = &mut self.memory[addr..][..1];
target.copy_from_slice(&unwrapped.to_le_bytes()[..1]);
}
I32STORE16 => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_i32();
let target = &mut self.memory[addr..][..2];
target.copy_from_slice(&unwrapped.to_le_bytes()[..2]);
}
I64STORE8 => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_i64();
let target = &mut self.memory[addr..][..1];
target.copy_from_slice(&unwrapped.to_le_bytes()[..1]);
}
I64STORE16 => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_i64();
let target = &mut self.memory[addr..][..2];
target.copy_from_slice(&unwrapped.to_le_bytes()[..2]);
}
I64STORE32 => {
let (addr, value) = self.get_store_addr_value(module);
let unwrapped = value.unwrap_i64();
let target = &mut self.memory[addr..][..4];
target.copy_from_slice(&unwrapped.to_le_bytes()[..4]);
}
CURRENTMEMORY => {
let size = self.memory.len() as i32 / MemorySection::PAGE_SIZE as i32;
self.value_stack.push(Value::I32(size));
}
GROWMEMORY => {
let old_bytes = self.memory.len() as u32;
let old_pages = old_bytes / MemorySection::PAGE_SIZE as u32;
let grow_pages = self.value_stack.pop_u32();
let grow_bytes = grow_pages * MemorySection::PAGE_SIZE;
let new_bytes = old_bytes + grow_bytes;
let success = match module.memory.max_bytes().unwrap() {
Some(max_bytes) => new_bytes <= max_bytes,
None => true,
};
if success {
self.memory
.extend(iter::repeat(0).take(grow_bytes as usize));
self.value_stack.push(Value::I32(old_pages as i32));
} else {
self.value_stack.push(Value::I32(-1));
}
}
I32CONST => {
let value = i32::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
self.write_debug(value);
self.value_stack.push(Value::I32(value));
}
I64CONST => {
let value = i64::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
self.write_debug(value);
self.value_stack.push(Value::I64(value));
}
F32CONST => {
let mut bytes = [0; 4];
bytes.copy_from_slice(&module.code.bytes[self.program_counter..][..4]);
let value = f32::from_le_bytes(bytes);
self.write_debug(value);
self.value_stack.push(Value::F32(value));
self.program_counter += 4;
}
F64CONST => {
let mut bytes = [0; 8];
bytes.copy_from_slice(&module.code.bytes[self.program_counter..][..8]);
let value = f64::from_le_bytes(bytes);
self.write_debug(value);
self.value_stack.push(Value::F64(value));
self.program_counter += 8;
}
I32EQZ => {
let arg = self.value_stack.pop_i32();
let result: bool = arg == 0;
self.value_stack.push(Value::I32(result as i32));
}
I32EQ => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
let result: bool = arg1 == arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32NE => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
let result: bool = arg1 != arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32LTS => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
let result: bool = arg1 < arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32LTU => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
let result: bool = arg1 < arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32GTS => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
let result: bool = arg1 > arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32GTU => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
let result: bool = arg1 > arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32LES => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
let result: bool = arg1 <= arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32LEU => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
let result: bool = arg1 <= arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32GES => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
let result: bool = arg1 >= arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32GEU => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
let result: bool = arg1 >= arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64EQZ => {
let arg = self.value_stack.pop_i64();
let result: bool = arg == 0;
self.value_stack.push(Value::I32(result as i32));
}
I64EQ => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
let result: bool = arg1 == arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64NE => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
let result: bool = arg1 != arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64LTS => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
let result: bool = arg1 < arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64LTU => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
let result: bool = arg1 < arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64GTS => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
let result: bool = arg1 > arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64GTU => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
let result: bool = arg1 > arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64LES => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
let result: bool = arg1 <= arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64LEU => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
let result: bool = arg1 <= arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64GES => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
let result: bool = arg1 >= arg2;
self.value_stack.push(Value::I32(result as i32));
}
I64GEU => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
let result: bool = arg1 >= arg2;
self.value_stack.push(Value::I32(result as i32));
}
F32EQ => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
let result: bool = arg1 == arg2;
self.value_stack.push(Value::I32(result as i32));
}
F32NE => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
let result: bool = arg1 != arg2;
self.value_stack.push(Value::I32(result as i32));
}
F32LT => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
let result: bool = arg1 < arg2;
self.value_stack.push(Value::I32(result as i32));
}
F32GT => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
let result: bool = arg1 > arg2;
self.value_stack.push(Value::I32(result as i32));
}
F32LE => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
let result: bool = arg1 <= arg2;
self.value_stack.push(Value::I32(result as i32));
}
F32GE => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
let result: bool = arg1 >= arg2;
self.value_stack.push(Value::I32(result as i32));
}
F64EQ => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
let result: bool = arg1 == arg2;
self.value_stack.push(Value::I32(result as i32));
}
F64NE => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
let result: bool = arg1 != arg2;
self.value_stack.push(Value::I32(result as i32));
}
F64LT => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
let result: bool = arg1 < arg2;
self.value_stack.push(Value::I32(result as i32));
}
F64GT => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
let result: bool = arg1 > arg2;
self.value_stack.push(Value::I32(result as i32));
}
F64LE => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
let result: bool = arg1 <= arg2;
self.value_stack.push(Value::I32(result as i32));
}
F64GE => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
let result: bool = arg1 >= arg2;
self.value_stack.push(Value::I32(result as i32));
}
I32CLZ => {
let arg = self.value_stack.pop_u32();
self.value_stack.push(Value::from(arg.leading_zeros()));
}
I32CTZ => {
let arg = self.value_stack.pop_u32();
self.value_stack.push(Value::from(arg.trailing_zeros()));
}
I32POPCNT => {
let arg = self.value_stack.pop_u32();
self.value_stack.push(Value::from(arg.count_ones()));
}
I32ADD => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
self.value_stack.push(Value::from(arg1.wrapping_add(arg2)));
}
I32SUB => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
self.value_stack.push(Value::from(arg1.wrapping_sub(arg2)));
}
I32MUL => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
self.value_stack.push(Value::from(arg1.wrapping_mul(arg2)));
}
I32DIVS => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
self.value_stack.push(Value::from(arg1.wrapping_div(arg2)));
}
I32DIVU => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
self.value_stack.push(Value::from(arg1.wrapping_div(arg2)));
}
I32REMS => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
self.value_stack.push(Value::from(arg1.wrapping_rem(arg2)));
}
I32REMU => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
self.value_stack.push(Value::from(arg1.wrapping_rem(arg2)));
}
I32AND => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
self.value_stack.push(Value::from(arg1 & arg2));
}
I32OR => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
self.value_stack.push(Value::from(arg1 | arg2));
}
I32XOR => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
self.value_stack.push(Value::from(arg1 ^ arg2));
}
I32SHL => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
// Take modulo N as per the spec https://webassembly.github.io/spec/core/exec/numerics.html#op-ishl
let k = arg2 % 32;
self.value_stack.push(Value::from(arg1 << k));
}
I32SHRS => {
let arg2 = self.value_stack.pop_i32();
let arg1 = self.value_stack.pop_i32();
let k = arg2 % 32;
self.value_stack.push(Value::from(arg1 >> k));
}
I32SHRU => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
let k = arg2 % 32;
self.value_stack.push(Value::from(arg1 >> k));
}
I32ROTL => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
let k = arg2 % 32;
self.value_stack.push(Value::from(arg1.rotate_left(k)));
}
I32ROTR => {
let arg2 = self.value_stack.pop_u32();
let arg1 = self.value_stack.pop_u32();
let k = arg2 % 32;
self.value_stack.push(Value::from(arg1.rotate_right(k)));
}
I64CLZ => {
let arg = self.value_stack.pop_u64();
self.value_stack
.push(Value::from(arg.leading_zeros() as u64));
}
I64CTZ => {
let arg = self.value_stack.pop_u64();
self.value_stack
.push(Value::from(arg.trailing_zeros() as u64));
}
I64POPCNT => {
let arg = self.value_stack.pop_u64();
self.value_stack.push(Value::from(arg.count_ones() as u64));
}
I64ADD => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
self.value_stack.push(Value::from(arg1.wrapping_add(arg2)));
}
I64SUB => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
self.value_stack.push(Value::from(arg1.wrapping_sub(arg2)));
}
I64MUL => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
self.value_stack.push(Value::from(arg1.wrapping_mul(arg2)));
}
I64DIVS => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
self.value_stack.push(Value::from(arg1.wrapping_div(arg2)));
}
I64DIVU => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
self.value_stack.push(Value::from(arg1.wrapping_div(arg2)));
}
I64REMS => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
self.value_stack.push(Value::from(arg1.wrapping_rem(arg2)));
}
I64REMU => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
self.value_stack.push(Value::from(arg1.wrapping_rem(arg2)));
}
I64AND => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
self.value_stack.push(Value::from(arg1 & arg2));
}
I64OR => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
self.value_stack.push(Value::from(arg1 | arg2));
}
I64XOR => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
self.value_stack.push(Value::from(arg1 ^ arg2));
}
I64SHL => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
// Take modulo N as per the spec https://webassembly.github.io/spec/core/exec/numerics.html#op-ishl
let k = arg2 % 64;
self.value_stack.push(Value::from(arg1 << k));
}
I64SHRS => {
let arg2 = self.value_stack.pop_i64();
let arg1 = self.value_stack.pop_i64();
let k = arg2 % 64;
self.value_stack.push(Value::from(arg1 >> k));
}
I64SHRU => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
let k = arg2 % 64;
self.value_stack.push(Value::from(arg1 >> k));
}
I64ROTL => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
let k = (arg2 % 64) as u32;
self.value_stack.push(Value::from(arg1.rotate_left(k)));
}
I64ROTR => {
let arg2 = self.value_stack.pop_u64();
let arg1 = self.value_stack.pop_u64();
let k = (arg2 % 64) as u32;
self.value_stack.push(Value::from(arg1.rotate_right(k)));
}
F32ABS => todo!("{:?} @ {:#x}", op_code, file_offset),
F32NEG => todo!("{:?} @ {:#x}", op_code, file_offset),
F32CEIL => todo!("{:?} @ {:#x}", op_code, file_offset),
F32FLOOR => todo!("{:?} @ {:#x}", op_code, file_offset),
F32TRUNC => todo!("{:?} @ {:#x}", op_code, file_offset),
F32NEAREST => todo!("{:?} @ {:#x}", op_code, file_offset),
F32SQRT => todo!("{:?} @ {:#x}", op_code, file_offset),
F32ADD => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
self.value_stack.push(Value::F32(arg1 + arg2));
}
F32SUB => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
self.value_stack.push(Value::F32(arg1 - arg2));
}
F32MUL => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
self.value_stack.push(Value::F32(arg1 * arg2));
}
F32DIV => {
let arg2 = self.value_stack.pop_f32();
let arg1 = self.value_stack.pop_f32();
self.value_stack.push(Value::F32(arg1 / arg2));
}
F32MIN => todo!("{:?} @ {:#x}", op_code, file_offset),
F32MAX => todo!("{:?} @ {:#x}", op_code, file_offset),
F32COPYSIGN => todo!("{:?} @ {:#x}", op_code, file_offset),
F64ABS => todo!("{:?} @ {:#x}", op_code, file_offset),
F64NEG => todo!("{:?} @ {:#x}", op_code, file_offset),
F64CEIL => todo!("{:?} @ {:#x}", op_code, file_offset),
F64FLOOR => todo!("{:?} @ {:#x}", op_code, file_offset),
F64TRUNC => todo!("{:?} @ {:#x}", op_code, file_offset),
F64NEAREST => todo!("{:?} @ {:#x}", op_code, file_offset),
F64SQRT => todo!("{:?} @ {:#x}", op_code, file_offset),
F64ADD => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
self.value_stack.push(Value::F64(arg1 + arg2));
}
F64SUB => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
self.value_stack.push(Value::F64(arg1 - arg2));
}
F64MUL => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
self.value_stack.push(Value::F64(arg1 * arg2));
}
F64DIV => {
let arg2 = self.value_stack.pop_f64();
let arg1 = self.value_stack.pop_f64();
self.value_stack.push(Value::F64(arg1 / arg2));
}
F64MIN => todo!("{:?} @ {:#x}", op_code, file_offset),
F64MAX => todo!("{:?} @ {:#x}", op_code, file_offset),
F64COPYSIGN => todo!("{:?} @ {:#x}", op_code, file_offset),
I32WRAPI64 => todo!("{:?} @ {:#x}", op_code, file_offset),
I32TRUNCSF32 => todo!("{:?} @ {:#x}", op_code, file_offset),
I32TRUNCUF32 => todo!("{:?} @ {:#x}", op_code, file_offset),
I32TRUNCSF64 => todo!("{:?} @ {:#x}", op_code, file_offset),
I32TRUNCUF64 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64EXTENDSI32 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64EXTENDUI32 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64TRUNCSF32 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64TRUNCUF32 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64TRUNCSF64 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64TRUNCUF64 => todo!("{:?} @ {:#x}", op_code, file_offset),
F32CONVERTSI32 => todo!("{:?} @ {:#x}", op_code, file_offset),
F32CONVERTUI32 => todo!("{:?} @ {:#x}", op_code, file_offset),
F32CONVERTSI64 => todo!("{:?} @ {:#x}", op_code, file_offset),
F32CONVERTUI64 => todo!("{:?} @ {:#x}", op_code, file_offset),
F32DEMOTEF64 => todo!("{:?} @ {:#x}", op_code, file_offset),
F64CONVERTSI32 => todo!("{:?} @ {:#x}", op_code, file_offset),
F64CONVERTUI32 => todo!("{:?} @ {:#x}", op_code, file_offset),
F64CONVERTSI64 => todo!("{:?} @ {:#x}", op_code, file_offset),
F64CONVERTUI64 => todo!("{:?} @ {:#x}", op_code, file_offset),
F64PROMOTEF32 => todo!("{:?} @ {:#x}", op_code, file_offset),
I32REINTERPRETF32 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64REINTERPRETF64 => todo!("{:?} @ {:#x}", op_code, file_offset),
F32REINTERPRETI32 => todo!("{:?} @ {:#x}", op_code, file_offset),
F64REINTERPRETI64 => todo!("{:?} @ {:#x}", op_code, file_offset),
}
if let Some(debug_string) = &self.debug_string {
let base = self.call_stack.value_stack_base();
let slice = self.value_stack.get_slice(base as usize);
eprintln!("{:#07x} {:17} {:?}", file_offset, debug_string, slice);
}
action
}
}