/* tc-aarch64.c -- Assemble for the AArch64 ISA
Copyright (C) 2009-2024 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 . */
#include "as.h"
#include
#include
#include
#define NO_RELOC 0
#include "safe-ctype.h"
#include "subsegs.h"
#include "obstack.h"
#ifdef OBJ_ELF
#include "elf/aarch64.h"
#include "dw2gencfi.h"
#include "sframe.h"
#include "gen-sframe.h"
#endif
#include "dw2gencfi.h"
#include "dwarf2dbg.h"
#define streq(a, b) (strcmp (a, b) == 0)
#define END_OF_INSN '\0'
static aarch64_feature_set cpu_variant;
/* Variables that we set while parsing command-line options. Once all
options have been read we re-process these values to set the real
assembly flags. */
static const aarch64_feature_set *mcpu_cpu_opt = NULL;
static const aarch64_feature_set *march_cpu_opt = NULL;
/* Constants for known architecture features. */
static const aarch64_feature_set cpu_default = AARCH64_ARCH_FEATURES (V8A);
/* Currently active instruction sequence. */
static aarch64_instr_sequence *insn_sequence = NULL;
#ifdef OBJ_ELF
/* Pre-defined "_GLOBAL_OFFSET_TABLE_" */
static symbolS *GOT_symbol;
#endif
/* Which ABI to use. */
enum aarch64_abi_type
{
AARCH64_ABI_NONE = 0,
AARCH64_ABI_LP64 = 1,
AARCH64_ABI_ILP32 = 2,
AARCH64_ABI_LLP64 = 3
};
unsigned int aarch64_sframe_cfa_sp_reg;
/* The other CFA base register for SFrame stack trace info. */
unsigned int aarch64_sframe_cfa_fp_reg;
unsigned int aarch64_sframe_cfa_ra_reg;
#ifndef DEFAULT_ARCH
#define DEFAULT_ARCH "aarch64"
#endif
#ifdef OBJ_ELF
/* DEFAULT_ARCH is initialized in gas/configure.tgt. */
static const char *default_arch = DEFAULT_ARCH;
#endif
/* AArch64 ABI for the output file. */
static enum aarch64_abi_type aarch64_abi = AARCH64_ABI_NONE;
/* When non-zero, program to a 32-bit model, in which the C data types
int, long and all pointer types are 32-bit objects (ILP32); or to a
64-bit model, in which the C int type is 32-bits but the C long type
and all pointer types are 64-bit objects (LP64). */
#define ilp32_p (aarch64_abi == AARCH64_ABI_ILP32)
/* When non zero, C types int and long are 32 bit,
pointers, however are 64 bit */
#define llp64_p (aarch64_abi == AARCH64_ABI_LLP64)
enum vector_el_type
{
NT_invtype = -1,
NT_b,
NT_h,
NT_s,
NT_d,
NT_q,
NT_zero,
NT_merge
};
/* Bits for DEFINED field in vector_type_el. */
#define NTA_HASTYPE 1
#define NTA_HASINDEX 2
#define NTA_HASVARWIDTH 4
struct vector_type_el
{
enum vector_el_type type;
unsigned char defined;
unsigned element_size;
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. */
aarch64_operand_error parsing_error;
/* The condition that appears in the assembly line. */
int cond;
/* Relocation information (including the GAS internal fixup). */
struct reloc reloc;
/* Need to generate an immediate in the literal pool. */
unsigned gen_lit_pool : 1;
};
typedef struct aarch64_instruction aarch64_instruction;
static aarch64_instruction inst;
static bool parse_operands (char *, const aarch64_opcode *);
static bool programmer_friendly_fixup (aarch64_instruction *);
/* If an AARCH64_OPDE_SYNTAX_ERROR has no error string, its first three
data fields contain the following information:
data[0].i:
A mask of register types that would have been acceptable as bare
operands, outside of a register list. In addition, SEF_DEFAULT_ERROR
is set if a general parsing error occured for an operand (that is,
an error not related to registers, and having no error string).
data[1].i:
A mask of register types that would have been acceptable inside
a register list. In addition, SEF_IN_REGLIST is set if the
operand contained a '{' and if we got to the point of trying
to parse a register inside a list.
data[2].i:
The mask associated with the register that was actually seen, or 0
if none. A nonzero value describes a register inside a register
list if data[1].i & SEF_IN_REGLIST, otherwise it describes a bare
register.
The idea is that stringless errors from multiple opcode templates can
be ORed together to give a summary of the available alternatives. */
#define SEF_DEFAULT_ERROR (1U << 31)
#define SEF_IN_REGLIST (1U << 31)
/* Diagnostics inline function utilities.
These are lightweight utilities which should only be called by parse_operands
and other parsers. GAS processes each assembly line by parsing it against
instruction template(s), in the case of multiple templates (for the same
mnemonic name), those templates are tried one by one until one succeeds or
all fail. An assembly line may fail a few templates before being
successfully parsed; an error saved here in most cases is not a user error
but an error indicating the current template is not the right template.
Therefore it is very important that errors can be saved at a low cost during
the parsing; we don't want to slow down the whole parsing by recording
non-user errors in detail.
Remember that the objective is to help GAS pick up the most appropriate
error message in the case of multiple templates, e.g. FMOV which has 8
templates. */
static inline void
clear_error (void)
{
memset (&inst.parsing_error, 0, sizeof (inst.parsing_error));
inst.parsing_error.kind = AARCH64_OPDE_NIL;
}
static inline bool
error_p (void)
{
return inst.parsing_error.kind != AARCH64_OPDE_NIL;
}
static inline void
set_error (enum aarch64_operand_error_kind kind, const char *error)
{
memset (&inst.parsing_error, 0, sizeof (inst.parsing_error));
inst.parsing_error.index = -1;
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);
inst.parsing_error.data[0].i = SEF_DEFAULT_ERROR;
}
static inline void
set_syntax_error (const char *error)
{
set_error (AARCH64_OPDE_SYNTAX_ERROR, error);
}
static inline void
set_first_syntax_error (const char *error)
{
if (! error_p ())
set_error (AARCH64_OPDE_SYNTAX_ERROR, error);
}
static inline void
set_fatal_syntax_error (const char *error)
{
set_error (AARCH64_OPDE_FATAL_SYNTAX_ERROR, error);
}
/* Return value for certain parsers when the parsing fails; those parsers
return the information of the parsed result, e.g. register number, on
success. */
#define PARSE_FAIL -1
/* This is an invalid condition code that means no conditional field is
present. */
#define COND_ALWAYS 0x10
typedef struct
{
const char *template;
uint32_t value;
} asm_nzcv;
struct reloc_entry
{
char *name;
bfd_reloc_code_real_type reloc;
};
/* Macros to define the register types and masks for the purpose
of parsing. */
#undef AARCH64_REG_TYPES
#define AARCH64_REG_TYPES \
BASIC_REG_TYPE(R_32) /* w[0-30] */ \
BASIC_REG_TYPE(R_64) /* x[0-30] */ \
BASIC_REG_TYPE(SP_32) /* wsp */ \
BASIC_REG_TYPE(SP_64) /* sp */ \
BASIC_REG_TYPE(ZR_32) /* wzr */ \
BASIC_REG_TYPE(ZR_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(V) /* v[0-31] */ \
BASIC_REG_TYPE(Z) /* z[0-31] */ \
BASIC_REG_TYPE(P) /* p[0-15] */ \
BASIC_REG_TYPE(PN) /* pn[0-15] */ \
BASIC_REG_TYPE(ZA) /* za */ \
BASIC_REG_TYPE(ZAT) /* za[0-15] (ZA tile) */ \
BASIC_REG_TYPE(ZATH) /* za[0-15]h (ZA tile horizontal slice) */ \
BASIC_REG_TYPE(ZATV) /* za[0-15]v (ZA tile vertical slice) */ \
BASIC_REG_TYPE(ZT0) /* zt0 */ \
/* Typecheck: any 64-bit int reg (inc SP exc XZR). */ \
MULTI_REG_TYPE(R64_SP, REG_TYPE(R_64) | REG_TYPE(SP_64)) \
/* Typecheck: same, plus SVE registers. */ \
MULTI_REG_TYPE(SVE_BASE, REG_TYPE(R_64) | REG_TYPE(SP_64) \
| REG_TYPE(Z)) \
/* Typecheck: x[0-30], w[0-30] or [xw]zr. */ \
MULTI_REG_TYPE(R_ZR, REG_TYPE(R_32) | REG_TYPE(R_64) \
| REG_TYPE(ZR_32) | REG_TYPE(ZR_64)) \
/* Typecheck: same, plus SVE registers. */ \
MULTI_REG_TYPE(SVE_OFFSET, REG_TYPE(R_32) | REG_TYPE(R_64) \
| REG_TYPE(ZR_32) | REG_TYPE(ZR_64) \
| REG_TYPE(Z)) \
/* Typecheck: x[0-30], w[0-30] or {w}sp. */ \
MULTI_REG_TYPE(R_SP, REG_TYPE(R_32) | REG_TYPE(R_64) \
| REG_TYPE(SP_32) | REG_TYPE(SP_64)) \
/* Typecheck: any int (inc {W}SP inc [WX]ZR). */ \
MULTI_REG_TYPE(R_ZR_SP, REG_TYPE(R_32) | REG_TYPE(R_64) \
| REG_TYPE(SP_32) | REG_TYPE(SP_64) \
| REG_TYPE(ZR_32) | REG_TYPE(ZR_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_ZR_BHSDQ_V, REG_TYPE(R_32) | REG_TYPE(R_64) \
| REG_TYPE(ZR_32) | REG_TYPE(ZR_64) | REG_TYPE(V) \
| REG_TYPE(FP_B) | REG_TYPE(FP_H) \
| REG_TYPE(FP_S) | REG_TYPE(FP_D) | REG_TYPE(FP_Q)) \
/* Typecheck: as above, but also Zn, Pn, and {W}SP. This should only \
be used for SVE instructions, since Zn and Pn are valid symbols \
in other contexts. */ \
MULTI_REG_TYPE(R_ZR_SP_BHSDQ_VZP, REG_TYPE(R_32) | REG_TYPE(R_64) \
| REG_TYPE(SP_32) | REG_TYPE(SP_64) \
| REG_TYPE(ZR_32) | REG_TYPE(ZR_64) | REG_TYPE(V) \
| REG_TYPE(FP_B) | REG_TYPE(FP_H) \
| REG_TYPE(FP_S) | REG_TYPE(FP_D) | REG_TYPE(FP_Q) \
| REG_TYPE(Z) | REG_TYPE(P)) \
/* Likewise, but with predicate-as-counter registers added. */ \
MULTI_REG_TYPE(R_ZR_SP_BHSDQ_VZP_PN, REG_TYPE(R_32) | REG_TYPE(R_64) \
| REG_TYPE(SP_32) | REG_TYPE(SP_64) \
| REG_TYPE(ZR_32) | REG_TYPE(ZR_64) | REG_TYPE(V) \
| REG_TYPE(FP_B) | REG_TYPE(FP_H) \
| REG_TYPE(FP_S) | REG_TYPE(FP_D) | REG_TYPE(FP_Q) \
| REG_TYPE(Z) | REG_TYPE(P) | REG_TYPE(PN)) \
/* Any integer register; used for error messages only. */ \
MULTI_REG_TYPE(R_N, REG_TYPE(R_32) | REG_TYPE(R_64) \
| REG_TYPE(SP_32) | REG_TYPE(SP_64) \
| REG_TYPE(ZR_32) | REG_TYPE(ZR_64)) \
/* Any vector register. */ \
MULTI_REG_TYPE(VZ, REG_TYPE(V) | REG_TYPE(Z)) \
/* An SVE vector or predicate register. */ \
MULTI_REG_TYPE(ZP, REG_TYPE(Z) | REG_TYPE(P)) \
/* Any vector or predicate register. */ \
MULTI_REG_TYPE(VZP, REG_TYPE(V) | REG_TYPE(Z) | REG_TYPE(P)) \
/* The whole of ZA or a single tile. */ \
MULTI_REG_TYPE(ZA_ZAT, REG_TYPE(ZA) | REG_TYPE(ZAT)) \
/* A horizontal or vertical slice of a ZA tile. */ \
MULTI_REG_TYPE(ZATHV, REG_TYPE(ZATH) | REG_TYPE(ZATV)) \
/* Pseudo type to mark the end of the enumerator sequence. */ \
END_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)
#undef END_REG_TYPE
#define END_REG_TYPE(T) BASIC_REG_TYPE(T)
/* Register type enumerators. */
typedef enum aarch64_reg_type_
{
/* A list of REG_TYPE_*. */
AARCH64_REG_TYPES
} aarch64_reg_type;
#undef BASIC_REG_TYPE
#define BASIC_REG_TYPE(T) 1 << REG_TYPE_##T,
#undef REG_TYPE
#define REG_TYPE(T) (1 << REG_TYPE_##T)
#undef MULTI_REG_TYPE
#define MULTI_REG_TYPE(T,V) V,
#undef END_REG_TYPE
#define END_REG_TYPE(T) 0
/* Structure for a hash table entry for a register. */
typedef struct
{
const char *name;
unsigned char number;
ENUM_BITFIELD (aarch64_reg_type_) type : 8;
unsigned char builtin;
} reg_entry;
/* Values indexed by aarch64_reg_type to assist the type checking. */
static const unsigned reg_type_masks[] =
{
AARCH64_REG_TYPES
};
#undef BASIC_REG_TYPE
#undef REG_TYPE
#undef MULTI_REG_TYPE
#undef END_REG_TYPE
#undef AARCH64_REG_TYPES
/* We expected one of the registers in MASK to be specified. If a register
of some kind was specified, SEEN is a mask that contains that register,
otherwise it is zero.
If it is possible to provide a relatively pithy message that describes
the error exactly, return a string that does so, reporting the error
against "operand %d". Return null otherwise.
From a QoI perspective, any REG_TYPE_* that is passed as the first
argument to set_expected_reg_error should generally have its own message.
Providing messages for combinations of such REG_TYPE_*s can be useful if
it is possible to summarize the combination in a relatively natural way.
On the other hand, it seems better to avoid long lists of unrelated
things. */
static const char *
get_reg_expected_msg (unsigned int mask, unsigned int seen)
{
/* First handle messages that use SEEN. */
if ((mask & reg_type_masks[REG_TYPE_ZAT])
&& (seen & reg_type_masks[REG_TYPE_ZATHV]))
return N_("expected an unsuffixed ZA tile at operand %d");
if ((mask & reg_type_masks[REG_TYPE_ZATHV])
&& (seen & reg_type_masks[REG_TYPE_ZAT]))
return N_("missing horizontal or vertical suffix at operand %d");
if ((mask & reg_type_masks[REG_TYPE_ZA])
&& (seen & (reg_type_masks[REG_TYPE_ZAT]
| reg_type_masks[REG_TYPE_ZATHV])))
return N_("expected 'za' rather than a ZA tile at operand %d");
if ((mask & reg_type_masks[REG_TYPE_PN])
&& (seen & reg_type_masks[REG_TYPE_P]))
return N_("expected a predicate-as-counter rather than predicate-as-mask"
" register at operand %d");
if ((mask & reg_type_masks[REG_TYPE_P])
&& (seen & reg_type_masks[REG_TYPE_PN]))
return N_("expected a predicate-as-mask rather than predicate-as-counter"
" register at operand %d");
/* Integer, zero and stack registers. */
if (mask == reg_type_masks[REG_TYPE_R_64])
return N_("expected a 64-bit integer register at operand %d");
if (mask == reg_type_masks[REG_TYPE_R_ZR])
return N_("expected an integer or zero register at operand %d");
if (mask == reg_type_masks[REG_TYPE_R_SP])
return N_("expected an integer or stack pointer register at operand %d");
/* Floating-point and SIMD registers. */
if (mask == reg_type_masks[REG_TYPE_BHSDQ])
return N_("expected a scalar SIMD or floating-point register"
" at operand %d");
if (mask == reg_type_masks[REG_TYPE_V])
return N_("expected an Advanced SIMD vector register at operand %d");
if (mask == reg_type_masks[REG_TYPE_Z])
return N_("expected an SVE vector register at operand %d");
if (mask == reg_type_masks[REG_TYPE_P]
|| mask == (reg_type_masks[REG_TYPE_P] | reg_type_masks[REG_TYPE_PN]))
/* Use this error for "predicate-as-mask only" and "either kind of
predicate". We report a more specific error if P is used where
PN is expected, and vice versa, so the issue at this point is
"predicate-like" vs. "not predicate-like". */
return N_("expected an SVE predicate register at operand %d");
if (mask == reg_type_masks[REG_TYPE_PN])
return N_("expected an SVE predicate-as-counter register at operand %d");
if (mask == reg_type_masks[REG_TYPE_VZ])
return N_("expected a vector register at operand %d");
if (mask == reg_type_masks[REG_TYPE_ZP])
return N_("expected an SVE vector or predicate register at operand %d");
if (mask == reg_type_masks[REG_TYPE_VZP])
return N_("expected a vector or predicate register at operand %d");
/* SME-related registers. */
if (mask == reg_type_masks[REG_TYPE_ZA])
return N_("expected a ZA array vector at operand %d");
if (mask == (reg_type_masks[REG_TYPE_ZA_ZAT] | reg_type_masks[REG_TYPE_ZT0]))
return N_("expected ZT0 or a ZA mask at operand %d");
if (mask == reg_type_masks[REG_TYPE_ZAT])
return N_("expected a ZA tile at operand %d");
if (mask == reg_type_masks[REG_TYPE_ZATHV])
return N_("expected a ZA tile slice at operand %d");
/* Integer and vector combos. */
if (mask == (reg_type_masks[REG_TYPE_R_ZR] | reg_type_masks[REG_TYPE_V]))
return N_("expected an integer register or Advanced SIMD vector register"
" at operand %d");
if (mask == (reg_type_masks[REG_TYPE_R_ZR] | reg_type_masks[REG_TYPE_Z]))
return N_("expected an integer register or SVE vector register"
" at operand %d");
if (mask == (reg_type_masks[REG_TYPE_R_ZR] | reg_type_masks[REG_TYPE_VZ]))
return N_("expected an integer or vector register at operand %d");
if (mask == (reg_type_masks[REG_TYPE_R_ZR] | reg_type_masks[REG_TYPE_P]))
return N_("expected an integer or predicate register at operand %d");
if (mask == (reg_type_masks[REG_TYPE_R_ZR] | reg_type_masks[REG_TYPE_VZP]))
return N_("expected an integer, vector or predicate register"
" at operand %d");
/* SVE and SME combos. */
if (mask == (reg_type_masks[REG_TYPE_Z] | reg_type_masks[REG_TYPE_ZATHV]))
return N_("expected an SVE vector register or ZA tile slice"
" at operand %d");
return NULL;
}
/* Record that we expected a register of type TYPE but didn't see one.
REG is the register that we actually saw, or null if we didn't see a
recognized register. FLAGS is SEF_IN_REGLIST if we are parsing the
contents of a register list, otherwise it is zero. */
static inline void
set_expected_reg_error (aarch64_reg_type type, const reg_entry *reg,
unsigned int flags)
{
assert (flags == 0 || flags == SEF_IN_REGLIST);
set_error (AARCH64_OPDE_SYNTAX_ERROR, NULL);
if (flags & SEF_IN_REGLIST)
inst.parsing_error.data[1].i = reg_type_masks[type] | flags;
else
inst.parsing_error.data[0].i = reg_type_masks[type];
if (reg)
inst.parsing_error.data[2].i = reg_type_masks[reg->type];
}
/* Record that we expected a register list containing registers of type TYPE,
but didn't see the opening '{'. If we saw a register instead, REG is the
register that we saw, otherwise it is null. */
static inline void
set_expected_reglist_error (aarch64_reg_type type, const reg_entry *reg)
{
set_error (AARCH64_OPDE_SYNTAX_ERROR, NULL);
inst.parsing_error.data[1].i = reg_type_masks[type];
if (reg)
inst.parsing_error.data[2].i = reg_type_masks[reg->type];
}
/* Some well known registers that we refer to directly elsewhere. */
#define REG_SP 31
#define REG_ZR 31
/* Instructions take 4 bytes in the object file. */
#define INSN_SIZE 4
static htab_t aarch64_ops_hsh;
static htab_t aarch64_cond_hsh;
static htab_t aarch64_shift_hsh;
static htab_t aarch64_sys_regs_hsh;
static htab_t aarch64_pstatefield_hsh;
static htab_t aarch64_sys_regs_ic_hsh;
static htab_t aarch64_sys_regs_dc_hsh;
static htab_t aarch64_sys_regs_at_hsh;
static htab_t aarch64_sys_regs_tlbi_hsh;
static htab_t aarch64_sys_regs_sr_hsh;
static htab_t aarch64_reg_hsh;
static htab_t aarch64_barrier_opt_hsh;
static htab_t aarch64_nzcv_hsh;
static htab_t aarch64_pldop_hsh;
static htab_t aarch64_hint_opt_hsh;
/* Stuff needed to resolve the label ambiguity
As:
...
label:
may differ from:
...
label:
*/
static symbolS *last_label_seen;
/* Literal pool structure. Held on a per-section
and per-sub-section basis. */
#define MAX_LITERAL_POOL_SIZE 1024
typedef struct literal_expression
{
expressionS exp;
/* If exp.op == O_big then this bignum holds a copy of the global bignum value. */
LITTLENUM_TYPE * bignum;
} literal_expression;
typedef struct literal_pool
{
literal_expression literals[MAX_LITERAL_POOL_SIZE];
unsigned int next_free_entry;
unsigned int id;
symbolS *symbol;
segT section;
subsegT sub_section;
int size;
struct literal_pool *next;
} literal_pool;
/* Pointer to a linked list of literal pools. */
static literal_pool *list_of_pools = NULL;
/* Pure syntax. */
/* This array holds the chars that always start a comment. If the
pre-processor is disabled, these aren't very useful. */
const char comment_chars[] = "";
/* This array holds the chars that only start a comment at the beginning of
a line. If the line seems to have the form '# 123 filename'
.line and .file directives will appear in the pre-processed output. */
/* Note that input_file.c hand checks for '#' at the beginning of the
first line of the input file. This is because the compiler outputs
#NO_APP at the beginning of its output. */
/* Also note that comments like this one will always work. */
const char line_comment_chars[] = "#";
const char line_separator_chars[] = ";";
/* Chars that can be used to separate mant
from exp in floating point numbers. */
const char EXP_CHARS[] = "eE";
/* Chars that mean this number is a floating point constant. */
/* As in 0f12.456 */
/* or 0d1.2345e12 */
const char FLT_CHARS[] = "rRsSfFdDxXeEpPhHb";
/* Prefix character that indicates the start of an immediate value. */
#define is_immediate_prefix(C) ((C) == '#')
/* Separator character handling. */
#define skip_whitespace(str) do { if (*(str) == ' ') ++(str); } while (0)
static inline bool
skip_past_char (char **str, char c)
{
if (**str == c)
{
(*str)++;
return true;
}
else
return false;
}
#define skip_past_comma(str) skip_past_char (str, ',')
/* Arithmetic expressions (possibly involving symbols). */
static bool in_aarch64_get_expression = false;
/* Third argument to aarch64_get_expression. */
#define GE_NO_PREFIX false
#define GE_OPT_PREFIX true
/* Fourth argument to aarch64_get_expression. */
#define ALLOW_ABSENT false
#define REJECT_ABSENT true
/* Return TRUE if the string pointed by *STR is successfully parsed
as an valid expression; *EP will be filled with the information of
such an expression. Otherwise return FALSE.
If ALLOW_IMMEDIATE_PREFIX is true then skip a '#' at the start.
If REJECT_ABSENT is true then trat missing expressions as an error. */
static bool
aarch64_get_expression (expressionS * ep,
char ** str,
bool allow_immediate_prefix,
bool reject_absent)
{
char *save_in;
segT seg;
bool prefix_present = false;
if (allow_immediate_prefix)
{
if (is_immediate_prefix (**str))
{
(*str)++;
prefix_present = true;
}
}
memset (ep, 0, sizeof (expressionS));
save_in = input_line_pointer;
input_line_pointer = *str;
in_aarch64_get_expression = true;
seg = expression (ep);
in_aarch64_get_expression = false;
if (ep->X_op == O_illegal || (reject_absent && ep->X_op == O_absent))
{
/* We found a bad expression in md_operand(). */
*str = input_line_pointer;
input_line_pointer = save_in;
if (prefix_present && ! error_p ())
set_fatal_syntax_error (_("bad expression"));
else
set_first_syntax_error (_("bad expression"));
return false;
}
#ifdef OBJ_AOUT
if (seg != absolute_section
&& seg != text_section
&& seg != data_section
&& seg != bss_section
&& seg != undefined_section)
{
set_syntax_error (_("bad segment"));
*str = input_line_pointer;
input_line_pointer = save_in;
return false;
}
#else
(void) seg;
#endif
*str = input_line_pointer;
input_line_pointer = save_in;
return true;
}
/* Turn a string in input_line_pointer into a floating point constant
of type TYPE, and store the appropriate bytes in *LITP. The number
of LITTLENUMS emitted is stored in *SIZEP. An error message is
returned, or NULL on OK. */
const char *
md_atof (int type, char *litP, int *sizeP)
{
return ieee_md_atof (type, litP, sizeP, target_big_endian);
}
/* We handle all bad expressions here, so that we can report the faulty
instruction in the error message. */
void
md_operand (expressionS * exp)
{
if (in_aarch64_get_expression)
exp->X_op = O_illegal;
}
/* Immediate values. */
/* Errors may be set multiple times during parsing or bit encoding
(particularly in the Neon bits), but usually the earliest error which is set
will be the most meaningful. Avoid overwriting it with later (cascading)
errors by calling this function. */
static void
first_error (const char *error)
{
if (! error_p ())
set_syntax_error (error);
}
/* Similar to first_error, but this function accepts formatted error
message. */
static void
first_error_fmt (const char *format, ...)
{
va_list args;
enum
{ size = 100 };
/* N.B. this single buffer will not cause error messages for different
instructions to pollute each other; this is because at the end of
processing of each assembly line, error message if any will be
collected by as_bad. */
static char buffer[size];
if (! error_p ())
{
int ret ATTRIBUTE_UNUSED;
va_start (args, format);
ret = vsnprintf (buffer, size, format, args);
know (ret <= size - 1 && ret >= 0);
va_end (args);
set_syntax_error (buffer);
}
}
/* Internal helper routine converting a vector_type_el structure *VECTYPE
to a corresponding operand qualifier. */
static inline aarch64_opnd_qualifier_t
vectype_to_qualifier (const struct vector_type_el *vectype)
{
/* Element size in bytes indexed by vector_el_type. */
const unsigned char ele_size[5]
= {1, 2, 4, 8, 16};
const unsigned int ele_base [5] =
{
AARCH64_OPND_QLF_V_4B,
AARCH64_OPND_QLF_V_2H,
AARCH64_OPND_QLF_V_2S,
AARCH64_OPND_QLF_V_1D,
AARCH64_OPND_QLF_V_1Q
};
if (!vectype->defined || vectype->type == NT_invtype)
goto vectype_conversion_fail;
if (vectype->type == NT_zero)
return AARCH64_OPND_QLF_P_Z;
if (vectype->type == NT_merge)
return AARCH64_OPND_QLF_P_M;
gas_assert (vectype->type >= NT_b && vectype->type <= NT_q);
if (vectype->defined & (NTA_HASINDEX | NTA_HASVARWIDTH))
{
/* Special case S_4B. */
if (vectype->type == NT_b && vectype->width == 4)
return AARCH64_OPND_QLF_S_4B;
/* Special case S_2H. */
if (vectype->type == NT_h && vectype->width == 2)
return AARCH64_OPND_QLF_S_2H;
/* Vector element register. */
return AARCH64_OPND_QLF_S_B + vectype->type;
}
else
{
/* Vector register. */
int reg_size = ele_size[vectype->type] * vectype->width;
unsigned offset;
unsigned shift;
if (reg_size != 16 && reg_size != 8 && reg_size != 4)
goto vectype_conversion_fail;
/* The conversion is by calculating the offset from the base operand
qualifier for the vector type. The operand qualifiers are regular
enough that the offset can established by shifting the vector width by
a vector-type dependent amount. */
shift = 0;
if (vectype->type == NT_b)
shift = 3;
else if (vectype->type == NT_h || vectype->type == NT_s)
shift = 2;
else if (vectype->type >= NT_d)
shift = 1;
else
gas_assert (0);
offset = ele_base [vectype->type] + (vectype->width >> shift);
gas_assert (AARCH64_OPND_QLF_V_4B <= offset
&& offset <= AARCH64_OPND_QLF_V_1Q);
return offset;
}
vectype_conversion_fail:
first_error (_("bad vector arrangement type"));
return AARCH64_OPND_QLF_NIL;
}
/* Register parsing. */
/* Generic register parser which is called by other specialized
register parsers.
CCP points to what should be the beginning of a register name.
If it is indeed a valid register name, advance CCP over it and
return the reg_entry structure; otherwise return NULL.
It does not issue diagnostics. */
static reg_entry *
parse_reg (char **ccp)
{
char *start = *ccp;
char *p;
reg_entry *reg;
#ifdef REGISTER_PREFIX
if (*start != REGISTER_PREFIX)
return NULL;
start++;
#endif
p = start;
if (!ISALPHA (*p) || !is_name_beginner (*p))
return NULL;
do
p++;
while (ISALPHA (*p) || ISDIGIT (*p) || *p == '_');
reg = (reg_entry *) str_hash_find_n (aarch64_reg_hsh, start, p - start);
if (!reg)
return NULL;
*ccp = p;
return reg;
}
/* Return the operand qualifier associated with all uses of REG, or
AARCH64_OPND_QLF_NIL if none. AARCH64_OPND_QLF_NIL means either
that qualifiers don't apply to REG or that qualifiers are added
using suffixes. */
static aarch64_opnd_qualifier_t
inherent_reg_qualifier (const reg_entry *reg)
{
switch (reg->type)
{
case REG_TYPE_R_32:
case REG_TYPE_SP_32:
case REG_TYPE_ZR_32:
return AARCH64_OPND_QLF_W;
case REG_TYPE_R_64:
case REG_TYPE_SP_64:
case REG_TYPE_ZR_64:
return AARCH64_OPND_QLF_X;
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 AARCH64_OPND_QLF_S_B + (reg->type - REG_TYPE_FP_B);
default:
return AARCH64_OPND_QLF_NIL;
}
}
/* Return TRUE if REG->TYPE is a valid type of TYPE; otherwise
return FALSE. */
static bool
aarch64_check_reg_type (const reg_entry *reg, aarch64_reg_type type)
{
return (reg_type_masks[type] & (1 << reg->type)) != 0;
}
/* Try to parse a base or offset register. Allow SVE base and offset
registers if REG_TYPE includes SVE registers. Return the register
entry on success, setting *QUALIFIER to the register qualifier.
Return null otherwise.
Note that this function does not issue any diagnostics. */
static const reg_entry *
aarch64_addr_reg_parse (char **ccp, aarch64_reg_type reg_type,
aarch64_opnd_qualifier_t *qualifier)
{
char *str = *ccp;
const reg_entry *reg = parse_reg (&str);
if (reg == NULL)
return NULL;
switch (reg->type)
{
case REG_TYPE_Z:
if ((reg_type_masks[reg_type] & (1 << REG_TYPE_Z)) == 0
|| str[0] != '.')
return NULL;
switch (TOLOWER (str[1]))
{
case 's':
*qualifier = AARCH64_OPND_QLF_S_S;
break;
case 'd':
*qualifier = AARCH64_OPND_QLF_S_D;
break;
default:
return NULL;
}
str += 2;
break;
default:
if (!aarch64_check_reg_type (reg, REG_TYPE_R_ZR_SP))
return NULL;
*qualifier = inherent_reg_qualifier (reg);
break;
}
*ccp = str;
return reg;
}
/* Try to parse a base or offset register. Return the register entry
on success, setting *QUALIFIER to the register qualifier. Return null
otherwise.
Note that this function does not issue any diagnostics. */
static const reg_entry *
aarch64_reg_parse_32_64 (char **ccp, aarch64_opnd_qualifier_t *qualifier)
{
return aarch64_addr_reg_parse (ccp, REG_TYPE_R_ZR_SP, qualifier);
}
/* Parse the qualifier of a vector register or vector element of type
REG_TYPE. Fill in *PARSED_TYPE and return TRUE if the parsing
succeeds; otherwise return FALSE.
Accept only one occurrence of:
4b 8b 16b 2h 4h 8h 2s 4s 1d 2d
b h s d q */
static bool
parse_vector_type_for_operand (aarch64_reg_type reg_type,
struct vector_type_el *parsed_type, char **str)
{
char *ptr = *str;
unsigned width;
unsigned element_size;
enum vector_el_type type;
/* skip '.' */
gas_assert (*ptr == '.');
ptr++;
if (reg_type != REG_TYPE_V || !ISDIGIT (*ptr))
{
width = 0;
goto elt_size;
}
width = strtoul (ptr, &ptr, 10);
if (width != 1 && width != 2 && width != 4 && width != 8 && width != 16)
{
first_error_fmt (_("bad size %d in vector width specifier"), width);
return false;
}
elt_size:
switch (TOLOWER (*ptr))
{
case 'b':
type = NT_b;
element_size = 8;
break;
case 'h':
type = NT_h;
element_size = 16;
break;
case 's':
type = NT_s;
element_size = 32;
break;
case 'd':
type = NT_d;
element_size = 64;
break;
case 'q':
if (reg_type != REG_TYPE_V || width == 1)
{
type = NT_q;
element_size = 128;
break;
}
/* fall through. */
default:
if (*ptr != '\0')
first_error_fmt (_("unexpected character `%c' in element size"), *ptr);
else
first_error (_("missing element size"));
return false;
}
if (width != 0 && width * element_size != 64
&& width * element_size != 128
&& !(width == 2 && element_size == 16)
&& !(width == 4 && element_size == 8))
{
first_error_fmt (_
("invalid element size %d and vector size combination %c"),
width, *ptr);
return false;
}
ptr++;
parsed_type->type = type;
parsed_type->width = width;
parsed_type->element_size = element_size;
*str = ptr;
return true;
}
/* *STR contains an SVE zero/merge predication suffix. Parse it into
*PARSED_TYPE and point *STR at the end of the suffix. */
static bool
parse_predication_for_operand (struct vector_type_el *parsed_type, char **str)
{
char *ptr = *str;
/* Skip '/'. */
gas_assert (*ptr == '/');
ptr++;
switch (TOLOWER (*ptr))
{
case 'z':
parsed_type->type = NT_zero;
break;
case 'm':
parsed_type->type = NT_merge;
break;
default:
if (*ptr != '\0' && *ptr != ',')
first_error_fmt (_("unexpected character `%c' in predication type"),
*ptr);
else
first_error (_("missing predication type"));
return false;
}
parsed_type->width = 0;
*str = ptr + 1;
return true;
}
/* Return true if CH is a valid suffix character for registers of
type TYPE. */
static bool
aarch64_valid_suffix_char_p (aarch64_reg_type type, char ch)
{
switch (type)
{
case REG_TYPE_V:
case REG_TYPE_Z:
case REG_TYPE_ZA:
case REG_TYPE_ZAT:
case REG_TYPE_ZATH:
case REG_TYPE_ZATV:
return ch == '.';
case REG_TYPE_P:
case REG_TYPE_PN:
return ch == '.' || ch == '/';
default:
return false;
}
}
/* Parse an index expression at *STR, storing it in *IMM on success. */
static bool
parse_index_expression (char **str, int64_t *imm)
{
expressionS exp;
aarch64_get_expression (&exp, str, GE_NO_PREFIX, REJECT_ABSENT);
if (exp.X_op != O_constant)
{
first_error (_("constant expression required"));
return false;
}
*imm = exp.X_add_number;
return true;
}
/* Parse a register of the type TYPE.
Return null if the string pointed to by *CCP is not a valid register
name or the parsed register is not of TYPE.
Otherwise return the register, and optionally return the register
shape and element index information in *TYPEINFO.
FLAGS includes PTR_IN_REGLIST if the caller is parsing a register list.
FLAGS includes PTR_FULL_REG if the function should ignore any potential
register index.
FLAGS includes PTR_GOOD_MATCH if we are sufficiently far into parsing
an operand that we can be confident that it is a good match. */
#define PTR_IN_REGLIST (1U << 0)
#define PTR_FULL_REG (1U << 1)
#define PTR_GOOD_MATCH (1U << 2)
static const reg_entry *
parse_typed_reg (char **ccp, aarch64_reg_type type,
struct vector_type_el *typeinfo, unsigned int flags)
{
char *str = *ccp;
bool is_alpha = ISALPHA (*str);
const reg_entry *reg = parse_reg (&str);
struct vector_type_el atype;
struct vector_type_el parsetype;
bool is_typed_vecreg = false;
unsigned int err_flags = (flags & PTR_IN_REGLIST) ? SEF_IN_REGLIST : 0;
atype.defined = 0;
atype.type = NT_invtype;
atype.width = -1;
atype.element_size = 0;
atype.index = 0;
if (reg == NULL)
{
if (typeinfo)
*typeinfo = atype;
if (!is_alpha && (flags & PTR_IN_REGLIST))
set_fatal_syntax_error (_("syntax error in register list"));
else if (flags & PTR_GOOD_MATCH)
set_fatal_syntax_error (NULL);
else
set_expected_reg_error (type, reg, err_flags);
return NULL;
}
if (! aarch64_check_reg_type (reg, type))
{
DEBUG_TRACE ("reg type check failed");
if (flags & PTR_GOOD_MATCH)
set_fatal_syntax_error (NULL);
else
set_expected_reg_error (type, reg, err_flags);
return NULL;
}
type = reg->type;
if (aarch64_valid_suffix_char_p (reg->type, *str))
{
if (*str == '.')
{
if (!parse_vector_type_for_operand (type, &parsetype, &str))
return NULL;
if ((reg->type == REG_TYPE_ZAT
|| reg->type == REG_TYPE_ZATH
|| reg->type == REG_TYPE_ZATV)
&& reg->number * 8U >= parsetype.element_size)
{
set_syntax_error (_("ZA tile number out of range"));
return NULL;
}
}
else
{
if (!parse_predication_for_operand (&parsetype, &str))
return NULL;
}
/* Register if of the form Vn.[bhsdq]. */
is_typed_vecreg = true;
if (type != REG_TYPE_V)
{
/* The width is always variable; we don't allow an integer width
to be specified. */
gas_assert (parsetype.width == 0);
atype.defined |= NTA_HASVARWIDTH | NTA_HASTYPE;
}
else if (parsetype.width == 0)
/* Expect index. In the new scheme we cannot have
Vn.[bhsdq] represent a scalar. Therefore any
Vn.[bhsdq] should have an index following it.
Except in reglists of course. */
atype.defined |= NTA_HASINDEX;
else
atype.defined |= NTA_HASTYPE;
atype.type = parsetype.type;
atype.width = parsetype.width;
}
if (!(flags & PTR_FULL_REG) && skip_past_char (&str, '['))
{
/* Reject Sn[index] syntax. */
if (reg->type != REG_TYPE_Z
&& reg->type != REG_TYPE_PN
&& reg->type != REG_TYPE_ZT0
&& !is_typed_vecreg)
{
first_error (_("this type of register can't be indexed"));
return NULL;
}
if (flags & PTR_IN_REGLIST)
{
first_error (_("index not allowed inside register list"));
return NULL;
}
atype.defined |= NTA_HASINDEX;
if (!parse_index_expression (&str, &atype.index))
return NULL;
if (! skip_past_char (&str, ']'))
return NULL;
}
else if (!(flags & PTR_IN_REGLIST) && (atype.defined & NTA_HASINDEX) != 0)
{
/* Indexed vector register expected. */
first_error (_("indexed vector register expected"));
return NULL;
}
/* A vector reg Vn should be typed or indexed. */
if (type == REG_TYPE_V && atype.defined == 0)
{
first_error (_("invalid use of vector register"));
}
if (typeinfo)
*typeinfo = atype;
*ccp = str;
return reg;
}
/* Parse register.
Return the register on success; return null otherwise.
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 lists. */
static const reg_entry *
aarch64_reg_parse (char **ccp, aarch64_reg_type type,
struct vector_type_el *vectype)
{
return parse_typed_reg (ccp, type, vectype, 0);
}
static inline bool
eq_vector_type_el (struct vector_type_el e1, struct vector_type_el e2)
{
return (e1.type == e2.type
&& e1.defined == e2.defined
&& e1.width == e2.width
&& e1.element_size == e2.element_size
&& e1.index == e2.index);
}
/* Return the register number mask for registers of type REG_TYPE. */
static inline int
reg_type_mask (aarch64_reg_type reg_type)
{
return reg_type == REG_TYPE_P ? 15 : 31;
}
/* This function parses a list of vector registers of type TYPE.
On success, it returns the parsed register list information in the
following encoded format:
bit 18-22 | 13-17 | 7-11 | 2-6 | 0-1
4th regno | 3rd regno | 2nd regno | 1st regno | num_of_reg
The information of the register shape and/or index is returned in
*VECTYPE.
It returns PARSE_FAIL if the register list is invalid.
The list contains one to four registers.
Each register can be one of:
.[]
.
All should be identical.
All should be identical.
There are restrictions on numbers which are checked later
(by reg_list_valid_p). */
static int
parse_vector_reg_list (char **ccp, aarch64_reg_type type,
struct vector_type_el *vectype)
{
char *str = *ccp;
int nb_regs;
struct vector_type_el typeinfo, typeinfo_first;
uint32_t val, val_range, mask;
int in_range;
int ret_val;
bool error = false;
bool expect_index = false;
unsigned int ptr_flags = PTR_IN_REGLIST;
if (*str != '{')
{
set_expected_reglist_error (type, parse_reg (&str));
return PARSE_FAIL;
}
str++;
nb_regs = 0;
typeinfo_first.defined = 0;
typeinfo_first.type = NT_invtype;
typeinfo_first.width = -1;
typeinfo_first.element_size = 0;
typeinfo_first.index = 0;
ret_val = 0;
val = -1u;
val_range = -1u;
in_range = 0;
mask = reg_type_mask (type);
do
{
if (in_range)
{
str++; /* skip over '-' */
val_range = val;
}
const reg_entry *reg = parse_typed_reg (&str, type, &typeinfo,
ptr_flags);
if (!reg)
{
set_first_syntax_error (_("invalid vector register in list"));
error = true;
continue;
}
val = reg->number;
/* reject [bhsd]n */
if (type == REG_TYPE_V && 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 = (val_range + 1) & mask;
}
else
{
val_range = val;
if (nb_regs == 0)
typeinfo_first = typeinfo;
else if (! eq_vector_type_el (typeinfo_first, typeinfo))
{
set_first_syntax_error
(_("type mismatch in vector register list"));
error = true;
}
}
if (! error)
for (;;)
{
ret_val |= val_range << ((5 * nb_regs) & 31);
nb_regs++;
if (val_range == val)
break;
val_range = (val_range + 1) & mask;
}
in_range = 0;
ptr_flags |= PTR_GOOD_MATCH;
}
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, '['))
{
if (!parse_index_expression (&str, &typeinfo_first.index))
error = true;
if (! skip_past_char (&str, ']'))
error = true;
}
else
{
set_first_syntax_error (_("expected index"));
error = true;
}
}
if (nb_regs > 4)
{
set_first_syntax_error (_("too many registers in vector register list"));
error = true;
}
else if (nb_regs == 0)
{
set_first_syntax_error (_("empty vector register list"));
error = true;
}
*ccp = str;
if (! error)
*vectype = typeinfo_first;
return error ? PARSE_FAIL : (ret_val << 2) | (nb_regs - 1);
}
/* Directives: register aliases. */
static reg_entry *
insert_reg_alias (char *str, int number, aarch64_reg_type type)
{
reg_entry *new;
const char *name;
if ((new = str_hash_find (aarch64_reg_hsh, str)) != 0)
{
if (new->builtin)
as_warn (_("ignoring attempt to redefine built-in register '%s'"),
str);
/* Only warn about a redefinition if it's not defined as the
same register. */
else if (new->number != number || new->type != type)
as_warn (_("ignoring redefinition of register alias '%s'"), str);
return NULL;
}
name = xstrdup (str);
new = XNEW (reg_entry);
new->name = name;
new->number = number;
new->type = type;
new->builtin = false;
str_hash_insert (aarch64_reg_hsh, name, new, 0);
return new;
}
/* Look for the .req directive. This is of the form:
new_register_name .req existing_register_name
If we find one, or if it looks sufficiently like one that we want to
handle any error here, return TRUE. Otherwise return FALSE. */
static bool
create_register_alias (char *newname, char *p)
{
const reg_entry *old;
char *oldname, *nbuf;
size_t nlen;
/* The input scrubber ensures that whitespace after the mnemonic is
collapsed to single spaces. */
oldname = p;
if (!startswith (oldname, " .req "))
return false;
oldname += 6;
if (*oldname == '\0')
return false;
old = str_hash_find (aarch64_reg_hsh, oldname);
if (!old)
{
as_warn (_("unknown register '%s' -- .req ignored"), oldname);
return true;
}
/* If TC_CASE_SENSITIVE is defined, then newname already points to
the desired alias name, and p points to its end. If not, then
the desired alias name is in the global original_case_string. */
#ifdef TC_CASE_SENSITIVE
nlen = p - newname;
#else
newname = original_case_string;
nlen = strlen (newname);
#endif
nbuf = xmemdup0 (newname, nlen);
/* Create aliases under the new name as stated; an all-lowercase
version of the new name; and an all-uppercase version of the new
name. */
if (insert_reg_alias (nbuf, old->number, old->type) != NULL)
{
for (p = nbuf; *p; p++)
*p = TOUPPER (*p);
if (strncmp (nbuf, newname, nlen))
{
/* If this attempt to create an additional alias fails, do not bother
trying to create the all-lower case alias. We will fail and issue
a second, duplicate error message. This situation arises when the
programmer does something like:
foo .req r0
Foo .req r1
The second .req creates the "Foo" alias but then fails to create
the artificial FOO alias because it has already been created by the
first .req. */
if (insert_reg_alias (nbuf, old->number, old->type) == NULL)
{
free (nbuf);
return true;
}
}
for (p = nbuf; *p; p++)
*p = TOLOWER (*p);
if (strncmp (nbuf, newname, nlen))
insert_reg_alias (nbuf, old->number, old->type);
}
free (nbuf);
return true;
}
/* Should never be called, as .req goes between the alias and the
register name, not at the beginning of the line. */
static void
s_req (int a ATTRIBUTE_UNUSED)
{
as_bad (_("invalid syntax for .req directive"));
}
/* The .unreq directive deletes an alias which was previously defined
by .req. For example:
my_alias .req r11
.unreq my_alias */
static void
s_unreq (int a ATTRIBUTE_UNUSED)
{
char *name;
char saved_char;
name = input_line_pointer;
input_line_pointer = find_end_of_line (input_line_pointer, flag_m68k_mri);
saved_char = *input_line_pointer;
*input_line_pointer = 0;
if (!*name)
as_bad (_("invalid syntax for .unreq directive"));
else
{
reg_entry *reg = str_hash_find (aarch64_reg_hsh, name);
if (!reg)
as_bad (_("unknown register alias '%s'"), name);
else if (reg->builtin)
as_warn (_("ignoring attempt to undefine built-in register '%s'"),
name);
else
{
char *p;
char *nbuf;
str_hash_delete (aarch64_reg_hsh, name);
free ((char *) reg->name);
free (reg);
/* Also locate the all upper case and all lower case versions.
Do not complain if we cannot find one or the other as it
was probably deleted above. */
nbuf = strdup (name);
for (p = nbuf; *p; p++)
*p = TOUPPER (*p);
reg = str_hash_find (aarch64_reg_hsh, nbuf);
if (reg)
{
str_hash_delete (aarch64_reg_hsh, nbuf);
free ((char *) reg->name);
free (reg);
}
for (p = nbuf; *p; p++)
*p = TOLOWER (*p);
reg = str_hash_find (aarch64_reg_hsh, nbuf);
if (reg)
{
str_hash_delete (aarch64_reg_hsh, nbuf);
free ((char *) reg->name);
free (reg);
}
free (nbuf);
}
}
*input_line_pointer = saved_char;
demand_empty_rest_of_line ();
}
/* Directives: Instruction set selection. */
#if defined OBJ_ELF || defined OBJ_COFF
/* This code is to handle mapping symbols as defined in the ARM AArch64 ELF
spec. (See "Mapping symbols", section 4.5.4, ARM AAELF64 version 0.05).
Note that previously, $a and $t has type STT_FUNC (BSF_OBJECT flag),
and $d has type STT_OBJECT (BSF_OBJECT flag). Now all three are untyped. */
/* Create a new mapping symbol for the transition to STATE. */
static void
make_mapping_symbol (enum mstate state, valueT value, fragS * frag)
{
symbolS *symbolP;
const char *symname;
int type;
switch (state)
{
case MAP_DATA:
symname = "$d";
type = BSF_NO_FLAGS;
break;
case MAP_INSN:
symname = "$x";
type = BSF_NO_FLAGS;
break;
default:
abort ();
}
symbolP = symbol_new (symname, now_seg, frag, value);
symbol_get_bfdsym (symbolP)->flags |= type | BSF_LOCAL;
/* Save the mapping symbols for future reference. Also check that
we do not place two mapping symbols at the same offset within a
frag. We'll handle overlap between frags in
check_mapping_symbols.
If .fill or other data filling directive generates zero sized data,
the mapping symbol for the following code will have the same value
as the one generated for the data filling directive. In this case,
we replace the old symbol with the new one at the same address. */
if (value == 0)
{
if (frag->tc_frag_data.first_map != NULL)
{
know (S_GET_VALUE (frag->tc_frag_data.first_map) == 0);
symbol_remove (frag->tc_frag_data.first_map, &symbol_rootP,
&symbol_lastP);
}
frag->tc_frag_data.first_map = symbolP;
}
if (frag->tc_frag_data.last_map != NULL)
{
know (S_GET_VALUE (frag->tc_frag_data.last_map) <=
S_GET_VALUE (symbolP));
if (S_GET_VALUE (frag->tc_frag_data.last_map) == S_GET_VALUE (symbolP))
symbol_remove (frag->tc_frag_data.last_map, &symbol_rootP,
&symbol_lastP);
}
frag->tc_frag_data.last_map = symbolP;
}
/* We must sometimes convert a region marked as code to data during
code alignment, if an odd number of bytes have to be padded. The
code mapping symbol is pushed to an aligned address. */
static void
insert_data_mapping_symbol (enum mstate state,
valueT value, fragS * frag, offsetT bytes)
{
/* If there was already a mapping symbol, remove it. */
if (frag->tc_frag_data.last_map != NULL
&& S_GET_VALUE (frag->tc_frag_data.last_map) ==
frag->fr_address + value)
{
symbolS *symp = frag->tc_frag_data.last_map;
if (value == 0)
{
know (frag->tc_frag_data.first_map == symp);
frag->tc_frag_data.first_map = NULL;
}
frag->tc_frag_data.last_map = NULL;
symbol_remove (symp, &symbol_rootP, &symbol_lastP);
}
make_mapping_symbol (MAP_DATA, value, frag);
make_mapping_symbol (state, value + bytes, frag);
}
static void mapping_state_2 (enum mstate state, int max_chars);
/* Set the mapping state to STATE. Only call this when about to
emit some STATE bytes to the file. */
void
mapping_state (enum mstate state)
{
enum mstate mapstate = seg_info (now_seg)->tc_segment_info_data.mapstate;
if (state == MAP_INSN)
/* AArch64 instructions require 4-byte alignment. When emitting
instructions into any section, record the appropriate section
alignment. */
record_alignment (now_seg, 2);
if (mapstate == state)
/* The mapping symbol has already been emitted.
There is nothing else to do. */
return;
#define TRANSITION(from, to) (mapstate == (from) && state == (to))
if (TRANSITION (MAP_UNDEFINED, MAP_DATA) && !subseg_text_p (now_seg))
/* Emit MAP_DATA within executable section in order. Otherwise, it will be
evaluated later in the next else. */
return;
else if (TRANSITION (MAP_UNDEFINED, MAP_INSN))
{
/* Only add the symbol if the offset is > 0:
if we're at the first frag, check it's size > 0;
if we're not at the first frag, then for sure
the offset is > 0. */
struct frag *const frag_first = seg_info (now_seg)->frchainP->frch_root;
const int add_symbol = (frag_now != frag_first)
|| (frag_now_fix () > 0);
if (add_symbol)
make_mapping_symbol (MAP_DATA, (valueT) 0, frag_first);
}
#undef TRANSITION
mapping_state_2 (state, 0);
}
/* Same as mapping_state, but MAX_CHARS bytes have already been
allocated. Put the mapping symbol that far back. */
static void
mapping_state_2 (enum mstate state, int max_chars)
{
enum mstate mapstate = seg_info (now_seg)->tc_segment_info_data.mapstate;
if (!SEG_NORMAL (now_seg))
return;
if (mapstate == state)
/* The mapping symbol has already been emitted.
There is nothing else to do. */
return;
seg_info (now_seg)->tc_segment_info_data.mapstate = state;
make_mapping_symbol (state, (valueT) frag_now_fix () - max_chars, frag_now);
}
#else
#define mapping_state(x) /* nothing */
#define mapping_state_2(x, y) /* nothing */
#endif
/* Directives: alignment. */
static void
s_even (int ignore ATTRIBUTE_UNUSED)
{
/* Never make frag if expect extra pass. */
if (!need_pass_2)
frag_align (1, 0, 0);
record_alignment (now_seg, 1);
demand_empty_rest_of_line ();
}
/* Directives: Literal pools. */
static literal_pool *
find_literal_pool (int size)
{
literal_pool *pool;
for (pool = list_of_pools; pool != NULL; pool = pool->next)
{
if (pool->section == now_seg
&& pool->sub_section == now_subseg && pool->size == size)
break;
}
return pool;
}
static literal_pool *
find_or_make_literal_pool (int size)
{
/* Next literal pool ID number. */
static unsigned int latest_pool_num = 1;
literal_pool *pool;
pool = find_literal_pool (size);
if (pool == NULL)
{
/* Create a new pool. */
pool = XNEW (literal_pool);
if (!pool)
return NULL;
/* Currently we always put the literal pool in the current text
section. If we were generating "small" model code where we
knew that all code and initialised data was within 1MB then
we could output literals to mergeable, read-only data
sections. */
pool->next_free_entry = 0;
pool->section = now_seg;
pool->sub_section = now_subseg;
pool->size = size;
pool->next = list_of_pools;
pool->symbol = NULL;
/* Add it to the list. */
list_of_pools = pool;
}
/* New pools, and emptied pools, will have a NULL symbol. */
if (pool->symbol == NULL)
{
pool->symbol = symbol_create (FAKE_LABEL_NAME, undefined_section,
&zero_address_frag, 0);
pool->id = latest_pool_num++;
}
/* Done. */
return pool;
}
/* Add the literal of size SIZE in *EXP to the relevant literal pool.
Return TRUE on success, otherwise return FALSE. */
static bool
add_to_lit_pool (expressionS *exp, int size)
{
literal_pool *pool;
unsigned int entry;
pool = find_or_make_literal_pool (size);
/* Check if this literal value is already in the pool. */
for (entry = 0; entry < pool->next_free_entry; entry++)
{
expressionS * litexp = & pool->literals[entry].exp;
if ((litexp->X_op == exp->X_op)
&& (exp->X_op == O_constant)
&& (litexp->X_add_number == exp->X_add_number)
&& (litexp->X_unsigned == exp->X_unsigned))
break;
if ((litexp->X_op == exp->X_op)
&& (exp->X_op == O_symbol)
&& (litexp->X_add_number == exp->X_add_number)
&& (litexp->X_add_symbol == exp->X_add_symbol)
&& (litexp->X_op_symbol == exp->X_op_symbol))
break;
}
/* Do we need to create a new entry? */
if (entry == pool->next_free_entry)
{
if (entry >= MAX_LITERAL_POOL_SIZE)
{
set_syntax_error (_("literal pool overflow"));
return false;
}
pool->literals[entry].exp = *exp;
pool->next_free_entry += 1;
if (exp->X_op == O_big)
{
/* PR 16688: Bignums are held in a single global array. We must
copy and preserve that value now, before it is overwritten. */
pool->literals[entry].bignum = XNEWVEC (LITTLENUM_TYPE,
exp->X_add_number);
memcpy (pool->literals[entry].bignum, generic_bignum,
CHARS_PER_LITTLENUM * exp->X_add_number);
}
else
pool->literals[entry].bignum = NULL;
}
exp->X_op = O_symbol;
exp->X_add_number = ((int) entry) * size;
exp->X_add_symbol = pool->symbol;
return true;
}
/* Can't use symbol_new here, so have to create a symbol and then at
a later date assign it a value. That's what these functions do. */
static void
symbol_locate (symbolS * symbolP,
const char *name,/* It is copied, the caller can modify. */
segT segment, /* Segment identifier (SEG_). */
valueT valu, /* Symbol value. */
fragS * frag) /* Associated fragment. */
{
size_t name_length;
char *preserved_copy_of_name;
name_length = strlen (name) + 1; /* +1 for \0. */
obstack_grow (¬es, name, name_length);
preserved_copy_of_name = obstack_finish (¬es);
#ifdef tc_canonicalize_symbol_name
preserved_copy_of_name =
tc_canonicalize_symbol_name (preserved_copy_of_name);
#endif
S_SET_NAME (symbolP, preserved_copy_of_name);
S_SET_SEGMENT (symbolP, segment);
S_SET_VALUE (symbolP, valu);
symbol_clear_list_pointers (symbolP);
symbol_set_frag (symbolP, frag);
/* Link to end of symbol chain. */
{
extern int symbol_table_frozen;
if (symbol_table_frozen)
abort ();
}
symbol_append (symbolP, symbol_lastP, &symbol_rootP, &symbol_lastP);
obj_symbol_new_hook (symbolP);
#ifdef tc_symbol_new_hook
tc_symbol_new_hook (symbolP);
#endif
#ifdef DEBUG_SYMS
verify_symbol_chain (symbol_rootP, symbol_lastP);
#endif /* DEBUG_SYMS */
}
static void
s_ltorg (int ignored ATTRIBUTE_UNUSED)
{
unsigned int entry;
literal_pool *pool;
char sym_name[20];
int align;
for (align = 2; align <= 4; align++)
{
int size = 1 << align;
pool = find_literal_pool (size);
if (pool == NULL || pool->symbol == NULL || pool->next_free_entry == 0)
continue;
/* Align pool as you have word accesses.
Only make a frag if we have to. */
if (!need_pass_2)
frag_align (align, 0, 0);
mapping_state (MAP_DATA);
record_alignment (now_seg, align);
sprintf (sym_name, "$$lit_\002%x", pool->id);
symbol_locate (pool->symbol, sym_name, now_seg,
(valueT) frag_now_fix (), frag_now);
symbol_table_insert (pool->symbol);
for (entry = 0; entry < pool->next_free_entry; entry++)
{
expressionS * exp = & pool->literals[entry].exp;
if (exp->X_op == O_big)
{
/* PR 16688: Restore the global bignum value. */
gas_assert (pool->literals[entry].bignum != NULL);
memcpy (generic_bignum, pool->literals[entry].bignum,
CHARS_PER_LITTLENUM * exp->X_add_number);
}
/* First output the expression in the instruction to the pool. */
emit_expr (exp, size); /* .word|.xword */
if (exp->X_op == O_big)
{
free (pool->literals[entry].bignum);
pool->literals[entry].bignum = NULL;
}
}
/* Mark the pool as empty. */
pool->next_free_entry = 0;
pool->symbol = NULL;
}
}
#if defined(OBJ_ELF) || defined(OBJ_COFF)
/* Forward declarations for functions below, in the MD interface
section. */
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_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
#ifdef OBJ_ELF
/* Forward declarations for functions below, in the MD interface
section. */
static fixS *fix_new_aarch64 (fragS *, int, short, expressionS *, int, int);
/* Mark symbol that it follows a variant PCS convention. */
static void
s_variant_pcs (int ignored ATTRIBUTE_UNUSED)
{
char *name;
char c;
symbolS *sym;
asymbol *bfdsym;
elf_symbol_type *elfsym;
c = get_symbol_name (&name);
if (!*name)
as_bad (_("Missing symbol name in directive"));
sym = symbol_find_or_make (name);
restore_line_pointer (c);
demand_empty_rest_of_line ();
bfdsym = symbol_get_bfdsym (sym);
elfsym = elf_symbol_from (bfdsym);
gas_assert (elfsym);
elfsym->internal_elf_sym.st_other |= STO_AARCH64_VARIANT_PCS;
}
#endif /* OBJ_ELF */
/* Output a 32-bit word, but mark as an instruction. */
static void
s_aarch64_inst (int ignored ATTRIBUTE_UNUSED)
{
expressionS exp;
unsigned n = 0;
#ifdef md_flush_pending_output
md_flush_pending_output ();
#endif
if (is_it_end_of_statement ())
{
demand_empty_rest_of_line ();
return;
}
/* Sections are assumed to start aligned. In executable section, there is no
MAP_DATA symbol pending. So we only align the address during
MAP_DATA --> MAP_INSN transition.
For other sections, this is not guaranteed. */
enum mstate mapstate = seg_info (now_seg)->tc_segment_info_data.mapstate;
if (!need_pass_2 && subseg_text_p (now_seg) && mapstate == MAP_DATA)
frag_align_code (2, 0);
#ifdef OBJ_ELF
mapping_state (MAP_INSN);
#endif
do
{
expression (&exp);
if (exp.X_op != O_constant)
{
as_bad (_("constant expression required"));
ignore_rest_of_line ();
return;
}
if (target_big_endian)
{
unsigned int val = exp.X_add_number;
exp.X_add_number = SWAP_32 (val);
}
emit_expr (&exp, INSN_SIZE);
++n;
}
while (*input_line_pointer++ == ',');
dwarf2_emit_insn (n * INSN_SIZE);
/* Put terminator back into stream. */
input_line_pointer--;
demand_empty_rest_of_line ();
}
static void
s_aarch64_cfi_b_key_frame (int ignored ATTRIBUTE_UNUSED)
{
demand_empty_rest_of_line ();
struct fde_entry *fde = frchain_now->frch_cfi_data->cur_fde_data;
fde->pauth_key = AARCH64_PAUTH_KEY_B;
}
#ifdef OBJ_ELF
/* Emit BFD_RELOC_AARCH64_TLSDESC_ADD on the next ADD instruction. */
static void
s_tlsdescadd (int ignored ATTRIBUTE_UNUSED)
{
expressionS exp;
expression (&exp);
frag_grow (4);
fix_new_aarch64 (frag_now, frag_more (0) - frag_now->fr_literal, 4, &exp, 0,
BFD_RELOC_AARCH64_TLSDESC_ADD);
demand_empty_rest_of_line ();
}
/* Emit BFD_RELOC_AARCH64_TLSDESC_CALL on the next BLR instruction. */
static void
s_tlsdesccall (int ignored ATTRIBUTE_UNUSED)
{
expressionS exp;
/* Since we're just labelling the code, there's no need to define a
mapping symbol. */
expression (&exp);
/* Make sure there is enough room in this frag for the following
blr. This trick only works if the blr follows immediately after
the .tlsdesc directive. */
frag_grow (4);
fix_new_aarch64 (frag_now, frag_more (0) - frag_now->fr_literal, 4, &exp, 0,
BFD_RELOC_AARCH64_TLSDESC_CALL);
demand_empty_rest_of_line ();
}
/* Emit BFD_RELOC_AARCH64_TLSDESC_LDR on the next LDR instruction. */
static void
s_tlsdescldr (int ignored ATTRIBUTE_UNUSED)
{
expressionS exp;
expression (&exp);
frag_grow (4);
fix_new_aarch64 (frag_now, frag_more (0) - frag_now->fr_literal, 4, &exp, 0,
BFD_RELOC_AARCH64_TLSDESC_LDR);
demand_empty_rest_of_line ();
}
#endif /* OBJ_ELF */
#ifdef TE_PE
static void
s_secrel (int dummy ATTRIBUTE_UNUSED)
{
expressionS exp;
do
{
expression (&exp);
if (exp.X_op == O_symbol)
exp.X_op = O_secrel;
emit_expr (&exp, 4);
}
while (*input_line_pointer++ == ',');
input_line_pointer--;
demand_empty_rest_of_line ();
}
void
tc_pe_dwarf2_emit_offset (symbolS *symbol, unsigned int size)
{
expressionS exp;
exp.X_op = O_secrel;
exp.X_add_symbol = symbol;
exp.X_add_number = 0;
emit_expr (&exp, size);
}
static void
s_secidx (int dummy ATTRIBUTE_UNUSED)
{
expressionS exp;
do
{
expression (&exp);
if (exp.X_op == O_symbol)
exp.X_op = O_secidx;
emit_expr (&exp, 2);
}
while (*input_line_pointer++ == ',');
input_line_pointer--;
demand_empty_rest_of_line ();
}
#endif /* TE_PE */
static void s_aarch64_arch (int);
static void s_aarch64_cpu (int);
static void s_aarch64_arch_extension (int);
/* This table describes all the machine specific pseudo-ops the assembler
has to support. The fields are:
pseudo-op name without dot
function to call to execute this pseudo-op
Integer arg to pass to the function. */
const pseudo_typeS md_pseudo_table[] = {
/* Never called because '.req' does not start a line. */
{"req", s_req, 0},
{"unreq", s_unreq, 0},
{"even", s_even, 0},
{"ltorg", s_ltorg, 0},
{"pool", s_ltorg, 0},
{"cpu", s_aarch64_cpu, 0},
{"arch", s_aarch64_arch, 0},
{"arch_extension", s_aarch64_arch_extension, 0},
{"inst", s_aarch64_inst, 0},
{"cfi_b_key_frame", s_aarch64_cfi_b_key_frame, 0},
#ifdef OBJ_ELF
{"tlsdescadd", s_tlsdescadd, 0},
{"tlsdesccall", s_tlsdesccall, 0},
{"tlsdescldr", s_tlsdescldr, 0},
{"variant_pcs", s_variant_pcs, 0},
#endif
#if defined(OBJ_ELF) || defined(OBJ_COFF)
{"word", s_aarch64_cons, 4},
{"long", s_aarch64_cons, 4},
{"xword", s_aarch64_cons, 8},
{"dword", s_aarch64_cons, 8},
#endif
#ifdef TE_PE
{"secrel32", s_secrel, 0},
{"secidx", s_secidx, 0},
#endif
{"float16", float_cons, 'h'},
{"bfloat16", float_cons, 'b'},
{0, 0, 0}
};
/* Check whether STR points to a register name followed by a comma or the
end of line; REG_TYPE indicates which register types are checked
against. Return TRUE if STR is such a register name; otherwise return
FALSE. The function does not intend to produce any diagnostics, but since
the register parser aarch64_reg_parse, which is called by this function,
does produce diagnostics, we call clear_error to clear any diagnostics
that may be generated by aarch64_reg_parse.
Also, the function returns FALSE directly if there is any user error
present at the function entry. This prevents the existing diagnostics
state from being spoiled.
The function currently serves parse_constant_immediate and
parse_big_immediate only. */
static bool
reg_name_p (char *str, aarch64_reg_type reg_type)
{
const reg_entry *reg;
/* Prevent the diagnostics state from being spoiled. */
if (error_p ())
return false;
reg = aarch64_reg_parse (&str, reg_type, NULL);
/* Clear the parsing error that may be set by the reg parser. */
clear_error ();
if (!reg)
return false;
skip_whitespace (str);
if (*str == ',' || is_end_of_line[(unsigned char) *str])
return true;
return false;
}
/* Parser functions used exclusively in instruction operands. */
/* Parse an immediate expression which may not be constant.
To prevent the expression parser from pushing a register name
into the symbol table as an undefined symbol, firstly a check is
done to find out whether STR is a register of type REG_TYPE followed
by a comma or the end of line. Return FALSE if STR is such a string. */
static bool
parse_immediate_expression (char **str, expressionS *exp,
aarch64_reg_type reg_type)
{
if (reg_name_p (*str, reg_type))
{
set_recoverable_error (_("immediate operand required"));
return false;
}
aarch64_get_expression (exp, str, GE_OPT_PREFIX, REJECT_ABSENT);
if (exp->X_op == O_absent)
{
set_fatal_syntax_error (_("missing immediate expression"));
return false;
}
return true;
}
/* Constant immediate-value read function for use in insn parsing.
STR points to the beginning of the immediate (with the optional
leading #); *VAL receives the value. REG_TYPE says which register
names should be treated as registers rather than as symbolic immediates.
Return TRUE on success; otherwise return FALSE. */
static bool
parse_constant_immediate (char **str, int64_t *val, aarch64_reg_type reg_type)
{
expressionS exp;
if (! parse_immediate_expression (str, &exp, reg_type))
return false;
if (exp.X_op != O_constant)
{
set_syntax_error (_("constant expression required"));
return false;
}
*val = exp.X_add_number;
return true;
}
static uint32_t
encode_imm_float_bits (uint32_t imm)
{
return ((imm >> 19) & 0x7f) /* b[25:19] -> b[6:0] */
| ((imm >> (31 - 7)) & 0x80); /* b[31] -> b[7] */
}
/* Return TRUE if the single-precision floating-point value encoded in IMM
can be expressed in the AArch64 8-bit signed floating-point format with
3-bit exponent and normalized 4 bits of precision; in other words, the
floating-point value must be expressable as
(+/-) n / 16 * power (2, r)
where n and r are integers such that 16 <= n <=31 and -3 <= r <= 4. */
static bool
aarch64_imm_float_p (uint32_t imm)
{
/* If a single-precision floating-point value has the following bit
pattern, it can be expressed in the AArch64 8-bit floating-point
format:
3 32222222 2221111111111
1 09876543 21098765432109876543210
n Eeeeeexx xxxx0000000000000000000
where n, e and each x are either 0 or 1 independently, with
E == ~ e. */
uint32_t pattern;
/* Prepare the pattern for 'Eeeeee'. */
if (((imm >> 30) & 0x1) == 0)
pattern = 0x3e000000;
else
pattern = 0x40000000;
return (imm & 0x7ffff) == 0 /* lower 19 bits are 0. */
&& ((imm & 0x7e000000) == pattern); /* bits 25 - 29 == ~ bit 30. */
}
/* Return TRUE if the IEEE double value encoded in IMM can be expressed
as an IEEE float without any loss of precision. Store the value in
*FPWORD if so. */
static bool
can_convert_double_to_float (uint64_t imm, uint32_t *fpword)
{
/* If a double-precision floating-point value has the following bit
pattern, it can be expressed in a float:
6 66655555555 5544 44444444 33333333 33222222 22221111 111111
3 21098765432 1098 76543210 98765432 10987654 32109876 54321098 76543210
n E~~~eeeeeee ssss ssssssss ssssssss SSS00000 00000000 00000000 00000000
-----------------------------> nEeeeeee esssssss ssssssss sssssSSS
if Eeee_eeee != 1111_1111
where n, e, s and S are either 0 or 1 independently and where ~ is the
inverse of E. */
uint32_t pattern;
uint32_t high32 = imm >> 32;
uint32_t low32 = imm;
/* Lower 29 bits need to be 0s. */
if ((imm & 0x1fffffff) != 0)
return false;
/* Prepare the pattern for 'Eeeeeeeee'. */
if (((high32 >> 30) & 0x1) == 0)
pattern = 0x38000000;
else
pattern = 0x40000000;
/* Check E~~~. */
if ((high32 & 0x78000000) != pattern)
return false;
/* Check Eeee_eeee != 1111_1111. */
if ((high32 & 0x7ff00000) == 0x47f00000)
return false;
*fpword = ((high32 & 0xc0000000) /* 1 n bit and 1 E bit. */
| ((high32 << 3) & 0x3ffffff8) /* 7 e and 20 s bits. */
| (low32 >> 29)); /* 3 S bits. */
return true;
}
/* Return true if we should treat OPERAND as a double-precision
floating-point operand rather than a single-precision one. */
static bool
double_precision_operand_p (const aarch64_opnd_info *operand)
{
/* Check for unsuffixed SVE registers, which are allowed
for LDR and STR but not in instructions that require an
immediate. We get better error messages if we arbitrarily
pick one size, parse the immediate normally, and then
report the match failure in the normal way. */
return (operand->qualifier == AARCH64_OPND_QLF_NIL
|| aarch64_get_qualifier_esize (operand->qualifier) == 8);
}
/* Parse a floating-point immediate. Return TRUE on success and return the
value in *IMMED in the format of IEEE754 single-precision encoding.
*CCP points to the start of the string; DP_P is TRUE when the immediate
is expected to be in double-precision (N.B. this only matters when
hexadecimal representation is involved). REG_TYPE says which register
names should be treated as registers rather than as symbolic immediates.
This routine accepts any IEEE float; it is up to the callers to reject
invalid ones. */
static bool
parse_aarch64_imm_float (char **ccp, int *immed, bool dp_p,
aarch64_reg_type reg_type)
{
char *str = *ccp;
char *fpnum;
LITTLENUM_TYPE words[MAX_LITTLENUMS];
int64_t val = 0;
unsigned fpword = 0;
bool hex_p = false;
skip_past_char (&str, '#');
fpnum = str;
skip_whitespace (fpnum);
if (startswith (fpnum, "0x"))
{
/* Support the hexadecimal representation of the IEEE754 encoding.
Double-precision is expected when DP_P is TRUE, otherwise the
representation should be in single-precision. */
if (! parse_constant_immediate (&str, &val, reg_type))
goto invalid_fp;
if (dp_p)
{
if (!can_convert_double_to_float (val, &fpword))
goto invalid_fp;
}
else if ((uint64_t) val > 0xffffffff)
goto invalid_fp;
else
fpword = val;
hex_p = true;
}
else if (reg_name_p (str, reg_type))
{
set_recoverable_error (_("immediate operand required"));
return false;
}
if (! hex_p)
{
int i;
if ((str = atof_ieee (str, 's', words)) == NULL)
goto invalid_fp;
/* Our FP word must be 32 bits (single-precision FP). */
for (i = 0; i < 32 / LITTLENUM_NUMBER_OF_BITS; i++)
{
fpword <<= LITTLENUM_NUMBER_OF_BITS;
fpword |= words[i];
}
}
*immed = fpword;
*ccp = str;
return true;
invalid_fp:
set_fatal_syntax_error (_("invalid floating-point constant"));
return false;
}
/* Less-generic immediate-value read function with the possibility of loading
a big (64-bit) immediate, as required by AdvSIMD Modified immediate
instructions.
To prevent the expression parser from pushing a register name into the
symbol table as an undefined symbol, a check is firstly done to find
out whether STR is a register of type REG_TYPE followed by a comma or
the end of line. Return FALSE if STR is such a register. */
static bool
parse_big_immediate (char **str, int64_t *imm, aarch64_reg_type reg_type)
{
char *ptr = *str;
if (reg_name_p (ptr, reg_type))
{
set_syntax_error (_("immediate operand required"));
return false;
}
aarch64_get_expression (&inst.reloc.exp, &ptr, GE_OPT_PREFIX, REJECT_ABSENT);
if (inst.reloc.exp.X_op == O_constant)
*imm = inst.reloc.exp.X_add_number;
*str = ptr;
return true;
}
/* Set operand IDX of the *INSTR that needs a GAS internal fixup.
if NEED_LIBOPCODES is non-zero, the fixup will need
assistance from the libopcodes. */
static inline void
aarch64_set_gas_internal_fixup (struct reloc *reloc,
const aarch64_opnd_info *operand,
int need_libopcodes_p)
{
reloc->type = BFD_RELOC_AARCH64_GAS_INTERNAL_FIXUP;
reloc->opnd = operand->type;
if (need_libopcodes_p)
reloc->need_libopcodes_p = 1;
};
/* Return TRUE if the instruction needs to be fixed up later internally by
the GAS; otherwise return FALSE. */
static inline bool
aarch64_gas_internal_fixup_p (void)
{
return inst.reloc.type == BFD_RELOC_AARCH64_GAS_INTERNAL_FIXUP;
}
/* Assign the immediate value to the relevant field in *OPERAND if
RELOC->EXP is a constant expression; otherwise, flag that *OPERAND
needs an internal fixup in a later stage.
ADDR_OFF_P determines whether it is the field ADDR.OFFSET.IMM or
IMM.VALUE that may get assigned with the constant. */
static inline void
assign_imm_if_const_or_fixup_later (struct reloc *reloc,
aarch64_opnd_info *operand,
int addr_off_p,
int need_libopcodes_p,
int skip_p)
{
if (reloc->exp.X_op == O_constant)
{
if (addr_off_p)
operand->addr.offset.imm = reloc->exp.X_add_number;
else
operand->imm.value = reloc->exp.X_add_number;
reloc->type = BFD_RELOC_UNUSED;
}
else
{
aarch64_set_gas_internal_fixup (reloc, operand, need_libopcodes_p);
/* Tell libopcodes to ignore this operand or not. This is helpful
when one of the operands needs to be fixed up later but we need
libopcodes to check the other operands. */
operand->skip = skip_p;
}
}
/* Relocation modifiers. Each entry in the table contains the textual
name for the relocation which may be placed before a symbol used as
a load/store offset, or add immediate. It must be surrounded by a
leading and trailing colon, for example:
ldr x0, [x1, #:rello:varsym]
add x0, x1, #:rello:varsym */
struct reloc_table_entry
{
const char *name;
int pc_rel;
bfd_reloc_code_real_type adr_type;
bfd_reloc_code_real_type adrp_type;
bfd_reloc_code_real_type movw_type;
bfd_reloc_code_real_type add_type;
bfd_reloc_code_real_type ldst_type;
bfd_reloc_code_real_type ld_literal_type;
};
static struct reloc_table_entry reloc_table[] =
{
/* Low 12 bits of absolute address: ADD/i and LDR/STR */
{"lo12", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_ADD_LO12,
BFD_RELOC_AARCH64_LDST_LO12,
0},
/* Higher 21 bits of pc-relative page offset: ADRP */
{"pg_hi21", 1,
0, /* adr_type */
BFD_RELOC_AARCH64_ADR_HI21_PCREL,
0,
0,
0,
0},
/* Higher 21 bits of pc-relative page offset: ADRP, no check */
{"pg_hi21_nc", 1,
0, /* adr_type */
BFD_RELOC_AARCH64_ADR_HI21_NC_PCREL,
0,
0,
0,
0},
/* Most significant bits 0-15 of unsigned address/value: MOVZ */
{"abs_g0", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G0,
0,
0,
0},
/* Most significant bits 0-15 of signed address/value: MOVN/Z */
{"abs_g0_s", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G0_S,
0,
0,
0},
/* Less significant bits 0-15 of address/value: MOVK, no check */
{"abs_g0_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G0_NC,
0,
0,
0},
/* Most significant bits 16-31 of unsigned address/value: MOVZ */
{"abs_g1", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G1,
0,
0,
0},
/* Most significant bits 16-31 of signed address/value: MOVN/Z */
{"abs_g1_s", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G1_S,
0,
0,
0},
/* Less significant bits 16-31 of address/value: MOVK, no check */
{"abs_g1_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G1_NC,
0,
0,
0},
/* Most significant bits 32-47 of unsigned address/value: MOVZ */
{"abs_g2", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G2,
0,
0,
0},
/* Most significant bits 32-47 of signed address/value: MOVN/Z */
{"abs_g2_s", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G2_S,
0,
0,
0},
/* Less significant bits 32-47 of address/value: MOVK, no check */
{"abs_g2_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G2_NC,
0,
0,
0},
/* Most significant bits 48-63 of signed/unsigned address/value: MOVZ */
{"abs_g3", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_G3,
0,
0,
0},
/* Most significant bits 0-15 of signed/unsigned address/value: MOVZ */
{"prel_g0", 1,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_PREL_G0,
0,
0,
0},
/* Most significant bits 0-15 of signed/unsigned address/value: MOVK */
{"prel_g0_nc", 1,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_PREL_G0_NC,
0,
0,
0},
/* Most significant bits 16-31 of signed/unsigned address/value: MOVZ */
{"prel_g1", 1,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_PREL_G1,
0,
0,
0},
/* Most significant bits 16-31 of signed/unsigned address/value: MOVK */
{"prel_g1_nc", 1,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_PREL_G1_NC,
0,
0,
0},
/* Most significant bits 32-47 of signed/unsigned address/value: MOVZ */
{"prel_g2", 1,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_PREL_G2,
0,
0,
0},
/* Most significant bits 32-47 of signed/unsigned address/value: MOVK */
{"prel_g2_nc", 1,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_PREL_G2_NC,
0,
0,
0},
/* Most significant bits 48-63 of signed/unsigned address/value: MOVZ */
{"prel_g3", 1,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_PREL_G3,
0,
0,
0},
/* Get to the page containing GOT entry for a symbol. */
{"got", 1,
0, /* adr_type */
BFD_RELOC_AARCH64_ADR_GOT_PAGE,
0,
0,
0,
BFD_RELOC_AARCH64_GOT_LD_PREL19},
/* 12 bit offset into the page containing GOT entry for that symbol. */
{"got_lo12", 0,
0, /* adr_type */
0,
0,
0,
BFD_RELOC_AARCH64_LD_GOT_LO12_NC,
0},
/* 0-15 bits of address/value: MOVk, no check. */
{"gotoff_g0_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_GOTOFF_G0_NC,
0,
0,
0},
/* Most significant bits 16-31 of address/value: MOVZ. */
{"gotoff_g1", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_MOVW_GOTOFF_G1,
0,
0,
0},
/* 15 bit offset into the page containing GOT entry for that symbol. */
{"gotoff_lo15", 0,
0, /* adr_type */
0,
0,
0,
BFD_RELOC_AARCH64_LD64_GOTOFF_LO15,
0},
/* Get to the page containing GOT TLS entry for a symbol */
{"gottprel_g0_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC,
0,
0,
0},
/* Get to the page containing GOT TLS entry for a symbol */
{"gottprel_g1", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G1,
0,
0,
0},
/* Get to the page containing GOT TLS entry for a symbol */
{"tlsgd", 0,
BFD_RELOC_AARCH64_TLSGD_ADR_PREL21, /* adr_type */
BFD_RELOC_AARCH64_TLSGD_ADR_PAGE21,
0,
0,
0,
0},
/* 12 bit offset into the page containing GOT TLS entry for a symbol */
{"tlsgd_lo12", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSGD_ADD_LO12_NC,
0,
0},
/* Lower 16 bits address/value: MOVk. */
{"tlsgd_g0_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSGD_MOVW_G0_NC,
0,
0,
0},
/* Most significant bits 16-31 of address/value: MOVZ. */
{"tlsgd_g1", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSGD_MOVW_G1,
0,
0,
0},
/* Get to the page containing GOT TLS entry for a symbol */
{"tlsdesc", 0,
BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21, /* adr_type */
BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21,
0,
0,
0,
BFD_RELOC_AARCH64_TLSDESC_LD_PREL19},
/* 12 bit offset into the page containing GOT TLS entry for a symbol */
{"tlsdesc_lo12", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSDESC_ADD_LO12,
BFD_RELOC_AARCH64_TLSDESC_LD_LO12_NC,
0},
/* Get to the page containing GOT TLS entry for a symbol.
The same as GD, we allocate two consecutive GOT slots
for module index and module offset, the only difference
with GD is the module offset should be initialized to
zero without any outstanding runtime relocation. */
{"tlsldm", 0,
BFD_RELOC_AARCH64_TLSLD_ADR_PREL21, /* adr_type */
BFD_RELOC_AARCH64_TLSLD_ADR_PAGE21,
0,
0,
0,
0},
/* 12 bit offset into the page containing GOT TLS entry for a symbol */
{"tlsldm_lo12_nc", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSLD_ADD_LO12_NC,
0,
0},
/* 12 bit offset into the module TLS base address. */
{"dtprel_lo12", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12,
BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12,
0},
/* Same as dtprel_lo12, no overflow check. */
{"dtprel_lo12_nc", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12_NC,
BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12_NC,
0},
/* bits[23:12] of offset to the module TLS base address. */
{"dtprel_hi12", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_HI12,
0,
0},
/* bits[15:0] of offset to the module TLS base address. */
{"dtprel_g0", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0,
0,
0,
0},
/* No overflow check version of BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0. */
{"dtprel_g0_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0_NC,
0,
0,
0},
/* bits[31:16] of offset to the module TLS base address. */
{"dtprel_g1", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1,
0,
0,
0},
/* No overflow check version of BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1. */
{"dtprel_g1_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1_NC,
0,
0,
0},
/* bits[47:32] of offset to the module TLS base address. */
{"dtprel_g2", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G2,
0,
0,
0},
/* Lower 16 bit offset into GOT entry for a symbol */
{"tlsdesc_off_g0_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC,
0,
0,
0},
/* Higher 16 bit offset into GOT entry for a symbol */
{"tlsdesc_off_g1", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSDESC_OFF_G1,
0,
0,
0},
/* Get to the page containing GOT TLS entry for a symbol */
{"gottprel", 0,
0, /* adr_type */
BFD_RELOC_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21,
0,
0,
0,
BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_PREL19},
/* 12 bit offset into the page containing GOT TLS entry for a symbol */
{"gottprel_lo12", 0,
0, /* adr_type */
0,
0,
0,
BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_LO12_NC,
0},
/* Get tp offset for a symbol. */
{"tprel", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12,
0,
0},
/* Get tp offset for a symbol. */
{"tprel_lo12", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12,
BFD_RELOC_AARCH64_TLSLE_LDST_TPREL_LO12,
0},
/* Get tp offset for a symbol. */
{"tprel_hi12", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_HI12,
0,
0},
/* Get tp offset for a symbol. */
{"tprel_lo12_nc", 0,
0, /* adr_type */
0,
0,
BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12_NC,
BFD_RELOC_AARCH64_TLSLE_LDST_TPREL_LO12_NC,
0},
/* Most significant bits 32-47 of address/value: MOVZ. */
{"tprel_g2", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2,
0,
0,
0},
/* Most significant bits 16-31 of address/value: MOVZ. */
{"tprel_g1", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1,
0,
0,
0},
/* Most significant bits 16-31 of address/value: MOVZ, no check. */
{"tprel_g1_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1_NC,
0,
0,
0},
/* Most significant bits 0-15 of address/value: MOVZ. */
{"tprel_g0", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0,
0,
0,
0},
/* Most significant bits 0-15 of address/value: MOVZ, no check. */
{"tprel_g0_nc", 0,
0, /* adr_type */
0,
BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0_NC,
0,
0,
0},
/* 15bit offset from got entry to base address of GOT table. */
{"gotpage_lo15", 0,
0,
0,
0,
0,
BFD_RELOC_AARCH64_LD64_GOTPAGE_LO15,
0},
/* 14bit offset from got entry to base address of GOT table. */
{"gotpage_lo14", 0,
0,
0,
0,
0,
BFD_RELOC_AARCH64_LD32_GOTPAGE_LO14,
0},
};
/* Given the address of a pointer pointing to the textual name of a
relocation as may appear in assembler source, attempt to find its
details in reloc_table. The pointer will be updated to the character
after the trailing colon. On failure, NULL will be returned;
otherwise return the reloc_table_entry. */
static struct reloc_table_entry *
find_reloc_table_entry (char **str)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE (reloc_table); i++)
{
int length = strlen (reloc_table[i].name);
if (strncasecmp (reloc_table[i].name, *str, length) == 0
&& (*str)[length] == ':')
{
*str += (length + 1);
return &reloc_table[i];
}
}
return NULL;
}
/* Returns 0 if the relocation should never be forced,
1 if the relocation must be forced, and -1 if either
result is OK. */
static signed int
aarch64_force_reloc (unsigned int type)
{
switch (type)
{
case BFD_RELOC_AARCH64_GAS_INTERNAL_FIXUP:
/* Perform these "immediate" internal relocations
even if the symbol is extern or weak. */
return 0;
case BFD_RELOC_AARCH64_LD_GOT_LO12_NC:
case BFD_RELOC_AARCH64_TLSDESC_LD_LO12_NC:
case BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_LO12_NC:
/* Pseudo relocs that need to be fixed up according to
ilp32_p. */
return 1;
case BFD_RELOC_AARCH64_ADD_LO12:
case BFD_RELOC_AARCH64_ADR_GOT_PAGE:
case BFD_RELOC_AARCH64_ADR_HI21_NC_PCREL:
case BFD_RELOC_AARCH64_ADR_HI21_PCREL:
case BFD_RELOC_AARCH64_GOT_LD_PREL19:
case BFD_RELOC_AARCH64_LD32_GOT_LO12_NC:
case BFD_RELOC_AARCH64_LD32_GOTPAGE_LO14:
case BFD_RELOC_AARCH64_LD64_GOTOFF_LO15:
case BFD_RELOC_AARCH64_LD64_GOTPAGE_LO15:
case BFD_RELOC_AARCH64_LD64_GOT_LO12_NC:
case BFD_RELOC_AARCH64_LDST128_LO12:
case BFD_RELOC_AARCH64_LDST16_LO12:
case BFD_RELOC_AARCH64_LDST32_LO12:
case BFD_RELOC_AARCH64_LDST64_LO12:
case BFD_RELOC_AARCH64_LDST8_LO12:
case BFD_RELOC_AARCH64_LDST_LO12:
case BFD_RELOC_AARCH64_TLSDESC_ADD_LO12:
case BFD_RELOC_AARCH64_TLSDESC_ADR_PAGE21:
case BFD_RELOC_AARCH64_TLSDESC_ADR_PREL21:
case BFD_RELOC_AARCH64_TLSDESC_LD32_LO12_NC:
case BFD_RELOC_AARCH64_TLSDESC_LD64_LO12:
case BFD_RELOC_AARCH64_TLSDESC_LD_PREL19:
case BFD_RELOC_AARCH64_TLSDESC_OFF_G0_NC:
case BFD_RELOC_AARCH64_TLSDESC_OFF_G1:
case BFD_RELOC_AARCH64_TLSGD_ADD_LO12_NC:
case BFD_RELOC_AARCH64_TLSGD_ADR_PAGE21:
case BFD_RELOC_AARCH64_TLSGD_ADR_PREL21:
case BFD_RELOC_AARCH64_TLSGD_MOVW_G0_NC:
case BFD_RELOC_AARCH64_TLSGD_MOVW_G1:
case BFD_RELOC_AARCH64_TLSIE_ADR_GOTTPREL_PAGE21:
case BFD_RELOC_AARCH64_TLSIE_LD32_GOTTPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSIE_LD64_GOTTPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSIE_LD_GOTTPREL_PREL19:
case BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G0_NC:
case BFD_RELOC_AARCH64_TLSIE_MOVW_GOTTPREL_G1:
case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_HI12:
case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12:
case BFD_RELOC_AARCH64_TLSLD_ADD_DTPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLD_ADD_LO12_NC:
case BFD_RELOC_AARCH64_TLSLD_ADR_PAGE21:
case BFD_RELOC_AARCH64_TLSLD_ADR_PREL21:
case BFD_RELOC_AARCH64_TLSLD_LDST16_DTPREL_LO12:
case BFD_RELOC_AARCH64_TLSLD_LDST16_DTPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12:
case BFD_RELOC_AARCH64_TLSLD_LDST32_DTPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12:
case BFD_RELOC_AARCH64_TLSLD_LDST64_DTPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLD_LDST8_DTPREL_LO12:
case BFD_RELOC_AARCH64_TLSLD_LDST8_DTPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12:
case BFD_RELOC_AARCH64_TLSLD_LDST_DTPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0:
case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G0_NC:
case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1:
case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G1_NC:
case BFD_RELOC_AARCH64_TLSLD_MOVW_DTPREL_G2:
case BFD_RELOC_AARCH64_TLSLE_LDST16_TPREL_LO12:
case BFD_RELOC_AARCH64_TLSLE_LDST16_TPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLE_LDST32_TPREL_LO12:
case BFD_RELOC_AARCH64_TLSLE_LDST32_TPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLE_LDST64_TPREL_LO12:
case BFD_RELOC_AARCH64_TLSLE_LDST64_TPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLE_LDST8_TPREL_LO12:
case BFD_RELOC_AARCH64_TLSLE_LDST8_TPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLE_LDST_TPREL_LO12:
case BFD_RELOC_AARCH64_TLSLE_LDST_TPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_HI12:
case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12:
case BFD_RELOC_AARCH64_TLSLE_ADD_TPREL_LO12_NC:
case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0:
case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G0_NC:
case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1:
case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G1_NC:
case BFD_RELOC_AARCH64_TLSLE_MOVW_TPREL_G2:
/* Always leave these relocations for the linker. */
return 1;
default:
return -1;
}
}
int
aarch64_force_relocation (struct fix *fixp)
{
int res = aarch64_force_reloc (fixp->fx_r_type);
if (res == -1)
return generic_force_reloc (fixp);
return res;
}
/* Mode argument to parse_shift and parser_shifter_operand. */
enum parse_shift_mode
{
SHIFTED_NONE, /* no shifter allowed */
SHIFTED_ARITH_IMM, /* "rn{,lsl|lsr|asl|asr|uxt|sxt #n}" or
"#imm{,lsl #n}" */
SHIFTED_LOGIC_IMM, /* "rn{,lsl|lsr|asl|asr|ror #n}" or
"#imm" */
SHIFTED_LSL, /* bare "lsl #n" */
SHIFTED_MUL, /* bare "mul #n" */
SHIFTED_LSL_MSL, /* "lsl|msl #n" */
SHIFTED_MUL_VL, /* "mul vl" */
SHIFTED_REG_OFFSET /* [su]xtw|sxtx {#n} or lsl #n */
};
/* Parse a operator on an AArch64 data processing instruction.
Return TRUE on success; otherwise return FALSE. */
static bool
parse_shift (char **str, aarch64_opnd_info *operand, enum parse_shift_mode mode)
{
const struct aarch64_name_value_pair *shift_op;
enum aarch64_modifier_kind kind;
expressionS exp;
int exp_has_prefix;
char *s = *str;
char *p = s;
for (p = *str; ISALPHA (*p); p++)
;
if (p == *str)
{
set_syntax_error (_("shift expression expected"));
return false;
}
shift_op = str_hash_find_n (aarch64_shift_hsh, *str, p - *str);
if (shift_op == NULL)
{
set_syntax_error (_("shift operator expected"));
return false;
}
kind = aarch64_get_operand_modifier (shift_op);
if (kind == AARCH64_MOD_MSL && mode != SHIFTED_LSL_MSL)
{
set_syntax_error (_("invalid use of 'MSL'"));
return false;
}
if (kind == AARCH64_MOD_MUL
&& mode != SHIFTED_MUL
&& mode != SHIFTED_MUL_VL)
{
set_syntax_error (_("invalid use of 'MUL'"));
return false;
}
switch (mode)
{
case SHIFTED_LOGIC_IMM:
if (aarch64_extend_operator_p (kind))
{
set_syntax_error (_("extending shift is not permitted"));
return false;
}
break;
case SHIFTED_ARITH_IMM:
if (kind == AARCH64_MOD_ROR)
{
set_syntax_error (_("'ROR' shift is not permitted"));
return false;
}
break;
case SHIFTED_LSL:
if (kind != AARCH64_MOD_LSL)
{
set_syntax_error (_("only 'LSL' shift is permitted"));
return false;
}
break;
case SHIFTED_MUL:
if (kind != AARCH64_MOD_MUL)
{
set_syntax_error (_("only 'MUL' is permitted"));
return false;
}
break;
case SHIFTED_MUL_VL:
/* "MUL VL" consists of two separate tokens. Require the first
token to be "MUL" and look for a following "VL". */
if (kind == AARCH64_MOD_MUL)
{
skip_whitespace (p);
if (strncasecmp (p, "vl", 2) == 0 && !ISALPHA (p[2]))
{
p += 2;
kind = AARCH64_MOD_MUL_VL;
break;
}
}
set_syntax_error (_("only 'MUL VL' is permitted"));
return false;
case SHIFTED_REG_OFFSET:
if (kind != AARCH64_MOD_UXTW && kind != AARCH64_MOD_LSL
&& kind != AARCH64_MOD_SXTW && kind != AARCH64_MOD_SXTX)
{
set_fatal_syntax_error
(_("invalid shift for the register offset addressing mode"));
return false;
}
break;
case SHIFTED_LSL_MSL:
if (kind != AARCH64_MOD_LSL && kind != AARCH64_MOD_MSL)
{
set_syntax_error (_("invalid shift operator"));
return false;
}
break;
default:
abort ();
}
/* Whitespace can appear here if the next thing is a bare digit. */
skip_whitespace (p);
/* Parse shift amount. */
exp_has_prefix = 0;
if ((mode == SHIFTED_REG_OFFSET && *p == ']') || kind == AARCH64_MOD_MUL_VL)
exp.X_op = O_absent;
else
{
if (is_immediate_prefix (*p))
{
p++;
exp_has_prefix = 1;
}
aarch64_get_expression (&exp, &p, GE_NO_PREFIX, ALLOW_ABSENT);
}
if (kind == AARCH64_MOD_MUL_VL)
/* For consistency, give MUL VL the same shift amount as an implicit
MUL #1. */
operand->shifter.amount = 1;
else if (exp.X_op == O_absent)
{
if (!aarch64_extend_operator_p (kind) || exp_has_prefix)
{
set_syntax_error (_("missing shift amount"));
return false;
}
operand->shifter.amount = 0;
}
else if (exp.X_op != O_constant)
{
set_syntax_error (_("constant shift amount required"));
return false;
}
/* For parsing purposes, MUL #n has no inherent range. The range
depends on the operand and will be checked by operand-specific
routines. */
else if (kind != AARCH64_MOD_MUL
&& (exp.X_add_number < 0 || exp.X_add_number > 63))
{
set_fatal_syntax_error (_("shift amount out of range 0 to 63"));
return false;
}
else
{
operand->shifter.amount = exp.X_add_number;
operand->shifter.amount_present = 1;
}
operand->shifter.operator_present = 1;
operand->shifter.kind = kind;
*str = p;
return true;
}
/* Parse a for a data processing instruction:
#
#, LSL #imm
Validation of immediate operands is deferred to md_apply_fix.
Return TRUE on success; otherwise return FALSE. */
static bool
parse_shifter_operand_imm (char **str, aarch64_opnd_info *operand,
enum parse_shift_mode mode)
{
char *p;
if (mode != SHIFTED_ARITH_IMM && mode != SHIFTED_LOGIC_IMM)
return false;
p = *str;
/* Accept an immediate expression. */
if (! aarch64_get_expression (&inst.reloc.exp, &p, GE_OPT_PREFIX,
REJECT_ABSENT))
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 for a data processing instruction:
,
#
#, LSL #imm
where 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 bool
parse_shifter_operand (char **str, aarch64_opnd_info *operand,
enum parse_shift_mode mode)
{
const reg_entry *reg;
aarch64_opnd_qualifier_t qualifier;
enum aarch64_operand_class opd_class
= aarch64_get_operand_class (operand->type);
reg = aarch64_reg_parse_32_64 (str, &qualifier);
if (reg)
{
if (opd_class == AARCH64_OPND_CLASS_IMMEDIATE)
{
set_syntax_error (_("unexpected register in the immediate operand"));
return false;
}
if (!aarch64_check_reg_type (reg, REG_TYPE_R_ZR))
{
set_expected_reg_error (REG_TYPE_R_ZR, reg, 0);
return false;
}
operand->reg.regno = reg->number;
operand->qualifier = qualifier;
/* 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 bool
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 (! aarch64_get_expression (&inst.reloc.exp, str, GE_NO_PREFIX,
REJECT_ABSENT))
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]! // in ldraa/ldrab exclusive
[base,#imm]!
Post-indexed
[base],#imm
[base],Xm // in SIMD ld/st structure
PC-relative (literal)
label
SVE:
[base,#imm,MUL VL]
[base,Zm.D{,LSL #imm}]
[base,Zm.S,(S|U)XTW {#imm}]
[base,Zm.D,(S|U)XTW {#imm}] // ignores top 32 bits of Zm.D elements
[Zn.S,#imm]
[Zn.D,#imm]
[Zn.S{, Xm}]
[Zn.S,Zm.S{,LSL #imm}] // in ADR
[Zn.D,Zm.D{,LSL #imm}] // in ADR
[Zn.D,Zm.D,(S|U)XTW {#imm}] // in ADR
(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 =
.offset.is_reg // 1 if the offset is a register
.offset.imm =
.offset.regno =
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.
The base and offset qualifiers will be stored in *BASE_QUALIFIER and
*OFFSET_QUALIFIER respectively, with NIL being used if there's no
corresponding register.
BASE_TYPE says which types of base register should be accepted and
OFFSET_TYPE says the same for offset registers. IMM_SHIFT_MODE
is the type of shifter that is allowed for immediate offsets,
or SHIFTED_NONE if none.
In all other respects, it is the caller's responsibility to check
for addressing modes not supported by the instruction, and to set
inst.reloc.type. */
static bool
parse_address_main (char **str, aarch64_opnd_info *operand,
aarch64_opnd_qualifier_t *base_qualifier,
aarch64_opnd_qualifier_t *offset_qualifier,
aarch64_reg_type base_type, aarch64_reg_type offset_type,
enum parse_shift_mode imm_shift_mode)
{
char *p = *str;
const reg_entry *reg;
expressionS *exp = &inst.reloc.exp;
*base_qualifier = AARCH64_OPND_QLF_NIL;
*offset_qualifier = AARCH64_OPND_QLF_NIL;
if (! skip_past_char (&p, '['))
{
/* =immediate or label. */
operand->addr.pcrel = 1;
operand->addr.preind = 1;
/* #:: */
skip_past_char (&p, '#');
if (skip_past_char (&p, ':'))
{
bfd_reloc_code_real_type ty;
struct reloc_table_entry *entry;
/* Try to parse a relocation modifier. Anything else is
an error. */
entry = find_reloc_table_entry (&p);
if (! entry)
{
set_syntax_error (_("unknown relocation modifier"));
return false;
}
switch (operand->type)
{
case AARCH64_OPND_ADDR_PCREL21:
/* adr */
ty = entry->adr_type;
break;
default:
ty = entry->ld_literal_type;
break;
}
if (ty == 0)
{
set_syntax_error
(_("this relocation modifier is not allowed on this "
"instruction"));
return false;
}
/* #:: */
if (! aarch64_get_expression (exp, &p, GE_NO_PREFIX, REJECT_ABSENT))
{
set_syntax_error (_("invalid relocation expression"));
return false;
}
/* #:: */
/* Record the relocation type. */
inst.reloc.type = ty;
inst.reloc.pc_rel = entry->pc_rel;
}
else
{
if (skip_past_char (&p, '='))
/* =immediate; need to generate the literal in the literal pool. */
inst.gen_lit_pool = 1;
if (!aarch64_get_expression (exp, &p, GE_NO_PREFIX, REJECT_ABSENT))
{
set_syntax_error (_("invalid address"));
return false;
}
}
*str = p;
return true;
}
/* [ */
bool alpha_base_p = ISALPHA (*p);
reg = aarch64_addr_reg_parse (&p, base_type, base_qualifier);
if (!reg || !aarch64_check_reg_type (reg, base_type))
{
if (reg
&& aarch64_check_reg_type (reg, REG_TYPE_R_SP)
&& *base_qualifier == AARCH64_OPND_QLF_W)
set_syntax_error (_("expected a 64-bit base register"));
else if (alpha_base_p)
set_syntax_error (_("invalid base register"));
else
set_syntax_error (_("expected a base register"));
return false;
}
operand->addr.base_regno = reg->number;
/* [Xn */
if (skip_past_comma (&p))
{
/* [Xn, */
operand->addr.preind = 1;
reg = aarch64_addr_reg_parse (&p, offset_type, offset_qualifier);
if (reg)
{
if (!aarch64_check_reg_type (reg, offset_type))
{
set_syntax_error (_("invalid offset register"));
return false;
}
/* [Xn,Rm */
operand->addr.offset.regno = reg->number;
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] # For vector plus scalar SVE2 indexing.
[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 (*offset_qualifier == AARCH64_OPND_QLF_W)
{
set_syntax_error (_("invalid use of 32-bit register offset"));
return false;
}
if (aarch64_get_qualifier_esize (*base_qualifier)
!= aarch64_get_qualifier_esize (*offset_qualifier)
&& (operand->type != AARCH64_OPND_SVE_ADDR_ZX
|| *base_qualifier != AARCH64_OPND_QLF_S_S
|| *offset_qualifier != AARCH64_OPND_QLF_X))
{
set_syntax_error (_("offset has different size from base"));
return false;
}
}
else if (*offset_qualifier == AARCH64_OPND_QLF_X)
{
set_syntax_error (_("invalid use of 64-bit register offset"));
return false;
}
}
else
{
/* [Xn,#:: */
skip_past_char (&p, '#');
if (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,#:: */
/* We now have the group relocation table entry corresponding to
the name in the assembler source. Next, we parse the
expression. */
if (! aarch64_get_expression (exp, &p, GE_NO_PREFIX, REJECT_ABSENT))
{
set_syntax_error (_("invalid relocation expression"));
return false;
}
/* [Xn,#:: */
/* Record the load/store relocation type. */
inst.reloc.type = entry->ldst_type;
inst.reloc.pc_rel = entry->pc_rel;
}
else
{
if (! aarch64_get_expression (exp, &p, GE_OPT_PREFIX, REJECT_ABSENT))
{
set_syntax_error (_("invalid expression in the address"));
return false;
}
/* [Xn, */
if (imm_shift_mode != SHIFTED_NONE && skip_past_comma (&p))
/* [Xn,, */
if (! parse_shift (&p, operand, imm_shift_mode))
return false;
}
}
}
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;
}
reg = aarch64_reg_parse_32_64 (&p, offset_qualifier);
if (reg)
{
/* [Xn],Xm */
if (!aarch64_check_reg_type (reg, REG_TYPE_R_64))
{
set_syntax_error (_("invalid offset register"));
return false;
}
operand->addr.offset.regno = reg->number;
operand->addr.offset.is_reg = 1;
}
else if (! aarch64_get_expression (exp, &p, GE_OPT_PREFIX, REJECT_ABSENT))
{
/* [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]{!}; only accept [Rn]! as a shorthand for [Rn,#0]! for ldraa and
ldrab, accept [Rn] as a shorthand for [Rn,#0].
For SVE2 vector plus scalar offsets, allow [Zn.] as shorthand for
[Zn., xzr]. */
if (operand->addr.preind == 0 && operand->addr.postind == 0)
{
if (operand->addr.writeback)
{
if (operand->type == AARCH64_OPND_ADDR_SIMM10)
{
/* Accept [Rn]! as a shorthand for [Rn,#0]! */
operand->addr.offset.is_reg = 0;
operand->addr.offset.imm = 0;
operand->addr.preind = 1;
}
else
{
/* Reject [Rn]! */
set_syntax_error (_("missing offset in the pre-indexed address"));
return false;
}
}
else
{
operand->addr.preind = 1;
if (operand->type == AARCH64_OPND_SVE_ADDR_ZX)
{
operand->addr.offset.is_reg = 1;
operand->addr.offset.regno = REG_ZR;
*offset_qualifier = AARCH64_OPND_QLF_X;
}
else
{
inst.reloc.exp.X_op = O_constant;
inst.reloc.exp.X_add_number = 0;
}
}
}
*str = p;
return true;
}
/* Parse a base AArch64 address (as opposed to an SVE one). Return TRUE
on success. */
static bool
parse_address (char **str, aarch64_opnd_info *operand)
{
aarch64_opnd_qualifier_t base_qualifier, offset_qualifier;
return parse_address_main (str, operand, &base_qualifier, &offset_qualifier,
REG_TYPE_R64_SP, REG_TYPE_R_ZR, SHIFTED_NONE);
}
/* Parse an address in which SVE vector registers and MUL VL are allowed.
The arguments have the same meaning as for parse_address_main.
Return TRUE on success. */
static bool
parse_sve_address (char **str, aarch64_opnd_info *operand,
aarch64_opnd_qualifier_t *base_qualifier,
aarch64_opnd_qualifier_t *offset_qualifier)
{
return parse_address_main (str, operand, base_qualifier, offset_qualifier,
REG_TYPE_SVE_BASE, REG_TYPE_SVE_OFFSET,
SHIFTED_MUL_VL);
}
/* Parse a register X0-X30. The register must be 64-bit and register 31
is unallocated. */
static bool
parse_x0_to_x30 (char **str, aarch64_opnd_info *operand)
{
const reg_entry *reg = parse_reg (str);
if (!reg || !aarch64_check_reg_type (reg, REG_TYPE_R_64))
{
set_expected_reg_error (REG_TYPE_R_64, reg, 0);
return false;
}
operand->reg.regno = reg->number;
operand->qualifier = AARCH64_OPND_QLF_X;
return true;
}
/* Parse an operand for a MOVZ, MOVN or MOVK instruction.
Return TRUE on success; otherwise return FALSE. */
static bool
parse_half (char **str, int *internal_fixup_p)
{
char *p = *str;
skip_past_char (&p, '#');
gas_assert (internal_fixup_p);
*internal_fixup_p = 0;
if (*p == ':')
{
struct reloc_table_entry *entry;
/* Try to parse a relocation. Anything else is an error. */
++p;
if (!(entry = find_reloc_table_entry (&p)))
{
set_syntax_error (_("unknown relocation modifier"));
return false;
}
if (entry->movw_type == 0)
{
set_syntax_error
(_("this relocation modifier is not allowed on this instruction"));
return false;
}
inst.reloc.type = entry->movw_type;
}
else
*internal_fixup_p = 1;
if (! aarch64_get_expression (&inst.reloc.exp, &p, GE_NO_PREFIX, REJECT_ABSENT))
return false;
*str = p;
return true;
}
/* Parse an operand for an ADRP instruction:
ADRP ,