Skip to content

Commit 549a943

Browse files
author
Alex B
committed
[lld-macho] Implement ObjC category merging (-objc_category_merging)
This change adds a flag to lld to enable category merging for MachoO + ObjC. Behavior: if in the same link unit, multiple categories are extending the same class, then they get merged into a single category. Ex: Cat1(method1+method2,protocol1) + Cat2(method3+method4,protocol2, property1) = Cat1_2(method1+method2+method3+method4, protocol1+protocol2, property1) Notes on implementation decisions made in this diff: There is a possibility to further improve the current implementation by directly merging the category data into the base class (if the base class is present in the link unit) - this improvement may be done as a follow-up. We do the merging as early as possible, on the raw inputSections. We add a new flag for ObjFile (isLinkerGenerated) and create such an ObjFile to which all new linker-generated date belongs. We add a new flag (linkerOptimizeReason) to ConcatInputSection and StringPiece to mark that this data has been optimized away. Another way to do it would have been to just mark the pieces as not 'live' but this would require some work-arounds in the actual live symbol determination logic and would also cause symbols to incorrectly show up as 'dead-stripped' when that's not the cause that they are not present.
1 parent c21ef15 commit 549a943

10 files changed

+1925
-17
lines changed

lld/MachO/Driver.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1979,9 +1979,14 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
19791979
if (config->deadStrip)
19801980
markLive();
19811981

1982+
// Categories are not subject to dead-strip. The __objc_catlist section is
1983+
// marked as NO_DEAD_STRIP and that propagates into all category data.
19821984
if (args.hasArg(OPT_check_category_conflicts))
19831985
objc::checkCategories();
19841986

1987+
if (args.hasArg(OPT_objc_category_merging, OPT_no_objc_category_merging))
1988+
objc::mergeCategories();
1989+
19851990
// ICF assumes that all literals have been folded already, so we must run
19861991
// foldIdenticalLiterals before foldIdenticalSections.
19871992
foldIdenticalLiterals();

lld/MachO/InputSection.h

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@
2424
namespace lld {
2525
namespace macho {
2626

27+
enum LinkerOptReason : uint8_t {
28+
NotOptimized,
29+
CategoryMerging,
30+
};
31+
2732
class InputFile;
2833
class OutputSection;
2934

@@ -60,6 +65,7 @@ class InputSection {
6065
// Whether the data at \p off in this InputSection is live.
6166
virtual bool isLive(uint64_t off) const = 0;
6267
virtual void markLive(uint64_t off) = 0;
68+
virtual bool isLinkOptimizedAway() const { return false; }
6369
virtual InputSection *canonical() { return this; }
6470
virtual const InputSection *canonical() const { return this; }
6571

@@ -93,9 +99,9 @@ class InputSection {
9399
// .subsections_via_symbols, there is typically only one element here.
94100
llvm::TinyPtrVector<Defined *> symbols;
95101

96-
protected:
97102
const Section &section;
98103

104+
protected:
99105
const Defined *getContainingSymbol(uint64_t off) const;
100106
};
101107

@@ -114,7 +120,12 @@ class ConcatInputSection final : public InputSection {
114120
bool isLive(uint64_t off) const override { return live; }
115121
void markLive(uint64_t off) override { live = true; }
116122
bool isCoalescedWeak() const { return wasCoalesced && symbols.empty(); }
117-
bool shouldOmitFromOutput() const { return !live || isCoalescedWeak(); }
123+
bool isLinkOptimizedAway() const override {
124+
return linkerOptimizeReason != LinkerOptReason::NotOptimized;
125+
}
126+
bool shouldOmitFromOutput() const {
127+
return isLinkOptimizedAway() || !live || isCoalescedWeak();
128+
}
118129
void writeTo(uint8_t *buf);
119130

120131
void foldIdentical(ConcatInputSection *redundant);
@@ -141,6 +152,11 @@ class ConcatInputSection final : public InputSection {
141152
// first and not copied to the output.
142153
bool wasCoalesced = false;
143154
bool live = !config->deadStrip;
155+
// Flag to specify if a linker optimzation flagged this section to be
156+
// discarded. Need a separate flag from live as live specifically means
157+
// 'dead-stripped' which is rellevant in contexts such as linker map
158+
// generation
159+
LinkerOptReason linkerOptimizeReason = LinkerOptReason::NotOptimized;
144160
bool hasCallSites = false;
145161
// This variable has two usages. Initially, it represents the input order.
146162
// After assignAddresses is called, it represents the offset from the
@@ -176,10 +192,20 @@ struct StringPiece {
176192
// Only set if deduplicating literals
177193
uint32_t hash : 31;
178194
// Offset from the start of the containing output section.
179-
uint64_t outSecOff = 0;
195+
uint64_t outSecOff : 48;
196+
// Have to declare the 'linkerOptimizeReason' and 'live' as uint64_t so that
197+
// the MSVC compiler will merge the storage of it and 'outSecOff' above.
198+
uint64_t /*LinkerOptReason*/ linkerOptimizeReason : 8;
199+
200+
bool shouldOmitFromOutput() const {
201+
return !live || linkerOptimizeReason != LinkerOptReason::NotOptimized;
202+
}
180203

181204
StringPiece(uint64_t off, uint32_t hash)
182-
: inSecOff(off), live(!config->deadStrip), hash(hash) {}
205+
: inSecOff(off), live(!config->deadStrip), hash(hash) {
206+
outSecOff = 0;
207+
linkerOptimizeReason = LinkerOptReason::NotOptimized;
208+
}
183209
};
184210

185211
static_assert(sizeof(StringPiece) == 16, "StringPiece is too big!");

lld/MachO/MapFile.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ static MapInfo gatherMapInfo() {
8080
if (d->isec && d->getFile() == file &&
8181
!isa<CStringInputSection>(d->isec)) {
8282
isReferencedFile = true;
83-
if (!d->isLive())
83+
if (!d->isLive() && (!d->isec || !d->isec->isLinkOptimizedAway()))
8484
info.deadSymbols.push_back(d);
8585
}
8686
}
@@ -93,6 +93,8 @@ static MapInfo gatherMapInfo() {
9393
if (auto isec = dyn_cast<CStringInputSection>(subsec.isec)) {
9494
auto &liveCStrings = info.liveCStringsForSection[isec->parent];
9595
for (const auto &[i, piece] : llvm::enumerate(isec->pieces)) {
96+
if (piece.linkerOptimizeReason != LinkerOptReason::NotOptimized)
97+
continue;
9698
if (piece.live)
9799
liveCStrings.push_back({isec->parent->addr + piece.outSecOff,
98100
{fileIndex, isec->getStringRef(i)}});
@@ -203,6 +205,8 @@ void macho::writeMapFile() {
203205
for (const OutputSection *osec : seg->getSections()) {
204206
if (auto *concatOsec = dyn_cast<ConcatOutputSection>(osec)) {
205207
for (const InputSection *isec : concatOsec->inputs) {
208+
if (isec->isLinkOptimizedAway())
209+
continue;
206210
for (Defined *sym : isec->symbols)
207211
if (!(isPrivateLabel(sym->getName()) && sym->size == 0))
208212
os << format("0x%08llX\t0x%08llX\t[%3u] %s\n", sym->getVA(),

lld/MachO/MarkLive.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,8 @@ void markLive() {
259259
dyn_cast_or_null<DylibSymbol>(symtab->find("dyld_stub_binder")))
260260
marker->addSym(stubBinder);
261261
for (ConcatInputSection *isec : inputSections) {
262+
if (isec->isLinkOptimizedAway())
263+
continue;
262264
// Sections marked no_dead_strip
263265
if (isec->getFlags() & S_ATTR_NO_DEAD_STRIP) {
264266
marker->enqueue(isec, 0);

0 commit comments

Comments
 (0)