[lld] [RISCV][LLD] Zcmt RISC-V extension in lld (PR #163142)
Robin Kastberg via llvm-commits
llvm-commits at lists.llvm.org
Tue Oct 14 23:17:44 PDT 2025
https://github.com/RobinKastberg updated https://github.com/llvm/llvm-project/pull/163142
>From e0c090e41790732cf8ca4936d5f1cae54d0fce25 Mon Sep 17 00:00:00 2001
From: Robin Kastberg <Robin.Kastberg at iar.com>
Date: Tue, 7 Oct 2025 20:25:01 +0200
Subject: [PATCH 1/7] squash zcmt
---
lld/ELF/Arch/RISCV.cpp | 47 ++++++++++++
lld/ELF/Config.h | 3 +
lld/ELF/Options.td | 3 +
lld/ELF/SyntheticSections.h | 45 ++++++++++++
lld/ELF/Target.h | 3 +
lld/test/ELF/riscv-no-tbljal-call.s | 33 +++++++++
lld/test/ELF/riscv-tbljal-call.s | 109 ++++++++++++++++++++++++++++
lld/test/ELF/riscv-tbljal-syms.s | 42 +++++++++++
8 files changed, 285 insertions(+)
create mode 100644 lld/test/ELF/riscv-no-tbljal-call.s
create mode 100644 lld/test/ELF/riscv-tbljal-call.s
create mode 100644 lld/test/ELF/riscv-tbljal-syms.s
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index dc2ab97e9d9be..49aadfba00584 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -38,6 +38,8 @@ class RISCV final : public TargetInfo {
void writePltHeader(uint8_t *buf) const override;
void writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const override;
+ void writeTableJumpHeader(uint8_t *buf) const override;
+ void writeTableJumpEntry(uint8_t *buf, const uint64_t symbol) const override;
RelType getDynRel(RelType type) const override;
RelExpr getRelExpr(RelType type, const Symbol &s,
const uint8_t *loc) const override;
@@ -70,8 +72,10 @@ class RISCV final : public TargetInfo {
#define INTERNAL_R_RISCV_GPREL_S 257
#define INTERNAL_R_RISCV_X0REL_I 258
#define INTERNAL_R_RISCV_X0REL_S 259
+#define INTERNAL_R_RISCV_TBJAL 260
const uint64_t dtpOffset = 0x800;
+const uint32_t jvtAlign = 64;
namespace {
enum Op {
@@ -269,6 +273,20 @@ void RISCV::writePlt(uint8_t *buf, const Symbol &sym,
write32le(buf + 12, itype(ADDI, 0, 0, 0));
}
+void RISCV::writeTableJumpHeader(uint8_t *buf) const {
+ if (ctx.arg.is64)
+ write64le(buf, ctx.mainPart->dynamic->getVA());
+ else
+ write32le(buf, ctx.mainPart->dynamic->getVA());
+}
+
+void RISCV::writeTableJumpEntry(uint8_t *buf, const uint64_t address) const {
+ if (ctx.arg.is64)
+ write64le(buf, address);
+ else
+ write32le(buf, address);
+}
+
RelType RISCV::getDynRel(RelType type) const {
return type == ctx.target->symbolicRel ? type
: static_cast<RelType>(R_RISCV_NONE);
@@ -490,6 +508,9 @@ void RISCV::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
return;
}
+ case INTERNAL_R_RISCV_TBJAL:
+ return;
+
case R_RISCV_ADD8:
*loc += val;
return;
@@ -739,6 +760,30 @@ void elf::initSymbolAnchors(Ctx &ctx) {
}
}
+static bool relaxTableJump(Ctx &ctx, const InputSection &sec, size_t i, uint64_t loc,
+ Relocation &r, uint32_t &remove) {
+ if (!ctx.in.riscvTableJumpSection || !ctx.in.riscvTableJumpSection->isFinalized)
+ return false;
+
+ const auto jalr = sec.contentMaybeDecompress().data()[r.offset + 4];
+ const uint8_t rd = extractBits(jalr, 11, 7);
+ int tblEntryIndex = -1;
+ if (rd == 0) {
+ tblEntryIndex = ctx.in.riscvTableJumpSection->getCMJTEntryIndex(r.sym);
+ } else if (rd == X_RA) {
+ tblEntryIndex = ctx.in.riscvTableJumpSection->getCMJALTEntryIndex(r.sym);
+ }
+
+ if (tblEntryIndex >= 0) {
+ sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_TBJAL;
+ sec.relaxAux->writes.push_back(0xA002 |
+ (tblEntryIndex << 2)); // cm.jt or cm.jalt
+ remove = 6;
+ return true;
+ }
+ return false;
+}
+
// Relax R_RISCV_CALL/R_RISCV_CALL_PLT auipc+jalr to c.j, c.jal, or jal.
static void relaxCall(Ctx &ctx, const InputSection &sec, size_t i, uint64_t loc,
Relocation &r, uint32_t &remove) {
@@ -1149,6 +1194,8 @@ void RISCV::finalizeRelax(int passes) const {
skip = 4;
write32le(p, aux.writes[writesIdx++]);
break;
+ case R_RISCV_64:
+ break;
case R_RISCV_32:
// Used by relaxTlsLe to write a uint32_t then suppress the handling
// in relocateAlloc.
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index fd57967a1d21f..58ec6919fb82b 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -67,6 +67,7 @@ class MipsGotSection;
class MipsRldMapSection;
class PPC32Got2Section;
class PPC64LongBranchTargetSection;
+class TableJumpSection;
class PltSection;
class RelocationBaseSection;
class RelroPaddingSection;
@@ -369,6 +370,7 @@ struct Config {
bool resolveGroups;
bool relrGlibc = false;
bool relrPackDynRelocs = false;
+ bool relaxTbljal;
llvm::DenseSet<llvm::StringRef> saveTempsArgs;
llvm::SmallVector<std::pair<llvm::GlobPattern, uint32_t>, 0> shuffleSections;
bool singleRoRx;
@@ -581,6 +583,7 @@ struct InStruct {
std::unique_ptr<RelroPaddingSection> relroPadding;
std::unique_ptr<SyntheticSection> armCmseSGSection;
std::unique_ptr<PPC64LongBranchTargetSection> ppc64LongBranchTarget;
+ std::unique_ptr<TableJumpSection> riscvTableJumpSection;
std::unique_ptr<SyntheticSection> mipsAbiFlags;
std::unique_ptr<MipsGotSection> mipsGot;
std::unique_ptr<SyntheticSection> mipsOptions;
diff --git a/lld/ELF/Options.td b/lld/ELF/Options.td
index 0d6dda4b60d3a..3a83f6c36a91c 100644
--- a/lld/ELF/Options.td
+++ b/lld/ELF/Options.td
@@ -378,6 +378,9 @@ defm use_android_relr_tags: BB<"use-android-relr-tags",
"Use SHT_ANDROID_RELR / DT_ANDROID_RELR* tags instead of SHT_RELR / DT_RELR*",
"Use SHT_RELR / DT_RELR* tags (default)">;
+def relax_tbljal: FF<"relax-tbljal">,
+ HelpText<"Enable conversion of call instructions to table jump instruction from the Zcmt extension for frequently called functions (RISC-V only)">;
+
def pic_veneer: F<"pic-veneer">,
HelpText<"Always generate position independent thunks (veneers)">;
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index ac3ec63f0a7a5..fcf9139b4011d 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -380,6 +380,51 @@ class GotPltSection final : public SyntheticSection {
SmallVector<const Symbol *, 0> entries;
};
+class TableJumpSection final : public SyntheticSection {
+public:
+ TableJumpSection(Ctx &);
+ size_t getSize() const override;
+ void writeTo(uint8_t *buf) override;
+ void finalizeContents() override;
+
+ int32_t getSizeReduction();
+ void addCMJTEntryCandidate(const Symbol *symbol, int csReduction);
+ int getCMJTEntryIndex(const Symbol *symbol);
+ void addCMJALTEntryCandidate(const Symbol *symbol, int csReduction);
+ int getCMJALTEntryIndex(const Symbol *symbol);
+ void scanTableJumpEntries(const InputSection &sec) const;
+
+ bool isFinalized = false;
+
+private:
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>
+ finalizeEntry(llvm::DenseMap<const Symbol *, int> EntryMap, uint32_t maxSize);
+ void addEntry(const Symbol *symbol,
+ llvm::DenseMap<const Symbol *, int> &entriesList,
+ int csReduction);
+ uint32_t getIndex(const Symbol *symbol, uint32_t maxSize,
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>,
+ 0> &entriesList);
+ void writeEntries(uint8_t *buf,
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>,
+ 0> &entriesList);
+ void padWords(uint8_t *buf, const uint8_t maxWordCount);
+
+ // used in finalizeContents function.
+ static const size_t maxCMJTEntrySize = 32;
+ static const size_t maxCMJALTEntrySize = 224;
+
+ static const size_t startCMJTEntryIdx = 0;
+ static const size_t startCMJALTEntryIdx = 32;
+
+ llvm::DenseMap<const Symbol *, int> CMJTEntryCandidates;
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>
+ finalizedCMJTEntries;
+ llvm::DenseMap<const Symbol *, int> CMJALTEntryCandidates;
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>
+ finalizedCMJALTEntries;
+};
+
// The IgotPltSection is a Got associated with the PltSection for GNU Ifunc
// Symbols that will be relocated by Target->IRelativeRel.
// On most Targets the IgotPltSection will immediately follow the GotPltSection
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index 9f0605138a4fb..0882b1f5ca2c4 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -37,6 +37,9 @@ class TargetInfo {
virtual void writeGotPltHeader(uint8_t *buf) const {}
virtual void writeGotHeader(uint8_t *buf) const {}
virtual void writeGotPlt(uint8_t *buf, const Symbol &s) const {};
+ virtual void writeTableJumpHeader(uint8_t *buf) const {};
+ virtual void writeTableJumpEntry(uint8_t *buf,
+ const uint64_t symbol) const {};
virtual void writeIgotPlt(uint8_t *buf, const Symbol &s) const {}
virtual int64_t getImplicitAddend(const uint8_t *buf, RelType type) const;
virtual int getTlsGdRelaxSkip(RelType type) const { return 1; }
diff --git a/lld/test/ELF/riscv-no-tbljal-call.s b/lld/test/ELF/riscv-no-tbljal-call.s
new file mode 100644
index 0000000000000..61d1d87d11057
--- /dev/null
+++ b/lld/test/ELF/riscv-no-tbljal-call.s
@@ -0,0 +1,33 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax -mattr=zcmt %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax -mattr=zcmt %s -o %t.rv64.o
+
+# tbljal conversion
+# RUN: ld.lld %t.rv32.o --relax-tbljal --defsym foo=0x150000 -o %t.rv32
+# RUN: ld.lld %t.rv64.o --relax-tbljal --defsym foo=0x150000 -o %t.rv64
+
+# jump table
+# RUN: llvm-objdump -h %t.rv32 | FileCheck --check-prefix=JUMPTABLE32 %s
+# RUN: llvm-objdump -h %t.rv64 | FileCheck --check-prefix=JUMPTABLE64 %s
+
+# JUMPTABLE32: 2 .riscv.jvt 00000000 00011100 TEXT
+# JUMPTABLE64: 2 .riscv.jvt 00000000 0000000000011140 TEXT
+
+.global _start
+.p2align 3
+_start:
+ call foo
+ tail foo_1
+ tail foo_2
+ tail foo_3
+
+foo_1:
+ nop
+
+foo_2:
+ nop
+
+foo_3:
+ nop
+
diff --git a/lld/test/ELF/riscv-tbljal-call.s b/lld/test/ELF/riscv-tbljal-call.s
new file mode 100644
index 0000000000000..1e13a328236f3
--- /dev/null
+++ b/lld/test/ELF/riscv-tbljal-call.s
@@ -0,0 +1,109 @@
+# REQUIRES: riscv
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax -mattr=zcmt %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax -mattr=zcmt %s -o %t.rv64.o
+
+# tbljal conversion
+# RUN: ld.lld %t.rv32.o --relax-tbljal --defsym foo=0x150000 --defsym foo_1=0x150010 --defsym foo_3=0x150030 -o %t.rv32
+# RUN: ld.lld %t.rv64.o --relax-tbljal --defsym foo=0x150000 --defsym foo_1=0x150010 --defsym foo_3=0x150030 -o %t.rv64
+# RUN: llvm-objdump -d -M no-aliases --mattr=zcmt --no-show-raw-insn %t.rv32 | FileCheck --check-prefix=TBLJAL32 %s
+# RUN: llvm-objdump -d -M no-aliases --mattr=zcmt --no-show-raw-insn %t.rv64 | FileCheck --check-prefix=TBLJAL64 %s
+
+# jump table
+# RUN: llvm-objdump -j .riscv.jvt -s %t.rv32 | FileCheck --check-prefix=JUMPTABLE32 %s
+# RUN: llvm-objdump -j .riscv.jvt -s %t.rv64 | FileCheck --check-prefix=JUMPTABLE64 %s
+
+# TBLJAL32: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jalt 0x20
+# TBLJAL32-NEXT: cm.jt 0x2
+# TBLJAL32-NEXT: cm.jt 0x1
+# TBLJAL32-NEXT: cm.jt 0x1
+# TBLJAL32-NEXT: cm.jt 0x1
+# TBLJAL32-NEXT: cm.jt 0x0
+# TBLJAL32-NEXT: c.j 0x110f6 <foo_2>
+# TBLJAL32-NEXT: cm.jt 0x0
+# TBLJAL32-NEXT: cm.jt 0x0
+# TBLJAL32-NEXT: cm.jt 0x0
+# TBLJAL32-NEXT: cm.jt 0x0
+
+# TBLJAL64: cm.jt 0x1
+# TBLJAL64-NEXT: cm.jt 0x1
+# TBLJAL64-NEXT: cm.jt 0x1
+# TBLJAL64-NEXT: cm.jt 0x0
+# TBLJAL64-NEXT: c.j 0x111e2 <foo_2>
+# TBLJAL64-NEXT: cm.jt 0x0
+# TBLJAL64-NEXT: cm.jt 0x0
+# TBLJAL64-NEXT: cm.jt 0x0
+# TBLJAL64-NEXT: cm.jt 0x0
+
+
+# JUMPTABLE32: 30001500 10001500 00001500 00000000
+# JUMPTABLE32-NEXT: 00000000 00000000 00000000 00000000
+# JUMPTABLE32-NEXT: 00000000 00000000 00000000 00000000
+# JUMPTABLE32-NEXT: 00000000 00000000 00000000 00000000
+# JUMPTABLE32-NEXT: 00000000 00000000 00000000 00000000
+# JUMPTABLE32-NEXT: 00000000 00000000 00000000 00000000
+# JUMPTABLE32-NEXT: 00000000 00000000 00000000 00000000
+# JUMPTABLE32-NEXT: 00000000 00000000 00000000 00000000
+# JUMPTABLE32-NEXT: 00001500
+
+# JUMPTABLE64: 30001500 00000000 10001500 00000000
+
+.global _start
+.p2align 3
+_start:
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ call foo
+ tail foo
+ tail foo_1
+ tail foo_1
+ tail foo_1
+ tail foo_3
+ tail foo_2
+ tail foo_3
+ tail foo_3
+ tail foo_3
+ tail foo_3
+
+foo_2:
+ nop
+
+
diff --git a/lld/test/ELF/riscv-tbljal-syms.s b/lld/test/ELF/riscv-tbljal-syms.s
new file mode 100644
index 0000000000000..2c9955c64ec32
--- /dev/null
+++ b/lld/test/ELF/riscv-tbljal-syms.s
@@ -0,0 +1,42 @@
+# REQUIRES: riscv
+
+// Check that relaxation correctly adjusts symbol addresses and sizes.
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax -mattr=zcmt %s -o %t.rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax -mattr=zcmt %s -o %t.rv64.o
+# RUN: ld.lld -Ttext=0x100000 --relax-tbljal %t.rv32.o -o %t.rv32
+# RUN: ld.lld -Ttext=0x100000 --relax-tbljal %t.rv64.o -o %t.rv64
+
+# RUN: llvm-readelf -s %t.rv32 | FileCheck --check-prefix=CHECK32 %s
+# RUN: llvm-readelf -s %t.rv64 | FileCheck --check-prefix=CHECK64 %s
+
+# CHECK32: 00100000 4 NOTYPE LOCAL DEFAULT 1 a
+# CHECK32-NEXT: 00100000 6 NOTYPE LOCAL DEFAULT 1 b
+# CHECK32-NEXT: 00100000 0 NOTYPE LOCAL DEFAULT 1 $x
+# CHECK32-NEXT: 00100004 2 NOTYPE LOCAL DEFAULT 1 c
+# CHECK32-NEXT: 00100004 6 NOTYPE LOCAL DEFAULT 1 d
+# CHECK32-NEXT: 00100000 10 NOTYPE GLOBAL DEFAULT 1 _start
+# CHECK32-NEXT: 00100040 0 NOTYPE GLOBAL DEFAULT 2 __jvt_base$
+
+# CHECK64: 00100000 4 NOTYPE LOCAL DEFAULT 1 a
+# CHECK64-NEXT: 00100000 8 NOTYPE LOCAL DEFAULT 1 b
+# CHECK64-NEXT: 00100000 0 NOTYPE LOCAL DEFAULT 1 $x
+# CHECK64-NEXT: 00100004 4 NOTYPE LOCAL DEFAULT 1 c
+# CHECK64-NEXT: 00100004 8 NOTYPE LOCAL DEFAULT 1 d
+# CHECK64-NEXT: 00100000 12 NOTYPE GLOBAL DEFAULT 1 _start
+# CHECK64-NEXT: 00100040 0 NOTYPE GLOBAL DEFAULT 2 __jvt_base$
+
+.global _start
+_start:
+a:
+b:
+ add a0, a1, a2
+.size a, . - a
+c:
+d:
+ call _start
+.size b, . - b
+.size c, . - c
+ add a0, a1, a2
+.size d, . - d
+.size _start, . - _start
>From 4a8274a2e686e9683eddd4de908ea2928ba3f26f Mon Sep 17 00:00:00 2001
From: Robin Kastberg <robin.kastberg at iar.com>
Date: Mon, 13 Oct 2025 15:51:15 +0200
Subject: [PATCH 2/7] clang-format
---
lld/ELF/Arch/RISCV.cpp | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 49aadfba00584..098c62bcf40f3 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -72,10 +72,10 @@ class RISCV final : public TargetInfo {
#define INTERNAL_R_RISCV_GPREL_S 257
#define INTERNAL_R_RISCV_X0REL_I 258
#define INTERNAL_R_RISCV_X0REL_S 259
-#define INTERNAL_R_RISCV_TBJAL 260
+#define INTERNAL_R_RISCV_TBJAL 260
const uint64_t dtpOffset = 0x800;
-const uint32_t jvtAlign = 64;
+const uint32_t jvtAlign = 64;
namespace {
enum Op {
@@ -760,9 +760,10 @@ void elf::initSymbolAnchors(Ctx &ctx) {
}
}
-static bool relaxTableJump(Ctx &ctx, const InputSection &sec, size_t i, uint64_t loc,
- Relocation &r, uint32_t &remove) {
- if (!ctx.in.riscvTableJumpSection || !ctx.in.riscvTableJumpSection->isFinalized)
+static bool relaxTableJump(Ctx &ctx, const InputSection &sec, size_t i,
+ uint64_t loc, Relocation &r, uint32_t &remove) {
+ if (!ctx.in.riscvTableJumpSection ||
+ !ctx.in.riscvTableJumpSection->isFinalized)
return false;
const auto jalr = sec.contentMaybeDecompress().data()[r.offset + 4];
>From 538d001b124245715530f4db25041cd53186e6ae Mon Sep 17 00:00:00 2001
From: Robin Kastberg <robin.kastberg at iar.com>
Date: Mon, 13 Oct 2025 16:39:56 +0200
Subject: [PATCH 3/7] right version of clang-format helps
---
lld/ELF/Target.h | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index 0882b1f5ca2c4..ebb8eae2fea1c 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -38,8 +38,8 @@ class TargetInfo {
virtual void writeGotHeader(uint8_t *buf) const {}
virtual void writeGotPlt(uint8_t *buf, const Symbol &s) const {};
virtual void writeTableJumpHeader(uint8_t *buf) const {};
- virtual void writeTableJumpEntry(uint8_t *buf,
- const uint64_t symbol) const {};
+ virtual void writeTableJumpEntry(uint8_t *buf, const uint64_t symbol) const {
+ };
virtual void writeIgotPlt(uint8_t *buf, const Symbol &s) const {}
virtual int64_t getImplicitAddend(const uint8_t *buf, RelType type) const;
virtual int getTlsGdRelaxSkip(RelType type) const { return 1; }
>From 1ca85487af3000b9c2180e04edb88f97da794395 Mon Sep 17 00:00:00 2001
From: Robin Kastberg <Robin.Kastberg at iar.com>
Date: Tue, 14 Oct 2025 11:30:58 +0200
Subject: [PATCH 4/7] i lost a commit somehow
---
lld/ELF/Arch/RISCV.cpp | 221 ++++++++++++++++++++++++++++++++++
lld/ELF/SyntheticSections.cpp | 10 ++
lld/ELF/Writer.cpp | 13 ++
3 files changed, 244 insertions(+)
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 098c62bcf40f3..7f662feca5645 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -807,6 +807,8 @@ static void relaxCall(Ctx &ctx, const InputSection &sec, size_t i, uint64_t loc,
sec.relaxAux->relocTypes[i] = R_RISCV_RVC_JUMP;
sec.relaxAux->writes.push_back(0x2001); // c.jal
remove = 6;
+ } else if (remove >= 6 && relaxTableJump(ctx, sec, i, loc, r, remove)) {
+ // relaxTableJump sets remove
} else if (remove >= 4 && isInt<21>(displace)) {
sec.relaxAux->relocTypes[i] = R_RISCV_JAL;
sec.relaxAux->writes.push_back(0x6f | rd << 7); // jal
@@ -1184,6 +1186,12 @@ void RISCV::finalizeRelax(int passes) const {
case INTERNAL_R_RISCV_X0REL_I:
case INTERNAL_R_RISCV_X0REL_S:
break;
+ case INTERNAL_R_RISCV_TBJAL:
+ assert(ctx.arg.relaxTbljal);
+ assert((aux.writes[writesIdx] & 0xfc03) == 0xA002);
+ skip = 2;
+ write16le(p, aux.writes[writesIdx++]);
+ break;
case R_RISCV_RELAX:
// Used by relaxTlsLe to indicate the relocation is ignored.
break;
@@ -1524,3 +1532,216 @@ void elf::mergeRISCVAttributesSections(Ctx &ctx) {
}
void elf::setRISCVTargetInfo(Ctx &ctx) { ctx.target.reset(new RISCV(ctx)); }
+
+TableJumpSection::TableJumpSection(Ctx &ctx)
+ : SyntheticSection(ctx, ".riscv.jvt", SHT_PROGBITS,
+ SHF_ALLOC | SHF_EXECINSTR, jvtAlign) {}
+
+void TableJumpSection::addCMJTEntryCandidate(const Symbol *symbol,
+ int csReduction) {
+ addEntry(symbol, CMJTEntryCandidates, csReduction);
+}
+
+int TableJumpSection::getCMJTEntryIndex(const Symbol *symbol) {
+ uint32_t index = getIndex(symbol, maxCMJTEntrySize, finalizedCMJTEntries);
+ return index < finalizedCMJTEntries.size() ? (int)(startCMJTEntryIdx + index)
+ : -1;
+}
+
+void TableJumpSection::addCMJALTEntryCandidate(const Symbol *symbol,
+ int csReduction) {
+ addEntry(symbol, CMJALTEntryCandidates, csReduction);
+}
+
+int TableJumpSection::getCMJALTEntryIndex(const Symbol *symbol) {
+ uint32_t index = getIndex(symbol, maxCMJALTEntrySize, finalizedCMJALTEntries);
+ return index < finalizedCMJALTEntries.size()
+ ? (int)(startCMJALTEntryIdx + index)
+ : -1;
+}
+
+void TableJumpSection::addEntry(
+ const Symbol *symbol, llvm::DenseMap<const Symbol *, int> &entriesList,
+ int csReduction) {
+ entriesList[symbol] += csReduction;
+}
+
+uint32_t TableJumpSection::getIndex(
+ const Symbol *symbol, uint32_t maxSize,
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>
+ &entriesList) {
+ // Find this symbol in the ordered list of entries if it exists.
+ assert(maxSize >= entriesList.size() &&
+ "Finalized vector of entries exceeds maximum");
+ auto idx = std::find_if(
+ entriesList.begin(), entriesList.end(),
+ [symbol](llvm::detail::DenseMapPair<const Symbol *, int> &e) {
+ return e.first == symbol;
+ });
+
+ if (idx == entriesList.end())
+ return entriesList.size();
+ return idx - entriesList.begin();
+}
+
+void TableJumpSection::scanTableJumpEntries(const InputSection &sec) const {
+ for (auto [i, r] : llvm::enumerate(sec.relocations)) {
+ Defined *definedSymbol = dyn_cast<Defined>(r.sym);
+ if (!definedSymbol)
+ continue;
+ if (i + 1 == sec.relocs().size() ||
+ sec.relocs()[i + 1].type != R_RISCV_RELAX)
+ continue;
+ switch (r.type) {
+ case R_RISCV_JAL:
+ case R_RISCV_CALL:
+ case R_RISCV_CALL_PLT: {
+ const auto jalr = sec.contentMaybeDecompress().data()[r.offset + 4];
+ const uint8_t rd = extractBits(jalr, 11, 7);
+
+ int csReduction = 6;
+ if (sec.relaxAux->relocTypes[i] == R_RISCV_RVC_JUMP)
+ continue;
+ else if (sec.relaxAux->relocTypes[i] == R_RISCV_JAL)
+ csReduction = 2;
+
+ if (rd == 0)
+ ctx.in.riscvTableJumpSection->addCMJTEntryCandidate(r.sym, csReduction);
+ else if (rd == X_RA)
+ ctx.in.riscvTableJumpSection->addCMJALTEntryCandidate(r.sym,
+ csReduction);
+ }
+ }
+ }
+}
+
+void TableJumpSection::finalizeContents() {
+ if (isFinalized)
+ return;
+ isFinalized = true;
+
+ finalizedCMJTEntries = finalizeEntry(CMJTEntryCandidates, maxCMJTEntrySize);
+ CMJTEntryCandidates.clear();
+ int32_t CMJTSizeReduction = getSizeReduction();
+ finalizedCMJALTEntries =
+ finalizeEntry(CMJALTEntryCandidates, maxCMJALTEntrySize);
+ CMJALTEntryCandidates.clear();
+
+ if (finalizedCMJALTEntries.size() > 0 &&
+ getSizeReduction() < CMJTSizeReduction) {
+ // Stop relax to cm.jalt if there will be the code reduction of cm.jalt is
+ // smaller then the size of padding 0 for doing cm.jalt optmise
+ finalizedCMJALTEntries.clear();
+ }
+ // if table jump still got negative effect, give up.
+ if (getSizeReduction() <= 0) {
+ warn("Table Jump Relaxation didn't got any reduction for code size.");
+ finalizedCMJTEntries.clear();
+ }
+}
+
+// Sort the map in decreasing order of the amount of code reduction provided
+// by the entries. Drop any entries that can't fit in the map from the tail
+// end since they provide less code reduction. Drop any entries that cause
+// an increase in code size (i.e. the reduction from instruction conversion
+// does not cover the code size gain from adding a table entry).
+SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>
+TableJumpSection::finalizeEntry(llvm::DenseMap<const Symbol *, int> EntryMap,
+ uint32_t maxSize) {
+ auto cmp = [](const llvm::detail::DenseMapPair<const Symbol *, int> &p1,
+ const llvm::detail::DenseMapPair<const Symbol *, int> &p2) {
+ return p1.second > p2.second;
+ };
+
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>
+ tempEntryVector;
+ std::copy(EntryMap.begin(), EntryMap.end(),
+ std::back_inserter(tempEntryVector));
+ std::sort(tempEntryVector.begin(), tempEntryVector.end(), cmp);
+
+ auto finalizedVector = tempEntryVector;
+ if (tempEntryVector.size() >= maxSize)
+ finalizedVector =
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>(
+ tempEntryVector.begin(), tempEntryVector.begin() + maxSize);
+
+ // Drop any items that have a negative effect (i.e. increase code size).
+ while (finalizedVector.size()) {
+ if (finalizedVector.rbegin()->second < ctx.arg.wordsize)
+ finalizedVector.pop_back();
+ else
+ break;
+ }
+ return finalizedVector;
+}
+
+size_t TableJumpSection::getSize() const {
+ if (isFinalized) {
+ if (!finalizedCMJALTEntries.empty())
+ return (startCMJALTEntryIdx + finalizedCMJALTEntries.size()) *
+ ctx.arg.wordsize;
+ return (startCMJTEntryIdx + finalizedCMJTEntries.size()) * ctx.arg.wordsize;
+ } else {
+ if (!CMJALTEntryCandidates.empty())
+ return (startCMJALTEntryIdx + CMJALTEntryCandidates.size()) *
+ ctx.arg.wordsize;
+ return (startCMJTEntryIdx + CMJTEntryCandidates.size()) * ctx.arg.wordsize;
+ }
+}
+
+int32_t TableJumpSection::getSizeReduction() {
+ // The total reduction in code size is J + JA - JTS - JAE.
+ // Where:
+ // J = number of bytes saved for all the cm.jt instructions emitted
+ // JA = number of bytes saved for all the cm.jalt instructions emitted
+ // JTS = size of the part of the table for cm.jt jumps (i.e. 32 x wordsize)
+ // JAE = number of entries emitted for the cm.jalt jumps x wordsize
+
+ int32_t sizeReduction = -getSize();
+ for (auto entry : finalizedCMJTEntries) {
+ sizeReduction += entry.second;
+ }
+ for (auto entry : finalizedCMJALTEntries) {
+ sizeReduction += entry.second;
+ }
+ return sizeReduction;
+}
+
+void TableJumpSection::writeTo(uint8_t *buf) {
+ if (getSizeReduction() <= 0)
+ return;
+ ctx.target->writeTableJumpHeader(buf);
+ writeEntries(buf + startCMJTEntryIdx * ctx.arg.wordsize,
+ finalizedCMJTEntries);
+ if (finalizedCMJALTEntries.size() > 0) {
+ padWords(buf + ((startCMJTEntryIdx + finalizedCMJTEntries.size()) *
+ ctx.arg.wordsize),
+ startCMJALTEntryIdx);
+ writeEntries(buf + (startCMJALTEntryIdx * ctx.arg.wordsize),
+ finalizedCMJALTEntries);
+ }
+}
+
+void TableJumpSection::padWords(uint8_t *buf, const uint8_t maxWordCount) {
+ for (size_t i = 0; i < maxWordCount; ++i) {
+ if (ctx.arg.is64)
+ write64le(buf + i, 0);
+ else
+ write32le(buf + i, 0);
+ }
+}
+
+void TableJumpSection::writeEntries(
+ uint8_t *buf,
+ SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>
+ &entriesList) {
+ for (const auto &entry : entriesList) {
+ assert(entry.second > 0);
+ // Use the symbol from in.symTab to ensure we have the final adjusted
+ // symbol.
+ if (!entry.first->isDefined())
+ continue;
+ ctx.target->writeTableJumpEntry(buf, entry.first->getVA(ctx, 0));
+ buf += ctx.arg.wordsize;
+ }
+}
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index bbf4b29a9fda5..e65e1f2c19048 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -4872,6 +4872,16 @@ template <class ELFT> void elf::createSyntheticSections(Ctx &ctx) {
add(*ctx.in.ppc64LongBranchTarget);
}
+ if (ctx.arg.emachine == EM_RISCV && ctx.arg.relaxTbljal) {
+ ctx.in.riscvTableJumpSection = std::make_unique<TableJumpSection>(ctx);
+ add(*ctx.in.riscvTableJumpSection);
+
+ Symbol *s = ctx.symtab->addSymbol(Defined{ctx,
+ /*file=*/nullptr, "__jvt_base$", STB_GLOBAL, STT_NOTYPE, STT_NOTYPE,
+ /*value=*/0, /*size=*/0, ctx.in.riscvTableJumpSection.get()});
+ s->isUsedInRegularObj = true;
+ }
+
ctx.in.gotPlt = std::make_unique<GotPltSection>(ctx);
add(*ctx.in.gotPlt);
ctx.in.igotPlt = std::make_unique<IgotPltSection>(ctx);
diff --git a/lld/ELF/Writer.cpp b/lld/ELF/Writer.cpp
index 4fa80397cbfa7..f8558aab7f372 100644
--- a/lld/ELF/Writer.cpp
+++ b/lld/ELF/Writer.cpp
@@ -1570,6 +1570,19 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
changed |= a32p.createFixes();
}
+ if (ctx.arg.relaxTbljal) {
+ if (!changed) {
+ // scan all R_RISCV_JAL, R_RISCV_CALL/R_RISCV_CALL_PLT for RISCV Zcmt
+ // Jump table.
+ for (InputSectionBase *inputSection : ctx.inputSections) {
+ ctx.in.riscvTableJumpSection->scanTableJumpEntries(
+ cast<InputSection>(*inputSection));
+ }
+ ctx.in.riscvTableJumpSection->finalizeContents();
+ changed |= ctx.target->relaxOnce(pass);
+ }
+ }
+
finalizeSynthetic(ctx, ctx.in.got.get());
if (ctx.in.mipsGot)
ctx.in.mipsGot->updateAllocSize(ctx);
>From 00784b93bea681f3b601fbd69ba7a5c60ca7e0f9 Mon Sep 17 00:00:00 2001
From: Robin Kastberg <robin.kastberg at iar.com>
Date: Tue, 14 Oct 2025 11:47:45 +0200
Subject: [PATCH 5/7] fix option setting. tests are passing again
---
lld/ELF/Driver.cpp | 1 +
1 file changed, 1 insertion(+)
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 62f7fffce7dbe..9cac927615839 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1621,6 +1621,7 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &args) {
}
ctx.arg.zCombreloc = getZFlag(args, "combreloc", "nocombreloc", true);
ctx.arg.zCopyreloc = getZFlag(args, "copyreloc", "nocopyreloc", true);
+ ctx.arg.relaxTbljal = args.hasArg(OPT_relax_tbljal);
ctx.arg.zForceBti = hasZOption(args, "force-bti");
ctx.arg.zForceIbt = hasZOption(args, "force-ibt");
ctx.arg.zZicfilp = getZZicfilp(ctx, args);
>From 914cf2b2f25c1d5ae437ebf8b2ad8920a63a73ba Mon Sep 17 00:00:00 2001
From: Robin Kastberg <robin.kastberg at iar.com>
Date: Tue, 14 Oct 2025 15:10:03 +0200
Subject: [PATCH 6/7] fix instruction read and R_RISCV_JAL handling
---
lld/ELF/Arch/RISCV.cpp | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 7f662feca5645..0667a046182f9 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -766,7 +766,8 @@ static bool relaxTableJump(Ctx &ctx, const InputSection &sec, size_t i,
!ctx.in.riscvTableJumpSection->isFinalized)
return false;
- const auto jalr = sec.contentMaybeDecompress().data()[r.offset + 4];
+ const uint32_t jalr = read32le(sec.contentMaybeDecompress().data() +
+ r.offset + (r.type == R_RISCV_JAL ? 0 : 4));
const uint8_t rd = extractBits(jalr, 11, 7);
int tblEntryIndex = -1;
if (rd == 0) {
@@ -779,7 +780,7 @@ static bool relaxTableJump(Ctx &ctx, const InputSection &sec, size_t i,
sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_TBJAL;
sec.relaxAux->writes.push_back(0xA002 |
(tblEntryIndex << 2)); // cm.jt or cm.jalt
- remove = 6;
+ remove = (r.type == R_RISCV_JAL ? 2 : 6);
return true;
}
return false;
@@ -932,6 +933,11 @@ static bool relax(Ctx &ctx, int pass, InputSection &sec) {
relaxCall(ctx, sec, i, loc, r, remove);
}
break;
+ case R_RISCV_JAL:
+ if (relaxable(relocs, i)) {
+ relaxTableJump(ctx, sec, i, loc, r, remove);
+ }
+ break;
case R_RISCV_TPREL_HI20:
case R_RISCV_TPREL_ADD:
case R_RISCV_TPREL_LO12_I:
@@ -1596,7 +1602,9 @@ void TableJumpSection::scanTableJumpEntries(const InputSection &sec) const {
case R_RISCV_JAL:
case R_RISCV_CALL:
case R_RISCV_CALL_PLT: {
- const auto jalr = sec.contentMaybeDecompress().data()[r.offset + 4];
+ const uint32_t jalr =
+ read32le(sec.contentMaybeDecompress().data() + r.offset +
+ (r.type == R_RISCV_JAL ? 0 : 4));
const uint8_t rd = extractBits(jalr, 11, 7);
int csReduction = 6;
>From a1b8ee1c876d7f3335f62ffd080da71bb7123888 Mon Sep 17 00:00:00 2001
From: Robin Kastberg <kastberg at gmail.com>
Date: Wed, 15 Oct 2025 08:17:33 +0200
Subject: [PATCH 7/7] Apply suggestions from code review
Co-authored-by: Craig Topper <craig.topper at sifive.com>
---
lld/ELF/Arch/RISCV.cpp | 6 +++---
lld/ELF/SyntheticSections.h | 8 ++++----
2 files changed, 7 insertions(+), 7 deletions(-)
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 0667a046182f9..64a7f83adc174 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -770,7 +770,7 @@ static bool relaxTableJump(Ctx &ctx, const InputSection &sec, size_t i,
r.offset + (r.type == R_RISCV_JAL ? 0 : 4));
const uint8_t rd = extractBits(jalr, 11, 7);
int tblEntryIndex = -1;
- if (rd == 0) {
+ if (rd == X_X0) {
tblEntryIndex = ctx.in.riscvTableJumpSection->getCMJTEntryIndex(r.sym);
} else if (rd == X_RA) {
tblEntryIndex = ctx.in.riscvTableJumpSection->getCMJALTEntryIndex(r.sym);
@@ -1635,7 +1635,7 @@ void TableJumpSection::finalizeContents() {
finalizeEntry(CMJALTEntryCandidates, maxCMJALTEntrySize);
CMJALTEntryCandidates.clear();
- if (finalizedCMJALTEntries.size() > 0 &&
+ if (!finalizedCMJALTEntries.empty() &&
getSizeReduction() < CMJTSizeReduction) {
// Stop relax to cm.jalt if there will be the code reduction of cm.jalt is
// smaller then the size of padding 0 for doing cm.jalt optmise
@@ -1674,7 +1674,7 @@ TableJumpSection::finalizeEntry(llvm::DenseMap<const Symbol *, int> EntryMap,
tempEntryVector.begin(), tempEntryVector.begin() + maxSize);
// Drop any items that have a negative effect (i.e. increase code size).
- while (finalizedVector.size()) {
+ while (!finalizedVector.empty()) {
if (finalizedVector.rbegin()->second < ctx.arg.wordsize)
finalizedVector.pop_back();
else
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index fcf9139b4011d..fefd071c04699 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -411,11 +411,11 @@ class TableJumpSection final : public SyntheticSection {
void padWords(uint8_t *buf, const uint8_t maxWordCount);
// used in finalizeContents function.
- static const size_t maxCMJTEntrySize = 32;
- static const size_t maxCMJALTEntrySize = 224;
+ static constexpr size_t maxCMJTEntrySize = 32;
+ static constexpr size_t maxCMJALTEntrySize = 224;
- static const size_t startCMJTEntryIdx = 0;
- static const size_t startCMJALTEntryIdx = 32;
+ static constexpr size_t startCMJTEntryIdx = 0;
+ static constexpr size_t startCMJALTEntryIdx = 32;
llvm::DenseMap<const Symbol *, int> CMJTEntryCandidates;
SmallVector<llvm::detail::DenseMapPair<const Symbol *, int>, 0>
More information about the llvm-commits
mailing list