binutils-gdb/gas/config/tc-i860.c

1398 lines
32 KiB
C
Raw Normal View History

/* tc-i860.c -- Assembler for the Intel i860 architecture.
2001-03-09 07:24:26 +08:00
Copyright 1989, 1992, 1993, 1994, 1995, 1998, 1999, 2000
Free Software Foundation, Inc.
Brought back from the dead and completely reworked
by Jason Eckhardt <jle@cygnus.com>.
1999-05-03 15:29:11 +08:00
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 2, 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, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */
#include <ctype.h>
#include <stdio.h>
#include <string.h>
1999-05-03 15:29:11 +08:00
#include "as.h"
#include "subsegs.h"
1999-05-03 15:29:11 +08:00
#include "opcode/i860.h"
#include "elf/i860.h"
1999-05-03 15:29:11 +08:00
/* Defined by default since this is primarily a SVR4/860 assembler.
However, I'm trying to leave the door open for Intel syntax. Of course,
if full support for anything other than SVR4 is done, then we should
select this based on a command-line flag. */
#define SYNTAX_SVR4
1999-05-03 15:29:11 +08:00
/* The opcode hash table. */
1999-05-03 15:29:11 +08:00
static struct hash_control *op_hash = NULL;
/* These characters always start a comment. */
const char comment_chars[] = "#!/";
1999-05-03 15:29:11 +08:00
/* These characters start a comment at the beginning of a line. */
1999-05-03 15:29:11 +08:00
const char line_comment_chars[] = "#/";
const char line_separator_chars[] = ";";
1999-05-03 15:29:11 +08:00
/* Characters that can be used to separate the mantissa from the exponent
in floating point numbers. */
1999-05-03 15:29:11 +08:00
const char EXP_CHARS[] = "eE";
/* Characters that indicate this number is a floating point constant.
As in 0f12.456 or 0d1.2345e12. */
1999-05-03 15:29:11 +08:00
const char FLT_CHARS[] = "rRsSfFdDxXpP";
/* Register prefix. */
#ifdef SYNTAX_SVR4
static const char reg_prefix = '%';
#else
static const char reg_prefix = 0;
#endif
1999-05-03 15:29:11 +08:00
struct i860_it
{
char *error;
unsigned long opcode;
expressionS exp;
enum expand_type expand;
bfd_reloc_code_real_type reloc;
int pcrel;
valueT fup;
} the_insn;
static char *expr_end;
1999-05-03 15:29:11 +08:00
/* Indicates error if a pseudo operation was expanded after a branch. */
static char last_expand;
1999-05-03 15:29:11 +08:00
/* If true, then warn if any pseudo operations were expanded. */
static int target_warn_expand = 0;
1999-05-03 15:29:11 +08:00
/* Prototypes. */
static void i860_process_insn PARAMS ((char *));
static void s_dual PARAMS ((int));
static void s_enddual PARAMS ((int));
static void s_atmp PARAMS ((int));
static int i860_get_expression PARAMS ((char *));
static bfd_reloc_code_real_type obtain_reloc_for_imm16
PARAMS ((fixS *, long *));
#ifdef DEBUG_I860
static void print_insn PARAMS ((struct i860_it *));
#endif
1999-05-03 15:29:11 +08:00
const pseudo_typeS md_pseudo_table[] =
{
#ifdef OBJ_ELF
{"align", s_align_bytes, 0},
#endif
{"dual", s_dual, 0},
{"enddual", s_enddual, 0},
{"atmp", s_atmp, 0},
{NULL, 0, 0},
};
/* Dual-instruction mode handling. */
1999-05-03 15:29:11 +08:00
enum dual
{
DUAL_OFF = 0, DUAL_ON, DUAL_DDOT, DUAL_ONDDOT,
};
static enum dual dual_mode = DUAL_OFF;
/* Handle ".dual" directive. */
1999-05-03 15:29:11 +08:00
static void
s_dual (ignore)
int ignore ATTRIBUTE_UNUSED;
1999-05-03 15:29:11 +08:00
{
dual_mode = DUAL_ON;
}
/* Handle ".enddual" directive. */
1999-05-03 15:29:11 +08:00
static void
s_enddual (ignore)
int ignore ATTRIBUTE_UNUSED;
1999-05-03 15:29:11 +08:00
{
dual_mode = DUAL_OFF;
}
/* Temporary register used when expanding assembler pseudo operations. */
static int atmp = 31;
1999-05-03 15:29:11 +08:00
static void
s_atmp (ignore)
int ignore ATTRIBUTE_UNUSED;
1999-05-03 15:29:11 +08:00
{
register int temp;
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 ();
}
/* 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. */
1999-05-03 15:29:11 +08:00
void
md_begin ()
{
const char *retval = NULL;
1999-05-03 15:29:11 +08:00
int lose = 0;
unsigned int i = 0;
1999-05-03 15:29:11 +08:00
op_hash = hash_new ();
while (i860_opcodes[i].name != NULL)
1999-05-03 15:29:11 +08:00
{
const char *name = i860_opcodes[i].name;
retval = hash_insert (op_hash, name, (PTR)&i860_opcodes[i]);
1999-05-03 15:29:11 +08:00
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"),
1999-05-03 15:29:11 +08:00
i860_opcodes[i].name, i860_opcodes[i].args);
lose = 1;
}
++i;
}
while (i860_opcodes[i].name != NULL
&& strcmp (i860_opcodes[i].name, name) == 0);
1999-05-03 15:29:11 +08:00
}
if (lose)
as_fatal (_("Defective assembler. No assembly attempted."));
1999-05-03 15:29:11 +08:00
}
/* 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. */
1999-05-03 15:29:11 +08:00
void
md_assemble (str)
char *str;
{
char *destp;
int num_opcodes = 1;
1999-05-03 15:29:11 +08:00
int i;
struct i860_it pseudo[3];
assert (str);
/* 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.fup & (OP_SEL_HA | OP_SEL_H | OP_SEL_L | OP_SEL_GOT
| OP_SEL_GOTOFF | OP_SEL_PLT)))
1999-05-03 15:29:11 +08:00
{
for (i = 0; i < 3; i++)
pseudo[i] = the_insn;
switch (the_insn.expand)
{
case E_DELAY:
num_opcodes = 1;
1999-05-03 15:29:11 +08:00
break;
case E_MOV:
if (the_insn.exp.X_add_symbol == NULL
&& the_insn.exp.X_op_symbol == NULL
&& (the_insn.exp.X_add_number < (1 << 15)
&& the_insn.exp.X_add_number >= -(1 << 15)))
1999-05-03 15:29:11 +08:00
break;
/* Emit "or l%const,r0,ireg_dest". */
1999-05-03 15:29:11 +08:00
pseudo[0].opcode = (the_insn.opcode & 0x001f0000) | 0xe4000000;
pseudo[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].fup = (OP_IMM_S16 | OP_SEL_H);
num_opcodes = 2;
1999-05-03 15:29:11 +08:00
break;
case E_ADDR:
if (the_insn.exp.X_add_symbol == NULL
&& the_insn.exp.X_op_symbol == NULL
&& (the_insn.exp.X_add_number < (1 << 15)
&& the_insn.exp.X_add_number >= -(1 << 15)))
1999-05-03 15:29:11 +08:00
break;
/* Emit "orh ha%addr_expr,r0,r31". */
1999-05-03 15:29:11 +08:00
pseudo[0].opcode = 0xec000000 | (atmp << 16);
pseudo[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].fup = the_insn.fup | OP_SEL_L;
num_opcodes = 2;
1999-05-03 15:29:11 +08:00
break;
case E_U32:
if (the_insn.exp.X_add_symbol == NULL
&& the_insn.exp.X_op_symbol == NULL
&& (the_insn.exp.X_add_number < (1 << 16)
&& the_insn.exp.X_add_number >= 0))
1999-05-03 15:29:11 +08:00
break;
/* Emit "$(opcode)h h%const,ireg_src2,r31". */
pseudo[0].opcode = (the_insn.opcode & 0xf3e0ffff) | 0x0c000000
| (atmp << 16);
pseudo[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].fup = (OP_IMM_S16 | OP_SEL_L);
num_opcodes = 2;
1999-05-03 15:29:11 +08:00
break;
case E_AND:
if (the_insn.exp.X_add_symbol == NULL
&& the_insn.exp.X_op_symbol == NULL
&& (the_insn.exp.X_add_number < (1 << 16)
&& the_insn.exp.X_add_number >= 0))
1999-05-03 15:29:11 +08:00
break;
/* Emit "andnot h%const,ireg_src2,r31". */
pseudo[0].opcode = (the_insn.opcode & 0x03e0ffff) | 0xd4000000
| (atmp << 16);
pseudo[0].fup = (OP_IMM_S16 | OP_SEL_H);
1999-05-03 15:29:11 +08:00
pseudo[0].exp.X_add_number = -1 - the_insn.exp.X_add_number;
/* Emit "andnot l%const,r31,ireg_dest". */
pseudo[1].opcode = (the_insn.opcode & 0x001f0000) | 0xd4000000
| (atmp << 21);
pseudo[1].fup = (OP_IMM_S16 | OP_SEL_L);
1999-05-03 15:29:11 +08:00
pseudo[1].exp.X_add_number = -1 - the_insn.exp.X_add_number;
num_opcodes = 2;
1999-05-03 15:29:11 +08:00
break;
case E_S32:
if (the_insn.exp.X_add_symbol == NULL
&& the_insn.exp.X_op_symbol == NULL
&& (the_insn.exp.X_add_number < (1 << 15)
&& the_insn.exp.X_add_number >= -(1 << 15)))
1999-05-03 15:29:11 +08:00
break;
/* Emit "orh h%const,r0,r31". */
1999-05-03 15:29:11 +08:00
pseudo[0].opcode = 0xec000000 | (atmp << 16);
pseudo[0].fup = (OP_IMM_S16 | OP_SEL_H);
/* Emit "or l%const,r31,r31". */
1999-05-03 15:29:11 +08:00
pseudo[1].opcode = 0xe4000000 | (atmp << 21) | (atmp << 16);
pseudo[1].fup = (OP_IMM_S16 | OP_SEL_L);
/* Emit "r31,ireg_src2,ireg_dest". */
1999-05-03 15:29:11 +08:00
pseudo[2].opcode = (the_insn.opcode & ~0x0400ffff) | (atmp << 11);
pseudo[2].fup = OP_IMM_S16;
num_opcodes = 3;
1999-05-03 15:29:11 +08:00
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)
1999-05-03 15:29:11 +08:00
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)
1999-05-03 15:29:11 +08:00
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);
1999-05-03 15:29:11 +08:00
}
i = 0;
do
{
/* 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);
1999-05-03 15:29:11 +08:00
/* Check for expanded opcode after branch or in dual mode. */
1999-05-03 15:29:11 +08:00
last_expand = the_insn.pcrel;
/* Output the symbol-dependent stuff. */
if (the_insn.fup != OP_NONE)
1999-05-03 15:29:11 +08:00
{
fixS *fix;
fix = fix_new_exp (frag_now,
destp - frag_now->fr_literal,
4,
&the_insn.exp,
the_insn.pcrel,
the_insn.reloc);
/* Despite the odd name, this is a scratch field. We use
it to encode operand type information. */
fix->fx_addnumber = the_insn.fup;
1999-05-03 15:29:11 +08:00
}
the_insn = pseudo[++i];
}
while (--num_opcodes > 0);
1999-05-03 15:29:11 +08:00
}
/* Assemble the instruction pointed to by STR. */
1999-05-03 15:29:11 +08:00
static void
i860_process_insn (str)
1999-05-03 15:29:11 +08:00
char *str;
{
char *s;
const char *args;
char c;
struct i860_opcode *insn;
char *args_start;
1999-05-03 15:29:11 +08:00
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
1999-05-03 15:29:11 +08:00
for (s = str; islower (*s) || *s == '.' || *s == '3'
|| *s == '2' || *s == '1'; ++s)
1999-05-03 15:29:11 +08:00
;
1999-05-03 15:29:11 +08:00
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. */
1999-05-03 15:29:11 +08:00
if (strncmp (str, "d.", 2) == 0)
{
1999-05-03 15:29:11 +08:00
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;
}
1999-05-03 15:29:11 +08:00
if (comma)
*--s = ',';
args_start = s;
1999-05-03 15:29:11 +08:00
for (;;)
{
opcode = insn->match;
memset (&the_insn, '\0', sizeof (the_insn));
the_insn.reloc = BFD_RELOC_NONE;
the_insn.pcrel = 0;
the_insn.fup = OP_NONE;
1999-05-03 15:29:11 +08:00
/* Build the opcode, checking as we go that the operands match. */
for (args = insn->args; ; ++args)
1999-05-03 15:29:11 +08:00
{
switch (*args)
{
/* End of args. */
case '\0':
1999-05-03 15:29:11 +08:00
if (*s == '\0')
match = 1;
1999-05-03 15:29:11 +08:00
break;
/* These must match exactly. */
1999-05-03 15:29:11 +08:00
case '+':
case '(':
1999-05-03 15:29:11 +08:00
case ')':
case ',':
case ' ':
if (*s++ == *args)
continue;
break;
/* Must be at least one digit. */
case '#':
1999-05-03 15:29:11 +08:00
if (isdigit (*s++))
{
while (isdigit (*s))
++s;
1999-05-03 15:29:11 +08:00
continue;
}
break;
/* Next operand must be a register. */
case '1':
1999-05-03 15:29:11 +08:00
case '2':
case 'd':
/* Check for register prefix if necessary. */
if (reg_prefix && *s != reg_prefix)
goto error;
else
s++;
1999-05-03 15:29:11 +08:00
switch (*s)
{
/* Frame pointer. */
case 'f':
1999-05-03 15:29:11 +08:00
s++;
if (*s++ == 'p')
{
mask = 0x3;
break;
}
goto error;
/* Stack pointer. */
case 's':
1999-05-03 15:29:11 +08:00
s++;
if (*s++ == 'p')
{
mask = 0x2;
break;
}
goto error;
/* Any register r0..r31. */
case 'r':
1999-05-03 15:29:11 +08:00
s++;
if (!isdigit (c = *s++))
{
goto error;
}
if (isdigit (*s))
{
if ((c = 10 * (c - '0') + (*s++ - '0')) >= 32)
goto error;
1999-05-03 15:29:11 +08:00
}
else
c -= '0';
1999-05-03 15:29:11 +08:00
mask = c;
break;
/* Not this opcode. */
default:
1999-05-03 15:29:11 +08:00
goto error;
}
/* Obtained the register, now place it in the opcode. */
1999-05-03 15:29:11 +08:00
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':
1999-05-03 15:29:11 +08:00
case 'f':
case 'g':
/* Check for register prefix if necessary. */
if (reg_prefix && *s != reg_prefix)
goto error;
else
s++;
1999-05-03 15:29:11 +08:00
if (*s++ == 'f' && isdigit (*s))
{
mask = *s++;
if (isdigit (*s))
{
mask = 10 * (mask - '0') + (*s++ - '0');
if (mask >= 32)
{
break;
}
}
else
mask -= '0';
1999-05-03 15:29:11 +08:00
switch (*args)
{
case 'e':
opcode |= mask << 11;
continue;
case 'f':
opcode |= mask << 21;
continue;
case 'g':
opcode |= mask << 16;
if (dual_mode != DUAL_OFF)
opcode |= (1 << 9);
1999-05-03 15:29:11 +08:00
if (dual_mode == DUAL_DDOT)
dual_mode = DUAL_OFF;
if (dual_mode == DUAL_ONDDOT)
dual_mode = DUAL_ON;
if ((opcode & (1 << 10)) && mask != 0
&& (mask == ((opcode >> 11) & 0x1f)))
as_warn (_("Pipelined instruction: fsrc1 = fdest"));
1999-05-03 15:29:11 +08:00
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
s++;
1999-05-03 15:29:11 +08:00
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;
}
break;
/* 5-bit immediate in src1. */
case '5':
if (! i860_get_expression (s))
1999-05-03 15:29:11 +08:00
{
s = expr_end;
the_insn.fup |= OP_IMM_U5;
1999-05-03 15:29:11 +08:00
continue;
}
break;
/* 26-bit immediate, relative branch (lbroff). */
case 'l':
1999-05-03 15:29:11 +08:00
the_insn.pcrel = 1;
the_insn.fup |= OP_IMM_BR26;
1999-05-03 15:29:11 +08:00
goto immediate;
/* 16-bit split immediate, relative branch (sbroff). */
case 'r':
1999-05-03 15:29:11 +08:00
the_insn.pcrel = 1;
the_insn.fup |= OP_IMM_BR16;
1999-05-03 15:29:11 +08:00
goto immediate;
/* 16-bit split immediate. */
case 's':
the_insn.fup |= OP_IMM_SPLIT16;
goto immediate;
/* 16-bit split immediate, byte aligned (st.b). */
case 'S':
the_insn.fup |= OP_IMM_SPLIT16;
goto immediate;
/* 16-bit split immediate, half-word aligned (st.s). */
case 'T':
the_insn.fup |= (OP_IMM_SPLIT16 | OP_ENCODE1 | OP_ALIGN2);
goto immediate;
/* 16-bit split immediate, word aligned (st.l). */
case 'U':
the_insn.fup |= (OP_IMM_SPLIT16 | OP_ENCODE1 | OP_ALIGN4);
goto immediate;
/* 16-bit immediate. */
case 'i':
the_insn.fup |= OP_IMM_S16;
goto immediate;
/* 16-bit immediate, byte aligned (ld.b). */
case 'I':
the_insn.fup |= OP_IMM_S16;
goto immediate;
/* 16-bit immediate, half-word aligned (ld.s). */
case 'J':
the_insn.fup |= (OP_IMM_S16 | OP_ENCODE1 | OP_ALIGN2);
1999-05-03 15:29:11 +08:00
goto immediate;
/* 16-bit immediate, word aligned (ld.l, {p}fld.l, fst.l). */
case 'K':
if (insn->name[0] == 'l')
the_insn.fup |= (OP_IMM_S16 | OP_ENCODE1 | OP_ALIGN4);
1999-05-03 15:29:11 +08:00
else
the_insn.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.fup |= (OP_IMM_S16 | OP_ENCODE3 | OP_ALIGN8);
1999-05-03 15:29:11 +08:00
goto immediate;
/* 16-bit immediate, quad-word aligned (fld.q, fst.q). */
case 'M':
the_insn.fup |= (OP_IMM_S16 | OP_ENCODE3 | OP_ALIGN16);
1999-05-03 15:29:11 +08:00
/*FALLTHROUGH*/
/* Handle the immediate for either the Intel syntax or
SVR4 syntax. The Intel syntax is "ha%immediate"
whereas SVR4 syntax is "[immediate]@ha". */
1999-05-03 15:29:11 +08:00
immediate:
#ifdef SYNTAX_SVR4
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.fup |= OP_SEL_HA;
s += 3;
}
else if (strncmp (s, "@h", 2) == 0)
{
the_insn.fup |= OP_SEL_H;
s += 2;
}
else if (strncmp (s, "@l", 2) == 0)
{
the_insn.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.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.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.fup |= OP_SEL_PLT;
s += 4;
}
the_insn.expand = insn->expand;
continue;
#else /* ! SYNTAX_SVR4 */
1999-05-03 15:29:11 +08:00
if (*s == ' ')
s++;
if (strncmp (s, "ha%", 3) == 0)
{
the_insn.fup |= OP_SEL_HA;
1999-05-03 15:29:11 +08:00
s += 3;
}
else if (strncmp (s, "h%", 2) == 0)
{
the_insn.fup |= OP_SEL_H;
1999-05-03 15:29:11 +08:00
s += 2;
}
else if (strncmp (s, "l%", 2) == 0)
{
the_insn.fup |= OP_SEL_L;
1999-05-03 15:29:11 +08:00
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;
1999-05-03 15:29:11 +08:00
continue;
#endif /* SYNTAX_SVR4 */
1999-05-03 15:29:11 +08:00
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))
1999-05-03 15:29:11 +08:00
{
++insn;
s = args_start;
1999-05-03 15:29:11 +08:00
continue;
}
else
{
as_bad (_("Illegal operands for %s"), insn->name);
1999-05-03 15:29:11 +08:00
return;
}
}
break;
}
the_insn.opcode = opcode;
}
static int
i860_get_expression (str)
1999-05-03 15:29:11 +08:00
char *str;
{
char *save_in;
segT seg;
save_in = input_line_pointer;
input_line_pointer = str;
seg = expression (&the_insn.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;
}
/* Turn a string in input_line_pointer into a floating point constant of
type TYPE, and store the appropriate bytes in *LITP. The number of
LITTLENUMS emitted is stored in *SIZEP. An error message is returned,
or NULL on OK. */
1999-05-03 15:29:11 +08:00
/* Equal to MAX_PRECISION in atof-ieee.c. */
1999-05-03 15:29:11 +08:00
#define MAX_LITTLENUMS 6
char *
md_atof (type, litP, sizeP)
char type;
char *litP;
int *sizeP;
{
int prec;
LITTLENUM_TYPE words[MAX_LITTLENUMS];
LITTLENUM_TYPE *wordP;
char *t;
char *atof_ieee ();
switch (type)
{
case 'f':
case 'F':
case 's':
case 'S':
prec = 2;
break;
case 'd':
case 'D':
case 'r':
case 'R':
prec = 4;
break;
case 'x':
case 'X':
prec = 6;
break;
case 'p':
case 'P':
prec = 6;
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);
for (wordP = words; prec--;)
{
md_number_to_chars (litP, (long) (*wordP++), sizeof (LITTLENUM_TYPE));
litP += sizeof (LITTLENUM_TYPE);
}
return 0;
}
/* Write out in current endian mode. */
1999-05-03 15:29:11 +08:00
void
md_number_to_chars (buf, val, n)
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);
1999-05-03 15:29:11 +08:00
}
/* This should never be called for i860. */
1999-05-03 15:29:11 +08:00
void
md_number_to_disp (buf, val, n)
char *buf ATTRIBUTE_UNUSED;
long val ATTRIBUTE_UNUSED;
int n ATTRIBUTE_UNUSED;
1999-05-03 15:29:11 +08:00
{
as_fatal (_("md_number_to_disp\n"));
}
/* This should never be called for i860. */
1999-05-03 15:29:11 +08:00
void
md_number_to_field (buf, val, fix)
char *buf ATTRIBUTE_UNUSED;
long val ATTRIBUTE_UNUSED;
void *fix ATTRIBUTE_UNUSED;
1999-05-03 15:29:11 +08:00
{
as_fatal (_("i860_number_to_field\n"));
}
/* This should never be called for i860. */
1999-05-03 15:29:11 +08:00
int
md_estimate_size_before_relax (fragP, segtype)
register fragS *fragP ATTRIBUTE_UNUSED;
segT segtype ATTRIBUTE_UNUSED;
1999-05-03 15:29:11 +08:00
{
as_fatal (_("i860_estimate_size_before_relax\n"));
}
#ifdef DEBUG_I860
1999-05-03 15:29:11 +08:00
static void
print_insn (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));
1999-05-03 15:29:11 +08:00
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",
1999-05-03 15:29:11 +08:00
insn->exp.X_add_number);
fprintf (stderr, "}\n");
}
#endif /* DEBUG_I860 */
1999-05-03 15:29:11 +08:00
#ifdef OBJ_ELF
CONST char *md_shortopts = "VQ:";
#else
1999-05-03 15:29:11 +08:00
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)
1999-05-03 15:29:11 +08:00
struct option md_longopts[] = {
{ "EB", no_argument, NULL, OPTION_EB },
{ "EL", no_argument, NULL, OPTION_EL },
{ "mwarn-expand", no_argument, NULL, OPTION_WARN_EXPAND },
{ NULL, no_argument, NULL, 0 }
1999-05-03 15:29:11 +08:00
};
size_t md_longopts_size = sizeof (md_longopts);
1999-05-03 15:29:11 +08:00
int
md_parse_option (c, arg)
int c;
char *arg ATTRIBUTE_UNUSED;
1999-05-03 15:29:11 +08:00
{
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;
#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;
1999-05-03 15:29:11 +08:00
}
void
md_show_usage (stream)
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"));
#ifdef OBJ_ELF
/* SVR4 compatibility flags. */
fprintf (stream, _("\
-V print assembler version number\n\
-Qy, -Qn ignored\n"));
#endif
1999-05-03 15:29:11 +08:00
}
1999-05-03 15:29:11 +08:00
/* We have no need to default values of symbols. */
symbolS *
md_undefined_symbol (name)
char *name ATTRIBUTE_UNUSED;
{
return 0;
}
/* The i860 denotes auto-increment with '++'. */
1999-05-03 15:29:11 +08:00
void
md_operand (exp)
expressionS *exp;
1999-05-03 15:29:11 +08:00
{
char *s;
1999-05-03 15:29:11 +08:00
for (s = input_line_pointer; *s; s++)
1999-05-03 15:29:11 +08:00
{
if (s[0] == '+' && s[1] == '+')
1999-05-03 15:29:11 +08:00
{
input_line_pointer += 2;
exp->X_op = O_register;
break;
1999-05-03 15:29:11 +08:00
}
}
}
/* Round up a section size to the appropriate boundary. */
valueT
md_section_align (segment, size)
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
of the offset plus its size. */
long
md_pcrel_from (fixP)
fixS *fixP;
{
return fixP->fx_size + fixP->fx_where + fixP->fx_frag->fr_address;
}
1999-05-03 15:29:11 +08:00
/* 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 (fix, val)
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)
1999-05-03 15:29:11 +08:00
{
if (fup & OP_ENCODE1)
1999-05-03 15:29:11 +08:00
{
num_encode = 1;
reloc = BFD_RELOC_860_SPLIT1;
}
else if (fup & OP_ENCODE2)
{
num_encode = 2;
reloc = BFD_RELOC_860_SPLIT2;
1999-05-03 15:29:11 +08:00
}
else
{
num_encode = 0;
reloc = BFD_RELOC_860_SPLIT0;
1999-05-03 15:29:11 +08:00
}
}
else
{
if (fup & OP_ENCODE1)
1999-05-03 15:29:11 +08:00
{
num_encode = 1;
reloc = BFD_RELOC_860_LOW1;
1999-05-03 15:29:11 +08:00
}
else if (fup & OP_ENCODE2)
1999-05-03 15:29:11 +08:00
{
num_encode = 2;
reloc = BFD_RELOC_860_LOW2;
}
else if (fup & OP_ENCODE3)
{
num_encode = 3;
reloc = BFD_RELOC_860_LOW3;
1999-05-03 15:29:11 +08:00
}
else
{
num_encode = 0;
reloc = BFD_RELOC_860_LOW0;
1999-05-03 15:29:11 +08:00
}
}
/* Preserve size encode bits. */
*val &= ~((1 << num_encode) - 1);
1999-05-03 15:29:11 +08:00
}
else
{
/* No selector. What reloc do we generate (???)? */
reloc = BFD_RELOC_32;
}
return reloc;
1999-05-03 15:29:11 +08:00
}
/* 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. */
int
md_apply_fix3 (fix, valuep, seg)
fixS *fix;
valueT *valuep;
segT seg ATTRIBUTE_UNUSED;
{
1999-05-03 15:29:11 +08:00
char *buf;
long val = (long) (*valuep);
unsigned long insn;
valueT fup;
1999-05-03 15:29:11 +08:00
buf = fix->fx_frag->fr_literal + fix->fx_where;
1999-05-03 15:29:11 +08:00
/* Recall that earlier we stored the opcode little-endian. */
insn = bfd_getl32 (buf);
1999-05-03 15:29:11 +08:00
/* 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)
1999-05-03 15:29:11 +08:00
{
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;
1999-05-03 15:29:11 +08:00
}
else if (fup & OP_IMM_S16)
1999-05-03 15:29:11 +08:00
{
fix->fx_r_type = obtain_reloc_for_imm16 (fix, &val);
1999-05-03 15:29:11 +08:00
/* 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)
1999-05-03 15:29:11 +08:00
{
abort ();
1999-05-03 15:29:11 +08:00
}
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)
1999-05-03 15:29:11 +08:00
{
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;
}
1999-05-03 15:29:11 +08:00
}
else if (fup & OP_IMM_BR26)
1999-05-03 15:29:11 +08:00
{
if (val & 0x3)
as_bad_where (fix->fx_file, fix->fx_line,
_("A branch offset requires 0 MOD 4 alignment"));
1999-05-03 15:29:11 +08:00
val >>= 2;
1999-05-03 15:29:11 +08:00
/* 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%08x)"), 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;
}
}
1999-05-03 15:29:11 +08:00
/* Return value ignored. */
1999-05-03 15:29:11 +08:00
return 0;
}
/* Generate a machine dependent reloc from a fixup. */
arelent*
tc_gen_reloc (section, fixp)
asection *section ATTRIBUTE_UNUSED;
fixS *fixp;
1999-05-03 15:29:11 +08:00
{
arelent *reloc;
1999-05-03 15:29:11 +08:00
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);
1999-05-03 15:29:11 +08:00
if (! reloc->howto)
1999-05-03 15:29:11 +08:00
{
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));
1999-05-03 15:29:11 +08:00
}
return reloc;
1999-05-03 15:29:11 +08:00
}