Skip to content

Commit 569e0b4

Browse files
feat: support the tail call proposal (#37)
Signed-off-by: Henry Gressmann <[email protected]>
1 parent 60bea1e commit 569e0b4

File tree

12 files changed

+110
-60
lines changed

12 files changed

+110
-60
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1010
### Added
1111

1212
- Support for the custom memory page sizes proposal ([#22](https://github.com/explodingcamera/tinywasm/pull/22) by [@danielstuart14](https://github.com/danielstuart14))
13+
- Support for the `tail_call` proposal
1314

1415
### Breaking Changes
1516

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,9 @@ TinyWasm passes all WebAssembly MVP tests from the [WebAssembly core testsuite](
3737
| [**Reference Types**](https://github.com/WebAssembly/reference-types/blob/master/proposals/reference-types/Overview.md) | 🟢 | 0.7.0 |
3838
| [**Multiple Memories**](https://github.com/WebAssembly/multi-memory/blob/master/proposals/multi-memory/Overview.md) | 🟢 | 0.8.0 |
3939
| [**Custom Page Sizes**](https://github.com/WebAssembly/custom-page-sizes/blob/main/proposals/custom-page-sizes/Overview.md) | 🟢 | `next` |
40+
| [**Tail Call**](https://github.com/WebAssembly/tail-call/blob/main/proposals/tail-call/Overview.md) | 🟢 | `next` |
4041
| [**Memory64**](https://github.com/WebAssembly/memory64/blob/master/proposals/memory64/Overview.md) | 🚧 | N/A |
41-
| [**Fixed-Width SIMD**](https://github.com/webassembly/simd) | 🌑 | N/A |
42+
| [**Fixed-Width SIMD**](https://github.com/webassembly/simd) | 🚧 | N/A |
4243

4344
## Usage
4445

crates/parser/src/visit.rs

+5-1
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ macro_rules! impl_visit_operator {
158158
(@@saturating_float_to_int $($rest:tt)* ) => {};
159159
(@@bulk_memory $($rest:tt)* ) => {};
160160
(@@simd $($rest:tt)* ) => {};
161+
(@@tail_call $($rest:tt)* ) => {};
161162

162163
(@@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*)) => {
163164
#[cold]
@@ -181,7 +182,7 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild
181182

182183
define_operands! {
183184
// basic instructions
184-
visit_br(Br, u32), visit_br_if(BrIf, u32), visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_return(Return), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_add(I32Add), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_add(I64Add), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotl(I64Rotl), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64),
185+
visit_br(Br, u32), visit_br_if(BrIf, u32), visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_return(Return), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_add(I32Add), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_add(I64Add), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotl(I64Rotl), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_reinterpret_f32(I32ReinterpretF32), visit_i64_reinterpret_f64(I64ReinterpretF64), visit_f32_reinterpret_i32(F32ReinterpretI32), visit_f64_reinterpret_i64(F64ReinterpretI64),
185186

186187
// sign_extension
187188
visit_i32_extend8_s(I32Extend8S), visit_i32_extend16_s(I32Extend16S), visit_i64_extend8_s(I64Extend8S), visit_i64_extend16_s(I64Extend16S), visit_i64_extend32_s(I64Extend32S),
@@ -431,6 +432,9 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild
431432
fn visit_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output {
432433
self.instructions.push(Instruction::CallIndirect(ty, table));
433434
}
435+
fn visit_return_call_indirect(&mut self, ty: u32, table: u32) -> Self::Output {
436+
self.instructions.push(Instruction::ReturnCallIndirect(ty, table));
437+
}
434438

435439
fn visit_f32_const(&mut self, val: wasmparser::Ieee32) -> Self::Output {
436440
self.instructions.push(Instruction::F32Const(f32::from_bits(val.bits())));

crates/tinywasm/Cargo.toml

+4
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ harness=false
6262
name="test-wasm-custom-page-sizes"
6363
harness=false
6464

65+
[[test]]
66+
name="test-wasm-tail-call"
67+
harness=false
68+
6569
[[test]]
6670
name="test-wasm-memory64"
6771
harness=false

crates/tinywasm/src/interpreter/executor.rs

+58-52
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,11 @@ impl<'store, 'stack> Executor<'store, 'stack> {
5555
Select128 => self.stack.values.select::<Value128>(),
5656
SelectRef => self.stack.values.select::<ValueRef>(),
5757

58-
Call(v) => return self.exec_call_direct(*v),
59-
CallIndirect(ty, table) => return self.exec_call_indirect(*ty, *table),
58+
Call(v) => return self.exec_call_direct::<false>(*v),
59+
CallIndirect(ty, table) => return self.exec_call_indirect::<false>(*ty, *table),
60+
61+
ReturnCall(v) => return self.exec_call_direct::<true>(*v),
62+
ReturnCallIndirect(ty, table) => return self.exec_call_indirect::<true>(*ty, *table),
6063

6164
If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())),
6265
IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())),
@@ -314,50 +317,71 @@ impl<'store, 'stack> Executor<'store, 'stack> {
314317
ControlFlow::Break(Some(Trap::Unreachable.into()))
315318
}
316319

317-
fn exec_call(&mut self, wasm_func: Rc<WasmFunction>, owner: ModuleInstanceAddr) -> ControlFlow<Option<Error>> {
318-
let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals);
319-
let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.stack.blocks.len() as u32);
320-
self.cf.incr_instr_ptr(); // skip the call instruction
321-
self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?;
322-
self.module.swap_with(self.cf.module_addr(), self.store);
320+
fn exec_call<const IS_RETURN_CALL: bool>(
321+
&mut self,
322+
wasm_func: Rc<WasmFunction>,
323+
owner: ModuleInstanceAddr,
324+
) -> ControlFlow<Option<Error>> {
325+
if !IS_RETURN_CALL {
326+
let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals);
327+
let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.stack.blocks.len() as u32);
328+
self.cf.incr_instr_ptr(); // skip the call instruction
329+
self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?;
330+
self.module.swap_with(self.cf.module_addr(), self.store);
331+
} else {
332+
let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals);
333+
self.cf.reuse_for(wasm_func, locals, self.stack.blocks.len() as u32, owner);
334+
self.module.swap_with(self.cf.module_addr(), self.store);
335+
}
336+
323337
ControlFlow::Continue(())
324338
}
325-
fn exec_call_direct(&mut self, v: u32) -> ControlFlow<Option<Error>> {
339+
fn exec_call_host(&mut self, host_func: Rc<imports::HostFunction>) -> ControlFlow<Option<Error>> {
340+
let params = self.stack.values.pop_params(&host_func.ty.params);
341+
let res = host_func
342+
.clone()
343+
.call(FuncContext { store: self.store, module_addr: self.module.id() }, &params)
344+
.to_cf()?;
345+
self.stack.values.extend_from_wasmvalues(&res);
346+
self.cf.incr_instr_ptr();
347+
ControlFlow::Continue(())
348+
}
349+
fn exec_call_direct<const IS_RETURN_CALL: bool>(&mut self, v: u32) -> ControlFlow<Option<Error>> {
326350
let func_inst = self.store.get_func(self.module.resolve_func_addr(v));
327-
let wasm_func = match &func_inst.func {
328-
crate::Function::Wasm(wasm_func) => wasm_func,
329-
crate::Function::Host(host_func) => {
330-
let func = &host_func.clone();
331-
let params = self.stack.values.pop_params(&host_func.ty.params);
332-
let res =
333-
func.call(FuncContext { store: self.store, module_addr: self.module.id() }, &params).to_cf()?;
334-
self.stack.values.extend_from_wasmvalues(&res);
335-
self.cf.incr_instr_ptr();
336-
return ControlFlow::Continue(());
337-
}
338-
};
339-
340-
self.exec_call(wasm_func.clone(), func_inst.owner)
351+
match func_inst.func.clone() {
352+
crate::Function::Wasm(wasm_func) => self.exec_call::<IS_RETURN_CALL>(wasm_func, func_inst.owner),
353+
crate::Function::Host(host_func) => self.exec_call_host(host_func),
354+
}
341355
}
342-
fn exec_call_indirect(&mut self, type_addr: u32, table_addr: u32) -> ControlFlow<Option<Error>> {
356+
fn exec_call_indirect<const IS_RETURN_CALL: bool>(
357+
&mut self,
358+
type_addr: u32,
359+
table_addr: u32,
360+
) -> ControlFlow<Option<Error>> {
343361
// verify that the table is of the right type, this should be validated by the parser already
344362
let func_ref = {
345363
let table = self.store.get_table(self.module.resolve_table_addr(table_addr));
346364
let table_idx: u32 = self.stack.values.pop::<i32>() as u32;
347365
assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref");
348-
table
349-
.get(table_idx)
350-
.map_err(|_| Error::Trap(Trap::UndefinedElement { index: table_idx as usize }))
351-
.to_cf()?
352-
.addr()
353-
.ok_or(Error::Trap(Trap::UninitializedElement { index: table_idx as usize }))
354-
.to_cf()?
366+
let table = table.get(table_idx).map_err(|_| Trap::UndefinedElement { index: table_idx as usize }.into());
367+
let table = table.to_cf()?;
368+
table.addr().ok_or(Trap::UninitializedElement { index: table_idx as usize }.into()).to_cf()?
355369
};
356370

357371
let func_inst = self.store.get_func(func_ref);
358372
let call_ty = self.module.func_ty(type_addr);
359-
let wasm_func = match &func_inst.func {
360-
crate::Function::Wasm(f) => f,
373+
374+
match func_inst.func.clone() {
375+
crate::Function::Wasm(wasm_func) => {
376+
if unlikely(wasm_func.ty != *call_ty) {
377+
return ControlFlow::Break(Some(
378+
Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }
379+
.into(),
380+
));
381+
}
382+
383+
self.exec_call::<IS_RETURN_CALL>(wasm_func, func_inst.owner)
384+
}
361385
crate::Function::Host(host_func) => {
362386
if unlikely(host_func.ty != *call_ty) {
363387
return ControlFlow::Break(Some(
@@ -366,27 +390,9 @@ impl<'store, 'stack> Executor<'store, 'stack> {
366390
));
367391
}
368392

369-
let host_func = host_func.clone();
370-
let params = self.stack.values.pop_params(&host_func.ty.params);
371-
let res =
372-
match host_func.call(FuncContext { store: self.store, module_addr: self.module.id() }, &params) {
373-
Ok(res) => res,
374-
Err(e) => return ControlFlow::Break(Some(e)),
375-
};
376-
377-
self.stack.values.extend_from_wasmvalues(&res);
378-
self.cf.incr_instr_ptr();
379-
return ControlFlow::Continue(());
393+
self.exec_call_host(host_func)
380394
}
381-
};
382-
383-
if unlikely(wasm_func.ty != *call_ty) {
384-
return ControlFlow::Break(Some(
385-
Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }.into(),
386-
));
387395
}
388-
389-
self.exec_call(wasm_func.clone(), func_inst.owner)
390396
}
391397

392398
fn exec_if(&mut self, else_offset: u32, end_offset: u32, (params, results): (StackHeight, StackHeight)) {

crates/tinywasm/src/interpreter/stack/call_stack.rs

+14
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,20 @@ impl CallFrame {
9898
}
9999
}
100100

101+
pub(crate) fn reuse_for(
102+
&mut self,
103+
func: Rc<WasmFunction>,
104+
locals: Locals,
105+
block_depth: u32,
106+
module_addr: ModuleInstanceAddr,
107+
) {
108+
self.func_instance = func;
109+
self.module_addr = module_addr;
110+
self.locals = locals;
111+
self.block_ptr = block_depth;
112+
self.instr_ptr = 0; // Reset to function entry
113+
}
114+
101115
/// Break to a block at the given index (relative to the current frame)
102116
/// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller)
103117
#[inline]

0 commit comments

Comments
 (0)