2026-07-23 01:24:42 +02:00
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
2026-07-22 18:16:38 +02:00
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
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>
2026-06-15 15:14:18 +00:00
import std ;
import Crafter . Build ;
namespace fs = std : : filesystem ;
using namespace Crafter ;
extern " C " int setenv ( const char * name , const char * value , int overwrite ) ;
// Covers the browser-wasm variant plumbing that does NOT require a wasm
// toolchain run: EnableWasiRelaxedSimdVariant's registration, the
// variants.json manifest EnableWasiBrowserRuntime emits, and the VariantId
// perturbation that keeps each variant's objects in their own build dir. The
// actual multi-.wasm production is exercised by a real wasm build (manual /
// integration), not here — same boundary the WasiBrowserRuntime test draws.
namespace {
2026-07-23 01:24:42 +02:00
std : : int32_t Failures = 0 ;
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
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>
2026-06-15 15:14:18 +00:00
void Check ( bool cond , std : : string_view msg ) {
2026-07-23 01:24:42 +02:00
if ( ! cond ) { std : : println ( std : : cerr , " FAIL: {} " , msg ) ; + + Failures ; }
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
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>
2026-06-15 15:14:18 +00:00
}
}
int main ( ) {
// ── EnableWasiRelaxedSimdVariant: registration + idempotency ──────────
{
Configuration cfg ;
cfg . target = " wasm32-wasip1 " ;
EnableWasiRelaxedSimdVariant ( cfg ) ;
Check ( cfg . wasmVariants . size ( ) = = 1 , " one variant registered " ) ;
if ( ! cfg . wasmVariants . empty ( ) ) {
const WasmVariant & v = cfg . wasmVariants . front ( ) ;
Check ( v . label = = " relaxed-simd " , " label is relaxed-simd " ) ;
Check ( v . flags . size ( ) = = 1 & & v . flags . front ( ) = = " -mrelaxed-simd " , " flag is -mrelaxed-simd " ) ;
Check ( v . probes . size ( ) = = 1 & & v . probes . front ( ) = = " relaxed-simd " , " probe is relaxed-simd " ) ;
}
EnableWasiRelaxedSimdVariant ( cfg ) ;
Check ( cfg . wasmVariants . size ( ) = = 1 , " second call is idempotent " ) ;
}
// ── wasmVariantFlags perturbs VariantId / BuildDir / BinDir ──────────
// Configuration owns move-only members (unique_ptr<Module>), so build the
// two cases independently rather than copying.
{
auto make = [ ] {
Configuration c ;
c . path = " /tmp/wasm-variant-id-test " ;
c . name = " wv " ; c . outputName = " wv " ;
c . target = " wasm32-wasip1 " ;
c . type = ConfigurationType : : Executable ;
return c ;
} ;
Configuration a = make ( ) ;
Configuration b = make ( ) ;
b . wasmVariantFlags = { " -mrelaxed-simd " } ;
Check ( a . VariantId ( ) ! = b . VariantId ( ) , " wasmVariantFlags perturbs VariantId " ) ;
Check ( a . BuildDir ( ) ! = b . BuildDir ( ) , " wasmVariantFlags perturbs BuildDir " ) ;
Check ( a . BinDir ( ) ! = b . BinDir ( ) , " wasmVariantFlags perturbs BinDir " ) ;
}
// ── variants.json manifest contents ──────────────────────────────────
{
fs : : path repoWasi = fs : : current_path ( ) / " wasi-runtime " ;
fs : : path systemHome = " /usr/local/share/crafter-build " ;
if ( fs : : exists ( repoWasi / " runtime.js " ) ) {
setenv ( " CRAFTER_BUILD_HOME " , fs : : current_path ( ) . c_str ( ) , 1 ) ;
} else if ( fs : : exists ( systemHome / " wasi-runtime " / " runtime.js " ) ) {
setenv ( " CRAFTER_BUILD_HOME " , systemHome . c_str ( ) , 1 ) ;
} else {
std : : println ( std : : cerr , " wasi-runtime assets not found; skipping manifest check " ) ;
2026-07-23 01:24:42 +02:00
return Failures ? 1 : 77 ;
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
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>
2026-06-15 15:14:18 +00:00
}
Configuration cfg ;
cfg . path = fs : : temp_directory_path ( ) / " crafter-build-wasm-variants-test " ;
cfg . name = " wvhelper " ;
cfg . outputName = " wvhelper " ;
cfg . target = " wasm32-wasip1 " ;
cfg . type = ConfigurationType : : Executable ;
std : : error_code ec ;
fs : : remove_all ( cfg . path , ec ) ;
fs : : create_directories ( cfg . path ) ;
EnableWasiRelaxedSimdVariant ( cfg ) ;
EnableWasiBrowserRuntime ( cfg ) ;
const fs : : path * variants = nullptr ;
for ( const fs : : path & f : cfg . files ) {
if ( f . filename ( ) = = " variants.json " ) variants = & f ;
}
Check ( variants ! = nullptr , " variants.json registered " ) ;
if ( variants ) {
std : : ifstream vin ( * variants ) ;
std : : string j { std : : istreambuf_iterator < char > ( vin ) , std : : istreambuf_iterator < char > ( ) } ;
// relaxed-simd entry, with its labelled url + probe.
Check ( j . find ( " \" label \" : \" relaxed-simd \" " ) ! = std : : string : : npos , " manifest has relaxed-simd label " ) ;
Check ( j . find ( " \" wvhelper.relaxed-simd.wasm \" " ) ! = std : : string : : npos , " manifest has labelled url " ) ;
Check ( j . find ( " \" relaxed-simd \" " ) ! = std : : string : : npos , " manifest has relaxed-simd probe " ) ;
fix(wasm): what the review found
-flto in a wasm configuration's compileFlags or linkFlags reached the
link, where LTO code generation runs without the backend options that
keep exception handling: every landing pad was dropped, and the build
succeeded with throws that escaped their catch blocks. -fno-exceptions
there made every `import std;` fail on the std module built with
exceptions, and a wasm shared library failed to link against the eh/
libc++, which is static archives built without -fPIC. Build() refuses
all three now, each with a line that says why, before it makes a
directory or starts a thread.
The sysroot check sat in BuildStdPcm, which the linter calls as well.
There the error only dropped the compile command, so lint ran no AST
rule on a wasm configuration and reported it clean. It also refused
static libraries, which never link, fired after the shader and
external threads had started, and called a missing sysroot, or one
without the target, "no C++ exception support". It is part of the same
early check now, which first says when the sysroot or the target's
directory is missing, and asks for libunwind only where something
links.
C sources got only the signal.h define. setjmp.h refused to compile in
C, and a C object given -wasm-enable-sjlj through compileFlags used the
legacy exception encoding next to C++ objects in the standard one, a
mix engines refuse to load. They get WasmCompileFlags now, like the C++
units, a variant pass's flags included.
The baseline probes include simd: V8 and SpiderMonkey switch SIMD off
on x86 CPUs without SSE4.1, which virtual machines often present, and
such an engine passed the exception-handling probe only to fail
compiling the module; the manifest tests expect it on every entry. The
Flatpak pins WASI_SDK_EXCEPTIONS=DUAL, which
wasi-sdk 34 defaults to only with clang 23 or newer, and checks that
the eh/ libunwind got installed: the wasm tests skip without it, so CI
would otherwise pass a Flatpak whose every wasm build fails.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 06:10:41 +01:00
// Every file is built with SIMD128 and exceptions, so every entry
// needs them besides its own features; the baseline needs
// nothing else.
Check ( j . find ( " \" probes \" :[ \" relaxed-simd \" , \" simd \" , \" exception-handling \" ] " ) ! = std : : string : : npos , " relaxed-simd entry also probes for the baseline's features " ) ;
Check ( j . find ( " \" url \" : \" wvhelper.wasm \" , \" probes \" :[ \" simd \" , \" exception-handling \" ] " ) ! = std : : string : : npos , " baseline entry probes for its own features only " ) ;
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
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>
2026-06-15 15:14:18 +00:00
// baseline fallback entry.
Check ( j . find ( " \" wvhelper.wasm \" " ) ! = std : : string : : npos , " manifest has baseline url " ) ;
// The relaxed-simd variant must precede the baseline so the runtime
// prefers it when probes pass.
auto relaxedPos = j . find ( " relaxed-simd " ) ;
auto baselinePos = j . find ( " \" wvhelper.wasm \" " ) ;
2026-07-23 01:24:42 +02:00
Check ( relaxedPos ! = std : : string : : npos & & baselinePos ! = std : : string : : npos & & relaxedPos < baselinePos , " relaxed-simd entry precedes baseline " ) ;
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
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>
2026-06-15 15:14:18 +00:00
}
fs : : remove_all ( cfg . path , ec ) ;
}
2026-07-23 01:24:42 +02:00
if ( Failures > 0 ) {
std : : println ( std : : cerr , " {} assertions failed " , Failures ) ;
feat: feature-detected browser-wasm variants (relaxed-SIMD) + variant-aware runtime
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>
2026-06-15 15:14:18 +00:00
return 1 ;
}
return 0 ;
}