feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
Some checks failed
CI / build-test-release (pull_request) Failing after 7m19s

The browser wasm pipeline hardcoded -msimd128 for every wasm32 target and
baked a single wasm URL into index.html, so newer codegen features that
aren't yet baseline across engines (relaxed SIMD today; threads, future SIMD
revisions later) couldn't be adopted without dropping the browsers that lack
them.

Add a general, feature-parameterized mechanism owned entirely by
Crafter.Build:

- Configuration::wasmVariants declares N codegen variants (label, extra -m
  flags, runtime probes). Build() compiles the baseline plus one
  outputName.<label>.wasm per variant, recompiling the whole graph (incl.
  dep libs + std PCM) with the variant's flags — relaxed-SIMD is per-TU
  codegen, not a link switch. wasmVariantFlags folds into VariantId so each
  variant's objects/PCMs land in their own build+bin dir.
- EnableWasiBrowserRuntime emits a variants.json manifest (label -> url +
  probes), preferred-first with the baseline as the universal fallback.
- The shipped runtime.js runs inlined wasm-feature-detect probes
  (relaxed-simd, simd, tail-call, bulk-memory, exception-handling, threads),
  picks the first variant whose probes all pass, and falls back to the single
  baked CRAFTER_WASM_URL when no manifest is present (backward compatible).
- EnableWasiRelaxedSimdVariant registers the relaxed-SIMD variant — the
  motivating case (Chrome 114+/Firefox 120+ enable it by default; Safari
  still flag-gates it as of mid-2026).

Verified end to end: a wasm32-wasip1 build emits both wasi-hello.wasm and
wasi-hello.relaxed-simd.wasm + variants.json; Firefox selects the
relaxed-simd variant and runs it.

Resolves #24

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-06-15 15:14:18 +00:00
commit 022ada70a6
7 changed files with 395 additions and 5 deletions

View file

@ -47,8 +47,8 @@ int main() {
EnableWasiBrowserRuntime(cfg);
// Three files registered: runtime.js, generated index.html, files.json.
if (cfg.files.size() != 3) {
std::println(std::cerr, "expected 3 cfg.files entries, got {}", cfg.files.size());
if (cfg.files.size() != 4) {
std::println(std::cerr, "expected 4 cfg.files entries, got {}", cfg.files.size());
for (const auto& f : cfg.files) std::println(std::cerr, " - {}", f.string());
return 1;
}
@ -63,10 +63,12 @@ int main() {
const fs::path* runtimeJs = findByName("runtime.js");
const fs::path* indexHtml = findByName("index.html");
const fs::path* manifest = findByName("files.json");
const fs::path* variants = findByName("variants.json");
if (!runtimeJs) { std::println(std::cerr, "runtime.js not registered"); return 1; }
if (!indexHtml) { std::println(std::cerr, "index.html not registered"); return 1; }
if (!manifest) { std::println(std::cerr, "files.json not registered"); return 1; }
if (!variants) { std::println(std::cerr, "variants.json not registered"); return 1; }
// The generated index.html and manifest live under the project's build/
// wasi-runtime/<name>/ directory and must actually exist on disk.
@ -78,6 +80,25 @@ int main() {
std::println(std::cerr, "files.json not produced at {}", manifest->string());
return 1;
}
if (!fs::exists(*variants)) {
std::println(std::cerr, "variants.json not produced at {}", variants->string());
return 1;
}
// With no declared variants, variants.json is a single baseline entry
// pointing at outputName.wasm with no probes.
{
std::ifstream vin(*variants);
std::string vjson{std::istreambuf_iterator<char>(vin), std::istreambuf_iterator<char>()};
if (vjson.find("\"wasi-helper.wasm\"") == std::string::npos) {
std::println(std::cerr, "variants.json missing baseline url: {}", vjson);
return 1;
}
if (vjson.find("relaxed-simd") != std::string::npos) {
std::println(std::cerr, "variants.json unexpectedly mentions relaxed-simd: {}", vjson);
return 1;
}
}
// The template's {{WASM}} placeholder should have been replaced with
// the configured outputName.