https://github.com/conrade-ctc created 
https://github.com/llvm/llvm-project/pull/209717

Relands #207161 (the `IRMaterializationUnit::discard` fix for duplicated
`__emutls_t.<var>` symbols). History: #207161 merged, reverted in #207775,
relanded in #208413, reverted again in #209260 (a day before the 23.x cut).

### Why #208413 was reverted

clang-repl's JIT lowers `thread_local` to emulated TLS on every target
(`JITTargetMachineBuilder` forces `EmulatedTLS`), so JIT'd code always
references `__emutls_get_address`. #208413 only made that symbol resolvable on
Darwin (`#ifdef __APPLE__` + `isOSBinFormatMachO()`), where it lives in the
compiler-rt builtins static archive. The same gap exists on any host that
links compiler-rt builtins instead of libgcc_s: the arm-toolchain CI hit it on
Linux/AArch64 built with `-rtlib=compiler-rt` (no libgcc_s), where
`__emutls_get_address` is only in the static archive and process-symbol lookup
cannot find it — `JIT session error: Symbols not found: [ __emutls_get_address 
]`.
(glibc bots pass because libgcc_s.so exports it.)

### This reland

The `IRMaterializationUnit` fix (`Layer.cpp`) and the test are unchanged from
#208413. The `IncrementalExecutor.cpp` workaround is broadened from Darwin-only
to all in-process Unix hosts:

- reference `__emutls_get_address` under `LLVM_ON_UNIX` (excluding Emscripten,
  AIX, and z/OS) to force-link it from whichever compiler runtime the toolchain
  uses (libgcc_s or the compiler-rt builtins archive), and
- define it as an absolute symbol in the process-symbols JITDylib regardless of
  target binary format.

This is target-independent: emulated-TLS lowering emits the accessor name from
shared codegen (`TargetLowering`), so a single symbol covers every arch. The
previous `isOSBinFormatMachO()` guard is dropped — with emulated TLS forced on,
a target-format check is vestigial and can only re-exclude the exact hosts that
fail. Where process-symbol lookup already resolves the symbol (glibc), the
absolute definition is a harmless shadow (an already-defined symbol is dropped
from generation candidates before the process-symbol generator runs), following
the MinGW `__main` precedent in `lli.cpp`. In-process only — a host address is
meaningless for an out-of-process executor. The `UNSUPPORTED: target=loongarch`
line from #208886 is retained (LoongArch rejects emulated TLS outright).

The reference resolves from the toolchain's compiler runtime; hosts with no
guaranteed emulated-TLS runtime on the link line (MSVC, Emscripten, AIX, z/OS)
are excluded and fall back to process-symbol lookup, as before.

### Alternatives considered

- **Load the compiler-rt builtins archive into a JITDylib** (via a
  static-library definition generator, as `lli --extra-archive` does) instead of
  force-linking + defining the symbol. This avoids the build-time link
  dependency and generalizes to other builtins, at the cost of runtime discovery
  of the builtins archive path (resource dir / sysroot / cross-compiles /
  libgcc-only toolchains) plus plumbing to reach the in-process JIT path. It's a
  reasonable alternative and a clean follow-on; this PR takes the smaller,
  target-independent route to get the discard fix relanded, and I'm happy to
  switch to it if reviewers prefer.
- **Native TLS + the ORC runtime** — the long-term fix (also removes the
  in-process-only constraint), tracked by the TODO added next to the workaround.
  Out of scope here.

### Testing

Verified on Linux/x86-64: full `check-clang` clean; 
`clang/test/Interpreter/emulated-tls.cpp`
passes. Darwin verified by the author on aarch64.

🤖 Done with the help of [Claude Code](https://claude.com/claude-code) (Claude 
Opus 4.8, human in the loop)


>From de799f7108cdaa754e9e66e47514462ece703900 Mon Sep 17 00:00:00 2001
From: Emery Conrad <[email protected]>
Date: Wed, 15 Jul 2026 02:49:22 -0500
Subject: [PATCH] Reland "[ORC] Track __emutls_t definitions in
 IRMaterializationUnit" (#207161)

#207161 was reverted in #207775, relanded in #208413, then reverted again in
#209260: the reland resolved __emutls_get_address only on Darwin (#ifdef
__APPLE__ + isOSBinFormatMachO), but clang-repl forces emulated TLS on every
target (JITTargetMachineBuilder), so any host that cannot resolve
__emutls_get_address through process-symbol lookup hits the same failure. Arm's
toolchain CI caught it on Linux/AArch64 built with -rtlib=compiler-rt (no
libgcc_s), where the symbol lives only in the compiler-rt builtins static
archive.

The IRMaterializationUnit fix (Layer.cpp) and the test are unchanged from
#208413. This reland broadens the IncrementalExecutor.cpp workaround from
Darwin-only to all in-process Unix hosts: force-link __emutls_get_address
(gated on LLVM_ON_UNIX, excluding Emscripten and AIX/z/OS whose runtimes may
not provide it) and define it as an absolute symbol in the process-symbols
JITDylib regardless of target binary format. Where process-symbol lookup
already resolves it (glibc/libgcc_s) the absolute definition is harmless -- an
already-defined symbol shadows the generator, following the MinGW __main
precedent in lli.cpp.

Co-developed-with-the-help-of: Claude Code (Claude Opus 4.8, human in the loop)
---
 clang/lib/Interpreter/IncrementalExecutor.cpp | 50 +++++++++++++++++++
 clang/test/Interpreter/emulated-tls.cpp       | 28 +++++++++++
 llvm/lib/ExecutionEngine/Orc/Layer.cpp        |  1 +
 3 files changed, 79 insertions(+)
 create mode 100644 clang/test/Interpreter/emulated-tls.cpp

diff --git a/clang/lib/Interpreter/IncrementalExecutor.cpp 
b/clang/lib/Interpreter/IncrementalExecutor.cpp
index 65cb29a2f441a..001a7ecb102b7 100644
--- a/clang/lib/Interpreter/IncrementalExecutor.cpp
+++ b/clang/lib/Interpreter/IncrementalExecutor.cpp
@@ -27,6 +27,7 @@
 #include "llvm/ADT/Twine.h"
 
 #include "llvm/ExecutionEngine/JITLink/JITLinkMemoryManager.h"
+#include "llvm/ExecutionEngine/Orc/AbsoluteSymbols.h"
 #include "llvm/ExecutionEngine/Orc/Debugging/DebuggerSupport.h"
 #include "llvm/ExecutionEngine/Orc/EPCDynamicLibrarySearchGenerator.h"
 #include "llvm/ExecutionEngine/Orc/ExecutionUtils.h"
@@ -60,6 +61,30 @@
 #include <unistd.h>
 #endif
 
+// Address of the host's emulated-TLS runtime entry point, or null if the host
+// cannot provide one. clang-repl's JIT always lowers thread_local to emulated
+// TLS (JITTargetMachineBuilder forces EmulatedTLS on), so JIT'd code 
references
+// __emutls_get_address on every target. That symbol lives in the compiler
+// runtime -- libgcc_s.so on a glibc toolchain, or the compiler-rt builtins
+// static archive on Darwin and on compiler-rt-rtlib toolchains. When it is 
only
+// in a static archive and nothing else references it, it is never linked in 
and
+// ORC's process-symbol lookup cannot resolve it. Referencing it here
+// force-links the archive member so it is present regardless of how the host
+// provides it. Excluded where an emulated-TLS runtime is not guaranteed on the
+// link line, so the reference would fail to link: non-Unix (MSVC has no such
+// runtime), Emscripten (the wasm executor below does not use this JIT path),
+// and AIX / z/OS (whose runtimes may not provide the symbol). On those hosts
+// thread_locals instead rely on process-symbol lookup, unchanged from before.
+#if defined(LLVM_ON_UNIX) && !defined(__EMSCRIPTEN__) && !defined(_AIX) &&     
\
+    !defined(__MVS__)
+extern "C" void *__emutls_get_address(void *);
+static void *getEmuTLSGetAddressPtr() {
+  return reinterpret_cast<void *>(&__emutls_get_address);
+}
+#else
+static void *getEmuTLSGetAddressPtr() { return nullptr; }
+#endif
+
 namespace clang {
 IncrementalExecutorBuilder::~IncrementalExecutorBuilder() = default;
 
@@ -388,6 +413,31 @@ 
IncrementalExecutorBuilder::create(llvm::orc::ThreadSafeContext &TSC,
     if (!JB)
       return JB.takeError();
     JITBuilder = std::move(*JB);
+    // TODO: Switch to native TLS once clang-repl can adopt the ORC runtime
+    // (which provides __emutls_get_address and supports the full TLS
+    // lifecycle). That will also remove the in-process-only constraint below.
+    //
+    // clang-repl lowers thread_local to emulated TLS on every target (see
+    // JITTargetMachineBuilder), so JIT'd code calls __emutls_get_address. When
+    // the host cannot resolve that symbol through process-symbol lookup
+    // (Darwin, and ELF toolchains that link compiler-rt builtins rather than
+    // libgcc_s), define the force-linked host symbol (see
+    // getEmuTLSGetAddressPtr) as an absolute symbol so it is visible to JIT'd
+    // code. This is harmless where process-symbol lookup would already resolve
+    // it: an already-defined symbol shadows the process-symbols generator.
+    // In-process execution only -- the host address is meaningless in an
+    // out-of-process executor.
+    if (void *EmuTLSGetAddress = getEmuTLSGetAddressPtr())
+      JITBuilder->setNotifyCreatedCallback(
+          [EmuTLSGetAddress](llvm::orc::LLJIT &J) {
+            auto &JD = J.getProcessSymbolsJITDylib()
+                           ? *J.getProcessSymbolsJITDylib()
+                           : J.getMainJITDylib();
+            return JD.define(llvm::orc::absoluteSymbols(
+                {{J.mangleAndIntern("__emutls_get_address"),
+                  {llvm::orc::ExecutorAddr::fromPtr(EmuTLSGetAddress),
+                   llvm::JITSymbolFlags::Exported}}}));
+          });
   }
 
   llvm::Error Err = llvm::Error::success();
diff --git a/clang/test/Interpreter/emulated-tls.cpp 
b/clang/test/Interpreter/emulated-tls.cpp
new file mode 100644
index 0000000000000..bd80b65957ee3
--- /dev/null
+++ b/clang/test/Interpreter/emulated-tls.cpp
@@ -0,0 +1,28 @@
+// REQUIRES: host-supports-jit
+// UNSUPPORTED: system-windows
+//
+// Emulated TLS is not supported by the LoongArch backend.
+// UNSUPPORTED: target=loongarch{{.*}}
+//
+// An inline function that odr-uses a non-zero-initialized thread_local is
+// emitted as a weak (linkonce_odr) definition into every 
PartialTranslationUnit
+// that references it. With emulated TLS that set includes an __emutls_t.<var>
+// symbol. When a later PTU re-defines the same weak set, ORC's
+// IRMaterializationUnit::discard() must find each duplicated symbol in its
+// SymbolToDefinition map. The emulated-TLS path used to register 
__emutls_t.<var>
+// in SymbolFlags but not SymbolToDefinition, so discarding it dereferenced
+// end() -- an assertion failure in +Asserts builds and heap corruption
+// otherwise. Two PTUs each pulling in the same inline worker reproduces it.
+//
+// RUN: cat %s | clang-repl | FileCheck %s
+
+extern "C" int printf(const char *, ...);
+template <int Tag> struct HeavyThing { static thread_local int tls; };
+template <int Tag> thread_local int HeavyThing<Tag>::tls = Tag + 1;
+inline int worker() { return HeavyThing<1>::tls; }
+int callA() { return worker(); }
+int callB() { return worker(); }
+auto r = printf("tls = %d, %d\n", callA(), callB());
+// CHECK: tls = 2, 2
+
+%quit
diff --git a/llvm/lib/ExecutionEngine/Orc/Layer.cpp 
b/llvm/lib/ExecutionEngine/Orc/Layer.cpp
index eb144275da589..5e95b8c73b482 100644
--- a/llvm/lib/ExecutionEngine/Orc/Layer.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/Layer.cpp
@@ -70,6 +70,7 @@ IRMaterializationUnit::IRMaterializationUnit(
 
           auto EmuTLST = Mangle(("__emutls_t." + GV.getName()).str());
           SymbolFlags[EmuTLST] = Flags;
+          SymbolToDefinition[EmuTLST] = &GV;
         }
         continue;
       }

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to