OSDN Git Service

Merge branch 'binutils' into tmp
[pf3gnuchains/pf3gnuchains4x.git] / gas / config / tc-tilegx.c
diff --git a/gas/config/tc-tilegx.c b/gas/config/tc-tilegx.c
new file mode 100644 (file)
index 0000000..39e1907
--- /dev/null
@@ -0,0 +1,1848 @@
+/* tc-tilegx.c -- Assemble for a Tile-Gx chip.
+   Copyright 2011 Free Software Foundation, Inc.
+
+   This file is part of GAS, the GNU Assembler.
+
+   This program is free software; you can redistribute it and/or modify
+   it under the terms of the GNU General Public License as published by
+   the Free Software Foundation; either version 3 of the License, or
+   (at your option) any later version.
+
+   This program is distributed in the hope that it will be useful,
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+   GNU General Public License for more details.
+
+   You should have received a copy of the GNU General Public License
+   along with this program; if not, write to the Free Software
+   Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston,
+   MA 02110-1301, USA.  */
+
+#include "as.h"
+#include "struc-symbol.h"
+#include "subsegs.h"
+
+#include "elf/tilegx.h"
+#include "opcode/tilegx.h"
+
+#include "dwarf2dbg.h"
+#include "dw2gencfi.h"
+
+#include "safe-ctype.h"
+
+
+/* Special registers.  */
+#define TREG_IDN0     57
+#define TREG_IDN1     58
+#define TREG_UDN0     59
+#define TREG_UDN1     60
+#define TREG_UDN2     61
+#define TREG_UDN3     62
+#define TREG_ZERO     63
+
+
+/* Generic assembler global variables which must be defined by all
+   targets.  */
+
+/* The dwarf2 data alignment, adjusted for 32 or 64 bit.  */
+int tilegx_cie_data_alignment;
+
+/* Characters which always start a comment.  */
+const char comment_chars[] = "#";
+
+/* Characters which start a comment at the beginning of a line.  */
+const char line_comment_chars[] = "#";
+
+/* Characters which may be used to separate multiple commands on a
+   single line.  */
+const char line_separator_chars[] = ";";
+
+/* Characters which are used to indicate an exponent in a floating
+   point number.  */
+const char EXP_CHARS[] = "eE";
+
+/* Characters which mean that a number is a floating point constant,
+   as in 0d1.0.  */
+const char FLT_CHARS[] = "rRsSfFdDxXpP";
+
+/* Either 32 or 64.  */
+static int tilegx_arch_size = 64;
+
+
+const char *
+tilegx_target_format (void)
+{
+  return tilegx_arch_size == 64 ? "elf64-tilegx" : "elf32-tilegx";
+}
+
+
+#define OPTION_32 (OPTION_MD_BASE + 0)
+#define OPTION_64 (OPTION_MD_BASE + 1)
+
+const char *md_shortopts = "VQ:";
+
+struct option md_longopts[] =
+{
+  {"32", no_argument, NULL, OPTION_32},
+  {"64", no_argument, NULL, OPTION_64},
+  {NULL, no_argument, NULL, 0}
+};
+
+size_t md_longopts_size = sizeof (md_longopts);
+
+int
+md_parse_option (int c, char *arg ATTRIBUTE_UNUSED)
+{
+  switch (c)
+    {
+      /* -Qy, -Qn: SVR4 arguments controlling whether a .comment section
+        should be emitted or not.  FIXME: Not implemented.  */
+    case 'Q':
+      break;
+
+      /* -V: SVR4 argument to print version ID.  */
+    case 'V':
+      print_version_id ();
+      break;
+
+    case OPTION_32:
+      tilegx_arch_size = 32;
+      break;
+
+    case OPTION_64:
+      tilegx_arch_size = 64;
+      break;
+
+    default:
+      return 0;
+    }
+
+  return 1;
+}
+
+void
+md_show_usage (FILE *stream)
+{
+  fprintf (stream, _("\
+  -Q                      ignored\n\
+  -V                      print assembler version number\n\
+  --32/--64               generate 32bit/64bit code\n"));
+}
+
+
+/* Extra expression types.  */
+
+#define O_hw0                  O_md1
+#define O_hw1                  O_md2
+#define O_hw2                  O_md3
+#define O_hw3                  O_md4
+#define O_hw0_last             O_md5
+#define O_hw1_last             O_md6
+#define O_hw2_last             O_md7
+#define O_hw0_got              O_md8
+#define O_hw1_got              O_md9
+#define O_hw2_got              O_md10
+#define O_hw3_got              O_md11
+#define O_hw0_last_got         O_md12
+#define O_hw1_last_got         O_md13
+#define O_hw2_last_got         O_md14
+#define O_plt                  O_md15
+#define O_hw0_tls_gd           O_md16
+#define O_hw1_tls_gd           O_md17
+#define O_hw2_tls_gd           O_md18
+#define O_hw3_tls_gd           O_md19
+#define O_hw0_last_tls_gd      O_md20
+#define O_hw1_last_tls_gd      O_md21
+#define O_hw2_last_tls_gd      O_md22
+#define O_hw0_tls_ie           O_md23
+#define O_hw1_tls_ie           O_md24
+#define O_hw2_tls_ie           O_md25
+#define O_hw3_tls_ie           O_md26
+#define O_hw0_last_tls_ie      O_md27
+#define O_hw1_last_tls_ie      O_md28
+#define O_hw2_last_tls_ie      O_md29
+
+static struct hash_control *special_operator_hash;
+
+/* Hash tables for instruction mnemonic lookup.  */
+static struct hash_control *op_hash;
+
+/* Hash table for spr lookup.  */
+static struct hash_control *spr_hash;
+
+/* True temporarily while parsing an SPR expression. This changes the
+ * namespace to include SPR names.  */
+static int parsing_spr;
+
+/* Are we currently inside `{ ... }'?  */
+static int inside_bundle;
+
+struct tilegx_instruction
+{
+  const struct tilegx_opcode *opcode;
+  tilegx_pipeline pipe;
+  expressionS operand_values[TILEGX_MAX_OPERANDS];
+};
+
+/* This keeps track of the current bundle being built up.  */
+static struct tilegx_instruction current_bundle[TILEGX_MAX_INSTRUCTIONS_PER_BUNDLE];
+
+/* Index in current_bundle for the next instruction to parse.  */
+static int current_bundle_index;
+
+/* Allow 'r63' in addition to 'zero', etc. Normally we disallow this as
+   'zero' is not a real register, so using it accidentally would be a
+   nasty bug. For other registers, such as 'sp', code using multiple names
+   for the same physical register is excessively confusing.
+
+   The '.require_canonical_reg_names' pseudo-op turns this error on,
+   and the '.no_require_canonical_reg_names' pseudo-op turns this off.
+   By default the error is on.  */
+static int require_canonical_reg_names;
+
+/* Allow bundles that do undefined or suspicious things like write
+   two different values to the same register at the same time.
+
+   The '.no_allow_suspicious_bundles' pseudo-op turns this error on,
+   and the '.allow_suspicious_bundles' pseudo-op turns this off.  */
+static int allow_suspicious_bundles;
+
+
+/* A hash table of main processor registers, mapping each register name
+   to its index.
+
+   Furthermore, if the register number is greater than the number
+   of registers for that processor, the user used an illegal alias
+   for that register (e.g. r63 instead of zero), so we should generate
+   a warning. The attempted register number can be found by clearing
+   NONCANONICAL_REG_NAME_FLAG.  */
+static struct hash_control *main_reg_hash;
+
+
+/* We cannot unambiguously store a 0 in a hash table and look it up,
+   so we OR in this flag to every canonical register.  */
+#define CANONICAL_REG_NAME_FLAG    0x1000
+
+/* By default we disallow register aliases like r63, but we record
+   them in the hash table in case the .no_require_canonical_reg_names
+   directive is used. Noncanonical names have this value added to them.  */
+#define NONCANONICAL_REG_NAME_FLAG 0x2000
+
+/* Discards flags for register hash table entries and returns the
+   reg number.  */
+#define EXTRACT_REGNO(p) ((p) & 63)
+
+/* This function is called once, at assembler startup time.  It should
+   set up all the tables, etc., that the MD part of the assembler will
+   need.  */
+
+void
+md_begin (void)
+{
+  const struct tilegx_opcode *op;
+  int i;
+
+  /* Guarantee text section is aligned.  */
+  bfd_set_section_alignment (stdoutput, text_section,
+                             TILEGX_LOG2_BUNDLE_ALIGNMENT_IN_BYTES);
+
+  require_canonical_reg_names = 1;
+  allow_suspicious_bundles = 0;
+  current_bundle_index = 0;
+  inside_bundle = 0;
+
+  tilegx_cie_data_alignment = (tilegx_arch_size == 64 ? -8 : -4);
+
+  /* Initialize special operator hash table.  */
+  special_operator_hash = hash_new ();
+#define INSERT_SPECIAL_OP(name)                                        \
+  hash_insert (special_operator_hash, #name, (void *)O_##name)
+
+  INSERT_SPECIAL_OP (hw0);
+  INSERT_SPECIAL_OP (hw1);
+  INSERT_SPECIAL_OP (hw2);
+  INSERT_SPECIAL_OP (hw3);
+  INSERT_SPECIAL_OP (hw0_last);
+  INSERT_SPECIAL_OP (hw1_last);
+  INSERT_SPECIAL_OP (hw2_last);
+  /* hw3_last is a convenience alias for the equivalent hw3.  */
+  hash_insert (special_operator_hash, "hw3_last", (void*)O_hw3);
+  INSERT_SPECIAL_OP (hw0_got);
+  INSERT_SPECIAL_OP (hw1_got);
+  INSERT_SPECIAL_OP (hw2_got);
+  INSERT_SPECIAL_OP (hw3_got);
+  INSERT_SPECIAL_OP (hw0_last_got);
+  INSERT_SPECIAL_OP (hw1_last_got);
+  INSERT_SPECIAL_OP (hw2_last_got);
+  INSERT_SPECIAL_OP(plt);
+  INSERT_SPECIAL_OP (hw0_tls_gd);
+  INSERT_SPECIAL_OP (hw1_tls_gd);
+  INSERT_SPECIAL_OP (hw2_tls_gd);
+  INSERT_SPECIAL_OP (hw3_tls_gd);
+  INSERT_SPECIAL_OP (hw0_last_tls_gd);
+  INSERT_SPECIAL_OP (hw1_last_tls_gd);
+  INSERT_SPECIAL_OP (hw2_last_tls_gd);
+  INSERT_SPECIAL_OP (hw0_tls_ie);
+  INSERT_SPECIAL_OP (hw1_tls_ie);
+  INSERT_SPECIAL_OP (hw2_tls_ie);
+  INSERT_SPECIAL_OP (hw3_tls_ie);
+  INSERT_SPECIAL_OP (hw0_last_tls_ie);
+  INSERT_SPECIAL_OP (hw1_last_tls_ie);
+  INSERT_SPECIAL_OP (hw2_last_tls_ie);
+#undef INSERT_SPECIAL_OP
+
+  /* Initialize op_hash hash table.  */
+  op_hash = hash_new ();
+  for (op = &tilegx_opcodes[0]; op->name != NULL; op++)
+    {
+      const char *hash_err = hash_insert (op_hash, op->name, (void *)op);
+      if (hash_err != NULL)
+       as_fatal (_("Internal Error:  Can't hash %s: %s"), op->name, hash_err);
+    }
+
+  /* Initialize the spr hash table.  */
+  parsing_spr = 0;
+  spr_hash = hash_new ();
+  for (i = 0; i < tilegx_num_sprs; i++)
+    hash_insert (spr_hash, tilegx_sprs[i].name,
+                 (void *) &tilegx_sprs[i]);
+
+  /* Set up the main_reg_hash table. We use this instead of
+     creating a symbol in the register section to avoid ambiguities
+     with labels that have the same names as registers.  */
+  main_reg_hash = hash_new ();
+  for (i = 0; i < TILEGX_NUM_REGISTERS; i++)
+    {
+      char buf[64];
+
+      hash_insert (main_reg_hash, tilegx_register_names[i],
+                  (void *) (long) (i | CANONICAL_REG_NAME_FLAG));
+
+      /* See if we should insert a noncanonical alias, like r63.  */
+      sprintf (buf, "r%d", i);
+      if (strcmp (buf, tilegx_register_names[i]) != 0)
+       hash_insert (main_reg_hash, xstrdup (buf),
+                    (void *) (long) (i | NONCANONICAL_REG_NAME_FLAG));
+    }
+}
+
+#define BUNDLE_TEMPLATE_MASK(p0, p1, p2) \
+  ((p0) | ((p1) << 8) | ((p2) << 16))
+#define BUNDLE_TEMPLATE(p0, p1, p2) \
+  { { (p0), (p1), (p2) }, \
+     BUNDLE_TEMPLATE_MASK(1 << (p0), 1 << (p1), (1 << (p2))) \
+  }
+
+#define NO_PIPELINE TILEGX_NUM_PIPELINE_ENCODINGS
+
+struct bundle_template
+{
+  tilegx_pipeline pipe[TILEGX_MAX_INSTRUCTIONS_PER_BUNDLE];
+  unsigned int pipe_mask;
+};
+
+static const struct bundle_template bundle_templates[] =
+{
+  /* In Y format we must always have something in Y2, since it has
+     no fnop, so this conveys that Y2 must always be used.  */
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y0, TILEGX_PIPELINE_Y2, NO_PIPELINE),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y1, TILEGX_PIPELINE_Y2, NO_PIPELINE),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y2, TILEGX_PIPELINE_Y0, NO_PIPELINE),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y2, TILEGX_PIPELINE_Y1, NO_PIPELINE),
+
+  /* Y format has three instructions.  */
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y0,TILEGX_PIPELINE_Y1,TILEGX_PIPELINE_Y2),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y0,TILEGX_PIPELINE_Y2,TILEGX_PIPELINE_Y1),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y1,TILEGX_PIPELINE_Y0,TILEGX_PIPELINE_Y2),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y1,TILEGX_PIPELINE_Y2,TILEGX_PIPELINE_Y0),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y2,TILEGX_PIPELINE_Y0,TILEGX_PIPELINE_Y1),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_Y2,TILEGX_PIPELINE_Y1,TILEGX_PIPELINE_Y0),
+
+  /* X format has only two instructions.  */
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_X0, TILEGX_PIPELINE_X1, NO_PIPELINE),
+  BUNDLE_TEMPLATE(TILEGX_PIPELINE_X1, TILEGX_PIPELINE_X0, NO_PIPELINE)
+};
+
+
+static void
+prepend_nop_to_bundle (tilegx_mnemonic mnemonic)
+{
+  memmove (&current_bundle[1], &current_bundle[0],
+          current_bundle_index * sizeof current_bundle[0]);
+  current_bundle[0].opcode = &tilegx_opcodes[mnemonic];
+  ++current_bundle_index;
+}
+
+static tilegx_bundle_bits
+insert_operand (tilegx_bundle_bits bits,
+                const struct tilegx_operand *operand,
+                int operand_value,
+                char *file,
+                unsigned lineno)
+{
+  /* Range-check the immediate.  */
+  int num_bits = operand->num_bits;
+
+  operand_value >>= operand->rightshift;
+
+  if (bfd_check_overflow (operand->is_signed
+                          ? complain_overflow_signed
+                          : complain_overflow_unsigned,
+                          num_bits,
+                          0,
+                          bfd_arch_bits_per_address (stdoutput),
+                          operand_value)
+      != bfd_reloc_ok)
+    {
+      offsetT min, max;
+      if (operand->is_signed)
+       {
+         min = -(1 << (num_bits - 1));
+         max = (1 << (num_bits - 1)) - 1;
+       }
+      else
+       {
+         min = 0;
+         max = (1 << num_bits) - 1;
+       }
+      as_bad_value_out_of_range (_("operand"), operand_value, min, max,
+                                file, lineno);
+    }
+
+  /* Write out the bits for the immediate.  */
+  return bits | operand->insert (operand_value);
+}
+
+
+static int
+apply_special_operator (operatorT op, offsetT num, char *file, unsigned lineno)
+{
+  int ret;
+  int check_shift = -1;
+
+  switch (op)
+    {
+    case O_hw0_last_tls_gd:
+    case O_hw0_last_tls_ie:
+    case O_hw0_last:
+      check_shift = 0;
+      /* Fall through.  */
+    case O_hw0_tls_gd:
+    case O_hw0_tls_ie:
+    case O_hw0:
+      ret = (signed short)num;
+      break;
+
+    case O_hw1_last_tls_gd:
+    case O_hw1_last_tls_ie:
+    case O_hw1_last:
+      check_shift = 16;
+      /* Fall through.  */
+    case O_hw1_tls_gd:
+    case O_hw1_tls_ie:
+    case O_hw1:
+      ret = (signed short)(num >> 16);
+      break;
+
+    case O_hw2_last_tls_gd:
+    case O_hw2_last_tls_ie:
+    case O_hw2_last:
+      check_shift = 32;
+      /* Fall through.  */
+    case O_hw2_tls_gd:
+    case O_hw2_tls_ie:
+    case O_hw2:
+      ret = (signed short)(num >> 32);
+      break;
+
+    case O_hw3_tls_gd:
+    case O_hw3_tls_ie:
+    case O_hw3:
+      ret = (signed short)(num >> 48);
+      break;
+
+    default:
+      abort ();
+      break;
+    }
+
+  if (check_shift >= 0 && ret != (num >> check_shift))
+    {
+      as_bad_value_out_of_range (_("operand"), num,
+                                 ~0ULL << (check_shift + 16 - 1),
+                                 ~0ULL >> (64 - (check_shift + 16 - 1)),
+                                 file, lineno);
+    }
+
+  return ret;
+}
+
+static tilegx_bundle_bits
+emit_tilegx_instruction (tilegx_bundle_bits bits,
+                        int num_operands,
+                        const unsigned char *operands,
+                        expressionS *operand_values,
+                        char *bundle_start)
+{
+  int i;
+
+  for (i = 0; i < num_operands; i++)
+    {
+      const struct tilegx_operand *operand =
+       &tilegx_operands[operands[i]];
+      expressionS *operand_exp = &operand_values[i];
+      int is_pc_relative = operand->is_pc_relative;
+
+      if (operand_exp->X_op == O_register
+         || (operand_exp->X_op == O_constant && !is_pc_relative))
+       {
+         /* We know what the bits are right now, so insert them.  */
+         bits = insert_operand (bits, operand, operand_exp->X_add_number,
+                                NULL, 0);
+       }
+      else
+       {
+         bfd_reloc_code_real_type reloc = operand->default_reloc;
+         expressionS subexp;
+         int die = 0, use_subexp = 0, require_symbol = 0;
+         fixS *fixP;
+
+         /* Take an expression like hw0(x) and turn it into x with
+            a different reloc type.  */
+         switch (operand_exp->X_op)
+           {
+#define HANDLE_OP16(suffix)                                    \
+             switch (reloc)                                    \
+               {                                               \
+               case BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST:        \
+                 reloc = BFD_RELOC_TILEGX_IMM16_X0_##suffix;   \
+                 break;                                        \
+               case BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST:        \
+                 reloc = BFD_RELOC_TILEGX_IMM16_X1_##suffix;   \
+                 break;                                        \
+               default:                                        \
+                 die = 1;                                      \
+                 break;                                        \
+               }                                               \
+             use_subexp = 1
+
+           case O_hw0:
+             HANDLE_OP16 (HW0);
+             break;
+
+           case O_hw1:
+             HANDLE_OP16 (HW1);
+             break;
+
+           case O_hw2:
+             HANDLE_OP16 (HW2);
+             break;
+
+           case O_hw3:
+             HANDLE_OP16 (HW3);
+             break;
+
+           case O_hw0_last:
+             HANDLE_OP16 (HW0_LAST);
+             break;
+
+           case O_hw1_last:
+             HANDLE_OP16 (HW1_LAST);
+             break;
+
+           case O_hw2_last:
+             HANDLE_OP16 (HW2_LAST);
+             break;
+
+           case O_hw0_got:
+             HANDLE_OP16 (HW0_GOT);
+             require_symbol = 1;
+             break;
+
+           case O_hw1_got:
+             HANDLE_OP16 (HW1_GOT);
+             require_symbol = 1;
+             break;
+
+           case O_hw2_got:
+             HANDLE_OP16 (HW2_GOT);
+             require_symbol = 1;
+             break;
+
+           case O_hw3_got:
+             HANDLE_OP16 (HW3_GOT);
+             require_symbol = 1;
+             break;
+
+           case O_hw0_last_got:
+             HANDLE_OP16 (HW0_LAST_GOT);
+             require_symbol = 1;
+             break;
+
+           case O_hw1_last_got:
+             HANDLE_OP16 (HW1_LAST_GOT);
+             require_symbol = 1;
+             break;
+
+           case O_hw2_last_got:
+             HANDLE_OP16 (HW2_LAST_GOT);
+             require_symbol = 1;
+             break;
+
+           case O_hw0_tls_gd:
+             HANDLE_OP16 (HW0_TLS_GD);
+             require_symbol = 1;
+             break;
+
+           case O_hw1_tls_gd:
+             HANDLE_OP16 (HW1_TLS_GD);
+             require_symbol = 1;
+             break;
+
+           case O_hw2_tls_gd:
+             HANDLE_OP16 (HW2_TLS_GD);
+             require_symbol = 1;
+             break;
+
+           case O_hw3_tls_gd:
+             HANDLE_OP16 (HW3_TLS_GD);
+             require_symbol = 1;
+             break;
+
+           case O_hw0_last_tls_gd:
+             HANDLE_OP16 (HW0_LAST_TLS_GD);
+             require_symbol = 1;
+             break;
+
+           case O_hw1_last_tls_gd:
+             HANDLE_OP16 (HW1_LAST_TLS_GD);
+             require_symbol = 1;
+             break;
+
+           case O_hw2_last_tls_gd:
+             HANDLE_OP16 (HW2_LAST_TLS_GD);
+             require_symbol = 1;
+             break;
+
+           case O_hw0_tls_ie:
+             HANDLE_OP16 (HW0_TLS_IE);
+             require_symbol = 1;
+             break;
+
+           case O_hw1_tls_ie:
+             HANDLE_OP16 (HW1_TLS_IE);
+             require_symbol = 1;
+             break;
+
+           case O_hw2_tls_ie:
+             HANDLE_OP16 (HW2_TLS_IE);
+             require_symbol = 1;
+             break;
+
+           case O_hw3_tls_ie:
+             HANDLE_OP16 (HW3_TLS_IE);
+             require_symbol = 1;
+             break;
+
+           case O_hw0_last_tls_ie:
+             HANDLE_OP16 (HW0_LAST_TLS_IE);
+             require_symbol = 1;
+             break;
+
+           case O_hw1_last_tls_ie:
+             HANDLE_OP16 (HW1_LAST_TLS_IE);
+             require_symbol = 1;
+             break;
+
+           case O_hw2_last_tls_ie:
+             HANDLE_OP16 (HW2_LAST_TLS_IE);
+             require_symbol = 1;
+             break;
+
+#undef HANDLE_OP16
+
+           case O_plt:
+             switch (reloc)
+               {
+               case BFD_RELOC_TILEGX_JUMPOFF_X1:
+                 reloc = BFD_RELOC_TILEGX_JUMPOFF_X1_PLT;
+                 break;
+               default:
+                 die = 1;
+                 break;
+               }
+             use_subexp = 1;
+             require_symbol = 1;
+             break;
+
+           default:
+             /* Do nothing.  */
+             break;
+           }
+
+         if (die)
+           {
+             as_bad (_("Invalid operator for operand."));
+           }
+         else if (use_subexp)
+           {
+             /* Now that we've changed the reloc, change ha16(x) into x,
+                etc.  */
+
+             if (operand_exp->X_add_symbol->sy_value.X_md)
+               {
+                 /* HACK: We used X_md to mark this symbol as a fake wrapper
+                    around a real expression. To unwrap it, we just grab its
+                    value here.  */
+                 operand_exp = &operand_exp->X_add_symbol->sy_value;
+
+                 if (require_symbol)
+                   {
+                     /* Look at the expression, and reject it if it's not a
+                        plain symbol.  */
+                     if (operand_exp->X_op != O_symbol
+                         || operand_exp->X_add_number != 0)
+                       as_bad (_("Operator may only be applied to symbols."));
+                   }
+               }
+             else
+               {
+                 /* The value of this expression is an actual symbol, so
+                    turn that into an expression.  */
+                 memset (&subexp, 0, sizeof subexp);
+                 subexp.X_op = O_symbol;
+                 subexp.X_add_symbol = operand_exp->X_add_symbol;
+                 operand_exp = &subexp;
+               }
+           }
+
+         /* Create a fixup to handle this later.  */
+         fixP = fix_new_exp (frag_now,
+                             bundle_start - frag_now->fr_literal,
+                             (operand->num_bits + 7) >> 3,
+                             operand_exp,
+                             is_pc_relative,
+                             reloc);
+         fixP->tc_fix_data = operand;
+
+         /* Don't do overflow checking if we are applying a function like
+            ha16.  */
+         fixP->fx_no_overflow |= use_subexp;
+       }
+    }
+  return bits;
+}
+
+
+/* Detects and complains if two instructions in current_bundle write
+   to the same register, either implicitly or explicitly, or if a
+   read-only register is written.  */
+static void
+check_illegal_reg_writes (void)
+{
+  BFD_HOST_U_64_BIT all_regs_written = 0;
+  int j;
+
+  for (j = 0; j < current_bundle_index; j++)
+    {
+      const struct tilegx_instruction *instr = &current_bundle[j];
+      int k;
+      BFD_HOST_U_64_BIT regs =
+       ((BFD_HOST_U_64_BIT)1) << instr->opcode->implicitly_written_register;
+      BFD_HOST_U_64_BIT conflict;
+
+      for (k = 0; k < instr->opcode->num_operands; k++)
+       {
+         const struct tilegx_operand *operand =
+           &tilegx_operands[instr->opcode->operands[instr->pipe][k]];
+
+         if (operand->is_dest_reg)
+           {
+             int regno = instr->operand_values[k].X_add_number;
+             BFD_HOST_U_64_BIT mask = ((BFD_HOST_U_64_BIT)1) << regno;
+
+             if ((mask & (  (((BFD_HOST_U_64_BIT)1) << TREG_IDN1)
+                            | (((BFD_HOST_U_64_BIT)1) << TREG_UDN1)
+                            | (((BFD_HOST_U_64_BIT)1) << TREG_UDN2)
+                            | (((BFD_HOST_U_64_BIT)1) << TREG_UDN3))) != 0
+                 && !allow_suspicious_bundles)
+               {
+                 as_bad (_("Writes to register '%s' are not allowed."),
+                         tilegx_register_names[regno]);
+               }
+
+             regs |= mask;
+           }
+       }
+
+      /* Writing to the zero register doesn't count.  */
+      regs &= ~(((BFD_HOST_U_64_BIT)1) << TREG_ZERO);
+
+      conflict = all_regs_written & regs;
+      if (conflict != 0 && !allow_suspicious_bundles)
+       {
+         /* Find which register caused the conflict.  */
+         const char *conflicting_reg_name = "???";
+         int i;
+
+         for (i = 0; i < TILEGX_NUM_REGISTERS; i++)
+           {
+             if (((conflict >> i) & 1) != 0)
+               {
+                 conflicting_reg_name = tilegx_register_names[i];
+                 break;
+               }
+           }
+
+         as_bad (_("Two instructions in the same bundle both write "
+                   "to register %s, which is not allowed."),
+                 conflicting_reg_name);
+       }
+
+      all_regs_written |= regs;
+    }
+}
+
+
+static void
+tilegx_flush_bundle (void)
+{
+  unsigned i;
+  int j;
+  addressT addr_mod;
+  unsigned compatible_pipes;
+  const struct bundle_template *match;
+  char *f;
+
+  inside_bundle = 0;
+
+  switch (current_bundle_index)
+    {
+    case 0:
+      /* No instructions.  */
+      return;
+    case 1:
+      if (current_bundle[0].opcode->can_bundle)
+       {
+         /* Simplify later logic by adding an explicit fnop.  */
+         prepend_nop_to_bundle (TILEGX_OPC_FNOP);
+       }
+      else
+       {
+         /* This instruction cannot be bundled with anything else.
+            Prepend an explicit 'nop', rather than an 'fnop', because
+            fnops can be replaced by later binary-processing tools while
+            nops cannot.  */
+         prepend_nop_to_bundle (TILEGX_OPC_NOP);
+       }
+      break;
+    default:
+      if (!allow_suspicious_bundles)
+       {
+         /* Make sure all instructions can be bundled with other
+            instructions.  */
+         const struct tilegx_opcode *cannot_bundle = NULL;
+         bfd_boolean seen_non_nop = FALSE;
+
+         for (j = 0; j < current_bundle_index; j++)
+           {
+             const struct tilegx_opcode *op = current_bundle[j].opcode;
+
+             if (!op->can_bundle && cannot_bundle == NULL)
+               cannot_bundle = op;
+             else if (op->mnemonic != TILEGX_OPC_NOP
+                      && op->mnemonic != TILEGX_OPC_INFO
+                      && op->mnemonic != TILEGX_OPC_INFOL)
+               seen_non_nop = TRUE;
+           }
+
+         if (cannot_bundle != NULL && seen_non_nop)
+           {
+             current_bundle_index = 0;
+             as_bad (_("'%s' may not be bundled with other instructions."),
+                     cannot_bundle->name);
+             return;
+           }
+       }
+      break;
+    }
+
+  compatible_pipes =
+    BUNDLE_TEMPLATE_MASK(current_bundle[0].opcode->pipes,
+                         current_bundle[1].opcode->pipes,
+                         (current_bundle_index == 3
+                          ? current_bundle[2].opcode->pipes
+                          : (1 << NO_PIPELINE)));
+
+  /* Find a template that works, if any.  */
+  match = NULL;
+  for (i = 0; i < sizeof bundle_templates / sizeof bundle_templates[0]; i++)
+    {
+      const struct bundle_template *b = &bundle_templates[i];
+      if ((b->pipe_mask & compatible_pipes) == b->pipe_mask)
+       {
+         match = b;
+         break;
+       }
+    }
+
+  if (match == NULL)
+    {
+      current_bundle_index = 0;
+      as_bad (_("Invalid combination of instructions for bundle."));
+      return;
+    }
+
+  /* If the section seems to have no alignment set yet, go ahead and
+     make it large enough to hold code.  */
+  if (bfd_get_section_alignment (stdoutput, now_seg) == 0)
+    bfd_set_section_alignment (stdoutput, now_seg,
+                               TILEGX_LOG2_BUNDLE_ALIGNMENT_IN_BYTES);
+
+  for (j = 0; j < current_bundle_index; j++)
+    current_bundle[j].pipe = match->pipe[j];
+
+  if (current_bundle_index == 2 && !tilegx_is_x_pipeline (match->pipe[0]))
+    {
+      /* We are in Y mode with only two instructions, so add an FNOP.  */
+      prepend_nop_to_bundle (TILEGX_OPC_FNOP);
+
+      /* Figure out what pipe the fnop must be in via arithmetic.
+       * p0 + p1 + p2 must sum to the sum of TILEGX_PIPELINE_Y[012].  */
+      current_bundle[0].pipe =
+       (tilegx_pipeline)((TILEGX_PIPELINE_Y0
+                          + TILEGX_PIPELINE_Y1
+                          + TILEGX_PIPELINE_Y2) -
+                         (current_bundle[1].pipe + current_bundle[2].pipe));
+    }
+
+  check_illegal_reg_writes ();
+
+  f = frag_more (TILEGX_BUNDLE_SIZE_IN_BYTES);
+
+  /* Check to see if this bundle is at an offset that is a multiple of 8-bytes
+     from the start of the frag.  */
+  addr_mod = frag_now_fix () & (TILEGX_BUNDLE_ALIGNMENT_IN_BYTES - 1);
+  if (frag_now->has_code && frag_now->insn_addr != addr_mod)
+    as_bad (_("instruction address is not a multiple of 8"));
+  frag_now->insn_addr = addr_mod;
+  frag_now->has_code = 1;
+
+  tilegx_bundle_bits bits = 0;
+  for (j = 0; j < current_bundle_index; j++)
+    {
+      struct tilegx_instruction *instr = &current_bundle[j];
+      tilegx_pipeline pipeline = instr->pipe;
+      const struct tilegx_opcode *opcode = instr->opcode;
+
+      bits |= emit_tilegx_instruction (opcode->fixed_bit_values[pipeline],
+                                      opcode->num_operands,
+                                      &opcode->operands[pipeline][0],
+                                      instr->operand_values,
+                                      f);
+    }
+
+  number_to_chars_littleendian (f, bits, 8);
+  current_bundle_index = 0;
+
+  /* Emit DWARF2 debugging information.  */
+  dwarf2_emit_insn (TILEGX_BUNDLE_SIZE_IN_BYTES);
+}
+
+
+/* Extend the expression parser to handle hw0(label), etc.
+   as well as SPR names when in the context of parsing an SPR.  */
+
+int
+tilegx_parse_name (char *name, expressionS *e, char *nextcharP)
+{
+  operatorT op = O_illegal;
+
+  if (parsing_spr)
+    {
+      void* val = hash_find (spr_hash, name);
+      if (val == NULL)
+       return 0;
+
+      memset (e, 0, sizeof *e);
+      e->X_op = O_constant;
+      e->X_add_number = ((const struct tilegx_spr *)val)->number;
+      return 1;
+    }
+
+  if (*nextcharP != '(')
+    {
+      /* hw0, etc. not followed by a paren is just a label with that name.  */
+      return 0;
+    }
+  else
+    {
+      /* Look up the operator in our table.  */
+      void* val = hash_find (special_operator_hash, name);
+      if (val == 0)
+       return 0;
+      op = (operatorT)(long)val;
+    }
+
+  /* Restore old '(' and skip it.  */
+  *input_line_pointer = '(';
+  ++input_line_pointer;
+
+  expression (e);
+
+  if (*input_line_pointer != ')')
+    {
+      as_bad (_("Missing ')'"));
+      *nextcharP = *input_line_pointer;
+      return 0;
+    }
+  /* Skip ')'.  */
+  ++input_line_pointer;
+
+  if (e->X_op == O_register || e->X_op == O_absent)
+    {
+      as_bad (_("Invalid expression."));
+      e->X_op = O_constant;
+      e->X_add_number = 0;
+    }
+  else
+    {
+      /* Wrap subexpression with a unary operator.  */
+      symbolS *sym = make_expr_symbol (e);
+
+      if (sym != e->X_add_symbol)
+       {
+         /* HACK: mark this symbol as a temporary wrapper around a proper
+            expression, so we can unwrap it later once we have communicated
+            the relocation type.  */
+         sym->sy_value.X_md = 1;
+       }
+
+      memset (e, 0, sizeof *e);
+      e->X_op = op;
+      e->X_add_symbol = sym;
+      e->X_add_number = 0;
+    }
+
+  *nextcharP = *input_line_pointer;
+  return 1;
+}
+
+
+/* Parses an expression which must be a register name.  */
+
+static void
+parse_reg_expression (expressionS* expression)
+{
+  /* Zero everything to make sure we don't miss any flags.  */
+  memset (expression, 0, sizeof *expression);
+
+  char* regname = input_line_pointer;
+  char terminating_char = get_symbol_end ();
+
+  void* pval = hash_find (main_reg_hash, regname);
+
+  if (pval == NULL)
+    {
+      as_bad (_("Expected register, got '%s'."), regname);
+    }
+
+  int regno_and_flags = (int)(size_t)pval;
+  int regno = EXTRACT_REGNO(regno_and_flags);
+
+  if ((regno_and_flags & NONCANONICAL_REG_NAME_FLAG)
+      && require_canonical_reg_names)
+    {
+      as_warn (_("Found use of non-canonical register name %s; "
+                "use %s instead."),
+              regname,
+              tilegx_register_names[regno]);
+    }
+
+  /* Restore the old character following the register name.  */
+  *input_line_pointer = terminating_char;
+
+  /* Fill in the expression fields to indicate it's a register.  */
+  expression->X_op = O_register;
+  expression->X_add_number = regno;
+}
+
+
+/* Parses and type-checks comma-separated operands in input_line_pointer.  */
+
+static void
+parse_operands (const char *opcode_name,
+                const unsigned char *operands,
+                int num_operands,
+                expressionS *operand_values)
+{
+  int i;
+
+  memset (operand_values, 0, num_operands * sizeof operand_values[0]);
+
+  SKIP_WHITESPACE ();
+  for (i = 0; i < num_operands; i++)
+    {
+      tilegx_operand_type type = tilegx_operands[operands[i]].type;
+
+      SKIP_WHITESPACE ();
+
+      if (type == TILEGX_OP_TYPE_REGISTER)
+       {
+         parse_reg_expression (&operand_values[i]);
+       }
+      else if (*input_line_pointer == '}')
+       {
+         operand_values[i].X_op = O_absent;
+       }
+      else if (type == TILEGX_OP_TYPE_SPR)
+       {
+         /* Modify the expression parser to add SPRs to the namespace.  */
+         parsing_spr = 1;
+         expression (&operand_values[i]);
+         parsing_spr = 0;
+       }
+      else
+       {
+         expression (&operand_values[i]);
+       }
+
+      SKIP_WHITESPACE ();
+
+      if (i + 1 < num_operands)
+       {
+         int separator = (unsigned char)*input_line_pointer++;
+
+         if (is_end_of_line[separator] || (separator == '}'))
+           {
+             as_bad (_("Too few operands to '%s'."), opcode_name);
+             return;
+           }
+         else if (separator != ',')
+           {
+             as_bad (_("Unexpected character '%c' after operand %d to %s."),
+                     (char)separator, i + 1, opcode_name);
+             return;
+           }
+       }
+
+      /* Arbitrarily use the first valid pipe to get the operand type,
+        since they are all the same.  */
+      switch (tilegx_operands[operands[i]].type)
+       {
+       case TILEGX_OP_TYPE_REGISTER:
+         /* Handled in parse_reg_expression already.  */
+         break;
+       case TILEGX_OP_TYPE_SPR:
+         /* Fall through  */
+       case TILEGX_OP_TYPE_IMMEDIATE:
+         /* Fall through  */
+       case TILEGX_OP_TYPE_ADDRESS:
+         if (   operand_values[i].X_op == O_register
+                || operand_values[i].X_op == O_illegal
+                || operand_values[i].X_op == O_absent)
+           as_bad (_("Expected immediate expression"));
+         break;
+       default:
+         abort();
+       }
+    }
+
+  if (!is_end_of_line[(unsigned char)*input_line_pointer])
+    {
+      switch (*input_line_pointer)
+       {
+       case '}':
+         if (!inside_bundle)
+           as_bad (_("Found '}' when not bundling."));
+         ++input_line_pointer;
+         inside_bundle = 0;
+         demand_empty_rest_of_line ();
+         break;
+
+       case ',':
+         as_bad (_("Too many operands"));
+         break;
+
+       default:
+         /* Use default error for unrecognized garbage.  */
+         demand_empty_rest_of_line ();
+         break;
+       }
+    }
+}
+
+
+/* This is the guts of the machine-dependent assembler.  STR points to a
+   machine dependent instruction.  This function is supposed to emit the
+   frags/bytes it assembles to.  */
+
+void
+md_assemble (char *str)
+{
+  char old_char;
+  size_t opname_len;
+  char *old_input_line_pointer;
+  const struct tilegx_opcode *op;
+  int first_pipe;
+
+  /* Split off the opcode and look it up.  */
+  opname_len = strcspn (str, " {}");
+  old_char = str[opname_len];
+  str[opname_len] = '\0';
+
+  op = hash_find(op_hash, str);
+  str[opname_len] = old_char;
+  if (op == NULL)
+    {
+      as_bad (_("Unknown opcode `%.*s'."), (int)opname_len, str);
+      return;
+    }
+
+  /* Prepare to parse the operands.  */
+  old_input_line_pointer = input_line_pointer;
+  input_line_pointer = str + opname_len;
+  SKIP_WHITESPACE ();
+
+  if (current_bundle_index == TILEGX_MAX_INSTRUCTIONS_PER_BUNDLE)
+    {
+      as_bad (_("Too many instructions for bundle."));
+      tilegx_flush_bundle ();
+    }
+
+  /* Make sure we have room for the upcoming bundle before we
+     create any fixups. Otherwise if we have to switch to a new
+     frag the fixup dot_value fields will be wrong.  */
+  frag_grow (TILEGX_BUNDLE_SIZE_IN_BYTES);
+
+  /* Find a valid pipe for this opcode.  */
+  for (first_pipe = 0; (op->pipes & (1 << first_pipe)) == 0; first_pipe++)
+    ;
+
+  /* Call the function that assembles this instruction.  */
+  current_bundle[current_bundle_index].opcode = op;
+  parse_operands (op->name,
+                  &op->operands[first_pipe][0],
+                  op->num_operands,
+                  current_bundle[current_bundle_index].operand_values);
+  ++current_bundle_index;
+
+  /* Restore the saved value of input_line_pointer.  */
+  input_line_pointer = old_input_line_pointer;
+
+  /* If we weren't inside curly braces, go ahead and emit
+     this lone instruction as a bundle right now.  */
+  if (!inside_bundle)
+    tilegx_flush_bundle ();
+}
+
+
+static void
+s_require_canonical_reg_names (int require)
+{
+  demand_empty_rest_of_line ();
+  require_canonical_reg_names = require;
+}
+
+static void
+s_allow_suspicious_bundles (int allow)
+{
+  demand_empty_rest_of_line ();
+  allow_suspicious_bundles = allow;
+}
+
+const pseudo_typeS md_pseudo_table[] =
+{
+  {"align", s_align_bytes, 0}, /* Defaulting is invalid (0).  */
+  {"word", cons, 4},
+  {"require_canonical_reg_names", s_require_canonical_reg_names, 1 },
+  {"no_require_canonical_reg_names", s_require_canonical_reg_names, 0 },
+  {"allow_suspicious_bundles", s_allow_suspicious_bundles, 1 },
+  {"no_allow_suspicious_bundles", s_allow_suspicious_bundles, 0 },
+  { NULL, 0, 0 }
+};
+
+/* Equal to MAX_PRECISION in atof-ieee.c  */
+#define MAX_LITTLENUMS 6
+
+/* Turn the string pointed to by litP into a floating point constant
+   of type TYPE, and emit the appropriate bytes.  The number of
+   LITTLENUMS emitted is stored in *SIZEP.  An error message is
+   returned, or NULL on OK.  */
+
+char *
+md_atof (int type, char *litP, int *sizeP)
+{
+  int prec;
+  LITTLENUM_TYPE words[MAX_LITTLENUMS];
+  LITTLENUM_TYPE *wordP;
+  char *t;
+
+  switch (type)
+    {
+    case 'f':
+    case 'F':
+      prec = 2;
+      break;
+
+    case 'd':
+    case 'D':
+      prec = 4;
+      break;
+
+    default:
+      *sizeP = 0;
+      return _("Bad call to md_atof ()");
+    }
+  t = atof_ieee (input_line_pointer, type, words);
+  if (t)
+    input_line_pointer = t;
+
+  *sizeP = prec * sizeof (LITTLENUM_TYPE);
+  /* This loops outputs the LITTLENUMs in REVERSE order; in accord with
+     the bigendian 386.  */
+  for (wordP = words + prec - 1; prec--;)
+    {
+      md_number_to_chars (litP, (valueT) (*wordP--), sizeof (LITTLENUM_TYPE));
+      litP += sizeof (LITTLENUM_TYPE);
+    }
+  return 0;
+}
+
+
+/* We have no need to default values of symbols.  */
+
+symbolS *
+md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
+{
+  return NULL;
+}
+
+
+void
+tilegx_cons_fix_new (fragS *frag,
+                    int where,
+                    int nbytes,
+                    expressionS *exp)
+{
+  expressionS subexp;
+  bfd_reloc_code_real_type reloc = BFD_RELOC_NONE;
+  int no_overflow = 0;
+  fixS *fixP;
+
+  /* See if it's one of our special functions.  */
+  switch (exp->X_op)
+    {
+    case O_hw0:
+      reloc = BFD_RELOC_TILEGX_HW0;
+      no_overflow = 1;
+      break;
+    case O_hw1:
+      reloc = BFD_RELOC_TILEGX_HW1;
+      no_overflow = 1;
+      break;
+    case O_hw2:
+      reloc = BFD_RELOC_TILEGX_HW2;
+      no_overflow = 1;
+      break;
+    case O_hw3:
+      reloc = BFD_RELOC_TILEGX_HW3;
+      no_overflow = 1;
+      break;
+    case O_hw0_last:
+      reloc = BFD_RELOC_TILEGX_HW0_LAST;
+      break;
+    case O_hw1_last:
+      reloc = BFD_RELOC_TILEGX_HW1_LAST;
+      break;
+    case O_hw2_last:
+      reloc = BFD_RELOC_TILEGX_HW2_LAST;
+      break;
+
+    default:
+      /* Do nothing.  */
+      break;
+    }
+
+  if (reloc != BFD_RELOC_NONE)
+    {
+      if (nbytes != 2)
+       {
+         as_bad (_("This operator only produces two byte values."));
+         nbytes = 2;
+       }
+
+      memset (&subexp, 0, sizeof subexp);
+      subexp.X_op = O_symbol;
+      subexp.X_add_symbol = exp->X_add_symbol;
+      exp = &subexp;
+    }
+  else
+    {
+      switch (nbytes)
+       {
+       case 1:
+         reloc = BFD_RELOC_8;
+         break;
+       case 2:
+         reloc = BFD_RELOC_16;
+         break;
+       case 4:
+         reloc = BFD_RELOC_32;
+         break;
+       case 8:
+         reloc = BFD_RELOC_64;
+         break;
+       default:
+         as_bad (_("unsupported BFD relocation size %d"), nbytes);
+         reloc = BFD_RELOC_64;
+         break;
+       }
+    }
+
+  fixP = fix_new_exp (frag, where, nbytes, exp, 0, reloc);
+  fixP->tc_fix_data = NULL;
+  fixP->fx_no_overflow |= no_overflow;
+}
+
+
+void
+md_apply_fix (fixS *fixP, valueT * valP, segT seg ATTRIBUTE_UNUSED)
+{
+  const struct tilegx_operand *operand;
+  valueT value = *valP;
+  operatorT special;
+  char *p;
+
+  /* Leave these for the linker.  */
+  if (fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT
+      || fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
+    return;
+
+  if (fixP->fx_subsy != (symbolS *) NULL)
+    {
+      /* We can't actually support subtracting a symbol.  */
+      as_bad_where (fixP->fx_file, fixP->fx_line, _("expression too complex"));
+    }
+
+  /* Correct relocation types for pc-relativeness.  */
+  switch (fixP->fx_r_type)
+    {
+#define FIX_PCREL(rtype)                        \
+      case rtype:                              \
+       if (fixP->fx_pcrel)                     \
+         fixP->fx_r_type = rtype##_PCREL;      \
+      break;                                   \
+                                                \
+    case rtype##_PCREL:                                \
+      if (!fixP->fx_pcrel)                     \
+       fixP->fx_r_type = rtype;                \
+      break
+
+      FIX_PCREL (BFD_RELOC_8);
+      FIX_PCREL (BFD_RELOC_16);
+      FIX_PCREL (BFD_RELOC_32);
+      FIX_PCREL (BFD_RELOC_64);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X0_HW0);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X1_HW0);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X0_HW1);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X1_HW1);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X0_HW2);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X1_HW2);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X0_HW3);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X1_HW3);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X0_HW1_LAST);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X1_HW1_LAST);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X0_HW2_LAST);
+      FIX_PCREL (BFD_RELOC_TILEGX_IMM16_X1_HW2_LAST);
+
+#undef FIX_PCREL
+
+    default:
+      /* Do nothing  */
+      break;
+    }
+
+  if (fixP->fx_addsy != NULL)
+    {
+#ifdef OBJ_ELF
+      switch (fixP->fx_r_type)
+       {
+       case BFD_RELOC_TILEGX_IMM16_X0_HW0_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW0_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW0_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW0_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW1_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW1_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW1_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW1_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW1_LAST_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW1_LAST_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW1_LAST_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW1_LAST_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW2_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW2_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW2_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW2_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW2_LAST_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW2_LAST_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW2_LAST_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW2_LAST_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW3_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW3_TLS_GD:
+       case BFD_RELOC_TILEGX_IMM16_X0_HW3_TLS_IE:
+       case BFD_RELOC_TILEGX_IMM16_X1_HW3_TLS_IE:
+       case BFD_RELOC_TILEGX_TLS_DTPMOD64:
+       case BFD_RELOC_TILEGX_TLS_DTPOFF64:
+       case BFD_RELOC_TILEGX_TLS_TPOFF64:
+       case BFD_RELOC_TILEGX_TLS_DTPMOD32:
+       case BFD_RELOC_TILEGX_TLS_DTPOFF32:
+       case BFD_RELOC_TILEGX_TLS_TPOFF32:
+         S_SET_THREAD_LOCAL (fixP->fx_addsy);
+         break;
+
+       default:
+         /* Do nothing  */
+         break;
+       }
+#endif
+      return;
+    }
+
+  /* Apply hw0, etc.  */
+  special = O_illegal;
+  switch (fixP->fx_r_type)
+    {
+    case BFD_RELOC_TILEGX_HW0:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_TLS_IE:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_TLS_IE:
+      special = O_hw0;
+      break;
+
+    case BFD_RELOC_TILEGX_HW0_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW0_LAST_TLS_IE:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW0_LAST_TLS_IE:
+      special = O_hw0_last;
+      break;
+
+    case BFD_RELOC_TILEGX_HW1:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_TLS_IE:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_TLS_IE:
+      special = O_hw1;
+      break;
+
+    case BFD_RELOC_TILEGX_HW1_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_LAST_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_LAST_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_LAST_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_LAST_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_LAST_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_LAST_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW1_LAST_TLS_IE:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW1_LAST_TLS_IE:
+      special = O_hw1_last;
+      break;
+
+    case BFD_RELOC_TILEGX_HW2:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_TLS_IE:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_TLS_IE:
+      special = O_hw2;
+      break;
+
+    case BFD_RELOC_TILEGX_HW2_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_LAST:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_LAST_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_LAST_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_LAST_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_LAST_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_LAST_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_LAST_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW2_LAST_TLS_IE:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW2_LAST_TLS_IE:
+      special = O_hw2_last;
+      break;
+
+    case BFD_RELOC_TILEGX_HW3:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW3:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW3:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW3_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW3_PCREL:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW3_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW3_GOT:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW3_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW3_TLS_GD:
+    case BFD_RELOC_TILEGX_IMM16_X0_HW3_TLS_IE:
+    case BFD_RELOC_TILEGX_IMM16_X1_HW3_TLS_IE:
+      special = O_hw3;
+      break;
+
+    default:
+      /* Do nothing  */
+      break;
+    }
+
+  if (special != O_illegal)
+    {
+      *valP = value = apply_special_operator (special, value,
+                                             fixP->fx_file, fixP->fx_line);
+    }
+
+  p = fixP->fx_frag->fr_literal + fixP->fx_where;
+
+  operand = fixP->tc_fix_data;
+  if (operand != NULL)
+    {
+      /* It's an instruction operand.  */
+      tilegx_bundle_bits bits =
+       insert_operand (0, operand, value, fixP->fx_file, fixP->fx_line);
+
+      /* Note that we might either be writing out bits for a bundle
+        or a static network instruction, which are different sizes, so it's
+        important to stop touching memory once we run out of bits.
+        ORing in values is OK since we know the existing bits for
+        this operand are zero.  */
+      for (; bits != 0; bits >>= 8)
+       *p++ |= (char)bits;
+    }
+  else
+    {
+      /* Some other kind of relocation.  */
+      switch (fixP->fx_r_type)
+       {
+       case BFD_RELOC_8:
+       case BFD_RELOC_8_PCREL:
+         md_number_to_chars (p, value, 1);
+         break;
+
+       case BFD_RELOC_16:
+       case BFD_RELOC_16_PCREL:
+         md_number_to_chars (p, value, 2);
+         break;
+
+       case BFD_RELOC_32:
+       case BFD_RELOC_32_PCREL:
+         md_number_to_chars (p, value, 4);
+         break;
+
+       case BFD_RELOC_64:
+       case BFD_RELOC_64_PCREL:
+         md_number_to_chars (p, value, 8);
+         break;
+
+       default:
+         /* Leave it for the linker.  */
+         return;
+       }
+    }
+
+  fixP->fx_done = 1;
+}
+
+
+/* Generate the BFD reloc to be stuck in the object file from the
+   fixup used internally in the assembler.  */
+
+arelent *
+tc_gen_reloc (asection *sec ATTRIBUTE_UNUSED, fixS *fixp)
+{
+  arelent *reloc;
+
+  reloc = (arelent *) xmalloc (sizeof (arelent));
+  reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *));
+  *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
+  reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
+
+  /* Make sure none of our internal relocations make it this far.
+     They'd better have been fully resolved by this point.  */
+  gas_assert ((int) fixp->fx_r_type > 0);
+
+  reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);
+  if (reloc->howto == NULL)
+    {
+      as_bad_where (fixp->fx_file, fixp->fx_line,
+                   _("cannot represent `%s' relocation in object file"),
+                   bfd_get_reloc_code_name (fixp->fx_r_type));
+      return NULL;
+    }
+
+  if (!fixp->fx_pcrel != !reloc->howto->pc_relative)
+    {
+      as_fatal (_("internal error? cannot generate `%s' relocation (%d, %d)"),
+               bfd_get_reloc_code_name (fixp->fx_r_type),
+                fixp->fx_pcrel, reloc->howto->pc_relative);
+    }
+  gas_assert (!fixp->fx_pcrel == !reloc->howto->pc_relative);
+
+  reloc->addend = fixp->fx_offset;
+
+  return reloc;
+}
+
+
+/* The location from which a PC relative jump should be calculated,
+   given a PC relative reloc.  */
+
+long
+md_pcrel_from (fixS *fixP)
+{
+  return fixP->fx_frag->fr_address + fixP->fx_where;
+}
+
+
+/* Return 1 if it's OK to adjust a reloc by replacing the symbol with
+   a section symbol plus some offset.  */
+int
+tilegx_fix_adjustable (fixS *fix)
+{
+  /* Prevent all adjustments to global symbols  */
+  if (S_IS_EXTERNAL (fix->fx_addsy) || S_IS_WEAK (fix->fx_addsy))
+    return 0;
+
+  return 1;
+}
+
+
+int
+tilegx_unrecognized_line (int ch)
+{
+  switch (ch)
+    {
+    case '{':
+      if (inside_bundle)
+       {
+         as_bad (_("Found '{' when already bundling."));
+       }
+      else
+       {
+         inside_bundle = 1;
+         current_bundle_index = 0;
+       }
+      return 1;
+
+    case '}':
+      if (!inside_bundle)
+       {
+         as_bad (_("Found '}' when not bundling."));
+       }
+      else
+       {
+         tilegx_flush_bundle ();
+       }
+
+      /* Allow '{' to follow on the same line.  We also allow ";;", but that
+        happens automatically because ';' is an end of line marker.  */
+      SKIP_WHITESPACE ();
+      if (input_line_pointer[0] == '{')
+       {
+         input_line_pointer++;
+         return tilegx_unrecognized_line ('{');
+       }
+
+      demand_empty_rest_of_line ();
+      return 1;
+
+    default:
+      break;
+    }
+
+  /* Not a valid line.  */
+  return 0;
+}
+
+
+/* This is called from HANDLE_ALIGN in write.c.  Fill in the contents
+   of an rs_align_code fragment.  */
+
+void
+tilegx_handle_align (fragS *fragp)
+{
+  addressT bytes, fix;
+  char *p;
+
+  if (fragp->fr_type != rs_align_code)
+    return;
+
+  bytes = fragp->fr_next->fr_address - fragp->fr_address - fragp->fr_fix;
+  p = fragp->fr_literal + fragp->fr_fix;
+  fix = 0;
+
+  /* Determine the bits for NOP.  */
+  const struct tilegx_opcode *nop_opcode =
+    &tilegx_opcodes[TILEGX_OPC_NOP];
+  tilegx_bundle_bits nop =
+    (  nop_opcode->fixed_bit_values[TILEGX_PIPELINE_X0]
+     | nop_opcode->fixed_bit_values[TILEGX_PIPELINE_X1]);
+
+  if ((bytes & (TILEGX_BUNDLE_SIZE_IN_BYTES - 1)) != 0)
+    {
+      fix = bytes & (TILEGX_BUNDLE_SIZE_IN_BYTES - 1);
+      memset (p, 0, fix);
+      p += fix;
+      bytes -= fix;
+    }
+
+  number_to_chars_littleendian (p, nop, 8);
+  fragp->fr_fix += fix;
+  fragp->fr_var = TILEGX_BUNDLE_SIZE_IN_BYTES;
+}
+
+/* Standard calling conventions leave the CFA at SP on entry.  */
+void
+tilegx_cfi_frame_initial_instructions (void)
+{
+  cfi_add_CFA_def_cfa_register (54);
+}
+
+int
+tc_tilegx_regname_to_dw2regnum (char *regname)
+{
+  int i;
+  for (i = 0; i < TILEGX_NUM_REGISTERS; i++)
+    {
+      if (!strcmp (regname, tilegx_register_names[i]))
+       return i;
+    }
+
+  return -1;
+}