[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