[lld] b963931 - [lld-macho][ObjC] Implement category merging into base class (#92448)
via llvm-commits
llvm-commits at lists.llvm.org
Tue May 28 10:21:26 PDT 2024
Author: alx32
Date: 2024-05-28T10:21:22-07:00
New Revision: b963931eb8bda810e2a8ad08832402993b931d69
URL: https://github.com/llvm/llvm-project/commit/b963931eb8bda810e2a8ad08832402993b931d69
DIFF: https://github.com/llvm/llvm-project/commit/b963931eb8bda810e2a8ad08832402993b931d69.diff
LOG: [lld-macho][ObjC] Implement category merging into base class (#92448)
Currently category merging only supports merging multiple categories
into one. With this commit we add the ability to fully merge categories
into the base class, if the base class is included in the current
module. This is the optimal approach for defined classes.
Added:
lld/test/MachO/objc-category-merging-minimal.s
Modified:
lld/MachO/ObjC.cpp
lld/test/MachO/objc-category-merging-complete-test.s
Removed:
lld/test/MachO/objc-category-merging-extern-class-minimal.s
################################################################################
diff --git a/lld/MachO/ObjC.cpp b/lld/MachO/ObjC.cpp
index 9d1612beae872..635ded554497b 100644
--- a/lld/MachO/ObjC.cpp
+++ b/lld/MachO/ObjC.cpp
@@ -379,12 +379,21 @@ class ObjcCategoryMerger {
InfoWriteSection catPtrListInfo;
};
- // Information about a pointer list in the original categories (method lists,
- // protocol lists, etc)
+ // Information about a pointer list in the original categories or class(method
+ // lists, protocol lists, etc)
struct PointerListInfo {
+ PointerListInfo() = default;
+ PointerListInfo(const PointerListInfo &) = default;
PointerListInfo(const char *_categoryPrefix, uint32_t _pointersPerStruct)
: categoryPrefix(_categoryPrefix),
pointersPerStruct(_pointersPerStruct) {}
+
+ inline bool operator==(const PointerListInfo &cmp) {
+ return pointersPerStruct == cmp.pointersPerStruct &&
+ structSize == cmp.structSize && structCount == cmp.structCount &&
+ allPtrs == cmp.allPtrs;
+ }
+
const char *categoryPrefix;
uint32_t pointersPerStruct = 0;
@@ -395,9 +404,9 @@ class ObjcCategoryMerger {
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.
+ // Full information describing an ObjC class . This will include all the
+ // additional methods, protocols, and properties that are contained in the
+ // class and all the categories that extend a particular class.
struct ClassExtensionInfo {
ClassExtensionInfo(CategoryLayout &_catLayout) : catLayout(_catLayout){};
@@ -449,6 +458,9 @@ class ObjcCategoryMerger {
void parseProtocolListInfo(const ConcatInputSection *isec, uint32_t secOffset,
PointerListInfo &ptrList);
+ PointerListInfo parseProtocolListInfo(const ConcatInputSection *isec,
+ uint32_t secOffset);
+
void parsePointerListInfo(const ConcatInputSection *isec, uint32_t secOffset,
PointerListInfo &ptrList);
@@ -456,9 +468,9 @@ class ObjcCategoryMerger {
const ClassExtensionInfo &extInfo,
const PointerListInfo &ptrList);
- void emitAndLinkProtocolList(Defined *parentSym, uint32_t linkAtOffset,
- const ClassExtensionInfo &extInfo,
- const PointerListInfo &ptrList);
+ Defined *emitAndLinkProtocolList(Defined *parentSym, uint32_t linkAtOffset,
+ const ClassExtensionInfo &extInfo,
+ const PointerListInfo &ptrList);
Defined *emitCategory(const ClassExtensionInfo &extInfo);
Defined *emitCatListEntrySec(const std::string &forCategoryName,
@@ -474,6 +486,10 @@ class ObjcCategoryMerger {
uint32_t offset);
Defined *tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
uint32_t offset);
+ Defined *getClassRo(const Defined *classSym, bool getMetaRo);
+ void mergeCategoriesIntoBaseClass(const Defined *baseClass,
+ std::vector<InfoInputCategory> &categories);
+ void eraseSymbolAtIsecOffset(ConcatInputSection *isec, uint32_t offset);
void tryEraseDefinedAtIsecOffset(const ConcatInputSection *isec,
uint32_t offset);
@@ -552,6 +568,29 @@ ObjcCategoryMerger::tryGetDefinedAtIsecOffset(const ConcatInputSection *isec,
return dyn_cast_or_null<Defined>(sym);
}
+// Get the class's ro_data symbol. If getMetaRo is true, then we will return
+// the meta-class's ro_data symbol. Otherwise, we will return the class
+// (instance) ro_data symbol.
+Defined *ObjcCategoryMerger::getClassRo(const Defined *classSym,
+ bool getMetaRo) {
+ ConcatInputSection *isec = dyn_cast<ConcatInputSection>(classSym->isec());
+ if (!isec)
+ return nullptr;
+
+ if (!getMetaRo)
+ return tryGetDefinedAtIsecOffset(isec, classLayout.roDataOffset +
+ classSym->value);
+
+ Defined *metaClass = tryGetDefinedAtIsecOffset(
+ isec, classLayout.metaClassOffset + classSym->value);
+ if (!metaClass)
+ return nullptr;
+
+ return tryGetDefinedAtIsecOffset(
+ dyn_cast<ConcatInputSection>(metaClass->isec()),
+ classLayout.roDataOffset);
+}
+
// 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(
@@ -663,6 +702,15 @@ void ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
"Protocol list end offset does not match expected size");
}
+// Parse a protocol list and return the PointerListInfo for it
+ObjcCategoryMerger::PointerListInfo
+ObjcCategoryMerger::parseProtocolListInfo(const ConcatInputSection *isec,
+ uint32_t secOffset) {
+ PointerListInfo ptrList;
+ parseProtocolListInfo(isec, secOffset, ptrList);
+ return ptrList;
+}
+
// 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.
@@ -769,11 +817,11 @@ void ObjcCategoryMerger::parseCatInfoToExtInfo(const InfoInputCategory &catInfo,
// Generate a protocol list (including header) and link it into the parent at
// the specified offset.
-void ObjcCategoryMerger::emitAndLinkProtocolList(
+Defined *ObjcCategoryMerger::emitAndLinkProtocolList(
Defined *parentSym, uint32_t linkAtOffset,
const ClassExtensionInfo &extInfo, const PointerListInfo &ptrList) {
if (ptrList.allPtrs.empty())
- return;
+ return nullptr;
assert(ptrList.allPtrs.size() == ptrList.structCount);
@@ -820,6 +868,8 @@ void ObjcCategoryMerger::emitAndLinkProtocolList(
infoCategoryWriter.catPtrListInfo.relocTemplate);
offset += target->wordSize;
}
+
+ return ptrListSym;
}
// Generate a pointer list (including header) and link it into the parent at the
@@ -1265,10 +1315,15 @@ void ObjcCategoryMerger::removeRefsToErasedIsecs() {
void ObjcCategoryMerger::doMerge() {
collectAndValidateCategoriesData();
- for (auto &entry : categoryMap)
- if (entry.second.size() > 1)
+ for (auto &[baseClass, catInfos] : categoryMap) {
+ if (auto *baseClassDef = dyn_cast<Defined>(baseClass)) {
+ // Merge all categories into the base class
+ mergeCategoriesIntoBaseClass(baseClassDef, catInfos);
+ } else if (catInfos.size() > 1) {
// Merge all categories into a new, single category
- mergeCategoriesIntoSingleCategory(entry.second);
+ mergeCategoriesIntoSingleCategory(catInfos);
+ }
+ }
// Erase all categories that were merged
eraseMergedCategories();
@@ -1302,3 +1357,101 @@ void objc::mergeCategories() {
}
void objc::doCleanup() { ObjcCategoryMerger::doCleanup(); }
+
+void ObjcCategoryMerger::mergeCategoriesIntoBaseClass(
+ const Defined *baseClass, std::vector<InfoInputCategory> &categories) {
+ assert(categories.size() >= 1 && "Expected at least one category to merge");
+
+ // Collect all the info from the categories
+ ClassExtensionInfo extInfo(catLayout);
+ for (auto &catInfo : categories) {
+ parseCatInfoToExtInfo(catInfo, extInfo);
+ }
+
+ // Get metadata for the base class
+ Defined *metaRo = getClassRo(baseClass, /*getMetaRo=*/true);
+ ConcatInputSection *metaIsec = dyn_cast<ConcatInputSection>(metaRo->isec());
+ Defined *classRo = getClassRo(baseClass, /*getMetaRo=*/false);
+ ConcatInputSection *classIsec = dyn_cast<ConcatInputSection>(classRo->isec());
+
+ // Now collect the info from the base class from the various lists in the
+ // class metadata
+
+ // Protocol lists are a special case - the same protocol list is in classRo
+ // and metaRo, so we only need to parse it once
+ parseProtocolListInfo(classIsec, roClassLayout.baseProtocolsOffset,
+ extInfo.protocols);
+
+ // Check that the classRo and metaRo protocol lists are identical
+ assert(
+ parseProtocolListInfo(classIsec, roClassLayout.baseProtocolsOffset) ==
+ parseProtocolListInfo(metaIsec, roClassLayout.baseProtocolsOffset) &&
+ "Category merger expects classRo and metaRo to have the same protocol "
+ "list");
+
+ parsePointerListInfo(metaIsec, roClassLayout.baseMethodsOffset,
+ extInfo.classMethods);
+ parsePointerListInfo(classIsec, roClassLayout.baseMethodsOffset,
+ extInfo.instanceMethods);
+
+ parsePointerListInfo(metaIsec, roClassLayout.basePropertiesOffset,
+ extInfo.classProps);
+ parsePointerListInfo(classIsec, roClassLayout.basePropertiesOffset,
+ extInfo.instanceProps);
+
+ // Erase the old lists - these will be generated and replaced
+ eraseSymbolAtIsecOffset(metaIsec, roClassLayout.baseMethodsOffset);
+ eraseSymbolAtIsecOffset(metaIsec, roClassLayout.baseProtocolsOffset);
+ eraseSymbolAtIsecOffset(metaIsec, roClassLayout.basePropertiesOffset);
+ eraseSymbolAtIsecOffset(classIsec, roClassLayout.baseMethodsOffset);
+ eraseSymbolAtIsecOffset(classIsec, roClassLayout.baseProtocolsOffset);
+ eraseSymbolAtIsecOffset(classIsec, roClassLayout.basePropertiesOffset);
+
+ // Emit the newly merged lists - first into the meta RO then into the class RO
+ // First we emit and link the protocol list into the meta RO. Then we link it
+ // in the classRo as well (they're supposed to be identical)
+ if (Defined *protoListSym =
+ emitAndLinkProtocolList(metaRo, roClassLayout.baseProtocolsOffset,
+ extInfo, extInfo.protocols)) {
+ createSymbolReference(classRo, protoListSym,
+ roClassLayout.baseProtocolsOffset,
+ infoCategoryWriter.catBodyInfo.relocTemplate);
+ }
+
+ emitAndLinkPointerList(metaRo, roClassLayout.baseMethodsOffset, extInfo,
+ extInfo.classMethods);
+ emitAndLinkPointerList(classRo, roClassLayout.baseMethodsOffset, extInfo,
+ extInfo.instanceMethods);
+
+ emitAndLinkPointerList(metaRo, roClassLayout.basePropertiesOffset, extInfo,
+ extInfo.classProps);
+
+ emitAndLinkPointerList(classRo, roClassLayout.basePropertiesOffset, extInfo,
+ extInfo.instanceProps);
+
+ // Mark all the categories as merged - this will be used to erase them later
+ for (auto &catInfo : categories)
+ catInfo.wasMerged = true;
+}
+
+// Erase the symbol at a given offset in an InputSection
+void ObjcCategoryMerger::eraseSymbolAtIsecOffset(ConcatInputSection *isec,
+ uint32_t offset) {
+ Defined *sym = tryGetDefinedAtIsecOffset(isec, offset);
+ if (!sym)
+ return;
+
+ // Remove the symbol from isec->symbols
+ assert(isa<Defined>(sym) && "Can only erase a Defined");
+ llvm::erase(isec->symbols, sym);
+
+ // Remove the relocs that refer to this symbol
+ auto removeAtOff = [offset](Reloc const &r) { return r.offset == offset; };
+ llvm::erase_if(isec->relocs, removeAtOff);
+
+ // Now, if the symbol fully occupies a ConcatInputSection, we can also erase
+ // the whole ConcatInputSection
+ if (ConcatInputSection *cisec = dyn_cast<ConcatInputSection>(sym->isec()))
+ if (cisec->data.size() == sym->size)
+ eraseISec(cisec);
+}
diff --git a/lld/test/MachO/objc-category-merging-complete-test.s b/lld/test/MachO/objc-category-merging-complete-test.s
index 74400177b550d..cf3e19e2f9c8b 100644
--- a/lld/test/MachO/objc-category-merging-complete-test.s
+++ b/lld/test/MachO/objc-category-merging-complete-test.s
@@ -1,6 +1,7 @@
# REQUIRES: aarch64
# RUN: rm -rf %t; split-file %s %t && cd %t
+############ Test merging multiple categories into a single category ############
## 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
@@ -12,6 +13,10 @@
# 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
+############ Test merging multiple categories into the base class ############
+# RUN: %lld -arch arm64 -o a64_file2_merge_into_class.exe -objc_category_merging a64_file1.o a64_file2.o
+# RUN: llvm-objdump --objc-meta-data --macho a64_file2_merge_into_class.exe | FileCheck %s --check-prefixes=MERGE_CATS_CLS
+
MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass(Category02|Category03)
MERGE_CATS-NEXT: name {{.*}} Category02|Category03
@@ -101,6 +106,211 @@ NO_MERGE_CATS-NEXT: 24
NO_MERGE_CATS-NEXT: 2
+MERGE_CATS_CLS: _OBJC_CLASS_$_MyBaseClass
+MERGE_CATS_CLS-NEXT: isa {{.*}} _OBJC_METACLASS_$_MyBaseClass
+MERGE_CATS_CLS-NEXT: superclass 0x0
+MERGE_CATS_CLS-NEXT: cache {{.*}} __objc_empty_cache
+MERGE_CATS_CLS-NEXT: vtable 0x0
+MERGE_CATS_CLS-NEXT: data {{.*}} (struct class_ro_t *)
+MERGE_CATS_CLS-NEXT: flags 0x2 RO_ROOT
+MERGE_CATS_CLS-NEXT: instanceStart 0
+MERGE_CATS_CLS-NEXT: instanceSize 4
+MERGE_CATS_CLS-NEXT: reserved 0x0
+MERGE_CATS_CLS-NEXT: ivarLayout 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyBaseClass
+MERGE_CATS_CLS-NEXT: baseMethods {{.*}} (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: entsize 24
+MERGE_CATS_CLS-NEXT: count 8
+MERGE_CATS_CLS-NEXT: name {{.*}} class02InstanceMethod
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp -[MyBaseClass(Category02) class02InstanceMethod]
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol02Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp -[MyBaseClass(Category02) myProtocol02Method]
+MERGE_CATS_CLS-NEXT: name {{.*}} class03InstanceMethod
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp -[MyBaseClass(Category03) class03InstanceMethod]
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol03Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp -[MyBaseClass(Category03) myProtocol03Method]
+MERGE_CATS_CLS-NEXT: name {{.*}} baseInstanceMethod
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp -[MyBaseClass baseInstanceMethod]
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol01Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp -[MyBaseClass myProtocol01Method]
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp -[MyBaseClass MyProtocol01Prop]
+MERGE_CATS_CLS-NEXT: name {{.*}} setMyProtocol01Prop:
+MERGE_CATS_CLS-NEXT: types {{.*}} v20 at 0:8i16
+MERGE_CATS_CLS-NEXT: imp -[MyBaseClass setMyProtocol01Prop:]
+MERGE_CATS_CLS-NEXT: baseProtocols {{.*}}
+MERGE_CATS_CLS-NEXT: count 3
+MERGE_CATS_CLS-NEXT: list[0] {{.*}} (struct protocol_t *)
+MERGE_CATS_CLS-NEXT: isa 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02
+MERGE_CATS_CLS-NEXT: protocols 0x0
+MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: entsize 24
+MERGE_CATS_CLS-NEXT: count 2
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol02Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0
+MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0
+MERGE_CATS_CLS-NEXT: instanceProperties {{.*}}
+MERGE_CATS_CLS-NEXT: list[1] {{.*}} (struct protocol_t *)
+MERGE_CATS_CLS-NEXT: isa 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03
+MERGE_CATS_CLS-NEXT: protocols 0x0
+MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: entsize 24
+MERGE_CATS_CLS-NEXT: count 2
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol03Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0
+MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0
+MERGE_CATS_CLS-NEXT: instanceProperties {{.*}}
+MERGE_CATS_CLS-NEXT: list[2] {{.*}} (struct protocol_t *)
+MERGE_CATS_CLS-NEXT: isa 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01
+MERGE_CATS_CLS-NEXT: protocols 0x0
+MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: entsize 24
+MERGE_CATS_CLS-NEXT: count 3
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol01Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} setMyProtocol01Prop:
+MERGE_CATS_CLS-NEXT: types {{.*}} v20 at 0:8i16
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0
+MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0
+MERGE_CATS_CLS-NEXT: instanceProperties {{.*}}
+MERGE_CATS_CLS-NEXT: ivars {{.*}}
+MERGE_CATS_CLS-NEXT: entsize 32
+MERGE_CATS_CLS-NEXT: count 1
+MERGE_CATS_CLS-NEXT: offset {{.*}} 0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop
+MERGE_CATS_CLS-NEXT: type {{.*}} i
+MERGE_CATS_CLS-NEXT: alignment 2
+MERGE_CATS_CLS-NEXT: size 4
+MERGE_CATS_CLS-NEXT: weakIvarLayout 0x0
+MERGE_CATS_CLS-NEXT: baseProperties {{.*}}
+MERGE_CATS_CLS-NEXT: entsize 16
+MERGE_CATS_CLS-NEXT: count 3
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02Prop
+MERGE_CATS_CLS-NEXT: attributes {{.*}} Ti,R,D
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03Prop
+MERGE_CATS_CLS-NEXT: attributes {{.*}} Ti,R,D
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop
+MERGE_CATS_CLS-NEXT: attributes {{.*}} Ti,N,VMyProtocol01Prop
+MERGE_CATS_CLS-NEXT: Meta Class
+MERGE_CATS_CLS-NEXT: isa {{.*}} _OBJC_METACLASS_$_MyBaseClass
+MERGE_CATS_CLS-NEXT: superclass {{.*}} _OBJC_CLASS_$_MyBaseClass
+MERGE_CATS_CLS-NEXT: cache {{.*}} __objc_empty_cache
+MERGE_CATS_CLS-NEXT: vtable 0x0
+MERGE_CATS_CLS-NEXT: data {{.*}} (struct class_ro_t *)
+MERGE_CATS_CLS-NEXT: flags 0x3 RO_META RO_ROOT
+MERGE_CATS_CLS-NEXT: instanceStart 40
+MERGE_CATS_CLS-NEXT: instanceSize 40
+MERGE_CATS_CLS-NEXT: reserved 0x0
+MERGE_CATS_CLS-NEXT: ivarLayout 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyBaseClass
+MERGE_CATS_CLS-NEXT: baseMethods {{.*}} (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: entsize 24
+MERGE_CATS_CLS-NEXT: count 5
+MERGE_CATS_CLS-NEXT: name {{.*}} class02ClassMethod
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp +[MyBaseClass(Category02) class02ClassMethod]
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp +[MyBaseClass(Category02) MyProtocol02Prop]
+MERGE_CATS_CLS-NEXT: name {{.*}} class03ClassMethod
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp +[MyBaseClass(Category03) class03ClassMethod]
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp +[MyBaseClass(Category03) MyProtocol03Prop]
+MERGE_CATS_CLS-NEXT: name {{.*}} baseClassMethod
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp +[MyBaseClass baseClassMethod]
+MERGE_CATS_CLS-NEXT: baseProtocols {{.*}}
+MERGE_CATS_CLS-NEXT: count 3
+MERGE_CATS_CLS-NEXT: list[0] {{.*}} (struct protocol_t *)
+MERGE_CATS_CLS-NEXT: isa 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02
+MERGE_CATS_CLS-NEXT: protocols 0x0
+MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: entsize 24
+MERGE_CATS_CLS-NEXT: count 2
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol02Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol02Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0
+MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0
+MERGE_CATS_CLS-NEXT: instanceProperties {{.*}}
+MERGE_CATS_CLS-NEXT: list[1] {{.*}} (struct protocol_t *)
+MERGE_CATS_CLS-NEXT: isa 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03
+MERGE_CATS_CLS-NEXT: protocols 0x0
+MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: entsize 24
+MERGE_CATS_CLS-NEXT: count 2
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol03Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol03Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0
+MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0
+MERGE_CATS_CLS-NEXT: instanceProperties {{.*}}
+MERGE_CATS_CLS-NEXT: list[2] {{.*}} (struct protocol_t *)
+MERGE_CATS_CLS-NEXT: isa 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01
+MERGE_CATS_CLS-NEXT: protocols 0x0
+MERGE_CATS_CLS-NEXT: instanceMethods {{.*}} (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: entsize 24
+MERGE_CATS_CLS-NEXT: count 3
+MERGE_CATS_CLS-NEXT: name {{.*}} myProtocol01Method
+MERGE_CATS_CLS-NEXT: types {{.*}} v16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} MyProtocol01Prop
+MERGE_CATS_CLS-NEXT: types {{.*}} i16 at 0:8
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: name {{.*}} setMyProtocol01Prop:
+MERGE_CATS_CLS-NEXT: types {{.*}} v20 at 0:8i16
+MERGE_CATS_CLS-NEXT: imp 0x0
+MERGE_CATS_CLS-NEXT: classMethods 0x0 (struct method_list_t *)
+MERGE_CATS_CLS-NEXT: optionalInstanceMethods 0x0
+MERGE_CATS_CLS-NEXT: optionalClassMethods 0x0
+MERGE_CATS_CLS-NEXT: instanceProperties {{.*}}
+MERGE_CATS_CLS-NEXT: ivars 0x0
+MERGE_CATS_CLS-NEXT: weakIvarLayout 0x0
+MERGE_CATS_CLS-NEXT: baseProperties 0x0
+MERGE_CATS_CLS: __OBJC_$_CATEGORY_MyBaseClass_$_Category04
+
+
#--- a64_file1.s
## @protocol MyProtocol01
diff --git a/lld/test/MachO/objc-category-merging-extern-class-minimal.s b/lld/test/MachO/objc-category-merging-minimal.s
similarity index 59%
rename from lld/test/MachO/objc-category-merging-extern-class-minimal.s
rename to lld/test/MachO/objc-category-merging-minimal.s
index 5dd8924df5ad6..fcd90f178b150 100644
--- a/lld/test/MachO/objc-category-merging-extern-class-minimal.s
+++ b/lld/test/MachO/objc-category-merging-minimal.s
@@ -1,7 +1,8 @@
# REQUIRES: aarch64
# RUN: rm -rf %t; split-file %s %t && cd %t
-## Create a dylib with a fake base class to link against
+############ Test merging multiple categories into a single category ############
+## Create a dylib with a fake base class to link against in when merging between categories
# 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
@@ -14,6 +15,15 @@
# 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
+############ Test merging multiple categories into the base class ############
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-macos -o merge_base_class_minimal.o merge_base_class_minimal.s
+# RUN: %lld -arch arm64 -dylib -o merge_base_class_minimal_yes_merge.dylib -objc_category_merging merge_base_class_minimal.o merge_cat_minimal.o
+# RUN: %lld -arch arm64 -dylib -o merge_base_class_minimal_no_merge.dylib merge_base_class_minimal.o merge_cat_minimal.o
+
+# RUN: llvm-objdump --objc-meta-data --macho merge_base_class_minimal_no_merge.dylib | FileCheck %s --check-prefixes=NO_MERGE_INTO_BASE
+# RUN: llvm-objdump --objc-meta-data --macho merge_base_class_minimal_yes_merge.dylib | FileCheck %s --check-prefixes=YES_MERGE_INTO_BASE
+
+
#### Check merge categories enabled ###
# Check that the original categories are not there
MERGE_CATS-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category01
@@ -44,6 +54,28 @@ NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category01
NO_MERGE_CATS: __OBJC_$_CATEGORY_MyBaseClass_$_Category02
+#### Check merge cateogires into base class is disabled ####
+NO_MERGE_INTO_BASE: __OBJC_$_CATEGORY_MyBaseClass_$_Category01
+NO_MERGE_INTO_BASE: __OBJC_$_CATEGORY_MyBaseClass_$_Category02
+
+#### Check merge cateogires into base class is enabled and categories are merged into base class ####
+YES_MERGE_INTO_BASE-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category01
+YES_MERGE_INTO_BASE-NOT: __OBJC_$_CATEGORY_MyBaseClass_$_Category02
+
+YES_MERGE_INTO_BASE: _OBJC_CLASS_$_MyBaseClass
+YES_MERGE_INTO_BASE-NEXT: _OBJC_METACLASS_$_MyBaseClass
+YES_MERGE_INTO_BASE: baseMethods
+YES_MERGE_INTO_BASE-NEXT: entsize 24
+YES_MERGE_INTO_BASE-NEXT: count 3
+YES_MERGE_INTO_BASE-NEXT: name {{.*}} cat01_InstanceMethod
+YES_MERGE_INTO_BASE-NEXT: types {{.*}} v16 at 0:8
+YES_MERGE_INTO_BASE-NEXT: imp -[MyBaseClass(Category01) cat01_InstanceMethod]
+YES_MERGE_INTO_BASE-NEXT: name {{.*}} cat02_InstanceMethod
+YES_MERGE_INTO_BASE-NEXT: types {{.*}} v16 at 0:8
+YES_MERGE_INTO_BASE-NEXT: imp -[MyBaseClass(Category02) cat02_InstanceMethod]
+YES_MERGE_INTO_BASE-NEXT: name {{.*}} baseInstanceMethod
+YES_MERGE_INTO_BASE-NEXT: types {{.*}} v16 at 0:8
+YES_MERGE_INTO_BASE-NEXT: imp -[MyBaseClass baseInstanceMethod]
#--- a64_fakedylib.s
@@ -156,3 +188,94 @@ L_OBJC_IMAGE_INFO:
.addrsig
.addrsig_sym __OBJC_$_CATEGORY_MyBaseClass_$_Category01
+
+#--- merge_base_class_minimal.s
+; clang -c merge_base_class_minimal.mm -O3 -target arm64-apple-macos -arch arm64 -S -o merge_base_class_minimal.s
+; ================== Generated from ObjC: ==================
+; __attribute__((objc_root_class))
+; @interface MyBaseClass
+; - (void)baseInstanceMethod;
+; @end
+;
+; @implementation MyBaseClass
+; - (void)baseInstanceMethod {}
+; @end
+; ================== Generated from ObjC ==================
+ .section __TEXT,__text,regular,pure_instructions
+ .build_version macos, 11, 0
+ .p2align 2
+"-[MyBaseClass baseInstanceMethod]":
+ .cfi_startproc
+; %bb.0:
+ ret
+ .cfi_endproc
+ .section __DATA,__objc_data
+ .globl _OBJC_CLASS_$_MyBaseClass
+ .p2align 3, 0x0
+_OBJC_CLASS_$_MyBaseClass:
+ .quad _OBJC_METACLASS_$_MyBaseClass
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad __OBJC_CLASS_RO_$_MyBaseClass
+ .globl _OBJC_METACLASS_$_MyBaseClass
+ .p2align 3, 0x0
+_OBJC_METACLASS_$_MyBaseClass:
+ .quad _OBJC_METACLASS_$_MyBaseClass
+ .quad _OBJC_CLASS_$_MyBaseClass
+ .quad 0
+ .quad 0
+ .quad __OBJC_METACLASS_RO_$_MyBaseClass
+ .section __TEXT,__objc_classname,cstring_literals
+l_OBJC_CLASS_NAME_:
+ .asciz "MyBaseClass"
+ .section __DATA,__objc_const
+ .p2align 3, 0x0
+__OBJC_METACLASS_RO_$_MyBaseClass:
+ .long 3
+ .long 40
+ .long 40
+ .space 4
+ .quad 0
+ .quad l_OBJC_CLASS_NAME_
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad 0
+ .section __TEXT,__objc_methname,cstring_literals
+l_OBJC_METH_VAR_NAME_:
+ .asciz "baseInstanceMethod"
+ .section __TEXT,__objc_methtype,cstring_literals
+l_OBJC_METH_VAR_TYPE_:
+ .asciz "v16 at 0:8"
+ .section __DATA,__objc_const
+ .p2align 3, 0x0
+__OBJC_$_INSTANCE_METHODS_MyBaseClass:
+ .long 24
+ .long 1
+ .quad l_OBJC_METH_VAR_NAME_
+ .quad l_OBJC_METH_VAR_TYPE_
+ .quad "-[MyBaseClass baseInstanceMethod]"
+ .p2align 3, 0x0
+__OBJC_CLASS_RO_$_MyBaseClass:
+ .long 2
+ .long 0
+ .long 0
+ .space 4
+ .quad 0
+ .quad l_OBJC_CLASS_NAME_
+ .quad __OBJC_$_INSTANCE_METHODS_MyBaseClass
+ .quad 0
+ .quad 0
+ .quad 0
+ .quad 0
+ .section __DATA,__objc_classlist,regular,no_dead_strip
+ .p2align 3, 0x0
+l_OBJC_LABEL_CLASS_$:
+ .quad _OBJC_CLASS_$_MyBaseClass
+ .section __DATA,__objc_imageinfo,regular,no_dead_strip
+L_OBJC_IMAGE_INFO:
+ .long 0
+ .long 64
+.subsections_via_symbols
More information about the llvm-commits
mailing list