[lld] [lld-macho] Implement ObjC category merging (-merge-objc-categories) (PR #82928)

via llvm-commits llvm-commits at lists.llvm.org
Sun Feb 25 09:10:39 PST 2024


https://github.com/alx32 created https://github.com/llvm/llvm-project/pull/82928

This change adds a flag to lld to enable category merging for MachoO + ObjC. Behavior: if in the same link unit, multiple categories are extending the same class, then they get merged into a single category.
Ex: `Cat1(method1+method2,protocol1) + Cat2(method3+method4,protocol2, property1) = Cat1_2(method1+method2+method3+method4, protocol1+protocol2, property1)`

Notes on implementation decisions made in this diff:
1. There is a possibility to even improve the current implementation by directly merging the category data into the base class (if the base class is present in the link unit) - this improvement may be done as a follow-up.
2. We do the merging as early as possible, on the raw inputSections.
3. We add a new flag for ObjFile (isLinkerGenerated) and create such an ObjFile to which all new linker-generated date belongs.
4. We add a new flag (linkerOptimizeReason) to ConcatInputSection and StringPiece to mark that this data has been optimized away. Another way to do it would have been to just mark the pieces as not 'live' but this would require some work-arounds in the actual live symbol determination logic and would also cause symbols to incorrectly show up as 'dead-stripped' when that's not the cause that they are not present.

>From 01239b8daaa46ab66e103130078972f9214cc3d2 Mon Sep 17 00:00:00 2001
From: Alex B <alexborcan at meta.com>
Date: Sun, 25 Feb 2024 08:50:21 -0800
Subject: [PATCH] [lld-macho] Implement ObjC category merging
 (-merge-objc-categories)

This change adds a flag to lld to enable category merging for MachoO + ObjC. If in the same link unit, multiple categories are extending the same class, then they get merged into a single cateogry.

Notes on implemetation decisions made in this diff:
1. There is a possibility to even improve the current implementation by directly merging the category data into the base class (if the base class is present in the link unit) - this may be done as a follow-up.
2. We do the merging as early as possible, on the raw inputSections.
3. We add a new flag for ObjFile (isLinkerGenerated) and create such an ObjFile to which all new linker-generated date belongs.
4. We add a new flag (linkerOptimizeReason) to ConcatInputSection and StringPiece to mark that this data has been optimized away. Another way to do it would have been to just mark the pieces as not 'live' but this would require some work-arounds in the actual live symbol determination logic and would also cause symbols to incorrectly show up as 'dead-stripped' when that's not the cause that they are not present.
---
 lld/MachO/Driver.cpp                   |   13 +-
 lld/MachO/InputFiles.cpp               |   30 +-
 lld/MachO/InputFiles.h                 |    3 +-
 lld/MachO/InputSection.h               |   32 +-
 lld/MachO/MapFile.cpp                  |    6 +-
 lld/MachO/MarkLive.cpp                 |    2 +
 lld/MachO/ObjC.cpp                     | 1042 +++++++++++++++++++++++-
 lld/MachO/ObjC.h                       |    2 +-
 lld/MachO/Options.td                   |    3 +
 lld/MachO/SyntheticSections.cpp        |    8 +-
 lld/test/MachO/objc-category-merging.s |  695 ++++++++++++++++
 11 files changed, 1803 insertions(+), 33 deletions(-)
 create mode 100644 lld/test/MachO/objc-category-merging.s

diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index a57f60c5eed36b..44b1df7592ce00 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1966,15 +1966,20 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     }
 
     gatherInputSections();
+
+    // Run category checking & merging before anything else, it operates
+    // directly on inputSections.
+    if (args.hasArg(OPT_check_category_conflicts))
+      objc::checkCategories();
+
+    if (args.hasArg(OPT_merge_objc_categories))
+      objc::mergeCategories();
+
     if (config->callGraphProfileSort)
       priorityBuilder.extractCallGraphProfile();
 
     if (config->deadStrip)
       markLive();
-
-    if (args.hasArg(OPT_check_category_conflicts))
-      objc::checkCategories();
-
     // ICF assumes that all literals have been folded already, so we must run
     // foldIdenticalLiterals before foldIdenticalSections.
     foldIdenticalLiterals();
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 158c3fbf7b0fca..2329dd57b6bf70 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -965,21 +965,23 @@ void ObjFile::parseLinkerOptions(SmallVectorImpl<StringRef> &LCLinkerOptions) {
 SmallVector<StringRef> macho::unprocessedLCLinkerOptions;
 ObjFile::ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName,
                  bool lazy, bool forceHidden, bool compatArch,
-                 bool builtFromBitcode)
+                 bool builtFromBitcode, bool isLinkerGenerated)
     : InputFile(ObjKind, mb, lazy), modTime(modTime), forceHidden(forceHidden),
-      builtFromBitcode(builtFromBitcode) {
+      builtFromBitcode(builtFromBitcode), isLinkerGenerated(isLinkerGenerated) {
   this->archiveName = std::string(archiveName);
   this->compatArch = compatArch;
-  if (lazy) {
-    if (target->wordSize == 8)
-      parseLazy<LP64>();
-    else
-      parseLazy<ILP32>();
-  } else {
-    if (target->wordSize == 8)
-      parse<LP64>();
-    else
-      parse<ILP32>();
+  if (!isLinkerGenerated) {
+    if (lazy) {
+      if (target->wordSize == 8)
+        parseLazy<LP64>();
+      else
+        parseLazy<ILP32>();
+    } else {
+      if (target->wordSize == 8)
+        parse<LP64>();
+      else
+        parse<ILP32>();
+    }
   }
 }
 
@@ -1103,6 +1105,8 @@ void ObjFile::parseDebugInfo() {
 }
 
 ArrayRef<data_in_code_entry> ObjFile::getDataInCode() const {
+  if (!mb.getBufferSize())
+    return {};
   const auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
   const load_command *cmd = findCommand(buf, LC_DATA_IN_CODE);
   if (!cmd)
@@ -1113,6 +1117,8 @@ ArrayRef<data_in_code_entry> ObjFile::getDataInCode() const {
 }
 
 ArrayRef<uint8_t> ObjFile::getOptimizationHints() const {
+  if (!mb.getBufferSize())
+    return {};
   const auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
   if (auto *cmd =
           findCommand<linkedit_data_command>(buf, LC_LINKER_OPTIMIZATION_HINT))
diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h
index 5e550c167c232e..be3fd6119d4786 100644
--- a/lld/MachO/InputFiles.h
+++ b/lld/MachO/InputFiles.h
@@ -161,7 +161,7 @@ class ObjFile final : public InputFile {
 public:
   ObjFile(MemoryBufferRef mb, uint32_t modTime, StringRef archiveName,
           bool lazy = false, bool forceHidden = false, bool compatArch = true,
-          bool builtFromBitcode = false);
+          bool builtFromBitcode = false, bool isLinkerGenerated = false);
   ArrayRef<llvm::MachO::data_in_code_entry> getDataInCode() const;
   ArrayRef<uint8_t> getOptimizationHints() const;
   template <class LP> void parse();
@@ -181,6 +181,7 @@ class ObjFile final : public InputFile {
   const uint32_t modTime;
   bool forceHidden;
   bool builtFromBitcode;
+  bool isLinkerGenerated;
   std::vector<ConcatInputSection *> debugSections;
   std::vector<CallGraphEntry> callGraph;
   llvm::DenseMap<ConcatInputSection *, FDE> fdes;
diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index becb01017d633a..2cd514f8c55df5 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -24,6 +24,11 @@
 namespace lld {
 namespace macho {
 
+enum LinkerOptReason : uint8_t {
+  NotOptimized,
+  CategoryMerging,
+};
+
 class InputFile;
 class OutputSection;
 
@@ -60,6 +65,7 @@ class InputSection {
   // Whether the data at \p off in this InputSection is live.
   virtual bool isLive(uint64_t off) const = 0;
   virtual void markLive(uint64_t off) = 0;
+  virtual bool isLinkOptimizedAway() const { return false; }
   virtual InputSection *canonical() { return this; }
   virtual const InputSection *canonical() const { return this; }
 
@@ -93,9 +99,9 @@ class InputSection {
   // .subsections_via_symbols, there is typically only one element here.
   llvm::TinyPtrVector<Defined *> symbols;
 
-protected:
   const Section §ion;
 
+protected:
   const Defined *getContainingSymbol(uint64_t off) const;
 };
 
@@ -114,7 +120,12 @@ class ConcatInputSection final : public InputSection {
   bool isLive(uint64_t off) const override { return live; }
   void markLive(uint64_t off) override { live = true; }
   bool isCoalescedWeak() const { return wasCoalesced && symbols.empty(); }
-  bool shouldOmitFromOutput() const { return !live || isCoalescedWeak(); }
+  bool isLinkOptimizedAway() const override {
+    return linkerOptimizeReason != LinkerOptReason::NotOptimized;
+  }
+  bool shouldOmitFromOutput() const {
+    return isLinkOptimizedAway() || !live || isCoalescedWeak();
+  }
   void writeTo(uint8_t *buf);
 
   void foldIdentical(ConcatInputSection *redundant);
@@ -141,6 +152,11 @@ class ConcatInputSection final : public InputSection {
   // first and not copied to the output.
   bool wasCoalesced = false;
   bool live = !config->deadStrip;
+  // Flag to specify if a linker optimzation flagged this section to be
+  // discarded. Need a separate flag from live as live specifically means
+  // 'dead-stripped' which is rellevant in contexts such as linker map
+  // generation
+  LinkerOptReason linkerOptimizeReason = LinkerOptReason::NotOptimized;
   bool hasCallSites = false;
   // This variable has two usages. Initially, it represents the input order.
   // After assignAddresses is called, it represents the offset from the
@@ -176,10 +192,18 @@ struct StringPiece {
   // Only set if deduplicating literals
   uint32_t hash : 31;
   // Offset from the start of the containing output section.
-  uint64_t outSecOff = 0;
+  uint64_t outSecOff : 56;
+  LinkerOptReason linkerOptimizeReason : 8;
+
+  bool shouldOmitFromOutput() const {
+    return !live || linkerOptimizeReason != LinkerOptReason::NotOptimized;
+  }
 
   StringPiece(uint64_t off, uint32_t hash)
-      : inSecOff(off), live(!config->deadStrip), hash(hash) {}
+      : inSecOff(off), live(!config->deadStrip), hash(hash) {
+    outSecOff = 0;
+    linkerOptimizeReason = LinkerOptReason::NotOptimized;
+  }
 };
 
 static_assert(sizeof(StringPiece) == 16, "StringPiece is too big!");
diff --git a/lld/MachO/MapFile.cpp b/lld/MachO/MapFile.cpp
index f736360624ebd1..455ebffbbd567a 100644
--- a/lld/MachO/MapFile.cpp
+++ b/lld/MachO/MapFile.cpp
@@ -80,7 +80,7 @@ static MapInfo gatherMapInfo() {
           if (d->isec && d->getFile() == file &&
               !isa<CStringInputSection>(d->isec)) {
             isReferencedFile = true;
-            if (!d->isLive())
+            if (!d->isLive() && (!d->isec || !d->isec->isLinkOptimizedAway()))
               info.deadSymbols.push_back(d);
           }
       }
@@ -93,6 +93,8 @@ static MapInfo gatherMapInfo() {
           if (auto isec = dyn_cast<CStringInputSection>(subsec.isec)) {
             auto &liveCStrings = info.liveCStringsForSection[isec->parent];
             for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+              if (piece.linkerOptimizeReason != LinkerOptReason::NotOptimized)
+                continue;
               if (piece.live)
                 liveCStrings.push_back({isec->parent->addr + piece.outSecOff,
                                         {fileIndex, isec->getStringRef(i)}});
@@ -203,6 +205,8 @@ void macho::writeMapFile() {
     for (const OutputSection *osec : seg->getSections()) {
       if (auto *concatOsec = dyn_cast<ConcatOutputSection>(osec)) {
         for (const InputSection *isec : concatOsec->inputs) {
+          if (isec->isLinkOptimizedAway())
+            continue;
           for (Defined *sym : isec->symbols)
             if (!(isPrivateLabel(sym->getName()) && sym->size == 0))
               os << format("0x%08llX\t0x%08llX\t[%3u] %s\n", sym->getVA(),
diff --git a/lld/MachO/MarkLive.cpp b/lld/MachO/MarkLive.cpp
index a37213d5613afb..5eb189574dec1e 100644
--- a/lld/MachO/MarkLive.cpp
+++ b/lld/MachO/MarkLive.cpp
@@ -259,6 +259,8 @@ void markLive() {
           dyn_cast_or_null<DylibSymbol>(symtab->find("dyld_stub_binder")))
     marker->addSym(stubBinder);
   for (ConcatInputSection *isec : inputSections) {
+    if (isec->isLinkOptimizedAway())
+      continue;
     // Sections marked no_dead_strip
     if (isec->getFlags() & S_ATTR_NO_DEAD_STRIP) {
       marker->enqueue(isec, 0);
diff --git a/lld/MachO/ObjC.cpp b/lld/MachO/ObjC.cpp
index 67254ec53a2145..04827933753350 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -7,16 +7,19 @@
 //===----------------------------------------------------------------------===//
 
 #include "ObjC.h"
+#include "ConcatOutputSection.h"
 #include "InputFiles.h"
 #include "InputSection.h"
 #include "Layout.h"
 #include "OutputSegment.h"
+#include "SyntheticSections.h"
 #include "Target.h"
 
 #include "lld/Common/ErrorHandler.h"
 #include "llvm/ADT/DenseMap.h"
 #include "llvm/BinaryFormat/MachO.h"
 #include "llvm/Bitcode/BitcodeReader.h"
+#include "llvm/Support/TimeProfiler.h"
 
 using namespace llvm;
 using namespace llvm::MachO;
@@ -78,7 +81,9 @@ namespace {
   DO(Ptr, classMethods)                                                        \
   DO(Ptr, protocols)                                                           \
   DO(Ptr, instanceProps)                                                       \
-  DO(Ptr, classProps)
+  DO(Ptr, classProps)                                                          \
+  DO(uint32_t, size)                                                           \
+  DO(uint32_t, padding)
 
 CREATE_LAYOUT_CLASS(Category, FOR_EACH_CATEGORY_FIELD);
 
@@ -112,13 +117,19 @@ CREATE_LAYOUT_CLASS(ROClass, FOR_EACH_RO_CLASS_FIELD);
 #undef FOR_EACH_RO_CLASS_FIELD
 
 #define FOR_EACH_LIST_HEADER(DO)                                               \
-  DO(uint32_t, size)                                                           \
-  DO(uint32_t, count)
+  DO(uint32_t, structSize)                                                     \
+  DO(uint32_t, structCount)
 
 CREATE_LAYOUT_CLASS(ListHeader, FOR_EACH_LIST_HEADER);
 
 #undef FOR_EACH_LIST_HEADER
 
+#define FOR_EACH_PROTOCOL_LIST_HEADER(DO) DO(Ptr, protocolCount)
+
+CREATE_LAYOUT_CLASS(ProtocolListHeader, FOR_EACH_PROTOCOL_LIST_HEADER);
+
+#undef FOR_EACH_PROTOCOL_LIST_HEADER
+
 #define FOR_EACH_METHOD(DO)                                                    \
   DO(Ptr, name)                                                                \
   DO(Ptr, type)                                                                \
@@ -176,13 +187,22 @@ ObjcCategoryChecker::ObjcCategoryChecker()
       roClassLayout(target->wordSize), listHeaderLayout(target->wordSize),
       methodLayout(target->wordSize) {}
 
-// \p r must point to an offset within a cstring section.
+// \p r must point to an offset within a cstring section or ConcatInputSection
 static StringRef getReferentString(const Reloc &r) {
   if (auto *isec = r.referent.dyn_cast<InputSection *>())
     return cast<CStringInputSection>(isec)->getStringRefAtOffset(r.addend);
   auto *sym = cast<Defined>(r.referent.get<Symbol *>());
-  return cast<CStringInputSection>(sym->isec)->getStringRefAtOffset(sym->value +
-                                                                    r.addend);
+  uint32_t dataOff = sym->value + r.addend;
+  if (auto *cisec = dyn_cast<ConcatInputSection>(sym->isec)) {
+    uint32_t buffSize = cisec->data.size();
+    const char *pszBuff = reinterpret_cast<const char *>(cisec->data.data());
+    assert(dataOff < buffSize);
+    uint32_t sLen = strnlen(pszBuff + dataOff, buffSize - dataOff);
+    llvm::StringRef strRef(pszBuff + dataOff, sLen);
+    assert(strRef.size() > 0 && "getReferentString returning empty string");
+    return strRef;
+  }
+  return cast<CStringInputSection>(sym->isec)->getStringRefAtOffset(dataOff);
 }
 
 void ObjcCategoryChecker::parseMethods(const ConcatInputSection *methodsIsec,
@@ -311,6 +331,8 @@ void ObjcCategoryChecker::parseClass(const Defined *classSym) {
 }
 
 void objc::checkCategories() {
+  TimeTraceScope timeScope("ObjcCategoryChecker");
+
   ObjcCategoryChecker checker;
   for (const InputSection *isec : inputSections) {
     if (isec->getName() == section_names::objcCatList)
@@ -320,3 +342,1011 @@ void objc::checkCategories() {
       }
   }
 }
+
+namespace {
+
+class ObjcCategoryMerger {
+  // Information about an input category
+  struct InfoInputCategory {
+    ConcatInputSection *catBodyIsec;
+    ConcatInputSection *catListIsec;
+    uint32_t offCatListIsec = 0;
+
+    bool wasMerged = false;
+  };
+
+  // To write new (merged) categories or classes, we will try make limited
+  // assumptions about the alignment and the sections the various class/category
+  // info are stored in and . So we'll just reuse the same sections and
+  // alignment as already used in existing (input) categories. To do this we
+  // have InfoCategoryWriter which contains the various sections that the
+  // generated categories will be written to.
+  template <typename T> struct InfroWriteSection {
+    bool valid = false; // Data has been successfully collected from input
+    uint32_t align = 0;
+    const Section *inputSection;
+    Reloc relocTemplate;
+    T *outputSection;
+  };
+
+  struct InfoCategoryWriter {
+    InfroWriteSection<ConcatOutputSection> catListInfo;
+    InfroWriteSection<CStringSection> catNameInfo;
+    InfroWriteSection<ConcatOutputSection> catBodyInfo;
+    InfroWriteSection<ConcatOutputSection> catPtrListInfo;
+
+    // Linker-generated ObjFile for all the binary data that we will be
+    // generating (category body, method lists, strings, etc ...)
+    ObjFile *generatedDataObjFile = nullptr;
+  };
+
+  // Information about a pointer list in the original categories (method lists,
+  // protocol lists, etc)
+  struct PointerListInfo {
+    PointerListInfo(const char *pszSymNamePrefix)
+        : namePrefix(pszSymNamePrefix) {}
+    const char *namePrefix;
+
+    uint32_t structSize = 0;
+    uint32_t structCount = 0;
+
+    std::vector<Symbol *> allPtrs;
+  };
+
+  // Full information about all the categories that are extending a class. This
+  // will have all the additional methods, protocols, proprieties that are
+  // contained in all the categories that extend a particular class.
+  struct ClassExtensionInfo {
+    // Merged names of containers. Ex: base|firstCategory|secondCategory|...
+    std::string mergedContainerName;
+    std::string baseClassName;
+    Symbol *baseClass = nullptr;
+
+    PointerListInfo instanceMethods = "__OBJC_$_CATEGORY_INSTANCE_METHODS_";
+    PointerListInfo classMethods = "__OBJC_$_CATEGORY_CLASS_METHODS_";
+    PointerListInfo protocols = "__OBJC_CATEGORY_PROTOCOLS_$_";
+    PointerListInfo instanceProps = "__OBJC_$_PROP_LIST_";
+    PointerListInfo classProps = "__OBJC_$_CLASS_PROP_LIST_";
+  };
+
+public:
+  ObjcCategoryMerger(std::vector<ConcatInputSection *> &_allInputSections);
+  bool doMerge();
+
+private:
+  // This returns bool and always false for easy 'return false;' statements
+  bool registerError(const char *msg);
+
+  bool collectAndValidateCategoriesData();
+  bool
+  mergeCategoriesIntoSingleCategory(std::vector<InfoInputCategory> &categories);
+  bool eraseMergedCategories();
+
+  ObjFile *getGenObjFile();
+
+  bool generateCatListForNonErasedCategories(
+      std::map<ConcatInputSection *, std::set<uint64_t>> catListToErasedOffsets,
+      uint32_t remainingCategories);
+  template <typename T>
+  bool collectSectionWriteInfoFromIsec(InputSection *isec,
+                                       InfroWriteSection<T> &catWriteInfo);
+  bool collectCategoryWriterInfoFromCategory(InfoInputCategory &catInfo);
+  bool parseCatInfoToExtInfo(InfoInputCategory &catInfo,
+                             ClassExtensionInfo &extInfo);
+
+  bool tryParseProtocolListInfo(ConcatInputSection *isec,
+                                uint32_t symbolsPerStruct,
+                                PointerListInfo &ptrList);
+
+  bool parsePointerListInfo(ConcatInputSection *isec, uint32_t secOffset,
+                            uint32_t symbolsPerStruct,
+                            PointerListInfo &ptrList);
+
+  bool emitAndLinkPointerList(Defined *parentSym, uint32_t linkAtOffset,
+                              ClassExtensionInfo &extInfo,
+                              PointerListInfo &ptrList);
+
+  bool emitAndLinkProtocolList(Defined *parentSym, uint32_t linkAtOffset,
+                               ClassExtensionInfo &extInfo,
+                               PointerListInfo &ptrList);
+
+  bool emitCategory(ClassExtensionInfo &extInfo, Defined *&catBodySym);
+  bool emitCatListEntrySec(std::string &forCateogryName,
+                           std::string &forBaseClassName, Defined *&catListSym);
+  bool emitCategoryBody(std::string &name, Defined *nameSym,
+                        Symbol *baseClassSym, Defined *&catBodySym);
+  bool emitCategoryName(std::string &name, Defined *&catNameSym);
+  bool createSymbolReference(Defined *refFrom, Symbol *refTo, uint32_t offset,
+                             Reloc &relocTemplate);
+  bool tryGetSymbolAtIsecOffset(ConcatInputSection *isec, uint32_t offset,
+                                Symbol *&sym);
+  bool tryGetDefinedAtIsecOffset(ConcatInputSection *isec, uint32_t offset,
+                                 Defined *&defined);
+  bool tryEraseDefinedAtIsecOffset(ConcatInputSection *isec, uint32_t offset,
+                                   bool stringOnly = false);
+
+  CategoryLayout catLayout;
+  ClassLayout classLayout;
+  ROClassLayout roClassLayout;
+  ListHeaderLayout listHeaderLayout;
+  MethodLayout methodLayout;
+  ProtocolListHeaderLayout protocolListHeaderLayout;
+
+  InfoCategoryWriter infoCategoryWriter;
+  std::vector<ConcatInputSection *> &allInputSections;
+  // Map of base class Symbol to list of InfoInputCategory's for it
+  std::map<const Symbol *, std::vector<InfoInputCategory>> categoryMap;
+
+  // Normally, the binary data comes from the input files, but since we're
+  // generating binary data ourselves, we use the below arrays to store it in.
+  // Need this to be 'static' so the data survives past the ObjcCategoryMerger
+  // object, as the data will be read by the Writer when the final binary is
+  // generated.
+  static SmallVector<SmallString<0>> generatedNames;
+  static SmallVector<SmallVector<uint8_t>> generatedSectionData;
+};
+
+SmallVector<SmallString<0>> ObjcCategoryMerger::generatedNames;
+SmallVector<SmallVector<uint8_t>> ObjcCategoryMerger::generatedSectionData;
+
+ObjcCategoryMerger::ObjcCategoryMerger(
+    std::vector<ConcatInputSection *> &_allInputSections)
+    : catLayout(target->wordSize), classLayout(target->wordSize),
+      roClassLayout(target->wordSize), listHeaderLayout(target->wordSize),
+      methodLayout(target->wordSize),
+      protocolListHeaderLayout(target->wordSize),
+      allInputSections(_allInputSections) {}
+
+bool ObjcCategoryMerger::registerError(const char *msg) {
+  std::string err = "ObjC category merging error[-merge-objc-categories]: ";
+  err += msg;
+  error(err);
+  return false; // Always return false for easy 'return registerError()' syntax.
+}
+
+ObjFile *ObjcCategoryMerger::getGenObjFile() {
+  // Only create the linker-generated ObjFile on-demand - so if it's not needed
+  // (i.e. no categories are to be merged) then we don't need to create it.
+  if (infoCategoryWriter.generatedDataObjFile)
+    return infoCategoryWriter.generatedDataObjFile;
+
+  SmallString<32> objBuf;
+
+  infoCategoryWriter.generatedDataObjFile = make<ObjFile>(
+      MemoryBufferRef(objBuf, "<-merge-objc-categories>"), 0,
+      /*archiveName=*/"<Linker generated module for category merging>",
+      /*lazy=*/false, /*forceHidden=*/false, /*compatArch=*/false,
+      /*builtFromBitcode=*/false, /*isLinkerGenerated=*/true);
+
+  inputFiles.insert(infoCategoryWriter.generatedDataObjFile);
+
+  return infoCategoryWriter.generatedDataObjFile;
+}
+
+// This is a template so that it can be used both for CStringSection and
+// ConcatOutputSection
+template <typename T>
+bool ObjcCategoryMerger::collectSectionWriteInfoFromIsec(
+    InputSection *isec, InfroWriteSection<T> &catWriteInfo) {
+  if (catWriteInfo.valid)
+    return true;
+
+  catWriteInfo.inputSection = &isec->section;
+  catWriteInfo.align = isec->align;
+  catWriteInfo.outputSection = dyn_cast_or_null<T>(isec->parent);
+
+  if (isec->relocs.size())
+    catWriteInfo.relocTemplate = isec->relocs[0];
+
+  if (!catWriteInfo.outputSection) {
+    std::string message =
+        "Unexpected output section type for" + isec->getName().str();
+    return registerError(message.c_str());
+  }
+
+  catWriteInfo.valid = true;
+
+  return true;
+}
+
+bool ObjcCategoryMerger::tryGetSymbolAtIsecOffset(ConcatInputSection *isec,
+                                                  uint32_t offset,
+                                                  Symbol *&sym) {
+  const Reloc *reloc = isec->getRelocAt(offset);
+
+  if (!reloc)
+    return false;
+
+  sym = reloc->referent.get<Symbol *>();
+  return sym != nullptr;
+}
+
+bool ObjcCategoryMerger::tryGetDefinedAtIsecOffset(ConcatInputSection *isec,
+                                                   uint32_t offset,
+                                                   Defined *&defined) {
+  Symbol *sym;
+  if (!tryGetSymbolAtIsecOffset(isec, offset, sym))
+    return false;
+
+  defined = dyn_cast_or_null<Defined>(sym);
+  return defined != nullptr;
+}
+
+// Given an ConcatInputSection and an offset, if there is a symbol(Defined) at
+// that offset, then erase the symbol (mark it not live) from the final output.
+// Used for easely erasing already merged strings, method lists, etc ...
+bool ObjcCategoryMerger::tryEraseDefinedAtIsecOffset(ConcatInputSection *isec,
+                                                     uint32_t offset,
+                                                     bool stringOnly) {
+  const Reloc *reloc = isec->getRelocAt(offset);
+
+  if (!reloc)
+    return false;
+
+  Defined *sym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
+
+  if (!sym)
+    return false;
+
+  auto *cisec = dyn_cast_or_null<ConcatInputSection>(sym->isec);
+  if (!stringOnly && cisec) {
+    cisec->linkerOptimizeReason = LinkerOptReason::CategoryMerging;
+    return true;
+  }
+
+  if (auto *cisec = dyn_cast_or_null<CStringInputSection>(sym->isec)) {
+    uint32_t totalOffset = sym->value + reloc->addend;
+    StringPiece &piece = cisec->getStringPiece(totalOffset);
+    piece.linkerOptimizeReason = LinkerOptReason::CategoryMerging;
+    return true;
+  }
+
+  return false;
+}
+
+bool ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
+    InfoInputCategory &catInfo) {
+
+  if (!collectSectionWriteInfoFromIsec<ConcatOutputSection>(
+          catInfo.catListIsec, infoCategoryWriter.catListInfo))
+    return false;
+  if (!collectSectionWriteInfoFromIsec<ConcatOutputSection>(
+          catInfo.catBodyIsec, infoCategoryWriter.catBodyInfo))
+    return false;
+
+  if (!infoCategoryWriter.catNameInfo.valid) {
+    const Reloc *catNameReloc =
+        catInfo.catBodyIsec->getRelocAt(catLayout.nameOffset);
+
+    if (!catNameReloc)
+      return registerError("Category does not have a reloc at nameOffset");
+
+    lld::macho::Defined *catDefSym =
+        dyn_cast_or_null<Defined>(catNameReloc->referent.dyn_cast<Symbol *>());
+    if (!catDefSym)
+      return registerError(
+          "Reloc of category name is not a valid Defined symbol");
+
+    if (!collectSectionWriteInfoFromIsec<CStringSection>(
+            catDefSym->isec, infoCategoryWriter.catNameInfo))
+      return false;
+  }
+
+  // Collect writer info from all the category lists (we're assuming they all
+  // would provide the same info)
+  if (!infoCategoryWriter.catPtrListInfo.valid) {
+    for (uint32_t off = catLayout.instanceMethodsOffset;
+         off <= catLayout.classPropsOffset; off += target->wordSize) {
+      Defined *ptrList;
+      if (tryGetDefinedAtIsecOffset(catInfo.catBodyIsec, off, ptrList)) {
+        if (!collectSectionWriteInfoFromIsec<ConcatOutputSection>(
+                ptrList->isec, infoCategoryWriter.catPtrListInfo))
+          return false;
+        break;
+      }
+    }
+  }
+
+  return true;
+}
+
+// Parse a protocol list that might be linked to at a ConcatInputSection given
+// offset. The format of the protocol list is different than other lists (prop
+// lists, method lists) so we need to parse it differently
+bool ObjcCategoryMerger::tryParseProtocolListInfo(ConcatInputSection *isec,
+                                                  uint32_t secOffset,
+                                                  PointerListInfo &ptrList) {
+  if (!isec || (secOffset + target->wordSize > isec->data.size()))
+    return registerError(
+        "Tried to read pointer list beyond protocol section end");
+
+  const Reloc *reloc = isec->getRelocAt(secOffset);
+  if (!reloc)
+    return true; // List is null, return true because no m_error
+
+  auto *ptrListSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
+  if (!ptrListSym)
+    return registerError("Protocol list reloc does not have a valid Defined");
+
+  // Theoretically protocol count can be either 32b or 64b, but reading the
+  // first 32b is good enough
+  uint32_t protocolCount = *reinterpret_cast<const uint32_t *>(
+      ptrListSym->isec->data.data() + listHeaderLayout.structSizeOffset);
+
+  ptrList.structCount += protocolCount;
+  ptrList.structSize = target->wordSize;
+
+  uint32_t expectedListSize =
+      (protocolCount * target->wordSize) +
+      /*header(count)*/ protocolListHeaderLayout.totalSize +
+      /*extra null value*/ target->wordSize;
+  if (expectedListSize != ptrListSym->isec->data.size())
+    return registerError("Protocol list does not match expected size");
+
+  uint32_t off = protocolListHeaderLayout.totalSize;
+  for (uint32_t inx = 0; inx < protocolCount; inx++) {
+    const Reloc *reloc = ptrListSym->isec->getRelocAt(off);
+    if (!reloc)
+      return registerError("No reloc found at protocol list offset");
+
+    auto *listSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
+    if (!listSym)
+      return registerError("Protocol list reloc does not have a valid Defined");
+
+    ptrList.allPtrs.push_back(listSym);
+    off += target->wordSize;
+  }
+
+  return true;
+}
+
+// Parse a pointer list that might be linked to at a ConcatInputSection given
+// offset. This can be used for instance methods, class methods, instance props
+// and class props since they have the same format.
+bool ObjcCategoryMerger::parsePointerListInfo(ConcatInputSection *isec,
+                                              uint32_t secOffset,
+                                              uint32_t symbolsPerStruct,
+                                              PointerListInfo &ptrList) {
+  assert(symbolsPerStruct == 2 || symbolsPerStruct == 3);
+  if (!isec || (secOffset + target->wordSize > isec->data.size()))
+    return registerError("Tried to read pointer list beyond section end");
+
+  const Reloc *reloc = isec->getRelocAt(secOffset);
+  if (!reloc)
+    return true; // No reloc found, nothing to parse, so return success
+
+  auto *ptrListSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
+  if (!ptrListSym)
+    return registerError("Reloc does not have a valid Defined");
+
+  uint32_t thisStructSize = *reinterpret_cast<const uint32_t *>(
+      ptrListSym->isec->data.data() + listHeaderLayout.structSizeOffset);
+  uint32_t thisStructCount = *reinterpret_cast<const uint32_t *>(
+      ptrListSym->isec->data.data() + listHeaderLayout.structCountOffset);
+
+  assert(!ptrList.structSize || (thisStructSize == ptrList.structSize));
+
+  ptrList.structCount += thisStructCount;
+  ptrList.structSize = thisStructSize;
+
+  uint32_t expectedListSize =
+      listHeaderLayout.totalSize + (thisStructSize * thisStructCount);
+
+  if (expectedListSize != ptrListSym->isec->data.size())
+    return registerError("Pointer list does not match expected size");
+
+  for (uint32_t off = listHeaderLayout.totalSize; off < expectedListSize;
+       off += target->wordSize) {
+    const Reloc *reloc = ptrListSym->isec->getRelocAt(off);
+    if (!reloc)
+      return registerError("No reloc found at pointer list offset");
+
+    auto *listSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
+    if (!listSym)
+      return registerError("Reloc does not have a valid Defined");
+
+    ptrList.allPtrs.push_back(listSym);
+  }
+
+  return true;
+}
+
+// Here we parse all the information of an input category (catInfo) and
+// append-store the parsed info into the strucutre which will contain all the
+// information about how a class is extended (extInfo)
+bool ObjcCategoryMerger::parseCatInfoToExtInfo(InfoInputCategory &catInfo,
+                                               ClassExtensionInfo &extInfo) {
+  const Reloc *catNameReloc =
+      catInfo.catBodyIsec->getRelocAt(catLayout.nameOffset);
+
+  //// Parse name ///////////////////////////////////////////////////////////
+  if (!catNameReloc)
+    return registerError("Category does not have a reloc at 'nameOffset'");
+
+  if (!extInfo.mergedContainerName.empty())
+    extInfo.mergedContainerName += "|";
+
+  StringRef catName = getReferentString(*catNameReloc);
+  extInfo.mergedContainerName += catName.str();
+
+  //// Parse base class /////////////////////////////////////////////////////
+  const Reloc *klassReloc =
+      catInfo.catBodyIsec->getRelocAt(catLayout.klassOffset);
+
+  if (!klassReloc)
+    return registerError("Category does not have a reloc at 'klassOffset'");
+
+  Symbol *classSym = klassReloc->referent.get<Symbol *>();
+
+  if (extInfo.baseClass && extInfo.baseClass != classSym)
+    return registerError("Trying to parse category info into container with "
+                         "different base class");
+
+  extInfo.baseClass = classSym;
+
+  if (extInfo.baseClassName.empty()) {
+    llvm::StringRef classPrefix("_OBJC_CLASS_$_");
+    if (!classSym->getName().startswith(classPrefix))
+      return registerError(
+          "Base class symbol does not start with '_OBJC_CLASS_$_'");
+
+    extInfo.baseClassName = classSym->getName().substr(classPrefix.size());
+  }
+
+  if (!parsePointerListInfo(catInfo.catBodyIsec,
+                            catLayout.instanceMethodsOffset,
+                            /*symbolsPerStruct=*/3, extInfo.instanceMethods))
+    return false;
+
+  if (!parsePointerListInfo(catInfo.catBodyIsec, catLayout.classMethodsOffset,
+                            /*symbolsPerStruct=*/3, extInfo.classMethods))
+    return false;
+
+  if (!tryParseProtocolListInfo(catInfo.catBodyIsec, catLayout.protocolsOffset,
+                                extInfo.protocols))
+    return false;
+
+  if (!parsePointerListInfo(catInfo.catBodyIsec, catLayout.instancePropsOffset,
+                            /*symbolsPerStruct=*/2, extInfo.instanceProps))
+    return false;
+
+  if (!parsePointerListInfo(catInfo.catBodyIsec, catLayout.classPropsOffset,
+                            /*symbolsPerStruct=*/2, extInfo.classProps))
+    return false;
+
+  return true;
+}
+
+// Generate a protocol list (including header) and link it into the parent at
+// the specified offset.
+bool ObjcCategoryMerger::emitAndLinkProtocolList(Defined *parentSym,
+                                                 uint32_t linkAtOffset,
+                                                 ClassExtensionInfo &extInfo,
+                                                 PointerListInfo &ptrList) {
+  if (ptrList.allPtrs.empty())
+    return true;
+
+  assert(ptrList.allPtrs.size() == ptrList.structCount);
+
+  uint32_t bodySize = (ptrList.structCount * target->wordSize) +
+                      /*header(count)*/ protocolListHeaderLayout.totalSize +
+                      /*extra null value*/ target->wordSize;
+  generatedSectionData.push_back(SmallVector<uint8_t>(bodySize, 0));
+  llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+
+  // This theoretically can be either 32b or 64b, but writing just the first 32b
+  // is good enough
+  const uint32_t *ptrProtoCount = reinterpret_cast<const uint32_t *>(
+      bodyData.data() + protocolListHeaderLayout.protocolCountOffset);
+
+  *const_cast<uint32_t *>(ptrProtoCount) = ptrList.allPtrs.size();
+
+  ConcatInputSection *listSec = make<ConcatInputSection>(
+      *infoCategoryWriter.catPtrListInfo.inputSection, bodyData,
+      infoCategoryWriter.catPtrListInfo.align);
+  listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
+  allInputSections.push_back(listSec);
+
+  listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
+
+  generatedNames.push_back(StringRef(ptrList.namePrefix));
+  auto &symName = generatedNames.back();
+  symName += extInfo.baseClassName + "_$_(" + extInfo.mergedContainerName + ")";
+
+  Defined *ptrListSym = make<Defined>(
+      symName.c_str(), /*file=*/getGenObjFile(), listSec,
+      /*value=*/0, bodyData.size(),
+      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
+      /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
+      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+
+  getGenObjFile()->symbols.push_back(ptrListSym);
+
+  if (!createSymbolReference(parentSym, ptrListSym, linkAtOffset,
+                             infoCategoryWriter.catBodyInfo.relocTemplate))
+    return false;
+
+  uint32_t offset = protocolListHeaderLayout.totalSize;
+  for (Symbol *symbol : ptrList.allPtrs) {
+    if (!createSymbolReference(ptrListSym, symbol, offset,
+                               infoCategoryWriter.catPtrListInfo.relocTemplate))
+      return false;
+
+    offset += target->wordSize;
+  }
+
+  return true;
+}
+
+// Generate a pointer list (including header) and link it into the parent at the
+// specified offset. This is used for instance and class methods and
+// proprieties.
+bool ObjcCategoryMerger::emitAndLinkPointerList(Defined *parentSym,
+                                                uint32_t linkAtOffset,
+                                                ClassExtensionInfo &extInfo,
+                                                PointerListInfo &ptrList) {
+  if (ptrList.allPtrs.empty())
+    return true;
+
+  assert(ptrList.allPtrs.size() * target->wordSize ==
+         ptrList.structCount * ptrList.structSize);
+
+  // Generate body
+  uint32_t bodySize =
+      listHeaderLayout.totalSize + (ptrList.structSize * ptrList.structCount);
+  generatedSectionData.push_back(SmallVector<uint8_t>(bodySize, 0));
+  llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+
+  const uint32_t *ptrStructSize = reinterpret_cast<const uint32_t *>(
+      bodyData.data() + listHeaderLayout.structSizeOffset);
+  const uint32_t *ptrStructCount = reinterpret_cast<const uint32_t *>(
+      bodyData.data() + listHeaderLayout.structCountOffset);
+
+  *const_cast<uint32_t *>(ptrStructSize) = ptrList.structSize;
+  *const_cast<uint32_t *>(ptrStructCount) = ptrList.structCount;
+
+  ConcatInputSection *listSec = make<ConcatInputSection>(
+      *infoCategoryWriter.catPtrListInfo.inputSection, bodyData,
+      infoCategoryWriter.catPtrListInfo.align);
+  listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
+  allInputSections.push_back(listSec);
+
+  listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
+
+  generatedNames.push_back(StringRef(ptrList.namePrefix));
+  auto &symName = generatedNames.back();
+  symName += extInfo.baseClassName + "_$_" + extInfo.mergedContainerName;
+
+  Defined *ptrListSym = make<Defined>(
+      symName.c_str(), /*file=*/getGenObjFile(), listSec,
+      /*value=*/0, bodyData.size(),
+      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
+      /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
+      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+
+  getGenObjFile()->symbols.push_back(ptrListSym);
+
+  if (!createSymbolReference(parentSym, ptrListSym, linkAtOffset,
+                             infoCategoryWriter.catBodyInfo.relocTemplate))
+    return false;
+
+  uint32_t offset = listHeaderLayout.totalSize;
+  for (Symbol *symbol : ptrList.allPtrs) {
+    if (!createSymbolReference(ptrListSym, symbol, offset,
+                               infoCategoryWriter.catPtrListInfo.relocTemplate))
+      return false;
+
+    offset += target->wordSize;
+  }
+
+  return true;
+}
+
+// This method creates an __objc_catlist ConcatInputSection with a single slot
+bool ObjcCategoryMerger::emitCatListEntrySec(std::string &forCateogryName,
+                                             std::string &forBaseClassName,
+                                             Defined *&catListSym) {
+  uint32_t sectionSize = target->wordSize;
+  generatedSectionData.push_back(SmallVector<uint8_t>(sectionSize, 0));
+  llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+
+  ConcatInputSection *newCatList =
+      make<ConcatInputSection>(*infoCategoryWriter.catListInfo.inputSection,
+                               bodyData, infoCategoryWriter.catListInfo.align);
+  newCatList->parent = infoCategoryWriter.catListInfo.outputSection;
+  allInputSections.push_back(newCatList);
+
+  newCatList->parent = infoCategoryWriter.catListInfo.outputSection;
+
+  SmallString<0> catSymName;
+  catSymName += "<linker-generated __objc_catlist slot for category ";
+  catSymName += forBaseClassName + "(" + forCateogryName + ")";
+  generatedNames.push_back(StringRef(catSymName));
+
+  catListSym = make<Defined>(
+      StringRef(generatedNames.back()), /*file=*/getGenObjFile(), newCatList,
+      /*value=*/0, bodyData.size(), /*isWeakDef=*/false, /*isExternal=*/false,
+      /*isPrivateExtern=*/false, /*includeInSymtab=*/false,
+      /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
+      /*isWeakDefCanBeHidden=*/false);
+
+  getGenObjFile()->symbols.push_back(catListSym);
+  return true;
+}
+
+// Here we generate the main category body and just the body and link the name
+// and base class into it. We don't link any other info like the protocol and
+// class/instance methods/props.
+bool ObjcCategoryMerger::emitCategoryBody(std::string &name, Defined *nameSym,
+                                          Symbol *baseClassSym,
+                                          Defined *&catBodySym) {
+  generatedSectionData.push_back(SmallVector<uint8_t>(catLayout.totalSize, 0));
+  llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+
+  uint32_t *ptrSize = (uint32_t *)(const_cast<uint8_t *>(bodyData.data()) +
+                                   catLayout.sizeOffset);
+  *ptrSize = catLayout.totalSize;
+
+  ConcatInputSection *newBodySec =
+      make<ConcatInputSection>(*infoCategoryWriter.catBodyInfo.inputSection,
+                               bodyData, infoCategoryWriter.catBodyInfo.align);
+  newBodySec->parent = infoCategoryWriter.catBodyInfo.outputSection;
+  allInputSections.push_back(newBodySec);
+
+  newBodySec->parent = infoCategoryWriter.catBodyInfo.outputSection;
+
+  std::string symName = "__OBJC_$_CATEGORY_NSArray_$_(" + name + ")";
+  generatedNames.push_back(StringRef(symName));
+  catBodySym = make<Defined>(
+      StringRef(generatedNames.back()), /*file=*/getGenObjFile(), newBodySec,
+      /*value=*/0, bodyData.size(), /*isWeakDef=*/false, /*isExternal=*/false,
+      /*isPrivateExtern=*/false, /*includeInSymtab=*/true,
+      /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
+      /*isWeakDefCanBeHidden=*/false);
+
+  getGenObjFile()->symbols.push_back(catBodySym);
+
+  if (!createSymbolReference(catBodySym, nameSym, catLayout.nameOffset,
+                             infoCategoryWriter.catBodyInfo.relocTemplate))
+    return false;
+
+  // Create a reloc to the base class (either external or internal)
+  if (!createSymbolReference(catBodySym, baseClassSym, catLayout.klassOffset,
+                             infoCategoryWriter.catBodyInfo.relocTemplate))
+    return false;
+
+  return true;
+}
+
+// This writes the new category name (for the merged category) into the binary
+// and returns the sybmol for it.
+bool ObjcCategoryMerger::emitCategoryName(std::string &name,
+                                          Defined *&catNamdeSym) {
+  llvm::ArrayRef<uint8_t> inputNameArrData(
+      reinterpret_cast<const uint8_t *>(name.c_str()), name.size() + 1);
+  generatedSectionData.push_back(SmallVector<uint8_t>(inputNameArrData));
+
+  llvm::ArrayRef<uint8_t> nameData = generatedSectionData.back();
+
+  CStringInputSection *newStringSec = make<CStringInputSection>(
+      *infoCategoryWriter.catNameInfo.inputSection, nameData,
+      infoCategoryWriter.catNameInfo.align, true);
+
+  newStringSec->splitIntoPieces();
+  newStringSec->pieces[0].live = true;
+  newStringSec->parent = infoCategoryWriter.catNameInfo.outputSection;
+
+  catNamdeSym = make<Defined>(
+      "<merged category name>", /*file=*/getGenObjFile(), newStringSec,
+      /*value=*/0, nameData.size(),
+      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
+      /*includeInSymtab=*/false, /*isReferencedDynamically=*/false,
+      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+
+  getGenObjFile()->symbols.push_back(catNamdeSym);
+  return true;
+}
+
+// This method fully creates a new category from the given ClassExtensionInfo.
+// It creates the category body, name and protocol/method/prop lists an links
+// everything together. Then it creates a new __objc_catlist entry and links the
+// category into it. Calling this method will fully generate a category which
+// will be available in the final binary.
+bool ObjcCategoryMerger::emitCategory(ClassExtensionInfo &extInfo,
+                                      Defined *&catBodySym) {
+  Defined *catNameSym = nullptr;
+  if (!emitCategoryName(extInfo.mergedContainerName, catNameSym))
+    return false;
+
+  if (!emitCategoryBody(extInfo.mergedContainerName, catNameSym,
+                        extInfo.baseClass, catBodySym))
+    return false;
+
+  Defined *catListSym = nullptr;
+  if (!emitCatListEntrySec(extInfo.mergedContainerName, extInfo.baseClassName,
+                           catListSym))
+    return false;
+
+  const uint32_t offsetFirstCat = 0;
+  if (!createSymbolReference(catListSym, catBodySym, offsetFirstCat,
+                             infoCategoryWriter.catListInfo.relocTemplate))
+    return false;
+
+  if (!emitAndLinkPointerList(catBodySym, catLayout.instanceMethodsOffset,
+                              extInfo, extInfo.instanceMethods))
+    return false;
+
+  if (!emitAndLinkPointerList(catBodySym, catLayout.classMethodsOffset, extInfo,
+                              extInfo.classMethods))
+    return false;
+
+  if (!emitAndLinkProtocolList(catBodySym, catLayout.protocolsOffset, extInfo,
+                               extInfo.protocols))
+    return false;
+
+  if (!emitAndLinkPointerList(catBodySym, catLayout.instancePropsOffset,
+                              extInfo, extInfo.instanceProps))
+    return false;
+
+  if (!emitAndLinkPointerList(catBodySym, catLayout.classPropsOffset, extInfo,
+                              extInfo.classProps))
+    return false;
+
+  return true;
+}
+
+// This method merges all the categories (sharing a base class) into a single
+// category.
+bool ObjcCategoryMerger::mergeCategoriesIntoSingleCategory(
+    std::vector<InfoInputCategory> &categories) {
+  assert(categories.size() > 1 && "Expected at least 2 categories");
+
+  ClassExtensionInfo extInfo;
+
+  for (auto &catInfo : categories)
+    if (!parseCatInfoToExtInfo(catInfo, extInfo))
+      return false;
+
+  Defined *newCatDef = nullptr;
+  if (!emitCategory(extInfo, newCatDef))
+    return false;
+
+  return true;
+}
+
+bool ObjcCategoryMerger::createSymbolReference(Defined *refFrom, Symbol *refTo,
+                                               uint32_t offset,
+                                               Reloc &relocTemplate) {
+  Reloc r = relocTemplate;
+  r.offset = offset;
+  r.addend = 0;
+  r.referent = refTo;
+  refFrom->isec->relocs.push_back(r);
+
+  return true;
+}
+
+bool ObjcCategoryMerger::collectAndValidateCategoriesData() {
+  for (InputSection *sec : allInputSections) {
+    if (sec->getName() != section_names::objcCatList)
+      continue;
+    ConcatInputSection *catListCisec = dyn_cast<ConcatInputSection>(sec);
+    if (!catListCisec)
+      return registerError(
+          "__objc_catList InputSection is not a ConcatInputSection");
+
+    for (const Reloc &r : catListCisec->relocs) {
+      auto *sym = cast<Defined>(r.referent.get<Symbol *>());
+      if (!sym || !sym->getName().startswith("__OBJC_$_CATEGORY_"))
+        continue; // Only support ObjC categories (no swift + @objc)
+
+      auto *catBodyIsec =
+          dyn_cast<ConcatInputSection>(r.getReferentInputSection());
+      if (!catBodyIsec)
+        return registerError(
+            "Category data section is not an ConcatInputSection");
+
+      if (catBodyIsec->getSize() != catLayout.totalSize) {
+        std::string err;
+        llvm::raw_string_ostream OS(err);
+        OS << "Invalid input category size encountered, category merging only "
+              "supports "
+           << catLayout.totalSize << " bytes";
+        OS.flush();
+        return registerError(err.c_str());
+      }
+
+      // Check that the category has a reloc at 'klassOffset' (which is
+      // a pointer to the class symbol)
+
+      auto *classReloc = catBodyIsec->getRelocAt(catLayout.klassOffset);
+      if (!classReloc)
+        return registerError("Category does not have a reloc at klassOffset");
+
+      auto *classSym = classReloc->referent.get<Symbol *>();
+      InfoInputCategory catInputInfo{catBodyIsec, catListCisec, r.offset};
+      categoryMap[classSym].push_back(catInputInfo);
+
+      if (!collectCategoryWriterInfoFromCategory(catInputInfo))
+        return false;
+    }
+  }
+
+  for (auto &entry : categoryMap) {
+    if (entry.second.size() > 1) {
+      // Sort categories by offset to make sure we process categories in
+      // the same order as they appear in the input
+      auto cmpFn = [](const InfoInputCategory &a, const InfoInputCategory &b) {
+        return (a.catListIsec == b.catListIsec) &&
+               (a.offCatListIsec < b.offCatListIsec);
+      };
+
+      std::stable_sort(entry.second.begin(), entry.second.end(), cmpFn);
+    }
+  }
+
+  return true;
+}
+
+// In the input we have multiple __objc_catlist InputSection, each of which may
+// contain links to multiple categories. Of these categories, we will merge (and
+// erase) only some. There will be some categories that will remain unoutched
+// (not erased). For these not erased categories, we need to copy them over to a
+// new __objc_catlist InputSection. We do this because we will destroy the
+// original __objc_catlist's as they also contain links to the merged (and by
+// now erased) categories.
+bool ObjcCategoryMerger::generateCatListForNonErasedCategories(
+    std::map<ConcatInputSection *, std::set<uint64_t>> catListToErasedOffsets,
+    uint32_t remainingCategories) {
+  uint32_t bodySize = remainingCategories * target->wordSize;
+  generatedSectionData.push_back(SmallVector<uint8_t>(bodySize, 0));
+  llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+
+  ConcatInputSection *listSec =
+      make<ConcatInputSection>(*infoCategoryWriter.catListInfo.inputSection,
+                               bodyData, infoCategoryWriter.catListInfo.align);
+  listSec->parent = infoCategoryWriter.catListInfo.outputSection;
+  allInputSections.push_back(listSec);
+
+  Defined *mergedCatListSym = make<Defined>(
+      "<category merging __objc_catlist>", /*file=*/getGenObjFile(), listSec,
+      /*value=*/0, bodyData.size(),
+      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
+      /*includeInSymtab=*/false, /*isReferencedDynamically=*/false,
+      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+
+  getGenObjFile()->symbols.push_back(mergedCatListSym);
+
+  uint32_t outSecOffset = 0;
+  // Copy over all the un-merged categories
+  for (auto &mapEntry : catListToErasedOffsets) {
+    ConcatInputSection *catListIsec = mapEntry.first;
+    uint32_t catListIsecOffset = 0;
+    while (catListIsecOffset < catListIsec->data.size()) {
+      // This was erased, no need to copy it over
+      if (mapEntry.second.count(catListIsecOffset)) {
+        catListIsecOffset += target->wordSize;
+        continue;
+      }
+
+      Defined *nonErasedCat = nullptr;
+      if (!tryGetDefinedAtIsecOffset(catListIsec, catListIsecOffset,
+                                     nonErasedCat))
+        return registerError("Failed to relocate non-delted category");
+
+      if (!createSymbolReference(mergedCatListSym, nonErasedCat, outSecOffset,
+                                 infoCategoryWriter.catListInfo.relocTemplate))
+        return registerError(
+            "Failed to create symbol reference to non-deleted category");
+
+      catListIsecOffset += target->wordSize;
+      outSecOffset += target->wordSize;
+    }
+  }
+  assert(outSecOffset == bodyData.size() &&
+         "Unexpected mismatch between size/max offset of generated "
+         "__objc_catlist section");
+  return true;
+}
+
+// This fully erases the merged categories, including their body, their names,
+// their method/protocol/prop lists and the __objc_catlist entries that link to
+// them.
+bool ObjcCategoryMerger::eraseMergedCategories() {
+  // We expect there to be many categories in an input __objc_catList, so we
+  // can't just, of which we will merge only some. Because of this, we can't
+  // just erase the entire __objc_catList, we need to erase the merged
+  // categories only. To do this, we generate a new __objc_catList and copy over
+  // all the un-merged categories and erase all the affected (and only the
+  // affected) __objc_catList's
+
+  // Map of InputSection to a set of offsets of the categories that were merged
+  std::map<ConcatInputSection *, std::set<uint64_t>> catListToErasedOffsets;
+
+  for (auto &mapEntry : categoryMap) {
+    for (InfoInputCategory &catInfo : mapEntry.second) {
+      if (!catInfo.wasMerged) {
+        continue;
+      }
+      catInfo.catListIsec->linkerOptimizeReason =
+          LinkerOptReason::CategoryMerging;
+      catListToErasedOffsets[catInfo.catListIsec].insert(
+          catInfo.offCatListIsec);
+    }
+  }
+
+  // The number of categories that were not touched (need to keep them as-is)
+  uint32_t remainingCategories = 0;
+  for (auto &mapEntry : catListToErasedOffsets) {
+    // total categories - erased categories = remaining categories
+    remainingCategories += mapEntry.first->data.size() / target->wordSize;
+    remainingCategories -= mapEntry.second.size();
+  }
+
+  // If there were categories that we did not erase, we need to generate a new
+  // __objc_catList that contains only the un-merged categories, and get rid of
+  // the references to the ones we merged.
+  if (remainingCategories > 0)
+    if (!generateCatListForNonErasedCategories(catListToErasedOffsets,
+                                               remainingCategories))
+      return false;
+
+  // Erase the old method lists & names of the categories that were merged
+  for (auto &mapEntry : categoryMap) {
+    for (InfoInputCategory &catInfo : mapEntry.second) {
+      if (!catInfo.wasMerged)
+        continue;
+
+      catInfo.catBodyIsec->linkerOptimizeReason =
+          LinkerOptReason::CategoryMerging;
+      tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec, catLayout.nameOffset,
+                                  /*stringOnly=*/true);
+      tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec,
+                                  catLayout.instanceMethodsOffset);
+      tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec,
+                                  catLayout.classMethodsOffset);
+      tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec,
+                                  catLayout.protocolsOffset);
+      tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec,
+                                  catLayout.classPropsOffset);
+      tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec,
+                                  catLayout.instancePropsOffset);
+    }
+  }
+
+  return true;
+}
+
+bool ObjcCategoryMerger::doMerge() {
+  if (!collectAndValidateCategoriesData())
+    return false;
+
+  for (auto &entry : categoryMap) {
+    // Can't merge a single category into the base class just yet.
+    if (entry.second.size() <= 1)
+      continue;
+
+    // Merge all categories into a new, single category
+    if (!mergeCategoriesIntoSingleCategory(entry.second))
+      return false;
+
+    for (auto &catInfo : entry.second) {
+      catInfo.wasMerged = true;
+    }
+  }
+
+  // If we reach here, all categories in entry were merged, so mark them
+  if (!eraseMergedCategories())
+    return false;
+
+  return true;
+}
+
+} // namespace
+
+void objc::mergeCategories() {
+  TimeTraceScope timeScope("ObjcCategoryMerger");
+
+  ObjcCategoryMerger merger(inputSections);
+  merger.doMerge();
+}
diff --git a/lld/MachO/ObjC.h b/lld/MachO/ObjC.h
index 560c5cc0bc509c..8a88ecfbd32fe0 100644
--- a/lld/MachO/ObjC.h
+++ b/lld/MachO/ObjC.h
@@ -22,7 +22,7 @@ constexpr const char ivar[] = "_OBJC_IVAR_$_";
 
 // Check for duplicate method names within related categories / classes.
 void checkCategories();
-
+void mergeCategories();
 } // namespace objc
 
 bool hasObjCSection(llvm::MemoryBufferRef);
diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td
index 01e73b789f9aab..4d3e5046b11aa8 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -129,6 +129,9 @@ def strict_auto_link : Flag<["--"], "strict-auto-link">,
 def check_category_conflicts : Flag<["--"], "check-category-conflicts">,
     HelpText<"Check for conflicts between category & class methods">,
     Group<grp_lld>;
+def merge_objc_categories : Flag<["-"], "merge-objc-categories">,
+    HelpText<"Merge ObjC Categories">,
+    Group<grp_lld>;
 def lto_debug_pass_manager: Flag<["--"], "lto-debug-pass-manager">,
     HelpText<"Debug new pass manager">, Group<grp_lld>;
 def cs_profile_generate: Flag<["--"], "cs-profile-generate">,
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index a5d66bb4ea0801..1c7c0b8acc3e46 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -1626,7 +1626,7 @@ void CStringSection::addInput(CStringInputSection *isec) {
 void CStringSection::writeTo(uint8_t *buf) const {
   for (const CStringInputSection *isec : inputs) {
     for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
-      if (!piece.live)
+      if (piece.shouldOmitFromOutput())
         continue;
       StringRef string = isec->getStringRef(i);
       memcpy(buf + piece.outSecOff, string.data(), string.size());
@@ -1638,7 +1638,7 @@ void CStringSection::finalizeContents() {
   uint64_t offset = 0;
   for (CStringInputSection *isec : inputs) {
     for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
-      if (!piece.live)
+      if (piece.shouldOmitFromOutput())
         continue;
       // See comment above DeduplicatedCStringSection for how alignment is
       // handled.
@@ -1696,7 +1696,7 @@ void DeduplicatedCStringSection::finalizeContents() {
   // Find the largest alignment required for each string.
   for (const CStringInputSection *isec : inputs) {
     for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
-      if (!piece.live)
+      if (piece.shouldOmitFromOutput())
         continue;
       auto s = isec->getCachedHashStringRef(i);
       assert(isec->align != 0);
@@ -1712,7 +1712,7 @@ void DeduplicatedCStringSection::finalizeContents() {
   // StringPieces for easy access.
   for (CStringInputSection *isec : inputs) {
     for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
-      if (!piece.live)
+      if (piece.shouldOmitFromOutput())
         continue;
       auto s = isec->getCachedHashStringRef(i);
       auto it = stringOffsetMap.find(s);
diff --git a/lld/test/MachO/objc-category-merging.s b/lld/test/MachO/objc-category-merging.s
new file mode 100644
index 00000000000000..bf0589788de7aa
--- /dev/null
+++ b/lld/test/MachO/objc-category-merging.s
@@ -0,0 +1,695 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t; split-file %s %t && cd %t
+
+## Create a dylib to link against(a64_file1.dylib) and merge categories in the main binary (file2_merge_a64.exe)
+# RUN: llvm-mc -filetype=obj -triple=arm64-ios-simulator -o a64_file1.o a64_file1.s
+# RUN: ld64.lld a64_file1.o -o a64_file1.dylib -dylib -arch arm64 -platform_version ios-simulator 14.0 15.0
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-ios-simulator -o a64_file2.o a64_file2.s
+# RUN: ld64.lld -o a64_file2_no_merge.exe a64_file1.dylib a64_file2.o -arch arm64 -platform_version ios-simulator 14.0 15.0
+# RUN: ld64.lld -o a64_file2_merge.exe -merge-objc-categories a64_file1.dylib a64_file2.o -arch arm64 -platform_version ios-simulator 14.0 15.0
+
+# RUN: llvm-objdump --objc-meta-data --macho a64_file2_no_merge.exe | FileCheck %s --check-prefixes=NO_MERGE_CATS
+# RUN: llvm-objdump --objc-meta-data --macho a64_file2_merge.exe | FileCheck %s --check-prefixes=MERGE_CATS
+
+
+MERGE_CATS: __OBJC_$_CATEGORY_NSArray_$_(Category02|Category03)
+MERGE_CATS: instanceMethods
+MERGE_CATS-NEXT: 24
+MERGE_CATS-NEXT: 4
+MERGE_CATS: classMethods
+MERGE_CATS-NEXT: 24
+MERGE_CATS-NEXT: 4
+
+
+NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category02
+NO_MERGE_CATS: instanceMethods
+NO_MERGE_CATS-NEXT: 24
+NO_MERGE_CATS-NEXT: 2
+NO_MERGE_CATS: classMethods
+NO_MERGE_CATS-NEXT: 24
+NO_MERGE_CATS-NEXT: 2
+
+
+#--- a64_file1.s
+
+## @protocol MyProtocol01
+## - (void)myProtocol01Method;
+## @property (nonatomic) int MyProtocol01Prop;
+## @end
+##
+## __attribute__((objc_root_class))
+## @interface MyBaseClass<MyProtocol01>
+## - (void)baseInstanceMethod;
+## - (void)myProtocol01Method;
+## + (void)baseClassMethod;
+## @end
+##
+## @implementation MyBaseClass
+## @synthesize MyProtocol01Prop;
+## - (void)baseInstanceMethod {}
+## - (void)myProtocol01Method {}
+## + (void)baseClassMethod {}
+## @end
+##
+## void *_objc_empty_cache;
+
+	.section	__TEXT,__text,regular,pure_instructions
+	.ios_version_min 7, 0
+	.p2align	2                               ; -- Begin function -[MyBaseClass baseInstanceMethod]
+"-[MyBaseClass baseInstanceMethod]":    ; @"\01-[MyBaseClass baseInstanceMethod]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function -[MyBaseClass myProtocol01Method]
+"-[MyBaseClass myProtocol01Method]":    ; @"\01-[MyBaseClass myProtocol01Method]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function +[MyBaseClass baseClassMethod]
+"+[MyBaseClass baseClassMethod]":       ; @"\01+[MyBaseClass baseClassMethod]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function -[MyBaseClass MyProtocol01Prop]
+"-[MyBaseClass MyProtocol01Prop]":      ; @"\01-[MyBaseClass MyProtocol01Prop]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+Lloh0:
+	adrp	x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop at PAGE
+Lloh1:
+	ldrsw	x8, [x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop at PAGEOFF]
+	ldr	w0, [x0, x8]
+	ret
+	.loh AdrpLdr	Lloh0, Lloh1
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function -[MyBaseClass setMyProtocol01Prop:]
+"-[MyBaseClass setMyProtocol01Prop:]":  ; @"\01-[MyBaseClass setMyProtocol01Prop:]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+Lloh2:
+	adrp	x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop at PAGE
+Lloh3:
+	ldrsw	x8, [x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop at PAGEOFF]
+	str	w2, [x0, x8]
+	ret
+	.loh AdrpLdr	Lloh2, Lloh3
+	.cfi_endproc
+                                        ; -- End function
+	.private_extern	_OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop ; @"OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop"
+	.section	__DATA,__objc_ivar
+	.globl	_OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop
+	.p2align	2, 0x0
+_OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop:
+	.long	0                               ; 0x0
+	.section	__DATA,__objc_data
+	.globl	_OBJC_CLASS_$_MyBaseClass       ; @"OBJC_CLASS_$_MyBaseClass"
+	.p2align	3, 0x0
+_OBJC_CLASS_$_MyBaseClass:
+	.quad	_OBJC_METACLASS_$_MyBaseClass
+	.quad	0
+	.quad	__objc_empty_cache
+	.quad	0
+	.quad	__OBJC_CLASS_RO_$_MyBaseClass
+	.globl	_OBJC_METACLASS_$_MyBaseClass   ; @"OBJC_METACLASS_$_MyBaseClass"
+	.p2align	3, 0x0
+_OBJC_METACLASS_$_MyBaseClass:
+	.quad	_OBJC_METACLASS_$_MyBaseClass
+	.quad	_OBJC_CLASS_$_MyBaseClass
+	.quad	__objc_empty_cache
+	.quad	0
+	.quad	__OBJC_METACLASS_RO_$_MyBaseClass
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_:                     ; @OBJC_CLASS_NAME_
+	.asciz	"MyBaseClass"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_:                  ; @OBJC_METH_VAR_NAME_
+	.asciz	"baseClassMethod"
+	.section	__TEXT,__objc_methtype,cstring_literals
+l_OBJC_METH_VAR_TYPE_:                  ; @OBJC_METH_VAR_TYPE_
+	.asciz	"v16 at 0:8"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_CLASS_METHODS_MyBaseClass"
+__OBJC_$_CLASS_METHODS_MyBaseClass:
+	.long	24                              ; 0x18
+	.long	1                               ; 0x1
+	.quad	l_OBJC_METH_VAR_NAME_
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"+[MyBaseClass baseClassMethod]"
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_.1:                   ; @OBJC_CLASS_NAME_.1
+	.asciz	"MyProtocol01"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.2:                ; @OBJC_METH_VAR_NAME_.2
+	.asciz	"myProtocol01Method"
+l_OBJC_METH_VAR_NAME_.3:                ; @OBJC_METH_VAR_NAME_.3
+	.asciz	"MyProtocol01Prop"
+	.section	__TEXT,__objc_methtype,cstring_literals
+l_OBJC_METH_VAR_TYPE_.4:                ; @OBJC_METH_VAR_TYPE_.4
+	.asciz	"i16 at 0:8"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.5:                ; @OBJC_METH_VAR_NAME_.5
+	.asciz	"setMyProtocol01Prop:"
+	.section	__TEXT,__objc_methtype,cstring_literals
+l_OBJC_METH_VAR_TYPE_.6:                ; @OBJC_METH_VAR_TYPE_.6
+	.asciz	"v20 at 0:8i16"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol01"
+__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol01:
+	.long	24                              ; 0x18
+	.long	3                               ; 0x3
+	.quad	l_OBJC_METH_VAR_NAME_.2
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	0
+	.quad	l_OBJC_METH_VAR_NAME_.3
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.quad	0
+	.quad	l_OBJC_METH_VAR_NAME_.5
+	.quad	l_OBJC_METH_VAR_TYPE_.6
+	.quad	0
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_PROP_NAME_ATTR_:                 ; @OBJC_PROP_NAME_ATTR_
+	.asciz	"MyProtocol01Prop"
+l_OBJC_PROP_NAME_ATTR_.7:               ; @OBJC_PROP_NAME_ATTR_.7
+	.asciz	"Ti,N"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROP_LIST_MyProtocol01"
+__OBJC_$_PROP_LIST_MyProtocol01:
+	.long	16                              ; 0x10
+	.long	1                               ; 0x1
+	.quad	l_OBJC_PROP_NAME_ATTR_
+	.quad	l_OBJC_PROP_NAME_ATTR_.7
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol01"
+__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol01:
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.quad	l_OBJC_METH_VAR_TYPE_.6
+	.private_extern	__OBJC_PROTOCOL_$_MyProtocol01 ; @"_OBJC_PROTOCOL_$_MyProtocol01"
+	.section	__DATA,__data
+	.globl	__OBJC_PROTOCOL_$_MyProtocol01
+	.weak_definition	__OBJC_PROTOCOL_$_MyProtocol01
+	.p2align	3, 0x0
+__OBJC_PROTOCOL_$_MyProtocol01:
+	.quad	0
+	.quad	l_OBJC_CLASS_NAME_.1
+	.quad	0
+	.quad	__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol01
+	.quad	0
+	.quad	0
+	.quad	0
+	.quad	__OBJC_$_PROP_LIST_MyProtocol01
+	.long	96                              ; 0x60
+	.long	0                               ; 0x0
+	.quad	__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol01
+	.quad	0
+	.quad	0
+	.private_extern	__OBJC_LABEL_PROTOCOL_$_MyProtocol01 ; @"_OBJC_LABEL_PROTOCOL_$_MyProtocol01"
+	.section	__DATA,__objc_protolist,coalesced,no_dead_strip
+	.globl	__OBJC_LABEL_PROTOCOL_$_MyProtocol01
+	.weak_definition	__OBJC_LABEL_PROTOCOL_$_MyProtocol01
+	.p2align	3, 0x0
+__OBJC_LABEL_PROTOCOL_$_MyProtocol01:
+	.quad	__OBJC_PROTOCOL_$_MyProtocol01
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_CLASS_PROTOCOLS_$_MyBaseClass"
+__OBJC_CLASS_PROTOCOLS_$_MyBaseClass:
+	.quad	1                               ; 0x1
+	.quad	__OBJC_PROTOCOL_$_MyProtocol01
+	.quad	0
+	.p2align	3, 0x0                          ; @"_OBJC_METACLASS_RO_$_MyBaseClass"
+__OBJC_METACLASS_RO_$_MyBaseClass:
+	.long	3                               ; 0x3
+	.long	40                              ; 0x28
+	.long	40                              ; 0x28
+	.space	4
+	.quad	0
+	.quad	l_OBJC_CLASS_NAME_
+	.quad	__OBJC_$_CLASS_METHODS_MyBaseClass
+	.quad	__OBJC_CLASS_PROTOCOLS_$_MyBaseClass
+	.quad	0
+	.quad	0
+	.quad	0
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.8:                ; @OBJC_METH_VAR_NAME_.8
+	.asciz	"baseInstanceMethod"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_INSTANCE_METHODS_MyBaseClass"
+__OBJC_$_INSTANCE_METHODS_MyBaseClass:
+	.long	24                              ; 0x18
+	.long	4                               ; 0x4
+	.quad	l_OBJC_METH_VAR_NAME_.8
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"-[MyBaseClass baseInstanceMethod]"
+	.quad	l_OBJC_METH_VAR_NAME_.2
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"-[MyBaseClass myProtocol01Method]"
+	.quad	l_OBJC_METH_VAR_NAME_.3
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.quad	"-[MyBaseClass MyProtocol01Prop]"
+	.quad	l_OBJC_METH_VAR_NAME_.5
+	.quad	l_OBJC_METH_VAR_TYPE_.6
+	.quad	"-[MyBaseClass setMyProtocol01Prop:]"
+	.section	__TEXT,__objc_methtype,cstring_literals
+l_OBJC_METH_VAR_TYPE_.9:                ; @OBJC_METH_VAR_TYPE_.9
+	.asciz	"i"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_INSTANCE_VARIABLES_MyBaseClass"
+__OBJC_$_INSTANCE_VARIABLES_MyBaseClass:
+	.long	32                              ; 0x20
+	.long	1                               ; 0x1
+	.quad	_OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop
+	.quad	l_OBJC_METH_VAR_NAME_.3
+	.quad	l_OBJC_METH_VAR_TYPE_.9
+	.long	2                               ; 0x2
+	.long	4                               ; 0x4
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_PROP_NAME_ATTR_.10:              ; @OBJC_PROP_NAME_ATTR_.10
+	.asciz	"Ti,N,VMyProtocol01Prop"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROP_LIST_MyBaseClass"
+__OBJC_$_PROP_LIST_MyBaseClass:
+	.long	16                              ; 0x10
+	.long	1                               ; 0x1
+	.quad	l_OBJC_PROP_NAME_ATTR_
+	.quad	l_OBJC_PROP_NAME_ATTR_.10
+	.p2align	3, 0x0                          ; @"_OBJC_CLASS_RO_$_MyBaseClass"
+__OBJC_CLASS_RO_$_MyBaseClass:
+	.long	2                               ; 0x2
+	.long	0                               ; 0x0
+	.long	4                               ; 0x4
+	.space	4
+	.quad	0
+	.quad	l_OBJC_CLASS_NAME_
+	.quad	__OBJC_$_INSTANCE_METHODS_MyBaseClass
+	.quad	__OBJC_CLASS_PROTOCOLS_$_MyBaseClass
+	.quad	__OBJC_$_INSTANCE_VARIABLES_MyBaseClass
+	.quad	0
+	.quad	__OBJC_$_PROP_LIST_MyBaseClass
+	.globl	__objc_empty_cache              ; @_objc_empty_cache
+.zerofill __DATA,__common,__objc_empty_cache,8,3
+	.section	__DATA,__objc_classlist,regular,no_dead_strip
+	.p2align	3, 0x0                          ; @"OBJC_LABEL_CLASS_$"
+l_OBJC_LABEL_CLASS_$:
+	.quad	_OBJC_CLASS_$_MyBaseClass
+	.no_dead_strip	__OBJC_LABEL_PROTOCOL_$_MyProtocol01
+	.no_dead_strip	__OBJC_PROTOCOL_$_MyProtocol01
+	.section	__DATA,__objc_imageinfo,regular,no_dead_strip
+L_OBJC_IMAGE_INFO:
+	.long	0
+	.long	96
+.subsections_via_symbols
+
+
+#--- a64_file2.s
+
+## @protocol MyProtocol01
+## - (void)myProtocol01Method;
+## @end
+##
+## @protocol MyProtocol02
+## - (void)myProtocol02Method;
+## @property(readonly) int MyProtocol02Prop;
+## @end
+##
+## @protocol MyProtocol03
+## - (void)myProtocol03Method;
+## @property(readonly) int MyProtocol03Prop;
+## @end
+##
+##
+## __attribute__((objc_root_class))
+## @interface MyBaseClass<MyProtocol01>
+## - (void)baseInstanceMethod;
+## - (void)myProtocol01Method;
+## + (void)baseClassMethod;
+## @end
+##
+##
+##
+## @interface MyBaseClass(Category02)<MyProtocol02>
+## - (void)class02InstanceMethod;
+## - (void)myProtocol02Method;
+## + (void)class02ClassMethod;
+## + (int)MyProtocol02Prop;
+## @end
+##
+## @implementation MyBaseClass(Category02)
+## - (void)class02InstanceMethod {}
+## - (void)myProtocol02Method {}
+## + (void)class02ClassMethod {}
+## + (int)MyProtocol02Prop { return 0;}
+## @dynamic MyProtocol02Prop;
+## @end
+##
+## @interface MyBaseClass(Category03)<MyProtocol03>
+## - (void)class03InstanceMethod;
+## - (void)myProtocol03Method;
+## + (void)class03ClassMethod;
+## + (int)MyProtocol03Prop;
+## @end
+##
+## @implementation MyBaseClass(Category03)
+## - (void)class03InstanceMethod {}
+## - (void)myProtocol03Method {}
+## + (void)class03ClassMethod {}
+## + (int)MyProtocol03Prop { return 0;}
+## @dynamic MyProtocol03Prop;
+## @end
+##
+## int main() {
+##     return 0;
+## }
+
+
+	.section	__TEXT,__text,regular,pure_instructions
+	.ios_version_min 7, 0
+	.p2align	2                               ; -- Begin function -[MyBaseClass(Category02) class02InstanceMethod]
+"-[MyBaseClass(Category02) class02InstanceMethod]": ; @"\01-[MyBaseClass(Category02) class02InstanceMethod]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function -[MyBaseClass(Category02) myProtocol02Method]
+"-[MyBaseClass(Category02) myProtocol02Method]": ; @"\01-[MyBaseClass(Category02) myProtocol02Method]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function +[MyBaseClass(Category02) class02ClassMethod]
+"+[MyBaseClass(Category02) class02ClassMethod]": ; @"\01+[MyBaseClass(Category02) class02ClassMethod]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function +[MyBaseClass(Category02) MyProtocol02Prop]
+"+[MyBaseClass(Category02) MyProtocol02Prop]": ; @"\01+[MyBaseClass(Category02) MyProtocol02Prop]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	b	_OUTLINED_FUNCTION_0
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function -[MyBaseClass(Category03) class03InstanceMethod]
+"-[MyBaseClass(Category03) class03InstanceMethod]": ; @"\01-[MyBaseClass(Category03) class03InstanceMethod]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function -[MyBaseClass(Category03) myProtocol03Method]
+"-[MyBaseClass(Category03) myProtocol03Method]": ; @"\01-[MyBaseClass(Category03) myProtocol03Method]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function +[MyBaseClass(Category03) class03ClassMethod]
+"+[MyBaseClass(Category03) class03ClassMethod]": ; @"\01+[MyBaseClass(Category03) class03ClassMethod]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function +[MyBaseClass(Category03) MyProtocol03Prop]
+"+[MyBaseClass(Category03) MyProtocol03Prop]": ; @"\01+[MyBaseClass(Category03) MyProtocol03Prop]"
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	b	_OUTLINED_FUNCTION_0
+	.cfi_endproc
+                                        ; -- End function
+	.globl	_main                           ; -- Begin function main
+	.p2align	2
+_main:                                  ; @main
+	.cfi_startproc
+; %bb.0:                                ; %entry
+	b	_OUTLINED_FUNCTION_0
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function OUTLINED_FUNCTION_0
+_OUTLINED_FUNCTION_0:                   ; @OUTLINED_FUNCTION_0 Tail Call
+	.cfi_startproc
+; %bb.0:
+	mov	w0, #0
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_:                     ; @OBJC_CLASS_NAME_
+	.asciz	"Category02"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_:                  ; @OBJC_METH_VAR_NAME_
+	.asciz	"class02InstanceMethod"
+	.section	__TEXT,__objc_methtype,cstring_literals
+l_OBJC_METH_VAR_TYPE_:                  ; @OBJC_METH_VAR_TYPE_
+	.asciz	"v16 at 0:8"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.1:                ; @OBJC_METH_VAR_NAME_.1
+	.asciz	"myProtocol02Method"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02"
+__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02:
+	.long	24                              ; 0x18
+	.long	2                               ; 0x2
+	.quad	l_OBJC_METH_VAR_NAME_
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"-[MyBaseClass(Category02) class02InstanceMethod]"
+	.quad	l_OBJC_METH_VAR_NAME_.1
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"-[MyBaseClass(Category02) myProtocol02Method]"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.2:                ; @OBJC_METH_VAR_NAME_.2
+	.asciz	"class02ClassMethod"
+l_OBJC_METH_VAR_NAME_.3:                ; @OBJC_METH_VAR_NAME_.3
+	.asciz	"MyProtocol02Prop"
+	.section	__TEXT,__objc_methtype,cstring_literals
+l_OBJC_METH_VAR_TYPE_.4:                ; @OBJC_METH_VAR_TYPE_.4
+	.asciz	"i16 at 0:8"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category02"
+__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category02:
+	.long	24                              ; 0x18
+	.long	2                               ; 0x2
+	.quad	l_OBJC_METH_VAR_NAME_.2
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"+[MyBaseClass(Category02) class02ClassMethod]"
+	.quad	l_OBJC_METH_VAR_NAME_.3
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.quad	"+[MyBaseClass(Category02) MyProtocol02Prop]"
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_.5:                   ; @OBJC_CLASS_NAME_.5
+	.asciz	"MyProtocol02"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol02"
+__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol02:
+	.long	24                              ; 0x18
+	.long	2                               ; 0x2
+	.quad	l_OBJC_METH_VAR_NAME_.1
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	0
+	.quad	l_OBJC_METH_VAR_NAME_.3
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.quad	0
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_PROP_NAME_ATTR_:                 ; @OBJC_PROP_NAME_ATTR_
+	.asciz	"MyProtocol02Prop"
+l_OBJC_PROP_NAME_ATTR_.6:               ; @OBJC_PROP_NAME_ATTR_.6
+	.asciz	"Ti,R"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROP_LIST_MyProtocol02"
+__OBJC_$_PROP_LIST_MyProtocol02:
+	.long	16                              ; 0x10
+	.long	1                               ; 0x1
+	.quad	l_OBJC_PROP_NAME_ATTR_
+	.quad	l_OBJC_PROP_NAME_ATTR_.6
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol02"
+__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol02:
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.private_extern	__OBJC_PROTOCOL_$_MyProtocol02 ; @"_OBJC_PROTOCOL_$_MyProtocol02"
+	.section	__DATA,__data
+	.globl	__OBJC_PROTOCOL_$_MyProtocol02
+	.weak_definition	__OBJC_PROTOCOL_$_MyProtocol02
+	.p2align	3, 0x0
+__OBJC_PROTOCOL_$_MyProtocol02:
+	.quad	0
+	.quad	l_OBJC_CLASS_NAME_.5
+	.quad	0
+	.quad	__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol02
+	.quad	0
+	.quad	0
+	.quad	0
+	.quad	__OBJC_$_PROP_LIST_MyProtocol02
+	.long	96                              ; 0x60
+	.long	0                               ; 0x0
+	.quad	__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol02
+	.quad	0
+	.quad	0
+	.private_extern	__OBJC_LABEL_PROTOCOL_$_MyProtocol02 ; @"_OBJC_LABEL_PROTOCOL_$_MyProtocol02"
+	.section	__DATA,__objc_protolist,coalesced,no_dead_strip
+	.globl	__OBJC_LABEL_PROTOCOL_$_MyProtocol02
+	.weak_definition	__OBJC_LABEL_PROTOCOL_$_MyProtocol02
+	.p2align	3, 0x0
+__OBJC_LABEL_PROTOCOL_$_MyProtocol02:
+	.quad	__OBJC_PROTOCOL_$_MyProtocol02
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category02"
+__OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category02:
+	.quad	1                               ; 0x1
+	.quad	__OBJC_PROTOCOL_$_MyProtocol02
+	.quad	0
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_PROP_NAME_ATTR_.7:               ; @OBJC_PROP_NAME_ATTR_.7
+	.asciz	"Ti,R,D"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROP_LIST_MyBaseClass_$_Category02"
+__OBJC_$_PROP_LIST_MyBaseClass_$_Category02:
+	.long	16                              ; 0x10
+	.long	1                               ; 0x1
+	.quad	l_OBJC_PROP_NAME_ATTR_
+	.quad	l_OBJC_PROP_NAME_ATTR_.7
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_MyBaseClass_$_Category02"
+__OBJC_$_CATEGORY_MyBaseClass_$_Category02:
+	.quad	l_OBJC_CLASS_NAME_
+	.quad	_OBJC_CLASS_$_MyBaseClass
+	.quad	__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02
+	.quad	__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category02
+	.quad	__OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category02
+	.quad	__OBJC_$_PROP_LIST_MyBaseClass_$_Category02
+	.quad	0
+	.long	64                              ; 0x40
+	.space	4
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_.8:                   ; @OBJC_CLASS_NAME_.8
+	.asciz	"Category03"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.9:                ; @OBJC_METH_VAR_NAME_.9
+	.asciz	"class03InstanceMethod"
+l_OBJC_METH_VAR_NAME_.10:               ; @OBJC_METH_VAR_NAME_.10
+	.asciz	"myProtocol03Method"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category03"
+__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category03:
+	.long	24                              ; 0x18
+	.long	2                               ; 0x2
+	.quad	l_OBJC_METH_VAR_NAME_.9
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"-[MyBaseClass(Category03) class03InstanceMethod]"
+	.quad	l_OBJC_METH_VAR_NAME_.10
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"-[MyBaseClass(Category03) myProtocol03Method]"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.11:               ; @OBJC_METH_VAR_NAME_.11
+	.asciz	"class03ClassMethod"
+l_OBJC_METH_VAR_NAME_.12:               ; @OBJC_METH_VAR_NAME_.12
+	.asciz	"MyProtocol03Prop"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category03"
+__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category03:
+	.long	24                              ; 0x18
+	.long	2                               ; 0x2
+	.quad	l_OBJC_METH_VAR_NAME_.11
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"+[MyBaseClass(Category03) class03ClassMethod]"
+	.quad	l_OBJC_METH_VAR_NAME_.12
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.quad	"+[MyBaseClass(Category03) MyProtocol03Prop]"
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_.13:                  ; @OBJC_CLASS_NAME_.13
+	.asciz	"MyProtocol03"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol03"
+__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol03:
+	.long	24                              ; 0x18
+	.long	2                               ; 0x2
+	.quad	l_OBJC_METH_VAR_NAME_.10
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	0
+	.quad	l_OBJC_METH_VAR_NAME_.12
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.quad	0
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_PROP_NAME_ATTR_.14:              ; @OBJC_PROP_NAME_ATTR_.14
+	.asciz	"MyProtocol03Prop"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROP_LIST_MyProtocol03"
+__OBJC_$_PROP_LIST_MyProtocol03:
+	.long	16                              ; 0x10
+	.long	1                               ; 0x1
+	.quad	l_OBJC_PROP_NAME_ATTR_.14
+	.quad	l_OBJC_PROP_NAME_ATTR_.6
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol03"
+__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol03:
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	l_OBJC_METH_VAR_TYPE_.4
+	.private_extern	__OBJC_PROTOCOL_$_MyProtocol03 ; @"_OBJC_PROTOCOL_$_MyProtocol03"
+	.section	__DATA,__data
+	.globl	__OBJC_PROTOCOL_$_MyProtocol03
+	.weak_definition	__OBJC_PROTOCOL_$_MyProtocol03
+	.p2align	3, 0x0
+__OBJC_PROTOCOL_$_MyProtocol03:
+	.quad	0
+	.quad	l_OBJC_CLASS_NAME_.13
+	.quad	0
+	.quad	__OBJC_$_PROTOCOL_INSTANCE_METHODS_MyProtocol03
+	.quad	0
+	.quad	0
+	.quad	0
+	.quad	__OBJC_$_PROP_LIST_MyProtocol03
+	.long	96                              ; 0x60
+	.long	0                               ; 0x0
+	.quad	__OBJC_$_PROTOCOL_METHOD_TYPES_MyProtocol03
+	.quad	0
+	.quad	0
+	.private_extern	__OBJC_LABEL_PROTOCOL_$_MyProtocol03 ; @"_OBJC_LABEL_PROTOCOL_$_MyProtocol03"
+	.section	__DATA,__objc_protolist,coalesced,no_dead_strip
+	.globl	__OBJC_LABEL_PROTOCOL_$_MyProtocol03
+	.weak_definition	__OBJC_LABEL_PROTOCOL_$_MyProtocol03
+	.p2align	3, 0x0
+__OBJC_LABEL_PROTOCOL_$_MyProtocol03:
+	.quad	__OBJC_PROTOCOL_$_MyProtocol03
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category03"
+__OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category03:
+	.quad	1                               ; 0x1
+	.quad	__OBJC_PROTOCOL_$_MyProtocol03
+	.quad	0
+	.p2align	3, 0x0                          ; @"_OBJC_$_PROP_LIST_MyBaseClass_$_Category03"
+__OBJC_$_PROP_LIST_MyBaseClass_$_Category03:
+	.long	16                              ; 0x10
+	.long	1                               ; 0x1
+	.quad	l_OBJC_PROP_NAME_ATTR_.14
+	.quad	l_OBJC_PROP_NAME_ATTR_.7
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_MyBaseClass_$_Category03"
+__OBJC_$_CATEGORY_MyBaseClass_$_Category03:
+	.quad	l_OBJC_CLASS_NAME_.8
+	.quad	_OBJC_CLASS_$_MyBaseClass
+	.quad	__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category03
+	.quad	__OBJC_$_CATEGORY_CLASS_METHODS_MyBaseClass_$_Category03
+	.quad	__OBJC_CATEGORY_PROTOCOLS_$_MyBaseClass_$_Category03
+	.quad	__OBJC_$_PROP_LIST_MyBaseClass_$_Category03
+	.quad	0
+	.long	64                              ; 0x40
+	.space	4
+	.section	__DATA,__objc_catlist,regular,no_dead_strip
+	.p2align	3, 0x0                          ; @"OBJC_LABEL_CATEGORY_$"
+l_OBJC_LABEL_CATEGORY_$:
+	.quad	__OBJC_$_CATEGORY_MyBaseClass_$_Category02
+	.quad	__OBJC_$_CATEGORY_MyBaseClass_$_Category03
+	.no_dead_strip	__OBJC_LABEL_PROTOCOL_$_MyProtocol02
+	.no_dead_strip	__OBJC_LABEL_PROTOCOL_$_MyProtocol03
+	.no_dead_strip	__OBJC_PROTOCOL_$_MyProtocol02
+	.no_dead_strip	__OBJC_PROTOCOL_$_MyProtocol03
+	.section	__DATA,__objc_imageinfo,regular,no_dead_strip
+L_OBJC_IMAGE_INFO:
+	.long	0
+	.long	96
+.subsections_via_symbols



More information about the llvm-commits mailing list