1237 lines
28 KiB
C
1237 lines
28 KiB
C
/* tc-dlx.c -- Assemble for the DLX
|
|
Copyright (C) 2002-2018 Free Software Foundation, Inc.
|
|
|
|
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. */
|
|
|
|
/* Initially created by Kuang Hwa Lin, 3/20/2002. */
|
|
|
|
#include "as.h"
|
|
#include "safe-ctype.h"
|
|
#include "tc-dlx.h"
|
|
#include "opcode/dlx.h"
|
|
#include "elf/dlx.h"
|
|
#include "bfd/elf32-dlx.h"
|
|
|
|
/* Make it easier to clone this machine desc into another one. */
|
|
#define machine_opcode dlx_opcode
|
|
#define machine_opcodes dlx_opcodes
|
|
#define machine_ip dlx_ip
|
|
#define machine_it dlx_it
|
|
|
|
#define NO_RELOC BFD_RELOC_NONE
|
|
#define RELOC_DLX_REL26 BFD_RELOC_DLX_JMP26
|
|
#define RELOC_DLX_16 BFD_RELOC_16
|
|
#define RELOC_DLX_REL16 BFD_RELOC_16_PCREL_S2
|
|
#define RELOC_DLX_HI16 BFD_RELOC_HI16_S
|
|
#define RELOC_DLX_LO16 BFD_RELOC_LO16
|
|
#define RELOC_DLX_VTINHERIT BFD_RELOC_VTABLE_INHERIT
|
|
#define RELOC_DLX_VTENTRY BFD_RELOC_VTABLE_ENTRY
|
|
|
|
/* handle of the OPCODE hash table */
|
|
static struct hash_control *op_hash = NULL;
|
|
|
|
struct machine_it
|
|
{
|
|
char *error;
|
|
unsigned long opcode;
|
|
struct nlist *nlistp;
|
|
expressionS exp;
|
|
int pcrel;
|
|
int size;
|
|
int reloc_offset; /* Offset of reloc within insn. */
|
|
bfd_reloc_code_real_type reloc;
|
|
int HI;
|
|
int LO;
|
|
}
|
|
the_insn;
|
|
|
|
/* This array holds the chars that always start a comment. If the
|
|
pre-processor is disabled, these aren't very useful. */
|
|
const char comment_chars[] = ";";
|
|
|
|
/* This array holds the chars that only start a comment at the beginning of
|
|
a line. If the line seems to have the form '# 123 filename'
|
|
.line and .file directives will appear in the pre-processed output. */
|
|
/* Note that input_file.c hand checks for '#' at the beginning of the
|
|
first line of the input file. This is because the compiler outputs
|
|
#NO_APP at the beginning of its output. */
|
|
/* Also note that comments like this one will always work. */
|
|
const char line_comment_chars[] = "#";
|
|
|
|
/* We needed an unused char for line separation to work around the
|
|
lack of macros, using sed and such. */
|
|
const char line_separator_chars[] = "@";
|
|
|
|
/* Chars that can be used to separate mant from exp in floating point nums. */
|
|
const char EXP_CHARS[] = "eE";
|
|
|
|
/* Chars that mean this number is a floating point constant.
|
|
As in 0f12.456
|
|
or 0d1.2345e12. */
|
|
const char FLT_CHARS[] = "rRsSfFdDxXpP";
|
|
|
|
static void
|
|
insert_sreg (const char *regname, int regnum)
|
|
{
|
|
/* Must be large enough to hold the names of the special registers. */
|
|
char buf[80];
|
|
int i;
|
|
|
|
symbol_table_insert (symbol_new (regname, reg_section, (valueT) regnum,
|
|
&zero_address_frag));
|
|
for (i = 0; regname[i]; i++)
|
|
buf[i] = ISLOWER (regname[i]) ? TOUPPER (regname[i]) : regname[i];
|
|
buf[i] = '\0';
|
|
|
|
symbol_table_insert (symbol_new (buf, reg_section, (valueT) regnum,
|
|
&zero_address_frag));
|
|
}
|
|
|
|
/* Install symbol definitions for assorted special registers.
|
|
See MIPS Assembly Language Programmer's Guide page 1-4 */
|
|
|
|
static void
|
|
define_some_regs (void)
|
|
{
|
|
/* Software representation. */
|
|
insert_sreg ("zero", 0);
|
|
insert_sreg ("at", 1);
|
|
insert_sreg ("v0", 2);
|
|
insert_sreg ("v1", 3);
|
|
insert_sreg ("a0", 4);
|
|
insert_sreg ("a1", 5);
|
|
insert_sreg ("a2", 6);
|
|
insert_sreg ("a3", 7);
|
|
insert_sreg ("t0", 8);
|
|
insert_sreg ("t1", 9);
|
|
insert_sreg ("t2", 10);
|
|
insert_sreg ("t3", 11);
|
|
insert_sreg ("t4", 12);
|
|
insert_sreg ("t5", 13);
|
|
insert_sreg ("t6", 14);
|
|
insert_sreg ("t7", 15);
|
|
insert_sreg ("s0", 16);
|
|
insert_sreg ("s1", 17);
|
|
insert_sreg ("s2", 18);
|
|
insert_sreg ("s3", 19);
|
|
insert_sreg ("s4", 20);
|
|
insert_sreg ("s5", 21);
|
|
insert_sreg ("s6", 22);
|
|
insert_sreg ("s7", 23);
|
|
insert_sreg ("t8", 24);
|
|
insert_sreg ("t9", 25);
|
|
insert_sreg ("k0", 26);
|
|
insert_sreg ("k1", 27);
|
|
insert_sreg ("gp", 28);
|
|
insert_sreg ("sp", 29);
|
|
insert_sreg ("fp", 30);
|
|
insert_sreg ("ra", 31);
|
|
/* Special registers. */
|
|
insert_sreg ("pc", 0);
|
|
insert_sreg ("npc", 1);
|
|
insert_sreg ("iad", 2);
|
|
}
|
|
|
|
/* Subroutine check the string to match an register. */
|
|
|
|
static int
|
|
match_sft_register (char *name)
|
|
{
|
|
#define MAX_REG_NO 35
|
|
/* Currently we have 35 software registers defined -
|
|
we borrowed from MIPS. */
|
|
static const char *soft_reg[] =
|
|
{
|
|
"zero", "at", "v0", "v1", "a0", "a1", "a2", "a3",
|
|
"t0", "t1", "t2", "t3", "t4", "t5", "t6", "t7", "t8", "t9",
|
|
"s0", "s1", "s2", "s3", "s4", "s5", "s7", "k0", "k1",
|
|
"gp", "sp", "fp", "ra", "pc", "npc", "iad",
|
|
"EndofTab" /* End of the Table indicator */
|
|
};
|
|
char low_name[21], *ptr;
|
|
int idx;
|
|
|
|
for (ptr = name,idx = 0; *ptr != '\0'; ptr++)
|
|
low_name[idx++] = TOLOWER (*ptr);
|
|
|
|
low_name[idx] = '\0';
|
|
idx = 0;
|
|
|
|
while (idx < MAX_REG_NO && strcmp (soft_reg[idx], & low_name [0]))
|
|
idx += 1;
|
|
|
|
return idx < MAX_REG_NO;
|
|
}
|
|
|
|
/* Subroutine check the string to match an register. */
|
|
|
|
static int
|
|
is_ldst_registers (char *name)
|
|
{
|
|
char *ptr = name;
|
|
|
|
/* The first character of the register name got to be either %, $, r of R. */
|
|
if ((ptr[0] == '%' || ptr[0] == '$' || ptr[0] == 'r' || ptr[0] == 'R')
|
|
&& ISDIGIT ((unsigned char) ptr[1]))
|
|
return 1;
|
|
|
|
/* Now check the software register representation. */
|
|
return match_sft_register (ptr);
|
|
}
|
|
|
|
/* Subroutine of s_proc so targets can choose a different default prefix.
|
|
If DEFAULT_PREFIX is NULL, use the target's "leading char". */
|
|
|
|
static void
|
|
s_proc (int end_p)
|
|
{
|
|
/* Record the current function so that we can issue an error message for
|
|
misplaced .func,.endfunc, and also so that .endfunc needs no
|
|
arguments. */
|
|
static char *current_name;
|
|
static char *current_label;
|
|
|
|
if (end_p)
|
|
{
|
|
if (current_name == NULL)
|
|
{
|
|
as_bad (_("missing .proc"));
|
|
ignore_rest_of_line ();
|
|
return;
|
|
}
|
|
|
|
current_name = current_label = NULL;
|
|
SKIP_WHITESPACE ();
|
|
while (!is_end_of_line[(unsigned char) *input_line_pointer])
|
|
input_line_pointer++;
|
|
}
|
|
else
|
|
{
|
|
char *name, *label;
|
|
char delim1, delim2;
|
|
|
|
if (current_name != NULL)
|
|
{
|
|
as_bad (_(".endfunc missing for previous .proc"));
|
|
ignore_rest_of_line ();
|
|
return;
|
|
}
|
|
|
|
delim1 = get_symbol_name (&name);
|
|
name = xstrdup (name);
|
|
*input_line_pointer = delim1;
|
|
SKIP_WHITESPACE_AFTER_NAME ();
|
|
|
|
if (*input_line_pointer != ',')
|
|
{
|
|
char leading_char = 0;
|
|
|
|
leading_char = bfd_get_symbol_leading_char (stdoutput);
|
|
/* Missing entry point, use function's name with the leading
|
|
char prepended. */
|
|
if (leading_char)
|
|
{
|
|
unsigned len = strlen (name) + 1;
|
|
label = XNEWVEC (char, len + 1);
|
|
label[0] = leading_char;
|
|
memcpy (label + 1, name, len);
|
|
}
|
|
else
|
|
label = name;
|
|
}
|
|
else
|
|
{
|
|
++input_line_pointer;
|
|
SKIP_WHITESPACE ();
|
|
delim2 = get_symbol_name (&label);
|
|
label = xstrdup (label);
|
|
(void) restore_line_pointer (delim2);
|
|
}
|
|
|
|
current_name = name;
|
|
current_label = label;
|
|
}
|
|
demand_empty_rest_of_line ();
|
|
}
|
|
|
|
/* This function is called once, at assembler startup time. It should
|
|
set up all the tables, etc., that the MD part of the assembler will
|
|
need. */
|
|
|
|
void
|
|
md_begin (void)
|
|
{
|
|
const char *retval = NULL;
|
|
int lose = 0;
|
|
unsigned int i;
|
|
|
|
/* Create a new hash table. */
|
|
op_hash = hash_new ();
|
|
|
|
/* Hash up all the opcodes for fast use later. */
|
|
for (i = 0; i < num_dlx_opcodes; i++)
|
|
{
|
|
const char *name = machine_opcodes[i].name;
|
|
|
|
retval = hash_insert (op_hash, name, (void *) &machine_opcodes[i]);
|
|
|
|
if (retval != NULL)
|
|
{
|
|
fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
|
|
machine_opcodes[i].name, retval);
|
|
lose = 1;
|
|
}
|
|
}
|
|
|
|
if (lose)
|
|
as_fatal (_("Broken assembler. No assembly attempted."));
|
|
|
|
define_some_regs ();
|
|
}
|
|
|
|
/* This function will check the opcode and return 1 if the opcode is one
|
|
of the load/store instruction, and it will fix the operand string to
|
|
the standard form so we can use the standard parse_operand routine. */
|
|
|
|
#define READ_OP 0x100
|
|
#define WRITE_OP 0x200
|
|
static char iBuf[81];
|
|
|
|
static char *
|
|
dlx_parse_loadop (char * str)
|
|
{
|
|
char *ptr = str;
|
|
int idx = 0;
|
|
|
|
/* The last pair of ()/[] is the register, all other are the
|
|
reloc displacement, and if there is a register then it ought
|
|
to have a pair of ()/[]
|
|
This is not necessarily true, what if the load instruction come
|
|
without the register and with %hi/%lo modifier? */
|
|
for (idx = 0; idx < 72 && ptr[idx] != '\0'; idx++)
|
|
;
|
|
|
|
if (idx == 72)
|
|
{
|
|
badoperand_load:
|
|
as_bad (_("Bad operand for a load instruction: <%s>"), str);
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
int i, pb = 0;
|
|
int m2 = 0;
|
|
char rs1[7], rd[7], endm, match = '0';
|
|
char imm[72];
|
|
|
|
idx -= 1;
|
|
switch (str[idx])
|
|
{
|
|
case ')':
|
|
match = '(';
|
|
endm = ')';
|
|
break;
|
|
case ']':
|
|
match = '[';
|
|
endm = ']';
|
|
break;
|
|
default:
|
|
/* No register indicated, fill in zero. */
|
|
rs1[0] = 'r';
|
|
rs1[1] = '0';
|
|
rs1[2] = '\0';
|
|
match = 0;
|
|
endm = 0;
|
|
m2 = 1;
|
|
}
|
|
|
|
if (!m2)
|
|
{
|
|
/* Searching for (/[ which will match the ]/). */
|
|
for (pb = idx - 1; str[pb] != match; pb -= 1)
|
|
/* Match can only be either '[' or '(', if it is
|
|
'(' then this can be a normal expression, we'll treat
|
|
it as an operand. */
|
|
if (str[pb] == endm || pb < (idx - 5))
|
|
goto load_no_rs1;
|
|
pb += 1;
|
|
|
|
for (i = 0; (pb + i) < idx; i++)
|
|
rs1[i] = str[pb+i];
|
|
|
|
rs1[i] = '\0';
|
|
|
|
if (is_ldst_registers (& rs1[0]))
|
|
/* Point to the last character of the imm. */
|
|
pb -= 1;
|
|
else
|
|
{
|
|
load_no_rs1:
|
|
if (match == '[')
|
|
goto badoperand_load;
|
|
/* No register indicated, fill in zero and restore the imm. */
|
|
rs1[0] = 'r';
|
|
rs1[1] = '0';
|
|
rs1[2] = '\0';
|
|
m2 = 1;
|
|
}
|
|
}
|
|
|
|
/* Duplicate the first register. */
|
|
for (i = 0; i < 7 && str[i] != ','; i++)
|
|
rd[i] = ptr[i];
|
|
|
|
if (str[i] != ',')
|
|
goto badoperand_load;
|
|
else
|
|
rd[i] = '\0';
|
|
|
|
/* Copy the immd. */
|
|
if (m2)
|
|
/* Put the '\0' back in. */
|
|
pb = idx + 1;
|
|
|
|
for (i++, m2 = 0; i < pb; m2++,i++)
|
|
imm[m2] = ptr[i];
|
|
|
|
imm[m2] = '\0';
|
|
|
|
/* Assemble the instruction to gas internal format. */
|
|
for (i = 0; rd[i] != '\0'; i++)
|
|
iBuf[i] = rd[i];
|
|
|
|
iBuf[i++] = ',';
|
|
|
|
for (pb = 0 ; rs1[pb] != '\0'; i++, pb++)
|
|
iBuf[i] = rs1[pb];
|
|
|
|
iBuf[i++] = ',';
|
|
|
|
for (pb = 0; imm[pb] != '\0'; i++, pb++)
|
|
iBuf[i] = imm[pb];
|
|
|
|
iBuf[i] = '\0';
|
|
return iBuf;
|
|
}
|
|
}
|
|
|
|
static char *
|
|
dlx_parse_storeop (char * str)
|
|
{
|
|
char *ptr = str;
|
|
int idx = 0;
|
|
|
|
/* Search for the ','. */
|
|
for (idx = 0; idx < 72 && ptr[idx] != ','; idx++)
|
|
;
|
|
|
|
if (idx == 72)
|
|
{
|
|
badoperand_store:
|
|
as_bad (_("Bad operand for a store instruction: <%s>"), str);
|
|
return NULL;
|
|
}
|
|
else
|
|
{
|
|
/* idx now points to the ','. */
|
|
int i, pb = 0;
|
|
int comma = idx;
|
|
int m2 = 0;
|
|
char rs1[7], rd[7], endm, match = '0';
|
|
char imm[72];
|
|
|
|
/* Now parse the '(' and ')', and make idx point to ')'. */
|
|
idx -= 1;
|
|
switch (str[idx])
|
|
{
|
|
case ')':
|
|
match = '(';
|
|
endm = ')';
|
|
break;
|
|
case ']':
|
|
match = '[';
|
|
endm = ']';
|
|
break;
|
|
default:
|
|
/* No register indicated, fill in zero. */
|
|
rs1[0] = 'r';
|
|
rs1[1] = '0';
|
|
rs1[2] = '\0';
|
|
match = 0;
|
|
endm = 0;
|
|
m2 = 1;
|
|
}
|
|
|
|
if (!m2)
|
|
{
|
|
/* Searching for (/[ which will match the ]/). */
|
|
for (pb = idx - 1; str[pb] != match; pb -= 1)
|
|
if (pb < (idx - 5) || str[pb] == endm)
|
|
goto store_no_rs1;
|
|
pb += 1;
|
|
|
|
for (i = 0; (pb + i) < idx; i++)
|
|
rs1[i] = str[pb + i];
|
|
|
|
rs1[i] = '\0';
|
|
|
|
if (is_ldst_registers (& rs1[0]))
|
|
/* Point to the last character of the imm. */
|
|
pb -= 1;
|
|
else
|
|
{
|
|
store_no_rs1:
|
|
if (match == '[')
|
|
goto badoperand_store;
|
|
|
|
/* No register indicated, fill in zero and restore the imm. */
|
|
rs1[0] = 'r';
|
|
rs1[1] = '0';
|
|
rs1[2] = '\0';
|
|
pb = comma;
|
|
}
|
|
}
|
|
else
|
|
/* No register was specified. */
|
|
pb = comma;
|
|
|
|
/* Duplicate the first register. */
|
|
for (i = comma + 1; (str[i] == ' ' || str[i] == '\t'); i++)
|
|
;
|
|
|
|
for (m2 = 0; (m2 < 7 && str[i] != '\0'); i++, m2++)
|
|
{
|
|
if (str[i] != ' ' && str[i] != '\t')
|
|
rd[m2] = str[i];
|
|
else
|
|
goto badoperand_store;
|
|
}
|
|
|
|
if (str[i] != '\0')
|
|
goto badoperand_store;
|
|
else
|
|
rd[m2] = '\0';
|
|
|
|
/* Copy the immd. */
|
|
for (i = 0; i < pb; i++)
|
|
imm[i] = ptr[i];
|
|
|
|
imm[i] = '\0';
|
|
|
|
/* Assemble the instruction to gas internal format. */
|
|
for (i = 0; rd[i] != '\0'; i++)
|
|
iBuf[i] = rd[i];
|
|
iBuf[i++] = ',';
|
|
for (pb = 0 ; rs1[pb] != '\0'; i++, pb++)
|
|
iBuf[i] = rs1[pb];
|
|
iBuf[i++] = ',';
|
|
for (pb = 0; imm[pb] != '\0'; i++, pb++)
|
|
iBuf[i] = imm[pb];
|
|
iBuf[i] = '\0';
|
|
return iBuf;
|
|
}
|
|
}
|
|
|
|
static char *
|
|
fix_ld_st_operand (unsigned long opcode, char* str)
|
|
{
|
|
/* Check the opcode. */
|
|
switch ((int) opcode)
|
|
{
|
|
case LBOP:
|
|
case LBUOP:
|
|
case LSBUOP:
|
|
case LHOP:
|
|
case LHUOP:
|
|
case LSHUOP:
|
|
case LWOP:
|
|
case LSWOP:
|
|
return dlx_parse_loadop (str);
|
|
case SBOP:
|
|
case SHOP:
|
|
case SWOP:
|
|
return dlx_parse_storeop (str);
|
|
default:
|
|
return str;
|
|
}
|
|
}
|
|
|
|
static int
|
|
hilo_modifier_ok (char *s)
|
|
{
|
|
char *ptr = s;
|
|
int idx, count = 1;
|
|
|
|
if (*ptr != '(')
|
|
return 1;
|
|
|
|
for (idx = 1; ptr[idx] != '\0' && ptr[idx] != '[' && idx < 73; idx += 1)
|
|
{
|
|
if (count == 0)
|
|
return count;
|
|
|
|
if (ptr[idx] == '(')
|
|
count += 1;
|
|
|
|
if (ptr[idx] == ')')
|
|
count -= 1;
|
|
}
|
|
|
|
return (count == 0) ? 1:0;
|
|
}
|
|
|
|
static char *
|
|
parse_operand (char *s, expressionS *operandp)
|
|
{
|
|
char *save = input_line_pointer;
|
|
char *new_pos;
|
|
|
|
the_insn.HI = the_insn.LO = 0;
|
|
|
|
/* Search for %hi and %lo, make a mark and skip it. */
|
|
if (strncmp (s, "%hi", 3) == 0)
|
|
{
|
|
s += 3;
|
|
the_insn.HI = 1;
|
|
}
|
|
else
|
|
{
|
|
if (strncmp (s, "%lo", 3) == 0)
|
|
{
|
|
s += 3;
|
|
the_insn.LO = 1;
|
|
}
|
|
else
|
|
the_insn.LO = 0;
|
|
}
|
|
|
|
if (the_insn.HI || the_insn.LO)
|
|
{
|
|
if (!hilo_modifier_ok (s))
|
|
as_bad (_("Expression Error for operand modifier %%hi/%%lo\n"));
|
|
}
|
|
|
|
/* Check for the % and $ register representation */
|
|
if ((s[0] == '%' || s[0] == '$' || s[0] == 'r' || s[0] == 'R')
|
|
&& ISDIGIT ((unsigned char) s[1]))
|
|
{
|
|
/* We have a numeric register expression. No biggy. */
|
|
s += 1;
|
|
input_line_pointer = s;
|
|
(void) expression (operandp);
|
|
if (operandp->X_op != O_constant
|
|
|| operandp->X_add_number > 31)
|
|
as_bad (_("Invalid expression after %%%%\n"));
|
|
operandp->X_op = O_register;
|
|
}
|
|
else
|
|
{
|
|
/* Normal operand parsing. */
|
|
input_line_pointer = s;
|
|
(void) expression (operandp);
|
|
}
|
|
|
|
new_pos = input_line_pointer;
|
|
input_line_pointer = save;
|
|
return new_pos;
|
|
}
|
|
|
|
/* Instruction parsing. Takes a string containing the opcode.
|
|
Operands are at input_line_pointer. Output is in the_insn.
|
|
Warnings or errors are generated. */
|
|
|
|
static void
|
|
machine_ip (char *str)
|
|
{
|
|
char *s;
|
|
const char *args;
|
|
struct machine_opcode *insn;
|
|
unsigned long opcode;
|
|
expressionS the_operand;
|
|
expressionS *operand = &the_operand;
|
|
unsigned int reg, reg_shift = 0;
|
|
|
|
memset (&the_insn, '\0', sizeof (the_insn));
|
|
the_insn.reloc = NO_RELOC;
|
|
|
|
/* Fixup the opcode string to all lower cases, and also
|
|
allow numerical digits. */
|
|
s = str;
|
|
|
|
if (ISALPHA (*s))
|
|
for (; ISALNUM (*s); ++s)
|
|
if (ISUPPER (*s))
|
|
*s = TOLOWER (*s);
|
|
|
|
switch (*s)
|
|
{
|
|
case '\0':
|
|
break;
|
|
|
|
/* FIXME-SOMEDAY more whitespace. */
|
|
case ' ':
|
|
*s++ = '\0';
|
|
break;
|
|
|
|
default:
|
|
as_bad (_("Unknown opcode: `%s'"), str);
|
|
return;
|
|
}
|
|
|
|
/* Hash the opcode, insn will have the string from opcode table. */
|
|
if ((insn = (struct machine_opcode *) hash_find (op_hash, str)) == NULL)
|
|
{
|
|
/* Handle the ret and return macro here. */
|
|
if ((strcmp (str, "ret") == 0) || (strcmp (str, "return") == 0))
|
|
the_insn.opcode = JROP | 0x03e00000; /* 0x03e00000 = r31 << 21 */
|
|
else
|
|
as_bad (_("Unknown opcode `%s'."), str);
|
|
|
|
return;
|
|
}
|
|
|
|
opcode = insn->opcode;
|
|
|
|
/* Set the sip reloc HI16 flag. */
|
|
if (!set_dlx_skip_hi16_flag (1))
|
|
as_bad (_("Can not set dlx_skip_hi16_flag"));
|
|
|
|
/* Fix the operand string if it is one of load store instructions. */
|
|
s = fix_ld_st_operand (opcode, s);
|
|
|
|
/* Build the opcode, checking as we go to make sure that the
|
|
operands match.
|
|
If an operand matches, we modify the_insn or opcode appropriately,
|
|
and do a "continue". If an operand fails to match, we "break". */
|
|
if (insn->args[0] != '\0' && insn->args[0] != 'N')
|
|
{
|
|
/* Prime the pump. */
|
|
if (*s == '\0')
|
|
{
|
|
as_bad (_("Missing arguments for opcode <%s>."), str);
|
|
return;
|
|
}
|
|
else
|
|
s = parse_operand (s, operand);
|
|
}
|
|
else if (insn->args[0] == 'N')
|
|
{
|
|
/* Clean up the insn and done! */
|
|
the_insn.opcode = opcode;
|
|
return;
|
|
}
|
|
|
|
/* Parse through the args (this is from opcode table), *s point to
|
|
the current character of the instruction stream. */
|
|
for (args = insn->args;; ++args)
|
|
{
|
|
switch (*args)
|
|
{
|
|
/* End of Line. */
|
|
case '\0':
|
|
/* End of args. */
|
|
if (*s == '\0')
|
|
{
|
|
/* We are truly done. */
|
|
the_insn.opcode = opcode;
|
|
/* Clean up the HI and LO mark. */
|
|
the_insn.HI = 0;
|
|
the_insn.LO = 0;
|
|
return;
|
|
}
|
|
|
|
the_insn.HI = 0;
|
|
the_insn.LO = 0;
|
|
as_bad (_("Too many operands: %s"), s);
|
|
break;
|
|
|
|
/* ',' Args separator */
|
|
case ',':
|
|
/* Must match a comma. */
|
|
if (*s++ == ',')
|
|
{
|
|
/* Parse next operand. */
|
|
s = parse_operand (s, operand);
|
|
continue;
|
|
}
|
|
break;
|
|
|
|
/* It can be a 'a' register or 'i' operand. */
|
|
case 'P':
|
|
/* Macro move operand/reg. */
|
|
if (operand->X_op == O_register)
|
|
{
|
|
/* It's a register. */
|
|
reg_shift = 21;
|
|
goto general_reg;
|
|
}
|
|
/* Fall through. */
|
|
|
|
/* The immediate 16 bits literal, bit 0-15. */
|
|
case 'i':
|
|
/* offset, unsigned. */
|
|
case 'I':
|
|
/* offset, signed. */
|
|
if (operand->X_op == O_constant)
|
|
{
|
|
if (the_insn.HI)
|
|
operand->X_add_number >>= 16;
|
|
|
|
opcode |= operand->X_add_number & 0xFFFF;
|
|
|
|
if (the_insn.HI && the_insn.LO)
|
|
as_bad (_("Both the_insn.HI and the_insn.LO are set : %s"), s);
|
|
else
|
|
{
|
|
the_insn.HI = 0;
|
|
the_insn.LO = 0;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
the_insn.reloc = (the_insn.HI) ? RELOC_DLX_HI16
|
|
: (the_insn.LO ? RELOC_DLX_LO16 : RELOC_DLX_16);
|
|
the_insn.reloc_offset = 2;
|
|
the_insn.size = 2;
|
|
the_insn.pcrel = 0;
|
|
the_insn.exp = * operand;
|
|
the_insn.HI = 0;
|
|
the_insn.LO = 0;
|
|
continue;
|
|
|
|
case 'd':
|
|
/* offset, signed. */
|
|
if (operand->X_op == O_constant)
|
|
{
|
|
opcode |= operand->X_add_number & 0xFFFF;
|
|
continue;
|
|
}
|
|
the_insn.reloc = RELOC_DLX_REL16;
|
|
the_insn.reloc_offset = 0; /* BIG-ENDIAN Byte 3 of insn. */
|
|
the_insn.size = 4;
|
|
the_insn.pcrel = 1;
|
|
the_insn.exp = *operand;
|
|
continue;
|
|
|
|
/* The immediate 26 bits literal, bit 0-25. */
|
|
case 'D':
|
|
/* offset, signed. */
|
|
if (operand->X_op == O_constant)
|
|
{
|
|
opcode |= operand->X_add_number & 0x3FFFFFF;
|
|
continue;
|
|
}
|
|
the_insn.reloc = RELOC_DLX_REL26;
|
|
the_insn.reloc_offset = 0; /* BIG-ENDIAN Byte 3 of insn. */
|
|
the_insn.size = 4;
|
|
the_insn.pcrel = 1;
|
|
the_insn.exp = *operand;
|
|
continue;
|
|
|
|
/* Type 'a' Register. */
|
|
case 'a':
|
|
/* A general register at bits 21-25, rs1. */
|
|
reg_shift = 21;
|
|
goto general_reg;
|
|
|
|
/* Type 'b' Register. */
|
|
case 'b':
|
|
/* A general register at bits 16-20, rs2/rd. */
|
|
reg_shift = 16;
|
|
goto general_reg;
|
|
|
|
/* Type 'c' Register. */
|
|
case 'c':
|
|
/* A general register at bits 11-15, rd. */
|
|
reg_shift = 11;
|
|
|
|
general_reg:
|
|
know (operand->X_add_symbol == 0);
|
|
know (operand->X_op_symbol == 0);
|
|
reg = operand->X_add_number;
|
|
if (reg & 0xffffffe0)
|
|
as_fatal (_("failed regnum sanity check."));
|
|
else
|
|
/* Got the register, now figure out where it goes in the opcode. */
|
|
opcode |= reg << reg_shift;
|
|
|
|
switch (*args)
|
|
{
|
|
case 'a':
|
|
case 'b':
|
|
case 'c':
|
|
case 'P':
|
|
continue;
|
|
}
|
|
as_fatal (_("failed general register sanity check."));
|
|
break;
|
|
|
|
default:
|
|
BAD_CASE (*args);
|
|
}
|
|
|
|
/* Types or values of args don't match. */
|
|
as_bad (_("Invalid operands"));
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* Assemble a single instruction. Its label has already been handled
|
|
by the generic front end. We just parse opcode and operands, and
|
|
produce the bytes of data and relocation. */
|
|
|
|
void
|
|
md_assemble (char *str)
|
|
{
|
|
char *toP;
|
|
fixS *fixP;
|
|
bit_fixS *bitP;
|
|
|
|
know (str);
|
|
machine_ip (str);
|
|
toP = frag_more (4);
|
|
dwarf2_emit_insn (4);
|
|
|
|
/* Put out the opcode. */
|
|
md_number_to_chars (toP, the_insn.opcode, 4);
|
|
|
|
/* Put out the symbol-dependent stuff. */
|
|
if (the_insn.reloc != NO_RELOC)
|
|
{
|
|
fixP = fix_new_exp (frag_now,
|
|
(toP - frag_now->fr_literal + the_insn.reloc_offset),
|
|
the_insn.size, & the_insn.exp, the_insn.pcrel,
|
|
the_insn.reloc);
|
|
|
|
/* Turn off complaints that the addend is
|
|
too large for things like foo+100000@ha. */
|
|
switch (the_insn.reloc)
|
|
{
|
|
case RELOC_DLX_HI16:
|
|
case RELOC_DLX_LO16:
|
|
fixP->fx_no_overflow = 1;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case RELOC_DLX_REL26:
|
|
bitP = XNEW (bit_fixS);
|
|
bitP->fx_bit_size = 26;
|
|
bitP->fx_bit_offset = 25;
|
|
bitP->fx_bit_base = the_insn.opcode & 0xFC000000;
|
|
bitP->fx_bit_base_adj = 0;
|
|
bitP->fx_bit_max = 0;
|
|
bitP->fx_bit_min = 0;
|
|
bitP->fx_bit_add = 0x03FFFFFF;
|
|
fixP->fx_bit_fixP = bitP;
|
|
break;
|
|
case RELOC_DLX_LO16:
|
|
case RELOC_DLX_REL16:
|
|
bitP = XNEW (bit_fixS);
|
|
bitP->fx_bit_size = 16;
|
|
bitP->fx_bit_offset = 15;
|
|
bitP->fx_bit_base = the_insn.opcode & 0xFFFF0000;
|
|
bitP->fx_bit_base_adj = 0;
|
|
bitP->fx_bit_max = 0;
|
|
bitP->fx_bit_min = 0;
|
|
bitP->fx_bit_add = 0x0000FFFF;
|
|
fixP->fx_bit_fixP = bitP;
|
|
break;
|
|
case RELOC_DLX_HI16:
|
|
bitP = XNEW (bit_fixS);
|
|
bitP->fx_bit_size = 16;
|
|
bitP->fx_bit_offset = 15;
|
|
bitP->fx_bit_base = the_insn.opcode & 0xFFFF0000;
|
|
bitP->fx_bit_base_adj = 0;
|
|
bitP->fx_bit_max = 0;
|
|
bitP->fx_bit_min = 0;
|
|
bitP->fx_bit_add = 0x0000FFFF;
|
|
fixP->fx_bit_fixP = bitP;
|
|
break;
|
|
default:
|
|
fixP->fx_bit_fixP = NULL;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* This is identical to the md_atof in m68k.c. I think this is right,
|
|
but I'm not sure. Dlx will not use it anyway, so I just leave it
|
|
here for now. */
|
|
|
|
const char *
|
|
md_atof (int type, char *litP, int *sizeP)
|
|
{
|
|
return ieee_md_atof (type, litP, sizeP, TRUE);
|
|
}
|
|
|
|
/* Write out big-endian. */
|
|
void
|
|
md_number_to_chars (char *buf, valueT val, int n)
|
|
{
|
|
number_to_chars_bigendian (buf, val, n);
|
|
}
|
|
|
|
bfd_boolean
|
|
md_dlx_fix_adjustable (fixS *fixP)
|
|
{
|
|
/* We need the symbol name for the VTABLE entries. */
|
|
return (fixP->fx_r_type != BFD_RELOC_VTABLE_INHERIT
|
|
&& fixP->fx_r_type != BFD_RELOC_VTABLE_ENTRY);
|
|
}
|
|
|
|
void
|
|
md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
|
|
{
|
|
long val = *valP;
|
|
char *place = fixP->fx_where + fixP->fx_frag->fr_literal;
|
|
|
|
switch (fixP->fx_r_type)
|
|
{
|
|
case RELOC_DLX_LO16:
|
|
case RELOC_DLX_REL16:
|
|
if (fixP->fx_bit_fixP != NULL)
|
|
{
|
|
val = (val & 0x0000FFFF) | fixP->fx_bit_fixP->fx_bit_base;
|
|
free (fixP->fx_bit_fixP);
|
|
fixP->fx_bit_fixP = NULL;
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
know ((fixP->fx_bit_fixP != NULL));
|
|
#endif
|
|
break;
|
|
|
|
case RELOC_DLX_HI16:
|
|
if (fixP->fx_bit_fixP != NULL)
|
|
{
|
|
val = (val >> 16) | fixP->fx_bit_fixP->fx_bit_base;
|
|
free (fixP->fx_bit_fixP);
|
|
fixP->fx_bit_fixP = NULL;
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
know ((fixP->fx_bit_fixP != NULL));
|
|
#endif
|
|
break;
|
|
|
|
case RELOC_DLX_REL26:
|
|
if (fixP->fx_bit_fixP != NULL)
|
|
{
|
|
val = (val & 0x03FFFFFF) | fixP->fx_bit_fixP->fx_bit_base;
|
|
free (fixP->fx_bit_fixP);
|
|
fixP->fx_bit_fixP = NULL;
|
|
}
|
|
#ifdef DEBUG
|
|
else
|
|
know ((fixP->fx_bit_fixP != NULL));
|
|
#endif
|
|
break;
|
|
|
|
case BFD_RELOC_VTABLE_INHERIT:
|
|
/* This borrowed from tc-ppc.c on a whim. */
|
|
fixP->fx_done = 0;
|
|
if (fixP->fx_addsy
|
|
&& !S_IS_DEFINED (fixP->fx_addsy)
|
|
&& !S_IS_WEAK (fixP->fx_addsy))
|
|
S_SET_WEAK (fixP->fx_addsy);
|
|
return;
|
|
|
|
case BFD_RELOC_VTABLE_ENTRY:
|
|
fixP->fx_done = 0;
|
|
return;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
number_to_chars_bigendian (place, val, fixP->fx_size);
|
|
if (fixP->fx_addsy == NULL)
|
|
fixP->fx_done = 1;
|
|
}
|
|
|
|
const char *md_shortopts = "";
|
|
|
|
struct option md_longopts[] =
|
|
{
|
|
{NULL, no_argument, NULL, 0}
|
|
};
|
|
|
|
size_t md_longopts_size = sizeof (md_longopts);
|
|
|
|
int
|
|
md_parse_option (int c ATTRIBUTE_UNUSED,
|
|
const char *arg ATTRIBUTE_UNUSED)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
md_show_usage (FILE *stream ATTRIBUTE_UNUSED)
|
|
{
|
|
}
|
|
|
|
/* This is called when a line is unrecognized. */
|
|
|
|
int
|
|
dlx_unrecognized_line (int c)
|
|
{
|
|
int lab;
|
|
char *s;
|
|
|
|
if (c != '$' || ! ISDIGIT ((unsigned char) input_line_pointer[0]))
|
|
return 0;
|
|
|
|
s = input_line_pointer;
|
|
|
|
lab = 0;
|
|
while (ISDIGIT ((unsigned char) *s))
|
|
{
|
|
lab = lab * 10 + *s - '0';
|
|
++s;
|
|
}
|
|
|
|
if (*s != ':')
|
|
/* Not a label definition. */
|
|
return 0;
|
|
|
|
if (dollar_label_defined (lab))
|
|
{
|
|
as_bad (_("label \"$%d\" redefined"), lab);
|
|
return 0;
|
|
}
|
|
|
|
define_dollar_label (lab);
|
|
colon (dollar_label_name (lab, 0));
|
|
input_line_pointer = s + 1;
|
|
|
|
return 1;
|
|
}
|
|
|
|
/* Default the values of symbols known that should be "predefined". We
|
|
don't bother to predefine them unless you actually use one, since there
|
|
are a lot of them. */
|
|
|
|
symbolS *
|
|
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse an operand that is machine-specific, the function was called
|
|
in expr.c by operand() function, when everything failed before it
|
|
call a quit. */
|
|
|
|
void
|
|
md_operand (expressionS* expressionP)
|
|
{
|
|
/* Check for the #number representation */
|
|
if (input_line_pointer[0] == '#' &&
|
|
ISDIGIT ((unsigned char) input_line_pointer[1]))
|
|
{
|
|
/* We have a numeric number expression. No biggy. */
|
|
input_line_pointer += 1; /* Skip # */
|
|
|
|
(void) expression (expressionP);
|
|
|
|
if (expressionP->X_op != O_constant)
|
|
as_bad (_("Invalid expression after # number\n"));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/* Round up a section size to the appropriate boundary. */
|
|
|
|
valueT
|
|
md_section_align (segT segment ATTRIBUTE_UNUSED,
|
|
valueT size)
|
|
{
|
|
/* Byte alignment is fine. */
|
|
return size;
|
|
}
|
|
|
|
/* Exactly what point is a PC-relative offset relative TO?
|
|
On the 29000, they're relative to the address of the instruction,
|
|
which we have set up as the address of the fixup too. */
|
|
|
|
long
|
|
md_pcrel_from (fixS* fixP)
|
|
{
|
|
return 4 + fixP->fx_where + fixP->fx_frag->fr_address;
|
|
}
|
|
|
|
/* Translate internal representation of relocation info to BFD target
|
|
format.
|
|
FIXME: To what extent can we get all relevant targets to use this?
|
|
The above FIXME is from a29k, but I think it is also needed here. */
|
|
|
|
arelent *
|
|
tc_gen_reloc (asection *section ATTRIBUTE_UNUSED,
|
|
fixS *fixP)
|
|
{
|
|
arelent * reloc;
|
|
|
|
reloc = XNEW (arelent);
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, fixP->fx_r_type);
|
|
|
|
if (reloc->howto == NULL)
|
|
{
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
_("internal error: can't export reloc type %d (`%s')"),
|
|
fixP->fx_r_type,
|
|
bfd_get_reloc_code_name (fixP->fx_r_type));
|
|
return NULL;
|
|
}
|
|
|
|
gas_assert (!fixP->fx_pcrel == !reloc->howto->pc_relative);
|
|
|
|
reloc->sym_ptr_ptr = XNEW (asymbol *);
|
|
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixP->fx_addsy);
|
|
reloc->address = fixP->fx_frag->fr_address + fixP->fx_where;
|
|
|
|
if (fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
|
|
reloc->address = fixP->fx_offset;
|
|
reloc->addend = 0;
|
|
|
|
return reloc;
|
|
}
|
|
|
|
const pseudo_typeS
|
|
dlx_pseudo_table[] =
|
|
{
|
|
/* Some additional ops that are used by gcc-dlx. */
|
|
{"asciiz", stringer, 8 + 1},
|
|
{"half", cons, 2},
|
|
{"dword", cons, 8},
|
|
{"word", cons, 4},
|
|
{"proc", s_proc, 0},
|
|
{"endproc", s_proc, 1},
|
|
{NULL, NULL, 0}
|
|
};
|
|
|
|
void
|
|
dlx_pop_insert (void)
|
|
{
|
|
pop_insert (dlx_pseudo_table);
|
|
return ;
|
|
}
|