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?
target/wasm32-wasip1
suggests you didn't compile as a component, components are wasip2
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.
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 :)
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
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.
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
)
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
...
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:
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!
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: Dec 23 2024 at 14:03 UTC