feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
Some checks failed
CI / build-test-release (pull_request) Failing after 7m19s
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:
parent
dbee23c564
commit
022ada70a6
7 changed files with 395 additions and 5 deletions
|
|
@ -820,9 +820,71 @@ class Wasi {
|
|||
}
|
||||
}
|
||||
|
||||
const wasmUrl = window.CRAFTER_WASM_URL;
|
||||
// ─── wasm feature-detect probes ────────────────────────────────────────
|
||||
// Each probe validates a ~dozen-byte module that only validates on an engine
|
||||
// implementing the named feature — the same technique as
|
||||
// GoogleChromeLabs/wasm-feature-detect, inlined so we ship no dependency. A
|
||||
// variant in variants.json lists the probe names that must all pass for that
|
||||
// variant to be served; an unknown name is treated as unsupported (we fall
|
||||
// through to a less demanding variant, ultimately the baseline). Keep the keys
|
||||
// in sync with the `probes` a WasmVariant declares in Crafter.Build.
|
||||
const WASM_PROBES = {
|
||||
// relaxed SIMD: i8x16.relaxed_swizzle (Chrome 114+, Firefox 120+; Safari
|
||||
// still flag-gated as of mid-2026).
|
||||
"relaxed-simd": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,15,1,13,0,65,1,253,15,65,2,253,15,253,128,2,11])),
|
||||
// fixed-width SIMD128 (v128.const + i8x16.popcnt) — the current baseline,
|
||||
// here for completeness / future baseline shifts.
|
||||
"simd": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,5,1,96,0,1,123,3,2,1,0,10,10,1,8,0,65,0,253,15,253,98,11])),
|
||||
// tail calls (return_call).
|
||||
"tail-call": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,6,1,4,0,18,0,11])),
|
||||
// bulk memory ops (memory.init).
|
||||
"bulk-memory": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,3,1,0,1,10,14,1,12,0,65,0,65,0,65,0,252,10,0,0,11])),
|
||||
// legacy exception handling (try/catch).
|
||||
"exception-handling": () => WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,10,8,1,6,0,6,64,25,11,11])),
|
||||
// threads/atomics — also requires SharedArrayBuffer (cross-origin isolated).
|
||||
"threads": () => {
|
||||
try {
|
||||
if (typeof SharedArrayBuffer === "undefined") return false;
|
||||
return WebAssembly.validate(new Uint8Array([0,97,115,109,1,0,0,0,1,4,1,96,0,0,3,2,1,0,5,4,1,3,1,1,10,11,1,9,0,65,0,254,16,2,0,26,11]));
|
||||
} catch (e) { return false; }
|
||||
},
|
||||
};
|
||||
|
||||
function variantSupported(probes) {
|
||||
return (probes || []).every((name) => {
|
||||
const probe = WASM_PROBES[name];
|
||||
if (!probe) {
|
||||
console.warn(`[wasi] unknown wasm probe '${name}' — treating variant as unsupported`);
|
||||
return false;
|
||||
}
|
||||
try { return !!probe(); } catch (e) { return false; }
|
||||
});
|
||||
}
|
||||
|
||||
// Pick the wasm URL: prefer the variants.json manifest (first entry whose
|
||||
// probes all pass), else fall back to the single baked window.CRAFTER_WASM_URL.
|
||||
async function pickWasmUrl() {
|
||||
let variants = null;
|
||||
try {
|
||||
const r = await fetch("variants.json");
|
||||
if (r.ok) variants = await r.json();
|
||||
} catch (e) {
|
||||
// No manifest (older build) — fall back below.
|
||||
}
|
||||
if (Array.isArray(variants) && variants.length) {
|
||||
for (const v of variants) {
|
||||
if (variantSupported(v.probes)) {
|
||||
if (v.label) console.log(`[wasi] selected wasm variant '${v.label}' (${(v.probes || []).join(", ")})`);
|
||||
return v.url;
|
||||
}
|
||||
}
|
||||
}
|
||||
return window.CRAFTER_WASM_URL;
|
||||
}
|
||||
|
||||
const wasmUrl = await pickWasmUrl();
|
||||
if (!wasmUrl) {
|
||||
throw new Error("runtime.js: window.CRAFTER_WASM_URL is not set (set it in index.html before loading runtime.js)");
|
||||
throw new Error("runtime.js: no wasm variant selected and window.CRAFTER_WASM_URL is not set");
|
||||
}
|
||||
|
||||
// Preload asset files listed in files.json (emitted by
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue