diff options
author | Jim Wilson <jimw@sifive.com> | 2018-03-14 16:04:03 -0700 |
---|---|---|
committer | Jim Wilson <jimw@sifive.com> | 2018-03-14 16:04:03 -0700 |
commit | 0e35537d754f1c687850d1caccb2d78d2e418391 (patch) | |
tree | 61e8574058def02b9f33ceba1eab999e575cad8e /gas/config | |
parent | 3ae9ce5dd7d1119ca2c94c63a07b04921048ebe3 (diff) | |
download | gdb-0e35537d754f1c687850d1caccb2d78d2e418391.zip gdb-0e35537d754f1c687850d1caccb2d78d2e418391.tar.gz gdb-0e35537d754f1c687850d1caccb2d78d2e418391.tar.bz2 |
RISC-V: Add .insn support.
gas/ChangeLog
2018-03-07 Kito Cheng <kito.cheng@gmail.com>
* config/tc-riscv.c (opcode_name_list): New.
(opcode_names_hash): Likewise.
(init_opcode_names_hash): Likewise.
(opcode_name_lookup): Likewise.
(validate_riscv_insn): New argument length, and add new format
which used in .insn directive.
(md_begin): Refine hash table initialization logic into
init_opcode_hash.
(init_opcode_hash): New.
(my_getOpcodeExpression): Parse opcode name for .insn.
(riscv_ip): New argument hash, able to handle .insn directive.
(s_riscv_insn): Handler for .insn directive.
(riscv_pseudo_table): New entry for .insn.
* doc/c-riscv.texi: Add documentation for .insn directive.
* testsuite/gas/riscv/insn.d: Add testcase for .insn directive.
* testsuite/gas/riscv/insn.s: Likewise.
include/ChangeLog
2018-03-07 Kito Cheng <kito.cheng@gmail.com>
* opcode/riscv.h (OP_MASK_FUNCT3): New.
(OP_SH_FUNCT3): Likewise.
(OP_MASK_FUNCT7): Likewise.
(OP_SH_FUNCT7): Likewise.
(OP_MASK_OP2): Likewise.
(OP_SH_OP2): Likewise.
(OP_MASK_CFUNCT4): Likewise.
(OP_SH_CFUNCT4): Likewise.
(OP_MASK_CFUNCT3): Likewise.
(OP_SH_CFUNCT3): Likewise.
(riscv_insn_types): Likewise.
opcodes/ChangeLog
2018-03-07 Kito Cheng <kito.cheng@gmail.com>
* riscv-opc.c (riscv_insn_types): New.
Diffstat (limited to 'gas/config')
-rw-r--r-- | gas/config/tc-riscv.c | 440 |
1 files changed, 413 insertions, 27 deletions
diff --git a/gas/config/tc-riscv.c b/gas/config/tc-riscv.c index f1bc7f9..454f283 100644 --- a/gas/config/tc-riscv.c +++ b/gas/config/tc-riscv.c @@ -221,6 +221,9 @@ riscv_set_arch (const char *s) /* Handle of the OPCODE hash table. */ static struct hash_control *op_hash = NULL; +/* Handle of the type of .insn hash table. */ +static struct hash_control *insn_type_hash = NULL; + /* This array holds the chars that always start a comment. If the pre-processor is disabled, these aren't very useful */ const char comment_chars[] = "#"; @@ -391,6 +394,111 @@ relaxed_branch_length (fragS *fragp, asection *sec, int update) return length; } +/* Information about an opcode name, mnemonics and its value. */ +struct opcode_name_t +{ + const char *name; + unsigned int val; +}; + +/* List for all supported opcode name. */ +static const struct opcode_name_t opcode_name_list[] = +{ + {"C0", 0x0}, + {"C1", 0x1}, + {"C2", 0x2}, + + {"LOAD", 0x03}, + {"LOAD_FP", 0x07}, + {"CUSTOM_0", 0x0b}, + {"MISC_MEM", 0x0f}, + {"OP_IMM", 0x13}, + {"AUIPC", 0x17}, + {"OP_IMM_32", 0x1b}, + /* 48b 0x1f. */ + + {"STORE", 0x23}, + {"STORE_FP", 0x27}, + {"CUSTOM_1", 0x2b}, + {"AMO", 0x2f}, + {"OP", 0x33}, + {"LUI", 0x37}, + {"OP_32", 0x3b}, + /* 64b 0x3f. */ + + {"MADD", 0x43}, + {"MSUB", 0x47}, + {"NMADD", 0x4f}, + {"NMSUB", 0x4b}, + {"OP_FP", 0x53}, + /*reserved 0x57. */ + {"CUSTOM_2", 0x5b}, + /* 48b 0x5f. */ + + {"BRANCH", 0x63}, + {"JALR", 0x67}, + /*reserved 0x5b. */ + {"JAL", 0x6f}, + {"SYSTEM", 0x73}, + /*reserved 0x77. */ + {"CUSTOM_3", 0x7b}, + /* >80b 0x7f. */ + + {NULL, 0} +}; + +/* Hash table for lookup opcode name. */ +static struct hash_control *opcode_names_hash = NULL; + +/* Initialization for hash table of opcode name. */ +static void +init_opcode_names_hash (void) +{ + const char *retval; + const struct opcode_name_t *opcode; + + for (opcode = &opcode_name_list[0]; opcode->name != NULL; ++opcode) + { + retval = hash_insert (opcode_names_hash, opcode->name, (void *)opcode); + + if (retval != NULL) + as_fatal (_("internal error: can't hash `%s': %s"), + opcode->name, retval); + } +} + +/* Find `s` is a valid opcode name or not, + return the opcode name info if found. */ +static const struct opcode_name_t * +opcode_name_lookup (char **s) +{ + char *e; + char save_c; + struct opcode_name_t *o; + + /* Find end of name. */ + e = *s; + if (is_name_beginner (*e)) + ++e; + while (is_part_of_name (*e)) + ++e; + + /* Terminate name. */ + save_c = *e; + *e = '\0'; + + o = (struct opcode_name_t *) hash_find (opcode_names_hash, *s); + + /* Advance to next token if one was recognized. */ + if (o) + *s = e; + + *e = save_c; + expr_end = e; + + return o; +} + struct regname { const char *name; @@ -488,15 +596,24 @@ arg_lookup (char **s, const char *const *array, size_t size, unsigned *regnop) /* For consistency checking, verify that all bits are specified either by the match/mask part of the instruction definition, or by the - operand list. */ + operand list. + + `length` could be 0, 4 or 8, 0 for auto detection. */ static bfd_boolean -validate_riscv_insn (const struct riscv_opcode *opc) +validate_riscv_insn (const struct riscv_opcode *opc, int length) { const char *p = opc->args; char c; insn_t used_bits = opc->mask; - int insn_width = 8 * riscv_insn_length (opc->match); - insn_t required_bits = ~0ULL >> (64 - insn_width); + int insn_width; + insn_t required_bits; + + if (length == 0) + insn_width = 8 * riscv_insn_length (opc->match); + else + insn_width = 8 * length; + + required_bits = ~0ULL >> (64 - insn_width); if ((used_bits & opc->match) != (opc->match & required_bits)) { @@ -536,8 +653,22 @@ validate_riscv_insn (const struct riscv_opcode *opc) case 'V': USE_BITS (OP_MASK_CRS2, OP_SH_CRS2); break; case '<': used_bits |= ENCODE_RVC_IMM (-1U); break; case '>': used_bits |= ENCODE_RVC_IMM (-1U); break; + case '8': used_bits |= ENCODE_RVC_UIMM8 (-1U); break; + case 'S': USE_BITS (OP_MASK_CRS1S, OP_SH_CRS1S); break; case 'T': USE_BITS (OP_MASK_CRS2, OP_SH_CRS2); break; case 'D': USE_BITS (OP_MASK_CRS2S, OP_SH_CRS2S); break; + case 'F': /* funct */ + switch (c = *p++) + { + case '4': USE_BITS (OP_MASK_CFUNCT4, OP_SH_CFUNCT4); break; + case '3': USE_BITS (OP_MASK_CFUNCT3, OP_SH_CFUNCT3); break; + default: + as_bad (_("internal: bad RISC-V opcode" + " (unknown operand type `CF%c'): %s %s"), + c, opc->name, opc->args); + return FALSE; + } + break; default: as_bad (_("internal: bad RISC-V opcode (unknown operand type `C%c'): %s %s"), c, opc->name, opc->args); @@ -562,6 +693,7 @@ validate_riscv_insn (const struct riscv_opcode *opc) case 'm': USE_BITS (OP_MASK_RM, OP_SH_RM); break; case 's': USE_BITS (OP_MASK_RS1, OP_SH_RS1); break; case 't': USE_BITS (OP_MASK_RS2, OP_SH_RS2); break; + case 'r': USE_BITS (OP_MASK_RS3, OP_SH_RS3); break; case 'P': USE_BITS (OP_MASK_PRED, OP_SH_PRED); break; case 'Q': USE_BITS (OP_MASK_SUCC, OP_SH_SUCC); break; case 'o': @@ -574,6 +706,31 @@ validate_riscv_insn (const struct riscv_opcode *opc) case '[': break; case ']': break; case '0': break; + case 'F': /* funct */ + switch (c = *p++) + { + case '7': USE_BITS (OP_MASK_FUNCT7, OP_SH_FUNCT7); break; + case '3': USE_BITS (OP_MASK_FUNCT3, OP_SH_FUNCT3); break; + case '2': USE_BITS (OP_MASK_FUNCT2, OP_SH_FUNCT2); break; + default: + as_bad (_("internal: bad RISC-V opcode" + " (unknown operand type `F%c'): %s %s"), + c, opc->name, opc->args); + return FALSE; + } + break; + case 'O': /* opcode */ + switch (c = *p++) + { + case '4': USE_BITS (OP_MASK_OP, OP_SH_OP); break; + case '2': USE_BITS (OP_MASK_OP2, OP_SH_OP2); break; + default: + as_bad (_("internal: bad RISC-V opcode" + " (unknown operand type `F%c'): %s %s"), + c, opc->name, opc->args); + return FALSE; + } + break; default: as_bad (_("internal: bad RISC-V opcode " "(unknown operand type `%c'): %s %s"), @@ -597,52 +754,73 @@ struct percent_op_match bfd_reloc_code_real_type reloc; }; -/* This function is called once, at assembler startup time. It should set up - all the tables, etc. that the MD part of the assembler will need. */ - -void -md_begin (void) +/* Common hash table initialization function for + instruction and .insn directive. */ +static struct hash_control * +init_opcode_hash (const struct riscv_opcode *opcodes, + bfd_boolean insn_directive_p) { int i = 0; - unsigned long mach = xlen == 64 ? bfd_mach_riscv64 : bfd_mach_riscv32; - - if (! bfd_set_arch_mach (stdoutput, bfd_arch_riscv, mach)) - as_warn (_("Could not set architecture and machine")); - - op_hash = hash_new (); - - while (riscv_opcodes[i].name) + int length; + struct hash_control *hash = hash_new (); + while (opcodes[i].name) { - const char *name = riscv_opcodes[i].name; + const char *name = opcodes[i].name; const char *hash_error = - hash_insert (op_hash, name, (void *) &riscv_opcodes[i]); + hash_insert (hash, name, (void *) &opcodes[i]); if (hash_error) { fprintf (stderr, _("internal error: can't hash `%s': %s\n"), - riscv_opcodes[i].name, hash_error); + opcodes[i].name, hash_error); /* Probably a memory allocation problem? Give up now. */ as_fatal (_("Broken assembler. No assembly attempted.")); } do { - if (riscv_opcodes[i].pinfo != INSN_MACRO) + if (opcodes[i].pinfo != INSN_MACRO) { - if (!validate_riscv_insn (&riscv_opcodes[i])) + if (insn_directive_p) + length = ((name[0] == 'c') ? 2 : 4); + else + length = 0; /* Let assembler determine the length. */ + if (!validate_riscv_insn (&opcodes[i], length)) as_fatal (_("Broken assembler. No assembly attempted.")); } + else + gas_assert (!insn_directive_p); ++i; } - while (riscv_opcodes[i].name && !strcmp (riscv_opcodes[i].name, name)); + while (opcodes[i].name && !strcmp (opcodes[i].name, name)); } + return hash; +} + +/* This function is called once, at assembler startup time. It should set up + all the tables, etc. that the MD part of the assembler will need. */ + +void +md_begin (void) +{ + unsigned long mach = xlen == 64 ? bfd_mach_riscv64 : bfd_mach_riscv32; + + if (! bfd_set_arch_mach (stdoutput, bfd_arch_riscv, mach)) + as_warn (_("Could not set architecture and machine")); + + op_hash = init_opcode_hash (riscv_opcodes, FALSE); + insn_type_hash = init_opcode_hash (riscv_insn_types, TRUE); + reg_names_hash = hash_new (); hash_reg_names (RCLASS_GPR, riscv_gpr_names_numeric, NGPR); hash_reg_names (RCLASS_GPR, riscv_gpr_names_abi, NGPR); hash_reg_names (RCLASS_FPR, riscv_fpr_names_numeric, NFPR); hash_reg_names (RCLASS_FPR, riscv_fpr_names_abi, NFPR); + opcode_names_hash = hash_new (); + init_opcode_names_hash (); + #define DECLARE_CSR(name, num) hash_reg_name (RCLASS_CSR, #name, num); #define DECLARE_CSR_ALIAS(name, num) DECLARE_CSR(name, num); #include "opcode/riscv-opc.h" @@ -1186,6 +1364,23 @@ my_getSmallExpression (expressionS *ep, bfd_reloc_code_real_type *reloc, return reloc_index; } +/* Parse opcode name, could be an mnemonics or number. */ +static size_t +my_getOpcodeExpression (expressionS *ep, bfd_reloc_code_real_type *reloc, + char *str, const struct percent_op_match *percent_op) +{ + const struct opcode_name_t *o = opcode_name_lookup (&str); + + if (o != NULL) + { + ep->X_op = O_constant; + ep->X_add_number = o->val; + return 0; + } + + return my_getSmallExpression (ep, reloc, str, percent_op); +} + /* Detect and handle implicitly zero load-store offsets. For example, "lw t0, (t1)" is shorthand for "lw t0, 0(t1)". Return TRUE iff such an implicit offset was detected. */ @@ -1211,7 +1406,7 @@ riscv_handle_implicit_zero_offset (expressionS *ep, const char *s) static const char * riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr, - bfd_reloc_code_real_type *imm_reloc) + bfd_reloc_code_real_type *imm_reloc, struct hash_control *hash) { char *s; const char *args; @@ -1234,7 +1429,7 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr, break; } - insn = (struct riscv_opcode *) hash_find (op_hash, str); + insn = (struct riscv_opcode *) hash_find (hash, str); argsStart = s; for ( ; insn && insn->name && strcmp (insn->name, str) == 0; insn++) @@ -1259,7 +1454,12 @@ riscv_ip (char *str, struct riscv_cl_insn *ip, expressionS *imm_expr, { if (!insn->match_func (insn, ip->insn_opcode)) break; - if (riscv_insn_length (insn->match) == 2 && !riscv_opts.rvc) + + /* For .insn, insn->match and insn->mask are 0. */ + if (riscv_insn_length ((insn->match == 0 && insn->mask == 0) + ? ip->insn_opcode + : insn->match) == 2 + && !riscv_opts.rvc) break; } if (*s != '\0') @@ -1328,6 +1528,15 @@ rvc_imm_done: break; ip->insn_opcode |= ENCODE_RVC_IMM (imm_expr->X_add_number); goto rvc_imm_done; + case '8': + if (my_getSmallExpression (imm_expr, imm_reloc, s, p) + || imm_expr->X_op != O_constant + || !VALID_RVC_UIMM8 (imm_expr->X_add_number) + || imm_expr->X_add_number < 0 + || imm_expr->X_add_number >= 256) + break; + ip->insn_opcode |= ENCODE_RVC_UIMM8 (imm_expr->X_add_number); + goto rvc_imm_done; case 'i': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) || imm_expr->X_op != O_constant @@ -1457,6 +1666,12 @@ rvc_lui: goto branch; case 'a': goto jump; + case 'S': /* Floating-point RS1 x8-x15. */ + if (!reg_lookup (&s, RCLASS_FPR, ®no) + || !(regno >= 8 && regno <= 15)) + break; + INSERT_OPERAND (CRS1S, *ip, regno % 8); + continue; case 'D': /* Floating-point RS2 x8-x15. */ if (!reg_lookup (&s, RCLASS_FPR, ®no) || !(regno >= 8 && regno <= 15)) @@ -1468,6 +1683,45 @@ rvc_lui: break; INSERT_OPERAND (CRS2, *ip, regno); continue; + case 'F': + switch (*++args) + { + case '4': + if (my_getSmallExpression (imm_expr, imm_reloc, s, p) + || imm_expr->X_op != O_constant + || imm_expr->X_add_number < 0 + || imm_expr->X_add_number >= 16) + { + as_bad (_("bad value for funct4 field, " + "value must be 0...15")); + break; + } + + INSERT_OPERAND (CFUNCT4, *ip, imm_expr->X_add_number); + imm_expr->X_op = O_absent; + s = expr_end; + continue; + case '3': + if (my_getSmallExpression (imm_expr, imm_reloc, s, p) + || imm_expr->X_op != O_constant + || imm_expr->X_add_number < 0 + || imm_expr->X_add_number >= 8) + { + as_bad (_("bad value for funct3 field, " + "value must be 0...7")); + break; + } + INSERT_OPERAND (CFUNCT3, *ip, imm_expr->X_add_number); + imm_expr->X_op = O_absent; + s = expr_end; + continue; + default: + as_bad (_("bad compressed FUNCT field" + " specifier 'CF%c'\n"), + *args); + } + break; + default: as_bad (_("bad RVC field specifier 'C%c'\n"), *args); } @@ -1561,6 +1815,7 @@ rvc_lui: case 'd': /* Destination register. */ case 's': /* Source register. */ case 't': /* Target register. */ + case 'r': /* rs3. */ if (reg_lookup (&s, RCLASS_GPR, ®no)) { c = *args; @@ -1580,6 +1835,9 @@ rvc_lui: case 't': INSERT_OPERAND (RS2, *ip, regno); break; + case 'r': + INSERT_OPERAND (RS3, *ip, regno); + break; } continue; } @@ -1712,6 +1970,99 @@ jump: else *imm_reloc = BFD_RELOC_RISCV_CALL; continue; + case 'O': + switch (*++args) + { + case '4': + if (my_getOpcodeExpression (imm_expr, imm_reloc, s, p) + || imm_expr->X_op != O_constant + || imm_expr->X_add_number < 0 + || imm_expr->X_add_number >= 128 + || (imm_expr->X_add_number & 0x3) != 3) + { + as_bad (_("bad value for opcode field, " + "value must be 0...127 and " + "lower 2 bits must be 0x3")); + break; + } + + INSERT_OPERAND (OP, *ip, imm_expr->X_add_number); + imm_expr->X_op = O_absent; + s = expr_end; + continue; + case '2': + if (my_getOpcodeExpression (imm_expr, imm_reloc, s, p) + || imm_expr->X_op != O_constant + || imm_expr->X_add_number < 0 + || imm_expr->X_add_number >= 3) + { + as_bad (_("bad value for opcode field, " + "value must be 0...2")); + break; + } + + INSERT_OPERAND (OP2, *ip, imm_expr->X_add_number); + imm_expr->X_op = O_absent; + s = expr_end; + continue; + default: + as_bad (_("bad Opcode field specifier 'O%c'\n"), *args); + } + break; + + case 'F': + switch (*++args) + { + case '7': + if (my_getSmallExpression (imm_expr, imm_reloc, s, p) + || imm_expr->X_op != O_constant + || imm_expr->X_add_number < 0 + || imm_expr->X_add_number >= 128) + { + as_bad (_("bad value for funct7 field, " + "value must be 0...127")); + break; + } + + INSERT_OPERAND (FUNCT7, *ip, imm_expr->X_add_number); + imm_expr->X_op = O_absent; + s = expr_end; + continue; + case '3': + if (my_getSmallExpression (imm_expr, imm_reloc, s, p) + || imm_expr->X_op != O_constant + || imm_expr->X_add_number < 0 + || imm_expr->X_add_number >= 8) + { + as_bad (_("bad value for funct3 field, " + "value must be 0...7")); + break; + } + + INSERT_OPERAND (FUNCT3, *ip, imm_expr->X_add_number); + imm_expr->X_op = O_absent; + s = expr_end; + continue; + case '2': + if (my_getSmallExpression (imm_expr, imm_reloc, s, p) + || imm_expr->X_op != O_constant + || imm_expr->X_add_number < 0 + || imm_expr->X_add_number >= 4) + { + as_bad (_("bad value for funct2 field, " + "value must be 0...3")); + break; + } + + INSERT_OPERAND (FUNCT2, *ip, imm_expr->X_add_number); + imm_expr->X_op = O_absent; + s = expr_end; + continue; + + default: + as_bad (_("bad FUNCT field specifier 'F%c'\n"), *args); + } + break; case 'z': if (my_getSmallExpression (imm_expr, imm_reloc, s, p) @@ -1746,7 +2097,7 @@ md_assemble (char *str) expressionS imm_expr; bfd_reloc_code_real_type imm_reloc = BFD_RELOC_UNUSED; - const char *error = riscv_ip (str, &insn, &imm_expr, &imm_reloc); + const char *error = riscv_ip (str, &insn, &imm_expr, &imm_reloc, op_hash); if (error) { @@ -2614,6 +2965,40 @@ s_riscv_leb128 (int sign) return s_leb128 (sign); } +/* Parse the .insn directive. */ + +static void +s_riscv_insn (int x ATTRIBUTE_UNUSED) +{ + char *str = input_line_pointer; + struct riscv_cl_insn insn; + expressionS imm_expr; + bfd_reloc_code_real_type imm_reloc = BFD_RELOC_UNUSED; + char save_c; + + while (!is_end_of_line[(unsigned char) *input_line_pointer]) + ++input_line_pointer; + + save_c = *input_line_pointer; + *input_line_pointer = '\0'; + + const char *error = riscv_ip (str, &insn, &imm_expr, + &imm_reloc, insn_type_hash); + + if (error) + { + as_bad ("%s `%s'", error, str); + } + else + { + gas_assert (insn.insn_mo->pinfo != INSN_MACRO); + append_insn (&insn, &imm_expr, imm_reloc); + } + + *input_line_pointer = save_c; + demand_empty_rest_of_line (); +} + /* Pseudo-op table. */ static const pseudo_typeS riscv_pseudo_table[] = @@ -2628,6 +3013,7 @@ static const pseudo_typeS riscv_pseudo_table[] = {"bss", s_bss, 0}, {"uleb128", s_riscv_leb128, 0}, {"sleb128", s_riscv_leb128, 1}, + {"insn", s_riscv_insn, 0}, { NULL, NULL, 0 }, }; |