[PATCH 2/2] RISC-V: Add ARC-V APEX disassembler support
Luis Silva
Luis.Silva1@synopsys.com
Mon May 25 13:30:52 GMT 2026
Add disassembler support for ARC-V APEX instructions by reading metadata from
.riscvapex.* ELF sections (emitted by GAS) and reconstructing instruction
definitions at startup.
Each metadata record is deserialized into a dynamically allocated riscv_opcode
entry and placed in the arcv_apex_insns[] lookup table, indexed by format offset
plus sub-opcode.
When the standard opcode table produces no match, the disassembler determines
the instruction format from bits [14:12], extracts the sub-opcode, and looks up
the corresponding entry.
gas/ChangeLog:
* testsuite/gas/riscv/x-arcv-apex-01.d: Add disassembly
expected output.
* testsuite/gas/riscv/x-arcv-apex-02.d: Likewise.
* testsuite/gas/riscv/x-arcv-apex-04.d: Likewise.
include/ChangeLog:
* opcode/riscv.h (EXTRACT_ARCV_APEX_XS_IMM): New macro.
(EXTRACT_ARCV_APEX_XI_XC_IMM): Likewise.
ld/ChangeLog:
* testsuite/ld-riscv-elf/x-arcv-apex-02.d: New test.
opcodes/ChangeLog:
* riscv-dis.c (arcv_apex_free_instructions): New function.
(print_insn_args): Add 'Xa' vendor operand handling.
(arcv_apex_xd_type_p): New function.
(arcv_apex_xs_type_p): Likewise.
(arcv_apex_xi_type_p): Likewise.
(arcv_apex_xc_type_p): Likewise.
(arcv_apex_decode_insn): New function.
(riscv_disassemble_insn): Try APEX decode after static table.
(arcv_apex_read_metadata): New function.
(riscv_init_disasm_info): Read APEX metadata from ELF sections.
Co-Authored-By: Alex Turjan <turjan@synopsys.com>
Signed-off-by: Luis Silva <luiss@synopsys.com>
---
gas/testsuite/gas/riscv/x-arcv-apex-01.d | 18 +-
gas/testsuite/gas/riscv/x-arcv-apex-02.d | 8 +-
gas/testsuite/gas/riscv/x-arcv-apex-04.d | 12 +-
include/opcode/riscv.h | 6 +
ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d | 36 +--
opcodes/riscv-dis.c | 269 +++++++++++++++++++++
6 files changed, 312 insertions(+), 37 deletions(-)
diff --git a/gas/testsuite/gas/riscv/x-arcv-apex-01.d b/gas/testsuite/gas/riscv/x-arcv-apex-01.d
index e3faa8bd632..621241d8674 100644
--- a/gas/testsuite/gas/riscv/x-arcv-apex-01.d
+++ b/gas/testsuite/gas/riscv/x-arcv-apex-01.d
@@ -7,12 +7,12 @@
Disassembly of section .text:
0+[0-9a-f]+ <.text>:
-[ ]+[0-9a-f]+:[ ]+00c5c50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+02c5800b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0205c00b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0400000b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0b15b50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0b15d00b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0163a50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0164200b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0214e50b[ ]+.*
+[ ]+[0-9a-f]+:[ ]+00c5c50b[ ]+foo0[ ]+a0,a1,a2
+[ ]+[0-9a-f]+:[ ]+02c5800b[ ]+foo1[ ]+a1,a2
+[ ]+[0-9a-f]+:[ ]+0205c00b[ ]+foo2[ ]+a1
+[ ]+[0-9a-f]+:[ ]+0400000b[ ]+foo3
+[ ]+[0-9a-f]+:[ ]+0b15b50b[ ]+foo4[ ]+a0,a1,(0xb|11)
+[ ]+[0-9a-f]+:[ ]+0b15d00b[ ]+foo5[ ]+a1,(0xb|11)
+[ ]+[0-9a-f]+:[ ]+0163a50b[ ]+foo6[ ]+a0,(0x16|22)
+[ ]+[0-9a-f]+:[ ]+0164200b[ ]+foo7[ ]+(0x16|22)
+[ ]+[0-9a-f]+:[ ]+0214e50b[ ]+foo8[ ]+a0,a0,(0x21|33)
diff --git a/gas/testsuite/gas/riscv/x-arcv-apex-02.d b/gas/testsuite/gas/riscv/x-arcv-apex-02.d
index 6bb08202e2a..d5fa36ff77f 100644
--- a/gas/testsuite/gas/riscv/x-arcv-apex-02.d
+++ b/gas/testsuite/gas/riscv/x-arcv-apex-02.d
@@ -7,7 +7,7 @@
Disassembly of section .text:
0+[0-9a-f]+ <.text>:
-[ ]+[0-9a-f]+:[ ]+0020e50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0205b50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+1000e50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0ff0e50b[ ]+.*
+[ ]+[0-9a-f]+:[ ]+0020e50b[ ]+foo[ ]+a0,a0,(0x2|2)
+[ ]+[0-9a-f]+:[ ]+0205b50b[ ]+foo[ ]+a0,a1,(0x2|2)
+[ ]+[0-9a-f]+:[ ]+1000e50b[ ]+foo[ ]+a0,a0,(0x100|256)
+[ ]+[0-9a-f]+:[ ]+0ff0e50b[ ]+foo[ ]+a0,a0,(0xff|255)
diff --git a/gas/testsuite/gas/riscv/x-arcv-apex-04.d b/gas/testsuite/gas/riscv/x-arcv-apex-04.d
index f62d160a30b..a9461929b37 100644
--- a/gas/testsuite/gas/riscv/x-arcv-apex-04.d
+++ b/gas/testsuite/gas/riscv/x-arcv-apex-04.d
@@ -7,9 +7,9 @@
Disassembly of section .text:
0+[0-9a-f]+ <.text>:
-[ ]+[0-9a-f]+:[ ]+8005b50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+7f05b50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+8000a50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+7ff0a50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+8000e50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+7ff0e50b[ ]+.*
+[ ]+[0-9a-f]+:[ ]+8005b50b[ ]+foo0[ ]+a0,a1,(-0x80|-128)
+[ ]+[0-9a-f]+:[ ]+7f05b50b[ ]+foo0[ ]+a0,a1,(0x7f|127)
+[ ]+[0-9a-f]+:[ ]+8000a50b[ ]+foo1[ ]+a0,(-0x800|-2048)
+[ ]+[0-9a-f]+:[ ]+7ff0a50b[ ]+foo1[ ]+a0,(0x7ff|2047)
+[ ]+[0-9a-f]+:[ ]+8000e50b[ ]+foo2[ ]+a0,a0,(-0x800|-2048)
+[ ]+[0-9a-f]+:[ ]+7ff0e50b[ ]+foo2[ ]+a0,a0,(0x7ff|2047)
diff --git a/include/opcode/riscv.h b/include/opcode/riscv.h
index fdb23c0958d..80cd0622683 100644
--- a/include/opcode/riscv.h
+++ b/include/opcode/riscv.h
@@ -794,6 +794,12 @@ struct apex_insn
/* Metadata record type for APEX instructions. */
#define ARCV_APEX_METADATA_TYPE 1
+#define EXTRACT_ARCV_APEX_XS_IMM(x) \
+ (RV_X(x, 24, 8) | (RV_IMM_SIGN(x) << 8))
+
+#define EXTRACT_ARCV_APEX_XI_XC_IMM(x) \
+ (RV_X(x, 20, 12) | (RV_IMM_SIGN(x) << 12))
+
#define ENCODE_ARCV_APEX_8BIT_IMM(x) \
(RV_X(x, 0, 8) << 24)
diff --git a/ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d b/ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d
index abebb3566e6..317417795de 100644
--- a/ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d
+++ b/ld/testsuite/ld-riscv-elf/x-arcv-apex-02.d
@@ -13,24 +13,24 @@ Disassembly of section .text:
#...
0+[0-9a-f]+ <func_1>:
-[ ]+[0-9a-f]+:[ ]+00c5c50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+02c5800b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0205c00b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0400000b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0b15b50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0b15d00b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0163a50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0164200b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0214e50b[ ]+.*
+[ ]+[0-9a-f]+:[ ]+00c5c50b[ ]+foo0[ ]+a0,a1,a2
+[ ]+[0-9a-f]+:[ ]+02c5800b[ ]+foo1[ ]+a1,a2
+[ ]+[0-9a-f]+:[ ]+0205c00b[ ]+foo2[ ]+a1
+[ ]+[0-9a-f]+:[ ]+0400000b[ ]+foo3
+[ ]+[0-9a-f]+:[ ]+0b15b50b[ ]+foo4[ ]+a0,a1,(0xb|11)
+[ ]+[0-9a-f]+:[ ]+0b15d00b[ ]+foo5[ ]+a1,(0xb|11)
+[ ]+[0-9a-f]+:[ ]+0163a50b[ ]+foo6[ ]+a0,(0x16|22)
+[ ]+[0-9a-f]+:[ ]+0164200b[ ]+foo7[ ]+(0x16|22)
+[ ]+[0-9a-f]+:[ ]+0214e50b[ ]+foo8[ ]+a0,a0,(0x21|33)
0+[0-9a-f]+ <func_2>:
-[ ]+[0-9a-f]+:[ ]+00c5c50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+02c5800b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0205c00b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0400000b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0b15b50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0b15d00b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0163a50b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0164200b[ ]+.*
-[ ]+[0-9a-f]+:[ ]+0214e50b[ ]+.*
+[ ]+[0-9a-f]+:[ ]+00c5c50b[ ]+foo0[ ]+a0,a1,a2
+[ ]+[0-9a-f]+:[ ]+02c5800b[ ]+foo1[ ]+a1,a2
+[ ]+[0-9a-f]+:[ ]+0205c00b[ ]+foo2[ ]+a1
+[ ]+[0-9a-f]+:[ ]+0400000b[ ]+foo3
+[ ]+[0-9a-f]+:[ ]+0b15b50b[ ]+foo4[ ]+a0,a1,(0xb|11)
+[ ]+[0-9a-f]+:[ ]+0b15d00b[ ]+foo5[ ]+a1,(0xb|11)
+[ ]+[0-9a-f]+:[ ]+0163a50b[ ]+foo6[ ]+a0,(0x16|22)
+[ ]+[0-9a-f]+:[ ]+0164200b[ ]+foo7[ ]+(0x16|22)
+[ ]+[0-9a-f]+:[ ]+0214e50b[ ]+foo8[ ]+a0,a0,(0x21|33)
#pass
diff --git a/opcodes/riscv-dis.c b/opcodes/riscv-dis.c
index eda802ff420..46668046e8d 100644
--- a/opcodes/riscv-dis.c
+++ b/opcodes/riscv-dis.c
@@ -74,6 +74,28 @@ struct riscv_private_data
bool all_ext;
};
+/* Global instruction table for dynamically-defined APEX extensions.
+ Indexed by format offset (XD, XS, XI, XC) plus sub-opcode. */
+static struct riscv_opcode *arcv_apex_insns[ARCV_APEX_INSN_LIMIT];
+
+/* Free all dynamically allocated APEX instructions. */
+
+static void
+arcv_apex_free_instructions (void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARCV_APEX_INSN_LIMIT; i++)
+ {
+ if (arcv_apex_insns[i] != NULL)
+ {
+ free ((char *) arcv_apex_insns[i]->name);
+ free (arcv_apex_insns[i]);
+ arcv_apex_insns[i] = NULL;
+ }
+ }
+}
+
/* Set default RISC-V disassembler options. */
static void
@@ -917,6 +939,33 @@ print_insn_args (const char *oparg, insn_t l, bfd_vma pc, disassemble_info *info
goto undefined_modifier;
}
break;
+ case 'a': /* Vendor-specific (ARC-V) operands. */
+ switch (*++oparg)
+ {
+ case 'd':
+ print (info->stream, dis_style_register, "%s",
+ pd->riscv_gpr_names[(l >> OP_SH_RD) & OP_MASK_RD]);
+ break;
+ case 's':
+ print (info->stream, dis_style_register, "%s",
+ pd->riscv_gpr_names[(l >> OP_SH_RS1) & OP_MASK_RS1]);
+ break;
+ case 't':
+ print (info->stream, dis_style_register, "%s",
+ pd->riscv_gpr_names[(l >> OP_SH_RS2) & OP_MASK_RS2]);
+ break;
+ case 'k':
+ print (info->stream, dis_style_immediate, "%d",
+ (int)EXTRACT_ARCV_APEX_XS_IMM (l));
+ break;
+ case 'j':
+ print (info->stream, dis_style_immediate, "%d",
+ (int)EXTRACT_ARCV_APEX_XI_XC_IMM (l));
+ break;
+ default:
+ goto undefined_modifier;
+ }
+ break;
default:
goto undefined_modifier;
}
@@ -933,6 +982,77 @@ print_insn_args (const char *oparg, insn_t l, bfd_vma pc, disassemble_info *info
}
}
+/* Check APEX format - bits [14:12] determine the format. */
+
+static bool
+arcv_apex_xd_type_p (insn_t word)
+{
+ return ((word >> 12) & 0x3) == 0x0;
+}
+
+static bool
+arcv_apex_xs_type_p (insn_t word)
+{
+ return ((word >> 12) & 0x1) == 0x1;
+}
+
+static bool
+arcv_apex_xi_type_p (insn_t word)
+{
+ return ((word >> 12) & 0x7) == 0x2;
+}
+
+static bool
+arcv_apex_xc_type_p (insn_t word)
+{
+ return ((word >> 12) & 0x7) == 0x6;
+}
+
+/* Try to decode WORD as an APEX Custom-0 instruction.
+ Returns the matching opcode entry, or NULL on failure. */
+
+static const struct riscv_opcode *
+arcv_apex_decode_insn (insn_t word)
+{
+ if ((word & 0xF) != ARCV_APEX_CUSTOM0_OPCODE)
+ return NULL;
+
+ unsigned int offset = 0, sub_opcode = 0;
+
+ if (arcv_apex_xd_type_p (word))
+ {
+ sub_opcode = (((word >> 25) & 0x7F) << 1)
+ | ((word >> 14) & 1);
+ offset = ARCV_APEX_OFFSET_XD;
+ }
+ else if (arcv_apex_xs_type_p (word))
+ {
+ sub_opcode = (((word >> 20) & 0xF) << 2)
+ | ((word >> 13) & 0x3);
+ offset = ARCV_APEX_OFFSET_XS;
+ }
+ else if (arcv_apex_xi_type_p (word))
+ {
+ sub_opcode = (word >> 15) & 0x1F;
+ offset = ARCV_APEX_OFFSET_XI;
+ }
+ else if (arcv_apex_xc_type_p (word))
+ {
+ sub_opcode = (word >> 15) & 0x1F;
+ offset = ARCV_APEX_OFFSET_XC;
+ }
+
+ if (sub_opcode + offset >= ARCV_APEX_INSN_LIMIT)
+ return NULL;
+
+ const struct riscv_opcode *op
+ = arcv_apex_insns[sub_opcode + offset];
+ if (op == NULL || !(op->match_func) (op, word))
+ return NULL;
+
+ return op;
+}
+
/* Print the RISC-V instruction at address MEMADDR in debugged memory,
on using INFO. Returns length of the instruction, in bytes.
BIGENDIAN must be 1 if this is big-endian code, 0 if
@@ -1068,6 +1188,17 @@ riscv_disassemble_insn (bfd_vma memaddr,
}
}
+ /* No match in the static table; try APEX metadata-driven Custom-0. */
+ op = arcv_apex_decode_insn (word);
+ if (op != NULL)
+ {
+ (*info->fprintf_styled_func) (info->stream,
+ dis_style_mnemonic,
+ "%s", op->name);
+ print_insn_args (op->args, word, memaddr, info);
+ return insnlen;
+ }
+
/* We did not find a match, so just print the instruction bits in
the shape of an assembler .insn directive. */
info->insn_type = dis_noninsn;
@@ -1412,6 +1543,123 @@ riscv_disassemble_data (bfd_vma memaddr ATTRIBUTE_UNUSED,
return info->bytes_per_chunk;
}
+/* Create a mapping between a serialized APEX instruction record
+ (emitted by GAS) and a dynamically allocated riscv_opcode entry.
+
+ Record format (see struct apex_insn):
+ +------+------+--------+--------+-----------+---------+
+ | Len | Type | Opcode | FuncT | Flags | Name... |
+ +------+------+--------+--------+-----------+---------+
+ 1B 1B 1B 1B 2B NUL-term
+
+ - 'block' points to the beginning of the serialized record.
+ - 'length' is the size of the block (must match record_len).
+ - The instruction name is stored at the end of the block and
+ is dynamically allocated here. */
+
+static void
+arcv_apex_read_metadata (unsigned char *block,
+ unsigned long length)
+{
+ /* Assert that the provided length matches the record length
+ declared at position 0 (as emitted by GAS). */
+ unsigned int record_len = block[0];
+ if (length != record_len)
+ {
+ opcodes_error_handler (_("APEX metadata: length mismatch "
+ "(section contents %lu bytes, "
+ "record header says %u)"),
+ (unsigned long) length, record_len);
+ return;
+ }
+
+ /* Validate the metadata record type. */
+ if (block[1] != ARCV_APEX_METADATA_TYPE)
+ {
+ opcodes_error_handler (_("APEX metadata: unknown "
+ "record type %u"),
+ (unsigned) block[1]);
+ return;
+ }
+
+ /* Compute the length of the instruction name by
+ subtracting the base struct size. */
+ size_t name_length = record_len
+ - offsetof (struct apex_insn, name);
+
+ /* Allocate and null-terminate the instruction mnemonic. */
+ char *name = xmalloc (name_length + 1);
+ memcpy (name,
+ block + offsetof (struct apex_insn, name), /* Mnemonic start. */
+ name_length);
+ name[name_length] = '\0';
+
+ /* Extract sub-opcode and flags (flags is 16-bit, little-endian). */
+ unsigned int sub_opcode = block[3];
+ unsigned int flags = bfd_getl16 (block + 4);
+
+ /* Allocate a new riscv_opcode entry. */
+ struct riscv_opcode *insn = XNEW (struct riscv_opcode);
+
+ arcv_apex_init_dynamic_insn (insn);
+ insn->name = name;
+
+ unsigned int offset = 0;
+ /* Decode flags and adjust the opcode entry accordingly. */
+ switch (flags & 0xF)
+ {
+ case APEX_FLAG_XD:
+ arcv_apex_setup_xd_insn (insn, flags, sub_opcode);
+ offset = ARCV_APEX_OFFSET_XD;
+ break;
+
+ case APEX_FLAG_XS:
+ arcv_apex_setup_xs_insn (insn, flags, sub_opcode);
+ offset = ARCV_APEX_OFFSET_XS;
+ break;
+
+ case APEX_FLAG_XI:
+ arcv_apex_setup_xi_insn (insn, flags, sub_opcode);
+ offset = ARCV_APEX_OFFSET_XI;
+ break;
+
+ case APEX_FLAG_XC:
+ arcv_apex_setup_xc_insn (insn, sub_opcode);
+ offset = ARCV_APEX_OFFSET_XC;
+ break;
+
+ /* If an instruction supports both XS and XC formats
+ (e.g., "extInstruction foo,123,XS,XC"), we create a separate
+ instruction for each format. This is safe because no other XS
+ or XC instruction can use the same sub-opcode (e.g., =123). */
+ case (APEX_FLAG_XS | APEX_FLAG_XC):
+ {
+ arcv_apex_setup_xs_insn (insn, flags, sub_opcode);
+ arcv_apex_insns[sub_opcode + ARCV_APEX_OFFSET_XS] = insn;
+
+ struct riscv_opcode *xc_copy = XNEW (struct riscv_opcode);
+ arcv_apex_init_dynamic_insn (xc_copy);
+ xc_copy->name = xstrdup (insn->name);
+ arcv_apex_setup_xc_insn (xc_copy, sub_opcode);
+ arcv_apex_insns[sub_opcode + ARCV_APEX_OFFSET_XC] = xc_copy;
+ return;
+ }
+
+ default:
+ opcodes_error_handler (_("APEX metadata: unknown format flags 0x%x"),
+ flags & 0xF);
+ free (name);
+ free (insn);
+ return;
+ }
+
+ /* Place the fully initialized APEX instruction in the
+ arcv_apex_insns array at an index determined by its sub-opcode
+ and the format-specific offset. This ensures each format
+ occupies a distinct array region. */
+ arcv_apex_insns[sub_opcode + offset] = insn;
+}
+
static bool
riscv_init_disasm_info (struct disassemble_info *info)
{
@@ -1450,6 +1698,27 @@ riscv_init_disasm_info (struct disassemble_info *info)
bfd *abfd = info->section->owner;
if (abfd && bfd_get_flavour (abfd) == bfd_target_elf_flavour)
{
+ arcv_apex_free_instructions ();
+
+ asection *sect;
+ for (sect = abfd->sections; sect != NULL; sect = sect->next)
+ {
+ if (strstr (sect->name, ".riscvapex."))
+ {
+ bfd_size_type count = bfd_section_size (sect);
+ unsigned char *buffer = xmalloc (count);
+
+ if (bfd_get_section_contents (abfd, sect, buffer, 0, count))
+ arcv_apex_read_metadata (buffer, count);
+ else
+ opcodes_error_handler
+ (_("warning: could not read APEX metadata "
+ "section `%s'"),
+ sect->name);
+ free (buffer);
+ }
+ }
+
const char *sec_name =
get_elf_backend_data (abfd)->obj_attrs_section;
if (bfd_get_section_by_name (abfd, sec_name) != NULL)
--
2.34.0
More information about the Binutils
mailing list