| /* aarch64-dis.c -- AArch64 disassembler. |
| Copyright (C) 2009-2016 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 <http://www.gnu.org/licenses/>. */ |
| |
| #include "sysdep.h" |
| #include "bfd_stdint.h" |
| #include "dis-asm.h" |
| #include "libiberty.h" |
| #include "opintl.h" |
| #include "aarch64-dis.h" |
| #include "elf-bfd.h" |
| |
| #define ERR_OK 0 |
| #define ERR_UND -1 |
| #define ERR_UNP -3 |
| #define ERR_NYI -5 |
| |
| #define INSNLEN 4 |
| |
| /* Cached mapping symbol state. */ |
| enum map_type |
| { |
| MAP_INSN, |
| MAP_DATA |
| }; |
| |
| static enum map_type last_type; |
| static int last_mapping_sym = -1; |
| static bfd_vma last_mapping_addr = 0; |
| |
| /* Other options */ |
| static int no_aliases = 0; /* If set disassemble as most general inst. */ |
| |
| |
| static void |
| set_default_aarch64_dis_options (struct disassemble_info *info ATTRIBUTE_UNUSED) |
| { |
| } |
| |
| static void |
| parse_aarch64_dis_option (const char *option, unsigned int len ATTRIBUTE_UNUSED) |
| { |
| /* Try to match options that are simple flags */ |
| if (CONST_STRNEQ (option, "no-aliases")) |
| { |
| no_aliases = 1; |
| return; |
| } |
| |
| if (CONST_STRNEQ (option, "aliases")) |
| { |
| no_aliases = 0; |
| return; |
| } |
| |
| #ifdef DEBUG_AARCH64 |
| if (CONST_STRNEQ (option, "debug_dump")) |
| { |
| debug_dump = 1; |
| return; |
| } |
| #endif /* DEBUG_AARCH64 */ |
| |
| /* Invalid option. */ |
| fprintf (stderr, _("Unrecognised disassembler option: %s\n"), option); |
| } |
| |
| static void |
| parse_aarch64_dis_options (const char *options) |
| { |
| const char *option_end; |
| |
| if (options == NULL) |
| return; |
| |
| while (*options != '\0') |
| { |
| /* Skip empty options. */ |
| if (*options == ',') |
| { |
| options++; |
| continue; |
| } |
| |
| /* We know that *options is neither NUL or a comma. */ |
| option_end = options + 1; |
| while (*option_end != ',' && *option_end != '\0') |
| option_end++; |
| |
| parse_aarch64_dis_option (options, option_end - options); |
| |
| /* Go on to the next one. If option_end points to a comma, it |
| will be skipped above. */ |
| options = option_end; |
| } |
| } |
| |
| /* Functions doing the instruction disassembling. */ |
| |
| /* The unnamed arguments consist of the number of fields and information about |
| these fields where the VALUE will be extracted from CODE and returned. |
| 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 most signficant |
| field for VALUE comes the first, e.g. the <index> in |
| SQDMLAL <Va><d>, <Vb><n>, <Vm>.<Ts>[<index>] |
| is encoded in H:L:M in some cases, the fields H:L:M should be passed in |
| the order of H, L, M. */ |
| |
| aarch64_insn |
| extract_fields (aarch64_insn code, 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); |
| aarch64_insn value = 0x0; |
| while (num--) |
| { |
| kind = va_arg (va, enum aarch64_field_kind); |
| field = &fields[kind]; |
| value <<= field->width; |
| value |= extract_field (kind, code, mask); |
| } |
| return value; |
| } |
| |
| /* Extract the value of all fields in SELF->fields from instruction CODE. |
| The least significant bit comes from the final field. */ |
| |
| static aarch64_insn |
| extract_all_fields (const aarch64_operand *self, aarch64_insn code) |
| { |
| aarch64_insn value; |
| unsigned int i; |
| enum aarch64_field_kind kind; |
| |
| value = 0; |
| for (i = 0; i < ARRAY_SIZE (self->fields) && self->fields[i] != FLD_NIL; ++i) |
| { |
| kind = self->fields[i]; |
| value <<= fields[kind].width; |
| value |= extract_field (kind, code, 0); |
| } |
| return value; |
| } |
| |
| /* Sign-extend bit I of VALUE. */ |
| static inline int32_t |
| sign_extend (aarch64_insn value, unsigned i) |
| { |
| uint32_t ret = value; |
| |
| assert (i < 32); |
| if ((value >> i) & 0x1) |
| { |
| uint32_t val = (uint32_t)(-1) << i; |
| ret = ret | val; |
| } |
| return (int32_t) ret; |
| } |
| |
| /* N.B. the following inline helpfer functions create a dependency on the |
| order of operand qualifier enumerators. */ |
| |
| /* Given VALUE, return qualifier for a general purpose register. */ |
| static inline enum aarch64_opnd_qualifier |
| get_greg_qualifier_from_value (aarch64_insn value) |
| { |
| enum aarch64_opnd_qualifier qualifier = AARCH64_OPND_QLF_W + value; |
| assert (value <= 0x1 |
| && aarch64_get_qualifier_standard_value (qualifier) == value); |
| return qualifier; |
| } |
| |
| /* Given VALUE, return qualifier for a vector register. This does not support |
| decoding instructions that accept the 2H vector type. */ |
| |
| static inline enum aarch64_opnd_qualifier |
| get_vreg_qualifier_from_value (aarch64_insn value) |
| { |
| enum aarch64_opnd_qualifier qualifier = AARCH64_OPND_QLF_V_8B + value; |
| |
| /* Instructions using vector type 2H should not call this function. Skip over |
| the 2H qualifier. */ |
| if (qualifier >= AARCH64_OPND_QLF_V_2H) |
| qualifier += 1; |
| |
| assert (value <= 0x8 |
| && aarch64_get_qualifier_standard_value (qualifier) == value); |
| return qualifier; |
| } |
| |
| /* Given VALUE, return qualifier for an FP or AdvSIMD scalar register. */ |
| static inline enum aarch64_opnd_qualifier |
| get_sreg_qualifier_from_value (aarch64_insn value) |
| { |
| enum aarch64_opnd_qualifier qualifier = AARCH64_OPND_QLF_S_B + value; |
| |
| assert (value <= 0x4 |
| && aarch64_get_qualifier_standard_value (qualifier) == value); |
| return qualifier; |
| } |
| |
| /* Given the instruction in *INST which is probably half way through the |
| decoding and our caller wants to know the expected qualifier for operand |
| I. Return such a qualifier if we can establish it; otherwise return |
| AARCH64_OPND_QLF_NIL. */ |
| |
| static aarch64_opnd_qualifier_t |
| get_expected_qualifier (const aarch64_inst *inst, int i) |
| { |
| aarch64_opnd_qualifier_seq_t qualifiers; |
| /* Should not be called if the qualifier is known. */ |
| assert (inst->operands[i].qualifier == AARCH64_OPND_QLF_NIL); |
| if (aarch64_find_best_match (inst, inst->opcode->qualifiers_list, |
| i, qualifiers)) |
| return qualifiers[i]; |
| else |
| return AARCH64_OPND_QLF_NIL; |
| } |
| |
| /* Operand extractors. */ |
| |
| int |
| aarch64_ext_regno (const aarch64_operand *self, aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| info->reg.regno = extract_field (self->fields[0], code, 0); |
| return 1; |
| } |
| |
| int |
| aarch64_ext_regno_pair (const aarch64_operand *self ATTRIBUTE_UNUSED, aarch64_opnd_info *info, |
| const aarch64_insn code ATTRIBUTE_UNUSED, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| assert (info->idx == 1 |
| || info->idx ==3); |
| info->reg.regno = inst->operands[info->idx - 1].reg.regno + 1; |
| return 1; |
| } |
| |
| /* e.g. IC <ic_op>{, <Xt>}. */ |
| int |
| aarch64_ext_regrt_sysins (const aarch64_operand *self, aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| info->reg.regno = extract_field (self->fields[0], code, 0); |
| assert (info->idx == 1 |
| && (aarch64_get_operand_class (inst->operands[0].type) |
| == AARCH64_OPND_CLASS_SYSTEM)); |
| /* This will make the constraint checking happy and more importantly will |
| help the disassembler determine whether this operand is optional or |
| not. */ |
| info->present = aarch64_sys_ins_reg_has_xt (inst->operands[0].sysins_op); |
| |
| return 1; |
| } |
| |
| /* e.g. SQDMLAL <Va><d>, <Vb><n>, <Vm>.<Ts>[<index>]. */ |
| int |
| aarch64_ext_reglane (const aarch64_operand *self, aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| /* regno */ |
| info->reglane.regno = extract_field (self->fields[0], code, |
| inst->opcode->mask); |
| |
| /* Index and/or type. */ |
| if (inst->opcode->iclass == asisdone |
| || inst->opcode->iclass == asimdins) |
| { |
| if (info->type == AARCH64_OPND_En |
| && inst->opcode->operands[0] == AARCH64_OPND_Ed) |
| { |
| unsigned shift; |
| /* index2 for e.g. INS <Vd>.<Ts>[<index1>], <Vn>.<Ts>[<index2>]. */ |
| assert (info->idx == 1); /* Vn */ |
| aarch64_insn value = extract_field (FLD_imm4, code, 0); |
| /* Depend on AARCH64_OPND_Ed to determine the qualifier. */ |
| info->qualifier = get_expected_qualifier (inst, info->idx); |
| shift = get_logsz (aarch64_get_qualifier_esize (info->qualifier)); |
| info->reglane.index = value >> shift; |
| } |
| else |
| { |
| /* index and type for e.g. DUP <V><d>, <Vn>.<T>[<index>]. |
| imm5<3:0> <V> |
| 0000 RESERVED |
| xxx1 B |
| xx10 H |
| x100 S |
| 1000 D */ |
| int pos = -1; |
| aarch64_insn value = extract_field (FLD_imm5, code, 0); |
| while (++pos <= 3 && (value & 0x1) == 0) |
| value >>= 1; |
| if (pos > 3) |
| return 0; |
| info->qualifier = get_sreg_qualifier_from_value (pos); |
| info->reglane.index = (unsigned) (value >> 1); |
| } |
| } |
| else |
| { |
| /* Index only for e.g. SQDMLAL <Va><d>, <Vb><n>, <Vm>.<Ts>[<index>] |
| or SQDMLAL <Va><d>, <Vb><n>, <Vm>.<Ts>[<index>]. */ |
| |
| /* Need information in other operand(s) to help decoding. */ |
| info->qualifier = get_expected_qualifier (inst, info->idx); |
| switch (info->qualifier) |
| { |
| case AARCH64_OPND_QLF_S_H: |
| /* h:l:m */ |
| info->reglane.index = extract_fields (code, 0, 3, FLD_H, FLD_L, |
| FLD_M); |
| info->reglane.regno &= 0xf; |
| break; |
| case AARCH64_OPND_QLF_S_S: |
| /* h:l */ |
| info->reglane.index = extract_fields (code, 0, 2, FLD_H, FLD_L); |
| break; |
| case AARCH64_OPND_QLF_S_D: |
| /* H */ |
| info->reglane.index = extract_field (FLD_H, code, 0); |
| break; |
| default: |
| return 0; |
| } |
| |
| if (inst->opcode->op == OP_FCMLA_ELEM) |
| { |
| /* Complex operand takes two elements. */ |
| if (info->reglane.index & 1) |
| return 0; |
| info->reglane.index /= 2; |
| } |
| } |
| |
| return 1; |
| } |
| |
| int |
| aarch64_ext_reglist (const aarch64_operand *self, aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| /* R */ |
| info->reglist.first_regno = extract_field (self->fields[0], code, 0); |
| /* len */ |
| info->reglist.num_regs = extract_field (FLD_len, code, 0) + 1; |
| return 1; |
| } |
| |
| /* Decode Rt and opcode fields of Vt in AdvSIMD load/store instructions. */ |
| int |
| aarch64_ext_ldst_reglist (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| aarch64_insn value; |
| /* Number of elements in each structure to be loaded/stored. */ |
| unsigned expected_num = get_opcode_dependent_value (inst->opcode); |
| |
| struct |
| { |
| unsigned is_reserved; |
| unsigned num_regs; |
| unsigned num_elements; |
| } data [] = |
| { {0, 4, 4}, |
| {1, 4, 4}, |
| {0, 4, 1}, |
| {0, 4, 2}, |
| {0, 3, 3}, |
| {1, 3, 3}, |
| {0, 3, 1}, |
| {0, 1, 1}, |
| {0, 2, 2}, |
| {1, 2, 2}, |
| {0, 2, 1}, |
| }; |
| |
| /* Rt */ |
| info->reglist.first_regno = extract_field (FLD_Rt, code, 0); |
| /* opcode */ |
| value = extract_field (FLD_opcode, code, 0); |
| if (expected_num != data[value].num_elements || data[value].is_reserved) |
| return 0; |
| info->reglist.num_regs = data[value].num_regs; |
| |
| return 1; |
| } |
| |
| /* Decode Rt and S fields of Vt in AdvSIMD load single structure to all |
| lanes instructions. */ |
| int |
| aarch64_ext_ldst_reglist_r (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| aarch64_insn value; |
| |
| /* Rt */ |
| info->reglist.first_regno = extract_field (FLD_Rt, code, 0); |
| /* S */ |
| value = extract_field (FLD_S, code, 0); |
| |
| /* Number of registers is equal to the number of elements in |
| each structure to be loaded/stored. */ |
| info->reglist.num_regs = get_opcode_dependent_value (inst->opcode); |
| assert (info->reglist.num_regs >= 1 && info->reglist.num_regs <= 4); |
| |
| /* Except when it is LD1R. */ |
| if (info->reglist.num_regs == 1 && value == (aarch64_insn) 1) |
| info->reglist.num_regs = 2; |
| |
| return 1; |
| } |
| |
| /* Decode Q, opcode<2:1>, S, size and Rt fields of Vt in AdvSIMD |
| load/store single element instructions. */ |
| int |
| aarch64_ext_ldst_elemlist (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, const 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> */ |
| |
| /* Rt */ |
| info->reglist.first_regno = extract_field (FLD_Rt, code, 0); |
| |
| /* Decode the index, opcode<2:1> and size. */ |
| gen_sub_field (FLD_asisdlso_opcode, 1, 2, &field); |
| opcodeh2 = extract_field_2 (&field, code, 0); |
| QSsize = extract_fields (code, 0, 3, FLD_Q, FLD_S, FLD_vldst_size); |
| switch (opcodeh2) |
| { |
| case 0x0: |
| info->qualifier = AARCH64_OPND_QLF_S_B; |
| /* Index encoded in "Q:S:size". */ |
| info->reglist.index = QSsize; |
| break; |
| case 0x1: |
| if (QSsize & 0x1) |
| /* UND. */ |
| return 0; |
| info->qualifier = AARCH64_OPND_QLF_S_H; |
| /* Index encoded in "Q:S:size<1>". */ |
| info->reglist.index = QSsize >> 1; |
| break; |
| case 0x2: |
| if ((QSsize >> 1) & 0x1) |
| /* UND. */ |
| return 0; |
| if ((QSsize & 0x1) == 0) |
| { |
| info->qualifier = AARCH64_OPND_QLF_S_S; |
| /* Index encoded in "Q:S". */ |
| info->reglist.index = QSsize >> 2; |
| } |
| else |
| { |
| if (extract_field (FLD_S, code, 0)) |
| /* UND */ |
| return 0; |
| info->qualifier = AARCH64_OPND_QLF_S_D; |
| /* Index encoded in "Q". */ |
| info->reglist.index = QSsize >> 3; |
| } |
| break; |
| default: |
| return 0; |
| } |
| |
| info->reglist.has_index = 1; |
| info->reglist.num_regs = 0; |
| /* Number of registers is equal to the number of elements in |
| each structure to be loaded/stored. */ |
| info->reglist.num_regs = get_opcode_dependent_value (inst->opcode); |
| assert (info->reglist.num_regs >= 1 && info->reglist.num_regs <= 4); |
| |
| return 1; |
| } |
| |
| /* Decode fields immh:immb and/or Q for e.g. |
| SSHR <Vd>.<T>, <Vn>.<T>, #<shift> |
| or SSHR <V><d>, <V><n>, #<shift>. */ |
| |
| int |
| aarch64_ext_advsimd_imm_shift (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| int pos; |
| aarch64_insn Q, imm, immh; |
| enum aarch64_insn_class iclass = inst->opcode->iclass; |
| |
| immh = extract_field (FLD_immh, code, 0); |
| if (immh == 0) |
| return 0; |
| imm = extract_fields (code, 0, 2, FLD_immh, FLD_immb); |
| pos = 4; |
| /* Get highest set bit in immh. */ |
| while (--pos >= 0 && (immh & 0x8) == 0) |
| immh <<= 1; |
| |
| assert ((iclass == asimdshf || iclass == asisdshf) |
| && (info->type == AARCH64_OPND_IMM_VLSR |
| || info->type == AARCH64_OPND_IMM_VLSL)); |
| |
| if (iclass == asimdshf) |
| { |
| Q = extract_field (FLD_Q, code, 0); |
| /* immh Q <T> |
| 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 */ |
| info->qualifier = |
| get_vreg_qualifier_from_value ((pos << 1) | (int) Q); |
| } |
| else |
| info->qualifier = get_sreg_qualifier_from_value (pos); |
| |
| if (info->type == AARCH64_OPND_IMM_VLSR) |
| /* immh <shift> |
| 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)) */ |
| info->imm.value = (16 << pos) - imm; |
| else |
| /* immh:immb |
| immh <shift> |
| 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) */ |
| info->imm.value = imm - (8 << pos); |
| |
| return 1; |
| } |
| |
| /* Decode shift immediate for e.g. sshr (imm). */ |
| int |
| aarch64_ext_shll_imm (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int64_t imm; |
| aarch64_insn val; |
| val = extract_field (FLD_size, code, 0); |
| switch (val) |
| { |
| case 0: imm = 8; break; |
| case 1: imm = 16; break; |
| case 2: imm = 32; break; |
| default: return 0; |
| } |
| info->imm.value = imm; |
| return 1; |
| } |
| |
| /* Decode imm for e.g. BFM <Wd>, <Wn>, #<immr>, #<imms>. |
| value in the field(s) will be extracted as unsigned immediate value. */ |
| int |
| aarch64_ext_imm (const aarch64_operand *self, aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int64_t imm; |
| |
| imm = extract_all_fields (self, code); |
| |
| if (operand_need_sign_extension (self)) |
| imm = sign_extend (imm, get_operand_fields_width (self) - 1); |
| |
| if (operand_need_shift_by_two (self)) |
| imm <<= 2; |
| |
| if (info->type == AARCH64_OPND_ADDR_ADRP) |
| imm <<= 12; |
| |
| info->imm.value = imm; |
| return 1; |
| } |
| |
| /* Decode imm and its shifter for e.g. MOVZ <Wd>, #<imm16>{, LSL #<shift>}. */ |
| int |
| aarch64_ext_imm_half (const aarch64_operand *self, aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| aarch64_ext_imm (self, info, code, inst); |
| info->shifter.kind = AARCH64_MOD_LSL; |
| info->shifter.amount = extract_field (FLD_hw, code, 0) << 4; |
| return 1; |
| } |
| |
| /* Decode cmode and "a:b:c:d:e:f:g:h" for e.g. |
| MOVI <Vd>.<T>, #<imm8> {, LSL #<amount>}. */ |
| int |
| aarch64_ext_advsimd_imm_modified (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| uint64_t imm; |
| enum aarch64_opnd_qualifier opnd0_qualifier = inst->operands[0].qualifier; |
| aarch64_field field = {0, 0}; |
| |
| assert (info->idx == 1); |
| |
| if (info->type == AARCH64_OPND_SIMD_FPIMM) |
| info->imm.is_fp = 1; |
| |
| /* a:b:c:d:e:f:g:h */ |
| imm = extract_fields (code, 0, 2, FLD_abc, FLD_defgh); |
| if (!info->imm.is_fp && aarch64_get_qualifier_esize (opnd0_qualifier) == 8) |
| { |
| /* Either MOVI <Dd>, #<imm> |
| or MOVI <Vd>.2D, #<imm>. |
| <imm> is a 64-bit immediate |
| 'aaaaaaaabbbbbbbbccccccccddddddddeeeeeeeeffffffffgggggggghhhhhhhh', |
| encoded in "a:b:c:d:e:f:g:h". */ |
| int i; |
| unsigned abcdefgh = imm; |
| for (imm = 0ull, i = 0; i < 8; i++) |
| if (((abcdefgh >> i) & 0x1) != 0) |
| imm |= 0xffull << (8 * i); |
| } |
| info->imm.value = imm; |
| |
| /* cmode */ |
| info->qualifier = get_expected_qualifier (inst, info->idx); |
| switch (info->qualifier) |
| { |
| case AARCH64_OPND_QLF_NIL: |
| /* no shift */ |
| info->shifter.kind = AARCH64_MOD_NONE; |
| return 1; |
| case AARCH64_OPND_QLF_LSL: |
| /* shift zeros */ |
| info->shifter.kind = AARCH64_MOD_LSL; |
| switch (aarch64_get_qualifier_esize (opnd0_qualifier)) |
| { |
| case 4: gen_sub_field (FLD_cmode, 1, 2, &field); break; /* per word */ |
| case 2: gen_sub_field (FLD_cmode, 1, 1, &field); break; /* per half */ |
| case 1: gen_sub_field (FLD_cmode, 1, 0, &field); break; /* per byte */ |
| default: assert (0); return 0; |
| } |
| /* 00: 0; 01: 8; 10:16; 11:24. */ |
| info->shifter.amount = extract_field_2 (&field, code, 0) << 3; |
| break; |
| case AARCH64_OPND_QLF_MSL: |
| /* shift ones */ |
| info->shifter.kind = AARCH64_MOD_MSL; |
| gen_sub_field (FLD_cmode, 0, 1, &field); /* per word */ |
| info->shifter.amount = extract_field_2 (&field, code, 0) ? 16 : 8; |
| break; |
| default: |
| assert (0); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Decode an 8-bit floating-point immediate. */ |
| int |
| aarch64_ext_fpimm (const aarch64_operand *self, aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| info->imm.value = extract_all_fields (self, code); |
| info->imm.is_fp = 1; |
| return 1; |
| } |
| |
| /* Decode rotate immediate for FCMLA <Vd>.<T>, <Vn>.<T>, <Vm>.<T>, #rotate. */ |
| int |
| aarch64_ext_imm_rotate (const aarch64_operand *self, aarch64_opnd_info *info, |
| const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| uint64_t rot = extract_field (self->fields[0], code, 0); |
| |
| switch (info->type) |
| { |
| case AARCH64_OPND_IMM_ROT1: |
| case AARCH64_OPND_IMM_ROT2: |
| /* rot value |
| 0 0 |
| 1 90 |
| 2 180 |
| 3 270 */ |
| assert (rot < 4U); |
| break; |
| case AARCH64_OPND_IMM_ROT3: |
| /* rot value |
| 0 90 |
| 1 270 */ |
| assert (rot < 2U); |
| rot = 2 * rot + 1; |
| break; |
| default: |
| assert (0); |
| return 0; |
| } |
| info->imm.value = rot * 90; |
| return 1; |
| } |
| |
| /* Decode scale for e.g. SCVTF <Dd>, <Wn>, #<fbits>. */ |
| int |
| aarch64_ext_fbits (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| info->imm.value = 64- extract_field (FLD_scale, code, 0); |
| return 1; |
| } |
| |
| /* Decode arithmetic immediate for e.g. |
| SUBS <Wd>, <Wn|WSP>, #<imm> {, <shift>}. */ |
| int |
| aarch64_ext_aimm (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| aarch64_insn value; |
| |
| info->shifter.kind = AARCH64_MOD_LSL; |
| /* shift */ |
| value = extract_field (FLD_shift, code, 0); |
| if (value >= 2) |
| return 0; |
| info->shifter.amount = value ? 12 : 0; |
| /* imm12 (unsigned) */ |
| info->imm.value = extract_field (FLD_imm12, code, 0); |
| |
| return 1; |
| } |
| |
| /* Return true if VALUE is a valid logical immediate encoding, storing the |
| decoded value in *RESULT if so. ESIZE is the number of bytes in the |
| decoded immediate. */ |
| static int |
| decode_limm (uint32_t esize, aarch64_insn value, int64_t *result) |
| { |
| uint64_t imm, mask; |
| uint32_t N, R, S; |
| unsigned simd_size; |
| |
| /* value is N:immr:imms. */ |
| S = value & 0x3f; |
| R = (value >> 6) & 0x3f; |
| N = (value >> 12) & 0x1; |
| |
| /* The immediate value is S+1 bits to 1, left rotated by SIMDsize - R |
| (in other words, right rotated by R), then replicated. */ |
| if (N != 0) |
| { |
| simd_size = 64; |
| mask = 0xffffffffffffffffull; |
| } |
| else |
| { |
| switch (S) |
| { |
| case 0x00 ... 0x1f: /* 0xxxxx */ simd_size = 32; break; |
| case 0x20 ... 0x2f: /* 10xxxx */ simd_size = 16; S &= 0xf; break; |
| case 0x30 ... 0x37: /* 110xxx */ simd_size = 8; S &= 0x7; break; |
| case 0x38 ... 0x3b: /* 1110xx */ simd_size = 4; S &= 0x3; break; |
| case 0x3c ... 0x3d: /* 11110x */ simd_size = 2; S &= 0x1; break; |
| default: return 0; |
| } |
| mask = (1ull << simd_size) - 1; |
| /* Top bits are IGNORED. */ |
| R &= simd_size - 1; |
| } |
| |
| if (simd_size > esize * 8) |
| return 0; |
| |
| /* NOTE: if S = simd_size - 1 we get 0xf..f which is rejected. */ |
| if (S == simd_size - 1) |
| return 0; |
| /* S+1 consecutive bits to 1. */ |
| /* NOTE: S can't be 63 due to detection above. */ |
| imm = (1ull << (S + 1)) - 1; |
| /* Rotate to the left by simd_size - R. */ |
| if (R != 0) |
| imm = ((imm << (simd_size - R)) & mask) | (imm >> R); |
| /* Replicate the value according to SIMD size. */ |
| switch (simd_size) |
| { |
| case 2: imm = (imm << 2) | imm; |
| /* Fall through. */ |
| case 4: imm = (imm << 4) | imm; |
| /* Fall through. */ |
| case 8: imm = (imm << 8) | imm; |
| /* Fall through. */ |
| case 16: imm = (imm << 16) | imm; |
| /* Fall through. */ |
| case 32: imm = (imm << 32) | imm; |
| /* Fall through. */ |
| case 64: break; |
| default: assert (0); return 0; |
| } |
| |
| *result = imm & ~((uint64_t) -1 << (esize * 4) << (esize * 4)); |
| |
| return 1; |
| } |
| |
| /* Decode a logical immediate for e.g. ORR <Wd|WSP>, <Wn>, #<imm>. */ |
| int |
| aarch64_ext_limm (const aarch64_operand *self, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| uint32_t esize; |
| aarch64_insn value; |
| |
| value = extract_fields (code, 0, 3, self->fields[0], self->fields[1], |
| self->fields[2]); |
| esize = aarch64_get_qualifier_esize (inst->operands[0].qualifier); |
| return decode_limm (esize, value, &info->imm.value); |
| } |
| |
| /* Decode a logical immediate for the BIC alias of AND (etc.). */ |
| int |
| aarch64_ext_inv_limm (const aarch64_operand *self, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| if (!aarch64_ext_limm (self, info, code, inst)) |
| return 0; |
| info->imm.value = ~info->imm.value; |
| return 1; |
| } |
| |
| /* Decode Ft for e.g. STR <Qt>, [<Xn|SP>, <R><m>{, <extend> {<amount>}}] |
| or LDP <Qt1>, <Qt2>, [<Xn|SP>], #<imm>. */ |
| int |
| aarch64_ext_ft (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| const aarch64_insn code, const aarch64_inst *inst) |
| { |
| aarch64_insn value; |
| |
| /* Rt */ |
| info->reg.regno = extract_field (FLD_Rt, code, 0); |
| |
| /* size */ |
| value = extract_field (FLD_ldst_size, code, 0); |
| if (inst->opcode->iclass == ldstpair_indexed |
| || inst->opcode->iclass == ldstnapair_offs |
| || inst->opcode->iclass == ldstpair_off |
| || inst->opcode->iclass == loadlit) |
| { |
| enum aarch64_opnd_qualifier qualifier; |
| switch (value) |
| { |
| case 0: qualifier = AARCH64_OPND_QLF_S_S; break; |
| case 1: qualifier = AARCH64_OPND_QLF_S_D; break; |
| case 2: qualifier = AARCH64_OPND_QLF_S_Q; break; |
| default: return 0; |
| } |
| info->qualifier = qualifier; |
| } |
| else |
| { |
| /* opc1:size */ |
| value = extract_fields (code, 0, 2, FLD_opc1, FLD_ldst_size); |
| if (value > 0x4) |
| return 0; |
| info->qualifier = get_sreg_qualifier_from_value (value); |
| } |
| |
| return 1; |
| } |
| |
| /* Decode the address operand for e.g. STXRB <Ws>, <Wt>, [<Xn|SP>{,#0}]. */ |
| int |
| aarch64_ext_addr_simple (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| /* Rn */ |
| info->addr.base_regno = extract_field (FLD_Rn, code, 0); |
| return 1; |
| } |
| |
| /* Decode the address operand for e.g. |
| STR <Qt>, [<Xn|SP>, <R><m>{, <extend> {<amount>}}]. */ |
| int |
| aarch64_ext_addr_regoff (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, const aarch64_inst *inst) |
| { |
| aarch64_insn S, value; |
| |
| /* Rn */ |
| info->addr.base_regno = extract_field (FLD_Rn, code, 0); |
| /* Rm */ |
| info->addr.offset.regno = extract_field (FLD_Rm, code, 0); |
| /* option */ |
| value = extract_field (FLD_option, code, 0); |
| info->shifter.kind = |
| aarch64_get_operand_modifier_from_value (value, TRUE /* extend_p */); |
| /* Fix-up the shifter kind; although the table-driven approach is |
| efficient, it is slightly inflexible, thus needing this fix-up. */ |
| if (info->shifter.kind == AARCH64_MOD_UXTX) |
| info->shifter.kind = AARCH64_MOD_LSL; |
| /* S */ |
| S = extract_field (FLD_S, code, 0); |
| if (S == 0) |
| { |
| info->shifter.amount = 0; |
| info->shifter.amount_present = 0; |
| } |
| else |
| { |
| int size; |
| /* Need information in other operand(s) to help achieve the decoding |
| from 'S' field. */ |
| info->qualifier = get_expected_qualifier (inst, info->idx); |
| /* Get the size of the data element that is accessed, which may be |
| different from that of the source register size, e.g. in strb/ldrb. */ |
| size = aarch64_get_qualifier_esize (info->qualifier); |
| info->shifter.amount = get_logsz (size); |
| info->shifter.amount_present = 1; |
| } |
| |
| return 1; |
| } |
| |
| /* Decode the address operand for e.g. LDRSW <Xt>, [<Xn|SP>], #<simm>. */ |
| int |
| aarch64_ext_addr_simm (const aarch64_operand *self, aarch64_opnd_info *info, |
| aarch64_insn code, const aarch64_inst *inst) |
| { |
| aarch64_insn imm; |
| info->qualifier = get_expected_qualifier (inst, info->idx); |
| |
| /* Rn */ |
| info->addr.base_regno = extract_field (FLD_Rn, code, 0); |
| /* simm (imm9 or imm7) */ |
| imm = extract_field (self->fields[0], code, 0); |
| info->addr.offset.imm = sign_extend (imm, fields[self->fields[0]].width - 1); |
| if (self->fields[0] == FLD_imm7) |
| /* scaled immediate in ld/st pair instructions. */ |
| info->addr.offset.imm *= aarch64_get_qualifier_esize (info->qualifier); |
| /* qualifier */ |
| if (inst->opcode->iclass == ldst_unscaled |
| || inst->opcode->iclass == ldstnapair_offs |
| || inst->opcode->iclass == ldstpair_off |
| || inst->opcode->iclass == ldst_unpriv) |
| info->addr.writeback = 0; |
| else |
| { |
| /* pre/post- index */ |
| info->addr.writeback = 1; |
| if (extract_field (self->fields[1], code, 0) == 1) |
| info->addr.preind = 1; |
| else |
| info->addr.postind = 1; |
| } |
| |
| return 1; |
| } |
| |
| /* Decode the address operand for e.g. LDRSW <Xt>, [<Xn|SP>{, #<simm>}]. */ |
| int |
| aarch64_ext_addr_uimm12 (const aarch64_operand *self, aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int shift; |
| info->qualifier = get_expected_qualifier (inst, info->idx); |
| shift = get_logsz (aarch64_get_qualifier_esize (info->qualifier)); |
| /* Rn */ |
| info->addr.base_regno = extract_field (self->fields[0], code, 0); |
| /* uimm12 */ |
| info->addr.offset.imm = extract_field (self->fields[1], code, 0) << shift; |
| return 1; |
| } |
| |
| /* Decode the address operand for e.g. LDRAA <Xt>, [<Xn|SP>{, #<simm>}]. */ |
| int |
| aarch64_ext_addr_simm10 (const aarch64_operand *self, aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| aarch64_insn imm; |
| |
| info->qualifier = get_expected_qualifier (inst, info->idx); |
| /* Rn */ |
| info->addr.base_regno = extract_field (self->fields[0], code, 0); |
| /* simm10 */ |
| imm = extract_fields (code, 0, 2, self->fields[1], self->fields[2]); |
| info->addr.offset.imm = sign_extend (imm, 9) << 3; |
| if (extract_field (self->fields[3], code, 0) == 1) { |
| info->addr.writeback = 1; |
| info->addr.preind = 1; |
| } |
| return 1; |
| } |
| |
| /* Decode the address operand for e.g. |
| LD1 {<Vt>.<T>, <Vt2>.<T>, <Vt3>.<T>}, [<Xn|SP>], <Xm|#<amount>>. */ |
| int |
| aarch64_ext_simd_addr_post (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, const aarch64_inst *inst) |
| { |
| /* 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; |
| |
| /* Rn */ |
| info->addr.base_regno = extract_field (FLD_Rn, code, 0); |
| /* Rm | #<amount> */ |
| info->addr.offset.regno = extract_field (FLD_Rm, code, 0); |
| if (info->addr.offset.regno == 31) |
| { |
| if (inst->opcode->operands[0] == AARCH64_OPND_LVt_AL) |
| /* Special handling of loading single structure to all lane. */ |
| info->addr.offset.imm = (is_ld1r ? 1 |
| : inst->operands[0].reglist.num_regs) |
| * aarch64_get_qualifier_esize (inst->operands[0].qualifier); |
| else |
| info->addr.offset.imm = inst->operands[0].reglist.num_regs |
| * aarch64_get_qualifier_esize (inst->operands[0].qualifier) |
| * aarch64_get_qualifier_nelem (inst->operands[0].qualifier); |
| } |
| else |
| info->addr.offset.is_reg = 1; |
| info->addr.writeback = 1; |
| |
| return 1; |
| } |
| |
| /* Decode the condition operand for e.g. CSEL <Xd>, <Xn>, <Xm>, <cond>. */ |
| int |
| aarch64_ext_cond (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| aarch64_insn value; |
| /* cond */ |
| value = extract_field (FLD_cond, code, 0); |
| info->cond = get_cond_from_value (value); |
| return 1; |
| } |
| |
| /* Decode the system register operand for e.g. MRS <Xt>, <systemreg>. */ |
| int |
| aarch64_ext_sysreg (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| /* op0:op1:CRn:CRm:op2 */ |
| info->sysreg = extract_fields (code, 0, 5, FLD_op0, FLD_op1, FLD_CRn, |
| FLD_CRm, FLD_op2); |
| return 1; |
| } |
| |
| /* Decode the PSTATE field operand for e.g. MSR <pstatefield>, #<imm>. */ |
| int |
| aarch64_ext_pstatefield (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int i; |
| /* op1:op2 */ |
| info->pstatefield = extract_fields (code, 0, 2, FLD_op1, FLD_op2); |
| for (i = 0; aarch64_pstatefields[i].name != NULL; ++i) |
| if (aarch64_pstatefields[i].value == (aarch64_insn)info->pstatefield) |
| return 1; |
| /* Reserved value in <pstatefield>. */ |
| return 0; |
| } |
| |
| /* Decode the system instruction op operand for e.g. AT <at_op>, <Xt>. */ |
| int |
| aarch64_ext_sysins_op (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int i; |
| aarch64_insn value; |
| const aarch64_sys_ins_reg *sysins_ops; |
| /* op0:op1:CRn:CRm:op2 */ |
| value = extract_fields (code, 0, 5, |
| FLD_op0, FLD_op1, FLD_CRn, |
| FLD_CRm, FLD_op2); |
| |
| switch (info->type) |
| { |
| case AARCH64_OPND_SYSREG_AT: sysins_ops = aarch64_sys_regs_at; break; |
| case AARCH64_OPND_SYSREG_DC: sysins_ops = aarch64_sys_regs_dc; break; |
| case AARCH64_OPND_SYSREG_IC: sysins_ops = aarch64_sys_regs_ic; break; |
| case AARCH64_OPND_SYSREG_TLBI: sysins_ops = aarch64_sys_regs_tlbi; break; |
| default: assert (0); return 0; |
| } |
| |
| for (i = 0; sysins_ops[i].name != NULL; ++i) |
| if (sysins_ops[i].value == value) |
| { |
| info->sysins_op = sysins_ops + i; |
| DEBUG_TRACE ("%s found value: %x, has_xt: %d, i: %d.", |
| info->sysins_op->name, |
| (unsigned)info->sysins_op->value, |
| aarch64_sys_ins_reg_has_xt (info->sysins_op), i); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Decode the memory barrier option operand for e.g. DMB <option>|#<imm>. */ |
| |
| int |
| aarch64_ext_barrier (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| /* CRm */ |
| info->barrier = aarch64_barrier_options + extract_field (FLD_CRm, code, 0); |
| return 1; |
| } |
| |
| /* Decode the prefetch operation option operand for e.g. |
| PRFM <prfop>, [<Xn|SP>{, #<pimm>}]. */ |
| |
| int |
| aarch64_ext_prfop (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| /* prfop in Rt */ |
| info->prfop = aarch64_prfops + extract_field (FLD_Rt, code, 0); |
| return 1; |
| } |
| |
| /* Decode the hint number for an alias taking an operand. Set info->hint_option |
| to the matching name/value pair in aarch64_hint_options. */ |
| |
| int |
| aarch64_ext_hint (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| /* CRm:op2. */ |
| unsigned hint_number; |
| int i; |
| |
| hint_number = extract_fields (code, 0, 2, FLD_CRm, FLD_op2); |
| |
| for (i = 0; aarch64_hint_options[i].name != NULL; i++) |
| { |
| if (hint_number == aarch64_hint_options[i].value) |
| { |
| info->hint_option = &(aarch64_hint_options[i]); |
| return 1; |
| } |
| } |
| |
| return 0; |
| } |
| |
| /* Decode the extended register operand for e.g. |
| STR <Qt>, [<Xn|SP>, <R><m>{, <extend> {<amount>}}]. */ |
| int |
| aarch64_ext_reg_extended (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| aarch64_insn value; |
| |
| /* Rm */ |
| info->reg.regno = extract_field (FLD_Rm, code, 0); |
| /* option */ |
| value = extract_field (FLD_option, code, 0); |
| info->shifter.kind = |
| aarch64_get_operand_modifier_from_value (value, TRUE /* extend_p */); |
| /* imm3 */ |
| info->shifter.amount = extract_field (FLD_imm3, code, 0); |
| |
| /* This makes the constraint checking happy. */ |
| info->shifter.operator_present = 1; |
| |
| /* Assume inst->operands[0].qualifier has been resolved. */ |
| assert (inst->operands[0].qualifier != AARCH64_OPND_QLF_NIL); |
| info->qualifier = AARCH64_OPND_QLF_W; |
| if (inst->operands[0].qualifier == AARCH64_OPND_QLF_X |
| && (info->shifter.kind == AARCH64_MOD_UXTX |
| || info->shifter.kind == AARCH64_MOD_SXTX)) |
| info->qualifier = AARCH64_OPND_QLF_X; |
| |
| return 1; |
| } |
| |
| /* Decode the shifted register operand for e.g. |
| SUBS <Xd>, <Xn>, <Xm> {, <shift> #<amount>}. */ |
| int |
| aarch64_ext_reg_shifted (const aarch64_operand *self ATTRIBUTE_UNUSED, |
| aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| aarch64_insn value; |
| |
| /* Rm */ |
| info->reg.regno = extract_field (FLD_Rm, code, 0); |
| /* shift */ |
| value = extract_field (FLD_shift, code, 0); |
| info->shifter.kind = |
| aarch64_get_operand_modifier_from_value (value, FALSE /* extend_p */); |
| if (info->shifter.kind == AARCH64_MOD_ROR |
| && inst->opcode->iclass != log_shift) |
| /* ROR is not available for the shifted register operand in arithmetic |
| instructions. */ |
| return 0; |
| /* imm6 */ |
| info->shifter.amount = extract_field (FLD_imm6, code, 0); |
| |
| /* This makes the constraint checking happy. */ |
| info->shifter.operator_present = 1; |
| |
| return 1; |
| } |
| |
| /* Decode an SVE address [<base>, #<offset>*<factor>, MUL VL], |
| where <offset> is given by the OFFSET parameter and where <factor> is |
| 1 plus SELF's operand-dependent value. fields[0] specifies the field |
| that holds <base>. */ |
| static int |
| aarch64_ext_sve_addr_reg_mul_vl (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| int64_t offset) |
| { |
| info->addr.base_regno = extract_field (self->fields[0], code, 0); |
| info->addr.offset.imm = offset * (1 + get_operand_specific_data (self)); |
| info->addr.offset.is_reg = FALSE; |
| info->addr.writeback = FALSE; |
| info->addr.preind = TRUE; |
| if (offset != 0) |
| info->shifter.kind = AARCH64_MOD_MUL_VL; |
| info->shifter.amount = 1; |
| info->shifter.operator_present = (info->addr.offset.imm != 0); |
| info->shifter.amount_present = FALSE; |
| return 1; |
| } |
| |
| /* Decode an SVE address [<base>, #<simm4>*<factor>, MUL VL], |
| where <simm4> is a 4-bit signed value and where <factor> is 1 plus |
| SELF's operand-dependent value. fields[0] specifies the field that |
| holds <base>. <simm4> is encoded in the SVE_imm4 field. */ |
| int |
| aarch64_ext_sve_addr_ri_s4xvl (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int offset; |
| |
| offset = extract_field (FLD_SVE_imm4, code, 0); |
| offset = ((offset + 8) & 15) - 8; |
| return aarch64_ext_sve_addr_reg_mul_vl (self, info, code, offset); |
| } |
| |
| /* Decode an SVE address [<base>, #<simm6>*<factor>, MUL VL], |
| where <simm6> is a 6-bit signed value and where <factor> is 1 plus |
| SELF's operand-dependent value. fields[0] specifies the field that |
| holds <base>. <simm6> is encoded in the SVE_imm6 field. */ |
| int |
| aarch64_ext_sve_addr_ri_s6xvl (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int offset; |
| |
| offset = extract_field (FLD_SVE_imm6, code, 0); |
| offset = (((offset + 32) & 63) - 32); |
| return aarch64_ext_sve_addr_reg_mul_vl (self, info, code, offset); |
| } |
| |
| /* Decode an SVE address [<base>, #<simm9>*<factor>, MUL VL], |
| where <simm9> is a 9-bit signed value and where <factor> is 1 plus |
| SELF's operand-dependent value. fields[0] specifies the field that |
| holds <base>. <simm9> is encoded in the concatenation of the SVE_imm6 |
| and imm3 fields, with imm3 being the less-significant part. */ |
| int |
| aarch64_ext_sve_addr_ri_s9xvl (const aarch64_operand *self, |
| aarch64_opnd_info *info, |
| aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int offset; |
| |
| offset = extract_fields (code, 0, 2, FLD_SVE_imm6, FLD_imm3); |
| offset = (((offset + 256) & 511) - 256); |
| return aarch64_ext_sve_addr_reg_mul_vl (self, info, code, offset); |
| } |
| |
| /* Decode an SVE address [<base>, #<offset> << <shift>], where <offset> |
| is given by the OFFSET parameter and where <shift> is SELF's operand- |
| dependent value. fields[0] specifies the base register field <base>. */ |
| static int |
| aarch64_ext_sve_addr_reg_imm (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| int64_t offset) |
| { |
| info->addr.base_regno = extract_field (self->fields[0], code, 0); |
| info->addr.offset.imm = offset * (1 << get_operand_specific_data (self)); |
| info->addr.offset.is_reg = FALSE; |
| info->addr.writeback = FALSE; |
| info->addr.preind = TRUE; |
| info->shifter.operator_present = FALSE; |
| info->shifter.amount_present = FALSE; |
| return 1; |
| } |
| |
| /* Decode an SVE address [X<n>, #<SVE_imm6> << <shift>], where <SVE_imm6> |
| is a 6-bit unsigned number and where <shift> is SELF's operand-dependent |
| value. fields[0] specifies the base register field. */ |
| int |
| aarch64_ext_sve_addr_ri_u6 (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int offset = extract_field (FLD_SVE_imm6, code, 0); |
| return aarch64_ext_sve_addr_reg_imm (self, info, code, offset); |
| } |
| |
| /* Decode an SVE address [X<n>, X<m>{, LSL #<shift>}], where <shift> |
| is SELF's operand-dependent value. fields[0] specifies the base |
| register field and fields[1] specifies the offset register field. */ |
| int |
| aarch64_ext_sve_addr_rr_lsl (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int index_regno; |
| |
| index_regno = extract_field (self->fields[1], code, 0); |
| if (index_regno == 31 && (self->flags & OPD_F_NO_ZR) != 0) |
| return 0; |
| |
| info->addr.base_regno = extract_field (self->fields[0], code, 0); |
| info->addr.offset.regno = index_regno; |
| info->addr.offset.is_reg = TRUE; |
| info->addr.writeback = FALSE; |
| info->addr.preind = TRUE; |
| info->shifter.kind = AARCH64_MOD_LSL; |
| info->shifter.amount = get_operand_specific_data (self); |
| info->shifter.operator_present = (info->shifter.amount != 0); |
| info->shifter.amount_present = (info->shifter.amount != 0); |
| return 1; |
| } |
| |
| /* Decode an SVE address [X<n>, Z<m>.<T>, (S|U)XTW {#<shift>}], where |
| <shift> 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. */ |
| int |
| aarch64_ext_sve_addr_rz_xtw (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| info->addr.base_regno = extract_field (self->fields[0], code, 0); |
| info->addr.offset.regno = extract_field (self->fields[1], code, 0); |
| info->addr.offset.is_reg = TRUE; |
| info->addr.writeback = FALSE; |
| info->addr.preind = TRUE; |
| if (extract_field (self->fields[2], code, 0)) |
| info->shifter.kind = AARCH64_MOD_SXTW; |
| else |
| info->shifter.kind = AARCH64_MOD_UXTW; |
| info->shifter.amount = get_operand_specific_data (self); |
| info->shifter.operator_present = TRUE; |
| info->shifter.amount_present = (info->shifter.amount != 0); |
| return 1; |
| } |
| |
| /* Decode an SVE address [Z<n>.<T>, #<imm5> << <shift>], where <imm5> is a |
| 5-bit unsigned number and where <shift> is SELF's operand-dependent value. |
| fields[0] specifies the base register field. */ |
| int |
| aarch64_ext_sve_addr_zi_u5 (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int offset = extract_field (FLD_imm5, code, 0); |
| return aarch64_ext_sve_addr_reg_imm (self, info, code, offset); |
| } |
| |
| /* Decode an SVE address [Z<n>.<T>, Z<m>.<T>{, <modifier> {#<msz>}}], |
| where <modifier> is given by KIND and where <msz> is a 2-bit unsigned |
| number. fields[0] specifies the base register field and fields[1] |
| specifies the offset register field. */ |
| static int |
| aarch64_ext_sve_addr_zz (const aarch64_operand *self, aarch64_opnd_info *info, |
| aarch64_insn code, enum aarch64_modifier_kind kind) |
| { |
| info->addr.base_regno = extract_field (self->fields[0], code, 0); |
| info->addr.offset.regno = extract_field (self->fields[1], code, 0); |
| info->addr.offset.is_reg = TRUE; |
| info->addr.writeback = FALSE; |
| info->addr.preind = TRUE; |
| info->shifter.kind = kind; |
| info->shifter.amount = extract_field (FLD_SVE_msz, code, 0); |
| info->shifter.operator_present = (kind != AARCH64_MOD_LSL |
| || info->shifter.amount != 0); |
| info->shifter.amount_present = (info->shifter.amount != 0); |
| return 1; |
| } |
| |
| /* Decode an SVE address [Z<n>.<T>, Z<m>.<T>{, LSL #<msz>}], where |
| <msz> is a 2-bit unsigned number. fields[0] specifies the base register |
| field and fields[1] specifies the offset register field. */ |
| int |
| aarch64_ext_sve_addr_zz_lsl (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| return aarch64_ext_sve_addr_zz (self, info, code, AARCH64_MOD_LSL); |
| } |
| |
| /* Decode an SVE address [Z<n>.<T>, Z<m>.<T>, SXTW {#<msz>}], where |
| <msz> is a 2-bit unsigned number. fields[0] specifies the base register |
| field and fields[1] specifies the offset register field. */ |
| int |
| aarch64_ext_sve_addr_zz_sxtw (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| return aarch64_ext_sve_addr_zz (self, info, code, AARCH64_MOD_SXTW); |
| } |
| |
| /* Decode an SVE address [Z<n>.<T>, Z<m>.<T>, UXTW {#<msz>}], where |
| <msz> is a 2-bit unsigned number. fields[0] specifies the base register |
| field and fields[1] specifies the offset register field. */ |
| int |
| aarch64_ext_sve_addr_zz_uxtw (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| return aarch64_ext_sve_addr_zz (self, info, code, AARCH64_MOD_UXTW); |
| } |
| |
| /* Finish decoding an SVE arithmetic immediate, given that INFO already |
| has the raw field value and that the low 8 bits decode to VALUE. */ |
| static int |
| decode_sve_aimm (aarch64_opnd_info *info, int64_t value) |
| { |
| info->shifter.kind = AARCH64_MOD_LSL; |
| info->shifter.amount = 0; |
| if (info->imm.value & 0x100) |
| { |
| if (value == 0) |
| /* Decode 0x100 as #0, LSL #8. */ |
| info->shifter.amount = 8; |
| else |
| value *= 256; |
| } |
| info->shifter.operator_present = (info->shifter.amount != 0); |
| info->shifter.amount_present = (info->shifter.amount != 0); |
| info->imm.value = value; |
| return 1; |
| } |
| |
| /* Decode an SVE ADD/SUB immediate. */ |
| int |
| aarch64_ext_sve_aimm (const aarch64_operand *self, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| return (aarch64_ext_imm (self, info, code, inst) |
| && decode_sve_aimm (info, (uint8_t) info->imm.value)); |
| } |
| |
| /* Decode an SVE CPY/DUP immediate. */ |
| int |
| aarch64_ext_sve_asimm (const aarch64_operand *self, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| return (aarch64_ext_imm (self, info, code, inst) |
| && decode_sve_aimm (info, (int8_t) info->imm.value)); |
| } |
| |
| /* Decode a single-bit immediate that selects between #0.5 and #1.0. |
| The fields array specifies which field to use. */ |
| int |
| aarch64_ext_sve_float_half_one (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| if (extract_field (self->fields[0], code, 0)) |
| info->imm.value = 0x3f800000; |
| else |
| info->imm.value = 0x3f000000; |
| info->imm.is_fp = TRUE; |
| return 1; |
| } |
| |
| /* Decode a single-bit immediate that selects between #0.5 and #2.0. |
| The fields array specifies which field to use. */ |
| int |
| aarch64_ext_sve_float_half_two (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| if (extract_field (self->fields[0], code, 0)) |
| info->imm.value = 0x40000000; |
| else |
| info->imm.value = 0x3f000000; |
| info->imm.is_fp = TRUE; |
| return 1; |
| } |
| |
| /* Decode a single-bit immediate that selects between #0.0 and #1.0. |
| The fields array specifies which field to use. */ |
| int |
| aarch64_ext_sve_float_zero_one (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| if (extract_field (self->fields[0], code, 0)) |
| info->imm.value = 0x3f800000; |
| else |
| info->imm.value = 0x0; |
| info->imm.is_fp = TRUE; |
| return 1; |
| } |
| |
| /* Decode 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. */ |
| int |
| aarch64_ext_sve_index (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| int val; |
| |
| info->reglane.regno = extract_field (self->fields[0], code, 0); |
| val = extract_fields (code, 0, 2, FLD_SVE_tszh, FLD_imm5); |
| if ((val & 15) == 0) |
| return 0; |
| while ((val & 1) == 0) |
| val /= 2; |
| info->reglane.index = val / 2; |
| return 1; |
| } |
| |
| /* Decode a logical immediate for the MOV alias of SVE DUPM. */ |
| int |
| aarch64_ext_sve_limm_mov (const aarch64_operand *self, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| int esize = aarch64_get_qualifier_esize (inst->operands[0].qualifier); |
| return (aarch64_ext_limm (self, info, code, inst) |
| && aarch64_sve_dupm_mov_immediate_p (info->imm.value, esize)); |
| } |
| |
| /* Decode {Zn.<T> - Zm.<T>}. The fields array specifies which field |
| to use for Zn. The opcode-dependent value specifies the number |
| of registers in the list. */ |
| int |
| aarch64_ext_sve_reglist (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst ATTRIBUTE_UNUSED) |
| { |
| info->reglist.first_regno = extract_field (self->fields[0], code, 0); |
| info->reglist.num_regs = get_opcode_dependent_value (inst->opcode); |
| return 1; |
| } |
| |
| /* Decode <pattern>{, MUL #<amount>}. The fields array specifies which |
| fields to use for <pattern>. <amount> - 1 is encoded in the SVE_imm4 |
| field. */ |
| int |
| aarch64_ext_sve_scale (const aarch64_operand *self, |
| aarch64_opnd_info *info, aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| int val; |
| |
| if (!aarch64_ext_imm (self, info, code, inst)) |
| return 0; |
| val = extract_field (FLD_SVE_imm4, code, 0); |
| info->shifter.kind = AARCH64_MOD_MUL; |
| info->shifter.amount = val + 1; |
| info->shifter.operator_present = (val != 0); |
| info->shifter.amount_present = (val != 0); |
| return 1; |
| } |
| |
| /* Return the top set bit in VALUE, which is expected to be relatively |
| small. */ |
| static uint64_t |
| get_top_bit (uint64_t value) |
| { |
| while ((value & -value) != value) |
| value -= value & -value; |
| return value; |
| } |
| |
| /* Decode an SVE shift-left immediate. */ |
| int |
| aarch64_ext_sve_shlimm (const aarch64_operand *self, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| if (!aarch64_ext_imm (self, info, code, inst) |
| || info->imm.value == 0) |
| return 0; |
| |
| info->imm.value -= get_top_bit (info->imm.value); |
| return 1; |
| } |
| |
| /* Decode an SVE shift-right immediate. */ |
| int |
| aarch64_ext_sve_shrimm (const aarch64_operand *self, |
| aarch64_opnd_info *info, const aarch64_insn code, |
| const aarch64_inst *inst) |
| { |
| if (!aarch64_ext_imm (self, info, code, inst) |
| || info->imm.value == 0) |
| return 0; |
| |
| info->imm.value = get_top_bit (info->imm.value) * 2 - info->imm.value; |
| return 1; |
| } |
| |
| /* Bitfields that are commonly used to encode certain operands' information |
| may be partially used as part of the base opcode in some instructions. |
| For example, the bit 1 of the field 'size' in |
| FCVTXN <Vb><d>, <Va><n> |
| is actually part of the base opcode, while only size<0> is available |
| for encoding the register type. Another example is the AdvSIMD |
| instruction ORR (register), in which the field 'size' is also used for |
| the base opcode, leaving only the field 'Q' available to encode the |
| vector register arrangement specifier '8B' or '16B'. |
| |
| This function tries to deduce the qualifier from the value of partially |
| constrained field(s). Given the VALUE of such a field or fields, the |
| qualifiers CANDIDATES and the MASK (indicating which bits are valid for |
| operand encoding), the function returns the matching qualifier or |
| AARCH64_OPND_QLF_NIL if nothing matches. |
| |
| N.B. CANDIDATES is a group of possible qualifiers that are valid for |
| one operand; it has a maximum of AARCH64_MAX_QLF_SEQ_NUM qualifiers and |
| may end with AARCH64_OPND_QLF_NIL. */ |
| |
| static enum aarch64_opnd_qualifier |
| get_qualifier_from_partial_encoding (aarch64_insn value, |
| const enum aarch64_opnd_qualifier* \ |
| candidates, |
| aarch64_insn mask) |
| { |
| int i; |
| DEBUG_TRACE ("enter with value: %d, mask: %d", (int)value, (int)mask); |
| for (i = 0; i < AARCH64_MAX_QLF_SEQ_NUM; ++i) |
| { |
| aarch64_insn standard_value; |
| if (candidates[i] == AARCH64_OPND_QLF_NIL) |
| break; |
| standard_value = aarch64_get_qualifier_standard_value (candidates[i]); |
| if ((standard_value & mask) == (value & mask)) |
| return candidates[i]; |
| } |
| return AARCH64_OPND_QLF_NIL; |
| } |
| |
| /* Given a list of qualifier sequences, return all possible valid qualifiers |
| for operand IDX in QUALIFIERS. |
| Assume QUALIFIERS is an array whose length is large enough. */ |
| |
| static void |
| get_operand_possible_qualifiers (int idx, |
| const aarch64_opnd_qualifier_seq_t *list, |
| enum aarch64_opnd_qualifier *qualifiers) |
| { |
| int i; |
| for (i = 0; i < AARCH64_MAX_QLF_SEQ_NUM; ++i) |
| if ((qualifiers[i] = list[i][idx]) == AARCH64_OPND_QLF_NIL) |
| break; |
| } |
| |
| /* Decode the size Q field for e.g. SHADD. |
| We tag one operand with the qualifer according to the code; |
| whether the qualifier is valid for this opcode or not, it is the |
| duty of the semantic checking. */ |
| |
| static int |
| decode_sizeq (aarch64_inst *inst) |
| { |
| int idx; |
| enum aarch64_opnd_qualifier qualifier; |
| aarch64_insn code; |
| aarch64_insn value, mask; |
| enum aarch64_field_kind fld_sz; |
| enum aarch64_opnd_qualifier candidates[AARCH64_MAX_QLF_SEQ_NUM]; |
| |
| if (inst->opcode->iclass == asisdlse |
| || inst->opcode->iclass == asisdlsep |
| || inst->opcode->iclass == asisdlso |
| || inst->opcode->iclass == asisdlsop) |
| fld_sz = FLD_vldst_size; |
| else |
| fld_sz = FLD_size; |
| |
| code = inst->value; |
| value = extract_fields (code, inst->opcode->mask, 2, fld_sz, FLD_Q); |
| /* Obtain the info that which bits of fields Q and size are actually |
| available for operand encoding. Opcodes like FMAXNM and FMLA have |
| size[1] unavailable. */ |
| mask = extract_fields (~inst->opcode->mask, 0, 2, fld_sz, FLD_Q); |
| |
| /* The index of the operand we are going to tag a qualifier and the qualifer |
| itself are reasoned from the value of the size and Q fields and the |
| possible valid qualifier lists. */ |
| idx = aarch64_select_operand_for_sizeq_field_coding (inst->opcode); |
| DEBUG_TRACE ("key idx: %d", idx); |
| |
| /* For most related instruciton, size:Q are fully available for operand |
| encoding. */ |
| if (mask == 0x7) |
| { |
| inst->operands[idx].qualifier = get_vreg_qualifier_from_value (value); |
| return 1; |
| } |
| |
| get_operand_possible_qualifiers (idx, inst->opcode->qualifiers_list, |
| candidates); |
| #ifdef DEBUG_AARCH64 |
| if (debug_dump) |
| { |
| int i; |
| for (i = 0; candidates[i] != AARCH64_OPND_QLF_NIL |
| && i < AARCH64_MAX_QLF_SEQ_NUM; ++i) |
| DEBUG_TRACE ("qualifier %d: %s", i, |
| aarch64_get_qualifier_name(candidates[i])); |
| DEBUG_TRACE ("%d, %d", (int)value, (int)mask); |
| } |
| #endif /* DEBUG_AARCH64 */ |
| |
| qualifier = get_qualifier_from_partial_encoding (value, candidates, mask); |
| |
| if (qualifier == AARCH64_OPND_QLF_NIL) |
| return 0; |
| |
| inst->operands[idx].qualifier = qualifier; |
| return 1; |
| } |
| |
| /* Decode size[0]:Q, i.e. bit 22 and bit 30, for |
| e.g. FCVTN<Q> <Vd>.<Tb>, <Vn>.<Ta>. */ |
| |
| static int |
| decode_asimd_fcvt (aarch64_inst *inst) |
| { |
| aarch64_field field = {0, 0}; |
| aarch64_insn value; |
| enum aarch64_opnd_qualifier qualifier; |
| |
| gen_sub_field (FLD_size, 0, 1, &field); |
| value = extract_field_2 (&field, inst->value, 0); |
| qualifier = value == 0 ? AARCH64_OPND_QLF_V_4S |
| : AARCH64_OPND_QLF_V_2D; |
| switch (inst->opcode->op) |
| { |
| case OP_FCVTN: |
| case OP_FCVTN2: |
| /* FCVTN<Q> <Vd>.<Tb>, <Vn>.<Ta>. */ |
| inst->operands[1].qualifier = qualifier; |
| break; |
| case OP_FCVTL: |
| case OP_FCVTL2: |
| /* FCVTL<Q> <Vd>.<Ta>, <Vn>.<Tb>. */ |
| inst->operands[0].qualifier = qualifier; |
| break; |
| default: |
| assert (0); |
| return 0; |
| } |
| |
| return 1; |
| } |
| |
| /* Decode size[0], i.e. bit 22, for |
| e.g. FCVTXN <Vb><d>, <Va><n>. */ |
| |
| static int |
| decode_asisd_fcvtxn (aarch64_inst *inst) |
| { |
| aarch64_field field = {0, 0}; |
| gen_sub_field (FLD_size, 0, 1, &field); |
| if (!extract_field_2 (&field, inst->value, 0)) |
| return 0; |
| inst->operands[0].qualifier = AARCH64_OPND_QLF_S_S; |
| return 1; |
| } |
| |
| /* Decode the 'opc' field for e.g. FCVT <Dd>, <Sn>. */ |
| static int |
| decode_fcvt (aarch64_inst *inst) |
| { |
| enum aarch64_opnd_qualifier qualifier; |
| aarch64_insn value; |
| const aarch64_field field = {15, 2}; |
| |
| /* opc dstsize */ |
| value = extract_field_2 (&field, inst->value, 0); |
| switch (value) |
| { |
| case 0: qualifier = AARCH64_OPND_QLF_S_S; break; |
| case 1: qualifier = AARCH64_OPND_QLF_S_D; break; |
| case 3: qualifier = AARCH64_OPND_QLF_S_H; break; |
| default: return 0; |
| } |
| inst->operands[0].qualifier = qualifier; |
| |
| return 1; |
| } |
| |
| /* Do miscellaneous decodings that are not common enough to be driven by |
| flags. */ |
| |
| static int |
| do_misc_decoding (aarch64_inst *inst) |
| { |
| unsigned int value; |
| switch (inst->opcode->op) |
| { |
| case OP_FCVT: |
| return decode_fcvt (inst); |
| |
| case OP_FCVTN: |
| case OP_FCVTN2: |
| case OP_FCVTL: |
| case OP_FCVTL2: |
| return decode_asimd_fcvt (inst); |
| |
| case OP_FCVTXN_S: |
| return decode_asisd_fcvtxn (inst); |
| |
| case OP_MOV_P_P: |
| case OP_MOVS_P_P: |
| value = extract_field (FLD_SVE_Pn, inst->value, 0); |
| return (value == extract_field (FLD_SVE_Pm, inst->value, 0) |
| && value == extract_field (FLD_SVE_Pg4_10, inst->value, 0)); |
| |
| case OP_MOV_Z_P_Z: |
| return (extract_field (FLD_SVE_Zd, inst->value, 0) |
| == extract_field (FLD_SVE_Zm_16, inst->value, 0)); |
| |
| case OP_MOV_Z_V: |
| /* Index must be zero. */ |
| value = extract_fields (inst->value, 0, 2, FLD_SVE_tszh, FLD_imm5); |
| return value == 1 || value == 2 || value == 4 || value == 8; |
| |
| case OP_MOV_Z_Z: |
| return (extract_field (FLD_SVE_Zn, inst->value, 0) |
| == extract_field (FLD_SVE_Zm_16, inst->value, 0)); |
| |
| case OP_MOV_Z_Zi: |
| /* Index must be nonzero. */ |
| value = extract_fields (inst->value, 0, 2, FLD_SVE_tszh, FLD_imm5); |
| return value != 1 && value != 2 && value != 4 && value != 8; |
| |
| case OP_MOVM_P_P_P: |
| return (extract_field (FLD_SVE_Pd, inst->value, 0) |
| == extract_field (FLD_SVE_Pm, inst->value, 0)); |
| |
| case OP_MOVZS_P_P_P: |
| case OP_MOVZ_P_P_P: |
| return (extract_field (FLD_SVE_Pn, inst->value, 0) |
| == extract_field (FLD_SVE_Pm, inst->value, 0)); |
| |
| case OP_NOTS_P_P_P_Z: |
| case OP_NOT_P_P_P_Z: |
| return (extract_field (FLD_SVE_Pm, inst->value, 0) |
| == extract_field (FLD_SVE_Pg4_10, inst->value, 0)); |
| |
| default: |
| return 0; |
| } |
| } |
| |
| /* Opcodes that have fields shared by multiple operands are usually flagged |
| with flags. In this function, we detect such flags, decode the related |
| field(s) and store the information in one of the related operands. The |
| 'one' operand is not any operand but one of the operands that can |
| accommadate all the information that has been decoded. */ |
| |
| static int |
| do_special_decoding (aarch64_inst *inst) |
| { |
| int idx; |
| aarch64_insn value; |
| /* Condition for truly conditional executed instructions, e.g. b.cond. */ |
| if (inst->opcode->flags & F_COND) |
| { |
| value = extract_field (FLD_cond2, inst->value, 0); |
| inst->cond = get_cond_from_value (value); |
| } |
| /* 'sf' field. */ |
| if (inst->opcode->flags & F_SF) |
| { |
| idx = select_operand_for_sf_field_coding (inst->opcode); |
| value = extract_field (FLD_sf, inst->value, 0); |
| inst->operands[idx].qualifier = get_greg_qualifier_from_value (value); |
| if ((inst->opcode->flags & F_N) |
| && extract_field (FLD_N, inst->value, 0) != value) |
| return 0; |
| } |
| /* 'sf' field. */ |
| if (inst->opcode->flags & F_LSE_SZ) |
| { |
| idx = select_operand_for_sf_field_coding (inst->opcode); |
| value = extract_field (FLD_lse_sz, inst->value, 0); |
| inst->operands[idx].qualifier = get_greg_qualifier_from_value (value); |
| } |
| /* size:Q fields. */ |
| if (inst->opcode->flags & F_SIZEQ) |
| return decode_sizeq (inst); |
| |
| if (inst->opcode->flags & F_FPTYPE) |
| { |
| idx = select_operand_for_fptype_field_coding (inst->opcode); |
| value = extract_field (FLD_type, inst->value, 0); |
| switch (value) |
| { |
| case 0: inst->operands[idx].qualifier = AARCH64_OPND_QLF_S_S; break; |
| case 1: inst->operands[idx].qualifier = AARCH64_OPND_QLF_S_D; break; |
| case 3: inst->operands[idx].qualifier = AARCH64_OPND_QLF_S_H; break; |
| default: return 0; |
| } |
| } |
| |
| if (inst->opcode->flags & F_SSIZE) |
| { |
| /* N.B. some opcodes like FCMGT <V><d>, <V><n>, #0 have the size[1] as part |
| of the base opcode. */ |
| aarch64_insn mask; |
| enum aarch64_opnd_qualifier candidates[AARCH64_MAX_QLF_SEQ_NUM]; |
| idx = select_operand_for_scalar_size_field_coding (inst->opcode); |
| value = extract_field (FLD_size, inst->value, inst->opcode->mask); |
| mask = extract_field (FLD_size, ~inst->opcode->mask, 0); |
| /* For most related instruciton, the 'size' field is fully available for |
| operand encoding. */ |
| if (mask == 0x3) |
| inst->operands[idx].qualifier = get_sreg_qualifier_from_value (value); |
| else |
| { |
| get_operand_possible_qualifiers (idx, inst->opcode->qualifiers_list, |
| candidates); |
| inst->operands[idx].qualifier |
| = get_qualifier_from_partial_encoding (value, candidates, mask); |
| } |
| } |
| |
| if (inst->opcode->flags & F_T) |
| { |
| /* Num of consecutive '0's on the right side of imm5<3:0>. */ |
| int num = 0; |
| unsigned val, Q; |
| assert (aarch64_get_operand_class (inst->opcode->operands[0]) |
| == AARCH64_OPND_CLASS_SIMD_REG); |
| /* imm5<3:0> q <t> |
| 0000 x reserved |
| xxx1 0 8b |
| xxx1 1 16b |
| xx10 0 4h |
| xx10 1 8h |
| x100 0 2s |
| x100 1 4s |
| 1000 0 reserved |
| 1000 1 2d */ |
| val = extract_field (FLD_imm5, inst->value, 0); |
| while ((val & 0x1) == 0 && ++num <= 3) |
| val >>= 1; |
| if (num > 3) |
| return 0; |
| Q = (unsigned) extract_field (FLD_Q, inst->value, inst->opcode->mask); |
| inst->operands[0].qualifier = |
| get_vreg_qualifier_from_value ((num << 1) | Q); |
| } |
| |
| if (inst->opcode->flags & F_GPRSIZE_IN_Q) |
| { |
| /* Use Rt to encode in the case of e.g. |
| STXP <Ws>, <Xt1>, <Xt2>, [<Xn|SP>{,#0}]. */ |
| idx = aarch64_operand_index (inst->opcode->operands, AARCH64_OPND_Rt); |
| if (idx == -1) |
| { |
| /* Otherwise use the result operand, which has to be a integer |
| register. */ |
| assert (aarch64_get_operand_class (inst->opcode->operands[0]) |
| == AARCH64_OPND_CLASS_INT_REG); |
| idx = 0; |
| } |
| assert (idx == 0 || idx == 1); |
| value = extract_field (FLD_Q, inst->value, 0); |
| inst->operands[idx].qualifier = get_greg_qualifier_from_value (value); |
| } |
| |
| if (inst->opcode->flags & F_LDS_SIZE) |
| { |
| aarch64_field field = {0, 0}; |
| assert (aarch64_get_operand_class (inst->opcode->operands[0]) |
| == AARCH64_OPND_CLASS_INT_REG); |
| gen_sub_field (FLD_opc, 0, 1, &field); |
| value = extract_field_2 (&field, inst->value, 0); |
| inst->operands[0].qualifier |
| = value ? AARCH64_OPND_QLF_W : AARCH64_OPND_QLF_X; |
| } |
| |
| /* Miscellaneous decoding; done as the last step. */ |
| if (inst->opcode->flags & F_MISC) |
| return do_misc_decoding (inst); |
| |
| return 1; |
| } |
| |
| /* Converters converting a real opcode instruction to its alias form. */ |
| |
| /* ROR <Wd>, <Ws>, #<shift> |
| is equivalent to: |
| EXTR <Wd>, <Ws>, <Ws>, #<shift>. */ |
| static int |
| convert_extr_to_ror (aarch64_inst *inst) |
| { |
| if (inst->operands[1].reg.regno == inst->operands[2].reg.regno) |
| { |
| copy_operand_info (inst, 2, 3); |
| inst->operands[3].type = AARCH64_OPND_NIL; |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* UXTL<Q> <Vd>.<Ta>, <Vn>.<Tb> |
| is equivalent to: |
| USHLL<Q> <Vd>.<Ta>, <Vn>.<Tb>, #0. */ |
| static int |
| convert_shll_to_xtl (aarch64_inst *inst) |
| { |
| if (inst->operands[2].imm.value == 0) |
| { |
| inst->operands[2].type = AARCH64_OPND_NIL; |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* Convert |
| UBFM <Xd>, <Xn>, #<shift>, #63. |
| to |
| LSR <Xd>, <Xn>, #<shift>. */ |
| static int |
| convert_bfm_to_sr (aarch64_inst *inst) |
| { |
| int64_t imms, val; |
| |
| imms = inst->operands[3].imm.value; |
| val = inst->operands[2].qualifier == AARCH64_OPND_QLF_imm_0_31 ? 31 : 63; |
| if (imms == val) |
| { |
| inst->operands[3].type = AARCH64_OPND_NIL; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* Convert MOV to ORR. */ |
| static int |
| convert_orr_to_mov (aarch64_inst *inst) |
| { |
| /* MOV <Vd>.<T>, <Vn>.<T> |
| is equivalent to: |
| ORR <Vd>.<T>, <Vn>.<T>, <Vn>.<T>. */ |
| if (inst->operands[1].reg.regno == inst->operands[2].reg.regno) |
| { |
| inst->operands[2].type = AARCH64_OPND_NIL; |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* When <imms> >= <immr>, the instruction written: |
| SBFX <Xd>, <Xn>, #<lsb>, #<width> |
| is equivalent to: |
| SBFM <Xd>, <Xn>, #<lsb>, #(<lsb>+<width>-1). */ |
| |
| static int |
| convert_bfm_to_bfx (aarch64_inst *inst) |
| { |
| int64_t immr, imms; |
| |
| immr = inst->operands[2].imm.value; |
| imms = inst->operands[3].imm.value; |
| if (imms >= immr) |
| { |
| int64_t lsb = immr; |
| inst->operands[2].imm.value = lsb; |
| inst->operands[3].imm.value = imms + 1 - lsb; |
| /* The two opcodes have different qualifiers for |
| the immediate operands; reset to help the checking. */ |
| reset_operand_qualifier (inst, 2); |
| reset_operand_qualifier (inst, 3); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* When <imms> < <immr>, the instruction written: |
| SBFIZ <Xd>, <Xn>, #<lsb>, #<width> |
| is equivalent to: |
| SBFM <Xd>, <Xn>, #((64-<lsb>)&0x3f), #(<width>-1). */ |
| |
| static int |
| convert_bfm_to_bfi (aarch64_inst *inst) |
| { |
| int64_t immr, imms, val; |
| |
| immr = inst->operands[2].imm.value; |
| imms = inst->operands[3].imm.value; |
| val = inst->operands[2].qualifier == AARCH64_OPND_QLF_imm_0_31 ? 32 : 64; |
| if (imms < immr) |
| { |
| inst->operands[2].imm.value = (val - immr) & (val - 1); |
| inst->operands[3].imm.value = imms + 1; |
| /* The two opcodes have different qualifiers for |
| the immediate operands; reset to help the checking. */ |
| reset_operand_qualifier (inst, 2); |
| reset_operand_qualifier (inst, 3); |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* The instruction written: |
| BFC <Xd>, #<lsb>, #<width> |
| is equivalent to: |
| BFM <Xd>, XZR, #((64-<lsb>)&0x3f), #(<width>-1). */ |
| |
| static int |
| convert_bfm_to_bfc (aarch64_inst *inst) |
| { |
| int64_t immr, imms, val; |
| |
| /* Should have been assured by the base opcode value. */ |
| assert (inst->operands[1].reg.regno == 0x1f); |
| |
| immr = inst->operands[2].imm.value; |
| imms = inst->operands[3].imm.value; |
| val = inst->operands[2].qualifier == AARCH64_OPND_QLF_imm_0_31 ? 32 : 64; |
| if (imms < immr) |
| { |
| /* Drop XZR from the second operand. */ |
| copy_operand_info (inst, 1, 2); |
| copy_operand_info (inst, 2, 3); |
| inst->operands[3].type = AARCH64_OPND_NIL; |
| |
| /* Recalculate the immediates. */ |
| inst->operands[1].imm.value = (val - immr) & (val - 1); |
| inst->operands[2].imm.value = imms + 1; |
| |
| /* The two opcodes have different qualifiers for the operands; reset to |
| help the checking. */ |
| reset_operand_qualifier (inst, 1); |
| reset_operand_qualifier (inst, 2); |
| reset_operand_qualifier (inst, 3); |
| |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* The instruction written: |
| LSL <Xd>, <Xn>, #<shift> |
| is equivalent to: |
| UBFM <Xd>, <Xn>, #((64-<shift>)&0x3f), #(63-<shift>). */ |
| |
| static int |
| convert_ubfm_to_lsl (aarch64_inst *inst) |
| { |
| int64_t immr = inst->operands[2].imm.value; |
| int64_t imms = inst->operands[3].imm.value; |
| int64_t val |
| = inst->operands[2].qualifier == AARCH64_OPND_QLF_imm_0_31 ? 31 : 63; |
| |
| if ((immr == 0 && imms == val) || immr == imms + 1) |
| { |
| inst->operands[3].type = AARCH64_OPND_NIL; |
| inst->operands[2].imm.value = val - imms; |
| return 1; |
| } |
| |
| return 0; |
| } |
| |
| /* CINC <Wd>, <Wn>, <cond> |
| is equivalent to: |
| CSINC <Wd>, <Wn>, <Wn>, invert(<cond>) |
| where <cond> is not AL or NV. */ |
| |
| static int |
| convert_from_csel (aarch64_inst *inst) |
| { |
| if (inst->operands[1].reg.regno == inst->operands[2].reg.regno |
| && (inst->operands[3].cond->value & 0xe) != 0xe) |
| { |
| copy_operand_info (inst, 2, 3); |
| inst->operands[2].cond = get_inverted_cond (inst->operands[3].cond); |
| inst->operands[3].type = AARCH64_OPND_NIL; |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* CSET <Wd>, <cond> |
| is equivalent to: |
| CSINC <Wd>, WZR, WZR, invert(<cond>) |
| where <cond> is not AL or NV. */ |
| |
| static int |
| convert_csinc_to_cset (aarch64_inst *inst) |
| { |
| if (inst->operands[1].reg.regno == 0x1f |
| && inst->operands[2].reg.regno == 0x1f |
| && (inst->operands[3].cond->value & 0xe) != 0xe) |
| { |
| copy_operand_info (inst, 1, 3); |
| inst->operands[1].cond = get_inverted_cond (inst->operands[3].cond); |
| inst->operands[3].type = AARCH64_OPND_NIL; |
| inst->operands[2].type = AARCH64_OPND_NIL; |
| return 1; |
| } |
| return 0; |
| } |
| |
| /* MOV <Wd>, #<imm> |
| is equivalent to: |
| MOVZ <Wd>, #<imm16>, LSL #<shift>. |
| |
| A disassembler may output ORR, MOVZ and MOVN as a MOV mnemonic, except when |
| ORR has an immediate that could be generated by a MOVZ or MOVN instruction, |
| or where a MOVN has an immediate that could be encoded by MOVZ, or where |
| MOVZ/MOVN #0 have a shift amount other than LSL #0, in which case the |
| machine-instruction mnemonic must be used. */ |
| |
| static int |
| convert_movewide_to_mov (aarch64_inst *inst) |
| { |
| uint64_t value = inst->operands[1].imm.value; |
| /* MOVZ/MOVN #0 have a shift amount other than LSL #0. */ |
| if (value == 0 && inst->operands[1].shifter.amount != 0) |
| return 0; |
| inst->operands[1].type = AARCH64_OPND_IMM_MOV; |
| inst->operands[1].shifter.kind = AARCH64_MOD_NONE; |
| value <<= inst->operands[1].shifter.amount; |
| /* As an alias convertor, it has to be clear that the INST->OPCODE |
| is the opcode of the real instruction. */ |
| if (inst->opcode->op == OP_MOVN) |
| { |
| int is32 = inst->operands[0].qualifier == AARCH64_OPND_QLF_W; |
| value = ~value; |
| /* A MOVN has an immediate that could be encoded by MOVZ. */ |
| if (aarch64_wide_constant_p (value, is32, NULL) == TRUE) |
| return 0; |
| } |
| inst->operands[1].imm.value = value; |
| inst->operands[1].shifter.amount = 0; |
| return 1; |
| } |
| |
| /* MOV <Wd>, #<imm> |
| is equivalent to: |
| ORR <Wd>, WZR, #<imm>. |
| |
| A disassembler may output ORR, MOVZ and MOVN as a MOV mnemonic, except when |
| ORR has an immediate that could be generated by a MOVZ or MOVN instruction, |
| or where a MOVN has an immediate that could be encoded by MOVZ, or where |
| MOVZ/MOVN #0 have a shift amount other than LSL #0, in which case the |
| machine-instruction mnemonic must be used. */ |
| |
| static int |
| convert_movebitmask_to_mov (aarch64_inst *inst) |
| { |
| int is32; |
| uint64_t value; |
| |
| /* Should have been assured by the base opcode value. */ |
| assert (inst->operands[1].reg.regno == 0x1f); |
| copy_operand_info (inst, 1, 2); |
| is32 = inst->operands[0].qualifier == AARCH64_OPND_QLF_W; |
| inst->operands[1].type = AARCH64_OPND_IMM_MOV; |
| value = inst->operands[1].imm.value; |
| /* ORR has an immediate that could be generated by a MOVZ or MOVN |
| instruction. */ |
| if (inst->operands[0].reg.regno != 0x1f |
| && (aarch64_wide_constant_p (value, is32, NULL) == TRUE |
| || aarch64_wide_constant_p (~value, is32, NULL) == TRUE)) |
| return 0; |
| |
| inst->operands[2].type = AARCH64_OPND_NIL; |
| return 1; |
| } |
| |
| /* Some alias opcodes are disassembled by being converted from their real-form. |
| N.B. INST->OPCODE is the real opcode rather than the alias. */ |
| |
| static int |
| convert_to_alias (aarch64_inst *inst, const aarch64_opcode *alias) |
| { |
| switch (alias->op) |
| { |
| case OP_ASR_IMM: |
| case OP_LSR_IMM: |
| return convert_bfm_to_sr (inst); |
| case OP_LSL_IMM: |
| return convert_ubfm_to_lsl (inst); |
| case OP_CINC: |
| case OP_CINV: |
| case OP_CNEG: |
| return convert_from_csel (inst); |
| case OP_CSET: |
| case OP_CSETM: |
| return convert_csinc_to_cset (inst); |
| case OP_UBFX: |
| case OP_BFXIL: |
| case OP_SBFX: |
| return convert_bfm_to_bfx (inst); |
| case OP_SBFIZ: |
| case OP_BFI: |
| case OP_UBFIZ: |
| return convert_bfm_to_bfi (inst); |
| case OP_BFC: |
| return convert_bfm_to_bfc (inst); |
| case OP_MOV_V: |
| return convert_orr_to_mov (inst); |
| case OP_MOV_IMM_WIDE: |
| case OP_MOV_IMM_WIDEN: |
| return convert_movewide_to_mov (inst); |
| case OP_MOV_IMM_LOG: |
| return convert_movebitmask_to_mov (inst); |
| case OP_ROR_IMM: |
| return convert_extr_to_ror (inst); |
| case OP_SXTL: |
| case OP_SXTL2: |
| case OP_UXTL: |
| case OP_UXTL2: |
| return convert_shll_to_xtl (inst); |
| default: |
| return 0; |
| } |
| } |
| |
| static int aarch64_opcode_decode (const aarch64_opcode *, const aarch64_insn, |
| aarch64_inst *, int); |
| |
| /* Given the instruction information in *INST, check if the instruction has |
| any alias form that can be used to represent *INST. If the answer is yes, |
| update *INST to be in the form of the determined alias. */ |
| |
| /* In the opcode description table, the following flags are used in opcode |
| entries to help establish the relations between the real and alias opcodes: |
| |
| F_ALIAS: opcode is an alias |
| F_HAS_ALIAS: opcode has alias(es) |
| F_P1 |
| F_P2 |
| F_P3: Disassembly preference priority 1-3 (the larger the |
| higher). If nothing is specified, it is the priority |
| 0 by default, i.e. the lowest priority. |
| |
| Although the relation between the machine and the alias instructions are not |
| explicitly described, it can be easily determined from the base opcode |
| values, masks and the flags F_ALIAS and F_HAS_ALIAS in their opcode |
| description entries: |
| |
| The mask of an alias opcode must be equal to or a super-set (i.e. more |
| constrained) of that of the aliased opcode; so is the base opcode value. |
| |
| if (opcode_has_alias (real) && alias_opcode_p (opcode) |
| && (opcode->mask & real->mask) == real->mask |
| && (real->mask & opcode->opcode) == (real->mask & real->opcode)) |
| then OPCODE is an alias of, and only of, the REAL instruction |
| |
| The alias relationship is forced flat-structured to keep related algorithm |
| simple; an opcode entry cannot be flagged with both F_ALIAS and F_HAS_ALIAS. |
| |
| During the disassembling, the decoding decision tree (in |
| opcodes/aarch64-dis-2.c) always returns an machine instruction opcode entry; |
| if the decoding of such a machine instruction succeeds (and -Mno-aliases is |
| not specified), the disassembler will check whether there is any alias |
| instruction exists for this real instruction. If there is, the disassembler |
| will try to disassemble the 32-bit binary again using the alias's rule, or |
| try to convert the IR to the form of the alias. In the case of the multiple |
| aliases, the aliases are tried one by one from the highest priority |
| (currently the flag F_P3) to the lowest priority (no priority flag), and the |
| first succeeds first adopted. |
| |
| You may ask why there is a need for the conversion of IR from one form to |
| another in handling certain aliases. This is because on one hand it avoids |
| adding more operand code to handle unusual encoding/decoding; on other |
| hand, during the disassembling, the conversion is an effective approach to |
| check the condition of an alias (as an alias may be adopted only if certain |
| conditions are met). |
| |
| In order to speed up the alias opcode lookup, aarch64-gen has preprocessed |
| aarch64_opcode_table and generated aarch64_find_alias_opcode and |
| aarch64_find_next_alias_opcode (in opcodes/aarch64-dis-2.c) to help. */ |
| |
| static void |
| determine_disassembling_preference (struct aarch64_inst *inst) |
| { |
| const aarch64_opcode *opcode; |
| const aarch64_opcode *alias; |
| |
| opcode = inst->opcode; |
| |
| /* This opcode does not have an alias, so use itself. */ |
| if (opcode_has_alias (opcode) == FALSE) |
| return; |
| |
| alias = aarch64_find_alias_opcode (opcode); |
| assert (alias); |
| |
| #ifdef DEBUG_AARCH64 |
| if (debug_dump) |
| { |
| const aarch64_opcode *tmp = alias; |
| printf ("#### LIST orderd: "); |
| while (tmp) |
| { |
| printf ("%s, ", tmp->name); |
| tmp = aarch64_find_next_alias_opcode (tmp); |
| } |
| printf ("\n"); |
| } |
| #endif /* DEBUG_AARCH64 */ |
| |
| for (; alias; alias = aarch64_find_next_alias_opcode (alias)) |
| { |
| DEBUG_TRACE ("try %s", alias->name); |
| assert (alias_opcode_p (alias) || opcode_has_alias (opcode)); |
| |
| /* An alias can be a pseudo opcode which will never be used in the |
| disassembly, e.g. BIC logical immediate is such a pseudo opcode |
| aliasing AND. */ |
| if (pseudo_opcode_p (alias)) |
| { |
| DEBUG_TRACE ("skip pseudo %s", alias->name); |
| continue; |
| } |
| |
| if ((inst->value & alias->mask) != alias->opcode) |
| { |
| DEBUG_TRACE ("skip %s as base opcode not match", alias->name); |
| continue; |
| } |
| /* No need to do any complicated transformation on operands, if the alias |
| opcode does not have any operand. */ |
| if (aarch64_num_of_operands (alias) == 0 && alias->opcode == inst->value) |
| { |
| DEBUG_TRACE ("succeed with 0-operand opcode %s", alias->name); |
| aarch64_replace_opcode (inst, alias); |
| return; |
| } |
| if (alias->flags & F_CONV) |
| { |
| aarch64_inst copy; |
| memcpy (©, inst, sizeof (aarch64_inst)); |
| /* ALIAS is the preference as long as the instruction can be |
| successfully converted to the form of ALIAS. */ |
| if (convert_to_alias (©, alias) == 1) |
| { |
| aarch64_replace_opcode (©, alias); |
| assert (aarch64_match_operands_constraint (©, NULL)); |
| DEBUG_TRACE ("succeed with %s via conversion", alias->name); |
| memcpy (inst, ©, sizeof (aarch64_inst)); |
| return; |
| } |
| } |
| else |
| { |
| /* Directly decode the alias opcode. */ |
| aarch64_inst temp; |
| memset (&temp, '\0', sizeof (aarch64_inst)); |
| if (aarch64_opcode_decode (alias, inst->value, &temp, 1) == 1) |
| { |
| DEBUG_TRACE ("succeed with %s via direct decoding", alias->name); |
| memcpy (inst, &temp, sizeof (aarch64_inst)); |
| return; |
| } |
| } |
| } |
| } |
| |
| /* Some instructions (including all SVE ones) use the instruction class |
| to describe how a qualifiers_list index is represented in the instruction |
| encoding. If INST is such an instruction, decode the appropriate fields |
| and fill in the operand qualifiers accordingly. Return true if no |
| problems are found. */ |
| |
| static bfd_boolean |
| aarch64_decode_variant_using_iclass (aarch64_inst *inst) |
| { |
| int i, variant; |
| |
| variant = 0; |
| switch (inst->opcode->iclass) |
| { |
| case sve_cpy: |
| variant = extract_fields (inst->value, 0, 2, FLD_size, FLD_SVE_M_14); |
| break; |
| |
| case sve_index: |
| i = extract_field (FLD_SVE_tsz, inst->value, 0); |
| if (i == 0) |
| return FALSE; |
| while ((i & 1) == 0) |
| { |
| i >>= 1; |
| variant += 1; |
| } |
| break; |
| |
| case sve_limm: |
| /* Pick the smallest applicable element size. */ |
| if ((inst->value & 0x20600) == 0x600) |
| variant = 0; |
| else if ((inst->value & 0x20400) == 0x400) |
| variant = 1; |
| else if ((inst->value & 0x20000) == 0) |
| variant = 2; |
| else |
| variant = 3; |
| break; |
| |
| case sve_misc: |
| /* sve_misc instructions have only a single variant. */ |
| break; |
| |
| case sve_movprfx: |
| variant = extract_fields (inst->value, 0, 2, FLD_size, FLD_SVE_M_16); |
| break; |
| |
| case sve_pred_zm: |
| variant = extract_field (FLD_SVE_M_4, inst->value, 0); |
| break; |
| |
| case sve_shift_pred: |
| i = extract_fields (inst->value, 0, 2, FLD_SVE_tszh, FLD_SVE_tszl_8); |
| sve_shift: |
| if (i == 0) |
| return FALSE; |
| while (i != 1) |
| { |
| i >>= 1; |
| variant += 1; |
| } |
| break; |
| |
| case sve_shift_unpred: |
| i = extract_fields (inst->value, 0, 2, FLD_SVE_tszh, FLD_SVE_tszl_19); |
| goto sve_shift; |
| |
| case sve_size_bhs: |
| variant = extract_field (FLD_size, inst->value, 0); |
| if (variant >= 3) |
| return FALSE; |
| break; |
| |
| case sve_size_bhsd: |
| variant = extract_field (FLD_size, inst->value, 0); |
| break; |
| |
| case sve_size_hsd: |
| i = extract_field (FLD_size, inst->value, 0); |
| if (i < 1) |
| return FALSE; |
| variant = i - 1; |
| break; |
| |
| case sve_size_sd: |
| variant = extract_field (FLD_SVE_sz, inst->value, 0); |
| break; |
| |
| default: |
| /* No mapping between instruction class and qualifiers. */ |
| return TRUE; |
| } |
| |
| for (i = 0; i < AARCH64_MAX_OPND_NUM; ++i) |
| inst->operands[i].qualifier = inst->opcode->qualifiers_list[variant][i]; |
| return TRUE; |
| } |
| /* Decode the CODE according to OPCODE; fill INST. Return 0 if the decoding |
| fails, which meanes that CODE is not an instruction of OPCODE; otherwise |
| return 1. |
| |
| If OPCODE has alias(es) and NOALIASES_P is 0, an alias opcode may be |
| determined and used to disassemble CODE; this is done just before the |
| return. */ |
| |
| static int |
| aarch64_opcode_decode (const aarch64_opcode *opcode, const aarch64_insn code, |
| aarch64_inst *inst, int noaliases_p) |
| { |
| int i; |
| |
| DEBUG_TRACE ("enter with %s", opcode->name); |
| |
| assert (opcode && inst); |
| |
| /* Check the base opcode. */ |
| if ((code & opcode->mask) != (opcode->opcode & opcode->mask)) |
| { |
| DEBUG_TRACE ("base opcode match FAIL"); |
| goto decode_fail; |
| } |
| |
| /* Clear inst. */ |
| memset (inst, '\0', sizeof (aarch64_inst)); |
| |
| inst->opcode = opcode; |
| inst->value = code; |
| |
| /* Assign operand codes and indexes. */ |
| for (i = 0; i < AARCH64_MAX_OPND_NUM; ++i) |
| { |
| if (opcode->operands[i] == AARCH64_OPND_NIL) |
| break; |
| inst->operands[i].type = opcode->operands[i]; |
| inst->operands[i].idx = i; |
| } |
| |
| /* Call the opcode decoder indicated by flags. */ |
| if (opcode_has_special_coder (opcode) && do_special_decoding (inst) == 0) |
| { |
| DEBUG_TRACE ("opcode flag-based decoder FAIL"); |
| goto decode_fail; |
| } |
| |
| /* Possibly use the instruction class to determine the correct |
| qualifier. */ |
| if (!aarch64_decode_variant_using_iclass (inst)) |
| { |
| DEBUG_TRACE ("iclass-based decoder FAIL"); |
| goto decode_fail; |
| } |
| |
| /* Call operand decoders. */ |
| for (i = 0; i < AARCH64_MAX_OPND_NUM; ++i) |
| { |
| const aarch64_operand *opnd; |
| enum aarch64_opnd type; |
| |
| type = opcode->operands[i]; |
| if (type == AARCH64_OPND_NIL) |
| break; |
| opnd = &aarch64_operands[type]; |
| if (operand_has_extractor (opnd) |
| && (! aarch64_extract_operand (opnd, &inst->operands[i], code, inst))) |
| { |
| DEBUG_TRACE ("operand decoder FAIL at operand %d", i); |
| goto decode_fail; |
| } |
| } |
| |
| /* If the opcode has a verifier, then check it now. */ |
| if (opcode->verifier && ! opcode->verifier (opcode, code)) |
| { |
| DEBUG_TRACE ("operand verifier FAIL"); |
| goto decode_fail; |
| } |
| |
| /* Match the qualifiers. */ |
| if (aarch64_match_operands_constraint (inst, NULL) == 1) |
| { |
| /* Arriving here, the CODE has been determined as a valid instruction |
| of OPCODE and *INST has been filled with information of this OPCODE |
| instruction. Before the return, check if the instruction has any |
| alias and should be disassembled in the form of its alias instead. |
| If the answer is yes, *INST will be updated. */ |
| if (!noaliases_p) |
| determine_disassembling_preference (inst); |
| DEBUG_TRACE ("SUCCESS"); |
| return 1; |
| } |
| else |
| { |
| DEBUG_TRACE ("constraint matching FAIL"); |
| } |
| |
| decode_fail: |
| return 0; |
| } |
| |
| /* This does some user-friendly fix-up to *INST. It is currently focus on |
| the adjustment of qualifiers to help the printed instruction |
| recognized/understood more easily. */ |
| |
| static void |
| user_friendly_fixup (aarch64_inst *inst) |
| { |
| switch (inst->opcode->iclass) |
| { |
| case testbranch: |
| /* TBNZ Xn|Wn, #uimm6, label |
| Test and Branch Not Zero: conditionally jumps to label if bit number |
| uimm6 in register Xn is not zero. The bit number implies the width of |
| the register, which may be written and should be disassembled as Wn if |
| uimm is less than 32. Limited to a branch offset range of +/- 32KiB. |
| */ |
| if (inst->operands[1].imm.value < 32) |
| inst->operands[0].qualifier = AARCH64_OPND_QLF_W; |
| break; |
| default: break; |
| } |
| } |
| |
| /* Decode INSN and fill in *INST the instruction information. An alias |
| opcode may be filled in *INSN if NOALIASES_P is FALSE. Return zero on |
| success. */ |
| |
| int |
| aarch64_decode_insn (aarch64_insn insn, aarch64_inst *inst, |
| bfd_boolean noaliases_p) |
| { |
| const aarch64_opcode *opcode = aarch64_opcode_lookup (insn); |
| |
| #ifdef DEBUG_AARCH64 |
| if (debug_dump) |
| { |
| const aarch64_opcode *tmp = opcode; |
| printf ("\n"); |
| DEBUG_TRACE ("opcode lookup:"); |
| while (tmp != NULL) |
| { |
| aarch64_verbose (" %s", tmp->name); |
| tmp = aarch64_find_next_opcode (tmp); |
| } |
| } |
| #endif /* DEBUG_AARCH64 */ |
| |
| /* A list of opcodes may have been found, as aarch64_opcode_lookup cannot |
| distinguish some opcodes, e.g. SSHR and MOVI, which almost share the same |
| opcode field and value, apart from the difference that one of them has an |
| extra field as part of the opcode, but such a field is used for operand |
| encoding in other opcode(s) ('immh' in the case of the example). */ |
| while (opcode != NULL) |
| { |
| /* But only one opcode can be decoded successfully for, as the |
| decoding routine will check the constraint carefully. */ |
| if (aarch64_opcode_decode (opcode, insn, inst, noaliases_p) == 1) |
| return ERR_OK; |
| opcode = aarch64_find_next_opcode (opcode); |
| } |
| |
| return ERR_UND; |
| } |
| |
| /* Print operands. */ |
| |
| static void |
| print_operands (bfd_vma pc, const aarch64_opcode *opcode, |
| const aarch64_opnd_info *opnds, struct disassemble_info *info) |
| { |
| int i, pcrel_p, num_printed; |
| for (i = 0, num_printed = 0; i < AARCH64_MAX_OPND_NUM; ++i) |
| { |
| char str[128]; |
| /* We regard the opcode operand info more, however we also look into |
| the inst->operands to support the disassembling of the optional |
| operand. |
| The two operand code should be the same in all cases, apart from |
| when the operand can be optional. */ |
| if (opcode->operands[i] == AARCH64_OPND_NIL |
| || opnds[i].type == AARCH64_OPND_NIL) |
| break; |
| |
| /* Generate the operand string in STR. */ |
| aarch64_print_operand (str, sizeof (str), pc, opcode, opnds, i, &pcrel_p, |
| &info->target); |
| |
| /* Print the delimiter (taking account of omitted operand(s)). */ |
| if (str[0] != '\0') |
| (*info->fprintf_func) (info->stream, "%s", |
| num_printed++ == 0 ? "\t" : ", "); |
| |
| /* Print the operand. */ |
| if (pcrel_p) |
| (*info->print_address_func) (info->target, info); |
| else |
| (*info->fprintf_func) (info->stream, "%s", str); |
| } |
| } |
| |
| /* Set NAME to a copy of INST's mnemonic with the "." suffix removed. */ |
| |
| static void |
| remove_dot_suffix (char *name, const aarch64_inst *inst) |
| { |
| char *ptr; |
| size_t len; |
| |
| ptr = strchr (inst->opcode->name, '.'); |
| assert (ptr && inst->cond); |
| len = ptr - inst->opcode->name; |
| assert (len < 8); |
| strncpy (name, inst->opcode->name, len); |
| name[len] = '\0'; |
| } |
| |
| /* Print the instruction mnemonic name. */ |
| |
| static void |
| print_mnemonic_name (const aarch64_inst *inst, struct disassemble_info *info) |
| { |
| if (inst->opcode->flags & F_COND) |
| { |
| /* For instructions that are truly conditionally executed, e.g. b.cond, |
| prepare the full mnemonic name with the corresponding condition |
| suffix. */ |
| char name[8]; |
| |
| remove_dot_suffix (name, inst); |
| (*info->fprintf_func) (info->stream, "%s.%s", name, inst->cond->names[0]); |
| } |
| else |
| (*info->fprintf_func) (info->stream, "%s", inst->opcode->name); |
| } |
| |
| /* Decide whether we need to print a comment after the operands of |
| instruction INST. */ |
| |
| static void |
| print_comment (const aarch64_inst *inst, struct disassemble_info *info) |
| { |
| if (inst->opcode->flags & F_COND) |
| { |
| char name[8]; |
| unsigned int i, num_conds; |
| |
| remove_dot_suffix (name, inst); |
| num_conds = ARRAY_SIZE (inst->cond->names); |
| for (i = 1; i < num_conds && inst->cond->names[i]; ++i) |
| (*info->fprintf_func) (info->stream, "%s %s.%s", |
| i == 1 ? " //" : ",", |
| name, inst->cond->names[i]); |
| } |
| } |
| |
| /* Print the instruction according to *INST. */ |
| |
| static void |
| print_aarch64_insn (bfd_vma pc, const aarch64_inst *inst, |
| struct disassemble_info *info) |
| { |
| print_mnemonic_name (inst, info); |
| print_operands (pc, inst->opcode, inst->operands, info); |
| print_comment (inst, info); |
| } |
| |
| /* Entry-point of the instruction disassembler and printer. */ |
| |
| static void |
| print_insn_aarch64_word (bfd_vma pc, |
| uint32_t word, |
| struct disassemble_info *info) |
| { |
| static const char *err_msg[6] = |
| { |
| [ERR_OK] = "_", |
| [-ERR_UND] = "undefined", |
| [-ERR_UNP] = "unpredictable", |
| [-ERR_NYI] = "NYI" |
| }; |
| |
| int ret; |
| aarch64_inst inst; |
| |
| info->insn_info_valid = 1; |
| info->branch_delay_insns = 0; |
| info->data_size = 0; |
| info->target = 0; |
| info->target2 = 0; |
| |
| if (info->flags & INSN_HAS_RELOC) |
| /* If the instruction has a reloc associated with it, then |
| the offset field in the instruction will actually be the |
| addend for the reloc. (If we are using REL type relocs). |
| In such cases, we can ignore the pc when computing |
| addresses, since the addend is not currently pc-relative. */ |
| pc = 0; |
| |
| ret = aarch64_decode_insn (word, &inst, no_aliases); |
| |
| if (((word >> 21) & 0x3ff) == 1) |
| { |
| /* RESERVED for ALES. */ |
| assert (ret != ERR_OK); |
| ret = ERR_NYI; |
| } |
| |
| switch (ret) |
| { |
| case ERR_UND: |
| case ERR_UNP: |
| case ERR_NYI: |
| /* Handle undefined instructions. */ |
| info->insn_type = dis_noninsn; |
| (*info->fprintf_func) (info->stream,".inst\t0x%08x ; %s", |
| word, err_msg[-ret]); |
| break; |
| case ERR_OK: |
| user_friendly_fixup (&inst); |
| print_aarch64_insn (pc, &inst, info); |
| break; |
| default: |
| abort (); |
| } |
| } |
| |
| /* Disallow mapping symbols ($x, $d etc) from |
| being displayed in symbol relative addresses. */ |
| |
| bfd_boolean |
| aarch64_symbol_is_valid (asymbol * sym, |
| struct disassemble_info * info ATTRIBUTE_UNUSED) |
| { |
| const char * name; |
| |
| if (sym == NULL) |
| return FALSE; |
| |
| name = bfd_asymbol_name (sym); |
| |
| return name |
| && (name[0] != '$' |
| || (name[1] != 'x' && name[1] != 'd') |
| || (name[2] != '\0' && name[2] != '.')); |
| } |
| |
| /* Print data bytes on INFO->STREAM. */ |
| |
| static void |
| print_insn_data (bfd_vma pc ATTRIBUTE_UNUSED, |
| uint32_t word, |
| struct disassemble_info *info) |
| { |
| switch (info->bytes_per_chunk) |
| { |
| case 1: |
| info->fprintf_func (info->stream, ".byte\t0x%02x", word); |
| break; |
| case 2: |
| info->fprintf_func (info->stream, ".short\t0x%04x", word); |
| break; |
| case 4: |
| info->fprintf_func (info->stream, ".word\t0x%08x", word); |
| break; |
| default: |
| abort (); |
| } |
| } |
| |
| /* Try to infer the code or data type from a symbol. |
| Returns nonzero if *MAP_TYPE was set. */ |
| |
| static int |
| get_sym_code_type (struct disassemble_info *info, int n, |
| enum map_type *map_type) |
| { |
| elf_symbol_type *es; |
| unsigned int type; |
| const char *name; |
| |
| es = *(elf_symbol_type **)(info->symtab + n); |
| type = ELF_ST_TYPE (es->internal_elf_sym.st_info); |
| |
| /* If the symbol has function type then use that. */ |
| if (type == STT_FUNC) |
| { |
| *map_type = MAP_INSN; |
| return TRUE; |
| } |
| |
| /* Check for mapping symbols. */ |
|