https://github.com/dicej created https://github.com/llvm/llvm-project/pull/222747
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. >From 8b6451224e3afa910f0bf6b4a2d64dfb70dafa46 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 | 6 ++- libunwind/include/unwind_wasm.h | 5 +- libunwind/src/Unwind-wasm.c | 5 ++ llvm/include/llvm/IR/RuntimeLibcalls.td | 5 +- llvm/lib/CodeGen/WasmEHPrepare.cpp | 48 +++++++++++-------- 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 | 11 +++-- 9 files changed, 61 insertions(+), 38 deletions(-) diff --git a/libcxxabi/src/cxa_personality.cpp b/libcxxabi/src/cxa_personality.cpp index 3fdcd8a0c1349..6423ffc10a862 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); + (struct _Unwind_Context*)context); } #endif diff --git a/libunwind/include/unwind_wasm.h b/libunwind/include/unwind_wasm.h index 7bf3f30562bd8..f06e646b296ac 100644 --- a/libunwind/include/unwind_wasm.h +++ b/libunwind/include/unwind_wasm.h @@ -22,6 +22,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 963019ea0efc3..cc5f17921417d 100644 --- a/libunwind/src/Unwind-wasm.c +++ b/libunwind/src/Unwind-wasm.c @@ -22,6 +22,11 @@ _LIBUNWIND_EXPORT 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 _Unwind_RaiseException(_Unwind_Exception *exception_object) { diff --git a/llvm/include/llvm/IR/RuntimeLibcalls.td b/llvm/include/llvm/IR/RuntimeLibcalls.td index 14dda6baf5a53..246a85df291b9 100644 --- a/llvm/include/llvm/IR/RuntimeLibcalls.td +++ b/llvm/include/llvm/IR/RuntimeLibcalls.td @@ -611,7 +611,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 @@ -1882,6 +1882,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>; diff --git a/llvm/lib/CodeGen/WasmEHPrepare.cpp b/llvm/lib/CodeGen/WasmEHPrepare.cpp index 54c6dcfd052e0..a6a7491a02b03 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); @@ -233,20 +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 = 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); @@ -272,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(IRB.getPtrTy(), {}, 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()); @@ -339,6 +339,14 @@ void WasmEHPrepareImpl::prepareEHPad(BasicBlock *BB, bool NeedPersonality, } IRB.SetInsertPoint(CatchCI->getNextNode()); + Instruction *LPadContext = IRB.CreateCall(GetWasmLPadContextF); + + 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 1f8fe2cdd8944..6993ac40b3f9e 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 2c5d918295249..1cdfc8fd16b57 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 808b1926e0be5..60d7d10e23b2a 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 f299b354b18f2..926adc928067b 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 ({ i32, ptr, i32 }, ptr @__wasm_lpad_context, i32 0, i32 1) +; 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 ({ i32, ptr, i32 }, ptr @__wasm_lpad_context, i32 0, i32 2) +; CHECK-NEXT: %[[SELECTOR:.*]] = load i32, ptr %selector_gep ; CHECK: icmp eq i32 %[[SELECTOR]] catch: ; preds = %catch.start _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
