blob: 554d4330d334a35190894a4512deb0441076a217 [file] [log] [blame]
/* tc-metag.c -- Assembler for the Imagination Technologies Meta.
Copyright (C) 2013-2021 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 <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, &regs_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, &regs_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 bool
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 bool
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;
bool 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 addressing 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, bool 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 bool
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 bool
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 bool
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);
bool 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)
{
bool is_mov = startswith (template->name, "MOV");
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);
bool is_get = MINOR_OPCODE (template->meta_opcode) == GET_EXT_MINOR;
bool 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 bool
check_rmask (const metag_reg **regs, size_t regs_read, bool is_fpu,
bool 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;
bool is_get = MAJOR_OPCODE (template->meta_opcode) == OPC_GET;
bool is_fpu = (MINOR_OPCODE (template->meta_opcode) & 0x6) == 0x6;
bool 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, &regs_read);
}
else
{
l = parse_mset (l, regs, &addr, &regs_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, &regs_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, &regs_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;
bool is_fpu = template->insn_type == INSN_FPU;
bool is_prime = (MINOR_OPCODE (template->meta_opcode) & 0x2) != 0 && !is_fpu;
bool is_64bit = (MINOR_OPCODE (template->meta_opcode) & 0x1) != 0;
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, &reg, &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, &regs_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;
bool is_hi = false;
bool 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];
bool top = (template->meta_opcode & 0x1) != 0;
bool is_trace = ((template->meta_opcode >> 2) & 0x1) != 0;
bool 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 && !top)
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;
/* PC.r | CT.r | TR.r | TT.r are treated as if they are a single unit. */
switch (regs[0]->unit)
{
case UNIT_PC:
case UNIT_CT:
case UNIT_TR:
case UNIT_TT:
if (regs[1]->unit == UNIT_PC
|| regs[1]->unit == UNIT_CT
|| regs[1]->unit == UNIT_TR
|| regs[1]->unit == UNIT_TT)
{
as_bad (_("PC, CT, TR and TT are treated as if they are a single unit but operands must be in different units"));
return NULL;
}
break;
default:
/* Registers must be in different units. */
if (regs[0]->unit == regs[1]->unit)
{
as_bad (_("source and destination register must be in different units"));
return NULL;
}
break;
}
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;
bool o1z = 0;
bool imm = ((template->meta_opcode >> 25) & 0x1) != 0;
bool cond = ((template->meta_opcode >> 26) & 0x1) != 0;
bool ca = ((template->meta_opcode >> 5) & 0x1) != 0;
bool top = (template->meta_opcode & 0x1) != 0;
bool sign_extend = 0;
bool is_addr_op = MAJOR_OPCODE (template->meta_opcode) == OPC_ADDR;
bool is_mul = MAJOR_OPCODE (template->meta_opcode) == OPC_MUL;
unsigned int unit_bit = 0;
bool is_quickrot = (template->arg_type & GP_ARGS_QR) != 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 (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
{
bool 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];
bool 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;
}
/* Fall through. */
default:
as_bad (_("invalid quickrot register specified"));
return NULL;
}
}
if (sign_extend && !top)
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;
bool cond = ((template->meta_opcode >> 26) & 0x1) != 0;
bool ca = ((template->meta_opcode >> 5) & 0x1) != 0;
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];
bool swap_inst = MAJOR_OPCODE (template->meta_opcode) == OPC_MISC;
bool 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;
bool imm = ((template->meta_opcode >> 25) & 0x1) != 0;
bool cond = ((template->meta_opcode >> 26) & 0x1) != 0;
bool top = (template->meta_opcode & 0x1) != 0;
bool 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
{
bool 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 && !top)
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)