From 6683f1bc118f4645ffb556bd621fffd71bb3be64 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 30 Jul 2026 12:56:41 -0600 Subject: [PATCH 01/10] enable exception support for shared libraries This backports https://github.com/llvm/llvm-project/pull/209282 to resolve the remaining LLVM issues with Wasm exceptions and shared libraries. I've updated `tests/CMakeLists.txt` to run all p2 and p3 tests as shared libraries as well as normal executables. As usual my CMake skills are underwhelming; very open to feedback. Currently a few of the p3 tests are failing in shared library mode, apparently due to TLS-related issues. I'll work on debugging those. --- cmake/wasi-sdk-sysroot.cmake | 9 +- cmake/wasi-sdk-toolchain.cmake | 3 + src/llvm-pr-209282.patch | 300 +++++++++++++++++++++++++++++++++ tests/CMakeLists.txt | 240 ++++++++++++++++---------- 4 files changed, 460 insertions(+), 92 deletions(-) create mode 100644 src/llvm-pr-209282.patch diff --git a/cmake/wasi-sdk-sysroot.cmake b/cmake/wasi-sdk-sysroot.cmake index 459627238..721d5b8ad 100644 --- a/cmake/wasi-sdk-sysroot.cmake +++ b/cmake/wasi-sdk-sysroot.cmake @@ -291,12 +291,6 @@ function(define_libcxx_sub sysroot target target_suffix extra_target_flags extra 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 - # 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) if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL") @@ -399,6 +393,9 @@ function(define_libcxx_sub sysroot target target_suffix extra_target_flags extra COMMAND ${CMAKE_COMMAND} -E chdir .. bash -c "git apply ${CMAKE_SOURCE_DIR}/src/llvm-undo-part-of-194317.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-undo-part-of-194317.patch -R --check" + COMMAND + ${CMAKE_COMMAND} -E chdir .. bash -c + "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-209282.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-209282.patch -R --check" ) add_dependencies(libcxx-${target} libcxx-${target}${target_suffix}-build) endfunction() diff --git a/cmake/wasi-sdk-toolchain.cmake b/cmake/wasi-sdk-toolchain.cmake index c0e6443bd..e74c8486a 100644 --- a/cmake/wasi-sdk-toolchain.cmake +++ b/cmake/wasi-sdk-toolchain.cmake @@ -261,6 +261,9 @@ ExternalProject_Add(llvm-build COMMAND ${CMAKE_COMMAND} -E chdir .. bash -c "git apply ${CMAKE_SOURCE_DIR}/src/llvm-prs-208263-208332-208597.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-prs-208263-208332-208597.patch -R --check" + COMMAND + ${CMAKE_COMMAND} -E chdir .. bash -c + "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-209282.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-209282.patch -R --check" ) add_custom_target(build ALL DEPENDS llvm-build) diff --git a/src/llvm-pr-209282.patch b/src/llvm-pr-209282.patch new file mode 100644 index 000000000..d7dbe1e96 --- /dev/null +++ b/src/llvm-pr-209282.patch @@ -0,0 +1,300 @@ +diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp +index 99dfaa80be42..b0fb3b4d85d1 100644 +--- a/clang/lib/CodeGen/CGException.cpp ++++ b/clang/lib/CodeGen/CGException.cpp +@@ -265,8 +265,14 @@ const EHPersonality &EHPersonality::get(CodeGenFunction &CGF) { + + static llvm::FunctionCallee getPersonalityFn(CodeGenModule &CGM, + const EHPersonality &Personality) { +- return CGM.CreateRuntimeFunction(llvm::FunctionType::get(CGM.Int32Ty, true), +- Personality.PersonalityFn, ++ llvm::FunctionType *FTy; ++ ++ if (Personality.isWasmPersonality()) { ++ FTy = llvm::FunctionType::get(CGM.Int32Ty, {CGM.VoidPtrTy}, false); ++ } else { ++ FTy = llvm::FunctionType::get(CGM.Int32Ty, true); ++ } ++ return CGM.CreateRuntimeFunction(FTy, Personality.PersonalityFn, + llvm::AttributeList(), /*Local=*/true); + } + +diff --git a/libcxxabi/src/cxa_personality.cpp b/libcxxabi/src/cxa_personality.cpp +index c5050e46c0e8..3fdcd8a0c134 100644 +--- a/libcxxabi/src/cxa_personality.cpp ++++ b/libcxxabi/src/cxa_personality.cpp +@@ -1011,9 +1011,7 @@ static inline void get_landing_pad(__cxa_catch_temp_type &dest, + #endif + } + +-#ifdef __WASM_EXCEPTIONS__ +-_Unwind_Reason_Code __gxx_personality_wasm0 +-#elif defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__) ++#if (defined(__SEH__) && !defined(__USING_SJLJ_EXCEPTIONS__)) || defined(__WASM_EXCEPTIONS__) + static _Unwind_Reason_Code __gxx_personality_imp + #else + _LIBCXXABI_FUNC_VIS _Unwind_Reason_Code +@@ -1114,6 +1112,20 @@ __gxx_personality_seh0(PEXCEPTION_RECORD ms_exc, void *this_frame, + } + #endif + ++#ifdef __WASM_EXCEPTIONS__ ++extern "C" _LIBCXXABI_FUNC_VIS _Unwind_Reason_Code __gxx_wasm_personality_v0(void* exception_ptr) { ++ struct _Unwind_Exception* exception_object = (struct _Unwind_Exception*)exception_ptr; ++ ++ // Reset the selector. ++ __wasm_lpad_context.selector = 0; ++ ++ // Call personality function. Wasm does not have two-phase unwinding, so we ++ // only do the search phase. ++ return __gxx_personality_imp(1, _UA_SEARCH_PHASE, exception_object->exception_class, exception_object, ++ (struct _Unwind_Context*)&__wasm_lpad_context); ++} ++#endif ++ + #else + + extern "C" _Unwind_Reason_Code __gnu_unwind_frame(_Unwind_Exception*, _Unwind_Context*); +diff --git a/libunwind/include/unwind.h b/libunwind/include/unwind.h +index b1775d3a3dec..93a9d92f327d 100644 +--- a/libunwind/include/unwind.h ++++ b/libunwind/include/unwind.h +@@ -61,6 +61,10 @@ typedef struct _Unwind_Context _Unwind_Context; // opaque + #include + #endif + ++#if defined(__WASM_EXCEPTIONS__) ++#include ++#endif ++ + typedef _Unwind_Reason_Code (*_Unwind_Stop_Fn) + (int version, + _Unwind_Action actions, +diff --git a/libunwind/include/unwind_wasm.h b/libunwind/include/unwind_wasm.h +new file mode 100644 +index 000000000000..7bf3f30562bd +--- /dev/null ++++ b/libunwind/include/unwind_wasm.h +@@ -0,0 +1,27 @@ ++//===----------------------------------------------------------------------===// ++// ++// 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 ++// ++//===----------------------------------------------------------------------===// ++ ++#ifndef __WASM_UNWIND_H__ ++#define __WASM_UNWIND_H__ ++ ++#include ++ ++struct _Unwind_LandingPadContext { ++ // Input information to personality function ++ uintptr_t lpad_index; // landing pad index ++ uintptr_t lsda; // LSDA address ++ ++ // Output information computed by personality function ++ uintptr_t selector; // selector value ++}; ++ ++// Communication channel between compiler-generated user code and personality ++// function ++extern thread_local struct _Unwind_LandingPadContext __wasm_lpad_context; ++ ++#endif // __WASM_UNWIND_H__ +diff --git a/libunwind/src/Unwind-wasm.c b/libunwind/src/Unwind-wasm.c +index 2e949d005b8f..963019ea0efc 100644 +--- a/libunwind/src/Unwind-wasm.c ++++ b/libunwind/src/Unwind-wasm.c +@@ -19,46 +19,8 @@ + #include "unwind.h" + #include + +-_Unwind_Reason_Code __gxx_personality_wasm0(int version, _Unwind_Action actions, +- uint64_t exceptionClass, +- _Unwind_Exception *unwind_exception, +- _Unwind_Context *context); +- +-struct _Unwind_LandingPadContext { +- // Input information to personality function +- uintptr_t lpad_index; // landing pad index +- uintptr_t lsda; // LSDA address +- +- // Output information computed by personality function +- uintptr_t selector; // selector value +-}; +- +-// Communication channel between compiler-generated user code and personality +-// function +-thread_local struct _Unwind_LandingPadContext __wasm_lpad_context; +- +-/// Calls to this function are in landing pads in compiler-generated user code. +-/// In other EH schemes, stack unwinding is done by libunwind library, which +-/// calls the personality function for each frame it lands. On the other hand, +-/// WebAssembly stack unwinding process is performed by a VM, and the +-/// personality function cannot be called from there. So the compiler inserts a +-/// call to this function in landing pads in the user code, which in turn calls +-/// the personality function. +-_Unwind_Reason_Code _Unwind_CallPersonality(void *exception_ptr) { +- struct _Unwind_Exception *exception_object = +- (struct _Unwind_Exception *)exception_ptr; +- _LIBUNWIND_TRACE_API("_Unwind_CallPersonality(exception_object=%p)", +- (void *)exception_object); +- +- // Reset the selector. +- __wasm_lpad_context.selector = 0; +- +- // Call personality function. Wasm does not have two-phase unwinding, so we +- // only do the search phase. +- return __gxx_personality_wasm0( +- 1, _UA_SEARCH_PHASE, exception_object->exception_class, exception_object, +- (struct _Unwind_Context *)&__wasm_lpad_context); +-} ++_LIBUNWIND_EXPORT thread_local struct _Unwind_LandingPadContext ++ __wasm_lpad_context; + + /// Called by __cxa_throw. + _LIBUNWIND_EXPORT _Unwind_Reason_Code +diff --git a/llvm/include/llvm/IR/RuntimeLibcalls.td b/llvm/include/llvm/IR/RuntimeLibcalls.td +index d5f38b9674cd..68fe561bb606 100644 +--- a/llvm/include/llvm/IR/RuntimeLibcalls.td ++++ b/llvm/include/llvm/IR/RuntimeLibcalls.td +@@ -1681,9 +1681,6 @@ defset list SjLjExceptionHandlingLibcalls = { + def _Unwind_SjLj_Unregister : RuntimeLibcallImpl; + } + +-// Only used on wasm? +-def _Unwind_CallPersonality : RuntimeLibcallImpl; +- + // Used on OpenBSD + def __stack_smash_handler : RuntimeLibcallImpl; + +@@ -3399,7 +3396,6 @@ def WasmSystemLibrary + (add DefaultRuntimeLibcallImpls, Int128RTLibcalls, + CompilerRTOnlyInt64Libcalls, CompilerRTOnlyInt128Libcalls, + exp10f, exp10, +- _Unwind_CallPersonality, + emscripten_return_address, + LibcallImpls<(add __small_printf, + __small_sprintf, +diff --git a/llvm/lib/CodeGen/WasmEHPrepare.cpp b/llvm/lib/CodeGen/WasmEHPrepare.cpp +index b83bcf67716f..e4da22274e69 100644 +--- a/llvm/lib/CodeGen/WasmEHPrepare.cpp ++++ b/llvm/lib/CodeGen/WasmEHPrepare.cpp +@@ -28,7 +28,7 @@ + // wasm.landingpad.index(index); + // __wasm_lpad_context.lpad_index = index; + // __wasm_lpad_context.lsda = wasm.lsda(); +-// _Unwind_CallPersonality(exn); ++// personality_fn(exn); + // selector = __wasm_lpad_context.selector; + // ... + // +@@ -39,9 +39,9 @@ + // transfered to WebAssembly 'catch' instruction. + // + // Unwinding the stack is not done by libunwind but the VM, so the personality +-// function in libcxxabi cannot be called from libunwind during the unwinding +-// process. So after a catch instruction, we insert a call to a wrapper function +-// in libunwind that in turn calls the real personality function. ++// function (e.g. in libcxxabi) cannot be called from libunwind during the ++// unwinding process. So after a catch instruction, we insert a direct call to ++// the personality instead. + // + // In Itanium EH, if the personality function decides there is no matching catch + // clause in a call frame and no cleanup action to perform, the unwinder doesn't +@@ -49,7 +49,7 @@ + // every call frame with a catch intruction, after which the personality + // function is called from the compiler-generated user code here. + // +-// In libunwind, we have this struct that serves as a communincation channel ++// In libunwind, we have this struct that serves as a communication channel + // between the compiler-generated user code and the personality function in + // libcxxabi. + // +@@ -60,20 +60,8 @@ + // }; + // struct _Unwind_LandingPadContext __wasm_lpad_context = ...; + // +-// And this wrapper in libunwind calls the personality function. +-// +-// _Unwind_Reason_Code _Unwind_CallPersonality(void *exception_ptr) { +-// struct _Unwind_Exception *exception_obj = +-// (struct _Unwind_Exception *)exception_ptr; +-// _Unwind_Reason_Code ret = __gxx_personality_v0( +-// 1, _UA_CLEANUP_PHASE, exception_obj->exception_class, exception_obj, +-// (struct _Unwind_Context *)__wasm_lpad_context); +-// return ret; +-// } +-// + // We pass a landing pad index, and the address of LSDA for the current function +-// to the wrapper function _Unwind_CallPersonality in libunwind, and we retrieve +-// the selector after it returns. ++// to the personality function, and we retrieve the selector after it returns. + // + //===----------------------------------------------------------------------===// + +@@ -111,8 +99,7 @@ class WasmEHPrepareImpl { + Function *GetExnF = nullptr; // wasm.get.exception() intrinsic + Function *CatchF = nullptr; // wasm.catch() intrinsic + Function *GetSelectorF = nullptr; // wasm.get.ehselector() intrinsic +- FunctionCallee CallPersonalityF = +- nullptr; // _Unwind_CallPersonality() wrapper ++ FunctionCallee PersonalityF = nullptr; + + bool prepareThrows(Function &F); + bool prepareEHPads(Function &F); +@@ -235,11 +222,14 @@ bool WasmEHPrepareImpl::prepareEHPads(Function &F) { + if (CatchPads.empty() && CleanupPads.empty()) + return false; + +- if (!F.hasPersonalityFn() || +- !isScopedEHPersonality(classifyEHPersonality(F.getPersonalityFn()))) { ++ if (!F.hasPersonalityFn()) ++ return false; ++ ++ auto Personality = classifyEHPersonality(F.getPersonalityFn()); ++ ++ if (!isScopedEHPersonality(Personality)) { + report_fatal_error("Function '" + F.getName() + +- "' does not have a correct Wasm personality function " +- "'__gxx_wasm_personality_v0'"); ++ "' does not have a supported Wasm personality function"); + } + assert(F.hasPersonalityFn() && "Personality function not found"); + +@@ -274,15 +264,12 @@ bool WasmEHPrepareImpl::prepareEHPads(Function &F) { + // instruction selection. + CatchF = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_catch); + +- // FIXME: Verify this is really supported for current module. +- StringRef UnwindCallPersonalityName = +- RTLIB::RuntimeLibcallsInfo::getLibcallImplName( +- RTLIB::impl__Unwind_CallPersonality); ++ auto *PersPrototype = ++ FunctionType::get(IRB.getInt32Ty(), {IRB.getPtrTy()}, false); ++ PersonalityF = ++ M.getOrInsertFunction(getEHPersonalityName(Personality), PersPrototype); + +- // _Unwind_CallPersonality() wrapper function, which calls the personality +- CallPersonalityF = M.getOrInsertFunction(UnwindCallPersonalityName, +- IRB.getInt32Ty(), IRB.getPtrTy()); +- if (Function *F = dyn_cast(CallPersonalityF.getCallee())) ++ if (Function *F = dyn_cast(PersonalityF.getCallee())) + F->setDoesNotThrow(); + + unsigned Index = 0; +@@ -367,9 +354,9 @@ void WasmEHPrepareImpl::prepareEHPad(BasicBlock *BB, bool NeedPersonality, + // Pseudocode: __wasm_lpad_context.lsda = wasm.lsda(); + IRB.CreateStore(IRB.CreateCall(LSDAF), LSDAField); + +- // Pseudocode: _Unwind_CallPersonality(exn); +- CallInst *PersCI = IRB.CreateCall(CallPersonalityF, CatchCI, +- OperandBundleDef("funclet", CPI)); ++ // Pseudocode: personality_fn(exn); ++ CallInst *PersCI = ++ IRB.CreateCall(PersonalityF, CatchCI, OperandBundleDef("funclet", CPI)); + PersCI->setDoesNotThrow(); + + // Pseudocode: int selector = __wasm_lpad_context.selector; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e703771a..dc081b92a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,7 +1,12 @@ # Support for running tests in the `tests/{compile-only,general}` folders cmake_minimum_required(VERSION 3.22) + +# Give access to `../src/wasi-libc/cmake` local folder for `include(...)`. +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/../src/wasi-libc/cmake") + project(wasi-sdk-test) include(CTest) +include(wasm-tools) enable_testing() set(CMAKE_EXECUTABLE_SUFFIX ".wasm") @@ -28,6 +33,16 @@ set(opt_flags -O0 -O2 "-O2 -flto") add_custom_target(build-tests) +# TODO: This was copied from wasi-libc/CMakeLists.txt and should be factored out +# into its own file for reuse: +function(set_pic target) + set_target_properties(${target} PROPERTIES POSITION_INDEPENDENT_CODE TRUE) + # Windows needs an extra nudge to pass `-fPIC` + if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") + target_compile_options(${target} PRIVATE -fPIC) + endif() +endfunction() + # Registers `test` with CMake, compiling it with a number of flag combinations # and for all enabled targets. This will register up to many tests with CTest. # @@ -49,101 +64,154 @@ function(add_testcase test) cmake_parse_arguments(PARSE_ARGV 1 arg "${options}" "${oneValueArgs}" "${multiValueArgs}") foreach(target IN LISTS WASI_SDK_TARGETS) + if(target MATCHES p1) + set(link_styles static) + else() + set(link_styles static shared) + endif() + foreach(compile_flags IN LISTS opt_flags) - # Mangle the options into something appropriate for a CMake rule name - string(REGEX REPLACE " " "." target_name "${target}.${compile_flags}.${test}") - - # Add a new test executable based on `test` - add_executable(${target_name} ${test}) - add_dependencies(build-tests ${target_name}) - - # Configure all the compile options necessary. For example `--target` here - # if the target doesn't look like it's already in the name of the compiler - # as well. - if(NOT(CMAKE_C_COMPILER MATCHES ${target})) - target_compile_options(${target_name} PRIVATE --target=${target}) - target_link_options(${target_name} PRIVATE --target=${target}) - endif() - - # Apply test-specific compile options and link flags. - if(${arg_EMULATED_CLOCKS}) - target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_PROCESS_CLOCKS) - target_link_options(${target_name} PRIVATE -lwasi-emulated-process-clocks) - endif() - if(${arg_EMULATED_MMAN}) - target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_MMAN) - target_link_options(${target_name} PRIVATE -lwasi-emulated-mman) - endif() - if(${arg_EMULATED_SIGNAL}) - target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_SIGNAL) - target_link_options(${target_name} PRIVATE -lwasi-emulated-signal) - endif() - if(${arg_PRINTSCAN_LONG_DOUBLE}) - target_link_options(${target_name} PRIVATE -lc-printscan-long-double) - endif() - - # Apply language-specific options and dependencies. - if(test MATCHES "cc$") - 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 -fwasm-exceptions -lunwind) - else() - target_compile_options(${target_name} PRIVATE -fno-exceptions) + foreach(link_style IN LISTS link_styles) + # Mangle the options into something appropriate for a CMake rule name + string(REGEX REPLACE " " "." target_name "${target}.${compile_flags}.${link_style}.${test}") + + # Add a new test executable based on `test` + add_executable(${target_name} ${test}) + add_dependencies(build-tests ${target_name}) + + # Configure all the compile options necessary. For example `--target` here + # if the target doesn't look like it's already in the name of the compiler + # as well. + if(NOT(CMAKE_C_COMPILER MATCHES ${target})) + target_compile_options(${target_name} PRIVATE --target=${target}) + target_link_options(${target_name} PRIVATE --target=${target}) endif() - if(NOT WASI_SDK_TEST_HOST_TOOLCHAIN) - add_dependencies(${target_name} libcxx-${target}) + + set(so_files "${wasi_sysroot}/lib/${target}/libc.so") + + # Apply test-specific compile options and link flags. + if(${arg_EMULATED_CLOCKS}) + set(so_files ${so_files} "${wasi_sysroot}/lib/${target}/libwasi-emulated-process-clocks.so") + target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_PROCESS_CLOCKS) + target_link_options(${target_name} PRIVATE -lwasi-emulated-process-clocks) + endif() + if(${arg_EMULATED_MMAN}) + set(so_files ${so_files} "${wasi_sysroot}/lib/${target}/libwasi-emulated-mman.so") + target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_MMAN) + target_link_options(${target_name} PRIVATE -lwasi-emulated-mman) + endif() + if(${arg_EMULATED_SIGNAL}) + set(so_files ${so_files} "${wasi_sysroot}/lib/${target}/libwasi-emulated-signal.so") + target_compile_options(${target_name} PRIVATE -D_WASI_EMULATED_SIGNAL) + target_link_options(${target_name} PRIVATE -lwasi-emulated-signal) endif() - else() - if(NOT WASI_SDK_TEST_HOST_TOOLCHAIN) - add_dependencies(${target_name} wasi-libc-${target}) + if(${arg_PRINTSCAN_LONG_DOUBLE}) + target_link_options(${target_name} PRIVATE -lc-printscan-long-double) + endif() + + # Apply language-specific options and dependencies. + if(test MATCHES "cc$") + if(WASI_SDK_EXCEPTIONS STREQUAL "DUAL") + set(so_files ${so_files} + "${wasi_sysroot}/lib/${target}/eh/libc++.so" + "${wasi_sysroot}/lib/${target}/eh/libc++abi.so") + else() + set(so_files ${so_files} + "${wasi_sysroot}/lib/${target}/libc++.so" + "${wasi_sysroot}/lib/${target}/libc++abi.so") + endif() + + if(NOT (WASI_SDK_EXCEPTIONS STREQUAL "OFF")) + if(WASI_SDK_EXCEPTIONS STREQUAL "DUAL") + set(so_files ${so_files} "${wasi_sysroot}/lib/${target}/eh/libunwind.so") + else() + set(so_files ${so_files} "${wasi_sysroot}/lib/${target}/libunwind.so") + endif() + + target_compile_options(${target_name} PRIVATE -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) + target_link_options(${target_name} PRIVATE -fwasm-exceptions -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() + else() + if(NOT WASI_SDK_TEST_HOST_TOOLCHAIN) + add_dependencies(${target_name} wasi-libc-${target}) + endif() endif() - endif() - - # Apply target-specific options. - if(target MATCHES threads) - target_compile_options(${target_name} PRIVATE -pthread) - target_link_options(${target_name} PRIVATE -pthread) - endif() - - if(target STREQUAL wasm32-wasi OR target STREQUAL wasm32-wasi-threads) - target_compile_options(${target_name} PRIVATE -Wno-deprecated) - target_link_options(${target_name} PRIVATE -Wno-deprecated) - endif() - - if(arg_COMPILE_ONLY) - continue() - endif() - - set(runner ${WASI_SDK_RUNWASI}) - set(args) - if(${runner} MATCHES wasmtime) + + # Apply target-specific options. if(target MATCHES threads) - list(APPEND runner -Wshared-memory) + target_compile_options(${target_name} PRIVATE -pthread) + target_link_options(${target_name} PRIVATE -pthread) endif() - if(WASI_SDK_EXCEPTIONS) - list(APPEND runner -Wexceptions) + + if(target STREQUAL wasm32-wasi OR target STREQUAL wasm32-wasi-threads) + target_compile_options(${target_name} PRIVATE -Wno-deprecated) + target_link_options(${target_name} PRIVATE -Wno-deprecated) endif() - endif() - foreach(env IN LISTS arg_ENV) - list(APPEND runner --env ${env}) - endforeach() + if(arg_COMPILE_ONLY) + continue() + endif() - if (${arg_FSDIR}) - list(APPEND runner --dir ${CMAKE_CURRENT_SOURCE_DIR}/${test}.dir::${test}.dir) - list(APPEND args ${test}.dir) - endif() - - add_test( - NAME test-${target_name} - COMMAND - ${runner} - $ - ${args} - ) - if (arg_PASS_REGULAR_EXPRESSION) - set_tests_properties(test-${target_name} PROPERTIES PASS_REGULAR_EXPRESSION ${arg_PASS_REGULAR_EXPRESSION}) - endif() + if(link_style MATCHES shared) + set_pic(${target_name}) + # Skip wit-component when linking to manually run `wasm-tools component + # link` below. Additionally use `-shared` to wasm-ld, but notably not clang, + # to get clang to work with this as an executable but get `wasm-ld` to + # emit shared library imports. + # + # Note that `-fvisibility=default` is used to make the generated + # `__main_void` symbol from clang visible to wasi-libc itself. + target_link_options(${target_name} PRIVATE -Wl,--skip-wit-component,-shared) + target_compile_options(${target_name} PRIVATE -fvisibility=default) + add_custom_command( + TARGET ${target_name} + POST_BUILD + COMMAND + ${wasm_tools} component link + $ + ${so_files} + ${arg_SHARED_LIBS} + -o $ + ) + add_dependencies(${target_name} wasm-tools) + endif() + + set(runner ${WASI_SDK_RUNWASI}) + set(args) + if(${runner} MATCHES wasmtime) + if(target MATCHES threads) + list(APPEND runner -Wshared-memory) + endif() + if(WASI_SDK_EXCEPTIONS) + list(APPEND runner -Wexceptions) + endif() + endif() + + foreach(env IN LISTS arg_ENV) + list(APPEND runner --env ${env}) + endforeach() + + if (${arg_FSDIR}) + list(APPEND runner --dir ${CMAKE_CURRENT_SOURCE_DIR}/${test}.dir::${test}.dir) + list(APPEND args ${test}.dir) + endif() + + add_test( + NAME test-${target_name} + COMMAND + ${runner} + $ + ${args} + ) + if (arg_PASS_REGULAR_EXPRESSION) + set_tests_properties(test-${target_name} PROPERTIES PASS_REGULAR_EXPRESSION ${arg_PASS_REGULAR_EXPRESSION}) + endif() + endforeach() endforeach() endforeach() endfunction() From 239a057c048baa31bf94f2c765d19f3f01109000 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 30 Jul 2026 16:25:38 -0600 Subject: [PATCH 02/10] reorder `wasm-tools component link` arguments This helps ensure that `libc.so` is instantiated first, which matters because other libraries like `libunwind.so` have start functions which call functions like `__wasm_set_tls_base` imported from `libc.so`. Specifically, there's a dependency cycle between `libc.so` and the application `.so` because `libc.so` imports `__main_argc_argv` and the application `.so` imports a bunch of stuff from `libc.so`. `wit-component` will try to break that cycle by having the synthesized `env` module re-export functions using `call_indirect`, but that only works if the start functions of each library participating in the cycle don't call imports from each other, since the table used by the `call_indirect` calls is only initialized as part of the last `__init` module's instantiation. `wit-component` isn't smart enough inspect the code and figure all that out, but it will attempt to preserve the order libraries were specified (programatically or via the CLI), so if we specify `libc.so` first, it will be instantiated first in its "cyclical dependency" group. --- tests/CMakeLists.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index dc081b92a..515a4993a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -173,9 +173,8 @@ function(add_testcase test) POST_BUILD COMMAND ${wasm_tools} component link - $ ${so_files} - ${arg_SHARED_LIBS} + $ -o $ ) add_dependencies(${target_name} wasm-tools) From 2c043597274e1c3ce1f94d48040f19e20e865d72 Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 30 Jul 2026 18:10:16 -0600 Subject: [PATCH 03/10] update wasi-libc and wasm-component-ld --- cmake/wasi-sdk-toolchain.cmake | 2 +- src/wasi-libc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/wasi-sdk-toolchain.cmake b/cmake/wasi-sdk-toolchain.cmake index e74c8486a..c2e697790 100644 --- a/cmake/wasi-sdk-toolchain.cmake +++ b/cmake/wasi-sdk-toolchain.cmake @@ -278,7 +278,7 @@ install(DIRECTORY ${wasi_tmp_install}/bin ${wasi_tmp_install}/lib ${wasi_tmp_ins # Build logic for `wasm-component-ld` installed from Rust code. set(wasm_component_ld_root ${CMAKE_CURRENT_BINARY_DIR}/wasm-component-ld) set(wasm_component_ld ${wasm_component_ld_root}/bin/wasm-component-ld${CMAKE_EXECUTABLE_SUFFIX}) -set(wasm_component_ld_version 0.5.27) +set(wasm_component_ld_version 0.5.28) if(RUST_TARGET) set(rust_target_flag --target=${RUST_TARGET}) endif() diff --git a/src/wasi-libc b/src/wasi-libc index f3e872871..6d8745c8c 160000 --- a/src/wasi-libc +++ b/src/wasi-libc @@ -1 +1 @@ -Subproject commit f3e872871c6fb77db9727a18ab812a1a8f6e85ce +Subproject commit 6d8745c8cec1aaa82c24f2e3d7a544f7ea9c2089 From 6f6f7fa82584ff85c5bdca690a1e0f2a6ce3f12a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Aug 2026 12:24:44 -0700 Subject: [PATCH 04/10] Update wasi-libc to fix TLS --- cmake/wasi-sdk-toolchain.cmake | 2 +- src/wasi-libc | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/wasi-sdk-toolchain.cmake b/cmake/wasi-sdk-toolchain.cmake index c2e697790..2585f50b8 100644 --- a/cmake/wasi-sdk-toolchain.cmake +++ b/cmake/wasi-sdk-toolchain.cmake @@ -278,7 +278,7 @@ install(DIRECTORY ${wasi_tmp_install}/bin ${wasi_tmp_install}/lib ${wasi_tmp_ins # Build logic for `wasm-component-ld` installed from Rust code. set(wasm_component_ld_root ${CMAKE_CURRENT_BINARY_DIR}/wasm-component-ld) set(wasm_component_ld ${wasm_component_ld_root}/bin/wasm-component-ld${CMAKE_EXECUTABLE_SUFFIX}) -set(wasm_component_ld_version 0.5.28) +set(wasm_component_ld_version 0.5.29) if(RUST_TARGET) set(rust_target_flag --target=${RUST_TARGET}) endif() diff --git a/src/wasi-libc b/src/wasi-libc index 6d8745c8c..79c1a738e 160000 --- a/src/wasi-libc +++ b/src/wasi-libc @@ -1 +1 @@ -Subproject commit 6d8745c8cec1aaa82c24f2e3d7a544f7ea9c2089 +Subproject commit 79c1a738e1b3df432545666a1a579c49a82b4514 From 08582ac033595c0b2ab283d8c4f07c94dba7623a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Aug 2026 14:58:33 -0700 Subject: [PATCH 05/10] Export TLS information by default on wasip3 --- cmake/wasi-sdk-toolchain.cmake | 3 +++ src/llvm-pr-215413.patch | 14 ++++++++++++++ 2 files changed, 17 insertions(+) create mode 100644 src/llvm-pr-215413.patch diff --git a/cmake/wasi-sdk-toolchain.cmake b/cmake/wasi-sdk-toolchain.cmake index 2585f50b8..a0086b461 100644 --- a/cmake/wasi-sdk-toolchain.cmake +++ b/cmake/wasi-sdk-toolchain.cmake @@ -264,6 +264,9 @@ ExternalProject_Add(llvm-build COMMAND ${CMAKE_COMMAND} -E chdir .. bash -c "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-209282.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-209282.patch -R --check" + COMMAND + ${CMAKE_COMMAND} -E chdir .. bash -c + "git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-215413.patch || git apply ${CMAKE_SOURCE_DIR}/src/llvm-pr-215413.patch -R --check" ) add_custom_target(build ALL DEPENDS llvm-build) diff --git a/src/llvm-pr-215413.patch b/src/llvm-pr-215413.patch new file mode 100644 index 000000000..d862fb6b3 --- /dev/null +++ b/src/llvm-pr-215413.patch @@ -0,0 +1,14 @@ +diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp +index 740e8878c6e03..da5a5d6092c1e 100644 +--- a/lld/wasm/Driver.cpp ++++ b/lld/wasm/Driver.cpp +@@ -1004,6 +1004,9 @@ static void createSyntheticSymbols() { + ctx.sym.getTLSBase = + createUndefinedFunction("__wasm_get_tls_base", &getTLSBaseSignature); + ctx.sym.getTLSBase->markLive(); ++ ctx.arg.exportedSymbols.insert(ctx.sym.tlsSize->getName()); ++ ctx.arg.exportedSymbols.insert(ctx.sym.tlsAlign->getName()); ++ ctx.arg.exportedSymbols.insert(ctx.sym.initTLS->getName()); + } + } + } From dd177fdd33c3fd1964fed655e0e147f368c82f5f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Aug 2026 15:42:19 -0700 Subject: [PATCH 06/10] Skip exceptions + shared libs tests Known to not work for now --- tests/CMakeLists.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 515a4993a..1ed67805a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -130,6 +130,13 @@ function(add_testcase test) target_compile_options(${target_name} PRIVATE -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) target_link_options(${target_name} PRIVATE -fwasm-exceptions -lunwind) + + # FIXME(WebAssembly/tool-conventions#270): exceptions require + # cross-thread TLS to work right now, but that's known to not work, + # so skip this test. + if(link_style MATCHES shared) + continue() + endif() else() target_compile_options(${target_name} PRIVATE -fno-exceptions) endif() From dedc75fa4c7f21fd6bc13ab4d092637bfd074614 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Aug 2026 16:18:35 -0700 Subject: [PATCH 07/10] Adjust skipping logic --- tests/CMakeLists.txt | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1ed67805a..fdb7fbf13 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -66,6 +66,11 @@ function(add_testcase test) foreach(target IN LISTS WASI_SDK_TARGETS) if(target MATCHES p1) set(link_styles static) + elseif(test MATCHES "cc$" AND NOT (WASI_SDK_EXCEPTIONS STREQUAL "OFF")) + # FIXME(WebAssembly/tool-conventions#270): exceptions require + # cross-thread TLS to work right now, but that's known to not work, + # so skip this test. + set(link_styles static) else() set(link_styles static shared) endif() @@ -130,13 +135,6 @@ function(add_testcase test) target_compile_options(${target_name} PRIVATE -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) target_link_options(${target_name} PRIVATE -fwasm-exceptions -lunwind) - - # FIXME(WebAssembly/tool-conventions#270): exceptions require - # cross-thread TLS to work right now, but that's known to not work, - # so skip this test. - if(link_style MATCHES shared) - continue() - endif() else() target_compile_options(${target_name} PRIVATE -fno-exceptions) endif() From 118b2d00c59968cd98b98442741a0b590bb3f688 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Aug 2026 07:07:05 -0700 Subject: [PATCH 08/10] Skip some more tests with the system compiler --- tests/CMakeLists.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index fdb7fbf13..2e3460e57 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -71,6 +71,10 @@ function(add_testcase test) # cross-thread TLS to work right now, but that's known to not work, # so skip this test. set(link_styles static) + elseif(CMAKE_C_COMPILER_VERSION VERSION_LESS 23.0.0) + # LLVM 22-and-prior has issues with shared linking, so skip those tests + # when using a system compiler. + set(link_styles static) else() set(link_styles static shared) endif() From 8901470a9a33d29059352bfddbc1187b046ebc4b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Aug 2026 07:55:56 -0700 Subject: [PATCH 09/10] Try again to fix things... --- cmake/wasi-sdk-sysroot.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cmake/wasi-sdk-sysroot.cmake b/cmake/wasi-sdk-sysroot.cmake index 721d5b8ad..568be80c7 100644 --- a/cmake/wasi-sdk-sysroot.cmake +++ b/cmake/wasi-sdk-sysroot.cmake @@ -291,6 +291,10 @@ function(define_libcxx_sub sysroot target target_suffix extra_target_flags extra set(exnsuffix "") if (exceptions) + # Building with `-fPIC` requires fixes in LLVM 23-and-later. + if(CMAKE_C_COMPILER_VERSION VERSION_LESS 23.0.0) + set(pic OFF) + endif() set(runtimes "libunwind;${runtimes}") list(APPEND extra_flags -fwasm-exceptions -mllvm -wasm-use-legacy-eh=false) if (WASI_SDK_EXCEPTIONS STREQUAL "DUAL") From f76074334d4cc680545463a7012bbdc9571e8934 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Aug 2026 08:23:07 -0700 Subject: [PATCH 10/10] Disable out-of-tree testing for now --- .github/workflows/main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 419d72093..1666ac9a6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -179,8 +179,10 @@ jobs: matrix: include: - name: default - - name: exceptions - defines: -DWASI_SDK_EXCEPTIONS=ON + # TODO: re-enable this once LLVM 24 is released as wasi-sdk now relies + # on patches that only LLVM 24 has. + # - name: exceptions + # defines: -DWASI_SDK_EXCEPTIONS=ON - name: static defines: -DWASI_SDK_BUILD_SHARED=OFF steps: