[lld-macho] Add support for objc_msgSend stubs
Apple Clang in Xcode 14 introduced a new feature for reducing the overhead of objc_msgSend calls by deduplicating the setup calls for each individual selector. This works by clang adding undefined symbols for each selector called in a translation unit, such as `_objc_msgSend$foo` for calling the `foo` method on any `NSObject`. There are 2 different modes for this behavior, the default directly does the setup for `_objc_msgSend` and calls it, and the smaller option does the selector setup, and then calls the standard `_objc_msgSend` stub function. The general overview of how this works is: - Undefined symbols with the given prefix are collected - The suffix of each matching undefined symbol is added as a string to `__objc_methname` - A pointer is added for every method name in the `__objc_selrefs` section - A `got` entry is emitted for `_objc_msgSend` - Stubs are emitting pointing to the synthesized locations Notes: - Both `__objc_methname` and `__objc_selrefs` can also exist from object files, so their contents are merged with our synthesized contents - The compiler emits method names for defined methods, but not for undefined symbols you call, but stubs are used for both - This only implements the default "fast" mode currently just to reduce the diff, I also doubt many folks will care to swap modes - This only implements this for arm64 and x86_64, we don't need to implement this for 32 bit iOS archs, but we should implement it for watchOS archs in a later diff Differential Revision: https://reviews.llvm.org/D128108
This commit is contained in:
parent
ad5f7895ef
commit
3c24fae398
|
@ -37,6 +37,11 @@ struct ARM : TargetInfo {
|
|||
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
|
||||
uint64_t entryAddr) const override;
|
||||
|
||||
void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
|
||||
uint64_t stubOffset, uint64_t selrefsVA,
|
||||
uint64_t selectorIndex, uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const override;
|
||||
|
||||
void relaxGotLoad(uint8_t *loc, uint8_t type) const override;
|
||||
uint64_t getPageSize() const override { return 4 * 1024; }
|
||||
|
||||
|
@ -148,6 +153,13 @@ void ARM::writeStubHelperEntry(uint8_t *buf, const Symbol &sym,
|
|||
fatal("TODO: implement this");
|
||||
}
|
||||
|
||||
void ARM::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
|
||||
uint64_t stubOffset, uint64_t selrefsVA,
|
||||
uint64_t selectorIndex, uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const {
|
||||
fatal("TODO: implement this");
|
||||
}
|
||||
|
||||
void ARM::relaxGotLoad(uint8_t *loc, uint8_t type) const {
|
||||
fatal("TODO: implement this");
|
||||
}
|
||||
|
|
|
@ -34,6 +34,11 @@ struct ARM64 : ARM64Common {
|
|||
void writeStubHelperHeader(uint8_t *buf) const override;
|
||||
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
|
||||
uint64_t entryAddr) const override;
|
||||
|
||||
void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
|
||||
uint64_t stubOffset, uint64_t selrefsVA,
|
||||
uint64_t selectorIndex, uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const override;
|
||||
void populateThunk(InputSection *thunk, Symbol *funcSym) override;
|
||||
void applyOptimizationHints(uint8_t *, const ConcatInputSection *,
|
||||
ArrayRef<uint64_t>) const override;
|
||||
|
@ -100,6 +105,26 @@ void ARM64::writeStubHelperEntry(uint8_t *buf8, const Symbol &sym,
|
|||
::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA);
|
||||
}
|
||||
|
||||
static constexpr uint32_t objcStubsFastCode[] = {
|
||||
0x90000001, // adrp x1, __objc_selrefs@page
|
||||
0xf9400021, // ldr x1, [x1, @selector("foo")@pageoff]
|
||||
0x90000010, // adrp x16, _got@page
|
||||
0xf9400210, // ldr x16, [x16, _objc_msgSend@pageoff]
|
||||
0xd61f0200, // br x16
|
||||
0xd4200020, // brk #0x1
|
||||
0xd4200020, // brk #0x1
|
||||
0xd4200020, // brk #0x1
|
||||
};
|
||||
|
||||
void ARM64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
|
||||
uint64_t stubOffset, uint64_t selrefsVA,
|
||||
uint64_t selectorIndex, uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const {
|
||||
::writeObjCMsgSendStub<LP64>(buf, objcStubsFastCode, sym, stubsAddr,
|
||||
stubOffset, selrefsVA, selectorIndex, gotAddr,
|
||||
msgSendIndex);
|
||||
}
|
||||
|
||||
// A thunk is the relaxed variation of stubCode. We don't need the
|
||||
// extra indirection through a lazy pointer because the target address
|
||||
// is known at link time.
|
||||
|
@ -130,6 +155,9 @@ ARM64::ARM64() : ARM64Common(LP64()) {
|
|||
stubSize = sizeof(stubCode);
|
||||
thunkSize = sizeof(thunkCode);
|
||||
|
||||
objcStubsFastSize = sizeof(objcStubsFastCode);
|
||||
objcStubsAlignment = 32;
|
||||
|
||||
// Branch immediate is two's complement 26 bits, which is implicitly
|
||||
// multiplied by 4 (since all functions are 4-aligned: The branch range
|
||||
// is -4*(2**(26-1))..4*(2**(26-1) - 1).
|
||||
|
|
|
@ -147,6 +147,32 @@ inline void writeStubHelperEntry(uint8_t *buf8,
|
|||
buf32[2] = sym.lazyBindOffset;
|
||||
}
|
||||
|
||||
template <class LP>
|
||||
inline void
|
||||
writeObjCMsgSendStub(uint8_t *buf, const uint32_t objcStubsFastCode[8],
|
||||
Symbol *sym, uint64_t stubsAddr, uint64_t stubOffset,
|
||||
uint64_t selrefsVA, uint64_t selectorIndex,
|
||||
uint64_t gotAddr, uint64_t msgSendIndex) {
|
||||
SymbolDiagnostic d = {sym, sym->getName()};
|
||||
auto *buf32 = reinterpret_cast<uint32_t *>(buf);
|
||||
|
||||
auto pcPageBits = [stubsAddr, stubOffset](int i) {
|
||||
return pageBits(stubsAddr + stubOffset + i * sizeof(uint32_t));
|
||||
};
|
||||
|
||||
uint64_t selectorOffset = selectorIndex * LP::wordSize;
|
||||
encodePage21(&buf32[0], d, objcStubsFastCode[0],
|
||||
pageBits(selrefsVA + selectorOffset) - pcPageBits(0));
|
||||
encodePageOff12(&buf32[1], objcStubsFastCode[1], selrefsVA + selectorOffset);
|
||||
encodePage21(&buf32[2], d, objcStubsFastCode[2],
|
||||
pageBits(gotAddr) - pcPageBits(2));
|
||||
encodePage21(&buf32[3], d, objcStubsFastCode[3], msgSendIndex * LP::wordSize);
|
||||
buf32[4] = objcStubsFastCode[4];
|
||||
buf32[5] = objcStubsFastCode[5];
|
||||
buf32[6] = objcStubsFastCode[6];
|
||||
buf32[7] = objcStubsFastCode[7];
|
||||
}
|
||||
|
||||
} // namespace lld::macho
|
||||
|
||||
#endif
|
||||
|
|
|
@ -33,6 +33,10 @@ struct ARM64_32 : ARM64Common {
|
|||
void writeStubHelperHeader(uint8_t *buf) const override;
|
||||
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
|
||||
uint64_t entryAddr) const override;
|
||||
void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
|
||||
uint64_t stubOffset, uint64_t selrefsVA,
|
||||
uint64_t selectorIndex, uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const override;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -94,6 +98,14 @@ void ARM64_32::writeStubHelperEntry(uint8_t *buf8, const Symbol &sym,
|
|||
::writeStubHelperEntry(buf8, stubHelperEntryCode, sym, entryVA);
|
||||
}
|
||||
|
||||
void ARM64_32::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym,
|
||||
uint64_t stubsAddr, uint64_t stubOffset,
|
||||
uint64_t selrefsVA, uint64_t selectorIndex,
|
||||
uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const {
|
||||
fatal("TODO: implement this");
|
||||
}
|
||||
|
||||
ARM64_32::ARM64_32() : ARM64Common(ILP32()) {
|
||||
cpuType = CPU_TYPE_ARM64_32;
|
||||
cpuSubtype = CPU_SUBTYPE_ARM64_V8;
|
||||
|
|
|
@ -36,6 +36,11 @@ struct X86_64 : TargetInfo {
|
|||
void writeStubHelperEntry(uint8_t *buf, const Symbol &,
|
||||
uint64_t entryAddr) const override;
|
||||
|
||||
void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
|
||||
uint64_t stubOffset, uint64_t selrefsVA,
|
||||
uint64_t selectorIndex, uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const override;
|
||||
|
||||
void relaxGotLoad(uint8_t *loc, uint8_t type) const override;
|
||||
uint64_t getPageSize() const override { return 4 * 1024; }
|
||||
|
||||
|
@ -170,6 +175,24 @@ void X86_64::writeStubHelperEntry(uint8_t *buf, const Symbol &sym,
|
|||
sizeof(stubHelperEntry), in.stubHelper->addr);
|
||||
}
|
||||
|
||||
static constexpr uint8_t objcStubsFastCode[] = {
|
||||
0x48, 0x8b, 0x35, 0, 0, 0, 0, // 0x0: movq selrefs@selector(%rip), %rsi
|
||||
0xff, 0x25, 0, 0, 0, 0, // 0x7: jmpq *_objc_msgSend@GOT(%rip)
|
||||
};
|
||||
|
||||
void X86_64::writeObjCMsgSendStub(uint8_t *buf, Symbol *sym, uint64_t stubsAddr,
|
||||
uint64_t stubOffset, uint64_t selrefsVA,
|
||||
uint64_t selectorIndex, uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const {
|
||||
memcpy(buf, objcStubsFastCode, sizeof(objcStubsFastCode));
|
||||
SymbolDiagnostic d = {sym, sym->getName()};
|
||||
uint64_t stubAddr = stubsAddr + stubOffset;
|
||||
writeRipRelative(d, buf, stubAddr, 7,
|
||||
selrefsVA + selectorIndex * LP64::wordSize);
|
||||
writeRipRelative(d, buf, stubAddr, 0xd,
|
||||
gotAddr + msgSendIndex * LP64::wordSize);
|
||||
}
|
||||
|
||||
void X86_64::relaxGotLoad(uint8_t *loc, uint8_t type) const {
|
||||
// Convert MOVQ to LEAQ
|
||||
if (loc[-2] != 0x8b)
|
||||
|
@ -189,6 +212,9 @@ X86_64::X86_64() : TargetInfo(LP64()) {
|
|||
stubHelperHeaderSize = sizeof(stubHelperHeader);
|
||||
stubHelperEntrySize = sizeof(stubHelperEntry);
|
||||
|
||||
objcStubsFastSize = sizeof(objcStubsFastCode);
|
||||
objcStubsAlignment = 1;
|
||||
|
||||
relocAttrs = {relocAttrsArray.data(), relocAttrsArray.size()};
|
||||
}
|
||||
|
||||
|
|
|
@ -68,6 +68,11 @@ enum class ICFLevel {
|
|||
all,
|
||||
};
|
||||
|
||||
enum class ObjCStubsMode {
|
||||
fast,
|
||||
small,
|
||||
};
|
||||
|
||||
struct SectionAlign {
|
||||
llvm::StringRef segName;
|
||||
llvm::StringRef sectName;
|
||||
|
@ -166,6 +171,7 @@ struct Configuration {
|
|||
UndefinedSymbolTreatment undefinedSymbolTreatment =
|
||||
UndefinedSymbolTreatment::error;
|
||||
ICFLevel icfLevel = ICFLevel::none;
|
||||
ObjCStubsMode objcStubsMode = ObjCStubsMode::fast;
|
||||
llvm::MachO::HeaderFileType outputType;
|
||||
std::vector<llvm::StringRef> systemLibraryRoots;
|
||||
std::vector<llvm::StringRef> librarySearchPaths;
|
||||
|
|
|
@ -781,6 +781,17 @@ static ICFLevel getICFLevel(const ArgList &args) {
|
|||
return icfLevel;
|
||||
}
|
||||
|
||||
static ObjCStubsMode getObjCStubsMode(const ArgList &args) {
|
||||
const Arg *arg = args.getLastArg(OPT_objc_stubs_fast, OPT_objc_stubs_small);
|
||||
if (!arg)
|
||||
return ObjCStubsMode::fast;
|
||||
|
||||
if (arg->getOption().getID() == OPT_objc_stubs_small)
|
||||
warn("-objc_stubs_small is not yet implemented, defaulting to "
|
||||
"-objc_stubs_fast");
|
||||
return ObjCStubsMode::fast;
|
||||
}
|
||||
|
||||
static void warnIfDeprecatedOption(const Option &opt) {
|
||||
if (!opt.getGroup().isValid())
|
||||
return;
|
||||
|
@ -1099,9 +1110,15 @@ static void gatherInputSections() {
|
|||
inputSections.push_back(isec);
|
||||
} else if (auto *isec =
|
||||
dyn_cast<CStringInputSection>(subsection.isec)) {
|
||||
if (isec->getName() == section_names::objcMethname) {
|
||||
if (in.objcMethnameSection->inputOrder == UnspecifiedInputOrder)
|
||||
in.objcMethnameSection->inputOrder = inputOrder++;
|
||||
in.objcMethnameSection->addInput(isec);
|
||||
} else {
|
||||
if (in.cStringSection->inputOrder == UnspecifiedInputOrder)
|
||||
in.cStringSection->inputOrder = inputOrder++;
|
||||
in.cStringSection->addInput(isec);
|
||||
}
|
||||
} else if (auto *isec =
|
||||
dyn_cast<WordLiteralInputSection>(subsection.isec)) {
|
||||
if (in.wordLiteralSection->inputOrder == UnspecifiedInputOrder)
|
||||
|
@ -1124,10 +1141,39 @@ static void foldIdenticalLiterals() {
|
|||
// true. If it isn't, we simply create a non-deduplicating CStringSection.
|
||||
// Either way, we must unconditionally finalize it here.
|
||||
in.cStringSection->finalizeContents();
|
||||
in.objcMethnameSection->finalizeContents();
|
||||
if (in.wordLiteralSection)
|
||||
in.wordLiteralSection->finalizeContents();
|
||||
}
|
||||
|
||||
static void addSynthenticMethnames() {
|
||||
std::string &data = *make<std::string>();
|
||||
llvm::raw_string_ostream os(data);
|
||||
const int prefixLength = ObjCStubsSection::symbolPrefix.size();
|
||||
for (Symbol *sym : symtab->getSymbols())
|
||||
if (const auto *undefined = dyn_cast<Undefined>(sym))
|
||||
if (sym->getName().startswith(ObjCStubsSection::symbolPrefix))
|
||||
os << sym->getName().drop_front(prefixLength) << '\0';
|
||||
|
||||
if (data.empty())
|
||||
return;
|
||||
|
||||
const auto *buf = reinterpret_cast<const uint8_t *>(data.c_str());
|
||||
Section §ion = *make<Section>(/*file=*/nullptr, segment_names::text,
|
||||
section_names::objcMethname,
|
||||
S_CSTRING_LITERALS, /*addr=*/0);
|
||||
|
||||
auto *isec =
|
||||
make<CStringInputSection>(section, ArrayRef<uint8_t>{buf, data.size()},
|
||||
/*align=*/1, /*dedupLiterals=*/true);
|
||||
isec->splitIntoPieces();
|
||||
for (auto &piece : isec->pieces)
|
||||
piece.live = true;
|
||||
section.subsections.push_back({0, isec});
|
||||
in.objcMethnameSection->addInput(isec);
|
||||
in.objcMethnameSection->isec->markLive(0);
|
||||
}
|
||||
|
||||
static void referenceStubBinder() {
|
||||
bool needsStubHelper = config->outputType == MH_DYLIB ||
|
||||
config->outputType == MH_EXECUTE ||
|
||||
|
@ -1398,6 +1444,7 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
|
|||
config->printSymbolOrder = args.getLastArgValue(OPT_print_symbol_order);
|
||||
config->forceExactCpuSubtypeMatch =
|
||||
getenv("LD_DYLIB_CPU_SUBTYPES_MUST_MATCH");
|
||||
config->objcStubsMode = getObjCStubsMode(args);
|
||||
|
||||
for (const Arg *arg : args.filtered(OPT_alias)) {
|
||||
config->aliasedSymbols.push_back(
|
||||
|
@ -1643,6 +1690,7 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
|
|||
|
||||
createSyntheticSections();
|
||||
createSyntheticSymbols();
|
||||
addSynthenticMethnames();
|
||||
|
||||
createAliases();
|
||||
// If we are in "explicit exports" mode, hide everything that isn't
|
||||
|
|
|
@ -342,7 +342,10 @@ void ObjFile::parseSections(ArrayRef<SectionHeader> sectionHeaders) {
|
|||
|
||||
InputSection *isec;
|
||||
if (sectionType(sec.flags) == S_CSTRING_LITERALS) {
|
||||
isec = make<CStringInputSection>(section, data, align);
|
||||
isec = make<CStringInputSection>(section, data, align,
|
||||
/*dedupLiterals=*/name ==
|
||||
section_names::objcMethname ||
|
||||
config->dedupLiterals);
|
||||
// FIXME: parallelize this?
|
||||
cast<CStringInputSection>(isec)->splitIntoPieces();
|
||||
} else {
|
||||
|
|
|
@ -251,7 +251,7 @@ void CStringInputSection::splitIntoPieces() {
|
|||
if (end == StringRef::npos)
|
||||
fatal(getLocation(off) + ": string is not null terminated");
|
||||
size_t size = end + 1;
|
||||
uint32_t hash = config->dedupLiterals ? xxHash64(s.substr(0, size)) : 0;
|
||||
uint32_t hash = deduplicateLiterals ? xxHash64(s.substr(0, size)) : 0;
|
||||
pieces.emplace_back(off, hash);
|
||||
s = s.substr(size);
|
||||
off += size;
|
||||
|
|
|
@ -192,8 +192,10 @@ static_assert(sizeof(StringPiece) == 16, "StringPiece is too big!");
|
|||
class CStringInputSection final : public InputSection {
|
||||
public:
|
||||
CStringInputSection(const Section §ion, ArrayRef<uint8_t> data,
|
||||
uint32_t align)
|
||||
: InputSection(CStringLiteralKind, section, data, align) {}
|
||||
uint32_t align, bool dedupLiterals)
|
||||
: InputSection(CStringLiteralKind, section, data, align),
|
||||
deduplicateLiterals(dedupLiterals) {}
|
||||
|
||||
uint64_t getOffset(uint64_t off) const override;
|
||||
bool isLive(uint64_t off) const override { return getStringPiece(off).live; }
|
||||
void markLive(uint64_t off) override { getStringPiece(off).live = true; }
|
||||
|
@ -215,7 +217,7 @@ public:
|
|||
// string merging is enabled, so we want to inline.
|
||||
LLVM_ATTRIBUTE_ALWAYS_INLINE
|
||||
llvm::CachedHashStringRef getCachedHashStringRef(size_t i) const {
|
||||
assert(config->dedupLiterals);
|
||||
assert(deduplicateLiterals);
|
||||
return {getStringRef(i), pieces[i].hash};
|
||||
}
|
||||
|
||||
|
@ -223,6 +225,7 @@ public:
|
|||
return isec->kind() == CStringLiteralKind;
|
||||
}
|
||||
|
||||
bool deduplicateLiterals = false;
|
||||
std::vector<StringPiece> pieces;
|
||||
};
|
||||
|
||||
|
@ -323,6 +326,9 @@ constexpr const char objcClassList[] = "__objc_classlist";
|
|||
constexpr const char objcClassRefs[] = "__objc_classrefs";
|
||||
constexpr const char objcConst[] = "__objc_const";
|
||||
constexpr const char objCImageInfo[] = "__objc_imageinfo";
|
||||
constexpr const char objcStubs[] = "__objc_stubs";
|
||||
constexpr const char objcSelrefs[] = "__objc_selrefs";
|
||||
constexpr const char objcMethname[] = "__objc_methname";
|
||||
constexpr const char objcNonLazyCatList[] = "__objc_nlcatlist";
|
||||
constexpr const char objcNonLazyClassList[] = "__objc_nlclslist";
|
||||
constexpr const char objcProtoList[] = "__objc_protolist";
|
||||
|
|
|
@ -978,6 +978,12 @@ def mcpu : Separate<["-"], "mcpu">,
|
|||
def no_dtrace_dof : Flag<["-"], "no_dtrace_dof">,
|
||||
HelpText<"Disable dtrace-dof processing (default).">,
|
||||
Group<grp_rare>;
|
||||
def objc_stubs_fast : Flag<["-"], "objc_stubs_fast">,
|
||||
HelpText<"Produce larger stubs for Objective-C method calls with fewer jumps (default).">,
|
||||
Group<grp_rare>;
|
||||
def objc_stubs_small : Flag<["-"], "objc_stubs_small">,
|
||||
HelpText<"Produce smaller stubs for Objective-C method calls with more jumps.">,
|
||||
Group<grp_rare>;
|
||||
|
||||
def grp_deprecated : OptionGroup<"deprecated">, HelpText<"DEPRECATED">;
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "llvm/Support/LEB128.h"
|
||||
#include "llvm/Support/Parallel.h"
|
||||
#include "llvm/Support/Path.h"
|
||||
#include "llvm/Support/xxhash.h"
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <sys/mman.h>
|
||||
|
@ -716,6 +717,84 @@ void StubHelperSection::setup() {
|
|||
dyldPrivate->used = true;
|
||||
}
|
||||
|
||||
ObjCStubsSection::ObjCStubsSection()
|
||||
: SyntheticSection(segment_names::text, section_names::objcStubs) {
|
||||
flags = S_ATTR_SOME_INSTRUCTIONS | S_ATTR_PURE_INSTRUCTIONS;
|
||||
align = target->objcStubsAlignment;
|
||||
}
|
||||
|
||||
void ObjCStubsSection::addEntry(Symbol *sym) {
|
||||
assert(sym->getName().startswith(symbolPrefix) && "not an objc stub");
|
||||
// Ensure our lookup string has the length of the actual string + the null
|
||||
// terminator to mirror
|
||||
StringRef methname =
|
||||
StringRef(sym->getName().data() + symbolPrefix.size(),
|
||||
sym->getName().size() - symbolPrefix.size() + 1);
|
||||
offsets.push_back(
|
||||
in.objcMethnameSection->getStringOffset(methname).outSecOff);
|
||||
Defined *newSym = replaceSymbol<Defined>(
|
||||
sym, sym->getName(), nullptr, isec,
|
||||
/*value=*/symbols.size() * target->objcStubsFastSize,
|
||||
/*size=*/target->objcStubsFastSize,
|
||||
/*isWeakDef=*/false, /*isExternal=*/true, /*isPrivateExtern=*/true,
|
||||
/*includeInSymtab=*/true, /*isThumb=*/false,
|
||||
/*isReferencedDynamically=*/false, /*noDeadStrip=*/false);
|
||||
symbols.push_back(newSym);
|
||||
}
|
||||
|
||||
void ObjCStubsSection::setup() {
|
||||
Symbol *objcMsgSend = symtab->addUndefined("_objc_msgSend", /*file=*/nullptr,
|
||||
/*isWeakRef=*/false);
|
||||
objcMsgSend->used = true;
|
||||
in.got->addEntry(objcMsgSend);
|
||||
assert(objcMsgSend->isInGot());
|
||||
objcMsgSendGotIndex = objcMsgSend->gotIndex;
|
||||
|
||||
size_t size = offsets.size() * target->wordSize;
|
||||
uint8_t *selrefsData = bAlloc().Allocate<uint8_t>(size);
|
||||
for (size_t i = 0, n = offsets.size(); i < n; ++i)
|
||||
write64le(&selrefsData[i * target->wordSize], offsets[i]);
|
||||
|
||||
in.objcSelrefs =
|
||||
makeSyntheticInputSection(segment_names::data, section_names::objcSelrefs,
|
||||
S_LITERAL_POINTERS | S_ATTR_NO_DEAD_STRIP,
|
||||
ArrayRef<uint8_t>{selrefsData, size},
|
||||
/*align=*/target->wordSize);
|
||||
in.objcSelrefs->live = true;
|
||||
|
||||
for (size_t i = 0, n = offsets.size(); i < n; ++i) {
|
||||
in.objcSelrefs->relocs.push_back(
|
||||
{/*type=*/target->unsignedRelocType,
|
||||
/*pcrel=*/false, /*length=*/3,
|
||||
/*offset=*/static_cast<uint32_t>(i * target->wordSize),
|
||||
/*addend=*/offsets[i] * in.objcMethnameSection->align,
|
||||
/*referent=*/in.objcMethnameSection->isec});
|
||||
}
|
||||
|
||||
in.objcSelrefs->parent =
|
||||
ConcatOutputSection::getOrCreateForInput(in.objcSelrefs);
|
||||
inputSections.push_back(in.objcSelrefs);
|
||||
in.objcSelrefs->isFinal = true;
|
||||
}
|
||||
|
||||
uint64_t ObjCStubsSection::getSize() const {
|
||||
return target->objcStubsFastSize * symbols.size();
|
||||
}
|
||||
|
||||
void ObjCStubsSection::writeTo(uint8_t *buf) const {
|
||||
assert(in.objcSelrefs->live);
|
||||
assert(in.objcSelrefs->isFinal);
|
||||
|
||||
uint64_t stubOffset = 0;
|
||||
for (size_t i = 0, n = symbols.size(); i < n; ++i) {
|
||||
Defined *sym = symbols[i];
|
||||
target->writeObjCMsgSendStub(buf + stubOffset, sym, in.objcStubs->addr,
|
||||
stubOffset, in.objcSelrefs->getVA(), i,
|
||||
in.got->addr, objcMsgSendGotIndex);
|
||||
stubOffset += target->objcStubsFastSize;
|
||||
}
|
||||
}
|
||||
|
||||
LazyPointerSection::LazyPointerSection()
|
||||
: SyntheticSection(segment_names::data, section_names::lazySymbolPtr) {
|
||||
align = target->wordSize;
|
||||
|
@ -1423,8 +1502,8 @@ void BitcodeBundleSection::writeTo(uint8_t *buf) const {
|
|||
remove(xarPath);
|
||||
}
|
||||
|
||||
CStringSection::CStringSection()
|
||||
: SyntheticSection(segment_names::text, section_names::cString) {
|
||||
CStringSection::CStringSection(const char *name)
|
||||
: SyntheticSection(segment_names::text, name) {
|
||||
flags = S_CSTRING_LITERALS;
|
||||
}
|
||||
|
||||
|
@ -1550,6 +1629,16 @@ void DeduplicatedCStringSection::writeTo(uint8_t *buf) const {
|
|||
}
|
||||
}
|
||||
|
||||
DeduplicatedCStringSection::StringOffset
|
||||
DeduplicatedCStringSection::getStringOffset(StringRef str) const {
|
||||
// StringPiece uses 31 bits to store the hashes, so we replicate that
|
||||
uint32_t hash = xxHash64(str) & 0x7fffffff;
|
||||
auto offset = stringOffsetMap.find(CachedHashStringRef(str, hash));
|
||||
assert(offset != stringOffsetMap.end() &&
|
||||
"Looked-up strings should always exist in section");
|
||||
return offset->second;
|
||||
}
|
||||
|
||||
// This section is actually emitted as __TEXT,__const by ld64, but clang may
|
||||
// emit input sections of that name, and LLD doesn't currently support mixing
|
||||
// synthetic and concat-type OutputSections. To work around this, I've given
|
||||
|
|
|
@ -310,6 +310,30 @@ public:
|
|||
Defined *dyldPrivate = nullptr;
|
||||
};
|
||||
|
||||
// Objective-C stubs are hoisted objc_msgSend calls per selector called in the
|
||||
// program. Apple Clang produces undefined symbols to each stub, such as
|
||||
// '_objc_msgSend$foo', which are then synthesized by the linker. The stubs
|
||||
// load the particular selector 'foo' from __objc_selrefs, setting it to the
|
||||
// first argument of the objc_msgSend call, and then jumps to objc_msgSend. The
|
||||
// actual stub contents are mirrored from ld64.
|
||||
class ObjCStubsSection final : public SyntheticSection {
|
||||
public:
|
||||
ObjCStubsSection();
|
||||
void addEntry(Symbol *sym);
|
||||
uint64_t getSize() const override;
|
||||
bool isNeeded() const override { return !symbols.empty(); }
|
||||
void finalize() override { isec->isFinal = true; }
|
||||
void writeTo(uint8_t *buf) const override;
|
||||
void setup();
|
||||
|
||||
static constexpr llvm::StringLiteral symbolPrefix = "_objc_msgSend$";
|
||||
|
||||
private:
|
||||
std::vector<Defined *> symbols;
|
||||
std::vector<uint32_t> offsets;
|
||||
int objcMsgSendGotIndex = 0;
|
||||
};
|
||||
|
||||
// Note that this section may also be targeted by non-lazy bindings. In
|
||||
// particular, this happens when branch relocations target weak symbols.
|
||||
class LazyPointerSection final : public SyntheticSection {
|
||||
|
@ -514,7 +538,7 @@ private:
|
|||
|
||||
class CStringSection : public SyntheticSection {
|
||||
public:
|
||||
CStringSection();
|
||||
CStringSection(const char *name);
|
||||
void addInput(CStringInputSection *);
|
||||
uint64_t getSize() const override { return size; }
|
||||
virtual void finalizeContents();
|
||||
|
@ -529,17 +553,21 @@ private:
|
|||
|
||||
class DeduplicatedCStringSection final : public CStringSection {
|
||||
public:
|
||||
DeduplicatedCStringSection(const char *name) : CStringSection(name){};
|
||||
uint64_t getSize() const override { return size; }
|
||||
void finalizeContents() override;
|
||||
void writeTo(uint8_t *buf) const override;
|
||||
|
||||
private:
|
||||
struct StringOffset {
|
||||
uint8_t trailingZeros;
|
||||
uint64_t outSecOff = UINT64_MAX;
|
||||
|
||||
explicit StringOffset(uint8_t zeros) : trailingZeros(zeros) {}
|
||||
};
|
||||
|
||||
StringOffset getStringOffset(StringRef str) const;
|
||||
|
||||
private:
|
||||
llvm::DenseMap<llvm::CachedHashStringRef, StringOffset> stringOffsetMap;
|
||||
size_t size = 0;
|
||||
};
|
||||
|
@ -623,6 +651,7 @@ struct InStruct {
|
|||
const uint8_t *bufferStart = nullptr;
|
||||
MachHeaderSection *header = nullptr;
|
||||
CStringSection *cStringSection = nullptr;
|
||||
DeduplicatedCStringSection *objcMethnameSection = nullptr;
|
||||
WordLiteralSection *wordLiteralSection = nullptr;
|
||||
RebaseSection *rebase = nullptr;
|
||||
BindingSection *binding = nullptr;
|
||||
|
@ -634,6 +663,8 @@ struct InStruct {
|
|||
LazyPointerSection *lazyPointers = nullptr;
|
||||
StubsSection *stubs = nullptr;
|
||||
StubHelperSection *stubHelper = nullptr;
|
||||
ObjCStubsSection *objcStubs = nullptr;
|
||||
ConcatInputSection *objcSelrefs = nullptr;
|
||||
UnwindInfoSection *unwindInfo = nullptr;
|
||||
ObjCImageInfoSection *objCImageInfo = nullptr;
|
||||
ConcatInputSection *imageLoaderCache = nullptr;
|
||||
|
|
|
@ -57,6 +57,12 @@ public:
|
|||
virtual void writeStubHelperEntry(uint8_t *buf, const Symbol &,
|
||||
uint64_t entryAddr) const = 0;
|
||||
|
||||
virtual void writeObjCMsgSendStub(uint8_t *buf, Symbol *sym,
|
||||
uint64_t stubsAddr, uint64_t stubOffset,
|
||||
uint64_t selrefsVA, uint64_t selectorIndex,
|
||||
uint64_t gotAddr,
|
||||
uint64_t msgSendIndex) const = 0;
|
||||
|
||||
// Symbols may be referenced via either the GOT or the stubs section,
|
||||
// depending on the relocation type. prepareSymbolRelocation() will set up the
|
||||
// GOT/stubs entries, and resolveSymbolVA() will return the addresses of those
|
||||
|
@ -104,6 +110,8 @@ public:
|
|||
size_t stubSize;
|
||||
size_t stubHelperHeaderSize;
|
||||
size_t stubHelperEntrySize;
|
||||
size_t objcStubsFastSize;
|
||||
size_t objcStubsAlignment;
|
||||
uint8_t p2WordSize;
|
||||
size_t wordSize;
|
||||
|
||||
|
|
|
@ -695,6 +695,9 @@ void Writer::scanSymbols() {
|
|||
continue;
|
||||
dysym->getFile()->refState =
|
||||
std::max(dysym->getFile()->refState, dysym->getRefState());
|
||||
} else if (const auto *undefined = dyn_cast<Undefined>(sym)) {
|
||||
if (sym->getName().startswith(ObjCStubsSection::symbolPrefix))
|
||||
in.objcStubs->addEntry(sym);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1165,6 +1168,8 @@ template <class LP> void Writer::run() {
|
|||
// these two scan* methods. I.e. from this point onward, for all live
|
||||
// InputSections, we should have `isec->canonical() == isec`.
|
||||
scanSymbols();
|
||||
if (in.objcStubs->isNeeded())
|
||||
in.objcStubs->setup();
|
||||
scanRelocations();
|
||||
|
||||
// Do not proceed if there was an undefined symbol.
|
||||
|
@ -1208,9 +1213,12 @@ void macho::resetWriter() { LCDylib::resetInstanceCount(); }
|
|||
void macho::createSyntheticSections() {
|
||||
in.header = make<MachHeaderSection>();
|
||||
if (config->dedupLiterals)
|
||||
in.cStringSection = make<DeduplicatedCStringSection>();
|
||||
in.cStringSection =
|
||||
make<DeduplicatedCStringSection>(section_names::cString);
|
||||
else
|
||||
in.cStringSection = make<CStringSection>();
|
||||
in.cStringSection = make<CStringSection>(section_names::cString);
|
||||
in.objcMethnameSection =
|
||||
make<DeduplicatedCStringSection>(section_names::objcMethname);
|
||||
in.wordLiteralSection =
|
||||
config->dedupLiterals ? make<WordLiteralSection>() : nullptr;
|
||||
in.rebase = make<RebaseSection>();
|
||||
|
@ -1223,6 +1231,7 @@ void macho::createSyntheticSections() {
|
|||
in.lazyPointers = make<LazyPointerSection>();
|
||||
in.stubs = make<StubsSection>();
|
||||
in.stubHelper = make<StubHelperSection>();
|
||||
in.objcStubs = make<ObjCStubsSection>();
|
||||
in.unwindInfo = makeUnwindInfoSection();
|
||||
in.objCImageInfo = make<ObjCImageInfoSection>();
|
||||
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# REQUIRES: aarch64
|
||||
|
||||
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o
|
||||
# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o
|
||||
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
|
||||
# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -dead_strip
|
||||
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
|
||||
# RUN: %lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_fast
|
||||
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
|
||||
# RUN: %no-fatal-warnings-lld -arch arm64 -lSystem -o %t.out %t.o -objc_stubs_small 2>&1 | FileCheck %s --check-prefix=WARNING
|
||||
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
|
||||
|
||||
# WARNING: warning: -objc_stubs_small is not yet implemented, defaulting to -objc_stubs_fast
|
||||
|
||||
# CHECK: Contents of (__TEXT,__objc_stubs) section
|
||||
|
||||
# CHECK-NEXT: _objc_msgSend$foo:
|
||||
# CHECK-NEXT: adrp x1, 8 ; 0x100008000
|
||||
# CHECK-NEXT: ldr x1, [x1, #0x10]
|
||||
# CHECK-NEXT: adrp x16, 4 ; 0x100004000
|
||||
# CHECK-NEXT: ldr x16, [x16]
|
||||
# CHECK-NEXT: br x16
|
||||
# CHECK-NEXT: brk #0x1
|
||||
# CHECK-NEXT: brk #0x1
|
||||
# CHECK-NEXT: brk #0x1
|
||||
|
||||
# CHECK-NEXT: _objc_msgSend$length:
|
||||
# CHECK-NEXT: adrp x1, 8 ; 0x100008000
|
||||
# CHECK-NEXT: ldr x1, [x1, #0x18]
|
||||
# CHECK-NEXT: adrp x16, 4 ; 0x100004000
|
||||
# CHECK-NEXT: ldr x16, [x16]
|
||||
# CHECK-NEXT: br x16
|
||||
# CHECK-NEXT: brk #0x1
|
||||
# CHECK-NEXT: brk #0x1
|
||||
# CHECK-NEXT: brk #0x1
|
||||
|
||||
# CHECK-EMPTY:
|
||||
|
||||
.section __TEXT,__objc_methname,cstring_literals
|
||||
lselref1:
|
||||
.asciz "foo"
|
||||
lselref2:
|
||||
.asciz "bar"
|
||||
|
||||
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
|
||||
.p2align 3
|
||||
.quad lselref1
|
||||
.quad lselref2
|
||||
|
||||
.text
|
||||
.globl _objc_msgSend
|
||||
_objc_msgSend:
|
||||
ret
|
||||
|
||||
.globl _main
|
||||
_main:
|
||||
bl _objc_msgSend$length
|
||||
bl _objc_msgSend$foo
|
||||
bl _objc_msgSend$foo
|
||||
ret
|
|
@ -0,0 +1,44 @@
|
|||
# REQUIRES: aarch64
|
||||
# RUN: rm -rf %t; split-file %s %t
|
||||
|
||||
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/strings.s -o %t/strings.o
|
||||
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/main.s -o %t/main.o
|
||||
|
||||
# RUN: %lld -arch arm64 -lSystem -o %t.out %t/strings.o %t/main.o
|
||||
|
||||
# RUN: llvm-otool -vs __TEXT __cstring %t.out | FileCheck %s --check-prefix=CSTRING
|
||||
# RUN: llvm-otool -vs __TEXT __objc_methname %t.out | FileCheck %s --check-prefix=METHNAME
|
||||
|
||||
# RUN: %lld -arch arm64 -lSystem -o %t/duplicates %t/strings.o %t/strings.o %t/main.o --deduplicate-literals
|
||||
|
||||
# RUN: llvm-otool -vs __TEXT __cstring %t/duplicates | FileCheck %s --check-prefix=CSTRING
|
||||
# RUN: llvm-otool -vs __TEXT __objc_methname %t/duplicates | FileCheck %s --check-prefix=METHNAME
|
||||
|
||||
# CSTRING: Contents of (__TEXT,__cstring) section
|
||||
# CSTRING-NEXT: existing-cstring
|
||||
# CSTIRNG-EMPTY:
|
||||
|
||||
# METHNAME: Contents of (__TEXT,__objc_methname) section
|
||||
# METHNAME-NEXT: existing_methname
|
||||
# METHNAME-NEXT: synthetic_methname
|
||||
# METHNAME-EMPTY:
|
||||
|
||||
#--- strings.s
|
||||
.cstring
|
||||
.p2align 2
|
||||
.asciz "existing-cstring"
|
||||
|
||||
.section __TEXT,__objc_methname,cstring_literals
|
||||
.asciz "existing_methname"
|
||||
|
||||
#--- main.s
|
||||
.text
|
||||
.globl _objc_msgSend
|
||||
_objc_msgSend:
|
||||
ret
|
||||
|
||||
.globl _main
|
||||
_main:
|
||||
bl _objc_msgSend$existing_methname
|
||||
bl _objc_msgSend$synthetic_methname
|
||||
ret
|
|
@ -0,0 +1,50 @@
|
|||
# REQUIRES: aarch64
|
||||
# RUN: rm -rf %t; split-file %s %t
|
||||
|
||||
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/existing.s -o %t/existing.o
|
||||
# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/main.s -o %t/main.o
|
||||
|
||||
# RUN: %lld -arch arm64 -lSystem -o %t/out %t/existing.o %t/main.o
|
||||
# RUN: llvm-otool -vs __DATA __objc_selrefs %t/out | FileCheck %s --check-prefix=SELREFS
|
||||
|
||||
# SELREFS: Contents of (__DATA,__objc_selrefs) section
|
||||
# SELREFS-NEXT: __TEXT:__objc_methname:foo
|
||||
# SELREFS-NEXT: __TEXT:__objc_methname:bar
|
||||
# SELREFS-NEXT: __TEXT:__objc_methname:foo
|
||||
# SELREFS-NEXT: __TEXT:__objc_methname:length
|
||||
# SELREFS-EMPTY:
|
||||
|
||||
# RUN: %lld -arch arm64 -lSystem -o %t/out %t/existing.o %t/main.o --deduplicate-literals
|
||||
# RUN: llvm-otool -vs __DATA __objc_selrefs %t/out | FileCheck %s --check-prefix=DEDUP
|
||||
|
||||
# DEDUP: Contents of (__DATA,__objc_selrefs) section
|
||||
# DEDUP-NEXT: __TEXT:__objc_methname:foo
|
||||
# DEDUP-NEXT: __TEXT:__objc_methname:bar
|
||||
# NOTE: Ideally this wouldn't exist, but while it does it needs to point to the deduplicated string
|
||||
# DEDUP-NEXT: __TEXT:__objc_methname:foo
|
||||
# DEDUP-NEXT: __TEXT:__objc_methname:length
|
||||
# DEDUP-EMPTY:
|
||||
|
||||
#--- existing.s
|
||||
.section __TEXT,__objc_methname,cstring_literals
|
||||
lselref1:
|
||||
.asciz "foo"
|
||||
lselref2:
|
||||
.asciz "bar"
|
||||
|
||||
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
|
||||
.p2align 3
|
||||
.quad lselref1
|
||||
.quad lselref2
|
||||
|
||||
#--- main.s
|
||||
.text
|
||||
.globl _objc_msgSend
|
||||
_objc_msgSend:
|
||||
ret
|
||||
|
||||
.globl _main
|
||||
_main:
|
||||
bl _objc_msgSend$length
|
||||
bl _objc_msgSend$foo
|
||||
ret
|
|
@ -0,0 +1,40 @@
|
|||
# REQUIRES: x86
|
||||
|
||||
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin %s -o %t.o
|
||||
# RUN: %lld -arch x86_64 -lSystem -o %t.out %t.o
|
||||
# RUN: llvm-otool -vs __TEXT __objc_stubs %t.out | FileCheck %s
|
||||
|
||||
# CHECK: Contents of (__TEXT,__objc_stubs) section
|
||||
|
||||
# CHECK-NEXT: _objc_msgSend$foo:
|
||||
# CHECK-NEXT: 00000001000004b8 movq 0x1b51(%rip), %rsi
|
||||
# CHECK-NEXT: 00000001000004bf jmpq *0xb3b(%rip)
|
||||
|
||||
# CHECK-NEXT: _objc_msgSend$length:
|
||||
# CHECK-NEXT: 00000001000004c5 movq 0x1b4c(%rip), %rsi
|
||||
# CHECK-NEXT: 00000001000004cc jmpq *0xb2e(%rip)
|
||||
|
||||
# CHECK-EMPTY:
|
||||
|
||||
.section __TEXT,__objc_methname,cstring_literals
|
||||
lselref1:
|
||||
.asciz "foo"
|
||||
lselref2:
|
||||
.asciz "bar"
|
||||
|
||||
.section __DATA,__objc_selrefs,literal_pointers,no_dead_strip
|
||||
.p2align 3
|
||||
.quad lselref1
|
||||
.quad lselref2
|
||||
|
||||
.text
|
||||
.globl _objc_msgSend
|
||||
_objc_msgSend:
|
||||
ret
|
||||
|
||||
.globl _main
|
||||
_main:
|
||||
callq _objc_msgSend$length
|
||||
callq _objc_msgSend$foo
|
||||
callq _objc_msgSend$foo
|
||||
ret
|
Loading…
Reference in New Issue