Stream: general

Topic: Building a platform for users to deploy wasm libraries


view this post on Zulip Ari Seyhun (Jan 27 2022 at 18:14):

I'm building a web platform similar to Netlify/Heroku, and I'd like users to write code in Rust/AssemblyScript/... and have my platform run their code (not targeted for web). The issue I'm facing is that wasm lacks support for advanced types like structs, strings, bools, etc.

Would the best solution for now be to make my users write their code with wit-bindgen?
(Their code will be generated with https://github.com/thalo-rs/esdl, and implemented in Rust).

I know the code users write will be very basic functions (if else kind of business logic), and all their functions are basic scalar types (string/int/float/bool) or structs with fields only of these types.

A language binding generator for WebAssembly interface types - GitHub - bytecodealliance/wit-bindgen: A language binding generator for WebAssembly interface types
Event Sourcing Schema Definition Language. Contribute to thalo-rs/esdl development by creating an account on GitHub.

view this post on Zulip Paul Colomiets (Jan 27 2022 at 18:45):

I would go with writing your SDK using wit-bindgen, but handle user's data via serialization. Otherwise you will need generate your host app for each of the user's data type (or work with dynamic types of wasm imports/exports, which is not convenient at all).

view this post on Zulip Ari Seyhun (Jan 28 2022 at 07:45):

I see. I am finding it quite hard to figure out how to use wit-bindgen.
Would the users need to run the cli themselves? The thing about my situation is that my library provides codegen, and they define their traits in a separate schema language.. so I will know what functions are available already.

view this post on Zulip Ari Seyhun (Jan 28 2022 at 08:55):

After trying and looking through Github issues, I have had no luck.
I came across this issue: https://github.com/rustwasm/wasm-bindgen/issues/2471
It seems like the type interface isn't ready and won't be for quite some time sadly

rust version: rustc 1.52.0-nightly (a143517d4 2021-02-16) wasm-bindgen: 0.2.71 wasm-bindgen-cli: 0.2.71 use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn greet(_name: &str) { } With the abov...

view this post on Zulip Paul Colomiets (Jan 28 2022 at 10:19):

It looks like your user will run Rust compiler right?
They don't need CLI then. Instead of using cli you use procedural macro in your SDK or generated code:

wit_bindgen_rust::export("some-wit-file.wit");
wit_bindgen_rust::import("other-wit-file.wit");

And then use types generated. For export to work you will need a new structure and implement a trait. Compiler will guide you through this (by error messages).

You can use CLI to generate another copy of the code just to see what is being generated (or alternatively use cargo expand)

view this post on Zulip Paul Colomiets (Jan 28 2022 at 10:19):

Generally, yes it's a bit rough. And documentation is lacking.

view this post on Zulip Till Schneidereit (Jan 28 2022 at 12:59):

(fwiw, we're very much aware. All of this is evolving rapidly and will be in a much better shape with fewer rough edges and more stability soon(-ish))

view this post on Zulip Ari Seyhun (Jan 28 2022 at 18:59):

Is the wit_bindgen_rust crate published on crates.io? Or could I import it from git directly?

view this post on Zulip Fabrice Desré (Jan 28 2022 at 19:01):

Importing from git works just fine

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:01):

I was struggling with it because it doesnt seem to be in the cargo workspace, and is nested in crates directory

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:02):

Pretty much what I'm trying to do is:

view this post on Zulip Fabrice Desré (Jan 28 2022 at 19:03):

I have that in my Cargo.toml :

[dependencies]
wit-bindgen-rust = {git = "https://github.com/bytecodealliance/wit-bindgen.git"}

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:04):

Can I write rust code and have my wit file generated from the Rust code?

view this post on Zulip Paul Colomiets (Jan 28 2022 at 19:06):

There is https://github.com/bnjjj/witgen but I'm not sure what the state it is.

witgen is a library to generate .wit files for WebAssembly in Rust - GitHub - bnjjj/witgen: witgen is a library to generate .wit files for WebAssembly in Rust

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:07):

Ah okay, sorry I'm still trying to understand the details.
Would the library author of the wasm code use wit_bindgen_rust::export, and my rust code that wants to use their wasm would use wit_bindgen_rust::import?

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:08):

For example, I want to write an add function. I have a wit file:

add: function(input: string) -> string

And then Rust lib.rs file:

use wit_bindgen_rust::import;

import!("./witgen.wit");

pub fn add(input: String) -> String {
    format!("{} there", input)
}

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:09):

But I'm not exactly sure what I'm doing.. if I need to use a proc macro on my add function (eg wasm_bindgen, or if wit_bindgen is a replacement for that..)

view this post on Zulip Paul Colomiets (Jan 28 2022 at 19:15):

This example might explain it:

use wit_bindgen_rust::import;

import!("./witgen.wit");

pub use witgen::add;

fn main() {
    println!("Hello {:?}", add("hello"));
}

Importing is to get this function from the host app. import macro will declare a function in a module that is named after the file. So common case is to rexport it from the module that uses import macro (pub use module_name::func_name;).

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:20):

Ah okay so import is for importing a wit file.. but how should I export?
The export macro seems to expect me to define a type with the same name or something

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:20):

Also, should I be using a specific wasm builder with this?

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:21):

Like wasm-pack, or wasmtime, etc

view this post on Zulip Paul Colomiets (Jan 28 2022 at 19:24):

Yeah, you just make an empty structure, and put function on it's implementation (note no self, just use type as a module):

export!("witgen.wit");
struct Witgen;
impl witgent::Witgen for Witgen {
  fn add(inp: String) -> String {
    format!("{} there", input)
  }
}

view this post on Zulip Paul Colomiets (Jan 28 2022 at 19:25):

Also, should I be using a specific wasm builder with this?

No. No specific builder, just regular cargo build --target=wasm32-wasi (or wasm32-unknown-unknown)

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:30):

Okay so I have made a project.
/app.wit:

add: function(input: string) -> string

/src/lib.rs:

wit_bindgen_rust::export!("./app.wit");

struct App;

impl app::App for App {
    fn add(input: String) -> String {
        format!("{} there", input)
    }
}

/src/main.rs:

use wit_bindgen_rust::import;

import!("./app.wit");

pub use app::add;

fn main() {
    println!("Hello {:?}", add("hello"));
}

Then I build the lib:

$ cargo build --target wasm32-wasi --lib --release

And try to run the app:

$ cargo run

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:30):

But I get this:

error: linking with cc failed: exit status: 1

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:31):

I must be missing something.. I'm not sure where it imports the wasm file from

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:33):

I tried to move the app.wasm file from /target to the root of my project

view this post on Zulip Paul Colomiets (Jan 28 2022 at 19:34):

Oh. well well, wit_bindgen_rust is only for in-wasm part. And you have to write full wasmtime-based runner/host for that. And use wit_bindgen_wasmtime for the host app.

view this post on Zulip Paul Colomiets (Jan 28 2022 at 19:35):

Along the lines of this: https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasi/main.rs

Standalone JIT-style runtime for WebAssembly, using Cranelift - wasmtime/main.rs at main · bytecodealliance/wasmtime

view this post on Zulip Paul Colomiets (Jan 28 2022 at 19:36):

Unless what you're trying it to link two wasm modules, then wasmtime-cli might have a some way to do that, I'm not sure.

view this post on Zulip Ari Seyhun (Jan 28 2022 at 19:40):

Oh cool thank you, I'll have a look!

view this post on Zulip Ari Seyhun (Jan 28 2022 at 20:17):

I've made a Github repo showing what I'm trying and how its not working. If anyone could take a look, it would be really appreciated... I feel like I'm almost there, but maybe missing one last thing.

https://github.com/tqwewe/wasmer-wasi

Issues with wasmer and wasi. Contribute to tqwewe/wasmer-wasi development by creating an account on GitHub.

view this post on Zulip Ari Seyhun (Jan 28 2022 at 20:17):

The README.md shows the commands and my error

view this post on Zulip Paul Colomiets (Jan 29 2022 at 00:13):

https://github.com/tqwewe/wasmer-wasi/blob/main/runner/src/main.rs
Should use wit_bindgen_wasmtime (not wit_bindgen_rust, _rust is for wasm-compiled modules)

Issues with wasmer and wasi. Contribute to tqwewe/wasmer-wasi development by creating an account on GitHub.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 08:10):

Hm I tried it in the runner package main.rs, but I get an error:

pub use domain::add;
//      ^^^^^^^^^^^ no `add` in `domain`

This is just from replacing wit_bindgen_rust with wit_bindgen_wasmtime in Cargo.toml and main.rs.

I thought the domain package (lib) also needs to use wit_bindgen_wasmtime, but when trying to build the wasm I get a bunch of errors:

thread 'main' panicked at 'error when identifying target: "no supported isa found for arch wasm32"', /Users/ari/.cargo/registry/src/github.com-1ecc6299db9ec823/cranelift-codegen-0.80.0/build.rs:42:53

view this post on Zulip Ari Seyhun (Jan 29 2022 at 08:18):

I see.. looks like I need to pub use domain::Domain... and then use Domain::new(...)

view this post on Zulip Ari Seyhun (Jan 29 2022 at 08:19):

I just need to figure out the right arguments to provide for ::new(...)... and caller: impl AsContextMut<Data = T>, for when calling .add()

view this post on Zulip Ari Seyhun (Jan 29 2022 at 08:37):

https://github.com/tqwewe/wasmer-wasi/blob/main/runner/src/main.rs

Issues with wasmer and wasi. Contribute to tqwewe/wasmer-wasi development by creating an account on GitHub.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 08:37):

That works... but I don't know if my approach is correct.
I did:

let mut wasi = WasiCtxBuilder::new()...;
let domain_index = wasi.table().push(Box::new(DomainData::default()))?;

Domain::instantiate(&mut store, &module, &mut linker, move |s| {
    s.table().get_mut::<DomainData>(domain_index).unwrap() // This doesnt seem very right...
})?;

view this post on Zulip Paul Colomiets (Jan 29 2022 at 11:57):

It looks like you've pushed your domain data to the WASI file descriptor table. It will work, but there is a simpler way.

// create your own Data structure
struct Data {
  //wasi structure here
  wasi: wasmtime_wasi:sync::WasiCtx,
  // and any extra data for wasm here, including domain data:
  domain: DomainData,
}
// then instead of passing wasi directly to `Store` use your own Data:
Store::new(&engine, Data { wasi, domain });
// in all linking functions use an attribute
wasmtime_wasi::add_to_linker(&mut linker, |s| &mut s.wasi)
Domain::instantiate(&mut store, &module, &mut linker, |s| &mut s.domain)

In some cases you need to specify type on these lambdas like |s: &mut Data| s.wasi.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 12:06):

Worked perfectly! Thank you so much, this is exactly what I was looking for since a few days ago

view this post on Zulip Ari Seyhun (Jan 29 2022 at 12:07):

So this approach should allow possibly Golang or Python to write the library side of it?

view this post on Zulip Paul Colomiets (Jan 29 2022 at 12:48):

In theory, yes. But I think there is no codegen for them yet. Also in Go I think standard compiler is tied to browser (GOOS=js), although tinygo should work if you write bindings manually. Similarly with Python there are few ways to compile it for wasm including RustPython and Pyodide, I'm not sure what is the state of any of that.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:14):

Is it possible to dynamically call functions with wit? For example, load a wit file and execute a function at runtime of a Rust app?

view this post on Zulip Paul Colomiets (Jan 29 2022 at 15:22):

In theory yes. With current implementation -- no, as far as I understand. Wasmtime allows to call functions dynamically, so you can use the parser of wit and build function arguments manually.

But I'm also not sure what's the point. If you want to user-specified JSON and give it to wasm for processing, just feed that json to wasm directly instead of deserializing it and building wasmtime signature of it.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:39):

I see, I guess the simple approach is json (de)serialize like you suggested.
But in my case, I am building a platform where users can upload/submit a wasm file, but along with the wasm file is a schema file similar to .wit files.
So it would be beneficial for my system to be able to call their functions directly as I know the function signatures available

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:40):

Otherwise the users have to use a standardized function which takes the data as json and they have to deserialize it themselves

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:42):

The only problem with wasmtime dynamic calling, is I don't think it is easy to do with non-number types

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:42):

https://docs.rs/wasmtime/latest/wasmtime/struct.Instance.html#method.get_typed_func

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:43):

Typed func needs params and return types to be WasmTy, which is only implemented for numeric types it seems.. unless there's a different way of doing it

view this post on Zulip Paul Colomiets (Jan 29 2022 at 15:47):

Take a look what wit-bindgen-cli wasmtime your.wit generates. You have to use ints as pointers and read the wasm VM's memory.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:47):

Yeah I was just about to say that

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:47):

(deleted)

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:48):

I guess I'd have to rewrite this code

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:48):

Screen-Shot-2022-01-29-at-10.47.32-pm.png

view this post on Zulip Paul Colomiets (Jan 29 2022 at 15:51):

Generally it looks like you'll be using wit as json-schema. I'm not sure this is a the best idea. JSON schema (or most other validators) are more powerful and rich in types. Although, I like variant types in wit, so it may be good idea.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:52):

I made a schema language https://github.com/thalo-rs/esdl
From ESDL files, it generates Rust code for the user. So given that I know the Rust code from this schema, I could probably some library that can dynamically call based on the schema file.. but it seems like a deep rabbit hole to go down, especially since I'm not very familiar with how wasm works under the hood

Event Sourcing Schema Definition Language. Contribute to thalo-rs/esdl development by creating an account on GitHub.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:54):

I could probably piggy back off the wit_bindgen_wasmtime import macro in some way if I did go down that route

view this post on Zulip Paul Colomiets (Jan 29 2022 at 15:55):

So you probably can generate structures and serde signatures for esdl, and make it transparent for users whether there is serde or wit under the hood?

view this post on Zulip Ari Seyhun (Jan 29 2022 at 15:59):

I'm not sure I understand completely, but my main goal is go keep things as simple as possible for users writing the code which will be compiled to wasm.
https://github.com/thalo-rs/thalo/tree/main/examples/bank-account
I'd like them to be able to just write this basic code, and upload the WASM + ESDL/Wit file to my platform, and my platform can run their commands defined

🚀 Event sourcing suite for Rust 🦀. Contribute to thalo-rs/thalo development by creating an account on GitHub.

view this post on Zulip Paul Colomiets (Jan 29 2022 at 16:05):

Hm. It looks like you're doing exactly that (generating serde models) in generated code :)

view this post on Zulip Ari Seyhun (Jan 29 2022 at 16:06):

Yep, additionally a trait for users to implement

view this post on Zulip Ari Seyhun (Jan 29 2022 at 16:06):

(in the example, it's BankAccountCommand trait)

view this post on Zulip Paul Colomiets (Jan 29 2022 at 16:11):

So I'd use some process_event: function (data: list<u8>) -> list<u8> signature and use the same codegen or a macro to generate necessary ser/de calls. So users don't have to come with their own wit file for each esdl file. This sounds friendlier to users. No?

view this post on Zulip Ari Seyhun (Jan 29 2022 at 16:14):

Yeah if I understand correctly, I started doing something like that. Making a standard wit file they use:

apply: function(event: string) -> string
handle: function(command: string) -> string

And all the wasm files use this wit file. And the users code would have some kind of macro or helper to define these methods which kind of wires things up from within the wasm module itself

view this post on Zulip Ari Seyhun (Jan 29 2022 at 16:14):

Going with that approach seems to make the most sense

view this post on Zulip Ari Seyhun (Jan 29 2022 at 19:52):

Is _ valid in wit files?
I was looking here: https://github.com/WebAssembly/interface-types/blob/main/proposals/interface-types/Explainer.md#interface-types
And I see for expected:

(expected <intertype>? (error <intertype>)?)

And tried apply: function(event: list<u8>) -> expected<_, s64> in the online playground and it seemed to work

Contribute to WebAssembly/interface-types development by creating an account on GitHub.

view this post on Zulip Ari Seyhun (Jan 29 2022 at 19:55):

Note the first generic of expected is _ in expected<_, s64>

view this post on Zulip Paul Colomiets (Jan 29 2022 at 20:41):

Yeah, looks like it stands for rustish (). I think it doesn't work in any other case except in expected though.

view this post on Zulip Ari Seyhun (Jan 30 2022 at 10:16):

I think I got a nice generic approach which should work with many different implementations.

// domain.wit

// Errors which can occur
variant error {
  command(list<u8>),
  deserialize-state,
  deserialize-event,
  deserialize-command,
  serialize-state,
}

// Creates a new instance
// Returns state
new-instance: function(id: string) -> expected<list<u8>, error>

// Takes state, event
// Returns new state
apply-event: function(state: list<u8>, event: list<u8>) -> expected<list<u8>, error>

// Takes state, command
// Returns list of events
handle-command: function(state: list<u8>, command: list<u8>) -> expected<list<list<u8>>, error>

view this post on Zulip Ari Seyhun (Jan 30 2022 at 10:17):

I wrote a bunch of magic in the thalo::include_aggregate!("BankAccount"); macro, and it makes that bank example code I sent above just work as is (just need to add the domain.wit file to the project

view this post on Zulip Ari Seyhun (Jan 30 2022 at 10:30):


Regarding the struct which implements the trait from wit_bindgen_rust::export!("./domain.wit");... I cannot store anything there in wasm?
As in, none of the wit methods take self, so I cannot maintain state there.

wit_bindgen_rust::export!("./domain.wit");

struct Domain { ... }; // the state inside cannot be used from my wasm module?

impl domain::Domain for Domain {
    fn my_function(foo: String) { ... } // Doesn't take self... cannot use any of the state?
}

view this post on Zulip Ari Seyhun (Jan 30 2022 at 11:09):

I put my function calls in a loop 100,000 times and timed it.
From Rust, calling wasm functions I got: ~920ms average

When I call the functions in pure Rust without compiling to wasm, I get: ~140ms average

view this post on Zulip Ari Seyhun (Jan 30 2022 at 11:10):

Is that just the performance of wasm? Can I expect that to improve with time?

view this post on Zulip Paul Colomiets (Jan 30 2022 at 22:49):

You can use resource to keep state: https://github.com/bytecodealliance/wit-bindgen/blob/main/WIT.md#item-resource (or just use global/static variables in Rust).

Regarding performance. I can't comment on specific numbers. But generally yes, some overhead is expected. You can probably send events in batches to make the overhead smaller.

A language binding generator for WebAssembly interface types - wit-bindgen/WIT.md at main · bytecodealliance/wit-bindgen

view this post on Zulip Ari Seyhun (Nov 10 2022 at 06:30):

Are resources still a thing in the wit spec?
I'm trying to use it on the wit playground <https://bytecodealliance.github.io/wit-bindgen/>... but it doesn't parse the syntax correctly.

view this post on Zulip Ramon Klass (Nov 10 2022 at 12:48):

I don't know when the playground was last deployed or if it even uses git version of wit but at this point wit is so much in fluctuation that I would always test locally

view this post on Zulip Ari Seyhun (Nov 10 2022 at 13:52):

Ramon Klass said:

I don't know when the playground was last deployed or if it even uses git version of wit but at this point wit is so much in fluctuation that I would always test locally

I also tested it locally and sadly got the same results. I was even using the dependency from git main branch

view this post on Zulip Lann Martin (Nov 10 2022 at 13:58):

Resources remain part of the WIT spec but have been (temporarily) removed from implementations to ease transition to the component model. ref: https://github.com/bytecodealliance/wit-bindgen/pull/346

This commit removes all support for the resource and Handle types from the AST of wit-parser and all related support in all code generators. The motivation for this commit is that wit-bindgen is on...

view this post on Zulip Alex Crichton (Nov 10 2022 at 15:19):

Resources are not currently part of the component model but the plan is that they're the next feature to add

view this post on Zulip Ari Seyhun (Nov 24 2022 at 03:35):

Is there anywhere I can follow updates about resources support?

view this post on Zulip Bailey Hayes (Dec 07 2022 at 01:33):

https://github.com/WebAssembly/component-model/pull/129

(I'm publishing in draft state in case anyone wants to look at it before all the TODOs are filled in. I think the essential additions in the AST and binary format are roughly right, I'm just filli...

Last updated: Dec 23 2024 at 12:05 UTC