Crafter.Build/project.cpp
catbot 022ada70a6
Some checks failed
CI / build-test-release (pull_request) Failing after 7m19s
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

121 lines
5.9 KiB
C++

import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view> args) {
std::vector<std::string> depArgs(args.begin(), args.end());
Configuration* math = GitProject({
.source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Math.git" },
.args = depArgs,
});
Configuration* asset = GitProject({
.source = { "https://forgejo.catcrafts.net/Catcrafts/Crafter.Asset.git" },
.args = depArgs,
});
static auto crafterBuildLib = std::make_unique<Configuration>();
crafterBuildLib->path = "./";
crafterBuildLib->name = "crafter.build-lib";
crafterBuildLib->outputName = "crafter-build";
ApplyStandardArgs(*crafterBuildLib, args);
// Windows builds (native msvc via build.cmd or cross-compiled mingw from
// Linux) need a DLL + import lib + launcher exe so LoadProject can
// compile project.cpp against a stable ABI boundary. Linux is monolithic.
crafterBuildLib->type = (crafterBuildLib->target == "x86_64-w64-mingw32" || crafterBuildLib->target == "x86_64-pc-windows-msvc")
? ConfigurationType::LibraryDynamic
: ConfigurationType::LibraryStatic;
crafterBuildLib->dependencies = { math, asset };
crafterBuildLib->defines.push_back({"CRAFTER_BUILD_HAS_ASSET", ""});
{
std::array<fs::path, 10> interfaces = {
"interfaces/Crafter.Build",
"interfaces/Crafter.Build-Shader",
"interfaces/Crafter.Build-Platform",
"interfaces/Crafter.Build-Interface",
"interfaces/Crafter.Build-Implementation",
"interfaces/Crafter.Build-External",
"interfaces/Crafter.Build-Clang",
"interfaces/Crafter.Build-Test",
"interfaces/Crafter.Build-Progress",
"interfaces/Crafter.Build-Asset",
};
std::array<fs::path, 9> implementations = {
"implementations/Crafter.Build-Shader",
"implementations/Crafter.Build-Platform",
"implementations/Crafter.Build-Interface",
"implementations/Crafter.Build-Implementation",
"implementations/Crafter.Build-External",
"implementations/Crafter.Build-Clang",
"implementations/Crafter.Build-Test",
"implementations/Crafter.Build-Progress",
"implementations/Crafter.Build-Asset",
};
crafterBuildLib->GetInterfacesAndImplementations(interfaces, implementations);
}
ExternalDependency& glslang = crafterBuildLib->externalDependencies.emplace_back();
glslang.name = "glslang";
glslang.source.url = "https://github.com/KhronosGroup/glslang.git";
glslang.source.branch = "main";
glslang.builder = ExternalBuilder::CMake;
glslang.options = { "-DENABLE_OPT=OFF" };
// mingw cross-build: skip the standalone executable. We only consume the
// libraries, and glslang.exe pulls in libgcc_eh which needs pthread that
// mingw-w64 doesn't link by default.
if (crafterBuildLib->target == "x86_64-w64-mingw32") {
glslang.options.push_back("-DENABLE_GLSLANG_BINARIES=OFF");
}
glslang.includeDirs = { "" };
glslang.libs = { "SPIRV", "GenericCodeGen", "glslang", "OSDependent", "MachineIndependent", "glslang-default-resource-limits" };
Configuration cfg;
cfg.path = "./";
cfg.name = "crafter.build-exe";
cfg.outputName = "crafter-build";
ApplyStandardArgs(cfg, args);
cfg.type = ConfigurationType::Executable;
cfg.dependencies = { crafterBuildLib.get() };
{
std::array<fs::path, 0> interfaces = {};
std::array<fs::path, 1> implementations = { "implementations/main" };
cfg.GetInterfacesAndImplementations(interfaces, implementations);
}
if (cfg.target == "x86_64-pc-linux-gnu") {
cfg.linkFlags.push_back("-Wl,--export-dynamic");
cfg.linkFlags.push_back("-ldl");
}
if (cfg.target == "x86_64-w64-mingw32" || cfg.target == "x86_64-pc-windows-msvc") {
// winsock for the -r wasm port probe (bind/WSAStartup).
crafterBuildLib->linkFlags.push_back("-lws2_32");
}
// Self-tests link the local crafter-build library and exercise it in
// process. The harness (whichever crafter-build invokes `test`) compiles
// these against the *installed* share/crafter-build .cppm files, then
// links each test exe against crafterBuildLib built from the local
// sources — so the code under test is whatever's in this checkout.
// Mirrors how downstream consumers link their own libraries into tests.
if (cfg.target == "x86_64-pc-linux-gnu") {
cfg.AddTest("HelloWorld").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("StaticLib").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("ModuleInterface").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("DependencyLink").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("ShaderCompile").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("StandardArgs").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("TestRunnerSpec").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("VariantId").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("WasiBrowserRuntime").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("WasmVariants").Dependencies({ crafterBuildLib.get() });
cfg.AddTest("RunSingleTestExit").Dependencies({ crafterBuildLib.get() });
// LoadProject dlopens the synthesized project.so, which references
// Crafter:: symbols (HostTarget, Configuration ctors) that have to be
// visible from the test exe — same wiring crafter-build itself uses
// for project.so.
cfg.AddTest("ConcurrentCacheRace").Dependencies({ crafterBuildLib.get() })
.LinkFlag("-Wl,--export-dynamic").LinkFlag("-ldl");
cfg.AddTest("ConcurrentDependencyReset").Dependencies({ crafterBuildLib.get() });
}
return cfg;
}