feat(wasm): compile C++ with WebAssembly exception handling
wasm builds were compiled with -fno-exceptions. A throw did not compile, libc++'s own throws (vector::at, std::format, filesystem) aborted the program, and code that relies on exceptions could not be built for the browser at all. Every wasm unit and the std module now get -fwasm-exceptions, and the link adds -lunwind, which code built that way calls into and the driver does not add. The flag also selects the libc++ under the sysroot's lib/<triple>/eh, which wasi-sdk 33 and later ship next to the noeh one. Those libraries use the standardized encoding (try_table, exnref) while LLVM 23 still emits the legacy one by default, which wasmtime refuses to load and which would put both in one binary, so the flags ask for the standard form with -wasm-use-legacy-eh=false. That makes the setjmp lowering loadable under wasmtime too. The flags live in one constant, WasmBaselineFlags, which the compile command and the std module both take through WasmCompileFlags: a BMI is refused by a unit whose exception setting differs from its own. VariantId and StdPcmDir hash it, so the first build after the update starts fresh instead of importing PCMs built without exceptions, and two releases sharing the Flatpak's cache don't take turns rebuilding one std PCM. A sysroot without the exception-enabled libraries stops the build with a line saying so, rather than at the link with "unable to find library -lunwind". The result needs an engine with exnref: Chrome 137, Firefox 131, Safari 18.4, the bundled wasmtime. The runtime's exception-handling probe tested the legacy encoding; it now tests try_table, every variants.json entry carries it, and a browser without it is told which feature it lacks instead of failing with a CompileError. There is no switch back to -fno-exceptions: engines without exnref are old enough not to keep a second build for them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
eefd1df3eb
commit
63c4040000
4 changed files with 124 additions and 37 deletions
|
|
@ -11,6 +11,34 @@ import :Toolchain;
|
|||
import :Agents;
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace Crafter {
|
||||
// What every wasm32 C++ translation unit is compiled with, and the std
|
||||
// module it imports. A BMI cannot be loaded by a unit whose options differ
|
||||
// from the ones that wrote it, so GetCompileCommand and BuildStdPcm both
|
||||
// take the set from here (through WasmCompileFlags), and VariantId and
|
||||
// StdPcmDir hash it so that a crafter-build which changes it never reuses
|
||||
// what the old set compiled.
|
||||
//
|
||||
// -fwasm-exceptions compiles C++ exceptions to WebAssembly exception
|
||||
// handling. It also selects the sysroot's include/<triple>/eh and
|
||||
// lib/<triple>/eh, the libc++ built with exceptions that wasi-sdk 33 and
|
||||
// later ship next to the noeh/ one, so it has to reach the link too; the
|
||||
// link reuses this command line, so it does. Those libraries use the
|
||||
// standardized form of the proposal (try_table, exnref), while LLVM 23
|
||||
// still defaults to the legacy one: wasmtime refuses to load that, and it
|
||||
// would mix two encodings in one binary. -wasm-use-legacy-eh=false asks
|
||||
// for the standard form, for setjmp as well. wasi-libc's setjmp.h does
|
||||
// not compile without -wasm-enable-sjlj, which lowers setjmp to the same
|
||||
// exception handling, and signal.h needs the emulation define.
|
||||
inline constexpr std::string_view WasmBaselineFlags = "-fwasm-exceptions -mllvm -wasm-use-legacy-eh=false -msimd128 -fno-c++-static-destructors -mllvm -wasm-enable-sjlj -D_WASI_EMULATED_SIGNAL";
|
||||
// The wasi-runtime/runtime.js probes an engine has to pass to run anything
|
||||
// built with WasmBaselineFlags; EnableWasiBrowserRuntime puts them on every
|
||||
// entry of variants.json. Exception handling with exnref is the newest
|
||||
// feature those flags need (Chrome 137, Firefox 131, Safari 18.4), so an
|
||||
// engine that has it has SIMD128 and the rest as well.
|
||||
inline constexpr auto WasmBaselineProbes = std::to_array<std::string_view>({"exception-handling"});
|
||||
}
|
||||
|
||||
export namespace Crafter {
|
||||
struct BuildResult {
|
||||
std::string result;
|
||||
|
|
@ -38,8 +66,10 @@ export namespace Crafter {
|
|||
// `label` emits an additional `outputName.<label>.wasm` alongside the
|
||||
// baseline `outputName.wasm`; `probes` names the runtime feature-detect
|
||||
// checks (see wasi-runtime/runtime.js's probe registry) that must all pass
|
||||
// for a browser to be served this variant. The first variant whose probes
|
||||
// pass wins; the baseline is the universal fallback.
|
||||
// for a browser to be served this variant, on top of WasmBaselineProbes,
|
||||
// which every build needs. The first variant whose probes pass wins; the
|
||||
// baseline is the fallback, and an engine that fails even its probes gets
|
||||
// an error naming them.
|
||||
struct WasmVariant {
|
||||
std::string label;
|
||||
std::vector<std::string> flags;
|
||||
|
|
@ -586,6 +616,16 @@ export namespace Crafter {
|
|||
compileKey += "|W:";
|
||||
compileKey += f;
|
||||
}
|
||||
// So does the set every wasm unit gets before those. Unkeyed, the
|
||||
// release that turned exceptions on would have kept the objects
|
||||
// and PCMs compiled without them: the first unit recompiled after
|
||||
// the update would import BMIs built without exceptions, which
|
||||
// clang rejects, and objects nothing recompiled would let
|
||||
// exceptions pass through without running their destructors.
|
||||
if (target.starts_with("wasm32")) {
|
||||
compileKey += "|B:";
|
||||
compileKey += WasmBaselineFlags;
|
||||
}
|
||||
// Sorted by ApplyStandardArgs, so flag order on the command line
|
||||
// doesn't split the cache.
|
||||
for (const std::string& a : projectArgs) {
|
||||
|
|
@ -622,7 +662,8 @@ export namespace Crafter {
|
|||
|
||||
// Directory holding the std module PCM for this Configuration. Keyed on
|
||||
// target+march because a BMI is only loadable by a TU with matching target
|
||||
// features, and suffixed with the wasm variant flags for the same reason.
|
||||
// features, and suffixed with the wasm flags (WasmBaselineFlags, then the
|
||||
// variant's) for the same reason.
|
||||
fs::path StdPcmDir(const Configuration& config);
|
||||
|
||||
// Where Build leaves this Configuration's linked artifact: the executable
|
||||
|
|
@ -739,3 +780,11 @@ export namespace Crafter {
|
|||
// for-arg-in-args loop.
|
||||
ArgQuery ApplyStandardArgs(Configuration& cfg, std::span<const std::string_view> args);
|
||||
}
|
||||
|
||||
namespace Crafter {
|
||||
// WasmBaselineFlags followed by the active variant pass's
|
||||
// wasmVariantFlags, each with a leading space: the wasm part of every
|
||||
// compile command and of the std module's, which have to agree flag for
|
||||
// flag.
|
||||
std::string WasmCompileFlags(const Configuration& config);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue