+/* Look up a register name and validate it for the given regtype.
+ Return the register mapping or NULL on failure. */
+static struct nios2_reg *
+nios2_parse_reg (const char *token, unsigned long regtype)
+{
+ struct nios2_reg *reg = nios2_reg_lookup (token);
+
+ if (reg == NULL)
+ {
+ as_bad (_("unknown register %s"), token);
+ return NULL;
+ }
+
+ /* Matched a register, but is it the wrong type? */
+ if (!(regtype & reg->regtype))
+ {
+ if (regtype & REG_CONTROL)
+ as_bad (_("expecting control register"));
+ else if (reg->regtype & REG_CONTROL)
+ as_bad (_("illegal use of control register"));
+ else if (reg->regtype & REG_COPROCESSOR)
+ as_bad (_("illegal use of coprocessor register"));
+ else
+ as_bad (_("invalid register %s"), token);
+ return NULL;
+ }
+
+ /* Warn for explicit use of special registers. */
+ if (reg->regtype & REG_NORMAL)
+ {
+ if (!nios2_as_options.noat && reg->index == 1)
+ as_warn (_("Register at (r1) can sometimes be corrupted by "
+ "assembler optimizations.\n"
+ "Use .set noat to turn off those optimizations "
+ "(and this warning)."));
+ if (!nios2_as_options.nobreak && reg->index == 25)
+ as_warn (_("The debugger will corrupt bt (r25).\n"
+ "If you don't need to debug this "
+ "code use .set nobreak to turn off this warning."));
+ if (!nios2_as_options.nobreak && reg->index == 30)
+ as_warn (_("The debugger will corrupt sstatus/ba (r30).\n"
+ "If you don't need to debug this "
+ "code use .set nobreak to turn off this warning."));
+ }
+
+ return reg;
+}
+
+/* This function parses a reglist for ldwm/stwm and push.n/pop.n
+ instructions, given as a brace-enclosed register list. The tokenizer
+ has replaced commas in the token with spaces.
+ The return value is a bitmask of registers in the set. It also
+ sets nios2_reglist_mask and nios2_reglist_dir to allow error checking
+ when parsing the base register. */
+
+static unsigned long nios2_reglist_mask;
+static int nios2_reglist_dir;
+
+static unsigned long
+nios2_parse_reglist (char *token, const struct nios2_opcode *op)
+{
+ unsigned long mask = 0;
+ int dir = 0;
+ unsigned long regtype = 0;
+ int last = -1;
+ const char *regname;
+
+ nios2_reglist_mask = 0;
+ nios2_reglist_dir = 0;
+
+ if (op->match == MATCH_R2_LDWM || op->match == MATCH_R2_STWM)
+ {
+ regtype = REG_LDWM;
+ dir = 0;
+ }
+ else if (op->match == MATCH_R2_PUSH_N)
+ {
+ regtype = REG_POP;
+ dir = -1;
+ }
+ else if (op->match == MATCH_R2_POP_N)
+ {
+ regtype = REG_POP;
+ dir = 1;
+ }
+ else
+ bad_opcode (op);
+
+ for (regname = strtok (token, "{ }");
+ regname;
+ regname = strtok (NULL, "{ }"))
+ {
+ int regno;
+ struct nios2_reg *reg = nios2_parse_reg (regname, regtype);
+
+ if (!reg)
+ break;
+ regno = reg->index;
+
+ /* Make sure registers are listed in proper sequence. */
+ if (last >= 0)
+ {
+ if (regno == last)
+ {
+ as_bad ("duplicate register %s\n", reg->name);
+ return 0;
+ }
+ else if (dir == 0)
+ dir = (regno < last ? -1 : 1);
+ else if ((dir > 0 && regno < last)
+ || (dir < 0 && regno > last)
+ || (op->match == MATCH_R2_PUSH_N
+ && ! ((last == 31 && regno == 28)
+ || (last == 31 && regno <= 23)
+ || (last == 28 && regno <= 23)
+ || (regno < 23 && regno == last - 1)))
+ || (op->match == MATCH_R2_POP_N
+ && ! ((regno == 31 && last == 28)
+ || (regno == 31 && last <= 23)
+ || (regno == 28 && last <= 23)
+ || (last < 23 && last == regno - 1))))
+ {
+ as_bad ("invalid register order");
+ return 0;
+ }
+ }
+
+ mask |= 1 << regno;
+ last = regno;
+ }
+
+ /* Check that all ldwm/stwm regs belong to the same set. */
+ if ((op->match == MATCH_R2_LDWM || op->match == MATCH_R2_STWM)
+ && (mask & 0x00003ffc) && (mask & 0x90ffc000))
+ {
+ as_bad ("invalid register set in reglist");
+ return 0;
+ }
+
+ /* Check that push.n/pop.n regs include RA. */
+ if ((op->match == MATCH_R2_PUSH_N || op->match == MATCH_R2_POP_N)
+ && ((mask & 0x80000000) == 0))
+ {
+ as_bad ("reglist must include ra (r31)");
+ return 0;
+ }
+
+ /* Check that there is at least one register in the set. */
+ if (!mask)
+ {
+ as_bad ("reglist must include at least one register");
+ return 0;
+ }
+
+ /* OK, reglist passed validation. */
+ nios2_reglist_mask = mask;
+ nios2_reglist_dir = dir;
+ return mask;
+}
+
+/* This function parses the base register and options used by the ldwm/stwm
+ instructions. Returns the base register and sets the option arguments
+ accordingly. On failure, returns NULL. */
+static struct nios2_reg *
+nios2_parse_base_register (char *str, int *direction, int *writeback, int *ret)
+{
+ char *regname;
+ struct nios2_reg *reg;
+
+ *direction = 0;
+ *writeback = 0;
+ *ret = 0;
+
+ /* Check for --. */
+ if (strncmp (str, "--", 2) == 0)
+ {
+ str += 2;
+ *direction -= 1;
+ }
+
+ /* Extract the base register. */
+ if (*str != '(')
+ {
+ as_bad ("expected '(' before base register");
+ return NULL;
+ }
+ str++;
+ regname = str;
+ str = strchr (str, ')');
+ if (!str)
+ {
+ as_bad ("expected ')' after base register");
+ return NULL;
+ }
+ *str = '\0';
+ str++;
+ reg = nios2_parse_reg (regname, REG_NORMAL);
+ if (reg == NULL)
+ return NULL;
+
+ /* Check for ++. */
+ if (strncmp (str, "++", 2) == 0)
+ {
+ str += 2;
+ *direction += 1;
+ }
+
+ /* Ensure that either -- or ++ is specified, but not both. */
+ if (*direction == 0)
+ {
+ as_bad ("invalid base register syntax");
+ return NULL;;
+ }
+
+ /* Check for options. The tokenizer has replaced commas with spaces. */
+ while (*str)
+ {
+ while (*str == ' ')
+ str++;
+ if (strncmp (str, "writeback", 9) == 0)
+ {
+ *writeback = 1;
+ str += 9;
+ }
+ else if (strncmp (str, "ret", 3) == 0)
+ {
+ *ret = 1;
+ str += 3;
+ }
+ else if (*str)
+ {
+ as_bad ("invalid option syntax");
+ return NULL;
+ }
+ }
+
+ return reg;
+}
+
+