From 2f7a86ed9442fe4c2fc78959a353acbf7d4617f7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 10 Mar 2026 15:44:03 -0700 Subject: [PATCH 1/3] Build a sysroot that supports C++ exceptions by default This commit collects together some LLVM PRs, some changes in the build configuration here, and some thoughts from #565 and related issues. Specifically the changes here are: * The patch for llvm/llvm-project#168449 is updated to its upstream (unlanded) form. * Patches for the (landed) llvm/llvm-project#185770 and llvm/llvm-project#185775 are added. * The `WASI_SDK_EXCEPTIONS` configuration is now either `ON`, `OFF`, or `DUAL`. The default depends on the version of Clang in use, where 23.0.0+ (which isn't released officially yet) will be `DUAL` and otherwise it's `OFF`. CI for our custom-built patched toolchain defaults to `DUAL`. * In `DUAL` mode libcxx is built twice into two different directories, once with exceptions and once without. This is supported by LLVM patches and means that Clang will select the right set of libraries based on compiler flags. The end result here is that the produced toolchain from this repository, by default, supports C++ exceptions. Additionally if exceptions-related flags are not passed then the final binary will not use C++ exceptions nor require the wasm exception-handling proposal. There's still follow-up work from #565, such as: * Subjectively it feels wordy to pass `-fwasm-exceptions` vs `-fexceptions`. * Personally I think `-mllvm -wasm-use-legacy-eh=false` should become the default upstream. * Subjectively I don't think that `-lunwind` should be necessary and it should be injected automatically with `-fwasm-exceptions` (or `-fexceptions`). * Shared libraries for exceptions remain disabled due to build errors I do not personally know how to resolve. I'll file follow-up issues for these once this has landed since they're more minor compared to the main body of "anything works". Closes #334 Closes #565 --- .github/workflows/main.yml | 10 ++- CppExceptions.md | 78 +++++++++-------- README.md | 4 +- ci/build.sh | 1 + cmake/wasi-sdk-sysroot.cmake | 73 ++++++++++++---- cmake/wasi-sdk-toolchain.cmake | 3 + src/llvm-pr-168449.patch | 27 +++--- src/llvm-pr-185770.patch | 105 +++++++++++++++++++++++ src/llvm-pr-185775.patch | 152 +++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 4 +- 10 files changed, 385 insertions(+), 72 deletions(-) create mode 100644 src/llvm-pr-185770.patch create mode 100644 src/llvm-pr-185775.patch diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 14d51b5b2..d00dc91f2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -170,10 +170,16 @@ jobs: - uses: ./.github/actions/checkout - uses: ./.github/actions/install-deps - run: cargo install wasm-component-ld@0.5.21 - - run: sudo apt-get update -y && sudo apt-get install -y clang-20 lld-20 + - name: Install LLVM 22 + run: | + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - + v=22 + rel=$(lsb_release -cs) + sudo apt-add-repository "deb http://apt.llvm.org/$rel/ llvm-toolchain-$rel-$v main" + sudo apt-get update -y && sudo apt-get install -y clang-$v lld-$v - run: | cmake -G Ninja -B build -S . \ - -DCMAKE_C_COMPILER=/usr/lib/llvm-20/bin/clang \ + -DCMAKE_C_COMPILER=/usr/lib/llvm-22/bin/clang \ -DCMAKE_SYSTEM_NAME=WASI \ -DWASI_SDK_INCLUDE_TESTS=ON \ -DWASI_SDK_CPU_CFLAGS="" \ diff --git a/CppExceptions.md b/CppExceptions.md index 7ea1e2d8c..f51e0ffd1 100644 --- a/CppExceptions.md +++ b/CppExceptions.md @@ -1,25 +1,17 @@ # Support for C++ Exceptions -The released artifacts for wasi-sdk at this time do not support C++ exceptions. -LLVM and Clang, however, have support for C++ exceptions in WebAssembly and this -is intended to serve as documentation of the current state of affairs of using -C++ exceptions. It should be noted though that the current status of C++ -exceptions support is not intended to be the final state of support, and this is -all continuing to be iterated on over time. +> **Note**: this documentation does not cover wasi-sdk-31, the latest version +> of wasi-sdk at this time. -## Building wasi-sdk with exceptions +From wasi-sdk-32 and onwards the artifacts produced by this repository support +compiling C++ code both with and without exceptions. The sysroot for wasm +targets contains two copies of the C++ standard library and headers -- one with +exceptions enabled and one with exceptions disabled. These are automatically +selected based on compilation flags. This means that wasi-sdk-produced binaries +can avoid using wasm exceptions entirely by disabling C++ exceptions, or C++ +exceptions can be enabled in which case wasm exceptions will be used. -When building the sysroot with wasi-sdk you can pass `-DWASI_SDK_EXCEPTIONS=ON` -to enable support for C++ exceptions. For example: - -```shell script -$ cmake -G Ninja -B build/sysroot -S . \ - -DCMAKE_TOOLCHAIN_FILE=$path/to/wasi-sdk-p1.cmake \ - -DWASI_SDK_EXCEPTIONS=ON -``` - -The C++ standard library will be compiled with support for exceptions for the -desired targets and the resulting sysroot supports using exceptions. +Currently the default is for C++ exceptions to be disabled. ## Compiling code with C++ exceptions @@ -36,25 +28,43 @@ This can be specified for example with: ```shell script $ export CFLAGS="-fwasm-exceptions -mllvm -wasm-use-legacy-eh=false" -$ export LDFLAGS="-lunwind" +$ export LDFLAGS="-fwasm-exceptions -lunwind" ``` -## Limitations +Note that `-fwasm-exceptions` must be present when linking to select the +correct C++ standard library to link. + +## Building wasi-sdk with exceptions -Currently C++ exceptions support in wasi-sdk does not support shared libraries. -Fixing this will require resolving some miscellaneous build issues in this -repository itself. +When building the sysroot with wasi-sdk you can pass `-DWASI_SDK_EXCEPTIONS=ON` +to enable support for C++ exceptions. For example: -## Future Plans +```shell script +$ cmake -G Ninja -B build/sysroot -S . \ + -DCMAKE_TOOLCHAIN_FILE=$path/to/wasi-sdk-p1.cmake \ + -DWASI_SDK_EXCEPTIONS=ON +``` + +The C++ standard library will be compiled with support for exceptions for the +desired targets and the resulting sysroot supports using exceptions. Note that +enabling C++ exceptions requires LLVM 22 or later. + +C++ exceptions are disabled by default for local builds. With a future release +of LLVM 23 the dual-sysroot nature will be on-by-default. + +## Limitations -There are a few tracking issues with historical discussion about C++ exceptions -support in wasi-sdk such as [#334](https://github.com/WebAssembly/wasi-sdk/issues/334) -and [#565](https://github.com/WebAssembly/wasi-sdk/issues/565). The major -remaining items are: +There are a few known limitations/bugs/todos around exceptions support in +wasi-sdk at this time: -* Figure out support for shared libraries. -* Determine how to ship a sysroot that supports both with-and-without - exceptions. -* Figure out how to avoid the need for extra compiler flags when using - exceptions. -* Figure out if a new wasm target is warranted. +* Currently C++ exceptions support in wasi-sdk does not support shared + libraries. Fixing this will require resolving some miscellaneous build + issues in this repository itself as well as [resolving some upstream + issues](https://github.com/llvm/llvm-project/issues/188077). +* Currently `-fwasm-exceptions` is a required flag to enable C++ exceptions. + It's unclear whethe `-fexceptions` should also be supported as a substitute. +* Currently LLVM defaults to using the legacy exception-handling proposal and + this will likely change in the future. Precompiled libraries for wasi-sdk are + all built with the standard exception-handlign proposal. +* Currently `-lunwind` is required when linking, but this may become automatic + in the future. diff --git a/README.md b/README.md index 7d28f6957..7893c9639 100644 --- a/README.md +++ b/README.md @@ -209,8 +209,8 @@ disabled in a configure step before building with WASI SDK. ## Notable Limitations -* C++ exceptions are disabled by default. For more information see - [CppExceptions.md]. +* C++ exceptions are disabled by default and require extra configuration to get + working, see [CppExceptions.md]. * C `setjmp`/`longjmp` require some extra configuration to get working, see [SetjmpLongjmp.md]. * Most targets do not support spawning a thread. Experimental support for diff --git a/ci/build.sh b/ci/build.sh index 7525aa4d4..7c02fff8f 100755 --- a/ci/build.sh +++ b/ci/build.sh @@ -37,6 +37,7 @@ cmake -G Ninja -B $build_dir/sysroot -S . \ -DCMAKE_C_COMPILER_WORKS=ON \ -DCMAKE_CXX_COMPILER_WORKS=ON \ -DWASI_SDK_INCLUDE_TESTS=ON \ + -DWASI_SDK_EXCEPTIONS=DUAL \ "-DCMAKE_INSTALL_PREFIX=$build_dir/install" ninja -C $build_dir/sysroot install dist -v diff --git a/cmake/wasi-sdk-sysroot.cmake b/cmake/wasi-sdk-sysroot.cmake index 38ed42636..b29a5f4a0 100644 --- a/cmake/wasi-sdk-sysroot.cmake +++ b/cmake/wasi-sdk-sysroot.cmake @@ -20,13 +20,28 @@ message(STATUS "Found executable for `ar`: ${CMAKE_AR}") find_program(MAKE make REQUIRED) +set(EXCEPTIONS_DEFAULT "OFF") +if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 23.0.0) + set(EXCEPTIONS_DEFAULT "DUAL") +endif() + option(WASI_SDK_DEBUG_PREFIX_MAP "Pass `-fdebug-prefix-map` for built artifacts" ON) option(WASI_SDK_INCLUDE_TESTS "Whether or not to build tests by default" OFF) option(WASI_SDK_INSTALL_TO_CLANG_RESOURCE_DIR "Whether or not to modify the compiler's resource directory" OFF) option(WASI_SDK_LTO "Whether or not to build LTO assets" ON) -option(WASI_SDK_EXCEPTIONS "Whether or not C++ exceptions are enabled" OFF) +set(WASI_SDK_EXCEPTIONS "${EXCEPTIONS_DEFAULT}" CACHE STRING "Whether or not C++ exceptions are enabled") set(WASI_SDK_CPU_CFLAGS "-mcpu=lime1" CACHE STRING "CFLAGS to specify wasm features to enable") +if ((WASI_SDK_EXCEPTIONS STREQUAL "DUAL") OR (WASI_SDK_EXCEPTIONS STREQUAL "ON")) + if(CMAKE_C_COMPILER_VERSION VERSION_LESS 22.0.0) + message(FATAL_ERROR "enabling C++ exceptions requires Clang 22 or later") + endif() +elseif(WASI_SDK_EXCEPTIONS STREQUAL "OFF") + # No extra validation needed +else() + message(FATAL_ERROR "unknown WASI_SDK_EXCEPTIONS value ${WASI_SDK_EXCEPTIONS}, expected one of: OFF, ON, DUAL") +endif() + set(wasi_tmp_install ${CMAKE_CURRENT_BINARY_DIR}/install) set(wasi_sysroot ${wasi_tmp_install}/share/wasi-sysroot) set(wasi_resource_dir ${wasi_tmp_install}/wasi-resource-dir) @@ -225,7 +240,7 @@ execute_process( OUTPUT_VARIABLE llvm_version OUTPUT_STRIP_TRAILING_WHITESPACE) -function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_suffix) +function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_suffix exceptions) if(${target} MATCHES threads) set(pic OFF) set(target_flags -pthread) @@ -251,7 +266,9 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_ --sysroot ${wasi_sysroot} -resource-dir ${wasi_resource_dir}) - if (WASI_SDK_EXCEPTIONS) + set(exnsuffix "") + + if (exceptions) # TODO: lots of builds fail with shared libraries and `-fPIC`. Looks like # things are maybe changing in llvm/llvm-project#159143 but otherwise I'm at # least not really sure what the state of shared libraries and exceptions @@ -260,6 +277,13 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_ set(pic OFF) set(runtimes "libunwind;${runtimes}") list(APPEND extra_flags -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) + if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL") + set(exnsuffix "/eh") + endif() + else() + if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL") + set(exnsuffix "/noeh") + endif() endif() # The `wasm32-wasi` target is deprecated in clang, so ignore the deprecation @@ -279,7 +303,7 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_ ${default_cmake_args} # Ensure headers are installed in a target-specific path instead of a # target-generic path. - -DCMAKE_INSTALL_INCLUDEDIR=${wasi_sysroot}/include/${target} + -DCMAKE_INSTALL_INCLUDEDIR=${wasi_sysroot}/include/${target}${exnsuffix} -DCMAKE_STAGING_PREFIX=${wasi_sysroot} -DCMAKE_POSITION_INDEPENDENT_CODE=${pic} -DLIBCXX_ENABLE_THREADS:BOOL=ON @@ -288,20 +312,20 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_ -DLIBCXX_HAS_WIN32_THREAD_API:BOOL=OFF -DLLVM_COMPILER_CHECKED=ON -DLIBCXX_ENABLE_SHARED:BOOL=${pic} - -DLIBCXX_ENABLE_EXCEPTIONS:BOOL=${WASI_SDK_EXCEPTIONS} + -DLIBCXX_ENABLE_EXCEPTIONS:BOOL=${exceptions} -DLIBCXX_ENABLE_FILESYSTEM:BOOL=ON -DLIBCXX_ENABLE_ABI_LINKER_SCRIPT:BOOL=OFF -DLIBCXX_CXX_ABI=libcxxabi -DLIBCXX_HAS_MUSL_LIBC:BOOL=OFF -DLIBCXX_ABI_VERSION=2 - -DLIBCXXABI_ENABLE_EXCEPTIONS:BOOL=${WASI_SDK_EXCEPTIONS} + -DLIBCXXABI_ENABLE_EXCEPTIONS:BOOL=${exceptions} -DLIBCXXABI_ENABLE_SHARED:BOOL=${pic} -DLIBCXXABI_SILENT_TERMINATE:BOOL=ON -DLIBCXXABI_ENABLE_THREADS:BOOL=ON -DLIBCXXABI_HAS_PTHREAD_API:BOOL=ON -DLIBCXXABI_HAS_EXTERNAL_THREAD_API:BOOL=OFF -DLIBCXXABI_HAS_WIN32_THREAD_API:BOOL=OFF - -DLIBCXXABI_USE_LLVM_UNWINDER:BOOL=${WASI_SDK_EXCEPTIONS} + -DLIBCXXABI_USE_LLVM_UNWINDER:BOOL=${exceptions} -DLIBUNWIND_ENABLE_SHARED:BOOL=${pic} -DLIBUNWIND_ENABLE_THREADS:BOOL=ON -DLIBUNWIND_USE_COMPILER_RT:BOOL=ON @@ -310,9 +334,9 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_ -DCMAKE_C_FLAGS=${extra_cflags} -DCMAKE_ASM_FLAGS=${extra_cflags} -DCMAKE_CXX_FLAGS=${extra_cxxflags} - -DLIBCXX_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix} - -DLIBCXXABI_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix} - -DLIBUNWIND_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix} + -DLIBCXX_LIBDIR_SUFFIX=/${target}${exnsuffix}${extra_libdir_suffix} + -DLIBCXXABI_LIBDIR_SUFFIX=/${target}${exnsuffix}${extra_libdir_suffix} + -DLIBUNWIND_LIBDIR_SUFFIX=/${target}${exnsuffix}${extra_libdir_suffix} -DLIBCXX_INCLUDE_TESTS=OFF -DLIBCXX_INCLUDE_BENCHMARKS=OFF @@ -327,21 +351,39 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_ USES_TERMINAL_CONFIGURE ON USES_TERMINAL_BUILD ON USES_TERMINAL_INSTALL ON + USES_TERMINAL_PATCH ON PATCH_COMMAND ${CMAKE_COMMAND} -E chdir .. bash -c "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-168449.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-168449.patch -R --check" COMMAND ${CMAKE_COMMAND} -E chdir .. bash -c "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-186054.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-186054.patch -R --check" + COMMAND + ${CMAKE_COMMAND} -E chdir .. bash -c + "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185770.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185770.patch -R --check" ) + add_dependencies(libcxx-${target} libcxx-${target}${target_suffix}-build) endfunction() -function(define_libcxx target) - define_libcxx_sub(${target} "" "" "") - if(WASI_SDK_LTO) +function(define_libcxx_and_lto target target_suffix exceptions) + define_libcxx_sub(${target} "${target_suffix}" "" "" ${exceptions}) + if (WASI_SDK_LTO) # Note: clang knows this /llvm-lto/${llvm_version} convention. # https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/clang/lib/Driver/ToolChains/WebAssembly.cpp#L204-L210 - define_libcxx_sub(${target} "-lto" "-flto=full" "/llvm-lto/${llvm_version}") + define_libcxx_sub(${target} ${target_suffix}-lto "-flto=full" "/llvm-lto/${llvm_version}" ${exceptions}) + endif() +endfunction() + +function(define_libcxx target) + add_custom_target(libcxx-${target}) + + if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL") + define_libcxx_and_lto(${target} "" OFF) + define_libcxx_and_lto(${target} "-exn" ON) + elseif(WASI_SDK_EXCEPTIONS STREQUAL "ON") + define_libcxx_and_lto(${target} "" ON) + else() + define_libcxx_and_lto(${target} "" OFF) endif() # As of this writing, `clang++` will ignore the target-specific include dirs @@ -349,8 +391,7 @@ function(define_libcxx target) add_custom_target(libcxx-${target}-extra-dir COMMAND ${CMAKE_COMMAND} -E make_directory ${wasi_sysroot}/include/c++/v1 COMMENT "creating libcxx-specific header file folder") - add_custom_target(libcxx-${target} - DEPENDS libcxx-${target}-build $<$:libcxx-${target}-lto-build> libcxx-${target}-extra-dir) + add_dependencies(libcxx-${target} libcxx-${target}-extra-dir) endfunction() foreach(target IN LISTS WASI_SDK_TARGETS) diff --git a/cmake/wasi-sdk-toolchain.cmake b/cmake/wasi-sdk-toolchain.cmake index 5468d930a..664746a70 100644 --- a/cmake/wasi-sdk-toolchain.cmake +++ b/cmake/wasi-sdk-toolchain.cmake @@ -247,6 +247,9 @@ ExternalProject_Add(llvm-build USES_TERMINAL_CONFIGURE ON USES_TERMINAL_BUILD ON USES_TERMINAL_INSTALL ON + PATCH_COMMAND + ${CMAKE_COMMAND} -E chdir .. bash -c + "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185775.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185775.patch -R --check" ) add_custom_target(build ALL DEPENDS llvm-build) diff --git a/src/llvm-pr-168449.patch b/src/llvm-pr-168449.patch index a0da1d8d4..f5b873844 100644 --- a/src/llvm-pr-168449.patch +++ b/src/llvm-pr-168449.patch @@ -1,22 +1,17 @@ -diff --git a/libunwind/src/assembly.h b/libunwind/src/assembly.h -index f8e83e138eff..c5097d25b0c6 100644 ---- a/libunwind/src/assembly.h -+++ b/libunwind/src/assembly.h -@@ -249,6 +249,9 @@ aliasname: \ - #define WEAK_ALIAS(name, aliasname) - #define NO_EXEC_STACK_DIRECTIVE - -+#elif defined(__wasm__) -+#define NO_EXEC_STACK_DIRECTIVE -+ - // clang-format on - #else - +From 852c8a2ebc0fdb1e781591e3e6e08d3a539bcfc3 Mon Sep 17 00:00:00 2001 +From: Yerzhan Zhamashev +Date: Wed, 21 Jan 2026 16:50:41 +0200 +Subject: [PATCH] libunwind: exclude __declspec from wasm build + +--- + libunwind/src/config.h | 3 ++- + 1 file changed, 2 insertions(+), 1 deletion(-) + diff --git a/libunwind/src/config.h b/libunwind/src/config.h -index deb5a4d4d73d..23c9f012cbcf 100644 +index f017403fa2234..6014a37e27212 100644 --- a/libunwind/src/config.h +++ b/libunwind/src/config.h -@@ -66,7 +66,8 @@ +@@ -75,7 +75,8 @@ #define _LIBUNWIND_EXPORT #define _LIBUNWIND_HIDDEN #else diff --git a/src/llvm-pr-185770.patch b/src/llvm-pr-185770.patch new file mode 100644 index 000000000..2d47923ef --- /dev/null +++ b/src/llvm-pr-185770.patch @@ -0,0 +1,105 @@ +From d702761d9135ebbb83590d4dd1323be433701ebd Mon Sep 17 00:00:00 2001 +From: Alex Crichton +Date: Tue, 10 Mar 2026 15:49:55 -0700 +Subject: [PATCH] [WebAssembly] Move __cpp_exception to libunwind + +The `__cpp_exception` symbol is now defined in libunwind instead of +compiler-rt. This is moved for a few reasons, but the primary reason is +that compiler-rt is linked duplicate-ly into all shared objects meaning +that it's not suitable for define-once symbols such as +`__cpp_exception`. By moving the definition to the user of the symbol, +libunwind itself, that guarantees that the symbol should be defined +exactly once and only when appropriate. A secondary reason for this +movement is that it avoids the need to compile compiler-rt twice: once +with exception and once without, and instead the same build can be used +for both exceptions-and-not. +--- + compiler-rt/lib/builtins/CMakeLists.txt | 1 - + .../lib/builtins/wasm/__cpp_exception.S | 26 ------------------- + libunwind/src/Unwind-wasm.c | 15 +++++++++++ + .../compiler-rt/lib/builtins/sources.gni | 1 - + 4 files changed, 15 insertions(+), 28 deletions(-) + delete mode 100644 compiler-rt/lib/builtins/wasm/__cpp_exception.S + +diff --git a/compiler-rt/lib/builtins/CMakeLists.txt b/compiler-rt/lib/builtins/CMakeLists.txt +index 6c27f6d4d529e..f0570a9092f40 100644 +--- a/compiler-rt/lib/builtins/CMakeLists.txt ++++ b/compiler-rt/lib/builtins/CMakeLists.txt +@@ -891,7 +891,6 @@ set(s390x_SOURCES + + set(wasm_SOURCES + wasm/__c_longjmp.S +- wasm/__cpp_exception.S + ${GENERIC_TF_SOURCES} + ${GENERIC_SOURCES} + ) +diff --git a/compiler-rt/lib/builtins/wasm/__cpp_exception.S b/compiler-rt/lib/builtins/wasm/__cpp_exception.S +deleted file mode 100644 +index 0496e1dbf6158..0000000000000 +--- a/compiler-rt/lib/builtins/wasm/__cpp_exception.S ++++ /dev/null +@@ -1,26 +0,0 @@ +-//===-- __cpp_exception.S - Implement __cpp_exception ---------------------===// +-// +-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +-// See https://llvm.org/LICENSE.txt for license information. +-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +-// +-//===----------------------------------------------------------------------===// +-// +-// This file implements __cpp_exception which LLVM uses to implement exception +-// handling when Wasm EH is enabled. +-// +-//===----------------------------------------------------------------------===// +- +-#ifdef __wasm_exception_handling__ +- +-#ifdef __wasm64__ +-#define PTR i64 +-#else +-#define PTR i32 +-#endif +- +-.globl __cpp_exception +-.tagtype __cpp_exception PTR +-__cpp_exception: +- +-#endif // !__wasm_exception_handling__ +diff --git a/libunwind/src/Unwind-wasm.c b/libunwind/src/Unwind-wasm.c +index 2f4498c3f3989..c0ca9b775d244 100644 +--- a/libunwind/src/Unwind-wasm.c ++++ b/libunwind/src/Unwind-wasm.c +@@ -69,6 +69,21 @@ _Unwind_RaiseException(_Unwind_Exception *exception_object) { + __builtin_wasm_throw(0, exception_object); + } + ++// Define the `__cpp_exception` symbol which `__builtin_wasm_throw` above will ++// reference. This is defined here in `libunwind` as the single canonical ++// definition for this API and it's required for users to ensure that there's ++// only one copy of `libunwind` within a wasm module to ensure this is only ++// defined once and exactly once. ++__asm__(".globl __cpp_exception\n" ++#if defined(__wasm32__) ++ ".tagtype __cpp_exception i32\n" ++#elif defined(__wasm64__) ++ ".tagtype __cpp_exception i64\n" ++#else ++#error "Unsupported Wasm architecture" ++#endif ++ "__cpp_exception:\n"); ++ + /// Called by __cxa_end_catch. + _LIBUNWIND_EXPORT void + _Unwind_DeleteException(_Unwind_Exception *exception_object) { +diff --git a/llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni b/llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni +index 2ac71aa8e8367..c9eeede16e3eb 100644 +--- a/llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni ++++ b/llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni +@@ -539,7 +539,6 @@ if (current_cpu == "ve") { + if (current_cpu == "wasm") { + builtins_sources += [ + "wasm/__c_longjmp.S", +- "wasm/__cpp_exception.S", + ] + } + diff --git a/src/llvm-pr-185775.patch b/src/llvm-pr-185775.patch new file mode 100644 index 000000000..f1fdb6a57 --- /dev/null +++ b/src/llvm-pr-185775.patch @@ -0,0 +1,152 @@ +From 0e36e8f304cd5f3997916f5d85201bb17e340337 Mon Sep 17 00:00:00 2001 +From: Alex Crichton +Date: Tue, 10 Mar 2026 16:14:36 -0700 +Subject: [PATCH] [WebAssembly] Clang support for exception-based lookup paths + +This commit is an attempt to make progress on WebAssembly/wasi-sdk#565 +where with wasi-sdk I'd like to ship a single toolchain which is +capable of building binaries both with C++ exceptions and without. This +means that there can't be a single set of precompiled libraries that are +used because one set of libraries is wrong for the other mode. The +support added here is to use `-fwasm-exceptions` to automatically select +a lookup path in the sysroot. The intention is then that wasi-sdk will +ship both a "eh" set of C++ libraries as well as a "noeh" set of C++ +libraries too. Clang will automatically select the correct one based on +compilation flags which means that the final distribution will be able +to build both binaries with exceptions and without. +--- + clang/lib/Driver/ToolChains/WebAssembly.cpp | 51 ++++++++++++++------- + clang/test/Driver/wasm-toolchain.cpp | 35 ++++++++++++++ + 2 files changed, 70 insertions(+), 16 deletions(-) + +diff --git a/clang/lib/Driver/ToolChains/WebAssembly.cpp b/clang/lib/Driver/ToolChains/WebAssembly.cpp +index b5fa5760a46a0..e532ef0743cc2 100644 +--- a/clang/lib/Driver/ToolChains/WebAssembly.cpp ++++ b/clang/lib/Driver/ToolChains/WebAssembly.cpp +@@ -34,6 +34,15 @@ std::string WebAssembly::getMultiarchTriple(const Driver &D, + TargetTriple.getOSAndEnvironmentName()).str(); + } + ++/// Returns a directory name in which separate objects compile with/without ++/// exceptions may lie. This is used both for `#include` paths as well as lib ++/// paths. ++static std::string GetCXXExceptionsDir(const ArgList &DriverArgs) { ++ if (DriverArgs.getLastArg(options::OPT_fwasm_exceptions)) ++ return "eh"; ++ return "noeh"; ++} ++ + std::string wasm::Linker::getLinkerPath(const ArgList &Args) const { + const ToolChain &ToolChain = getToolChain(); + if (const Arg* A = Args.getLastArg(options::OPT_fuse_ld_EQ)) { +@@ -230,12 +239,16 @@ void wasm::Linker::ConstructJob(Compilation &C, const JobAction &JA, + } + } + +-/// Given a base library directory, append path components to form the +-/// LTO directory. +-static std::string AppendLTOLibDir(const std::string &Dir) { ++/// Append `Dir` to `Paths`, but also include the LTO directories before that if ++/// LTO is eanbled. ++static void AppendLibDirAndLTODir(ToolChain::path_list &Paths, const Driver &D, ++ const std::string &Dir) { ++ if (D.isUsingLTO()) { + // The version allows the path to be keyed to the specific version of + // LLVM in used, as the bitcode format is not stable. +- return Dir + "/llvm-lto/" LLVM_VERSION_STRING; ++ Paths.push_back(Dir + "/llvm-lto/" LLVM_VERSION_STRING); ++ } ++ Paths.push_back(Dir); + } + + WebAssembly::WebAssembly(const Driver &D, const llvm::Triple &Triple, +@@ -256,14 +269,15 @@ WebAssembly::WebAssembly(const Driver &D, const llvm::Triple &Triple, + } else { + const std::string MultiarchTriple = + getMultiarchTriple(getDriver(), Triple, SysRoot); +- if (D.isUsingLTO()) { +- // For LTO, enable use of lto-enabled sysroot libraries too, if available. +- // Note that the directory is keyed to the LLVM revision, as LLVM's +- // bitcode format is not stable. +- auto Dir = AppendLTOLibDir(SysRoot + "/lib/" + MultiarchTriple); +- getFilePaths().push_back(Dir); +- } +- getFilePaths().push_back(SysRoot + "/lib/" + MultiarchTriple); ++ std::string TripleLibDir = SysRoot + "/lib/" + MultiarchTriple; ++ // Allow sysroots to segregate objects based on whether exceptions are ++ // enabled or not. This is intended to assist with distribution of pre-built ++ // sysroots that contain libraries that are capable of producing binaries ++ // entirely without exception-handling instructions but also with if ++ // exceptions are enabled, for example. ++ AppendLibDirAndLTODir(getFilePaths(), D, ++ TripleLibDir + "/" + GetCXXExceptionsDir(Args)); ++ AppendLibDirAndLTODir(getFilePaths(), D, TripleLibDir); + } + + if (getTriple().getOS() == llvm::Triple::WASI) { +@@ -580,13 +594,18 @@ void WebAssembly::addLibCxxIncludePaths( + if (Version.empty()) + return; + +- // First add the per-target include path if the OS is known. ++ // First add the per-target-per-exception-handling include path if the ++ // OS is known, then second add the per-target include path. + if (IsKnownOs) { +- std::string TargetDir = LibPath + "/" + MultiarchTriple + "/c++/" + Version; +- addSystemInclude(DriverArgs, CC1Args, TargetDir); ++ std::string TargetDir = LibPath + "/" + MultiarchTriple; ++ std::string Suffix = "/c++/" + Version; ++ addSystemInclude(DriverArgs, CC1Args, ++ TargetDir + "/" + GetCXXExceptionsDir(DriverArgs) + ++ Suffix); ++ addSystemInclude(DriverArgs, CC1Args, TargetDir + Suffix); + } + +- // Second add the generic one. ++ // Third add the generic one. + addSystemInclude(DriverArgs, CC1Args, LibPath + "/c++/" + Version); + } + +diff --git a/clang/test/Driver/wasm-toolchain.cpp b/clang/test/Driver/wasm-toolchain.cpp +index d7ff76cedfd10..30a2f9397e3f4 100644 +--- a/clang/test/Driver/wasm-toolchain.cpp ++++ b/clang/test/Driver/wasm-toolchain.cpp +@@ -111,3 +111,38 @@ + // COMPILE_WALI_STDCXX: "-internal-isystem" "[[RESOURCE_DIR]]{{(/|\\\\)}}include" + // COMPILE_WALI_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-linux-muslwali" + // COMPILE_WALI_STDCXX: "-internal-isystem" "[[SYSROOT:[^"]+]]/include" ++ ++// With a known OS "eh" and "noeh" directories are added to enable segregating ++// object built with/without exception-handling ++ ++// RUN: %clangxx -### --target=wasm32-wasi --stdlib=libc++ %s 2>&1 \ ++// RUN: --sysroot=%S/Inputs/basic_linux_libcxx_tree/usr \ ++// RUN: | FileCheck -check-prefix=EH_OFF %s ++// EH_OFF: "-cc1" ++// EH_OFF: "-isysroot" "[[SYSROOT:[^"]+]]" ++// EH_OFF: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi/noeh/c++/v1" ++// EH_OFF-NOT: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi/eh/c++/v1" ++// EH_OFF: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi/c++/v1" ++// EH_OFF: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/c++/v1" ++// EH_OFF: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi" ++// EH_OFF: "-internal-isystem" "[[SYSROOT:[^"]+]]/include" ++ ++// RUN: %clangxx -### --target=wasm32-wasi -fwasm-exceptions --stdlib=libc++ %s 2>&1 \ ++// RUN: --sysroot=%S/Inputs/basic_linux_libcxx_tree/usr \ ++// RUN: | FileCheck -check-prefix=EH_ON %s ++// EH_ON: "-cc1" ++// EH_ON: "-isysroot" "[[SYSROOT:[^"]+]]" ++// EH_ON: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi/eh/c++/v1" ++// EH_ON-NOT: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi/noeh/c++/v1" ++// EH_ON: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi/c++/v1" ++// EH_ON: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/c++/v1" ++// EH_ON: "-internal-isystem" "[[SYSROOT:[^"]+]]/include/wasm32-wasi" ++// EH_ON: "-internal-isystem" "[[SYSROOT:[^"]+]]/include" ++// ++// RUN: %clangxx -### --target=wasm32-wasi --sysroot=/foo --stdlib=libc++ %s 2>&1 \ ++// RUN: | FileCheck -check-prefix=EH_OFF_LINK %s ++// EH_OFF_LINK: wasm-ld{{.*}}" "-L/foo/lib/wasm32-wasi/noeh" "-L/foo/lib/wasm32-wasi" ++// ++// RUN: %clangxx -### --target=wasm32-wasi -fwasm-exceptions --sysroot=/foo --stdlib=libc++ %s 2>&1 \ ++// RUN: | FileCheck -check-prefix=EH_ON_LINK %s ++// EH_ON_LINK: wasm-ld{{.*}}" "-L/foo/lib/wasm32-wasi/eh" "-L/foo/lib/wasm32-wasi" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c573aea65..9bb5fc2eb 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -67,9 +67,9 @@ function(add_testcase runwasi test) # Apply language-specific options and dependencies. if(test MATCHES "cc$") - if(WASI_SDK_EXCEPTIONS) + if(NOT (WASI_SDK_EXCEPTIONS STREQUAL "OFF")) target_compile_options(${target_name} PRIVATE -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) - target_link_options(${target_name} PRIVATE -lunwind) + target_link_options(${target_name} PRIVATE -fwasm-exceptions -lunwind) else() target_compile_options(${target_name} PRIVATE -fno-exceptions) endif() From a7ca86ddb11e2e4247a62d32cf3fa1c2293c743a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 10 Mar 2026 15:44:03 -0700 Subject: [PATCH 2/3] Review comments --- .github/workflows/main.yml | 4 ++-- CppExceptions.md | 6 +++--- cmake/wasi-sdk-sysroot.cmake | 6 ++++++ 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index d00dc91f2..83c2c8b22 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -172,10 +172,10 @@ jobs: - run: cargo install wasm-component-ld@0.5.21 - name: Install LLVM 22 run: | - wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - v=22 rel=$(lsb_release -cs) - sudo apt-add-repository "deb http://apt.llvm.org/$rel/ llvm-toolchain-$rel-$v main" + wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo tee /etc/apt/keyrings/llvm.asc + echo "deb [signed-by=/etc/apt/keyrings/llvm.asc] http://apt.llvm.org/$rel/ llvm-toolchain-$rel-$v main" | sudo tee /etc/apt/sources.list.d/llvm-$v.list sudo apt-get update -y && sudo apt-get install -y clang-$v lld-$v - run: | cmake -G Ninja -B build -S . \ diff --git a/CppExceptions.md b/CppExceptions.md index f51e0ffd1..1ca79bf3c 100644 --- a/CppExceptions.md +++ b/CppExceptions.md @@ -3,7 +3,7 @@ > **Note**: this documentation does not cover wasi-sdk-31, the latest version > of wasi-sdk at this time. -From wasi-sdk-32 and onwards the artifacts produced by this repository support +From wasi-sdk-33 and onwards the artifacts produced by this repository support compiling C++ code both with and without exceptions. The sysroot for wasm targets contains two copies of the C++ standard library and headers -- one with exceptions enabled and one with exceptions disabled. These are automatically @@ -62,9 +62,9 @@ wasi-sdk at this time: issues in this repository itself as well as [resolving some upstream issues](https://github.com/llvm/llvm-project/issues/188077). * Currently `-fwasm-exceptions` is a required flag to enable C++ exceptions. - It's unclear whethe `-fexceptions` should also be supported as a substitute. + It's unclear whether `-fexceptions` should also be supported as a substitute. * Currently LLVM defaults to using the legacy exception-handling proposal and this will likely change in the future. Precompiled libraries for wasi-sdk are - all built with the standard exception-handlign proposal. + all built with the standard exception-handling proposal. * Currently `-lunwind` is required when linking, but this may become automatic in the future. diff --git a/cmake/wasi-sdk-sysroot.cmake b/cmake/wasi-sdk-sysroot.cmake index b29a5f4a0..d58afd236 100644 --- a/cmake/wasi-sdk-sysroot.cmake +++ b/cmake/wasi-sdk-sysroot.cmake @@ -377,6 +377,12 @@ endfunction() function(define_libcxx target) add_custom_target(libcxx-${target}) + # For dual-mode exceptions-and-not there are two versions of libcxx which are + # compiled and placed into the sysroot. They're named slightly differently to + # have unique CMake rules. + # + # Otherwise there's only one build of libcxx and it's either got exceptions for + # it doesn't depending on configuration. if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL") define_libcxx_and_lto(${target} "" OFF) define_libcxx_and_lto(${target} "-exn" ON) From a9ad96cc09637cebab2e2bfc423e4920f4d25008 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 8 Apr 2026 11:40:18 -0500 Subject: [PATCH 3/3] Update cmake/wasi-sdk-sysroot.cmake Co-authored-by: Joel Dice --- cmake/wasi-sdk-sysroot.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/wasi-sdk-sysroot.cmake b/cmake/wasi-sdk-sysroot.cmake index d58afd236..491180ee4 100644 --- a/cmake/wasi-sdk-sysroot.cmake +++ b/cmake/wasi-sdk-sysroot.cmake @@ -381,7 +381,7 @@ function(define_libcxx target) # compiled and placed into the sysroot. They're named slightly differently to # have unique CMake rules. # - # Otherwise there's only one build of libcxx and it's either got exceptions for + # Otherwise there's only one build of libcxx and it's either got exceptions or # it doesn't depending on configuration. if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL") define_libcxx_and_lto(${target} "" OFF)