trunk icon indicating copy to clipboard operation
trunk copied to clipboard

Using trunk with wasm_thread

Open Shapur1234 opened this issue 1 year ago • 9 comments

I would like to use the wasm_thread library inside of a trunk project, but I was unable to create a functioning test application using the two. I tried to splice together the trunk cdylib example with and example from the wasm_thread repo, but couldn't get it to work.

My latest attempt built successfully, when run, panics called Result::unwrap() on an Err value: JsValue(DataCloneError: WebAssembly. Memory object could not be cloned. I am not very knowledgeable with trunk, but I have successfully used wasm_thread with wasm-pack in the past.

Are there any examples on how to use trunk with the wasm_thread library?

Shapur1234 avatar Jan 20 '24 14:01 Shapur1234

Trunk is "just" a build tool for Rust based WebAssembly applications (for the browser). In general, I think that should work with wasm_thread. I might just be that the way other tools (like wasm_bindgen) get called might differ a bit from what wasm_thread expects.

Maybe people can easier help if there would be a reproducer for this.

ctron avatar Jan 22 '24 08:01 ctron

Thank you for you reply. I've looked into this some more.

To compile wasm_thread, you need to enable some experimental flags and recompile the standard library with atomics enable.

Example from the wasm_thread example:

RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals'
cargo +nightly build --example simple --target wasm32-unknown-unknown --release -Z build-std=std,panic_abort

The problem is, I think the -Z build-std=std,panic_abort part. I haven't been able to pass that argument to trunk (something like trunk serve -- -Z build-std=std,panic_abort doesn't work.

Is it possible to recompile the standard library when building using trunk (passing the -Z build-std=std,panic_abort argument through trunk to cargo)?

P.S.: This repo contains my latest attempts at a minimal example (note: I am also trying to build this using nix crane)

Shapur1234 avatar Jan 24 '24 15:01 Shapur1234

I don't think that's possible … yet!

It might make sense to add that using a data- attribute (data-z?). It's definitely for the "pro" user :)

ctron avatar Jan 25 '24 10:01 ctron

So according to the cargo documentation, you can already do this with cargo alone: https://doc.rust-lang.org/cargo/reference/unstable.html

Anything which can be configured with a -Z flag can also be set in the cargo config file (.cargo/config.toml) in the unstable table. For example:

[unstable]
mtime-on-use = true
build-std = ["core", "alloc"]

So, I am not sure there's anything for trunk to do TBH.

ctron avatar Jan 29 '24 17:01 ctron

Ok, I think I know what the problem is. Trunk creates the bootstrapper for the wasm initialization. And wasm_thread requires a specific entry point to be called:

      wasm_bindgen('./target/simple_bg.wasm').then((wasm) => {
        wasm.run();
      });

That's currently not happening. But I am sure we could add this.

ctron avatar Jan 29 '24 17:01 ctron

I have updated my minimal example test repo.

So according to the cargo documentation, you can already do this with cargo alone: https://doc.rust-lang.org/cargo/reference/unstable.html

I think I tried this before and it didn't work, so I tried it again... and it now works! :face_with_spiral_eyes: (Or rather it compiles)

When I run the test application (using trunk serve): error

Ok, I think I know what the problem is. Trunk creates the bootstrapper for the wasm initialization. And wasm_thread requires a specific entry point to be called:

The error seems to me to be exactly the issue you are describing.

That's currently not happening. But I am sure we could add this.

That would be great. I think being able to run multi-threaded code using "native std::threads" (not just standard webworkers) would be very useful.
(And not just for my use-case of this (a minecraft-like game )

Shapur1234 avatar Jan 29 '24 21:01 Shapur1234

That would be great. I think being able to run multi-threaded code using "native std::threads" (not just standard webworkers) would be very useful.

However, the result will not be that. wasm_thread still uses web workers under the hood. It just tries to hide that fact away from you. And peeking into that code, I am wondering if it wouldn't be cleaner to just just acknowledge that fact and add some small abstraction layer in your application to deal with this. In the end, if it's running in a browser, it will always be single threaded.

ctron avatar Jan 30 '24 07:01 ctron

Ok, digging a bit more into this: It looks like it creates a new worker, and then tries to send over the "memory" of the original WASM module. Which feels kind of weird. And it looks like the browser actually prevents that. Things can be sent to other web workers, but not everything: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm

Sending the memory handle might work, but only when the WebAssembly memory was initialized as "shared": https://developer.mozilla.org/en-US/docs/WebAssembly/Understanding_the_text_format#shared_memories

There's another crate with a similar approach: https://github.com/RReverser/wasm-bindgen-rayon … however, it also requires a nightly compiler from 2022.

It seems to be around the flag of +atomics: https://github.com/rustwasm/wasm-bindgen/issues/2225#issuecomment-1532441512 … but honestly, I've no idea. Unfortunately, I don't have the time to dig deeper. If you have a working setup and some things that would help you get there, let me know and we might be able to add this to trunk.

ctron avatar Jan 30 '24 07:01 ctron

That would be great. I think being able to run multi-threaded code using "native std::threads" (not just standard webworkers) would be very useful.

However, the result will not be that. wasm_thread still uses web workers under the hood. It just tries to hide that fact away from you. And peeking into that code, I am wondering if it wouldn't be cleaner to just just acknowledge that fact and add some small abstraction layer in your application to deal with this. In the end, if it's running in a browser, it will always be single threaded.

For me, the main benefit of wasm_thread over plain webworkers is, that I can use the same code (threaded) for both wasm and native.

If you have a working setup and some things that would help you get there, let me know and we might be able to add this to trunk.

I'll try fiddling with it a bit more, if I get anywhere, I'll share my progress here.

Shapur1234 avatar Jan 30 '24 08:01 Shapur1234

@ctron yeah, this is the main reason I want this.

Indeed its my understanding that workers with something like wasm_thread, same btw with the often recommended wasm-bindgen-rayon use SharedArrayBuffer to actually share memory between threads.

This requires a strictly isolated website, since otherwise relevant apis on the SharedArrayBuffer will be disabled, for this one needs to enable a bunch of Headers MDN.

This should btw also allow another improvement. As far as I understand it, with the approach used in the current webworker-{"", gloo, module} examples, anything that is serialized over the border needs to be known to both modules. This can lead to a near doubling of the size of the website, if a large dependency is needed on both sides, eg. wgpu.

With the approach that wasm_thread etc. take, meanwhile, AFAIK you end up with a single wasm binary, you just use different entry points. So no size increase beyond the stuff that's different between both sides.

So I would identify 2.5 major wins for the wasm_thread approach:

  • code sharing
    • improves dev experience
    • can in the limit up to halve the size of the website, relevant for the typically large wasm downloads.
  • shared memory

For me the shared memory is pretty relevant. The reason I wanted to move stuff to a worker was that I don't block the UI thread. But without shared memory, webworkers probably don't make sense for my usecase. I want to do a markov-chain simulation, thats AFAIK inherently single threaded (maybe theres a clever trick that I could not find, but its by its nature sequential). So I don't win anything from being multithreaded. And with that simulation I'll generate a bunch of data, in AFAIK a large memory/compute ratio. So the serialization and deserialization of the data might up to triple the latency. Then it'll also block the ui thread due to deserialization of a bunch of data. Up to tripling the latency for a small improvement in the blockage of the UI thread isn't worth it.

I was thinking that I might be able to smuggle the data through a webgpu storagebuffer, but even if that survives the serialization over the worker boundary, which I'm not at all certain in, then we still retain the duplication of the size of the website, and also, that approach isn't something that other projects can do just like that. I've already got wgpu and I do already (plan to) put some data in a storage buffer.

9SMTM6 avatar Aug 03 '24 21:08 9SMTM6

I think I've got it working in a simple example. My app doesn't compile because it seems wgpu doesnt like nightly rust. But even for the example there's a bunch of gotchas. I'll make a PR for it.

About the gotchas. I'll list them in the readme. For starters, @ctron, trunk serve doesn't serve files with Cross-Origin isolation as required for SharedArrayBuffer, so it cant be used. That is something you could fix. The headers remain these on MDN. Note however that these headers are not on by default for a reason, they stop you from from requesting resources from other origins and stop your site form being embedded in certain ways, so perhaps it should be behind a flag.

The equivalent also needs to be done when serving the contents in actual deployment, which means that AFAIK Github pages is a no-go. Cloudflare pages should work with some additional work though.

Then it needs a Nightly toolchain.

And then theres additional limitations listed in wasm_treads readme.

9SMTM6 avatar Aug 04 '24 09:08 9SMTM6

@ctron this can be closed, I'd say.

@Shapur1234 example is merged. Works already with up to date trunk, no need to wait for a release.

9SMTM6 avatar Aug 08 '24 18:08 9SMTM6

Awesome! I'll go check it out.

Shapur1234 avatar Aug 08 '24 18:08 Shapur1234

@9SMTM6 many thanks for resolving this!

ctron avatar Aug 09 '24 07:08 ctron