diff --git a/lld/MachO/Arch/ARM.cpp b/lld/MachO/Arch/ARM.cpp --- a/lld/MachO/Arch/ARM.cpp +++ b/lld/MachO/Arch/ARM.cpp @@ -37,6 +37,11 @@ void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; + void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, + uint64_t stubOffset, uint64_t selrefsVA, + uint64_t selectorIndex, uint64_t gotAddr, + uint64_t msgSendIndex) const override; + void relaxGotLoad(uint8_t *loc, uint8_t type) const override; uint64_t getPageSize() const override { return 4 * 1024; } @@ -148,6 +153,13 @@ fatal("TODO: implement this"); } +void ARM::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, + uint64_t stubOffset, uint64_t selrefsVA, + uint64_t selectorIndex, uint64_t gotAddr, + uint64_t msgSendIndex) const { + fatal("TODO: implement this"); +} + void ARM::relaxGotLoad(uint8_t *loc, uint8_t type) const { fatal("TODO: implement this"); } diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp --- a/lld/MachO/Arch/ARM64.cpp +++ b/lld/MachO/Arch/ARM64.cpp @@ -34,6 +34,11 @@ void writeStubHelperHeader(uint8_t *buf) const override; void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; + + void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, + uint64_t stubOffset, uint64_t selrefsVA, + uint64_t selectorIndex, uint64_t gotAddr, + uint64_t msgSendIndex) const override; void populateThunk(InputSection *thunk, Symbol *funcSym) override; void applyOptimizationHints(uint8_t *, const ConcatInputSection *, ArrayRef) const override; @@ -100,6 +105,26 @@ ::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA); } +static constexpr uint32_t objcStubsFastCode[] = { + 0x90000001, // adrp x1, __objc_selrefs@page + 0xf9400021, // ldr x1, [x1, @selector("foo")@pageoff] + 0x90000010, // adrp x16, _got@page + 0xf9400210, // ldr x16, [x16, _objc_msgSend@pageoff] + 0xd61f0200, // br x16 + 0xd4200020, // brk #0x1 + 0xd4200020, // brk #0x1 + 0xd4200020, // brk #0x1 +}; + +void ARM64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, + uint64_t stubOffset, uint64_t selrefsVA, + uint64_t selectorIndex, uint64_t gotAddr, + uint64_t msgSendIndex) const { + ::writeObjCMsgSendStub(buf, objcStubsFastCode, sym, stubsAddr, + stubOffset, selrefsVA, selectorIndex, gotAddr, + msgSendIndex); +} + // A thunk is the relaxed variation of stubCode. We don't need the // extra indirection through a lazy pointer because the target address // is known at link time. @@ -130,6 +155,9 @@ stubSize = sizeof(stubCode); thunkSize = sizeof(thunkCode); + objcStubsFastSize = sizeof(objcStubsFastCode); + objcStubsAlignment = 32; + // Branch immediate is two's complement 26 bits, which is implicitly // multiplied by 4 (since all functions are 4-aligned: The branch range // is -4*(2**(26-1))..4*(2**(26-1) - 1). diff --git a/lld/MachO/Arch/ARM64Common.h b/lld/MachO/Arch/ARM64Common.h --- a/lld/MachO/Arch/ARM64Common.h +++ b/lld/MachO/Arch/ARM64Common.h @@ -147,6 +147,32 @@ buf32[2] = sym.lazyBindOffset; } +template +inline void +writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8], + Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset, + uint64_t selrefsVA, uint64_t selectorIndex, + uint64_t gotAddr, uint64_t msgSendIndex) { + SymbolDiagnostic d = {sym, sym->getName()}; + auto *buf32 = reinterpret_cast(buf); + + auto pcPageBits = [stubsAddr, stubOffset](int i) { + return pageBits(stubsAddr + stubOffset + i * sizeof(uint32_t)); + }; + + uint64_t selectorOffset = selectorIndex * LP::wordSize; + encodePage21(&buf32[0], d, objcStubsFastCode[0], + pageBits(selrefsVA + selectorOffset) - pcPageBits(0)); + encodePageOff12(&buf32[1], objcStubsFastCode[1], selrefsVA + selectorOffset); + encodePage21(&buf32[2], d, objcStubsFastCode[2], + pageBits(gotAddr) - pcPageBits(2)); + encodePage21(&buf32[3], d, objcStubsFastCode[3], msgSendIndex * LP::wordSize); + buf32[4] = objcStubsFastCode[4]; + buf32[5] = objcStubsFastCode[5]; + buf32[6] = objcStubsFastCode[6]; + buf32[7] = objcStubsFastCode[7]; +} + } // namespace lld::macho #endif diff --git a/lld/MachO/Arch/ARM64_32.cpp b/lld/MachO/Arch/ARM64_32.cpp --- a/lld/MachO/Arch/ARM64_32.cpp +++ b/lld/MachO/Arch/ARM64_32.cpp @@ -33,6 +33,10 @@ void writeStubHelperHeader(uint8_t *buf) const override; void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; + void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, + uint64_t stubOffset, uint64_t selrefsVA, + uint64_t selectorIndex, uint64_t gotAddr, + uint64_t msgSendIndex) const override; }; } // namespace @@ -94,6 +98,14 @@ ::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA); } +void ARM64_32::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t stubsAddr, uint64_t stubOffset, + uint64_t selrefsVA, uint64_t selectorIndex, + uint64_t gotAddr, + uint64_t msgSendIndex) const { + fatal("TODO: implement this"); +} + ARM64_32::ARM64_32() : ARM64Common(ILP32()) { cpuType = CPU_TYPE_ARM64_32; cpuSubtype = CPU_SUBTYPE_ARM64_V8; 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 @@ -36,6 +36,11 @@ void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const override; + void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, + uint64_t stubOffset, uint64_t selrefsVA, + uint64_t selectorIndex, uint64_t gotAddr, + uint64_t msgSendIndex) const override; + void relaxGotLoad(uint8_t *loc, uint8_t type) const override; uint64_t getPageSize() const override { return 4 * 1024; } @@ -170,6 +175,24 @@ sizeof(stubHelperEntry), in.stubHelper->addr); } +static constexpr uint8_t objcStubsFastCode[] = { + 0x48, 0x8b, 0x35, 0, 0, 0, 0, // 0x0: movq selrefs@selector(%rip), %rsi + 0xff, 0x25, 0, 0, 0, 0, // 0x7: jmpq *_objc_msgSend@GOT(%rip) +}; + +void X86_64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr, + uint64_t stubOffset, uint64_t selrefsVA, + uint64_t selectorIndex, uint64_t gotAddr, + uint64_t msgSendIndex) const { + memcpy(buf, objcStubsFastCode, sizeof(objcStubsFastCode)); + SymbolDiagnostic d = {sym, sym->getName()}; + uint64_t stubAddr = stubsAddr + stubOffset; + writeRipRelative(d, buf, stubAddr, 7, + selrefsVA + selectorIndex * LP64::wordSize); + writeRipRelative(d, buf, stubAddr, 0xd, + gotAddr + msgSendIndex * LP64::wordSize); +} + void X86_64::relaxGotLoad(uint8_t *loc, uint8_t type) const { // Convert MOVQ to LEAQ if (loc[-2] != 0x8b) @@ -189,6 +212,9 @@ stubHelperHeaderSize = sizeof(stubHelperHeader); stubHelperEntrySize = sizeof(stubHelperEntry); + objcStubsFastSize = sizeof(objcStubsFastCode); + objcStubsAlignment = 1; + relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()}; } diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h --- a/lld/MachO/Config.h +++ b/lld/MachO/Config.h @@ -68,6 +68,11 @@ all, }; +enum class ObjCStubsMode { + fast, + small, +}; + struct SectionAlign { llvm::StringRef segName; llvm::StringRef sectName; @@ -166,6 +171,7 @@ UndefinedSymbolTreatment undefinedSymbolTreatment = UndefinedSymbolTreatment::error; ICFLevel icfLevel = ICFLevel::none; + ObjCStubsMode objcStubsMode = ObjCStubsMode::fast; llvm::MachO::HeaderFileType outputType; std::vector systemLibraryRoots; std::vector librarySearchPaths; diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp --- a/lld/MachO/Driver.cpp +++ b/lld/MachO/Driver.cpp @@ -781,6 +781,17 @@ return icfLevel; } +static ObjCStubsMode getObjCStubsMode(const ArgList &args) { + const Arg *arg = args.getLastArg(OPT_objc_stubs_fast, OPT_objc_stubs_small); + if (!arg) + return ObjCStubsMode::fast; + + if (arg->getOption().getID() == OPT_objc_stubs_small) + warn("-objc_stubs_small is not yet implemented, defaulting to " + "-objc_stubs_fast"); + return ObjCStubsMode::fast; +} + static void warnIfDeprecatedOption(const Option &opt) { if (!opt.getGroup().isValid()) return; @@ -1099,9 +1110,15 @@ inputSections.push_back(isec); } else if (auto *isec = dyn_cast(subsection.isec)) { - if (in.cStringSection->inputOrder == UnspecifiedInputOrder) - in.cStringSection->inputOrder = inputOrder++; - in.cStringSection->addInput(isec); + if (isec->getName() == section_names::objcMethname) { + if (in.objcMethnameSection->inputOrder == UnspecifiedInputOrder) + in.objcMethnameSection->inputOrder = inputOrder++; + in.objcMethnameSection->addInput(isec); + } else { + if (in.cStringSection->inputOrder == UnspecifiedInputOrder) + in.cStringSection->inputOrder = inputOrder++; + in.cStringSection->addInput(isec); + } } else if (auto *isec = dyn_cast(subsection.isec)) { if (in.wordLiteralSection->inputOrder == UnspecifiedInputOrder) @@ -1124,10 +1141,39 @@ // true. If it isn't, we simply create a non-deduplicating CStringSection. // Either way, we must unconditionally finalize it here. in.cStringSection->finalizeContents(); + in.objcMethnameSection->finalizeContents(); if (in.wordLiteralSection) in.wordLiteralSection->finalizeContents(); } +static void addSynthenticMethnames() { + std::string &data = *make(); + llvm::raw_string_ostream os(data); + const int prefixLength = ObjCStubsSection::symbolPrefix.size(); + for (Symbol *sym : symtab->getSymbols()) + if (const auto *undefined = dyn_cast(sym)) + if (sym->getName().startswith(ObjCStubsSection::symbolPrefix)) + os << sym->getName().drop_front(prefixLength) << '\0'; + + if (data.empty()) + return; + + const auto *buf = reinterpret_cast(data.c_str()); + Section §ion = *make
(/*file=*/nullptr, segment_names::text, + section_names::objcMethname, + S_CSTRING_LITERALS, /*addr=*/0); + + auto *isec = + make(section, ArrayRef{buf, data.size()}, + /*align=*/1, /*dedupLiterals=*/true); + isec->splitIntoPieces(); + for (auto &piece : isec->pieces) + piece.live = true; + section.subsections.push_back({0, isec}); + in.objcMethnameSection->addInput(isec); + in.objcMethnameSection->isec->markLive(0); +} + static void referenceStubBinder() { bool needsStubHelper = config->outputType == MH_DYLIB || config->outputType == MH_EXECUTE || @@ -1398,6 +1444,7 @@ config->printSymbolOrder = args.getLastArgValue(OPT_print_symbol_order); config->forceExactCpuSubtypeMatch = getenv("LD_DYLIB_CPU_SUBTYPES_MUST_MATCH"); + config->objcStubsMode = getObjCStubsMode(args); for (const Arg *arg : args.filtered(OPT_alias)) { config->aliasedSymbols.push_back( @@ -1643,6 +1690,7 @@ createSyntheticSections(); createSyntheticSymbols(); + addSynthenticMethnames(); createAliases(); // If we are in "explicit exports" mode, hide everything that isn't diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp --- a/lld/MachO/InputFiles.cpp +++ b/lld/MachO/InputFiles.cpp @@ -342,7 +342,10 @@ InputSection *isec; if (sectionType(sec.flags) == S_CSTRING_LITERALS) { - isec = make(section, data, align); + isec = make(section, data, align, + /*dedupLiterals=*/name == + section_names::objcMethname || + config->dedupLiterals); // FIXME: parallelize this? cast(isec)->splitIntoPieces(); } else { diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h --- a/lld/MachO/InputSection.h +++ b/lld/MachO/InputSection.h @@ -192,8 +192,10 @@ class CStringInputSection final : public InputSection { public: CStringInputSection(const Section §ion, ArrayRef data, - uint32_t align) - : InputSection(CStringLiteralKind, section, data, align) {} + uint32_t align, bool dedupLiterals) + : InputSection(CStringLiteralKind, section, data, align), + deduplicateLiterals(dedupLiterals) {} + uint64_t getOffset(uint64_t off) const override; bool isLive(uint64_t off) const override { return getStringPiece(off).live; } void markLive(uint64_t off) override { getStringPiece(off).live = true; } @@ -215,7 +217,7 @@ // string merging is enabled, so we want to inline. LLVM_ATTRIBUTE_ALWAYS_INLINE llvm::CachedHashStringRef getCachedHashStringRef(size_t i) const { - assert(config->dedupLiterals); + assert(deduplicateLiterals); return {getStringRef(i), pieces[i].hash}; } @@ -223,6 +225,7 @@ return isec->kind() == CStringLiteralKind; } + bool deduplicateLiterals = false; std::vector pieces; }; @@ -323,6 +326,9 @@ constexpr const char objcClassRefs[] = "__objc_classrefs"; constexpr const char objcConst[] = "__objc_const"; constexpr const char objCImageInfo[] = "__objc_imageinfo"; +constexpr const char objcStubs[] = "__objc_stubs"; +constexpr const char objcSelrefs[] = "__objc_selrefs"; +constexpr const char objcMethname[] = "__objc_methname"; constexpr const char objcNonLazyCatList[] = "__objc_nlcatlist"; constexpr const char objcNonLazyClassList[] = "__objc_nlclslist"; constexpr const char objcProtoList[] = "__objc_protolist"; diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp --- a/lld/MachO/InputSection.cpp +++ b/lld/MachO/InputSection.cpp @@ -251,7 +251,7 @@ if (end == StringRef::npos) fatal(getLocation(off) + ": string is not null terminated"); size_t size = end + 1; - uint32_t hash = config->dedupLiterals ? xxHash64(s.substr(0, size)) : 0; + uint32_t hash = deduplicateLiterals ? xxHash64(s.substr(0, size)) : 0; pieces.emplace_back(off, hash); s = s.substr(size); off += size; diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td --- a/lld/MachO/Options.td +++ b/lld/MachO/Options.td @@ -978,6 +978,12 @@ def no_dtrace_dof : Flag<["-"], "no_dtrace_dof">, HelpText<"Disable dtrace-dof processing (default).">, Group; +def objc_stubs_fast : Flag<["-"], "objc_stubs_fast">, + HelpText<"Produce larger stubs for Objective-C method calls with fewer jumps (default).">, + Group; +def objc_stubs_small : Flag<["-"], "objc_stubs_small">, + HelpText<"Produce smaller stubs for Objective-C method calls with more jumps.">, + Group; def grp_deprecated : OptionGroup<"deprecated">, HelpText<"DEPRECATED">; diff --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h --- a/lld/MachO/SyntheticSections.h +++ b/lld/MachO/SyntheticSections.h @@ -310,6 +310,30 @@ Defined *dyldPrivate = nullptr; }; +// Objective-C stubs are hoisted objc_msgSend calls per selector called in the +// program. Apple Clang produces undefined symbols to each stub, such as +// '_objc_msgSend$foo', which are then synthesized by the linker. The stubs +// load the particular selector 'foo' from __objc_selrefs, setting it to the +// first argument of the objc_msgSend call, and then jumps to objc_msgSend. The +// actual stub contents are mirrored from ld64. +class ObjCStubsSection final : public SyntheticSection { +public: + ObjCStubsSection(); + void addEntry(Symbol *sym); + uint64_t getSize() const override; + bool isNeeded() const override { return !symbols.empty(); } + void finalize() override { isec->isFinal = true; } + void writeTo(uint8_t *buf) const override; + void setup(); + + static constexpr llvm::StringLiteral symbolPrefix = "_objc_msgSend$"; + +private: + std::vector symbols; + std::vector offsets; + int objcMsgSendGotIndex = 0; +}; + // Note that this section may also be targeted by non-lazy bindings. In // particular, this happens when branch relocations target weak symbols. class LazyPointerSection final : public SyntheticSection { @@ -514,7 +538,7 @@ class CStringSection : public SyntheticSection { public: - CStringSection(); + CStringSection(const char *name); void addInput(CStringInputSection *); uint64_t getSize() const override { return size; } virtual void finalizeContents(); @@ -529,17 +553,21 @@ class DeduplicatedCStringSection final : public CStringSection { public: + DeduplicatedCStringSection(const char *name) : CStringSection(name){}; uint64_t getSize() const override { return size; } void finalizeContents() override; void writeTo(uint8_t *buf) const override; -private: struct StringOffset { uint8_t trailingZeros; uint64_t outSecOff = UINT64_MAX; explicit StringOffset(uint8_t zeros) : trailingZeros(zeros) {} }; + + StringOffset getStringOffset(StringRef str) const; + +private: llvm::DenseMap stringOffsetMap; size_t size = 0; }; @@ -623,6 +651,7 @@ const uint8_t *bufferStart = nullptr; MachHeaderSection *header = nullptr; CStringSection *cStringSection = nullptr; + DeduplicatedCStringSection *objcMethnameSection = nullptr; WordLiteralSection *wordLiteralSection = nullptr; RebaseSection *rebase = nullptr; BindingSection *binding = nullptr; @@ -634,6 +663,8 @@ LazyPointerSection *lazyPointers = nullptr; StubsSection *stubs = nullptr; StubHelperSection *stubHelper = nullptr; + ObjCStubsSection *objcStubs = nullptr; + ConcatInputSection *objcSelrefs = nullptr; UnwindInfoSection *unwindInfo = nullptr; ObjCImageInfoSection *objCImageInfo = nullptr; ConcatInputSection *imageLoaderCache = nullptr; diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp --- a/lld/MachO/SyntheticSections.cpp +++ b/lld/MachO/SyntheticSections.cpp @@ -24,6 +24,7 @@ #include "llvm/Support/LEB128.h" #include "llvm/Support/Parallel.h" #include "llvm/Support/Path.h" +#include "llvm/Support/xxhash.h" #if defined(__APPLE__) #include @@ -716,6 +717,84 @@ dyldPrivate->used = true; } +ObjCStubsSection::ObjCStubsSection() + : SyntheticSection(segment_names::text, section_names::objcStubs) { + flags = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS; + align = target->objcStubsAlignment; +} + +void ObjCStubsSection::addEntry(Symbol *sym) { + assert(sym->getName().startswith(symbolPrefix) && "not an objc stub"); + // Ensure our lookup string has the length of the actual string + the null + // terminator to mirror + StringRef methname = + StringRef(sym->getName().data() + symbolPrefix.size(), + sym->getName().size() - symbolPrefix.size() + 1); + offsets.push_back( + in.objcMethnameSection->getStringOffset(methname).outSecOff); + Defined *newSym = replaceSymbol( + sym, sym->getName(), nullptr, isec, + /*value=*/symbols.size() * target->objcStubsFastSize, + /*size=*/target->objcStubsFastSize, + /*isWeakDef=*/false, /*isExternal=*/true, /*isPrivateExtern=*/true, + /*includeInSymtab=*/true, /*isThumb=*/false, + /*isReferencedDynamically=*/false, /*noDeadStrip=*/false); + symbols.push_back(newSym); +} + +void ObjCStubsSection::setup() { + Symbol *objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr, + /*isWeakRef=*/false); + objcMsgSend->used = true; + in.got->addEntry(objcMsgSend); + assert(objcMsgSend->isInGot()); + objcMsgSendGotIndex = objcMsgSend->gotIndex; + + size_t size = offsets.size() * target->wordSize; + uint8_t *selrefsData = bAlloc().Allocate(size); + for (size_t i = 0, n = offsets.size(); i < n; ++i) + write64le(&selrefsData[i * target->wordSize], offsets[i]); + + in.objcSelrefs = + makeSyntheticInputSection(segment_names::data, section_names::objcSelrefs, + S_LITERAL_POINTERS | S_ATTR_NO_DEAD_STRIP, + ArrayRef{selrefsData, size}, + /*align=*/target->wordSize); + in.objcSelrefs->live = true; + + for (size_t i = 0, n = offsets.size(); i < n; ++i) { + in.objcSelrefs->relocs.push_back( + {/*type=*/target->unsignedRelocType, + /*pcrel=*/false, /*length=*/3, + /*offset=*/static_cast(i * target->wordSize), + /*addend=*/offsets[i] * in.objcMethnameSection->align, + /*referent=*/in.objcMethnameSection->isec}); + } + + in.objcSelrefs->parent = + ConcatOutputSection::getOrCreateForInput(in.objcSelrefs); + inputSections.push_back(in.objcSelrefs); + in.objcSelrefs->isFinal = true; +} + +uint64_t ObjCStubsSection::getSize() const { + return target->objcStubsFastSize * symbols.size(); +} + +void ObjCStubsSection::writeTo(uint8_t *buf) const { + assert(in.objcSelrefs->live); + assert(in.objcSelrefs->isFinal); + + uint64_t stubOffset = 0; + for (size_t i = 0, n = symbols.size(); i < n; ++i) { + Defined *sym = symbols[i]; + target->writeObjCMsgSendStub(buf + stubOffset, sym, in.objcStubs->addr, + stubOffset, in.objcSelrefs->getVA(), i, + in.got->addr, objcMsgSendGotIndex); + stubOffset += target->objcStubsFastSize; + } +} + LazyPointerSection::LazyPointerSection() : SyntheticSection(segment_names::data, section_names::lazySymbolPtr) { align = target->wordSize; @@ -1423,8 +1502,8 @@ remove(xarPath); } -CStringSection::CStringSection() - : SyntheticSection(segment_names::text, section_names::cString) { +CStringSection::CStringSection(const char *name) + : SyntheticSection(segment_names::text, name) { flags = S_CSTRING_LITERALS; } @@ -1550,6 +1629,16 @@ } } +DeduplicatedCStringSection::StringOffset +DeduplicatedCStringSection::getStringOffset(StringRef str) const { + // StringPiece uses 31 bits to store the hashes, so we replicate that + uint32_t hash = xxHash64(str) & 0x7fffffff; + auto offset = stringOffsetMap.find(CachedHashStringRef(str, hash)); + assert(offset != stringOffsetMap.end() && + "Looked-up strings should always exist in section"); + return offset->second; +} + // This section is actually emitted as __TEXT,__const by ld64, but clang may // emit input sections of that name, and LLD doesn't currently support mixing // synthetic and concat-type OutputSections. To work around this, I've given diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h --- a/lld/MachO/Target.h +++ b/lld/MachO/Target.h @@ -57,6 +57,12 @@ virtual void writeStubHelperEntry(uint8_t *buf, const Symbol &, uint64_t entryAddr) const = 0; + virtual void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, + uint64_t stubsAddr, uint64_t stubOffset, + uint64_t selrefsVA, uint64_t selectorIndex, + uint64_t gotAddr, + uint64_t msgSendIndex) const = 0; + // Symbols may be referenced via either the GOT or the stubs section, // depending on the relocation type. prepareSymbolRelocation() will set up the // GOT/stubs entries, and resolveSymbolVA() will return the addresses of those @@ -104,6 +110,8 @@ size_t stubSize; size_t stubHelperHeaderSize; size_t stubHelperEntrySize; + size_t objcStubsFastSize; + size_t objcStubsAlignment; uint8_t p2WordSize; size_t wordSize; diff --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp --- a/lld/MachO/Writer.cpp +++ b/lld/MachO/Writer.cpp @@ -695,6 +695,9 @@ continue; dysym->getFile()->refState = std::max(dysym->getFile()->refState, dysym->getRefState()); + } else if (const auto *undefined = dyn_cast(sym)) { + if (sym->getName().startswith(ObjCStubsSection::symbolPrefix)) + in.objcStubs->addEntry(sym); } } @@ -1165,6 +1168,8 @@ // these two scan* methods. I.e. from this point onward, for all live // InputSections, we should have `isec->canonical() == isec`. scanSymbols(); + if (in.objcStubs->isNeeded()) + in.objcStubs->setup(); scanRelocations(); // Do not proceed if there was an undefined symbol. @@ -1208,9 +1213,12 @@ void macho::createSyntheticSections() { in.header = make(); if (config->dedupLiterals) - in.cStringSection = make(); + in.cStringSection = + make(section_names::cString); else - in.cStringSection = make(); + in.cStringSection = make(section_names::cString); + in.objcMethnameSection = + make(section_names::objcMethname); in.wordLiteralSection = config->dedupLiterals ? make() : nullptr; in.rebase = make(); @@ -1223,6 +1231,7 @@ in.lazyPointers = make(); in.stubs = make(); in.stubHelper = make(); + in.objcStubs = make(); in.unwindInfo = makeUnwindInfoSection(); in.objCImageInfo = make(); diff --git a/lld/test/MachO/arm64-objc-stubs.s b/lld/test/MachO/arm64-objc-stubs.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/arm64-objc-stubs.s @@ -0,0 +1,60 @@ +# REQUIRES: aarch64 + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o +# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -dead_strip +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_fast +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s +# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_small 2>&1 | FileCheck %s --check-prefix=WARNING +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s + +# WARNING: warning: -objc_stubs_small is not yet implemented, defaulting to -objc_stubs_fast + +# CHECK: Contents of (__TEXT,__objc_stubs) section + +# CHECK-NEXT: _objc_msgSend$foo: +# CHECK-NEXT: adrp x1, 8 ; 0x100008000 +# CHECK-NEXT: ldr x1, [x1, #0x10] +# CHECK-NEXT: adrp x16, 4 ; 0x100004000 +# CHECK-NEXT: ldr x16, [x16] +# CHECK-NEXT: br x16 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 + +# CHECK-NEXT: _objc_msgSend$length: +# CHECK-NEXT: adrp x1, 8 ; 0x100008000 +# CHECK-NEXT: ldr x1, [x1, #0x18] +# CHECK-NEXT: adrp x16, 4 ; 0x100004000 +# CHECK-NEXT: ldr x16, [x16] +# CHECK-NEXT: br x16 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 +# CHECK-NEXT: brk #0x1 + +# CHECK-EMPTY: + +.section __TEXT,__objc_methname,cstring_literals +lselref1: + .asciz "foo" +lselref2: + .asciz "bar" + +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +.p2align 3 +.quad lselref1 +.quad lselref2 + +.text +.globl _objc_msgSend +_objc_msgSend: + ret + +.globl _main +_main: + bl _objc_msgSend$length + bl _objc_msgSend$foo + bl _objc_msgSend$foo + ret diff --git a/lld/test/MachO/objc-methname.s b/lld/test/MachO/objc-methname.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/objc-methname.s @@ -0,0 +1,44 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/strings.s -o %t/strings.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/main.s -o %t/main.o + +# RUN: %lld -arch arm64 -lSystem -o %t.out %t/strings.o %t/main.o + +# RUN: llvm-otool -vs __TEXT __cstring %t.out | FileCheck %s --check-prefix=CSTRING +# RUN: llvm-otool -vs __TEXT __objc_methname %t.out | FileCheck %s --check-prefix=METHNAME + +# RUN: %lld -arch arm64 -lSystem -o %t/duplicates %t/strings.o %t/strings.o %t/main.o --deduplicate-literals + +# RUN: llvm-otool -vs __TEXT __cstring %t/duplicates | FileCheck %s --check-prefix=CSTRING +# RUN: llvm-otool -vs __TEXT __objc_methname %t/duplicates | FileCheck %s --check-prefix=METHNAME + +# CSTRING: Contents of (__TEXT,__cstring) section +# CSTRING-NEXT: existing-cstring +# CSTIRNG-EMPTY: + +# METHNAME: Contents of (__TEXT,__objc_methname) section +# METHNAME-NEXT: existing_methname +# METHNAME-NEXT: synthetic_methname +# METHNAME-EMPTY: + +#--- strings.s +.cstring +.p2align 2 + .asciz "existing-cstring" + +.section __TEXT,__objc_methname,cstring_literals + .asciz "existing_methname" + +#--- main.s +.text +.globl _objc_msgSend +_objc_msgSend: + ret + +.globl _main +_main: + bl _objc_msgSend$existing_methname + bl _objc_msgSend$synthetic_methname + ret diff --git a/lld/test/MachO/objc-selrefs.s b/lld/test/MachO/objc-selrefs.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/objc-selrefs.s @@ -0,0 +1,50 @@ +# REQUIRES: aarch64 +# RUN: rm -rf %t; split-file %s %t + +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/existing.s -o %t/existing.o +# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/main.s -o %t/main.o + +# RUN: %lld -arch arm64 -lSystem -o %t/out %t/existing.o %t/main.o +# RUN: llvm-otool -vs __DATA __objc_selrefs %t/out | FileCheck %s --check-prefix=SELREFS + +# SELREFS: Contents of (__DATA,__objc_selrefs) section +# SELREFS-NEXT: __TEXT:__objc_methname:foo +# SELREFS-NEXT: __TEXT:__objc_methname:bar +# SELREFS-NEXT: __TEXT:__objc_methname:foo +# SELREFS-NEXT: __TEXT:__objc_methname:length +# SELREFS-EMPTY: + +# RUN: %lld -arch arm64 -lSystem -o %t/out %t/existing.o %t/main.o --deduplicate-literals +# RUN: llvm-otool -vs __DATA __objc_selrefs %t/out | FileCheck %s --check-prefix=DEDUP + +# DEDUP: Contents of (__DATA,__objc_selrefs) section +# DEDUP-NEXT: __TEXT:__objc_methname:foo +# DEDUP-NEXT: __TEXT:__objc_methname:bar +# NOTE: Ideally this wouldn't exist, but while it does it needs to point to the deduplicated string +# DEDUP-NEXT: __TEXT:__objc_methname:foo +# DEDUP-NEXT: __TEXT:__objc_methname:length +# DEDUP-EMPTY: + +#--- existing.s +.section __TEXT,__objc_methname,cstring_literals +lselref1: + .asciz "foo" +lselref2: + .asciz "bar" + +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +.p2align 3 +.quad lselref1 +.quad lselref2 + +#--- main.s +.text +.globl _objc_msgSend +_objc_msgSend: + ret + +.globl _main +_main: + bl _objc_msgSend$length + bl _objc_msgSend$foo + ret diff --git a/lld/test/MachO/x86-64-objc-stubs.s b/lld/test/MachO/x86-64-objc-stubs.s new file mode 100644 --- /dev/null +++ b/lld/test/MachO/x86-64-objc-stubs.s @@ -0,0 +1,40 @@ +# REQUIRES: x86 + +# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o +# RUN: %lld -arch x86_64 -lSystem -o %t.out %t.o +# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s + +# CHECK: Contents of (__TEXT,__objc_stubs) section + +# CHECK-NEXT: _objc_msgSend$foo: +# CHECK-NEXT: 00000001000004b8 movq 0x1b51(%rip), %rsi +# CHECK-NEXT: 00000001000004bf jmpq *0xb3b(%rip) + +# CHECK-NEXT: _objc_msgSend$length: +# CHECK-NEXT: 00000001000004c5 movq 0x1b4c(%rip), %rsi +# CHECK-NEXT: 00000001000004cc jmpq *0xb2e(%rip) + +# CHECK-EMPTY: + +.section __TEXT,__objc_methname,cstring_literals +lselref1: + .asciz "foo" +lselref2: + .asciz "bar" + +.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip +.p2align 3 +.quad lselref1 +.quad lselref2 + +.text +.globl _objc_msgSend +_objc_msgSend: + ret + +.globl _main +_main: + callq _objc_msgSend$length + callq _objc_msgSend$foo + callq _objc_msgSend$foo + ret