[PATCH] RISC-V: Add second-pass relaxation for JAL to C.J/C.JAL
Kito Cheng
kito.cheng@sifive.com
Thu Jan 8 07:00:44 GMT 2026
Hi Nelson:
That's a good point, I guess I will try to add one more internal
relocation R_RISCV_DELETE_AND_RELAX to prevent it immediately delete
the bytes, let me try and post v2 later :)
On Thu, Jan 8, 2026 at 11:35 AM Nelson Chu <nelson@rivosinc.com> wrote:
>
> Only deleting the bytes for call immediately seems like we can only do c.jal after relaxing many call patterns. This is also the case from the test cases. Doing relaxation after shrinking will also defeat our original intention of adding _riscv_relax_delete_piecewise to reduce link time. I would suggest having a new linker option to let users decide whether they care about the code size more and want more chance to do relaxation, the more link times for them are acceptable, so linker will always delete the bytes immediately rather than the piecewise. The benefit is that other relaxations also have the same opportunities to do relaxations after shrinking if users want. The increased link times are only for the people who enable the option and care about the code size more, so this won't affect others who care about the link time more. I would suggest we should have the new option at the same time with this patch, but it would also be fine to have the option later from my side. Just that maybe we can have some comments in code to say this may increase the link time and do nothing, so people who care about the link time more will know what's going on after the change.
>
> Thanks
> Nelson
>
> On Thu, Jan 8, 2026 at 9:51 AM Kito Cheng <kito.cheng@sifive.com> wrote:
>>
>> 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