Skip to content

Commit b2947d1

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 7755356 commit b2947d1

8 files changed

Lines changed: 339 additions & 36 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="" \

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,27 +351,44 @@ 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"
358+
COMMAND
359+
${CMAKE_COMMAND} -E chdir .. bash -c
360+
"git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185770.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185770.patch -R --check"
333361
)
362+
add_dependencies(libcxx-${target} libcxx-${target}${target_suffix}-build)
334363
endfunction()
335364

336-
function(define_libcxx target)
337-
define_libcxx_sub(${target} "" "" "")
338-
if(WASI_SDK_LTO)
365+
function(define_libcxx_and_lto target target_suffix exceptions)
366+
define_libcxx_sub(${target} "${target_suffix}" "" "" ${exceptions})
367+
if (WASI_SDK_LTO)
339368
# Note: clang knows this /llvm-lto/${llvm_version} convention.
340369
# https://github.com/llvm/llvm-project/blob/llvmorg-18.1.8/clang/lib/Driver/ToolChains/WebAssembly.cpp#L204-L210
341-
define_libcxx_sub(${target} "-lto" "-flto=full" "/llvm-lto/${llvm_version}")
370+
define_libcxx_sub(${target} ${target_suffix}-lto "-flto=full" "/llvm-lto/${llvm_version}" ${exceptions})
371+
endif()
372+
endfunction()
373+
374+
function(define_libcxx target)
375+
add_custom_target(libcxx-${target})
376+
377+
if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL")
378+
define_libcxx_and_lto(${target} "" OFF)
379+
define_libcxx_and_lto(${target} "-exn" ON)
380+
elseif(WASI_SDK_EXCEPTIONS STREQUAL "ON")
381+
define_libcxx_and_lto(${target} "" ON)
382+
else()
383+
define_libcxx_and_lto(${target} "" OFF)
342384
endif()
343385

344386
# As of this writing, `clang++` will ignore the target-specific include dirs
345387
# unless this one also exists:
346388
add_custom_target(libcxx-${target}-extra-dir
347389
COMMAND ${CMAKE_COMMAND} -E make_directory ${wasi_sysroot}/include/c++/v1
348390
COMMENT "creating libcxx-specific header file folder")
349-
add_custom_target(libcxx-${target}
350-
DEPENDS libcxx-${target}-build $<$<BOOL:${WASI_SDK_LTO}>:libcxx-${target}-lto-build> libcxx-${target}-extra-dir)
391+
add_dependencies(libcxx-${target} libcxx-${target}-extra-dir)
351392
endfunction()
352393

353394
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
@@ -182,6 +182,9 @@ ExternalProject_Add(llvm-build
182182
USES_TERMINAL_CONFIGURE ON
183183
USES_TERMINAL_BUILD ON
184184
USES_TERMINAL_INSTALL ON
185+
PATCH_COMMAND
186+
${CMAKE_COMMAND} -E chdir .. bash -c
187+
"git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185775.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-185775.patch -R --check"
185188
)
186189

187190
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

src/llvm-pr-185770.patch

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
From d702761d9135ebbb83590d4dd1323be433701ebd Mon Sep 17 00:00:00 2001
2+
From: Alex Crichton <alex@alexcrichton.com>
3+
Date: Tue, 10 Mar 2026 15:49:55 -0700
4+
Subject: [PATCH] [WebAssembly] Move __cpp_exception to libunwind
5+
6+
The `__cpp_exception` symbol is now defined in libunwind instead of
7+
compiler-rt. This is moved for a few reasons, but the primary reason is
8+
that compiler-rt is linked duplicate-ly into all shared objects meaning
9+
that it's not suitable for define-once symbols such as
10+
`__cpp_exception`. By moving the definition to the user of the symbol,
11+
libunwind itself, that guarantees that the symbol should be defined
12+
exactly once and only when appropriate. A secondary reason for this
13+
movement is that it avoids the need to compile compiler-rt twice: once
14+
with exception and once without, and instead the same build can be used
15+
for both exceptions-and-not.
16+
---
17+
compiler-rt/lib/builtins/CMakeLists.txt | 1 -
18+
.../lib/builtins/wasm/__cpp_exception.S | 26 -------------------
19+
libunwind/src/Unwind-wasm.c | 15 +++++++++++
20+
.../compiler-rt/lib/builtins/sources.gni | 1 -
21+
4 files changed, 15 insertions(+), 28 deletions(-)
22+
delete mode 100644 compiler-rt/lib/builtins/wasm/__cpp_exception.S
23+
24+
diff --git a/compiler-rt/lib/builtins/CMakeLists.txt b/compiler-rt/lib/builtins/CMakeLists.txt
25+
index 6c27f6d4d529e..f0570a9092f40 100644
26+
--- a/compiler-rt/lib/builtins/CMakeLists.txt
27+
+++ b/compiler-rt/lib/builtins/CMakeLists.txt
28+
@@ -891,7 +891,6 @@ set(s390x_SOURCES
29+
30+
set(wasm_SOURCES
31+
wasm/__c_longjmp.S
32+
- wasm/__cpp_exception.S
33+
${GENERIC_TF_SOURCES}
34+
${GENERIC_SOURCES}
35+
)
36+
diff --git a/compiler-rt/lib/builtins/wasm/__cpp_exception.S b/compiler-rt/lib/builtins/wasm/__cpp_exception.S
37+
deleted file mode 100644
38+
index 0496e1dbf6158..0000000000000
39+
--- a/compiler-rt/lib/builtins/wasm/__cpp_exception.S
40+
+++ /dev/null
41+
@@ -1,26 +0,0 @@
42+
-//===-- __cpp_exception.S - Implement __cpp_exception ---------------------===//
43+
-//
44+
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
45+
-// See https://llvm.org/LICENSE.txt for license information.
46+
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
47+
-//
48+
-//===----------------------------------------------------------------------===//
49+
-//
50+
-// This file implements __cpp_exception which LLVM uses to implement exception
51+
-// handling when Wasm EH is enabled.
52+
-//
53+
-//===----------------------------------------------------------------------===//
54+
-
55+
-#ifdef __wasm_exception_handling__
56+
-
57+
-#ifdef __wasm64__
58+
-#define PTR i64
59+
-#else
60+
-#define PTR i32
61+
-#endif
62+
-
63+
-.globl __cpp_exception
64+
-.tagtype __cpp_exception PTR
65+
-__cpp_exception:
66+
-
67+
-#endif // !__wasm_exception_handling__
68+
diff --git a/libunwind/src/Unwind-wasm.c b/libunwind/src/Unwind-wasm.c
69+
index 2f4498c3f3989..c0ca9b775d244 100644
70+
--- a/libunwind/src/Unwind-wasm.c
71+
+++ b/libunwind/src/Unwind-wasm.c
72+
@@ -69,6 +69,21 @@ _Unwind_RaiseException(_Unwind_Exception *exception_object) {
73+
__builtin_wasm_throw(0, exception_object);
74+
}
75+
76+
+// Define the `__cpp_exception` symbol which `__builtin_wasm_throw` above will
77+
+// reference. This is defined here in `libunwind` as the single canonical
78+
+// definition for this API and it's required for users to ensure that there's
79+
+// only one copy of `libunwind` within a wasm module to ensure this is only
80+
+// defined once and exactly once.
81+
+__asm__(".globl __cpp_exception\n"
82+
+#if defined(__wasm32__)
83+
+ ".tagtype __cpp_exception i32\n"
84+
+#elif defined(__wasm64__)
85+
+ ".tagtype __cpp_exception i64\n"
86+
+#else
87+
+#error "Unsupported Wasm architecture"
88+
+#endif
89+
+ "__cpp_exception:\n");
90+
+
91+
/// Called by __cxa_end_catch.
92+
_LIBUNWIND_EXPORT void
93+
_Unwind_DeleteException(_Unwind_Exception *exception_object) {
94+
diff --git a/llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni b/llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni
95+
index 2ac71aa8e8367..c9eeede16e3eb 100644
96+
--- a/llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni
97+
+++ b/llvm/utils/gn/secondary/compiler-rt/lib/builtins/sources.gni
98+
@@ -539,7 +539,6 @@ if (current_cpu == "ve") {
99+
if (current_cpu == "wasm") {
100+
builtins_sources += [
101+
"wasm/__c_longjmp.S",
102+
- "wasm/__cpp_exception.S",
103+
]
104+
}
105+

0 commit comments

Comments
 (0)