From 29bb7ff7407554f9ec7bdb33846103d3ac09e110 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 13:42:10 +0100 Subject: [PATCH 01/29] add tail call support to Config --- crates/wasmi/src/engine/config.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/crates/wasmi/src/engine/config.rs b/crates/wasmi/src/engine/config.rs index 0bfdd7416f..44a4a811c1 100644 --- a/crates/wasmi/src/engine/config.rs +++ b/crates/wasmi/src/engine/config.rs @@ -25,6 +25,8 @@ pub struct Config { bulk_memory: bool, /// Is `true` if the [`reference-types`] Wasm proposal is enabled. reference_types: bool, + /// Is `true` if the [`tail-call`] Wasm proposal is enabled. + tail_call: bool, /// Is `true` if Wasm instructions on `f32` and `f64` types are allowed. floats: bool, /// Is `true` if `wasmi` executions shall consume fuel. @@ -94,6 +96,7 @@ impl Default for Config { multi_value: true, bulk_memory: true, reference_types: true, + tail_call: false, floats: true, consume_fuel: false, fuel_costs: FuelCosts::default(), @@ -201,6 +204,18 @@ impl Config { self } + /// Enable or disable the [`tail-call`] Wasm proposal for the [`Config`]. + /// + /// # Note + /// + /// Disabled by default. + /// + /// [`tail-call`]: https://github.com/WebAssembly/tail-calls + pub fn wasm_tail_call(&mut self, enable: bool) -> &mut Self { + self.tail_call = enable; + self + } + /// Enable or disable Wasm floating point (`f32` and `f64`) instructions and types. /// /// Enabled by default. @@ -252,12 +267,12 @@ impl Config { sign_extension: self.sign_extension, bulk_memory: self.bulk_memory, reference_types: self.reference_types, + tail_call: self.tail_call, floats: self.floats, component_model: false, simd: false, relaxed_simd: false, threads: false, - tail_call: false, multi_memory: false, exceptions: false, memory64: false, From 0ca3b8153f9d84bb1c0cad9670adbc1503a7dbfc Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 14:05:47 +0100 Subject: [PATCH 02/29] implement return_call[_indirect] for func translator --- crates/wasmi/src/engine/bytecode/mod.rs | 5 ++ crates/wasmi/src/engine/executor.rs | 16 ++++ crates/wasmi/src/engine/func_builder/mod.rs | 3 + .../src/engine/func_builder/translator.rs | 90 +++++++++++++------ 4 files changed, 87 insertions(+), 27 deletions(-) diff --git a/crates/wasmi/src/engine/bytecode/mod.rs b/crates/wasmi/src/engine/bytecode/mod.rs index 17a111197d..78e2e6292c 100644 --- a/crates/wasmi/src/engine/bytecode/mod.rs +++ b/crates/wasmi/src/engine/bytecode/mod.rs @@ -53,6 +53,11 @@ pub enum Instruction { }, Return(DropKeep), ReturnIfNez(DropKeep), + ReturnCall(FuncIdx), + ReturnCallIndirect { + table: TableIdx, + func_type: SignatureIdx, + }, Call(FuncIdx), CallIndirect { table: TableIdx, diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 05a5d69788..c1a9bb8814 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -141,6 +141,10 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { return Ok(CallOutcome::Return); } } + Instr::ReturnCall(func) => return self.visit_return_call(func), + Instr::ReturnCallIndirect { table, func_type } => { + return self.visit_return_call_indirect(table, func_type) + } Instr::Call(func) => return self.visit_call(func), Instr::CallIndirect { table, func_type } => { return self.visit_call_indirect(table, func_type) @@ -646,6 +650,18 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.next_instr() } + fn visit_return_call(&mut self, _func_index: FuncIdx) -> Result { + todo!() + } + + fn visit_return_call_indirect( + &mut self, + _table: TableIdx, + _func_type: SignatureIdx, + ) -> Result { + todo!() + } + fn visit_call(&mut self, func_index: FuncIdx) -> Result { let callee = self.cache.get_func(self.ctx, func_index.into_inner()); self.call_func(&callee) diff --git a/crates/wasmi/src/engine/func_builder/mod.rs b/crates/wasmi/src/engine/func_builder/mod.rs index f05292360b..1c161ee43c 100644 --- a/crates/wasmi/src/engine/func_builder/mod.rs +++ b/crates/wasmi/src/engine/func_builder/mod.rs @@ -135,6 +135,9 @@ macro_rules! impl_visit_operator { ( @reference_types $($rest:tt)* ) => { impl_visit_operator!(@@supported $($rest)*); }; + ( @tail_call $($rest:tt)* ) => { + impl_visit_operator!(@@supported $($rest)*); + }; ( @@supported $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $($rest:tt)* ) => { fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output { let offset = self.current_pos(); diff --git a/crates/wasmi/src/engine/func_builder/translator.rs b/crates/wasmi/src/engine/func_builder/translator.rs index e1e24aeadb..dc9e0b38bb 100644 --- a/crates/wasmi/src/engine/func_builder/translator.rs +++ b/crates/wasmi/src/engine/func_builder/translator.rs @@ -694,6 +694,45 @@ impl<'parser> FuncTranslator<'parser> { fn unsupported_operator(&self, name: &str) -> Result<(), TranslationError> { panic!("tried to translate an unsupported Wasm operator: {name}") } + + /// Translates `call` and `return_call`. + fn translate_call( + &mut self, + func_idx: u32, + make_instr: fn(bytecode::FuncIdx) -> Instruction, + ) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + builder.bump_fuel_consumption(builder.fuel_costs().call); + let func_idx = FuncIdx::from(func_idx); + let func_type = builder.func_type_of(func_idx); + builder.adjust_value_stack_for_call(&func_type); + let func_idx = func_idx.into_u32().into(); + builder.alloc.inst_builder.push_inst(make_instr(func_idx)); + Ok(()) + }) + } + + /// Translates `call_indirect` and `return_call_indirect`. + fn translate_call_indirect( + &mut self, + func_type_index: u32, + table_index: u32, + make_instr: fn(TableIdx, SignatureIdx) -> Instruction, + ) -> Result<(), TranslationError> { + self.translate_if_reachable(|builder| { + builder.bump_fuel_consumption(builder.fuel_costs().call); + let func_type_index = SignatureIdx::from(func_type_index); + let table = TableIdx::from(table_index); + builder.stack_height.pop1(); + let func_type = builder.func_type_at(func_type_index); + builder.adjust_value_stack_for_call(&func_type); + builder + .alloc + .inst_builder + .push_inst(make_instr(table, func_type_index)); + Ok(()) + }) + } } /// An acquired target. @@ -729,6 +768,9 @@ macro_rules! impl_visit_operator { ( @reference_types $($rest:tt)* ) => { impl_visit_operator!(@@skipped $($rest)*); }; + ( @tail_call $($rest:tt)* ) => { + impl_visit_operator!(@@skipped $($rest)*); + }; ( @@skipped $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $($rest:tt)* ) => { // We skip Wasm operators that we already implement manually. impl_visit_operator!($($rest)*); @@ -1087,19 +1129,26 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { }) } + fn visit_return_call(&mut self, func_idx: u32) -> Result<(), TranslationError> { + self.translate_call(func_idx, Instruction::ReturnCall)?; + self.reachable = false; + Ok(()) + } + + fn visit_return_call_indirect( + &mut self, + func_type_index: u32, + table_index: u32, + ) -> Result<(), TranslationError> { + self.translate_call_indirect(func_type_index, table_index, |table, func_type| { + Instruction::ReturnCallIndirect { table, func_type } + })?; + self.reachable = false; + Ok(()) + } + fn visit_call(&mut self, func_idx: u32) -> Result<(), TranslationError> { - self.translate_if_reachable(|builder| { - builder.bump_fuel_consumption(builder.fuel_costs().call); - let func_idx = FuncIdx::from(func_idx); - let func_type = builder.func_type_of(func_idx); - builder.adjust_value_stack_for_call(&func_type); - let func_idx = func_idx.into_u32().into(); - builder - .alloc - .inst_builder - .push_inst(Instruction::Call(func_idx)); - Ok(()) - }) + self.translate_call(func_idx, Instruction::Call) } fn visit_call_indirect( @@ -1108,21 +1157,8 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { table_index: u32, _table_byte: u8, ) -> Result<(), TranslationError> { - self.translate_if_reachable(|builder| { - builder.bump_fuel_consumption(builder.fuel_costs().call); - let func_type_index = SignatureIdx::from(func_type_index); - let table = TableIdx::from(table_index); - builder.stack_height.pop1(); - let func_type = builder.func_type_at(func_type_index); - builder.adjust_value_stack_for_call(&func_type); - builder - .alloc - .inst_builder - .push_inst(Instruction::CallIndirect { - table, - func_type: func_type_index, - }); - Ok(()) + self.translate_call_indirect(func_type_index, table_index, |table, func_type| { + Instruction::CallIndirect { table, func_type } }) } From 759c01ddb7f36c42bb6207ed32f6e727cc0ebc46 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 14:08:50 +0100 Subject: [PATCH 03/29] add tail-call tests from Wasm spec testsuite --- crates/wasmi/tests/spec/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/wasmi/tests/spec/mod.rs b/crates/wasmi/tests/spec/mod.rs index 6324967851..1fa3b94760 100644 --- a/crates/wasmi/tests/spec/mod.rs +++ b/crates/wasmi/tests/spec/mod.rs @@ -77,6 +77,7 @@ fn make_config() -> Config { config.wasm_multi_value(true); config.wasm_bulk_memory(true); config.wasm_reference_types(true); + config.wasm_tail_call(true); config } @@ -95,6 +96,8 @@ define_spec_tests! { fn wasm_bulk("bulk"); fn wasm_call("call"); fn wasm_call_indirect("call_indirect"); + fn wasm_return_call("proposals/tail-call/return_call"); + fn wasm_return_call_indirect("proposals/tail-call/return_call_indirect"); fn wasm_comments("comments"); fn wasm_const("const"); fn wasm_conversions("conversions"); From 084780df8c6d8c77ceed2c02720bce0001fcd20b Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 14:22:26 +0100 Subject: [PATCH 04/29] expect failure for tail call tests since not yet implemented --- crates/wasmi/tests/spec/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/wasmi/tests/spec/mod.rs b/crates/wasmi/tests/spec/mod.rs index 1fa3b94760..3d858fe54d 100644 --- a/crates/wasmi/tests/spec/mod.rs +++ b/crates/wasmi/tests/spec/mod.rs @@ -96,7 +96,10 @@ define_spec_tests! { fn wasm_bulk("bulk"); fn wasm_call("call"); fn wasm_call_indirect("call_indirect"); + #[should_panic] fn wasm_return_call("proposals/tail-call/return_call"); + #[should_panic] + fn wasm_return_call_indirect("proposals/tail-call/return_call_indirect"); fn wasm_comments("comments"); fn wasm_const("const"); From c87b1056e6f87521503fb1542325b5b57c389458 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 14:22:45 +0100 Subject: [PATCH 05/29] generalize CallOutcome to later support tail calls --- crates/wasmi/src/engine/executor.rs | 2 +- crates/wasmi/src/engine/mod.rs | 41 ++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index c1a9bb8814..6090b6b57c 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -486,7 +486,7 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.next_instr(); self.frame.update_ip(self.ip); self.sync_stack_ptr(); - Ok(CallOutcome::NestedCall(*func)) + Ok(CallOutcome::nested_call(*func)) } /// Returns to the caller. diff --git a/crates/wasmi/src/engine/mod.rs b/crates/wasmi/src/engine/mod.rs index 5d6d5fb0d9..f427e429c9 100644 --- a/crates/wasmi/src/engine/mod.rs +++ b/crates/wasmi/src/engine/mod.rs @@ -59,7 +59,40 @@ pub enum CallOutcome { /// The function has returned. Return, /// The function called another function. - NestedCall(Func), + Call { + /// The kind of the call. + kind: CallKind, + /// The called function. + func: Func, + }, +} + +/// The kind of a call. +#[derive(Debug, Copy, Clone)] +pub enum CallKind { + /// The call is a nested call within the caller. + Nested, + /// The call is a tail call and returns to the caller. + Return, +} + +impl CallOutcome { + /// Creates a nested call [`CallOutcome`] for `func`. + pub fn nested_call(func: Func) -> Self { + Self::Call { + kind: CallKind::Nested, + func, + } + } + + /// Creates a returning tail call [`CallOutcome`] for `func`. + #[allow(dead_code)] // TODO: remove + pub fn return_call(func: Func) -> Self { + Self::Call { + kind: CallKind::Return, + func, + } + } } /// A unique engine index. @@ -684,8 +717,8 @@ impl<'engine> EngineExecutor<'engine> { } None => return Ok(()), }, - CallOutcome::NestedCall(called_func) => { - match called_func.as_internal(ctx.as_context()) { + CallOutcome::Call { func, .. } => { + match func.as_internal(ctx.as_context()) { FuncEntityInner::Wasm(wasm_func) => { *frame = self.stack.call_wasm(frame, wasm_func, &self.res.code_map)?; } @@ -702,7 +735,7 @@ impl<'engine> EngineExecutor<'engine> { .or_else(|trap| { // Push the calling function onto the Stack to make it possible to resume execution. self.stack.push_frame(*frame)?; - Err(TaggedTrap::host(called_func, trap)) + Err(TaggedTrap::host(func, trap)) })?; } } From 97ea69ff9e123f9003d0b6e3c4a1db4079c18b36 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 14:56:19 +0100 Subject: [PATCH 06/29] make StoreIdx base on NonZeroU32 --- crates/wasmi/src/store.rs | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/crates/wasmi/src/store.rs b/crates/wasmi/src/store.rs index a3bc12490f..baf33ea6fb 100644 --- a/crates/wasmi/src/store.rs +++ b/crates/wasmi/src/store.rs @@ -30,6 +30,7 @@ use crate::{ use core::{ fmt::{self, Debug}, sync::atomic::{AtomicU32, Ordering}, + num::NonZeroU32, }; use wasmi_arena::{Arena, ArenaIndex, ComponentVec, GuardedEntity}; use wasmi_core::TrapCode; @@ -40,28 +41,41 @@ use wasmi_core::TrapCode; /// /// Used to protect against invalid entity indices. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct StoreIdx(u32); +pub struct StoreIdx(NonZeroU32); impl ArenaIndex for StoreIdx { fn into_usize(self) -> usize { - self.0 as usize + self.0.get().wrapping_sub(1) as usize } fn from_usize(value: usize) -> Self { - let value = value.try_into().unwrap_or_else(|error| { - panic!("index {value} is out of bounds as store index: {error}") - }); - Self(value) + u32::try_from(value) + .map(|value| value.wrapping_add(1)) + .ok() + .and_then(NonZeroU32::new) + .map(Self) + .unwrap_or_else(|| panic!("value is out of bounds as store index: {value}")) } } impl StoreIdx { + /// Creates a new [`StoreIdx`] from the given `value`. + /// + /// # Panics + /// + /// If `value` is out of bounds. + fn from_u32(value: u32) -> Self { + NonZeroU32::new(value.wrapping_add(1)) + .map(Self) + .unwrap_or_else(|| panic!("value is out of bounds as store index: {value}")) + } + /// Returns a new unique [`StoreIdx`]. fn new() -> Self { /// A static store index counter. static CURRENT_STORE_IDX: AtomicU32 = AtomicU32::new(0); let next_idx = CURRENT_STORE_IDX.fetch_add(1, Ordering::AcqRel); - Self(next_idx) + Self::from_u32(next_idx) } } From afc4ef44ddcbd28f6bfd3c405335ea6b7149acc0 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 15:02:08 +0100 Subject: [PATCH 07/29] apply rustfmt --- crates/wasmi/src/store.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/wasmi/src/store.rs b/crates/wasmi/src/store.rs index baf33ea6fb..60fc8113d5 100644 --- a/crates/wasmi/src/store.rs +++ b/crates/wasmi/src/store.rs @@ -29,8 +29,8 @@ use crate::{ }; use core::{ fmt::{self, Debug}, - sync::atomic::{AtomicU32, Ordering}, num::NonZeroU32, + sync::atomic::{AtomicU32, Ordering}, }; use wasmi_arena::{Arena, ArenaIndex, ComponentVec, GuardedEntity}; use wasmi_core::TrapCode; @@ -60,9 +60,9 @@ impl ArenaIndex for StoreIdx { impl StoreIdx { /// Creates a new [`StoreIdx`] from the given `value`. - /// + /// /// # Panics - /// + /// /// If `value` is out of bounds. fn from_u32(value: u32) -> Self { NonZeroU32::new(value.wrapping_add(1)) From faf06115d9ec16192f503d5a2665744537e2174f Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 15:26:24 +0100 Subject: [PATCH 08/29] partially implement return_call[_indirect] --- crates/wasmi/src/engine/executor.rs | 83 ++++++++++++++++++----------- crates/wasmi/src/engine/mod.rs | 1 - 2 files changed, 53 insertions(+), 31 deletions(-) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 6090b6b57c..41a1d28b9f 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -482,11 +482,15 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { /// This also prepares the instruction pointer and stack pointer for /// the function call so that the stack and execution state is synchronized /// with the outer structures. - fn call_func(&mut self, func: &Func) -> Result { + fn call_func( + &mut self, + func: &Func, + make_outcome: fn(Func) -> CallOutcome, + ) -> Result { self.next_instr(); self.frame.update_ip(self.ip); self.sync_stack_ptr(); - Ok(CallOutcome::nested_call(*func)) + Ok(make_outcome(*func)) } /// Returns to the caller. @@ -549,6 +553,46 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { fn fuel_costs(&self) -> &FuelCosts { self.ctx.engine().config().fuel_costs() } + + /// Executes a `call` or `return_call` instruction. + fn execute_call( + &mut self, + func_index: FuncIdx, + make_outcome: fn(Func) -> CallOutcome, + ) -> Result { + let callee = self.cache.get_func(self.ctx, func_index.into_inner()); + self.call_func(&callee, make_outcome) + } + + /// Executes a `call_indirect` or `return_call_indirect` instruction. + fn execute_call_indirect( + &mut self, + table: TableIdx, + func_type: SignatureIdx, + make_outcome: fn(Func) -> CallOutcome, + ) -> Result { + let func_index: u32 = self.value_stack.pop_as(); + let table = self.cache.get_table(self.ctx, table); + let funcref = self + .ctx + .resolve_table(&table) + .get_untyped(func_index) + .map(FuncRef::from) + .ok_or(TrapCode::TableOutOfBounds)?; + let func = funcref.func().ok_or(TrapCode::IndirectCallToNull)?; + let actual_signature = self.ctx.get_func_type(func); + let expected_signature = self + .ctx + .resolve_instance(self.frame.instance()) + .get_signature(func_type.into_inner()) + .unwrap_or_else(|| { + panic!("missing signature for call_indirect at index: {func_type:?}") + }); + if actual_signature != expected_signature { + return Err(TrapCode::BadSignature).map_err(Into::into); + } + self.call_func(func, make_outcome) + } } #[derive(Debug, Copy, Clone)] @@ -650,21 +694,20 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.next_instr() } - fn visit_return_call(&mut self, _func_index: FuncIdx) -> Result { - todo!() + fn visit_return_call(&mut self, func_index: FuncIdx) -> Result { + self.execute_call(func_index, CallOutcome::return_call) } fn visit_return_call_indirect( &mut self, - _table: TableIdx, - _func_type: SignatureIdx, + table: TableIdx, + func_type: SignatureIdx, ) -> Result { - todo!() + self.execute_call_indirect(table, func_type, CallOutcome::return_call) } fn visit_call(&mut self, func_index: FuncIdx) -> Result { - let callee = self.cache.get_func(self.ctx, func_index.into_inner()); - self.call_func(&callee) + self.execute_call(func_index, CallOutcome::nested_call) } fn visit_call_indirect( @@ -672,27 +715,7 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { table: TableIdx, func_type: SignatureIdx, ) -> Result { - let func_index: u32 = self.value_stack.pop_as(); - let table = self.cache.get_table(self.ctx, table); - let funcref = self - .ctx - .resolve_table(&table) - .get_untyped(func_index) - .map(FuncRef::from) - .ok_or(TrapCode::TableOutOfBounds)?; - let func = funcref.func().ok_or(TrapCode::IndirectCallToNull)?; - let actual_signature = self.ctx.get_func_type(func); - let expected_signature = self - .ctx - .resolve_instance(self.frame.instance()) - .get_signature(func_type.into_inner()) - .unwrap_or_else(|| { - panic!("missing signature for call_indirect at index: {func_type:?}") - }); - if actual_signature != expected_signature { - return Err(TrapCode::BadSignature).map_err(Into::into); - } - self.call_func(func) + self.execute_call_indirect(table, func_type, CallOutcome::nested_call) } fn visit_const(&mut self, bytes: UntypedValue) { diff --git a/crates/wasmi/src/engine/mod.rs b/crates/wasmi/src/engine/mod.rs index f427e429c9..d6a7d56450 100644 --- a/crates/wasmi/src/engine/mod.rs +++ b/crates/wasmi/src/engine/mod.rs @@ -86,7 +86,6 @@ impl CallOutcome { } /// Creates a returning tail call [`CallOutcome`] for `func`. - #[allow(dead_code)] // TODO: remove pub fn return_call(func: Func) -> Self { Self::Call { kind: CallKind::Return, From 5802a8bae54063084070b236a4eac7ec0f43e70b Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 15:27:46 +0100 Subject: [PATCH 09/29] revert --- crates/wasmi/src/store.rs | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/crates/wasmi/src/store.rs b/crates/wasmi/src/store.rs index 60fc8113d5..a3bc12490f 100644 --- a/crates/wasmi/src/store.rs +++ b/crates/wasmi/src/store.rs @@ -29,7 +29,6 @@ use crate::{ }; use core::{ fmt::{self, Debug}, - num::NonZeroU32, sync::atomic::{AtomicU32, Ordering}, }; use wasmi_arena::{Arena, ArenaIndex, ComponentVec, GuardedEntity}; @@ -41,41 +40,28 @@ use wasmi_core::TrapCode; /// /// Used to protect against invalid entity indices. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct StoreIdx(NonZeroU32); +pub struct StoreIdx(u32); impl ArenaIndex for StoreIdx { fn into_usize(self) -> usize { - self.0.get().wrapping_sub(1) as usize + self.0 as usize } fn from_usize(value: usize) -> Self { - u32::try_from(value) - .map(|value| value.wrapping_add(1)) - .ok() - .and_then(NonZeroU32::new) - .map(Self) - .unwrap_or_else(|| panic!("value is out of bounds as store index: {value}")) + let value = value.try_into().unwrap_or_else(|error| { + panic!("index {value} is out of bounds as store index: {error}") + }); + Self(value) } } impl StoreIdx { - /// Creates a new [`StoreIdx`] from the given `value`. - /// - /// # Panics - /// - /// If `value` is out of bounds. - fn from_u32(value: u32) -> Self { - NonZeroU32::new(value.wrapping_add(1)) - .map(Self) - .unwrap_or_else(|| panic!("value is out of bounds as store index: {value}")) - } - /// Returns a new unique [`StoreIdx`]. fn new() -> Self { /// A static store index counter. static CURRENT_STORE_IDX: AtomicU32 = AtomicU32::new(0); let next_idx = CURRENT_STORE_IDX.fetch_add(1, Ordering::AcqRel); - Self::from_u32(next_idx) + Self(next_idx) } } From 616ed6ccdc577a9b27136f0a01576dad3d7a7c37 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 16:37:59 +0100 Subject: [PATCH 10/29] add DropKeep to return call bytecodes --- crates/wasmi/src/engine/bytecode/mod.rs | 6 +- crates/wasmi/src/engine/executor.rs | 19 ++- .../src/engine/func_builder/translator.rs | 115 ++++++++++-------- crates/wasmi/src/engine/mod.rs | 10 +- crates/wasmi/src/engine/stack/mod.rs | 6 +- 5 files changed, 99 insertions(+), 57 deletions(-) diff --git a/crates/wasmi/src/engine/bytecode/mod.rs b/crates/wasmi/src/engine/bytecode/mod.rs index 78e2e6292c..858409d001 100644 --- a/crates/wasmi/src/engine/bytecode/mod.rs +++ b/crates/wasmi/src/engine/bytecode/mod.rs @@ -53,8 +53,12 @@ pub enum Instruction { }, Return(DropKeep), ReturnIfNez(DropKeep), - ReturnCall(FuncIdx), + ReturnCall { + drop_keep: DropKeep, + func: FuncIdx, + }, ReturnCallIndirect { + drop_keep: DropKeep, table: TableIdx, func_type: SignatureIdx, }, diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 41a1d28b9f..30a63fb2d9 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -141,10 +141,14 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { return Ok(CallOutcome::Return); } } - Instr::ReturnCall(func) => return self.visit_return_call(func), - Instr::ReturnCallIndirect { table, func_type } => { - return self.visit_return_call_indirect(table, func_type) + Instr::ReturnCall { drop_keep, func } => { + return self.visit_return_call(drop_keep, func) } + Instr::ReturnCallIndirect { + drop_keep, + table, + func_type, + } => return self.visit_return_call_indirect(drop_keep, table, func_type), Instr::Call(func) => return self.visit_call(func), Instr::CallIndirect { table, func_type } => { return self.visit_call_indirect(table, func_type) @@ -694,15 +698,22 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { self.next_instr() } - fn visit_return_call(&mut self, func_index: FuncIdx) -> Result { + fn visit_return_call( + &mut self, + drop_keep: DropKeep, + func_index: FuncIdx, + ) -> Result { + self.value_stack.drop_keep(drop_keep); self.execute_call(func_index, CallOutcome::return_call) } fn visit_return_call_indirect( &mut self, + drop_keep: DropKeep, table: TableIdx, func_type: SignatureIdx, ) -> Result { + self.value_stack.drop_keep(drop_keep); self.execute_call_indirect(table, func_type, CallOutcome::return_call) } diff --git a/crates/wasmi/src/engine/func_builder/translator.rs b/crates/wasmi/src/engine/func_builder/translator.rs index dc9e0b38bb..c65b20e93f 100644 --- a/crates/wasmi/src/engine/func_builder/translator.rs +++ b/crates/wasmi/src/engine/func_builder/translator.rs @@ -695,43 +695,18 @@ impl<'parser> FuncTranslator<'parser> { panic!("tried to translate an unsupported Wasm operator: {name}") } - /// Translates `call` and `return_call`. - fn translate_call( - &mut self, - func_idx: u32, - make_instr: fn(bytecode::FuncIdx) -> Instruction, - ) -> Result<(), TranslationError> { - self.translate_if_reachable(|builder| { - builder.bump_fuel_consumption(builder.fuel_costs().call); - let func_idx = FuncIdx::from(func_idx); - let func_type = builder.func_type_of(func_idx); - builder.adjust_value_stack_for_call(&func_type); - let func_idx = func_idx.into_u32().into(); - builder.alloc.inst_builder.push_inst(make_instr(func_idx)); - Ok(()) - }) - } - - /// Translates `call_indirect` and `return_call_indirect`. - fn translate_call_indirect( - &mut self, - func_type_index: u32, - table_index: u32, - make_instr: fn(TableIdx, SignatureIdx) -> Instruction, - ) -> Result<(), TranslationError> { - self.translate_if_reachable(|builder| { - builder.bump_fuel_consumption(builder.fuel_costs().call); - let func_type_index = SignatureIdx::from(func_type_index); - let table = TableIdx::from(table_index); - builder.stack_height.pop1(); - let func_type = builder.func_type_at(func_type_index); - builder.adjust_value_stack_for_call(&func_type); - builder - .alloc - .inst_builder - .push_inst(make_instr(table, func_type_index)); - Ok(()) - }) + /// Computes how many values should be dropped and kept for the return call. + /// + /// # Panics + /// + /// If underflow of the value stack is detected. + fn drop_keep_return_call(&self, callee_type: &FuncType) -> Result { + debug_assert!(self.is_reachable()); + let drop_keep = self.drop_keep_return()?; + // For return calls we need to adjust the `keep` value to + // be equal to the amount of parameters the callee expects. + let keep = callee_type.params().len(); + DropKeep::new(drop_keep.drop(), keep).map_err(Into::into) } } @@ -1130,9 +1105,20 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { } fn visit_return_call(&mut self, func_idx: u32) -> Result<(), TranslationError> { - self.translate_call(func_idx, Instruction::ReturnCall)?; - self.reachable = false; - Ok(()) + self.translate_if_reachable(|builder| { + let func = bytecode::FuncIdx::from(func_idx); + let func_type = builder.func_type_of(func_idx.into()); + let drop_keep = builder.drop_keep_return_call(&func_type)?; + builder.adjust_value_stack_for_call(&func_type); + builder.bump_fuel_consumption(builder.fuel_costs().call); + builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs())); + builder + .alloc + .inst_builder + .push_inst(Instruction::ReturnCall { drop_keep, func }); + builder.reachable = false; + Ok(()) + }) } fn visit_return_call_indirect( @@ -1140,15 +1126,39 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { func_type_index: u32, table_index: u32, ) -> Result<(), TranslationError> { - self.translate_call_indirect(func_type_index, table_index, |table, func_type| { - Instruction::ReturnCallIndirect { table, func_type } - })?; - self.reachable = false; - Ok(()) + self.translate_if_reachable(|builder| { + builder.bump_fuel_consumption(builder.fuel_costs().call); + let signature = SignatureIdx::from(func_type_index); + let func_type = builder.func_type_at(signature); + let table = TableIdx::from(table_index); + let drop_keep = builder.drop_keep_return_call(&func_type)?; + builder.stack_height.pop1(); + builder.adjust_value_stack_for_call(&func_type); + builder + .alloc + .inst_builder + .push_inst(Instruction::ReturnCallIndirect { + drop_keep, + table, + func_type: signature, + }); + Ok(()) + }) } fn visit_call(&mut self, func_idx: u32) -> Result<(), TranslationError> { - self.translate_call(func_idx, Instruction::Call) + self.translate_if_reachable(|builder| { + builder.bump_fuel_consumption(builder.fuel_costs().call); + let func_idx = FuncIdx::from(func_idx); + let func_type = builder.func_type_of(func_idx); + builder.adjust_value_stack_for_call(&func_type); + let func_idx = func_idx.into_u32().into(); + builder + .alloc + .inst_builder + .push_inst(Instruction::Call(func_idx)); + Ok(()) + }) } fn visit_call_indirect( @@ -1157,8 +1167,17 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { table_index: u32, _table_byte: u8, ) -> Result<(), TranslationError> { - self.translate_call_indirect(func_type_index, table_index, |table, func_type| { - Instruction::CallIndirect { table, func_type } + self.translate_if_reachable(|builder| { + builder.bump_fuel_consumption(builder.fuel_costs().call); + let func_type = SignatureIdx::from(func_type_index); + let table = TableIdx::from(table_index); + builder.stack_height.pop1(); + builder.adjust_value_stack_for_call(&builder.func_type_at(func_type)); + builder + .alloc + .inst_builder + .push_inst(Instruction::CallIndirect { table, func_type }); + Ok(()) }) } diff --git a/crates/wasmi/src/engine/mod.rs b/crates/wasmi/src/engine/mod.rs index d6a7d56450..beab1f3f00 100644 --- a/crates/wasmi/src/engine/mod.rs +++ b/crates/wasmi/src/engine/mod.rs @@ -716,10 +716,12 @@ impl<'engine> EngineExecutor<'engine> { } None => return Ok(()), }, - CallOutcome::Call { func, .. } => { + CallOutcome::Call { func, kind } => { match func.as_internal(ctx.as_context()) { FuncEntityInner::Wasm(wasm_func) => { - *frame = self.stack.call_wasm(frame, wasm_func, &self.res.code_map)?; + *frame = + self.stack + .call_wasm(frame, wasm_func, &self.res.code_map, kind)?; } FuncEntityInner::Host(host_func) => { cache.reset_default_memory_bytes(); @@ -733,7 +735,9 @@ impl<'engine> EngineExecutor<'engine> { ) .or_else(|trap| { // Push the calling function onto the Stack to make it possible to resume execution. - self.stack.push_frame(*frame)?; + if matches!(kind, CallKind::Nested) { + self.stack.push_frame(*frame)?; + } Err(TaggedTrap::host(func, trap)) })?; } diff --git a/crates/wasmi/src/engine/stack/mod.rs b/crates/wasmi/src/engine/stack/mod.rs index 52f0f73fff..8436fbb0e0 100644 --- a/crates/wasmi/src/engine/stack/mod.rs +++ b/crates/wasmi/src/engine/stack/mod.rs @@ -8,6 +8,7 @@ pub use self::{ use super::{ code_map::{CodeMap, InstructionPtr}, func_types::FuncTypeRegistry, + CallKind, FuncParams, }; use crate::{ @@ -188,9 +189,12 @@ impl Stack { caller: &FuncFrame, wasm_func: &WasmFuncEntity, code_map: &CodeMap, + kind: CallKind, ) -> Result { let ip = self.call_wasm_impl(wasm_func, code_map)?; - self.frames.push(*caller)?; + if matches!(kind, CallKind::Nested) { + self.frames.push(*caller)?; + } let instance = wasm_func.instance(); let frame = FuncFrame::new(ip, instance); Ok(frame) From 4ab5c2467632e10805959ab7ce9d1827245c38d8 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 17:07:12 +0100 Subject: [PATCH 11/29] create unique drop_keep for return calls --- .../src/engine/func_builder/translator.rs | 62 +++++++++++++------ 1 file changed, 42 insertions(+), 20 deletions(-) diff --git a/crates/wasmi/src/engine/func_builder/translator.rs b/crates/wasmi/src/engine/func_builder/translator.rs index c65b20e93f..2debc33166 100644 --- a/crates/wasmi/src/engine/func_builder/translator.rs +++ b/crates/wasmi/src/engine/func_builder/translator.rs @@ -274,6 +274,24 @@ impl<'parser> FuncTranslator<'parser> { Ok(()) } + /// Return the value stack height difference to the height at the given `depth`. + /// + /// # Panics + /// + /// - If the current code is unreachable. + fn height_diff(&self, depth: u32) -> u32 { + debug_assert!(self.is_reachable()); + let current_height = self.stack_height.height(); + let frame = self.alloc.control_frames.nth_back(depth); + let origin_height = frame.stack_height().expect("frame is reachable"); + assert!( + origin_height <= current_height, + "encountered value stack underflow: \ + current height {current_height}, original height {origin_height}", + ); + current_height - origin_height + } + /// Computes how many values should be dropped and kept for the specific branch. /// /// # Panics @@ -290,14 +308,7 @@ impl<'parser> FuncTranslator<'parser> { ControlFrameKind::Loop => frame.block_type().len_params(self.res.engine()), }; // Find out how many values we need to drop. - let current_height = self.stack_height.height(); - let origin_height = frame.stack_height().expect("frame is reachable"); - assert!( - origin_height <= current_height, - "encountered value stack underflow: \ - current height {current_height}, original height {origin_height}", - ); - let height_diff = current_height - origin_height; + let height_diff = self.height_diff(depth); assert!( keep <= height_diff, "tried to keep {keep} values while having \ @@ -307,6 +318,15 @@ impl<'parser> FuncTranslator<'parser> { DropKeep::new(drop as usize, keep as usize).map_err(Into::into) } + /// Returns the maximum control stack depth at the current position in the code. + fn max_depth(&self) -> u32 { + self.alloc + .control_frames + .len() + .checked_sub(1) + .expect("control flow frame stack must not be empty") as u32 + } + /// Compute [`DropKeep`] for the return statement. /// /// # Panics @@ -319,12 +339,7 @@ impl<'parser> FuncTranslator<'parser> { !self.alloc.control_frames.is_empty(), "drop_keep_return cannot be called with the frame stack empty" ); - let max_depth = self - .alloc - .control_frames - .len() - .checked_sub(1) - .expect("control flow frame stack must not be empty") as u32; + let max_depth = self.max_depth(); let drop_keep = self.compute_drop_keep(max_depth)?; let len_params_locals = self.locals.len_registered() as usize; DropKeep::new( @@ -702,11 +717,19 @@ impl<'parser> FuncTranslator<'parser> { /// If underflow of the value stack is detected. fn drop_keep_return_call(&self, callee_type: &FuncType) -> Result { debug_assert!(self.is_reachable()); - let drop_keep = self.drop_keep_return()?; // For return calls we need to adjust the `keep` value to // be equal to the amount of parameters the callee expects. - let keep = callee_type.params().len(); - DropKeep::new(drop_keep.drop(), keep).map_err(Into::into) + let keep = callee_type.params().len() as u32; + // Find out how many values we need to drop. + let max_depth = self.max_depth(); + let height_diff = self.height_diff(max_depth); + assert!( + keep <= height_diff, + "tried to keep {keep} values while having \ + only {height_diff} values available on the frame", + ); + let drop = height_diff - keep; + DropKeep::new(drop as usize, keep as usize).map_err(Into::into) } } @@ -1109,7 +1132,6 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { let func = bytecode::FuncIdx::from(func_idx); let func_type = builder.func_type_of(func_idx.into()); let drop_keep = builder.drop_keep_return_call(&func_type)?; - builder.adjust_value_stack_for_call(&func_type); builder.bump_fuel_consumption(builder.fuel_costs().call); builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs())); builder @@ -1131,9 +1153,8 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { let signature = SignatureIdx::from(func_type_index); let func_type = builder.func_type_at(signature); let table = TableIdx::from(table_index); - let drop_keep = builder.drop_keep_return_call(&func_type)?; builder.stack_height.pop1(); - builder.adjust_value_stack_for_call(&func_type); + let drop_keep = builder.drop_keep_return_call(&func_type)?; builder .alloc .inst_builder @@ -1142,6 +1163,7 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { table, func_type: signature, }); + builder.reachable = false; Ok(()) }) } From b756bcbe1d696a8bf55c4e35ddad64a4abb92b88 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 17:12:15 +0100 Subject: [PATCH 12/29] fix bug in drop_keep_return_call method --- crates/wasmi/src/engine/func_builder/translator.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/wasmi/src/engine/func_builder/translator.rs b/crates/wasmi/src/engine/func_builder/translator.rs index 2debc33166..d9c63299f5 100644 --- a/crates/wasmi/src/engine/func_builder/translator.rs +++ b/crates/wasmi/src/engine/func_builder/translator.rs @@ -728,7 +728,8 @@ impl<'parser> FuncTranslator<'parser> { "tried to keep {keep} values while having \ only {height_diff} values available on the frame", ); - let drop = height_diff - keep; + let len_params_locals = self.locals.len_registered() as u32; + let drop = height_diff - keep + len_params_locals; DropKeep::new(drop as usize, keep as usize).map_err(Into::into) } } From a20f0161e00c45206f2acbd0054ebf826f95fbb7 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 17:12:28 +0100 Subject: [PATCH 13/29] wasm_return_call works now --- crates/wasmi/tests/spec/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/wasmi/tests/spec/mod.rs b/crates/wasmi/tests/spec/mod.rs index 3d858fe54d..ca31f2c1ed 100644 --- a/crates/wasmi/tests/spec/mod.rs +++ b/crates/wasmi/tests/spec/mod.rs @@ -96,7 +96,6 @@ define_spec_tests! { fn wasm_bulk("bulk"); fn wasm_call("call"); fn wasm_call_indirect("call_indirect"); - #[should_panic] fn wasm_return_call("proposals/tail-call/return_call"); #[should_panic] From 1cd11a8ce217caed56b22bf3df6cb296eddf45d6 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 17:12:57 +0100 Subject: [PATCH 14/29] apply clippy suggestion --- crates/wasmi/src/engine/func_builder/translator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasmi/src/engine/func_builder/translator.rs b/crates/wasmi/src/engine/func_builder/translator.rs index d9c63299f5..de574942d0 100644 --- a/crates/wasmi/src/engine/func_builder/translator.rs +++ b/crates/wasmi/src/engine/func_builder/translator.rs @@ -728,7 +728,7 @@ impl<'parser> FuncTranslator<'parser> { "tried to keep {keep} values while having \ only {height_diff} values available on the frame", ); - let len_params_locals = self.locals.len_registered() as u32; + let len_params_locals = self.locals.len_registered(); let drop = height_diff - keep + len_params_locals; DropKeep::new(drop as usize, keep as usize).map_err(Into::into) } From 8cc5bbd407056c84129af9c10e33abd65b5cc82d Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 17:17:41 +0100 Subject: [PATCH 15/29] properly charge for drop_keep in return_call_indirect --- crates/wasmi/src/engine/func_builder/translator.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/crates/wasmi/src/engine/func_builder/translator.rs b/crates/wasmi/src/engine/func_builder/translator.rs index de574942d0..9a52d8f5a3 100644 --- a/crates/wasmi/src/engine/func_builder/translator.rs +++ b/crates/wasmi/src/engine/func_builder/translator.rs @@ -1150,12 +1150,13 @@ impl<'a> VisitOperator<'a> for FuncTranslator<'a> { table_index: u32, ) -> Result<(), TranslationError> { self.translate_if_reachable(|builder| { - builder.bump_fuel_consumption(builder.fuel_costs().call); let signature = SignatureIdx::from(func_type_index); let func_type = builder.func_type_at(signature); let table = TableIdx::from(table_index); builder.stack_height.pop1(); let drop_keep = builder.drop_keep_return_call(&func_type)?; + builder.bump_fuel_consumption(builder.fuel_costs().call); + builder.bump_fuel_consumption(drop_keep.fuel_consumption(builder.fuel_costs())); builder .alloc .inst_builder From 7fa9148c2dd43b097a040ab7dd00e9d1ae11bd37 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 17:21:51 +0100 Subject: [PATCH 16/29] fix return_call_indirect --- crates/wasmi/src/engine/executor.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 30a63fb2d9..2111905bd9 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -572,10 +572,10 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { fn execute_call_indirect( &mut self, table: TableIdx, + func_index: u32, func_type: SignatureIdx, make_outcome: fn(Func) -> CallOutcome, ) -> Result { - let func_index: u32 = self.value_stack.pop_as(); let table = self.cache.get_table(self.ctx, table); let funcref = self .ctx @@ -713,8 +713,9 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { table: TableIdx, func_type: SignatureIdx, ) -> Result { + let func_index: u32 = self.value_stack.pop_as(); self.value_stack.drop_keep(drop_keep); - self.execute_call_indirect(table, func_type, CallOutcome::return_call) + self.execute_call_indirect(table, func_index, func_type, CallOutcome::return_call) } fn visit_call(&mut self, func_index: FuncIdx) -> Result { @@ -726,7 +727,8 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { table: TableIdx, func_type: SignatureIdx, ) -> Result { - self.execute_call_indirect(table, func_type, CallOutcome::nested_call) + let func_index: u32 = self.value_stack.pop_as(); + self.execute_call_indirect(table, func_index, func_type, CallOutcome::nested_call) } fn visit_const(&mut self, bytes: UntypedValue) { From 48f7324c5a0ac726d2b2e6cac82212fd4ad316d9 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 17:22:05 +0100 Subject: [PATCH 17/29] enable return_call_indirect Wasm spec test case --- crates/wasmi/tests/spec/mod.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/wasmi/tests/spec/mod.rs b/crates/wasmi/tests/spec/mod.rs index ca31f2c1ed..5475b4b4d1 100644 --- a/crates/wasmi/tests/spec/mod.rs +++ b/crates/wasmi/tests/spec/mod.rs @@ -97,7 +97,6 @@ define_spec_tests! { fn wasm_call("call"); fn wasm_call_indirect("call_indirect"); fn wasm_return_call("proposals/tail-call/return_call"); - #[should_panic] fn wasm_return_call_indirect("proposals/tail-call/return_call_indirect"); fn wasm_comments("comments"); From ffd7248e497802e721c05cac0afb4e55e4927376 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 18:25:27 +0100 Subject: [PATCH 18/29] fix performance regressions --- crates/wasmi/src/engine/executor.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 2111905bd9..00b5692dd1 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -486,6 +486,7 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { /// This also prepares the instruction pointer and stack pointer for /// the function call so that the stack and execution state is synchronized /// with the outer structures. + #[inline(always)] fn call_func( &mut self, func: &Func, @@ -559,6 +560,7 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { } /// Executes a `call` or `return_call` instruction. + #[inline(always)] fn execute_call( &mut self, func_index: FuncIdx, @@ -569,6 +571,7 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { } /// Executes a `call_indirect` or `return_call_indirect` instruction. + #[inline(always)] fn execute_call_indirect( &mut self, table: TableIdx, From 849edef9f2ee63d0590237b888042f9f0d6e134a Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 19:02:24 +0100 Subject: [PATCH 19/29] add fib_tail_recursive to benchmarks --- crates/wasmi/benches/bench/mod.rs | 1 + crates/wasmi/benches/benches.rs | 6 ++++++ crates/wasmi/benches/wat/fibonacci.wat | 24 +++++++++++++++++++++++- 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/wasmi/benches/bench/mod.rs b/crates/wasmi/benches/bench/mod.rs index b47df4bc45..9f9bf33a13 100644 --- a/crates/wasmi/benches/bench/mod.rs +++ b/crates/wasmi/benches/bench/mod.rs @@ -23,6 +23,7 @@ pub fn load_wasm_from_file(file_name: &str) -> Vec { /// Returns a [`Config`] useful for benchmarking. fn bench_config() -> Config { let mut config = Config::default(); + config.wasm_tail_call(true); config.set_stack_limits(StackLimits::new(1024, 1024 * 1024, 64 * 1024).unwrap()); config } diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index 2e92d97722..cb7763f1a0 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -925,6 +925,12 @@ fn bench_execute_fibonacci(c: &mut Criterion) { FIBONACCI_REC_N, FIBONACCI_REC_RESULT, ); + bench_fib( + "execute/fib_tail_recursive", + "fib_tail_recursive", + FIBONACCI_INC_N, + FIBONACCI_INC_RESULT, + ); bench_fib( "execute/fib_iterative", "fib_iterative", diff --git a/crates/wasmi/benches/wat/fibonacci.wat b/crates/wasmi/benches/wat/fibonacci.wat index 4b155191e3..e92f31eab6 100644 --- a/crates/wasmi/benches/wat/fibonacci.wat +++ b/crates/wasmi/benches/wat/fibonacci.wat @@ -16,7 +16,29 @@ ) ) - (func $fib_iterative (export "fib_iterative") (param $N i64) (result i64) + (func $fib_tail_recursive (param $N i64) (param $a i64) (param $b i64) (result i64) + (if (i64.eqz (local.get $N)) + (then + (return (local.get $a)) + ) + ) + (if (i64.eq (local.get $N) (i64.const 1)) + (then + (return (local.get $b)) + ) + ) + (return_call $fib_tail_recursive + (i64.sub (local.get $N) (i64.const 1)) + (local.get $b) + (i64.add (local.get $a) (local.get $b)) + ) + ) + + (func (export "fib_tail_recursive") (param $N i64) (result i64) + (return_call $fib_tail_recursive (local.get $N) (i64.const 0) (i64.const 1)) + ) + + (func (export "fib_iterative") (param $N i64) (result i64) (local $n1 i64) (local $n2 i64) (local $tmp i64) From 60418459cf768e40981d3377a265580de97ff426 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 19:31:36 +0100 Subject: [PATCH 20/29] add TODO comment for resumable calls --- crates/wasmi/src/engine/mod.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/crates/wasmi/src/engine/mod.rs b/crates/wasmi/src/engine/mod.rs index beab1f3f00..217dc4847c 100644 --- a/crates/wasmi/src/engine/mod.rs +++ b/crates/wasmi/src/engine/mod.rs @@ -736,6 +736,9 @@ impl<'engine> EngineExecutor<'engine> { .or_else(|trap| { // Push the calling function onto the Stack to make it possible to resume execution. if matches!(kind, CallKind::Nested) { + // TODO: for resumable calls we might need to remove this check again + // and instead always push a frame before a host call since that + // is what a call resumption expects. self.stack.push_frame(*frame)?; } Err(TaggedTrap::host(func, trap)) From 5505735d1cc796feb1d2aa414ab353f54f838307 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 19:33:50 +0100 Subject: [PATCH 21/29] add more TODO comments for resumable calls --- crates/wasmi/src/engine/executor.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 00b5692dd1..a73b2c3ece 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -706,6 +706,9 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { drop_keep: DropKeep, func_index: FuncIdx, ) -> Result { + // TODO: If an executor executes in a resumable way we actually need to convert all + // tail calls to host functions to normal calls since the call resumption expects + // the caller on the stack. self.value_stack.drop_keep(drop_keep); self.execute_call(func_index, CallOutcome::return_call) } @@ -717,6 +720,9 @@ impl<'ctx, 'engine, 'func> Executor<'ctx, 'engine, 'func> { func_type: SignatureIdx, ) -> Result { let func_index: u32 = self.value_stack.pop_as(); + // TODO: If an executor executes in a resumable way we actually need to convert all + // tail calls to host functions to normal calls since the call resumption expects + // the caller on the stack. self.value_stack.drop_keep(drop_keep); self.execute_call_indirect(table, func_index, func_type, CallOutcome::return_call) } From b55bda21afeb97bbc00c393e35454832a85582f4 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Thu, 16 Feb 2023 19:48:37 +0100 Subject: [PATCH 22/29] comment out fib_tail_recursive This is just temporary so that the CI produces benchmarks again. --- crates/wasmi/benches/benches.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/wasmi/benches/benches.rs b/crates/wasmi/benches/benches.rs index cb7763f1a0..b65bdb9905 100644 --- a/crates/wasmi/benches/benches.rs +++ b/crates/wasmi/benches/benches.rs @@ -925,12 +925,12 @@ fn bench_execute_fibonacci(c: &mut Criterion) { FIBONACCI_REC_N, FIBONACCI_REC_RESULT, ); - bench_fib( - "execute/fib_tail_recursive", - "fib_tail_recursive", - FIBONACCI_INC_N, - FIBONACCI_INC_RESULT, - ); + // bench_fib( + // "execute/fib_tail_recursive", + // "fib_tail_recursive", + // FIBONACCI_INC_N, + // FIBONACCI_INC_RESULT, + // ); bench_fib( "execute/fib_iterative", "fib_iterative", From 51b221fb3d2a61c9edfe0ba1d40934b41128b7b8 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Tue, 28 Feb 2023 11:54:46 +0100 Subject: [PATCH 23/29] make use of return_call in benchmark .wat --- crates/wasmi/benches/wat/fibonacci.wat | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wasmi/benches/wat/fibonacci.wat b/crates/wasmi/benches/wat/fibonacci.wat index eb65fc5a58..4f095caa87 100644 --- a/crates/wasmi/benches/wat/fibonacci.wat +++ b/crates/wasmi/benches/wat/fibonacci.wat @@ -27,7 +27,7 @@ (return (local.get $b)) ) ) - (call $fib_tail_recursive + (return_call $fib_tail_recursive (i64.sub (local.get $N) (i64.const 1)) (local.get $b) (i64.add (local.get $a) (local.get $b)) @@ -35,7 +35,7 @@ ) (func (export "fibonacci_tail") (param $N i64) (result i64) - (call $fib_tail_recursive (local.get $N) (i64.const 0) (i64.const 1)) + (return_call $fib_tail_recursive (local.get $N) (i64.const 0) (i64.const 1)) ) (func $fib_iterative (export "fibonacci_iter") (param $N i64) (result i64) From 83c0725f7e207392e972a32b5f083bdaf658563c Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Tue, 28 Feb 2023 12:30:40 +0100 Subject: [PATCH 24/29] refactor engine executor calls --- crates/wasmi/src/engine/executor.rs | 46 +++++++++++++++++++---------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index d26f22d436..3b5e4ccf83 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -14,7 +14,7 @@ use crate::{ TableIdx, }, cache::InstanceCache, - code_map::{CodeMap, InstructionPtr}, + code_map::{CodeMap, InstructionPtr, FuncBody}, config::FuelCosts, stack::{CallStack, ValueStackPtr}, DropKeep, @@ -25,6 +25,7 @@ use crate::{ table::TableEntity, Func, FuncRef, + Instance, StoreInner, Table, }; @@ -547,25 +548,38 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { fn call_func(&mut self, func: &Func, kind: CallKind) -> Result { self.next_instr(); self.sync_stack_ptr(); - let wasm_func = match self.ctx.resolve_func(func) { + match self.ctx.resolve_func(func) { FuncEntity::Wasm(wasm_func) => { - if matches!(kind, CallKind::Nested) { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - } - wasm_func - } - FuncEntity::Host(_host_func) => { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - self.cache.reset(); - return Ok(CallOutcome::Call(*func)); + self.call_wasm_func(wasm_func.func_body(), *wasm_func.instance(), kind) } - }; - let header = self.code_map.header(wasm_func.func_body()); + FuncEntity::Host(_host_func) => self.call_host_func(func), + } + } + + #[inline(always)] + #[cold] + fn call_host_func(&mut self, func: &Func) -> Result { + self.call_stack + .push(FuncFrame::new(self.ip, self.cache.instance()))?; + self.cache.reset(); + Ok(CallOutcome::Call(*func)) + } + + #[inline(always)] + fn call_wasm_func( + &mut self, + func_body: FuncBody, + instance: Instance, + kind: CallKind, + ) -> Result { + if matches!(kind, CallKind::Nested) { + self.call_stack + .push(FuncFrame::new(self.ip, self.cache.instance()))?; + } + let header = self.code_map.header(func_body); self.value_stack.prepare_wasm_call(header)?; self.sp = self.value_stack.stack_ptr(); - self.cache.update_instance(wasm_func.instance()); + self.cache.update_instance(&instance); self.ip = self.code_map.instr_ptr(header.iref()); Ok(CallOutcome::Continue) } From d50ac6081fe7da81130ee0a0acffa1b811602a88 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Tue, 28 Feb 2023 12:31:08 +0100 Subject: [PATCH 25/29] apply rustfmt --- crates/wasmi/src/engine/executor.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 3b5e4ccf83..a1e3fa8f31 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -14,7 +14,7 @@ use crate::{ TableIdx, }, cache::InstanceCache, - code_map::{CodeMap, InstructionPtr, FuncBody}, + code_map::{CodeMap, FuncBody, InstructionPtr}, config::FuelCosts, stack::{CallStack, ValueStackPtr}, DropKeep, From ec3f2c23b85055b860671a102a7f6788b985173f Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Tue, 28 Feb 2023 12:32:10 +0100 Subject: [PATCH 26/29] add doc comments --- crates/wasmi/src/engine/executor.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index a1e3fa8f31..6806ca1d1b 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -556,6 +556,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { } } + /// Calls the host function. #[inline(always)] #[cold] fn call_host_func(&mut self, func: &Func) -> Result { @@ -565,6 +566,7 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { Ok(CallOutcome::Call(*func)) } + /// Calls the Wasm function. #[inline(always)] fn call_wasm_func( &mut self, From a93949e7f1a5a864d370de85a9437266f60e1d4f Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Tue, 28 Feb 2023 13:03:14 +0100 Subject: [PATCH 27/29] revert changes as it slowed down Wasm targets --- crates/wasmi/src/engine/executor.rs | 51 ++++++++++------------------- 1 file changed, 17 insertions(+), 34 deletions(-) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index 6806ca1d1b..fc20fdacfb 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -14,7 +14,7 @@ use crate::{ TableIdx, }, cache::InstanceCache, - code_map::{CodeMap, FuncBody, InstructionPtr}, + code_map::{CodeMap, InstructionPtr}, config::FuelCosts, stack::{CallStack, ValueStackPtr}, DropKeep, @@ -25,7 +25,6 @@ use crate::{ table::TableEntity, Func, FuncRef, - Instance, StoreInner, Table, }; @@ -550,40 +549,24 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { self.sync_stack_ptr(); match self.ctx.resolve_func(func) { FuncEntity::Wasm(wasm_func) => { - self.call_wasm_func(wasm_func.func_body(), *wasm_func.instance(), kind) + if matches!(kind, CallKind::Nested) { + self.call_stack + .push(FuncFrame::new(self.ip, self.cache.instance()))?; + } + let header = self.code_map.header(wasm_func.func_body()); + self.value_stack.prepare_wasm_call(header)?; + self.sp = self.value_stack.stack_ptr(); + self.cache.update_instance(wasm_func.instance()); + self.ip = self.code_map.instr_ptr(header.iref()); + Ok(CallOutcome::Continue) + } + FuncEntity::Host(_host_func) => { + self.call_stack + .push(FuncFrame::new(self.ip, self.cache.instance()))?; + self.cache.reset(); + Ok(CallOutcome::Call(*func)) } - FuncEntity::Host(_host_func) => self.call_host_func(func), - } - } - - /// Calls the host function. - #[inline(always)] - #[cold] - fn call_host_func(&mut self, func: &Func) -> Result { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - self.cache.reset(); - Ok(CallOutcome::Call(*func)) - } - - /// Calls the Wasm function. - #[inline(always)] - fn call_wasm_func( - &mut self, - func_body: FuncBody, - instance: Instance, - kind: CallKind, - ) -> Result { - if matches!(kind, CallKind::Nested) { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; } - let header = self.code_map.header(func_body); - self.value_stack.prepare_wasm_call(header)?; - self.sp = self.value_stack.stack_ptr(); - self.cache.update_instance(&instance); - self.ip = self.code_map.instr_ptr(header.iref()); - Ok(CallOutcome::Continue) } /// Returns to the caller. From 12efc9b3200a7eefb2482d51f6bef66c110e9c43 Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Wed, 1 Mar 2023 10:53:50 +0100 Subject: [PATCH 28/29] add tests for resumable calls + tail calls --- crates/wasmi/tests/e2e/v1/resumable_call.rs | 80 ++++++++++++++++++--- 1 file changed, 71 insertions(+), 9 deletions(-) diff --git a/crates/wasmi/tests/e2e/v1/resumable_call.rs b/crates/wasmi/tests/e2e/v1/resumable_call.rs index a264b9719d..d2d9d73824 100644 --- a/crates/wasmi/tests/e2e/v1/resumable_call.rs +++ b/crates/wasmi/tests/e2e/v1/resumable_call.rs @@ -3,6 +3,7 @@ use core::slice; use wasmi::{ + Config, Engine, Error, Extern, @@ -13,6 +14,7 @@ use wasmi::{ ResumableCall, ResumableInvocation, Store, + TypedFunc, TypedResumableCall, TypedResumableInvocation, Value, @@ -20,13 +22,15 @@ use wasmi::{ use wasmi_core::{Trap, TrapCode, ValueType}; fn test_setup() -> (Store<()>, Linker<()>) { - let engine = Engine::default(); + let mut config = Config::default(); + config.wasm_tail_call(true); + let engine = Engine::new(&config); let store = Store::new(&engine, ()); let linker = >::new(&engine); (store, linker) } -fn resumable_call_smoldot_common(wasm: &str) -> (Store<()>, TypedResumableInvocation) { +fn resumable_call_smoldot_common(wasm: &str) -> (Store<()>, TypedFunc<(), i32>) { let (mut store, mut linker) = test_setup(); // The important part about this test is that this // host function has more results than parameters. @@ -47,16 +51,29 @@ fn resumable_call_smoldot_common(wasm: &str) -> (Store<()>, TypedResumableInvoca .start(&mut store) .unwrap(); let wasm_fn = instance.get_typed_func::<(), i32>(&store, "test").unwrap(); - let invocation = match wasm_fn.call_resumable(&mut store, ()).unwrap() { - TypedResumableCall::Resumable(invocation) => invocation, - TypedResumableCall::Finished(_) => panic!("expected TypedResumableCall::Resumable"), - }; - (store, invocation) + (store, wasm_fn) +} + +pub trait UnwrapResumable { + type Results; + + fn unwrap_resumable(self) -> TypedResumableInvocation; +} + +impl UnwrapResumable for Result, Trap> { + type Results = Results; + + fn unwrap_resumable(self) -> TypedResumableInvocation { + match self.unwrap() { + TypedResumableCall::Resumable(invocation) => invocation, + TypedResumableCall::Finished(_) => panic!("expected TypedResumableCall::Resumable"), + } + } } #[test] fn resumable_call_smoldot_01() { - let (mut store, invocation) = resumable_call_smoldot_common( + let (mut store, wasm_fn) = resumable_call_smoldot_common( r#" (module (import "env" "host_fn" (func $host_fn (result i32))) @@ -66,6 +83,50 @@ fn resumable_call_smoldot_01() { ) "#, ); + let invocation = wasm_fn.call_resumable(&mut store, ()).unwrap_resumable(); + match invocation.resume(&mut store, &[Value::I32(42)]).unwrap() { + TypedResumableCall::Finished(result) => assert_eq!(result, 42), + TypedResumableCall::Resumable(_) => panic!("expected TypeResumableCall::Finished"), + } +} + +#[test] +fn resumable_call_smoldot_tail_01() { + let (mut store, wasm_fn) = resumable_call_smoldot_common( + r#" + (module + (import "env" "host_fn" (func $host_fn (result i32))) + (func (export "test") (result i32) + (return_call $host_fn) + ) + ) + "#, + ); + assert_eq!( + wasm_fn + .call_resumable(&mut store, ()) + .unwrap_err() + .i32_exit_status(), + Some(100), + ); +} + +#[test] +fn resumable_call_smoldot_tail_02() { + let (mut store, wasm_fn) = resumable_call_smoldot_common( + r#" + (module + (import "env" "host_fn" (func $host (result i32))) + (func $wasm (result i32) + (return_call $host) + ) + (func (export "test") (result i32) + (call $wasm) + ) + ) + "#, + ); + let invocation = wasm_fn.call_resumable(&mut store, ()).unwrap_resumable(); match invocation.resume(&mut store, &[Value::I32(42)]).unwrap() { TypedResumableCall::Finished(result) => assert_eq!(result, 42), TypedResumableCall::Resumable(_) => panic!("expected TypeResumableCall::Finished"), @@ -74,7 +135,7 @@ fn resumable_call_smoldot_01() { #[test] fn resumable_call_smoldot_02() { - let (mut store, invocation) = resumable_call_smoldot_common( + let (mut store, wasm_fn) = resumable_call_smoldot_common( r#" (module (import "env" "host_fn" (func $host_fn (result i32))) @@ -91,6 +152,7 @@ fn resumable_call_smoldot_02() { ) "#, ); + let invocation = wasm_fn.call_resumable(&mut store, ()).unwrap_resumable(); match invocation.resume(&mut store, &[Value::I32(42)]).unwrap() { TypedResumableCall::Finished(result) => assert_eq!(result, 11), TypedResumableCall::Resumable(_) => panic!("expected TypeResumableCall::Finished"), From f4b461263303ea9bb642d5098b609ef914323ebe Mon Sep 17 00:00:00 2001 From: Robin Freyler Date: Wed, 1 Mar 2023 10:54:11 +0100 Subject: [PATCH 29/29] fix resumable tail calls edge case --- crates/wasmi/src/engine/executor.rs | 64 +++++++++++++++++++--------- crates/wasmi/src/engine/mod.rs | 29 +++++++++++-- crates/wasmi/src/engine/stack/mod.rs | 19 +-------- 3 files changed, 71 insertions(+), 41 deletions(-) diff --git a/crates/wasmi/src/engine/executor.rs b/crates/wasmi/src/engine/executor.rs index fc20fdacfb..3b95c5dde3 100644 --- a/crates/wasmi/src/engine/executor.rs +++ b/crates/wasmi/src/engine/executor.rs @@ -25,6 +25,7 @@ use crate::{ table::TableEntity, Func, FuncRef, + Instance, StoreInner, Table, }; @@ -42,7 +43,7 @@ pub enum WasmOutcome { /// The Wasm execution has ended and returns to the host side. Return, /// The Wasm execution calls a host function. - Call(Func), + Call { host_func: Func, instance: Instance }, } /// The outcome of a Wasm execution. @@ -56,7 +57,7 @@ pub enum CallOutcome { /// The Wasm execution continues in Wasm. Continue, /// The Wasm execution calls a host function. - Call(Func), + Call { host_func: Func, instance: Instance }, } /// The kind of a function call. @@ -213,8 +214,15 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { } } Instr::ReturnCall { drop_keep, func } => { - if let CallOutcome::Call(host_func) = self.visit_return_call(drop_keep, func)? { - return Ok(WasmOutcome::Call(host_func)); + if let CallOutcome::Call { + host_func, + instance, + } = self.visit_return_call(drop_keep, func)? + { + return Ok(WasmOutcome::Call { + host_func, + instance, + }); } } Instr::ReturnCallIndirect { @@ -222,22 +230,39 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { table, func_type, } => { - if let CallOutcome::Call(host_func) = - self.visit_return_call_indirect(drop_keep, table, func_type)? + if let CallOutcome::Call { + host_func, + instance, + } = self.visit_return_call_indirect(drop_keep, table, func_type)? { - return Ok(WasmOutcome::Call(host_func)); + return Ok(WasmOutcome::Call { + host_func, + instance, + }); } } Instr::Call(func) => { - if let CallOutcome::Call(host_func) = self.visit_call(func)? { - return Ok(WasmOutcome::Call(host_func)); + if let CallOutcome::Call { + host_func, + instance, + } = self.visit_call(func)? + { + return Ok(WasmOutcome::Call { + host_func, + instance, + }); } } Instr::CallIndirect { table, func_type } => { - if let CallOutcome::Call(host_func) = - self.visit_call_indirect(table, func_type)? + if let CallOutcome::Call { + host_func, + instance, + } = self.visit_call_indirect(table, func_type)? { - return Ok(WasmOutcome::Call(host_func)); + return Ok(WasmOutcome::Call { + host_func, + instance, + }); } } Instr::Drop => self.visit_drop(), @@ -547,12 +572,12 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { fn call_func(&mut self, func: &Func, kind: CallKind) -> Result { self.next_instr(); self.sync_stack_ptr(); + if matches!(kind, CallKind::Nested) { + self.call_stack + .push(FuncFrame::new(self.ip, self.cache.instance()))?; + } match self.ctx.resolve_func(func) { FuncEntity::Wasm(wasm_func) => { - if matches!(kind, CallKind::Nested) { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - } let header = self.code_map.header(wasm_func.func_body()); self.value_stack.prepare_wasm_call(header)?; self.sp = self.value_stack.stack_ptr(); @@ -561,10 +586,11 @@ impl<'ctx, 'engine> Executor<'ctx, 'engine> { Ok(CallOutcome::Continue) } FuncEntity::Host(_host_func) => { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; self.cache.reset(); - Ok(CallOutcome::Call(*func)) + Ok(CallOutcome::Call { + host_func: *func, + instance: *self.cache.instance(), + }) } } } diff --git a/crates/wasmi/src/engine/mod.rs b/crates/wasmi/src/engine/mod.rs index 32f56476b2..8d1e598bba 100644 --- a/crates/wasmi/src/engine/mod.rs +++ b/crates/wasmi/src/engine/mod.rs @@ -677,14 +677,35 @@ impl<'engine> EngineExecutor<'engine> { loop { match self.execute_wasm(ctx.as_context_mut(), &mut cache)? { WasmOutcome::Return => return Ok(()), - WasmOutcome::Call(ref func) => { + WasmOutcome::Call { + ref host_func, + instance, + } => { + let func = host_func; let host_func = match ctx.as_context().store.inner.resolve_func(func) { FuncEntity::Wasm(_) => unreachable!("`func` must be a host function"), FuncEntity::Host(host_func) => *host_func, }; - self.stack - .call_host_from_wasm(ctx.as_context_mut(), host_func, &self.res.func_types) - .map_err(|trap| TaggedTrap::host(*func, trap))?; + let result = self.stack.call_host_impl( + ctx.as_context_mut(), + host_func, + Some(&instance), + &self.res.func_types, + ); + if self.stack.frames.peek().is_some() { + // Case: There is a frame on the call stack. + // + // This is the default case and we can easily make host function + // errors return a resumable call handle. + result.map_err(|trap| TaggedTrap::host(*func, trap))?; + } else { + // Case: No frame is on the call stack. (edge case) + // + // This can happen if the host function was called by a tail call. + // In this case we treat host function errors the same as if we called + // the host function as root and do not allow to resume the call. + result.map_err(TaggedTrap::Wasm)?; + } } } } diff --git a/crates/wasmi/src/engine/stack/mod.rs b/crates/wasmi/src/engine/stack/mod.rs index 98b72ae5d8..81b6dfc991 100644 --- a/crates/wasmi/src/engine/stack/mod.rs +++ b/crates/wasmi/src/engine/stack/mod.rs @@ -164,23 +164,6 @@ impl Stack { self.call_host_impl(ctx, host_func, None, func_types) } - /// Executes the given host function called by a Wasm function. - #[inline(always)] - pub fn call_host_from_wasm( - &mut self, - ctx: StoreContextMut, - host_func: HostFuncEntity, - func_types: &FuncTypeRegistry, - ) -> Result<(), Trap> { - let caller = self - .frames - .peek() - .copied() - .expect("must have a frame on the call stack"); - let instance = caller.instance(); - self.call_host_impl(ctx, host_func, Some(instance), func_types) - } - /// Executes the given host function. /// /// # Errors @@ -188,7 +171,7 @@ impl Stack { /// - If the host function returns a host side error or trap. /// - If the value stack overflowed upon pushing parameters or results. #[inline(always)] - fn call_host_impl( + pub fn call_host_impl( &mut self, ctx: StoreContextMut, host_func: HostFuncEntity,