Skip to content

Commit

Permalink
Optimize bare call overhead (#522)
Browse files Browse the repository at this point in the history
* add benchmarks to test root call overhead with different amounts of parameters and results

* optimize TypedFunc::call at cost of Func::call

* remove WasmTypeList::{values, from_values} trait methods

* refactor WasmTypeList::from_untyped_values impl for a single T

* remove WasmTypeList::{Values, ValuesIter} assoc types

* rename some WasmTypeList trait methods

* apply clippy suggestions

* replace raw for loop with for_each
  • Loading branch information
Robbepop authored Oct 18, 2022
1 parent f163134 commit 00bbcd0
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 141 deletions.
36 changes: 5 additions & 31 deletions crates/wasmi/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,23 +285,19 @@ impl EngineInner {
Results: CallResults,
{
self.initialize_args(params);
let signature = match func.as_internal(ctx.as_context()) {
match func.as_internal(ctx.as_context()) {
FuncEntityInternal::Wasm(wasm_func) => {
let signature = wasm_func.signature();
let mut frame = self.stack.call_wasm_root(wasm_func, &self.code_map)?;
let mut cache = InstanceCache::from(frame.instance());
self.execute_wasm_func(ctx.as_context_mut(), &mut frame, &mut cache)?;
signature
}
FuncEntityInternal::Host(host_func) => {
let signature = host_func.signature();
let host_func = host_func.clone();
self.stack
.call_host_root(ctx.as_context_mut(), host_func, &self.func_types)?;
signature
}
};
let results = self.write_results_back(signature, results);
let results = self.write_results_back(results);
Ok(results)
}

Expand All @@ -311,9 +307,7 @@ impl EngineInner {
Params: CallParams,
{
self.stack.clear();
for param in params.feed_params() {
self.stack.values.push(param);
}
self.stack.values.extend(params.call_params());
}

/// Writes the results of the function execution back into the `results` buffer.
Expand All @@ -325,31 +319,11 @@ impl EngineInner {
/// # Panics
///
/// - If the `results` buffer length does not match the remaining amount of stack values.
fn write_results_back<Results>(
&mut self,
func_type: DedupFuncType,
results: Results,
) -> <Results as CallResults>::Results
fn write_results_back<Results>(&mut self, results: Results) -> <Results as CallResults>::Results
where
Results: CallResults,
{
let result_types = self.func_types.resolve_func_type(func_type).results();
assert_eq!(
self.stack.values.len(),
results.len_results(),
"expected {} values on the stack after function execution but found {}",
results.len_results(),
self.stack.values.len(),
);
assert_eq!(results.len_results(), result_types.len());
results.feed_results(
self.stack
.values
.drain()
.iter()
.zip(result_types)
.map(|(raw_value, value_type)| raw_value.with_type(*value_type)),
)
results.call_results(self.stack.values.drain())
}

/// Executes the top most Wasm function on the [`Stack`] until the [`Stack`] is empty.
Expand Down
78 changes: 35 additions & 43 deletions crates/wasmi/src/engine/traits.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use crate::core::Value;
use core::{iter, slice};
use wasmi_core::UntypedValue;

/// Types implementing this trait may be used as parameters for function execution.
///
Expand All @@ -12,33 +13,45 @@ use core::{iter, slice};
/// [`Engine`]: [`crate::Engine`]
pub trait CallParams {
/// The iterator over the parameter values.
type Params: Iterator<Item = Value>;

/// Returns the number of given parameter values.
///
/// # Note
///
/// Used by the [`Engine`] to determine how many parameters are received.
///
/// [`Engine`]: [`crate::Engine`]
fn len_params(&self) -> usize;
type Params: ExactSizeIterator<Item = UntypedValue>;

/// Feeds the parameter values from the caller.
fn feed_params(self) -> Self::Params;
fn call_params(self) -> Self::Params;
}

impl<'a> CallParams for &'a [Value] {
type Params = iter::Copied<slice::Iter<'a, Value>>;
type Params = CallParamsValueIter<'a>;

fn len_params(&self) -> usize {
self.len()
#[inline]
fn call_params(self) -> Self::Params {
CallParamsValueIter {
iter: self.iter().copied(),
}
}
}

/// An iterator over the [`UntypedValue`] call parameters.
#[derive(Debug)]
pub struct CallParamsValueIter<'a> {
iter: iter::Copied<slice::Iter<'a, Value>>,
}

fn feed_params(self) -> Self::Params {
self.iter().copied()
impl<'a> Iterator for CallParamsValueIter<'a> {
type Item = UntypedValue;

#[inline]
fn size_hint(&self) -> (usize, Option<usize>) {
self.iter.size_hint()
}

#[inline]
fn next(&mut self) -> Option<Self::Item> {
self.iter.next().map(UntypedValue::from)
}
}

impl ExactSizeIterator for CallParamsValueIter<'_> {}

/// Types implementing this trait may be used as results for function execution.
///
/// # Note
Expand All @@ -53,43 +66,22 @@ pub trait CallResults {
/// The type of the returned results value.
type Results;

/// Returns the number of expected result values.
///
/// # Note
///
/// Used by the [`Engine`] to determine how many results are expected.
///
/// [`Engine`]: [`crate::Engine`]
fn len_results(&self) -> usize;

/// Feeds the result values back to the caller.
///
/// # Panics
///
/// If the given `results` do not match the expected amount.
fn feed_results<T>(self, results: T) -> Self::Results
where
T: IntoIterator<Item = Value>,
T::IntoIter: ExactSizeIterator;
fn call_results(self, results: &[UntypedValue]) -> Self::Results;
}

impl<'a> CallResults for &'a mut [Value] {
type Results = Self;

fn len_results(&self) -> usize {
self.len()
}

fn feed_results<T>(self, results: T) -> Self::Results
where
T: IntoIterator<Item = Value>,
T::IntoIter: ExactSizeIterator,
{
let results = results.into_iter();
assert_eq!(self.len_results(), results.len());
for (dst, src) in self.iter_mut().zip(results) {
*dst = src;
}
fn call_results(self, results: &[UntypedValue]) -> Self::Results {
assert_eq!(self.len(), results.len());
self.iter_mut().zip(results).for_each(|(dst, src)| {
*dst = src.with_type(dst.value_type());
});
self
}
}
84 changes: 38 additions & 46 deletions crates/wasmi/src/func/into_func.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ macro_rules! impl_into_func {
#[allow(non_snake_case)]
fn into_func(self) -> (FuncType, HostFuncTrampoline<T>) {
let signature = FuncType::new(
<Self::Params as WasmTypeList>::value_types(),
<Self::Results as WasmTypeList>::value_types(),
<Self::Params as WasmTypeList>::types(),
<Self::Results as WasmTypeList>::types(),
);
let trampoline = HostFuncTrampoline::new(
move |caller: Caller<T>, params_results: FuncParams| -> Result<FuncResults, Trap> {
Expand Down Expand Up @@ -197,31 +197,32 @@ pub trait WasmTypeList: DecodeUntypedSlice + EncodeUntypedSlice + Sized {
/// The [`ValueType`] sequence as array.
type Types: IntoIterator<IntoIter = Self::TypesIter, Item = ValueType>
+ AsRef<[ValueType]>
+ AsMut<[ValueType]>;
+ AsMut<[ValueType]>
+ Copy
+ Clone;

/// The iterator type of the sequence of [`ValueType`].
type TypesIter: ExactSizeIterator<Item = ValueType> + DoubleEndedIterator + FusedIterator;

/// The [`Value`] sequence as array.
type Values: IntoIterator<IntoIter = Self::ValuesIter, Item = Value>
+ AsRef<[Value]>
+ AsMut<[Value]>;
type Values: IntoIterator<IntoIter = Self::ValuesIter, Item = UntypedValue>
+ AsRef<[UntypedValue]>
+ AsMut<[UntypedValue]>
+ Copy
+ Clone;

/// The iterator type of the sequence of [`Value`].
type ValuesIter: ExactSizeIterator<Item = Value> + DoubleEndedIterator + FusedIterator;
type ValuesIter: ExactSizeIterator<Item = UntypedValue> + DoubleEndedIterator + FusedIterator;

/// Returns an array representing the [`ValueType`] sequence of `Self`.
fn value_types() -> Self::Types;
fn types() -> Self::Types;

/// Returns an array representing the [`Value`] sequence of `self`.
/// Returns an array representing the [`UntypedValue`] sequence of `self`.
fn values(self) -> Self::Values;

/// Consumes the [`Value`] iterator and creates `Self` if possible.
/// Consumes the [`UntypedValue`] iterator and creates `Self` if possible.
///
/// Returns `None` if construction of `Self` is impossible.
fn from_values<T>(values: T) -> Option<Self>
where
T: Iterator<Item = Value>;
fn from_values(values: &[UntypedValue]) -> Option<Self>;
}

impl<T1> WasmTypeList for T1
Expand All @@ -232,31 +233,25 @@ where

type Types = [ValueType; 1];
type TypesIter = array::IntoIter<ValueType, 1>;
type Values = [Value; 1];
type ValuesIter = array::IntoIter<Value, 1>;
type Values = [UntypedValue; 1];
type ValuesIter = array::IntoIter<UntypedValue, 1>;

#[inline]
fn value_types() -> Self::Types {
fn types() -> Self::Types {
[<T1 as WasmType>::value_type()]
}

#[inline]
fn values(self) -> Self::Values {
[<T1 as Into<Value>>::into(self)]
[<T1 as Into<UntypedValue>>::into(self)]
}

fn from_values<T>(mut values: T) -> Option<Self>
where
T: Iterator<Item = Value>,
{
let value: T1 = values.next().and_then(Value::try_into)?;
if values.next().is_some() {
// Note: If the iterator yielded more items than
// necessary we create no value from this procedure
// as it is likely a bug.
return None;
#[inline]
fn from_values(values: &[UntypedValue]) -> Option<Self> {
if let [value] = *values {
return Some(value.into());
}
Some(value)
None
}
}

Expand All @@ -272,37 +267,34 @@ macro_rules! impl_wasm_type_list {

type Types = [ValueType; $n];
type TypesIter = array::IntoIter<ValueType, $n>;
type Values = [Value; $n];
type ValuesIter = array::IntoIter<Value, $n>;
type Values = [UntypedValue; $n];
type ValuesIter = array::IntoIter<UntypedValue, $n>;

fn value_types() -> Self::Types {
#[inline]
fn types() -> Self::Types {
[$(
<$tuple as WasmType>::value_type()
),*]
}

#[inline]
#[allow(non_snake_case)]
fn values(self) -> Self::Values {
let ($($tuple,)*) = self;
[$(
<$tuple as Into<Value>>::into($tuple)
<$tuple as Into<UntypedValue>>::into($tuple)
),*]
}

fn from_values<T>(mut values: T) -> Option<Self>
where
T: Iterator<Item = Value>,
{
let result = ($(
values.next().and_then(Value::try_into::<$tuple>)?,
)*);
if values.next().is_some() {
// Note: If the iterator yielded more items than
// necessary we create no value from this procedure
// as it is likely a bug.
return None
#[inline]
#[allow(non_snake_case)]
fn from_values(values: &[UntypedValue]) -> Option<Self> {
if let [$($tuple),*] = *values {
return Some(
( $( Into::into($tuple), )* )
)
}
Some(result)
None
}
}
};
Expand Down
4 changes: 4 additions & 0 deletions crates/wasmi/src/func/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,10 @@ impl Func {
if expected_outputs.len() != outputs.len() {
return Err(FuncError::MismatchingResults { func: *self }).map_err(Into::into);
}
outputs
.iter_mut()
.zip(expected_outputs.iter().copied().map(Value::default))
.for_each(|(output, expected_output)| *output = expected_output);
// Note: Cloning an [`Engine`] is intentionally a cheap operation.
ctx.as_context().store.engine().clone().execute_func(
ctx.as_context_mut(),
Expand Down
Loading

0 comments on commit 00bbcd0

Please sign in to comment.