| /* 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: |
|