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