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

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


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

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

>From e7326016369133cd6b1363e2dcea4e970bd6217c Mon Sep 17 00:00:00 2001
From: Sam Clegg <sbc at chromium.org>
Date: Wed, 22 Apr 2026 11:39:57 -0700
Subject: [PATCH] [lld][WebAssembly] Alwasy initialize __tls_base in single
 threaded builds

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
---
 lld/test/wasm/tls-base-none-shared-memory.s | 37 +++++++++++++++++++++
 lld/wasm/Writer.cpp                         | 15 ++++++---
 2 files changed, 48 insertions(+), 4 deletions(-)
 create mode 100644 lld/test/wasm/tls-base-none-shared-memory.s

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);



More information about the llvm-commits mailing list