Hi there,
I've been trying to write a data transformer function which takes string as input and outputs a f32 in a wasm.
Also, I want to use wasmtime crate to have this wasm function run inside a wasmtime module/sandbox.
I've been using wit-bindgen to generate the necessary bindings and export a function parse_cost() and parse_unit(),
mod bindings;
use bindings::exports::component::parser::parse::Guest;
struct Component;
impl Guest for Component {
fn parse_cost(cost: String) -> f32 {
cost.parse().unwrap_or(0.0)
}
fn parse_unit(unit: String) -> f32 {
unit.parse().unwrap_or(0.0)
}
}
bindings::export!(Component with_types_in bindings);
however, when using get_typed_func(), the compile complained about required for
std::string::String to implement
WasmParams``
fn parse_cost(&self, func_id: &str, cost: &str) -> Result<f32, String> {
let guard = self.modules.lock().unwrap();
let module = guard.get(func_id).ok_or("Module not found")?;
let mut store = Store::new(&self.engine, ());
let instance = Instance::new(&mut store, module, &[]).unwrap();
let parse_cost = instance.get_typed_func::<(String), f32>(&mut store, "parse_cost").unwrap();
let result = parse_cost.call(&mut store, cost.to_string()).map_err(|e| e.to_string())?;
Ok(result)
}
Before this, I figured out a way to manually allocate memory and convert string to {ptr, len}, it worked but it was not perfect, I was hoping wit-bindgen could help me avoid this manual efforts, now I wonder if it is possible?
thanks in advance.
Try changing (String)
to (String,)
in the host code.
Also, consider using the wasmtime::component::bindgen
macro, which will make the host side easier.
@Joel Dice thanks for the suggestion, seems not work, either.
| -------------- ^^^^^^^^^ the trait `WasmParams` is not implemented for `(std::string::String,)`
| |
| required by a bound introduced by this call
Oops, I see -- you're using the Wasmtime module API rather than the component API. You'll need to convert your module into a component using e.g. wasm-tools component new
or else use cargo-component
to build it. Then, in the host, you'll need to use the wasmtime::component
api to instantiate the component.
This might be helpful: https://component-model.bytecodealliance.org/
@Joel Dice
Hmm, still confused as I'm new to wasm.
so what I am doing is to write a grpc server and in this grpc server process, host a few wasm functions by wasm sandboxes, each function in one wasmtime sandbox, would wasmtime::component be able to support the sandbox?
Yes, components are run with the same isolation guarantees as modules are.
Just to confirm, is wasmtime::component::Linker
what you referred to?
fn parse_cost(&self, func_id: &str, cost: &str) -> Result<f32, String> {
let guard = self.modules.lock().unwrap();
let component = guard.get(func_id).ok_or("Module not found")?;
let mut linker = Linker::new(&self.engine);
let instance = linker.instantiate(&mut store, &component).map_err(|e| e.to_string())?;
let func = instance.get_func(&mut store, "parse_cost").ok_or("Function not found")?;
let mut result = [Val::F32(0.0)];
func.call(&mut store, &[Val::String(cost.to_string()), &mut result)?;
Ok(result[0])
}
Yes, but keep in mind you'll need to give it a Wasm component -- not a module -- or it will fail at runtime.
Also, you should be able to use e.g. https://docs.rs/wasmtime/23.0.1/wasmtime/component/struct.Instance.html#method.get_typed_func with (String,)
as the parameter type.
Or better yet, https://docs.rs/wasmtime/23.0.1/wasmtime/component/macro.bindgen.html
for wasm component, I've created a .wit file , and use cargo component build --target wasm32-wasip1 --release
, I assume it will give me a component back, right?
package component:parser;
interface parse {
parse-cost: func(cost: string) -> f32;
parse-unit: func(unit: string) -> f32;
}
world parser {
export parse;
}
I think so, yes. I haven't used cargo component
for a while, but that's what it's made for.
I'm guessing you don't need to specify --target
when using cargo component
Joel Dice said:
Or better yet, https://docs.rs/wasmtime/23.0.1/wasmtime/component/macro.bindgen.html
by looking at its example, I'm confused where are the definition of exported functions
Joel Dice said:
I'm guessing you don't need to specify
--target
when usingcargo component
Yes, that's true
You can use e.g. cargo-expand
to see the code generated by that macro. This example might be helpful, also: https://docs.rs/wasmtime/23.0.1/wasmtime/component/bindgen_examples/_5_all_world_export_kinds/index.html
Joel Dice said:
Also, you should be able
Does (String,)
refer to (Val::String
,)?
no, (std::string::String,)
Val
is only used for the dynamic (i.e. slower, not-statically-type-checked) API
you should use get_typed_func
if you know the types at compile time
and that's what wasmtime::component::bindgen
uses in its generated code
I guess for result, (f32,) is also needed?
error[E0277]: the trait bound `f32: ComponentNamedList` is not satisfied
--> src/server.rs:36:57
|
36 | let func = instance.get_typed_func::<(String,), f32>(&mut store, "parse_cost").map_err(|e| e.to_string())?;
| -------------- ^^^ the trait `ComponentNamedList` is not implemented for `f32`
Maybe so -- it's been a while since I used that API directly instead of using the bindgen
macro
ok, let me also try bindgen macro directly
Joel Dice said:
You can use e.g.
cargo-expand
to see the code generated by that macro. This example might be helpful, also: https://docs.rs/wasmtime/23.0.1/wasmtime/component/bindgen_examples/_5_all_world_export_kinds/index.html
quick question, if one wants to implement bytes-to-string
differently, like
format!("hello {}", bytes)
where should we put the customization?
You're refering to the code in the example? Are you wanting to modify the example for some reason? Or are you trying to translate what the example does to your use case and WIT world? I'm not quite understanding the question.
sure, I want to have a better understanding of bindgen!(), by using cargo expand, I did see the function definition,
let callee = unsafe {
wasmtime::component::TypedFunc::<
(u64,),
(wasmtime::component::__internal::String,),
>::new_unchecked(self.bytes_to_string)
but it's not clear to me where is bytes_to_string defined,
IOW, if we want to use a different implementation for bytes_to_string() in the 5_all_world_export example, can you please suggest how to do it?
The bytes_to_string
function is implemented in the component in that example. The host is calling a function that the component exports.
Thank you!
I see, it's using function from
let component = Component::from_file(&engine, "./your-component.wasm")?;
where bytes-to-string
is defined.
somehow I've been missing this line over and over again.
Hello,
Following this thread, I was wondering if wit can support references, say if I want to pass a json vec, besides in world.wit I can use list<u8>, which is interpreted into Vec<u8> after bindgen, is it possible to have &[u8] which holds a pointer from host?
package component:parser;
world parse {
export parse: func(payload: list<u8>) -> list<u8>;
}
Nope, the component needs to have the list copied into its own linear memory- wasm can’t express a pointer into something that’s not a memory, and components need to instantiate their own memory, not import it from the host, to make sure they can be composed with other components
Pat Hickey said:
Nope, the component needs to have the list copied into its own linear memory- wasm can’t express a pointer into something that’s not a memory, and components need to instantiate their own memory, not import it from the host, to make sure they can be composed with other components
Thanks!
So in this case can you please suggest what is the idiomatic way to represent binary bytes of json in wit?
json has no notion of bytes or binary, if you want to transport serialized json, you'd probably use string, if you have something that contains an u8[] buffer then json is simply not able to handle this. If it has to be json, people use base64, but I would suggest something like messagepack that can serialize arrays of bytes directly
sounds good, I'm going to explore more with base64
json is a string so you can just use the string type, you dont have to use base64
what I meant is: if your data contains a Uint8Array, you can't toJson it, but if that's not the case use string :)
Ramon Klass said:
what I meant is: if your data contains a Uint8Array, you can't toJson it, but if that's not the case use string :)
Right, I want to reduce json size during the transportation, and I'm using a https://docs.rs/serde_json/1.0.122/serde_json/fn.to_vec.html# instead of to_string(), so on wasm guest, I'll have to use list<u8> to serve json's Vec<u8>
the size during transportation wont be changed by using to_vec instead of to_string, the underlying representation is the same, string is just asserting with the rust type system that the vec contents are utf-8 encoded, but json guarantees that anyway
I see, thank you for the clarification.
again, several binary formats would be smaller if payload size if a concern
Hi, for the same wit file, I'm playing with python wasm support, however, it kept reporting TypeError: Can't instantiate abstract class Parse without an implementation for abstract method 'parse'
while I did follow https://component-model.bytecodealliance.org/language-support/python.html
# cat wit/world.wit
package component:parser;
world parse {
export parse: func(payload: list<u8>) -> list<u8>;
}
# cat src/lib.py
import parser
class Parse(parser.Parse):
def parse(self, payload: bytes) -> bytes:
# todo: implement parsing logic
return payload
# /mnt/wasm/path/to/venv/bin/componentize-py -d ./wit/world.wit -w parse componentize -p ./src parse -o parser.wasm
Traceback (most recent call last):
File "/mnt/wasm/path/to/venv/bin/componentize-py", line 8, in <module>
sys.exit(script())
^^^^^^^^
AssertionError:
Caused by:
TypeError: Can't instantiate abstract class Parse without an implementation for abstract method 'parse'
I checked the bindings output, looks like I've used the exactly same class and function name, not sure where went wrong.
# /mnt/wasm/path/to/venv/bin/componentize-py -d ./wit/world.wit -w parse bindings tmp
root@a23ad5413a51:/mnt/wasm/parser_py# cat tmp/parse/
__init__.py types.py
root@a23ad5413a51:/mnt/wasm/parser_py# cat tmp/parse/__init__.py
from typing import TypeVar, Generic, Union, Optional, Protocol, Tuple, List, Any, Self
from enum import Flag, Enum, auto
from dataclasses import dataclass
from abc import abstractmethod
import weakref
from .types import Result, Ok, Err, Some
class Parse(Protocol):
@abstractmethod
def parse(self, payload: bytes) -> bytes:
raise NotImplementedError
Did I miss anything?
It looks like you told componentize-py
the module name is "parse", but the file name is lib.py
. Can you try changing that to parse.py
?
actually, nevermind; try changing the module name you give componentize-py
to lib
instead
There might be a conflict if the world name and the implementation module name are the same
e.g. componentize-py -d ./wit/world.wit -w parse componentize -p ./src lib -o parser.wasm
Thanks! oh, right, now I got a different err,
/mnt/wasm/path/to/venv/bin/componentize-py -d ./wit/world.wit -w parse componentize -p ./src -o parser.wasm lib
Traceback (most recent call last):
File "/mnt/wasm/path/to/venv/bin/componentize-py", line 8, in <module>
sys.exit(script())
^^^^^^^^
AssertionError: Traceback (most recent call last):
File "/0/lib.py", line 1, in <module>
import parser
ModuleNotFoundError: No module named 'parser'
Caused by:
ModuleNotFoundError: No module named 'parser'
hmm, I don't see lib
in your command
oops, there it is at the end
at the very end, after -o parser.wasm
;)
what is the import parser
line for? It doesn't look like you're using it.
ugh, I need to read better before I type; I see you're using the type parser.Parse
right, trying to get parser.Parse, which is the def from world.wit
shouldn't it be parse
, not parser
?
hmm, maybe.
I thought it was named after package component:parser;
part
no, just the world name
I see, now I got
y# /mnt/wasm/path/to/venv/bin/componentize-py -d ./wit/world.wit -w parse componentize -p ./src -o parser.wasm lib
Component built successfully
hooray
Thank you so much!
Till Schneidereit has marked this topic as resolved.
Last updated: Dec 23 2024 at 13:07 UTC