[lld] 0d30e92 - [lld-macho] Add support for emitting chained fixups

Daniel Bertalan via llvm-commits llvm-commits at lists.llvm.org
Tue Oct 4 02:51:25 PDT 2022


Author: Daniel Bertalan
Date: 2022-10-04T11:48:45+02:00
New Revision: 0d30e92f59589de44a185c53671b7fe2e83cd2ae

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

LOG: [lld-macho] Add support for emitting chained fixups

This commit adds support for chained fixups, which were introduced in
Apple's late 2020 OS releases. This format replaces the dyld opcodes
used for supplying rebase and binding information, and encodes most of
that data directly in the memory location that will have the fixup
applied.

This reduces binary size and is a requirement for page-in linking, which
will be available starting with macOS 13.

A high-level overview of the format and my implementation can be found
in SyntheticSections.h.

This feature is currently gated behind the `-fixup_chains` flag, and
will be enabled by default for supported targets in a later commit.

Like in ld64, lazy binding is disabled when chained fixups are in use,
and the `-init_offsets` transformation is performed by default.

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

Added: 
    lld/test/MachO/chained-fixups-addend.s
    lld/test/MachO/chained-fixups-empty.s
    lld/test/MachO/invalid/chained-fixups-incompatible.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/InputSection.cpp
    lld/MachO/InputSection.h
    lld/MachO/Options.td
    lld/MachO/Symbols.cpp
    lld/MachO/Symbols.h
    lld/MachO/SyntheticSections.cpp
    lld/MachO/SyntheticSections.h
    lld/MachO/Target.h
    lld/MachO/Writer.cpp
    lld/test/MachO/flat-namespace-interposable.s
    lld/test/MachO/tlv-dylib.s
    lld/test/MachO/weak-binding.s
    lld/test/MachO/weak-reference.s
    llvm/include/llvm/BinaryFormat/MachO.h

Removed: 
    


################################################################################
diff  --git a/lld/MachO/Arch/ARM.cpp b/lld/MachO/Arch/ARM.cpp
index 5cfc9e335141..c20d34494053 100644
--- a/lld/MachO/Arch/ARM.cpp
+++ b/lld/MachO/Arch/ARM.cpp
@@ -32,7 +32,7 @@ struct ARM : TargetInfo {
   void relocateOne(uint8_t *loc, const Reloc &, uint64_t va,
                    uint64_t pc) const override;
 
-  void writeStub(uint8_t *buf, const Symbol &) const override;
+  void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
   void writeStubHelperHeader(uint8_t *buf) const override;
   void writeStubHelperEntry(uint8_t *buf, const Symbol &,
                             uint64_t entryAddr) const override;
@@ -140,7 +140,7 @@ void ARM::relocateOne(uint8_t *loc, const Reloc &r, uint64_t value,
   }
 }
 
-void ARM::writeStub(uint8_t *buf, const Symbol &sym) const {
+void ARM::writeStub(uint8_t *buf, const Symbol &sym, uint64_t) const {
   fatal("TODO: implement this");
 }
 

diff  --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp
index 241b3f557b45..02e2d393ef6e 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -31,7 +31,7 @@ namespace {
 
 struct ARM64 : ARM64Common {
   ARM64();
-  void writeStub(uint8_t *buf, const Symbol &) const override;
+  void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
   void writeStubHelperHeader(uint8_t *buf) const override;
   void writeStubHelperEntry(uint8_t *buf, const Symbol &,
                             uint64_t entryAddr) const override;
@@ -77,8 +77,9 @@ static constexpr uint32_t stubCode[] = {
     0xd61f0200, // 08: br    x16
 };
 
-void ARM64::writeStub(uint8_t *buf8, const Symbol &sym) const {
-  ::writeStub<LP64>(buf8, stubCode, sym);
+void ARM64::writeStub(uint8_t *buf8, const Symbol &sym,
+                      uint64_t pointerVA) const {
+  ::writeStub(buf8, stubCode, sym, pointerVA);
 }
 
 static constexpr uint32_t stubHelperHeaderCode[] = {

diff  --git a/lld/MachO/Arch/ARM64Common.h b/lld/MachO/Arch/ARM64Common.h
index 31b4b5176fde..8dc2412b29bc 100644
--- a/lld/MachO/Arch/ARM64Common.h
+++ b/lld/MachO/Arch/ARM64Common.h
@@ -107,18 +107,15 @@ inline uint64_t pageBits(uint64_t address) {
   return address & pageMask;
 }
 
-template <class LP>
 inline void writeStub(uint8_t *buf8, const uint32_t stubCode[3],
-                      const macho::Symbol &sym) {
+                      const macho::Symbol &sym, uint64_t pointerVA) {
   auto *buf32 = reinterpret_cast<uint32_t *>(buf8);
   constexpr size_t stubCodeSize = 3 * sizeof(uint32_t);
   SymbolDiagnostic d = {&sym, "stub"};
   uint64_t pcPageBits =
       pageBits(in.stubs->addr + sym.stubsIndex * stubCodeSize);
-  uint64_t lazyPointerVA =
-      in.lazyPointers->addr + sym.stubsIndex * LP::wordSize;
-  encodePage21(&buf32[0], d, stubCode[0], pageBits(lazyPointerVA) - pcPageBits);
-  encodePageOff12(&buf32[1], d, stubCode[1], lazyPointerVA);
+  encodePage21(&buf32[0], d, stubCode[0], pageBits(pointerVA) - pcPageBits);
+  encodePageOff12(&buf32[1], d, stubCode[1], pointerVA);
   buf32[2] = stubCode[2];
 }
 

diff  --git a/lld/MachO/Arch/ARM64_32.cpp b/lld/MachO/Arch/ARM64_32.cpp
index daeda0136dc9..c6bb6ee6c1c7 100644
--- a/lld/MachO/Arch/ARM64_32.cpp
+++ b/lld/MachO/Arch/ARM64_32.cpp
@@ -29,7 +29,7 @@ namespace {
 
 struct ARM64_32 : ARM64Common {
   ARM64_32();
-  void writeStub(uint8_t *buf, const Symbol &) const override;
+  void writeStub(uint8_t *buf, const Symbol &, uint64_t) const override;
   void writeStubHelperHeader(uint8_t *buf) const override;
   void writeStubHelperEntry(uint8_t *buf, const Symbol &,
                             uint64_t entryAddr) const override;
@@ -70,8 +70,9 @@ static constexpr uint32_t stubCode[] = {
     0xd61f0200, // 08: br    x16
 };
 
-void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym) const {
-  ::writeStub<ILP32>(buf8, stubCode, sym);
+void ARM64_32::writeStub(uint8_t *buf8, const Symbol &sym,
+                         uint64_t pointerVA) const {
+  ::writeStub(buf8, stubCode, sym, pointerVA);
 }
 
 static constexpr uint32_t stubHelperHeaderCode[] = {

diff  --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp
index e5bdd8939940..048702f12900 100644
--- a/lld/MachO/Arch/X86_64.cpp
+++ b/lld/MachO/Arch/X86_64.cpp
@@ -31,7 +31,8 @@ struct X86_64 : TargetInfo {
   void relocateOne(uint8_t *loc, const Reloc &, uint64_t va,
                    uint64_t relocVA) const override;
 
-  void writeStub(uint8_t *buf, const Symbol &) const override;
+  void writeStub(uint8_t *buf, const Symbol &,
+                 uint64_t pointerVA) const override;
   void writeStubHelperHeader(uint8_t *buf) const override;
   void writeStubHelperEntry(uint8_t *buf, const Symbol &,
                             uint64_t entryAddr) const override;
@@ -138,11 +139,11 @@ static constexpr uint8_t stub[] = {
     0xff, 0x25, 0, 0, 0, 0, // jmpq *__la_symbol_ptr(%rip)
 };
 
-void X86_64::writeStub(uint8_t *buf, const Symbol &sym) const {
+void X86_64::writeStub(uint8_t *buf, const Symbol &sym,
+                       uint64_t pointerVA) const {
   memcpy(buf, stub, 2); // just copy the two nonzero bytes
   uint64_t stubAddr = in.stubs->addr + sym.stubsIndex * sizeof(stub);
-  writeRipRelative({&sym, "stub"}, buf, stubAddr, sizeof(stub),
-                   in.lazyPointers->addr + sym.stubsIndex * LP64::wordSize);
+  writeRipRelative({&sym, "stub"}, buf, stubAddr, sizeof(stub), pointerVA);
 }
 
 static constexpr uint8_t stubHelperHeader[] = {

diff  --git a/lld/MachO/Config.h b/lld/MachO/Config.h
index fa79b87268cd..b07c8a5e6d2b 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -132,6 +132,7 @@ struct Configuration {
   bool emitDataInCodeInfo = false;
   bool emitEncryptionInfo = false;
   bool emitInitOffsets = false;
+  bool emitChainedFixups = false;
   bool timeTraceEnabled = false;
   bool dataConst = false;
   bool dedupLiterals = true;

diff  --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 9e30239fa945..febeace99d3a 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -957,6 +957,47 @@ static bool dataConstDefault(const InputArgList &args) {
   return false;
 }
 
+static bool shouldEmitChainedFixups(const InputArgList &args) {
+  const Arg *arg = args.getLastArg(OPT_fixup_chains, OPT_no_fixup_chains);
+  if (arg && arg->getOption().matches(OPT_no_fixup_chains))
+    return false;
+
+  bool isRequested = arg != nullptr;
+
+  // Version numbers taken from the Xcode 13.3 release notes.
+  static const std::array<std::pair<PlatformType, VersionTuple>, 4> minVersion =
+      {{{PLATFORM_MACOS, VersionTuple(11, 0)},
+        {PLATFORM_IOS, VersionTuple(13, 4)},
+        {PLATFORM_TVOS, VersionTuple(14, 0)},
+        {PLATFORM_WATCHOS, VersionTuple(7, 0)}}};
+  PlatformType platform = removeSimulator(config->platformInfo.target.Platform);
+  auto it = llvm::find_if(minVersion,
+                          [&](const auto &p) { return p.first == platform; });
+  if (it != minVersion.end() && it->second > config->platformInfo.minimum) {
+    if (!isRequested)
+      return false;
+
+    warn("-fixup_chains requires " + getPlatformName(config->platform()) + " " +
+         it->second.getAsString() + ", which is newer than target minimum of " +
+         config->platformInfo.minimum.getAsString());
+  }
+
+  if (!is_contained({AK_x86_64, AK_x86_64h, AK_arm64}, config->arch())) {
+    if (isRequested)
+      error("-fixup_chains is only supported on x86_64 and arm64 targets");
+    return false;
+  }
+
+  if (!config->isPic) {
+    if (isRequested)
+      error("-fixup_chains is incompatible with -no_pie");
+    return false;
+  }
+
+  // TODO: Enable by default once stable.
+  return isRequested;
+}
+
 void SymbolPatterns::clear() {
   literals.clear();
   globs.clear();
@@ -1376,6 +1417,11 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     }
   }
 
+  config->isPic = config->outputType == MH_DYLIB ||
+                  config->outputType == MH_BUNDLE ||
+                  (config->outputType == MH_EXECUTE &&
+                   args.hasFlag(OPT_pie, OPT_no_pie, true));
+
   // Must be set before any InputSections and Symbols are created.
   config->deadStrip = args.hasArg(OPT_dead_strip);
 
@@ -1475,7 +1521,9 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
   config->emitBitcodeBundle = args.hasArg(OPT_bitcode_bundle);
   config->emitDataInCodeInfo =
       args.hasFlag(OPT_data_in_code_info, OPT_no_data_in_code_info, true);
-  config->emitInitOffsets = args.hasArg(OPT_init_offsets);
+  config->emitChainedFixups = shouldEmitChainedFixups(args);
+  config->emitInitOffsets =
+      config->emitChainedFixups || args.hasArg(OPT_init_offsets);
   config->icfLevel = getICFLevel(args);
   config->dedupLiterals =
       args.hasFlag(OPT_deduplicate_literals, OPT_icf_eq, false) ||
@@ -1698,11 +1746,6 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     initLLVM(); // must be run before any call to addFile()
     createFiles(args);
 
-    config->isPic = config->outputType == MH_DYLIB ||
-                    config->outputType == MH_BUNDLE ||
-                    (config->outputType == MH_EXECUTE &&
-                     args.hasFlag(OPT_pie, OPT_no_pie, true));
-
     // Now that all dylibs have been loaded, search for those that should be
     // re-exported.
     {

diff  --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index 660a27c3d317..ca5c7522d58e 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -181,6 +181,9 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
     const Reloc &r = relocs[i];
     uint8_t *loc = buf + r.offset;
     uint64_t referentVA = 0;
+
+    const bool needsFixup = config->emitChainedFixups &&
+                            target->hasAttr(r.type, RelocAttrBits::UNSIGNED);
     if (target->hasAttr(r.type, RelocAttrBits::SUBTRAHEND)) {
       const Symbol *fromSym = r.referent.get<Symbol *>();
       const Reloc &minuend = relocs[++i];
@@ -205,17 +208,24 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
       }
       referentVA = resolveSymbolVA(referentSym, r.type) + r.addend;
 
-      if (isThreadLocalVariables(getFlags())) {
+      if (isThreadLocalVariables(getFlags()) && isa<Defined>(referentSym)) {
         // References from thread-local variable sections are treated as offsets
         // relative to the start of the thread-local data memory area, which
         // is initialized via copying all the TLV data sections (which are all
         // contiguous).
-        if (isa<Defined>(referentSym))
-          referentVA -= firstTLVDataSection->addr;
+        referentVA -= firstTLVDataSection->addr;
+      } else if (needsFixup) {
+        writeChainedFixup(loc, referentSym, r.addend);
+        continue;
       }
     } else if (auto *referentIsec = r.referent.dyn_cast<InputSection *>()) {
       assert(!::shouldOmitFromOutput(referentIsec));
       referentVA = referentIsec->getVA(r.addend);
+
+      if (needsFixup) {
+        writeChainedRebase(loc, referentVA);
+        continue;
+      }
     }
     target->relocateOne(loc, r, referentVA, getVA() + r.offset);
   }

diff  --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index ecb46f926a0a..1a9706861c11 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -299,6 +299,7 @@ constexpr const char bitcodeBundle[] = "__bundle";
 constexpr const char cString[] = "__cstring";
 constexpr const char cfString[] = "__cfstring";
 constexpr const char cgProfile[] = "__cg_profile";
+constexpr const char chainFixups[] = "__chainfixups";
 constexpr const char codeSignature[] = "__code_signature";
 constexpr const char common[] = "__common";
 constexpr const char compactUnwind[] = "__compact_unwind";

diff  --git a/lld/MachO/Options.td b/lld/MachO/Options.td
index 8a08d036730a..56498ec20062 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -1230,8 +1230,10 @@ def executable_path : Flag<["-"], "executable_path">,
     Flags<[HelpHidden]>,
     Group<grp_undocumented>;
 def fixup_chains : Flag<["-"], "fixup_chains">,
-    HelpText<"This option is undocumented in ld64">,
-    Flags<[HelpHidden]>,
+    HelpText<"Emit chained fixups">,
+    Group<grp_undocumented>;
+def no_fixup_chains : Flag<["-"], "no_fixup_chains">,
+    HelpText<"Emit fixup information as classic dyld opcodes">,
     Group<grp_undocumented>;
 def fixup_chains_section : Flag<["-"], "fixup_chains_section">,
     HelpText<"This option is undocumented in ld64">,

diff  --git a/lld/MachO/Symbols.cpp b/lld/MachO/Symbols.cpp
index 40e99cb6dde5..acfc2d6dcd39 100644
--- a/lld/MachO/Symbols.cpp
+++ b/lld/MachO/Symbols.cpp
@@ -37,6 +37,9 @@ std::string lld::toMachOString(const object::Archive::Symbol &b) {
 }
 
 uint64_t Symbol::getStubVA() const { return in.stubs->getVA(stubsIndex); }
+uint64_t Symbol::getLazyPtrVA() const {
+  return in.lazyPointers->getVA(stubsIndex);
+}
 uint64_t Symbol::getGotVA() const { return in.got->getVA(gotIndex); }
 uint64_t Symbol::getTlvVA() const { return in.tlvPointers->getVA(gotIndex); }
 

diff  --git a/lld/MachO/Symbols.h b/lld/MachO/Symbols.h
index 6773170e028a..0f718e711cae 100644
--- a/lld/MachO/Symbols.h
+++ b/lld/MachO/Symbols.h
@@ -75,6 +75,7 @@ class Symbol {
   bool isInStubs() const { return stubsIndex != UINT32_MAX; }
 
   uint64_t getStubVA() const;
+  uint64_t getLazyPtrVA() const;
   uint64_t getGotVA() const;
   uint64_t getTlvVA() const;
   uint64_t resolveBranchVA() const {

diff  --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index a9900b016621..bf5f75c5f80e 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -111,6 +111,16 @@ static uint32_t cpuSubtype() {
   return subtype;
 }
 
+static bool hasWeakBinding() {
+  return config->emitChainedFixups ? in.chainedFixups->hasWeakBinding()
+                                   : in.weakBinding->hasEntry();
+}
+
+static bool hasNonWeakDefinition() {
+  return config->emitChainedFixups ? in.chainedFixups->hasNonWeakDefinition()
+                                   : in.weakBinding->hasNonWeakDefinition();
+}
+
 void MachHeaderSection::writeTo(uint8_t *buf) const {
   auto *hdr = reinterpret_cast<mach_header *>(buf);
   hdr->magic = target->magic;
@@ -136,10 +146,10 @@ void MachHeaderSection::writeTo(uint8_t *buf) const {
   if (config->outputType == MH_DYLIB && config->applicationExtension)
     hdr->flags |= MH_APP_EXTENSION_SAFE;
 
-  if (in.exports->hasWeakSymbol || in.weakBinding->hasNonWeakDefinition())
+  if (in.exports->hasWeakSymbol || hasNonWeakDefinition())
     hdr->flags |= MH_WEAK_DEFINES;
 
-  if (in.exports->hasWeakSymbol || in.weakBinding->hasEntry())
+  if (in.exports->hasWeakSymbol || hasWeakBinding())
     hdr->flags |= MH_BINDS_TO_WEAK;
 
   for (const OutputSegment *seg : outputSegments) {
@@ -304,6 +314,16 @@ NonLazyPointerSectionBase::NonLazyPointerSectionBase(const char *segname,
 void macho::addNonLazyBindingEntries(const Symbol *sym,
                                      const InputSection *isec, uint64_t offset,
                                      int64_t addend) {
+  if (config->emitChainedFixups) {
+    if (needsBinding(sym))
+      in.chainedFixups->addBinding(sym, isec, offset, addend);
+    else if (isa<Defined>(sym))
+      in.chainedFixups->addRebase(isec, offset);
+    else
+      llvm_unreachable("cannot bind to an undefined symbol");
+    return;
+  }
+
   if (const auto *dysym = dyn_cast<DylibSymbol>(sym)) {
     in.binding->addEntry(dysym, isec, offset, addend);
     if (dysym->isWeakDef())
@@ -330,10 +350,52 @@ void NonLazyPointerSectionBase::addEntry(Symbol *sym) {
   }
 }
 
+void macho::writeChainedRebase(uint8_t *buf, uint64_t targetVA) {
+  assert(config->emitChainedFixups);
+  assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
+  auto *rebase = reinterpret_cast<dyld_chained_ptr_64_rebase *>(buf);
+  rebase->target = targetVA & 0xf'ffff'ffff;
+  rebase->high8 = (targetVA >> 56);
+  rebase->reserved = 0;
+  rebase->next = 0;
+  rebase->bind = 0;
+
+  // The fixup format places a 64 GiB limit on the output's size.
+  // Should we handle this gracefully?
+  uint64_t encodedVA = rebase->target | ((uint64_t)rebase->high8 << 56);
+  if (encodedVA != targetVA)
+    error("rebase target address 0x" + Twine::utohexstr(targetVA) +
+          " does not fit into chained fixup. Re-link with -no_fixup_chains");
+}
+
+static void writeChainedBind(uint8_t *buf, const Symbol *sym, int64_t addend) {
+  assert(config->emitChainedFixups);
+  assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
+  auto *bind = reinterpret_cast<dyld_chained_ptr_64_bind *>(buf);
+  auto [ordinal, inlineAddend] = in.chainedFixups->getBinding(sym, addend);
+  bind->ordinal = ordinal;
+  bind->addend = inlineAddend;
+  bind->reserved = 0;
+  bind->next = 0;
+  bind->bind = 1;
+}
+
+void macho::writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend) {
+  if (needsBinding(sym))
+    writeChainedBind(buf, sym, addend);
+  else
+    writeChainedRebase(buf, sym->getVA() + addend);
+}
+
 void NonLazyPointerSectionBase::writeTo(uint8_t *buf) const {
-  for (size_t i = 0, n = entries.size(); i < n; ++i)
-    if (auto *defined = dyn_cast<Defined>(entries[i]))
-      write64le(&buf[i * target->wordSize], defined->getVA());
+  if (config->emitChainedFixups) {
+    for (size_t i = 0, n = entries.size(); i < n; ++i)
+      writeChainedFixup(&buf[i * target->wordSize], entries[i], 0);
+  } else {
+    for (size_t i = 0, n = entries.size(); i < n; ++i)
+      if (auto *defined = dyn_cast<Defined>(entries[i]))
+        write64le(&buf[i * target->wordSize], defined->getVA());
+  }
 }
 
 GotSection::GotSection()
@@ -652,7 +714,9 @@ uint64_t StubsSection::getSize() const {
 void StubsSection::writeTo(uint8_t *buf) const {
   size_t off = 0;
   for (const Symbol *sym : entries) {
-    target->writeStub(buf + off, *sym);
+    uint64_t pointerVA =
+        config->emitChainedFixups ? sym->getGotVA() : sym->getLazyPtrVA();
+    target->writeStub(buf + off, *sym, pointerVA);
     off += target->stubSize;
   }
 }
@@ -660,6 +724,7 @@ void StubsSection::writeTo(uint8_t *buf) const {
 void StubsSection::finalize() { isFinal = true; }
 
 static void addBindingsForStub(Symbol *sym) {
+  assert(!config->emitChainedFixups);
   if (auto *dysym = dyn_cast<DylibSymbol>(sym)) {
     if (sym->isWeakDef()) {
       in.binding->addEntry(dysym, in.lazyPointers->isec,
@@ -689,7 +754,11 @@ void StubsSection::addEntry(Symbol *sym) {
   bool inserted = entries.insert(sym);
   if (inserted) {
     sym->stubsIndex = entries.size() - 1;
-    addBindingsForStub(sym);
+
+    if (config->emitChainedFixups)
+      in.got->addEntry(sym);
+    else
+      addBindingsForStub(sym);
   }
 }
 
@@ -864,6 +933,7 @@ void LazyBindingSection::writeTo(uint8_t *buf) const {
 }
 
 void LazyBindingSection::addEntry(Symbol *sym) {
+  assert(!config->emitChainedFixups && "Chained fixups always bind eagerly");
   if (entries.insert(sym)) {
     sym->stubsHelperIndex = entries.size() - 1;
     in.rebase->addEntry(in.lazyPointers->isec,
@@ -1188,8 +1258,8 @@ void SymtabSection::finalizeContents() {
 
   // __dyld_private is a local symbol too. It's linker-created and doesn't
   // exist in any object file.
-  if (Defined *dyldPrivate = in.stubHelper->dyldPrivate)
-    localSymbolsHandler(dyldPrivate);
+  if (in.stubHelper && in.stubHelper->dyldPrivate)
+    localSymbolsHandler(in.stubHelper->dyldPrivate);
 
   for (Symbol *sym : symtab->getSymbols()) {
     if (!sym->isLive())
@@ -1311,8 +1381,12 @@ IndirectSymtabSection::IndirectSymtabSection()
                       section_names::indirectSymbolTable) {}
 
 uint32_t IndirectSymtabSection::getNumSymbols() const {
-  return in.got->getEntries().size() + in.tlvPointers->getEntries().size() +
-         2 * in.stubs->getEntries().size();
+  uint32_t size = in.got->getEntries().size() +
+                  in.tlvPointers->getEntries().size() +
+                  in.stubs->getEntries().size();
+  if (!config->emitChainedFixups)
+    size += in.stubs->getEntries().size();
+  return size;
 }
 
 bool IndirectSymtabSection::isNeeded() const {
@@ -1327,8 +1401,10 @@ void IndirectSymtabSection::finalizeContents() {
   in.tlvPointers->reserved1 = off;
   off += in.tlvPointers->getEntries().size();
   in.stubs->reserved1 = off;
-  off += in.stubs->getEntries().size();
-  in.lazyPointers->reserved1 = off;
+  if (in.lazyPointers) {
+    off += in.stubs->getEntries().size();
+    in.lazyPointers->reserved1 = off;
+  }
 }
 
 static uint32_t indirectValue(const Symbol *sym) {
@@ -1354,14 +1430,17 @@ void IndirectSymtabSection::writeTo(uint8_t *buf) const {
     write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
     ++off;
   }
-  // There is a 1:1 correspondence between stubs and LazyPointerSection
-  // entries. But giving __stubs and __la_symbol_ptr the same reserved1
-  // (the offset into the indirect symbol table) so that they both refer
-  // to the same range of offsets confuses `strip`, so write the stubs
-  // symbol table offsets a second time.
-  for (const Symbol *sym : in.stubs->getEntries()) {
-    write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
-    ++off;
+
+  if (in.lazyPointers) {
+    // There is a 1:1 correspondence between stubs and LazyPointerSection
+    // entries. But giving __stubs and __la_symbol_ptr the same reserved1
+    // (the offset into the indirect symbol table) so that they both refer
+    // to the same range of offsets confuses `strip`, so write the stubs
+    // symbol table offsets a second time.
+    for (const Symbol *sym : in.stubs->getEntries()) {
+      write32le(buf + off * sizeof(uint32_t), indirectValue(sym));
+      ++off;
+    }
   }
 }
 
@@ -1932,5 +2011,246 @@ void macho::createSyntheticSymbols() {
   addHeaderSymbol("___dso_handle");
 }
 
+ChainedFixupsSection::ChainedFixupsSection()
+    : LinkEditSection(segment_names::linkEdit, section_names::chainFixups) {}
+
+bool ChainedFixupsSection::isNeeded() const {
+  assert(config->emitChainedFixups);
+  // dyld always expects LC_DYLD_CHAINED_FIXUPS to point to a valid
+  // dyld_chained_fixups_header, so we create this section even if there aren't
+  // any fixups.
+  return true;
+}
+
+static bool needsWeakBind(const Symbol &sym) {
+  if (auto *dysym = dyn_cast<DylibSymbol>(&sym))
+    return dysym->isWeakDef();
+  if (auto *defined = dyn_cast<Defined>(&sym))
+    return defined->isExternalWeakDef();
+  return false;
+}
+
+void ChainedFixupsSection::addBinding(const Symbol *sym,
+                                      const InputSection *isec, uint64_t offset,
+                                      int64_t addend) {
+  locations.emplace_back(isec, offset);
+  int64_t outlineAddend = (addend < 0 || addend > 0xFF) ? addend : 0;
+  auto [it, inserted] = bindings.insert(
+      {{sym, outlineAddend}, static_cast<uint32_t>(bindings.size())});
+
+  if (inserted) {
+    symtabSize += sym->getName().size() + 1;
+    hasWeakBind = hasWeakBind || needsWeakBind(*sym);
+    if (!isInt<23>(outlineAddend))
+      needsLargeAddend = true;
+    else if (outlineAddend != 0)
+      needsAddend = true;
+  }
+}
+
+std::pair<uint32_t, uint8_t>
+ChainedFixupsSection::getBinding(const Symbol *sym, int64_t addend) const {
+  int64_t outlineAddend = (addend < 0 || addend > 0xFF) ? addend : 0;
+  auto it = bindings.find({sym, outlineAddend});
+  assert(it != bindings.end() && "binding not found in the imports table");
+  if (outlineAddend == 0)
+    return {it->second, addend};
+  return {it->second, 0};
+}
+
+static size_t writeImport(uint8_t *buf, int format, uint32_t libOrdinal,
+                          bool weakRef, uint32_t nameOffset, int64_t addend) {
+  switch (format) {
+  case DYLD_CHAINED_IMPORT: {
+    auto *import = reinterpret_cast<dyld_chained_import *>(buf);
+    import->lib_ordinal = libOrdinal;
+    import->weak_import = weakRef;
+    import->name_offset = nameOffset;
+    return sizeof(dyld_chained_import);
+  }
+  case DYLD_CHAINED_IMPORT_ADDEND: {
+    auto *import = reinterpret_cast<dyld_chained_import_addend *>(buf);
+    import->lib_ordinal = libOrdinal;
+    import->weak_import = weakRef;
+    import->name_offset = nameOffset;
+    import->addend = addend;
+    return sizeof(dyld_chained_import_addend);
+  }
+  case DYLD_CHAINED_IMPORT_ADDEND64: {
+    auto *import = reinterpret_cast<dyld_chained_import_addend64 *>(buf);
+    import->lib_ordinal = libOrdinal;
+    import->weak_import = weakRef;
+    import->name_offset = nameOffset;
+    import->addend = addend;
+    return sizeof(dyld_chained_import_addend64);
+  }
+  default:
+    llvm_unreachable("Unknown import format");
+  }
+}
+
+size_t ChainedFixupsSection::SegmentInfo::getSize() const {
+  assert(pageStarts.size() > 0 && "SegmentInfo for segment with no fixups?");
+  return alignTo<8>(sizeof(dyld_chained_starts_in_segment) +
+                    pageStarts.back().first * sizeof(uint16_t));
+}
+
+size_t ChainedFixupsSection::SegmentInfo::writeTo(uint8_t *buf) const {
+  auto *segInfo = reinterpret_cast<dyld_chained_starts_in_segment *>(buf);
+  segInfo->size = getSize();
+  segInfo->page_size = target->getPageSize();
+  // FIXME: Use DYLD_CHAINED_PTR_64_OFFSET on newer OS versions.
+  segInfo->pointer_format = DYLD_CHAINED_PTR_64;
+  segInfo->segment_offset = oseg->addr - in.header->addr;
+  segInfo->max_valid_pointer = 0; // not used on 64-bit
+  segInfo->page_count = pageStarts.back().first + 1;
+
+  uint16_t *starts = segInfo->page_start;
+  for (size_t i = 0; i < segInfo->page_count; ++i)
+    starts[i] = DYLD_CHAINED_PTR_START_NONE;
+
+  for (auto [pageIdx, startAddr] : pageStarts)
+    starts[pageIdx] = startAddr;
+  return segInfo->size;
+}
+
+static size_t importEntrySize(int format) {
+  switch (format) {
+  case DYLD_CHAINED_IMPORT:
+    return sizeof(dyld_chained_import);
+  case DYLD_CHAINED_IMPORT_ADDEND:
+    return sizeof(dyld_chained_import_addend);
+  case DYLD_CHAINED_IMPORT_ADDEND64:
+    return sizeof(dyld_chained_import_addend64);
+  default:
+    llvm_unreachable("Unknown import format");
+  }
+}
+
+// This is step 3 of the algorithm described in the class comment of
+// ChainedFixupsSection.
+//
+// LC_DYLD_CHAINED_FIXUPS data consists of (in this order):
+// * A dyld_chained_fixups_header
+// * A dyld_chained_starts_in_image
+// * One dyld_chained_starts_in_segment per segment
+// * List of all imports (dyld_chained_import, dyld_chained_import_addend, or
+//   dyld_chained_import_addend64)
+// * Names of imported symbols
+void ChainedFixupsSection::writeTo(uint8_t *buf) const {
+  auto *header = reinterpret_cast<dyld_chained_fixups_header *>(buf);
+  header->fixups_version = 0;
+  header->imports_count = bindings.size();
+  header->imports_format = importFormat;
+  header->symbols_format = 0;
+
+  buf += alignTo<8>(sizeof(*header));
+
+  auto curOffset = [&buf, &header]() -> uint32_t {
+    return buf - reinterpret_cast<uint8_t *>(header);
+  };
+
+  header->starts_offset = curOffset();
+
+  auto *imageInfo = reinterpret_cast<dyld_chained_starts_in_image *>(buf);
+  imageInfo->seg_count = outputSegments.size();
+  uint32_t *segStarts = imageInfo->seg_info_offset;
+
+  // dyld_chained_starts_in_image ends in a flexible array member containing an
+  // uint32_t for each segment. Leave room for it, and fill it via segStarts.
+  buf += alignTo<8>(offsetof(dyld_chained_starts_in_image, seg_info_offset) +
+                    outputSegments.size() * sizeof(uint32_t));
+
+  // Initialize all offsets to 0, which indicates that the segment does not have
+  // fixups. Those that do have them will be filled in below.
+  for (size_t i = 0; i < outputSegments.size(); ++i)
+    segStarts[i] = 0;
+
+  for (const SegmentInfo &seg : fixupSegments) {
+    segStarts[seg.oseg->index] = curOffset() - header->starts_offset;
+    buf += seg.writeTo(buf);
+  }
+
+  // Write imports table.
+  header->imports_offset = curOffset();
+  uint64_t nameOffset = 0;
+  for (auto [import, idx] : bindings) {
+    const Symbol &sym = *import.first;
+    int16_t libOrdinal = needsWeakBind(sym) ? BIND_SPECIAL_DYLIB_WEAK_LOOKUP
+                                            : ordinalForSymbol(sym);
+    buf += writeImport(buf, importFormat, libOrdinal, sym.isWeakRef(),
+                       nameOffset, import.second);
+    nameOffset += sym.getName().size() + 1;
+  }
+
+  // Write imported symbol names.
+  header->symbols_offset = curOffset();
+  for (auto [import, idx] : bindings) {
+    StringRef name = import.first->getName();
+    memcpy(buf, name.data(), name.size());
+    buf += name.size() + 1; // account for null terminator
+  }
+
+  assert(curOffset() == getRawSize());
+}
+
+// This is step 2 of the algorithm described in the class comment of
+// ChainedFixupsSection.
+void ChainedFixupsSection::finalizeContents() {
+  assert(target->wordSize == 8 && "Only 64-bit platforms are supported");
+  assert(config->emitChainedFixups);
+
+  if (!isUInt<32>(symtabSize))
+    error("cannot encode chained fixups: imported symbols table size " +
+          Twine(symtabSize) + " exceeds 4 GiB");
+
+  if (needsLargeAddend || !isUInt<23>(symtabSize))
+    importFormat = DYLD_CHAINED_IMPORT_ADDEND64;
+  else if (needsAddend)
+    importFormat = DYLD_CHAINED_IMPORT_ADDEND;
+  else
+    importFormat = DYLD_CHAINED_IMPORT;
+
+  for (Location &loc : locations)
+    loc.offset =
+        loc.isec->parent->getSegmentOffset() + loc.isec->getOffset(loc.offset);
+
+  llvm::sort(locations, [](const Location &a, const Location &b) {
+    const OutputSegment *segA = a.isec->parent->parent;
+    const OutputSegment *segB = b.isec->parent->parent;
+    if (segA == segB)
+      return a.offset < b.offset;
+    return segA->addr < segB->addr;
+  });
+
+  auto sameSegment = [](const Location &a, const Location &b) {
+    return a.isec->parent->parent == b.isec->parent->parent;
+  };
+
+  const uint64_t pageSize = target->getPageSize();
+  for (size_t i = 0, count = locations.size(); i < count;) {
+    const Location &firstLoc = locations[i];
+    fixupSegments.emplace_back(firstLoc.isec->parent->parent);
+    while (i < count && sameSegment(locations[i], firstLoc)) {
+      uint32_t pageIdx = locations[i].offset / pageSize;
+      fixupSegments.back().pageStarts.emplace_back(
+          pageIdx, locations[i].offset % pageSize);
+      ++i;
+      while (i < count && sameSegment(locations[i], firstLoc) &&
+             locations[i].offset / pageSize == pageIdx)
+        ++i;
+    }
+  }
+
+  // Compute expected encoded size.
+  size = alignTo<8>(sizeof(dyld_chained_fixups_header));
+  size += alignTo<8>(offsetof(dyld_chained_starts_in_image, seg_info_offset) +
+                     outputSegments.size() * sizeof(uint32_t));
+  for (const SegmentInfo &seg : fixupSegments)
+    size += seg.getSize();
+  size += importEntrySize(importFormat) * bindings.size();
+  size += symtabSize;
+}
+
 template SymtabSection *macho::makeSymtabSection<LP64>(StringTableSection &);
 template SymtabSection *macho::makeSymtabSection<ILP32>(StringTableSection &);

diff  --git a/lld/MachO/SyntheticSections.h b/lld/MachO/SyntheticSections.h
index 20695dc38521..791581cb8897 100644
--- a/lld/MachO/SyntheticSections.h
+++ b/lld/MachO/SyntheticSections.h
@@ -21,6 +21,7 @@
 #include "llvm/ADT/Hashing.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/SetVector.h"
+#include "llvm/BinaryFormat/MachO.h"
 #include "llvm/MC/StringTableBuilder.h"
 #include "llvm/Support/MathExtras.h"
 #include "llvm/Support/raw_ostream.h"
@@ -270,6 +271,12 @@ class WeakBindingSection final : public LinkEditSection {
 // order that the weak bindings may overwrite the non-lazy bindings if an
 // appropriate symbol is found at runtime. However, the bound addresses will
 // still be written (non-lazily) into the LazyPointerSection.
+//
+// Symbols are always bound eagerly when chained fixups are used. In that case,
+// StubsSection contains indirect jumps to addresses stored in the GotSection.
+// The GOT directly contains the fixup entries, which will be replaced by the
+// address of the target symbols on load. LazyPointerSection and
+// StubHelperSection are not used.
 
 class StubsSection final : public SyntheticSection {
 public:
@@ -342,6 +349,9 @@ class LazyPointerSection final : public SyntheticSection {
   uint64_t getSize() const override;
   bool isNeeded() const override;
   void writeTo(uint8_t *buf) const override;
+  uint64_t getVA(uint32_t index) const {
+    return addr + (index << target->p2WordSize);
+  }
 };
 
 class LazyBindingSection final : public LinkEditSection {
@@ -674,6 +684,111 @@ class InitOffsetsSection final : public SyntheticSection {
   std::vector<ConcatInputSection *> sections;
 };
 
+// Chained fixups are a replacement for classic dyld opcodes. In this format,
+// most of the metadata necessary for binding symbols and rebasing addresses is
+// stored directly in the memory location that will have the fixup applied.
+//
+// The fixups form singly linked lists; each one covering a single page in
+// memory. The __LINKEDIT,__chainfixups section stores the page offset of the
+// first fixup of each page; the rest can be found by walking the chain using
+// the offset that is embedded in each entry.
+//
+// This setup allows pages to be relocated lazily at page-in time and without
+// being dirtied. The kernel can discard and load them again as needed. This
+// technique, called page-in linking, was introduced in macOS 13.
+//
+// The benefits of this format are:
+//  - smaller __LINKEDIT segment, as most of the fixup information is stored in
+//    the data segment
+//  - faster startup, since not all relocations need to be done upfront
+//  - slightly lower memory usage, as fewer pages are dirtied
+//
+// Userspace x86_64 and arm64 binaries have two types of fixup entries:
+//   - Rebase entries contain an absolute address, to which the object's load
+//     address will be added to get the final value. This is used for loading
+//     the address of a symbol defined in the same binary.
+//   - Binding entries are mostly used for symbols imported from other dylibs,
+//     but for weakly bound and interposable symbols as well. They are looked up
+//     by a (symbol name, library) pair stored in __chainfixups. This import
+//     entry also encodes whether the import is weak (i.e. if the symbol is
+//     missing, it should be set to null instead of producing a load error).
+//     The fixup encodes an ordinal associated with the import, and an optional
+//     addend.
+//
+// The entries are tightly packed 64-bit bitfields. One of the bits specifies
+// which kind of fixup to interpret them as.
+//
+// LLD generates the fixup data in 5 stages:
+//   1. While scanning relocations, we make a note of each location that needs
+//      a fixup by calling addRebase() or addBinding(). During this, we assign
+//      a unique ordinal for each (symbol name, library, addend) import tuple.
+//   2. After addresses have been assigned to all sections, and thus the memory
+//      layout of the linked image is final; finalizeContents() is called. Here,
+//      the page offsets of the chain start entries are calculated.
+//   3. ChainedFixupsSection::writeTo() writes the page start offsets and the
+//      imports table to the output file.
+//   4. Each section's fixup entries are encoded and written to disk in
+//      ConcatInputSection::writeTo(), but without writing the offsets that form
+//      the chain.
+//   5. Finally, each page's (which might correspond to multiple sections)
+//      fixups are linked together in Writer::buildFixupChains().
+class ChainedFixupsSection final : public LinkEditSection {
+public:
+  ChainedFixupsSection();
+  void finalizeContents() override;
+  uint64_t getRawSize() const override { return size; }
+  bool isNeeded() const override;
+  void writeTo(uint8_t *buf) const override;
+
+  void addRebase(const InputSection *isec, uint64_t offset) {
+    locations.emplace_back(isec, offset);
+  }
+  void addBinding(const Symbol *dysym, const InputSection *isec,
+                  uint64_t offset, int64_t addend = 0);
+
+  void setHasNonWeakDefinition() { hasNonWeakDef = true; }
+
+  // Returns an (ordinal, inline addend) tuple used by dyld_chained_ptr_64_bind.
+  std::pair<uint32_t, uint8_t> getBinding(const Symbol *sym,
+                                          int64_t addend) const;
+
+  const std::vector<Location> &getLocations() const { return locations; }
+
+  bool hasWeakBinding() const { return hasWeakBind; }
+  bool hasNonWeakDefinition() const { return hasNonWeakDef; }
+
+private:
+  // Location::offset initially stores the offset within an InputSection, but
+  // contains output segment offsets after finalizeContents().
+  std::vector<Location> locations;
+  // (target symbol, addend) => import ordinal
+  llvm::MapVector<std::pair<const Symbol *, int64_t>, uint32_t> bindings;
+
+  struct SegmentInfo {
+    SegmentInfo(const OutputSegment *oseg) : oseg(oseg) {}
+
+    const OutputSegment *oseg;
+    // (page index, fixup starts offset)
+    llvm::SmallVector<std::pair<uint16_t, uint16_t>> pageStarts;
+
+    size_t getSize() const;
+    size_t writeTo(uint8_t *buf) const;
+  };
+  llvm::SmallVector<SegmentInfo, 4> fixupSegments;
+
+  size_t symtabSize = 0;
+  size_t size = 0;
+
+  bool needsAddend = false;
+  bool needsLargeAddend = false;
+  bool hasWeakBind = false;
+  bool hasNonWeakDef = false;
+  llvm::MachO::ChainedImportFormat importFormat;
+};
+
+void writeChainedRebase(uint8_t *buf, uint64_t targetVA);
+void writeChainedFixup(uint8_t *buf, const Symbol *sym, int64_t addend);
+
 struct InStruct {
   const uint8_t *bufferStart = nullptr;
   MachHeaderSection *header = nullptr;
@@ -696,6 +811,7 @@ struct InStruct {
   ObjCImageInfoSection *objCImageInfo = nullptr;
   ConcatInputSection *imageLoaderCache = nullptr;
   InitOffsetsSection *initOffsets = nullptr;
+  ChainedFixupsSection *chainedFixups = nullptr;
 };
 
 extern InStruct in;

diff  --git a/lld/MachO/Target.h b/lld/MachO/Target.h
index ff7998b96ce8..42c1142db6e1 100644
--- a/lld/MachO/Target.h
+++ b/lld/MachO/Target.h
@@ -52,7 +52,8 @@ class TargetInfo {
 
   // Write code for lazy binding. See the comments on StubsSection for more
   // details.
-  virtual void writeStub(uint8_t *buf, const Symbol &) const = 0;
+  virtual void writeStub(uint8_t *buf, const Symbol &,
+                         uint64_t pointerVA) const = 0;
   virtual void writeStubHelperHeader(uint8_t *buf) const = 0;
   virtual void writeStubHelperEntry(uint8_t *buf, const Symbol &,
                                     uint64_t entryAddr) const = 0;

diff  --git a/lld/MachO/Writer.cpp b/lld/MachO/Writer.cpp
index cffc89b2ea3d..b1bd8dcc09ec 100644
--- a/lld/MachO/Writer.cpp
+++ b/lld/MachO/Writer.cpp
@@ -61,6 +61,7 @@ class Writer {
   void openFile();
   void writeSections();
   void applyOptimizationHints();
+  void buildFixupChains();
   void writeUuid();
   void writeCodeSignature();
   void writeOutputFile();
@@ -579,6 +580,40 @@ class LCCodeSignature final : public LoadCommand {
   CodeSignatureSection *section;
 };
 
+class LCExportsTrie final : public LoadCommand {
+public:
+  LCExportsTrie(ExportSection *section) : section(section) {}
+
+  uint32_t getSize() const override { return sizeof(linkedit_data_command); }
+
+  void writeTo(uint8_t *buf) const override {
+    auto *c = reinterpret_cast<linkedit_data_command *>(buf);
+    c->cmd = LC_DYLD_EXPORTS_TRIE;
+    c->cmdsize = getSize();
+    c->dataoff = section->fileOff;
+    c->datasize = section->getSize();
+  }
+
+  ExportSection *section;
+};
+
+class LCChainedFixups final : public LoadCommand {
+public:
+  LCChainedFixups(ChainedFixupsSection *section) : section(section) {}
+
+  uint32_t getSize() const override { return sizeof(linkedit_data_command); }
+
+  void writeTo(uint8_t *buf) const override {
+    auto *c = reinterpret_cast<linkedit_data_command *>(buf);
+    c->cmd = LC_DYLD_CHAINED_FIXUPS;
+    c->cmdsize = getSize();
+    c->dataoff = section->fileOff;
+    c->datasize = section->getSize();
+  }
+
+  ChainedFixupsSection *section;
+};
+
 } // namespace
 
 void Writer::treatSpecialUndefineds() {
@@ -657,8 +692,12 @@ void Writer::scanRelocations() {
         // too...
         auto *referentIsec = r.referent.get<InputSection *>();
         r.referent = referentIsec->canonical();
-        if (!r.pcrel)
-          in.rebase->addEntry(isec, r.offset);
+        if (!r.pcrel) {
+          if (config->emitChainedFixups)
+            in.chainedFixups->addRebase(isec, r.offset);
+          else
+            in.rebase->addEntry(isec, r.offset);
+        }
       }
     }
   }
@@ -666,6 +705,13 @@ void Writer::scanRelocations() {
   in.unwindInfo->prepareRelocations();
 }
 
+static void addNonWeakDefinition(const Defined *defined) {
+  if (config->emitChainedFixups)
+    in.chainedFixups->setHasNonWeakDefinition();
+  else
+    in.weakBinding->addNonWeakDefinition(defined);
+}
+
 void Writer::scanSymbols() {
   TimeTraceScope timeScope("Scan symbols");
   for (Symbol *sym : symtab->getSymbols()) {
@@ -674,7 +720,7 @@ void Writer::scanSymbols() {
         continue;
       defined->canonicalize();
       if (defined->overridesWeakDef)
-        in.weakBinding->addNonWeakDefinition(defined);
+        addNonWeakDefinition(defined);
       if (!defined->isAbsolute() && isCodeSection(defined->isec))
         in.unwindInfo->addSymbol(defined);
     } else if (const auto *dysym = dyn_cast<DylibSymbol>(sym)) {
@@ -727,8 +773,13 @@ template <class LP> void Writer::createLoadCommands() {
     seg->index = segIndex++;
   }
 
-  in.header->addLoadCommand(make<LCDyldInfo>(
-      in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports));
+  if (config->emitChainedFixups) {
+    in.header->addLoadCommand(make<LCChainedFixups>(in.chainedFixups));
+    in.header->addLoadCommand(make<LCExportsTrie>(in.exports));
+  } else {
+    in.header->addLoadCommand(make<LCDyldInfo>(
+        in.rebase, in.binding, in.weakBinding, in.lazyBinding, in.exports));
+  }
   in.header->addLoadCommand(make<LCSymtab>(symtabSection, stringTableSection));
   in.header->addLoadCommand(
       make<LCDysymtab>(symtabSection, indirectSymtabSection));
@@ -1037,16 +1088,12 @@ void Writer::finalizeAddresses() {
 void Writer::finalizeLinkEditSegment() {
   TimeTraceScope timeScope("Finalize __LINKEDIT segment");
   // Fill __LINKEDIT contents.
-  std::vector<LinkEditSection *> linkEditSections{
-      in.rebase,
-      in.binding,
-      in.weakBinding,
-      in.lazyBinding,
-      in.exports,
-      symtabSection,
-      indirectSymtabSection,
-      dataInCodeSection,
-      functionStartsSection,
+  std::array<LinkEditSection *, 10> linkEditSections{
+      in.rebase,         in.binding,
+      in.weakBinding,    in.lazyBinding,
+      in.exports,        in.chainedFixups,
+      symtabSection,     indirectSymtabSection,
+      dataInCodeSection, functionStartsSection,
   };
   SmallVector<std::shared_future<void>> threadFutures;
   threadFutures.reserve(linkEditSections.size());
@@ -1147,6 +1194,53 @@ void Writer::writeUuid() {
   uuidCommand->writeUuid(digest);
 }
 
+// This is step 5 of the algorithm described in the class comment of
+// ChainedFixupsSection.
+void Writer::buildFixupChains() {
+  if (!config->emitChainedFixups)
+    return;
+
+  const std::vector<Location> &loc = in.chainedFixups->getLocations();
+  if (loc.empty())
+    return;
+
+  TimeTraceScope timeScope("Build fixup chains");
+
+  const uint64_t pageSize = target->getPageSize();
+  constexpr uint32_t stride = 4; // for DYLD_CHAINED_PTR_64
+
+  for (size_t i = 0, count = loc.size(); i < count;) {
+    const OutputSegment *oseg = loc[i].isec->parent->parent;
+    uint8_t *buf = buffer->getBufferStart() + oseg->fileOff;
+    uint64_t pageIdx = loc[i].offset / pageSize;
+    ++i;
+
+    while (i < count && loc[i].isec->parent->parent == oseg &&
+           (loc[i].offset / pageSize) == pageIdx) {
+      uint64_t offset = loc[i].offset - loc[i - 1].offset;
+
+      auto fail = [&](Twine message) {
+        error(loc[i].isec->getSegName() + "," + loc[i].isec->getName() +
+              ", offset " +
+              Twine(loc[i].offset - loc[i].isec->parent->getSegmentOffset()) +
+              ": " + message);
+      };
+
+      if (offset < target->wordSize)
+        return fail("fixups overlap");
+      if (offset % stride != 0)
+        return fail(
+            "fixups are unaligned (offset " + Twine(offset) +
+            " is not a multiple of the stride). Re-link with -no_fixup_chains");
+
+      // The "next" field is in the same location for bind and rebase entries.
+      reinterpret_cast<dyld_chained_ptr_64_bind *>(buf + loc[i - 1].offset)
+          ->next = offset / stride;
+      ++i;
+    }
+  }
+}
+
 void Writer::writeCodeSignature() {
   if (codeSignatureSection) {
     TimeTraceScope timeScope("Write code signature");
@@ -1162,6 +1256,7 @@ void Writer::writeOutputFile() {
     return;
   writeSections();
   applyOptimizationHints();
+  buildFixupChains();
   writeUuid();
   writeCodeSignature();
 
@@ -1191,7 +1286,7 @@ template <class LP> void Writer::run() {
   if (errorCount())
     return;
 
-  if (in.stubHelper->isNeeded())
+  if (in.stubHelper && in.stubHelper->isNeeded())
     in.stubHelper->setUp();
 
   if (in.objCImageInfo->isNeeded())
@@ -1235,16 +1330,20 @@ void macho::createSyntheticSections() {
       make<DeduplicatedCStringSection>(section_names::objcMethname);
   in.wordLiteralSection =
       config->dedupLiterals ? make<WordLiteralSection>() : nullptr;
-  in.rebase = make<RebaseSection>();
-  in.binding = make<BindingSection>();
-  in.weakBinding = make<WeakBindingSection>();
-  in.lazyBinding = make<LazyBindingSection>();
+  if (config->emitChainedFixups) {
+    in.chainedFixups = make<ChainedFixupsSection>();
+  } else {
+    in.rebase = make<RebaseSection>();
+    in.binding = make<BindingSection>();
+    in.weakBinding = make<WeakBindingSection>();
+    in.lazyBinding = make<LazyBindingSection>();
+    in.lazyPointers = make<LazyPointerSection>();
+    in.stubHelper = make<StubHelperSection>();
+  }
   in.exports = make<ExportSection>();
   in.got = make<GotSection>();
   in.tlvPointers = make<TlvPointerSection>();
-  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/chained-fixups-addend.s b/lld/test/MachO/chained-fixups-addend.s
new file mode 100644
index 000000000000..f82da13b3ba2
--- /dev/null
+++ b/lld/test/MachO/chained-fixups-addend.s
@@ -0,0 +1,74 @@
+# REQUIRES: x86
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/dylib.s -o %t/dylib.o
+# RUN: %lld -lSystem -dylib %t/dylib.o -o %t/libdylib.dylib
+
+## FileCheck does not like wrapping arithmetic, so we specify all 3 check variables manually:
+##   ADDEND  := inline/outline addend, unsigned
+##   OUTLINE := outline addend, signed
+##   REBASE  := target of rebase; 0x1000 + ADDEND, unsigned
+
+## We can use the DYLD_CHAINED_IMPORT import format if 0 <= ADDEND <= 255 bytes.
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=0
+# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out
+# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \
+# RUN:     FileCheck %s -D#OUTLINE=0 -D#ADDEND=0 -D#%x,REBASE=0x1000 --check-prefixes=IMPORT,COMMON
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=255
+# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out
+# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \
+# RUN:     FileCheck %s -D#OUTLINE=0 -D#ADDEND=255 -D#%x,REBASE=0x10FF --check-prefixes=IMPORT,COMMON
+
+## DYLD_CHAINED_IMPORT_ADDEND is used if the addend fits in a 32-bit signed integer.
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=-1
+# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out
+# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \
+# RUN:     FileCheck %s -D#%d,OUTLINE=-1 -D#%x,ADDEND=0xFFFFFFFFFFFFFFFF -D#%x,REBASE=0xFFF \
+# RUN:     --check-prefixes=IMPORT-ADDEND,COMMON
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=256
+# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out
+# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \
+# RUN:     FileCheck %s -D#OUTLINE=256 -D#ADDEND=256 -D#%x,REBASE=0x1100 \
+# RUN:     --check-prefixes=IMPORT-ADDEND,COMMON
+
+## Otherwise, DYLD_CHAINED_IMPORT_ADDEND64 is used.
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/main.s -o %t/main.o --defsym ADDEND=0x100000000
+# RUN: %lld -lSystem -dylib %t/main.o -L%t -ldylib -fixup_chains -o %t/out
+# RUN: llvm-objdump --macho --chained-fixups --dyld-info %t/out | \
+# RUN:     FileCheck %s -D#%x,OUTLINE=0x100000000 -D#%x,ADDEND=0x100000000 \
+# RUN:     -D#%x,REBASE=0x100001000 --check-prefixes=IMPORT-ADDEND64,COMMON
+
+# COMMON:      dyld information:
+# COMMON-NEXT: segment  section address pointer  type  addend            dylib     symbol/vm address
+# COMMON-NEXT: __DATA   __data    {{.*}}         bind  0x[[#%X, ADDEND]] libdylib  _dysym
+# COMMON-NEXT: __DATA   __data    {{.*}}         rebase                            0x[[#%X, REBASE]]
+
+# COMMON:        chained fixups header (LC_DYLD_CHAINED_FIXUPS)
+# IMPORT:          imports_format = 1 (DYLD_CHAINED_IMPORT)
+# IMPORT-ADDEND:   imports_format = 2 (DYLD_CHAINED_IMPORT_ADDEND)
+# IMPORT-ADDEND64: imports_format = 3 (DYLD_CHAINED_IMPORT_ADDEND64)
+
+# IMPORT:             dyld chained import[0]
+# IMPORT-ADDEND:      dyld chained import addend[0]
+# IMPORT-ADDEND64:    dyld chained import addend64[0]
+# COMMON-NEXT:          lib_ordinal = 2 (libdylib)
+# COMMON-NEXT:          weak_import = 0
+# COMMON-NEXT:          name_offset = 0 (_dysym)
+# IMPORT-ADDEND-NEXT:   addend      = [[#%d, OUTLINE]]
+# IMPORT-ADDEND64-NEXT: addend      = [[#%d, OUTLINE]]
+
+#--- dylib.s
+.globl _dysym
+
+_dysym:
+  ret
+
+#--- main.s
+.globl _dysym, _local
+
+.data
+_local:
+.skip 128
+
+.p2align 3
+.quad _dysym + ADDEND
+.quad _local + ADDEND

diff  --git a/lld/test/MachO/chained-fixups-empty.s b/lld/test/MachO/chained-fixups-empty.s
new file mode 100644
index 000000000000..fc4af21df401
--- /dev/null
+++ b/lld/test/MachO/chained-fixups-empty.s
@@ -0,0 +1,24 @@
+# REQUIRES: x86
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
+# RUN: %lld -dylib -fixup_chains -o %t %t.o
+# RUN: llvm-objdump --macho --all-headers %t | FileCheck %s
+
+## dyld always expects LC_DYLD_CHAINED_FIXUPS to point to a valid
+## chained fixups header, even if there aren't any fixups.
+# CHECK:            cmd LC_DYLD_CHAINED_FIXUPS
+# CHECK-NEXT:   cmdsize 16
+# CHECK-NEXT:   dataoff [[#]]
+# CHECK-NEXT:  datasize 48
+
+## While ld64 emits the root trie node even if there are no exports,
+## setting the data size and offset to zero works too in practice.
+# CHECK:            cmd LC_DYLD_EXPORTS_TRIE
+# CHECK-NEXT:   cmdsize 16
+# CHECK-NEXT:   dataoff 0
+# CHECK-NEXT:  datasize 0
+
+## Old load commands should not be generated.
+# CHECK-NOT: cmd LC_DYLD_INFO_ONLY
+
+_not_exported:
+  .space 1

diff  --git a/lld/test/MachO/flat-namespace-interposable.s b/lld/test/MachO/flat-namespace-interposable.s
index fb15d447a23c..950de32726d1 100644
--- a/lld/test/MachO/flat-namespace-interposable.s
+++ b/lld/test/MachO/flat-namespace-interposable.s
@@ -9,14 +9,22 @@
 
 # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos -o %t/foo.o %t/foo.s
 # RUN: %lld -lSystem -flat_namespace -o %t/foo %t/foo.o
+# RUN: %lld -lSystem -flat_namespace -fixup_chains -o %t/chained %t/foo.o
 # RUN: %lld -lSystem -dylib -flat_namespace -o %t/foo.dylib %t/foo.o
+# RUN: %lld -lSystem -dylib -flat_namespace -fixup_chains -o %t/chained.dylib %t/foo.o
 # RUN: %lld -lSystem -bundle -flat_namespace -o %t/foo.bundle %t/foo.o
+# RUN: %lld -lSystem -bundle -flat_namespace -fixup_chains -o %t/chained.bundle %t/foo.o
 # RUN: llvm-objdump --macho --syms --rebase --bind --lazy-bind --weak-bind %t/foo | \
 # RUN:   FileCheck %s --check-prefixes=SYMS,EXEC --implicit-check-not=_private_extern
-# RUN: llvm-objdump --macho --syms --rebase --bind --lazy-bind --weak-bind %t/foo.dylib | \
-# RUN:   FileCheck %s --check-prefixes=SYMS,DYLIB --implicit-check-not=_private_extern
-# RUN: llvm-objdump --macho --syms --rebase --bind --lazy-bind --weak-bind %t/foo.bundle | \
-# RUN:   FileCheck %s --check-prefixes=SYMS,DYLIB --implicit-check-not=_private_extern
+# RUN: llvm-objdump --macho --syms %t/chained >> %t/chained.objdump
+# RUN: llvm-objdump --macho --dyld-info %t/chained >> %t/chained.objdump
+# RUN: FileCheck %s --check-prefixes=SYMS,CHAINED-EXEC < %t/chained.objdump
+# RUN: llvm-objdump --macho --syms %t/chained.dylib >> %t/dylib.objdump
+# RUN: llvm-objdump --macho --dyld-info %t/chained.dylib >> %t/dylib.objdump
+# RUN: FileCheck %s --check-prefixes=SYMS,CHAINED-DYLIB < %t/dylib.objdump
+# RUN: llvm-objdump --macho --syms %t/chained.bundle >> %t/bundle.objdump
+# RUN: llvm-objdump --macho --dyld-info %t/chained.bundle >> %t/bundle.objdump
+# RUN: FileCheck %s --check-prefixes=SYMS,CHAINED-DYLIB < %t/bundle.objdump
 
 # SYMS: SYMBOL TABLE:
 # SYMS-DAG: [[#%x, EXTERN_REF:]] l     O __DATA,__data _extern_ref
@@ -44,6 +52,14 @@
 # EXEC-NEXT:  __DATA   __data           0x[[#%.8X, WEAK_REF]]  pointer       0   _weak_extern
 # EXEC-EMPTY:
 
+# CHAINED-EXEC:      dyld information:
+# CHAINED-EXEC-NEXT: segment      section  address               pointer type   addend dylib  symbol/vm address
+# CHAINED-EXEC-DAG:  __DATA_CONST __got    {{.*}}                {{.*}}  bind   0x0    weak   _weak_extern
+# CHAINED-EXEC-DAG: __DATA       __data    0x[[#%x, EXTERN_REF]] {{.*}}  rebase               {{.*}}
+# CHAINED-EXEC-DAG: __DATA       __data    0x[[#%x, WEAK_REF]]   {{.*}}  bind   0x0    weak   _weak_extern
+# CHAINED-EXEC-DAG: __DATA       __data    0x[[#%x, LOCAL_REF]]  {{.*}}  rebase               {{.*}}
+# CHAINED-EXEC-EMPTY:
+
 # DYLIB:       Rebase table:
 # DYLIB-NEXT:  segment      section          address                  type
 # DYLIB-DAG:   __DATA       __la_symbol_ptr  0x[[#%X, WEAK_LAZY:]]    pointer
@@ -69,6 +85,16 @@
 # DYLIB-NEXT:  __DATA   __data             0x[[#%.8X, WEAK_REF]]    pointer      0   _weak_extern
 # DYLIB-EMPTY:
 
+# CHAINED-DYLIB: dyld information:
+# CHAINED-DYLIB-NEXT: segment      section      address               pointer type  addend dylib           symbol/vm address
+# CHAINED-DYLIB-DAG: __DATA_CONST __got         {{.*}}                {{.*}}  bind  0x0    weak            _weak_extern
+# CHAINED-DYLIB-DAG: __DATA_CONST __got         {{.*}}                {{.*}}  bind  0x0    flat-namespace  _extern
+# CHAINED-DYLIB-DAG: __DATA       __data        0x[[#%x, EXTERN_REF]] {{.*}}  bind  0x0    flat-namespace  _extern
+# CHAINED-DYLIB-DAG: __DATA       __data        0x[[#%x, WEAK_REF]]   {{.*}}  bind  0x0    weak            _weak_extern
+# CHAINED-DYLIB-DAG: __DATA       __data        0x[[#%x, LOCAL_REF]]  {{.*}}  rebase                       {{.*}}
+# CHAINED-DYLIB-DAG: __DATA       __thread_ptrs 0x[[#%x, TLV_REF]]    {{.*}}  bind  0x0    flat-namespace  _tlv
+# CHAINED-DYLIB-EMPTY:
+
 #--- foo.s
 
 .globl _main, _extern, _weak_extern, _tlv

diff  --git a/lld/test/MachO/invalid/chained-fixups-incompatible.s b/lld/test/MachO/invalid/chained-fixups-incompatible.s
new file mode 100644
index 000000000000..83411472beb9
--- /dev/null
+++ b/lld/test/MachO/invalid/chained-fixups-incompatible.s
@@ -0,0 +1,20 @@
+# REQUIRES: x86, aarch64
+# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
+# RUN: not %lld -lSystem -no_pie -fixup_chains %t.o -o /dev/null 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=NO-PIE
+# RUN: %no-fatal-warnings-lld -fixup_chains %t.o -o /dev/null \
+# RUN:   -lSystem -platform_version macos 10.15 10.15 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=VERSION
+# RUN: llvm-mc -filetype=obj -triple=arm64_32-apple-darwin %s -o %t-arm64_32.o
+# RUN: not %lld-watchos -lSystem -fixup_chains %t-arm64_32.o -o /dev/null 2>&1 | \
+# RUN:   FileCheck %s --check-prefix=ARCH
+
+## Check that we emit diagnostics when -fixup_chains is explicitly specified,
+## but we don't support creating chained fixups for said configuration.
+# NO-PIE:  error: -fixup_chains is incompatible with -no_pie
+# VERSION: warning: -fixup_chains requires macOS 11.0, which is newer than target minimum of 10.15
+# ARCH:    error: -fixup_chains is only supported on x86_64 and arm64 targets
+
+.globl _main
+_main:
+  ret

diff  --git a/lld/test/MachO/tlv-dylib.s b/lld/test/MachO/tlv-dylib.s
index 0aff5f5040a2..39b951c0962e 100644
--- a/lld/test/MachO/tlv-dylib.s
+++ b/lld/test/MachO/tlv-dylib.s
@@ -13,6 +13,13 @@
 # DYLIB-NEXT:  segment  section            address     type
 # DYLIB-EMPTY:
 
+# RUN: %lld -dylib -install_name @executable_path/libtlv.dylib \
+# RUN:   -lSystem -fixup_chains -o %t/libtlv.dylib %t/libtlv.o
+## Make sure we don't emit fixups in __thread_vars.
+# RUN: llvm-objdump --macho --chained-fixups %t/libtlv.dylib | \
+# RUN:   FileCheck %s --check-prefix=CHAINED
+# CHAINED-NOT: __thread_vars
+
 # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/test.s -o %t/test.o
 # RUN: %lld -lSystem -L%t -ltlv %t/test.o -o %t/test
 # RUN: llvm-objdump --bind -d --no-show-raw-insn %t/test | FileCheck %s

diff  --git a/lld/test/MachO/weak-binding.s b/lld/test/MachO/weak-binding.s
index 0c79f981137a..e9d143d7b806 100644
--- a/lld/test/MachO/weak-binding.s
+++ b/lld/test/MachO/weak-binding.s
@@ -4,13 +4,21 @@
 # RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %t/libfoo.s -o %t/libfoo.o
 # RUN: %lld -dylib %t/libfoo.o -o %t/libfoo.dylib
 # RUN: %lld %t/test.o -L%t -lfoo -o %t/test -lSystem
-# RUN: llvm-objdump -d --no-show-raw-insn --rebase --bind --lazy-bind \
-# RUN:   --weak-bind --full-contents %t/test | FileCheck %s
+# RUN: llvm-objdump -d --no-show-raw-insn --rebase --bind --lazy-bind --weak-bind \
+# RUN:     --full-contents %t/test | FileCheck --check-prefixes=COMMON,CHECK %s
 
-# CHECK:      Contents of section __DATA_CONST,__got:
+# RUN: %lld -fixup_chains %t/test.o -L%t -lfoo -o %t/chained -lSystem
+# RUN: llvm-objdump -d --no-show-raw-insn --syms --full-contents %t/chained > %t/chained.objdump
+# RUN: llvm-objdump --macho --dyld-info %t/chained >> %t/chained.objdump
+# RUN: FileCheck %s --check-prefixes=COMMON,CHAINED < %t/chained.objdump
+
+# CHAINED: SYMBOL TABLE:
+# CHAINED: [[#%x,WEAK_INT:]] l     F __TEXT,__text _weak_internal{{$}}
+
+# COMMON:      Contents of section __DATA_CONST,__got:
 ## Check that this section contains a nonzero pointer. It should point to
 ## _weak_external_for_gotpcrel.
-# CHECK-NEXT: {{[0-9a-f]+}} {{[0-9a-f ]*[1-9a-f]+[0-9a-f ]*}}
+# COMMON-NEXT: {{[0-9a-f]+}} {{[0-9a-f ]*[1-9a-f]+[0-9a-f ]*}}
 
 # CHECK:      Contents of section __DATA,__la_symbol_ptr:
 ## Check that this section contains a nonzero pointer. It should point to
@@ -18,16 +26,16 @@
 ## the bytes here are in little-endian order.
 # CHECK-NEXT: {{[0-9a-f]+}} {{[0-9a-f ]*[1-9a-f]+[0-9a-f ]*}}
 
-# CHECK:      <_main>:
-# CHECK-NEXT: movq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_DY_GOT_ADDR:]]
-# CHECK-NEXT: movq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_EXT_GOT_ADDR:]]
-# CHECK-NEXT: leaq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_INT_GOT_ADDR:]]
-# CHECK-NEXT: movq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_TLV_ADDR:]]
-# CHECK-NEXT: movq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_DY_TLV_ADDR:]]
-# CHECK-NEXT: leaq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_INT_TLV_ADDR:]]
-# CHECK-NEXT: callq 0x{{[0-9a-f]*}}
-# CHECK-NEXT: callq 0x{{[0-9a-f]*}}
-# CHECK-NEXT: callq 0x{{[0-9a-f]*}}
+# COMMON-LABEL:      <_main>:
+# COMMON-NEXT: movq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_DY_GOT_ADDR:]]
+# COMMON-NEXT: movq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_EXT_GOT_ADDR:]]
+# COMMON-NEXT: leaq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_INT_GOT_ADDR:]]
+# COMMON-NEXT: movq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_TLV_ADDR:]]
+# COMMON-NEXT: movq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_DY_TLV_ADDR:]]
+# COMMON-NEXT: leaq  [[#]](%rip), %rax  ## 0x[[#%X,WEAK_INT_TLV_ADDR:]]
+# COMMON-NEXT: callq 0x{{[0-9a-f]*}}
+# COMMON-NEXT: callq 0x{{[0-9a-f]*}}
+# COMMON-NEXT: callq 0x{{[0-9a-f]*}}
 
 # CHECK-LABEL: Rebase table:
 # CHECK:       __DATA        __la_symbol_ptr 0x[[#%x,WEAK_EXT_FN:]]  pointer
@@ -65,6 +73,21 @@
 # WEAK-INTERNAL-NOT: _weak_internal_fn
 # WEAK-INTERNAL-NOT: _weak_internal_tlv
 
+# CHAINED-LABEL: dyld information:
+# CHAINED-NEXT:  segment      section       address                  pointer type  addend dylib      symbol/vm address
+# CHAINED-DAG:   __DATA_CONST __got         0x{{[0-9a-f]*}}          {{.*}}  bind  0x0    weak       _weak_external_fn
+# CHAINED-DAG:   __DATA_CONST __got         0x{{[0-9a-f]*}}          {{.*}}  bind  0x0    weak       _weak_dysym_fn
+# CHAINED-DAG:   __DATA_CONST __got         0x[[#WEAK_EXT_GOT_ADDR]] {{.*}}  bind  0x0    weak       _weak_external_for_gotpcrel
+# CHAINED-DAG:   __DATA_CONST __got         0x[[#WEAK_DY_GOT_ADDR]]  {{.*}}  bind  0x0    weak       _weak_dysym_for_gotpcrel
+# CHAINED-DAG:   __DATA       __data        0x{{[0-9a-f]*}}          {{.*}}  bind  0x0    weak       _weak_dysym
+# CHAINED-DAG:   __DATA       __data        0x{{[0-9a-f]*}}          {{.*}}  bind  0x2    weak       _weak_external
+# CHAINED-DAG:   __DATA       __data        0x{{[0-9a-f]*}}          {{.*}}  rebase                  0x[[#%X,WEAK_INT]]
+# CHAINED-DAG:   __DATA       __thread_vars 0x{{[0-9a-f]*}}          {{.*}}  bind  0x0    libSystem  __tlv_bootstrap
+# CHAINED-DAG:   __DATA       __thread_vars 0x{{[0-9a-f]*}}          {{.*}}  bind  0x0    libSystem  __tlv_bootstrap
+# CHAINED-DAG:   __DATA       __thread_ptrs 0x[[#WEAK_DY_TLV_ADDR]]  {{.*}}  bind  0x0    weak       _weak_dysym_tlv
+# CHAINED-DAG:   __DATA       __thread_ptrs 0x[[#WEAK_TLV_ADDR]]     {{.*}}  bind  0x0    weak       _weak_tlv
+# CHAINED-EMPTY:
+
 #--- libfoo.s
 
 .globl _weak_dysym

diff  --git a/lld/test/MachO/weak-reference.s b/lld/test/MachO/weak-reference.s
index 6847db1c5df3..ff72cc3d505f 100644
--- a/lld/test/MachO/weak-reference.s
+++ b/lld/test/MachO/weak-reference.s
@@ -7,13 +7,15 @@
 # RUN: %lld -lSystem -dylib %t/libfoo.o -o %t/libfoo.dylib
 
 # RUN: %lld -lSystem %t/test.o %t/libfoo.dylib -o %t/test
-# RUN: llvm-objdump --macho --syms --bind %t/test | FileCheck %s --check-prefixes=SYMS,BIND
+# RUN: %lld -fixup_chains -lSystem %t/test.o %t/libfoo.dylib -o %t/chained
+# RUN: llvm-objdump --macho --syms --bind --lazy-bind %t/test | FileCheck %s --check-prefixes=SYMS,BIND
+# RUN: llvm-objdump --macho --syms --dyld-info %t/chained | FileCheck %s --check-prefixes=CHAINED
 ## llvm-objdump doesn't print out all the flags info for lazy & weak bindings,
 ## so we use obj2yaml instead to test them.
 # RUN: obj2yaml %t/test | FileCheck %s --check-prefix=YAML
 
 # RUN: %lld -lSystem %t/libfoo.dylib %t/test.o -o %t/test
-# RUN: llvm-objdump --macho --syms --bind %t/test | FileCheck %s --check-prefixes=SYMS,BIND
+# RUN: llvm-objdump --macho --syms --bind --lazy-bind %t/test | FileCheck %s --check-prefixes=SYMS,BIND
 # RUN: obj2yaml %t/test | FileCheck %s --check-prefix=YAML
 
 # SYMS:     SYMBOL TABLE:
@@ -30,6 +32,18 @@
 # BIND-DAG:  __DATA        __thread_ptrs    0x{{[0-9a-f]+}} pointer       0 libfoo  _foo_tlv (weak_import)
 # BIND-DAG:  __DATA        __data           0x{{[0-9a-f]+}} pointer       0 libfoo  _weak_foo (weak_import)
 # BIND-DAG:  __DATA        __la_symbol_ptr  0x{{[0-9a-f]+}} pointer       0 libfoo  _weak_foo_fn (weak_import)
+# BIND:      Lazy bind table:
+# BIND-NEXT: segment       section          address                         dylib   symbol
+# BIND-DAG:  __DATA        __la_symbol_ptr  0x{{[0-9a-f]+}}                  libfoo  _foo_fn
+
+# CHAINED:      dyld information:
+# CHAINED-NEXT: segment      section       address pointer type  addend dylib    symbol/vm address
+# CHAINED-DAG:  __DATA_CONST __got             {{.*}}      bind  0x0    libfoo   _foo (weak import)
+# CHAINED-DAG:  __DATA       __data            {{.*}}      bind  0x0    libfoo   _foo (weak import)
+# CHAINED-DAG:  __DATA       __thread_ptrs     {{.*}}      bind  0x0    libfoo   _foo_tlv (weak import)
+# CHAINED-DAG:  __DATA_CONST __got             {{.*}}      bind  0x0    libfoo   _foo_fn (weak import)
+# CHAINED-DAG:  __DATA       __data            {{.*}}      bind  0x0    weak     _weak_foo (weak import)
+# CHAINED-DAG:  __DATA_CONST __got             {{.*}}      bind  0x0    weak     _weak_foo_fn (weak import)
 
 # YAML-LABEL: WeakBindOpcodes:
 # YAML:        - Opcode:          BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM
@@ -48,22 +62,34 @@
 ## the reference is to a function symbol or a TLV. I'm not sure if there's a
 ## good reason for that, so I'm deviating here for a simpler implementation.
 # RUN: %lld -lSystem %t/test.o %t/strongref.o %t/libfoo.dylib -o %t/with-strong
+# RUN: %lld -fixup_chains -lSystem %t/test.o %t/strongref.o %t/libfoo.dylib -o %t/with-strong-chained
 # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND
+# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED
 # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML
 # RUN: %lld -lSystem %t/strongref.o %t/test.o %t/libfoo.dylib -o %t/with-strong
+# RUN: %lld -fixup_chains -lSystem %t/strongref.o %t/test.o %t/libfoo.dylib -o %t/with-strong-chained
 # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND
+# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED
 # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML
 # RUN: %lld -lSystem %t/libfoo.dylib %t/strongref.o %t/test.o -o %t/with-strong
+# RUN: %lld -fixup_chains -lSystem %t/libfoo.dylib %t/strongref.o %t/test.o -o %t/with-strong-chained
 # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND
+# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED
 # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML
 # RUN: %lld -lSystem %t/libfoo.dylib %t/test.o %t/strongref.o -o %t/with-strong
+# RUN: %lld -fixup_chains -lSystem %t/libfoo.dylib %t/test.o %t/strongref.o -o %t/with-strong-chained
 # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND
+# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED
 # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML
 # RUN: %lld -lSystem %t/test.o %t/libfoo.dylib %t/strongref.o -o %t/with-strong
+# RUN: %lld -fixup_chains -lSystem %t/test.o %t/libfoo.dylib %t/strongref.o -o %t/with-strong-chained
 # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND
+# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED
 # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML
 # RUN: %lld -lSystem %t/strongref.o %t/libfoo.dylib %t/test.o -o %t/with-strong
+# RUN: %lld -fixup_chains -lSystem %t/strongref.o %t/libfoo.dylib %t/test.o -o %t/with-strong-chained
 # RUN: llvm-objdump --macho --bind %t/with-strong | FileCheck %s --check-prefix=STRONG-BIND
+# RUN: llvm-objdump --macho --dyld-info %t/with-strong-chained | FileCheck %s --check-prefix=STRONG-CHAINED
 # RUN: obj2yaml %t/with-strong | FileCheck %s --check-prefix=STRONG-YAML
 
 # STRONG-BIND:      Bind table:
@@ -76,6 +102,17 @@
 # STRONG-BIND-DAG:  __DATA        __data           0x{{[0-9a-f]+}} pointer         0 libfoo  _weak_foo{{$}}
 # STRONG-BIND-DAG:  __DATA        __la_symbol_ptr  0x{{[0-9a-f]+}} pointer         0 libfoo  _weak_foo_fn{{$}}
 
+# STRONG-CHAINED:      dyld information:
+# STRONG-CHAINED-NEXT: segment      section      address pointer type  addend dylib   symbol/vm address
+# STRONG-CHAINED-DAG:  __DATA_CONST __got            {{.*}}      bind  0x0    weak    _weak_foo_fn{{$}}
+# STRONG-CHAINED-DAG:  __DATA_CONST __got            {{.*}}      bind  0x0    libfoo  _foo_fn{{$}}
+# STRONG-CHAINED-DAG:  __DATA_CONST __got            {{.*}}      bind  0x0    libfoo  _foo{{$}}
+# STRONG-CHAINED-DAG:  __DATA       __data           {{.*}}      bind  0x0    libfoo  _foo{{$}}
+# STRONG-CHAINED-DAG:  __DATA       __data           {{.*}}      bind  0x0    libfoo  _foo{{$}}
+# STRONG-CHAINED-DAG:  __DATA       __data           {{.*}}      bind  0x0    weak    _weak_foo{{$}}
+# STRONG-CHAINED-DAG:  __DATA       __data           {{.*}}      bind  0x0    weak    _weak_foo{{$}}
+# STRONG-CHAINED-DAG:  __DATA       __thread_ptrs    {{.*}}      bind  0x0    libfoo  _foo_tlv{{$}}
+
 # STRONG-YAML-LABEL: WeakBindOpcodes:
 # STRONG-YAML:        - Opcode:          BIND_OPCODE_SET_SYMBOL_TRAILING_FLAGS_IMM
 # STRONG-YAML-NEXT:     Imm:             0

diff  --git a/llvm/include/llvm/BinaryFormat/MachO.h b/llvm/include/llvm/BinaryFormat/MachO.h
index 6205cd82067d..d51af31fb14f 100644
--- a/llvm/include/llvm/BinaryFormat/MachO.h
+++ b/llvm/include/llvm/BinaryFormat/MachO.h
@@ -1022,7 +1022,7 @@ struct nlist_64 {
 };
 
 // Values for dyld_chained_fixups_header::imports_format.
-enum {
+enum ChainedImportFormat {
   DYLD_CHAINED_IMPORT = 1,
   DYLD_CHAINED_IMPORT_ADDEND = 2,
   DYLD_CHAINED_IMPORT_ADDEND64 = 3,


        


More information about the llvm-commits mailing list