wasm_interp: create a block for each function and get tests working again

This commit is contained in:
Brian Carroll 2022-12-16 23:58:53 +00:00
parent caedb9060b
commit d51beb073f
No known key found for this signature in database
GPG Key ID: 5C7B2EC4101703C0
6 changed files with 390 additions and 376 deletions

View File

@ -10,11 +10,11 @@ pub struct Frame {
pub fn_index: usize,
/// Address in the code section where this frame returns to
pub return_addr: usize,
/// Number of block scopes when this frame returns
pub return_block_depth: usize,
/// Offset in the ValueStack where the locals begin
/// Depth of the "function block" for this frame
pub function_block_depth: usize,
/// Offset in the ValueStack where the args & locals begin
pub locals_start: usize,
/// Number of locals in the frame
/// Number of args & locals in the frame
pub locals_count: usize,
}
@ -23,7 +23,7 @@ impl Frame {
Frame {
fn_index: 0,
return_addr: 0,
return_block_depth: 0,
function_block_depth: 0,
locals_start: 0,
locals_count: 0,
}
@ -32,7 +32,7 @@ impl Frame {
pub fn enter(
fn_index: usize,
return_addr: usize,
return_block_depth: usize,
function_block_depth: usize,
arg_type_bytes: &[u8],
code_bytes: &[u8],
value_stack: &mut ValueStack<'_>,
@ -60,7 +60,7 @@ impl Frame {
Frame {
fn_index,
return_addr,
return_block_depth,
function_block_depth,
locals_start,
locals_count,
}
@ -77,6 +77,7 @@ impl Frame {
}
}
#[allow(dead_code)]
pub fn write_stack_trace(
_current_frame: &Frame,
_previous_frames: &[Frame],

View File

@ -8,9 +8,9 @@ use roc_wasm_module::sections::{ImportDesc, MemorySection};
use roc_wasm_module::{ExportType, WasmModule};
use roc_wasm_module::{Value, ValueType};
use crate::frame::{write_stack_trace, Frame};
use crate::frame::Frame;
use crate::value_stack::ValueStack;
use crate::{pc_to_fn_index, Error, ImportDispatcher};
use crate::{Error, ImportDispatcher};
#[derive(Debug)]
pub enum Action {
@ -295,10 +295,13 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
) -> Result<Option<Value>, String> {
self.previous_frames.clear();
self.blocks.clear();
self.blocks.push(Block::Normal {
vstack: self.value_stack.depth(),
});
self.current_frame = Frame::enter(
fn_index,
0, // return_addr
0, // return_block_depth
self.blocks.len(),
arg_type_bytes,
&module.code.bytes,
&mut self.value_stack,
@ -313,16 +316,16 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
}
Err(e) => {
let file_offset = self.program_counter + module.code.section_offset as usize;
let mut message = e.to_string_at(file_offset);
write_stack_trace(
&self.current_frame,
&self.previous_frames,
self.module,
&self.value_stack,
self.program_counter,
&mut message,
)
.unwrap();
let message = e.to_string_at(file_offset);
// write_stack_trace(
// &self.current_frame,
// &self.previous_frames,
// self.module,
// &self.value_stack,
// self.program_counter,
// &mut message,
// )
// .unwrap();
return Err(message);
}
};
@ -348,7 +351,7 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
fn do_return(&mut self) -> Action {
let Frame {
return_addr,
return_block_depth,
function_block_depth,
locals_start,
..
} = self.current_frame;
@ -371,7 +374,7 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
self.value_stack.push(val);
}
self.blocks.truncate(return_block_depth);
self.blocks.truncate(function_block_depth - 1);
self.program_counter = return_addr;
if let Some(caller_frame) = self.previous_frames.pop() {
@ -502,7 +505,13 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
.extend(std::iter::repeat(Value::I64(0)).take(arg_type_bytes.len()));
for (i, type_byte) in arg_type_bytes.iter().copied().enumerate().rev() {
let arg = self.value_stack.pop();
assert_eq!(ValueType::from(arg), ValueType::from(type_byte));
let expected = ValueType::from(type_byte);
let actual = ValueType::from(arg);
if actual != expected {
return Err(Error::ValueStackType(expected, actual));
}
self.import_arguments[i] = arg;
}
@ -520,24 +529,26 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
}
} else {
let return_addr = self.program_counter;
let return_block_depth = self.blocks.len();
// set PC to start of function bytes
let internal_fn_index = fn_index - self.import_count;
self.program_counter = module.code.function_offsets[internal_fn_index] as usize;
// advance PC to the start of the local variable declarations
u32::parse((), &module.code.bytes, &mut self.program_counter).unwrap();
self.blocks.push(Block::Normal {
vstack: self.value_stack.depth(),
});
let function_block_depth = self.blocks.len();
let mut swap_frame = Frame::enter(
fn_index,
return_addr,
return_block_depth,
function_block_depth,
arg_type_bytes,
&module.code.bytes,
&mut self.value_stack,
&mut self.program_counter,
);
std::mem::swap(&mut swap_frame, &mut self.current_frame);
self.previous_frames.push(swap_frame);
}
@ -636,7 +647,7 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
self.do_break(0, module);
}
END => {
if self.blocks.len() == self.current_frame.return_block_depth {
if self.blocks.len() == self.current_frame.function_block_depth {
// implicit RETURN at end of function
action = self.do_return();
implicit_return = true;
@ -1646,11 +1657,13 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> {
let base = self.current_frame.locals_start + self.current_frame.locals_count;
let slice = self.value_stack.get_slice(base as usize);
eprintln!("{:06x} {:17} {:?}", file_offset, debug_string, slice);
if op_code == RETURN || (op_code == END && implicit_return) {
let fn_index = pc_to_fn_index(self.program_counter, module);
let is_return = op_code == RETURN || (op_code == END && implicit_return);
let is_program_end = self.program_counter == 0;
if is_return && !is_program_end {
eprintln!(
"returning to function {} at pc={:06x}\n",
fn_index, self.program_counter
"returning to function {} at {:06x}\n",
self.current_frame.fn_index,
self.program_counter + self.module.code.section_offset as usize
);
} else if op_code == CALL || op_code == CALLINDIRECT {
eprintln!();

View File

@ -9,7 +9,7 @@ pub use instance::Instance;
pub use wasi::{WasiDispatcher, WasiFile};
pub use roc_wasm_module::Value;
use roc_wasm_module::{ValueType, WasmModule};
use roc_wasm_module::ValueType;
pub trait ImportDispatcher {
/// Dispatch a call from WebAssembly to your own code, based on module and function name.
@ -100,22 +100,3 @@ impl From<(ValueType, ValueType)> for Error {
Error::ValueStackType(expected, actual)
}
}
// Determine which function the program counter is in
pub(crate) fn pc_to_fn_index(program_counter: usize, module: &WasmModule<'_>) -> usize {
if module.code.function_offsets.is_empty() {
0
} else {
// Find the first function that starts *after* the given program counter
let next_internal_fn_index = module
.code
.function_offsets
.iter()
.position(|o| *o as usize > program_counter)
.unwrap_or(module.code.function_offsets.len());
// Go back 1
let internal_fn_index = next_internal_fn_index - 1;
// Adjust for imports, whose indices come before the code section
module.import.imports.len() + internal_fn_index
}
}

View File

@ -11,7 +11,8 @@ mod test_mem;
use crate::{DefaultImportDispatcher, Instance};
use bumpalo::{collections::Vec, Bump};
use roc_wasm_module::{
opcodes::OpCode, Export, ExportType, SerialBuffer, Signature, Value, ValueType, WasmModule,
opcodes::OpCode, Export, ExportType, SerialBuffer, Serialize, Signature, Value, ValueType,
WasmModule,
};
pub fn default_state(arena: &Bump) -> Instance<DefaultImportDispatcher> {
@ -126,3 +127,32 @@ pub fn create_exported_function_no_locals<'a, F>(
module.code.function_count += 1;
module.code.function_offsets.push(offset as u32);
}
pub fn create_exported_function_with_locals<'a, F>(
module: &mut WasmModule<'a>,
name: &'a str,
signature: Signature<'a>,
local_types: &[(u32, ValueType)],
write_instructions: F,
) where
F: FnOnce(&mut Vec<'a, u8>),
{
let internal_fn_index = module.code.function_offsets.len();
let fn_index = module.import.function_count() + internal_fn_index;
module.export.exports.push(Export {
name,
ty: ExportType::Func,
index: fn_index as u32,
});
module.add_function_signature(signature);
let offset = module.code.bytes.encode_padded_u32(0);
let start = module.code.bytes.len();
local_types.serialize(&mut module.code.bytes);
write_instructions(&mut module.code.bytes);
let len = module.code.bytes.len() - start;
module.code.bytes.overwrite_padded_u32(offset, len as u32);
module.code.function_count += 1;
module.code.function_offsets.push(offset as u32);
}

View File

@ -1,8 +1,11 @@
#![cfg(test)]
use super::{const_value, create_exported_function_no_locals, default_state};
use crate::frame::Frame;
use crate::{instance::Action, DefaultImportDispatcher, ImportDispatcher, Instance};
use crate::tests::{
const_value, create_exported_function_no_locals, create_exported_function_with_locals,
default_state,
};
use crate::{DefaultImportDispatcher, ImportDispatcher, Instance};
use bumpalo::{collections::Vec, Bump};
use roc_wasm_module::sections::{Import, ImportDesc};
use roc_wasm_module::{
@ -119,12 +122,13 @@ fn test_if_else() {
fn test_if_else_help(condition: i32, expected: i32) {
let arena = Bump::new();
let mut module = WasmModule::new(&arena);
let buf = &mut module.code.bytes;
buf.push(1); // one group of the given type
buf.push(1); // one local in the group
buf.push(ValueType::I32 as u8);
let signature = Signature {
param_types: bumpalo::vec![in &arena],
ret_type: Some(ValueType::I32),
};
let local_types = [(1, ValueType::I32)];
create_exported_function_with_locals(&mut module, "test", signature, &local_types, |buf| {
// i32.const <condition>
buf.push(OpCode::I32CONST as u8);
buf.encode_i32(condition);
@ -161,40 +165,38 @@ fn test_if_else_help(condition: i32, expected: i32) {
// end function
buf.push(OpCode::END as u8);
});
let mut state = default_state(&arena);
let fn_index = 0;
let return_addr = 0x1234;
let return_block_depth = 0;
let arg_type_bytes = &[];
let frame = Frame::enter(
fn_index,
return_addr,
return_block_depth,
arg_type_bytes,
&buf,
&mut state.value_stack,
&mut state.program_counter,
);
state.current_frame = frame;
let is_debug_mode = false;
let mut inst = Instance::for_module(
&arena,
&module,
DefaultImportDispatcher::default(),
is_debug_mode,
)
.unwrap();
let result = inst.call_export("test", []).unwrap().unwrap();
while let Ok(Action::Continue) = state.execute_next_instruction(&module) {}
assert_eq!(state.value_stack.pop_i32(), Ok(expected));
assert_eq!(result, Value::I32(expected));
}
#[test]
fn test_br() {
let start_fn_name = "test";
let arena = Bump::new();
let mut state = default_state(&arena);
let mut module = WasmModule::new(&arena);
let buf = &mut module.code.bytes;
// (local i32)
buf.encode_u32(1);
buf.encode_u32(1);
buf.push(ValueType::I32 as u8);
let signature = Signature {
param_types: bumpalo::vec![in &arena],
ret_type: Some(ValueType::I32),
};
let local_types = [(1, ValueType::I32)];
create_exported_function_with_locals(
&mut module,
start_fn_name,
signature,
&local_types,
|buf| {
// i32.const 111
buf.push(OpCode::I32CONST as u8);
buf.encode_i32(111);
@ -257,25 +259,20 @@ fn test_br() {
buf.encode_u32(0);
buf.push(OpCode::END as u8);
let fn_index = 0;
let return_addr = 0x1234;
let return_block_depth = 0;
let arg_type_bytes = &[];
let frame = Frame::enter(
fn_index,
return_addr,
return_block_depth,
arg_type_bytes,
&buf,
&mut state.value_stack,
&mut state.program_counter,
},
);
state.current_frame = frame;
while let Ok(Action::Continue) = state.execute_next_instruction(&module) {}
let is_debug_mode = false;
let mut inst = Instance::for_module(
&arena,
&module,
DefaultImportDispatcher::default(),
is_debug_mode,
)
.unwrap();
let result = inst.call_export(start_fn_name, []).unwrap().unwrap();
assert_eq!(state.value_stack.pop(), Value::I32(111))
assert_eq!(result, Value::I32(111))
}
#[test]
@ -285,16 +282,21 @@ fn test_br_if() {
}
fn test_br_if_help(condition: i32, expected: i32) {
let start_fn_name = "test";
let arena = Bump::new();
let mut state = default_state(&arena);
let mut module = WasmModule::new(&arena);
let buf = &mut module.code.bytes;
// (local i32)
buf.encode_u32(1);
buf.encode_u32(1);
buf.push(ValueType::I32 as u8);
let signature = Signature {
param_types: bumpalo::vec![in &arena],
ret_type: Some(ValueType::I32),
};
let local_types = [(1, ValueType::I32)];
create_exported_function_with_locals(
&mut module,
start_fn_name,
signature,
&local_types,
|buf| {
// i32.const 111
buf.push(OpCode::I32CONST as u8);
buf.encode_i32(111);
@ -361,25 +363,20 @@ fn test_br_if_help(condition: i32, expected: i32) {
buf.encode_u32(0);
buf.push(OpCode::END as u8);
let fn_index = 0;
let return_addr = 0x1234;
let return_block_depth = 0;
let arg_type_bytes = &[];
let frame = Frame::enter(
fn_index,
return_addr,
return_block_depth,
arg_type_bytes,
&buf,
&mut state.value_stack,
&mut state.program_counter,
},
);
state.current_frame = frame;
while let Ok(Action::Continue) = state.execute_next_instruction(&module) {}
let is_debug_mode = true;
let mut inst = Instance::for_module(
&arena,
&module,
DefaultImportDispatcher::default(),
is_debug_mode,
)
.unwrap();
let result = inst.call_export(start_fn_name, []).unwrap().unwrap();
assert_eq!(state.value_stack.pop(), Value::I32(expected))
assert_eq!(result, Value::I32(expected))
}
#[test]
@ -390,16 +387,21 @@ fn test_br_table() {
}
fn test_br_table_help(condition: i32, expected: i32) {
let start_fn_name = "test";
let arena = Bump::new();
let mut state = default_state(&arena);
let mut module = WasmModule::new(&arena);
let buf = &mut module.code.bytes;
// (local i32)
buf.encode_u32(1);
buf.encode_u32(1);
buf.push(ValueType::I32 as u8);
let signature = Signature {
param_types: bumpalo::vec![in &arena],
ret_type: Some(ValueType::I32),
};
let local_types = [(1, ValueType::I32)];
create_exported_function_with_locals(
&mut module,
start_fn_name,
signature,
&local_types,
|buf| {
// i32.const 111
buf.push(OpCode::I32CONST as u8);
buf.encode_i32(111);
@ -469,27 +471,20 @@ fn test_br_table_help(condition: i32, expected: i32) {
buf.encode_u32(0);
buf.push(OpCode::END as u8);
println!("{:02x?}", buf);
let fn_index = 0;
let return_addr = 0x1234;
let return_block_depth = 0;
let arg_type_bytes = &[];
let frame = Frame::enter(
fn_index,
return_addr,
return_block_depth,
arg_type_bytes,
&buf,
&mut state.value_stack,
&mut state.program_counter,
},
);
state.current_frame = frame;
while let Ok(Action::Continue) = state.execute_next_instruction(&module) {}
let is_debug_mode = false;
let mut inst = Instance::for_module(
&arena,
&module,
DefaultImportDispatcher::default(),
is_debug_mode,
)
.unwrap();
let result = inst.call_export(start_fn_name, []).unwrap().unwrap();
assert_eq!(state.value_stack.pop(), Value::I32(expected))
assert_eq!(result, Value::I32(expected))
}
struct TestDispatcher {
@ -650,28 +645,22 @@ fn test_call_return_no_args() {
#[test]
fn test_call_return_with_args() {
let arena = Bump::new();
let mut state = default_state(&arena);
let mut module = WasmModule::new(&arena);
// Function 0: calculate 2+2
let func0_offset = module.code.bytes.len() as u32;
module.code.function_offsets.push(func0_offset);
module.add_function_signature(Signature {
param_types: bumpalo::vec![in &arena;],
let signature0 = Signature {
param_types: bumpalo::vec![in &arena],
ret_type: Some(ValueType::I32),
};
create_exported_function_no_locals(&mut module, "two_plus_two", signature0, |buf| {
buf.push(OpCode::I32CONST as u8);
buf.push(2);
buf.push(OpCode::I32CONST as u8);
buf.push(2);
buf.push(OpCode::CALL as u8);
buf.push(1);
buf.push(OpCode::END as u8);
});
[
0, // no locals
OpCode::I32CONST as u8,
2,
OpCode::I32CONST as u8,
2,
OpCode::CALL as u8,
1,
OpCode::END as u8,
]
.serialize(&mut module.code.bytes);
let func0_first_instruction = func0_offset + 2; // skip function length and locals length
// Function 1: add two numbers
let func1_offset = module.code.bytes.len() as u32;
@ -691,11 +680,24 @@ fn test_call_return_with_args() {
]
.serialize(&mut module.code.bytes);
state.program_counter = func0_first_instruction as usize;
let signature0 = Signature {
param_types: bumpalo::vec![in &arena; ValueType::I32, ValueType::I32],
ret_type: Some(ValueType::I32),
};
create_exported_function_no_locals(&mut module, "add", signature0, |buf| {
buf.push(OpCode::GETLOCAL as u8);
buf.push(0);
buf.push(OpCode::GETLOCAL as u8);
buf.push(1);
buf.push(OpCode::I32ADD as u8);
buf.push(OpCode::END as u8);
});
while let Ok(Action::Continue) = state.execute_next_instruction(&module) {}
let mut inst =
Instance::for_module(&arena, &module, DefaultImportDispatcher::default(), false).unwrap();
let result = inst.call_export("two_plus_two", []).unwrap().unwrap();
assert_eq!(state.value_stack.peek(), Value::I32(4));
assert_eq!(result, Value::I32(4));
}
#[test]
@ -769,11 +771,9 @@ fn test_call_indirect_help(table_index: u32, elem_index: u32) -> Value {
if false {
let mut outfile_buf = Vec::new_in(&arena);
module.serialize(&mut outfile_buf);
std::fs::write(
format!("/tmp/roc/call_indirect_{}_{}.wasm", table_index, elem_index),
outfile_buf,
)
.unwrap();
let filename = format!("/tmp/roc/call_indirect_{}_{}.wasm", table_index, elem_index);
std::fs::write(&filename, outfile_buf).unwrap();
println!("\nWrote to {}\n", filename);
}
let mut inst = Instance::for_module(
@ -798,36 +798,25 @@ fn test_select() {
fn test_select_help(first: Value, second: Value, condition: i32, expected: Value) {
let arena = Bump::new();
let mut module = WasmModule::new(&arena);
let buf = &mut module.code.bytes;
buf.push(0); // no locals
// Function 0: calculate 2+2
let signature0 = Signature {
param_types: bumpalo::vec![in &arena],
ret_type: Some(ValueType::from(expected)),
};
create_exported_function_no_locals(&mut module, "test", signature0, |buf| {
const_value(buf, first);
const_value(buf, second);
const_value(buf, Value::I32(condition));
buf.push(OpCode::SELECT as u8);
buf.push(OpCode::END as u8);
});
let mut inst = default_state(&arena);
let mut inst =
Instance::for_module(&arena, &module, DefaultImportDispatcher::default(), false).unwrap();
let result = inst.call_export("test", []).unwrap().unwrap();
let fn_index = 0;
let return_addr = 0x1234;
let return_block_depth = 0;
let arg_type_bytes = &[];
let frame = Frame::enter(
fn_index,
return_addr,
return_block_depth,
arg_type_bytes,
&buf,
&mut inst.value_stack,
&mut inst.program_counter,
);
inst.current_frame = frame;
while let Ok(Action::Continue) = inst.execute_next_instruction(&module) {}
assert_eq!(inst.value_stack.pop(), expected);
assert_eq!(result, expected);
}
#[test]

View File

@ -249,7 +249,6 @@ fn test_store<'a>(
offset: u32,
value: Value,
) -> Vec<'a, u8> {
let is_debug_mode = false;
let start_fn_name = "test";
module.memory = MemorySection::new(arena, MemorySection::PAGE_SIZE);
@ -286,6 +285,7 @@ fn test_store<'a>(
buf.append_u8(OpCode::END as u8);
});
let is_debug_mode = false;
let mut inst = Instance::for_module(
arena,
module,