https://github.com/dicej updated https://github.com/llvm/llvm-project/pull/222747
>From aa0c3543e79615b1b3f8bb771ca4187e91eaf4b6 Mon Sep 17 00:00:00 2001 From: Joel Dice <[email protected]> Date: Tue, 8 Sep 2026 17:34:36 -0600 Subject: [PATCH] fix Wasm exceptions + coop threading + shared libraries Prior to this commit, the combination of Wasm exception handling, cooperative multithreading, and shared libraries was broken. Specifically, the code generation in `WasmEHPrepare.cpp` involved direct, cross-library access to `libunwind.so`'s thread-local `__wasm_lpad_context` variable. However, the ABI used for cooperative multithreading does not support cross-library access to thread-local variables. The solution used here is to add a new `_Unwind_GetWasmLPadContext` function to `libunwind.so` and use that to get address of the `__wasm_lpad_context` for the current thread, both in the code generated by `WasmEHPrepare.cpp` and in the `__gxx_wasm_personality_v0` function defined in `cxa_personality.cpp`. I've used this strategy unconditionally for all targets, regardless of whether cooperative multithreading and/or position-independent are enabled. If desired (e.g. for performance or code complexity reasons), I could make it conditional on both of those features being enabled and fall back to using `__wasm_lpad_context` directly otherwise. --- libcxxabi/src/cxa_personality.cpp | 8 ++- libunwind/include/unwind_wasm.h | 7 ++- libunwind/src/Unwind-wasm.c | 8 ++- llvm/include/llvm/IR/RuntimeLibcalls.td | 6 +- llvm/lib/CodeGen/WasmEHPrepare.cpp | 56 ++++++++++--------- llvm/test/CodeGen/WebAssembly/eh-lsda.ll | 15 ++--- .../CodeGen/WebAssembly/exception-legacy.ll | 2 +- llvm/test/CodeGen/WebAssembly/exception.ll | 2 +- .../CodeGen/WebAssembly/wasm-eh-prepare.ll | 14 +++-- 9 files changed, 67 insertions(+), 51 deletions(-) diff --git a/libcxxabi/src/cxa_personality.cpp b/libcxxabi/src/cxa_personality.cpp index 3fdcd8a0c13497..da2d8b2599d9d5 100644 --- a/libcxxabi/src/cxa_personality.cpp +++ b/libcxxabi/src/cxa_personality.cpp @@ -1116,13 +1116,15 @@ __gxx_personality_seh0(PEXCEPTION_RECORD ms_exc, void *this_frame, 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. - __wasm_lpad_context.selector = 0; + 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); + return __gxx_personality_imp( + 1, _UA_SEARCH_PHASE, exception_object->exception_class, exception_object, (struct _Unwind_Context*)context); } #endif diff --git a/libunwind/include/unwind_wasm.h b/libunwind/include/unwind_wasm.h index 7bf3f30562bd84..66d1792919ab02 100644 --- a/libunwind/include/unwind_wasm.h +++ b/libunwind/include/unwind_wasm.h @@ -9,8 +9,6 @@ #ifndef __WASM_UNWIND_H__ #define __WASM_UNWIND_H__ -#include <threads.h> - struct _Unwind_LandingPadContext { // Input information to personality function uintptr_t lpad_index; // landing pad index @@ -22,6 +20,9 @@ struct _Unwind_LandingPadContext { // Communication channel between compiler-generated user code and personality // function -extern thread_local struct _Unwind_LandingPadContext __wasm_lpad_context; +#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 963019ea0efc35..f17eeb27e97bb3 100644 --- a/libunwind/src/Unwind-wasm.c +++ b/libunwind/src/Unwind-wasm.c @@ -19,8 +19,12 @@ #include "unwind.h" #include <threads.h> -_LIBUNWIND_EXPORT thread_local struct _Unwind_LandingPadContext - __wasm_lpad_context; +thread_local struct _Unwind_LandingPadContext __wasm_lpad_context; + +_LIBUNWIND_EXPORT struct _Unwind_LandingPadContext * +_Unwind_GetWasmLPadContext(void) { + return &__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 69a2793d23b7ab..5803e7cfe8dd2f 100644 --- a/llvm/include/llvm/IR/RuntimeLibcalls.td +++ b/llvm/include/llvm/IR/RuntimeLibcalls.td @@ -615,7 +615,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 @@ -1886,6 +1886,9 @@ defset list<RuntimeLibcallImpl> SjLjExceptionHandlingLibcalls = { def _Unwind_SjLj_Unregister : RuntimeLibcallImpl<UNWIND_UNREGISTER>; } +// Only used on wasm +def _Unwind_GetWasmLPadContext : RuntimeLibcallImpl<UNWIND_GET_WASM_LPAD_CONTEXT>; + // Used on OpenBSD def __stack_smash_handler : RuntimeLibcallImpl<STACK_SMASH_HANDLER>; @@ -3642,6 +3645,7 @@ def WasmSystemLibrary : SystemRuntimeLibrary<isWasm, (add DefaultRuntimeLibcallImpls, Int128RTLibcalls, CompilerRTOnlyInt64Libcalls, CompilerRTOnlyInt128Libcalls, + _Unwind_GetWasmLPadContext, exp10f, exp10, emscripten_return_address, LibcallImpls<(add __small_printf, diff --git a/llvm/lib/CodeGen/WasmEHPrepare.cpp b/llvm/lib/CodeGen/WasmEHPrepare.cpp index 3ac0126c9c9a14..37cd2048dfc85b 100644 --- a/llvm/lib/CodeGen/WasmEHPrepare.cpp +++ b/llvm/lib/CodeGen/WasmEHPrepare.cpp @@ -86,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 @@ -100,6 +94,8 @@ class WasmEHPrepareImpl { Function *CatchF = nullptr; // wasm.catch() intrinsic Function *GetSelectorF = nullptr; // wasm.get.ehselector() intrinsic FunctionCallee PersonalityF = nullptr; + FunctionCallee GetWasmLPadContextF = + nullptr; // _Unwind_GetWasmLPadContext() wrapper bool prepareThrows(Function &F); bool prepareEHPads(Function &F); @@ -207,7 +203,6 @@ bool WasmEHPrepareImpl::prepareThrows(Function &F) { bool WasmEHPrepareImpl::prepareEHPads(Function &F) { Module &M = *F.getParent(); LLVMContext &Ctx = M.getContext(); - const DataLayout &DL = M.getDataLayout(); SmallVector<BasicBlock *, 16> CatchPads; SmallVector<BasicBlock *, 16> CleanupPads; @@ -234,26 +229,6 @@ bool WasmEHPrepareImpl::prepareEHPads(Function &F) { } 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 = - ConstantExpr::getGetElementPtr(DL, LPadContextTy, LPadContextGV, - {ConstantInt::get(Ctx, APInt(32, 0)), - ConstantInt::get(Ctx, APInt(32, 1))}, - GEPNoWrapFlags::inBounds()); - SelectorField = - ConstantExpr::getGetElementPtr(DL, LPadContextTy, LPadContextGV, - {ConstantInt::get(Ctx, APInt(32, 0)), - ConstantInt::get(Ctx, APInt(32, 2))}, - GEPNoWrapFlags::inBounds()); - // wasm.landingpad.index() intrinsic, which is to specify landingpad index LPadIndexF = Intrinsic::getOrInsertDeclaration(&M, Intrinsic::wasm_landingpad_index); @@ -279,6 +254,24 @@ bool WasmEHPrepareImpl::prepareEHPads(Function &F) { if (Function *F = dyn_cast<Function>(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<Function>(GetWasmLPadContextF.getCallee())) + F->setDoesNotThrow(); + unsigned Index = 0; for (auto *BB : CatchPads) { auto *CPI = cast<CatchPadInst>(BB->getFirstNonPHIIt()); @@ -346,6 +339,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 <landingpad EH label, landingpad index> in // SelectionDAGISel, which is to be used in EHStreamer to emit LSDA tables. // Pseudocode: wasm.landingpad.index(Index); diff --git a/llvm/test/CodeGen/WebAssembly/eh-lsda.ll b/llvm/test/CodeGen/WebAssembly/eh-lsda.ll index 517de9c307b493..917b3882a8e96a 100644 --- a/llvm/test/CodeGen/WebAssembly/eh-lsda.ll +++ b/llvm/test/CodeGen/WebAssembly/eh-lsda.ll @@ -66,18 +66,19 @@ try.cont: ; preds = %entry, %catch.start ; CHECK-LABEL: test1: ; In static linking, we load GCC_except_table as a constant directly. -; NOPIC: i[[PTR]].const $push[[CONTEXT:.*]]=, {{[48]}} +; NOPIC: call $push[[CONTEXT:.*]]=, _Unwind_GetWasmLPadContext +; NOPIC-NEXT: local.tee $push[[CONTEXT_LOCAL:.*]]=, $1=, $pop[[CONTEXT]] ; NOPIC-NEXT: i[[PTR]].const $push[[EXCEPT_TABLE:.*]]=, GCC_except_table1 -; NOPIC-NEXT: i[[PTR]].store __wasm_lpad_context($pop[[CONTEXT]]), $pop[[EXCEPT_TABLE]] +; NOPIC-NEXT: i[[PTR]].store {{[48]}}($pop[[CONTEXT_LOCAL]]), $pop[[EXCEPT_TABLE]] ; In case of PIC, we make GCC_except_table symbols a relative on based on ; __memory_base. -; PIC: global.get $push[[CONTEXT:.*]]=, __wasm_lpad_context@GOT -; PIC-NEXT: local.tee $push{{.*}}=, $[[CONTEXT_LOCAL:.*]]=, $pop[[CONTEXT]] -; PIC: global.get $push[[MEMORY_BASE:.*]]=, __memory_base +; PIC: global.get $[[MEMORY_BASE:.*]]=, __memory_base +; PIC-NEXT: call $push[[CONTEXT:.*]]=, _Unwind_GetWasmLPadContext +; PIC-NEXT: local.tee $push[[CONTEXT_LOCAL:.*]]=, $2=, $pop[[CONTEXT]] ; PIC-NEXT: i[[PTR]].const $push[[EXCEPT_TABLE_REL:.*]]=, GCC_except_table1@MBREL -; PIC-NEXT: i[[PTR]].add $push[[EXCEPT_TABLE:.*]]=, $pop[[MEMORY_BASE]], $pop[[EXCEPT_TABLE_REL]] -; PIC-NEXT: i[[PTR]].store {{[48]}}($[[CONTEXT_LOCAL]]), $pop[[EXCEPT_TABLE]] +; PIC-NEXT: i[[PTR]].add $push[[EXCEPT_TABLE:.*]]=, $[[MEMORY_BASE]], $pop[[EXCEPT_TABLE_REL]] +; PIC-NEXT: i[[PTR]].store {{[48]}}($pop[[CONTEXT_LOCAL]]), $pop[[EXCEPT_TABLE]] ; CHECK: .section .rodata.gcc_except_table,"",@ ; CHECK-NEXT: .p2align 2 diff --git a/llvm/test/CodeGen/WebAssembly/exception-legacy.ll b/llvm/test/CodeGen/WebAssembly/exception-legacy.ll index 0fa360116baedb..5795c4ccced418 100644 --- a/llvm/test/CodeGen/WebAssembly/exception-legacy.ll +++ b/llvm/test/CodeGen/WebAssembly/exception-legacy.ll @@ -34,7 +34,7 @@ define void @throw(ptr %p) { ; CHECK: call foo ; CHECK: catch $[[EXN:[0-9]+]]=, __cpp_exception ; CHECK: global.set __stack_pointer -; CHECK: i32.store __wasm_lpad_context +; CHECK: call $push[[CONTEXT:.*]]=, _Unwind_GetWasmLPadContext ; CHECK: call $drop=, __gxx_wasm_personality_v0, $[[EXN]] ; CHECK: block ; CHECK: br_if 0 diff --git a/llvm/test/CodeGen/WebAssembly/exception.ll b/llvm/test/CodeGen/WebAssembly/exception.ll index e2f44b4e8c2631..86df0f961eb9d6 100644 --- a/llvm/test/CodeGen/WebAssembly/exception.ll +++ b/llvm/test/CodeGen/WebAssembly/exception.ll @@ -46,7 +46,7 @@ define void @throw(ptr %p) { ; CHECK: local.set 2 ; CHECK: local.get 0 ; CHECK: global.set __stack_pointer -; CHECK: i32.store __wasm_lpad_context +; CHECK: call _Unwind_GetWasmLPadContext ; CHECK: call __gxx_wasm_personality_v0 ; CHECK: block ; CHECK: br_if 0 diff --git a/llvm/test/CodeGen/WebAssembly/wasm-eh-prepare.ll b/llvm/test/CodeGen/WebAssembly/wasm-eh-prepare.ll index 2eacaed8223f4e..5639f1aa047159 100644 --- a/llvm/test/CodeGen/WebAssembly/wasm-eh-prepare.ll +++ b/llvm/test/CodeGen/WebAssembly/wasm-eh-prepare.ll @@ -7,8 +7,6 @@ target datalayout = "e-m:e-p:32:32-i64:64-n32:64-S128" target triple = "wasm32-unknown-unknown" -; CHECK: @__wasm_lpad_context = external thread_local global { i32, ptr, i32 } - @_ZTIi = external constant ptr %struct.Temp = type { i8 } @@ -42,12 +40,15 @@ catch.start: ; preds = %catch.dispatch ; CHECK: catch.start: ; CHECK-NEXT: %[[CATCHPAD:.*]] = catchpad ; CHECK-NEXT: %[[EXN:.*]] = call ptr @llvm.wasm.catch(i32 0) +; CHECK-NEXT: %[[CONTEXT:.*]] = call ptr @_Unwind_GetWasmLPadContext() +; CHECK-NEXT: %lsda_gep = getelementptr inbounds { i32, ptr, i32 }, ptr %[[CONTEXT]], i32 0, i32 1 +; CHECK-NEXT: %selector_gep = getelementptr inbounds { i32, ptr, i32 }, ptr %[[CONTEXT]], i32 0, i32 2 ; CHECK-NEXT: call void @llvm.wasm.landingpad.index(token %[[CATCHPAD]], i32 0) -; CHECK-NEXT: store i32 0, ptr @__wasm_lpad_context +; CHECK-NEXT: store i32 0, ptr %[[CONTEXT]] ; CHECK-NEXT: %[[LSDA:.*]] = call ptr @llvm.wasm.lsda() -; CHECK-NEXT: store ptr %[[LSDA]], ptr getelementptr inbounds (i8, ptr @__wasm_lpad_context, i32 4) +; CHECK-NEXT: store ptr %[[LSDA]], ptr %lsda_gep ; CHECK-NEXT: call i32 @__gxx_wasm_personality_v0(ptr %[[EXN]]) {{.*}} [ "funclet"(token %[[CATCHPAD]]) ] -; CHECK-NEXT: %[[SELECTOR:.*]] = load i32, ptr getelementptr inbounds (i8, ptr @__wasm_lpad_context, i32 8) +; CHECK-NEXT: %[[SELECTOR:.*]] = load i32, ptr %selector_gep ; CHECK: icmp eq i32 %[[SELECTOR]] catch: ; preds = %catch.start @@ -101,7 +102,7 @@ catch.start: ; preds = %catch.dispatch ; CHECK: catch.start: ; CHECK-NEXT: catchpad within %0 [ptr null] ; CHECK-NOT: call void @llvm.wasm.landingpad.index -; CHECK-NOT: store {{.*}} @__wasm_lpad_context +; CHECK-NOT: call ptr @_Unwind_GetWasmLPadContext() ; CHECK-NOT: call ptr @llvm.wasm.lsda() ; CHECK-NOT: call i32 @__gxx_wasm_personality_v0 ; CHECK-NOT: load {{.*}} @__wasm_lpad_context @@ -278,3 +279,4 @@ attributes #1 = { noreturn } ; CHECK-DAG: declare void @llvm.wasm.landingpad.index(token, i32 immarg) ; CHECK-DAG: declare ptr @llvm.wasm.lsda() ; CHECK-DAG: declare i32 @__gxx_wasm_personality_v0(ptr) +; CHECK-DAG: declare ptr @_Unwind_GetWasmLPadContext() _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
