[lld] [llvm] [lld-macho] Implement ObjC category merging (-objc_category_merging) (PR #82928)

via llvm-commits llvm-commits at lists.llvm.org
Sun Mar 10 13:15:48 PDT 2024


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

>From 310cd45bf33ac2f1a89d022679da30ff931deacf Mon Sep 17 00:00:00 2001
From: Alex B <alexborcan at meta.com>
Date: Wed, 28 Feb 2024 12:48:49 -0800
Subject: [PATCH 1/5] [lld-macho] Implement ObjC category merging
 (-objc_category_merging)

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:

There is a possibility to further 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.
We do the merging as early as possible, on the raw inputSections.
We add a new flag for ObjFile (isLinkerGenerated) and create such an ObjFile to which all new linker-generated date belongs.
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                          |    5 +
 lld/MachO/InputSection.h                      |   34 +-
 lld/MachO/MapFile.cpp                         |    6 +-
 lld/MachO/MarkLive.cpp                        |    2 +
 lld/MachO/ObjC.cpp                            | 1038 ++++++++++++++++-
 lld/MachO/ObjC.h                              |    2 +-
 lld/MachO/Options.td                          |    6 +-
 lld/MachO/SyntheticSections.cpp               |    8 +-
 .../objc-category-merging-complete-test.s     |  695 +++++++++++
 ...jc-category-merging-extern-class-minimal.s |  146 +++
 10 files changed, 1925 insertions(+), 17 deletions(-)
 create mode 100644 lld/test/MachO/objc-category-merging-complete-test.s
 create mode 100644 lld/test/MachO/objc-category-merging-extern-class-minimal.s

diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 9edb6b9c60a1fe..dad9e0dc032ff2 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1979,9 +1979,14 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     if (config->deadStrip)
       markLive();
 
+    // Categories are not subject to dead-strip. The __objc_catlist section is
+    // marked as NO_DEAD_STRIP and that propagates into all category data.
     if (args.hasArg(OPT_check_category_conflicts))
       objc::checkCategories();
 
+    if (args.hasArg(OPT_objc_category_merging, OPT_no_objc_category_merging))
+      objc::mergeCategories();
+
     // ICF assumes that all literals have been folded already, so we must run
     // foldIdenticalLiterals before foldIdenticalSections.
     foldIdenticalLiterals();
diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index becb01017d633a..c4a63c05850479 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,20 @@ 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 : 48;
+  // Have to declare the 'linkerOptimizeReason' and 'live' as uint64_t so that
+  // the MSVC compiler will merge the storage of it and 'outSecOff' above.
+  uint64_t /*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..35e0c300194f18 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,1007 @@ 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;
+  };
+
+  // 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;
+    // In case we generate new data, mark the new data as belonging to this file
+    ObjFile *objFileForMergeData = 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();
+
+  bool generateCatListForNonErasedCategories(
+      std::map<ConcatInputSection *, std::set<uint64_t>>
+          catListToErasedOffsets);
+  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, ObjFile *objFile,
+                           Defined *&catListSym);
+  bool emitCategoryBody(std::string &name, Defined *nameSym,
+                        Symbol *baseClassSym, std::string &baseClassName,
+                        ObjFile *objFile, Defined *&catBodySym);
+  bool emitCategoryName(std::string &name, ObjFile *objFile,
+                        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.
+}
+
+// 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 += "|";
+
+  if (!extInfo.objFileForMergeData)
+    extInfo.objFileForMergeData =
+        dyn_cast_or_null<ObjFile>(catInfo.catBodyIsec->getFile());
+
+  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().starts_with(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;
+  listSec->live = true;
+  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=*/parentSym->getObjectFile(), listSec,
+      /*value=*/0, bodyData.size(),
+      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
+      /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
+      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+
+  ptrListSym->used = true;
+  parentSym->getObjectFile()->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;
+  listSec->live = true;
+  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=*/parentSym->getObjectFile(), listSec,
+      /*value=*/0, bodyData.size(),
+      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
+      /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
+      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+
+  ptrListSym->used = true;
+  parentSym->getObjectFile()->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,
+                                             ObjFile *objFile,
+                                             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;
+  newCatList->live = true;
+  allInputSections.push_back(newCatList);
+
+  newCatList->parent = infoCategoryWriter.catListInfo.outputSection;
+
+  SmallString<0> catSymName;
+  catSymName += "<__objc_catlist slot for merged category ";
+  catSymName += forBaseClassName + "(" + forCateogryName + ")>";
+  generatedNames.push_back(StringRef(catSymName));
+
+  catListSym = make<Defined>(
+      StringRef(generatedNames.back()), /*file=*/objFile, newCatList,
+      /*value=*/0, bodyData.size(), /*isWeakDef=*/false, /*isExternal=*/false,
+      /*isPrivateExtern=*/false, /*includeInSymtab=*/false,
+      /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
+      /*isWeakDefCanBeHidden=*/false);
+
+  catListSym->used = true;
+  objFile->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,
+                                          std::string &baseClassName,
+                                          ObjFile *objFile,
+                                          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;
+  newBodySec->live = true;
+  allInputSections.push_back(newBodySec);
+
+  newBodySec->parent = infoCategoryWriter.catBodyInfo.outputSection;
+
+  std::string symName =
+      "__OBJC_$_CATEGORY_" + baseClassName + "_$_(" + name + ")";
+  generatedNames.push_back(StringRef(symName));
+  catBodySym = make<Defined>(
+      StringRef(generatedNames.back()), /*file=*/objFile, newBodySec,
+      /*value=*/0, bodyData.size(), /*isWeakDef=*/false, /*isExternal=*/false,
+      /*isPrivateExtern=*/false, /*includeInSymtab=*/true,
+      /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
+      /*isWeakDefCanBeHidden=*/false);
+
+  catBodySym->used = true;
+  objFile->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, ObjFile *objFile,
+                                          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=*/objFile, newStringSec,
+      /*value=*/0, nameData.size(),
+      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
+      /*includeInSymtab=*/false, /*isReferencedDynamically=*/false,
+      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+
+  catNamdeSym->used = true;
+  objFile->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,
+                        extInfo.objFileForMergeData, catNameSym))
+    return false;
+
+  if (!emitCategoryBody(extInfo.mergedContainerName, catNameSym,
+                        extInfo.baseClass, extInfo.baseClassName,
+                        extInfo.objFileForMergeData, catBodySym))
+    return false;
+
+  Defined *catListSym = nullptr;
+  if (!emitCatListEntrySec(extInfo.mergedContainerName, extInfo.baseClassName,
+                           extInfo.objFileForMergeData, 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().starts_with("__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);
+      };
+
+      llvm::sort(entry.second, 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 generate new __objc_catlist
+// entries since the parent __objc_catlist entry will be erased
+bool ObjcCategoryMerger::generateCatListForNonErasedCategories(
+    std::map<ConcatInputSection *, std::set<uint64_t>> catListToErasedOffsets) {
+
+  // Go through all offsets of all __objc_catlist's that we process and if there
+  // are categories that we didn't process - generate a new __objv_catlist for
+  // each.
+  for (auto &mapEntry : catListToErasedOffsets) {
+    ConcatInputSection *catListIsec = mapEntry.first;
+    uint32_t catListIsecOffset = 0;
+    while (catListIsecOffset < catListIsec->data.size()) {
+      // This slot was erased, we can jsut skip it
+      if (mapEntry.second.count(catListIsecOffset)) {
+        catListIsecOffset += target->wordSize;
+        continue;
+      }
+
+      Defined *nonErasedCatBody = nullptr;
+      if (!tryGetDefinedAtIsecOffset(catListIsec, catListIsecOffset,
+                                     nonErasedCatBody))
+        return registerError("Failed to relocate non-deleted category");
+
+      // Allocate data for the new __objc_catlist slot
+      generatedSectionData.push_back(SmallVector<uint8_t>(target->wordSize, 0));
+      llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+
+      // We mark the __objc_catlist slot as belonging to the same file as the
+      // category
+      ObjFile *objFile = dyn_cast<ObjFile>(nonErasedCatBody->getFile());
+
+      ConcatInputSection *listSec = make<ConcatInputSection>(
+          *infoCategoryWriter.catListInfo.inputSection, bodyData,
+          infoCategoryWriter.catListInfo.align);
+      listSec->parent = infoCategoryWriter.catListInfo.outputSection;
+      listSec->live = true;
+      allInputSections.push_back(listSec);
+
+      generatedNames.push_back(StringRef("<__objc_catlist slot for category "));
+      auto &slotSymName = generatedNames.back();
+      slotSymName += nonErasedCatBody->getName();
+      slotSymName += ">";
+
+      Defined *catListSlotSym = make<Defined>(
+          slotSymName.c_str(), /*file=*/objFile, listSec,
+          /*value=*/0, bodyData.size(),
+          /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
+          /*includeInSymtab=*/false, /*isReferencedDynamically=*/false,
+          /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+
+      catListSlotSym->used = true;
+      objFile->symbols.push_back(catListSlotSym);
+
+      // Now link the category body into the newly created slot
+      if (!createSymbolReference(catListSlotSym, nonErasedCatBody, 0,
+                                 infoCategoryWriter.catListInfo.relocTemplate))
+        return registerError(
+            "Failed to create symbol reference to non-deleted category");
+
+      catListIsecOffset += target->wordSize;
+    }
+  }
+  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);
+    }
+  }
+
+  // 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 (!generateCatListForNonErasedCategories(catListToErasedOffsets))
+    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 4c65f9a1f78812..f4a1cc5cb43c2e 100644
--- a/lld/MachO/ObjC.h
+++ b/lld/MachO/ObjC.h
@@ -24,7 +24,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 a524e4a4c50841..d6eb0441386e05 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -966,8 +966,12 @@ def interposable_list : Separate<["-"], "interposable_list">,
 def no_function_starts : Flag<["-"], "no_function_starts">,
     HelpText<"Do not create table of function start addresses">,
     Group<grp_rare>;
+def objc_category_merging : Flag<["-"], "objc_category_merging">,
+    HelpText<"Merge Objective-C categories that share the same base class">,
+    Flags<[HelpHidden]>,
+    Group<grp_rare>;
 def no_objc_category_merging : Flag<["-"], "no_objc_category_merging">,
-    HelpText<"Do not merge Objective-C categories into their classes">,
+    HelpText<"Do not merge Objective-C categories">,
     Flags<[HelpHidden]>,
     Group<grp_rare>;
 def object_path_lto : Separate<["-"], "object_path_lto">,
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-complete-test.s b/lld/test/MachO/objc-category-merging-complete-test.s
new file mode 100644
index 00000000000000..db807972baa840
--- /dev/null
+++ b/lld/test/MachO/objc-category-merging-complete-test.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 -objc_category_merging 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_MyBaseClass_$_(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
diff --git a/lld/test/MachO/objc-category-merging-extern-class-minimal.s b/lld/test/MachO/objc-category-merging-extern-class-minimal.s
new file mode 100644
index 00000000000000..1add19350c642b
--- /dev/null
+++ b/lld/test/MachO/objc-category-merging-extern-class-minimal.s
@@ -0,0 +1,146 @@
+# REQUIRES: aarch64
+# RUN: rm -rf %t; split-file %s %t && cd %t
+
+## Create a dylib with a fake base class to link against
+# RUN: llvm-mc -filetype=obj -triple=arm64-ios-simulator -o a64_fakedylib.o a64_fakedylib.s
+# RUN: ld64.lld a64_fakedylib.o -o a64_fakedylib.dylib -dylib -arch arm64 -platform_version ios-simulator 14.0 15.0
+
+## Create our main testing dylib - linking against the fake dylib above
+# RUN: llvm-mc -filetype=obj -triple=arm64-ios-simulator -o merge_cat_minimal.o merge_cat_minimal.s
+# RUN: ld64.lld -dylib -o merge_cat_minimal_no_merge.dylib a64_fakedylib.dylib merge_cat_minimal.o -arch arm64 -platform_version ios-simulator 14.0 15.0
+# RUN: ld64.lld -dylib -o merge_cat_minimal_merge.dylib -objc_category_merging a64_fakedylib.dylib merge_cat_minimal.o -arch arm64 -platform_version ios-simulator 14.0 15.0
+
+## Now verify that the flag caused category merging to happen appropriatelly
+# RUN: llvm-objdump --objc-meta-data --macho merge_cat_minimal_no_merge.dylib | FileCheck %s --check-prefixes=NO_MERGE_CATS
+# RUN: llvm-objdump --objc-meta-data --macho merge_cat_minimal_merge.dylib | FileCheck %s --check-prefixes=MERGE_CATS
+
+#### Check merge categories enabled ###
+# Check that the original categories are not there
+MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category01
+MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category02
+
+# Check that the merged cateogry is there, in the correct format
+MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_(Category01|Category02)
+MERGE_CATS: instanceMethods
+MERGE_CATS-NEXT: 24
+MERGE_CATS-NEXT: 2
+
+#### Check merge categories disabled ###
+# Check that the merged category is not there
+NO_MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_(Category01|Category02)
+
+# Check that the original categories are there
+NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category01
+NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category02
+
+
+
+#--- a64_fakedylib.s
+
+    .section    __DATA,__objc_data
+    .globl    _OBJC_CLASS_$_MyBaseClass
+_OBJC_CLASS_$_MyBaseClass:
+    .quad    0
+
+#--- merge_cat_minimal.s
+
+;  ================== Generated from ObjC: ==================
+; __attribute__((objc_root_class))
+; @interface MyBaseClass
+; - (void)baseInstanceMethod;
+; @end
+;
+; @interface MyBaseClass(Category01)
+; - (void)cat01_InstanceMethod;
+; @end
+;
+; @implementation MyBaseClass(Category01)
+; - (void)cat01_InstanceMethod {}
+; @end
+;
+; @interface MyBaseClass(Category02)
+; - (void)cat02_InstanceMethod;
+; @end
+;
+; @implementation MyBaseClass(Category02)
+; - (void)cat02_InstanceMethod {}
+; @end
+;  ================== Generated from ObjC: ==================
+
+	.section	__TEXT,__text,regular,pure_instructions
+	.ios_version_min 7, 0
+	.p2align	2                               ; -- Begin function -[MyBaseClass(Category01) cat01_InstanceMethod]
+"-[MyBaseClass(Category01) cat01_InstanceMethod]": ; @"\01-[MyBaseClass(Category01) cat01_InstanceMethod]"
+	.cfi_startproc
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.p2align	2                               ; -- Begin function -[MyBaseClass(Category02) cat02_InstanceMethod]
+"-[MyBaseClass(Category02) cat02_InstanceMethod]": ; @"\01-[MyBaseClass(Category02) cat02_InstanceMethod]"
+	.cfi_startproc
+	ret
+	.cfi_endproc
+                                        ; -- End function
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_:                     ; @OBJC_CLASS_NAME_
+	.asciz	"Category01"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_:                  ; @OBJC_METH_VAR_NAME_
+	.asciz	"cat01_InstanceMethod"
+	.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_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category01"
+__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category01:
+	.long	24                              ; 0x18
+	.long	1                               ; 0x1
+	.quad	l_OBJC_METH_VAR_NAME_
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"-[MyBaseClass(Category01) cat01_InstanceMethod]"
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_MyBaseClass_$_Category01"
+__OBJC_$_CATEGORY_MyBaseClass_$_Category01:
+	.quad	l_OBJC_CLASS_NAME_
+	.quad	_OBJC_CLASS_$_MyBaseClass
+	.quad	__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category01
+	.quad	0
+	.quad	0
+	.quad	0
+	.quad	0
+	.long	64                              ; 0x40
+	.space	4
+	.section	__TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_.1:                   ; @OBJC_CLASS_NAME_.1
+	.asciz	"Category02"
+	.section	__TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_.2:                ; @OBJC_METH_VAR_NAME_.2
+	.asciz	"cat02_InstanceMethod"
+	.section	__DATA,__objc_const
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02"
+__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02:
+	.long	24                              ; 0x18
+	.long	1                               ; 0x1
+	.quad	l_OBJC_METH_VAR_NAME_.2
+	.quad	l_OBJC_METH_VAR_TYPE_
+	.quad	"-[MyBaseClass(Category02) cat02_InstanceMethod]"
+	.p2align	3, 0x0                          ; @"_OBJC_$_CATEGORY_MyBaseClass_$_Category02"
+__OBJC_$_CATEGORY_MyBaseClass_$_Category02:
+	.quad	l_OBJC_CLASS_NAME_.1
+	.quad	_OBJC_CLASS_$_MyBaseClass
+	.quad	__OBJC_$_CATEGORY_INSTANCE_METHODS_MyBaseClass_$_Category02
+	.quad	0
+	.quad	0
+	.quad	0
+	.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_$_Category01
+	.quad	__OBJC_$_CATEGORY_MyBaseClass_$_Category02
+	.section	__DATA,__objc_imageinfo,regular,no_dead_strip
+L_OBJC_IMAGE_INFO:
+	.long	0
+	.long	96
+.subsections_via_symbols

>From e5d7137bf70a49fad196a9be56e8fd742839303e Mon Sep 17 00:00:00 2001
From: Alex B <alexborcan at meta.com>
Date: Fri, 1 Mar 2024 08:44:10 -0800
Subject: [PATCH 2/5] [lld-macho] Address Feedback #1

---
 lld/MachO/Driver.cpp                          |   4 +
 lld/MachO/InputSection.h                      |  32 +-
 lld/MachO/MapFile.cpp                         |   6 +-
 lld/MachO/MarkLive.cpp                        |   2 -
 lld/MachO/ObjC.cpp                            | 499 +++++++-----------
 lld/MachO/ObjC.h                              |  12 +
 lld/MachO/Options.td                          |  14 +-
 lld/MachO/SyntheticSections.cpp               |   8 +-
 .../objc-category-merging-complete-test.s     |  82 ++-
 ...jc-category-merging-extern-class-minimal.s |   9 +
 llvm/lib/Target/AMDGPU/SIInstrInfo.h          |   2 -
 11 files changed, 314 insertions(+), 356 deletions(-)

diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index dad9e0dc032ff2..36248925d65ad2 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1437,6 +1437,8 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     resetOutputSegments();
     resetWriter();
     InputFile::resetIdCount();
+
+    objc::doCleanup();
   };
 
   ctx->e.logName = args::getFilenameWithoutExe(argsArr[0]);
@@ -1984,6 +1986,8 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     if (args.hasArg(OPT_check_category_conflicts))
       objc::checkCategories();
 
+    // Category merging uses "->live = false" to erase old category data, so
+    // it has to run after dead-stripping (markLive).
     if (args.hasArg(OPT_objc_category_merging, OPT_no_objc_category_merging))
       objc::mergeCategories();
 
diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index c4a63c05850479..b25f0638f4c6cb 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -24,11 +24,6 @@
 namespace lld {
 namespace macho {
 
-enum LinkerOptReason : uint8_t {
-  NotOptimized,
-  CategoryMerging,
-};
-
 class InputFile;
 class OutputSection;
 
@@ -65,7 +60,6 @@ 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; }
 
@@ -120,12 +114,7 @@ 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 isLinkOptimizedAway() const override {
-    return linkerOptimizeReason != LinkerOptReason::NotOptimized;
-  }
-  bool shouldOmitFromOutput() const {
-    return isLinkOptimizedAway() || !live || isCoalescedWeak();
-  }
+  bool shouldOmitFromOutput() const { return !live || isCoalescedWeak(); }
   void writeTo(uint8_t *buf);
 
   void foldIdentical(ConcatInputSection *redundant);
@@ -152,11 +141,6 @@ 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
@@ -192,20 +176,10 @@ struct StringPiece {
   // Only set if deduplicating literals
   uint32_t hash : 31;
   // Offset from the start of the containing output section.
-  uint64_t outSecOff : 48;
-  // Have to declare the 'linkerOptimizeReason' and 'live' as uint64_t so that
-  // the MSVC compiler will merge the storage of it and 'outSecOff' above.
-  uint64_t /*LinkerOptReason*/ linkerOptimizeReason : 8;
-
-  bool shouldOmitFromOutput() const {
-    return !live || linkerOptimizeReason != LinkerOptReason::NotOptimized;
-  }
+  uint64_t outSecOff = 0;
 
   StringPiece(uint64_t off, uint32_t hash)
-      : inSecOff(off), live(!config->deadStrip), hash(hash) {
-    outSecOff = 0;
-    linkerOptimizeReason = LinkerOptReason::NotOptimized;
-  }
+      : inSecOff(off), live(!config->deadStrip), hash(hash) {}
 };
 
 static_assert(sizeof(StringPiece) == 16, "StringPiece is too big!");
diff --git a/lld/MachO/MapFile.cpp b/lld/MachO/MapFile.cpp
index 455ebffbbd567a..f736360624ebd1 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() && (!d->isec || !d->isec->isLinkOptimizedAway()))
+            if (!d->isLive())
               info.deadSymbols.push_back(d);
           }
       }
@@ -93,8 +93,6 @@ 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)}});
@@ -205,8 +203,6 @@ 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 5eb189574dec1e..a37213d5613afb 100644
--- a/lld/MachO/MarkLive.cpp
+++ b/lld/MachO/MarkLive.cpp
@@ -259,8 +259,6 @@ 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 35e0c300194f18..29245019bcdde3 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -82,8 +82,7 @@ namespace {
   DO(Ptr, protocols)                                                           \
   DO(Ptr, instanceProps)                                                       \
   DO(Ptr, classProps)                                                          \
-  DO(uint32_t, size)                                                           \
-  DO(uint32_t, padding)
+  DO(uint32_t, size)
 
 CREATE_LAYOUT_CLASS(Category, FOR_EACH_CATEGORY_FIELD);
 
@@ -187,22 +186,13 @@ ObjcCategoryChecker::ObjcCategoryChecker()
       roClassLayout(target->wordSize), listHeaderLayout(target->wordSize),
       methodLayout(target->wordSize) {}
 
-// \p r must point to an offset within a cstring section or ConcatInputSection
+// \p r must point to an offset within a cstring section.
 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 *>());
-  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);
+  return cast<CStringInputSection>(sym->isec)->getStringRefAtOffset(sym->value +
+                                                                    r.addend);
 }
 
 void ObjcCategoryChecker::parseMethods(const ConcatInputSection *methodsIsec,
@@ -364,15 +354,15 @@ class ObjcCategoryMerger {
   template <typename T> struct InfroWriteSection {
     bool valid = false; // Data has been successfully collected from input
     uint32_t align = 0;
-    const Section *inputSection;
+    Section *inputSection;
     Reloc relocTemplate;
     T *outputSection;
   };
 
   struct InfoCategoryWriter {
     InfroWriteSection<ConcatOutputSection> catListInfo;
-    InfroWriteSection<CStringSection> catNameInfo;
     InfroWriteSection<ConcatOutputSection> catBodyInfo;
+    InfroWriteSection<CStringSection> catNameInfo;
     InfroWriteSection<ConcatOutputSection> catPtrListInfo;
   };
 
@@ -400,68 +390,69 @@ class ObjcCategoryMerger {
     // In case we generate new data, mark the new data as belonging to this file
     ObjFile *objFileForMergeData = 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_";
+    PointerListInfo instanceMethods =
+        objc::symbol_names::categoryInstanceMethods;
+    PointerListInfo classMethods = objc::symbol_names::categoryClassMethods;
+    PointerListInfo protocols = objc::symbol_names::categoryProtocols;
+    PointerListInfo instanceProps = objc::symbol_names::listProprieties;
+    PointerListInfo classProps = objc::symbol_names::klassPropList;
   };
 
 public:
   ObjcCategoryMerger(std::vector<ConcatInputSection *> &_allInputSections);
-  bool doMerge();
+  void doMerge();
+  static void doCleanup();
 
 private:
-  // This returns bool and always false for easy 'return false;' statements
-  bool registerError(const char *msg);
-
-  bool collectAndValidateCategoriesData();
-  bool
+  void collectAndValidateCategoriesData();
+  void
   mergeCategoriesIntoSingleCategory(std::vector<InfoInputCategory> &categories);
-  bool eraseMergedCategories();
 
-  bool generateCatListForNonErasedCategories(
+  void eraseISec(ConcatInputSection *isec);
+  void eraseMergedCategories();
+
+  void generateCatListForNonErasedCategories(
       std::map<ConcatInputSection *, std::set<uint64_t>>
           catListToErasedOffsets);
   template <typename T>
-  bool collectSectionWriteInfoFromIsec(InputSection *isec,
+  void collectSectionWriteInfoFromIsec(InputSection *isec,
                                        InfroWriteSection<T> &catWriteInfo);
-  bool collectCategoryWriterInfoFromCategory(InfoInputCategory &catInfo);
-  bool parseCatInfoToExtInfo(InfoInputCategory &catInfo,
+  void collectCategoryWriterInfoFromCategory(InfoInputCategory &catInfo);
+  void parseCatInfoToExtInfo(InfoInputCategory &catInfo,
                              ClassExtensionInfo &extInfo);
 
-  bool tryParseProtocolListInfo(ConcatInputSection *isec,
-                                uint32_t symbolsPerStruct,
-                                PointerListInfo &ptrList);
+  void parseProtocolListInfo(ConcatInputSection *isec,
+                             uint32_t symbolsPerStruct,
+                             PointerListInfo &ptrList);
 
-  bool parsePointerListInfo(ConcatInputSection *isec, uint32_t secOffset,
+  void parsePointerListInfo(ConcatInputSection *isec, uint32_t secOffset,
                             uint32_t symbolsPerStruct,
                             PointerListInfo &ptrList);
 
-  bool emitAndLinkPointerList(Defined *parentSym, uint32_t linkAtOffset,
+  void emitAndLinkPointerList(Defined *parentSym, uint32_t linkAtOffset,
                               ClassExtensionInfo &extInfo,
                               PointerListInfo &ptrList);
 
-  bool emitAndLinkProtocolList(Defined *parentSym, uint32_t linkAtOffset,
+  void emitAndLinkProtocolList(Defined *parentSym, uint32_t linkAtOffset,
                                ClassExtensionInfo &extInfo,
                                PointerListInfo &ptrList);
 
-  bool emitCategory(ClassExtensionInfo &extInfo, Defined *&catBodySym);
-  bool emitCatListEntrySec(std::string &forCateogryName,
+  void emitCategory(ClassExtensionInfo &extInfo, Defined *&catBodySym);
+  void emitCatListEntrySec(std::string &forCateogryName,
                            std::string &forBaseClassName, ObjFile *objFile,
                            Defined *&catListSym);
-  bool emitCategoryBody(std::string &name, Defined *nameSym,
+  void emitCategoryBody(std::string &name, Defined *nameSym,
                         Symbol *baseClassSym, std::string &baseClassName,
                         ObjFile *objFile, Defined *&catBodySym);
-  bool emitCategoryName(std::string &name, ObjFile *objFile,
+  void emitCategoryName(std::string &name, ObjFile *objFile,
                         Defined *&catNameSym);
-  bool createSymbolReference(Defined *refFrom, Symbol *refTo, uint32_t offset,
+  void 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,
+  void tryEraseDefinedAtIsecOffset(ConcatInputSection *isec, uint32_t offset,
                                    bool stringOnly = false);
 
   CategoryLayout catLayout;
@@ -496,37 +487,23 @@ ObjcCategoryMerger::ObjcCategoryMerger(
       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.
-}
-
 // This is a template so that it can be used both for CStringSection and
 // ConcatOutputSection
 template <typename T>
-bool ObjcCategoryMerger::collectSectionWriteInfoFromIsec(
+void ObjcCategoryMerger::collectSectionWriteInfoFromIsec(
     InputSection *isec, InfroWriteSection<T> &catWriteInfo) {
-  if (catWriteInfo.valid)
-    return true;
 
-  catWriteInfo.inputSection = &isec->section;
+  catWriteInfo.inputSection = const_cast<Section *>(&isec->section);
   catWriteInfo.align = isec->align;
   catWriteInfo.outputSection = dyn_cast_or_null<T>(isec->parent);
 
+  assert(catWriteInfo.outputSection &&
+         "outputSection may not be null in collectSectionWriteInfoFromIsec.");
+
   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,
@@ -555,61 +532,52 @@ bool ObjcCategoryMerger::tryGetDefinedAtIsecOffset(ConcatInputSection *isec,
 // 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,
+void ObjcCategoryMerger::tryEraseDefinedAtIsecOffset(ConcatInputSection *isec,
                                                      uint32_t offset,
                                                      bool stringOnly) {
   const Reloc *reloc = isec->getRelocAt(offset);
 
   if (!reloc)
-    return false;
+    return;
 
   Defined *sym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
-
   if (!sym)
-    return false;
+    return;
 
   auto *cisec = dyn_cast_or_null<ConcatInputSection>(sym->isec);
   if (!stringOnly && cisec) {
-    cisec->linkerOptimizeReason = LinkerOptReason::CategoryMerging;
-    return true;
+    eraseISec(cisec);
+    return;
   }
 
   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;
+    piece.live = false;
+    return;
   }
-
-  return false;
 }
 
-bool ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
+void ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
     InfoInputCategory &catInfo) {
 
-  if (!collectSectionWriteInfoFromIsec<ConcatOutputSection>(
-          catInfo.catListIsec, infoCategoryWriter.catListInfo))
-    return false;
-  if (!collectSectionWriteInfoFromIsec<ConcatOutputSection>(
-          catInfo.catBodyIsec, infoCategoryWriter.catBodyInfo))
-    return false;
+  collectSectionWriteInfoFromIsec<ConcatOutputSection>(
+      catInfo.catListIsec, infoCategoryWriter.catListInfo);
+  collectSectionWriteInfoFromIsec<ConcatOutputSection>(
+      catInfo.catBodyIsec, infoCategoryWriter.catBodyInfo);
 
   if (!infoCategoryWriter.catNameInfo.valid) {
     const Reloc *catNameReloc =
         catInfo.catBodyIsec->getRelocAt(catLayout.nameOffset);
 
-    if (!catNameReloc)
-      return registerError("Category does not have a reloc at nameOffset");
+    assert(catNameReloc && "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");
+    assert(catDefSym && "Reloc of category name is not a valid Defined symbol");
 
-    if (!collectSectionWriteInfoFromIsec<CStringSection>(
-            catDefSym->isec, infoCategoryWriter.catNameInfo))
-      return false;
+    collectSectionWriteInfoFromIsec<CStringSection>(
+        catDefSym->isec, infoCategoryWriter.catNameInfo);
   }
 
   // Collect writer info from all the category lists (we're assuming they all
@@ -619,34 +587,30 @@ bool ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
          off <= catLayout.classPropsOffset; off += target->wordSize) {
       Defined *ptrList;
       if (tryGetDefinedAtIsecOffset(catInfo.catBodyIsec, off, ptrList)) {
-        if (!collectSectionWriteInfoFromIsec<ConcatOutputSection>(
-                ptrList->isec, infoCategoryWriter.catPtrListInfo))
-          return false;
+        collectSectionWriteInfoFromIsec<ConcatOutputSection>(
+            ptrList->isec, infoCategoryWriter.catPtrListInfo);
+        // we've successfully collected data, so we can break
         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) {
+void ObjcCategoryMerger::parseProtocolListInfo(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");
+    assert("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
+    return; // List is null, nothing to do
 
   auto *ptrListSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
-  if (!ptrListSym)
-    return registerError("Protocol list reloc does not have a valid Defined");
+  assert(ptrListSym && "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
@@ -660,44 +624,40 @@ bool ObjcCategoryMerger::tryParseProtocolListInfo(ConcatInputSection *isec,
       (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");
+  assert(expectedListSize == ptrListSym->isec->data.size() &&
+         "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");
+    assert(reloc && "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");
+    assert(listSym && "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,
+void 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");
+  assert(isec && "Trying to parse pointer list from null isec");
+  assert(secOffset + target->wordSize <= isec->data.size() &&
+         "Trying 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
+    return; // No reloc found, nothing to parse
 
   auto *ptrListSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
-  if (!ptrListSym)
-    return registerError("Reloc does not have a valid Defined");
+  assert(ptrListSym && "Reloc does not have a valid Defined");
 
   uint32_t thisStructSize = *reinterpret_cast<const uint32_t *>(
       ptrListSym->isec->data.data() + listHeaderLayout.structSizeOffset);
@@ -712,36 +672,31 @@ bool ObjcCategoryMerger::parsePointerListInfo(ConcatInputSection *isec,
   uint32_t expectedListSize =
       listHeaderLayout.totalSize + (thisStructSize * thisStructCount);
 
-  if (expectedListSize != ptrListSym->isec->data.size())
-    return registerError("Pointer list does not match expected size");
+  assert(expectedListSize == ptrListSym->isec->data.size() &&
+         "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");
+    assert(reloc && "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");
+    assert(listSym && "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,
+void 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'");
+  // Parse name
+  assert(catNameReloc && "Category does not have a reloc at 'nameOffset'");
 
   if (!extInfo.mergedContainerName.empty())
     extInfo.mergedContainerName += "|";
@@ -753,62 +708,52 @@ bool ObjcCategoryMerger::parseCatInfoToExtInfo(InfoInputCategory &catInfo,
   StringRef catName = getReferentString(*catNameReloc);
   extInfo.mergedContainerName += catName.str();
 
-  //// Parse base class /////////////////////////////////////////////////////
+  // Parse base class
   const Reloc *klassReloc =
       catInfo.catBodyIsec->getRelocAt(catLayout.klassOffset);
 
-  if (!klassReloc)
-    return registerError("Category does not have a reloc at 'klassOffset'");
+  assert(klassReloc && "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");
+  assert(
+      (!extInfo.baseClass || (extInfo.baseClass == classSym)) &&
+      "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().starts_with(classPrefix))
-      return registerError(
-          "Base class symbol does not start with '_OBJC_CLASS_$_'");
+    llvm::StringRef classPrefix(objc::symbol_names::klass);
+    assert(classSym->getName().starts_with(classPrefix) &&
+           "Base class symbol does not start with expected prefix");
 
     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;
+  parsePointerListInfo(catInfo.catBodyIsec, catLayout.instanceMethodsOffset,
+                       /*symbolsPerStruct=*/3, extInfo.instanceMethods);
 
-  if (!tryParseProtocolListInfo(catInfo.catBodyIsec, catLayout.protocolsOffset,
-                                extInfo.protocols))
-    return false;
+  parsePointerListInfo(catInfo.catBodyIsec, catLayout.classMethodsOffset,
+                       /*symbolsPerStruct=*/3, extInfo.classMethods);
 
-  if (!parsePointerListInfo(catInfo.catBodyIsec, catLayout.instancePropsOffset,
-                            /*symbolsPerStruct=*/2, extInfo.instanceProps))
-    return false;
+  parseProtocolListInfo(catInfo.catBodyIsec, catLayout.protocolsOffset,
+                        extInfo.protocols);
 
-  if (!parsePointerListInfo(catInfo.catBodyIsec, catLayout.classPropsOffset,
-                            /*symbolsPerStruct=*/2, extInfo.classProps))
-    return false;
+  parsePointerListInfo(catInfo.catBodyIsec, catLayout.instancePropsOffset,
+                       /*symbolsPerStruct=*/2, extInfo.instanceProps);
 
-  return true;
+  parsePointerListInfo(catInfo.catBodyIsec, catLayout.classPropsOffset,
+                       /*symbolsPerStruct=*/2, extInfo.classProps);
 }
 
 // Generate a protocol list (including header) and link it into the parent at
 // the specified offset.
-bool ObjcCategoryMerger::emitAndLinkProtocolList(Defined *parentSym,
+void ObjcCategoryMerger::emitAndLinkProtocolList(Defined *parentSym,
                                                  uint32_t linkAtOffset,
                                                  ClassExtensionInfo &extInfo,
                                                  PointerListInfo &ptrList) {
   if (ptrList.allPtrs.empty())
-    return true;
+    return;
 
   assert(ptrList.allPtrs.size() == ptrList.structCount);
 
@@ -848,31 +793,26 @@ bool ObjcCategoryMerger::emitAndLinkProtocolList(Defined *parentSym,
   ptrListSym->used = true;
   parentSym->getObjectFile()->symbols.push_back(ptrListSym);
 
-  if (!createSymbolReference(parentSym, ptrListSym, linkAtOffset,
-                             infoCategoryWriter.catBodyInfo.relocTemplate))
-    return false;
+  createSymbolReference(parentSym, ptrListSym, linkAtOffset,
+                        infoCategoryWriter.catBodyInfo.relocTemplate);
 
   uint32_t offset = protocolListHeaderLayout.totalSize;
   for (Symbol *symbol : ptrList.allPtrs) {
-    if (!createSymbolReference(ptrListSym, symbol, offset,
-                               infoCategoryWriter.catPtrListInfo.relocTemplate))
-      return false;
-
+    createSymbolReference(ptrListSym, symbol, offset,
+                          infoCategoryWriter.catPtrListInfo.relocTemplate);
     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,
+void ObjcCategoryMerger::emitAndLinkPointerList(Defined *parentSym,
                                                 uint32_t linkAtOffset,
                                                 ClassExtensionInfo &extInfo,
                                                 PointerListInfo &ptrList) {
   if (ptrList.allPtrs.empty())
-    return true;
+    return;
 
   assert(ptrList.allPtrs.size() * target->wordSize ==
          ptrList.structCount * ptrList.structSize);
@@ -914,24 +854,19 @@ bool ObjcCategoryMerger::emitAndLinkPointerList(Defined *parentSym,
   ptrListSym->used = true;
   parentSym->getObjectFile()->symbols.push_back(ptrListSym);
 
-  if (!createSymbolReference(parentSym, ptrListSym, linkAtOffset,
-                             infoCategoryWriter.catBodyInfo.relocTemplate))
-    return false;
+  createSymbolReference(parentSym, ptrListSym, linkAtOffset,
+                        infoCategoryWriter.catBodyInfo.relocTemplate);
 
   uint32_t offset = listHeaderLayout.totalSize;
   for (Symbol *symbol : ptrList.allPtrs) {
-    if (!createSymbolReference(ptrListSym, symbol, offset,
-                               infoCategoryWriter.catPtrListInfo.relocTemplate))
-      return false;
-
+    createSymbolReference(ptrListSym, symbol, offset,
+                          infoCategoryWriter.catPtrListInfo.relocTemplate);
     offset += target->wordSize;
   }
-
-  return true;
 }
 
 // This method creates an __objc_catlist ConcatInputSection with a single slot
-bool ObjcCategoryMerger::emitCatListEntrySec(std::string &forCateogryName,
+void ObjcCategoryMerger::emitCatListEntrySec(std::string &forCateogryName,
                                              std::string &forBaseClassName,
                                              ObjFile *objFile,
                                              Defined *&catListSym) {
@@ -962,13 +897,12 @@ bool ObjcCategoryMerger::emitCatListEntrySec(std::string &forCateogryName,
 
   catListSym->used = true;
   objFile->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,
+void ObjcCategoryMerger::emitCategoryBody(std::string &name, Defined *nameSym,
                                           Symbol *baseClassSym,
                                           std::string &baseClassName,
                                           ObjFile *objFile,
@@ -990,7 +924,7 @@ bool ObjcCategoryMerger::emitCategoryBody(std::string &name, Defined *nameSym,
   newBodySec->parent = infoCategoryWriter.catBodyInfo.outputSection;
 
   std::string symName =
-      "__OBJC_$_CATEGORY_" + baseClassName + "_$_(" + name + ")";
+      objc::symbol_names::category + baseClassName + "_$_(" + name + ")";
   generatedNames.push_back(StringRef(symName));
   catBodySym = make<Defined>(
       StringRef(generatedNames.back()), /*file=*/objFile, newBodySec,
@@ -1002,21 +936,17 @@ bool ObjcCategoryMerger::emitCategoryBody(std::string &name, Defined *nameSym,
   catBodySym->used = true;
   objFile->symbols.push_back(catBodySym);
 
-  if (!createSymbolReference(catBodySym, nameSym, catLayout.nameOffset,
-                             infoCategoryWriter.catBodyInfo.relocTemplate))
-    return false;
+  createSymbolReference(catBodySym, nameSym, catLayout.nameOffset,
+                        infoCategoryWriter.catBodyInfo.relocTemplate);
 
   // Create a reloc to the base class (either external or internal)
-  if (!createSymbolReference(catBodySym, baseClassSym, catLayout.klassOffset,
-                             infoCategoryWriter.catBodyInfo.relocTemplate))
-    return false;
-
-  return true;
+  createSymbolReference(catBodySym, baseClassSym, catLayout.klassOffset,
+                        infoCategoryWriter.catBodyInfo.relocTemplate);
 }
 
 // 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, ObjFile *objFile,
+void ObjcCategoryMerger::emitCategoryName(std::string &name, ObjFile *objFile,
                                           Defined *&catNamdeSym) {
   llvm::ArrayRef<uint8_t> inputNameArrData(
       reinterpret_cast<const uint8_t *>(name.c_str()), name.size() + 1);
@@ -1024,10 +954,13 @@ bool ObjcCategoryMerger::emitCategoryName(std::string &name, ObjFile *objFile,
 
   llvm::ArrayRef<uint8_t> nameData = generatedSectionData.back();
 
+  auto *parentSection = infoCategoryWriter.catNameInfo.inputSection;
   CStringInputSection *newStringSec = make<CStringInputSection>(
       *infoCategoryWriter.catNameInfo.inputSection, nameData,
       infoCategoryWriter.catNameInfo.align, true);
 
+  parentSection->subsections.push_back({0, newStringSec});
+
   newStringSec->splitIntoPieces();
   newStringSec->pieces[0].live = true;
   newStringSec->parent = infoCategoryWriter.catNameInfo.outputSection;
@@ -1041,7 +974,6 @@ bool ObjcCategoryMerger::emitCategoryName(std::string &name, ObjFile *objFile,
 
   catNamdeSym->used = true;
   objFile->symbols.push_back(catNamdeSym);
-  return true;
 }
 
 // This method fully creates a new category from the given ClassExtensionInfo.
@@ -1049,71 +981,57 @@ bool ObjcCategoryMerger::emitCategoryName(std::string &name, ObjFile *objFile,
 // 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,
+void ObjcCategoryMerger::emitCategory(ClassExtensionInfo &extInfo,
                                       Defined *&catBodySym) {
   Defined *catNameSym = nullptr;
-  if (!emitCategoryName(extInfo.mergedContainerName,
-                        extInfo.objFileForMergeData, catNameSym))
-    return false;
+  emitCategoryName(extInfo.mergedContainerName, extInfo.objFileForMergeData,
+                   catNameSym);
 
-  if (!emitCategoryBody(extInfo.mergedContainerName, catNameSym,
-                        extInfo.baseClass, extInfo.baseClassName,
-                        extInfo.objFileForMergeData, catBodySym))
-    return false;
+  emitCategoryBody(extInfo.mergedContainerName, catNameSym, extInfo.baseClass,
+                   extInfo.baseClassName, extInfo.objFileForMergeData,
+                   catBodySym);
 
   Defined *catListSym = nullptr;
-  if (!emitCatListEntrySec(extInfo.mergedContainerName, extInfo.baseClassName,
-                           extInfo.objFileForMergeData, catListSym))
-    return false;
+  emitCatListEntrySec(extInfo.mergedContainerName, extInfo.baseClassName,
+                      extInfo.objFileForMergeData, catListSym);
 
   const uint32_t offsetFirstCat = 0;
-  if (!createSymbolReference(catListSym, catBodySym, offsetFirstCat,
-                             infoCategoryWriter.catListInfo.relocTemplate))
-    return false;
+  createSymbolReference(catListSym, catBodySym, offsetFirstCat,
+                        infoCategoryWriter.catListInfo.relocTemplate);
 
-  if (!emitAndLinkPointerList(catBodySym, catLayout.instanceMethodsOffset,
-                              extInfo, extInfo.instanceMethods))
-    return false;
+  emitAndLinkPointerList(catBodySym, catLayout.instanceMethodsOffset, extInfo,
+                         extInfo.instanceMethods);
 
-  if (!emitAndLinkPointerList(catBodySym, catLayout.classMethodsOffset, extInfo,
-                              extInfo.classMethods))
-    return false;
+  emitAndLinkPointerList(catBodySym, catLayout.classMethodsOffset, extInfo,
+                         extInfo.classMethods);
 
-  if (!emitAndLinkProtocolList(catBodySym, catLayout.protocolsOffset, extInfo,
-                               extInfo.protocols))
-    return false;
+  emitAndLinkProtocolList(catBodySym, catLayout.protocolsOffset, extInfo,
+                          extInfo.protocols);
 
-  if (!emitAndLinkPointerList(catBodySym, catLayout.instancePropsOffset,
-                              extInfo, extInfo.instanceProps))
-    return false;
+  emitAndLinkPointerList(catBodySym, catLayout.instancePropsOffset, extInfo,
+                         extInfo.instanceProps);
 
-  if (!emitAndLinkPointerList(catBodySym, catLayout.classPropsOffset, extInfo,
-                              extInfo.classProps))
-    return false;
-
-  return true;
+  emitAndLinkPointerList(catBodySym, catLayout.classPropsOffset, extInfo,
+                         extInfo.classProps);
 }
 
 // This method merges all the categories (sharing a base class) into a single
 // category.
-bool ObjcCategoryMerger::mergeCategoriesIntoSingleCategory(
+void 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;
+    parseCatInfoToExtInfo(catInfo, extInfo);
 
   Defined *newCatDef = nullptr;
-  if (!emitCategory(extInfo, newCatDef))
-    return false;
-
-  return true;
+  emitCategory(extInfo, newCatDef);
+  assert(newCatDef && "Failed to create a new category");
 }
 
-bool ObjcCategoryMerger::createSymbolReference(Defined *refFrom, Symbol *refTo,
+void ObjcCategoryMerger::createSymbolReference(Defined *refFrom, Symbol *refTo,
                                                uint32_t offset,
                                                Reloc &relocTemplate) {
   Reloc r = relocTemplate;
@@ -1121,53 +1039,40 @@ bool ObjcCategoryMerger::createSymbolReference(Defined *refFrom, Symbol *refTo,
   r.addend = 0;
   r.referent = refTo;
   refFrom->isec->relocs.push_back(r);
-
-  return true;
 }
 
-bool ObjcCategoryMerger::collectAndValidateCategoriesData() {
+void 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().starts_with("__OBJC_$_CATEGORY_"))
+    assert(catListCisec &&
+           "__objc_catList InputSection is not a ConcatInputSection");
+
+    for (uint32_t off = 0; off < catListCisec->getSize();
+         off += target->wordSize) {
+      Defined *categorySym = nullptr;
+      tryGetDefinedAtIsecOffset(catListCisec, off, categorySym);
+      assert(categorySym &&
+             "Failed to get a valid cateogry at __objc_catlit offset");
+      if (!categorySym->getName().starts_with(objc::symbol_names::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());
-      }
+      auto *catBodyIsec = dyn_cast<ConcatInputSection>(categorySym->isec);
+      assert(catBodyIsec &&
+             "Category data section is not an ConcatInputSection");
 
       // 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");
+      Symbol *classSym = nullptr;
+      tryGetSymbolAtIsecOffset(catBodyIsec, catLayout.klassOffset, classSym);
+      assert(classSym && "Category does not have a valid base class");
 
-      auto *classSym = classReloc->referent.get<Symbol *>();
-      InfoInputCategory catInputInfo{catBodyIsec, catListCisec, r.offset};
+      InfoInputCategory catInputInfo{catBodyIsec, catListCisec, off};
       categoryMap[classSym].push_back(catInputInfo);
 
-      if (!collectCategoryWriterInfoFromCategory(catInputInfo))
-        return false;
+      collectCategoryWriterInfoFromCategory(catInputInfo);
     }
   }
 
@@ -1183,8 +1088,6 @@ bool ObjcCategoryMerger::collectAndValidateCategoriesData() {
       llvm::sort(entry.second, cmpFn);
     }
   }
-
-  return true;
 }
 
 // In the input we have multiple __objc_catlist InputSection, each of which may
@@ -1192,7 +1095,7 @@ bool ObjcCategoryMerger::collectAndValidateCategoriesData() {
 // erase) only some. There will be some categories that will remain unoutched
 // (not erased). For these not erased categories, we generate new __objc_catlist
 // entries since the parent __objc_catlist entry will be erased
-bool ObjcCategoryMerger::generateCatListForNonErasedCategories(
+void ObjcCategoryMerger::generateCatListForNonErasedCategories(
     std::map<ConcatInputSection *, std::set<uint64_t>> catListToErasedOffsets) {
 
   // Go through all offsets of all __objc_catlist's that we process and if there
@@ -1209,9 +1112,9 @@ bool ObjcCategoryMerger::generateCatListForNonErasedCategories(
       }
 
       Defined *nonErasedCatBody = nullptr;
-      if (!tryGetDefinedAtIsecOffset(catListIsec, catListIsecOffset,
-                                     nonErasedCatBody))
-        return registerError("Failed to relocate non-deleted category");
+      tryGetDefinedAtIsecOffset(catListIsec, catListIsecOffset,
+                                nonErasedCatBody);
+      assert(nonErasedCatBody && "Failed to relocate non-deleted category");
 
       // Allocate data for the new __objc_catlist slot
       generatedSectionData.push_back(SmallVector<uint8_t>(target->wordSize, 0));
@@ -1244,21 +1147,25 @@ bool ObjcCategoryMerger::generateCatListForNonErasedCategories(
       objFile->symbols.push_back(catListSlotSym);
 
       // Now link the category body into the newly created slot
-      if (!createSymbolReference(catListSlotSym, nonErasedCatBody, 0,
-                                 infoCategoryWriter.catListInfo.relocTemplate))
-        return registerError(
-            "Failed to create symbol reference to non-deleted category");
+      createSymbolReference(catListSlotSym, nonErasedCatBody, 0,
+                            infoCategoryWriter.catListInfo.relocTemplate);
 
       catListIsecOffset += target->wordSize;
     }
   }
-  return true;
+}
+
+void ObjcCategoryMerger::eraseISec(ConcatInputSection *isec) {
+  isec->live = false;
+  for (auto &sym : isec->symbols) {
+    sym->used = false;
+  }
 }
 
 // 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() {
+void 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
@@ -1274,8 +1181,7 @@ bool ObjcCategoryMerger::eraseMergedCategories() {
       if (!catInfo.wasMerged) {
         continue;
       }
-      catInfo.catListIsec->linkerOptimizeReason =
-          LinkerOptReason::CategoryMerging;
+      eraseISec(catInfo.catListIsec);
       catListToErasedOffsets[catInfo.catListIsec].insert(
           catInfo.offCatListIsec);
     }
@@ -1284,8 +1190,7 @@ bool ObjcCategoryMerger::eraseMergedCategories() {
   // 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 (!generateCatListForNonErasedCategories(catListToErasedOffsets))
-    return false;
+  generateCatListForNonErasedCategories(catListToErasedOffsets);
 
   // Erase the old method lists & names of the categories that were merged
   for (auto &mapEntry : categoryMap) {
@@ -1293,8 +1198,7 @@ bool ObjcCategoryMerger::eraseMergedCategories() {
       if (!catInfo.wasMerged)
         continue;
 
-      catInfo.catBodyIsec->linkerOptimizeReason =
-          LinkerOptReason::CategoryMerging;
+      eraseISec(catInfo.catBodyIsec);
       tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec, catLayout.nameOffset,
                                   /*stringOnly=*/true);
       tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec,
@@ -1309,33 +1213,28 @@ bool ObjcCategoryMerger::eraseMergedCategories() {
                                   catLayout.instancePropsOffset);
     }
   }
-
-  return true;
 }
 
-bool ObjcCategoryMerger::doMerge() {
-  if (!collectAndValidateCategoriesData())
-    return false;
+void ObjcCategoryMerger::doMerge() {
+  collectAndValidateCategoriesData();
 
   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 (entry.second.size() > 1) {
+      // Merge all categories into a new, single category
+      mergeCategoriesIntoSingleCategory(entry.second);
+      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;
+  // Erase all categories that were merged
+  eraseMergedCategories();
+}
 
-  return true;
+void ObjcCategoryMerger::doCleanup() {
+  generatedNames.clear();
+  generatedSectionData.clear();
 }
 
 } // namespace
@@ -1346,3 +1245,5 @@ void objc::mergeCategories() {
   ObjcCategoryMerger merger(inputSections);
   merger.doMerge();
 }
+
+void objc::doCleanup() { ObjcCategoryMerger::doCleanup(); }
diff --git a/lld/MachO/ObjC.h b/lld/MachO/ObjC.h
index f4a1cc5cb43c2e..9fbe984e6223ec 100644
--- a/lld/MachO/ObjC.h
+++ b/lld/MachO/ObjC.h
@@ -17,14 +17,26 @@ namespace objc {
 
 namespace symbol_names {
 constexpr const char klass[] = "_OBJC_CLASS_$_";
+constexpr const char klassPropList[] = "__OBJC_$_CLASS_PROP_LIST_";
+
 constexpr const char metaclass[] = "_OBJC_METACLASS_$_";
 constexpr const char ehtype[] = "_OBJC_EHTYPE_$_";
 constexpr const char ivar[] = "_OBJC_IVAR_$_";
+constexpr const char listProprieties[] = "__OBJC_$_PROP_LIST_";
+
+constexpr const char category[] = "__OBJC_$_CATEGORY_";
+constexpr const char categoryInstanceMethods[] =
+    "__OBJC_$_CATEGORY_INSTANCE_METHODS_";
+constexpr const char categoryClassMethods[] =
+    "__OBJC_$_CATEGORY_CLASS_METHODS_";
+constexpr const char categoryProtocols[] = "__OBJC_CATEGORY_PROTOCOLS_$_";
 } // namespace symbol_names
 
 // Check for duplicate method names within related categories / classes.
 void checkCategories();
 void mergeCategories();
+
+void doCleanup();
 } // namespace objc
 
 bool hasObjCSection(llvm::MemoryBufferRef);
diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td
index d6eb0441386e05..0d8ee2a0926be2 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -129,6 +129,12 @@ 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 objc_category_merging : Flag<["-"], "objc_category_merging">,
+    HelpText<"Merge Objective-C categories that share the same base class">,
+    Group<grp_lld>;
+def no_objc_category_merging : Flag<["-"], "no_objc_category_merging">,
+    HelpText<"Do not merge Objective-C 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">,
@@ -966,14 +972,6 @@ def interposable_list : Separate<["-"], "interposable_list">,
 def no_function_starts : Flag<["-"], "no_function_starts">,
     HelpText<"Do not create table of function start addresses">,
     Group<grp_rare>;
-def objc_category_merging : Flag<["-"], "objc_category_merging">,
-    HelpText<"Merge Objective-C categories that share the same base class">,
-    Flags<[HelpHidden]>,
-    Group<grp_rare>;
-def no_objc_category_merging : Flag<["-"], "no_objc_category_merging">,
-    HelpText<"Do not merge Objective-C categories">,
-    Flags<[HelpHidden]>,
-    Group<grp_rare>;
 def object_path_lto : Separate<["-"], "object_path_lto">,
     MetaVarName<"<path>">,
     HelpText<"Retain any temporary mach-o file in <path> that would otherwise be deleted during LTO">,
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 1c7c0b8acc3e46..a5d66bb4ea0801 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.shouldOmitFromOutput())
+      if (!piece.live)
         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.shouldOmitFromOutput())
+      if (!piece.live)
         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.shouldOmitFromOutput())
+      if (!piece.live)
         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.shouldOmitFromOutput())
+      if (!piece.live)
         continue;
       auto s = isec->getCachedHashStringRef(i);
       auto it = stringOffsetMap.find(s);
diff --git a/lld/test/MachO/objc-category-merging-complete-test.s b/lld/test/MachO/objc-category-merging-complete-test.s
index db807972baa840..c843bd0c206113 100644
--- a/lld/test/MachO/objc-category-merging-complete-test.s
+++ b/lld/test/MachO/objc-category-merging-complete-test.s
@@ -13,15 +13,83 @@
 # RUN: llvm-objdump --objc-meta-data --macho a64_file2_merge.exe | FileCheck %s --check-prefixes=MERGE_CATS
 
 
-MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_(Category02|Category03)
-MERGE_CATS: instanceMethods
-MERGE_CATS-NEXT: 24
-MERGE_CATS-NEXT: 4
-MERGE_CATS: classMethods
-MERGE_CATS-NEXT: 24
-MERGE_CATS-NEXT: 4
+MERGE_CATS:     __OBJC_$_CATEGORY_MyBaseClass_$_(Category02|Category03)
+MERGE_CATS:           instanceMethods
+MERGE_CATS-NEXT:           entsize 24
+MERGE_CATS-NEXT:             count 4
+MERGE_CATS-NEXT:              name {{.*}} class02InstanceMethod
+MERGE_CATS-NEXT:             types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:               imp -[MyBaseClass(Category02) class02InstanceMethod]
+MERGE_CATS-NEXT:              name {{.*}} myProtocol02Method
+MERGE_CATS-NEXT:             types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:               imp -[MyBaseClass(Category02) myProtocol02Method]
+MERGE_CATS-NEXT:              name {{.*}} class03InstanceMethod
+MERGE_CATS-NEXT:             types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:               imp -[MyBaseClass(Category03) class03InstanceMethod]
+MERGE_CATS-NEXT:              name {{.*}} myProtocol03Method
+MERGE_CATS-NEXT:             types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:               imp -[MyBaseClass(Category03) myProtocol03Method]
+MERGE_CATS-NEXT:      classMethods {{.*}}
+MERGE_CATS-NEXT:           entsize 24
+MERGE_CATS-NEXT:             count 4
+MERGE_CATS-NEXT:              name {{.*}} class02ClassMethod
+MERGE_CATS-NEXT:             types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:               imp +[MyBaseClass(Category02) class02ClassMethod]
+MERGE_CATS-NEXT:              name {{.*}} MyProtocol02Prop
+MERGE_CATS-NEXT:             types {{.*}} i16 at 0:8
+MERGE_CATS-NEXT:               imp +[MyBaseClass(Category02) MyProtocol02Prop]
+MERGE_CATS-NEXT:              name {{.*}} class03ClassMethod
+MERGE_CATS-NEXT:             types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:               imp +[MyBaseClass(Category03) class03ClassMethod]
+MERGE_CATS-NEXT:              name {{.*}} MyProtocol03Prop
+MERGE_CATS-NEXT:             types {{.*}} i16 at 0:8
+MERGE_CATS-NEXT:               imp +[MyBaseClass(Category03) MyProtocol03Prop]
+MERGE_CATS-NEXT:         protocols
+MERGE_CATS-NEXT:                      count 2
+MERGE_CATS-NEXT:              list[0] {{.*}} (struct protocol_t *)
+MERGE_CATS-NEXT:                  isa 0x0
+MERGE_CATS-NEXT:                 name {{.*}} MyProtocol02
+MERGE_CATS-NEXT:            protocols 0x0
+MERGE_CATS-NEXT:          instanceMethods
+MERGE_CATS-NEXT:               entsize 24
+MERGE_CATS-NEXT:                 count 2
+MERGE_CATS-NEXT:                  name {{.*}} myProtocol02Method
+MERGE_CATS-NEXT:                 types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:                   imp 0x0
+MERGE_CATS-NEXT:                  name {{.*}} MyProtocol02Prop
+MERGE_CATS-NEXT:                 types {{.*}} i16 at 0:8
+MERGE_CATS-NEXT:                   imp 0x0
+MERGE_CATS-NEXT:             classMethods
+MERGE_CATS-NEXT:      optionalInstanceMethods 0x0
+MERGE_CATS-NEXT:         optionalClassMethods 0x0
+MERGE_CATS-NEXT:           instanceProperties {{.*}}
+MERGE_CATS-NEXT:              list[1] {{.*}}
+MERGE_CATS-NEXT:                  isa 0x0
+MERGE_CATS-NEXT:                 name {{.*}} MyProtocol03
+MERGE_CATS-NEXT:            protocols 0x0
+MERGE_CATS-NEXT:          instanceMethods
+MERGE_CATS-NEXT:               entsize 24
+MERGE_CATS-NEXT:                 count 2
+MERGE_CATS-NEXT:                  name {{.*}} myProtocol03Method
+MERGE_CATS-NEXT:                 types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:                   imp 0x0
+MERGE_CATS-NEXT:                  name {{.*}} MyProtocol03Prop
+MERGE_CATS-NEXT:                 types {{.*}} i16 at 0:8
+MERGE_CATS-NEXT:                   imp 0x0
+MERGE_CATS-NEXT:             classMethods 0x0
+MERGE_CATS-NEXT:      optionalInstanceMethods 0x0
+MERGE_CATS-NEXT:         optionalClassMethods 0x0
+MERGE_CATS-NEXT:           instanceProperties {{.*}}
+MERGE_CATS-NEXT:      instanceProperties
+MERGE_CATS-NEXT:                    entsize 16
+MERGE_CATS-NEXT:                      count 2
+MERGE_CATS-NEXT:                 name {{.*}} MyProtocol02Prop
+MERGE_CATS-NEXT:            attributes {{.*}} Ti,R,D
+MERGE_CATS-NEXT:                 name {{.*}} MyProtocol03Prop
+MERGE_CATS-NEXT:            attributes {{.*}} Ti,R,D
 
 
+NO_MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_(Category02|Category03)
 NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category02
 NO_MERGE_CATS: instanceMethods
 NO_MERGE_CATS-NEXT: 24
diff --git a/lld/test/MachO/objc-category-merging-extern-class-minimal.s b/lld/test/MachO/objc-category-merging-extern-class-minimal.s
index 1add19350c642b..6b8117e6a2ed76 100644
--- a/lld/test/MachO/objc-category-merging-extern-class-minimal.s
+++ b/lld/test/MachO/objc-category-merging-extern-class-minimal.s
@@ -24,6 +24,15 @@ MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_(Category01|Category02)
 MERGE_CATS: instanceMethods
 MERGE_CATS-NEXT: 24
 MERGE_CATS-NEXT: 2
+MERGE_CATS-NEXT:   name {{.*}} cat01_InstanceMethod
+MERGE_CATS-NEXT:  types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:    imp -[MyBaseClass(Category01) cat01_InstanceMethod]
+MERGE_CATS-NEXT:   name {{.*}} cat02_InstanceMethod
+MERGE_CATS-NEXT:  types {{.*}} v16 at 0:8
+MERGE_CATS-NEXT:    imp -[MyBaseClass(Category02) cat02_InstanceMethod]
+MERGE_CATS-NEXT:         classMethods 0x0
+MERGE_CATS-NEXT:            protocols 0x0
+MERGE_CATS-NEXT:   instanceProperties 0x0
 
 #### Check merge categories disabled ###
 # Check that the merged category is not there
diff --git a/llvm/lib/Target/AMDGPU/SIInstrInfo.h b/llvm/lib/Target/AMDGPU/SIInstrInfo.h
index 4200e0c8a29e1c..a0fe8d9bb9845c 100644
--- a/llvm/lib/Target/AMDGPU/SIInstrInfo.h
+++ b/llvm/lib/Target/AMDGPU/SIInstrInfo.h
@@ -949,8 +949,6 @@ class SIInstrInfo final : public AMDGPUGenInstrInfo {
       return AMDGPU::S_WAIT_BVHCNT;
     case AMDGPU::S_WAIT_DSCNT_soft:
       return AMDGPU::S_WAIT_DSCNT;
-    case AMDGPU::S_WAIT_KMCNT_soft:
-      return AMDGPU::S_WAIT_KMCNT;
     default:
       return Opcode;
     }

>From dae6e98af2a3cbb7c2d8a6a803133c2128ab1456 Mon Sep 17 00:00:00 2001
From: Alex B <alexborcan at meta.com>
Date: Fri, 1 Mar 2024 14:35:32 -0800
Subject: [PATCH 3/5]  [lld-macho] Address Feedback #2

---
 lld/MachO/ObjC.cpp                            | 325 +++++++++---------
 lld/MachO/SyntheticSections.cpp               |   6 +-
 .../objc-category-merging-complete-test.s     |   1 +
 ...jc-category-merging-extern-class-minimal.s |   7 +-
 4 files changed, 177 insertions(+), 162 deletions(-)

diff --git a/lld/MachO/ObjC.cpp b/lld/MachO/ObjC.cpp
index 29245019bcdde3..0660043f0c0a0c 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -338,8 +338,8 @@ namespace {
 class ObjcCategoryMerger {
   // Information about an input category
   struct InfoInputCategory {
-    ConcatInputSection *catBodyIsec;
     ConcatInputSection *catListIsec;
+    ConcatInputSection *catBodyIsec;
     uint32_t offCatListIsec = 0;
 
     bool wasMerged = false;
@@ -369,9 +369,14 @@ class ObjcCategoryMerger {
   // 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;
+    PointerListInfo(const char *_categoryPrefix, uint32_t _categoryOffset,
+                    uint32_t _pointersPerStruct)
+        : categoryPrefix(_categoryPrefix), categoryOffset(_categoryOffset),
+          pointersPerStruct(_pointersPerStruct) {}
+    const char *categoryPrefix;
+    uint32_t categoryOffset = 0;
+
+    uint32_t pointersPerStruct = 0;
 
     uint32_t structSize = 0;
     uint32_t structCount = 0;
@@ -383,19 +388,36 @@ class ObjcCategoryMerger {
   // will have all the additional methods, protocols, proprieties that are
   // contained in all the categories that extend a particular class.
   struct ClassExtensionInfo {
+    ClassExtensionInfo(CategoryLayout &_catLayout) : catLayout(_catLayout){};
+
     // Merged names of containers. Ex: base|firstCategory|secondCategory|...
     std::string mergedContainerName;
     std::string baseClassName;
     Symbol *baseClass = nullptr;
+    CategoryLayout &catLayout;
+
     // In case we generate new data, mark the new data as belonging to this file
     ObjFile *objFileForMergeData = nullptr;
 
-    PointerListInfo instanceMethods =
-        objc::symbol_names::categoryInstanceMethods;
-    PointerListInfo classMethods = objc::symbol_names::categoryClassMethods;
-    PointerListInfo protocols = objc::symbol_names::categoryProtocols;
-    PointerListInfo instanceProps = objc::symbol_names::listProprieties;
-    PointerListInfo classProps = objc::symbol_names::klassPropList;
+    PointerListInfo instanceMethods = {
+        objc::symbol_names::categoryInstanceMethods,
+        /*_categoryOffset=*/catLayout.instanceMethodsOffset,
+        /*pointersPerStruct=*/3};
+    PointerListInfo classMethods = {
+        objc::symbol_names::categoryClassMethods,
+        /*_categoryOffset=*/catLayout.classMethodsOffset,
+        /*pointersPerStruct=*/3};
+    PointerListInfo protocols = {objc::symbol_names::categoryProtocols,
+                                 /*_categoryOffset=*/catLayout.protocolsOffset,
+                                 /*pointersPerStruct=*/0};
+    PointerListInfo instanceProps = {
+        objc::symbol_names::listProprieties,
+        /*_categoryOffset=*/catLayout.instancePropsOffset,
+        /*pointersPerStruct=*/2};
+    PointerListInfo classProps = {
+        objc::symbol_names::klassPropList,
+        /*_categoryOffset=*/catLayout.classPropsOffset,
+        /*pointersPerStruct=*/2};
   };
 
 public:
@@ -415,45 +437,42 @@ class ObjcCategoryMerger {
       std::map<ConcatInputSection *, std::set<uint64_t>>
           catListToErasedOffsets);
   template <typename T>
-  void collectSectionWriteInfoFromIsec(InputSection *isec,
+  void collectSectionWriteInfoFromIsec(const InputSection *isec,
                                        InfroWriteSection<T> &catWriteInfo);
-  void collectCategoryWriterInfoFromCategory(InfoInputCategory &catInfo);
-  void parseCatInfoToExtInfo(InfoInputCategory &catInfo,
+  void collectCategoryWriterInfoFromCategory(const InfoInputCategory &catInfo);
+  void parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
                              ClassExtensionInfo &extInfo);
 
-  void parseProtocolListInfo(ConcatInputSection *isec,
-                             uint32_t symbolsPerStruct,
+  void parseProtocolListInfo(const ConcatInputSection *isec, uint32_t secOffset,
                              PointerListInfo &ptrList);
 
-  void parsePointerListInfo(ConcatInputSection *isec, uint32_t secOffset,
-                            uint32_t symbolsPerStruct,
+  void parsePointerListInfo(const ConcatInputSection *isec, uint32_t secOffset,
                             PointerListInfo &ptrList);
 
   void emitAndLinkPointerList(Defined *parentSym, uint32_t linkAtOffset,
-                              ClassExtensionInfo &extInfo,
-                              PointerListInfo &ptrList);
+                              const ClassExtensionInfo &extInfo,
+                              const PointerListInfo &ptrList);
 
   void emitAndLinkProtocolList(Defined *parentSym, uint32_t linkAtOffset,
-                               ClassExtensionInfo &extInfo,
-                               PointerListInfo &ptrList);
-
-  void emitCategory(ClassExtensionInfo &extInfo, Defined *&catBodySym);
-  void emitCatListEntrySec(std::string &forCateogryName,
-                           std::string &forBaseClassName, ObjFile *objFile,
-                           Defined *&catListSym);
-  void emitCategoryBody(std::string &name, Defined *nameSym,
-                        Symbol *baseClassSym, std::string &baseClassName,
-                        ObjFile *objFile, Defined *&catBodySym);
-  void emitCategoryName(std::string &name, ObjFile *objFile,
-                        Defined *&catNameSym);
-  void 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);
-  void tryEraseDefinedAtIsecOffset(ConcatInputSection *isec, uint32_t offset,
-                                   bool stringOnly = false);
+                               const ClassExtensionInfo &extInfo,
+                               const PointerListInfo &ptrList);
+
+  Defined *emitCategory(const ClassExtensionInfo &extInfo);
+  Defined *emitCatListEntrySec(const std::string &forCateogryName,
+                               const std::string &forBaseClassName,
+                               ObjFile *objFile);
+  Defined *emitCategoryBody(const std::string &name, const Defined *nameSym,
+                            const Symbol *baseClassSym,
+                            const std::string &baseClassName, ObjFile *objFile);
+  Defined *emitCategoryName(const std::string &name, ObjFile *objFile);
+  void createSymbolReference(Defined *refFrom, const Symbol *refTo,
+                             uint32_t offset, const Reloc &relocTemplate);
+  Symbol *tryGetSymbolAtIsecOffset(const ConcatInputSection *isec,
+                                   uint32_t offset);
+  Defined *tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
+                                     uint32_t offset);
+  void tryEraseDefinedAtIsecOffset(const ConcatInputSection *isec,
+                                   uint32_t offset, bool stringOnly = false);
 
   CategoryLayout catLayout;
   ClassLayout classLayout;
@@ -491,7 +510,7 @@ ObjcCategoryMerger::ObjcCategoryMerger(
 // ConcatOutputSection
 template <typename T>
 void ObjcCategoryMerger::collectSectionWriteInfoFromIsec(
-    InputSection *isec, InfroWriteSection<T> &catWriteInfo) {
+    const InputSection *isec, InfroWriteSection<T> &catWriteInfo) {
 
   catWriteInfo.inputSection = const_cast<Section *>(&isec->section);
   catWriteInfo.align = isec->align;
@@ -506,35 +525,29 @@ void ObjcCategoryMerger::collectSectionWriteInfoFromIsec(
   catWriteInfo.valid = true;
 }
 
-bool ObjcCategoryMerger::tryGetSymbolAtIsecOffset(ConcatInputSection *isec,
-                                                  uint32_t offset,
-                                                  Symbol *&sym) {
+Symbol *
+ObjcCategoryMerger::tryGetSymbolAtIsecOffset(const ConcatInputSection *isec,
+                                             uint32_t offset) {
   const Reloc *reloc = isec->getRelocAt(offset);
 
   if (!reloc)
-    return false;
+    return nullptr;
 
-  sym = reloc->referent.get<Symbol *>();
-  return sym != nullptr;
+  return reloc->referent.get<Symbol *>();
 }
 
-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;
+Defined *
+ObjcCategoryMerger::tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
+                                              uint32_t offset) {
+  Symbol *sym = tryGetSymbolAtIsecOffset(isec, offset);
+  return dyn_cast_or_null<Defined>(sym);
 }
 
 // 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 ...
-void ObjcCategoryMerger::tryEraseDefinedAtIsecOffset(ConcatInputSection *isec,
-                                                     uint32_t offset,
-                                                     bool stringOnly) {
+void ObjcCategoryMerger::tryEraseDefinedAtIsecOffset(
+    const ConcatInputSection *isec, uint32_t offset, bool stringOnly) {
   const Reloc *reloc = isec->getRelocAt(offset);
 
   if (!reloc)
@@ -559,25 +572,22 @@ void ObjcCategoryMerger::tryEraseDefinedAtIsecOffset(ConcatInputSection *isec,
 }
 
 void ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
-    InfoInputCategory &catInfo) {
+    const InfoInputCategory &catInfo) {
 
-  collectSectionWriteInfoFromIsec<ConcatOutputSection>(
-      catInfo.catListIsec, infoCategoryWriter.catListInfo);
-  collectSectionWriteInfoFromIsec<ConcatOutputSection>(
-      catInfo.catBodyIsec, infoCategoryWriter.catBodyInfo);
+  if (!infoCategoryWriter.catListInfo.valid)
+    collectSectionWriteInfoFromIsec<ConcatOutputSection>(
+        catInfo.catListIsec, infoCategoryWriter.catListInfo);
+  if (!infoCategoryWriter.catBodyInfo.valid)
+    collectSectionWriteInfoFromIsec<ConcatOutputSection>(
+        catInfo.catBodyIsec, infoCategoryWriter.catBodyInfo);
 
   if (!infoCategoryWriter.catNameInfo.valid) {
-    const Reloc *catNameReloc =
-        catInfo.catBodyIsec->getRelocAt(catLayout.nameOffset);
-
-    assert(catNameReloc && "Category does not have a reloc at nameOffset");
-
-    lld::macho::Defined *catDefSym =
-        dyn_cast_or_null<Defined>(catNameReloc->referent.dyn_cast<Symbol *>());
-    assert(catDefSym && "Reloc of category name is not a valid Defined symbol");
+    lld::macho::Defined *catNameSym =
+        tryGetDefinedAtIsecOffset(catInfo.catBodyIsec, catLayout.nameOffset);
+    assert(catNameSym && "Category does not have a valid name Symbol");
 
     collectSectionWriteInfoFromIsec<CStringSection>(
-        catDefSym->isec, infoCategoryWriter.catNameInfo);
+        catNameSym->isec, infoCategoryWriter.catNameInfo);
   }
 
   // Collect writer info from all the category lists (we're assuming they all
@@ -585,8 +595,8 @@ void ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
   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 (Defined *ptrList =
+              tryGetDefinedAtIsecOffset(catInfo.catBodyIsec, off)) {
         collectSectionWriteInfoFromIsec<ConcatOutputSection>(
             ptrList->isec, infoCategoryWriter.catPtrListInfo);
         // we've successfully collected data, so we can break
@@ -599,7 +609,7 @@ void ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
 // 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
-void ObjcCategoryMerger::parseProtocolListInfo(ConcatInputSection *isec,
+void ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
                                                uint32_t secOffset,
                                                PointerListInfo &ptrList) {
   if (!isec || (secOffset + target->wordSize > isec->data.size()))
@@ -643,11 +653,10 @@ void ObjcCategoryMerger::parseProtocolListInfo(ConcatInputSection *isec,
 // 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.
-void ObjcCategoryMerger::parsePointerListInfo(ConcatInputSection *isec,
+void ObjcCategoryMerger::parsePointerListInfo(const ConcatInputSection *isec,
                                               uint32_t secOffset,
-                                              uint32_t symbolsPerStruct,
                                               PointerListInfo &ptrList) {
-  assert(symbolsPerStruct == 2 || symbolsPerStruct == 3);
+  assert(ptrList.pointersPerStruct == 2 || ptrList.pointersPerStruct == 3);
   assert(isec && "Trying to parse pointer list from null isec");
   assert(secOffset + target->wordSize <= isec->data.size() &&
          "Trying to read pointer list beyond section end");
@@ -663,6 +672,7 @@ void ObjcCategoryMerger::parsePointerListInfo(ConcatInputSection *isec,
       ptrListSym->isec->data.data() + listHeaderLayout.structSizeOffset);
   uint32_t thisStructCount = *reinterpret_cast<const uint32_t *>(
       ptrListSym->isec->data.data() + listHeaderLayout.structCountOffset);
+  assert(thisStructSize == ptrList.pointersPerStruct * target->wordSize);
 
   assert(!ptrList.structSize || (thisStructSize == ptrList.structSize));
 
@@ -671,7 +681,6 @@ void ObjcCategoryMerger::parsePointerListInfo(ConcatInputSection *isec,
 
   uint32_t expectedListSize =
       listHeaderLayout.totalSize + (thisStructSize * thisStructCount);
-
   assert(expectedListSize == ptrListSym->isec->data.size() &&
          "Pointer list does not match expected size");
 
@@ -688,9 +697,9 @@ void ObjcCategoryMerger::parsePointerListInfo(ConcatInputSection *isec,
 }
 
 // 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
+// append the parsed info into the structure which will contain all the
 // information about how a class is extended (extInfo)
-void ObjcCategoryMerger::parseCatInfoToExtInfo(InfoInputCategory &catInfo,
+void ObjcCategoryMerger::parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
                                                ClassExtensionInfo &extInfo) {
   const Reloc *catNameReloc =
       catInfo.catBodyIsec->getRelocAt(catLayout.nameOffset);
@@ -698,60 +707,58 @@ void ObjcCategoryMerger::parseCatInfoToExtInfo(InfoInputCategory &catInfo,
   // Parse name
   assert(catNameReloc && "Category does not have a reloc at 'nameOffset'");
 
-  if (!extInfo.mergedContainerName.empty())
-    extInfo.mergedContainerName += "|";
-
-  if (!extInfo.objFileForMergeData)
+  // is this the first category we are parsing?
+  if (extInfo.mergedContainerName.empty()) {
     extInfo.objFileForMergeData =
         dyn_cast_or_null<ObjFile>(catInfo.catBodyIsec->getFile());
+  } else {
+    extInfo.mergedContainerName += "|";
+  }
+  assert(extInfo.objFileForMergeData &&
+         "Expected to already have valid objextInfo.objFileForMergeData");
 
   StringRef catName = getReferentString(*catNameReloc);
   extInfo.mergedContainerName += catName.str();
 
   // Parse base class
-  const Reloc *klassReloc =
-      catInfo.catBodyIsec->getRelocAt(catLayout.klassOffset);
-
-  assert(klassReloc && "Category does not have a reloc at 'klassOffset'");
-
-  Symbol *classSym = klassReloc->referent.get<Symbol *>();
-
-  assert(
-      (!extInfo.baseClass || (extInfo.baseClass == classSym)) &&
-      "Trying to parse category info into container with different base class");
-
-  extInfo.baseClass = classSym;
-
-  if (extInfo.baseClassName.empty()) {
+  if (!extInfo.baseClass) {
+    Symbol *classSym =
+        tryGetSymbolAtIsecOffset(catInfo.catBodyIsec, catLayout.klassOffset);
+    assert(extInfo.baseClassName.empty());
+    extInfo.baseClass = classSym;
     llvm::StringRef classPrefix(objc::symbol_names::klass);
     assert(classSym->getName().starts_with(classPrefix) &&
            "Base class symbol does not start with expected prefix");
-
     extInfo.baseClassName = classSym->getName().substr(classPrefix.size());
+  } else {
+    assert((extInfo.baseClass ==
+            tryGetSymbolAtIsecOffset(catInfo.catBodyIsec,
+                                     catLayout.klassOffset)) &&
+           "Trying to parse category info into container with different base "
+           "class");
   }
 
   parsePointerListInfo(catInfo.catBodyIsec, catLayout.instanceMethodsOffset,
-                       /*symbolsPerStruct=*/3, extInfo.instanceMethods);
+                       extInfo.instanceMethods);
 
   parsePointerListInfo(catInfo.catBodyIsec, catLayout.classMethodsOffset,
-                       /*symbolsPerStruct=*/3, extInfo.classMethods);
+                       extInfo.classMethods);
 
   parseProtocolListInfo(catInfo.catBodyIsec, catLayout.protocolsOffset,
                         extInfo.protocols);
 
   parsePointerListInfo(catInfo.catBodyIsec, catLayout.instancePropsOffset,
-                       /*symbolsPerStruct=*/2, extInfo.instanceProps);
+                       extInfo.instanceProps);
 
   parsePointerListInfo(catInfo.catBodyIsec, catLayout.classPropsOffset,
-                       /*symbolsPerStruct=*/2, extInfo.classProps);
+                       extInfo.classProps);
 }
 
 // Generate a protocol list (including header) and link it into the parent at
 // the specified offset.
-void ObjcCategoryMerger::emitAndLinkProtocolList(Defined *parentSym,
-                                                 uint32_t linkAtOffset,
-                                                 ClassExtensionInfo &extInfo,
-                                                 PointerListInfo &ptrList) {
+void ObjcCategoryMerger::emitAndLinkProtocolList(
+    Defined *parentSym, uint32_t linkAtOffset,
+    const ClassExtensionInfo &extInfo, const PointerListInfo &ptrList) {
   if (ptrList.allPtrs.empty())
     return;
 
@@ -779,7 +786,7 @@ void ObjcCategoryMerger::emitAndLinkProtocolList(Defined *parentSym,
 
   listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
 
-  generatedNames.push_back(StringRef(ptrList.namePrefix));
+  generatedNames.push_back(StringRef(ptrList.categoryPrefix));
   auto &symName = generatedNames.back();
   symName += extInfo.baseClassName + "_$_(" + extInfo.mergedContainerName + ")";
 
@@ -807,10 +814,9 @@ void ObjcCategoryMerger::emitAndLinkProtocolList(Defined *parentSym,
 // 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.
-void ObjcCategoryMerger::emitAndLinkPointerList(Defined *parentSym,
-                                                uint32_t linkAtOffset,
-                                                ClassExtensionInfo &extInfo,
-                                                PointerListInfo &ptrList) {
+void ObjcCategoryMerger::emitAndLinkPointerList(
+    Defined *parentSym, uint32_t linkAtOffset,
+    const ClassExtensionInfo &extInfo, const PointerListInfo &ptrList) {
   if (ptrList.allPtrs.empty())
     return;
 
@@ -840,7 +846,7 @@ void ObjcCategoryMerger::emitAndLinkPointerList(Defined *parentSym,
 
   listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
 
-  generatedNames.push_back(StringRef(ptrList.namePrefix));
+  generatedNames.push_back(StringRef(ptrList.categoryPrefix));
   auto &symName = generatedNames.back();
   symName += extInfo.baseClassName + "_$_" + extInfo.mergedContainerName;
 
@@ -866,10 +872,10 @@ void ObjcCategoryMerger::emitAndLinkPointerList(Defined *parentSym,
 }
 
 // This method creates an __objc_catlist ConcatInputSection with a single slot
-void ObjcCategoryMerger::emitCatListEntrySec(std::string &forCateogryName,
-                                             std::string &forBaseClassName,
-                                             ObjFile *objFile,
-                                             Defined *&catListSym) {
+Defined *
+ObjcCategoryMerger::emitCatListEntrySec(const std::string &forCateogryName,
+                                        const std::string &forBaseClassName,
+                                        ObjFile *objFile) {
   uint32_t sectionSize = target->wordSize;
   generatedSectionData.push_back(SmallVector<uint8_t>(sectionSize, 0));
   llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
@@ -888,7 +894,7 @@ void ObjcCategoryMerger::emitCatListEntrySec(std::string &forCateogryName,
   catSymName += forBaseClassName + "(" + forCateogryName + ")>";
   generatedNames.push_back(StringRef(catSymName));
 
-  catListSym = make<Defined>(
+  Defined *catListSym = make<Defined>(
       StringRef(generatedNames.back()), /*file=*/objFile, newCatList,
       /*value=*/0, bodyData.size(), /*isWeakDef=*/false, /*isExternal=*/false,
       /*isPrivateExtern=*/false, /*includeInSymtab=*/false,
@@ -897,16 +903,17 @@ void ObjcCategoryMerger::emitCatListEntrySec(std::string &forCateogryName,
 
   catListSym->used = true;
   objFile->symbols.push_back(catListSym);
+  return catListSym;
 }
 
 // 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.
-void ObjcCategoryMerger::emitCategoryBody(std::string &name, Defined *nameSym,
-                                          Symbol *baseClassSym,
-                                          std::string &baseClassName,
-                                          ObjFile *objFile,
-                                          Defined *&catBodySym) {
+Defined *ObjcCategoryMerger::emitCategoryBody(const std::string &name,
+                                              const Defined *nameSym,
+                                              const Symbol *baseClassSym,
+                                              const std::string &baseClassName,
+                                              ObjFile *objFile) {
   generatedSectionData.push_back(SmallVector<uint8_t>(catLayout.totalSize, 0));
   llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
 
@@ -926,7 +933,7 @@ void ObjcCategoryMerger::emitCategoryBody(std::string &name, Defined *nameSym,
   std::string symName =
       objc::symbol_names::category + baseClassName + "_$_(" + name + ")";
   generatedNames.push_back(StringRef(symName));
-  catBodySym = make<Defined>(
+  Defined *catBodySym = make<Defined>(
       StringRef(generatedNames.back()), /*file=*/objFile, newBodySec,
       /*value=*/0, bodyData.size(), /*isWeakDef=*/false, /*isExternal=*/false,
       /*isPrivateExtern=*/false, /*includeInSymtab=*/true,
@@ -942,12 +949,14 @@ void ObjcCategoryMerger::emitCategoryBody(std::string &name, Defined *nameSym,
   // Create a reloc to the base class (either external or internal)
   createSymbolReference(catBodySym, baseClassSym, catLayout.klassOffset,
                         infoCategoryWriter.catBodyInfo.relocTemplate);
+
+  return catBodySym;
 }
 
 // This writes the new category name (for the merged category) into the binary
 // and returns the sybmol for it.
-void ObjcCategoryMerger::emitCategoryName(std::string &name, ObjFile *objFile,
-                                          Defined *&catNamdeSym) {
+Defined *ObjcCategoryMerger::emitCategoryName(const std::string &name,
+                                              ObjFile *objFile) {
   llvm::ArrayRef<uint8_t> inputNameArrData(
       reinterpret_cast<const uint8_t *>(name.c_str()), name.size() + 1);
   generatedSectionData.push_back(SmallVector<uint8_t>(inputNameArrData));
@@ -964,8 +973,9 @@ void ObjcCategoryMerger::emitCategoryName(std::string &name, ObjFile *objFile,
   newStringSec->splitIntoPieces();
   newStringSec->pieces[0].live = true;
   newStringSec->parent = infoCategoryWriter.catNameInfo.outputSection;
+  in.cStringSection->addInput(newStringSec);
 
-  catNamdeSym = make<Defined>(
+  Defined *catNamdeSym = make<Defined>(
       "<merged category name>", /*file=*/objFile, newStringSec,
       /*value=*/0, nameData.size(),
       /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
@@ -974,6 +984,7 @@ void ObjcCategoryMerger::emitCategoryName(std::string &name, ObjFile *objFile,
 
   catNamdeSym->used = true;
   objFile->symbols.push_back(catNamdeSym);
+  return catNamdeSym;
 }
 
 // This method fully creates a new category from the given ClassExtensionInfo.
@@ -981,19 +992,17 @@ void ObjcCategoryMerger::emitCategoryName(std::string &name, ObjFile *objFile,
 // 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.
-void ObjcCategoryMerger::emitCategory(ClassExtensionInfo &extInfo,
-                                      Defined *&catBodySym) {
-  Defined *catNameSym = nullptr;
-  emitCategoryName(extInfo.mergedContainerName, extInfo.objFileForMergeData,
-                   catNameSym);
+Defined *ObjcCategoryMerger::emitCategory(const ClassExtensionInfo &extInfo) {
+  Defined *catNameSym = emitCategoryName(extInfo.mergedContainerName,
+                                         extInfo.objFileForMergeData);
 
-  emitCategoryBody(extInfo.mergedContainerName, catNameSym, extInfo.baseClass,
-                   extInfo.baseClassName, extInfo.objFileForMergeData,
-                   catBodySym);
+  Defined *catBodySym = emitCategoryBody(
+      extInfo.mergedContainerName, catNameSym, extInfo.baseClass,
+      extInfo.baseClassName, extInfo.objFileForMergeData);
 
-  Defined *catListSym = nullptr;
-  emitCatListEntrySec(extInfo.mergedContainerName, extInfo.baseClassName,
-                      extInfo.objFileForMergeData, catListSym);
+  Defined *catListSym =
+      emitCatListEntrySec(extInfo.mergedContainerName, extInfo.baseClassName,
+                          extInfo.objFileForMergeData);
 
   const uint32_t offsetFirstCat = 0;
   createSymbolReference(catListSym, catBodySym, offsetFirstCat,
@@ -1013,6 +1022,8 @@ void ObjcCategoryMerger::emitCategory(ClassExtensionInfo &extInfo,
 
   emitAndLinkPointerList(catBodySym, catLayout.classPropsOffset, extInfo,
                          extInfo.classProps);
+
+  return catBodySym;
 }
 
 // This method merges all the categories (sharing a base class) into a single
@@ -1021,23 +1032,27 @@ void ObjcCategoryMerger::mergeCategoriesIntoSingleCategory(
     std::vector<InfoInputCategory> &categories) {
   assert(categories.size() > 1 && "Expected at least 2 categories");
 
-  ClassExtensionInfo extInfo;
+  ClassExtensionInfo extInfo(catLayout);
 
   for (auto &catInfo : categories)
     parseCatInfoToExtInfo(catInfo, extInfo);
 
-  Defined *newCatDef = nullptr;
-  emitCategory(extInfo, newCatDef);
+  Defined *newCatDef = emitCategory(extInfo);
   assert(newCatDef && "Failed to create a new category");
+
+  for (auto &catInfo : categories) {
+    catInfo.wasMerged = true;
+  }
 }
 
-void ObjcCategoryMerger::createSymbolReference(Defined *refFrom, Symbol *refTo,
+void ObjcCategoryMerger::createSymbolReference(Defined *refFrom,
+                                               const Symbol *refTo,
                                                uint32_t offset,
-                                               Reloc &relocTemplate) {
+                                               const Reloc &relocTemplate) {
   Reloc r = relocTemplate;
   r.offset = offset;
   r.addend = 0;
-  r.referent = refTo;
+  r.referent = const_cast<Symbol *>(refTo);
   refFrom->isec->relocs.push_back(r);
 }
 
@@ -1051,8 +1066,7 @@ void ObjcCategoryMerger::collectAndValidateCategoriesData() {
 
     for (uint32_t off = 0; off < catListCisec->getSize();
          off += target->wordSize) {
-      Defined *categorySym = nullptr;
-      tryGetDefinedAtIsecOffset(catListCisec, off, categorySym);
+      Defined *categorySym = tryGetDefinedAtIsecOffset(catListCisec, off);
       assert(categorySym &&
              "Failed to get a valid cateogry at __objc_catlit offset");
       if (!categorySym->getName().starts_with(objc::symbol_names::category))
@@ -1065,11 +1079,11 @@ void ObjcCategoryMerger::collectAndValidateCategoriesData() {
       // Check that the category has a reloc at 'klassOffset' (which is
       // a pointer to the class symbol)
 
-      Symbol *classSym = nullptr;
-      tryGetSymbolAtIsecOffset(catBodyIsec, catLayout.klassOffset, classSym);
+      Symbol *classSym =
+          tryGetSymbolAtIsecOffset(catBodyIsec, catLayout.klassOffset);
       assert(classSym && "Category does not have a valid base class");
 
-      InfoInputCategory catInputInfo{catBodyIsec, catListCisec, off};
+      InfoInputCategory catInputInfo{catListCisec, catBodyIsec, off};
       categoryMap[classSym].push_back(catInputInfo);
 
       collectCategoryWriterInfoFromCategory(catInputInfo);
@@ -1096,7 +1110,8 @@ void ObjcCategoryMerger::collectAndValidateCategoriesData() {
 // (not erased). For these not erased categories, we generate new __objc_catlist
 // entries since the parent __objc_catlist entry will be erased
 void ObjcCategoryMerger::generateCatListForNonErasedCategories(
-    std::map<ConcatInputSection *, std::set<uint64_t>> catListToErasedOffsets) {
+    const std::map<ConcatInputSection *, std::set<uint64_t>>
+        catListToErasedOffsets) {
 
   // Go through all offsets of all __objc_catlist's that we process and if there
   // are categories that we didn't process - generate a new __objv_catlist for
@@ -1111,9 +1126,8 @@ void ObjcCategoryMerger::generateCatListForNonErasedCategories(
         continue;
       }
 
-      Defined *nonErasedCatBody = nullptr;
-      tryGetDefinedAtIsecOffset(catListIsec, catListIsecOffset,
-                                nonErasedCatBody);
+      Defined *nonErasedCatBody =
+          tryGetDefinedAtIsecOffset(catListIsec, catListIsecOffset);
       assert(nonErasedCatBody && "Failed to relocate non-deleted category");
 
       // Allocate data for the new __objc_catlist slot
@@ -1222,9 +1236,6 @@ void ObjcCategoryMerger::doMerge() {
     if (entry.second.size() > 1) {
       // Merge all categories into a new, single category
       mergeCategoriesIntoSingleCategory(entry.second);
-      for (auto &catInfo : entry.second) {
-        catInfo.wasMerged = true;
-      }
     }
   }
 
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index a5d66bb4ea0801..5b7cb5e74cd60a 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -1695,7 +1695,8 @@ void CStringSection::finalizeContents() {
 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)) {
+    for (int i = 0; i < isec->pieces.size(); ++i) {
+      const StringPiece &piece = isec->pieces[i];
       if (!piece.live)
         continue;
       auto s = isec->getCachedHashStringRef(i);
@@ -1711,7 +1712,8 @@ void DeduplicatedCStringSection::finalizeContents() {
   // Assign an offset for each string and save it to the corresponding
   // StringPieces for easy access.
   for (CStringInputSection *isec : inputs) {
-    for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
+    for (int i = 0; i < isec->pieces.size(); ++i) {
+      StringPiece &piece = isec->pieces[i];
       if (!piece.live)
         continue;
       auto s = isec->getCachedHashStringRef(i);
diff --git a/lld/test/MachO/objc-category-merging-complete-test.s b/lld/test/MachO/objc-category-merging-complete-test.s
index c843bd0c206113..bb36a9008f6d10 100644
--- a/lld/test/MachO/objc-category-merging-complete-test.s
+++ b/lld/test/MachO/objc-category-merging-complete-test.s
@@ -14,6 +14,7 @@
 
 
 MERGE_CATS:     __OBJC_$_CATEGORY_MyBaseClass_$_(Category02|Category03)
+MERGE_CATS-NEXT:              name {{.*}} Category02|Category03
 MERGE_CATS:           instanceMethods
 MERGE_CATS-NEXT:           entsize 24
 MERGE_CATS-NEXT:             count 4
diff --git a/lld/test/MachO/objc-category-merging-extern-class-minimal.s b/lld/test/MachO/objc-category-merging-extern-class-minimal.s
index 6b8117e6a2ed76..0e30ac0df9acf2 100644
--- a/lld/test/MachO/objc-category-merging-extern-class-minimal.s
+++ b/lld/test/MachO/objc-category-merging-extern-class-minimal.s
@@ -21,9 +21,10 @@ MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category02
 
 # Check that the merged cateogry is there, in the correct format
 MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_(Category01|Category02)
-MERGE_CATS: instanceMethods
-MERGE_CATS-NEXT: 24
-MERGE_CATS-NEXT: 2
+MERGE_CATS-NEXT:   name {{.*}} Category01|Category02
+MERGE_CATS:       instanceMethods
+MERGE_CATS-NEXT:  24
+MERGE_CATS-NEXT:  2
 MERGE_CATS-NEXT:   name {{.*}} cat01_InstanceMethod
 MERGE_CATS-NEXT:  types {{.*}} v16 at 0:8
 MERGE_CATS-NEXT:    imp -[MyBaseClass(Category01) cat01_InstanceMethod]

>From d694bd8fb813912008dd82f87aa43fda688b917d Mon Sep 17 00:00:00 2001
From: Alex B <alexborcan at meta.com>
Date: Fri, 8 Mar 2024 13:55:30 -0800
Subject: [PATCH 4/5] [lld-macho] Address Feedback #3

---
 lld/MachO/ObjC.cpp                   | 227 +++++++++++++--------------
 lld/MachO/SyntheticSections.cpp      |   6 +-
 llvm/lib/Target/AMDGPU/SIInstrInfo.h |   2 +
 3 files changed, 110 insertions(+), 125 deletions(-)

diff --git a/lld/MachO/ObjC.cpp b/lld/MachO/ObjC.cpp
index 0660043f0c0a0c..080e597f4602a7 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -351,7 +351,7 @@ class ObjcCategoryMerger {
   // 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 {
+  template <typename T> struct InfoWriteSection {
     bool valid = false; // Data has been successfully collected from input
     uint32_t align = 0;
     Section *inputSection;
@@ -360,10 +360,10 @@ class ObjcCategoryMerger {
   };
 
   struct InfoCategoryWriter {
-    InfroWriteSection<ConcatOutputSection> catListInfo;
-    InfroWriteSection<ConcatOutputSection> catBodyInfo;
-    InfroWriteSection<CStringSection> catNameInfo;
-    InfroWriteSection<ConcatOutputSection> catPtrListInfo;
+    InfoWriteSection<ConcatOutputSection> catListInfo;
+    InfoWriteSection<ConcatOutputSection> catBodyInfo;
+    InfoWriteSection<CStringSection> catNameInfo;
+    InfoWriteSection<ConcatOutputSection> catPtrListInfo;
   };
 
   // Information about a pointer list in the original categories (method lists,
@@ -384,8 +384,8 @@ class ObjcCategoryMerger {
     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
+  // Full information about all the categories that extend a class. This will
+  // include all the additional methods, protocols, and properties that are
   // contained in all the categories that extend a particular class.
   struct ClassExtensionInfo {
     ClassExtensionInfo(CategoryLayout &_catLayout) : catLayout(_catLayout){};
@@ -438,7 +438,7 @@ class ObjcCategoryMerger {
           catListToErasedOffsets);
   template <typename T>
   void collectSectionWriteInfoFromIsec(const InputSection *isec,
-                                       InfroWriteSection<T> &catWriteInfo);
+                                       InfoWriteSection<T> &catWriteInfo);
   void collectCategoryWriterInfoFromCategory(const InfoInputCategory &catInfo);
   void parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
                              ClassExtensionInfo &extInfo);
@@ -472,7 +472,12 @@ class ObjcCategoryMerger {
   Defined *tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
                                      uint32_t offset);
   void tryEraseDefinedAtIsecOffset(const ConcatInputSection *isec,
-                                   uint32_t offset, bool stringOnly = false);
+                                   uint32_t offset);
+
+  // Allocate a null-terminated StringRef backed by generatedSectionData
+  StringRef newStringData(const char *str);
+  // Allocate section data, backed by generatedSectionData
+  SmallVector<uint8_t> &newSectionData(uint32_t size);
 
   CategoryLayout catLayout;
   ClassLayout classLayout;
@@ -487,15 +492,13 @@ class ObjcCategoryMerger {
   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.
+  // generating binary data ourselves, we use the below array 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(
@@ -510,7 +513,7 @@ ObjcCategoryMerger::ObjcCategoryMerger(
 // ConcatOutputSection
 template <typename T>
 void ObjcCategoryMerger::collectSectionWriteInfoFromIsec(
-    const InputSection *isec, InfroWriteSection<T> &catWriteInfo) {
+    const InputSection *isec, InfoWriteSection<T> &catWriteInfo) {
 
   catWriteInfo.inputSection = const_cast<Section *>(&isec->section);
   catWriteInfo.align = isec->align;
@@ -543,11 +546,10 @@ ObjcCategoryMerger::tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
   return dyn_cast_or_null<Defined>(sym);
 }
 
-// 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 ...
+// Given an ConcatInputSection or CStringInputSection and an offset, if there is
+// a symbol(Defined) at that offset, then erase the symbol (mark it not live)
 void ObjcCategoryMerger::tryEraseDefinedAtIsecOffset(
-    const ConcatInputSection *isec, uint32_t offset, bool stringOnly) {
+    const ConcatInputSection *isec, uint32_t offset) {
   const Reloc *reloc = isec->getRelocAt(offset);
 
   if (!reloc)
@@ -557,17 +559,14 @@ void ObjcCategoryMerger::tryEraseDefinedAtIsecOffset(
   if (!sym)
     return;
 
-  auto *cisec = dyn_cast_or_null<ConcatInputSection>(sym->isec);
-  if (!stringOnly && cisec) {
+  if (auto *cisec = dyn_cast_or_null<ConcatInputSection>(sym->isec))
     eraseISec(cisec);
-    return;
-  }
-
-  if (auto *cisec = dyn_cast_or_null<CStringInputSection>(sym->isec)) {
+  else if (auto *csisec = dyn_cast_or_null<CStringInputSection>(sym->isec)) {
     uint32_t totalOffset = sym->value + reloc->addend;
-    StringPiece &piece = cisec->getStringPiece(totalOffset);
+    StringPiece &piece = csisec->getStringPiece(totalOffset);
     piece.live = false;
-    return;
+  } else {
+    llvm_unreachable("erased symbol has to be Defined or CStringInputSection");
   }
 }
 
@@ -606,7 +605,7 @@ void ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
   }
 }
 
-// Parse a protocol list that might be linked to at a ConcatInputSection given
+// Parse a protocol list that might be linked to ConcatInputSection at a given
 // offset. The format of the protocol list is different than other lists (prop
 // lists, method lists) so we need to parse it differently
 void ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
@@ -617,13 +616,14 @@ void ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
 
   const Reloc *reloc = isec->getRelocAt(secOffset);
   if (!reloc)
-    return; // List is null, nothing to do
+    return;
 
   auto *ptrListSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
   assert(ptrListSym && "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
+  // Theoretically protocol count can be either 32b or 64b, depending on
+  // platform pointer size, but to simplify implementation we always just read
+  // the lower 32b which should be good enough.
   uint32_t protocolCount = *reinterpret_cast<const uint32_t *>(
       ptrListSym->isec->data.data() + listHeaderLayout.structSizeOffset);
 
@@ -638,7 +638,7 @@ void ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
          "Protocol list does not match expected size");
 
   uint32_t off = protocolListHeaderLayout.totalSize;
-  for (uint32_t inx = 0; inx < protocolCount; inx++) {
+  for (uint32_t inx = 0; inx < protocolCount; ++inx) {
     const Reloc *reloc = ptrListSym->isec->getRelocAt(off);
     assert(reloc && "No reloc found at protocol list offset");
 
@@ -648,9 +648,13 @@ void ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
     ptrList.allPtrs.push_back(listSym);
     off += target->wordSize;
   }
+  assert((ptrListSym->isec->getRelocAt(off) == nullptr) &&
+         "expected null terminating protocol");
+  assert(off + /*extra null value*/ target->wordSize == expectedListSize &&
+         "Protocol list end offset does not match expected size");
 }
 
-// Parse a pointer list that might be linked to at a ConcatInputSection given
+// Parse a pointer list that might be linked to ConcatInputSection at a given
 // offset. This can be used for instance methods, class methods, instance props
 // and class props since they have the same format.
 void ObjcCategoryMerger::parsePointerListInfo(const ConcatInputSection *isec,
@@ -663,7 +667,7 @@ void ObjcCategoryMerger::parsePointerListInfo(const ConcatInputSection *isec,
 
   const Reloc *reloc = isec->getRelocAt(secOffset);
   if (!reloc)
-    return; // No reloc found, nothing to parse
+    return;
 
   auto *ptrListSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
   assert(ptrListSym && "Reloc does not have a valid Defined");
@@ -708,12 +712,12 @@ void ObjcCategoryMerger::parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
   assert(catNameReloc && "Category does not have a reloc at 'nameOffset'");
 
   // is this the first category we are parsing?
-  if (extInfo.mergedContainerName.empty()) {
+  if (extInfo.mergedContainerName.empty())
     extInfo.objFileForMergeData =
         dyn_cast_or_null<ObjFile>(catInfo.catBodyIsec->getFile());
-  } else {
+  else
     extInfo.mergedContainerName += "|";
-  }
+
   assert(extInfo.objFileForMergeData &&
          "Expected to already have valid objextInfo.objFileForMergeData");
 
@@ -767,8 +771,7 @@ void ObjcCategoryMerger::emitAndLinkProtocolList(
   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();
+  llvm::ArrayRef<uint8_t> bodyData = newSectionData(bodySize);
 
   // This theoretically can be either 32b or 64b, but writing just the first 32b
   // is good enough
@@ -786,16 +789,15 @@ void ObjcCategoryMerger::emitAndLinkProtocolList(
 
   listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
 
-  generatedNames.push_back(StringRef(ptrList.categoryPrefix));
-  auto &symName = generatedNames.back();
+  std::string symName = ptrList.categoryPrefix;
   symName += extInfo.baseClassName + "_$_(" + extInfo.mergedContainerName + ")";
 
   Defined *ptrListSym = make<Defined>(
-      symName.c_str(), /*file=*/parentSym->getObjectFile(), listSec,
-      /*value=*/0, bodyData.size(),
-      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
-      /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
-      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+      newStringData(symName.c_str()), /*file=*/parentSym->getObjectFile(),
+      listSec, /*value=*/0, bodyData.size(), /*isWeakDef=*/false,
+      /*isExternal=*/false, /*isPrivateExtern=*/false, /*includeInSymtab=*/true,
+      /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
+      /*isWeakDefCanBeHidden=*/false);
 
   ptrListSym->used = true;
   parentSym->getObjectFile()->symbols.push_back(ptrListSym);
@@ -826,8 +828,7 @@ void ObjcCategoryMerger::emitAndLinkPointerList(
   // 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();
+  llvm::ArrayRef<uint8_t> bodyData = newSectionData(bodySize);
 
   const uint32_t *ptrStructSize = reinterpret_cast<const uint32_t *>(
       bodyData.data() + listHeaderLayout.structSizeOffset);
@@ -846,16 +847,15 @@ void ObjcCategoryMerger::emitAndLinkPointerList(
 
   listSec->parent = infoCategoryWriter.catPtrListInfo.outputSection;
 
-  generatedNames.push_back(StringRef(ptrList.categoryPrefix));
-  auto &symName = generatedNames.back();
+  std::string symName = ptrList.categoryPrefix;
   symName += extInfo.baseClassName + "_$_" + extInfo.mergedContainerName;
 
   Defined *ptrListSym = make<Defined>(
-      symName.c_str(), /*file=*/parentSym->getObjectFile(), listSec,
-      /*value=*/0, bodyData.size(),
-      /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
-      /*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
-      /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
+      newStringData(symName.c_str()), /*file=*/parentSym->getObjectFile(),
+      listSec, /*value=*/0, bodyData.size(), /*isWeakDef=*/false,
+      /*isExternal=*/false, /*isPrivateExtern=*/false, /*includeInSymtab=*/true,
+      /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
+      /*isWeakDefCanBeHidden=*/false);
 
   ptrListSym->used = true;
   parentSym->getObjectFile()->symbols.push_back(ptrListSym);
@@ -877,8 +877,7 @@ ObjcCategoryMerger::emitCatListEntrySec(const std::string &forCateogryName,
                                         const std::string &forBaseClassName,
                                         ObjFile *objFile) {
   uint32_t sectionSize = target->wordSize;
-  generatedSectionData.push_back(SmallVector<uint8_t>(sectionSize, 0));
-  llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+  llvm::ArrayRef<uint8_t> bodyData = newSectionData(sectionSize);
 
   ConcatInputSection *newCatList =
       make<ConcatInputSection>(*infoCategoryWriter.catListInfo.inputSection,
@@ -889,13 +888,11 @@ ObjcCategoryMerger::emitCatListEntrySec(const std::string &forCateogryName,
 
   newCatList->parent = infoCategoryWriter.catListInfo.outputSection;
 
-  SmallString<0> catSymName;
-  catSymName += "<__objc_catlist slot for merged category ";
+  std::string catSymName = "<__objc_catlist slot for merged category ";
   catSymName += forBaseClassName + "(" + forCateogryName + ")>";
-  generatedNames.push_back(StringRef(catSymName));
 
   Defined *catListSym = make<Defined>(
-      StringRef(generatedNames.back()), /*file=*/objFile, newCatList,
+      newStringData(catSymName.c_str()), /*file=*/objFile, newCatList,
       /*value=*/0, bodyData.size(), /*isWeakDef=*/false, /*isExternal=*/false,
       /*isPrivateExtern=*/false, /*includeInSymtab=*/false,
       /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
@@ -914,8 +911,7 @@ Defined *ObjcCategoryMerger::emitCategoryBody(const std::string &name,
                                               const Symbol *baseClassSym,
                                               const std::string &baseClassName,
                                               ObjFile *objFile) {
-  generatedSectionData.push_back(SmallVector<uint8_t>(catLayout.totalSize, 0));
-  llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+  llvm::ArrayRef<uint8_t> bodyData = newSectionData(catLayout.totalSize);
 
   uint32_t *ptrSize = (uint32_t *)(const_cast<uint8_t *>(bodyData.data()) +
                                    catLayout.sizeOffset);
@@ -928,13 +924,10 @@ Defined *ObjcCategoryMerger::emitCategoryBody(const std::string &name,
   newBodySec->live = true;
   allInputSections.push_back(newBodySec);
 
-  newBodySec->parent = infoCategoryWriter.catBodyInfo.outputSection;
-
   std::string symName =
       objc::symbol_names::category + baseClassName + "_$_(" + name + ")";
-  generatedNames.push_back(StringRef(symName));
   Defined *catBodySym = make<Defined>(
-      StringRef(generatedNames.back()), /*file=*/objFile, newBodySec,
+      newStringData(symName.c_str()), /*file=*/objFile, newBodySec,
       /*value=*/0, bodyData.size(), /*isWeakDef=*/false, /*isExternal=*/false,
       /*isPrivateExtern=*/false, /*includeInSymtab=*/true,
       /*isReferencedDynamically=*/false, /*noDeadStrip=*/false,
@@ -957,16 +950,16 @@ Defined *ObjcCategoryMerger::emitCategoryBody(const std::string &name,
 // and returns the sybmol for it.
 Defined *ObjcCategoryMerger::emitCategoryName(const std::string &name,
                                               ObjFile *objFile) {
-  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();
+  StringRef nameStrData = newStringData(name.c_str());
+  // We use +1 below to include the null terminator
+  llvm::ArrayRef<uint8_t> nameData(
+      reinterpret_cast<const uint8_t *>(nameStrData.data()),
+      nameStrData.size() + 1);
 
   auto *parentSection = infoCategoryWriter.catNameInfo.inputSection;
   CStringInputSection *newStringSec = make<CStringInputSection>(
       *infoCategoryWriter.catNameInfo.inputSection, nameData,
-      infoCategoryWriter.catNameInfo.align, true);
+      infoCategoryWriter.catNameInfo.align, /*dedupLiterals=*/true);
 
   parentSection->subsections.push_back({0, newStringSec});
 
@@ -974,24 +967,25 @@ Defined *ObjcCategoryMerger::emitCategoryName(const std::string &name,
   newStringSec->pieces[0].live = true;
   newStringSec->parent = infoCategoryWriter.catNameInfo.outputSection;
   in.cStringSection->addInput(newStringSec);
+  assert(newStringSec->pieces.size() == 1);
 
-  Defined *catNamdeSym = make<Defined>(
+  Defined *catNameSym = make<Defined>(
       "<merged category name>", /*file=*/objFile, newStringSec,
       /*value=*/0, nameData.size(),
       /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
       /*includeInSymtab=*/false, /*isReferencedDynamically=*/false,
       /*noDeadStrip=*/false, /*isWeakDefCanBeHidden=*/false);
 
-  catNamdeSym->used = true;
-  objFile->symbols.push_back(catNamdeSym);
-  return catNamdeSym;
+  catNameSym->used = true;
+  objFile->symbols.push_back(catNameSym);
+  return catNameSym;
 }
 
 // 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.
+// It creates the category name, body and method/protocol/prop lists and links
+// them all together. Then it creates a new __objc_catlist entry and adds the
+// category to it. Calling this method will fully generate a category which will
+// be available in the final binary.
 Defined *ObjcCategoryMerger::emitCategory(const ClassExtensionInfo &extInfo) {
   Defined *catNameSym = emitCategoryName(extInfo.mergedContainerName,
                                          extInfo.objFileForMergeData);
@@ -1004,8 +998,8 @@ Defined *ObjcCategoryMerger::emitCategory(const ClassExtensionInfo &extInfo) {
       emitCatListEntrySec(extInfo.mergedContainerName, extInfo.baseClassName,
                           extInfo.objFileForMergeData);
 
-  const uint32_t offsetFirstCat = 0;
-  createSymbolReference(catListSym, catBodySym, offsetFirstCat,
+  // Add the single category body to the category list at the offset 0.
+  createSymbolReference(catListSym, catBodySym, /*offset=*/0,
                         infoCategoryWriter.catListInfo.relocTemplate);
 
   emitAndLinkPointerList(catBodySym, catLayout.instanceMethodsOffset, extInfo,
@@ -1040,9 +1034,8 @@ void ObjcCategoryMerger::mergeCategoriesIntoSingleCategory(
   Defined *newCatDef = emitCategory(extInfo);
   assert(newCatDef && "Failed to create a new category");
 
-  for (auto &catInfo : categories) {
+  for (auto &catInfo : categories)
     catInfo.wasMerged = true;
-  }
 }
 
 void ObjcCategoryMerger::createSymbolReference(Defined *refFrom,
@@ -1090,7 +1083,7 @@ void ObjcCategoryMerger::collectAndValidateCategoriesData() {
     }
   }
 
-  for (auto &entry : categoryMap) {
+  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
@@ -1101,12 +1094,11 @@ void ObjcCategoryMerger::collectAndValidateCategoriesData() {
 
       llvm::sort(entry.second, cmpFn);
     }
-  }
 }
 
 // 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
+// erase) only some. There will be some categories that will remain untouched
 // (not erased). For these not erased categories, we generate new __objc_catlist
 // entries since the parent __objc_catlist entry will be erased
 void ObjcCategoryMerger::generateCatListForNonErasedCategories(
@@ -1118,21 +1110,19 @@ void ObjcCategoryMerger::generateCatListForNonErasedCategories(
   // each.
   for (auto &mapEntry : catListToErasedOffsets) {
     ConcatInputSection *catListIsec = mapEntry.first;
-    uint32_t catListIsecOffset = 0;
-    while (catListIsecOffset < catListIsec->data.size()) {
+    for (uint32_t catListIsecOffset = 0;
+         catListIsecOffset < catListIsec->data.size();
+         catListIsecOffset += target->wordSize) {
       // This slot was erased, we can jsut skip it
-      if (mapEntry.second.count(catListIsecOffset)) {
-        catListIsecOffset += target->wordSize;
+      if (mapEntry.second.count(catListIsecOffset))
         continue;
-      }
 
       Defined *nonErasedCatBody =
           tryGetDefinedAtIsecOffset(catListIsec, catListIsecOffset);
       assert(nonErasedCatBody && "Failed to relocate non-deleted category");
 
       // Allocate data for the new __objc_catlist slot
-      generatedSectionData.push_back(SmallVector<uint8_t>(target->wordSize, 0));
-      llvm::ArrayRef<uint8_t> bodyData = generatedSectionData.back();
+      auto bodyData = newSectionData(target->wordSize);
 
       // We mark the __objc_catlist slot as belonging to the same file as the
       // category
@@ -1145,13 +1135,12 @@ void ObjcCategoryMerger::generateCatListForNonErasedCategories(
       listSec->live = true;
       allInputSections.push_back(listSec);
 
-      generatedNames.push_back(StringRef("<__objc_catlist slot for category "));
-      auto &slotSymName = generatedNames.back();
+      std::string slotSymName = "<__objc_catlist slot for category ";
       slotSymName += nonErasedCatBody->getName();
       slotSymName += ">";
 
       Defined *catListSlotSym = make<Defined>(
-          slotSymName.c_str(), /*file=*/objFile, listSec,
+          newStringData(slotSymName.c_str()), /*file=*/objFile, listSec,
           /*value=*/0, bodyData.size(),
           /*isWeakDef=*/false, /*isExternal=*/false, /*isPrivateExtern=*/false,
           /*includeInSymtab=*/false, /*isReferencedDynamically=*/false,
@@ -1163,41 +1152,30 @@ void ObjcCategoryMerger::generateCatListForNonErasedCategories(
       // Now link the category body into the newly created slot
       createSymbolReference(catListSlotSym, nonErasedCatBody, 0,
                             infoCategoryWriter.catListInfo.relocTemplate);
-
-      catListIsecOffset += target->wordSize;
     }
   }
 }
 
 void ObjcCategoryMerger::eraseISec(ConcatInputSection *isec) {
   isec->live = false;
-  for (auto &sym : isec->symbols) {
+  for (auto &sym : isec->symbols)
     sym->used = false;
-  }
 }
 
 // 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.
 void 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;
+      if (catInfo.wasMerged) {
+        eraseISec(catInfo.catListIsec);
+        catListToErasedOffsets[catInfo.catListIsec].insert(
+            catInfo.offCatListIsec);
       }
-      eraseISec(catInfo.catListIsec);
-      catListToErasedOffsets[catInfo.catListIsec].insert(
-          catInfo.offCatListIsec);
     }
   }
 
@@ -1213,8 +1191,7 @@ void ObjcCategoryMerger::eraseMergedCategories() {
         continue;
 
       eraseISec(catInfo.catBodyIsec);
-      tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec, catLayout.nameOffset,
-                                  /*stringOnly=*/true);
+      tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec, catLayout.nameOffset);
       tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec,
                                   catLayout.instanceMethodsOffset);
       tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec,
@@ -1232,20 +1209,28 @@ void ObjcCategoryMerger::eraseMergedCategories() {
 void ObjcCategoryMerger::doMerge() {
   collectAndValidateCategoriesData();
 
-  for (auto &entry : categoryMap) {
-    if (entry.second.size() > 1) {
+  for (auto &entry : categoryMap)
+    if (entry.second.size() > 1)
       // Merge all categories into a new, single category
       mergeCategoriesIntoSingleCategory(entry.second);
-    }
-  }
 
   // Erase all categories that were merged
   eraseMergedCategories();
 }
 
-void ObjcCategoryMerger::doCleanup() {
-  generatedNames.clear();
-  generatedSectionData.clear();
+void ObjcCategoryMerger::doCleanup() { generatedSectionData.clear(); }
+
+StringRef ObjcCategoryMerger::newStringData(const char *str) {
+  uint32_t len = strlen(str);
+  auto &data = newSectionData(len + 1);
+  char *strData = reinterpret_cast<char *>(data.data());
+  strncpy(strData, str, len);
+  return StringRef(strData, len);
+}
+
+SmallVector<uint8_t> &ObjcCategoryMerger::newSectionData(uint32_t size) {
+  generatedSectionData.push_back(SmallVector<uint8_t>(size, 0));
+  return generatedSectionData.back();
 }
 
 } // namespace
diff --git a/lld/MachO/SyntheticSections.cpp b/lld/MachO/SyntheticSections.cpp
index 5b7cb5e74cd60a..a5d66bb4ea0801 100644
--- a/lld/MachO/SyntheticSections.cpp
+++ b/lld/MachO/SyntheticSections.cpp
@@ -1695,8 +1695,7 @@ void CStringSection::finalizeContents() {
 void DeduplicatedCStringSection::finalizeContents() {
   // Find the largest alignment required for each string.
   for (const CStringInputSection *isec : inputs) {
-    for (int i = 0; i < isec->pieces.size(); ++i) {
-      const StringPiece &piece = isec->pieces[i];
+    for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
       if (!piece.live)
         continue;
       auto s = isec->getCachedHashStringRef(i);
@@ -1712,8 +1711,7 @@ void DeduplicatedCStringSection::finalizeContents() {
   // Assign an offset for each string and save it to the corresponding
   // StringPieces for easy access.
   for (CStringInputSection *isec : inputs) {
-    for (int i = 0; i < isec->pieces.size(); ++i) {
-      StringPiece &piece = isec->pieces[i];
+    for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
       if (!piece.live)
         continue;
       auto s = isec->getCachedHashStringRef(i);
diff --git a/llvm/lib/Target/AMDGPU/SIInstrInfo.h b/llvm/lib/Target/AMDGPU/SIInstrInfo.h
index a0fe8d9bb9845c..4200e0c8a29e1c 100644
--- a/llvm/lib/Target/AMDGPU/SIInstrInfo.h
+++ b/llvm/lib/Target/AMDGPU/SIInstrInfo.h
@@ -949,6 +949,8 @@ class SIInstrInfo final : public AMDGPUGenInstrInfo {
       return AMDGPU::S_WAIT_BVHCNT;
     case AMDGPU::S_WAIT_DSCNT_soft:
       return AMDGPU::S_WAIT_DSCNT;
+    case AMDGPU::S_WAIT_KMCNT_soft:
+      return AMDGPU::S_WAIT_KMCNT;
     default:
       return Opcode;
     }

>From 8992adcb049131c62fa0c46ecc33baf23f19e305 Mon Sep 17 00:00:00 2001
From: Alex B <alexborcan at meta.com>
Date: Sun, 10 Mar 2024 13:14:20 -0700
Subject: [PATCH 5/5] [lld-macho] Address Feedback #4

---
 lld/MachO/ObjC.cpp | 20 ++++----------------
 1 file changed, 4 insertions(+), 16 deletions(-)

diff --git a/lld/MachO/ObjC.cpp b/lld/MachO/ObjC.cpp
index 080e597f4602a7..2d1d02062f9de4 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -903,9 +903,7 @@ ObjcCategoryMerger::emitCatListEntrySec(const std::string &forCateogryName,
   return catListSym;
 }
 
-// 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.
+// Here we generate the main category body and link the name and base class into it. We don't link any other info yet like the protocol and class/instance methods/props.
 Defined *ObjcCategoryMerger::emitCategoryBody(const std::string &name,
                                               const Defined *nameSym,
                                               const Symbol *baseClassSym,
@@ -1062,8 +1060,10 @@ void ObjcCategoryMerger::collectAndValidateCategoriesData() {
       Defined *categorySym = tryGetDefinedAtIsecOffset(catListCisec, off);
       assert(categorySym &&
              "Failed to get a valid cateogry at __objc_catlit offset");
+
+      // We only support ObjC categories (no swift + @objc)
       if (!categorySym->getName().starts_with(objc::symbol_names::category))
-        continue; // Only support ObjC categories (no swift + @objc)
+        continue;
 
       auto *catBodyIsec = dyn_cast<ConcatInputSection>(categorySym->isec);
       assert(catBodyIsec &&
@@ -1082,18 +1082,6 @@ void ObjcCategoryMerger::collectAndValidateCategoriesData() {
       collectCategoryWriterInfoFromCategory(catInputInfo);
     }
   }
-
-  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);
-      };
-
-      llvm::sort(entry.second, cmpFn);
-    }
 }
 
 // In the input we have multiple __objc_catlist InputSection, each of which may



More information about the llvm-commits mailing list