Stream: wasmtime

Topic: How do I get an interface method programmatically?


view this post on Zulip Samuel Mwangi (Jul 20 2024 at 17:13):

I am trying to implement the solution proposed in this GitHub issue in the wasmtime repository.

I have a wasm component whose interface is defined as:

package docs:adder@0.1.0;

interface add {
    add: func(a: s32, b: s32) -> s32;
}

world adder {
    export add;
}

I am trying to invoke the exported add function using the following Rust code:

use anyhow::Result;
use wasmtime::component::{Component, Linker, Val};
use wasmtime::{Engine, Store};
use wasmtime_wasi::{ResourceTable, WasiCtx, WasiCtxBuilder, WasiView};

fn main() -> Result<()> {
    // Create an engine and store
    let engine = Engine::default();
    let wasi = WasiCtxBuilder::new().inherit_stdio().build();
    let mut store = Store::new(&engine, ServerWasiView::new(wasi));

    // Load the component
    let component = Component::from_file(&engine, "target/wasm32-wasip1/release/add.wasm")?;

    // Create a linker
    let mut linker = Linker::<ServerWasiView>::new(&engine);

    // Add WASI to the linker
    wasmtime_wasi::add_to_linker_sync(&mut linker)?;

    // Instantiate the component
    let instance = linker.instantiate(&mut store, &component)?;

    let possible_names = [
        "add#add",
        "docs:adder/add@0.1.0#add",
        "adder/add@0.1.0#add",
        "/add@0.1.0#add",
        "add@0.1.0#add",
        "#add",
        "add",
    ];

    {
        let mut exports = instance.exports(&mut store);

        for name in possible_names.iter() {
            if exports.instance(name).is_some() {
                println!("Found add instance with name: {}", name);
            }
        }
    }

    // Try to get the add function with different possible names
    let add = instance
        .get_func(&mut store, "add")
        .or_else(|| instance.get_func(&mut store, "docs:adder/add@0.1.0#add"))
        .or_else(|| instance.get_func(&mut store, "adder/add@0.1.0#add"))
        .or_else(|| instance.get_func(&mut store, "/add@0.1.0#add"))
        .or_else(|| instance.get_func(&mut store, "add@0.1.0#add"))
        .or_else(|| instance.get_func(&mut store, "#add"))
        .or_else(|| instance.get_func(&mut store, "add"))
        .expect("add function not found");

    println!("Found add function");

    // Prepare the arguments and results
    let mut results = [Val::S32(0)];

    // Call the function
    add.call(&mut store, &[Val::S32(5), Val::S32(3)], &mut results)?;

    // Extract the result
    let Val::S32(result) = results[0] else {
        panic!("Unexpected result type");
    };

    println!("5 + 3 = {}", result);

    Ok(())
}

struct ServerWasiView {
    table: ResourceTable,
    ctx: WasiCtx,
}

impl ServerWasiView {
    fn new(ctx: WasiCtx) -> Self {
        let table = ResourceTable::new();
        Self { table, ctx }
    }
}

impl WasiView for ServerWasiView {
    fn table(&mut self) -> &mut ResourceTable {
        &mut self.table
    }

    fn ctx(&mut self) -> &mut WasiCtx {
        &mut self.ctx
    }
}

I have not managed to get a reference to the exported function in the host code. What am I missing?

EDIT: So it all boils down to: how can I invoke a function on an interface?! ORIGINAL: Okay, so I am a total noob with wasm(time) and my mistake is totally a layer 8 problem. But after digging I co...

view this post on Zulip Ramon Klass (Jul 20 2024 at 18:48):

target/wasm32-wasip1 suggests you didn't compile as a component, components are wasip2

view this post on Zulip Samuel Mwangi (Jul 22 2024 at 11:40):

Thank you, @Ramon Klass .

The information you provided is incorrect though. I tried going down that path but could not get a wasm32-wasip2 target for stable and nightly Rust toolchains.

I have figured out what I was doing wrong and provided an example for future users who run into the same issue.

Run a basic WebAssembly component using wasmtime that exports a function named `add` through an interface. - cryarchy/add-workspace-basic

view this post on Zulip Ramon Klass (Jul 22 2024 at 11:48):

I'm glad you found a solution and thanks for sharing it :) My guess was really a shot in the dark, personally I've started only working with components through bindgen a long time ago, I should also do more with the lowlevel interfaces again :)

view this post on Zulip Victor Adossi (Jul 22 2024 at 15:50):

Hey @Samuel Mwangi if there's anything you could contribute to the docs that you read which lead you down the wrong path/wasn't clear enough that would be greatly appreciated!

I looked at the examples you posted and couldn't figure out what was wrong (and unlike @Ramon Klass I didn't even have an educated guess to offer in trying to help) -- so it would be great if we could fix this for future users

view this post on Zulip Samuel Mwangi (Jul 22 2024 at 16:20):

Hi, @Victor Adossi .

I will look into how I can contribute to the docs. I am trying to resolve a couple of other issues that I encountered when trying to invoke a function defined in one wasm component in another component without having to pipe the function via a wrapper as I have in the example associated with this issue.

If you have any idea on how I can go about this I will greatly appreciate any help you can offer.

Once I am done. I will provide comprehensive documentation on how to set up the projects correctly and how to invoke a function exported from one wasm module in another wasm module.

I have provided an example to reproduce the bug. To run the example: Run the following command from the add folder: cargo component build --release Run the following command from the calculator fol...

view this post on Zulip Victor Adossi (Jul 23 2024 at 05:17):

Was the core issue that you wanted to trigger the function call from one component in another, without the host call?

Ah it looks like Alex alreadyy helped squash the errors? -- did you need more help on this issue or do you want to mark it resolved?

Along with Alex's suggestion of looking into wac codebase, I'd suggest also looking at/using appreciate the wasm-tools compose codebase (and along the way, wasm-tools component wit & wasm-tools print)

I have provided an example to reproduce the bug. To run the example: Run the following command from the add folder: cargo component build --release Run the following command from the calculator fol...
WebAssembly Composition (WAC) tooling. Contribute to peterhuene/wac development by creating an account on GitHub.
CLI and Rust libraries for low-level manipulation of WebAssembly modules - bytecodealliance/wasm-tools

view this post on Zulip Samuel Mwangi (Jul 23 2024 at 17:31):

Thank you, @Victor Adossi.

Yes. I wanted to invoke a function defined in one component from another without providing a host function to forward the call.

Thank you for the links. I did not want to compose the components. I wanted them linked separately.

It would be nice if it was possible to do something like:

...

let component_1 = ...
let component_2 = ...

let linker = ...

let instance_1 = linker.instantiate_async(&mut store, &component_1).await?;

linker.link_from_instance(instance_1); // define the names exported from component_1 in the linker

let (_, interface_export) = component_2
    .export_index(None, "package:name/interface")
    .unwrap();
let (_, fn_2_export) = component_2
    .export_index(Some(&calc_interface_export), "fn-2")
    .unwrap();

let component_2_instance = linker
    .instantiate_async(&mut store, &component_2)
    .await?;
let fn_2 = calc_instance
    .get_func(&mut store, fn_2_export)
    .expect("fn-2 function not found");
let mut result = [Val::S32(0)];
fn_2.call_async(...).await?; // defined in component_2 and internally invokes a function defined in component_1

...

view this post on Zulip Victor Adossi (Jul 24 2024 at 16:32):

Yeah I think what you're asking for might not be possible -- is there a way you could imagine it working?

The binaries are separate, so I'm not sure how they could call each other without being composed or something doing the call. If you are trying to achieve something like... wasm-in-wasm (i.e. trying to feed a component to another component) there is actually some really interesting work done by @Ryan Levick (rylev) on that:

https://github.com/rylev/dyna

At the end of the day you still need a host to do some forwarding though, so I'm not sure if this fits what you wanted!

Instantiate, inspect, and invoke Wasm components from within a component. - rylev/dyna

view this post on Zulip Samuel Mwangi (Jul 24 2024 at 21:34):

Thank you, @Victor Adossi.

The work by @Ryan Levick (rylev) is pretty cool and is somewhat similar to what I was trying to achieve. The subtle difference is that the host application would be responsible for instantiating both components and generating the request piping code using the component WITs.


Last updated: Nov 22 2024 at 16:03 UTC