[lld] [lld][WebAssembly] Fix TLS-relative relocations when linking without shared memory (PR #116136)

Sam Clegg via llvm-commits llvm-commits at lists.llvm.org
Wed Nov 13 16:59:18 PST 2024


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

TLS-relative relocation always need to be relative the TLS section since they get added to `__tls_base` at runtime.

Without this change the tls base address was effectively being added to the final value twice in this case.

This only effects code the is built with `-pthread` but linked without shared memory (i.e. without threads).

Fixes: https://github.com/emscripten-core/emscripten/issues/22880

>From 23d374deb75aa49d137fcb08fd18dab0f8d1d0e5 Mon Sep 17 00:00:00 2001
From: Sam Clegg <sbc at chromium.org>
Date: Wed, 13 Nov 2024 16:46:30 -0800
Subject: [PATCH] [lld][WebAssembly] Fix TLS-relative relocations when linking
 without shared memory

TLS-relative relocation always need to be relative the TLS section since
they get added to `__tls_base` at runtime.

Without this change the tls base address was effectively being added
to the final value twice in this case.

This only effects code the is built with `-pthread` but linked without
shared memory (i.e. without threads).

Fixes: https://github.com/emscripten-core/emscripten/issues/22880
---
 lld/test/wasm/tls-non-shared-memory.s |  9 +++++++++
 lld/wasm/Relocations.cpp              | 12 ++++++------
 lld/wasm/Symbols.cpp                  |  9 ++++-----
 lld/wasm/Symbols.h                    |  4 +++-
 lld/wasm/SyntheticSections.cpp        |  5 ++++-
 5 files changed, 26 insertions(+), 13 deletions(-)

diff --git a/lld/test/wasm/tls-non-shared-memory.s b/lld/test/wasm/tls-non-shared-memory.s
index 1754fd6254bb80..04fbb62228a7e2 100644
--- a/lld/test/wasm/tls-non-shared-memory.s
+++ b/lld/test/wasm/tls-non-shared-memory.s
@@ -44,6 +44,7 @@ tls1:
 
 # RUN: wasm-ld --no-gc-sections --no-entry -o %t.wasm %t.o
 # RUN: obj2yaml %t.wasm | FileCheck %s
+# RUN: llvm-objdump --disassemble-symbols=get_tls1 --no-show-raw-insn --no-leading-addr %t.wasm | FileCheck %s --check-prefixes DIS
 
 # RUN: wasm-ld --experimental-pic -shared -o %t.so %t.o
 # RUN: obj2yaml %t.so | FileCheck %s --check-prefixes=SHARED,PIC
@@ -97,6 +98,14 @@ tls1:
 # CHECK-NEXT:        Content:         2A000000
 # CHECK-NEXT:  - Type:            CUSTOM
 
+# The constant value here which we add to `__tls_base` should not be absolute
+# but relative to `__tls_base`, in this case zero rather than 1024.
+# DIS:      <get_tls1>:
+# DIS-EMPTY:
+# DIS-NEXT:  global.get 1
+# DIS-NEXT:  i32.const 0
+# DIS-NEXT:  i32.add
+# DIS-NEXT:  end
 
 # In PIC mode we expect TLS data and non-TLS data to be merged into
 # a single segment which is initialized via the  __memory_base import
diff --git a/lld/wasm/Relocations.cpp b/lld/wasm/Relocations.cpp
index 45ad32701616a1..2776ede8be9034 100644
--- a/lld/wasm/Relocations.cpp
+++ b/lld/wasm/Relocations.cpp
@@ -122,16 +122,16 @@ void scanRelocations(InputChunk *chunk) {
               " cannot be used against an undefined symbol `" + toString(*sym) +
               "`");
       }
+      if (!sym->isTLS()) {
+        error(toString(file) + ": relocation " +
+              relocTypeToString(reloc.Type) +
+              " cannot be used against non-TLS symbol `" + toString(*sym) +
+              "`");
+      }
       // In single-threaded builds TLS is lowered away and TLS data can be
       // merged with normal data and allowing TLS relocation in non-TLS
       // segments.
       if (config->sharedMemory) {
-        if (!sym->isTLS()) {
-          error(toString(file) + ": relocation " +
-                relocTypeToString(reloc.Type) +
-                " cannot be used against non-TLS symbol `" + toString(*sym) +
-                "`");
-        }
         if (auto *D = dyn_cast<DefinedData>(sym)) {
           if (!D->segment->outputSeg->isTLS()) {
             error(toString(file) + ": relocation " +
diff --git a/lld/wasm/Symbols.cpp b/lld/wasm/Symbols.cpp
index b2bbd11c53ef23..8df850ce23032f 100644
--- a/lld/wasm/Symbols.cpp
+++ b/lld/wasm/Symbols.cpp
@@ -310,12 +310,11 @@ uint32_t DefinedFunction::getExportedFunctionIndex() const {
   return function->getFunctionIndex();
 }
 
-uint64_t DefinedData::getVA() const {
+uint64_t DefinedData::getVA(bool absolute) const {
   LLVM_DEBUG(dbgs() << "getVA: " << getName() << "\n");
-  // In the shared memory case, TLS symbols are relative to the start of the TLS
-  // output segment (__tls_base).  When building without shared memory, TLS
-  // symbols absolute, just like non-TLS.
-  if (isTLS() && config->sharedMemory)
+  // TLS symbols are relative to the start of the TLS output segment
+  // (__tls_base).
+  if (isTLS() && !absolute)
     return getOutputSegmentOffset();
   if (segment)
     return segment->getVA(value);
diff --git a/lld/wasm/Symbols.h b/lld/wasm/Symbols.h
index 5ce3ecbc4ab194..310842b89098f9 100644
--- a/lld/wasm/Symbols.h
+++ b/lld/wasm/Symbols.h
@@ -315,7 +315,9 @@ class DefinedData : public DataSymbol {
   static bool classof(const Symbol *s) { return s->kind() == DefinedDataKind; }
 
   // Returns the output virtual address of a defined data symbol.
-  uint64_t getVA() const;
+  // For TLS symbols, by default (unless absolute us set), this returns an
+  // address relative the `__tls_base`.
+  uint64_t getVA(bool absolute = false) const;
   void setVA(uint64_t va);
 
   // Returns the offset of a defined data symbol within its OutputSegment.
diff --git a/lld/wasm/SyntheticSections.cpp b/lld/wasm/SyntheticSections.cpp
index a3bc90cfe759ca..1e8abe9bc7b246 100644
--- a/lld/wasm/SyntheticSections.cpp
+++ b/lld/wasm/SyntheticSections.cpp
@@ -514,7 +514,10 @@ void GlobalSection::writeBody() {
     } else {
       WasmInitExpr initExpr;
       if (auto *d = dyn_cast<DefinedData>(sym))
-        initExpr = intConst(d->getVA(), is64);
+        // In the sharedMemory case `__wasm_apply_global_tls_relocs` is used
+        // to set the final value of this globel, but in the non-shared case
+        // we know the absolute value at link time.
+        initExpr = intConst(d->getVA(/*absolute=*/!config->sharedMemory), is64);
       else if (auto *f = dyn_cast<FunctionSymbol>(sym))
         initExpr = intConst(f->isStub ? 0 : f->getTableIndex(), is64);
       else {



More information about the llvm-commits mailing list