Maybe it's too early to try this, but I am trying to create an export in Rust and link it as a component to a Wasm module with this
fn main() -> wasmtime::Result<()> {
// Configure an `Engine` and compile the `Component` that is being run for
// the application.
let mut config = Config::new();
config.wasm_component_model(true);
let engine = Engine::new(&config)?;
let module = Module::from_file(&engine, "./cswasi.wasm")?;
let mut linker = Linker::new(&engine);
HelloWorld::add_to_linker(&mut linker, |state: &mut MyState| state)?;
let mut store = Store::new(
&engine,
MyState {
name: 1f32,
},
);
let (bindings, _) = HelloWorld::instantiate(&mut store, &module, &linker)?;
which is wrong, for starters instantiate
doesn't take the module but the component. Is this too early, or can I modify this to work?
preceeding code is
bindgen!();
struct MyState {
name: f32,
}
// Imports into the world, like the `name` import for this world, are satisfied
// through traits.
impl HelloWorldImports for MyState {
// Note the `Result` return value here where `Ok` is returned back to
// the component and `Err` will raise a trap.S
fn name(&mut self, f: f32) -> wasmtime::Result<()> {
println!("Hello from rust {}", f);
Ok(())
}
}
and the wit
package my:project
world hello-world {
import name: func(f: float32)
export greet: func()
}
@Scott Waye this should work, but you want let component = Component::from_binary(&engine, &component_bytes).unwrap();
I haven't personally been able to figure out function imports yet, but instances with functions can be added eg via
let mut iface_instance = linker.instance("iface")?;
iface_instance.func_wrap("name", |_store, params: (f32,)| {
Ok(())
})?;
For the WIT:
import iface: interface {
name: func(f: float32)
}
also depending on how you produce the component you may need to include the wasi bindings
there's a wasi linking example in https://github.com/bytecodealliance/componentize-js/blob/main/example/src/main.rs if it helps
for plain function imports which are not inside an instance, linker.root()
gives you the root namespace, as a substitute to linker.instance("name")?
ah nice, I wasn't aware of that
If my terminology is right, I thought the component would be produced by the bindgen!
macro and the impl
. I want to export from rust, so I change the wit to
world hello-world {
export name: func(f: float32)
}
but what is the trait name, I tried HelloWorld and HelloWorldExports and neither compiles. or if I func_wrap
do I not need the trait?
the trait should be named Host
i'm not sure off the top of my head what module it lives in. i usually run cargo doc
when i need to figure out what got generated.
the componentize-js example looks interesting, I guess I should be getting some wasm generated by the macro which I need to let component = Component::from_file(&engine, "hello.component.wasm").unwrap();
no wait, that can't be right
let me try linker.root
Pat Hickey said:
the trait should be named
Host
WHere does the string Host
come from, is that hard coded somewhere as it is not in my rust or wit?
and I get
error[E0405]: cannot find trait `Host` in this scope
--> src\main.rs:12:6
|
12 | impl Host for MyState {
| ^^^^ not found in this scope
every trait generated by wasmtime::component::bindgen! is named Host
, but they end up in different modules to differentiate them
im not sure what module the root ends up in (and maybe it doesnt, which would be a bug) - that was what i was suggesting cargo doc to explore
try hello_world::Host
i suppose
I see, unfortunately cargo doc
errors with
Documenting cs-runtime-example v0.1.0 (C:\github\cs-runtime-example-wasmtime)
error[E0405]: cannot find trait `Host` in this scope
--> src\main.rs:12:6
|
12 | impl Host for MyState {
| ^^^^ not found in this scope
For more information about this error, try `rustc --explain E0405`.
oh, you'll have to comment out the parts of your code that dont typecheck yet
it should be sufficient to just run bindgen in an otherwise empty crate and see the docs of it
thanks, doc
has completed, but I dont thinkg the trait was generated as a grep for Host in cs-runtime-example-wasmtime\target\doc\cs_runtime_example
revealed no hits
image.png
oh, ok. i had gotten import
and export
mixed up when i read your example.
export name: func...
means that the HelloWorld struct will have a run_name
method.
try adding import name2: func...
and then hopefully itll generate a trait
wasmtime is defining the implementation of a world, so the traits provide the implementations of imports, and methods on the world struct are used to call exports, which are implemented in the Component
Thanks, that does indeed generate the trait, in my case I want the rust to be the component and provide the method which will be called by a wasm module, at least that is what I'm trying to do. So if I'm reading your last comment right. I don't need the export, I just need the import.
I'm trying something which I hope is simpler, wrapping a function in the root namespace. I'm going to need a componet::Linker I think, but looking at https://github.com/bytecodealliance/componentize-js/blob/4afe77879cef8154f4c8671a61ffbd3d651fc251/example/src/main.rs#L27 that would seem easy, but for me I get
Compiling cs-runtime-example v0.1.0 (C:\github\cs-runtime-example-wasmtime)
error[E0282]: type annotations needed for `wasmtime::component::Linker<T>`
--> src\main.rs:36:9
|
36 | let mut linker = Linker::new(&engine);
| ^^^^^^^^^^
|
help: consider giving `linker` an explicit type, where the type for type parameter `T` is specified
|
36 | let mut linker: wasmtime::component::Linker<T> = Linker::new(&engine);
| ++++++++++++++++++++++++++++++++
For more information about this error, try `rustc --explain E0282`.
What type should be given to the variable?
Maybe I dont need that looking at other examples. I now have
world hello-world {
import name2: func (f: float32)
export name: func(f: float32)
}
and
fn main() -> wasmtime::Result<()> {
// Configure an `Engine` and compile the `Component` that is being run for
// the application.
let mut config = Config::new();
config.wasm_component_model(true);
let engine = Engine::new(&config)?;
let module = Module::from_file(&engine, "./cswasi.wasm")?;
let mut linker = Linker::new(&engine);
let mut state :MyState;
let mut store = Store::new(
&engine,
state
);
HelloWorld::add_to_linker(&mut linker, |x| x)?;
|x| x
seems to be a common way to call add_to_linker
, but I get
Compiling cs-runtime-example v0.1.0 (C:\github\cs-runtime-example-wasmtime)
error[E0282]: type annotations needed for `&mut T`
--> src\main.rs:45:45
|
45 | HelloWorld::add_to_linker(&mut linker, |x| x)?;
| ^
|
help: consider giving this closure parameter an explicit type, where the type for type parameter `T` is specified
|
45 | HelloWorld::add_to_linker(&mut linker, |x: &mut T| x)?;
| ++++++++
For more information about this error, try `rustc --explain E0282`.
error: could not compile `cs-runtime-example` (bin "cs-runtime-example") due to previous error
I don't think you need to call HelloWorld::add_to_linker, as long as you define the import function in the linker via eg linker.root().func_wrap
as above
HelloWorld::add_to_linker provides the correct func_wrap invocations that will dispatch to the Host traits
ah, I never figured out how to call it correctly myself :P
the closure argument given to add_to_linker is for mapping to the particular ctx type that your Host trait uses - the idea is that you can have some struct CustomCtx { wasi: WasiCtx, table: Table, hello: HelloCtx }
that suits your embedding
and then you provide an impl hello_world::Host for HelloCtx { ... }
and your struct HelloCtx {...}
can have whatever members you need to implement those imports.
the func wrapping that happens in add_to_linker is all about applying the closure you provide - in this case it would be something like |ctx: &mut CustomCtx| &mut ctx.hello
- to the data (T) kept in your Store<T> (accessed in the func_wrap via Caller<T>, but if you squint Caller is just a more restricted interface to Store)
the reason youve seen the |x| x
pattern a bunch in components is a complication we have at the moment where we need to share the Table
member across many different impls... in the future we're going to make Table provided directly by the component runtime instead of a plug in from wasi, and then all the WasiView nonsense will go away and you'll go back to calling Wasi::add_to_linker(&mut store, |ctx| &mut ctx.wasi)
you can ignore that explanation if you arent yet trying to do anything resource-like. proper support for resources is coming soon.
Thanks, that is super useful and I think I now have the structs and closure set up correctly. At https://radu-matei.com/pdf/wasm-components-host-implementations.pdf it has let instance = linker.instantiate(&mut store, &module)?;
but I suppose that is not the right way to do things now and I need a component wasm, not a module? I.e. its not possible to link a module with a component, or at least not yet.
Which seems to lead to the next question, how does one create a wasm component. There is wasm-tools component
but can it convert a module with an import
to a component that can be instantiated using wasmtime::component::from_file
that I could then use Linker
to link with the rust compoent?
I did try but get
C:\github\cs-runtime-example-wasmtime>wasm-tools component new cswasi.wasm -o cswasi.component.wasm
error: failed to encode a component from module
Caused by:
0: module requires an import interface named `rust`
This module does indeed have an import from rust
as the one I am trying to satisfy from the call to add_to_linker
I tried wasm-tools component embed --dummy -o dummy.wat -t -w hello-world wit/cswasi.wit
and it looks like the import name is going to be $root
, no way to change that from the wit I suppose?
(module
(type (;0;) (func (param f32)))
(type (;1;) (func (param i32 i32 i32 i32) (result i32)))
(import "$root" "name2" (func (;0;) (type 0)))
Having aligned the module name it wasm-tools component new
now seems to want to know where the import is coming from which seems counter intuitive
C:\github\cs-runtime-example-wasmtime>wasm-tools component new cswasi.wasm -o cswasi.component.wasm
error: failed to encode a component from module
Caused by:
0: no top-level imported function `wasmImportFloat32Param` specified
I changed my wit to use that name, but as the wit is not an import to wasm-tools component new
that seems irrelevant for this step.
Think I will look at the source code for linker.instantiate
to see what is uses to determine if a wasm file is a component or module
that doc from radu is super out of date, that was using machinery for components that was basically just a prototype for what ended up becoming the implementation today.
you want to use wasmtime::component::Linker
for all your linking. that will only accept a Component
in instantiate: https://docs.rs/wasmtime/latest/wasmtime/component/struct.Linker.html#method.instantiate
wasm-tools component new
creates a component from a module. to do so, it needs to map all of the module's imports and exports (we often call these the core wasm imports and exports) to component imports and exports. to do so, it needs all of the component type information
it can get that type information from one of two places: special custom sections in the wasm module, or from special adapter arguments (this is really just used to map wasi preview 1 to wasi preview 2, and i would recommend not using it for anything else)
if you are creating your wasm module using one of the various wit-bindgen language implementations, it will stick the special custom sections into your linked executable for you (exception being the C backend, where it emits an object file that you need to manually pass into your linking step, but thats really just a C-specific detail)
if you are not using wit-bindgen, then you have to replicate that behavior on your own somehow, and that is a whole can of worms, but for now i'll assume you are indeed using wit-bindgen - is that correct?
Pat Hickey said:
wit-bindgen - is that correct?
Not really, I(we) want to add the ability to wit-bindgen because we are using c#. So for now we are using a mixture of a prototype wit-bindgen
and the NativeAOT-LLVM c# compiler (which I help develop). I assume our Wasm that we produce is lacking some info that makes the wasm file a component, and I need to understand what those "special custom sections" are to see if ideally I can coerce clang/wasm-ld to produce them or if not, create a tool using a library that Alex Crichton mentioned wasm-encoder
For a first cut I could just edit the wat to add them. For my simple case, I'm guessing that is not too much.
I think a need a component-type
section, I'll see what the layout of that should be.
That is not trivial: https://github.com/bytecodealliance/wasm-tools/blob/main/crates/wit-component/src/metadata.rs
maybe not, wasm2wat doesn't help as apparently custom sections are not defined for wat
does look like it is text though
Contents of section Custom:
00b0f05: 1363 6f6d 706f 6e65 6e74 2d74 7970 653a .component-type:
00b0f15: 686f 7374 0300 0468 6f73 7400 6173 6d0d host...host.asm.
00b0f25: 0001 0007 3d01 4102 0141 0401 4001 036d ....=.A..A..@..m
00b0f35: 7367 7301 0003 0005 7072 696e 7401 0001 sgs.....print...
00b0f45: 4000 0100 0400 0372 756e 0101 0401 1165 @......run.....e
00b0f55: 7861 6d70 6c65 3a68 6f73 742f 686f 7374 xample:host/host
00b0f65: 0400 0045 0970 726f 6475 6365 7273 010c ...E.producers..
00b0f75: 7072 6f63 6573 7365 642d 6279 020d 7769 processed-by..wi
00b0f85: 742d 636f 6d70 6f6e 656e 7406 302e 3131 t-component.0.11
00b0f95: 2e30 1077 6974 2d62 696e 6467 656e 2d72 .0.wit-bindgen-r
00b0fa5: 7573 7405 302e 382e 300b 1601 0110 6578 ust.0.8.0.....ex
00b0fb5: 616d 706c 653a 686f 7374 2f77 6974 0300 ample:host/wit.
Will try to reuse https://github.com/bytecodealliance/wit-bindgen/blob/main/crates/c/src/component_type_object.rs Seems like the sensible path
a component type section contains a wasm component
so, to parse it, you need to use wasmparser again
I've produced a component section, but for this error
C:\github\cs-wit-bindgen>wasm-tools component new bin\Release\net8.0\wasi-wasm\native\cswasi.wasm -o my-component-cs.wasm --adapt wasi_snapshot_preview1=c:\github\rust_wit\host\wasi_snapshot_preview1.reactor.wasm
error: decoding custom section component-type:the-world
Caused by:
0: component-type version 2 does not match supported version 3
I suppose this is because the wasm-tools is newer than than the version specified in the custom section and I should merge the latest wit-bindgen
Looks like upgrading solved the version porblem. I'm not sure about the naming conventions here, when i get the error
C:\github\cs-wit-bindgen>wasm-tools component new bin\Release\net8.0\wasi-wasm\native\cswasi.wasm -o my-component-cs.wasm --adapt wasi_snapshot_preview1=c:\github\rust_wit\host\wasi_snapshot_preview1.reactor.wasm
error: failed to encode a component from module
Caused by:
0: failed to validate exported interface `foo:foo/floats`
1: module does not export required function `foo:foo/floats#float32-param`
Should I have a function in my wasm module with that exact name: foo:foo/floats#float32-param
?
yes this is an error where a core wasm being lifted into a component doesn't export the function of that name, and that symbol (with :
and /
and #
) is the name that's expected
Many thanks, I now have a componet from C# which feels like a decent step forward. I'm following https://github.com/bytecodealliance/wasmtime/issues/6523 for ideas and have the following error at runtime
Running `C:\github\cs-runtime-example-wasmtime\target\debug\cs-runtime-example.exe`
thread 'main' panicked at 'cannot use `func_wrap_async` without enabling async support in the config', C:\Users\scott\.cargo\registry\src\index.crates.io-6f17d22bba15001f\wasmtime-10.0.1\src\component\linker.rs:287:9
Is this a simple one? Sorry I'm a rust newbie.
in my toml
I have
wasmtime = { version= "10.0.1", features = ["component-model", "async"] }
I believe you need to use https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html#method.async_support to fix that error.
Thanks, that did fix it. I missed that from the other example :-O
Feels close
let customCtx = CustomCtx { state: state, table, wasi };
wasmtime_wasi::preview2::wasi::command::add_to_linker(&mut linker)?;
let mut store: Store<CustomCtx> = Store::new(
&engine,
customCtx
);
TheWorld::add_to_linker(&mut linker, |ctx : &mut CustomCtx| &mut ctx.state)?;
let instance = linker.instantiate_async(&mut store, &component.unwrap()).await;
let start = (instance.unwrap().get_func(store, "_start")).expect("export wasnt a function");
match start.call(&mut store, &[], &mut []) {
Ok(()) => {}
Err(trap) => {
panic!("execution of _start failed {}", trap);
}
}
complains
error[E0382]: borrow of moved value: `store`
--> src\main.rs:91:22
|
81 | let mut store: Store<CustomCtx> = Store::new(
| --------- move occurs because `store` has type `Store<CustomCtx>`, which does not implement the `Copy` trait
...
89 | let start = (instance.unwrap().get_func(store, "_start")).expect("export wasnt a function");
| ----- value moved here
90 |
91 | match start.call(&mut store, &[], &mut []) {
| ^^^^^^^^^^ value borrowed here after move
Have a got something wrong with the store creation?
nvm more RTFM required between keyboard and chair
Can WasiCtxBuilder
and inherit_stdout
be used to make printf
out put to stdout from cargo run
?
I'm trying to do some low level debugging
Currently my printfs seems to go nowhere
Maybe a simpler question is in order. If my component includes static global c++ constructors e.g.
static struct InitializeRuntimePointerHelper
{
InitializeRuntimePointerHelper()
{
RhSetRuntimeInitializationCallback(&InitializeRuntime);
}
} initializeRuntimePointerHelper;
Is wasmtime going to run them ?
I'm guessing it wont. So I'm trying to initialize things myself by calling __wasm_call_ctors
which is a function that I've exported before creating the component:
(export "__wasm_call_ctors" (func $__wasm_call_ctors))
However from rust
let (bindings, instance) = TheWorld::instantiate_async(&mut store, &component, &linker).await?;
let init_func = instance.get_func(&mut store, "__wasm_call_ctors").expect("__wasm_call_ctors not found");
panics
thread 'main' panicked at '__wasm_call_ctors not found', src\main.rs:105:72
Is this possible or is wasm-tools component new
going to remove my exports?
wasm and ctors is a sticky subject. when we last were having problems with it, here is where i wrote up everything i was able to learn from talking to joel, dan, and others https://github.com/bytecodealliance/preview2-prototyping/issues/99
preview 1 adapter short-circuits import function calls in ctors
this fix is done but it isnt actually relevant to you i dont think
guest bindgen behavior eliminates ctor calls in cabi_realloc
this change was done in the rust bindgen. if you're using a c bindgen, you may need to do it yourself.
wasi-libc still uses ctors, and the long-term goal of straitening out the whole ctors story by way of tool-conventions and canonical ABI support is kicked off but nowhere near done afaik
the simpler question might be what happens with your printfs. WasiCtxBuilder by default will not do anything with the stdout and stderr coming from your wasi program. if you inherit_stdout
and inherit_stderr
it will forward them to your host process's stdout and stderr.
so please do give that a try
the behavior you're seeing with export "__wasm_call_ctors"
being inside your component, but not available via instance.get_func, is that the export you are seeing is actually a core wasm module export
core wasm modules are present inside components, but only functions that are part of your wit definition's exports will get turned into component exports.
the core wasm modules and their exports inside a component are totally opaque, so you'll never be able to call __wasm_call_ctors from the wasmtime component apis.
Thanks. So it seems that if I want __wasm_call_ctors
to be called I'm going to have to do it myself, which might be possible - I'll need to try a few things. Regards the printf, I'll take a closer look at what I've done.
not quite - if you dont ever call __wasm_call_ctors yourself, lld will put calls to it in on each export function from your module
if you use __wasm_call_ctors even once in your code, then lld will not have that behavior
we suggest you invoke them manually so that it does not get inserted by lld before the invocation of cabi_realloc.
because during calls into cabi_realloc, the component is not allowed to call any export functions, and sometimes ctors can call export functions (e.g. the ctors in wasi-libc may end up initializing the env variables, for older wasi-libcs)
hope that makes sense. like i said before, the ctors story is tricky :)
Yes, thanks that does makes sense. I seem to have _start
which is stopping lld inserting the calls, I must have invoked lld
badly. If the only exports I have are those from the wit, I suppose I'll have to do it via a wit export
https://github.com/WebAssembly/wasi-sdk/issues/110#issuecomment-1185582357 I'll give this a try.
Is _initialize
executed by reactor components after instansiation ?
https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasmtime/src/linker.rs looks like it should
What I'e found is that _initialize
would work and be called if it was exported but it is not. Maybe while there appears to be no solution at present I can fork wasm-tools and change component new
to re-export.
I have a workaround for _intiialize
and I have got so far as the host calling the component. Now I want to call back to the host from the component, but I don't know what the wasm symbol should be for the instantiation to link the component back to the host. I.e. float32_param
here
use foo::foo::floats::Host;
use wasmtime_wasi::preview2::{WasiCtx, Table, WasiView, WasiCtxBuilder, self};
bindgen!({world:"the-world",async: true});
struct MyCtx {
}
// Imports into the world, like the `name` import for this world, are satisfied
// through traits.
#[async_trait]
impl Host for MyCtx {
// Note the `Result` return value here where `Ok` is returned back to
// the component and `Err` will raise a trap.S
async fn float32_param(&mut self,x:f32,) -> wasmtime::Result<()> {
println!("Hello from rust {}", x);
Ok(())
}
Something that is not clear to me from https://bytecodealliance.zulipchat.com/#narrow/stream/217126-wasmtime/topic/component.20Linker.20and.20instantiate/near/376046216 is if a world imports and exports the same interface how are the import names differentiated from the export names in the module before conversion to a component?
Components cannot import and export the same name
(deleted)
Kebab names are required to be unique between all the imports and exports in a single component definition, component type or instance type, so that a single kebab-name uniquely identifies one import or export.
But it's valid to import and export the same interface like https://github.com/bytecodealliance/wit-bindgen/blob/main/tests/codegen/floats.wit isn't it?
These are not kebab names as I understand it
the _initialize logic you see is in wasmtimes core wasm module linker, but support for that is not part of the component model
if you need to call a function to initialize your component, you'll need to add a function for that as an export in your component's world. it will need a kebab-name so you cant call it _initialize, but plain initialize should work
for your component to be able to call into the trait defined by bindgen, you need to use the add_to_linker(&mut wasmtime::component::Linker, ...)
function that is created by bindgen. it will be in the same rust module as the Host trait is defined.
Thanks, for the .Net CoreCLR runtime, that approach would be complicated as the transition from unmanaged to managed code (which occurs on a WIT call to .Net) expects the runtime to already be initialised so it's a bit of a chicken and egg. I can implement something in the runtime to get round this utilizing the fact that webassembly memory content is zero bytes upon creation.
I think I see how wit-bindgen
is doing the import and export of the same interface. It prefixes the exports with exports
resolving the conflicts (if there was a moduled named exports_foo
, that might be a problem I suppose, not sure about the mangling rules:
// Imported Functions from `foo:foo/floats`
void foo_foo_floats_float32_param(float x);
// Exported Functions from `foo:foo/floats`
void exports_foo_foo_floats_float32_param(float x);
Thanks all who commented to my wanderings here, I have it working now so can continue on wit->c#.
I can't close this due to it being > 7 days old, but please can a moderator close it? THanks
Till Schneidereit has marked this topic as resolved.
Last updated: Dec 23 2024 at 13:07 UTC