[lld] a3f67f0 - [lld-macho] Initial support for Linker Optimization Hints
Daniel Bertalan via llvm-commits
llvm-commits at lists.llvm.org
Wed Jun 29 21:29:38 PDT 2022
Author: Daniel Bertalan
Date: 2022-06-30T06:28:42+02:00
New Revision: a3f67f0920eaa111637b3411209213f46de202d7
URL: https://github.com/llvm/llvm-project/commit/a3f67f0920eaa111637b3411209213f46de202d7
DIFF: https://github.com/llvm/llvm-project/commit/a3f67f0920eaa111637b3411209213f46de202d7.diff
LOG: [lld-macho] Initial support for Linker Optimization Hints
Linker optimization hints mark a sequence of instructions used for
synthesizing an address, like ADRP+ADD. If the referenced symbol ends up
close enough, it can be replaced by a faster sequence of instructions
like ADR+NOP.
This commit adds support for 2 of the 7 defined ARM64 optimization
hints:
- LOH_ARM64_ADRP_ADD, which transforms a pair of ADRP+ADD into ADR+NOP
if the referenced address is within +/- 1 MiB
- LOH_ARM64_ADRP_ADRP, which transforms two ADRP instructions into
ADR+NOP if they reference the same page
These two kinds already cover more than 50% of all LOHs in
chromium_framework.
Differential Review: https://reviews.llvm.org/D128093
Added:
lld/test/MachO/invalid/invalid-loh.s
lld/test/MachO/loh-adrp-add.s
lld/test/MachO/loh-adrp-adrp.s
Modified:
lld/MachO/Arch/ARM64.cpp
lld/MachO/Config.h
lld/MachO/Driver.cpp
lld/MachO/InputFiles.cpp
lld/MachO/InputFiles.h
lld/MachO/InputSection.cpp
lld/MachO/InputSection.h
lld/MachO/Options.td
lld/MachO/Relocations.h
lld/MachO/Target.h
llvm/include/llvm/BinaryFormat/MachO.h
Removed:
################################################################################
diff --git a/lld/MachO/Arch/ARM64.cpp b/lld/MachO/Arch/ARM64.cpp
index e5b8a1b722f66..8c0c2d7e716ad 100644
--- a/lld/MachO/Arch/ARM64.cpp
+++ b/lld/MachO/Arch/ARM64.cpp
@@ -36,6 +36,8 @@ struct ARM64 : ARM64Common {
uint64_t entryAddr) const override;
const RelocAttrs &getRelocAttrs(uint8_t type) const override;
void populateThunk(InputSection *thunk, Symbol *funcSym) override;
+ void applyOptimizationHints(uint8_t *, const ConcatInputSection *,
+ ArrayRef<uint64_t>) const override;
};
} // namespace
@@ -150,6 +152,207 @@ ARM64::ARM64() : ARM64Common(LP64()) {
stubHelperEntrySize = sizeof(stubHelperEntryCode);
}
+namespace {
+struct Adrp {
+ uint32_t destRegister;
+};
+
+struct Add {
+ uint8_t destRegister;
+ uint8_t srcRegister;
+ uint32_t addend;
+};
+
+struct PerformedReloc {
+ const Reloc &rel;
+ uint64_t referentVA;
+};
+
+class OptimizationHintContext {
+public:
+ OptimizationHintContext(uint8_t *buf, const ConcatInputSection *isec,
+ ArrayRef<uint64_t> relocTargets)
+ : buf(buf), isec(isec), relocTargets(relocTargets),
+ relocIt(isec->relocs.rbegin()) {}
+
+ void applyAdrpAdd(const OptimizationHint &);
+ void applyAdrpAdrp(const OptimizationHint &);
+
+private:
+ uint8_t *buf;
+ const ConcatInputSection *isec;
+ ArrayRef<uint64_t> relocTargets;
+ std::vector<Reloc>::const_reverse_iterator relocIt;
+
+ uint64_t getRelocTarget(const Reloc &);
+
+ Optional<PerformedReloc> findPrimaryReloc(uint64_t offset);
+ Optional<PerformedReloc> findReloc(uint64_t offset);
+};
+} // namespace
+
+static bool parseAdrp(uint32_t insn, Adrp &adrp) {
+ if ((insn & 0x9f000000) != 0x90000000)
+ return false;
+ adrp.destRegister = insn & 0x1f;
+ return true;
+}
+
+static bool parseAdd(uint32_t insn, Add &add) {
+ if ((insn & 0xffc00000) != 0x91000000)
+ return false;
+ add.destRegister = insn & 0x1f;
+ add.srcRegister = (insn >> 5) & 0x1f;
+ add.addend = (insn >> 10) & 0xfff;
+ return true;
+}
+
+static void writeAdr(void *loc, uint32_t dest, int32_t delta) {
+ uint32_t opcode = 0x10000000;
+ uint32_t immHi = (delta & 0x001ffffc) << 3;
+ uint32_t immLo = (delta & 0x00000003) << 29;
+ write32le(loc, opcode | immHi | immLo | dest);
+}
+
+static void writeNop(void *loc) { write32le(loc, 0xd503201f); }
+
+uint64_t OptimizationHintContext::getRelocTarget(const Reloc &reloc) {
+ size_t relocIdx = &reloc - isec->relocs.data();
+ return relocTargets[relocIdx];
+}
+
+// Optimization hints are sorted in a monotonically increasing order by their
+// first address as are relocations (albeit in decreasing order), so if we keep
+// a pointer around to the last found relocation, we don't have to do a full
+// binary search every time.
+Optional<PerformedReloc>
+OptimizationHintContext::findPrimaryReloc(uint64_t offset) {
+ const auto end = isec->relocs.rend();
+ while (relocIt != end && relocIt->offset < offset)
+ ++relocIt;
+ if (relocIt == end || relocIt->offset != offset)
+ return None;
+ return PerformedReloc{*relocIt, getRelocTarget(*relocIt)};
+}
+
+// The second and third addresses of optimization hints have no such
+// monotonicity as the first, so we search the entire range of relocations.
+Optional<PerformedReloc> OptimizationHintContext::findReloc(uint64_t offset) {
+ // Optimization hints often apply to successive relocations, so we check for
+ // that first before doing a full binary search.
+ auto end = isec->relocs.rend();
+ if (relocIt < end - 1 && (relocIt + 1)->offset == offset)
+ return PerformedReloc{*(relocIt + 1), getRelocTarget(*(relocIt + 1))};
+
+ auto reloc = lower_bound(isec->relocs, offset,
+ [](const Reloc &reloc, uint64_t offset) {
+ return offset < reloc.offset;
+ });
+
+ if (reloc == isec->relocs.end() || reloc->offset != offset)
+ return None;
+ return PerformedReloc{*reloc, getRelocTarget(*reloc)};
+}
+
+// Transforms a pair of adrp+add instructions into an adr instruction if the
+// target is within the +/- 1 MiB range allowed by the adr's 21 bit signed
+// immediate offset.
+//
+// adrp xN, _foo at PAGE
+// add xM, xN, _foo at PAGEOFF
+// ->
+// adr xM, _foo
+// nop
+void OptimizationHintContext::applyAdrpAdd(const OptimizationHint &hint) {
+ uint32_t ins1 = read32le(buf + hint.offset0);
+ uint32_t ins2 = read32le(buf + hint.offset0 + hint.delta[0]);
+ Adrp adrp;
+ if (!parseAdrp(ins1, adrp))
+ return;
+ Add add;
+ if (!parseAdd(ins2, add))
+ return;
+ if (adrp.destRegister != add.srcRegister)
+ return;
+
+ Optional<PerformedReloc> rel1 = findPrimaryReloc(hint.offset0);
+ Optional<PerformedReloc> rel2 = findReloc(hint.offset0 + hint.delta[0]);
+ if (!rel1 || !rel2)
+ return;
+ if (rel1->referentVA != rel2->referentVA)
+ return;
+ int64_t delta = rel1->referentVA - rel1->rel.offset - isec->getVA();
+ if (delta >= (1 << 20) || delta < -(1 << 20))
+ return;
+
+ writeAdr(buf + hint.offset0, add.destRegister, delta);
+ writeNop(buf + hint.offset0 + hint.delta[0]);
+}
+
+// Transforms two adrp instructions into a single adrp if their referent
+// addresses are located on the same 4096 byte page.
+//
+// adrp xN, _foo at PAGE
+// adrp xN, _bar at PAGE
+// ->
+// adrp xN, _foo at PAGE
+// nop
+void OptimizationHintContext::applyAdrpAdrp(const OptimizationHint &hint) {
+ uint32_t ins1 = read32le(buf + hint.offset0);
+ uint32_t ins2 = read32le(buf + hint.offset0 + hint.delta[0]);
+ Adrp adrp1, adrp2;
+ if (!parseAdrp(ins1, adrp1) || !parseAdrp(ins2, adrp2))
+ return;
+ if (adrp1.destRegister != adrp2.destRegister)
+ return;
+
+ Optional<PerformedReloc> rel1 = findPrimaryReloc(hint.offset0);
+ Optional<PerformedReloc> rel2 = findReloc(hint.offset0 + hint.delta[0]);
+ if (!rel1 || !rel2)
+ return;
+ if ((rel1->referentVA & ~0xfffULL) != (rel2->referentVA & ~0xfffULL))
+ return;
+
+ writeNop(buf + hint.offset0 + hint.delta[0]);
+}
+
+void ARM64::applyOptimizationHints(uint8_t *buf, const ConcatInputSection *isec,
+ ArrayRef<uint64_t> relocTargets) const {
+ assert(isec);
+ assert(relocTargets.size() == isec->relocs.size());
+
+ // Note: Some of these optimizations might not be valid when shared regions
+ // are in use. Will need to revisit this if splitSegInfo is added.
+
+ OptimizationHintContext ctx1(buf, isec, relocTargets);
+ for (const OptimizationHint &hint : isec->optimizationHints) {
+ switch (hint.type) {
+ case LOH_ARM64_ADRP_ADRP:
+ // This is done in another pass because the other optimization hints
+ // might cause its targets to be turned into NOPs.
+ break;
+ case LOH_ARM64_ADRP_LDR:
+ case LOH_ARM64_ADRP_ADD_LDR:
+ case LOH_ARM64_ADRP_LDR_GOT_LDR:
+ case LOH_ARM64_ADRP_ADD_STR:
+ case LOH_ARM64_ADRP_LDR_GOT_STR:
+ // TODO: Implement these
+ break;
+ case LOH_ARM64_ADRP_ADD:
+ ctx1.applyAdrpAdd(hint);
+ break;
+ case LOH_ARM64_ADRP_LDR_GOT:
+ // TODO: Implement this as well
+ break;
+ }
+ }
+
+ OptimizationHintContext ctx2(buf, isec, relocTargets);
+ for (const OptimizationHint &hint : isec->optimizationHints)
+ if (hint.type == LOH_ARM64_ADRP_ADRP)
+ ctx2.applyAdrpAdrp(hint);
+}
+
TargetInfo *macho::createARM64TargetInfo() {
static ARM64 t;
return &t;
diff --git a/lld/MachO/Config.h b/lld/MachO/Config.h
index 206509c3df83f..b6c6abb44c656 100644
--- a/lld/MachO/Config.h
+++ b/lld/MachO/Config.h
@@ -130,6 +130,7 @@ struct Configuration {
bool dedupLiterals = true;
bool omitDebugInfo = false;
bool warnDylibInstallName = false;
+ bool ignoreOptimizationHints = false;
// Temporary config flag that will be removed once we have fully implemented
// support for __eh_frame.
bool parseEhFrames = false;
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index badab058c84aa..708facd180baa 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -1301,6 +1301,7 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
config->icfLevel != ICFLevel::none;
config->warnDylibInstallName = args.hasFlag(
OPT_warn_dylib_install_name, OPT_no_warn_dylib_install_name, false);
+ config->ignoreOptimizationHints = args.hasArg(OPT_ignore_optimization_hints);
config->callGraphProfileSort = args.hasFlag(
OPT_call_graph_profile_sort, OPT_no_call_graph_profile_sort, true);
config->printSymbolOrder = args.getLastArgValue(OPT_print_symbol_order);
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 439a1fe032626..c53874133a788 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -65,6 +65,7 @@
#include "llvm/LTO/LTO.h"
#include "llvm/Support/BinaryStreamReader.h"
#include "llvm/Support/Endian.h"
+#include "llvm/Support/LEB128.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/TarWriter.h"
@@ -449,6 +450,154 @@ static Defined *findSymbolAtOffset(const ConcatInputSection *isec,
return *it;
}
+// Linker optimization hints mark a sequence of instructions used for
+// synthesizing an address which that be transformed into a faster sequence. The
+// transformations depend on conditions that are determined at link time, like
+// the distance to the referenced symbol or its alignment.
+//
+// Each hint has a type and refers to 2 or 3 instructions. Each of those
+// instructions must have a corresponding relocation. After addresses have been
+// finalized and relocations have been performed, we check if the requirements
+// hold, and perform the optimizations if they do.
+//
+// Similar linker relaxations exist for ELF as well, with the
diff erence being
+// that the explicit marking allows for the relaxation of non-consecutive
+// relocations too.
+//
+// The specific types of hints are documented in Arch/ARM64.cpp
+void ObjFile::parseOptimizationHints(ArrayRef<uint8_t> data) {
+ auto expectedArgCount = [](uint8_t type) {
+ switch (type) {
+ case LOH_ARM64_ADRP_ADRP:
+ case LOH_ARM64_ADRP_LDR:
+ case LOH_ARM64_ADRP_ADD:
+ case LOH_ARM64_ADRP_LDR_GOT:
+ return 2;
+ case LOH_ARM64_ADRP_ADD_LDR:
+ case LOH_ARM64_ADRP_ADD_STR:
+ case LOH_ARM64_ADRP_LDR_GOT_LDR:
+ case LOH_ARM64_ADRP_LDR_GOT_STR:
+ return 3;
+ }
+ return -1;
+ };
+
+ // Each hint contains at least 4 ULEB128-encoded fields, so in the worst case,
+ // there are data.size() / 4 LOHs. It's a huge overestimation though, as
+ // offsets are unlikely to fall in the 0-127 byte range, so we pre-allocate
+ // half as much.
+ optimizationHints.reserve(data.size() / 8);
+
+ for (const uint8_t *p = data.begin(); p < data.end();) {
+ const ptr
diff _t inputOffset = p - data.begin();
+ unsigned int n = 0;
+ uint8_t type = decodeULEB128(p, &n, data.end());
+ p += n;
+
+ // An entry of type 0 terminates the list.
+ if (type == 0)
+ break;
+
+ int expectedCount = expectedArgCount(type);
+ if (LLVM_UNLIKELY(expectedCount == -1)) {
+ error("Linker optimization hint at offset " + Twine(inputOffset) +
+ " has unknown type " + Twine(type));
+ return;
+ }
+
+ uint8_t argCount = decodeULEB128(p, &n, data.end());
+ p += n;
+
+ if (LLVM_UNLIKELY(argCount != expectedCount)) {
+ error("Linker optimization hint at offset " + Twine(inputOffset) +
+ " has " + Twine(argCount) + " arguments instead of the expected " +
+ Twine(expectedCount));
+ return;
+ }
+
+ uint64_t offset0 = decodeULEB128(p, &n, data.end());
+ p += n;
+
+ int16_t delta[2];
+ for (int i = 0; i < argCount - 1; ++i) {
+ uint64_t address = decodeULEB128(p, &n, data.end());
+ p += n;
+ int64_t d = address - offset0;
+ if (LLVM_UNLIKELY(d > std::numeric_limits<int16_t>::max() ||
+ d < std::numeric_limits<int16_t>::min())) {
+ error("Linker optimization hint at offset " + Twine(inputOffset) +
+ " has addresses too far apart");
+ return;
+ }
+ delta[i] = d;
+ }
+
+ optimizationHints.push_back({offset0, {delta[0], delta[1]}, type});
+ }
+
+ // We sort the per-object vector of optimization hints so each section only
+ // needs to hold an ArrayRef to a contiguous range of hints.
+ llvm::sort(optimizationHints,
+ [](const OptimizationHint &a, const OptimizationHint &b) {
+ return a.offset0 < b.offset0;
+ });
+
+ auto section = sections.begin();
+ auto subsection = (*section)->subsections.begin();
+ uint64_t subsectionBase = 0;
+ uint64_t subsectionEnd = 0;
+
+ auto updateAddr = [&]() {
+ subsectionBase = (*section)->addr + subsection->offset;
+ subsectionEnd = subsectionBase + subsection->isec->getSize();
+ };
+
+ auto advanceSubsection = [&]() {
+ if (section == sections.end())
+ return;
+ ++subsection;
+ if (subsection == (*section)->subsections.end()) {
+ ++section;
+ if (section == sections.end())
+ return;
+ subsection = (*section)->subsections.begin();
+ }
+ };
+
+ updateAddr();
+ auto hintStart = optimizationHints.begin();
+ for (auto hintEnd = hintStart, end = optimizationHints.end(); hintEnd != end;
+ ++hintEnd) {
+ if (hintEnd->offset0 >= subsectionEnd) {
+ subsection->isec->optimizationHints =
+ ArrayRef<OptimizationHint>(&*hintStart, hintEnd - hintStart);
+
+ hintStart = hintEnd;
+ while (hintStart->offset0 >= subsectionEnd) {
+ advanceSubsection();
+ if (section == sections.end())
+ break;
+ updateAddr();
+ }
+ }
+
+ hintEnd->offset0 -= subsectionBase;
+ for (int i = 0, count = expectedArgCount(hintEnd->type); i < count - 1;
+ ++i) {
+ if (LLVM_UNLIKELY(
+ hintEnd->delta[i] < -static_cast<int64_t>(hintEnd->offset0) ||
+ hintEnd->delta[i] >=
+ static_cast<int64_t>(subsectionEnd - hintEnd->offset0))) {
+ error("Linker optimization hint spans multiple sections");
+ return;
+ }
+ }
+ }
+ if (section != sections.end())
+ subsection->isec->optimizationHints = ArrayRef<OptimizationHint>(
+ &*hintStart, optimizationHints.end() - hintStart);
+}
+
template <class SectionHeader>
static bool validateRelocationInfo(InputFile *file, const SectionHeader &sec,
relocation_info rel) {
@@ -949,6 +1098,11 @@ template <class LP> void ObjFile::parse() {
if (!sections[i]->subsections.empty())
parseRelocations(sectionHeaders, sectionHeaders[i], *sections[i]);
+ if (!config->ignoreOptimizationHints)
+ if (auto *cmd = findCommand<linkedit_data_command>(
+ hdr, LC_LINKER_OPTIMIZATION_HINT))
+ parseOptimizationHints({buf + cmd->dataoff, cmd->datasize});
+
parseDebugInfo();
Section *ehFrameSection = nullptr;
diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h
index c8e6332394000..524418b91ee1a 100644
--- a/lld/MachO/InputFiles.h
+++ b/lld/MachO/InputFiles.h
@@ -173,6 +173,7 @@ class ObjFile final : public InputFile {
std::vector<ConcatInputSection *> debugSections;
std::vector<CallGraphEntry> callGraph;
llvm::DenseMap<ConcatInputSection *, FDE> fdes;
+ std::vector<OptimizationHint> optimizationHints;
private:
llvm::once_flag initDwarf;
@@ -188,6 +189,7 @@ class ObjFile final : public InputFile {
void parseRelocations(ArrayRef<SectionHeader> sectionHeaders,
const SectionHeader &, Section &);
void parseDebugInfo();
+ void parseOptimizationHints(ArrayRef<uint8_t> data);
void splitEhFrames(ArrayRef<uint8_t> dataArr, Section &ehFrameSection);
void registerCompactUnwind(Section &compactUnwindSection);
void registerEhFrames(Section &ehFrameSection);
diff --git a/lld/MachO/InputSection.cpp b/lld/MachO/InputSection.cpp
index e683e0dbe3b7f..25eb878736d9f 100644
--- a/lld/MachO/InputSection.cpp
+++ b/lld/MachO/InputSection.cpp
@@ -29,8 +29,8 @@ using namespace lld::macho;
// Verify ConcatInputSection's size on 64-bit builds. The size of std::vector
// can
diff er based on STL debug levels (e.g. iterator debugging on MSVC's STL),
// so account for that.
-static_assert(sizeof(void *) != 8 ||
- sizeof(ConcatInputSection) == sizeof(std::vector<Reloc>) + 88,
+static_assert(sizeof(void *) != 8 || sizeof(ConcatInputSection) ==
+ sizeof(std::vector<Reloc>) + 104,
"Try to minimize ConcatInputSection's size, we create many "
"instances of it");
@@ -177,6 +177,10 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
memcpy(buf, data.data(), data.size());
+ std::vector<uint64_t> relocTargets;
+ if (!optimizationHints.empty())
+ relocTargets.reserve(relocs.size());
+
for (size_t i = 0; i < relocs.size(); i++) {
const Reloc &r = relocs[i];
uint8_t *loc = buf + r.offset;
@@ -212,7 +216,13 @@ void ConcatInputSection::writeTo(uint8_t *buf) {
referentVA = referentIsec->getVA(r.addend);
}
target->relocateOne(loc, r, referentVA, getVA() + r.offset);
+
+ if (!optimizationHints.empty())
+ relocTargets.push_back(referentVA);
}
+
+ if (!optimizationHints.empty())
+ target->applyOptimizationHints(buf, this, relocTargets);
}
ConcatInputSection *macho::makeSyntheticInputSection(StringRef segName,
diff --git a/lld/MachO/InputSection.h b/lld/MachO/InputSection.h
index e7f8f10e32635..e8710c25f5347 100644
--- a/lld/MachO/InputSection.h
+++ b/lld/MachO/InputSection.h
@@ -83,6 +83,7 @@ class InputSection {
OutputSection *parent = nullptr;
ArrayRef<uint8_t> data;
std::vector<Reloc> relocs;
+ ArrayRef<OptimizationHint> optimizationHints;
// The symbols that belong to this InputSection, sorted by value. With
// .subsections_via_symbols, there is typically only one element here.
llvm::TinyPtrVector<Defined *> symbols;
diff --git a/lld/MachO/Options.td b/lld/MachO/Options.td
index c3ccbec6c997b..9b57f8a0bd498 100644
--- a/lld/MachO/Options.td
+++ b/lld/MachO/Options.td
@@ -1257,8 +1257,7 @@ def ignore_auto_link : Flag<["-"], "ignore_auto_link">,
Flags<[HelpHidden]>,
Group<grp_undocumented>;
def ignore_optimization_hints : Flag<["-"], "ignore_optimization_hints">,
- HelpText<"This option is undocumented in ld64">,
- Flags<[HelpHidden]>,
+ HelpText<"Ignore Linker Optimization Hints">,
Group<grp_undocumented>;
def init_offsets : Flag<["-"], "init_offsets">,
HelpText<"This option is undocumented in ld64">,
diff --git a/lld/MachO/Relocations.h b/lld/MachO/Relocations.h
index 3c134d55cb201..6c0475fe02000 100644
--- a/lld/MachO/Relocations.h
+++ b/lld/MachO/Relocations.h
@@ -70,6 +70,14 @@ struct Reloc {
addend(addend), referent(referent) {}
};
+struct OptimizationHint {
+ // Offset of the first address within the containing InputSection.
+ uint64_t offset0;
+ // Offset of the other addresses relative to the first one.
+ int16_t delta[2];
+ uint8_t type;
+};
+
bool validateSymbolRelocation(const Symbol *, const InputSection *,
const Reloc &);
diff --git a/lld/MachO/Target.h b/lld/MachO/Target.h
index e66a6966b59d7..09ff3c5639ea6 100644
--- a/lld/MachO/Target.h
+++ b/lld/MachO/Target.h
@@ -28,6 +28,7 @@ class Symbol;
class Defined;
class DylibSymbol;
class InputSection;
+class ConcatInputSection;
class TargetInfo {
public:
@@ -78,6 +79,9 @@ class TargetInfo {
bool usesThunks() const { return thunkSize > 0; }
+ virtual void applyOptimizationHints(uint8_t *buf, const ConcatInputSection *,
+ llvm::ArrayRef<uint64_t>) const {};
+
uint32_t magic;
llvm::MachO::CPUType cpuType;
uint32_t cpuSubtype;
diff --git a/lld/test/MachO/invalid/invalid-loh.s b/lld/test/MachO/invalid/invalid-loh.s
new file mode 100644
index 0000000000000..19ed52866948a
--- /dev/null
+++ b/lld/test/MachO/invalid/invalid-loh.s
@@ -0,0 +1,39 @@
+# REQUIRES: aarch64
+
+# RUN: rm -rf %t; split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/section.s -o %t/section.o
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %t/far.s -o %t/far.o
+# RUN: not %lld -arch arm64 %t/section.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=SECTION
+# RUN: not %lld -arch arm64 %t/far.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=FAR
+
+# SECTION: error: Linker optimization hint spans multiple sections
+# FAR: error: Linker optimization hint at offset 0 has addresses too far apart
+
+#--- section.s
+.globl _main
+_main:
+L1:
+ adrp x0, _target at PAGE
+
+_foo:
+L2:
+ add x0, x0, _target at PAGEOFF
+
+_target:
+
+.loh AdrpAdd L1, L2
+.subsections_via_symbols
+
+#--- far.s
+.globl _main
+_main:
+L1:
+ adrp x0, _target at PAGE
+ .zero 0x8000
+L2:
+ add x0, x0, _target at PAGEOFF
+
+_target:
+
+.loh AdrpAdd L1, L2
+.subsections_via_symbols
diff --git a/lld/test/MachO/loh-adrp-add.s b/lld/test/MachO/loh-adrp-add.s
new file mode 100644
index 0000000000000..e5430b38e83d5
--- /dev/null
+++ b/lld/test/MachO/loh-adrp-add.s
@@ -0,0 +1,98 @@
+# REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o
+# RUN: %lld -arch arm64 %t.o -o %t
+# RUN: llvm-objdump -d --macho %t | FileCheck %s
+
+# CHECK-LABEL: _main:
+## Out of range, before
+# CHECK-NEXT: adrp x0
+# CHECK-NEXT: add x0, x0
+## In range, before
+# CHECK-NEXT: adr x1
+# CHECK-NEXT: nop
+## Registers don't match (invalid input)
+# CHECK-NEXT: adrp x2
+# CHECK-NEXT: add x0
+## Targets don't match (invalid input)
+# CHECK-NEXT: adrp x3
+# CHECK-NEXT: add x3
+## Not an adrp instruction (invalid input)
+# CHECK-NEXT: nop
+# CHECK-NEXT: add x4
+## In range, after
+# CHECK-NEXT: adr x5
+# CHECK-NEXT: nop
+## In range, add's destination register is not the same as its source
+# CHECK-NEXT: adr x7
+# CHECK-NEXT: nop
+## Valid, non-adjacent instructions - start
+# CHECK-NEXT: adr x8
+## Out of range, after
+# CHECK-NEXT: adrp x9
+# CHECK-NEXT: add x9, x9
+## Valid, non-adjacent instructions - end
+# CHECK-NEXT: nop
+
+.text
+.align 2
+_before_far:
+ .space 1048576
+
+_before_near:
+ nop
+
+.globl _main
+_main:
+L1:
+ adrp x0, _before_far at PAGE
+L2:
+ add x0, x0, _before_far at PAGEOFF
+L3:
+ adrp x1, _before_near at PAGE
+L4:
+ add x1, x1, _before_near at PAGEOFF
+L5:
+ adrp x2, _before_near at PAGE
+L6:
+ add x0, x0, _before_near at PAGEOFF
+L7:
+ adrp x3, _before_near at PAGE
+L8:
+ add x3, x3, _after_near at PAGEOFF
+L9:
+ nop
+L10:
+ add x4, x4, _after_near at PAGEOFF
+L11:
+ adrp x5, _after_near at PAGE
+L12:
+ add x5, x5, _after_near at PAGEOFF
+L13:
+ adrp x6, _after_near at PAGE
+L14:
+ add x7, x6, _after_near at PAGEOFF
+L15:
+ adrp x8, _after_near at PAGE
+L16:
+ adrp x9, _after_far at PAGE
+L17:
+ add x9, x9, _after_far at PAGEOFF
+L18:
+ add x8, x8, _after_near at PAGEOFF
+
+_after_near:
+ .space 1048576
+
+_after_far:
+ nop
+
+.loh AdrpAdd L1, L2
+.loh AdrpAdd L3, L4
+.loh AdrpAdd L5, L6
+.loh AdrpAdd L7, L8
+.loh AdrpAdd L9, L10
+.loh AdrpAdd L11, L12
+.loh AdrpAdd L13, L14
+.loh AdrpAdd L15, L18
+.loh AdrpAdd L16, L17
diff --git a/lld/test/MachO/loh-adrp-adrp.s b/lld/test/MachO/loh-adrp-adrp.s
new file mode 100644
index 0000000000000..05abc8ab1961d
--- /dev/null
+++ b/lld/test/MachO/loh-adrp-adrp.s
@@ -0,0 +1,56 @@
+# REQUIRES: aarch64
+
+# RUN: llvm-mc -filetype=obj -triple=arm64-apple-darwin %s -o %t.o
+# RUN: %lld -arch arm64 %t.o -o %t
+# RUN: llvm-objdump -d --macho %t | FileCheck %s
+
+# CHECK-LABEL: _main:
+## Valid
+# CHECK-NEXT: adrp x0
+# CHECK-NEXT: nop
+## Mismatched registers
+# CHECK-NEXT: adrp x1
+# CHECK-NEXT: adrp x2
+## Not on the same page
+# CHECK-NEXT: adrp x3
+# CHECK-NEXT: adrp x3
+## Not an adrp instruction (invalid)
+# CHECK-NEXT: nop
+# CHECK-NEXT: adrp x4
+
+.text
+.align 2
+
+.globl _main
+_main:
+L1:
+ adrp x0, _foo at PAGE
+L2:
+ adrp x0, _bar at PAGE
+L3:
+ adrp x1, _foo at PAGE
+L4:
+ adrp x2, _bar at PAGE
+L5:
+ adrp x3, _foo at PAGE
+L6:
+ adrp x3, _baz at PAGE
+L7:
+ nop
+L8:
+ adrp x4, _baz at PAGE
+
+.data
+.align 12
+_foo:
+ .byte 0
+_bar:
+ .byte 0
+.space 4094
+_baz:
+ .byte 0
+
+.loh AdrpAdrp L1, L2
+.loh AdrpAdrp L3, L4
+.loh AdrpAdrp L5, L6
+.loh AdrpAdrp L7, L8
diff --git a/llvm/include/llvm/BinaryFormat/MachO.h b/llvm/include/llvm/BinaryFormat/MachO.h
index 7e38c1342e321..c05e79333d38b 100644
--- a/llvm/include/llvm/BinaryFormat/MachO.h
+++ b/llvm/include/llvm/BinaryFormat/MachO.h
@@ -2237,6 +2237,17 @@ enum SecCSDigestAlgorithm {
kSecCodeSignatureHashSHA512 = 5, /* SHA-512 */
};
+enum LinkerOptimizationHintKind {
+ LOH_ARM64_ADRP_ADRP = 1,
+ LOH_ARM64_ADRP_LDR = 2,
+ LOH_ARM64_ADRP_ADD_LDR = 3,
+ LOH_ARM64_ADRP_LDR_GOT_LDR = 4,
+ LOH_ARM64_ADRP_ADD_STR = 5,
+ LOH_ARM64_ADRP_LDR_GOT_STR = 6,
+ LOH_ARM64_ADRP_ADD = 7,
+ LOH_ARM64_ADRP_LDR_GOT = 8,
+};
+
} // end namespace MachO
} // end namespace llvm
More information about the llvm-commits
mailing list