diff --git a/implementations/Crafter.Build-Clang.cpp b/implementations/Crafter.Build-Clang.cpp index 7dec117..e2d89ef 100644 --- a/implementations/Crafter.Build-Clang.cpp +++ b/implementations/Crafter.Build-Clang.cpp @@ -447,7 +447,16 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map.wasm. Relaxed-SIMD (and friends) is + // per-translation-unit codegen, not a link switch, so a full rebuild — incl. + // dep libs and the std PCM — is the only correct way to produce it. + // + // Guard: only fires on the top-level config that *declared* wasmVariants + // (deps leave it empty) and never on a variant pass itself (those carry + // wasmVariantFlags), so there's no recursion. Skipped on build error. + if (buildResult.result.empty() + && config.target.starts_with("wasm32") + && config.type == ConfigurationType::Executable + && !config.wasmVariants.empty() + && config.wasmVariantFlags.empty()) { + fs::path baselineBin = config.BinDir(); + + // The whole transitive graph, so the variant flags reach every TU. + std::vector graph; + { + std::unordered_set graphSeen; + std::function collect = [&](Configuration* c) { + if (!c || !graphSeen.insert(c).second) return; + graph.push_back(c); + for (Configuration* dep : c->dependencies) collect(dep); + }; + collect(&config); + } + + for (const WasmVariant& variant : config.wasmVariants) { + // A labelless or flagless "variant" is just the baseline under + // another name — nothing extra to compile. + if (variant.label.empty() || variant.flags.empty()) continue; + + Progress::Task task(std::format("Building wasm variant {} ({})", + variant.label, config.outputName)); + + for (Configuration* c : graph) c->wasmVariantFlags = variant.flags; + fs::path variantBin = config.BinDir(); + + std::unordered_map> variantDeps; + std::mutex variantMutex; + BuildResult vr = Build(config, variantDeps, variantMutex); + + for (Configuration* c : graph) c->wasmVariantFlags.clear(); + + if (!vr.result.empty()) { + buildResult.result = std::format( + "wasm variant '{}' build failed: {}", variant.label, vr.result); + break; + } + + std::error_code ec; + fs::path src = variantBin / (config.outputName + ".wasm"); + fs::path dst = baselineBin / std::format("{}.{}.wasm", config.outputName, variant.label); + fs::copy_file(src, dst, fs::copy_options::overwrite_existing, ec); + if (ec) { + buildResult.result = std::format( + "copy wasm variant '{}' {} -> {}: {}", + variant.label, src.string(), dst.string(), ec.message()); + break; + } + } + } + return buildResult; } @@ -1250,6 +1331,50 @@ void Crafter::EnableWasiBrowserRuntime(Configuration& cfg) { m << "]"; } + // Variant manifest. runtime.js fetches this, runs each entry's probes in + // order, and instantiates the first whose probes all pass — so the + // preferred (most feature-rich) variants come first and the baseline last + // as the universal fallback (empty probes always pass). A variant with a + // non-empty label maps to outputName.