Skip to content

Commit

Permalink
Add Instance::new API (#1134)
Browse files Browse the repository at this point in the history
* simplify extract_imports

* add Extern::originates_from_store method

* further simplify Module::extract_imports method

* rename + deref instead of AsContext

* add Instance::new API

* remove Extern::originates_from_store method again

* add tests for Instance::new API

* fix doc link
  • Loading branch information
Robbepop authored Jul 24, 2024
1 parent 098508b commit 3d7f8be
Show file tree
Hide file tree
Showing 4 changed files with 311 additions and 34 deletions.
45 changes: 45 additions & 0 deletions crates/wasmi/src/instance/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ use crate::{
collections::{arena::ArenaIndex, Map},
func::FuncError,
memory::DataSegment,
AsContextMut,
ElementSegment,
Error,
TypedFunc,
Expand All @@ -26,6 +27,9 @@ use std::{boxed::Box, sync::Arc};
mod builder;
mod exports;

#[cfg(test)]
mod tests;

/// A raw index to a module instance entity.
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct InstanceIdx(u32);
Expand Down Expand Up @@ -147,6 +151,47 @@ impl InstanceEntity {
pub struct Instance(Stored<InstanceIdx>);

impl Instance {
/// Creates a new [`Instance`] from the pre-compiled [`Module`] and the list of `imports`.
///
/// Uses the official [Wasm instantiation prodecure] in order to resolve and type-check
/// the provided `imports` and match them with the required imports of the [`Module`].
///
/// # Note
///
/// - This function intentionally is rather low-level for [`Instance`] creation.
/// Please use the [`Linker`](crate::Linker) type for a more high-level API for Wasm
/// module instantiation with name-based resolution.
/// - Wasm module instantiation implies running the Wasm `start` function which is _not_
/// to be confused with WASI's `_start` function.
///
/// # Usage
///
/// The `imports` are intended to correspond 1:1 with the required imports as returned by [`Module::imports`].
/// For each import type returned by [`Module::imports`], create an [`Extern`] which corresponds to that type.
/// Collect the [`Extern`] values created this way into a list and pass them to this function.
///
/// # Errors
///
/// - If the number of provided imports does not match the number of imports required by the [`Module`].
/// - If the type of any provided [`Extern`] does not match the corresponding required [`ExternType`].
/// - If the `start` function, that is run at the end of the Wasm module instantiation, traps.
/// - If Wasm module or instance related resource limits are exceeded.
///
/// # Panics
///
/// If any [`Extern`] does not originate from the provided `store`.
///
/// [Wasm instantiation procedure]: https://webassembly.github.io/spec/core/exec/modules.html#exec-instantiation
pub fn new(
mut store: impl AsContextMut,
module: &Module,
imports: &[Extern],
) -> Result<Instance, Error> {
let instance_pre = Module::instantiate(module, &mut store, imports.iter().cloned())?;
let instance = instance_pre.start(&mut store)?;
Ok(instance)
}

/// Creates a new stored instance reference.
///
/// # Note
Expand Down
240 changes: 240 additions & 0 deletions crates/wasmi/src/instance/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,240 @@
use super::*;
use crate::{
core::{TrapCode, ValType},
error::ErrorKind,
global::GlobalError,
memory::MemoryError,
module::InstantiationError,
table::TableError,
Caller,
Engine,
ExternRef,
FuncRef,
MemoryType,
Mutability,
Store,
TableType,
Val,
};
use std::vec::Vec;

/// Converts the `.wat` encoded `bytes` into `.wasm` encoded bytes.
pub fn wat2wasm(bytes: &str) -> Vec<u8> {
wat::parse_bytes(bytes.as_bytes()).unwrap().into_owned()
}

#[test]
fn instantiate_no_imports() {
let wasm = wat2wasm(
r#"
(module
(func (export "f") (param i32 i32) (result i32)
(i32.add (local.get 0) (local.get 1))
)
)
"#,
);
let engine = Engine::default();
let module = Module::new(&engine, &wasm[..]).unwrap();
let mut store = Store::new(&engine, ());
let instance = Instance::new(&mut store, &module, &[]).unwrap();
assert!(instance.get_func(&store, "f").is_some());
}

#[test]
fn instantiate_with_start() {
let wasm = wat2wasm(
r#"
(module
(func $f)
(start $f)
)
"#,
);
let engine = Engine::default();
let module = Module::new(&engine, &wasm[..]).unwrap();
let mut store = Store::new(&engine, ());
let _instance = Instance::new(&mut store, &module, &[]).unwrap();
}

#[test]
fn instantiate_with_trapping_start() {
let wasm = wat2wasm(
r#"
(module
(func $f
(unreachable)
)
(start $f)
)
"#,
);
let engine = Engine::default();
let module = Module::new(&engine, &wasm[..]).unwrap();
let mut store = Store::new(&engine, ());
let error = Instance::new(&mut store, &module, &[]).unwrap_err();
assert_eq!(error.as_trap_code(), Some(TrapCode::UnreachableCodeReached));
}

#[test]
fn instantiate_with_imports_and_start() {
let wasm = wat2wasm(
r#"
(module
(import "env" "f" (func $f (param i32)))
(import "env" "t" (table $t 0 funcref))
(import "env" "m" (memory $m 0))
(import "env" "g" (global $g (mut i32)))
(elem declare func $f)
(func $main
(global.set $g (i32.const 1))
(i32.store8 $m (i32.const 0) (i32.const 1))
(table.set $t (i32.const 0) (ref.func $f))
(call $f (i32.const 1))
)
(start $main)
)
"#,
);
let engine = Engine::default();
let module = Module::new(&engine, &wasm[..]).unwrap();
let data: i32 = 0;
let mut store = Store::new(&engine, data);
let g = Global::new(&mut store, Val::I32(0), Mutability::Var);
let m = Memory::new(&mut store, MemoryType::new(1, None).unwrap()).unwrap();
let t = Table::new(
&mut store,
TableType::new(ValType::FuncRef, 1, None),
Val::from(FuncRef::null()),
)
.unwrap();
let f = Func::wrap(&mut store, |mut caller: Caller<i32>, a: i32| {
let data = caller.data_mut();
*data = a;
});
let externals = [
Extern::from(f),
Extern::from(t),
Extern::from(m),
Extern::from(g),
]
.map(Extern::from);
let _instance = Instance::new(&mut store, &module, &externals).unwrap();
assert_eq!(g.get(&store).i32(), Some(1));
assert_eq!(m.data(&store)[0], 0x01_u8);
assert!(!t.get(&store, 0).unwrap().funcref().unwrap().is_null());
assert_eq!(store.data(), &1);
}

#[test]
fn instantiate_with_invalid_global_import() {
let wasm = wat2wasm(
r#"
(module
(import "env" "g" (global $g (mut i32)))
(func $main
(global.set $g (i32.const 1))
)
)
"#,
);
let engine = Engine::default();
let module = Module::new(&engine, &wasm[..]).unwrap();
let mut store = Store::new(&engine, ());
let g = Global::new(&mut store, Val::I64(0), Mutability::Var);
let externals = [Extern::from(g)].map(Extern::from);
let error = Instance::new(&mut store, &module, &externals).unwrap_err();
assert!(matches!(
error.kind(),
ErrorKind::Instantiation(InstantiationError::Global(
GlobalError::UnsatisfyingGlobalType { .. }
))
));
}

#[test]
fn instantiate_with_invalid_memory_import() {
let wasm = wat2wasm(
r#"
(module
(import "env" "m" (memory $m 2))
(func
(i32.store8 $m (i32.const 0) (i32.const 1))
)
)
"#,
);
let engine = Engine::default();
let module = Module::new(&engine, &wasm[..]).unwrap();
let mut store = Store::new(&engine, ());
let m = Memory::new(&mut store, MemoryType::new(0, Some(1)).unwrap()).unwrap();
let externals = [Extern::from(m)].map(Extern::from);
let error = Instance::new(&mut store, &module, &externals).unwrap_err();
assert!(matches!(
error.kind(),
ErrorKind::Instantiation(InstantiationError::Memory(
MemoryError::InvalidSubtype { .. }
))
));
}

#[test]
fn instantiate_with_invalid_table_import() {
let wasm = wat2wasm(
r#"
(module
(import "env" "t" (table $t 0 funcref))
(elem declare func $f)
(func $f
(table.set $t (i32.const 0) (ref.func $f))
)
)
"#,
);
let engine = Engine::default();
let module = Module::new(&engine, &wasm[..]).unwrap();
let mut store = Store::new(&engine, ());
let t = Table::new(
&mut store,
TableType::new(ValType::ExternRef, 1, None),
Val::from(ExternRef::null()),
)
.unwrap();
let externals = [Extern::from(t)].map(Extern::from);
let error = Instance::new(&mut store, &module, &externals).unwrap_err();
assert!(matches!(
error.kind(),
ErrorKind::Instantiation(InstantiationError::Table(TableError::InvalidSubtype { .. }))
));
}

#[test]
fn instantiate_with_invalid_func_import() {
let wasm = wat2wasm(
r#"
(module
(import "env" "f" (func $f (param i32)))
(elem declare func $f)
(func
(call $f (i32.const 1))
)
)
"#,
);
let engine = Engine::default();
let module = Module::new(&engine, &wasm[..]).unwrap();
let data: i64 = 0;
let mut store = Store::new(&engine, data);
let f = Func::wrap(&mut store, |mut caller: Caller<i64>, a: i64| {
let data = caller.data_mut();
*data = a;
});
let externals = [Extern::from(f)].map(Extern::from);
let error = Instance::new(&mut store, &module, &externals).unwrap_err();
assert!(matches!(
error.kind(),
ErrorKind::Instantiation(InstantiationError::SignatureMismatch { .. })
));
}
15 changes: 10 additions & 5 deletions crates/wasmi/src/module/instantiate/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,14 @@ use core::{fmt, fmt::Display};
/// An error that may occur upon instantiation of a Wasm module.
#[derive(Debug)]
pub enum InstantiationError {
/// Caused when the number of required imports does not match
/// the number of given externals upon module instantiation.
ImportsExternalsLenMismatch,
/// Encountered when trying to instantiate a Wasm module with
/// a non-matching number of external imports.
InvalidNumberOfImports {
/// The number of imports required by the Wasm module definition.
required: usize,
/// The number of imports given by the faulty Wasm module instantiation.
given: usize,
},
/// Caused when a given external value does not match the
/// type of the required import for module instantiation.
ImportsExternalsMismatch {
Expand Down Expand Up @@ -58,9 +63,9 @@ impl std::error::Error for InstantiationError {}
impl Display for InstantiationError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Self::ImportsExternalsLenMismatch => write!(
Self::InvalidNumberOfImports { required, given } => write!(
f,
"encountered mismatch between number of given externals and module imports",
"invalid number of imports: required = {required}, given = {given}",
),
Self::ImportsExternalsMismatch { expected, actual } => write!(
f,
Expand Down
Loading

0 comments on commit 3d7f8be

Please sign in to comment.