wasm-bindgen-rayon

repository·main·Indexed 43 days ago

https://github.com/rreverser/wasm-bindgen-rayon

An adapter for using Rayon-based concurrency in WebAssembly by leveraging Web Workers and SharedArrayBuffer. It enables the use of Rayon's parallel iterators (e.g., par_iter()) within #[wasm_bindgen] functions, providing a mechanism to initialize a thread pool via an async initThreadPool function in JavaScript.

Tokens
2.5K
Snippets
8
Records
10
Agent score
56%

What's inside wasm-bindgen-rayon

  1. Initialize the thread pool in JavaScript

    main

    After adding the dependency, you must re-export init_thread_pool from your Rust code. This exposes an async initThreadPool function in your generated JavaScript.

    You must call initThreadPool on the main thread immediately after the standard wasm-bindgen initialization and before calling any functions that require concurrency.

    // In your Rust code
    pub use wasm_bindgen_rayon::init_thread_pool;
    import init, { initThreadPool /* ... */ } from './pkg/index.js';
    
    // Regular wasm-bindgen initialization.
    await init();
    
    // Thread pool initialization with the given number of threads
    // (pass `navigator.hardwareConcurrency` if you want to use all cores).
    await initThreadPool(navigator.hardwareConcurrency);
    
    // ...now you can invoke any exported functions as you normally would
  2. Implement feature detection for Wasm threads

    main

    To support older browsers that do not support WebAssembly threads, you should provide two builds (one with threads and one without) and use wasm-feature-detect to select the correct one at runtime.

    import { threads } from 'wasm-feature-detect';
    
    let wasmPkg;
    
    if (await threads()) {
      wasmPkg = await import('./pkg-with-threads/index.js');
      await wasmPkg.default();
      await wasmPkg.initThreadPool(navigator.hardwareConcurrency);
    } else {
      wasmPkg = await import('./pkg-without-threads/index.js');
      await wasmPkg.default();
    }
    
    wasmPkg.nowCallAnyExportedFuncs();
  3. Use wasm-bindgen-rayon with various bundlers

    main

    The library uses a syntax for Web Workers that is compatible with most modern bundlers:

    • Webpack: Works out of the box with v5 (>= 5.25.1). You may see circular dependency warnings; these are safe to ignore.
    • Parcel: Works out of the box with v2.
    • Rollup / Vite: Vite is recommended. For raw Rollup, use @surma/rollup-plugin-off-main-thread and @web/rollup-plugin-import-meta-assets.
    • No Bundler: If you are not using a bundler, you must enable the no-bundler feature in Cargo.toml to handle import paths correctly.
    wasm-bindgen-rayon = { version = "1.2", features = ["no-bundler"] }
  4. Build Rust code with thread support

    main

    Building for WebAssembly with threads requires the nightly toolchain and the --target web flag for wasm-pack. This is because the standard library must be rebuilt with atomics support using build-std.

    1. Create a rust-toolchain.toml to pin a nightly version:
    [toolchain]
    channel = "nightly-2025-11-15"
    components = ["rust-src"]
    targets = ["wasm32-unknown-unknown"]
    1. Create a .cargo/config.toml to provide the necessary rustflags and enable build-std:
    [target.wasm32-unknown-unknown]
    rustflags = [
      "-C", "target-feature=+atomics,+bulk-memory",
      "-C", "link-arg=--shared-memory",
      "-C", "link-arg=--max-memory=1073741824",
      "-C", "link-arg=--import-memory",
      "-C", "link-arg=--export=__wasm_init_tls",
      "-C", "link-arg=--export=__tls_size",
      "-C", "link-arg=--export=__tls_align",
      "-C", "link-arg=--export=__tls_base"
    ]
    
    [unstable]
    build-std = ["panic_abort", "std"]
    1. Build using wasm-pack:
    wasm-pack build --target web

    Option 2: Using command-line parameters

    You can pass all flags directly in a single command:

    RUSTFLAGS='-C target-feature=+atomics,+bulk-memory\n    -Clink-arg=--shared-memory -Clink-arg=--max-memory=1073741824 -Clink-arg=--import-memory\n    -Clink-arg=--export=__wasm_init_tls -Clink-arg=--export=__tls_size\n    -Clink-arg=--export=__tls_align -Clink-arg=--export=__tls_base' \
      rustup run nightly-2025-11-15 \
      wasm-pack build --target web [...] \
      -- -Z build-std=panic_abort,std
  5. How the thread pool initialization works

    main

    The initialization process follows a specific lifecycle to bridge Rust's Rayon with Web Workers:

    1. JS Call: The user calls initThreadPool(num_threads) from JavaScript.
    2. Worker Spawning: The underlying start_workers helper (provided by the library's JS glue code) spawns the requested number of Web Workers.
    3. Pool Construction: A wbg_rayon_PoolBuilder is created in Rust to manage the communication between the main thread and the new workers.
    4. Handshake: Once the workers are spawned, the build() method is called on the builder. This sets up a spawn_handler that sends ThreadBuilder objects through a channel to the workers.
    5. Worker Execution: Each worker calls wbg_rayon_start_worker (internally) to receive its ThreadBuilder and start Rayon's internal event loop.
  6. Use Rayon iterators in Rust

    main

    Once initialized, you can use Rayon's parallel iterators (e.g., par_iter()) inside your #[wasm_bindgen] functions. These functions will automatically utilize the initialized thread pool to perform computations across multiple Web Workers.

    #[wasm_bindgen]
    pub fn sum(numbers: &[i32]) -> i32 {
        numbers.par_iter().sum()
    }
  7. Start Web Workers with startWorkers()

    main

    The startWorkers function is used to initialize and spawn the pool of Web Workers required for Rayon-based concurrency. It automates the process of creating workers, passing the WebAssembly module and memory to them, and waiting for them to signal they are ready.

    To use it, you must provide:

    1. module: The compiled WebAssembly module.
    2. memory: The WebAssembly.Memory instance (which must be shared via SharedArrayBuffer).
    3. builder: A builder object provided by the wasm-bindgen-rayon glue code that provides numThreads(), receiver(), and build() methods.

    Internally, it spawns workers using new Worker() with the wasm_bindgen_worker name to ensure message type isolation.

    // Note: This is an internal helper typically used by the generated glue code.
    // The parameters module, memory, and builder are provided by the wasm-bindgen-rayon runtime.
    await startWorkers(module, memory, builder);
  8. Initialize the Rayon thread pool via `initThreadPool`

    main

    The primary way to enable Rayon concurrency in your WebAssembly application is to call initThreadPool from JavaScript. This function handles the spawning of Web Workers and the initialization of the shared memory required for Rayon to function.

    Parameters:

    • num_threads (number): The number of worker threads to spawn.

    Returns:

    • A JavaScript Promise that resolves once the thread pool is successfully initialized.

    Note: While you can technically call this from Rust using wasm-bindgen-futures, it is recommended to call it from JavaScript to avoid unnecessary Wasm size overhead and complexity.

    // Example usage in JavaScript
    import init, { initThreadPool } from './pkg/your_wasm_package.js';
    
    async function run() {
      await init();
      // Initialize a pool with 4 threads
      await initThreadPool(4);
      console.log('Rayon thread pool is ready!');
    }
    
    run();
  9. Initialize workers in a bundlerless ES module environment

    main

    In environments without a bundler, you can use the startWorkers function to spawn and initialize the Web Workers required for Rayon concurrency. This function handles fetching the current script as a blob to avoid cross-origin security errors and ensures workers are correctly named as wasm_bindgen_worker so they can self-initialize.

    To use this, you must provide a module, the memory object, and a builder object that implements the following interface:

    • builder.numThreads(): Returns the number of workers to spawn.
    • builder.receiver(): Returns the receiver for the worker communication.
    • builder.mainJS(): Returns the path/URL to the main JavaScript module.
    • builder.build(): A callback to execute once all workers are initialized.
    import { startWorkers } from './workerHelpers.no-bundler.js';
    
    // Example usage pattern
    await startWorkers(
      wasm_module,
      wasm_memory,
      {
        numThreads: () => 4,
        receiver: () => myReceiver,
        mainJS: () => './my_wasm_pkg.js',
        build: () => console.log('Workers ready!')
      }
    );