[PATCH] RISC-V: Add second-pass relaxation for JAL to C.J/C.JAL
Kito Cheng
kito.cheng@sifive.com
Thu Jan 8 01:50:59 GMT 2026
When linker relaxation converts CALL (auipc+jalr, 8 bytes) to JAL
(4 bytes), further relaxation to C.J or C.JAL (2 bytes) may become
possible as code shrinks and jump distances decrease.
This patch adds _bfd_riscv_relax_jal to perform this second-pass
relaxation. To enable this, _bfd_riscv_relax_call now preserves the
R_RISCV_RELAX relocation when relaxing to JAL by using immediate
deletion instead of piecewise deletion. The preserved R_RISCV_RELAX
allows _bfd_riscv_relax_jal to further relax JAL to C.J/C.JAL.
C.JAL is only available on RV32 (rd=ra), while C.J is available on
both RV32 and RV64 (rd=x0).
---
bfd/elfnn-riscv.c | 75 +++++++++++++++++++++-
ld/testsuite/ld-riscv-elf/j-to-cj-32.d | 33 ++++++++++
ld/testsuite/ld-riscv-elf/j-to-cj-64.d | 34 ++++++++++
ld/testsuite/ld-riscv-elf/j-to-cj.s | 59 +++++++++++++++++
ld/testsuite/ld-riscv-elf/jal-to-cjal.d | 34 ++++++++++
ld/testsuite/ld-riscv-elf/jal-to-cjal.s | 59 +++++++++++++++++
ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp | 3 +
7 files changed, 296 insertions(+), 1 deletion(-)
create mode 100644 ld/testsuite/ld-riscv-elf/j-to-cj-32.d
create mode 100644 ld/testsuite/ld-riscv-elf/j-to-cj-64.d
create mode 100644 ld/testsuite/ld-riscv-elf/j-to-cj.s
create mode 100644 ld/testsuite/ld-riscv-elf/jal-to-cjal.d
create mode 100644 ld/testsuite/ld-riscv-elf/jal-to-cjal.s
diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index 4447eea0682..0a7aec77099 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -4941,12 +4941,83 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
/* Replace the AUIPC. */
riscv_put_insn (8 * len, auipc, contents + rel->r_offset);
- /* Delete unnecessary JALR and reuse the R_RISCV_RELAX reloc. */
+ /* Delete unnecessary JALR. */
*again = true;
+ if (r_type == R_RISCV_JAL)
+ {
+ /* For JAL, use immediate deletion and preserve R_RISCV_RELAX for
+ potential second-pass relaxation to C.J/C.JAL. */
+ return _riscv_relax_delete_immediate (abfd, sec, rel->r_offset + len,
+ 8 - len, link_info, pcgp_relocs,
+ NULL);
+ }
+ /* Reuse the R_RISCV_RELAX reloc for piecewise deletion. */
return riscv_relax_delete_bytes (abfd, sec, rel->r_offset + len, 8 - len,
link_info, pcgp_relocs, rel + 1);
}
+/* Relax JAL to C.J or C.JAL. */
+
+static bool
+_bfd_riscv_relax_jal (bfd *abfd, asection *sec, asection *sym_sec,
+ struct bfd_link_info *link_info,
+ Elf_Internal_Rela *rel,
+ bfd_vma symval,
+ bfd_vma max_alignment,
+ bfd_vma reserve_size ATTRIBUTE_UNUSED,
+ bool *again,
+ riscv_pcgp_relocs *pcgp_relocs,
+ bool undefined_weak ATTRIBUTE_UNUSED)
+{
+ bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
+ bfd_vma foff = symval - (sec_addr (sec) + rel->r_offset);
+ bool rvc = elf_elfheader (abfd)->e_flags & EF_RISCV_RVC;
+
+ /* Can't relax to compressed instruction without RVC. */
+ if (!rvc)
+ return true;
+
+ bfd_vma jal = bfd_getl32 (contents + rel->r_offset);
+ int rd = (jal >> OP_SH_RD) & OP_MASK_RD;
+
+ /* C.J exists on RV32 and RV64, but C.JAL is RV32-only. */
+ if (!(rd == 0 || (rd == X_RA && ARCH_SIZE == 32)))
+ return true;
+
+ /* If the jump crosses section boundaries, an alignment directive could
+ cause the PC-relative offset to later increase, so we need to add in the
+ max alignment of any section inclusive from the jump to the target.
+ Otherwise, we only need to use the alignment of the current section. */
+ if (VALID_CJTYPE_IMM (foff))
+ {
+ if (sym_sec->output_section == sec->output_section
+ && sym_sec->output_section != bfd_abs_section_ptr)
+ max_alignment = (bfd_vma) 1 << sym_sec->output_section->alignment_power;
+ foff += ((bfd_signed_vma) foff < 0 ? -max_alignment : max_alignment);
+ }
+
+ /* See if this jump can be shortened. */
+ if (!VALID_CJTYPE_IMM (foff))
+ return true;
+
+ /* Shorten the jump. */
+ BFD_ASSERT (rel->r_offset + 4 <= sec->size);
+
+ /* Relax to C.J[AL] rd, addr. */
+ int r_type = R_RISCV_RVC_JUMP;
+ bfd_vma insn = (rd == 0) ? MATCH_C_J : MATCH_C_JAL;
+
+ /* Replace the R_RISCV_JAL reloc. */
+ rel->r_info = ELFNN_R_INFO (ELFNN_R_SYM (rel->r_info), r_type);
+ /* Replace the JAL with C.J or C.JAL. */
+ riscv_put_insn (8 * 2, insn, contents + rel->r_offset);
+
+ /* Delete 2 bytes and reuse the R_RISCV_RELAX reloc. */
+ *again = true;
+ return riscv_relax_delete_bytes (abfd, sec, rel->r_offset + 2, 2,
+ link_info, pcgp_relocs, rel + 1);
+}
+
/* Traverse all output sections and return the max alignment.
If gp is zero, then all the output section alignments are
@@ -5459,6 +5530,8 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
|| type == R_RISCV_PCREL_LO12_I
|| type == R_RISCV_PCREL_LO12_S))
relax_func = _bfd_riscv_relax_pc;
+ else if (type == R_RISCV_JAL)
+ relax_func = _bfd_riscv_relax_jal;
else
continue;
riscv_relax_delete_bytes = _riscv_relax_delete_piecewise;
diff --git a/ld/testsuite/ld-riscv-elf/j-to-cj-32.d b/ld/testsuite/ld-riscv-elf/j-to-cj-32.d
new file mode 100644
index 00000000000..964e0522f19
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/j-to-cj-32.d
@@ -0,0 +1,33 @@
+#name: j to c.j second-pass relaxation (RV32)
+#source: j-to-cj.s
+#as: -march=rv32ic
+#ld: -melf32lriscv --relax
+#objdump: -d -M no-aliases
+
+# This test verifies that the linker performs second-pass relaxation
+# to convert j (jal x0) to c.j when distance shrinks due to other relaxations.
+#
+# Expected: All tails including critical should become c.j (2 bytes)
+#
+# c.j opcode: 2 bytes (4 hex digits)
+# j opcode: 4 bytes (8 hex digits)
+
+.*:[ ]+file format .*
+
+Disassembly of section \.text:
+
+.* <_start>:
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f1>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f2>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f3>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f4>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f5>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f6>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f7>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f8>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f9>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f10>
+
+.* <critical>:
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <target>
+#...
diff --git a/ld/testsuite/ld-riscv-elf/j-to-cj-64.d b/ld/testsuite/ld-riscv-elf/j-to-cj-64.d
new file mode 100644
index 00000000000..6b9c0f08757
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/j-to-cj-64.d
@@ -0,0 +1,34 @@
+#name: j to c.j second-pass relaxation (RV64)
+#source: j-to-cj.s
+#as: -march=rv64ic
+#ld: -melf64lriscv --relax
+#objdump: -d -M no-aliases
+
+# This test verifies that the linker performs second-pass relaxation
+# to convert j (jal x0) to c.j when distance shrinks due to other relaxations.
+#
+# Expected: All tails including critical should become c.j (2 bytes)
+# c.j is available on both RV32 and RV64.
+#
+# c.j opcode: 2 bytes (4 hex digits)
+# j opcode: 4 bytes (8 hex digits)
+
+.*:[ ]+file format .*
+
+Disassembly of section \.text:
+
+.* <_start>:
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f1>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f2>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f3>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f4>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f5>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f6>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f7>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f8>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f9>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <f10>
+
+.* <critical>:
+.*:[ ]+[0-9a-f]{4}[ ]+c\.j[ ]+.* <target>
+#...
diff --git a/ld/testsuite/ld-riscv-elf/j-to-cj.s b/ld/testsuite/ld-riscv-elf/j-to-cj.s
new file mode 100644
index 00000000000..39f1590713e
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/j-to-cj.s
@@ -0,0 +1,59 @@
+# Test case: j -> c.j second-pass relaxation
+#
+# This test verifies that the linker performs a second relaxation pass
+# to convert j (jal x0) instructions to c.j when the distance shrinks
+# due to other relaxations.
+#
+# c.j range: +/-2046 bytes (12-bit signed offset, LSB=0)
+# Available on both RV32C and RV64C
+#
+# Scenario:
+# 1. Multiple tails that relax from auipc+jalr (8B) to j (4B)
+# 2. After first pass: critical tail to target is ~2050 bytes (> c.j range)
+# 3. If second pass runs (j -> c.j for other tails), shrinkage
+# brings critical tail distance under 2046 bytes
+#
+# Expected behavior with two-pass relaxation:
+# - All tails to f1-f10 become c.j
+# - Critical tail also becomes c.j
+
+ .text
+ .globl _start
+_start:
+ tail f1
+ tail f2
+ tail f3
+ tail f4
+ tail f5
+ tail f6
+ tail f7
+ tail f8
+ tail f9
+ tail f10
+
+critical:
+ # This tail should become c.j with two-pass relaxation
+ tail target
+
+ # Padding: 505 * 4 = 2020 bytes
+ # After 1st pass (tail->j): distance = 4 + 2020 + 20 + 2 = 2046 (borderline)
+ # After 2nd pass (j->c.j): distance = 2 + 2020 + 20 + 2 = 2044 (can c.j)
+ .option norvc
+ .rept 505
+ nop
+ .endr
+ .option rvc
+
+f1: ret
+f2: ret
+f3: ret
+f4: ret
+f5: ret
+f6: ret
+f7: ret
+f8: ret
+f9: ret
+f10: ret
+
+target:
+ ret
diff --git a/ld/testsuite/ld-riscv-elf/jal-to-cjal.d b/ld/testsuite/ld-riscv-elf/jal-to-cjal.d
new file mode 100644
index 00000000000..6d448e23375
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/jal-to-cjal.d
@@ -0,0 +1,34 @@
+#name: jal to c.jal second-pass relaxation
+#source: jal-to-cjal.s
+#as: -march=rv32ic
+#ld: -melf32lriscv --relax
+#objdump: -d -M no-aliases
+
+# This test verifies that the linker performs second-pass relaxation
+# to convert jal to c.jal when distance shrinks due to other relaxations.
+#
+# Expected: All calls including critical should become c.jal (2 bytes)
+# Current trunk behavior: All calls remain as jal (4 bytes)
+#
+# c.jal opcode: 2 bytes (4 hex digits)
+# jal opcode: 4 bytes (8 hex digits)
+
+.*:[ ]+file format .*
+
+Disassembly of section \.text:
+
+.* <_start>:
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f1>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f2>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f3>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f4>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f5>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f6>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f7>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f8>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f9>
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <f10>
+
+.* <critical>:
+.*:[ ]+[0-9a-f]{4}[ ]+c\.jal[ ]+.* <target>
+#...
diff --git a/ld/testsuite/ld-riscv-elf/jal-to-cjal.s b/ld/testsuite/ld-riscv-elf/jal-to-cjal.s
new file mode 100644
index 00000000000..4570b87f320
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/jal-to-cjal.s
@@ -0,0 +1,59 @@
+# Test case: jal -> c.jal second-pass relaxation
+#
+# This test verifies that the linker performs a second relaxation pass
+# to convert jal instructions to c.jal when the distance shrinks
+# due to other relaxations.
+#
+# c.jal range: +/-2046 bytes (12-bit signed offset, LSB=0)
+# Only available in RV32C (not RV64C)
+#
+# Scenario:
+# 1. Multiple calls that relax from auipc+jalr (8B) to jal (4B)
+# 2. After first pass: critical call to target is ~2050 bytes (> c.jal range)
+# 3. If second pass runs (jal -> c.jal for other calls), shrinkage
+# brings critical call distance under 2046 bytes
+#
+# Expected behavior with two-pass relaxation:
+# - All calls to f1-f10 become c.jal
+# - Critical call also becomes c.jal
+
+ .text
+ .globl _start
+_start:
+ call f1
+ call f2
+ call f3
+ call f4
+ call f5
+ call f6
+ call f7
+ call f8
+ call f9
+ call f10
+
+critical:
+ # This call should become c.jal with two-pass relaxation
+ call target
+
+ # Padding: 505 * 4 = 2020 bytes
+ # After 1st pass (call->jal): distance = 4 + 2020 + 20 + 2 = 2046 (borderline)
+ # After 2nd pass (jal->c.jal): distance = 2 + 2020 + 20 + 2 = 2044 (can c.jal)
+ .option norvc
+ .rept 505
+ nop
+ .endr
+ .option rvc
+
+f1: ret
+f2: ret
+f3: ret
+f4: ret
+f5: ret
+f6: ret
+f7: ret
+f8: ret
+f9: ret
+f10: ret
+
+target:
+ ret
diff --git a/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp b/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
index f6706b2c6a3..8e26ccff10a 100644
--- a/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
+++ b/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
@@ -121,6 +121,9 @@ proc run_relax_twice_test {} {
if [istarget "riscv*-*-*"] {
run_dump_test "align-small-region"
run_dump_test "call-relax"
+ run_dump_test "jal-to-cjal"
+ run_dump_test "j-to-cj-32"
+ run_dump_test "j-to-cj-64"
run_dump_test "pcgp-relax-01"
run_dump_test "pcgp-relax-01-norelaxgp"
run_dump_test "pcgp-relax-02"
--
2.52.0
More information about the Binutils
mailing list