]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blobdiff - gas/config/tc-nios2.c
Update year range in copyright notice of binutils files
[thirdparty/binutils-gdb.git] / gas / config / tc-nios2.c
index 4ac3eaaf6340b734c466ddc9ca2058e9e69dd55a..999890ecfcea8473cc7e7bd4e8b8d5ed101b63b3 100644 (file)
@@ -1,5 +1,5 @@
 /* Altera Nios II assembler.
-   Copyright (C) 2012-2017 Free Software Foundation, Inc.
+   Copyright (C) 2012-2023 Free Software Foundation, Inc.
    Contributed by Nigel Gray (ngray@altera.com).
    Contributed by Mentor Graphics, Inc.
 
@@ -103,12 +103,12 @@ static struct
      and macro expansions generate a warning.
      .set at -> noat = 0, assembly code using at warn but macro expansions
      do not generate warnings.  */
-  bfd_boolean noat;
+  bool noat;
 
   /* .set nobreak -> nobreak = 1 allows assembly code to use ba,bt without
                                 warning.
      .set break -> nobreak = 0, assembly code using ba,bt warns.  */
-  bfd_boolean nobreak;
+  bool nobreak;
 
   /* .cmd line option -relax-all allows all branches and calls to be replaced
      with longer versions.
@@ -117,7 +117,7 @@ static struct
      a section.  */
   relax_optionT relax;
 
-} nios2_as_options = {FALSE, FALSE, relax_section};
+} nios2_as_options = {false, false, relax_section};
 
 
 typedef struct nios2_insn_reloc
@@ -184,20 +184,20 @@ typedef struct nios2_ps_insn_info
 } nios2_ps_insn_infoS;
 
 /* Opcode hash table.  */
-static struct hash_control *nios2_opcode_hash = NULL;
+static htab_t nios2_opcode_hash = NULL;
 #define nios2_opcode_lookup(NAME) \
-  ((struct nios2_opcode *) hash_find (nios2_opcode_hash, (NAME)))
+  ((struct nios2_opcode *) str_hash_find (nios2_opcode_hash, (NAME)))
 
 /* Register hash table.  */
-static struct hash_control *nios2_reg_hash = NULL;
+static htab_t nios2_reg_hash = NULL;
 #define nios2_reg_lookup(NAME) \
-  ((struct nios2_reg *) hash_find (nios2_reg_hash, (NAME)))
+  ((struct nios2_reg *) str_hash_find (nios2_reg_hash, (NAME)))
 
 
 /* Pseudo-op hash table.  */
-static struct hash_control *nios2_ps_hash = NULL;
+static htab_t nios2_ps_hash = NULL;
 #define nios2_ps_lookup(NAME) \
-  ((nios2_ps_insn_infoS *) hash_find (nios2_ps_hash, (NAME)))
+  ((nios2_ps_insn_infoS *) str_hash_find (nios2_ps_hash, (NAME)))
 
 /* The known current alignment of the current section.  */
 static int nios2_current_align;
@@ -238,10 +238,10 @@ md_chars_to_number (char *buf, int n)
   val = 0;
   if (target_big_endian)
     for (i = 0; i < n; ++i)
-      val = val | ((buf[i] & 0xff) << 8 * (n - (i + 1)));
+      val = val | ((valueT) (buf[i] & 0xff) << 8 * (n - (i + 1)));
   else
     for (i = 0; i < n; ++i)
-      val = val | ((buf[i] & 0xff) << 8 * i);
+      val = val | ((valueT) (buf[i] & 0xff) << 8 * i);
   return val;
 }
 
@@ -300,29 +300,24 @@ md_atof (int type, char *litP, int *sizeP)
   return NULL;
 }
 
-/* Return true if STR starts with PREFIX, which should be a string literal.  */
-#define strprefix(STR, PREFIX) \
-  (strncmp ((STR), PREFIX, strlen (PREFIX)) == 0)
-
-
 /* Return true if STR is prefixed with a special relocation operator.  */
 static int
 nios2_special_relocation_p (const char *str)
 {
-  return (strprefix (str, "%lo")
-         || strprefix (str, "%hi")
-         || strprefix (str, "%hiadj")
-         || strprefix (str, "%gprel")
-         || strprefix (str, "%got")
-         || strprefix (str, "%call")
-         || strprefix (str, "%gotoff_lo")
-         || strprefix (str, "%gotoff_hiadj")
-         || strprefix (str, "%tls_gd")
-         || strprefix (str, "%tls_ldm")
-         || strprefix (str, "%tls_ldo")
-         || strprefix (str, "%tls_ie")
-         || strprefix (str, "%tls_le")
-         || strprefix (str, "%gotoff"));
+  return (startswith (str, "%lo")
+         || startswith (str, "%hi")
+         || startswith (str, "%hiadj")
+         || startswith (str, "%gprel")
+         || startswith (str, "%got")
+         || startswith (str, "%call")
+         || startswith (str, "%gotoff_lo")
+         || startswith (str, "%gotoff_hiadj")
+         || startswith (str, "%tls_gd")
+         || startswith (str, "%tls_ldm")
+         || startswith (str, "%tls_ldo")
+         || startswith (str, "%tls_ie")
+         || startswith (str, "%tls_le")
+         || startswith (str, "%gotoff"));
 }
 
 
@@ -391,7 +386,7 @@ nios2_align (int log_size, const char *pfill, symbolS *label)
       if (label != NULL && !switched_seg_p)
        {
          symbolS *sym;
-         int label_seen = FALSE;
+         int label_seen = false;
          struct frag *old_frag;
          valueT old_value;
          valueT new_value;
@@ -417,7 +412,7 @@ nios2_align (int log_size, const char *pfill, symbolS *label)
            if (symbol_get_frag (sym) == old_frag
                && S_GET_VALUE (sym) == old_value)
              {
-               label_seen = TRUE;
+               label_seen = true;
                symbol_set_frag (sym, frag_now);
                S_SET_VALUE (sym, new_value);
              }
@@ -579,17 +574,17 @@ s_nios2_set (int equiv)
   SKIP_WHITESPACE ();
   if (is_end_of_line[(unsigned char) *input_line_pointer])
     {
-      bfd_boolean done = TRUE;
+      bool done = true;
       *endline = 0;
 
       if (!strcmp (directive, "noat"))
-         nios2_as_options.noat = TRUE;
+         nios2_as_options.noat = true;
       else if (!strcmp (directive, "at"))
-         nios2_as_options.noat = FALSE;
+         nios2_as_options.noat = false;
       else if (!strcmp (directive, "nobreak"))
-         nios2_as_options.nobreak = TRUE;
+         nios2_as_options.nobreak = true;
       else if (!strcmp (directive, "break"))
-         nios2_as_options.nobreak = FALSE;
+         nios2_as_options.nobreak = false;
       else if (!strcmp (directive, "norelax"))
          nios2_as_options.relax = relax_none;
       else if (!strcmp (directive, "relaxsection"))
@@ -597,7 +592,7 @@ s_nios2_set (int equiv)
       else if (!strcmp (directive, "relaxall"))
          nios2_as_options.relax = relax_all;
       else
-       done = FALSE;
+       done = false;
 
       if (done)
        {
@@ -789,7 +784,7 @@ nios2_relax_frag (segT segment, fragS *fragp, long stretch)
       fragS *sym_frag = symbol_get_frag (symbolp);
       offsetT offset;
       int n;
-      bfd_boolean is_cdx = FALSE;
+      bool is_cdx = false;
 
       target += S_GET_VALUE (symbolp);
 
@@ -809,11 +804,11 @@ nios2_relax_frag (segT segment, fragS *fragp, long stretch)
       if (IS_CDXBRANCH (subtype) && IS_UBRANCH (subtype)
          && offset >= -1024 && offset < 1024)
        /* PC-relative CDX branch with 11-bit offset.  */
-       is_cdx = TRUE;
+       is_cdx = true;
       else if (IS_CDXBRANCH (subtype) && IS_CBRANCH (subtype)
               && offset >= -128 && offset < 128)
        /* PC-relative CDX branch with 8-bit offset.  */
-       is_cdx = TRUE;
+       is_cdx = true;
       else if (offset >= -32768 && offset < 32768)
        /* Fits in PC-relative branch.  */
        n = 0;
@@ -883,7 +878,7 @@ md_convert_frag (bfd *headers ATTRIBUTE_UNUSED, segT segment ATTRIBUTE_UNUSED,
   unsigned int addend_mask, addi_mask, op;
   offsetT addend, remainder;
   int i;
-  bfd_boolean is_r2 = (bfd_get_mach (stdoutput) == bfd_mach_nios2r2);
+  bool is_r2 = (bfd_get_mach (stdoutput) == bfd_mach_nios2r2);
 
   /* If this is a CDX branch we're not relaxing, just generate the fixup.  */
   if (IS_CDXBRANCH (subtype))
@@ -1100,7 +1095,7 @@ md_convert_frag (bfd *headers ATTRIBUTE_UNUSED, segT segment ATTRIBUTE_UNUSED,
 /** Fixups and overflow checking.  */
 
 /* Check a fixup for overflow. */
-static bfd_boolean
+static bool
 nios2_check_overflow (valueT fixup, reloc_howto_type *howto)
 {
   /* If there is a rightshift, check that the low-order bits are
@@ -1109,7 +1104,7 @@ nios2_check_overflow (valueT fixup, reloc_howto_type *howto)
     {
       if ((~(~((valueT) 0) << howto->rightshift) & fixup)
          && howto->complain_on_overflow != complain_overflow_dont)
-       return TRUE;
+       return true;
       fixup = ((signed)fixup) >> howto->rightshift;
     }
 
@@ -1121,31 +1116,31 @@ nios2_check_overflow (valueT fixup, reloc_howto_type *howto)
     case complain_overflow_bitfield:
       if ((fixup >> howto->bitsize) != 0
          && ((signed) fixup >> howto->bitsize) != -1)
-       return TRUE;
+       return true;
       break;
     case complain_overflow_signed:
       if ((fixup & 0x80000000) > 0)
        {
          /* Check for negative overflow.  */
          if ((signed) fixup < (signed) (~0U << (howto->bitsize - 1)))
-           return TRUE;
+           return true;
        }
       else
        {
          /* Check for positive overflow.  */
          if (fixup >= ((unsigned) 1 << (howto->bitsize - 1)))
-           return TRUE;
+           return true;
        }
       break;
     case complain_overflow_unsigned:
       if ((fixup >> howto->bitsize) != 0)
-       return TRUE;
+       return true;
       break;
     default:
       as_bad (_("error checking for overflow - broken assembler"));
       break;
     }
-  return FALSE;
+  return false;
 }
 
 /* Emit diagnostic for fixup overflow.  */
@@ -1384,7 +1379,7 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
            nios2_diagnose_overflow (fixup, howto, fixP, value);
 
          /* Apply the right shift.  */
-         fixup = ((signed)fixup) >> howto->rightshift;
+         fixup = (offsetT) fixup >> howto->rightshift;
 
          /* Truncate the fixup to right size.  */
          switch (fixP->fx_r_type)
@@ -1396,13 +1391,11 @@ md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
              fixup = fixup & 0xFFFF;
              break;
            case BFD_RELOC_NIOS2_HIADJ16:
-             fixup = ((((fixup >> 16) & 0xFFFF) + ((fixup >> 15) & 0x01))
-                      & 0xFFFF);
+             fixup = ((fixup + 0x8000) >> 16) & 0xFFFF;
              break;
            default:
              {
-               int n = sizeof (fixup) * 8 - howto->bitsize;
-               fixup = (fixup << n) >> n;
+               fixup &= ((valueT) 2 << (howto->bitsize - 1)) - 1;
                break;
              }
            }
@@ -1633,7 +1626,7 @@ nios2_parse_reglist (char *token, const struct nios2_opcode *op)
            }
        }
 
-      mask |= 1 << regno;
+      mask |= 1UL << regno;
       last = regno;
     }
 
@@ -1680,7 +1673,7 @@ nios2_parse_base_register (char *str, int *direction, int *writeback, int *ret)
   *ret = 0;
 
   /* Check for --.  */
-  if (strncmp (str, "--", 2) == 0)
+  if (startswith (str, "--"))
     {
       str += 2;
       *direction -= 1;
@@ -1707,7 +1700,7 @@ nios2_parse_base_register (char *str, int *direction, int *writeback, int *ret)
     return NULL;
 
   /* Check for ++.  */
-  if (strncmp (str, "++", 2) == 0)
+  if (startswith (str, "++"))
     {
       str += 2;
       *direction += 1;
@@ -1725,12 +1718,12 @@ nios2_parse_base_register (char *str, int *direction, int *writeback, int *ret)
     {
       while (*str == ' ')
        str++;
-      if (strncmp (str, "writeback", 9) == 0)
+      if (startswith (str, "writeback"))
        {
          *writeback = 1;
          str += 9;
        }
-      else if (strncmp (str, "ret", 3) == 0)
+      else if (startswith (str, "ret"))
        {
          *ret = 1;
          str += 3;
@@ -2712,7 +2705,7 @@ nios2_assemble_arg_R (const char *token, nios2_insn_infoS *insn)
          mask = (reglist & 0x00ffc000) >> 14;
          if (reglist & (1 << 28))
            mask |= 1 << 10;
-         if (reglist & (1 << 31))
+         if (reglist & (1u << 31))
            mask |= 1 << 11;
        }
       insn->insn_code |= SET_IW_F1X4L17_REGMASK (mask);
@@ -3101,7 +3094,7 @@ nios2_parse_args (nios2_insn_infoS *insn, char *argstr,
   int i;
   p = argstr;
   i = 0;
-  bfd_boolean terminate = FALSE;
+  bool terminate = false;
 
   /* This rest of this function is it too fragile and it mostly works,
      therefore special case this one.  */
@@ -3138,7 +3131,7 @@ nios2_parse_args (nios2_insn_infoS *insn, char *argstr,
        }
 
       if (*parsestr == '\0' || (p != NULL && *p == '\0'))
-       terminate = TRUE;
+       terminate = true;
       ++i;
     }
 
@@ -3232,11 +3225,8 @@ nios2_insert_arg (char **parsed_args, const char *insert, int num,
 static void
 nios2_free_arg (char **parsed_args, int num ATTRIBUTE_UNUSED, int start)
 {
-  if (parsed_args[start])
-    {
-      free (parsed_args[start]);
-      parsed_args[start] = NULL;
-    }
+  free (parsed_args[start]);
+  parsed_args[start] = NULL;
 }
 
 /* This function swaps the pseudo-op for a real op.  */
@@ -3244,16 +3234,29 @@ static nios2_ps_insn_infoS*
 nios2_translate_pseudo_insn (nios2_insn_infoS *insn)
 {
 
+  const struct nios2_opcode *op = insn->insn_nios2_opcode;
   nios2_ps_insn_infoS *ps_insn;
+  unsigned int tokidx, ntok;
 
   /* Find which real insn the pseudo-op translates to and
      switch the insn_info ptr to point to it.  */
-  ps_insn = nios2_ps_lookup (insn->insn_nios2_opcode->name);
+  ps_insn = nios2_ps_lookup (op->name);
 
   if (ps_insn != NULL)
     {
       insn->insn_nios2_opcode = nios2_opcode_lookup (ps_insn->insn);
       insn->insn_tokens[0] = insn->insn_nios2_opcode->name;
+      
+      /* Make sure there are enough arguments.  */
+      ntok = ((op->pinfo & NIOS2_INSN_OPTARG)
+             ? op->num_args - 1 : op->num_args);
+      for (tokidx = 1; tokidx <= ntok; tokidx++)
+       if (insn->insn_tokens[tokidx] == NULL)
+         {
+           as_bad ("missing argument");
+           return NULL;
+         }
+
       /* Modify the args so they work with the real insn.  */
       ps_insn->arg_modifer_func ((char **) insn->insn_tokens,
                                 ps_insn->arg_modifier, ps_insn->num,
@@ -3342,7 +3345,7 @@ output_ubranch (nios2_insn_infoS *insn)
       symbolS *symp = reloc->reloc_expression.X_add_symbol;
       offsetT offset = reloc->reloc_expression.X_add_number;
       char *f;
-      bfd_boolean is_cdx = (insn->insn_nios2_opcode->size == 2);
+      bool is_cdx = (insn->insn_nios2_opcode->size == 2);
 
       /* Tag dwarf2 debug info to the address at the start of the insn.
         We must do it before frag_var() below closes off the frag.  */
@@ -3374,7 +3377,7 @@ output_cbranch (nios2_insn_infoS *insn)
       symbolS *symp = reloc->reloc_expression.X_add_symbol;
       offsetT offset = reloc->reloc_expression.X_add_number;
       char *f;
-      bfd_boolean is_cdx = (insn->insn_nios2_opcode->size == 2);
+      bool is_cdx = (insn->insn_nios2_opcode->size == 2);
 
       /* Tag dwarf2 debug info to the address at the start of the insn.
         We must do it before frag_var() below closes off the frag.  */
@@ -3595,7 +3598,6 @@ void
 md_begin (void)
 {
   int i;
-  const char *inserted;
 
   switch (nios2_architecture)
     {
@@ -3612,60 +3614,29 @@ md_begin (void)
 
   /* Create and fill a hashtable for the Nios II opcodes, registers and
      arguments.  */
-  nios2_opcode_hash = hash_new ();
-  nios2_reg_hash = hash_new ();
-  nios2_ps_hash = hash_new ();
+  nios2_opcode_hash = str_htab_create ();
+  nios2_reg_hash = str_htab_create ();
+  nios2_ps_hash = str_htab_create ();
 
   for (i = 0; i < nios2_num_opcodes; ++i)
-    {
-      inserted
-       = hash_insert (nios2_opcode_hash, nios2_opcodes[i].name,
-                      (PTR) & nios2_opcodes[i]);
-      if (inserted != NULL)
-       {
-         fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
-                  nios2_opcodes[i].name, inserted);
-         /* Probably a memory allocation problem?  Give up now.  */
-         as_fatal (_("Broken assembler.  No assembly attempted."));
-       }
-    }
+    if (str_hash_insert (nios2_opcode_hash, nios2_opcodes[i].name,
+                        &nios2_opcodes[i], 0) != NULL)
+      as_fatal (_("duplicate %s"), nios2_opcodes[i].name);
 
   for (i = 0; i < nios2_num_regs; ++i)
-    {
-      inserted
-       = hash_insert (nios2_reg_hash, nios2_regs[i].name,
-                      (PTR) & nios2_regs[i]);
-      if (inserted != NULL)
-       {
-         fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
-                  nios2_regs[i].name, inserted);
-         /* Probably a memory allocation problem?  Give up now.  */
-         as_fatal (_("Broken assembler.  No assembly attempted."));
-       }
-
-    }
+    if (str_hash_insert (nios2_reg_hash, nios2_regs[i].name,
+                        &nios2_regs[i], 0) != NULL)
+      as_fatal (_("duplicate %s"), nios2_regs[i].name);
 
   for (i = 0; i < nios2_num_ps_insn_info_structs; ++i)
-    {
-      inserted
-       = hash_insert (nios2_ps_hash, nios2_ps_insn_info_structs[i].pseudo_insn,
-                      (PTR) & nios2_ps_insn_info_structs[i]);
-      if (inserted != NULL)
-       {
-         fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
-                  nios2_ps_insn_info_structs[i].pseudo_insn, inserted);
-         /* Probably a memory allocation problem?  Give up now.  */
-         as_fatal (_("Broken assembler.  No assembly attempted."));
-       }
-    }
+    if (str_hash_insert (nios2_ps_hash,
+                        nios2_ps_insn_info_structs[i].pseudo_insn,
+                        &nios2_ps_insn_info_structs[i], 0) != NULL)
+      as_fatal (_("duplicate %s"), nios2_ps_insn_info_structs[i].pseudo_insn);
 
   /* Assembler option defaults.  */
-  nios2_as_options.noat = FALSE;
-  nios2_as_options.nobreak = FALSE;
-
-  /* Debug information is incompatible with relaxation.  */
-  if (debug_type != DEBUG_UNSPECIFIED)
-    nios2_as_options.relax = relax_none;
+  nios2_as_options.noat = false;
+  nios2_as_options.nobreak = false;
 
   /* Initialize the alignment data.  */
   nios2_current_align_seg = now_seg;
@@ -3684,6 +3655,7 @@ md_assemble (char *op_str)
   unsigned long saved_pinfo = 0;
   nios2_insn_infoS thisinsn;
   nios2_insn_infoS *insn = &thisinsn;
+  bool ps_error = false;
 
   /* Make sure we are aligned on an appropriate boundary.  */
   if (nios2_current_align < nios2_min_align)
@@ -3730,35 +3702,45 @@ md_assemble (char *op_str)
         with its real equivalent, and then continue.  */
       if ((insn->insn_nios2_opcode->pinfo & NIOS2_INSN_MACRO)
          == NIOS2_INSN_MACRO)
-       ps_insn = nios2_translate_pseudo_insn (insn);
-
-      /* Assemble the parsed arguments into the instruction word.  */
-      nios2_assemble_args (insn);
-
-      /* Handle relaxation and other transformations.  */
-      if (nios2_as_options.relax != relax_none
-         && !nios2_as_options.noat
-         && insn->insn_nios2_opcode->pinfo & NIOS2_INSN_UBRANCH)
-       output_ubranch (insn);
-      else if (nios2_as_options.relax != relax_none
-              && !nios2_as_options.noat
-              && insn->insn_nios2_opcode->pinfo & NIOS2_INSN_CBRANCH)
-       output_cbranch (insn);
-      else if (nios2_as_options.relax == relax_all
-              && !nios2_as_options.noat
-              && insn->insn_nios2_opcode->pinfo & NIOS2_INSN_CALL
-              && insn->insn_reloc
-              && ((insn->insn_reloc->reloc_type
-                   == BFD_RELOC_NIOS2_CALL26)
-                  || (insn->insn_reloc->reloc_type
-                      == BFD_RELOC_NIOS2_CALL26_NOAT)))
-       output_call (insn);
-      else if (saved_pinfo == NIOS2_INSN_MACRO_MOVIA)
-       output_movia (insn);
-      else
-       output_insn (insn);
-      if (ps_insn)
-       nios2_cleanup_pseudo_insn (insn, ps_insn);
+       {
+         ps_insn = nios2_translate_pseudo_insn (insn);
+         if (!ps_insn)
+           ps_error = true;
+       }
+
+      /* If we found invalid pseudo-instruction syntax, the error's already
+        been diagnosed in nios2_translate_pseudo_insn, so skip
+        remaining processing.  */
+      if (!ps_error)
+       {
+         /* Assemble the parsed arguments into the instruction word.  */
+         nios2_assemble_args (insn);
+
+         /* Handle relaxation and other transformations.  */
+         if (nios2_as_options.relax != relax_none
+             && !nios2_as_options.noat
+             && insn->insn_nios2_opcode->pinfo & NIOS2_INSN_UBRANCH)
+           output_ubranch (insn);
+         else if (nios2_as_options.relax != relax_none
+                  && !nios2_as_options.noat
+                  && insn->insn_nios2_opcode->pinfo & NIOS2_INSN_CBRANCH)
+           output_cbranch (insn);
+         else if (nios2_as_options.relax == relax_all
+                  && !nios2_as_options.noat
+                  && insn->insn_nios2_opcode->pinfo & NIOS2_INSN_CALL
+                  && insn->insn_reloc
+                  && ((insn->insn_reloc->reloc_type
+                       == BFD_RELOC_NIOS2_CALL26)
+                      || (insn->insn_reloc->reloc_type
+                          == BFD_RELOC_NIOS2_CALL26_NOAT)))
+           output_call (insn);
+         else if (saved_pinfo == NIOS2_INSN_MACRO_MOVIA)
+           output_movia (insn);
+         else
+           output_insn (insn);
+         if (ps_insn)
+           nios2_cleanup_pseudo_insn (insn, ps_insn);
+       }
     }
   else
     /* Unrecognised instruction - error.  */
@@ -3920,7 +3902,7 @@ md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
            as_bad ("GOT already in the symbol table");
 
          GOT_symbol = symbol_new (name, undefined_section,
-                                  (valueT) 0, &zero_address_frag);
+                                  &zero_address_frag, 0);
        }
 
       return GOT_symbol;
@@ -3989,31 +3971,48 @@ nios2_elf_section_flags (flagword flags, int attr, int type ATTRIBUTE_UNUSED)
   return flags;
 }
 
-/* Implement TC_PARSE_CONS_EXPRESSION to handle %tls_ldo(...) */
+/* Implement TC_PARSE_CONS_EXPRESSION to handle %tls_ldo(...) and
+   %gotoff(...).  */
 bfd_reloc_code_real_type
 nios2_cons (expressionS *exp, int size)
 {
-  bfd_reloc_code_real_type nios2_tls_ldo_reloc = BFD_RELOC_NONE;
+  bfd_reloc_code_real_type explicit_reloc = BFD_RELOC_NONE;
+  const char *reloc_name = NULL;
 
   SKIP_WHITESPACE ();
   if (input_line_pointer[0] == '%')
     {
-      if (strprefix (input_line_pointer + 1, "tls_ldo"))
+      if (startswith (input_line_pointer + 1, "tls_ldo"))
        {
+         reloc_name = "%tls_ldo";
          if (size != 4)
            as_bad (_("Illegal operands: %%tls_ldo in %d-byte data field"),
                    size);
          else
            {
              input_line_pointer += 8;
-             nios2_tls_ldo_reloc = BFD_RELOC_NIOS2_TLS_DTPREL;
+             explicit_reloc = BFD_RELOC_NIOS2_TLS_DTPREL;
            }
        }
-      if (nios2_tls_ldo_reloc != BFD_RELOC_NONE)
+      else if (startswith (input_line_pointer + 1, "gotoff"))
+       {
+         reloc_name = "%gotoff";
+         if (size != 4)
+           as_bad (_("Illegal operands: %%gotoff in %d-byte data field"),
+                   size);
+         else
+           {
+             input_line_pointer += 7;
+             explicit_reloc = BFD_RELOC_NIOS2_GOTOFF;
+           }
+       }
+
+      if (explicit_reloc != BFD_RELOC_NONE)
        {
          SKIP_WHITESPACE ();
          if (input_line_pointer[0] != '(')
-           as_bad (_("Illegal operands: %%tls_ldo requires arguments in ()"));
+           as_bad (_("Illegal operands: %s requires arguments in ()"),
+                   reloc_name);
          else
            {
              int c;
@@ -4031,29 +4030,32 @@ nios2_cons (expressionS *exp, int size)
                  }
 
              if (c != ')')
-               as_bad (_("Illegal operands: %%tls_ldo requires arguments in ()"));
+               as_bad (_("Illegal operands: %s requires arguments in ()"),
+                       reloc_name);
              else
                {
                  *end = '\0';
                  expression (exp);
                  *end = c;
                  if (input_line_pointer != end)
-                   as_bad (_("Illegal operands: %%tls_ldo requires arguments in ()"));
+                   as_bad (_("Illegal operands: %s requires arguments in ()"),
+                           reloc_name);
                  else
                    {
                      input_line_pointer++;
                      SKIP_WHITESPACE ();
                      c = *input_line_pointer;
                      if (! is_end_of_line[c] && c != ',')
-                       as_bad (_("Illegal operands: garbage after %%tls_ldo()"));
+                       as_bad (_("Illegal operands: garbage after %s()"),
+                               reloc_name);
                    }
                }
            }
        }
     }
-  if (nios2_tls_ldo_reloc == BFD_RELOC_NONE)
+  if (explicit_reloc == BFD_RELOC_NONE)
     expression (exp);
-  return nios2_tls_ldo_reloc;
+  return explicit_reloc;
 }
 
 /* Implement HANDLE_ALIGN.  */