aboutsummaryrefslogtreecommitdiff
path: root/gas/config/tc-mips.c
diff options
context:
space:
mode:
Diffstat (limited to 'gas/config/tc-mips.c')
-rw-r--r--gas/config/tc-mips.c431
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, &regno))
+ if (mips_parse_register (s, &regno, 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, &regno))
+
+ /* 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, &regno, &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, &regno1))
+ if (mips_parse_register (&s, &regno1, &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, &regno2))
+ if (!mips_parse_register (&s, &regno2, 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. */