WasmExceptions builds a fixture for wasm32-wasip1 and runs it under
wasmtime. It throws through four frames whose destructors have to run,
catches the exception, catches one that vector::at throws, and with an
argument lets one escape main, which has to fail the run. With the
previous flags the fixture does not compile ("cannot use 'throw' with
exceptions disabled"), and each part of turning exceptions on fails it
in its own way, from a BMI built without them to the legacy encoding
wasmtime refuses.
WasiSysrootWithoutExceptions takes a scratch sysroot through three
states, no libunwind, libunwind under eh/ and libunwind in the
triple's own directory, and checks that only the first stops the
build, with the message naming the library it looked for. It needs no
working wasm toolchain, so it also runs where the others skip.
WasmHelloRuns and WasmExceptions treat a sysroot without the
exception-enabled libraries as missing and skip. The manifest tests
expect the exception-handling probe on every entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
5.6 KiB
C++
121 lines
5.6 KiB
C++
// SPDX-License-Identifier: LGPL-3.0-only
|
|
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
|
|
|
|
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 {
|
|
std::int32_t Failures = 0;
|
|
void Check(bool cond, std::string_view msg) {
|
|
if (!cond) { std::println(std::cerr, "FAIL: {}", msg); ++Failures; }
|
|
}
|
|
}
|
|
|
|
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");
|
|
return Failures ? 1 : 77;
|
|
}
|
|
|
|
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");
|
|
// Every file is built with exceptions, so every entry needs them
|
|
// besides its own features; the baseline needs nothing else.
|
|
Check(j.find("\"probes\":[\"relaxed-simd\",\"exception-handling\"]") != std::string::npos, "relaxed-simd entry also probes for exception handling");
|
|
Check(j.find("\"url\":\"wvhelper.wasm\",\"probes\":[\"exception-handling\"]") != std::string::npos, "baseline entry probes for exception handling only");
|
|
// 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\"");
|
|
Check(relaxedPos != std::string::npos && baselinePos != std::string::npos && relaxedPos < baselinePos, "relaxed-simd entry precedes baseline");
|
|
}
|
|
|
|
fs::remove_all(cfg.path, ec);
|
|
}
|
|
|
|
if (Failures > 0) {
|
|
std::println(std::cerr, "{} assertions failed", Failures);
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|