[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