Stream: git-wasmtime

Topic: wasmtime / issue #8857 Problems with leveraging `cargo co...


view this post on Zulip Wasmtime GitHub notifications bot (Jun 21 2024 at 06:46):

Froidoh opened issue #8857:

Okay, so I am a total noob with wasm(time) and my mistake is totally a layer 8 problem. But after digging I couldn't find the solution, so here I am, opening an issue (sorry for that!)

What I want:

I want to leverage cargo component and wasmtime to create a simple plugin system experiment.

So I created a new (binary) webassembly component via: cargo component new test-plugin.

[package]
name = "test-plugin"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] }

[profile.release]
codegen-units = 1
opt-level = "s"
debug = false
strip = true
lto = true

# [package.metadata.component]
# package = "component:test-plugin"

[package.metadata.component.target]
path = "wit"
world = "test-plugin"

[package.metadata.component.dependencies]
package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

And my src/main.rs

#[allow(warnings)]
mod bindings;

use bindings::exports::justatest::testplugin::greetings::Guest;

struct Component;

impl Guest for Component {
    fn hello(s: String) -> String {
        format!("Hello {s}")
    }
}

bindings::export!(Component with_types_in bindings);

fn main() {
    println!("Hello from test-plugin!");
}

I then generated the binary via cargo component build --release and let it run via:

wasmtime target/wasm32-wasi/release/test-plugin.wasm
Hello from test-plugin!

As expected. Via the wasmtime cli I cannot --invoke any functions on a wasm32-wasi webassembly module (at least that's what the cli tells me)...

So then I created a simple-host as a "normal", "native" rust project, compiled via llvm:

Cargo.toml:

[package]
name = "simple-host"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.86"
wasmtime = "22.0.0"
wasmtime-wasi = "22.0.0"
#wit-component = "0.211.1"

main.rs

use wasmtime::{
    component::{Component, Linker, Val},
    Config, Engine,
};
use wasmtime_wasi::{WasiCtxBuilder, WasiView};

/*
fn convert_to_component(path: impl AsRef<Path>) -> wasmtime::Result<Vec<u8>> {

    let bytes = &std::fs::read(&path).context("failed to read input file")?;
    wit_component::ComponentEncoder::default()
        .module(&bytes)?
        .encode()
}
*/

struct Host {
    ctx: wasmtime_wasi::WasiCtx,
    table: wasmtime_wasi::ResourceTable,
}

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

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

fn main() -> wasmtime::Result<()> {
    let mut config = Config::new();
    config.wasm_component_model(true);
    //config.async_support(true);
    let engine = Engine::new(&config)?;
    // Load the component from disk
    let component = std::fs::read("../test-plugin/target/wasm32-wasi/release/test-plugin.wasm")?;
    let component = Component::from_binary(&engine, &component)?;
    //println!("bytes: {bytes:?}");

    // As `cargo component` by default builds as `wasm32-wasi` webassembly module, we need to also include `wasmtime-wasi` and provide certain native functionality (in my own words)
    let mut wasi = WasiCtxBuilder::new();
    wasi.arg("--help");
    let host = Host {
        ctx: wasi.build(),
        table: wasmtime_wasi::ResourceTable::new(),
    };

    let mut store = wasmtime::Store::new(&engine, host);
    // Configure the linker
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker_sync(&mut linker)?;
    // The component expects one import `name` that
    // takes no params and returns a string

    linker
            .root()
            //.func_wrap("name", |_store, _params: ()| Ok((String::from("Alice"),)))?
        ;

    println!("have linker");
    // Instantiate the component
    let instance = linker.instantiate(&mut store, &component)?;
    // TODO: find out whethere there is a way to programmatically "explore" an instance,
    // to see it's "API" so to speak.
    // Could be useful for generating some docs or whatnot
    /*
    let mut exports = instance.exports(&mut store);
    let root = exports.instance("greetings").expect("greetings to exist");
    let modules = root.modules();
    for (name, module) in modules {
        println!("module: {module:?}")
    }
    println!("no modules?!");
    */

    println!("have instance");

    /*
    // TODO: I definitely misunderstood modules, because "greetings" does not exist
    let module = instance
        .get_module(&mut store, "greetings")
        .expect("module not found");
    */

    // Call the `greet` function
    let func = instance
        .get_func(&mut store, "hello")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

    Ok(())
}

When I run this I get a panic for hello export not found.

What am I doing wrong?

On the other hand, if I create a library component via cargo component new test-plugin-lib --lib which creates a default wit file and impl for it:

package component:test-plugin-lib;

/// An example world for the component to target.
world example {
    export hello-world: func() -> string;
}

and then load this and call it like so:

    let func = instance
        .get_func(&mut store, "hello-world")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[],
        //&[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

it works like a charm.

So I guess my problem boils down to one or two things:

1) How do I get a function in a "namespace" programmatically like hello if the wit file looks like:

package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

2) Maybe it's different when it's a "binary" webassembly component in contrast to a lib?

view this post on Zulip Wasmtime GitHub notifications bot (Jun 21 2024 at 06:48):

Froidoh edited issue #8857:

Okay, so I am a total noob with wasm(time) and my mistake is totally a layer 8 problem. But after digging I couldn't find the solution, so here I am, opening an issue (sorry for that!)

What I want:

I want to leverage cargo component and wasmtime to create a simple plugin system experiment.

So I created a new (binary) webassembly component via: cargo component new test-plugin.

[package]
name = "test-plugin"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] }

[profile.release]
codegen-units = 1
opt-level = "s"
debug = false
strip = true
lto = true

# [package.metadata.component]
# package = "component:test-plugin"

[package.metadata.component.target]
path = "wit"
world = "test-plugin"

[package.metadata.component.dependencies]
package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

And my src/main.rs

#[allow(warnings)]
mod bindings;

use bindings::exports::justatest::testplugin::greetings::Guest;

struct Component;

impl Guest for Component {
    fn hello(s: String) -> String {
        format!("Hello {s}")
    }
}

bindings::export!(Component with_types_in bindings);

fn main() {
    println!("Hello from test-plugin!");
}

I then generated the binary via cargo component build --release and let it run via:

wasmtime target/wasm32-wasi/release/test-plugin.wasm
Hello from test-plugin!

As expected. Via the wasmtime cli I cannot --invoke any functions on a wasm32-wasi webassembly module (at least that's what the cli tells me)...

So then I created a simple-host as a "normal", "native" rust project, compiled via llvm:

Cargo.toml:

[package]
name = "simple-host"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.86"
wasmtime = "22.0.0"
wasmtime-wasi = "22.0.0"
#wit-component = "0.211.1"

main.rs

use wasmtime::{
    component::{Component, Linker, Val},
    Config, Engine,
};
use wasmtime_wasi::{WasiCtxBuilder, WasiView};

/*
fn convert_to_component(path: impl AsRef<Path>) -> wasmtime::Result<Vec<u8>> {

    let bytes = &std::fs::read(&path).context("failed to read input file")?;
    wit_component::ComponentEncoder::default()
        .module(&bytes)?
        .encode()
}
*/

struct Host {
    ctx: wasmtime_wasi::WasiCtx,
    table: wasmtime_wasi::ResourceTable,
}

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

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

fn main() -> wasmtime::Result<()> {
    let mut config = Config::new();
    config.wasm_component_model(true);
    //config.async_support(true);
    let engine = Engine::new(&config)?;
    // Load the component from disk
    let component = std::fs::read("../test-plugin/target/wasm32-wasi/release/test-plugin.wasm")?;
    let component = Component::from_binary(&engine, &component)?;
    //println!("bytes: {bytes:?}");

    // As `cargo component` by default builds as `wasm32-wasi` webassembly module, we need to also include `wasmtime-wasi` and provide certain native functionality (in my own words)
    let mut wasi = WasiCtxBuilder::new();
    wasi.arg("--help");
    let host = Host {
        ctx: wasi.build(),
        table: wasmtime_wasi::ResourceTable::new(),
    };

    let mut store = wasmtime::Store::new(&engine, host);
    // Configure the linker
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker_sync(&mut linker)?;
    // The component expects one import `name` that
    // takes no params and returns a string

    linker
            .root()
            //.func_wrap("name", |_store, _params: ()| Ok((String::from("Alice"),)))?
        ;

    println!("have linker");
    // Instantiate the component
    let instance = linker.instantiate(&mut store, &component)?;
    // TODO: find out whethere there is a way to programmatically "explore" an instance,
    // to see it's "API" so to speak.
    // Could be useful for generating some docs or whatnot
    /*
    let mut exports = instance.exports(&mut store);
    let root = exports.instance("greetings").expect("greetings to exist");
    let modules = root.modules();
    for (name, module) in modules {
        println!("module: {module:?}")
    }
    println!("no modules?!");
    */

    println!("have instance");

    /*
    // TODO: I definitely misunderstood modules, because "greetings" does not exist
    let module = instance
        .get_module(&mut store, "greetings")
        .expect("module not found");
    */

    // Call the `greet` function
    let func = instance
        .get_func(&mut store, "hello")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

    Ok(())
}

When I run this I get a panic for hello export not found.

What am I doing wrong? I also tried with: greetings.hello and greetings:hello for get_func but to no avail.

On the other hand, if I create a library component via cargo component new test-plugin-lib --lib which creates a default wit file and impl for it:

package component:test-plugin-lib;

/// An example world for the component to target.
world example {
    export hello-world: func() -> string;
}

and then load this and call it like so:

    let func = instance
        .get_func(&mut store, "hello-world")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[],
        //&[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

it works like a charm.

So I guess my problem boils down to one or two things:

1) How do I get a function in a "namespace" programmatically like hello if the wit file looks like:

package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

2) Maybe it's different when it's a "binary" webassembly component in contrast to a lib?

view this post on Zulip Wasmtime GitHub notifications bot (Jun 21 2024 at 06:54):

Froidoh edited issue #8857:

Okay, so I am a total noob with wasm(time) and my mistake is totally a layer 8 problem. But after digging I couldn't find the solution, so here I am, opening an issue (sorry for that!)

What I want:

I want to leverage cargo component and wasmtime to create a simple plugin system experiment.

So I created a new (binary) webassembly component via: cargo component new test-plugin.

[package]
name = "test-plugin"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] }

[profile.release]
codegen-units = 1
opt-level = "s"
debug = false
strip = true
lto = true

# [package.metadata.component]
# package = "component:test-plugin"

[package.metadata.component.target]
path = "wit"
world = "test-plugin"

[package.metadata.component.dependencies]
package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

And my src/main.rs

#[allow(warnings)]
mod bindings;

use bindings::exports::justatest::testplugin::greetings::Guest;

struct Component;

impl Guest for Component {
    fn hello(s: String) -> String {
        format!("Hello {s}")
    }
}

bindings::export!(Component with_types_in bindings);

fn main() {
    println!("Hello from test-plugin!");
}

I then generated the binary via cargo component build --release and let it run via:

wasmtime target/wasm32-wasi/release/test-plugin.wasm
Hello from test-plugin!

As expected. Via the wasmtime cli I cannot --invoke any functions on a wasm32-wasi webassembly module (at least that's what the cli tells me)...

So then I created a simple-host as a "normal", "native" rust project, compiled via llvm:

Cargo.toml:

[package]
name = "simple-host"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.86"
wasmtime = "22.0.0"
wasmtime-wasi = "22.0.0"
#wit-component = "0.211.1"

main.rs

use wasmtime::{
    component::{Component, Linker, Val},
    Config, Engine,
};
use wasmtime_wasi::{WasiCtxBuilder, WasiView};

/*
fn convert_to_component(path: impl AsRef<Path>) -> wasmtime::Result<Vec<u8>> {

    let bytes = &std::fs::read(&path).context("failed to read input file")?;
    wit_component::ComponentEncoder::default()
        .module(&bytes)?
        .encode()
}
*/

struct Host {
    ctx: wasmtime_wasi::WasiCtx,
    table: wasmtime_wasi::ResourceTable,
}

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

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

fn main() -> wasmtime::Result<()> {
    let mut config = Config::new();
    config.wasm_component_model(true);
    //config.async_support(true);
    let engine = Engine::new(&config)?;
    // Load the component from disk
    let component = std::fs::read("../test-plugin/target/wasm32-wasi/release/test-plugin.wasm")?;
    let component = Component::from_binary(&engine, &component)?;
    //println!("bytes: {bytes:?}");

    // As `cargo component` by default builds as `wasm32-wasi` webassembly module, we need to also include `wasmtime-wasi` and provide certain native functionality (in my own words)
    let mut wasi = WasiCtxBuilder::new();
    wasi.arg("--help");
    let host = Host {
        ctx: wasi.build(),
        table: wasmtime_wasi::ResourceTable::new(),
    };

    let mut store = wasmtime::Store::new(&engine, host);
    // Configure the linker
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker_sync(&mut linker)?;
    // The component expects one import `name` that
    // takes no params and returns a string

    linker
            .root()
            //.func_wrap("name", |_store, _params: ()| Ok((String::from("Alice"),)))?
        ;

    println!("have linker");
    // Instantiate the component
    let instance = linker.instantiate(&mut store, &component)?;
    // TODO: find out whethere there is a way to programmatically "explore" an instance,
    // to see it's "API" so to speak.
    // Could be useful for generating some docs or whatnot
    /*
    let mut exports = instance.exports(&mut store);
    let root = exports.instance("greetings").expect("greetings to exist");
    let modules = root.modules();
    for (name, module) in modules {
        println!("module: {module:?}")
    }
    println!("no modules?!");
    */

    println!("have instance");

    /*
    // TODO: I definitely misunderstood modules, because "greetings" does not exist
    let module = instance
        .get_module(&mut store, "greetings")
        .expect("module not found");
    */

    // Call the `greet` function
    let func = instance
        .get_func(&mut store, "hello")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

    Ok(())
}

When I run this I get a panic for hello export not found.

What am I doing wrong? I also tried with: greetings.hello and greetings:hello for get_func but to no avail.

On the other hand, if I create a library component via cargo component new test-plugin-lib --lib which creates a default wit file and impl for it:

package component:test-plugin-lib;

/// An example world for the component to target.
world example {
    export hello-world: func() -> string;
}

and then load this and call it like so:

    let func = instance
        .get_func(&mut store, "hello-world")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[],
        //&[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

it works like a charm.

So I guess my problem boils down to one or two things:

1) How do I get a function in an interface programmatically like hello if the wit file looks like:

package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

2) Maybe it's different when it's a "binary" webassembly component in contrast to a lib?

view this post on Zulip Wasmtime GitHub notifications bot (Jun 21 2024 at 07:02):

Froidoh edited issue #8857:

Okay, so I am a total noob with wasm(time) and my mistake is totally a layer 8 problem. But after digging I couldn't find the solution, so here I am, opening an issue (sorry for that!)

What I want:

I want to leverage cargo component and wasmtime to create a simple plugin system experiment.

So I created a new (binary) webassembly component via: cargo component new test-plugin.

[package]
name = "test-plugin"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] }

[profile.release]
codegen-units = 1
opt-level = "s"
debug = false
strip = true
lto = true

# [package.metadata.component]
# package = "component:test-plugin"

[package.metadata.component.target]
path = "wit"
world = "test-plugin"

[package.metadata.component.dependencies]
package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

And my src/main.rs

#[allow(warnings)]
mod bindings;

use bindings::exports::justatest::testplugin::greetings::Guest;

struct Component;

impl Guest for Component {
    fn hello(s: String) -> String {
        format!("Hello {s}")
    }
}

bindings::export!(Component with_types_in bindings);

fn main() {
    println!("Hello from test-plugin!");
}

I then generated the binary via cargo component build --release and let it run via:

wasmtime target/wasm32-wasi/release/test-plugin.wasm
Hello from test-plugin!

As expected. Via the wasmtime cli I cannot --invoke any functions on a wasm32-wasi webassembly module (at least that's what the cli tells me)...

So then I created a simple-host as a "normal", "native" rust project, compiled via llvm:

Cargo.toml:

[package]
name = "simple-host"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.86"
wasmtime = "22.0.0"
wasmtime-wasi = "22.0.0"
#wit-component = "0.211.1"

main.rs

use wasmtime::{
    component::{Component, Linker, Val},
    Config, Engine,
};
use wasmtime_wasi::{WasiCtxBuilder, WasiView};

/*
fn convert_to_component(path: impl AsRef<Path>) -> wasmtime::Result<Vec<u8>> {

    let bytes = &std::fs::read(&path).context("failed to read input file")?;
    wit_component::ComponentEncoder::default()
        .module(&bytes)?
        .encode()
}
*/

struct Host {
    ctx: wasmtime_wasi::WasiCtx,
    table: wasmtime_wasi::ResourceTable,
}

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

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

fn main() -> wasmtime::Result<()> {
    let mut config = Config::new();
    config.wasm_component_model(true);
    //config.async_support(true);
    let engine = Engine::new(&config)?;
    // Load the component from disk
    let component = std::fs::read("../test-plugin/target/wasm32-wasi/release/test-plugin.wasm")?;
    let component = Component::from_binary(&engine, &component)?;
    //println!("bytes: {bytes:?}");

    // As `cargo component` by default builds as `wasm32-wasi` webassembly module, we need to also include `wasmtime-wasi` and provide certain native functionality (in my own words)
    let mut wasi = WasiCtxBuilder::new();
    wasi.arg("--help");
    let host = Host {
        ctx: wasi.build(),
        table: wasmtime_wasi::ResourceTable::new(),
    };

    let mut store = wasmtime::Store::new(&engine, host);
    // Configure the linker
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker_sync(&mut linker)?;
    // The component expects one import `name` that
    // takes no params and returns a string

    linker
            .root()
            //.func_wrap("name", |_store, _params: ()| Ok((String::from("Alice"),)))?
        ;

    println!("have linker");
    // Instantiate the component
    let instance = linker.instantiate(&mut store, &component)?;
    // TODO: find out whethere there is a way to programmatically "explore" an instance,
    // to see it's "API" so to speak.
    // Could be useful for generating some docs or whatnot
    /*
    let mut exports = instance.exports(&mut store);
    let root = exports.instance("greetings").expect("greetings to exist");
    let modules = root.modules();
    for (name, module) in modules {
        println!("module: {module:?}")
    }
    println!("no modules?!");
    */

    println!("have instance");

    /*
    // TODO: I definitely misunderstood modules, because "greetings" does not exist
    let module = instance
        .get_module(&mut store, "greetings")
        .expect("module not found");
    */

    // Call the `greet` function
    let func = instance
        .get_func(&mut store, "hello")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

    Ok(())
}

When I run this I get a panic for hello export not found.

What am I doing wrong? I also tried with: greetings.hello and greetings:hello for get_func but to no avail.

On the other hand, if I create a library component via cargo component new test-plugin-lib --lib which creates a default wit file and impl for it:

package component:test-plugin-lib;

/// An example world for the component to target.
world example {
    export hello-world: func() -> string;
}

and then load this and call it like so:

    let func = instance
        .get_func(&mut store, "hello-world")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[],
        //&[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

it works like a charm.

So I guess my problem boils down to one or two things:

1) How do I get a function in an interface programmatically like hello if the wit file looks like:

package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

~2) Maybe it's different when it's a "binary" webassembly component in contrast to a lib?~

I can answer that one: It's not!

If I add a helloto function like to:

package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
  export helloto: func(name: string) -> string;
}

and implement it like so:

#[allow(warnings)]
mod bindings;

use bindings::exports::justatest::testplugin::greetings::Guest;
use bindings::Guest as WorldGuest;

struct Component;

impl Guest for Component {
    fn hello(s: String) -> String {
        format!("Hello {s}")
    }
}

impl WorldGuest for Component {
    fn helloto(name: String) -> String {
        format!("Bonjour {name}")
    }
}

bindings::export!(Component with_types_in bindings);

fn main() {
    println!("Hello from test-plugin!");
}

then I can invoke helloto just fine :)

So it all boils down to: > how can I invoke a function on an interface?!

view this post on Zulip Wasmtime GitHub notifications bot (Jun 21 2024 at 07:03):

Froidoh edited issue #8857:

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 couldn't find the solution, so here I am, opening an issue (sorry for that!)

What I want:

I want to leverage cargo component and wasmtime to create a simple plugin system experiment.

So I created a new (binary) webassembly component via: cargo component new test-plugin.

[package]
name = "test-plugin"
version = "0.1.0"
edition = "2021"

[dependencies]
wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] }

[profile.release]
codegen-units = 1
opt-level = "s"
debug = false
strip = true
lto = true

# [package.metadata.component]
# package = "component:test-plugin"

[package.metadata.component.target]
path = "wit"
world = "test-plugin"

[package.metadata.component.dependencies]
package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

And my src/main.rs

#[allow(warnings)]
mod bindings;

use bindings::exports::justatest::testplugin::greetings::Guest;

struct Component;

impl Guest for Component {
    fn hello(s: String) -> String {
        format!("Hello {s}")
    }
}

bindings::export!(Component with_types_in bindings);

fn main() {
    println!("Hello from test-plugin!");
}

I then generated the binary via cargo component build --release and let it run via:

wasmtime target/wasm32-wasi/release/test-plugin.wasm
Hello from test-plugin!

As expected. Via the wasmtime cli I cannot --invoke any functions on a wasm32-wasi webassembly module (at least that's what the cli tells me)...

So then I created a simple-host as a "normal", "native" rust project, compiled via llvm:

Cargo.toml:

[package]
name = "simple-host"
version = "0.1.0"
edition = "2021"

[dependencies]
anyhow = "1.0.86"
wasmtime = "22.0.0"
wasmtime-wasi = "22.0.0"
#wit-component = "0.211.1"

main.rs

use wasmtime::{
    component::{Component, Linker, Val},
    Config, Engine,
};
use wasmtime_wasi::{WasiCtxBuilder, WasiView};

/*
fn convert_to_component(path: impl AsRef<Path>) -> wasmtime::Result<Vec<u8>> {

    let bytes = &std::fs::read(&path).context("failed to read input file")?;
    wit_component::ComponentEncoder::default()
        .module(&bytes)?
        .encode()
}
*/

struct Host {
    ctx: wasmtime_wasi::WasiCtx,
    table: wasmtime_wasi::ResourceTable,
}

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

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

fn main() -> wasmtime::Result<()> {
    let mut config = Config::new();
    config.wasm_component_model(true);
    //config.async_support(true);
    let engine = Engine::new(&config)?;
    // Load the component from disk
    let component = std::fs::read("../test-plugin/target/wasm32-wasi/release/test-plugin.wasm")?;
    let component = Component::from_binary(&engine, &component)?;
    //println!("bytes: {bytes:?}");

    // As `cargo component` by default builds as `wasm32-wasi` webassembly module, we need to also include `wasmtime-wasi` and provide certain native functionality (in my own words)
    let mut wasi = WasiCtxBuilder::new();
    wasi.arg("--help");
    let host = Host {
        ctx: wasi.build(),
        table: wasmtime_wasi::ResourceTable::new(),
    };

    let mut store = wasmtime::Store::new(&engine, host);
    // Configure the linker
    let mut linker = Linker::new(&engine);
    wasmtime_wasi::add_to_linker_sync(&mut linker)?;
    // The component expects one import `name` that
    // takes no params and returns a string

    linker
            .root()
            //.func_wrap("name", |_store, _params: ()| Ok((String::from("Alice"),)))?
        ;

    println!("have linker");
    // Instantiate the component
    let instance = linker.instantiate(&mut store, &component)?;
    // TODO: find out whethere there is a way to programmatically "explore" an instance,
    // to see it's "API" so to speak.
    // Could be useful for generating some docs or whatnot
    /*
    let mut exports = instance.exports(&mut store);
    let root = exports.instance("greetings").expect("greetings to exist");
    let modules = root.modules();
    for (name, module) in modules {
        println!("module: {module:?}")
    }
    println!("no modules?!");
    */

    println!("have instance");

    /*
    // TODO: I definitely misunderstood modules, because "greetings" does not exist
    let module = instance
        .get_module(&mut store, "greetings")
        .expect("module not found");
    */

    // Call the `greet` function
    let func = instance
        .get_func(&mut store, "hello")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

    Ok(())
}

When I run this I get a panic for hello export not found.

What am I doing wrong? I also tried with: greetings.hello and greetings:hello for get_func but to no avail.

On the other hand, if I create a library component via cargo component new test-plugin-lib --lib which creates a default wit file and impl for it:

package component:test-plugin-lib;

/// An example world for the component to target.
world example {
    export hello-world: func() -> string;
}

and then load this and call it like so:

    let func = instance
        .get_func(&mut store, "hello-world")
        .expect("hello export not found");
    let mut result = [wasmtime::component::Val::String("".into())];
    func.call(
        &mut store,
        &[],
        //&[Val::String("freund nachtigall".to_string())],
        &mut result,
    )?;

    println!("Greeting: {:?}", result);

it works like a charm.

So I guess my problem boils down to one or two things:

1) How do I get a function in an interface programmatically like hello if the wit file looks like:

package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
}

~2) Maybe it's different when it's a "binary" webassembly component in contrast to a lib?~

I can answer that one: It's not!

If I add a helloto function like to:

package justatest:testplugin@0.1.0;

interface greetings {
  hello: func(s: string) -> string;
}

world test-plugin {
  export greetings;
  export helloto: func(name: string) -> string;
}

and implement it like so:

#[allow(warnings)]
mod bindings;

use bindings::exports::justatest::testplugin::greetings::Guest;
use bindings::Guest as WorldGuest;

struct Component;

impl Guest for Component {
    fn hello(s: String) -> String {
        format!("Hello {s}")
    }
}

impl WorldGuest for Component {
    fn helloto(name: String) -> String {
        format!("Bonjour {name}")
    }
}

bindings::export!(Component with_types_in bindings);

fn main() {
    println!("Hello from test-plugin!");
}

then I can invoke helloto just fine :)

So it all boils down to:

how can I invoke a function on an interface?!


Last updated: Oct 23 2024 at 20:03 UTC