blob: 522a2f70b963faca9335b1712556c7eeafb28d3c [file] [log] [blame] [view]
# Parallel Raytracing
[View full source code][code] or [view the compiled example online][online]
[online]: https://wasm-bindgen.netlify.app/exbuild/raytrace-parallel/
[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/raytrace-parallel
This is an example of using threads with WebAssembly, Rust, and `wasm-bindgen`,
culminating in a parallel raytracer demo. There's a number of moving pieces to
this demo and it's unfortunately not the easiest thing to wrangle, but it's
hoped that this'll give you a bit of a taste of what it's like to use threads
and wasm with Rust on the web.
### Building the demo
One of the major gotchas with threaded WebAssembly is that Rust does not ship a
precompiled target (e.g. standard library) which has threading support enabled.
This means that you'll need to recompile the standard library with the
appropriate rustc flags, namely
`-C target-feature=+atomics,+bulk-memory,+mutable-globals`.
Note that this requires a nightly Rust toolchain.
To do this you can use the `RUSTFLAGS` environment variable that Cargo reads:
```sh
export RUSTFLAGS='-C target-feature=+atomics,+bulk-memory,+mutable-globals'
```
To recompile the standard library it's recommended to use Cargo's
[`-Zbuild-std`](https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#build-std)
feature:
```sh
cargo build --target wasm32-unknown-unknown -Z build-std=panic_abort,std
```
Note that you can also configure this via `.cargo/config.toml`:
```toml
[unstable]
build-std = ['std', 'panic_abort']
[build]
target = "wasm32-unknown-unknown"
rustflags = '-Ctarget-feature=+atomics,+bulk-memory,+mutable-globals'
```
After this `cargo build` should produce a WebAssembly file with threading
enabled, and the standard library will be appropriately compiled as well.
The final step in this is to run `wasm-bindgen` as usual, and `wasm-bindgen`
needs no extra configuration to work with threads. You can continue to run it
through `wasm-pack`, for example.
### Running the demo
Currently it's required to use the `--target no-modules` or `--target web` flag
with `wasm-bindgen` to run threaded code. This is because the WebAssembly file
imports memory instead of exporting it, so we need to hook initialization of the
wasm module at this time to provide the appropriate memory object. This demo
uses `--target no-modules`, because Firefox does not support modules in workers.
With `--target no-modules` you'll be able to use `importScripts` inside of each
web worker to import the shim JS generated by `wasm-bindgen` as well as calling
the `wasm_bindgen` initialization function with the shared memory instance from
the main thread. The expected usage is that WebAssembly on the main thread will
post its memory object to all other threads to get instantiated with.
### Caveats
Unfortunately at this time running wasm on the web with threads has a number of
caveats, although some are specific to just `wasm-bindgen`. These are some
pieces to consider and watch out for, although we're always looking for
improvements to be made so if you have an idea please file an issue!
* The main thread in a browser cannot block. This means that if you run
WebAssembly code on the main thread you can *never* block, meaning you can't
do so much as acquire a mutex. This is an extremely difficult limitation to
work with on the web, although one workaround is to run wasm exclusively in
web workers and run JS on the main thread. It is possible to run the same wasm
across all threads, but you need to be extremely vigilant about
synchronization with the main thread.
* Setting up a threaded environment is a bit wonky and doesn't feel smooth
today. For example `--target bundler` is unsupported and very specific shims
are required on both the main thread and worker threads. These are possible to
work with but are somewhat brittle since there's no standard way to spin up
web workers as wasm threads.
* There is no standard notion of a "thread". For example the standard library
has no viable route to implement the `std::thread` module. As a consequence
there is no concept of thread exit and TLS destructors will never run.
We do expose a helper, `__wbindgen_thread_destroy`, that deallocates
the thread stack and TLS. If you invoke it, it *must* be the last function
you invoke from the wasm module for a given thread.
* Any thread launched after the first one _might attempt to block_ implicitly
in its initialization routine. This is a constraint introduced by the way
we set up the space for thread stacks and TLS. This means that if you attempt
to run a wasm module in the main thread _after_ you are already running it
in a worker, it might fail.
* Web Workers executing WebAssembly code cannot receive events from JS. A Web
Worker has to fully return back to the browser (and ideally should do so
occasionally) to receive JS messages and such. This means that common
paradigms like a rayon thread pool do not apply straightforward-ly to the web.
The intention of the web is that all long-term blocking happens in the browser
itself, not in each thread, but many crates in the ecosystem leveraging
threading are not necessarily engineered this way.
These caveats are all largely inherited from the web platform itself, and
they're important to consider when designing an application for threading. It's
highly unlikely that you can pull a crate off the shelf and "just use it" due to
these limitations. You'll need to be sure to carefully plan ahead and ensure
that gotchas such as these don't cause issues in the future. As mentioned before
though we're always trying to actively develop this support so if folks have
ideas about how to improve, or if web standards change, we'll try to update this
documentation!
### Browser Requirements
This demo should work in the latest Firefox and Chrome versions at this time,
and other browsers are likely to follow suit. Note that threads and
`SharedArrayBuffer` require HTTP headers to be set to work correctly. For more
information see the [documentation on
MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer)
under "Security requirements" as well as [Firefox's rollout blog
post](https://hacks.mozilla.org/2020/07/safely-reviving-shared-memory/). This
means that during local development you'll need to configure your web server
appropriately or enable a workaround in your browser.