[PATCH v2] RISC-V: Add second-pass relaxation for JAL to C.J/C.JAL

Kito Cheng kito.cheng@sifive.com
Mon Jan 12 08:26:32 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, we introduce R_RISCV_DELETE_AND_RELAX,
a new internal relocation that combines piecewise deletion with
preservation of relaxation capability.  When _bfd_riscv_relax_call
relaxes CALL to JAL, it marks the deletion as R_RISCV_DELETE_AND_RELAX
instead of R_RISCV_DELETE.  After the piecewise deletion is resolved,
R_RISCV_DELETE_AND_RELAX is converted back to R_RISCV_RELAX at the
JAL instruction offset, allowing _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).

Changes since v1:
- Use R_RISCV_DELETE_AND_RELAX with piecewise deletion instead of
  calling _riscv_relax_delete_immediate directly, to maintain
  relaxation performance.
- Add preserve_relax parameter to riscv_relax_delete_bytes to
  simplify the logic in _bfd_riscv_relax_call.
---
 bfd/elfnn-riscv.c                          | 130 ++++++++++++++++++---
 bfd/elfxx-riscv.c                          |   3 +
 include/elf/riscv.h                        |  13 ++-
 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 +
 9 files changed, 344 insertions(+), 24 deletions(-)
 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..d3963ec9a58 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -2133,6 +2133,7 @@ perform_relocation (const reloc_howto_type *howto,
       break;
 
     case R_RISCV_DELETE:
+    case R_RISCV_DELETE_AND_RELAX:
       return bfd_reloc_ok;
 
     default:
@@ -2793,6 +2794,7 @@ riscv_elf_relocate_section (bfd *output_bfd,
 	case R_RISCV_SET32:
 	case R_RISCV_32_PCREL:
 	case R_RISCV_DELETE:
+	case R_RISCV_DELETE_AND_RELAX:
 	  /* These require no special handling beyond perform_relocation.  */
 	  break;
 
@@ -4770,12 +4772,14 @@ typedef bool (*relax_delete_t) (bfd *, asection *,
 				bfd_vma, size_t,
 				struct bfd_link_info *,
 				riscv_pcgp_relocs *,
-				Elf_Internal_Rela *);
+				Elf_Internal_Rela *,
+				bool preserve_relax);
 
 static relax_delete_t riscv_relax_delete_bytes;
 
 /* Do not delete some bytes from a section while relaxing.
-   Just mark the deleted bytes as R_RISCV_DELETE.  */
+   Just mark the deleted bytes as R_RISCV_DELETE.  If PRESERVE_RELAX is true,
+   use R_RISCV_DELETE_AND_RELAX to preserve the ability to further relax.  */
 
 static bool
 _riscv_relax_delete_piecewise (bfd *abfd ATTRIBUTE_UNUSED,
@@ -4784,11 +4788,13 @@ _riscv_relax_delete_piecewise (bfd *abfd ATTRIBUTE_UNUSED,
 			       size_t count,
 			       struct bfd_link_info *link_info ATTRIBUTE_UNUSED,
 			       riscv_pcgp_relocs *p ATTRIBUTE_UNUSED,
-			       Elf_Internal_Rela *rel)
+			       Elf_Internal_Rela *rel,
+			       bool preserve_relax)
 {
   if (rel == NULL)
     return false;
-  rel->r_info = ELFNN_R_INFO (0, R_RISCV_DELETE);
+  rel->r_info = ELFNN_R_INFO (0, preserve_relax
+			      ? R_RISCV_DELETE_AND_RELAX : R_RISCV_DELETE);
   rel->r_offset = addr;
   rel->r_addend = count;
   return true;
@@ -4803,7 +4809,8 @@ _riscv_relax_delete_immediate (bfd *abfd,
 			       size_t count,
 			       struct bfd_link_info *link_info,
 			       riscv_pcgp_relocs *p,
-			       Elf_Internal_Rela *rel)
+			       Elf_Internal_Rela *rel,
+			       bool preserve_relax ATTRIBUTE_UNUSED)
 {
   if (rel != NULL)
     rel->r_info = ELFNN_R_INFO (0, R_RISCV_NONE);
@@ -4811,7 +4818,15 @@ _riscv_relax_delete_immediate (bfd *abfd,
 				    link_info, p, 0, sec->size);
 }
 
-/* Delete the bytes for R_RISCV_DELETE relocs.  */
+/* Return true if TYPE is a delete relocation.  */
+
+static bool
+riscv_is_delete_reloc (unsigned int type)
+{
+  return type == R_RISCV_DELETE || type == R_RISCV_DELETE_AND_RELAX;
+}
+
+/* Delete the bytes for R_RISCV_DELETE and R_RISCV_DELETE_AND_RELAX relocs.  */
 
 static bool
 riscv_relax_resolve_delete_relocs (bfd *abfd,
@@ -4825,10 +4840,11 @@ riscv_relax_resolve_delete_relocs (bfd *abfd,
   for (i = 0; i < sec->reloc_count; i++)
     {
       Elf_Internal_Rela *rel = relocs + i;
-      if (ELFNN_R_TYPE (rel->r_info) != R_RISCV_DELETE)
+      unsigned int type = ELFNN_R_TYPE (rel->r_info);
+      if (!riscv_is_delete_reloc (type))
 	continue;
 
-      /* Find the next R_RISCV_DELETE reloc if possible.  */
+      /* Find the next delete reloc if possible.  */
       Elf_Internal_Rela *rel_next = NULL;
       unsigned int start = rel - relocs;
       for (i = start; i < sec->reloc_count; i++)
@@ -4837,8 +4853,8 @@ riscv_relax_resolve_delete_relocs (bfd *abfd,
 	     relocs are in sequential order. We can skip the relocs prior to this
 	     one, making this search linear time.  */
 	  rel_next = relocs + i;
-	  if (ELFNN_R_TYPE ((rel_next)->r_info) == R_RISCV_DELETE
-	      && (rel_next)->r_offset > rel->r_offset)
+	  if (riscv_is_delete_reloc (ELFNN_R_TYPE (rel_next->r_info))
+	      && rel_next->r_offset > rel->r_offset)
 	    {
 	      BFD_ASSERT (rel_next - rel > 0);
 	      break;
@@ -4853,7 +4869,18 @@ riscv_relax_resolve_delete_relocs (bfd *abfd,
 	return false;
 
       delete_total += rel->r_addend;
-      rel->r_info = ELFNN_R_INFO (0, R_RISCV_NONE);
+
+      if (type == R_RISCV_DELETE_AND_RELAX)
+	{
+	  /* Convert to R_RISCV_RELAX at the instruction offset.
+	     The deletion started after the instruction, so subtract
+	     the number of deleted bytes to get back to the instruction.  */
+	  rel->r_info = ELFNN_R_INFO (0, R_RISCV_RELAX);
+	  rel->r_offset -= rel->r_addend;
+	  rel->r_addend = 0;
+	}
+      else
+	rel->r_info = ELFNN_R_INFO (0, R_RISCV_NONE);
 
       /* Skip ahead to the next delete reloc.  */
       i = rel_next != NULL ? (unsigned int) (rel_next - relocs - 1)
@@ -4941,10 +4968,75 @@ _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 and reuse the R_RISCV_RELAX reloc.
+     For JAL, use R_RISCV_DELETE_AND_RELAX to preserve the ability to
+     further relax to C.J/C.JAL in a second pass.  */
   *again = true;
   return riscv_relax_delete_bytes (abfd, sec, rel->r_offset + len, 8 - len,
-				   link_info, pcgp_relocs, rel + 1);
+				   link_info, pcgp_relocs, rel + 1,
+				   r_type == R_RISCV_JAL);
+}
+
+/* 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, false);
 }
 
 /* Traverse all output sections and return the max alignment.
@@ -5060,7 +5152,7 @@ _bfd_riscv_relax_lui (bfd *abfd,
 	  /* Delete unnecessary LUI and reuse the reloc.  */
 	  *again = true;
 	  return riscv_relax_delete_bytes (abfd, sec, rel->r_offset, 4,
-					   link_info, pcgp_relocs, rel);
+					   link_info, pcgp_relocs, rel, false);
 
 	default:
 	  abort ();
@@ -5093,7 +5185,7 @@ _bfd_riscv_relax_lui (bfd *abfd,
       /* Delete extra 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);
+				       link_info, pcgp_relocs, rel + 1, false);
     }
 
   return true;
@@ -5134,7 +5226,7 @@ _bfd_riscv_relax_tls_le (bfd *abfd,
       /* Delete unnecessary instruction and reuse the reloc.  */
       *again = true;
       return riscv_relax_delete_bytes (abfd, sec, rel->r_offset, 4, link_info,
-				       pcgp_relocs, rel);
+				       pcgp_relocs, rel, false);
 
     default:
       abort ();
@@ -5198,7 +5290,7 @@ _bfd_riscv_relax_align (bfd *abfd, asection *sec,
   /* Delete excess bytes.  */
   return riscv_relax_delete_bytes (abfd, sec, rel->r_offset + nop_bytes,
 				   rel->r_addend - nop_bytes, link_info,
-				   NULL, NULL);
+				   NULL, NULL, false);
 }
 
 /* Relax PC-relative references to GP-relative references.  */
@@ -5345,7 +5437,7 @@ _bfd_riscv_relax_pc (bfd *abfd ATTRIBUTE_UNUSED,
 	  /* Delete unnecessary AUIPC and reuse the reloc.  */
 	  *again = true;
 	  riscv_relax_delete_bytes (abfd, sec, rel->r_offset, 4, link_info,
-				    pcgp_relocs, rel);
+				    pcgp_relocs, rel, false);
 	  return true;
 
 	default:
@@ -5459,6 +5551,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/bfd/elfxx-riscv.c b/bfd/elfxx-riscv.c
index b0f635114e1..ee962b4f6f1 100644
--- a/bfd/elfxx-riscv.c
+++ b/bfd/elfxx-riscv.c
@@ -884,6 +884,9 @@ static const reloc_howto_type howto_table_internal[] =
   /* R_RISCV_DELETE.  */
   EMPTY_HOWTO (0),
 
+  /* R_RISCV_DELETE_AND_RELAX.  */
+  EMPTY_HOWTO (0),
+
   /* High 6 bits of 18-bit absolute address.  */
   HOWTO (R_RISCV_RVC_LUI,		/* type */
 	 0,				/* rightshift */
diff --git a/include/elf/riscv.h b/include/elf/riscv.h
index 49d6cbf57c4..f6ca3d4acd8 100644
--- a/include/elf/riscv.h
+++ b/include/elf/riscv.h
@@ -98,12 +98,13 @@ START_RELOC_NUMBERS (elf_riscv_reloc_type)
 END_RELOC_NUMBERS (R_RISCV_max)
 
 /* Internal relocations used exclusively by the relaxation pass.  */
-#define R_RISCV_DELETE  (R_RISCV_max)
-#define R_RISCV_RVC_LUI (R_RISCV_max + 1)
-#define R_RISCV_GPREL_I (R_RISCV_max + 2)
-#define R_RISCV_GPREL_S (R_RISCV_max + 3)
-#define R_RISCV_TPREL_I (R_RISCV_max + 4)
-#define R_RISCV_TPREL_S (R_RISCV_max + 5)
+#define R_RISCV_DELETE            (R_RISCV_max)
+#define R_RISCV_DELETE_AND_RELAX  (R_RISCV_max + 1)
+#define R_RISCV_RVC_LUI           (R_RISCV_max + 2)
+#define R_RISCV_GPREL_I           (R_RISCV_max + 3)
+#define R_RISCV_GPREL_S           (R_RISCV_max + 4)
+#define R_RISCV_TPREL_I           (R_RISCV_max + 5)
+#define R_RISCV_TPREL_S           (R_RISCV_max + 6)
 
 /* Processor specific flags for the ELF header e_flags field.  */
 
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