[lld] a3f67f0 - [lld-macho] Initial support for Linker Optimization Hints

Daniel Bertalan via llvm-commits llvm-commits at lists.llvm.org
Wed Jun 29 21:29:38 PDT 2022


Author: Daniel Bertalan
Date: 2022-06-30T06:28:42+02:00
New Revision: a3f67f0920eaa111637b3411209213f46de202d7

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

LOG: [lld-macho] Initial support for Linker Optimization Hints

Linker optimization hints mark a sequence of instructions used for
synthesizing an address, like ADRP+ADD. If the referenced symbol ends up
close enough, it can be replaced by a faster sequence of instructions
like ADR+NOP.

This commit adds support for 2 of the 7 defined ARM64 optimization
hints:
- LOH_ARM64_ADRP_ADD, which transforms a pair of ADRP+ADD into ADR+NOP
  if the referenced address is within +/- 1 MiB
- LOH_ARM64_ADRP_ADRP, which transforms two ADRP instructions into
  ADR+NOP if they reference the same page

These two kinds already cover more than 50% of all LOHs in
chromium_framework.

Differential Review: https://reviews.llvm.org/D128093

Added: 
    lld/test/MachO/invalid/invalid-loh.s
    lld/test/MachO/loh-adrp-add.s
    lld/test/MachO/loh-adrp-adrp.s

Modified: 
    lld/MachO/Arch/ARM64.cpp
    lld/MachO/Config.h
    lld/MachO/Driver.cpp
    lld/MachO/InputFiles.cpp
    lld/MachO/InputFiles.h
    lld/MachO/InputSection.cpp
    lld/MachO/InputSection.h
    lld/MachO/Options.td
    lld/MachO/Relocations.h
    lld/MachO/Target.h
    llvm/include/llvm/BinaryFormat/MachO.h

Removed: 
    


################################################################################
diff  --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp
index e5b8a1b722f66..8c0c2d7e716ad 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -36,6 +36,8 @@ struct ARM64 : ARM64Common {
                             uint64_t entryAddr) const override;
   const RelocAttrs &getRelocAttrs(uint8_t type) const override;
   void populateThunk(InputSection *thunk, Symbol *funcSym) override;
+  void applyOptimizationHints(uint8_t *, const ConcatInputSection *,
+                              ArrayRef<uint64_t>) const override;
 };
 
 } // namespace
@@ -150,6 +152,207 @@ ARM64::ARM64() : ARM64Common(LP64()) {
   stubHelperEntrySize = sizeof(stubHelperEntryCode);
 }
 
+namespace {
+struct Adrp {
+  uint32_t destRegister;
+};
+
+struct Add {
+  uint8_t destRegister;
+  uint8_t srcRegister;
+  uint32_t addend;
+};
+
+struct PerformedReloc {
+  const Reloc &rel;
+  uint64_t referentVA;
+};
+
+class OptimizationHintContext {
+public:
+  OptimizationHintContext(uint8_t *buf, const ConcatInputSection *isec,
+                          ArrayRef<uint64_t> relocTargets)
+      : buf(buf), isec(isec), relocTargets(relocTargets),
+        relocIt(isec->relocs.rbegin()) {}
+
+  void applyAdrpAdd(const OptimizationHint &);
+  void applyAdrpAdrp(const OptimizationHint &);
+
+private:
+  uint8_t *buf;
+  const ConcatInputSection *isec;
+  ArrayRef<uint64_t> relocTargets;
+  std::vector<Reloc>::const_reverse_iterator relocIt;
+
+  uint64_t getRelocTarget(const Reloc &);
+
+  Optional<PerformedReloc> findPrimaryReloc(uint64_t offset);
+  Optional<PerformedReloc> findReloc(uint64_t offset);
+};
+} // namespace
+
+static bool parseAdrp(uint32_t insn, Adrp &adrp) {
+  if ((insn & 0x9f000000) != 0x90000000)
+    return false;
+  adrp.destRegister = insn & 0x1f;
+  return true;
+}
+
+static bool parseAdd(uint32_t insn, Add &add) {
+  if ((insn & 0xffc00000) != 0x91000000)
+    return false;
+  add.destRegister = insn & 0x1f;
+  add.srcRegister = (insn >> 5) & 0x1f;
+  add.addend = (insn >> 10) & 0xfff;
+  return true;
+}
+
+static void writeAdr(void *loc, uint32_t dest, int32_t delta) {
+  uint32_t opcode = 0x10000000;
+  uint32_t immHi = (delta & 0x001ffffc) << 3;
+  uint32_t immLo = (delta & 0x00000003) << 29;
+  write32le(loc, opcode | immHi | immLo | dest);
+}
+
+static void writeNop(void *loc) { write32le(loc, 0xd503201f); }
+
+uint64_t OptimizationHintContext::getRelocTarget(const Reloc &reloc) {
+  size_t relocIdx = &reloc - isec->relocs.data();
+  return relocTargets[relocIdx];
+}
+
+// Optimization hints are sorted in a monotonically increasing order by their
+// first address as are relocations (albeit in decreasing order), so if we keep
+// a pointer around to the last found relocation, we don't have to do a full
+// binary search every time.
+Optional<PerformedReloc>
+OptimizationHintContext::findPrimaryReloc(uint64_t offset) {
+  const auto end = isec->relocs.rend();
+  while (relocIt != end && relocIt->offset < offset)
+    ++relocIt;
+  if (relocIt == end || relocIt->offset != offset)
+    return None;
+  return PerformedReloc{*relocIt, getRelocTarget(*relocIt)};
+}
+
+// The second and third addresses of optimization hints have no such
+// monotonicity as the first, so we search the entire range of relocations.
+Optional<PerformedReloc> OptimizationHintContext::findReloc(uint64_t offset) {
+  // Optimization hints often apply to successive relocations, so we check for
+  // that first before doing a full binary search.
+  auto end = isec->relocs.rend();
+  if (relocIt < end - 1 && (relocIt + 1)->offset == offset)
+    return PerformedReloc{*(relocIt + 1), getRelocTarget(*(relocIt + 1))};
+
+  auto reloc = lower_bound(isec->relocs, offset,
+                           [](const Reloc &reloc, uint64_t offset) {
+                             return offset < reloc.offset;
+                           });
+
+  if (reloc == isec->relocs.end() || reloc->offset != offset)
+    return None;
+  return PerformedReloc{*reloc, getRelocTarget(*reloc)};
+}
+
+// Transforms a pair of adrp+add instructions into an adr instruction if the
+// target is within the +/- 1 MiB range allowed by the adr's 21 bit signed
+// immediate offset.
+//
+//   adrp xN, _foo at PAGE
+//   add  xM, xN, _foo at PAGEOFF
+// ->
+//   adr  xM, _foo
+//   nop
+void OptimizationHintContext::applyAdrpAdd(const OptimizationHint &hint) {
+  uint32_t ins1 = read32le(buf + hint.offset0);
+  uint32_t ins2 = read32le(buf + hint.offset0 + hint.delta[0]);
+  Adrp adrp;
+  if (!parseAdrp(ins1, adrp))
+    return;
+  Add add;
+  if (!parseAdd(ins2, add))
+    return;
+  if (adrp.destRegister != add.srcRegister)
+    return;
+
+  Optional<PerformedReloc> rel1 = findPrimaryReloc(hint.offset0);
+  Optional<PerformedReloc> rel2 = findReloc(hint.offset0 + hint.delta[0]);
+  if (!rel1 || !rel2)
+    return;
+  if (rel1->referentVA != rel2->referentVA)
+    return;
+  int64_t delta = rel1->referentVA - rel1->rel.offset - isec->getVA();
+  if (delta >= (1 << 20) || delta < -(1 << 20))
+    return;
+
+  writeAdr(buf + hint.offset0, add.destRegister, delta);
+  writeNop(buf + hint.offset0 + hint.delta[0]);
+}
+
+// Transforms two adrp instructions into a single adrp if their referent
+// addresses are located on the same 4096 byte page.
+//
+//   adrp xN, _foo at PAGE
+//   adrp xN, _bar at PAGE
+// ->
+//   adrp xN, _foo at PAGE
+//   nop
+void OptimizationHintContext::applyAdrpAdrp(const OptimizationHint &hint) {
+  uint32_t ins1 = read32le(buf + hint.offset0);
+  uint32_t ins2 = read32le(buf + hint.offset0 + hint.delta[0]);
+  Adrp adrp1, adrp2;
+  if (!parseAdrp(ins1, adrp1) || !parseAdrp(ins2, adrp2))
+    return;
+  if (adrp1.destRegister != adrp2.destRegister)
+    return;
+
+  Optional<PerformedReloc> rel1 = findPrimaryReloc(hint.offset0);
+  Optional<PerformedReloc> rel2 = findReloc(hint.offset0 + hint.delta[0]);
+  if (!rel1 || !rel2)
+    return;
+  if ((rel1->referentVA & ~0xfffULL) != (rel2->referentVA & ~0xfffULL))
+    return;
+
+  writeNop(buf + hint.offset0 + hint.delta[0]);
+}
+
+void ARM64::applyOptimizationHints(uint8_t *buf, const ConcatInputSection *isec,
+                                   ArrayRef<uint64_t> relocTargets) const {
+  assert(isec);
+  assert(relocTargets.size() == isec->relocs.size());
+
+  // Note: Some of these optimizations might not be valid when shared regions
+  // are in use. Will need to revisit this if splitSegInfo is added.
+
+  OptimizationHintContext ctx1(buf, isec, relocTargets);
+  for (const OptimizationHint &hint : isec->optimizationHints) {
+    switch (hint.type) {
+    case LOH_ARM64_ADRP_ADRP:
+      // This is done in another pass because the other optimization hints
+      // might cause its targets to be turned into NOPs.
+      break;
+    case LOH_ARM64_ADRP_LDR:
+    case LOH_ARM64_ADRP_ADD_LDR:
+    case LOH_ARM64_ADRP_LDR_GOT_LDR:
+    case LOH_ARM64_ADRP_ADD_STR:
+    case LOH_ARM64_ADRP_LDR_GOT_STR:
+      // TODO: Implement these
+      break;
+    case LOH_ARM64_ADRP_ADD:
+      ctx1.applyAdrpAdd(hint);
+      break;
+    case LOH_ARM64_ADRP_LDR_GOT:
+      // TODO: Implement this as well
+      break;
+    }
+  }
+
+  OptimizationHintContext ctx2(buf, isec, relocTargets);
+  for (const OptimizationHint &hint : isec->optimizationHints)
+    if (hint.type == LOH_ARM64_ADRP_ADRP)
+      ctx2.applyAdrpAdrp(hint);
+}
+
 TargetInfo *macho::createARM64TargetInfo() {
   static ARM64 t;
   return &t;

diff  --git a/lld/MachO/Config.h b/lld/MachO/Config.h
index 206509c3df83f..b6c6abb44c656 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -130,6 +130,7 @@ struct Configuration {
   bool dedupLiterals = true;
   bool omitDebugInfo = false;
   bool warnDylibInstallName = false;
+  bool ignoreOptimizationHints = false;
   // Temporary config flag that will be removed once we have fully implemented
   // support for __eh_frame.
   bool parseEhFrames = false;

diff  --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index badab058c84aa..708facd180baa 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1301,6 +1301,7 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
       config->icfLevel != ICFLevel::none;
   config->warnDylibInstallName = args.hasFlag(
       OPT_warn_dylib_install_name, OPT_no_warn_dylib_install_name, false);
+  config->ignoreOptimizationHints = args.hasArg(OPT_ignore_optimization_hints);
   config->callGraphProfileSort = args.hasFlag(
       OPT_call_graph_profile_sort, OPT_no_call_graph_profile_sort, true);
   config->printSymbolOrder = args.getLastArgValue(OPT_print_symbol_order);

diff  --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 439a1fe032626..c53874133a788 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -65,6 +65,7 @@
 #include "llvm/LTO/LTO.h"
 #include "llvm/Support/BinaryStreamReader.h"
 #include "llvm/Support/Endian.h"
+#include "llvm/Support/LEB128.h"
 #include "llvm/Support/MemoryBuffer.h"
 #include "llvm/Support/Path.h"
 #include "llvm/Support/TarWriter.h"
@@ -449,6 +450,154 @@ static Defined *findSymbolAtOffset(const ConcatInputSection *isec,
   return *it;
 }
 
+// Linker optimization hints mark a sequence of instructions used for
+// synthesizing an address which that be transformed into a faster sequence. The
+// transformations depend on conditions that are determined at link time, like
+// the distance to the referenced symbol or its alignment.
+//
+// Each hint has a type and refers to 2 or 3 instructions. Each of those
+// instructions must have a corresponding relocation. After addresses have been
+// finalized and relocations have been performed, we check if the requirements
+// hold, and perform the optimizations if they do.
+//
+// Similar linker relaxations exist for ELF as well, with the 
diff erence being
+// that the explicit marking allows for the relaxation of non-consecutive
+// relocations too.
+//
+// The specific types of hints are documented in Arch/ARM64.cpp
+void ObjFile::parseOptimizationHints(ArrayRef<uint8_t> data) {
+  auto expectedArgCount = [](uint8_t type) {
+    switch (type) {
+    case LOH_ARM64_ADRP_ADRP:
+    case LOH_ARM64_ADRP_LDR:
+    case LOH_ARM64_ADRP_ADD:
+    case LOH_ARM64_ADRP_LDR_GOT:
+      return 2;
+    case LOH_ARM64_ADRP_ADD_LDR:
+    case LOH_ARM64_ADRP_ADD_STR:
+    case LOH_ARM64_ADRP_LDR_GOT_LDR:
+    case LOH_ARM64_ADRP_LDR_GOT_STR:
+      return 3;
+    }
+    return -1;
+  };
+
+  // Each hint contains at least 4 ULEB128-encoded fields, so in the worst case,
+  // there are data.size() / 4 LOHs. It's a huge overestimation though, as
+  // offsets are unlikely to fall in the 0-127 byte range, so we pre-allocate
+  // half as much.
+  optimizationHints.reserve(data.size() / 8);
+
+  for (const uint8_t *p = data.begin(); p < data.end();) {
+    const ptr
diff _t inputOffset = p - data.begin();
+    unsigned int n = 0;
+    uint8_t type = decodeULEB128(p, &n, data.end());
+    p += n;
+
+    // An entry of type 0 terminates the list.
+    if (type == 0)
+      break;
+
+    int expectedCount = expectedArgCount(type);
+    if (LLVM_UNLIKELY(expectedCount == -1)) {
+      error("Linker optimization hint at offset " + Twine(inputOffset) +
+            " has unknown type " + Twine(type));
+      return;
+    }
+
+    uint8_t argCount = decodeULEB128(p, &n, data.end());
+    p += n;
+
+    if (LLVM_UNLIKELY(argCount != expectedCount)) {
+      error("Linker optimization hint at offset " + Twine(inputOffset) +
+            " has " + Twine(argCount) + " arguments instead of the expected " +
+            Twine(expectedCount));
+      return;
+    }
+
+    uint64_t offset0 = decodeULEB128(p, &n, data.end());
+    p += n;
+
+    int16_t delta[2];
+    for (int i = 0; i < argCount - 1; ++i) {
+      uint64_t address = decodeULEB128(p, &n, data.end());
+      p += n;
+      int64_t d = address - offset0;
+      if (LLVM_UNLIKELY(d > std::numeric_limits<int16_t>::max() ||
+                        d < std::numeric_limits<int16_t>::min())) {
+        error("Linker optimization hint at offset " + Twine(inputOffset) +
+              " has addresses too far apart");
+        return;
+      }
+      delta[i] = d;
+    }
+
+    optimizationHints.push_back({offset0, {delta[0], delta[1]}, type});
+  }
+
+  // We sort the per-object vector of optimization hints so each section only
+  // needs to hold an ArrayRef to a contiguous range of hints.
+  llvm::sort(optimizationHints,
+             [](const OptimizationHint &a, const OptimizationHint &b) {
+               return a.offset0 < b.offset0;
+             });
+
+  auto section = sections.begin();
+  auto subsection = (*section)->subsections.begin();
+  uint64_t subsectionBase = 0;
+  uint64_t subsectionEnd = 0;
+
+  auto updateAddr = [&]() {
+    subsectionBase = (*section)->addr + subsection->offset;
+    subsectionEnd = subsectionBase + subsection->isec->getSize();
+  };
+
+  auto advanceSubsection = [&]() {
+    if (section == sections.end())
+      return;
+    ++subsection;
+    if (subsection == (*section)->subsections.end()) {
+      ++section;
+      if (section == sections.end())
+        return;
+      subsection = (*section)->subsections.begin();
+    }
+  };
+
+  updateAddr();
+  auto hintStart = optimizationHints.begin();
+  for (auto hintEnd = hintStart, end = optimizationHints.end(); hintEnd != end;
+       ++hintEnd) {
+    if (hintEnd->offset0 >= subsectionEnd) {
+      subsection->isec->optimizationHints =
+          ArrayRef<OptimizationHint>(&*hintStart, hintEnd - hintStart);
+
+      hintStart = hintEnd;
+      while (hintStart->offset0 >= subsectionEnd) {
+        advanceSubsection();
+        if (section == sections.end())
+          break;
+        updateAddr();
+      }
+    }
+
+    hintEnd->offset0 -= subsectionBase;
+    for (int i = 0, count = expectedArgCount(hintEnd->type); i < count - 1;
+         ++i) {
+      if (LLVM_UNLIKELY(
+              hintEnd->delta[i] < -static_cast<int64_t>(hintEnd->offset0) ||
+              hintEnd->delta[i] >=
+                  static_cast<int64_t>(subsectionEnd - hintEnd->offset0))) {
+        error("Linker optimization hint spans multiple sections");
+        return;
+      }
+    }
+  }
+  if (section != sections.end())
+    subsection->isec->optimizationHints = ArrayRef<OptimizationHint>(
+        &*hintStart, optimizationHints.end() - hintStart);
+}
+
 template <class SectionHeader>
 static bool validateRelocationInfo(InputFile *file, const SectionHeader &sec,
                                    relocation_info rel) {
@@ -949,6 +1098,11 @@ template <class LP> void ObjFile::parse() {
     if (!sections[i]->subsections.empty())
       parseRelocations(sectionHeaders, sectionHeaders[i], *sections[i]);
 
+  if (!config->ignoreOptimizationHints)
+    if (auto *cmd = findCommand<linkedit_data_command>(
+            hdr, LC_LINKER_OPTIMIZATION_HINT))
+      parseOptimizationHints({buf + cmd->dataoff, cmd->datasize});
+
   parseDebugInfo();
 
   Section *ehFrameSection = nullptr;

diff  --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h
index c8e6332394000..524418b91ee1a 100644
--- a/lld/MachO/InputFiles.h
+++ b/lld/MachO/InputFiles.h
@@ -173,6 +173,7 @@ class ObjFile final : public InputFile {
   std::vector<ConcatInputSection *> debugSections;
   std::vector<CallGraphEntry> callGraph;
   llvm::DenseMap<ConcatInputSection *, FDE> fdes;
+  std::vector<OptimizationHint> optimizationHints;
 
 private:
   llvm::once_flag initDwarf;
@@ -188,6 +189,7 @@ class ObjFile final : public InputFile {
   void parseRelocations(ArrayRef<SectionHeader> sectionHeaders,
                         const SectionHeader &, Section &);
   void parseDebugInfo();
+  void parseOptimizationHints(ArrayRef<uint8_t> data);
   void splitEhFrames(ArrayRef<uint8_t> dataArr, Section &ehFrameSection);
   void registerCompactUnwind(Section &compactUnwindSection);
   void registerEhFrames(Section &ehFrameSection);

diff  --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index e683e0dbe3b7f..25eb878736d9f 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -29,8 +29,8 @@ using namespace lld::macho;
 // Verify ConcatInputSection's size on 64-bit builds. The size of std::vector
 // can 
diff er based on STL debug levels (e.g. iterator debugging on MSVC's STL),
 // so account for that.
-static_assert(sizeof(void *) != 8 ||
-                  sizeof(ConcatInputSection) == sizeof(std::vector<Reloc>) + 88,
+static_assert(sizeof(void *) != 8 || sizeof(ConcatInputSection) ==
+                                         sizeof(std::vector<Reloc>) + 104,
               "Try to minimize ConcatInputSection's size, we create many "
               "instances of it");
 
@@ -177,6 +177,10 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
 
   memcpy(buf, data.data(), data.size());
 
+  std::vector<uint64_t> relocTargets;
+  if (!optimizationHints.empty())
+    relocTargets.reserve(relocs.size());
+
   for (size_t i = 0; i < relocs.size(); i++) {
     const Reloc &r = relocs[i];
     uint8_t *loc = buf + r.offset;
@@ -212,7 +216,13 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
       referentVA = referentIsec->getVA(r.addend);
     }
     target->relocateOne(loc, r, referentVA, getVA() + r.offset);
+
+    if (!optimizationHints.empty())
+      relocTargets.push_back(referentVA);
   }
+
+  if (!optimizationHints.empty())
+    target->applyOptimizationHints(buf, this, relocTargets);
 }
 
 ConcatInputSection *macho::makeSyntheticInputSection(StringRef segName,

diff  --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index e7f8f10e32635..e8710c25f5347 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -83,6 +83,7 @@ class InputSection {
   OutputSection *parent = nullptr;
   ArrayRef<uint8_t> data;
   std::vector<Reloc> relocs;
+  ArrayRef<OptimizationHint> optimizationHints;
   // The symbols that belong to this InputSection, sorted by value. With
   // .subsections_via_symbols, there is typically only one element here.
   llvm::TinyPtrVector<Defined *> symbols;

diff  --git a/lld/MachO/Options.td b/lld/MachO/Options.td
index c3ccbec6c997b..9b57f8a0bd498 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -1257,8 +1257,7 @@ def ignore_auto_link : Flag<["-"], "ignore_auto_link">,
     Flags<[HelpHidden]>,
     Group<grp_undocumented>;
 def ignore_optimization_hints : Flag<["-"], "ignore_optimization_hints">,
-    HelpText<"This option is undocumented in ld64">,
-    Flags<[HelpHidden]>,
+    HelpText<"Ignore Linker Optimization Hints">,
     Group<grp_undocumented>;
 def init_offsets : Flag<["-"], "init_offsets">,
     HelpText<"This option is undocumented in ld64">,

diff  --git a/lld/MachO/Relocations.h b/lld/MachO/Relocations.h
index 3c134d55cb201..6c0475fe02000 100644
--- a/lld/MachO/Relocations.h
+++ b/lld/MachO/Relocations.h
@@ -70,6 +70,14 @@ struct Reloc {
         addend(addend), referent(referent) {}
 };
 
+struct OptimizationHint {
+  // Offset of the first address within the containing InputSection.
+  uint64_t offset0;
+  // Offset of the other addresses relative to the first one.
+  int16_t delta[2];
+  uint8_t type;
+};
+
 bool validateSymbolRelocation(const Symbol *, const InputSection *,
                               const Reloc &);
 

diff  --git a/lld/MachO/Target.h b/lld/MachO/Target.h
index e66a6966b59d7..09ff3c5639ea6 100644
--- a/lld/MachO/Target.h
+++ b/lld/MachO/Target.h
@@ -28,6 +28,7 @@ class Symbol;
 class Defined;
 class DylibSymbol;
 class InputSection;
+class ConcatInputSection;
 
 class TargetInfo {
 public:
@@ -78,6 +79,9 @@ class TargetInfo {
 
   bool usesThunks() const { return thunkSize > 0; }
 
+  virtual void applyOptimizationHints(uint8_t *buf, const ConcatInputSection *,
+                                      llvm::ArrayRef<uint64_t>) const {};
+
   uint32_t magic;
   llvm::MachO::CPUType cpuType;
   uint32_t cpuSubtype;

diff  --git a/lld/test/MachO/invalid/invalid-loh.s b/lld/test/MachO/invalid/invalid-loh.s
new file mode 100644
index 0000000000000..19ed52866948a
--- /dev/null
+++ b/lld/test/MachO/invalid/invalid-loh.s
@@ -0,0 +1,39 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/section.s -o %t/section.o
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/far.s -o %t/far.o
+# RUN: not %lld -arch arm64 %t/section.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=SECTION
+# RUN: not %lld -arch arm64 %t/far.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=FAR
+
+# SECTION: error: Linker optimization hint spans multiple sections
+# FAR:     error: Linker optimization hint at offset 0 has addresses too far apart
+
+#--- section.s
+.globl _main
+_main:
+L1:
+  adrp x0, _target at PAGE
+
+_foo:
+L2:
+  add x0, x0, _target at PAGEOFF
+
+_target:
+
+.loh AdrpAdd L1, L2
+.subsections_via_symbols
+
+#--- far.s
+.globl _main
+_main:
+L1:
+  adrp x0, _target at PAGE
+  .zero 0x8000
+L2:
+  add  x0, x0, _target at PAGEOFF
+
+_target:
+
+.loh AdrpAdd L1, L2
+.subsections_via_symbols

diff  --git a/lld/test/MachO/loh-adrp-add.s b/lld/test/MachO/loh-adrp-add.s
new file mode 100644
index 0000000000000..e5430b38e83d5
--- /dev/null
+++ b/lld/test/MachO/loh-adrp-add.s
@@ -0,0 +1,98 @@
+# REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o
+# RUN: %lld -arch arm64 %t.o -o %t
+# RUN: llvm-objdump -d --macho %t | FileCheck %s
+
+# CHECK-LABEL: _main:
+## Out of range, before
+# CHECK-NEXT: adrp x0
+# CHECK-NEXT: add x0, x0
+## In range, before
+# CHECK-NEXT: adr x1
+# CHECK-NEXT: nop
+## Registers don't match (invalid input)
+# CHECK-NEXT: adrp x2
+# CHECK-NEXT: add x0
+## Targets don't match (invalid input)
+# CHECK-NEXT: adrp x3
+# CHECK-NEXT: add x3
+## Not an adrp instruction (invalid input)
+# CHECK-NEXT: nop
+# CHECK-NEXT: add x4
+## In range, after
+# CHECK-NEXT: adr x5
+# CHECK-NEXT: nop
+## In range, add's destination register is not the same as its source
+# CHECK-NEXT: adr x7
+# CHECK-NEXT: nop
+## Valid, non-adjacent instructions - start
+# CHECK-NEXT: adr x8
+## Out of range, after
+# CHECK-NEXT: adrp x9
+# CHECK-NEXT: add x9, x9
+## Valid, non-adjacent instructions - end
+# CHECK-NEXT: nop
+
+.text
+.align 2
+_before_far:
+  .space 1048576
+
+_before_near:
+  nop
+
+.globl _main
+_main:
+L1:
+  adrp x0, _before_far at PAGE
+L2:
+  add  x0, x0, _before_far at PAGEOFF
+L3:
+  adrp x1, _before_near at PAGE
+L4:
+  add  x1, x1, _before_near at PAGEOFF
+L5:
+  adrp x2, _before_near at PAGE
+L6:
+  add  x0, x0, _before_near at PAGEOFF
+L7:
+  adrp x3, _before_near at PAGE
+L8:
+  add  x3, x3, _after_near at PAGEOFF
+L9:
+  nop
+L10:
+  add  x4, x4, _after_near at PAGEOFF
+L11:
+  adrp x5, _after_near at PAGE
+L12:
+  add  x5, x5, _after_near at PAGEOFF
+L13:
+  adrp x6, _after_near at PAGE
+L14:
+  add  x7, x6, _after_near at PAGEOFF
+L15:
+  adrp x8, _after_near at PAGE
+L16:
+  adrp x9, _after_far at PAGE
+L17:
+  add  x9, x9, _after_far at PAGEOFF
+L18:
+  add  x8, x8, _after_near at PAGEOFF
+
+_after_near:
+  .space 1048576
+
+_after_far:
+  nop
+
+.loh AdrpAdd L1, L2
+.loh AdrpAdd L3, L4
+.loh AdrpAdd L5, L6
+.loh AdrpAdd L7, L8
+.loh AdrpAdd L9, L10
+.loh AdrpAdd L11, L12
+.loh AdrpAdd L13, L14
+.loh AdrpAdd L15, L18
+.loh AdrpAdd L16, L17

diff  --git a/lld/test/MachO/loh-adrp-adrp.s b/lld/test/MachO/loh-adrp-adrp.s
new file mode 100644
index 0000000000000..05abc8ab1961d
--- /dev/null
+++ b/lld/test/MachO/loh-adrp-adrp.s
@@ -0,0 +1,56 @@
+# REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o
+# RUN: %lld -arch arm64 %t.o -o %t
+# RUN: llvm-objdump -d --macho %t | FileCheck %s
+
+# CHECK-LABEL: _main:
+## Valid
+# CHECK-NEXT: adrp x0
+# CHECK-NEXT: nop
+## Mismatched registers
+# CHECK-NEXT: adrp x1
+# CHECK-NEXT: adrp x2
+## Not on the same page
+# CHECK-NEXT: adrp x3
+# CHECK-NEXT: adrp x3
+## Not an adrp instruction (invalid)
+# CHECK-NEXT: nop
+# CHECK-NEXT: adrp x4
+
+.text
+.align 2
+
+.globl _main
+_main:
+L1:
+  adrp x0, _foo at PAGE
+L2:
+  adrp x0, _bar at PAGE
+L3:
+  adrp x1, _foo at PAGE
+L4:
+  adrp x2, _bar at PAGE
+L5:
+  adrp x3, _foo at PAGE
+L6:
+  adrp x3, _baz at PAGE
+L7:
+  nop
+L8:
+  adrp x4, _baz at PAGE
+
+.data
+.align 12
+_foo:
+  .byte 0
+_bar:
+  .byte 0
+.space 4094
+_baz:
+  .byte 0
+
+.loh AdrpAdrp L1, L2
+.loh AdrpAdrp L3, L4
+.loh AdrpAdrp L5, L6
+.loh AdrpAdrp L7, L8

diff  --git a/llvm/include/llvm/BinaryFormat/MachO.h b/llvm/include/llvm/BinaryFormat/MachO.h
index 7e38c1342e321..c05e79333d38b 100644
--- a/llvm/include/llvm/BinaryFormat/MachO.h
+++ b/llvm/include/llvm/BinaryFormat/MachO.h
@@ -2237,6 +2237,17 @@ enum SecCSDigestAlgorithm {
   kSecCodeSignatureHashSHA512 = 5, /* SHA-512 */
 };
 
+enum LinkerOptimizationHintKind {
+  LOH_ARM64_ADRP_ADRP = 1,
+  LOH_ARM64_ADRP_LDR = 2,
+  LOH_ARM64_ADRP_ADD_LDR = 3,
+  LOH_ARM64_ADRP_LDR_GOT_LDR = 4,
+  LOH_ARM64_ADRP_ADD_STR = 5,
+  LOH_ARM64_ADRP_LDR_GOT_STR = 6,
+  LOH_ARM64_ADRP_ADD = 7,
+  LOH_ARM64_ADRP_LDR_GOT = 8,
+};
+
 } // end namespace MachO
 } // end namespace llvm
 


        


More information about the llvm-commits mailing list