Retro68/binutils/gas/config/tc-aarch64.c

7577 lines
201 KiB
C
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* 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 (&notes, name, name_length);
preserved_copy_of_name = obstack_finish (&notes);
#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 (&regname);
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);
}