[libcxxabi] [libunwind] [llvm] fix Wasm exceptions + coop threading + shared libraries (PR #222747)
via cfe-commits
cfe-commits at lists.llvm.org
Thu Sep 10 11:50:28 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-libunwind
Author: Joel Dice (dicej)
<details>
<summary>Changes</summary>
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.
---
Full diff: https://github.com/llvm/llvm-project/pull/222747.diff
9 Files Affected:
- (modified) libcxxabi/src/cxa_personality.cpp (+4-2)
- (modified) libunwind/include/unwind_wasm.h (+4-1)
- (modified) libunwind/src/Unwind-wasm.c (+5)
- (modified) llvm/include/llvm/IR/RuntimeLibcalls.td (+4-1)
- (modified) llvm/lib/CodeGen/WasmEHPrepare.cpp (+28-20)
- (modified) llvm/test/CodeGen/WebAssembly/eh-lsda.ll (+8-7)
- (modified) llvm/test/CodeGen/WebAssembly/exception-legacy.ll (+1-1)
- (modified) llvm/test/CodeGen/WebAssembly/exception.ll (+1-1)
- (modified) llvm/test/CodeGen/WebAssembly/wasm-eh-prepare.ll (+6-5)
``````````diff
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 at 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 at 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
``````````
</details>
https://github.com/llvm/llvm-project/pull/222747
More information about the cfe-commits
mailing list