diff --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp --- a/lld/MachO/Arch/X86_64.cpp +++ b/lld/MachO/Arch/X86_64.cpp @@ -224,6 +224,9 @@ // TODO: implement mov -> lea relaxation for non-dynamic symbols case X86_64_RELOC_GOT: in.got->addEntry(sym); + if (sym.isTlv()) + error("found GOT relocation referencing thread-local variable in " + + toString(isec)); break; case X86_64_RELOC_BRANCH: { // TODO: weak dysyms should go into the weak binding section instead @@ -248,10 +251,20 @@ case X86_64_RELOC_SIGNED_4: break; case X86_64_RELOC_TLV: - if (isa(&sym)) - error("relocations to thread-local dylib symbols not yet implemented"); - else + if (isa(&sym)) { + in.tlvPointers->addEntry(sym); + } else { assert(isa(&sym)); + // TLV relocations on x86_64 are always used with a movq opcode, which + // can be converted to leaq opcodes if they reference a defined symbol. + // (This is in contrast to GOT relocations, which can be used with + // non-movq opcodes.) As such, there is no need to add an entry to + // tlvPointers here. + } + if (!sym.isTlv()) + error( + "found X86_64_RELOC_TLV referencing a non-thread-local variable in " + + toString(isec)); break; case X86_64_RELOC_SUBTRACTOR: fatal("TODO: handle relocation type " + std::to_string(r.type)); @@ -279,7 +292,7 @@ return sym.getVA(); case X86_64_RELOC_TLV: { if (isa(&sym)) - error("relocations to thread-local dylib symbols not yet implemented"); + return in.tlvPointers->addr + sym.gotIndex * WordSize; // Convert the movq to a leaq. assert(isa(&sym)); diff --git a/lld/MachO/ExportTrie.cpp b/lld/MachO/ExportTrie.cpp --- a/lld/MachO/ExportTrie.cpp +++ b/lld/MachO/ExportTrie.cpp @@ -59,11 +59,14 @@ struct ExportInfo { uint64_t address; - uint8_t flags; - explicit ExportInfo(const Symbol &sym) - : address(sym.getVA()), - flags(sym.isWeakDef() ? EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION : 0) {} - // TODO: Add proper support for re-exports & stub-and-resolver flags. + uint8_t flags = 0; + explicit ExportInfo(const Symbol &sym) : address(sym.getVA()) { + if (sym.isWeakDef()) + flags |= EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION; + if (sym.isTlv()) + flags |= EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL; + // TODO: Add proper support for re-exports & stub-and-resolver flags. + } }; } // namespace diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -363,8 +363,9 @@ parseTrie(buf + c->export_off, c->export_size, [&](const Twine &name, uint64_t flags) { bool isWeakDef = flags & EXPORT_SYMBOL_FLAGS_WEAK_DEFINITION; - symbols.push_back( - symtab->addDylib(saver.save(name), umbrella, isWeakDef)); + bool isTlv = flags & EXPORT_SYMBOL_FLAGS_KIND_THREAD_LOCAL; + symbols.push_back(symtab->addDylib(saver.save(name), umbrella, + isWeakDef, isTlv)); }); } else { error("LC_DYLD_INFO_ONLY not found in " + getName()); @@ -403,11 +404,12 @@ dylibName = saver.save(interface->getInstallName()); // TODO(compnerd) filter out symbols based on the target platform - // TODO: handle weak defs + // TODO: handle weak defs, thread locals for (const auto symbol : interface->symbols()) if (symbol->getArchitectures().has(config->arch)) symbols.push_back(symtab->addDylib(saver.save(symbol->getName()), - umbrella, /*isWeakDef=*/false)); + umbrella, /*isWeakDef=*/false, + /*isTlv=*/false)); // TODO(compnerd) properly represent the hierarchy of the documents as it is // in theory possible to have re-exported dylibs from re-exported dylibs which // should be parent'ed to the child. diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h --- a/lld/MachO/InputSection.h +++ b/lld/MachO/InputSection.h @@ -74,6 +74,9 @@ extern std::vector inputSections; } // namespace macho + +std::string toString(const macho::InputSection *); + } // namespace lld #endif diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp --- a/lld/MachO/InputSection.cpp +++ b/lld/MachO/InputSection.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "InputSection.h" +#include "InputFiles.h" #include "OutputSegment.h" #include "Symbols.h" #include "Target.h" @@ -55,3 +56,7 @@ target->relocateOne(buf + r.offset, r, val); } } + +std::string lld::toString(const InputSection *isec) { + return (toString(isec->file) + ":(" + isec->name + ")").str(); +} diff --git a/lld/MachO/SymbolTable.h b/lld/MachO/SymbolTable.h --- a/lld/MachO/SymbolTable.h +++ b/lld/MachO/SymbolTable.h @@ -36,7 +36,7 @@ Symbol *addUndefined(StringRef name); - Symbol *addDylib(StringRef name, DylibFile *file, bool isWeakDef); + Symbol *addDylib(StringRef name, DylibFile *file, bool isWeakDef, bool isTlv); Symbol *addLazy(StringRef name, ArchiveFile *file, const llvm::object::Archive::Symbol &sym); diff --git a/lld/MachO/SymbolTable.cpp b/lld/MachO/SymbolTable.cpp --- a/lld/MachO/SymbolTable.cpp +++ b/lld/MachO/SymbolTable.cpp @@ -69,14 +69,15 @@ return s; } -Symbol *SymbolTable::addDylib(StringRef name, DylibFile *file, bool isWeakDef) { +Symbol *SymbolTable::addDylib(StringRef name, DylibFile *file, bool isWeakDef, + bool isTlv) { Symbol *s; bool wasInserted; std::tie(s, wasInserted) = insert(name); if (wasInserted || isa(s) || (isa(s) && !isWeakDef && s->isWeakDef())) - replaceSymbol(s, file, name, isWeakDef); + replaceSymbol(s, file, name, isWeakDef, isTlv); return s; } diff --git a/lld/MachO/Symbols.h b/lld/MachO/Symbols.h --- a/lld/MachO/Symbols.h +++ b/lld/MachO/Symbols.h @@ -55,6 +55,11 @@ virtual bool isWeakDef() const { llvm_unreachable("cannot be weak"); } + virtual bool isTlv() const { llvm_unreachable("cannot be TLV"); } + + // The index of this symbol in the GOT or the TLVPointer section, depending + // on whether it is a thread-local. A given symbol cannot be referenced by + // both these sections at once. uint32_t gotIndex = UINT32_MAX; protected: @@ -72,6 +77,8 @@ bool isWeakDef() const override { return weakDef; } + bool isTlv() const override { return isThreadLocalVariables(isec->flags); } + static bool classof(const Symbol *s) { return s->kind() == DefinedKind; } uint64_t getVA() const override { return isec->getVA() + value; } @@ -96,11 +103,13 @@ class DylibSymbol : public Symbol { public: - DylibSymbol(DylibFile *file, StringRefZ name, bool isWeakDef) - : Symbol(DylibKind, name), file(file), weakDef(isWeakDef) {} + DylibSymbol(DylibFile *file, StringRefZ name, bool isWeakDef, bool isTlv) + : Symbol(DylibKind, name), file(file), weakDef(isWeakDef), tlv(isTlv) {} bool isWeakDef() const override { return weakDef; } + bool isTlv() const override { return tlv; } + static bool classof(const Symbol *s) { return s->kind() == DylibKind; } DylibFile *file; @@ -109,6 +118,7 @@ private: const bool weakDef; + const bool tlv; }; class LazySymbol : public Symbol { diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -13,6 +13,7 @@ #include "ExportTrie.h" #include "InputSection.h" #include "OutputSection.h" +#include "OutputSegment.h" #include "Target.h" #include "llvm/ADT/SetVector.h" @@ -31,6 +32,7 @@ constexpr const char symbolTable[] = "__symbol_table"; constexpr const char stringTable[] = "__string_table"; constexpr const char got[] = "__got"; +constexpr const char threadPtrs[] = "__thread_ptrs"; } // namespace section_names @@ -95,11 +97,13 @@ void writeTo(uint8_t *buf) const override {} }; -// This section will be populated by dyld with addresses to non-lazily-loaded -// dylib symbols. -class GotSection : public SyntheticSection { +// This is the base class for the GOT and TLVPointer sections, which are nearly +// functionally identical -- they will both be populated by dyld with addresses +// to non-lazily-loaded dylib symbols. The main difference is that the +// TLVPointerSection stores references to thread-local variables. +class NonLazyPointerSectionBase : public SyntheticSection { public: - GotSection(); + NonLazyPointerSectionBase(const char *segname, const char *name); const llvm::SetVector &getEntries() const { return entries; } @@ -115,6 +119,23 @@ llvm::SetVector entries; }; +class GotSection : public NonLazyPointerSectionBase { +public: + GotSection() + : NonLazyPointerSectionBase(segment_names::dataConst, + section_names::got) { + // TODO: section_64::reserved1 should be an index into the indirect symbol + // table, which we do not currently emit + } +}; + +class TlvPointerSection : public NonLazyPointerSectionBase { +public: + TlvPointerSection() + : NonLazyPointerSectionBase(segment_names::data, + section_names::threadPtrs) {} +}; + struct BindingEntry { const DylibSymbol *dysym; const InputSection *isec; @@ -297,6 +318,7 @@ MachHeaderSection *header = nullptr; BindingSection *binding = nullptr; GotSection *got = nullptr; + TlvPointerSection *tlvPointers = nullptr; LazyPointerSection *lazyPointers = nullptr; StubsSection *stubs = nullptr; StubHelperSection *stubHelper = nullptr; diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -82,22 +82,21 @@ PageZeroSection::PageZeroSection() : SyntheticSection(segment_names::pageZero, section_names::pageZero) {} -GotSection::GotSection() - : SyntheticSection(segment_names::dataConst, section_names::got) { +NonLazyPointerSectionBase::NonLazyPointerSectionBase(const char *segname, + const char *name) + : SyntheticSection(segname, name) { align = 8; flags = MachO::S_NON_LAZY_SYMBOL_POINTERS; - - // TODO: section_64::reserved1 should be an index into the indirect symbol - // table, which we do not currently emit } -void GotSection::addEntry(Symbol &sym) { +void NonLazyPointerSectionBase::addEntry(Symbol &sym) { if (entries.insert(&sym)) { + assert(sym.gotIndex == UINT32_MAX); sym.gotIndex = entries.size() - 1; } } -void GotSection::writeTo(uint8_t *buf) const { +void NonLazyPointerSectionBase::writeTo(uint8_t *buf) const { for (size_t i = 0, n = entries.size(); i < n; ++i) if (auto *defined = dyn_cast(entries[i])) write64le(&buf[i * WordSize], defined->getVA()); @@ -107,7 +106,8 @@ : LinkEditSection(segment_names::linkEdit, section_names::binding) {} bool BindingSection::isNeeded() const { - return bindings.size() != 0 || in.got->isNeeded(); + return bindings.size() != 0 || in.got->isNeeded() || + in.tlvPointers->isNeeded(); } namespace { @@ -138,7 +138,6 @@ lastBinding.segment = seg; lastBinding.offset = offset; } else if (lastBinding.offset != offset) { - assert(lastBinding.offset <= offset); os << static_cast(BIND_OPCODE_ADD_ADDR_ULEB); encodeULEB128(offset - lastBinding.offset, os); lastBinding.offset = offset; @@ -169,6 +168,22 @@ lastBinding.offset += WordSize; } +static bool encodeNonLazyPointerSection(NonLazyPointerSectionBase *osec, + Binding &lastBinding, + raw_svector_ostream &os) { + bool didEncode = false; + size_t idx = 0; + for (const Symbol *sym : osec->getEntries()) { + if (const auto *dysym = dyn_cast(sym)) { + didEncode = true; + encodeBinding(*dysym, osec, idx * WordSize, /*addend=*/0, lastBinding, + os); + } + ++idx; + } + return didEncode; +} + // Emit bind opcodes, which are a stream of byte-sized opcodes that dyld // interprets to update a record with the following fields: // * segment index (of the segment to write the symbol addresses to, typically @@ -185,15 +200,8 @@ void BindingSection::finalizeContents() { raw_svector_ostream os{contents}; Binding lastBinding; - bool didEncode = false; - size_t gotIdx = 0; - for (const Symbol *sym : in.got->getEntries()) { - if (const auto *dysym = dyn_cast(sym)) { - didEncode = true; - encodeBinding(*dysym, in.got, gotIdx * WordSize, 0, lastBinding, os); - } - ++gotIdx; - } + bool didEncode = encodeNonLazyPointerSection(in.got, lastBinding, os); + didEncode |= encodeNonLazyPointerSection(in.tlvPointers, lastBinding, os); // Sorting the relocations by segment and address allows us to encode them // more compactly. diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -540,6 +540,7 @@ in.header = make(); in.binding = make(); in.got = make(); + in.tlvPointers = make(); in.lazyPointers = make(); in.stubs = make(); in.stubHelper = make(); diff --git a/lld/test/MachO/invalid/bad-got-to-dylib-tlv-reference.s b/lld/test/MachO/invalid/bad-got-to-dylib-tlv-reference.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/invalid/bad-got-to-dylib-tlv-reference.s @@ -0,0 +1,23 @@ +# REQUIRES: x86 +# RUN: split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/libtlv.s -o %t/libtlv.o +# RUN: lld -flavor darwinnew -dylib -install_name @executable_path/libtlv.dylib \ +# RUN: -Z -L%S/../Inputs/MacOSX.sdk/usr/lib -lSystem -o %t/libtlv.dylib %t/libtlv.o + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/test.o +# RUN: not lld -flavor darwinnew -Z -L%S/../Inputs/MacOSX.sdk/usr/lib -lSystem -L%t -ltlv -o /dev/null %t/test.o 2>&1 | FileCheck %s -DFILE=%t/test.o + +# CHECK: error: found GOT relocation referencing thread-local variable in [[FILE]]:(__text) + +#--- libtlv.s +.section __DATA,__thread_vars,thread_local_variables +.globl _foo +_foo: + +#--- test.s +.text +.globl _main +_main: + movq _foo@GOTPCREL(%rip), %rax + ret diff --git a/lld/test/MachO/invalid/bad-got-to-tlv-reference.s b/lld/test/MachO/invalid/bad-got-to-tlv-reference.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/invalid/bad-got-to-tlv-reference.s @@ -0,0 +1,14 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o +# RUN: not lld -flavor darwinnew -o /dev/null %t.o 2>&1 | FileCheck %s -DFILE=%t.o + +# CHECK: error: found GOT relocation referencing thread-local variable in [[FILE]]:(__text) + +.text +.globl _main +_main: + movq _foo@GOTPCREL(%rip), %rax + ret + +.section __DATA,__thread_vars,thread_local_variables +_foo: diff --git a/lld/test/MachO/invalid/bad-tlv-relocation.s b/lld/test/MachO/invalid/bad-tlv-relocation.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/invalid/bad-tlv-relocation.s @@ -0,0 +1,14 @@ +# REQUIRES: x86 +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o +# RUN: not lld -flavor darwinnew -o /dev/null %t.o 2>&1 | FileCheck %s -DFILE=%t.o + +# CHECK: error: found X86_64_RELOC_TLV referencing a non-thread-local variable in [[FILE]]:(__text) + +.text +.globl _main +_main: + leaq _foo@TLVP(%rip), %rax + ret + +.data +_foo: diff --git a/lld/test/MachO/tlv-dylib.s b/lld/test/MachO/tlv-dylib.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/tlv-dylib.s @@ -0,0 +1,40 @@ +# REQUIRES: x86 +# RUN: split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/libtlv.s -o %t/libtlv.o +# RUN: lld -flavor darwinnew -dylib -install_name @executable_path/libtlv.dylib \ +# RUN: -Z -L%S/Inputs/MacOSX.sdk/usr/lib -lSystem -o %t/libtlv.dylib %t/libtlv.o +# RUN: llvm-objdump --exports-trie -d --no-show-raw-insn %t/libtlv.dylib | FileCheck %s --check-prefix=DYLIB +# DYLIB-DAG: _foo [per-thread] +# DYLIB-DAG: _bar [per-thread] + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/test.o +# RUN: lld -flavor darwinnew -Z -L%S/Inputs/MacOSX.sdk/usr/lib -lSystem -L%t -ltlv %t/test.o -o %t/test +# RUN: llvm-objdump --bind -d --no-show-raw-insn %t/test | FileCheck %s + +# CHECK: movq [[#]](%rip), %rax # [[#%x, FOO:]] +# CHECK-NEXT: movq [[#]](%rip), %rax # [[#%x, BAR:]] +# CHECK-NEXT: movq [[#]](%rip), %rax # [[#%x, BAZ:]] + +# CHECK-LABEL: Bind table: +# CHECK-DAG: __DATA __thread_ptrs 0x{{0*}}[[#%x, FOO]] pointer 0 libtlv _foo +# CHECK-DAG: __DATA __thread_ptrs 0x{{0*}}[[#%x, BAR]] pointer 0 libtlv _bar +# CHECK-DAG: __DATA_CONST __got 0x{{0*}}[[#%x, BAZ]] pointer 0 libtlv _baz + +#--- libtlv.s +.section __DATA,__thread_vars,thread_local_variables +.globl _foo, _bar, _baz +_foo: +_bar: + +.text +_baz: + +#--- test.s +.globl _main +_main: + mov _foo@TLVP(%rip), %rax + mov _bar@TLVP(%rip), %rax +## Add a GOT entry to make sure we don't mix it up with TLVs + mov _baz@GOTPCREL(%rip), %rax + ret