Skip to content

Commit 2f7a86e

Browse files
committed
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
1 parent 249054e commit 2f7a86e

10 files changed

Lines changed: 385 additions & 72 deletions

File tree

.github/workflows/main.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,10 +170,16 @@ jobs:
170170
- uses: ./.github/actions/checkout
171171
- uses: ./.github/actions/install-deps
172172
- run: cargo install wasm-component-ld@0.5.21
173-
- run: sudo apt-get update -y && sudo apt-get install -y clang-20 lld-20
173+
- name: Install LLVM 22
174+
run: |
175+
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add -
176+
v=22
177+
rel=$(lsb_release -cs)
178+
sudo apt-add-repository "deb http://apt.llvm.org/$rel/ llvm-toolchain-$rel-$v main"
179+
sudo apt-get update -y && sudo apt-get install -y clang-$v lld-$v
174180
- run: |
175181
cmake -G Ninja -B build -S . \
176-
-DCMAKE_C_COMPILER=/usr/lib/llvm-20/bin/clang \
182+
-DCMAKE_C_COMPILER=/usr/lib/llvm-22/bin/clang \
177183
-DCMAKE_SYSTEM_NAME=WASI \
178184
-DWASI_SDK_INCLUDE_TESTS=ON \
179185
-DWASI_SDK_CPU_CFLAGS="" \

CppExceptions.md

Lines changed: 44 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,17 @@
11
# Support for C++ Exceptions
22

3-
The released artifacts for wasi-sdk at this time do not support C++ exceptions.
4-
LLVM and Clang, however, have support for C++ exceptions in WebAssembly and this
5-
is intended to serve as documentation of the current state of affairs of using
6-
C++ exceptions. It should be noted though that the current status of C++
7-
exceptions support is not intended to be the final state of support, and this is
8-
all continuing to be iterated on over time.
3+
> **Note**: this documentation does not cover wasi-sdk-31, the latest version
4+
> of wasi-sdk at this time.
95
10-
## Building wasi-sdk with exceptions
6+
From wasi-sdk-32 and onwards the artifacts produced by this repository support
7+
compiling C++ code both with and without exceptions. The sysroot for wasm
8+
targets contains two copies of the C++ standard library and headers -- one with
9+
exceptions enabled and one with exceptions disabled. These are automatically
10+
selected based on compilation flags. This means that wasi-sdk-produced binaries
11+
can avoid using wasm exceptions entirely by disabling C++ exceptions, or C++
12+
exceptions can be enabled in which case wasm exceptions will be used.
1113

12-
When building the sysroot with wasi-sdk you can pass `-DWASI_SDK_EXCEPTIONS=ON`
13-
to enable support for C++ exceptions. For example:
14-
15-
```shell script
16-
$ cmake -G Ninja -B build/sysroot -S . \
17-
-DCMAKE_TOOLCHAIN_FILE=$path/to/wasi-sdk-p1.cmake \
18-
-DWASI_SDK_EXCEPTIONS=ON
19-
```
20-
21-
The C++ standard library will be compiled with support for exceptions for the
22-
desired targets and the resulting sysroot supports using exceptions.
14+
Currently the default is for C++ exceptions to be disabled.
2315

2416
## Compiling code with C++ exceptions
2517

@@ -36,25 +28,43 @@ This can be specified for example with:
3628

3729
```shell script
3830
$ export CFLAGS="-fwasm-exceptions -mllvm -wasm-use-legacy-eh=false"
39-
$ export LDFLAGS="-lunwind"
31+
$ export LDFLAGS="-fwasm-exceptions -lunwind"
4032
```
4133

42-
## Limitations
34+
Note that `-fwasm-exceptions` must be present when linking to select the
35+
correct C++ standard library to link.
36+
37+
## Building wasi-sdk with exceptions
4338

44-
Currently C++ exceptions support in wasi-sdk does not support shared libraries.
45-
Fixing this will require resolving some miscellaneous build issues in this
46-
repository itself.
39+
When building the sysroot with wasi-sdk you can pass `-DWASI_SDK_EXCEPTIONS=ON`
40+
to enable support for C++ exceptions. For example:
4741

48-
## Future Plans
42+
```shell script
43+
$ cmake -G Ninja -B build/sysroot -S . \
44+
-DCMAKE_TOOLCHAIN_FILE=$path/to/wasi-sdk-p1.cmake \
45+
-DWASI_SDK_EXCEPTIONS=ON
46+
```
47+
48+
The C++ standard library will be compiled with support for exceptions for the
49+
desired targets and the resulting sysroot supports using exceptions. Note that
50+
enabling C++ exceptions requires LLVM 22 or later.
51+
52+
C++ exceptions are disabled by default for local builds. With a future release
53+
of LLVM 23 the dual-sysroot nature will be on-by-default.
54+
55+
## Limitations
4956

50-
There are a few tracking issues with historical discussion about C++ exceptions
51-
support in wasi-sdk such as [#334](https://github.com/WebAssembly/wasi-sdk/issues/334)
52-
and [#565](https://github.com/WebAssembly/wasi-sdk/issues/565). The major
53-
remaining items are:
57+
There are a few known limitations/bugs/todos around exceptions support in
58+
wasi-sdk at this time:
5459

55-
* Figure out support for shared libraries.
56-
* Determine how to ship a sysroot that supports both with-and-without
57-
exceptions.
58-
* Figure out how to avoid the need for extra compiler flags when using
59-
exceptions.
60-
* Figure out if a new wasm target is warranted.
60+
* Currently C++ exceptions support in wasi-sdk does not support shared
61+
libraries. Fixing this will require resolving some miscellaneous build
62+
issues in this repository itself as well as [resolving some upstream
63+
issues](https://github.com/llvm/llvm-project/issues/188077).
64+
* Currently `-fwasm-exceptions` is a required flag to enable C++ exceptions.
65+
It's unclear whethe `-fexceptions` should also be supported as a substitute.
66+
* Currently LLVM defaults to using the legacy exception-handling proposal and
67+
this will likely change in the future. Precompiled libraries for wasi-sdk are
68+
all built with the standard exception-handlign proposal.
69+
* Currently `-lunwind` is required when linking, but this may become automatic
70+
in the future.

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -209,8 +209,8 @@ disabled in a configure step before building with WASI SDK.
209209

210210
## Notable Limitations
211211

212-
* C++ exceptions are disabled by default. For more information see
213-
[CppExceptions.md].
212+
* C++ exceptions are disabled by default and require extra configuration to get
213+
working, see [CppExceptions.md].
214214
* C `setjmp`/`longjmp` require some extra configuration to get working, see
215215
[SetjmpLongjmp.md].
216216
* Most targets do not support spawning a thread. Experimental support for

ci/build.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ cmake -G Ninja -B $build_dir/sysroot -S . \
3737
-DCMAKE_C_COMPILER_WORKS=ON \
3838
-DCMAKE_CXX_COMPILER_WORKS=ON \
3939
-DWASI_SDK_INCLUDE_TESTS=ON \
40+
-DWASI_SDK_EXCEPTIONS=DUAL \
4041
"-DCMAKE_INSTALL_PREFIX=$build_dir/install"
4142
ninja -C $build_dir/sysroot install dist -v
4243

cmake/wasi-sdk-sysroot.cmake

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,28 @@ message(STATUS "Found executable for `ar`: ${CMAKE_AR}")
2020

2121
find_program(MAKE make REQUIRED)
2222

23+
set(EXCEPTIONS_DEFAULT "OFF")
24+
if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 23.0.0)
25+
set(EXCEPTIONS_DEFAULT "DUAL")
26+
endif()
27+
2328
option(WASI_SDK_DEBUG_PREFIX_MAP "Pass `-fdebug-prefix-map` for built artifacts" ON)
2429
option(WASI_SDK_INCLUDE_TESTS "Whether or not to build tests by default" OFF)
2530
option(WASI_SDK_INSTALL_TO_CLANG_RESOURCE_DIR "Whether or not to modify the compiler's resource directory" OFF)
2631
option(WASI_SDK_LTO "Whether or not to build LTO assets" ON)
27-
option(WASI_SDK_EXCEPTIONS "Whether or not C++ exceptions are enabled" OFF)
32+
set(WASI_SDK_EXCEPTIONS "${EXCEPTIONS_DEFAULT}" CACHE STRING "Whether or not C++ exceptions are enabled")
2833
set(WASI_SDK_CPU_CFLAGS "-mcpu=lime1" CACHE STRING "CFLAGS to specify wasm features to enable")
2934

35+
if ((WASI_SDK_EXCEPTIONS STREQUAL "DUAL") OR (WASI_SDK_EXCEPTIONS STREQUAL "ON"))
36+
if(CMAKE_C_COMPILER_VERSION VERSION_LESS 22.0.0)
37+
message(FATAL_ERROR "enabling C++ exceptions requires Clang 22 or later")
38+
endif()
39+
elseif(WASI_SDK_EXCEPTIONS STREQUAL "OFF")
40+
# No extra validation needed
41+
else()
42+
message(FATAL_ERROR "unknown WASI_SDK_EXCEPTIONS value ${WASI_SDK_EXCEPTIONS}, expected one of: OFF, ON, DUAL")
43+
endif()
44+
3045
set(wasi_tmp_install ${CMAKE_CURRENT_BINARY_DIR}/install)
3146
set(wasi_sysroot ${wasi_tmp_install}/share/wasi-sysroot)
3247
set(wasi_resource_dir ${wasi_tmp_install}/wasi-resource-dir)
@@ -225,7 +240,7 @@ execute_process(
225240
OUTPUT_VARIABLE llvm_version
226241
OUTPUT_STRIP_TRAILING_WHITESPACE)
227242

228-
function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_suffix)
243+
function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_suffix exceptions)
229244
if(${target} MATCHES threads)
230245
set(pic OFF)
231246
set(target_flags -pthread)
@@ -251,7 +266,9 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_
251266
--sysroot ${wasi_sysroot}
252267
-resource-dir ${wasi_resource_dir})
253268

254-
if (WASI_SDK_EXCEPTIONS)
269+
set(exnsuffix "")
270+
271+
if (exceptions)
255272
# TODO: lots of builds fail with shared libraries and `-fPIC`. Looks like
256273
# things are maybe changing in llvm/llvm-project#159143 but otherwise I'm at
257274
# 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_
260277
set(pic OFF)
261278
set(runtimes "libunwind;${runtimes}")
262279
list(APPEND extra_flags -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false)
280+
if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL")
281+
set(exnsuffix "/eh")
282+
endif()
283+
else()
284+
if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL")
285+
set(exnsuffix "/noeh")
286+
endif()
263287
endif()
264288

265289
# 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_
279303
${default_cmake_args}
280304
# Ensure headers are installed in a target-specific path instead of a
281305
# target-generic path.
282-
-DCMAKE_INSTALL_INCLUDEDIR=${wasi_sysroot}/include/${target}
306+
-DCMAKE_INSTALL_INCLUDEDIR=${wasi_sysroot}/include/${target}${exnsuffix}
283307
-DCMAKE_STAGING_PREFIX=${wasi_sysroot}
284308
-DCMAKE_POSITION_INDEPENDENT_CODE=${pic}
285309
-DLIBCXX_ENABLE_THREADS:BOOL=ON
@@ -288,20 +312,20 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_
288312
-DLIBCXX_HAS_WIN32_THREAD_API:BOOL=OFF
289313
-DLLVM_COMPILER_CHECKED=ON
290314
-DLIBCXX_ENABLE_SHARED:BOOL=${pic}
291-
-DLIBCXX_ENABLE_EXCEPTIONS:BOOL=${WASI_SDK_EXCEPTIONS}
315+
-DLIBCXX_ENABLE_EXCEPTIONS:BOOL=${exceptions}
292316
-DLIBCXX_ENABLE_FILESYSTEM:BOOL=ON
293317
-DLIBCXX_ENABLE_ABI_LINKER_SCRIPT:BOOL=OFF
294318
-DLIBCXX_CXX_ABI=libcxxabi
295319
-DLIBCXX_HAS_MUSL_LIBC:BOOL=OFF
296320
-DLIBCXX_ABI_VERSION=2
297-
-DLIBCXXABI_ENABLE_EXCEPTIONS:BOOL=${WASI_SDK_EXCEPTIONS}
321+
-DLIBCXXABI_ENABLE_EXCEPTIONS:BOOL=${exceptions}
298322
-DLIBCXXABI_ENABLE_SHARED:BOOL=${pic}
299323
-DLIBCXXABI_SILENT_TERMINATE:BOOL=ON
300324
-DLIBCXXABI_ENABLE_THREADS:BOOL=ON
301325
-DLIBCXXABI_HAS_PTHREAD_API:BOOL=ON
302326
-DLIBCXXABI_HAS_EXTERNAL_THREAD_API:BOOL=OFF
303327
-DLIBCXXABI_HAS_WIN32_THREAD_API:BOOL=OFF
304-
-DLIBCXXABI_USE_LLVM_UNWINDER:BOOL=${WASI_SDK_EXCEPTIONS}
328+
-DLIBCXXABI_USE_LLVM_UNWINDER:BOOL=${exceptions}
305329
-DLIBUNWIND_ENABLE_SHARED:BOOL=${pic}
306330
-DLIBUNWIND_ENABLE_THREADS:BOOL=ON
307331
-DLIBUNWIND_USE_COMPILER_RT:BOOL=ON
@@ -310,9 +334,9 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_
310334
-DCMAKE_C_FLAGS=${extra_cflags}
311335
-DCMAKE_ASM_FLAGS=${extra_cflags}
312336
-DCMAKE_CXX_FLAGS=${extra_cxxflags}
313-
-DLIBCXX_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix}
314-
-DLIBCXXABI_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix}
315-
-DLIBUNWIND_LIBDIR_SUFFIX=/${target}${extra_libdir_suffix}
337+
-DLIBCXX_LIBDIR_SUFFIX=/${target}${exnsuffix}${extra_libdir_suffix}
338+
-DLIBCXXABI_LIBDIR_SUFFIX=/${target}${exnsuffix}${extra_libdir_suffix}
339+
-DLIBUNWIND_LIBDIR_SUFFIX=/${target}${exnsuffix}${extra_libdir_suffix}
316340
-DLIBCXX_INCLUDE_TESTS=OFF
317341
-DLIBCXX_INCLUDE_BENCHMARKS=OFF
318342

@@ -327,30 +351,47 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_
327351
USES_TERMINAL_CONFIGURE ON
328352
USES_TERMINAL_BUILD ON
329353
USES_TERMINAL_INSTALL ON
354+
USES_TERMINAL_PATCH ON
330355
PATCH_COMMAND
331356
${CMAKE_COMMAND} -E chdir .. bash -c
332357
"git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-168449.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-168449.patch -R --check"
333358
COMMAND
334359
${CMAKE_COMMAND} -E chdir .. bash -c
335360
"git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-186054.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-186054.patch -R --check"
361+
COMMAND
362+
${CMAKE_COMMAND} -E chdir .. bash -c
363+
"git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185770.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185770.patch -R --check"
336364
)
365+
add_dependencies(libcxx-${target} libcxx-${target}${target_suffix}-build)
337366
endfunction()
338367

339-
function(define_libcxx target)
340-
define_libcxx_sub(${target} "" "" "")
341-
if(WASI_SDK_LTO)
368+
function(define_libcxx_and_lto target target_suffix exceptions)
369+
define_libcxx_sub(${target} "${target_suffix}" "" "" ${exceptions})
370+
if (WASI_SDK_LTO)
342371
# Note: clang knows this /llvm-lto/${llvm_version} convention.
343372
# https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/clang/lib/Driver/ToolChains/WebAssembly.cpp#L204-L210
344-
define_libcxx_sub(${target} "-lto" "-flto=full" "/llvm-lto/${llvm_version}")
373+
define_libcxx_sub(${target} ${target_suffix}-lto "-flto=full" "/llvm-lto/${llvm_version}" ${exceptions})
374+
endif()
375+
endfunction()
376+
377+
function(define_libcxx target)
378+
add_custom_target(libcxx-${target})
379+
380+
if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL")
381+
define_libcxx_and_lto(${target} "" OFF)
382+
define_libcxx_and_lto(${target} "-exn" ON)
383+
elseif(WASI_SDK_EXCEPTIONS STREQUAL "ON")
384+
define_libcxx_and_lto(${target} "" ON)
385+
else()
386+
define_libcxx_and_lto(${target} "" OFF)
345387
endif()
346388

347389
# As of this writing, `clang++` will ignore the target-specific include dirs
348390
# unless this one also exists:
349391
add_custom_target(libcxx-${target}-extra-dir
350392
COMMAND ${CMAKE_COMMAND} -E make_directory ${wasi_sysroot}/include/c++/v1
351393
COMMENT "creating libcxx-specific header file folder")
352-
add_custom_target(libcxx-${target}
353-
DEPENDS libcxx-${target}-build $<$<BOOL:${WASI_SDK_LTO}>:libcxx-${target}-lto-build> libcxx-${target}-extra-dir)
394+
add_dependencies(libcxx-${target} libcxx-${target}-extra-dir)
354395
endfunction()
355396

356397
foreach(target IN LISTS WASI_SDK_TARGETS)

cmake/wasi-sdk-toolchain.cmake

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,9 @@ ExternalProject_Add(llvm-build
247247
USES_TERMINAL_CONFIGURE ON
248248
USES_TERMINAL_BUILD ON
249249
USES_TERMINAL_INSTALL ON
250+
PATCH_COMMAND
251+
${CMAKE_COMMAND} -E chdir .. bash -c
252+
"git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185775.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185775.patch -R --check"
250253
)
251254

252255
add_custom_target(build ALL DEPENDS llvm-build)

src/llvm-pr-168449.patch

Lines changed: 11 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
1-
diff --git a/libunwind/src/assembly.h b/libunwind/src/assembly.h
2-
index f8e83e138eff..c5097d25b0c6 100644
3-
--- a/libunwind/src/assembly.h
4-
+++ b/libunwind/src/assembly.h
5-
@@ -249,6 +249,9 @@ aliasname: \
6-
#define WEAK_ALIAS(name, aliasname)
7-
#define NO_EXEC_STACK_DIRECTIVE
8-
9-
+#elif defined(__wasm__)
10-
+#define NO_EXEC_STACK_DIRECTIVE
11-
+
12-
// clang-format on
13-
#else
14-
1+
From 852c8a2ebc0fdb1e781591e3e6e08d3a539bcfc3 Mon Sep 17 00:00:00 2001
2+
From: Yerzhan Zhamashev <yerzhan@novel.systems>
3+
Date: Wed, 21 Jan 2026 16:50:41 +0200
4+
Subject: [PATCH] libunwind: exclude __declspec from wasm build
5+
6+
---
7+
libunwind/src/config.h | 3 ++-
8+
1 file changed, 2 insertions(+), 1 deletion(-)
9+
1510
diff --git a/libunwind/src/config.h b/libunwind/src/config.h
16-
index deb5a4d4d73d..23c9f012cbcf 100644
11+
index f017403fa2234..6014a37e27212 100644
1712
--- a/libunwind/src/config.h
1813
+++ b/libunwind/src/config.h
19-
@@ -66,7 +66,8 @@
14+
@@ -75,7 +75,8 @@
2015
#define _LIBUNWIND_EXPORT
2116
#define _LIBUNWIND_HIDDEN
2217
#else

0 commit comments

Comments
 (0)