[lld] 3c24fae - [lld-macho] Add support for objc_msgSend stubs

Keith Smiley via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 10 17:17:34 PDT 2022


Author: Keith Smiley
Date: 2022-08-10T17:17:17-07:00
New Revision: 3c24fae3986aea9a920025c87d0feb3d03c6e876

URL: https://github.com/llvm/llvm-project/commit/3c24fae3986aea9a920025c87d0feb3d03c6e876
DIFF: https://github.com/llvm/llvm-project/commit/3c24fae3986aea9a920025c87d0feb3d03c6e876.diff

LOG: [lld-macho] Add support for objc_msgSend stubs

Apple Clang in Xcode 14 introduced a new feature for reducing the
overhead of objc_msgSend calls by deduplicating the setup calls for each
individual selector. This works by clang adding undefined symbols for
each selector called in a translation unit, such as `_objc_msgSend$foo`
for calling the `foo` method on any `NSObject`. There are 2
different modes for this behavior, the default directly does the setup
for `_objc_msgSend` and calls it, and the smaller option does the
selector setup, and then calls the standard `_objc_msgSend` stub
function.

The general overview of how this works is:

- Undefined symbols with the given prefix are collected
- The suffix of each matching undefined symbol is added as a string to
  `__objc_methname`
- A pointer is added for every method name in the `__objc_selrefs`
  section
- A `got` entry is emitted for `_objc_msgSend`
- Stubs are emitting pointing to the synthesized locations

Notes:

- Both `__objc_methname` and `__objc_selrefs` can also exist from object
  files, so their contents are merged with our synthesized contents
- The compiler emits method names for defined methods, but not for
  undefined symbols you call, but stubs are used for both
- This only implements the default "fast" mode currently just to reduce
  the diff, I also doubt many folks will care to swap modes
- This only implements this for arm64 and x86_64, we don't need to
  implement this for 32 bit iOS archs, but we should implement it for
  watchOS archs in a later diff

Differential Revision: https://reviews.llvm.org/D128108

Added: 
    lld/test/MachO/arm64-objc-stubs.s
    lld/test/MachO/objc-methname.s
    lld/test/MachO/objc-selrefs.s
    lld/test/MachO/x86-64-objc-stubs.s

Modified: 
    lld/MachO/Arch/ARM.cpp
    lld/MachO/Arch/ARM64.cpp
    lld/MachO/Arch/ARM64Common.h
    lld/MachO/Arch/ARM64_32.cpp
    lld/MachO/Arch/X86_64.cpp
    lld/MachO/Config.h
    lld/MachO/Driver.cpp
    lld/MachO/InputFiles.cpp
    lld/MachO/InputSection.cpp
    lld/MachO/InputSection.h
    lld/MachO/Options.td
    lld/MachO/SyntheticSections.cpp
    lld/MachO/SyntheticSections.h
    lld/MachO/Target.h
    lld/MachO/Writer.cpp

Removed: 
    


################################################################################
diff  --git a/lld/MachO/Arch/ARM.cpp b/lld/MachO/Arch/ARM.cpp
index 424df414229f3..5cfc9e3351419 100644
--- a/lld/MachO/Arch/ARM.cpp
+++ b/lld/MachO/Arch/ARM.cpp
@@ -37,6 +37,11 @@ struct ARM : TargetInfo {
   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 @@ void ARM::writeStubHelperEntry(uint8_t *buf, const Symbol &sym,
   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
index 0065752545e3f..efa1727986be8 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -34,6 +34,11 @@ struct ARM64 : ARM64Common {
   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<uint64_t>) const override;
@@ -100,6 +105,26 @@ void ARM64::writeStubHelperEntry(uint8_t *buf8, const Symbol &sym,
   ::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA);
 }
 
+static constexpr uint32_t objcStubsFastCode[] = {
+    0x90000001, // adrp  x1, __objc_selrefs at page
+    0xf9400021, // ldr   x1, [x1, @selector("foo")@pageoff]
+    0x90000010, // adrp  x16, _got at page
+    0xf9400210, // ldr   x16, [x16, _objc_msgSend at 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<LP64>(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 @@ ARM64::ARM64() : ARM64Common(LP64()) {
   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
index 8b75a5f3160d0..6bd0039bd9f4b 100644
--- a/lld/MachO/Arch/ARM64Common.h
+++ b/lld/MachO/Arch/ARM64Common.h
@@ -147,6 +147,32 @@ inline void writeStubHelperEntry(uint8_t *buf8,
   buf32[2] = sym.lazyBindOffset;
 }
 
+template <class LP>
+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<uint32_t *>(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
index 4830c26297618..daeda0136dc9e 100644
--- a/lld/MachO/Arch/ARM64_32.cpp
+++ b/lld/MachO/Arch/ARM64_32.cpp
@@ -33,6 +33,10 @@ struct ARM64_32 : ARM64Common {
   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 @@ void ARM64_32::writeStubHelperEntry(uint8_t *buf8, const Symbol &sym,
   ::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
index b1c46cbab9d4e..e5bdd89399404 100644
--- a/lld/MachO/Arch/X86_64.cpp
+++ b/lld/MachO/Arch/X86_64.cpp
@@ -36,6 +36,11 @@ struct X86_64 : TargetInfo {
   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 @@ void X86_64::writeStubHelperEntry(uint8_t *buf, const Symbol &sym,
                    sizeof(stubHelperEntry), in.stubHelper->addr);
 }
 
+static constexpr uint8_t objcStubsFastCode[] = {
+    0x48, 0x8b, 0x35, 0, 0, 0, 0, // 0x0: movq selrefs at selector(%rip), %rsi
+    0xff, 0x25, 0,    0, 0, 0,    // 0x7: jmpq *_objc_msgSend at 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 @@ X86_64::X86_64() : TargetInfo(LP64()) {
   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
index 4ec5f03e8a0e9..8f2d790603c63 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -68,6 +68,11 @@ enum class ICFLevel {
   all,
 };
 
+enum class ObjCStubsMode {
+  fast,
+  small,
+};
+
 struct SectionAlign {
   llvm::StringRef segName;
   llvm::StringRef sectName;
@@ -166,6 +171,7 @@ struct Configuration {
   UndefinedSymbolTreatment undefinedSymbolTreatment =
       UndefinedSymbolTreatment::error;
   ICFLevel icfLevel = ICFLevel::none;
+  ObjCStubsMode objcStubsMode = ObjCStubsMode::fast;
   llvm::MachO::HeaderFileType outputType;
   std::vector<llvm::StringRef> systemLibraryRoots;
   std::vector<llvm::StringRef> librarySearchPaths;

diff  --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 218e0d5a92e65..fe3b1a4d6e758 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -781,6 +781,17 @@ static ICFLevel getICFLevel(const ArgList &args) {
   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 @@ static void gatherInputSections() {
           inputSections.push_back(isec);
         } else if (auto *isec =
                        dyn_cast<CStringInputSection>(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<WordLiteralInputSection>(subsection.isec)) {
           if (in.wordLiteralSection->inputOrder == UnspecifiedInputOrder)
@@ -1124,10 +1141,39 @@ static void foldIdenticalLiterals() {
   // 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<std::string>();
+  llvm::raw_string_ostream os(data);
+  const int prefixLength = ObjCStubsSection::symbolPrefix.size();
+  for (Symbol *sym : symtab->getSymbols())
+    if (const auto *undefined = dyn_cast<Undefined>(sym))
+      if (sym->getName().startswith(ObjCStubsSection::symbolPrefix))
+        os << sym->getName().drop_front(prefixLength) << '\0';
+
+  if (data.empty())
+    return;
+
+  const auto *buf = reinterpret_cast<const uint8_t *>(data.c_str());
+  Section &section = *make<Section>(/*file=*/nullptr, segment_names::text,
+                                    section_names::objcMethname,
+                                    S_CSTRING_LITERALS, /*addr=*/0);
+
+  auto *isec =
+      make<CStringInputSection>(section, ArrayRef<uint8_t>{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 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
   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 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
 
     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
index 1f1cf5aa00094..62f27c7262a8e 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -342,7 +342,10 @@ void ObjFile::parseSections(ArrayRef<SectionHeader> sectionHeaders) {
 
       InputSection *isec;
       if (sectionType(sec.flags) == S_CSTRING_LITERALS) {
-        isec = make<CStringInputSection>(section, data, align);
+        isec = make<CStringInputSection>(section, data, align,
+                                         /*dedupLiterals=*/name ==
+                                                 section_names::objcMethname ||
+                                             config->dedupLiterals);
         // FIXME: parallelize this?
         cast<CStringInputSection>(isec)->splitIntoPieces();
       } else {

diff  --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index ca073f8ac6f39..83e1583bc6678 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -251,7 +251,7 @@ void CStringInputSection::splitIntoPieces() {
     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/InputSection.h b/lld/MachO/InputSection.h
index 3982cbf309861..0f79bdfd22648 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -192,8 +192,10 @@ static_assert(sizeof(StringPiece) == 16, "StringPiece is too big!");
 class CStringInputSection final : public InputSection {
 public:
   CStringInputSection(const Section &section, ArrayRef<uint8_t> 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 @@ class CStringInputSection final : public InputSection {
   // 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 @@ class CStringInputSection final : public InputSection {
     return isec->kind() == CStringLiteralKind;
   }
 
+  bool deduplicateLiterals = false;
   std::vector<StringPiece> pieces;
 };
 
@@ -323,6 +326,9 @@ constexpr const char objcClassList[] = "__objc_classlist";
 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/Options.td b/lld/MachO/Options.td
index d97d72a118bb1..cdc33911b5736 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -978,6 +978,12 @@ def mcpu : Separate<["-"], "mcpu">,
 def no_dtrace_dof : Flag<["-"], "no_dtrace_dof">,
     HelpText<"Disable dtrace-dof processing (default).">,
     Group<grp_rare>;
+def objc_stubs_fast : Flag<["-"], "objc_stubs_fast">,
+    HelpText<"Produce larger stubs for Objective-C method calls with fewer jumps (default).">,
+    Group<grp_rare>;
+def objc_stubs_small : Flag<["-"], "objc_stubs_small">,
+    HelpText<"Produce smaller stubs for Objective-C method calls with more jumps.">,
+    Group<grp_rare>;
 
 def grp_deprecated : OptionGroup<"deprecated">, HelpText<"DEPRECATED">;
 

diff  --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index e8a31f904b440..d242dc2b1df4d 100644
--- 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 <sys/mman.h>
@@ -716,6 +717,84 @@ void StubHelperSection::setup() {
   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<Defined>(
+      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<uint8_t>(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<uint8_t>{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<uint32_t>(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 @@ void BitcodeBundleSection::writeTo(uint8_t *buf) const {
   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 @@ void DeduplicatedCStringSection::writeTo(uint8_t *buf) const {
   }
 }
 
+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/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index 1d2f219973c68..44461dc73006f 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -310,6 +310,30 @@ class StubHelperSection final : public SyntheticSection {
   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<Defined *> symbols;
+  std::vector<uint32_t> 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 BitcodeBundleSection final : public SyntheticSection {
 
 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 CStringSection : public SyntheticSection {
 
 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<llvm::CachedHashStringRef, StringOffset> stringOffsetMap;
   size_t size = 0;
 };
@@ -623,6 +651,7 @@ struct InStruct {
   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 @@ struct InStruct {
   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/Target.h b/lld/MachO/Target.h
index 61fabf4060152..037cd0775ee48 100644
--- a/lld/MachO/Target.h
+++ b/lld/MachO/Target.h
@@ -57,6 +57,12 @@ class TargetInfo {
   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 @@ class TargetInfo {
   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
index 3c44a60f4be26..4addaab815998 100644
--- a/lld/MachO/Writer.cpp
+++ b/lld/MachO/Writer.cpp
@@ -695,6 +695,9 @@ void Writer::scanSymbols() {
         continue;
       dysym->getFile()->refState =
           std::max(dysym->getFile()->refState, dysym->getRefState());
+    } else if (const auto *undefined = dyn_cast<Undefined>(sym)) {
+      if (sym->getName().startswith(ObjCStubsSection::symbolPrefix))
+        in.objcStubs->addEntry(sym);
     }
   }
 
@@ -1165,6 +1168,8 @@ template <class LP> void Writer::run() {
   // 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::resetWriter() { LCDylib::resetInstanceCount(); }
 void macho::createSyntheticSections() {
   in.header = make<MachHeaderSection>();
   if (config->dedupLiterals)
-    in.cStringSection = make<DeduplicatedCStringSection>();
+    in.cStringSection =
+        make<DeduplicatedCStringSection>(section_names::cString);
   else
-    in.cStringSection = make<CStringSection>();
+    in.cStringSection = make<CStringSection>(section_names::cString);
+  in.objcMethnameSection =
+      make<DeduplicatedCStringSection>(section_names::objcMethname);
   in.wordLiteralSection =
       config->dedupLiterals ? make<WordLiteralSection>() : nullptr;
   in.rebase = make<RebaseSection>();
@@ -1223,6 +1231,7 @@ void macho::createSyntheticSections() {
   in.lazyPointers = make<LazyPointerSection>();
   in.stubs = make<StubsSection>();
   in.stubHelper = make<StubHelperSection>();
+  in.objcStubs = make<ObjCStubsSection>();
   in.unwindInfo = makeUnwindInfoSection();
   in.objCImageInfo = make<ObjCImageInfoSection>();
 

diff  --git a/lld/test/MachO/arm64-objc-stubs.s b/lld/test/MachO/arm64-objc-stubs.s
new file mode 100644
index 0000000000000..feba40ac36d84
--- /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
index 0000000000000..62387e14cc6bc
--- /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
index 0000000000000..e9ec6ca483e00
--- /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
index 0000000000000..f29326939c7f6
--- /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


        


More information about the llvm-commits mailing list