[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:11:05 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lld-wasm

Author: None (Arshia001)

<details>
<summary>Changes</summary>

# 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.

---
Full diff: https://github.com/llvm/llvm-project/pull/149832.diff


1 Files Affected:

- (modified) lld/wasm/Writer.cpp (+9) 


``````````diff
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");
+        }
       }
     }
 

``````````

</details>


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


More information about the llvm-commits mailing list