Skip to content

Commit 77e204c

Browse files
authored
[lld-macho][arm64] implement -objc_stubs_small (#78665)
This patch implements `-objc_stubs_small` targeting arm64, aiming to align with ld64's behavior. 1. `-objc_stubs_fast`: As previously implemented, this always uses the Global Offset Table (GOT) to invoke `objc_msgSend`. The alignment of the objc stub is 32 bytes. 2. `-objc_stubs_small`: This behavior depends on whether `objc_msgSend` is defined. If it is, it directly jumps to `objc_msgSend`. If not, it creates another stub to indirectly jump to `objc_msgSend`, minimizing the size. The alignment of the objc stub in this case is 4 bytes.
1 parent 291ac25 commit 77e204c

File tree

11 files changed

+237
-51
lines changed

11 files changed

+237
-51
lines changed

lld/MachO/Arch/ARM64.cpp

Lines changed: 41 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,9 @@ struct ARM64 : ARM64Common {
3737
uint64_t entryAddr) const override;
3838

3939
void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
40-
uint64_t stubOffset, uint64_t selrefsVA,
41-
uint64_t selectorIndex, uint64_t gotAddr,
42-
uint64_t msgSendIndex) const override;
40+
uint64_t &stubOffset, uint64_t selrefsVA,
41+
uint64_t selectorIndex,
42+
Symbol *objcMsgSend) const override;
4343
void populateThunk(InputSection *thunk, Symbol *funcSym) override;
4444
void applyOptimizationHints(uint8_t *, const ObjFile &) const override;
4545
};
@@ -117,13 +117,42 @@ static constexpr uint32_t objcStubsFastCode[] = {
117117
0xd4200020, // brk #0x1
118118
};
119119

120+
static constexpr uint32_t objcStubsSmallCode[] = {
121+
0x90000001, // adrp x1, __objc_selrefs@page
122+
0xf9400021, // ldr x1, [x1, @selector("foo")@pageoff]
123+
0x14000000, // b _objc_msgSend
124+
};
125+
120126
void ARM64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
121-
uint64_t stubOffset, uint64_t selrefsVA,
122-
uint64_t selectorIndex, uint64_t gotAddr,
123-
uint64_t msgSendIndex) const {
124-
::writeObjCMsgSendStub<LP64>(buf, objcStubsFastCode, sym, stubsAddr,
125-
stubOffset, selrefsVA, selectorIndex, gotAddr,
126-
msgSendIndex);
127+
uint64_t &stubOffset, uint64_t selrefsVA,
128+
uint64_t selectorIndex,
129+
Symbol *objcMsgSend) const {
130+
uint64_t objcMsgSendAddr;
131+
uint64_t objcStubSize;
132+
uint64_t objcMsgSendIndex;
133+
134+
if (config->objcStubsMode == ObjCStubsMode::fast) {
135+
objcStubSize = target->objcStubsFastSize;
136+
objcMsgSendAddr = in.got->addr;
137+
objcMsgSendIndex = objcMsgSend->gotIndex;
138+
::writeObjCMsgSendFastStub<LP64>(buf, objcStubsFastCode, sym, stubsAddr,
139+
stubOffset, selrefsVA, selectorIndex,
140+
objcMsgSendAddr, objcMsgSendIndex);
141+
} else {
142+
assert(config->objcStubsMode == ObjCStubsMode::small);
143+
objcStubSize = target->objcStubsSmallSize;
144+
if (auto *d = dyn_cast<Defined>(objcMsgSend)) {
145+
objcMsgSendAddr = d->getVA();
146+
objcMsgSendIndex = 0;
147+
} else {
148+
objcMsgSendAddr = in.stubs->addr;
149+
objcMsgSendIndex = objcMsgSend->stubsIndex;
150+
}
151+
::writeObjCMsgSendSmallStub<LP64>(buf, objcStubsSmallCode, sym, stubsAddr,
152+
stubOffset, selrefsVA, selectorIndex,
153+
objcMsgSendAddr, objcMsgSendIndex);
154+
}
155+
stubOffset += objcStubSize;
127156
}
128157

129158
// A thunk is the relaxed variation of stubCode. We don't need the
@@ -157,7 +186,9 @@ ARM64::ARM64() : ARM64Common(LP64()) {
157186
thunkSize = sizeof(thunkCode);
158187

159188
objcStubsFastSize = sizeof(objcStubsFastCode);
160-
objcStubsAlignment = 32;
189+
objcStubsFastAlignment = 32;
190+
objcStubsSmallSize = sizeof(objcStubsSmallCode);
191+
objcStubsSmallAlignment = 4;
161192

162193
// Branch immediate is two's complement 26 bits, which is implicitly
163194
// multiplied by 4 (since all functions are 4-aligned: The branch range

lld/MachO/Arch/ARM64Common.h

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,10 @@ inline void writeStubHelperEntry(uint8_t *buf8,
154154

155155
template <class LP>
156156
inline void
157-
writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
158-
Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
159-
uint64_t selrefsVA, uint64_t selectorIndex,
160-
uint64_t gotAddr, uint64_t msgSendIndex) {
157+
writeObjCMsgSendFastStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
158+
Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
159+
uint64_t selrefsVA, uint64_t selectorIndex,
160+
uint64_t gotAddr, uint64_t msgSendIndex) {
161161
SymbolDiagnostic d = {sym, sym->getName()};
162162
auto *buf32 = reinterpret_cast<uint32_t *>(buf);
163163

@@ -180,6 +180,30 @@ writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
180180
buf32[7] = objcStubsFastCode[7];
181181
}
182182

183+
template <class LP>
184+
inline void
185+
writeObjCMsgSendSmallStub(uint8_t *buf, const uint32_t objcStubsSmallCode[3],
186+
Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
187+
uint64_t selrefsVA, uint64_t selectorIndex,
188+
uint64_t msgSendAddr, uint64_t msgSendIndex) {
189+
SymbolDiagnostic d = {sym, sym->getName()};
190+
auto *buf32 = reinterpret_cast<uint32_t *>(buf);
191+
192+
auto pcPageBits = [stubsAddr, stubOffset](int i) {
193+
return pageBits(stubsAddr + stubOffset + i * sizeof(uint32_t));
194+
};
195+
196+
uint64_t selectorOffset = selectorIndex * LP::wordSize;
197+
encodePage21(&buf32[0], d, objcStubsSmallCode[0],
198+
pageBits(selrefsVA + selectorOffset) - pcPageBits(0));
199+
encodePageOff12(&buf32[1], d, objcStubsSmallCode[1],
200+
selrefsVA + selectorOffset);
201+
uint64_t msgSendStubVA = msgSendAddr + msgSendIndex * target->stubSize;
202+
uint64_t pcVA = stubsAddr + stubOffset + 2 * sizeof(uint32_t);
203+
encodeBranch26(&buf32[2], {nullptr, "objc_msgSend stub"},
204+
objcStubsSmallCode[2], msgSendStubVA - pcVA);
205+
}
206+
183207
} // namespace lld::macho
184208

185209
#endif

lld/MachO/Arch/ARM64_32.cpp

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,9 @@ struct ARM64_32 : ARM64Common {
3434
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
3535
uint64_t entryAddr) const override;
3636
void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
37-
uint64_t stubOffset, uint64_t selrefsVA,
38-
uint64_t selectorIndex, uint64_t gotAddr,
39-
uint64_t msgSendIndex) const override;
37+
uint64_t &stubOffset, uint64_t selrefsVA,
38+
uint64_t selectorIndex,
39+
Symbol *objcMsgSend) const override;
4040
};
4141

4242
} // namespace
@@ -100,10 +100,9 @@ void ARM64_32::writeStubHelperEntry(uint8_t *buf8, const Symbol &sym,
100100
}
101101

102102
void ARM64_32::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym,
103-
uint64_t stubsAddr, uint64_t stubOffset,
103+
uint64_t stubsAddr, uint64_t &stubOffset,
104104
uint64_t selrefsVA, uint64_t selectorIndex,
105-
uint64_t gotAddr,
106-
uint64_t msgSendIndex) const {
105+
Symbol *objcMsgSend) const {
107106
fatal("TODO: implement this");
108107
}
109108

lld/MachO/Arch/X86_64.cpp

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,9 +38,9 @@ struct X86_64 : TargetInfo {
3838
uint64_t entryAddr) const override;
3939

4040
void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
41-
uint64_t stubOffset, uint64_t selrefsVA,
42-
uint64_t selectorIndex, uint64_t gotAddr,
43-
uint64_t msgSendIndex) const override;
41+
uint64_t &stubOffset, uint64_t selrefsVA,
42+
uint64_t selectorIndex,
43+
Symbol *objcMsgSend) const override;
4444

4545
void relaxGotLoad(uint8_t *loc, uint8_t type) const override;
4646
uint64_t getPageSize() const override { return 4 * 1024; }
@@ -182,16 +182,20 @@ static constexpr uint8_t objcStubsFastCode[] = {
182182
};
183183

184184
void X86_64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
185-
uint64_t stubOffset, uint64_t selrefsVA,
186-
uint64_t selectorIndex, uint64_t gotAddr,
187-
uint64_t msgSendIndex) const {
185+
uint64_t &stubOffset, uint64_t selrefsVA,
186+
uint64_t selectorIndex,
187+
Symbol *objcMsgSend) const {
188+
uint64_t objcMsgSendAddr = in.got->addr;
189+
uint64_t objcMsgSendIndex = objcMsgSend->gotIndex;
190+
188191
memcpy(buf, objcStubsFastCode, sizeof(objcStubsFastCode));
189192
SymbolDiagnostic d = {sym, sym->getName()};
190193
uint64_t stubAddr = stubsAddr + stubOffset;
191194
writeRipRelative(d, buf, stubAddr, 7,
192195
selrefsVA + selectorIndex * LP64::wordSize);
193196
writeRipRelative(d, buf, stubAddr, 0xd,
194-
gotAddr + msgSendIndex * LP64::wordSize);
197+
objcMsgSendAddr + objcMsgSendIndex * LP64::wordSize);
198+
stubOffset += target->objcStubsFastSize;
195199
}
196200

197201
void X86_64::relaxGotLoad(uint8_t *loc, uint8_t type) const {
@@ -214,7 +218,7 @@ X86_64::X86_64() : TargetInfo(LP64()) {
214218
stubHelperEntrySize = sizeof(stubHelperEntry);
215219

216220
objcStubsFastSize = sizeof(objcStubsFastCode);
217-
objcStubsAlignment = 1;
221+
objcStubsFastAlignment = 1;
218222

219223
relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
220224
}

lld/MachO/Driver.cpp

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -827,9 +827,13 @@ static ObjCStubsMode getObjCStubsMode(const ArgList &args) {
827827
if (!arg)
828828
return ObjCStubsMode::fast;
829829

830-
if (arg->getOption().getID() == OPT_objc_stubs_small)
831-
warn("-objc_stubs_small is not yet implemented, defaulting to "
832-
"-objc_stubs_fast");
830+
if (arg->getOption().getID() == OPT_objc_stubs_small) {
831+
if (is_contained({AK_arm64e, AK_arm64}, config->arch()))
832+
return ObjCStubsMode::small;
833+
else
834+
warn("-objc_stubs_small is not yet implemented, defaulting to "
835+
"-objc_stubs_fast");
836+
}
833837
return ObjCStubsMode::fast;
834838
}
835839

lld/MachO/SyntheticSections.cpp

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -809,31 +809,49 @@ void StubHelperSection::setUp() {
809809
ObjCStubsSection::ObjCStubsSection()
810810
: SyntheticSection(segment_names::text, section_names::objcStubs) {
811811
flags = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
812-
align = target->objcStubsAlignment;
812+
align = config->objcStubsMode == ObjCStubsMode::fast
813+
? target->objcStubsFastAlignment
814+
: target->objcStubsSmallAlignment;
813815
}
814816

815817
void ObjCStubsSection::addEntry(Symbol *sym) {
816818
assert(sym->getName().starts_with(symbolPrefix) && "not an objc stub");
817819
StringRef methname = sym->getName().drop_front(symbolPrefix.size());
818820
offsets.push_back(
819821
in.objcMethnameSection->getStringOffset(methname).outSecOff);
822+
823+
auto stubSize = config->objcStubsMode == ObjCStubsMode::fast
824+
? target->objcStubsFastSize
825+
: target->objcStubsSmallSize;
820826
Defined *newSym = replaceSymbol<Defined>(
821827
sym, sym->getName(), nullptr, isec,
822-
/*value=*/symbols.size() * target->objcStubsFastSize,
823-
/*size=*/target->objcStubsFastSize,
828+
/*value=*/symbols.size() * stubSize,
829+
/*size=*/stubSize,
824830
/*isWeakDef=*/false, /*isExternal=*/true, /*isPrivateExtern=*/true,
825831
/*includeInSymtab=*/true, /*isReferencedDynamically=*/false,
826832
/*noDeadStrip=*/false);
827833
symbols.push_back(newSym);
828834
}
829835

830836
void ObjCStubsSection::setUp() {
831-
Symbol *objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr,
832-
/*isWeakRef=*/false);
837+
objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr,
838+
/*isWeakRef=*/false);
839+
if (auto *undefined = dyn_cast<Undefined>(objcMsgSend))
840+
treatUndefinedSymbol(*undefined,
841+
"lazy binding (normally in libobjc.dylib)");
833842
objcMsgSend->used = true;
834-
in.got->addEntry(objcMsgSend);
835-
assert(objcMsgSend->isInGot());
836-
objcMsgSendGotIndex = objcMsgSend->gotIndex;
843+
if (config->objcStubsMode == ObjCStubsMode::fast) {
844+
in.got->addEntry(objcMsgSend);
845+
assert(objcMsgSend->isInGot());
846+
} else {
847+
assert(config->objcStubsMode == ObjCStubsMode::small);
848+
// In line with ld64's behavior, when objc_msgSend is a direct symbol,
849+
// we directly reference it.
850+
// In other cases, typically when binding in libobjc.dylib,
851+
// we generate a stub to invoke objc_msgSend.
852+
if (!isa<Defined>(objcMsgSend))
853+
in.stubs->addEntry(objcMsgSend);
854+
}
837855

838856
size_t size = offsets.size() * target->wordSize;
839857
uint8_t *selrefsData = bAlloc().Allocate<uint8_t>(size);
@@ -863,7 +881,10 @@ void ObjCStubsSection::setUp() {
863881
}
864882

865883
uint64_t ObjCStubsSection::getSize() const {
866-
return target->objcStubsFastSize * symbols.size();
884+
auto stubSize = config->objcStubsMode == ObjCStubsMode::fast
885+
? target->objcStubsFastSize
886+
: target->objcStubsSmallSize;
887+
return stubSize * symbols.size();
867888
}
868889

869890
void ObjCStubsSection::writeTo(uint8_t *buf) const {
@@ -875,8 +896,7 @@ void ObjCStubsSection::writeTo(uint8_t *buf) const {
875896
Defined *sym = symbols[i];
876897
target->writeObjCMsgSendStub(buf + stubOffset, sym, in.objcStubs->addr,
877898
stubOffset, in.objcSelrefs->getVA(), i,
878-
in.got->addr, objcMsgSendGotIndex);
879-
stubOffset += target->objcStubsFastSize;
899+
objcMsgSend);
880900
}
881901
}
882902

lld/MachO/SyntheticSections.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ class ObjCStubsSection final : public SyntheticSection {
336336
private:
337337
std::vector<Defined *> symbols;
338338
std::vector<uint32_t> offsets;
339-
int objcMsgSendGotIndex = 0;
339+
Symbol *objcMsgSend = nullptr;
340340
};
341341

342342
// Note that this section may also be targeted by non-lazy bindings. In

lld/MachO/Target.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,9 @@ class TargetInfo {
7070
uint64_t entryAddr) const = 0;
7171

7272
virtual void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym,
73-
uint64_t stubsAddr, uint64_t stubOffset,
73+
uint64_t stubsAddr, uint64_t &stubOffset,
7474
uint64_t selrefsVA, uint64_t selectorIndex,
75-
uint64_t gotAddr,
76-
uint64_t msgSendIndex) const = 0;
75+
Symbol *objcMsgSend) const = 0;
7776

7877
// Symbols may be referenced via either the GOT or the stubs section,
7978
// depending on the relocation type. prepareSymbolRelocation() will set up the
@@ -121,7 +120,9 @@ class TargetInfo {
121120
size_t stubHelperHeaderSize;
122121
size_t stubHelperEntrySize;
123122
size_t objcStubsFastSize;
124-
size_t objcStubsAlignment;
123+
size_t objcStubsSmallSize;
124+
size_t objcStubsFastAlignment;
125+
size_t objcStubsSmallAlignment;
125126
uint8_t p2WordSize;
126127
size_t wordSize;
127128

lld/test/MachO/arm64-objc-stubs-dyn.s

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# REQUIRES: aarch64
2+
3+
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o
4+
# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o
5+
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
6+
# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -dead_strip
7+
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
8+
# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -objc_stubs_fast
9+
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
10+
# RUN: %lld -arch arm64 -lSystem -U _objc_msgSend -o %t.out %t.o -objc_stubs_small
11+
# RUN: llvm-otool -vs __TEXT __stubs %t.out | FileCheck %s --check-prefix=STUB
12+
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s --check-prefix=SMALL
13+
14+
# Unlike arm64-objc-stubs.s, in this test, _objc_msgSend is not defined,
15+
# which usually binds with libobjc.dylib.
16+
# 1. -objc_stubs_fast: No change as it uses GOT.
17+
# 2. -objc_stubs_small: Create a (shared) stub to invoke _objc_msgSend, to minimize the size.
18+
19+
# CHECK: Contents of (__TEXT,__objc_stubs) section
20+
21+
# CHECK-NEXT: _objc_msgSend$foo:
22+
# CHECK-NEXT: adrp x1, 8 ; 0x100008000
23+
# CHECK-NEXT: ldr x1, [x1, #0x10]
24+
# CHECK-NEXT: adrp x16, 4 ; 0x100004000
25+
# CHECK-NEXT: ldr x16, [x16]
26+
# CHECK-NEXT: br x16
27+
# CHECK-NEXT: brk #0x1
28+
# CHECK-NEXT: brk #0x1
29+
# CHECK-NEXT: brk #0x1
30+
31+
# CHECK-NEXT: _objc_msgSend$length:
32+
# CHECK-NEXT: adrp x1, 8 ; 0x100008000
33+
# CHECK-NEXT: ldr x1, [x1, #0x18]
34+
# CHECK-NEXT: adrp x16, 4 ; 0x100004000
35+
# CHECK-NEXT: ldr x16, [x16]
36+
# CHECK-NEXT: br x16
37+
# CHECK-NEXT: brk #0x1
38+
# CHECK-NEXT: brk #0x1
39+
# CHECK-NEXT: brk #0x1
40+
41+
# CHECK-EMPTY:
42+
43+
# STUB: Contents of (__TEXT,__stubs) section
44+
# STUB-NEXT: adrp x16, 8 ; 0x100008000
45+
# STUB-NEXT: ldr x16, [x16]
46+
# STUB-NEXT: br x16
47+
48+
# SMALL: Contents of (__TEXT,__objc_stubs) section
49+
# SMALL-NEXT: _objc_msgSend$foo:
50+
# SMALL-NEXT: adrp x1, 8 ; 0x100008000
51+
# SMALL-NEXT: ldr x1, [x1, #0x18]
52+
# SMALL-NEXT: b
53+
# SMALL-NEXT: _objc_msgSend$length:
54+
# SMALL-NEXT: adrp x1, 8 ; 0x100008000
55+
# SMALL-NEXT: ldr x1, [x1, #0x20]
56+
# SMALL-NEXT: b
57+
58+
.section __TEXT,__objc_methname,cstring_literals
59+
lselref1:
60+
.asciz "foo"
61+
lselref2:
62+
.asciz "bar"
63+
64+
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
65+
.p2align 3
66+
.quad lselref1
67+
.quad lselref2
68+
69+
.text
70+
71+
.globl _main
72+
_main:
73+
bl _objc_msgSend$length
74+
bl _objc_msgSend$foo
75+
bl _objc_msgSend$foo
76+
ret

0 commit comments

Comments
 (0)