diff options
Diffstat (limited to 'gas/config/tc-mips.c')
-rw-r--r-- | gas/config/tc-mips.c | 431 |
1 files changed, 354 insertions, 77 deletions
diff --git a/gas/config/tc-mips.c b/gas/config/tc-mips.c index 655e669..4255f93 100644 --- a/gas/config/tc-mips.c +++ b/gas/config/tc-mips.c @@ -690,7 +690,7 @@ static int mips_debug = 0; static struct mips_cl_insn history[1 + MAX_NOPS]; /* Arrays of operands for each instruction. */ -#define MAX_OPERANDS 5 +#define MAX_OPERANDS 6 struct mips_operand_array { const struct mips_operand *operand[MAX_OPERANDS]; }; @@ -2206,18 +2206,24 @@ struct regname { unsigned int num; }; -#define RTYPE_MASK 0x1ff00 -#define RTYPE_NUM 0x00100 -#define RTYPE_FPU 0x00200 -#define RTYPE_FCC 0x00400 -#define RTYPE_VEC 0x00800 -#define RTYPE_GP 0x01000 -#define RTYPE_CP0 0x02000 -#define RTYPE_PC 0x04000 -#define RTYPE_ACC 0x08000 -#define RTYPE_CCC 0x10000 -#define RNUM_MASK 0x000ff -#define RWARN 0x80000 +#define RNUM_MASK 0x00000ff +#define RTYPE_MASK 0x0efff00 +#define RTYPE_NUM 0x0000100 +#define RTYPE_FPU 0x0000200 +#define RTYPE_FCC 0x0000400 +#define RTYPE_VEC 0x0000800 +#define RTYPE_GP 0x0001000 +#define RTYPE_CP0 0x0002000 +#define RTYPE_PC 0x0004000 +#define RTYPE_ACC 0x0008000 +#define RTYPE_CCC 0x0010000 +#define RTYPE_VI 0x0020000 +#define RTYPE_VF 0x0040000 +#define RTYPE_R5900_I 0x0080000 +#define RTYPE_R5900_Q 0x0100000 +#define RTYPE_R5900_R 0x0200000 +#define RTYPE_R5900_ACC 0x0400000 +#define RWARN 0x8000000 #define GENERIC_REGISTER_NUMBERS \ {"$0", RTYPE_NUM | 0}, \ @@ -2403,6 +2409,18 @@ struct regname { {"$v30", RTYPE_VEC | 30}, \ {"$v31", RTYPE_VEC | 31} +#define R5900_I_NAMES \ + {"$I", RTYPE_R5900_I | 0} + +#define R5900_Q_NAMES \ + {"$Q", RTYPE_R5900_Q | 0} + +#define R5900_R_NAMES \ + {"$R", RTYPE_R5900_R | 0} + +#define R5900_ACC_NAMES \ + {"$ACC", RTYPE_R5900_ACC | 0 } + #define MIPS_DSP_ACCUMULATOR_NAMES \ {"$ac0", RTYPE_ACC | 0}, \ {"$ac1", RTYPE_ACC | 1}, \ @@ -2423,6 +2441,10 @@ static const struct regname reg_names[] = { MIPS16_SPECIAL_REGISTER_NAMES, MDMX_VECTOR_REGISTER_NAMES, + R5900_I_NAMES, + R5900_Q_NAMES, + R5900_R_NAMES, + R5900_ACC_NAMES, MIPS_DSP_ACCUMULATOR_NAMES, {0, 0} }; @@ -2450,24 +2472,14 @@ mips_prefer_vec_regno (unsigned int symval) return symval; } -/* Return true if the string at *SPTR is a valid register name. If so, - move *SPTR past the register and store the register's symbol value - in *SYMVAL. This symbol value includes the register number - (RNUM_MASK) and register type (RTYPE_MASK). */ +/* Return true if string [S, E) is a valid register name, storing its + symbol value in *SYMVAL_PTR if so. */ static bfd_boolean -mips_parse_register (char **sptr, unsigned int *symval) +mips_parse_register_1 (char *s, char *e, unsigned int *symval_ptr) { - symbolS *symbol; - char *s, *e; char save_c; - - /* Find end of name. */ - s = e = *sptr; - if (is_name_beginner (*e)) - ++e; - while (is_part_of_name (*e)) - ++e; + symbolS *symbol; /* Terminate name. */ save_c = *e; @@ -2480,8 +2492,63 @@ mips_parse_register (char **sptr, unsigned int *symval) if (!symbol || S_GET_SEGMENT (symbol) != reg_section) return FALSE; + *symval_ptr = S_GET_VALUE (symbol); + return TRUE; +} + +/* Return true if the string at *SPTR is a valid register name. Allow it + to have a VU0-style channel suffix of the form x?y?z?w? if CHANNELS_PTR + is nonnull. + + When returning true, move *SPTR past the register, store the + register's symbol value in *SYMVAL_PTR and the channel mask in + *CHANNELS_PTR (if nonnull). The symbol value includes the register + number (RNUM_MASK) and register type (RTYPE_MASK). The channel mask + is a 4-bit value of the form XYZW and is 0 if no suffix was given. */ + +static bfd_boolean +mips_parse_register (char **sptr, unsigned int *symval_ptr, + unsigned int *channels_ptr) +{ + char *s, *e, *m; + const char *q; + unsigned int channels, symval, bit; + + /* Find end of name. */ + s = e = *sptr; + if (is_name_beginner (*e)) + ++e; + while (is_part_of_name (*e)) + ++e; + + channels = 0; + if (!mips_parse_register_1 (s, e, &symval)) + { + if (!channels_ptr) + return FALSE; + + /* Eat characters from the end of the string that are valid + channel suffixes. The preceding register must be $ACC or + end with a digit, so there is no ambiguity. */ + bit = 1; + m = e; + for (q = "wzyx"; *q; q++, bit <<= 1) + if (m > s && m[-1] == *q) + { + --m; + channels |= bit; + } + + if (channels == 0 + || !mips_parse_register_1 (s, m, &symval) + || (symval & (RTYPE_VI | RTYPE_VF | RTYPE_R5900_ACC)) == 0) + return FALSE; + } + *sptr = e; - *symval = S_GET_VALUE (symbol); + *symval_ptr = symval; + if (channels_ptr) + *channels_ptr = channels; return TRUE; } @@ -2494,7 +2561,7 @@ reg_lookup (char **s, unsigned int types, unsigned int *regnop) { unsigned int regno; - if (mips_parse_register (s, ®no)) + if (mips_parse_register (s, ®no, NULL)) { if (types & RTYPE_VEC) regno = mips_prefer_vec_regno (regno); @@ -2514,11 +2581,32 @@ reg_lookup (char **s, unsigned int types, unsigned int *regnop) return regno <= RNUM_MASK; } +/* Parse a VU0 "x?y?z?w?" channel mask at S and store the associated + mask in *CHANNELS. Return a pointer to the first unconsumed character. */ + +static char * +mips_parse_vu0_channels (char *s, unsigned int *channels) +{ + unsigned int i; + + *channels = 0; + for (i = 0; i < 4; i++) + if (*s == "xyzw"[i]) + { + *channels |= 1 << (3 - i); + ++s; + } + return s; +} + /* Token types for parsed operand lists. */ enum mips_operand_token_type { /* A plain register, e.g. $f2. */ OT_REG, + /* A 4-bit XYZW channel mask. */ + OT_CHANNELS, + /* An element of a vector, e.g. $v0[1]. */ OT_REG_ELEMENT, @@ -2535,6 +2623,9 @@ enum mips_operand_token_type { before OT_REGs. */ OT_CHAR, + /* A doubled character, either "--" or "++". */ + OT_DOUBLE_CHAR, + /* The end of the operand list. */ OT_END }; @@ -2549,6 +2640,9 @@ struct mips_operand_token /* The register symbol value for an OT_REG. */ unsigned int regno; + /* The 4-bit channel mask for an OT_CHANNEL_SUFFIX. */ + unsigned int channels; + /* The register symbol value and index for an OT_REG_ELEMENT. */ struct { unsigned int regno; @@ -2577,7 +2671,7 @@ struct mips_operand_token int length; } flt; - /* The character represented by an OT_CHAR. */ + /* The character represented by an OT_CHAR or OT_DOUBLE_CHAR. */ char ch; } u; }; @@ -2603,22 +2697,56 @@ static char * mips_parse_base_start (char *s) { struct mips_operand_token token; - unsigned int regno; + unsigned int regno, channels; + bfd_boolean decrement_p; if (*s != '(') return 0; ++s; SKIP_SPACE_TABS (s); - if (!mips_parse_register (&s, ®no)) + + /* Only match "--" as part of a base expression. In other contexts "--X" + is a double negative. */ + decrement_p = (s[0] == '-' && s[1] == '-'); + if (decrement_p) + { + s += 2; + SKIP_SPACE_TABS (s); + } + + /* Allow a channel specifier because that leads to better error messages + than treating something like "$vf0x++" as an expression. */ + if (!mips_parse_register (&s, ®no, &channels)) return 0; token.u.ch = '('; mips_add_token (&token, OT_CHAR); + if (decrement_p) + { + token.u.ch = '-'; + mips_add_token (&token, OT_DOUBLE_CHAR); + } + token.u.regno = regno; mips_add_token (&token, OT_REG); + if (channels) + { + token.u.channels = channels; + mips_add_token (&token, OT_CHANNELS); + } + + /* For consistency, only match "++" as part of base expressions too. */ + SKIP_SPACE_TABS (s); + if (s[0] == '+' && s[1] == '+') + { + s += 2; + token.u.ch = '+'; + mips_add_token (&token, OT_DOUBLE_CHAR); + } + return s; } @@ -2631,7 +2759,7 @@ static char * mips_parse_argument_token (char *s, char float_format) { char *end, *save_in, *err; - unsigned int regno1, regno2; + unsigned int regno1, regno2, channels; struct mips_operand_token token; /* First look for "($reg", since we want to treat that as an @@ -2650,15 +2778,26 @@ mips_parse_argument_token (char *s, char float_format) } /* Handle tokens that start with a register. */ - if (mips_parse_register (&s, ®no1)) + if (mips_parse_register (&s, ®no1, &channels)) { + if (channels) + { + /* A register and a VU0 channel suffix. */ + token.u.regno = regno1; + mips_add_token (&token, OT_REG); + + token.u.channels = channels; + mips_add_token (&token, OT_CHANNELS); + return s; + } + SKIP_SPACE_TABS (s); if (*s == '-') { /* A register range. */ ++s; SKIP_SPACE_TABS (s); - if (!mips_parse_register (&s, ®no2)) + if (!mips_parse_register (&s, ®no2, NULL)) { insn_error = _("Invalid register range"); return 0; @@ -2897,6 +3036,8 @@ validate_mips_insn (const struct mips_opcode *opcode, } used_bits = 0; opno = 0; + if (opcode->pinfo2 & INSN2_VU0_CHANNEL_SUFFIX) + used_bits = mips_insert_operand (&mips_vu0_channel_mask, used_bits, -1); for (s = opcode->args; *s; ++s) switch (*s) { @@ -2905,6 +3046,10 @@ validate_mips_insn (const struct mips_opcode *opcode, case ')': break; + case '#': + s++; + break; + default: if (!decode_operand) operand = decode_mips16_operand (*s, FALSE); @@ -2918,9 +3063,9 @@ validate_mips_insn (const struct mips_opcode *opcode, } gas_assert (opno < MAX_OPERANDS); operands->operand[opno] = operand; - if (operand) + if (operand && operand->type != OP_VU0_MATCH_SUFFIX) { - used_bits |= ((1 << operand->size) - 1) << operand->lsb; + used_bits = mips_insert_operand (operand, used_bits, -1); if (operand->type == OP_MDMX_IMM_REG) /* Bit 5 is the format selector (OB vs QH). The opcode table has separate entries for each format. */ @@ -3160,6 +3305,23 @@ md_begin (void) reg_names_o32[i].num, /* & RNUM_MASK, */ &zero_address_frag)); + for (i = 0; i < 32; i++) + { + char regname[7]; + + /* R5900 VU0 floating-point register. */ + regname[sizeof (rename) - 1] = 0; + snprintf (regname, sizeof (regname) - 1, "$vf%d", i); + symbol_table_insert (symbol_new (regname, reg_section, + RTYPE_VF | i, &zero_address_frag)); + + /* R5900 VU0 integer register. */ + snprintf (regname, sizeof (regname) - 1, "$vi%d", i); + symbol_table_insert (symbol_new (regname, reg_section, + RTYPE_VI | i, &zero_address_frag)); + + } + obstack_init (&mips_operand_tokens); mips_no_prev_insn (); @@ -3701,6 +3863,8 @@ operand_reg_mask (const struct mips_cl_insn *insn, case OP_REPEAT_DEST_REG: case OP_REPEAT_PREV_REG: case OP_PC: + case OP_VU0_SUFFIX: + case OP_VU0_MATCH_SUFFIX: abort (); case OP_REG: @@ -4113,6 +4277,24 @@ convert_reg_type (const struct mips_opcode *opcode, case OP_REG_HW: return RTYPE_NUM; + + case OP_REG_VI: + return RTYPE_NUM | RTYPE_VI; + + case OP_REG_VF: + return RTYPE_NUM | RTYPE_VF; + + case OP_REG_R5900_I: + return RTYPE_R5900_I; + + case OP_REG_R5900_Q: + return RTYPE_R5900_Q; + + case OP_REG_R5900_R: + return RTYPE_R5900_R; + + case OP_REG_R5900_ACC: + return RTYPE_R5900_ACC; } abort (); } @@ -5046,6 +5228,42 @@ match_float_constant (struct mips_arg_info *arg, expressionS *imm, return TRUE; } +/* OP_VU0_SUFFIX and OP_VU0_MATCH_SUFFIX matcher; MATCH_P selects between + them. */ + +static bfd_boolean +match_vu0_suffix_operand (struct mips_arg_info *arg, + const struct mips_operand *operand, + bfd_boolean match_p) +{ + unsigned int uval; + + /* The operand can be an XYZW mask or a single 2-bit channel index + (with X being 0). */ + gas_assert (operand->size == 2 || operand->size == 4); + + /* The suffix can be omitted when matching a previous 4-bit mask. */ + if (arg->token->type != OT_CHANNELS) + return operand->size == 4 && match_p; + + uval = arg->token->u.channels; + if (operand->size == 2) + { + /* Check that a single bit is set and convert it into a 2-bit index. */ + if ((uval & -uval) != uval) + return FALSE; + uval = 4 - ffs (uval); + } + + if (match_p && insn_extract_operand (arg->insn, operand) != uval) + return FALSE; + + ++arg->token; + if (!match_p) + insn_insert_operand (arg->insn, operand, uval); + return TRUE; +} + /* S is the text seen for ARG. Match it against OPERAND. Return the end of the argument text if the match is successful, otherwise return null. */ @@ -5102,6 +5320,12 @@ match_operand (struct mips_arg_info *arg, case OP_PC: return match_pc_operand (arg); + + case OP_VU0_SUFFIX: + return match_vu0_suffix_operand (arg, operand, FALSE); + + case OP_VU0_MATCH_SUFFIX: + return match_vu0_suffix_operand (arg, operand, TRUE); } abort (); } @@ -9913,7 +10137,7 @@ macro (struct mips_cl_insn *ip, char *str) goto ld_st; case M_LQC2_AB: s = "lqc2"; - fmt = "E,o(b)"; + fmt = "+7,o(b)"; /* Itbl support may require additional care here. */ coproc = 1; goto ld_st; @@ -10077,7 +10301,7 @@ macro (struct mips_cl_insn *ip, char *str) goto ld_st; case M_SQC2_AB: s = "sqc2"; - fmt = "E,o(b)"; + fmt = "+7,o(b)"; /* Itbl support may require additional care here. */ coproc = 1; goto ld_st; @@ -12078,6 +12302,74 @@ mips16_macro (struct mips_cl_insn *ip) } } +/* Look up instruction [START, START + LENGTH) in HASH. Record any extra + opcode bits in *OPCODE_EXTRA. */ + +static struct mips_opcode * +mips_lookup_insn (struct hash_control *hash, const char *start, + unsigned int length, unsigned int *opcode_extra) +{ + char *name, *dot, *p; + unsigned int mask, suffix; + size_t opend; + struct mips_opcode *insn; + + /* Make a copy of the instruction so that we can fiddle with it. */ + name = alloca (length + 1); + memcpy (name, start, length); + name[length] = '\0'; + + /* Look up the instruction as-is. */ + insn = (struct mips_opcode *) hash_find (hash, name); + if (insn && (insn->pinfo2 & INSN2_VU0_CHANNEL_SUFFIX) == 0) + return insn; + + dot = strchr (name, '.'); + if (dot && dot[1]) + { + /* Try to interpret the text after the dot as a VU0 channel suffix. */ + p = mips_parse_vu0_channels (dot + 1, &mask); + if (*p == 0 && mask != 0) + { + *dot = 0; + insn = (struct mips_opcode *) hash_find (hash, name); + *dot = '.'; + if (insn && (insn->pinfo2 & INSN2_VU0_CHANNEL_SUFFIX) != 0) + { + *opcode_extra |= mask << mips_vu0_channel_mask.lsb; + return insn; + } + } + } + + if (mips_opts.micromips) + { + /* See if there's an instruction size override suffix, + either `16' or `32', at the end of the mnemonic proper, + that defines the operation, i.e. before the first `.' + character if any. Strip it and retry. */ + opend = dot != NULL ? dot - name : length; + if (opend >= 3 && name[opend - 2] == '1' && name[opend - 1] == '6') + suffix = 2; + else if (name[opend - 2] == '3' && name[opend - 1] == '2') + suffix = 4; + else + suffix = 0; + if (suffix) + { + memcpy (name + opend - 2, name + opend, length - opend + 1); + insn = (struct mips_opcode *) hash_find (hash, name); + if (insn && (insn->pinfo2 & INSN2_VU0_CHANNEL_SUFFIX) == 0) + { + forced_insn_length = suffix; + return insn; + } + } + } + + return NULL; +} + /* Assemble an instruction into its binary format. If the instruction is a macro, set imm_expr, imm2_expr and offset_expr to the values associated with "I", "+I" and "A" operands respectively. Otherwise @@ -12095,16 +12387,14 @@ mips_ip (char *str, struct mips_cl_insn *ip) struct hash_control *hash; const char *args; char c = 0; - struct mips_opcode *insn; - long opend; - char *name; - char *dot; + struct mips_opcode *first, *insn; char format; - long end; + size_t end; const struct mips_operand *operand; struct mips_arg_info arg; struct mips_operand_token *tokens; bfd_boolean optional_reg; + unsigned int opcode_extra; insn_error = NULL; @@ -12120,50 +12410,22 @@ mips_ip (char *str, struct mips_cl_insn *ip) } forced_insn_length = 0; insn = NULL; + opcode_extra = 0; /* We first try to match an instruction up to a space or to the end. */ for (end = 0; str[end] != '\0' && !ISSPACE (str[end]); end++) continue; - /* Make a copy of the instruction so that we can fiddle with it. */ - name = alloca (end + 1); - memcpy (name, str, end); - name[end] = '\0'; - - for (;;) - { - insn = (struct mips_opcode *) hash_find (hash, name); - - if (insn != NULL || !mips_opts.micromips) - break; - if (forced_insn_length) - break; - - /* See if there's an instruction size override suffix, - either `16' or `32', at the end of the mnemonic proper, - that defines the operation, i.e. before the first `.' - character if any. Strip it and retry. */ - dot = strchr (name, '.'); - opend = dot != NULL ? dot - name : end; - if (opend < 3) - break; - if (name[opend - 2] == '1' && name[opend - 1] == '6') - forced_insn_length = 2; - else if (name[opend - 2] == '3' && name[opend - 1] == '2') - forced_insn_length = 4; - else - break; - memcpy (name + opend - 2, name + opend, end - opend + 1); - } + first = insn = mips_lookup_insn (hash, str, end, &opcode_extra); if (insn == NULL) { insn_error = _("Unrecognized opcode"); return; } - if (strcmp (name, "li.s") == 0) + if (strcmp (insn->name, "li.s") == 0) format = 'f'; - else if (strcmp (name, "li.d") == 0) + else if (strcmp (insn->name, "li.d") == 0) format = 'd'; else format = 0; @@ -12184,7 +12446,7 @@ mips_ip (char *str, struct mips_cl_insn *ip) bfd_boolean ok; bfd_boolean more_alts; - gas_assert (strcmp (insn->name, name) == 0); + gas_assert (strcmp (insn->name, first->name) == 0); ok = is_opcode_valid (insn); size_ok = is_size_valid (insn); @@ -12240,6 +12502,7 @@ mips_ip (char *str, struct mips_cl_insn *ip) offset_reloc[2] = BFD_RELOC_UNUSED; create_insn (ip, insn); + ip->insn_opcode |= opcode_extra; insn_error = NULL; memset (&arg, 0, sizeof (arg)); arg.insn = ip; @@ -12272,6 +12535,9 @@ mips_ip (char *str, struct mips_cl_insn *ip) if (strcmp (args, "(b)") == 0) args += 3; + if (args[0] == '+' && args[1] == 'K') + args += 2; + /* Fail the match if there were too few operands. */ if (*args) break; @@ -12301,6 +12567,17 @@ mips_ip (char *str, struct mips_cl_insn *ip) continue; break; } + if (*args == '#') + { + ++args; + if (arg.token->type == OT_DOUBLE_CHAR + && arg.token->u.ch == *args) + { + ++arg.token; + continue; + } + break; + } /* Handle special macro operands. Work out the properties of other operands. */ |