[lld-macho] Implement ObjC category merging (-objc_category_merging) (#85727)

This change adds a flag to lld to enable category merging for MachoO +
ObjC.
It adds the '-objc_category_merging' flag for enabling this option and
uses the existing '-no_objc_category_merging' flag for disabling it.
In ld64, this optimization is enabled by default, but in lld, for now,
we require explicitly passing the '-objc_category_merging' flag in order
to enable it.

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. This improved functionality is already present in ld64.
We do the merging on the raw inputSections - after dead-stripping
(categories can't be dead stripped anyway).
The changes are mostly self-contained to ObjC.cpp, except for adding 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 cause the
old symbols to show up in the linker map as being dead-stripped - even
if dead-stripping is disabled. This flag allows us to match the ld64
behavior.

Note: This is a re-land of
https://github.com/llvm/llvm-project/pull/82928 after fixing using
already freed memory in `generatedSectionData`. This issue was detected
by ASAN build.

---------

Co-authored-by: Alex B <alexborcan@meta.com>
This commit is contained in:
alx32 2024-03-19 13:14:29 -07:00 committed by GitHub
parent ffd08c7759
commit cd34860705
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1871 additions and 8 deletions

View File

@ -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]);
@ -1979,9 +1981,16 @@ 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();
// 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();
// ICF assumes that all literals have been folded already, so we must run
// foldIdenticalLiterals before foldIdenticalSections.
foldIdenticalLiterals();

View File

@ -93,9 +93,9 @@ public:
// .subsections_via_symbols, there is typically only one element here.
llvm::TinyPtrVector<Defined *> symbols;
protected:
const Section &section;
protected:
const Defined *getContainingSymbol(uint64_t off) const;
};

View File

@ -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,8 @@ namespace {
DO(Ptr, classMethods) \
DO(Ptr, protocols) \
DO(Ptr, instanceProps) \
DO(Ptr, classProps)
DO(Ptr, classProps) \
DO(uint32_t, size)
CREATE_LAYOUT_CLASS(Category, FOR_EACH_CATEGORY_FIELD);
@ -112,13 +116,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) \
@ -311,6 +321,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 +332,914 @@ void objc::checkCategories() {
}
}
}
namespace {
class ObjcCategoryMerger {
// Information about an input category
struct InfoInputCategory {
ConcatInputSection *catListIsec;
ConcatInputSection *catBodyIsec;
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 InfoWriteSection {
bool valid = false; // Data has been successfully collected from input
uint32_t align = 0;
Section *inputSection;
Reloc relocTemplate;
T *outputSection;
};
struct InfoCategoryWriter {
InfoWriteSection<ConcatOutputSection> catListInfo;
InfoWriteSection<ConcatOutputSection> catBodyInfo;
InfoWriteSection<CStringSection> catNameInfo;
InfoWriteSection<ConcatOutputSection> catPtrListInfo;
};
// Information about a pointer list in the original categories (method lists,
// protocol lists, etc)
struct PointerListInfo {
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;
std::vector<Symbol *> allPtrs;
};
// 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){};
// 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,
/*_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:
ObjcCategoryMerger(std::vector<ConcatInputSection *> &_allInputSections);
void doMerge();
static void doCleanup();
private:
void collectAndValidateCategoriesData();
void
mergeCategoriesIntoSingleCategory(std::vector<InfoInputCategory> &categories);
void eraseISec(ConcatInputSection *isec);
void eraseMergedCategories();
void generateCatListForNonErasedCategories(
std::map<ConcatInputSection *, std::set<uint64_t>>
catListToErasedOffsets);
template <typename T>
void collectSectionWriteInfoFromIsec(const InputSection *isec,
InfoWriteSection<T> &catWriteInfo);
void collectCategoryWriterInfoFromCategory(const InfoInputCategory &catInfo);
void parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
ClassExtensionInfo &extInfo);
void parseProtocolListInfo(const ConcatInputSection *isec, uint32_t secOffset,
PointerListInfo &ptrList);
void parsePointerListInfo(const ConcatInputSection *isec, uint32_t secOffset,
PointerListInfo &ptrList);
void emitAndLinkPointerList(Defined *parentSym, uint32_t linkAtOffset,
const ClassExtensionInfo &extInfo,
const PointerListInfo &ptrList);
void emitAndLinkProtocolList(Defined *parentSym, uint32_t linkAtOffset,
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);
// 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;
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
DenseMap<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 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<std::unique_ptr<SmallVector<uint8_t>>>
generatedSectionData;
};
SmallVector<std::unique_ptr<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) {}
// This is a template so that it can be used both for CStringSection and
// ConcatOutputSection
template <typename T>
void ObjcCategoryMerger::collectSectionWriteInfoFromIsec(
const InputSection *isec, InfoWriteSection<T> &catWriteInfo) {
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];
catWriteInfo.valid = true;
}
Symbol *
ObjcCategoryMerger::tryGetSymbolAtIsecOffset(const ConcatInputSection *isec,
uint32_t offset) {
const Reloc *reloc = isec->getRelocAt(offset);
if (!reloc)
return nullptr;
return reloc->referent.get<Symbol *>();
}
Defined *
ObjcCategoryMerger::tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
uint32_t offset) {
Symbol *sym = tryGetSymbolAtIsecOffset(isec, offset);
return dyn_cast_or_null<Defined>(sym);
}
// 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) {
const Reloc *reloc = isec->getRelocAt(offset);
if (!reloc)
return;
Defined *sym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
if (!sym)
return;
if (auto *cisec = dyn_cast_or_null<ConcatInputSection>(sym->isec))
eraseISec(cisec);
else if (auto *csisec = dyn_cast_or_null<CStringInputSection>(sym->isec)) {
uint32_t totalOffset = sym->value + reloc->addend;
StringPiece &piece = csisec->getStringPiece(totalOffset);
piece.live = false;
} else {
llvm_unreachable("erased symbol has to be Defined or CStringInputSection");
}
}
void ObjcCategoryMerger::collectCategoryWriterInfoFromCategory(
const InfoInputCategory &catInfo) {
if (!infoCategoryWriter.catListInfo.valid)
collectSectionWriteInfoFromIsec<ConcatOutputSection>(
catInfo.catListIsec, infoCategoryWriter.catListInfo);
if (!infoCategoryWriter.catBodyInfo.valid)
collectSectionWriteInfoFromIsec<ConcatOutputSection>(
catInfo.catBodyIsec, infoCategoryWriter.catBodyInfo);
if (!infoCategoryWriter.catNameInfo.valid) {
lld::macho::Defined *catNameSym =
tryGetDefinedAtIsecOffset(catInfo.catBodyIsec, catLayout.nameOffset);
assert(catNameSym && "Category does not have a valid name Symbol");
collectSectionWriteInfoFromIsec<CStringSection>(
catNameSym->isec, infoCategoryWriter.catNameInfo);
}
// 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) {
if (Defined *ptrList =
tryGetDefinedAtIsecOffset(catInfo.catBodyIsec, off)) {
collectSectionWriteInfoFromIsec<ConcatOutputSection>(
ptrList->isec, infoCategoryWriter.catPtrListInfo);
// we've successfully collected data, so we can break
break;
}
}
}
}
// 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,
uint32_t secOffset,
PointerListInfo &ptrList) {
if (!isec || (secOffset + target->wordSize > isec->data.size()))
assert("Tried to read pointer list beyond protocol section end");
const Reloc *reloc = isec->getRelocAt(secOffset);
if (!reloc)
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, 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);
ptrList.structCount += protocolCount;
ptrList.structSize = target->wordSize;
uint32_t expectedListSize =
(protocolCount * target->wordSize) +
/*header(count)*/ protocolListHeaderLayout.totalSize +
/*extra null value*/ target->wordSize;
assert(expectedListSize == ptrListSym->isec->data.size() &&
"Protocol list does not match expected size");
// Suppress unsuded var warning
(void)expectedListSize;
uint32_t off = protocolListHeaderLayout.totalSize;
for (uint32_t inx = 0; inx < protocolCount; ++inx) {
const Reloc *reloc = ptrListSym->isec->getRelocAt(off);
assert(reloc && "No reloc found at protocol list offset");
auto *listSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
assert(listSym && "Protocol list reloc does not have a valid Defined");
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 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,
uint32_t secOffset,
PointerListInfo &ptrList) {
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");
const Reloc *reloc = isec->getRelocAt(secOffset);
if (!reloc)
return;
auto *ptrListSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
assert(ptrListSym && "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(thisStructSize == ptrList.pointersPerStruct * target->wordSize);
assert(!ptrList.structSize || (thisStructSize == ptrList.structSize));
ptrList.structCount += thisStructCount;
ptrList.structSize = thisStructSize;
uint32_t expectedListSize =
listHeaderLayout.totalSize + (thisStructSize * thisStructCount);
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);
assert(reloc && "No reloc found at pointer list offset");
auto *listSym = dyn_cast_or_null<Defined>(reloc->referent.get<Symbol *>());
assert(listSym && "Reloc does not have a valid Defined");
ptrList.allPtrs.push_back(listSym);
}
}
// Here we parse all the information of an input category (catInfo) and
// append the parsed info into the structure which will contain all the
// information about how a class is extended (extInfo)
void ObjcCategoryMerger::parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
ClassExtensionInfo &extInfo) {
const Reloc *catNameReloc =
catInfo.catBodyIsec->getRelocAt(catLayout.nameOffset);
// Parse name
assert(catNameReloc && "Category does not have a reloc at 'nameOffset'");
// 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
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,
extInfo.instanceMethods);
parsePointerListInfo(catInfo.catBodyIsec, catLayout.classMethodsOffset,
extInfo.classMethods);
parseProtocolListInfo(catInfo.catBodyIsec, catLayout.protocolsOffset,
extInfo.protocols);
parsePointerListInfo(catInfo.catBodyIsec, catLayout.instancePropsOffset,
extInfo.instanceProps);
parsePointerListInfo(catInfo.catBodyIsec, catLayout.classPropsOffset,
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,
const ClassExtensionInfo &extInfo, const PointerListInfo &ptrList) {
if (ptrList.allPtrs.empty())
return;
assert(ptrList.allPtrs.size() == ptrList.structCount);
uint32_t bodySize = (ptrList.structCount * target->wordSize) +
/*header(count)*/ protocolListHeaderLayout.totalSize +
/*extra null value*/ target->wordSize;
llvm::ArrayRef<uint8_t> bodyData = newSectionData(bodySize);
// 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;
std::string symName = ptrList.categoryPrefix;
symName += extInfo.baseClassName + "_$_(" + extInfo.mergedContainerName + ")";
Defined *ptrListSym = make<Defined>(
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);
createSymbolReference(parentSym, ptrListSym, linkAtOffset,
infoCategoryWriter.catBodyInfo.relocTemplate);
uint32_t offset = protocolListHeaderLayout.totalSize;
for (Symbol *symbol : ptrList.allPtrs) {
createSymbolReference(ptrListSym, symbol, offset,
infoCategoryWriter.catPtrListInfo.relocTemplate);
offset += target->wordSize;
}
}
// 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,
const ClassExtensionInfo &extInfo, const PointerListInfo &ptrList) {
if (ptrList.allPtrs.empty())
return;
assert(ptrList.allPtrs.size() * target->wordSize ==
ptrList.structCount * ptrList.structSize);
// Generate body
uint32_t bodySize =
listHeaderLayout.totalSize + (ptrList.structSize * ptrList.structCount);
llvm::ArrayRef<uint8_t> bodyData = newSectionData(bodySize);
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;
std::string symName = ptrList.categoryPrefix;
symName += extInfo.baseClassName + "_$_" + extInfo.mergedContainerName;
Defined *ptrListSym = make<Defined>(
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);
createSymbolReference(parentSym, ptrListSym, linkAtOffset,
infoCategoryWriter.catBodyInfo.relocTemplate);
uint32_t offset = listHeaderLayout.totalSize;
for (Symbol *symbol : ptrList.allPtrs) {
createSymbolReference(ptrListSym, symbol, offset,
infoCategoryWriter.catPtrListInfo.relocTemplate);
offset += target->wordSize;
}
}
// This method creates an __objc_catlist ConcatInputSection with a single slot
Defined *
ObjcCategoryMerger::emitCatListEntrySec(const std::string &forCateogryName,
const std::string &forBaseClassName,
ObjFile *objFile) {
uint32_t sectionSize = target->wordSize;
llvm::ArrayRef<uint8_t> bodyData = newSectionData(sectionSize);
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;
std::string catSymName = "<__objc_catlist slot for merged category ";
catSymName += forBaseClassName + "(" + forCateogryName + ")>";
Defined *catListSym = make<Defined>(
newStringData(catSymName.c_str()), /*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 catListSym;
}
// 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,
const std::string &baseClassName,
ObjFile *objFile) {
llvm::ArrayRef<uint8_t> bodyData = newSectionData(catLayout.totalSize);
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);
std::string symName =
objc::symbol_names::category + baseClassName + "_$_(" + name + ")";
Defined *catBodySym = make<Defined>(
newStringData(symName.c_str()), /*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);
createSymbolReference(catBodySym, nameSym, catLayout.nameOffset,
infoCategoryWriter.catBodyInfo.relocTemplate);
// 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.
Defined *ObjcCategoryMerger::emitCategoryName(const std::string &name,
ObjFile *objFile) {
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, /*dedupLiterals=*/true);
parentSection->subsections.push_back({0, newStringSec});
newStringSec->splitIntoPieces();
newStringSec->pieces[0].live = true;
newStringSec->parent = infoCategoryWriter.catNameInfo.outputSection;
in.cStringSection->addInput(newStringSec);
assert(newStringSec->pieces.size() == 1);
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);
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 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);
Defined *catBodySym = emitCategoryBody(
extInfo.mergedContainerName, catNameSym, extInfo.baseClass,
extInfo.baseClassName, extInfo.objFileForMergeData);
Defined *catListSym =
emitCatListEntrySec(extInfo.mergedContainerName, extInfo.baseClassName,
extInfo.objFileForMergeData);
// 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,
extInfo.instanceMethods);
emitAndLinkPointerList(catBodySym, catLayout.classMethodsOffset, extInfo,
extInfo.classMethods);
emitAndLinkProtocolList(catBodySym, catLayout.protocolsOffset, extInfo,
extInfo.protocols);
emitAndLinkPointerList(catBodySym, catLayout.instancePropsOffset, extInfo,
extInfo.instanceProps);
emitAndLinkPointerList(catBodySym, catLayout.classPropsOffset, extInfo,
extInfo.classProps);
return catBodySym;
}
// This method merges all the categories (sharing a base class) into a single
// category.
void ObjcCategoryMerger::mergeCategoriesIntoSingleCategory(
std::vector<InfoInputCategory> &categories) {
assert(categories.size() > 1 && "Expected at least 2 categories");
ClassExtensionInfo extInfo(catLayout);
for (auto &catInfo : categories)
parseCatInfoToExtInfo(catInfo, extInfo);
Defined *newCatDef = emitCategory(extInfo);
assert(newCatDef && "Failed to create a new category");
// Suppress unsuded var warning
(void)newCatDef;
for (auto &catInfo : categories)
catInfo.wasMerged = true;
}
void ObjcCategoryMerger::createSymbolReference(Defined *refFrom,
const Symbol *refTo,
uint32_t offset,
const Reloc &relocTemplate) {
Reloc r = relocTemplate;
r.offset = offset;
r.addend = 0;
r.referent = const_cast<Symbol *>(refTo);
refFrom->isec->relocs.push_back(r);
}
void ObjcCategoryMerger::collectAndValidateCategoriesData() {
for (InputSection *sec : allInputSections) {
if (sec->getName() != section_names::objcCatList)
continue;
ConcatInputSection *catListCisec = dyn_cast<ConcatInputSection>(sec);
assert(catListCisec &&
"__objc_catList InputSection is not a ConcatInputSection");
for (uint32_t off = 0; off < catListCisec->getSize();
off += target->wordSize) {
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)
// TODO: Support swift + @objc categories also
if (!categorySym->getName().starts_with(objc::symbol_names::category))
continue;
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)
Symbol *classSym =
tryGetSymbolAtIsecOffset(catBodyIsec, catLayout.klassOffset);
assert(classSym && "Category does not have a valid base class");
InfoInputCategory catInputInfo{catListCisec, catBodyIsec, off};
categoryMap[classSym].push_back(catInputInfo);
collectCategoryWriterInfoFromCategory(catInputInfo);
}
}
}
// 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 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(
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 __objc_catlist for
// each.
for (auto &mapEntry : catListToErasedOffsets) {
ConcatInputSection *catListIsec = mapEntry.first;
for (uint32_t catListIsecOffset = 0;
catListIsecOffset < catListIsec->data.size();
catListIsecOffset += target->wordSize) {
// This slot was erased, we can just skip it
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
auto bodyData = newSectionData(target->wordSize);
// 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);
std::string slotSymName = "<__objc_catlist slot for category ";
slotSymName += nonErasedCatBody->getName();
slotSymName += ">";
Defined *catListSlotSym = make<Defined>(
newStringData(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
createSymbolReference(catListSlotSym, nonErasedCatBody, 0,
infoCategoryWriter.catListInfo.relocTemplate);
}
}
}
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.
void ObjcCategoryMerger::eraseMergedCategories() {
// 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) {
eraseISec(catInfo.catListIsec);
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.
generateCatListForNonErasedCategories(catListToErasedOffsets);
// 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;
eraseISec(catInfo.catBodyIsec);
tryEraseDefinedAtIsecOffset(catInfo.catBodyIsec, catLayout.nameOffset);
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);
}
}
}
void ObjcCategoryMerger::doMerge() {
collectAndValidateCategoriesData();
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() { 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(
std::make_unique<SmallVector<uint8_t>>(size, 0));
return *generatedSectionData.back();
}
} // namespace
void objc::mergeCategories() {
TimeTraceScope timeScope("ObjcCategoryMerger");
ObjcCategoryMerger merger(inputSections);
merger.doMerge();
}
void objc::doCleanup() { ObjcCategoryMerger::doCleanup(); }

View File

@ -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);

View File

@ -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,10 +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 no_objc_category_merging : Flag<["-"], "no_objc_category_merging">,
HelpText<"Do not merge Objective-C categories into their classes">,
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">,

View File

@ -0,0 +1,762 @@
# 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-apple-macos -o a64_file1.o a64_file1.s
# RUN: %lld -arch arm64 a64_file1.o -o a64_file1.dylib -dylib
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o a64_file2.o a64_file2.s
# RUN: %lld -arch arm64 -o a64_file2_no_merge.exe a64_file1.dylib a64_file2.o
# RUN: %lld -arch arm64 -o a64_file2_merge.exe -objc_category_merging a64_file1.dylib a64_file2.o
# 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-NEXT: name {{.*}} 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@0:8
MERGE_CATS-NEXT: imp -[MyBaseClass(Category02) class02InstanceMethod]
MERGE_CATS-NEXT: name {{.*}} myProtocol02Method
MERGE_CATS-NEXT: types {{.*}} v16@0:8
MERGE_CATS-NEXT: imp -[MyBaseClass(Category02) myProtocol02Method]
MERGE_CATS-NEXT: name {{.*}} class03InstanceMethod
MERGE_CATS-NEXT: types {{.*}} v16@0:8
MERGE_CATS-NEXT: imp -[MyBaseClass(Category03) class03InstanceMethod]
MERGE_CATS-NEXT: name {{.*}} myProtocol03Method
MERGE_CATS-NEXT: types {{.*}} v16@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@0:8
MERGE_CATS-NEXT: imp +[MyBaseClass(Category02) class02ClassMethod]
MERGE_CATS-NEXT: name {{.*}} MyProtocol02Prop
MERGE_CATS-NEXT: types {{.*}} i16@0:8
MERGE_CATS-NEXT: imp +[MyBaseClass(Category02) MyProtocol02Prop]
MERGE_CATS-NEXT: name {{.*}} class03ClassMethod
MERGE_CATS-NEXT: types {{.*}} v16@0:8
MERGE_CATS-NEXT: imp +[MyBaseClass(Category03) class03ClassMethod]
MERGE_CATS-NEXT: name {{.*}} MyProtocol03Prop
MERGE_CATS-NEXT: types {{.*}} i16@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@0:8
MERGE_CATS-NEXT: imp 0x0
MERGE_CATS-NEXT: name {{.*}} MyProtocol02Prop
MERGE_CATS-NEXT: types {{.*}} i16@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@0:8
MERGE_CATS-NEXT: imp 0x0
MERGE_CATS-NEXT: name {{.*}} MyProtocol03Prop
MERGE_CATS-NEXT: types {{.*}} i16@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
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
.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@PAGE
Lloh1:
ldrsw x8, [x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop@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@PAGE
Lloh3:
ldrsw x8, [x8, _OBJC_IVAR_$_MyBaseClass.MyProtocol01Prop@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@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@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@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
.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@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@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

View File

@ -0,0 +1,155 @@
# 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-apple-macos -o a64_fakedylib.o a64_fakedylib.s
# RUN: %lld -arch arm64 a64_fakedylib.o -o a64_fakedylib.dylib -dylib
## Create our main testing dylib - linking against the fake dylib above
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o merge_cat_minimal.o merge_cat_minimal.s
# RUN: %lld -arch arm64 -dylib -o merge_cat_minimal_no_merge.dylib a64_fakedylib.dylib merge_cat_minimal.o
# RUN: %lld -arch arm64 -dylib -o merge_cat_minimal_merge.dylib -objc_category_merging a64_fakedylib.dylib merge_cat_minimal.o
## 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-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@0:8
MERGE_CATS-NEXT: imp -[MyBaseClass(Category01) cat01_InstanceMethod]
MERGE_CATS-NEXT: name {{.*}} cat02_InstanceMethod
MERGE_CATS-NEXT: types {{.*}} v16@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
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
.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@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