[PATCH v1]RISC-V: Zcmt relaxationi support.

Umesh Kalappa ukalappa.mips@gmail.com
Tue Nov 25 12:34:06 GMT 2025


This patch is refactored the https://sourceware.org/pipermail/binutils/2024-January/131573.html.

and below changes are made like
a)Enabled zcmt relaxation from linker along --relax option.
b)Added min-zcmt-size option to zcmt size heuristic.
c)Disassembler support for zcmt insn.

---
 bfd/elfnn-riscv.c                          | 648 ++++++++++++++++++++-
 bfd/elfxx-riscv.c                          |  17 +
 gas/config/tc-riscv.c                      |  41 +-
 include/bfdlink.h                          |   3 +
 include/elf/riscv.h                        |  10 +
 ld/ldlex.h                                 |   1 +
 ld/ldmain.c                                |   2 +
 ld/lexsup.c                                |  13 +
 ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp |   1 +
 ld/testsuite/ld-riscv-elf/zcmt_relax.d     |  18 +
 ld/testsuite/ld-riscv-elf/zcmt_relax.ld    |  38 ++
 ld/testsuite/ld-riscv-elf/zcmt_relax.s     |   9 +
 opcodes/riscv-dis.c                        |  40 ++
 13 files changed, 831 insertions(+), 10 deletions(-)
 create mode 100644 ld/testsuite/ld-riscv-elf/zcmt_relax.d
 create mode 100644 ld/testsuite/ld-riscv-elf/zcmt_relax.ld
 create mode 100644 ld/testsuite/ld-riscv-elf/zcmt_relax.s

diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index dd5268b7d16..d5642842527 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -208,6 +208,40 @@ elfNN_riscv_mkobject (bfd *abfd)
 #include "elf/common.h"
 #include "elf/internal.h"
 
+/* debug use */
+#define ZCMT_PRINT_TABLE_JUMP_ENTRIES 0
+
+/* Hash table for storing table jump candidate entries.  */
+typedef struct
+{
+  htab_t tbljt_htab;
+  htab_t tbljalt_htab;
+  uintNN_t *tbj_indexes;
+  asection *tablejump_sec;
+  bfd *tablejump_sec_owner;
+  /* cmjt table active index */
+  short tbljt_index;
+  /* cmjalt table active index */
+  short tbljalt_index;
+  /* used for profiling */
+  int end_idx;
+  int total_saving;
+
+  /* debug use.  */
+  int *savings;
+  const char **names;
+} riscv_table_jump_htab_t;
+
+typedef struct
+{
+  bfd_vma address;
+  unsigned int index;
+
+  /* debug use.  */
+  const char *name;
+  int benefit;
+} riscv_table_jump_htab_entry;
+
 struct riscv_elf_link_hash_table
 {
   struct elf_link_hash_table elf;
@@ -246,6 +280,7 @@ struct riscv_elf_link_hash_table
   bool (*make_plt_header) (bfd *output_bfd, struct riscv_elf_link_hash_table *htab);
   bool (*make_plt_entry) (bfd *output_bfd, asection *got, bfd_vma got_offset,
 			  asection *plt, bfd_vma plt_offset);
+   riscv_table_jump_htab_t *table_jump_htab;
 };
 
 /* Instruction access functions. */
@@ -568,6 +603,83 @@ link_hash_newfunc (struct bfd_hash_entry *entry,
   return entry;
 }
 
+static hashval_t
+riscv_table_jump_htab_hash (const void *entry)
+{
+  const riscv_table_jump_htab_entry *e = entry;
+  return (hashval_t)(e->address >> 2);
+}
+
+static int
+riscv_table_jump_htab_entry_eq (const void *entry1, const void *entry2)
+{
+  const riscv_table_jump_htab_entry *e1 = entry1, *e2 = entry2;
+  return e1->address == e2->address;
+}
+
+static bool
+riscv_init_table_jump_htab (riscv_table_jump_htab_t *htab)
+{
+  htab->names = bfd_zmalloc (sizeof (const char *) * 256);
+  htab->savings = bfd_zmalloc (sizeof (unsigned int) * 256);
+  htab->tbj_indexes = bfd_zmalloc (RISCV_ELF_WORD_BYTES * 256);
+  htab->end_idx = 0;
+  htab->total_saving = 0;
+
+  htab->tbljt_htab = htab_create (50, riscv_table_jump_htab_hash,
+			      riscv_table_jump_htab_entry_eq, free);
+  if (htab->tbljt_htab == NULL)
+    return false;
+
+  htab->tbljalt_htab = htab_create (50, riscv_table_jump_htab_hash,
+			      riscv_table_jump_htab_entry_eq, free);
+  return htab->tbljalt_htab != NULL;
+}
+
+static void
+riscv_free_table_jump_htab (riscv_table_jump_htab_t *htab)
+{
+  free (htab->names);
+  free (htab->savings);
+  free (htab->tbj_indexes);
+  htab_delete (htab->tbljt_htab);
+  htab_delete (htab->tbljalt_htab);
+}
+
+static bool
+riscv_update_table_jump_entry (htab_t htab,
+			       bfd_vma addr,
+			       unsigned int benefit,
+			       const char *name)
+{
+  riscv_table_jump_htab_entry search = {addr, 0, NULL, 0};
+  riscv_table_jump_htab_entry *entry = htab_find (htab, &search);
+
+  if (entry == NULL)
+    {
+      riscv_table_jump_htab_entry **slot =
+	(riscv_table_jump_htab_entry **) htab_find_slot (
+	  htab, &search, INSERT);
+
+      BFD_ASSERT (*slot == NULL);
+
+      *slot = (riscv_table_jump_htab_entry *) bfd_zmalloc (
+	    sizeof (riscv_table_jump_htab_entry));
+
+      if (*slot == NULL)
+	return false;
+
+      (*slot)->address = addr;
+      (*slot)->benefit = benefit;
+      (*slot)->name = name;
+    }
+  else {
+	/* account for jump entry size */
+    entry->benefit +=  benefit + RISCV_ELF_WORD_BYTES;
+  }
+  return true;
+}
+
 /* Compute a hash of a local hash entry.  We use elf_link_hash_entry
    for local symbol so that we can handle local STT_GNU_IFUNC symbols
    as global symbol.  We reuse indx and dynstr_index for local symbol
@@ -632,6 +744,25 @@ riscv_elf_get_local_sym_hash (struct riscv_elf_link_hash_table *htab,
   return &ret->elf;
 }
 
+#if ZCMT_PRINT_TABLE_JUMP_ENTRIES
+static void
+print_tablejump_entries(riscv_table_jump_htab_t *table_jump_htab)
+{
+
+  if (table_jump_htab->tbj_indexes[0])
+    printf("cm.jt:\n");
+  for (unsigned int z = 0; z < 32 && table_jump_htab->tbj_indexes[z] != 0; z ++)
+    printf ("\tindex=%d, sym name=%s, address=0x%08lx, savings=%u\n",
+	z, table_jump_htab->names[z], table_jump_htab->tbj_indexes[z], table_jump_htab->savings[z]);
+
+  if (table_jump_htab->tbj_indexes[32])
+    printf("cm.jalt:\n");
+  for (unsigned int z = 32; z < 256 && table_jump_htab->tbj_indexes[z] != 0; z ++)
+    printf ("\tindex=%d, sym name=%s, address=0x%08lx, savings=%u\n",
+	z, table_jump_htab->names[z], table_jump_htab->tbj_indexes[z], table_jump_htab->savings[z]);
+}
+#endif
+
 /* Destroy a RISC-V elf linker hash table.  */
 
 static void
@@ -645,6 +776,15 @@ riscv_elf_link_hash_table_free (bfd *obfd)
   if (ret->loc_hash_memory)
     objalloc_free ((struct objalloc *) ret->loc_hash_memory);
 
+  if (ret->table_jump_htab)
+    {
+      #if ZCMT_PRINT_TABLE_JUMP_ENTRIES
+	print_tablejump_entries(ret->table_jump_htab);
+      #endif
+      riscv_free_table_jump_htab (ret->table_jump_htab);
+      free (ret->table_jump_htab);
+    }
+
   _bfd_elf_link_hash_table_free (obfd);
 }
 
@@ -699,6 +839,16 @@ riscv_elf_link_hash_table_create (bfd *abfd)
       return NULL;
     }
 
+  ret->table_jump_htab = (riscv_table_jump_htab_t *) bfd_zmalloc (
+	  sizeof (riscv_table_jump_htab_t));
+
+  if (ret->table_jump_htab == NULL
+	|| !riscv_init_table_jump_htab(ret->table_jump_htab))
+    {
+      riscv_elf_link_hash_table_free (abfd);
+      return NULL;
+    }
+
   ret->max_alignment = (bfd_vma) -1;
   ret->max_alignment_for_gp = (bfd_vma) -1;
 
@@ -920,6 +1070,75 @@ bad_static_reloc (bfd *abfd, unsigned r_type, struct elf_link_hash_entry *h)
   return false;
 }
 
+static bool
+riscv_use_table_jump (struct bfd_link_info *info)
+{
+  unsigned xlen = ARCH_SIZE;
+  riscv_subset_list_t subsets;
+  bool ret;
+
+  /* if --no-relax */
+  if (info->disable_target_specific_optimizations > 0)
+    return false;
+
+  if (!bfd_link_executable (info))
+    return false;
+
+  bfd *obfd = info->output_bfd;
+  obj_attribute *out_attr = elf_known_obj_attributes_proc (obfd);
+
+  subsets.head = NULL;
+  subsets.tail = NULL;
+  subsets.arch_str = NULL;
+
+  riscv_parse_subset_t riscv_rps_ld_out =
+	{&subsets, _bfd_error_handler, &xlen, NULL, false};
+
+  if (!riscv_parse_subset (&riscv_rps_ld_out, out_attr[Tag_RISCV_arch].s))
+    return false;
+
+  ret = riscv_subset_supports (&riscv_rps_ld_out, "zcmt");
+  riscv_release_subset_list (&subsets);
+
+  return ret;
+}
+
+static bool
+bfd_elf_riscv_make_tablejump_section (bfd *abfd, struct bfd_link_info *info)
+{
+  asection *sec;
+  struct riscv_elf_link_hash_table *htab;
+  const struct elf_backend_data *bed;
+
+  /* Skip if no Zcmt or --no-relax.  */
+  if (!riscv_use_table_jump (info))
+    return true;
+
+  bed = get_elf_backend_data (abfd);
+  htab = riscv_elf_hash_table (info);
+  sec = bfd_get_linker_section (abfd, TABLE_JUMP_SEC_NAME);
+
+  if (sec != NULL)
+    return true;
+
+  if (htab->table_jump_htab->tablejump_sec == NULL)
+    {
+      sec = bfd_make_section_anyway_with_flags (abfd, TABLE_JUMP_SEC_NAME,
+		  (SEC_ALLOC | SEC_LOAD | SEC_READONLY | SEC_HAS_CONTENTS
+		  | SEC_IN_MEMORY | SEC_KEEP));
+
+      if (sec == NULL
+	  || !bfd_set_section_alignment (sec, bed->s->log_file_align)
+	  || !bfd_set_section_size (sec, 256 * RISCV_ELF_WORD_BYTES))
+	return false;
+
+      htab->table_jump_htab->tablejump_sec = sec;
+      htab->table_jump_htab->tablejump_sec_owner = abfd;
+    }
+
+  return true;
+}
+
 /* Look through the relocs for a section during the first phase, and
    allocate space in the global offset table or procedure linkage
    table.  */
@@ -1266,6 +1485,9 @@ riscv_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	}
     }
 
+  if (!bfd_elf_riscv_make_tablejump_section (abfd, info))
+    return false;
+
   return true;
 }
 
@@ -1977,6 +2199,10 @@ perform_relocation (const reloc_howto_type *howto,
 		    bfd *input_bfd,
 		    bfd_byte *contents)
 {
+  /* R_RISCV_TABLE_JUMP expected for zcmt.  */
+  if (ELFNN_R_TYPE (rel->r_info) == R_RISCV_TABLE_JUMP)
+     return bfd_reloc_ok;
+
   if (howto->pc_relative)
     value -= sec_addr (input_section) + rel->r_offset;
 
@@ -3353,6 +3579,9 @@ riscv_elf_relocate_section (bfd *output_bfd,
 	  unresolved_reloc = false;
 	  break;
 
+	case  R_RISCV_TABLE_JUMP:
+	  break;
+
 	default:
 	  r = bfd_reloc_notsupported;
 	}
@@ -4869,6 +5098,94 @@ typedef bool (*relax_func_t) (bfd *, asection *, asection *,
 			      riscv_pcgp_relocs *,
 			      bool undefined_weak);
 
+static htab_t
+riscv_get_table_jump_htab (struct bfd_link_info *info, unsigned int link_reg)
+{
+  struct riscv_elf_link_hash_table *htab = riscv_elf_hash_table (info);
+  riscv_table_jump_htab_t *tbj_htab = htab->table_jump_htab;
+
+  BFD_ASSERT (tbj_htab != NULL);
+
+  if (link_reg == 0)
+    return tbj_htab->tbljt_htab;
+
+  if (link_reg == X_RA
+	  && tbj_htab->tbljalt_index >= CMJALT_INDEX)
+    return tbj_htab->tbljalt_htab;
+
+  return NULL;
+}
+
+static const char*
+riscv_get_symbol_name (bfd *abfd, Elf_Internal_Rela *rel)
+{
+  unsigned long r_symndx = ELFNN_R_SYM (rel->r_info);
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+  const char *name;
+
+  if (!symtab_hdr->contents)
+    return NULL;
+
+  if (ELFNN_R_SYM (rel->r_info) < symtab_hdr->sh_info)
+    {
+      /* A local symbol.  */
+      Elf_Internal_Sym *sym = ((Elf_Internal_Sym *) symtab_hdr->contents
+		+ r_symndx);
+      name = bfd_elf_sym_name (abfd, symtab_hdr, sym, NULL);
+    }
+  else
+    {
+      struct elf_link_hash_entry *h;
+      unsigned indx = r_symndx - symtab_hdr->sh_info;
+      h = elf_sym_hashes (abfd)[indx];
+      while (h->root.type == bfd_link_hash_indirect
+	  || h->root.type == bfd_link_hash_warning)
+	h = (struct elf_link_hash_entry *) h->root.u.i.link;
+      if (h != NULL && h->type != STT_GNU_IFUNC)
+	name = h->root.root.string;
+      else
+	/* We do not handle STT_GNU_IFUNC currently.  */
+	return NULL;
+    }
+
+  return name;
+}
+
+static bool
+_bfd_riscv_table_jump_mark (bfd *abfd ATTRIBUTE_UNUSED, asection *sec,
+		       asection *sym_sec ATTRIBUTE_UNUSED,
+		       struct bfd_link_info *link_info,
+		       Elf_Internal_Rela *rel,
+		       bfd_vma symval,
+		       bfd_vma max_alignment ATTRIBUTE_UNUSED,
+		       bfd_vma reserve_size ATTRIBUTE_UNUSED,
+		       bool *again ATTRIBUTE_UNUSED,
+		       riscv_pcgp_relocs *pcgp_relocs ATTRIBUTE_UNUSED,
+		       bool undefined_weak ATTRIBUTE_UNUSED)
+{
+  bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
+  bfd_vma target = bfd_getl32 (contents + rel->r_offset);
+  if(ELFNN_R_TYPE (rel->r_info) != R_RISCV_JAL)
+    target = bfd_getl32 (contents + rel->r_offset + 4);
+  int rd = (target >> OP_SH_RD) & OP_MASK_RD;
+  htab_t tbljal_htab = riscv_get_table_jump_htab (link_info, rd);
+
+  /* Check if it uses a valid link register.  */
+  if (tbljal_htab == NULL)
+    return true;
+
+  riscv_table_jump_htab_entry search = {symval, 0, NULL, 0};
+  riscv_table_jump_htab_entry *entry = htab_find (tbljal_htab, &search);
+
+  /* entry->index == 0 when the entry is not used as a table jump entry.  */
+  if (entry != NULL && entry->index > 0)
+    {
+      target = MATCH_CM_JT | ENCODE_ZCMT_INDEX (entry->index-1);
+      bfd_putl32 (target, contents + rel->r_offset);
+    }
+  return true;
+}
+
 /* Relax AUIPC + JALR into JAL.  */
 
 static bool
@@ -4900,9 +5217,12 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
       foff += ((bfd_signed_vma) foff < 0 ? -max_alignment : max_alignment);
     }
 
-  /* See if this function call can be shortened.  */
-  if (!VALID_JTYPE_IMM (foff) && !(!bfd_link_pic (link_info) && near_zero))
-    return true;
+  /* See if this function call can be shortened or fall back to zcmt
+     opporunity.  */
+  if (!VALID_JTYPE_IMM (foff) && !(!bfd_link_pic (link_info) && near_zero )
+      && !riscv_use_table_jump (link_info)) {
+	return true;
+  }
 
   /* Shorten the function call.  */
   BFD_ASSERT (rel->r_offset + 8 <= sec->size);
@@ -4935,6 +5255,26 @@ _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
       auipc = MATCH_JALR | (rd << OP_SH_RD);
     }
 
+  /* Table jump profiling stage. It will be moved out of the relax_call function.  */
+  if (link_info->relax_pass == 0)
+    {
+      /* Early stop to prevent _bfd_riscv_relax_call to delete bytes in pass 0.  */
+      if (link_info->relax_trip != 0)
+	     return true;
+
+      htab_t tbljal_htab = riscv_get_table_jump_htab (link_info, rd);
+      const char *name = riscv_get_symbol_name (abfd, rel);
+      unsigned int benefit = len - 2 ;
+
+      if (tbljal_htab == NULL
+	      || name == NULL
+	      || benefit == 0)
+	    return true;
+
+	 *again = true;
+      return riscv_update_table_jump_entry (tbljal_htab, symval, benefit, name);
+    }
+
   /* Replace the R_RISCV_CALL reloc.  */
   rel->r_info = ELFNN_R_INFO (ELFNN_R_SYM (rel->r_info), r_type);
   /* Replace the AUIPC.  */
@@ -5355,6 +5695,159 @@ _bfd_riscv_relax_pc (bfd *abfd ATTRIBUTE_UNUSED,
   return true;
 }
 
+static bool
+_bfd_riscv_record_jal (bfd *abfd,
+			 asection *sec ATTRIBUTE_UNUSED,
+			 asection *sym_sec ATTRIBUTE_UNUSED,
+			 struct bfd_link_info *link_info,
+			 Elf_Internal_Rela *rel,
+			 bfd_vma symval,
+			 bfd_vma max_alignment ATTRIBUTE_UNUSED,
+			 bfd_vma reserve_size ATTRIBUTE_UNUSED,
+			 bool *again ATTRIBUTE_UNUSED,
+			 riscv_pcgp_relocs *pcgp_relocs ATTRIBUTE_UNUSED,
+			 bool undefined_weak ATTRIBUTE_UNUSED)
+{
+  bfd_byte *contents = elf_section_data (sec)->this_hdr.contents;
+  bfd_vma jal = bfd_getl32 (contents + rel->r_offset);
+  unsigned int rd = (jal >> OP_SH_RD) & OP_MASK_RD;
+  htab_t tbljal_htab = riscv_get_table_jump_htab (link_info, rd);
+  const char *name = riscv_get_symbol_name (abfd, rel);
+
+  if (link_info->relax_pass == 1
+      && ((jal ^ MATCH_CM_JT) & MASK_CM_JALT) == 0)
+    {
+      rel->r_info = ELFNN_R_INFO (ELFNN_R_SYM (rel->r_info), R_RISCV_TABLE_JUMP);
+      *again = true;
+      return riscv_relax_delete_bytes (abfd, sec,
+		  rel->r_offset + 2, 6, link_info, pcgp_relocs, NULL);
+    }
+
+  if (tbljal_htab == NULL
+      || name == NULL
+      || (link_info->relax_pass == 0 && link_info->relax_trip > 0)
+      || link_info->relax_pass > 0)
+    return true;
+
+ *again = true;
+  return riscv_update_table_jump_entry (tbljal_htab, symval, 2, name);
+}
+
+typedef struct
+{
+  riscv_table_jump_htab_t *htab;
+  unsigned int start;
+  unsigned int end;
+} riscv_table_jump_args;
+
+static int
+riscv_ranking_table_jump (void **entry_ptr, void *_arg)
+{
+  const riscv_table_jump_htab_entry *entry;
+  riscv_table_jump_args *arg;
+  riscv_table_jump_htab_t *htab;
+  int *savings;
+  const char **names;
+  uintNN_t *tbj_indexes;
+
+  entry = (const riscv_table_jump_htab_entry *) *entry_ptr;
+  arg = (riscv_table_jump_args*) _arg;
+  htab = (riscv_table_jump_htab_t *) arg->htab;
+
+  savings = htab->savings;
+  names = htab->names;
+  tbj_indexes = htab->tbj_indexes;
+
+  /* search insert position and rank.  */
+  unsigned int left = arg->start;
+  unsigned int right = arg->end + 1;
+
+  while (left < right)
+    {
+      unsigned int mid = (left + right) / 2;
+      /* `entry->benefit != 0` helps prioritize index with a zero benefit.  */
+      if (savings[mid] == entry->benefit && entry->benefit != 0)
+	{
+	  left = mid;
+	  break;
+	}
+      else if (savings[mid] == 0
+	  || savings[mid] < entry->benefit)
+	right = mid;
+      else
+	left = mid + 1;
+    }
+
+  for (unsigned int idx = arg->end; idx > left; idx--)
+    {
+      tbj_indexes[idx] = tbj_indexes[idx-1];
+      savings[idx] = savings[idx-1];
+      names[idx] = names[idx-1];
+    }
+
+  if (left <= arg->end)
+    {
+      tbj_indexes[left] = (uintNN_t) entry->address;
+      savings[left] = entry->benefit;
+      names[left] = entry->name;
+    }
+  return true;
+}
+
+static bool
+riscv_record_table_jump_index (htab_t htab, riscv_table_jump_args *args, bool iscmjl)
+{
+  unsigned int idx;
+  riscv_table_jump_htab_t *tbj_htab = args->htab;
+  riscv_table_jump_htab_entry search;
+  riscv_table_jump_htab_entry *entry = NULL;
+
+  for (idx = args->start; idx <= args->end && tbj_htab->tbj_indexes[idx]; idx++)
+    {
+      search = (riscv_table_jump_htab_entry)
+	  {tbj_htab->tbj_indexes[idx], 0, NULL, 0};
+      entry = htab_find (htab, &search);
+
+      BFD_ASSERT (entry != NULL);
+      entry->index = idx + 1;
+      tbj_htab->total_saving += tbj_htab->savings[idx];
+    }
+
+  /* True if there is at least one entry in table jump section.  */
+  if (entry && entry->index)
+    tbj_htab->end_idx = tbj_htab->tbljt_index = entry->index;
+  if (iscmjl)
+    tbj_htab->tbljt_index = entry->index;
+
+  return true;
+}
+
+static bool
+riscv_table_jump_profiling (riscv_table_jump_htab_t *table_jump_htab,
+    riscv_table_jump_args *args)
+{
+  args->start = 0, args->end = 31;
+  /* Do a ranking.  */
+  htab_traverse (table_jump_htab->tbljt_htab,
+	riscv_ranking_table_jump,
+	args);
+  /* cmjl enteries and update tbljt_index.  */
+  riscv_record_table_jump_index (
+	  table_jump_htab->tbljt_htab,
+	  args,
+	  true);
+
+  args->start = 32, args->end = 255;
+  htab_traverse (table_jump_htab->tbljalt_htab,
+	riscv_ranking_table_jump,
+	args);
+  riscv_record_table_jump_index (
+	  table_jump_htab->tbljalt_htab,
+	  args,
+	  false);
+  return true;
+}
+
 /* Called by after_allocation to set the information of data segment
    before relaxing.  */
 
@@ -5366,6 +5859,41 @@ bfd_elfNN_riscv_set_data_segment_info (struct bfd_link_info *info,
   htab->data_segment_phase = data_segment_phase;
 }
 
+
+static void  cleanup_pad_CMJALT(riscv_table_jump_htab_t *htab)
+{
+  unsigned int end_idx = htab->end_idx;
+  unsigned int jt_index = htab->tbljt_index;
+  unsigned int alt_index = htab->tbljalt_index;
+
+  /* Nothing to clean up if no entries,set up cmjalt index.  */
+  if (htab->tbljalt_index == 0)
+    {
+      htab->tbljalt_index = jt_index;
+      return ;
+    }
+
+  /* Check if there's enough "room" to do cleanup.  */
+  unsigned int entries_after_cmj = end_idx - (CMJALT_INDEX - 1);
+  unsigned int alt_space = CMJALT_INDEX - jt_index;
+
+  if (alt_space > entries_after_cmj)
+    {
+      /* Clean up from CMJALT_INDEX up to current alt_index.  */
+      for (unsigned int i = CMJALT_INDEX; i < alt_index; i++)
+	{
+	  htab->total_saving -= htab->savings[i];
+	  htab->tbj_indexes[i] = 0;
+	  htab->savings[i] = 0;
+	  htab->names[i] = NULL;
+	}
+
+	/* Reset alt index to jump-table index.  */
+	htab->tbljalt_index = jt_index;
+    }
+    return ;
+}
+
 /* Relax a section.
 
    Pass 0: Shortens code sequences for LUI/CALL/TPREL/PCREL relocs and
@@ -5386,6 +5914,9 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
   bfd_vma max_alignment, reserve_size = 0;
   riscv_pcgp_relocs pcgp_relocs;
   static asection *first_section = NULL;
+  riscv_table_jump_htab_t *table_jump_htab = htab->table_jump_htab;
+  struct elf_link_hash_entry *jvt_sym;
+  bool is_zcmt = riscv_use_table_jump (info);
 
   *again = false;
 
@@ -5426,6 +5957,80 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
       htab->max_alignment = max_alignment;
     }
 
+  /* relax_trip 0:
+     Record symbol address and expected size saving of each relocation
+     that can be replaced by table jump instructions.
+
+     relax_trip 1:
+     Rank the best 32 relocations to replace for cm.jt and the best 224
+     relocations for cm.jalt in terms of the total size saved.
+
+     relax_trip 2:
+     Check if table jump can reduce the size, and delete the whole table
+     jump section if the size will not be reduced.
+
+     relax_trip 3: Trim unused slots in the table jump section.
+	 If table jump can save size, and then we replace all targeted
+     instructions/instruction pairs(e.g. auipc+jalr) to table jump
+     instructions with the index encoded.  */
+
+  if (info->relax_pass == 0
+      && is_zcmt)
+    {
+	  /* Avoid size savings of relocations to be recoreded multiple times.  */
+      if (info->relax_trip == 0 && *(htab->data_segment_phase) != 0 )
+	    return true;
+      /* Rank the entries, and calculate the expected total saving.  */
+      else if (info->relax_trip == 1)
+	{
+	  /*come back for trip 2.  */
+	  *again = true;
+	  /* Profiling stage finished.  */
+	  if (table_jump_htab->end_idx != 0)
+	    return true;
+
+	  riscv_table_jump_args args = {table_jump_htab, 0, 0};
+	  /* Estimate size savings if table jump is used.  */
+	  riscv_table_jump_profiling (table_jump_htab, &args);
+	  return true;
+	}
+      /* Skip generating table jump instructions if they do not help reduce code size.
+	 Or install the internal zcmt symbol  definition.  */
+      else if (info->relax_trip == 2)
+	{
+	  /* check the min size to be saved  */
+	  if (table_jump_htab->total_saving <= info->min_zcmt_size)
+    return true;
+
+	  /* Table jump entry symbol definition.  */
+	  jvt_sym = elf_link_hash_lookup (elf_hash_table (info),
+					  RISCV_TABLE_JUMP_BASE_SYMBOL,
+					  true, false, true);
+	  jvt_sym->root.u.def.section = bfd_abs_section_ptr;
+	  /* come back for trip 3.  */
+	  *again = true;
+	}
+      /* cleanup the tables entries and update the section content.  */
+      else if (info->relax_trip == 3)
+	{
+	  /* Table jump entry section is trimmed.  */
+	  if (table_jump_htab->end_idx < 0)
+	    return true;
+
+	  cleanup_pad_CMJALT(table_jump_htab);
+	  /* buffer to hold the table enteries.  */
+	  int cmt_jbl_section_content_size = (table_jump_htab->tbljalt_index
+					      * RISCV_ELF_WORD_BYTES);
+	  bfd_byte *buf = (bfd_byte*) bfd_malloc(cmt_jbl_section_content_size);
+	  /* copy the table entries. */
+	  for (short int index = 0; index < table_jump_htab->tbljalt_index ; index++)
+	    bfd_putl32(table_jump_htab->tbj_indexes[index],buf+(index *4));
+
+	  elf_section_data(table_jump_htab->tablejump_sec)->this_hdr.contents=buf;
+	  /* we are done ,tbljalt_index used to patch the relocation.  */
+	  table_jump_htab->end_idx = -1;
+	}
+  }
   /* Examine and consider relaxing each reloc.  */
   for (i = 0; i < sec->reloc_count; i++)
     {
@@ -5439,11 +6044,16 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 
       relax_func = NULL;
       riscv_relax_delete_bytes = NULL;
+
       if (info->relax_pass == 0)
 	{
+	  if (!is_zcmt)
+	    return true;
 	  if (type == R_RISCV_CALL
 	      || type == R_RISCV_CALL_PLT)
 	    relax_func = _bfd_riscv_relax_call;
+	  else if (type == R_RISCV_JAL)
+		relax_func = _bfd_riscv_record_jal;
 	  else if (type == R_RISCV_HI20
 		   || type == R_RISCV_LO12_I
 		   || type == R_RISCV_LO12_S)
@@ -5459,25 +6069,41 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 		       || type == R_RISCV_PCREL_LO12_S))
 	    relax_func = _bfd_riscv_relax_pc;
 	  else
-	    continue;
+	   continue;
+
 	  riscv_relax_delete_bytes = _riscv_relax_delete_piecewise;
 
 	  /* Only relax this reloc if it is paired with R_RISCV_RELAX.  */
-	  if (i == sec->reloc_count - 1
+	  if ((i == sec->reloc_count - 1
 	      || ELFNN_R_TYPE ((rel + 1)->r_info) != R_RISCV_RELAX
 	      || rel->r_offset != (rel + 1)->r_offset)
+	      && !is_zcmt)
 	    continue;
 
+	  /*  Replace JAL/JALR with cmjlt/ cmjalt.  */
+	  if (info->relax_trip == 3)
+	    relax_func = _bfd_riscv_table_jump_mark;
+
 	  /* Skip over the R_RISCV_RELAX.  */
 	  i++;
-	}
-      else if (info->relax_pass == 1 && type == R_RISCV_ALIGN)
+}
+      else if (info->relax_pass == 1
+	       && type == R_RISCV_ALIGN)
 	{
 	  relax_func = _bfd_riscv_relax_align;
 	  riscv_relax_delete_bytes = _riscv_relax_delete_immediate;
 	}
+      else if (info->relax_pass == 1
+	       && (type == R_RISCV_JAL
+	       || type == R_RISCV_CALL
+	       || type == R_RISCV_CALL_PLT))
+	{
+	  relax_func = _bfd_riscv_record_jal;
+	  riscv_relax_delete_bytes = _riscv_relax_delete_immediate;
+	 }
+
       else
-	continue;
+       continue;
 
       data->relocs = relocs;
 
@@ -5648,6 +6274,12 @@ _bfd_riscv_relax_section (bfd *abfd, asection *sec,
     free (relocs);
   riscv_free_pcgp_relocs (&pcgp_relocs, abfd, sec);
 
+  /* If no size is saved, exclude the table jump section.  */
+  if ((info->relax_pass == 1)
+      && (table_jump_htab->total_saving == 0)
+      && is_zcmt)
+    table_jump_htab->tablejump_sec->flags |= SEC_EXCLUDE;
+
   return ret;
 }
 
diff --git a/bfd/elfxx-riscv.c b/bfd/elfxx-riscv.c
index 085d77923a0..989689969ed 100644
--- a/bfd/elfxx-riscv.c
+++ b/bfd/elfxx-riscv.c
@@ -958,6 +958,23 @@ static const reloc_howto_type howto_table_internal[] =
 	 0,				/* src_mask */
 	 ENCODE_STYPE_IMM (-1U),	/* dst_mask */
 	 false),			/* pcrel_offset */
+
+  /* Table jump entries.  */
+  HOWTO (R_RISCV_TABLE_JUMP,		/* type */
+	 0,				/* rightshift */
+	 1,				/* size */
+	 16,				/* bitsize */
+	 true,				/* pc_relative */
+	 0,				/* bitpos */
+	 complain_overflow_dont,	/* complain_on_overflow */
+	 bfd_elf_generic_reloc,		/* special_function */
+	 "R_RISCV_TABLE_JUMP",	/* name */
+	 false,				/* partial_inplace */
+	 0,				/* src_mask */
+	 ENCODE_ZCMT_INDEX (-1U),	/* dst_mask */
+	 true),				/* pcrel_offset */
+
+   EMPTY_HOWTO (226),
 };
 
 /* A mapping from BFD reloc types to RISC-V ELF reloc types.  */
diff --git a/gas/config/tc-riscv.c b/gas/config/tc-riscv.c
index df60c206b82..25404ddd773 100644
--- a/gas/config/tc-riscv.c
+++ b/gas/config/tc-riscv.c
@@ -4854,6 +4854,12 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg)
       break;
 
     case BFD_RELOC_RISCV_JMP:
+      /* j and jal can be relaxed into cm.jalt or cm.jt if zcmt is used.  */
+      if (riscv_subset_supports (&riscv_rps_as, "zcmt"))
+	{
+	  relaxable = true;
+	  fixP->fx_tcbit = riscv_opts.relax;
+	}
       if (fixP->fx_addsy)
 	{
 	  /* Fill in a tentative value to improve objdump readability.  */
@@ -5433,7 +5439,22 @@ md_convert_frag_branch (fragS *fragp)
 	    /* Invert the branch condition.  Branch over the jump.  */
 	    insn = bfd_getl16 (buf);
 	    insn ^= MATCH_C_BEQZ ^ MATCH_C_BNEZ;
-	    insn |= ENCODE_CBTYPE_IMM (6);
+      if (riscv_subset_supports (&riscv_rps_as, "zcmt"))
+	      {
+		symbolS *sym = symbol_new (FAKE_LABEL_NAME , now_seg, fragp,
+				     fragp->fr_fix + fragp->fr_var);
+		fixp = fix_new (fragp,
+				buf - (bfd_byte *)fragp->fr_literal,
+				2,
+				sym,
+				0,
+				false,
+				BFD_RELOC_RISCV_RVC_BRANCH);
+		fixp->fx_file = fragp->fr_file;
+		fixp->fx_line = fragp->fr_line;
+	      }
+	    else
+	      insn |= ENCODE_CBTYPE_IMM (6);
 	    bfd_putl16 (insn, buf);
 	    buf += 2;
 	    goto jump;
@@ -5460,7 +5481,23 @@ md_convert_frag_branch (fragS *fragp)
       /* Invert the branch condition.  Branch over the jump.  */
       insn = bfd_getl32 (buf);
       insn ^= MATCH_BEQ ^ MATCH_BNE;
-      insn |= ENCODE_BTYPE_IMM (8);
+      if (riscv_subset_supports (&riscv_rps_as, "zcmt"))
+	{
+	  symbolS *sym = (symbolS *) local_symbol_make (
+		    FAKE_LABEL_NAME, now_seg, fragp,
+		    fragp->fr_fix + fragp->fr_var);
+	  fixp = fix_new (fragp,
+			  buf - (bfd_byte *)fragp->fr_literal,
+			  4,
+			  sym,
+			  0,
+			  false,
+			  BFD_RELOC_12_PCREL);
+	  fixp->fx_file = fragp->fr_file;
+	  fixp->fx_line = fragp->fr_line;
+	}
+      else
+	insn |= ENCODE_BTYPE_IMM (8);
       bfd_putl32 (insn, buf);
       buf += 4;
 
diff --git a/include/bfdlink.h b/include/bfdlink.h
index 00fe0f8c7c8..b40d23b516d 100644
--- a/include/bfdlink.h
+++ b/include/bfdlink.h
@@ -769,6 +769,9 @@ struct bfd_link_info
   /* The maximum cache size.  Backend can use cache_size and and
      max_cache_size to decide if keep_memory should be honored.  */
   bfd_size_type max_cache_size;
+
+   /* Minimum size to be saved with ZCMT sections.  */
+  bfd_size_type min_zcmt_size;
 };
 
 /* Some forward-definitions used by some callbacks.  */
diff --git a/include/elf/riscv.h b/include/elf/riscv.h
index 2ea1ae82eb6..f2f5f8618e3 100644
--- a/include/elf/riscv.h
+++ b/include/elf/riscv.h
@@ -95,6 +95,8 @@ START_RELOC_NUMBERS (elf_riscv_reloc_type)
   RELOC_NUMBER (R_RISCV_TLSDESC_LOAD_LO12, 63)
   RELOC_NUMBER (R_RISCV_TLSDESC_ADD_LO12, 64)
   RELOC_NUMBER (R_RISCV_TLSDESC_CALL, 65)
+  /* Zcmt Specific Relocation.  */
+  RELOC_NUMBER (R_RISCV_TABLE_JUMP, 226)
 END_RELOC_NUMBERS (R_RISCV_max)
 
 /* Internal relocations used exclusively by the relaxation pass.  */
@@ -131,6 +133,14 @@ END_RELOC_NUMBERS (R_RISCV_max)
 /* The name of the global pointer symbol.  */
 #define RISCV_GP_SYMBOL "__global_pointer$"
 
+/* Zcmt table jump symbol.  */
+#define RISCV_TABLE_JUMP_BASE_SYMBOL "__jvt_base$"
+
+#define TABLE_JUMP_SEC_NAME ".jvt"
+
+/* CMJALT Start index.  */
+#define CMJALT_INDEX 32
+
 /* Processor specific dynamic array tags.  */
 #define DT_RISCV_VARIANT_CC (DT_LOPROC + 1)
 
diff --git a/ld/ldlex.h b/ld/ldlex.h
index 24cac1cdfc0..8158aa223a8 100644
--- a/ld/ldlex.h
+++ b/ld/ldlex.h
@@ -475,6 +475,7 @@ enum option_values
   /* Used by emultempl/elf-i386-glibc.em.  */
   OPTION_GNU_TLS_VERSION_TAG,
   OPTION_NO_GNU_TLS_VERSION_TAG,
+  OPTION_MIN_ZCMT_SIZE,
 };
 
 /* The initial parser states.  */
diff --git a/ld/ldmain.c b/ld/ldmain.c
index 157f205671e..45b0cf2649d 100644
--- a/ld/ldmain.c
+++ b/ld/ldmain.c
@@ -677,6 +677,8 @@ main (int argc, char **argv)
   link_info.allow_undefined_version = true;
   link_info.keep_memory = true;
   link_info.max_cache_size = (bfd_size_type) -1;
+  /* default min 4 bytes to be saved */
+  link_info.min_zcmt_size = 4;
   link_info.combreloc = true;
   link_info.strip_discarded = true;
   link_info.prohibit_multiple_definition_absolute = false;
diff --git a/ld/lexsup.c b/ld/lexsup.c
index 5cb77992733..1ee90e3a47c 100644
--- a/ld/lexsup.c
+++ b/ld/lexsup.c
@@ -447,6 +447,10 @@ static const struct ld_option ld_options[] =
     OPTION_MAX_CACHE_SIZE},
     '\0', NULL, N_("Set the maximum cache size to SIZE bytes"),
     TWO_DASHES },
+  { {"min-zcmt-size=SIZE", required_argument, NULL,
+	 OPTION_MIN_ZCMT_SIZE},
+	'\0', NULL, N_("Set the minimum size to be saved with zcmt and SIZE in bytes"),
+	TWO_DASHES },
   { {"relax", no_argument, NULL, OPTION_RELAX},
     '\0', NULL, N_("Reduce code size by using target specific optimizations"), TWO_DASHES },
   { {"no-relax", no_argument, NULL, OPTION_NO_RELAX},
@@ -1777,6 +1781,15 @@ parse_args (unsigned argc, char **argv)
 	  }
 	  break;
 
+	case OPTION_MIN_ZCMT_SIZE:
+	  {
+	    char *end;
+	    bfd_size_type zcmt_size = strtoul (optarg, &end, 0);
+	    if (*end != '\0')
+	      fatal (_("%P: invalid cache memory size: %s\n"), optarg);
+	    link_info.min_zcmt_size = zcmt_size;
+	  }
+	  break;
 	case OPTION_HASH_SIZE:
 	  {
 	    bfd_size_type new_size;
diff --git a/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp b/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
index b0d510ac8da..1f37a9b0fac 100644
--- a/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
+++ b/ld/testsuite/ld-riscv-elf/ld-riscv-elf.exp
@@ -127,6 +127,7 @@ if [istarget "riscv*-*-*"] {
     run_dump_test "c-lui"
     run_dump_test "c-lui-2"
     run_dump_test "disas-jalr"
+    run_dump_test "zcmt_relax"
     run_dump_test "pcrel-lo-addend"
     run_dump_test "pcrel-lo-addend-2a"
     run_dump_test "pcrel-lo-addend-2b"
diff --git a/ld/testsuite/ld-riscv-elf/zcmt_relax.d b/ld/testsuite/ld-riscv-elf/zcmt_relax.d
new file mode 100644
index 00000000000..4ea4e53fb49
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/zcmt_relax.d
@@ -0,0 +1,18 @@
+#source: zcmt_relax.s
+#as: -march=rv32imafdb_zicbop_zicond_zicsr_zihintpause_zimop_zaamo_zalrsc_zfa_zca_zcb_zcf_zcmop_zcmp_zcmt_zba_zbb_zbs
+#ld: -melf32lriscv -Tzcmt_relax.ld --min-zcmt-size=6 --defsym foo=0x150000 --defsym foo_1=0x150010
+#objdump: -d
+
+.*:     file format .*
+
+Disassembly of section \.text:
+
+[0-9a-f]+ <main>:
+^\s+[0-9A-Fa-f]+:\s+[0-9A-Fa-f]+\s+auipc\s+ra,0x[0-9A-Fa-f]+$
+^\s*[0-9A-Fa-f]+:\s+[0-9A-Fa-f]+\s+jalr\s+ra(?:\s+#\s*[0-9A-Fa-f]+(?:\s+<[^>]+>)?)?$
+^\s*[0-9A-Fa-f]+:\s+[0-9A-Fa-f]+\s+cm\.jt\s+[0-9A-Fa-f]+$
+^\s+[0-9A-Fa-f]+:\s+[0-9A-Fa-f]+\s+auipc\s+ra,0x[0-9A-Fa-f]+$
+^\s*[0-9A-Fa-f]+:\s+[0-9A-Fa-f]+\s+jalr\s+[-+]?[0-9A-Fa-f]+\(ra\)\s*#\s*[0-9A-Fa-f]+\s+<[^>]+>$
+^\s*[0-9A-Fa-f]+:\s+[0-9A-Fa-f]+\s+cm\.jt\s+[0-9A-Fa-f]+$
+^\s*[0-9A-Fa-f]+:\s+[0-9A-Fa-f]+\s+cm\.jt\s+[0-9A-Fa-f]+$
+#pass
diff --git a/ld/testsuite/ld-riscv-elf/zcmt_relax.ld b/ld/testsuite/ld-riscv-elf/zcmt_relax.ld
new file mode 100644
index 00000000000..7ec7c98a45b
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/zcmt_relax.ld
@@ -0,0 +1,38 @@
+/* linker.ld - minimal RISC-V linker script with __jvt_base$ */
+
+ENTRY(main)   /* program entry point */
+
+SECTIONS
+{
+    /* Code section */
+    .text : ALIGN(4)
+    {
+        *(.text*)
+    }
+
+    /* Jump Vector Table (JVT) section */
+    .jvt : ALIGN(8)
+    {
+        __jvt_base$ = .;     /* define the base symbol for the JVT */
+        KEEP(*(.jvt*))       /* keep all JVT entries */
+    }
+
+    /* Read-only data */
+    .rodata : ALIGN(4)
+    {
+        *(.rodata*)
+    }
+
+    /* Data section */
+    .data : ALIGN(4)
+    {
+        *(.data*)
+    }
+
+    /* BSS section */
+    .bss : ALIGN(4)
+    {
+        *(.bss*)
+        *(COMMON)
+    }
+}
diff --git a/ld/testsuite/ld-riscv-elf/zcmt_relax.s b/ld/testsuite/ld-riscv-elf/zcmt_relax.s
new file mode 100644
index 00000000000..fec4020cd2c
--- /dev/null
+++ b/ld/testsuite/ld-riscv-elf/zcmt_relax.s
@@ -0,0 +1,9 @@
+.text
+.global main
+.p2align 3
+main:
+  call foo
+  tail foo_1
+  call foo
+  tail foo_1
+  tail foo_1
diff --git a/opcodes/riscv-dis.c b/opcodes/riscv-dis.c
index fa2d44af28f..a2665714680 100644
--- a/opcodes/riscv-dis.c
+++ b/opcodes/riscv-dis.c
@@ -44,6 +44,8 @@ struct riscv_private_data
 {
   bfd_vma gp;
   bfd_vma print_addr;
+  bfd_vma jvt_base;
+  bfd_vma jvt_end;
   bfd_vma hi_addr[OP_MASK_RD + 1];
   bool to_print_addr;
   bool has_gp;
@@ -963,6 +965,44 @@ riscv_disassemble_insn (bfd_vma memaddr,
       init = true;
     }
 
+  if (info->private_data == NULL)
+    {
+      bfd_vma sym_val;
+
+      pd = info->private_data = xcalloc (1, sizeof (struct riscv_private_data));
+      pd->gp = -1;
+      pd->print_addr = -1;
+      pd->jvt_base = -1;
+      pd->jvt_end = -1;
+
+      for (i = 0; i < (int)ARRAY_SIZE (pd->hi_addr); i++)
+	pd->hi_addr[i] = -1;
+
+      for (i = 0; i < info->symtab_size; i++)
+	{
+	  if (strcmp (bfd_asymbol_name (info->symtab[i]), RISCV_GP_SYMBOL) == 0)
+	    pd->gp = bfd_asymbol_value (info->symtab[i]);
+	  /* Read the address of table jump entries.  */
+	  else if (strcmp (bfd_asymbol_name (info->symtab[i]),
+				  RISCV_TABLE_JUMP_BASE_SYMBOL) == 0)
+	    pd->jvt_base = bfd_asymbol_value (info->symtab[i]);
+	}
+
+      /* Calculate the closest symbol from jvt base to determine the size of table jump
+	 entry section.  */
+      if (pd->jvt_base != 0)
+	{
+	  for (i = 0; i < info->symtab_size; i++)
+	    {
+	      sym_val = bfd_asymbol_value (info->symtab[i]);
+	      if (sym_val > pd->jvt_base && sym_val < pd->jvt_end)
+		pd->jvt_end = sym_val;
+	    }
+	}
+    }
+  else
+    pd = info->private_data;
+
   insnlen = riscv_insn_length (word);
 
   /* RISC-V instructions are always little-endian.  */
-- 
2.43.0



More information about the Binutils mailing list