From 70ff11d0a1ec10b2cf30f2090c831fa60c161792 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sat, 31 Oct 2026 06:29:52 +0100 Subject: [PATCH] feat(wasm): link libsetjmp, so setjmp needs no flag -mllvm -wasm-enable-sjlj, in every wasm compile because setjmp.h does not compile without it, turns setjmp and longjmp into calls to __wasm_setjmp, __wasm_setjmp_test and __wasm_longjmp and a throw of the __c_longjmp tag, all of which live in wasi-libc's libsetjmp. The driver does not link it, so a program that used setjmp, or a C library that handles its errors that way, failed to link with undefined __wasm_setjmp until the project put -lsetjmp in its linkFlags. Build() adds it to every wasm link, next to -lunwind. A static archive contributes only the members something references, so a program without setjmp gets nothing from it, and every sysroot that passes the exception check ships it. WasmExceptions drops its own -lsetjmp, and its C fixture now fails to link without this. Co-Authored-By: Claude Opus 5.5 --- README.md | 2 +- implementations/Crafter.Build-Clang.cpp | 9 +++++++-- tests/WasmExceptions/main.cpp | 6 ++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 86d1371..77514ec 100644 --- a/README.md +++ b/README.md @@ -189,7 +189,7 @@ For `--target=x86_64-w64-mingw32` (Windows from Linux), the pipeline cross-compi For `--target=wasm32-wasip1`, the WASI sysroot is the one beside the toolchain (the Flatpak bundles one) unless `CRAFTER_BUILD_WASI_SYSROOT` names another, and tests run under `wasmtime`. C++ exceptions work as on the other targets: every unit and the std module are compiled with `-fwasm-exceptions` in the standardized form of WebAssembly exception handling (exnref), against the exception-enabled libc++ in the sysroot's `lib//eh/`, and `-lunwind` is linked automatically. A sysroot without that libc++ (wasi-sdk before 33) stops the build with one line saying so. The result runs on engines with that proposal: Chrome 137, Firefox 131 and Safari 18.4 or newer, and the wasmtime the Flatpak bundles. A page from `EnableWasiBrowserRuntime` checks first and names what an older browser lacks in the console, instead of failing to compile the module. An exception that escapes `main` leaves the program as an uncaught WebAssembly exception (wasmtime reports `thrown Wasm exception` and exits 1, a page gets a `WebAssembly.Exception`) without calling `std::terminate`, so a `std::set_terminate` handler does not run and nothing prints the exception's type or `what()`: wasm exception handling unwinds in a single phase, and nothing finds out that no handler is left until the stack is gone. Catch it in `main` to report it. -A wasm build refuses three things before it starts, each with a message that says why: `-flto` in `compileFlags` or `linkFlags` (LTO code generation drops the exception handling, [WebAssembly/wasi-sdk#629](https://github.com/WebAssembly/wasi-sdk/issues/629)), `-fno-exceptions` (the std module is built with exceptions, and a unit without them cannot import it) and a shared library (the exception-enabled libc++ is static archives only). `setjmp` and `longjmp` work in C and C++ sources once `-lsetjmp` is in `linkFlags`; a `longjmp` across a `noexcept` function ends the program, because it is a wasm exception and the function's terminate handler catches it. Outside the Flatpak, a wasmtime older than 47 has exception handling off and needs `-W exceptions=y`, and a `wasm-opt` on `PATH` from a binaryen that cannot read the standardized encoding breaks release links, since clang runs it on optimized wasm links when it finds one. +A wasm build refuses three things before it starts, each with a message that says why: `-flto` in `compileFlags` or `linkFlags` (LTO code generation drops the exception handling, [WebAssembly/wasi-sdk#629](https://github.com/WebAssembly/wasi-sdk/issues/629)), `-fno-exceptions` (the std module is built with exceptions, and a unit without them cannot import it) and a shared library (the exception-enabled libc++ is static archives only). `setjmp` and `longjmp` work in C and C++ sources, with `-lsetjmp` linked automatically like `-lunwind`; a `longjmp` across a `noexcept` function ends the program, because it is a wasm exception and the function's terminate handler catches it. Outside the Flatpak, a wasmtime older than 47 has exception handling off and needs `-W exceptions=y`, and a `wasm-opt` on `PATH` from a binaryen that cannot read the standardized encoding breaks release links, since clang runs it on optimized wasm links when it finds one. In a page from `EnableWasiBrowserRuntime`, `exit` and `_Exit` end the program as they would anywhere else: none of its code runs on the way out, no destructor and no `catch (...)`, its status is in `window.crafter_wasi.exitCode`, and bridges that call into it on their own are expected to stop. A program that hands the thread to the browser for good and goes on serving calls, as Crafter.Graphics's `Window::StartSync()` does, parks instead of exiting. Parking never returns, runs nothing of the program either, and leaves `main`'s locals valid for the calls that follow: diff --git a/implementations/Crafter.Build-Clang.cpp b/implementations/Crafter.Build-Clang.cpp index 916abf8..e0d8329 100644 --- a/implementations/Crafter.Build-Clang.cpp +++ b/implementations/Crafter.Build-Clang.cpp @@ -1208,9 +1208,14 @@ BuildResult Crafter::Build(Configuration& config, std::unordered_map impls = { "main" }; cfg.GetInterfacesAndImplementations(ifaces, impls); cfg.cFiles = { cfg.path / "jump" }; - // setjmp and longjmp live in libsetjmp, which the build does not link - // on its own. - cfg.linkFlags.push_back("-lsetjmp"); std::unordered_map> depResults; std::mutex depMutex;