[lld] [WebAssembly] Generate a call to __wasm_apply_global_tls_relocs in __wasm_init_memory (PR #149832)

via llvm-commits llvm-commits at lists.llvm.org
Mon Jul 21 08:10:11 PDT 2025


https://github.com/Arshia001 created https://github.com/llvm/llvm-project/pull/149832

# Motivation

We recently implemented the WebAssembly exception handling proposal in Wasmer 6.0. As a result, we can now take advantage of clang's support for compiling SjLj and C++ exceptions to WASM EH. This PR fixes a wasm-ld issue that breaks the use of C++ exception handling in WASI(X) modules.

Note: I use WASI(X) to mean either [wasi preview 1](https://wasi.dev/) or [WASIX](https://wasix.org) modules.

# Error details

When compiling C++ code that uses exceptions, clang generates a `GOT.data.internal.__wasm_lpad_context` global, which points to the wasm landing pad context that's shared between compiler code and libunwind. This global is initialized in the `__wasm_apply_global_tls_relocs` function.

TLS initialization happens in two separate places; for the "main thread", `__wasm_init_memory` runs as the `(start)` function of the WASM module, initializing all memory segments (including TLS), while also initializing the main thread's `__tls_base` to the space reserved for it by the compiler, and signalling this fact to other threads via an atomic. Other threads need to run `__wasm_init_tls` after getting their respective `__tls_base` global initialized externally.

As it stands, `__wasm_apply_global_tls_relocs` is only called through `__wasm_init_tls`, meaning if code doesn't call `__wasm_init_tls`, any globals that are initialized in `__wasm_apply_global_tls_relocs` do not get initialized. This is the case for the main thread.

It is important to note that exception handling code generated by the compiler uses `GOT.data.internal.__wasm_lpad_context`, while the code in `_Unwind_CallPersonality` goes through `__tls_base + offset` directly. Because `GOT.data.internal.__wasm_lpad_context` is not initialized in the main thread, the compiler and `_Unwind_CallPersonality` do not agree on where the landing pad context is stored. This results in `scan_eh_tab` not getting the correct LSDA pointer. Exception handling is then completely broken; the catch-all block runs for every exception due to a lack of any type information at runtime.

This PR allows a call to `__wasm_apply_global_tls_relocs` to be generated in `__wasm_init_memory` if needed, which should fix the value of `GOT.data.internal.__wasm_lpad_context` in modules' main threads. Interestingly, through all of our recent work on dynamic linking and PIC modules, we never encountered `__wasm_apply_global_tls_relocs`, and I don't know if it's used for anything besides `GOT.data.internal.__wasm_lpad_context`.

## But how does emscripten work if this is broken?

Good question! Emscripten calls `__wasm_init_tls` redundantly for main threads, and thus initializes the TLS area twice. This has no observable effect besides being slower, and does indeed fix C++ exception handling.

This is a workaround that we can use in WASIX as well. However, as far as I understand, the current behavior is wasm-ld is broken, since `__wasm_init_memory` and `__wasm_init_tls` should behave similarly with respect to TLS initialization, but feel free to disagree with me here.

>From 738d8b1482f83e1404dede15e1d4e7a9b134d673 Mon Sep 17 00:00:00 2001
From: Arshia Ghafoori <arshia001 at live.com>
Date: Mon, 21 Jul 2025 12:53:32 +0000
Subject: [PATCH] Generate a call to __wasm_apply_global_tls_relocs in
 __wasm_init_memory

---
 lld/wasm/Writer.cpp | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index b704677d36c93..3cd6a73fb1a31 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -1366,6 +1366,15 @@ void Writer::createInitMemoryFunction() {
           writeUleb128(os, s->index, "segment index immediate");
           writeU8(os, 0, "memory index immediate");
         }
+
+        // After initializing the TLS segment, we also need to apply TLS
+        // relocations in the same way __wasm_init_tls does.
+        if (ctx.arg.sharedMemory && s->isTLS() &&
+            ctx.sym.applyGlobalTLSRelocs) {
+          writeU8(os, WASM_OPCODE_CALL, "CALL");
+          writeUleb128(os, ctx.sym.applyGlobalTLSRelocs->getFunctionIndex(),
+                      "function index");
+        }
       }
     }
 



More information about the llvm-commits mailing list