Merge pull request #4593 from roc-lang/wasm_interp_cli

Wasm interp CLI
This commit is contained in:
Brian Carroll 2022-11-25 13:01:09 +00:00 committed by GitHub
commit 74e1bc412f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 430 additions and 507 deletions

1
Cargo.lock generated
View File

@ -4160,6 +4160,7 @@ version = "0.1.0"
dependencies = [
"bitvec 1.0.1",
"bumpalo",
"clap 3.2.20",
"roc_wasm_module",
]

View File

@ -56,7 +56,8 @@ impl Env<'_> {
/// Parse the preprocessed host binary
/// If successful, the module can be passed to build_app_binary
pub fn parse_host<'a>(arena: &'a Bump, host_bytes: &[u8]) -> Result<WasmModule<'a>, ParseError> {
WasmModule::preload(arena, host_bytes)
let require_relocatable = true;
WasmModule::preload(arena, host_bytes, require_relocatable)
}
/// Generate a Wasm module in binary form, ready to write to a file. Entry point from roc_build.

View File

@ -3,10 +3,13 @@ name = "roc_wasm_interp"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[[bin]]
name = "roc_wasm_interp"
path = "src/main.rs"
[dependencies]
roc_wasm_module = { path = "../wasm_module" }
bitvec.workspace = true
bumpalo.workspace = true
clap.workspace = true

View File

@ -14,6 +14,8 @@ pub struct CallStack<'a> {
return_addrs_and_block_depths: Vec<'a, (u32, u32)>,
/// frame offsets into the `locals`, `is_float`, and `is_64` vectors (one entry per frame)
frame_offsets: Vec<'a, u32>,
/// base size of the value stack before executing (one entry per frame)
value_stack_bases: Vec<'a, u32>,
/// binary data for local variables (one entry per local)
locals_data: Vec<'a, u64>,
/// int/float type info (one entry per local)
@ -37,6 +39,7 @@ impl<'a> CallStack<'a> {
CallStack {
return_addrs_and_block_depths: Vec::with_capacity_in(256, arena),
frame_offsets: Vec::with_capacity_in(256, arena),
value_stack_bases: Vec::with_capacity_in(256, arena),
locals_data: Vec::with_capacity_in(16 * 256, arena),
is_float: BitVec::with_capacity(256),
is_64: BitVec::with_capacity(256),
@ -70,6 +73,8 @@ impl<'a> CallStack<'a> {
self.set_local_help(i, arg);
}
self.value_stack_bases.push(value_stack.len() as u32);
// Parse local variable declarations in the function header. They're grouped by type.
let local_group_count = u32::parse((), code_bytes, pc).unwrap();
for _ in 0..local_group_count {
@ -87,6 +92,7 @@ impl<'a> CallStack<'a> {
/// On returning from a Wasm call, drop its locals and retrieve the return address
pub fn pop_frame(&mut self) -> Option<(u32, u32)> {
let frame_offset = self.frame_offsets.pop()? as usize;
self.value_stack_bases.pop()?;
self.locals_data.truncate(frame_offset);
self.is_64.truncate(frame_offset);
self.is_64.truncate(frame_offset);
@ -142,6 +148,14 @@ impl<'a> CallStack<'a> {
}
}
}
pub fn value_stack_base(&self) -> u32 {
*self.value_stack_bases.last().unwrap_or(&0)
}
pub fn is_empty(&self) -> bool {
self.is_64.is_empty()
}
}
#[cfg(test)]

View File

@ -1,4 +1,6 @@
use bumpalo::{collections::Vec, Bump};
use std::fmt::Write;
use roc_wasm_module::opcodes::OpCode;
use roc_wasm_module::parse::Parse;
use roc_wasm_module::sections::{ImportDesc, MemorySection};
@ -24,6 +26,7 @@ pub struct ExecutionState<'a> {
pub program_counter: usize,
block_depth: u32,
import_signatures: Vec<'a, u32>,
debug_string: Option<String>,
}
impl<'a> ExecutionState<'a> {
@ -40,6 +43,7 @@ impl<'a> ExecutionState<'a> {
program_counter,
block_depth: 0,
import_signatures: Vec::new_in(arena),
debug_string: None,
}
}
@ -47,10 +51,11 @@ impl<'a> ExecutionState<'a> {
arena: &'a Bump,
module: &WasmModule<'a>,
start_fn_name: &str,
) -> Result<Self, String> {
is_debug_mode: bool,
) -> Result<Self, std::string::String> {
let mem_bytes = module.memory.min_bytes().map_err(|e| {
format!(
"Error parsing Memory section at offset 0x{:x}:\n{}",
"Error parsing Memory section at offset {:#x}:\n{}",
e.offset, e.message
)
})?;
@ -67,7 +72,7 @@ impl<'a> ExecutionState<'a> {
Vec::from_iter_in(sig_iter, arena)
};
let program_counter = {
let mut program_counter = {
let mut export_iter = module.export.exports.iter();
let start_fn_index = export_iter
.find_map(|ex| {
@ -87,27 +92,55 @@ impl<'a> ExecutionState<'a> {
cursor
};
let mut value_stack = ValueStack::new(arena);
let mut call_stack = CallStack::new(arena);
call_stack.push_frame(
0, // return_addr
0, // return_block_depth
0, // n_args
&mut value_stack,
&module.code.bytes,
&mut program_counter,
);
let debug_string = if is_debug_mode {
Some(String::new())
} else {
None
};
Ok(ExecutionState {
memory: Vec::with_capacity_in(mem_bytes as usize, arena),
call_stack: CallStack::new(arena),
value_stack: ValueStack::new(arena),
call_stack,
value_stack,
globals,
program_counter,
block_depth: 0,
import_signatures,
debug_string,
})
}
fn fetch_immediate_u32(&mut self, module: &WasmModule<'a>) -> u32 {
u32::parse((), &module.code.bytes, &mut self.program_counter).unwrap()
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() {
self.program_counter = return_addr as usize;
self.block_depth = block_depth;
Action::Continue
if self.call_stack.is_empty() {
// We just popped the stack frame for the entry function. Terminate the program.
Action::Break
} else {
self.program_counter = return_addr as usize;
self.block_depth = block_depth;
Action::Continue
}
} else {
// We should never get here with real programs but maybe in tests. Terminate the program.
Action::Break
}
}
@ -115,47 +148,48 @@ impl<'a> ExecutionState<'a> {
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();
write!(debug_string, "{:?} ", op_code).unwrap();
}
let mut action = Action::Continue;
match op_code {
UNREACHABLE => {
unreachable!("WebAssembly tried to execute an `unreachable` instruction.");
unreachable!(
"WebAssembly `unreachable` instruction at file offset {:#x?}.",
file_offset
);
}
NOP => {}
BLOCK => {
self.block_depth += 1;
todo!("{:?}", op_code);
todo!("{:?} @ {:#x}", op_code, file_offset);
}
LOOP => {
self.block_depth += 1;
todo!("{:?}", op_code);
}
IF => {
todo!("{:?}", op_code);
}
ELSE => {
todo!("{:?}", op_code);
todo!("{:?} @ {:#x}", op_code, file_offset);
}
IF => todo!("{:?} @ {:#x}", op_code, file_offset),
ELSE => todo!("{:?} @ {:#x}", op_code, file_offset),
END => {
if self.block_depth == 0 {
// implicit RETURN at end of function
return self.do_return();
action = self.do_return();
} else {
self.block_depth -= 1;
}
}
BR => {
todo!("{:?}", op_code);
}
BRIF => {
todo!("{:?}", op_code);
}
BRTABLE => {
todo!("{:?}", op_code);
}
BR => todo!("{:?} @ {:#x}", op_code, file_offset),
BRIF => todo!("{:?} @ {:#x}", op_code, file_offset),
BRTABLE => todo!("{:?} @ {:#x}", op_code, file_offset),
RETURN => {
return self.do_return();
action = self.do_return();
}
CALL => {
let index = self.fetch_immediate_u32(module) as usize;
@ -185,15 +219,11 @@ impl<'a> ExecutionState<'a> {
&mut self.program_counter,
);
}
CALLINDIRECT => {
todo!("{:?}", op_code);
}
CALLINDIRECT => todo!("{:?} @ {:#x}", op_code, file_offset),
DROP => {
self.value_stack.pop();
}
SELECT => {
todo!("{:?}", op_code);
}
SELECT => todo!("{:?} @ {:#x}", op_code, file_offset),
GETLOCAL => {
let index = self.fetch_immediate_u32(module);
let value = self.call_stack.get_local(index);
@ -217,215 +247,105 @@ impl<'a> ExecutionState<'a> {
let index = self.fetch_immediate_u32(module);
self.globals[index as usize] = self.value_stack.pop();
}
I32LOAD => {
todo!("{:?}", op_code);
}
I64LOAD => {
todo!("{:?}", op_code);
}
F32LOAD => {
todo!("{:?}", op_code);
}
F64LOAD => {
todo!("{:?}", op_code);
}
I32LOAD8S => {
todo!("{:?}", op_code);
}
I32LOAD8U => {
todo!("{:?}", op_code);
}
I32LOAD16S => {
todo!("{:?}", op_code);
}
I32LOAD16U => {
todo!("{:?}", op_code);
}
I64LOAD8S => {
todo!("{:?}", op_code);
}
I64LOAD8U => {
todo!("{:?}", op_code);
}
I64LOAD16S => {
todo!("{:?}", op_code);
}
I64LOAD16U => {
todo!("{:?}", op_code);
}
I64LOAD32S => {
todo!("{:?}", op_code);
}
I64LOAD32U => {
todo!("{:?}", op_code);
}
I32STORE => {
todo!("{:?}", op_code);
}
I64STORE => {
todo!("{:?}", op_code);
}
F32STORE => {
todo!("{:?}", op_code);
}
F64STORE => {
todo!("{:?}", op_code);
}
I32STORE8 => {
todo!("{:?}", op_code);
}
I32STORE16 => {
todo!("{:?}", op_code);
}
I64STORE8 => {
todo!("{:?}", op_code);
}
I64STORE16 => {
todo!("{:?}", op_code);
}
I64STORE32 => {
todo!("{:?}", op_code);
}
CURRENTMEMORY => {
todo!("{:?}", op_code);
}
GROWMEMORY => {
todo!("{:?}", op_code);
}
I32LOAD => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LOAD => todo!("{:?} @ {:#x}", op_code, file_offset),
F32LOAD => todo!("{:?} @ {:#x}", op_code, file_offset),
F64LOAD => todo!("{:?} @ {:#x}", op_code, file_offset),
I32LOAD8S => todo!("{:?} @ {:#x}", op_code, file_offset),
I32LOAD8U => todo!("{:?} @ {:#x}", op_code, file_offset),
I32LOAD16S => todo!("{:?} @ {:#x}", op_code, file_offset),
I32LOAD16U => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LOAD8S => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LOAD8U => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LOAD16S => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LOAD16U => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LOAD32S => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LOAD32U => todo!("{:?} @ {:#x}", op_code, file_offset),
I32STORE => todo!("{:?} @ {:#x}", op_code, file_offset),
I64STORE => todo!("{:?} @ {:#x}", op_code, file_offset),
F32STORE => todo!("{:?} @ {:#x}", op_code, file_offset),
F64STORE => todo!("{:?} @ {:#x}", op_code, file_offset),
I32STORE8 => todo!("{:?} @ {:#x}", op_code, file_offset),
I32STORE16 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64STORE8 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64STORE16 => todo!("{:?} @ {:#x}", op_code, file_offset),
I64STORE32 => todo!("{:?} @ {:#x}", op_code, file_offset),
CURRENTMEMORY => todo!("{:?} @ {:#x}", op_code, file_offset),
GROWMEMORY => todo!("{:?} @ {:#x}", op_code, file_offset),
I32CONST => {
let value = i32::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
if let Some(debug_string) = self.debug_string.as_mut() {
write!(debug_string, "{}", value).unwrap();
}
self.value_stack.push(Value::I32(value));
}
I64CONST => {
let value = i64::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
if let Some(debug_string) = self.debug_string.as_mut() {
write!(debug_string, "{}", value).unwrap();
}
self.value_stack.push(Value::I64(value));
}
F32CONST => {
let mut bytes = [0; 4];
bytes.copy_from_slice(&module.code.bytes[self.program_counter..][..4]);
self.value_stack.push(Value::F32(f32::from_le_bytes(bytes)));
let value = f32::from_le_bytes(bytes);
if let Some(debug_string) = self.debug_string.as_mut() {
write!(debug_string, "{}", value).unwrap();
}
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]);
self.value_stack.push(Value::F64(f64::from_le_bytes(bytes)));
let value = f64::from_le_bytes(bytes);
if let Some(debug_string) = self.debug_string.as_mut() {
write!(debug_string, "{}", value).unwrap();
}
self.value_stack.push(Value::F64(value));
self.program_counter += 8;
}
I32EQZ => {
todo!("{:?}", op_code);
}
I32EQ => {
todo!("{:?}", op_code);
}
I32NE => {
todo!("{:?}", op_code);
}
I32LTS => {
todo!("{:?}", op_code);
}
I32LTU => {
todo!("{:?}", op_code);
}
I32GTS => {
todo!("{:?}", op_code);
}
I32GTU => {
todo!("{:?}", op_code);
}
I32LES => {
todo!("{:?}", op_code);
}
I32LEU => {
todo!("{:?}", op_code);
}
I32GES => {
todo!("{:?}", op_code);
}
I32GEU => {
todo!("{:?}", op_code);
}
I64EQZ => {
todo!("{:?}", op_code);
}
I64EQ => {
todo!("{:?}", op_code);
}
I64NE => {
todo!("{:?}", op_code);
}
I64LTS => {
todo!("{:?}", op_code);
}
I64LTU => {
todo!("{:?}", op_code);
}
I64GTS => {
todo!("{:?}", op_code);
}
I64GTU => {
todo!("{:?}", op_code);
}
I64LES => {
todo!("{:?}", op_code);
}
I64LEU => {
todo!("{:?}", op_code);
}
I64GES => {
todo!("{:?}", op_code);
}
I64GEU => {
todo!("{:?}", op_code);
}
I32EQZ => todo!("{:?} @ {:#x}", op_code, file_offset),
I32EQ => todo!("{:?} @ {:#x}", op_code, file_offset),
I32NE => todo!("{:?} @ {:#x}", op_code, file_offset),
I32LTS => todo!("{:?} @ {:#x}", op_code, file_offset),
I32LTU => todo!("{:?} @ {:#x}", op_code, file_offset),
I32GTS => todo!("{:?} @ {:#x}", op_code, file_offset),
I32GTU => todo!("{:?} @ {:#x}", op_code, file_offset),
I32LES => todo!("{:?} @ {:#x}", op_code, file_offset),
I32LEU => todo!("{:?} @ {:#x}", op_code, file_offset),
I32GES => todo!("{:?} @ {:#x}", op_code, file_offset),
I32GEU => todo!("{:?} @ {:#x}", op_code, file_offset),
I64EQZ => todo!("{:?} @ {:#x}", op_code, file_offset),
I64EQ => todo!("{:?} @ {:#x}", op_code, file_offset),
I64NE => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LTS => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LTU => todo!("{:?} @ {:#x}", op_code, file_offset),
I64GTS => todo!("{:?} @ {:#x}", op_code, file_offset),
I64GTU => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LES => todo!("{:?} @ {:#x}", op_code, file_offset),
I64LEU => todo!("{:?} @ {:#x}", op_code, file_offset),
I64GES => todo!("{:?} @ {:#x}", op_code, file_offset),
I64GEU => todo!("{:?} @ {:#x}", op_code, file_offset),
F32EQ => {
todo!("{:?}", op_code);
}
F32NE => {
todo!("{:?}", op_code);
}
F32LT => {
todo!("{:?}", op_code);
}
F32GT => {
todo!("{:?}", op_code);
}
F32LE => {
todo!("{:?}", op_code);
}
F32GE => {
todo!("{:?}", op_code);
}
F32EQ => todo!("{:?} @ {:#x}", op_code, file_offset),
F32NE => todo!("{:?} @ {:#x}", op_code, file_offset),
F32LT => todo!("{:?} @ {:#x}", op_code, file_offset),
F32GT => todo!("{:?} @ {:#x}", op_code, file_offset),
F32LE => todo!("{:?} @ {:#x}", op_code, file_offset),
F32GE => todo!("{:?} @ {:#x}", op_code, file_offset),
F64EQ => {
todo!("{:?}", op_code);
}
F64NE => {
todo!("{:?}", op_code);
}
F64LT => {
todo!("{:?}", op_code);
}
F64GT => {
todo!("{:?}", op_code);
}
F64LE => {
todo!("{:?}", op_code);
}
F64GE => {
todo!("{:?}", op_code);
}
F64EQ => todo!("{:?} @ {:#x}", op_code, file_offset),
F64NE => todo!("{:?} @ {:#x}", op_code, file_offset),
F64LT => todo!("{:?} @ {:#x}", op_code, file_offset),
F64GT => todo!("{:?} @ {:#x}", op_code, file_offset),
F64LE => todo!("{:?} @ {:#x}", op_code, file_offset),
F64GE => todo!("{:?} @ {:#x}", op_code, file_offset),
I32CLZ => {
todo!("{:?}", op_code);
}
I32CTZ => {
todo!("{:?}", op_code);
}
I32POPCNT => {
todo!("{:?}", op_code);
}
I32CLZ => todo!("{:?} @ {:#x}", op_code, file_offset),
I32CTZ => todo!("{:?} @ {:#x}", op_code, file_offset),
I32POPCNT => todo!("{:?} @ {:#x}", op_code, file_offset),
I32ADD => {
let x = self.value_stack.pop_i32();
let y = self.value_stack.pop_i32();
@ -441,259 +361,100 @@ impl<'a> ExecutionState<'a> {
let y = self.value_stack.pop_i32();
self.value_stack.push(Value::I32(y * x));
}
I32DIVS => {
todo!("{:?}", op_code);
}
I32DIVU => {
todo!("{:?}", op_code);
}
I32REMS => {
todo!("{:?}", op_code);
}
I32REMU => {
todo!("{:?}", op_code);
}
I32AND => {
todo!("{:?}", op_code);
}
I32OR => {
todo!("{:?}", op_code);
}
I32XOR => {
todo!("{:?}", op_code);
}
I32SHL => {
todo!("{:?}", op_code);
}
I32SHRS => {
todo!("{:?}", op_code);
}
I32SHRU => {
todo!("{:?}", op_code);
}
I32ROTL => {
todo!("{:?}", op_code);
}
I32ROTR => {
todo!("{:?}", op_code);
}
I32DIVS => todo!("{:?} @ {:#x}", op_code, file_offset),
I32DIVU => todo!("{:?} @ {:#x}", op_code, file_offset),
I32REMS => todo!("{:?} @ {:#x}", op_code, file_offset),
I32REMU => todo!("{:?} @ {:#x}", op_code, file_offset),
I32AND => todo!("{:?} @ {:#x}", op_code, file_offset),
I32OR => todo!("{:?} @ {:#x}", op_code, file_offset),
I32XOR => todo!("{:?} @ {:#x}", op_code, file_offset),
I32SHL => todo!("{:?} @ {:#x}", op_code, file_offset),
I32SHRS => todo!("{:?} @ {:#x}", op_code, file_offset),
I32SHRU => todo!("{:?} @ {:#x}", op_code, file_offset),
I32ROTL => todo!("{:?} @ {:#x}", op_code, file_offset),
I32ROTR => todo!("{:?} @ {:#x}", op_code, file_offset),
I64CLZ => {
todo!("{:?}", op_code);
}
I64CTZ => {
todo!("{:?}", op_code);
}
I64POPCNT => {
todo!("{:?}", op_code);
}
I64ADD => {
todo!("{:?}", op_code);
}
I64SUB => {
todo!("{:?}", op_code);
}
I64MUL => {
todo!("{:?}", op_code);
}
I64DIVS => {
todo!("{:?}", op_code);
}
I64DIVU => {
todo!("{:?}", op_code);
}
I64REMS => {
todo!("{:?}", op_code);
}
I64REMU => {
todo!("{:?}", op_code);
}
I64AND => {
todo!("{:?}", op_code);
}
I64OR => {
todo!("{:?}", op_code);
}
I64XOR => {
todo!("{:?}", op_code);
}
I64SHL => {
todo!("{:?}", op_code);
}
I64SHRS => {
todo!("{:?}", op_code);
}
I64SHRU => {
todo!("{:?}", op_code);
}
I64ROTL => {
todo!("{:?}", op_code);
}
I64ROTR => {
todo!("{:?}", op_code);
}
F32ABS => {
todo!("{:?}", op_code);
}
F32NEG => {
todo!("{:?}", op_code);
}
F32CEIL => {
todo!("{:?}", op_code);
}
F32FLOOR => {
todo!("{:?}", op_code);
}
F32TRUNC => {
todo!("{:?}", op_code);
}
F32NEAREST => {
todo!("{:?}", op_code);
}
F32SQRT => {
todo!("{:?}", op_code);
}
F32ADD => {
todo!("{:?}", op_code);
}
F32SUB => {
todo!("{:?}", op_code);
}
F32MUL => {
todo!("{:?}", op_code);
}
F32DIV => {
todo!("{:?}", op_code);
}
F32MIN => {
todo!("{:?}", op_code);
}
F32MAX => {
todo!("{:?}", op_code);
}
F32COPYSIGN => {
todo!("{:?}", op_code);
}
F64ABS => {
todo!("{:?}", op_code);
}
F64NEG => {
todo!("{:?}", op_code);
}
F64CEIL => {
todo!("{:?}", op_code);
}
F64FLOOR => {
todo!("{:?}", op_code);
}
F64TRUNC => {
todo!("{:?}", op_code);
}
F64NEAREST => {
todo!("{:?}", op_code);
}
F64SQRT => {
todo!("{:?}", op_code);
}
F64ADD => {
todo!("{:?}", op_code);
}
F64SUB => {
todo!("{:?}", op_code);
}
F64MUL => {
todo!("{:?}", op_code);
}
F64DIV => {
todo!("{:?}", op_code);
}
F64MIN => {
todo!("{:?}", op_code);
}
F64MAX => {
todo!("{:?}", op_code);
}
F64COPYSIGN => {
todo!("{:?}", op_code);
}
I64CLZ => todo!("{:?} @ {:#x}", op_code, file_offset),
I64CTZ => todo!("{:?} @ {:#x}", op_code, file_offset),
I64POPCNT => todo!("{:?} @ {:#x}", op_code, file_offset),
I64ADD => todo!("{:?} @ {:#x}", op_code, file_offset),
I64SUB => todo!("{:?} @ {:#x}", op_code, file_offset),
I64MUL => todo!("{:?} @ {:#x}", op_code, file_offset),
I64DIVS => todo!("{:?} @ {:#x}", op_code, file_offset),
I64DIVU => todo!("{:?} @ {:#x}", op_code, file_offset),
I64REMS => todo!("{:?} @ {:#x}", op_code, file_offset),
I64REMU => todo!("{:?} @ {:#x}", op_code, file_offset),
I64AND => todo!("{:?} @ {:#x}", op_code, file_offset),
I64OR => todo!("{:?} @ {:#x}", op_code, file_offset),
I64XOR => todo!("{:?} @ {:#x}", op_code, file_offset),
I64SHL => todo!("{:?} @ {:#x}", op_code, file_offset),
I64SHRS => todo!("{:?} @ {:#x}", op_code, file_offset),
I64SHRU => todo!("{:?} @ {:#x}", op_code, file_offset),
I64ROTL => todo!("{:?} @ {:#x}", op_code, file_offset),
I64ROTR => todo!("{:?} @ {:#x}", op_code, file_offset),
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 => todo!("{:?} @ {:#x}", op_code, file_offset),
F32SUB => todo!("{:?} @ {:#x}", op_code, file_offset),
F32MUL => todo!("{:?} @ {:#x}", op_code, file_offset),
F32DIV => todo!("{:?} @ {:#x}", op_code, file_offset),
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 => todo!("{:?} @ {:#x}", op_code, file_offset),
F64SUB => todo!("{:?} @ {:#x}", op_code, file_offset),
F64MUL => todo!("{:?} @ {:#x}", op_code, file_offset),
F64DIV => todo!("{:?} @ {:#x}", op_code, file_offset),
F64MIN => todo!("{:?} @ {:#x}", op_code, file_offset),
F64MAX => todo!("{:?} @ {:#x}", op_code, file_offset),
F64COPYSIGN => todo!("{:?} @ {:#x}", op_code, file_offset),
I32WRAPI64 => {
todo!("{:?}", op_code);
}
I32TRUNCSF32 => {
todo!("{:?}", op_code);
}
I32TRUNCUF32 => {
todo!("{:?}", op_code);
}
I32TRUNCSF64 => {
todo!("{:?}", op_code);
}
I32TRUNCUF64 => {
todo!("{:?}", op_code);
}
I64EXTENDSI32 => {
todo!("{:?}", op_code);
}
I64EXTENDUI32 => {
todo!("{:?}", op_code);
}
I64TRUNCSF32 => {
todo!("{:?}", op_code);
}
I64TRUNCUF32 => {
todo!("{:?}", op_code);
}
I64TRUNCSF64 => {
todo!("{:?}", op_code);
}
I64TRUNCUF64 => {
todo!("{:?}", op_code);
}
F32CONVERTSI32 => {
todo!("{:?}", op_code);
}
F32CONVERTUI32 => {
todo!("{:?}", op_code);
}
F32CONVERTSI64 => {
todo!("{:?}", op_code);
}
F32CONVERTUI64 => {
todo!("{:?}", op_code);
}
F32DEMOTEF64 => {
todo!("{:?}", op_code);
}
F64CONVERTSI32 => {
todo!("{:?}", op_code);
}
F64CONVERTUI32 => {
todo!("{:?}", op_code);
}
F64CONVERTSI64 => {
todo!("{:?}", op_code);
}
F64CONVERTUI64 => {
todo!("{:?}", op_code);
}
F64PROMOTEF32 => {
todo!("{:?}", op_code);
}
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!("{:?}", op_code);
}
I64REINTERPRETF64 => {
todo!("{:?}", op_code);
}
F32REINTERPRETI32 => {
todo!("{:?}", op_code);
}
F64REINTERPRETI64 => {
todo!("{:?}", op_code);
}
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),
}
Action::Continue
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
}
}

View File

@ -0,0 +1,89 @@
use bumpalo::Bump;
use clap::ArgAction;
use clap::{Arg, Command};
use roc_wasm_interp::Action;
use std::ffi::OsString;
use std::fs;
use std::io;
use std::process;
use roc_wasm_interp::ExecutionState;
use roc_wasm_module::WasmModule;
pub const FLAG_FUNCTION: &str = "function";
pub const FLAG_DEBUG: &str = "debug";
pub const WASM_FILE: &str = "WASM_FILE";
fn main() -> io::Result<()> {
// Define the command line arguments
let flag_function = Arg::new(FLAG_FUNCTION)
.long(FLAG_FUNCTION)
.help("Call a specific function exported from the WebAssembly module")
.default_value("_start")
.required(false);
let flag_debug = Arg::new(FLAG_DEBUG)
.long(FLAG_DEBUG)
.help("Print a log of every instruction executed, for debugging purposes.")
.action(ArgAction::SetTrue)
.required(false);
let wasm_file_to_run = Arg::new(WASM_FILE)
.help("The .wasm file to run")
.allow_invalid_utf8(true)
.required(true);
let app = Command::new("roc_wasm_interp")
.about("Run the given .wasm file")
.arg(flag_function)
.arg(flag_debug)
.arg(wasm_file_to_run);
// Parse the command line arguments
let matches = app.get_matches();
let start_fn_name = matches.get_one::<String>(FLAG_FUNCTION).unwrap();
let is_debug_mode = matches.get_flag(FLAG_DEBUG);
// Load the WebAssembly binary file
let wasm_path = matches.get_one::<OsString>(WASM_FILE).unwrap();
let module_bytes = fs::read(wasm_path)?;
// Parse the binary data
let arena = Bump::new();
let require_relocatable = false;
let module = match WasmModule::preload(&arena, &module_bytes, require_relocatable) {
Ok(m) => m,
Err(e) => {
eprintln!("I couldn't parse this WebAssembly module! There's something wrong at byte offset 0x{}.", e.offset);
eprintln!("{}", e.message);
eprintln!("If you think this could be a code generation problem in the Roc compiler, see crates/compiler/gen_wasm/README.md for debugging tips.");
process::exit(1);
}
};
// Initialise the execution state
let mut state = ExecutionState::for_module(&arena, &module, start_fn_name, is_debug_mode)
.unwrap_or_else(|e| {
eprintln!("{}", e);
process::exit(2);
});
// Run
while let Action::Continue = state.execute_next_instruction(&module) {}
// Print out return value(s), if any
match state.value_stack.len() {
0 => {}
1 => println!("{:?}", state.value_stack.pop()),
_ => println!("{:?}", &state.value_stack),
}
Ok(())
}

View File

@ -76,8 +76,8 @@ impl<'a> ValueStack<'a> {
}
pub fn peek(&self) -> Value {
let is_64 = self.is_64[self.is_64.len() - 1];
let is_float = self.is_float[self.is_float.len() - 1];
let is_64 = *self.is_64.last().unwrap();
let is_float = *self.is_float.last().unwrap();
let size = if is_64 { 8 } else { 4 };
let bytes_idx = self.bytes.len() - size;
self.get(is_64, is_float, bytes_idx)
@ -146,6 +146,36 @@ impl<'a> ValueStack<'a> {
_ => panic!("Expected F64 but value stack was empty"),
}
}
fn fmt_from_index(
&self,
f: &mut std::fmt::Formatter<'_>,
from_index: usize,
) -> std::fmt::Result {
write!(f, "[")?;
let mut bytes_index = 0;
assert_eq!(self.is_64.len(), self.is_float.len());
if from_index < self.is_64.len() {
let iter_64 = self.is_64.iter().by_vals();
let iter_float = self.is_float.iter().by_vals();
for (i, (is_64, is_float)) in iter_64.zip(iter_float).enumerate() {
if i < from_index {
continue;
}
let value = self.get(is_64, is_float, bytes_index);
bytes_index += if is_64 { 8 } else { 4 };
value.fmt(f)?;
if i < self.is_64.len() - 1 {
write!(f, ", ")?;
}
}
}
write!(f, "]")
}
pub fn get_slice<'b>(&'b self, index: usize) -> ValueStackSlice<'a, 'b> {
ValueStackSlice { stack: self, index }
}
}
fn type_from_flags(is_float: bool, is_64: bool) -> ValueType {
@ -159,20 +189,18 @@ fn type_from_flags(is_float: bool, is_64: bool) -> ValueType {
impl Debug for ValueStack<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[")?;
let mut index = 0;
assert_eq!(self.is_64.len(), self.is_float.len());
let iter_64 = self.is_64.iter().by_vals();
let iter_float = self.is_float.iter().by_vals();
for (i, (is_64, is_float)) in iter_64.zip(iter_float).enumerate() {
let value = self.get(is_64, is_float, index);
index += if is_64 { 8 } else { 4 };
value.fmt(f)?;
if i < self.is_64.len() - 1 {
write!(f, ", ")?;
}
}
write!(f, "]")
self.fmt_from_index(f, 0)
}
}
pub struct ValueStackSlice<'a, 'b> {
stack: &'b ValueStack<'a>,
index: usize,
}
impl Debug for ValueStackSlice<'_, '_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.stack.fmt_from_index(f, self.index)
}
}

View File

@ -115,7 +115,11 @@ impl<'a> WasmModule<'a> {
+ self.names.size()
}
pub fn preload(arena: &'a Bump, bytes: &[u8]) -> Result<Self, ParseError> {
pub fn preload(
arena: &'a Bump,
bytes: &[u8],
require_relocatable: bool,
) -> Result<Self, ParseError> {
let is_valid_magic_number = &bytes[0..4] == "\0asm".as_bytes();
let is_valid_version = bytes[4..8] == Self::WASM_VERSION.to_le_bytes();
if !is_valid_magic_number || !is_valid_version {
@ -155,27 +159,36 @@ impl<'a> WasmModule<'a> {
if code.bytes.is_empty() {
module_errors.push_str("Missing Code section\n");
}
if linking.symbol_table.is_empty() {
module_errors.push_str("Missing \"linking\" Custom section\n");
}
if reloc_code.entries.is_empty() {
module_errors.push_str("Missing \"reloc.CODE\" Custom section\n");
}
if global.count != 0 {
let global_err_msg =
if require_relocatable {
if linking.symbol_table.is_empty() {
module_errors.push_str("Missing \"linking\" Custom section\n");
}
if reloc_code.entries.is_empty() {
module_errors.push_str("Missing \"reloc.CODE\" Custom section\n");
}
if global.count != 0 {
let global_err_msg =
format!("All globals in a relocatable Wasm module should be imported, but found {} internally defined", global.count);
module_errors.push_str(&global_err_msg);
module_errors.push_str(&global_err_msg);
}
}
if !module_errors.is_empty() {
return Err(ParseError {
offset: 0,
message: format!("{}\n{}\n{}",
let message = if require_relocatable {
format!(
"{}\n{}\n{}",
"The host file has the wrong structure. I need a relocatable WebAssembly binary file.",
"If you're using wasm-ld, try the --relocatable option.",
module_errors,
)
});
} else {
format!(
"I wasn't able to understand this WebAssembly file.\n{}",
module_errors,
)
};
return Err(ParseError { offset: 0, message });
}
Ok(WasmModule {

View File

@ -214,7 +214,7 @@ impl<'a> Parse<RelocCtx<'a>> for RelocationSection<'a> {
fn parse(ctx: RelocCtx<'a>, bytes: &[u8], cursor: &mut usize) -> Result<Self, ParseError> {
let (arena, name) = ctx;
if *cursor > bytes.len() || bytes[*cursor] != SectionId::Custom as u8 {
if *cursor >= bytes.len() || bytes[*cursor] != SectionId::Custom as u8 {
// The section we're looking for is missing, which is the same as being empty.
return Ok(RelocationSection::new(arena, name));
}
@ -626,7 +626,7 @@ impl<'a> LinkingSection<'a> {
impl<'a> Parse<&'a Bump> for LinkingSection<'a> {
fn parse(arena: &'a Bump, bytes: &[u8], cursor: &mut usize) -> Result<Self, ParseError> {
if *cursor > bytes.len() || bytes[*cursor] != SectionId::Custom as u8 {
if *cursor >= bytes.len() || bytes[*cursor] != SectionId::Custom as u8 {
return Ok(LinkingSection::new(arena));
}
*cursor += 1;

View File

@ -739,6 +739,9 @@ impl SkipBytes for Limits {
impl Parse<()> for Limits {
fn parse(_: (), bytes: &[u8], cursor: &mut usize) -> Result<Self, ParseError> {
if *cursor >= bytes.len() {
return Ok(Limits::Min(0));
}
let variant_id = bytes[*cursor];
*cursor += 1;
@ -1299,6 +1302,7 @@ impl<'a> Serialize for ElementSection<'a> {
#[derive(Debug)]
pub struct CodeSection<'a> {
pub function_count: u32,
pub section_offset: u32,
pub bytes: Vec<'a, u8>,
/// The start of each function
pub function_offsets: Vec<'a, u32>,
@ -1310,6 +1314,7 @@ impl<'a> CodeSection<'a> {
pub fn new(arena: &'a Bump) -> Self {
CodeSection {
function_count: 0,
section_offset: 0,
bytes: Vec::new_in(arena),
function_offsets: Vec::new_in(arena),
dead_import_dummy_count: 0,
@ -1357,6 +1362,7 @@ impl<'a> CodeSection<'a> {
Ok(CodeSection {
function_count,
section_offset: section_body_start as u32,
bytes,
function_offsets,
dead_import_dummy_count: 0,
@ -1488,6 +1494,13 @@ impl<'a> DataSection<'a> {
impl<'a> Parse<&'a Bump> for DataSection<'a> {
fn parse(arena: &'a Bump, module_bytes: &[u8], cursor: &mut usize) -> Result<Self, ParseError> {
if *cursor >= module_bytes.len() {
return Ok(DataSection {
end_addr: 0,
count: 0,
bytes: Vec::<u8>::new_in(arena),
});
}
let (count, range) = parse_section(Self::ID, module_bytes, cursor)?;
let end = range.end;