2013-02-07 00:22:26 +01:00
|
|
|
|
/* Altera Nios II assembler.
|
|
|
|
|
Copyright (C) 2012, 2013 Free Software Foundation, Inc.
|
|
|
|
|
Contributed by Nigel Gray (ngray@altera.com).
|
|
|
|
|
Contributed by Mentor Graphics, 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. */
|
|
|
|
|
|
|
|
|
|
#include "as.h"
|
|
|
|
|
#include "opcode/nios2.h"
|
|
|
|
|
#include "elf/nios2.h"
|
|
|
|
|
#include "tc-nios2.h"
|
|
|
|
|
#include "bfd.h"
|
|
|
|
|
#include "dwarf2dbg.h"
|
|
|
|
|
#include "subsegs.h"
|
|
|
|
|
#include "safe-ctype.h"
|
|
|
|
|
#include "dw2gencfi.h"
|
|
|
|
|
|
|
|
|
|
#ifndef OBJ_ELF
|
|
|
|
|
/* We are not supporting any other target so we throw a compile time error. */
|
|
|
|
|
OBJ_ELF not defined
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* We can choose our endianness at run-time, regardless of configuration. */
|
|
|
|
|
extern int target_big_endian;
|
|
|
|
|
|
|
|
|
|
/* 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 C style comments are always supported. */
|
|
|
|
|
const char line_comment_chars[] = "#";
|
|
|
|
|
|
|
|
|
|
/* This array holds machine specific line separator characters. */
|
|
|
|
|
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";
|
|
|
|
|
|
|
|
|
|
/* Also be aware that MAXIMUM_NUMBER_OF_CHARS_FOR_FLOAT may have to be
|
|
|
|
|
changed in read.c. Ideally it shouldn't have to know about it at all,
|
|
|
|
|
but nothing is ideal around here. */
|
|
|
|
|
|
|
|
|
|
/* Machine-dependent command-line options. */
|
|
|
|
|
|
|
|
|
|
const char *md_shortopts = "r";
|
|
|
|
|
|
|
|
|
|
struct option md_longopts[] = {
|
|
|
|
|
#define OPTION_RELAX_ALL (OPTION_MD_BASE + 0)
|
|
|
|
|
{"relax-all", no_argument, NULL, OPTION_RELAX_ALL},
|
|
|
|
|
#define OPTION_NORELAX (OPTION_MD_BASE + 1)
|
|
|
|
|
{"no-relax", no_argument, NULL, OPTION_NORELAX},
|
|
|
|
|
#define OPTION_RELAX_SECTION (OPTION_MD_BASE + 2)
|
|
|
|
|
{"relax-section", no_argument, NULL, OPTION_RELAX_SECTION},
|
|
|
|
|
#define OPTION_EB (OPTION_MD_BASE + 3)
|
|
|
|
|
{"EB", no_argument, NULL, OPTION_EB},
|
|
|
|
|
#define OPTION_EL (OPTION_MD_BASE + 4)
|
|
|
|
|
{"EL", no_argument, NULL, OPTION_EL}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
size_t md_longopts_size = sizeof (md_longopts);
|
|
|
|
|
|
|
|
|
|
/* The assembler supports three different relaxation modes, controlled by
|
|
|
|
|
command-line options. */
|
|
|
|
|
typedef enum
|
|
|
|
|
{
|
|
|
|
|
relax_section = 0,
|
|
|
|
|
relax_none,
|
|
|
|
|
relax_all
|
|
|
|
|
} relax_optionT;
|
|
|
|
|
|
|
|
|
|
/* Struct contains all assembler options set with .set. */
|
|
|
|
|
struct
|
|
|
|
|
{
|
|
|
|
|
/* .set noat -> noat = 1 allows assembly code to use at without warning
|
|
|
|
|
and macro expansions generate a warning.
|
|
|
|
|
.set at -> noat = 0, assembly code using at warn but macro expansions
|
|
|
|
|
do not generate warnings. */
|
|
|
|
|
bfd_boolean noat;
|
|
|
|
|
|
|
|
|
|
/* .set nobreak -> nobreak = 1 allows assembly code to use ba,bt without
|
|
|
|
|
warning.
|
|
|
|
|
.set break -> nobreak = 0, assembly code using ba,bt warns. */
|
|
|
|
|
bfd_boolean nobreak;
|
|
|
|
|
|
|
|
|
|
/* .cmd line option -relax-all allows all branches and calls to be replaced
|
|
|
|
|
with longer versions.
|
|
|
|
|
-no-relax inhibits branch/call conversion.
|
|
|
|
|
The default value is relax_section, which relaxes branches within
|
|
|
|
|
a section. */
|
|
|
|
|
relax_optionT relax;
|
|
|
|
|
|
|
|
|
|
} nios2_as_options = {FALSE, FALSE, relax_section};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
typedef struct nios2_insn_reloc
|
|
|
|
|
{
|
|
|
|
|
/* Any expression in the instruction is parsed into this field,
|
|
|
|
|
which is passed to fix_new_exp() to generate a fixup. */
|
|
|
|
|
expressionS reloc_expression;
|
|
|
|
|
|
|
|
|
|
/* The type of the relocation to be applied. */
|
|
|
|
|
bfd_reloc_code_real_type reloc_type;
|
|
|
|
|
|
|
|
|
|
/* PC-relative. */
|
|
|
|
|
unsigned int reloc_pcrel;
|
|
|
|
|
|
|
|
|
|
/* The next relocation to be applied to the instruction. */
|
|
|
|
|
struct nios2_insn_reloc *reloc_next;
|
|
|
|
|
} nios2_insn_relocS;
|
|
|
|
|
|
|
|
|
|
/* This struct is used to hold state when assembling instructions. */
|
|
|
|
|
typedef struct nios2_insn_info
|
|
|
|
|
{
|
|
|
|
|
/* Assembled instruction. */
|
|
|
|
|
unsigned long insn_code;
|
|
|
|
|
/* Pointer to the relevant bit of the opcode table. */
|
|
|
|
|
const struct nios2_opcode *insn_nios2_opcode;
|
|
|
|
|
/* After parsing ptrs to the tokens in the instruction fill this array
|
|
|
|
|
it is terminated with a null pointer (hence the first +1).
|
|
|
|
|
The second +1 is because in some parts of the code the opcode
|
|
|
|
|
is not counted as a token, but still placed in this array. */
|
|
|
|
|
const char *insn_tokens[NIOS2_MAX_INSN_TOKENS + 1 + 1];
|
|
|
|
|
|
|
|
|
|
/* This holds information used to generate fixups
|
|
|
|
|
and eventually relocations if it is not null. */
|
|
|
|
|
nios2_insn_relocS *insn_reloc;
|
|
|
|
|
} nios2_insn_infoS;
|
|
|
|
|
|
|
|
|
|
/* This struct associates an argument assemble function with
|
|
|
|
|
an argument syntax string. Used by the assembler to find out
|
|
|
|
|
how to parse and assemble a set of instruction operands and
|
|
|
|
|
return the instruction field values. */
|
|
|
|
|
typedef struct nios2_arg_info
|
|
|
|
|
{
|
|
|
|
|
const char *args;
|
|
|
|
|
void (*assemble_args_func) (nios2_insn_infoS *insn_info);
|
|
|
|
|
} nios2_arg_infoS;
|
|
|
|
|
|
|
|
|
|
/* This struct is used to convert Nios II pseudo-ops into the
|
|
|
|
|
corresponding real op. */
|
|
|
|
|
typedef struct nios2_ps_insn_info
|
|
|
|
|
{
|
|
|
|
|
/* Map this pseudo_op... */
|
|
|
|
|
const char *pseudo_insn;
|
|
|
|
|
|
|
|
|
|
/* ...to this real instruction. */
|
|
|
|
|
const char *insn;
|
|
|
|
|
|
|
|
|
|
/* Call this function to modify the operands.... */
|
|
|
|
|
void (*arg_modifer_func) (char ** parsed_args, const char *arg, int num,
|
|
|
|
|
int start);
|
|
|
|
|
|
|
|
|
|
/* ...with these arguments. */
|
|
|
|
|
const char *arg_modifier;
|
|
|
|
|
int num;
|
|
|
|
|
int index;
|
|
|
|
|
|
|
|
|
|
/* If arg_modifier_func allocates new memory, provide this function
|
|
|
|
|
to free it afterwards. */
|
|
|
|
|
void (*arg_cleanup_func) (char **parsed_args, int num, int start);
|
|
|
|
|
} nios2_ps_insn_infoS;
|
|
|
|
|
|
|
|
|
|
/* Opcode hash table. */
|
|
|
|
|
static struct hash_control *nios2_opcode_hash = NULL;
|
|
|
|
|
#define nios2_opcode_lookup(NAME) \
|
|
|
|
|
((struct nios2_opcode *) hash_find (nios2_opcode_hash, (NAME)))
|
|
|
|
|
|
|
|
|
|
/* Register hash table. */
|
|
|
|
|
static struct hash_control *nios2_reg_hash = NULL;
|
|
|
|
|
#define nios2_reg_lookup(NAME) \
|
|
|
|
|
((struct nios2_reg *) hash_find (nios2_reg_hash, (NAME)))
|
|
|
|
|
|
|
|
|
|
/* Parse args hash table. */
|
|
|
|
|
static struct hash_control *nios2_arg_hash = NULL;
|
|
|
|
|
#define nios2_arg_lookup(NAME) \
|
|
|
|
|
((nios2_arg_infoS *) hash_find (nios2_arg_hash, (NAME)))
|
|
|
|
|
|
|
|
|
|
/* Pseudo-op hash table. */
|
|
|
|
|
static struct hash_control *nios2_ps_hash = NULL;
|
|
|
|
|
#define nios2_ps_lookup(NAME) \
|
|
|
|
|
((nios2_ps_insn_infoS *) hash_find (nios2_ps_hash, (NAME)))
|
|
|
|
|
|
|
|
|
|
/* The known current alignment of the current section. */
|
|
|
|
|
static int nios2_current_align;
|
|
|
|
|
static segT nios2_current_align_seg;
|
|
|
|
|
|
|
|
|
|
static int nios2_auto_align_on = 1;
|
|
|
|
|
|
|
|
|
|
/* The last seen label in the current section. This is used to auto-align
|
|
|
|
|
labels preceeding instructions. */
|
|
|
|
|
static symbolS *nios2_last_label;
|
|
|
|
|
|
|
|
|
|
#ifdef OBJ_ELF
|
|
|
|
|
/* Pre-defined "_GLOBAL_OFFSET_TABLE_" */
|
|
|
|
|
symbolS *GOT_symbol;
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Utility routines. */
|
|
|
|
|
/* Function md_chars_to_number takes the sequence of
|
|
|
|
|
bytes in buf and returns the corresponding value
|
|
|
|
|
in an int. n must be 1, 2 or 4. */
|
|
|
|
|
static valueT
|
|
|
|
|
md_chars_to_number (char *buf, int n)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
valueT val;
|
|
|
|
|
|
|
|
|
|
gas_assert (n == 1 || n == 2 || n == 4);
|
|
|
|
|
|
|
|
|
|
val = 0;
|
|
|
|
|
if (target_big_endian)
|
|
|
|
|
for (i = 0; i < n; ++i)
|
|
|
|
|
val = val | ((buf[i] & 0xff) << 8 * (n - (i + 1)));
|
|
|
|
|
else
|
|
|
|
|
for (i = 0; i < n; ++i)
|
|
|
|
|
val = val | ((buf[i] & 0xff) << 8 * i);
|
|
|
|
|
return val;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* This function turns a C long int, short int or char
|
|
|
|
|
into the series of bytes that represent the number
|
|
|
|
|
on the target machine. */
|
|
|
|
|
void
|
|
|
|
|
md_number_to_chars (char *buf, valueT val, int n)
|
|
|
|
|
{
|
|
|
|
|
gas_assert (n == 1 || n == 2 || n == 4 || n == 8);
|
|
|
|
|
if (target_big_endian)
|
|
|
|
|
number_to_chars_bigendian (buf, val, n);
|
|
|
|
|
else
|
|
|
|
|
number_to_chars_littleendian (buf, val, n);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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. */
|
|
|
|
|
char *
|
|
|
|
|
md_atof (int type, char *litP, int *sizeP)
|
|
|
|
|
{
|
|
|
|
|
int prec;
|
|
|
|
|
LITTLENUM_TYPE words[4];
|
|
|
|
|
char *t;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
switch (type)
|
|
|
|
|
{
|
|
|
|
|
case 'f':
|
|
|
|
|
prec = 2;
|
|
|
|
|
break;
|
|
|
|
|
case 'd':
|
|
|
|
|
prec = 4;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
*sizeP = 0;
|
|
|
|
|
return _("bad call to md_atof");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
t = atof_ieee (input_line_pointer, type, words);
|
|
|
|
|
if (t)
|
|
|
|
|
input_line_pointer = t;
|
|
|
|
|
|
|
|
|
|
*sizeP = prec * 2;
|
|
|
|
|
|
|
|
|
|
if (! target_big_endian)
|
|
|
|
|
for (i = prec - 1; i >= 0; i--, litP += 2)
|
|
|
|
|
md_number_to_chars (litP, (valueT) words[i], 2);
|
|
|
|
|
else
|
|
|
|
|
for (i = 0; i < prec; i++, litP += 2)
|
|
|
|
|
md_number_to_chars (litP, (valueT) words[i], 2);
|
|
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Return true if STR starts with PREFIX, which should be a string literal. */
|
|
|
|
|
#define strprefix(STR, PREFIX) \
|
|
|
|
|
(strncmp ((STR), PREFIX, strlen (PREFIX)) == 0)
|
|
|
|
|
|
|
|
|
|
/* Return true if STR is prefixed with a control register name. */
|
|
|
|
|
static int
|
|
|
|
|
nios2_control_register_arg_p (const char *str)
|
|
|
|
|
{
|
|
|
|
|
return (strprefix (str, "ctl")
|
|
|
|
|
|| strprefix (str, "cpuid")
|
|
|
|
|
|| strprefix (str, "status")
|
|
|
|
|
|| strprefix (str, "estatus")
|
|
|
|
|
|| strprefix (str, "bstatus")
|
|
|
|
|
|| strprefix (str, "ienable")
|
|
|
|
|
|| strprefix (str, "ipending")
|
|
|
|
|
|| strprefix (str, "exception")
|
|
|
|
|
|| strprefix (str, "pteaddr")
|
|
|
|
|
|| strprefix (str, "tlbacc")
|
|
|
|
|
|| strprefix (str, "tlbmisc")
|
2013-04-24 22:51:58 +02:00
|
|
|
|
|| strprefix (str, "eccinj")
|
2013-02-07 00:22:26 +01:00
|
|
|
|
|| strprefix (str, "config")
|
|
|
|
|
|| strprefix (str, "mpubase")
|
|
|
|
|
|| strprefix (str, "mpuacc")
|
|
|
|
|
|| strprefix (str, "badaddr"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Return true if STR is prefixed with a special relocation operator. */
|
|
|
|
|
static int
|
|
|
|
|
nios2_special_relocation_p (const char *str)
|
|
|
|
|
{
|
|
|
|
|
return (strprefix (str, "%lo")
|
|
|
|
|
|| strprefix (str, "%hi")
|
|
|
|
|
|| strprefix (str, "%hiadj")
|
|
|
|
|
|| strprefix (str, "%gprel")
|
|
|
|
|
|| strprefix (str, "%got")
|
|
|
|
|
|| strprefix (str, "%call")
|
|
|
|
|
|| strprefix (str, "%gotoff_lo")
|
|
|
|
|
|| strprefix (str, "%gotoff_hiadj")
|
|
|
|
|
|| strprefix (str, "%tls_gd")
|
|
|
|
|
|| strprefix (str, "%tls_ldm")
|
|
|
|
|
|| strprefix (str, "%tls_ldo")
|
|
|
|
|
|| strprefix (str, "%tls_ie")
|
|
|
|
|
|| strprefix (str, "%tls_le")
|
|
|
|
|
|| strprefix (str, "%gotoff"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Checks whether the register name is a coprocessor
|
|
|
|
|
register - returns TRUE if it is, FALSE otherwise. */
|
|
|
|
|
static bfd_boolean
|
|
|
|
|
nios2_coproc_reg (const char *reg_name)
|
|
|
|
|
{
|
|
|
|
|
gas_assert (reg_name != NULL);
|
|
|
|
|
|
|
|
|
|
/* Check that we do have a valid register name and that it is a
|
|
|
|
|
coprocessor register.
|
|
|
|
|
It must begin with c, not be a control register, and be a valid
|
|
|
|
|
register name. */
|
|
|
|
|
if (strprefix (reg_name, "c")
|
|
|
|
|
&& !strprefix (reg_name, "ctl")
|
|
|
|
|
&& hash_find (nios2_reg_hash, reg_name) != NULL)
|
|
|
|
|
return TRUE;
|
|
|
|
|
else
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* nop fill pattern for text section. */
|
|
|
|
|
static char const nop[4] = { 0x3a, 0x88, 0x01, 0x00 };
|
|
|
|
|
|
|
|
|
|
/* Handles all machine-dependent alignment needs. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_align (int log_size, const char *pfill, symbolS *label)
|
|
|
|
|
{
|
|
|
|
|
int align;
|
|
|
|
|
long max_alignment = 15;
|
|
|
|
|
|
|
|
|
|
/* The front end is prone to changing segments out from under us
|
|
|
|
|
temporarily when -g is in effect. */
|
|
|
|
|
int switched_seg_p = (nios2_current_align_seg != now_seg);
|
|
|
|
|
|
|
|
|
|
align = log_size;
|
|
|
|
|
if (align > max_alignment)
|
|
|
|
|
{
|
|
|
|
|
align = max_alignment;
|
|
|
|
|
as_bad (_("Alignment too large: %d. assumed"), align);
|
|
|
|
|
}
|
|
|
|
|
else if (align < 0)
|
|
|
|
|
{
|
|
|
|
|
as_warn (_("Alignment negative: 0 assumed"));
|
|
|
|
|
align = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (align != 0)
|
|
|
|
|
{
|
|
|
|
|
if (subseg_text_p (now_seg) && align >= 2)
|
|
|
|
|
{
|
|
|
|
|
/* First, make sure we're on a four-byte boundary, in case
|
|
|
|
|
someone has been putting .byte values the text section. */
|
|
|
|
|
if (nios2_current_align < 2 || switched_seg_p)
|
|
|
|
|
frag_align (2, 0, 0);
|
|
|
|
|
|
|
|
|
|
/* Now fill in the alignment pattern. */
|
|
|
|
|
if (pfill != NULL)
|
|
|
|
|
frag_align_pattern (align, pfill, sizeof nop, 0);
|
|
|
|
|
else
|
|
|
|
|
frag_align (align, 0, 0);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
frag_align (align, 0, 0);
|
|
|
|
|
|
|
|
|
|
if (!switched_seg_p)
|
|
|
|
|
nios2_current_align = align;
|
|
|
|
|
|
|
|
|
|
/* If the last label was in a different section we can't align it. */
|
|
|
|
|
if (label != NULL && !switched_seg_p)
|
|
|
|
|
{
|
|
|
|
|
symbolS *sym;
|
|
|
|
|
int label_seen = FALSE;
|
|
|
|
|
struct frag *old_frag;
|
|
|
|
|
valueT old_value;
|
|
|
|
|
valueT new_value;
|
|
|
|
|
|
|
|
|
|
gas_assert (S_GET_SEGMENT (label) == now_seg);
|
|
|
|
|
|
|
|
|
|
old_frag = symbol_get_frag (label);
|
|
|
|
|
old_value = S_GET_VALUE (label);
|
|
|
|
|
new_value = (valueT) frag_now_fix ();
|
|
|
|
|
|
|
|
|
|
/* It is possible to have more than one label at a particular
|
|
|
|
|
address, especially if debugging is enabled, so we must
|
|
|
|
|
take care to adjust all the labels at this address in this
|
|
|
|
|
fragment. To save time we search from the end of the symbol
|
|
|
|
|
list, backwards, since the symbols we are interested in are
|
|
|
|
|
almost certainly the ones that were most recently added.
|
|
|
|
|
Also to save time we stop searching once we have seen at least
|
|
|
|
|
one matching label, and we encounter a label that is no longer
|
|
|
|
|
in the target fragment. Note, this search is guaranteed to
|
|
|
|
|
find at least one match when sym == label, so no special case
|
|
|
|
|
code is necessary. */
|
|
|
|
|
for (sym = symbol_lastP; sym != NULL; sym = symbol_previous (sym))
|
|
|
|
|
if (symbol_get_frag (sym) == old_frag
|
|
|
|
|
&& S_GET_VALUE (sym) == old_value)
|
|
|
|
|
{
|
|
|
|
|
label_seen = TRUE;
|
|
|
|
|
symbol_set_frag (sym, frag_now);
|
|
|
|
|
S_SET_VALUE (sym, new_value);
|
|
|
|
|
}
|
|
|
|
|
else if (label_seen && symbol_get_frag (sym) != old_frag)
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
record_alignment (now_seg, align);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Support for self-check mode. */
|
|
|
|
|
|
|
|
|
|
/* Mode of the assembler. */
|
|
|
|
|
typedef enum
|
|
|
|
|
{
|
|
|
|
|
NIOS2_MODE_ASSEMBLE, /* Ordinary operation. */
|
|
|
|
|
NIOS2_MODE_TEST /* Hidden mode used for self testing. */
|
|
|
|
|
} NIOS2_MODE;
|
|
|
|
|
|
|
|
|
|
static NIOS2_MODE nios2_mode = NIOS2_MODE_ASSEMBLE;
|
|
|
|
|
|
|
|
|
|
/* This function is used to in self-checking mode
|
|
|
|
|
to check the assembled instruction
|
|
|
|
|
opcode should be the assembled opcode, and exp_opcode
|
|
|
|
|
the parsed string representing the expected opcode. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_check_assembly (unsigned int opcode, const char *exp_opcode)
|
|
|
|
|
{
|
|
|
|
|
if (nios2_mode == NIOS2_MODE_TEST)
|
|
|
|
|
{
|
|
|
|
|
if (exp_opcode == NULL)
|
|
|
|
|
as_bad (_("expecting opcode string in self test mode"));
|
|
|
|
|
else if (opcode != strtoul (exp_opcode, NULL, 16))
|
|
|
|
|
as_bad (_("assembly 0x%08x, expected %s"), opcode, exp_opcode);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Support for machine-dependent assembler directives. */
|
|
|
|
|
/* Handle the .align pseudo-op. This aligns to a power of two. It
|
|
|
|
|
also adjusts any current instruction label. We treat this the same
|
|
|
|
|
way the MIPS port does: .align 0 turns off auto alignment. */
|
|
|
|
|
static void
|
|
|
|
|
s_nios2_align (int ignore ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
int align;
|
|
|
|
|
char fill;
|
|
|
|
|
const char *pfill = NULL;
|
|
|
|
|
long max_alignment = 15;
|
|
|
|
|
|
|
|
|
|
align = get_absolute_expression ();
|
|
|
|
|
if (align > max_alignment)
|
|
|
|
|
{
|
|
|
|
|
align = max_alignment;
|
|
|
|
|
as_bad (_("Alignment too large: %d. assumed"), align);
|
|
|
|
|
}
|
|
|
|
|
else if (align < 0)
|
|
|
|
|
{
|
|
|
|
|
as_warn (_("Alignment negative: 0 assumed"));
|
|
|
|
|
align = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (*input_line_pointer == ',')
|
|
|
|
|
{
|
|
|
|
|
input_line_pointer++;
|
|
|
|
|
fill = get_absolute_expression ();
|
|
|
|
|
pfill = (const char *) &fill;
|
|
|
|
|
}
|
|
|
|
|
else if (subseg_text_p (now_seg))
|
|
|
|
|
pfill = (const char *) &nop;
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
pfill = NULL;
|
|
|
|
|
nios2_last_label = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (align != 0)
|
|
|
|
|
{
|
|
|
|
|
nios2_auto_align_on = 1;
|
|
|
|
|
nios2_align (align, pfill, nios2_last_label);
|
|
|
|
|
nios2_last_label = NULL;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
nios2_auto_align_on = 0;
|
|
|
|
|
|
|
|
|
|
demand_empty_rest_of_line ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Handle the .text pseudo-op. This is like the usual one, but it
|
|
|
|
|
clears the saved last label and resets known alignment. */
|
|
|
|
|
static void
|
|
|
|
|
s_nios2_text (int i)
|
|
|
|
|
{
|
|
|
|
|
s_text (i);
|
|
|
|
|
nios2_last_label = NULL;
|
|
|
|
|
nios2_current_align = 0;
|
|
|
|
|
nios2_current_align_seg = now_seg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Handle the .data pseudo-op. This is like the usual one, but it
|
|
|
|
|
clears the saved last label and resets known alignment. */
|
|
|
|
|
static void
|
|
|
|
|
s_nios2_data (int i)
|
|
|
|
|
{
|
|
|
|
|
s_data (i);
|
|
|
|
|
nios2_last_label = NULL;
|
|
|
|
|
nios2_current_align = 0;
|
|
|
|
|
nios2_current_align_seg = now_seg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Handle the .section pseudo-op. This is like the usual one, but it
|
|
|
|
|
clears the saved last label and resets known alignment. */
|
|
|
|
|
static void
|
|
|
|
|
s_nios2_section (int ignore)
|
|
|
|
|
{
|
|
|
|
|
obj_elf_section (ignore);
|
|
|
|
|
nios2_last_label = NULL;
|
|
|
|
|
nios2_current_align = 0;
|
|
|
|
|
nios2_current_align_seg = now_seg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Explicitly unaligned cons. */
|
|
|
|
|
static void
|
|
|
|
|
s_nios2_ucons (int nbytes)
|
|
|
|
|
{
|
|
|
|
|
int hold;
|
|
|
|
|
hold = nios2_auto_align_on;
|
|
|
|
|
nios2_auto_align_on = 0;
|
|
|
|
|
cons (nbytes);
|
|
|
|
|
nios2_auto_align_on = hold;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Handle the .sdata directive. */
|
|
|
|
|
static void
|
|
|
|
|
s_nios2_sdata (int ignore ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
get_absolute_expression (); /* Ignored. */
|
|
|
|
|
subseg_new (".sdata", 0);
|
|
|
|
|
demand_empty_rest_of_line ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* .set sets assembler options eg noat/at and is also used
|
|
|
|
|
to set symbol values (.equ, .equiv ). */
|
|
|
|
|
static void
|
|
|
|
|
s_nios2_set (int equiv)
|
|
|
|
|
{
|
|
|
|
|
char *directive = input_line_pointer;
|
|
|
|
|
char delim = get_symbol_end ();
|
|
|
|
|
char *endline = input_line_pointer;
|
|
|
|
|
*endline = delim;
|
|
|
|
|
|
|
|
|
|
/* We only want to handle ".set XXX" if the
|
|
|
|
|
user has tried ".set XXX, YYY" they are not
|
|
|
|
|
trying a directive. This prevents
|
|
|
|
|
us from polluting the name space. */
|
|
|
|
|
SKIP_WHITESPACE ();
|
|
|
|
|
if (is_end_of_line[(unsigned char) *input_line_pointer])
|
|
|
|
|
{
|
|
|
|
|
bfd_boolean done = TRUE;
|
|
|
|
|
*endline = 0;
|
|
|
|
|
|
|
|
|
|
if (!strcmp (directive, "noat"))
|
|
|
|
|
nios2_as_options.noat = TRUE;
|
|
|
|
|
else if (!strcmp (directive, "at"))
|
|
|
|
|
nios2_as_options.noat = FALSE;
|
|
|
|
|
else if (!strcmp (directive, "nobreak"))
|
|
|
|
|
nios2_as_options.nobreak = TRUE;
|
|
|
|
|
else if (!strcmp (directive, "break"))
|
|
|
|
|
nios2_as_options.nobreak = FALSE;
|
|
|
|
|
else if (!strcmp (directive, "norelax"))
|
|
|
|
|
nios2_as_options.relax = relax_none;
|
|
|
|
|
else if (!strcmp (directive, "relaxsection"))
|
|
|
|
|
nios2_as_options.relax = relax_section;
|
|
|
|
|
else if (!strcmp (directive, "relaxall"))
|
|
|
|
|
nios2_as_options.relax = relax_all;
|
|
|
|
|
else
|
|
|
|
|
done = FALSE;
|
|
|
|
|
|
|
|
|
|
if (done)
|
|
|
|
|
{
|
|
|
|
|
*endline = delim;
|
|
|
|
|
demand_empty_rest_of_line ();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we fall through to here, either we have ".set XXX, YYY"
|
|
|
|
|
or we have ".set XXX" where XXX is unknown or we have
|
|
|
|
|
a syntax error. */
|
|
|
|
|
input_line_pointer = directive;
|
|
|
|
|
*endline = delim;
|
|
|
|
|
s_set (equiv);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Machine-dependent assembler directives.
|
|
|
|
|
Format of each entry is:
|
|
|
|
|
{ "directive", handler_func, param } */
|
|
|
|
|
const pseudo_typeS md_pseudo_table[] = {
|
|
|
|
|
{"align", s_nios2_align, 0},
|
|
|
|
|
{"text", s_nios2_text, 0},
|
|
|
|
|
{"data", s_nios2_data, 0},
|
|
|
|
|
{"section", s_nios2_section, 0},
|
|
|
|
|
{"section.s", s_nios2_section, 0},
|
|
|
|
|
{"sect", s_nios2_section, 0},
|
|
|
|
|
{"sect.s", s_nios2_section, 0},
|
|
|
|
|
/* .dword and .half are included for compatibility with MIPS. */
|
|
|
|
|
{"dword", cons, 8},
|
|
|
|
|
{"half", cons, 2},
|
|
|
|
|
/* NIOS2 native word size is 4 bytes, so we override
|
|
|
|
|
the GAS default of 2. */
|
|
|
|
|
{"word", cons, 4},
|
|
|
|
|
/* Explicitly unaligned directives. */
|
|
|
|
|
{"2byte", s_nios2_ucons, 2},
|
|
|
|
|
{"4byte", s_nios2_ucons, 4},
|
|
|
|
|
{"8byte", s_nios2_ucons, 8},
|
|
|
|
|
{"16byte", s_nios2_ucons, 16},
|
|
|
|
|
#ifdef OBJ_ELF
|
|
|
|
|
{"sdata", s_nios2_sdata, 0},
|
|
|
|
|
#endif
|
|
|
|
|
{"set", s_nios2_set, 0},
|
|
|
|
|
{NULL, NULL, 0}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Relaxation support. */
|
|
|
|
|
|
|
|
|
|
/* We support two relaxation modes: a limited PC-relative mode with
|
|
|
|
|
-relax-section (the default), and an absolute jump mode with -relax-all.
|
|
|
|
|
|
|
|
|
|
Nios II PC-relative branch instructions only support 16-bit offsets.
|
|
|
|
|
And, there's no good way to add a 32-bit constant to the PC without
|
|
|
|
|
using two registers.
|
|
|
|
|
|
|
|
|
|
To deal with this, for the pc-relative relaxation mode we convert
|
|
|
|
|
br label
|
|
|
|
|
into a series of 16-bit adds, like:
|
|
|
|
|
nextpc at
|
|
|
|
|
addi at, at, 32767
|
|
|
|
|
...
|
|
|
|
|
addi at, at, remainder
|
|
|
|
|
jmp at
|
|
|
|
|
|
|
|
|
|
Similarly, conditional branches are converted from
|
|
|
|
|
b(condition) r, s, label
|
|
|
|
|
into a series like:
|
|
|
|
|
b(opposite condition) r, s, skip
|
|
|
|
|
nextpc at
|
|
|
|
|
addi at, at, 32767
|
|
|
|
|
...
|
|
|
|
|
addi at, at, remainder
|
|
|
|
|
jmp at
|
|
|
|
|
skip:
|
|
|
|
|
|
|
|
|
|
The compiler can do a better job, either by converting the branch
|
|
|
|
|
directly into a JMP (going through the GOT for PIC) or by allocating
|
|
|
|
|
a second register for the 32-bit displacement.
|
|
|
|
|
|
|
|
|
|
For the -relax-all relaxation mode, the conversions are
|
|
|
|
|
movhi at, %hi(symbol+offset)
|
|
|
|
|
ori at, %lo(symbol+offset)
|
|
|
|
|
jmp at
|
|
|
|
|
and
|
|
|
|
|
b(opposite condition), r, s, skip
|
|
|
|
|
movhi at, %hi(symbol+offset)
|
|
|
|
|
ori at, %lo(symbol+offset)
|
|
|
|
|
jmp at
|
|
|
|
|
skip:
|
|
|
|
|
respectively.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
/* Arbitrarily limit the number of addis we can insert; we need to be able
|
|
|
|
|
to specify the maximum growth size for each frag that contains a
|
|
|
|
|
relaxable branch. There's no point in specifying a huge number here
|
|
|
|
|
since that means the assembler needs to allocate that much extra
|
|
|
|
|
memory for every branch, and almost no real code will ever need it.
|
|
|
|
|
Plus, as already noted a better solution is to just use a jmp, or
|
|
|
|
|
allocate a second register to hold a 32-bit displacement.
|
|
|
|
|
FIXME: Rather than making this a constant, it could be controlled by
|
|
|
|
|
a command-line argument. */
|
|
|
|
|
#define RELAX_MAX_ADDI 32
|
|
|
|
|
|
|
|
|
|
/* The fr_subtype field represents the target-specific relocation state.
|
|
|
|
|
It has type relax_substateT (unsigned int). We use it to track the
|
|
|
|
|
number of addis necessary, plus a bit to track whether this is a
|
|
|
|
|
conditional branch.
|
|
|
|
|
Regardless of the smaller RELAX_MAX_ADDI limit, we reserve 16 bits
|
|
|
|
|
in the fr_subtype to encode the number of addis so that the whole
|
|
|
|
|
theoretically-valid range is representable.
|
|
|
|
|
For the -relax-all mode, N = 0 represents an in-range branch and N = 1
|
|
|
|
|
represents a branch that needs to be relaxed. */
|
|
|
|
|
#define UBRANCH (0 << 16)
|
|
|
|
|
#define CBRANCH (1 << 16)
|
|
|
|
|
#define IS_CBRANCH(SUBTYPE) ((SUBTYPE) & CBRANCH)
|
|
|
|
|
#define IS_UBRANCH(SUBTYPE) (!IS_CBRANCH (SUBTYPE))
|
|
|
|
|
#define UBRANCH_SUBTYPE(N) (UBRANCH | (N))
|
|
|
|
|
#define CBRANCH_SUBTYPE(N) (CBRANCH | (N))
|
|
|
|
|
#define SUBTYPE_ADDIS(SUBTYPE) ((SUBTYPE) & 0xffff)
|
|
|
|
|
|
|
|
|
|
/* For the -relax-section mode, unconditional branches require 2 extra i
|
|
|
|
|
nstructions besides the addis, conditional branches require 3. */
|
|
|
|
|
#define UBRANCH_ADDIS_TO_SIZE(N) (((N) + 2) * 4)
|
|
|
|
|
#define CBRANCH_ADDIS_TO_SIZE(N) (((N) + 3) * 4)
|
|
|
|
|
|
|
|
|
|
/* For the -relax-all mode, unconditional branches require 3 instructions
|
|
|
|
|
and conditional branches require 4. */
|
|
|
|
|
#define UBRANCH_JUMP_SIZE 12
|
|
|
|
|
#define CBRANCH_JUMP_SIZE 16
|
|
|
|
|
|
|
|
|
|
/* Maximum sizes of relaxation sequences. */
|
|
|
|
|
#define UBRANCH_MAX_SIZE \
|
|
|
|
|
(nios2_as_options.relax == relax_all \
|
|
|
|
|
? UBRANCH_JUMP_SIZE \
|
|
|
|
|
: UBRANCH_ADDIS_TO_SIZE (RELAX_MAX_ADDI))
|
|
|
|
|
#define CBRANCH_MAX_SIZE \
|
|
|
|
|
(nios2_as_options.relax == relax_all \
|
|
|
|
|
? CBRANCH_JUMP_SIZE \
|
|
|
|
|
: CBRANCH_ADDIS_TO_SIZE (RELAX_MAX_ADDI))
|
|
|
|
|
|
|
|
|
|
/* Register number of AT, the assembler temporary. */
|
|
|
|
|
#define AT_REGNUM 1
|
|
|
|
|
|
|
|
|
|
/* Determine how many bytes are required to represent the sequence
|
|
|
|
|
indicated by SUBTYPE. */
|
|
|
|
|
static int
|
|
|
|
|
nios2_relax_subtype_size (relax_substateT subtype)
|
|
|
|
|
{
|
|
|
|
|
int n = SUBTYPE_ADDIS (subtype);
|
|
|
|
|
if (n == 0)
|
|
|
|
|
/* Regular conditional/unconditional branch instruction. */
|
|
|
|
|
return 4;
|
|
|
|
|
else if (nios2_as_options.relax == relax_all)
|
|
|
|
|
return (IS_CBRANCH (subtype) ? CBRANCH_JUMP_SIZE : UBRANCH_JUMP_SIZE);
|
|
|
|
|
else if (IS_CBRANCH (subtype))
|
|
|
|
|
return CBRANCH_ADDIS_TO_SIZE (n);
|
|
|
|
|
else
|
|
|
|
|
return UBRANCH_ADDIS_TO_SIZE (n);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Estimate size of fragp before relaxation.
|
|
|
|
|
This could also examine the offset in fragp and adjust
|
|
|
|
|
fragp->fr_subtype, but we will do that in nios2_relax_frag anyway. */
|
|
|
|
|
int
|
|
|
|
|
md_estimate_size_before_relax (fragS *fragp, segT segment ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
return nios2_relax_subtype_size (fragp->fr_subtype);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement md_relax_frag, returning the change in size of the frag. */
|
|
|
|
|
long
|
|
|
|
|
nios2_relax_frag (segT segment, fragS *fragp, long stretch)
|
|
|
|
|
{
|
|
|
|
|
addressT target = fragp->fr_offset;
|
|
|
|
|
relax_substateT subtype = fragp->fr_subtype;
|
|
|
|
|
symbolS *symbolp = fragp->fr_symbol;
|
|
|
|
|
|
|
|
|
|
if (symbolp)
|
|
|
|
|
{
|
|
|
|
|
fragS *sym_frag = symbol_get_frag (symbolp);
|
|
|
|
|
offsetT offset;
|
|
|
|
|
int n;
|
|
|
|
|
|
|
|
|
|
target += S_GET_VALUE (symbolp);
|
|
|
|
|
|
|
|
|
|
/* See comments in write.c:relax_frag about handling of stretch. */
|
|
|
|
|
if (stretch != 0
|
|
|
|
|
&& sym_frag->relax_marker != fragp->relax_marker)
|
|
|
|
|
{
|
|
|
|
|
if (stretch < 0 || sym_frag->region == fragp->region)
|
|
|
|
|
target += stretch;
|
|
|
|
|
else if (target < fragp->fr_address)
|
|
|
|
|
target = fragp->fr_next->fr_address + stretch;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* We subtract 4 because all pc relative branches are
|
|
|
|
|
from the next instruction. */
|
|
|
|
|
offset = target - fragp->fr_address - fragp->fr_fix - 4;
|
|
|
|
|
if (offset >= -32768 && offset <= 32764)
|
|
|
|
|
/* Fits in PC-relative branch. */
|
|
|
|
|
n = 0;
|
|
|
|
|
else if (nios2_as_options.relax == relax_all)
|
|
|
|
|
/* Convert to jump. */
|
|
|
|
|
n = 1;
|
|
|
|
|
else if (nios2_as_options.relax == relax_section
|
|
|
|
|
&& S_GET_SEGMENT (symbolp) == segment
|
|
|
|
|
&& S_IS_DEFINED (symbolp))
|
|
|
|
|
/* Attempt a PC-relative relaxation on a branch to a defined
|
|
|
|
|
symbol in the same segment. */
|
|
|
|
|
{
|
|
|
|
|
/* The relaxation for conditional branches is offset by 4
|
|
|
|
|
bytes because we insert the inverted branch around the
|
|
|
|
|
sequence. */
|
|
|
|
|
if (IS_CBRANCH (subtype))
|
|
|
|
|
offset = offset - 4;
|
|
|
|
|
if (offset > 0)
|
|
|
|
|
n = offset / 32767 + 1;
|
|
|
|
|
else
|
|
|
|
|
n = offset / -32768 + 1;
|
|
|
|
|
|
|
|
|
|
/* Bail out immediately if relaxation has failed. If we try to
|
|
|
|
|
defer the diagnostic to md_convert_frag, some pathological test
|
|
|
|
|
cases (e.g. gcc/testsuite/gcc.c-torture/compile/20001226-1.c)
|
|
|
|
|
apparently never converge. By returning 0 here we could pretend
|
|
|
|
|
to the caller that nothing has changed, but that leaves things
|
|
|
|
|
in an inconsistent state when we get to md_convert_frag. */
|
|
|
|
|
if (n > RELAX_MAX_ADDI)
|
|
|
|
|
{
|
|
|
|
|
as_bad_where (fragp->fr_file, fragp->fr_line,
|
|
|
|
|
_("branch offset out of range\n"));
|
|
|
|
|
as_fatal (_("branch relaxation failed\n"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
/* We cannot handle this case, diagnose overflow later. */
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
if (IS_CBRANCH (subtype))
|
|
|
|
|
fragp->fr_subtype = CBRANCH_SUBTYPE (n);
|
|
|
|
|
else
|
|
|
|
|
fragp->fr_subtype = UBRANCH_SUBTYPE (n);
|
|
|
|
|
|
|
|
|
|
return (nios2_relax_subtype_size (fragp->fr_subtype)
|
|
|
|
|
- nios2_relax_subtype_size (subtype));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* If we got here, it's probably an error. */
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Complete fragp using the data from the relaxation pass. */
|
|
|
|
|
void
|
|
|
|
|
md_convert_frag (bfd *headers ATTRIBUTE_UNUSED, segT segment ATTRIBUTE_UNUSED,
|
|
|
|
|
fragS *fragp)
|
|
|
|
|
{
|
|
|
|
|
char *buffer = fragp->fr_literal + fragp->fr_fix;
|
|
|
|
|
relax_substateT subtype = fragp->fr_subtype;
|
|
|
|
|
int n = SUBTYPE_ADDIS (subtype);
|
|
|
|
|
addressT target = fragp->fr_offset;
|
|
|
|
|
symbolS *symbolp = fragp->fr_symbol;
|
|
|
|
|
offsetT offset;
|
|
|
|
|
unsigned int addend_mask, addi_mask;
|
|
|
|
|
offsetT addend, remainder;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* If we didn't or can't relax, this is a regular branch instruction.
|
|
|
|
|
We just need to generate the fixup for the symbol and offset. */
|
|
|
|
|
if (n == 0)
|
|
|
|
|
{
|
|
|
|
|
fix_new (fragp, fragp->fr_fix, 4, fragp->fr_symbol, fragp->fr_offset, 1,
|
|
|
|
|
BFD_RELOC_16_PCREL);
|
|
|
|
|
fragp->fr_fix += 4;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Replace the cbranch at fr_fix with one that has the opposite condition
|
|
|
|
|
in order to jump around the block of instructions we'll be adding. */
|
|
|
|
|
if (IS_CBRANCH (subtype))
|
|
|
|
|
{
|
|
|
|
|
unsigned int br_opcode;
|
|
|
|
|
int nbytes;
|
|
|
|
|
|
|
|
|
|
/* Account for the nextpc and jmp in the pc-relative case, or the two
|
|
|
|
|
load instructions and jump in the absolute case. */
|
|
|
|
|
if (nios2_as_options.relax == relax_section)
|
|
|
|
|
nbytes = (n + 2) * 4;
|
|
|
|
|
else
|
|
|
|
|
nbytes = 12;
|
|
|
|
|
|
|
|
|
|
br_opcode = md_chars_to_number (buffer, 4);
|
|
|
|
|
switch (br_opcode & OP_MASK_OP)
|
|
|
|
|
{
|
|
|
|
|
case OP_MATCH_BEQ:
|
|
|
|
|
br_opcode = (br_opcode & ~OP_MASK_OP) | OP_MATCH_BNE;
|
|
|
|
|
break;
|
|
|
|
|
case OP_MATCH_BNE:
|
|
|
|
|
br_opcode = (br_opcode & ~OP_MASK_OP) | OP_MATCH_BEQ ;
|
|
|
|
|
break;
|
|
|
|
|
case OP_MATCH_BGE:
|
|
|
|
|
br_opcode = (br_opcode & ~OP_MASK_OP) | OP_MATCH_BLT ;
|
|
|
|
|
break;
|
|
|
|
|
case OP_MATCH_BGEU:
|
|
|
|
|
br_opcode = (br_opcode & ~OP_MASK_OP) | OP_MATCH_BLTU ;
|
|
|
|
|
break;
|
|
|
|
|
case OP_MATCH_BLT:
|
|
|
|
|
br_opcode = (br_opcode & ~OP_MASK_OP) | OP_MATCH_BGE ;
|
|
|
|
|
break;
|
|
|
|
|
case OP_MATCH_BLTU:
|
|
|
|
|
br_opcode = (br_opcode & ~OP_MASK_OP) | OP_MATCH_BGEU ;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
as_bad_where (fragp->fr_file, fragp->fr_line,
|
|
|
|
|
_("expecting conditional branch for relaxation\n"));
|
|
|
|
|
abort ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
br_opcode = br_opcode | (nbytes << OP_SH_IMM16);
|
|
|
|
|
md_number_to_chars (buffer, br_opcode, 4);
|
|
|
|
|
fragp->fr_fix += 4;
|
|
|
|
|
buffer += 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Load at for the PC-relative case. */
|
|
|
|
|
if (nios2_as_options.relax == relax_section)
|
|
|
|
|
{
|
|
|
|
|
/* Insert the nextpc instruction. */
|
|
|
|
|
md_number_to_chars (buffer,
|
|
|
|
|
OP_MATCH_NEXTPC | (AT_REGNUM << OP_SH_RRD), 4);
|
|
|
|
|
fragp->fr_fix += 4;
|
|
|
|
|
buffer += 4;
|
|
|
|
|
|
|
|
|
|
/* We need to know whether the offset is positive or negative. */
|
|
|
|
|
target += S_GET_VALUE (symbolp);
|
|
|
|
|
offset = target - fragp->fr_address - fragp->fr_fix;
|
|
|
|
|
if (offset > 0)
|
|
|
|
|
addend = 32767;
|
|
|
|
|
else
|
|
|
|
|
addend = -32768;
|
|
|
|
|
addend_mask = (((unsigned int)addend) & 0xffff) << OP_SH_IMM16;
|
|
|
|
|
|
|
|
|
|
/* Insert n-1 addi instructions. */
|
|
|
|
|
addi_mask = (OP_MATCH_ADDI
|
|
|
|
|
| (AT_REGNUM << OP_SH_IRD)
|
|
|
|
|
| (AT_REGNUM << OP_SH_IRS));
|
|
|
|
|
for (i = 0; i < n - 1; i ++)
|
|
|
|
|
{
|
|
|
|
|
md_number_to_chars (buffer, addi_mask | addend_mask, 4);
|
|
|
|
|
fragp->fr_fix += 4;
|
|
|
|
|
buffer += 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Insert the last addi instruction to hold the remainder. */
|
|
|
|
|
remainder = offset - addend * (n - 1);
|
|
|
|
|
gas_assert (remainder >= -32768 && remainder <= 32767);
|
|
|
|
|
addend_mask = (((unsigned int)remainder) & 0xffff) << OP_SH_IMM16;
|
|
|
|
|
md_number_to_chars (buffer, addi_mask | addend_mask, 4);
|
|
|
|
|
fragp->fr_fix += 4;
|
|
|
|
|
buffer += 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Load at for the absolute case. */
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
md_number_to_chars (buffer, OP_MATCH_ORHI | 0x00400000, 4);
|
|
|
|
|
fix_new (fragp, fragp->fr_fix, 4, fragp->fr_symbol, fragp->fr_offset,
|
|
|
|
|
0, BFD_RELOC_NIOS2_HI16);
|
|
|
|
|
fragp->fr_fix += 4;
|
|
|
|
|
buffer += 4;
|
|
|
|
|
md_number_to_chars (buffer, OP_MATCH_ORI | 0x08400000, 4);
|
|
|
|
|
fix_new (fragp, fragp->fr_fix, 4, fragp->fr_symbol, fragp->fr_offset,
|
|
|
|
|
0, BFD_RELOC_NIOS2_LO16);
|
|
|
|
|
fragp->fr_fix += 4;
|
|
|
|
|
buffer += 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Insert the jmp instruction. */
|
|
|
|
|
md_number_to_chars (buffer, OP_MATCH_JMP | (AT_REGNUM << OP_SH_RRS), 4);
|
|
|
|
|
fragp->fr_fix += 4;
|
|
|
|
|
buffer += 4;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Fixups and overflow checking. */
|
|
|
|
|
|
|
|
|
|
/* Check a fixup for overflow. */
|
|
|
|
|
static bfd_boolean
|
|
|
|
|
nios2_check_overflow (valueT fixup, reloc_howto_type *howto)
|
|
|
|
|
{
|
|
|
|
|
/* Apply the rightshift before checking for overflow. */
|
|
|
|
|
fixup = ((signed)fixup) >> howto->rightshift;
|
|
|
|
|
|
|
|
|
|
/* Check for overflow - return TRUE if overflow, FALSE if not. */
|
|
|
|
|
switch (howto->complain_on_overflow)
|
|
|
|
|
{
|
|
|
|
|
case complain_overflow_dont:
|
|
|
|
|
break;
|
|
|
|
|
case complain_overflow_bitfield:
|
|
|
|
|
if ((fixup >> howto->bitsize) != 0
|
|
|
|
|
&& ((signed) fixup >> howto->bitsize) != -1)
|
|
|
|
|
return TRUE;
|
|
|
|
|
break;
|
|
|
|
|
case complain_overflow_signed:
|
|
|
|
|
if ((fixup & 0x80000000) > 0)
|
|
|
|
|
{
|
|
|
|
|
/* Check for negative overflow. */
|
|
|
|
|
if ((signed) fixup < ((signed) 0x80000000 >> howto->bitsize))
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Check for positive overflow. */
|
|
|
|
|
if (fixup >= ((unsigned) 1 << (howto->bitsize - 1)))
|
|
|
|
|
return TRUE;
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case complain_overflow_unsigned:
|
|
|
|
|
if ((fixup >> howto->bitsize) != 0)
|
|
|
|
|
return TRUE;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
as_bad (_("error checking for overflow - broken assembler"));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
return FALSE;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Emit diagnostic for fixup overflow. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_diagnose_overflow (valueT fixup, reloc_howto_type *howto,
|
|
|
|
|
fixS *fixP, valueT value)
|
|
|
|
|
{
|
|
|
|
|
if (fixP->fx_r_type == BFD_RELOC_8
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_32)
|
|
|
|
|
/* These relocs are against data, not instructions. */
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("immediate value 0x%x truncated to 0x%x"),
|
|
|
|
|
(unsigned int) fixup,
|
|
|
|
|
(unsigned int) (~(~(valueT) 0 << howto->bitsize) & fixup));
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* What opcode is the instruction? This will determine
|
|
|
|
|
whether we check for overflow in immediate values
|
|
|
|
|
and what error message we get. */
|
|
|
|
|
const struct nios2_opcode *opcode;
|
|
|
|
|
enum overflow_type overflow_msg_type;
|
|
|
|
|
unsigned int range_min;
|
|
|
|
|
unsigned int range_max;
|
|
|
|
|
unsigned int address;
|
|
|
|
|
gas_assert (fixP->fx_size == 4);
|
|
|
|
|
opcode = nios2_find_opcode_hash (value);
|
|
|
|
|
gas_assert (opcode);
|
|
|
|
|
overflow_msg_type = opcode->overflow_msg;
|
|
|
|
|
switch (overflow_msg_type)
|
|
|
|
|
{
|
|
|
|
|
case call_target_overflow:
|
|
|
|
|
range_min
|
|
|
|
|
= ((fixP->fx_frag->fr_address + fixP->fx_where) & 0xf0000000);
|
|
|
|
|
range_max = range_min + 0x0fffffff;
|
|
|
|
|
address = fixup | range_min;
|
|
|
|
|
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("call target address 0x%08x out of range 0x%08x to 0x%08x"),
|
|
|
|
|
address, range_min, range_max);
|
|
|
|
|
break;
|
|
|
|
|
case branch_target_overflow:
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("branch offset %d out of range %d to %d"),
|
|
|
|
|
(int)fixup, -32768, 32767);
|
|
|
|
|
break;
|
|
|
|
|
case address_offset_overflow:
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("%s offset %d out of range %d to %d"),
|
|
|
|
|
opcode->name, (int)fixup, -32768, 32767);
|
|
|
|
|
break;
|
|
|
|
|
case signed_immed16_overflow:
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("immediate value %d out of range %d to %d"),
|
|
|
|
|
(int)fixup, -32768, 32767);
|
|
|
|
|
break;
|
|
|
|
|
case unsigned_immed16_overflow:
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("immediate value %u out of range %u to %u"),
|
|
|
|
|
(unsigned int)fixup, 0, 65535);
|
|
|
|
|
break;
|
|
|
|
|
case unsigned_immed5_overflow:
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("immediate value %u out of range %u to %u"),
|
|
|
|
|
(unsigned int)fixup, 0, 31);
|
|
|
|
|
break;
|
|
|
|
|
case custom_opcode_overflow:
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("custom instruction opcode %u out of range %u to %u"),
|
|
|
|
|
(unsigned int)fixup, 0, 255);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("overflow in immediate argument"));
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Apply a fixup to the object file. */
|
|
|
|
|
void
|
|
|
|
|
md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
/* Assert that the fixup is one we can handle. */
|
|
|
|
|
gas_assert (fixP != NULL && valP != NULL
|
|
|
|
|
&& (fixP->fx_r_type == BFD_RELOC_8
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_32
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_64
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_S16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_U16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_16_PCREL
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_CALL26
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_IMM5
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_CACHE_OPX
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_IMM6
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_IMM8
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_HI16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_LO16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_HIADJ16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_GPREL
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_UJMP
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_CJMP
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_CALLR
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_ALIGN
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_GOT16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_CALL16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_GOTOFF_LO
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_GOTOFF_HA
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_TLS_GD16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_TLS_LDM16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_TLS_LDO16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_TLS_IE16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_TLS_LE16
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_GOTOFF
|
|
|
|
|
|| fixP->fx_r_type == BFD_RELOC_NIOS2_TLS_DTPREL
|
|
|
|
|
/* Add other relocs here as we generate them. */
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
if (fixP->fx_r_type == BFD_RELOC_64)
|
|
|
|
|
{
|
|
|
|
|
/* We may reach here due to .8byte directives, but we never output
|
|
|
|
|
BFD_RELOC_64; it must be resolved. */
|
|
|
|
|
if (fixP->fx_addsy != NULL)
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("cannot create 64-bit relocation"));
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
md_number_to_chars (fixP->fx_frag->fr_literal + fixP->fx_where,
|
|
|
|
|
*valP, 8);
|
|
|
|
|
fixP->fx_done = 1;
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The value passed in valP can be the value of a fully
|
|
|
|
|
resolved expression, or it can be the value of a partially
|
|
|
|
|
resolved expression. In the former case, both fixP->fx_addsy
|
|
|
|
|
and fixP->fx_subsy are NULL, and fixP->fx_offset == *valP, and
|
|
|
|
|
we can fix up the instruction that fixP relates to.
|
|
|
|
|
In the latter case, one or both of fixP->fx_addsy and
|
|
|
|
|
fixP->fx_subsy are not NULL, and fixP->fx_offset may or may not
|
|
|
|
|
equal *valP. We don't need to check for fixP->fx_subsy being null
|
|
|
|
|
because the generic part of the assembler generates an error if
|
|
|
|
|
it is not an absolute symbol. */
|
|
|
|
|
if (fixP->fx_addsy != NULL)
|
|
|
|
|
/* Partially resolved expression. */
|
|
|
|
|
{
|
|
|
|
|
fixP->fx_addnumber = fixP->fx_offset;
|
|
|
|
|
fixP->fx_done = 0;
|
|
|
|
|
|
|
|
|
|
switch (fixP->fx_r_type)
|
|
|
|
|
{
|
|
|
|
|
case BFD_RELOC_NIOS2_TLS_GD16:
|
|
|
|
|
case BFD_RELOC_NIOS2_TLS_LDM16:
|
|
|
|
|
case BFD_RELOC_NIOS2_TLS_LDO16:
|
|
|
|
|
case BFD_RELOC_NIOS2_TLS_IE16:
|
|
|
|
|
case BFD_RELOC_NIOS2_TLS_LE16:
|
|
|
|
|
case BFD_RELOC_NIOS2_TLS_DTPMOD:
|
|
|
|
|
case BFD_RELOC_NIOS2_TLS_DTPREL:
|
|
|
|
|
case BFD_RELOC_NIOS2_TLS_TPREL:
|
|
|
|
|
S_SET_THREAD_LOCAL (fixP->fx_addsy);
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
/* Fully resolved fixup. */
|
|
|
|
|
{
|
|
|
|
|
reloc_howto_type *howto
|
|
|
|
|
= bfd_reloc_type_lookup (stdoutput, fixP->fx_r_type);
|
|
|
|
|
|
|
|
|
|
if (howto == NULL)
|
|
|
|
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|
|
|
|
_("relocation is not supported"));
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
valueT fixup = *valP;
|
|
|
|
|
valueT value;
|
|
|
|
|
char *buf;
|
|
|
|
|
|
|
|
|
|
/* If this is a pc-relative relocation, we need to
|
|
|
|
|
subtract the current offset within the object file
|
|
|
|
|
FIXME : for some reason fixP->fx_pcrel isn't 1 when it should be
|
|
|
|
|
so I'm using the howto structure instead to determine this. */
|
|
|
|
|
if (howto->pc_relative == 1)
|
|
|
|
|
fixup = fixup - (fixP->fx_frag->fr_address + fixP->fx_where + 4);
|
|
|
|
|
|
|
|
|
|
/* Get the instruction or data to be fixed up. */
|
|
|
|
|
buf = fixP->fx_frag->fr_literal + fixP->fx_where;
|
|
|
|
|
value = md_chars_to_number (buf, fixP->fx_size);
|
|
|
|
|
|
|
|
|
|
/* Check for overflow, emitting a diagnostic if necessary. */
|
|
|
|
|
if (nios2_check_overflow (fixup, howto))
|
|
|
|
|
nios2_diagnose_overflow (fixup, howto, fixP, value);
|
|
|
|
|
|
|
|
|
|
/* Apply the right shift. */
|
|
|
|
|
fixup = ((signed)fixup) >> howto->rightshift;
|
|
|
|
|
|
|
|
|
|
/* Truncate the fixup to right size. */
|
|
|
|
|
switch (fixP->fx_r_type)
|
|
|
|
|
{
|
|
|
|
|
case BFD_RELOC_NIOS2_HI16:
|
|
|
|
|
fixup = (fixup >> 16) & 0xFFFF;
|
|
|
|
|
break;
|
|
|
|
|
case BFD_RELOC_NIOS2_LO16:
|
|
|
|
|
fixup = fixup & 0xFFFF;
|
|
|
|
|
break;
|
|
|
|
|
case BFD_RELOC_NIOS2_HIADJ16:
|
2013-06-13 01:11:57 +02:00
|
|
|
|
fixup = ((((fixup >> 16) & 0xFFFF) + ((fixup >> 15) & 0x01))
|
|
|
|
|
& 0xFFFF);
|
2013-02-07 00:22:26 +01:00
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
{
|
|
|
|
|
int n = sizeof (fixup) * 8 - howto->bitsize;
|
|
|
|
|
fixup = (fixup << n) >> n;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Fix up the instruction. */
|
|
|
|
|
value = (value & ~howto->dst_mask) | (fixup << howto->bitpos);
|
|
|
|
|
md_number_to_chars (buf, value, fixP->fx_size);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fixP->fx_done = 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT)
|
|
|
|
|
{
|
|
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
else if (fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
|
|
|
|
|
fixP->fx_done = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Instruction parsing support. */
|
|
|
|
|
|
|
|
|
|
/* Special relocation directive strings. */
|
|
|
|
|
|
|
|
|
|
struct nios2_special_relocS
|
|
|
|
|
{
|
|
|
|
|
const char *string;
|
|
|
|
|
bfd_reloc_code_real_type reloc_type;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct nios2_special_relocS nios2_special_reloc[] = {
|
|
|
|
|
{"%hiadj", BFD_RELOC_NIOS2_HIADJ16},
|
|
|
|
|
{"%hi", BFD_RELOC_NIOS2_HI16},
|
|
|
|
|
{"%lo", BFD_RELOC_NIOS2_LO16},
|
|
|
|
|
{"%gprel", BFD_RELOC_NIOS2_GPREL},
|
|
|
|
|
{"%call", BFD_RELOC_NIOS2_CALL16},
|
|
|
|
|
{"%gotoff_lo", BFD_RELOC_NIOS2_GOTOFF_LO},
|
|
|
|
|
{"%gotoff_hiadj", BFD_RELOC_NIOS2_GOTOFF_HA},
|
|
|
|
|
{"%tls_gd", BFD_RELOC_NIOS2_TLS_GD16},
|
|
|
|
|
{"%tls_ldm", BFD_RELOC_NIOS2_TLS_LDM16},
|
|
|
|
|
{"%tls_ldo", BFD_RELOC_NIOS2_TLS_LDO16},
|
|
|
|
|
{"%tls_ie", BFD_RELOC_NIOS2_TLS_IE16},
|
|
|
|
|
{"%tls_le", BFD_RELOC_NIOS2_TLS_LE16},
|
|
|
|
|
{"%gotoff", BFD_RELOC_NIOS2_GOTOFF},
|
|
|
|
|
{"%got", BFD_RELOC_NIOS2_GOT16}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define NIOS2_NUM_SPECIAL_RELOCS \
|
|
|
|
|
(sizeof(nios2_special_reloc)/sizeof(nios2_special_reloc[0]))
|
|
|
|
|
const int nios2_num_special_relocs = NIOS2_NUM_SPECIAL_RELOCS;
|
|
|
|
|
|
|
|
|
|
/* Creates a new nios2_insn_relocS and returns a pointer to it. */
|
|
|
|
|
static nios2_insn_relocS *
|
|
|
|
|
nios2_insn_reloc_new (bfd_reloc_code_real_type reloc_type, unsigned int pcrel)
|
|
|
|
|
{
|
|
|
|
|
nios2_insn_relocS *retval;
|
|
|
|
|
retval = (nios2_insn_relocS *) malloc (sizeof (nios2_insn_relocS));
|
|
|
|
|
if (retval == NULL)
|
|
|
|
|
{
|
|
|
|
|
as_bad (_("can't create relocation"));
|
|
|
|
|
abort ();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Fill out the fields with default values. */
|
|
|
|
|
retval->reloc_next = NULL;
|
|
|
|
|
retval->reloc_type = reloc_type;
|
|
|
|
|
retval->reloc_pcrel = pcrel;
|
|
|
|
|
return retval;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Frees up memory previously allocated by nios2_insn_reloc_new(). */
|
|
|
|
|
/* FIXME: this is never called; memory leak? */
|
|
|
|
|
#if 0
|
|
|
|
|
static void
|
|
|
|
|
nios2_insn_reloc_destroy (nios2_insn_relocS *reloc)
|
|
|
|
|
{
|
|
|
|
|
gas_assert (reloc != NULL);
|
|
|
|
|
free (reloc);
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
/* The various nios2_assemble_* functions call this
|
|
|
|
|
function to generate an expression from a string representing an expression.
|
|
|
|
|
It then tries to evaluate the expression, and if it can, returns its value.
|
|
|
|
|
If not, it creates a new nios2_insn_relocS and stores the expression and
|
|
|
|
|
reloc_type for future use. */
|
|
|
|
|
static unsigned long
|
|
|
|
|
nios2_assemble_expression (const char *exprstr,
|
|
|
|
|
nios2_insn_infoS *insn,
|
|
|
|
|
nios2_insn_relocS *prev_reloc,
|
|
|
|
|
bfd_reloc_code_real_type reloc_type,
|
|
|
|
|
unsigned int pcrel)
|
|
|
|
|
{
|
|
|
|
|
nios2_insn_relocS *reloc;
|
|
|
|
|
char *saved_line_ptr;
|
|
|
|
|
unsigned short value;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
gas_assert (exprstr != NULL);
|
|
|
|
|
gas_assert (insn != NULL);
|
|
|
|
|
|
|
|
|
|
/* Check for relocation operators.
|
|
|
|
|
Change the relocation type and advance the ptr to the start of
|
|
|
|
|
the expression proper. */
|
|
|
|
|
for (i = 0; i < nios2_num_special_relocs; i++)
|
|
|
|
|
if (strstr (exprstr, nios2_special_reloc[i].string) != NULL)
|
|
|
|
|
{
|
|
|
|
|
reloc_type = nios2_special_reloc[i].reloc_type;
|
|
|
|
|
exprstr += strlen (nios2_special_reloc[i].string) + 1;
|
|
|
|
|
|
|
|
|
|
/* %lo and %hiadj have different meanings for PC-relative
|
|
|
|
|
expressions. */
|
|
|
|
|
if (pcrel)
|
|
|
|
|
{
|
|
|
|
|
if (reloc_type == BFD_RELOC_NIOS2_LO16)
|
|
|
|
|
reloc_type = BFD_RELOC_NIOS2_PCREL_LO;
|
|
|
|
|
if (reloc_type == BFD_RELOC_NIOS2_HIADJ16)
|
|
|
|
|
reloc_type = BFD_RELOC_NIOS2_PCREL_HA;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* We potentially have a relocation. */
|
|
|
|
|
reloc = nios2_insn_reloc_new (reloc_type, pcrel);
|
|
|
|
|
if (prev_reloc != NULL)
|
|
|
|
|
prev_reloc->reloc_next = reloc;
|
|
|
|
|
else
|
|
|
|
|
insn->insn_reloc = reloc;
|
|
|
|
|
|
|
|
|
|
/* Parse the expression string. */
|
|
|
|
|
saved_line_ptr = input_line_pointer;
|
|
|
|
|
input_line_pointer = (char *) exprstr;
|
|
|
|
|
expression (&reloc->reloc_expression);
|
|
|
|
|
input_line_pointer = saved_line_ptr;
|
|
|
|
|
|
|
|
|
|
/* This is redundant as the fixup will put this into
|
|
|
|
|
the instruction, but it is included here so that
|
|
|
|
|
self-test mode (-r) works. */
|
|
|
|
|
value = 0;
|
|
|
|
|
if (nios2_mode == NIOS2_MODE_TEST
|
|
|
|
|
&& reloc->reloc_expression.X_op == O_constant)
|
|
|
|
|
value = reloc->reloc_expression.X_add_number;
|
|
|
|
|
|
|
|
|
|
return (unsigned long) value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Argument assemble functions.
|
|
|
|
|
All take an instruction argument string, and a pointer
|
|
|
|
|
to an instruction opcode. Upon return the insn_opcode
|
|
|
|
|
has the relevant fields filled in to represent the arg
|
|
|
|
|
string. The return value is NULL if successful, or
|
|
|
|
|
an error message if an error was detected.
|
|
|
|
|
|
|
|
|
|
The naming conventions for these functions match the args template
|
|
|
|
|
in the nios2_opcode structure, as documented in include/opcode/nios2.h.
|
|
|
|
|
For example, nios2_assemble_args_dst is used for instructions with
|
|
|
|
|
"d,s,t" args.
|
|
|
|
|
See nios2_arg_info_structs below for the exact correspondence. */
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_dst (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[2] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[3] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
struct nios2_reg *src1 = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
struct nios2_reg *src2 = nios2_reg_lookup (insn_info->insn_tokens[3]);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRD, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
if (src1 == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRS, insn_info->insn_code, src1->index);
|
|
|
|
|
|
|
|
|
|
if (src2 == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[3]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRT, insn_info->insn_code, src2->index);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[4]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_tsi (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL &&
|
|
|
|
|
insn_info->insn_tokens[2] != NULL && insn_info->insn_tokens[3] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
struct nios2_reg *src1 = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
unsigned int src2
|
|
|
|
|
= nios2_assemble_expression (insn_info->insn_tokens[3], insn_info,
|
|
|
|
|
insn_info->insn_reloc, BFD_RELOC_NIOS2_S16,
|
|
|
|
|
0);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (IRT, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
if (src1 == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (IRS, insn_info->insn_code, src1->index);
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, src2);
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[4]);
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_tsu (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[2] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[3] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
struct nios2_reg *src1 = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
unsigned int src2
|
|
|
|
|
= nios2_assemble_expression (insn_info->insn_tokens[3], insn_info,
|
|
|
|
|
insn_info->insn_reloc, BFD_RELOC_NIOS2_U16,
|
|
|
|
|
0);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (IRT, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
if (src1 == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (IRS, insn_info->insn_code, src1->index);
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, src2);
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[4]);
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_sto (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[2] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[3] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
struct nios2_reg *src1 = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
unsigned int src2
|
|
|
|
|
= nios2_assemble_expression (insn_info->insn_tokens[3], insn_info,
|
|
|
|
|
insn_info->insn_reloc, BFD_RELOC_16_PCREL,
|
|
|
|
|
1);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (IRS, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
if (src1 == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (IRT, insn_info->insn_code, src1->index);
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, src2);
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[4]);
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_o (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL)
|
|
|
|
|
{
|
|
|
|
|
unsigned long immed
|
|
|
|
|
= nios2_assemble_expression (insn_info->insn_tokens[1], insn_info,
|
|
|
|
|
insn_info->insn_reloc, BFD_RELOC_16_PCREL,
|
|
|
|
|
1);
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, immed);
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[2]);
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_is (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL && insn_info->insn_tokens[2] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *addr_src = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
unsigned long immed
|
|
|
|
|
= nios2_assemble_expression (insn_info->insn_tokens[1], insn_info,
|
|
|
|
|
insn_info->insn_reloc, BFD_RELOC_NIOS2_S16,
|
|
|
|
|
0);
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, immed);
|
|
|
|
|
|
|
|
|
|
if (addr_src == NULL)
|
|
|
|
|
as_bad (_("unknown base register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRS, insn_info->insn_code, addr_src->index);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[3]);
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_m (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL)
|
|
|
|
|
{
|
|
|
|
|
unsigned long immed
|
|
|
|
|
= nios2_assemble_expression (insn_info->insn_tokens[1], insn_info,
|
|
|
|
|
insn_info->insn_reloc,
|
|
|
|
|
BFD_RELOC_NIOS2_CALL26, 0);
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (IMM26, insn_info->insn_code, immed);
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[2]);
|
|
|
|
|
SET_INSN_FIELD (IMM26, insn_info->insn_code, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_s (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *src = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
if (src == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRS, insn_info->insn_code, src->index);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[2]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_tis (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[2] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[3] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
struct nios2_reg *addr_src = nios2_reg_lookup (insn_info->insn_tokens[3]);
|
|
|
|
|
unsigned long immed
|
|
|
|
|
= nios2_assemble_expression (insn_info->insn_tokens[2], insn_info,
|
|
|
|
|
insn_info->insn_reloc, BFD_RELOC_NIOS2_S16,
|
|
|
|
|
0);
|
|
|
|
|
|
|
|
|
|
if (addr_src == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[3]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRS, insn_info->insn_code, addr_src->index);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRT, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, immed);
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[4]);
|
|
|
|
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_dc (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL && insn_info->insn_tokens[2] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *ctl = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
|
|
|
|
|
if (ctl == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RCTL, insn_info->insn_code, ctl->index);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRD, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[3]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_cs (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL && insn_info->insn_tokens[2] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *ctl = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
struct nios2_reg *src = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
|
|
|
|
|
if (ctl == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else if (ctl->index == 4)
|
|
|
|
|
as_bad (_("ipending control register (ctl4) is read-only\n"));
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RCTL, insn_info->insn_code, ctl->index);
|
|
|
|
|
|
|
|
|
|
if (src == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRS, insn_info->insn_code, src->index);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[3]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-03-12 03:41:26 +01:00
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_ds (nios2_insn_infoS * insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL && insn_info->insn_tokens[2] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
struct nios2_reg *src = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRD, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
if (src == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRS, insn_info->insn_code, src->index);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[3]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2013-02-07 00:22:26 +01:00
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_ldst (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[2] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[3] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[4] != NULL)
|
|
|
|
|
{
|
|
|
|
|
unsigned long custom_n
|
|
|
|
|
= nios2_assemble_expression (insn_info->insn_tokens[1], insn_info,
|
|
|
|
|
insn_info->insn_reloc,
|
|
|
|
|
BFD_RELOC_NIOS2_IMM8, 0);
|
|
|
|
|
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
struct nios2_reg *src1 = nios2_reg_lookup (insn_info->insn_tokens[3]);
|
|
|
|
|
struct nios2_reg *src2 = nios2_reg_lookup (insn_info->insn_tokens[4]);
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (CUSTOM_N, insn_info->insn_code, custom_n);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRD, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
if (src1 == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[3]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRS, insn_info->insn_code, src1->index);
|
|
|
|
|
|
|
|
|
|
if (src2 == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[4]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRT, insn_info->insn_code, src2->index);
|
|
|
|
|
|
|
|
|
|
/* Set or clear the bits to indicate whether coprocessor registers are
|
|
|
|
|
used. */
|
|
|
|
|
if (nios2_coproc_reg (insn_info->insn_tokens[2]))
|
|
|
|
|
SET_INSN_FIELD (CUSTOM_C, insn_info->insn_code, 0);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (CUSTOM_C, insn_info->insn_code, 1);
|
|
|
|
|
|
|
|
|
|
if (nios2_coproc_reg (insn_info->insn_tokens[3]))
|
|
|
|
|
SET_INSN_FIELD (CUSTOM_A, insn_info->insn_code, 0);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (CUSTOM_A, insn_info->insn_code, 1);
|
|
|
|
|
|
|
|
|
|
if (nios2_coproc_reg (insn_info->insn_tokens[4]))
|
|
|
|
|
SET_INSN_FIELD (CUSTOM_B, insn_info->insn_code, 0);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (CUSTOM_B, insn_info->insn_code, 1);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[5]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_none (nios2_insn_infoS *insn_info ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
/* Nothing to do. */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_dsj (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[2] != NULL
|
|
|
|
|
&& insn_info->insn_tokens[3] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
struct nios2_reg *src1 = nios2_reg_lookup (insn_info->insn_tokens[2]);
|
|
|
|
|
|
|
|
|
|
/* A 5-bit constant expression. */
|
|
|
|
|
unsigned int src2 =
|
|
|
|
|
nios2_assemble_expression (insn_info->insn_tokens[3], insn_info,
|
|
|
|
|
insn_info->insn_reloc,
|
|
|
|
|
BFD_RELOC_NIOS2_IMM5, 0);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRD, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
if (src1 == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[2]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRS, insn_info->insn_code, src1->index);
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (IMM5, insn_info->insn_code, src2);
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[4]);
|
|
|
|
|
SET_INSN_FIELD (IMM5, insn_info->insn_code, 0);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_d (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *dst = nios2_reg_lookup (insn_info->insn_tokens[1]);
|
|
|
|
|
|
|
|
|
|
if (dst == NULL)
|
|
|
|
|
as_bad (_("unknown register %s"), insn_info->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
SET_INSN_FIELD (RRD, insn_info->insn_code, dst->index);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[2]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
nios2_assemble_args_b (nios2_insn_infoS *insn_info)
|
|
|
|
|
{
|
|
|
|
|
unsigned int imm5 = 0;
|
|
|
|
|
|
|
|
|
|
if (insn_info->insn_tokens[1] != NULL)
|
|
|
|
|
{
|
|
|
|
|
/* A 5-bit constant expression. */
|
|
|
|
|
imm5 = nios2_assemble_expression (insn_info->insn_tokens[1],
|
|
|
|
|
insn_info, insn_info->insn_reloc,
|
|
|
|
|
BFD_RELOC_NIOS2_IMM5, 0);
|
|
|
|
|
SET_INSN_FIELD (TRAP_IMM5, insn_info->insn_code, imm5);
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[2]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
SET_INSN_FIELD (TRAP_IMM5, insn_info->insn_code, imm5);
|
|
|
|
|
|
|
|
|
|
nios2_check_assembly (insn_info->insn_code, insn_info->insn_tokens[2]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This table associates pointers to functions that parse the arguments to an
|
|
|
|
|
instruction and fill in the relevant fields of the instruction. */
|
|
|
|
|
const nios2_arg_infoS nios2_arg_info_structs[] = {
|
|
|
|
|
/* args, assemble_args_func */
|
|
|
|
|
{"d,s,t", nios2_assemble_args_dst},
|
|
|
|
|
{"d,s,t,E", nios2_assemble_args_dst},
|
|
|
|
|
{"t,s,i", nios2_assemble_args_tsi},
|
|
|
|
|
{"t,s,i,E", nios2_assemble_args_tsi},
|
|
|
|
|
{"t,s,u", nios2_assemble_args_tsu},
|
|
|
|
|
{"t,s,u,E", nios2_assemble_args_tsu},
|
|
|
|
|
{"s,t,o", nios2_assemble_args_sto},
|
|
|
|
|
{"s,t,o,E", nios2_assemble_args_sto},
|
|
|
|
|
{"o", nios2_assemble_args_o},
|
|
|
|
|
{"o,E", nios2_assemble_args_o},
|
|
|
|
|
{"s", nios2_assemble_args_s},
|
|
|
|
|
{"s,E", nios2_assemble_args_s},
|
|
|
|
|
{"", nios2_assemble_args_none},
|
|
|
|
|
{"E", nios2_assemble_args_none},
|
|
|
|
|
{"i(s)", nios2_assemble_args_is},
|
|
|
|
|
{"i(s)E", nios2_assemble_args_is},
|
|
|
|
|
{"m", nios2_assemble_args_m},
|
|
|
|
|
{"m,E", nios2_assemble_args_m},
|
|
|
|
|
{"t,i(s)", nios2_assemble_args_tis},
|
|
|
|
|
{"t,i(s)E", nios2_assemble_args_tis},
|
|
|
|
|
{"d,c", nios2_assemble_args_dc},
|
|
|
|
|
{"d,c,E", nios2_assemble_args_dc},
|
|
|
|
|
{"c,s", nios2_assemble_args_cs},
|
|
|
|
|
{"c,s,E", nios2_assemble_args_cs},
|
2013-03-12 03:41:26 +01:00
|
|
|
|
{"d,s", nios2_assemble_args_ds},
|
|
|
|
|
{"d,s,E", nios2_assemble_args_ds},
|
2013-02-07 00:22:26 +01:00
|
|
|
|
{"l,d,s,t", nios2_assemble_args_ldst},
|
|
|
|
|
{"l,d,s,t,E", nios2_assemble_args_ldst},
|
|
|
|
|
{"d,s,j", nios2_assemble_args_dsj},
|
|
|
|
|
{"d,s,j,E", nios2_assemble_args_dsj},
|
|
|
|
|
{"d", nios2_assemble_args_d},
|
|
|
|
|
{"d,E", nios2_assemble_args_d},
|
|
|
|
|
{"b", nios2_assemble_args_b},
|
|
|
|
|
{"b,E", nios2_assemble_args_b}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define NIOS2_NUM_ARGS \
|
|
|
|
|
((sizeof(nios2_arg_info_structs)/sizeof(nios2_arg_info_structs[0])))
|
|
|
|
|
const int nios2_num_arg_info_structs = NIOS2_NUM_ARGS;
|
|
|
|
|
|
|
|
|
|
/* The function consume_arg takes a pointer into a string
|
|
|
|
|
of instruction tokens (args) and a pointer into a string
|
|
|
|
|
representing the expected sequence of tokens and separators.
|
|
|
|
|
It checks whether the first argument in argstr is of the
|
|
|
|
|
expected type, throwing an error if it is not, and returns
|
|
|
|
|
the pointer argstr. */
|
|
|
|
|
static char *
|
|
|
|
|
nios2_consume_arg (nios2_insn_infoS *insn, char *argstr, const char *parsestr)
|
|
|
|
|
{
|
|
|
|
|
char *temp;
|
|
|
|
|
int regno = -1;
|
|
|
|
|
|
|
|
|
|
switch (*parsestr)
|
|
|
|
|
{
|
|
|
|
|
case 'c':
|
|
|
|
|
if (!nios2_control_register_arg_p (argstr))
|
|
|
|
|
as_bad (_("expecting control register"));
|
|
|
|
|
break;
|
|
|
|
|
case 'd':
|
|
|
|
|
case 's':
|
|
|
|
|
case 't':
|
|
|
|
|
|
|
|
|
|
/* We check to make sure we don't have a control register. */
|
|
|
|
|
if (nios2_control_register_arg_p (argstr))
|
|
|
|
|
as_bad (_("illegal use of control register"));
|
|
|
|
|
|
|
|
|
|
/* And whether coprocessor registers are valid here. */
|
|
|
|
|
if (nios2_coproc_reg (argstr)
|
|
|
|
|
&& insn->insn_nios2_opcode->match != OP_MATCH_CUSTOM)
|
|
|
|
|
as_bad (_("illegal use of coprocessor register\n"));
|
|
|
|
|
|
|
|
|
|
/* Extract a register number if the register is of the
|
|
|
|
|
form r[0-9]+, if it is a normal register, set
|
|
|
|
|
regno to its number (0-31), else set regno to -1. */
|
|
|
|
|
if (argstr[0] == 'r' && ISDIGIT (argstr[1]))
|
|
|
|
|
{
|
|
|
|
|
char *p = argstr;
|
|
|
|
|
|
|
|
|
|
++p;
|
|
|
|
|
regno = 0;
|
|
|
|
|
do
|
|
|
|
|
{
|
|
|
|
|
regno *= 10;
|
|
|
|
|
regno += *p - '0';
|
|
|
|
|
++p;
|
|
|
|
|
}
|
|
|
|
|
while (ISDIGIT (*p));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
regno = -1;
|
|
|
|
|
|
|
|
|
|
/* And whether we are using at. */
|
|
|
|
|
if (!nios2_as_options.noat
|
|
|
|
|
&& (regno == 1 || strprefix (argstr, "at")))
|
|
|
|
|
as_warn (_("Register at (r1) can sometimes be corrupted by assembler "
|
|
|
|
|
"optimizations.\n"
|
|
|
|
|
"Use .set noat to turn off those optimizations (and this "
|
|
|
|
|
"warning)."));
|
|
|
|
|
|
|
|
|
|
/* And whether we are using oci registers. */
|
|
|
|
|
if (!nios2_as_options.nobreak
|
|
|
|
|
&& (regno == 25 || strprefix (argstr, "bt")))
|
2013-10-14 02:42:28 +02:00
|
|
|
|
as_warn (_("The debugger will corrupt bt (r25).\n"
|
|
|
|
|
"If you don't need to debug this "
|
|
|
|
|
"code use .set nobreak to turn off this warning."));
|
2013-02-07 00:22:26 +01:00
|
|
|
|
|
|
|
|
|
if (!nios2_as_options.nobreak
|
2013-10-14 02:42:28 +02:00
|
|
|
|
&& (regno == 30
|
|
|
|
|
|| strprefix (argstr, "ba")
|
|
|
|
|
|| strprefix (argstr, "sstatus")))
|
|
|
|
|
as_warn (_("The debugger will corrupt sstatus/ba (r30).\n"
|
|
|
|
|
"If you don't need to debug this "
|
|
|
|
|
"code use .set nobreak to turn off this warning."));
|
2013-02-07 00:22:26 +01:00
|
|
|
|
break;
|
|
|
|
|
case 'i':
|
|
|
|
|
case 'u':
|
|
|
|
|
if (*argstr == '%')
|
|
|
|
|
{
|
|
|
|
|
if (nios2_special_relocation_p (argstr))
|
|
|
|
|
{
|
|
|
|
|
/* We zap the parentheses because we don't want them confused
|
|
|
|
|
with separators. */
|
|
|
|
|
temp = strchr (argstr, '(');
|
|
|
|
|
if (temp != NULL)
|
|
|
|
|
*temp = ' ';
|
|
|
|
|
temp = strchr (argstr, ')');
|
|
|
|
|
if (temp != NULL)
|
|
|
|
|
*temp = ' ';
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
as_bad (_("badly formed expression near %s"), argstr);
|
|
|
|
|
}
|
|
|
|
|
break;
|
|
|
|
|
case 'm':
|
|
|
|
|
case 'j':
|
|
|
|
|
case 'l':
|
|
|
|
|
case 'b':
|
|
|
|
|
/* We can't have %hi, %lo or %hiadj here. */
|
|
|
|
|
if (*argstr == '%')
|
|
|
|
|
as_bad (_("badly formed expression near %s"), argstr);
|
|
|
|
|
break;
|
2013-03-12 20:18:57 +01:00
|
|
|
|
case 'o':
|
|
|
|
|
break;
|
2013-02-07 00:22:26 +01:00
|
|
|
|
default:
|
2013-03-12 20:18:57 +01:00
|
|
|
|
BAD_CASE (*parsestr);
|
2013-02-07 00:22:26 +01:00
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return argstr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The function consume_separator takes a pointer into a string
|
|
|
|
|
of instruction tokens (args) and a pointer into a string representing
|
|
|
|
|
the expected sequence of tokens and separators. It finds the first
|
|
|
|
|
instance of the character pointed to by separator in argstr, and
|
|
|
|
|
returns a pointer to the next element of argstr, which is the
|
|
|
|
|
following token in the sequence. */
|
|
|
|
|
static char *
|
|
|
|
|
nios2_consume_separator (char *argstr, const char *separator)
|
|
|
|
|
{
|
|
|
|
|
char *p;
|
|
|
|
|
|
|
|
|
|
/* If we have a opcode reg, expr(reg) type instruction, and
|
|
|
|
|
* we are separating the expr from the (reg), we find the last
|
|
|
|
|
* (, just in case the expression has parentheses. */
|
|
|
|
|
|
|
|
|
|
if (*separator == '(')
|
|
|
|
|
p = strrchr (argstr, *separator);
|
|
|
|
|
else
|
|
|
|
|
p = strchr (argstr, *separator);
|
|
|
|
|
|
|
|
|
|
if (p != NULL)
|
|
|
|
|
*p++ = 0;
|
|
|
|
|
else
|
|
|
|
|
as_bad (_("expecting %c near %s"), *separator, argstr);
|
|
|
|
|
return p;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* The principal argument parsing function which takes a string argstr
|
|
|
|
|
representing the instruction arguments for insn, and extracts the argument
|
|
|
|
|
tokens matching parsestr into parsed_args. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_parse_args (nios2_insn_infoS *insn, char *argstr,
|
|
|
|
|
const char *parsestr, char **parsed_args)
|
|
|
|
|
{
|
|
|
|
|
char *p;
|
|
|
|
|
char *end = NULL;
|
|
|
|
|
int i;
|
|
|
|
|
p = argstr;
|
|
|
|
|
i = 0;
|
|
|
|
|
bfd_boolean terminate = FALSE;
|
|
|
|
|
|
|
|
|
|
/* This rest of this function is it too fragile and it mostly works,
|
|
|
|
|
therefore special case this one. */
|
|
|
|
|
if (*parsestr == 0 && argstr != 0)
|
|
|
|
|
{
|
|
|
|
|
as_bad (_("too many arguments"));
|
|
|
|
|
parsed_args[0] = NULL;
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
while (p != NULL && !terminate && i < NIOS2_MAX_INSN_TOKENS)
|
|
|
|
|
{
|
|
|
|
|
parsed_args[i] = nios2_consume_arg (insn, p, parsestr);
|
|
|
|
|
++parsestr;
|
|
|
|
|
if (*parsestr != '\0')
|
|
|
|
|
{
|
|
|
|
|
p = nios2_consume_separator (p, parsestr);
|
|
|
|
|
++parsestr;
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* Check that the argument string has no trailing arguments. */
|
|
|
|
|
/* If we've got a %lo etc relocation, we've zapped the parens with
|
|
|
|
|
spaces. */
|
|
|
|
|
if (nios2_special_relocation_p (p))
|
|
|
|
|
end = strpbrk (p, ",");
|
|
|
|
|
else
|
|
|
|
|
end = strpbrk (p, " ,");
|
|
|
|
|
|
|
|
|
|
if (end != NULL)
|
|
|
|
|
as_bad (_("too many arguments"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (*parsestr == '\0' || (p != NULL && *p == '\0'))
|
|
|
|
|
terminate = TRUE;
|
|
|
|
|
++i;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
parsed_args[i] = NULL;
|
|
|
|
|
|
2013-06-10 03:04:42 +02:00
|
|
|
|
/* The argument to break and trap instructions is optional; complain
|
|
|
|
|
for other cases of missing arguments. */
|
|
|
|
|
if (*parsestr != '\0'
|
|
|
|
|
&& insn->insn_nios2_opcode->match != OP_MATCH_BREAK
|
|
|
|
|
&& insn->insn_nios2_opcode->match != OP_MATCH_TRAP)
|
2013-02-07 00:22:26 +01:00
|
|
|
|
as_bad (_("missing argument"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Support for pseudo-op parsing. These are macro-like opcodes that
|
|
|
|
|
expand into real insns by suitable fiddling with the operands. */
|
|
|
|
|
|
|
|
|
|
/* Append the string modifier to the string contained in the argument at
|
|
|
|
|
parsed_args[ndx]. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_modify_arg (char **parsed_args, const char *modifier,
|
|
|
|
|
int unused ATTRIBUTE_UNUSED, int ndx)
|
|
|
|
|
{
|
|
|
|
|
char *tmp = parsed_args[ndx];
|
|
|
|
|
|
|
|
|
|
parsed_args[ndx]
|
|
|
|
|
= (char *) malloc (strlen (parsed_args[ndx]) + strlen (modifier) + 1);
|
|
|
|
|
strcpy (parsed_args[ndx], tmp);
|
|
|
|
|
strcat (parsed_args[ndx], modifier);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Modify parsed_args[ndx] by negating that argument. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_negate_arg (char **parsed_args, const char *modifier ATTRIBUTE_UNUSED,
|
|
|
|
|
int unused ATTRIBUTE_UNUSED, int ndx)
|
|
|
|
|
{
|
|
|
|
|
char *tmp = parsed_args[ndx];
|
|
|
|
|
|
|
|
|
|
parsed_args[ndx]
|
|
|
|
|
= (char *) malloc (strlen ("~(") + strlen (parsed_args[ndx]) +
|
|
|
|
|
strlen (")+1") + 1);
|
|
|
|
|
|
|
|
|
|
strcpy (parsed_args[ndx], "~(");
|
|
|
|
|
strcat (parsed_args[ndx], tmp);
|
|
|
|
|
strcat (parsed_args[ndx], ")+1");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The function nios2_swap_args swaps the pointers at indices index_1 and
|
|
|
|
|
index_2 in the array parsed_args[] - this is used for operand swapping
|
|
|
|
|
for comparison operations. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_swap_args (char **parsed_args, const char *unused ATTRIBUTE_UNUSED,
|
|
|
|
|
int index_1, int index_2)
|
|
|
|
|
{
|
|
|
|
|
char *tmp;
|
|
|
|
|
gas_assert (index_1 < NIOS2_MAX_INSN_TOKENS
|
|
|
|
|
&& index_2 < NIOS2_MAX_INSN_TOKENS);
|
|
|
|
|
tmp = parsed_args[index_1];
|
|
|
|
|
parsed_args[index_1] = parsed_args[index_2];
|
|
|
|
|
parsed_args[index_2] = tmp;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This function appends the string appnd to the array of strings in
|
|
|
|
|
parsed_args num times starting at index start in the array. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_append_arg (char **parsed_args, const char *appnd, int num,
|
|
|
|
|
int start)
|
|
|
|
|
{
|
|
|
|
|
int i, count;
|
|
|
|
|
char *tmp;
|
|
|
|
|
|
|
|
|
|
gas_assert ((start + num) < NIOS2_MAX_INSN_TOKENS);
|
|
|
|
|
|
|
|
|
|
if (nios2_mode == NIOS2_MODE_TEST)
|
|
|
|
|
tmp = parsed_args[start];
|
|
|
|
|
else
|
|
|
|
|
tmp = NULL;
|
|
|
|
|
|
|
|
|
|
for (i = start, count = num; count > 0; ++i, --count)
|
|
|
|
|
parsed_args[i] = (char *) appnd;
|
|
|
|
|
|
|
|
|
|
gas_assert (i == (start + num));
|
|
|
|
|
parsed_args[i] = tmp;
|
|
|
|
|
parsed_args[i + 1] = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This function inserts the string insert num times in the array
|
|
|
|
|
parsed_args, starting at the index start. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_insert_arg (char **parsed_args, const char *insert, int num,
|
|
|
|
|
int start)
|
|
|
|
|
{
|
|
|
|
|
int i, count;
|
|
|
|
|
|
|
|
|
|
gas_assert ((start + num) < NIOS2_MAX_INSN_TOKENS);
|
|
|
|
|
|
|
|
|
|
/* Move the existing arguments up to create space. */
|
|
|
|
|
for (i = NIOS2_MAX_INSN_TOKENS; i - num >= start; --i)
|
|
|
|
|
parsed_args[i] = parsed_args[i - num];
|
|
|
|
|
|
|
|
|
|
for (i = start, count = num; count > 0; ++i, --count)
|
|
|
|
|
parsed_args[i] = (char *) insert;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Cleanup function to free malloc'ed arg strings. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_free_arg (char **parsed_args, int num ATTRIBUTE_UNUSED, int start)
|
|
|
|
|
{
|
|
|
|
|
if (parsed_args[start])
|
|
|
|
|
{
|
|
|
|
|
free (parsed_args[start]);
|
|
|
|
|
parsed_args[start] = NULL;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This function swaps the pseudo-op for a real op. */
|
|
|
|
|
static nios2_ps_insn_infoS*
|
|
|
|
|
nios2_translate_pseudo_insn (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
|
|
|
|
|
nios2_ps_insn_infoS *ps_insn;
|
|
|
|
|
|
|
|
|
|
/* Find which real insn the pseudo-op transates to and
|
|
|
|
|
switch the insn_info ptr to point to it. */
|
|
|
|
|
ps_insn = nios2_ps_lookup (insn->insn_nios2_opcode->name);
|
|
|
|
|
|
|
|
|
|
if (ps_insn != NULL)
|
|
|
|
|
{
|
|
|
|
|
insn->insn_nios2_opcode = nios2_opcode_lookup (ps_insn->insn);
|
|
|
|
|
insn->insn_tokens[0] = insn->insn_nios2_opcode->name;
|
|
|
|
|
/* Modify the args so they work with the real insn. */
|
|
|
|
|
ps_insn->arg_modifer_func ((char **) insn->insn_tokens,
|
|
|
|
|
ps_insn->arg_modifier, ps_insn->num,
|
|
|
|
|
ps_insn->index);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
/* we cannot recover from this. */
|
|
|
|
|
as_fatal (_("unrecognized pseudo-instruction %s"),
|
|
|
|
|
ps_insn->pseudo_insn);
|
|
|
|
|
return ps_insn;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Invoke the cleanup handler for pseudo-insn ps_insn on insn. */
|
|
|
|
|
static void
|
|
|
|
|
nios2_cleanup_pseudo_insn (nios2_insn_infoS *insn,
|
|
|
|
|
nios2_ps_insn_infoS *ps_insn)
|
|
|
|
|
{
|
|
|
|
|
if (ps_insn->arg_cleanup_func)
|
|
|
|
|
(ps_insn->arg_cleanup_func) ((char **) insn->insn_tokens,
|
|
|
|
|
ps_insn->num, ps_insn->index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const nios2_ps_insn_infoS nios2_ps_insn_info_structs[] = {
|
|
|
|
|
/* pseudo-op, real-op, arg, arg_modifier_func, num, index, arg_cleanup_func */
|
|
|
|
|
{"mov", "add", nios2_append_arg, "zero", 1, 3, NULL},
|
|
|
|
|
{"movi", "addi", nios2_insert_arg, "zero", 1, 2, NULL},
|
|
|
|
|
{"movhi", "orhi", nios2_insert_arg, "zero", 1, 2, NULL},
|
|
|
|
|
{"movui", "ori", nios2_insert_arg, "zero", 1, 2, NULL},
|
|
|
|
|
{"movia", "orhi", nios2_insert_arg, "zero", 1, 2, NULL},
|
|
|
|
|
{"nop", "add", nios2_append_arg, "zero", 3, 1, NULL},
|
|
|
|
|
{"bgt", "blt", nios2_swap_args, "", 1, 2, NULL},
|
|
|
|
|
{"bgtu", "bltu", nios2_swap_args, "", 1, 2, NULL},
|
|
|
|
|
{"ble", "bge", nios2_swap_args, "", 1, 2, NULL},
|
|
|
|
|
{"bleu", "bgeu", nios2_swap_args, "", 1, 2, NULL},
|
|
|
|
|
{"cmpgt", "cmplt", nios2_swap_args, "", 2, 3, NULL},
|
|
|
|
|
{"cmpgtu", "cmpltu", nios2_swap_args, "", 2, 3, NULL},
|
|
|
|
|
{"cmple", "cmpge", nios2_swap_args, "", 2, 3, NULL},
|
|
|
|
|
{"cmpleu", "cmpgeu", nios2_swap_args, "", 2, 3, NULL},
|
|
|
|
|
{"cmpgti", "cmpgei", nios2_modify_arg, "+1", 0, 3, nios2_free_arg},
|
|
|
|
|
{"cmpgtui", "cmpgeui", nios2_modify_arg, "+1", 0, 3, nios2_free_arg},
|
|
|
|
|
{"cmplei", "cmplti", nios2_modify_arg, "+1", 0, 3, nios2_free_arg},
|
|
|
|
|
{"cmpleui", "cmpltui", nios2_modify_arg, "+1", 0, 3, nios2_free_arg},
|
|
|
|
|
{"subi", "addi", nios2_negate_arg, "", 0, 3, nios2_free_arg}
|
|
|
|
|
/* Add further pseudo-ops here. */
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define NIOS2_NUM_PSEUDO_INSNS \
|
|
|
|
|
((sizeof(nios2_ps_insn_info_structs)/ \
|
|
|
|
|
sizeof(nios2_ps_insn_info_structs[0])))
|
|
|
|
|
const int nios2_num_ps_insn_info_structs = NIOS2_NUM_PSEUDO_INSNS;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** Assembler output support. */
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
can_evaluate_expr (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
/* Remove this check for null and the invalid insn "ori r9, 1234" seg faults. */
|
|
|
|
|
if (!insn->insn_reloc)
|
|
|
|
|
/* ??? Ideally we should do something other than as_fatal here as we can
|
|
|
|
|
continue to assemble.
|
|
|
|
|
However this function (actually the output_* functions) should not
|
|
|
|
|
have been called in the first place once an illegal instruction had
|
|
|
|
|
been encountered. */
|
|
|
|
|
as_fatal (_("Invalid instruction encountered, cannot recover. No assembly attempted."));
|
|
|
|
|
|
|
|
|
|
if (insn->insn_reloc->reloc_expression.X_op == O_constant)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
get_expr_value (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
int value = 0;
|
|
|
|
|
|
|
|
|
|
if (insn->insn_reloc->reloc_expression.X_op == O_constant)
|
|
|
|
|
value = insn->insn_reloc->reloc_expression.X_add_number;
|
|
|
|
|
return value;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output a normal instruction. */
|
|
|
|
|
static void
|
|
|
|
|
output_insn (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
char *f;
|
|
|
|
|
nios2_insn_relocS *reloc;
|
|
|
|
|
|
|
|
|
|
f = frag_more (4);
|
|
|
|
|
/* This allocates enough space for the instruction
|
|
|
|
|
and puts it in the current frag. */
|
|
|
|
|
md_number_to_chars (f, insn->insn_code, 4);
|
|
|
|
|
/* Emit debug info. */
|
|
|
|
|
dwarf2_emit_insn (4);
|
|
|
|
|
/* Create any fixups to be acted on later. */
|
|
|
|
|
for (reloc = insn->insn_reloc; reloc != NULL; reloc = reloc->reloc_next)
|
|
|
|
|
fix_new_exp (frag_now, f - frag_now->fr_literal, 4,
|
|
|
|
|
&reloc->reloc_expression, reloc->reloc_pcrel,
|
|
|
|
|
reloc->reloc_type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output an unconditional branch. */
|
|
|
|
|
static void
|
|
|
|
|
output_ubranch (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
nios2_insn_relocS *reloc = insn->insn_reloc;
|
|
|
|
|
|
|
|
|
|
/* If the reloc is NULL, there was an error assembling the branch. */
|
|
|
|
|
if (reloc != NULL)
|
|
|
|
|
{
|
|
|
|
|
symbolS *symp = reloc->reloc_expression.X_add_symbol;
|
|
|
|
|
offsetT offset = reloc->reloc_expression.X_add_number;
|
|
|
|
|
char *f;
|
|
|
|
|
|
|
|
|
|
/* Tag dwarf2 debug info to the address at the start of the insn.
|
|
|
|
|
We must do it before frag_var() below closes off the frag. */
|
|
|
|
|
dwarf2_emit_insn (0);
|
|
|
|
|
|
|
|
|
|
/* We create a machine dependent frag which can grow
|
|
|
|
|
to accommodate the largest possible instruction sequence
|
|
|
|
|
this may generate. */
|
|
|
|
|
f = frag_var (rs_machine_dependent,
|
|
|
|
|
UBRANCH_MAX_SIZE, 4, UBRANCH_SUBTYPE (0),
|
|
|
|
|
symp, offset, NULL);
|
|
|
|
|
|
|
|
|
|
md_number_to_chars (f, insn->insn_code, 4);
|
|
|
|
|
|
|
|
|
|
/* We leave fixup generation to md_convert_frag. */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output a conditional branch. */
|
|
|
|
|
static void
|
|
|
|
|
output_cbranch (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
nios2_insn_relocS *reloc = insn->insn_reloc;
|
|
|
|
|
|
|
|
|
|
/* If the reloc is NULL, there was an error assembling the branch. */
|
|
|
|
|
if (reloc != NULL)
|
|
|
|
|
{
|
|
|
|
|
symbolS *symp = reloc->reloc_expression.X_add_symbol;
|
|
|
|
|
offsetT offset = reloc->reloc_expression.X_add_number;
|
|
|
|
|
char *f;
|
|
|
|
|
|
|
|
|
|
/* Tag dwarf2 debug info to the address at the start of the insn.
|
|
|
|
|
We must do it before frag_var() below closes off the frag. */
|
|
|
|
|
dwarf2_emit_insn (0);
|
|
|
|
|
|
|
|
|
|
/* We create a machine dependent frag which can grow
|
|
|
|
|
to accommodate the largest possible instruction sequence
|
|
|
|
|
this may generate. */
|
|
|
|
|
f = frag_var (rs_machine_dependent,
|
|
|
|
|
CBRANCH_MAX_SIZE, 4, CBRANCH_SUBTYPE (0),
|
|
|
|
|
symp, offset, NULL);
|
|
|
|
|
|
|
|
|
|
md_number_to_chars (f, insn->insn_code, 4);
|
|
|
|
|
|
|
|
|
|
/* We leave fixup generation to md_convert_frag. */
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output a call sequence. Since calls are not pc-relative for NIOS2,
|
|
|
|
|
but are page-relative, we cannot tell at any stage in assembly
|
|
|
|
|
whether a call will be out of range since a section may be linked
|
|
|
|
|
at any address. So if we are relaxing, we convert all call instructions
|
|
|
|
|
to long call sequences, and rely on the linker to relax them back to
|
|
|
|
|
short calls. */
|
|
|
|
|
static void
|
|
|
|
|
output_call (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
/* This allocates enough space for the instruction
|
|
|
|
|
and puts it in the current frag. */
|
|
|
|
|
char *f = frag_more (12);
|
|
|
|
|
nios2_insn_relocS *reloc = insn->insn_reloc;
|
|
|
|
|
|
|
|
|
|
md_number_to_chars (f, OP_MATCH_ORHI | 0x00400000, 4);
|
|
|
|
|
dwarf2_emit_insn (4);
|
|
|
|
|
fix_new_exp (frag_now, f - frag_now->fr_literal, 4,
|
|
|
|
|
&reloc->reloc_expression, 0, BFD_RELOC_NIOS2_HI16);
|
|
|
|
|
md_number_to_chars (f + 4, OP_MATCH_ORI | 0x08400000, 4);
|
|
|
|
|
dwarf2_emit_insn (4);
|
|
|
|
|
fix_new_exp (frag_now, f - frag_now->fr_literal + 4, 4,
|
|
|
|
|
&reloc->reloc_expression, 0, BFD_RELOC_NIOS2_LO16);
|
|
|
|
|
md_number_to_chars (f + 8, OP_MATCH_CALLR | 0x08000000, 4);
|
|
|
|
|
dwarf2_emit_insn (4);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output an addi - will silently convert to
|
|
|
|
|
orhi if rA = r0 and (expr & 0xffff0000) == 0. */
|
|
|
|
|
static void
|
|
|
|
|
output_addi (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
if (can_evaluate_expr (insn))
|
|
|
|
|
{
|
|
|
|
|
int expr_val = get_expr_value (insn);
|
|
|
|
|
if (GET_INSN_FIELD (RRS, insn->insn_code) == 0
|
|
|
|
|
&& (expr_val & 0xffff) == 0
|
|
|
|
|
&& expr_val != 0)
|
|
|
|
|
{
|
|
|
|
|
/* We really want a movhi (orhi) here. */
|
|
|
|
|
insn->insn_code = (insn->insn_code & ~OP_MATCH_ADDI) | OP_MATCH_ORHI;
|
|
|
|
|
insn->insn_reloc->reloc_expression.X_add_number =
|
|
|
|
|
(insn->insn_reloc->reloc_expression.X_add_number >> 16) & 0xffff;
|
|
|
|
|
insn->insn_reloc->reloc_type = BFD_RELOC_NIOS2_U16;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output an instruction. */
|
|
|
|
|
output_insn (insn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
output_andi (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
if (can_evaluate_expr (insn))
|
|
|
|
|
{
|
|
|
|
|
int expr_val = get_expr_value (insn);
|
|
|
|
|
if (expr_val != 0 && (expr_val & 0xffff) == 0)
|
|
|
|
|
{
|
|
|
|
|
/* We really want a movhi (orhi) here. */
|
|
|
|
|
insn->insn_code = (insn->insn_code & ~OP_MATCH_ANDI) | OP_MATCH_ANDHI;
|
|
|
|
|
insn->insn_reloc->reloc_expression.X_add_number =
|
|
|
|
|
(insn->insn_reloc->reloc_expression.X_add_number >> 16) & 0xffff;
|
|
|
|
|
insn->insn_reloc->reloc_type = BFD_RELOC_NIOS2_U16;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output an instruction. */
|
|
|
|
|
output_insn (insn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
output_ori (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
if (can_evaluate_expr (insn))
|
|
|
|
|
{
|
|
|
|
|
int expr_val = get_expr_value (insn);
|
|
|
|
|
if (expr_val != 0 && (expr_val & 0xffff) == 0)
|
|
|
|
|
{
|
|
|
|
|
/* We really want a movhi (orhi) here. */
|
|
|
|
|
insn->insn_code = (insn->insn_code & ~OP_MATCH_ORI) | OP_MATCH_ORHI;
|
|
|
|
|
insn->insn_reloc->reloc_expression.X_add_number =
|
|
|
|
|
(insn->insn_reloc->reloc_expression.X_add_number >> 16) & 0xffff;
|
|
|
|
|
insn->insn_reloc->reloc_type = BFD_RELOC_NIOS2_U16;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output an instruction. */
|
|
|
|
|
output_insn (insn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
output_xori (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
if (can_evaluate_expr (insn))
|
|
|
|
|
{
|
|
|
|
|
int expr_val = get_expr_value (insn);
|
|
|
|
|
if (expr_val != 0 && (expr_val & 0xffff) == 0)
|
|
|
|
|
{
|
|
|
|
|
/* We really want a movhi (orhi) here. */
|
|
|
|
|
insn->insn_code = (insn->insn_code & ~OP_MATCH_XORI) | OP_MATCH_XORHI;
|
|
|
|
|
insn->insn_reloc->reloc_expression.X_add_number =
|
|
|
|
|
(insn->insn_reloc->reloc_expression.X_add_number >> 16) & 0xffff;
|
|
|
|
|
insn->insn_reloc->reloc_type = BFD_RELOC_NIOS2_U16;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Output an instruction. */
|
|
|
|
|
output_insn (insn);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Output a movhi/addi pair for the movia pseudo-op. */
|
|
|
|
|
static void
|
|
|
|
|
output_movia (nios2_insn_infoS *insn)
|
|
|
|
|
{
|
|
|
|
|
/* This allocates enough space for the instruction
|
|
|
|
|
and puts it in the current frag. */
|
|
|
|
|
char *f = frag_more (8);
|
|
|
|
|
nios2_insn_relocS *reloc = insn->insn_reloc;
|
|
|
|
|
unsigned long reg_index = GET_INSN_FIELD (IRT, insn->insn_code);
|
|
|
|
|
|
|
|
|
|
/* If the reloc is NULL, there was an error assembling the movia. */
|
|
|
|
|
if (reloc != NULL)
|
|
|
|
|
{
|
|
|
|
|
md_number_to_chars (f, insn->insn_code, 4);
|
|
|
|
|
dwarf2_emit_insn (4);
|
|
|
|
|
md_number_to_chars (f + 4,
|
|
|
|
|
(OP_MATCH_ADDI | (reg_index << OP_SH_IRT)
|
|
|
|
|
| (reg_index << OP_SH_IRS)),
|
|
|
|
|
4);
|
|
|
|
|
dwarf2_emit_insn (4);
|
|
|
|
|
fix_new (frag_now, f - frag_now->fr_literal, 4,
|
|
|
|
|
reloc->reloc_expression.X_add_symbol,
|
|
|
|
|
reloc->reloc_expression.X_add_number, 0,
|
|
|
|
|
BFD_RELOC_NIOS2_HIADJ16);
|
|
|
|
|
fix_new (frag_now, f + 4 - frag_now->fr_literal, 4,
|
|
|
|
|
reloc->reloc_expression.X_add_symbol,
|
|
|
|
|
reloc->reloc_expression.X_add_number, 0, BFD_RELOC_NIOS2_LO16);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** External interfaces. */
|
|
|
|
|
|
|
|
|
|
/* The following functions are called by machine-independent parts of
|
|
|
|
|
the assembler. */
|
|
|
|
|
int
|
|
|
|
|
md_parse_option (int c, char *arg ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
switch (c)
|
|
|
|
|
{
|
|
|
|
|
case 'r':
|
|
|
|
|
/* Hidden option for self-test mode. */
|
|
|
|
|
nios2_mode = NIOS2_MODE_TEST;
|
|
|
|
|
break;
|
|
|
|
|
case OPTION_RELAX_ALL:
|
|
|
|
|
nios2_as_options.relax = relax_all;
|
|
|
|
|
break;
|
|
|
|
|
case OPTION_NORELAX:
|
|
|
|
|
nios2_as_options.relax = relax_none;
|
|
|
|
|
break;
|
|
|
|
|
case OPTION_RELAX_SECTION:
|
|
|
|
|
nios2_as_options.relax = relax_section;
|
|
|
|
|
break;
|
|
|
|
|
case OPTION_EB:
|
|
|
|
|
target_big_endian = 1;
|
|
|
|
|
break;
|
|
|
|
|
case OPTION_EL:
|
|
|
|
|
target_big_endian = 0;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return 0;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement TARGET_FORMAT. We can choose to be big-endian or
|
|
|
|
|
little-endian at runtime based on a switch. */
|
|
|
|
|
const char *
|
|
|
|
|
nios2_target_format (void)
|
|
|
|
|
{
|
|
|
|
|
return target_big_endian ? "elf32-bignios2" : "elf32-littlenios2";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Machine-dependent usage message. */
|
|
|
|
|
void
|
|
|
|
|
md_show_usage (FILE *stream)
|
|
|
|
|
{
|
|
|
|
|
fprintf (stream, " NIOS2 options:\n"
|
|
|
|
|
" -relax-all replace all branch and call "
|
|
|
|
|
"instructions with jmp and callr sequences\n"
|
|
|
|
|
" -relax-section replace identified out of range "
|
|
|
|
|
"branches with jmp sequences (default)\n"
|
|
|
|
|
" -no-relax do not replace any branches or calls\n"
|
|
|
|
|
" -EB force big-endian byte ordering\n"
|
|
|
|
|
" -EL force little-endian byte ordering\n");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* 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)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
const char *inserted;
|
|
|
|
|
|
|
|
|
|
/* Create and fill a hashtable for the Nios II opcodes, registers and
|
|
|
|
|
arguments. */
|
|
|
|
|
nios2_opcode_hash = hash_new ();
|
|
|
|
|
nios2_reg_hash = hash_new ();
|
|
|
|
|
nios2_arg_hash = hash_new ();
|
|
|
|
|
nios2_ps_hash = hash_new ();
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < NUMOPCODES; ++i)
|
|
|
|
|
{
|
|
|
|
|
inserted
|
|
|
|
|
= hash_insert (nios2_opcode_hash, nios2_opcodes[i].name,
|
|
|
|
|
(PTR) & nios2_opcodes[i]);
|
|
|
|
|
if (inserted != NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
|
|
|
|
|
nios2_opcodes[i].name, inserted);
|
|
|
|
|
/* Probably a memory allocation problem? Give up now. */
|
|
|
|
|
as_fatal (_("Broken assembler. No assembly attempted."));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < nios2_num_regs; ++i)
|
|
|
|
|
{
|
|
|
|
|
inserted
|
|
|
|
|
= hash_insert (nios2_reg_hash, nios2_regs[i].name,
|
|
|
|
|
(PTR) & nios2_regs[i]);
|
|
|
|
|
if (inserted != NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
|
|
|
|
|
nios2_regs[i].name, inserted);
|
|
|
|
|
/* Probably a memory allocation problem? Give up now. */
|
|
|
|
|
as_fatal (_("Broken assembler. No assembly attempted."));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < nios2_num_arg_info_structs; ++i)
|
|
|
|
|
{
|
|
|
|
|
inserted
|
|
|
|
|
= hash_insert (nios2_arg_hash, nios2_arg_info_structs[i].args,
|
|
|
|
|
(PTR) & nios2_arg_info_structs[i]);
|
|
|
|
|
if (inserted != NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
|
|
|
|
|
nios2_arg_info_structs[i].args, inserted);
|
|
|
|
|
/* Probably a memory allocation problem? Give up now. */
|
|
|
|
|
as_fatal (_("Broken assembler. No assembly attempted."));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < nios2_num_ps_insn_info_structs; ++i)
|
|
|
|
|
{
|
|
|
|
|
inserted
|
|
|
|
|
= hash_insert (nios2_ps_hash, nios2_ps_insn_info_structs[i].pseudo_insn,
|
|
|
|
|
(PTR) & nios2_ps_insn_info_structs[i]);
|
|
|
|
|
if (inserted != NULL)
|
|
|
|
|
{
|
|
|
|
|
fprintf (stderr, _("internal error: can't hash `%s': %s\n"),
|
|
|
|
|
nios2_ps_insn_info_structs[i].pseudo_insn, inserted);
|
|
|
|
|
/* Probably a memory allocation problem? Give up now. */
|
|
|
|
|
as_fatal (_("Broken assembler. No assembly attempted."));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Assembler option defaults. */
|
|
|
|
|
nios2_as_options.noat = FALSE;
|
|
|
|
|
nios2_as_options.nobreak = FALSE;
|
|
|
|
|
|
|
|
|
|
/* Debug information is incompatible with relaxation. */
|
|
|
|
|
if (debug_type != DEBUG_UNSPECIFIED)
|
|
|
|
|
nios2_as_options.relax = relax_none;
|
|
|
|
|
|
|
|
|
|
/* Initialize the alignment data. */
|
|
|
|
|
nios2_current_align_seg = now_seg;
|
|
|
|
|
nios2_last_label = NULL;
|
|
|
|
|
nios2_current_align = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/* Assembles a single line of Nios II assembly language. */
|
|
|
|
|
void
|
|
|
|
|
md_assemble (char *op_str)
|
|
|
|
|
{
|
|
|
|
|
char *argstr;
|
|
|
|
|
char *op_strdup = NULL;
|
|
|
|
|
nios2_arg_infoS *arg_info;
|
|
|
|
|
unsigned long saved_pinfo = 0;
|
|
|
|
|
nios2_insn_infoS thisinsn;
|
|
|
|
|
nios2_insn_infoS *insn = &thisinsn;
|
|
|
|
|
|
|
|
|
|
/* Make sure we are aligned on a 4-byte boundary. */
|
|
|
|
|
if (nios2_current_align < 2)
|
|
|
|
|
nios2_align (2, NULL, nios2_last_label);
|
|
|
|
|
else if (nios2_current_align > 2)
|
|
|
|
|
nios2_current_align = 2;
|
|
|
|
|
nios2_last_label = NULL;
|
|
|
|
|
|
|
|
|
|
/* We don't want to clobber to op_str
|
|
|
|
|
because we want to be able to use it in messages. */
|
|
|
|
|
op_strdup = strdup (op_str);
|
|
|
|
|
insn->insn_tokens[0] = strtok (op_strdup, " ");
|
|
|
|
|
argstr = strtok (NULL, "");
|
|
|
|
|
|
|
|
|
|
/* Assemble the opcode. */
|
|
|
|
|
insn->insn_nios2_opcode = nios2_opcode_lookup (insn->insn_tokens[0]);
|
|
|
|
|
insn->insn_reloc = NULL;
|
|
|
|
|
|
|
|
|
|
if (insn->insn_nios2_opcode != NULL)
|
|
|
|
|
{
|
|
|
|
|
nios2_ps_insn_infoS *ps_insn = NULL;
|
|
|
|
|
/* Set the opcode for the instruction. */
|
|
|
|
|
insn->insn_code = insn->insn_nios2_opcode->match;
|
|
|
|
|
|
|
|
|
|
/* Parse the arguments pointed to by argstr. */
|
|
|
|
|
if (nios2_mode == NIOS2_MODE_ASSEMBLE)
|
|
|
|
|
nios2_parse_args (insn, argstr, insn->insn_nios2_opcode->args,
|
|
|
|
|
(char **) &insn->insn_tokens[1]);
|
|
|
|
|
else
|
|
|
|
|
nios2_parse_args (insn, argstr, insn->insn_nios2_opcode->args_test,
|
|
|
|
|
(char **) &insn->insn_tokens[1]);
|
|
|
|
|
|
|
|
|
|
/* We need to preserve the MOVIA macro as this is clobbered by
|
|
|
|
|
translate_pseudo_insn. */
|
|
|
|
|
if (insn->insn_nios2_opcode->pinfo == NIOS2_INSN_MACRO_MOVIA)
|
|
|
|
|
saved_pinfo = NIOS2_INSN_MACRO_MOVIA;
|
|
|
|
|
/* If the instruction is an pseudo-instruction, we want to replace it
|
|
|
|
|
with its real equivalent, and then continue. */
|
|
|
|
|
if ((insn->insn_nios2_opcode->pinfo & NIOS2_INSN_MACRO)
|
|
|
|
|
== NIOS2_INSN_MACRO)
|
|
|
|
|
ps_insn = nios2_translate_pseudo_insn (insn);
|
|
|
|
|
|
|
|
|
|
/* Find the assemble function, and call it. */
|
|
|
|
|
arg_info = nios2_arg_lookup (insn->insn_nios2_opcode->args);
|
|
|
|
|
if (arg_info != NULL)
|
|
|
|
|
{
|
|
|
|
|
arg_info->assemble_args_func (insn);
|
|
|
|
|
|
|
|
|
|
if (nios2_as_options.relax != relax_none
|
|
|
|
|
&& !nios2_as_options.noat
|
|
|
|
|
&& insn->insn_nios2_opcode->pinfo & NIOS2_INSN_UBRANCH)
|
|
|
|
|
output_ubranch (insn);
|
|
|
|
|
else if (nios2_as_options.relax != relax_none
|
|
|
|
|
&& !nios2_as_options.noat
|
|
|
|
|
&& insn->insn_nios2_opcode->pinfo & NIOS2_INSN_CBRANCH)
|
|
|
|
|
output_cbranch (insn);
|
|
|
|
|
else if (nios2_as_options.relax == relax_all
|
|
|
|
|
&& !nios2_as_options.noat
|
|
|
|
|
&& insn->insn_nios2_opcode->pinfo & NIOS2_INSN_CALL
|
|
|
|
|
&& insn->insn_reloc
|
|
|
|
|
&& insn->insn_reloc->reloc_type == BFD_RELOC_NIOS2_CALL26)
|
|
|
|
|
output_call (insn);
|
|
|
|
|
else if (insn->insn_nios2_opcode->pinfo & NIOS2_INSN_ANDI)
|
|
|
|
|
output_andi (insn);
|
|
|
|
|
else if (insn->insn_nios2_opcode->pinfo & NIOS2_INSN_ORI)
|
|
|
|
|
output_ori (insn);
|
|
|
|
|
else if (insn->insn_nios2_opcode->pinfo & NIOS2_INSN_XORI)
|
|
|
|
|
output_xori (insn);
|
|
|
|
|
else if (insn->insn_nios2_opcode->pinfo & NIOS2_INSN_ADDI)
|
|
|
|
|
output_addi (insn);
|
|
|
|
|
else if (saved_pinfo == NIOS2_INSN_MACRO_MOVIA)
|
|
|
|
|
output_movia (insn);
|
|
|
|
|
else
|
|
|
|
|
output_insn (insn);
|
|
|
|
|
if (ps_insn)
|
|
|
|
|
nios2_cleanup_pseudo_insn (insn, ps_insn);
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
/* The assembler is broken. */
|
|
|
|
|
fprintf (stderr,
|
|
|
|
|
_("internal error: %s is not a valid argument syntax\n"),
|
|
|
|
|
insn->insn_nios2_opcode->args);
|
|
|
|
|
/* Probably a memory allocation problem. Give up now. */
|
|
|
|
|
as_fatal (_("Broken assembler. No assembly attempted."));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
/* Unrecognised instruction - error. */
|
|
|
|
|
as_bad (_("unrecognised instruction %s"), insn->insn_tokens[0]);
|
|
|
|
|
|
|
|
|
|
/* Don't leak memory. */
|
|
|
|
|
free (op_strdup);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Round up section size. */
|
|
|
|
|
valueT
|
|
|
|
|
md_section_align (asection *seg ATTRIBUTE_UNUSED, valueT size)
|
|
|
|
|
{
|
|
|
|
|
/* I think byte alignment is fine here. */
|
|
|
|
|
return size;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement TC_FORCE_RELOCATION. */
|
|
|
|
|
int
|
|
|
|
|
nios2_force_relocation (fixS *fixp)
|
|
|
|
|
{
|
|
|
|
|
if (fixp->fx_r_type == BFD_RELOC_VTABLE_INHERIT
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_VTABLE_ENTRY
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_ALIGN)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
return generic_force_reloc (fixp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement tc_fix_adjustable. */
|
|
|
|
|
int
|
|
|
|
|
nios2_fix_adjustable (fixS *fixp)
|
|
|
|
|
{
|
|
|
|
|
if (fixp->fx_addsy == NULL)
|
|
|
|
|
return 1;
|
|
|
|
|
|
|
|
|
|
#ifdef OBJ_ELF
|
|
|
|
|
/* Prevent all adjustments to global symbols. */
|
|
|
|
|
if (OUTPUT_FLAVOR == bfd_target_elf_flavour
|
|
|
|
|
&& (S_IS_EXTERNAL (fixp->fx_addsy) || S_IS_WEAK (fixp->fx_addsy)))
|
|
|
|
|
return 0;
|
|
|
|
|
#endif
|
|
|
|
|
if (fixp->fx_r_type == BFD_RELOC_VTABLE_INHERIT
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_VTABLE_ENTRY)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/* Preserve relocations against symbols with function type. */
|
|
|
|
|
if (symbol_get_bfdsym (fixp->fx_addsy)->flags & BSF_FUNCTION)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
/* Don't allow symbols to be discarded on GOT related relocs. */
|
|
|
|
|
if (fixp->fx_r_type == BFD_RELOC_NIOS2_GOT16
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_CALL16
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_GOTOFF_LO
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_GOTOFF_HA
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_TLS_GD16
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_TLS_LDM16
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_TLS_LDO16
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_TLS_IE16
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_TLS_LE16
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_TLS_DTPMOD
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_TLS_DTPREL
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_TLS_TPREL
|
|
|
|
|
|| fixp->fx_r_type == BFD_RELOC_NIOS2_GOTOFF)
|
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement tc_frob_symbol. This is called in adjust_reloc_syms;
|
|
|
|
|
it is used to remove *ABS* references from the symbol table. */
|
|
|
|
|
int
|
|
|
|
|
nios2_frob_symbol (symbolS *symp)
|
|
|
|
|
{
|
|
|
|
|
if ((OUTPUT_FLAVOR == bfd_target_elf_flavour
|
|
|
|
|
&& symp == section_symbol (absolute_section))
|
|
|
|
|
|| !S_IS_DEFINED (symp))
|
|
|
|
|
return 1;
|
|
|
|
|
else
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* The function tc_gen_reloc creates a relocation structure for the
|
|
|
|
|
fixup fixp, and returns a pointer to it. This structure is passed
|
|
|
|
|
to bfd_install_relocation so that it can be written to the object
|
|
|
|
|
file for linking. */
|
|
|
|
|
arelent *
|
|
|
|
|
tc_gen_reloc (asection *section ATTRIBUTE_UNUSED, fixS *fixp)
|
|
|
|
|
{
|
|
|
|
|
arelent *reloc = (arelent *) xmalloc (sizeof (arelent));
|
|
|
|
|
reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *));
|
|
|
|
|
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
|
|
|
|
|
|
|
|
|
|
reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
|
|
|
|
|
reloc->addend = fixp->fx_offset; /* fixp->fx_addnumber; */
|
|
|
|
|
|
|
|
|
|
if (fixp->fx_pcrel)
|
|
|
|
|
{
|
|
|
|
|
switch (fixp->fx_r_type)
|
|
|
|
|
{
|
|
|
|
|
case BFD_RELOC_16:
|
|
|
|
|
fixp->fx_r_type = BFD_RELOC_16_PCREL;
|
|
|
|
|
break;
|
|
|
|
|
case BFD_RELOC_NIOS2_LO16:
|
|
|
|
|
fixp->fx_r_type = BFD_RELOC_NIOS2_PCREL_LO;
|
|
|
|
|
break;
|
|
|
|
|
case BFD_RELOC_NIOS2_HIADJ16:
|
|
|
|
|
fixp->fx_r_type = BFD_RELOC_NIOS2_PCREL_HA;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type);
|
|
|
|
|
if (reloc->howto == NULL)
|
|
|
|
|
{
|
|
|
|
|
as_bad_where (fixp->fx_file, fixp->fx_line,
|
|
|
|
|
_("can't represent relocation type %s"),
|
|
|
|
|
bfd_get_reloc_code_name (fixp->fx_r_type));
|
|
|
|
|
|
|
|
|
|
/* Set howto to a garbage value so that we can keep going. */
|
|
|
|
|
reloc->howto = bfd_reloc_type_lookup (stdoutput, BFD_RELOC_32);
|
|
|
|
|
gas_assert (reloc->howto != NULL);
|
|
|
|
|
}
|
|
|
|
|
return reloc;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
long
|
|
|
|
|
md_pcrel_from (fixS *fixP ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Called just before the assembler exits. */
|
|
|
|
|
void
|
|
|
|
|
md_end ()
|
|
|
|
|
{
|
|
|
|
|
/* FIXME - not yet implemented */
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Under ELF we need to default _GLOBAL_OFFSET_TABLE.
|
|
|
|
|
Otherwise we have no need to default values of symbols. */
|
|
|
|
|
symbolS *
|
|
|
|
|
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
#ifdef OBJ_ELF
|
|
|
|
|
if (name[0] == '_' && name[1] == 'G'
|
|
|
|
|
&& strcmp (name, GLOBAL_OFFSET_TABLE_NAME) == 0)
|
|
|
|
|
{
|
|
|
|
|
if (!GOT_symbol)
|
|
|
|
|
{
|
|
|
|
|
if (symbol_find (name))
|
|
|
|
|
as_bad ("GOT already in the symbol table");
|
|
|
|
|
|
|
|
|
|
GOT_symbol = symbol_new (name, undefined_section,
|
|
|
|
|
(valueT) 0, &zero_address_frag);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return GOT_symbol;
|
|
|
|
|
}
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement tc_frob_label. */
|
|
|
|
|
void
|
|
|
|
|
nios2_frob_label (symbolS *lab)
|
|
|
|
|
{
|
|
|
|
|
/* Emit dwarf information. */
|
|
|
|
|
dwarf2_emit_label (lab);
|
|
|
|
|
|
|
|
|
|
/* Update the label's address with the current output pointer. */
|
|
|
|
|
symbol_set_frag (lab, frag_now);
|
|
|
|
|
S_SET_VALUE (lab, (valueT) frag_now_fix ());
|
|
|
|
|
|
|
|
|
|
/* Record this label for future adjustment after we find out what
|
|
|
|
|
kind of data it references, and the required alignment therewith. */
|
|
|
|
|
nios2_last_label = lab;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement md_cons_align. */
|
|
|
|
|
void
|
|
|
|
|
nios2_cons_align (int size)
|
|
|
|
|
{
|
|
|
|
|
int log_size = 0;
|
|
|
|
|
const char *pfill = NULL;
|
|
|
|
|
|
|
|
|
|
while ((size >>= 1) != 0)
|
|
|
|
|
++log_size;
|
|
|
|
|
|
|
|
|
|
if (subseg_text_p (now_seg))
|
|
|
|
|
pfill = (const char *) &nop;
|
|
|
|
|
else
|
|
|
|
|
pfill = NULL;
|
|
|
|
|
|
|
|
|
|
if (nios2_auto_align_on)
|
|
|
|
|
nios2_align (log_size, pfill, NULL);
|
|
|
|
|
|
|
|
|
|
nios2_last_label = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Map 's' to SHF_NIOS2_GPREL. */
|
|
|
|
|
/* This is from the Alpha code tc-alpha.c. */
|
|
|
|
|
int
|
|
|
|
|
nios2_elf_section_letter (int letter, char **ptr_msg)
|
|
|
|
|
{
|
|
|
|
|
if (letter == 's')
|
|
|
|
|
return SHF_NIOS2_GPREL;
|
|
|
|
|
|
|
|
|
|
*ptr_msg = _("Bad .section directive: want a,s,w,x,M,S,G,T in string");
|
|
|
|
|
return -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Map SHF_ALPHA_GPREL to SEC_SMALL_DATA. */
|
|
|
|
|
/* This is from the Alpha code tc-alpha.c. */
|
|
|
|
|
flagword
|
|
|
|
|
nios2_elf_section_flags (flagword flags, int attr, int type ATTRIBUTE_UNUSED)
|
|
|
|
|
{
|
|
|
|
|
if (attr & SHF_NIOS2_GPREL)
|
|
|
|
|
flags |= SEC_SMALL_DATA;
|
|
|
|
|
return flags;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement TC_PARSE_CONS_EXPRESSION to handle %tls_ldo(...) */
|
|
|
|
|
static int nios2_tls_ldo_reloc;
|
|
|
|
|
|
|
|
|
|
void
|
|
|
|
|
nios2_cons (expressionS *exp, int size)
|
|
|
|
|
{
|
|
|
|
|
nios2_tls_ldo_reloc = 0;
|
|
|
|
|
|
|
|
|
|
SKIP_WHITESPACE ();
|
|
|
|
|
if (input_line_pointer[0] == '%')
|
|
|
|
|
{
|
|
|
|
|
if (strprefix (input_line_pointer + 1, "tls_ldo"))
|
|
|
|
|
{
|
|
|
|
|
if (size != 4)
|
|
|
|
|
as_bad (_("Illegal operands: %%tls_ldo in %d-byte data field"),
|
|
|
|
|
size);
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
input_line_pointer += 8;
|
|
|
|
|
nios2_tls_ldo_reloc = 1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (nios2_tls_ldo_reloc)
|
|
|
|
|
{
|
|
|
|
|
SKIP_WHITESPACE ();
|
|
|
|
|
if (input_line_pointer[0] != '(')
|
|
|
|
|
as_bad (_("Illegal operands: %%tls_ldo requires arguments in ()"));
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
int c;
|
|
|
|
|
char *end = ++input_line_pointer;
|
|
|
|
|
int npar = 0;
|
|
|
|
|
|
|
|
|
|
for (c = *end; !is_end_of_line[c]; end++, c = *end)
|
|
|
|
|
if (c == '(')
|
|
|
|
|
npar++;
|
|
|
|
|
else if (c == ')')
|
|
|
|
|
{
|
|
|
|
|
if (!npar)
|
|
|
|
|
break;
|
|
|
|
|
npar--;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (c != ')')
|
|
|
|
|
as_bad (_("Illegal operands: %%tls_ldo requires arguments in ()"));
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
*end = '\0';
|
|
|
|
|
expression (exp);
|
|
|
|
|
*end = c;
|
|
|
|
|
if (input_line_pointer != end)
|
|
|
|
|
as_bad (_("Illegal operands: %%tls_ldo requires arguments in ()"));
|
|
|
|
|
else
|
|
|
|
|
{
|
|
|
|
|
input_line_pointer++;
|
|
|
|
|
SKIP_WHITESPACE ();
|
|
|
|
|
c = *input_line_pointer;
|
|
|
|
|
if (! is_end_of_line[c] && c != ',')
|
|
|
|
|
as_bad (_("Illegal operands: garbage after %%tls_ldo()"));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if (!nios2_tls_ldo_reloc)
|
|
|
|
|
expression (exp);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement TC_CONS_FIX_NEW. */
|
|
|
|
|
void
|
|
|
|
|
nios2_cons_fix_new (fragS *frag, int where, unsigned int nbytes,
|
|
|
|
|
expressionS *exp)
|
|
|
|
|
{
|
|
|
|
|
bfd_reloc_code_real_type r;
|
|
|
|
|
|
|
|
|
|
r = (nbytes == 1 ? BFD_RELOC_8
|
|
|
|
|
: (nbytes == 2 ? BFD_RELOC_16
|
|
|
|
|
: (nbytes == 4 ? BFD_RELOC_32 : BFD_RELOC_64)));
|
|
|
|
|
|
|
|
|
|
if (nios2_tls_ldo_reloc)
|
|
|
|
|
r = BFD_RELOC_NIOS2_TLS_DTPREL;
|
|
|
|
|
|
|
|
|
|
fix_new_exp (frag, where, (int) nbytes, exp, 0, r);
|
|
|
|
|
nios2_tls_ldo_reloc = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement HANDLE_ALIGN. */
|
|
|
|
|
void
|
|
|
|
|
nios2_handle_align (fragS *fragp)
|
|
|
|
|
{
|
|
|
|
|
/* If we are expecting to relax in the linker, then we must output a
|
|
|
|
|
relocation to tell the linker we are aligning code. */
|
|
|
|
|
if (nios2_as_options.relax == relax_all
|
|
|
|
|
&& (fragp->fr_type == rs_align || fragp->fr_type == rs_align_code)
|
|
|
|
|
&& fragp->fr_address + fragp->fr_fix > 0
|
|
|
|
|
&& fragp->fr_offset > 1
|
|
|
|
|
&& now_seg != bss_section)
|
|
|
|
|
fix_new (fragp, fragp->fr_fix, 0, &abs_symbol, fragp->fr_offset, 0,
|
|
|
|
|
BFD_RELOC_NIOS2_ALIGN);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement tc_regname_to_dw2regnum, to convert REGNAME to a DWARF-2
|
|
|
|
|
register number. */
|
|
|
|
|
int
|
|
|
|
|
nios2_regname_to_dw2regnum (char *regname)
|
|
|
|
|
{
|
|
|
|
|
struct nios2_reg *r = nios2_reg_lookup (regname);
|
|
|
|
|
if (r == NULL)
|
|
|
|
|
return -1;
|
|
|
|
|
return r->index;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Implement tc_cfi_frame_initial_instructions, to initialize the DWARF-2
|
|
|
|
|
unwind information for this procedure. */
|
|
|
|
|
void
|
|
|
|
|
nios2_frame_initial_instructions (void)
|
|
|
|
|
{
|
|
|
|
|
cfi_add_CFA_def_cfa (27, 0);
|
|
|
|
|
}
|