mirror of
https://github.com/roc-lang/roc.git
synced 2024-09-21 15:59:20 +03:00
wasm_interp: fix implicit return for calls inside blocks
This commit is contained in:
parent
82aa126353
commit
acb871abeb
@ -11,7 +11,7 @@ use crate::ValueStack;
|
||||
#[derive(Debug)]
|
||||
pub struct CallStack<'a> {
|
||||
/// return addresses and nested block depths (one entry per frame)
|
||||
return_addrs: Vec<'a, u32>,
|
||||
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)
|
||||
@ -37,7 +37,7 @@ Not clear if this would be better! Stack access pattern is pretty cache-friendly
|
||||
impl<'a> CallStack<'a> {
|
||||
pub fn new(arena: &'a Bump) -> Self {
|
||||
CallStack {
|
||||
return_addrs: Vec::with_capacity_in(256, arena),
|
||||
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),
|
||||
@ -50,12 +50,14 @@ impl<'a> CallStack<'a> {
|
||||
pub fn push_frame(
|
||||
&mut self,
|
||||
return_addr: u32,
|
||||
return_block_depth: u32,
|
||||
arg_type_bytes: &[u8],
|
||||
value_stack: &mut ValueStack<'a>,
|
||||
code_bytes: &[u8],
|
||||
pc: &mut usize,
|
||||
) {
|
||||
self.return_addrs.push(return_addr);
|
||||
self.return_addrs_and_block_depths
|
||||
.push((return_addr, return_block_depth));
|
||||
let frame_offset = self.is_64.len();
|
||||
self.frame_offsets.push(frame_offset as u32);
|
||||
let mut total = 0;
|
||||
@ -90,13 +92,13 @@ 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> {
|
||||
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);
|
||||
self.return_addrs.pop()
|
||||
self.return_addrs_and_block_depths.pop()
|
||||
}
|
||||
|
||||
pub fn get_local(&self, local_index: u32) -> Value {
|
||||
@ -178,13 +180,13 @@ mod tests {
|
||||
|
||||
// Push a other few frames before the test frame, just to make the scenario more typical.
|
||||
[(1u32, ValueType::I32)].serialize(&mut buffer);
|
||||
call_stack.push_frame(0x11111, &[], &mut vs, &buffer, &mut cursor);
|
||||
call_stack.push_frame(0x11111, 0, &[], &mut vs, &buffer, &mut cursor);
|
||||
|
||||
[(2u32, ValueType::I32)].serialize(&mut buffer);
|
||||
call_stack.push_frame(0x22222, &[], &mut vs, &buffer, &mut cursor);
|
||||
call_stack.push_frame(0x22222, 0, &[], &mut vs, &buffer, &mut cursor);
|
||||
|
||||
[(3u32, ValueType::I32)].serialize(&mut buffer);
|
||||
call_stack.push_frame(0x33333, &[], &mut vs, &buffer, &mut cursor);
|
||||
call_stack.push_frame(0x33333, 0, &[], &mut vs, &buffer, &mut cursor);
|
||||
|
||||
// Create a test call frame with local variables of every type
|
||||
[
|
||||
@ -194,7 +196,7 @@ mod tests {
|
||||
(1u32, ValueType::F64),
|
||||
]
|
||||
.serialize(&mut buffer);
|
||||
call_stack.push_frame(RETURN_ADDR, &[], &mut vs, &buffer, &mut cursor);
|
||||
call_stack.push_frame(RETURN_ADDR, 0, &[], &mut vs, &buffer, &mut cursor);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -221,7 +223,7 @@ mod tests {
|
||||
test_get_set(&mut call_stack, 14, Value::F64(f64::MIN));
|
||||
test_get_set(&mut call_stack, 14, Value::F64(f64::MAX));
|
||||
|
||||
assert_eq!(call_stack.pop_frame(), Some(RETURN_ADDR));
|
||||
assert_eq!(call_stack.pop_frame(), Some((RETURN_ADDR, 0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -18,13 +18,23 @@ pub enum Action {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ExecutionState<'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>,
|
||||
}
|
||||
|
||||
@ -41,6 +51,7 @@ impl<'a> ExecutionState<'a> {
|
||||
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()),
|
||||
}
|
||||
@ -121,6 +132,7 @@ impl<'a> ExecutionState<'a> {
|
||||
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,
|
||||
@ -140,6 +152,7 @@ impl<'a> ExecutionState<'a> {
|
||||
globals,
|
||||
program_counter,
|
||||
block_loop_addrs: Vec::new_in(arena),
|
||||
outermost_block: 0,
|
||||
import_signatures,
|
||||
debug_string,
|
||||
})
|
||||
@ -154,12 +167,14 @@ impl<'a> ExecutionState<'a> {
|
||||
}
|
||||
|
||||
fn do_return(&mut self) -> Action {
|
||||
if let Some(return_addr) = self.call_stack.pop_frame() {
|
||||
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 {
|
||||
@ -298,7 +313,7 @@ impl<'a> ExecutionState<'a> {
|
||||
self.do_break(0, module);
|
||||
}
|
||||
END => {
|
||||
if self.block_loop_addrs.is_empty() {
|
||||
if self.block_loop_addrs.len() == self.outermost_block as usize {
|
||||
// implicit RETURN at end of function
|
||||
action = self.do_return();
|
||||
} else {
|
||||
@ -347,10 +362,14 @@ impl<'a> ExecutionState<'a> {
|
||||
let return_addr = self.program_counter as u32;
|
||||
self.program_counter = module.code.function_offsets[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,
|
||||
|
@ -92,6 +92,7 @@ fn test_loop_help(end: i32, expected: i32) {
|
||||
|
||||
let mut state = default_state(&arena);
|
||||
state.call_stack.push_frame(
|
||||
0,
|
||||
0,
|
||||
&[],
|
||||
&mut state.value_stack,
|
||||
@ -159,6 +160,7 @@ fn test_if_else_help(condition: i32, expected: i32) {
|
||||
|
||||
let mut state = default_state(&arena);
|
||||
state.call_stack.push_frame(
|
||||
0,
|
||||
0,
|
||||
&[],
|
||||
&mut state.value_stack,
|
||||
@ -247,6 +249,7 @@ fn test_br() {
|
||||
buf.push(OpCode::END as u8);
|
||||
|
||||
state.call_stack.push_frame(
|
||||
0,
|
||||
0,
|
||||
&[],
|
||||
&mut state.value_stack,
|
||||
@ -344,6 +347,7 @@ fn test_br_if_help(condition: i32, expected: i32) {
|
||||
buf.push(OpCode::END as u8);
|
||||
|
||||
state.call_stack.push_frame(
|
||||
0,
|
||||
0,
|
||||
&[],
|
||||
&mut state.value_stack,
|
||||
@ -447,6 +451,7 @@ fn test_br_table_help(condition: i32, expected: i32) {
|
||||
println!("{:02x?}", buf);
|
||||
|
||||
state.call_stack.push_frame(
|
||||
0,
|
||||
0,
|
||||
&[],
|
||||
&mut state.value_stack,
|
||||
@ -462,8 +467,10 @@ fn test_br_table_help(condition: i32, expected: i32) {
|
||||
#[test]
|
||||
fn test_call_return_no_args() {
|
||||
let arena = Bump::new();
|
||||
let mut state = default_state(&arena);
|
||||
let mut module = WasmModule::new(&arena);
|
||||
let start_fn_name = "test";
|
||||
|
||||
module.code.function_count = 2;
|
||||
|
||||
// Function 0
|
||||
let func0_offset = module.code.bytes.len() as u32;
|
||||
@ -472,14 +479,28 @@ fn test_call_return_no_args() {
|
||||
param_types: Vec::new_in(&arena),
|
||||
ret_type: Some(ValueType::I32),
|
||||
});
|
||||
module.export.append(Export {
|
||||
name: start_fn_name,
|
||||
ty: ExportType::Func,
|
||||
index: 0,
|
||||
});
|
||||
[
|
||||
0, // no locals
|
||||
1, // 1 group of locals
|
||||
1, // 1 local
|
||||
ValueType::I32 as u8,
|
||||
OpCode::BLOCK as u8, /* */
|
||||
// call from inside a block. callee's implicit return should still work correctly.
|
||||
ValueType::VOID as u8,
|
||||
OpCode::CALL as u8,
|
||||
1, // function 1
|
||||
OpCode::SETLOCAL as u8,
|
||||
0, // local 0
|
||||
OpCode::END as u8,
|
||||
OpCode::GETLOCAL as u8,
|
||||
0, // local 0
|
||||
OpCode::END as u8,
|
||||
]
|
||||
.serialize(&mut module.code.bytes);
|
||||
let func0_first_instruction = func0_offset + 2; // skip function length and locals length
|
||||
|
||||
// Function 1
|
||||
let func1_offset = module.code.bytes.len() as u32;
|
||||
@ -496,7 +517,15 @@ fn test_call_return_no_args() {
|
||||
]
|
||||
.serialize(&mut module.code.bytes);
|
||||
|
||||
state.program_counter = func0_first_instruction as usize;
|
||||
if false {
|
||||
let mut buf = Vec::new_in(&arena);
|
||||
module.serialize(&mut buf);
|
||||
let filename = "/tmp/roc/call-return.wasm";
|
||||
std::fs::write(filename, buf).unwrap();
|
||||
println!("Wrote to {}", filename);
|
||||
}
|
||||
|
||||
let mut state = ExecutionState::for_module(&arena, &module, start_fn_name, true, []).unwrap();
|
||||
|
||||
while let Action::Continue = state.execute_next_instruction(&module) {}
|
||||
|
||||
@ -581,7 +610,7 @@ fn test_set_get_local() {
|
||||
.serialize(&mut buffer);
|
||||
state
|
||||
.call_stack
|
||||
.push_frame(0x1234, &[], &mut vs, &buffer, &mut cursor);
|
||||
.push_frame(0x1234, 0, &[], &mut vs, &buffer, &mut cursor);
|
||||
|
||||
module.code.bytes.push(OpCode::I32CONST as u8);
|
||||
module.code.bytes.encode_i32(12345);
|
||||
@ -616,7 +645,7 @@ fn test_tee_get_local() {
|
||||
.serialize(&mut buffer);
|
||||
state
|
||||
.call_stack
|
||||
.push_frame(0x1234, &[], &mut vs, &buffer, &mut cursor);
|
||||
.push_frame(0x1234, 0, &[], &mut vs, &buffer, &mut cursor);
|
||||
|
||||
module.code.bytes.push(OpCode::I32CONST as u8);
|
||||
module.code.bytes.encode_i32(12345);
|
||||
@ -1204,6 +1233,7 @@ fn test_i32_compare_help(op: OpCode, x: i32, y: i32, expected: bool) {
|
||||
|
||||
let mut state = default_state(&arena);
|
||||
state.call_stack.push_frame(
|
||||
0,
|
||||
0,
|
||||
&[],
|
||||
&mut state.value_stack,
|
||||
|
Loading…
Reference in New Issue
Block a user