diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index 7803d08..5e7be0e 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -1,4 +1,5 @@ use alloc::{boxed::Box, format, string::ToString, vec::Vec}; +use log::info; use tinywasm_types::{BlockArgs, Export, ExternalKind, FuncType, Instruction, MemArg, ValType}; use wasmparser::{FuncValidator, ValidatorResources}; @@ -113,9 +114,11 @@ pub fn process_operators<'a>( mut validator: FuncValidator, ) -> Result> { let mut instructions = Vec::new(); - let mut last_label_pointer = Option::None; + let mut labels_ptrs = Vec::new(); for (i, op) in ops.enumerate() { + info!("op: {:?}", op); + let op = op?; validator.op(offset, &op)?; offset += 1; @@ -134,19 +137,19 @@ pub fn process_operators<'a>( Unreachable => Instruction::Unreachable, Nop => Instruction::Nop, Block { blockty } => { - last_label_pointer = Some(i); + labels_ptrs.push(instructions.len()); Instruction::Block(convert_blocktype(blockty), 0) } Loop { blockty } => { - last_label_pointer = Some(i); + labels_ptrs.push(instructions.len()); Instruction::Loop(convert_blocktype(blockty), 0) } If { blockty } => { - last_label_pointer = Some(i); + labels_ptrs.push(instructions.len()); Instruction::If(convert_blocktype(blockty), 0) } End => { - if let Some(label_pointer) = last_label_pointer { + if let Some(label_pointer) = labels_ptrs.pop() { // last_label_pointer is Some if we're ending a block match instructions[label_pointer] { Instruction::Block(_, ref mut end) @@ -154,7 +157,6 @@ pub fn process_operators<'a>( | Instruction::Else(ref mut end) | Instruction::If(_, ref mut end) => { *end = i + 1; // Set the end position to be one after the End instruction - last_label_pointer = None; } _ => { return Err(crate::ParseError::UnsupportedOperator( @@ -170,7 +172,7 @@ pub fn process_operators<'a>( } } Else => { - let Some(label_pointer) = last_label_pointer else { + let Some(label_pointer) = labels_ptrs.pop() else { return Err(crate::ParseError::UnsupportedOperator( "Expected to end an if block, but the last label was None".to_string(), )); @@ -187,7 +189,6 @@ pub fn process_operators<'a>( } } - last_label_pointer = Some(i); Instruction::Else(0) } Br { relative_depth } => Instruction::Br(relative_depth), @@ -369,6 +370,13 @@ pub fn process_operators<'a>( instructions.push(res); } + if !labels_ptrs.is_empty() { + panic!( + "last_label_pointer should be None after processing all instructions: {:?}", + labels_ptrs + ); + } + validator.finish(offset)?; Ok(instructions.into_boxed_slice()) diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index dc53106..bea97fa 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -39,8 +39,8 @@ pub enum Error { /// The stack is empty StackUnderflow, - /// The block stack is empty - BlockStackUnderflow, + /// The label stack is empty + LabelStackUnderflow, /// The call stack is empty CallStackEmpty, @@ -63,7 +63,7 @@ impl Display for Error { Self::Other(message) => write!(f, "unknown error: {}", message), Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {}", feature), Self::FuncDidNotReturn => write!(f, "function did not return"), - Self::BlockStackUnderflow => write!(f, "block stack underflow"), + Self::LabelStackUnderflow => write!(f, "label stack underflow"), Self::StackUnderflow => write!(f, "stack underflow"), Self::CallStackEmpty => write!(f, "call stack empty"), Self::InvalidStore => write!(f, "invalid store"), diff --git a/crates/tinywasm/src/runtime/executor/macros.rs b/crates/tinywasm/src/runtime/executor/macros.rs index 01a7186..91d48d3 100644 --- a/crates/tinywasm/src/runtime/executor/macros.rs +++ b/crates/tinywasm/src/runtime/executor/macros.rs @@ -31,7 +31,7 @@ macro_rules! div_instr { /// Less than signed instruction macro_rules! lts_instr { ($ty:ty, $stack:ident) => {{ - let [b, a] = $stack.values.pop_n_const::<2>()?; + let [a, b] = $stack.values.pop_n_const::<2>()?; let a: $ty = a.into(); let b: $ty = b.into(); $stack.values.push(((a < b) as i32).into()); @@ -58,9 +58,20 @@ macro_rules! eq_instr { }}; } +/// Greater or equal than signed instruction +macro_rules! ges_instr { + ($ty:ty, $stack:ident) => {{ + let [a, b] = $stack.values.pop_n_const::<2>()?; + let a: $ty = a.into(); + let b: $ty = b.into(); + $stack.values.push(((a >= b) as i32).into()); + }}; +} + pub(super) use add_instr; pub(super) use div_instr; pub(super) use eq_instr; +pub(super) use ges_instr; pub(super) use lts_instr; pub(super) use mul_instr; pub(super) use sub_instr; diff --git a/crates/tinywasm/src/runtime/executor/mod.rs b/crates/tinywasm/src/runtime/executor/mod.rs index 662b080..25c7601 100644 --- a/crates/tinywasm/src/runtime/executor/mod.rs +++ b/crates/tinywasm/src/runtime/executor/mod.rs @@ -1,10 +1,11 @@ use super::{DefaultRuntime, Stack}; use crate::{ log::debug, - runtime::{BlockFrame, BlockFrameInner, RawWasmValue}, + runtime::{BlockType, LabelFrame, RawWasmValue}, CallFrame, Error, ModuleInstance, Result, Store, }; use alloc::vec::Vec; +use log::info; use tinywasm_types::{BlockArgs, Instruction}; mod macros; @@ -64,6 +65,9 @@ enum ExecResult { Trap(crate::Trap), } +/// Run a single step of the interpreter +/// A seperate function is used so later, we can more easily implement +/// a step-by-step debugger (using generators once they're stable?) #[inline] fn exec_one( cf: &mut CallFrame, @@ -74,6 +78,9 @@ fn exec_one( module: &ModuleInstance, ) -> Result { use tinywasm_types::Instruction::*; + + info!("ptr: {} instr: {:?}", cf.instr_ptr, instr); + match instr { Nop => { /* do nothing */ } Unreachable => return Ok(ExecResult::Trap(crate::Trap::Unreachable)), // we don't need to include the call frame here because it's already on the stack @@ -117,23 +124,23 @@ fn exec_one( } Loop(args, end_offset) => { - cf.block_frames.push(BlockFrame { + cf.labels.push(LabelFrame { instr_ptr: cf.instr_ptr, end_instr_ptr: cf.instr_ptr + *end_offset, stack_ptr: stack.values.len(), args: *args, - block: BlockFrameInner::Loop, + ty: BlockType::Loop, }); stack.values.block_args(*args)?; } Block(args, end_offset) => { - cf.block_frames.push(BlockFrame { + cf.labels.push(LabelFrame { instr_ptr: cf.instr_ptr, end_instr_ptr: cf.instr_ptr + *end_offset, stack_ptr: stack.values.len(), args: *args, - block: BlockFrameInner::Block, + ty: BlockType::Block, }); stack.values.block_args(*args)?; } @@ -163,7 +170,7 @@ fn exec_one( } EndFunc => { - if cf.block_frames.len() > 0 { + if cf.labels.len() > 0 { panic!("endfunc: block frames not empty, this should have been validated by the parser"); } @@ -178,44 +185,24 @@ fn exec_one( } EndBlockFrame => { - let blocks = &mut cf.block_frames; + let blocks = &mut cf.labels; + + // remove the label from the label stack let Some(block) = blocks.pop() else { - panic!("end: no block to end, this should have been validated by the parser"); + panic!("end: no label to end, this should have been validated by the parser"); }; - debug!("end, blocks: {:?}", blocks); - debug!(" instr_ptr: {}", cf.instr_ptr); - let res: &[RawWasmValue] = match block.args { BlockArgs::Empty => &[], BlockArgs::Type(_t) => todo!(), BlockArgs::FuncType(_t) => todo!(), }; - match block.block { - BlockFrameInner::Loop => { - debug!("end(loop): continue loop"); + // trim the lable's stack from the stack + stack.values.trim(block.stack_ptr); - // remove the loop values from the stack - stack.values.trim(block.stack_ptr); - - // set the instruction pointer to the start of the loop - cf.instr_ptr = block.instr_ptr; - - // push the loop back onto the stack - blocks.push(block); - } - BlockFrameInner::Block => { - // remove the block values from the stack - stack.values.trim(block.stack_ptr); - - // push the block result values to the stack - stack.values.extend(res.iter().copied()); - } - _ => { - panic!("end: unimplemented block type end: {:?}", block.block); - } - } + // push the block result values to the stack + stack.values.extend(res.iter().copied()); } LocalGet(local_index) => { @@ -244,17 +231,16 @@ fn exec_one( F32Sub => sub_instr!(f32, stack), F64Sub => sub_instr!(f64, stack), - I32LtS => { - let [b, a] = stack.values.pop_n_const::<2>()?; - let a: i32 = a.into(); - let b: i32 = b.into(); - stack.values.push(((a < b) as i32).into()); - debug!("i32.lt_s: {} < {} = {}", a, b, a < b); - } + I32LtS => lts_instr!(i32, stack), I64LtS => lts_instr!(i64, stack), F32Lt => lts_instr!(f32, stack), F64Lt => lts_instr!(f64, stack), + I32GeS => ges_instr!(i32, stack), + I64GeS => ges_instr!(i64, stack), + F32Ge => ges_instr!(f32, stack), + F64Ge => ges_instr!(f64, stack), + I32DivS => div_instr!(i32, stack), I64DivS => div_instr!(i64, stack), F32Div => div_instr!(f32, stack), diff --git a/crates/tinywasm/src/runtime/stack.rs b/crates/tinywasm/src/runtime/stack.rs index 8e921b6..ed978bb 100644 --- a/crates/tinywasm/src/runtime/stack.rs +++ b/crates/tinywasm/src/runtime/stack.rs @@ -3,7 +3,7 @@ mod call_stack; mod value_stack; use self::{call_stack::CallStack, value_stack::ValueStack}; -pub(crate) use blocks::{BlockFrame, BlockFrameInner}; +pub(crate) use blocks::{BlockType, LabelFrame}; pub(crate) use call_stack::CallFrame; /// A WebAssembly Stack diff --git a/crates/tinywasm/src/runtime/stack/blocks.rs b/crates/tinywasm/src/runtime/stack/blocks.rs index 4979fa2..d0ce02a 100644 --- a/crates/tinywasm/src/runtime/stack/blocks.rs +++ b/crates/tinywasm/src/runtime/stack/blocks.rs @@ -3,28 +3,33 @@ use log::info; use tinywasm_types::BlockArgs; #[derive(Debug, Default, Clone)] -pub(crate) struct Blocks(Vec); +pub(crate) struct Labels(Vec); -impl Blocks { +impl Labels { pub(crate) fn len(&self) -> usize { self.0.len() } #[inline] - pub(crate) fn push(&mut self, block: BlockFrame) { + pub(crate) fn push(&mut self, block: LabelFrame) { self.0.push(block); } + #[inline] + pub(crate) fn top(&self) -> Option<&LabelFrame> { + self.0.last() + } + #[inline] /// get the block at the given index, where 0 is the top of the stack - pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&BlockFrame> { + pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&LabelFrame> { info!("get block: {}", index); info!("blocks: {:?}", self.0); self.0.get(self.0.len() - index - 1) } #[inline] - pub(crate) fn pop(&mut self) -> Option { + pub(crate) fn pop(&mut self) -> Option { self.0.pop() } @@ -36,7 +41,7 @@ impl Blocks { } #[derive(Debug, Clone)] -pub(crate) struct BlockFrame { +pub(crate) struct LabelFrame { // position of the instruction pointer when the block was entered pub(crate) instr_ptr: usize, // position of the end instruction of the block @@ -45,12 +50,12 @@ pub(crate) struct BlockFrame { // position of the stack pointer when the block was entered pub(crate) stack_ptr: usize, pub(crate) args: BlockArgs, - pub(crate) block: BlockFrameInner, + pub(crate) ty: BlockType, } #[derive(Debug, Copy, Clone)] #[allow(dead_code)] -pub(crate) enum BlockFrameInner { +pub(crate) enum BlockType { Loop, If, Else, diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index 0fc1d12..3005953 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -1,9 +1,9 @@ -use crate::{runtime::RawWasmValue, Error, Result}; +use crate::{runtime::RawWasmValue, BlockType, Error, Result}; use alloc::{boxed::Box, vec::Vec}; -use log::info; +use log::{debug, info}; use tinywasm_types::{ValType, WasmValue}; -use super::blocks::Blocks; +use super::blocks::Labels; // minimum call stack size const CALL_STACK_SIZE: usize = 1024; @@ -73,7 +73,7 @@ pub(crate) struct CallFrame { pub(crate) instr_ptr: usize, pub(crate) func_ptr: usize, - pub(crate) block_frames: Blocks, + pub(crate) labels: Labels, pub(crate) locals: Box<[RawWasmValue]>, pub(crate) local_count: usize, } @@ -82,18 +82,42 @@ impl CallFrame { /// Break to a block at the given index (relative to the current frame) #[inline] pub(crate) fn break_to(&mut self, break_to_relative: u32, value_stack: &mut super::ValueStack) -> Result<()> { - info!("we're in block_index: {}", self.block_frames.len()); - info!("block_frames: {:?}", self.block_frames); - info!("break_to_relative: {}", break_to_relative); - - let block_frame = self - .block_frames + let current_label = self.labels.top().ok_or(Error::LabelStackUnderflow)?; + let break_to = self + .labels .get_relative_to_top(break_to_relative as usize) - .ok_or(Error::BlockStackUnderflow)?; + .ok_or(Error::LabelStackUnderflow)?; + + info!("current label: {:?}", current_label); + info!("we're breaking to: {:?} ?", break_to); + + // instr_ptr points to the label instruction, but the next step + // will increment it by 1 since we're changing the "current" instr_ptr + match break_to.ty { + BlockType::Loop => { + // this is a loop, so we want to jump back to the start of the loop + self.instr_ptr = break_to.instr_ptr; + value_stack.trim(break_to.stack_ptr); + + // we also want to trim the label stack to the loop (but not including the loop) + self.labels.trim(self.labels.len() - break_to_relative as usize); + } + BlockType::Block => { + debug!("current instr_ptr: {}", self.instr_ptr); - info!("so we're breaking to: {:?} ?", block_frame); + // this is a block, so we want to jump to the end of the block + self.instr_ptr = break_to.end_instr_ptr; + value_stack.trim(break_to.stack_ptr); - todo!("break based on the type of the block we're breaking to"); + // we also want to trim the label stack, including the block + self.labels.trim(self.labels.len() - break_to_relative as usize + 1); + + debug!("break_to.end_instr_ptr: {}", self.instr_ptr); + + panic!() + } + _ => unimplemented!("break to block type: {:?}", current_label.ty), + } // self.instr_ptr = block_frame.instr_ptr; // value_stack.trim(block_frame.stack_ptr); @@ -121,7 +145,7 @@ impl CallFrame { func_ptr, local_count: locals.len(), locals: locals.into_boxed_slice(), - block_frames: Blocks::default(), + labels: Labels::default(), } } diff --git a/examples/wasm/loop.wat b/examples/wasm/loop.wat index 6224ea0..0dcd191 100644 --- a/examples/wasm/loop.wat +++ b/examples/wasm/loop.wat @@ -1,43 +1,3 @@ -;; (module -;; (func $test (export "test") (result i32) -;; (local i32) ;; Local 0: Counter for the outer loop -;; (local i32) ;; Local 1: Counter for the inner loop -;; (local i32) ;; Local 2: Result variable - -;; ;; Initialize variables -;; (local.set 0 (i32.const 0)) ;; Initialize outer loop counter -;; (local.set 1 (i32.const 0)) ;; Initialize inner loop counter -;; (local.set 2 (i32.const 0)) ;; Initialize result variable - -;; (block $outer ;; Outer loop label -;; (loop $outer_loop -;; (local.set 1 (i32.const 5)) ;; Reset inner loop counter for each iteration of the outer loop - -;; (block $inner ;; Inner loop label -;; (loop $inner_loop -;; (br_if $inner (i32.eqz (local.get 1))) ;; Break to $inner if inner loop counter is zero - -;; ;; Computation: Adding product of counters to the result -;; (local.set 2 (i32.add (local.get 2) (i32.mul (local.get 0) (local.get 1)))) - -;; ;; Decrement inner loop counter -;; (local.set 1 (i32.sub (local.get 1) (i32.const 1))) -;; ) -;; ) - -;; ;; Increment outer loop counter -;; (local.set 0 (i32.add (local.get 0) (i32.const 1))) - -;; ;; Break condition for outer loop: break if outer loop counter >= 5 -;; (br_if $outer (i32.ge_s (local.get 0) (i32.const 5))) -;; ) -;; ) - -;; ;; Return the result -;; (local.get 2) -;; ) -;; ) - (module (func $loop_test (export "loop_test") (result i32) (local i32) ;; Local 0: Counter @@ -57,4 +17,68 @@ ;; Return the counter value (local.get 0) ) + + (func $loop_test3 (export "loop_test3") (result i32) + (local i32) ;; Local 0: Counter + + ;; Initialize the counter + (local.set 0 (i32.const 0)) + + ;; Loop starts here + (block $exit_loop ;; Label for exiting the loop + (loop $my_loop + ;; Increment the counter + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + + ;; Prepare an index for br_table + ;; Here, we use the counter, but you could modify this + ;; For simplicity, 0 will continue the loop, any other value will exit + (local.get 0) + (i32.const 10) + (i32.lt_s) + (br_table $my_loop $exit_loop) + ) + ) + + ;; Return the counter value + (local.get 0) + ) + + (func $calculate (export "loop_test2") (result i32) + (local i32) ;; Local 0: Counter for the outer loop + (local i32) ;; Local 1: Counter for the inner loop + (local i32) ;; Local 2: Result variable + + ;; Initialize variables + (local.set 0 (i32.const 0)) ;; Initialize outer loop counter + (local.set 1 (i32.const 0)) ;; Initialize inner loop counter + (local.set 2 (i32.const 0)) ;; Initialize result variable + + (block $outer ;; Outer loop label + (loop $outer_loop + (local.set 1 (i32.const 5)) ;; Reset inner loop counter for each iteration of the outer loop + + (block $inner ;; Inner loop label + (loop $inner_loop + (br_if $inner (i32.eqz (local.get 1))) ;; Break to $inner if inner loop counter is zero + + ;; Computation: Adding product of counters to the result + (local.set 2 (i32.add (local.get 2) (i32.mul (local.get 0) (local.get 1)))) + + ;; Decrement inner loop counter + (local.set 1 (i32.sub (local.get 1) (i32.const 1))) + ) + ) + + ;; Increment outer loop counter + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + + ;; Break condition for outer loop: break if outer loop counter >= 5 + (br_if $outer (i32.ge_s (local.get 0) (i32.const 5))) + ) + ) + + ;; Return the result + (local.get 2) + ) ) \ No newline at end of file