Add experimental support for C++ exceptions (#590)

> **Note**: this PR is me weaving together the work of many others. For
example @yerzham's and @cpetig's work on #565 pretty much shaped this
PR. Thank you!

This commit adds initial configuration support for building wasi-sdk
with support for C++ exceptions in WebAssembly. A small test is included
which is exercised in CI at this time, but otherwise this does not
update CI to actually ship sdk builds with C++ exceptions enabled.
Instead the intention here is to make it easier to test builds with
support for C++ exceptions and centralize planning/development around
this.

The goal here is to get things compiling to the point that applications
can be compiled. I haven't thoroughly tested C++ exceptions and as
evident in this PR it still requires changes in LLVM. Some small logic
is added here to apply a `*.patch` file to LLVM to avoid needing a new
submodule fork or waiting for upstream changes. The intention is that
this `*.patch` is short-lived once the changes are officially merged
into LLVM itself.

The `*.patch` included here contains llvm/llvm-project#168449 as well as
another minor edit I found was necessary to get things compiling
locally. Given the discussion on that PR it looks like once LLVM is
updated with that merged the extra part of the `*.patch` won't be
necessary.

This PR notably does not enable shared libraries when exceptions are
enabled. I don't know enough about how things are supposed to work to be
able to fully diagnose the compilation errors I'm seeing if shared
libraries are enabled. This is something I'd hope would be fixed before
actually shipping exceptions support.

This PR then additionally folds in [this gist][gist] for various bits of
build logic to this repository itself.

Finally, this PR includes some documentation about the current status of
exceptions and links to various tracking issues too.

[gist]: https://gist.github.com/yerzham/302efcec6a2e82c1e8de4aed576ea29d
pull/592/head
Alex Crichton 1 week ago committed by GitHub
parent 3d4ea12dbe
commit 372ba0d222
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -7,7 +7,7 @@ runs:
- name: Setup `wasmtime` for tests
uses: bytecodealliance/actions/wasmtime/setup@v1
with:
version: "29.0.1"
version: "41.0.3"
- name: Install ccache, ninja (macOS)
run: brew install ccache ninja
if: runner.os == 'macOS'

@ -155,8 +155,14 @@ jobs:
key: 0-cache-${{ matrix.artifact }}-${{ github.run_id }}
build-only-sysroot:
name: Build only sysroot
name: Build only sysroot - ${{ matrix.name }}
runs-on: ubuntu-24.04
strategy:
matrix:
include:
- name: default
- name: exceptions
defines: -DWASI_SDK_EXCEPTIONS=ON
steps:
- uses: actions/checkout@v4
with:
@ -172,7 +178,8 @@ jobs:
-DWASI_SDK_INCLUDE_TESTS=ON \
-DWASI_SDK_CPU_CFLAGS="" \
-DCMAKE_C_LINKER_DEPFILE_SUPPORTED=OFF \
-DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=OFF
-DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=OFF \
${{ matrix.defines }}
- run: ninja -C build
- run: ctest --output-on-failure --parallel 10 --test-dir build/tests

@ -0,0 +1,60 @@
# 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.
## Building wasi-sdk with exceptions
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.
## Compiling code with C++ exceptions
Currently extra compilation flags are required to fully support C++ exceptions.
Without these flags programs using C++ exceptions will not work correctly:
* `-fwasm-exceptions` - needed to enable the WebAssembly exception-handling
proposal.
* `-mllvm -wasm-use-legacy-eh=false` - indicates that the standard WebAssembly
exception-handling instructions should be used.
* `-lunwind` - links in support for unwinding which C++ exceptions requires.
This can be specified for example with:
```shell script
$ export CFLAGS="-fwasm-exceptions -mllvm -wasm-use-legacy-eh=false"
$ export LDFLAGS="-lunwind"
```
## Limitations
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.
## Future Plans
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:
* 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.

@ -88,6 +88,10 @@ in compiling WebAssembly code. Supported CMake flags are:
* `-DWASI_SDK_INCLUDE_TESTS=ON` - used for building tests.
* `-DWASI_SDK_CPU_CFLAGS=..` - used to specify CFLAGS to tweak wasm features
to enable/disable. The default is `-mcpu=lime1`.
* `-DWASI_SDK_LTO=ON` - whether to enable/disable builds of LTO-capable
libraries as part of the build.
* `-DWASI_SDK_EXCEPTIONS=ON` - whether to enable/disable support for C++
exceptions, see [CppExceptions.md](./CppExceptions.md) for more information.
* `-DWASI_SDK_TEST_HOST_TOOLCHAIN=ON` - test the host toolchain's wasi-libc and
sysroot libraries, don't build or use fresh libraries for tests.
* `-DWASI_SDK_TARGETS=..` - a list of targets to build, by default all WASI

@ -24,6 +24,7 @@ option(WASI_SDK_DEBUG_PREFIX_MAP "Pass `-fdebug-prefix-map` for built artifacts"
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_CPU_CFLAGS "-mcpu=lime1" CACHE STRING "CFLAGS to specify wasm features to enable")
set(wasi_tmp_install ${CMAKE_CURRENT_BINARY_DIR}/install)
@ -89,6 +90,8 @@ function(define_compiler_rt target)
-DCOMPILER_RT_BUILD_GWP_ASAN=OFF
-DCMAKE_C_COMPILER_TARGET=${target}
-DCMAKE_C_FLAGS=${WASI_SDK_CPU_CFLAGS}
-DCMAKE_CXX_FLAGS=${WASI_SDK_CPU_CFLAGS}
-DCMAKE_ASM_FLAGS=${WASI_SDK_CPU_CFLAGS}
-DCMAKE_INSTALL_PREFIX=${wasi_resource_dir}
EXCLUDE_FROM_ALL ON
USES_TERMINAL_CONFIGURE ON
@ -231,6 +234,17 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_
--sysroot ${wasi_sysroot}
-resource-dir ${wasi_resource_dir})
if (WASI_SDK_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
# are. For now shared libraries are disabled and supporting them is left for
# a future endeavor.
set(pic OFF)
set(runtimes "libunwind;${runtimes}")
list(APPEND extra_flags -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false)
endif()
set(extra_cflags_list ${CMAKE_C_FLAGS} ${extra_flags})
list(JOIN extra_cflags_list " " extra_cflags)
set(extra_cxxflags_list ${CMAKE_CXX_FLAGS} ${extra_flags})
@ -254,14 +268,14 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_
-DLLVM_COMPILER_CHECKED=ON
-DLIBCXX_ENABLE_SHARED:BOOL=${pic}
-DLIBCXX_ENABLE_EXPERIMENTAL_LIBRARY:BOOL=OFF
-DLIBCXX_ENABLE_EXCEPTIONS:BOOL=OFF
-DLIBCXX_ENABLE_EXCEPTIONS:BOOL=${WASI_SDK_EXCEPTIONS}
-DLIBCXX_ENABLE_FILESYSTEM:BOOL=ON
-DLIBCXX_ENABLE_ABI_LINKER_SCRIPT:BOOL=OFF
-DLIBCXX_CXX_ABI=libcxxabi
-DLIBCXX_CXX_ABI_INCLUDE_PATHS=${llvm_proj_dir}/libcxxabi/include
-DLIBCXX_HAS_MUSL_LIBC:BOOL=ON
-DLIBCXX_ABI_VERSION=2
-DLIBCXXABI_ENABLE_EXCEPTIONS:BOOL=OFF
-DLIBCXXABI_ENABLE_EXCEPTIONS:BOOL=${WASI_SDK_EXCEPTIONS}
-DLIBCXXABI_ENABLE_SHARED:BOOL=${pic}
-DLIBCXXABI_SILENT_TERMINATE:BOOL=ON
-DLIBCXXABI_ENABLE_THREADS:BOOL=ON
@ -270,12 +284,18 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_
-DLIBCXXABI_BUILD_EXTERNAL_THREAD_LIBRARY:BOOL=OFF
-DLIBCXXABI_HAS_WIN32_THREAD_API:BOOL=OFF
-DLIBCXXABI_ENABLE_PIC:BOOL=${pic}
-DLIBCXXABI_USE_LLVM_UNWINDER:BOOL=OFF
-DLIBCXXABI_USE_LLVM_UNWINDER:BOOL=${WASI_SDK_EXCEPTIONS}
-DLIBUNWIND_ENABLE_SHARED:BOOL=${pic}
-DLIBUNWIND_ENABLE_THREADS:BOOL=ON
-DLIBUNWIND_USE_COMPILER_RT:BOOL=ON
-DLIBUNWIND_INCLUDE_TESTS:BOOL=OFF
-DUNIX:BOOL=ON
-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_INCLUDE_TESTS=OFF
-DLIBCXX_INCLUDE_BENCHMARKS=OFF
@ -290,6 +310,9 @@ function(define_libcxx_sub target target_suffix extra_target_flags extra_libdir_
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-168449.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-168449.patch -R --check"
)
endfunction()

@ -0,0 +1,28 @@
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
diff --git a/libunwind/src/config.h b/libunwind/src/config.h
index deb5a4d4d73d..23c9f012cbcf 100644
--- a/libunwind/src/config.h
+++ b/libunwind/src/config.h
@@ -66,7 +66,8 @@
#define _LIBUNWIND_EXPORT
#define _LIBUNWIND_HIDDEN
#else
- #if !defined(__ELF__) && !defined(__MACH__) && !defined(_AIX)
+ #if !defined(__ELF__) && !defined(__MACH__) && !defined(_AIX) && \
+ !defined(__wasm__)
#define _LIBUNWIND_EXPORT __declspec(dllexport)
#define _LIBUNWIND_HIDDEN
#else

@ -67,7 +67,12 @@ function(add_testcase runwasi test)
# Apply language-specific options and dependencies.
if(test MATCHES "cc$")
target_compile_options(${target_name} PRIVATE -fno-exceptions)
if(WASI_SDK_EXCEPTIONS)
target_compile_options(${target_name} PRIVATE -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false)
target_link_options(${target_name} PRIVATE -lunwind)
else()
target_compile_options(${target_name} PRIVATE -fno-exceptions)
endif()
if(NOT WASI_SDK_TEST_HOST_TOOLCHAIN)
add_dependencies(${target_name} libcxx-${target})
endif()
@ -84,11 +89,20 @@ function(add_testcase runwasi test)
endif()
if(runwasi)
set(runner ${runwasi})
if(${runner} MATCHES wasmtime)
if(target MATCHES threads)
set(runner "${runner} -Wshared-memory")
endif()
if(WASI_SDK_EXCEPTIONS)
set(runner "${runner} -Wexceptions")
endif()
endif()
add_test(
NAME test-${target_name}
COMMAND
bash ../testcase.sh
"${runwasi}"
${runner}
${test}
$<TARGET_FILE:${target_name}>
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR})

@ -0,0 +1,17 @@
#include <iostream>
#include <stdlib.h>
int main() {
#ifdef __wasm_exception_handling__
try {
throw std::runtime_error("An error occurred");
abort();
} catch (const std::runtime_error& e) {
// ..
return 0;
}
abort();
#else
return 0;
#endif
}

@ -46,7 +46,7 @@ fi
# Run the test, capturing stdout, stderr, and the exit status.
exit_status=0
"$runwasi" $env $dir "$wasm" $dirarg \
$runwasi $env $dir "$wasm" $dirarg \
< "$stdin" \
> "$stdout_observed" \
2> "$stderr_observed" \

Loading…
Cancel
Save