Skip to content

Commit b643e90

Browse files
Merge branch 'next' into feat/simd
2 parents afc76e4 + 569e0b4 commit b643e90

File tree

12 files changed

+110
-60
lines changed

12 files changed

+110
-60
lines changed

Diff for: 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

Diff for: 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

Diff for: 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())));

Diff for: crates/tinywasm/Cargo.toml

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

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

Diff for: crates/tinywasm/src/interpreter/executor.rs

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

72-
Call(v) => return self.exec_call_direct(*v),
73-
CallIndirect(ty, table) => return self.exec_call_indirect(*ty, *table),
72+
Call(v) => return self.exec_call_direct::<false>(*v),
73+
CallIndirect(ty, table) => return self.exec_call_indirect::<false>(*ty, *table),
74+
75+
ReturnCall(v) => return self.exec_call_direct::<true>(*v),
76+
ReturnCallIndirect(ty, table) => return self.exec_call_indirect::<true>(*ty, *table),
7477

7578
If(end, el) => self.exec_if(*end, *el, (StackHeight::default(), StackHeight::default())),
7679
IfWithType(ty, end, el) => self.exec_if(*end, *el, (StackHeight::default(), (*ty).into())),
@@ -505,50 +508,71 @@ impl<'store, 'stack> Executor<'store, 'stack> {
505508
ControlFlow::Break(Some(Trap::Unreachable.into()))
506509
}
507510

508-
fn exec_call(&mut self, wasm_func: Rc<WasmFunction>, owner: ModuleInstanceAddr) -> ControlFlow<Option<Error>> {
509-
let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals);
510-
let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.stack.blocks.len() as u32);
511-
self.cf.incr_instr_ptr(); // skip the call instruction
512-
self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?;
513-
self.module.swap_with(self.cf.module_addr(), self.store);
511+
fn exec_call<const IS_RETURN_CALL: bool>(
512+
&mut self,
513+
wasm_func: Rc<WasmFunction>,
514+
owner: ModuleInstanceAddr,
515+
) -> ControlFlow<Option<Error>> {
516+
if !IS_RETURN_CALL {
517+
let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals);
518+
let new_call_frame = CallFrame::new_raw(wasm_func, owner, locals, self.stack.blocks.len() as u32);
519+
self.cf.incr_instr_ptr(); // skip the call instruction
520+
self.stack.call_stack.push(core::mem::replace(&mut self.cf, new_call_frame))?;
521+
self.module.swap_with(self.cf.module_addr(), self.store);
522+
} else {
523+
let locals = self.stack.values.pop_locals(wasm_func.params, wasm_func.locals);
524+
self.cf.reuse_for(wasm_func, locals, self.stack.blocks.len() as u32, owner);
525+
self.module.swap_with(self.cf.module_addr(), self.store);
526+
}
527+
514528
ControlFlow::Continue(())
515529
}
516-
fn exec_call_direct(&mut self, v: u32) -> ControlFlow<Option<Error>> {
530+
fn exec_call_host(&mut self, host_func: Rc<imports::HostFunction>) -> ControlFlow<Option<Error>> {
531+
let params = self.stack.values.pop_params(&host_func.ty.params);
532+
let res = host_func
533+
.clone()
534+
.call(FuncContext { store: self.store, module_addr: self.module.id() }, &params)
535+
.to_cf()?;
536+
self.stack.values.extend_from_wasmvalues(&res);
537+
self.cf.incr_instr_ptr();
538+
ControlFlow::Continue(())
539+
}
540+
fn exec_call_direct<const IS_RETURN_CALL: bool>(&mut self, v: u32) -> ControlFlow<Option<Error>> {
517541
let func_inst = self.store.get_func(self.module.resolve_func_addr(v));
518-
let wasm_func = match &func_inst.func {
519-
crate::Function::Wasm(wasm_func) => wasm_func,
520-
crate::Function::Host(host_func) => {
521-
let func = &host_func.clone();
522-
let params = self.stack.values.pop_params(&host_func.ty.params);
523-
let res =
524-
func.call(FuncContext { store: self.store, module_addr: self.module.id() }, &params).to_cf()?;
525-
self.stack.values.extend_from_wasmvalues(&res);
526-
self.cf.incr_instr_ptr();
527-
return ControlFlow::Continue(());
528-
}
529-
};
530-
531-
self.exec_call(wasm_func.clone(), func_inst.owner)
542+
match func_inst.func.clone() {
543+
crate::Function::Wasm(wasm_func) => self.exec_call::<IS_RETURN_CALL>(wasm_func, func_inst.owner),
544+
crate::Function::Host(host_func) => self.exec_call_host(host_func),
545+
}
532546
}
533-
fn exec_call_indirect(&mut self, type_addr: u32, table_addr: u32) -> ControlFlow<Option<Error>> {
547+
fn exec_call_indirect<const IS_RETURN_CALL: bool>(
548+
&mut self,
549+
type_addr: u32,
550+
table_addr: u32,
551+
) -> ControlFlow<Option<Error>> {
534552
// verify that the table is of the right type, this should be validated by the parser already
535553
let func_ref = {
536554
let table = self.store.get_table(self.module.resolve_table_addr(table_addr));
537555
let table_idx: u32 = self.stack.values.pop::<i32>() as u32;
538556
assert!(table.kind.element_type == ValType::RefFunc, "table is not of type funcref");
539-
table
540-
.get(table_idx)
541-
.map_err(|_| Error::Trap(Trap::UndefinedElement { index: table_idx as usize }))
542-
.to_cf()?
543-
.addr()
544-
.ok_or(Error::Trap(Trap::UninitializedElement { index: table_idx as usize }))
545-
.to_cf()?
557+
let table = table.get(table_idx).map_err(|_| Trap::UndefinedElement { index: table_idx as usize }.into());
558+
let table = table.to_cf()?;
559+
table.addr().ok_or(Trap::UninitializedElement { index: table_idx as usize }.into()).to_cf()?
546560
};
547561

548562
let func_inst = self.store.get_func(func_ref);
549563
let call_ty = self.module.func_ty(type_addr);
550-
let wasm_func = match &func_inst.func {
551-
crate::Function::Wasm(f) => f,
564+
565+
match func_inst.func.clone() {
566+
crate::Function::Wasm(wasm_func) => {
567+
if unlikely(wasm_func.ty != *call_ty) {
568+
return ControlFlow::Break(Some(
569+
Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }
570+
.into(),
571+
));
572+
}
573+
574+
self.exec_call::<IS_RETURN_CALL>(wasm_func, func_inst.owner)
575+
}
552576
crate::Function::Host(host_func) => {
553577
if unlikely(host_func.ty != *call_ty) {
554578
return ControlFlow::Break(Some(
@@ -557,27 +581,9 @@ impl<'store, 'stack> Executor<'store, 'stack> {
557581
));
558582
}
559583

560-
let host_func = host_func.clone();
561-
let params = self.stack.values.pop_params(&host_func.ty.params);
562-
let res =
563-
match host_func.call(FuncContext { store: self.store, module_addr: self.module.id() }, &params) {
564-
Ok(res) => res,
565-
Err(e) => return ControlFlow::Break(Some(e)),
566-
};
567-
568-
self.stack.values.extend_from_wasmvalues(&res);
569-
self.cf.incr_instr_ptr();
570-
return ControlFlow::Continue(());
584+
self.exec_call_host(host_func)
571585
}
572-
};
573-
574-
if unlikely(wasm_func.ty != *call_ty) {
575-
return ControlFlow::Break(Some(
576-
Trap::IndirectCallTypeMismatch { actual: wasm_func.ty.clone(), expected: call_ty.clone() }.into(),
577-
));
578586
}
579-
580-
self.exec_call(wasm_func.clone(), func_inst.owner)
581587
}
582588

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

Diff for: crates/tinywasm/src/interpreter/stack/call_stack.rs

+14
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,20 @@ impl CallFrame {
103103
}
104104
}
105105

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

0 commit comments

Comments
 (0)