Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow host functions to call Wasm functions #590

Merged
merged 23 commits into from
Dec 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1c84111
add StashArena data structure to wasmi_arena
Robbepop Dec 5, 2022
467a450
rename wasmi_arena::Index to ArenaIndex
Robbepop Dec 5, 2022
6846175
import core::ops::Index[Mut] since wasmi_arena::Index got renamed
Robbepop Dec 5, 2022
12fd0f1
properly forward to Stash::is_empty
Robbepop Dec 5, 2022
29ff885
put shared engine resources behind RwLock
Robbepop Dec 7, 2022
612e76d
Merge branch 'master' of github.com:paritytech/wasmi into rf-host-to-…
Robbepop Dec 7, 2022
259ffe3
remove unnecessary clone call
Robbepop Dec 7, 2022
dc68ca3
remove redundant clone in stash tests
Robbepop Dec 7, 2022
865386b
Merge branch 'master' of github.com:paritytech/wasmi into rf-host-to-…
Robbepop Dec 7, 2022
0b18577
Merge branch 'master' of github.com:paritytech/wasmi into rf-host-to-…
Robbepop Dec 12, 2022
d046a0e
add proper Default impls to Stash[Arena]
Robbepop Dec 19, 2022
02a07bc
add EngineStacks struct
Robbepop Dec 22, 2022
dd10ac4
integrate new EngineStacks type
Robbepop Dec 22, 2022
fd4f944
add cached_stacks option to Config
Robbepop Dec 22, 2022
8984efb
add Config::cached_stacks option
Robbepop Dec 22, 2022
a8db8dc
fix various compile warnings and errors
Robbepop Dec 22, 2022
2dfd25c
add test for host calling wasm back
Robbepop Dec 22, 2022
cf6c690
remove [Arena]Stash data structure again
Robbepop Dec 22, 2022
c453d2a
apply clippy suggestions
Robbepop Dec 22, 2022
59b572a
apply rustfmt suggestions
Robbepop Dec 22, 2022
367246d
apply rustfmt suggestions v2
Robbepop Dec 22, 2022
b59fc93
apply rustfmt suggestions v3
Robbepop Dec 22, 2022
587d8dc
apply rustfmt suggestions v4
Robbepop Dec 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/wasmi/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ wasmi_arena = { version = "0.1", path = "../arena", default-features = false }
spin = { version = "0.9", default-features = false, features = [
"mutex",
"spin_mutex",
"rwlock",
] }

[dev-dependencies]
Expand Down
21 changes: 21 additions & 0 deletions crates/wasmi/src/engine/config.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
use super::stack::StackLimits;
use wasmparser::WasmFeatures;

/// The default amount of stacks kept in the cache at most.
const DEFAULT_CACHED_STACKS: usize = 2;

/// Configuration for an [`Engine`].
///
/// [`Engine`]: [`crate::Engine`]
#[derive(Debug, Copy, Clone)]
pub struct Config {
/// The limits set on the value stack and call stack.
stack_limits: StackLimits,
/// The amount of Wasm stacks to keep in cache at most.
cached_stacks: usize,
/// Is `true` if the `mutable-global` Wasm proposal is enabled.
mutable_global: bool,
/// Is `true` if the `sign-extension` Wasm proposal is enabled.
Expand All @@ -22,6 +27,7 @@ impl Default for Config {
fn default() -> Self {
Self {
stack_limits: StackLimits::default(),
cached_stacks: DEFAULT_CACHED_STACKS,
mutable_global: true,
sign_extension: true,
saturating_float_to_int: true,
Expand All @@ -42,6 +48,21 @@ impl Config {
self.stack_limits
}

/// Sets the maximum amount of cached stacks for reuse for the [`Config`].
///
/// # Note
///
/// Defaults to 2.
pub fn set_cached_stacks(&mut self, amount: usize) -> &mut Self {
self.cached_stacks = amount;
self
}

/// Returns the maximum amount of cached stacks for reuse of the [`Config`].
pub(super) fn cached_stacks(&self) -> usize {
self.cached_stacks
}

/// Enable or disable the [`mutable-global`] Wasm proposal for the [`Config`].
///
/// # Note
Expand Down
200 changes: 153 additions & 47 deletions crates/wasmi/src/engine/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ mod traits;
#[cfg(test)]
mod tests;

pub(crate) use self::func_args::{FuncParams, FuncResults};
pub use self::{
bytecode::DropKeep,
code_map::FuncBody,
Expand All @@ -37,15 +36,18 @@ use self::{
func_types::FuncTypeRegistry,
stack::{FuncFrame, Stack, ValueStack},
};
pub(crate) use self::{
func_args::{FuncParams, FuncResults},
func_types::DedupFuncType,
};
use super::{func::FuncEntityInternal, AsContextMut, Func};
use crate::{
core::{Trap, TrapCode},
FuncType,
};
use alloc::sync::Arc;
use alloc::{sync::Arc, vec::Vec};
use core::sync::atomic::{AtomicU32, Ordering};
pub use func_types::DedupFuncType;
use spin::mutex::Mutex;
use spin::{Mutex, RwLock};
use wasmi_arena::{ArenaIndex, GuardedEntity};

/// The outcome of a `wasmi` function execution.
Expand Down Expand Up @@ -100,7 +102,7 @@ type Guarded<Idx> = GuardedEntity<EngineIdx, Idx>;
/// Most of its API has a `&self` receiver, so can be shared easily.
#[derive(Debug, Clone)]
pub struct Engine {
inner: Arc<Mutex<EngineInner>>,
inner: Arc<EngineInner>,
}

impl Default for Engine {
Expand All @@ -117,18 +119,18 @@ impl Engine {
/// Users should ues [`Engine::default`] to construct a default [`Engine`].
pub fn new(config: &Config) -> Self {
Self {
inner: Arc::new(Mutex::new(EngineInner::new(config))),
inner: Arc::new(EngineInner::new(config)),
}
}

/// Returns a shared reference to the [`Config`] of the [`Engine`].
pub fn config(&self) -> Config {
*self.inner.lock().config()
self.inner.config()
}

/// Allocates a new function type to the engine.
pub(super) fn alloc_func_type(&self, func_type: FuncType) -> DedupFuncType {
self.inner.lock().func_types.alloc_func_type(func_type)
self.inner.alloc_func_type(func_type)
}

/// Resolves a deduplicated function type into a [`FuncType`] entity.
Expand All @@ -141,8 +143,7 @@ impl Engine {
where
F: FnOnce(&FuncType) -> R,
{
// Note: The clone operation on FuncType is intentionally cheap.
f(self.inner.lock().func_types.resolve_func_type(func_type))
self.inner.resolve_func_type(func_type, f)
}

/// Allocates the instructions of a Wasm function body to the [`Engine`].
Expand All @@ -159,7 +160,6 @@ impl Engine {
I::IntoIter: ExactSizeIterator,
{
self.inner
.lock()
.alloc_func_body(len_locals, max_stack_height, insts)
}

Expand All @@ -176,11 +176,7 @@ impl Engine {
/// If the [`FuncBody`] is invalid for the [`Engine`].
#[cfg(test)]
pub(crate) fn resolve_inst(&self, func_body: FuncBody, index: usize) -> Option<Instruction> {
self.inner
.lock()
.code_map
.get_instr(func_body, index)
.copied()
self.inner.resolve_inst(func_body, index)
}

/// Executes the given [`Func`] using the given arguments `params` and stores the result into `results`.
Expand Down Expand Up @@ -211,17 +207,133 @@ impl Engine {
Params: CallParams,
Results: CallResults,
{
self.inner.lock().execute_func(ctx, func, params, results)
self.inner.execute_func(ctx, func, params, results)
}
}

/// The internal state of the `wasmi` engine.
#[derive(Debug)]
pub struct EngineInner {
/// Engine resources shared across multiple engine executors.
res: RwLock<EngineResources>,
/// Reusable engine stacks for Wasm execution.
///
/// Concurrently executing Wasm executions each require their own stack to
/// operate on. Therefore a Wasm engine is required to provide stacks and
/// ideally recycles old ones since creation of a new stack is rather expensive.
stacks: Mutex<EngineStacks>,
}

/// The engine's stacks for reuse.
///
/// Rquired for efficient concurrent Wasm executions.
#[derive(Debug)]
pub struct EngineStacks {
/// Stacks to be (re)used.
stacks: Vec<Stack>,
/// Stack limits for newly constructed engine stacks.
limits: StackLimits,
/// How many stacks should be kept for reuse at most.
keep: usize,
}

impl EngineStacks {
/// Creates new [`EngineStacks`] with the given [`StackLimits`].
pub fn new(config: &Config) -> Self {
Self {
stacks: Vec::new(),
limits: config.stack_limits(),
keep: config.cached_stacks(),
}
}

/// Reuse or create a new [`Stack`] if none was available.
pub fn reuse_or_new(&mut self) -> Stack {
match self.stacks.pop() {
Some(stack) => stack,
None => Stack::new(self.limits),
}
}

/// Disose and recycle the `stack`.
pub fn recycle(&mut self, stack: Stack) {
if self.stacks.len() < self.keep {
self.stacks.push(stack);
}
}
}

impl EngineInner {
/// Creates a new [`EngineInner`] with the given [`Config`].
fn new(config: &Config) -> Self {
Self {
res: RwLock::new(EngineResources::new(config)),
stacks: Mutex::new(EngineStacks::new(config)),
}
}

fn config(&self) -> Config {
self.res.read().config
}

fn alloc_func_type(&self, func_type: FuncType) -> DedupFuncType {
self.res.write().func_types.alloc_func_type(func_type)
}

fn alloc_func_body<I>(&self, len_locals: usize, max_stack_height: usize, insts: I) -> FuncBody
where
I: IntoIterator<Item = Instruction>,
I::IntoIter: ExactSizeIterator,
{
self.res
.write()
.code_map
.alloc(len_locals, max_stack_height, insts)
}

fn resolve_func_type<F, R>(&self, func_type: DedupFuncType, f: F) -> R
where
F: FnOnce(&FuncType) -> R,
{
f(self.res.read().func_types.resolve_func_type(func_type))
}

#[cfg(test)]
fn resolve_inst(&self, func_body: FuncBody, index: usize) -> Option<Instruction> {
self.res
.read()
.code_map
.get_instr(func_body, index)
.copied()
}

fn execute_func<Params, Results>(
&self,
ctx: impl AsContextMut,
func: Func,
params: Params,
results: Results,
) -> Result<<Results as CallResults>::Results, Trap>
where
Params: CallParams,
Results: CallResults,
{
let res = self.res.read();
let mut stack = self.stacks.lock().reuse_or_new();
let results =
EngineExecutor::new(&mut stack).execute_func(ctx, &res, func, params, results);
self.stacks.lock().recycle(stack);
results
}
}

/// Engine resources that are immutable during function execution.
///
/// Can be shared by multiple engine executors.
#[derive(Debug)]
pub struct EngineResources {
/// The configuration with which the [`Engine`] has been created.
config: Config,
/// The value and call stacks.
stack: Stack,
/// Stores all Wasm function bodies that the interpreter is aware of.
code_map: CodeMap,
/// Deduplicated function types.
Expand All @@ -233,37 +345,29 @@ pub struct EngineInner {
func_types: FuncTypeRegistry,
}

impl EngineInner {
/// Creates a new [`EngineInner`] with the given [`Config`].
pub fn new(config: &Config) -> Self {
impl EngineResources {
/// Creates a new [`EngineResources`] with the given [`Config`].
fn new(config: &Config) -> Self {
let engine_idx = EngineIdx::new();
Self {
config: *config,
stack: Stack::new(config.stack_limits()),
code_map: CodeMap::default(),
func_types: FuncTypeRegistry::new(engine_idx),
}
}
}

/// Returns a shared reference to the [`Config`] of the [`Engine`].
pub fn config(&self) -> &Config {
&self.config
}
/// The internal state of the `wasmi` engine.
#[derive(Debug)]
pub struct EngineExecutor<'stack> {
/// The value and call stacks.
stack: &'stack mut Stack,
}

/// Allocates the instructions of a Wasm function body to the [`Engine`].
///
/// Returns a [`FuncBody`] reference to the allocated function body.
pub fn alloc_func_body<I>(
&mut self,
len_locals: usize,
max_stack_height: usize,
insts: I,
) -> FuncBody
where
I: IntoIterator<Item = Instruction>,
I::IntoIter: ExactSizeIterator,
{
self.code_map.alloc(len_locals, max_stack_height, insts)
impl<'stack> EngineExecutor<'stack> {
/// Creates a new [`EngineExecutor`] with the given [`StackLimits`].
fn new(stack: &'stack mut Stack) -> Self {
Self { stack }
}

/// Executes the given [`Func`] using the given arguments `args` and stores the result into `results`.
Expand All @@ -273,9 +377,10 @@ impl EngineInner {
/// - If the given arguments `args` do not match the expected parameters of `func`.
/// - If the given `results` do not match the the length of the expected results of `func`.
/// - When encountering a Wasm trap during the execution of `func`.
pub fn execute_func<Params, Results>(
fn execute_func<Params, Results>(
&mut self,
mut ctx: impl AsContextMut,
res: &EngineResources,
func: Func,
params: Params,
results: Results,
Expand All @@ -287,14 +392,14 @@ impl EngineInner {
self.initialize_args(params);
match func.as_internal(ctx.as_context()) {
FuncEntityInternal::Wasm(wasm_func) => {
let mut frame = self.stack.call_wasm_root(wasm_func, &self.code_map)?;
let mut frame = self.stack.call_wasm_root(wasm_func, &res.code_map)?;
let mut cache = InstanceCache::from(frame.instance());
self.execute_wasm_func(ctx.as_context_mut(), &mut frame, &mut cache)?;
self.execute_wasm_func(ctx.as_context_mut(), res, &mut frame, &mut cache)?;
}
FuncEntityInternal::Host(host_func) => {
let host_func = host_func.clone();
self.stack
.call_host_root(ctx.as_context_mut(), host_func, &self.func_types)?;
.call_host_root(ctx.as_context_mut(), host_func, &res.func_types)?;
}
};
let results = self.write_results_back(results);
Expand Down Expand Up @@ -335,6 +440,7 @@ impl EngineInner {
fn execute_wasm_func(
&mut self,
mut ctx: impl AsContextMut,
res: &EngineResources,
frame: &mut FuncFrame,
cache: &mut InstanceCache,
) -> Result<(), Trap> {
Expand All @@ -350,7 +456,7 @@ impl EngineInner {
CallOutcome::NestedCall(called_func) => {
match called_func.as_internal(ctx.as_context()) {
FuncEntityInternal::Wasm(wasm_func) => {
*frame = self.stack.call_wasm(frame, wasm_func, &self.code_map)?;
*frame = self.stack.call_wasm(frame, wasm_func, &res.code_map)?;
}
FuncEntityInternal::Host(host_func) => {
cache.reset_default_memory_bytes();
Expand All @@ -359,7 +465,7 @@ impl EngineInner {
ctx.as_context_mut(),
frame,
host_func,
&self.func_types,
&res.func_types,
)?;
}
}
Expand Down
Loading