wasmedge-bindgen icon indicating copy to clipboard operation
wasmedge-bindgen copied to clipboard

Calling host functions from the guest

Open clarkmcc opened this issue 3 years ago • 7 comments

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?

clarkmcc avatar Jun 14 '22 16:06 clarkmcc

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.

juntao avatar Jul 06 '22 02:07 juntao

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'

Tails avatar Oct 10 '22 10:10 Tails

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(())
}

apepkuss avatar Oct 11 '22 04:10 apepkuss

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 avatar Oct 11 '22 08:10 Tails

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.

Tails avatar Oct 11 '22 11:10 Tails

@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!

apepkuss avatar Oct 11 '22 11:10 apepkuss

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 :)

Tails avatar Oct 11 '22 12:10 Tails