/* tc-i860.c -- Assembler for the Intel i860 architecture.
   Copyright 1989, 1992, 1993, 1994, 1995, 1998, 1999, 2000, 2001, 2002,
   2003, 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc.

   Brought back from the dead and completely reworked
   by Jason Eckhardt <jle@cygnus.com>.

   This file is part of GAS, the GNU Assembler.

   GAS 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, or (at your option)
   any later version.

   GAS 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 GAS; see the file COPYING.  If not, write to the Free Software
   Foundation, 51 Franklin Street - Fifth Floor, Boston, MA 02110-1301, USA.  */

#include "as.h"
#include "safe-ctype.h"
#include "subsegs.h"
#include "opcode/i860.h"
#include "elf/i860.h"


/* The opcode hash table.  */
static struct hash_control *op_hash = NULL;

/* These characters always start a comment.  */
const char comment_chars[] = "#!/";

/* These characters start a comment at the beginning of a line.  */
const char line_comment_chars[] = "#/";

const char line_separator_chars[] = ";";

/* Characters that can be used to separate the mantissa from the exponent
   in floating point numbers.  */
const char EXP_CHARS[] = "eE";

/* Characters that indicate this number is a floating point constant.
   As in 0f12.456 or 0d1.2345e12.  */
const char FLT_CHARS[] = "rRsSfFdDxXpP";

/* Register prefix (depends on syntax).  */
static char reg_prefix;

#define MAX_FIXUPS 2

struct i860_it
{
  char *error;
  unsigned long opcode;
  enum expand_type expand;
  struct i860_fi
  {
    expressionS exp;
    bfd_reloc_code_real_type reloc;
    int pcrel;
    valueT fup;
  } fi[MAX_FIXUPS];
} the_insn;

/* The current fixup count.  */
static int fc;

static char *expr_end;

/* Indicates error if a pseudo operation was expanded after a branch.  */
static char last_expand;

/* If true, then warn if any pseudo operations were expanded.  */
static int target_warn_expand = 0;

/* If true, then XP support is enabled.  */
static int target_xp = 0;

/* If true, then Intel syntax is enabled (default to AT&T/SVR4 syntax).  */
static int target_intel_syntax = 0;


/* Prototypes.  */
static void i860_process_insn (char *);
static void s_dual (int);
static void s_enddual (int);
static void s_atmp (int);
static void s_align_wrapper (int);
static int i860_get_expression (char *);
static bfd_reloc_code_real_type obtain_reloc_for_imm16 (fixS *, long *); 
#ifdef DEBUG_I860
static void print_insn (struct i860_it *);
#endif

const pseudo_typeS md_pseudo_table[] =
{
  {"align",   s_align_wrapper, 0},
  {"dual",    s_dual,          0},
  {"enddual", s_enddual,       0},
  {"atmp",    s_atmp,          0},
  {NULL,      0,               0},
};

/* Dual-instruction mode handling.  */
enum dual
{
  DUAL_OFF = 0, DUAL_ON, DUAL_DDOT, DUAL_ONDDOT,
};
static enum dual dual_mode = DUAL_OFF;

/* Handle ".dual" directive.  */
static void
s_dual (int ignore ATTRIBUTE_UNUSED)
{
  if (target_intel_syntax)
    dual_mode = DUAL_ON;
  else
    as_bad (_("Directive .dual available only with -mintel-syntax option"));
}

/* Handle ".enddual" directive.  */
static void
s_enddual (int ignore ATTRIBUTE_UNUSED)
{
  if (target_intel_syntax)
    dual_mode = DUAL_OFF;
  else
    as_bad (_("Directive .enddual available only with -mintel-syntax option"));
}

/* Temporary register used when expanding assembler pseudo operations.  */
static int atmp = 31;

static void
s_atmp (int ignore ATTRIBUTE_UNUSED)
{
  int temp;

  if (! target_intel_syntax)
    {
      as_bad (_("Directive .atmp available only with -mintel-syntax option"));
      demand_empty_rest_of_line ();
      return;
    }

  if (strncmp (input_line_pointer, "sp", 2) == 0)
    {
      input_line_pointer += 2;
      atmp = 2;
    }
  else if (strncmp (input_line_pointer, "fp", 2) == 0)
    {
      input_line_pointer += 2;
      atmp = 3;
    }
  else if (strncmp (input_line_pointer, "r", 1) == 0)
    {
      input_line_pointer += 1;
      temp = get_absolute_expression ();
      if (temp >= 0 && temp <= 31)
	atmp = temp;
      else
	as_bad (_("Unknown temporary pseudo register"));
    }
  else
    {
      as_bad (_("Unknown temporary pseudo register"));
    }
  demand_empty_rest_of_line ();
}

/* Handle ".align" directive depending on syntax mode.
   AT&T/SVR4 syntax uses the standard align directive.  However, 
   the Intel syntax additionally allows keywords for the alignment
   parameter: ".align type", where type is one of {.short, .long,
   .quad, .single, .double} representing alignments of 2, 4,
   16, 4, and 8, respectively.  */
static void
s_align_wrapper (int arg)
{
  char *parm = input_line_pointer;

  if (target_intel_syntax)
    {
      /* Replace a keyword with the equivalent integer so the
         standard align routine can parse the directive.  */
      if (strncmp (parm, ".short", 6) == 0)
        strncpy (parm, "     2", 6);
      else if (strncmp (parm, ".long", 5) == 0)
        strncpy (parm, "    4", 5);
      else if (strncmp (parm, ".quad", 5) == 0)
        strncpy (parm, "   16", 5);
      else if (strncmp (parm, ".single", 7) == 0)
        strncpy (parm, "      4", 7);
      else if (strncmp (parm, ".double", 7) == 0)
        strncpy (parm, "      8", 7);
     
      while (*input_line_pointer == ' ')
        ++input_line_pointer;
    }

  s_align_bytes (arg);
}

/* This function is called once, at assembler startup time.  It should
   set up all the tables and data structures that the MD part of the
   assembler will need.  */
void
md_begin (void)
{
  const char *retval = NULL;
  int lose = 0;
  unsigned int i = 0;

  op_hash = hash_new ();

  while (i860_opcodes[i].name != NULL)
    {
      const char *name = i860_opcodes[i].name;
      retval = hash_insert (op_hash, name, (void *) &i860_opcodes[i]);
      if (retval != NULL)
	{
	  fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
		   i860_opcodes[i].name, retval);
	  lose = 1;
	}
      do
	{
	  if (i860_opcodes[i].match & i860_opcodes[i].lose)
	    {
	      fprintf (stderr,
		       _("internal error: losing opcode: `%s' \"%s\"\n"),
		       i860_opcodes[i].name, i860_opcodes[i].args);
	      lose = 1;
	    }
	  ++i;
	}
      while (i860_opcodes[i].name != NULL
	     && strcmp (i860_opcodes[i].name, name) == 0);
    }

  if (lose)
    as_fatal (_("Defective assembler.  No assembly attempted."));

  /* Set the register prefix for either Intel or AT&T/SVR4 syntax.  */
  reg_prefix = target_intel_syntax ? 0 : '%';
}

/* This is the core of the machine-dependent assembler.  STR points to a
   machine dependent instruction.  This function emits the frags/bytes
   it assembles to.  */
void
md_assemble (char *str)
{
  char *destp;
  int num_opcodes = 1;
  int i;
  struct i860_it pseudo[3];

  gas_assert (str);
  fc = 0;

  /* Assemble the instruction.  */
  i860_process_insn (str);

  /* Check for expandable flag to produce pseudo-instructions.  This
     is an undesirable feature that should be avoided.  */
  if (the_insn.expand != 0 && the_insn.expand != XP_ONLY
      && ! (the_insn.fi[0].fup & (OP_SEL_HA | OP_SEL_H | OP_SEL_L | OP_SEL_GOT
			    | OP_SEL_GOTOFF | OP_SEL_PLT)))
    {
      for (i = 0; i < 3; i++)
	pseudo[i] = the_insn;

      fc = 1;
      switch (the_insn.expand)
	{

	case E_DELAY:
	  num_opcodes = 1;
	  break;

	case E_MOV:
	  if (the_insn.fi[0].exp.X_add_symbol == NULL
	      && the_insn.fi[0].exp.X_op_symbol == NULL
	      && (the_insn.fi[0].exp.X_add_number < (1 << 15)
		  && the_insn.fi[0].exp.X_add_number >= -(1 << 15)))
	    break;

	  /* Emit "or l%const,r0,ireg_dest".  */
	  pseudo[0].opcode = (the_insn.opcode & 0x001f0000) | 0xe4000000;
	  pseudo[0].fi[0].fup = (OP_IMM_S16 | OP_SEL_L);

	  /* Emit "orh h%const,ireg_dest,ireg_dest".  */
	  pseudo[1].opcode = (the_insn.opcode & 0x03ffffff) | 0xec000000
			      | ((the_insn.opcode & 0x001f0000) << 5);
	  pseudo[1].fi[0].fup = (OP_IMM_S16 | OP_SEL_H);

	  num_opcodes = 2;
	  break;

	case E_ADDR:
	  if (the_insn.fi[0].exp.X_add_symbol == NULL
	      && the_insn.fi[0].exp.X_op_symbol == NULL
	      && (the_insn.fi[0].exp.X_add_number < (1 << 15)
		  && the_insn.fi[0].exp.X_add_number >= -(1 << 15)))
	    break;

	  /* Emit "orh ha%addr_expr,ireg_src2,r31".  */
	  pseudo[0].opcode = 0xec000000 | (the_insn.opcode & 0x03e00000)
			     | (atmp << 16);
	  pseudo[0].fi[0].fup = (OP_IMM_S16 | OP_SEL_HA);

	  /* Emit "l%addr_expr(r31),ireg_dest".  We pick up the fixup
	     information from the original instruction.   */
	  pseudo[1].opcode = (the_insn.opcode & ~0x03e00000) | (atmp << 21);
	  pseudo[1].fi[0].fup = the_insn.fi[0].fup | OP_SEL_L;

	  num_opcodes = 2;
	  break;

	case E_U32:
	  if (the_insn.fi[0].exp.X_add_symbol == NULL
	      && the_insn.fi[0].exp.X_op_symbol == NULL
	      && (the_insn.fi[0].exp.X_add_number < (1 << 16)
		  && the_insn.fi[0].exp.X_add_number >= 0))
	    break;

	  /* Emit "$(opcode)h h%const,ireg_src2,r31".  */
	  pseudo[0].opcode = (the_insn.opcode & 0xf3e0ffff) | 0x0c000000
			      | (atmp << 16);
	  pseudo[0].fi[0].fup = (OP_IMM_S16 | OP_SEL_H);

	  /* Emit "$(opcode) l%const,r31,ireg_dest".  */
	  pseudo[1].opcode = (the_insn.opcode & 0xf01f0000) | 0x04000000
			      | (atmp << 21);
	  pseudo[1].fi[0].fup = (OP_IMM_S16 | OP_SEL_L);

	  num_opcodes = 2;
	  break;

	case E_AND:
	  if (the_insn.fi[0].exp.X_add_symbol == NULL
	      && the_insn.fi[0].exp.X_op_symbol == NULL
	      && (the_insn.fi[0].exp.X_add_number < (1 << 16)
		  && the_insn.fi[0].exp.X_add_number >= 0))
	    break;

	  /* Emit "andnot h%const,ireg_src2,r31".  */
	  pseudo[0].opcode = (the_insn.opcode & 0x03e0ffff) | 0xd4000000
			      | (atmp << 16);
	  pseudo[0].fi[0].fup = (OP_IMM_S16 | OP_SEL_H);
	  pseudo[0].fi[0].exp.X_add_number =
            -1 - the_insn.fi[0].exp.X_add_number;

	  /* Emit "andnot l%const,r31,ireg_dest".  */
	  pseudo[1].opcode = (the_insn.opcode & 0x001f0000) | 0xd4000000
			      | (atmp << 21);
	  pseudo[1].fi[0].fup = (OP_IMM_S16 | OP_SEL_L);
	  pseudo[1].fi[0].exp.X_add_number =
            -1 - the_insn.fi[0].exp.X_add_number;

	  num_opcodes = 2;
	  break;

	case E_S32:
	  if (the_insn.fi[0].exp.X_add_symbol == NULL
	      && the_insn.fi[0].exp.X_op_symbol == NULL
	      && (the_insn.fi[0].exp.X_add_number < (1 << 15)
		  && the_insn.fi[0].exp.X_add_number >= -(1 << 15)))
	    break;

	  /* Emit "orh h%const,r0,r31".  */
	  pseudo[0].opcode = 0xec000000 | (atmp << 16);
	  pseudo[0].fi[0].fup = (OP_IMM_S16 | OP_SEL_H);

	  /* Emit "or l%const,r31,r31".  */
	  pseudo[1].opcode = 0xe4000000 | (atmp << 21) | (atmp << 16);
	  pseudo[1].fi[0].fup = (OP_IMM_S16 | OP_SEL_L);

	  /* Emit "r31,ireg_src2,ireg_dest".  */
	  pseudo[2].opcode = (the_insn.opcode & ~0x0400ffff) | (atmp << 11);
	  pseudo[2].fi[0].fup = OP_IMM_S16;

	  num_opcodes = 3;
	  break;

	default:
	  as_fatal (_("failed sanity check."));
	}

      the_insn = pseudo[0];

      /* Warn if an opcode is expanded after a delayed branch.  */
      if (num_opcodes > 1 && last_expand == 1)
	as_warn (_("Expanded opcode after delayed branch: `%s'"), str);

      /* Warn if an opcode is expanded in dual mode.  */
      if (num_opcodes > 1 && dual_mode != DUAL_OFF)
	as_warn (_("Expanded opcode in dual mode: `%s'"), str);

      /* Notify if any expansions happen.  */
      if (target_warn_expand && num_opcodes > 1)
	as_warn (_("An instruction was expanded (%s)"), str);
    }

  dwarf2_emit_insn (0);
  i = 0;
  do
    {
      int tmp;

      /* Output the opcode.  Note that the i860 always reads instructions
	 as little-endian data.  */
      destp = frag_more (4);
      number_to_chars_littleendian (destp, the_insn.opcode, 4);

      /* Check for expanded opcode after branch or in dual mode.  */
      last_expand = the_insn.fi[0].pcrel;

      /* Output the symbol-dependent stuff.  Only btne and bte will ever
         loop more than once here, since only they (possibly) have more
         than one fixup.  */
      for (tmp = 0; tmp < fc; tmp++)
        {
          if (the_insn.fi[tmp].fup != OP_NONE)
	    {
	      fixS *fix;
	      fix = fix_new_exp (frag_now,
			         destp - frag_now->fr_literal,
			         4,
			         &the_insn.fi[tmp].exp,
			         the_insn.fi[tmp].pcrel,
			         the_insn.fi[tmp].reloc);

	     /* Despite the odd name, this is a scratch field.  We use
	        it to encode operand type information.  */
	     fix->fx_addnumber = the_insn.fi[tmp].fup;
	   }
        }
      the_insn = pseudo[++i];
    }
  while (--num_opcodes > 0);

}

/* Assemble the instruction pointed to by STR.  */
static void
i860_process_insn (char *str)
{
  char *s;
  const char *args;
  char c;
  struct i860_opcode *insn;
  char *args_start;
  unsigned long opcode;
  unsigned int mask;
  int match = 0;
  int comma = 0;

#if 1 /* For compiler warnings.  */
  args = 0;
  insn = 0;
  args_start = 0;
  opcode = 0;
#endif

  for (s = str; ISLOWER (*s) || *s == '.' || *s == '3'
       || *s == '2' || *s == '1'; ++s)
    ;

  switch (*s)
    {
    case '\0':
      break;

    case ',':
      comma = 1;

      /*FALLTHROUGH*/

    case ' ':
      *s++ = '\0';
      break;

    default:
      as_fatal (_("Unknown opcode: `%s'"), str);
    }

  /* Check for dual mode ("d.") opcode prefix.  */
  if (strncmp (str, "d.", 2) == 0)
    {
      if (dual_mode == DUAL_ON)
	dual_mode = DUAL_ONDDOT;
      else
	dual_mode = DUAL_DDOT;
      str += 2;
    }

  if ((insn = (struct i860_opcode *) hash_find (op_hash, str)) == NULL)
    {
      if (dual_mode == DUAL_DDOT || dual_mode == DUAL_ONDDOT)
	str -= 2;
      as_bad (_("Unknown opcode: `%s'"), str);
      return;
    }

  if (comma)
    *--s = ',';

  args_start = s;
  for (;;)
    {
      int t;
      opcode = insn->match;
      memset (&the_insn, '\0', sizeof (the_insn));
      fc = 0;
      for (t = 0; t < MAX_FIXUPS; t++)
        {
          the_insn.fi[t].reloc = BFD_RELOC_NONE;
          the_insn.fi[t].pcrel = 0;
          the_insn.fi[t].fup = OP_NONE;
        }

      /* Build the opcode, checking as we go that the operands match.  */
      for (args = insn->args; ; ++args)
	{
          if (fc > MAX_FIXUPS)
            abort ();

	  switch (*args)
	    {

	    /* End of args.  */
	    case '\0':
	      if (*s == '\0')
		match = 1;
	      break;

	    /* These must match exactly.  */
	    case '+':
	    case '(':
	    case ')':
	    case ',':
	    case ' ':
	      if (*s++ == *args)
		continue;
	      break;

	    /* Must be at least one digit.  */
	    case '#':
	      if (ISDIGIT (*s++))
		{
		  while (ISDIGIT (*s))
		    ++s;
		  continue;
		}
	      break;

	    /* Next operand must be a register.  */
	    case '1':
	    case '2':
	    case 'd':
	      /* Check for register prefix if necessary.  */
	      if (reg_prefix && *s != reg_prefix)
		goto error;
	      else if (reg_prefix)
		s++;

	      switch (*s)
		{
		/* Frame pointer.  */
		case 'f':
		  s++;
		  if (*s++ == 'p')
		    {
		      mask = 0x3;
		      break;
		    }
		  goto error;

		/* Stack pointer.  */
		case 's':
		  s++;
		  if (*s++ == 'p')
		    {
		      mask = 0x2;
		      break;
		    }
		  goto error;

		/* Any register r0..r31.  */
		case 'r':
		  s++;
		  if (!ISDIGIT (c = *s++))
		    {
		      goto error;
		    }
		  if (ISDIGIT (*s))
		    {
		      if ((c = 10 * (c - '0') + (*s++ - '0')) >= 32)
			goto error;
		    }
		  else
		    c -= '0';
		  mask = c;
		  break;

		/* Not this opcode.  */
		default:
		  goto error;
		}

	      /* Obtained the register, now place it in the opcode.  */
	      switch (*args)
		{
		case '1':
		  opcode |= mask << 11;
		  continue;

		case '2':
		  opcode |= mask << 21;
		  continue;

		case 'd':
		  opcode |= mask << 16;
		  continue;

		}
	      break;

	    /* Next operand is a floating point register.  */
	    case 'e':
	    case 'f':
	    case 'g':
	      /* Check for register prefix if necessary.  */
	      if (reg_prefix && *s != reg_prefix)
		goto error;
	      else if (reg_prefix)
		s++;

	      if (*s++ == 'f' && ISDIGIT (*s))
		{
		  mask = *s++;
		  if (ISDIGIT (*s))
		    {
		      mask = 10 * (mask - '0') + (*s++ - '0');
		      if (mask >= 32)
			{
			  break;
			}
		    }
		  else
		    mask -= '0';

		  switch (*args)
		    {

		    case 'e':
		      opcode |= mask << 11;
		      continue;

		    case 'f':
		      opcode |= mask << 21;
		      continue;

		    case 'g':
		      opcode |= mask << 16;
		      if ((opcode & (1 << 10)) && mask != 0
			  && (mask == ((opcode >> 11) & 0x1f)))
			as_warn (_("Pipelined instruction: fsrc1 = fdest"));
		      continue;
		    }
		}
	      break;

	    /* Next operand must be a control register.  */
	    case 'c':
	      /* Check for register prefix if necessary.  */
	      if (reg_prefix && *s != reg_prefix)
		goto error;
	      else if (reg_prefix)
		s++;

	      if (strncmp (s, "fir", 3) == 0)
		{
		  opcode |= 0x0 << 21;
		  s += 3;
		  continue;
		}
	      if (strncmp (s, "psr", 3) == 0)
		{
		  opcode |= 0x1 << 21;
		  s += 3;
		  continue;
		}
	      if (strncmp (s, "dirbase", 7) == 0)
		{
		  opcode |= 0x2 << 21;
		  s += 7;
		  continue;
		}
	      if (strncmp (s, "db", 2) == 0)
		{
		  opcode |= 0x3 << 21;
		  s += 2;
		  continue;
		}
	      if (strncmp (s, "fsr", 3) == 0)
		{
		  opcode |= 0x4 << 21;
		  s += 3;
		  continue;
		}
	      if (strncmp (s, "epsr", 4) == 0)
		{
		  opcode |= 0x5 << 21;
		  s += 4;
		  continue;
		}
	      /* The remaining control registers are XP only.  */
	      if (target_xp && strncmp (s, "bear", 4) == 0)
		{
		  opcode |= 0x6 << 21;
		  s += 4;
		  continue;
		}
	      if (target_xp && strncmp (s, "ccr", 3) == 0)
		{
		  opcode |= 0x7 << 21;
		  s += 3;
		  continue;
		}
	      if (target_xp && strncmp (s, "p0", 2) == 0)
		{
		  opcode |= 0x8 << 21;
		  s += 2;
		  continue;
		}
	      if (target_xp && strncmp (s, "p1", 2) == 0)
		{
		  opcode |= 0x9 << 21;
		  s += 2;
		  continue;
		}
	      if (target_xp && strncmp (s, "p2", 2) == 0)
		{
		  opcode |= 0xa << 21;
		  s += 2;
		  continue;
		}
	      if (target_xp && strncmp (s, "p3", 2) == 0)
		{
		  opcode |= 0xb << 21;
		  s += 2;
		  continue;
		}
	      break;

	    /* 5-bit immediate in src1.  */
	    case '5':
	      if (! i860_get_expression (s))
		{
		  s = expr_end;
		  the_insn.fi[fc].fup |= OP_IMM_U5;
		  fc++;
		  continue;
		}
	      break;

	    /* 26-bit immediate, relative branch (lbroff).  */
	    case 'l':
	      the_insn.fi[fc].pcrel = 1;
	      the_insn.fi[fc].fup |= OP_IMM_BR26;
	      goto immediate;

	    /* 16-bit split immediate, relative branch (sbroff).  */
	    case 'r':
	      the_insn.fi[fc].pcrel = 1;
	      the_insn.fi[fc].fup |= OP_IMM_BR16;
	      goto immediate;

	    /* 16-bit split immediate.  */
	    case 's':
	      the_insn.fi[fc].fup |= OP_IMM_SPLIT16;
	      goto immediate;

	    /* 16-bit split immediate, byte aligned (st.b).  */
	    case 'S':
	      the_insn.fi[fc].fup |= OP_IMM_SPLIT16;
	      goto immediate;

	    /* 16-bit split immediate, half-word aligned (st.s).  */
	    case 'T':
	      the_insn.fi[fc].fup |= (OP_IMM_SPLIT16 | OP_ENCODE1 | OP_ALIGN2);
	      goto immediate;

	    /* 16-bit split immediate, word aligned (st.l).  */
	    case 'U':
	      the_insn.fi[fc].fup |= (OP_IMM_SPLIT16 | OP_ENCODE1 | OP_ALIGN4);
	      goto immediate;

	    /* 16-bit immediate.  */
	    case 'i':
	      the_insn.fi[fc].fup |= OP_IMM_S16;
	      goto immediate;

	    /* 16-bit immediate, byte aligned (ld.b).  */
	    case 'I':
	      the_insn.fi[fc].fup |= OP_IMM_S16;
	      goto immediate;

	    /* 16-bit immediate, half-word aligned (ld.s).  */
	    case 'J':
	      the_insn.fi[fc].fup |= (OP_IMM_S16 | OP_ENCODE1 | OP_ALIGN2);
	      goto immediate;

	    /* 16-bit immediate, word aligned (ld.l, {p}fld.l, fst.l).  */
	    case 'K':
	      if (insn->name[0] == 'l')
		the_insn.fi[fc].fup |= (OP_IMM_S16 | OP_ENCODE1 | OP_ALIGN4);
	      else
		the_insn.fi[fc].fup |= (OP_IMM_S16 | OP_ENCODE2 | OP_ALIGN4);
	      goto immediate;

	    /* 16-bit immediate, double-word aligned ({p}fld.d, fst.d).  */
	    case 'L':
	      the_insn.fi[fc].fup |= (OP_IMM_S16 | OP_ENCODE3 | OP_ALIGN8);
	      goto immediate;

	    /* 16-bit immediate, quad-word aligned (fld.q, fst.q).  */
	    case 'M':
	      the_insn.fi[fc].fup |= (OP_IMM_S16 | OP_ENCODE3 | OP_ALIGN16);

	      /*FALLTHROUGH*/

	      /* Handle the immediate for either the Intel syntax or
		 SVR4 syntax. The Intel syntax is "ha%immediate"
		 whereas SVR4 syntax is "[immediate]@ha".  */
	    immediate:
	      if (target_intel_syntax == 0)
		{
		  /* AT&T/SVR4 syntax.  */
	          if (*s == ' ')
		    s++;

	          /* Note that if i860_get_expression() fails, we will still
	  	     have created U entries in the symbol table for the
		     'symbols' in the input string.  Try not to create U
		     symbols for registers, etc.  */
	          if (! i860_get_expression (s))
		    s = expr_end;
	          else
		    goto error;

	          if (strncmp (s, "@ha", 3) == 0)
		    {
		      the_insn.fi[fc].fup |= OP_SEL_HA;
		      s += 3;
		    }
	          else if (strncmp (s, "@h", 2) == 0)
		    {
		      the_insn.fi[fc].fup |= OP_SEL_H;
		      s += 2;
		    }
	          else if (strncmp (s, "@l", 2) == 0)
		    {
		      the_insn.fi[fc].fup |= OP_SEL_L;
		      s += 2;
		    }
	          else if (strncmp (s, "@gotoff", 7) == 0
		           || strncmp (s, "@GOTOFF", 7) == 0)
		    {
		      as_bad (_("Assembler does not yet support PIC"));
		      the_insn.fi[fc].fup |= OP_SEL_GOTOFF;
		      s += 7;
		    }
	          else if (strncmp (s, "@got", 4) == 0
		           || strncmp (s, "@GOT", 4) == 0)
		    {
		      as_bad (_("Assembler does not yet support PIC"));
		      the_insn.fi[fc].fup |= OP_SEL_GOT;
		      s += 4;
		    }
	          else if (strncmp (s, "@plt", 4) == 0
		           || strncmp (s, "@PLT", 4) == 0)
		    {
		      as_bad (_("Assembler does not yet support PIC"));
		      the_insn.fi[fc].fup |= OP_SEL_PLT;
		      s += 4;
		    }

	          the_insn.expand = insn->expand;
                  fc++;
              
	          continue;
		}
	      else
		{
		  /* Intel syntax.  */
	          if (*s == ' ')
		    s++;
	          if (strncmp (s, "ha%", 3) == 0)
		    {
		      the_insn.fi[fc].fup |= OP_SEL_HA;
		      s += 3;
		    }
	          else if (strncmp (s, "h%", 2) == 0)
		    {
		      the_insn.fi[fc].fup |= OP_SEL_H;
		      s += 2;
		    }
	          else if (strncmp (s, "l%", 2) == 0)
		    {
		      the_insn.fi[fc].fup |= OP_SEL_L;
		      s += 2;
		    }
	          the_insn.expand = insn->expand;

	          /* Note that if i860_get_expression() fails, we will still
		     have created U entries in the symbol table for the
		     'symbols' in the input string.  Try not to create U
		     symbols for registers, etc.  */
	          if (! i860_get_expression (s))
		    s = expr_end;
	          else
		    goto error;

                  fc++;
	          continue;
		}
	      break;

	    default:
	      as_fatal (_("failed sanity check."));
	    }
	  break;
	}
    error:
      if (match == 0)
	{
	  /* Args don't match.  */
	  if (insn[1].name != NULL
	      && ! strcmp (insn->name, insn[1].name))
	    {
	      ++insn;
	      s = args_start;
	      continue;
	    }
	  else
	    {
	      as_bad (_("Illegal operands for %s"), insn->name);
	      return;
	    }
	}
      break;
    }

  /* Set the dual bit on this instruction if necessary.  */
  if (dual_mode != DUAL_OFF)
    {
      if ((opcode & 0xfc000000) == 0x48000000 || opcode == 0xb0000000)
        {
	  /* The instruction is a flop or a fnop, so set its dual bit
	     (but check that it is 8-byte aligned).  */
	  if (((frag_now->fr_address + frag_now_fix_octets ()) & 7) == 0)
	    opcode |= (1 << 9);
	  else
            as_bad (_("'d.%s' must be 8-byte aligned"), insn->name);

          if (dual_mode == DUAL_DDOT)
	    dual_mode = DUAL_OFF;
          else if (dual_mode == DUAL_ONDDOT)
	    dual_mode = DUAL_ON;
        }
      else if (dual_mode == DUAL_DDOT || dual_mode == DUAL_ONDDOT)
        as_bad (_("Prefix 'd.' invalid for instruction `%s'"), insn->name);
    }

  the_insn.opcode = opcode;

  /* Only recognize XP instructions when the user has requested it.  */
  if (insn->expand == XP_ONLY && ! target_xp)
    as_bad (_("Unknown opcode: `%s'"), insn->name);
}

static int
i860_get_expression (char *str)
{
  char *save_in;
  segT seg;

  save_in = input_line_pointer;
  input_line_pointer = str;
  seg = expression (&the_insn.fi[fc].exp);
  if (seg != absolute_section
      && seg != undefined_section
      && ! SEG_NORMAL (seg))
    {
      the_insn.error = _("bad segment");
      expr_end = input_line_pointer;
      input_line_pointer = save_in;
      return 1;
    }
  expr_end = input_line_pointer;
  input_line_pointer = save_in;
  return 0;
}

char *
md_atof (int type, char *litP, int *sizeP)
{
  return ieee_md_atof (type, litP, sizeP, TRUE);
}

/* Write out in current endian mode.  */
void
md_number_to_chars (char *buf, valueT val, int n)
{
  if (target_big_endian)
    number_to_chars_bigendian (buf, val, n);
  else
    number_to_chars_littleendian (buf, val, n);
}

/* This should never be called for i860.  */
int
md_estimate_size_before_relax (register fragS *fragP ATTRIBUTE_UNUSED,
			       segT segtype ATTRIBUTE_UNUSED)
{
  as_fatal (_("relaxation not supported\n"));
}

#ifdef DEBUG_I860
static void
print_insn (struct i860_it *insn)
{
  if (insn->error)
    fprintf (stderr, "ERROR: %s\n", insn->error);

  fprintf (stderr, "opcode = 0x%08lx\t", insn->opcode);
  fprintf (stderr, "expand = 0x%x\t", insn->expand);
  fprintf (stderr, "reloc = %s\t\n",
	   bfd_get_reloc_code_name (insn->reloc));
  fprintf (stderr, "exp =  {\n");
  fprintf (stderr, "\t\tX_add_symbol = %s\n",
	   insn->exp.X_add_symbol ?
	   (S_GET_NAME (insn->exp.X_add_symbol) ?
	    S_GET_NAME (insn->exp.X_add_symbol) : "???") : "0");
  fprintf (stderr, "\t\tX_op_symbol = %s\n",
	   insn->exp.X_op_symbol ?
	   (S_GET_NAME (insn->exp.X_op_symbol) ?
	    S_GET_NAME (insn->exp.X_op_symbol) : "???") : "0");
  fprintf (stderr, "\t\tX_add_number = %lx\n",
	   insn->exp.X_add_number);
  fprintf (stderr, "}\n");
}
#endif /* DEBUG_I860 */


#ifdef OBJ_ELF
const char *md_shortopts = "VQ:";
#else
const char *md_shortopts = "";
#endif

#define OPTION_EB		(OPTION_MD_BASE + 0)
#define OPTION_EL		(OPTION_MD_BASE + 1)
#define OPTION_WARN_EXPAND	(OPTION_MD_BASE + 2)
#define OPTION_XP		(OPTION_MD_BASE + 3)
#define OPTION_INTEL_SYNTAX	(OPTION_MD_BASE + 4)

struct option md_longopts[] = {
  { "EB",	    no_argument, NULL, OPTION_EB },
  { "EL",	    no_argument, NULL, OPTION_EL },
  { "mwarn-expand", no_argument, NULL, OPTION_WARN_EXPAND },
  { "mxp",	    no_argument, NULL, OPTION_XP },
  { "mintel-syntax",no_argument, NULL, OPTION_INTEL_SYNTAX },
  { 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)
    {
    case OPTION_EB:
      target_big_endian = 1;
      break;

    case OPTION_EL:
      target_big_endian = 0;
      break;

    case OPTION_WARN_EXPAND:
      target_warn_expand = 1;
      break;

    case OPTION_XP:
      target_xp = 1;
      break;

    case OPTION_INTEL_SYNTAX:
      target_intel_syntax = 1;
      break;

#ifdef OBJ_ELF
    /* SVR4 argument compatibility (-V): print version ID.  */
    case 'V':
      print_version_id ();
      break;

    /* SVR4 argument compatibility (-Qy, -Qn): controls whether
       a .comment section should be emitted or not (ignored).  */
    case 'Q':
      break;
#endif

    default:
      return 0;
    }

  return 1;
}

void
md_show_usage (FILE *stream)
{
  fprintf (stream, _("\
  -EL			  generate code for little endian mode (default)\n\
  -EB			  generate code for big endian mode\n\
  -mwarn-expand		  warn if pseudo operations are expanded\n\
  -mxp			  enable i860XP support (disabled by default)\n\
  -mintel-syntax	  enable Intel syntax (default to AT&T/SVR4)\n"));
#ifdef OBJ_ELF
  /* SVR4 compatibility flags.  */
  fprintf (stream, _("\
  -V			  print assembler version number\n\
  -Qy, -Qn		  ignored\n"));
#endif
}


/* We have no need to default values of symbols.  */
symbolS *
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
{
  return 0;
}

/* The i860 denotes auto-increment with '++'.  */
void
md_operand (expressionS *exp)
{
  char *s;

  for (s = input_line_pointer; *s; s++)
    {
      if (s[0] == '+' && s[1] == '+')
	{
	  input_line_pointer += 2;
	  exp->X_op = O_register;
	  break;
	}
    }
}

/* Round up a section size to the appropriate boundary.  */
valueT
md_section_align (segT segment ATTRIBUTE_UNUSED,
		  valueT size ATTRIBUTE_UNUSED)
{
  /* Byte alignment is fine.  */
  return size;
}

/* On the i860, a PC-relative offset is relative to the address of the
   offset plus its size.  */
long
md_pcrel_from (fixS *fixP)
{
  return fixP->fx_size + fixP->fx_where + fixP->fx_frag->fr_address;
}

/* Determine the relocation needed for non PC-relative 16-bit immediates.
   Also adjust the given immediate as necessary.  Finally, check that
   all constraints (such as alignment) are satisfied.   */
static bfd_reloc_code_real_type
obtain_reloc_for_imm16 (fixS *fix, long *val)
{
  valueT fup = fix->fx_addnumber;
  bfd_reloc_code_real_type reloc;

  if (fix->fx_pcrel)
    abort ();

  /* Check alignment restrictions.  */
  if ((fup & OP_ALIGN2) && (*val & 0x1))
    as_bad_where (fix->fx_file, fix->fx_line,
		  _("This immediate requires 0 MOD 2 alignment"));
  else if ((fup & OP_ALIGN4) && (*val & 0x3))
    as_bad_where (fix->fx_file, fix->fx_line,
		  _("This immediate requires 0 MOD 4 alignment"));
  else if ((fup & OP_ALIGN8) && (*val & 0x7))
    as_bad_where (fix->fx_file, fix->fx_line,
		  _("This immediate requires 0 MOD 8 alignment"));
  else if ((fup & OP_ALIGN16) && (*val & 0xf))
    as_bad_where (fix->fx_file, fix->fx_line,
		  _("This immediate requires 0 MOD 16 alignment"));

  if (fup & OP_SEL_HA)
    {
      *val = (*val >> 16) + (*val & 0x8000 ? 1 : 0);
      reloc = BFD_RELOC_860_HIGHADJ;
    }
  else if (fup & OP_SEL_H)
    {
      *val >>= 16;
      reloc = BFD_RELOC_860_HIGH;
    }
  else if (fup & OP_SEL_L)
    {
      int num_encode;
      if (fup & OP_IMM_SPLIT16)
	{
	  if (fup & OP_ENCODE1)
	    {
	      num_encode = 1;
	      reloc = BFD_RELOC_860_SPLIT1;
	    }
	  else if (fup & OP_ENCODE2)
	    {
	      num_encode = 2;
	      reloc = BFD_RELOC_860_SPLIT2;
	    }
	  else
	    {
	      num_encode = 0;
	      reloc = BFD_RELOC_860_SPLIT0;
	    }
	}
      else
	{
	  if (fup & OP_ENCODE1)
	    {
	      num_encode = 1;
	      reloc = BFD_RELOC_860_LOW1;
	    }
	  else if (fup & OP_ENCODE2)
	    {
	      num_encode = 2;
	      reloc = BFD_RELOC_860_LOW2;
	    }
	  else if (fup & OP_ENCODE3)
	    {
	      num_encode = 3;
	      reloc = BFD_RELOC_860_LOW3;
	    }
	  else
	    {
	      num_encode = 0;
	      reloc = BFD_RELOC_860_LOW0;
	    }
	}

      /* Preserve size encode bits.  */
      *val &= ~((1 << num_encode) - 1);
    }
  else
    {
      /* No selector.  What reloc do we generate (???)?  */
      reloc = BFD_RELOC_32;
    }

  return reloc;
}

/* Attempt to simplify or eliminate a fixup. To indicate that a fixup
   has been eliminated, set fix->fx_done. If fix->fx_addsy is non-NULL,
   we will have to generate a reloc entry.  */

void
md_apply_fix (fixS *fix, valueT *valP, segT seg ATTRIBUTE_UNUSED)
{
  char *buf;
  long val = *valP;
  unsigned long insn;
  valueT fup;

  buf = fix->fx_frag->fr_literal + fix->fx_where;

  /* Recall that earlier we stored the opcode little-endian.  */
  insn = bfd_getl32 (buf);

  /* We stored a fix-up in this oddly-named scratch field.  */
  fup = fix->fx_addnumber;

  /* Determine the necessary relocations as well as inserting an
     immediate into the instruction.   */
  if (fup & OP_IMM_U5)
    {
      if (val & ~0x1f)
	as_bad_where (fix->fx_file, fix->fx_line,
		      _("5-bit immediate too large"));
      if (fix->fx_addsy)
	as_bad_where (fix->fx_file, fix->fx_line,
		      _("5-bit field must be absolute"));

      insn |= (val & 0x1f) << 11;
      bfd_putl32 (insn, buf);
      fix->fx_r_type = BFD_RELOC_NONE;
      fix->fx_done = 1;
    }
  else if (fup & OP_IMM_S16)
    {
      fix->fx_r_type = obtain_reloc_for_imm16 (fix, &val);

      /* Insert the immediate.  */
      if (fix->fx_addsy)
	fix->fx_done = 0;
      else
	{
	  insn |= val & 0xffff;
	  bfd_putl32 (insn, buf);
	  fix->fx_r_type = BFD_RELOC_NONE;
	  fix->fx_done = 1;
	}
    }
  else if (fup & OP_IMM_U16)
    abort ();

  else if (fup & OP_IMM_SPLIT16)
    {
      fix->fx_r_type = obtain_reloc_for_imm16 (fix, &val);

      /* Insert the immediate.  */
      if (fix->fx_addsy)
	fix->fx_done = 0;
      else
	{
	  insn |= val & 0x7ff;
	  insn |= (val & 0xf800) << 5;
	  bfd_putl32 (insn, buf);
	  fix->fx_r_type = BFD_RELOC_NONE;
	  fix->fx_done = 1;
	}
    }
  else if (fup & OP_IMM_BR16)
    {
      if (val & 0x3)
	as_bad_where (fix->fx_file, fix->fx_line,
		      _("A branch offset requires 0 MOD 4 alignment"));

      val = val >> 2;

      /* Insert the immediate.  */
      if (fix->fx_addsy)
	{
	  fix->fx_done = 0;
	  fix->fx_r_type = BFD_RELOC_860_PC16;
	}
      else
	{
	  insn |= (val & 0x7ff);
	  insn |= ((val & 0xf800) << 5);
	  bfd_putl32 (insn, buf);
	  fix->fx_r_type = BFD_RELOC_NONE;
	  fix->fx_done = 1;
	}
    }
  else if (fup & OP_IMM_BR26)
    {
      if (val & 0x3)
	as_bad_where (fix->fx_file, fix->fx_line,
		      _("A branch offset requires 0 MOD 4 alignment"));

      val >>= 2;

      /* Insert the immediate.  */
      if (fix->fx_addsy)
	{
	  fix->fx_r_type = BFD_RELOC_860_PC26;
	  fix->fx_done = 0;
	}
      else
	{
	  insn |= (val & 0x3ffffff);
	  bfd_putl32 (insn, buf);
	  fix->fx_r_type = BFD_RELOC_NONE;
	  fix->fx_done = 1;
	}
    }
  else if (fup != OP_NONE)
    {
      as_bad_where (fix->fx_file, fix->fx_line,
		    _("Unrecognized fix-up (0x%08lx)"), (unsigned long) fup);
      abort ();
    }
  else
    {
      /* I believe only fix-ups such as ".long .ep.main-main+0xc8000000"
 	 reach here (???).  */
      if (fix->fx_addsy)
	{
	  fix->fx_r_type = BFD_RELOC_32;
	  fix->fx_done = 0;
	}
      else
	{
	  insn |= (val & 0xffffffff);
	  bfd_putl32 (insn, buf);
	  fix->fx_r_type = BFD_RELOC_NONE;
	  fix->fx_done = 1;
	}
    }
}

/* Generate a machine dependent reloc from a fixup.  */
arelent*
tc_gen_reloc (asection *section ATTRIBUTE_UNUSED,
	      fixS *fixp)
{
  arelent *reloc;

  reloc = xmalloc (sizeof (*reloc));
  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;
  reloc->addend = fixp->fx_offset;
  reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);

  if (! reloc->howto)
    {
      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 reloc;
}

/* This is called from HANDLE_ALIGN in write.c.  Fill in the contents
   of an rs_align_code fragment.  */

void
i860_handle_align (fragS *fragp)
{
  /* Instructions are always stored little-endian on the i860.  */
  static const unsigned char le_nop[] = { 0x00, 0x00, 0x00, 0xA0 };

  int bytes;
  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;

  /* Make sure we are on a 4-byte boundary, in case someone has been
     putting data into a text section.  */
  if (bytes & 3)
    {
      int fix = bytes & 3;
      memset (p, 0, fix);
      p += fix;
      fragp->fr_fix += fix;
    }

  memcpy (p, le_nop, 4);
  fragp->fr_var = 4;
}

/* This is called after a user-defined label is seen.  We check
   if the label has a double colon (valid in Intel syntax mode only),
   in which case it should be externalized.  */

void
i860_check_label (symbolS *labelsym)
{
  /* At this point, the current line pointer is sitting on the character
     just after the first colon on the label.  */ 
  if (target_intel_syntax && *input_line_pointer == ':')
    {
      S_SET_EXTERNAL (labelsym);
      input_line_pointer++;
    }
}
