Browser wasm: feature-detected module variants (relaxed-SIMD) + variant-aware runtime.js #24

Closed
opened 2026-06-15 16:43:19 +02:00 by jorijnvdgraaf · 1 comment

Summary

The browser wasm pipeline produces a single .wasm compiled with a fixed ISA feature set, and EnableWasiBrowserRuntime bakes one wasm URL into the generated index.html. To adopt newer codegen features that aren't yet baseline across browsers — relaxed SIMD today, others later — without dropping the browsers that lack them, Crafter.Build should be able to:

  1. build N variants of the same wasm target with different -m flags, and
  2. emit a runtime.js that feature-detects the browser and fetches the supported variant.

Why this belongs in Crafter.Build

Both halves are owned here, not by downstream projects (this surfaced from Crafter.Graphics' WebGPU/DOM backend, which can't influence either):

  • Flags are hardcoded for every wasm32 targetimplementations/Crafter.Build-Clang.cpp:496:

    -fno-exceptions -msimd128 -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL -Wno-unused-command-line-argument
    

    -msimd128 is unconditional, so the emitted module hard-requires fixed-width SIMD and there is no way to produce a relaxed-SIMD variant or a fallback.

  • A single wasm URL is templated into the HTMLimplementations/Crafter.Build-Clang.cpp:1257:

    html = std::regex_replace(html, std::regex(R"(\{\{WASM\}\})"), cfg.outputName + ".wasm");
    

    The shipped wasi-runtime/runtime.js fetches/instantiates that one URL; there is no notion of alternative variants.

Current motivation: relaxed SIMD

  • Chrome 114+ and Firefox 120+ enable relaxed SIMD by default; Safari 18.4+ still gates it behind a flag (not baseline as of mid-2026). So shipping a single -mrelaxed-simd module would break Safari, while staying on -msimd128 leaves FMA / dot-product throughput on the table for the other ~2/3 of browsers.
  • The mechanism should be general — the same plumbing serves any future "supported-in-some-engines" codegen axis (threads, future SIMD revisions, exnref EH, …). Build it once, parameterized on the feature, not special-cased to relaxed SIMD.

Proposed shape (for discussion, not prescriptive)

  1. The wasm compile/link path emits one .wasm per variant, named outputName.<label>.wasm.
  2. EnableWasiBrowserRuntime emits a small manifest (label → URL + required probes) instead of / alongside the single {{WASM}} substitution.
  3. The shipped wasi-runtime/runtime.js runs the probes at startup (tiny compile-test modules à la GoogleChromeLabs/wasm-feature-detect — each is ~a dozen bytes, no external dependency needed), picks the first variant whose probes all pass (else baseline), then runs the existing instantiateStreaming against the chosen URL.
## Summary The browser wasm pipeline produces a **single `.wasm`** compiled with a fixed ISA feature set, and `EnableWasiBrowserRuntime` bakes **one** wasm URL into the generated `index.html`. To adopt newer codegen features that aren't yet baseline across browsers — relaxed SIMD today, others later — *without* dropping the browsers that lack them, Crafter.Build should be able to: 1. build N variants of the same wasm target with different `-m` flags, and 2. emit a `runtime.js` that feature-detects the browser and fetches the supported variant. ## Why this belongs in Crafter.Build Both halves are owned here, not by downstream projects (this surfaced from Crafter.Graphics' WebGPU/DOM backend, which can't influence either): - **Flags are hardcoded for every wasm32 target** — `implementations/Crafter.Build-Clang.cpp:496`: ``` -fno-exceptions -msimd128 -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL -Wno-unused-command-line-argument ``` `-msimd128` is unconditional, so the emitted module hard-requires fixed-width SIMD and there is no way to produce a relaxed-SIMD variant or a fallback. - **A single wasm URL is templated into the HTML** — `implementations/Crafter.Build-Clang.cpp:1257`: ```cpp html = std::regex_replace(html, std::regex(R"(\{\{WASM\}\})"), cfg.outputName + ".wasm"); ``` The shipped `wasi-runtime/runtime.js` fetches/instantiates that one URL; there is no notion of alternative variants. ## Current motivation: relaxed SIMD - Chrome 114+ and Firefox 120+ enable relaxed SIMD **by default**; Safari 18.4+ still gates it **behind a flag** (not baseline as of mid-2026). So shipping a single `-mrelaxed-simd` module would break Safari, while staying on `-msimd128` leaves FMA / dot-product throughput on the table for the other ~2/3 of browsers. - The mechanism should be **general** — the same plumbing serves any future "supported-in-some-engines" codegen axis (threads, future SIMD revisions, exnref EH, …). Build it once, parameterized on the feature, not special-cased to relaxed SIMD. ## Proposed shape (for discussion, not prescriptive) 1. The wasm compile/link path emits one `.wasm` per variant, named `outputName.<label>.wasm`. 2. `EnableWasiBrowserRuntime` emits a small manifest (label → URL + required probes) instead of / alongside the single `{{WASM}}` substitution. 3. The shipped `wasi-runtime/runtime.js` runs the probes at startup (tiny compile-test modules à la GoogleChromeLabs/wasm-feature-detect — each is ~a dozen bytes, no external dependency needed), picks the first variant whose probes all pass (else baseline), then runs the existing `instantiateStreaming` against the chosen URL.
Member

Claude exited with code 3.

There's an issue with the selected model (claude-fable-5). It may not exist or you may not have access to it. Run --model to pick a different model.
(log unavailable)
Claude exited with code 3. ``` There's an issue with the selected model (claude-fable-5). It may not exist or you may not have access to it. Run --model to pick a different model. (log unavailable) ```
catbot 2026-06-15 17:15:08 +02:00
Sign in to join this conversation.
No description provided.