diff --git a/crates/wasm_interp/src/call_stack.rs b/crates/wasm_interp/src/call_stack.rs index 5a0654a069..5888f4a573 100644 --- a/crates/wasm_interp/src/call_stack.rs +++ b/crates/wasm_interp/src/call_stack.rs @@ -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 { + 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] diff --git a/crates/wasm_interp/src/execute.rs b/crates/wasm_interp/src/execute.rs index 204f9d4de1..1f7fd4f9d4 100644 --- a/crates/wasm_interp/src/execute.rs +++ b/crates/wasm_interp/src/execute.rs @@ -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>, + /// 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, } @@ -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, diff --git a/crates/wasm_interp/tests/test_opcodes.rs b/crates/wasm_interp/tests/test_opcodes.rs index 9296730f0f..53c2bab39d 100644 --- a/crates/wasm_interp/tests/test_opcodes.rs +++ b/crates/wasm_interp/tests/test_opcodes.rs @@ -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,