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

Kito Cheng kito.cheng@gmail.com
Wed Jan 14 09:47:16 GMT 2026


Verified with following configuration with riscv-gnu-toolchain, and no
new regression:

      rv32i/  ilp32/ medlow
   rv32iac/  ilp32/ medlow
    rv32im/  ilp32/ medlow
  rv32imac/  ilp32/ medlow
 rv32imafc/ ilp32f/ medlow
    rv64gc/  lp64d/ medlow
  rv64imac/   lp64/ medlow

Nelson Chu <nelson.chu1990@gmail.com> 於 2026年1月14日週三 上午11:28寫道:
>
> Okay, since this definitely helps the code size reduction, so LGTM.
> Let me know if we already pass at least the gcc/binutils regressions
> of riscv-gnu-toolchain in case of any accident, then I will commit ;)
>
> Thanks
> Nelson
>
> On Mon, Jan 12, 2026 at 4:27 PM Kito Cheng via Binutils
> <binutils@sourceware.org> 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, 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