Skip to content

Commit

Permalink
[Rust] Introduced new host function storing design for the single-thr…
Browse files Browse the repository at this point in the history
…ead scenario (WasmEdge#1648)

* [Rust][feat](lib): Introduced global variable for storing non-thread-safe host functions.
* [Rust][feat](function): Implemented new API.
* [Rust][feat](examples): Added new example.
* [Rust][refactor](vm): Refactored APIs.
* [Rust SDK][feat](function, import): Introduced new APIs for storing single-thread host functions.
* [Rust SDK][feat](examples): Added new example.
* [Rust][fix](lib): Fixed review issue.

Signed-off-by: Xin Liu <[email protected]>
  • Loading branch information
apepkuss authored Jul 14, 2022
1 parent 67d3142 commit e90861d
Show file tree
Hide file tree
Showing 10 changed files with 355 additions and 10 deletions.
75 changes: 75 additions & 0 deletions bindings/rust/wasmedge-sdk/examples/hostfunc_call_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
//! This example demonstrates how to implement hostfunc call chaining, namely call
//! another hostfunc in the current hostfunc.
//!
//! To run this example, use the following command:
//!
//! ```bash
//! cd /wasmedge-root-dir/bindings/rust/
//!
//! cargo run -p wasmedge-sdk --example hostfunc_call_chain -- --nocapture
//! ```
//!
//! The following info will be printed out in the terminal:
//!
//! ```bash
//! There is layer1!
//! There is layer2!
//! ```
use std::sync::{Arc, Mutex};
use wasmedge_sdk::{params, ImportObjectBuilder, Module, Vm, WasmValue};
use wasmedge_types::wat2wasm;

struct Wrapper(*const Vm);
unsafe impl Send for Wrapper {}

#[cfg_attr(test, test)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let vm = Vm::new(None)?;

let host_layer1 = |_: Vec<WasmValue>| -> Result<Vec<WasmValue>, u8> {
println!("There is layer1!");
Ok(vec![])
};

let s = Arc::new(Mutex::new(Wrapper(&vm as *const Vm)));
let host_layer2 = move |_: Vec<WasmValue>| -> Result<Vec<WasmValue>, u8> {
unsafe {
(*s.lock().unwrap().0)
.run_func(None, "layer1", params!())
.unwrap();
}
println!("There is layer2!");
Ok(vec![])
};

let import = ImportObjectBuilder::new()
.with_func_single_thread::<(), ()>("layer1", host_layer1)?
.with_func_single_thread::<(), ()>("layer2", host_layer2)?
.build("host")?;

let vm = vm.register_import_module(import)?;

let wasm_bytes = wat2wasm(
br#"
(module
(import "host" "layer1" (func $host_layer1))
(import "host" "layer2" (func $host_layer2))
(func (export "layer1")
call $host_layer1)
(func (export "layer2")
call $host_layer2)
)
"#,
)?;

let module = Module::from_bytes(None, wasm_bytes)?;

// register the wasm module into vm
let vm = vm.register_module(None, module)?;

vm.run_func(None, "layer2", params!())?;

Ok(())
}
32 changes: 32 additions & 0 deletions bindings/rust/wasmedge-sdk/src/externals/function.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ pub struct Func {
impl Func {
/// Creates a host function by wrapping a native function.
///
/// N.B. that this function is used for thread-safe scenarios.
///
/// # Arguments
///
/// * `real_func` - The native function to be wrapped.
Expand All @@ -92,6 +94,36 @@ impl Func {
})
}

/// Creates a host function by wrapping a native function.
///
/// N.B. that this function is used for single-threaded scenarios. If you would like to use hostfunc call chaining design, you should use this method to create a [Func] instance.
///
/// # Arguments
///
/// * `real_func` - The native function to be wrapped.
///
/// # Error
///
/// If fail to create the host function, then an error is returned.
pub fn wrap_single_thread<Args, Rets>(
real_func: impl Fn(Vec<WasmValue>) -> Result<Vec<WasmValue>, u8> + 'static,
) -> WasmEdgeResult<Self>
where
Args: WasmValTypeList,
Rets: WasmValTypeList,
{
let boxed_func = Box::new(real_func);
let args = Args::wasm_types();
let returns = Rets::wasm_types();
let ty = FuncType::new(Some(args.to_vec()), Some(returns.to_vec()));
let inner = sys::Function::create_single_thread(&ty.into(), boxed_func, 0)?;
Ok(Self {
inner,
name: None,
mod_name: None,
})
}

/// Returns the exported name of this function.
///
/// Notice that this field is meaningful only if this host function is used as an exported instance.
Expand Down
33 changes: 33 additions & 0 deletions bindings/rust/wasmedge-sdk/src/import.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,8 @@ impl ImportObjectBuilder {

/// Adds a [host function](crate::Func) to the [ImportObject] to create.
///
/// N.B. that this function is used for thread-safe scenarios.
///
/// # Arguments
///
/// * `name` - The exported name of the [host function](crate::Func) to add.
Expand Down Expand Up @@ -120,6 +122,37 @@ impl ImportObjectBuilder {
Ok(self)
}

/// Adds a [host function](crate::Func) to the [ImportObject] to create.
///
/// N.B. that this function is used for single-threaded scenarios. If you would like to use hostfunc call chaining design, you should use this method to create a [Func](crate::Func) instance.
///
/// # Arguments
///
/// * `name` - The exported name of the [host function](crate::Func) to add.
///
/// * `real_func` - The native function.
///
/// # error
///
/// If fail to create or add the [host function](crate::Func), then an error is returned.
pub fn with_func_single_thread<Args, Rets>(
mut self,
name: impl AsRef<str>,
real_func: impl Fn(Vec<WasmValue>) -> Result<Vec<WasmValue>, u8> + 'static,
) -> WasmEdgeResult<Self>
where
Args: WasmValTypeList,
Rets: WasmValTypeList,
{
let boxed_func = Box::new(real_func);
let args = Args::wasm_types();
let returns = Rets::wasm_types();
let ty = FuncType::new(Some(args.to_vec()), Some(returns.to_vec()));
let inner_func = sys::Function::create_single_thread(&ty.into(), boxed_func, 0)?;
self.funcs.push((name.as_ref().to_owned(), inner_func));
Ok(self)
}

/// Adds a [global](crate::Global) to the [ImportObject] to create.
///
/// # Arguments
Expand Down
2 changes: 1 addition & 1 deletion bindings/rust/wasmedge-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ pub use store::Store;
#[doc(inline)]
pub use vm::Vm;

use wasmedge_sys::types::WasmValue;
pub type WasmValue = wasmedge_sys::types::WasmValue;

/// The object that is used to perform a [host function](crate::Func) is required to implement this trait.
pub trait Engine {
Expand Down
2 changes: 1 addition & 1 deletion bindings/rust/wasmedge-sdk/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ impl Vm {
///
/// If fail to run the WASM function, then an error is returned.
pub fn run_func(
&mut self,
&self,
mod_name: Option<&str>,
func_name: impl AsRef<str>,
args: impl IntoIterator<Item = sys::WasmValue>,
Expand Down
74 changes: 74 additions & 0 deletions bindings/rust/wasmedge-sys/examples/call_hostfunc_chain.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
//! This example demonstrates how to implement hostfunc call chaining, namely call
//! another hostfunc in the current hostfunc.
//!
//! To run this example, use the following command:
//!
//! ```bash
//! cd /wasmedge-root-dir/bindings/rust/
//!
//! cargo run -p wasmedge-sys --example call_hostfunc_chain -- --nocapture
//! ```
//!
//! The following info will be printed out in the terminal:
//!
//! ```bash
//! There is layer1!
//! There is layer2!
//! ```
use std::sync::{Arc, Mutex};
use wasmedge_sys::*;
use wasmedge_types::wat2wasm;

struct Wrapper(*const Vm);
unsafe impl Send for Wrapper {}

#[cfg_attr(test, test)]
fn main() -> Result<(), Box<dyn std::error::Error>> {
let wasm_bytes = wat2wasm(
br#"
(module
(import "host" "layer1" (func $host_layer1))
(import "host" "layer2" (func $host_layer2))
(func (export "layer1")
call $host_layer1)
(func (export "layer2")
call $host_layer2)
)
"#,
)?;

let mut vm = Vm::create(None, None)?;
vm.load_wasm_from_bytes(&wasm_bytes)?;
vm.validate()?;

let host_layer1 = |_: Vec<WasmValue>| -> Result<Vec<WasmValue>, u8> {
println!("There is layer1!");
Ok(vec![])
};

let s = Arc::new(Mutex::new(Wrapper(&vm as *const Vm)));
let host_layer2 = move |_: Vec<WasmValue>| -> Result<Vec<WasmValue>, u8> {
unsafe {
(*s.lock().unwrap().0).run_function("layer1", []).unwrap();
}
println!("There is layer2!");
Ok(vec![])
};

let func_ty = FuncType::create(vec![], vec![]).unwrap();
let host_layer1 = Function::create_single_thread(&func_ty, Box::new(host_layer1), 0)?;
let host_layer2 = Function::create_single_thread(&func_ty, Box::new(host_layer2), 0)?;

let mut import = ImportModule::create("host")?;
import.add_func("layer1", host_layer1);
import.add_func("layer2", host_layer2);

vm.register_wasm_from_import(ImportObject::Import(import))?;
vm.instantiate()?;

vm.run_function("layer2", [])?;

Ok(())
}
4 changes: 2 additions & 2 deletions bindings/rust/wasmedge-sys/examples/threads.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// compute fib(4) by a child thread
let vm_cloned = Arc::clone(&vm);
let handle_a = thread::spawn(move || {
let mut vm_child_thread = vm_cloned.lock().expect("fail to lock vm");
let vm_child_thread = vm_cloned.lock().expect("fail to lock vm");
let returns = vm_child_thread
.run_registered_function("extern", "fib", [WasmValue::from_i32(4)])
.expect("fail to compute fib(4)");
Expand All @@ -45,7 +45,7 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
// compute fib(5) by a child thread
let vm_cloned = Arc::clone(&vm);
let handle_b = thread::spawn(move || {
let mut vm_child_thread = vm_cloned.lock().expect("fail to lock vm");
let vm_child_thread = vm_cloned.lock().expect("fail to lock vm");
let returns = vm_child_thread
.run_registered_function("extern", "fib", [WasmValue::from_i32(5)])
.expect("fail to compute fib(5)");
Expand Down
Loading

0 comments on commit e90861d

Please sign in to comment.