Automatic date update in version.in
[deliverable/binutils-gdb.git] / gas / config / tc-msp430.c
index 6f3acdf727532d8f7d0d8c26843ddb3127e5a2f8..41413f0de92dd9c5c3ebc6627f18bf0619329f8a 100644 (file)
@@ -1,6 +1,6 @@
 /* tc-msp430.c -- Assembler code for the Texas Instruments MSP430
 
-  Copyright (C) 2002-2017 Free Software Foundation, Inc.
+  Copyright (C) 2002-2019 Free Software Foundation, Inc.
   Contributed by Dmitry Diky <diwil@mail.ru>
 
   This file is part of GAS, the GNU Assembler.
@@ -209,9 +209,9 @@ static struct hash_control *msp430_hash;
 #define CEBL   4
 
 /* Length.  */
-#define STATE_BITS10   1       /* wild guess. short jump */
-#define STATE_WORD     2       /* 2 bytes pc rel. addr. more */
-#define STATE_UNDEF    3       /* cannot handle this yet. convert to word mode */
+#define STATE_BITS10   1       /* Wild guess. short jump.  */
+#define STATE_WORD     2       /* 2 bytes pc rel. addr. more */
+#define STATE_UNDEF    3       /* Cannot handle this yet. convert to word mode.  */
 
 #define ENCODE_RELAX(what,length) (((what) << 2) + (length))
 #define RELAX_STATE(s)            ((s) & 3)
@@ -415,6 +415,7 @@ parse_exp (char * s, expressionS * op)
   expression (op);
   if (op->X_op == O_absent)
     as_bad (_("missing operand"));
+
   /* Our caller is likely to check that the entire expression was parsed.
      If we have found a hex constant with an 'h' suffix, ilp will be left
      pointing at the 'h', so skip it here.  */
@@ -680,11 +681,16 @@ static bfd_boolean gen_interrupt_nops = FALSE;
 #define OPTION_WARN_INTR_NOPS 'y'
 #define OPTION_NO_WARN_INTR_NOPS 'Y'
 static bfd_boolean warn_interrupt_nops = TRUE;
+#define OPTION_UNKNOWN_INTR_NOPS 'u'
+#define OPTION_NO_UNKNOWN_INTR_NOPS 'U'
+static bfd_boolean do_unknown_interrupt_nops = TRUE;
 #define OPTION_MCPU 'c'
 #define OPTION_MOVE_DATA 'd'
 static bfd_boolean move_data = FALSE;
 #define OPTION_DATA_REGION 'r'
 static bfd_boolean upper_data_region_in_use = FALSE;
+/* The default is to use the lower region only.  */
+static bfd_boolean lower_data_region_only = TRUE;
 
 enum
 {
@@ -1454,6 +1460,13 @@ md_parse_option (int c, const char * arg)
       warn_interrupt_nops = FALSE;
       return 1;
 
+    case OPTION_UNKNOWN_INTR_NOPS:
+      do_unknown_interrupt_nops = TRUE;
+      return 1;
+    case OPTION_NO_UNKNOWN_INTR_NOPS:
+      do_unknown_interrupt_nops = FALSE;
+      return 1;
+
     case OPTION_MOVE_DATA:
       move_data = TRUE;
       return 1;
@@ -1462,6 +1475,13 @@ md_parse_option (int c, const char * arg)
       if (strcmp (arg, "upper") == 0
          || strcmp (arg, "either") == 0)
        upper_data_region_in_use = TRUE;
+      if (strcmp (arg, "upper") == 0
+         || strcmp (arg, "either") == 0
+         /* With data-region=none, the compiler has generated code assuming
+            data could be in the upper region, but nothing has been explicitly
+            placed there.  */
+         || strcmp (arg, "none") == 0)
+       lower_data_region_only = FALSE;
       return 1;
     }
 
@@ -1484,13 +1504,16 @@ static void
 msp430_make_init_symbols (const char * name)
 {
   if (strncmp (name, ".bss", 4) == 0
+      || strncmp (name, ".lower.bss", 10) == 0
+      || strncmp (name, ".either.bss", 11) == 0
       || strncmp (name, ".gnu.linkonce.b.", 16) == 0)
     (void) symbol_find_or_make ("__crt0_init_bss");
 
   if (strncmp (name, ".data", 5) == 0
+      || strncmp (name, ".lower.data", 11) == 0
+      || strncmp (name, ".either.data", 12) == 0
       || strncmp (name, ".gnu.linkonce.d.", 16) == 0)
     (void) symbol_find_or_make ("__crt0_movedata");
-
   /* Note - data assigned to the .either.data section may end up being
      placed in the .upper.data section if the .lower.data section is
      full.  Hence the need to define the crt0 symbol.
@@ -1507,6 +1530,32 @@ msp430_make_init_symbols (const char * name)
       || strncmp (name, ".either.bss", 11) == 0
       || upper_data_region_in_use)
     (void) symbol_find_or_make ("__crt0_init_highbss");
+
+  /* The following symbols are for the crt0 functions that run through
+     the different .*_array sections and call the functions placed there.
+     - init_array stores global static C++ constructors to run before main.
+     - preinit_array is not expected to ever be used for MSP430.
+     GCC only places initialization functions for runtime "sanitizers"
+     (i.e. {a,l,t,u}san) and "virtual table verification" in preinit_array.
+     - fini_array stores global static C++ destructors to run after calling
+     exit() or returning from main.
+     __crt0_run_array is required to actually call the functions in the above
+     arrays.  */
+  if (strncmp (name, ".init_array", 11) == 0)
+    {
+      (void) symbol_find_or_make ("__crt0_run_init_array");
+      (void) symbol_find_or_make ("__crt0_run_array");
+    }
+  else if (strncmp (name, ".preinit_array", 14) == 0)
+    {
+      (void) symbol_find_or_make ("__crt0_run_preinit_array");
+      (void) symbol_find_or_make ("__crt0_run_array");
+    }
+  else if (strncmp (name, ".fini_array", 11) == 0)
+    {
+      (void) symbol_find_or_make ("__crt0_run_fini_array");
+      (void) symbol_find_or_make ("__crt0_run_array");
+    }
 }
 
 static void
@@ -1558,6 +1607,120 @@ msp430_refsym (int arg ATTRIBUTE_UNUSED)
   (void) symbol_find_or_make (sym_name);
 }
 
+/* Handle a .mspabi_attribute or .gnu_attribute directive.
+   attr_type is 0 for .mspabi_attribute or 1 for .gnu_attribute.
+   This is only used for validating the attributes in the assembly file against
+   the options gas has been invoked with.  If the attributes and options are
+   compatible then we add the attributes to the assembly file in
+   msp430_md_end.  */
+static void
+msp430_object_attribute (int attr_type)
+{
+  char tag_name_s[32];
+  char tag_value_s[32];
+  int tag_name, tag_value;
+  /* First operand is the tag name, second is the tag value e.g.
+     ".mspabi_attribute 4, 2".  */
+  input_line_pointer = extract_operand (input_line_pointer, tag_name_s, 32);
+  input_line_pointer = extract_operand (input_line_pointer, tag_value_s, 32);
+  tag_name = atoi (tag_name_s);
+  tag_value = atoi (tag_value_s);
+  /* If the attribute directive is present, the tag_value should never be set
+     to 0.  */
+  if (tag_name == 0 || tag_value == 0)
+    as_bad (_("bad arguments \"%s\" and/or \"%s\" in %s directive"),
+             tag_name_s, tag_value_s, (attr_type ? ".gnu_attribute"
+                                       : ".mspabi_attribute"));
+  else if (attr_type == 0)
+    /* Handle .mspabi_attribute.  */
+    switch (tag_name)
+      {
+      case OFBA_MSPABI_Tag_ISA:
+       switch (tag_value)
+         {
+         case OFBA_MSPABI_Val_ISA_MSP430:
+           if (target_is_430x ())
+             as_bad (_("file was compiled for the 430 ISA but the %s ISA is "
+                       "selected"), (target_is_430xv2 () ? "430X" : "430Xv2"));
+           break;
+         case OFBA_MSPABI_Val_ISA_MSP430X:
+           if (!target_is_430x ())
+             as_bad (_("file was compiled for the 430X ISA but the 430 ISA is "
+                       "selected"));
+           break;
+         default:
+           as_bad (_("unknown MSPABI build attribute value '%d' for "
+                     "OFBA_MSPABI_Tag_ISA(%d) in .mspabi_attribute directive"),
+                   tag_value, OFBA_MSPABI_Tag_ISA);
+           break;
+         }
+       break;
+      case OFBA_MSPABI_Tag_Code_Model:
+       /* Fall through.  */
+      case OFBA_MSPABI_Tag_Data_Model:
+       /* FIXME: Might we want to set the memory model to large if the assembly
+          file has the large model attribute, but -ml has not been passed?  */
+       switch (tag_value)
+         {
+         case OFBA_MSPABI_Val_Code_Model_SMALL:
+           if (large_model)
+             as_bad (_("file was compiled for the small memory model, but the "
+                       "large memory model is selected"));
+           break;
+         case OFBA_MSPABI_Val_Code_Model_LARGE:
+           if (!large_model)
+             as_bad (_("file was compiled for the large memory model, "
+                       "but the small memory model is selected"));
+           break;
+         default:
+           as_bad (_("unknown MSPABI build attribute value '%d' for %s(%d) "
+                     "in .mspabi_attribute directive"), tag_value,
+                   (tag_name == OFBA_MSPABI_Tag_Code_Model
+                    ? "OFBA_MSPABI_Tag_Code_Model"
+                    : "OFBA_MSPABI_Tag_Data_Model"),
+                   (tag_name == OFBA_MSPABI_Tag_Code_Model
+                    ? OFBA_MSPABI_Tag_Code_Model
+                    : OFBA_MSPABI_Tag_Data_Model));
+           break;
+         }
+       break;
+      default:
+       as_bad (_("unknown MSPABI build attribute tag '%d' in "
+                 ".mspabi_attribute directive"), tag_name);
+       break;
+      }
+  else if (attr_type == 1)
+    /* Handle .gnu_attribute.  */
+    switch (tag_name)
+      {
+      case Tag_GNU_MSP430_Data_Region:
+       /* This attribute is only applicable in the large memory model.  */
+       if (!large_model)
+         break;
+       switch (tag_value)
+         {
+         case Val_GNU_MSP430_Data_Region_Lower:
+           if (!lower_data_region_only)
+             as_bad (_("file was compiled assuming all data will be in the "
+                       "lower memory region, but the upper region is in use"));
+           break;
+         case Val_GNU_MSP430_Data_Region_Any:
+           if (lower_data_region_only)
+             as_bad (_("file was compiled assuming data could be in the upper "
+                       "memory region, but the lower data region is "
+                       "exclusively in use"));
+           break;
+         default:
+           as_bad (_("unknown GNU build attribute value '%d' for "
+                     "Tag_GNU_MSP430_Data_Region(%d) in .gnu_attribute "
+                     "directive"), tag_value, Tag_GNU_MSP430_Data_Region);
+         }
+      }
+  else
+    as_bad (_("internal: unexpected argument '%d' to msp430_object_attribute"),
+           attr_type);
+}
+
 const pseudo_typeS md_pseudo_table[] =
 {
   {"arch", msp430_set_arch, OPTION_MMCU},
@@ -1571,10 +1734,12 @@ const pseudo_typeS md_pseudo_table[] =
   {"refsym", msp430_refsym, 0},
   {"comm", msp430_comm, 0},
   {"lcomm", msp430_lcomm, 0},
+  {"mspabi_attribute", msp430_object_attribute, 0},
+  {"gnu_attribute", msp430_object_attribute, 1},
   {NULL, NULL, 0}
 };
 
-const char *md_shortopts = "mm:,mP,mQ,ml,mN,mn,my,mY";
+const char *md_shortopts = "mm:,mP,mQ,ml,mN,mn,my,mY,mu,mU";
 
 struct option md_longopts[] =
 {
@@ -1589,6 +1754,8 @@ struct option md_longopts[] =
   {"mn", no_argument, NULL, OPTION_INTR_NOPS},
   {"mY", no_argument, NULL, OPTION_NO_WARN_INTR_NOPS},
   {"my", no_argument, NULL, OPTION_WARN_INTR_NOPS},
+  {"mu", no_argument, NULL, OPTION_UNKNOWN_INTR_NOPS},
+  {"mU", no_argument, NULL, OPTION_NO_UNKNOWN_INTR_NOPS},
   {"md", no_argument, NULL, OPTION_MOVE_DATA},
   {"mdata-region", required_argument, NULL, OPTION_DATA_REGION},
   {NULL, no_argument, NULL, 0}
@@ -1620,6 +1787,13 @@ md_show_usage (FILE * stream)
           _("  -mY - do not warn about missing NOPs after changing interrupts\n"));
   fprintf (stream,
           _("  -my - warn about missing NOPs after changing interrupts (default)\n"));
+  fprintf (stream,
+          _("  -mU - for an instruction which changes interrupt state, but where it is not\n"
+            "        known how the state is changed, do not warn/insert NOPs\n"));
+  fprintf (stream,
+          _("  -mu - for an instruction which changes interrupt state, but where it is not\n"
+            "        known how the state is changed, warn/insert NOPs (default)\n"
+            "        -mn and/or -my are required for this to have any effect\n"));
   fprintf (stream,
           _("  -md - Force copying of data from ROM to RAM at startup\n"));
   fprintf (stream,
@@ -1672,6 +1846,12 @@ md_begin (void)
   linkrelax = 1;
 }
 
+static inline bfd_boolean
+is_regname_end (char c)
+{
+  return (c == 0 || ! ISALNUM (c));
+}
+  
 /* Returns the register number equivalent to the string T.
    Returns -1 if there is no such register.
    Skips a leading 'r' or 'R' character if there is one.
@@ -1680,32 +1860,36 @@ md_begin (void)
 static signed int
 check_reg (char * t)
 {
-  signed int val;
+  char * endt;
+  signed long int val;
 
-  if (t == NULL)
+  if (t == NULL || t[0] == 0)
     return -1;
 
   if (*t == 'r' || *t == 'R')
     ++t;
 
-  if (strncasecmp (t, "pc", 2) == 0)
+  if (strncasecmp (t, "pc", 2) == 0 && is_regname_end (t[2]))
     return 0;
 
-  if (strncasecmp (t, "sp", 2) == 0)
+  if (strncasecmp (t, "sp", 2) == 0 && is_regname_end (t[2]))
     return 1;
 
-  if (strncasecmp (t, "sr", 2) == 0)
+  if (strncasecmp (t, "sr", 2) == 0 && is_regname_end (t[2]))
     return 2;
 
-  if (*t == '0')
+  if (*t == '0' && is_regname_end (t[1]))
     return 0;
 
-  val = atoi (t);
+  val = strtol (t, & endt, 0);
 
   if (val < 1 || val > 15)
     return -1;
 
-  return val;
+  if (is_regname_end (*endt))
+    return val;
+
+  return -1;
 }
 
 static int
@@ -1971,8 +2155,8 @@ msp430_srcoperand (struct msp430_operand_s * op,
     {
       char *h = l;
 
-      op->reg = 2;             /* reg 2 in absolute addr mode.  */
-      op->am = 1;              /* mode As == 01 bin.  */
+      op->reg = 2;             /* Reg 2 in absolute addr mode.  */
+      op->am = 1;              /* Mode As == 01 bin.  */
       op->ol = 1;              /* Immediate value followed by instruction.  */
       __tl = h + 1;
       end = parse_exp (__tl, &(op->exp));
@@ -2485,6 +2669,90 @@ static signed int check_for_nop = 0;
 
 #define is_opcode(NAME) (strcmp (opcode->name, NAME) == 0)
 
+/* is_{e,d}int only check the explicit enabling/disabling of interrupts.
+   For MOV insns, more sophisticated processing is needed to determine if they
+   result in enabling/disabling interrupts.  */
+#define is_dint(OPCODE, BIN) ((strcmp (OPCODE, "dint") == 0) \
+                                  || ((strcmp (OPCODE, "bic") == 0) \
+                                      && BIN == 0xc232) \
+                                  || ((strcmp (OPCODE, "clr") == 0) \
+                                      && BIN == 0x4302))
+
+#define is_eint(OPCODE, BIN) ((strcmp (OPCODE, "eint") == 0) \
+                                  || ((strcmp (OPCODE, "bis") == 0) \
+                                      && BIN == 0xd232))
+
+const char * const INSERT_NOP_BEFORE_EINT = "NOP inserted here, before an interrupt enable instruction";
+const char * const INSERT_NOP_AFTER_DINT = "NOP inserted here, after an interrupt disable instruction";
+const char * const INSERT_NOP_AFTER_EINT = "NOP inserted here, after an interrupt enable instruction";
+const char * const INSERT_NOP_BEFORE_UNKNOWN = "NOP inserted here, before this interrupt state change";
+const char * const INSERT_NOP_AFTER_UNKNOWN ="NOP inserted here, after the instruction that changed interrupt state";
+const char * const INSERT_NOP_AT_EOF = "NOP inserted after the interrupt state change at the end of the file";
+
+const char * const WARN_NOP_BEFORE_EINT = "a NOP might be needed here, before an interrupt enable instruction";
+const char * const WARN_NOP_AFTER_DINT = "a NOP might be needed here, after an interrupt disable instruction";
+const char * const WARN_NOP_AFTER_EINT = "a NOP might be needed here, after an interrupt enable instruction";
+const char * const WARN_NOP_BEFORE_UNKNOWN = "a NOP might be needed here, before this interrupt state change";
+const char * const WARN_NOP_AFTER_UNKNOWN = "a NOP might also be needed here, after the instruction that changed interrupt state";
+const char * const WARN_NOP_AT_EOF = "a NOP might be needed after the interrupt state change at the end of the file";
+
+static void
+gen_nop (void)
+{
+  char *frag;
+  frag = frag_more (2);
+  bfd_putl16 ((bfd_vma) 0x4303 /* NOP */, frag);
+  dwarf2_emit_insn (2);
+}
+
+/* Insert/inform about adding a NOP if this insn enables interrupts.  */
+
+static void
+warn_eint_nop (bfd_boolean prev_insn_is_nop, bfd_boolean prev_insn_is_dint)
+{
+  if (prev_insn_is_nop
+      /* If the last insn was a DINT, we will have already warned that a NOP is
+        required after it.  */
+      || prev_insn_is_dint
+      /* 430 ISA does not require a NOP before EINT.  */
+      || (! target_is_430x ()))
+    return;
+
+  if (gen_interrupt_nops)
+    {
+      gen_nop ();
+      if (warn_interrupt_nops)
+       as_warn (_(INSERT_NOP_BEFORE_EINT));
+    }
+  else if (warn_interrupt_nops)
+    as_warn (_(WARN_NOP_BEFORE_EINT));
+}
+
+/* Use when unsure what effect the insn will have on the interrupt status,
+   to insert/warn about adding a NOP before the current insn.  */
+
+static void
+warn_unsure_interrupt (bfd_boolean prev_insn_is_nop,
+                      bfd_boolean prev_insn_is_dint)
+{
+  if (prev_insn_is_nop
+      /* If the last insn was a DINT, we will have already warned that a NOP is
+        required after it.  */
+      || prev_insn_is_dint
+      /* 430 ISA does not require a NOP before EINT or DINT.  */
+      || (! target_is_430x ()))
+    return;
+
+  if (gen_interrupt_nops)
+    {
+      gen_nop ();
+      if (warn_interrupt_nops)
+       as_warn (_(INSERT_NOP_BEFORE_UNKNOWN));
+    }
+  else if (warn_interrupt_nops)
+    as_warn (_(WARN_NOP_BEFORE_UNKNOWN));
+}
+
 /* Parse instruction operands.
    Return binary opcode.  */
 
@@ -2509,6 +2777,12 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
   const char * error_message;
   static signed int repeat_count = 0;
   static bfd_boolean prev_insn_is_nop = FALSE;
+  static bfd_boolean prev_insn_is_dint = FALSE;
+  static bfd_boolean prev_insn_is_eint = FALSE;
+  /* We might decide before the end of the function that the current insn is
+     equivalent to DINT/EINT.  */
+  bfd_boolean this_insn_is_dint = FALSE;
+  bfd_boolean this_insn_is_eint = FALSE;
   bfd_boolean fix_emitted;
 
   /* Opcode is the one from opcodes table
@@ -2601,7 +2875,9 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
       && opcode->insn_opnumb
       && (!*line || *line == '\n'))
     {
-      as_bad (_("instruction %s requires %d operand(s)"),
+      as_bad (ngettext ("instruction %s requires %d operand",
+                       "instruction %s requires %d operands",
+                       opcode->insn_opnumb),
              opcode->name, opcode->insn_opnumb);
       return 0;
     }
@@ -2642,29 +2918,69 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
       repeat_count = 0;
     }
 
+  /* The previous instruction set this flag if it wants to check if this insn
+     is a NOP.  */
   if (check_for_nop)
     {
       if (! is_opcode ("nop"))
        {
-         bfd_boolean doit = FALSE;
-
          do
            {
              switch (check_for_nop & - check_for_nop)
                {
                case NOP_CHECK_INTERRUPT:
-                 if (warn_interrupt_nops)
+                 /* NOP_CHECK_INTERRUPT rules:
+                    1.  430 and 430x ISA require a NOP after DINT.
+                    2.  Only the 430x ISA requires NOP before EINT (this has
+                       been dealt with in the previous call to this function).
+                    3.  Only the 430x ISA requires NOP after every EINT.
+                       CPU42 errata.  */
+                 if (gen_interrupt_nops || warn_interrupt_nops)
                    {
-                     if (gen_interrupt_nops)
-                       as_warn (_("NOP inserted between two instructions that change interrupt state"));
+                     if (prev_insn_is_dint)
+                       {
+                         if (gen_interrupt_nops)
+                           {
+                             gen_nop ();
+                             if (warn_interrupt_nops)
+                               as_warn (_(INSERT_NOP_AFTER_DINT));
+                           }
+                         else
+                           as_warn (_(WARN_NOP_AFTER_DINT));
+                       }
+                     else if (prev_insn_is_eint)
+                       {
+                         if (gen_interrupt_nops)
+                           {
+                             gen_nop ();
+                             if (warn_interrupt_nops)
+                               as_warn (_(INSERT_NOP_AFTER_EINT));
+                           }
+                         else
+                           as_warn (_(WARN_NOP_AFTER_EINT));
+                       }
+                     /* If we get here it's because the last instruction was
+                        determined to either disable or enable interrupts, but
+                        we're not sure which.
+                        We have no information yet about what effect the
+                        current instruction has on interrupts, that has to be
+                        sorted out later.
+                        The last insn may have required a NOP after it, so we
+                        deal with that now.  */
                      else
-                       as_warn (_("a NOP might be needed here because of successive changes in interrupt state"));
+                       {
+                         if (gen_interrupt_nops)
+                           {
+                             gen_nop ();
+                             if (warn_interrupt_nops)
+                               as_warn (_(INSERT_NOP_AFTER_UNKNOWN));
+                           }
+                         else
+                           /* warn_unsure_interrupt was called on the previous
+                              insn.  */
+                           as_warn (_(WARN_NOP_AFTER_UNKNOWN));
+                       }
                    }
-
-                 if (gen_interrupt_nops)
-                   /* Emit a NOP between interrupt enable/disable.
-                      See 1.3.4.1 of the MSP430x5xx User Guide.  */
-                   doit = TRUE;
                  break;
 
                case NOP_CHECK_CPU12:
@@ -2672,7 +2988,7 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
                    as_warn (_("CPU12: CMP/BIT with PC destination ignores next instruction"));
 
                  if (silicon_errata_fix & SILICON_ERRATA_CPU12)
-                   doit = TRUE;
+                   gen_nop ();
                  break;
 
                case NOP_CHECK_CPU19:
@@ -2680,7 +2996,7 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
                    as_warn (_("CPU19: Instruction setting CPUOFF must be followed by a NOP"));
 
                  if (silicon_errata_fix & SILICON_ERRATA_CPU19)
-                   doit = TRUE;
+                   gen_nop ();
                  break;
                  
                default:
@@ -2690,43 +3006,19 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
              check_for_nop &= ~ (check_for_nop & - check_for_nop);
            }
          while (check_for_nop);
-         
-         if (doit)
-           {
-             frag = frag_more (2);
-             bfd_putl16 ((bfd_vma) 0x4303 /* NOP */, frag);
-             dwarf2_emit_insn (2);
-           }
        }
-
       check_for_nop = 0;
     }
 
   switch (fmt)
     {
-    case 0:                    /* Emulated.  */
+    case 0:
+      /* Emulated.  */
       switch (opcode->insn_opnumb)
        {
        case 0:
          if (is_opcode ("eint"))
-           {
-             if (! prev_insn_is_nop)
-               {
-                 if (gen_interrupt_nops)
-                   {
-                     frag = frag_more (2);
-                     bfd_putl16 ((bfd_vma) 0x4303 /* NOP */, frag);
-                     dwarf2_emit_insn (2);
-
-                     if (warn_interrupt_nops)
-                       as_warn (_("inserting a NOP before EINT"));
-                   }
-                 else if (warn_interrupt_nops)
-                   as_warn (_("a NOP might be needed before the EINT"));
-               }
-           }
-         else if (is_opcode ("dint"))
-           check_for_nop |= NOP_CHECK_INTERRUPT;
+           warn_eint_nop (prev_insn_is_nop, prev_insn_is_dint);
 
          /* Set/clear bits instructions.  */
          if (extended_op)
@@ -2784,9 +3076,6 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
                as_warn (_("CPU13: SR is destination of SR altering instruction"));
            }
          
-         if (is_opcode ("clr") && bin == 0x4302 /* CLR R2*/)
-           check_for_nop |= NOP_CHECK_INTERRUPT;
-
          /* Compute the entire instruction length, in bytes.  */
          op_length = (extended_op ? 2 : 0) + 2 + (op1.ol * 2);
          insn_length += op_length;
@@ -2884,6 +3173,8 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
              /* ... and the opcode alters the SR.  */
              && (is_opcode ("rla") || is_opcode ("rlc")
                  || is_opcode ("rlax") || is_opcode ("rlcx")
+                 || is_opcode ("sxt") || is_opcode ("sxtx")
+                 || is_opcode ("swpb")
                  ))
            {
              if (silicon_errata_fix & SILICON_ERRATA_CPU13)
@@ -3435,6 +3726,12 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
        }
       break;
 
+      /* FIXME: Emit warning when dest reg SR(R2) is addressed with .B or .A.
+        From f5 ref man 6.3.3:
+          The 16-bit Status Register (SR, also called R2), used as a source or
+          destination register, can only be used in register mode addressed
+          with word instructions.  */
+
     case 1:                    /* Format 1, double operand.  */
       line = extract_operand (line, l1, sizeof (l1));
       line = extract_operand (line, l2, sizeof (l2));
@@ -3489,20 +3786,9 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
          else if (silicon_errata_warn & SILICON_ERRATA_CPU13)
            as_warn (_("CPU13: SR is destination of SR altering instruction"));
        }
-         
-      if (   (is_opcode ("bic") && bin == 0xc232)
-         || (is_opcode ("bis") && bin == 0xd232)
-         || (is_opcode ("mov") && op2.mode == OP_REG && op2.reg == 2))
-       {
-         /* Avoid false checks when a constant value is being put into the SR.  */
-         if (op1.mode == OP_EXP
-             && op1.exp.X_op == O_constant
-             && (op1.exp.X_add_number & 0x8) != 0x8)
-           ;
-         else
-           check_for_nop |= NOP_CHECK_INTERRUPT;
-       }
 
+      /* Chain these checks for SR manipulations so we can warn if they are not
+        caught.  */
       if (((is_opcode ("bis") && bin == 0xd032)
           || (is_opcode ("mov") && bin == 0x4032)
           || (is_opcode ("xor") && bin == 0xe032))
@@ -3510,6 +3796,60 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
          && op1.exp.X_op == O_constant
          && (op1.exp.X_add_number & 0x10) == 0x10)
        check_for_nop |= NOP_CHECK_CPU19;
+      else if ((is_opcode ("mov") && op2.mode == OP_REG && op2.reg == 2))
+       {
+         /* Any MOV with the SR as the destination either enables or disables
+            interrupts.  */
+         if (op1.mode == OP_EXP
+             && op1.exp.X_op == O_constant)
+           {
+             if ((op1.exp.X_add_number & 0x8) == 0x8)
+               {
+                 /* The GIE bit is being set.  */
+                 warn_eint_nop (prev_insn_is_nop, prev_insn_is_dint);
+                 this_insn_is_eint = TRUE;
+               }
+             else
+               /* The GIE bit is being cleared.  */
+               this_insn_is_dint = TRUE;
+           }
+         /* If an immediate value which is covered by the constant generator
+            is the src, then op1 will have been changed to either R2 or R3 by
+            this point.
+            The only constants covered by CG1 and CG2, which have bit 3 set
+            and therefore would enable interrupts when writing to the SR, are
+            R2 with addresing mode 0b11 and R3 with 0b11.
+            The addressing mode is in bits 5:4 of the binary opcode.  */
+         else if (op1.mode == OP_REG
+                  && (op1.reg == 2 || op1.reg == 3)
+                  && (bin & 0x30) == 0x30)
+           {
+             warn_eint_nop (prev_insn_is_nop, prev_insn_is_dint);
+             this_insn_is_eint = TRUE;
+           }
+         /* Any other use of the constant generator with destination R2, will
+            disable interrupts.  */
+         else if (op1.mode == OP_REG
+                  && (op1.reg == 2 || op1.reg == 3))
+           this_insn_is_dint = TRUE;
+         else if (do_unknown_interrupt_nops)
+           {
+             /* FIXME: Couldn't work out whether the insn is enabling or
+                disabling interrupts, so for safety need to treat it as both
+                a DINT and EINT.  */
+             warn_unsure_interrupt (prev_insn_is_nop, prev_insn_is_dint);
+             check_for_nop |= NOP_CHECK_INTERRUPT;
+           }
+       }
+      else if (is_eint (opcode->name, bin))
+       warn_eint_nop (prev_insn_is_nop, prev_insn_is_dint);
+      else if ((bin & 0x32) == 0x32)
+       {
+         /* Double-operand insn with the As==0b11 and Rdst==0x2 will result in
+          * an interrupt state change if a write happens.  */
+         /* FIXME: How strict to be here? */
+         ;
+       }
 
       /* Compute the entire length of the instruction in bytes.  */
       op_length = (extended_op ? 2 : 0)        /* The extension word.  */
@@ -3947,11 +4287,34 @@ msp430_operands (struct msp430_opcode_s * opcode, char * line)
       as_bad (_("Illegal instruction or not implemented opcode."));
     }
 
-  if (is_opcode ("nop"))
-    prev_insn_is_nop = TRUE;
-  else
-    prev_insn_is_nop = FALSE;
-           
+    if (is_opcode ("nop"))
+      {
+       prev_insn_is_nop = TRUE;
+       prev_insn_is_dint = FALSE;
+       prev_insn_is_eint = FALSE;
+      }
+    else if (this_insn_is_dint || is_dint (opcode->name, bin))
+      {
+       prev_insn_is_dint = TRUE;
+       prev_insn_is_eint = FALSE;
+       prev_insn_is_nop = FALSE;
+       check_for_nop |= NOP_CHECK_INTERRUPT;
+      }
+    /* NOP is not needed after EINT for 430 ISA.  */
+    else if (target_is_430x () && (this_insn_is_eint || is_eint (opcode->name, bin)))
+      {
+       prev_insn_is_eint = TRUE;
+       prev_insn_is_nop = FALSE;
+       prev_insn_is_dint = FALSE;
+       check_for_nop |= NOP_CHECK_INTERRUPT;
+      }
+    else
+      {
+       prev_insn_is_nop = FALSE;
+       prev_insn_is_dint = FALSE;
+       prev_insn_is_eint = FALSE;
+      }
+
   input_line_pointer = line;
   return 0;
 }
@@ -4001,7 +4364,7 @@ md_assemble (char * str)
 valueT
 md_section_align (asection * seg, valueT addr)
 {
-  int align = bfd_get_section_alignment (stdoutput, seg);
+  int align = bfd_section_alignment (seg);
 
   return ((addr + (1 << align) - 1) & -(1 << align));
 }
@@ -4681,22 +5044,44 @@ msp430_fix_adjustable (struct fix *fixp ATTRIBUTE_UNUSED)
   return FALSE;
 }
 
-/* Set the contents of the .MSP430.attributes section.  */
+/* Set the contents of the .MSP430.attributes and .GNU.attributes sections.  */
 
 void
 msp430_md_end (void)
 {
   if (check_for_nop)
-    as_warn ("assembly finished without a possibly needed NOP instruction");
+    {
+      if (gen_interrupt_nops)
+       {
+         gen_nop ();
+         if (warn_interrupt_nops)
+           as_warn (INSERT_NOP_AT_EOF);
+       }
+      else if (warn_interrupt_nops)
+       as_warn (_(WARN_NOP_AT_EOF));
+    }
 
+  /* We have already emitted an error if any of the following attributes
+     disagree with the attributes in the input assembly file.  See
+     msp430_object_attribute.  */
   bfd_elf_add_proc_attr_int (stdoutput, OFBA_MSPABI_Tag_ISA,
-                            target_is_430x () ? 2 : 1);
+                            target_is_430x () ? OFBA_MSPABI_Val_ISA_MSP430X
+                            : OFBA_MSPABI_Val_ISA_MSP430);
 
   bfd_elf_add_proc_attr_int (stdoutput, OFBA_MSPABI_Tag_Code_Model,
-                            large_model ? 2 : 1);
+                            large_model ? OFBA_MSPABI_Val_Code_Model_LARGE
+                            : OFBA_MSPABI_Val_Code_Model_SMALL);
 
   bfd_elf_add_proc_attr_int (stdoutput, OFBA_MSPABI_Tag_Data_Model,
-                            large_model ? 2 : 1);
+                            large_model ? OFBA_MSPABI_Val_Code_Model_LARGE
+                            : OFBA_MSPABI_Val_Code_Model_SMALL);
+
+  /* The data region GNU attribute is ignored for the small memory model.  */
+  if (large_model)
+    bfd_elf_add_obj_attr_int (stdoutput, OBJ_ATTR_GNU,
+                             Tag_GNU_MSP430_Data_Region, lower_data_region_only
+                             ? Val_GNU_MSP430_Data_Region_Lower
+                             : Val_GNU_MSP430_Data_Region_Any);
 }
 
 /* Returns FALSE if there is a msp430 specific reason why the
This page took 0.036319 seconds and 4 git commands to generate.