| /* tc-aarch64.c -- Assemble for the AArch64 ISA |
| |
| Copyright (C) 2009-2016 Free Software Foundation, Inc. |
| Contributed by ARM Ltd. |
| |
| This file is part of GAS. |
| |
| 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 of the license, 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 this program; see the file COPYING3. If not, |
| see <http://www.gnu.org/licenses/>. */ |
| |
| #include "as.h" |
| #include <limits.h> |
| #include <stdarg.h> |
| #include "bfd_stdint.h" |
| #define NO_RELOC 0 |
| #include "safe-ctype.h" |
| #include "subsegs.h" |
| #include "obstack.h" |
| |
| #ifdef OBJ_ELF |
| #include "elf/aarch64.h" |
| #include "dw2gencfi.h" |
| #endif |
| |
| #include "dwarf2dbg.h" |
| |
| /* Types of processor to assemble for. */ |
| #ifndef CPU_DEFAULT |
| #define CPU_DEFAULT AARCH64_ARCH_V8 |
| #endif |
| |
| #define streq(a, b) (strcmp (a, b) == 0) |
| |
| #define END_OF_INSN '\0' |
| |
| static aarch64_feature_set cpu_variant; |
| |
| /* Variables that we set while parsing command-line options. Once all |
| options have been read we re-process these values to set the real |
| assembly flags. */ |
| static const aarch64_feature_set *mcpu_cpu_opt = NULL; |
| static const aarch64_feature_set *march_cpu_opt = NULL; |
| |
| /* Constants for known architecture features. */ |
| static const aarch64_feature_set cpu_default = CPU_DEFAULT; |
| |
| #ifdef OBJ_ELF |
| /* Pre-defined "_GLOBAL_OFFSET_TABLE_" */ |
| static symbolS *GOT_symbol; |
| |
| /* Which ABI to use. */ |
| enum aarch64_abi_type |
| { |
| AARCH64_ABI_LP64 = 0, |
| AARCH64_ABI_ILP32 = 1 |
| }; |
| |
| /* AArch64 ABI for the output file. */ |
| static enum aarch64_abi_type aarch64_abi = AARCH64_ABI_LP64; |
| |
| /* When non-zero, program to a 32-bit model, in which the C data types |
| int, long and all pointer types are 32-bit objects (ILP32); or to a |
| 64-bit model, in which the C int type is 32-bits but the C long type |
| and all pointer types are 64-bit objects (LP64). */ |
| #define ilp32_p (aarch64_abi == AARCH64_ABI_ILP32) |
| #endif |
| |
| enum neon_el_type |
| { |
| NT_invtype = -1, |
| NT_b, |
| NT_h, |
| NT_s, |
| NT_d, |
| NT_q |
| }; |
| |
| /* Bits for DEFINED field in neon_type_el. */ |
| #define NTA_HASTYPE 1 |
| #define NTA_HASINDEX 2 |
| |
| struct neon_type_el |
| { |
| enum neon_el_type type; |
| unsigned char defined; |
| unsigned width; |
| int64_t index; |
| }; |
| |
| #define FIXUP_F_HAS_EXPLICIT_SHIFT 0x00000001 |
| |
| struct reloc |
| { |
| bfd_reloc_code_real_type type; |
| expressionS exp; |
| int pc_rel; |
| enum aarch64_opnd opnd; |
| uint32_t flags; |
| unsigned need_libopcodes_p : 1; |
| }; |
| |
| struct aarch64_instruction |
| { |
| /* libopcodes structure for instruction intermediate representation. */ |
| aarch64_inst base; |
| /* Record assembly errors found during the parsing. */ |
| struct |
| { |
| enum aarch64_operand_error_kind kind; |
| const char *error; |
| } parsing_error; |
| /* The condition that appears in the assembly line. */ |
| int cond; |
| /* Relocation information (including the GAS internal fixup). */ |
| struct reloc reloc; |
| /* Need to generate an immediate in the literal pool. */ |
| unsigned gen_lit_pool : 1; |
| }; |
| |
| typedef struct aarch64_instruction aarch64_instruction; |
| |
| static aarch64_instruction inst; |
| |
| static bfd_boolean parse_operands (char *, const aarch64_opcode *); |
| static bfd_boolean programmer_friendly_fixup (aarch64_instruction *); |
| |
| /* Diagnostics inline function utilites. |
| |
| These are lightweight utlities which should only be called by parse_operands |
| and other parsers. GAS processes each assembly line by parsing it against |
| instruction template(s), in the case of multiple templates (for the same |
| mnemonic name), those templates are tried one by one until one succeeds or |
| all fail. An assembly line may fail a few templates before being |
| successfully parsed; an error saved here in most cases is not a user error |
| but an error indicating the current template is not the right template. |
| Therefore it is very important that errors can be saved at a low cost during |
| the parsing; we don't want to slow down the whole parsing by recording |
| non-user errors in detail. |
| |
| Remember that the objective is to help GAS pick up the most approapriate |
| error message in the case of multiple templates, e.g. FMOV which has 8 |
| templates. */ |
| |
| static inline void |
| clear_error (void) |
| { |
| inst.parsing_error.kind = AARCH64_OPDE_NIL; |
| inst.parsing_error.error = NULL; |
| } |
| |
| static inline bfd_boolean |
| error_p (void) |
| { |
| return inst.parsing_error.kind != AARCH64_OPDE_NIL; |
| } |
| |
| static inline const char * |
| get_error_message (void) |
| { |
| return inst.parsing_error.error; |
| } |
| |
| static inline enum aarch64_operand_error_kind |
| get_error_kind (void) |
| { |
| return inst.parsing_error.kind; |
| } |
| |
| static inline void |
| set_error (enum aarch64_operand_error_kind kind, const char *error) |
| { |
| inst.parsing_error.kind = kind; |
| inst.parsing_error.error = error; |
| } |
| |
| static inline void |
| set_recoverable_error (const char *error) |
| { |
| set_error (AARCH64_OPDE_RECOVERABLE, error); |
| } |
| |
| /* Use the DESC field of the corresponding aarch64_operand entry to compose |
| the error message. */ |
| static inline void |
| set_default_error (void) |
| { |
| set_error (AARCH64_OPDE_SYNTAX_ERROR, NULL); |
| } |
| |
| static inline void |
| set_syntax_error (const char *error) |
| { |
| set_error (AARCH64_OPDE_SYNTAX_ERROR, error); |
| } |
| |
| static inline void |
| set_first_syntax_error (const char *error) |
| { |
| if (! error_p ()) |
| set_error (AARCH64_OPDE_SYNTAX_ERROR, error); |
| } |
| |
| static inline void |
| set_fatal_syntax_error (const char *error) |
| { |
| set_error (AARCH64_OPDE_FATAL_SYNTAX_ERROR, error); |
| } |
| |
| /* Number of littlenums required to hold an extended precision number. */ |
| #define MAX_LITTLENUMS 6 |
| |
| /* Return value for certain parsers when the parsing fails; those parsers |
| return the information of the parsed result, e.g. register number, on |
| success. */ |
| #define PARSE_FAIL -1 |
| |
| /* This is an invalid condition code that means no conditional field is |
| present. */ |
| #define COND_ALWAYS 0x10 |
| |
| typedef struct |
| { |
| const char *template; |
| unsigned long value; |
| } asm_barrier_opt; |
| |
| typedef struct |
| { |
| const char *template; |
| uint32_t value; |
| } asm_nzcv; |
| |
| struct reloc_entry |
| { |
| char *name; |
| bfd_reloc_code_real_type reloc; |
| }; |
| |
| /* Macros to define the register types and masks for the purpose |
| of parsing. */ |
| |
| #undef AARCH64_REG_TYPES |
| #define AARCH64_REG_TYPES \ |
| BASIC_REG_TYPE(R_32) /* w[0-30] */ \ |
| BASIC_REG_TYPE(R_64) /* x[0-30] */ \ |
| BASIC_REG_TYPE(SP_32) /* wsp */ \ |
| BASIC_REG_TYPE(SP_64) /* sp */ \ |
| BASIC_REG_TYPE(Z_32) /* wzr */ \ |
| BASIC_REG_TYPE(Z_64) /* xzr */ \ |
| BASIC_REG_TYPE(FP_B) /* b[0-31] *//* NOTE: keep FP_[BHSDQ] consecutive! */\ |
| BASIC_REG_TYPE(FP_H) /* h[0-31] */ \ |
| BASIC_REG_TYPE(FP_S) /* s[0-31] */ \ |
| BASIC_REG_TYPE(FP_D) /* d[0-31] */ \ |
| BASIC_REG_TYPE(FP_Q) /* q[0-31] */ \ |
| BASIC_REG_TYPE(CN) /* c[0-7] */ \ |
| BASIC_REG_TYPE(VN) /* v[0-31] */ \ |
| /* Typecheck: any 64-bit int reg (inc SP exc XZR) */ \ |
| MULTI_REG_TYPE(R64_SP, REG_TYPE(R_64) | REG_TYPE(SP_64)) \ |
| /* Typecheck: any int (inc {W}SP inc [WX]ZR) */ \ |
| MULTI_REG_TYPE(R_Z_SP, REG_TYPE(R_32) | REG_TYPE(R_64) \ |
| | REG_TYPE(SP_32) | REG_TYPE(SP_64) \ |
| | REG_TYPE(Z_32) | REG_TYPE(Z_64)) \ |
| /* Typecheck: any [BHSDQ]P FP. */ \ |
| MULTI_REG_TYPE(BHSDQ, REG_TYPE(FP_B) | REG_TYPE(FP_H) \ |
| | REG_TYPE(FP_S) | REG_TYPE(FP_D) | REG_TYPE(FP_Q)) \ |
| /* Typecheck: any int or [BHSDQ]P FP or V reg (exc SP inc [WX]ZR) */ \ |
| MULTI_REG_TYPE(R_Z_BHSDQ_V, REG_TYPE(R_32) | REG_TYPE(R_64) \ |
| | REG_TYPE(Z_32) | REG_TYPE(Z_64) | REG_TYPE(VN) \ |
| | REG_TYPE(FP_B) | REG_TYPE(FP_H) \ |
| | REG_TYPE(FP_S) | REG_TYPE(FP_D) | REG_TYPE(FP_Q)) \ |
| /* Any integer register; used for error messages only. */ \ |
| MULTI_REG_TYPE(R_N, REG_TYPE(R_32) | REG_TYPE(R_64) \ |
| | REG_TYPE(SP_32) | REG_TYPE(SP_64) \ |
| | REG_TYPE(Z_32) | REG_TYPE(Z_64)) \ |
| /* Pseudo type to mark the end of the enumerator sequence. */ \ |
| BASIC_REG_TYPE(MAX) |
| |
| #undef BASIC_REG_TYPE |
| #define BASIC_REG_TYPE(T) REG_TYPE_##T, |
| #undef MULTI_REG_TYPE |
| #define MULTI_REG_TYPE(T,V) BASIC_REG_TYPE(T) |
| |
| /* Register type enumerators. */ |
| typedef enum aarch64_reg_type_ |
| { |
| /* A list of REG_TYPE_*. */ |
| AARCH64_REG_TYPES |
| } aarch64_reg_type; |
| |
| #undef BASIC_REG_TYPE |
| #define BASIC_REG_TYPE(T) 1 << REG_TYPE_##T, |
| #undef REG_TYPE |
| #define REG_TYPE(T) (1 << REG_TYPE_##T) |
| #undef MULTI_REG_TYPE |
| #define MULTI_REG_TYPE(T,V) V, |
| |
| /* Structure for a hash table entry for a register. */ |
| typedef struct |
| { |
| const char *name; |
| unsigned char number; |
| ENUM_BITFIELD (aarch64_reg_type_) type : 8; |
| unsigned char builtin; |
| } reg_entry; |
| |
| /* Values indexed by aarch64_reg_type to assist the type checking. */ |
| static const unsigned reg_type_masks[] = |
| { |
| AARCH64_REG_TYPES |
| }; |
| |
| #undef BASIC_REG_TYPE |
| #undef REG_TYPE |
| #undef MULTI_REG_TYPE |
| #undef AARCH64_REG_TYPES |
| |
| /* Diagnostics used when we don't get a register of the expected type. |
| Note: this has to synchronized with aarch64_reg_type definitions |
| above. */ |
| static const char * |
| get_reg_expected_msg (aarch64_reg_type reg_type) |
| { |
| const char *msg; |
| |
| switch (reg_type) |
| { |
| case REG_TYPE_R_32: |
| msg = N_("integer 32-bit register expected"); |
| break; |
| case REG_TYPE_R_64: |
| msg = N_("integer 64-bit register expected"); |
| break; |
| case REG_TYPE_R_N: |
| msg = N_("integer register expected"); |
| break; |
| case REG_TYPE_R_Z_SP: |
| msg = N_("integer, zero or SP register expected"); |
| break; |
| case REG_TYPE_FP_B: |
| msg = N_("8-bit SIMD scalar register expected"); |
| break; |
| case REG_TYPE_FP_H: |
| msg = N_("16-bit SIMD scalar or floating-point half precision " |
| "register expected"); |
| break; |
| case REG_TYPE_FP_S: |
| msg = N_("32-bit SIMD scalar or floating-point single precision " |
| "register expected"); |
| break; |
| case REG_TYPE_FP_D: |
| msg = N_("64-bit SIMD scalar or floating-point double precision " |
| "register expected"); |
| break; |
| case REG_TYPE_FP_Q: |
| msg = N_("128-bit SIMD scalar or floating-point quad precision " |
| "register expected"); |
| break; |
| case REG_TYPE_CN: |
| msg = N_("C0 - C15 expected"); |
| break; |
| case REG_TYPE_R_Z_BHSDQ_V: |
| msg = N_("register expected"); |
| break; |
| case REG_TYPE_BHSDQ: /* any [BHSDQ]P FP */ |
| msg = N_("SIMD scalar or floating-point register expected"); |
| break; |
| case REG_TYPE_VN: /* any V reg */ |
| msg = N_("vector register expected"); |
| break; |
| default: |
| as_fatal (_("invalid register type %d"), reg_type); |
| } |
| return msg; |
| } |
| |
| /* Some well known registers that we refer to directly elsewhere. */ |
| #define REG_SP 31 |
| |
| /* Instructions take 4 bytes in the object file. */ |
| #define INSN_SIZE 4 |
| |
| /* Define some common error messages. */ |
| #define BAD_SP _("SP not allowed here") |
| |
| static struct hash_control *aarch64_ops_hsh; |
| static struct hash_control *aarch64_cond_hsh; |
| static struct hash_control *aarch64_shift_hsh; |
| static struct hash_control *aarch64_sys_regs_hsh; |
| static struct hash_control *aarch64_pstatefield_hsh; |
| static struct hash_control *aarch64_sys_regs_ic_hsh; |
| static struct hash_control *aarch64_sys_regs_dc_hsh; |
| static struct hash_control *aarch64_sys_regs_at_hsh; |
| static struct hash_control *aarch64_sys_regs_tlbi_hsh; |
| static struct hash_control *aarch64_reg_hsh; |
| static struct hash_control *aarch64_barrier_opt_hsh; |
| static struct hash_control *aarch64_nzcv_hsh; |
| static struct hash_control *aarch64_pldop_hsh; |
| static struct hash_control *aarch64_hint_opt_hsh; |
| |
| /* Stuff needed to resolve the label ambiguity |
| As: |
| ... |
| label: <insn> |
| may differ from: |
| ... |
| label: |
| <insn> */ |
| |
| static symbolS *last_label_seen; |
| |
| /* Literal pool structure. Held on a per-section |
| and per-sub-section basis. */ |
| |
| #define MAX_LITERAL_POOL_SIZE 1024 |
| typedef struct literal_expression |
| { |
| expressionS exp; |
| /* If exp.op == O_big then this bignum holds a copy of the global bignum value. */ |
| LITTLENUM_TYPE * bignum; |
| } literal_expression; |
| |
| typedef struct literal_pool |
| { |
| literal_expression literals[MAX_LITERAL_POOL_SIZE]; |
| unsigned int next_free_entry; |
| unsigned int id; |
| symbolS *symbol; |
| segT section; |
| subsegT sub_section; |
| int size; |
| struct literal_pool *next; |
| } literal_pool; |
| |
| /* Pointer to a linked list of literal pools. */ |
| static literal_pool *list_of_pools = NULL; |
| |
| /* Pure syntax. */ |
| |
| /* This array holds the chars that always start a comment. If the |
| pre-processor is disabled, these aren't very useful. */ |
| const char comment_chars[] = ""; |
| |
| /* This array holds the chars that only start a comment at the beginning of |
| a line. If the line seems to have the form '# 123 filename' |
| .line and .file directives will appear in the pre-processed output. */ |
| /* Note that input_file.c hand checks for '#' at the beginning of the |
| first line of the input file. This is because the compiler outputs |
| #NO_APP at the beginning of its output. */ |
| /* Also note that comments like this one will always work. */ |
| const char line_comment_chars[] = "#"; |
| |
| const char line_separator_chars[] = ";"; |
| |
| /* Chars that can be used to separate mant |
| from exp in floating point numbers. */ |
| 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[] = "rRsSfFdDxXeEpP"; |
| |
| /* Prefix character that indicates the start of an immediate value. */ |
| #define is_immediate_prefix(C) ((C) == '#') |
| |
| /* Separator character handling. */ |
| |
| #define skip_whitespace(str) do { if (*(str) == ' ') ++(str); } while (0) |
| |
| static inline bfd_boolean |
| skip_past_char (char **str, char c) |
| { |
| if (**str == c) |
| { |
| (*str)++; |
| return TRUE; |
| } |
| else |
| return FALSE; |
| } |
| |
| #define skip_past_comma(str) skip_past_char (str, ',') |
| |
| /* Arithmetic expressions (possibly involving symbols). */ |
| |
| static bfd_boolean in_my_get_expression_p = FALSE; |
| |
| /* Third argument to my_get_expression. */ |
| #define GE_NO_PREFIX 0 |
| #define GE_OPT_PREFIX 1 |
| |
| /* Return TRUE if the string pointed by *STR is successfully parsed |
| as an valid expression; *EP will be filled with the information of |
| such an expression. Otherwise return FALSE. */ |
| |
| static bfd_boolean |
| my_get_expression (expressionS * ep, char **str, int prefix_mode, |
| int reject_absent) |
| { |
| char *save_in; |
| segT seg; |
| int prefix_present_p = 0; |
| |
| switch (prefix_mode) |
| { |
| case GE_NO_PREFIX: |
| break; |
| case GE_OPT_PREFIX: |
| if (is_immediate_prefix (**str)) |
| { |
| (*str)++; |
| prefix_present_p = 1; |
| } |
| break; |
| default: |
| abort (); |
| } |
| |
| memset (ep, 0, sizeof (expressionS)); |
| |
| save_in = input_line_pointer; |
| input_line_pointer = *str; |
| in_my_get_expression_p = TRUE; |
| seg = expression (ep); |
| in_my_get_expression_p = FALSE; |
| |
| if (ep->X_op == O_illegal || (reject_absent && ep->X_op == O_absent)) |
| { |
| /* We found a bad expression in md_operand(). */ |
| *str = input_line_pointer; |
| input_line_pointer = save_in; |
| if (prefix_present_p && ! error_p ()) |
| set_fatal_syntax_error (_("bad expression")); |
| else |
| set_first_syntax_error (_("bad expression")); |
| return FALSE; |
| } |
| |
| #ifdef OBJ_AOUT |
| if (seg != absolute_section |
| && seg != text_section |
| && seg != data_section |
| && seg != bss_section && seg != undefined_section) |
| { |
| set_syntax_error (_("bad segment")); |
| *str = input_line_pointer; |
| input_line_pointer = save_in; |
| return FALSE; |
| } |
| #else |
| (void) seg; |
| #endif |
| |
| *str = input_line_pointer; |
| input_line_pointer = save_in; |
| return TRUE; |
| } |
| |
| /* Turn a string in input_line_pointer into a floating point constant |
| of type TYPE, and store the appropriate bytes in *LITP. The number |
| of LITTLENUMS emitted is stored in *SIZEP. An error message is |
| returned, or NULL on OK. */ |
| |
| const char * |
| md_atof (int type, char *litP, int *sizeP) |
| { |
| return ieee_md_atof (type, litP, sizeP, target_big_endian); |
| } |
| |
| /* We handle all bad expressions here, so that we can report the faulty |
| instruction in the error message. */ |
| void |
| md_operand (expressionS * exp) |
| { |
| if (in_my_get_expression_p) |
| exp->X_op = O_illegal; |
| } |
| |
| /* Immediate values. */ |
| |
| /* Errors may be set multiple times during parsing or bit encoding |
| (particularly in the Neon bits), but usually the earliest error which is set |
| will be the most meaningful. Avoid overwriting it with later (cascading) |
| errors by calling this function. */ |
| |
| static void |
| first_error (const char *error) |
| { |
| if (! error_p ()) |
| set_syntax_error (error); |
| } |
| |
| /* Similiar to first_error, but this function accepts formatted error |
| message. */ |
| static void |
| first_error_fmt (const char *format, ...) |
| { |
| va_list args; |
| enum |
| { size = 100 }; |
| /* N.B. this single buffer will not cause error messages for different |
| instructions to pollute each other; this is because at the end of |
| processing of each assembly line, error message if any will be |
| collected by as_bad. */ |
| static char buffer[size]; |
| |
| if (! error_p ()) |
| { |
| int ret ATTRIBUTE_UNUSED; |
| va_start (args, format); |
| ret = vsnprintf (buffer, size, format, args); |
| know (ret <= size - 1 && ret >= 0); |
| va_end (args); |
| set_syntax_error (buffer); |
| } |
| } |
| |
| /* Register parsing. */ |
| |
| /* Generic register parser which is called by other specialized |
| register parsers. |
| CCP points to what should be the beginning of a register name. |
| If it is indeed a valid register name, advance CCP over it and |
| return the reg_entry structure; otherwise return NULL. |
| It does not issue diagnostics. */ |
| |
| static reg_entry * |
| parse_reg (char **ccp) |
| { |
| char *start = *ccp; |
| char *p; |
| reg_entry *reg; |
| |
| #ifdef REGISTER_PREFIX |
| if (*start != REGISTER_PREFIX) |
| return NULL; |
| start++; |
| #endif |
| |
| p = start; |
| if (!ISALPHA (*p) || !is_name_beginner (*p)) |
| return NULL; |
| |
| do |
| p++; |
| while (ISALPHA (*p) || ISDIGIT (*p) || *p == '_'); |
| |
| reg = (reg_entry *) hash_find_n (aarch64_reg_hsh, start, p - start); |
| |
| if (!reg) |
| return NULL; |
| |
| *ccp = p; |
| return reg; |
| } |
| |
| /* Return TRUE if REG->TYPE is a valid type of TYPE; otherwise |
| return FALSE. */ |
| static bfd_boolean |
| aarch64_check_reg_type (const reg_entry *reg, aarch64_reg_type type) |
| { |
| if (reg->type == type) |
| return TRUE; |
| |
| switch (type) |
| { |
| case REG_TYPE_R64_SP: /* 64-bit integer reg (inc SP exc XZR). */ |
| case REG_TYPE_R_Z_SP: /* Integer reg (inc {X}SP inc [WX]ZR). */ |
| case REG_TYPE_R_Z_BHSDQ_V: /* Any register apart from Cn. */ |
| case REG_TYPE_BHSDQ: /* Any [BHSDQ]P FP or SIMD scalar register. */ |
| case REG_TYPE_VN: /* Vector register. */ |
| gas_assert (reg->type < REG_TYPE_MAX && type < REG_TYPE_MAX); |
| return ((reg_type_masks[reg->type] & reg_type_masks[type]) |
| == reg_type_masks[reg->type]); |
| default: |
| as_fatal ("unhandled type %d", type); |
| abort (); |
| } |
| } |
| |
| /* Parse a register and return PARSE_FAIL if the register is not of type R_Z_SP. |
| Return the register number otherwise. *ISREG32 is set to one if the |
| register is 32-bit wide; *ISREGZERO is set to one if the register is |
| of type Z_32 or Z_64. |
| Note that this function does not issue any diagnostics. */ |
| |
| static int |
| aarch64_reg_parse_32_64 (char **ccp, int reject_sp, int reject_rz, |
| int *isreg32, int *isregzero) |
| { |
| char *str = *ccp; |
| const reg_entry *reg = parse_reg (&str); |
| |
| if (reg == NULL) |
| return PARSE_FAIL; |
| |
| if (! aarch64_check_reg_type (reg, REG_TYPE_R_Z_SP)) |
| return PARSE_FAIL; |
| |
| switch (reg->type) |
| { |
| case REG_TYPE_SP_32: |
| case REG_TYPE_SP_64: |
| if (reject_sp) |
| return PARSE_FAIL; |
| *isreg32 = reg->type == REG_TYPE_SP_32; |
| *isregzero = 0; |
| break; |
| case REG_TYPE_R_32: |
| case REG_TYPE_R_64: |
| *isreg32 = reg->type == REG_TYPE_R_32; |
| *isregzero = 0; |
| break; |
| case REG_TYPE_Z_32: |
| case REG_TYPE_Z_64: |
| if (reject_rz) |
| return PARSE_FAIL; |
| *isreg32 = reg->type == REG_TYPE_Z_32; |
| *isregzero = 1; |
| break; |
| default: |
| return PARSE_FAIL; |
| } |
| |
| *ccp = str; |
| |
| return reg->number; |
| } |
| |
| /* Parse the qualifier of a SIMD vector register or a SIMD vector element. |
| Fill in *PARSED_TYPE and return TRUE if the parsing succeeds; |
| otherwise return FALSE. |
| |
| Accept only one occurrence of: |
| 8b 16b 2h 4h 8h 2s 4s 1d 2d |
| b h s d q */ |
| static bfd_boolean |
| parse_neon_type_for_operand (struct neon_type_el *parsed_type, char **str) |
| { |
| char *ptr = *str; |
| unsigned width; |
| unsigned element_size; |
| enum neon_el_type type; |
| |
| /* skip '.' */ |
| ptr++; |
| |
| if (!ISDIGIT (*ptr)) |
| { |
| width = 0; |
| goto elt_size; |
| } |
| width = strtoul (ptr, &ptr, 10); |
| if (width != 1 && width != 2 && width != 4 && width != 8 && width != 16) |
| { |
| first_error_fmt (_("bad size %d in vector width specifier"), width); |
| return FALSE; |
| } |
| |
| elt_size: |
| switch (TOLOWER (*ptr)) |
| { |
| case 'b': |
| type = NT_b; |
| element_size = 8; |
| break; |
| case 'h': |
| type = NT_h; |
| element_size = 16; |
| break; |
| case 's': |
| type = NT_s; |
| element_size = 32; |
| break; |
| case 'd': |
| type = NT_d; |
| element_size = 64; |
| break; |
| case 'q': |
| if (width == 1) |
| { |
| type = NT_q; |
| element_size = 128; |
| break; |
| } |
| /* fall through. */ |
| default: |
| if (*ptr != '\0') |
| first_error_fmt (_("unexpected character `%c' in element size"), *ptr); |
| else |
| first_error (_("missing element size")); |
| return FALSE; |
| } |
| if (width != 0 && width * element_size != 64 && width * element_size != 128 |
| && !(width == 2 && element_size == 16)) |
| { |
| first_error_fmt (_ |
| ("invalid element size %d and vector size combination %c"), |
| width, *ptr); |
| return FALSE; |
| } |
| ptr++; |
| |
| parsed_type->type = type; |
| parsed_type->width = width; |
| |
| *str = ptr; |
| |
| return TRUE; |
| } |
| |
| /* Parse a single type, e.g. ".8b", leading period included. |
| Only applicable to Vn registers. |
| |
| Return TRUE on success; otherwise return FALSE. */ |
| static bfd_boolean |
| parse_neon_operand_type (struct neon_type_el *vectype, char **ccp) |
| { |
| char *str = *ccp; |
| |
| if (*str == '.') |
| { |
| if (! parse_neon_type_for_operand (vectype, &str)) |
| { |
| first_error (_("vector type expected")); |
| return FALSE; |
| } |
| } |
| else |
| return FALSE; |
| |
| *ccp = str; |
| |
| return TRUE; |
| } |
| |
| /* Parse a register of the type TYPE. |
| |
| Return PARSE_FAIL if the string pointed by *CCP is not a valid register |
| name or the parsed register is not of TYPE. |
| |
| Otherwise return the register number, and optionally fill in the actual |
| type of the register in *RTYPE when multiple alternatives were given, and |
| return the register shape and element index information in *TYPEINFO. |
| |
| IN_REG_LIST should be set with TRUE if the caller is parsing a register |
| list. */ |
| |
| static int |
| parse_typed_reg (char **ccp, aarch64_reg_type type, aarch64_reg_type *rtype, |
| struct neon_type_el *typeinfo, bfd_boolean in_reg_list) |
| { |
| char *str = *ccp; |
| const reg_entry *reg = parse_reg (&str); |
| struct neon_type_el atype; |
| struct neon_type_el parsetype; |
| bfd_boolean is_typed_vecreg = FALSE; |
| |
| atype.defined = 0; |
| atype.type = NT_invtype; |
| atype.width = -1; |
| atype.index = 0; |
| |
| if (reg == NULL) |
| { |
| if (typeinfo) |
| *typeinfo = atype; |
| set_default_error (); |
| return PARSE_FAIL; |
| } |
| |
| if (! aarch64_check_reg_type (reg, type)) |
| { |
| DEBUG_TRACE ("reg type check failed"); |
| set_default_error (); |
| return PARSE_FAIL; |
| } |
| type = reg->type; |
| |
| if (type == REG_TYPE_VN |
| && parse_neon_operand_type (&parsetype, &str)) |
| { |
| /* Register if of the form Vn.[bhsdq]. */ |
| is_typed_vecreg = TRUE; |
| |
| if (parsetype.width == 0) |
| /* Expect index. In the new scheme we cannot have |
| Vn.[bhsdq] represent a scalar. Therefore any |
| Vn.[bhsdq] should have an index following it. |
| Except in reglists ofcourse. */ |
| atype.defined |= NTA_HASINDEX; |
| else |
| atype.defined |= NTA_HASTYPE; |
| |
| atype.type = parsetype.type; |
| atype.width = parsetype.width; |
| } |
| |
| if (skip_past_char (&str, '[')) |
| { |
| expressionS exp; |
| |
| /* Reject Sn[index] syntax. */ |
| if (!is_typed_vecreg) |
| { |
| first_error (_("this type of register can't be indexed")); |
| return PARSE_FAIL; |
| } |
| |
| if (in_reg_list == TRUE) |
| { |
| first_error (_("index not allowed inside register list")); |
| return PARSE_FAIL; |
| } |
| |
| atype.defined |= NTA_HASINDEX; |
| |
| my_get_expression (&exp, &str, GE_NO_PREFIX, 1); |
| |
| if (exp.X_op != O_constant) |
| { |
| first_error (_("constant expression required")); |
| return PARSE_FAIL; |
| } |
| |
| if (! skip_past_char (&str, ']')) |
| return PARSE_FAIL; |
| |
| atype.index = exp.X_add_number; |
| } |
| else if (!in_reg_list && (atype.defined & NTA_HASINDEX) != 0) |
| { |
| /* Indexed vector register expected. */ |
| first_error (_("indexed vector register expected")); |
| return PARSE_FAIL; |
| } |
| |
| /* A vector reg Vn should be typed or indexed. */ |
| if (type == REG_TYPE_VN && atype.defined == 0) |
| { |
| first_error (_("invalid use of vector register")); |
| } |
| |
| if (typeinfo) |
| *typeinfo = atype; |
| |
| if (rtype) |
| *rtype = type; |
| |
| *ccp = str; |
| |
| return reg->number; |
| } |
| |
| /* Parse register. |
| |
| Return the register number on success; return PARSE_FAIL otherwise. |
| |
| If RTYPE is not NULL, return in *RTYPE the (possibly restricted) type of |
| the register (e.g. NEON double or quad reg when either has been requested). |
| |
| If this is a NEON vector register with additional type information, fill |
| in the struct pointed to by VECTYPE (if non-NULL). |
| |
| This parser does not handle register list. */ |
| |
| static int |
| aarch64_reg_parse (char **ccp, aarch64_reg_type type, |
| aarch64_reg_type *rtype, struct neon_type_el *vectype) |
| { |
| struct neon_type_el atype; |
| char *str = *ccp; |
| int reg = parse_typed_reg (&str, type, rtype, &atype, |
| /*in_reg_list= */ FALSE); |
| |
| if (reg == PARSE_FAIL) |
| return PARSE_FAIL; |
| |
| if (vectype) |
| *vectype = atype; |
| |
| *ccp = str; |
| |
| return reg; |
| } |
| |
| static inline bfd_boolean |
| eq_neon_type_el (struct neon_type_el e1, struct neon_type_el e2) |
| { |
| return |
| e1.type == e2.type |
| && e1.defined == e2.defined |
| && e1.width == e2.width && e1.index == e2.index; |
| } |
| |
| /* This function parses the NEON register list. On success, it returns |
| the parsed register list information in the following encoded format: |
| |
| bit 18-22 | 13-17 | 7-11 | 2-6 | 0-1 |
| 4th regno | 3rd regno | 2nd regno | 1st regno | num_of_reg |
| |
| The information of the register shape and/or index is returned in |
| *VECTYPE. |
| |
| It returns PARSE_FAIL if the register list is invalid. |
| |
| The list contains one to four registers. |
| Each register can be one of: |
| <Vt>.<T>[<index>] |
| <Vt>.<T> |
| All <T> should be identical. |
| All <index> should be identical. |
| There are restrictions on <Vt> numbers which are checked later |
| (by reg_list_valid_p). */ |
| |
| static int |
| parse_neon_reg_list (char **ccp, struct neon_type_el *vectype) |
| { |
| char *str = *ccp; |
| int nb_regs; |
| struct neon_type_el typeinfo, typeinfo_first; |
| int val, val_range; |
| int in_range; |
| int ret_val; |
| int i; |
| bfd_boolean error = FALSE; |
| bfd_boolean expect_index = FALSE; |
| |
| if (*str != '{') |
| { |
| set_syntax_error (_("expecting {")); |
| return PARSE_FAIL; |
| } |
| str++; |
| |
| nb_regs = 0; |
| typeinfo_first.defined = 0; |
| typeinfo_first.type = NT_invtype; |
| typeinfo_first.width = -1; |
| typeinfo_first.index = 0; |
| ret_val = 0; |
| val = -1; |
| val_range = -1; |
| in_range = 0; |
| do |
| { |
| if (in_range) |
| { |
| str++; /* skip over '-' */ |
| val_range = val; |
| } |
| val = parse_typed_reg (&str, REG_TYPE_VN, NULL, &typeinfo, |
| /*in_reg_list= */ TRUE); |
| if (val == PARSE_FAIL) |
| { |
| set_first_syntax_error (_("invalid vector register in list")); |
| error = TRUE; |
| continue; |
| } |
| /* reject [bhsd]n */ |
| if (typeinfo.defined == 0) |
| { |
| set_first_syntax_error (_("invalid scalar register in list")); |
| error = TRUE; |
| continue; |
| } |
| |
| if (typeinfo.defined & NTA_HASINDEX) |
| expect_index = TRUE; |
| |
| if (in_range) |
| { |
| if (val < val_range) |
| { |
| set_first_syntax_error |
| (_("invalid range in vector register list")); |
| error = TRUE; |
| } |
| val_range++; |
| } |
| else |
| { |
| val_range = val; |
| if (nb_regs == 0) |
| typeinfo_first = typeinfo; |
| else if (! eq_neon_type_el (typeinfo_first, typeinfo)) |
| { |
| set_first_syntax_error |
| (_("type mismatch in vector register list")); |
| error = TRUE; |
| } |
| } |
| if (! error) |
| for (i = val_range; i <= val; i++) |
| { |
| ret_val |= i << (5 * nb_regs); |
| nb_regs++; |
| } |
| in_range = 0; |
| } |
| while (skip_past_comma (&str) || (in_range = 1, *str == '-')); |
| |
| skip_whitespace (str); |
| if (*str != '}') |
| { |
| set_first_syntax_error (_("end of vector register list not found")); |
| error = TRUE; |
| } |
| str++; |
| |
| skip_whitespace (str); |
| |
| if (expect_index) |
| { |
| if (skip_past_char (&str, '[')) |
| { |
| expressionS exp; |
| |
| my_get_expression (&exp, &str, GE_NO_PREFIX, 1); |
| if (exp.X_op != O_constant) |
| { |
| set_first_syntax_error (_("constant expression required.")); |
| error = TRUE; |
| } |
| if (! skip_past_char (&str, ']')) |
| error = TRUE; |
| else |
| typeinfo_first.index = exp.X_add_number; |
| } |
| else |
| { |
| set_first_syntax_error (_("expected index")); |
| error = TRUE; |
| } |
| } |
| |
| if (nb_regs > 4) |
| { |
| set_first_syntax_error (_("too many registers in vector register list")); |
| error = TRUE; |
| } |
| else if (nb_regs == 0) |
| { |
| set_first_syntax_error (_("empty vector register list")); |
| error = TRUE; |
| } |
| |
| *ccp = str; |
| if (! error) |
| *vectype = typeinfo_first; |
| |
| return error ? PARSE_FAIL : (ret_val << 2) | (nb_regs - 1); |
| } |
| |
| /* Directives: register aliases. */ |
| |
| static reg_entry * |
| insert_reg_alias (char *str, int number, aarch64_reg_type type) |
| { |
| reg_entry *new; |
| const char *name; |
| |
| if ((new = hash_find (aarch64_reg_hsh, str)) != 0) |
| { |
| if (new->builtin) |
| as_warn (_("ignoring attempt to redefine built-in register '%s'"), |
| str); |
| |
| /* Only warn about a redefinition if it's not defined as the |
| same register. */ |
| else if (new->number != number || new->type != type) |
| as_warn (_("ignoring redefinition of register alias '%s'"), str); |
| |
| return NULL; |
| } |
| |
| name = xstrdup (str); |
| new = XNEW (reg_entry); |
| |
| new->name = name; |
| new->number = number; |
| new->type = type; |
| new->builtin = FALSE; |
| |
| if (hash_insert (aarch64_reg_hsh, name, (void *) new)) |
| abort (); |
| |
| return new; |
| } |
| |
| /* Look for the .req directive. This is of the form: |
| |
| new_register_name .req existing_register_name |
| |
| If we find one, or if it looks sufficiently like one that we want to |
| handle any error here, return TRUE. Otherwise return FALSE. */ |
| |
| static bfd_boolean |
| create_register_alias (char *newname, char *p) |
| { |
| const reg_entry *old; |
| char *oldname, *nbuf; |
| size_t nlen; |
| |
| /* The input scrubber ensures that whitespace after the mnemonic is |
| collapsed to single spaces. */ |
| oldname = p; |
| if (strncmp (oldname, " .req ", 6) != 0) |
| return FALSE; |
| |
| oldname += 6; |
| if (*oldname == '\0') |
| return FALSE; |
| |
| old = hash_find (aarch64_reg_hsh, oldname); |
| if (!old) |
| { |
| as_warn (_("unknown register '%s' -- .req ignored"), oldname); |
| return TRUE; |
| } |
| |
| /* If TC_CASE_SENSITIVE is defined, then newname already points to |
| the desired alias name, and p points to its end. If not, then |
| the desired alias name is in the global original_case_string. */ |
| #ifdef TC_CASE_SENSITIVE |
| nlen = p - newname; |
| #else |
| newname = original_case_string; |
| nlen = strlen (newname); |
| #endif |
| |
| nbuf = xmemdup0 (newname, nlen); |
| |
| /* Create aliases under the new name as stated; an all-lowercase |
| version of the new name; and an all-uppercase version of the new |
| name. */ |
| if (insert_reg_alias (nbuf, old->number, old->type) != NULL) |
| { |
| for (p = nbuf; *p; p++) |
| *p = TOUPPER (*p); |
| |
| if (strncmp (nbuf, newname, nlen)) |
| { |
| /* If this attempt to create an additional alias fails, do not bother |
| trying to create the all-lower case alias. We will fail and issue |
| a second, duplicate error message. This situation arises when the |
| programmer does something like: |
| foo .req r0 |
| Foo .req r1 |
| The second .req creates the "Foo" alias but then fails to create |
| the artificial FOO alias because it has already been created by the |
| first .req. */ |
| if (insert_reg_alias (nbuf, old->number, old->type) == NULL) |
| { |
| free (nbuf); |
| return TRUE; |
| } |
| } |
| |
| for (p = nbuf; *p; p++) |
| *p = TOLOWER (*p); |
| |
| if (strncmp (nbuf, newname, nlen)) |
| insert_reg_alias (nbuf, old->number, old->type); |
| } |
| |
| free (nbuf); |
| return TRUE; |
| } |
| |
| /* Should never be called, as .req goes between the alias and the |
| register name, not at the beginning of the line. */ |
| static void |
| s_req (int a ATTRIBUTE_UNUSED) |
| { |
| as_bad (_("invalid syntax for .req directive")); |
| } |
| |
| /* The .unreq directive deletes an alias which was previously defined |
| by .req. For example: |
| |
| my_alias .req r11 |
| .unreq my_alias */ |
| |
| static void |
| s_unreq (int a ATTRIBUTE_UNUSED) |
| { |
| char *name; |
| char saved_char; |
| |
| name = input_line_pointer; |
| |
| while (*input_line_pointer != 0 |
| && *input_line_pointer != ' ' && *input_line_pointer != '\n') |
| ++input_line_pointer; |
| |
| saved_char = *input_line_pointer; |
| *input_line_pointer = 0; |
| |
| if (!*name) |
| as_bad (_("invalid syntax for .unreq directive")); |
| else |
| { |
| reg_entry *reg = hash_find (aarch64_reg_hsh, name); |
| |
| if (!reg) |
| as_bad (_("unknown register alias '%s'"), name); |
| else if (reg->builtin) |
| as_warn (_("ignoring attempt to undefine built-in register '%s'"), |
| name); |
| else |
| { |
| char *p; |
| char *nbuf; |
| |
| hash_delete (aarch64_reg_hsh, name, FALSE); |
| free ((char *) reg->name); |
| free (reg); |
| |
| /* Also locate the all upper case and all lower case versions. |
| Do not complain if we cannot find one or the other as it |
| was probably deleted above. */ |
| |
| nbuf = strdup (name); |
| for (p = nbuf; *p; p++) |
| *p = TOUPPER (*p); |
| reg = hash_find (aarch64_reg_hsh, nbuf); |
| if (reg) |
| { |
| hash_delete (aarch64_reg_hsh, nbuf, FALSE); |
| free ((char *) reg->name); |
| free (reg); |
| } |
| |
| for (p = nbuf; *p; p++) |
| *p = TOLOWER (*p); |
| reg = hash_find (aarch64_reg_hsh, nbuf); |
| if (reg) |
| { |
| hash_delete (aarch64_reg_hsh, nbuf, FALSE); |
| free ((char *) reg->name); |
| free (reg); |
| } |
| |
| free (nbuf); |
| } |
| } |
| |
| *input_line_pointer = saved_char; |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Directives: Instruction set selection. */ |
| |
| #ifdef OBJ_ELF |
| /* This code is to handle mapping symbols as defined in the ARM AArch64 ELF |
| spec. (See "Mapping symbols", section 4.5.4, ARM AAELF64 version 0.05). |
| Note that previously, $a and $t has type STT_FUNC (BSF_OBJECT flag), |
| and $d has type STT_OBJECT (BSF_OBJECT flag). Now all three are untyped. */ |
| |
| /* Create a new mapping symbol for the transition to STATE. */ |
| |
| static void |
| make_mapping_symbol (enum mstate state, valueT value, fragS * frag) |
| { |
| symbolS *symbolP; |
| const char *symname; |
| int type; |
| |
| switch (state) |
| { |
| case MAP_DATA: |
| symname = "$d"; |
| type = BSF_NO_FLAGS; |
| break; |
| case MAP_INSN: |
| symname = "$x"; |
| type = BSF_NO_FLAGS; |
| break; |
| default: |
| abort (); |
| } |
| |
| symbolP = symbol_new (symname, now_seg, value, frag); |
| symbol_get_bfdsym (symbolP)->flags |= type | BSF_LOCAL; |
| |
| /* Save the mapping symbols for future reference. Also check that |
| we do not place two mapping symbols at the same offset within a |
| frag. We'll handle overlap between frags in |
| check_mapping_symbols. |
| |
| If .fill or other data filling directive generates zero sized data, |
| the mapping symbol for the following code will have the same value |
| as the one generated for the data filling directive. In this case, |
| we replace the old symbol with the new one at the same address. */ |
| if (value == 0) |
| { |
| if (frag->tc_frag_data.first_map != NULL) |
| { |
| know (S_GET_VALUE (frag->tc_frag_data.first_map) == 0); |
| symbol_remove (frag->tc_frag_data.first_map, &symbol_rootP, |
| &symbol_lastP); |
| } |
| frag->tc_frag_data.first_map = symbolP; |
| } |
| if (frag->tc_frag_data.last_map != NULL) |
| { |
| know (S_GET_VALUE (frag->tc_frag_data.last_map) <= |
| S_GET_VALUE (symbolP)); |
| if (S_GET_VALUE (frag->tc_frag_data.last_map) == S_GET_VALUE (symbolP)) |
| symbol_remove (frag->tc_frag_data.last_map, &symbol_rootP, |
| &symbol_lastP); |
| } |
| frag->tc_frag_data.last_map = symbolP; |
| } |
| |
| /* We must sometimes convert a region marked as code to data during |
| code alignment, if an odd number of bytes have to be padded. The |
| code mapping symbol is pushed to an aligned address. */ |
| |
| static void |
| insert_data_mapping_symbol (enum mstate state, |
| valueT value, fragS * frag, offsetT bytes) |
| { |
| /* If there was already a mapping symbol, remove it. */ |
| if (frag->tc_frag_data.last_map != NULL |
| && S_GET_VALUE (frag->tc_frag_data.last_map) == |
| frag->fr_address + value) |
| { |
| symbolS *symp = frag->tc_frag_data.last_map; |
| |
| if (value == 0) |
| { |
| know (frag->tc_frag_data.first_map == symp); |
| frag->tc_frag_data.first_map = NULL; |
| } |
| frag->tc_frag_data.last_map = NULL; |
| symbol_remove (symp, &symbol_rootP, &symbol_lastP); |
| } |
| |
| make_mapping_symbol (MAP_DATA, value, frag); |
| make_mapping_symbol (state, value + bytes, frag); |
| } |
| |
| static void mapping_state_2 (enum mstate state, int max_chars); |
| |
| /* Set the mapping state to STATE. Only call this when about to |
| emit some STATE bytes to the file. */ |
| |
| void |
| mapping_state (enum mstate state) |
| { |
| enum mstate mapstate = seg_info (now_seg)->tc_segment_info_data.mapstate; |
| |
| if (state == MAP_INSN) |
| /* AArch64 instructions require 4-byte alignment. When emitting |
| instructions into any section, record the appropriate section |
| alignment. */ |
| record_alignment (now_seg, 2); |
| |
| if (mapstate == state) |
| /* The mapping symbol has already been emitted. |
| There is nothing else to do. */ |
| return; |
| |
| #define TRANSITION(from, to) (mapstate == (from) && state == (to)) |
| if (TRANSITION (MAP_UNDEFINED, MAP_DATA) && !subseg_text_p (now_seg)) |
| /* Emit MAP_DATA within executable section in order. Otherwise, it will be |
| evaluated later in the next else. */ |
| return; |
| else if (TRANSITION (MAP_UNDEFINED, MAP_INSN)) |
| { |
| /* Only add the symbol if the offset is > 0: |
| if we're at the first frag, check it's size > 0; |
| if we're not at the first frag, then for sure |
| the offset is > 0. */ |
| struct frag *const frag_first = seg_info (now_seg)->frchainP->frch_root; |
| const int add_symbol = (frag_now != frag_first) |
| || (frag_now_fix () > 0); |
| |
| if (add_symbol) |
| make_mapping_symbol (MAP_DATA, (valueT) 0, frag_first); |
| } |
| #undef TRANSITION |
| |
| mapping_state_2 (state, 0); |
| } |
| |
| /* Same as mapping_state, but MAX_CHARS bytes have already been |
| allocated. Put the mapping symbol that far back. */ |
| |
| static void |
| mapping_state_2 (enum mstate state, int max_chars) |
| { |
| enum mstate mapstate = seg_info (now_seg)->tc_segment_info_data.mapstate; |
| |
| if (!SEG_NORMAL (now_seg)) |
| return; |
| |
| if (mapstate == state) |
| /* The mapping symbol has already been emitted. |
| There is nothing else to do. */ |
| return; |
| |
| seg_info (now_seg)->tc_segment_info_data.mapstate = state; |
| make_mapping_symbol (state, (valueT) frag_now_fix () - max_chars, frag_now); |
| } |
| #else |
| #define mapping_state(x) /* nothing */ |
| #define mapping_state_2(x, y) /* nothing */ |
| #endif |
| |
| /* Directives: sectioning and alignment. */ |
| |
| static void |
| s_bss (int ignore ATTRIBUTE_UNUSED) |
| { |
| /* We don't support putting frags in the BSS segment, we fake it by |
| marking in_bss, then looking at s_skip for clues. */ |
| subseg_set (bss_section, 0); |
| demand_empty_rest_of_line (); |
| mapping_state (MAP_DATA); |
| } |
| |
| static void |
| s_even (int ignore ATTRIBUTE_UNUSED) |
| { |
| /* Never make frag if expect extra pass. */ |
| if (!need_pass_2) |
| frag_align (1, 0, 0); |
| |
| record_alignment (now_seg, 1); |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Directives: Literal pools. */ |
| |
| static literal_pool * |
| find_literal_pool (int size) |
| { |
| literal_pool *pool; |
| |
| for (pool = list_of_pools; pool != NULL; pool = pool->next) |
| { |
| if (pool->section == now_seg |
| && pool->sub_section == now_subseg && pool->size == size) |
| break; |
| } |
| |
| return pool; |
| } |
| |
| static literal_pool * |
| find_or_make_literal_pool (int size) |
| { |
| /* Next literal pool ID number. */ |
| static unsigned int latest_pool_num = 1; |
| literal_pool *pool; |
| |
| pool = find_literal_pool (size); |
| |
| if (pool == NULL) |
| { |
| /* Create a new pool. */ |
| pool = XNEW (literal_pool); |
| if (!pool) |
| return NULL; |
| |
| /* Currently we always put the literal pool in the current text |
| section. If we were generating "small" model code where we |
| knew that all code and initialised data was within 1MB then |
| we could output literals to mergeable, read-only data |
| sections. */ |
| |
| pool->next_free_entry = 0; |
| pool->section = now_seg; |
| pool->sub_section = now_subseg; |
| pool->size = size; |
| pool->next = list_of_pools; |
| pool->symbol = NULL; |
| |
| /* Add it to the list. */ |
| list_of_pools = pool; |
| } |
| |
| /* New pools, and emptied pools, will have a NULL symbol. */ |
| if (pool->symbol == NULL) |
| { |
| pool->symbol = symbol_create (FAKE_LABEL_NAME, undefined_section, |
| (valueT) 0, &zero_address_frag); |
| pool->id = latest_pool_num++; |
| } |
| |
| /* Done. */ |
| return pool; |
| } |
| |
| /* Add the literal of size SIZE in *EXP to the relevant literal pool. |
| Return TRUE on success, otherwise return FALSE. */ |
| static bfd_boolean |
| add_to_lit_pool (expressionS *exp, int size) |
| { |
| literal_pool *pool; |
| unsigned int entry; |
| |
| pool = find_or_make_literal_pool (size); |
| |
| /* Check if this literal value is already in the pool. */ |
| for (entry = 0; entry < pool->next_free_entry; entry++) |
| { |
| expressionS * litexp = & pool->literals[entry].exp; |
| |
| if ((litexp->X_op == exp->X_op) |
| && (exp->X_op == O_constant) |
| && (litexp->X_add_number == exp->X_add_number) |
| && (litexp->X_unsigned == exp->X_unsigned)) |
| break; |
| |
| if ((litexp->X_op == exp->X_op) |
| && (exp->X_op == O_symbol) |
| && (litexp->X_add_number == exp->X_add_number) |
| && (litexp->X_add_symbol == exp->X_add_symbol) |
| && (litexp->X_op_symbol == exp->X_op_symbol)) |
| break; |
| } |
| |
| /* Do we need to create a new entry? */ |
| if (entry == pool->next_free_entry) |
| { |
| if (entry >= MAX_LITERAL_POOL_SIZE) |
| { |
| set_syntax_error (_("literal pool overflow")); |
| return FALSE; |
| } |
| |
| pool->literals[entry].exp = *exp; |
| pool->next_free_entry += 1; |
| if (exp->X_op == O_big) |
| { |
| /* PR 16688: Bignums are held in a single global array. We must |
| copy and preserve that value now, before it is overwritten. */ |
| pool->literals[entry].bignum = XNEWVEC (LITTLENUM_TYPE, |
| exp->X_add_number); |
| memcpy (pool->literals[entry].bignum, generic_bignum, |
| CHARS_PER_LITTLENUM * exp->X_add_number); |
| } |
| else |
| pool->literals[entry].bignum = NULL; |
| } |
| |
| exp->X_op = O_symbol; |
| exp->X_add_number = ((int) entry) * size; |
| exp->X_add_symbol = pool->symbol; |
| |
| return TRUE; |
| } |
| |
| /* Can't use symbol_new here, so have to create a symbol and then at |
| a later date assign it a value. Thats what these functions do. */ |
| |
| static void |
| symbol_locate (symbolS * symbolP, |
| const char *name,/* It is copied, the caller can modify. */ |
| segT segment, /* Segment identifier (SEG_<something>). */ |
| valueT valu, /* Symbol value. */ |
| fragS * frag) /* Associated fragment. */ |
| { |
| size_t name_length; |
| char *preserved_copy_of_name; |
| |
| name_length = strlen (name) + 1; /* +1 for \0. */ |
| obstack_grow (¬es, name, name_length); |
| preserved_copy_of_name = obstack_finish (¬es); |
| |
| #ifdef tc_canonicalize_symbol_name |
| preserved_copy_of_name = |
| tc_canonicalize_symbol_name (preserved_copy_of_name); |
| #endif |
| |
| S_SET_NAME (symbolP, preserved_copy_of_name); |
| |
| S_SET_SEGMENT (symbolP, segment); |
| S_SET_VALUE (symbolP, valu); |
| symbol_clear_list_pointers (symbolP); |
| |
| symbol_set_frag (symbolP, frag); |
| |
| /* Link to end of symbol chain. */ |
| { |
| extern int symbol_table_frozen; |
| |
| if (symbol_table_frozen) |
| abort (); |
| } |
| |
| symbol_append (symbolP, symbol_lastP, &symbol_rootP, &symbol_lastP); |
| |
| obj_symbol_new_hook (symbolP); |
| |
| #ifdef tc_symbol_new_hook |
| tc_symbol_new_hook (symbolP); |
| #endif |
| |
| #ifdef DEBUG_SYMS |
| verify_symbol_chain (symbol_rootP, symbol_lastP); |
| #endif /* DEBUG_SYMS */ |
| } |
| |
| |
| static void |
| s_ltorg (int ignored ATTRIBUTE_UNUSED) |
| { |
| unsigned int entry; |
| literal_pool *pool; |
| char sym_name[20]; |
| int align; |
| |
| for (align = 2; align <= 4; align++) |
| { |
| int size = 1 << align; |
| |
| pool = find_literal_pool (size); |
| if (pool == NULL || pool->symbol == NULL || pool->next_free_entry == 0) |
| continue; |
| |
| mapping_state (MAP_DATA); |
| |
| /* Align pool as you have word accesses. |
| Only make a frag if we have to. */ |
| if (!need_pass_2) |
| frag_align (align, 0, 0); |
| |
| record_alignment (now_seg, align); |
| |
| sprintf (sym_name, "$$lit_\002%x", pool->id); |
| |
| symbol_locate (pool->symbol, sym_name, now_seg, |
| (valueT) frag_now_fix (), frag_now); |
| symbol_table_insert (pool->symbol); |
| |
| for (entry = 0; entry < pool->next_free_entry; entry++) |
| { |
| expressionS * exp = & pool->literals[entry].exp; |
| |
| if (exp->X_op == O_big) |
| { |
| /* PR 16688: Restore the global bignum value. */ |
| gas_assert (pool->literals[entry].bignum != NULL); |
| memcpy (generic_bignum, pool->literals[entry].bignum, |
| CHARS_PER_LITTLENUM * exp->X_add_number); |
| } |
| |
| /* First output the expression in the instruction to the pool. */ |
| emit_expr (exp, size); /* .word|.xword */ |
| |
| if (exp->X_op == O_big) |
| { |
| free (pool->literals[entry].bignum); |
| pool->literals[entry].bignum = NULL; |
| } |
| } |
| |
| /* Mark the pool as empty. */ |
| pool->next_free_entry = 0; |
| pool->symbol = NULL; |
| } |
| } |
| |
| #ifdef OBJ_ELF |
| /* Forward declarations for functions below, in the MD interface |
| section. */ |
| static fixS *fix_new_aarch64 (fragS *, int, short, expressionS *, int, int); |
| static struct reloc_table_entry * find_reloc_table_entry (char **); |
| |
| /* Directives: Data. */ |
| /* N.B. the support for relocation suffix in this directive needs to be |
| implemented properly. */ |
| |
| static void |
| s_aarch64_elf_cons (int nbytes) |
| { |
| expressionS exp; |
| |
| #ifdef md_flush_pending_output |
| md_flush_pending_output (); |
| #endif |
| |
| if (is_it_end_of_statement ()) |
| { |
| demand_empty_rest_of_line (); |
| return; |
| } |
| |
| #ifdef md_cons_align |
| md_cons_align (nbytes); |
| #endif |
| |
| mapping_state (MAP_DATA); |
| do |
| { |
| struct reloc_table_entry *reloc; |
| |
| expression (&exp); |
| |
| if (exp.X_op != O_symbol) |
| emit_expr (&exp, (unsigned int) nbytes); |
| else |
| { |
| skip_past_char (&input_line_pointer, '#'); |
| if (skip_past_char (&input_line_pointer, ':')) |
| { |
| reloc = find_reloc_table_entry (&input_line_pointer); |
| if (reloc == NULL) |
| as_bad (_("unrecognized relocation suffix")); |
| else |
| as_bad (_("unimplemented relocation suffix")); |
| ignore_rest_of_line (); |
| return; |
| } |
| else |
| emit_expr (&exp, (unsigned int) nbytes); |
| } |
| } |
| while (*input_line_pointer++ == ','); |
| |
| /* Put terminator back into stream. */ |
| input_line_pointer--; |
| demand_empty_rest_of_line (); |
| } |
| |
| #endif /* OBJ_ELF */ |
| |
| /* Output a 32-bit word, but mark as an instruction. */ |
| |
| static void |
| s_aarch64_inst (int ignored ATTRIBUTE_UNUSED) |
| { |
| expressionS exp; |
| |
| #ifdef md_flush_pending_output |
| md_flush_pending_output (); |
| #endif |
| |
| if (is_it_end_of_statement ()) |
| { |
| demand_empty_rest_of_line (); |
| return; |
| } |
| |
| /* Sections are assumed to start aligned. In executable section, there is no |
| MAP_DATA symbol pending. So we only align the address during |
| MAP_DATA --> MAP_INSN transition. |
| For other sections, this is not guaranteed. */ |
| enum mstate mapstate = seg_info (now_seg)->tc_segment_info_data.mapstate; |
| if (!need_pass_2 && subseg_text_p (now_seg) && mapstate == MAP_DATA) |
| frag_align_code (2, 0); |
| |
| #ifdef OBJ_ELF |
| mapping_state (MAP_INSN); |
| #endif |
| |
| do |
| { |
| expression (&exp); |
| if (exp.X_op != O_constant) |
| { |
| as_bad (_("constant expression required")); |
| ignore_rest_of_line (); |
| return; |
| } |
| |
| if (target_big_endian) |
| { |
| unsigned int val = exp.X_add_number; |
| exp.X_add_number = SWAP_32 (val); |
| } |
| emit_expr (&exp, 4); |
| } |
| while (*input_line_pointer++ == ','); |
| |
| /* Put terminator back into stream. */ |
| input_line_pointer--; |
| demand_empty_rest_of_line (); |
| } |
| |
| #ifdef OBJ_ELF |
| /* Emit BFD_RELOC_AARCH64_TLSDESC_ADD on the next ADD instruction. */ |
| |
| static void |
| s_tlsdescadd (int ignored ATTRIBUTE_UNUSED) |
| { |
| expressionS exp; |
| |
| expression (&exp); |
| frag_grow (4); |
| fix_new_aarch64 (frag_now, frag_more (0) - frag_now->fr_literal, 4, &exp, 0, |
| BFD_RELOC_AARCH64_TLSDESC_ADD); |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Emit BFD_RELOC_AARCH64_TLSDESC_CALL on the next BLR instruction. */ |
| |
| static void |
| s_tlsdesccall (int ignored ATTRIBUTE_UNUSED) |
| { |
| expressionS exp; |
| |
| /* Since we're just labelling the code, there's no need to define a |
| mapping symbol. */ |
| expression (&exp); |
| /* Make sure there is enough room in this frag for the following |
| blr. This trick only works if the blr follows immediately after |
| the .tlsdesc directive. */ |
| frag_grow (4); |
| fix_new_aarch64 (frag_now, frag_more (0) - frag_now->fr_literal, 4, &exp, 0, |
| BFD_RELOC_AARCH64_TLSDESC_CALL); |
| |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Emit BFD_RELOC_AARCH64_TLSDESC_LDR on the next LDR instruction. */ |
| |
| static void |
| s_tlsdescldr (int ignored ATTRIBUTE_UNUSED) |
| { |
| expressionS exp; |
| |
| expression (&exp); |
| frag_grow (4); |
| fix_new_aarch64 (frag_now, frag_more (0) - frag_now->fr_literal, 4, &exp, 0, |
| BFD_RELOC_AARCH64_TLSDESC_LDR); |
| |
| demand_empty_rest_of_line (); |
| } |
| #endif /* OBJ_ELF */ |
| |
| static void s_aarch64_arch (int); |
| static void s_aarch64_cpu (int); |
| static void s_aarch64_arch_extension (int); |
| |
| /* This table describes all the machine specific pseudo-ops the assembler |
| has to support. The fields are: |
| pseudo-op name without dot |
| function to call to execute this pseudo-op |
| Integer arg to pass to the function. */ |
| |
| const pseudo_typeS md_pseudo_table[] = { |
| /* Never called because '.req' does not start a line. */ |
| {"req", s_req, 0}, |
| {"unreq", s_unreq, 0}, |
| {"bss", s_bss, 0}, |
| {"even", s_even, 0}, |
| {"ltorg", s_ltorg, 0}, |
| {"pool", s_ltorg, 0}, |
| {"cpu", s_aarch64_cpu, 0}, |
| {"arch", s_aarch64_arch, 0}, |
| {"arch_extension", s_aarch64_arch_extension, 0}, |
| {"inst", s_aarch64_inst, 0}, |
| #ifdef OBJ_ELF |
| {"tlsdescadd", s_tlsdescadd, 0}, |
| {"tlsdesccall", s_tlsdesccall, 0}, |
| {"tlsdescldr", s_tlsdescldr, 0}, |
| {"word", s_aarch64_elf_cons, 4}, |
| {"long", s_aarch64_elf_cons, 4}, |
| {"xword", s_aarch64_elf_cons, 8}, |
| {"dword", s_aarch64_elf_cons, 8}, |
| #endif |
| {0, 0, 0} |
| }; |
| |
| |
| /* Check whether STR points to a register name followed by a comma or the |
| end of line; REG_TYPE indicates which register types are checked |
| against. Return TRUE if STR is such a register name; otherwise return |
| FALSE. The function does not intend to produce any diagnostics, but since |
| the register parser aarch64_reg_parse, which is called by this function, |
| does produce diagnostics, we call clear_error to clear any diagnostics |
| that may be generated by aarch64_reg_parse. |
| Also, the function returns FALSE directly if there is any user error |
| present at the function entry. This prevents the existing diagnostics |
| state from being spoiled. |
| The function currently serves parse_constant_immediate and |
| parse_big_immediate only. */ |
| static bfd_boolean |
| reg_name_p (char *str, aarch64_reg_type reg_type) |
| { |
| int reg; |
| |
| /* Prevent the diagnostics state from being spoiled. */ |
| if (error_p ()) |
| return FALSE; |
| |
| reg = aarch64_reg_parse (&str, reg_type, NULL, NULL); |
| |
| /* Clear the parsing error that may be set by the reg parser. */ |
| clear_error (); |
| |
| if (reg == PARSE_FAIL) |
| return FALSE; |
| |
| skip_whitespace (str); |
| if (*str == ',' || is_end_of_line[(unsigned int) *str]) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| /* Parser functions used exclusively in instruction operands. */ |
| |
| /* Parse an immediate expression which may not be constant. |
| |
| To prevent the expression parser from pushing a register name |
| into the symbol table as an undefined symbol, firstly a check is |
| done to find out whether STR is a valid register name followed |
| by a comma or the end of line. Return FALSE if STR is such a |
| string. */ |
| |
| static bfd_boolean |
| parse_immediate_expression (char **str, expressionS *exp) |
| { |
| if (reg_name_p (*str, REG_TYPE_R_Z_BHSDQ_V)) |
| { |
| set_recoverable_error (_("immediate operand required")); |
| return FALSE; |
| } |
| |
| my_get_expression (exp, str, GE_OPT_PREFIX, 1); |
| |
| if (exp->X_op == O_absent) |
| { |
| set_fatal_syntax_error (_("missing immediate expression")); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| /* Constant immediate-value read function for use in insn parsing. |
| STR points to the beginning of the immediate (with the optional |
| leading #); *VAL receives the value. |
| |
| Return TRUE on success; otherwise return FALSE. */ |
| |
| static bfd_boolean |
| parse_constant_immediate (char **str, int64_t * val) |
| { |
| expressionS exp; |
| |
| if (! parse_immediate_expression (str, &exp)) |
| return FALSE; |
| |
| if (exp.X_op != O_constant) |
| { |
| set_syntax_error (_("constant expression required")); |
| return FALSE; |
| } |
| |
| *val = exp.X_add_number; |
| return TRUE; |
| } |
| |
| static uint32_t |
| encode_imm_float_bits (uint32_t imm) |
| { |
| return ((imm >> 19) & 0x7f) /* b[25:19] -> b[6:0] */ |
| | ((imm >> (31 - 7)) & 0x80); /* b[31] -> b[7] */ |
| } |
| |
| /* Return TRUE if the single-precision floating-point value encoded in IMM |
| can be expressed in the AArch64 8-bit signed floating-point format with |
| 3-bit exponent and normalized 4 bits of precision; in other words, the |
| floating-point value must be expressable as |
| (+/-) n / 16 * power (2, r) |
| where n and r are integers such that 16 <= n <=31 and -3 <= r <= 4. */ |
| |
| static bfd_boolean |
| aarch64_imm_float_p (uint32_t imm) |
| { |
| /* If a single-precision floating-point value has the following bit |
| pattern, it can be expressed in the AArch64 8-bit floating-point |
| format: |
| |
| 3 32222222 2221111111111 |
| 1 09876543 21098765432109876543210 |
| n Eeeeeexx xxxx0000000000000000000 |
| |
| where n, e and each x are either 0 or 1 independently, with |
| E == ~ e. */ |
| |
| uint32_t pattern; |
| |
| /* Prepare the pattern for 'Eeeeee'. */ |
| if (((imm >> 30) & 0x1) == 0) |
| pattern = 0x3e000000; |
| else |
| pattern = 0x40000000; |
| |
| return (imm & 0x7ffff) == 0 /* lower 19 bits are 0. */ |
| && ((imm & 0x7e000000) == pattern); /* bits 25 - 29 == ~ bit 30. */ |
| } |
| |
| /* Like aarch64_imm_float_p but for a double-precision floating-point value. |
| |
| Return TRUE if the value encoded in IMM can be expressed in the AArch64 |
| 8-bit signed floating-point format with 3-bit exponent and normalized 4 |
| bits of precision (i.e. can be used in an FMOV instruction); return the |
| equivalent single-precision encoding in *FPWORD. |
| |
| Otherwise return FALSE. */ |
| |
| static bfd_boolean |
| aarch64_double_precision_fmovable (uint64_t imm, uint32_t *fpword) |
| { |
| /* If a double-precision floating-point value has the following bit |
| pattern, it can be expressed in the AArch64 8-bit floating-point |
| format: |
| |
| 6 66655555555 554444444...21111111111 |
| 3 21098765432 109876543...098765432109876543210 |
| n Eeeeeeeeexx xxxx00000...000000000000000000000 |
| |
| where n, e and each x are either 0 or 1 independently, with |
| E == ~ e. */ |
| |
| uint32_t pattern; |
| uint32_t high32 = imm >> 32; |
| |
| /* Lower 32 bits need to be 0s. */ |
| if ((imm & 0xffffffff) != 0) |
| return FALSE; |
| |
| /* Prepare the pattern for 'Eeeeeeeee'. */ |
| if (((high32 >> 30) & 0x1) == 0) |
| pattern = 0x3fc00000; |
| else |
| pattern = 0x40000000; |
| |
| if ((high32 & 0xffff) == 0 /* bits 32 - 47 are 0. */ |
| && (high32 & 0x7fc00000) == pattern) /* bits 54 - 61 == ~ bit 62. */ |
| { |
| /* Convert to the single-precision encoding. |
| i.e. convert |
| n Eeeeeeeeexx xxxx00000...000000000000000000000 |
| to |
| n Eeeeeexx xxxx0000000000000000000. */ |
| *fpword = ((high32 & 0xfe000000) /* nEeeeee. */ |
| | (((high32 >> 16) & 0x3f) << 19)); /* xxxxxx. */ |
| return TRUE; |
| } |
| else |
| return FALSE; |
| } |
| |
| /* Parse a floating-point immediate. Return TRUE on success and return the |
| value in *IMMED in the format of IEEE754 single-precision encoding. |
| *CCP points to the start of the string; DP_P is TRUE when the immediate |
| is expected to be in double-precision (N.B. this only matters when |
| hexadecimal representation is involved). |
| |
| N.B. 0.0 is accepted by this function. */ |
| |
| static bfd_boolean |
| parse_aarch64_imm_float (char **ccp, int *immed, bfd_boolean dp_p) |
| { |
| char *str = *ccp; |
| char *fpnum; |
| LITTLENUM_TYPE words[MAX_LITTLENUMS]; |
| int found_fpchar = 0; |
| int64_t val = 0; |
| unsigned fpword = 0; |
| bfd_boolean hex_p = FALSE; |
| |
| skip_past_char (&str, '#'); |
| |
| fpnum = str; |
| skip_whitespace (fpnum); |
| |
| if (strncmp (fpnum, "0x", 2) == 0) |
| { |
| /* Support the hexadecimal representation of the IEEE754 encoding. |
| Double-precision is expected when DP_P is TRUE, otherwise the |
| representation should be in single-precision. */ |
| if (! parse_constant_immediate (&str, &val)) |
| goto invalid_fp; |
| |
| if (dp_p) |
| { |
| if (! aarch64_double_precision_fmovable (val, &fpword)) |
| goto invalid_fp; |
| } |
| else if ((uint64_t) val > 0xffffffff) |
| goto invalid_fp; |
| else |
| fpword = val; |
| |
| hex_p = TRUE; |
| } |
| else |
| { |
| /* We must not accidentally parse an integer as a floating-point number. |
| Make sure that the value we parse is not an integer by checking for |
| special characters '.' or 'e'. */ |
| for (; *fpnum != '\0' && *fpnum != ' ' && *fpnum != '\n'; fpnum++) |
| if (*fpnum == '.' || *fpnum == 'e' || *fpnum == 'E') |
| { |
| found_fpchar = 1; |
| break; |
| } |
| |
| if (!found_fpchar) |
| return FALSE; |
| } |
| |
| if (! hex_p) |
| { |
| int i; |
| |
| if ((str = atof_ieee (str, 's', words)) == NULL) |
| goto invalid_fp; |
| |
| /* Our FP word must be 32 bits (single-precision FP). */ |
| for (i = 0; i < 32 / LITTLENUM_NUMBER_OF_BITS; i++) |
| { |
| fpword <<= LITTLENUM_NUMBER_OF_BITS; |
| fpword |= words[i]; |
| } |
| } |
| |
| if (aarch64_imm_float_p (fpword) || (fpword & 0x7fffffff) == 0) |
| { |
| *immed = fpword; |
| *ccp = str; |
| return TRUE; |
| } |
| |
| invalid_fp: |
| set_fatal_syntax_error (_("invalid floating-point constant")); |
| return FALSE; |
| } |
| |
| /* Less-generic immediate-value read function with the possibility of loading |
| a big (64-bit) immediate, as required by AdvSIMD Modified immediate |
| instructions. |
| |
| To prevent the expression parser from pushing a register name into the |
| symbol table as an undefined symbol, a check is firstly done to find |
| out whether STR is a valid register name followed by a comma or the end |
| of line. Return FALSE if STR is such a register. */ |
| |
| static bfd_boolean |
| parse_big_immediate (char **str, int64_t *imm) |
| { |
| char *ptr = *str; |
| |
| if (reg_name_p (ptr, REG_TYPE_R_Z_BHSDQ_V)) |
| { |
| set_syntax_error (_("immediate operand required")); |
| return FALSE; |
| } |
| |
| my_get_expression (&inst.reloc.exp, &ptr, GE_OPT_PREFIX, 1); |
| |
| if (inst.reloc.exp.X_op == O_constant) |
| *imm = inst.reloc.exp.X_add_number; |
| |
| *str = ptr; |
| |
| return TRUE; |
| } |
| |
| /* Set operand IDX of the *INSTR that needs a GAS internal fixup. |
| if NEED_LIBOPCODES is non-zero, the fixup will need |
| assistance from the libopcodes. */ |
| |
| static inline void |
| aarch64_set_gas_internal_fixup (struct reloc *reloc, |
| const aarch64_opnd_info *operand, |
| int need_libopcodes_p) |
| { |
| reloc->type = BFD_RELOC_AARCH64_GAS_INTERNAL_FIXUP; |
| reloc->opnd = operand->type; |
| if (need_libopcodes_p) |
| reloc->need_libopcodes_p = 1; |
| }; |
| |
| /* Return TRUE if the instruction needs to be fixed up later internally by |
| the GAS; otherwise return FALSE. */ |
| |
| static inline bfd_boolean |
| aarch64_gas_internal_fixup_p (void) |
| { |
| return inst.reloc.type == BFD_RELOC_AARCH64_GAS_INTERNAL_FIXUP; |
| } |
| |
| /* Assign the immediate value to the relavant field in *OPERAND if |
| RELOC->EXP is a constant expression; otherwise, flag that *OPERAND |
| needs an internal fixup in a later stage. |
| ADDR_OFF_P determines whether it is the field ADDR.OFFSET.IMM or |
| IMM.VALUE that may get assigned with the constant. */ |
| static inline void |
| assign_imm_if_const_or_fixup_later (struct reloc *reloc, |
| aarch64_opnd_info *operand, |
| int addr_off_p, |
| int need_libopcodes_p, |
| int skip_p) |
| { |
| if (reloc->exp.X_op == O_constant) |
| { |
| if (addr_off_p) |
| operand->addr.offset.imm = reloc->exp.X_add_number; |
| else |
| operand->imm.value = reloc->exp.X_add_number; |
| reloc->type = BFD_RELOC_UNUSED; |
| } |
| else |
| { |
| aarch64_set_gas_internal_fixup (reloc, operand, need_libopcodes_p); |
| /* Tell libopcodes to ignore this operand or not. This is helpful |
| when one of the operands needs to be fixed up later but we need |
| libopcodes to check the other operands. */ |
| operand->skip = skip_p; |
| } |
| } |
| |
| /* Relocation modifiers. Each entry in the table contains the textual |
| name for the relocation which may be placed before a symbol used as |
| a load/store offset, or add immediate. It must be surrounded by a |
| leading and trailing colon, for example: |
| |
| ldr x0, [x1, #:rello:varsym] |
| add x0, x1, #:rello:varsym */ |
| |
| struct reloc_table_entry |
| { |
| const char *name; |
| int pc_rel; |
| bfd_reloc_code_real_type adr_type; |
| bfd_reloc_code_real_type adrp_type; |
| bfd_reloc_code_real_type movw_type; |
| bfd_reloc_code_real_type add_type; |
| bfd_reloc_code_real_type ldst_type; |
| bfd_reloc_code_real_type ld_literal_type; |
| }; |
| |
| static struct reloc_table_entry reloc_table[] = { |
| /* Low 12 bits of absolute address: ADD/i and LDR/STR */ |
| {"lo12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_ADD_LO12, |
| BFD_RELOC_AARCH64_LDST_LO12, |
| 0}, |
| |
| /* Higher 21 bits of pc-relative page offset: ADRP */ |
| {"pg_hi21", 1, |
| 0, /* adr_type */ |
| BFD_RELOC_AARCH64_ADR_HI21_PCREL, |
| 0, |
| 0, |
| 0, |
| 0}, |
| |
| /* Higher 21 bits of pc-relative page offset: ADRP, no check */ |
| {"pg_hi21_nc", 1, |
| 0, /* adr_type */ |
| BFD_RELOC_AARCH64_ADR_HI21_NC_PCREL, |
| 0, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 0-15 of unsigned address/value: MOVZ */ |
| {"abs_g0", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G0, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 0-15 of signed address/value: MOVN/Z */ |
| {"abs_g0_s", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G0_S, |
| 0, |
| 0, |
| 0}, |
| |
| /* Less significant bits 0-15 of address/value: MOVK, no check */ |
| {"abs_g0_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G0_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 16-31 of unsigned address/value: MOVZ */ |
| {"abs_g1", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G1, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 16-31 of signed address/value: MOVN/Z */ |
| {"abs_g1_s", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G1_S, |
| 0, |
| 0, |
| 0}, |
| |
| /* Less significant bits 16-31 of address/value: MOVK, no check */ |
| {"abs_g1_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G1_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 32-47 of unsigned address/value: MOVZ */ |
| {"abs_g2", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G2, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 32-47 of signed address/value: MOVN/Z */ |
| {"abs_g2_s", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G2_S, |
| 0, |
| 0, |
| 0}, |
| |
| /* Less significant bits 32-47 of address/value: MOVK, no check */ |
| {"abs_g2_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G2_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 48-63 of signed/unsigned address/value: MOVZ */ |
| {"abs_g3", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_G3, |
| 0, |
| 0, |
| 0}, |
| |
| /* Get to the page containing GOT entry for a symbol. */ |
| {"got", 1, |
| 0, /* adr_type */ |
| BFD_RELOC_AARCH64_ADR_GOT_PAGE, |
| 0, |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_GOT_LD_PREL19}, |
| |
| /* 12 bit offset into the page containing GOT entry for that symbol. */ |
| {"got_lo12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_LD_GOT_LO12_NC, |
| 0}, |
| |
| /* 0-15 bits of address/value: MOVk, no check. */ |
| {"gotoff_g0_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_GOTOFF_G0_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 16-31 of address/value: MOVZ. */ |
| {"gotoff_g1", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_GOTOFF_G1, |
| 0, |
| 0, |
| 0}, |
| |
| /* 15 bit offset into the page containing GOT entry for that symbol. */ |
| {"gotoff_lo15", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_LD64_GOTOFF_LO15, |
| 0}, |
| |
| /* Get to the page containing GOT TLS entry for a symbol */ |
| {"gottprel_g0_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Get to the page containing GOT TLS entry for a symbol */ |
| {"gottprel_g1", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G1, |
| 0, |
| 0, |
| 0}, |
| |
| /* Get to the page containing GOT TLS entry for a symbol */ |
| {"tlsgd", 0, |
| BFD_RELOC_AARCH64_TLSGD_ADR_PREL21, /* adr_type */ |
| BFD_RELOC_AARCH64_TLSGD_ADR_PAGE21, |
| 0, |
| 0, |
| 0, |
| 0}, |
| |
| /* 12 bit offset into the page containing GOT TLS entry for a symbol */ |
| {"tlsgd_lo12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSGD_ADD_LO12_NC, |
| 0, |
| 0}, |
| |
| /* Lower 16 bits address/value: MOVk. */ |
| {"tlsgd_g0_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSGD_MOVW_G0_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 16-31 of address/value: MOVZ. */ |
| {"tlsgd_g1", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSGD_MOVW_G1, |
| 0, |
| 0, |
| 0}, |
| |
| /* Get to the page containing GOT TLS entry for a symbol */ |
| {"tlsdesc", 0, |
| BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21, /* adr_type */ |
| BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21, |
| 0, |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSDESC_LD_PREL19}, |
| |
| /* 12 bit offset into the page containing GOT TLS entry for a symbol */ |
| {"tlsdesc_lo12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSDESC_ADD_LO12_NC, |
| BFD_RELOC_AARCH64_TLSDESC_LD_LO12_NC, |
| 0}, |
| |
| /* Get to the page containing GOT TLS entry for a symbol. |
| The same as GD, we allocate two consecutive GOT slots |
| for module index and module offset, the only difference |
| with GD is the module offset should be intialized to |
| zero without any outstanding runtime relocation. */ |
| {"tlsldm", 0, |
| BFD_RELOC_AARCH64_TLSLD_ADR_PREL21, /* adr_type */ |
| BFD_RELOC_AARCH64_TLSLD_ADR_PAGE21, |
| 0, |
| 0, |
| 0, |
| 0}, |
| |
| /* 12 bit offset into the page containing GOT TLS entry for a symbol */ |
| {"tlsldm_lo12_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_ADD_LO12_NC, |
| 0, |
| 0}, |
| |
| /* 12 bit offset into the module TLS base address. */ |
| {"dtprel_lo12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12, |
| BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12, |
| 0}, |
| |
| /* Same as dtprel_lo12, no overflow check. */ |
| {"dtprel_lo12_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12_NC, |
| BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12_NC, |
| 0}, |
| |
| /* bits[23:12] of offset to the module TLS base address. */ |
| {"dtprel_hi12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_HI12, |
| 0, |
| 0}, |
| |
| /* bits[15:0] of offset to the module TLS base address. */ |
| {"dtprel_g0", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0, |
| 0, |
| 0, |
| 0}, |
| |
| /* No overflow check version of BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0. */ |
| {"dtprel_g0_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* bits[31:16] of offset to the module TLS base address. */ |
| {"dtprel_g1", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1, |
| 0, |
| 0, |
| 0}, |
| |
| /* No overflow check version of BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1. */ |
| {"dtprel_g1_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* bits[47:32] of offset to the module TLS base address. */ |
| {"dtprel_g2", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G2, |
| 0, |
| 0, |
| 0}, |
| |
| /* Lower 16 bit offset into GOT entry for a symbol */ |
| {"tlsdesc_off_g0_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Higher 16 bit offset into GOT entry for a symbol */ |
| {"tlsdesc_off_g1", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSDESC_OFF_G1, |
| 0, |
| 0, |
| 0}, |
| |
| /* Get to the page containing GOT TLS entry for a symbol */ |
| {"gottprel", 0, |
| 0, /* adr_type */ |
| BFD_RELOC_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21, |
| 0, |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_PREL19}, |
| |
| /* 12 bit offset into the page containing GOT TLS entry for a symbol */ |
| {"gottprel_lo12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_LO12_NC, |
| 0}, |
| |
| /* Get tp offset for a symbol. */ |
| {"tprel", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12, |
| 0, |
| 0}, |
| |
| /* Get tp offset for a symbol. */ |
| {"tprel_lo12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12, |
| 0, |
| 0}, |
| |
| /* Get tp offset for a symbol. */ |
| {"tprel_hi12", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_HI12, |
| 0, |
| 0}, |
| |
| /* Get tp offset for a symbol. */ |
| {"tprel_lo12_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12_NC, |
| 0, |
| 0}, |
| |
| /* Most significant bits 32-47 of address/value: MOVZ. */ |
| {"tprel_g2", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 16-31 of address/value: MOVZ. */ |
| {"tprel_g1", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 16-31 of address/value: MOVZ, no check. */ |
| {"tprel_g1_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 0-15 of address/value: MOVZ. */ |
| {"tprel_g0", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 0-15 of address/value: MOVZ, no check. */ |
| {"tprel_g0_nc", 0, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* 15bit offset from got entry to base address of GOT table. */ |
| {"gotpage_lo15", 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_LD64_GOTPAGE_LO15, |
| 0}, |
| |
| /* 14bit offset from got entry to base address of GOT table. */ |
| {"gotpage_lo14", 0, |
| 0, |
| 0, |
| 0, |
| 0, |
| BFD_RELOC_AARCH64_LD32_GOTPAGE_LO14, |
| 0}, |
| }; |
| |
| /* Given the address of a pointer pointing to the textual name of a |
| relocation as may appear in assembler source, attempt to find its |
| details in reloc_table. The pointer will be updated to the character |
| after the trailing colon. On failure, NULL will be returned; |
| otherwise return the reloc_table_entry. */ |
| |
| static struct reloc_table_entry * |
| find_reloc_table_entry (char **str) |
| { |
| unsigned int i; |
| for (i = 0; i < ARRAY_SIZE (reloc_table); i++) |
| { |
| int length = strlen (reloc_table[i].name); |
| |
| if (strncasecmp (reloc_table[i].name, *str, length) == 0 |
| && (*str)[length] == ':') |
| { |
| *str += (length + 1); |
| return &reloc_table[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| /* Mode argument to parse_shift and parser_shifter_operand. */ |
| enum parse_shift_mode |
| { |
| SHIFTED_ARITH_IMM, /* "rn{,lsl|lsr|asl|asr|uxt|sxt #n}" or |
| "#imm{,lsl #n}" */ |
| SHIFTED_LOGIC_IMM, /* "rn{,lsl|lsr|asl|asr|ror #n}" or |
| "#imm" */ |
| SHIFTED_LSL, /* bare "lsl #n" */ |
| SHIFTED_LSL_MSL, /* "lsl|msl #n" */ |
| SHIFTED_REG_OFFSET /* [su]xtw|sxtx {#n} or lsl #n */ |
| }; |
| |
| /* Parse a <shift> operator on an AArch64 data processing instruction. |
| Return TRUE on success; otherwise return FALSE. */ |
| static bfd_boolean |
| parse_shift (char **str, aarch64_opnd_info *operand, enum parse_shift_mode mode) |
| { |
| const struct aarch64_name_value_pair *shift_op; |
| enum aarch64_modifier_kind kind; |
| expressionS exp; |
| int exp_has_prefix; |
| char *s = *str; |
| char *p = s; |
| |
| for (p = *str; ISALPHA (*p); p++) |
| ; |
| |
| if (p == *str) |
| { |
| set_syntax_error (_("shift expression expected")); |
| return FALSE; |
| } |
| |
| shift_op = hash_find_n (aarch64_shift_hsh, *str, p - *str); |
| |
| if (shift_op == NULL) |
| { |
| set_syntax_error (_("shift operator expected")); |
| return FALSE; |
| } |
| |
| kind = aarch64_get_operand_modifier (shift_op); |
| |
| if (kind == AARCH64_MOD_MSL && mode != SHIFTED_LSL_MSL) |
| { |
| set_syntax_error (_("invalid use of 'MSL'")); |
| return FALSE; |
| } |
| |
| switch (mode) |
| { |
| case SHIFTED_LOGIC_IMM: |
| if (aarch64_extend_operator_p (kind) == TRUE) |
| { |
| set_syntax_error (_("extending shift is not permitted")); |
| return FALSE; |
| } |
| break; |
| |
| case SHIFTED_ARITH_IMM: |
| if (kind == AARCH64_MOD_ROR) |
| { |
| set_syntax_error (_("'ROR' shift is not permitted")); |
| return FALSE; |
| } |
| break; |
| |
| case SHIFTED_LSL: |
| if (kind != AARCH64_MOD_LSL) |
| { |
| set_syntax_error (_("only 'LSL' shift is permitted")); |
| return FALSE; |
| } |
| break; |
| |
| case SHIFTED_REG_OFFSET: |
| if (kind != AARCH64_MOD_UXTW && kind != AARCH64_MOD_LSL |
| && kind != AARCH64_MOD_SXTW && kind != AARCH64_MOD_SXTX) |
| { |
| set_fatal_syntax_error |
| (_("invalid shift for the register offset addressing mode")); |
| return FALSE; |
| } |
| break; |
| |
| case SHIFTED_LSL_MSL: |
| if (kind != AARCH64_MOD_LSL && kind != AARCH64_MOD_MSL) |
| { |
| set_syntax_error (_("invalid shift operator")); |
| return FALSE; |
| } |
| break; |
| |
| default: |
| abort (); |
| } |
| |
| /* Whitespace can appear here if the next thing is a bare digit. */ |
| skip_whitespace (p); |
| |
| /* Parse shift amount. */ |
| exp_has_prefix = 0; |
| if (mode == SHIFTED_REG_OFFSET && *p == ']') |
| exp.X_op = O_absent; |
| else |
| { |
| if (is_immediate_prefix (*p)) |
| { |
| p++; |
| exp_has_prefix = 1; |
| } |
| my_get_expression (&exp, &p, GE_NO_PREFIX, 0); |
| } |
| if (exp.X_op == O_absent) |
| { |
| if (aarch64_extend_operator_p (kind) == FALSE || exp_has_prefix) |
| { |
| set_syntax_error (_("missing shift amount")); |
| return FALSE; |
| } |
| operand->shifter.amount = 0; |
| } |
| else if (exp.X_op != O_constant) |
| { |
| set_syntax_error (_("constant shift amount required")); |
| return FALSE; |
| } |
| else if (exp.X_add_number < 0 || exp.X_add_number > 63) |
| { |
| set_fatal_syntax_error (_("shift amount out of range 0 to 63")); |
| return FALSE; |
| } |
| else |
| { |
| operand->shifter.amount = exp.X_add_number; |
| operand->shifter.amount_present = 1; |
| } |
| |
| operand->shifter.operator_present = 1; |
| operand->shifter.kind = kind; |
| |
| *str = p; |
| return TRUE; |
| } |
| |
| /* Parse a <shifter_operand> for a data processing instruction: |
| |
| #<immediate> |
| #<immediate>, LSL #imm |
| |
| Validation of immediate operands is deferred to md_apply_fix. |
| |
| Return TRUE on success; otherwise return FALSE. */ |
| |
| static bfd_boolean |
| parse_shifter_operand_imm (char **str, aarch64_opnd_info *operand, |
| enum parse_shift_mode mode) |
| { |
| char *p; |
| |
| if (mode != SHIFTED_ARITH_IMM && mode != SHIFTED_LOGIC_IMM) |
| return FALSE; |
| |
| p = *str; |
| |
| /* Accept an immediate expression. */ |
| if (! my_get_expression (&inst.reloc.exp, &p, GE_OPT_PREFIX, 1)) |
| return FALSE; |
| |
| /* Accept optional LSL for arithmetic immediate values. */ |
| if (mode == SHIFTED_ARITH_IMM && skip_past_comma (&p)) |
| if (! parse_shift (&p, operand, SHIFTED_LSL)) |
| return FALSE; |
| |
| /* Not accept any shifter for logical immediate values. */ |
| if (mode == SHIFTED_LOGIC_IMM && skip_past_comma (&p) |
| && parse_shift (&p, operand, mode)) |
| { |
| set_syntax_error (_("unexpected shift operator")); |
| return FALSE; |
| } |
| |
| *str = p; |
| return TRUE; |
| } |
| |
| /* Parse a <shifter_operand> for a data processing instruction: |
| |
| <Rm> |
| <Rm>, <shift> |
| #<immediate> |
| #<immediate>, LSL #imm |
| |
| where <shift> is handled by parse_shift above, and the last two |
| cases are handled by the function above. |
| |
| Validation of immediate operands is deferred to md_apply_fix. |
| |
| Return TRUE on success; otherwise return FALSE. */ |
| |
| static bfd_boolean |
| parse_shifter_operand (char **str, aarch64_opnd_info *operand, |
| enum parse_shift_mode mode) |
| { |
| int reg; |
| int isreg32, isregzero; |
| enum aarch64_operand_class opd_class |
| = aarch64_get_operand_class (operand->type); |
| |
| if ((reg = |
| aarch64_reg_parse_32_64 (str, 0, 0, &isreg32, &isregzero)) != PARSE_FAIL) |
| { |
| if (opd_class == AARCH64_OPND_CLASS_IMMEDIATE) |
| { |
| set_syntax_error (_("unexpected register in the immediate operand")); |
| return FALSE; |
| } |
| |
| if (!isregzero && reg == REG_SP) |
| { |
| set_syntax_error (BAD_SP); |
| return FALSE; |
| } |
| |
| operand->reg.regno = reg; |
| operand->qualifier = isreg32 ? AARCH64_OPND_QLF_W : AARCH64_OPND_QLF_X; |
| |
| /* Accept optional shift operation on register. */ |
| if (! skip_past_comma (str)) |
| return TRUE; |
| |
| if (! parse_shift (str, operand, mode)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| else if (opd_class == AARCH64_OPND_CLASS_MODIFIED_REG) |
| { |
| set_syntax_error |
| (_("integer register expected in the extended/shifted operand " |
| "register")); |
| return FALSE; |
| } |
| |
| /* We have a shifted immediate variable. */ |
| return parse_shifter_operand_imm (str, operand, mode); |
| } |
| |
| /* Return TRUE on success; return FALSE otherwise. */ |
| |
| static bfd_boolean |
| parse_shifter_operand_reloc (char **str, aarch64_opnd_info *operand, |
| enum parse_shift_mode mode) |
| { |
| char *p = *str; |
| |
| /* Determine if we have the sequence of characters #: or just : |
| coming next. If we do, then we check for a :rello: relocation |
| modifier. If we don't, punt the whole lot to |
| parse_shifter_operand. */ |
| |
| if ((p[0] == '#' && p[1] == ':') || p[0] == ':') |
| { |
| struct reloc_table_entry *entry; |
| |
| if (p[0] == '#') |
| p += 2; |
| else |
| p++; |
| *str = p; |
| |
| /* Try to parse a relocation. Anything else is an error. */ |
| if (!(entry = find_reloc_table_entry (str))) |
| { |
| set_syntax_error (_("unknown relocation modifier")); |
| return FALSE; |
| } |
| |
| if (entry->add_type == 0) |
| { |
| set_syntax_error |
| (_("this relocation modifier is not allowed on this instruction")); |
| return FALSE; |
| } |
| |
| /* Save str before we decompose it. */ |
| p = *str; |
| |
| /* Next, we parse the expression. */ |
| if (! my_get_expression (&inst.reloc.exp, str, GE_NO_PREFIX, 1)) |
| return FALSE; |
| |
| /* Record the relocation type (use the ADD variant here). */ |
| inst.reloc.type = entry->add_type; |
| inst.reloc.pc_rel = entry->pc_rel; |
| |
| /* If str is empty, we've reached the end, stop here. */ |
| if (**str == '\0') |
| return TRUE; |
| |
| /* Otherwise, we have a shifted reloc modifier, so rewind to |
| recover the variable name and continue parsing for the shifter. */ |
| *str = p; |
| return parse_shifter_operand_imm (str, operand, mode); |
| } |
| |
| return parse_shifter_operand (str, operand, mode); |
| } |
| |
| /* Parse all forms of an address expression. Information is written |
| to *OPERAND and/or inst.reloc. |
| |
| The A64 instruction set has the following addressing modes: |
| |
| Offset |
| [base] // in SIMD ld/st structure |
| [base{,#0}] // in ld/st exclusive |
| [base{,#imm}] |
| [base,Xm{,LSL #imm}] |
| [base,Xm,SXTX {#imm}] |
| [base,Wm,(S|U)XTW {#imm}] |
| Pre-indexed |
| [base,#imm]! |
| Post-indexed |
| [base],#imm |
| [base],Xm // in SIMD ld/st structure |
| PC-relative (literal) |
| label |
| =immediate |
| |
| (As a convenience, the notation "=immediate" is permitted in conjunction |
| with the pc-relative literal load instructions to automatically place an |
| immediate value or symbolic address in a nearby literal pool and generate |
| a hidden label which references it.) |
| |
| Upon a successful parsing, the address structure in *OPERAND will be |
| filled in the following way: |
| |
| .base_regno = <base> |
| .offset.is_reg // 1 if the offset is a register |
| .offset.imm = <imm> |
| .offset.regno = <Rm> |
| |
| For different addressing modes defined in the A64 ISA: |
| |
| Offset |
| .pcrel=0; .preind=1; .postind=0; .writeback=0 |
| Pre-indexed |
| .pcrel=0; .preind=1; .postind=0; .writeback=1 |
| Post-indexed |
| .pcrel=0; .preind=0; .postind=1; .writeback=1 |
| PC-relative (literal) |
| .pcrel=1; .preind=1; .postind=0; .writeback=0 |
| |
| The shift/extension information, if any, will be stored in .shifter. |
| |
| It is the caller's responsibility to check for addressing modes not |
| supported by the instruction, and to set inst.reloc.type. */ |
| |
| static bfd_boolean |
| parse_address_main (char **str, aarch64_opnd_info *operand, int reloc, |
| int accept_reg_post_index) |
| { |
| char *p = *str; |
| int reg; |
| int isreg32, isregzero; |
| expressionS *exp = &inst.reloc.exp; |
| |
| if (! skip_past_char (&p, '[')) |
| { |
| /* =immediate or label. */ |
| operand->addr.pcrel = 1; |
| operand->addr.preind = 1; |
| |
| /* #:<reloc_op>:<symbol> */ |
| skip_past_char (&p, '#'); |
| if (reloc && skip_past_char (&p, ':')) |
| { |
| bfd_reloc_code_real_type ty; |
| struct reloc_table_entry *entry; |
| |
| /* Try to parse a relocation modifier. Anything else is |
| an error. */ |
| entry = find_reloc_table_entry (&p); |
| if (! entry) |
| { |
| set_syntax_error (_("unknown relocation modifier")); |
| return FALSE; |
| } |
| |
| switch (operand->type) |
| { |
| case AARCH64_OPND_ADDR_PCREL21: |
| /* adr */ |
| ty = entry->adr_type; |
| break; |
| |
| default: |
| ty = entry->ld_literal_type; |
| break; |
| } |
| |
| if (ty == 0) |
| { |
| set_syntax_error |
| (_("this relocation modifier is not allowed on this " |
| "instruction")); |
| return FALSE; |
| } |
| |
| /* #:<reloc_op>: */ |
| if (! my_get_expression (exp, &p, GE_NO_PREFIX, 1)) |
| { |
| set_syntax_error (_("invalid relocation expression")); |
| return FALSE; |
| } |
| |
| /* #:<reloc_op>:<expr> */ |
| /* Record the relocation type. */ |
| inst.reloc.type = ty; |
| inst.reloc.pc_rel = entry->pc_rel; |
| } |
| else |
| { |
| |
| if (skip_past_char (&p, '=')) |
| /* =immediate; need to generate the literal in the literal pool. */ |
| inst.gen_lit_pool = 1; |
| |
| if (!my_get_expression (exp, &p, GE_NO_PREFIX, 1)) |
| { |
| set_syntax_error (_("invalid address")); |
| return FALSE; |
| } |
| } |
| |
| *str = p; |
| return TRUE; |
| } |
| |
| /* [ */ |
| |
| /* Accept SP and reject ZR */ |
| reg = aarch64_reg_parse_32_64 (&p, 0, 1, &isreg32, &isregzero); |
| if (reg == PARSE_FAIL || isreg32) |
| { |
| set_syntax_error (_(get_reg_expected_msg (REG_TYPE_R_64))); |
| return FALSE; |
| } |
| operand->addr.base_regno = reg; |
| |
| /* [Xn */ |
| if (skip_past_comma (&p)) |
| { |
| /* [Xn, */ |
| operand->addr.preind = 1; |
| |
| /* Reject SP and accept ZR */ |
| reg = aarch64_reg_parse_32_64 (&p, 1, 0, &isreg32, &isregzero); |
| if (reg != PARSE_FAIL) |
| { |
| /* [Xn,Rm */ |
| operand->addr.offset.regno = reg; |
| operand->addr.offset.is_reg = 1; |
| /* Shifted index. */ |
| if (skip_past_comma (&p)) |
| { |
| /* [Xn,Rm, */ |
| if (! parse_shift (&p, operand, SHIFTED_REG_OFFSET)) |
| /* Use the diagnostics set in parse_shift, so not set new |
| error message here. */ |
| return FALSE; |
| } |
| /* We only accept: |
| [base,Xm{,LSL #imm}] |
| [base,Xm,SXTX {#imm}] |
| [base,Wm,(S|U)XTW {#imm}] */ |
| if (operand->shifter.kind == AARCH64_MOD_NONE |
| || operand->shifter.kind == AARCH64_MOD_LSL |
| || operand->shifter.kind == AARCH64_MOD_SXTX) |
| { |
| if (isreg32) |
| { |
| set_syntax_error (_("invalid use of 32-bit register offset")); |
| return FALSE; |
| } |
| } |
| else if (!isreg32) |
| { |
| set_syntax_error (_("invalid use of 64-bit register offset")); |
| return FALSE; |
| } |
| } |
| else |
| { |
| /* [Xn,#:<reloc_op>:<symbol> */ |
| skip_past_char (&p, '#'); |
| if (reloc && skip_past_char (&p, ':')) |
| { |
| struct reloc_table_entry *entry; |
| |
| /* Try to parse a relocation modifier. Anything else is |
| an error. */ |
| if (!(entry = find_reloc_table_entry (&p))) |
| { |
| set_syntax_error (_("unknown relocation modifier")); |
| return FALSE; |
| } |
| |
| if (entry->ldst_type == 0) |
| { |
| set_syntax_error |
| (_("this relocation modifier is not allowed on this " |
| "instruction")); |
| return FALSE; |
| } |
| |
| /* [Xn,#:<reloc_op>: */ |
| /* We now have the group relocation table entry corresponding to |
| the name in the assembler source. Next, we parse the |
| expression. */ |
| if (! my_get_expression (exp, &p, GE_NO_PREFIX, 1)) |
| { |
| set_syntax_error (_("invalid relocation expression")); |
| return FALSE; |
| } |
| |
| /* [Xn,#:<reloc_op>:<expr> */ |
| /* Record the load/store relocation type. */ |
| inst.reloc.type = entry->ldst_type; |
| inst.reloc.pc_rel = entry->pc_rel; |
| } |
| else if (! my_get_expression (exp, &p, GE_OPT_PREFIX, 1)) |
| { |
| set_syntax_error (_("invalid expression in the address")); |
| return FALSE; |
| } |
| /* [Xn,<expr> */ |
| } |
| } |
| |
| if (! skip_past_char (&p, ']')) |
| { |
| set_syntax_error (_("']' expected")); |
| return FALSE; |
| } |
| |
| if (skip_past_char (&p, '!')) |
| { |
| if (operand->addr.preind && operand->addr.offset.is_reg) |
| { |
| set_syntax_error (_("register offset not allowed in pre-indexed " |
| "addressing mode")); |
| return FALSE; |
| } |
| /* [Xn]! */ |
| operand->addr.writeback = 1; |
| } |
| else if (skip_past_comma (&p)) |
| { |
| /* [Xn], */ |
| operand->addr.postind = 1; |
| operand->addr.writeback = 1; |
| |
| if (operand->addr.preind) |
| { |
| set_syntax_error (_("cannot combine pre- and post-indexing")); |
| return FALSE; |
| } |
| |
| if (accept_reg_post_index |
| && (reg = aarch64_reg_parse_32_64 (&p, 1, 1, &isreg32, |
| &isregzero)) != PARSE_FAIL) |
| { |
| /* [Xn],Xm */ |
| if (isreg32) |
| { |
| set_syntax_error (_("invalid 32-bit register offset")); |
| return FALSE; |
| } |
| operand->addr.offset.regno = reg; |
| operand->addr.offset.is_reg = 1; |
| } |
| else if (! my_get_expression (exp, &p, GE_OPT_PREFIX, 1)) |
| { |
| /* [Xn],#expr */ |
| set_syntax_error (_("invalid expression in the address")); |
| return FALSE; |
| } |
| } |
| |
| /* If at this point neither .preind nor .postind is set, we have a |
| bare [Rn]{!}; reject [Rn]! but accept [Rn] as a shorthand for [Rn,#0]. */ |
| if (operand->addr.preind == 0 && operand->addr.postind == 0) |
| { |
| if (operand->addr.writeback) |
| { |
| /* Reject [Rn]! */ |
| set_syntax_error (_("missing offset in the pre-indexed address")); |
| return FALSE; |
| } |
| operand->addr.preind = 1; |
| inst.reloc.exp.X_op = O_constant; |
| inst.reloc.exp.X_add_number = 0; |
| } |
| |
| *str = p; |
| return TRUE; |
| } |
| |
| /* Return TRUE on success; otherwise return FALSE. */ |
| static bfd_boolean |
| parse_address (char **str, aarch64_opnd_info *operand, |
| int accept_reg_post_index) |
| { |
| return parse_address_main (str, operand, 0, accept_reg_post_index); |
| } |
| |
| /* Return TRUE on success; otherwise return FALSE. */ |
| static bfd_boolean |
| parse_address_reloc (char **str, aarch64_opnd_info *operand) |
| { |
| return parse_address_main (str, operand, 1, 0); |
| } |
| |
| /* Parse an operand for a MOVZ, MOVN or MOVK instruction. |
| Return TRUE on success; otherwise return FALSE. */ |
| static bfd_boolean |
| parse_half (char **str, int *internal_fixup_p) |
| { |
| char *p = *str; |
| |
| skip_past_char (&p, '#'); |
| |
| gas_assert (internal_fixup_p); |
| *internal_fixup_p = 0; |
| |
| if (*p == ':') |
| { |
| struct reloc_table_entry *entry; |
| |
| /* Try to parse a relocation. Anything else is an error. */ |
| ++p; |
| if (!(entry = find_reloc_table_entry (&p))) |
| { |
| set_syntax_error (_("unknown relocation modifier")); |
| return FALSE; |
| } |
| |
| if (entry->movw_type == 0) |
| { |
| set_syntax_error |
| (_("this relocation modifier is not allowed on this instruction")); |
| return FALSE; |
| } |
| |
| inst.reloc.type = entry->movw_type; |
| } |
| else |
| *internal_fixup_p = 1; |
| |
| if (! my_get_expression (&inst.reloc.exp, &p, GE_NO_PREFIX, 1)) |
| return FALSE; |
| |
| *str = p; |
| return TRUE; |
| } |
| |
| /* Parse an operand for an ADRP instruction: |
| ADRP <Xd>, <label> |
| Return TRUE on success; otherwise return FALSE. */ |
| |
| static bfd_boolean |
| parse_adrp (char **str) |
| { |
| char *p; |
| |
| p = *str; |
| if (*p == ':') |
| { |
| struct reloc_table_entry *entry; |
| |
| /* Try to parse a relocation. Anything else is an error. */ |
| ++p; |
| if (!(entry = find_reloc_table_entry (&p))) |
| { |
| set_syntax_error (_("unknown relocation modifier")); |
| return FALSE; |
| } |
| |
| if (entry->adrp_type == 0) |
| { |
| set_syntax_error |
| (_("this relocation modifier is not allowed on this instruction")); |
| return FALSE; |
| } |
| |
| inst.reloc.type = entry->adrp_type; |
| } |
| else |
| inst.reloc.type = BFD_RELOC_AARCH64_ADR_HI21_PCREL; |
| |
| inst.reloc.pc_rel = 1; |
| |
| if (! my_get_expression (&inst.reloc.exp, &p, GE_NO_PREFIX, 1)) |
| return FALSE; |
| |
| *str = p; |
| return TRUE; |
| } |
| |
| /* Miscellaneous. */ |
| |
| /* Parse an option for a preload instruction. Returns the encoding for the |
| option, or PARSE_FAIL. */ |
| |
| static int |
| parse_pldop (char **str) |
| { |
| char *p, *q; |
| const struct aarch64_name_value_pair *o; |
| |
| p = q = *str; |
| while (ISALNUM (*q)) |
| q++; |
| |
| o = hash_find_n (aarch64_pldop_hsh, p, q - p); |
| if (!o) |
| return PARSE_FAIL; |
| |
| *str = q; |
| return o->value; |
| } |
| |
| /* Parse an option for a barrier instruction. Returns the encoding for the |
| option, or PARSE_FAIL. */ |
| |
| static int |
| parse_barrier (char **str) |
| { |
| char *p, *q; |
| const asm_barrier_opt *o; |
| |
| p = q = *str; |
| while (ISALPHA (*q)) |
| q++; |
| |
| o = hash_find_n (aarch64_barrier_opt_hsh, p, q - p); |
| if (!o) |
| return PARSE_FAIL; |
| |
| *str = q; |
| return o->value; |
| } |
| |
| /* Parse an operand for a PSB barrier. Set *HINT_OPT to the hint-option record |
| return 0 if successful. Otherwise return PARSE_FAIL. */ |
| |
| static int |
| parse_barrier_psb (char **str, |
| const struct aarch64_name_value_pair ** hint_opt) |
| { |
| char *p, *q; |
| const struct aarch64_name_value_pair *o; |
| |
| p = q = *str; |
| while (ISALPHA (*q)) |
| q++; |
| |
| o = hash_find_n (aarch64_hint_opt_hsh, p, q - p); |
| if (!o) |
| { |
| set_fatal_syntax_error |
| ( _("unknown or missing option to PSB")); |
| return PARSE_FAIL; |
| } |
| |
| if (o->value != 0x11) |
| { |
| /* PSB only accepts option name 'CSYNC'. */ |
| set_syntax_error |
| (_("the specified option is not accepted for PSB")); |
| return PARSE_FAIL; |
| } |
| |
| *str = q; |
| *hint_opt = o; |
| return 0; |
| } |
| |
| /* Parse a system register or a PSTATE field name for an MSR/MRS instruction. |
| Returns the encoding for the option, or PARSE_FAIL. |
| |
| If IMPLE_DEFINED_P is non-zero, the function will also try to parse the |
| implementation defined system register name S<op0>_<op1>_<Cn>_<Cm>_<op2>. |
| |
| If PSTATEFIELD_P is non-zero, the function will parse the name as a PSTATE |
| field, otherwise as a system register. |
| */ |
| |
| static int |
| parse_sys_reg (char **str, struct hash_control *sys_regs, |
| int imple_defined_p, int pstatefield_p) |
| { |
| char *p, *q; |
| char buf[32]; |
| const aarch64_sys_reg *o; |
| int value; |
| |
| p = buf; |
| for (q = *str; ISALNUM (*q) || *q == '_'; q++) |
| if (p < buf + 31) |
| *p++ = TOLOWER (*q); |
| *p = '\0'; |
| /* Assert that BUF be large enough. */ |
| gas_assert (p - buf == q - *str); |
| |
| o = hash_find (sys_regs, buf); |
| if (!o) |
| { |
| if (!imple_defined_p) |
| return PARSE_FAIL; |
| else |
| { |
| /* Parse S<op0>_<op1>_<Cn>_<Cm>_<op2>. */ |
| unsigned int op0, op1, cn, cm, op2; |
| |
| if (sscanf (buf, "s%u_%u_c%u_c%u_%u", &op0, &op1, &cn, &cm, &op2) |
| != 5) |
| return PARSE_FAIL; |
| if (op0 > 3 || op1 > 7 || cn > 15 || cm > 15 || op2 > 7) |
| return PARSE_FAIL; |
| value = (op0 << 14) | (op1 << 11) | (cn << 7) | (cm << 3) | op2; |
| } |
| } |
| else |
| { |
| if (pstatefield_p && !aarch64_pstatefield_supported_p (cpu_variant, o)) |
| as_bad (_("selected processor does not support PSTATE field " |
| "name '%s'"), buf); |
| if (!pstatefield_p && !aarch64_sys_reg_supported_p (cpu_variant, o)) |
| as_bad (_("selected processor does not support system register " |
| "name '%s'"), buf); |
| if (aarch64_sys_reg_deprecated_p (o)) |
| as_warn (_("system register name '%s' is deprecated and may be " |
| "removed in a future release"), buf); |
| value = o->value; |
| } |
| |
| *str = q; |
| return value; |
| } |
| |
| /* Parse a system reg for ic/dc/at/tlbi instructions. Returns the table entry |
| for the option, or NULL. */ |
| |
| static const aarch64_sys_ins_reg * |
| parse_sys_ins_reg (char **str, struct hash_control *sys_ins_regs) |
| { |
| char *p, *q; |
| char buf[32]; |
| const aarch64_sys_ins_reg *o; |
| |
| p = buf; |
| for (q = *str; ISALNUM (*q) || *q == '_'; q++) |
| if (p < buf + 31) |
| *p++ = TOLOWER (*q); |
| *p = '\0'; |
| |
| o = hash_find (sys_ins_regs, buf); |
| if (!o) |
| return NULL; |
| |
| if (!aarch64_sys_ins_reg_supported_p (cpu_variant, o)) |
| as_bad (_("selected processor does not support system register " |
| "name '%s'"), buf); |
| |
| *str = q; |
| return o; |
| } |
| |
| #define po_char_or_fail(chr) do { \ |
| if (! skip_past_char (&str, chr)) \ |
| goto failure; \ |
| } while (0) |
| |
| #define po_reg_or_fail(regtype) do { \ |
| val = aarch64_reg_parse (&str, regtype, &rtype, NULL); \ |
| if (val == PARSE_FAIL) \ |
| { \ |
| set_default_error (); \ |
| goto failure; \ |
| } \ |
| } while (0) |
| |
| #define po_int_reg_or_fail(reject_sp, reject_rz) do { \ |
| val = aarch64_reg_parse_32_64 (&str, reject_sp, reject_rz, \ |
| &isreg32, &isregzero); \ |
| if (val == PARSE_FAIL) \ |
| { \ |
| set_default_error (); \ |
| goto failure; \ |
| } \ |
| info->reg.regno = val; \ |
| if (isreg32) \ |
| info->qualifier = AARCH64_OPND_QLF_W; \ |
| else \ |
| info->qualifier = AARCH64_OPND_QLF_X; \ |
| } while (0) |
| |
| #define po_imm_nc_or_fail() do { \ |
| if (! parse_constant_immediate (&str, &val)) \ |
| goto failure; \ |
| } while (0) |
| |
| #define po_imm_or_fail(min, max) do { \ |
| if (! parse_constant_immediate (&str, &val)) \ |
| goto failure; \ |
| if (val < min || val > max) \ |
| { \ |
| set_fatal_syntax_error (_("immediate value out of range "\ |
| #min " to "#max)); \ |
| goto failure; \ |
| } \ |
| } while (0) |
| |
| #define po_misc_or_fail(expr) do { \ |
| if (!expr) \ |
| goto failure; \ |
| } while (0) |
| |
| /* encode the 12-bit imm field of Add/sub immediate */ |
| static inline uint32_t |
| encode_addsub_imm (uint32_t imm) |
| { |
| return imm << 10; |
| } |
| |
| /* encode the shift amount field of Add/sub immediate */ |
| static inline uint32_t |
| encode_addsub_imm_shift_amount (uint32_t cnt) |
| { |
| return cnt << 22; |
| } |
| |
| |
| /* encode the imm field of Adr instruction */ |
| static inline uint32_t |
| encode_adr_imm (uint32_t imm) |
| { |
| return (((imm & 0x3) << 29) /* [1:0] -> [30:29] */ |
| | ((imm & (0x7ffff << 2)) << 3)); /* [20:2] -> [23:5] */ |
| } |
| |
| /* encode the immediate field of Move wide immediate */ |
| static inline uint32_t |
| encode_movw_imm (uint32_t imm) |
| { |
| return imm << 5; |
| } |
| |
| /* encode the 26-bit offset of unconditional branch */ |
| static inline uint32_t |
| encode_branch_ofs_26 (uint32_t ofs) |
| { |
| return ofs & ((1 << 26) - 1); |
| } |
| |
| /* encode the 19-bit offset of conditional branch and compare & branch */ |
| static inline uint32_t |
| encode_cond_branch_ofs_19 (uint32_t ofs) |
| { |
| return (ofs & ((1 << 19) - 1)) << 5; |
| } |
| |
| /* encode the 19-bit offset of ld literal */ |
| static inline uint32_t |
| encode_ld_lit_ofs_19 (uint32_t ofs) |
| { |
| return (ofs & ((1 << 19) - 1)) << 5; |
| } |
| |
| /* Encode the 14-bit offset of test & branch. */ |
| static inline uint32_t |
| encode_tst_branch_ofs_14 (uint32_t ofs) |
| { |
| return (ofs & ((1 << 14) - 1)) << 5; |
| } |
| |
| /* Encode the 16-bit imm field of svc/hvc/smc. */ |
| static inline uint32_t |
| encode_svc_imm (uint32_t imm) |
| { |
| return imm << 5; |
| } |
| |
| /* Reencode add(s) to sub(s), or sub(s) to add(s). */ |
| static inline uint32_t |
| reencode_addsub_switch_add_sub (uint32_t opcode) |
| { |
| return opcode ^ (1 << 30); |
| } |
| |
| static inline uint32_t |
| reencode_movzn_to_movz (uint32_t opcode) |
| { |
| return opcode | (1 << 30); |
| } |
| |
| static inline uint32_t |
| reencode_movzn_to_movn (uint32_t opcode) |
| { |
| return opcode & ~(1 << 30); |
| } |
| |
| /* Overall per-instruction processing. */ |
| |
| /* We need to be able to fix up arbitrary expressions in some statements. |
| This is so that we can handle symbols that are an arbitrary distance from |
| the pc. The most common cases are of the form ((+/-sym -/+ . - 8) & mask), |
| which returns part of an address in a form which will be valid for |
| a data instruction. We do this by pushing the expression into a symbol |
| in the expr_section, and creating a fix for that. */ |
| |
| static fixS * |
| fix_new_aarch64 (fragS * frag, |
| int where, |
| short int size, expressionS * exp, int pc_rel, int reloc) |
| { |
| fixS *new_fix; |
| |
| switch (exp->X_op) |
| { |
| case O_constant: |
| case O_symbol: |
| case O_add: |
| case O_subtract: |
| new_fix = fix_new_exp (frag, where, size, exp, pc_rel, reloc); |
| break; |
| |
| default: |
| new_fix = fix_new (frag, where, size, make_expr_symbol (exp), 0, |
| pc_rel, reloc); |
| break; |
| } |
| return new_fix; |
| } |
| |
| /* Diagnostics on operands errors. */ |
| |
| /* By default, output verbose error message. |
| Disable the verbose error message by -mno-verbose-error. */ |
| static int verbose_error_p = 1; |
| |
| #ifdef DEBUG_AARCH64 |
| /* N.B. this is only for the purpose of debugging. */ |
| const char* operand_mismatch_kind_names[] = |
| { |
| "AARCH64_OPDE_NIL", |
| "AARCH64_OPDE_RECOVERABLE", |
| "AARCH64_OPDE_SYNTAX_ERROR", |
| "AARCH64_OPDE_FATAL_SYNTAX_ERROR", |
| "AARCH64_OPDE_INVALID_VARIANT", |
| "AARCH64_OPDE_OUT_OF_RANGE", |
| "AARCH64_OPDE_UNALIGNED", |
| "AARCH64_OPDE_REG_LIST", |
| "AARCH64_OPDE_OTHER_ERROR", |
| }; |
| #endif /* DEBUG_AARCH64 */ |
| |
| /* Return TRUE if LHS is of higher severity than RHS, otherwise return FALSE. |
| |
| When multiple errors of different kinds are found in the same assembly |
| line, only the error of the highest severity will be picked up for |
| issuing the diagnostics. */ |
| |
| static inline bfd_boolean |
| operand_error_higher_severity_p (enum aarch64_operand_error_kind lhs, |
| enum aarch64_operand_error_kind rhs) |
| { |
| gas_assert (AARCH64_OPDE_RECOVERABLE > AARCH64_OPDE_NIL); |
| gas_assert (AARCH64_OPDE_SYNTAX_ERROR > AARCH64_OPDE_RECOVERABLE); |
| gas_assert (AARCH64_OPDE_FATAL_SYNTAX_ERROR > AARCH64_OPDE_SYNTAX_ERROR); |
| gas_assert (AARCH64_OPDE_INVALID_VARIANT > AARCH64_OPDE_FATAL_SYNTAX_ERROR); |
| gas_assert (AARCH64_OPDE_OUT_OF_RANGE > AARCH64_OPDE_INVALID_VARIANT); |
| gas_assert (AARCH64_OPDE_UNALIGNED > AARCH64_OPDE_OUT_OF_RANGE); |
| gas_assert (AARCH64_OPDE_REG_LIST > AARCH64_OPDE_UNALIGNED); |
| gas_assert (AARCH64_OPDE_OTHER_ERROR > AARCH64_OPDE_REG_LIST); |
| return lhs > rhs; |
| } |
| |
| /* Helper routine to get the mnemonic name from the assembly instruction |
| line; should only be called for the diagnosis purpose, as there is |
| string copy operation involved, which may affect the runtime |
| performance if used in elsewhere. */ |
| |
| static const char* |
| get_mnemonic_name (const char *str) |
| { |
| static char mnemonic[32]; |
| char *ptr; |
| |
| /* Get the first 15 bytes and assume that the full name is included. */ |
| strncpy (mnemonic, str, 31); |
| mnemonic[31] = '\0'; |
| |
| /* Scan up to the end of the mnemonic, which must end in white space, |
| '.', or end of string. */ |
| for (ptr = mnemonic; is_part_of_name(*ptr); ++ptr) |
| ; |
| |
| *ptr = '\0'; |
| |
| /* Append '...' to the truncated long name. */ |
| if (ptr - mnemonic == 31) |
| mnemonic[28] = mnemonic[29] = mnemonic[30] = '.'; |
| |
| return mnemonic; |
| } |
| |
| static void |
| reset_aarch64_instruction (aarch64_instruction *instruction) |
| { |
| memset (instruction, '\0', sizeof (aarch64_instruction)); |
| instruction->reloc.type = BFD_RELOC_UNUSED; |
| } |
| |
| /* Data strutures storing one user error in the assembly code related to |
| operands. */ |
| |
| struct operand_error_record |
| { |
| const aarch64_opcode *opcode; |
| aarch64_operand_error detail; |
| struct operand_error_record *next; |
| }; |
| |
| typedef struct operand_error_record operand_error_record; |
| |
| struct operand_errors |
| { |
| operand_error_record *head; |
| operand_error_record *tail; |
| }; |
| |
| typedef struct operand_errors operand_errors; |
| |
| /* Top-level data structure reporting user errors for the current line of |
| the assembly code. |
| The way md_assemble works is that all opcodes sharing the same mnemonic |
| name are iterated to find a match to the assembly line. In this data |
| structure, each of the such opcodes will have one operand_error_record |
| allocated and inserted. In other words, excessive errors related with |
| a single opcode are disregarded. */ |
| operand_errors operand_error_report; |
| |
| /* Free record nodes. */ |
| static operand_error_record *free_opnd_error_record_nodes = NULL; |
| |
| /* Initialize the data structure that stores the operand mismatch |
| information on assembling one line of the assembly code. */ |
| static void |
| init_operand_error_report (void) |
| { |
| if (operand_error_report.head != NULL) |
| { |
| gas_assert (operand_error_report.tail != NULL); |
| operand_error_report.tail->next = free_opnd_error_record_nodes; |
| free_opnd_error_record_nodes = operand_error_report.head; |
| operand_error_report.head = NULL; |
| operand_error_report.tail = NULL; |
| return; |
| } |
| gas_assert (operand_error_report.tail == NULL); |
| } |
| |
| /* Return TRUE if some operand error has been recorded during the |
| parsing of the current assembly line using the opcode *OPCODE; |
| otherwise return FALSE. */ |
| static inline bfd_boolean |
| opcode_has_operand_error_p (const aarch64_opcode *opcode) |
| { |
| operand_error_record *record = operand_error_report.head; |
| return record && record->opcode == opcode; |
| } |
| |
| /* Add the error record *NEW_RECORD to operand_error_report. The record's |
| OPCODE field is initialized with OPCODE. |
| N.B. only one record for each opcode, i.e. the maximum of one error is |
| recorded for each instruction template. */ |
| |
| static void |
| add_operand_error_record (const operand_error_record* new_record) |
| { |
| const aarch64_opcode *opcode = new_record->opcode; |
| operand_error_record* record = operand_error_report.head; |
| |
| /* The record may have been created for this opcode. If not, we need |
| to prepare one. */ |
| if (! opcode_has_operand_error_p (opcode)) |
| { |
| /* Get one empty record. */ |
| if (free_opnd_error_record_nodes == NULL) |
| { |
| record = XNEW (operand_error_record); |
| } |
| else |
| { |
| record = free_opnd_error_record_nodes; |
| free_opnd_error_record_nodes = record->next; |
| } |
| record->opcode = opcode; |
| /* Insert at the head. */ |
| record->next = operand_error_report.head; |
| operand_error_report.head = record; |
| if (operand_error_report.tail == NULL) |
| operand_error_report.tail = record; |
| } |
| else if (record->detail.kind != AARCH64_OPDE_NIL |
| && record->detail.index <= new_record->detail.index |
| && operand_error_higher_severity_p (record->detail.kind, |
| new_record->detail.kind)) |
| { |
| /* In the case of multiple errors found on operands related with a |
| single opcode, only record the error of the leftmost operand and |
| only if the error is of higher severity. */ |
| DEBUG_TRACE ("error %s on operand %d not added to the report due to" |
| " the existing error %s on operand %d", |
| operand_mismatch_kind_names[new_record->detail.kind], |
| new_record->detail.index, |
| operand_mismatch_kind_names[record->detail.kind], |
| record->detail.index); |
| return; |
| } |
| |
| record->detail = new_record->detail; |
| } |
| |
| static inline void |
| record_operand_error_info (const aarch64_opcode *opcode, |
| aarch64_operand_error *error_info) |
| { |
| operand_error_record record; |
| record.opcode = opcode; |
| record.detail = *error_info; |
| add_operand_error_record (&record); |
| } |
| |
| /* Record an error of kind KIND and, if ERROR is not NULL, of the detailed |
| error message *ERROR, for operand IDX (count from 0). */ |
| |
| static void |
| record_operand_error (const aarch64_opcode *opcode, int idx, |
| enum aarch64_operand_error_kind kind, |
| const char* error) |
| { |
| aarch64_operand_error info; |
| memset(&info, 0, sizeof (info)); |
| info.index = idx; |
| info.kind = kind; |
| info.error = error; |
| record_operand_error_info (opcode, &info); |
| } |
| |
| static void |
| record_operand_error_with_data (const aarch64_opcode *opcode, int idx, |
| enum aarch64_operand_error_kind kind, |
| const char* error, const int *extra_data) |
| { |
| aarch64_operand_error info; |
| info.index = idx; |
| info.kind = kind; |
| info.error = error; |
| info.data[0] = extra_data[0]; |
| info.data[1] = extra_data[1]; |
| info.data[2] = extra_data[2]; |
| record_operand_error_info (opcode, &info); |
| } |
| |
| static void |
| record_operand_out_of_range_error (const aarch64_opcode *opcode, int idx, |
| const char* error, int lower_bound, |
| int upper_bound) |
| { |
| int data[3] = {lower_bound, upper_bound, 0}; |
| record_operand_error_with_data (opcode, idx, AARCH64_OPDE_OUT_OF_RANGE, |
| error, data); |
| } |
| |
| /* Remove the operand error record for *OPCODE. */ |
| static void ATTRIBUTE_UNUSED |
| remove_operand_error_record (const aarch64_opcode *opcode) |
| { |
| if (opcode_has_operand_error_p (opcode)) |
| { |
| operand_error_record* record = operand_error_report.head; |
| gas_assert (record != NULL && operand_error_report.tail != NULL); |
| operand_error_report.head = record->next; |
| record->next = free_opnd_error_record_nodes; |
| free_opnd_error_record_nodes = record; |
| if (operand_error_report.head == NULL) |
| { |
| gas_assert (operand_error_report.tail == record); |
| operand_error_report.tail = NULL; |
| } |
| } |
| } |
| |
| /* Given the instruction in *INSTR, return the index of the best matched |
| qualifier sequence in the list (an array) headed by QUALIFIERS_LIST. |
| |
| Return -1 if there is no qualifier sequence; return the first match |
| if there is multiple matches found. */ |
| |
| static int |
| find_best_match (const aarch64_inst *instr, |
| const aarch64_opnd_qualifier_seq_t *qualifiers_list) |
| { |
| int i, num_opnds, max_num_matched, idx; |
| |
| num_opnds = aarch64_num_of_operands (instr->opcode); |
| if (num_opnds == 0) |
| { |
| DEBUG_TRACE ("no operand"); |
| return -1; |
| } |
| |
| max_num_matched = 0; |
| idx = -1; |
| |
| /* For each pattern. */ |
| for (i = 0; i < AARCH64_MAX_QLF_SEQ_NUM; ++i, ++qualifiers_list) |
| { |
| int j, num_matched; |
| const aarch64_opnd_qualifier_t *qualifiers = *qualifiers_list; |
| |
| /* Most opcodes has much fewer patterns in the list. */ |
| if (empty_qualifier_sequence_p (qualifiers) == TRUE) |
| { |
| DEBUG_TRACE_IF (i == 0, "empty list of qualifier sequence"); |
| if (i != 0 && idx == -1) |
| /* If nothing has been matched, return the 1st sequence. */ |
| idx = 0; |
| break; |
| } |
| |
| for (j = 0, num_matched = 0; j < num_opnds; ++j, ++qualifiers) |
| if (*qualifiers == instr->operands[j].qualifier) |
| ++num_matched; |
| |
| if (num_matched > max_num_matched) |
| { |
| max_num_matched = num_matched; |
| idx = i; |
| } |
| } |
| |
| DEBUG_TRACE ("return with %d", idx); |
| return idx; |
| } |
| |
| /* Assign qualifiers in the qualifier seqence (headed by QUALIFIERS) to the |
| corresponding operands in *INSTR. */ |
| |
| static inline void |
| assign_qualifier_sequence (aarch64_inst *instr, |
| const aarch64_opnd_qualifier_t *qualifiers) |
| { |
| int i = 0; |
| int num_opnds = aarch64_num_of_operands (instr->opcode); |
| gas_assert (num_opnds); |
| for (i = 0; i < num_opnds; ++i, ++qualifiers) |
| instr->operands[i].qualifier = *qualifiers; |
| } |
| |
| /* Print operands for the diagnosis purpose. */ |
| |
| static void |
| print_operands (char *buf, const aarch64_opcode *opcode, |
| const aarch64_opnd_info *opnds) |
| { |
| int i; |
| |
| for (i = 0; i < AARCH64_MAX_OPND_NUM; ++i) |
| { |
| char str[128]; |
| |
| /* We regard the opcode operand info more, however we also look into |
| the inst->operands to support the disassembling of the optional |
| operand. |
| The two operand code should be the same in all cases, apart from |
| when the operand can be optional. */ |
| if (opcode->operands[i] == AARCH64_OPND_NIL |
| || opnds[i].type == AARCH64_OPND_NIL) |
| break; |
| |
| /* Generate the operand string in STR. */ |
| aarch64_print_operand (str, sizeof (str), 0, opcode, opnds, i, NULL, NULL); |
| |
| /* Delimiter. */ |
| if (str[0] != '\0') |
| strcat (buf, i == 0 ? " " : ","); |
| |
| /* Append the operand string. */ |
| strcat (buf, str); |
| } |
| } |
| |
| /* Send to stderr a string as information. */ |
| |
| static void |
| output_info (const char *format, ...) |
| { |
| const char *file; |
| unsigned int line; |
| va_list args; |
| |
| file = as_where (&line); |
| if (file) |
| { |
| if (line != 0) |
| fprintf (stderr, "%s:%u: ", file, line); |
| else |
| fprintf (stderr, "%s: ", file); |
| } |
| fprintf (stderr, _("Info: ")); |
| va_start (args, format); |
| vfprintf (stderr, format, args); |
| va_end (args); |
| (void) putc ('\n', stderr); |
| } |
| |
| /* Output one operand error record. */ |
| |
| static void |
| output_operand_error_record (const operand_error_record *record, char *str) |
| { |
| const aarch64_operand_error *detail = &record->detail; |
| int idx = detail->index; |
| const aarch64_opcode *opcode = record->opcode; |
| enum aarch64_opnd opd_code = (idx >= 0 ? opcode->operands[idx] |
| : AARCH64_OPND_NIL); |
| |
| switch (detail->kind) |
| { |
| case AARCH64_OPDE_NIL: |
| gas_assert (0); |
| break; |
| |
| case AARCH64_OPDE_SYNTAX_ERROR: |
| case AARCH64_OPDE_RECOVERABLE: |
| case AARCH64_OPDE_FATAL_SYNTAX_ERROR: |
| case AARCH64_OPDE_OTHER_ERROR: |
| /* Use the prepared error message if there is, otherwise use the |
| operand description string to describe the error. */ |
| if (detail->error != NULL) |
| { |
| if (idx < 0) |
| as_bad (_("%s -- `%s'"), detail->error, str); |
| else |
| as_bad (_("%s at operand %d -- `%s'"), |
| detail->error, idx + 1, str); |
| } |
| else |
| { |
| gas_assert (idx >= 0); |
| as_bad (_("operand %d should be %s -- `%s'"), idx + 1, |
| aarch64_get_operand_desc (opd_code), str); |
| } |
| break; |
| |
| case AARCH64_OPDE_INVALID_VARIANT: |
| as_bad (_("operand mismatch -- `%s'"), str); |
| if (verbose_error_p) |
| { |
| /* We will try to correct the erroneous instruction and also provide |
| more information e.g. all other valid variants. |
| |
| The string representation of the corrected instruction and other |
| valid variants are generated by |
| |
| 1) obtaining the intermediate representation of the erroneous |
| instruction; |
| 2) manipulating the IR, e.g. replacing the operand qualifier; |
| 3) printing out the instruction by calling the printer functions |
| shared with the disassembler. |
| |
| The limitation of this method is that the exact input assembly |
| line cannot be accurately reproduced in some cases, for example an |
| optional operand present in the actual assembly line will be |
| omitted in the output; likewise for the optional syntax rules, |
| e.g. the # before the immediate. Another limitation is that the |
| assembly symbols and relocation operations in the assembly line |
| currently cannot be printed out in the error report. Last but not |
| least, when there is other error(s) co-exist with this error, the |
| 'corrected' instruction may be still incorrect, e.g. given |
| 'ldnp h0,h1,[x0,#6]!' |
| this diagnosis will provide the version: |
| 'ldnp s0,s1,[x0,#6]!' |
| which is still not right. */ |
| size_t len = strlen (get_mnemonic_name (str)); |
| int i, qlf_idx; |
| bfd_boolean result; |
| char buf[2048]; |
| aarch64_inst *inst_base = &inst.base; |
| const aarch64_opnd_qualifier_seq_t *qualifiers_list; |
| |
| /* Init inst. */ |
| reset_aarch64_instruction (&inst); |
| inst_base->opcode = opcode; |
| |
| /* Reset the error report so that there is no side effect on the |
| following operand parsing. */ |
| init_operand_error_report (); |
| |
| /* Fill inst. */ |
| result = parse_operands (str + len, opcode) |
| && programmer_friendly_fixup (&inst); |
| gas_assert (result); |
| result = aarch64_opcode_encode (opcode, inst_base, &inst_base->value, |
| NULL, NULL); |
| gas_assert (!result); |
| |
| /* Find the most matched qualifier sequence. */ |
| qlf_idx = find_best_match (inst_base, opcode->qualifiers_list); |
| gas_assert (qlf_idx > -1); |
| |
| /* Assign the qualifiers. */ |
| assign_qualifier_sequence (inst_base, |
| opcode->qualifiers_list[qlf_idx]); |
| |
| /* Print the hint. */ |
| output_info (_(" did you mean this?")); |
| snprintf (buf, sizeof (buf), "\t%s", get_mnemonic_name (str)); |
| print_operands (buf, opcode, inst_base->operands); |
| output_info (_(" %s"), buf); |
| |
| /* Print out other variant(s) if there is any. */ |
| if (qlf_idx != 0 || |
| !empty_qualifier_sequence_p (opcode->qualifiers_list[1])) |
| output_info (_(" other valid variant(s):")); |
| |
| /* For each pattern. */ |
| qualifiers_list = opcode->qualifiers_list; |
| for (i = 0; i < AARCH64_MAX_QLF_SEQ_NUM; ++i, ++qualifiers_list) |
| { |
| /* Most opcodes has much fewer patterns in the list. |
| First NIL qualifier indicates the end in the list. */ |
| if (empty_qualifier_sequence_p (*qualifiers_list) == TRUE) |
| break; |
| |
| if (i != qlf_idx) |
| { |
| /* Mnemonics name. */ |
| snprintf (buf, sizeof (buf), "\t%s", get_mnemonic_name (str)); |
| |
| /* Assign the qualifiers. */ |
| assign_qualifier_sequence (inst_base, *qualifiers_list); |
| |
| /* Print instruction. */ |
| print_operands (buf, opcode, inst_base->operands); |
| |
| output_info (_(" %s"), buf); |
| } |
| } |
| } |
| break; |
| |
| case AARCH64_OPDE_OUT_OF_RANGE: |
| if (detail->data[0] != detail->data[1]) |
| as_bad (_("%s out of range %d to %d at operand %d -- `%s'"), |
| detail->error ? detail->error : _("immediate value"), |
| detail->data[0], detail->data[1], idx + 1, str); |
| else |
| as_bad (_("%s expected to be %d at operand %d -- `%s'"), |
| detail->error ? detail->error : _("immediate value"), |
| detail->data[0], idx + 1, str); |
| break; |
| |
| case AARCH64_OPDE_REG_LIST: |
| if (detail->data[0] == 1) |
| as_bad (_("invalid number of registers in the list; " |
| "only 1 register is expected at operand %d -- `%s'"), |
| idx + 1, str); |
| else |
| as_bad (_("invalid number of registers in the list; " |
| "%d registers are expected at operand %d -- `%s'"), |
| detail->data[0], idx + 1, str); |
| break; |
| |
| case AARCH64_OPDE_UNALIGNED: |
| as_bad (_("immediate value should be a multiple of " |
| "%d at operand %d -- `%s'"), |
| detail->data[0], idx + 1, str); |
| break; |
| |
| default: |
| gas_assert (0); |
| break; |
| } |
| } |
| |
| /* Process and output the error message about the operand mismatching. |
| |
| When this function is called, the operand error information had |
| been collected for an assembly line and there will be multiple |
| errors in the case of mulitple instruction templates; output the |
| error message that most closely describes the problem. */ |
| |
| static void |
| output_operand_error_report (char *str) |
| { |
| int largest_error_pos; |
| const char *msg = NULL; |
| enum aarch64_operand_error_kind kind; |
| operand_error_record *curr; |
| operand_error_record *head = operand_error_report.head; |
| operand_error_record *record = NULL; |
| |
| /* No error to report. */ |
| if (head == NULL) |
| return; |
| |
| gas_assert (head != NULL && operand_error_report.tail != NULL); |
| |
| /* Only one error. */ |
| if (head == operand_error_report.tail) |
| { |
| DEBUG_TRACE ("single opcode entry with error kind: %s", |
| operand_mismatch_kind_names[head->detail.kind]); |
| output_operand_error_record (head, str); |
| return; |
| } |
| |
| /* Find the error kind of the highest severity. */ |
| DEBUG_TRACE ("multiple opcode entres with error kind"); |
| kind = AARCH64_OPDE_NIL; |
| for (curr = head; curr != NULL; curr = curr->next) |
| { |
| gas_assert (curr->detail.kind != AARCH64_OPDE_NIL); |
| DEBUG_TRACE ("\t%s", operand_mismatch_kind_names[curr->detail.kind]); |
| if (operand_error_higher_severity_p (curr->detail.kind, kind)) |
| kind = curr->detail.kind; |
| } |
| gas_assert (kind != AARCH64_OPDE_NIL); |
| |
| /* Pick up one of errors of KIND to report. */ |
| largest_error_pos = -2; /* Index can be -1 which means unknown index. */ |
| for (curr = head; curr != NULL; curr = curr->next) |
| { |
| if (curr->detail.kind != kind) |
| continue; |
| /* If there are multiple errors, pick up the one with the highest |
| mismatching operand index. In the case of multiple errors with |
| the equally highest operand index, pick up the first one or the |
| first one with non-NULL error message. */ |
| if (curr->detail.index > largest_error_pos |
| || (curr->detail.index == largest_error_pos && msg == NULL |
| && curr->detail.error != NULL)) |
| { |
| largest_error_pos = curr->detail.index; |
| record = curr; |
| msg = record->detail.error; |
| } |
| } |
| |
| gas_assert (largest_error_pos != -2 && record != NULL); |
| DEBUG_TRACE ("Pick up error kind %s to report", |
| operand_mismatch_kind_names[record->detail.kind]); |
| |
| /* Output. */ |
| output_operand_error_record (record, str); |
| } |
| |
| /* Write an AARCH64 instruction to buf - always little-endian. */ |
| static void |
| put_aarch64_insn (char *buf, uint32_t insn) |
| { |
| unsigned char *where = (unsigned char *) buf; |
| where[0] = insn; |
| where[1] = insn >> 8; |
| where[2] = insn >> 16; |
| where[3] = insn >> 24; |
| } |
| |
| static uint32_t |
| get_aarch64_insn (char *buf) |
| { |
| unsigned char *where = (unsigned char *) buf; |
| uint32_t result; |
| result = (where[0] | (where[1] << 8) | (where[2] << 16) | (where[3] << 24)); |
| return result; |
| } |
| |
| static void |
| output_inst (struct aarch64_inst *new_inst) |
| { |
| char *to = NULL; |
| |
| to = frag_more (INSN_SIZE); |
| |
| frag_now->tc_frag_data.recorded = 1; |
| |
| put_aarch64_insn (to, inst.base.value); |
| |
| if (inst.reloc.type != BFD_RELOC_UNUSED) |
| { |
| fixS *fixp = fix_new_aarch64 (frag_now, to - frag_now->fr_literal, |
| INSN_SIZE, &inst.reloc.exp, |
| inst.reloc.pc_rel, |
| inst.reloc.type); |
| DEBUG_TRACE ("Prepared relocation fix up"); |
| /* Don't check the addend value against the instruction size, |
| that's the job of our code in md_apply_fix(). */ |
| fixp->fx_no_overflow = 1; |
| if (new_inst != NULL) |
| fixp->tc_fix_data.inst = new_inst; |
| if (aarch64_gas_internal_fixup_p ()) |
| { |
| gas_assert (inst.reloc.opnd != AARCH64_OPND_NIL); |
| fixp->tc_fix_data.opnd = inst.reloc.opnd; |
| fixp->fx_addnumber = inst.reloc.flags; |
| } |
| } |
| |
| dwarf2_emit_insn (INSN_SIZE); |
| } |
| |
| /* Link together opcodes of the same name. */ |
| |
| struct templates |
| { |
| aarch64_opcode *opcode; |
| struct templates *next; |
| }; |
| |
| typedef struct templates templates; |
| |
| static templates * |
| lookup_mnemonic (const char *start, int len) |
| { |
| templates *templ = NULL; |
| |
| templ = hash_find_n (aarch64_ops_hsh, start, len); |
| return templ; |
| } |
| |
| /* Subroutine of md_assemble, responsible for looking up the primary |
| opcode from the mnemonic the user wrote. STR points to the |
| beginning of the mnemonic. */ |
| |
| static templates * |
| opcode_lookup (char **str) |
| { |
| char *end, *base; |
| const aarch64_cond *cond; |
| char condname[16]; |
| int len; |
| |
| /* Scan up to the end of the mnemonic, which must end in white space, |
| '.', or end of string. */ |
| for (base = end = *str; is_part_of_name(*end); end++) |
| if (*end == '.') |
| break; |
| |
| if (end == base) |
| return 0; |
| |
| inst.cond = COND_ALWAYS; |
| |
| /* Handle a possible condition. */ |
| if (end[0] == '.') |
| { |
| cond = hash_find_n (aarch64_cond_hsh, end + 1, 2); |
| if (cond) |
| { |
| inst.cond = cond->value; |
| *str = end + 3; |
| } |
| else |
| { |
| *str = end; |
| return 0; |
| } |
| } |
| else |
| *str = end; |
| |
| len = end - base; |
| |
| if (inst.cond == COND_ALWAYS) |
| { |
| /* Look for unaffixed mnemonic. */ |
| return lookup_mnemonic (base, len); |
| } |
| else if (len <= 13) |
| { |
| /* append ".c" to mnemonic if conditional */ |
| memcpy (condname, base, len); |
| memcpy (condname + len, ".c", 2); |
| base = condname; |
| len += 2; |
| return lookup_mnemonic (base, len); |
| } |
| |
| return NULL; |
| } |
| |
| /* Internal helper routine converting a vector neon_type_el structure |
| *VECTYPE to a corresponding operand qualifier. */ |
| |
| static inline aarch64_opnd_qualifier_t |
| vectype_to_qualifier (const struct neon_type_el *vectype) |
| { |
| /* Element size in bytes indexed by neon_el_type. */ |
| const unsigned char ele_size[5] |
| = {1, 2, 4, 8, 16}; |
| const unsigned int ele_base [5] = |
| { |
| AARCH64_OPND_QLF_V_8B, |
| AARCH64_OPND_QLF_V_2H, |
| AARCH64_OPND_QLF_V_2S, |
| AARCH64_OPND_QLF_V_1D, |
| AARCH64_OPND_QLF_V_1Q |
| }; |
| |
| if (!vectype->defined || vectype->type == NT_invtype) |
| goto vectype_conversion_fail; |
| |
| gas_assert (vectype->type >= NT_b && vectype->type <= NT_q); |
| |
| if (vectype->defined & NTA_HASINDEX) |
| /* Vector element register. */ |
| return AARCH64_OPND_QLF_S_B + vectype->type; |
| else |
| { |
| /* Vector register. */ |
| int reg_size = ele_size[vectype->type] * vectype->width; |
| unsigned offset; |
| unsigned shift; |
| if (reg_size != 16 && reg_size != 8 && reg_size != 4) |
| goto vectype_conversion_fail; |
| |
| /* The conversion is by calculating the offset from the base operand |
| qualifier for the vector type. The operand qualifiers are regular |
| enough that the offset can established by shifting the vector width by |
| a vector-type dependent amount. */ |
| shift = 0; |
| if (vectype->type == NT_b) |
| shift = 4; |
| else if (vectype->type == NT_h || vectype->type == NT_s) |
| shift = 2; |
| else if (vectype->type >= NT_d) |
| shift = 1; |
| else |
| gas_assert (0); |
| |
| offset = ele_base [vectype->type] + (vectype->width >> shift); |
| gas_assert (AARCH64_OPND_QLF_V_8B <= offset |
| && offset <= AARCH64_OPND_QLF_V_1Q); |
| return offset; |
| } |
| |
| vectype_conversion_fail: |
| first_error (_("bad vector arrangement type")); |
| return AARCH64_OPND_QLF_NIL; |
| } |
| |
| /* Process an optional operand that is found omitted from the assembly line. |
| Fill *OPERAND for such an operand of type TYPE. OPCODE points to the |
| instruction's opcode entry while IDX is the index of this omitted operand. |
| */ |
| |
| static void |
| process_omitted_operand (enum aarch64_opnd type, const aarch64_opcode *opcode, |
| int idx, aarch64_opnd_info *operand) |
| { |
| aarch64_insn default_value = get_optional_operand_default_value (opcode); |
| gas_assert (optional_operand_p (opcode, idx)); |
| gas_assert (!operand->present); |
| |
| switch (type) |
| { |
| case AARCH64_OPND_Rd: |
| case AARCH64_OPND_Rn: |
| case AARCH64_OPND_Rm: |
| case AARCH64_OPND_Rt: |
| case AARCH64_OPND_Rt2: |
| case AARCH64_OPND_Rs: |
| case AARCH64_OPND_Ra: |
| case AARCH64_OPND_Rt_SYS: |
| case AARCH64_OPND_Rd_SP: |
| case AARCH64_OPND_Rn_SP: |
| case AARCH64_OPND_Fd: |
| case AARCH64_OPND_Fn: |
| case AARCH64_OPND_Fm: |
| case AARCH64_OPND_Fa: |
| case AARCH64_OPND_Ft: |
| case AARCH64_OPND_Ft2: |
| case AARCH64_OPND_Sd: |
| case AARCH64_OPND_Sn: |
| case AARCH64_OPND_Sm: |
| case AARCH64_OPND_Vd: |
| case AARCH64_OPND_Vn: |
| case AARCH64_OPND_Vm: |
| case AARCH64_OPND_VdD1: |
| case AARCH64_OPND_VnD1: |
| operand->reg.regno = default_value; |
| break; |
| |
| case AARCH64_OPND_Ed: |
| case AARCH64_OPND_En: |
| case AARCH64_OPND_Em: |
| operand->reglane.regno = default_value; |
| break; |
| |
| case AARCH64_OPND_IDX: |
| case AARCH64_OPND_BIT_NUM: |
| case AARCH64_OPND_IMMR: |
| case AARCH64_OPND_IMMS: |
| case AARCH64_OPND_SHLL_IMM: |
| case AARCH64_OPND_IMM_VLSL: |
| case AARCH64_OPND_IMM_VLSR: |
| case AARCH64_OPND_CCMP_IMM: |
| case AARCH64_OPND_FBITS: |
| case AARCH64_OPND_UIMM4: |
| case AARCH64_OPND_UIMM3_OP1: |
| case AARCH64_OPND_UIMM3_OP2: |
| case AARCH64_OPND_IMM: |
| case AARCH64_OPND_WIDTH: |
| case AARCH64_OPND_UIMM7: |
| case AARCH64_OPND_NZCV: |
| operand->imm.value = default_value; |
| break; |
| |
| case AARCH64_OPND_EXCEPTION: |
| inst.reloc.type = BFD_RELOC_UNUSED; |
| break; |
| |
| case AARCH64_OPND_BARRIER_ISB: |
| operand->barrier = aarch64_barrier_options + default_value; |
| |
| default: |
| break; |
| } |
| } |
| |
| /* Process the relocation type for move wide instructions. |
| Return TRUE on success; otherwise return FALSE. */ |
| |
| static bfd_boolean |
| process_movw_reloc_info (void) |
| { |
| int is32; |
| unsigned shift; |
| |
| is32 = inst.base.operands[0].qualifier == AARCH64_OPND_QLF_W ? 1 : 0; |
| |
| if (inst.base.opcode->op == OP_MOVK) |
| switch (inst.reloc.type) |
| { |
| case BFD_RELOC_AARCH64_MOVW_G0_S: |
| case BFD_RELOC_AARCH64_MOVW_G1_S: |
| case BFD_RELOC_AARCH64_MOVW_G2_S: |
| case BFD_RELOC_AARCH64_TLSGD_MOVW_G1: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2: |
| set_syntax_error |
| (_("the specified relocation type is not allowed for MOVK")); |
| return FALSE; |
| default: |
| break; |
| } |
| |
| switch (inst.reloc.type) |
| { |
| case BFD_RELOC_AARCH64_MOVW_G0: |
| case BFD_RELOC_AARCH64_MOVW_G0_NC: |
| case BFD_RELOC_AARCH64_MOVW_G0_S: |
| case BFD_RELOC_AARCH64_MOVW_GOTOFF_G0_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC: |
| case BFD_RELOC_AARCH64_TLSGD_MOVW_G0_NC: |
| case BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0_NC: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0_NC: |
| shift = 0; |
| break; |
| case BFD_RELOC_AARCH64_MOVW_G1: |
| case BFD_RELOC_AARCH64_MOVW_G1_NC: |
| case BFD_RELOC_AARCH64_MOVW_G1_S: |
| case BFD_RELOC_AARCH64_MOVW_GOTOFF_G1: |
| case BFD_RELOC_AARCH64_TLSDESC_OFF_G1: |
| case BFD_RELOC_AARCH64_TLSGD_MOVW_G1: |
| case BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1_NC: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1_NC: |
| shift = 16; |
| break; |
| case BFD_RELOC_AARCH64_MOVW_G2: |
| case BFD_RELOC_AARCH64_MOVW_G2_NC: |
| case BFD_RELOC_AARCH64_MOVW_G2_S: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G2: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2: |
| if (is32) |
| { |
| set_fatal_syntax_error |
| (_("the specified relocation type is not allowed for 32-bit " |
| "register")); |
| return FALSE; |
| } |
| shift = 32; |
| break; |
| case BFD_RELOC_AARCH64_MOVW_G3: |
| if (is32) |
| { |
| set_fatal_syntax_error |
| (_("the specified relocation type is not allowed for 32-bit " |
| "register")); |
| return FALSE; |
| } |
| shift = 48; |
| break; |
| default: |
| /* More cases should be added when more MOVW-related relocation types |
| are supported in GAS. */ |
| gas_assert (aarch64_gas_internal_fixup_p ()); |
| /* The shift amount should have already been set by the parser. */ |
| return TRUE; |
| } |
| inst.base.operands[1].shifter.amount = shift; |
| return TRUE; |
| } |
| |
| /* A primitive log caculator. */ |
| |
| static inline unsigned int |
| get_logsz (unsigned int size) |
| { |
| const unsigned char ls[16] = |
| {0, 1, -1, 2, -1, -1, -1, 3, -1, -1, -1, -1, -1, -1, -1, 4}; |
| if (size > 16) |
| { |
| gas_assert (0); |
| return -1; |
| } |
| gas_assert (ls[size - 1] != (unsigned char)-1); |
| return ls[size - 1]; |
| } |
| |
| /* Determine and return the real reloc type code for an instruction |
| with the pseudo reloc type code BFD_RELOC_AARCH64_LDST_LO12. */ |
| |
| static inline bfd_reloc_code_real_type |
| ldst_lo12_determine_real_reloc_type (void) |
| { |
| unsigned logsz; |
| enum aarch64_opnd_qualifier opd0_qlf = inst.base.operands[0].qualifier; |
| enum aarch64_opnd_qualifier opd1_qlf = inst.base.operands[1].qualifier; |
| |
| const bfd_reloc_code_real_type reloc_ldst_lo12[3][5] = { |
| { |
| BFD_RELOC_AARCH64_LDST8_LO12, |
| BFD_RELOC_AARCH64_LDST16_LO12, |
| BFD_RELOC_AARCH64_LDST32_LO12, |
| BFD_RELOC_AARCH64_LDST64_LO12, |
| BFD_RELOC_AARCH64_LDST128_LO12 |
| }, |
| { |
| BFD_RELOC_AARCH64_TLSLD_LDST8_DTPREL_LO12, |
| BFD_RELOC_AARCH64_TLSLD_LDST16_DTPREL_LO12, |
| BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12, |
| BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12, |
| BFD_RELOC_AARCH64_NONE |
| }, |
| { |
| BFD_RELOC_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC, |
| BFD_RELOC_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC, |
| BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC, |
| BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC, |
| BFD_RELOC_AARCH64_NONE |
| } |
| }; |
| |
| gas_assert (inst.reloc.type == BFD_RELOC_AARCH64_LDST_LO12 |
| || inst.reloc.type == BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12 |
| || (inst.reloc.type |
| == BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12_NC)); |
| gas_assert (inst.base.opcode->operands[1] == AARCH64_OPND_ADDR_UIMM12); |
| |
| if (opd1_qlf == AARCH64_OPND_QLF_NIL) |
| opd1_qlf = |
| aarch64_get_expected_qualifier (inst.base.opcode->qualifiers_list, |
| 1, opd0_qlf, 0); |
| gas_assert (opd1_qlf != AARCH64_OPND_QLF_NIL); |
| |
| logsz = get_logsz (aarch64_get_qualifier_esize (opd1_qlf)); |
| if (inst.reloc.type == BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12 |
| || inst.reloc.type == BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12_NC) |
| gas_assert (logsz <= 3); |
| else |
| gas_assert (logsz <= 4); |
| |
| /* In reloc.c, these pseudo relocation types should be defined in similar |
| order as above reloc_ldst_lo12 array. Because the array index calcuation |
| below relies on this. */ |
| return reloc_ldst_lo12[inst.reloc.type - BFD_RELOC_AARCH64_LDST_LO12][logsz]; |
| } |
| |
| /* Check whether a register list REGINFO is valid. The registers must be |
| numbered in increasing order (modulo 32), in increments of one or two. |
| |
| If ACCEPT_ALTERNATE is non-zero, the register numbers should be in |
| increments of two. |
| |
| Return FALSE if such a register list is invalid, otherwise return TRUE. */ |
| |
| static bfd_boolean |
| reg_list_valid_p (uint32_t reginfo, int accept_alternate) |
| { |
| uint32_t i, nb_regs, prev_regno, incr; |
| |
| nb_regs = 1 + (reginfo & 0x3); |
| reginfo >>= 2; |
| prev_regno = reginfo & 0x1f; |
| incr = accept_alternate ? 2 : 1; |
| |
| for (i = 1; i < nb_regs; ++i) |
| { |
| uint32_t curr_regno; |
| reginfo >>= 5; |
| curr_regno = reginfo & 0x1f; |
| if (curr_regno != ((prev_regno + incr) & 0x1f)) |
| return FALSE; |
| prev_regno = curr_regno; |
| } |
| |
| return TRUE; |
| } |
| |
| /* Generic instruction operand parser. This does no encoding and no |
| semantic validation; it merely squirrels values away in the inst |
| structure. Returns TRUE or FALSE depending on whether the |
| specified grammar matched. */ |
| |
| static bfd_boolean |
| parse_operands (char *str, const aarch64_opcode *opcode) |
| { |
| int i; |
| char *backtrack_pos = 0; |
| const enum aarch64_opnd *operands = opcode->operands; |
| |
| clear_error (); |
| skip_whitespace (str); |
| |
| for (i = 0; operands[i] != AARCH64_OPND_NIL; i++) |
| { |
| int64_t val; |
| int isreg32, isregzero; |
| int comma_skipped_p = 0; |
| aarch64_reg_type rtype; |
| struct neon_type_el vectype; |
| aarch64_opnd_info *info = &inst.base.operands[i]; |
| |
| DEBUG_TRACE ("parse operand %d", i); |
| |
| /* Assign the operand code. */ |
| info->type = operands[i]; |
| |
| if (optional_operand_p (opcode, i)) |
| { |
| /* Remember where we are in case we need to backtrack. */ |
| gas_assert (!backtrack_pos); |
| backtrack_pos = str; |
| } |
| |
| /* Expect comma between operands; the backtrack mechanizm will take |
| care of cases of omitted optional operand. */ |
| if (i > 0 && ! skip_past_char (&str, ',')) |
| { |
| set_syntax_error (_("comma expected between operands")); |
| goto failure; |
| } |
| else |
| comma_skipped_p = 1; |
| |
| switch (operands[i]) |
| { |
| case AARCH64_OPND_Rd: |
| case AARCH64_OPND_Rn: |
| case AARCH64_OPND_Rm: |
| case AARCH64_OPND_Rt: |
| case AARCH64_OPND_Rt2: |
| case AARCH64_OPND_Rs: |
| case AARCH64_OPND_Ra: |
| case AARCH64_OPND_Rt_SYS: |
| case AARCH64_OPND_PAIRREG: |
| po_int_reg_or_fail (1, 0); |
| break; |
| |
| case AARCH64_OPND_Rd_SP: |
| case AARCH64_OPND_Rn_SP: |
| po_int_reg_or_fail (0, 1); |
| break; |
| |
| case AARCH64_OPND_Rm_EXT: |
| case AARCH64_OPND_Rm_SFT: |
| po_misc_or_fail (parse_shifter_operand |
| (&str, info, (operands[i] == AARCH64_OPND_Rm_EXT |
| ? SHIFTED_ARITH_IMM |
| : SHIFTED_LOGIC_IMM))); |
| if (!info->shifter.operator_present) |
| { |
| /* Default to LSL if not present. Libopcodes prefers shifter |
| kind to be explicit. */ |
| gas_assert (info->shifter.kind == AARCH64_MOD_NONE); |
| info->shifter.kind = AARCH64_MOD_LSL; |
| /* For Rm_EXT, libopcodes will carry out further check on whether |
| or not stack pointer is used in the instruction (Recall that |
| "the extend operator is not optional unless at least one of |
| "Rd" or "Rn" is '11111' (i.e. WSP)"). */ |
| } |
| break; |
| |
| case AARCH64_OPND_Fd: |
| case AARCH64_OPND_Fn: |
| case AARCH64_OPND_Fm: |
| case AARCH64_OPND_Fa: |
| case AARCH64_OPND_Ft: |
| case AARCH64_OPND_Ft2: |
| case AARCH64_OPND_Sd: |
| case AARCH64_OPND_Sn: |
| case AARCH64_OPND_Sm: |
| val = aarch64_reg_parse (&str, REG_TYPE_BHSDQ, &rtype, NULL); |
| if (val == PARSE_FAIL) |
| { |
| first_error (_(get_reg_expected_msg (REG_TYPE_BHSDQ))); |
| goto failure; |
| } |
| gas_assert (rtype >= REG_TYPE_FP_B && rtype <= REG_TYPE_FP_Q); |
| |
| info->reg.regno = val; |
| info->qualifier = AARCH64_OPND_QLF_S_B + (rtype - REG_TYPE_FP_B); |
| break; |
| |
| case AARCH64_OPND_Vd: |
| case AARCH64_OPND_Vn: |
| case AARCH64_OPND_Vm: |
| val = aarch64_reg_parse (&str, REG_TYPE_VN, NULL, &vectype); |
| if (val == PARSE_FAIL) |
| { |
| first_error (_(get_reg_expected_msg (REG_TYPE_VN))); |
| goto failure; |
| } |
| if (vectype.defined & NTA_HASINDEX) |
| goto failure; |
| |
| info->reg.regno = val; |
| info->qualifier = vectype_to_qualifier (&vectype); |
| if (info->qualifier == AARCH64_OPND_QLF_NIL) |
| goto failure; |
| break; |
| |
| case AARCH64_OPND_VdD1: |
| case AARCH64_OPND_VnD1: |
| val = aarch64_reg_parse (&str, REG_TYPE_VN, NULL, &vectype); |
| if (val == PARSE_FAIL) |
| { |
| set_first_syntax_error (_(get_reg_expected_msg (REG_TYPE_VN))); |
| goto failure; |
| } |
| if (vectype.type != NT_d || vectype.index != 1) |
| { |
| set_fatal_syntax_error |
| (_("the top half of a 128-bit FP/SIMD register is expected")); |
| goto failure; |
| } |
| info->reg.regno = val; |
| /* N.B: VdD1 and VnD1 are treated as an fp or advsimd scalar register |
| here; it is correct for the purpose of encoding/decoding since |
| only the register number is explicitly encoded in the related |
| instructions, although this appears a bit hacky. */ |
| info->qualifier = AARCH64_OPND_QLF_S_D; |
| break; |
| |
| case AARCH64_OPND_Ed: |
| case AARCH64_OPND_En: |
| case AARCH64_OPND_Em: |
| val = aarch64_reg_parse (&str, REG_TYPE_VN, NULL, &vectype); |
| if (val == PARSE_FAIL) |
| { |
| first_error (_(get_reg_expected_msg (REG_TYPE_VN))); |
| goto failure; |
| } |
| if (vectype.type == NT_invtype || !(vectype.defined & NTA_HASINDEX)) |
| goto failure; |
| |
| info->reglane.regno = val; |
| info->reglane.index = vectype.index; |
| info->qualifier = vectype_to_qualifier (&vectype); |
| if (info->qualifier == AARCH64_OPND_QLF_NIL) |
| goto failure; |
| break; |
| |
| case AARCH64_OPND_LVn: |
| case AARCH64_OPND_LVt: |
| case AARCH64_OPND_LVt_AL: |
| case AARCH64_OPND_LEt: |
| if ((val = parse_neon_reg_list (&str, &vectype)) == PARSE_FAIL) |
| goto failure; |
| if (! reg_list_valid_p (val, /* accept_alternate */ 0)) |
| { |
| set_fatal_syntax_error (_("invalid register list")); |
| goto failure; |
| } |
| info->reglist.first_regno = (val >> 2) & 0x1f; |
| info->reglist.num_regs = (val & 0x3) + 1; |
| if (operands[i] == AARCH64_OPND_LEt) |
| { |
| if (!(vectype.defined & NTA_HASINDEX)) |
| goto failure; |
| info->reglist.has_index = 1; |
| info->reglist.index = vectype.index; |
| } |
| else if (!(vectype.defined & NTA_HASTYPE)) |
| goto failure; |
| info->qualifier = vectype_to_qualifier (&vectype); |
| if (info->qualifier == AARCH64_OPND_QLF_NIL) |
| goto failure; |
| break; |
| |
| case AARCH64_OPND_Cn: |
| case AARCH64_OPND_Cm: |
| po_reg_or_fail (REG_TYPE_CN); |
| if (val > 15) |
| { |
| set_fatal_syntax_error (_(get_reg_expected_msg (REG_TYPE_CN))); |
| goto failure; |
| } |
| inst.base.operands[i].reg.regno = val; |
| break; |
| |
| case AARCH64_OPND_SHLL_IMM: |
| case AARCH64_OPND_IMM_VLSR: |
| po_imm_or_fail (1, 64); |
| info->imm.value = val; |
| break; |
| |
| case AARCH64_OPND_CCMP_IMM: |
| case AARCH64_OPND_FBITS: |
| case AARCH64_OPND_UIMM4: |
| case AARCH64_OPND_UIMM3_OP1: |
| case AARCH64_OPND_UIMM3_OP2: |
| case AARCH64_OPND_IMM_VLSL: |
| case AARCH64_OPND_IMM: |
| case AARCH64_OPND_WIDTH: |
| po_imm_nc_or_fail (); |
| info->imm.value = val; |
| break; |
| |
| case AARCH64_OPND_UIMM7: |
| po_imm_or_fail (0, 127); |
| info->imm.value = val; |
| break; |
| |
| case AARCH64_OPND_IDX: |
| case AARCH64_OPND_BIT_NUM: |
| case AARCH64_OPND_IMMR: |
| case AARCH64_OPND_IMMS: |
| po_imm_or_fail (0, 63); |
| info->imm.value = val; |
| break; |
| |
| case AARCH64_OPND_IMM0: |
| po_imm_nc_or_fail (); |
| if (val != 0) |
| { |
| set_fatal_syntax_error (_("immediate zero expected")); |
| goto failure; |
| } |
| info->imm.value = 0; |
| break; |
| |
| case AARCH64_OPND_FPIMM0: |
| { |
| int qfloat; |
| bfd_boolean res1 = FALSE, res2 = FALSE; |
| /* N.B. -0.0 will be rejected; although -0.0 shouldn't be rejected, |
| it is probably not worth the effort to support it. */ |
| if (!(res1 = parse_aarch64_imm_float (&str, &qfloat, FALSE)) |
| && !(res2 = parse_constant_immediate (&str, &val))) |
| goto failure; |
| if ((res1 && qfloat == 0) || (res2 && val == 0)) |
| { |
| info->imm.value = 0; |
| info->imm.is_fp = 1; |
| break; |
| } |
| set_fatal_syntax_error (_("immediate zero expected")); |
| goto failure; |
| } |
| |
| case AARCH64_OPND_IMM_MOV: |
| { |
| char *saved = str; |
| if (reg_name_p (str, REG_TYPE_R_Z_SP) || |
| reg_name_p (str, REG_TYPE_VN)) |
| goto failure; |
| str = saved; |
| po_misc_or_fail (my_get_expression (&inst.reloc.exp, &str, |
| GE_OPT_PREFIX, 1)); |
| /* The MOV immediate alias will be fixed up by fix_mov_imm_insn |
| later. fix_mov_imm_insn will try to determine a machine |
| instruction (MOVZ, MOVN or ORR) for it and will issue an error |
| message if the immediate cannot be moved by a single |
| instruction. */ |
| aarch64_set_gas_internal_fixup (&inst.reloc, info, 1); |
| inst.base.operands[i].skip = 1; |
| } |
| break; |
| |
| case AARCH64_OPND_SIMD_IMM: |
| case AARCH64_OPND_SIMD_IMM_SFT: |
| if (! parse_big_immediate (&str, &val)) |
| goto failure; |
| assign_imm_if_const_or_fixup_later (&inst.reloc, info, |
| /* addr_off_p */ 0, |
| /* need_libopcodes_p */ 1, |
| /* skip_p */ 1); |
| /* Parse shift. |
| N.B. although AARCH64_OPND_SIMD_IMM doesn't permit any |
| shift, we don't check it here; we leave the checking to |
| the libopcodes (operand_general_constraint_met_p). By |
| doing this, we achieve better diagnostics. */ |
| if (skip_past_comma (&str) |
| && ! parse_shift (&str, info, SHIFTED_LSL_MSL)) |
| goto failure; |
| if (!info->shifter.operator_present |
| && info->type == AARCH64_OPND_SIMD_IMM_SFT) |
| { |
| /* Default to LSL if not present. Libopcodes prefers shifter |
| kind to be explicit. */ |
| gas_assert (info->shifter.kind == AARCH64_MOD_NONE); |
| info->shifter.kind = AARCH64_MOD_LSL; |
| } |
| break; |
| |
| case AARCH64_OPND_FPIMM: |
| case AARCH64_OPND_SIMD_FPIMM: |
| { |
| int qfloat; |
| bfd_boolean dp_p |
| = (aarch64_get_qualifier_esize (inst.base.operands[0].qualifier) |
| == 8); |
| if (! parse_aarch64_imm_float (&str, &qfloat, dp_p)) |
| goto failure; |
| if (qfloat == 0) |
| { |
| set_fatal_syntax_error (_("invalid floating-point constant")); |
| goto failure; |
| } |
| inst.base.operands[i].imm.value = encode_imm_float_bits (qfloat); |
| inst.base.operands[i].imm.is_fp = 1; |
| } |
| break; |
| |
| case AARCH64_OPND_LIMM: |
| po_misc_or_fail (parse_shifter_operand (&str, info, |
| SHIFTED_LOGIC_IMM)); |
| if (info->shifter.operator_present) |
| { |
| set_fatal_syntax_error |
| (_("shift not allowed for bitmask immediate")); |
| goto failure; |
| } |
| assign_imm_if_const_or_fixup_later (&inst.reloc, info, |
| /* addr_off_p */ 0, |
| /* need_libopcodes_p */ 1, |
| /* skip_p */ 1); |
| break; |
| |
| case AARCH64_OPND_AIMM: |
| if (opcode->op == OP_ADD) |
| /* ADD may have relocation types. */ |
| po_misc_or_fail (parse_shifter_operand_reloc (&str, info, |
| SHIFTED_ARITH_IMM)); |
| else |
| po_misc_or_fail (parse_shifter_operand (&str, info, |
| SHIFTED_ARITH_IMM)); |
| switch (inst.reloc.type) |
| { |
| case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_HI12: |
| info->shifter.amount = 12; |
| break; |
| case BFD_RELOC_UNUSED: |
| aarch64_set_gas_internal_fixup (&inst.reloc, info, 0); |
| if (info->shifter.kind != AARCH64_MOD_NONE) |
| inst.reloc.flags = FIXUP_F_HAS_EXPLICIT_SHIFT; |
| inst.reloc.pc_rel = 0; |
| break; |
| default: |
| break; |
| } |
| info->imm.value = 0; |
| if (!info->shifter.operator_present) |
| { |
| /* Default to LSL if not present. Libopcodes prefers shifter |
| kind to be explicit. */ |
| gas_assert (info->shifter.kind == AARCH64_MOD_NONE); |
| info->shifter.kind = AARCH64_MOD_LSL; |
| } |
| break; |
| |
| case AARCH64_OPND_HALF: |
| { |
| /* #<imm16> or relocation. */ |
| int internal_fixup_p; |
| po_misc_or_fail (parse_half (&str, &internal_fixup_p)); |
| if (internal_fixup_p) |
| aarch64_set_gas_internal_fixup (&inst.reloc, info, 0); |
| skip_whitespace (str); |
| if (skip_past_comma (&str)) |
| { |
| /* {, LSL #<shift>} */ |
| if (! aarch64_gas_internal_fixup_p ()) |
| { |
| set_fatal_syntax_error (_("can't mix relocation modifier " |
| "with explicit shift")); |
| goto failure; |
| } |
| po_misc_or_fail (parse_shift (&str, info, SHIFTED_LSL)); |
| } |
| else |
| inst.base.operands[i].shifter.amount = 0; |
| inst.base.operands[i].shifter.kind = AARCH64_MOD_LSL; |
| inst.base.operands[i].imm.value = 0; |
| if (! process_movw_reloc_info ()) |
| goto failure; |
| } |
| break; |
| |
| case AARCH64_OPND_EXCEPTION: |
| po_misc_or_fail (parse_immediate_expression (&str, &inst.reloc.exp)); |
| assign_imm_if_const_or_fixup_later (&inst.reloc, info, |
| /* addr_off_p */ 0, |
| /* need_libopcodes_p */ 0, |
| /* skip_p */ 1); |
| break; |
| |
| case AARCH64_OPND_NZCV: |
| { |
| const asm_nzcv *nzcv = hash_find_n (aarch64_nzcv_hsh, str, 4); |
| if (nzcv != NULL) |
| { |
| str += 4; |
| info->imm.value = nzcv->value; |
| break; |
| } |
| po_imm_or_fail (0, 15); |
| info->imm.value = val; |
| } |
| break; |
| |
| case AARCH64_OPND_COND: |
| case AARCH64_OPND_COND1: |
| info->cond = hash_find_n (aarch64_cond_hsh, str, 2); |
| str += 2; |
| if (info->cond == NULL) |
| { |
| set_syntax_error (_("invalid condition")); |
| goto failure; |
| } |
| else if (operands[i] == AARCH64_OPND_COND1 |
| && (info->cond->value & 0xe) == 0xe) |
| { |
| /* Not allow AL or NV. */ |
| set_default_error (); |
| goto failure; |
| } |
| break; |
| |
| case AARCH64_OPND_ADDR_ADRP: |
| po_misc_or_fail (parse_adrp (&str)); |
| /* Clear the value as operand needs to be relocated. */ |
| info->imm.value = 0; |
| break; |
| |
| case AARCH64_OPND_ADDR_PCREL14: |
| case AARCH64_OPND_ADDR_PCREL19: |
| case AARCH64_OPND_ADDR_PCREL21: |
| case AARCH64_OPND_ADDR_PCREL26: |
| po_misc_or_fail (parse_address_reloc (&str, info)); |
| if (!info->addr.pcrel) |
| { |
| set_syntax_error (_("invalid pc-relative address")); |
| goto failure; |
| } |
| if (inst.gen_lit_pool |
| && (opcode->iclass != loadlit || opcode->op == OP_PRFM_LIT)) |
| { |
| /* Only permit "=value" in the literal load instructions. |
| The literal will be generated by programmer_friendly_fixup. */ |
| set_syntax_error (_("invalid use of \"=immediate\"")); |
| goto failure; |
| } |
| if (inst.reloc.exp.X_op == O_symbol && find_reloc_table_entry (&str)) |
| { |
| set_syntax_error (_("unrecognized relocation suffix")); |
| goto failure; |
| } |
| if (inst.reloc.exp.X_op == O_constant && !inst.gen_lit_pool) |
| { |
| info->imm.value = inst.reloc.exp.X_add_number; |
| inst.reloc.type = BFD_RELOC_UNUSED; |
| } |
| else |
| { |
| info->imm.value = 0; |
| if (inst.reloc.type == BFD_RELOC_UNUSED) |
| switch (opcode->iclass) |
| { |
| case compbranch: |
| case condbranch: |
| /* e.g. CBZ or B.COND */ |
| gas_assert (operands[i] == AARCH64_OPND_ADDR_PCREL19); |
| inst.reloc.type = BFD_RELOC_AARCH64_BRANCH19; |
| break; |
| case testbranch: |
| /* e.g. TBZ */ |
| gas_assert (operands[i] == AARCH64_OPND_ADDR_PCREL14); |
| inst.reloc.type = BFD_RELOC_AARCH64_TSTBR14; |
| break; |
| case branch_imm: |
| /* e.g. B or BL */ |
| gas_assert (operands[i] == AARCH64_OPND_ADDR_PCREL26); |
| inst.reloc.type = |
| (opcode->op == OP_BL) ? BFD_RELOC_AARCH64_CALL26 |
| : BFD_RELOC_AARCH64_JUMP26; |
| break; |
| case loadlit: |
| gas_assert (operands[i] == AARCH64_OPND_ADDR_PCREL19); |
| inst.reloc.type = BFD_RELOC_AARCH64_LD_LO19_PCREL; |
| break; |
| case pcreladdr: |
| gas_assert (operands[i] == AARCH64_OPND_ADDR_PCREL21); |
| inst.reloc.type = BFD_RELOC_AARCH64_ADR_LO21_PCREL; |
| break; |
| default: |
| gas_assert (0); |
| abort (); |
| } |
| inst.reloc.pc_rel = 1; |
| } |
| break; |
| |
| case AARCH64_OPND_ADDR_SIMPLE: |
| case AARCH64_OPND_SIMD_ADDR_SIMPLE: |
| /* [<Xn|SP>{, #<simm>}] */ |
| po_char_or_fail ('['); |
| po_reg_or_fail (REG_TYPE_R64_SP); |
| /* Accept optional ", #0". */ |
| if (operands[i] == AARCH64_OPND_ADDR_SIMPLE |
| && skip_past_char (&str, ',')) |
| { |
| skip_past_char (&str, '#'); |
| if (! skip_past_char (&str, '0')) |
| { |
| set_fatal_syntax_error |
| (_("the optional immediate offset can only be 0")); |
| goto failure; |
| } |
| } |
| po_char_or_fail (']'); |
| info->addr.base_regno = val; |
| break; |
| |
| case AARCH64_OPND_ADDR_REGOFF: |
| /* [<Xn|SP>, <R><m>{, <extend> {<amount>}}] */ |
| po_misc_or_fail (parse_address (&str, info, 0)); |
| if (info->addr.pcrel || !info->addr.offset.is_reg |
| || !info->addr.preind || info->addr.postind |
| || info->addr.writeback) |
| { |
| set_syntax_error (_("invalid addressing mode")); |
| goto failure; |
| } |
| if (!info->shifter.operator_present) |
| { |
| /* Default to LSL if not present. Libopcodes prefers shifter |
| kind to be explicit. */ |
| gas_assert (info->shifter.kind == AARCH64_MOD_NONE); |
| info->shifter.kind = AARCH64_MOD_LSL; |
| } |
| /* Qualifier to be deduced by libopcodes. */ |
| break; |
| |
| case AARCH64_OPND_ADDR_SIMM7: |
| po_misc_or_fail (parse_address (&str, info, 0)); |
| if (info->addr.pcrel || info->addr.offset.is_reg |
| || (!info->addr.preind && !info->addr.postind)) |
| { |
| set_syntax_error (_("invalid addressing mode")); |
| goto failure; |
| } |
| assign_imm_if_const_or_fixup_later (&inst.reloc, info, |
| /* addr_off_p */ 1, |
| /* need_libopcodes_p */ 1, |
| /* skip_p */ 0); |
| break; |
| |
| case AARCH64_OPND_ADDR_SIMM9: |
| case AARCH64_OPND_ADDR_SIMM9_2: |
| po_misc_or_fail (parse_address_reloc (&str, info)); |
| if (info->addr.pcrel || info->addr.offset.is_reg |
| || (!info->addr.preind && !info->addr.postind) |
| || (operands[i] == AARCH64_OPND_ADDR_SIMM9_2 |
| && info->addr.writeback)) |
| { |
| set_syntax_error (_("invalid addressing mode")); |
| goto failure; |
| } |
| if (inst.reloc.type != BFD_RELOC_UNUSED) |
| { |
| set_syntax_error (_("relocation not allowed")); |
| goto failure; |
| } |
| assign_imm_if_const_or_fixup_later (&inst.reloc, info, |
| /* addr_off_p */ 1, |
| /* need_libopcodes_p */ 1, |
| /* skip_p */ 0); |
| break; |
| |
| case AARCH64_OPND_ADDR_UIMM12: |
| po_misc_or_fail (parse_address_reloc (&str, info)); |
| if (info->addr.pcrel || info->addr.offset.is_reg |
| || !info->addr.preind || info->addr.writeback) |
| { |
| set_syntax_error (_("invalid addressing mode")); |
| goto failure; |
| } |
| if (inst.reloc.type == BFD_RELOC_UNUSED) |
| aarch64_set_gas_internal_fixup (&inst.reloc, info, 1); |
| else if (inst.reloc.type == BFD_RELOC_AARCH64_LDST_LO12 |
| || (inst.reloc.type |
| == BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12) |
| || (inst.reloc.type |
| == BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12_NC)) |
| inst.reloc.type = ldst_lo12_determine_real_reloc_type (); |
| /* Leave qualifier to be determined by libopcodes. */ |
| break; |
| |
| case AARCH64_OPND_SIMD_ADDR_POST: |
| /* [<Xn|SP>], <Xm|#<amount>> */ |
| po_misc_or_fail (parse_address (&str, info, 1)); |
| if (!info->addr.postind || !info->addr.writeback) |
| { |
| set_syntax_error (_("invalid addressing mode")); |
| goto failure; |
| } |
| if (!info->addr.offset.is_reg) |
| { |
| if (inst.reloc.exp.X_op == O_constant) |
| info->addr.offset.imm = inst.reloc.exp.X_add_number; |
| else |
| { |
| set_fatal_syntax_error |
| (_("writeback value should be an immediate constant")); |
| goto failure; |
| } |
| } |
| /* No qualifier. */ |
| break; |
| |
| case AARCH64_OPND_SYSREG: |
| if ((val = parse_sys_reg (&str, aarch64_sys_regs_hsh, 1, 0)) |
| == PARSE_FAIL) |
| { |
| set_syntax_error (_("unknown or missing system register name")); |
| goto failure; |
| } |
| inst.base.operands[i].sysreg = val; |
| break; |
| |
| case AARCH64_OPND_PSTATEFIELD: |
| if ((val = parse_sys_reg (&str, aarch64_pstatefield_hsh, 0, 1)) |
| == PARSE_FAIL) |
| { |
| set_syntax_error (_("unknown or missing PSTATE field name")); |
| goto failure; |
| } |
| inst.base.operands[i].pstatefield = val; |
| break; |
| |
| case AARCH64_OPND_SYSREG_IC: |
| inst.base.operands[i].sysins_op = |
| parse_sys_ins_reg (&str, aarch64_sys_regs_ic_hsh); |
| goto sys_reg_ins; |
| case AARCH64_OPND_SYSREG_DC: |
| inst.base.operands[i].sysins_op = |
| parse_sys_ins_reg (&str, aarch64_sys_regs_dc_hsh); |
| goto sys_reg_ins; |
| case AARCH64_OPND_SYSREG_AT: |
| inst.base.operands[i].sysins_op = |
| parse_sys_ins_reg (&str, aarch64_sys_regs_at_hsh); |
| goto sys_reg_ins; |
| case AARCH64_OPND_SYSREG_TLBI: |
| inst.base.operands[i].sysins_op = |
| parse_sys_ins_reg (&str, aarch64_sys_regs_tlbi_hsh); |
| sys_reg_ins: |
| if (inst.base.operands[i].sysins_op == NULL) |
| { |
| set_fatal_syntax_error ( _("unknown or missing operation name")); |
| goto failure; |
| } |
| break; |
| |
| case AARCH64_OPND_BARRIER: |
| case AARCH64_OPND_BARRIER_ISB: |
| val = parse_barrier (&str); |
| if (val != PARSE_FAIL |
| && operands[i] == AARCH64_OPND_BARRIER_ISB && val != 0xf) |
| { |
| /* ISB only accepts options name 'sy'. */ |
| set_syntax_error |
| (_("the specified option is not accepted in ISB")); |
| /* Turn off backtrack as this optional operand is present. */ |
| backtrack_pos = 0; |
| goto failure; |
| } |
| /* This is an extension to accept a 0..15 immediate. */ |
| if (val == PARSE_FAIL) |
| po_imm_or_fail (0, 15); |
| info->barrier = aarch64_barrier_options + val; |
| break; |
| |
| case AARCH64_OPND_PRFOP: |
| val = parse_pldop (&str); |
| /* This is an extension to accept a 0..31 immediate. */ |
| if (val == PARSE_FAIL) |
| po_imm_or_fail (0, 31); |
| inst.base.operands[i].prfop = aarch64_prfops + val; |
| break; |
| |
| case AARCH64_OPND_BARRIER_PSB: |
| val = parse_barrier_psb (&str, &(info->hint_option)); |
| if (val == PARSE_FAIL) |
| goto failure; |
| break; |
| |
| default: |
| as_fatal (_("unhandled operand code %d"), operands[i]); |
| } |
| |
| /* If we get here, this operand was successfully parsed. */ |
| inst.base.operands[i].present = 1; |
| continue; |
| |
| failure: |
| /* The parse routine should already have set the error, but in case |
| not, set a default one here. */ |
| if (! error_p ()) |
| set_default_error (); |
| |
| if (! backtrack_pos) |
| goto parse_operands_return; |
| |
| { |
| /* We reach here because this operand is marked as optional, and |
| either no operand was supplied or the operand was supplied but it |
| was syntactically incorrect. In the latter case we report an |
| error. In the former case we perform a few more checks before |
| dropping through to the code to insert the default operand. */ |
| |
| char *tmp = backtrack_pos; |
| char endchar = END_OF_INSN; |
| |
| if (i != (aarch64_num_of_operands (opcode) - 1)) |
| endchar = ','; |
| skip_past_char (&tmp, ','); |
| |
| if (*tmp != endchar) |
| /* The user has supplied an operand in the wrong format. */ |
| goto parse_operands_return; |
| |
| /* Make sure there is not a comma before the optional operand. |
| For example the fifth operand of 'sys' is optional: |
| |
| sys #0,c0,c0,#0, <--- wrong |
| sys #0,c0,c0,#0 <--- correct. */ |
| if (comma_skipped_p && i && endchar == END_OF_INSN) |
| { |
| set_fatal_syntax_error |
| (_("unexpected comma before the omitted optional operand")); |
| goto parse_operands_return; |
| } |
| } |
| |
| /* Reaching here means we are dealing with an optional operand that is |
| omitted from the assembly line. */ |
| gas_assert (optional_operand_p (opcode, i)); |
| info->present = 0; |
| process_omitted_operand (operands[i], opcode, i, info); |
| |
| /* Try again, skipping the optional operand at backtrack_pos. */ |
| str = backtrack_pos; |
| backtrack_pos = 0; |
| |
| /* Clear any error record after the omitted optional operand has been |
| successfully handled. */ |
| clear_error (); |
| } |
| |
| /* Check if we have parsed all the operands. */ |
| if (*str != '\0' && ! error_p ()) |
| { |
| /* Set I to the index of the last present operand; this is |
| for the purpose of diagnostics. */ |
| for (i -= 1; i >= 0 && !inst.base.operands[i].present; --i) |
| ; |
| set_fatal_syntax_error |
| (_("unexpected characters following instruction")); |
| } |
| |
| parse_operands_return: |
| |
| if (error_p ()) |
| { |
| DEBUG_TRACE ("parsing FAIL: %s - %s", |
| operand_mismatch_kind_names[get_error_kind ()], |
| get_error_message ()); |
| /* Record the operand error properly; this is useful when there |
| are multiple instruction templates for a mnemonic name, so that |
| later on, we can select the error that most closely describes |
| the problem. */ |
| record_operand_error (opcode, i, get_error_kind (), |
| get_error_message ()); |
| return FALSE; |
| } |
| else |
| { |
| DEBUG_TRACE ("parsing SUCCESS"); |
| return TRUE; |
| } |
| } |
| |
| /* It does some fix-up to provide some programmer friendly feature while |
| keeping the libopcodes happy, i.e. libopcodes only accepts |
| the preferred architectural syntax. |
| Return FALSE if there is any failure; otherwise return TRUE. */ |
| |
| static bfd_boolean |
| programmer_friendly_fixup (aarch64_instruction *instr) |
| { |
| aarch64_inst *base = &instr->base; |
| const aarch64_opcode *opcode = base->opcode; |
| enum aarch64_op op = opcode->op; |
| aarch64_opnd_info *operands = base->operands; |
| |
| DEBUG_TRACE ("enter"); |
| |
| switch (opcode->iclass) |
| { |
| case testbranch: |
| /* TBNZ Xn|Wn, #uimm6, label |
| Test and Branch Not Zero: conditionally jumps to label if bit number |
| uimm6 in register Xn is not zero. The bit number implies the width of |
| the register, which may be written and should be disassembled as Wn if |
| uimm is less than 32. */ |
| if (operands[0].qualifier == AARCH64_OPND_QLF_W) |
| { |
| if (operands[1].imm.value >= 32) |
| { |
| record_operand_out_of_range_error (opcode, 1, _("immediate value"), |
| 0, 31); |
| return FALSE; |
| } |
| operands[0].qualifier = AARCH64_OPND_QLF_X; |
| } |
| break; |
| case loadlit: |
| /* LDR Wt, label | =value |
| As a convenience assemblers will typically permit the notation |
| "=value" in conjunction with the pc-relative literal load instructions |
| to automatically place an immediate value or symbolic address in a |
| nearby literal pool and generate a hidden label which references it. |
| ISREG has been set to 0 in the case of =value. */ |
| if (instr->gen_lit_pool |
| && (op == OP_LDR_LIT || op == OP_LDRV_LIT || op == OP_LDRSW_LIT)) |
| { |
| int size = aarch64_get_qualifier_esize (operands[0].qualifier); |
| if (op == OP_LDRSW_LIT) |
| size = 4; |
| if (instr->reloc.exp.X_op != O_constant |
| && instr->reloc.exp.X_op != O_big |
| && instr->reloc.exp.X_op != O_symbol) |
| { |
| record_operand_error (opcode, 1, |
| AARCH64_OPDE_FATAL_SYNTAX_ERROR, |
| _("constant expression expected")); |
| return FALSE; |
| } |
| if (! add_to_lit_pool (&instr->reloc.exp, size)) |
| { |
| record_operand_error (opcode, 1, |
| AARCH64_OPDE_OTHER_ERROR, |
| _("literal pool insertion failed")); |
| return FALSE; |
| } |
| } |
| break; |
| case log_shift: |
| case bitfield: |
| /* UXT[BHW] Wd, Wn |
| Unsigned Extend Byte|Halfword|Word: UXT[BH] is architectural alias |
| for UBFM Wd,Wn,#0,#7|15, while UXTW is pseudo instruction which is |
| encoded using ORR Wd, WZR, Wn (MOV Wd,Wn). |
| A programmer-friendly assembler should accept a destination Xd in |
| place of Wd, however that is not the preferred form for disassembly. |
| */ |
| if ((op == OP_UXTB || op == OP_UXTH || op == OP_UXTW) |
| && operands[1].qualifier == AARCH64_OPND_QLF_W |
| && operands[0].qualifier == AARCH64_OPND_QLF_X) |
| operands[0].qualifier = AARCH64_OPND_QLF_W; |
| break; |
| |
| case addsub_ext: |
| { |
| /* In the 64-bit form, the final register operand is written as Wm |
| for all but the (possibly omitted) UXTX/LSL and SXTX |
| operators. |
| As a programmer-friendly assembler, we accept e.g. |
| ADDS <Xd>, <Xn|SP>, <Xm>{, UXTB {#<amount>}} and change it to |
| ADDS <Xd>, <Xn|SP>, <Wm>{, UXTB {#<amount>}}. */ |
| int idx = aarch64_operand_index (opcode->operands, |
| AARCH64_OPND_Rm_EXT); |
| gas_assert (idx == 1 || idx == 2); |
| if (operands[0].qualifier == AARCH64_OPND_QLF_X |
| && operands[idx].qualifier == AARCH64_OPND_QLF_X |
| && operands[idx].shifter.kind != AARCH64_MOD_LSL |
| && operands[idx].shifter.kind != AARCH64_MOD_UXTX |
| && operands[idx].shifter.kind != AARCH64_MOD_SXTX) |
| operands[idx].qualifier = AARCH64_OPND_QLF_W; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| |
| DEBUG_TRACE ("exit with SUCCESS"); |
| return TRUE; |
| } |
| |
| /* Check for loads and stores that will cause unpredictable behavior. */ |
| |
| static void |
| warn_unpredictable_ldst (aarch64_instruction *instr, char *str) |
| { |
| aarch64_inst *base = &instr->base; |
| const aarch64_opcode *opcode = base->opcode; |
| const aarch64_opnd_info *opnds = base->operands; |
| switch (opcode->iclass) |
| { |
| case ldst_pos: |
| case ldst_imm9: |
| case ldst_unscaled: |
| case ldst_unpriv: |
| /* Loading/storing the base register is unpredictable if writeback. */ |
| if ((aarch64_get_operand_class (opnds[0].type) |
| == AARCH64_OPND_CLASS_INT_REG) |
| && opnds[0].reg.regno == opnds[1].addr.base_regno |
| && opnds[1].addr.base_regno != REG_SP |
| && opnds[1].addr.writeback) |
| as_warn (_("unpredictable transfer with writeback -- `%s'"), str); |
| break; |
| case ldstpair_off: |
| case ldstnapair_offs: |
| case ldstpair_indexed: |
| /* Loading/storing the base register is unpredictable if writeback. */ |
| if ((aarch64_get_operand_class (opnds[0].type) |
| == AARCH64_OPND_CLASS_INT_REG) |
| && (opnds[0].reg.regno == opnds[2].addr.base_regno |
| || opnds[1].reg.regno == opnds[2].addr.base_regno) |
| && opnds[2].addr.base_regno != REG_SP |
| && opnds[2].addr.writeback) |
| as_warn (_("unpredictable transfer with writeback -- `%s'"), str); |
| /* Load operations must load different registers. */ |
| if ((opcode->opcode & (1 << 22)) |
| && opnds[0].reg.regno == opnds[1].reg.regno) |
| as_warn (_("unpredictable load of register pair -- `%s'"), str); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* A wrapper function to interface with libopcodes on encoding and |
| record the error message if there is any. |
| |
| Return TRUE on success; otherwise return FALSE. */ |
| |
| static bfd_boolean |
| do_encode (const aarch64_opcode *opcode, aarch64_inst *instr, |
| aarch64_insn *code) |
| { |
| aarch64_operand_error error_info; |
| error_info.kind = AARCH64_OPDE_NIL; |
| if (aarch64_opcode_encode (opcode, instr, code, NULL, &error_info)) |
| return TRUE; |
| else |
| { |
| gas_assert (error_info.kind != AARCH64_OPDE_NIL); |
| record_operand_error_info (opcode, &error_info); |
| return FALSE; |
| } |
| } |
| |
| #ifdef DEBUG_AARCH64 |
| static inline void |
| dump_opcode_operands (const aarch64_opcode *opcode) |
| { |
| int i = 0; |
| while (opcode->operands[i] != AARCH64_OPND_NIL) |
| { |
| aarch64_verbose ("\t\t opnd%d: %s", i, |
| aarch64_get_operand_name (opcode->operands[i])[0] != '\0' |
| ? aarch64_get_operand_name (opcode->operands[i]) |
| : aarch64_get_operand_desc (opcode->operands[i])); |
| ++i; |
| } |
| } |
| #endif /* DEBUG_AARCH64 */ |
| |
| /* This is the guts of the machine-dependent assembler. STR points to a |
| machine dependent instruction. This function is supposed to emit |
| the frags/bytes it assembles to. */ |
| |
| void |
| md_assemble (char *str) |
| { |
| char *p = str; |
| templates *template; |
| aarch64_opcode *opcode; |
| aarch64_inst *inst_base; |
| unsigned saved_cond; |
| |
| /* Align the previous label if needed. */ |
| if (last_label_seen != NULL) |
| { |
| symbol_set_frag (last_label_seen, frag_now); |
| S_SET_VALUE (last_label_seen, (valueT) frag_now_fix ()); |
| S_SET_SEGMENT (last_label_seen, now_seg); |
| } |
| |
| inst.reloc.type = BFD_RELOC_UNUSED; |
| |
| DEBUG_TRACE ("\n\n"); |
| DEBUG_TRACE ("=============================="); |
| DEBUG_TRACE ("Enter md_assemble with %s", str); |
| |
| template = opcode_lookup (&p); |
| if (!template) |
| { |
| /* It wasn't an instruction, but it might be a register alias of |
| the form alias .req reg directive. */ |
| if (!create_register_alias (str, p)) |
| as_bad (_("unknown mnemonic `%s' -- `%s'"), get_mnemonic_name (str), |
| str); |
| return; |
| } |
| |
| skip_whitespace (p); |
| if (*p == ',') |
| { |
| as_bad (_("unexpected comma after the mnemonic name `%s' -- `%s'"), |
| get_mnemonic_name (str), str); |
| return; |
| } |
| |
| init_operand_error_report (); |
| |
| /* Sections are assumed to start aligned. In executable section, there is no |
| MAP_DATA symbol pending. So we only align the address during |
| MAP_DATA --> MAP_INSN transition. |
| For other sections, this is not guaranteed. */ |
| enum mstate mapstate = seg_info (now_seg)->tc_segment_info_data.mapstate; |
| if (!need_pass_2 && subseg_text_p (now_seg) && mapstate == MAP_DATA) |
| frag_align_code (2, 0); |
| |
| saved_cond = inst.cond; |
| reset_aarch64_instruction (&inst); |
| inst.cond = saved_cond; |
| |
| /* Iterate through all opcode entries with the same mnemonic name. */ |
| do |
| { |
| opcode = template->opcode; |
| |
| DEBUG_TRACE ("opcode %s found", opcode->name); |
| #ifdef DEBUG_AARCH64 |
| if (debug_dump) |
| dump_opcode_operands (opcode); |
| #endif /* DEBUG_AARCH64 */ |
| |
| mapping_state (MAP_INSN); |
| |
| inst_base = &inst.base; |
| inst_base->opcode = opcode; |
| |
| /* Truly conditionally executed instructions, e.g. b.cond. */ |
| if (opcode->flags & F_COND) |
| { |
| gas_assert (inst.cond != COND_ALWAYS); |
| inst_base->cond = get_cond_from_value (inst.cond); |
| DEBUG_TRACE ("condition found %s", inst_base->cond->names[0]); |
| } |
| else if (inst.cond != COND_ALWAYS) |
| { |
| /* It shouldn't arrive here, where the assembly looks like a |
| conditional instruction but the found opcode is unconditional. */ |
| gas_assert (0); |
| continue; |
| } |
| |
| if (parse_operands (p, opcode) |
| && programmer_friendly_fixup (&inst) |
| && do_encode (inst_base->opcode, &inst.base, &inst_base->value)) |
| { |
| /* Check that this instruction is supported for this CPU. */ |
| if (!opcode->avariant |
| || !AARCH64_CPU_HAS_FEATURE (cpu_variant, *opcode->avariant)) |
| { |
| as_bad (_("selected processor does not support `%s'"), str); |
| return; |
| } |
| |
| warn_unpredictable_ldst (&inst, str); |
| |
| if (inst.reloc.type == BFD_RELOC_UNUSED |
| || !inst.reloc.need_libopcodes_p) |
| output_inst (NULL); |
| else |
| { |
| /* If there is relocation generated for the instruction, |
| store the instruction information for the future fix-up. */ |
| struct aarch64_inst *copy; |
| gas_assert (inst.reloc.type != BFD_RELOC_UNUSED); |
| copy = XNEW (struct aarch64_inst); |
| memcpy (copy, &inst.base, sizeof (struct aarch64_inst)); |
| output_inst (copy); |
| } |
| return; |
| } |
| |
| template = template->next; |
| if (template != NULL) |
| { |
| reset_aarch64_instruction (&inst); |
| inst.cond = saved_cond; |
| } |
| } |
| while (template != NULL); |
| |
| /* Issue the error messages if any. */ |
| output_operand_error_report (str); |
| } |
| |
| /* Various frobbings of labels and their addresses. */ |
| |
| void |
| aarch64_start_line_hook (void) |
| { |
| last_label_seen = NULL; |
| } |
| |
| void |
| aarch64_frob_label (symbolS * sym) |
| { |
| last_label_seen = sym; |
| |
| dwarf2_emit_label (sym); |
| } |
| |
| int |
| aarch64_data_in_code (void) |
| { |
| if (!strncmp (input_line_pointer + 1, "data:", 5)) |
| { |
| *input_line_pointer = '/'; |
| input_line_pointer += 5; |
| *input_line_pointer = 0; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| char * |
| aarch64_canonicalize_symbol_name (char *name) |
| { |
| int len; |
| |
| if ((len = strlen (name)) > 5 && streq (name + len - 5, "/data")) |
| *(name + len - 5) = 0; |
| |
| return name; |
| } |
| |
| /* Table of all register names defined by default. The user can |
| define additional names with .req. Note that all register names |
| should appear in both upper and lowercase variants. Some registers |
| also have mixed-case names. */ |
| |
| #define REGDEF(s,n,t) { #s, n, REG_TYPE_##t, TRUE } |
| #define REGNUM(p,n,t) REGDEF(p##n, n, t) |
| #define REGSET31(p,t) \ |
| REGNUM(p, 0,t), REGNUM(p, 1,t), REGNUM(p, 2,t), REGNUM(p, 3,t), \ |
| REGNUM(p, 4,t), REGNUM(p, 5,t), REGNUM(p, 6,t), REGNUM(p, 7,t), \ |
| REGNUM(p, 8,t), REGNUM(p, 9,t), REGNUM(p,10,t), REGNUM(p,11,t), \ |
| REGNUM(p,12,t), REGNUM(p,13,t), REGNUM(p,14,t), REGNUM(p,15,t), \ |
| REGNUM(p,16,t), REGNUM(p,17,t), REGNUM(p,18,t), REGNUM(p,19,t), \ |
| REGNUM(p,20,t), REGNUM(p,21,t), REGNUM(p,22,t), REGNUM(p,23,t), \ |
| REGNUM(p,24,t), REGNUM(p,25,t), REGNUM(p,26,t), REGNUM(p,27,t), \ |
| REGNUM(p,28,t), REGNUM(p,29,t), REGNUM(p,30,t) |
| #define REGSET(p,t) \ |
| REGSET31(p,t), REGNUM(p,31,t) |
| |
| /* These go into aarch64_reg_hsh hash-table. */ |
| static const reg_entry reg_names[] = { |
| /* Integer registers. */ |
| REGSET31 (x, R_64), REGSET31 (X, R_64), |
| REGSET31 (w, R_32), REGSET31 (W, R_32), |
| |
| REGDEF (wsp, 31, SP_32), REGDEF (WSP, 31, SP_32), |
| REGDEF (sp, 31, SP_64), REGDEF (SP, 31, SP_64), |
| |
| REGDEF (wzr, 31, Z_32), REGDEF (WZR, 31, Z_32), |
| REGDEF (xzr, 31, Z_64), REGDEF (XZR, 31, Z_64), |
| |
| /* Coprocessor register numbers. */ |
| REGSET (c, CN), REGSET (C, CN), |
| |
| /* Floating-point single precision registers. */ |
| REGSET (s, FP_S), REGSET (S, FP_S), |
| |
| /* Floating-point double precision registers. */ |
| REGSET (d, FP_D), REGSET (D, FP_D), |
| |
| /* Floating-point half precision registers. */ |
| REGSET (h, FP_H), REGSET (H, FP_H), |
| |
| /* Floating-point byte precision registers. */ |
| REGSET (b, FP_B), REGSET (B, FP_B), |
| |
| /* Floating-point quad precision registers. */ |
| REGSET (q, FP_Q), REGSET (Q, FP_Q), |
| |
| /* FP/SIMD registers. */ |
| REGSET (v, VN), REGSET (V, VN), |
| }; |
| |
| #undef REGDEF |
| #undef REGNUM |
| #undef REGSET |
| |
| #define N 1 |
| #define n 0 |
| #define Z 1 |
| #define z 0 |
| #define C 1 |
| #define c 0 |
| #define V 1 |
| #define v 0 |
| #define B(a,b,c,d) (((a) << 3) | ((b) << 2) | ((c) << 1) | (d)) |
| static const asm_nzcv nzcv_names[] = { |
| {"nzcv", B (n, z, c, v)}, |
| {"nzcV", B (n, z, c, V)}, |
| {"nzCv", B (n, z, C, v)}, |
| {"nzCV", B (n, z, C, V)}, |
| {"nZcv", B (n, Z, c, v)}, |
| {"nZcV", B (n, Z, c, V)}, |
| {"nZCv", B (n, Z, C, v)}, |
| {"nZCV", B (n, Z, C, V)}, |
| {"Nzcv", B (N, z, c, v)}, |
| {"NzcV", B (N, z, c, V)}, |
| {"NzCv", B (N, z, C, v)}, |
| {"NzCV", B (N, z, C, V)}, |
| {"NZcv", B (N, Z, c, v)}, |
| {"NZcV", B (N, Z, c, V)}, |
| {"NZCv", B (N, Z, C, v)}, |
| {"NZCV", B (N, Z, C, V)} |
| }; |
| |
| #undef N |
| #undef n |
| #undef Z |
| #undef z |
| #undef C |
| #undef c |
| #undef V |
| #undef v |
| #undef B |
| |
| /* MD interface: bits in the object file. */ |
| |
| /* Turn an integer of n bytes (in val) into a stream of bytes appropriate |
| for use in the a.out file, and stores them in the array pointed to by buf. |
| This knows about the endian-ness of the target machine and does |
| THE RIGHT THING, whatever it is. Possible values for n are 1 (byte) |
| 2 (short) and 4 (long) Floating numbers are put out as a series of |
| LITTLENUMS (shorts, here at least). */ |
| |
| 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); |
| } |
| |
| /* MD interface: Sections. */ |
| |
| /* Estimate the size of a frag before relaxing. Assume everything fits in |
| 4 bytes. */ |
| |
| int |
| md_estimate_size_before_relax (fragS * fragp, segT segtype ATTRIBUTE_UNUSED) |
| { |
| fragp->fr_var = 4; |
| return 4; |
| } |
| |
| /* Round up a section size to the appropriate boundary. */ |
| |
| valueT |
| md_section_align (segT segment ATTRIBUTE_UNUSED, valueT size) |
| { |
| return size; |
| } |
| |
| /* This is called from HANDLE_ALIGN in write.c. Fill in the contents |
| of an rs_align_code fragment. |
| |
| Here we fill the frag with the appropriate info for padding the |
| output stream. The resulting frag will consist of a fixed (fr_fix) |
| and of a repeating (fr_var) part. |
| |
| The fixed content is always emitted before the repeating content and |
| these two parts are used as follows in constructing the output: |
| - the fixed part will be used to align to a valid instruction word |
| boundary, in case that we start at a misaligned address; as no |
| executable instruction can live at the misaligned location, we |
| simply fill with zeros; |
| - the variable part will be used to cover the remaining padding and |
| we fill using the AArch64 NOP instruction. |
| |
| Note that the size of a RS_ALIGN_CODE fragment is always 7 to provide |
| enough storage space for up to 3 bytes for padding the back to a valid |
| instruction alignment and exactly 4 bytes to store the NOP pattern. */ |
| |
| void |
| aarch64_handle_align (fragS * fragP) |
| { |
| /* NOP = d503201f */ |
| /* AArch64 instructions are always little-endian. */ |
| static unsigned char const aarch64_noop[4] = { 0x1f, 0x20, 0x03, 0xd5 }; |
| |
| int bytes, fix, noop_size; |
| char *p; |
| |
| if (fragP->fr_type != rs_align_code) |
| return; |
| |
| bytes = fragP->fr_next->fr_address - fragP->fr_address - fragP->fr_fix; |
| p = fragP->fr_literal + fragP->fr_fix; |
| |
| #ifdef OBJ_ELF |
| gas_assert (fragP->tc_frag_data.recorded); |
| #endif |
| |
| noop_size = sizeof (aarch64_noop); |
| |
| fix = bytes & (noop_size - 1); |
| if (fix) |
| { |
| #ifdef OBJ_ELF |
| insert_data_mapping_symbol (MAP_INSN, fragP->fr_fix, fragP, fix); |
| #endif |
| memset (p, 0, fix); |
| p += fix; |
| fragP->fr_fix += fix; |
| } |
| |
| if (noop_size) |
| memcpy (p, aarch64_noop, noop_size); |
| fragP->fr_var = noop_size; |
| } |
| |
| /* Perform target specific initialisation of a frag. |
| Note - despite the name this initialisation is not done when the frag |
| is created, but only when its type is assigned. A frag can be created |
| and used a long time before its type is set, so beware of assuming that |
| this initialisationis performed first. */ |
| |
| #ifndef OBJ_ELF |
| void |
| aarch64_init_frag (fragS * fragP ATTRIBUTE_UNUSED, |
| int max_chars ATTRIBUTE_UNUSED) |
| { |
| } |
| |
| #else /* OBJ_ELF is defined. */ |
| void |
| aarch64_init_frag (fragS * fragP, int max_chars) |
| { |
| /* Record a mapping symbol for alignment frags. We will delete this |
| later if the alignment ends up empty. */ |
| if (!fragP->tc_frag_data.recorded) |
| fragP->tc_frag_data.recorded = 1; |
| |
| switch (fragP->fr_type) |
| { |
| case rs_align: |
| case rs_align_test: |
| case rs_fill: |
| mapping_state_2 (MAP_DATA, max_chars); |
| break; |
| case rs_align_code: |
| mapping_state_2 (MAP_INSN, max_chars); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| /* Initialize the DWARF-2 unwind information for this procedure. */ |
| |
| void |
| tc_aarch64_frame_initial_instructions (void) |
| { |
| cfi_add_CFA_def_cfa (REG_SP, 0); |
| } |
| #endif /* OBJ_ELF */ |
| |
| /* Convert REGNAME to a DWARF-2 register number. */ |
| |
| int |
| tc_aarch64_regname_to_dw2regnum (char *regname) |
| { |
| const reg_entry *reg = parse_reg (®name); |
| if (reg == NULL) |
| return -1; |
| |
| switch (reg->type) |
| { |
| case REG_TYPE_SP_32: |
| case REG_TYPE_SP_64: |
| case REG_TYPE_R_32: |
| case REG_TYPE_R_64: |
| return reg->number; |
| |
| case REG_TYPE_FP_B: |
| case REG_TYPE_FP_H: |
| case REG_TYPE_FP_S: |
| case REG_TYPE_FP_D: |
| case REG_TYPE_FP_Q: |
| return reg->number + 64; |
| |
| default: |
| break; |
| } |
| return -1; |
| } |
| |
| /* Implement DWARF2_ADDR_SIZE. */ |
| |
| int |
| aarch64_dwarf2_addr_size (void) |
| { |
| #if defined (OBJ_MAYBE_ELF) || defined (OBJ_ELF) |
| if (ilp32_p) |
| return 4; |
| #endif |
| return bfd_arch_bits_per_address (stdoutput) / 8; |
| } |
| |
| /* MD interface: Symbol and relocation handling. */ |
| |
| /* Return the address within the segment that a PC-relative fixup is |
| relative to. For AArch64 PC-relative fixups applied to instructions |
| are generally relative to the location plus AARCH64_PCREL_OFFSET bytes. */ |
| |
| long |
| md_pcrel_from_section (fixS * fixP, segT seg) |
| { |
| offsetT base = fixP->fx_where + fixP->fx_frag->fr_address; |
| |
| /* If this is pc-relative and we are going to emit a relocation |
| then we just want to put out any pipeline compensation that the linker |
| will need. Otherwise we want to use the calculated base. */ |
| if (fixP->fx_pcrel |
| && ((fixP->fx_addsy && S_GET_SEGMENT (fixP->fx_addsy) != seg) |
| || aarch64_force_relocation (fixP))) |
| base = 0; |
| |
| /* AArch64 should be consistent for all pc-relative relocations. */ |
| return base + AARCH64_PCREL_OFFSET; |
| } |
| |
| /* Under ELF we need to default _GLOBAL_OFFSET_TABLE. |
| Otherwise we have no need to default values of symbols. */ |
| |
| symbolS * |
| md_undefined_symbol (char *name ATTRIBUTE_UNUSED) |
| { |
| #ifdef OBJ_ELF |
| if (name[0] == '_' && name[1] == 'G' |
| && streq (name, GLOBAL_OFFSET_TABLE_NAME)) |
| { |
| if (!GOT_symbol) |
| { |
| if (symbol_find (name)) |
| as_bad (_("GOT already in the symbol table")); |
| |
| GOT_symbol = symbol_new (name, undefined_section, |
| (valueT) 0, &zero_address_frag); |
| } |
| |
| return GOT_symbol; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| /* Return non-zero if the indicated VALUE has overflowed the maximum |
| range expressible by a unsigned number with the indicated number of |
| BITS. */ |
| |
| static bfd_boolean |
| unsigned_overflow (valueT value, unsigned bits) |
| { |
| valueT lim; |
| if (bits >= sizeof (valueT) * 8) |
| return FALSE; |
| lim = (valueT) 1 << bits; |
| return (value >= lim); |
| } |
| |
| |
| /* Return non-zero if the indicated VALUE has overflowed the maximum |
| range expressible by an signed number with the indicated number of |
| BITS. */ |
| |
| static bfd_boolean |
| signed_overflow (offsetT value, unsigned bits) |
| { |
| offsetT lim; |
| if (bits >= sizeof (offsetT) * 8) |
| return FALSE; |
| lim = (offsetT) 1 << (bits - 1); |
| return (value < -lim || value >= lim); |
| } |
| |
| /* Given an instruction in *INST, which is expected to be a scaled, 12-bit, |
| unsigned immediate offset load/store instruction, try to encode it as |
| an unscaled, 9-bit, signed immediate offset load/store instruction. |
| Return TRUE if it is successful; otherwise return FALSE. |
| |
| As a programmer-friendly assembler, LDUR/STUR instructions can be generated |
| in response to the standard LDR/STR mnemonics when the immediate offset is |
| unambiguous, i.e. when it is negative or unaligned. */ |
| |
| static bfd_boolean |
| try_to_encode_as_unscaled_ldst (aarch64_inst *instr) |
| { |
| int idx; |
| enum aarch64_op new_op; |
| const aarch64_opcode *new_opcode; |
| |
| gas_assert (instr->opcode->iclass == ldst_pos); |
| |
| switch (instr->opcode->op) |
| { |
| case OP_LDRB_POS:new_op = OP_LDURB; break; |
| case OP_STRB_POS: new_op = OP_STURB; break; |
| case OP_LDRSB_POS: new_op = OP_LDURSB; break; |
| case OP_LDRH_POS: new_op = OP_LDURH; break; |
| case OP_STRH_POS: new_op = OP_STURH; break; |
| case OP_LDRSH_POS: new_op = OP_LDURSH; break; |
| case OP_LDR_POS: new_op = OP_LDUR; break; |
| case OP_STR_POS: new_op = OP_STUR; break; |
| case OP_LDRF_POS: new_op = OP_LDURV; break; |
| case OP_STRF_POS: new_op = OP_STURV; break; |
| case OP_LDRSW_POS: new_op = OP_LDURSW; break; |
| case OP_PRFM_POS: new_op = OP_PRFUM; break; |
| default: new_op = OP_NIL; break; |
| } |
| |
| if (new_op == OP_NIL) |
| return FALSE; |
| |
| new_opcode = aarch64_get_opcode (new_op); |
| gas_assert (new_opcode != NULL); |
| |
| DEBUG_TRACE ("Check programmer-friendly STURB/LDURB -> STRB/LDRB: %d == %d", |
| instr->opcode->op, new_opcode->op); |
| |
| aarch64_replace_opcode (instr, new_opcode); |
| |
| /* Clear up the ADDR_SIMM9's qualifier; otherwise the |
| qualifier matching may fail because the out-of-date qualifier will |
| prevent the operand being updated with a new and correct qualifier. */ |
| idx = aarch64_operand_index (instr->opcode->operands, |
| AARCH64_OPND_ADDR_SIMM9); |
| gas_assert (idx == 1); |
| instr->operands[idx].qualifier = AARCH64_OPND_QLF_NIL; |
| |
| DEBUG_TRACE ("Found LDURB entry to encode programmer-friendly LDRB"); |
| |
| if (!aarch64_opcode_encode (instr->opcode, instr, &instr->value, NULL, NULL)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| /* Called by fix_insn to fix a MOV immediate alias instruction. |
| |
| Operand for a generic move immediate instruction, which is an alias |
| instruction that generates a single MOVZ, MOVN or ORR instruction to loads |
| a 32-bit/64-bit immediate value into general register. An assembler error |
| shall result if the immediate cannot be created by a single one of these |
| instructions. If there is a choice, then to ensure reversability an |
| assembler must prefer a MOVZ to MOVN, and MOVZ or MOVN to ORR. */ |
| |
| static void |
| fix_mov_imm_insn (fixS *fixP, char *buf, aarch64_inst *instr, offsetT value) |
| { |
| const aarch64_opcode *opcode; |
| |
| /* Need to check if the destination is SP/ZR. The check has to be done |
| before any aarch64_replace_opcode. */ |
| int try_mov_wide_p = !aarch64_stack_pointer_p (&instr->operands[0]); |
| int try_mov_bitmask_p = !aarch64_zero_register_p (&instr->operands[0]); |
| |
| instr->operands[1].imm.value = value; |
| instr->operands[1].skip = 0; |
| |
| if (try_mov_wide_p) |
| { |
| /* Try the MOVZ alias. */ |
| opcode = aarch64_get_opcode (OP_MOV_IMM_WIDE); |
| aarch64_replace_opcode (instr, opcode); |
| if (aarch64_opcode_encode (instr->opcode, instr, |
| &instr->value, NULL, NULL)) |
| { |
| put_aarch64_insn (buf, instr->value); |
| return; |
| } |
| /* Try the MOVK alias. */ |
| opcode = aarch64_get_opcode (OP_MOV_IMM_WIDEN); |
| aarch64_replace_opcode (instr, opcode); |
| if (aarch64_opcode_encode (instr->opcode, instr, |
| &instr->value, NULL, NULL)) |
| { |
| put_aarch64_insn (buf, instr->value); |
| return; |
| } |
| } |
| |
| if (try_mov_bitmask_p) |
| { |
| /* Try the ORR alias. */ |
| opcode = aarch64_get_opcode (OP_MOV_IMM_LOG); |
| aarch64_replace_opcode (instr, opcode); |
| if (aarch64_opcode_encode (instr->opcode, instr, |
| &instr->value, NULL, NULL)) |
| { |
| put_aarch64_insn (buf, instr->value); |
| return; |
| } |
| } |
| |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("immediate cannot be moved by a single instruction")); |
| } |
| |
| /* An instruction operand which is immediate related may have symbol used |
| in the assembly, e.g. |
| |
| mov w0, u32 |
| .set u32, 0x00ffff00 |
| |
| At the time when the assembly instruction is parsed, a referenced symbol, |
| like 'u32' in the above example may not have been seen; a fixS is created |
| in such a case and is handled here after symbols have been resolved. |
| Instruction is fixed up with VALUE using the information in *FIXP plus |
| extra information in FLAGS. |
| |
| This function is called by md_apply_fix to fix up instructions that need |
| a fix-up described above but does not involve any linker-time relocation. */ |
| |
| static void |
| fix_insn (fixS *fixP, uint32_t flags, offsetT value) |
| { |
| int idx; |
| uint32_t insn; |
| char *buf = fixP->fx_where + fixP->fx_frag->fr_literal; |
| enum aarch64_opnd opnd = fixP->tc_fix_data.opnd; |
| aarch64_inst *new_inst = fixP->tc_fix_data.inst; |
| |
| if (new_inst) |
| { |
| /* Now the instruction is about to be fixed-up, so the operand that |
| was previously marked as 'ignored' needs to be unmarked in order |
| to get the encoding done properly. */ |
| idx = aarch64_operand_index (new_inst->opcode->operands, opnd); |
| new_inst->operands[idx].skip = 0; |
| } |
| |
| gas_assert (opnd != AARCH64_OPND_NIL); |
| |
| switch (opnd) |
| { |
| case AARCH64_OPND_EXCEPTION: |
| if (unsigned_overflow (value, 16)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("immediate out of range")); |
| insn = get_aarch64_insn (buf); |
| insn |= encode_svc_imm (value); |
| put_aarch64_insn (buf, insn); |
| break; |
| |
| case AARCH64_OPND_AIMM: |
| /* ADD or SUB with immediate. |
| NOTE this assumes we come here with a add/sub shifted reg encoding |
| 3 322|2222|2 2 2 21111 111111 |
| 1 098|7654|3 2 1 09876 543210 98765 43210 |
| 0b000000 sf 000|1011|shift 0 Rm imm6 Rn Rd ADD |
| 2b000000 sf 010|1011|shift 0 Rm imm6 Rn Rd ADDS |
| 4b000000 sf 100|1011|shift 0 Rm imm6 Rn Rd SUB |
| 6b000000 sf 110|1011|shift 0 Rm imm6 Rn Rd SUBS |
| -> |
| 3 322|2222|2 2 221111111111 |
| 1 098|7654|3 2 109876543210 98765 43210 |
| 11000000 sf 001|0001|shift imm12 Rn Rd ADD |
| 31000000 sf 011|0001|shift imm12 Rn Rd ADDS |
| 51000000 sf 101|0001|shift imm12 Rn Rd SUB |
| 71000000 sf 111|0001|shift imm12 Rn Rd SUBS |
| Fields sf Rn Rd are already set. */ |
| insn = get_aarch64_insn (buf); |
| if (value < 0) |
| { |
| /* Add <-> sub. */ |
| insn = reencode_addsub_switch_add_sub (insn); |
| value = -value; |
| } |
| |
| if ((flags & FIXUP_F_HAS_EXPLICIT_SHIFT) == 0 |
| && unsigned_overflow (value, 12)) |
| { |
| /* Try to shift the value by 12 to make it fit. */ |
| if (((value >> 12) << 12) == value |
| && ! unsigned_overflow (value, 12 + 12)) |
| { |
| value >>= 12; |
| insn |= encode_addsub_imm_shift_amount (1); |
| } |
| } |
| |
| if (unsigned_overflow (value, 12)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("immediate out of range")); |
| |
| insn |= encode_addsub_imm (value); |
| |
| put_aarch64_insn (buf, insn); |
| break; |
| |
| case AARCH64_OPND_SIMD_IMM: |
| case AARCH64_OPND_SIMD_IMM_SFT: |
| case AARCH64_OPND_LIMM: |
| /* Bit mask immediate. */ |
| gas_assert (new_inst != NULL); |
| idx = aarch64_operand_index (new_inst->opcode->operands, opnd); |
| new_inst->operands[idx].imm.value = value; |
| if (aarch64_opcode_encode (new_inst->opcode, new_inst, |
| &new_inst->value, NULL, NULL)) |
| put_aarch64_insn (buf, new_inst->value); |
| else |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("invalid immediate")); |
| break; |
| |
| case AARCH64_OPND_HALF: |
| /* 16-bit unsigned immediate. */ |
| if (unsigned_overflow (value, 16)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("immediate out of range")); |
| insn = get_aarch64_insn (buf); |
| insn |= encode_movw_imm (value & 0xffff); |
| put_aarch64_insn (buf, insn); |
| break; |
| |
| case AARCH64_OPND_IMM_MOV: |
| /* Operand for a generic move immediate instruction, which is |
| an alias instruction that generates a single MOVZ, MOVN or ORR |
| instruction to loads a 32-bit/64-bit immediate value into general |
| register. An assembler error shall result if the immediate cannot be |
| created by a single one of these instructions. If there is a choice, |
| then to ensure reversability an assembler must prefer a MOVZ to MOVN, |
| and MOVZ or MOVN to ORR. */ |
| gas_assert (new_inst != NULL); |
| fix_mov_imm_insn (fixP, buf, new_inst, value); |
| break; |
| |
| case AARCH64_OPND_ADDR_SIMM7: |
| case AARCH64_OPND_ADDR_SIMM9: |
| case AARCH64_OPND_ADDR_SIMM9_2: |
| case AARCH64_OPND_ADDR_UIMM12: |
| /* Immediate offset in an address. */ |
| insn = get_aarch64_insn (buf); |
| |
| gas_assert (new_inst != NULL && new_inst->value == insn); |
| gas_assert (new_inst->opcode->operands[1] == opnd |
| || new_inst->opcode->operands[2] == opnd); |
| |
| /* Get the index of the address operand. */ |
| if (new_inst->opcode->operands[1] == opnd) |
| /* e.g. STR <Xt>, [<Xn|SP>, <R><m>{, <extend> {<amount>}}]. */ |
| idx = 1; |
| else |
| /* e.g. LDP <Qt1>, <Qt2>, [<Xn|SP>{, #<imm>}]. */ |
| idx = 2; |
| |
| /* Update the resolved offset value. */ |
| new_inst->operands[idx].addr.offset.imm = value; |
| |
| /* Encode/fix-up. */ |
| if (aarch64_opcode_encode (new_inst->opcode, new_inst, |
| &new_inst->value, NULL, NULL)) |
| { |
| put_aarch64_insn (buf, new_inst->value); |
| break; |
| } |
| else if (new_inst->opcode->iclass == ldst_pos |
| && try_to_encode_as_unscaled_ldst (new_inst)) |
| { |
| put_aarch64_insn (buf, new_inst->value); |
| break; |
| } |
| |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("immediate offset out of range")); |
| break; |
| |
| default: |
| gas_assert (0); |
| as_fatal (_("unhandled operand code %d"), opnd); |
| } |
| } |
| |
| /* Apply a fixup (fixP) to segment data, once it has been determined |
| by our caller that we have all the info we need to fix it up. |
| |
| Parameter valP is the pointer to the value of the bits. */ |
| |
| void |
| md_apply_fix (fixS * fixP, valueT * valP, segT seg) |
| { |
| offsetT value = *valP; |
| uint32_t insn; |
| char *buf = fixP->fx_where + fixP->fx_frag->fr_literal; |
| int scale; |
| unsigned flags = fixP->fx_addnumber; |
| |
| DEBUG_TRACE ("\n\n"); |
| DEBUG_TRACE ("~~~~~~~~~~~~~~~~~~~~~~~~~"); |
| DEBUG_TRACE ("Enter md_apply_fix"); |
| |
| gas_assert (fixP->fx_r_type <= BFD_RELOC_UNUSED); |
| |
| /* Note whether this will delete the relocation. */ |
| |
| if (fixP->fx_addsy == 0 && !fixP->fx_pcrel) |
| fixP->fx_done = 1; |
| |
| /* Process the relocations. */ |
| switch (fixP->fx_r_type) |
| { |
| case BFD_RELOC_NONE: |
| /* This will need to go in the object file. */ |
| fixP->fx_done = 0; |
| break; |
| |
| case BFD_RELOC_8: |
| case BFD_RELOC_8_PCREL: |
| if (fixP->fx_done || !seg->use_rela_p) |
| md_number_to_chars (buf, value, 1); |
| break; |
| |
| case BFD_RELOC_16: |
| case BFD_RELOC_16_PCREL: |
| if (fixP->fx_done || !seg->use_rela_p) |
| md_number_to_chars (buf, value, 2); |
| break; |
| |
| case BFD_RELOC_32: |
| case BFD_RELOC_32_PCREL: |
| if (fixP->fx_done || !seg->use_rela_p) |
| md_number_to_chars (buf, value, 4); |
| break; |
| |
| case BFD_RELOC_64: |
| case BFD_RELOC_64_PCREL: |
| if (fixP->fx_done || !seg->use_rela_p) |
| md_number_to_chars (buf, value, 8); |
| break; |
| |
| case BFD_RELOC_AARCH64_GAS_INTERNAL_FIXUP: |
| /* We claim that these fixups have been processed here, even if |
| in fact we generate an error because we do not have a reloc |
| for them, so tc_gen_reloc() will reject them. */ |
| fixP->fx_done = 1; |
| if (fixP->fx_addsy && !S_IS_DEFINED (fixP->fx_addsy)) |
| { |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("undefined symbol %s used as an immediate value"), |
| S_GET_NAME (fixP->fx_addsy)); |
| goto apply_fix_return; |
| } |
| fix_insn (fixP, flags, value); |
| break; |
| |
| case BFD_RELOC_AARCH64_LD_LO19_PCREL: |
| if (fixP->fx_done || !seg->use_rela_p) |
| { |
| if (value & 3) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("pc-relative load offset not word aligned")); |
| if (signed_overflow (value, 21)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("pc-relative load offset out of range")); |
| insn = get_aarch64_insn (buf); |
| insn |= encode_ld_lit_ofs_19 (value >> 2); |
| put_aarch64_insn (buf, insn); |
| } |
| break; |
| |
| case BFD_RELOC_AARCH64_ADR_LO21_PCREL: |
| if (fixP->fx_done || !seg->use_rela_p) |
| { |
| if (signed_overflow (value, 21)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("pc-relative address offset out of range")); |
| insn = get_aarch64_insn (buf); |
| insn |= encode_adr_imm (value); |
| put_aarch64_insn (buf, insn); |
| } |
| break; |
| |
| case BFD_RELOC_AARCH64_BRANCH19: |
| if (fixP->fx_done || !seg->use_rela_p) |
| { |
| if (value & 3) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("conditional branch target not word aligned")); |
| if (signed_overflow (value, 21)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("conditional branch out of range")); |
| insn = get_aarch64_insn (buf); |
| insn |= encode_cond_branch_ofs_19 (value >> 2); |
| put_aarch64_insn (buf, insn); |
| } |
| break; |
| |
| case BFD_RELOC_AARCH64_TSTBR14: |
| if (fixP->fx_done || !seg->use_rela_p) |
| { |
| if (value & 3) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("conditional branch target not word aligned")); |
| if (signed_overflow (value, 16)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("conditional branch out of range")); |
| insn = get_aarch64_insn (buf); |
| insn |= encode_tst_branch_ofs_14 (value >> 2); |
| put_aarch64_insn (buf, insn); |
| } |
| break; |
| |
| case BFD_RELOC_AARCH64_CALL26: |
| case BFD_RELOC_AARCH64_JUMP26: |
| if (fixP->fx_done || !seg->use_rela_p) |
| { |
| if (value & 3) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("branch target not word aligned")); |
| if (signed_overflow (value, 28)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("branch out of range")); |
| insn = get_aarch64_insn (buf); |
| insn |= encode_branch_ofs_26 (value >> 2); |
| put_aarch64_insn (buf, insn); |
| } |
| break; |
| |
| case BFD_RELOC_AARCH64_MOVW_G0: |
| case BFD_RELOC_AARCH64_MOVW_G0_NC: |
| case BFD_RELOC_AARCH64_MOVW_G0_S: |
| case BFD_RELOC_AARCH64_MOVW_GOTOFF_G0_NC: |
| scale = 0; |
| goto movw_common; |
| case BFD_RELOC_AARCH64_MOVW_G1: |
| case BFD_RELOC_AARCH64_MOVW_G1_NC: |
| case BFD_RELOC_AARCH64_MOVW_G1_S: |
| case BFD_RELOC_AARCH64_MOVW_GOTOFF_G1: |
| scale = 16; |
| goto movw_common; |
| case BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC: |
| scale = 0; |
| S_SET_THREAD_LOCAL (fixP->fx_addsy); |
| /* Should always be exported to object file, see |
| aarch64_force_relocation(). */ |
| gas_assert (!fixP->fx_done); |
| gas_assert (seg->use_rela_p); |
| goto movw_common; |
| case BFD_RELOC_AARCH64_TLSDESC_OFF_G1: |
| scale = 16; |
| S_SET_THREAD_LOCAL (fixP->fx_addsy); |
| /* Should always be exported to object file, see |
| aarch64_force_relocation(). */ |
| gas_assert (!fixP->fx_done); |
| gas_assert (seg->use_rela_p); |
| goto movw_common; |
| case BFD_RELOC_AARCH64_MOVW_G2: |
| case BFD_RELOC_AARCH64_MOVW_G2_NC: |
| case BFD_RELOC_AARCH64_MOVW_G2_S: |
| scale = 32; |
| goto movw_common; |
| case BFD_RELOC_AARCH64_MOVW_G3: |
| scale = 48; |
| movw_common: |
| if (fixP->fx_done || !seg->use_rela_p) |
| { |
| insn = get_aarch64_insn (buf); |
| |
| if (!fixP->fx_done) |
| { |
| /* REL signed addend must fit in 16 bits */ |
| if (signed_overflow (value, 16)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("offset out of range")); |
| } |
| else |
| { |
| /* Check for overflow and scale. */ |
| switch (fixP->fx_r_type) |
| { |
| case BFD_RELOC_AARCH64_MOVW_G0: |
| case BFD_RELOC_AARCH64_MOVW_G1: |
| case BFD_RELOC_AARCH64_MOVW_G2: |
| case BFD_RELOC_AARCH64_MOVW_G3: |
| case BFD_RELOC_AARCH64_MOVW_GOTOFF_G1: |
| case BFD_RELOC_AARCH64_TLSDESC_OFF_G1: |
| if (unsigned_overflow (value, scale + 16)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("unsigned value out of range")); |
| break; |
| case BFD_RELOC_AARCH64_MOVW_G0_S: |
| case BFD_RELOC_AARCH64_MOVW_G1_S: |
| case BFD_RELOC_AARCH64_MOVW_G2_S: |
| /* NOTE: We can only come here with movz or movn. */ |
| if (signed_overflow (value, scale + 16)) |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("signed value out of range")); |
| if (value < 0) |
| { |
| /* Force use of MOVN. */ |
| value = ~value; |
| insn = reencode_movzn_to_movn (insn); |
| } |
| else |
| { |
| /* Force use of MOVZ. */ |
| insn = reencode_movzn_to_movz (insn); |
| } |
| break; |
| default: |
| /* Unchecked relocations. */ |
| break; |
| } |
| value >>= scale; |
| } |
| |
| /* Insert value into MOVN/MOVZ/MOVK instruction. */ |
| insn |= encode_movw_imm (value & 0xffff); |
| |
| put_aarch64_insn (buf, insn); |
| } |
| break; |
| |
| case BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_LO12_NC: |
| fixP->fx_r_type = (ilp32_p |
| ? BFD_RELOC_AARCH64_TLSIE_LD32_GOTTPREL_LO12_NC |
| : BFD_RELOC_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC); |
| S_SET_THREAD_LOCAL (fixP->fx_addsy); |
| /* Should always be exported to object file, see |
| aarch64_force_relocation(). */ |
| gas_assert (!fixP->fx_done); |
| gas_assert (seg->use_rela_p); |
| break; |
| |
| case BFD_RELOC_AARCH64_TLSDESC_LD_LO12_NC: |
| fixP->fx_r_type = (ilp32_p |
| ? BFD_RELOC_AARCH64_TLSDESC_LD32_LO12_NC |
| : BFD_RELOC_AARCH64_TLSDESC_LD64_LO12_NC); |
| S_SET_THREAD_LOCAL (fixP->fx_addsy); |
| /* Should always be exported to object file, see |
| aarch64_force_relocation(). */ |
| gas_assert (!fixP->fx_done); |
| gas_assert (seg->use_rela_p); |
| break; |
| |
| case BFD_RELOC_AARCH64_TLSDESC_ADD_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21: |
| case BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21: |
| case BFD_RELOC_AARCH64_TLSDESC_LD32_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_LD64_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_LD_PREL19: |
| case BFD_RELOC_AARCH64_TLSGD_ADD_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSGD_ADR_PAGE21: |
| case BFD_RELOC_AARCH64_TLSGD_ADR_PREL21: |
| case BFD_RELOC_AARCH64_TLSGD_MOVW_G0_NC: |
| case BFD_RELOC_AARCH64_TLSGD_MOVW_G1: |
| case BFD_RELOC_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: |
| case BFD_RELOC_AARCH64_TLSIE_LD32_GOTTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_PREL19: |
| case BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC: |
| case BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_HI12: |
| case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_ADD_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_ADR_PAGE21: |
| case BFD_RELOC_AARCH64_TLSLD_ADR_PREL21: |
| case BFD_RELOC_AARCH64_TLSLD_LDST16_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_LDST8_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0_NC: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1_NC: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G2: |
| case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_HI12: |
| case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0_NC: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1_NC: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2: |
| S_SET_THREAD_LOCAL (fixP->fx_addsy); |
| /* Should always be exported to object file, see |
| aarch64_force_relocation(). */ |
| gas_assert (!fixP->fx_done); |
| gas_assert (seg->use_rela_p); |
| break; |
| |
| case BFD_RELOC_AARCH64_LD_GOT_LO12_NC: |
| /* Should always be exported to object file, see |
| aarch64_force_relocation(). */ |
| fixP->fx_r_type = (ilp32_p |
| ? BFD_RELOC_AARCH64_LD32_GOT_LO12_NC |
| : BFD_RELOC_AARCH64_LD64_GOT_LO12_NC); |
| gas_assert (!fixP->fx_done); |
| gas_assert (seg->use_rela_p); |
| break; |
| |
| case BFD_RELOC_AARCH64_ADD_LO12: |
| case BFD_RELOC_AARCH64_ADR_GOT_PAGE: |
| case BFD_RELOC_AARCH64_ADR_HI21_NC_PCREL: |
| case BFD_RELOC_AARCH64_ADR_HI21_PCREL: |
| case BFD_RELOC_AARCH64_GOT_LD_PREL19: |
| case BFD_RELOC_AARCH64_LD32_GOT_LO12_NC: |
| case BFD_RELOC_AARCH64_LD32_GOTPAGE_LO14: |
| case BFD_RELOC_AARCH64_LD64_GOTOFF_LO15: |
| case BFD_RELOC_AARCH64_LD64_GOTPAGE_LO15: |
| case BFD_RELOC_AARCH64_LD64_GOT_LO12_NC: |
| case BFD_RELOC_AARCH64_LDST128_LO12: |
| case BFD_RELOC_AARCH64_LDST16_LO12: |
| case BFD_RELOC_AARCH64_LDST32_LO12: |
| case BFD_RELOC_AARCH64_LDST64_LO12: |
| case BFD_RELOC_AARCH64_LDST8_LO12: |
| /* Should always be exported to object file, see |
| aarch64_force_relocation(). */ |
| gas_assert (!fixP->fx_done); |
| gas_assert (seg->use_rela_p); |
| break; |
| |
| case BFD_RELOC_AARCH64_TLSDESC_ADD: |
| case BFD_RELOC_AARCH64_TLSDESC_CALL: |
| case BFD_RELOC_AARCH64_TLSDESC_LDR: |
| break; |
| |
| case BFD_RELOC_UNUSED: |
| /* An error will already have been reported. */ |
| break; |
| |
| default: |
| as_bad_where (fixP->fx_file, fixP->fx_line, |
| _("unexpected %s fixup"), |
| bfd_get_reloc_code_name (fixP->fx_r_type)); |
| break; |
| } |
| |
| apply_fix_return: |
| /* Free the allocated the struct aarch64_inst. |
| N.B. currently there are very limited number of fix-up types actually use |
| this field, so the impact on the performance should be minimal . */ |
| if (fixP->tc_fix_data.inst != NULL) |
| free (fixP->tc_fix_data.inst); |
| |
| return; |
| } |
| |
| /* Translate internal representation of relocation info to BFD target |
| format. */ |
| |
| arelent * |
| tc_gen_reloc (asection * section, fixS * fixp) |
| { |
| arelent *reloc; |
| bfd_reloc_code_real_type code; |
| |
| reloc = XNEW (arelent); |
| |
| reloc->sym_ptr_ptr = XNEW (asymbol *); |
| *reloc->sym_ptr_ptr = symbol_get_bfdsym (fixp->fx_addsy); |
| reloc->address = fixp->fx_frag->fr_address + fixp->fx_where; |
| |
| if (fixp->fx_pcrel) |
| { |
| if (section->use_rela_p) |
| fixp->fx_offset -= md_pcrel_from_section (fixp, section); |
| else |
| fixp->fx_offset = reloc->address; |
| } |
| reloc->addend = fixp->fx_offset; |
| |
| code = fixp->fx_r_type; |
| switch (code) |
| { |
| case BFD_RELOC_16: |
| if (fixp->fx_pcrel) |
| code = BFD_RELOC_16_PCREL; |
| break; |
| |
| case BFD_RELOC_32: |
| if (fixp->fx_pcrel) |
| code = BFD_RELOC_32_PCREL; |
| break; |
| |
| case BFD_RELOC_64: |
| if (fixp->fx_pcrel) |
| code = BFD_RELOC_64_PCREL; |
| break; |
| |
| default: |
| break; |
| } |
| |
| reloc->howto = bfd_reloc_type_lookup (stdoutput, code); |
| if (reloc->howto == NULL) |
| { |
| as_bad_where (fixp->fx_file, fixp->fx_line, |
| _ |
| ("cannot represent %s relocation in this object file format"), |
| bfd_get_reloc_code_name (code)); |
| return NULL; |
| } |
| |
| return reloc; |
| } |
| |
| /* This fix_new is called by cons via TC_CONS_FIX_NEW. */ |
| |
| void |
| cons_fix_new_aarch64 (fragS * frag, int where, int size, expressionS * exp) |
| { |
| bfd_reloc_code_real_type type; |
| int pcrel = 0; |
| |
| /* Pick a reloc. |
| FIXME: @@ Should look at CPU word size. */ |
| switch (size) |
| { |
| case 1: |
| type = BFD_RELOC_8; |
| break; |
| case 2: |
| type = BFD_RELOC_16; |
| break; |
| case 4: |
| type = BFD_RELOC_32; |
| break; |
| case 8: |
| type = BFD_RELOC_64; |
| break; |
| default: |
| as_bad (_("cannot do %u-byte relocation"), size); |
| type = BFD_RELOC_UNUSED; |
| break; |
| } |
| |
| fix_new_exp (frag, where, (int) size, exp, pcrel, type); |
| } |
| |
| int |
| aarch64_force_relocation (struct fix *fixp) |
| { |
| switch (fixp->fx_r_type) |
| { |
| case BFD_RELOC_AARCH64_GAS_INTERNAL_FIXUP: |
| /* Perform these "immediate" internal relocations |
| even if the symbol is extern or weak. */ |
| return 0; |
| |
| case BFD_RELOC_AARCH64_LD_GOT_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_LD_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_LO12_NC: |
| /* Pseudo relocs that need to be fixed up according to |
| ilp32_p. */ |
| return 0; |
| |
| case BFD_RELOC_AARCH64_ADD_LO12: |
| case BFD_RELOC_AARCH64_ADR_GOT_PAGE: |
| case BFD_RELOC_AARCH64_ADR_HI21_NC_PCREL: |
| case BFD_RELOC_AARCH64_ADR_HI21_PCREL: |
| case BFD_RELOC_AARCH64_GOT_LD_PREL19: |
| case BFD_RELOC_AARCH64_LD32_GOT_LO12_NC: |
| case BFD_RELOC_AARCH64_LD32_GOTPAGE_LO14: |
| case BFD_RELOC_AARCH64_LD64_GOTOFF_LO15: |
| case BFD_RELOC_AARCH64_LD64_GOTPAGE_LO15: |
| case BFD_RELOC_AARCH64_LD64_GOT_LO12_NC: |
| case BFD_RELOC_AARCH64_LDST128_LO12: |
| case BFD_RELOC_AARCH64_LDST16_LO12: |
| case BFD_RELOC_AARCH64_LDST32_LO12: |
| case BFD_RELOC_AARCH64_LDST64_LO12: |
| case BFD_RELOC_AARCH64_LDST8_LO12: |
| case BFD_RELOC_AARCH64_TLSDESC_ADD_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21: |
| case BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21: |
| case BFD_RELOC_AARCH64_TLSDESC_LD32_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_LD64_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_LD_PREL19: |
| case BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC: |
| case BFD_RELOC_AARCH64_TLSDESC_OFF_G1: |
| case BFD_RELOC_AARCH64_TLSGD_ADD_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSGD_ADR_PAGE21: |
| case BFD_RELOC_AARCH64_TLSGD_ADR_PREL21: |
| case BFD_RELOC_AARCH64_TLSGD_MOVW_G0_NC: |
| case BFD_RELOC_AARCH64_TLSGD_MOVW_G1: |
| case BFD_RELOC_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21: |
| case BFD_RELOC_AARCH64_TLSIE_LD32_GOTTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_PREL19: |
| case BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC: |
| case BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_HI12: |
| case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_ADD_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_ADR_PAGE21: |
| case BFD_RELOC_AARCH64_TLSLD_ADR_PREL21: |
| case BFD_RELOC_AARCH64_TLSLD_LDST16_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_LDST8_DTPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0_NC: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1_NC: |
| case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G2: |
| case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_HI12: |
| case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0_NC: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1_NC: |
| case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2: |
| /* Always leave these relocations for the linker. */ |
| return 1; |
| |
| default: |
| break; |
| } |
| |
| return generic_force_reloc (fixp); |
| } |
| |
| #ifdef OBJ_ELF |
| |
| const char * |
| elf64_aarch64_target_format (void) |
| { |
| if (strcmp (TARGET_OS, "cloudabi") == 0) |
| { |
| /* FIXME: What to do for ilp32_p ? */ |
| return target_big_endian ? "elf64-bigaarch64-cloudabi" : "elf64-littleaarch64-cloudabi"; |
| } |
| if (target_big_endian) |
| return ilp32_p ? "elf32-bigaarch64" : "elf64-bigaarch64"; |
| else |
| return ilp32_p ? "elf32-littleaarch64" : "elf64-littleaarch64"; |
| } |
| |
| void |
| aarch64elf_frob_symbol (symbolS * symp, int *puntp) |
| { |
| elf_frob_symbol (symp, puntp); |
| } |
| #endif |
| |
| /* MD interface: Finalization. */ |
| |
| /* A good place to do this, although this was probably not intended |
| for this kind of use. We need to dump the literal pool before |
| references are made to a null symbol pointer. */ |
| |
| void |
| aarch64_cleanup (void) |
| { |
| literal_pool *pool; |
| |
| for (pool = list_of_pools; pool; pool = pool->next) |
| { |
| /* Put it at the end of the relevant section. */ |
| subseg_set (pool->section, pool->sub_section); |
| s_ltorg (0); |
| } |
| } |
| |
| #ifdef OBJ_ELF |
| /* Remove any excess mapping symbols generated for alignment frags in |
| SEC. We may have created a mapping symbol before a zero byte |
| alignment; remove it if there's a mapping symbol after the |
| alignment. */ |
| static void |
| check_mapping_symbols (bfd * abfd ATTRIBUTE_UNUSED, asection * sec, |
| void *dummy ATTRIBUTE_UNUSED) |
| { |
| segment_info_type *seginfo = seg_info (sec); |
| fragS *fragp; |
| |
| if (seginfo == NULL || seginfo->frchainP == NULL) |
| return; |
| |
| for (fragp = seginfo->frchainP->frch_root; |
| fragp != NULL; fragp = fragp->fr_next) |
| { |
| symbolS *sym = fragp->tc_frag_data.last_map; |
| fragS *next = fragp->fr_next; |
| |
| /* Variable-sized frags have been converted to fixed size by |
| this point. But if this was variable-sized to start with, |
| there will be a fixed-size frag after it. So don't handle |
| next == NULL. */ |
| if (sym == NULL || next == NULL) |
| continue; |
| |
| if (S_GET_VALUE (sym) < next->fr_address) |
| /* Not at the end of this frag. */ |
| continue; |
| know (S_GET_VALUE (sym) == next->fr_address); |
| |
| do |
| { |
| if (next->tc_frag_data.first_map != NULL) |
| { |
| /* Next frag starts with a mapping symbol. Discard this |
| one. */ |
| symbol_remove (sym, &symbol_rootP, &symbol_lastP); |
| break; |
| } |
| |
| if (next->fr_next == NULL) |
| { |
| /* This mapping symbol is at the end of the section. Discard |
| it. */ |
| know (next->fr_fix == 0 && next->fr_var == 0); |
| symbol_remove (sym, &symbol_rootP, &symbol_lastP); |
| break; |
| } |
| |
| /* As long as we have empty frags without any mapping symbols, |
| keep looking. */ |
| /* If the next frag is non-empty and does not start with a |
| mapping symbol, then this mapping symbol is required. */ |
| if (next->fr_address != next->fr_next->fr_address) |
| break; |
| |
| next = next->fr_next; |
| } |
| while (next != NULL); |
| } |
| } |
| #endif |
| |
| /* Adjust the symbol table. */ |
| |
| void |
| aarch64_adjust_symtab (void) |
| { |
| #ifdef OBJ_ELF |
| /* Remove any overlapping mapping symbols generated by alignment frags. */ |
| bfd_map_over_sections (stdoutput, check_mapping_symbols, (char *) 0); |
| /* Now do generic ELF adjustments. */ |
| elf_adjust_symtab (); |
| #endif |
| } |
| |
| static void |
| checked_hash_insert (struct hash_control *table, const char *key, void *value) |
| { |
| const char *hash_err; |
| |
| hash_err = hash_insert (table, key, value); |
| if (hash_err) |
| printf ("Internal Error: Can't hash %s\n", key); |
| } |
| |
| static void |
| fill_instruction_hash_table (void) |
| { |
| aarch64_opcode *opcode = aarch64_opcode_table; |
| |
| while (opcode->name != NULL) |
| { |
| templates *templ, *new_templ; |
| templ = hash_find (aarch64_ops_hsh, opcode->name); |
| |
| new_templ = XNEW (templates); |
| new_templ->opcode = opcode; |
| new_templ->next = NULL; |
| |
| if (!templ) |
| checked_hash_insert (aarch64_ops_hsh, opcode->name, (void *) new_templ); |
| else |
| { |
| new_templ->next = templ->next; |
| templ->next = new_templ; |
| } |
| ++opcode; |
| } |
| } |
| |
| static inline void |
| convert_to_upper (char *dst, const char *src, size_t num) |
| { |
| unsigned int i; |
| for (i = 0; i < num && *src != '\0'; ++i, ++dst, ++src) |
| *dst = TOUPPER (*src); |
| *dst = '\0'; |
| } |
| |
| /* Assume STR point to a lower-case string, allocate, convert and return |
| the corresponding upper-case string. */ |
| static inline const char* |
| get_upper_str (const char *str) |
| { |
| char *ret; |
| size_t len = strlen (str); |
| ret = XNEWVEC (char, len + 1); |
| convert_to_upper (ret, str, len); |
| return ret; |
| } |
| |
| /* MD interface: Initialization. */ |
| |
| void |
| md_begin (void) |
| { |
| unsigned mach; |
| unsigned int i; |
| |
| if ((aarch64_ops_hsh = hash_new ()) == NULL |
| || (aarch64_cond_hsh = hash_new ()) == NULL |
| || (aarch64_shift_hsh = hash_new ()) == NULL |
| || (aarch64_sys_regs_hsh = hash_new ()) == NULL |
| || (aarch64_pstatefield_hsh = hash_new ()) == NULL |
| || (aarch64_sys_regs_ic_hsh = hash_new ()) == NULL |
| || (aarch64_sys_regs_dc_hsh = hash_new ()) == NULL |
| || (aarch64_sys_regs_at_hsh = hash_new ()) == NULL |
| || (aarch64_sys_regs_tlbi_hsh = hash_new ()) == NULL |
| || (aarch64_reg_hsh = hash_new ()) == NULL |
| || (aarch64_barrier_opt_hsh = hash_new ()) == NULL |
| || (aarch64_nzcv_hsh = hash_new ()) == NULL |
| || (aarch64_pldop_hsh = hash_new ()) == NULL |
| || (aarch64_hint_opt_hsh = hash_new ()) == NULL) |
| as_fatal (_("virtual memory exhausted")); |
| |
| fill_instruction_hash_table (); |
| |
| for (i = 0; aarch64_sys_regs[i].name != NULL; ++i) |
| checked_hash_insert (aarch64_sys_regs_hsh, aarch64_sys_regs[i].name, |
| (void *) (aarch64_sys_regs + i)); |
| |
| for (i = 0; aarch64_pstatefields[i].name != NULL; ++i) |
| checked_hash_insert (aarch64_pstatefield_hsh, |
| aarch64_pstatefields[i].name, |
| (void *) (aarch64_pstatefields + i)); |
| |
| for (i = 0; aarch64_sys_regs_ic[i].name != NULL; i++) |
| checked_hash_insert (aarch64_sys_regs_ic_hsh, |
| aarch64_sys_regs_ic[i].name, |
| (void *) (aarch64_sys_regs_ic + i)); |
| |
| for (i = 0; aarch64_sys_regs_dc[i].name != NULL; i++) |
| checked_hash_insert (aarch64_sys_regs_dc_hsh, |
| aarch64_sys_regs_dc[i].name, |
| (void *) (aarch64_sys_regs_dc + i)); |
| |
| for (i = 0; aarch64_sys_regs_at[i].name != NULL; i++) |
| checked_hash_insert (aarch64_sys_regs_at_hsh, |
| aarch64_sys_regs_at[i].name, |
| (void *) (aarch64_sys_regs_at + i)); |
| |
| for (i = 0; aarch64_sys_regs_tlbi[i].name != NULL; i++) |
| checked_hash_insert (aarch64_sys_regs_tlbi_hsh, |
| aarch64_sys_regs_tlbi[i].name, |
| (void *) (aarch64_sys_regs_tlbi + i)); |
| |
| for (i = 0; i < ARRAY_SIZE (reg_names); i++) |
| checked_hash_insert (aarch64_reg_hsh, reg_names[i].name, |
| (void *) (reg_names + i)); |
| |
| for (i = 0; i < ARRAY_SIZE (nzcv_names); i++) |
| checked_hash_insert (aarch64_nzcv_hsh, nzcv_names[i].template, |
| (void *) (nzcv_names + i)); |
| |
| for (i = 0; aarch64_operand_modifiers[i].name != NULL; i++) |
| { |
| const char *name = aarch64_operand_modifiers[i].name; |
| checked_hash_insert (aarch64_shift_hsh, name, |
| (void *) (aarch64_operand_modifiers + i)); |
| /* Also hash the name in the upper case. */ |
| checked_hash_insert (aarch64_shift_hsh, get_upper_str (name), |
| (void *) (aarch64_operand_modifiers + i)); |
| } |
| |
| for (i = 0; i < ARRAY_SIZE (aarch64_conds); i++) |
| { |
| unsigned int j; |
| /* A condition code may have alias(es), e.g. "cc", "lo" and "ul" are |
| the same condition code. */ |
| for (j = 0; j < ARRAY_SIZE (aarch64_conds[i].names); ++j) |
| { |
| const char *name = aarch64_conds[i].names[j]; |
| if (name == NULL) |
| break; |
| checked_hash_insert (aarch64_cond_hsh, name, |
| (void *) (aarch64_conds + i)); |
| /* Also hash the name in the upper case. */ |
| checked_hash_insert (aarch64_cond_hsh, get_upper_str (name), |
| (void *) (aarch64_conds + i)); |
| } |
| } |
| |
| for (i = 0; i < ARRAY_SIZE (aarch64_barrier_options); i++) |
| { |
| const char *name = aarch64_barrier_options[i].name; |
| /* Skip xx00 - the unallocated values of option. */ |
| if ((i & 0x3) == 0) |
| continue; |
| checked_hash_insert (aarch64_barrier_opt_hsh, name, |
| (void *) (aarch64_barrier_options + i)); |
| /* Also hash the name in the upper case. */ |
| checked_hash_insert (aarch64_barrier_opt_hsh, get_upper_str (name), |
| (void *) (aarch64_barrier_options + i)); |
| } |
| |
| for (i = 0; i < ARRAY_SIZE (aarch64_prfops); i++) |
| { |
| const char* name = aarch64_prfops[i].name; |
| /* Skip the unallocated hint encodings. */ |
| if (name == NULL) |
| continue; |
| checked_hash_insert (aarch64_pldop_hsh, name, |
| (void *) (aarch64_prfops + i)); |
| /* Also hash the name in the upper case. */ |
| checked_hash_insert (aarch64_pldop_hsh, get_upper_str (name), |
| (void *) (aarch64_prfops + i)); |
| } |
| |
| for (i = 0; aarch64_hint_options[i].name != NULL; i++) |
| { |
| const char* name = aarch64_hint_options[i].name; |
| |
| checked_hash_insert (aarch64_hint_opt_hsh, name, |
| (void *) (aarch64_hint_options + i)); |
| /* Also hash the name in the upper case. */ |
| checked_hash_insert (aarch64_pldop_hsh, get_upper_str (name), |
| (void *) (aarch64_hint_options + i)); |
| } |
| |
| /* Set the cpu variant based on the command-line options. */ |
| if (!mcpu_cpu_opt) |
| mcpu_cpu_opt = march_cpu_opt; |
| |
| if (!mcpu_cpu_opt) |
| mcpu_cpu_opt = &cpu_default; |
| |
| cpu_variant = *mcpu_cpu_opt; |
| |
| /* Record the CPU type. */ |
| mach = ilp32_p ? bfd_mach_aarch64_ilp32 : bfd_mach_aarch64; |
| |
| bfd_set_arch_mach (stdoutput, TARGET_ARCH, mach); |
| } |
| |
| /* Command line processing. */ |
| |
| const char *md_shortopts = "m:"; |
| |
| #ifdef AARCH64_BI_ENDIAN |
| #define OPTION_EB (OPTION_MD_BASE + 0) |
| #define OPTION_EL (OPTION_MD_BASE + 1) |
| #else |
| #if TARGET_BYTES_BIG_ENDIAN |
| #define OPTION_EB (OPTION_MD_BASE + 0) |
| #else |
| #define OPTION_EL (OPTION_MD_BASE + 1) |
| #endif |
| #endif |
| |
| struct option md_longopts[] = { |
| #ifdef OPTION_EB |
| {"EB", no_argument, NULL, OPTION_EB}, |
| #endif |
| #ifdef OPTION_EL |
| {"EL", no_argument, NULL, OPTION_EL}, |
| #endif |
| {NULL, no_argument, NULL, 0} |
| }; |
| |
| size_t md_longopts_size = sizeof (md_longopts); |
| |
| struct aarch64_option_table |
| { |
| const char *option; /* Option name to match. */ |
| const char *help; /* Help information. */ |
| int *var; /* Variable to change. */ |
| int value; /* What to change it to. */ |
| char *deprecated; /* If non-null, print this message. */ |
| }; |
| |
| static struct aarch64_option_table aarch64_opts[] = { |
| {"mbig-endian", N_("assemble for big-endian"), &target_big_endian, 1, NULL}, |
| {"mlittle-endian", N_("assemble for little-endian"), &target_big_endian, 0, |
| NULL}, |
| #ifdef DEBUG_AARCH64 |
| {"mdebug-dump", N_("temporary switch for dumping"), &debug_dump, 1, NULL}, |
| #endif /* DEBUG_AARCH64 */ |
| {"mverbose-error", N_("output verbose error messages"), &verbose_error_p, 1, |
| NULL}, |
| {"mno-verbose-error", N_("do not output verbose error messages"), |
| &verbose_error_p, 0, NULL}, |
| {NULL, NULL, NULL, 0, NULL} |
| }; |
| |
| struct aarch64_cpu_option_table |
| { |
| const char *name; |
| const aarch64_feature_set value; |
| /* The canonical name of the CPU, or NULL to use NAME converted to upper |
| case. */ |
| const char *canonical_name; |
| }; |
| |
| /* This list should, at a minimum, contain all the cpu names |
| recognized by GCC. */ |
| static const struct aarch64_cpu_option_table aarch64_cpus[] = { |
| {"all", AARCH64_ANY, NULL}, |
| {"cortex-a35", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC), "Cortex-A35"}, |
| {"cortex-a53", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC), "Cortex-A53"}, |
| {"cortex-a57", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC), "Cortex-A57"}, |
| {"cortex-a72", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC), "Cortex-A72"}, |
| {"cortex-a73", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC), "Cortex-A73"}, |
| {"exynos-m1", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC | AARCH64_FEATURE_CRYPTO), |
| "Samsung Exynos M1"}, |
| {"qdf24xx", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC | AARCH64_FEATURE_CRYPTO), |
| "Qualcomm QDF24XX"}, |
| {"thunderx", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC | AARCH64_FEATURE_CRYPTO), |
| "Cavium ThunderX"}, |
| {"vulcan", AARCH64_FEATURE (AARCH64_ARCH_V8_1, |
| AARCH64_FEATURE_CRYPTO), |
| "Broadcom Vulcan"}, |
| /* The 'xgene-1' name is an older name for 'xgene1', which was used |
| in earlier releases and is superseded by 'xgene1' in all |
| tools. */ |
| {"xgene-1", AARCH64_ARCH_V8, "APM X-Gene 1"}, |
| {"xgene1", AARCH64_ARCH_V8, "APM X-Gene 1"}, |
| {"xgene2", AARCH64_FEATURE (AARCH64_ARCH_V8, |
| AARCH64_FEATURE_CRC), "APM X-Gene 2"}, |
| {"generic", AARCH64_ARCH_V8, NULL}, |
| |
| {NULL, AARCH64_ARCH_NONE, NULL} |
| }; |
| |
| struct aarch64_arch_option_table |
| { |
| const char *name; |
| const aarch64_feature_set value; |
| }; |
| |
| /* This list should, at a minimum, contain all the architecture names |
| recognized by GCC. */ |
| static const struct aarch64_arch_option_table aarch64_archs[] = { |
| {"all", AARCH64_ANY}, |
| {"armv8-a", AARCH64_ARCH_V8}, |
| {"armv8.1-a", AARCH64_ARCH_V8_1}, |
| {"armv8.2-a", AARCH64_ARCH_V8_2}, |
| {NULL, AARCH64_ARCH_NONE} |
| }; |
| |
| /* ISA extensions. */ |
| struct aarch64_option_cpu_value_table |
| { |
| const char *name; |
| const aarch64_feature_set value; |
| }; |
| |
| static const struct aarch64_option_cpu_value_table aarch64_features[] = { |
| {"crc", AARCH64_FEATURE (AARCH64_FEATURE_CRC, 0)}, |
| {"crypto", AARCH64_FEATURE (AARCH64_FEATURE_CRYPTO, 0)}, |
| {"fp", AARCH64_FEATURE (AARCH64_FEATURE_FP, 0)}, |
| {"lse", AARCH64_FEATURE (AARCH64_FEATURE_LSE, 0)}, |
| {"simd", AARCH64_FEATURE (AARCH64_FEATURE_SIMD, 0)}, |
| {"pan", AARCH64_FEATURE (AARCH64_FEATURE_PAN, 0)}, |
| {"lor", AARCH64_FEATURE (AARCH64_FEATURE_LOR, 0)}, |
| {"ras", AARCH64_FEATURE (AARCH64_FEATURE_RAS, 0)}, |
| {"rdma", AARCH64_FEATURE (AARCH64_FEATURE_SIMD |
| | AARCH64_FEATURE_RDMA, 0)}, |
| {"fp16", AARCH64_FEATURE (AARCH64_FEATURE_F16 |
| | AARCH64_FEATURE_FP, 0)}, |
| {"profile", AARCH64_FEATURE (AARCH64_FEATURE_PROFILE, 0)}, |
| {NULL, AARCH64_ARCH_NONE} |
| }; |
| |
| struct aarch64_long_option_table |
| { |
| const char *option; /* Substring to match. */ |
| const char *help; /* Help information. */ |
| int (*func) (const char *subopt); /* Function to decode sub-option. */ |
| char *deprecated; /* If non-null, print this message. */ |
| }; |
| |
| static int |
| aarch64_parse_features (const char *str, const aarch64_feature_set **opt_p, |
| bfd_boolean ext_only) |
| { |
| /* We insist on extensions being added before being removed. We achieve |
| this by using the ADDING_VALUE variable to indicate whether we are |
| adding an extension (1) or removing it (0) and only allowing it to |
| change in the order -1 -> 1 -> 0. */ |
| int adding_value = -1; |
| aarch64_feature_set *ext_set = XNEW (aarch64_feature_set); |
| |
| /* Copy the feature set, so that we can modify it. */ |
| *ext_set = **opt_p; |
| *opt_p = ext_set; |
| |
| while (str != NULL && *str != 0) |
| { |
| const struct aarch64_option_cpu_value_table *opt; |
| const char *ext = NULL; |
| int optlen; |
| |
| if (!ext_only) |
| { |
| if (*str != '+') |
| { |
| as_bad (_("invalid architectural extension")); |
| return 0; |
| } |
| |
| ext = strchr (++str, '+'); |
| } |
| |
| if (ext != NULL) |
| optlen = ext - str; |
| else |
| optlen = strlen (str); |
| |
| if (optlen >= 2 && strncmp (str, "no", 2) == 0) |
| { |
| if (adding_value != 0) |
| adding_value = 0; |
| optlen -= 2; |
| str += 2; |
| } |
| else if (optlen > 0) |
| { |
| if (adding_value == -1) |
| adding_value = 1; |
| else if (adding_value != 1) |
| { |
| as_bad (_("must specify extensions to add before specifying " |
| "those to remove")); |
| return FALSE; |
| } |
| } |
| |
| if (optlen == 0) |
| { |
| as_bad (_("missing architectural extension")); |
| return 0; |
| } |
| |
| gas_assert (adding_value != -1); |
| |
| for (opt = aarch64_features; opt->name != NULL; opt++) |
| if (strncmp (opt->name, str, optlen) == 0) |
| { |
| /* Add or remove the extension. */ |
| if (adding_value) |
| AARCH64_MERGE_FEATURE_SETS (*ext_set, *ext_set, opt->value); |
| else |
| AARCH64_CLEAR_FEATURE (*ext_set, *ext_set, opt->value); |
| break; |
| } |
| |
| if (opt->name == NULL) |
| { |
| as_bad (_("unknown architectural extension `%s'"), str); |
| return 0; |
| } |
| |
| str = ext; |
| }; |
| |
| return 1; |
| } |
| |
| static int |
| aarch64_parse_cpu (const char *str) |
| { |
| const struct aarch64_cpu_option_table *opt; |
| const char *ext = strchr (str, '+'); |
| size_t optlen; |
| |
| if (ext != NULL) |
| optlen = ext - str; |
| else |
| optlen = strlen (str); |
| |
| if (optlen == 0) |
| { |
| as_bad (_("missing cpu name `%s'"), str); |
| return 0; |
| } |
| |
| for (opt = aarch64_cpus; opt->name != NULL; opt++) |
| if (strlen (opt->name) == optlen && strncmp (str, opt->name, optlen) == 0) |
| { |
| mcpu_cpu_opt = &opt->value; |
| if (ext != NULL) |
| return aarch64_parse_features (ext, &mcpu_cpu_opt, FALSE); |
| |
| return 1; |
| } |
| |
| as_bad (_("unknown cpu `%s'"), str); |
| return 0; |
| } |
| |
| static int |
| aarch64_parse_arch (const char *str) |
| { |
| const struct aarch64_arch_option_table *opt; |
| const char *ext = strchr (str, '+'); |
| size_t optlen; |
| |
| if (ext != NULL) |
| optlen = ext - str; |
| else |
| optlen = strlen (str); |
| |
| if (optlen == 0) |
| { |
| as_bad (_("missing architecture name `%s'"), str); |
| return 0; |
| } |
| |
| for (opt = aarch64_archs; opt->name != NULL; opt++) |
| if (strlen (opt->name) == optlen && strncmp (str, opt->name, optlen) == 0) |
| { |
| march_cpu_opt = &opt->value; |
| if (ext != NULL) |
| return aarch64_parse_features (ext, &march_cpu_opt, FALSE); |
| |
| return 1; |
| } |
| |
| as_bad (_("unknown architecture `%s'\n"), str); |
| return 0; |
| } |
| |
| /* ABIs. */ |
| struct aarch64_option_abi_value_table |
| { |
| const char *name; |
| enum aarch64_abi_type value; |
| }; |
| |
| static const struct aarch64_option_abi_value_table aarch64_abis[] = { |
| {"ilp32", AARCH64_ABI_ILP32}, |
| {"lp64", AARCH64_ABI_LP64}, |
| }; |
| |
| static int |
| aarch64_parse_abi (const char *str) |
| { |
| unsigned int i; |
| |
| if (str[0] == '\0') |
| { |
| as_bad (_("missing abi name `%s'"), str); |
| return 0; |
| } |
| |
| for (i = 0; i < ARRAY_SIZE (aarch64_abis); i++) |
| if (strcmp (str, aarch64_abis[i].name) == 0) |
| { |
| aarch64_abi = aarch64_abis[i].value; |
| return 1; |
| } |
| |
| as_bad (_("unknown abi `%s'\n"), str); |
| return 0; |
| } |
| |
| static struct aarch64_long_option_table aarch64_long_opts[] = { |
| #ifdef OBJ_ELF |
| {"mabi=", N_("<abi name>\t specify for ABI <abi name>"), |
| aarch64_parse_abi, NULL}, |
| #endif /* OBJ_ELF */ |
| {"mcpu=", N_("<cpu name>\t assemble for CPU <cpu name>"), |
| aarch64_parse_cpu, NULL}, |
| {"march=", N_("<arch name>\t assemble for architecture <arch name>"), |
| aarch64_parse_arch, NULL}, |
| {NULL, NULL, 0, NULL} |
| }; |
| |
| int |
| md_parse_option (int c, const char *arg) |
| { |
| struct aarch64_option_table *opt; |
| struct aarch64_long_option_table *lopt; |
| |
| switch (c) |
| { |
| #ifdef OPTION_EB |
| case OPTION_EB: |
| target_big_endian = 1; |
| break; |
| #endif |
| |
| #ifdef OPTION_EL |
| case OPTION_EL: |
| target_big_endian = 0; |
| break; |
| #endif |
| |
| case 'a': |
| /* Listing option. Just ignore these, we don't support additional |
| ones. */ |
| return 0; |
| |
| default: |
| for (opt = aarch64_opts; opt->option != NULL; opt++) |
| { |
| if (c == opt->option[0] |
| && ((arg == NULL && opt->option[1] == 0) |
| || streq (arg, opt->option + 1))) |
| { |
| /* If the option is deprecated, tell the user. */ |
| if (opt->deprecated != NULL) |
| as_tsktsk (_("option `-%c%s' is deprecated: %s"), c, |
| arg ? arg : "", _(opt->deprecated)); |
| |
| if (opt->var != NULL) |
| *opt->var = opt->value; |
| |
| return 1; |
| } |
| } |
| |
| for (lopt = aarch64_long_opts; lopt->option != NULL; lopt++) |
| { |
| /* These options are expected to have an argument. */ |
| if (c == lopt->option[0] |
| && arg != NULL |
| && strncmp (arg, lopt->option + 1, |
| strlen (lopt->option + 1)) == 0) |
| { |
| /* If the option is deprecated, tell the user. */ |
| if (lopt->deprecated != NULL) |
| as_tsktsk (_("option `-%c%s' is deprecated: %s"), c, arg, |
| _(lopt->deprecated)); |
| |
| /* Call the sup-option parser. */ |
| return lopt->func (arg + strlen (lopt->option) - 1); |
| } |
| } |
| |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| void |
| md_show_usage (FILE * fp) |
| { |
| struct aarch64_option_table *opt; |
| struct aarch64_long_option_table *lopt; |
| |
| fprintf (fp, _(" AArch64-specific assembler options:\n")); |
| |
| for (opt = aarch64_opts; opt->option != NULL; opt++) |
| if (opt->help != NULL) |
| fprintf (fp, " -%-23s%s\n", opt->option, _(opt->help)); |
| |
| for (lopt = aarch64_long_opts; lopt->option != NULL; lopt++) |
| if (lopt->help != NULL) |
| fprintf (fp, " -%s%s\n", lopt->option, _(lopt->help)); |
| |
| #ifdef OPTION_EB |
| fprintf (fp, _("\ |
| -EB assemble code for a big-endian cpu\n")); |
| #endif |
| |
| #ifdef OPTION_EL |
| fprintf (fp, _("\ |
| -EL assemble code for a little-endian cpu\n")); |
| #endif |
| } |
| |
| /* Parse a .cpu directive. */ |
| |
| static void |
| s_aarch64_cpu (int ignored ATTRIBUTE_UNUSED) |
| { |
| const struct aarch64_cpu_option_table *opt; |
| char saved_char; |
| char *name; |
| char *ext; |
| size_t optlen; |
| |
| name = input_line_pointer; |
| while (*input_line_pointer && !ISSPACE (*input_line_pointer)) |
| input_line_pointer++; |
| saved_char = *input_line_pointer; |
| *input_line_pointer = 0; |
| |
| ext = strchr (name, '+'); |
| |
| if (ext != NULL) |
| optlen = ext - name; |
| else |
| optlen = strlen (name); |
| |
| /* Skip the first "all" entry. */ |
| for (opt = aarch64_cpus + 1; opt->name != NULL; opt++) |
| if (strlen (opt->name) == optlen |
| && strncmp (name, opt->name, optlen) == 0) |
| { |
| mcpu_cpu_opt = &opt->value; |
| if (ext != NULL) |
| if (!aarch64_parse_features (ext, &mcpu_cpu_opt, FALSE)) |
| return; |
| |
| cpu_variant = *mcpu_cpu_opt; |
| |
| *input_line_pointer = saved_char; |
| demand_empty_rest_of_line (); |
| return; |
| } |
| as_bad (_("unknown cpu `%s'"), name); |
| *input_line_pointer = saved_char; |
| ignore_rest_of_line (); |
| } |
| |
| |
| /* Parse a .arch directive. */ |
| |
| static void |
| s_aarch64_arch (int ignored ATTRIBUTE_UNUSED) |
| { |
| const struct aarch64_arch_option_table *opt; |
| char saved_char; |
| char *name; |
| char *ext; |
| size_t optlen; |
| |
| name = input_line_pointer; |
| while (*input_line_pointer && !ISSPACE (*input_line_pointer)) |
| input_line_pointer++; |
| saved_char = *input_line_pointer; |
| *input_line_pointer = 0; |
| |
| ext = strchr (name, '+'); |
| |
| if (ext != NULL) |
| optlen = ext - name; |
| else |
| optlen = strlen (name); |
| |
| /* Skip the first "all" entry. */ |
| for (opt = aarch64_archs + 1; opt->name != NULL; opt++) |
| if (strlen (opt->name) == optlen |
| && strncmp (name, opt->name, optlen) == 0) |
| { |
| mcpu_cpu_opt = &opt->value; |
| if (ext != NULL) |
| if (!aarch64_parse_features (ext, &mcpu_cpu_opt, FALSE)) |
| return; |
| |
| cpu_variant = *mcpu_cpu_opt; |
| |
| *input_line_pointer = saved_char; |
| demand_empty_rest_of_line (); |
| return; |
| } |
| |
| as_bad (_("unknown architecture `%s'\n"), name); |
| *input_line_pointer = saved_char; |
| ignore_rest_of_line (); |
| } |
| |
| /* Parse a .arch_extension directive. */ |
| |
| static void |
| s_aarch64_arch_extension (int ignored ATTRIBUTE_UNUSED) |
| { |
| char saved_char; |
| char *ext = input_line_pointer;; |
| |
| while (*input_line_pointer && !ISSPACE (*input_line_pointer)) |
| input_line_pointer++; |
| saved_char = *input_line_pointer; |
| *input_line_pointer = 0; |
| |
| if (!aarch64_parse_features (ext, &mcpu_cpu_opt, TRUE)) |
| return; |
| |
| cpu_variant = *mcpu_cpu_opt; |
| |
| *input_line_pointer = saved_char; |
| demand_empty_rest_of_line (); |
| } |
| |
| /* Copy symbol information. */ |
| |
| void |
| aarch64_copy_symbol_attributes (symbolS * dest, symbolS * src) |
| { |
| AARCH64_GET_FLAG (dest) = AARCH64_GET_FLAG (src); |
| } |