/* aarch64-asm.c -- AArch64 assembler support.
Copyright 2012, 2013 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 "aarch64-asm.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 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);
}
/* Operand inserters. */
/* Insert register number. */
const char*
aarch64_ins_regno (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED)
{
insert_field (self->fields[0], code, info->reg.regno, 0);
return 0;
}
/* Insert register number, index and/or other data for SIMD register element
operand, e.g. the last source operand in
SQDMLAL , , .[]. */
const char*
aarch64_ins_reglane (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst)
{
/* 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, 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
{
/* index for e.g. SQDMLAL , , .[]
or SQDMLAL , , .[]. */
switch (info->qualifier)
{
case AARCH64_OPND_QLF_S_H:
/* H:L:M */
insert_fields (code, info->reglane.index, 0, 3, FLD_M, FLD_L, FLD_H);
break;
case AARCH64_OPND_QLF_S_S:
/* H:L */
insert_fields (code, info->reglane.index, 0, 2, FLD_L, FLD_H);
break;
case AARCH64_OPND_QLF_S_D:
/* H */
insert_field (FLD_H, code, info->reglane.index, 0);
break;
default:
assert (0);
}
}
return 0;
}
/* Insert regno and len field of a register list operand, e.g. Vn in TBL. */
const char*
aarch64_ins_reglist (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst 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 0;
}
/* Insert Rt and opcode fields for a register list operand, e.g. Vt
in AdvSIMD load/store instructions. */
const char*
aarch64_ins_ldst_reglist (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst)
{
aarch64_insn value;
/* 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: assert (0);
}
break;
case 2:
value = info->reglist.num_regs == 4 ? 0x3 : 0x8;
break;
case 3:
value = 0x4;
break;
case 4:
value = 0x0;
break;
default:
assert (0);
}
insert_field (FLD_opcode, code, value, 0);
return 0;
}
/* Insert Rt and S fields for a register list operand, e.g. Vt in AdvSIMD load
single structure to all lanes instructions. */
const char*
aarch64_ins_ldst_reglist_r (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst)
{
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 0;
}
/* 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. */
const char*
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_field field = {0, 0};
aarch64_insn QSsize; /* fields Q:S:size. */
aarch64_insn opcodeh2; /* 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:
assert (0);
}
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 0;
}
/* Insert fields immh:immb and/or Q for e.g. the shift immediate in
SSHR ., ., #
or SSHR , , #. */
const char*
aarch64_ins_advsimd_imm_shift (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst)
{
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 0;
}
/* Insert fields for e.g. the immediate operands in
BFM , , #, #. */
const char*
aarch64_ins_imm (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED)
{
int64_t imm;
/* Maximum of two fields to insert. */
assert (self->fields[2] == FLD_NIL);
imm = info->imm.value;
if (operand_need_shift_by_two (self))
imm >>= 2;
if (self->fields[1] == FLD_NIL)
insert_field (self->fields[0], code, imm, 0);
else
/* e.g. TBZ b5:b40. */
insert_fields (code, imm, 0, 2, self->fields[1], self->fields[0]);
return 0;
}
/* Insert immediate and its shift amount for e.g. the last operand in
MOVZ , #{, LSL #}. */
const char*
aarch64_ins_imm_half (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst)
{
/* imm16 */
aarch64_ins_imm (self, info, code, inst);
/* hw */
insert_field (FLD_hw, code, info->shifter.amount >> 4, 0);
return 0;
}
/* Insert cmode and "a:b:c:d:e:f:g:h" fields for e.g. the last operand in
MOVI ., # {, LSL #}. */
const char*
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)
{
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);
}
assert (imm <= 255);
insert_fields (code, imm, 0, 2, FLD_defgh, FLD_abc);
if (kind == AARCH64_MOD_NONE)
return 0;
/* 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);
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 0;
}
/* Insert # for the immediate operand in fp fix-point instructions,
e.g. SCVTF
, , #. */
const char*
aarch64_ins_fbits (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED)
{
insert_field (self->fields[0], code, 64 - info->imm.value, 0);
return 0;
}
/* Insert arithmetic immediate for e.g. the last operand in
SUBS , , # {, }. */
const char*
aarch64_ins_aimm (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst 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 0;
}
/* Insert logical/bitmask immediate for e.g. the last operand in
ORR , , #. */
const char*
aarch64_ins_limm (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst ATTRIBUTE_UNUSED)
{
aarch64_insn value;
uint64_t imm = info->imm.value;
int is32 = aarch64_get_qualifier_esize (inst->operands[0].qualifier) == 4;
if (inst->opcode->op == OP_BIC)
imm = ~imm;
if (aarch64_logical_immediate_p (imm, is32, &value) == FALSE)
/* The constraint check should have guaranteed this wouldn't happen. */
assert (0);
insert_fields (code, value, 0, 3, self->fields[2], self->fields[1],
self->fields[0]);
return 0;
}
/* Encode Ft for e.g. STR , [, {, {}}]
or LDP , , [], #. */
const char*
aarch64_ins_ft (const aarch64_operand *self, const aarch64_opnd_info *info,
aarch64_insn *code, const aarch64_inst *inst)
{
aarch64_insn value;
assert (info->idx == 0);
/* Rt */
aarch64_ins_regno (self, info, code, inst);
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: assert (0);
}
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 0;
}
/* Encode the address operand for e.g. STXRB , , [{,#0}]. */
const char*
aarch64_ins_addr_simple (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED)
{
/* Rn */
insert_field (FLD_Rn, code, info->addr.base_regno, 0);
return 0;
}
/* Encode the address operand for e.g.
STR , [, {, {}}]. */
const char*
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_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 0;
}
/* Encode the address operand for e.g. LDRSW , [, #]!. */
const char*
aarch64_ins_addr_simm (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst 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)
/* 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 0;
}
/* Encode the address operand for e.g. LDRSW , [{, #}]. */
const char*
aarch64_ins_addr_uimm12 (const aarch64_operand *self,
const aarch64_opnd_info *info,
aarch64_insn *code,
const aarch64_inst *inst 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 0;
}
/* Encode the address operand for e.g.
LD1 {., ., .}, [], >. */
const char*
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)
{
/* 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 0;
}
/* Encode the condition operand for e.g. CSEL , , , . */
const char*
aarch64_ins_cond (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED)
{
/* cond */
insert_field (FLD_cond, code, info->cond->value, 0);
return 0;
}
/* Encode the system register operand for e.g. MRS , . */
const char*
aarch64_ins_sysreg (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED)
{
/* op0:op1:CRn:CRm:op2 */
insert_fields (code, info->sysreg, inst->opcode->mask, 5,
FLD_op2, FLD_CRm, FLD_CRn, FLD_op1, FLD_op0);
return 0;
}
/* Encode the PSTATE field operand for e.g. MSR , #. */
const char*
aarch64_ins_pstatefield (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst ATTRIBUTE_UNUSED)
{
/* op1:op2 */
insert_fields (code, info->pstatefield, inst->opcode->mask, 2,
FLD_op2, FLD_op1);
return 0;
}
/* Encode the system instruction op operand for e.g. AT , . */
const char*
aarch64_ins_sysins_op (const aarch64_operand *self ATTRIBUTE_UNUSED,
const aarch64_opnd_info *info, aarch64_insn *code,
const aarch64_inst *inst 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 0;
}
/* Encode the memory barrier option operand for e.g. DMB