gas/
authorRichard Sandiford <rdsandiford@googlemail.com>
Sun, 14 Jul 2013 13:51:52 +0000 (13:51 +0000)
committerRichard Sandiford <rdsandiford@googlemail.com>
Sun, 14 Jul 2013 13:51:52 +0000 (13:51 +0000)
* config/tc-mips.c (mips32_to_16_reg_map): Delete.
(match_entry_exit_operand): New function.
(match_save_restore_list_operand): Likewise.
(match_operand): Use them.
(check_absolute_expr): Delete.
(mips16_ip): Rewrite main parsing loop to use mips_operands.

gas/ChangeLog
gas/config/tc-mips.c

index ceeb1b2..9b36c85 100644 (file)
@@ -1,5 +1,14 @@
 2013-07-14  Richard Sandiford  <rdsandiford@googlemail.com>
 
+       * config/tc-mips.c (mips32_to_16_reg_map): Delete.
+       (match_entry_exit_operand): New function.
+       (match_save_restore_list_operand): Likewise.
+       (match_operand): Use them.
+       (check_absolute_expr): Delete.
+       (mips16_ip): Rewrite main parsing loop to use mips_operands.
+
+2013-07-14  Richard Sandiford  <rdsandiford@googlemail.com>
+
        * config/tc-mips.c: Enable functions commented out in previous patch.
        (SKIP_SPACE_TABS): Move further up file.
        (mips32_to_micromips_reg_b_map, mips32_to_micromips_reg_c_map)
index c7e0edc..ff86165 100644 (file)
@@ -755,18 +755,6 @@ static struct mips_hi_fixup *mips_hi_fixup_list;
 
 static fragS *prev_reloc_op_frag;
 
-/* Map normal MIPS register numbers to mips16 register numbers.  */
-
-#define X ILLEGAL_REG
-static const int mips32_to_16_reg_map[] =
-{
-  X, X, 2, 3, 4, 5, 6, 7,
-  X, X, X, X, X, X, X, X,
-  0, 1, X, X, X, X, X, X,
-  X, X, X, X, X, X, X, X
-};
-#undef X
-
 /* Map mips16 register numbers to normal MIPS register numbers.  */
 
 static const unsigned int mips16_to_32_reg_map[] =
@@ -4280,6 +4268,236 @@ match_lwm_swm_list_operand (struct mips_arg_info *arg,
   return s;
 }
 
+/* OP_ENTRY_EXIT_LIST matcher.  */
+
+static char *
+match_entry_exit_operand (struct mips_arg_info *arg,
+                         const struct mips_operand *operand, char *s)
+{
+  unsigned int mask;
+  bfd_boolean is_exit;
+
+  /* The format is the same for both ENTRY and EXIT, but the constraints
+     are different.  */
+  is_exit = strcmp (arg->insn->insn_mo->name, "exit") == 0;
+  mask = (is_exit ? 7 << 3 : 0);
+  for (;;)
+    {
+      unsigned int regno1, regno2;
+      bfd_boolean is_freg;
+
+      if (reg_lookup (&s, RTYPE_GP | RTYPE_NUM, &regno1))
+       is_freg = FALSE;
+      else if (reg_lookup (&s, RTYPE_FPU, &regno1))
+       is_freg = TRUE;
+      else
+       return 0;
+
+      SKIP_SPACE_TABS (s);
+      if (*s == '-')
+       {
+         ++s;
+         SKIP_SPACE_TABS (s);
+         if (!reg_lookup (&s, (is_freg ? RTYPE_FPU
+                               : RTYPE_GP | RTYPE_NUM), &regno2))
+           return 0;
+         SKIP_SPACE_TABS (s);
+       }
+      else
+       regno2 = regno1;
+
+      if (is_exit && is_freg && regno1 == 0 && regno2 < 2)
+       {
+         mask &= ~(7 << 3);
+         mask |= (5 + regno2) << 3;
+       }
+      else if (!is_exit && regno1 == 4 && regno2 >= 4 && regno2 <= 7)
+       mask |= (regno2 - 3) << 3;
+      else if (regno1 == 16 && regno2 >= 16 && regno2 <= 17)
+       mask |= (regno2 - 15) << 1;
+      else if (regno1 == RA && regno2 == RA)
+       mask |= 1;
+      else
+       return 0;
+
+      if (!*s)
+       break;
+      if (*s != ',')
+       return 0;
+      arg->argnum += 1;
+      ++s;
+      SKIP_SPACE_TABS (s);
+    }
+  insn_insert_operand (arg->insn, operand, mask);
+  return s;
+}
+
+/* OP_SAVE_RESTORE_LIST matcher.  */
+
+static char *
+match_save_restore_list_operand (struct mips_arg_info *arg, char *s)
+{
+  unsigned int opcode, args, statics, sregs;
+  unsigned int num_frame_sizes, num_args, num_statics, num_sregs;
+  expressionS value;
+  offsetT frame_size;
+  const char *error;
+
+  error = 0;
+  opcode = arg->insn->insn_opcode;
+  frame_size = 0;
+  num_frame_sizes = 0;
+  args = 0;
+  statics = 0;
+  sregs = 0;
+  for (;;)
+    {
+      unsigned int regno1, regno2;
+
+      my_getExpression (&value, s);
+      if (value.X_op == O_constant)
+       {
+         /* Handle the frame size.  */
+         num_frame_sizes += 1;
+         frame_size = value.X_add_number;
+         s = expr_end;
+         SKIP_SPACE_TABS (s);
+       }
+      else
+       {
+         if (!reg_lookup (&s, RTYPE_GP | RTYPE_NUM, &regno1))
+           return 0;
+
+         SKIP_SPACE_TABS (s);
+         if (*s == '-')
+           {
+             ++s;
+             SKIP_SPACE_TABS (s);
+             if (!reg_lookup (&s, RTYPE_GP | RTYPE_NUM, &regno2)
+                 || regno2 < regno1)
+               return 0;
+             SKIP_SPACE_TABS (s);
+           }
+         else
+           regno2 = regno1;
+
+         while (regno1 <= regno2)
+           {
+             if (regno1 >= 4 && regno1 <= 7)
+               {
+                 if (num_frame_sizes == 0)
+                   /* args $a0-$a3 */
+                   args |= 1 << (regno1 - 4);
+                 else
+                   /* statics $a0-$a3 */
+                   statics |= 1 << (regno1 - 4);
+               }
+             else if (regno1 >= 16 && regno1 <= 23)
+               /* $s0-$s7 */
+               sregs |= 1 << (regno1 - 16);
+             else if (regno1 == 30)
+               /* $s8 */
+               sregs |= 1 << 8;
+             else if (regno1 == 31)
+               /* Add $ra to insn.  */
+               opcode |= 0x40;
+             else
+               return 0;
+             regno1 += 1;
+             if (regno1 == 24)
+               regno1 = 30;
+           }
+       }
+      if (!*s)
+       break;
+      if (*s != ',')
+       return 0;
+      arg->argnum += 1;
+      ++s;
+      SKIP_SPACE_TABS (s);
+    }
+
+  /* Encode args/statics combination.  */
+  if (args & statics)
+    return 0;
+  else if (args == 0xf)
+    /* All $a0-$a3 are args.  */
+    opcode |= MIPS16_ALL_ARGS << 16;
+  else if (statics == 0xf)
+    /* All $a0-$a3 are statics.  */
+    opcode |= MIPS16_ALL_STATICS << 16;
+  else
+    {
+      /* Count arg registers.  */
+      num_args = 0;
+      while (args & 0x1)
+       {
+         args >>= 1;
+         num_args += 1;
+       }
+      if (args != 0)
+       return 0;
+
+      /* Count static registers.  */
+      num_statics = 0;
+      while (statics & 0x8)
+       {
+         statics = (statics << 1) & 0xf;
+         num_statics += 1;
+       }
+      if (statics != 0)
+       return 0;
+
+      /* Encode args/statics.  */
+      opcode |= ((num_args << 2) | num_statics) << 16;
+    }
+
+  /* Encode $s0/$s1.  */
+  if (sregs & (1 << 0))                /* $s0 */
+    opcode |= 0x20;
+  if (sregs & (1 << 1))                /* $s1 */
+    opcode |= 0x10;
+  sregs >>= 2;
+
+  /* Encode $s2-$s8. */
+  num_sregs = 0;
+  while (sregs & 1)
+    {
+      sregs >>= 1;
+      num_sregs += 1;
+    }
+  if (sregs != 0)
+    return 0;
+  opcode |= num_sregs << 24;
+
+  /* Encode frame size.  */
+  if (num_frame_sizes == 0)
+    error = _("Missing frame size");
+  else if (num_frame_sizes > 1)
+    error = _("Frame size specified twice");
+  else if ((frame_size & 7) != 0 || frame_size < 0 || frame_size > 0xff * 8)
+    error = _("Invalid frame size");
+  else if (frame_size != 128 || (opcode >> 16) != 0)
+    {
+      frame_size /= 8;
+      opcode |= (((frame_size & 0xf0) << 16)
+                | (frame_size & 0x0f));
+    }
+
+  if (error)
+    {
+      if (arg->soft_match)
+       return 0;
+      as_bad (error);
+    }
+
+  /* Finally build the instruction.  */
+  if ((opcode >> 16) != 0 || frame_size == 0)
+    opcode |= MIPS16_EXTEND;
+  arg->insn->insn_opcode = opcode;
+  return s;
+}
+
 /* OP_MDMX_IMM_REG matcher.  */
 
 static char *
@@ -4454,8 +4672,10 @@ match_operand (struct mips_arg_info *arg,
       return match_lwm_swm_list_operand (arg, operand, s);
 
     case OP_ENTRY_EXIT_LIST:
+      return match_entry_exit_operand (arg, operand, s);
+
     case OP_SAVE_RESTORE_LIST:
-      abort ();
+      return match_save_restore_list_operand (arg, s);
 
     case OP_MDMX_IMM_REG:
       return match_mdmx_imm_reg_operand (arg, operand, s);
@@ -6573,21 +6793,6 @@ set_at (int reg, int unsignedp)
     }
 }
 
-/* Warn if an expression is not a constant.  */
-
-static void
-check_absolute_expr (struct mips_cl_insn *ip, expressionS *ex)
-{
-  if (ex->X_op == O_big)
-    as_bad (_("unsupported large constant"));
-  else if (ex->X_op != O_constant)
-    as_bad (_("Instruction %s requires absolute expression"),
-           ip->insn_mo->name);
-
-  if (HAVE_32BIT_GPRS)
-    normalize_constant_expr (ex);
-}
-
 /* Count the leading zeroes by performing a binary chop. This is a
    bulky bit of source, but performance is a LOT better for the
    majority of values than a simple loop to count the bits:
@@ -12064,10 +12269,10 @@ mips16_ip (char *str, struct mips_cl_insn *ip)
   const char *args;
   struct mips_opcode *insn;
   char *argsstart;
-  unsigned int regno;
-  unsigned int lastregno = 0;
-  char *s_reset;
   size_t i;
+  const struct mips_operand *operand;
+  const struct mips_operand *ext_operand;
+  struct mips_arg_info arg;
 
   insn_error = NULL;
 
@@ -12118,15 +12323,17 @@ mips16_ip (char *str, struct mips_cl_insn *ip)
   for (;;)
     {
       bfd_boolean ok;
+      bfd_boolean more_alts;
       char relax_char;
 
       gas_assert (strcmp (insn->name, str) == 0);
 
       ok = is_opcode_valid_16 (insn);
+      more_alts = (insn + 1 < &mips16_opcodes[bfd_mips16_num_opcodes]
+                  && strcmp (insn[0].name, insn[1].name) == 0);
       if (! ok)
        {
-         if (insn + 1 < &mips16_opcodes[bfd_mips16_num_opcodes]
-             && strcmp (insn->name, insn[1].name) == 0)
+         if (more_alts)
            {
              ++insn;
              continue;
@@ -12154,292 +12361,103 @@ mips16_ip (char *str, struct mips_cl_insn *ip)
       offset_reloc[1] = BFD_RELOC_UNUSED;
       offset_reloc[2] = BFD_RELOC_UNUSED;
       relax_char = 0;
+
+      memset (&arg, 0, sizeof (arg));
+      arg.insn = ip;
+      arg.argnum = 1;
+      arg.last_regno = ILLEGAL_REG;
+      arg.dest_regno = ILLEGAL_REG;
+      arg.soft_match = more_alts;
+      relax_char = 0;
       for (args = insn->args; 1; ++args)
        {
          int c;
 
-         if (*s == ' ')
-           ++s;
-
-         /* In this switch statement we call break if we did not find
-             a match, continue if we did find a match, or return if we
-             are done.  */
-
-         c = *args;
-         switch (c)
+         SKIP_SPACE_TABS (s);
+         if (*s == 0)
            {
-           case '\0':
-             if (*s == '\0')
-               {
-                 offsetT value;
+             offsetT value;
 
-                 /* Stuff the immediate value in now, if we can.  */
-                 if (insn->pinfo == INSN_MACRO)
-                   {
-                     gas_assert (relax_char == 0);
-                     gas_assert (*offset_reloc == BFD_RELOC_UNUSED);
-                   }
-                 else if (relax_char
-                          && offset_expr.X_op == O_constant
-                          && calculate_reloc (*offset_reloc,
-                                              offset_expr.X_add_number,
-                                              &value))
-                   {
-                     mips16_immed (NULL, 0, relax_char, *offset_reloc, value,
-                                   forced_insn_length, &ip->insn_opcode);
-                     offset_expr.X_op = O_absent;
-                     *offset_reloc = BFD_RELOC_UNUSED;
-                   }
-                 else if (relax_char && *offset_reloc != BFD_RELOC_UNUSED)
-                   {
-                     if (forced_insn_length == 2)
-                       as_bad (_("invalid unextended operand value"));
-                     forced_insn_length = 4;
-                     ip->insn_opcode |= MIPS16_EXTEND;
-                   }
-                 else if (relax_char)
-                   *offset_reloc = (int) BFD_RELOC_UNUSED + relax_char;
-
-                 return;
-               }
-             break;
-
-           case ',':
-             if (*s++ == c)
-               continue;
-             s--;
-             switch (*++args)
-               {
-               case 'v':
-                 MIPS16_INSERT_OPERAND (RX, *ip, lastregno);
-                 continue;
-               case 'w':
-                 MIPS16_INSERT_OPERAND (RY, *ip, lastregno);
-                 continue;
-               }
-             break;
+             /* Handle unary instructions in which only one operand is given.
+                The source is then the same as the destination.  */
+             if (arg.opnum == 1 && *args == ',')
+               switch (args[1])
+                 {
+                 case 'v':
+                 case 'w':
+                   arg.argnum = 1;
+                   s = argsstart;
+                   continue;
+                 }
 
-           case '(':
-           case ')':
-             if (*s++ == c)
-               continue;
-             break;
+             /* Fail the match if there were too few operands.  */
+             if (*args)
+               break;
 
-           case 'v':
-           case 'w':
-             if (s[0] != '$')
+             /* Successful match.  Stuff the immediate value in now, if
+                we can.  */
+             if (insn->pinfo == INSN_MACRO)
                {
-                 if (c == 'v')
-                   MIPS16_INSERT_OPERAND (RX, *ip, lastregno);
-                 else
-                   MIPS16_INSERT_OPERAND (RY, *ip, lastregno);
-                 ++args;
-                 continue;
+                 gas_assert (relax_char == 0);
+                 gas_assert (*offset_reloc == BFD_RELOC_UNUSED);
                }
-             /* Fall through.  */
-           case 'x':
-           case 'y':
-           case 'z':
-           case 'Z':
-           case '0':
-           case 'S':
-           case 'R':
-           case 'X':
-           case 'Y':
-             s_reset = s;
-             if (!reg_lookup (&s, RTYPE_NUM | RTYPE_GP, &regno))
+             else if (relax_char
+                      && offset_expr.X_op == O_constant
+                      && calculate_reloc (*offset_reloc,
+                                          offset_expr.X_add_number,
+                                          &value))
                {
-                 if (c == 'v' || c == 'w')
-                   {
-                     if (c == 'v')
-                       MIPS16_INSERT_OPERAND (RX, *ip, lastregno);
-                     else
-                       MIPS16_INSERT_OPERAND (RY, *ip, lastregno);
-                     ++args;
-                     continue;
-                   }
-                 break;
+                 mips16_immed (NULL, 0, relax_char, *offset_reloc, value,
+                               forced_insn_length, &ip->insn_opcode);
+                 offset_expr.X_op = O_absent;
+                 *offset_reloc = BFD_RELOC_UNUSED;
                }
-
-             if (*s == ' ')
-               ++s;
-             if (args[1] != *s)
+             else if (relax_char && *offset_reloc != BFD_RELOC_UNUSED)
                {
-                 if (c == 'v' || c == 'w')
-                   {
-                     regno = mips16_to_32_reg_map[lastregno];
-                     s = s_reset;
-                     ++args;
-                   }
+                 if (forced_insn_length == 2)
+                   as_bad (_("invalid unextended operand value"));
+                 forced_insn_length = 4;
+                 ip->insn_opcode |= MIPS16_EXTEND;
                }
+             else if (relax_char)
+               *offset_reloc = (int) BFD_RELOC_UNUSED + relax_char;
 
-             switch (c)
-               {
-               case 'x':
-               case 'y':
-               case 'z':
-               case 'v':
-               case 'w':
-               case 'Z':
-                 regno = mips32_to_16_reg_map[regno];
-                 break;
-
-               case '0':
-                 if (regno != 0)
-                   regno = ILLEGAL_REG;
-                 break;
-
-               case 'S':
-                 if (regno != SP)
-                   regno = ILLEGAL_REG;
-                 break;
-
-               case 'R':
-                 if (regno != RA)
-                   regno = ILLEGAL_REG;
-                 break;
-
-               case 'X':
-               case 'Y':
-                 if (regno == AT && mips_opts.at)
-                   {
-                     if (mips_opts.at == ATREG)
-                       as_warn (_("used $at without \".set noat\""));
-                     else
-                       as_warn (_("used $%u with \".set at=$%u\""),
-                                regno, mips_opts.at);
-                   }
-                 break;
+             check_completed_insn (&arg);
+             return;
+           }
 
-               default:
-                 abort ();
-               }
+         /* Fail the match if the line has too many operands.   */
+         if (*args == 0)
+           break;
 
-             if (regno == ILLEGAL_REG)
+         /* Handle characters that need to match exactly.  */
+         if (*args == '(' || *args == ')' || *args == ',')
+           {
+             if (*s != *args)
                break;
-
-             switch (c)
-               {
-               case 'x':
-               case 'v':
-                 MIPS16_INSERT_OPERAND (RX, *ip, regno);
-                 break;
-               case 'y':
-               case 'w':
-                 MIPS16_INSERT_OPERAND (RY, *ip, regno);
-                 break;
-               case 'z':
-                 MIPS16_INSERT_OPERAND (RZ, *ip, regno);
-                 break;
-               case 'Z':
-                 MIPS16_INSERT_OPERAND (MOVE32Z, *ip, regno);
-               case '0':
-               case 'S':
-               case 'R':
-                 break;
-               case 'X':
-                 MIPS16_INSERT_OPERAND (REGR32, *ip, regno);
-                 break;
-               case 'Y':
-                 regno = ((regno & 7) << 2) | ((regno & 0x18) >> 3);
-                 MIPS16_INSERT_OPERAND (REG32R, *ip, regno);
-                 break;
-               default:
-                 abort ();
-               }
-
-             lastregno = regno;
+             if (*s == ',')
+               arg.argnum += 1;
+             ++s;
              continue;
+           }
 
-           case 'P':
-             if (strncmp (s, "$pc", 3) == 0)
-               {
-                 s += 3;
-                 continue;
-               }
+         arg.opnum += 1;
+         arg.optional_reg = FALSE;
+         c = *args;
+         switch (c)
+           {
+           case 'v':
+           case 'w':
+             arg.optional_reg = (args[1] == ',');
              break;
 
-           case '5':
-           case 'H':
-           case 'W':
-           case 'D':
-           case 'j':
-           case 'V':
-           case 'C':
-           case 'U':
-           case 'k':
-           case 'K':
-             i = my_getSmallExpression (&offset_expr, offset_reloc, s);
-             if (i > 0)
-               {
-                 relax_char = c;
-                 s = expr_end;
-                 continue;
-               }
-             *offset_reloc = BFD_RELOC_UNUSED;
-             /* Fall through.  */
-           case '<':
-           case '>':
-           case '[':
-           case ']':
-           case '4':
-           case '8':
-             my_getExpression (&offset_expr, s);
-             if (offset_expr.X_op == O_register)
-               {
-                 /* What we thought was an expression turned out to
-                     be a register.  */
-
-                 if (s[0] == '(' && args[1] == '(')
-                   {
-                     /* It looks like the expression was omitted
-                        before a register indirection, which means
-                        that the expression is implicitly zero.  We
-                        still set up offset_expr, so that we handle
-                        explicit extensions correctly.  */
-                     offset_expr.X_op = O_constant;
-                     offset_expr.X_add_number = 0;
-                     relax_char = c;
-                     continue;
-                   }
-
-                 break;
-               }
-
-             /* We need to relax this instruction.  */
-             relax_char = c;
-             s = expr_end;
-             continue;
-
            case 'p':
            case 'q':
            case 'A':
            case 'B':
            case 'E':
-             /* We use offset_reloc rather than imm_reloc for the PC
-                 relative operands.  This lets macros with both
-                 immediate and address operands work correctly.  */
-             my_getExpression (&offset_expr, s);
-
-             if (offset_expr.X_op == O_register)
-               break;
-
-             /* We need to relax this instruction.  */
              relax_char = c;
-             s = expr_end;
-             continue;
-
-           case '6':           /* break code */
-             my_getExpression (&imm_expr, s);
-             check_absolute_expr (ip, &imm_expr);
-             if ((unsigned long) imm_expr.X_add_number > 63)
-               as_warn (_("Invalid value for `%s' (%lu)"),
-                        ip->insn_mo->name,
-                        (unsigned long) imm_expr.X_add_number);
-             MIPS16_INSERT_OPERAND (IMM6, *ip, imm_expr.X_add_number);
-             imm_expr.X_op = O_absent;
-             s = expr_end;
-             continue;
+             break;
 
            case 'I':
              my_getExpression (&imm_expr, s);
@@ -12451,268 +12469,73 @@ mips16_ip (char *str, struct mips_cl_insn *ip)
              s = expr_end;
              continue;
 
-           case 'a':           /* 26 bit address */
+           case 'a':
            case 'i':
-             my_getExpression (&offset_expr, s);
-             s = expr_end;
              *offset_reloc = BFD_RELOC_MIPS16_JMP;
              ip->insn_opcode <<= 16;
-             continue;
-
-           case 'l':           /* register list for entry macro */
-           case 'L':           /* register list for exit macro */
-             {
-               int mask;
-
-               if (c == 'l')
-                 mask = 0;
-               else
-                 mask = 7 << 3;
-               while (*s != '\0')
-                 {
-                   unsigned int freg, reg1, reg2;
-
-                   while (*s == ' ' || *s == ',')
-                     ++s;
-                   if (reg_lookup (&s, RTYPE_GP | RTYPE_NUM, &reg1))
-                     freg = 0;
-                   else if (reg_lookup (&s, RTYPE_FPU, &reg1))
-                     freg = 1;
-                   else
-                     {
-                       as_bad (_("can't parse register list"));
-                       break;
-                     }
-                   if (*s == ' ')
-                     ++s;
-                   if (*s != '-')
-                     reg2 = reg1;
-                   else
-                     {
-                       ++s;
-                       if (!reg_lookup (&s, freg ? RTYPE_FPU 
-                                        : (RTYPE_GP | RTYPE_NUM), &reg2))
-                         {
-                           as_bad (_("invalid register list"));
-                           break;
-                         }
-                     }
-                   if (freg && reg1 == 0 && reg2 == 0 && c == 'L')
-                     {
-                       mask &= ~ (7 << 3);
-                       mask |= 5 << 3;
-                     }
-                   else if (freg && reg1 == 0 && reg2 == 1 && c == 'L')
-                     {
-                       mask &= ~ (7 << 3);
-                       mask |= 6 << 3;
-                     }
-                   else if (reg1 == 4 && reg2 >= 4 && reg2 <= 7 && c != 'L')
-                     mask |= (reg2 - 3) << 3;
-                   else if (reg1 == 16 && reg2 >= 16 && reg2 <= 17)
-                     mask |= (reg2 - 15) << 1;
-                   else if (reg1 == RA && reg2 == RA)
-                     mask |= 1;
-                   else
-                     {
-                       as_bad (_("invalid register list"));
-                       break;
-                     }
-                 }
-               /* The mask is filled in in the opcode table for the
-                   benefit of the disassembler.  We remove it before
-                   applying the actual mask.  */
-               ip->insn_opcode &= ~ ((7 << 3) << MIPS16OP_SH_IMM6);
-               ip->insn_opcode |= mask << MIPS16OP_SH_IMM6;
-             }
-           continue;
-
-           case 'm':           /* Register list for save insn.  */
-           case 'M':           /* Register list for restore insn.  */
-             {
-               int opcode = ip->insn_opcode;
-               int framesz = 0, seen_framesz = 0;
-               int nargs = 0, statics = 0, sregs = 0;
-
-               while (*s != '\0')
-                 {
-                   unsigned int reg1, reg2;
-
-                   SKIP_SPACE_TABS (s);
-                   while (*s == ',')
-                     ++s;
-                   SKIP_SPACE_TABS (s);
-
-                   my_getExpression (&imm_expr, s);
-                   if (imm_expr.X_op == O_constant)
-                     {
-                       /* Handle the frame size.  */
-                       if (seen_framesz)
-                         {
-                           as_bad (_("more than one frame size in list"));
-                           break;
-                         }
-                       seen_framesz = 1;
-                       framesz = imm_expr.X_add_number;
-                       imm_expr.X_op = O_absent;
-                       s = expr_end;
-                       continue;
-                     }
-
-                   if (! reg_lookup (&s, RTYPE_GP | RTYPE_NUM, &reg1))
-                     {
-                       as_bad (_("can't parse register list"));
-                       break;
-                     }
-
-                   while (*s == ' ')
-                     ++s;
-
-                   if (*s != '-')
-                     reg2 = reg1;
-                   else
-                     {
-                       ++s;
-                       if (! reg_lookup (&s, RTYPE_GP | RTYPE_NUM, &reg2)
-                           || reg2 < reg1)
-                         {
-                           as_bad (_("can't parse register list"));
-                           break;
-                         }
-                     }
-
-                   while (reg1 <= reg2)
-                     {
-                       if (reg1 >= 4 && reg1 <= 7)
-                         {
-                           if (!seen_framesz)
-                               /* args $a0-$a3 */
-                               nargs |= 1 << (reg1 - 4);
-                           else
-                               /* statics $a0-$a3 */
-                               statics |= 1 << (reg1 - 4);
-                         }
-                       else if ((reg1 >= 16 && reg1 <= 23) || reg1 == 30)
-                         {
-                           /* $s0-$s8 */
-                           sregs |= 1 << ((reg1 == 30) ? 8 : (reg1 - 16));
-                         }
-                       else if (reg1 == 31)
-                         {
-                           /* Add $ra to insn.  */
-                           opcode |= 0x40;
-                         }
-                       else
-                         {
-                           as_bad (_("unexpected register in list"));
-                           break;
-                         }
-                       if (++reg1 == 24)
-                         reg1 = 30;
-                     }
-                 }
-
-               /* Encode args/statics combination.  */
-               if (nargs & statics)
-                 as_bad (_("arg/static registers overlap"));
-               else if (nargs == 0xf)
-                 /* All $a0-$a3 are args.  */
-                 opcode |= MIPS16_ALL_ARGS << 16;
-               else if (statics == 0xf)
-                 /* All $a0-$a3 are statics.  */
-                 opcode |= MIPS16_ALL_STATICS << 16;
-               else 
-                 {
-                   int narg = 0, nstat = 0;
-
-                   /* Count arg registers.  */
-                   while (nargs & 0x1)
-                     {
-                       nargs >>= 1;
-                       narg++;
-                     }
-                   if (nargs != 0)
-                     as_bad (_("invalid arg register list"));
-
-                   /* Count static registers.  */
-                   while (statics & 0x8)
-                     {
-                       statics = (statics << 1) & 0xf;
-                       nstat++;
-                     }
-                   if (statics != 0) 
-                     as_bad (_("invalid static register list"));
-
-                   /* Encode args/statics.  */
-                   opcode |= ((narg << 2) | nstat) << 16;
-                 }
-
-               /* Encode $s0/$s1.  */
-               if (sregs & (1 << 0))           /* $s0 */
-                 opcode |= 0x20;
-               if (sregs & (1 << 1))           /* $s1 */
-                 opcode |= 0x10;
-               sregs >>= 2;
-
-               if (sregs != 0)
-                 {
-                   /* Count regs $s2-$s8.  */
-                   int nsreg = 0;
-                   while (sregs & 1)
-                     {
-                       sregs >>= 1;
-                       nsreg++;
-                     }
-                   if (sregs != 0)
-                     as_bad (_("invalid static register list"));
-                   /* Encode $s2-$s8. */
-                   opcode |= nsreg << 24;
-                 }
-
-               /* Encode frame size.  */
-               if (!seen_framesz)
-                 as_bad (_("missing frame size"));
-               else if ((framesz & 7) != 0 || framesz < 0
-                        || framesz > 0xff * 8)
-                 as_bad (_("invalid frame size"));
-               else if (framesz != 128 || (opcode >> 16) != 0)
-                 {
-                   framesz /= 8;
-                   opcode |= (((framesz & 0xf0) << 16)
-                            | (framesz & 0x0f));
-                 }
+             break;
+           }
 
-               /* Finally build the instruction.  */
-               if ((opcode >> 16) != 0 || framesz == 0)
-                 opcode |= MIPS16_EXTEND;
-               ip->insn_opcode = opcode;
-             }
-           continue;
+         operand = decode_mips16_operand (c, FALSE);
+         if (!operand)
+           abort ();
 
-           case 'e':           /* extend code */
-             my_getExpression (&imm_expr, s);
-             check_absolute_expr (ip, &imm_expr);
-             if ((unsigned long) imm_expr.X_add_number > 0x7ff)
+         /* '6' is a special case.  It is used for BREAK and SDBBP,
+            whose operands are only meaningful to the software that decodes
+            them.  This means that there is no architectural reason why
+            they cannot be prefixed by EXTEND, but in practice,
+            exception handlers will only look at the instruction
+            itself.  We therefore allow '6' to be extended when
+            disassembling but not when assembling.  */
+         if (operand->type != OP_PCREL && c != '6')
+           {
+             ext_operand = decode_mips16_operand (c, TRUE);
+             if (operand != ext_operand)
                {
-                 as_warn (_("Invalid value for `%s' (%lu)"),
-                          ip->insn_mo->name,
-                          (unsigned long) imm_expr.X_add_number);
-                 imm_expr.X_add_number &= 0x7ff;
+                 /* Parse the expression, allowing relocation operators.  */
+                 i = my_getSmallExpression (&offset_expr, offset_reloc, s);
+                 s = expr_end;
+
+                 if (offset_expr.X_op == O_register)
+                   {
+                     /* Handle elided offsets, which are equivalent to 0.  */
+                     if (*s == '(')
+                       {
+                         offset_expr.X_op = O_constant;
+                         offset_expr.X_add_number = 0;
+                         relax_char = c;
+                         continue;
+                       }
+                     /* Fail the match.  */
+                     break;
+                   }
+                 /* '8' is used for SLTI(U) and has traditionally not
+                    been allowed to take relocation operators.  */
+                 if (i > 0 && (ext_operand->size != 16 || c == '8'))
+                   break;
+                 relax_char = c;
+                 continue;
                }
-             ip->insn_opcode |= imm_expr.X_add_number;
-             imm_expr.X_op = O_absent;
-             s = expr_end;
-             continue;
+           }
 
-           default:
-             abort ();
+         s = match_operand (&arg, operand, s);
+         if (!s && arg.optional_reg)
+           {
+             /* Assume that the register has been elided and is the
+                same as the first operand.  */
+             arg.optional_reg = FALSE;
+             arg.argnum = 1;
+             s = argsstart;
+             SKIP_SPACE_TABS (s);
+             s = match_operand (&arg, operand, s);
            }
-         break;
+         if (!s)
+           break;
+         continue;
        }
 
       /* Args don't match.  */
-      if (insn + 1 < &mips16_opcodes[bfd_mips16_num_opcodes] &&
-         strcmp (insn->name, insn[1].name) == 0)
+      if (more_alts)
        {
          ++insn;
          s = argsstart;