diff options
author | Dimitar Dimitrov <dimitar@dinux.eu> | 2016-12-27 22:43:38 +0200 |
---|---|---|
committer | Alan Modra <amodra@gmail.com> | 2016-12-31 12:03:35 +1030 |
commit | 93f11b16ec1f5775c7f6c32b4a39d6dd0fb0c92a (patch) | |
tree | 239a723ef7f1ce40a785067b4fda0a66d770aef4 /gas/config/tc-pru.c | |
parent | 2b100bb5cf206f9254453a00e4b48e32d3584625 (diff) | |
download | gdb-93f11b16ec1f5775c7f6c32b4a39d6dd0fb0c92a.zip gdb-93f11b16ec1f5775c7f6c32b4a39d6dd0fb0c92a.tar.gz gdb-93f11b16ec1f5775c7f6c32b4a39d6dd0fb0c92a.tar.bz2 |
PRU GAS Port
* NEWS: Mention new PRU target.
* Makefile.am: Add PRU target.
* config/obj-elf.c: Ditto.
* configure.tgt: Ditto.
* config/tc-pru.c: New file.
* config/tc-pru.h: New file.
* doc/Makefile.am: Add documentation for PRU GAS port.
* doc/all.texi, Ditto.
* doc/as.texinfo: Ditto.
* doc/c-pru.texi: Document PRU GAS options.
* Makefile.in: Regenerate.
* doc/Makefile.in: Regenerate.
* po/POTFILES.in: Regenerate.
* testsuite/gas/pru/alu.d: New file for PRU GAS testsuite.
* testsuite/gas/pru/alu.s: Ditto.
* testsuite/gas/pru/branch.d: Ditto.
* testsuite/gas/pru/branch.s: Ditto.
* testsuite/gas/pru/illegal.l: Ditto.
* testsuite/gas/pru/illegal.s: Ditto.
* testsuite/gas/pru/ldi.d: Ditto.
* testsuite/gas/pru/ldi.s: Ditto.
* testsuite/gas/pru/ldst.d: Ditto.
* testsuite/gas/pru/ldst.s: Ditto.
* testsuite/gas/pru/loop.d: Ditto.
* testsuite/gas/pru/loop.s: Ditto.
* testsuite/gas/pru/misc.d: Ditto.
* testsuite/gas/pru/misc.s: Ditto.
* testsuite/gas/pru/pru.exp: Ditto.
* testsuite/gas/pru/pseudo.d: Ditto.
* testsuite/gas/pru/pseudo.s: Ditto.
* testsuite/gas/pru/warn_reglabel.l: Ditto.
* testsuite/gas/pru/warn_reglabel.s: Ditto.
* testsuite/gas/pru/xfr.d: Ditto.
* testsuite/gas/pru/xfr.s: Ditto.
* testsuite/gas/lns/lns.exp: Mark lns-common-1-alt variant for PRU.
Signed-off-by: Dimitar Dimitrov <dimitar@dinux.eu>
Diffstat (limited to 'gas/config/tc-pru.c')
-rw-r--r-- | gas/config/tc-pru.c | 1946 |
1 files changed, 1946 insertions, 0 deletions
diff --git a/gas/config/tc-pru.c b/gas/config/tc-pru.c new file mode 100644 index 0000000..63aca6e --- /dev/null +++ b/gas/config/tc-pru.c @@ -0,0 +1,1946 @@ +/* TI PRU assembler. + Copyright (C) 2014-2016 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 "bfd_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. */ + bfd_boolean link_relax; + + /* -mno-warn-regname-label: do not output a warning that a label name + matches a register name. */ + bfd_boolean 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 struct hash_control *pru_opcode_hash = NULL; +#define pru_opcode_lookup(NAME) \ + ((struct pru_opcode *) hash_find (pru_opcode_hash, (NAME))) + +/* Register hash table. */ +static struct hash_control *pru_reg_hash = NULL; +#define pru_reg_lookup(NAME) \ + ((struct pru_reg *) 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 preceeding 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, uint64_t 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]) + { + bfd_boolean 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 bfd_boolean +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; + long n; + + /* 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_where (fixP->fx_file, fixP->fx_line, + _("expression too complex")); + break; + } + + value = *valP = diffval; + + fixP->fx_subsy = NULL; + } + /* We don't actually support subtracting a symbol. */ + if (fixP->fx_subsy != (symbolS *) NULL) + as_bad_where (fixP->fx_file, fixP->fx_line, _("expression too complex")); + + /* 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. */ + n = sizeof (fixup) * 8 - howto->bitsize; + fixup = (fixup << n) >> n; + + /* Fix up the instruction. Non-contiguous bitfields need + special handling. */ + if (fixP->fx_r_type == BFD_RELOC_PRU_S10_PCREL) + SET_BROFF_URAW (insn, fixup); + else 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 & 0xffff); + SET_INSN_FIELD (IMM16, insn2, fixup >> 16); + insn = insn1 | ((uint64_t)insn2 << 32); + } + 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); + 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 (IMM16, insn_info->insn_code, imm32 & 0xffff); + 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; + 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 < 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); + md_number_to_chars (f, insn->insn_code, 4); + + insn2 = insn->insn_code; + SET_INSN_FIELD (IMM16, insn2, insn->ldi32_imm32 >> 16); + SET_INSN_FIELD (RDSEL, insn2, RSEL_31_16); + 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; + const char *inserted; + + /* Create and fill a hashtable for the PRU opcodes, registers and + arguments. */ + pru_opcode_hash = hash_new (); + pru_reg_hash = hash_new (); + + for (i = 0; i < NUMOPCODES; ++i) + { + inserted + = hash_insert (pru_opcode_hash, pru_opcodes[i].name, + (PTR) & pru_opcodes[i]); + if (inserted != NULL) + { + fprintf (stderr, _("internal error: can't hash `%s': %s\n"), + pru_opcodes[i].name, inserted); + /* Probably a memory allocation problem? Give up now. */ + as_fatal (_("Broken assembler. No assembly attempted.")); + } + } + + for (i = 0; i < pru_num_regs; ++i) + { + inserted + = hash_insert (pru_reg_hash, pru_regs[i].name, + (PTR) & pru_regs[i]); + if (inserted != NULL) + { + fprintf (stderr, _("internal error: can't hash `%s': %s\n"), + pru_regs[i].name, inserted); + /* Probably a memory allocation problem? Give up now. */ + as_fatal (_("Broken assembler. No assembly attempted.")); + } + + } + + 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++); + 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_get_section_alignment (stdoutput, 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) +{ + hash_die (pru_opcode_hash); + hash_die (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. */ +int +pru_regname_to_dw2regnum (char *regname) +{ + struct pru_reg *r = pru_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 +pru_frame_initial_instructions (void) +{ + const unsigned fp_regno = 4; + cfi_add_CFA_def_cfa (fp_regno, 0); +} + +bfd_boolean +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; +} |