[RFC,2/5] RISC-V: Add Zcmt table-jump relaxation

Message ID 20260627104555.331493-3-jiawei@iscas.ac.cn
State New
Headers
Series RISC-V: Add Zcmt table-jump relaxation support |

Commit Message

Jiawei June 27, 2026, 10:45 a.m. UTC
  Collect profitable post-relax call and jump sites, rank JT and JALT candidates
deterministically, emit a JVT only when the net benefit is positive, and
rewrite selected sites to table-jump relocations for non-position-independent
executable links.

bfd/ChangeLog:

	* elfnn-riscv.c (struct riscv_elf_link_hash_table): Add JVT fields.
	(riscv_set_jvt_addr): Add Zcmt support.
	(riscv_is_local_symbol): Ditto.
	(riscv_is_special_symbol_name): Ditto.
	(riscv_jvt_global_hash_entry): Ditto.
	(riscv_jvt_init_key): Ditto.
	(riscv_jvt_key_hash): Ditto.
	(riscv_jvt_htab_hash): Ditto.
	(riscv_jvt_key_eq): Ditto.
	(riscv_jvt_htab_entry_eq): Ditto.
	(riscv_init_jvt_htab): Ditto.
	(riscv_free_jvt_htab): Ditto.
	(riscv_ensure_jvt_htab): Ditto.
	(riscv_update_jvt_entry): Ditto.
	(riscv_elf_link_hash_table_free): Ditto.
	(riscv_elf_link_hash_table_create): Ditto.
	(riscv_use_jvt): Ditto.
	(riscv_elf_create_jvt_section): Ditto.
	(riscv_reject_local_jvt_base_symbol): Ditto.
	(riscv_define_jvt_base_symbol): Ditto.
	(riscv_elf_check_relocs): Ditto.
	(perform_relocation): Ditto.
	(riscv_elf_relocate_section): Ditto.
	(riscv_get_jvt_htab): Ditto.
	(_bfd_riscv_jvt_record): Ditto.
	(riscv_compare_jvt_key): Ditto.
	(riscv_jvt_entry_better): Ditto.
	(riscv_ranking_jvt): Ditto.
	(riscv_record_jvt_index): Ditto.
	(riscv_jvt_profiling): Ditto.
	(_bfd_riscv_jvt_mark): Ditto.
	(_bfd_riscv_relax_call): Ditto.
	(_bfd_riscv_relax_jal): Ditto.
	(_bfd_riscv_get_max_alignment): Ditto.
	(bfd_elfNN_riscv_set_data_segment_info): Ditto.
	(_bfd_riscv_relax_section): Ditto.
	(riscv_final_link): Ditto.
	(bfd_elfNN_bfd_final_link): Ditto.
---
 bfd/elfnn-riscv.c | 1134 +++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 1098 insertions(+), 36 deletions(-)
  

Patch

diff --git a/bfd/elfnn-riscv.c b/bfd/elfnn-riscv.c
index 1c1c3ea36c5..b1b21e996e8 100644
--- a/bfd/elfnn-riscv.c
+++ b/bfd/elfnn-riscv.c
@@ -27,6 +27,7 @@ 
 #include "libbfd.h"
 #include "bfdlink.h"
 #include "genlink.h"
+#include "hashtab.h"
 #include "elf-bfd.h"
 #include "elfxx-riscv.h"
 #include "elf/riscv.h"
@@ -203,6 +204,65 @@  elfNN_riscv_mkobject (bfd *abfd)
 #include "elf/common.h"
 #include "elf/internal.h"
 
+/* Identity of one JVT entry.  */
+/* JVT candidates are keyed by relocation symbol identity plus addend and PLT
+   selection.  This v2 implementation deliberately does not canonicalize
+   aliases to final section+offset addresses after ordinary relaxation.  */
+typedef struct
+{
+  bool is_local;
+  bool use_plt;
+  bfd *input_bfd;
+  unsigned long symndx;
+  struct elf_link_hash_entry *h;
+  bfd_signed_vma addend;
+} riscv_jvt_key_t;
+
+/* Per-slot data for JVT entries.  */
+typedef struct
+{
+  bfd_size_type benefit;
+  bfd_size_type first_seen;
+  riscv_jvt_key_t key;
+  const char *name;
+} riscv_jvt_slot_t;
+
+/* Hash table for storing table jump candidate entries.  */
+typedef struct
+{
+  /* Hash tables for cm.jt and cm.jalt.  They record all possible
+     entries and will be ranked to put the most beneficial ones into JVT
+     section.  */
+  htab_t jt_htab;
+  htab_t jalt_htab;
+
+  /* Target-endian contents that will be put into JVT section.  */
+  bfd_byte *jvt_addrs;
+
+  asection *jvt_sec;
+  bfd *jvt_sec_owner;
+
+  /* END_IDX is used to calculate size of used slots in the table jump section,
+     and it is set to -1 if the profiling stage is complete.  */
+  int end_idx;
+  bfd_size_type total_benefits;
+  bfd_size_type next_ordinal;
+
+  /* Unified slot array for ranked JVT entries.  */
+  riscv_jvt_slot_t *slots;
+} riscv_jvt_htab_t;
+
+typedef struct
+{
+  unsigned int index;
+
+  riscv_jvt_key_t key;
+
+  bfd_size_type benefit;
+  bfd_size_type first_seen;
+  const char *name;
+} riscv_jvt_htab_entry;
+
 struct riscv_elf_link_hash_table
 {
   struct elf_link_hash_table elf;
@@ -241,6 +301,11 @@  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_jvt_htab_t *jvt_htab;
+
+  /* Cached result for riscv_use_jvt, scoped to one link.  */
+  int use_jvt;
 };
 
 /* Instruction access functions. */
@@ -266,6 +331,8 @@  static bool
 riscv_make_plt_header (bfd *, struct riscv_elf_link_hash_table *);
 static bool
 riscv_make_plt_entry (bfd *, asection *, bfd_vma, asection *, bfd_vma);
+static void
+riscv_set_jvt_addr (struct bfd_link_info *, unsigned int, bfd_vma);
 
 void
 riscv_elfNN_set_options (struct bfd_link_info *link_info,
@@ -307,6 +374,13 @@  riscv_is_insn_reloc (const reloc_howto_type *howto)
 	       ? (MINUS_ONE << howto->bitsize) : (bfd_vma)0)) != 0);
 }
 
+/* Return true if the given symbol index is a local symbol.  */
+static inline bool
+riscv_is_local_symbol (Elf_Internal_Shdr *symtab_hdr, unsigned long symndx)
+{
+  return symndx < symtab_hdr->sh_info;
+}
+
 /* PLT/GOT stuff.  */
 #define PLT_HEADER_INSNS 8
 #define PLT_ENTRY_INSNS 4
@@ -533,6 +607,14 @@  riscv_make_plt_zicfilp_unlabeled_entry (bfd *output_bfd, asection *got,
   return true;
 }
 
+static inline bool
+riscv_is_special_symbol_name (bfd *abfd, const char *name)
+{
+  return (!strcmp (name, "")
+	  || _bfd_elf_is_local_label_name (abfd, name)
+	  || riscv_elf_is_mapping_symbols (name));
+}
+
 /* Create an entry in an RISC-V ELF linker hash table.  */
 
 static struct bfd_hash_entry *
@@ -563,6 +645,211 @@  link_hash_newfunc (struct bfd_hash_entry *entry,
   return entry;
 }
 
+static struct elf_link_hash_entry *
+riscv_jvt_global_hash_entry (bfd *abfd, unsigned long symndx)
+{
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+  struct elf_link_hash_entry *h;
+
+  if (symndx < symtab_hdr->sh_info)
+    return NULL;
+
+  h = elf_sym_hashes (abfd)[symndx - symtab_hdr->sh_info];
+  if (h == NULL)
+    return NULL;
+
+  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;
+
+  return h;
+}
+
+static bool
+riscv_jvt_init_key (struct bfd_link_info *info, bfd *abfd,
+		    const Elf_Internal_Rela *rel, riscv_jvt_key_t *key,
+		    const char **name)
+{
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+  unsigned long symndx = ELFNN_R_SYM (rel->r_info);
+
+  memset (key, 0, sizeof (*key));
+  key->input_bfd = abfd;
+  key->symndx = symndx;
+  key->addend = rel->r_addend;
+
+  if (riscv_is_local_symbol (symtab_hdr, symndx))
+    {
+      Elf_Internal_Sym *sym;
+
+      if (symtab_hdr->contents == NULL)
+	return false;
+
+      sym = (Elf_Internal_Sym *) symtab_hdr->contents + symndx;
+      *name = bfd_elf_sym_name (abfd, symtab_hdr, sym, NULL);
+      key->is_local = true;
+    }
+  else
+    {
+      struct riscv_elf_link_hash_table *htab = riscv_elf_hash_table (info);
+      struct elf_link_hash_entry *h = riscv_jvt_global_hash_entry (abfd,
+								   symndx);
+
+      if (h == NULL || h->type == STT_GNU_IFUNC)
+	return false;
+
+      key->h = h;
+      key->use_plt = htab->elf.splt != NULL && h->plt.offset != MINUS_ONE;
+      *name = h->root.root.string;
+    }
+
+  return *name != NULL && !riscv_is_special_symbol_name (abfd, *name);
+}
+
+static hashval_t
+riscv_jvt_key_hash (const riscv_jvt_key_t *key)
+{
+  hashval_t h;
+  bfd_vma addend = (bfd_vma) key->addend;
+
+  if (key->is_local)
+    {
+      unsigned int id = key->input_bfd->id;
+      unsigned int symndx = key->symndx;
+
+      h = (id + symndx) * (id + symndx + 1) / 2 + id;
+    }
+  else
+    h = htab_hash_pointer (key->h);
+
+  h ^= (hashval_t) addend;
+  h ^= (hashval_t) (addend >> 32);
+  if (key->use_plt)
+    h ^= 0x9e3779b9;
+  return h;
+}
+
+static hashval_t
+riscv_jvt_htab_hash (const void *entry)
+{
+  const riscv_jvt_htab_entry *e = entry;
+  return riscv_jvt_key_hash (&e->key);
+}
+
+static bool
+riscv_jvt_key_eq (const riscv_jvt_key_t *key1, const riscv_jvt_key_t *key2)
+{
+  if (key1->is_local != key2->is_local
+      || key1->use_plt != key2->use_plt
+      || key1->addend != key2->addend)
+    return false;
+
+  if (key1->is_local)
+    return (key1->input_bfd->id == key2->input_bfd->id
+	    && key1->symndx == key2->symndx);
+
+  return key1->h == key2->h;
+}
+
+static int
+riscv_jvt_htab_entry_eq (const void *entry1, const void *entry2)
+{
+  const riscv_jvt_htab_entry *e1 = entry1, *e2 = entry2;
+  return riscv_jvt_key_eq (&e1->key, &e2->key);
+}
+
+static bool
+riscv_init_jvt_htab (riscv_jvt_htab_t *htab)
+{
+  htab->slots = bfd_zmalloc (sizeof (riscv_jvt_slot_t) * ZCMT_TOTAL_ENTRIES);
+  htab->jvt_addrs = bfd_zmalloc (RISCV_ELF_WORD_BYTES * ZCMT_TOTAL_ENTRIES);
+  if (htab->slots == NULL || htab->jvt_addrs == NULL)
+    return false;
+  htab->end_idx = 0;
+  htab->total_benefits = 0;
+  htab->next_ordinal = 0;
+
+  htab->jt_htab = htab_create (ZCMT_JT_NUM_ENTRIES, riscv_jvt_htab_hash,
+			       riscv_jvt_htab_entry_eq, free);
+  if (htab->jt_htab == NULL)
+    return false;
+
+  htab->jalt_htab = htab_create (ZCMT_JALT_NUM_ENTRIES, riscv_jvt_htab_hash,
+				 riscv_jvt_htab_entry_eq, free);
+  return htab->jalt_htab != NULL;
+}
+
+static void
+riscv_free_jvt_htab (riscv_jvt_htab_t *htab)
+{
+  free (htab->slots);
+  free (htab->jvt_addrs);
+  if (htab->jt_htab)
+    htab_delete (htab->jt_htab);
+  if (htab->jalt_htab)
+    htab_delete (htab->jalt_htab);
+}
+
+static bool
+riscv_ensure_jvt_htab (struct riscv_elf_link_hash_table *htab)
+{
+  if (htab->jvt_htab != NULL)
+    return true;
+
+  htab->jvt_htab = (riscv_jvt_htab_t *) bfd_zmalloc (sizeof (*htab->jvt_htab));
+  if (htab->jvt_htab == NULL)
+    return false;
+
+  if (!riscv_init_jvt_htab (htab->jvt_htab))
+    {
+      riscv_free_jvt_htab (htab->jvt_htab);
+      free (htab->jvt_htab);
+      htab->jvt_htab = NULL;
+      return false;
+    }
+
+  return true;
+}
+
+/* Update table jump hash entry.  */
+
+static bool
+riscv_update_jvt_entry (riscv_jvt_htab_t *jvt_htab, htab_t htab,
+			const riscv_jvt_key_t *key,
+			bfd_size_type benefit, const char *name)
+{
+  riscv_jvt_htab_entry search = {.index = 0,
+				 .key = *key,
+				 .benefit = 0,
+				 .first_seen = 0,
+				 .name = NULL};
+
+  riscv_jvt_htab_entry *entry = htab_find (htab, &search);
+
+  if (entry == NULL)
+    {
+      riscv_jvt_htab_entry **slot
+	= (riscv_jvt_htab_entry **) htab_find_slot (htab, &search, INSERT);
+
+      BFD_ASSERT (*slot == NULL);
+
+      *slot
+	= (riscv_jvt_htab_entry *) bfd_zmalloc (sizeof (riscv_jvt_htab_entry));
+
+      if (*slot == NULL)
+	return false;
+
+      (*slot)->key = *key;
+      (*slot)->benefit = benefit;
+      (*slot)->first_seen = jvt_htab->next_ordinal++;
+      (*slot)->name = name;
+    }
+  else
+    entry->benefit += benefit;
+
+  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
@@ -640,6 +927,12 @@  riscv_elf_link_hash_table_free (bfd *obfd)
   if (ret->loc_hash_memory)
     objalloc_free ((struct objalloc *) ret->loc_hash_memory);
 
+  if (ret->jvt_htab)
+    {
+      riscv_free_jvt_htab (ret->jvt_htab);
+      free (ret->jvt_htab);
+    }
+
   _bfd_elf_link_hash_table_free (obfd);
 }
 
@@ -696,6 +989,7 @@  riscv_elf_link_hash_table_create (bfd *abfd)
 
   ret->max_alignment = (bfd_vma) -1;
   ret->max_alignment_for_gp = (bfd_vma) -1;
+  ret->use_jvt = -1;
 
   setup_plt_values (abfd, ret, PLT_NORMAL);
 
@@ -915,6 +1209,193 @@  bad_static_reloc (bfd *abfd, unsigned r_type, struct elf_link_hash_entry *h)
   return false;
 }
 
+static bool
+riscv_use_jvt (struct bfd_link_info *info)
+{
+  unsigned xlen = ARCH_SIZE;
+  riscv_subset_list_t subsets;
+  struct riscv_elf_link_hash_table *htab;
+
+  htab = riscv_elf_hash_table (info);
+  if (htab->use_jvt >= 0)
+    return htab->use_jvt;
+
+  /* If relax is disabled by user, generating position-independent output,
+     or not linking an executable, table jump insns will not be generated.  */
+  if (info->disable_target_specific_optimizations >= 1
+      || bfd_link_pic (info)
+      || !bfd_link_executable (info)
+      || !htab->params->relax_zcmt)
+    {
+      htab->use_jvt = 0;
+      return htab->use_jvt;
+    }
+
+  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))
+    {
+      htab->use_jvt = 0;
+      riscv_release_subset_list (&subsets);
+      return htab->use_jvt;
+    }
+
+  /* Mirror the ISA and psABI constraints: Zcmt depends on Zca and Zicsr,
+     and its encodings overlap with Zcd compressed floating-point encodings.  */
+  htab->use_jvt = (riscv_subset_supports (&riscv_rps_ld_out, "zcmt")
+		   && riscv_subset_supports (&riscv_rps_ld_out, "zca")
+		   && riscv_subset_supports (&riscv_rps_ld_out, "zicsr")
+		   && !riscv_subset_supports (&riscv_rps_ld_out, "zcd"));
+  riscv_release_subset_list (&subsets);
+
+  return htab->use_jvt;
+}
+
+static bool
+riscv_elf_create_jvt_section (bfd *abfd, struct bfd_link_info *info)
+{
+  asection *sec;
+  struct riscv_elf_link_hash_table *htab;
+
+  /* Skip if no Zcmt.  */
+  if (!riscv_use_jvt (info))
+    return true;
+
+  htab = riscv_elf_hash_table (info);
+  if (!riscv_ensure_jvt_htab (htab))
+    return false;
+  sec = bfd_get_linker_section (abfd, TABLE_JUMP_SEC_NAME);
+
+  if (sec != NULL)
+    return true;
+
+  if (htab->jvt_htab->jvt_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 | SEC_CODE));
+
+      /* Create only a zero-sized placeholder before relaxation.  The Zcmt
+	 profiling pass sets the final alignment and size if a profitable
+	 table is chosen, so links that opt in but select no JVT do not reserve
+	 a full table or perturb layout.  */
+      if (sec == NULL || !bfd_set_section_alignment (sec, 0)
+	  || !bfd_set_section_size (sec, 0))
+	return false;
+
+      htab->jvt_htab->jvt_sec = sec;
+      htab->jvt_htab->jvt_sec_owner = abfd;
+    }
+
+  return true;
+}
+
+static bool
+riscv_reject_local_jvt_base_symbol (bfd *abfd)
+{
+  Elf_Internal_Shdr *symtab_hdr = &elf_symtab_hdr (abfd);
+  Elf_Internal_Sym *isymbuf;
+  unsigned long symndx;
+
+  if (symtab_hdr->sh_info == 0)
+    return true;
+
+  if (symtab_hdr->contents == NULL
+      && !(symtab_hdr->contents = (unsigned char *) bfd_elf_get_elf_syms
+	   (abfd, symtab_hdr, symtab_hdr->sh_info, 0, NULL, NULL, NULL)))
+    return false;
+
+  isymbuf = (Elf_Internal_Sym *) symtab_hdr->contents;
+  for (symndx = 0; symndx < symtab_hdr->sh_info; symndx++)
+    {
+      const char *name = bfd_elf_sym_name (abfd, symtab_hdr,
+					   isymbuf + symndx, NULL);
+
+      if (name != NULL && strcmp (name, RISCV_TABLE_JUMP_BASE_SYMBOL) == 0)
+	{
+	  _bfd_error_handler
+	    (_("%pB: %s is reserved for Zcmt table-jump relaxation"),
+	     abfd, RISCV_TABLE_JUMP_BASE_SYMBOL);
+	  bfd_set_error (bfd_error_bad_value);
+	  return false;
+	}
+    }
+
+  return true;
+}
+
+static bool
+riscv_define_jvt_base_symbol (bfd *abfd, struct bfd_link_info *info)
+{
+  struct riscv_elf_link_hash_table *htab = riscv_elf_hash_table (info);
+  riscv_jvt_htab_t *jvt_htab = htab->jvt_htab;
+  struct elf_link_hash_entry *h;
+  struct bfd_link_hash_entry *bh = NULL;
+  const struct elf_backend_data *bed = get_elf_backend_data (abfd);
+  bfd *ibfd;
+
+  if (jvt_htab == NULL || jvt_htab->jvt_sec == NULL
+      || jvt_htab->jvt_sec->size == 0)
+    return true;
+
+  if (jvt_htab->jvt_sec->contents == NULL)
+    {
+      jvt_htab->jvt_sec->contents
+	= (bfd_byte *) bfd_zalloc (jvt_htab->jvt_sec->owner,
+				   jvt_htab->jvt_sec->size);
+      jvt_htab->jvt_sec->alloced = 1;
+      if (jvt_htab->jvt_sec->contents == NULL)
+	return false;
+    }
+
+  h = elf_link_hash_lookup (elf_hash_table (info),
+			    RISCV_TABLE_JUMP_BASE_SYMBOL,
+			    false, false, true);
+  /* Code may reference __jvt_base$ to initialize the JVT CSR before
+     executing cm.jt/cm.jalt.  That is not a user definition; resolve
+     undefined references by defining the linker-owned symbol below, but
+     still reject any user-provided definition of the reserved name.  */
+  if (h != NULL
+      && h->root.type != bfd_link_hash_new
+      && h->root.type != bfd_link_hash_undefined
+      && h->root.type != bfd_link_hash_undefweak)
+    {
+      _bfd_error_handler
+	(_("%pB: %s is reserved for Zcmt table-jump relaxation"),
+	 abfd, RISCV_TABLE_JUMP_BASE_SYMBOL);
+      bfd_set_error (bfd_error_bad_value);
+      return false;
+    }
+
+  for (ibfd = info->input_bfds; ibfd != NULL; ibfd = ibfd->link.next)
+    if (!riscv_reject_local_jvt_base_symbol (ibfd))
+      return false;
+
+  if (!_bfd_generic_link_add_one_symbol (info, abfd,
+					 RISCV_TABLE_JUMP_BASE_SYMBOL,
+					 BSF_GLOBAL, jvt_htab->jvt_sec,
+					 0, NULL, true, bed->collect, &bh))
+    return false;
+
+  h = (struct elf_link_hash_entry *) bh;
+  h->root.linker_def = 1;
+  h->def_regular = 1;
+  h->type = STT_NOTYPE;
+  if (ELF_ST_VISIBILITY (h->other) != STV_INTERNAL)
+    h->other = ((h->other & ~ELF_ST_VISIBILITY (-1)) | STV_HIDDEN);
+  bed->elf_backend_hide_symbol (info, h, true);
+  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.  */
@@ -1261,6 +1742,9 @@  riscv_elf_check_relocs (bfd *abfd, struct bfd_link_info *info,
 	}
     }
 
+  if (!riscv_elf_create_jvt_section (abfd, info))
+    return false;
+
   return true;
 }
 
@@ -2037,6 +2521,9 @@  perform_relocation (const reloc_howto_type *howto,
       value = ENCODE_CBTYPE_IMM (value);
       break;
 
+    case R_RISCV_TABLE_JUMP:
+      return bfd_reloc_ok;
+
     case R_RISCV_RVC_JUMP:
       if (!VALID_CJTYPE_IMM (value))
 	return bfd_reloc_overflow;
@@ -2985,6 +3472,24 @@  riscv_elf_relocate_section (struct bfd_link_info *info,
 	  }
 	  break;
 
+	case R_RISCV_TABLE_JUMP:
+	  {
+	    bfd_vma insn = bfd_getl16 (contents + rel->r_offset);
+	    unsigned int tbl_index = EXTRACT_ZCMT_INDEX (insn);
+
+	    if (via_plt)
+	      {
+		relocation = sec_addr (htab->elf.splt) + h->plt.offset;
+		unresolved_reloc = false;
+	      }
+
+	    if (!riscv_ensure_jvt_htab (htab))
+	      return false;
+	    riscv_set_jvt_addr (info, tbl_index, relocation + rel->r_addend);
+	    rel->r_info = ELFNN_R_INFO (0, R_RISCV_NONE);
+	    continue;
+	  }
+
 	case R_RISCV_CALL:
 	case R_RISCV_CALL_PLT:
 	  /* Handle a call to an undefined weak function.  This won't be
@@ -4267,6 +4772,331 @@  _riscv_relax_delete_immediate (bfd *abfd,
 				    link_info, p, 0, sec->size);
 }
 
+/* Return the cm.jt hash table for x0, or the cm.jalt hash table for ra.  */
+
+static htab_t
+riscv_get_jvt_htab (struct bfd_link_info *info, unsigned int link_reg)
+{
+  struct riscv_elf_link_hash_table *htab = riscv_elf_hash_table (info);
+
+  if (htab == NULL || !riscv_ensure_jvt_htab (htab))
+    return NULL;
+
+  if (link_reg == 0)
+    return htab->jvt_htab->jt_htab;
+  if (link_reg == X_RA)
+    return htab->jvt_htab->jalt_htab;
+
+  return NULL;
+}
+
+/* Record JAL, CALL, or CALL_PLT as a jump table candidate.  */
+
+static bool
+_bfd_riscv_jvt_record (bfd *abfd, asection *sec ATTRIBUTE_UNUSED,
+		       asection *sym_sec ATTRIBUTE_UNUSED,
+		       struct bfd_link_info *link_info, Elf_Internal_Rela *rel,
+		       bfd_vma symval ATTRIBUTE_UNUSED,
+		       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;
+  int type = ELFNN_R_TYPE (rel->r_info);
+  bfd_vma insn;
+  bfd_size_type benefit;
+  unsigned int rd;
+  htab_t tbljal_htab;
+  const char *name;
+  riscv_jvt_key_t key;
+
+  if (type == R_RISCV_CALL || type == R_RISCV_CALL_PLT)
+    {
+      insn = bfd_getl32 (contents + rel->r_offset + 4);
+      benefit = (4 + 4) - 2; /* AUIPC + JALR -> cm.jt/cm.jalt.  */
+    }
+  else if (type == R_RISCV_JAL)
+    {
+      insn = bfd_getl32 (contents + rel->r_offset);
+      benefit = 4 - 2; /* JAL -> cm.jt/cm.jalt.  */
+    }
+  else
+    {
+      BFD_ASSERT (false);
+      return true;
+    }
+
+  rd = (insn >> OP_SH_RD) & OP_MASK_RD;
+  tbljal_htab = riscv_get_jvt_htab (link_info, rd);
+
+  if (tbljal_htab == NULL)
+    return true;
+
+  if (!riscv_jvt_init_key (link_info, abfd, rel, &key, &name))
+    return true;
+
+  return riscv_update_jvt_entry (riscv_elf_hash_table (link_info)->jvt_htab,
+				 tbljal_htab, &key, benefit, name);
+}
+
+static void
+riscv_set_jvt_addr (struct bfd_link_info *info, unsigned int index,
+		    bfd_vma addr)
+{
+  struct riscv_elf_link_hash_table *htab = riscv_elf_hash_table (info);
+  bfd_byte *loc;
+
+  BFD_ASSERT (index < ZCMT_TOTAL_ENTRIES);
+  BFD_ASSERT (htab->jvt_htab != NULL);
+
+  addr &= ~(bfd_vma) 1;
+  loc = htab->jvt_htab->jvt_addrs + index * RISCV_ELF_WORD_BYTES;
+  bfd_put_NN (info->output_bfd, addr, loc);
+}
+
+typedef struct
+{
+  riscv_jvt_htab_t *htab;
+  unsigned int start;
+  unsigned int end;
+} riscv_jvt_args;
+
+static int
+riscv_compare_jvt_key (const riscv_jvt_key_t *key1, const char *name1,
+		       const riscv_jvt_key_t *key2, const char *name2)
+{
+  int cmp;
+
+  cmp = strcmp (name1 == NULL ? "" : name1, name2 == NULL ? "" : name2);
+  if (cmp != 0)
+    return cmp;
+
+  cmp = strcmp (bfd_get_filename (key1->input_bfd),
+		bfd_get_filename (key2->input_bfd));
+  if (cmp != 0)
+    return cmp;
+
+  if (key1->symndx != key2->symndx)
+    return key1->symndx < key2->symndx ? -1 : 1;
+  if (key1->addend != key2->addend)
+    return key1->addend < key2->addend ? -1 : 1;
+  if (key1->use_plt != key2->use_plt)
+    return key1->use_plt ? 1 : -1;
+  return 0;
+}
+
+static bool
+riscv_jvt_entry_better (const riscv_jvt_htab_entry *entry,
+			const riscv_jvt_slot_t *slot)
+{
+  if (slot->benefit == 0)
+    return true;
+  if (entry->benefit != slot->benefit)
+    return entry->benefit > slot->benefit;
+  if (entry->first_seen != slot->first_seen)
+    return entry->first_seen < slot->first_seen;
+  return riscv_compare_jvt_key (&entry->key, entry->name, &slot->key,
+				slot->name) < 0;
+}
+
+static int
+riscv_ranking_jvt (void **entry_ptr, void *arg_)
+{
+  const riscv_jvt_htab_entry *entry = *entry_ptr;
+  riscv_jvt_args *arg = arg_;
+  riscv_jvt_slot_t *slots = arg->htab->slots;
+  unsigned int left;
+
+  for (left = arg->start; left <= arg->end; left++)
+    if (riscv_jvt_entry_better (entry, &slots[left]))
+      break;
+
+  if (left > arg->end)
+    return true;
+
+  for (unsigned int idx = arg->end; idx > left; idx--)
+    slots[idx] = slots[idx - 1];
+
+  slots[left].benefit = entry->benefit;
+  slots[left].first_seen = entry->first_seen;
+  slots[left].name = entry->name;
+  slots[left].key = entry->key;
+
+  return true;
+}
+
+static bool
+riscv_record_jvt_index (htab_t htab, riscv_jvt_args *args,
+			unsigned int count)
+{
+  riscv_jvt_htab_t *jvt_htab = args->htab;
+  riscv_jvt_slot_t *slots = jvt_htab->slots;
+  riscv_jvt_htab_entry *entry = NULL;
+  unsigned int idx, end;
+
+  end = args->start + count;
+  for (idx = args->start; idx < end; idx++)
+    {
+      riscv_jvt_htab_entry search =
+	{
+	  .index = 0,
+	  .key = slots[idx].key,
+	  .benefit = 0,
+	  .first_seen = 0,
+	  .name = NULL,
+	};
+
+      entry = htab_find (htab, &search);
+
+      BFD_ASSERT (entry != NULL);
+      entry->index = idx + 1;
+      jvt_htab->total_benefits += slots[idx].benefit;
+    }
+
+  if (entry != NULL && entry->index != 0)
+    jvt_htab->end_idx = entry->index;
+
+  return true;
+}
+
+static bool
+riscv_jvt_profiling (riscv_jvt_htab_t *jvt_htab, riscv_jvt_args *args)
+{
+  bfd_size_type jt_prefix[ZCMT_JT_NUM_ENTRIES + 1];
+  bfd_size_type jalt_prefix[ZCMT_JALT_NUM_ENTRIES + 1];
+  unsigned int jt_count = 0;
+  unsigned int jalt_count = 0;
+  unsigned int best_jt = 0;
+  unsigned int best_jalt = 0;
+  unsigned int best_entries = 0;
+  bfd_size_type best_benefit = 0;
+  bfd_size_type best_net = 0;
+
+  memset (jvt_htab->slots, 0,
+	  sizeof (riscv_jvt_slot_t) * ZCMT_TOTAL_ENTRIES);
+  jvt_htab->total_benefits = 0;
+  jvt_htab->end_idx = 0;
+
+  args->start = ZCMT_JT_BEGIN;
+  args->end = ZCMT_JT_END;
+  htab_traverse (jvt_htab->jt_htab, riscv_ranking_jvt, args);
+
+  args->start = ZCMT_JALT_BEGIN;
+  args->end = ZCMT_JALT_END;
+  htab_traverse (jvt_htab->jalt_htab, riscv_ranking_jvt, args);
+
+  jt_prefix[0] = 0;
+  while (jt_count < ZCMT_JT_NUM_ENTRIES
+	 && jvt_htab->slots[ZCMT_JT_BEGIN + jt_count].benefit != 0)
+    {
+      jt_prefix[jt_count + 1]
+	= jt_prefix[jt_count]
+	  + jvt_htab->slots[ZCMT_JT_BEGIN + jt_count].benefit;
+      jt_count++;
+    }
+
+  jalt_prefix[0] = 0;
+  while (jalt_count < ZCMT_JALT_NUM_ENTRIES
+	 && jvt_htab->slots[ZCMT_JALT_BEGIN + jalt_count].benefit != 0)
+    {
+      jalt_prefix[jalt_count + 1]
+	= jalt_prefix[jalt_count]
+	  + jvt_htab->slots[ZCMT_JALT_BEGIN + jalt_count].benefit;
+      jalt_count++;
+    }
+
+  for (unsigned int jt = 0; jt <= jt_count; jt++)
+    for (unsigned int jalt = 0; jalt <= jalt_count; jalt++)
+      {
+	unsigned int entries;
+	bfd_size_type benefit;
+	bfd_size_type table_cost, net;
+
+	if (jt == 0 && jalt == 0)
+	  entries = 0;
+	else if (jalt == 0)
+	  entries = jt;
+	else
+	  entries = ZCMT_JALT_BEGIN + jalt;
+
+	benefit = jt_prefix[jt] + jalt_prefix[jalt];
+	table_cost = (bfd_size_type) entries * RISCV_ELF_WORD_BYTES;
+	net = benefit > table_cost ? benefit - table_cost : 0;
+
+	if (net > best_net
+	    || (net == best_net
+		&& (entries < best_entries
+		    || (entries == best_entries
+			&& benefit > best_benefit))))
+	  {
+	    best_net = net;
+	    best_jt = jt;
+	    best_jalt = jalt;
+	    best_entries = entries;
+	    best_benefit = benefit;
+	  }
+      }
+
+  if (best_net == 0)
+    {
+      if (jvt_htab->jvt_sec != NULL)
+	{
+	  asection *sec = jvt_htab->jvt_sec;
+	  asection *out_sec = sec->output_section;
+
+	  /* The JVT placeholder is created before profitability is known so
+	     that orphan placement and relaxation sizing can see a normal
+	     linker-created section.  When no positive-benefit table is chosen,
+	     SEC_EXCLUDE alone is not enough: the orphan output section may
+	     already have been inserted into the output BFD section list and a
+	     zero-sized .riscv.jvt would remain visible to later layout and
+	     inspection.  It is safe to remove these sections here because this
+	     path runs before __jvt_base$ is defined, the placeholder has no
+	     user input section owner, and no table-jump relocations have been
+	     marked.  Tests cover both custom-script and default-script no-JVT
+	     outputs.  */
+	  sec->flags &= ~SEC_KEEP;
+	  sec->flags |= SEC_EXCLUDE;
+	  sec->output_section = bfd_abs_section_ptr;
+
+	  if (out_sec != NULL && out_sec != bfd_abs_section_ptr)
+	    {
+	      out_sec->flags &= ~SEC_KEEP;
+	      out_sec->flags |= SEC_EXCLUDE;
+	      if (!bfd_section_removed_from_list (out_sec->owner, out_sec))
+		{
+		  bfd_section_list_remove (out_sec->owner, out_sec);
+		  out_sec->owner->section_count--;
+		}
+	    }
+
+	  if (sec != out_sec
+	      && !bfd_section_removed_from_list (sec->owner, sec))
+	    {
+	      bfd_section_list_remove (sec->owner, sec);
+	      sec->owner->section_count--;
+	    }
+	}
+      jvt_htab->end_idx = -1;
+      return true;
+    }
+
+  args->start = ZCMT_JT_BEGIN;
+  args->end = ZCMT_JT_END;
+  riscv_record_jvt_index (jvt_htab->jt_htab, args, best_jt);
+
+  args->start = ZCMT_JALT_BEGIN;
+  args->end = ZCMT_JALT_END;
+  riscv_record_jvt_index (jvt_htab->jalt_htab, args, best_jalt);
+
+  jvt_htab->end_idx = best_entries;
+  jvt_htab->total_benefits = best_benefit;
+
+  return true;
+}
+
 /* Return true if TYPE is a delete relocation.  */
 
 static bool
@@ -4346,7 +5176,84 @@  typedef bool (*relax_func_t) (bfd *, asection *, asection *,
 			      riscv_pcgp_relocs *,
 			      bool undefined_weak);
 
-/* Relax AUIPC + JALR into JAL.  */
+/* Mark JAL/CALL/CALL_PLT to use table jump instructions.  */
+
+static bool
+_bfd_riscv_jvt_mark (bfd *abfd, asection *sec,
+		     asection *sym_sec ATTRIBUTE_UNUSED,
+		     struct bfd_link_info *link_info, Elf_Internal_Rela *rel,
+		     bfd_vma symval ATTRIBUTE_UNUSED,
+		     bfd_vma max_alignment ATTRIBUTE_UNUSED,
+		     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;
+  int type = ELFNN_R_TYPE (rel->r_info);
+  bfd_byte *jump_inst_location = NULL;
+  bfd_vma target, jump_inst;
+  unsigned int rd;
+  htab_t tbljal_htab;
+  riscv_jvt_key_t key;
+  const char *name;
+  riscv_jvt_htab_entry search;
+  riscv_jvt_htab_entry *entry;
+  size_t delete_count = 0;
+
+  if (type == R_RISCV_JAL)
+    {
+      jump_inst_location = contents + rel->r_offset;
+      delete_count = 2;
+    }
+  else if (type == R_RISCV_CALL || type == R_RISCV_CALL_PLT)
+    {
+      jump_inst_location = contents + rel->r_offset + 4;
+      delete_count = 6;
+    }
+  else
+    {
+      /* Dispatch should only pass JAL/CALL/CALL_PLT here.  Keep malformed
+	 or unexpected input from turning an internal invariant into a crash.  */
+      BFD_ASSERT (false);
+      return true;
+    }
+
+  jump_inst = bfd_getl32 (jump_inst_location);
+  rd = (jump_inst >> OP_SH_RD) & OP_MASK_RD;
+  tbljal_htab = riscv_get_jvt_htab (link_info, rd);
+
+  /* Check if it uses a valid link register.  */
+  if (tbljal_htab == NULL)
+    return true;
+
+  if (!riscv_jvt_init_key (link_info, abfd, rel, &key, &name))
+    return true;
+
+  search.index = 0;
+  search.key = key;
+  search.benefit = 0;
+  search.first_seen = 0;
+  search.name = NULL;
+  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_putl16 (target, contents + rel->r_offset);
+      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,
+				       delete_count, link_info, pcgp_relocs,
+				       rel + 1, false);
+    }
+
+  return true;
+}
+
+/* Relax AUIPC + JALR into JAL or CM.[JT,JALT].  */
 
 static bool
 _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
@@ -4365,6 +5272,9 @@  _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
   bfd_vma auipc, jalr;
   int rd, r_type, len = 4, rvc = elf_elfheader (abfd)->e_flags & EF_RISCV_RVC;
 
+  auipc = bfd_getl32 (contents + rel->r_offset);
+  jalr = bfd_getl32 (contents + rel->r_offset + 4);
+
   /* If the call 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 call to the target.
@@ -4384,8 +5294,6 @@  _bfd_riscv_relax_call (bfd *abfd, asection *sec, asection *sym_sec,
   /* Shorten the function call.  */
   BFD_ASSERT (rel->r_offset + 8 <= sec->size);
 
-  auipc = bfd_getl32 (contents + rel->r_offset);
-  jalr = bfd_getl32 (contents + rel->r_offset + 4);
   rd = (jalr >> OP_SH_RD) & OP_MASK_RD;
   rvc = rvc && VALID_CJTYPE_IMM (foff);
 
@@ -4442,13 +5350,14 @@  _bfd_riscv_relax_jal (bfd *abfd, asection *sec, asection *sym_sec,
   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;
+  bfd_vma jal = bfd_getl32 (contents + rel->r_offset);
+  int rd;
 
   /* 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;
+  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)))
@@ -4503,6 +5412,11 @@  _bfd_riscv_get_max_alignment (asection *sec, bfd_vma gp)
   for (o = sec->output_section->owner->sections; o != NULL; o = o->next)
     {
       bool valid = true;
+      /* Exclude the table jump section because it does not affect
+	 alignment.  */
+      if (strcmp (o->name, TABLE_JUMP_SEC_NAME) == 0)
+	continue;
+
       if (gp
 	  && !(VALID_ITYPE_IMM (sec_addr (o) - gp)
 	       || VALID_ITYPE_IMM (sec_addr (o) + o->size - gp)))
@@ -4909,10 +5823,10 @@  bfd_elfNN_riscv_set_data_segment_info (struct bfd_link_info *info,
 }
 
 /* Relax a section.
-
    Pass 0: Shortens code sequences for LUI/CALL/TPREL/PCREL relocs and
 	   deletes the obsolete bytes.
-   Pass 1: Which cannot be disabled, handles code alignment directives.  */
+   Pass 1: Table jump profiling.
+   Pass 2: Which cannot be disabled, handles code alignment directives.  */
 
 static bool
 _bfd_riscv_relax_section (bfd *abfd, asection *sec,
@@ -4925,19 +5839,17 @@  _bfd_riscv_relax_section (bfd *abfd, asection *sec,
   Elf_Internal_Rela *relocs;
   bool ret = false;
   unsigned int i;
-  bfd_vma max_alignment, reserve_size = 0;
+  bfd_vma max_alignment, reserve_size = 0, used_bytes, trimmed_bytes;
   riscv_pcgp_relocs pcgp_relocs;
   static asection *first_section = NULL;
+  riscv_jvt_htab_t *jvt_htab = NULL;
 
   *again = false;
 
-  if (bfd_link_relocatable (info)
-      || sec->sec_flg0
-      || sec->reloc_count == 0
-      || (sec->flags & SEC_RELOC) == 0
-      || (sec->flags & SEC_HAS_CONTENTS) == 0
+  if (bfd_link_relocatable (info) || sec->sec_flg0 || sec->reloc_count == 0
+      || (sec->flags & SEC_RELOC) == 0 || (sec->flags & SEC_HAS_CONTENTS) == 0
       || (info->disable_target_specific_optimizations
-	  && info->relax_pass == 0)
+	  && info->relax_pass <= RELAX_PASS_CAN_BE_DISABLED)
       /* The exp_seg_relro_adjust is enum phase_enum (0x4),
 	 and defined in ld/ldexp.h.  */
       || *(htab->data_segment_phase) == 4)
@@ -4958,6 +5870,16 @@  _bfd_riscv_relax_section (bfd *abfd, asection *sec,
   else if (!(relocs = _bfd_elf_link_read_relocs (abfd, sec, NULL, NULL,
 						 info->keep_memory)))
     goto fail;
+  /* Read this BFD's contents if we haven't done so already.  */
+  if (!data->this_hdr.contents
+      && !bfd_malloc_and_get_section (abfd, sec, &data->this_hdr.contents))
+    goto fail;
+
+  /* Read this BFD's symbols if we haven't done so already.  */
+  if (symtab_hdr->sh_info != 0 && !symtab_hdr->contents
+      && !(symtab_hdr->contents = (unsigned char *) bfd_elf_get_elf_syms (
+	     abfd, symtab_hdr, symtab_hdr->sh_info, 0, NULL, NULL, NULL)))
+    goto fail;
 
   /* Estimate the maximum alignment for all output sections once time
      should be enough.  */
@@ -4968,6 +5890,96 @@  _bfd_riscv_relax_section (bfd *abfd, asection *sec,
       htab->max_alignment = max_alignment;
     }
 
+  /* relax_trip 0 / JVT_PROFILING_RECORD_SYM:
+	Record symbol address and expected size saving of each relocation
+	that can be replaced by table jump instructions.
+
+     relax_trip 1 / JVT_PROFILING_RANK:
+	Rank the best ZCMT_JT_NUM_ENTRIES relocations to replace for cm.jt and
+	the best ZCMT_JALT_NUM_ENTRIES relocations for cm.jalt in terms of the
+	total size saved.
+
+     relax_trip 2 / JVT_PROFILING_DETERMINE:
+	Check if table jump can reduce the size, and delete the whole table
+	jump section if the size will not be reduced.
+	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. Then add relocations to the jump
+	table.
+
+     relax_trip 3 / JVT_PROFILING_TRIM:
+	Trim unused slots in the table jump section.  */
+
+  if (info->relax_pass == RELAX_PASS_TABLE_JUMP_PROFILING
+      && riscv_use_jvt (info))
+    {
+      if (!riscv_ensure_jvt_htab (htab))
+	goto fail;
+      jvt_htab = htab->jvt_htab;
+
+      riscv_relax_delete_bytes = _riscv_relax_delete_immediate;
+      /* Avoid size benefits of relocations to be recorded multiple times.  */
+      if (info->relax_trip == JVT_PROFILING_RECORD_SYM
+	  && *(htab->data_segment_phase) != 0)
+	return true;
+
+      /* Rank the entries, and calculate the expected total saving.  */
+      else if (info->relax_trip == JVT_PROFILING_RANK)
+	{
+	  *again = true;
+	  /* Profiling stage finished.  */
+	  if (jvt_htab->end_idx != 0)
+	    return true;
+
+	  riscv_jvt_args args = {jvt_htab, 0, 0};
+	  /* Estimate size benefits if table jump is used.  */
+	  riscv_jvt_profiling (jvt_htab, &args);
+	  if (jvt_htab->end_idx > 0
+	      && (!bfd_set_section_alignment (jvt_htab->jvt_sec, 6)
+		  || !bfd_set_section_size
+		       (jvt_htab->jvt_sec,
+			(bfd_size_type) jvt_htab->end_idx
+			* RISCV_ELF_WORD_BYTES)))
+	    goto fail;
+	  return true;
+	}
+
+      /* Generate table-jump instructions only if profiling chose a
+	 positive-benefit table and sized .riscv.jvt.  */
+      else if (info->relax_trip == JVT_PROFILING_DETERMINE)
+	{
+	  if (jvt_htab->end_idx <= 0 || jvt_htab->jvt_sec->size == 0)
+	    return true;
+
+	  *again = true;
+	}
+
+      /* Trim unused slots in the table jump section.  */
+      else if (info->relax_trip == JVT_PROFILING_TRIM)
+	{
+	  /* Table jump entry section is trimmed.  */
+	  if (jvt_htab->end_idx < 0)
+	    return true;
+
+	  used_bytes = jvt_htab->end_idx * RISCV_ELF_WORD_BYTES;
+	  if (jvt_htab->jvt_sec->size <= used_bytes)
+	    {
+	      jvt_htab->end_idx = -1;
+	      return true;
+	    }
+
+	  trimmed_bytes = jvt_htab->jvt_sec->size - used_bytes;
+	  /* Trim unused slots.  */
+	  if (!riscv_relax_delete_bytes (jvt_htab->jvt_sec->owner,
+					 jvt_htab->jvt_sec, used_bytes,
+					 trimmed_bytes, info, NULL, NULL, false))
+	    return false;
+	  /* Mark table jump profiling stage as completed.  */
+	  jvt_htab->end_idx = -1;
+	  return true;
+	}
+    }
+
   /* Examine and consider relaxing each reloc.  */
   for (i = 0; i < sec->reloc_count; i++)
     {
@@ -4981,17 +5993,49 @@  _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 
       relax_func = NULL;
       riscv_relax_delete_bytes = NULL;
-      if (info->relax_pass == 0)
+      if (info->relax_pass == RELAX_PASS_TABLE_JUMP_PROFILING)
+	{
+	  if (!riscv_use_jvt (info))
+	    return true;
+	  riscv_relax_delete_bytes = _riscv_relax_delete_immediate;
+
+	  if ((type == R_RISCV_CALL || type == R_RISCV_CALL_PLT
+	       || type == R_RISCV_JAL)
+	      && (i == sec->reloc_count - 1
+		  || ELFNN_R_TYPE ((rel + 1)->r_info) != R_RISCV_RELAX
+		  || rel->r_offset != (rel + 1)->r_offset))
+	    continue;
+
+	  if (info->relax_trip == JVT_PROFILING_RECORD_SYM)
+	    {
+	      if (type == R_RISCV_CALL || type == R_RISCV_CALL_PLT
+		  || type == R_RISCV_JAL)
+		relax_func = _bfd_riscv_jvt_record;
+	      else
+		continue;
+	      *again = true;
+	      i++;
+	    }
+	  else if (info->relax_trip == JVT_PROFILING_DETERMINE)
+	    {
+	      if (type == R_RISCV_CALL || type == R_RISCV_CALL_PLT
+		  || type == R_RISCV_JAL)
+		relax_func = _bfd_riscv_jvt_mark;
+	      else
+		continue;
+	      i++;
+	    }
+	}
+      else if (info->relax_pass == RELAX_PASS_SHORTEN_LUI_CALL_TRREL_PCREL)
 	{
-	  if (type == R_RISCV_CALL
-	      || type == R_RISCV_CALL_PLT)
+	  if (type == R_RISCV_CALL || type == R_RISCV_CALL_PLT)
 	    relax_func = _bfd_riscv_relax_call;
-	  else if (type == R_RISCV_HI20
-		   || type == R_RISCV_LO12_I
+	  else if (type == R_RISCV_JAL)
+	    relax_func = _bfd_riscv_relax_jal;
+	  else if (type == R_RISCV_HI20 || type == R_RISCV_LO12_I
 		   || type == R_RISCV_LO12_S)
 	    relax_func = _bfd_riscv_relax_lui;
-	  else if (type == R_RISCV_TPREL_HI20
-		   || type == R_RISCV_TPREL_ADD
+	  else if (type == R_RISCV_TPREL_HI20 || type == R_RISCV_TPREL_ADD
 		   || type == R_RISCV_TPREL_LO12_I
 		   || type == R_RISCV_TPREL_LO12_S)
 	    relax_func = _bfd_riscv_relax_tls_le;
@@ -5015,7 +6059,8 @@  _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 	  /* Skip over the R_RISCV_RELAX.  */
 	  i++;
 	}
-      else if (info->relax_pass == 1 && type == R_RISCV_ALIGN)
+      else if (info->relax_pass == RELAX_PASS_ALIGNMENT
+	       && type == R_RISCV_ALIGN)
 	{
 	  relax_func = _bfd_riscv_relax_align;
 	  riscv_relax_delete_bytes = _riscv_relax_delete_immediate;
@@ -5025,20 +6070,6 @@  _bfd_riscv_relax_section (bfd *abfd, asection *sec,
 
       data->relocs = relocs;
 
-      /* Read this BFD's contents if we haven't done so already.  */
-      if (!data->this_hdr.contents
-	  && !bfd_malloc_and_get_section (abfd, sec, &data->this_hdr.contents))
-	goto fail;
-
-      /* Read this BFD's symbols if we haven't done so already.  */
-      if (symtab_hdr->sh_info != 0
-	  && !symtab_hdr->contents
-	  && !(symtab_hdr->contents =
-	       (unsigned char *) bfd_elf_get_elf_syms (abfd, symtab_hdr,
-						       symtab_hdr->sh_info,
-						       0, NULL, NULL, NULL)))
-	goto fail;
-
       /* Get the value of the symbol referred to by the reloc.  */
       if (ELFNN_R_SYM (rel->r_info) < symtab_hdr->sh_info)
 	{
@@ -5384,6 +6415,36 @@  riscv_elf_obj_attrs_arg_type (obj_attr_tag_t tag)
   return (tag & 1) != 0 ? ATTR_TYPE_FLAG_STR_VAL : ATTR_TYPE_FLAG_INT_VAL;
 }
 
+static bool
+riscv_final_link (bfd *abfd, struct bfd_link_info *info)
+{
+  struct riscv_elf_link_hash_table *htab = riscv_elf_hash_table (info);
+  riscv_jvt_htab_t *jvt_htab = htab->jvt_htab;
+
+  if (riscv_use_jvt (info)
+      && !riscv_define_jvt_base_symbol (abfd, info))
+    return false;
+
+  if (!_bfd_elf_final_link (abfd, info))
+    return false;
+
+  if (riscv_use_jvt (info)
+      && jvt_htab != NULL
+      && jvt_htab->jvt_sec != NULL
+      && jvt_htab->jvt_sec->size != 0
+      && (jvt_htab->jvt_sec->flags & SEC_EXCLUDE) == 0)
+    {
+      asection *sec = jvt_htab->jvt_sec;
+      asection *out_sec = sec->output_section;
+
+      if (!bfd_set_section_contents (abfd, out_sec, jvt_htab->jvt_addrs,
+				     (file_ptr) sec->output_offset, sec->size))
+	return false;
+    }
+
+  return true;
+}
+
 /* Do not choose mapping symbols as a function name.  */
 
 static bfd_size_type
@@ -5549,6 +6610,7 @@  elfNN_riscv_merge_gnu_properties (struct bfd_link_info *info, bfd *abfd,
 #define bfd_elfNN_mkobject			elfNN_riscv_mkobject
 #define bfd_elfNN_get_synthetic_symtab		\
   elfNN_riscv_get_synthetic_symtab
+#define bfd_elfNN_bfd_final_link riscv_final_link
 
 #define elf_backend_reloc_type_class		riscv_reloc_type_class
 #define elf_backend_copy_indirect_symbol	riscv_elf_copy_indirect_symbol