| /* tc-aarch64.c -- Assemble for the AArch64 ISA |
| |
| Copyright (C) 2009-2021 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 <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; |
| |
| /* Currently active instruction sequence. */ |
| static aarch64_instr_sequence *insn_sequence = NULL; |
| |
| #ifdef OBJ_ELF |
| /* Pre-defined "_GLOBAL_OFFSET_TABLE_" */ |
| static symbolS *GOT_symbol; |
| |
| /* Which ABI to use. */ |
| enum aarch64_abi_type |
| { |
| AARCH64_ABI_NONE = 0, |
| AARCH64_ABI_LP64 = 1, |
| AARCH64_ABI_ILP32 = 2 |
| }; |
| |
| #ifndef DEFAULT_ARCH |
| #define DEFAULT_ARCH "aarch64" |
| #endif |
| |
| /* DEFAULT_ARCH is initialized in gas/configure.tgt. */ |
| static const char *default_arch = DEFAULT_ARCH; |
| |
| /* AArch64 ABI for the output file. */ |
| static enum aarch64_abi_type aarch64_abi = AARCH64_ABI_NONE; |
| |
| /* 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 vector_el_type |
| { |
| NT_invtype = -1, |
| NT_b, |
| NT_h, |
| NT_s, |
| NT_d, |
| NT_q, |
| NT_zero, |
| NT_merge |
| }; |
| |
| /* Bits for DEFINED field in vector_type_el. */ |
| #define NTA_HASTYPE 1 |
| #define NTA_HASINDEX 2 |
| #define NTA_HASVARWIDTH 4 |
| |
| struct vector_type_el |
| { |
| enum vector_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 bool parse_operands (char *, const aarch64_opcode *); |
| static bool programmer_friendly_fixup (aarch64_instruction *); |
| |
| #ifdef OBJ_ELF |
| # define now_instr_sequence seg_info \ |
| (now_seg)->tc_segment_info_data.insn_sequence |
| #else |
| static struct aarch64_instr_sequence now_instr_sequence; |
| #endif |
| |
| /* Diagnostics inline function utilities. |
| |
| These are lightweight utilities 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 appropriate |
| 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 bool |
| 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); |
| } |
| |
| /* 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; |
| 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(VN) /* v[0-31] */ \ |
| BASIC_REG_TYPE(ZN) /* z[0-31] */ \ |
| BASIC_REG_TYPE(PN) /* p[0-15] */ \ |
| /* Typecheck: any 64-bit int reg (inc SP exc XZR). */ \ |
| MULTI_REG_TYPE(R64_SP, REG_TYPE(R_64) | REG_TYPE(SP_64)) \ |
| /* Typecheck: same, plus SVE registers. */ \ |
| MULTI_REG_TYPE(SVE_BASE, REG_TYPE(R_64) | REG_TYPE(SP_64) \ |
| | REG_TYPE(ZN)) \ |
| /* Typecheck: x[0-30], w[0-30] or [xw]zr. */ \ |
| MULTI_REG_TYPE(R_Z, REG_TYPE(R_32) | REG_TYPE(R_64) \ |
| | REG_TYPE(Z_32) | REG_TYPE(Z_64)) \ |
| /* Typecheck: same, plus SVE registers. */ \ |
| MULTI_REG_TYPE(SVE_OFFSET, REG_TYPE(R_32) | REG_TYPE(R_64) \ |
| | REG_TYPE(Z_32) | REG_TYPE(Z_64) \ |
| | REG_TYPE(ZN)) \ |
| /* Typecheck: x[0-30], w[0-30] or {w}sp. */ \ |
| MULTI_REG_TYPE(R_SP, REG_TYPE(R_32) | REG_TYPE(R_64) \ |
| | REG_TYPE(SP_32) | 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)) \ |
| /* Typecheck: as above, but also Zn, Pn, and {W}SP. This should only \ |
| be used for SVE instructions, since Zn and Pn are valid symbols \ |
| in other contexts. */ \ |
| MULTI_REG_TYPE(R_Z_SP_BHSDQ_VZP, REG_TYPE(R_32) | REG_TYPE(R_64) \ |
| | REG_TYPE(SP_32) | REG_TYPE(SP_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) \ |
| | REG_TYPE(ZN) | REG_TYPE(PN)) \ |
| /* 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_R64_SP: |
| msg = N_("64-bit integer or SP register expected"); |
| break; |
| case REG_TYPE_SVE_BASE: |
| msg = N_("base register expected"); |
| break; |
| case REG_TYPE_R_Z: |
| msg = N_("integer or zero register expected"); |
| break; |
| case REG_TYPE_SVE_OFFSET: |
| msg = N_("offset register expected"); |
| break; |
| case REG_TYPE_R_SP: |
| msg = N_("integer or SP 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_R_Z_BHSDQ_V: |
| case REG_TYPE_R_Z_SP_BHSDQ_VZP: |
| 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; |
| case REG_TYPE_ZN: |
| msg = N_("SVE vector register expected"); |
| break; |
| case REG_TYPE_PN: |
| msg = N_("SVE predicate 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 |
| #define REG_ZR 31 |
| |
| /* Instructions take 4 bytes in the object file. */ |
| #define INSN_SIZE 4 |
| |
| static htab_t aarch64_ops_hsh; |
| static htab_t aarch64_cond_hsh; |
| static htab_t aarch64_shift_hsh; |
| static htab_t aarch64_sys_regs_hsh; |
| static htab_t aarch64_pstatefield_hsh; |
| static htab_t aarch64_sys_regs_ic_hsh; |
| static htab_t aarch64_sys_regs_dc_hsh; |
| static htab_t aarch64_sys_regs_at_hsh; |
| static htab_t aarch64_sys_regs_tlbi_hsh; |
| static htab_t aarch64_sys_regs_sr_hsh; |
| static htab_t aarch64_reg_hsh; |
| static htab_t aarch64_barrier_opt_hsh; |
| static htab_t aarch64_nzcv_hsh; |
| static htab_t aarch64_pldop_hsh; |
| static htab_t 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[] = "rRsSfFdDxXeEpPhHb"; |
| |
| /* 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 bool |
| 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 bool in_aarch64_get_expression = false; |
| |
| /* Third argument to aarch64_get_expression. */ |
| #define GE_NO_PREFIX false |
| #define GE_OPT_PREFIX true |
| |
| /* Fourth argument to aarch64_get_expression. */ |
| #define ALLOW_ABSENT false |
| #define REJECT_ABSENT true |
| |
| /* Fifth argument to aarch64_get_expression. */ |
| #define NORMAL_RESOLUTION false |
| |
| /* 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. |
| |
| If ALLOW_IMMEDIATE_PREFIX is true then skip a '#' at the start. |
| If REJECT_ABSENT is true then trat missing expressions as an error. |
| If DEFER_RESOLUTION is true, then do not resolve expressions against |
| constant symbols. Necessary if the expression is part of a fixup |
| that uses a reloc that must be emitted. */ |
| |
| static bool |
| aarch64_get_expression (expressionS * ep, |
| char ** str, |
| bool allow_immediate_prefix, |
| bool reject_absent, |
| bool defer_resolution) |
| { |
| char *save_in; |
| segT seg; |
| bool prefix_present = false; |
| |
| if (allow_immediate_prefix) |
| { |
| if (is_immediate_prefix (**str)) |
| { |
| (*str)++; |
| prefix_present = true; |
| } |
| } |
| |
| memset (ep, 0, sizeof (expressionS)); |
| |
| save_in = input_line_pointer; |
| input_line_pointer = *str; |
| in_aarch64_get_expression = true; |
| if (defer_resolution) |
| seg = deferred_expression (ep); |
| else |
| seg = expression (ep); |
| in_aarch64_get_expression = 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 && ! 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_aarch64_get_expression) |
| 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); |
| } |
| |
| /* Similar 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 *) str_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 bool |
| aarch64_check_reg_type (const reg_entry *reg, aarch64_reg_type type) |
| { |
| return (reg_type_masks[type] & (1 << reg->type)) != 0; |
| } |
| |
| /* Try to parse a base or offset register. Allow SVE base and offset |
| registers if REG_TYPE includes SVE registers. Return the register |
| entry on success, setting *QUALIFIER to the register qualifier. |
| Return null otherwise. |
| |
| Note that this function does not issue any diagnostics. */ |
| |
| static const reg_entry * |
| aarch64_addr_reg_parse (char **ccp, aarch64_reg_type reg_type, |
| aarch64_opnd_qualifier_t *qualifier) |
| { |
| char *str = *ccp; |
| const reg_entry *reg = parse_reg (&str); |
| |
| if (reg == NULL) |
| return NULL; |
| |
| switch (reg->type) |
| { |
| case REG_TYPE_R_32: |
| case REG_TYPE_SP_32: |
| case REG_TYPE_Z_32: |
| *qualifier = AARCH64_OPND_QLF_W; |
| break; |
| |
| case REG_TYPE_R_64: |
| case REG_TYPE_SP_64: |
| case REG_TYPE_Z_64: |
| *qualifier = AARCH64_OPND_QLF_X; |
| break; |
| |
| case REG_TYPE_ZN: |
| if ((reg_type_masks[reg_type] & (1 << REG_TYPE_ZN)) == 0 |
| || str[0] != '.') |
| return NULL; |
| switch (TOLOWER (str[1])) |
| { |
| case 's': |
| *qualifier = AARCH64_OPND_QLF_S_S; |
| break; |
| case 'd': |
| *qualifier = AARCH64_OPND_QLF_S_D; |
| break; |
| default: |
| return NULL; |
| } |
| str += 2; |
| break; |
| |
| default: |
| return NULL; |
| } |
| |
| *ccp = str; |
| |
| return reg; |
| } |
| |
| /* Try to parse a base or offset register. Return the register entry |
| on success, setting *QUALIFIER to the register qualifier. Return null |
| otherwise. |
| |
| Note that this function does not issue any diagnostics. */ |
| |
| static const reg_entry * |
| aarch64_reg_parse_32_64 (char **ccp, aarch64_opnd_qualifier_t *qualifier) |
| { |
| return aarch64_addr_reg_parse (ccp, REG_TYPE_R_Z_SP, qualifier); |
| } |
| |
| /* Parse the qualifier of a vector register or vector element of type |
| REG_TYPE. Fill in *PARSED_TYPE and return TRUE if the parsing |
| succeeds; otherwise return FALSE. |
| |
| Accept only one occurrence of: |
| 4b 8b 16b 2h 4h 8h 2s 4s 1d 2d |
| b h s d q */ |
| static bool |
| parse_vector_type_for_operand (aarch64_reg_type reg_type, |
| struct vector_type_el *parsed_type, char **str) |
| { |
| char *ptr = *str; |
| unsigned width; |
| unsigned element_size; |
| enum vector_el_type type; |
| |
| /* skip '.' */ |
| gas_assert (*ptr == '.'); |
| ptr++; |
| |
| if (reg_type == REG_TYPE_ZN || reg_type == REG_TYPE_PN || !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 (reg_type == REG_TYPE_ZN || 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) |
| && !(width == 4 && element_size == 8)) |
| { |
| 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; |
| } |
| |
| /* *STR contains an SVE zero/merge predication suffix. Parse it into |
| *PARSED_TYPE and point *STR at the end of the suffix. */ |
| |
| static bool |
| parse_predication_for_operand (struct vector_type_el *parsed_type, char **str) |
| { |
| char *ptr = *str; |
| |
| /* Skip '/'. */ |
| gas_assert (*ptr == '/'); |
| ptr++; |
| switch (TOLOWER (*ptr)) |
| { |
| case 'z': |
| parsed_type->type = NT_zero; |
| break; |
| case 'm': |
| parsed_type->type = NT_merge; |
| break; |
| default: |
| if (*ptr != '\0' && *ptr != ',') |
| first_error_fmt (_("unexpected character `%c' in predication type"), |
| *ptr); |
| else |
| first_error (_("missing predication type")); |
| return false; |
| } |
| parsed_type->width = 0; |
| *str = ptr + 1; |
| 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 vector_type_el *typeinfo, bool in_reg_list) |
| { |
| char *str = *ccp; |
| const reg_entry *reg = parse_reg (&str); |
| struct vector_type_el atype; |
| struct vector_type_el parsetype; |
| bool 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 || type == REG_TYPE_ZN || type == REG_TYPE_PN) |
| && (*str == '.' || (type == REG_TYPE_PN && *str == '/'))) |
| { |
| if (*str == '.') |
| { |
| if (!parse_vector_type_for_operand (type, &parsetype, &str)) |
| return PARSE_FAIL; |
| } |
| else |
| { |
| if (!parse_predication_for_operand (&parsetype, &str)) |
| return PARSE_FAIL; |
| } |
| |
| /* Register if of the form Vn.[bhsdq]. */ |
| is_typed_vecreg = true; |
| |
| if (type == REG_TYPE_ZN || type == REG_TYPE_PN) |
| { |
| /* The width is always variable; we don't allow an integer width |
| to be specified. */ |
| gas_assert (parsetype.width == 0); |
| atype.defined |= NTA_HASVARWIDTH | NTA_HASTYPE; |
| } |
| else 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 of course. */ |
| 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) |
| { |
| first_error (_("index not allowed inside register list")); |
| return PARSE_FAIL; |
| } |
| |
| atype.defined |= NTA_HASINDEX; |
| |
| aarch64_get_expression (&exp, &str, GE_NO_PREFIX, REJECT_ABSENT, |
| NORMAL_RESOLUTION); |
| |
| 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 vector_type_el *vectype) |
| { |
| struct vector_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 bool |
| eq_vector_type_el (struct vector_type_el e1, struct vector_type_el e2) |
| { |
| return |
| e1.type == e2.type |
| && e1.defined == e2.defined |
| && e1.width == e2.width && e1.index == e2.index; |
| } |
| |
| /* This function parses a list of vector registers of type TYPE. |
| 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_vector_reg_list (char **ccp, aarch64_reg_type type, |
| struct vector_type_el *vectype) |
| { |
| char *str = *ccp; |
| int nb_regs; |
| struct vector_type_el typeinfo, typeinfo_first; |
| int val, val_range; |
| int in_range; |
| int ret_val; |
| int i; |
| bool error = false; |
| bool 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, type, 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 (type == REG_TYPE_VN && 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_vector_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; |
| |
| aarch64_get_expression (&exp, &str, GE_NO_PREFIX, REJECT_ABSENT, |
| NORMAL_RESOLUTION); |
| 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 = str_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; |
| |
| str_hash_insert (aarch64_reg_hsh, name, new, 0); |
| |
| 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 bool |
| 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 (!startswith (oldname, " .req ")) |
| return false; |
| |
| oldname += 6; |
| if (*oldname == '\0') |
| return false; |
| |
| old = str_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 = str_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; |
| |
| str_hash_delete (aarch64_reg_hsh, name); |
| 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 = str_hash_find (aarch64_reg_hsh, nbuf); |
| if (reg) |
| { |
| str_hash_delete (aarch64_reg_hsh, nbuf); |
| free ((char *) reg->name); |
| free (reg); |
| } |
| |
| for (p = nbuf; *p; p++) |
| *p = TOLOWER (*p); |
| reg = str_hash_find (aarch64_reg_hsh, nbuf); |
| if (reg) |
| { |
| str_hash_delete (aarch64_reg_hsh, nbuf); |
| 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, frag, value); |
| 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, |
| &zero_address_frag, 0); |
| 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 bool |
| 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. That's 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; |
| |
| /* Align pool as you have word accesses. |
| Only make a frag if we have to. */ |
| if (!need_pass_2) |
| frag_align (align, 0, 0); |
| |
| mapping_state (MAP_DATA); |
| |
| 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 (); |
| } |
| |
| /* Mark symbol that it follows a variant PCS convention. */ |
| |
| static void |
| s_variant_pcs (int ignored ATTRIBUTE_UNUSED) |
| { |
| char *name; |
| char c; |
| symbolS *sym; |
| asymbol *bfdsym; |
| elf_symbol_type *elfsym; |
| |
| c = get_symbol_name (&name); |
| if (!*name) |
| as_bad (_("Missing symbol name in directive")); |
| sym = symbol_find_or_make (name); |
| restore_line_pointer (c); |
| demand_empty_rest_of_line (); |
| bfdsym = symbol_get_bfdsym (sym); |
| elfsym = elf_symbol_from (bfdsym); |
| gas_assert (elfsym); |
| elfsym->internal_elf_sym.st_other |= STO_AARCH64_VARIANT_PCS; |
| } |
| #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 (); |
| } |
| |
| static void |
| s_aarch64_cfi_b_key_frame (int ignored ATTRIBUTE_UNUSED) |
| { |
| demand_empty_rest_of_line (); |
| struct fde_entry *fde = frchain_now->frch_cfi_data->cur_fde_data; |
| fde->pauth_key = AARCH64_PAUTH_KEY_B; |
| } |
| |
| #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}, |
| {"cfi_b_key_frame", s_aarch64_cfi_b_key_frame, 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}, |
| {"variant_pcs", s_variant_pcs, 0}, |
| #endif |
| {"float16", float_cons, 'h'}, |
| {"bfloat16", float_cons, 'b'}, |
| {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 bool |
| 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 char) *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 register of type REG_TYPE followed |
| by a comma or the end of line. Return FALSE if STR is such a string. */ |
| |
| static bool |
| parse_immediate_expression (char **str, expressionS *exp, |
| aarch64_reg_type reg_type) |
| { |
| if (reg_name_p (*str, reg_type)) |
| { |
| set_recoverable_error (_("immediate operand required")); |
| return false; |
| } |
| |
| aarch64_get_expression (exp, str, GE_OPT_PREFIX, REJECT_ABSENT, |
| NORMAL_RESOLUTION); |
| |
| 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. REG_TYPE says which register |
| names should be treated as registers rather than as symbolic immediates. |
| |
| Return TRUE on success; otherwise return FALSE. */ |
| |
| static bool |
| parse_constant_immediate (char **str, int64_t *val, aarch64_reg_type reg_type) |
| { |
| expressionS exp; |
| |
| if (! parse_immediate_expression (str, &exp, reg_type)) |
| 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 bool |
| 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. */ |
| } |
| |
| /* Return TRUE if the IEEE double value encoded in IMM can be expressed |
| as an IEEE float without any loss of precision. Store the value in |
| *FPWORD if so. */ |
| |
| static bool |
| can_convert_double_to_float (uint64_t imm, uint32_t *fpword) |
| { |
| /* If a double-precision floating-point value has the following bit |
| pattern, it can be expressed in a float: |
| |
| 6 66655555555 5544 44444444 33333333 33222222 22221111 111111 |
| 3 21098765432 1098 76543210 98765432 10987654 32109876 54321098 76543210 |
| n E~~~eeeeeee ssss ssssssss ssssssss SSS00000 00000000 00000000 00000000 |
| |
| -----------------------------> nEeeeeee esssssss ssssssss sssssSSS |
| if Eeee_eeee != 1111_1111 |
| |
| where n, e, s and S are either 0 or 1 independently and where ~ is the |
| inverse of E. */ |
| |
| uint32_t pattern; |
| uint32_t high32 = imm >> 32; |
| uint32_t low32 = imm; |
| |
| /* Lower 29 bits need to be 0s. */ |
| if ((imm & 0x1fffffff) != 0) |
| return false; |
| |
| /* Prepare the pattern for 'Eeeeeeeee'. */ |
| if (((high32 >> 30) & 0x1) == 0) |
| pattern = 0x38000000; |
| else |
| pattern = 0x40000000; |
| |
| /* Check E~~~. */ |
| if ((high32 & 0x78000000) != pattern) |
| return false; |
| |
| /* Check Eeee_eeee != 1111_1111. */ |
| if ((high32 & 0x7ff00000) == 0x47f00000) |
| return false; |
| |
| *fpword = ((high32 & 0xc0000000) /* 1 n bit and 1 E bit. */ |
| | ((high32 << 3) & 0x3ffffff8) /* 7 e and 20 s bits. */ |
| | (low32 >> 29)); /* 3 S bits. */ |
| return true; |
| } |
| |
| /* Return true if we should treat OPERAND as a double-precision |
| floating-point operand rather than a single-precision one. */ |
| static bool |
| double_precision_operand_p (const aarch64_opnd_info *operand) |
| { |
| /* Check for unsuffixed SVE registers, which are allowed |
| for LDR and STR but not in instructions that require an |
| immediate. We get better error messages if we arbitrarily |
| pick one size, parse the immediate normally, and then |
| report the match failure in the normal way. */ |
| return (operand->qualifier == AARCH64_OPND_QLF_NIL |
| || aarch64_get_qualifier_esize (operand->qualifier) == 8); |
| } |
| |
| /* 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). REG_TYPE says which register |
| names should be treated as registers rather than as symbolic immediates. |
| |
| This routine accepts any IEEE float; it is up to the callers to reject |
| invalid ones. */ |
| |
| static bool |
| parse_aarch64_imm_float (char **ccp, int *immed, bool dp_p, |
| aarch64_reg_type reg_type) |
| { |
| char *str = *ccp; |
| char *fpnum; |
| LITTLENUM_TYPE words[MAX_LITTLENUMS]; |
| int64_t val = 0; |
| unsigned fpword = 0; |
| bool hex_p = false; |
| |
| skip_past_char (&str, '#'); |
| |
| fpnum = str; |
| skip_whitespace (fpnum); |
| |
| if (startswith (fpnum, "0x")) |
| { |
| /* 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, reg_type)) |
| goto invalid_fp; |
| |
| if (dp_p) |
| { |
| if (!can_convert_double_to_float (val, &fpword)) |
| goto invalid_fp; |
| } |
| else if ((uint64_t) val > 0xffffffff) |
| goto invalid_fp; |
| else |
| fpword = val; |
| |
| hex_p = true; |
| } |
| else if (reg_name_p (str, reg_type)) |
| { |
| set_recoverable_error (_("immediate operand required")); |
| 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]; |
| } |
| } |
| |
| *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 register of type REG_TYPE followed by a comma or |
| the end of line. Return FALSE if STR is such a register. */ |
| |
| static bool |
| parse_big_immediate (char **str, int64_t *imm, aarch64_reg_type reg_type) |
| { |
| char *ptr = *str; |
| |
| if (reg_name_p (ptr, reg_type)) |
| { |
| set_syntax_error (_("immediate operand required")); |
| return false; |
| } |
| |
| aarch64_get_expression (&inst.reloc.exp, &ptr, GE_OPT_PREFIX, REJECT_ABSENT, |
| NORMAL_RESOLUTION); |
| |
| 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 bool |
| aarch64_gas_internal_fixup_p (void) |
| { |
| return inst.reloc.type == BFD_RELOC_AARCH64_GAS_INTERNAL_FIXUP; |
| } |
| |
| /* Assign the immediate value to the relevant 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}, |
| |
| /* Most significant bits 0-15 of signed/unsigned address/value: MOVZ */ |
| {"prel_g0", 1, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_PREL_G0, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 0-15 of signed/unsigned address/value: MOVK */ |
| {"prel_g0_nc", 1, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_PREL_G0_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 16-31 of signed/unsigned address/value: MOVZ */ |
| {"prel_g1", 1, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_PREL_G1, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 16-31 of signed/unsigned address/value: MOVK */ |
| {"prel_g1_nc", 1, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_PREL_G1_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 32-47 of signed/unsigned address/value: MOVZ */ |
| {"prel_g2", 1, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_PREL_G2, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 32-47 of signed/unsigned address/value: MOVK */ |
| {"prel_g2_nc", 1, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_PREL_G2_NC, |
| 0, |
| 0, |
| 0}, |
| |
| /* Most significant bits 48-63 of signed/unsigned address/value: MOVZ */ |
| {"prel_g3", 1, |
| 0, /* adr_type */ |
| 0, |
| BFD_RELOC_AARCH64_MOVW_PREL_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, |
| 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 initialized 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, |
| BFD_RELOC_AARCH64_TLSLE_LDST_TPREL_LO12, |
| 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, |
| BFD_RELOC_AARCH64_TLSLE_LDST_TPREL_LO12_NC, |
| 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; |
| } |
| |
| /* Returns 0 if the relocation should never be forced, |
| 1 if the relocation must be forced, and -1 if either |
| result is OK. */ |
| |
| static signed int |
| aarch64_force_reloc (unsigned int type) |
| { |
| switch (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: |
| 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: |
| 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_LDST16_TPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLE_LDST16_TPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLE_LDST32_TPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLE_LDST32_TPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLE_LDST64_TPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLE_LDST64_TPREL_LO12_NC: |
| case BFD_RELOC_AARCH64_TLSLE_LDST8_TPREL_LO12: |
| case BFD_RELOC_AARCH64_TLSLE_LDST8_TPREL_LO12_NC: |
| 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: |
| return -1; |
| } |
| } |
| |
| int |
| aarch64_force_relocation (struct fix *fixp) |
| { |
| int res = aarch64_force_reloc (fixp->fx_r_type); |
| |
| if (res == -1) |
| return generic_force_reloc (fixp); |
| return res; |
| } |
| |
| /* Mode argument to parse_shift and parser_shifter_operand. */ |
| enum parse_shift_mode |
| { |
| SHIFTED_NONE, /* no shifter allowed */ |
| 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_MUL, /* bare "mul #n" */ |
| SHIFTED_LSL_MSL, /* "lsl|msl #n" */ |
| SHIFTED_MUL_VL, /* "mul vl" */ |
| 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 bool |
| 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 = str_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; |
| } |
| |
| if (kind == AARCH64_MOD_MUL |
| && mode != SHIFTED_MUL |
| && mode != SHIFTED_MUL_VL) |
| { |
| set_syntax_error (_("invalid use of 'MUL'")); |
| return false; |
| } |
| |
| switch (mode) |
| { |
| case SHIFTED_LOGIC_IMM: |
| if (aarch64_extend_operator_p (kind)) |
| { |
| 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_MUL: |
| if (kind != AARCH64_MOD_MUL) |
| { |
| set_syntax_error (_("only 'MUL' is permitted")); |
| return false; |
| } |
| break; |
| |
| case SHIFTED_MUL_VL: |
| /* "MUL VL" consists of two separate tokens. Require the first |
| token to be "MUL" and look for a following "VL". */ |
| if (kind == AARCH64_MOD_MUL) |
| { |
| skip_whitespace (p); |
| if (strncasecmp (p, "vl", 2) == 0 && !ISALPHA (p[2])) |
| { |
| p += 2; |
| kind = AARCH64_MOD_MUL_VL; |
| break; |
| } |
| } |
| set_syntax_error (_("only 'MUL VL' is permitted")); |
| return false; |
| |
| 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 == ']') || kind == AARCH64_MOD_MUL_VL) |
| exp.X_op = O_absent; |
| else |
| { |
| if (is_immediate_prefix (*p)) |
| { |
| p++; |
| exp_has_prefix = 1; |
| } |
| (void) aarch64_get_expression (&exp, &p, GE_NO_PREFIX, ALLOW_ABSENT, |
| NORMAL_RESOLUTION); |
| } |
| if (kind == AARCH64_MOD_MUL_VL) |
| /* For consistency, give MUL VL the same shift amount as an implicit |
| MUL #1. */ |
| operand->shifter.amount = 1; |
| else if (exp.X_op == O_absent) |
| { |
| if (!aarch64_extend_operator_p (kind) || 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; |
| } |
| /* For parsing purposes, MUL #n has no inherent range. The range |
| depends on the operand and will be checked by operand-specific |
| routines. */ |
| else if (kind != AARCH64_MOD_MUL |
| && (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 bool |
| 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 (! aarch64_get_expression (&inst.reloc.exp, &p, GE_OPT_PREFIX, |
| REJECT_ABSENT, NORMAL_RESOLUTION)) |
| 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; |
|