1956 lines
53 KiB
C
1956 lines
53 KiB
C
|
/* TI PRU assembler.
|
|||
|
Copyright (C) 2014-2022 Free Software Foundation, Inc.
|
|||
|
Contributed by Dimitar Dimitrov <dimitar@dinux.eu>
|
|||
|
Based on tc-nios2.c
|
|||
|
|
|||
|
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 <stdint.h>
|
|||
|
#include "opcode/pru.h"
|
|||
|
#include "elf/pru.h"
|
|||
|
#include "tc-pru.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. */
|
|||
|
#error "OBJ_ELF not defined"
|
|||
|
#endif
|
|||
|
|
|||
|
/* 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";
|
|||
|
|
|||
|
/* Machine-dependent command-line options. */
|
|||
|
|
|||
|
struct pru_opt_s
|
|||
|
{
|
|||
|
/* -mno-link-relax / -mlink-relax: generate (or not)
|
|||
|
relocations for linker relaxation. */
|
|||
|
bool link_relax;
|
|||
|
|
|||
|
/* -mno-warn-regname-label: do not output a warning that a label name
|
|||
|
matches a register name. */
|
|||
|
bool warn_regname_label;
|
|||
|
};
|
|||
|
|
|||
|
static struct pru_opt_s pru_opt = { true, true };
|
|||
|
|
|||
|
const char *md_shortopts = "r";
|
|||
|
|
|||
|
enum options
|
|||
|
{
|
|||
|
OPTION_LINK_RELAX = OPTION_MD_BASE + 1,
|
|||
|
OPTION_NO_LINK_RELAX,
|
|||
|
OPTION_NO_WARN_REGNAME_LABEL,
|
|||
|
};
|
|||
|
|
|||
|
struct option md_longopts[] = {
|
|||
|
{ "mlink-relax", no_argument, NULL, OPTION_LINK_RELAX },
|
|||
|
{ "mno-link-relax", no_argument, NULL, OPTION_NO_LINK_RELAX },
|
|||
|
{ "mno-warn-regname-label", no_argument, NULL,
|
|||
|
OPTION_NO_WARN_REGNAME_LABEL },
|
|||
|
{ NULL, no_argument, NULL, 0 }
|
|||
|
};
|
|||
|
|
|||
|
size_t md_longopts_size = sizeof (md_longopts);
|
|||
|
|
|||
|
typedef struct pru_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 pru_insn_reloc *reloc_next;
|
|||
|
} pru_insn_relocS;
|
|||
|
|
|||
|
/* This struct is used to hold state when assembling instructions. */
|
|||
|
typedef struct pru_insn_info
|
|||
|
{
|
|||
|
/* Assembled instruction. */
|
|||
|
unsigned long insn_code;
|
|||
|
/* Used for assembling LDI32. */
|
|||
|
unsigned long ldi32_imm32;
|
|||
|
|
|||
|
/* Pointer to the relevant bit of the opcode table. */
|
|||
|
const struct pru_opcode *insn_pru_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[PRU_MAX_INSN_TOKENS + 1 + 1];
|
|||
|
|
|||
|
/* This holds information used to generate fixups
|
|||
|
and eventually relocations if it is not null. */
|
|||
|
pru_insn_relocS *insn_reloc;
|
|||
|
} pru_insn_infoS;
|
|||
|
|
|||
|
/* Opcode hash table. */
|
|||
|
static htab_t pru_opcode_hash = NULL;
|
|||
|
#define pru_opcode_lookup(NAME) \
|
|||
|
((struct pru_opcode *) str_hash_find (pru_opcode_hash, (NAME)))
|
|||
|
|
|||
|
/* Register hash table. */
|
|||
|
static htab_t pru_reg_hash = NULL;
|
|||
|
#define pru_reg_lookup(NAME) \
|
|||
|
((struct pru_reg *) str_hash_find (pru_reg_hash, (NAME)))
|
|||
|
|
|||
|
/* The known current alignment of the current section. */
|
|||
|
static int pru_current_align;
|
|||
|
static segT pru_current_align_seg;
|
|||
|
|
|||
|
static int pru_auto_align_on = 1;
|
|||
|
|
|||
|
/* The last seen label in the current section. This is used to auto-align
|
|||
|
labels preceding instructions. */
|
|||
|
static symbolS *pru_last_label;
|
|||
|
|
|||
|
|
|||
|
/** 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, 4 or 8. */
|
|||
|
static uint64_t
|
|||
|
md_chars_to_number (char *buf, int n)
|
|||
|
{
|
|||
|
int i;
|
|||
|
uint64_t val;
|
|||
|
|
|||
|
gas_assert (n == 1 || n == 2 || n == 4 || n == 8);
|
|||
|
|
|||
|
val = 0;
|
|||
|
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);
|
|||
|
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. */
|
|||
|
const char *
|
|||
|
md_atof (int type, char *litP, int *sizeP)
|
|||
|
{
|
|||
|
return ieee_md_atof (type, litP, sizeP, false);
|
|||
|
}
|
|||
|
|
|||
|
/* Return true if STR starts with PREFIX, which should be a string literal. */
|
|||
|
#define strprefix(STR, PREFIX) \
|
|||
|
(strncmp ((STR), PREFIX, strlen (PREFIX)) == 0)
|
|||
|
|
|||
|
/* nop fill pattern for text section. */
|
|||
|
static char const nop[4] = { 0xe0, 0xe0, 0xe0, 0x12 };
|
|||
|
|
|||
|
/* Handles all machine-dependent alignment needs. */
|
|||
|
static void
|
|||
|
pru_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 = (pru_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 (pru_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)
|
|||
|
pru_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
|
|||
|
{
|
|||
|
PRU_MODE_ASSEMBLE, /* Ordinary operation. */
|
|||
|
PRU_MODE_TEST /* Hidden mode used for self testing. */
|
|||
|
} PRU_MODE;
|
|||
|
|
|||
|
static PRU_MODE pru_mode = PRU_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
|
|||
|
pru_check_assembly (unsigned int opcode, const char *exp_opcode)
|
|||
|
{
|
|||
|
if (pru_mode == PRU_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_pru_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;
|
|||
|
pru_last_label = NULL;
|
|||
|
}
|
|||
|
|
|||
|
if (align != 0)
|
|||
|
{
|
|||
|
pru_auto_align_on = 1;
|
|||
|
pru_align (align, pfill, pru_last_label);
|
|||
|
pru_last_label = NULL;
|
|||
|
}
|
|||
|
else
|
|||
|
pru_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_pru_text (int i)
|
|||
|
{
|
|||
|
s_text (i);
|
|||
|
pru_last_label = NULL;
|
|||
|
pru_current_align = 0;
|
|||
|
pru_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_pru_data (int i)
|
|||
|
{
|
|||
|
s_data (i);
|
|||
|
pru_last_label = NULL;
|
|||
|
pru_current_align = 0;
|
|||
|
pru_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_pru_section (int ignore)
|
|||
|
{
|
|||
|
obj_elf_section (ignore);
|
|||
|
pru_last_label = NULL;
|
|||
|
pru_current_align = 0;
|
|||
|
pru_current_align_seg = now_seg;
|
|||
|
}
|
|||
|
|
|||
|
/* Explicitly unaligned cons. */
|
|||
|
static void
|
|||
|
s_pru_ucons (int nbytes)
|
|||
|
{
|
|||
|
int hold;
|
|||
|
hold = pru_auto_align_on;
|
|||
|
pru_auto_align_on = 0;
|
|||
|
cons (nbytes);
|
|||
|
pru_auto_align_on = hold;
|
|||
|
}
|
|||
|
|
|||
|
/* .set sets assembler options. */
|
|||
|
static void
|
|||
|
s_pru_set (int equiv)
|
|||
|
{
|
|||
|
char *save = input_line_pointer;
|
|||
|
char *directive;
|
|||
|
char delim = get_symbol_name (&directive);
|
|||
|
char *endline = input_line_pointer;
|
|||
|
|
|||
|
(void) restore_line_pointer (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])
|
|||
|
{
|
|||
|
bool done = true;
|
|||
|
*endline = 0;
|
|||
|
|
|||
|
if (!strcmp (directive, "no_warn_regname_label"))
|
|||
|
pru_opt.warn_regname_label = false;
|
|||
|
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 = save;
|
|||
|
s_set (equiv);
|
|||
|
}
|
|||
|
|
|||
|
/* Machine-dependent assembler directives.
|
|||
|
Format of each entry is:
|
|||
|
{ "directive", handler_func, param } */
|
|||
|
const pseudo_typeS md_pseudo_table[] = {
|
|||
|
{"align", s_pru_align, 0},
|
|||
|
{"text", s_pru_text, 0},
|
|||
|
{"data", s_pru_data, 0},
|
|||
|
{"section", s_pru_section, 0},
|
|||
|
{"section.s", s_pru_section, 0},
|
|||
|
{"sect", s_pru_section, 0},
|
|||
|
{"sect.s", s_pru_section, 0},
|
|||
|
/* .dword and .half are included for compatibility with MIPS. */
|
|||
|
{"dword", cons, 8},
|
|||
|
{"half", cons, 2},
|
|||
|
/* PRU native word size is 4 bytes, so we override
|
|||
|
the GAS default of 2. */
|
|||
|
{"word", cons, 4},
|
|||
|
/* Explicitly unaligned directives. */
|
|||
|
{"2byte", s_pru_ucons, 2},
|
|||
|
{"4byte", s_pru_ucons, 4},
|
|||
|
{"8byte", s_pru_ucons, 8},
|
|||
|
{"16byte", s_pru_ucons, 16},
|
|||
|
{"set", s_pru_set, 0},
|
|||
|
{NULL, NULL, 0}
|
|||
|
};
|
|||
|
|
|||
|
|
|||
|
int
|
|||
|
md_estimate_size_before_relax (fragS *fragp ATTRIBUTE_UNUSED,
|
|||
|
asection *seg ATTRIBUTE_UNUSED)
|
|||
|
{
|
|||
|
abort ();
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
void
|
|||
|
md_convert_frag (bfd *headers ATTRIBUTE_UNUSED, segT segment ATTRIBUTE_UNUSED,
|
|||
|
fragS *fragp ATTRIBUTE_UNUSED)
|
|||
|
{
|
|||
|
abort ();
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
static bool
|
|||
|
relaxable_section (asection *sec)
|
|||
|
{
|
|||
|
return ((sec->flags & SEC_DEBUGGING) == 0
|
|||
|
&& (sec->flags & SEC_CODE) != 0
|
|||
|
&& (sec->flags & SEC_ALLOC) != 0);
|
|||
|
}
|
|||
|
|
|||
|
/* Does whatever the xtensa port does. */
|
|||
|
int
|
|||
|
pru_validate_fix_sub (fixS *fix)
|
|||
|
{
|
|||
|
segT add_symbol_segment, sub_symbol_segment;
|
|||
|
|
|||
|
/* The difference of two symbols should be resolved by the assembler when
|
|||
|
linkrelax is not set. If the linker may relax the section containing
|
|||
|
the symbols, then an Xtensa DIFF relocation must be generated so that
|
|||
|
the linker knows to adjust the difference value. */
|
|||
|
if (!linkrelax || fix->fx_addsy == NULL)
|
|||
|
return 0;
|
|||
|
|
|||
|
/* Make sure both symbols are in the same segment, and that segment is
|
|||
|
"normal" and relaxable. If the segment is not "normal", then the
|
|||
|
fix is not valid. If the segment is not "relaxable", then the fix
|
|||
|
should have been handled earlier. */
|
|||
|
add_symbol_segment = S_GET_SEGMENT (fix->fx_addsy);
|
|||
|
if (! SEG_NORMAL (add_symbol_segment)
|
|||
|
|| ! relaxable_section (add_symbol_segment))
|
|||
|
return 0;
|
|||
|
|
|||
|
sub_symbol_segment = S_GET_SEGMENT (fix->fx_subsy);
|
|||
|
return (sub_symbol_segment == add_symbol_segment);
|
|||
|
}
|
|||
|
|
|||
|
/* TC_FORCE_RELOCATION hook. */
|
|||
|
|
|||
|
/* If linkrelax is turned on, and the symbol to relocate
|
|||
|
against is in a relaxable segment, don't compute the value -
|
|||
|
generate a relocation instead. */
|
|||
|
int
|
|||
|
pru_force_relocation (fixS *fix)
|
|||
|
{
|
|||
|
if (linkrelax && fix->fx_addsy
|
|||
|
&& relaxable_section (S_GET_SEGMENT (fix->fx_addsy)))
|
|||
|
return 1;
|
|||
|
|
|||
|
return generic_force_reloc (fix);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
|
|||
|
/** Fixups and overflow checking. */
|
|||
|
|
|||
|
/* Check a fixup for overflow. */
|
|||
|
static bfd_reloc_status_type
|
|||
|
pru_check_overflow (valueT fixup, reloc_howto_type *howto)
|
|||
|
{
|
|||
|
bfd_reloc_status_type ret;
|
|||
|
|
|||
|
ret = bfd_check_overflow (howto->complain_on_overflow,
|
|||
|
howto->bitsize,
|
|||
|
howto->rightshift,
|
|||
|
bfd_get_reloc_size (howto) * 8,
|
|||
|
fixup);
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
/* Emit diagnostic for fixup overflow. */
|
|||
|
static void
|
|||
|
pru_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 pru_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 = pru_find_opcode (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 qbranch_target_overflow:
|
|||
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|||
|
_("quick branch offset %d out of range %d to %d"),
|
|||
|
(int)fixup, -((1<<9) * 4), (1 << 9) * 4);
|
|||
|
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_immed32_overflow:
|
|||
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|||
|
_("immediate value %llu out of range %u to %lu"),
|
|||
|
(unsigned long long)fixup, 0, 0xfffffffflu);
|
|||
|
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;
|
|||
|
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)
|
|||
|
{
|
|||
|
unsigned char *where;
|
|||
|
valueT value = *valP;
|
|||
|
|
|||
|
/* 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_PRU_LDI32
|
|||
|
|| fixP->fx_r_type == BFD_RELOC_PRU_U16
|
|||
|
|| fixP->fx_r_type == BFD_RELOC_PRU_U16_PMEMIMM
|
|||
|
|| fixP->fx_r_type == BFD_RELOC_PRU_S10_PCREL
|
|||
|
|| fixP->fx_r_type == BFD_RELOC_PRU_U8_PCREL
|
|||
|
|| fixP->fx_r_type == BFD_RELOC_PRU_32_PMEM
|
|||
|
|| fixP->fx_r_type == BFD_RELOC_PRU_16_PMEM
|
|||
|
/* 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;
|
|||
|
}
|
|||
|
|
|||
|
/* gas_assert (had_errors () || !fixP->fx_subsy); */
|
|||
|
|
|||
|
/* In general, fix instructions with immediate
|
|||
|
constants. But leave LDI32 for the linker,
|
|||
|
which is prepared to shorten insns. */
|
|||
|
if (fixP->fx_addsy == (symbolS *) NULL
|
|||
|
&& fixP->fx_r_type != BFD_RELOC_PRU_LDI32)
|
|||
|
fixP->fx_done = 1;
|
|||
|
|
|||
|
else if (fixP->fx_pcrel)
|
|||
|
{
|
|||
|
segT s = S_GET_SEGMENT (fixP->fx_addsy);
|
|||
|
|
|||
|
if (s == seg || s == absolute_section)
|
|||
|
{
|
|||
|
/* Blindly copied from AVR, but I don't understand why
|
|||
|
this is needed in the first place. Fail hard to catch
|
|||
|
when this curious code snippet is utilized. */
|
|||
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|||
|
_("unexpected PC relative expression"));
|
|||
|
value += S_GET_VALUE (fixP->fx_addsy);
|
|||
|
fixP->fx_done = 1;
|
|||
|
}
|
|||
|
}
|
|||
|
else if (linkrelax && fixP->fx_subsy)
|
|||
|
{
|
|||
|
/* For a subtraction relocation expression, generate one
|
|||
|
of the DIFF relocs, with the value being the difference.
|
|||
|
Note that a sym1 - sym2 expression is adjusted into a
|
|||
|
section_start_sym + sym4_offset_from_section_start - sym1
|
|||
|
expression. fixP->fx_addsy holds the section start symbol,
|
|||
|
fixP->fx_offset holds sym2's offset, and fixP->fx_subsy
|
|||
|
holds sym1. Calculate the current difference and write value,
|
|||
|
but leave fx_offset as is - during relaxation,
|
|||
|
fx_offset - value gives sym1's value. */
|
|||
|
|
|||
|
offsetT diffval; /* valueT is unsigned, so use offsetT. */
|
|||
|
|
|||
|
diffval = S_GET_VALUE (fixP->fx_addsy)
|
|||
|
+ fixP->fx_offset - S_GET_VALUE (fixP->fx_subsy);
|
|||
|
|
|||
|
switch (fixP->fx_r_type)
|
|||
|
{
|
|||
|
case BFD_RELOC_8:
|
|||
|
fixP->fx_r_type = BFD_RELOC_PRU_GNU_DIFF8;
|
|||
|
break;
|
|||
|
case BFD_RELOC_16:
|
|||
|
fixP->fx_r_type = BFD_RELOC_PRU_GNU_DIFF16;
|
|||
|
break;
|
|||
|
case BFD_RELOC_32:
|
|||
|
fixP->fx_r_type = BFD_RELOC_PRU_GNU_DIFF32;
|
|||
|
break;
|
|||
|
case BFD_RELOC_PRU_16_PMEM:
|
|||
|
fixP->fx_r_type = BFD_RELOC_PRU_GNU_DIFF16_PMEM;
|
|||
|
if (diffval % 4)
|
|||
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|||
|
_("residual low bits in pmem diff relocation"));
|
|||
|
diffval /= 4;
|
|||
|
break;
|
|||
|
case BFD_RELOC_PRU_32_PMEM:
|
|||
|
fixP->fx_r_type = BFD_RELOC_PRU_GNU_DIFF32_PMEM;
|
|||
|
if (diffval % 4)
|
|||
|
as_bad_where (fixP->fx_file, fixP->fx_line,
|
|||
|
_("residual low bits in pmem diff relocation"));
|
|||
|
diffval /= 4;
|
|||
|
break;
|
|||
|
default:
|
|||
|
as_bad_subtract (fixP);
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
value = *valP = diffval;
|
|||
|
|
|||
|
fixP->fx_subsy = NULL;
|
|||
|
}
|
|||
|
/* We don't actually support subtracting a symbol. */
|
|||
|
if (fixP->fx_subsy != (symbolS *) NULL)
|
|||
|
as_bad_subtract (fixP);
|
|||
|
|
|||
|
/* For the DIFF relocs, write the value into the object file while still
|
|||
|
keeping fx_done FALSE, as both the difference (recorded in the object file)
|
|||
|
and the sym offset (part of fixP) are needed at link relax time. */
|
|||
|
where = (unsigned char *) fixP->fx_frag->fr_literal + fixP->fx_where;
|
|||
|
switch (fixP->fx_r_type)
|
|||
|
{
|
|||
|
case BFD_RELOC_PRU_GNU_DIFF8:
|
|||
|
*where = value;
|
|||
|
break;
|
|||
|
case BFD_RELOC_PRU_GNU_DIFF16:
|
|||
|
case BFD_RELOC_PRU_GNU_DIFF16_PMEM:
|
|||
|
bfd_putl16 ((bfd_vma) value, where);
|
|||
|
break;
|
|||
|
case BFD_RELOC_PRU_GNU_DIFF32:
|
|||
|
case BFD_RELOC_PRU_GNU_DIFF32_PMEM:
|
|||
|
bfd_putl32 ((bfd_vma) value, where);
|
|||
|
break;
|
|||
|
default:
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
if (fixP->fx_done)
|
|||
|
/* 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 = value;
|
|||
|
uint64_t insn;
|
|||
|
char *buf;
|
|||
|
|
|||
|
/* Get the instruction or data to be fixed up. */
|
|||
|
buf = fixP->fx_frag->fr_literal + fixP->fx_where;
|
|||
|
insn = md_chars_to_number (buf, fixP->fx_size);
|
|||
|
|
|||
|
/* Check for overflow, emitting a diagnostic if necessary. */
|
|||
|
if (pru_check_overflow (fixup, howto) != bfd_reloc_ok)
|
|||
|
pru_diagnose_overflow (fixup, howto, fixP, insn);
|
|||
|
|
|||
|
/* Apply the right shift. */
|
|||
|
fixup = (offsetT) fixup >> howto->rightshift;
|
|||
|
|
|||
|
/* Truncate the fixup to right size. */
|
|||
|
if (howto->bitsize == 0)
|
|||
|
fixup = 0;
|
|||
|
else
|
|||
|
fixup &= ((valueT) 2 << (howto->bitsize - 1)) - 1;
|
|||
|
|
|||
|
/* Fix up the instruction. Non-contiguous bitfields need
|
|||
|
special handling. */
|
|||
|
if (fixP->fx_r_type == BFD_RELOC_PRU_LDI32)
|
|||
|
{
|
|||
|
/* As the only 64-bit "insn", LDI32 needs special handling. */
|
|||
|
uint32_t insn1 = insn & 0xffffffff;
|
|||
|
uint32_t insn2 = insn >> 32;
|
|||
|
SET_INSN_FIELD (IMM16, insn1, fixup >> 16);
|
|||
|
SET_INSN_FIELD (IMM16, insn2, fixup & 0xffff);
|
|||
|
|
|||
|
SET_INSN_FIELD (RDSEL, insn1, RSEL_31_16);
|
|||
|
SET_INSN_FIELD (RDSEL, insn2, RSEL_15_0);
|
|||
|
|
|||
|
md_number_to_chars (buf, insn1, 4);
|
|||
|
md_number_to_chars (buf + 4, insn2, 4);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (fixP->fx_r_type == BFD_RELOC_PRU_S10_PCREL)
|
|||
|
SET_BROFF_URAW (insn, fixup);
|
|||
|
else
|
|||
|
insn = (insn & ~howto->dst_mask) | (fixup << howto->bitpos);
|
|||
|
md_number_to_chars (buf, insn, 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. */
|
|||
|
|
|||
|
/* Creates a new pru_insn_relocS and returns a pointer to it. */
|
|||
|
static pru_insn_relocS *
|
|||
|
pru_insn_reloc_new (bfd_reloc_code_real_type reloc_type, unsigned int pcrel)
|
|||
|
{
|
|||
|
pru_insn_relocS *retval;
|
|||
|
retval = XNEW (pru_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 pru_insn_reloc_new (). */
|
|||
|
static void
|
|||
|
pru_insn_reloc_destroy (pru_insn_relocS *reloc)
|
|||
|
{
|
|||
|
pru_insn_relocS *next;
|
|||
|
|
|||
|
while (reloc)
|
|||
|
{
|
|||
|
next = reloc->reloc_next;
|
|||
|
free (reloc);
|
|||
|
reloc = next;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/* The various pru_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 pru_insn_relocS and stores the expression and
|
|||
|
reloc_type for future use. */
|
|||
|
static unsigned long
|
|||
|
pru_assemble_expression (const char *exprstr,
|
|||
|
pru_insn_infoS *insn,
|
|||
|
pru_insn_relocS *prev_reloc,
|
|||
|
bfd_reloc_code_real_type reloc_type,
|
|||
|
unsigned int pcrel)
|
|||
|
{
|
|||
|
expressionS *ep;
|
|||
|
pru_insn_relocS *reloc;
|
|||
|
char *saved_line_ptr;
|
|||
|
unsigned short value;
|
|||
|
|
|||
|
gas_assert (exprstr != NULL);
|
|||
|
gas_assert (insn != NULL);
|
|||
|
|
|||
|
/* We use this blank keyword to distinguish register from
|
|||
|
label operands. */
|
|||
|
if (strstr (exprstr, "%label") != NULL)
|
|||
|
{
|
|||
|
exprstr += strlen ("%label") + 1;
|
|||
|
}
|
|||
|
|
|||
|
/* Check for pmem relocation operator.
|
|||
|
Change the relocation type and advance the ptr to the start of
|
|||
|
the expression proper. */
|
|||
|
if (strstr (exprstr, "%pmem") != NULL)
|
|||
|
{
|
|||
|
reloc_type = BFD_RELOC_PRU_U16_PMEMIMM;
|
|||
|
exprstr += strlen ("%pmem") + 1;
|
|||
|
}
|
|||
|
|
|||
|
/* We potentially have a relocation. */
|
|||
|
reloc = pru_insn_reloc_new (reloc_type, pcrel);
|
|||
|
if (prev_reloc != NULL)
|
|||
|
prev_reloc->reloc_next = reloc;
|
|||
|
else
|
|||
|
insn->insn_reloc = reloc;
|
|||
|
|
|||
|
/* Parse the expression string. */
|
|||
|
ep = &reloc->reloc_expression;
|
|||
|
saved_line_ptr = input_line_pointer;
|
|||
|
input_line_pointer = (char *) exprstr;
|
|||
|
SKIP_WHITESPACE ();
|
|||
|
expression (ep);
|
|||
|
SKIP_WHITESPACE ();
|
|||
|
if (*input_line_pointer)
|
|||
|
as_bad (_("trailing garbage after expression: %s"), input_line_pointer);
|
|||
|
input_line_pointer = saved_line_ptr;
|
|||
|
|
|||
|
|
|||
|
if (ep->X_op == O_illegal || ep->X_op == O_absent)
|
|||
|
as_bad (_("expected expression, got %s"), exprstr);
|
|||
|
|
|||
|
/* 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 (pru_mode == PRU_MODE_TEST && ep->X_op == O_constant)
|
|||
|
value = ep->X_add_number;
|
|||
|
|
|||
|
return (unsigned long) value;
|
|||
|
}
|
|||
|
|
|||
|
/* Try to parse a non-relocatable expression. */
|
|||
|
static unsigned long
|
|||
|
pru_assemble_noreloc_expression (const char *exprstr)
|
|||
|
{
|
|||
|
expressionS exp;
|
|||
|
char *saved_line_ptr;
|
|||
|
unsigned long val;
|
|||
|
|
|||
|
gas_assert (exprstr != NULL);
|
|||
|
|
|||
|
saved_line_ptr = input_line_pointer;
|
|||
|
input_line_pointer = (char *) exprstr;
|
|||
|
SKIP_WHITESPACE ();
|
|||
|
expression (&exp);
|
|||
|
SKIP_WHITESPACE ();
|
|||
|
if (*input_line_pointer)
|
|||
|
as_bad (_("trailing garbage after expression: %s"), input_line_pointer);
|
|||
|
input_line_pointer = saved_line_ptr;
|
|||
|
|
|||
|
val = 0;
|
|||
|
if (exp.X_op != O_constant)
|
|||
|
as_bad (_("expected constant expression, got %s"), exprstr);
|
|||
|
else
|
|||
|
val = exp.X_add_number;
|
|||
|
|
|||
|
return val;
|
|||
|
}
|
|||
|
|
|||
|
/* 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. */
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_d (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
struct pru_reg *dst = pru_reg_lookup (argstr);
|
|||
|
|
|||
|
if (dst == NULL)
|
|||
|
as_bad (_("unknown register %s"), argstr);
|
|||
|
else
|
|||
|
{
|
|||
|
SET_INSN_FIELD (RD, insn_info->insn_code, dst->index);
|
|||
|
SET_INSN_FIELD (RDSEL, insn_info->insn_code, dst->regsel);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_D (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
struct pru_reg *dst;
|
|||
|
|
|||
|
/* The leading & before an address register is optional. */
|
|||
|
if (*argstr == '&')
|
|||
|
argstr++;
|
|||
|
|
|||
|
dst = pru_reg_lookup (argstr);
|
|||
|
|
|||
|
if (dst == NULL)
|
|||
|
as_bad (_("unknown register %s"), argstr);
|
|||
|
else
|
|||
|
{
|
|||
|
unsigned long rxb = 0;
|
|||
|
|
|||
|
switch (dst->regsel)
|
|||
|
{
|
|||
|
case RSEL_31_0: rxb = 0; break; /* whole register defaults to .b0 */
|
|||
|
case RSEL_7_0: rxb = 0; break;
|
|||
|
case RSEL_15_8: rxb = 1; break;
|
|||
|
case RSEL_23_16: rxb = 2; break;
|
|||
|
case RSEL_31_24: rxb = 3; break;
|
|||
|
default:
|
|||
|
as_bad (_("data transfer register cannot be halfword"));
|
|||
|
}
|
|||
|
|
|||
|
SET_INSN_FIELD (RD, insn_info->insn_code, dst->index);
|
|||
|
SET_INSN_FIELD (RDB, insn_info->insn_code, rxb);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_R (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
struct pru_reg *dst = pru_reg_lookup (argstr);
|
|||
|
|
|||
|
if (dst == NULL)
|
|||
|
as_bad (_("unknown register %s"), argstr);
|
|||
|
else
|
|||
|
{
|
|||
|
if (dst->regsel != RSEL_31_0)
|
|||
|
{
|
|||
|
as_bad (_("destination register must be full-word"));
|
|||
|
}
|
|||
|
|
|||
|
SET_INSN_FIELD (RD, insn_info->insn_code, dst->index);
|
|||
|
SET_INSN_FIELD (RDSEL, insn_info->insn_code, dst->regsel);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_s (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
struct pru_reg *src1 = pru_reg_lookup (argstr);
|
|||
|
|
|||
|
if (src1 == NULL)
|
|||
|
as_bad (_("unknown register %s"), argstr);
|
|||
|
else
|
|||
|
{
|
|||
|
SET_INSN_FIELD (RS1, insn_info->insn_code, src1->index);
|
|||
|
SET_INSN_FIELD (RS1SEL, insn_info->insn_code, src1->regsel);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_S (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
struct pru_reg *src1 = pru_reg_lookup (argstr);
|
|||
|
|
|||
|
if (src1 == NULL)
|
|||
|
as_bad (_("unknown register %s"), argstr);
|
|||
|
else
|
|||
|
{
|
|||
|
if (src1->regsel != RSEL_31_0)
|
|||
|
as_bad (_("cannot use partial register %s for addressing"), argstr);
|
|||
|
SET_INSN_FIELD (RS1, insn_info->insn_code, src1->index);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_b (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
struct pru_reg *src2 = pru_reg_lookup (argstr);
|
|||
|
if (src2 == NULL)
|
|||
|
{
|
|||
|
unsigned long imm8 = pru_assemble_noreloc_expression (argstr);
|
|||
|
if (imm8 >= 0x100)
|
|||
|
as_bad (_("value %lu is too large for a byte operand"), imm8);
|
|||
|
SET_INSN_FIELD (IMM8, insn_info->insn_code, imm8);
|
|||
|
SET_INSN_FIELD (IO, insn_info->insn_code, 1);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SET_INSN_FIELD (IO, insn_info->insn_code, 0);
|
|||
|
SET_INSN_FIELD (RS2, insn_info->insn_code, src2->index);
|
|||
|
SET_INSN_FIELD (RS2SEL, insn_info->insn_code, src2->regsel);
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_B (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
struct pru_reg *src2 = pru_reg_lookup (argstr);
|
|||
|
if (src2 == NULL)
|
|||
|
{
|
|||
|
unsigned long imm8;
|
|||
|
imm8 = pru_assemble_noreloc_expression (argstr);
|
|||
|
if (!imm8 || imm8 > 0xff)
|
|||
|
as_bad (_("loop count constant %ld is out of range [1..%d]"),
|
|||
|
imm8, 0xff);
|
|||
|
/* Note: HW expects the immediate loop count field
|
|||
|
to be one less than the actual loop count. */
|
|||
|
SET_INSN_FIELD (IMM8, insn_info->insn_code, imm8 - 1);
|
|||
|
SET_INSN_FIELD (IO, insn_info->insn_code, 1);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SET_INSN_FIELD (IO, insn_info->insn_code, 0);
|
|||
|
SET_INSN_FIELD (RS2, insn_info->insn_code, src2->index);
|
|||
|
SET_INSN_FIELD (RS2SEL, insn_info->insn_code, src2->regsel);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_i (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long imm32;
|
|||
|
|
|||
|
/* We must not generate PRU_LDI32 relocation if relaxation is disabled in
|
|||
|
GAS. Consider the following scenario: GAS relaxation is disabled, so
|
|||
|
DIFF* expressions are fixed and not emitted as relocations. Then if LD
|
|||
|
has relaxation enabled, it may shorten LDI32 but will not update
|
|||
|
accordingly the DIFF expressions. */
|
|||
|
if (pru_opt.link_relax)
|
|||
|
imm32 = pru_assemble_expression (argstr, insn_info,
|
|||
|
insn_info->insn_reloc,
|
|||
|
BFD_RELOC_PRU_LDI32, 0);
|
|||
|
else
|
|||
|
imm32 = pru_assemble_noreloc_expression (argstr);
|
|||
|
|
|||
|
/* QUIRK: LDI must clear IO bit high, even though it has immediate arg. */
|
|||
|
SET_INSN_FIELD (IO, insn_info->insn_code, 0);
|
|||
|
SET_INSN_FIELD (RDSEL, insn_info->insn_code, RSEL_31_16);
|
|||
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, imm32 >> 16);
|
|||
|
insn_info->ldi32_imm32 = imm32;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_j (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
struct pru_reg *src2 = pru_reg_lookup (argstr);
|
|||
|
|
|||
|
if (src2 == NULL)
|
|||
|
{
|
|||
|
unsigned long imm16 = pru_assemble_expression (argstr, insn_info,
|
|||
|
insn_info->insn_reloc,
|
|||
|
BFD_RELOC_PRU_U16_PMEMIMM,
|
|||
|
0);
|
|||
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, imm16);
|
|||
|
SET_INSN_FIELD (IO, insn_info->insn_code, 1);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
SET_INSN_FIELD (IO, insn_info->insn_code, 0);
|
|||
|
SET_INSN_FIELD (RS2, insn_info->insn_code, src2->index);
|
|||
|
SET_INSN_FIELD (RS2SEL, insn_info->insn_code, src2->regsel);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_W (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long imm16 = pru_assemble_expression (argstr, insn_info,
|
|||
|
insn_info->insn_reloc,
|
|||
|
BFD_RELOC_PRU_U16, 0);
|
|||
|
/* QUIRK: LDI must clear IO bit high, even though it has immediate arg. */
|
|||
|
SET_INSN_FIELD (IO, insn_info->insn_code, 0);
|
|||
|
SET_INSN_FIELD (IMM16, insn_info->insn_code, imm16);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_o (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long imm10 = pru_assemble_expression (argstr, insn_info,
|
|||
|
insn_info->insn_reloc,
|
|||
|
BFD_RELOC_PRU_S10_PCREL, 1);
|
|||
|
SET_BROFF_URAW (insn_info->insn_code, imm10);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_O (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long imm8 = pru_assemble_expression (argstr, insn_info,
|
|||
|
insn_info->insn_reloc,
|
|||
|
BFD_RELOC_PRU_U8_PCREL, 1);
|
|||
|
SET_INSN_FIELD (LOOP_JMPOFFS, insn_info->insn_code, imm8);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_l (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long burstlen = 0;
|
|||
|
struct pru_reg *blreg = pru_reg_lookup (argstr);
|
|||
|
|
|||
|
if (blreg == NULL)
|
|||
|
{
|
|||
|
burstlen = pru_assemble_noreloc_expression (argstr);
|
|||
|
if (!burstlen || burstlen > LSSBBO_BYTECOUNT_R0_BITS7_0)
|
|||
|
as_bad (_("byte count constant %ld is out of range [1..%d]"),
|
|||
|
burstlen, LSSBBO_BYTECOUNT_R0_BITS7_0);
|
|||
|
burstlen--;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (blreg->index != 0)
|
|||
|
as_bad (_("only r0 can be used as byte count register"));
|
|||
|
else if (blreg->regsel > RSEL_31_24)
|
|||
|
as_bad (_("only r0.bX byte fields of r0 can be used as byte count"));
|
|||
|
else
|
|||
|
burstlen = LSSBBO_BYTECOUNT_R0_BITS7_0 + blreg->regsel;
|
|||
|
}
|
|||
|
SET_BURSTLEN (insn_info->insn_code, burstlen);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_n (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long burstlen = 0;
|
|||
|
struct pru_reg *blreg = pru_reg_lookup (argstr);
|
|||
|
|
|||
|
if (blreg == NULL)
|
|||
|
{
|
|||
|
burstlen = pru_assemble_noreloc_expression (argstr);
|
|||
|
if (!burstlen || burstlen > LSSBBO_BYTECOUNT_R0_BITS7_0)
|
|||
|
as_bad (_("byte count constant %ld is out of range [1..%d]"),
|
|||
|
burstlen, LSSBBO_BYTECOUNT_R0_BITS7_0);
|
|||
|
burstlen--;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
if (blreg->index != 0)
|
|||
|
as_bad (_("only r0 can be used as byte count register"));
|
|||
|
else if (blreg->regsel > RSEL_31_24)
|
|||
|
as_bad (_("only r0.bX byte fields of r0 can be used as byte count"));
|
|||
|
else
|
|||
|
burstlen = LSSBBO_BYTECOUNT_R0_BITS7_0 + blreg->regsel;
|
|||
|
}
|
|||
|
SET_INSN_FIELD (XFR_LENGTH, insn_info->insn_code, burstlen);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_c (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long cb = pru_assemble_noreloc_expression (argstr);
|
|||
|
|
|||
|
if (cb > 31)
|
|||
|
as_bad (_("invalid constant table offset %ld"), cb);
|
|||
|
else
|
|||
|
SET_INSN_FIELD (CB, insn_info->insn_code, cb);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_w (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long wk = pru_assemble_noreloc_expression (argstr);
|
|||
|
|
|||
|
if (wk != 0 && wk != 1)
|
|||
|
as_bad (_("invalid WakeOnStatus %ld"), wk);
|
|||
|
else
|
|||
|
SET_INSN_FIELD (WAKEONSTATUS, insn_info->insn_code, wk);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
pru_assemble_arg_x (pru_insn_infoS *insn_info, const char *argstr)
|
|||
|
{
|
|||
|
unsigned long wba = pru_assemble_noreloc_expression (argstr);
|
|||
|
|
|||
|
if (wba > 255)
|
|||
|
as_bad (_("invalid XFR WideBus Address %ld"), wba);
|
|||
|
else
|
|||
|
SET_INSN_FIELD (XFR_WBA, insn_info->insn_code, wba);
|
|||
|
}
|
|||
|
|
|||
|
/* 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 *
|
|||
|
pru_consume_arg (char *argstr, const char *parsestr)
|
|||
|
{
|
|||
|
char *temp;
|
|||
|
|
|||
|
switch (*parsestr)
|
|||
|
{
|
|||
|
case 'W':
|
|||
|
if (*argstr == '%')
|
|||
|
{
|
|||
|
if (strprefix (argstr, "%pmem") || strprefix (argstr, "%label"))
|
|||
|
{
|
|||
|
/* 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 'j':
|
|||
|
case 'o':
|
|||
|
case 'O':
|
|||
|
if (*argstr == '%')
|
|||
|
{
|
|||
|
/* Only 'j' really requires %label for distinguishing registers
|
|||
|
from labels, but we include 'o' and 'O' here to avoid
|
|||
|
confusing assembler programmers. Thus for completeness all
|
|||
|
jump operands can be prefixed with %label. */
|
|||
|
if (strprefix (argstr, "%label"))
|
|||
|
{
|
|||
|
/* 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 'b':
|
|||
|
case 'B':
|
|||
|
case 'c':
|
|||
|
case 'd':
|
|||
|
case 'D':
|
|||
|
case 'E':
|
|||
|
case 'i':
|
|||
|
case 's':
|
|||
|
case 'S':
|
|||
|
case 'l':
|
|||
|
case 'n':
|
|||
|
case 'R':
|
|||
|
case 'w':
|
|||
|
case 'x':
|
|||
|
/* We can't have %pmem here. */
|
|||
|
if (*argstr == '%')
|
|||
|
as_bad (_("badly formed expression near %s"), argstr);
|
|||
|
break;
|
|||
|
default:
|
|||
|
BAD_CASE (*parsestr);
|
|||
|
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 *
|
|||
|
pru_consume_separator (char *argstr, const char *separator)
|
|||
|
{
|
|||
|
char *p;
|
|||
|
|
|||
|
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
|
|||
|
pru_parse_args (pru_insn_infoS *insn ATTRIBUTE_UNUSED, char *argstr,
|
|||
|
const char *parsestr, char **parsed_args)
|
|||
|
{
|
|||
|
char *p;
|
|||
|
char *end = NULL;
|
|||
|
int i;
|
|||
|
p = argstr;
|
|||
|
i = 0;
|
|||
|
bool 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 < PRU_MAX_INSN_TOKENS)
|
|||
|
{
|
|||
|
parsed_args[i] = pru_consume_arg (p, parsestr);
|
|||
|
++parsestr;
|
|||
|
if (*parsestr != '\0')
|
|||
|
{
|
|||
|
p = pru_consume_separator (p, parsestr);
|
|||
|
++parsestr;
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
/* Check that the argument string has no trailing arguments. */
|
|||
|
/* If we've got a %pmem relocation, we've zapped the parens with
|
|||
|
spaces. */
|
|||
|
if (strprefix (p, "%pmem") || strprefix (p, "%label"))
|
|||
|
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;
|
|||
|
|
|||
|
/* There are no instructions with optional arguments; complain. */
|
|||
|
if (*parsestr != '\0')
|
|||
|
as_bad (_("missing argument"));
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/** Assembler output support. */
|
|||
|
|
|||
|
/* Output a normal instruction. */
|
|||
|
static void
|
|||
|
output_insn (pru_insn_infoS *insn)
|
|||
|
{
|
|||
|
char *f;
|
|||
|
pru_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 two LDI instructions from LDI32 macro */
|
|||
|
static void
|
|||
|
output_insn_ldi32 (pru_insn_infoS *insn)
|
|||
|
{
|
|||
|
char *f;
|
|||
|
pru_insn_relocS *reloc;
|
|||
|
unsigned long insn2;
|
|||
|
|
|||
|
f = frag_more (8);
|
|||
|
SET_INSN_FIELD (IMM16, insn->insn_code, insn->ldi32_imm32 >> 16);
|
|||
|
SET_INSN_FIELD (RDSEL, insn->insn_code, RSEL_31_16);
|
|||
|
md_number_to_chars (f, insn->insn_code, 4);
|
|||
|
|
|||
|
insn2 = insn->insn_code;
|
|||
|
SET_INSN_FIELD (IMM16, insn2, insn->ldi32_imm32 & 0xffff);
|
|||
|
SET_INSN_FIELD (RDSEL, insn2, RSEL_15_0);
|
|||
|
md_number_to_chars (f + 4, insn2, 4);
|
|||
|
|
|||
|
/* Emit debug info. */
|
|||
|
dwarf2_emit_insn (8);
|
|||
|
|
|||
|
/* 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);
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/** External interfaces. */
|
|||
|
|
|||
|
/* The following functions are called by machine-independent parts of
|
|||
|
the assembler. */
|
|||
|
int
|
|||
|
md_parse_option (int c, const char *arg ATTRIBUTE_UNUSED)
|
|||
|
{
|
|||
|
switch (c)
|
|||
|
{
|
|||
|
case 'r':
|
|||
|
/* Hidden option for self-test mode. */
|
|||
|
pru_mode = PRU_MODE_TEST;
|
|||
|
break;
|
|||
|
case OPTION_LINK_RELAX:
|
|||
|
pru_opt.link_relax = true;
|
|||
|
break;
|
|||
|
case OPTION_NO_LINK_RELAX:
|
|||
|
pru_opt.link_relax = false;
|
|||
|
break;
|
|||
|
case OPTION_NO_WARN_REGNAME_LABEL:
|
|||
|
pru_opt.warn_regname_label = false;
|
|||
|
break;
|
|||
|
default:
|
|||
|
return 0;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
const char *
|
|||
|
pru_target_format (void)
|
|||
|
{
|
|||
|
return "elf32-pru";
|
|||
|
}
|
|||
|
|
|||
|
/* Machine-dependent usage message. */
|
|||
|
void
|
|||
|
md_show_usage (FILE *stream)
|
|||
|
{
|
|||
|
fprintf (stream,
|
|||
|
_("PRU options:\n"
|
|||
|
" -mlink-relax generate relocations for linker relaxation (default).\n"
|
|||
|
" -mno-link-relax don't generate relocations for linker relaxation.\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;
|
|||
|
|
|||
|
/* Create and fill a hashtable for the PRU opcodes, registers and
|
|||
|
arguments. */
|
|||
|
pru_opcode_hash = str_htab_create ();
|
|||
|
pru_reg_hash = str_htab_create ();
|
|||
|
|
|||
|
for (i = 0; i < NUMOPCODES; ++i)
|
|||
|
if (str_hash_insert (pru_opcode_hash, pru_opcodes[i].name,
|
|||
|
&pru_opcodes[i], 0) != NULL)
|
|||
|
as_fatal (_("duplicate %s"), pru_opcodes[i].name);
|
|||
|
|
|||
|
for (i = 0; i < pru_num_regs; ++i)
|
|||
|
if (str_hash_insert (pru_reg_hash, pru_regs[i].name, &pru_regs[i], 0))
|
|||
|
as_fatal (_("duplicate %s"), pru_regs[i].name);
|
|||
|
|
|||
|
linkrelax = pru_opt.link_relax;
|
|||
|
/* Initialize the alignment data. */
|
|||
|
pru_current_align_seg = now_seg;
|
|||
|
pru_last_label = NULL;
|
|||
|
pru_current_align = 0;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/* Assembles a single line of PRU assembly language. */
|
|||
|
void
|
|||
|
md_assemble (char *op_str)
|
|||
|
{
|
|||
|
char *argstr;
|
|||
|
char *op_strdup = NULL;
|
|||
|
pru_insn_infoS thisinsn;
|
|||
|
pru_insn_infoS *insn = &thisinsn;
|
|||
|
|
|||
|
/* Make sure we are aligned on a 4-byte boundary. */
|
|||
|
if (pru_current_align < 2)
|
|||
|
pru_align (2, NULL, pru_last_label);
|
|||
|
else if (pru_current_align > 2)
|
|||
|
pru_current_align = 2;
|
|||
|
pru_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_pru_opcode = pru_opcode_lookup (insn->insn_tokens[0]);
|
|||
|
insn->insn_reloc = NULL;
|
|||
|
|
|||
|
if (insn->insn_pru_opcode != NULL)
|
|||
|
{
|
|||
|
const char *argsfmt = insn->insn_pru_opcode->args;
|
|||
|
const char **argtk = &insn->insn_tokens[1];
|
|||
|
const char *argp;
|
|||
|
|
|||
|
/* Set the opcode for the instruction. */
|
|||
|
insn->insn_code = insn->insn_pru_opcode->match;
|
|||
|
|
|||
|
if (pru_mode == PRU_MODE_TEST)
|
|||
|
{
|
|||
|
/* Add the "expected" instruction parameter used for validation. */
|
|||
|
argsfmt = malloc (strlen (argsfmt) + 3);
|
|||
|
sprintf ((char *)argsfmt, "%s,E", insn->insn_pru_opcode->args);
|
|||
|
}
|
|||
|
pru_parse_args (insn, argstr, argsfmt,
|
|||
|
(char **) &insn->insn_tokens[1]);
|
|||
|
|
|||
|
for (argp = argsfmt; !had_errors () && *argp && *argtk; ++argp)
|
|||
|
{
|
|||
|
gas_assert (argtk <= &insn->insn_tokens[PRU_MAX_INSN_TOKENS]);
|
|||
|
|
|||
|
switch (*argp)
|
|||
|
{
|
|||
|
case ',':
|
|||
|
continue;
|
|||
|
|
|||
|
case 'd':
|
|||
|
pru_assemble_arg_d (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'D':
|
|||
|
pru_assemble_arg_D (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'R':
|
|||
|
pru_assemble_arg_R (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 's':
|
|||
|
pru_assemble_arg_s (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'S':
|
|||
|
pru_assemble_arg_S (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'b':
|
|||
|
pru_assemble_arg_b (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'B':
|
|||
|
pru_assemble_arg_B (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'i':
|
|||
|
pru_assemble_arg_i (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'j':
|
|||
|
pru_assemble_arg_j (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'W':
|
|||
|
pru_assemble_arg_W (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'o':
|
|||
|
pru_assemble_arg_o (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'O':
|
|||
|
pru_assemble_arg_O (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'l':
|
|||
|
pru_assemble_arg_l (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'n':
|
|||
|
pru_assemble_arg_n (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'c':
|
|||
|
pru_assemble_arg_c (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'w':
|
|||
|
pru_assemble_arg_w (insn, *argtk++);
|
|||
|
continue;
|
|||
|
case 'x':
|
|||
|
pru_assemble_arg_x (insn, *argtk++);
|
|||
|
continue;
|
|||
|
|
|||
|
case 'E':
|
|||
|
pru_check_assembly (insn->insn_code, *argtk++);
|
|||
|
continue;
|
|||
|
|
|||
|
default:
|
|||
|
BAD_CASE (*argp);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (*argp && !had_errors ())
|
|||
|
as_bad (_("missing argument"));
|
|||
|
|
|||
|
if (!had_errors ())
|
|||
|
{
|
|||
|
if (insn->insn_pru_opcode->pinfo & PRU_INSN_LDI32)
|
|||
|
{
|
|||
|
output_insn_ldi32 (insn);
|
|||
|
}
|
|||
|
else
|
|||
|
{
|
|||
|
output_insn (insn);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
if (pru_mode == PRU_MODE_TEST)
|
|||
|
free ((char *)argsfmt);
|
|||
|
}
|
|||
|
else
|
|||
|
/* Unrecognised instruction - error. */
|
|||
|
as_bad (_("unrecognised instruction %s"), insn->insn_tokens[0]);
|
|||
|
|
|||
|
/* Don't leak memory. */
|
|||
|
pru_insn_reloc_destroy (insn->insn_reloc);
|
|||
|
free (op_strdup);
|
|||
|
}
|
|||
|
|
|||
|
/* Round up section size. */
|
|||
|
valueT
|
|||
|
md_section_align (asection *seg, valueT addr)
|
|||
|
{
|
|||
|
int align = bfd_section_alignment (seg);
|
|||
|
return ((addr + (1 << align) - 1) & (-((valueT) 1 << align)));
|
|||
|
}
|
|||
|
|
|||
|
/* Implement tc_fix_adjustable. */
|
|||
|
int
|
|||
|
pru_fix_adjustable (fixS *fixp)
|
|||
|
{
|
|||
|
if (fixp->fx_addsy == NULL)
|
|||
|
return 1;
|
|||
|
|
|||
|
/* 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;
|
|||
|
|
|||
|
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;
|
|||
|
|
|||
|
return 1;
|
|||
|
}
|
|||
|
|
|||
|
/* 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 = XNEW (arelent);
|
|||
|
reloc->sym_ptr_ptr = XNEW (asymbol *);
|
|||
|
*reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy);
|
|||
|
|
|||
|
reloc->address = fixp->fx_frag->fr_address + fixp->fx_where;
|
|||
|
reloc->addend = fixp->fx_offset; /* fixp->fx_addnumber; */
|
|||
|
|
|||
|
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 fixP->fx_where + fixP->fx_frag->fr_address;
|
|||
|
}
|
|||
|
|
|||
|
/* Called just before the assembler exits. */
|
|||
|
void
|
|||
|
md_end (void)
|
|||
|
{
|
|||
|
htab_delete (pru_opcode_hash);
|
|||
|
htab_delete (pru_reg_hash);
|
|||
|
}
|
|||
|
|
|||
|
symbolS *
|
|||
|
md_undefined_symbol (char *name ATTRIBUTE_UNUSED)
|
|||
|
{
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/* Implement tc_frob_label. */
|
|||
|
void
|
|||
|
pru_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. */
|
|||
|
pru_last_label = lab;
|
|||
|
|
|||
|
if (pru_opt.warn_regname_label && pru_reg_lookup (S_GET_NAME (lab)))
|
|||
|
as_warn (_("Label \"%s\" matches a CPU register name"), S_GET_NAME (lab));
|
|||
|
}
|
|||
|
|
|||
|
static inline char *
|
|||
|
skip_space (char *s)
|
|||
|
{
|
|||
|
while (*s == ' ' || *s == '\t')
|
|||
|
++s;
|
|||
|
return s;
|
|||
|
}
|
|||
|
|
|||
|
/* Parse special CONS expression: pmem (expression). Idea from AVR.
|
|||
|
|
|||
|
Used to catch and mark code (program memory) in constant expression
|
|||
|
relocations. Return non-zero for program memory. */
|
|||
|
|
|||
|
int
|
|||
|
pru_parse_cons_expression (expressionS *exp, int nbytes)
|
|||
|
{
|
|||
|
int is_pmem = false;
|
|||
|
char *tmp;
|
|||
|
|
|||
|
tmp = input_line_pointer = skip_space (input_line_pointer);
|
|||
|
|
|||
|
if (nbytes == 4 || nbytes == 2)
|
|||
|
{
|
|||
|
const char *pmem_str = "%pmem";
|
|||
|
int len = strlen (pmem_str);
|
|||
|
|
|||
|
if (strncasecmp (input_line_pointer, pmem_str, len) == 0)
|
|||
|
{
|
|||
|
input_line_pointer = skip_space (input_line_pointer + len);
|
|||
|
|
|||
|
if (*input_line_pointer == '(')
|
|||
|
{
|
|||
|
input_line_pointer = skip_space (input_line_pointer + 1);
|
|||
|
is_pmem = true;
|
|||
|
expression (exp);
|
|||
|
|
|||
|
if (*input_line_pointer == ')')
|
|||
|
++input_line_pointer;
|
|||
|
else
|
|||
|
{
|
|||
|
as_bad (_("`)' required"));
|
|||
|
is_pmem = false;
|
|||
|
}
|
|||
|
|
|||
|
return is_pmem;
|
|||
|
}
|
|||
|
|
|||
|
input_line_pointer = tmp;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
expression (exp);
|
|||
|
|
|||
|
return is_pmem;
|
|||
|
}
|
|||
|
|
|||
|
/* Implement TC_CONS_FIX_NEW. */
|
|||
|
void
|
|||
|
pru_cons_fix_new (fragS *frag, int where, unsigned int nbytes,
|
|||
|
expressionS *exp, const int is_pmem)
|
|||
|
{
|
|||
|
bfd_reloc_code_real_type r;
|
|||
|
|
|||
|
switch (nbytes | (!!is_pmem << 8))
|
|||
|
{
|
|||
|
case 1 | (0 << 8): r = BFD_RELOC_8; break;
|
|||
|
case 2 | (0 << 8): r = BFD_RELOC_16; break;
|
|||
|
case 4 | (0 << 8): r = BFD_RELOC_32; break;
|
|||
|
case 8 | (0 << 8): r = BFD_RELOC_64; break;
|
|||
|
case 2 | (1 << 8): r = BFD_RELOC_PRU_16_PMEM; break;
|
|||
|
case 4 | (1 << 8): r = BFD_RELOC_PRU_32_PMEM; break;
|
|||
|
default:
|
|||
|
as_bad (_("illegal %s relocation size: %d"),
|
|||
|
is_pmem ? "text" : "data", nbytes);
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
fix_new_exp (frag, where, (int) nbytes, exp, 0, r);
|
|||
|
}
|
|||
|
|
|||
|
/* Implement tc_regname_to_dw2regnum, to convert REGNAME to a DWARF-2
|
|||
|
register number. Return the starting HW byte-register number. */
|
|||
|
|
|||
|
int
|
|||
|
pru_regname_to_dw2regnum (char *regname)
|
|||
|
{
|
|||
|
static const unsigned int regstart[RSEL_NUM_ITEMS] =
|
|||
|
{
|
|||
|
[RSEL_7_0] = 0,
|
|||
|
[RSEL_15_8] = 1,
|
|||
|
[RSEL_23_16] = 2,
|
|||
|
[RSEL_31_24] = 3,
|
|||
|
[RSEL_15_0] = 0,
|
|||
|
[RSEL_23_8] = 1,
|
|||
|
[RSEL_31_16] = 2,
|
|||
|
[RSEL_31_0] = 0,
|
|||
|
};
|
|||
|
|
|||
|
struct pru_reg *r = pru_reg_lookup (regname);
|
|||
|
|
|||
|
if (r == NULL || r->regsel >= RSEL_NUM_ITEMS)
|
|||
|
return -1;
|
|||
|
return r->index * 4 + regstart[r->regsel];
|
|||
|
}
|
|||
|
|
|||
|
/* Implement tc_cfi_frame_initial_instructions, to initialize the DWARF-2
|
|||
|
unwind information for this procedure. */
|
|||
|
void
|
|||
|
pru_frame_initial_instructions (void)
|
|||
|
{
|
|||
|
const unsigned fp_regno = 4 * 4;
|
|||
|
cfi_add_CFA_def_cfa (fp_regno, 0);
|
|||
|
}
|
|||
|
|
|||
|
bool
|
|||
|
pru_allow_local_subtract (expressionS * left,
|
|||
|
expressionS * right,
|
|||
|
segT section)
|
|||
|
{
|
|||
|
/* If we are not in relaxation mode, subtraction is OK. */
|
|||
|
if (!linkrelax)
|
|||
|
return true;
|
|||
|
|
|||
|
/* If the symbols are not in a code section then they are OK. */
|
|||
|
if ((section->flags & SEC_CODE) == 0)
|
|||
|
return true;
|
|||
|
|
|||
|
if (left->X_add_symbol == right->X_add_symbol)
|
|||
|
return true;
|
|||
|
|
|||
|
/* We have to assume that there may be instructions between the
|
|||
|
two symbols and that relaxation may increase the distance between
|
|||
|
them. */
|
|||
|
return false;
|
|||
|
}
|