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 #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(&__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. +// 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. +// 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 struct HeavyThing { static thread_local int tls; }; +template thread_local int HeavyThing::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; }