[lld] [lld][macho] Support 1-byte branch relocs for x86_64 (PR #164439)
Jez Ng via llvm-commits
llvm-commits at lists.llvm.org
Tue Oct 21 12:21:14 PDT 2025
https://github.com/int3 updated https://github.com/llvm/llvm-project/pull/164439
>From 245c582d075aca58a265e975efd72fc6311e377d Mon Sep 17 00:00:00 2001
From: Jez Ng <me at jezng.com>
Date: Tue, 21 Oct 2025 10:27:45 -0400
Subject: [PATCH 1/2] [lld][macho] Support 1-byte branch relocs for x86_64
Fixes #160894.
---
lld/MachO/Arch/X86_64.cpp | 17 ++++++++---
lld/MachO/InputFiles.cpp | 8 ++----
lld/MachO/Relocations.h | 28 ++++++++++---------
.../invalid/invalid-relocation-length.yaml | 2 +-
lld/test/MachO/x86-64-relocs.s | 4 ++-
5 files changed, 34 insertions(+), 25 deletions(-)
diff --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp
index 0a950f28f02ae..d3d7341aa6799 100644
--- a/lld/MachO/Arch/X86_64.cpp
+++ b/lld/MachO/Arch/X86_64.cpp
@@ -51,10 +51,10 @@ struct X86_64 : TargetInfo {
static constexpr std::array<RelocAttrs, 10> relocAttrsArray{{
#define B(x) RelocAttrBits::x
- {"UNSIGNED",
- B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE4) | B(BYTE8)},
+ {"UNSIGNED", B(UNSIGNED) | B(ABSOLUTE) | B(EXTERN) | B(LOCAL) | B(BYTE1) |
+ B(BYTE4) | B(BYTE8)},
{"SIGNED", B(PCREL) | B(EXTERN) | B(LOCAL) | B(BYTE4)},
- {"BRANCH", B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE4)},
+ {"BRANCH", B(PCREL) | B(EXTERN) | B(BRANCH) | B(BYTE1) | B(BYTE4)},
{"GOT_LOAD", B(PCREL) | B(EXTERN) | B(GOT) | B(LOAD) | B(BYTE4)},
{"GOT", B(PCREL) | B(EXTERN) | B(GOT) | B(POINTER) | B(BYTE4)},
{"SUBTRACTOR", B(SUBTRAHEND) | B(EXTERN) | B(BYTE4) | B(BYTE8)},
@@ -84,6 +84,8 @@ int64_t X86_64::getEmbeddedAddend(MemoryBufferRef mb, uint64_t offset,
const uint8_t *loc = buf + offset + rel.r_address;
switch (rel.r_length) {
+ case 0:
+ return *loc + pcrelOffset(rel.r_type);
case 2:
return static_cast<int32_t>(read32le(loc)) + pcrelOffset(rel.r_type);
case 3:
@@ -96,11 +98,18 @@ int64_t X86_64::getEmbeddedAddend(MemoryBufferRef mb, uint64_t offset,
void X86_64::relocateOne(uint8_t *loc, const Reloc &r, uint64_t value,
uint64_t relocVA) const {
if (r.pcrel) {
- uint64_t pc = relocVA + 4 + pcrelOffset(r.type);
+ uint64_t pc = relocVA + (1 << r.length) + pcrelOffset(r.type);
value -= pc;
}
switch (r.length) {
+ case 0:
+ if (r.type == X86_64_RELOC_UNSIGNED)
+ checkUInt(loc, r, value, 8);
+ else
+ checkInt(loc, r, value, 8);
+ *loc = value;
+ break;
case 2:
if (r.type == X86_64_RELOC_UNSIGNED)
checkUInt(loc, r, value, 32);
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index 442fc608865d2..20e4a1d755229 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -516,12 +516,8 @@ static bool validateRelocationInfo(InputFile *file, const SectionHeader &sec,
if (isThreadLocalVariables(sec.flags) &&
!relocAttrs.hasAttr(RelocAttrBits::UNSIGNED))
error(message("not allowed in thread-local section, must be UNSIGNED"));
- if (rel.r_length < 2 || rel.r_length > 3 ||
- !relocAttrs.hasAttr(static_cast<RelocAttrBits>(1 << rel.r_length))) {
- static SmallVector<StringRef, 4> widths{"0", "4", "8", "4 or 8"};
- error(message("has width " + std::to_string(1 << rel.r_length) +
- " bytes, but must be " +
- widths[(static_cast<int>(relocAttrs.bits) >> 2) & 3] +
+ if (!relocAttrs.hasAttr(static_cast<RelocAttrBits>(1 << rel.r_length))) {
+ error(message("has invalid width of " + std::to_string(1 << rel.r_length) +
" bytes"));
}
return valid;
diff --git a/lld/MachO/Relocations.h b/lld/MachO/Relocations.h
index b2f621451349e..d3347de36781b 100644
--- a/lld/MachO/Relocations.h
+++ b/lld/MachO/Relocations.h
@@ -25,21 +25,23 @@ class InputSection;
enum class RelocAttrBits {
_0 = 0, // invalid
- PCREL = 1 << 0, // Value is PC-relative offset
- ABSOLUTE = 1 << 1, // Value is an absolute address or fixed offset
+ BYTE1 = 1 << 0, // 1 byte datum
+ BYTE2 = 1 << 1, // 2 byte datum
BYTE4 = 1 << 2, // 4 byte datum
BYTE8 = 1 << 3, // 8 byte datum
- EXTERN = 1 << 4, // Can have an external symbol
- LOCAL = 1 << 5, // Can have a local symbol
- ADDEND = 1 << 6, // *_ADDEND paired prefix reloc
- SUBTRAHEND = 1 << 7, // *_SUBTRACTOR paired prefix reloc
- BRANCH = 1 << 8, // Value is branch target
- GOT = 1 << 9, // References a symbol in the Global Offset Table
- TLV = 1 << 10, // References a thread-local symbol
- LOAD = 1 << 11, // Relaxable indirect load
- POINTER = 1 << 12, // Non-relaxable indirect load (pointer is taken)
- UNSIGNED = 1 << 13, // *_UNSIGNED relocs
- LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue*/ (1 << 14) - 1),
+ PCREL = 1 << 4, // Value is PC-relative offset
+ ABSOLUTE = 1 << 5, // Value is an absolute address or fixed offset
+ EXTERN = 1 << 6, // Can have an external symbol
+ LOCAL = 1 << 7, // Can have a local symbol
+ ADDEND = 1 << 8, // *_ADDEND paired prefix reloc
+ SUBTRAHEND = 1 << 9, // *_SUBTRACTOR paired prefix reloc
+ BRANCH = 1 << 10, // Value is branch target
+ GOT = 1 << 11, // References a symbol in the Global Offset Table
+ TLV = 1 << 12, // References a thread-local symbol
+ LOAD = 1 << 13, // Relaxable indirect load
+ POINTER = 1 << 14, // Non-relaxable indirect load (pointer is taken)
+ UNSIGNED = 1 << 15, // *_UNSIGNED relocs
+ LLVM_MARK_AS_BITMASK_ENUM(/*LargestValue*/ (1 << 16) - 1),
};
// Note: SUBTRACTOR always pairs with UNSIGNED (a delta between two symbols).
diff --git a/lld/test/MachO/invalid/invalid-relocation-length.yaml b/lld/test/MachO/invalid/invalid-relocation-length.yaml
index ff8759b417478..2661e4ea165ec 100644
--- a/lld/test/MachO/invalid/invalid-relocation-length.yaml
+++ b/lld/test/MachO/invalid/invalid-relocation-length.yaml
@@ -2,7 +2,7 @@
# RUN: yaml2obj %s -o %t.o
# RUN: not %lld -o %t %t.o 2>&1 | FileCheck %s -DFILE=%t.o
#
-# CHECK: error: UNSIGNED relocation has width 2 bytes, but must be 4 or 8 bytes at offset 1 of __TEXT,__text in [[FILE]]
+# CHECK: error: UNSIGNED relocation has invalid width of 2 bytes at offset 1 of __TEXT,__text in [[FILE]]
!mach-o
FileHeader:
diff --git a/lld/test/MachO/x86-64-relocs.s b/lld/test/MachO/x86-64-relocs.s
index cdeee96c182f0..422808df81357 100644
--- a/lld/test/MachO/x86-64-relocs.s
+++ b/lld/test/MachO/x86-64-relocs.s
@@ -12,6 +12,7 @@
# CHECK-LABEL: <_main>:
## Test X86_64_RELOC_BRANCH
# CHECK: callq 0x[[#%x, F_ADDR]] <_f>
+# CHECK: jrcxz 0x[[#%x, F_ADDR]] <_f>
## Test extern (symbol) X86_64_RELOC_SIGNED
# CHECK: leaq [[#%u, LOCAL_OFF:]](%rip), %rsi
# CHECK-NEXT: [[#%x, DATA_ADDR - LOCAL_OFF]]
@@ -26,7 +27,8 @@
.section __TEXT,__text
.globl _main, _f
_main:
- callq _f # X86_64_RELOC_BRANCH
+ callq _f # X86_64_RELOC_BRANCH with r_length=2
+ jrcxz _f # X86_64_RELOC_BRANCH with r_length=0
mov $0, %rax
ret
>From 06ab70deb462faf887f6b88c96e9b0d7203d4ced Mon Sep 17 00:00:00 2001
From: Jez Ng <me at jezng.com>
Date: Tue, 21 Oct 2025 13:09:18 -0400
Subject: [PATCH 2/2] Factor out pcrelOffset call
---
lld/MachO/Arch/X86_64.cpp | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
diff --git a/lld/MachO/Arch/X86_64.cpp b/lld/MachO/Arch/X86_64.cpp
index d3d7341aa6799..5d69c5deeba24 100644
--- a/lld/MachO/Arch/X86_64.cpp
+++ b/lld/MachO/Arch/X86_64.cpp
@@ -82,17 +82,23 @@ int64_t X86_64::getEmbeddedAddend(MemoryBufferRef mb, uint64_t offset,
relocation_info rel) const {
auto *buf = reinterpret_cast<const uint8_t *>(mb.getBufferStart());
const uint8_t *loc = buf + offset + rel.r_address;
+ int64_t addend;
switch (rel.r_length) {
case 0:
- return *loc + pcrelOffset(rel.r_type);
+ addend = *loc;
+ break;
case 2:
- return static_cast<int32_t>(read32le(loc)) + pcrelOffset(rel.r_type);
+ addend = static_cast<int32_t>(read32le(loc));
+ break;
case 3:
- return read64le(loc) + pcrelOffset(rel.r_type);
+ addend = read64le(loc);
+ break;
default:
llvm_unreachable("invalid r_length");
}
+
+ return addend + pcrelOffset(rel.r_type);
}
void X86_64::relocateOne(uint8_t *loc, const Reloc &r, uint64_t value,
More information about the llvm-commits
mailing list