Skip to content

Commit 1ee2422

Browse files
committed
RISC-V: Use faster hash table on disassembling
This commit improves performance on disassembling RISC-V code. It replaces riscv_hash (in opcodes/riscv-dis.c) with much faster data structure: a sorted and partitioned hash table. This is a technique actually used on SPARC architecture (opcodes/sparc-dis.c) and the author simplified the algorithm even further. Unlike SPARC, RISC-V's hashed opcode table is not a table to linked lists, it's just a table, pointing "start" elements in the sorted opcode list (per hash code) and a global tail. It is expected to have 20-40% performance improvements when disassembling linked RISC-V ELF programs using objdump. That is a significant improvement and pretty nice for such a small modification (with about 12KB heap memory allocation on 64-bit environment). This is not the end. This structure significantly improves plain binary file handling (on objdump, "objdump -b binary -m riscv:rv[32|64] -D $FILE"). The author tested on various binary files including random one and big vmlinux images and confirmed significant performance improvements (>70% on many cases). This is partially due to the fact that, disassembling about one quarter of invalid "instruction" words required iterating over one thousand opcode entries (348 or more being vector instructions with OP-V, that can be easily skipped with this new data structure). Another reason for this significance is it doesn't have various ELF overhead. opcodes/ChangeLog: * riscv-dis.c (init_riscv_dis_state_for_arch_and_options): Build the hash table on the first run. (OP_HASH_LEN): Move from riscv_disassemble_insn. (OP_HASH_IDX): Move from riscv_disassemble_insn and mask by OP_MASK_OP2 == 0x03 for only real 16-bit instructions. (riscv_hash): New sorted and partitioned hash table. (riscv_opcodes_sorted): New sorted opcode table. (compare_opcodes): New function to compare RISC-V opcode entries. (build_riscv_opcodes_hash_table): New function to build faster hash table to disassemble. (riscv_disassemble_insn): Use sorted and partitioned hash table.
1 parent 7d54ac5 commit 1ee2422

File tree

1 file changed

+76
-17
lines changed

1 file changed

+76
-17
lines changed

opcodes/riscv-dis.c

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,8 @@ free_riscv_dis_arch_context (riscv_dis_arch_context_t* context)
176176
}
177177

178178

179+
static void build_riscv_opcodes_hash_table (void);
180+
179181
/* Guess and update current XLEN. */
180182

181183
static void
@@ -219,6 +221,12 @@ init_riscv_dis_state_for_arch (void)
219221
static void
220222
init_riscv_dis_state_for_arch_and_options (void)
221223
{
224+
static bool init = false;
225+
if (!init)
226+
{
227+
build_riscv_opcodes_hash_table ();
228+
init = true;
229+
}
222230
/* If the architecture string is changed, update XLEN. */
223231
if (is_arch_changed)
224232
update_riscv_dis_xlen (NULL);
@@ -882,6 +890,69 @@ print_insn_args (const char *oparg, insn_t l, bfd_vma pc, disassemble_info *info
882890
}
883891
}
884892

893+
/* Build a hash table for the disassembler to shorten the search time.
894+
We sort riscv_opcodes entry pointers for further performance.
895+
Hash index is computed by masking the instruction with...
896+
- 0x03 (OP_MASK_OP2) for real 16-bit instructions
897+
- 0x7f (OP_MASK_OP) for all other instructions. */
898+
899+
#define OP_HASH_LEN (OP_MASK_OP + 1)
900+
#define OP_HASH_IDX(i) \
901+
((i) & (((i & OP_MASK_OP2) != OP_MASK_OP2) ? OP_MASK_OP2 : OP_MASK_OP))
902+
static const struct riscv_opcode **riscv_hash[OP_HASH_LEN + 1];
903+
static const struct riscv_opcode **riscv_opcodes_sorted;
904+
905+
/* Compare two riscv_opcode* objects to sort by hash index. */
906+
907+
static int
908+
compare_opcodes (const void *ap, const void *bp)
909+
{
910+
const struct riscv_opcode *a = *(const struct riscv_opcode **) ap;
911+
const struct riscv_opcode *b = *(const struct riscv_opcode **) bp;
912+
int ai = (int) OP_HASH_IDX (a->match);
913+
int bi = (int) OP_HASH_IDX (b->match);
914+
if (ai != bi)
915+
return ai - bi;
916+
/* Stable sort (on riscv_opcodes entry order) is required. */
917+
if (a < b)
918+
return -1;
919+
if (a > b)
920+
return +1;
921+
return 0;
922+
}
923+
924+
/* Build riscv_opcodes-based hash table. */
925+
926+
static void
927+
build_riscv_opcodes_hash_table (void)
928+
{
929+
const struct riscv_opcode *op;
930+
const struct riscv_opcode **pop, **pop_end;
931+
size_t len = 0;
932+
933+
/* Sort riscv_opcodes entry pointers (except macros). */
934+
for (op = riscv_opcodes; op->name; op++)
935+
if (op->pinfo != INSN_MACRO)
936+
len++;
937+
riscv_opcodes_sorted = xcalloc (len, sizeof (struct riscv_opcode *));
938+
pop_end = riscv_opcodes_sorted;
939+
for (op = riscv_opcodes; op->name; op++)
940+
if (op->pinfo != INSN_MACRO)
941+
*pop_end++ = op;
942+
qsort (riscv_opcodes_sorted, len, sizeof (struct riscv_opcode *),
943+
compare_opcodes);
944+
945+
/* Initialize faster hash table. */
946+
pop = riscv_opcodes_sorted;
947+
for (unsigned i = 0; i < OP_HASH_LEN; i++)
948+
{
949+
riscv_hash[i] = pop;
950+
while (pop != pop_end && OP_HASH_IDX ((*pop)->match) == i)
951+
pop++;
952+
}
953+
riscv_hash[OP_HASH_LEN] = pop_end;
954+
}
955+
885956
/* Print the RISC-V instruction at address MEMADDR in debugged memory,
886957
on using INFO. Returns length of the instruction, in bytes.
887958
BIGENDIAN must be 1 if this is big-endian code, 0 if
@@ -893,25 +964,12 @@ riscv_disassemble_insn (bfd_vma memaddr,
893964
const bfd_byte *packet,
894965
disassemble_info *info)
895966
{
967+
const struct riscv_opcode **pop, **pop_end;
896968
const struct riscv_opcode *op, *matched_op;
897-
static bool init = false;
898-
static const struct riscv_opcode *riscv_hash[OP_MASK_OP + 1];
899969
struct riscv_private_data *pd = info->private_data;
900970
int insnlen, i;
901971
bool printed;
902972

903-
#define OP_HASH_IDX(i) ((i) & (riscv_insn_length (i) == 2 ? 0x3 : OP_MASK_OP))
904-
905-
/* Build a hash table to shorten the search time. */
906-
if (! init)
907-
{
908-
for (op = riscv_opcodes; op->name; op++)
909-
if (!riscv_hash[OP_HASH_IDX (op->match)])
910-
riscv_hash[OP_HASH_IDX (op->match)] = op;
911-
912-
init = true;
913-
}
914-
915973
insnlen = riscv_insn_length (word);
916974

917975
/* RISC-V instructions are always little-endian. */
@@ -929,10 +987,11 @@ riscv_disassemble_insn (bfd_vma memaddr,
929987
info->target2 = 0;
930988

931989
matched_op = NULL;
932-
op = riscv_hash[OP_HASH_IDX (word)];
933-
934-
for (; op && op->name; op++)
990+
pop = riscv_hash[OP_HASH_IDX (word)];
991+
pop_end = riscv_hash[OP_HASH_IDX (word) + 1];
992+
for (; pop != pop_end; pop++)
935993
{
994+
op = *pop;
936995
/* Does the opcode match? */
937996
if (!(op->match_func) (op, word))
938997
continue;

0 commit comments

Comments
 (0)