-
Notifications
You must be signed in to change notification settings - Fork 13.6k
[lld-macho][ObjC] Implement category merging into base class #92448
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 2 commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -379,8 +379,8 @@ 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(const char *_categoryPrefix, uint32_t _pointersPerStruct) | ||
: categoryPrefix(_categoryPrefix), | ||
|
@@ -395,9 +395,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){}; | ||
|
||
|
@@ -456,9 +456,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 +474,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 +556,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( | ||
|
@@ -769,11 +796,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 +847,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 +1294,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 +1336,96 @@ 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 | ||
parseProtocolListInfo(classIsec, roClassLayout.baseProtocolsOffset, | ||
alx32 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
extInfo.protocols); | ||
|
||
parsePointerListInfo(metaIsec, roClassLayout.baseMethodsOffset, | ||
extInfo.classMethods); | ||
|
||
parsePointerListInfo(metaIsec, roClassLayout.basePropertiesOffset, | ||
extInfo.classProps); | ||
|
||
parsePointerListInfo(classIsec, roClassLayout.baseMethodsOffset, | ||
extInfo.instanceMethods); | ||
|
||
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 | ||
emitAndLinkPointerList(metaRo, roClassLayout.baseMethodsOffset, extInfo, | ||
extInfo.classMethods); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code pattern seems unfortunate from the previous/existing work. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
How should the enumeration look like ? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's too disruptive at this moment in this change. I'm just saying with this |
||
|
||
// Protocols are a special case - the single list is referenced by both the | ||
// class RO and meta RO. Here we emit it and link it into the meta RO | ||
Defined *protoListSym = emitAndLinkProtocolList( | ||
metaRo, roClassLayout.baseProtocolsOffset, extInfo, extInfo.protocols); | ||
|
||
emitAndLinkPointerList(metaRo, roClassLayout.basePropertiesOffset, extInfo, | ||
extInfo.classProps); | ||
|
||
emitAndLinkPointerList(classRo, roClassLayout.baseMethodsOffset, extInfo, | ||
extInfo.instanceMethods); | ||
|
||
// If we emitted a new protocol list, link it to the class RO also | ||
if (protoListSym) { | ||
alx32 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
createSymbolReference(classRo, protoListSym, | ||
roClassLayout.baseProtocolsOffset, | ||
infoCategoryWriter.catBodyInfo.relocTemplate); | ||
} | ||
|
||
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); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can it be
null
? ThentryGetDefinedAtIsecOffset
didn't seem to check it.Otherwise, can we make it a static case, or adding an assertion?
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tryGetDefinedAtIsecOffset
does check it fornull
. It doestryGetSymbolAtIsecOffset
(which checks fornull
) +dyn_cast_or_null
(also checks fornull
).