| # 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. |