[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