Various fixes and improvements for d10v.
authorTom Rix <trix@redhat.com>
Wed, 3 Apr 2002 19:44:05 +0000 (19:44 +0000)
committerTom Rix <trix@redhat.com>
Wed, 3 Apr 2002 19:44:05 +0000 (19:44 +0000)
gas/ChangeLog
gas/config/tc-d10v.c
gas/config/tc-d10v.h

index fa2cfb0..f88e309 100644 (file)
@@ -1,3 +1,36 @@
+2002-04-03  Tom Rix  <trix@redhat.com>
+
+       * config/tc-d10v.c (d10v_insert_operand): Fix warning in as_bad_where.
+       (build_insn): Same.
+       (find_opcode): Fix warning in as_warn.
+       * config/tc-d10v.h: Update Copyright.
+
+2002-04-03  Alan Matsuoka <alanm@redhat.com>
+           Tom Rix  <trix@redhat.com>
+
+       From Jeff Knaggs <jknaggs@redhat.com>
+       * config/tc-d10v.c (check_resource_conflict): New function to 
+       check for resource conflicts.
+
+       From Jason Eckhardt  <jle@redhat.com>
+        * config/tc-d10v.c (build_insn): Check for unresolved imm4 or
+        imm3 fields.
+        * config/tc-d10v.c (find_opcode): Emit a warning if one of the
+        reserved control registers is used.
+        * config/tc-d10v.c (build_insn): Check for unresolved imm4 or
+        imm3 fields.
+
+       From 2001-03-28  Diego Novillo  <dnovillo@redhat.com>
+        * tc-d10v.c (parallel_ok): Prevent packing only if the first
+        instruction cannot be packed.
+
+       From 2001-03-30  Diego Novillo  <dnovillo@redhat.com>
+        * gas/config/tc-d10v.c (check_resource_conflict): Only check
+        write-write conflicts.
+        (md_assemble): Reformat introductory comment.
+        * opcodes/d10v-opc.c (d10v_opcodes): `btsti' does not modify its
+        arguments.
+
 2002-04-03  Alan Modra  <amodra@bigpond.net.au>
 
        * symbols.c (resolve_symbol_value <O_uminus, O_bit_not,
index 31d7fca..10c673e 100644 (file)
@@ -1,5 +1,5 @@
 /* tc-d10v.c -- Assembler code for the Mitsubishi D10V
-   Copyright 1996, 1997, 1998, 1999, 2000, 2001
+   Copyright 1996, 1997, 1998, 1999, 2000, 2001, 2002
    Free Software Foundation, Inc.
 
    This file is part of GAS, the GNU Assembler.
@@ -25,7 +25,6 @@
 #include "subsegs.h"
 #include "opcode/d10v.h"
 #include "elf/ppc.h"
-//#include "read.h"
 
 const char comment_chars[] = ";";
 const char line_comment_chars[] = "#";
@@ -99,6 +98,12 @@ static unsigned long d10v_insert_operand PARAMS (( unsigned long insn, int op_ty
 static int parallel_ok PARAMS ((struct d10v_opcode *opcode1, unsigned long insn1,
                                struct d10v_opcode *opcode2, unsigned long insn2,
                                packing_type exec_type));
+
+static void check_resource_conflict PARAMS ((struct d10v_opcode *opcode1, 
+                                            unsigned long insn1, 
+                                            struct d10v_opcode *opcode2, 
+                                            unsigned long insn2));
+
 static symbolS * find_symbol_matching_register PARAMS ((expressionS *));
 
 struct option md_longopts[] =
@@ -578,7 +583,7 @@ d10v_insert_operand (insn, op_type, value, left, fix)
   /* Truncate to the proper number of bits.  */
   if (check_range (value, bits, d10v_operands[op_type].flags))
     as_bad_where (fix->fx_file, fix->fx_line,
-                 _("operand out of range: %d"), value);
+                 _("operand out of range: %ld"), (long) value);
 
   value &= 0x7FFFFFFF >> (31 - bits);
   insn |= (value << shift);
@@ -660,14 +665,14 @@ build_insn (opcode, opers, insn)
 
       /* Truncate to the proper number of bits.  */
       if ((opers[i].X_op == O_constant) && check_range (number, bits, flags))
-       as_bad (_("operand out of range: %d"), number);
+       as_bad (_("operand out of range: %lu"), number);
       number &= 0x7FFFFFFF >> (31 - bits);
       insn = insn | (number << shift);
     }
 
-  /* kludge: for DIVS, we need to put the operands in twice  */
-  /* on the second pass, format is changed to LONG_R to force
-     the second set of operands to not be shifted over 15.  */
+  /* kludge: for DIVS, we need to put the operands in twice on the second 
+     pass, format is changed to LONG_R to force the second set of operands 
+     to not be shifted over 15.  */
   if ((opcode->opcode == OPCODE_DIVS) && (format == LONG_L))
     insn = build_insn (opcode, opers, insn);
 
@@ -723,9 +728,9 @@ write_1_short (opcode, insn, fx)
   if (opcode->exec_type & PARONLY)
     as_fatal (_("Instruction must be executed in parallel with another instruction."));
 
-  /* The other container needs to be NOP.  */
-  /* According to 4.3.1: for FM=00, sub-instructions performed only
-     by IU cannot be encoded in L-container.  */
+  /* The other container needs to be NOP.  
+     According to 4.3.1: for FM=00, sub-instructions performed only by IU 
+     cannot be encoded in L-container.  */
   if (opcode->unit == IU)
     insn |= FM00 | (NOP << 15);                /* Right container.  */
   else
@@ -787,8 +792,7 @@ write_2_short (opcode1, insn1, opcode2, insn2, exec_type, fx)
     case PACK_UNSPEC:  /* Order not specified.  */
       if (opcode1->exec_type & ALONE)
        {
-         /* Case of a short branch on a separate GAS line.
-            Pack with NOP.  */
+         /* Case of a short branch on a separate GAS line.  Pack with NOP.  */
          write_1_short (opcode1, insn1, fx->next);
          return 1;
        }
@@ -833,6 +837,7 @@ write_2_short (opcode1, insn1, opcode2, insn2, exec_type, fx)
        }
       else
        insn = FM00 | (insn1 << 15) | insn2;
+      check_resource_conflict (opcode1, insn1, opcode2, insn2);
       break;
 
     case PACK_LEFT_RIGHT:
@@ -939,7 +944,8 @@ parallel_ok (op1, insn1, op2, insn2, exec_type)
   /* If this is auto parallization, and either instruction is a branch,
      don't parallel.  */
   if (exec_type == PACK_UNSPEC
-      && (op1->exec_type & ALONE || op2->exec_type & ALONE))
+      && (op1->exec_type & (ALONE | BRANCH) 
+         || op2->exec_type & (ALONE | BRANCH)))
     return 0;
 
   /* The idea here is to create two sets of bitmasks (mod and used)
@@ -957,12 +963,12 @@ parallel_ok (op1, insn1, op2, insn2, exec_type)
      and the second reads the PSW (which includes C, F0, and F1), then
      they cannot operate safely in parallel.  */
 
-  /* The bitmasks (mod and used) look like this (bit 31 = MSB).  */
-  /* r0-r15      0-15   */
-  /* a0-a1       16-17  */
-  /* cr (not psw) 18     */
-  /* psw         19     */
-  /* mem         20     */
+  /* The bitmasks (mod and used) look like this (bit 31 = MSB). 
+     r0-r15      0-15   
+     a0-a1       16-17
+     cr (not psw) 18
+     psw         19
+     mem         20  */
 
   for (j = 0; j < 2; j++)
     {
@@ -1042,6 +1048,155 @@ parallel_ok (op1, insn1, op2, insn2, exec_type)
   return 0;
 }
 
+/* Determine if there are any resource conflicts among two manually
+   parallelized instructions.  Some of this was lifted from parallel_ok.  */
+
+static void 
+check_resource_conflict (op1, insn1, op2, insn2)
+     struct d10v_opcode *op1, *op2;
+     unsigned long insn1, insn2;
+{
+  int i, j, flags, mask, shift, regno;
+  unsigned long ins, mod[2], used[2];
+  struct d10v_opcode *op;
+
+  if ((op1->exec_type & SEQ)
+      || ! ((op1->exec_type & PAR) || (op1->exec_type & PARONLY)))
+    {
+      as_warn (_("packing conflict: %s must dispatch sequentially"),
+             op1->name);
+      return;
+    }
+
+  if ((op2->exec_type & SEQ)
+      || ! ((op2->exec_type & PAR) || (op2->exec_type & PARONLY)))
+    {
+      as_warn (_("packing conflict: %s must dispatch sequentially"),
+             op2->name);
+      return;
+    }
+
+  /* The idea here is to create two sets of bitmasks (mod and used)
+     which indicate which registers are modified or used by each
+     instruction.  The operation can only be done in parallel if
+     instruction 1 and instruction 2 modify different registers, and
+     the first instruction does not modify registers that the second
+     is using (The second instruction can modify registers that the
+     first is using as they are only written back after the first
+     instruction has completed).  Accesses to control registers
+     and memory are treated as accesses to a single register.  So if
+     both instructions write memory or if the first instruction writes
+     memory and the second reads, then they cannot be done in
+     parallel. We treat reads to the PSW (which includes C, F0, and F1)
+     in isolation. So simultaneously writing C and F0 in two different
+     sub-instructions is permitted.  */
+
+  /* The bitmasks (mod and used) look like this (bit 31 = MSB).
+     r0-r15      0-15
+     a0-a1       16-17
+     cr (not psw) 18
+     psw(other)   19
+     mem         20
+     psw(C flag)  21
+     psw(F0 flag) 22  */
+
+  for (j = 0; j < 2; j++)
+    {
+      if (j == 0)
+       {
+         op = op1;
+         ins = insn1;
+       }
+      else
+       {
+         op = op2;
+         ins = insn2;
+       }
+      mod[j] = used[j] = 0;
+      if (op->exec_type & BRANCH_LINK)
+       mod[j] |= 1 << 13;
+
+      for (i = 0; op->operands[i]; i++)
+       {
+         flags = d10v_operands[op->operands[i]].flags;
+         shift = d10v_operands[op->operands[i]].shift;
+         mask = 0x7FFFFFFF >> (31 - d10v_operands[op->operands[i]].bits);
+         if (flags & OPERAND_REG)
+           {
+             regno = (ins >> shift) & mask;
+             if (flags & (OPERAND_ACC0 | OPERAND_ACC1))
+               regno += 16;
+             else if (flags & OPERAND_CONTROL) /* mvtc or mvfc */
+               { 
+                 if (regno == 0)
+                   regno = 19;
+                 else
+                   regno = 18; 
+               }
+             else if (flags & OPERAND_FFLAG)
+               regno = 22;
+             else if (flags & OPERAND_CFLAG)
+               regno = 21;
+             
+            if ( flags & OPERAND_DEST )
+               {
+                 mod[j] |= 1 << regno;
+                 if (flags & OPERAND_EVEN)
+                   mod[j] |= 1 << (regno + 1);
+               }
+            else
+              {
+                used[j] |= 1 << regno ;
+                if (flags & OPERAND_EVEN)
+                  used[j] |= 1 << (regno + 1);
+
+                /* Auto inc/dec also modifies the register.  */
+                if (op->operands[i+1] != 0
+                    && (d10v_operands[op->operands[i+1]].flags
+                        & (OPERAND_PLUS | OPERAND_MINUS)) != 0)
+                  mod[j] |= 1 << regno;
+              }
+           }
+         else if (flags & OPERAND_ATMINUS)
+           {
+             /* SP implicitly used/modified.  */
+             mod[j] |= 1 << 15;
+             used[j] |= 1 << 15;
+           }
+       }
+      if (op->exec_type & RMEM)
+       used[j] |= 1 << 20;
+      else if (op->exec_type & WMEM)
+       mod[j] |= 1 << 20;
+      else if (op->exec_type & RF0)
+       used[j] |= 1 << 22;
+      else if (op->exec_type & WF0)
+       mod[j] |= 1 << 22;
+      else if (op->exec_type & WCAR)
+       mod[j] |= 1 << 21;
+    }
+  if ((mod[0] & mod[1]) == 0)
+    return;
+  else
+    {
+      unsigned long x;
+      x = mod[0] & mod[1];
+
+      for (j = 0; j <= 15; j++)
+       if (x & (1 << j))
+         as_warn (_("resource conflict (R%d)"), j);
+      for (j = 16; j <= 17; j++)
+       if (x & (1 << j))
+         as_warn (_("resource conflict (A%d)"), j - 16);
+      if (x & (1 << 19))
+       as_warn (_("resource conflict (PSW)"));
+      if (x & (1 << 21))
+       as_warn (_("resource conflict (C flag)"));
+      if (x & (1 << 22))
+       as_warn (_("resource conflict (F flag)"));
+    }
+}
+
 /* This is the main entry point for the machine-dependent assembler.
    STR points to a machine-dependent instruction.  This function is
    supposed to emit the frags/bytes it assembles to.  For the D10V, it
@@ -1083,7 +1238,8 @@ md_assemble (str)
                extype = PACK_RIGHT_LEFT;
            }
        }
-      /* STR2 points to the separator, if there is one.  */
+
+      /* str2 points to the separator, if there is one.  */
       if (str2)
        {
          *str2 = 0;
@@ -1138,7 +1294,8 @@ md_assemble (str)
     d10v_cleanup ();
 
   if (prev_opcode
-      && (write_2_short (prev_opcode, prev_insn, opcode, insn, extype, fixups) == 0))
+      && (0 == write_2_short (prev_opcode, prev_insn, opcode, insn, extype, 
+                             fixups)))
     {
       /* No instructions saved.  */
       prev_opcode = NULL;
@@ -1206,7 +1363,7 @@ do_assemble (str, opcode)
   return (insn);
 }
 
-/* Find the symbol which has the same name as the register in EXP.  */
+/* Find the symbol which has the same name as the register in exp.  */
 
 static symbolS *
 find_symbol_matching_register (exp)
@@ -1296,7 +1453,7 @@ find_opcode (opcode, myops)
              if (myops[opnum].X_op == O_constant)
                {
                  if (!check_range (myops[opnum].X_add_number, bits, flags))
-                   return next_opcode;
+                   break;
                }
              else
                {
@@ -1341,109 +1498,112 @@ find_opcode (opcode, myops)
                        {
                          bits += 2;
                          if (!check_range (value, bits, flags))
-                           return next_opcode;
+                           break;
                        }
                    }
                  else if (!check_range (value, bits, flags))
-                   return next_opcode;
+                   break;
                }
              next_opcode++;
            }
-         as_fatal (_("value out of range"));
+
+         if (opcode->operands [i + 1] == 0)
+           as_fatal (_("value out of range"));
+         else
+           opcode = next_opcode;
        }
       else
        {
          /* Not a constant, so use a long instruction.  */
-         return opcode + 2;
+         opcode += 2;
        }
     }
-  else
+
+  match = 0;
+  
+  /* Now search the opcode table table for one with operands
+     that matches what we've got.  */
+  while (!match)
     {
-      match = 0;
-      /* Now search the opcode table table for one with operands
-        that matches what we've got.  */
-      while (!match)
+      match = 1;
+      for (i = 0; opcode->operands[i]; i++)
        {
-         match = 1;
-         for (i = 0; opcode->operands[i]; i++)
+         int flags = d10v_operands[opcode->operands[i]].flags;
+         int X_op = myops[i].X_op;
+         int num = myops[i].X_add_number;
+
+         if (X_op == 0)
            {
-             int flags = d10v_operands[opcode->operands[i]].flags;
-             int X_op = myops[i].X_op;
-             int num = myops[i].X_add_number;
+             match = 0;
+             break;
+           }
 
-             if (X_op == 0)
+         if (flags & OPERAND_REG)
+           {
+             if ((X_op != O_register)
+                 || (num & ~flags
+                     & (OPERAND_GPR | OPERAND_ACC0 | OPERAND_ACC1
+                        | OPERAND_FFLAG | OPERAND_CFLAG
+                        | OPERAND_CONTROL))
+                 || ((flags & OPERAND_SP) && ! (num & OPERAND_SP)))
                {
                  match = 0;
                  break;
                }
+           }
 
-             if (flags & OPERAND_REG)
-               {
-                 if ((X_op != O_register)
-                     || (num & ~flags
-                         & (OPERAND_GPR | OPERAND_ACC0 | OPERAND_ACC1
-                            | OPERAND_FFLAG | OPERAND_CFLAG
-                            | OPERAND_CONTROL))
-                     || ((flags & OPERAND_SP) && ! (num & OPERAND_SP)))
-                   {
-                     match = 0;
-                     break;
-                   }
-               }
+         if (((flags & OPERAND_MINUS)   && ((X_op != O_absent) || (num != OPERAND_MINUS))) ||
+             ((flags & OPERAND_PLUS)    && ((X_op != O_absent) || (num != OPERAND_PLUS))) ||
+             ((flags & OPERAND_ATMINUS) && ((X_op != O_absent) || (num != OPERAND_ATMINUS))) ||
+             ((flags & OPERAND_ATPAR)   && ((X_op != O_absent) || (num != OPERAND_ATPAR))) ||
+             ((flags & OPERAND_ATSIGN)  && ((X_op != O_absent) || ((num != OPERAND_ATSIGN) && (num != OPERAND_ATPAR)))))
+           {
+             match = 0;
+             break;
+           }
 
-             if (((flags & OPERAND_MINUS)   && ((X_op != O_absent) || (num != OPERAND_MINUS))) ||
-                 ((flags & OPERAND_PLUS)    && ((X_op != O_absent) || (num != OPERAND_PLUS))) ||
-                 ((flags & OPERAND_ATMINUS) && ((X_op != O_absent) || (num != OPERAND_ATMINUS))) ||
-                 ((flags & OPERAND_ATPAR)   && ((X_op != O_absent) || (num != OPERAND_ATPAR))) ||
-                 ((flags & OPERAND_ATSIGN)  && ((X_op != O_absent) || ((num != OPERAND_ATSIGN) && (num != OPERAND_ATPAR)))))
-               {
-                 match = 0;
-                 break;
-               }
+         /* Unfortunatly, for the indirect operand in instructions such 
+            as ``ldb r1, @(c,r14)'' this function can be passed 
+            X_op == O_register (because 'c' is a valid register name).  
+            However we cannot just ignore the case when X_op == O_register 
+            but flags & OPERAND_REG is null, so we check to see if a symbol 
+            of the same name as the register exists.  If the symbol does 
+            exist, then the parser was unable to distinguish the two cases 
+            and we fix things here. (Ref: PR14826)  */
 
-             /* Unfortunatly, for the indirect operand in
-                instructions such as ``ldb r1, @(c,r14)'' this
-                function can be passed X_op == O_register (because
-                'c' is a valid register name).  However we cannot
-                just ignore the case when X_op == O_register but
-                flags & OPERAND_REG is null, so we check to see if a
-                symbol of the same name as the register exists.  If
-                the symbol does exist, then the parser was unable to
-                distinguish the two cases and we fix things here.
-                (Ref: PR14826)  */
-
-             if (!(flags & OPERAND_REG) && (X_op == O_register))
-               {
-                 symbolS *sym = find_symbol_matching_register (&myops[i]);
+         if (!(flags & OPERAND_REG) && (X_op == O_register))
+           {
+             symbolS * sym;
+                 
+             sym = find_symbol_matching_register (& myops[i]);
 
-                 if (sym != NULL)
-                   {
-                     myops[i].X_op = X_op = O_symbol;
-                     myops[i].X_add_symbol = sym;
-                   }
-                 else
-                   as_bad
-                     (_("illegal operand - register name found where none expected"));
+             if (sym != NULL)
+               {
+                 myops[i].X_op = X_op = O_symbol;
+                 myops[i].X_add_symbol = sym;
                }
+             else
+               as_bad
+                 (_("illegal operand - register name found where none expected"));
            }
+       }
 
-         /* We're only done if the operands matched so far AND there
+      /* We're only done if the operands matched so far AND there
             are no more to check.  */
-         if (match && myops[i].X_op == 0)
-           break;
-         else
-           match = 0;
+      if (match && myops[i].X_op == 0)
+       break;
+      else
+       match = 0;
 
-         next_opcode = opcode + 1;
+      next_opcode = opcode + 1;
 
-         if (next_opcode->opcode == 0)
-           break;
+      if (next_opcode->opcode == 0)
+       break;
 
-         if (strcmp (next_opcode->name, opcode->name))
-           break;
+      if (strcmp (next_opcode->name, opcode->name))
+       break;
 
-         opcode = next_opcode;
-       }
+      opcode = next_opcode;
     }
 
   if (!match)
@@ -1474,6 +1634,15 @@ find_opcode (opcode, myops)
              myops[i].X_op_symbol = NULL;
            }
        }
+      if ((d10v_operands[opcode->operands[i]].flags & OPERAND_CONTROL)
+         && (myops[i].X_add_number == OPERAND_CONTROL + 4
+             || myops[i].X_add_number == OPERAND_CONTROL + 5
+             || myops[i].X_add_number == OPERAND_CONTROL + 6
+             || myops[i].X_add_number == OPERAND_CONTROL + 12
+             || myops[i].X_add_number == OPERAND_CONTROL + 13
+             || myops[i].X_add_number == OPERAND_CONTROL + 15))
+       as_warn (_("cr%ld is a reserved control register"),
+                myops[i].X_add_number - OPERAND_CONTROL);
     }
   return opcode;
 }
@@ -1503,9 +1672,9 @@ tc_gen_reloc (seg, fixp)
   if (fixp->fx_r_type == BFD_RELOC_VTABLE_INHERIT
       || fixp->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
     reloc->address = fixp->fx_offset;
-
+  
   reloc->addend = fixp->fx_addnumber;
-
+  
   return reloc;
 }
 
@@ -1595,6 +1764,19 @@ md_apply_fix3 (fixP, valP, seg)
     case BFD_RELOC_D10V_10_PCREL_L:
     case BFD_RELOC_D10V_10_PCREL_R:
     case BFD_RELOC_D10V_18_PCREL:
+      /* If the fix is relative to a global symbol, not a section
+        symbol, then ignore the offset.
+         XXX - Do we have to worry about branches to a symbol + offset ?  */
+      if (fixP->fx_addsy != NULL
+         && S_IS_EXTERN (fixP->fx_addsy) )
+        {
+          segT fseg = S_GET_SEGMENT (fixP->fx_addsy);
+          segment_info_type *segf = seg_info(fseg);
+
+         if ( segf && segf->sym != fixP->fx_addsy)
+           value = 0;
+        }
+      /* Drop through.  */
     case BFD_RELOC_D10V_18:
       /* Instruction addresses are always right-shifted by 2.  */
       value >>= AT_WORD_RIGHT_SHIFT;
@@ -1607,8 +1789,10 @@ md_apply_fix3 (fixP, valP, seg)
          rep = (struct d10v_opcode *) hash_find (d10v_hash, "rep");
          repi = (struct d10v_opcode *) hash_find (d10v_hash, "repi");
          if ((insn & FM11) == FM11
-             && (  (repi != NULL && (insn & repi->mask) == (unsigned) repi->opcode)
-                 || (rep != NULL && (insn & rep->mask) == (unsigned) rep->opcode))
+             && ((repi != NULL 
+                  && (insn & repi->mask) == (unsigned) repi->opcode)
+                 || (rep != NULL 
+                     && (insn & rep->mask) == (unsigned) rep->opcode))
              && value < 4)
            as_fatal
              (_("line %d: rep or repi must include at least 4 instructions"),
@@ -1669,15 +1853,14 @@ d10v_cleanup ()
        subseg_set (prev_seg, prev_subseg);
 
       write_1_short (prev_opcode, prev_insn, fixups->next);
-
       subseg_set (seg, subseg);
       prev_opcode = NULL;
     }
   return 1;
 }
 
-/* Like normal .word, except support @word.  */
-/* Clobbers input_line_pointer, checks end-of-line.  */
+/* Like normal .word, except support @word.
+   Clobbers input_line_pointer, checks end-of-line.  */
 
 static void
 d10v_dot_word (dummy)
@@ -1720,8 +1903,8 @@ d10v_dot_word (dummy)
    compatibility problem by simply ignoring any '#' at the beginning
    of an operand.  */
 
-/* Operands that begin with '#' should fall through to here.  */
-/* From expr.c.  */
+/* Operands that begin with '#' should fall through to here.
+   From expr.c.  */
 
 void
 md_operand (expressionP)
index 2bcd2ce..6815950 100644 (file)
@@ -1,5 +1,5 @@
 /* tc-d10v.h -- Header file for tc-d10v.c.
-   Copyright 1996, 1997, 1998, 2000, 2001 Free Software Foundation, Inc.
+   Copyright 1996, 1997, 1998, 2000, 2001, 2002 Free Software Foundation, Inc.
    Written by Martin Hunt, Cygnus Support.
 
    This file is part of GAS, the GNU Assembler.