diff options
author | Nick Clifton <nickc@redhat.com> | 2013-01-10 09:49:22 +0000 |
---|---|---|
committer | Nick Clifton <nickc@redhat.com> | 2013-01-10 09:49:22 +0000 |
commit | a3c629886c2cdaa6ee89513b64c7f989ba30eba3 (patch) | |
tree | 6076704fbfa023a83d161ff5d236d1cb0b7aefa3 /gas/config | |
parent | 9e9143bc874ab4742433b7498413313fb1723e8b (diff) | |
download | gdb-a3c629886c2cdaa6ee89513b64c7f989ba30eba3.zip gdb-a3c629886c2cdaa6ee89513b64c7f989ba30eba3.tar.gz gdb-a3c629886c2cdaa6ee89513b64c7f989ba30eba3.tar.bz2 |
* common.h: Fix case of "Meta".
* metag.h: New file.
* dis-asm.h (print_insn_metag): New declaration.
* metag.h: New file.
* Makefile.am: Add Meta.
* Makefile.in: Regenerate.
* configure: Regenerate.
* configure.in: Add Meta.
* disassemble.c: Add Meta support.
* metag-dis.c: New file.
* Makefile.am: Add Meta.
* Makefile.in: Regenerate.
* archures.c (bfd_mach_metag): New.
* bfd-in2.h: Regenerate.
* config.bfd: Add Meta.
* configure: Regenerate.
* configure.in: Add Meta.
* cpu-metag.c: New file.
* elf-bfd.h: Add Meta.
* elf32-metag.c: New file.
* elf32-metag.h: New file.
* libbfd.h: Regenerate.
* reloc.c: Add Meta relocations.
* targets.c: Add Meta.
* Makefile.am: Add Meta.
* Makefile.in: Regenerate.
* config/tc-metag.c: New file.
* config/tc-metag.h: New file.
* configure.tgt: Add Meta.
* doc/Makefile.am: Add Meta.
* doc/Makefile.in: Regenerate.
* doc/all.texi: Add Meta.
* doc/as.texiinfo: Document Meta options.
* doc/c-metag.texi: New file.
* gas/metag/labelarithmetic.d: New file.
* gas/metag/labelarithmetic.s: New file.
* gas/metag/metacore12.d: New file.
* gas/metag/metacore12.s: New file.
* gas/metag/metacore21-invalid.l: New file.
* gas/metag/metacore21-invalid.s: New file.
* gas/metag/metacore21.d: New file.
* gas/metag/metacore21.s: New file.
* gas/metag/metacore21ext.d: New file.
* gas/metag/metacore21ext.s: New file.
* gas/metag/metadsp21-invalid.l: New file.
* gas/metag/metadsp21-invalid.s: New file.
* gas/metag/metadsp21.d: New file.
* gas/metag/metadsp21.s: New file.
* gas/metag/metadsp21ext.d: New file.
* gas/metag/metadsp21ext.s: New file.
* gas/metag/metafpu21.d: New file.
* gas/metag/metafpu21.s: New file.
* gas/metag/metafpu21ext.d: New file.
* gas/metag/metafpu21ext.s: New file.
* gas/metag/metag.exp: New file.
* gas/metag/tls.d: New file.
* gas/metag/tls.s: New file.
* Makefile.am: Add Meta.
* Makefile.in: Regenerate.
* configure.tgt: Add Meta.
* emulparams/elf32metag.sh: New file.
* emultempl/metagelf.em: New file.
* ld-elf/merge.d: Mark Meta as xfail.
* ld-gc/start.d: Skip this test on Meta.
* ld-gc/personality.d: Skip this test on Meta.
* ld-metag/external.s: New file.
* ld-metag/metag.exp: New file.
* ld-metag/pcrel.d: New file.
* ld-metag/pcrel.s: New file.
* ld-metag/shared.d: New file.
* ld-metag/shared.r: New file.
* ld-metag/shared.s: New file.
* ld-metag/stub.d: New file.
* ld-metag/stub.s: New file.
* ld-metag/stub_pic_app.d: New file.
* ld-metag/stub_pic_app.r: New file.
* ld-metag/stub_pic_app.s: New file.
* ld-metag/stub_pic_shared.d: New file.
* ld-metag/stub_pic_shared.s: New file.
* ld-metag/stub_shared.d: New file.
* ld-metag/stub_shared.r: New file.
* ld-metag/stub_shared.s: New file.
* binutils/readelf.c: (guess_is_rela): Add EM_METAG.
(dump_relocations): Add EM_METAG.
(get_machine_name): Correct case for Meta.
(is_32bit_abs_reloc): Add support for Meta ADDR32 reloc.
(is_none_reloc): Add support for Meta NONE reloc.
Diffstat (limited to 'gas/config')
-rw-r--r-- | gas/config/tc-metag.c | 7115 | ||||
-rw-r--r-- | gas/config/tc-metag.h | 72 |
2 files changed, 7187 insertions, 0 deletions
diff --git a/gas/config/tc-metag.c b/gas/config/tc-metag.c new file mode 100644 index 0000000..d5e603a --- /dev/null +++ b/gas/config/tc-metag.c @@ -0,0 +1,7115 @@ +/* tc-metag.c -- Assembler for the Imagination Technologies Meta. + Copyright (C) 2013 Free Software Foundation, Inc. + Contributed by Imagination Technologies Ltd. + + 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 "subsegs.h" +#include "symcat.h" +#include "safe-ctype.h" +#include "hashtab.h" +#include "libbfd.h" + +#include <stdio.h> + +#include "opcode/metag.h" + +const char comment_chars[] = "!"; +const char line_comment_chars[] = "!#"; +const char line_separator_chars[] = ";"; +const char FLT_CHARS[] = "rRsSfFdDxXpP"; +const char EXP_CHARS[] = "eE"; +const char metag_symbol_chars[] = "["; + +static char register_chars[256]; +static char mnemonic_chars[256]; + +#define is_register_char(x) (register_chars[(unsigned char) x]) +#define is_mnemonic_char(x) (mnemonic_chars[(unsigned char) x]) +#define is_whitespace_char(x) (((x) == ' ') || ((x) == '\t')) +#define is_space_char(x) ((x) == ' ') + +#define FPU_PREFIX_CHAR 'f' +#define DSP_PREFIX_CHAR 'd' + +/* Instruction mnemonics that need disambiguating with respect to prefixes. */ +#define FFB_INSN "ffb" +#define DCACHE_INSN "dcache" +#define DEFR_INSN "defr" + +#define FPU_DOUBLE_CHAR 'd' +#define FPU_PAIR_CHAR 'l' + +#define DSP_DUAL_CHAR 'l' + +#define END_OF_INSN '\0' + +/* Maximum length of a mnemonic including all suffixes. */ +#define MAX_MNEMONIC_LEN 16 +/* Maximum length of a register name. */ +#define MAX_REG_LEN 17 + +/* Addressing modes must be enclosed with square brackets. */ +#define ADDR_BEGIN_CHAR '[' +#define ADDR_END_CHAR ']' +/* Immediates must be prefixed with a hash. */ +#define IMM_CHAR '#' + +#define COMMA ',' +#define PLUS '+' +#define MINUS '-' + +/* Short units are those that can be encoded with 2 bits. */ +#define SHORT_UNITS "D0, D1, A0 or A1" + +static unsigned int mcpu_opt = CoreMeta12; +static unsigned int mfpu_opt = 0; +static unsigned int mdsp_opt = 0; + +const char * md_shortopts = "m:"; + +struct option md_longopts[] = +{ + {NULL, no_argument, NULL, 0} +}; +size_t md_longopts_size = sizeof (md_longopts); + +/* Parser hash tables. */ +static htab_t mnemonic_htab; +static htab_t reg_htab; +static htab_t dsp_reg_htab; +static htab_t dsp_tmpl_reg_htab[2]; +static htab_t scond_htab; + +#define GOT_NAME "__GLOBAL_OFFSET_TABLE__" +symbolS * GOT_symbol; + +enum fpu_insn_width { + FPU_WIDTH_SINGLE, + FPU_WIDTH_DOUBLE, + FPU_WIDTH_PAIR, +}; + +#define FPU_ACTION_ABS_CHAR 'a' +#define FPU_ACTION_INV_CHAR 'i' +#define FPU_ACTION_QUIET_CHAR 'q' +#define FPU_ACTION_ZERO_CHAR 'z' + +#define FPU_ACTION_ABS 0x1 +#define FPU_ACTION_INV 0x2 +#define FPU_ACTION_QUIET 0x4 +#define FPU_ACTION_ZERO 0x8 + +enum dsp_insn_width { + DSP_WIDTH_SINGLE, + DSP_WIDTH_DUAL, +}; + +#define DSP_ACTION_QR64_CHAR 'q' +#define DSP_ACTION_UMUL_CHAR 'u' +#define DSP_ACTION_ROUND_CHAR 'r' +#define DSP_ACTION_CLAMP9_CHAR 'g' +#define DSP_ACTION_CLAMP8_CHAR 'b' +#define DSP_ACTION_MOD_CHAR 'm' +#define DSP_ACTION_ACC_ZERO_CHAR 'z' +#define DSP_ACTION_ACC_ADD_CHAR 'p' +#define DSP_ACTION_ACC_SUB_CHAR 'n' +#define DSP_ACTION_OV_CHAR 'o' + +#define DSP_ACTION_QR64 0x001 +#define DSP_ACTION_UMUL 0x002 +#define DSP_ACTION_ROUND 0x004 +#define DSP_ACTION_CLAMP9 0x008 +#define DSP_ACTION_CLAMP8 0x010 +#define DSP_ACTION_MOD 0x020 +#define DSP_ACTION_ACC_ZERO 0x040 +#define DSP_ACTION_ACC_ADD 0x080 +#define DSP_ACTION_ACC_SUB 0x100 +#define DSP_ACTION_OV 0x200 + +#define DSP_DAOPPAME_8_CHAR 'b' +#define DSP_DAOPPAME_16_CHAR 'w' +#define DSP_DAOPPAME_TEMP_CHAR 't' +#define DSP_DAOPPAME_HIGH_CHAR 'h' + +#define DSP_DAOPPAME_8 0x1 +#define DSP_DAOPPAME_16 0x2 +#define DSP_DAOPPAME_TEMP 0x4 +#define DSP_DAOPPAME_HIGH 0x8 + +/* Structure holding information about a parsed instruction. */ +typedef struct { + /* Instruction type. */ + enum insn_type type; + /* Split condition code. */ + enum scond_code scond; + + /* Instruction bits. */ + unsigned int bits; + /* Size of the instruction in bytes. */ + size_t len; + + /* FPU instruction encoding. */ + enum fpu_insn_width fpu_width; + unsigned int fpu_action_flags; + + /* DSP instruction encoding. */ + enum dsp_insn_width dsp_width; + unsigned int dsp_action_flags; + unsigned int dsp_daoppame_flags; + + /* Reloc encoding information, maximum of one reloc per insn. */ + enum bfd_reloc_code_real reloc_type; + int reloc_pcrel; + expressionS reloc_exp; + unsigned int reloc_size; +} metag_insn; + +/* Structure holding information about a parsed addressing mode. */ +typedef struct { + const metag_reg *base_reg; + const metag_reg *offset_reg; + + expressionS exp; + + enum bfd_reloc_code_real reloc_type; + + /* Whether we have an immediate or not. */ + unsigned short immediate:1; + /* Whether or not the base register is updated. */ + unsigned short update:1; + /* Whether the operation uses the address pre or post increment. */ + unsigned short post_increment:1; + /* Whether the immediate should be negated. */ + unsigned short negate:1; +} metag_addr; + +/* Linked list of possible parsers for this instruction. */ +typedef struct _insn_templates { + const insn_template *template; + struct _insn_templates *next; +} insn_templates; + +/* Parse an instruction that takes no operands. */ +static const char * +parse_none (const char *line, metag_insn *insn, + const insn_template *template) +{ + insn->bits = template->meta_opcode; + insn->len = 4; + return line; +} + +/* Return the next non-whitespace character in LINE or NULL. */ +static const char * +skip_whitespace (const char *line) +{ + const char *l = line; + + if (is_whitespace_char (*l)) + { + l++; + } + + return l; +} + +/* Return the next non-space character in LINE or NULL. */ +static const char * +skip_space (const char *line) +{ + const char *l = line; + + if (is_space_char (*l)) + { + l++; + } + + return l; +} + +/* Return the character after the current one in LINE if the current + character is a comma, otherwise NULL. */ +static const char * +skip_comma (const char *line) +{ + const char *l = line; + + if (l == NULL || *l != COMMA) + return NULL; + + l++; + + return l; +} + +/* Return the metag_reg struct corresponding to NAME or NULL if no such + register exists. */ +static const metag_reg * +parse_gp_reg (const char *name) +{ + const metag_reg *reg; + metag_reg entry; + + entry.name = name; + + reg = (const metag_reg *) htab_find (reg_htab, &entry); + + return reg; +} + +/* Parse a list of up to COUNT GP registers from LINE, returning the + registers parsed in REGS and the number parsed in REGS_READ. Return + a pointer to the next character or NULL. */ +static const char * +parse_gp_regs_list (const char *line, const metag_reg **regs, size_t count, + size_t *regs_read) +{ + const char *l = line; + char reg_buf[MAX_REG_LEN]; + int seen_regs = 0; + size_t i; + + for (i = 0; i < count; i++) + { + size_t len = 0; + const char *next; + + next = l; + + if (i > 0) + { + l = skip_comma (l); + if (l == NULL) + { + *regs_read = seen_regs; + return next; + } + } + + while (is_register_char (*l)) + { + reg_buf[len] = *l; + l++; + len++; + if (!(len < MAX_REG_LEN)) + return NULL; + } + + reg_buf[len] = '\0'; + + if (len) + { + const metag_reg *reg = parse_gp_reg (reg_buf); + + if (!reg) + { + *regs_read = seen_regs; + return next; + } + else + { + regs[i] = reg; + seen_regs++; + } + } + else + { + *regs_read = seen_regs; + return next; + } + } + + *regs_read = seen_regs; + return l; +} + +/* Parse a list of exactly COUNT GP registers from LINE, returning the + registers parsed in REGS. Return a pointer to the next character or NULL. */ +static const char * +parse_gp_regs (const char *line, const metag_reg **regs, size_t count) +{ + const char *l = line; + size_t regs_read = 0; + + l = parse_gp_regs_list (l, regs, count, ®s_read); + + if (regs_read != count) + return NULL; + else + return l; +} + +/* Parse a list of exactly COUNT FPU registers from LINE, returning the + registers parsed in REGS. Return a pointer to the next character or NULL. */ +static const char * +parse_fpu_regs (const char *line, const metag_reg **regs, size_t count) +{ + const char *l = line; + size_t regs_read = 0; + + l = parse_gp_regs_list (l, regs, count, ®s_read); + + if (regs_read != count) + return NULL; + else + { + size_t i; + for (i = 0; i < count; i++) + { + if (regs[i]->unit != UNIT_FX) + return NULL; + } + return l; + } +} + +/* Return TRUE if REG1 and REG2 are in paired units. */ +static bfd_boolean +is_unit_pair (const metag_reg *reg1, const metag_reg *reg2) +{ + if ((reg1->unit == UNIT_A0 && + (reg2->unit == UNIT_A1)) || + (reg1->unit == UNIT_A1 && + (reg2->unit == UNIT_A0)) || + (reg1->unit == UNIT_D0 && + (reg2->unit == UNIT_D1)) || + (reg1->unit == UNIT_D1 && + (reg2->unit == UNIT_D0))) + return TRUE; + + return FALSE; +} + +/* Return TRUE if REG1 and REG2 form a register pair. */ +static bfd_boolean +is_reg_pair (const metag_reg *reg1, const metag_reg *reg2) +{ + if (reg1->unit == UNIT_FX && + reg2->unit == UNIT_FX && + reg2->no == reg1->no + 1) + return TRUE; + + if (reg1->no != reg2->no) + return FALSE; + + return is_unit_pair (reg1, reg2); +} + +/* Parse a pair of GP registers from LINE, returning the registers parsed + in REGS. Return a pointer to the next character or NULL. */ +static const char * +parse_pair_gp_regs (const char *line, const metag_reg **regs) +{ + const char *l = line; + + l = parse_gp_regs (line, regs, 2); + + if (l == NULL) + { + l = parse_gp_regs (line, regs, 1); + + if (l == NULL) + return NULL; + + if (regs[0]->unit == UNIT_RD) + return l; + else + return NULL; + } + + if (is_reg_pair (regs[0], regs[1])) + return l; + + return NULL; +} + +/* Parse a unit-to-unit MOV instruction. */ +static const char * +parse_mov_u2u (const char *line, metag_insn *insn, + const insn_template *template) +{ + const metag_reg *regs[2]; + + line = parse_gp_regs (line, regs, 2); + + if (line == NULL) + return NULL; + + if (!mfpu_opt && (regs[0]->unit == UNIT_FX || regs[1]->unit == UNIT_FX)) + { + as_bad (_("no floating point unit specified")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (regs[1]->no << 19) | + (regs[0]->no << 14) | + (regs[1]->unit << 10) | + (regs[0]->unit << 5)); + insn->len = 4; + return line; +} + +/* Parse a MOV to port instruction. */ +static const char * +parse_mov_port (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + unsigned int is_movl = MINOR_OPCODE (template->meta_opcode) == MOVL_MINOR; + const metag_reg *dest_regs[2]; + const metag_reg *port_regs[1]; + + if (is_movl) + l = parse_gp_regs (l, dest_regs, 2); + else + l = parse_gp_regs (l, dest_regs, 1); + + if (l == NULL) + return NULL; + + if (template->insn_type == INSN_FPU && dest_regs[0]->unit != UNIT_FX) + return NULL; + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_gp_regs (l, port_regs, 1); + + if (l == NULL) + return NULL; + + if (port_regs[0]->unit != UNIT_RD || + port_regs[0]->no != 0) + return NULL; + + if (is_movl) + { + if (!is_unit_pair (dest_regs[0], dest_regs[1])) + return NULL; + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 14) | + (dest_regs[1]->no << 9) | + ((dest_regs[0]->unit & SHORT_UNIT_MASK) << 5)); + } + else + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 14) | + (dest_regs[0]->unit << 5)); + + insn->len = 4; + return l; +} + +/* Parse a MOVL to TTREC instruction. */ +static const char * +parse_movl_ttrec (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *src_regs[2]; + const metag_reg *dest_regs[1]; + + l = parse_gp_regs (l, dest_regs, 1); + + if (l == NULL) + return NULL; + + if (dest_regs[0]->unit != UNIT_TT || + dest_regs[0]->no != 3) + return NULL; + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_gp_regs (l, src_regs, 2); + + if (l == NULL) + return NULL; + + if (!is_unit_pair (src_regs[0], src_regs[1])) + return NULL; + + insn->bits = (template->meta_opcode | + (src_regs[0]->no << 19) | + (src_regs[1]->no << 14) | + ((src_regs[0]->unit & SHORT_UNIT_MASK) << 7)); + + insn->len = 4; + return l; +} + +/* Parse an incrementing or decrementing addressing mode. */ +static const char * +parse_addr_incr_op (const char *line, metag_addr *addr) +{ + const char *l = line; + const char *ll; + + ll = l + 1; + + if (*l == PLUS && + *ll == PLUS) + { + addr->update = 1; + ll++; + return ll; + } + else if (*l == MINUS && + *ll == MINUS) + { + addr->update = 1; + addr->negate = 1; + ll++; + return ll; + } + return NULL; +} + +/* Parse an pre-incrementing or pre-decrementing addressing mode. */ +static const char * +parse_addr_pre_incr_op (const char *line, metag_addr *addr) +{ + return parse_addr_incr_op (line, addr); +} + +/* Parse an post-incrementing or post-decrementing addressing mode. */ +static const char * +parse_addr_post_incr_op (const char *line, metag_addr *addr) +{ + const char *l; + + l = parse_addr_incr_op (line, addr); + + if (l == NULL) + return NULL; + + addr->post_increment = 1; + + return l; +} + +/* Parse an infix addressing mode. */ +static const char * +parse_addr_op (const char *line, metag_addr *addr) +{ + const char *l = line; + const char *ll; + + ll = l + 1; + + if (*l == PLUS) + { + if (*ll == PLUS) + { + addr->update = 1; + ll++; + return ll; + } + l++; + return l; + } + return NULL; +} + +/* Parse the immediate portion of an addrssing mode. */ +static const char * +parse_imm_addr (const char *line, metag_addr *addr) +{ + const char *l = line; + char *save_input_line_pointer; + expressionS *exp = &addr->exp; + + /* Skip #. */ + if (*l == '#') + l++; + else + return NULL; + + save_input_line_pointer = input_line_pointer; + input_line_pointer = (char *) l; + + expression (exp); + + l = input_line_pointer; + input_line_pointer = save_input_line_pointer; + + if (exp->X_op == O_absent || exp->X_op == O_big) + { + return NULL; + } + else if (exp->X_op == O_constant) + { + return l; + } + else + { + if (exp->X_op == O_PIC_reloc && + exp->X_md == BFD_RELOC_METAG_GETSET_GOT) + { + exp->X_op = O_symbol; + addr->reloc_type = BFD_RELOC_METAG_GETSET_GOT; + } + else if (exp->X_op == O_PIC_reloc && + exp->X_md == BFD_RELOC_METAG_TLS_IE) + { + exp->X_op = O_symbol; + addr->reloc_type = BFD_RELOC_METAG_TLS_IE; + } + else if (exp->X_op == O_PIC_reloc && + exp->X_md == BFD_RELOC_METAG_GOTOFF) + { + exp->X_op = O_symbol; + addr->reloc_type = BFD_RELOC_METAG_GETSET_GOTOFF; + } + else + addr->reloc_type = BFD_RELOC_METAG_GETSETOFF; + return l; + } +} + +/* Parse the offset portion of an addressing mode (register or immediate). */ +static const char * +parse_addr_offset (const char *line, metag_addr *addr, int size) +{ + const char *l = line; + const metag_reg *regs[1]; + + if (*l == IMM_CHAR) + { + /* ++ is a valid operator in our addressing but not in an expr. Make + sure that the expression parser never sees it. */ + char *ppp = strstr(l, "++"); + char ppch = '+'; + + if (ppp) + *ppp = '\0'; + + l = parse_imm_addr (l, addr); + + if (ppp) + *ppp = ppch; + + if (l == NULL) + return NULL; + + if (addr->exp.X_add_number % size) + { + as_bad (_("offset must be a multiple of %d"), size); + return NULL; + } + + addr->immediate = 1; + return l; + } + else + { + l = parse_gp_regs (l, regs, 1); + + if (l == NULL) + return NULL; + + if (regs[0]->unit != addr->base_reg->unit) + { + as_bad (_("offset and base must be from the same unit")); + return NULL; + } + + addr->offset_reg = regs[0]; + return l; + } +} + +/* Parse an addressing mode. */ +static const char * +parse_addr (const char *line, metag_addr *addr, unsigned int size) +{ + const char *l = line; + const char *ll; + const metag_reg *regs[1]; + + /* Skip opening square bracket. */ + l++; + + ll = parse_addr_pre_incr_op (l, addr); + + if (ll != NULL) + l = ll; + + l = parse_gp_regs (l, regs, 1); + + if (l == NULL) + return NULL; + + addr->base_reg = regs[0]; + + if (*l == ADDR_END_CHAR) + { + addr->exp.X_op = O_constant; + addr->exp.X_add_symbol = NULL; + addr->exp.X_op_symbol = NULL; + if (addr->update == 1) + { + /* We have a pre increment/decrement. */ + addr->exp.X_add_number = size; + } + else + { + /* Simple register with no offset (0 immediate). */ + addr->exp.X_add_number = 0; + } + addr->immediate = 1; + l++; + return l; + } + + /* We already had a pre increment/decrement. */ + if (addr->update == 1) + return NULL; + + ll = parse_addr_post_incr_op (l, addr); + + if (ll && *ll == ADDR_END_CHAR) + { + if (addr->update == 1) + { + /* We have a post increment/decrement. */ + addr->exp.X_op = O_constant; + addr->exp.X_add_number = size; + addr->exp.X_add_symbol = NULL; + addr->exp.X_op_symbol = NULL; + addr->post_increment = 1; + } + addr->immediate = 1; + ll++; + return ll; + } + + addr->post_increment = 0; + + l = parse_addr_op (l, addr); + + if (l == NULL) + return NULL; + + l = parse_addr_offset (l, addr, size); + + if (l == NULL) + return NULL; + + if (*l == ADDR_END_CHAR) + { + l++; + return l; + } + + /* We already had a pre increment/decrement. */ + if (addr->update == 1) + return NULL; + + l = parse_addr_post_incr_op (l, addr); + + if (l == NULL) + return NULL; + + if (*l == ADDR_END_CHAR) + { + l++; + return l; + } + + return NULL; +} + +/* Parse a GET or pipeline MOV instruction. */ +static const char * +parse_get (const char *line, const metag_reg **regs, metag_addr *addr, + unsigned int size, bfd_boolean is_mov) +{ + const char *l = line; + + if (size == 8) + { + l = parse_pair_gp_regs (l, regs); + + if (l == NULL) + return NULL; + } + else + { + l = parse_gp_regs (l, regs, 1); + + if (l == NULL) + { + if (!is_mov) + as_bad (_("invalid destination register")); + return NULL; + } + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_addr (l, addr, size); + + if (l == NULL) + { + if (!is_mov) + as_bad (_("invalid memory operand")); + return NULL; + } + + return l; +} + +/* Parse a SET instruction. */ +static const char * +parse_set (const char *line, const metag_reg **regs, metag_addr *addr, + unsigned int size) +{ + const char *l = line; + + l = parse_addr (l, addr, size); + + if (l == NULL) + { + as_bad (_("invalid memory operand")); + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + if (size == 8) + { + const char *ll = l; + + ll = parse_pair_gp_regs (l, regs); + + if (ll == NULL) + { + /* Maybe this is an RD register, which is 64 bits wide so needs no + pair. */ + l = parse_gp_regs (l, regs, 1); + + if (l == NULL || + regs[0]->unit != UNIT_RD) + { + return NULL; + } + } + else + l = ll; + } + else + { + l = parse_gp_regs (l, regs, 1); + + if (l == NULL) + { + as_bad (_("invalid source register")); + return NULL; + } + } + + return l; +} + +/* Check a signed integer value can be represented in the given number + of bits. */ +static bfd_boolean +within_signed_range (int value, unsigned int bits) +{ + int min_val = -(1 << (bits - 1)); + int max_val = (1 << (bits - 1)) - 1; + return (value <= max_val) && (value >= min_val); +} + +/* Check an unsigned integer value can be represented in the given number + of bits. */ +static bfd_boolean +within_unsigned_range (unsigned int value, unsigned int bits) +{ + return value < (unsigned int)(1 << bits); +} + +/* Return TRUE if UNIT can be expressed using a short code. */ +static bfd_boolean +is_short_unit (enum metag_unit unit) +{ + switch (unit) + { + case UNIT_A0: + case UNIT_A1: + case UNIT_D0: + case UNIT_D1: + return TRUE; + default: + return FALSE; + } +} + +/* Copy reloc data from ADDR to INSN. */ +static void +copy_addr_reloc (metag_insn *insn, metag_addr *addr) +{ + memcpy (&insn->reloc_exp, &addr->exp, sizeof(insn->reloc_exp)); + insn->reloc_type = addr->reloc_type; +} + +/* Parse a GET, SET or pipeline MOV instruction. */ +static const char * +parse_get_set (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + metag_addr addr; + unsigned int size = metag_get_set_size_bytes (template->meta_opcode); + bfd_boolean is_get = MAJOR_OPCODE (template->meta_opcode) == OPC_GET; + unsigned int reg_no; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + if (is_get) + { + bfd_boolean is_mov = strncmp (template->name, "MOV", 3) == 0; + + l = parse_get (l, regs, &addr, size, is_mov); + + if (l == NULL) + return NULL; + + if (!(regs[0]->unit == UNIT_D0 || + regs[0]->unit == UNIT_D1 || + regs[0]->unit == UNIT_A0 || + regs[0]->unit == UNIT_A1 || + (regs[0]->unit == UNIT_RD && is_mov) || + (regs[0]->unit == UNIT_CT && size == 4) || + (regs[0]->unit == UNIT_PC && size == 4) || + (regs[0]->unit == UNIT_TR && size == 4) || + (regs[0]->unit == UNIT_TT && (size == 4 || size == 8)) || + regs[0]->unit == UNIT_FX)) + { + as_bad (_("invalid destination unit")); + return NULL; + } + + if (regs[0]->unit == UNIT_RD) + { + if (regs[0]->no == 0) + { + as_bad (_("mov cannot use RD port as destination")); + return NULL; + } + } + + reg_no = regs[0]->no; + } + else + { + l = parse_set (l, regs, &addr, size); + + if (l == NULL) + return NULL; + + if (!(regs[0]->unit == UNIT_D0 || + regs[0]->unit == UNIT_D1 || + regs[0]->unit == UNIT_A0 || + regs[0]->unit == UNIT_A1 || + regs[0]->unit == UNIT_RD || + (regs[0]->unit == UNIT_CT && size == 4) || + (regs[0]->unit == UNIT_PC && size == 4) || + (regs[0]->unit == UNIT_TR && size == 4) || + (regs[0]->unit == UNIT_TT && (size == 4 || size == 8)) || + regs[0]->unit == UNIT_FX)) + { + as_bad (_("invalid source unit")); + return NULL; + } + + if (addr.immediate == 0 && + (regs[0]->unit == addr.base_reg->unit || + (size == 8 && is_unit_pair (regs[0], addr.base_reg)))) + { + as_bad (_("source and address units must not be shared for this addressing mode")); + return NULL; + } + + if (regs[0]->unit == UNIT_RD) + { + if (regs[0]->no != 0) + { + as_bad (_("set can only use RD port as source")); + return NULL; + } + reg_no = 16; + } + else + reg_no = regs[0]->no; + } + + insn->bits = (template->meta_opcode | + (reg_no << 19) | + (regs[0]->unit << 1)); + + if (!is_short_unit (addr.base_reg->unit)) + { + as_bad (_("base unit must be one of %s"), SHORT_UNITS); + return NULL; + } + + insn->bits |= ((addr.base_reg->no << 14) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5)); + + if (addr.immediate) + { + int offset = addr.exp.X_add_number; + + copy_addr_reloc (insn, &addr); + + if (addr.negate) + offset = -offset; + + offset = offset / (int)size; + + if (!within_signed_range (offset, GET_SET_IMM_BITS)) + { + /* We already tried to encode as an extended GET/SET. */ + as_bad (_("offset value out of range")); + return NULL; + } + + offset = offset & GET_SET_IMM_MASK; + + insn->bits |= (0x1 << 25); + insn->bits |= (offset << 8); + } + else + { + insn->bits |= (addr.offset_reg->no << 9); + } + + if (addr.update) + insn->bits |= (0x1 << 7); + + if (addr.post_increment) + insn->bits |= 0x1; + + insn->len = 4; + return l; +} + +/* Parse an extended GET or SET instruction. */ +static const char * +parse_get_set_ext (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + metag_addr addr; + unsigned int size = metag_get_set_ext_size_bytes (template->meta_opcode); + bfd_boolean is_get = MINOR_OPCODE (template->meta_opcode) == GET_EXT_MINOR; + bfd_boolean is_mov = MINOR_OPCODE (template->meta_opcode) == MOV_EXT_MINOR; + unsigned int reg_unit; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + if (is_get || is_mov) + { + l = parse_get (l, regs, &addr, size, is_mov); + } + else + { + l = parse_set (l, regs, &addr, size); + } + + if (l == NULL) + return NULL; + + /* Extended GET/SET does not support incrementing addressing. */ + if (addr.update) + return NULL; + + if (is_mov) + { + if (regs[0]->unit != UNIT_RD) + { + as_bad (_("destination unit must be RD")); + return NULL; + } + reg_unit = 0; + } + else + { + if (!is_short_unit (regs[0]->unit)) + { + return NULL; + } + reg_unit = regs[0]->unit; + } + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + ((reg_unit & SHORT_UNIT_MASK) << 3)); + + if (!is_short_unit (addr.base_reg->unit)) + { + as_bad (_("base unit must be one of %s"), SHORT_UNITS); + return NULL; + } + + if (addr.base_reg->no > 1) + { + return NULL; + } + + insn->bits |= ((addr.base_reg->no & EXT_BASE_REG_MASK) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5)); + + if (addr.immediate) + { + int offset = addr.exp.X_add_number; + + copy_addr_reloc (insn, &addr); + + if (addr.negate) + offset = -offset; + + offset = offset / (int)size; + + if (!within_signed_range (offset, GET_SET_EXT_IMM_BITS)) + { + /* Parsing as a standard GET/SET provides a smaller offset. */ + as_bad (_("offset value out of range")); + return NULL; + } + + offset = offset & GET_SET_EXT_IMM_MASK; + + insn->bits |= (offset << 7); + } + else + { + return NULL; + } + + insn->len = 4; + return l; +} + +/* Parse an MGET or MSET instruction addressing mode. */ +static const char * +parse_mget_mset_addr (const char *line, metag_addr *addr) +{ + const char *l = line; + const char *ll; + const metag_reg *regs[1]; + + /* Skip opening square bracket. */ + l++; + + l = parse_gp_regs (l, regs, 1); + + if (l == NULL) + return NULL; + + addr->base_reg = regs[0]; + + ll = parse_addr_post_incr_op (l, addr); + + if (ll != NULL) + l = ll; + + if (addr->negate == 1) + return NULL; + + if (*l == ADDR_END_CHAR) + { + l++; + return l; + } + + return NULL; +} + +/* Parse an MGET instruction. */ +static const char * +parse_mget (const char *line, const metag_reg **regs, metag_addr *addr, + size_t *regs_read) +{ + const char *l = line; + + l = parse_gp_regs_list (l, regs, MGET_MSET_MAX_REGS, regs_read); + + if (l == NULL || + *regs_read == 0) + { + as_bad (_("invalid destination register list")); + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_mget_mset_addr (l, addr); + + if (l == NULL) + { + as_bad (_("invalid memory operand")); + return NULL; + } + + return l; +} + +/* Parse an MSET instruction. */ +static const char * +parse_mset (const char *line, const metag_reg **regs, metag_addr *addr, + size_t *regs_read) +{ + const char *l = line; + + l = parse_mget_mset_addr (l, addr); + + if (l == NULL) + { + as_bad (_("invalid memory operand")); + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_gp_regs_list (l, regs, MGET_MSET_MAX_REGS, regs_read); + + if (l == NULL || + *regs_read == 0) + { + as_bad (_("invalid source register list")); + return NULL; + } + + return l; +} + +/* Take a register list REGS of size REGS_READ and convert it into an + rmask value if possible. Return the rmask value in RMASK and the + lowest numbered register in LOWEST_REG. Return TRUE if the conversion + was successful. */ +static bfd_boolean +check_rmask (const metag_reg **regs, size_t regs_read, bfd_boolean is_fpu, + bfd_boolean is_64bit, unsigned int *lowest_reg, + unsigned int *rmask) +{ + unsigned int reg_unit = regs[0]->unit; + size_t i; + + for (i = 0; i < regs_read; i++) + { + if (is_fpu) + { + if (is_64bit && regs[i]->no % 2) + { + as_bad (_("register list must be even numbered")); + return FALSE; + } + } + else if (regs[i]->unit != reg_unit) + { + as_bad (_("register list must be from the same unit")); + return FALSE; + } + + if (regs[i]->no < *lowest_reg) + *lowest_reg = regs[i]->no; + } + + for (i = 0; i < regs_read; i++) + { + unsigned int next_bit, next_reg; + if (regs[i]->no == *lowest_reg) + continue; + + if (is_fpu && is_64bit) + next_reg = ((regs[i]->no / 2) - ((*lowest_reg / 2) + 1)); + else + next_reg = (regs[i]->no - (*lowest_reg + 1)); + + next_bit = (1 << next_reg); + + if (*rmask & next_bit) + { + as_bad (_("register list must not contain duplicates")); + return FALSE; + } + + *rmask |= next_bit; + } + + return TRUE; +} + +/* Parse an MGET or MSET instruction. */ +static const char * +parse_mget_mset (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[MGET_MSET_MAX_REGS]; + metag_addr addr; + bfd_boolean is_get = MAJOR_OPCODE (template->meta_opcode) == OPC_GET; + bfd_boolean is_fpu = (MINOR_OPCODE (template->meta_opcode) & 0x6) == 0x6; + bfd_boolean is_64bit = (MINOR_OPCODE (template->meta_opcode) & 0x1) == 0x1; + size_t regs_read = 0; + unsigned int rmask = 0, reg_unit = 0, lowest_reg = 0xffffffff; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + if (is_get) + { + l = parse_mget (l, regs, &addr, ®s_read); + } + else + { + l = parse_mset (l, regs, &addr, ®s_read); + } + + if (l == NULL) + return NULL; + + if (!check_rmask (regs, regs_read, is_fpu, is_64bit, &lowest_reg, &rmask)) + return NULL; + + reg_unit = regs[0]->unit; + + if (is_fpu) + { + if (reg_unit != UNIT_FX) + return NULL; + + reg_unit = 0; + } + else if (reg_unit == UNIT_FX) + return NULL; + + insn->bits = (template->meta_opcode | + (lowest_reg << 19) | + ((reg_unit & SHORT_UNIT_MASK) << 3)); + + if (!is_short_unit (addr.base_reg->unit)) + { + as_bad (_("base unit must be one of %s"), SHORT_UNITS); + return NULL; + } + + insn->bits |= ((addr.base_reg->no << 14) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5)); + + insn->bits |= (rmask & RMASK_MASK) << 7; + + insn->len = 4; + return l; +} + +/* Parse a list of registers for MMOV pipeline prime. */ +static const char * +parse_mmov_prime_list (const char *line, const metag_reg **regs, + unsigned int *rmask) +{ + const char *l = line; + const metag_reg *ra_regs[MMOV_MAX_REGS]; + size_t regs_read = 0, i; + unsigned int mask = 0; + + l = parse_gp_regs_list (l, regs, 1, ®s_read); + + /* First register must be a port. */ + if (l == NULL || regs[0]->unit != UNIT_RD) + return NULL; + + l = skip_comma (l); + + if (l == NULL) + return NULL; + + l = parse_gp_regs_list (l, ra_regs, MMOV_MAX_REGS, ®s_read); + + if (l == NULL) + return NULL; + + /* Check remaining registers match the first. + + Note that we also accept RA (0x10) as input for the remaining registers. + Whilst this doesn't represent the instruction in any way we're stuck + with it because the embedded assembler accepts it. */ + for (i = 0; i < regs_read; i++) + { + if (ra_regs[i]->unit != UNIT_RD || + (ra_regs[i]->no != 0x10 && ra_regs[i]->no != regs[0]->no)) + return NULL; + + mask = (mask << 1) | 0x1; + } + + *rmask = mask; + + return l; +} + +/* Parse a MMOV instruction. */ +static const char * +parse_mmov (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + unsigned int is_fpu = template->insn_type == INSN_FPU; + unsigned int is_prime = ((MINOR_OPCODE (template->meta_opcode) & 0x2) && + !is_fpu); + unsigned int is_64bit = MINOR_OPCODE (template->meta_opcode) & 0x1; + unsigned int rmask = 0; + + if (is_prime) + { + const metag_reg *reg; + metag_addr addr; + + memset (&addr, 0, sizeof(addr)); + + l = parse_mmov_prime_list (l, ®, &rmask); + + if (l == NULL) + return NULL; + + l = skip_comma (l); + + if (l == NULL) + return NULL; + + l = parse_mget_mset_addr (l, &addr); + + if (l == NULL) + { + as_bad (_("invalid memory operand")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (reg->no << 19) | + (addr.base_reg->no << 14) | + ((rmask & RMASK_MASK) << 7) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5)); + } + else + { + const metag_reg *regs[MMOV_MAX_REGS + 1]; + unsigned int lowest_reg = 0xffffffff; + size_t regs_read = 0; + + l = parse_gp_regs_list (l, regs, MMOV_MAX_REGS + 1, ®s_read); + + if (l == NULL || regs_read == 0) + return NULL; + + if (!is_short_unit (regs[0]->unit) && + !(is_fpu && regs[0]->unit == UNIT_FX)) + { + return NULL; + } + + if (!(regs[regs_read-1]->unit == UNIT_RD && + regs[regs_read-1]->no == 0)) + { + return NULL; + } + + if (!check_rmask (regs, regs_read - 1, is_fpu, is_64bit, &lowest_reg, + &rmask)) + return NULL; + + if (is_fpu) + { + insn->bits = (template->meta_opcode | + (regs[0]->no << 14) | + ((rmask & RMASK_MASK) << 7)); + } + else + { + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + ((rmask & RMASK_MASK) << 7) | + ((regs[0]->unit & SHORT_UNIT_MASK) << 3)); + } + } + + insn->len = 4; + return l; +} + +/* Parse an immediate constant. */ +static const char * +parse_imm_constant (const char *line, metag_insn *insn, int *value) +{ + const char *l = line; + char *save_input_line_pointer; + expressionS *exp = &insn->reloc_exp; + + /* Skip #. */ + if (*l == '#') + l++; + else + return NULL; + + save_input_line_pointer = input_line_pointer; + input_line_pointer = (char *) l; + + expression (exp); + + l = input_line_pointer; + input_line_pointer = save_input_line_pointer; + + if (exp->X_op == O_constant) + { + *value = exp->X_add_number; + + return l; + } + else + { + return NULL; + } +} + +/* Parse an MDRD instruction. */ +static const char * +parse_mdrd (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + unsigned int rmask = 0; + int value = 0, i; + + l = parse_imm_constant (l, insn, &value); + + if (l == NULL) + return NULL; + + if (value < 1 || value > 8) + { + as_bad (_("MDRD value must be between 1 and 8")); + return NULL; + } + + for (i = 1; i < value; i++) + { + rmask <<= 1; + rmask |= 1; + } + + insn->bits = (template->meta_opcode | + (rmask << 7)); + + insn->len = 4; + return l; +} + +/* Parse a conditional SET instruction. */ +static const char * +parse_cond_set (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + metag_addr addr; + unsigned int size = metag_cond_set_size_bytes (template->meta_opcode); + unsigned int reg_no; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + l = parse_set (l, regs, &addr, size); + + if (l == NULL) + return NULL; + + if (regs[0]->unit == UNIT_RD) + { + if (regs[0]->no != 0) + { + as_bad (_("set can only use RD port as source")); + return NULL; + } + reg_no = 16; + } + else + reg_no = regs[0]->no; + + if (addr.update) + return NULL; + + if (!(addr.immediate && + addr.exp.X_add_number == 0)) + return NULL; + + insn->bits = (template->meta_opcode | + (reg_no << 19) | + (regs[0]->unit << 10)); + + if (!is_short_unit (addr.base_reg->unit)) + { + as_bad (_("base unit must be one of %s"), SHORT_UNITS); + return NULL; + } + + insn->bits |= ((addr.base_reg->no << 14) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5)); + + insn->len = 4; + return l; +} + +/* Parse an XFR instruction. */ +static const char * +parse_xfr (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + metag_addr dest_addr, src_addr; + unsigned int size = 4; + + memset(&dest_addr, 0, sizeof(dest_addr)); + memset(&src_addr, 0, sizeof(src_addr)); + dest_addr.reloc_type = BFD_RELOC_UNUSED; + src_addr.reloc_type = BFD_RELOC_UNUSED; + + l = parse_addr (l, &dest_addr, size); + + if (l == NULL || + dest_addr.immediate == 1) + { + as_bad (_("invalid destination memory operand")); + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_addr (l, &src_addr, size); + + if (l == NULL || + src_addr.immediate == 1) + { + as_bad (_("invalid source memory operand")); + return NULL; + } + + if (!is_short_unit (dest_addr.base_reg->unit) || + !is_short_unit (src_addr.base_reg->unit)) + { + as_bad (_("address units must be one of %s"), SHORT_UNITS); + return NULL; + } + + if ((dest_addr.base_reg->unit != dest_addr.offset_reg->unit) || + (src_addr.base_reg->unit != src_addr.offset_reg->unit)) + { + as_bad (_("base and offset must be from the same unit")); + return NULL; + } + + if (dest_addr.update == 1 && + src_addr.update == 1 && + dest_addr.post_increment != src_addr.post_increment) + { + as_bad (_("source and destination increment mode must agree")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (src_addr.base_reg->no << 19) | + (src_addr.offset_reg->no << 14) | + ((src_addr.base_reg->unit & SHORT_UNIT_MASK) << 2)); + + insn->bits |= ((dest_addr.base_reg->no << 9) | + (dest_addr.offset_reg->no << 4) | + ((dest_addr.base_reg->unit & SHORT_UNIT_MASK))); + + if (dest_addr.update == 1) + insn->bits |= (1 << 26); + + if (src_addr.update == 1) + insn->bits |= (1 << 27); + + if (dest_addr.post_increment == 1 || + src_addr.post_increment == 1) + insn->bits |= (1 << 24); + + insn->len = 4; + return l; +} + +/* Parse an 8bit immediate value. */ +static const char * +parse_imm8 (const char *line, metag_insn *insn, int *value) +{ + const char *l = line; + char *save_input_line_pointer; + expressionS *exp = &insn->reloc_exp; + + /* Skip #. */ + if (*l == '#') + l++; + else + return NULL; + + save_input_line_pointer = input_line_pointer; + input_line_pointer = (char *) l; + + expression (exp); + + l = input_line_pointer; + input_line_pointer = save_input_line_pointer; + + if (exp->X_op == O_absent || exp->X_op == O_big) + { + return NULL; + } + else if (exp->X_op == O_constant) + { + *value = exp->X_add_number; + } + else + { + insn->reloc_type = BFD_RELOC_METAG_REL8; + insn->reloc_pcrel = 0; + } + + return l; +} + +/* Parse a 16bit immediate value. */ +static const char * +parse_imm16 (const char *line, metag_insn *insn, int *value) +{ + const char *l = line; + char *save_input_line_pointer; + expressionS *exp = &insn->reloc_exp; + bfd_boolean is_hi = FALSE; + bfd_boolean is_lo = FALSE; + + /* Skip #. */ + if (*l == '#') + l++; + else + return NULL; + + if (strncasecmp (l, "HI", 2) == 0) + { + is_hi = TRUE; + l += 2; + } + else if (strncasecmp (l, "LO", 2) == 0) + { + is_lo = TRUE; + l += 2; + } + + save_input_line_pointer = input_line_pointer; + input_line_pointer = (char *) l; + + expression (exp); + + l = input_line_pointer; + input_line_pointer = save_input_line_pointer; + + if (exp->X_op == O_absent || exp->X_op == O_big) + { + return NULL; + } + else if (exp->X_op == O_constant) + { + if (is_hi) + *value = (exp->X_add_number >> 16) & IMM16_MASK; + else if (is_lo) + *value = exp->X_add_number & IMM16_MASK; + else + *value = exp->X_add_number; + } + else + { + if (exp->X_op == O_PIC_reloc) + { + exp->X_op = O_symbol; + + if (exp->X_md == BFD_RELOC_METAG_GOTOFF) + { + if (is_hi) + insn->reloc_type = BFD_RELOC_METAG_HI16_GOTOFF; + else if (is_lo) + insn->reloc_type = BFD_RELOC_METAG_LO16_GOTOFF; + else + return NULL; + } + else if (exp->X_md == BFD_RELOC_METAG_PLT) + { + if (is_hi) + insn->reloc_type = BFD_RELOC_METAG_HI16_PLT; + else if (is_lo) + insn->reloc_type = BFD_RELOC_METAG_LO16_PLT; + else + return NULL; + } + else if (exp->X_md == BFD_RELOC_METAG_TLS_LDO) + { + if (is_hi) + insn->reloc_type = BFD_RELOC_METAG_TLS_LDO_HI16; + else if (is_lo) + insn->reloc_type = BFD_RELOC_METAG_TLS_LDO_LO16; + else + return NULL; + } + else if (exp->X_md == BFD_RELOC_METAG_TLS_IENONPIC) + { + if (is_hi) + insn->reloc_type = BFD_RELOC_METAG_TLS_IENONPIC_HI16; + else if (is_lo) + insn->reloc_type = BFD_RELOC_METAG_TLS_IENONPIC_LO16; + else + return NULL; + } + else if (exp->X_md == BFD_RELOC_METAG_TLS_LE) + { + if (is_hi) + insn->reloc_type = BFD_RELOC_METAG_TLS_LE_HI16; + else if (is_lo) + insn->reloc_type = BFD_RELOC_METAG_TLS_LE_LO16; + else + return NULL; + } + else if (exp->X_md == BFD_RELOC_METAG_TLS_GD || + exp->X_md == BFD_RELOC_METAG_TLS_LDM) + insn->reloc_type = exp->X_md; + } + else + { + if (exp->X_op == O_symbol && exp->X_add_symbol == GOT_symbol) + { + if (is_hi) + insn->reloc_type = BFD_RELOC_METAG_HI16_GOTPC; + else if (is_lo) + insn->reloc_type = BFD_RELOC_METAG_LO16_GOTPC; + else + return NULL; + } + else + { + if (is_hi) + insn->reloc_type = BFD_RELOC_METAG_HIADDR16; + else if (is_lo) + insn->reloc_type = BFD_RELOC_METAG_LOADDR16; + else + insn->reloc_type = BFD_RELOC_METAG_REL16; + } + } + + insn->reloc_pcrel = 0; + } + + return l; +} + +/* Parse a MOV to control unit instruction. */ +static const char * +parse_mov_ct (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[1]; + unsigned int top = template->meta_opcode & 0x1; + unsigned int is_trace = (template->meta_opcode >> 2) & 0x1; + unsigned int sign_extend = 0; + int value = 0; + + l = parse_gp_regs (l, regs, 1); + + if (l == NULL) + return NULL; + + if (is_trace) + { + if (regs[0]->unit != UNIT_TT) + return NULL; + } + else + { + if (regs[0]->unit != UNIT_CT) + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_imm16 (l, insn, &value); + + if (l == NULL) + return NULL; + + if (value < 0) + sign_extend = 1; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + ((value & IMM16_MASK) << 3)); + + if (sign_extend == 1 && top == 0) + insn->bits |= (1 << 1); + + insn->len = 4; + return l; +} + +/* Parse a SWAP instruction. */ +static const char * +parse_swap (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + + l = parse_gp_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[1]->no << 19) | + (regs[0]->no << 14) | + (regs[1]->unit << 10) | + (regs[0]->unit << 5)); + + insn->len = 4; + return l; +} + +/* Parse a JUMP instruction. */ +static const char * +parse_jump (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[1]; + int value = 0; + + l = parse_gp_regs (l, regs, 1); + + if (l == NULL) + return NULL; + + if (!is_short_unit (regs[0]->unit)) + { + as_bad (_("register unit must be one of %s"), SHORT_UNITS); + return FALSE; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_imm16 (l, insn, &value); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[0]->unit & SHORT_UNIT_MASK) | + ((value & IMM16_MASK) << 3)); + + insn->len = 4; + return l; +} + +/* Parse a 19bit immediate value. */ +static const char * +parse_imm19 (const char *line, metag_insn *insn, int *value) +{ + const char *l = line; + char *save_input_line_pointer; + expressionS *exp = &insn->reloc_exp; + + /* Skip #. */ + if (*l == '#') + l++; + + save_input_line_pointer = input_line_pointer; + input_line_pointer = (char *) l; + + expression (exp); + + l = input_line_pointer; + input_line_pointer = save_input_line_pointer; + + if (exp->X_op == O_absent || exp->X_op == O_big) + { + return NULL; + } + else if (exp->X_op == O_constant) + { + *value = exp->X_add_number; + } + else + { + if (exp->X_op == O_PIC_reloc) + { + exp->X_op = O_symbol; + + if (exp->X_md == BFD_RELOC_METAG_PLT) + insn->reloc_type = BFD_RELOC_METAG_RELBRANCH_PLT; + else + return NULL; + } + else + insn->reloc_type = BFD_RELOC_METAG_RELBRANCH; + insn->reloc_pcrel = 1; + } + + return l; +} + +/* Parse a CALLR instruction. */ +static const char * +parse_callr (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[1]; + int value = 0; + + l = parse_gp_regs (l, regs, 1); + + if (l == NULL) + return NULL; + + if (!is_short_unit (regs[0]->unit)) + { + as_bad (_("link register unit must be one of %s"), SHORT_UNITS); + return NULL; + } + + if (regs[0]->no & ~CALLR_REG_MASK) + { + as_bad (_("link register must be in a low numbered register")); + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_imm19 (l, insn, &value); + + if (l == NULL) + return NULL; + + if (!within_signed_range (value / 4, IMM19_BITS)) + { + as_bad (_("target out of range")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (regs[0]->no & CALLR_REG_MASK) | + ((regs[0]->unit & SHORT_UNIT_MASK) << 3) | + ((value & IMM19_MASK) << 5)); + + insn->len = 4; + return l; +} + +/* Return the value for the register field if we apply the O2R modifier + to operand 2 REG, combined with UNIT_BIT derived from the destination + register or source1. Uses address unit O2R if IS_ADDR is set. */ +static int +lookup_o2r (unsigned int is_addr, unsigned int unit_bit, const metag_reg *reg) +{ + if (reg->no & ~O2R_REG_MASK) + return -1; + + if (is_addr) + { + if (unit_bit) + { + switch (reg->unit) + { + case UNIT_D1: + return reg->no; + case UNIT_D0: + return (1 << 3) | reg->no; + case UNIT_RD: + return (2 << 3) | reg->no; + case UNIT_A0: + return (3 << 3) | reg->no; + default: + return -1; + } + } + else + { + switch (reg->unit) + { + case UNIT_A1: + return reg->no; + case UNIT_D0: + return (1 << 3) | reg->no; + case UNIT_RD: + return (2 << 3) | reg->no; + case UNIT_D1: + return (3 << 3) | reg->no; + default: + return -1; + } + } + } + else + { + if (unit_bit) + { + switch (reg->unit) + { + case UNIT_A1: + return reg->no; + case UNIT_D0: + return (1 << 3) | reg->no; + case UNIT_RD: + return (2 << 3) | reg->no; + case UNIT_A0: + return (3 << 3) | reg->no; + default: + return -1; + } + } + else + { + switch (reg->unit) + { + case UNIT_A1: + return reg->no; + case UNIT_D1: + return (1 << 3) | reg->no; + case UNIT_RD: + return (2 << 3) | reg->no; + case UNIT_A0: + return (3 << 3) | reg->no; + default: + return -1; + } + } + } +} + +/* Parse GP ALU instruction. */ +static const char * +parse_alu (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *dest_regs[1]; + const metag_reg *src_regs[2]; + int value = 0; + unsigned int o1z = 0; + unsigned int imm = (template->meta_opcode >> 25) & 0x1; + unsigned int cond = (template->meta_opcode >> 26) & 0x1; + unsigned int ca = (template->meta_opcode >> 5) & 0x1; + unsigned int top = template->meta_opcode & 0x1; + unsigned int sign_extend = 0; + unsigned int is_addr_op = MAJOR_OPCODE (template->meta_opcode) == OPC_ADDR; + unsigned int is_mul = MAJOR_OPCODE (template->meta_opcode) == OPC_MUL; + unsigned int unit_bit = 0; + bfd_boolean is_quickrot = template->arg_type & GP_ARGS_QR; + + l = parse_gp_regs (l, dest_regs, 1); + + if (l == NULL) + return NULL; + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + if (is_addr_op) + { + if (dest_regs[0]->unit == UNIT_A0) + unit_bit = 0; + else if (dest_regs[0]->unit == UNIT_A1) + unit_bit = 1; + } + else + { + if (dest_regs[0]->unit == UNIT_D0) + unit_bit = 0; + else if (dest_regs[0]->unit == UNIT_D1) + unit_bit = 1; + } + + if ((MAJOR_OPCODE (template->meta_opcode) == OPC_ADDR || + MAJOR_OPCODE (template->meta_opcode) == OPC_ADD || + MAJOR_OPCODE (template->meta_opcode) == OPC_SUB) && + ((template->meta_opcode >> 2) & 0x1)) + o1z = 1; + + if (imm) + { + if (!cond) + { + if (is_addr_op) + { + if (dest_regs[0]->unit == UNIT_A0) + unit_bit = 0; + else if (dest_regs[0]->unit == UNIT_A1) + unit_bit = 1; + else + return NULL; + } + else + { + if (dest_regs[0]->unit == UNIT_D0) + unit_bit = 0; + else if (dest_regs[0]->unit == UNIT_D1) + unit_bit = 1; + else + return NULL; + } + } + + if (cond) + { + l = parse_gp_regs (l, src_regs, 1); + + if (l == NULL) + return NULL; + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + if (is_addr_op) + { + if (src_regs[0]->unit == UNIT_A0) + unit_bit = 0; + else if (src_regs[0]->unit == UNIT_A1) + unit_bit = 1; + else + return NULL; + } + else + { + if (src_regs[0]->unit == UNIT_D0) + unit_bit = 0; + else if (src_regs[0]->unit == UNIT_D1) + unit_bit = 1; + else + return NULL; + } + + if (src_regs[0]->unit != dest_regs[0]->unit && !ca) + return NULL; + + l = parse_imm8 (l, insn, &value); + + if (l == NULL) + return NULL; + + if (!within_unsigned_range (value, IMM8_BITS)) + return NULL; + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + (src_regs[0]->no << 14) | + ((value & IMM8_MASK) << 6)); + + if (ca) + { + if (is_addr_op) + { + if (src_regs[0]->unit == UNIT_A0) + unit_bit = 0; + else if (src_regs[0]->unit == UNIT_A1) + unit_bit = 1; + else + return NULL; + } + else + { + if (src_regs[0]->unit == UNIT_D0) + unit_bit = 0; + else if (src_regs[0]->unit == UNIT_D1) + unit_bit = 1; + else + return NULL; + } + + insn->bits |= dest_regs[0]->unit << 1; + } + } + else if (o1z) + { + l = parse_imm16 (l, insn, &value); + + if (l == NULL) + return NULL; + + if (value < 0) + { + if (!within_signed_range (value, IMM16_BITS)) + { + as_bad (_("immediate out of range")); + return NULL; + } + sign_extend = 1; + } + else + { + if (!within_unsigned_range (value, IMM16_BITS)) + { + as_bad (_("immediate out of range")); + return NULL; + } + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + ((value & IMM16_MASK) << 3)); + } + else + { + l = parse_gp_regs (l, src_regs, 1); + + if (l == NULL) + return NULL; + + if (!(src_regs[0]->unit == dest_regs[0]->unit)) + return NULL; + + /* CPC is valid for address ops. */ + if (src_regs[0]->no != dest_regs[0]->no && + !(is_addr_op && src_regs[0]->no == 0x10)) + return NULL; + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_imm16 (l, insn, &value); + + if (l == NULL) + return NULL; + + if (value < 0) + { + if (!within_signed_range (value, IMM16_BITS)) + { + as_bad (_("immediate out of range")); + return NULL; + } + sign_extend = 1; + } + else + { + if (!within_unsigned_range (value, IMM16_BITS)) + { + as_bad (_("immediate out of range")); + return NULL; + } + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + (src_regs[0]->no << 19) | + ((value & IMM16_MASK) << 3)); + } + } + else + { + unsigned int o2r = 0; + int rs2; + + if (cond || !o1z) + l = parse_gp_regs (l, src_regs, 2); + else + l = parse_gp_regs (l, src_regs, 1); + + if (l == NULL) + return NULL; + + if (cond || !o1z) + { + if (is_addr_op) + { + if (src_regs[0]->unit == UNIT_A0) + unit_bit = 0; + else if (src_regs[0]->unit == UNIT_A1) + unit_bit = 1; + else + return NULL; + } + else + { + if (src_regs[0]->unit == UNIT_D0) + unit_bit = 0; + else if (src_regs[0]->unit == UNIT_D1) + unit_bit = 1; + else + return NULL; + } + } + else + { + if (is_addr_op) + { + if (dest_regs[0]->unit == UNIT_A0) + unit_bit = 0; + else if (dest_regs[0]->unit == UNIT_A1) + unit_bit = 1; + else + return NULL; + } + else + { + if (dest_regs[0]->unit == UNIT_D0) + unit_bit = 0; + else if (dest_regs[0]->unit == UNIT_D1) + unit_bit = 1; + else + return NULL; + } + } + + if (cond) + { + if (src_regs[0]->unit != src_regs[1]->unit) + { + rs2 = lookup_o2r (is_addr_op, unit_bit, src_regs[1]); + + if (rs2 < 0) + return NULL; + + o2r = 1; + } + else + { + rs2 = src_regs[1]->no; + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + (src_regs[0]->no << 14) | + (rs2 << 9)); + + if (is_mul) + { + if (dest_regs[0]->unit != src_regs[0]->unit && is_mul) + { + if (ca) + { + insn->bits |= dest_regs[0]->unit << 1; + } + else + return NULL; + } + } + else + insn->bits |= dest_regs[0]->unit << 5; + } + else if (o1z) + { + if (dest_regs[0]->unit != src_regs[0]->unit) + { + rs2 = lookup_o2r (is_addr_op, unit_bit, src_regs[0]); + + if (rs2 < 0) + return NULL; + + o2r = 1; + } + else + { + rs2 = src_regs[0]->no; + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + (rs2 << 9)); + } + else + { + if (dest_regs[0]->unit != src_regs[0]->unit) + return NULL; + + if (dest_regs[0]->unit != src_regs[1]->unit) + { + rs2 = lookup_o2r (is_addr_op, unit_bit, src_regs[1]); + + if (rs2 < 0) + return NULL; + + o2r = 1; + } + else + { + rs2 = src_regs[1]->no; + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + (src_regs[0]->no << 14) | + (rs2 << 9)); + } + + if (o2r) + insn->bits |= 1; + } + + if (is_quickrot) + { + const metag_reg *qr_regs[1]; + bfd_boolean limit_regs = imm && cond; + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_gp_regs (l, qr_regs, 1); + + if (l == NULL) + return NULL; + + if (!((unit_bit == 0 && qr_regs[0]->unit != UNIT_A0) || + !(unit_bit == 1 && qr_regs[0]->unit != UNIT_A1))) + { + as_bad (_("invalid quickrot unit specified")); + return NULL; + } + + switch (qr_regs[0]->no) + { + case 2: + break; + case 3: + if (!limit_regs) + { + insn->bits |= (1 << 7); + break; + } + default: + as_bad (_("invalid quickrot register specified")); + return NULL; + } + } + + if (sign_extend == 1 && top == 0) + insn->bits |= (1 << 1); + + insn->bits |= unit_bit << 24; + insn->len = 4; + return l; +} + +/* Parse a B instruction. */ +static const char * +parse_branch (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + int value = 0; + + l = parse_imm19 (l, insn, &value); + + if (l == NULL) + return NULL; + + if (!within_signed_range (value / 4, IMM19_BITS)) + { + as_bad (_("target out of range")); + return NULL; + } + + insn->bits = (template->meta_opcode | + ((value & IMM19_MASK) << 5)); + + insn->len = 4; + return l; +} + +/* Parse a KICK instruction. */ +static const char * +parse_kick (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + + l = parse_gp_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + if (regs[1]->unit != UNIT_TR) + { + as_bad (_("source register must be in the trigger unit")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (regs[1]->no << 19) | + (regs[0]->no << 14) | + (regs[0]->unit << 5)); + + insn->len = 4; + return l; +} + +/* Parse a SWITCH instruction. */ +static const char * +parse_switch (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + int value = 0; + + l = parse_imm_constant (l, insn, &value); + + if (l == NULL) + return NULL; + + if (!within_unsigned_range (value, IMM24_BITS)) + { + as_bad (_("target out of range")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (value & IMM24_MASK)); + + insn->len = 4; + return l; +} + +/* Parse a shift instruction. */ +static const char * +parse_shift (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + const metag_reg *src2_regs[1]; + int value = 0; + unsigned int cond = (template->meta_opcode >> 26) & 0x1; + unsigned int ca = (template->meta_opcode >> 5) & 0x1; + unsigned int unit_bit = 0; + + l = parse_gp_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + if (regs[1]->unit == UNIT_D0) + unit_bit = 0; + else if (regs[1]->unit == UNIT_D1) + unit_bit = 1; + else + return NULL; + + if (regs[0]->unit != regs[1]->unit && !(cond && ca)) + return NULL; + + if (*l == '#') + { + l = parse_imm_constant (l, insn, &value); + + if (l == NULL) + return NULL; + + if (!within_unsigned_range (value, IMM5_BITS)) + return NULL; + + insn->bits = (template->meta_opcode | + (1 << 25) | + (regs[0]->no << 19) | + (regs[1]->no << 14) | + ((value & IMM5_MASK) << 9)); + } + else + { + l = parse_gp_regs (l, src2_regs, 1); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14) | + (src2_regs[0]->no << 9)); + + if (src2_regs[0]->unit != regs[1]->unit) + { + as_bad(_("Source registers must be in the same unit")); + return NULL; + } + } + + if (regs[0]->unit != regs[1]->unit) + { + if (cond && ca) + { + if (regs[1]->unit == UNIT_D0) + unit_bit = 0; + else if (regs[1]->unit == UNIT_D1) + unit_bit = 1; + else + return NULL; + + insn->bits |= ((1 << 5) | + (regs[0]->unit << 1)); + } + else + return NULL; + } + + insn->bits |= unit_bit << 24; + insn->len = 4; + return l; +} + +/* Parse a MIN or MAX instruction. */ +static const char * +parse_min_max (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[3]; + + l = parse_gp_regs (l, regs, 3); + + if (l == NULL) + return NULL; + + if (!(regs[0]->unit == UNIT_D0 || + regs[0]->unit == UNIT_D1)) + return NULL; + + if (!(regs[0]->unit == regs[1]->unit && + regs[1]->unit == regs[2]->unit)) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14) | + (regs[2]->no << 9)); + + if (regs[0]->unit == UNIT_D1) + insn->bits |= (1 << 24); + + insn->len = 4; + return l; +} + +/* Parse a bit operation instruction. */ +static const char * +parse_bitop (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + unsigned int swap_inst = MAJOR_OPCODE (template->meta_opcode) == OPC_MISC; + unsigned int is_bexl = 0; + + if (swap_inst && + ((template->meta_opcode >> 1) & 0xb) == 0xa) + is_bexl = 1; + + l = parse_gp_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + if (!(regs[0]->unit == UNIT_D0 || + regs[0]->unit == UNIT_D1)) + return NULL; + + if (is_bexl) + { + if (regs[0]->unit == UNIT_D0 && + regs[1]->unit != UNIT_D1) + return NULL; + else if (regs[0]->unit == UNIT_D1 && + regs[1]->unit != UNIT_D0) + return NULL; + } + else if (!(regs[0]->unit == regs[1]->unit)) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14)); + + if (swap_inst) + { + if (regs[1]->unit == UNIT_D1) + insn->bits |= 1; + } + else + { + if (regs[1]->unit == UNIT_D1) + insn->bits |= (1 << 24); + } + + insn->len = 4; + return l; +} + +/* Parse a CMP or TST instruction. */ +static const char * +parse_cmp (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *dest_regs[1]; + const metag_reg *src_regs[1]; + int value = 0; + unsigned int imm = (template->meta_opcode >> 25) & 0x1; + unsigned int cond = (template->meta_opcode >> 26) & 0x1; + unsigned int top = template->meta_opcode & 0x1; + unsigned int sign_extend = 0; + unsigned int unit_bit = 0; + + l = parse_gp_regs (l, dest_regs, 1); + + if (l == NULL) + return NULL; + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + if (dest_regs[0]->unit == UNIT_D0) + unit_bit = 0; + else if (dest_regs[0]->unit == UNIT_D1) + unit_bit = 1; + else + return NULL; + + if (imm) + { + if (cond) + { + l = parse_imm_constant (l, insn, &value); + + if (l == NULL) + return NULL; + + if (!within_unsigned_range (value, IMM8_BITS)) + return NULL; + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 14) | + ((value & IMM8_MASK) << 6)); + + } + else + { + l = parse_imm16 (l, insn, &value); + + if (l == NULL) + return NULL; + + if (value < 0) + { + if (!within_signed_range (value, IMM16_BITS)) + { + as_bad (_("immediate out of range")); + return NULL; + } + sign_extend = 1; + } + else + { + if (!within_unsigned_range (value, IMM16_BITS)) + { + as_bad (_("immediate out of range")); + return NULL; + } + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + ((value & IMM16_MASK) << 3)); + } + } + else + { + unsigned int o2r = 0; + int rs2; + + l = parse_gp_regs (l, src_regs, 1); + + if (l == NULL) + return NULL; + + if (dest_regs[0]->unit != src_regs[0]->unit) + { + rs2 = lookup_o2r (0, unit_bit, src_regs[0]); + + if (rs2 < 0) + return NULL; + + o2r = 1; + } + else + { + rs2 = src_regs[0]->no; + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 14) | + (rs2 << 9)); + + if (o2r) + insn->bits |= 1; + } + + if (sign_extend == 1 && top == 0) + insn->bits |= (1 << 1); + + insn->bits |= unit_bit << 24; + insn->len = 4; + return l; +} + +/* Parse a CACHEW instruction. */ +static const char * +parse_cachew (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *src_regs[2]; + unsigned int size = ((template->meta_opcode >> 1) & 0x1) ? 8 : 4; + metag_addr addr; + int offset; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + l = parse_addr (l, &addr, size); + + if (l == NULL || + !is_short_unit (addr.base_reg->unit) || + addr.update || + !addr.immediate) + { + as_bad (_("invalid memory operand")); + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + if (size == 4) + l = parse_gp_regs (l, src_regs, 1); + else + l = parse_pair_gp_regs (l, src_regs); + + if (l == NULL || + !is_short_unit (src_regs[0]->unit)) + { + as_bad (_("invalid source register")); + return NULL; + } + + offset = addr.exp.X_add_number; + + if (addr.negate) + offset = -offset; + + offset = offset / 64; + + if (!within_signed_range (offset, GET_SET_IMM_BITS)) + { + as_bad (_("offset value out of range")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (src_regs[0]->no << 19) | + (addr.base_reg->no << 14) | + ((offset & GET_SET_IMM_MASK) << 8) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5) | + ((src_regs[0]->unit & SHORT_UNIT_MASK) << 3)); + + insn->len = 4; + return l; +} + +/* Parse a CACHEW instruction. */ +static const char * +parse_cacher (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *dest_regs[2]; + unsigned int size = ((template->meta_opcode >> 1) & 0x1) ? 8 : 4; + metag_addr addr; + int offset; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + if (size == 4) + l = parse_gp_regs (l, dest_regs, 1); + else + l = parse_pair_gp_regs (l, dest_regs); + + if (l == NULL || + !is_short_unit (dest_regs[0]->unit)) + { + as_bad (_("invalid destination register")); + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_addr (l, &addr, size); + + if (l == NULL || + !is_short_unit (addr.base_reg->unit) || + addr.update || + !addr.immediate) + { + as_bad (_("invalid memory operand")); + return NULL; + } + + offset = addr.exp.X_add_number; + + if (addr.negate) + offset = -offset; + + offset = offset / (int)size; + + if (!within_signed_range (offset, GET_SET_IMM_BITS)) + { + as_bad (_("offset value out of range")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + (addr.base_reg->no << 14) | + ((offset & GET_SET_IMM_MASK) << 8) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5) | + ((dest_regs[0]->unit & SHORT_UNIT_MASK) << 3)); + + insn->len = 4; + return l; +} + +/* Parse an ICACHE instruction. */ +static const char * +parse_icache (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + int offset; + int pfcount; + + l = parse_imm_constant (l, insn, &offset); + + if (l == NULL) + return NULL; + + if (!within_signed_range (offset, IMM15_BITS)) + return NULL; + + l = skip_comma (l); + + l = parse_imm_constant (l, insn, &pfcount); + + if (l == NULL) + return NULL; + + if (!within_unsigned_range (pfcount, IMM4_BITS)) + return NULL; + + insn->bits = (template->meta_opcode | + ((offset & IMM15_MASK) << 9) | + ((pfcount & IMM4_MASK) << 1)); + + insn->len = 4; + return l; +} + +/* Parse a LNKGET instruction. */ +static const char * +parse_lnkget (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *dest_regs[2]; + unsigned int size = metag_get_set_ext_size_bytes (template->meta_opcode); + metag_addr addr; + int offset; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + if (size == 8) + l = parse_pair_gp_regs (l, dest_regs); + else + l = parse_gp_regs (l, dest_regs, 1); + + if (l == NULL || + !is_short_unit (dest_regs[0]->unit)) + { + as_bad (_("invalid destination register")); + return NULL; + } + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_addr (l, &addr, size); + + if (l == NULL || + !is_short_unit (addr.base_reg->unit) || + addr.update || + !addr.immediate) + { + as_bad (_("invalid memory operand")); + return NULL; + } + + offset = addr.exp.X_add_number; + + if (addr.negate) + offset = -offset; + + offset = offset / size; + + if (!within_signed_range (offset, GET_SET_IMM_BITS)) + { + as_bad (_("offset value out of range")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (dest_regs[0]->no << 19) | + (addr.base_reg->no << 14) | + ((offset & GET_SET_IMM_MASK) << 8) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5) | + ((dest_regs[0]->unit & SHORT_UNIT_MASK) << 3)); + + insn->len = 4; + return l; +} + +/* Parse an FPU MOV instruction. */ +static const char * +parse_fmov (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + + l = parse_fpu_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14)); + + if (insn->fpu_width == FPU_WIDTH_DOUBLE) + insn->bits |= (1 << 5); + else if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 6); + + insn->len = 4; + return l; +} + +/* Parse an FPU MMOV instruction. */ +static const char * +parse_fmmov (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + bfd_boolean to_fpu = MAJOR_OPCODE (template->meta_opcode) == OPC_GET; + bfd_boolean is_mmovl = MINOR_OPCODE (template->meta_opcode) & 0x1; + size_t regs_read = 0; + const metag_reg *regs[16]; + unsigned int lowest_data_reg = 0xffffffff; + unsigned int lowest_fpu_reg = 0xffffffff; + unsigned int rmask = 0, data_unit; + size_t i; + int last_reg = -1; + + if (insn->fpu_width != FPU_WIDTH_SINGLE) + return NULL; + + l = parse_gp_regs_list (l, regs, 16, ®s_read); + + if (l == NULL) + return NULL; + + if (regs_read % 2) + return NULL; + + if (to_fpu) + { + for (i = 0; i < regs_read / 2; i++) + { + if (regs[i]->unit != UNIT_FX) + return NULL; + + if (last_reg == -1) + { + last_reg = regs[i]->no; + lowest_fpu_reg = last_reg; + } + else + { + if (is_mmovl) + { + if (regs[i]->no != (unsigned int)(last_reg + 2)) + return NULL; + } + else if (regs[i]->no != (unsigned int)(last_reg + 1)) + return NULL; + + last_reg = regs[i]->no; + } + } + + if (regs[i]->unit == UNIT_D0) + data_unit = 0; + else if (regs[i]->unit == UNIT_D1) + data_unit = 1; + else + return NULL; + + if (!check_rmask (®s[i], regs_read / 2, TRUE, FALSE, &lowest_data_reg, + &rmask)) + return NULL; + } + else + { + if (regs[0]->unit == UNIT_D0) + data_unit = 0; + else if (regs[0]->unit == UNIT_D1) + data_unit = 1; + else + return NULL; + + if (!check_rmask (regs, regs_read / 2, TRUE, FALSE, &lowest_data_reg, + &rmask)) + return NULL; + + for (i = regs_read / 2; i < regs_read; i++) + { + if (regs[i]->unit != UNIT_FX) + return NULL; + + if (last_reg == -1) + { + last_reg = regs[i]->no; + lowest_fpu_reg = last_reg; + } + else + { + if (is_mmovl) + { + if (regs[i]->no != (unsigned int)(last_reg + 2)) + return NULL; + } + else if (regs[i]->no != (unsigned int)(last_reg + 1)) + return NULL; + + last_reg = regs[i]->no; + } + } + } + + insn->bits = (template->meta_opcode | + ((lowest_data_reg & REG_MASK) << 19) | + ((lowest_fpu_reg & REG_MASK) << 14) | + ((rmask & RMASK_MASK) << 7) | + data_unit); + + insn->len = 4; + return l; +} + +/* Parse an FPU data unit MOV instruction. */ +static const char * +parse_fmov_data (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + unsigned int to_fpu = ((template->meta_opcode >> 7) & 0x1); + const metag_reg *regs[2]; + unsigned int base_unit; + + if (insn->fpu_width == FPU_WIDTH_PAIR) + return NULL; + + l = parse_gp_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + if (to_fpu) + { + if (regs[0]->unit != UNIT_FX) + return NULL; + + if (regs[1]->unit == UNIT_D0) + base_unit = 0; + else if (regs[1]->unit == UNIT_D1) + base_unit = 1; + else + return NULL; + } + else + { + if (regs[0]->unit == UNIT_D0) + base_unit = 0; + else if (regs[0]->unit == UNIT_D1) + base_unit = 1; + else + return NULL; + + if (regs[1]->unit != UNIT_FX) + return NULL; + } + + insn->bits = (template->meta_opcode | + (base_unit << 24) | + (regs[0]->no << 19) | + (regs[1]->no << 9)); + + insn->len = 4; + return l; +} + +/* Parse an FPU immediate MOV instruction. */ +static const char * +parse_fmov_i (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[1]; + int value = 0; + + l = parse_fpu_regs (l, regs, 1); + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_imm16 (l, insn, &value); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + ((value & IMM16_MASK) << 3)); + + if (insn->fpu_width == FPU_WIDTH_DOUBLE) + insn->bits |= (1 << 1); + else if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 2); + + insn->len = 4; + return l; +} + +/* Parse an FPU PACK instruction. */ +static const char * +parse_fpack (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[3]; + + l = parse_fpu_regs (l, regs, 3); + + if (l == NULL) + return NULL; + + if (regs[0]->no % 2) + { + as_bad (_("destination register should be even numbered")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14) | + (regs[2]->no << 9)); + + insn->len = 4; + return l; +} + +/* Parse an FPU SWAP instruction. */ +static const char * +parse_fswap (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + + if (insn->fpu_width != FPU_WIDTH_PAIR) + return NULL; + + l = parse_fpu_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + if (regs[0]->no % 2) + return NULL; + + if (regs[1]->no % 2) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14)); + + insn->len = 4; + return l; +} + +/* Parse an FPU CMP instruction. */ +static const char * +parse_fcmp (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line, *l2; + const metag_reg *regs1[1]; + const metag_reg *regs2[1]; + + l = parse_fpu_regs (l, regs1, 1); + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l2 = parse_fpu_regs (l, regs2, 1); + + if (l2 != NULL) + { + insn->bits = (regs2[0]->no << 9); + } + else + { + int constant = 0; + l2 = parse_imm_constant (l, insn, &constant); + if (!l2 || constant != 0) + { + as_bad (_("comparison must be with register or #0")); + return NULL; + } + insn->bits = (1 << 8); + } + + insn->bits |= (template->meta_opcode | + (regs1[0]->no << 14)); + + if (insn->fpu_action_flags & FPU_ACTION_ABS) + insn->bits |= (1 << 19); + + if (insn->fpu_action_flags & FPU_ACTION_QUIET) + insn->bits |= (1 << 7); + + if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 6); + else if (insn->fpu_width == FPU_WIDTH_DOUBLE) + insn->bits |= (1 << 5); + + insn->len = 4; + return l2; +} + +/* Parse an FPU MIN or MAX instruction. */ +static const char * +parse_fminmax (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[3]; + + l = parse_fpu_regs (l, regs, 3); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14) | + (regs[2]->no << 9)); + + if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 6); + else if (insn->fpu_width == FPU_WIDTH_DOUBLE) + insn->bits |= (1 << 5); + + insn->len = 4; + return l; +} + +/* Parse an FPU data conversion instruction. */ +static const char * +parse_fconv (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + + if (insn->fpu_width == FPU_WIDTH_PAIR) + { + if (strncasecmp (template->name, "FTOH", 4) && + strncasecmp (template->name, "HTOF", 4) && + strncasecmp (template->name, "FTOI", 4) && + strncasecmp (template->name, "ITOF", 4)) + { + as_bad (_("instruction cannot operate on pair values")); + return NULL; + } + } + + if (insn->fpu_action_flags & FPU_ACTION_ZERO) + { + if (strncasecmp (template->name, "FTOI", 4) && + strncasecmp (template->name, "DTOI", 4) && + strncasecmp (template->name, "DTOL", 4)) + { + as_bad (_("zero flag is not valid for this instruction")); + return NULL; + } + } + + l = parse_fpu_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + if (!strncasecmp (template->name, "DTOL", 4) || + !strncasecmp (template->name, "LTOD", 4)) + { + if (regs[0]->no % 2) + { + as_bad (_("destination register should be even numbered")); + return NULL; + } + + if (regs[1]->no % 2) + { + as_bad (_("source register should be even numbered")); + return NULL; + } + } + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14)); + + if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 6); + + if (insn->fpu_action_flags & FPU_ACTION_ZERO) + insn->bits |= (1 << 12); + + insn->len = 4; + return l; +} + +/* Parse an FPU extended data conversion instruction. */ +static const char * +parse_fconvx (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + int fraction_bits = 0; + + if (insn->fpu_width == FPU_WIDTH_PAIR) + { + if (strncasecmp (template->name, "FTOX", 4) && + strncasecmp (template->name, "XTOF", 4)) + { + as_bad (_("instruction cannot operate on pair values")); + return NULL; + } + } + + l = parse_fpu_regs (l, regs, 2); + + l = skip_comma (l); + + if (l == NULL || + *l == END_OF_INSN) + return NULL; + + l = parse_imm_constant (l, insn, &fraction_bits); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14)); + + if (strncasecmp (template->name, "DTOXL", 5) && + strncasecmp (template->name, "XLTOD", 5)) + { + if (!within_unsigned_range (fraction_bits, IMM5_BITS)) + { + as_bad (_("fraction bits value out of range")); + return NULL; + } + insn->bits |= ((fraction_bits & IMM5_MASK) << 9); + } + else + { + if (!within_unsigned_range (fraction_bits, IMM6_BITS)) + { + as_bad (_("fraction bits value out of range")); + return NULL; + } + insn->bits |= ((fraction_bits & IMM6_MASK) << 8); + } + + if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 6); + + insn->len = 4; + return l; +} + +/* Parse an FPU basic arithmetic instruction. */ +static const char * +parse_fbarith (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[3]; + + l = parse_fpu_regs (l, regs, 3); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14) | + (regs[2]->no << 9)); + + if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 6); + else if (insn->fpu_width == FPU_WIDTH_DOUBLE) + insn->bits |= (1 << 5); + + if (insn->fpu_action_flags & FPU_ACTION_INV) + insn->bits |= (1 << 7); + + insn->len = 4; + return l; +} + +/* Parse a floating point accumulator name. */ +static const char * +parse_acf (const char *line, int *part) +{ + const char *l = line; + size_t i; + + for (i = 0; i < sizeof(metag_acftab)/sizeof(metag_acftab[0]); i++) + { + const metag_acf *acf = &metag_acftab[i]; + size_t name_len = strlen (acf->name); + + if (strncasecmp (l, acf->name, name_len) == 0) + { + l += name_len; + *part = acf->part; + return l; + } + } + return NULL; +} + +/* Parse an FPU extended arithmetic instruction. */ +static const char * +parse_fearith (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[3]; + bfd_boolean is_muz = (MINOR_OPCODE (template->meta_opcode) == 0x6 && + ((template->meta_opcode >> 4) & 0x1)); + unsigned int is_o3o = template->meta_opcode & 0x1; + unsigned int is_mac = 0; + unsigned int is_maw = 0; + + if (!strncasecmp (template->name, "MAW", 3)) + is_maw = 1; + + if (!strncasecmp (template->name, "MAC", 3)) + { + int part; + l = parse_acf (l, &part); + + if (l == NULL || part != 0) + return NULL; + + l = skip_comma (l); + + l = parse_fpu_regs (l, ®s[1], 2); + + is_mac = 1; + } + else + { + if (is_o3o && is_maw) + l = parse_fpu_regs (l, regs, 2); + else + l = parse_fpu_regs (l, regs, 3); + } + + if (l == NULL) + return NULL; + + if (is_o3o && is_maw) + insn->bits = (template->meta_opcode | + (regs[1]->no << 9)); + else + insn->bits = (template->meta_opcode | + (regs[1]->no << 14)); + + if (!(is_o3o && is_maw)) + insn->bits |= (regs[2]->no << 9); + + if (is_o3o && is_maw) + insn->bits |= (regs[0]->no << 14); + else if (!is_mac) + insn->bits |= (regs[0]->no << 19); + + if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 6); + else if (insn->fpu_width == FPU_WIDTH_DOUBLE) + insn->bits |= (1 << 5); + + if (!is_mac && !is_maw) + if (insn->fpu_action_flags & FPU_ACTION_INV) + insn->bits |= (1 << 7); + + if (is_muz) + if (insn->fpu_action_flags & FPU_ACTION_QUIET) + insn->bits |= (1 << 1); + + insn->len = 4; + return l; +} + +/* Parse an FPU RCP or RSQ instruction. */ +static const char * +parse_frec (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[2]; + + l = parse_fpu_regs (l, regs, 2); + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14)); + + if (insn->fpu_width == FPU_WIDTH_PAIR) + insn->bits |= (1 << 6); + else if (insn->fpu_width == FPU_WIDTH_DOUBLE) + insn->bits |= (1 << 5); + + if (insn->fpu_action_flags & FPU_ACTION_ZERO) + insn->bits |= (1 << 10); + else if (insn->fpu_action_flags & FPU_ACTION_QUIET) + insn->bits |= (1 << 9); + + if (insn->fpu_action_flags & FPU_ACTION_INV) + insn->bits |= (1 << 7); + + insn->len = 4; + return l; +} + +/* Parse an FPU vector arithmetic instruction. */ +static const char * +parse_fsimd (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[3]; + + if (insn->fpu_width != FPU_WIDTH_PAIR) + { + as_bad (_("simd instructions operate on pair values (L prefix)")); + return NULL; + } + + l = parse_fpu_regs (l, regs, 3); + + if (l == NULL) + return NULL; + + if (regs[0]->no % 2) + { + as_bad (_("destination register should be even numbered")); + return NULL; + } + + if ((regs[1]->no % 2) || + (regs[2]->no % 2)) + { + as_bad (_("source registers should be even numbered")); + return NULL; + } + + insn->bits = (template->meta_opcode | + (regs[0]->no << 19) | + (regs[1]->no << 14) | + (regs[2]->no << 9)); + + if (insn->fpu_action_flags & FPU_ACTION_INV) + insn->bits |= (1 << 7); + + insn->len = 4; + return l; +} + +/* Parse an FPU accumulator GET or SET instruction. */ +static const char * +parse_fget_set_acf (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + int part; + metag_addr addr; + bfd_boolean is_get = MAJOR_OPCODE (template->meta_opcode) == OPC_GET; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + if (is_get) + { + l = parse_acf (l, &part); + + l = skip_comma (l); + + if (l == NULL) + return NULL; + + l = parse_mget_mset_addr (l, &addr); + } + else + { + l = parse_mget_mset_addr (l, &addr); + + l = skip_comma (l); + + if (l == NULL) + return NULL; + + l = parse_acf (l, &part); + } + + if (l == NULL) + return NULL; + + insn->bits = (template->meta_opcode | + (part << 19)); + + if (!is_short_unit (addr.base_reg->unit)) + { + as_bad (_("base unit must be one of %s"), SHORT_UNITS); + return NULL; + } + + insn->bits |= ((addr.base_reg->no << 14) | + ((addr.base_reg->unit & SHORT_UNIT_MASK) << 5)); + + insn->len = 4; + return l; +} + +/* Copy the name of the next register in LINE to REG_BUF. */ +static size_t +strip_reg_name(const char *line, char *reg_buf) +{ + const char *l = line; + size_t len = 0; + + while (is_register_char (*l)) + { + reg_buf[len] = *l; + l++; + len++; + if (!(len < MAX_REG_LEN)) + return 0; + } + + if (len) + reg_buf[len] = '\0'; + + return len; +} + +/* Parse a DSP register from LINE into REG using only the registers + from DSP_REGTAB. Return the next character or NULL. */ +static const char * +__parse_dsp_reg (const char *line, const metag_reg **reg, htab_t dsp_regtab) +{ + const char *l = line; + char name[MAX_REG_LEN]; + size_t len = 0; + metag_reg entry; + const metag_reg *_reg; + + /* We don't entirely strip the register name because we might + actually want to match whole string in the register table, + e.g. "D0AW.1++" not just "D0AW.1". The string length of the table + entry limits our comaprison to a reasonable bound anyway. */ + while (is_register_char (*l) || *l == PLUS) + { + name[len] = *l; + l++; + len++; + if (!(len < MAX_REG_LEN)) + return NULL; + } + + if (!len) + return NULL; + + name[len] = '\0'; + entry.name = name; + + _reg = (const metag_reg *) htab_find (dsp_regtab, &entry); + if (!_reg) + return NULL; + + *reg = _reg; + + return l; +} + +/* Parse a DSP register and setup "reg" with a metag_reg whose "no" + member is suitable for encoding into a DSP insn register field. */ +static const char * +parse_dsp_insn_reg (const char *line, const metag_reg **reg) +{ + return __parse_dsp_reg (line, reg, dsp_reg_htab); +} + +/* Parse a DSP register and setup "reg" with a metag_reg whose "no" + member is suitable for encoding into a DSP template definition insn + register field. + + There is a separate table for whether we're doing a load or a store + definition. "load" specifies which table to look at. */ +static const char * +parse_dsp_template_reg (const char *line, const metag_reg **reg, + bfd_boolean load) +{ + return __parse_dsp_reg (line, reg, dsp_tmpl_reg_htab[load]); +} + +/* Parse a single DSP register from LINE. */ +static const char * +parse_dsp_reg (const char *line, const metag_reg **reg, + bfd_boolean tmpl, bfd_boolean load) +{ + if (tmpl) + return parse_dsp_template_reg (line, reg, load); + else + return parse_dsp_insn_reg (line, reg); +} + +/* Return TRUE if UNIT is an address unit. */ +static bfd_boolean +is_addr_unit (enum metag_unit unit) +{ + switch (unit) + { + case UNIT_A0: + case UNIT_A1: + return TRUE; + default: + return FALSE; + } +} + +/* Return TRUE if UNIT1 and UNIT2 are equivalent units. */ +static bfd_boolean +is_same_data_unit (enum metag_unit unit1, enum metag_unit unit2) +{ + if (unit1 == unit2) + return TRUE; + + switch (unit1) + { + case UNIT_D0: + if (unit2 == UNIT_ACC_D0 || unit2 == UNIT_RAM_D0) + return TRUE; + break; + case UNIT_D1: + if (unit2 == UNIT_ACC_D1 || unit2 == UNIT_RAM_D1) + return TRUE; + break; + case UNIT_ACC_D0: + if (unit2 == UNIT_D0 || unit2 == UNIT_RAM_D0) + return TRUE; + break; + case UNIT_ACC_D1: + if (unit2 == UNIT_D1 || unit2 == UNIT_RAM_D1) + return TRUE; + break; + case UNIT_RAM_D0: + if (unit2 == UNIT_ACC_D0 || unit2 == UNIT_D0) + return TRUE; + break; + case UNIT_RAM_D1: + if (unit2 == UNIT_ACC_D1 || unit2 == UNIT_D1) + return TRUE; + break; + default: + return FALSE; + } + + return FALSE; +} + +/* Return TRUE if the register NUM is a quickrot control register. */ +static bfd_boolean +is_quickrot_reg (unsigned int num) +{ + switch (num) + { + case 2: + case 3: + return TRUE; + } + + return FALSE; +} + +/* Return TRUE if REG is an accumulator register. */ +static bfd_boolean +is_accumulator_reg (const metag_reg *reg) +{ + if (reg->unit == UNIT_ACC_D0 || reg->unit == UNIT_ACC_D1) + return TRUE; + + return FALSE; +} + +/* Return TRUE if REG is a DSP RAM register. */ +static bfd_boolean +is_dspram_reg (const metag_reg *reg) +{ + if (reg->unit == UNIT_RAM_D0 || reg->unit == UNIT_RAM_D1) + return TRUE; + + return FALSE; +} + +static const char * +__parse_gp_reg (const char *line, const metag_reg **reg, bfd_boolean load) +{ + const char *l = line; + char reg_buf[MAX_REG_LEN]; + size_t len = 0; + + if (l == NULL) + return NULL; + + /* Parse [DSPRAM.x]. */ + if (*l == ADDR_BEGIN_CHAR) + { + l++; + + if (l == NULL) + return NULL; + + l = parse_dsp_reg (l, reg, TRUE, load); + if (l == NULL) + return NULL; + + if (*l == ADDR_END_CHAR) + l++; + else + { + as_bad (_("expected ']', not %c in %s"), *l, l); + return NULL; + } + + return l; + } + else + { + + len = strip_reg_name (l, reg_buf); + if (!len) + return NULL; + + l += len; + *reg = parse_gp_reg (reg_buf); + if (*reg == NULL) + return NULL; + } + + return l; +} + +/* Parse a list of DSP/GP registers. TRY_GP indicates whether we + should try to parse the register as a general-purpose register if + we fail to parse it as a DSP one. TMPL indicates whether the + registers are part of a template definition instruction. If this is + a template definition instruction LOAD says whether it's a load + template insn. FIRST_DST indicates whether the first register is + a destination operand. */ +static const char * +parse_dsp_regs_list (const char *line, const metag_reg **regs, size_t count, + size_t *regs_read, bfd_boolean try_gp, bfd_boolean tmpl, + bfd_boolean load, bfd_boolean first_dst) +{ + const char *l = line; + int seen_regs = 0; + size_t i; + const metag_reg *reg; + + for (i = 0; i < count; i++) + { + const char *next, *ll; + + next = l; + + if (i > 0) + { + l = skip_comma (l); + if (l == NULL) + { + *regs_read = seen_regs; + return next; + } + } + + ll = parse_dsp_reg (l, ®, tmpl, load); + + if (!ll) + { + if (try_gp) + { + l = __parse_gp_reg (l, ®, !(first_dst && i == 0)); + if (l == NULL) + { + *regs_read = seen_regs; + return next; + } + regs[i] = reg; + seen_regs++; + } + else + { + *regs_read = seen_regs; + return l; + } + } + else + { + regs[i] = reg; + seen_regs++; + l = ll; + } + } + + *regs_read = seen_regs; + return l; +} + +/* Parse the following memory references: + + - [Ax.r] + - [Ax.r++] + - [Ax.r--] + - [Ax.r+Ax.r++] + - [Ax.r-Ax.r--] + + - [DSPRam] + - [DSPRam++] + - [DSPRam+DSPRam++] + - [DSPRam-DSPRam--] */ +static const char * +parse_dsp_addr (const char *line, metag_addr *addr, unsigned int size, + bfd_boolean load) +{ + const char *l = line, *ll; + const metag_reg *regs[1]; + size_t regs_read; + + /* Skip opening square bracket. */ + l++; + + l = parse_dsp_regs_list (l, regs, 1, ®s_read, TRUE, TRUE, load, FALSE); + + if (l == NULL) + return NULL; + + if (!is_addr_unit (regs[0]->unit) && + !is_dspram_reg (regs[0])) + { + as_bad (_("invalid register for memory access")); + return NULL; + } + + addr->base_reg = regs[0]; + + if (*l == ADDR_END_CHAR) + { + addr->exp.X_op = O_constant; + addr->exp.X_add_symbol = NULL; + addr->exp.X_op_symbol = NULL; + + /* Simple register with no offset (0 immediate). */ + addr->exp.X_add_number = 0; + + addr->immediate = 1; + l++; + + return l; + } + + ll = parse_addr_post_incr_op (l, addr); + + if (ll && *ll == ADDR_END_CHAR) + { + if (addr->update == 1) + { + /* We have a post increment/decrement. */ + addr->exp.X_op = O_constant; + addr->exp.X_add_number = size; + addr->exp.X_add_symbol = NULL; + addr->exp.X_op_symbol = NULL; + addr->post_increment = 1; + } + addr->immediate = 1; + ll++; + return ll; + } + + addr->post_increment = 0; + + l = parse_addr_op (l, addr); + + if (l == NULL) + return NULL; + + l = parse_dsp_regs_list (l, regs, 1, ®s_read, TRUE, TRUE, load, FALSE); + + if (l == NULL) + return NULL; + + if (regs[0]->unit != addr->base_reg->unit) + { + as_bad (_("offset and base must be from the same unit")); + return NULL; + } + + addr->offset_reg = regs[0]; + + if (*l == ADDR_END_CHAR) + { + l++; + return l; + } + + l = parse_addr_post_incr_op (l, addr); + + if (l == NULL) + return NULL; + + if (*l == ADDR_END_CHAR) + { + l++; + return l; + } + + return NULL; +} + +/* Parse a DSP GET or SET instruction. */ +static const char * +parse_dget_set (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + metag_addr addr; + int unit = 0; + int rd_reg = 0; + bfd_boolean is_get = (template->meta_opcode & 0x100); + bfd_boolean is_dual = (template->meta_opcode & 0x4); + bfd_boolean is_template = FALSE; + const metag_reg *regs[2]; + unsigned int size; + size_t count, regs_read; + + memset(&addr, 0, sizeof(addr)); + addr.reloc_type = BFD_RELOC_UNUSED; + + size = is_dual ? 8 : 4; + count = is_dual ? 2 : 1; + + if (is_get) + { + /* GETL can be used on one template table entry. */ + if (*l == 'T') + count = 1; + + l = parse_dsp_regs_list (l, regs, count, ®s_read, FALSE, + FALSE, FALSE, FALSE); + l = skip_comma (l); + + if (l == NULL) + { + as_bad (_("unexpected end of line")); + return NULL; + } + + l = parse_addr (l, &addr, size); + } + else + { + l = parse_addr (l, &addr, size); + + l = skip_comma (l); + + if (l == NULL) + return NULL; + + /* GETL can be used on one template table entry. */ + if (*l == 'T') + count = 1; + + l = parse_dsp_regs_list (l, regs, count, ®s_read, FALSE, FALSE, + FALSE, FALSE); + } + + if (l == NULL) + return NULL; + + /* The first register dictates the unit. */ + if (regs[0]->unit == UNIT_DT) + is_template = TRUE; + else + { + if (regs[0]->unit == UNIT_D0 || regs[0]->unit == UNIT_RAM_D0 || + regs[0]->unit == UNIT_ACC_D0) + unit = 0; + else + unit = 1; + } + + rd_reg = regs[0]->no; + + /* The 'H' modifier allows a DSP GET/SET instruction to target the + upper 8-bits of an accumulator. It is _only_ valid for the + accumulators. */ + if (insn->dsp_daoppame_flags & DSP_DAOPPAME_HIGH) + { + if (is_template || !(rd_reg >= 16 && rd_reg < 20)) + { + as_bad (_("'H' modifier only valid for accumulator registers")); + return NULL; + } + + /* Top 8-bits of the accumulator. */ + rd_reg |= 8; + } + + if (is_template) + { + insn->bits = (template->meta_opcode | (1 << 1)); + } + else + { + insn->bits = (template->meta_opcode | unit); + } + + insn->bits |= (rd_reg << 19); + + if (addr.immediate) + { + int offset = addr.exp.X_add_number; + + if (addr.negate) + offset = -offset; + + offset = offset / (int)size; + + if (!within_signed_range (offset, DGET_SET_IMM_BITS)) + { + as_bad (_("offset value out of range")); + return NULL; + } + + offset = offset & DGET_SET_IMM_MASK; + + insn->bits |= (1 << 13); + insn->bits |= (offset << 9); + } + else + { + int au = (addr.base_reg->unit == UNIT_A1); + + insn->bits |= (au << 18); + insn->bits |= ((addr.base_reg->no & REG_MASK) << 14); + insn->bits |= ((addr.offset_reg->no & REG_MASK) << 9); + } + + if (is_dual) + insn->bits |= (1 << 2); + + if (!is_addr_unit (addr.base_reg->unit)) + { + as_bad (_("base unit must be either A0 or A1")); + return NULL; + } + + unit = (addr.base_reg->unit == UNIT_A0) ? 0 : 1; + insn->bits |= ((addr.base_reg->no << 14) | (unit << 18)); + + insn->len = 4; + + return l; +} + +/* Parse a DSP template instruction. */ +static const char * +parse_dtemplate (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const metag_reg *regs[TEMPLATE_NUM_REGS]; + bfd_boolean daop_only = FALSE; + int regs_val[4]; + int regs_which[4] = { -1, -1, -1, -1}; /* Register or immediate? */ + int i; + + for (i = 0; i < TEMPLATE_NUM_REGS; i++) + { + if (l == NULL) + { + as_bad (_("unexpected end of line")); + return NULL; + } + + /* We may only have 3 register operands. */ + if (*l == END_OF_INSN && i == 3) + { + daop_only = TRUE; + break; + } + + if (i != 0) + { + l = skip_comma (l); + if (l == NULL) + return NULL; + } + + if (*l == IMM_CHAR) + { + l = parse_imm_constant (l, insn, ®s_val[i]); + if (l == NULL) + { + as_bad (_("invalid immediate")); + return NULL; + } + regs_which[i] = 0; + } + else + { + /* We can't tell from the template instantiation whether + this is a load or store. So we have to try looking up the + register name in both the load and store tables. */ + const char *l2 = l; + l = __parse_gp_reg (l, ®s[i], TRUE); + if (l == NULL) + { + /* Try the store table too. */ + l = __parse_gp_reg (l2, ®s[i], FALSE); + if (l == NULL) + { + /* Then try a DSP register. */ + l = parse_dsp_insn_reg (l2, ®s[i]); + if (l == NULL || regs[i]->unit == UNIT_DT) + { + as_bad (_("invalid register")); + return NULL; + } + } + } + regs_which[i] = 1; + } + } + + insn->bits = template->meta_opcode; + + if (regs_which[0] == 0) + insn->bits |= (regs_val[0] << 19); + else if (regs_which[0] == 1) + insn->bits |= (regs[0]->no << 19); + + if (regs_which[1] == 0) + insn->bits |= (regs_val[1] << 14); + else if (regs_which[1] == 1) + insn->bits |= (regs[1]->no << 14); + + if (regs_which[2] == 0) + insn->bits |= (regs_val[2] << 9); + else if (regs_which[2] == 1) + insn->bits |= (regs[2]->no << 9); + + if (regs_which[3] == 0) + insn->bits |= (regs_val[3] << 4); + else if (regs_which[3] == 1) + insn->bits |= (regs[3]->no << 4); + + /* DaOp only. */ + if (daop_only) + insn->bits |= (0x3 << 24); /* Set the minor opcode. */ + else if (insn->dsp_daoppame_flags & DSP_DAOPPAME_HIGH) /* Half Load/Store. */ + insn->bits |= (0x5 << 24); /* Set the minor opcode. */ + + insn->len = 4; + + return l; +} + +/* Parse a DSP Template definiton memory reference, e.g + [A0.7+A0.5++]. DSPRAM is set to true by this function if this + template definition is a DSP RAM template definition. */ +static const char * +template_mem_ref(const char *line, metag_addr *addr, + bfd_boolean *dspram, int size, bfd_boolean load) +{ + const char *l = line; + + l = parse_dsp_addr (l, addr, size, load); + + if (l != NULL) + { + if (is_addr_unit(addr->base_reg->unit)) + *dspram = FALSE; + else + *dspram = TRUE; + } + + return l; +} + +/* Sets LOAD to TRUE if this is a Template load definiton (otherwise + it's a store). Fills out ADDR, TEMPLATE_REG and ADDR_UNIT. */ +static const char * +parse_template_regs (const char *line, bfd_boolean *load, + unsigned int *addr_unit, + const metag_reg **template_reg, metag_addr *addr, + bfd_boolean *dspram, int size) +{ + const char *l = line; + + if (l == NULL) + return NULL; + + /* DSP Template load definition (Tx, [Ax]) */ + if (*l == 'T') + { + *load = TRUE; + l = parse_dsp_reg (l, &template_reg[0], FALSE, FALSE); + if (l == NULL) + return NULL; + + l = skip_comma (l); + + l = template_mem_ref (l, addr, dspram, size, *load); + + if (addr->base_reg->unit == UNIT_A1) + *addr_unit = 1; + + } + else if (*l == ADDR_BEGIN_CHAR) /* DSP Template store ([Ax], Tx) */ + { + *load = FALSE; + l = template_mem_ref (l, addr, dspram, size, *load); + l = skip_comma(l); + + if (l == NULL) + return NULL; + + l = parse_dsp_reg (l, &template_reg[0], FALSE, FALSE); + if (l == NULL) + return NULL; + + if (addr->base_reg->unit == UNIT_A1) + *addr_unit = 1; + } + else + { + as_bad (_("invalid register operand")); + return NULL; + } + + return l; +} + +#define INVALID_SHIFT (-1) + +static metag_reg _reg; + +/* Parse a template instruction definition. */ +static const char * +interpret_template_regs(const char *line, metag_insn *insn, + const metag_reg **regs, + int *regs_shift, bfd_boolean *load, bfd_boolean *dspram, + int size, int *ls_shift, int *au_shift, + unsigned int *au, int *imm, int *imm_shift, + unsigned int *imm_mask) +{ + const char *l = line; + metag_addr addr; + const metag_reg *template_reg[1]; + + memset (&addr, 0, sizeof(addr)); + + regs_shift[0] = 19; + regs_shift[1] = INVALID_SHIFT; + + insn->bits |= (1 << 1); + + l = skip_whitespace (l); + + l = parse_template_regs (l, load, au, template_reg, + &addr, dspram, size); + if (l == NULL) + { + as_bad (_("could not parse template definition")); + return NULL; + } + + regs[2] = template_reg[0]; + regs_shift[2] = 9; + + /* DSPRAM definition. */ + if (*dspram) + { + + _reg = *addr.base_reg; + + if (addr.immediate) + { + /* Set the post-increment bit in the register field. */ + if (addr.update) + _reg.no |= 0x1; + } + else + { + /* The bottom bit of the increment register tells us + whether it's increment register 0 or 1. */ + if (addr.offset_reg->no & 0x1) + _reg.no |= 0x3; + else + _reg.no |= 0x2; + } + + regs[0] = &_reg; + + insn->bits |= (0x3 << 17); /* This signifies a DSPRAM definition. */ + } + else /* DaOpPaMe definition. */ + { + regs[0] = addr.base_reg; + if (addr.immediate) + { + /* Set the I bit. */ + insn->bits |= (1 << 18); + + if (addr.update == 1) + { + if (addr.negate == 1) + *imm = 0x3; + else + *imm = 0x1; + } + + *imm_shift = 14; + *imm_mask = 0x3; + } + else + { + /* Setup the offset register. */ + regs[1] = addr.offset_reg; + regs_shift[1] = 14; + } + *au_shift = 23; + } + + *ls_shift = 13; + + return l; +} + +/* Does this combination of units need the O2R bit and can it be encoded? */ +static bfd_boolean +units_need_o2r (enum metag_unit unit1, enum metag_unit unit2) +{ + if (unit1 == unit2) + return FALSE; + + if (unit1 == UNIT_D0 || unit1 == UNIT_ACC_D0 || unit1 == UNIT_RAM_D0) + { + if (unit2 == UNIT_ACC_D0 || unit2 == UNIT_RAM_D0 || unit2 == UNIT_D0) + return FALSE; + + switch (unit2) + { + case UNIT_A1: + case UNIT_D1: + case UNIT_RD: + case UNIT_A0: + return TRUE; + default: + return FALSE; + } + } + + if (unit1 == UNIT_D1 || unit1 == UNIT_ACC_D1 || unit1 == UNIT_RAM_D1) + { + if (unit2 == UNIT_ACC_D1 || unit2 == UNIT_RAM_D1 || unit2 == UNIT_D1) + return FALSE; + + switch (unit2) + { + case UNIT_A1: + case UNIT_D0: + case UNIT_RD: + case UNIT_A0: + return TRUE; + default: + return FALSE; + } + } + + return FALSE; +} + +/* Return TRUE if this is a DSP data unit. */ +static bfd_boolean +is_dsp_data_unit (const metag_reg *reg) +{ + switch (reg->unit) + { + case UNIT_D0: + case UNIT_D1: + case UNIT_ACC_D0: + case UNIT_ACC_D1: + case UNIT_RAM_D0: + case UNIT_RAM_D1: + return TRUE; + default: + return FALSE; + } +} + +static metag_reg o2r_reg; + +/* Parse a DaOpPaMe load template definition. */ +static const char * +parse_dalu (const char *line, metag_insn *insn, + const insn_template *template) +{ + const char *l = line; + const char *ll; + const metag_reg *regs[4]; + metag_addr addr; + size_t regs_read; + bfd_boolean is_mov = MAJOR_OPCODE (template->meta_opcode) == OPC_ADD; + bfd_boolean is_cmp = ((MAJOR_OPCODE (template->meta_opcode) == OPC_CMP) && + ((template->meta_opcode & 0xee) == 0)); + bfd_boolean is_dual = (insn->dsp_width == DSP_WIDTH_DUAL); + bfd_boolean is_quickrot64 = ((insn->dsp_action_flags & DSP_ACTION_QR64) != 0); + int l1_shift = INVALID_SHIFT; + bfd_boolean load = FALSE; + int ls_shift = INVALID_SHIFT; + bfd_boolean ar = FALSE; + int ar_shift = INVALID_SHIFT; + int regs_shift[3] = { INVALID_SHIFT, INVALID_SHIFT, INVALID_SHIFT }; + int imm = 0; + int imm_shift = INVALID_SHIFT; + unsigned int imm_mask = 0; + unsigned int au = 0; + int au_shift = INVALID_SHIFT; + unsigned int du = 0; + int du_shift = INVALID_SHIFT; + unsigned int sc = ((insn->dsp_action_flags & DSP_ACTION_OV) != 0); + int sc_shift = INVALID_SHIFT; + unsigned int om = ((insn->dsp_action_flags & DSP_ACTION_MOD) != 0); + int om_shift = INVALID_SHIFT; + unsigned int o2r = 0; + int o2r_shift = INVALID_SHIFT; + unsigned int qr = 0; + int qr_shift = INVALID_SHIFT; + int qd_shift = INVALID_SHIFT; + unsigned int qn = 0; + int qn_shift = INVALID_SHIFT; + unsigned int a1 = ((insn->dsp_action_flags & (DSP_ACTION_ACC_SUB|DSP_ACTION_ACC_ZERO)) != 0); + int a1_shift = INVALID_SHIFT; + unsigned int a2 = ((insn->dsp_action_flags & (DSP_ACTION_ACC_SUB|DSP_ACTION_ACC_ADD)) != 0); + int a2_shift = INVALID_SHIFT; + unsigned su = ((insn->dsp_action_flags & DSP_ACTION_UMUL) != 0); + int su_shift = INVALID_SHIFT; + unsigned int ac; + int ac_shift = INVALID_SHIFT; + unsigned int mx = (((insn->dsp_daoppame_flags & DSP_DAOPPAME_8) != 0) || + (insn->dsp_daoppame_flags & DSP_DAOPPAME_16) != 0); + int mx_shift = INVALID_SHIFT; + int size = is_dual ? 8 : 4; + bfd_boolean dspram; + bfd_boolean conditional = (MINOR_OPCODE (template->meta_opcode) & 0x4); + + /* XFIXME: check the flags are valid with the instruction. */ + if (is_quickrot64 && !(template->arg_type & DSP_ARGS_QR)) + { + as_bad (_("QUICKRoT 64-bit extension not applicable to this instruction")); + return NULL; + } + + insn->bits = template->meta_opcode; + + memset (regs, 0, sizeof (regs)); + memset (&addr, 0, sizeof (addr)); + + /* There are the following forms of DSP ALU instructions, + + * Group 1: + 19. D[T] Op De.r,Dx.r,De.r + 1. D[T] Op De.r,Dx.r,De.r|ACe.r [Accumulator in src 2] + 3. D[T] Op De.r,Dx.r,De.r[,Ae.r] [QUICKRoT] + 2. D[T] Op ACe.e,ACx.r,ACo.e [cross-unit accumulator op] + 5. D[T] Op De.r|ACe.r,Dx.r,De.r + 20. D[T] Op De.r,Dx.r|ACx.r,De.r + 8. D Opcc De.r,Dx.r,Rx.r + 6. D Op De.r,Dx.r,Rx.r|RD + 17. D Op De.r|ACe.r,Dx.r,Rx.r|RD + 7. D Op De.e,Dx.r,#I16 + + * Group 2: + 4. D[T] Op Dx.r,De.r + 10. D Op Dx.r,Rx.r|RD + 13. D Op Dx.r,Rx.r + 11. D Op Dx.r,#I16 + 12. D[T] Op De.r,Dx.r + 14. D Op DSPe.r,Dx.r + 15. D Op DSPx.r,#I16 + 16. D Op De.r,DSPx.r + 18. D Op De.r,Dx.r|ACx.r + + * Group 3: + 22. D Op De.r,Dx.r|ACx.r,De.r|#I5 + 23. D Op Ux.r,Dx.r|ACx.r,De.r|#I5 + 21. D Op De.r,Dx.r|ACx.r,#I5 */ + + /* Group 1. */ + if (template->arg_type & DSP_ARGS_1) + { + du_shift = 24; + + /* Could this be a cross-unit accumulator op, + e.g. ACe.e,ACx.r,ACo.e */ + if (template->arg_type & DSP_ARGS_XACC) + { + ll = parse_dsp_regs_list (l, regs, 3, ®s_read, FALSE, FALSE, + FALSE, FALSE); + if (ll != NULL && regs_read == 3 + && is_accumulator_reg (regs[0])) + { + if (regs[0]->unit != regs[1]->unit || + regs[2]->unit == regs[1]->unit) + { + as_bad (_("invalid operands for cross-unit op")); + return NULL; + } + + du = (regs[1]->unit == UNIT_ACC_D1); + regs_shift[1] = 19; + l = ll; + + /* All cross-unit accumulator ops have bits 8 and 6 set. */ + insn->bits |= (5 << 6); + + goto check_for_template; + } + + /* If we reach here, this instruction is not a + cross-unit accumulator op. */ + } + + if (template->arg_type & DSP_ARGS_SPLIT8) + om_shift = 7; + + sc_shift = 5; + l1_shift = 4; + o2r_shift = 0; + + /* De.r|ACe.r,Dx.r,De.r */ + if (template->arg_type & DSP_ARGS_DACC) + { + /* XFIXME: these need moving? */ + a2_shift = 7; + su_shift = 6; + a1_shift = 2; + om_shift = 3; + + ll = parse_dsp_reg (l, ®s[0], FALSE, FALSE); + if (ll != NULL) + { + /* Using ACe.r as the dst requires one of the P,N or Z + flags to be used. */ + if (!(insn->dsp_action_flags & + (DSP_ACTION_ACC_SUB|DSP_ACTION_ACC_ADD|DSP_ACTION_ACC_ZERO))) + { + as_bad (_("missing flags: one of 'P', 'N' or 'Z' required")); + return NULL; + } + + l = ll; + l = skip_comma (l); + l = parse_dsp_regs_list (l, ®s[1], 2, ®s_read, + TRUE, FALSE, FALSE, FALSE); + if (l == NULL || regs_read != 2) + { + as_bad (_("invalid register")); + return NULL; + } + + if (regs[1]->unit == UNIT_D1 || regs[1]->unit == UNIT_RAM_D1) + du = 1; + + regs_shift[0] = 19; + regs_shift[1] = 14; + regs_shift[2] = 9; + goto check_for_template; + } + + /* If we reach here, this instruction does not use the + accumulator as the destination register. */ + if ((insn->dsp_action_flags & + (DSP_ACTION_ACC_SUB|DSP_ACTION_ACC_ADD|DSP_ACTION_ACC_ZERO))) + { + as_bad (_("'P', 'N' or 'Z' flags may only be specified when accumulating")); + return NULL; + } + } + + regs_shift[0] = 19; + + + l = parse_dsp_regs_list (l, regs, 2, ®s_read, TRUE, FALSE, FALSE, TRUE); + if (l == NULL || regs_read != 2) + return NULL; + + l = skip_comma (l); + if (l == NULL) + return NULL; + + if (regs[1]->unit == UNIT_D1 || regs[1]->unit == UNIT_RAM_D1) + du = 1; + + if (is_accumulator_reg(regs[0]) && !(template->arg_type & DSP_ARGS_DACC)) + { + as_bad (_("accumulator not a valid destination")); + return NULL; + } + + /* Check for immediate, e.g. De.r,Dx.r,#I16 */ + if (*l == IMM_CHAR) + { + l = parse_imm16 (l, insn, &imm); + if (l == NULL) + { + as_bad (_("invalid immediate value")); + return NULL; + } + + if (!within_signed_range (imm, IMM16_BITS)) + { + as_bad (_("immediate value out of range")); + return NULL; + } + + if (regs[0]->unit != regs[1]->unit || regs[0]->no != regs[1]->no) + { + as_bad (_("immediate value not allowed when source & dest differ")); + return NULL; + } + + imm_mask = 0xffff; + imm_shift = 3; + + /* Set the I-bit */ + insn->bits |= (1 << 25); + + insn->bits |= (0x3 << 0); + + l1_shift = 2; + + /* Remove any bits that have been set in the immediate + field. */ + insn->bits &= ~(imm_mask << imm_shift); + } + else + { + + regs_shift[1] = 14; + regs_shift[2] = 9; + + /* Is Rs2 an accumulator reg, e.g. De.r,Dx.r,De.r|ACe.r */ + ll = parse_dsp_reg (l, ®s[2], FALSE, FALSE); + if (ll != NULL) + { + l = ll; + + if (!(template->arg_type & DSP_ARGS_ACC2)) + { + as_bad (_("invalid register operand: %s"), regs[2]->name); + return NULL; + } + + om_shift = 3; + ar_shift = 7; + ar = TRUE; + } + else + { + /* De.r,Dx.r,De.r */ + l = __parse_gp_reg (l, ®s[2], TRUE); + if (l == NULL) + return NULL; + } + + if (template->arg_type & DSP_ARGS_ACC2) + om_shift = 3; + + /* Is this a QUICKRoT instruction? De.r,Dx.r,De.r[,Ae.r] */ + if (template->arg_type & DSP_ARGS_QR) + { + if (conditional) + qn_shift = 5; + else + { + qn_shift = 7; + qr_shift = 6; + qd_shift = 5; + } + + l = skip_comma (l); + if (l == NULL) + { + as_bad (_("QUICKRoT extension requires 4 registers")); + return NULL; + } + + l = __parse_gp_reg (l, ®s[3], TRUE); + if (l == NULL) + { + as_bad (_("invalid fourth register")); + return NULL; + } + + if (!is_addr_unit (regs[3]->unit) || + !is_quickrot_reg (regs[3]->no)) + { + as_bad (_("A0.2,A0.3,A1.2,A1.3 required for QUICKRoT register")); + return NULL; + } + + qn = (regs[3]->no == 3); + } + } + + check_for_template: + /* This is the common exit path. Check for o2r. */ + if (regs[2] != NULL) + { + o2r = units_need_o2r (regs[1]->unit, regs[2]->unit); + if (o2r) + { + o2r_reg.no = lookup_o2r (0, du, regs[2]); + o2r_reg.unit = regs[2]->unit; + regs[2] = &o2r_reg; + } + } + + /* Check any DSP RAM pointers are valid for this unit. */ + if ((du && (regs[0]->unit == UNIT_RAM_D0)) || + (!du && (regs[0]->unit == UNIT_RAM_D1)) || + (du && (regs[1]->unit == UNIT_RAM_D0)) || + (!du && (regs[1]->unit == UNIT_RAM_D1)) || + (du && regs[2] && (regs[2]->unit == UNIT_RAM_D0)) || + (!du && regs[2] && (regs[2]->unit == UNIT_RAM_D1))) { + as_bad (_("DSP RAM pointer in incorrect unit")); + return NULL; + } + + /* Is this a template definition? */ + if (IS_TEMPLATE_DEF (insn)) + { + l = interpret_template_regs(l, insn, regs, regs_shift, &load, + &dspram, size, &ls_shift, &au_shift, + &au, &imm, &imm_shift, &imm_mask); + + if (l == NULL) + return NULL; + + if (!dspram) + mx_shift = 0; + } + + goto matched; + } + + /* Group 2. */ + if (template->arg_type & DSP_ARGS_2) + { + bfd_boolean is_xsd = ((MAJOR_OPCODE (template->meta_opcode) == OPC_MISC) && + (MINOR_OPCODE (template->meta_opcode) == 0xa)); + bfd_boolean is_fpu_mov = template->insn_type == INSN_DSP_FPU; + bfd_boolean to_fpu = (template->meta_opcode >> 7) & 0x1; + + if (is_xsd) + du_shift = 0; + else + du_shift = 24; + + l1_shift = 4; + + /* CMPs and TSTs don't store to their destination operand. */ + ll = __parse_gp_reg (l, regs, is_cmp); + if (ll == NULL) + { + /* DSPe.r,Dx.r or DSPx.r,#I16 */ + if (template->arg_type & DSP_ARGS_DSP_SRC1) + { + l = parse_dsp_reg (l, regs, FALSE, FALSE); + if (l == NULL) + { + as_bad (_("invalid register operand #1")); + return NULL; + } + + /* Only MOV instructions have a DSP register as a + destination. Set the MOV DSPe.r opcode. The simple + OR'ing is OK because the usual MOV opcode is 0x00. */ + insn->bits = (0x91 << 24); + du_shift = 0; + l1_shift = 2; + regs_shift[0] = 19; + } + else + { + as_bad (_("invalid register operand #2")); + return NULL; + } + } + else + { + l = ll; + + /* Everything but CMP and TST. */ + if (MAJOR_OPCODE (template->meta_opcode) == OPC_ADD || + MAJOR_OPCODE (template->meta_opcode) == OPC_SUB || + MAJOR_OPCODE (insn->bits) == OPC_9 || + MAJOR_OPCODE (template->meta_opcode) == OPC_MISC || + ((template->meta_opcode & 0x0000002c) != 0)) + regs_shift[0] = 19; + else + regs_shift[0] = 14; + } + + if (!is_dsp_data_unit (regs[0]) && !(regs[0]->unit == UNIT_FX && + is_fpu_mov && to_fpu)) + return NULL; + + du = (regs[0]->unit == UNIT_D1 || regs[0]->unit == UNIT_RAM_D1 || + regs[0]->unit == UNIT_ACC_D1); + + l = skip_comma (l); + + if (*l == IMM_CHAR) + { + if (template->arg_type & DSP_ARGS_IMM && + !(is_mov && (MAJOR_OPCODE (insn->bits) != OPC_9))) + { + l = parse_imm16 (l, insn, &imm); + if (l == NULL) + { + as_bad (_("invalid immediate value")); + return NULL; + } + + if (!within_signed_range (imm, IMM16_BITS)) + return NULL; + + l1_shift = 2; + regs_shift[0] = 19; + + imm_mask = 0xffff; + imm_shift = 3; + + /* Set the I-bit unless it's a MOV because they're + different. */ + if (!(is_mov && MAJOR_OPCODE (insn->bits) == OPC_9)) + insn->bits |= (1 << 25); + + /* All instructions that takes immediates also have bit 1 set. */ + insn->bits |= (1 << 1); + + if (MAJOR_OPCODE (insn->bits) != OPC_9) + insn->bits |= (1 << 0); + + insn->bits &= ~(1 << 8); + } + else + { + as_bad (_("this instruction does not accept an immediate")); + return NULL; + } + } + else + { + if (MAJOR_OPCODE (insn->bits) != OPC_9) + { + insn->bits |= (1 << 8); + l1_shift = 4; + } + + ll = __parse_gp_reg (l, ®s[1], TRUE); + if (ll == NULL) + { + if (template->arg_type & DSP_ARGS_DSP_SRC2) + { + l = parse_dsp_reg (l, ®s[1], FALSE, FALSE); + if (l == NULL) + { + as_bad (_("invalid register operand #3")); + return NULL; + } + + /* MOV and NEG. */ + if ((is_mov && (MAJOR_OPCODE (insn->bits) != OPC_9)) || + MAJOR_OPCODE (template->meta_opcode) == OPC_SUB) + { + if (is_accumulator_reg (regs[1])) + { + if (is_fpu_mov) + { + as_bad (_("this instruction does not accept an accumulator")); + return NULL; + } + ar_shift = 7; + ar = 1; + regs_shift[1] = 9; + } + else + { + du_shift = 0; + l1_shift = 2; + regs_shift[1] = 14; + insn->bits = (0x92 << 24); /* Set opcode. */ + } + } + } + else + { + as_bad (_("invalid register operand #4")); + return NULL; + } + } + else + { + /* Set the o2r bit if required. */ + if (!is_fpu_mov && units_need_o2r (regs[0]->unit, regs[1]->unit)) + { + o2r_reg = *regs[1]; + o2r_reg.no = lookup_o2r (0, du, regs[1]); + regs[1] = &o2r_reg; + o2r_shift = 0; + o2r = 1; + } + else if (!is_dsp_data_unit (regs[1]) && + !(is_fpu_mov && !to_fpu && regs[1]->unit == UNIT_FX)) + return NULL; + + if (is_fpu_mov && to_fpu) + du = (regs[1]->unit == UNIT_D1 || + regs[1]->unit == UNIT_RAM_D1 || + regs[1]->unit == UNIT_ACC_D1); + + l = ll; + + if (MAJOR_OPCODE (insn->bits) == OPC_ADD || + MAJOR_OPCODE (template->meta_opcode) == OPC_SUB || + (((template->meta_opcode & 0x0000002c) == 0) && + MAJOR_OPCODE (template->meta_opcode) != OPC_MISC)) + regs_shift[1] = 9; + else + regs_shift[1] = 14; + } + } + + /* If it's an 0x0 MOV or NEG set some lower bits. */ + if ((MAJOR_OPCODE (insn->bits) == OPC_ADD || + MAJOR_OPCODE (template->meta_opcode) == OPC_SUB) && !is_fpu_mov) + { + om_shift = 3; + sc_shift = 5; + insn->bits |= (1 << 2); + } + + /* Check for template definitons. */ + if (IS_TEMPLATE_DEF (insn)) + { + l = interpret_template_regs(l, insn, regs, regs_shift, &load, + &dspram, size, &ls_shift, &au_shift, + &au, &imm, &imm_shift, &imm_mask); + mx_shift = 0; + + if (l == NULL) + return NULL; + } + goto matched; + } + + /* Group 3. */ + du_shift = 24; + l1_shift = 4; + + l = __parse_gp_reg (l, regs, FALSE); + if (l == NULL) + { + as_bad (_("invalid register operand")); + return NULL; + } + + l = skip_comma (l); + + if (*l == 'A') + { + l = parse_dsp_reg (l, ®s[1], FALSE, FALSE); + if (l == NULL) + { + as_bad (_("invalid accumulator register")); + return NULL; + } + ac = 1; + ac_shift = 0; + } + else + { + l = __parse_gp_reg (l, ®s[1], TRUE); + if (l == NULL) + { + as_bad (_("invalid register operand")); + return NULL; + } + } + + regs_shift[0] = 19; + regs_shift[1] = 14; + + du = (regs[1]->unit == UNIT_D1 || regs[1]->unit == UNIT_ACC_D1 + || regs[1]->unit == UNIT_RAM_D1); + + l = skip_comma (l); + + if (*l == IMM_CHAR) + { + l = parse_imm_constant (l, insn, &imm); + if (l == NULL) + { + as_bad (_("invalid immediate value")); + return NULL; + } + + if (!within_unsigned_range (imm, IMM5_BITS)) + return NULL; + + imm_mask = 0x1f; + imm_shift = 9; + + /* Set the I-bit */ + insn->bits |= (1 << 25); + } + else + { + regs_shift[2] = 9; + l = __parse_gp_reg (l, ®s[2], TRUE); + if (l == NULL) + return NULL; + } + + /* Check for post-processing R,G,B flags. Conditional instructions + do not have these bits. */ + if (insn->dsp_action_flags & DSP_ACTION_CLAMP9) + { + if ((template->meta_opcode >> 26) & 0x1) + { + as_bad (_("conditional instruction cannot use G flag")); + return NULL; + } + + insn->bits |= (1 << 3); + } + + if (insn->dsp_action_flags & DSP_ACTION_CLAMP8) + { + if ((template->meta_opcode >> 26) & 0x1) + { + as_bad (_("conditional instruction cannot use B flag")); + return NULL; + } + + insn->bits |= (0x3 << 2); + } + + if (insn->dsp_action_flags & DSP_ACTION_ROUND) + { + if ((template->meta_opcode >> 26) & 0x1) + { + as_bad (_("conditional instruction cannot use R flag")); + return NULL; + } + insn->bits |= (1 << 2); + } + + /* Conditional Data Unit Shift instructions cannot be dual unit. */ + if ((template->meta_opcode >> 26) & 0x1) + ls_shift = INVALID_SHIFT; + + /* The Condition Is Always (CA) bit must be set if we're targetting a + Ux.r register as the destination. This means that we can't have + any other condition bits set. */ + if (!is_same_data_unit (regs[1]->unit, regs[0]->unit)) + { + /* Set both the Conditional bit and the Condition is Always bit. */ + insn->bits |= (1 << 26); + insn->bits |= (1 << 5); + + /* Fill out the Ud field. */ + insn->bits |= (regs[0]->unit << 1); + } + + if (IS_TEMPLATE_DEF (insn)) + { + l = interpret_template_regs(l, insn, regs, regs_shift, &load, + &dspram, size, &ls_shift, &au_shift, + &au, &imm, &imm_shift, &imm_mask); + + if (l == NULL) + return NULL; + + if (!dspram) + mx_shift = 5; + } + + /* Fall through. */ + matched: + + /* Set the registers and immediate values. */ + if (regs_shift[0] != INVALID_SHIFT) + insn->bits |= (regs[0]->no << regs_shift[0]); + + if (regs_shift[1] != INVALID_SHIFT) + insn->bits |= (regs[1]->no << regs_shift[1]); + + if (regs_shift[2] != INVALID_SHIFT) + insn->bits |= (regs[2]->no << regs_shift[2]); + + /* Does this insn have an 'IMM' bit? The immediate value should + already have been masked. */ + if (imm_shift != INVALID_SHIFT) + insn->bits |= ((imm & imm_mask) << imm_shift); + + /* Does this insn have an 'AU' bit? */ + if (au_shift != INVALID_SHIFT) + insn->bits |= (au << au_shift); + + /* Does this instruction have an 'LS' bit? */ + if (ls_shift != INVALID_SHIFT) + insn->bits |= (load << ls_shift); + + /* Does this instruction have an 'AR' bit? */ + if (ar) + insn->bits |= (1 << ar_shift); + + if (du_shift != INVALID_SHIFT) + insn->bits |= (du << du_shift); + + if (sc_shift != INVALID_SHIFT) + insn->bits |= (sc << sc_shift); + + if (om_shift != INVALID_SHIFT) + insn->bits |= (om << om_shift); + + if (o2r_shift != INVALID_SHIFT) + insn->bits |= (o2r << o2r_shift); + + if (qn_shift != INVALID_SHIFT) + insn->bits |= (qn << qn_shift); + + if (qr_shift != INVALID_SHIFT) + insn->bits |= (qr << qr_shift); + + if (qd_shift != INVALID_SHIFT) + insn->bits |= (is_quickrot64 << qd_shift); + + if (a1_shift != INVALID_SHIFT) + insn->bits |= (a1 << a1_shift); + + if (a2_shift != INVALID_SHIFT) + insn->bits |= (a2 << a2_shift); + + if (su_shift != INVALID_SHIFT) + insn->bits |= (su << su_shift); + + if (imm_shift != INVALID_SHIFT) + insn->bits |= ((imm & imm_mask) << imm_shift); + + if (ac_shift != INVALID_SHIFT) + insn->bits |= (ac << ac_shift); + + if (mx_shift != INVALID_SHIFT) + insn->bits |= (mx << mx_shift); + + if (is_dual) + { + if (l1_shift == INVALID_SHIFT) + { + as_bad (_("'L' modifier not valid for this instruction")); + return NULL; + } + + insn->bits |= (1 << l1_shift); + } + + insn->len = 4; + + return l; +} + +typedef const char *(*insn_parser)(const char *, metag_insn *, + const insn_template *); + +/* Parser table. */ +static const insn_parser insn_parsers[ENC_MAX] = + { + [ENC_NONE] = parse_none, + [ENC_MOV_U2U] = parse_mov_u2u, + [ENC_MOV_PORT] = parse_mov_port, + [ENC_MMOV] = parse_mmov, + [ENC_MDRD] = parse_mdrd, + [ENC_MOVL_TTREC] = parse_movl_ttrec, + [ENC_GET_SET] = parse_get_set, + [ENC_GET_SET_EXT] = parse_get_set_ext, + [ENC_MGET_MSET] = parse_mget_mset, + [ENC_COND_SET] = parse_cond_set, + [ENC_XFR] = parse_xfr, + [ENC_MOV_CT] = parse_mov_ct, + [ENC_SWAP] = parse_swap, + [ENC_JUMP] = parse_jump, + [ENC_CALLR] = parse_callr, + [ENC_ALU] = parse_alu, + [ENC_SHIFT] = parse_shift, + [ENC_MIN_MAX] = parse_min_max, + [ENC_BITOP] = parse_bitop, + [ENC_CMP] = parse_cmp, + [ENC_BRANCH] = parse_branch, + [ENC_KICK] = parse_kick, + [ENC_SWITCH] = parse_switch, + [ENC_CACHER] = parse_cacher, + [ENC_CACHEW] = parse_cachew, + [ENC_ICACHE] = parse_icache, + [ENC_LNKGET] = parse_lnkget, + [ENC_FMOV] = parse_fmov, + [ENC_FMMOV] = parse_fmmov, + [ENC_FMOV_DATA] = parse_fmov_data, + [ENC_FMOV_I] = parse_fmov_i, + [ENC_FPACK] = parse_fpack, + [ENC_FSWAP] = parse_fswap, + [ENC_FCMP] = parse_fcmp, + [ENC_FMINMAX] = parse_fminmax, + [ENC_FCONV] = parse_fconv, + [ENC_FCONVX] = parse_fconvx, + [ENC_FBARITH] = parse_fbarith, + [ENC_FEARITH] = parse_fearith, + [ENC_FREC] = parse_frec, + [ENC_FSIMD] = parse_fsimd, + [ENC_FGET_SET_ACF] = parse_fget_set_acf, + [ENC_DGET_SET] = parse_dget_set, + [ENC_DTEMPLATE] = parse_dtemplate, + [ENC_DALU] = parse_dalu, + }; + +struct metag_core_option +{ + char *name; + unsigned int value; +}; + +/* CPU type options. */ +static const struct metag_core_option metag_cpus[] = + { + {"all", CoreMeta11|CoreMeta12|CoreMeta21}, + {"metac11", CoreMeta11}, + {"metac12", CoreMeta12}, + {"metac21", CoreMeta21}, + {NULL, 0}, + }; + +/* FPU type options. */ +static const struct metag_core_option metag_fpus[] = + { + {"metac21", FpuMeta21}, + {NULL, 0}, + }; + +/* DSP type options. */ +static const struct metag_core_option metag_dsps[] = + { + {"metac21", DspMeta21}, + {NULL, 0}, + }; + +/* Parse a CPU command line option. */ +static int +metag_parse_cpu (char * str) +{ + const struct metag_core_option * opt; + int optlen; + + optlen = strlen (str); + + if (optlen == 0) + { + as_bad (_("missing cpu name `%s'"), str); + return 0; + } + + for (opt = metag_cpus; opt->name != NULL; opt++) + if (strncmp (opt->name, str, optlen) == 0) + { + mcpu_opt = opt->value; + return 1; + } + + as_bad (_("unknown cpu `%s'"), str); + return 0; +} + +/* Parse an FPU command line option. */ +static int +metag_parse_fpu (char * str) +{ + const struct metag_core_option * opt; + int optlen; + + optlen = strlen (str); + + if (optlen == 0) + { + as_bad (_("missing fpu name `%s'"), str); + return 0; + } + + for (opt = metag_fpus; opt->name != NULL; opt++) + if (strncmp (opt->name, str, optlen) == 0) + { + mfpu_opt = opt->value; + return 1; + } + + as_bad (_("unknown fpu `%s'"), str); + return 0; +} + +/* Parse a DSP command line option. */ +static int +metag_parse_dsp (char * str) +{ + const struct metag_core_option * opt; + int optlen; + + optlen = strlen (str); + + if (optlen == 0) + { + as_bad (_("missing DSP name `%s'"), str); + return 0; + } + + for (opt = metag_dsps; opt->name != NULL; opt++) + if (strncmp (opt->name, str, optlen) == 0) + { + mdsp_opt = opt->value; + return 1; + } + + as_bad (_("unknown DSP `%s'"), str); + return 0; +} + +struct metag_long_option +{ + char * option; /* Substring to match. */ + char * help; /* Help information. */ + int (* func) (char * subopt); /* Function to decode sub-option. */ + char * deprecated; /* If non-null, print this message. */ +}; + +struct metag_long_option metag_long_opts[] = + { + {"mcpu=", N_("<cpu name>\t assemble for CPU <cpu name>"), + metag_parse_cpu, NULL}, + {"mfpu=", N_("<fpu name>\t assemble for FPU architecture <fpu name>"), + metag_parse_fpu, NULL}, + {"mdsp=", N_("<dsp name>\t assemble for DSP architecture <dsp name>"), + metag_parse_dsp, NULL}, + {NULL, NULL, 0, NULL} + }; + +int +md_parse_option (int c, char * arg) +{ + struct metag_long_option *lopt; + + for (lopt = metag_long_opts; lopt->option != NULL; lopt++) + { + /* These options are expected to have an argument. */ + if (c == lopt->option[0] + && arg != NULL + && strncmp (arg, lopt->option + 1, + strlen (lopt->option + 1)) == 0) + { +#if WARN_DEPRECATED + /* If the option is deprecated, tell the user. */ + if (lopt->deprecated != NULL) + as_tsktsk (_("option `-%c%s' is deprecated: %s"), c, arg, + _(lopt->deprecated)); +#endif + + /* Call the sup-option parser. */ + return lopt->func (arg + strlen (lopt->option) - 1); + } + } + + return 0; +} + +void +md_show_usage (FILE * stream) +{ + struct metag_long_option *lopt; + + fprintf (stream, _(" Meta specific command line options:\n")); + + for (lopt = metag_long_opts; lopt->option != NULL; lopt++) + if (lopt->help != NULL) + fprintf (stream, " -%s%s\n", lopt->option, _(lopt->help)); +} + +/* The target specific pseudo-ops which we support. */ +const pseudo_typeS md_pseudo_table[] = +{ + { "word", cons, 2 }, + { NULL, NULL, 0 } +}; + +void +md_begin (void) +{ + int c; + + for (c = 0; c < 256; c++) + { + if (ISDIGIT (c)) + { + register_chars[c] = c; + /* LOCK0, LOCK1, LOCK2. */ + mnemonic_chars[c] = c; + } + else if (ISLOWER (c)) + { + register_chars[c] = c; + mnemonic_chars[c] = c; + } + else if (ISUPPER (c)) + { + register_chars[c] = c; + mnemonic_chars[c] = c; + } + else if (c == '.') + { + register_chars[c] = c; + } + } +} + +/* Parse a split condition code prefix. */ +static const char * +parse_split_condition (const char *line, metag_insn *insn) +{ + const char *l = line; + const split_condition *scond; + split_condition entry; + char buf[4]; + + memcpy (buf, l, 4); + buf[3] = '\0'; + + entry.name = buf; + + scond = (const split_condition *) htab_find (scond_htab, &entry); + + if (!scond) + return NULL; + + insn->scond = scond->code; + + return l + strlen (scond->name); +} + +/* Parse an instruction prefix - F for float, D for DSP - and associated + flags and condition codes. */ +static const char * +parse_prefix (const char *line, metag_insn *insn) +{ + const char *l = line; + + l = skip_whitespace (l); + + insn->type = INSN_GP; + + if (TOLOWER (*l) == FPU_PREFIX_CHAR) + { + if (strncasecmp (l, FFB_INSN, strlen(FFB_INSN))) + { + insn->type = INSN_FPU; + + l++; + + if (*l == END_OF_INSN) + { + as_bad (_("premature end of floating point prefix")); + return NULL; + } + + if (TOLOWER (*l) == FPU_DOUBLE_CHAR) + { + insn->fpu_width = FPU_WIDTH_DOUBLE; + l++; + } + else if (TOLOWER (*l) == FPU_PAIR_CHAR) + { + const char *l2 = l; + + /* Check this isn't a split condition beginning with L. */ + l2 = parse_split_condition (l2, insn); + + if (l2 && is_whitespace_char (*l2)) + { + l = l2; + } + else + { + insn->fpu_width = FPU_WIDTH_PAIR; + l++; + } + } + else + { + insn->fpu_width = FPU_WIDTH_SINGLE; + } + + if (TOLOWER (*l) == FPU_ACTION_ABS_CHAR) + { + insn->fpu_action_flags |= FPU_ACTION_ABS; + l++; + } + else if (TOLOWER (*l) == FPU_ACTION_INV_CHAR) + { + insn->fpu_action_flags |= FPU_ACTION_INV; + l++; + } + + if (TOLOWER (*l) == FPU_ACTION_QUIET_CHAR) + { + insn->fpu_action_flags |= FPU_ACTION_QUIET; + l++; + } + + if (TOLOWER (*l) == FPU_ACTION_ZERO_CHAR) + { + insn->fpu_action_flags |= FPU_ACTION_ZERO; + l++; + } + + if (! is_whitespace_char (*l)) + { + l = parse_split_condition (l, insn); + + if (!l) + { + as_bad (_("unknown floating point prefix character")); + return NULL; + } + } + + l = skip_space (l); + } + } + else if (TOLOWER (*l) == DSP_PREFIX_CHAR) + { + if (strncasecmp (l, DCACHE_INSN, strlen (DCACHE_INSN)) && + strncasecmp (l, DEFR_INSN, strlen (DEFR_INSN))) + { + const char *ll = l; + insn->type = INSN_DSP; + + l++; + + insn->dsp_width = DSP_WIDTH_SINGLE; + + while (!is_whitespace_char (*l)) + { + /* We have to check for split condition codes first + because they are the longest strings to match, + e.g. if the string contains "LLS" we want it to match + the split condition code "LLS", not the dual unit + character "L". */ + ll = l; + l = parse_split_condition (l, insn); + + if (l == NULL) + l = ll; + else + continue; + + /* Accept an FPU prefix char which may be used when doing + template MOV with FPU registers. */ + if (TOLOWER(*l) == FPU_PREFIX_CHAR) + { + insn->type = INSN_DSP_FPU; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_DUAL_CHAR) + { + insn->dsp_width = DSP_WIDTH_DUAL; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_QR64_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_QR64; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_UMUL_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_UMUL; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_ROUND_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_ROUND; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_CLAMP9_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_CLAMP9; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_CLAMP8_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_CLAMP8; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_MOD_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_MOD; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_ACC_ZERO_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_ACC_ZERO; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_ACC_ADD_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_ACC_ADD; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_ACC_SUB_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_ACC_SUB; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_ACTION_OV_CHAR) + { + insn->dsp_action_flags |= DSP_ACTION_OV; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_DAOPPAME_8_CHAR) + { + insn->dsp_daoppame_flags |= DSP_DAOPPAME_8; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_DAOPPAME_16_CHAR) + { + insn->dsp_daoppame_flags |= DSP_DAOPPAME_16; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_DAOPPAME_TEMP_CHAR) + { + insn->dsp_daoppame_flags |= DSP_DAOPPAME_TEMP; + l++; + continue; + } + + if (TOLOWER(*l) == DSP_DAOPPAME_HIGH_CHAR) + { + insn->dsp_daoppame_flags |= DSP_DAOPPAME_HIGH; + l++; + continue; + } + + as_bad (_("unknown DSP prefix character %c %s"), *l, l); + return NULL; + } + + l = skip_space (l); + } + } + + return l; +} + +/* Return a list of appropriate instruction parsers for MNEMONIC. */ +static insn_templates * +find_insn_templates (const char *mnemonic) +{ + insn_template template; + insn_templates entry; + insn_templates *slot; + + entry.template = &template; + + memcpy ((void *)&entry.template->name, &mnemonic, sizeof (char *)); + + slot = (insn_templates *) htab_find (mnemonic_htab, &entry); + + if (slot) + return slot; + + return NULL; +} + +/* Make an uppercase copy of SRC into DST and return DST. */ +static char * +strupper (char * dst, const char *src) +{ + size_t i = 0; + + while (src[i]) + { + dst[i] = TOUPPER (src[i]); + i++; + } + + dst[i] = 0; + + return dst; +} + +/* Calculate a hash value for a template. */ +static hashval_t +hash_templates (const void *p) +{ + insn_templates *tp = (insn_templates *)p; + char buf[MAX_MNEMONIC_LEN]; + + strupper (buf, tp->template->name); + + return htab_hash_string (buf); +} + +/* Check if two templates are equal. */ +static int +eq_templates (const void *a, const void *b) +{ + insn_templates *ta = (insn_templates *)a; + insn_templates *tb = (insn_templates *)b; + return strcasecmp (ta->template->name, tb->template->name) == 0; +} + +/* Create the hash table required for parsing instructions. */ +static void +create_mnemonic_htab (void) +{ + size_t i, num_templates = sizeof(metag_optab)/sizeof(metag_optab[0]); + + mnemonic_htab = htab_create_alloc (num_templates, hash_templates, + eq_templates, NULL, xcalloc, free); + + for (i = 0; i < num_templates; i++) + { + const insn_template *template = &metag_optab[i]; + insn_templates **slot = NULL; + insn_templates *new_entry; + + new_entry = xmalloc (sizeof (insn_templates)); + + new_entry->template = template; + new_entry->next = NULL; + + slot = (insn_templates **) htab_find_slot (mnemonic_htab, new_entry, + INSERT); + + if (*slot) + { + insn_templates *last_entry = *slot; + + while (last_entry->next) + last_entry = last_entry->next; + + last_entry->next = new_entry; + } + else + { + *slot = new_entry; + } + } +} + +/* Calculate a hash value for a register. */ +static hashval_t +hash_regs (const void *p) +{ + metag_reg *rp = (metag_reg *)p; + char buf[MAX_REG_LEN]; + + strupper (buf, rp->name); + + return htab_hash_string (buf); +} + +/* Check if two registers are equal. */ +static int +eq_regs (const void *a, const void *b) +{ + metag_reg *ra = (metag_reg *)a; + metag_reg *rb = (metag_reg *)b; + return strcasecmp (ra->name, rb->name) == 0; +} + +/* Create the hash table required for parsing registers. */ +static void +create_reg_htab (void) +{ + size_t i, num_regs = sizeof(metag_regtab)/sizeof(metag_regtab[0]); + + reg_htab = htab_create_alloc (num_regs, hash_regs, + eq_regs, NULL, xcalloc, free); + + for (i = 0; i < num_regs; i++) + { + const metag_reg *reg = &metag_regtab[i]; + const metag_reg **slot; + + slot = (const metag_reg **) htab_find_slot (reg_htab, reg, INSERT); + + if (!*slot) + *slot = reg; + } +} + +/* Create the hash table required for parsing DSP registers. */ +static void +create_dspreg_htabs (void) +{ + size_t i, num_regs = sizeof(metag_dsp_regtab)/sizeof(metag_dsp_regtab[0]); + size_t h; + + dsp_reg_htab = htab_create_alloc (num_regs, hash_regs, + eq_regs, NULL, xcalloc, free); + + for (i = 0; i < num_regs; i++) + { + const metag_reg *reg = &metag_dsp_regtab[i]; + const metag_reg **slot; + + slot = (const metag_reg **) htab_find_slot (dsp_reg_htab, reg, INSERT); + + /* Make sure there are no hash table collisions, which would + require chaining entries. */ + BFD_ASSERT (*slot == NULL); + *slot = reg; + } + + num_regs = sizeof(metag_dsp_tmpl_regtab[0])/sizeof(metag_dsp_tmpl_regtab[0][0]); + + for (h = 0; h < 2; h++) + { + dsp_tmpl_reg_htab[h] = htab_create_alloc (num_regs, hash_regs, + eq_regs, NULL, xcalloc, free); + } + + for (h = 0; h < 2; h++) + { + for (i = 0; i < num_regs; i++) + { + const metag_reg *reg = &metag_dsp_tmpl_regtab[h][i]; + const metag_reg **slot; + slot = (const metag_reg **) htab_find_slot (dsp_tmpl_reg_htab[h], + reg, INSERT); + + /* Make sure there are no hash table collisions, which would + require chaining entries. */ + BFD_ASSERT (*slot == NULL); + *slot = reg; + } + } +} + +/* Calculate a hash value for a split condition code. */ +static hashval_t +hash_scond (const void *p) +{ + split_condition *cp = (split_condition *)p; + char buf[4]; + + strupper (buf, cp->name); + + return htab_hash_string (buf); +} + +/* Check if two split condition codes are equal. */ +static int +eq_scond (const void *a, const void *b) +{ + split_condition *ra = (split_condition *)a; + split_condition *rb = (split_condition *)b; + + return strcasecmp (ra->name, rb->name) == 0; +} + +/* Create the hash table required for parsing split condition codes. */ +static void +create_scond_htab (void) +{ + size_t i, nentries; + + nentries = sizeof (metag_scondtab) / sizeof (metag_scondtab[0]); + + scond_htab = htab_create_alloc (nentries, hash_scond, eq_scond, + NULL, xcalloc, free); + for (i = 0; i < nentries; i++) + { + const split_condition *scond = &metag_scondtab[i]; + const split_condition **slot; + + slot = (const split_condition **) htab_find_slot (scond_htab, + scond, INSERT); + /* Make sure there are no hash table collisions, which would + require chaining entries. */ + BFD_ASSERT (*slot == NULL); + *slot = scond; + } +} + +/* Entry point for instruction parsing. */ +static bfd_boolean +parse_insn (const char *line, metag_insn *insn) +{ + char mnemonic[MAX_MNEMONIC_LEN]; + const char *l = line; + size_t mnemonic_len = 0; + insn_templates *templates; + + l = skip_space (l); + + while (is_mnemonic_char(*l)) + { + l++; + mnemonic_len++; + } + + if (mnemonic_len >= MAX_MNEMONIC_LEN) + { + as_bad (_("instruction mnemonic too long: %s"), line); + return FALSE; + } + + strncpy(mnemonic, line, mnemonic_len); + + mnemonic[mnemonic_len] = '\0'; + + templates = find_insn_templates (mnemonic); + + if (templates) + { + insn_templates *current_template = templates; + + l = skip_space (l); + + while (current_template) + { + const insn_template *template = current_template->template; + enum insn_encoding encoding = template->encoding; + insn_parser parser = insn_parsers[encoding]; + + current_template = current_template->next; + + if (template->insn_type == INSN_GP && + !(template->core_flags & mcpu_opt)) + continue; + + if (template->insn_type == INSN_FPU && + !(template->core_flags & mfpu_opt)) + continue; + + if (template->insn_type == INSN_DSP && + !(template->core_flags & mdsp_opt)) + continue; + + if (template->insn_type == INSN_DSP_FPU && + !((template->core_flags & mdsp_opt) && + (template->core_flags & mfpu_opt))) + continue; + + /* DSP instructions always require special decoding */ + if ((insn->type == INSN_DSP && (template->insn_type != INSN_DSP)) || + ((template->insn_type == INSN_DSP) && insn->type != INSN_DSP) || + (insn->type == INSN_DSP_FPU && (template->insn_type != INSN_DSP_FPU)) || + ((template->insn_type == INSN_DSP_FPU) && insn->type != INSN_DSP_FPU)) + continue; + + if (parser) + { + const char *end = parser(l, insn, template); + + if (end != NULL) + { + if (*end != END_OF_INSN) + as_bad (_("junk at end of line: \"%s\""), line); + else + return TRUE; + } + } + } + + as_bad (_("failed to assemble instruction: \"%s\""), line); + } + else + { + if (insn->type == INSN_FPU) + as_bad (_("unknown floating point mnemonic: \"%s\""), mnemonic); + else + as_bad (_("unknown mnemonic: \"%s\""), mnemonic); + } + return FALSE; +} + +static void +output_insn (metag_insn *insn) +{ + char *output; + + output = frag_more (insn->len); + dwarf2_emit_insn (insn->len); + + if (insn->reloc_type != BFD_RELOC_UNUSED) + { + fix_new_exp (frag_now, output - frag_now->fr_literal, + insn->reloc_size, &insn->reloc_exp, + insn->reloc_pcrel, insn->reloc_type); + } + + md_number_to_chars (output, insn->bits, insn->len); +} + +void +md_assemble (char *line) +{ + const char *l = line; + metag_insn insn; + + memset (&insn, 0, sizeof(insn)); + + insn.reloc_type = BFD_RELOC_UNUSED; + insn.reloc_pcrel = 0; + insn.reloc_size = 4; + + if (!mnemonic_htab) + { + create_mnemonic_htab (); + create_reg_htab (); + create_dspreg_htabs (); + create_scond_htab (); + } + + l = parse_prefix (l, &insn); + + if (l == NULL) + return; + + if (insn.type == INSN_DSP && + !mdsp_opt) + { + as_bad (_("cannot assemble DSP instruction, DSP option not set: %s"), + line); + return; + } + else if (insn.type == INSN_FPU && + !mfpu_opt) + { + as_bad (_("cannot assemble FPU instruction, FPU option not set: %s"), + line); + return; + } + + if (!parse_insn (l, &insn)) + return; + + output_insn (&insn); +} + +void +md_operand (expressionS * expressionP) +{ + if (* input_line_pointer == IMM_CHAR) + { + input_line_pointer ++; + expression (expressionP); + } +} + +valueT +md_section_align (segT segment ATTRIBUTE_UNUSED, valueT size) +{ + return size; +} + +symbolS * +md_undefined_symbol (char *name ATTRIBUTE_UNUSED) +{ + return NULL; +} + +/* Functions concerning relocs. */ + +/* The location from which a PC relative jump should be calculated, + given a PC relative reloc. */ + +long +md_pcrel_from_section (fixS * fixP, segT sec) +{ + if ((fixP->fx_addsy != (symbolS *) NULL + && (! S_IS_DEFINED (fixP->fx_addsy) + || S_GET_SEGMENT (fixP->fx_addsy) != sec)) + || metag_force_relocation (fixP)) + { + /* The symbol is undefined (or is defined but not in this section). + Let the linker figure it out. */ + return 0; + } + + return fixP->fx_frag->fr_address + fixP->fx_where; +} + +/* Write a value out to the object file, using the appropriate endianness. */ + +void +md_number_to_chars (char * buf, valueT val, int n) +{ + 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. +*/ + +/* Equal to MAX_PRECISION in atof-ieee.c */ +#define MAX_LITTLENUMS 6 + +char * +md_atof (int type, char * litP, int * sizeP) +{ + int i; + int prec; + LITTLENUM_TYPE words [MAX_LITTLENUMS]; + char * t; + + switch (type) + { + case 'f': + case 'F': + case 's': + case 'S': + prec = 2; + break; + + case 'd': + case 'D': + case 'r': + case 'R': + prec = 4; + break; + + /* FIXME: Some targets allow other format chars for bigger sizes here. */ + + default: + * sizeP = 0; + return _("Bad call to md_atof()"); + } + + t = atof_ieee (input_line_pointer, type, words); + if (t) + input_line_pointer = t; + * sizeP = prec * sizeof (LITTLENUM_TYPE); + + for (i = 0; i < prec; i++) + { + md_number_to_chars (litP, (valueT) words[i], + sizeof (LITTLENUM_TYPE)); + litP += sizeof (LITTLENUM_TYPE); + } + + return 0; +} + +/* If this function returns non-zero, it prevents the relocation + against symbol(s) in the FIXP from being replaced with relocations + against section symbols, and guarantees that a relocation will be + emitted even when the value can be resolved locally. */ + +int +metag_force_relocation (fixS * fix) +{ + switch (fix->fx_r_type) + { + case BFD_RELOC_METAG_RELBRANCH_PLT: + case BFD_RELOC_METAG_TLS_LE: + case BFD_RELOC_METAG_TLS_IE: + case BFD_RELOC_METAG_TLS_LDO: + case BFD_RELOC_METAG_TLS_LDM: + case BFD_RELOC_METAG_TLS_GD: + return 1; + default: + ; + } + + return generic_force_reloc (fix); +} + +bfd_boolean +metag_fix_adjustable (fixS * fixP) +{ + if (fixP->fx_addsy == NULL) + return 1; + + /* Prevent all adjustments to global symbols. */ + if (S_IS_EXTERNAL (fixP->fx_addsy)) + return 0; + if (S_IS_WEAK (fixP->fx_addsy)) + return 0; + + if (fixP->fx_r_type == BFD_RELOC_METAG_HI16_GOTOFF || + fixP->fx_r_type == BFD_RELOC_METAG_LO16_GOTOFF || + fixP->fx_r_type == BFD_RELOC_METAG_GETSET_GOTOFF || + fixP->fx_r_type == BFD_RELOC_METAG_GETSET_GOT || + fixP->fx_r_type == BFD_RELOC_METAG_HI16_GOTPC || + fixP->fx_r_type == BFD_RELOC_METAG_LO16_GOTPC || + fixP->fx_r_type == BFD_RELOC_METAG_HI16_PLT || + fixP->fx_r_type == BFD_RELOC_METAG_LO16_PLT || + fixP->fx_r_type == BFD_RELOC_METAG_RELBRANCH_PLT) + return 0; + + /* We need the symbol name for the VTABLE entries. */ + if (fixP->fx_r_type == BFD_RELOC_VTABLE_INHERIT + || fixP->fx_r_type == BFD_RELOC_VTABLE_ENTRY) + return 0; + + return 1; +} + +/* Return an initial guess of the length by which a fragment must grow to + hold a branch to reach its destination. + Also updates fr_type/fr_subtype as necessary. + + Called just before doing relaxation. + Any symbol that is now undefined will not become defined. + The guess for fr_var is ACTUALLY the growth beyond fr_fix. + Whatever we do to grow fr_fix or fr_var contributes to our returned value. + Although it may not be explicit in the frag, pretend fr_var starts with a + 0 value. */ + +int +md_estimate_size_before_relax (fragS * fragP ATTRIBUTE_UNUSED, + segT segment ATTRIBUTE_UNUSED) +{ + /* No assembler relaxation is defined (or necessary) for this port. */ + abort (); +} + +/* *fragP has been relaxed to its final size, and now needs to have + the bytes inside it modified to conform to the new size. + + Called after relaxation is finished. + fragP->fr_type == rs_machine_dependent. + fragP->fr_subtype is the subtype of what the address relaxed to. */ + +void +md_convert_frag (bfd * abfd ATTRIBUTE_UNUSED, segT sec ATTRIBUTE_UNUSED, + fragS * fragP ATTRIBUTE_UNUSED) +{ + /* No assembler relaxation is defined (or necessary) for this port. */ + abort (); +} + +/* This is called from HANDLE_ALIGN in tc-metag.h. */ + +void +metag_handle_align (fragS * fragP) +{ + static char const noop[4] = { 0xfe, 0xff, 0xff, 0xa0 }; + int bytes, fix; + char *p; + + if (fragP->fr_type != rs_align_code) + return; + + bytes = fragP->fr_next->fr_address - fragP->fr_address - fragP->fr_fix; + p = fragP->fr_literal + fragP->fr_fix; + fix = 0; + + if (bytes & 3) + { + fix = bytes & 3; + memset (p, 0, fix); + p += fix; + bytes -= fix; + } + + while (bytes >= 4) + { + memcpy (p, noop, 4); + p += 4; + bytes -= 4; + fix += 4; + } + + fragP->fr_fix += fix; + fragP->fr_var = 4; +} + +static char * +metag_end_of_match (char * cont, char * what) +{ + int len = strlen (what); + + if (strncasecmp (cont, what, strlen (what)) == 0 + && ! is_part_of_name (cont[len])) + return cont + len; + + return NULL; +} + +int +metag_parse_name (char const * name, expressionS * exprP, enum expr_mode mode, + char * nextcharP) +{ + char *next = input_line_pointer; + char *next_end; + int reloc_type; + operatorT op_type; + segT segment; + + exprP->X_op_symbol = NULL; + exprP->X_md = BFD_RELOC_UNUSED; + + if (strcmp (name, GOT_NAME) == 0) + { + if (! GOT_symbol) + GOT_symbol = symbol_find_or_make (name); + + exprP->X_add_symbol = GOT_symbol; + no_suffix: + /* If we have an absolute symbol or a + reg, then we know its value now. */ + segment = S_GET_SEGMENT (exprP->X_add_symbol); + if (mode != expr_defer && segment == absolute_section) + { + exprP->X_op = O_constant; + exprP->X_add_number = S_GET_VALUE (exprP->X_add_symbol); + exprP->X_add_symbol = NULL; + } + else if (mode != expr_defer && segment == reg_section) + { + exprP->X_op = O_register; + exprP->X_add_number = S_GET_VALUE (exprP->X_add_symbol); + exprP->X_add_symbol = NULL; + } + else + { + exprP->X_op = O_symbol; + exprP->X_add_number = 0; + } + + return 1; + } + + exprP->X_add_symbol = symbol_find_or_make (name); + + if (*nextcharP != '@') + goto no_suffix; + else if ((next_end = metag_end_of_match (next + 1, "GOTOFF"))) + { + reloc_type = BFD_RELOC_METAG_GOTOFF; + op_type = O_PIC_reloc; + } + else if ((next_end = metag_end_of_match (next + 1, "GOT"))) + { + reloc_type = BFD_RELOC_METAG_GETSET_GOT; + op_type = O_PIC_reloc; + } + else if ((next_end = metag_end_of_match (next + 1, "PLT"))) + { + reloc_type = BFD_RELOC_METAG_PLT; + op_type = O_PIC_reloc; + } + else if ((next_end = metag_end_of_match (next + 1, "TLSGD"))) + { + reloc_type = BFD_RELOC_METAG_TLS_GD; + op_type = O_PIC_reloc; + } + else if ((next_end = metag_end_of_match (next + 1, "TLSLDM"))) + { + reloc_type = BFD_RELOC_METAG_TLS_LDM; + op_type = O_PIC_reloc; + } + else if ((next_end = metag_end_of_match (next + 1, "TLSLDO"))) + { + reloc_type = BFD_RELOC_METAG_TLS_LDO; + op_type = O_PIC_reloc; + } + else if ((next_end = metag_end_of_match (next + 1, "TLSIE"))) + { + reloc_type = BFD_RELOC_METAG_TLS_IE; + op_type = O_PIC_reloc; + } + else if ((next_end = metag_end_of_match (next + 1, "TLSIENONPIC"))) + { + reloc_type = BFD_RELOC_METAG_TLS_IENONPIC; + op_type = O_PIC_reloc; /* FIXME: is this correct? */ + } + else if ((next_end = metag_end_of_match (next + 1, "TLSLE"))) + { + reloc_type = BFD_RELOC_METAG_TLS_LE; + op_type = O_PIC_reloc; + } + else + goto no_suffix; + + *input_line_pointer = *nextcharP; + input_line_pointer = next_end; + *nextcharP = *input_line_pointer; + *input_line_pointer = '\0'; + + exprP->X_op = op_type; + exprP->X_add_number = 0; + exprP->X_md = reloc_type; + + return 1; +} + +/* If while processing a fixup, a reloc really needs to be created + then it is done here. */ + +arelent * +tc_gen_reloc (seg, fixp) + asection *seg ATTRIBUTE_UNUSED; + fixS *fixp; +{ + arelent *reloc; + + reloc = (arelent *) xmalloc (sizeof (arelent)); + reloc->sym_ptr_ptr = (asymbol **) xmalloc (sizeof (asymbol *)); + *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); + reloc->address = fixp->fx_frag->fr_address + fixp->fx_where; + + reloc->addend = fixp->fx_offset; + reloc->howto = bfd_reloc_type_lookup (stdoutput, fixp->fx_r_type); + + if (reloc->howto == (reloc_howto_type *) NULL) + { + as_bad_where (fixp->fx_file, fixp->fx_line, + /* xgettext:c-format. */ + _("reloc %d not supported by object file format"), + (int) fixp->fx_r_type); + + xfree (reloc); + + return NULL; + } + + return reloc; +} + +static unsigned int +md_chars_to_number (char *val, int n) +{ + int retval; + unsigned char * where = (unsigned char *) val; + + for (retval = 0; n--;) + { + retval <<= 8; + retval |= where[n]; + } + return retval; +} + +void +md_apply_fix (fixS *fixP, valueT *valP, segT seg ATTRIBUTE_UNUSED) +{ + char *buf = fixP->fx_where + fixP->fx_frag->fr_literal; + int value = (int)*valP; + + switch (fixP->fx_r_type) + { + case BFD_RELOC_METAG_TLS_GD: + case BFD_RELOC_METAG_TLS_LE_HI16: + case BFD_RELOC_METAG_TLS_LE_LO16: + case BFD_RELOC_METAG_TLS_IE: + case BFD_RELOC_METAG_TLS_IENONPIC_HI16: + case BFD_RELOC_METAG_TLS_IENONPIC_LO16: + case BFD_RELOC_METAG_TLS_LDM: + case BFD_RELOC_METAG_TLS_LDO_HI16: + case BFD_RELOC_METAG_TLS_LDO_LO16: + S_SET_THREAD_LOCAL (fixP->fx_addsy); + /* Fall through */ + + case BFD_RELOC_METAG_HIADDR16: + case BFD_RELOC_METAG_LOADDR16: + case BFD_RELOC_VTABLE_INHERIT: + case BFD_RELOC_VTABLE_ENTRY: + fixP->fx_done = FALSE; + break; + + case BFD_RELOC_METAG_REL8: + if (!within_unsigned_range (value, IMM8_BITS)) + { + as_bad_where (fixP->fx_file, fixP->fx_line, + "rel8 out of range %d", value); + } + else + { + unsigned int newval; + newval = md_chars_to_number (buf, 4); + newval = (newval & 0xffffc03f) | ((value & IMM8_MASK) << 6); + md_number_to_chars (buf, newval, 4); + } + break; + case BFD_RELOC_METAG_REL16: + if (!within_unsigned_range (value, IMM16_BITS)) + { + as_bad_where (fixP->fx_file, fixP->fx_line, + "rel16 out of range %d", value); + } + else + { + unsigned int newval; + newval = md_chars_to_number (buf, 4); + newval = (newval & 0xfff80007) | ((value & IMM16_MASK) << 3); + md_number_to_chars (buf, newval, 4); + } + break; + + case BFD_RELOC_8: + md_number_to_chars (buf, value, 1); + break; + case BFD_RELOC_16: + md_number_to_chars (buf, value, 2); + break; + case BFD_RELOC_32: + md_number_to_chars (buf, value, 4); + break; + case BFD_RELOC_64: + md_number_to_chars (buf, value, 8); + + case BFD_RELOC_METAG_RELBRANCH: + if (!value) + break; + + value = value / 4; + + if (!within_signed_range (value, IMM19_BITS)) + { + as_bad_where (fixP->fx_file, fixP->fx_line, + "relbranch out of range %d", value); + } + else + { + unsigned int newval; + newval = md_chars_to_number (buf, 4); + newval = (newval & 0xff00001f) | ((value & IMM19_MASK) << 5); + md_number_to_chars (buf, newval, 4); + } + break; + default: + break; + } + + if (fixP->fx_addsy == NULL) + fixP->fx_done = TRUE; +} diff --git a/gas/config/tc-metag.h b/gas/config/tc-metag.h new file mode 100644 index 0000000..e0411f5 --- /dev/null +++ b/gas/config/tc-metag.h @@ -0,0 +1,72 @@ +/* tc-metag.h -- Header file for tc-metag.c. + Copyright (C) 2013 Free Software Foundation, Inc. + Contributed by Imagination Technologies Ltd. + + 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. */ + +#define TC_METAG + +#define LISTING_HEADER "META GAS " + +/* The target BFD architecture. */ +#define TARGET_ARCH bfd_arch_metag + +#define TARGET_FORMAT "elf32-metag" + +#define TARGET_BYTES_BIG_ENDIAN 0 + +/* Permit temporary numeric labels. */ +#define LOCAL_LABELS_FB 1 + +#define DIFF_EXPR_OK /* foo-. gets turned into PC relative relocs */ + +/* We don't need to handle .word strangely. */ +#define WORKING_DOT_WORD + +/* Values passed to md_apply_fix don't include the symbol value. */ +#define MD_APPLY_SYM_VALUE(FIX) 0 + +#define tc_fix_adjustable(FIX) metag_fix_adjustable (FIX) +extern bfd_boolean metag_fix_adjustable (struct fix *); + +#define TC_FORCE_RELOCATION(fix) metag_force_relocation (fix) +extern int metag_force_relocation (struct fix *); + +#define TC_HANDLES_FX_DONE + +/* Call md_pcrel_from_section(), not md_pcrel_from(). */ +#define MD_PCREL_FROM_SECTION(FIX, SEC) md_pcrel_from_section (FIX, SEC) +extern long md_pcrel_from_section (struct fix *, segT); + +#define HANDLE_ALIGN(fragp) metag_handle_align (fragp) +extern void metag_handle_align (struct frag *); + +#define DWARF2_LINE_MIN_INSN_LENGTH 1 + +#define md_parse_name(name, exprP, mode, nextcharP) \ + metag_parse_name ((name), (exprP), (mode), (nextcharP)) +extern int metag_parse_name (char const *, expressionS *, enum expr_mode, char *); + +/* This is used to construct expressions out of @GOTOFF, @PLT and @GOT + symbols. The relocation type is stored in X_md. */ +#define O_PIC_reloc O_md1 + +#define TC_CASE_SENSITIVE + +extern const char metag_symbol_chars[]; +#define tc_symbol_chars metag_symbol_chars |