/* aarch64-asm.c -- AArch64 assembler support.
Copyright (C) 2012-2024 Free Software Foundation, Inc.
Contributed by ARM Ltd.
This file is part of the GNU opcodes library.
This library 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, or (at your option)
any later version.
It 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 "sysdep.h"
#include
#include "libiberty.h"
#include "aarch64-asm.h"
#include "opintl.h"
/* Utilities. */
/* The unnamed arguments consist of the number of fields and information about
these fields where the VALUE will be inserted into CODE. MASK can be zero or
the base mask of the opcode.
N.B. the fields are required to be in such an order than the least signficant
field for VALUE comes the first, e.g. the in
SQDMLAL , , .[]
is encoded in H:L:M in some cases, the fields H:L:M should be passed in
the order of M, L, H. */
static inline void
insert_fields (aarch64_insn *code, aarch64_insn value, aarch64_insn mask, ...)
{
uint32_t num;
const aarch64_field *field;
enum aarch64_field_kind kind;
va_list va;
va_start (va, mask);
num = va_arg (va, uint32_t);
assert (num <= 5);
while (num--)
{
kind = va_arg (va, enum aarch64_field_kind);
field = &fields[kind];
insert_field (kind, code, value, mask);
value >>= field->width;
}
va_end (va);
}
/* Insert a raw field value VALUE into all fields in SELF->fields after START.
The least significant bit goes in the final field. */
static void
insert_all_fields_after (const aarch64_operand *self, unsigned int start,
aarch64_insn *code, aarch64_insn value)
{
unsigned int i;
enum aarch64_field_kind kind;
for (i = ARRAY_SIZE (self->fields); i-- > start; )
if (self->fields[i] != FLD_NIL)
{
kind = self->fields[i];
insert_field (kind, code, value, 0);
value >>= fields[kind].width;
}
}
/* Insert a raw field value VALUE into all fields in SELF->fields.
The least significant bit goes in the final field. */
static void
insert_all_fields (const aarch64_operand *self, aarch64_insn *code,
aarch64_insn value)
{
return insert_all_fields_after (self, 0, code, value);
}
/* Operand inserters. */
/* Insert nothing. */
bool
aarch64_ins_none (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info ATTRIBUTE_UNUSED,
aarch64_insn *code ATTRIBUTE_UNUSED,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
return true;
}
/* Insert register number. */
bool
aarch64_ins_regno (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int val = info->reg.regno - get_operand_specific_data (self);
insert_field (self->fields[0], code, val, 0);
return true;
}
/* Insert register number, index and/or other data for SIMD register element
operand, e.g. the last source operand in
SQDMLAL , , .[]. */
bool
aarch64_ins_reglane (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* regno */
insert_field (self->fields[0], code, info->reglane.regno, inst->opcode->mask);
/* index and/or type */
if (inst->opcode->iclass == asisdone || inst->opcode->iclass == asimdins)
{
int pos = info->qualifier - AARCH64_OPND_QLF_S_B;
if (info->type == AARCH64_OPND_En
&& inst->opcode->operands[0] == AARCH64_OPND_Ed)
{
/* index2 for e.g. INS .[], .[]. */
assert (info->idx == 1); /* Vn */
aarch64_insn value = info->reglane.index << pos;
insert_field (FLD_imm4_11, code, value, 0);
}
else
{
/* index and type for e.g. DUP , .[].
imm5<3:0>
0000 RESERVED
xxx1 B
xx10 H
x100 S
1000 D */
aarch64_insn value = ((info->reglane.index << 1) | 1) << pos;
insert_field (FLD_imm5, code, value, 0);
}
}
else if (inst->opcode->iclass == dotproduct)
{
unsigned reglane_index = info->reglane.index;
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_4B:
case AARCH64_OPND_QLF_S_2H:
/* H:L */
assert (reglane_index < 4);
insert_fields (code, reglane_index, 0, 2, FLD_L, FLD_H);
break;
case AARCH64_OPND_QLF_S_2B:
/* H:L:M */
assert (reglane_index < 8);
insert_fields (code, reglane_index, 0, 3, FLD_M, FLD_L, FLD_H);
break;
default:
return false;
}
}
else if (inst->opcode->iclass == cryptosm3)
{
/* index for e.g. SM3TT2A .4S, .4S, S[]. */
unsigned reglane_index = info->reglane.index;
assert (reglane_index < 4);
insert_field (FLD_SM3_imm2, code, reglane_index, 0);
}
else
{
/* index for e.g. SQDMLAL , , .[]
or SQDMLAL , , .[]. */
unsigned reglane_index = info->reglane.index;
if (inst->opcode->op == OP_FCMLA_ELEM)
/* Complex operand takes two elements. */
reglane_index *= 2;
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_B:
/* H:imm3 */
assert (reglane_index < 16);
insert_fields (code, reglane_index, 0, 2, FLD_imm3_19, FLD_H);
break;
case AARCH64_OPND_QLF_S_H:
/* H:L:M */
assert (reglane_index < 8);
insert_fields (code, reglane_index, 0, 3, FLD_M, FLD_L, FLD_H);
break;
case AARCH64_OPND_QLF_S_S:
/* H:L */
assert (reglane_index < 4);
insert_fields (code, reglane_index, 0, 2, FLD_L, FLD_H);
break;
case AARCH64_OPND_QLF_S_D:
/* H */
assert (reglane_index < 2);
insert_field (FLD_H, code, reglane_index, 0);
break;
default:
return false;
}
}
return true;
}
/* Insert regno and len field of a register list operand, e.g. Vn in TBL. */
bool
aarch64_ins_reglist (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* R */
insert_field (self->fields[0], code, info->reglist.first_regno, 0);
/* len */
insert_field (FLD_len, code, info->reglist.num_regs - 1, 0);
return true;
}
/* Insert Rt and opcode fields for a register list operand, e.g. Vt
in AdvSIMD load/store instructions. */
bool
aarch64_ins_ldst_reglist (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
aarch64_insn value = 0;
/* Number of elements in each structure to be loaded/stored. */
unsigned num = get_opcode_dependent_value (inst->opcode);
/* Rt */
insert_field (FLD_Rt, code, info->reglist.first_regno, 0);
/* opcode */
switch (num)
{
case 1:
switch (info->reglist.num_regs)
{
case 1: value = 0x7; break;
case 2: value = 0xa; break;
case 3: value = 0x6; break;
case 4: value = 0x2; break;
default: return false;
}
break;
case 2:
value = info->reglist.num_regs == 4 ? 0x3 : 0x8;
break;
case 3:
value = 0x4;
break;
case 4:
value = 0x0;
break;
default:
return false;
}
insert_field (FLD_opcode, code, value, 0);
return true;
}
/* Insert Rt and S fields for a register list operand, e.g. Vt in AdvSIMD load
single structure to all lanes instructions. */
bool
aarch64_ins_ldst_reglist_r (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
aarch64_insn value;
/* The opcode dependent area stores the number of elements in
each structure to be loaded/stored. */
int is_ld1r = get_opcode_dependent_value (inst->opcode) == 1;
/* Rt */
insert_field (FLD_Rt, code, info->reglist.first_regno, 0);
/* S */
value = (aarch64_insn) 0;
if (is_ld1r && info->reglist.num_regs == 2)
/* OP_LD1R does not have alternating variant, but have "two consecutive"
instead. */
value = (aarch64_insn) 1;
insert_field (FLD_S, code, value, 0);
return true;
}
/* Insert regnos of register list operand for AdvSIMD lut instructions. */
bool
aarch64_ins_lut_reglist (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
insert_field (self->fields[0], code, info->reglist.first_regno, 0);
return true;
}
/* Insert Q, opcode<2:1>, S, size and Rt fields for a register element list
operand e.g. Vt in AdvSIMD load/store single element instructions. */
bool
aarch64_ins_ldst_elemlist (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
aarch64_field field = {0, 0};
aarch64_insn QSsize = 0; /* fields Q:S:size. */
aarch64_insn opcodeh2 = 0; /* opcode<2:1> */
assert (info->reglist.has_index);
/* Rt */
insert_field (FLD_Rt, code, info->reglist.first_regno, 0);
/* Encode the index, opcode<2:1> and size. */
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_B:
/* Index encoded in "Q:S:size". */
QSsize = info->reglist.index;
opcodeh2 = 0x0;
break;
case AARCH64_OPND_QLF_S_H:
/* Index encoded in "Q:S:size<1>". */
QSsize = info->reglist.index << 1;
opcodeh2 = 0x1;
break;
case AARCH64_OPND_QLF_S_S:
/* Index encoded in "Q:S". */
QSsize = info->reglist.index << 2;
opcodeh2 = 0x2;
break;
case AARCH64_OPND_QLF_S_D:
/* Index encoded in "Q". */
QSsize = info->reglist.index << 3 | 0x1;
opcodeh2 = 0x2;
break;
default:
return false;
}
insert_fields (code, QSsize, 0, 3, FLD_vldst_size, FLD_S, FLD_Q);
gen_sub_field (FLD_asisdlso_opcode, 1, 2, &field);
insert_field_2 (&field, code, opcodeh2, 0);
return true;
}
/* Insert fields immh:immb and/or Q for e.g. the shift immediate in
SSHR ., ., #
or SSHR , , #. */
bool
aarch64_ins_advsimd_imm_shift (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
unsigned val = aarch64_get_qualifier_standard_value (info->qualifier);
aarch64_insn Q, imm;
if (inst->opcode->iclass == asimdshf)
{
/* Q
immh Q
0000 x SEE AdvSIMD modified immediate
0001 0 8B
0001 1 16B
001x 0 4H
001x 1 8H
01xx 0 2S
01xx 1 4S
1xxx 0 RESERVED
1xxx 1 2D */
Q = (val & 0x1) ? 1 : 0;
insert_field (FLD_Q, code, Q, inst->opcode->mask);
val >>= 1;
}
assert (info->type == AARCH64_OPND_IMM_VLSR
|| info->type == AARCH64_OPND_IMM_VLSL);
if (info->type == AARCH64_OPND_IMM_VLSR)
/* immh:immb
immh
0000 SEE AdvSIMD modified immediate
0001 (16-UInt(immh:immb))
001x (32-UInt(immh:immb))
01xx (64-UInt(immh:immb))
1xxx (128-UInt(immh:immb)) */
imm = (16 << (unsigned)val) - info->imm.value;
else
/* immh:immb
immh
0000 SEE AdvSIMD modified immediate
0001 (UInt(immh:immb)-8)
001x (UInt(immh:immb)-16)
01xx (UInt(immh:immb)-32)
1xxx (UInt(immh:immb)-64) */
imm = info->imm.value + (8 << (unsigned)val);
insert_fields (code, imm, 0, 2, FLD_immb, FLD_immh);
return true;
}
/* Insert fields for e.g. the immediate operands in
BFM , , #, #. */
bool
aarch64_ins_imm (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int64_t imm;
imm = info->imm.value;
if (operand_need_shift_by_two (self))
imm >>= 2;
if (operand_need_shift_by_three (self))
imm >>= 3;
if (operand_need_shift_by_four (self))
imm >>= 4;
insert_all_fields (self, code, imm);
return true;
}
/* Insert immediate and its shift amount for e.g. the last operand in
MOVZ , #{, LSL #}. */
bool
aarch64_ins_imm_half (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst,
aarch64_operand_error *errors)
{
/* imm16 */
aarch64_ins_imm (self, info, code, inst, errors);
/* hw */
insert_field (FLD_hw, code, info->shifter.amount >> 4, 0);
return true;
}
/* Insert cmode and "a:b:c:d:e:f:g:h" fields for e.g. the last operand in
MOVI ., # {, LSL #}. */
bool
aarch64_ins_advsimd_imm_modified (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors
ATTRIBUTE_UNUSED)
{
enum aarch64_opnd_qualifier opnd0_qualifier = inst->operands[0].qualifier;
uint64_t imm = info->imm.value;
enum aarch64_modifier_kind kind = info->shifter.kind;
int amount = info->shifter.amount;
aarch64_field field = {0, 0};
/* a:b:c:d:e:f:g:h */
if (!info->imm.is_fp && aarch64_get_qualifier_esize (opnd0_qualifier) == 8)
{
/* Either MOVI , #
or MOVI .2D, #.
is a 64-bit immediate
"aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh",
encoded in "a:b:c:d:e:f:g:h". */
imm = aarch64_shrink_expanded_imm8 (imm);
assert ((int)imm >= 0);
}
insert_fields (code, imm, 0, 2, FLD_defgh, FLD_abc);
if (kind == AARCH64_MOD_NONE)
return true;
/* shift amount partially in cmode */
assert (kind == AARCH64_MOD_LSL || kind == AARCH64_MOD_MSL);
if (kind == AARCH64_MOD_LSL)
{
/* AARCH64_MOD_LSL: shift zeros. */
int esize = aarch64_get_qualifier_esize (opnd0_qualifier);
assert (esize == 4 || esize == 2 || esize == 1);
/* For 8-bit move immediate, the optional LSL #0 does not require
encoding. */
if (esize == 1)
return true;
amount >>= 3;
if (esize == 4)
gen_sub_field (FLD_cmode, 1, 2, &field); /* per word */
else
gen_sub_field (FLD_cmode, 1, 1, &field); /* per halfword */
}
else
{
/* AARCH64_MOD_MSL: shift ones. */
amount >>= 4;
gen_sub_field (FLD_cmode, 0, 1, &field); /* per word */
}
insert_field_2 (&field, code, amount, 0);
return true;
}
/* Insert fields for an 8-bit floating-point immediate. */
bool
aarch64_ins_fpimm (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
insert_all_fields (self, code, info->imm.value);
return true;
}
/* Insert 1-bit rotation immediate (#90 or #270). */
bool
aarch64_ins_imm_rotate1 (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
uint64_t rot = (info->imm.value - 90) / 180;
assert (rot < 2U);
insert_field (self->fields[0], code, rot, inst->opcode->mask);
return true;
}
/* Insert 2-bit rotation immediate (#0, #90, #180 or #270). */
bool
aarch64_ins_imm_rotate2 (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
uint64_t rot = info->imm.value / 90;
assert (rot < 4U);
insert_field (self->fields[0], code, rot, inst->opcode->mask);
return true;
}
/* Insert # for the immediate operand in fp fix-point instructions,
e.g. SCVTF , , #. */
bool
aarch64_ins_fbits (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
insert_field (self->fields[0], code, 64 - info->imm.value, 0);
return true;
}
/* Insert arithmetic immediate for e.g. the last operand in
SUBS , , # {, }. */
bool
aarch64_ins_aimm (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* shift */
aarch64_insn value = info->shifter.amount ? 1 : 0;
insert_field (self->fields[0], code, value, 0);
/* imm12 (unsigned) */
insert_field (self->fields[1], code, info->imm.value, 0);
return true;
}
/* Common routine shared by aarch64_ins{,_inv}_limm. INVERT_P says whether
the operand should be inverted before encoding. */
static bool
aarch64_ins_limm_1 (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst, bool invert_p,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
bool res;
aarch64_insn value;
uint64_t imm = info->imm.value;
int esize = aarch64_get_qualifier_esize (inst->operands[0].qualifier);
if (invert_p)
imm = ~imm;
/* The constraint check should guarantee that this will work. */
res = aarch64_logical_immediate_p (imm, esize, &value);
if (res)
insert_fields (code, value, 0, 3, self->fields[2], self->fields[1],
self->fields[0]);
return res;
}
/* Insert logical/bitmask immediate for e.g. the last operand in
ORR , , #. */
bool
aarch64_ins_limm (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
return aarch64_ins_limm_1 (self, info, code, inst,
inst->opcode->op == OP_BIC, errors);
}
/* Insert a logical/bitmask immediate for the BIC alias of AND (etc.). */
bool
aarch64_ins_inv_limm (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
return aarch64_ins_limm_1 (self, info, code, inst, true, errors);
}
/* Encode Ft for e.g. STR , [, {, {}}]
or LDP , , [], #. */
bool
aarch64_ins_ft (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst,
aarch64_operand_error *errors)
{
aarch64_insn value = 0;
assert (info->idx == 0);
/* Rt */
aarch64_ins_regno (self, info, code, inst, errors);
if (inst->opcode->iclass == ldstpair_indexed
|| inst->opcode->iclass == ldstnapair_offs
|| inst->opcode->iclass == ldstpair_off
|| inst->opcode->iclass == loadlit)
{
/* size */
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_S: value = 0; break;
case AARCH64_OPND_QLF_S_D: value = 1; break;
case AARCH64_OPND_QLF_S_Q: value = 2; break;
default: return false;
}
insert_field (FLD_ldst_size, code, value, 0);
}
else
{
/* opc[1]:size */
value = aarch64_get_qualifier_standard_value (info->qualifier);
insert_fields (code, value, 0, 2, FLD_ldst_size, FLD_opc1);
}
return true;
}
/* Encode the address operand for e.g. STXRB , , [{,#0}]. */
bool
aarch64_ins_addr_simple (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* Rn */
insert_field (FLD_Rn, code, info->addr.base_regno, 0);
return true;
}
/* Encode the address operand for e.g.
STR , [, {, {}}]. */
bool
aarch64_ins_addr_regoff (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
aarch64_insn S;
enum aarch64_modifier_kind kind = info->shifter.kind;
/* Rn */
insert_field (FLD_Rn, code, info->addr.base_regno, 0);
/* Rm */
insert_field (FLD_Rm, code, info->addr.offset.regno, 0);
/* option */
if (kind == AARCH64_MOD_LSL)
kind = AARCH64_MOD_UXTX; /* Trick to enable the table-driven. */
insert_field (FLD_option, code, aarch64_get_operand_modifier_value (kind), 0);
/* S */
if (info->qualifier != AARCH64_OPND_QLF_S_B)
S = info->shifter.amount != 0;
else
/* For STR , [, {, {}},
S
0 [absent]
1 #0
Must be #0 if is explicitly LSL. */
S = info->shifter.operator_present && info->shifter.amount_present;
insert_field (FLD_S, code, S, 0);
return true;
}
/* Encode the address operand for e.g.
stlur , [{, }]. */
bool
aarch64_ins_addr_offset (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* Rn */
insert_field (self->fields[0], code, info->addr.base_regno, 0);
/* simm9 */
int imm = info->addr.offset.imm;
insert_field (self->fields[1], code, imm, 0);
/* writeback */
if (info->addr.writeback)
{
assert (info->addr.preind == 1 && info->addr.postind == 0);
insert_field (self->fields[2], code, 1, 0);
}
return true;
}
/* Encode the address operand for e.g.
stlur , [{, }]. */
bool
aarch64_ins_rcpc3_addr_offset (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* Rn */
insert_field (self->fields[0], code, info->addr.base_regno, 0);
/* simm9 */
int imm = info->addr.offset.imm;
insert_field (self->fields[1], code, imm, 0);
return true;
}
/* Encode the address operand for e.g. LDRSW , [, #]!. */
bool
aarch64_ins_addr_simm (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int imm;
/* Rn */
insert_field (FLD_Rn, code, info->addr.base_regno, 0);
/* simm (imm9 or imm7) */
imm = info->addr.offset.imm;
if (self->fields[0] == FLD_imm7
|| info->qualifier == AARCH64_OPND_QLF_imm_tag)
/* scaled immediate in ld/st pair instructions.. */
imm >>= get_logsz (aarch64_get_qualifier_esize (info->qualifier));
insert_field (self->fields[0], code, imm, 0);
/* pre/post- index */
if (info->addr.writeback)
{
assert (inst->opcode->iclass != ldst_unscaled
&& inst->opcode->iclass != ldstnapair_offs
&& inst->opcode->iclass != ldstpair_off
&& inst->opcode->iclass != ldst_unpriv);
assert (info->addr.preind != info->addr.postind);
if (info->addr.preind)
insert_field (self->fields[1], code, 1, 0);
}
return true;
}
/* Encode the address operand, potentially offset by the load/store ammount,
e.g. LDIAPP , [, #]
and STILP , [], #.*/
bool
aarch64_ins_rcpc3_addr_opt_offset (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int imm;
/* Rn */
insert_field (FLD_Rn, code, info->addr.base_regno, 0);
/* simm */
imm = info->addr.offset.imm;
if (!imm)
insert_field (FLD_opc2, code, 1, 0);
return true;
}
/* Encode the address operand for e.g. LDRAA , [{, #}]. */
bool
aarch64_ins_addr_simm10 (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int imm;
/* Rn */
insert_field (self->fields[0], code, info->addr.base_regno, 0);
/* simm10 */
imm = info->addr.offset.imm >> 3;
insert_field (self->fields[1], code, imm >> 9, 0);
insert_field (self->fields[2], code, imm, 0);
/* writeback */
if (info->addr.writeback)
{
assert (info->addr.preind == 1 && info->addr.postind == 0);
insert_field (self->fields[3], code, 1, 0);
}
return true;
}
/* Encode the address operand for e.g. LDRSW , [{, #}]. */
bool
aarch64_ins_addr_uimm12 (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int shift = get_logsz (aarch64_get_qualifier_esize (info->qualifier));
/* Rn */
insert_field (self->fields[0], code, info->addr.base_regno, 0);
/* uimm12 */
insert_field (self->fields[1], code,info->addr.offset.imm >> shift, 0);
return true;
}
/* Encode the address operand for e.g.
LD1 {., ., .}, [], >. */
bool
aarch64_ins_simd_addr_post (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* Rn */
insert_field (FLD_Rn, code, info->addr.base_regno, 0);
/* Rm | # */
if (info->addr.offset.is_reg)
insert_field (FLD_Rm, code, info->addr.offset.regno, 0);
else
insert_field (FLD_Rm, code, 0x1f, 0);
return true;
}
/* Encode the condition operand for e.g. CSEL , , , . */
bool
aarch64_ins_cond (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* cond */
insert_field (FLD_cond, code, info->cond->value, 0);
return true;
}
/* Encode the system register operand for e.g. MRS , . */
bool
aarch64_ins_sysreg (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst,
aarch64_operand_error *detail ATTRIBUTE_UNUSED)
{
/* If a system instruction check if we have any restrictions on which
registers it can use. */
if (inst->opcode->iclass == ic_system)
{
uint64_t opcode_flags
= inst->opcode->flags & (F_SYS_READ | F_SYS_WRITE);
uint32_t sysreg_flags
= info->sysreg.flags & (F_REG_READ | F_REG_WRITE);
/* Check to see if it's read-only, else check if it's write only.
if it's both or unspecified don't care. */
if (opcode_flags == F_SYS_READ
&& sysreg_flags
&& sysreg_flags != F_REG_READ)
{
detail->kind = AARCH64_OPDE_SYNTAX_ERROR;
detail->error = _("specified register cannot be read from");
detail->index = info->idx;
detail->non_fatal = true;
}
else if (opcode_flags == F_SYS_WRITE
&& sysreg_flags
&& sysreg_flags != F_REG_WRITE)
{
detail->kind = AARCH64_OPDE_SYNTAX_ERROR;
detail->error = _("specified register cannot be written to");
detail->index = info->idx;
detail->non_fatal = true;
}
}
/* op0:op1:CRn:CRm:op2 */
insert_fields (code, info->sysreg.value, inst->opcode->mask, 5,
FLD_op2, FLD_CRm, FLD_CRn, FLD_op1, FLD_op0);
return true;
}
/* Encode the PSTATE field operand for e.g. MSR , #. */
bool
aarch64_ins_pstatefield (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* op1:op2 */
insert_fields (code, info->pstatefield, inst->opcode->mask, 2,
FLD_op2, FLD_op1);
/* Extra CRm mask. */
if (info->sysreg.flags | F_REG_IN_CRM)
insert_field (FLD_CRm, code, PSTATE_DECODE_CRM (info->sysreg.flags), 0);
return true;
}
/* Encode the system instruction op operand for e.g. AT , . */
bool
aarch64_ins_sysins_op (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* op1:CRn:CRm:op2 */
insert_fields (code, info->sysins_op->value, inst->opcode->mask, 4,
FLD_op2, FLD_CRm, FLD_CRn, FLD_op1);
return true;
}
/* Encode the memory barrier option operand for e.g. DMB |#. */
bool
aarch64_ins_barrier (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* CRm */
insert_field (FLD_CRm, code, info->barrier->value, 0);
return true;
}
/* Encode the memory barrier option operand for DSB nXS|#. */
bool
aarch64_ins_barrier_dsb_nxs (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* For the DSB nXS barrier variant: is a 5-bit unsigned immediate,
encoded in CRm<3:2>. */
aarch64_insn value = (info->barrier->value >> 2) - 4;
insert_field (FLD_CRm_dsb_nxs, code, value, 0);
return true;
}
/* Encode the prefetch operation option operand for e.g.
PRFM , [{, #}]. */
bool
aarch64_ins_prfop (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* prfop in Rt */
insert_field (FLD_Rt, code, info->prfop->value, 0);
return true;
}
/* Encode the hint number for instructions that alias HINT but take an
operand. */
bool
aarch64_ins_hint (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* CRm:op2. */
insert_fields (code, info->hint_option->value, 0, 2, FLD_op2, FLD_CRm);
return true;
}
/* Encode the extended register operand for e.g.
STR , [, {, {}}]. */
bool
aarch64_ins_reg_extended (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
enum aarch64_modifier_kind kind;
/* Rm */
insert_field (FLD_Rm, code, info->reg.regno, 0);
/* option */
kind = info->shifter.kind;
if (kind == AARCH64_MOD_LSL)
kind = info->qualifier == AARCH64_OPND_QLF_W
? AARCH64_MOD_UXTW : AARCH64_MOD_UXTX;
insert_field (FLD_option, code, aarch64_get_operand_modifier_value (kind), 0);
/* imm3 */
insert_field (FLD_imm3_10, code, info->shifter.amount, 0);
return true;
}
/* Encode the shifted register operand for e.g.
SUBS , , {, #}. */
bool
aarch64_ins_reg_shifted (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* Rm */
insert_field (FLD_Rm, code, info->reg.regno, 0);
/* shift */
insert_field (FLD_shift, code,
aarch64_get_operand_modifier_value (info->shifter.kind), 0);
/* imm6 */
insert_field (FLD_imm6_10, code, info->shifter.amount, 0);
return true;
}
/* Encode the LSL-shifted register operand for e.g.
ADDPT , , {, LSL #}. */
bool
aarch64_ins_reg_lsl_shifted (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
/* Rm */
insert_field (FLD_Rm, code, info->reg.regno, 0);
/* imm3 */
insert_field (FLD_imm3_10, code, info->shifter.amount, 0);
return true;
}
/* Encode an SVE address [ , #*, MUL VL],
where is a 4-bit signed value and where is 1 plus
SELF's operand-dependent value. fields[0] specifies the field that
holds . is encoded in the SVE_imm4 field. */
bool
aarch64_ins_sve_addr_ri_s4xvl (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int factor = 1 + get_operand_specific_data (self);
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_field (FLD_SVE_imm4, code, info->addr.offset.imm / factor, 0);
return true;
}
/* Encode an SVE address [ , #*, MUL VL],
where is a 6-bit signed value and where is 1 plus
SELF's operand-dependent value. fields[0] specifies the field that
holds . is encoded in the SVE_imm6 field. */
bool
aarch64_ins_sve_addr_ri_s6xvl (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int factor = 1 + get_operand_specific_data (self);
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_field (FLD_SVE_imm6, code, info->addr.offset.imm / factor, 0);
return true;
}
/* Encode an SVE address [ , #*, MUL VL],
where is a 9-bit signed value and where is 1 plus
SELF's operand-dependent value. fields[0] specifies the field that
holds . is encoded in the concatenation of the SVE_imm6
and imm3 fields, with imm3 being the less-significant part. */
bool
aarch64_ins_sve_addr_ri_s9xvl (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int factor = 1 + get_operand_specific_data (self);
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_fields (code, info->addr.offset.imm / factor, 0,
2, FLD_imm3_10, FLD_SVE_imm6);
return true;
}
/* Encode an SVE address [X, # << ], where
is a 4-bit signed number and where is SELF's operand-dependent
value. fields[0] specifies the base register field. */
bool
aarch64_ins_sve_addr_ri_s4 (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int factor = 1 << get_operand_specific_data (self);
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_field (FLD_SVE_imm4, code, info->addr.offset.imm / factor, 0);
return true;
}
/* Encode an SVE address [X, # << ], where
is a 6-bit unsigned number and where is SELF's operand-dependent
value. fields[0] specifies the base register field. */
bool
aarch64_ins_sve_addr_ri_u6 (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int factor = 1 << get_operand_specific_data (self);
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_field (FLD_SVE_imm6, code, info->addr.offset.imm / factor, 0);
return true;
}
/* Encode an SVE address [X, X{, LSL #}], where
is SELF's operand-dependent value. fields[0] specifies the base
register field and fields[1] specifies the offset register field. */
bool
aarch64_ins_sve_addr_rr_lsl (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_field (self->fields[1], code, info->addr.offset.regno, 0);
return true;
}
/* Encode an SVE address [X, Z., (S|U)XTW {#}], where
is SELF's operand-dependent value. fields[0] specifies the
base register field, fields[1] specifies the offset register field and
fields[2] is a single-bit field that selects SXTW over UXTW. */
bool
aarch64_ins_sve_addr_rz_xtw (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_field (self->fields[1], code, info->addr.offset.regno, 0);
if (info->shifter.kind == AARCH64_MOD_UXTW)
insert_field (self->fields[2], code, 0, 0);
else
insert_field (self->fields[2], code, 1, 0);
return true;
}
/* Encode an SVE address [Z., # << ], where is a
5-bit unsigned number and where is SELF's operand-dependent value.
fields[0] specifies the base register field. */
bool
aarch64_ins_sve_addr_zi_u5 (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int factor = 1 << get_operand_specific_data (self);
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_field (FLD_imm5, code, info->addr.offset.imm / factor, 0);
return true;
}
/* Encode an SVE address [Z., Z.{, {#}}],
where is fixed by the instruction and where is a
2-bit unsigned number. fields[0] specifies the base register field
and fields[1] specifies the offset register field. */
static bool
aarch64_ext_sve_addr_zz (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
insert_field (self->fields[0], code, info->addr.base_regno, 0);
insert_field (self->fields[1], code, info->addr.offset.regno, 0);
insert_field (FLD_SVE_msz, code, info->shifter.amount, 0);
return true;
}
/* Encode an SVE address [Z., Z.{, LSL #}], where
is a 2-bit unsigned number. fields[0] specifies the base register
field and fields[1] specifies the offset register field. */
bool
aarch64_ins_sve_addr_zz_lsl (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors)
{
return aarch64_ext_sve_addr_zz (self, info, code, errors);
}
/* Encode an SVE address [Z., Z., SXTW {#}], where
is a 2-bit unsigned number. fields[0] specifies the base register
field and fields[1] specifies the offset register field. */
bool
aarch64_ins_sve_addr_zz_sxtw (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors)
{
return aarch64_ext_sve_addr_zz (self, info, code, errors);
}
/* Encode an SVE address [Z., Z., UXTW {#}], where
is a 2-bit unsigned number. fields[0] specifies the base register
field and fields[1] specifies the offset register field. */
bool
aarch64_ins_sve_addr_zz_uxtw (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors)
{
return aarch64_ext_sve_addr_zz (self, info, code, errors);
}
/* Encode an SVE ADD/SUB immediate. */
bool
aarch64_ins_sve_aimm (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
if (info->shifter.amount == 8)
insert_all_fields (self, code, (info->imm.value & 0xff) | 256);
else if (info->imm.value != 0 && (info->imm.value & 0xff) == 0)
insert_all_fields (self, code, ((info->imm.value / 256) & 0xff) | 256);
else
insert_all_fields (self, code, info->imm.value & 0xff);
return true;
}
bool
aarch64_ins_sve_aligned_reglist (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
unsigned int num_regs = get_operand_specific_data (self);
unsigned int val = info->reglist.first_regno;
insert_field (self->fields[0], code, val / num_regs, 0);
return true;
}
/* Encode an SVE CPY/DUP immediate. */
bool
aarch64_ins_sve_asimm (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst,
aarch64_operand_error *errors)
{
return aarch64_ins_sve_aimm (self, info, code, inst, errors);
}
/* Encode Zn[MM], where MM has a 7-bit triangular encoding. The fields
array specifies which field to use for Zn. MM is encoded in the
concatenation of imm5 and SVE_tszh, with imm5 being the less
significant part. */
bool
aarch64_ins_sve_index (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
unsigned int esize = aarch64_get_qualifier_esize (info->qualifier);
insert_field (self->fields[0], code, info->reglane.regno, 0);
insert_all_fields_after (self, 1, code,
(info->reglane.index * 2 + 1) * esize);
return true;
}
/* Encode a logical/bitmask immediate for the MOV alias of SVE DUPM. */
bool
aarch64_ins_sve_limm_mov (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst,
aarch64_operand_error *errors)
{
return aarch64_ins_limm (self, info, code, inst, errors);
}
/* Encode Zn[MM], where Zn occupies the least-significant part of the field
and where MM occupies the most-significant part. The operand-dependent
value specifies the number of bits in Zn. */
bool
aarch64_ins_sve_quad_index (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
unsigned int reg_bits = get_operand_specific_data (self);
assert (info->reglane.regno < (1U << reg_bits));
unsigned int val = (info->reglane.index << reg_bits) + info->reglane.regno;
insert_all_fields (self, code, val);
return true;
}
/* Encode {Zn. - Zm.}. The fields array specifies which field
to use for Zn. */
bool
aarch64_ins_sve_reglist (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
insert_field (self->fields[0], code, info->reglist.first_regno, 0);
return true;
}
/* Encode a strided register list. The first field holds the top bit
(0 or 16) and the second field holds the lower bits. The stride is
16 divided by the list length. */
bool
aarch64_ins_sve_strided_reglist (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors
ATTRIBUTE_UNUSED)
{
unsigned int num_regs = get_operand_specific_data (self);
unsigned int mask ATTRIBUTE_UNUSED = 16 | (16 / num_regs - 1);
unsigned int val = info->reglist.first_regno;
assert ((val & mask) == val);
insert_field (self->fields[0], code, val >> 4, 0);
insert_field (self->fields[1], code, val & 15, 0);
return true;
}
/* Encode {, MUL #}. The fields array specifies which
fields to use for . - 1 is encoded in the SVE_imm4
field. */
bool
aarch64_ins_sve_scale (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
insert_all_fields (self, code, info->imm.value);
insert_field (FLD_SVE_imm4, code, info->shifter.amount - 1, 0);
return true;
}
/* Encode an SVE shift left immediate. */
bool
aarch64_ins_sve_shlimm (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
const aarch64_opnd_info *prev_operand;
unsigned int esize;
assert (info->idx > 0);
prev_operand = &inst->operands[info->idx - 1];
esize = aarch64_get_qualifier_esize (prev_operand->qualifier);
insert_all_fields (self, code, 8 * esize + info->imm.value);
return true;
}
/* Encode an SVE shift right immediate. */
bool
aarch64_ins_sve_shrimm (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
const aarch64_opnd_info *prev_operand;
unsigned int esize;
unsigned int opnd_backshift = get_operand_specific_data (self);
assert (info->idx >= (int)opnd_backshift);
prev_operand = &inst->operands[info->idx - opnd_backshift];
esize = aarch64_get_qualifier_esize (prev_operand->qualifier);
insert_all_fields (self, code, 16 * esize - info->imm.value);
return true;
}
/* Encode a single-bit immediate that selects between #0.5 and #1.0.
The fields array specifies which field to use. */
bool
aarch64_ins_sve_float_half_one (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
if (info->imm.value == 0x3f000000)
insert_field (self->fields[0], code, 0, 0);
else
insert_field (self->fields[0], code, 1, 0);
return true;
}
/* Encode a single-bit immediate that selects between #0.5 and #2.0.
The fields array specifies which field to use. */
bool
aarch64_ins_sve_float_half_two (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
if (info->imm.value == 0x3f000000)
insert_field (self->fields[0], code, 0, 0);
else
insert_field (self->fields[0], code, 1, 0);
return true;
}
/* Encode a single-bit immediate that selects between #0.0 and #1.0.
The fields array specifies which field to use. */
bool
aarch64_ins_sve_float_zero_one (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
if (info->imm.value == 0)
insert_field (self->fields[0], code, 0, 0);
else
insert_field (self->fields[0], code, 1, 0);
return true;
}
bool
aarch64_ins_sme_za_vrs1 (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int za_reg = info->indexed_za.regno;
int regno = info->indexed_za.index.regno & 3;
int imm = info->indexed_za.index.imm;
int v = info->indexed_za.v;
int countm1 = info->indexed_za.index.countm1;
insert_field (self->fields[0], code, v, 0);
insert_field (self->fields[1], code, regno, 0);
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_B:
insert_field (self->fields[2], code, imm / (countm1 + 1), 0);
break;
case AARCH64_OPND_QLF_S_H:
case AARCH64_OPND_QLF_S_S:
insert_field (self->fields[2], code, za_reg, 0);
insert_field (self->fields[3], code, imm / (countm1 + 1), 0);
break;
case AARCH64_OPND_QLF_S_D:
insert_field (self->fields[2], code, za_reg, 0);
break;
default:
return false;
}
return true;
}
bool
aarch64_ins_sme_za_vrs2 (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int za_reg = info->indexed_za.regno;
int regno = info->indexed_za.index.regno & 3;
int imm = info->indexed_za.index.imm;
int v = info->indexed_za.v;
int countm1 = info->indexed_za.index.countm1;
insert_field (self->fields[0], code, v, 0);
insert_field (self->fields[1], code, regno, 0);
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_B:
insert_field (self->fields[2], code, imm / (countm1 + 1), 0);
break;
case AARCH64_OPND_QLF_S_H:
insert_field (self->fields[2], code, za_reg, 0);
insert_field (self->fields[3], code, imm / (countm1 + 1), 0);
break;
case AARCH64_OPND_QLF_S_S:
case AARCH64_OPND_QLF_S_D:
insert_field (self->fields[2], code, za_reg, 0);
break;
default:
return false;
}
return true;
}
/* Encode in SME instruction such as MOVZA ZA tile slice to vector. */
bool
aarch64_ins_sme_za_tile_to_vec (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int fld_v = info->indexed_za.v;
int fld_rv = info->indexed_za.index.regno - 12;
int fld_zan_imm = info->indexed_za.index.imm;
int regno = info->indexed_za.regno;
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_B:
insert_field (FLD_imm4_5, code, fld_zan_imm, 0);
break;
case AARCH64_OPND_QLF_S_H:
insert_field (FLD_ZA8_1, code, regno, 0);
insert_field (FLD_imm3_5, code, fld_zan_imm, 0);
break;
case AARCH64_OPND_QLF_S_S:
insert_field (FLD_ZA7_2, code, regno, 0);
insert_field (FLD_off2, code, fld_zan_imm, 0);
break;
case AARCH64_OPND_QLF_S_D:
insert_field (FLD_ZA6_3, code, regno, 0);
insert_field (FLD_ol, code, fld_zan_imm, 0);
break;
case AARCH64_OPND_QLF_S_Q:
insert_field (FLD_ZA5_4, code, regno, 0);
break;
default:
return false;
}
insert_field (self->fields[0], code, fld_v, 0);
insert_field (self->fields[1], code, fld_rv, 0);
return true;
}
/* Encode in SME instruction such as MOVA ZA tile vector register number,
vector indicator, vector selector and immediate. */
bool
aarch64_ins_sme_za_hv_tiles (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int fld_size;
int fld_q;
int fld_v = info->indexed_za.v;
int fld_rv = info->indexed_za.index.regno - 12;
int fld_zan_imm = info->indexed_za.index.imm;
int regno = info->indexed_za.regno;
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_B:
fld_size = 0;
fld_q = 0;
break;
case AARCH64_OPND_QLF_S_H:
fld_size = 1;
fld_q = 0;
fld_zan_imm |= regno << 3;
break;
case AARCH64_OPND_QLF_S_S:
fld_size = 2;
fld_q = 0;
fld_zan_imm |= regno << 2;
break;
case AARCH64_OPND_QLF_S_D:
fld_size = 3;
fld_q = 0;
fld_zan_imm |= regno << 1;
break;
case AARCH64_OPND_QLF_S_Q:
fld_size = 3;
fld_q = 1;
fld_zan_imm = regno;
break;
default:
return false;
}
insert_field (self->fields[0], code, fld_size, 0);
insert_field (self->fields[1], code, fld_q, 0);
insert_field (self->fields[2], code, fld_v, 0);
insert_field (self->fields[3], code, fld_rv, 0);
insert_field (self->fields[4], code, fld_zan_imm, 0);
return true;
}
bool
aarch64_ins_sme_za_hv_tiles_range (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors
ATTRIBUTE_UNUSED)
{
int ebytes = aarch64_get_qualifier_esize (info->qualifier);
int range_size = get_opcode_dependent_value (inst->opcode);
int fld_v = info->indexed_za.v;
int fld_rv = info->indexed_za.index.regno - 12;
int imm = info->indexed_za.index.imm;
int max_value = 16 / range_size / ebytes;
if (max_value == 0)
max_value = 1;
assert (imm % range_size == 0 && (imm / range_size) < max_value);
int fld_zan_imm = (info->indexed_za.regno * max_value) | (imm / range_size);
assert (fld_zan_imm < (range_size == 4 && ebytes < 8 ? 4 : 8));
insert_field (self->fields[0], code, fld_v, 0);
insert_field (self->fields[1], code, fld_rv, 0);
insert_field (self->fields[2], code, fld_zan_imm, 0);
return true;
}
/* Encode in SME instruction ZERO list of up to eight 64-bit element tile names
separated by commas, encoded in the "imm8" field.
For programmer convenience an assembler must also accept the names of
32-bit, 16-bit and 8-bit element tiles which are converted into the
corresponding set of 64-bit element tiles.
*/
bool
aarch64_ins_sme_za_list (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int fld_mask = info->imm.value;
insert_field (self->fields[0], code, fld_mask, 0);
return true;
}
bool
aarch64_ins_sme_za_array (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int regno = info->indexed_za.index.regno & 3;
int imm = info->indexed_za.index.imm;
int countm1 = info->indexed_za.index.countm1;
assert (imm % (countm1 + 1) == 0);
insert_field (self->fields[0], code, regno, 0);
insert_field (self->fields[1], code, imm / (countm1 + 1), 0);
return true;
}
bool
aarch64_ins_sme_addr_ri_u4xvl (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int regno = info->addr.base_regno;
int imm = info->addr.offset.imm;
insert_field (self->fields[0], code, regno, 0);
insert_field (self->fields[1], code, imm, 0);
return true;
}
/* Encode in SMSTART and SMSTOP {SM | ZA } mode. */
bool
aarch64_ins_sme_sm_za (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
aarch64_insn fld_crm;
/* Set CRm[3:1] bits. */
if (info->reg.regno == 's')
fld_crm = 0x02 ; /* SVCRSM. */
else if (info->reg.regno == 'z')
fld_crm = 0x04; /* SVCRZA. */
else
return false;
insert_field (self->fields[0], code, fld_crm, 0);
return true;
}
/* Encode source scalable predicate register (Pn), name of the index base
register W12-W15 (Rm), and optional element index, defaulting to 0, in the
range 0 to one less than the number of vector elements in a 128-bit vector
register, encoded in "i1:tszh:tszl".
*/
bool
aarch64_ins_sme_pred_reg_with_index (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int fld_pn = info->indexed_za.regno;
int fld_rm = info->indexed_za.index.regno - 12;
int imm = info->indexed_za.index.imm;
int fld_i1, fld_tszh, fld_tshl;
insert_field (self->fields[0], code, fld_rm, 0);
insert_field (self->fields[1], code, fld_pn, 0);
/* Optional element index, defaulting to 0, in the range 0 to one less than
the number of vector elements in a 128-bit vector register, encoded in
"i1:tszh:tszl".
i1 tszh tszl
0 0 000 RESERVED
x x xx1 B
x x x10 H
x x 100 S
x 1 000 D
*/
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_B:
/* is 4 bit value. */
fld_i1 = (imm >> 3) & 0x1;
fld_tszh = (imm >> 2) & 0x1;
fld_tshl = ((imm << 1) | 0x1) & 0x7;
break;
case AARCH64_OPND_QLF_S_H:
/* is 3 bit value. */
fld_i1 = (imm >> 2) & 0x1;
fld_tszh = (imm >> 1) & 0x1;
fld_tshl = ((imm << 2) | 0x2) & 0x7;
break;
case AARCH64_OPND_QLF_S_S:
/* is 2 bit value. */
fld_i1 = (imm >> 1) & 0x1;
fld_tszh = imm & 0x1;
fld_tshl = 0x4;
break;
case AARCH64_OPND_QLF_S_D:
/* is 1 bit value. */
fld_i1 = imm & 0x1;
fld_tszh = 0x1;
fld_tshl = 0x0;
break;
default:
return false;
}
insert_field (self->fields[2], code, fld_i1, 0);
insert_field (self->fields[3], code, fld_tszh, 0);
insert_field (self->fields[4], code, fld_tshl, 0);
return true;
}
/* Insert X0-X30. Register 31 is unallocated. */
bool
aarch64_ins_x0_to_x30 (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
assert (info->reg.regno <= 30);
insert_field (self->fields[0], code, info->reg.regno, 0);
return true;
}
/* Insert an indexed register, with the first field being the register
number and the remaining fields being the index. */
bool
aarch64_ins_simple_index (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
int bias = get_operand_specific_data (self);
insert_field (self->fields[0], code, info->reglane.regno - bias, 0);
insert_all_fields_after (self, 1, code, info->reglane.index);
return true;
}
/* Insert a plain shift-right immediate, when there is only a single
element size. */
bool
aarch64_ins_plain_shrimm (const aarch64_operand *self,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED,
aarch64_operand_error *errors ATTRIBUTE_UNUSED)
{
unsigned int base = 1 << get_operand_field_width (self, 0);
insert_field (self->fields[0], code, base - info->imm.value, 0);
return true;
}
/* Miscellaneous encoding functions. */
/* Encode size[0], i.e. bit 22, for
e.g. FCVTN