From d2b30c3e5ee51b76472dd7cef29963b7757298a6 Mon Sep 17 00:00:00 2001 From: Henry Gressmann Date: Thu, 14 Dec 2023 18:00:46 +0100 Subject: feat: support more wasm opcodes Signed-off-by: Henry Gressmann --- crates/tinywasm/src/error.rs | 6 ++ crates/tinywasm/src/runtime/executor/macros.rs | 78 ++++++++++++++++++++++ crates/tinywasm/src/runtime/executor/mod.rs | 83 ++++++++++-------------- crates/tinywasm/src/runtime/stack/value_stack.rs | 16 +++-- 4 files changed, 131 insertions(+), 52 deletions(-) (limited to 'crates') diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index bea97fa..8776365 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -11,6 +11,12 @@ use tinywasm_parser::ParseError; pub enum Trap { /// An unreachable instruction was executed Unreachable, + + /// An out-of-bounds memory access occurred + MemoryOutOfBounds, + + /// A division by zero occurred + DivisionByZero, } #[derive(Debug)] diff --git a/crates/tinywasm/src/runtime/executor/macros.rs b/crates/tinywasm/src/runtime/executor/macros.rs index 91d48d3..12e8ce8 100644 --- a/crates/tinywasm/src/runtime/executor/macros.rs +++ b/crates/tinywasm/src/runtime/executor/macros.rs @@ -18,6 +18,34 @@ macro_rules! sub_instr { }}; } +/// Divide the top two values on the stack +macro_rules! checked_divs_instr { + ($ty:ty, $stack:ident) => {{ + let [a, b] = $stack.values.pop_n_const::<2>()?; + let a: $ty = a.into(); + let b: $ty = b.into(); + let Some(res) = a.checked_div(b) else { + return Err(Error::Trap(crate::Trap::DivisionByZero)); + }; + + $stack.values.push(res.into()); + }}; +} + +/// Divide the top two values on the stack +macro_rules! checked_divu_instr { + ($ty:ty, $uty:ty, $stack:ident) => {{ + let [a, b] = $stack.values.pop_n_const::<2>()?; + let a: $ty = a.into(); + let b: $ty = b.into(); + let Some(res) = (a as $uty).checked_div(b as $uty) else { + return Err(Error::Trap(crate::Trap::DivisionByZero)); + }; + + $stack.values.push((res as $ty).into()); + }}; +} + /// Divide the top two values on the stack macro_rules! div_instr { ($ty:ty, $stack:ident) => {{ @@ -38,6 +66,19 @@ macro_rules! lts_instr { }}; } +/// Less than unsigned instruction +macro_rules! ltu_instr { + ($ty:ty, $uty:ty, $stack:ident) => {{ + let [a, b] = $stack.values.pop_n_const::<2>()?; + let a: $ty = a.into(); + let b: $ty = b.into(); + // Cast to unsigned type before comparison + let a_unsigned: $uty = a as $uty; + let b_unsigned: $uty = b as $uty; + $stack.values.push(((a_unsigned < b_unsigned) as i32).into()); + }}; +} + /// Multiply the top two values on the stack macro_rules! mul_instr { ($ty:ty, $stack:ident) => {{ @@ -58,6 +99,24 @@ macro_rules! eq_instr { }}; } +/// Compare the top value on the stack for equality with zero +macro_rules! eqz_instr { + ($ty:ty, $stack:ident) => {{ + let a: $ty = $stack.values.pop()?.into(); + $stack.values.push(((a == 0) as i32).into()); + }}; +} + +/// Compare the top two values on the stack for inequality +macro_rules! ne_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()); + }}; +} + /// Greater or equal than signed instruction macro_rules! ges_instr { ($ty:ty, $stack:ident) => {{ @@ -68,10 +127,29 @@ macro_rules! ges_instr { }}; } +/// Greater or equal than unsigned instruction +macro_rules! geu_instr { + ($ty:ty, $uty:ty, $stack:ident) => {{ + let [a, b] = $stack.values.pop_n_const::<2>()?; + let a: $ty = a.into(); + let b: $ty = b.into(); + // Cast to unsigned type before comparison + let a_unsigned: $uty = a as $uty; + let b_unsigned: $uty = b as $uty; + $stack.values.push(((a_unsigned >= b_unsigned) as i32).into()); + }}; +} + pub(super) use add_instr; +pub(super) use checked_divs_instr; +pub(super) use checked_divu_instr; pub(super) use div_instr; pub(super) use eq_instr; +pub(super) use eqz_instr; pub(super) use ges_instr; +pub(super) use geu_instr; pub(super) use lts_instr; +pub(super) use ltu_instr; pub(super) use mul_instr; +pub(super) use ne_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 25c7601..422726e 100644 --- a/crates/tinywasm/src/runtime/executor/mod.rs +++ b/crates/tinywasm/src/runtime/executor/mod.rs @@ -84,23 +84,18 @@ fn exec_one( 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 - Drop => { - stack.values.pop().ok_or(Error::StackUnderflow)?; - } + Drop => stack.values.pop().map(|_| ())?, + Return => todo!("called function returned"), Select => { - let cond: i32 = stack.values.pop().ok_or(Error::StackUnderflow)?.into(); - let val2 = stack.values.pop().ok_or(Error::StackUnderflow)?; + let cond: i32 = stack.values.pop()?.into(); + let val2 = stack.values.pop()?; // if cond != 0, we already have the right value on the stack if cond == 0 { - let _ = stack.values.pop().ok_or(Error::StackUnderflow)?; + let _ = stack.values.pop()?; stack.values.push(val2); } } - Return => { - debug!("return"); - } - Call(v) => { debug!("start call"); // prepare the call frame @@ -131,7 +126,7 @@ fn exec_one( args: *args, ty: BlockType::Loop, }); - stack.values.block_args(*args)?; + stack.values.push_block_args(*args)?; } Block(args, end_offset) => { @@ -142,7 +137,7 @@ fn exec_one( args: *args, ty: BlockType::Block, }); - stack.values.block_args(*args)?; + stack.values.push_block_args(*args)?; } BrTable(_default, len) => { @@ -160,11 +155,10 @@ fn exec_one( todo!() } + Br(v) => cf.break_to(*v, &mut stack.values)?, BrIf(v) => { - let val: i32 = stack.values.pop().ok_or(Error::StackUnderflow)?.into(); - debug!("br_if: {}", val); - if val > 0 { + if stack.values.pop_t::()? > 0 { cf.break_to(*v, &mut stack.values)? }; } @@ -174,13 +168,12 @@ fn exec_one( panic!("endfunc: block frames not empty, this should have been validated by the parser"); } - if stack.call_stack.is_empty() { - debug!("end: no block to end and no parent call frame, returning"); - return Ok(ExecResult::Return); - } else { - debug!("end: no block to end, returning to parent call frame"); - *cf = stack.call_stack.pop()?; - return Ok(ExecResult::Call); + match stack.call_stack.is_empty() { + true => return Ok(ExecResult::Return), + false => { + *cf = stack.call_stack.pop()?; + return Ok(ExecResult::Call); + } } } @@ -205,22 +198,15 @@ fn exec_one( stack.values.extend(res.iter().copied()); } - LocalGet(local_index) => { - debug!("local.get: {:?}", local_index); - let val = cf.get_local(*local_index as usize); - stack.values.push(val); - } - LocalSet(local_index) => { - let val = stack.values.pop().ok_or(Error::StackUnderflow)?; - cf.set_local(*local_index as usize, val); - } - // Equivalent to local.set, local.get - LocalTee(local_index) => { - let val = stack.values.last().ok_or(Error::StackUnderflow)?; - cf.set_local(*local_index as usize, *val); - } + LocalGet(local_index) => stack.values.push(cf.get_local(*local_index as usize)), + LocalSet(local_index) => cf.set_local(*local_index as usize, stack.values.pop()?), + LocalTee(local_index) => cf.set_local(*local_index as usize, *stack.values.last()?), + I32Const(val) => stack.values.push((*val).into()), I64Const(val) => stack.values.push((*val).into()), + F32Const(val) => stack.values.push((*val).into()), + F64Const(val) => stack.values.push((*val).into()), + I64Add => add_instr!(i64, stack), I32Add => add_instr!(i32, stack), F32Add => add_instr!(f32, stack), @@ -233,16 +219,22 @@ fn exec_one( I32LtS => lts_instr!(i32, stack), I64LtS => lts_instr!(i64, stack), + I32LtU => ltu_instr!(i32, u32, stack), + I64LtU => ltu_instr!(i64, u64, stack), F32Lt => lts_instr!(f32, stack), F64Lt => lts_instr!(f64, stack), I32GeS => ges_instr!(i32, stack), I64GeS => ges_instr!(i64, stack), + I32GeU => geu_instr!(i32, u32, stack), + I64GeU => geu_instr!(i64, u64, stack), F32Ge => ges_instr!(f32, stack), F64Ge => ges_instr!(f64, stack), - I32DivS => div_instr!(i32, stack), - I64DivS => div_instr!(i64, stack), + I32DivS => checked_divs_instr!(i32, stack), + I64DivS => checked_divs_instr!(i64, stack), + I32DivU => checked_divu_instr!(i32, u32, stack), + I64DivU => checked_divu_instr!(i64, u64, stack), F32Div => div_instr!(f32, stack), F64Div => div_instr!(f64, stack), @@ -253,18 +245,15 @@ fn exec_one( I32Eq => eq_instr!(i32, stack), I64Eq => eq_instr!(i64, stack), + I32Eqz => eqz_instr!(i32, stack), + I64Eqz => eqz_instr!(i64, stack), F32Eq => eq_instr!(f32, stack), F64Eq => eq_instr!(f64, stack), - I32Eqz => { - let val: i32 = stack.values.pop().ok_or(Error::StackUnderflow)?.into(); - stack.values.push(((val == 0) as i32).into()); - } - - I64Eqz => { - let val: i64 = stack.values.pop().ok_or(Error::StackUnderflow)?.into(); - stack.values.push(((val == 0) as i32).into()); - } + I32Ne => ne_instr!(i32, stack), + I64Ne => ne_instr!(i64, stack), + F32Ne => ne_instr!(f32, stack), + F64Ne => ne_instr!(f64, stack), i => todo!("{:?}", i), }; diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index 9dc9bcb..59be3d2 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -37,7 +37,7 @@ impl ValueStack { } #[inline] - pub(crate) fn block_args(&self, args: BlockArgs) -> Result<()> { + pub(crate) fn push_block_args(&self, args: BlockArgs) -> Result<()> { match args { BlockArgs::Empty => Ok(()), BlockArgs::Type(_t) => todo!(), @@ -58,14 +58,20 @@ impl ValueStack { } #[inline] - pub(crate) fn last(&self) -> Option<&RawWasmValue> { - self.stack.last() + pub(crate) fn last(&self) -> Result<&RawWasmValue> { + self.stack.last().ok_or(Error::StackUnderflow) } #[inline] - pub(crate) fn pop(&mut self) -> Option { + pub(crate) fn pop_t>(&mut self) -> Result { self.top -= 1; - self.stack.pop() + Ok(self.pop()?.into()) + } + + #[inline] + pub(crate) fn pop(&mut self) -> Result { + self.top -= 1; + self.stack.pop().ok_or(Error::StackUnderflow) } #[inline] -- cgit v1.3.1