blob: d03dc72b401becc32c81aeed10aa169b7f584e5a [file] [log] [blame]
/* tc-arc.c -- Assembler for the ARC
Copyright (C) 1994-2021 Free Software Foundation, Inc.
Contributor: Claudiu Zissulescu <claziss@synopsys.com>
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 "dwarf2dbg.h"
#include "dw2gencfi.h"
#include "safe-ctype.h"
#include "opcode/arc.h"
#include "opcode/arc-attrs.h"
#include "elf/arc.h"
#include "../opcodes/arc-ext.h"
/* Defines section. */
#define MAX_INSN_FIXUPS 2
#define MAX_CONSTR_STR 20
#define FRAG_MAX_GROWTH 8
#ifdef DEBUG
# define pr_debug(fmt, args...) fprintf (stderr, fmt, ##args)
#else
# define pr_debug(fmt, args...)
#endif
#define MAJOR_OPCODE(x) (((x) & 0xF8000000) >> 27)
#define SUB_OPCODE(x) (((x) & 0x003F0000) >> 16)
#define LP_INSN(x) ((MAJOR_OPCODE (x) == 0x4) \
&& (SUB_OPCODE (x) == 0x28))
#ifndef TARGET_WITH_CPU
#define TARGET_WITH_CPU "arc700"
#endif /* TARGET_WITH_CPU */
#define ARC_GET_FLAG(s) (*symbol_get_tc (s))
#define ARC_SET_FLAG(s,v) (*symbol_get_tc (s) |= (v))
#define streq(a, b) (strcmp (a, b) == 0)
/* Enum used to enumerate the relaxable ins operands. */
enum rlx_operand_type
{
EMPTY = 0,
REGISTER,
REGISTER_S, /* Register for short instruction(s). */
REGISTER_NO_GP, /* Is a register but not gp register specifically. */
REGISTER_DUP, /* Duplication of previous operand of type register. */
IMMEDIATE,
BRACKET
};
enum arc_rlx_types
{
ARC_RLX_NONE = 0,
ARC_RLX_BL_S,
ARC_RLX_BL,
ARC_RLX_B_S,
ARC_RLX_B,
ARC_RLX_ADD_U3,
ARC_RLX_ADD_U6,
ARC_RLX_ADD_LIMM,
ARC_RLX_LD_U7,
ARC_RLX_LD_S9,
ARC_RLX_LD_LIMM,
ARC_RLX_MOV_U8,
ARC_RLX_MOV_S12,
ARC_RLX_MOV_LIMM,
ARC_RLX_SUB_U3,
ARC_RLX_SUB_U6,
ARC_RLX_SUB_LIMM,
ARC_RLX_MPY_U6,
ARC_RLX_MPY_LIMM,
ARC_RLX_MOV_RU6,
ARC_RLX_MOV_RLIMM,
ARC_RLX_ADD_RRU6,
ARC_RLX_ADD_RRLIMM,
};
/* Macros section. */
#define regno(x) ((x) & 0x3F)
#define is_ir_num(x) (((x) & ~0x3F) == 0)
#define is_code_density_p(sc) (((sc) == CD1 || (sc) == CD2))
#define is_spfp_p(op) (((sc) == SPX))
#define is_dpfp_p(op) (((sc) == DPX))
#define is_fpuda_p(op) (((sc) == DPA))
#define is_br_jmp_insn_p(op) (((op)->insn_class == BRANCH \
|| (op)->insn_class == JUMP \
|| (op)->insn_class == BRCC \
|| (op)->insn_class == BBIT0 \
|| (op)->insn_class == BBIT1 \
|| (op)->insn_class == BI \
|| (op)->insn_class == EI \
|| (op)->insn_class == ENTER \
|| (op)->insn_class == JLI \
|| (op)->insn_class == LOOP \
|| (op)->insn_class == LEAVE \
))
#define is_kernel_insn_p(op) (((op)->insn_class == KERNEL))
#define is_nps400_p(op) (((sc) == NPS400))
/* Generic assembler global variables which must be defined by all
targets. */
/* Characters which always start a comment. */
const char comment_chars[] = "#;";
/* Characters which start a comment at the beginning of a line. */
const char line_comment_chars[] = "#";
/* Characters which may be used to separate multiple commands on a
single line. */
const char line_separator_chars[] = "`";
/* Characters which are used to indicate an exponent in a floating
point number. */
const char EXP_CHARS[] = "eE";
/* Chars that mean this number is a floating point constant
As in 0f12.456 or 0d1.2345e12. */
const char FLT_CHARS[] = "rRsSfFdD";
/* Byte order. */
extern int target_big_endian;
const char *arc_target_format = DEFAULT_TARGET_FORMAT;
static int byte_order = DEFAULT_BYTE_ORDER;
/* Arc extension section. */
static segT arcext_section;
/* By default relaxation is disabled. */
static int relaxation_state = 0;
extern int arc_get_mach (char *);
/* Forward declarations. */
static void arc_lcomm (int);
static void arc_option (int);
static void arc_extra_reloc (int);
static void arc_extinsn (int);
static void arc_extcorereg (int);
static void arc_attribute (int);
const pseudo_typeS md_pseudo_table[] =
{
/* Make sure that .word is 32 bits. */
{ "word", cons, 4 },
{ "align", s_align_bytes, 0 }, /* Defaulting is invalid (0). */
{ "lcomm", arc_lcomm, 0 },
{ "lcommon", arc_lcomm, 0 },
{ "cpu", arc_option, 0 },
{ "arc_attribute", arc_attribute, 0 },
{ "extinstruction", arc_extinsn, 0 },
{ "extcoreregister", arc_extcorereg, EXT_CORE_REGISTER },
{ "extauxregister", arc_extcorereg, EXT_AUX_REGISTER },
{ "extcondcode", arc_extcorereg, EXT_COND_CODE },
{ "tls_gd_ld", arc_extra_reloc, BFD_RELOC_ARC_TLS_GD_LD },
{ "tls_gd_call", arc_extra_reloc, BFD_RELOC_ARC_TLS_GD_CALL },
{ NULL, NULL, 0 }
};
const char *md_shortopts = "";
enum options
{
OPTION_EB = OPTION_MD_BASE,
OPTION_EL,
OPTION_ARC600,
OPTION_ARC601,
OPTION_ARC700,
OPTION_ARCEM,
OPTION_ARCHS,
OPTION_MCPU,
OPTION_CD,
OPTION_RELAX,
OPTION_NPS400,
OPTION_SPFP,
OPTION_DPFP,
OPTION_FPUDA,
/* The following options are deprecated and provided here only for
compatibility reasons. */
OPTION_USER_MODE,
OPTION_LD_EXT_MASK,
OPTION_SWAP,
OPTION_NORM,
OPTION_BARREL_SHIFT,
OPTION_MIN_MAX,
OPTION_NO_MPY,
OPTION_EA,
OPTION_MUL64,
OPTION_SIMD,
OPTION_XMAC_D16,
OPTION_XMAC_24,
OPTION_DSP_PACKA,
OPTION_CRC,
OPTION_DVBF,
OPTION_TELEPHONY,
OPTION_XYMEMORY,
OPTION_LOCK,
OPTION_SWAPE,
OPTION_RTSC
};
struct option md_longopts[] =
{
{ "EB", no_argument, NULL, OPTION_EB },
{ "EL", no_argument, NULL, OPTION_EL },
{ "mcpu", required_argument, NULL, OPTION_MCPU },
{ "mA6", no_argument, NULL, OPTION_ARC600 },
{ "mARC600", no_argument, NULL, OPTION_ARC600 },
{ "mARC601", no_argument, NULL, OPTION_ARC601 },
{ "mARC700", no_argument, NULL, OPTION_ARC700 },
{ "mA7", no_argument, NULL, OPTION_ARC700 },
{ "mEM", no_argument, NULL, OPTION_ARCEM },
{ "mHS", no_argument, NULL, OPTION_ARCHS },
{ "mcode-density", no_argument, NULL, OPTION_CD },
{ "mrelax", no_argument, NULL, OPTION_RELAX },
{ "mnps400", no_argument, NULL, OPTION_NPS400 },
/* Floating point options */
{ "mspfp", no_argument, NULL, OPTION_SPFP},
{ "mspfp-compact", no_argument, NULL, OPTION_SPFP},
{ "mspfp_compact", no_argument, NULL, OPTION_SPFP},
{ "mspfp-fast", no_argument, NULL, OPTION_SPFP},
{ "mspfp_fast", no_argument, NULL, OPTION_SPFP},
{ "mdpfp", no_argument, NULL, OPTION_DPFP},
{ "mdpfp-compact", no_argument, NULL, OPTION_DPFP},
{ "mdpfp_compact", no_argument, NULL, OPTION_DPFP},
{ "mdpfp-fast", no_argument, NULL, OPTION_DPFP},
{ "mdpfp_fast", no_argument, NULL, OPTION_DPFP},
{ "mfpuda", no_argument, NULL, OPTION_FPUDA},
/* The following options are deprecated and provided here only for
compatibility reasons. */
{ "mav2em", no_argument, NULL, OPTION_ARCEM },
{ "mav2hs", no_argument, NULL, OPTION_ARCHS },
{ "muser-mode-only", no_argument, NULL, OPTION_USER_MODE },
{ "mld-extension-reg-mask", required_argument, NULL, OPTION_LD_EXT_MASK },
{ "mswap", no_argument, NULL, OPTION_SWAP },
{ "mnorm", no_argument, NULL, OPTION_NORM },
{ "mbarrel-shifter", no_argument, NULL, OPTION_BARREL_SHIFT },
{ "mbarrel_shifter", no_argument, NULL, OPTION_BARREL_SHIFT },
{ "mmin-max", no_argument, NULL, OPTION_MIN_MAX },
{ "mmin_max", no_argument, NULL, OPTION_MIN_MAX },
{ "mno-mpy", no_argument, NULL, OPTION_NO_MPY },
{ "mea", no_argument, NULL, OPTION_EA },
{ "mEA", no_argument, NULL, OPTION_EA },
{ "mmul64", no_argument, NULL, OPTION_MUL64 },
{ "msimd", no_argument, NULL, OPTION_SIMD},
{ "mmac-d16", no_argument, NULL, OPTION_XMAC_D16},
{ "mmac_d16", no_argument, NULL, OPTION_XMAC_D16},
{ "mmac-24", no_argument, NULL, OPTION_XMAC_24},
{ "mmac_24", no_argument, NULL, OPTION_XMAC_24},
{ "mdsp-packa", no_argument, NULL, OPTION_DSP_PACKA},
{ "mdsp_packa", no_argument, NULL, OPTION_DSP_PACKA},
{ "mcrc", no_argument, NULL, OPTION_CRC},
{ "mdvbf", no_argument, NULL, OPTION_DVBF},
{ "mtelephony", no_argument, NULL, OPTION_TELEPHONY},
{ "mxy", no_argument, NULL, OPTION_XYMEMORY},
{ "mlock", no_argument, NULL, OPTION_LOCK},
{ "mswape", no_argument, NULL, OPTION_SWAPE},
{ "mrtsc", no_argument, NULL, OPTION_RTSC},
{ NULL, no_argument, NULL, 0 }
};
size_t md_longopts_size = sizeof (md_longopts);
/* Local data and data types. */
/* Used since new relocation types are introduced in this
file (DUMMY_RELOC_LITUSE_*). */
typedef int extended_bfd_reloc_code_real_type;
struct arc_fixup
{
expressionS exp;
extended_bfd_reloc_code_real_type reloc;
/* index into arc_operands. */
unsigned int opindex;
/* PC-relative, used by internals fixups. */
unsigned char pcrel;
/* TRUE if this fixup is for LIMM operand. */
bool islong;
};
struct arc_insn
{
unsigned long long int insn;
int nfixups;
struct arc_fixup fixups[MAX_INSN_FIXUPS];
long limm;
unsigned int len; /* Length of instruction in bytes. */
bool has_limm; /* Boolean value: TRUE if limm field is valid. */
bool relax; /* Boolean value: TRUE if needs relaxation. */
};
/* Structure to hold any last two instructions. */
static struct arc_last_insn
{
/* Saved instruction opcode. */
const struct arc_opcode *opcode;
/* Boolean value: TRUE if current insn is short. */
bool has_limm;
/* Boolean value: TRUE if current insn has delay slot. */
bool has_delay_slot;
} arc_last_insns[2];
/* Extension instruction suffix classes. */
typedef struct
{
const char *name;
int len;
int attr_class;
} attributes_t;
static const attributes_t suffixclass[] =
{
{ "SUFFIX_FLAG", 11, ARC_SUFFIX_FLAG },
{ "SUFFIX_COND", 11, ARC_SUFFIX_COND },
{ "SUFFIX_NONE", 11, ARC_SUFFIX_NONE }
};
/* Extension instruction syntax classes. */
static const attributes_t syntaxclass[] =
{
{ "SYNTAX_3OP", 10, ARC_SYNTAX_3OP },
{ "SYNTAX_2OP", 10, ARC_SYNTAX_2OP },
{ "SYNTAX_1OP", 10, ARC_SYNTAX_1OP },
{ "SYNTAX_NOP", 10, ARC_SYNTAX_NOP }
};
/* Extension instruction syntax classes modifiers. */
static const attributes_t syntaxclassmod[] =
{
{ "OP1_IMM_IMPLIED" , 15, ARC_OP1_IMM_IMPLIED },
{ "OP1_MUST_BE_IMM" , 15, ARC_OP1_MUST_BE_IMM }
};
/* Extension register type. */
typedef struct
{
char *name;
int number;
int imode;
} extRegister_t;
/* A structure to hold the additional conditional codes. */
static struct
{
struct arc_flag_operand *arc_ext_condcode;
int size;
} ext_condcode = { NULL, 0 };
/* Structure to hold an entry in ARC_OPCODE_HASH. */
struct arc_opcode_hash_entry
{
/* The number of pointers in the OPCODE list. */
size_t count;
/* Points to a list of opcode pointers. */
const struct arc_opcode **opcode;
};
/* Structure used for iterating through an arc_opcode_hash_entry. */
struct arc_opcode_hash_entry_iterator
{
/* Index into the OPCODE element of the arc_opcode_hash_entry. */
size_t index;
/* The specific ARC_OPCODE from the ARC_OPCODES table that was last
returned by this iterator. */
const struct arc_opcode *opcode;
};
/* Forward declaration. */
static void assemble_insn
(const struct arc_opcode *, const expressionS *, int,
const struct arc_flags *, int, struct arc_insn *);
/* The selection of the machine type can come from different sources. This
enum is used to track how the selection was made in order to perform
error checks. */
enum mach_selection_type
{
MACH_SELECTION_NONE,
MACH_SELECTION_FROM_DEFAULT,
MACH_SELECTION_FROM_CPU_DIRECTIVE,
MACH_SELECTION_FROM_COMMAND_LINE
};
/* How the current machine type was selected. */
static enum mach_selection_type mach_selection_mode = MACH_SELECTION_NONE;
/* The hash table of instruction opcodes. */
static htab_t arc_opcode_hash;
/* The hash table of register symbols. */
static htab_t arc_reg_hash;
/* The hash table of aux register symbols. */
static htab_t arc_aux_hash;
/* The hash table of address types. */
static htab_t arc_addrtype_hash;
#define ARC_CPU_TYPE_A6xx(NAME,EXTRA) \
{ #NAME, ARC_OPCODE_ARC600, bfd_mach_arc_arc600, \
E_ARC_MACH_ARC600, EXTRA}
#define ARC_CPU_TYPE_A7xx(NAME,EXTRA) \
{ #NAME, ARC_OPCODE_ARC700, bfd_mach_arc_arc700, \
E_ARC_MACH_ARC700, EXTRA}
#define ARC_CPU_TYPE_AV2EM(NAME,EXTRA) \
{ #NAME, ARC_OPCODE_ARCv2EM, bfd_mach_arc_arcv2, \
EF_ARC_CPU_ARCV2EM, EXTRA}
#define ARC_CPU_TYPE_AV2HS(NAME,EXTRA) \
{ #NAME, ARC_OPCODE_ARCv2HS, bfd_mach_arc_arcv2, \
EF_ARC_CPU_ARCV2HS, EXTRA}
#define ARC_CPU_TYPE_NONE \
{ 0, 0, 0, 0, 0 }
/* A table of CPU names and opcode sets. */
static const struct cpu_type
{
const char *name;
unsigned flags;
int mach;
unsigned eflags;
unsigned features;
}
cpu_types[] =
{
#include "elf/arc-cpu.def"
};
/* Information about the cpu/variant we're assembling for. */
static struct cpu_type selected_cpu = { 0, 0, 0, E_ARC_OSABI_CURRENT, 0 };
/* TRUE if current assembly code uses RF16 only registers. */
static bool rf16_only = true;
/* MPY option. */
static unsigned mpy_option = 0;
/* Use PIC. */
static unsigned pic_option = 0;
/* Use small data. */
static unsigned sda_option = 0;
/* Use TLS. */
static unsigned tls_option = 0;
/* Command line given features. */
static unsigned cl_features = 0;
/* Used by the arc_reloc_op table. Order is important. */
#define O_gotoff O_md1 /* @gotoff relocation. */
#define O_gotpc O_md2 /* @gotpc relocation. */
#define O_plt O_md3 /* @plt relocation. */
#define O_sda O_md4 /* @sda relocation. */
#define O_pcl O_md5 /* @pcl relocation. */
#define O_tlsgd O_md6 /* @tlsgd relocation. */
#define O_tlsie O_md7 /* @tlsie relocation. */
#define O_tpoff9 O_md8 /* @tpoff9 relocation. */
#define O_tpoff O_md9 /* @tpoff relocation. */
#define O_dtpoff9 O_md10 /* @dtpoff9 relocation. */
#define O_dtpoff O_md11 /* @dtpoff relocation. */
#define O_last O_dtpoff
/* Used to define a bracket as operand in tokens. */
#define O_bracket O_md32
/* Used to define a colon as an operand in tokens. */
#define O_colon O_md31
/* Used to define address types in nps400. */
#define O_addrtype O_md30
/* Dummy relocation, to be sorted out. */
#define DUMMY_RELOC_ARC_ENTRY (BFD_RELOC_UNUSED + 1)
#define USER_RELOC_P(R) ((R) >= O_gotoff && (R) <= O_last)
/* A table to map the spelling of a relocation operand into an appropriate
bfd_reloc_code_real_type type. The table is assumed to be ordered such
that op-O_literal indexes into it. */
#define ARC_RELOC_TABLE(op) \
(&arc_reloc_op[ ((!USER_RELOC_P (op)) \
? (abort (), 0) \
: (int) (op) - (int) O_gotoff) ])
#define DEF(NAME, RELOC, REQ) \
{ #NAME, sizeof (#NAME)-1, O_##NAME, RELOC, REQ}
static const struct arc_reloc_op_tag
{
/* String to lookup. */
const char *name;
/* Size of the string. */
size_t length;
/* Which operator to use. */
operatorT op;
extended_bfd_reloc_code_real_type reloc;
/* Allows complex relocation expression like identifier@reloc +
const. */
unsigned int complex_expr : 1;
}
arc_reloc_op[] =
{
DEF (gotoff, BFD_RELOC_ARC_GOTOFF, 1),
DEF (gotpc, BFD_RELOC_ARC_GOTPC32, 0),
DEF (plt, BFD_RELOC_ARC_PLT32, 0),
DEF (sda, DUMMY_RELOC_ARC_ENTRY, 1),
DEF (pcl, BFD_RELOC_ARC_PC32, 1),
DEF (tlsgd, BFD_RELOC_ARC_TLS_GD_GOT, 0),
DEF (tlsie, BFD_RELOC_ARC_TLS_IE_GOT, 0),
DEF (tpoff9, BFD_RELOC_ARC_TLS_LE_S9, 0),
DEF (tpoff, BFD_RELOC_ARC_TLS_LE_32, 1),
DEF (dtpoff9, BFD_RELOC_ARC_TLS_DTPOFF_S9, 0),
DEF (dtpoff, BFD_RELOC_ARC_TLS_DTPOFF, 1),
};
static const int arc_num_reloc_op
= sizeof (arc_reloc_op) / sizeof (*arc_reloc_op);
/* Structure for relaxable instruction that have to be swapped with a
smaller alternative instruction. */
struct arc_relaxable_ins
{
/* Mnemonic that should be checked. */
const char *mnemonic_r;
/* Operands that should be checked.
Indexes of operands from operand array. */
enum rlx_operand_type operands[6];
/* Flags that should be checked. */
unsigned flag_classes[5];
/* Mnemonic (smaller) alternative to be used later for relaxation. */
const char *mnemonic_alt;
/* Index of operand that generic relaxation has to check. */
unsigned opcheckidx;
/* Base subtype index used. */
enum arc_rlx_types subtype;
};
#define RELAX_TABLE_ENTRY(BITS, ISSIGNED, SIZE, NEXT) \
{ (ISSIGNED) ? ((1 << ((BITS) - 1)) - 1) : ((1 << (BITS)) - 1), \
(ISSIGNED) ? -(1 << ((BITS) - 1)) : 0, \
(SIZE), \
(NEXT) } \
#define RELAX_TABLE_ENTRY_MAX(ISSIGNED, SIZE, NEXT) \
{ (ISSIGNED) ? 0x7FFFFFFF : 0xFFFFFFFF, \
(ISSIGNED) ? -(0x7FFFFFFF) : 0, \
(SIZE), \
(NEXT) } \
/* ARC relaxation table. */
const relax_typeS md_relax_table[] =
{
/* Fake entry. */
{0, 0, 0, 0},
/* BL_S s13 ->
BL s25. */
RELAX_TABLE_ENTRY (13, 1, 2, ARC_RLX_BL),
RELAX_TABLE_ENTRY (25, 1, 4, ARC_RLX_NONE),
/* B_S s10 ->
B s25. */
RELAX_TABLE_ENTRY (10, 1, 2, ARC_RLX_B),
RELAX_TABLE_ENTRY (25, 1, 4, ARC_RLX_NONE),
/* ADD_S c,b, u3 ->
ADD<.f> a,b,u6 ->
ADD<.f> a,b,limm. */
RELAX_TABLE_ENTRY (3, 0, 2, ARC_RLX_ADD_U6),
RELAX_TABLE_ENTRY (6, 0, 4, ARC_RLX_ADD_LIMM),
RELAX_TABLE_ENTRY_MAX (0, 8, ARC_RLX_NONE),
/* LD_S a, [b, u7] ->
LD<zz><.x><.aa><.di> a, [b, s9] ->
LD<zz><.x><.aa><.di> a, [b, limm] */
RELAX_TABLE_ENTRY (7, 0, 2, ARC_RLX_LD_S9),
RELAX_TABLE_ENTRY (9, 1, 4, ARC_RLX_LD_LIMM),
RELAX_TABLE_ENTRY_MAX (1, 8, ARC_RLX_NONE),
/* MOV_S b, u8 ->
MOV<.f> b, s12 ->
MOV<.f> b, limm. */
RELAX_TABLE_ENTRY (8, 0, 2, ARC_RLX_MOV_S12),
RELAX_TABLE_ENTRY (8, 0, 4, ARC_RLX_MOV_LIMM),
RELAX_TABLE_ENTRY_MAX (0, 8, ARC_RLX_NONE),
/* SUB_S c, b, u3 ->
SUB<.f> a, b, u6 ->
SUB<.f> a, b, limm. */
RELAX_TABLE_ENTRY (3, 0, 2, ARC_RLX_SUB_U6),
RELAX_TABLE_ENTRY (6, 0, 4, ARC_RLX_SUB_LIMM),
RELAX_TABLE_ENTRY_MAX (0, 8, ARC_RLX_NONE),
/* MPY<.f> a, b, u6 ->
MPY<.f> a, b, limm. */
RELAX_TABLE_ENTRY (6, 0, 4, ARC_RLX_MPY_LIMM),
RELAX_TABLE_ENTRY_MAX (0, 8, ARC_RLX_NONE),
/* MOV<.f><.cc> b, u6 ->
MOV<.f><.cc> b, limm. */
RELAX_TABLE_ENTRY (6, 0, 4, ARC_RLX_MOV_RLIMM),
RELAX_TABLE_ENTRY_MAX (0, 8, ARC_RLX_NONE),
/* ADD<.f><.cc> b, b, u6 ->
ADD<.f><.cc> b, b, limm. */
RELAX_TABLE_ENTRY (6, 0, 4, ARC_RLX_ADD_RRLIMM),
RELAX_TABLE_ENTRY_MAX (0, 8, ARC_RLX_NONE),
};
/* Order of this table's entries matters! */
const struct arc_relaxable_ins arc_relaxable_insns[] =
{
{ "bl", { IMMEDIATE }, { 0 }, "bl_s", 0, ARC_RLX_BL_S },
{ "b", { IMMEDIATE }, { 0 }, "b_s", 0, ARC_RLX_B_S },
{ "add", { REGISTER, REGISTER_DUP, IMMEDIATE }, { 5, 1, 0 }, "add",
2, ARC_RLX_ADD_RRU6},
{ "add", { REGISTER_S, REGISTER_S, IMMEDIATE }, { 0 }, "add_s", 2,
ARC_RLX_ADD_U3 },
{ "add", { REGISTER, REGISTER, IMMEDIATE }, { 5, 0 }, "add", 2,
ARC_RLX_ADD_U6 },
{ "ld", { REGISTER_S, BRACKET, REGISTER_S, IMMEDIATE, BRACKET },
{ 0 }, "ld_s", 3, ARC_RLX_LD_U7 },
{ "ld", { REGISTER, BRACKET, REGISTER_NO_GP, IMMEDIATE, BRACKET },
{ 11, 4, 14, 17, 0 }, "ld", 3, ARC_RLX_LD_S9 },
{ "mov", { REGISTER_S, IMMEDIATE }, { 0 }, "mov_s", 1, ARC_RLX_MOV_U8 },
{ "mov", { REGISTER, IMMEDIATE }, { 5, 0 }, "mov", 1, ARC_RLX_MOV_S12 },
{ "mov", { REGISTER, IMMEDIATE }, { 5, 1, 0 },"mov", 1, ARC_RLX_MOV_RU6 },
{ "sub", { REGISTER_S, REGISTER_S, IMMEDIATE }, { 0 }, "sub_s", 2,
ARC_RLX_SUB_U3 },
{ "sub", { REGISTER, REGISTER, IMMEDIATE }, { 5, 0 }, "sub", 2,
ARC_RLX_SUB_U6 },
{ "mpy", { REGISTER, REGISTER, IMMEDIATE }, { 5, 0 }, "mpy", 2,
ARC_RLX_MPY_U6 },
};
const unsigned arc_num_relaxable_ins = ARRAY_SIZE (arc_relaxable_insns);
/* Pre-defined "_GLOBAL_OFFSET_TABLE_". */
symbolS * GOT_symbol = 0;
/* Set to TRUE when we assemble instructions. */
static bool assembling_insn = false;
/* List with attributes set explicitly. */
static bool attributes_set_explicitly[NUM_KNOWN_OBJ_ATTRIBUTES];
/* Functions implementation. */
/* Return a pointer to ARC_OPCODE_HASH_ENTRY that identifies all
ARC_OPCODE entries in ARC_OPCODE_HASH that match NAME, or NULL if there
are no matching entries in ARC_OPCODE_HASH. */
static const struct arc_opcode_hash_entry *
arc_find_opcode (const char *name)
{
const struct arc_opcode_hash_entry *entry;
entry = str_hash_find (arc_opcode_hash, name);
return entry;
}
/* Initialise the iterator ITER. */
static void
arc_opcode_hash_entry_iterator_init (struct arc_opcode_hash_entry_iterator *iter)
{
iter->index = 0;
iter->opcode = NULL;
}
/* Return the next ARC_OPCODE from ENTRY, using ITER to hold state between
calls to this function. Return NULL when all ARC_OPCODE entries have
been returned. */
static const struct arc_opcode *
arc_opcode_hash_entry_iterator_next (const struct arc_opcode_hash_entry *entry,
struct arc_opcode_hash_entry_iterator *iter)
{
if (iter->opcode == NULL && iter->index == 0)
{
gas_assert (entry->count > 0);
iter->opcode = entry->opcode[iter->index];
}
else if (iter->opcode != NULL)
{
const char *old_name = iter->opcode->name;
iter->opcode++;
if (iter->opcode->name == NULL
|| strcmp (old_name, iter->opcode->name) != 0)
{
iter->index++;
if (iter->index == entry->count)
iter->opcode = NULL;
else
iter->opcode = entry->opcode[iter->index];
}
}
return iter->opcode;
}
/* Insert an opcode into opcode hash structure. */
static void
arc_insert_opcode (const struct arc_opcode *opcode)
{
const char *name;
struct arc_opcode_hash_entry *entry;
name = opcode->name;
entry = str_hash_find (arc_opcode_hash, name);
if (entry == NULL)
{
entry = XNEW (struct arc_opcode_hash_entry);
entry->count = 0;
entry->opcode = NULL;
if (str_hash_insert (arc_opcode_hash, name, entry, 0) != NULL)
as_fatal (_("duplicate %s"), name);
}
entry->opcode = XRESIZEVEC (const struct arc_opcode *, entry->opcode,
entry->count + 1);
entry->opcode[entry->count] = opcode;
entry->count++;
}
/* Like md_number_to_chars but for middle-endian values. The 4-byte limm
value, is encoded as 'middle-endian' for a little-endian target. This
function is used for regular 4, 6, and 8 byte instructions as well. */
static void
md_number_to_chars_midend (char *buf, unsigned long long val, int n)
{
switch (n)
{
case 2:
md_number_to_chars (buf, val, n);
break;
case 6:
md_number_to_chars (buf, (val & 0xffff00000000ull) >> 32, 2);
md_number_to_chars_midend (buf + 2, (val & 0xffffffff), 4);
break;
case 4:
md_number_to_chars (buf, (val & 0xffff0000) >> 16, 2);
md_number_to_chars (buf + 2, (val & 0xffff), 2);
break;
case 8:
md_number_to_chars_midend (buf, (val & 0xffffffff00000000ull) >> 32, 4);
md_number_to_chars_midend (buf + 4, (val & 0xffffffff), 4);
break;
default:
abort ();
}
}
/* Check if a feature is allowed for a specific CPU. */
static void
arc_check_feature (void)
{
unsigned i;
if (!selected_cpu.features
|| !selected_cpu.name)
return;
for (i = 0; i < ARRAY_SIZE (feature_list); i++)
if ((selected_cpu.features & feature_list[i].feature)
&& !(selected_cpu.flags & feature_list[i].cpus))
as_bad (_("invalid %s option for %s cpu"), feature_list[i].name,
selected_cpu.name);
for (i = 0; i < ARRAY_SIZE (conflict_list); i++)
if ((selected_cpu.features & conflict_list[i]) == conflict_list[i])
as_bad(_("conflicting ISA extension attributes."));
}
/* Select an appropriate entry from CPU_TYPES based on ARG and initialise
the relevant static global variables. Parameter SEL describes where
this selection originated from. */
static void
arc_select_cpu (const char *arg, enum mach_selection_type sel)
{
int i;
static struct cpu_type old_cpu = { 0, 0, 0, E_ARC_OSABI_CURRENT, 0 };
/* We should only set a default if we've not made a selection from some
other source. */
gas_assert (sel != MACH_SELECTION_FROM_DEFAULT
|| mach_selection_mode == MACH_SELECTION_NONE);
if ((mach_selection_mode == MACH_SELECTION_FROM_CPU_DIRECTIVE)
&& (sel == MACH_SELECTION_FROM_CPU_DIRECTIVE))
as_bad (_("Multiple .cpu directives found"));
/* Look for a matching entry in CPU_TYPES array. */
for (i = 0; cpu_types[i].name; ++i)
{
if (!strcasecmp (cpu_types[i].name, arg))
{
/* If a previous selection was made on the command line, then we
allow later selections on the command line to override earlier
ones. However, a selection from a '.cpu NAME' directive must
match the command line selection, or we give a warning. */
if (mach_selection_mode == MACH_SELECTION_FROM_COMMAND_LINE)
{
gas_assert (sel == MACH_SELECTION_FROM_COMMAND_LINE
|| sel == MACH_SELECTION_FROM_CPU_DIRECTIVE);
if (sel == MACH_SELECTION_FROM_CPU_DIRECTIVE
&& selected_cpu.mach != cpu_types[i].mach)
{
as_warn (_("Command-line value overrides \".cpu\" directive"));
}
return;
}
/* Initialise static global data about selected machine type. */
selected_cpu.flags = cpu_types[i].flags;
selected_cpu.name = cpu_types[i].name;
selected_cpu.features = cpu_types[i].features | cl_features;
selected_cpu.mach = cpu_types[i].mach;
selected_cpu.eflags = ((selected_cpu.eflags & ~EF_ARC_MACH_MSK)
| cpu_types[i].eflags);
break;
}
}
if (!cpu_types[i].name)
as_fatal (_("unknown architecture: %s\n"), arg);
/* Check if set features are compatible with the chosen CPU. */
arc_check_feature ();
/* If we change the CPU, we need to re-init the bfd. */
if (mach_selection_mode != MACH_SELECTION_NONE
&& (old_cpu.mach != selected_cpu.mach))
{
bfd_find_target (arc_target_format, stdoutput);
if (! bfd_set_arch_mach (stdoutput, bfd_arch_arc, selected_cpu.mach))
as_warn (_("Could not set architecture and machine"));
}
mach_selection_mode = sel;
old_cpu = selected_cpu;
}
/* Here ends all the ARCompact extension instruction assembling
stuff. */
static void
arc_extra_reloc (int r_type)
{
char *sym_name, c;
symbolS *sym, *lab = NULL;
if (*input_line_pointer == '@')
input_line_pointer++;
c = get_symbol_name (&sym_name);
sym = symbol_find_or_make (sym_name);
restore_line_pointer (c);
if (c == ',' && r_type == BFD_RELOC_ARC_TLS_GD_LD)
{
++input_line_pointer;
char *lab_name;
c = get_symbol_name (&lab_name);
lab = symbol_find_or_make (lab_name);
restore_line_pointer (c);
}
/* These relocations exist as a mechanism for the compiler to tell the
linker how to patch the code if the tls model is optimised. However,
the relocation itself does not require any space within the assembler
fragment, and so we pass a size of 0.
The lines that generate these relocations look like this:
.tls_gd_ld @.tdata`bl __tls_get_addr@plt
The '.tls_gd_ld @.tdata' is processed first and generates the
additional relocation, while the 'bl __tls_get_addr@plt' is processed
second and generates the additional branch.
It is possible that the additional relocation generated by the
'.tls_gd_ld @.tdata' will be attached at the very end of one fragment,
while the 'bl __tls_get_addr@plt' will be generated as the first thing
in the next fragment. This will be fine; both relocations will still
appear to be at the same address in the generated object file.
However, this only works as the additional relocation is generated
with size of 0 bytes. */
fixS *fixP
= fix_new (frag_now, /* Which frag? */
frag_now_fix (), /* Where in that frag? */
0, /* size: 1, 2, or 4 usually. */
sym, /* X_add_symbol. */
0, /* X_add_number. */
false, /* TRUE if PC-relative relocation. */
r_type /* Relocation type. */);
fixP->fx_subsy = lab;
}
static symbolS *
arc_lcomm_internal (int ignore ATTRIBUTE_UNUSED,
symbolS *symbolP, addressT size)
{
addressT align = 0;
SKIP_WHITESPACE ();
if (*input_line_pointer == ',')
{
align = parse_align (1);
if (align == (addressT) -1)
return NULL;
}
else
{
if (size >= 8)
align = 3;
else if (size >= 4)
align = 2;
else if (size >= 2)
align = 1;
else
align = 0;
}
bss_alloc (symbolP, size, align);
S_CLEAR_EXTERNAL (symbolP);
return symbolP;
}
static void
arc_lcomm (int ignore)
{
symbolS *symbolP = s_comm_internal (ignore, arc_lcomm_internal);
if (symbolP)
symbol_get_bfdsym (symbolP)->flags |= BSF_OBJECT;
}
/* Select the cpu we're assembling for. */
static void
arc_option (int ignore ATTRIBUTE_UNUSED)
{
char c;
char *cpu;
const char *cpu_name;
c = get_symbol_name (&cpu);
cpu_name = cpu;
if ((!strcmp ("ARC600", cpu))
|| (!strcmp ("ARC601", cpu))
|| (!strcmp ("A6", cpu)))
cpu_name = "arc600";
else if ((!strcmp ("ARC700", cpu))
|| (!strcmp ("A7", cpu)))
cpu_name = "arc700";
else if (!strcmp ("EM", cpu))
cpu_name = "arcem";
else if (!strcmp ("HS", cpu))
cpu_name = "archs";
else if (!strcmp ("NPS400", cpu))
cpu_name = "nps400";
arc_select_cpu (cpu_name, MACH_SELECTION_FROM_CPU_DIRECTIVE);
restore_line_pointer (c);
demand_empty_rest_of_line ();
}
/* Smartly print an expression. */
static void
debug_exp (expressionS *t)
{
const char *name ATTRIBUTE_UNUSED;
const char *namemd ATTRIBUTE_UNUSED;
pr_debug ("debug_exp: ");
switch (t->X_op)
{
default: name = "unknown"; break;
case O_illegal: name = "O_illegal"; break;
case O_absent: name = "O_absent"; break;
case O_constant: name = "O_constant"; break;
case O_symbol: name = "O_symbol"; break;
case O_symbol_rva: name = "O_symbol_rva"; break;
case O_register: name = "O_register"; break;
case O_big: name = "O_big"; break;
case O_uminus: name = "O_uminus"; break;
case O_bit_not: name = "O_bit_not"; break;
case O_logical_not: name = "O_logical_not"; break;
case O_multiply: name = "O_multiply"; break;
case O_divide: name = "O_divide"; break;
case O_modulus: name = "O_modulus"; break;
case O_left_shift: name = "O_left_shift"; break;
case O_right_shift: name = "O_right_shift"; break;
case O_bit_inclusive_or: name = "O_bit_inclusive_or"; break;
case O_bit_or_not: name = "O_bit_or_not"; break;
case O_bit_exclusive_or: name = "O_bit_exclusive_or"; break;
case O_bit_and: name = "O_bit_and"; break;
case O_add: name = "O_add"; break;
case O_subtract: name = "O_subtract"; break;
case O_eq: name = "O_eq"; break;
case O_ne: name = "O_ne"; break;
case O_lt: name = "O_lt"; break;
case O_le: name = "O_le"; break;
case O_ge: name = "O_ge"; break;
case O_gt: name = "O_gt"; break;
case O_logical_and: name = "O_logical_and"; break;
case O_logical_or: name = "O_logical_or"; break;
case O_index: name = "O_index"; break;
case O_bracket: name = "O_bracket"; break;
case O_colon: name = "O_colon"; break;
case O_addrtype: name = "O_addrtype"; break;
}
switch (t->X_md)
{
default: namemd = "unknown"; break;
case O_gotoff: namemd = "O_gotoff"; break;
case O_gotpc: namemd = "O_gotpc"; break;
case O_plt: namemd = "O_plt"; break;
case O_sda: namemd = "O_sda"; break;
case O_pcl: namemd = "O_pcl"; break;
case O_tlsgd: namemd = "O_tlsgd"; break;
case O_tlsie: namemd = "O_tlsie"; break;
case O_tpoff9: namemd = "O_tpoff9"; break;
case O_tpoff: namemd = "O_tpoff"; break;
case O_dtpoff9: namemd = "O_dtpoff9"; break;
case O_dtpoff: namemd = "O_dtpoff"; break;
}
pr_debug ("%s (%s, %s, %d, %s)", name,
(t->X_add_symbol) ? S_GET_NAME (t->X_add_symbol) : "--",
(t->X_op_symbol) ? S_GET_NAME (t->X_op_symbol) : "--",
(int) t->X_add_number,
(t->X_md) ? namemd : "--");
pr_debug ("\n");
fflush (stderr);
}
/* Helper for parsing an argument, used for sorting out the relocation
type. */
static void
parse_reloc_symbol (expressionS *resultP)
{
char *reloc_name, c, *sym_name;
size_t len;
int i;
const struct arc_reloc_op_tag *r;
expressionS right;
symbolS *base;
/* A relocation operand has the following form
@identifier@relocation_type. The identifier is already in
tok! */
if (resultP->X_op != O_symbol)
{
as_bad (_("No valid label relocation operand"));
resultP->X_op = O_illegal;
return;
}
/* Parse @relocation_type. */
input_line_pointer++;
c = get_symbol_name (&reloc_name);
len = input_line_pointer - reloc_name;
if (len == 0)
{
as_bad (_("No relocation operand"));
resultP->X_op = O_illegal;
return;
}
/* Go through known relocation and try to find a match. */
r = &arc_reloc_op[0];
for (i = arc_num_reloc_op - 1; i >= 0; i--, r++)
if (len == r->length
&& memcmp (reloc_name, r->name, len) == 0)
break;
if (i < 0)
{
as_bad (_("Unknown relocation operand: @%s"), reloc_name);
resultP->X_op = O_illegal;
return;
}
*input_line_pointer = c;
SKIP_WHITESPACE_AFTER_NAME ();
/* Extra check for TLS: base. */
if (*input_line_pointer == '@')
{
if (resultP->X_op_symbol != NULL
|| resultP->X_op != O_symbol)
{
as_bad (_("Unable to parse TLS base: %s"),
input_line_pointer);
resultP->X_op = O_illegal;
return;
}
input_line_pointer++;
c = get_symbol_name (&sym_name);
base = symbol_find_or_make (sym_name);
resultP->X_op = O_subtract;
resultP->X_op_symbol = base;
restore_line_pointer (c);
right.X_add_number = 0;
}
if ((*input_line_pointer != '+')
&& (*input_line_pointer != '-'))
right.X_add_number = 0;
else
{
/* Parse the constant of a complex relocation expression
like @identifier@reloc +/- const. */
if (! r->complex_expr)
{
as_bad (_("@%s is not a complex relocation."), r->name);
resultP->X_op = O_illegal;
return;
}
expression (&right);
if (right.X_op != O_constant)
{
as_bad (_("Bad expression: @%s + %s."),
r->name, input_line_pointer);
resultP->X_op = O_illegal;
return;
}
}
resultP->X_md = r->op;
resultP->X_add_number = right.X_add_number;
}
/* Parse the arguments to an opcode. */
static int
tokenize_arguments (char *str,
expressionS *tok,
int ntok)
{
char *old_input_line_pointer;
bool saw_comma = false;
bool saw_arg = false;
int brk_lvl = 0;
int num_args = 0;
memset (tok, 0, sizeof (*tok) * ntok);
/* Save and restore input_line_pointer around this function. */
old_input_line_pointer = input_line_pointer;
input_line_pointer = str;
while (*input_line_pointer)
{
SKIP_WHITESPACE ();
switch (*input_line_pointer)
{
case '\0':
goto fini;
case ',':
input_line_pointer++;
if (saw_comma || !saw_arg)
goto err;
saw_comma = true;
break;
case '}':
case ']':
++input_line_pointer;
--brk_lvl;
if (!saw_arg || num_args == ntok)
goto err;
tok->X_op = O_bracket;
++tok;
++num_args;
break;
case '{':
case '[':
input_line_pointer++;
if (brk_lvl || num_args == ntok)
goto err;
++brk_lvl;
tok->X_op = O_bracket;
++tok;
++num_args;
break;
case ':':
input_line_pointer++;
if (!saw_arg || num_args == ntok)
goto err;
tok->X_op = O_colon;
saw_arg = false;
++tok;
++num_args;
break;
case '@':
/* We have labels, function names and relocations, all
starting with @ symbol. Sort them out. */
if ((saw_arg && !saw_comma) || num_args == ntok)
goto err;
/* Parse @label. */
input_line_pointer++;
tok->X_op = O_symbol;
tok->X_md = O_absent;
expression (tok);
if (*input_line_pointer == '@')
parse_reloc_symbol (tok);
debug_exp (tok);
if (tok->X_op == O_illegal
|| tok->X_op == O_absent
|| num_args == ntok)
goto err;
saw_comma = false;
saw_arg = true;
tok++;
num_args++;
break;
case '%':
/* Can be a register. */
++input_line_pointer;
/* Fall through. */
default:
if ((saw_arg && !saw_comma) || num_args == ntok)
goto err;
tok->X_op = O_absent;
tok->X_md = O_absent;
expression (tok);
/* Legacy: There are cases when we have
identifier@relocation_type, if it is the case parse the
relocation type as well. */
if (*input_line_pointer == '@')
parse_reloc_symbol (tok);
debug_exp (tok);
if (tok->X_op == O_illegal
|| tok->X_op == O_absent
|| num_args == ntok)
goto err;
saw_comma = false;
saw_arg = true;
tok++;
num_args++;
break;
}
}
fini:
if (saw_comma || brk_lvl)
goto err;
input_line_pointer = old_input_line_pointer;
return num_args;
err:
if (brk_lvl)
as_bad (_("Brackets in operand field incorrect"));
else if (saw_comma)
as_bad (_("extra comma"));
else if (!saw_arg)
as_bad (_("missing argument"));
else
as_bad (_("missing comma or colon"));
input_line_pointer = old_input_line_pointer;
return -1;
}
/* Parse the flags to a structure. */
static int
tokenize_flags (const char *str,
struct arc_flags flags[],
int nflg)
{
char *old_input_line_pointer;
bool saw_flg = false;
bool saw_dot = false;
int num_flags = 0;
size_t flgnamelen;
memset (flags, 0, sizeof (*flags) * nflg);
/* Save and restore input_line_pointer around this function. */
old_input_line_pointer = input_line_pointer;
input_line_pointer = (char *) str;
while (*input_line_pointer)
{
switch (*input_line_pointer)
{
case ' ':
case '\0':
goto fini;
case '.':
input_line_pointer++;
if (saw_dot)
goto err;
saw_dot = true;
saw_flg = false;
break;
default:
if (saw_flg && !saw_dot)
goto err;
if (num_flags >= nflg)
goto err;
flgnamelen = strspn (input_line_pointer,
"abcdefghijklmnopqrstuvwxyz0123456789");
if (flgnamelen > MAX_FLAG_NAME_LENGTH)
goto err;
memcpy (flags->name, input_line_pointer, flgnamelen);
input_line_pointer += flgnamelen;
flags++;
saw_dot = false;
saw_flg = true;
num_flags++;
break;
}
}
fini:
input_line_pointer = old_input_line_pointer;
return num_flags;
err:
if (saw_dot)
as_bad (_("extra dot"));
else if (!saw_flg)
as_bad (_("unrecognized flag"));
else
as_bad (_("failed to parse flags"));
input_line_pointer = old_input_line_pointer;
return -1;
}
/* Apply the fixups in order. */
static void
apply_fixups (struct arc_insn *insn, fragS *fragP, int fix)
{
int i;
for (i = 0; i < insn->nfixups; i++)
{
struct arc_fixup *fixup = &insn->fixups[i];
int size, pcrel, offset = 0;
/* FIXME! the reloc size is wrong in the BFD file.
When it is fixed please delete me. */
size = ((insn->len == 2) && !fixup->islong) ? 2 : 4;
if (fixup->islong)
offset = insn->len;
/* Some fixups are only used internally, thus no howto. */
if ((int) fixup->reloc == 0)
as_fatal (_("Unhandled reloc type"));
if ((int) fixup->reloc < 0)
{
/* FIXME! the reloc size is wrong in the BFD file.
When it is fixed please enable me.
size = ((insn->len == 2 && !fixup->islong) ? 2 : 4; */
pcrel = fixup->pcrel;
}
else
{
reloc_howto_type *reloc_howto =
bfd_reloc_type_lookup (stdoutput,
(bfd_reloc_code_real_type) fixup->reloc);
gas_assert (reloc_howto);
/* FIXME! the reloc size is wrong in the BFD file.
When it is fixed please enable me.
size = bfd_get_reloc_size (reloc_howto); */
pcrel = reloc_howto->pc_relative;
}
pr_debug ("%s:%d: apply_fixups: new %s fixup (PCrel:%s) of size %d @ \
offset %d + %d\n",
fragP->fr_file, fragP->fr_line,
(fixup->reloc < 0) ? "Internal" :
bfd_get_reloc_code_name (fixup->reloc),
pcrel ? "Y" : "N",
size, fix, offset);
fix_new_exp (fragP, fix + offset,
size, &fixup->exp, pcrel, fixup->reloc);
/* Check for ZOLs, and update symbol info if any. */
if (LP_INSN (insn->insn))
{
gas_assert (fixup->exp.X_add_symbol);
ARC_SET_FLAG (fixup->exp.X_add_symbol, ARC_FLAG_ZOL);
}
}
}
/* Actually output an instruction with its fixup. */
static void
emit_insn0 (struct arc_insn *insn, char *where, bool relax)
{
char *f = where;
size_t total_len;
pr_debug ("Emit insn : 0x%llx\n", insn->insn);
pr_debug ("\tLength : %d\n", insn->len);
pr_debug ("\tLong imm: 0x%lx\n", insn->limm);
/* Write out the instruction. */
total_len = insn->len + (insn->has_limm ? 4 : 0);
if (!relax)
f = frag_more (total_len);
md_number_to_chars_midend(f, insn->insn, insn->len);
if (insn->has_limm)
md_number_to_chars_midend (f + insn->len, insn->limm, 4);
dwarf2_emit_insn (total_len);
if (!relax)
apply_fixups (insn, frag_now, (f - frag_now->fr_literal));
}
static void
emit_insn1 (struct arc_insn *insn)
{
/* How frag_var's args are currently configured:
- rs_machine_dependent, to dictate it's a relaxation frag.
- FRAG_MAX_GROWTH, maximum size of instruction
- 0, variable size that might grow...unused by generic relaxation.
- frag_now->fr_subtype, fr_subtype starting value, set previously.
- s, opand expression.
- 0, offset but it's unused.
- 0, opcode but it's unused. */
symbolS *s = make_expr_symbol (&insn->fixups[0].exp);
frag_now->tc_frag_data.pcrel = insn->fixups[0].pcrel;
if (frag_room () < FRAG_MAX_GROWTH)
{
/* Handle differently when frag literal memory is exhausted.
This is used because when there's not enough memory left in
the current frag, a new frag is created and the information
we put into frag_now->tc_frag_data is disregarded. */
struct arc_relax_type relax_info_copy;
relax_substateT subtype = frag_now->fr_subtype;
memcpy (&relax_info_copy, &frag_now->tc_frag_data,
sizeof (struct arc_relax_type));
frag_wane (frag_now);
frag_grow (FRAG_MAX_GROWTH);
memcpy (&frag_now->tc_frag_data, &relax_info_copy,
sizeof (struct arc_relax_type));
frag_var (rs_machine_dependent, FRAG_MAX_GROWTH, 0,
subtype, s, 0, 0);
}
else
frag_var (rs_machine_dependent, FRAG_MAX_GROWTH, 0,
frag_now->fr_subtype, s, 0, 0);
}
static void
emit_insn (struct arc_insn *insn)
{
if (insn->relax)
emit_insn1 (insn);
else
emit_insn0 (insn, NULL, false);
}
/* Check whether a symbol involves a register. */
static bool
contains_register (symbolS *sym)
{
if (sym)
{
expressionS *ex = symbol_get_value_expression (sym);
return ((O_register == ex->X_op)
&& !contains_register (ex->X_add_symbol)
&& !contains_register (ex->X_op_symbol));
}
return false;
}
/* Returns the register number within a symbol. */
static int
get_register (symbolS *sym)
{
if (!contains_register (sym))
return -1;
expressionS *ex = symbol_get_value_expression (sym);
return regno (ex->X_add_number);
}
/* Return true if a RELOC is generic. A generic reloc is PC-rel of a
simple ME relocation (e.g. RELOC_ARC_32_ME, BFD_RELOC_ARC_PC32. */
static bool
generic_reloc_p (extended_bfd_reloc_code_real_type reloc)
{
if (!reloc)
return false;
switch (reloc)
{
case BFD_RELOC_ARC_SDA_LDST:
case BFD_RELOC_ARC_SDA_LDST1:
case BFD_RELOC_ARC_SDA_LDST2:
case BFD_RELOC_ARC_SDA16_LD:
case BFD_RELOC_ARC_SDA16_LD1:
case BFD_RELOC_ARC_SDA16_LD2:
case BFD_RELOC_ARC_SDA16_ST2:
case BFD_RELOC_ARC_SDA32_ME:
return false;
default:
return true;
}
}
/* Allocates a tok entry. */
static int
allocate_tok (expressionS *tok, int ntok, int cidx)
{
if (ntok > MAX_INSN_ARGS - 2)
return 0; /* No space left. */
if (cidx > ntok)
return 0; /* Incorrect args. */
memcpy (&tok[ntok+1], &tok[ntok], sizeof (*tok));
if (cidx == ntok)
return 1; /* Success. */
return allocate_tok (tok, ntok - 1, cidx);
}
/* Check if an particular ARC feature is enabled. */
static bool
check_cpu_feature (insn_subclass_t sc)
{
if (is_code_density_p (sc) && !(selected_cpu.features & CD))
return false;
if (is_spfp_p (sc) && !(selected_cpu.features & SPX))
return false;
if (is_dpfp_p (sc) && !(selected_cpu.features & DPX))
return false;
if (is_fpuda_p (sc) && !(selected_cpu.features & DPA))
return false;
if (is_nps400_p (sc) && !(selected_cpu.features & NPS400))
return false;
return true;
}
/* Parse the flags described by FIRST_PFLAG and NFLGS against the flag
operands in OPCODE. Stores the matching OPCODES into the FIRST_PFLAG
array and returns TRUE if the flag operands all match, otherwise,
returns FALSE, in which case the FIRST_PFLAG array may have been
modified. */
static bool
parse_opcode_flags (const struct arc_opcode *opcode,
int nflgs,
struct arc_flags *first_pflag)
{
int lnflg, i;
const unsigned char *flgidx;
lnflg = nflgs;
for (i = 0; i < nflgs; i++)
first_pflag[i].flgp = NULL;
/* Check the flags. Iterate over the valid flag classes. */
for (flgidx = opcode->flags; *flgidx; ++flgidx)
{
/* Get a valid flag class. */
const struct arc_flag_class *cl_flags = &arc_flag_classes[*flgidx];
const unsigned *flgopridx;
int cl_matches = 0;
struct arc_flags *pflag = NULL;
/* Check if opcode has implicit flag classes. */
if (cl_flags->flag_class & F_CLASS_IMPLICIT)
continue;
/* Check for extension conditional codes. */
if (ext_condcode.arc_ext_condcode
&& cl_flags->flag_class & F_CLASS_EXTEND)
{
struct arc_flag_operand *pf = ext_condcode.arc_ext_condcode;
while (pf->name)
{
pflag = first_pflag;
for (i = 0; i < nflgs; i++, pflag++)
{
if (!strcmp (pf->name, pflag->name))
{
if (pflag->flgp != NULL)
return false;
/* Found it. */
cl_matches++;
pflag->flgp = pf;
lnflg--;
break;
}
}
pf++;
}
}
for (flgopridx = cl_flags->flags; *flgopridx; ++flgopridx)
{
const struct arc_flag_operand *flg_operand;
pflag = first_pflag;
flg_operand = &arc_flag_operands[*flgopridx];
for (i = 0; i < nflgs; i++, pflag++)
{
/* Match against the parsed flags. */
if (!strcmp (flg_operand->name, pflag->name))
{
if (pflag->flgp != NULL)
return false;
cl_matches++;
pflag->flgp = flg_operand;
lnflg--;
break; /* goto next flag class and parsed flag. */
}
}
}
if ((cl_flags->flag_class & F_CLASS_REQUIRED) && cl_matches == 0)
return false;
if ((cl_flags->flag_class & F_CLASS_OPTIONAL) && cl_matches > 1)
return false;
}
/* Did I check all the parsed flags? */
return lnflg == 0;
}
/* Search forward through all variants of an opcode looking for a
syntax match. */
static const struct arc_opcode *
find_opcode_match (const struct arc_opcode_hash_entry *entry,
expressionS *tok,
int *pntok,
struct arc_flags *first_pflag,
int nflgs,
int *pcpumatch,
const char **errmsg)
{
const struct arc_opcode *opcode;
struct arc_opcode_hash_entry_iterator iter;
int ntok = *pntok;
int got_cpu_match = 0;
expressionS bktok[MAX_INSN_ARGS];
int bkntok, maxerridx = 0;
expressionS emptyE;
const char *tmpmsg = NULL;
arc_opcode_hash_entry_iterator_init (&iter);
memset (&emptyE, 0, sizeof (emptyE));
memcpy (bktok, tok, MAX_INSN_ARGS * sizeof (*tok));
bkntok = ntok;
for (opcode = arc_opcode_hash_entry_iterator_next (entry, &iter);
opcode != NULL;
opcode = arc_opcode_hash_entry_iterator_next (entry, &iter))
{
const unsigned char *opidx;
int tokidx = 0;
const expressionS *t = &emptyE;
pr_debug ("%s:%d: find_opcode_match: trying opcode 0x%08llX ",
frag_now->fr_file, frag_now->fr_line, opcode->opcode);
/* Don't match opcodes that don't exist on this
architecture. */
if (!(opcode->cpu & selected_cpu.flags))
goto match_failed;
if (!check_cpu_feature (opcode->subclass))
goto match_failed;
got_cpu_match = 1;
pr_debug ("cpu ");
/* Check the operands. */
for (opidx = opcode->operands; *opidx; ++opidx)
{
const struct arc_operand *operand = &arc_operands[*opidx];
/* Only take input from real operands. */
if (ARC_OPERAND_IS_FAKE (operand))
continue;
/* When we expect input, make sure we have it. */
if (tokidx >= ntok)
goto match_failed;
/* Match operand type with expression type. */
switch (operand->flags & ARC_OPERAND_TYPECHECK_MASK)
{
case ARC_OPERAND_ADDRTYPE:
{
tmpmsg = NULL;
/* Check to be an address type. */
if (tok[tokidx].X_op != O_addrtype)
goto match_failed;
/* All address type operands need to have an insert
method in order to check that we have the correct
address type. */
gas_assert (operand->insert != NULL);
(*operand->insert) (0, tok[tokidx].X_add_number,
&tmpmsg);
if (tmpmsg != NULL)
goto match_failed;
}
break;
case ARC_OPERAND_IR:
/* Check to be a register. */
if ((tok[tokidx].X_op != O_register
|| !is_ir_num (tok[tokidx].X_add_number))
&& !(operand->flags & ARC_OPERAND_IGNORE))
goto match_failed;
/* If expect duplicate, make sure it is duplicate. */
if (operand->flags & ARC_OPERAND_DUPLICATE)
{
/* Check for duplicate. */
if (t->X_op != O_register
|| !is_ir_num (t->X_add_number)
|| (regno (t->X_add_number) !=
regno (tok[tokidx].X_add_number)))
goto match_failed;
}
/* Special handling? */
if (operand->insert)
{
tmpmsg = NULL;
(*operand->insert)(0,
regno (tok[tokidx].X_add_number),
&tmpmsg);
if (tmpmsg)
{
if (operand->flags & ARC_OPERAND_IGNORE)
{
/* Missing argument, create one. */
if (!allocate_tok (tok, ntok - 1, tokidx))
goto match_failed;
tok[tokidx].X_op = O_absent;
++ntok;
}
else
goto match_failed;
}
}
t = &tok[tokidx];
break;
case ARC_OPERAND_BRAKET:
/* Check if bracket is also in opcode table as
operand. */
if (tok[tokidx].X_op != O_bracket)
goto match_failed;
break;
case ARC_OPERAND_COLON:
/* Check if colon is also in opcode table as operand. */
if (tok[tokidx].X_op != O_colon)
goto match_failed;
break;
case ARC_OPERAND_LIMM:
case ARC_OPERAND_SIGNED:
case ARC_OPERAND_UNSIGNED:
switch (tok[tokidx].X_op)
{
case O_illegal:
case O_absent:
case O_register:
goto match_failed;
case O_bracket:
/* Got an (too) early bracket, check if it is an
ignored operand. N.B. This procedure works only
when bracket is the last operand! */
if (!(operand->flags & ARC_OPERAND_IGNORE))
goto match_failed;
/* Insert the missing operand. */
if (!allocate_tok (tok, ntok - 1, tokidx))
goto match_failed;
tok[tokidx].X_op = O_absent;
++ntok;
break;
case O_symbol:
{
const char *p;
char *tmpp, *pp;
const struct arc_aux_reg *auxr;
if (opcode->insn_class != AUXREG)
goto de_fault;
p = S_GET_NAME (tok[tokidx].X_add_symbol);
/* For compatibility reasons, an aux register can
be spelled with upper or lower case
letters. */
tmpp = strdup (p);
for (pp = tmpp; *pp; ++pp) *pp = TOLOWER (*pp);
auxr = str_hash_find (arc_aux_hash, tmpp);
if (auxr)
{
/* We modify the token array here, safe in the
knowledge, that if this was the wrong
choice then the original contents will be
restored from BKTOK. */
tok[tokidx].X_op = O_constant;
tok[tokidx].X_add_number = auxr->address;
ARC_SET_FLAG (tok[tokidx].X_add_symbol, ARC_FLAG_AUX);
}
free (tmpp);
if (tok[tokidx].X_op != O_constant)
goto de_fault;
}
/* Fall through. */
case O_constant:
/* Check the range. */
if (operand->bits != 32
&& !(operand->flags & ARC_OPERAND_NCHK))
{
offsetT min, max, val;
val = tok[tokidx].X_add_number;
if (operand->flags & ARC_OPERAND_SIGNED)
{
max = (1 << (operand->bits - 1)) - 1;
min = -(1 << (operand->bits - 1));
}
else
{
max = (1 << operand->bits) - 1;
min = 0;
}
if (val < min || val > max)
{
tmpmsg = _("immediate is out of bounds");
goto match_failed;
}
/* Check alignments. */
if ((operand->flags & ARC_OPERAND_ALIGNED32)
&& (val & 0x03))
{
tmpmsg = _("immediate is not 32bit aligned");
goto match_failed;
}
if ((operand->flags & ARC_OPERAND_ALIGNED16)
&& (val & 0x01))
{
tmpmsg = _("immediate is not 16bit aligned");
goto match_failed;
}
}
else if (operand->flags & ARC_OPERAND_NCHK)
{
if (operand->insert)
{
tmpmsg = NULL;
(*operand->insert)(0,
tok[tokidx].X_add_number,
&tmpmsg);
if (tmpmsg)
goto match_failed;
}
else if (!(operand->flags & ARC_OPERAND_IGNORE))
goto match_failed;
}
break;
case O_subtract:
/* Check if it is register range. */
if ((tok[tokidx].X_add_number == 0)
&& contains_register (tok[tokidx].X_add_symbol)
&& contains_register (tok[tokidx].X_op_symbol))
{
int regs;
regs = get_register (tok[tokidx].X_add_symbol);
regs <<= 16;
regs |= get_register (tok[tokidx].X_op_symbol);
if (operand->insert)
{
tmpmsg = NULL;
(*operand->insert)(0,
regs,
&tmpmsg);
if (tmpmsg)
goto match_failed;
}
else
goto match_failed;
break;
}
/* Fall through. */
default:
de_fault:
if (operand->default_reloc == 0)
goto match_failed; /* The operand needs relocation. */
/* Relocs requiring long immediate. FIXME! make it
generic and move it to a function. */
switch (tok[tokidx].X_md)
{
case O_gotoff:
case O_gotpc:
case O_pcl:
case O_tpoff:
case O_dtpoff:
case O_tlsgd:
case O_tlsie:
if (!(operand->flags & ARC_OPERAND_LIMM))
goto match_failed;
/* Fall through. */
case O_absent:
if (!generic_reloc_p (operand->default_reloc))
goto match_failed;
break;
default:
break;
}
break;
}
/* If expect duplicate, make sure it is duplicate. */
if (operand->flags & ARC_OPERAND_DUPLICATE)
{
if (t->X_op == O_illegal
|| t->X_op == O_absent
|| t->X_op == O_register
|| (t->X_add_number != tok[tokidx].X_add_number))
{
tmpmsg = _("operand is not duplicate of the "
"previous one");
goto match_failed;
}
}
t = &tok[tokidx];
break;
default:
/* Everything else should have been fake. */
abort ();
}
++tokidx;
}
pr_debug ("opr ");
/* Setup ready for flag parsing. */
if (!parse_opcode_flags (opcode, nflgs, first_pflag))
{
tmpmsg = _("flag mismatch");
goto match_failed;
}
pr_debug ("flg");
/* Possible match -- did we use all of our input? */
if (tokidx == ntok)
{
*pntok = ntok;
pr_debug ("\n");
return opcode;
}
tmpmsg = _("too many arguments");
match_failed:;
pr_debug ("\n");
/* Restore the original parameters. */
memcpy (tok, bktok, MAX_INSN_ARGS * sizeof (*tok));
ntok = bkntok;
if (tokidx >= maxerridx
&& tmpmsg)
{
maxerridx = tokidx;
*errmsg = tmpmsg;
}
}
if (*pcpumatch)
*pcpumatch = got_cpu_match;
return NULL;
}
/* Swap operand tokens. */
static void
swap_operand (expressionS *operand_array,
unsigned source,
unsigned destination)
{
expressionS cpy_operand;
expressionS *src_operand;
expressionS *dst_operand;
size_t size;
if (source == destination)
return;
src_operand = &operand_array[source];
dst_operand = &operand_array[destination];
size = sizeof (expressionS);
/* Make copy of operand to swap with and swap. */
memcpy (&cpy_operand, dst_operand, size);
memcpy (dst_operand, src_operand, size);
memcpy (src_operand, &cpy_operand, size);
}
/* Check if *op matches *tok type.
Returns FALSE if they don't match, TRUE if they match. */
static bool
pseudo_operand_match (const expressionS *tok,
const struct arc_operand_operation *op)
{
offsetT min, max, val;
bool ret;
const struct arc_operand *operand_real = &arc_operands[op->operand_idx];
ret = false;
switch (tok->X_op)
{
case O_constant:
if (operand_real->bits == 32 && (operand_real->flags & ARC_OPERAND_LIMM))
ret = 1;
else if (!(operand_real->flags & ARC_OPERAND_IR))
{
val = tok->X_add_number + op->count;
if (operand_real->flags & ARC_OPERAND_SIGNED)
{
max = (1 << (operand_real->bits - 1)) - 1;
min = -(1 << (operand_real->bits - 1));
}
else
{
max = (1 << operand_real->bits) - 1;
min = 0;
}
if (min <= val && val <= max)
ret = true;
}
break;
case O_symbol:
/* Handle all symbols as long immediates or signed 9. */
if (operand_real->flags & ARC_OPERAND_LIMM
|| ((operand_real->flags & ARC_OPERAND_SIGNED)
&& operand_real->bits == 9))
ret = true;
break;
case O_register:
if (operand_real->flags & ARC_OPERAND_IR)
ret = true;
break;
case O_bracket:
if (operand_real->flags & ARC_OPERAND_BRAKET)
ret = true;
break;
default:
/* Unknown. */
break;
}
return ret;
}
/* Find pseudo instruction in array. */
static const struct arc_pseudo_insn *
find_pseudo_insn (const char *opname,
int ntok,
const expressionS *tok)
{
const struct arc_pseudo_insn *pseudo_insn = NULL;
const struct arc_operand_operation *op;
unsigned int i;
int j;
for (i = 0; i < arc_num_pseudo_insn; ++i)
{
pseudo_insn = &arc_pseudo_insns[i];
if (strcmp (pseudo_insn->mnemonic_p, opname) == 0)
{
op = pseudo_insn->operand;
for (j = 0; j < ntok; ++j)
if (!pseudo_operand_match (&tok[j], &op[j]))
break;
/* Found the right instruction. */
if (j == ntok)
return pseudo_insn;
}
}
return NULL;
}
/* Assumes the expressionS *tok is of sufficient size. */
static const struct arc_opcode_hash_entry *
find_special_case_pseudo (const char *opname,
int *ntok,
expressionS *tok,
int *nflgs,
struct arc_flags *pflags)
{
const struct arc_pseudo_insn *pseudo_insn = NULL;
const struct arc_operand_operation *operand_pseudo;
const struct arc_operand *operand_real;
unsigned i;
char construct_operand[MAX_CONSTR_STR];
/* Find whether opname is in pseudo instruction array. */
pseudo_insn = find_pseudo_insn (opname, *ntok, tok);
if (pseudo_insn == NULL)
return NULL;
/* Handle flag, Limited to one flag at the moment. */
if (pseudo_insn->flag_r != NULL)
*nflgs += tokenize_flags (pseudo_insn->flag_r, &pflags[*nflgs],
MAX_INSN_FLGS - *nflgs);
/* Handle operand operations. */
for (i = 0; i < pseudo_insn->operand_cnt; ++i)
{
operand_pseudo = &pseudo_insn->operand[i];
operand_real = &arc_operands[operand_pseudo->operand_idx];
if (operand_real->flags & ARC_OPERAND_BRAKET
&& !operand_pseudo->needs_insert)
continue;
/* Has to be inserted (i.e. this token does not exist yet). */
if (operand_pseudo->needs_insert)
{
if (operand_real->flags & ARC_OPERAND_BRAKET)
{
tok[i].X_op = O_bracket;
++(*ntok);
continue;
}
/* Check if operand is a register or constant and handle it
by type. */
if (operand_real->flags & ARC_OPERAND_IR)
snprintf (construct_operand, MAX_CONSTR_STR, "r%d",
operand_pseudo->count);
else
snprintf (construct_operand, MAX_CONSTR_STR, "%d",
operand_pseudo->count);
tokenize_arguments (construct_operand, &tok[i], 1);
++(*ntok);
}
else if (operand_pseudo->count)
{
/* Operand number has to be adjusted accordingly (by operand
type). */
switch (tok[i].X_op)
{
case O_constant:
tok[i].X_add_number += operand_pseudo->count;
break;
case O_symbol:
break;
default:
/* Ignored. */
break;
}
}
}
/* Swap operands if necessary. Only supports one swap at the
moment. */
for (i = 0; i < pseudo_insn->operand_cnt; ++i)
{
operand_pseudo = &pseudo_insn->operand[i];
if (operand_pseudo->swap_operand_idx == i)
continue;
swap_operand (tok, i, operand_pseudo->swap_operand_idx);
/* Prevent a swap back later by breaking out. */
break;
}
return arc_find_opcode (pseudo_insn->mnemonic_r);
}
static const struct arc_opcode_hash_entry *
find_special_case_flag (const char *opname,
int *nflgs,
struct arc_flags *pflags)
{
unsigned int i;
const char *flagnm;
unsigned flag_idx, flag_arr_idx;
size_t flaglen, oplen;
const struct arc_flag_special *arc_flag_special_opcode;
const struct arc_opcode_hash_entry *entry;
/* Search for special case instruction. */
for (i = 0; i < arc_num_flag_special; i++)
{
arc_flag_special_opcode = &arc_flag_special_cases[i];
oplen = strlen (arc_flag_special_opcode->name);
if (strncmp (opname, arc_flag_special_opcode->name, oplen) != 0)
continue;
/* Found a potential special case instruction, now test for
flags. */
for (flag_arr_idx = 0;; ++flag_arr_idx)
{
flag_idx = arc_flag_special_opcode->flags[flag_arr_idx];
if (flag_idx == 0)
break; /* End of array, nothing found. */
flagnm = arc_flag_operands[flag_idx].name;
flaglen = strlen (flagnm);
if (strcmp (opname + oplen, flagnm) == 0)
{
entry = arc_find_opcode (arc_flag_special_opcode->name);
if (*nflgs + 1 > MAX_INSN_FLGS)
break;
memcpy (pflags[*nflgs].name, flagnm, flaglen);
pflags[*nflgs].name[flaglen] = '\0';
(*nflgs)++;
return entry;
}
}
}
return NULL;
}
/* Used to find special case opcode. */
static const struct arc_opcode_hash_entry *
find_special_case (const char *opname,
int *nflgs,
struct arc_flags *pflags,
expressionS *tok,
int *ntok)
{
const struct arc_opcode_hash_entry *entry;
entry = find_special_case_pseudo (opname, ntok, tok, nflgs, pflags);
if (entry == NULL)
entry = find_special_case_flag (opname, nflgs, pflags);
return entry;
}
/* Autodetect cpu attribute list. */
static void
autodetect_attributes (const struct arc_opcode *opcode,
const expressionS *tok,
int ntok)
{
unsigned i;
struct mpy_type
{
unsigned feature;
unsigned encoding;
} mpy_list[] = {{ MPY1E, 1 }, { MPY6E, 6 }, { MPY7E, 7 }, { MPY8E, 8 },
{ MPY9E, 9 }};
for (i = 0; i < ARRAY_SIZE (feature_list); i++)
if (opcode->subclass == feature_list[i].feature)
selected_cpu.features |= feature_list[i].feature;
for (i = 0; i < ARRAY_SIZE (mpy_list); i++)
if (opcode->subclass == mpy_list[i].feature)
mpy_option = mpy_list[i].encoding;
for (i = 0; i < (unsigned) ntok; i++)
{
switch (tok[i].X_md)
{
case O_gotoff:
case O_gotpc:
case O_plt:
pic_option = 2;
break;
case O_sda:
sda_option = 2;
break;
case O_tlsgd:
case O_tlsie:
case O_tpoff9:
case O_tpoff:
case O_dtpoff9:
case O_dtpoff:
tls_option = 1;
break;
default:
break;
}
switch (tok[i].X_op)
{
case O_register:
if ((tok[i].X_add_number >= 4 && tok[i].X_add_number <= 9)
|| (tok[i].X_add_number >= 16 && tok[i].X_add_number <= 25))
rf16_only = false;
break;
default:
break;
}
}
}
/* Given an opcode name, pre-tockenized set of argumenst and the
opcode flags, take it all the way through emission. */
static void
assemble_tokens (const char *opname,
expressionS *tok,
int ntok,
struct arc_flags *pflags,
int nflgs)
{
bool found_something = false;
const struct arc_opcode_hash_entry *entry;
int cpumatch = 1;
const char *errmsg = NULL;
/* Search opcodes. */
entry = arc_find_opcode (opname);
/* Couldn't find opcode conventional way, try special cases. */
if (entry == NULL)
entry = find_special_case (opname, &nflgs, pflags, tok, &ntok);
if (entry != NULL)
{
const struct arc_opcode *opcode;
pr_debug ("%s:%d: assemble_tokens: %s\n",
frag_now->fr_file, frag_now->fr_line, opname);
found_something = true;
opcode = find_opcode_match (entry, tok, &ntok, pflags,
nflgs, &cpumatch, &errmsg);
if (opcode != NULL)
{
struct arc_insn insn;
autodetect_attributes (opcode, tok, ntok);
assemble_insn (opcode, tok, ntok, pflags, nflgs, &insn);
emit_insn (&insn);
return;
}
}
if (found_something)
{
if (cpumatch)
if (errmsg)
as_bad (_("%s for instruction '%s'"), errmsg, opname);
else
as_bad (_("inappropriate arguments for opcode '%s'"), opname);
else
as_bad (_("opcode '%s' not supported for target %s"), opname,
selected_cpu.name);
}
else
as_bad (_("unknown opcode '%s'"), opname);
}
/* The public interface to the instruction assembler. */
void
md_assemble (char *str)
{
char *opname;
expressionS tok[MAX_INSN_ARGS];
int ntok, nflg;
size_t opnamelen;
struct arc_flags flags[MAX_INSN_FLGS];
/* Split off the opcode. */
opnamelen = strspn (str, "abcdefghijklmnopqrstuvwxyz_0123468");
opname = xmemdup0 (str, opnamelen);
/* Signalize we are assembling the instructions. */
assembling_insn = true;
/* Tokenize the flags. */
if ((nflg = tokenize_flags (str + opnamelen, flags, MAX_INSN_FLGS)) == -1)
{
as_bad (_("syntax error"));
return;
}
/* Scan up to the end of the mnemonic which must end in space or end
of string. */
str += opnamelen;
for (; *str != '\0'; str++)
if (*str == ' ')
break;
/* Tokenize the rest of the line. */
if ((ntok = tokenize_arguments (str, tok, MAX_INSN_ARGS)) < 0)
{
as_bad (_("syntax error"));
return;
}
/* Finish it off. */
assemble_tokens (opname, tok, ntok, flags, nflg);
assembling_insn = false;
}
/* Callback to insert a register into the hash table. */
static void
declare_register (const char *name, int number)
{
symbolS *regS = symbol_create (name, reg_section,
&zero_address_frag, number);
if (str_hash_insert (arc_reg_hash, S_GET_NAME (regS), regS, 0) != NULL)
as_fatal (_("duplicate %s"), name);
}
/* Construct symbols for each of the general registers. */
static void
declare_register_set (void)
{
int i;
for (i = 0; i < 64; ++i)
{
char name[32];
sprintf (name, "r%d", i);
declare_register (name, i);
if ((i & 0x01) == 0)
{
sprintf (name, "r%dr%d", i, i+1);
declare_register (name, i);
}
}
}
/* Construct a symbol for an address type. */
static void
declare_addrtype (const char *name, int number)
{
symbolS *addrtypeS = symbol_create (name, undefined_section,
&zero_address_frag, number);
if (str_hash_insert (arc_addrtype_hash, S_GET_NAME (addrtypeS), addrtypeS, 0))
as_fatal (_("duplicate %s"), name);
}
/* Port-specific assembler initialization. This function is called
once, at assembler startup time. */
void
md_begin (void)
{
const struct arc_opcode *opcode = arc_opcodes;
if (mach_selection_mode == MACH_SELECTION_NONE)
arc_select_cpu (TARGET_WITH_CPU, MACH_SELECTION_FROM_DEFAULT);
/* The endianness can be chosen "at the factory". */
target_big_endian = byte_order == BIG_ENDIAN;
if (!bfd_set_arch_mach (stdoutput, bfd_arch_arc, selected_cpu.mach))
as_warn (_("could not set architecture and machine"));
/* Set elf header flags. */
bfd_set_private_flags (stdoutput, selected_cpu.eflags);
/* Set up a hash table for the instructions. */
arc_opcode_hash = str_htab_create ();
/* Initialize the hash table with the insns. */
do
{
const char *name = opcode->name;
arc_insert_opcode (opcode);
while (++opcode && opcode->name
&& (opcode->name == name
|| !strcmp (opcode->name, name)))
continue;
}while (opcode->name);
/* Register declaration. */
arc_reg_hash = str_htab_create ();
declare_register_set ();
declare_register ("gp", 26);
declare_register ("fp", 27);
declare_register ("sp", 28);
declare_register ("ilink", 29);
declare_register ("ilink1", 29);
declare_register ("ilink2", 30);
declare_register ("blink", 31);
/* XY memory registers. */
declare_register ("x0_u0", 32);
declare_register ("x0_u1", 33);
declare_register ("x1_u0", 34);
declare_register ("x1_u1", 35);
declare_register ("x2_u0", 36);
declare_register ("x2_u1", 37);
declare_register ("x3_u0", 38);
declare_register ("x3_u1", 39);
declare_register ("y0_u0", 40);
declare_register ("y0_u1", 41);
declare_register ("y1_u0", 42);
declare_register ("y1_u1", 43);
declare_register ("y2_u0", 44);
declare_register ("y2_u1", 45);
declare_register ("y3_u0", 46);
declare_register ("y3_u1", 47);
declare_register ("x0_nu", 48);
declare_register ("x1_nu", 49);
declare_register ("x2_nu", 50);
declare_register ("x3_nu", 51);
declare_register ("y0_nu", 52);
declare_register ("y1_nu", 53);
declare_register ("y2_nu", 54);
declare_register ("y3_nu", 55);
declare_register ("mlo", 57);
declare_register ("mmid", 58);
declare_register ("mhi", 59);
declare_register ("acc1", 56);
declare_register ("acc2", 57);
declare_register ("lp_count", 60);
declare_register ("pcl", 63);
/* Initialize the last instructions. */
memset (&arc_last_insns[0], 0, sizeof (arc_last_insns));
/* Aux register declaration. */
arc_aux_hash = str_htab_create ();
const struct arc_aux_reg *auxr = &arc_aux_regs[0];
unsigned int i;
for (i = 0; i < arc_num_aux_regs; i++, auxr++)
{
if (!(auxr->cpu & selected_cpu.flags))
continue;
if ((auxr->subclass != NONE)
&& !check_cpu_feature (auxr->subclass))
continue;
if (str_hash_insert (arc_aux_hash, auxr->name, auxr, 0) != 0)
as_fatal (_("duplicate %s"), auxr->name);
}
/* Address type declaration. */
arc_addrtype_hash = str_htab_create ();
declare_addrtype ("bd", ARC_NPS400_ADDRTYPE_BD);
declare_addrtype ("jid", ARC_NPS400_ADDRTYPE_JID);
declare_addrtype ("lbd", ARC_NPS400_ADDRTYPE_LBD);
declare_addrtype ("mbd", ARC_NPS400_ADDRTYPE_MBD);
declare_addrtype ("sd", ARC_NPS400_ADDRTYPE_SD);
declare_addrtype ("sm", ARC_NPS400_ADDRTYPE_SM);
declare_addrtype ("xa", ARC_NPS400_ADDRTYPE_XA);
declare_addrtype ("xd", ARC_NPS400_ADDRTYPE_XD);
declare_addrtype ("cd", ARC_NPS400_ADDRTYPE_CD);
declare_addrtype ("cbd", ARC_NPS400_ADDRTYPE_CBD);
declare_addrtype ("cjid", ARC_NPS400_ADDRTYPE_CJID);
declare_addrtype ("clbd", ARC_NPS400_ADDRTYPE_CLBD);
declare_addrtype ("cm", ARC_NPS400_ADDRTYPE_CM);
declare_addrtype ("csd", ARC_NPS400_ADDRTYPE_CSD);
declare_addrtype ("cxa", ARC_NPS400_ADDRTYPE_CXA);
declare_addrtype ("cxd", ARC_NPS400_ADDRTYPE_CXD);
}
/* Write a value out to the object file, using the appropriate
endianness. */
void
md_number_to_chars (char *buf,
valueT val,
int n)
{
if (target_big_endian)
number_to_chars_bigendian (buf, val, n);
else
number_to_chars_littleendian (buf, val, n);
}
/* Round up a section size to the appropriate boundary. */
valueT
md_section_align (segT segment,
valueT size)
{
int align = bfd_section_alignment (segment);
return ((size + (1 << align) - 1) & (-((valueT) 1 << align)));
}
/* 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)
{
offsetT base = fixP->fx_where + fixP->fx_frag->fr_address;
pr_debug ("pcrel_from_section, fx_offset = %d\n", (int) fixP->fx_offset);
if (fixP->fx_addsy != (symbolS *) NULL
&& (!S_IS_DEFINED (fixP->fx_addsy)
|| S_GET_SEGMENT (fixP->fx_addsy) != sec))
{
pr_debug ("Unknown pcrel symbol: %s\n", S_GET_NAME (fixP->fx_addsy));
/* The symbol is undefined (or is defined but not in this section).
Let the linker figure it out. */
return 0;
}
if ((int) fixP->fx_r_type < 0)
{
/* These are the "internal" relocations. Align them to
32 bit boundary (PCL), for the moment. */
base &= ~3;
}
else
{
switch (fixP->fx_r_type)
{
case BFD_RELOC_ARC_PC32:
/* The hardware calculates relative to the start of the
insn, but this relocation is relative to location of the
LIMM, compensate. The base always needs to be
subtracted by 4 as we do not support this type of PCrel
relocation for short instructions. */
base -= 4;
/* Fall through. */
case BFD_RELOC_ARC_PLT32:
case BFD_RELOC_ARC_S25H_PCREL_PLT:
case BFD_RELOC_ARC_S21H_PCREL_PLT:
case BFD_RELOC_ARC_S25W_PCREL_PLT:
case BFD_RELOC_ARC_S21W_PCREL_PLT:
case BFD_RELOC_ARC_S21H_PCREL:
case BFD_RELOC_ARC_S25H_PCREL:
case BFD_RELOC_ARC_S13_PCREL:
case BFD_RELOC_ARC_S21W_PCREL:
case BFD_RELOC_ARC_S25W_PCREL:
base &= ~3;
break;
default:
as_bad_where (fixP->fx_file, fixP->fx_line,
_("unhandled reloc %s in md_pcrel_from_section"),
bfd_get_reloc_code_name (fixP->fx_r_type));
break;
}
}
pr_debug ("pcrel from %"BFD_VMA_FMT"x + %lx = %"BFD_VMA_FMT"x, "
"symbol: %s (%"BFD_VMA_FMT"x)\n",
fixP->fx_frag->fr_address, fixP->fx_where, base,
fixP->fx_addsy ? S_GET_NAME (fixP->fx_addsy) : "(null)",
fixP->fx_addsy ? S_GET_VALUE (fixP->fx_addsy) : 0);
return base;
}
/* Given a BFD relocation find the corresponding operand. */
static const struct arc_operand *
find_operand_for_reloc (extended_bfd_reloc_code_real_type reloc)
{
unsigned i;
for (i = 0; i < arc_num_operands; i++)
if (arc_operands[i].default_reloc == reloc)
return &arc_operands[i];
return NULL;
}
/* Insert an operand value into an instruction. */
static unsigned long long
insert_operand (unsigned long long insn,
const struct arc_operand *operand,
long long val,
const char *file,
unsigned line)
{
offsetT min = 0, max = 0;
if (operand->bits != 32
&& !(operand->flags & ARC_OPERAND_NCHK)
&& !(operand->flags & ARC_OPERAND_FAKE))
{
if (operand->flags & ARC_OPERAND_SIGNED)
{
max = (1 << (operand->bits - 1)) - 1;
min = -(1 << (operand->bits - 1));
}
else
{
max = (1 << operand->bits) - 1;
min = 0;
}
if (val < min || val > max)
as_bad_value_out_of_range (_("operand"),
val, min, max, file, line);
}
pr_debug ("insert field: %ld <= %lld <= %ld in 0x%08llx\n",
min, val, max, insn);
if ((operand->flags & ARC_OPERAND_ALIGNED32)
&& (val & 0x03))
as_bad_where (file, line,
_("Unaligned operand. Needs to be 32bit aligned"));
if ((operand->flags & ARC_OPERAND_ALIGNED16)
&& (val & 0x01))
as_bad_where (file, line,
_("Unaligned operand. Needs to be 16bit aligned"));
if (operand->insert)
{
const char *errmsg = NULL;
insn = (*operand->insert) (insn, val, &errmsg);
if (errmsg)
as_warn_where (file, line, "%s", errmsg);
}
else
{
if (operand->flags & ARC_OPERAND_TRUNCATE)
{
if (operand->flags & ARC_OPERAND_ALIGNED32)
val >>= 2;
if (operand->flags & ARC_OPERAND_ALIGNED16)
val >>= 1;
}
insn |= ((val & ((1 << operand->bits) - 1)) << operand->shift);
}
return insn;
}
/* Apply a fixup to the object code. At this point all symbol values
should be fully resolved, and we attempt to completely resolve the
reloc. If we can not do that, we determine the correct reloc code
and put it back in the fixup. To indicate that a fixup has been
eliminated, set fixP->fx_done. */
void
md_apply_fix (fixS *fixP,
valueT *valP,
segT seg)
{
char * const fixpos = fixP->fx_frag->fr_literal + fixP->fx_where;
valueT value = *valP;
unsigned insn = 0;
symbolS *fx_addsy, *fx_subsy;
offsetT fx_offset;
segT add_symbol_segment = absolute_section;
segT sub_symbol_segment = absolute_section;
const struct arc_operand *operand = NULL;
extended_bfd_reloc_code_real_type reloc;
pr_debug ("%s:%u: apply_fix: r_type=%d (%s) value=0x%lX offset=0x%lX\n",
fixP->fx_file, fixP->fx_line, fixP->fx_r_type,
((int) fixP->fx_r_type < 0) ? "Internal":
bfd_get_reloc_code_name (fixP->fx_r_type), value,
fixP->fx_offset);
fx_addsy = fixP->fx_addsy;
fx_subsy = fixP->fx_subsy;
fx_offset = 0;
if (fx_addsy)
{
add_symbol_segment = S_GET_SEGMENT (fx_addsy);
}
if (fx_subsy
&& fixP->fx_r_type != BFD_RELOC_ARC_TLS_DTPOFF
&& fixP->fx_r_type != BFD_RELOC_ARC_TLS_DTPOFF_S9
&& fixP->fx_r_type != BFD_RELOC_ARC_TLS_GD_LD)
{
resolve_symbol_value (fx_subsy);
sub_symbol_segment = S_GET_SEGMENT (fx_subsy);
if (sub_symbol_segment == absolute_section)
{
/* The symbol is really a constant. */
fx_offset -= S_GET_VALUE (fx_subsy);
fx_subsy = NULL;
}
else
{
as_bad_subtract (fixP);
return;
}
}
if (fx_addsy
&& !S_IS_WEAK (fx_addsy))
{
if (add_symbol_segment == seg
&& fixP->fx_pcrel)
{
value += S_GET_VALUE (fx_addsy);
value -= md_pcrel_from_section (fixP, seg);
fx_addsy = NULL;
fixP->fx_pcrel = false;
}
else if (add_symbol_segment == absolute_section)
{
value = fixP->fx_offset;
fx_offset += S_GET_VALUE (fixP->fx_addsy);
fx_addsy = NULL;
fixP->fx_pcrel = false;
}
}
if (!fx_addsy)
fixP->fx_done = true;
if (fixP->fx_pcrel)
{
if (fx_addsy
&& ((S_IS_DEFINED (fx_addsy)
&& S_GET_SEGMENT (fx_addsy) != seg)
|| S_IS_WEAK (fx_addsy)))
value += md_pcrel_from_section (fixP, seg);
switch (fixP->fx_r_type)
{
case BFD_RELOC_ARC_32_ME:
/* This is a pc-relative value in a LIMM. Adjust it to the
address of the instruction not to the address of the
LIMM. Note: it is not any longer valid this affirmation as
the linker consider ARC_PC32 a fixup to entire 64 bit
insn. */
fixP->fx_offset += fixP->fx_frag->fr_address;
/* Fall through. */
case BFD_RELOC_32:
fixP->fx_r_type = BFD_RELOC_ARC_PC32;
/* Fall through. */
case BFD_RELOC_ARC_PC32:
/* fixP->fx_offset += fixP->fx_where - fixP->fx_dot_value; */
break;
default:
if ((int) fixP->fx_r_type < 0)
as_bad_where (fixP->fx_file, fixP->fx_line,
_("PC relative relocation not allowed for (internal)"
" type %d"),
fixP->fx_r_type);
break;
}
}
pr_debug ("%s:%u: apply_fix: r_type=%d (%s) value=0x%lX offset=0x%lX\n",
fixP->fx_file, fixP->fx_line, fixP->fx_r_type,
((int) fixP->fx_r_type < 0) ? "Internal":
bfd_get_reloc_code_name (fixP->fx_r_type), value,
fixP->fx_offset);
/* Now check for TLS relocations. */
reloc = fixP->fx_r_type;
switch (reloc)
{
case BFD_RELOC_ARC_TLS_DTPOFF:
case BFD_RELOC_ARC_TLS_LE_32:
if (fixP->fx_done)
break;
/* Fall through. */
case BFD_RELOC_ARC_TLS_GD_GOT:
case BFD_RELOC_ARC_TLS_IE_GOT:
S_SET_THREAD_LOCAL (fixP->fx_addsy);
break;
case BFD_RELOC_ARC_TLS_GD_LD:
gas_assert (!fixP->fx_offset);
if (fixP->fx_subsy)
fixP->fx_offset
= (S_GET_VALUE (fixP->fx_subsy)
- fixP->fx_frag->fr_address- fixP->fx_where);
fixP->fx_subsy = NULL;
/* Fall through. */
case BFD_RELOC_ARC_TLS_GD_CALL:
/* These two relocs are there just to allow ld to change the tls
model for this symbol, by patching the code. The offset -
and scale, if any - will be installed by the linker. */
S_SET_THREAD_LOCAL (fixP->fx_addsy);
break;
case BFD_RELOC_ARC_TLS_LE_S9:
case BFD_RELOC_ARC_TLS_DTPOFF_S9:
as_bad (_("TLS_*_S9 relocs are not supported yet"));
break;
default:
break;
}
if (!fixP->fx_done)
{
return;
}
/* Adjust the value if we have a constant. */
value += fx_offset;
/* For hosts with longs bigger than 32-bits make sure that the top
bits of a 32-bit negative value read in by the parser are set,
so that the correct comparisons are made. */
if (value & 0x80000000)
value |= (-1UL << 31);
reloc = fixP->fx_r_type;
switch (reloc)
{
case BFD_RELOC_8:
case BFD_RELOC_16:
case BFD_RELOC_24:
case BFD_RELOC_32:
case BFD_RELOC_64:
case BFD_RELOC_ARC_32_PCREL:
md_number_to_chars (fixpos, value, fixP->fx_size);
return;
case BFD_RELOC_ARC_GOTPC32:
/* I cannot fix an GOTPC relocation because I need to relax it
from ld rx,[pcl,@sym@gotpc] to add rx,pcl,@sym@gotpc. */
as_bad (_("Unsupported operation on reloc"));
return;
case BFD_RELOC_ARC_TLS_DTPOFF:
case BFD_RELOC_ARC_TLS_LE_32:
gas_assert (!fixP->fx_addsy);
gas_assert (!fixP->fx_subsy);
/* Fall through. */
case BFD_RELOC_ARC_GOTOFF:
case BFD_RELOC_ARC_32_ME:
case BFD_RELOC_ARC_PC32:
md_number_to_chars_midend (fixpos, value, fixP->fx_size);
return;
case BFD_RELOC_ARC_PLT32:
md_number_to_chars_midend (fixpos, value, fixP->fx_size);
return;
case BFD_RELOC_ARC_S25H_PCREL_PLT:
reloc = BFD_RELOC_ARC_S25W_PCREL;
goto solve_plt;
case BFD_RELOC_ARC_S21H_PCREL_PLT:
reloc = BFD_RELOC_ARC_S21H_PCREL;
goto solve_plt;
case BFD_RELOC_ARC_S25W_PCREL_PLT:
reloc = BFD_RELOC_ARC_S25W_PCREL;
goto solve_plt;
case BFD_RELOC_ARC_S21W_PCREL_PLT:
reloc = BFD_RELOC_ARC_S21W_PCREL;
/* Fall through. */
case BFD_RELOC_ARC_S25W_PCREL:
case BFD_RELOC_ARC_S21W_PCREL:
case BFD_RELOC_ARC_S21H_PCREL:
case BFD_RELOC_ARC_S25H_PCREL:
case BFD_RELOC_ARC_S13_PCREL: