[lld] [lld][WebAssembly] Always initialize fixed `__tls_base` in single threaded mode (PR #193563)

via llvm-commits llvm-commits at lists.llvm.org
Wed Apr 22 11:50:42 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lld-wasm

@llvm/pr-subscribers-lld

Author: Sam Clegg (sbc100)

<details>
<summary>Changes</summary>

Without this fix `__tls_base` can remain set to zero which leads `__builtin_thread_pointer` to return NULL, which is should not.

See https://github.com/emscripten-core/emscripten/pull/26747

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


2 Files Affected:

- (added) lld/test/wasm/tls-base-none-shared-memory.s (+37) 
- (modified) lld/wasm/Writer.cpp (+11-4) 


``````````diff
diff --git a/lld/test/wasm/tls-base-none-shared-memory.s b/lld/test/wasm/tls-base-none-shared-memory.s
new file mode 100644
index 0000000000000..df027e520a649
--- /dev/null
+++ b/lld/test/wasm/tls-base-none-shared-memory.s
@@ -0,0 +1,37 @@
+# Test that linking without shared memory causes __tls_base to be
+# internalized, and initialized to a non-zero value, even without any TLS data
+# present.
+
+# RUN: llvm-mc -filetype=obj -triple=wasm32-unknown-unknown -o %t.o %s
+# RUN: wasm-ld -o %t.wasm %t.o
+# RUN: obj2yaml %t.wasm | FileCheck %s
+
+.globaltype __tls_base, i32
+
+.globl _start
+_start:
+  .functype _start () -> ()
+  global.get __tls_base
+  drop
+  end_function
+
+# CHECK:        - Type:            GLOBAL
+# CHECK-NEXT:     Globals:
+# CHECK-NEXT:       - Index:           0
+# CHECK-NEXT:         Type:            I32
+# CHECK-NEXT:         Mutable:         true
+# CHECK-NEXT:         InitExpr:
+# CHECK-NEXT:           Opcode:          I32_CONST
+# CHECK-NEXT:           Value:           65536
+# CHECK-NEXT:       - Index:           1
+# CHECK-NEXT:         Type:            I32
+# CHECK-NEXT:         Mutable:         false
+# CHECK-NEXT:         InitExpr:
+# CHECK-NEXT:           Opcode:          I32_CONST
+# CHECK-NEXT:           Value:           65536
+
+# CHECK:          GlobalNames:
+# CHECK-NEXT:       - Index:           0
+# CHECK-NEXT:         Name:            __stack_pointer
+# CHECK-NEXT:       - Index:           1
+# CHECK-NEXT:         Name:            __tls_base
diff --git a/lld/wasm/Writer.cpp b/lld/wasm/Writer.cpp
index 128931513b215..038a8b3f5417d 100644
--- a/lld/wasm/Writer.cpp
+++ b/lld/wasm/Writer.cpp
@@ -381,6 +381,7 @@ void Writer::layoutMemory() {
     ctx.sym.dsoHandle->setVA(dataStart);
 
   out.dylinkSec->memAlign = 0;
+  uint64_t fixedTLSBase = memoryPtr;
   for (OutputSegment *seg : segments) {
     out.dylinkSec->memAlign = std::max(out.dylinkSec->memAlign, seg->alignment);
     memoryPtr = alignTo(memoryPtr, 1ULL << seg->alignment);
@@ -397,10 +398,7 @@ void Writer::layoutMemory() {
         auto *tlsAlign = cast<DefinedGlobal>(ctx.sym.tlsAlign);
         setGlobalPtr(tlsAlign, int64_t{1} << seg->alignment);
       }
-      if (!ctx.arg.sharedMemory && ctx.sym.tlsBase) {
-        auto *tlsBase = cast<DefinedGlobal>(ctx.sym.tlsBase);
-        setGlobalPtr(tlsBase, memoryPtr);
-      }
+      fixedTLSBase = memoryPtr;
     }
 
     if (ctx.sym.rodataStart && seg->name.starts_with(".rodata") &&
@@ -414,6 +412,15 @@ void Writer::layoutMemory() {
       ctx.sym.rodataEnd->setVA(memoryPtr);
   }
 
+  // In single-threaded builds we set __tls_base statically.
+  // Even in the absense of any actual TLS data, this symbol can still be
+  // referenced (for example by __builtin_thread_pointer, which should not
+  // return NULL).
+  if (!ctx.arg.sharedMemory && ctx.sym.tlsBase) {
+    auto *tlsBase = cast<DefinedGlobal>(ctx.sym.tlsBase);
+    setGlobalPtr(tlsBase, fixedTLSBase);
+  }
+
   // Make space for the memory initialization flag
   if (ctx.arg.sharedMemory && hasPassiveInitializedSegments()) {
     memoryPtr = alignTo(memoryPtr, 4);

``````````

</details>


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


More information about the llvm-commits mailing list