[llvm-branch-commits] [lld] ELF: CFI jump table relaxation. (PR #147424)
Peter Collingbourne via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Aug 29 13:13:17 PDT 2025
https://github.com/pcc updated https://github.com/llvm/llvm-project/pull/147424
>From 5bce06b0d8db161a2e09709bcfe15b4623e43d01 Mon Sep 17 00:00:00 2001
From: Peter Collingbourne <peter at pcc.me.uk>
Date: Mon, 7 Jul 2025 16:41:10 -0700
Subject: [PATCH 1/3] =?UTF-8?q?[=F0=9D=98=80=F0=9D=97=BD=F0=9D=97=BF]=20in?=
=?UTF-8?q?itial=20version?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Created using spr 1.3.6-beta.1
---
lld/ELF/Arch/X86_64.cpp | 95 +++++++++++++++++++++++++++++++++++++++++
lld/ELF/Relocations.cpp | 2 +-
lld/ELF/Target.h | 1 +
3 files changed, 97 insertions(+), 1 deletion(-)
diff --git a/lld/ELF/Arch/X86_64.cpp b/lld/ELF/Arch/X86_64.cpp
index 488f4803b2cb4..04ca79befdc4a 100644
--- a/lld/ELF/Arch/X86_64.cpp
+++ b/lld/ELF/Arch/X86_64.cpp
@@ -318,6 +318,9 @@ bool X86_64::deleteFallThruJmpInsn(InputSection &is, InputFile *file,
}
bool X86_64::relaxOnce(int pass) const {
+ if (pass == 0)
+ relaxJumpTables(ctx);
+
uint64_t minVA = UINT64_MAX, maxVA = 0;
for (OutputSection *osec : ctx.outputSections) {
if (!(osec->flags & SHF_ALLOC))
@@ -1231,6 +1234,98 @@ void X86_64::applyBranchToBranchOpt() const {
redirectControlTransferRelocations);
}
+void elf::relaxJumpTables(Ctx &ctx) {
+ // Relax CFI jump tables.
+ // - Split jump table into pieces and place target functions inside the jump
+ // table if small enough.
+ // - Move jump table before last called function and delete last branch
+ // instruction.
+ std::map<InputSection *, std::vector<InputSection *>> sectionReplacements;
+ SmallVector<InputSection *, 0> storage;
+ for (OutputSection *osec : ctx.outputSections) {
+ if (!(osec->flags & SHF_EXECINSTR))
+ continue;
+ for (InputSection *sec : getInputSections(*osec, storage)) {
+ if (!sec->name.starts_with(".text..L.cfi.jumptable"))
+ continue;
+ std::vector<InputSection *> replacements;
+ replacements.push_back(sec);
+ auto addSectionSlice = [&](size_t begin, size_t end, Relocation *rbegin,
+ Relocation *rend) {
+ if (begin == end)
+ return;
+ auto *slice = make<InputSection>(
+ sec->file, sec->name, sec->type, sec->flags, 1, sec->entsize,
+ sec->contentMaybeDecompress().slice(begin, end - begin));
+ for (const Relocation &r : ArrayRef<Relocation>(rbegin, rend)) {
+ slice->relocations.push_back(
+ Relocation{r.expr, r.type, r.offset - begin, r.addend, r.sym});
+ }
+ replacements.push_back(slice);
+ };
+ auto getMovableSection = [&](Relocation &r) -> InputSection * {
+ auto *sym = dyn_cast_or_null<Defined>(r.sym);
+ if (!sym || sym->isPreemptible || sym->isGnuIFunc() || sym->value != 0)
+ return nullptr;
+ auto *sec = dyn_cast_or_null<InputSection>(sym->section);
+ if (!sec || sectionReplacements.count(sec))
+ return nullptr;
+ return sec;
+ };
+ size_t begin = 0;
+ Relocation *rbegin = sec->relocs().begin();
+ for (auto &r : sec->relocs().slice(0, sec->relocs().size() - 1)) {
+ auto entrySize = (&r + 1)->offset - r.offset;
+ InputSection *target = getMovableSection(r);
+ if (!target || target->size > entrySize)
+ continue;
+ target->addralign = 1;
+ addSectionSlice(begin, r.offset - 1, rbegin, &r);
+ replacements.push_back(target);
+ sectionReplacements[target] = {};
+ begin = r.offset - 1 + target->size;
+ rbegin = &r + 1;
+ }
+ InputSection *lastSec = getMovableSection(sec->relocs().back());
+ if (lastSec) {
+ lastSec->addralign = 1;
+ addSectionSlice(begin, sec->relocs().back().offset - 1, rbegin,
+ &sec->relocs().back());
+ replacements.push_back(lastSec);
+ sectionReplacements[sec] = {};
+ sectionReplacements[lastSec] = replacements;
+ for (auto *s : replacements)
+ s->parent = lastSec->parent;
+ } else {
+ addSectionSlice(begin, sec->size, rbegin, sec->relocs().end());
+ sectionReplacements[sec] = replacements;
+ for (auto *s : replacements)
+ s->parent = sec->parent;
+ }
+ sec->relocations.clear();
+ sec->size = 0;
+ }
+ }
+ for (OutputSection *osec : ctx.outputSections) {
+ if (!(osec->flags & SHF_EXECINSTR))
+ continue;
+ for (SectionCommand *cmd : osec->commands) {
+ auto *isd = dyn_cast<InputSectionDescription>(cmd);
+ if (!isd)
+ continue;
+ SmallVector<InputSection *> newSections;
+ for (auto *sec : isd->sections) {
+ auto i = sectionReplacements.find(sec);
+ if (i == sectionReplacements.end())
+ newSections.push_back(sec);
+ else
+ newSections.append(i->second.begin(), i->second.end());
+ }
+ isd->sections = std::move(newSections);
+ }
+ }
+}
+
// If Intel Indirect Branch Tracking is enabled, we have to emit special PLT
// entries containing endbr64 instructions. A PLT entry will be split into two
// parts, one in .plt.sec (writePlt), and the other in .plt (writeIBTPlt).
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index cebd564036b2c..f7e3d54878395 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -1674,7 +1674,7 @@ void RelocationScanner::scan(Relocs<RelTy> rels) {
// R_RISCV_PCREL_HI20, R_PPC64_ADDR64 and the branch-to-branch optimization.
if (ctx.arg.emachine == EM_RISCV ||
(ctx.arg.emachine == EM_PPC64 && sec->name == ".toc") ||
- ctx.arg.branchToBranch)
+ ctx.arg.branchToBranch || sec->name.starts_with(".text..L.cfi.jumptable"))
llvm::stable_sort(sec->relocs(),
[](const Relocation &lhs, const Relocation &rhs) {
return lhs.offset < rhs.offset;
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index 6dd20b2f0cbaa..e6eb33fa5338c 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -195,6 +195,7 @@ void setSPARCV9TargetInfo(Ctx &);
void setSystemZTargetInfo(Ctx &);
void setX86TargetInfo(Ctx &);
void setX86_64TargetInfo(Ctx &);
+void relaxJumpTables(Ctx &);
struct ErrorPlace {
InputSectionBase *isec;
>From afa726a766603c29393a6c3f0d3500a11c85d9e9 Mon Sep 17 00:00:00 2001
From: Peter Collingbourne <peter at pcc.me.uk>
Date: Thu, 28 Aug 2025 15:34:34 -0700
Subject: [PATCH 2/3] Fix bug where overaligned jump tables are laid out
incorrectly
Created using spr 1.3.6-beta.1
---
lld/ELF/Arch/X86_64.cpp | 7 +++++++
lld/test/ELF/x86_64-relax-jump-tables.s | 27 +++++++++++++++++++++++++
2 files changed, 34 insertions(+)
diff --git a/lld/ELF/Arch/X86_64.cpp b/lld/ELF/Arch/X86_64.cpp
index 2ced135a4d0c3..50026dabfeee5 100644
--- a/lld/ELF/Arch/X86_64.cpp
+++ b/lld/ELF/Arch/X86_64.cpp
@@ -376,6 +376,13 @@ void X86_64::relaxCFIJumpTables() const {
// because the last entry controls which output section the jump table is
// placed into, which affects move eligibility for other sections.
auto *lastSec = [&]() -> InputSection * {
+ // If the jump table section is more aligned than the entry size, skip
+ // this because there's no guarantee that we'll be able to emit a
+ // padding section that places the last entry at a correctly aligned
+ // address.
+ if (sec->addralign > sec->entsize)
+ return nullptr;
+
Relocation *lastReloc = sec->relocs().end();
while (lastReloc != sec->relocs().begin() &&
(lastReloc - 1)->offset >= sec->size - sec->entsize)
diff --git a/lld/test/ELF/x86_64-relax-jump-tables.s b/lld/test/ELF/x86_64-relax-jump-tables.s
index 8c2394003bacb..50edf4c240ecc 100644
--- a/lld/test/ELF/x86_64-relax-jump-tables.s
+++ b/lld/test/ELF/x86_64-relax-jump-tables.s
@@ -109,6 +109,23 @@ jmp f12.cfi
f13:
jmp f13.cfi
+// Jumptable alignment > entsize prevents it from being moved before last
+// function, but moving non-last functions into the jumptable should work.
+// CHECK: <f14>:
+// CHECK-NEXT: <f14.cfi>:
+// CHECK-NEXT: retq $0xe
+.section .text.jt5,"ax", at llvm_cfi_jump_table,8
+.balign 16
+f14:
+jmp f14.cfi
+.balign 8, 0xcc
+
+// CHECK: <f15>:
+// CHECK-NEXT: jmp {{.*}} <f15.cfi>
+f15:
+jmp f15.cfi
+.balign 8, 0xcc
+
// CHECK: <f1>:
// CHECK-NEXT: <f1.cfi>:
// CHECK-NEXT: retq $0x1
@@ -180,5 +197,15 @@ ret $12
f13.cfi:
ret $13
+.section .text.f14,"ax", at progbits
+f14.cfi:
+ret $14
+
+.section .text.f15,"ax", at progbits
+.balign 64
+f15.cfi:
+ret $15
+.zero 16
+
// CHECK: <.iplt>:
// CHECK-NEXT: [[IPLT]]:
>From 711837fbaa2ac03a05df508ba009e047ecae7958 Mon Sep 17 00:00:00 2001
From: Peter Collingbourne <peter at pcc.me.uk>
Date: Fri, 29 Aug 2025 13:13:02 -0700
Subject: [PATCH 3/3] Fix bug with zero length target sections
Created using spr 1.3.6-beta.1
---
lld/ELF/Arch/X86_64.cpp | 2 +-
...ax-jump-tables.s => x86-64-relax-jump-tables.s} | 14 ++++++++++++--
2 files changed, 13 insertions(+), 3 deletions(-)
rename lld/test/ELF/{x86_64-relax-jump-tables.s => x86-64-relax-jump-tables.s} (95%)
diff --git a/lld/ELF/Arch/X86_64.cpp b/lld/ELF/Arch/X86_64.cpp
index 50026dabfeee5..a8a52bfce31a7 100644
--- a/lld/ELF/Arch/X86_64.cpp
+++ b/lld/ELF/Arch/X86_64.cpp
@@ -437,7 +437,7 @@ void X86_64::relaxCFIJumpTables() const {
++rnext;
if (rcur + 1 == rnext) {
InputSection *target = getMovableSection(*rcur);
- if (target && target->size <= sec->entsize &&
+ if (target && target->size != 0 && target->size <= sec->entsize &&
target->addralign <= sec->entsize &&
target->getParent() == targetOutputSec) {
// Okay, we found a small enough section. Move it into the jump
diff --git a/lld/test/ELF/x86_64-relax-jump-tables.s b/lld/test/ELF/x86-64-relax-jump-tables.s
similarity index 95%
rename from lld/test/ELF/x86_64-relax-jump-tables.s
rename to lld/test/ELF/x86-64-relax-jump-tables.s
index 50edf4c240ecc..50e60aa42f74a 100644
--- a/lld/test/ELF/x86_64-relax-jump-tables.s
+++ b/lld/test/ELF/x86-64-relax-jump-tables.s
@@ -120,12 +120,19 @@ f14:
jmp f14.cfi
.balign 8, 0xcc
+// Empty target section.
// CHECK: <f15>:
// CHECK-NEXT: jmp {{.*}} <f15.cfi>
f15:
jmp f15.cfi
.balign 8, 0xcc
+// CHECK: <f16>:
+// CHECK-NEXT: jmp {{.*}} <f16.cfi>
+f16:
+jmp f16.cfi
+.balign 8, 0xcc
+
// CHECK: <f1>:
// CHECK-NEXT: <f1.cfi>:
// CHECK-NEXT: retq $0x1
@@ -202,9 +209,12 @@ f14.cfi:
ret $14
.section .text.f15,"ax", at progbits
-.balign 64
f15.cfi:
-ret $15
+
+.section .text.f16,"ax", at progbits
+.balign 64
+f16.cfi:
+ret $16
.zero 16
// CHECK: <.iplt>:
More information about the llvm-branch-commits
mailing list