diff --git a/.github/actions/install-deps/action.yml b/.github/actions/install-deps/action.yml index 2e4967a..e3d00f8 100644 --- a/.github/actions/install-deps/action.yml +++ b/.github/actions/install-deps/action.yml @@ -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' diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0e2df4b..8811575 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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 diff --git a/CppExceptions.md b/CppExceptions.md new file mode 100644 index 0000000..7ea1e2d --- /dev/null +++ b/CppExceptions.md @@ -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. diff --git a/README.md b/README.md index 014016d..97bb4f5 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/cmake/wasi-sdk-sysroot.cmake b/cmake/wasi-sdk-sysroot.cmake index 72088d9..7be2a18 100644 --- a/cmake/wasi-sdk-sysroot.cmake +++ b/cmake/wasi-sdk-sysroot.cmake @@ -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() diff --git a/src/llvm-pr-168449.patch b/src/llvm-pr-168449.patch new file mode 100644 index 0000000..a0da1d8 --- /dev/null +++ b/src/llvm-pr-168449.patch @@ -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 diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index edbb129..a213392 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -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} $ WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/tests/general/exceptions.cc b/tests/general/exceptions.cc new file mode 100644 index 0000000..3daed98 --- /dev/null +++ b/tests/general/exceptions.cc @@ -0,0 +1,17 @@ +#include +#include + +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 +} diff --git a/tests/testcase.sh b/tests/testcase.sh index 551eec3..d2c5a8c 100755 --- a/tests/testcase.sh +++ b/tests/testcase.sh @@ -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" \