[lld][WebAssembly] Always initialize fixed __tls_base in single threaded mode (#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
This commit is contained in:
Sam Clegg
2026-04-23 11:39:38 -07:00
committed by GitHub
parent 0bdaf63d01
commit 44a1d74033
3 changed files with 49 additions and 5 deletions

View File

@@ -124,7 +124,7 @@ ret32_ptr:
# CHECK-NEXT: Mutable: false # CHECK-NEXT: Mutable: false
# CHECK-NEXT: InitExpr: # CHECK-NEXT: InitExpr:
# CHECK-NEXT: Opcode: I32_CONST # CHECK-NEXT: Opcode: I32_CONST
# CHECK-NEXT: Value: 0 # CHECK-NEXT: Value: 65536
# GOT.func.internal.ret32 # GOT.func.internal.ret32
# CHECK-NEXT: - Index: 4 # CHECK-NEXT: - Index: 4

View File

@@ -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

View File

@@ -381,6 +381,7 @@ void Writer::layoutMemory() {
ctx.sym.dsoHandle->setVA(dataStart); ctx.sym.dsoHandle->setVA(dataStart);
out.dylinkSec->memAlign = 0; out.dylinkSec->memAlign = 0;
uint64_t fixedTLSBase = memoryPtr;
for (OutputSegment *seg : segments) { for (OutputSegment *seg : segments) {
out.dylinkSec->memAlign = std::max(out.dylinkSec->memAlign, seg->alignment); out.dylinkSec->memAlign = std::max(out.dylinkSec->memAlign, seg->alignment);
memoryPtr = alignTo(memoryPtr, 1ULL << seg->alignment); memoryPtr = alignTo(memoryPtr, 1ULL << seg->alignment);
@@ -397,10 +398,7 @@ void Writer::layoutMemory() {
auto *tlsAlign = cast<DefinedGlobal>(ctx.sym.tlsAlign); auto *tlsAlign = cast<DefinedGlobal>(ctx.sym.tlsAlign);
setGlobalPtr(tlsAlign, int64_t{1} << seg->alignment); setGlobalPtr(tlsAlign, int64_t{1} << seg->alignment);
} }
if (!ctx.arg.sharedMemory && ctx.sym.tlsBase) { fixedTLSBase = memoryPtr;
auto *tlsBase = cast<DefinedGlobal>(ctx.sym.tlsBase);
setGlobalPtr(tlsBase, memoryPtr);
}
} }
if (ctx.sym.rodataStart && seg->name.starts_with(".rodata") && if (ctx.sym.rodataStart && seg->name.starts_with(".rodata") &&
@@ -414,6 +412,15 @@ void Writer::layoutMemory() {
ctx.sym.rodataEnd->setVA(memoryPtr); 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 // Make space for the memory initialization flag
if (ctx.arg.sharedMemory && hasPassiveInitializedSegments()) { if (ctx.arg.sharedMemory && hasPassiveInitializedSegments()) {
memoryPtr = alignTo(memoryPtr, 4); memoryPtr = alignTo(memoryPtr, 4);