Skip to content
This repository was archived by the owner on Oct 3, 2025. It is now read-only.

Commit d2b30c3

Browse files
feat: support more wasm opcodes
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
1 parent 366ef54 commit d2b30c3

File tree

5 files changed

+133
-54
lines changed

5 files changed

+133
-54
lines changed

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/tinywasm/src/error.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ use tinywasm_parser::ParseError;
1111
pub enum Trap {
1212
/// An unreachable instruction was executed
1313
Unreachable,
14+
15+
/// An out-of-bounds memory access occurred
16+
MemoryOutOfBounds,
17+
18+
/// A division by zero occurred
19+
DivisionByZero,
1420
}
1521

1622
#[derive(Debug)]

crates/tinywasm/src/runtime/executor/macros.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,34 @@ macro_rules! sub_instr {
1818
}};
1919
}
2020

21+
/// Divide the top two values on the stack
22+
macro_rules! checked_divs_instr {
23+
($ty:ty, $stack:ident) => {{
24+
let [a, b] = $stack.values.pop_n_const::<2>()?;
25+
let a: $ty = a.into();
26+
let b: $ty = b.into();
27+
let Some(res) = a.checked_div(b) else {
28+
return Err(Error::Trap(crate::Trap::DivisionByZero));
29+
};
30+
31+
$stack.values.push(res.into());
32+
}};
33+
}
34+
35+
/// Divide the top two values on the stack
36+
macro_rules! checked_divu_instr {
37+
($ty:ty, $uty:ty, $stack:ident) => {{
38+
let [a, b] = $stack.values.pop_n_const::<2>()?;
39+
let a: $ty = a.into();
40+
let b: $ty = b.into();
41+
let Some(res) = (a as $uty).checked_div(b as $uty) else {
42+
return Err(Error::Trap(crate::Trap::DivisionByZero));
43+
};
44+
45+
$stack.values.push((res as $ty).into());
46+
}};
47+
}
48+
2149
/// Divide the top two values on the stack
2250
macro_rules! div_instr {
2351
($ty:ty, $stack:ident) => {{
@@ -38,6 +66,19 @@ macro_rules! lts_instr {
3866
}};
3967
}
4068

69+
/// Less than unsigned instruction
70+
macro_rules! ltu_instr {
71+
($ty:ty, $uty:ty, $stack:ident) => {{
72+
let [a, b] = $stack.values.pop_n_const::<2>()?;
73+
let a: $ty = a.into();
74+
let b: $ty = b.into();
75+
// Cast to unsigned type before comparison
76+
let a_unsigned: $uty = a as $uty;
77+
let b_unsigned: $uty = b as $uty;
78+
$stack.values.push(((a_unsigned < b_unsigned) as i32).into());
79+
}};
80+
}
81+
4182
/// Multiply the top two values on the stack
4283
macro_rules! mul_instr {
4384
($ty:ty, $stack:ident) => {{
@@ -58,6 +99,24 @@ macro_rules! eq_instr {
5899
}};
59100
}
60101

102+
/// Compare the top value on the stack for equality with zero
103+
macro_rules! eqz_instr {
104+
($ty:ty, $stack:ident) => {{
105+
let a: $ty = $stack.values.pop()?.into();
106+
$stack.values.push(((a == 0) as i32).into());
107+
}};
108+
}
109+
110+
/// Compare the top two values on the stack for inequality
111+
macro_rules! ne_instr {
112+
($ty:ty, $stack:ident) => {{
113+
let [a, b] = $stack.values.pop_n_const::<2>()?;
114+
let a: $ty = a.into();
115+
let b: $ty = b.into();
116+
$stack.values.push(((a != b) as i32).into());
117+
}};
118+
}
119+
61120
/// Greater or equal than signed instruction
62121
macro_rules! ges_instr {
63122
($ty:ty, $stack:ident) => {{
@@ -68,10 +127,29 @@ macro_rules! ges_instr {
68127
}};
69128
}
70129

130+
/// Greater or equal than unsigned instruction
131+
macro_rules! geu_instr {
132+
($ty:ty, $uty:ty, $stack:ident) => {{
133+
let [a, b] = $stack.values.pop_n_const::<2>()?;
134+
let a: $ty = a.into();
135+
let b: $ty = b.into();
136+
// Cast to unsigned type before comparison
137+
let a_unsigned: $uty = a as $uty;
138+
let b_unsigned: $uty = b as $uty;
139+
$stack.values.push(((a_unsigned >= b_unsigned) as i32).into());
140+
}};
141+
}
142+
71143
pub(super) use add_instr;
144+
pub(super) use checked_divs_instr;
145+
pub(super) use checked_divu_instr;
72146
pub(super) use div_instr;
73147
pub(super) use eq_instr;
148+
pub(super) use eqz_instr;
74149
pub(super) use ges_instr;
150+
pub(super) use geu_instr;
75151
pub(super) use lts_instr;
152+
pub(super) use ltu_instr;
76153
pub(super) use mul_instr;
154+
pub(super) use ne_instr;
77155
pub(super) use sub_instr;

crates/tinywasm/src/runtime/executor/mod.rs

Lines changed: 36 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -84,23 +84,18 @@ fn exec_one(
8484
match instr {
8585
Nop => { /* do nothing */ }
8686
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
87-
Drop => {
88-
stack.values.pop().ok_or(Error::StackUnderflow)?;
89-
}
87+
Drop => stack.values.pop().map(|_| ())?,
88+
Return => todo!("called function returned"),
9089
Select => {
91-
let cond: i32 = stack.values.pop().ok_or(Error::StackUnderflow)?.into();
92-
let val2 = stack.values.pop().ok_or(Error::StackUnderflow)?;
90+
let cond: i32 = stack.values.pop()?.into();
91+
let val2 = stack.values.pop()?;
9392

9493
// if cond != 0, we already have the right value on the stack
9594
if cond == 0 {
96-
let _ = stack.values.pop().ok_or(Error::StackUnderflow)?;
95+
let _ = stack.values.pop()?;
9796
stack.values.push(val2);
9897
}
9998
}
100-
Return => {
101-
debug!("return");
102-
}
103-
10499
Call(v) => {
105100
debug!("start call");
106101
// prepare the call frame
@@ -131,7 +126,7 @@ fn exec_one(
131126
args: *args,
132127
ty: BlockType::Loop,
133128
});
134-
stack.values.block_args(*args)?;
129+
stack.values.push_block_args(*args)?;
135130
}
136131

137132
Block(args, end_offset) => {
@@ -142,7 +137,7 @@ fn exec_one(
142137
args: *args,
143138
ty: BlockType::Block,
144139
});
145-
stack.values.block_args(*args)?;
140+
stack.values.push_block_args(*args)?;
146141
}
147142

148143
BrTable(_default, len) => {
@@ -160,11 +155,10 @@ fn exec_one(
160155

161156
todo!()
162157
}
158+
163159
Br(v) => cf.break_to(*v, &mut stack.values)?,
164160
BrIf(v) => {
165-
let val: i32 = stack.values.pop().ok_or(Error::StackUnderflow)?.into();
166-
debug!("br_if: {}", val);
167-
if val > 0 {
161+
if stack.values.pop_t::<i32>()? > 0 {
168162
cf.break_to(*v, &mut stack.values)?
169163
};
170164
}
@@ -174,13 +168,12 @@ fn exec_one(
174168
panic!("endfunc: block frames not empty, this should have been validated by the parser");
175169
}
176170

177-
if stack.call_stack.is_empty() {
178-
debug!("end: no block to end and no parent call frame, returning");
179-
return Ok(ExecResult::Return);
180-
} else {
181-
debug!("end: no block to end, returning to parent call frame");
182-
*cf = stack.call_stack.pop()?;
183-
return Ok(ExecResult::Call);
171+
match stack.call_stack.is_empty() {
172+
true => return Ok(ExecResult::Return),
173+
false => {
174+
*cf = stack.call_stack.pop()?;
175+
return Ok(ExecResult::Call);
176+
}
184177
}
185178
}
186179

@@ -205,22 +198,15 @@ fn exec_one(
205198
stack.values.extend(res.iter().copied());
206199
}
207200

208-
LocalGet(local_index) => {
209-
debug!("local.get: {:?}", local_index);
210-
let val = cf.get_local(*local_index as usize);
211-
stack.values.push(val);
212-
}
213-
LocalSet(local_index) => {
214-
let val = stack.values.pop().ok_or(Error::StackUnderflow)?;
215-
cf.set_local(*local_index as usize, val);
216-
}
217-
// Equivalent to local.set, local.get
218-
LocalTee(local_index) => {
219-
let val = stack.values.last().ok_or(Error::StackUnderflow)?;
220-
cf.set_local(*local_index as usize, *val);
221-
}
201+
LocalGet(local_index) => stack.values.push(cf.get_local(*local_index as usize)),
202+
LocalSet(local_index) => cf.set_local(*local_index as usize, stack.values.pop()?),
203+
LocalTee(local_index) => cf.set_local(*local_index as usize, *stack.values.last()?),
204+
222205
I32Const(val) => stack.values.push((*val).into()),
223206
I64Const(val) => stack.values.push((*val).into()),
207+
F32Const(val) => stack.values.push((*val).into()),
208+
F64Const(val) => stack.values.push((*val).into()),
209+
224210
I64Add => add_instr!(i64, stack),
225211
I32Add => add_instr!(i32, stack),
226212
F32Add => add_instr!(f32, stack),
@@ -233,16 +219,22 @@ fn exec_one(
233219

234220
I32LtS => lts_instr!(i32, stack),
235221
I64LtS => lts_instr!(i64, stack),
222+
I32LtU => ltu_instr!(i32, u32, stack),
223+
I64LtU => ltu_instr!(i64, u64, stack),
236224
F32Lt => lts_instr!(f32, stack),
237225
F64Lt => lts_instr!(f64, stack),
238226

239227
I32GeS => ges_instr!(i32, stack),
240228
I64GeS => ges_instr!(i64, stack),
229+
I32GeU => geu_instr!(i32, u32, stack),
230+
I64GeU => geu_instr!(i64, u64, stack),
241231
F32Ge => ges_instr!(f32, stack),
242232
F64Ge => ges_instr!(f64, stack),
243233

244-
I32DivS => div_instr!(i32, stack),
245-
I64DivS => div_instr!(i64, stack),
234+
I32DivS => checked_divs_instr!(i32, stack),
235+
I64DivS => checked_divs_instr!(i64, stack),
236+
I32DivU => checked_divu_instr!(i32, u32, stack),
237+
I64DivU => checked_divu_instr!(i64, u64, stack),
246238
F32Div => div_instr!(f32, stack),
247239
F64Div => div_instr!(f64, stack),
248240

@@ -253,18 +245,15 @@ fn exec_one(
253245

254246
I32Eq => eq_instr!(i32, stack),
255247
I64Eq => eq_instr!(i64, stack),
248+
I32Eqz => eqz_instr!(i32, stack),
249+
I64Eqz => eqz_instr!(i64, stack),
256250
F32Eq => eq_instr!(f32, stack),
257251
F64Eq => eq_instr!(f64, stack),
258252

259-
I32Eqz => {
260-
let val: i32 = stack.values.pop().ok_or(Error::StackUnderflow)?.into();
261-
stack.values.push(((val == 0) as i32).into());
262-
}
263-
264-
I64Eqz => {
265-
let val: i64 = stack.values.pop().ok_or(Error::StackUnderflow)?.into();
266-
stack.values.push(((val == 0) as i32).into());
267-
}
253+
I32Ne => ne_instr!(i32, stack),
254+
I64Ne => ne_instr!(i64, stack),
255+
F32Ne => ne_instr!(f32, stack),
256+
F64Ne => ne_instr!(f64, stack),
268257

269258
i => todo!("{:?}", i),
270259
};

crates/tinywasm/src/runtime/stack/value_stack.rs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ impl ValueStack {
3737
}
3838

3939
#[inline]
40-
pub(crate) fn block_args(&self, args: BlockArgs) -> Result<()> {
40+
pub(crate) fn push_block_args(&self, args: BlockArgs) -> Result<()> {
4141
match args {
4242
BlockArgs::Empty => Ok(()),
4343
BlockArgs::Type(_t) => todo!(),
@@ -58,14 +58,20 @@ impl ValueStack {
5858
}
5959

6060
#[inline]
61-
pub(crate) fn last(&self) -> Option<&RawWasmValue> {
62-
self.stack.last()
61+
pub(crate) fn last(&self) -> Result<&RawWasmValue> {
62+
self.stack.last().ok_or(Error::StackUnderflow)
6363
}
6464

6565
#[inline]
66-
pub(crate) fn pop(&mut self) -> Option<RawWasmValue> {
66+
pub(crate) fn pop_t<T: From<RawWasmValue>>(&mut self) -> Result<T> {
6767
self.top -= 1;
68-
self.stack.pop()
68+
Ok(self.pop()?.into())
69+
}
70+
71+
#[inline]
72+
pub(crate) fn pop(&mut self) -> Result<RawWasmValue> {
73+
self.top -= 1;
74+
self.stack.pop().ok_or(Error::StackUnderflow)
6975
}
7076

7177
#[inline]

0 commit comments

Comments
 (0)