second-state / wasmedge-bindgen

Let WebAssembly's exported function support more data types for its parameters and return values.
Apache License 2.0
29 stars 8 forks source link

Calling host functions from the guest #4

Open clarkmcc opened 2 years ago

clarkmcc commented 2 years ago

I see examples of how to use this to call guest functions from the host, but how can this be used to call host functions from the guest?

juntao commented 2 years ago

To call host functions from the guest, you will need to use host function API. We have several examples.

The wasmedge_wasi_socket Rust API calls the socket host functions from a Rust guest app.

The wasmedge_tensorflow_interface Rust API calls the Tensorflow host functions from a Rust guest app.

Tails commented 1 year ago

What I see from the Tensorflow module is that instead of a bindgen annotation, we need to annotate the extern {} block in the guest with:

// guest
#[link(wasm_import_module = "env")]
extern "C" {
    pub fn say_hello();
}

And make sure the module "env" corresponds with the module name of the ImportObject created in the host, correct?

// host
fn say_hello(caller: &CallingFrame, _args: Vec<WasmValue>) -> Result<Vec<WasmValue>, HostFuncError> {
    println!("Hello, world!");

    Ok(vec![])
}

pub fn run() -> anyhow::Result<()> {
    // create an import module
    println!("creating importobject...");
    let import = ImportObjectBuilder::new()
        .with_func::<(), ()>("say_hello", say_hello)?
        .build("env")?; // <------- is this the #[link(wasm_import_module = "env")] ?

    // create VM and register our wasm module
    println!("creating vm...");
    let mut vm = result?
        .register_import_module(import)?
        .register_module(Some("my_wasm_code_module"), ..wasm_code_reference..)?
        ;

    // this is essential, without it the runtime cannot read files and will error out
    // https://github.com/WasmEdge/WasmEdge/issues/1872
    println!("configuring wasi...");
    let mut wasi_module = vm.wasi_module()?;
    wasi_module.initialize(None, None, Some(vec!(".:.")));

    println!("running app...");
    let results = vm.run_func(
        Some("my_wasm_code_module"),
        "_start",
        params!())?;

    dbg!(results);

    Ok(())
}

When running, I get an error on that the import cannot be found:

creating vm...
wasmedge_1  | [2022-10-10 10:32:39.777] [error] instantiation failed: unknown import, Code: 0x62
wasmedge_1  | [2022-10-10 10:32:39.777] [error]     When linking module: "env" , function name: "say_hello"
wasmedge_1  | [2022-10-10 10:32:39.777] [error]     At AST node: import description
wasmedge_1  | [2022-10-10 10:32:39.777] [error]     At AST node: import section
wasmedge_1  | [2022-10-10 10:32:39.777] [error]     At AST node: module
wasmedge_1  | thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: unknown import'
apepkuss commented 1 year ago

Hi @Tails,

You should enable the wasi config option if you'd like to use wasi module in your program. I attached a code snippet below to present how to do it:

// create a config with `wasi` option enabled
let config = ConfigBuilder::new(CommonConfigOptions::default())
        .with_host_registration_config(HostRegistrationConfigOptions::default().wasi(true))
        .build()?;

 // create a vm
let vm = Vm::new(Some(config))?;

For your convenience, I rewrite your example code above with wasmedge-sdk v0.5.0. Hope it would be helpful for you.

#![feature(never_type)]

use wasmedge_sdk::{
    config::{CommonConfigOptions, ConfigBuilder, HostRegistrationConfigOptions},
    error::HostFuncError,
    host_function, Caller, ImportObjectBuilder, Vm,
    WasmValue,
};

// We define a function to act as our "env" "say_hello" function imported in the
// Wasm program above.
#[host_function]
fn say_hello(caller: &Caller, _args: Vec<WasmValue>) -> Result<Vec<WasmValue>, HostFuncError> {
    println!("Hello, world!");

    Ok(vec![])
}

#[cfg_attr(test, test)]
fn main() -> anyhow::Result<()> {
    // create an import module
    let import = ImportObjectBuilder::new()
        .with_func::<(), (), !>("say_hello", say_hello, None)?
        .build("env")?;

    // create a config with `wasi` option enabled
    let config = ConfigBuilder::new(CommonConfigOptions::default())
        .with_host_registration_config(HostRegistrationConfigOptions::default().wasi(true))
        .build()?;

    // create a vm
    let vm = Vm::new(Some(config))?;

    // this is essential, without it the runtime cannot read files and will error out
    // https://github.com/WasmEdge/WasmEdge/issues/1872
    println!("configuring wasi...");
    let mut wasi_module = vm.wasi_module()?;
    wasi_module.initialize(None, None, Some(vec!(".:.")));

    // create VM and register our wasm module
    println!("creating vm...");
    let mut vm = vm
        .register_import_module(import)?
        .register_module(Some("my_wasm_code_module"), ..wasm_code_reference..)?
        ;

    println!("running app...");
    let results = vm.run_func(
        Some("my_wasm_code_module"),
        "_start",
        params!())?;

    dbg!(results);

    Ok(())
}
Tails commented 1 year ago

I have the wasi config enabled, I had unfortunately just removed it from my code sample for brevity. The result? unpacks the VM instance with the wasi config from the Option. I'll post an integral code sample to illustrate the error.

Tails commented 1 year ago

For future reference, the ImportObject could successfully be registered by switching the some order of operations around. I think what did it is initializing the Wasi module before registering the import object and main wasm code.

pub fn run() -> anyhow::Result<()> {
    let file = "../my_wasm.wasm";

    // create an import module
    let import = ImportObjectBuilder::new()
        .with_func::<(), ()>("say_hello", say_hello)?
        .build("env")?;

    let mut vm = Vm::new(Some(config()))?;

    let mut wasi_module = vm.wasi_module()?;
    wasi_module.initialize(None, None, Some(vec!(".:.")));

    // our code
    let module = Module::from_file(Some(&config()), file).unwrap();

    vm = vm
        .register_import_module(import)?
        .register_module(Some("app"), module)?
        ;

    println!("running app...");
    let results = vm.run_func(
        Some("app"),
        "_start",
        params!())?;

    Ok(())
}

fn config() -> Config {
    let result = ConfigBuilder::new(CommonConfigOptions::default())
        .with_host_registration_config(HostRegistrationConfigOptions::new()
        .wasi(true))
        .build();

    result.unwrap()
}

#  [2022-10-11 11:11:31.284] [error] execution failed: out of bounds memory access, Code: 0x88
wasmedge_1  | [2022-10-11 11:11:31.284] [error]     When executing module name: "app" , function name: "_start"
wasmedge_1  | thread 'main' panicked at 'called `Result::unwrap()` on an `Err` value: out of bounds memory access'

This error seems no longer related to the ImportObject, so I'll have to check that out first before the host call is actually reached.

apepkuss commented 1 year ago

@Tails Thanks for sharing your code. According to the error message, it seems a memory issue. Do you mind sharing the wasm file with us? so that I can reproduce the error. You can attach it here. Thanks a lot!

Tails commented 1 year ago

The memory error was due to running a Ahead Of Time (AOT) .wasm for some reason. When I switched it back to use the regular interpreted .wasm it worked! And also the host call! So indeed, the only magic needed is:

// annotate the extern block
#[link(wasm_import_module = "env")]
extern "C" {...}

and a

unsafe { say_hello() };

and the registration of the ImportObject:

let import = ImportObjectBuilder::new()
        .with_func::<(), ()>("say_hello", say_hello)?
        .build("env")?;

...

vm = vm
        .register_import_module(import)?

If that is taken up in the documentation, this ticket can in my opinion be closed, but this is not my ticket :)