From 56d1fe731df07afebee4b30ceb73d034b7726aaf Mon Sep 17 00:00:00 2001 From: Joel Dice Date: Thu, 30 Jul 2026 12:56:41 -0600 Subject: [PATCH] enable exception support for 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. This includes patches for the required LLVM PR backports: - https://github.com/llvm/llvm-project/pull/209282 - https://github.com/llvm/llvm-project/pull/222747 --- cmake/wasi-sdk-sysroot.cmake | 11 +- cmake/wasi-sdk-toolchain.cmake | 8 + src/llvm-pr-209282-and-pr-222747.patch | 389 +++++++++++++++++++++++++ tests/CMakeLists.txt | 237 ++++++++++----- 4 files changed, 557 insertions(+), 88 deletions(-) create mode 100644 src/llvm-pr-209282-and-pr-222747.patch diff --git a/cmake/wasi-sdk-sysroot.cmake b/cmake/wasi-sdk-sysroot.cmake index 18770c9..fac4c72 100644 --- a/cmake/wasi-sdk-sysroot.cmake +++ b/cmake/wasi-sdk-sysroot.cmake @@ -274,7 +274,9 @@ function(define_libcxx_sub sysroot target target_suffix extra_target_flags extra set(pic OFF) set(target_flags -pthread) else() - set(pic ON) + if(${target} MATCHES "p[23]") + set(pic ON) + endif() set(target_flags "") endif() if(${target_suffix} MATCHES lto) @@ -298,12 +300,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") @@ -348,6 +344,7 @@ function(define_libcxx_sub sysroot target target_suffix extra_target_flags extra ${CMAKE_SOURCE_DIR}/src/llvm-pr-168449.patch ${CMAKE_SOURCE_DIR}/src/llvm-pr-186054.patch ${CMAKE_SOURCE_DIR}/src/llvm-undo-part-of-194317.patch + ${CMAKE_SOURCE_DIR}/src/llvm-pr-209282-and-pr-222747.patch ) list(JOIN patches " " patches) diff --git a/cmake/wasi-sdk-toolchain.cmake b/cmake/wasi-sdk-toolchain.cmake index 7c98f7f..dfffc7b 100644 --- a/cmake/wasi-sdk-toolchain.cmake +++ b/cmake/wasi-sdk-toolchain.cmake @@ -214,6 +214,11 @@ endif() list(TRANSFORM tools PREPEND --target= OUTPUT_VARIABLE build_targets) list(TRANSFORM tools PREPEND --target=install- OUTPUT_VARIABLE install_targets) +set(patches + ${CMAKE_SOURCE_DIR}/src/llvm-pr-209282-and-pr-222747.patch +) +list(JOIN patches " " patches) + ExternalProject_Add(llvm-build SOURCE_DIR "${llvm_proj_dir}/llvm" CMAKE_ARGS @@ -255,6 +260,9 @@ ExternalProject_Add(llvm-build USES_TERMINAL_CONFIGURE ON USES_TERMINAL_BUILD ON USES_TERMINAL_INSTALL ON + PATCH_COMMAND + ${CMAKE_COMMAND} -E chdir .. bash -c + "git apply ${patches} || git apply ${patches} -R --check" ) add_custom_target(build ALL DEPENDS llvm-build) diff --git a/src/llvm-pr-209282-and-pr-222747.patch b/src/llvm-pr-209282-and-pr-222747.patch new file mode 100644 index 0000000..391010e --- /dev/null +++ b/src/llvm-pr-209282-and-pr-222747.patch @@ -0,0 +1,389 @@ +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..da2d8b2599d9 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,22 @@ __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; ++ ++ struct _Unwind_LandingPadContext* context = _Unwind_GetWasmLPadContext(); ++ ++ // Reset the selector. ++ 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*)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..66d1792919ab +--- /dev/null ++++ b/libunwind/include/unwind_wasm.h +@@ -0,0 +1,28 @@ ++//===----------------------------------------------------------------------===// ++// ++// 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__ ++ ++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 ++#ifdef __cplusplus ++extern "C" ++#endif ++ struct _Unwind_LandingPadContext *_Unwind_GetWasmLPadContext(void); ++ ++#endif // __WASM_UNWIND_H__ +diff --git a/libunwind/src/Unwind-wasm.c b/libunwind/src/Unwind-wasm.c +index 2e949d005b8f..f17eeb27e97b 100644 +--- a/libunwind/src/Unwind-wasm.c ++++ b/libunwind/src/Unwind-wasm.c +@@ -19,45 +19,11 @@ + #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 struct _Unwind_LandingPadContext * ++_Unwind_GetWasmLPadContext(void) { ++ return &__wasm_lpad_context; + } + + /// Called by __cxa_throw. +diff --git a/llvm/include/llvm/IR/RuntimeLibcalls.td b/llvm/include/llvm/IR/RuntimeLibcalls.td +index d5f38b9674cd..6e393a6d5c24 100644 +--- a/llvm/include/llvm/IR/RuntimeLibcalls.td ++++ b/llvm/include/llvm/IR/RuntimeLibcalls.td +@@ -411,7 +411,7 @@ foreach MemSize = [1, 2, 4, 8, 16] in { + def UNWIND_RESUME : RuntimeLibcall; + def UNWIND_REGISTER : RuntimeLibcall; + def UNWIND_UNREGISTER : RuntimeLibcall; +-def UNWIND_CALL_PERSONALITY : RuntimeLibcall; ++def UNWIND_GET_WASM_LPAD_CONTEXT : RuntimeLibcall; + def CXA_END_CLEANUP : RuntimeLibcall; + + // Note: there are two sets of atomics libcalls; see +@@ -1681,8 +1681,8 @@ defset list SjLjExceptionHandlingLibcalls = { + def _Unwind_SjLj_Unregister : RuntimeLibcallImpl; + } + +-// Only used on wasm? +-def _Unwind_CallPersonality : RuntimeLibcallImpl; ++// Only used on wasm ++def _Unwind_GetWasmLPadContext : RuntimeLibcallImpl; + + // Used on OpenBSD + def __stack_smash_handler : RuntimeLibcallImpl; +@@ -3398,8 +3398,8 @@ def WasmSystemLibrary + : SystemRuntimeLibraryexception_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. + // + //===----------------------------------------------------------------------===// + +@@ -98,12 +86,6 @@ class WasmEHPrepareImpl { + friend class WasmEHPrepare; + + Type *LPadContextTy = nullptr; // type of 'struct _Unwind_LandingPadContext' +- GlobalVariable *LPadContextGV = nullptr; // __wasm_lpad_context +- +- // Field addresses of struct _Unwind_LandingPadContext +- Value *LPadIndexField = nullptr; // lpad_index field +- Value *LSDAField = nullptr; // lsda field +- Value *SelectorField = nullptr; // selector + + Function *ThrowF = nullptr; // wasm.throw() intrinsic + Function *LPadIndexF = nullptr; // wasm.landingpad.index() intrinsic +@@ -111,8 +93,9 @@ 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; ++ FunctionCallee GetWasmLPadContextF = ++ nullptr; // _Unwind_GetWasmLPadContext() wrapper + + bool prepareThrows(Function &F); + bool prepareEHPads(Function &F); +@@ -220,6 +203,7 @@ bool WasmEHPrepareImpl::prepareThrows(Function &F) { + bool WasmEHPrepareImpl::prepareEHPads(Function &F) { + Module &M = *F.getParent(); + IRBuilder<> IRB(F.getContext()); ++ LLVMContext &Ctx = M.getContext(); + + SmallVector CatchPads; + SmallVector CleanupPads; +@@ -235,28 +219,17 @@ 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"); + +- // __wasm_lpad_context global variable. +- // This variable should be thread local. If the target does not support TLS, +- // we depend on CoalesceFeaturesAndStripAtomics to downgrade it to +- // non-thread-local ones, in which case we don't allow this object to be +- // linked with other objects using shared memory. +- LPadContextGV = M.getOrInsertGlobal("__wasm_lpad_context", LPadContextTy); +- LPadContextGV->setThreadLocalMode(GlobalValue::GeneralDynamicTLSModel); +- +- LPadIndexField = LPadContextGV; +- LSDAField = IRB.CreateConstInBoundsGEP2_32(LPadContextTy, LPadContextGV, 0, 1, +- "lsda_gep"); +- SelectorField = IRB.CreateConstInBoundsGEP2_32(LPadContextTy, LPadContextGV, +- 0, 2, "selector_gep"); +- + // wasm.landingpad.index() intrinsic, which is to specify landingpad index + LPadIndexF = + Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_landingpad_index); +@@ -274,15 +247,30 @@ 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(); ++ ++ StringRef UnwindGetWasmLPadContextName = ++ RTLIB::RuntimeLibcallsInfo::getLibcallImplName( ++ RTLIB::impl__Unwind_GetWasmLPadContext); ++ ++ // _Unwind_GetWasmLPadContext() wrapper function ++ // ++ // We use this function to get the address of `libunwind`'s thread-local ++ // `__wasm_lpad_context` variable for the current thread. Note that we ++ // cannot, in general, access the `__wasm_lpad_context` directly here because, ++ // when the cooperative multithreading feature is enabled, direct, ++ // cross-library access to thread local variables is not supported. ++ auto *UnwindGetWasmLPadContextType = ++ FunctionType::get(PointerType::getUnqual(Ctx), {}, false); ++ GetWasmLPadContextF = M.getOrInsertFunction(UnwindGetWasmLPadContextName, ++ UnwindGetWasmLPadContextType); ++ if (Function *F = dyn_cast(GetWasmLPadContextF.getCallee())) + F->setDoesNotThrow(); + + unsigned Index = 0; +@@ -352,6 +340,15 @@ void WasmEHPrepareImpl::prepareEHPad(BasicBlock *BB, bool NeedPersonality, + } + IRB.SetInsertPoint(CatchCI->getNextNode()); + ++ Instruction *LPadContext = ++ IRB.CreateCall(GetWasmLPadContextF, {}, OperandBundleDef("funclet", FPI)); ++ ++ Value *LPadIndexField = LPadContext; ++ Value *LSDAField = IRB.CreateConstInBoundsGEP2_32(LPadContextTy, LPadContext, ++ 0, 1, "lsda_gep"); ++ Value *SelectorField = IRB.CreateConstInBoundsGEP2_32( ++ LPadContextTy, LPadContext, 0, 2, "selector_gep"); ++ + // This is to create a map of in + // SelectionDAGISel, which is to be used in EHStreamer to emit LSDA tables. + // Pseudocode: wasm.landingpad.index(Index); +@@ -367,9 +364,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 a3cc04a..2878849 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,95 +64,155 @@ 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}) - add_dependencies(${target_name} build) - - # 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) + 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}) + add_dependencies(${target_name} build) + + # 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() + + 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() + 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) + target_link_libraries(${target_name} unwind) + 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() - target_compile_options(${target_name} PRIVATE -fno-exceptions) + 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} + $ + -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()