// Copyright 2015, VIXL authors
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   * Redistributions of source code must retain the above copyright notice,
//     this list of conditions and the following disclaimer.
//   * Redistributions in binary form must reproduce the above copyright notice,
//     this list of conditions and the following disclaimer in the documentation
//     and/or other materials provided with the distribution.
//   * Neither the name of ARM Limited nor the names of its contributors may be
//     used to endorse or promote products derived from this software without
//     specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

#include "disasm-aarch64.h"

#include <bitset>
#include <cstdlib>
#include <sstream>

namespace vixl {
namespace aarch64 {

const Disassembler::FormToVisitorFnMap *Disassembler::GetFormToVisitorFnMap() {
  static const FormToVisitorFnMap form_to_visitor = {
      DEFAULT_FORM_TO_VISITOR_MAP(Disassembler),
      {"autia1716_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"autiasp_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"autiaz_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"autib1716_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"autibsp_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"autibz_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"axflag_m_pstate"_h, &Disassembler::DisassembleNoArgs},
      {"cfinv_m_pstate"_h, &Disassembler::DisassembleNoArgs},
      {"csdb_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"dgh_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"ssbb_only_barriers"_h, &Disassembler::DisassembleNoArgs},
      {"esb_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"isb_bi_barriers"_h, &Disassembler::DisassembleNoArgs},
      {"nop_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"pacia1716_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"paciasp_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"paciaz_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"pacib1716_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"pacibsp_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"pacibz_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"sev_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"sevl_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"wfe_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"wfi_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"xaflag_m_pstate"_h, &Disassembler::DisassembleNoArgs},
      {"xpaclri_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"yield_hi_hints"_h, &Disassembler::DisassembleNoArgs},
      {"abs_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"cls_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"clz_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"cnt_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"neg_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"rev16_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"rev32_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"rev64_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"sqabs_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"sqneg_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"suqadd_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"urecpe_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"ursqrte_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"usqadd_asimdmisc_r"_h, &Disassembler::VisitNEON2RegMisc},
      {"not_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegLogical},
      {"rbit_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegLogical},
      {"xtn_asimdmisc_n"_h, &Disassembler::DisassembleNEON2RegExtract},
      {"sqxtn_asimdmisc_n"_h, &Disassembler::DisassembleNEON2RegExtract},
      {"uqxtn_asimdmisc_n"_h, &Disassembler::DisassembleNEON2RegExtract},
      {"sqxtun_asimdmisc_n"_h, &Disassembler::DisassembleNEON2RegExtract},
      {"shll_asimdmisc_s"_h, &Disassembler::DisassembleNEON2RegExtract},
      {"sadalp_asimdmisc_p"_h, &Disassembler::DisassembleNEON2RegAddlp},
      {"saddlp_asimdmisc_p"_h, &Disassembler::DisassembleNEON2RegAddlp},
      {"uadalp_asimdmisc_p"_h, &Disassembler::DisassembleNEON2RegAddlp},
      {"uaddlp_asimdmisc_p"_h, &Disassembler::DisassembleNEON2RegAddlp},
      {"cmeq_asimdmisc_z"_h, &Disassembler::DisassembleNEON2RegCompare},
      {"cmge_asimdmisc_z"_h, &Disassembler::DisassembleNEON2RegCompare},
      {"cmgt_asimdmisc_z"_h, &Disassembler::DisassembleNEON2RegCompare},
      {"cmle_asimdmisc_z"_h, &Disassembler::DisassembleNEON2RegCompare},
      {"cmlt_asimdmisc_z"_h, &Disassembler::DisassembleNEON2RegCompare},
      {"fcmeq_asimdmisc_fz"_h, &Disassembler::DisassembleNEON2RegFPCompare},
      {"fcmge_asimdmisc_fz"_h, &Disassembler::DisassembleNEON2RegFPCompare},
      {"fcmgt_asimdmisc_fz"_h, &Disassembler::DisassembleNEON2RegFPCompare},
      {"fcmle_asimdmisc_fz"_h, &Disassembler::DisassembleNEON2RegFPCompare},
      {"fcmlt_asimdmisc_fz"_h, &Disassembler::DisassembleNEON2RegFPCompare},
      {"fcvtl_asimdmisc_l"_h, &Disassembler::DisassembleNEON2RegFPConvert},
      {"fcvtn_asimdmisc_n"_h, &Disassembler::DisassembleNEON2RegFPConvert},
      {"fcvtxn_asimdmisc_n"_h, &Disassembler::DisassembleNEON2RegFPConvert},
      {"fabs_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtas_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtau_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtms_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtmu_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtns_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtnu_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtps_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtpu_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtzs_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fcvtzu_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fneg_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frecpe_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frint32x_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frint32z_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frint64x_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frint64z_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frinta_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frinti_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frintm_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frintn_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frintp_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frintx_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frintz_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"frsqrte_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"fsqrt_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"scvtf_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"ucvtf_asimdmisc_r"_h, &Disassembler::DisassembleNEON2RegFP},
      {"smlal_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"smlsl_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"smull_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"umlal_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"umlsl_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"umull_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"sqdmull_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"sqdmlal_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"sqdmlsl_asimdelem_l"_h, &Disassembler::DisassembleNEONMulByElementLong},
      {"sdot_asimdelem_d"_h, &Disassembler::DisassembleNEONDotProdByElement},
      {"udot_asimdelem_d"_h, &Disassembler::DisassembleNEONDotProdByElement},
      {"usdot_asimdelem_d"_h, &Disassembler::DisassembleNEONDotProdByElement},
      {"sudot_asimdelem_d"_h, &Disassembler::DisassembleNEONDotProdByElement},
      {"fmlal2_asimdelem_lh"_h,
       &Disassembler::DisassembleNEONFPMulByElementLong},
      {"fmlal_asimdelem_lh"_h,
       &Disassembler::DisassembleNEONFPMulByElementLong},
      {"fmlsl2_asimdelem_lh"_h,
       &Disassembler::DisassembleNEONFPMulByElementLong},
      {"fmlsl_asimdelem_lh"_h,
       &Disassembler::DisassembleNEONFPMulByElementLong},
      {"fcmla_asimdelem_c_h"_h,
       &Disassembler::DisassembleNEONComplexMulByElement},
      {"fcmla_asimdelem_c_s"_h,
       &Disassembler::DisassembleNEONComplexMulByElement},
      {"fmla_asimdelem_rh_h"_h,
       &Disassembler::DisassembleNEONHalfFPMulByElement},
      {"fmls_asimdelem_rh_h"_h,
       &Disassembler::DisassembleNEONHalfFPMulByElement},
      {"fmulx_asimdelem_rh_h"_h,
       &Disassembler::DisassembleNEONHalfFPMulByElement},
      {"fmul_asimdelem_rh_h"_h,
       &Disassembler::DisassembleNEONHalfFPMulByElement},
      {"fmla_asimdelem_r_sd"_h, &Disassembler::DisassembleNEONFPMulByElement},
      {"fmls_asimdelem_r_sd"_h, &Disassembler::DisassembleNEONFPMulByElement},
      {"fmulx_asimdelem_r_sd"_h, &Disassembler::DisassembleNEONFPMulByElement},
      {"fmul_asimdelem_r_sd"_h, &Disassembler::DisassembleNEONFPMulByElement},
      {"mla_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"mls_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"mul_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"saba_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"sabd_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"shadd_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"shsub_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"smaxp_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"smax_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"sminp_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"smin_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"srhadd_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"uaba_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"uabd_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"uhadd_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"uhsub_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"umaxp_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"umax_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"uminp_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"umin_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"urhadd_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameNoD},
      {"and_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"bic_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"bif_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"bit_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"bsl_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"eor_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"orr_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"orn_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"pmul_asimdsame_only"_h, &Disassembler::DisassembleNEON3SameLogical},
      {"fmlal2_asimdsame_f"_h, &Disassembler::DisassembleNEON3SameFHM},
      {"fmlal_asimdsame_f"_h, &Disassembler::DisassembleNEON3SameFHM},
      {"fmlsl2_asimdsame_f"_h, &Disassembler::DisassembleNEON3SameFHM},
      {"fmlsl_asimdsame_f"_h, &Disassembler::DisassembleNEON3SameFHM},
      {"sri_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"srshr_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"srsra_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"sshr_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"ssra_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"urshr_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"ursra_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"ushr_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"usra_asimdshf_r"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"scvtf_asimdshf_c"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"ucvtf_asimdshf_c"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"fcvtzs_asimdshf_c"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"fcvtzu_asimdshf_c"_h, &Disassembler::DisassembleNEONShiftRightImm},
      {"ushll_asimdshf_l"_h, &Disassembler::DisassembleNEONShiftLeftLongImm},
      {"sshll_asimdshf_l"_h, &Disassembler::DisassembleNEONShiftLeftLongImm},
      {"shrn_asimdshf_n"_h, &Disassembler::DisassembleNEONShiftRightNarrowImm},
      {"rshrn_asimdshf_n"_h, &Disassembler::DisassembleNEONShiftRightNarrowImm},
      {"sqshrn_asimdshf_n"_h,
       &Disassembler::DisassembleNEONShiftRightNarrowImm},
      {"sqrshrn_asimdshf_n"_h,
       &Disassembler::DisassembleNEONShiftRightNarrowImm},
      {"sqshrun_asimdshf_n"_h,
       &Disassembler::DisassembleNEONShiftRightNarrowImm},
      {"sqrshrun_asimdshf_n"_h,
       &Disassembler::DisassembleNEONShiftRightNarrowImm},
      {"uqshrn_asimdshf_n"_h,
       &Disassembler::DisassembleNEONShiftRightNarrowImm},
      {"uqrshrn_asimdshf_n"_h,
       &Disassembler::DisassembleNEONShiftRightNarrowImm},
      {"sqdmlal_asisdelem_l"_h,
       &Disassembler::DisassembleNEONScalarSatMulLongIndex},
      {"sqdmlsl_asisdelem_l"_h,
       &Disassembler::DisassembleNEONScalarSatMulLongIndex},
      {"sqdmull_asisdelem_l"_h,
       &Disassembler::DisassembleNEONScalarSatMulLongIndex},
      {"fmla_asisdelem_rh_h"_h, &Disassembler::DisassembleNEONFPScalarMulIndex},
      {"fmla_asisdelem_r_sd"_h, &Disassembler::DisassembleNEONFPScalarMulIndex},
      {"fmls_asisdelem_rh_h"_h, &Disassembler::DisassembleNEONFPScalarMulIndex},
      {"fmls_asisdelem_r_sd"_h, &Disassembler::DisassembleNEONFPScalarMulIndex},
      {"fmulx_asisdelem_rh_h"_h,
       &Disassembler::DisassembleNEONFPScalarMulIndex},
      {"fmulx_asisdelem_r_sd"_h,
       &Disassembler::DisassembleNEONFPScalarMulIndex},
      {"fmul_asisdelem_rh_h"_h, &Disassembler::DisassembleNEONFPScalarMulIndex},
      {"fmul_asisdelem_r_sd"_h, &Disassembler::DisassembleNEONFPScalarMulIndex},
      {"fabd_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"facge_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"facgt_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"fcmeq_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"fcmge_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"fcmgt_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"fmulx_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"frecps_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"frsqrts_asisdsame_only"_h, &Disassembler::DisassembleNEONFPScalar3Same},
      {"sqrdmlah_asisdsame2_only"_h, &Disassembler::VisitNEONScalar3Same},
      {"sqrdmlsh_asisdsame2_only"_h, &Disassembler::VisitNEONScalar3Same},
      {"cmeq_asisdsame_only"_h, &Disassembler::DisassembleNEONScalar3SameOnlyD},
      {"cmge_asisdsame_only"_h, &Disassembler::DisassembleNEONScalar3SameOnlyD},
      {"cmgt_asisdsame_only"_h, &Disassembler::DisassembleNEONScalar3SameOnlyD},
      {"cmhi_asisdsame_only"_h, &Disassembler::DisassembleNEONScalar3SameOnlyD},
      {"cmhs_asisdsame_only"_h, &Disassembler::DisassembleNEONScalar3SameOnlyD},
      {"cmtst_asisdsame_only"_h,
       &Disassembler::DisassembleNEONScalar3SameOnlyD},
      {"add_asisdsame_only"_h, &Disassembler::DisassembleNEONScalar3SameOnlyD},
      {"sub_asisdsame_only"_h, &Disassembler::DisassembleNEONScalar3SameOnlyD},
      {"fmaxnmv_asimdall_only_h"_h,
       &Disassembler::DisassembleNEONFP16AcrossLanes},
      {"fmaxv_asimdall_only_h"_h,
       &Disassembler::DisassembleNEONFP16AcrossLanes},
      {"fminnmv_asimdall_only_h"_h,
       &Disassembler::DisassembleNEONFP16AcrossLanes},
      {"fminv_asimdall_only_h"_h,
       &Disassembler::DisassembleNEONFP16AcrossLanes},
      {"fmaxnmv_asimdall_only_sd"_h,
       &Disassembler::DisassembleNEONFPAcrossLanes},
      {"fminnmv_asimdall_only_sd"_h,
       &Disassembler::DisassembleNEONFPAcrossLanes},
      {"fmaxv_asimdall_only_sd"_h, &Disassembler::DisassembleNEONFPAcrossLanes},
      {"fminv_asimdall_only_sd"_h, &Disassembler::DisassembleNEONFPAcrossLanes},
      {"shl_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"sli_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"sri_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"srshr_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"srsra_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"sshr_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"ssra_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"urshr_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"ursra_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"ushr_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"usra_asisdshf_r"_h, &Disassembler::DisassembleNEONScalarShiftImmOnlyD},
      {"sqrshrn_asisdshf_n"_h,
       &Disassembler::DisassembleNEONScalarShiftRightNarrowImm},
      {"sqrshrun_asisdshf_n"_h,
       &Disassembler::DisassembleNEONScalarShiftRightNarrowImm},
      {"sqshrn_asisdshf_n"_h,
       &Disassembler::DisassembleNEONScalarShiftRightNarrowImm},
      {"sqshrun_asisdshf_n"_h,
       &Disassembler::DisassembleNEONScalarShiftRightNarrowImm},
      {"uqrshrn_asisdshf_n"_h,
       &Disassembler::DisassembleNEONScalarShiftRightNarrowImm},
      {"uqshrn_asisdshf_n"_h,
       &Disassembler::DisassembleNEONScalarShiftRightNarrowImm},
      {"cmeq_asisdmisc_z"_h, &Disassembler::DisassembleNEONScalar2RegMiscOnlyD},
      {"cmge_asisdmisc_z"_h, &Disassembler::DisassembleNEONScalar2RegMiscOnlyD},
      {"cmgt_asisdmisc_z"_h, &Disassembler::DisassembleNEONScalar2RegMiscOnlyD},
      {"cmle_asisdmisc_z"_h, &Disassembler::DisassembleNEONScalar2RegMiscOnlyD},
      {"cmlt_asisdmisc_z"_h, &Disassembler::DisassembleNEONScalar2RegMiscOnlyD},
      {"abs_asisdmisc_r"_h, &Disassembler::DisassembleNEONScalar2RegMiscOnlyD},
      {"neg_asisdmisc_r"_h, &Disassembler::DisassembleNEONScalar2RegMiscOnlyD},
      {"fcmeq_asisdmisc_fz"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcmge_asisdmisc_fz"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcmgt_asisdmisc_fz"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcmle_asisdmisc_fz"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcmlt_asisdmisc_fz"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtas_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtau_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtms_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtmu_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtns_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtnu_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtps_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtpu_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtxn_asisdmisc_n"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtzs_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"fcvtzu_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"frecpe_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"frecpx_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"frsqrte_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"scvtf_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"ucvtf_asisdmisc_r"_h, &Disassembler::DisassembleNEONFPScalar2RegMisc},
      {"pmull_asimddiff_l"_h, &Disassembler::DisassembleNEONPolynomialMul},
      {"adclb_z_zzz"_h, &Disassembler::DisassembleSVEAddSubCarry},
      {"adclt_z_zzz"_h, &Disassembler::DisassembleSVEAddSubCarry},
      {"addhnb_z_zz"_h, &Disassembler::DisassembleSVEAddSubHigh},
      {"addhnt_z_zz"_h, &Disassembler::DisassembleSVEAddSubHigh},
      {"addp_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"aesd_z_zz"_h, &Disassembler::Disassemble_ZdnB_ZdnB_ZmB},
      {"aese_z_zz"_h, &Disassembler::Disassemble_ZdnB_ZdnB_ZmB},
      {"aesimc_z_z"_h, &Disassembler::Disassemble_ZdnB_ZdnB},
      {"aesmc_z_z"_h, &Disassembler::Disassemble_ZdnB_ZdnB},
      {"bcax_z_zzz"_h, &Disassembler::DisassembleSVEBitwiseTernary},
      {"bdep_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"bext_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"bgrp_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"bsl1n_z_zzz"_h, &Disassembler::DisassembleSVEBitwiseTernary},
      {"bsl2n_z_zzz"_h, &Disassembler::DisassembleSVEBitwiseTernary},
      {"bsl_z_zzz"_h, &Disassembler::DisassembleSVEBitwiseTernary},
      {"cadd_z_zz"_h, &Disassembler::DisassembleSVEComplexIntAddition},
      {"cdot_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb_const},
      {"cdot_z_zzzi_d"_h, &Disassembler::Disassemble_ZdaD_ZnH_ZmH_imm_const},
      {"cdot_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnB_ZmB_imm_const},
      {"cmla_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnT_ZmT_const},
      {"cmla_z_zzzi_h"_h, &Disassembler::Disassemble_ZdaH_ZnH_ZmH_imm_const},
      {"cmla_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnS_ZmS_imm_const},
      {"eor3_z_zzz"_h, &Disassembler::DisassembleSVEBitwiseTernary},
      {"eorbt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"eortb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"ext_z_zi_con"_h, &Disassembler::Disassemble_ZdB_Zn1B_Zn2B_imm},
      {"faddp_z_p_zz"_h, &Disassembler::DisassembleSVEFPPair},
      {"fcvtlt_z_p_z_h2s"_h, &Disassembler::Disassemble_ZdS_PgM_ZnH},
      {"fcvtlt_z_p_z_s2d"_h, &Disassembler::Disassemble_ZdD_PgM_ZnS},
      {"fcvtnt_z_p_z_d2s"_h, &Disassembler::Disassemble_ZdS_PgM_ZnD},
      {"fcvtnt_z_p_z_s2h"_h, &Disassembler::Disassemble_ZdH_PgM_ZnS},
      {"fcvtx_z_p_z_d2s"_h, &Disassembler::Disassemble_ZdS_PgM_ZnD},
      {"fcvtxnt_z_p_z_d2s"_h, &Disassembler::Disassemble_ZdS_PgM_ZnD},
      {"flogb_z_p_z"_h, &Disassembler::DisassembleSVEFlogb},
      {"fmaxnmp_z_p_zz"_h, &Disassembler::DisassembleSVEFPPair},
      {"fmaxp_z_p_zz"_h, &Disassembler::DisassembleSVEFPPair},
      {"fminnmp_z_p_zz"_h, &Disassembler::DisassembleSVEFPPair},
      {"fminp_z_p_zz"_h, &Disassembler::DisassembleSVEFPPair},
      {"fmlalb_z_zzz"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH},
      {"fmlalb_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm},
      {"fmlalt_z_zzz"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH},
      {"fmlalt_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm},
      {"fmlslb_z_zzz"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH},
      {"fmlslb_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm},
      {"fmlslt_z_zzz"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH},
      {"fmlslt_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm},
      {"histcnt_z_p_zz"_h, &Disassembler::Disassemble_ZdT_PgZ_ZnT_ZmT},
      {"histseg_z_zz"_h, &Disassembler::Disassemble_ZdB_ZnB_ZmB},
      {"ldnt1b_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_PgZ_ZnD_Xm},
      {"ldnt1b_z_p_ar_s_x32_unscaled"_h,
       &Disassembler::Disassemble_ZtS_PgZ_ZnS_Xm},
      {"ldnt1d_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_PgZ_ZnD_Xm},
      {"ldnt1h_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_PgZ_ZnD_Xm},
      {"ldnt1h_z_p_ar_s_x32_unscaled"_h,
       &Disassembler::Disassemble_ZtS_PgZ_ZnS_Xm},
      {"ldnt1sb_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_PgZ_ZnD_Xm},
      {"ldnt1sb_z_p_ar_s_x32_unscaled"_h,
       &Disassembler::Disassemble_ZtS_PgZ_ZnS_Xm},
      {"ldnt1sh_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_PgZ_ZnD_Xm},
      {"ldnt1sh_z_p_ar_s_x32_unscaled"_h,
       &Disassembler::Disassemble_ZtS_PgZ_ZnS_Xm},
      {"ldnt1sw_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_PgZ_ZnD_Xm},
      {"ldnt1w_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_PgZ_ZnD_Xm},
      {"ldnt1w_z_p_ar_s_x32_unscaled"_h,
       &Disassembler::Disassemble_ZtS_PgZ_ZnS_Xm},
      {"match_p_p_zz"_h, &Disassembler::Disassemble_PdT_PgZ_ZnT_ZmT},
      {"mla_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnD_ZmD_imm},
      {"mla_z_zzzi_h"_h, &Disassembler::Disassemble_ZdH_ZnH_ZmH_imm},
      {"mla_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnS_ZmS_imm},
      {"mls_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnD_ZmD_imm},
      {"mls_z_zzzi_h"_h, &Disassembler::Disassemble_ZdH_ZnH_ZmH_imm},
      {"mls_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnS_ZmS_imm},
      {"mul_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"mul_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnD_ZmD_imm},
      {"mul_z_zzi_h"_h, &Disassembler::Disassemble_ZdH_ZnH_ZmH_imm},
      {"mul_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnS_ZmS_imm},
      {"nbsl_z_zzz"_h, &Disassembler::DisassembleSVEBitwiseTernary},
      {"nmatch_p_p_zz"_h, &Disassembler::Disassemble_PdT_PgZ_ZnT_ZmT},
      {"pmul_z_zz"_h, &Disassembler::Disassemble_ZdB_ZnB_ZmB},
      {"pmullb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"pmullt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"raddhnb_z_zz"_h, &Disassembler::DisassembleSVEAddSubHigh},
      {"raddhnt_z_zz"_h, &Disassembler::DisassembleSVEAddSubHigh},
      {"rax1_z_zz"_h, &Disassembler::Disassemble_ZdD_ZnD_ZmD},
      {"rshrnb_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"rshrnt_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"rsubhnb_z_zz"_h, &Disassembler::DisassembleSVEAddSubHigh},
      {"rsubhnt_z_zz"_h, &Disassembler::DisassembleSVEAddSubHigh},
      {"saba_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnT_ZmT},
      {"sabalb_z_zzz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"sabalt_z_zzz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"sabdlb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"sabdlt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"sadalp_z_p_z"_h, &Disassembler::Disassemble_ZdaT_PgM_ZnTb},
      {"saddlb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"saddlbt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"saddlt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"saddwb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmTb},
      {"saddwt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmTb},
      {"sbclb_z_zzz"_h, &Disassembler::DisassembleSVEAddSubCarry},
      {"sbclt_z_zzz"_h, &Disassembler::DisassembleSVEAddSubCarry},
      {"shadd_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"shrnb_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"shrnt_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"shsub_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"shsubr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sli_z_zzi"_h, &Disassembler::VisitSVEBitwiseShiftUnpredicated},
      {"sm4e_z_zz"_h, &Disassembler::Disassemble_ZdnS_ZdnS_ZmS},
      {"sm4ekey_z_zz"_h, &Disassembler::Disassemble_ZdS_ZnS_ZmS},
      {"smaxp_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sminp_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"smlalb_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"smlalb_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"smlalb_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"smlalt_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"smlalt_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"smlalt_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"smlslb_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"smlslb_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"smlslb_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"smlslt_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"smlslt_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"smlslt_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"smulh_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"smullb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"smullb_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"smullb_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"smullt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"smullt_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"smullt_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"splice_z_p_zz_con"_h, &Disassembler::Disassemble_ZdT_Pg_Zn1T_Zn2T},
      {"sqabs_z_p_z"_h, &Disassembler::Disassemble_ZdT_PgM_ZnT},
      {"sqadd_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sqcadd_z_zz"_h, &Disassembler::DisassembleSVEComplexIntAddition},
      {"sqdmlalb_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"sqdmlalb_z_zzzi_d"_h, &Disassembler::Disassemble_ZdaD_ZnS_ZmS_imm},
      {"sqdmlalb_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm},
      {"sqdmlalbt_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"sqdmlalt_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"sqdmlalt_z_zzzi_d"_h, &Disassembler::Disassemble_ZdaD_ZnS_ZmS_imm},
      {"sqdmlalt_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm},
      {"sqdmlslb_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"sqdmlslb_z_zzzi_d"_h, &Disassembler::Disassemble_ZdaD_ZnS_ZmS_imm},
      {"sqdmlslb_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm},
      {"sqdmlslbt_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"sqdmlslt_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"sqdmlslt_z_zzzi_d"_h, &Disassembler::Disassemble_ZdaD_ZnS_ZmS_imm},
      {"sqdmlslt_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm},
      {"sqdmulh_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"sqdmulh_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnD_ZmD_imm},
      {"sqdmulh_z_zzi_h"_h, &Disassembler::Disassemble_ZdH_ZnH_ZmH_imm},
      {"sqdmulh_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnS_ZmS_imm},
      {"sqdmullb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"sqdmullb_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"sqdmullb_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"sqdmullt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"sqdmullt_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"sqdmullt_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"sqneg_z_p_z"_h, &Disassembler::Disassemble_ZdT_PgM_ZnT},
      {"sqrdcmlah_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnT_ZmT_const},
      {"sqrdcmlah_z_zzzi_h"_h,
       &Disassembler::Disassemble_ZdaH_ZnH_ZmH_imm_const},
      {"sqrdcmlah_z_zzzi_s"_h,
       &Disassembler::Disassemble_ZdaS_ZnS_ZmS_imm_const},
      {"sqrdmlah_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnT_ZmT},
      {"sqrdmlah_z_zzzi_d"_h, &Disassembler::Disassemble_ZdaD_ZnD_ZmD_imm},
      {"sqrdmlah_z_zzzi_h"_h, &Disassembler::Disassemble_ZdaH_ZnH_ZmH_imm},
      {"sqrdmlah_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnS_ZmS_imm},
      {"sqrdmlsh_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnT_ZmT},
      {"sqrdmlsh_z_zzzi_d"_h, &Disassembler::Disassemble_ZdaD_ZnD_ZmD_imm},
      {"sqrdmlsh_z_zzzi_h"_h, &Disassembler::Disassemble_ZdaH_ZnH_ZmH_imm},
      {"sqrdmlsh_z_zzzi_s"_h, &Disassembler::Disassemble_ZdaS_ZnS_ZmS_imm},
      {"sqrdmulh_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"sqrdmulh_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnD_ZmD_imm},
      {"sqrdmulh_z_zzi_h"_h, &Disassembler::Disassemble_ZdH_ZnH_ZmH_imm},
      {"sqrdmulh_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnS_ZmS_imm},
      {"sqrshl_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sqrshlr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sqrshrnb_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"sqrshrnt_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"sqrshrunb_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"sqrshrunt_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"sqshl_z_p_zi"_h, &Disassembler::VisitSVEBitwiseShiftByImm_Predicated},
      {"sqshl_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sqshlr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sqshlu_z_p_zi"_h, &Disassembler::VisitSVEBitwiseShiftByImm_Predicated},
      {"sqshrnb_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"sqshrnt_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"sqshrunb_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"sqshrunt_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"sqsub_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sqsubr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sqxtnb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb},
      {"sqxtnt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb},
      {"sqxtunb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb},
      {"sqxtunt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb},
      {"srhadd_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"sri_z_zzi"_h, &Disassembler::VisitSVEBitwiseShiftUnpredicated},
      {"srshl_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"srshlr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"srshr_z_p_zi"_h, &Disassembler::VisitSVEBitwiseShiftByImm_Predicated},
      {"srsra_z_zi"_h, &Disassembler::VisitSVEBitwiseShiftUnpredicated},
      {"sshllb_z_zi"_h, &Disassembler::DisassembleSVEShiftLeftImm},
      {"sshllt_z_zi"_h, &Disassembler::DisassembleSVEShiftLeftImm},
      {"ssra_z_zi"_h, &Disassembler::VisitSVEBitwiseShiftUnpredicated},
      {"ssublb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"ssublbt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"ssublt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"ssubltb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"ssubwb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmTb},
      {"ssubwt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmTb},
      {"stnt1b_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_Pg_ZnD_Xm},
      {"stnt1b_z_p_ar_s_x32_unscaled"_h,
       &Disassembler::Disassemble_ZtS_Pg_ZnS_Xm},
      {"stnt1d_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_Pg_ZnD_Xm},
      {"stnt1h_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_Pg_ZnD_Xm},
      {"stnt1h_z_p_ar_s_x32_unscaled"_h,
       &Disassembler::Disassemble_ZtS_Pg_ZnS_Xm},
      {"stnt1w_z_p_ar_d_64_unscaled"_h,
       &Disassembler::Disassemble_ZtD_Pg_ZnD_Xm},
      {"stnt1w_z_p_ar_s_x32_unscaled"_h,
       &Disassembler::Disassemble_ZtS_Pg_ZnS_Xm},
      {"subhnb_z_zz"_h, &Disassembler::DisassembleSVEAddSubHigh},
      {"subhnt_z_zz"_h, &Disassembler::DisassembleSVEAddSubHigh},
      {"suqadd_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"tbl_z_zz_2"_h, &Disassembler::Disassemble_ZdT_Zn1T_Zn2T_ZmT},
      {"tbx_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"uaba_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnT_ZmT},
      {"uabalb_z_zzz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"uabalt_z_zzz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"uabdlb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"uabdlt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"uadalp_z_p_z"_h, &Disassembler::Disassemble_ZdaT_PgM_ZnTb},
      {"uaddlb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"uaddlt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"uaddwb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmTb},
      {"uaddwt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmTb},
      {"uhadd_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uhsub_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uhsubr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"umaxp_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uminp_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"umlalb_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"umlalb_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"umlalb_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"umlalt_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"umlalt_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"umlalt_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"umlslb_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"umlslb_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"umlslb_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"umlslt_z_zzz"_h, &Disassembler::Disassemble_ZdaT_ZnTb_ZmTb},
      {"umlslt_z_zzzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"umlslt_z_zzzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"umulh_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmT},
      {"umullb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"umullb_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"umullb_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"umullt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"umullt_z_zzi_d"_h, &Disassembler::Disassemble_ZdD_ZnS_ZmS_imm},
      {"umullt_z_zzi_s"_h, &Disassembler::Disassemble_ZdS_ZnH_ZmH_imm},
      {"uqadd_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uqrshl_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uqrshlr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uqrshrnb_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"uqrshrnt_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"uqshl_z_p_zi"_h, &Disassembler::VisitSVEBitwiseShiftByImm_Predicated},
      {"uqshl_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uqshlr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uqshrnb_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"uqshrnt_z_zi"_h, &Disassembler::DisassembleSVEShiftRightImm},
      {"uqsub_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uqsubr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"uqxtnb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb},
      {"uqxtnt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb},
      {"urecpe_z_p_z"_h, &Disassembler::Disassemble_ZdS_PgM_ZnS},
      {"urhadd_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"urshl_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"urshlr_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"urshr_z_p_zi"_h, &Disassembler::VisitSVEBitwiseShiftByImm_Predicated},
      {"ursqrte_z_p_z"_h, &Disassembler::Disassemble_ZdS_PgM_ZnS},
      {"ursra_z_zi"_h, &Disassembler::VisitSVEBitwiseShiftUnpredicated},
      {"ushllb_z_zi"_h, &Disassembler::DisassembleSVEShiftLeftImm},
      {"ushllt_z_zi"_h, &Disassembler::DisassembleSVEShiftLeftImm},
      {"usqadd_z_p_zz"_h, &Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT},
      {"usra_z_zi"_h, &Disassembler::VisitSVEBitwiseShiftUnpredicated},
      {"usublb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"usublt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnTb_ZmTb},
      {"usubwb_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmTb},
      {"usubwt_z_zz"_h, &Disassembler::Disassemble_ZdT_ZnT_ZmTb},
      {"whilege_p_p_rr"_h,
       &Disassembler::VisitSVEIntCompareScalarCountAndLimit},
      {"whilegt_p_p_rr"_h,
       &Disassembler::VisitSVEIntCompareScalarCountAndLimit},
      {"whilehi_p_p_rr"_h,
       &Disassembler::VisitSVEIntCompareScalarCountAndLimit},
      {"whilehs_p_p_rr"_h,
       &Disassembler::VisitSVEIntCompareScalarCountAndLimit},
      {"whilerw_p_rr"_h, &Disassembler::VisitSVEIntCompareScalarCountAndLimit},
      {"whilewr_p_rr"_h, &Disassembler::VisitSVEIntCompareScalarCountAndLimit},
      {"xar_z_zzi"_h, &Disassembler::Disassemble_ZdnT_ZdnT_ZmT_const},
      {"fmmla_z_zzz_s"_h, &Disassembler::Disassemble_ZdaT_ZnT_ZmT},
      {"fmmla_z_zzz_d"_h, &Disassembler::Disassemble_ZdaT_ZnT_ZmT},
      {"smmla_z_zzz"_h, &Disassembler::Disassemble_ZdaS_ZnB_ZmB},
      {"ummla_z_zzz"_h, &Disassembler::Disassemble_ZdaS_ZnB_ZmB},
      {"usmmla_z_zzz"_h, &Disassembler::Disassemble_ZdaS_ZnB_ZmB},
      {"usdot_z_zzz_s"_h, &Disassembler::Disassemble_ZdaS_ZnB_ZmB},
      {"smmla_asimdsame2_g"_h, &Disassembler::Disassemble_Vd4S_Vn16B_Vm16B},
      {"ummla_asimdsame2_g"_h, &Disassembler::Disassemble_Vd4S_Vn16B_Vm16B},
      {"usmmla_asimdsame2_g"_h, &Disassembler::Disassemble_Vd4S_Vn16B_Vm16B},
      {"ld1row_z_p_bi_u32"_h,
       &Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusImm},
      {"ld1row_z_p_br_contiguous"_h,
       &Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusScalar},
      {"ld1rod_z_p_bi_u64"_h,
       &Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusImm},
      {"ld1rod_z_p_br_contiguous"_h,
       &Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusScalar},
      {"ld1rob_z_p_bi_u8"_h,
       &Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusImm},
      {"ld1rob_z_p_br_contiguous"_h,
       &Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusScalar},
      {"ld1roh_z_p_bi_u16"_h,
       &Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusImm},
      {"ld1roh_z_p_br_contiguous"_h,
       &Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusScalar},
      {"usdot_z_zzzi_s"_h, &Disassembler::VisitSVEMulIndex},
      {"sudot_z_zzzi_s"_h, &Disassembler::VisitSVEMulIndex},
      {"usdot_asimdsame2_d"_h, &Disassembler::VisitNEON3SameExtra},
      {"addg_64_addsub_immtags"_h,
       &Disassembler::Disassemble_XdSP_XnSP_uimm6_uimm4},
      {"gmi_64g_dp_2src"_h, &Disassembler::Disassemble_Xd_XnSP_Xm},
      {"irg_64i_dp_2src"_h, &Disassembler::Disassemble_XdSP_XnSP_Xm},
      {"ldg_64loffset_ldsttags"_h, &Disassembler::DisassembleMTELoadTag},
      {"st2g_64soffset_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"st2g_64spost_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"st2g_64spre_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stgp_64_ldstpair_off"_h, &Disassembler::DisassembleMTEStoreTagPair},
      {"stgp_64_ldstpair_post"_h, &Disassembler::DisassembleMTEStoreTagPair},
      {"stgp_64_ldstpair_pre"_h, &Disassembler::DisassembleMTEStoreTagPair},
      {"stg_64soffset_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stg_64spost_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stg_64spre_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stz2g_64soffset_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stz2g_64spost_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stz2g_64spre_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stzg_64soffset_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stzg_64spost_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"stzg_64spre_ldsttags"_h, &Disassembler::DisassembleMTEStoreTag},
      {"subg_64_addsub_immtags"_h,
       &Disassembler::Disassemble_XdSP_XnSP_uimm6_uimm4},
      {"subps_64s_dp_2src"_h, &Disassembler::Disassemble_Xd_XnSP_XmSP},
      {"subp_64s_dp_2src"_h, &Disassembler::Disassemble_Xd_XnSP_XmSP},
      {"cpyen_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyern_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyewn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpye_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfen_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfern_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfewn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfe_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfmn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfmrn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfmwn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfm_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfpn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfprn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfpwn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyfp_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpymn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpymrn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpymwn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpym_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpypn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyprn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpypwn_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"cpyp_cpy_memcms"_h, &Disassembler::DisassembleCpy},
      {"seten_set_memcms"_h, &Disassembler::DisassembleSet},
      {"sete_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setgen_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setge_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setgmn_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setgm_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setgpn_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setgp_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setmn_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setm_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setpn_set_memcms"_h, &Disassembler::DisassembleSet},
      {"setp_set_memcms"_h, &Disassembler::DisassembleSet},
      {"abs_32_dp_1src"_h, &Disassembler::VisitDataProcessing1Source},
      {"abs_64_dp_1src"_h, &Disassembler::VisitDataProcessing1Source},
      {"cnt_32_dp_1src"_h, &Disassembler::VisitDataProcessing1Source},
      {"cnt_64_dp_1src"_h, &Disassembler::VisitDataProcessing1Source},
      {"ctz_32_dp_1src"_h, &Disassembler::VisitDataProcessing1Source},
      {"ctz_64_dp_1src"_h, &Disassembler::VisitDataProcessing1Source},
      {"smax_32_dp_2src"_h, &Disassembler::VisitDataProcessing2Source},
      {"smax_64_dp_2src"_h, &Disassembler::VisitDataProcessing2Source},
      {"smin_32_dp_2src"_h, &Disassembler::VisitDataProcessing2Source},
      {"smin_64_dp_2src"_h, &Disassembler::VisitDataProcessing2Source},
      {"umax_32_dp_2src"_h, &Disassembler::VisitDataProcessing2Source},
      {"umax_64_dp_2src"_h, &Disassembler::VisitDataProcessing2Source},
      {"umin_32_dp_2src"_h, &Disassembler::VisitDataProcessing2Source},
      {"umin_64_dp_2src"_h, &Disassembler::VisitDataProcessing2Source},
      {"smax_32_minmax_imm"_h, &Disassembler::DisassembleMinMaxImm},
      {"smax_64_minmax_imm"_h, &Disassembler::DisassembleMinMaxImm},
      {"smin_32_minmax_imm"_h, &Disassembler::DisassembleMinMaxImm},
      {"smin_64_minmax_imm"_h, &Disassembler::DisassembleMinMaxImm},
      {"umax_32u_minmax_imm"_h, &Disassembler::DisassembleMinMaxImm},
      {"umax_64u_minmax_imm"_h, &Disassembler::DisassembleMinMaxImm},
      {"umin_32u_minmax_imm"_h, &Disassembler::DisassembleMinMaxImm},
      {"umin_64u_minmax_imm"_h, &Disassembler::DisassembleMinMaxImm},
      {"bcax_vvv16_crypto4"_h, &Disassembler::DisassembleNEON4Same},
      {"eor3_vvv16_crypto4"_h, &Disassembler::DisassembleNEON4Same},
      {"xar_vvv2_crypto3_imm6"_h, &Disassembler::DisassembleNEONXar},
      {"rax1_vvv2_cryptosha512_3"_h, &Disassembler::DisassembleNEONRax1},
      {"sha512h2_qqv_cryptosha512_3"_h, &Disassembler::DisassembleSHA512},
      {"sha512h_qqv_cryptosha512_3"_h, &Disassembler::DisassembleSHA512},
      {"sha512su0_vv2_cryptosha512_2"_h, &Disassembler::DisassembleSHA512},
      {"sha512su1_vvv2_cryptosha512_3"_h, &Disassembler::DisassembleSHA512},
  };
  return &form_to_visitor;
}  // NOLINT(readability/fn_size)

Disassembler::Disassembler() {
  buffer_size_ = 256;
  buffer_ = reinterpret_cast<char *>(malloc(buffer_size_));
  buffer_pos_ = 0;
  own_buffer_ = true;
  code_address_offset_ = 0;
}

Disassembler::Disassembler(char *text_buffer, int buffer_size) {
  buffer_size_ = buffer_size;
  buffer_ = text_buffer;
  buffer_pos_ = 0;
  own_buffer_ = false;
  code_address_offset_ = 0;
}

Disassembler::~Disassembler() {
  if (own_buffer_) {
    free(buffer_);
  }
}

char *Disassembler::GetOutput() { return buffer_; }

void Disassembler::VisitAddSubImmediate(const Instruction *instr) {
  bool rd_is_zr = RdIsZROrSP(instr);
  bool stack_op =
      (rd_is_zr || RnIsZROrSP(instr)) && (instr->GetImmAddSub() == 0) ? true
                                                                      : false;
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Rds, 'Rns, 'IAddSub";
  const char *form_cmp = "'Rns, 'IAddSub";
  const char *form_mov = "'Rds, 'Rns";

  switch (form_hash_) {
    case "add_32_addsub_imm"_h:
    case "add_64_addsub_imm"_h:
      if (stack_op) {
        mnemonic = "mov";
        form = form_mov;
      }
      break;
    case "adds_32s_addsub_imm"_h:
    case "adds_64s_addsub_imm"_h:
      if (rd_is_zr) {
        mnemonic = "cmn";
        form = form_cmp;
      }
      break;
    case "subs_32s_addsub_imm"_h:
    case "subs_64s_addsub_imm"_h:
      if (rd_is_zr) {
        mnemonic = "cmp";
        form = form_cmp;
      }
      break;
  }
  Format(instr, mnemonic, form);
}


void Disassembler::VisitAddSubShifted(const Instruction *instr) {
  bool rd_is_zr = RdIsZROrSP(instr);
  bool rn_is_zr = RnIsZROrSP(instr);
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Rd, 'Rn, 'Rm'NDP";
  const char *form_cmp = "'Rn, 'Rm'NDP";
  const char *form_neg = "'Rd, 'Rm'NDP";

  if (instr->GetShiftDP() == ROR) {
    // Add/sub/adds/subs don't allow ROR as a shift mode.
    VisitUnallocated(instr);
    return;
  }

  switch (form_hash_) {
    case "adds_32_addsub_shift"_h:
    case "adds_64_addsub_shift"_h:
      if (rd_is_zr) {
        mnemonic = "cmn";
        form = form_cmp;
      }
      break;
    case "sub_32_addsub_shift"_h:
    case "sub_64_addsub_shift"_h:
      if (rn_is_zr) {
        mnemonic = "neg";
        form = form_neg;
      }
      break;
    case "subs_32_addsub_shift"_h:
    case "subs_64_addsub_shift"_h:
      if (rd_is_zr) {
        mnemonic = "cmp";
        form = form_cmp;
      } else if (rn_is_zr) {
        mnemonic = "negs";
        form = form_neg;
      }
  }
  Format(instr, mnemonic, form);
}


void Disassembler::VisitAddSubExtended(const Instruction *instr) {
  bool rd_is_zr = RdIsZROrSP(instr);
  const char *mnemonic = "";
  Extend mode = static_cast<Extend>(instr->GetExtendMode());
  const char *form = ((mode == UXTX) || (mode == SXTX)) ? "'Rds, 'Rns, 'Xm'Ext"
                                                        : "'Rds, 'Rns, 'Wm'Ext";
  const char *form_cmp =
      ((mode == UXTX) || (mode == SXTX)) ? "'Rns, 'Xm'Ext" : "'Rns, 'Wm'Ext";

  switch (instr->Mask(AddSubExtendedMask)) {
    case ADD_w_ext:
    case ADD_x_ext:
      mnemonic = "add";
      break;
    case ADDS_w_ext:
    case ADDS_x_ext: {
      mnemonic = "adds";
      if (rd_is_zr) {
        mnemonic = "cmn";
        form = form_cmp;
      }
      break;
    }
    case SUB_w_ext:
    case SUB_x_ext:
      mnemonic = "sub";
      break;
    case SUBS_w_ext:
    case SUBS_x_ext: {
      mnemonic = "subs";
      if (rd_is_zr) {
        mnemonic = "cmp";
        form = form_cmp;
      }
      break;
    }
    default:
      VIXL_UNREACHABLE();
  }
  Format(instr, mnemonic, form);
}


void Disassembler::VisitAddSubWithCarry(const Instruction *instr) {
  bool rn_is_zr = RnIsZROrSP(instr);
  const char *mnemonic = "";
  const char *form = "'Rd, 'Rn, 'Rm";
  const char *form_neg = "'Rd, 'Rm";

  switch (instr->Mask(AddSubWithCarryMask)) {
    case ADC_w:
    case ADC_x:
      mnemonic = "adc";
      break;
    case ADCS_w:
    case ADCS_x:
      mnemonic = "adcs";
      break;
    case SBC_w:
    case SBC_x: {
      mnemonic = "sbc";
      if (rn_is_zr) {
        mnemonic = "ngc";
        form = form_neg;
      }
      break;
    }
    case SBCS_w:
    case SBCS_x: {
      mnemonic = "sbcs";
      if (rn_is_zr) {
        mnemonic = "ngcs";
        form = form_neg;
      }
      break;
    }
    default:
      VIXL_UNREACHABLE();
  }
  Format(instr, mnemonic, form);
}


void Disassembler::VisitRotateRightIntoFlags(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Xn, 'IRr, 'INzcv");
}


void Disassembler::VisitEvaluateIntoFlags(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Wn");
}


void Disassembler::VisitLogicalImmediate(const Instruction *instr) {
  bool rd_is_zr = RdIsZROrSP(instr);
  bool rn_is_zr = RnIsZROrSP(instr);
  const char *mnemonic = "";
  const char *form = "'Rds, 'Rn, 'ITri";

  if (instr->GetImmLogical() == 0) {
    // The immediate encoded in the instruction is not in the expected format.
    Format(instr, "unallocated", "(LogicalImmediate)");
    return;
  }

  switch (instr->Mask(LogicalImmediateMask)) {
    case AND_w_imm:
    case AND_x_imm:
      mnemonic = "and";
      break;
    case ORR_w_imm:
    case ORR_x_imm: {
      mnemonic = "orr";
      unsigned reg_size =
          (instr->GetSixtyFourBits() == 1) ? kXRegSize : kWRegSize;
      if (rn_is_zr && !IsMovzMovnImm(reg_size, instr->GetImmLogical())) {
        mnemonic = "mov";
        form = "'Rds, 'ITri";
      }
      break;
    }
    case EOR_w_imm:
    case EOR_x_imm:
      mnemonic = "eor";
      break;
    case ANDS_w_imm:
    case ANDS_x_imm: {
      mnemonic = "ands";
      if (rd_is_zr) {
        mnemonic = "tst";
        form = "'Rn, 'ITri";
      }
      break;
    }
    default:
      VIXL_UNREACHABLE();
  }
  Format(instr, mnemonic, form);
}


bool Disassembler::IsMovzMovnImm(unsigned reg_size, uint64_t value) {
  VIXL_ASSERT((reg_size == kXRegSize) ||
              ((reg_size == kWRegSize) && (value <= 0xffffffff)));

  // Test for movz: 16 bits set at positions 0, 16, 32 or 48.
  if (((value & UINT64_C(0xffffffffffff0000)) == 0) ||
      ((value & UINT64_C(0xffffffff0000ffff)) == 0) ||
      ((value & UINT64_C(0xffff0000ffffffff)) == 0) ||
      ((value & UINT64_C(0x0000ffffffffffff)) == 0)) {
    return true;
  }

  // Test for movn: NOT(16 bits set at positions 0, 16, 32 or 48).
  if ((reg_size == kXRegSize) &&
      (((~value & UINT64_C(0xffffffffffff0000)) == 0) ||
       ((~value & UINT64_C(0xffffffff0000ffff)) == 0) ||
       ((~value & UINT64_C(0xffff0000ffffffff)) == 0) ||
       ((~value & UINT64_C(0x0000ffffffffffff)) == 0))) {
    return true;
  }
  if ((reg_size == kWRegSize) && (((value & 0xffff0000) == 0xffff0000) ||
                                  ((value & 0x0000ffff) == 0x0000ffff))) {
    return true;
  }
  return false;
}


void Disassembler::VisitLogicalShifted(const Instruction *instr) {
  bool rd_is_zr = RdIsZROrSP(instr);
  bool rn_is_zr = RnIsZROrSP(instr);
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Rd, 'Rn, 'Rm'NLo";

  switch (form_hash_) {
    case "ands_32_log_shift"_h:
    case "ands_64_log_shift"_h:
      if (rd_is_zr) {
        mnemonic = "tst";
        form = "'Rn, 'Rm'NLo";
      }
      break;
    case "orr_32_log_shift"_h:
    case "orr_64_log_shift"_h:
      if (rn_is_zr && (instr->GetImmDPShift() == 0) &&
          (instr->GetShiftDP() == LSL)) {
        mnemonic = "mov";
        form = "'Rd, 'Rm";
      }
      break;
    case "orn_32_log_shift"_h:
    case "orn_64_log_shift"_h:
      if (rn_is_zr) {
        mnemonic = "mvn";
        form = "'Rd, 'Rm'NLo";
      }
      break;
  }

  Format(instr, mnemonic, form);
}


void Disassembler::VisitConditionalCompareRegister(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Rn, 'Rm, 'INzcv, 'Cond");
}


void Disassembler::VisitConditionalCompareImmediate(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Rn, 'IP, 'INzcv, 'Cond");
}


void Disassembler::VisitConditionalSelect(const Instruction *instr) {
  bool rnm_is_zr = (RnIsZROrSP(instr) && RmIsZROrSP(instr));
  bool rn_is_rm = (instr->GetRn() == instr->GetRm());
  const char *mnemonic = "";
  const char *form = "'Rd, 'Rn, 'Rm, 'Cond";
  const char *form_test = "'Rd, 'CInv";
  const char *form_update = "'Rd, 'Rn, 'CInv";

  Condition cond = static_cast<Condition>(instr->GetCondition());
  bool invertible_cond = (cond != al) && (cond != nv);

  switch (instr->Mask(ConditionalSelectMask)) {
    case CSEL_w:
    case CSEL_x:
      mnemonic = "csel";
      break;
    case CSINC_w:
    case CSINC_x: {
      mnemonic = "csinc";
      if (rnm_is_zr && invertible_cond) {
        mnemonic = "cset";
        form = form_test;
      } else if (rn_is_rm && invertible_cond) {
        mnemonic = "cinc";
        form = form_update;
      }
      break;
    }
    case CSINV_w:
    case CSINV_x: {
      mnemonic = "csinv";
      if (rnm_is_zr && invertible_cond) {
        mnemonic = "csetm";
        form = form_test;
      } else if (rn_is_rm && invertible_cond) {
        mnemonic = "cinv";
        form = form_update;
      }
      break;
    }
    case CSNEG_w:
    case CSNEG_x: {
      mnemonic = "csneg";
      if (rn_is_rm && invertible_cond) {
        mnemonic = "cneg";
        form = form_update;
      }
      break;
    }
    default:
      VIXL_UNREACHABLE();
  }
  Format(instr, mnemonic, form);
}


void Disassembler::VisitBitfield(const Instruction *instr) {
  unsigned s = instr->GetImmS();
  unsigned r = instr->GetImmR();
  unsigned rd_size_minus_1 =
      ((instr->GetSixtyFourBits() == 1) ? kXRegSize : kWRegSize) - 1;
  const char *mnemonic = "";
  const char *form = "";
  const char *form_shift_right = "'Rd, 'Rn, 'IBr";
  const char *form_extend = "'Rd, 'Wn";
  const char *form_bfiz = "'Rd, 'Rn, 'IBZ-r, 'IBs+1";
  const char *form_bfc = "'Rd, 'IBZ-r, 'IBs+1";
  const char *form_bfx = "'Rd, 'Rn, 'IBr, 'IBs-r+1";
  const char *form_lsl = "'Rd, 'Rn, 'IBZ-r";

  if (instr->GetSixtyFourBits() != instr->GetBitN()) {
    VisitUnallocated(instr);
    return;
  }

  if ((instr->GetSixtyFourBits() == 0) && ((s > 31) || (r > 31))) {
    VisitUnallocated(instr);
    return;
  }

  switch (instr->Mask(BitfieldMask)) {
    case SBFM_w:
    case SBFM_x: {
      mnemonic = "sbfx";
      form = form_bfx;
      if (r == 0) {
        form = form_extend;
        if (s == 7) {
          mnemonic = "sxtb";
        } else if (s == 15) {
          mnemonic = "sxth";
        } else if ((s == 31) && (instr->GetSixtyFourBits() == 1)) {
          mnemonic = "sxtw";
        } else {
          form = form_bfx;
        }
      } else if (s == rd_size_minus_1) {
        mnemonic = "asr";
        form = form_shift_right;
      } else if (s < r) {
        mnemonic = "sbfiz";
        form = form_bfiz;
      }
      break;
    }
    case UBFM_w:
    case UBFM_x: {
      mnemonic = "ubfx";
      form = form_bfx;
      if (r == 0) {
        form = form_extend;
        if (s == 7) {
          mnemonic = "uxtb";
        } else if (s == 15) {
          mnemonic = "uxth";
        } else {
          form = form_bfx;
        }
      }
      if (s == rd_size_minus_1) {
        mnemonic = "lsr";
        form = form_shift_right;
      } else if (r == s + 1) {
        mnemonic = "lsl";
        form = form_lsl;
      } else if (s < r) {
        mnemonic = "ubfiz";
        form = form_bfiz;
      }
      break;
    }
    case BFM_w:
    case BFM_x: {
      mnemonic = "bfxil";
      form = form_bfx;
      if (s < r) {
        if (instr->GetRn() == kZeroRegCode) {
          mnemonic = "bfc";
          form = form_bfc;
        } else {
          mnemonic = "bfi";
          form = form_bfiz;
        }
      }
    }
  }
  Format(instr, mnemonic, form);
}


void Disassembler::VisitExtract(const Instruction *instr) {
  const char *mnemonic = "";
  const char *form = "'Rd, 'Rn, 'Rm, 'IExtract";

  switch (instr->Mask(ExtractMask)) {
    case EXTR_w:
    case EXTR_x: {
      if (instr->GetRn() == instr->GetRm()) {
        mnemonic = "ror";
        form = "'Rd, 'Rn, 'IExtract";
      } else {
        mnemonic = "extr";
      }
      break;
    }
    default:
      VIXL_UNREACHABLE();
  }
  Format(instr, mnemonic, form);
}


void Disassembler::VisitPCRelAddressing(const Instruction *instr) {
  switch (instr->Mask(PCRelAddressingMask)) {
    case ADR:
      Format(instr, "adr", "'Xd, 'AddrPCRelByte");
      break;
    case ADRP:
      Format(instr, "adrp", "'Xd, 'AddrPCRelPage");
      break;
    default:
      Format(instr, "unimplemented", "(PCRelAddressing)");
  }
}


void Disassembler::VisitConditionalBranch(const Instruction *instr) {
  // We can't use the mnemonic directly here, as there's no space between it and
  // the condition. Assert that we have the correct mnemonic, then use "b"
  // explicitly for formatting the output.
  VIXL_ASSERT(form_hash_ == "b_only_condbranch"_h);
  Format(instr, "b.'CBrn", "'TImmCond");
}


void Disassembler::VisitUnconditionalBranchToRegister(
    const Instruction *instr) {
  const char *form = "'Xn";

  switch (form_hash_) {
    case "ret_64r_branch_reg"_h:
      if (instr->GetRn() == kLinkRegCode) {
        form = "";
      }
      break;
    case "retaa_64e_branch_reg"_h:
    case "retab_64e_branch_reg"_h:
      form = "";
      break;
    case "braa_64p_branch_reg"_h:
    case "brab_64p_branch_reg"_h:
    case "blraa_64p_branch_reg"_h:
    case "blrab_64p_branch_reg"_h:
      form = "'Xn, 'Xds";
      break;
  }

  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitUnconditionalBranch(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'TImmUncn");
}


void Disassembler::VisitDataProcessing1Source(const Instruction *instr) {
  const char *form = "'Rd, 'Rn";

  switch (form_hash_) {
    case "pacia_64p_dp_1src"_h:
    case "pacda_64p_dp_1src"_h:
    case "autia_64p_dp_1src"_h:
    case "autda_64p_dp_1src"_h:
    case "pacib_64p_dp_1src"_h:
    case "pacdb_64p_dp_1src"_h:
    case "autib_64p_dp_1src"_h:
    case "autdb_64p_dp_1src"_h:
      form = "'Xd, 'Xns";
      break;
    case "paciza_64z_dp_1src"_h:
    case "pacdza_64z_dp_1src"_h:
    case "autiza_64z_dp_1src"_h:
    case "autdza_64z_dp_1src"_h:
    case "pacizb_64z_dp_1src"_h:
    case "pacdzb_64z_dp_1src"_h:
    case "autizb_64z_dp_1src"_h:
    case "autdzb_64z_dp_1src"_h:
    case "xpacd_64z_dp_1src"_h:
    case "xpaci_64z_dp_1src"_h:
      form = "'Xd";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitDataProcessing2Source(const Instruction *instr) {
  std::string mnemonic = mnemonic_;
  const char *form = "'Rd, 'Rn, 'Rm";

  switch (form_hash_) {
    case "asrv_32_dp_2src"_h:
    case "asrv_64_dp_2src"_h:
    case "lslv_32_dp_2src"_h:
    case "lslv_64_dp_2src"_h:
    case "lsrv_32_dp_2src"_h:
    case "lsrv_64_dp_2src"_h:
    case "rorv_32_dp_2src"_h:
    case "rorv_64_dp_2src"_h:
      // Drop the last 'v' character.
      VIXL_ASSERT(mnemonic[3] == 'v');
      mnemonic.pop_back();
      break;
    case "pacga_64p_dp_2src"_h:
      form = "'Xd, 'Xn, 'Xms";
      break;
    case "crc32x_64c_dp_2src"_h:
    case "crc32cx_64c_dp_2src"_h:
      form = "'Wd, 'Wn, 'Xm";
      break;
  }
  Format(instr, mnemonic.c_str(), form);
}


void Disassembler::VisitDataProcessing3Source(const Instruction *instr) {
  bool ra_is_zr = RaIsZROrSP(instr);
  const char *mnemonic = "";
  const char *form = "'Xd, 'Wn, 'Wm, 'Xa";
  const char *form_rrr = "'Rd, 'Rn, 'Rm";
  const char *form_rrrr = "'Rd, 'Rn, 'Rm, 'Ra";
  const char *form_xww = "'Xd, 'Wn, 'Wm";
  const char *form_xxx = "'Xd, 'Xn, 'Xm";

  switch (instr->Mask(DataProcessing3SourceMask)) {
    case MADD_w:
    case MADD_x: {
      mnemonic = "madd";
      form = form_rrrr;
      if (ra_is_zr) {
        mnemonic = "mul";
        form = form_rrr;
      }
      break;
    }
    case MSUB_w:
    case MSUB_x: {
      mnemonic = "msub";
      form = form_rrrr;
      if (ra_is_zr) {
        mnemonic = "mneg";
        form = form_rrr;
      }
      break;
    }
    case SMADDL_x: {
      mnemonic = "smaddl";
      if (ra_is_zr) {
        mnemonic = "smull";
        form = form_xww;
      }
      break;
    }
    case SMSUBL_x: {
      mnemonic = "smsubl";
      if (ra_is_zr) {
        mnemonic = "smnegl";
        form = form_xww;
      }
      break;
    }
    case UMADDL_x: {
      mnemonic = "umaddl";
      if (ra_is_zr) {
        mnemonic = "umull";
        form = form_xww;
      }
      break;
    }
    case UMSUBL_x: {
      mnemonic = "umsubl";
      if (ra_is_zr) {
        mnemonic = "umnegl";
        form = form_xww;
      }
      break;
    }
    case SMULH_x: {
      mnemonic = "smulh";
      form = form_xxx;
      break;
    }
    case UMULH_x: {
      mnemonic = "umulh";
      form = form_xxx;
      break;
    }
    default:
      VIXL_UNREACHABLE();
  }
  Format(instr, mnemonic, form);
}

void Disassembler::DisassembleMinMaxImm(const Instruction *instr) {
  const char *suffix = (instr->ExtractBit(18) == 0) ? "'s1710" : "'u1710";
  FormatWithDecodedMnemonic(instr, "'Rd, 'Rn, #", suffix);
}

void Disassembler::VisitCompareBranch(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Rt, 'TImmCmpa");
}


void Disassembler::VisitTestBranch(const Instruction *instr) {
  // If the top bit of the immediate is clear, the tested register is
  // disassembled as Wt, otherwise Xt. As the top bit of the immediate is
  // encoded in bit 31 of the instruction, we can reuse the Rt form, which
  // uses bit 31 (normally "sf") to choose the register size.
  FormatWithDecodedMnemonic(instr, "'Rt, 'It, 'TImmTest");
}


void Disassembler::VisitMoveWideImmediate(const Instruction *instr) {
  const char *mnemonic = "";
  const char *form = "'Rd, 'IMoveImm";

  // Print the shift separately for movk, to make it clear which half word will
  // be overwritten. Movn and movz print the computed immediate, which includes
  // shift calculation.
  switch (instr->Mask(MoveWideImmediateMask)) {
    case MOVN_w:
    case MOVN_x:
      if ((instr->GetImmMoveWide()) || (instr->GetShiftMoveWide() == 0)) {
        if ((instr->GetSixtyFourBits() == 0) &&
            (instr->GetImmMoveWide() == 0xffff)) {
          mnemonic = "movn";
        } else {
          mnemonic = "mov";
          form = "'Rd, 'IMoveNeg";
        }
      } else {
        mnemonic = "movn";
      }
      break;
    case MOVZ_w:
    case MOVZ_x:
      if ((instr->GetImmMoveWide()) || (instr->GetShiftMoveWide() == 0))
        mnemonic = "mov";
      else
        mnemonic = "movz";
      break;
    case MOVK_w:
    case MOVK_x:
      mnemonic = "movk";
      form = "'Rd, 'IMoveLSL";
      break;
    default:
      VIXL_UNREACHABLE();
  }
  Format(instr, mnemonic, form);
}


#define LOAD_STORE_LIST(V) \
  V(STRB_w, "'Wt")         \
  V(STRH_w, "'Wt")         \
  V(STR_w, "'Wt")          \
  V(STR_x, "'Xt")          \
  V(LDRB_w, "'Wt")         \
  V(LDRH_w, "'Wt")         \
  V(LDR_w, "'Wt")          \
  V(LDR_x, "'Xt")          \
  V(LDRSB_x, "'Xt")        \
  V(LDRSH_x, "'Xt")        \
  V(LDRSW_x, "'Xt")        \
  V(LDRSB_w, "'Wt")        \
  V(LDRSH_w, "'Wt")        \
  V(STR_b, "'Bt")          \
  V(STR_h, "'Ht")          \
  V(STR_s, "'St")          \
  V(STR_d, "'Dt")          \
  V(LDR_b, "'Bt")          \
  V(LDR_h, "'Ht")          \
  V(LDR_s, "'St")          \
  V(LDR_d, "'Dt")          \
  V(STR_q, "'Qt")          \
  V(LDR_q, "'Qt")

void Disassembler::VisitLoadStorePreIndex(const Instruction *instr) {
  const char *form = "(LoadStorePreIndex)";
  const char *suffix = ", ['Xns'ILSi]!";

  switch (instr->Mask(LoadStorePreIndexMask)) {
#define LS_PREINDEX(A, B) \
  case A##_pre:           \
    form = B;             \
    break;
    LOAD_STORE_LIST(LS_PREINDEX)
#undef LS_PREINDEX
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}


void Disassembler::VisitLoadStorePostIndex(const Instruction *instr) {
  const char *form = "(LoadStorePostIndex)";
  const char *suffix = ", ['Xns]'ILSi";

  switch (instr->Mask(LoadStorePostIndexMask)) {
#define LS_POSTINDEX(A, B) \
  case A##_post:           \
    form = B;              \
    break;
    LOAD_STORE_LIST(LS_POSTINDEX)
#undef LS_POSTINDEX
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}


void Disassembler::VisitLoadStoreUnsignedOffset(const Instruction *instr) {
  const char *form = "(LoadStoreUnsignedOffset)";
  const char *suffix = ", ['Xns'ILU]";

  switch (instr->Mask(LoadStoreUnsignedOffsetMask)) {
#define LS_UNSIGNEDOFFSET(A, B) \
  case A##_unsigned:            \
    form = B;                   \
    break;
    LOAD_STORE_LIST(LS_UNSIGNEDOFFSET)
#undef LS_UNSIGNEDOFFSET
    case PRFM_unsigned:
      form = "'prefOp";
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}


void Disassembler::VisitLoadStoreRCpcUnscaledOffset(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Wt, ['Xns'ILS]";
  const char *form_x = "'Xt, ['Xns'ILS]";

  switch (form_hash_) {
    case "ldapursb_64_ldapstl_unscaled"_h:
    case "ldapursh_64_ldapstl_unscaled"_h:
    case "ldapursw_64_ldapstl_unscaled"_h:
    case "ldapur_64_ldapstl_unscaled"_h:
    case "stlur_64_ldapstl_unscaled"_h:
      form = form_x;
      break;
  }

  Format(instr, mnemonic, form);
}


void Disassembler::VisitLoadStoreRegisterOffset(const Instruction *instr) {
  const char *form = "(LoadStoreRegisterOffset)";
  const char *suffix = ", ['Xns, 'Offsetreg]";

  switch (instr->Mask(LoadStoreRegisterOffsetMask)) {
#define LS_REGISTEROFFSET(A, B) \
  case A##_reg:                 \
    form = B;                   \
    break;
    LOAD_STORE_LIST(LS_REGISTEROFFSET)
#undef LS_REGISTEROFFSET
    case PRFM_reg:
      form = "'prefOp";
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}


void Disassembler::VisitLoadStoreUnscaledOffset(const Instruction *instr) {
  const char *form = "'Wt";
  const char *suffix = ", ['Xns'ILS]";

  switch (form_hash_) {
    case "ldur_64_ldst_unscaled"_h:
    case "ldursb_64_ldst_unscaled"_h:
    case "ldursh_64_ldst_unscaled"_h:
    case "ldursw_64_ldst_unscaled"_h:
    case "stur_64_ldst_unscaled"_h:
      form = "'Xt";
      break;
    case "ldur_b_ldst_unscaled"_h:
    case "stur_b_ldst_unscaled"_h:
      form = "'Bt";
      break;
    case "ldur_h_ldst_unscaled"_h:
    case "stur_h_ldst_unscaled"_h:
      form = "'Ht";
      break;
    case "ldur_s_ldst_unscaled"_h:
    case "stur_s_ldst_unscaled"_h:
      form = "'St";
      break;
    case "ldur_d_ldst_unscaled"_h:
    case "stur_d_ldst_unscaled"_h:
      form = "'Dt";
      break;
    case "ldur_q_ldst_unscaled"_h:
    case "stur_q_ldst_unscaled"_h:
      form = "'Qt";
      break;
    case "prfum_p_ldst_unscaled"_h:
      form = "'prefOp";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}


void Disassembler::VisitLoadLiteral(const Instruction *instr) {
  const char *form = "'Wt";
  const char *suffix = ", 'ILLiteral 'LValue";

  switch (form_hash_) {
    case "ldr_64_loadlit"_h:
    case "ldrsw_64_loadlit"_h:
      form = "'Xt";
      break;
    case "ldr_s_loadlit"_h:
      form = "'St";
      break;
    case "ldr_d_loadlit"_h:
      form = "'Dt";
      break;
    case "ldr_q_loadlit"_h:
      form = "'Qt";
      break;
    case "prfm_p_loadlit"_h:
      form = "'prefOp";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}


#define LOAD_STORE_PAIR_LIST(V) \
  V(STP_w, "'Wt, 'Wt2", "2")    \
  V(LDP_w, "'Wt, 'Wt2", "2")    \
  V(LDPSW_x, "'Xt, 'Xt2", "2")  \
  V(STP_x, "'Xt, 'Xt2", "3")    \
  V(LDP_x, "'Xt, 'Xt2", "3")    \
  V(STP_s, "'St, 'St2", "2")    \
  V(LDP_s, "'St, 'St2", "2")    \
  V(STP_d, "'Dt, 'Dt2", "3")    \
  V(LDP_d, "'Dt, 'Dt2", "3")    \
  V(LDP_q, "'Qt, 'Qt2", "4")    \
  V(STP_q, "'Qt, 'Qt2", "4")

void Disassembler::VisitLoadStorePairPostIndex(const Instruction *instr) {
  const char *form = "(LoadStorePairPostIndex)";

  switch (instr->Mask(LoadStorePairPostIndexMask)) {
#define LSP_POSTINDEX(A, B, C)     \
  case A##_post:                   \
    form = B ", ['Xns]'ILP" C "i"; \
    break;
    LOAD_STORE_PAIR_LIST(LSP_POSTINDEX)
#undef LSP_POSTINDEX
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitLoadStorePairPreIndex(const Instruction *instr) {
  const char *form = "(LoadStorePairPreIndex)";

  switch (instr->Mask(LoadStorePairPreIndexMask)) {
#define LSP_PREINDEX(A, B, C)       \
  case A##_pre:                     \
    form = B ", ['Xns'ILP" C "i]!"; \
    break;
    LOAD_STORE_PAIR_LIST(LSP_PREINDEX)
#undef LSP_PREINDEX
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitLoadStorePairOffset(const Instruction *instr) {
  const char *form = "(LoadStorePairOffset)";

  switch (instr->Mask(LoadStorePairOffsetMask)) {
#define LSP_OFFSET(A, B, C)       \
  case A##_off:                   \
    form = B ", ['Xns'ILP" C "]"; \
    break;
    LOAD_STORE_PAIR_LIST(LSP_OFFSET)
#undef LSP_OFFSET
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitLoadStorePairNonTemporal(const Instruction *instr) {
  const char *form = "'Wt, 'Wt2, ['Xns'ILP2]";

  switch (form_hash_) {
    case "ldnp_64_ldstnapair_offs"_h:
    case "stnp_64_ldstnapair_offs"_h:
      form = "'Xt, 'Xt2, ['Xns'ILP3]";
      break;
    case "ldnp_s_ldstnapair_offs"_h:
    case "stnp_s_ldstnapair_offs"_h:
      form = "'St, 'St2, ['Xns'ILP2]";
      break;
    case "ldnp_d_ldstnapair_offs"_h:
    case "stnp_d_ldstnapair_offs"_h:
      form = "'Dt, 'Dt2, ['Xns'ILP3]";
      break;
    case "ldnp_q_ldstnapair_offs"_h:
    case "stnp_q_ldstnapair_offs"_h:
      form = "'Qt, 'Qt2, ['Xns'ILP4]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

// clang-format off
#define LOAD_STORE_EXCLUSIVE_LIST(V)   \
  V(STXRB_w,  "'Ws, 'Wt")              \
  V(STXRH_w,  "'Ws, 'Wt")              \
  V(STXR_w,   "'Ws, 'Wt")              \
  V(STXR_x,   "'Ws, 'Xt")              \
  V(LDXR_x,   "'Xt")                   \
  V(STXP_w,   "'Ws, 'Wt, 'Wt2")        \
  V(STXP_x,   "'Ws, 'Xt, 'Xt2")        \
  V(LDXP_w,   "'Wt, 'Wt2")             \
  V(LDXP_x,   "'Xt, 'Xt2")             \
  V(STLXRB_w, "'Ws, 'Wt")              \
  V(STLXRH_w, "'Ws, 'Wt")              \
  V(STLXR_w,  "'Ws, 'Wt")              \
  V(STLXR_x,  "'Ws, 'Xt")              \
  V(LDAXR_x,  "'Xt")                   \
  V(STLXP_w,  "'Ws, 'Wt, 'Wt2")        \
  V(STLXP_x,  "'Ws, 'Xt, 'Xt2")        \
  V(LDAXP_w,  "'Wt, 'Wt2")             \
  V(LDAXP_x,  "'Xt, 'Xt2")             \
  V(STLR_x,   "'Xt")                   \
  V(LDAR_x,   "'Xt")                   \
  V(STLLR_x,  "'Xt")                   \
  V(LDLAR_x,  "'Xt")                   \
  V(CAS_w,    "'Ws, 'Wt")              \
  V(CAS_x,    "'Xs, 'Xt")              \
  V(CASA_w,   "'Ws, 'Wt")              \
  V(CASA_x,   "'Xs, 'Xt")              \
  V(CASL_w,   "'Ws, 'Wt")              \
  V(CASL_x,   "'Xs, 'Xt")              \
  V(CASAL_w,  "'Ws, 'Wt")              \
  V(CASAL_x,  "'Xs, 'Xt")              \
  V(CASB,     "'Ws, 'Wt")              \
  V(CASAB,    "'Ws, 'Wt")              \
  V(CASLB,    "'Ws, 'Wt")              \
  V(CASALB,   "'Ws, 'Wt")              \
  V(CASH,     "'Ws, 'Wt")              \
  V(CASAH,    "'Ws, 'Wt")              \
  V(CASLH,    "'Ws, 'Wt")              \
  V(CASALH,   "'Ws, 'Wt")              \
  V(CASP_w,   "'Ws, 'Ws+, 'Wt, 'Wt+")  \
  V(CASP_x,   "'Xs, 'Xs+, 'Xt, 'Xt+")  \
  V(CASPA_w,  "'Ws, 'Ws+, 'Wt, 'Wt+")  \
  V(CASPA_x,  "'Xs, 'Xs+, 'Xt, 'Xt+")  \
  V(CASPL_w,  "'Ws, 'Ws+, 'Wt, 'Wt+")  \
  V(CASPL_x,  "'Xs, 'Xs+, 'Xt, 'Xt+")  \
  V(CASPAL_w, "'Ws, 'Ws+, 'Wt, 'Wt+")  \
  V(CASPAL_x, "'Xs, 'Xs+, 'Xt, 'Xt+")
// clang-format on


void Disassembler::VisitLoadStoreExclusive(const Instruction *instr) {
  const char *form = "'Wt";
  const char *suffix = ", ['Xns]";

  switch (instr->Mask(LoadStoreExclusiveMask)) {
#define LSX(A, B) \
  case A:         \
    form = B;     \
    break;
    LOAD_STORE_EXCLUSIVE_LIST(LSX)
#undef LSX
  }

  switch (instr->Mask(LoadStoreExclusiveMask)) {
    case CASP_w:
    case CASP_x:
    case CASPA_w:
    case CASPA_x:
    case CASPL_w:
    case CASPL_x:
    case CASPAL_w:
    case CASPAL_x:
      if ((instr->GetRs() % 2 == 1) || (instr->GetRt() % 2 == 1)) {
        VisitUnallocated(instr);
        return;
      }
      break;
  }

  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitLoadStorePAC(const Instruction *instr) {
  const char *form = "'Xt, ['Xns'ILA]";
  const char *suffix = "";
  switch (form_hash_) {
    case "ldraa_64w_ldst_pac"_h:
    case "ldrab_64w_ldst_pac"_h:
      suffix = "!";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitAtomicMemory(const Instruction *instr) {
  bool is_x = (instr->ExtractBits(31, 30) == 3);
  const char *form = is_x ? "'Xs, 'Xt" : "'Ws, 'Wt";
  const char *suffix = ", ['Xns]";

  std::string mnemonic = mnemonic_;

  switch (form_hash_) {
    case "ldaprb_32l_memop"_h:
    case "ldaprh_32l_memop"_h:
    case "ldapr_32l_memop"_h:
      form = "'Wt";
      break;
    case "ldapr_64l_memop"_h:
      form = "'Xt";
      break;
    default:
      // Zero register implies a store instruction.
      if (instr->GetRt() == kZeroRegCode) {
        mnemonic.replace(0, 2, "st");
        form = is_x ? "'Xs" : "'Ws";
      }
  }
  Format(instr, mnemonic.c_str(), form, suffix);
}


void Disassembler::VisitFPCompare(const Instruction *instr) {
  const char *form = "'Fn, 'Fm";
  switch (form_hash_) {
    case "fcmpe_dz_floatcmp"_h:
    case "fcmpe_hz_floatcmp"_h:
    case "fcmpe_sz_floatcmp"_h:
    case "fcmp_dz_floatcmp"_h:
    case "fcmp_hz_floatcmp"_h:
    case "fcmp_sz_floatcmp"_h:
      form = "'Fn, #0.0";
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitFPConditionalCompare(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Fn, 'Fm, 'INzcv, 'Cond");
}


void Disassembler::VisitFPConditionalSelect(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Fd, 'Fn, 'Fm, 'Cond");
}


void Disassembler::VisitFPDataProcessing1Source(const Instruction *instr) {
  const char *form = "'Fd, 'Fn";
  switch (form_hash_) {
    case "fcvt_ds_floatdp1"_h:
      form = "'Dd, 'Sn";
      break;
    case "fcvt_sd_floatdp1"_h:
      form = "'Sd, 'Dn";
      break;
    case "fcvt_hs_floatdp1"_h:
      form = "'Hd, 'Sn";
      break;
    case "fcvt_sh_floatdp1"_h:
      form = "'Sd, 'Hn";
      break;
    case "fcvt_dh_floatdp1"_h:
      form = "'Dd, 'Hn";
      break;
    case "fcvt_hd_floatdp1"_h:
      form = "'Hd, 'Dn";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitFPDataProcessing2Source(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Fd, 'Fn, 'Fm");
}


void Disassembler::VisitFPDataProcessing3Source(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Fd, 'Fn, 'Fm, 'Fa");
}


void Disassembler::VisitFPImmediate(const Instruction *instr) {
  const char *form = "'Hd";
  const char *suffix = ", 'IFP";
  switch (form_hash_) {
    case "fmov_s_floatimm"_h:
      form = "'Sd";
      break;
    case "fmov_d_floatimm"_h:
      form = "'Dd";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}


void Disassembler::VisitFPIntegerConvert(const Instruction *instr) {
  const char *form = "'Rd, 'Fn";
  switch (form_hash_) {
    case "fmov_h32_float2int"_h:
    case "fmov_h64_float2int"_h:
    case "fmov_s32_float2int"_h:
    case "fmov_d64_float2int"_h:
    case "scvtf_d32_float2int"_h:
    case "scvtf_d64_float2int"_h:
    case "scvtf_h32_float2int"_h:
    case "scvtf_h64_float2int"_h:
    case "scvtf_s32_float2int"_h:
    case "scvtf_s64_float2int"_h:
    case "ucvtf_d32_float2int"_h:
    case "ucvtf_d64_float2int"_h:
    case "ucvtf_h32_float2int"_h:
    case "ucvtf_h64_float2int"_h:
    case "ucvtf_s32_float2int"_h:
    case "ucvtf_s64_float2int"_h:
      form = "'Fd, 'Rn";
      break;
    case "fmov_v64i_float2int"_h:
      form = "'Vd.D[1], 'Rn";
      break;
    case "fmov_64vx_float2int"_h:
      form = "'Rd, 'Vn.D[1]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitFPFixedPointConvert(const Instruction *instr) {
  const char *form = "'Rd, 'Fn";
  const char *suffix = ", 'IFPFBits";

  switch (form_hash_) {
    case "scvtf_d32_float2fix"_h:
    case "scvtf_d64_float2fix"_h:
    case "scvtf_h32_float2fix"_h:
    case "scvtf_h64_float2fix"_h:
    case "scvtf_s32_float2fix"_h:
    case "scvtf_s64_float2fix"_h:
    case "ucvtf_d32_float2fix"_h:
    case "ucvtf_d64_float2fix"_h:
    case "ucvtf_h32_float2fix"_h:
    case "ucvtf_h64_float2fix"_h:
    case "ucvtf_s32_float2fix"_h:
    case "ucvtf_s64_float2fix"_h:
      form = "'Fd, 'Rn";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::DisassembleNoArgs(const Instruction *instr) {
  Format(instr, mnemonic_.c_str(), "");
}

void Disassembler::VisitSystem(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "";
  const char *suffix = NULL;

  switch (form_hash_) {
    case "clrex_bn_barriers"_h:
      form = (instr->GetCRm() == 0xf) ? "" : "'IX";
      break;
    case "mrs_rs_systemmove"_h:
      form = "'Xt, 'IY";
      break;
    case "msr_sr_systemmove"_h:
      form = "'IY, 'Xt";
      break;
    case "bti_hb_hints"_h:
      switch (instr->ExtractBits(7, 6)) {
        case 0:
          form = "";
          break;
        case 1:
          form = "c";
          break;
        case 2:
          form = "j";
          break;
        case 3:
          form = "jc";
          break;
      }
      break;
    case "chkfeat_hf_hints"_h:
      mnemonic = "chkfeat";
      form = "x16";
      break;
    case "hint_hm_hints"_h:
      form = "'IH";
      break;
    case Hash("dmb_bo_barriers"):
      form = "'M";
      break;
    case Hash("dsb_bo_barriers"): {
      int crm = instr->GetCRm();
      if (crm == 0) {
        mnemonic = "ssbb";
        form = "";
      } else if (crm == 4) {
        mnemonic = "pssbb";
        form = "";
      } else {
        form = "'M";
      }
      break;
    }
    case Hash("sys_cr_systeminstrs"): {
      const std::map<uint32_t, const char *> dcop = {
          {IVAU, "ivau"},
          {CVAC, "cvac"},
          {CVAU, "cvau"},
          {CVAP, "cvap"},
          {CVADP, "cvadp"},
          {CIVAC, "civac"},
          {ZVA, "zva"},
          {GVA, "gva"},
          {GZVA, "gzva"},
          {CGVAC, "cgvac"},
          {CGDVAC, "cgdvac"},
          {CGVAP, "cgvap"},
          {CGDVAP, "cgdvap"},
          {CIGVAC, "cigvac"},
          {CIGDVAC, "cigdvac"},
      };

      uint32_t sysop = instr->GetSysOp();
      if (dcop.count(sysop)) {
        if (sysop == IVAU) {
          mnemonic = "ic";
        } else {
          mnemonic = "dc";
        }
        form = dcop.at(sysop);
        suffix = ", 'Xt";
      } else if (sysop == GCSSS1) {
        mnemonic = "gcsss1";
        form = "'Xt";
      } else if (sysop == GCSPUSHM) {
        mnemonic = "gcspushm";
        form = "'Xt";
      } else {
        mnemonic = "sys";
        form = "'G1, 'Kn, 'Km, 'G2";
        if (instr->GetRt() < 31) {
          suffix = ", 'Xt";
        }
      }
      break;
    }
    case "sysl_rc_systeminstrs"_h:
      uint32_t sysop = instr->GetSysOp();
      if (sysop == GCSPOPM) {
        mnemonic = "gcspopm";
        form = (instr->GetRt() == 31) ? "" : "'Xt";
      } else if (sysop == GCSSS2) {
        mnemonic = "gcsss2";
        form = "'Xt";
      }
      break;
  }
  Format(instr, mnemonic, form, suffix);
}


void Disassembler::VisitException(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "'IDebug";

  switch (instr->Mask(ExceptionMask)) {
    case HLT:
      mnemonic = "hlt";
      break;
    case BRK:
      mnemonic = "brk";
      break;
    case SVC:
      mnemonic = "svc";
      break;
    case HVC:
      mnemonic = "hvc";
      break;
    case SMC:
      mnemonic = "smc";
      break;
    case DCPS1:
      mnemonic = "dcps1";
      form = "{'IDebug}";
      break;
    case DCPS2:
      mnemonic = "dcps2";
      form = "{'IDebug}";
      break;
    case DCPS3:
      mnemonic = "dcps3";
      form = "{'IDebug}";
      break;
    default:
      form = "(Exception)";
  }
  Format(instr, mnemonic, form);
}


void Disassembler::VisitCrypto2RegSHA(const Instruction *instr) {
  const char *form = "'Vd.4s, 'Vn.4s";
  if (form_hash_ == "sha1h_ss_cryptosha2"_h) {
    form = "'Sd, 'Sn";
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitCrypto3RegSHA(const Instruction *instr) {
  const char *form = "'Qd, 'Sn, 'Vm.4s";
  switch (form_hash_) {
    case "sha1su0_vvv_cryptosha3"_h:
    case "sha256su1_vvv_cryptosha3"_h:
      form = "'Vd.4s, 'Vn.4s, 'Vm.4s";
      break;
    case "sha256h_qqv_cryptosha3"_h:
    case "sha256h2_qqv_cryptosha3"_h:
      form = "'Qd, 'Qn, 'Vm.4s";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}


void Disassembler::VisitCryptoAES(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Vd.16b, 'Vn.16b");
}

void Disassembler::VisitCryptoSM3(const Instruction *instr) {
  const char *form = "'Vd.4s, 'Vn.4s, 'Vm.";
  const char *suffix = "4s";

  switch (form_hash_) {
    case "sm3ss1_vvv4_crypto4"_h:
      suffix = "4s, 'Va.4s";
      break;
    case "sm3tt1a_vvv4_crypto3_imm2"_h:
    case "sm3tt1b_vvv4_crypto3_imm2"_h:
    case "sm3tt2a_vvv4_crypto3_imm2"_h:
    case "sm3tt2b_vvv_crypto3_imm2"_h:
      suffix = "s['u1312]";
      break;
  }

  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::DisassembleSHA512(const Instruction *instr) {
  const char *form = "'Qd, 'Qn, 'Vm.2d";
  const char *suffix = NULL;
  switch (form_hash_) {
    case "sha512su1_vvv2_cryptosha512_3"_h:
      suffix = ", 'Vm.2d";
      VIXL_FALLTHROUGH();
    case "sha512su0_vv2_cryptosha512_2"_h:
      form = "'Vd.2d, 'Vn.2d";
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::DisassembleNEON2RegAddlp(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s";

  static const NEONFormatMap map_lp_ta =
      {{23, 22, 30}, {NF_4H, NF_8H, NF_2S, NF_4S, NF_1D, NF_2D}};
  NEONFormatDecoder nfd(instr);
  nfd.SetFormatMap(0, &map_lp_ta);
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::DisassembleNEON2RegCompare(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, #0";
  NEONFormatDecoder nfd(instr);
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::DisassembleNEON2RegFPCompare(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, #0.0";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::FPFormatMap());
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::DisassembleNEON2RegFPConvert(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s";
  static const NEONFormatMap map_cvt_ta = {{22}, {NF_4S, NF_2D}};

  static const NEONFormatMap map_cvt_tb = {{22, 30},
                                           {NF_4H, NF_8H, NF_2S, NF_4S}};
  NEONFormatDecoder nfd(instr, &map_cvt_tb, &map_cvt_ta);

  VectorFormat vform_dst = nfd.GetVectorFormat(0);
  switch (form_hash_) {
    case "fcvtl_asimdmisc_l"_h:
      nfd.SetFormatMaps(&map_cvt_ta, &map_cvt_tb);
      break;
    case "fcvtxn_asimdmisc_n"_h:
      if ((vform_dst != kFormat2S) && (vform_dst != kFormat4S)) {
        mnemonic = NULL;
      }
      break;
  }
  Format(instr, nfd.Mnemonic(mnemonic), nfd.Substitute(form));
}

void Disassembler::DisassembleNEON2RegFP(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::FPFormatMap());
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::DisassembleNEON2RegLogical(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LogicalFormatMap());
  if (form_hash_ == "not_asimdmisc_r"_h) {
    mnemonic = "mvn";
  }
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::DisassembleNEON2RegExtract(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s";
  const char *suffix = NULL;
  NEONFormatDecoder nfd(instr,
                        NEONFormatDecoder::IntegerFormatMap(),
                        NEONFormatDecoder::LongIntegerFormatMap());

  if (form_hash_ == "shll_asimdmisc_s"_h) {
    nfd.SetFormatMaps(nfd.LongIntegerFormatMap(), nfd.IntegerFormatMap());
    switch (instr->GetNEONSize()) {
      case 0:
        suffix = ", #8";
        break;
      case 1:
        suffix = ", #16";
        break;
      case 2:
        suffix = ", #32";
        break;
    }
  }
  Format(instr, nfd.Mnemonic(mnemonic), nfd.Substitute(form), suffix);
}

void Disassembler::VisitNEON2RegMisc(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s";
  NEONFormatDecoder nfd(instr);

  VectorFormat vform_dst = nfd.GetVectorFormat(0);
  if (vform_dst != kFormatUndefined) {
    uint32_t ls_dst = LaneSizeInBitsFromFormat(vform_dst);
    switch (form_hash_) {
      case "cnt_asimdmisc_r"_h:
      case "rev16_asimdmisc_r"_h:
        if (ls_dst != kBRegSize) {
          mnemonic = NULL;
        }
        break;
      case "rev32_asimdmisc_r"_h:
        if ((ls_dst == kDRegSize) || (ls_dst == kSRegSize)) {
          mnemonic = NULL;
        }
        break;
      case "urecpe_asimdmisc_r"_h:
      case "ursqrte_asimdmisc_r"_h:
        // For urecpe and ursqrte, only S-sized elements are supported. The MSB
        // of the size field is always set by the instruction (0b1x) so we need
        // only check and discard D-sized elements here.
        VIXL_ASSERT((ls_dst == kSRegSize) || (ls_dst == kDRegSize));
        VIXL_FALLTHROUGH();
      case "clz_asimdmisc_r"_h:
      case "cls_asimdmisc_r"_h:
      case "rev64_asimdmisc_r"_h:
        if (ls_dst == kDRegSize) {
          mnemonic = NULL;
        }
        break;
    }
  }

  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::VisitNEON2RegMiscFP16(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.'?30:84h, 'Vn.'?30:84h";
  const char *suffix = NULL;

  switch (form_hash_) {
    case "fcmeq_asimdmiscfp16_fz"_h:
    case "fcmge_asimdmiscfp16_fz"_h:
    case "fcmgt_asimdmiscfp16_fz"_h:
    case "fcmle_asimdmiscfp16_fz"_h:
    case "fcmlt_asimdmiscfp16_fz"_h:
      suffix = ", #0.0";
  }
  Format(instr, mnemonic, form, suffix);
}

void Disassembler::DisassembleNEON3SameLogical(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'Vm.%s";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LogicalFormatMap());

  switch (form_hash_) {
    case "orr_asimdsame_only"_h:
      if (instr->GetRm() == instr->GetRn()) {
        mnemonic = "mov";
        form = "'Vd.%s, 'Vn.%s";
      }
      break;
    case "pmul_asimdsame_only"_h:
      if (instr->GetNEONSize() != 0) {
        mnemonic = NULL;
      }
  }
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::DisassembleNEON3SameFHM(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Vd.'?30:42s, 'Vn.'?30:42h, 'Vm.'?30:42h");
}

void Disassembler::DisassembleNEON3SameNoD(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'Vm.%s";
  static const NEONFormatMap map =
      {{23, 22, 30},
       {NF_8B, NF_16B, NF_4H, NF_8H, NF_2S, NF_4S, NF_UNDEF, NF_UNDEF}};
  NEONFormatDecoder nfd(instr, &map);
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::VisitNEON3Same(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'Vm.%s";
  NEONFormatDecoder nfd(instr);

  if (instr->Mask(NEON3SameFPFMask) == NEON3SameFPFixed) {
    nfd.SetFormatMaps(nfd.FPFormatMap());
  }

  VectorFormat vform_dst = nfd.GetVectorFormat(0);
  if (vform_dst != kFormatUndefined) {
    uint32_t ls_dst = LaneSizeInBitsFromFormat(vform_dst);
    switch (form_hash_) {
      case "sqdmulh_asimdsame_only"_h:
      case "sqrdmulh_asimdsame_only"_h:
        if ((ls_dst == kBRegSize) || (ls_dst == kDRegSize)) {
          mnemonic = NULL;
        }
        break;
    }
  }
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::VisitNEON3SameFP16(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'Vm.%s";
  NEONFormatDecoder nfd(instr);
  nfd.SetFormatMaps(nfd.FP16FormatMap());
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::VisitNEON3SameExtra(const Instruction *instr) {
  static const NEONFormatMap map_dot =
      {{23, 22, 30}, {NF_UNDEF, NF_UNDEF, NF_UNDEF, NF_UNDEF, NF_2S, NF_4S}};
  static const NEONFormatMap map_fc =
      {{23, 22, 30},
       {NF_UNDEF, NF_UNDEF, NF_4H, NF_8H, NF_2S, NF_4S, NF_UNDEF, NF_2D}};
  static const NEONFormatMap map_rdm =
      {{23, 22, 30}, {NF_UNDEF, NF_UNDEF, NF_4H, NF_8H, NF_2S, NF_4S}};

  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'Vm.%s";
  const char *suffix = NULL;

  NEONFormatDecoder nfd(instr, &map_fc);

  switch (form_hash_) {
    case "fcmla_asimdsame2_c"_h:
      suffix = ", #'u1211*90";
      break;
    case "fcadd_asimdsame2_c"_h:
      // Bit 10 is always set, so this gives 90 * 1 or 3.
      suffix = ", #'u1212:1010*90";
      break;
    case "sdot_asimdsame2_d"_h:
    case "udot_asimdsame2_d"_h:
    case "usdot_asimdsame2_d"_h:
      nfd.SetFormatMaps(nfd.LogicalFormatMap());
      nfd.SetFormatMap(0, &map_dot);
      break;
    default:
      nfd.SetFormatMaps(&map_rdm);
      break;
  }

  Format(instr, mnemonic, nfd.Substitute(form), suffix);
}

void Disassembler::DisassembleNEON4Same(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Vd.16b, 'Vn.16b, 'Vm.16b, 'Va.16b");
}

void Disassembler::DisassembleNEONXar(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Vd.2d, 'Vn.2d, 'Vm.2d, #'u1510");
}

void Disassembler::DisassembleNEONRax1(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Vd.2d, 'Vn.2d, 'Vm.2d");
}

void Disassembler::VisitNEON3Different(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'Vm.%s";

  NEONFormatDecoder nfd(instr);
  nfd.SetFormatMap(0, nfd.LongIntegerFormatMap());

  switch (form_hash_) {
    case "saddw_asimddiff_w"_h:
    case "ssubw_asimddiff_w"_h:
    case "uaddw_asimddiff_w"_h:
    case "usubw_asimddiff_w"_h:
      nfd.SetFormatMap(1, nfd.LongIntegerFormatMap());
      break;
    case "addhn_asimddiff_n"_h:
    case "raddhn_asimddiff_n"_h:
    case "rsubhn_asimddiff_n"_h:
    case "subhn_asimddiff_n"_h:
      nfd.SetFormatMaps(nfd.LongIntegerFormatMap());
      nfd.SetFormatMap(0, nfd.IntegerFormatMap());
      break;
    case "sqdmlal_asimddiff_l"_h:
    case "sqdmlsl_asimddiff_l"_h:
    case "sqdmull_asimddiff_l"_h:
      if (nfd.GetVectorFormat(0) == kFormat8H) {
        mnemonic = NULL;
      }
      break;
  }
  Format(instr, nfd.Mnemonic(mnemonic), nfd.Substitute(form));
}

void Disassembler::DisassembleNEONPolynomialMul(const Instruction *instr) {
  const char *mnemonic = instr->ExtractBit(30) ? "pmull2" : "pmull";
  const char *form = NULL;
  int size = instr->ExtractBits(23, 22);
  if (size == 0) {
    // Bits 30:27 of the instruction are x001, where x is the Q bit. Map
    // this to "8" and "16" by adding 7.
    form = "'Vd.8h, 'Vn.'u3127+7b, 'Vm.'u3127+7b";
  } else if (size == 3) {
    form = "'Vd.1q, 'Vn.'?30:21d, 'Vm.'?30:21d";
  } else {
    mnemonic = NULL;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::DisassembleNEONFPAcrossLanes(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Sd, 'Vn.4s";
  if ((instr->GetNEONQ() == 0) || (instr->ExtractBit(22) == 1)) {
    mnemonic = NULL;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::DisassembleNEONFP16AcrossLanes(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Hd, 'Vn.'?30:84h");
}

void Disassembler::VisitNEONAcrossLanes(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, 'Vn.%s";

  NEONFormatDecoder nfd(instr,
                        NEONFormatDecoder::ScalarFormatMap(),
                        NEONFormatDecoder::IntegerFormatMap());

  switch (form_hash_) {
    case "saddlv_asimdall_only"_h:
    case "uaddlv_asimdall_only"_h:
      nfd.SetFormatMap(0, nfd.LongScalarFormatMap());
  }

  VectorFormat vform_src = nfd.GetVectorFormat(1);
  if ((vform_src == kFormat2S) || (vform_src == kFormat2D)) {
    mnemonic = NULL;
  }

  Format(instr,
         mnemonic,
         nfd.Substitute(form,
                        NEONFormatDecoder::kPlaceholder,
                        NEONFormatDecoder::kFormat));
}

void Disassembler::VisitNEONByIndexedElement(const Instruction *instr) {
  const char *form = "'Vd.%s, 'Vn.%s, 'Vf.%s['IVByElemIndex]";
  static const NEONFormatMap map_v =
      {{23, 22, 30},
       {NF_UNDEF, NF_UNDEF, NF_4H, NF_8H, NF_2S, NF_4S, NF_UNDEF, NF_UNDEF}};
  static const NEONFormatMap map_s = {{23, 22},
                                      {NF_UNDEF, NF_H, NF_S, NF_UNDEF}};
  NEONFormatDecoder nfd(instr, &map_v, &map_v, &map_s);
  Format(instr, mnemonic_.c_str(), nfd.Substitute(form));
}

void Disassembler::DisassembleNEONMulByElementLong(const Instruction *instr) {
  const char *form = "'Vd.%s, 'Vn.%s, 'Vf.%s['IVByElemIndex]";
  // TODO: Disallow undefined element types for this instruction.
  static const NEONFormatMap map_ta = {{23, 22}, {NF_UNDEF, NF_4S, NF_2D}};
  NEONFormatDecoder nfd(instr,
                        &map_ta,
                        NEONFormatDecoder::IntegerFormatMap(),
                        NEONFormatDecoder::ScalarFormatMap());
  Format(instr, nfd.Mnemonic(mnemonic_.c_str()), nfd.Substitute(form));
}

void Disassembler::DisassembleNEONDotProdByElement(const Instruction *instr) {
  const char *form = instr->ExtractBit(30) ? "'Vd.4s, 'Vn.16" : "'Vd.2s, 'Vn.8";
  const char *suffix = "b, 'Vm.4b['u1111:2121]";
  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::DisassembleNEONFPMulByElement(const Instruction *instr) {
  const char *form = "'Vd.%s, 'Vn.%s, 'Vf.%s['IVByElemIndex]";
  NEONFormatDecoder nfd(instr,
                        NEONFormatDecoder::FPFormatMap(),
                        NEONFormatDecoder::FPFormatMap(),
                        NEONFormatDecoder::FPScalarFormatMap());
  Format(instr, mnemonic_.c_str(), nfd.Substitute(form));
}

void Disassembler::DisassembleNEONHalfFPMulByElement(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "'Vd.'?30:84h, 'Vn.'?30:84h, "
                            "'Ve.h['IVByElemIndex]");
}

void Disassembler::DisassembleNEONFPMulByElementLong(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "'Vd.'?30:42s, 'Vn.'?30:42h, "
                            "'Ve.h['IVByElemIndexFHM]");
}

void Disassembler::DisassembleNEONComplexMulByElement(
    const Instruction *instr) {
  const char *form = "'Vd.%s, 'Vn.%s, 'Vm.%s['IVByElemIndexRot], #'u1413*90";
  // TODO: Disallow undefined element types for this instruction.
  static const NEONFormatMap map_cn =
      {{23, 22, 30},
       {NF_UNDEF, NF_UNDEF, NF_4H, NF_8H, NF_UNDEF, NF_4S, NF_UNDEF, NF_UNDEF}};
  NEONFormatDecoder nfd(instr,
                        &map_cn,
                        &map_cn,
                        NEONFormatDecoder::ScalarFormatMap());
  Format(instr, mnemonic_.c_str(), nfd.Substitute(form));
}

void Disassembler::VisitNEONCopy(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "(NEONCopy)";

  NEONFormatDecoder nfd(instr,
                        NEONFormatDecoder::TriangularFormatMap(),
                        NEONFormatDecoder::TriangularScalarFormatMap());

  switch (form_hash_) {
    case "ins_asimdins_iv_v"_h:
      mnemonic = "mov";
      nfd.SetFormatMap(0, nfd.TriangularScalarFormatMap());
      form = "'Vd.%s['IVInsIndex1], 'Vn.%s['IVInsIndex2]";
      break;
    case "ins_asimdins_ir_r"_h:
      mnemonic = "mov";
      nfd.SetFormatMap(0, nfd.TriangularScalarFormatMap());
      if (nfd.GetVectorFormat() == kFormatD) {
        form = "'Vd.%s['IVInsIndex1], 'Xn";
      } else {
        form = "'Vd.%s['IVInsIndex1], 'Wn";
      }
      break;
    case "umov_asimdins_w_w"_h:
    case "umov_asimdins_x_x"_h:
      if (instr->Mask(NEON_Q) || ((instr->GetImmNEON5() & 7) == 4)) {
        mnemonic = "mov";
      }
      nfd.SetFormatMap(0, nfd.TriangularScalarFormatMap());
      if (nfd.GetVectorFormat() == kFormatD) {
        form = "'Xd, 'Vn.%s['IVInsIndex1]";
      } else {
        form = "'Wd, 'Vn.%s['IVInsIndex1]";
      }
      break;
    case "smov_asimdins_w_w"_h:
    case "smov_asimdins_x_x"_h: {
      nfd.SetFormatMap(0, nfd.TriangularScalarFormatMap());
      VectorFormat vform = nfd.GetVectorFormat();
      if ((vform == kFormatD) ||
          ((vform == kFormatS) && (instr->ExtractBit(30) == 0))) {
        mnemonic = NULL;
      }
      form = "'R30d, 'Vn.%s['IVInsIndex1]";
      break;
    }
    case "dup_asimdins_dv_v"_h:
      form = "'Vd.%s, 'Vn.%s['IVInsIndex1]";
      break;
    case "dup_asimdins_dr_r"_h:
      if (nfd.GetVectorFormat() == kFormat2D) {
        form = "'Vd.%s, 'Xn";
      } else {
        form = "'Vd.%s, 'Wn";
      }
  }
  Format(instr, mnemonic, nfd.Substitute(form));
}


void Disassembler::VisitNEONExtract(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'Vm.%s, 'IVExtract";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LogicalFormatMap());
  if ((instr->GetImmNEONExt() > 7) && (instr->GetNEONQ() == 0)) {
    mnemonic = NULL;
  }
  Format(instr, mnemonic, nfd.Substitute(form));
}


void Disassembler::VisitNEONLoadStoreMultiStruct(const Instruction *instr) {
  const char *mnemonic = NULL;
  const char *form = NULL;
  const char *form_1v = "{'Vt.%1$s}, ['Xns]";
  const char *form_2v = "{'Vt.%1$s, 'Vt2.%1$s}, ['Xns]";
  const char *form_3v = "{'Vt.%1$s, 'Vt2.%1$s, 'Vt3.%1$s}, ['Xns]";
  const char *form_4v = "{'Vt.%1$s, 'Vt2.%1$s, 'Vt3.%1$s, 'Vt4.%1$s}, ['Xns]";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LoadStoreFormatMap());

  switch (instr->Mask(NEONLoadStoreMultiStructMask)) {
    case NEON_LD1_1v:
      mnemonic = "ld1";
      form = form_1v;
      break;
    case NEON_LD1_2v:
      mnemonic = "ld1";
      form = form_2v;
      break;
    case NEON_LD1_3v:
      mnemonic = "ld1";
      form = form_3v;
      break;
    case NEON_LD1_4v:
      mnemonic = "ld1";
      form = form_4v;
      break;
    case NEON_LD2:
      mnemonic = "ld2";
      form = form_2v;
      break;
    case NEON_LD3:
      mnemonic = "ld3";
      form = form_3v;
      break;
    case NEON_LD4:
      mnemonic = "ld4";
      form = form_4v;
      break;
    case NEON_ST1_1v:
      mnemonic = "st1";
      form = form_1v;
      break;
    case NEON_ST1_2v:
      mnemonic = "st1";
      form = form_2v;
      break;
    case NEON_ST1_3v:
      mnemonic = "st1";
      form = form_3v;
      break;
    case NEON_ST1_4v:
      mnemonic = "st1";
      form = form_4v;
      break;
    case NEON_ST2:
      mnemonic = "st2";
      form = form_2v;
      break;
    case NEON_ST3:
      mnemonic = "st3";
      form = form_3v;
      break;
    case NEON_ST4:
      mnemonic = "st4";
      form = form_4v;
      break;
    default:
      break;
  }

  // Work out unallocated encodings.
  bool allocated = (mnemonic != NULL);
  switch (instr->Mask(NEONLoadStoreMultiStructMask)) {
    case NEON_LD2:
    case NEON_LD3:
    case NEON_LD4:
    case NEON_ST2:
    case NEON_ST3:
    case NEON_ST4:
      // LD[2-4] and ST[2-4] cannot use .1d format.
      allocated = (instr->GetNEONQ() != 0) || (instr->GetNEONLSSize() != 3);
      break;
    default:
      break;
  }
  if (allocated) {
    VIXL_ASSERT(mnemonic != NULL);
    VIXL_ASSERT(form != NULL);
  } else {
    mnemonic = "unallocated";
    form = "(NEONLoadStoreMultiStruct)";
  }

  Format(instr, mnemonic, nfd.Substitute(form));
}


void Disassembler::VisitNEONLoadStoreMultiStructPostIndex(
    const Instruction *instr) {
  const char *mnemonic = NULL;
  const char *form = NULL;
  const char *form_1v = "{'Vt.%1$s}, ['Xns], 'Xmr1";
  const char *form_2v = "{'Vt.%1$s, 'Vt2.%1$s}, ['Xns], 'Xmr2";
  const char *form_3v = "{'Vt.%1$s, 'Vt2.%1$s, 'Vt3.%1$s}, ['Xns], 'Xmr3";
  const char *form_4v =
      "{'Vt.%1$s, 'Vt2.%1$s, 'Vt3.%1$s, 'Vt4.%1$s}, ['Xns], 'Xmr4";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LoadStoreFormatMap());

  switch (instr->Mask(NEONLoadStoreMultiStructPostIndexMask)) {
    case NEON_LD1_1v_post:
      mnemonic = "ld1";
      form = form_1v;
      break;
    case NEON_LD1_2v_post:
      mnemonic = "ld1";
      form = form_2v;
      break;
    case NEON_LD1_3v_post:
      mnemonic = "ld1";
      form = form_3v;
      break;
    case NEON_LD1_4v_post:
      mnemonic = "ld1";
      form = form_4v;
      break;
    case NEON_LD2_post:
      mnemonic = "ld2";
      form = form_2v;
      break;
    case NEON_LD3_post:
      mnemonic = "ld3";
      form = form_3v;
      break;
    case NEON_LD4_post:
      mnemonic = "ld4";
      form = form_4v;
      break;
    case NEON_ST1_1v_post:
      mnemonic = "st1";
      form = form_1v;
      break;
    case NEON_ST1_2v_post:
      mnemonic = "st1";
      form = form_2v;
      break;
    case NEON_ST1_3v_post:
      mnemonic = "st1";
      form = form_3v;
      break;
    case NEON_ST1_4v_post:
      mnemonic = "st1";
      form = form_4v;
      break;
    case NEON_ST2_post:
      mnemonic = "st2";
      form = form_2v;
      break;
    case NEON_ST3_post:
      mnemonic = "st3";
      form = form_3v;
      break;
    case NEON_ST4_post:
      mnemonic = "st4";
      form = form_4v;
      break;
    default:
      break;
  }

  // Work out unallocated encodings.
  bool allocated = (mnemonic != NULL);
  switch (instr->Mask(NEONLoadStoreMultiStructPostIndexMask)) {
    case NEON_LD2_post:
    case NEON_LD3_post:
    case NEON_LD4_post:
    case NEON_ST2_post:
    case NEON_ST3_post:
    case NEON_ST4_post:
      // LD[2-4] and ST[2-4] cannot use .1d format.
      allocated = (instr->GetNEONQ() != 0) || (instr->GetNEONLSSize() != 3);
      break;
    default:
      break;
  }
  if (allocated) {
    VIXL_ASSERT(mnemonic != NULL);
    VIXL_ASSERT(form != NULL);
  } else {
    mnemonic = "unallocated";
    form = "(NEONLoadStoreMultiStructPostIndex)";
  }

  Format(instr, mnemonic, nfd.Substitute(form));
}


void Disassembler::VisitNEONLoadStoreSingleStruct(const Instruction *instr) {
  const char *mnemonic = NULL;
  const char *form = NULL;

  const char *form_1b = "{'Vt.b}['IVLSLane0], ['Xns]";
  const char *form_1h = "{'Vt.h}['IVLSLane1], ['Xns]";
  const char *form_1s = "{'Vt.s}['IVLSLane2], ['Xns]";
  const char *form_1d = "{'Vt.d}['IVLSLane3], ['Xns]";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LoadStoreFormatMap());

  switch (instr->Mask(NEONLoadStoreSingleStructMask)) {
    case NEON_LD1_b:
      mnemonic = "ld1";
      form = form_1b;
      break;
    case NEON_LD1_h:
      mnemonic = "ld1";
      form = form_1h;
      break;
    case NEON_LD1_s:
      mnemonic = "ld1";
      VIXL_STATIC_ASSERT((NEON_LD1_s | (1 << NEONLSSize_offset)) == NEON_LD1_d);
      form = ((instr->GetNEONLSSize() & 1) == 0) ? form_1s : form_1d;
      break;
    case NEON_ST1_b:
      mnemonic = "st1";
      form = form_1b;
      break;
    case NEON_ST1_h:
      mnemonic = "st1";
      form = form_1h;
      break;
    case NEON_ST1_s:
      mnemonic = "st1";
      VIXL_STATIC_ASSERT((NEON_ST1_s | (1 << NEONLSSize_offset)) == NEON_ST1_d);
      form = ((instr->GetNEONLSSize() & 1) == 0) ? form_1s : form_1d;
      break;
    case NEON_LD1R:
      mnemonic = "ld1r";
      form = "{'Vt.%s}, ['Xns]";
      break;
    case NEON_LD2_b:
    case NEON_ST2_b:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld2" : "st2";
      form = "{'Vt.b, 'Vt2.b}['IVLSLane0], ['Xns]";
      break;
    case NEON_LD2_h:
    case NEON_ST2_h:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld2" : "st2";
      form = "{'Vt.h, 'Vt2.h}['IVLSLane1], ['Xns]";
      break;
    case NEON_LD2_s:
    case NEON_ST2_s:
      VIXL_STATIC_ASSERT((NEON_ST2_s | (1 << NEONLSSize_offset)) == NEON_ST2_d);
      VIXL_STATIC_ASSERT((NEON_LD2_s | (1 << NEONLSSize_offset)) == NEON_LD2_d);
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld2" : "st2";
      if ((instr->GetNEONLSSize() & 1) == 0) {
        form = "{'Vt.s, 'Vt2.s}['IVLSLane2], ['Xns]";
      } else {
        form = "{'Vt.d, 'Vt2.d}['IVLSLane3], ['Xns]";
      }
      break;
    case NEON_LD2R:
      mnemonic = "ld2r";
      form = "{'Vt.%s, 'Vt2.%s}, ['Xns]";
      break;
    case NEON_LD3_b:
    case NEON_ST3_b:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld3" : "st3";
      form = "{'Vt.b, 'Vt2.b, 'Vt3.b}['IVLSLane0], ['Xns]";
      break;
    case NEON_LD3_h:
    case NEON_ST3_h:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld3" : "st3";
      form = "{'Vt.h, 'Vt2.h, 'Vt3.h}['IVLSLane1], ['Xns]";
      break;
    case NEON_LD3_s:
    case NEON_ST3_s:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld3" : "st3";
      if ((instr->GetNEONLSSize() & 1) == 0) {
        form = "{'Vt.s, 'Vt2.s, 'Vt3.s}['IVLSLane2], ['Xns]";
      } else {
        form = "{'Vt.d, 'Vt2.d, 'Vt3.d}['IVLSLane3], ['Xns]";
      }
      break;
    case NEON_LD3R:
      mnemonic = "ld3r";
      form = "{'Vt.%s, 'Vt2.%s, 'Vt3.%s}, ['Xns]";
      break;
    case NEON_LD4_b:
    case NEON_ST4_b:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld4" : "st4";
      form = "{'Vt.b, 'Vt2.b, 'Vt3.b, 'Vt4.b}['IVLSLane0], ['Xns]";
      break;
    case NEON_LD4_h:
    case NEON_ST4_h:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld4" : "st4";
      form = "{'Vt.h, 'Vt2.h, 'Vt3.h, 'Vt4.h}['IVLSLane1], ['Xns]";
      break;
    case NEON_LD4_s:
    case NEON_ST4_s:
      VIXL_STATIC_ASSERT((NEON_LD4_s | (1 << NEONLSSize_offset)) == NEON_LD4_d);
      VIXL_STATIC_ASSERT((NEON_ST4_s | (1 << NEONLSSize_offset)) == NEON_ST4_d);
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld4" : "st4";
      if ((instr->GetNEONLSSize() & 1) == 0) {
        form = "{'Vt.s, 'Vt2.s, 'Vt3.s, 'Vt4.s}['IVLSLane2], ['Xns]";
      } else {
        form = "{'Vt.d, 'Vt2.d, 'Vt3.d, 'Vt4.d}['IVLSLane3], ['Xns]";
      }
      break;
    case NEON_LD4R:
      mnemonic = "ld4r";
      form = "{'Vt.%1$s, 'Vt2.%1$s, 'Vt3.%1$s, 'Vt4.%1$s}, ['Xns]";
      break;
    default:
      break;
  }

  // Work out unallocated encodings.
  bool allocated = (mnemonic != NULL);
  switch (instr->Mask(NEONLoadStoreSingleStructMask)) {
    case NEON_LD1_h:
    case NEON_LD2_h:
    case NEON_LD3_h:
    case NEON_LD4_h:
    case NEON_ST1_h:
    case NEON_ST2_h:
    case NEON_ST3_h:
    case NEON_ST4_h:
      VIXL_ASSERT(allocated);
      allocated = ((instr->GetNEONLSSize() & 1) == 0);
      break;
    case NEON_LD1_s:
    case NEON_LD2_s:
    case NEON_LD3_s:
    case NEON_LD4_s:
    case NEON_ST1_s:
    case NEON_ST2_s:
    case NEON_ST3_s:
    case NEON_ST4_s:
      VIXL_ASSERT(allocated);
      allocated = (instr->GetNEONLSSize() <= 1) &&
                  ((instr->GetNEONLSSize() == 0) || (instr->GetNEONS() == 0));
      break;
    case NEON_LD1R:
    case NEON_LD2R:
    case NEON_LD3R:
    case NEON_LD4R:
      VIXL_ASSERT(allocated);
      allocated = (instr->GetNEONS() == 0);
      break;
    default:
      break;
  }
  if (allocated) {
    VIXL_ASSERT(mnemonic != NULL);
    VIXL_ASSERT(form != NULL);
  } else {
    mnemonic = "unallocated";
    form = "(NEONLoadStoreSingleStruct)";
  }

  Format(instr, mnemonic, nfd.Substitute(form));
}


void Disassembler::VisitNEONLoadStoreSingleStructPostIndex(
    const Instruction *instr) {
  const char *mnemonic = NULL;
  const char *form = NULL;

  const char *form_1b = "{'Vt.b}['IVLSLane0], ['Xns], 'Xmb1";
  const char *form_1h = "{'Vt.h}['IVLSLane1], ['Xns], 'Xmb2";
  const char *form_1s = "{'Vt.s}['IVLSLane2], ['Xns], 'Xmb4";
  const char *form_1d = "{'Vt.d}['IVLSLane3], ['Xns], 'Xmb8";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LoadStoreFormatMap());

  switch (instr->Mask(NEONLoadStoreSingleStructPostIndexMask)) {
    case NEON_LD1_b_post:
      mnemonic = "ld1";
      form = form_1b;
      break;
    case NEON_LD1_h_post:
      mnemonic = "ld1";
      form = form_1h;
      break;
    case NEON_LD1_s_post:
      mnemonic = "ld1";
      VIXL_STATIC_ASSERT((NEON_LD1_s | (1 << NEONLSSize_offset)) == NEON_LD1_d);
      form = ((instr->GetNEONLSSize() & 1) == 0) ? form_1s : form_1d;
      break;
    case NEON_ST1_b_post:
      mnemonic = "st1";
      form = form_1b;
      break;
    case NEON_ST1_h_post:
      mnemonic = "st1";
      form = form_1h;
      break;
    case NEON_ST1_s_post:
      mnemonic = "st1";
      VIXL_STATIC_ASSERT((NEON_ST1_s | (1 << NEONLSSize_offset)) == NEON_ST1_d);
      form = ((instr->GetNEONLSSize() & 1) == 0) ? form_1s : form_1d;
      break;
    case NEON_LD1R_post:
      mnemonic = "ld1r";
      form = "{'Vt.%s}, ['Xns], 'Xmz1";
      break;
    case NEON_LD2_b_post:
    case NEON_ST2_b_post:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld2" : "st2";
      form = "{'Vt.b, 'Vt2.b}['IVLSLane0], ['Xns], 'Xmb2";
      break;
    case NEON_ST2_h_post:
    case NEON_LD2_h_post:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld2" : "st2";
      form = "{'Vt.h, 'Vt2.h}['IVLSLane1], ['Xns], 'Xmb4";
      break;
    case NEON_LD2_s_post:
    case NEON_ST2_s_post:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld2" : "st2";
      if ((instr->GetNEONLSSize() & 1) == 0)
        form = "{'Vt.s, 'Vt2.s}['IVLSLane2], ['Xns], 'Xmb8";
      else
        form = "{'Vt.d, 'Vt2.d}['IVLSLane3], ['Xns], 'Xmb16";
      break;
    case NEON_LD2R_post:
      mnemonic = "ld2r";
      form = "{'Vt.%s, 'Vt2.%s}, ['Xns], 'Xmz2";
      break;
    case NEON_LD3_b_post:
    case NEON_ST3_b_post:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld3" : "st3";
      form = "{'Vt.b, 'Vt2.b, 'Vt3.b}['IVLSLane0], ['Xns], 'Xmb3";
      break;
    case NEON_LD3_h_post:
    case NEON_ST3_h_post:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld3" : "st3";
      form = "{'Vt.h, 'Vt2.h, 'Vt3.h}['IVLSLane1], ['Xns], 'Xmb6";
      break;
    case NEON_LD3_s_post:
    case NEON_ST3_s_post:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld3" : "st3";
      if ((instr->GetNEONLSSize() & 1) == 0)
        form = "{'Vt.s, 'Vt2.s, 'Vt3.s}['IVLSLane2], ['Xns], 'Xmb12";
      else
        form = "{'Vt.d, 'Vt2.d, 'Vt3.d}['IVLSLane3], ['Xns], 'Xmb24";
      break;
    case NEON_LD3R_post:
      mnemonic = "ld3r";
      form = "{'Vt.%s, 'Vt2.%s, 'Vt3.%s}, ['Xns], 'Xmz3";
      break;
    case NEON_LD4_b_post:
    case NEON_ST4_b_post:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld4" : "st4";
      form = "{'Vt.b, 'Vt2.b, 'Vt3.b, 'Vt4.b}['IVLSLane0], ['Xns], 'Xmb4";
      break;
    case NEON_LD4_h_post:
    case NEON_ST4_h_post:
      mnemonic = (instr->GetLdStXLoad()) == 1 ? "ld4" : "st4";
      form = "{'Vt.h, 'Vt2.h, 'Vt3.h, 'Vt4.h}['IVLSLane1], ['Xns], 'Xmb8";
      break;
    case NEON_LD4_s_post:
    case NEON_ST4_s_post:
      mnemonic = (instr->GetLdStXLoad() == 1) ? "ld4" : "st4";
      if ((instr->GetNEONLSSize() & 1) == 0)
        form = "{'Vt.s, 'Vt2.s, 'Vt3.s, 'Vt4.s}['IVLSLane2], ['Xns], 'Xmb16";
      else
        form = "{'Vt.d, 'Vt2.d, 'Vt3.d, 'Vt4.d}['IVLSLane3], ['Xns], 'Xmb32";
      break;
    case NEON_LD4R_post:
      mnemonic = "ld4r";
      form = "{'Vt.%1$s, 'Vt2.%1$s, 'Vt3.%1$s, 'Vt4.%1$s}, ['Xns], 'Xmz4";
      break;
    default:
      break;
  }

  // Work out unallocated encodings.
  bool allocated = (mnemonic != NULL);
  switch (instr->Mask(NEONLoadStoreSingleStructPostIndexMask)) {
    case NEON_LD1_h_post:
    case NEON_LD2_h_post:
    case NEON_LD3_h_post:
    case NEON_LD4_h_post:
    case NEON_ST1_h_post:
    case NEON_ST2_h_post:
    case NEON_ST3_h_post:
    case NEON_ST4_h_post:
      VIXL_ASSERT(allocated);
      allocated = ((instr->GetNEONLSSize() & 1) == 0);
      break;
    case NEON_LD1_s_post:
    case NEON_LD2_s_post:
    case NEON_LD3_s_post:
    case NEON_LD4_s_post:
    case NEON_ST1_s_post:
    case NEON_ST2_s_post:
    case NEON_ST3_s_post:
    case NEON_ST4_s_post:
      VIXL_ASSERT(allocated);
      allocated = (instr->GetNEONLSSize() <= 1) &&
                  ((instr->GetNEONLSSize() == 0) || (instr->GetNEONS() == 0));
      break;
    case NEON_LD1R_post:
    case NEON_LD2R_post:
    case NEON_LD3R_post:
    case NEON_LD4R_post:
      VIXL_ASSERT(allocated);
      allocated = (instr->GetNEONS() == 0);
      break;
    default:
      break;
  }
  if (allocated) {
    VIXL_ASSERT(mnemonic != NULL);
    VIXL_ASSERT(form != NULL);
  } else {
    mnemonic = "unallocated";
    form = "(NEONLoadStoreSingleStructPostIndex)";
  }

  Format(instr, mnemonic, nfd.Substitute(form));
}


void Disassembler::VisitNEONModifiedImmediate(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vt.%s, 'IVMIImm8, lsl 'IVMIShiftAmt1";

  static const NEONFormatMap map_h = {{30}, {NF_4H, NF_8H}};
  static const NEONFormatMap map_s = {{30}, {NF_2S, NF_4S}};
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LogicalFormatMap());

  switch (form_hash_) {
    case "movi_asimdimm_n_b"_h:
      form = "'Vt.%s, 'IVMIImm8";
      break;
    case "bic_asimdimm_l_hl"_h:
    case "movi_asimdimm_l_hl"_h:
    case "mvni_asimdimm_l_hl"_h:
    case "orr_asimdimm_l_hl"_h:
      nfd.SetFormatMap(0, &map_h);
      break;
    case "movi_asimdimm_m_sm"_h:
    case "mvni_asimdimm_m_sm"_h:
      form = "'Vt.%s, 'IVMIImm8, msl 'IVMIShiftAmt2";
      VIXL_FALLTHROUGH();
    case "bic_asimdimm_l_sl"_h:
    case "movi_asimdimm_l_sl"_h:
    case "mvni_asimdimm_l_sl"_h:
    case "orr_asimdimm_l_sl"_h:
      nfd.SetFormatMap(0, &map_s);
      break;
    case "movi_asimdimm_d_ds"_h:
      form = "'Dd, 'IVMIImm";
      break;
    case "movi_asimdimm_d2_d"_h:
      form = "'Vt.2d, 'IVMIImm";
      break;
    case "fmov_asimdimm_h_h"_h:
      form = "'Vt.%s, 'IFPNeon";
      nfd.SetFormatMap(0, &map_h);
      break;
    case "fmov_asimdimm_s_s"_h:
      form = "'Vt.%s, 'IFPNeon";
      nfd.SetFormatMap(0, &map_s);
      break;
    case "fmov_asimdimm_d2_d"_h:
      form = "'Vt.2d, 'IFPNeon";
      break;
  }

  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::DisassembleNEONScalar2RegMiscOnlyD(
    const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Dd, 'Dn";
  const char *suffix = ", #0";
  if (instr->GetNEONSize() != 3) {
    mnemonic = NULL;
  }
  switch (form_hash_) {
    case "abs_asisdmisc_r"_h:
    case "neg_asisdmisc_r"_h:
      suffix = NULL;
  }
  Format(instr, mnemonic, form, suffix);
}

void Disassembler::DisassembleNEONFPScalar2RegMisc(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn";
  const char *suffix = NULL;
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::FPScalarFormatMap());
  switch (form_hash_) {
    case "fcmeq_asisdmisc_fz"_h:
    case "fcmge_asisdmisc_fz"_h:
    case "fcmgt_asisdmisc_fz"_h:
    case "fcmle_asisdmisc_fz"_h:
    case "fcmlt_asisdmisc_fz"_h:
      suffix = ", #0.0";
      break;
    case "fcvtxn_asisdmisc_n"_h:
      if (nfd.GetVectorFormat(0) == kFormatS) {  // Source format.
        mnemonic = NULL;
      }
      form = "'Sd, 'Dn";
  }
  Format(instr, mnemonic, nfd.SubstitutePlaceholders(form), suffix);
}

void Disassembler::VisitNEONScalar2RegMisc(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::ScalarFormatMap());
  switch (form_hash_) {
    case "sqxtn_asisdmisc_n"_h:
    case "sqxtun_asisdmisc_n"_h:
    case "uqxtn_asisdmisc_n"_h:
      nfd.SetFormatMap(1, nfd.LongScalarFormatMap());
  }
  Format(instr, mnemonic, nfd.SubstitutePlaceholders(form));
}

void Disassembler::VisitNEONScalar2RegMiscFP16(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Hd, 'Hn";
  const char *suffix = NULL;

  switch (form_hash_) {
    case "fcmeq_asisdmiscfp16_fz"_h:
    case "fcmge_asisdmiscfp16_fz"_h:
    case "fcmgt_asisdmiscfp16_fz"_h:
    case "fcmle_asisdmiscfp16_fz"_h:
    case "fcmlt_asisdmiscfp16_fz"_h:
      suffix = ", #0.0";
  }
  Format(instr, mnemonic, form, suffix);
}


void Disassembler::VisitNEONScalar3Diff(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn, %sm";
  NEONFormatDecoder nfd(instr,
                        NEONFormatDecoder::LongScalarFormatMap(),
                        NEONFormatDecoder::ScalarFormatMap());
  if (nfd.GetVectorFormat(0) == kFormatH) {
    mnemonic = NULL;
  }
  Format(instr, mnemonic, nfd.SubstitutePlaceholders(form));
}

void Disassembler::DisassembleNEONFPScalar3Same(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn, %sm";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::FPScalarFormatMap());
  Format(instr, mnemonic, nfd.SubstitutePlaceholders(form));
}

void Disassembler::DisassembleNEONScalar3SameOnlyD(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Dd, 'Dn, 'Dm";
  if (instr->GetNEONSize() != 3) {
    mnemonic = NULL;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitNEONScalar3Same(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn, %sm";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::ScalarFormatMap());
  VectorFormat vform = nfd.GetVectorFormat(0);
  switch (form_hash_) {
    case "srshl_asisdsame_only"_h:
    case "urshl_asisdsame_only"_h:
    case "sshl_asisdsame_only"_h:
    case "ushl_asisdsame_only"_h:
      if (vform != kFormatD) {
        mnemonic = NULL;
      }
      break;
    case "sqdmulh_asisdsame_only"_h:
    case "sqrdmulh_asisdsame_only"_h:
    case "sqrdmlah_asisdsame2_only"_h:
    case "sqrdmlsh_asisdsame2_only"_h:
      if ((vform == kFormatB) || (vform == kFormatD)) {
        mnemonic = NULL;
      }
  }
  Format(instr, mnemonic, nfd.SubstitutePlaceholders(form));
}

void Disassembler::VisitNEONScalar3SameFP16(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Hd, 'Hn, 'Hm");
}

void Disassembler::VisitNEONScalar3SameExtra(const Instruction *instr) {
  USE(instr);
  // Nothing to do - handled by VisitNEONScalar3Same.
  VIXL_UNREACHABLE();
}

void Disassembler::DisassembleNEONScalarSatMulLongIndex(
    const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn, 'Vf.%s['IVByElemIndex]";
  NEONFormatDecoder nfd(instr,
                        NEONFormatDecoder::LongScalarFormatMap(),
                        NEONFormatDecoder::ScalarFormatMap());
  if (nfd.GetVectorFormat(0) == kFormatH) {
    mnemonic = NULL;
  }
  Format(instr,
         mnemonic,
         nfd.Substitute(form, nfd.kPlaceholder, nfd.kPlaceholder, nfd.kFormat));
}

void Disassembler::DisassembleNEONFPScalarMulIndex(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn, 'Vf.%s['IVByElemIndex]";
  static const NEONFormatMap map = {{23, 22}, {NF_H, NF_UNDEF, NF_S, NF_D}};
  NEONFormatDecoder nfd(instr, &map);
  Format(instr,
         mnemonic,
         nfd.Substitute(form, nfd.kPlaceholder, nfd.kPlaceholder, nfd.kFormat));
}

void Disassembler::VisitNEONScalarByIndexedElement(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn, 'Vf.%s['IVByElemIndex]";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::ScalarFormatMap());
  VectorFormat vform_dst = nfd.GetVectorFormat(0);
  if ((vform_dst == kFormatB) || (vform_dst == kFormatD)) {
    mnemonic = NULL;
  }
  Format(instr,
         mnemonic,
         nfd.Substitute(form, nfd.kPlaceholder, nfd.kPlaceholder, nfd.kFormat));
}


void Disassembler::VisitNEONScalarCopy(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(NEONScalarCopy)";

  NEONFormatDecoder nfd(instr, NEONFormatDecoder::TriangularScalarFormatMap());

  if (instr->Mask(NEONScalarCopyMask) == NEON_DUP_ELEMENT_scalar) {
    mnemonic = "mov";
    form = "%sd, 'Vn.%s['IVInsIndex1]";
  }

  Format(instr, mnemonic, nfd.Substitute(form, nfd.kPlaceholder, nfd.kFormat));
}


void Disassembler::VisitNEONScalarPairwise(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  if (form_hash_ == "addp_asisdpair_only"_h) {
    // All pairwise operations except ADDP use bit U to differentiate FP16
    // from FP32/FP64 variations.
    if (instr->GetNEONSize() != 3) {
      mnemonic = NULL;
    }
    Format(instr, mnemonic, "'Dd, 'Vn.2d");
  } else {
    const char *form = "%sd, 'Vn.2%s";
    NEONFormatDecoder nfd(instr,
                          NEONFormatDecoder::FPScalarPairwiseFormatMap());

    Format(instr,
           mnemonic,
           nfd.Substitute(form,
                          NEONFormatDecoder::kPlaceholder,
                          NEONFormatDecoder::kFormat));
  }
}

void Disassembler::DisassembleNEONScalarShiftImmOnlyD(
    const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Dd, 'Dn, ";
  const char *suffix = "'IsR";

  if (instr->ExtractBit(22) == 0) {
    // Only D registers are supported.
    mnemonic = NULL;
  }

  switch (form_hash_) {
    case "shl_asisdshf_r"_h:
    case "sli_asisdshf_r"_h:
      suffix = "'IsL";
  }

  Format(instr, mnemonic, form, suffix);
}

void Disassembler::DisassembleNEONScalarShiftRightNarrowImm(
    const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn, 'IsR";
  static const NEONFormatMap map_dst =
      {{22, 21, 20, 19}, {NF_UNDEF, NF_B, NF_H, NF_H, NF_S, NF_S, NF_S, NF_S}};
  static const NEONFormatMap map_src =
      {{22, 21, 20, 19}, {NF_UNDEF, NF_H, NF_S, NF_S, NF_D, NF_D, NF_D, NF_D}};
  NEONFormatDecoder nfd(instr, &map_dst, &map_src);
  Format(instr, mnemonic, nfd.SubstitutePlaceholders(form));
}

void Disassembler::VisitNEONScalarShiftImmediate(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "%sd, %sn, ";
  const char *suffix = "'IsR";

  // clang-format off
  static const NEONFormatMap map = {{22, 21, 20, 19},
                                    {NF_UNDEF, NF_B, NF_H, NF_H,
                                     NF_S,     NF_S, NF_S, NF_S,
                                     NF_D,     NF_D, NF_D, NF_D,
                                     NF_D,     NF_D, NF_D, NF_D}};
  // clang-format on
  NEONFormatDecoder nfd(instr, &map);
  switch (form_hash_) {
    case "sqshlu_asisdshf_r"_h:
    case "sqshl_asisdshf_r"_h:
    case "uqshl_asisdshf_r"_h:
      suffix = "'IsL";
      break;
    default:
      if (nfd.GetVectorFormat(0) == kFormatB) {
        mnemonic = NULL;
      }
  }
  Format(instr, mnemonic, nfd.SubstitutePlaceholders(form), suffix);
}

void Disassembler::DisassembleNEONShiftLeftLongImm(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s";
  const char *suffix = ", 'IsL";

  NEONFormatDecoder nfd(instr,
                        NEONFormatDecoder::ShiftLongNarrowImmFormatMap(),
                        NEONFormatDecoder::ShiftImmFormatMap());

  if (instr->GetImmNEONImmb() == 0 &&
      CountSetBits(instr->GetImmNEONImmh(), 32) == 1) {  // xtl variant.
    VIXL_ASSERT((form_hash_ == "sshll_asimdshf_l"_h) ||
                (form_hash_ == "ushll_asimdshf_l"_h));
    mnemonic = (form_hash_ == "sshll_asimdshf_l"_h) ? "sxtl" : "uxtl";
    suffix = NULL;
  }
  Format(instr, nfd.Mnemonic(mnemonic), nfd.Substitute(form), suffix);
}

void Disassembler::DisassembleNEONShiftRightImm(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'IsR";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::ShiftImmFormatMap());

  VectorFormat vform_dst = nfd.GetVectorFormat(0);
  if (vform_dst != kFormatUndefined) {
    uint32_t ls_dst = LaneSizeInBitsFromFormat(vform_dst);
    switch (form_hash_) {
      case "scvtf_asimdshf_c"_h:
      case "ucvtf_asimdshf_c"_h:
      case "fcvtzs_asimdshf_c"_h:
      case "fcvtzu_asimdshf_c"_h:
        if (ls_dst == kBRegSize) {
          mnemonic = NULL;
        }
        break;
    }
  }
  Format(instr, mnemonic, nfd.Substitute(form));
}

void Disassembler::DisassembleNEONShiftRightNarrowImm(
    const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'IsR";

  NEONFormatDecoder nfd(instr,
                        NEONFormatDecoder::ShiftImmFormatMap(),
                        NEONFormatDecoder::ShiftLongNarrowImmFormatMap());
  Format(instr, nfd.Mnemonic(mnemonic), nfd.Substitute(form));
}

void Disassembler::VisitNEONShiftImmediate(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Vd.%s, 'Vn.%s, 'IsL";
  NEONFormatDecoder nfd(instr, NEONFormatDecoder::ShiftImmFormatMap());
  Format(instr, mnemonic, nfd.Substitute(form));
}


void Disassembler::VisitNEONTable(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char form_1v[] = "'Vd.%%s, {'Vn.16b}, 'Vm.%%s";
  const char form_2v[] = "'Vd.%%s, {'Vn.16b, v%d.16b}, 'Vm.%%s";
  const char form_3v[] = "'Vd.%%s, {'Vn.16b, v%d.16b, v%d.16b}, 'Vm.%%s";
  const char form_4v[] =
      "'Vd.%%s, {'Vn.16b, v%d.16b, v%d.16b, v%d.16b}, 'Vm.%%s";
  const char *form = form_1v;

  NEONFormatDecoder nfd(instr, NEONFormatDecoder::LogicalFormatMap());

  switch (form_hash_) {
    case "tbl_asimdtbl_l2_2"_h:
    case "tbx_asimdtbl_l2_2"_h:
      form = form_2v;
      break;
    case "tbl_asimdtbl_l3_3"_h:
    case "tbx_asimdtbl_l3_3"_h:
      form = form_3v;
      break;
    case "tbl_asimdtbl_l4_4"_h:
    case "tbx_asimdtbl_l4_4"_h:
      form = form_4v;
      break;
  }
  VIXL_ASSERT(form != NULL);

  char re_form[sizeof(form_4v) + 6];  // 3 * two-digit substitutions => 6
  int reg_num = instr->GetRn();
  snprintf(re_form,
           sizeof(re_form),
           form,
           (reg_num + 1) % kNumberOfVRegisters,
           (reg_num + 2) % kNumberOfVRegisters,
           (reg_num + 3) % kNumberOfVRegisters);

  Format(instr, mnemonic, nfd.Substitute(re_form));
}


void Disassembler::VisitNEONPerm(const Instruction *instr) {
  NEONFormatDecoder nfd(instr);
  FormatWithDecodedMnemonic(instr, nfd.Substitute("'Vd.%s, 'Vn.%s, 'Vm.%s"));
}

void Disassembler::Disassemble_Vd4S_Vn16B_Vm16B(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Vd.4s, 'Vn.16b, 'Vm.16b");
}

void Disassembler::
    VisitSVE32BitGatherLoadHalfwords_ScalarPlus32BitScaledOffsets(
        const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "{'Zt.s}, 'Pgl/z, ['Xns, 'Zm.s, '?22:suxtw #1]");
}

void Disassembler::VisitSVE32BitGatherLoadWords_ScalarPlus32BitScaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "{'Zt.s}, 'Pgl/z, ['Xns, 'Zm.s, '?22:suxtw #2]");
}

void Disassembler::VisitSVE32BitGatherLoad_ScalarPlus32BitUnscaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "{'Zt.s}, 'Pgl/z, ['Xns, 'Zm.s, '?22:suxtw]");
}

void Disassembler::VisitSVE32BitGatherLoad_VectorPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.s}, 'Pgl/z, ['Zn.s]";
  const char *form_imm = "{'Zt.s}, 'Pgl/z, ['Zn.s, #'u2016]";
  const char *form_imm_h = "{'Zt.s}, 'Pgl/z, ['Zn.s, #'u2016*2]";
  const char *form_imm_w = "{'Zt.s}, 'Pgl/z, ['Zn.s, #'u2016*4]";

  const char *mnemonic = mnemonic_.c_str();
  switch (form_hash_) {
    case "ld1h_z_p_ai_s"_h:
    case "ld1sh_z_p_ai_s"_h:
    case "ldff1h_z_p_ai_s"_h:
    case "ldff1sh_z_p_ai_s"_h:
      form_imm = form_imm_h;
      break;
    case "ld1w_z_p_ai_s"_h:
    case "ldff1w_z_p_ai_s"_h:
      form_imm = form_imm_w;
      break;
  }
  if (instr->ExtractBits(20, 16) != 0) form = form_imm;

  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVE32BitGatherPrefetch_ScalarPlus32BitScaledOffsets(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "'prefSVEOp, 'Pgl, ['Xns, 'Zm.s, '?22:suxtw";
  const char *suffix = NULL;

  switch (
      instr->Mask(SVE32BitGatherPrefetch_ScalarPlus32BitScaledOffsetsMask)) {
    case PRFB_i_p_bz_s_x32_scaled:
      mnemonic = "prfb";
      suffix = "]";
      break;
    case PRFD_i_p_bz_s_x32_scaled:
      mnemonic = "prfd";
      suffix = " #3]";
      break;
    case PRFH_i_p_bz_s_x32_scaled:
      mnemonic = "prfh";
      suffix = " #1]";
      break;
    case PRFW_i_p_bz_s_x32_scaled:
      mnemonic = "prfw";
      suffix = " #2]";
      break;
    default:
      form = "(SVE32BitGatherPrefetch_ScalarPlus32BitScaledOffsets)";
      break;
  }
  Format(instr, mnemonic, form, suffix);
}

void Disassembler::VisitSVE32BitGatherPrefetch_VectorPlusImm(
    const Instruction *instr) {
  const char *form = (instr->ExtractBits(20, 16) != 0)
                         ? "'prefSVEOp, 'Pgl, ['Zn.s, #'u2016]"
                         : "'prefSVEOp, 'Pgl, ['Zn.s]";
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVE32BitScatterStore_ScalarPlus32BitScaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "{'Zt.s}, 'Pgl, ['Xns, 'Zm.s, '?14:suxtw #'u2423]");
}

void Disassembler::VisitSVE32BitScatterStore_ScalarPlus32BitUnscaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "{'Zt.s}, 'Pgl, ['Xns, 'Zm.s, '?14:suxtw]");
}

void Disassembler::VisitSVE32BitScatterStore_VectorPlusImm(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "{'Zt.s}, 'Pgl, ['Zn.s";
  const char *suffix = NULL;

  bool is_zero = instr->ExtractBits(20, 16) == 0;

  switch (instr->Mask(SVE32BitScatterStore_VectorPlusImmMask)) {
    case ST1B_z_p_ai_s:
      mnemonic = "st1b";
      suffix = is_zero ? "]" : ", #'u2016]";
      break;
    case ST1H_z_p_ai_s:
      mnemonic = "st1h";
      suffix = is_zero ? "]" : ", #'u2016*2]";
      break;
    case ST1W_z_p_ai_s:
      mnemonic = "st1w";
      suffix = is_zero ? "]" : ", #'u2016*4]";
      break;
    default:
      form = "(SVE32BitScatterStore_VectorPlusImm)";
      break;
  }
  Format(instr, mnemonic, form, suffix);
}

void Disassembler::VisitSVE64BitGatherLoad_ScalarPlus32BitUnpackedScaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "{'Zt.d}, 'Pgl/z, ['Xns, 'Zm.d, '?22:suxtw "
                            "#'u2423]");
}

void Disassembler::VisitSVE64BitGatherLoad_ScalarPlus64BitScaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "{'Zt.d}, 'Pgl/z, ['Xns, 'Zm.d, lsl #'u2423]");
}

void Disassembler::VisitSVE64BitGatherLoad_ScalarPlus64BitUnscaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "{'Zt.d}, 'Pgl/z, ['Xns, 'Zm.d]");
}

void Disassembler::
    VisitSVE64BitGatherLoad_ScalarPlusUnpacked32BitUnscaledOffsets(
        const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "{'Zt.d}, 'Pgl/z, ['Xns, 'Zm.d, '?22:suxtw]");
}

void Disassembler::VisitSVE64BitGatherLoad_VectorPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.d}, 'Pgl/z, ['Zn.d]";
  const char *form_imm[4] = {"{'Zt.d}, 'Pgl/z, ['Zn.d, #'u2016]",
                             "{'Zt.d}, 'Pgl/z, ['Zn.d, #'u2016*2]",
                             "{'Zt.d}, 'Pgl/z, ['Zn.d, #'u2016*4]",
                             "{'Zt.d}, 'Pgl/z, ['Zn.d, #'u2016*8]"};

  if (instr->ExtractBits(20, 16) != 0) {
    unsigned msz = instr->ExtractBits(24, 23);
    bool sign_extend = instr->ExtractBit(14) == 0;
    if ((msz == kDRegSizeInBytesLog2) && sign_extend) {
      form = "(SVE64BitGatherLoad_VectorPlusImm)";
    } else {
      VIXL_ASSERT(msz < ArrayLength(form_imm));
      form = form_imm[msz];
    }
  }

  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVE64BitGatherPrefetch_ScalarPlus64BitScaledOffsets(
    const Instruction *instr) {
  const char *form = "'prefSVEOp, 'Pgl, ['Xns, 'Zm.d";
  const char *suffix = "]";

  switch (form_hash_) {
    case "prfh_i_p_bz_d_64_scaled"_h:
      suffix = ", lsl #1]";
      break;
    case "prfs_i_p_bz_d_64_scaled"_h:
      suffix = ", lsl #2]";
      break;
    case "prfd_i_p_bz_d_64_scaled"_h:
      suffix = ", lsl #3]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::
    VisitSVE64BitGatherPrefetch_ScalarPlusUnpacked32BitScaledOffsets(
        const Instruction *instr) {
  const char *form = "'prefSVEOp, 'Pgl, ['Xns, 'Zm.d, '?22:suxtw ";
  const char *suffix = "]";

  switch (form_hash_) {
    case "prfh_i_p_bz_d_x32_scaled"_h:
      suffix = "#1]";
      break;
    case "prfs_i_p_bz_d_x32_scaled"_h:
      suffix = "#2]";
      break;
    case "prfd_i_p_bz_d_x32_scaled"_h:
      suffix = "#3]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVE64BitGatherPrefetch_VectorPlusImm(
    const Instruction *instr) {
  const char *form = (instr->ExtractBits(20, 16) != 0)
                         ? "'prefSVEOp, 'Pgl, ['Zn.d, #'u2016]"
                         : "'prefSVEOp, 'Pgl, ['Zn.d]";

  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVE64BitScatterStore_ScalarPlus64BitScaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "{'Zt.d}, 'Pgl, ['Xns, 'Zm.d, lsl #'u2423]");
}

void Disassembler::VisitSVE64BitScatterStore_ScalarPlus64BitUnscaledOffsets(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "{'Zt.d}, 'Pgl, ['Xns, 'Zm.d]");
}

void Disassembler::
    VisitSVE64BitScatterStore_ScalarPlusUnpacked32BitScaledOffsets(
        const Instruction *instr) {
  FormatWithDecodedMnemonic(instr,
                            "{'Zt.d}, 'Pgl, ['Xns, 'Zm.d, '?14:suxtw #'u2423]");
}

void Disassembler::
    VisitSVE64BitScatterStore_ScalarPlusUnpacked32BitUnscaledOffsets(
        const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "{'Zt.d}, 'Pgl, ['Xns, 'Zm.d, '?14:suxtw]");
}

void Disassembler::VisitSVE64BitScatterStore_VectorPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.d}, 'Pgl, ['Zn.d";
  const char *suffix = "]";

  if (instr->ExtractBits(20, 16) != 0) {
    switch (form_hash_) {
      case "st1b_z_p_ai_d"_h:
        suffix = ", #'u2016]";
        break;
      case "st1h_z_p_ai_d"_h:
        suffix = ", #'u2016*2]";
        break;
      case "st1w_z_p_ai_d"_h:
        suffix = ", #'u2016*4]";
        break;
      case "st1d_z_p_ai_d"_h:
        suffix = ", #'u2016*8]";
        break;
    }
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEBitwiseLogicalWithImm_Unpredicated(
    const Instruction *instr) {
  if (instr->GetSVEImmLogical() == 0) {
    // The immediate encoded in the instruction is not in the expected format.
    Format(instr, "unallocated", "(SVEBitwiseImm)");
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'tl, 'Zd.'tl, 'ITriSvel");
  }
}

void Disassembler::VisitSVEBitwiseLogical_Predicated(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t");
}

void Disassembler::VisitSVEBitwiseShiftByImm_Predicated(
    const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Zd.'tszp, 'Pgl/m, 'Zd.'tszp, ";
  const char *suffix = NULL;
  unsigned tsize = (instr->ExtractBits(23, 22) << 2) | instr->ExtractBits(9, 8);

  if (tsize == 0) {
    mnemonic = "unimplemented";
    form = "(SVEBitwiseShiftByImm_Predicated)";
  } else {
    switch (form_hash_) {
      case "lsl_z_p_zi"_h:
      case "sqshl_z_p_zi"_h:
      case "sqshlu_z_p_zi"_h:
      case "uqshl_z_p_zi"_h:
        suffix = "'ITriSvep";
        break;
      case "asrd_z_p_zi"_h:
      case "asr_z_p_zi"_h:
      case "lsr_z_p_zi"_h:
      case "srshr_z_p_zi"_h:
      case "urshr_z_p_zi"_h:
        suffix = "'ITriSveq";
        break;
      default:
        mnemonic = "unimplemented";
        form = "(SVEBitwiseShiftByImm_Predicated)";
        break;
    }
  }
  Format(instr, mnemonic, form, suffix);
}

void Disassembler::VisitSVEBitwiseShiftByVector_Predicated(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t");
}

void Disassembler::VisitSVEBitwiseShiftByWideElements_Predicated(
    const Instruction *instr) {
  if (instr->GetSVESize() == kDRegSizeInBytesLog2) {
    Format(instr, "unallocated", "(SVEBitwiseShiftByWideElements_Predicated)");
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.d");
  }
}

static bool SVEMoveMaskPreferred(uint64_t value, int lane_bytes_log2) {
  VIXL_ASSERT(IsUintN(8 << lane_bytes_log2, value));

  // Duplicate lane-sized value across double word.
  switch (lane_bytes_log2) {
    case 0:
      value *= 0x0101010101010101;
      break;
    case 1:
      value *= 0x0001000100010001;
      break;
    case 2:
      value *= 0x0000000100000001;
      break;
    case 3:  // Nothing to do
      break;
    default:
      VIXL_UNREACHABLE();
  }

  if ((value & 0xff) == 0) {
    // Check for 16-bit patterns. Set least-significant 16 bits, to make tests
    // easier; we already checked least-significant byte is zero above.
    uint64_t generic_value = value | 0xffff;

    // Check 0x00000000_0000pq00 or 0xffffffff_ffffpq00.
    if ((generic_value == 0xffff) || (generic_value == UINT64_MAX)) {
      return false;
    }

    // Check 0x0000pq00_0000pq00 or 0xffffpq00_ffffpq00.
    if (AllWordsMatch(value)) {
      generic_value &= 0xffffffff;
      if ((generic_value == 0xffff) || (generic_value == UINT32_MAX)) {
        return false;
      }
    }

    // Check 0xpq00pq00_pq00pq00.
    if (AllHalfwordsMatch(value)) {
      return false;
    }
  } else {
    // Check for 8-bit patterns. Set least-significant byte, to make tests
    // easier.
    uint64_t generic_value = value | 0xff;

    // Check 0x00000000_000000pq or 0xffffffff_ffffffpq.
    if ((generic_value == 0xff) || (generic_value == UINT64_MAX)) {
      return false;
    }

    // Check 0x000000pq_000000pq or 0xffffffpq_ffffffpq.
    if (AllWordsMatch(value)) {
      generic_value &= 0xffffffff;
      if ((generic_value == 0xff) || (generic_value == UINT32_MAX)) {
        return false;
      }
    }

    // Check 0x00pq00pq_00pq00pq or 0xffpqffpq_ffpqffpq.
    if (AllHalfwordsMatch(value)) {
      generic_value &= 0xffff;
      if ((generic_value == 0xff) || (generic_value == UINT16_MAX)) {
        return false;
      }
    }

    // Check 0xpqpqpqpq_pqpqpqpq.
    if (AllBytesMatch(value)) {
      return false;
    }
  }
  return true;
}

void Disassembler::VisitSVEBroadcastBitmaskImm(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVEBroadcastBitmaskImm)";

  switch (instr->Mask(SVEBroadcastBitmaskImmMask)) {
    case DUPM_z_i: {
      uint64_t imm = instr->GetSVEImmLogical();
      if (imm != 0) {
        int lane_size = instr->GetSVEBitwiseImmLaneSizeInBytesLog2();
        mnemonic = SVEMoveMaskPreferred(imm, lane_size) ? "mov" : "dupm";
        form = "'Zd.'tl, 'ITriSvel";
      }
      break;
    }
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEBroadcastFPImm_Unpredicated(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVEBroadcastFPImm_Unpredicated)";

  if (instr->GetSVEVectorFormat() != kFormatVnB) {
    switch (instr->Mask(SVEBroadcastFPImm_UnpredicatedMask)) {
      case FDUP_z_i:
        // The preferred disassembly for fdup is "fmov".
        mnemonic = "fmov";
        form = "'Zd.'t, 'IFPSve";
        break;
      default:
        break;
    }
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEBroadcastGeneralRegister(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVEBroadcastGeneralRegister)";

  switch (instr->Mask(SVEBroadcastGeneralRegisterMask)) {
    case DUP_z_r:
      // The preferred disassembly for dup is "mov".
      mnemonic = "mov";
      if (instr->GetSVESize() == kDRegSizeInBytesLog2) {
        form = "'Zd.'t, 'Xns";
      } else {
        form = "'Zd.'t, 'Wns";
      }
      break;
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEBroadcastIndexElement(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVEBroadcastIndexElement)";

  switch (instr->Mask(SVEBroadcastIndexElementMask)) {
    case DUP_z_zi: {
      // The tsz field must not be zero.
      int tsz = instr->ExtractBits(20, 16);
      if (tsz != 0) {
        // The preferred disassembly for dup is "mov".
        mnemonic = "mov";
        int imm2 = instr->ExtractBits(23, 22);
        if ((CountSetBits(imm2) + CountSetBits(tsz)) == 1) {
          // If imm2:tsz has one set bit, the index is zero. This is
          // disassembled as a mov from a b/h/s/d/q scalar register.
          form = "'Zd.'ti, 'ti'u0905";
        } else {
          form = "'Zd.'ti, 'Zn.'ti['IVInsSVEIndex]";
        }
      }
      break;
    }
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEBroadcastIntImm_Unpredicated(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVEBroadcastIntImm_Unpredicated)";

  switch (instr->Mask(SVEBroadcastIntImm_UnpredicatedMask)) {
    case DUP_z_i:
      // The encoding of byte-sized lanes with lsl #8 is undefined.
      if ((instr->GetSVEVectorFormat() == kFormatVnB) &&
          (instr->ExtractBit(13) == 1))
        break;

      // The preferred disassembly for dup is "mov".
      mnemonic = "mov";
      form = (instr->ExtractBit(13) == 0) ? "'Zd.'t, #'s1205"
                                          : "'Zd.'t, #'s1205, lsl #8";
      break;
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVECompressActiveElements(const Instruction *instr) {
  // The top bit of size is always set for compact, so 't can only be
  // substituted with types S and D.
  if (instr->ExtractBit(23) == 1) {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl, 'Zn.'t");
  } else {
    VisitUnallocated(instr);
  }
}

void Disassembler::VisitSVEConditionallyBroadcastElementToVector(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl, 'Zd.'t, 'Zn.'t");
}

void Disassembler::VisitSVEConditionallyExtractElementToGeneralRegister(
    const Instruction *instr) {
  const char *form = "'Wd, 'Pgl, 'Wd, 'Zn.'t";

  if (instr->GetSVESize() == kDRegSizeInBytesLog2) {
    form = "'Xd, p'u1210, 'Xd, 'Zn.'t";
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEConditionallyExtractElementToSIMDFPScalar(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'t'u0400, 'Pgl, 't'u0400, 'Zn.'t");
}

void Disassembler::VisitSVEConditionallyTerminateScalars(
    const Instruction *instr) {
  const char *form = (instr->ExtractBit(22) == 0) ? "'Wn, 'Wm" : "'Xn, 'Xm";
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEConstructivePrefix_Unpredicated(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd, 'Zn");
}

void Disassembler::VisitSVEContiguousFirstFaultLoad_ScalarPlusScalar(
    const Instruction *instr) {
  const char *form = "{'Zt.'tlss}, 'Pgl/z, ['Xns";
  const char *suffix = "]";

  if (instr->GetRm() != kZeroRegCode) {
    switch (form_hash_) {
      case "ldff1b_z_p_br_u8"_h:
      case "ldff1b_z_p_br_u16"_h:
      case "ldff1b_z_p_br_u32"_h:
      case "ldff1b_z_p_br_u64"_h:
      case "ldff1sb_z_p_br_s16"_h:
      case "ldff1sb_z_p_br_s32"_h:
      case "ldff1sb_z_p_br_s64"_h:
        suffix = ", 'Xm]";
        break;
      case "ldff1h_z_p_br_u16"_h:
      case "ldff1h_z_p_br_u32"_h:
      case "ldff1h_z_p_br_u64"_h:
      case "ldff1sh_z_p_br_s32"_h:
      case "ldff1sh_z_p_br_s64"_h:
        suffix = ", 'Xm, lsl #1]";
        break;
      case "ldff1w_z_p_br_u32"_h:
      case "ldff1w_z_p_br_u64"_h:
      case "ldff1sw_z_p_br_s64"_h:
        suffix = ", 'Xm, lsl #2]";
        break;
      case "ldff1d_z_p_br_u64"_h:
        suffix = ", 'Xm, lsl #3]";
        break;
    }
  }

  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEContiguousNonFaultLoad_ScalarPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.'tlss}, 'Pgl/z, ['Xns";
  const char *suffix =
      (instr->ExtractBits(19, 16) == 0) ? "]" : ", #'s1916, mul vl]";
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEContiguousNonTemporalLoad_ScalarPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.b}, 'Pgl/z, ['Xns";
  const char *suffix =
      (instr->ExtractBits(19, 16) == 0) ? "]" : ", #'s1916, mul vl]";
  switch (form_hash_) {
    case "ldnt1d_z_p_bi_contiguous"_h:
      form = "{'Zt.d}, 'Pgl/z, ['Xns";
      break;
    case "ldnt1h_z_p_bi_contiguous"_h:
      form = "{'Zt.h}, 'Pgl/z, ['Xns";
      break;
    case "ldnt1w_z_p_bi_contiguous"_h:
      form = "{'Zt.s}, 'Pgl/z, ['Xns";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEContiguousNonTemporalLoad_ScalarPlusScalar(
    const Instruction *instr) {
  const char *form = "{'Zt.b}, 'Pgl/z, ['Xns, 'Rm]";
  switch (form_hash_) {
    case "ldnt1d_z_p_br_contiguous"_h:
      form = "{'Zt.d}, 'Pgl/z, ['Xns, 'Rm, lsl #3]";
      break;
    case "ldnt1h_z_p_br_contiguous"_h:
      form = "{'Zt.h}, 'Pgl/z, ['Xns, 'Rm, lsl #1]";
      break;
    case "ldnt1w_z_p_br_contiguous"_h:
      form = "{'Zt.s}, 'Pgl/z, ['Xns, 'Rm, lsl #2]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEContiguousNonTemporalStore_ScalarPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.b}, 'Pgl, ['Xns";
  const char *suffix =
      (instr->ExtractBits(19, 16) == 0) ? "]" : ", #'s1916, mul vl]";

  switch (form_hash_) {
    case "stnt1d_z_p_bi_contiguous"_h:
      form = "{'Zt.d}, 'Pgl, ['Xns";
      break;
    case "stnt1h_z_p_bi_contiguous"_h:
      form = "{'Zt.h}, 'Pgl, ['Xns";
      break;
    case "stnt1w_z_p_bi_contiguous"_h:
      form = "{'Zt.s}, 'Pgl, ['Xns";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEContiguousNonTemporalStore_ScalarPlusScalar(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVEContiguousNonTemporalStore_ScalarPlusScalar)";

  switch (instr->Mask(SVEContiguousNonTemporalStore_ScalarPlusScalarMask)) {
    case STNT1B_z_p_br_contiguous:
      mnemonic = "stnt1b";
      form = "{'Zt.b}, 'Pgl, ['Xns, 'Rm]";
      break;
    case STNT1D_z_p_br_contiguous:
      mnemonic = "stnt1d";
      form = "{'Zt.d}, 'Pgl, ['Xns, 'Rm, lsl #3]";
      break;
    case STNT1H_z_p_br_contiguous:
      mnemonic = "stnt1h";
      form = "{'Zt.h}, 'Pgl, ['Xns, 'Rm, lsl #1]";
      break;
    case STNT1W_z_p_br_contiguous:
      mnemonic = "stnt1w";
      form = "{'Zt.s}, 'Pgl, ['Xns, 'Rm, lsl #2]";
      break;
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEContiguousPrefetch_ScalarPlusImm(
    const Instruction *instr) {
  const char *form = (instr->ExtractBits(21, 16) != 0)
                         ? "'prefSVEOp, 'Pgl, ['Xns, #'s2116, mul vl]"
                         : "'prefSVEOp, 'Pgl, ['Xns]";
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEContiguousPrefetch_ScalarPlusScalar(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVEContiguousPrefetch_ScalarPlusScalar)";

  if (instr->GetRm() != kZeroRegCode) {
    switch (instr->Mask(SVEContiguousPrefetch_ScalarPlusScalarMask)) {
      case PRFB_i_p_br_s:
        mnemonic = "prfb";
        form = "'prefSVEOp, 'Pgl, ['Xns, 'Rm]";
        break;
      case PRFD_i_p_br_s:
        mnemonic = "prfd";
        form = "'prefSVEOp, 'Pgl, ['Xns, 'Rm, lsl #3]";
        break;
      case PRFH_i_p_br_s:
        mnemonic = "prfh";
        form = "'prefSVEOp, 'Pgl, ['Xns, 'Rm, lsl #1]";
        break;
      case PRFW_i_p_br_s:
        mnemonic = "prfw";
        form = "'prefSVEOp, 'Pgl, ['Xns, 'Rm, lsl #2]";
        break;
      default:
        break;
    }
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEContiguousStore_ScalarPlusImm(
    const Instruction *instr) {
  // The 'size' field isn't in the usual place here.
  const char *form = "{'Zt.'tls}, 'Pgl, ['Xns, #'s1916, mul vl]";
  if (instr->ExtractBits(19, 16) == 0) {
    form = "{'Zt.'tls}, 'Pgl, ['Xns]";
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEContiguousStore_ScalarPlusScalar(
    const Instruction *instr) {
  // The 'size' field isn't in the usual place here.
  FormatWithDecodedMnemonic(instr, "{'Zt.'tls}, 'Pgl, ['Xns, 'Xm'NSveS]");
}

void Disassembler::VisitSVECopyFPImm_Predicated(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVECopyFPImm_Predicated)";

  if (instr->GetSVEVectorFormat() != kFormatVnB) {
    switch (instr->Mask(SVECopyFPImm_PredicatedMask)) {
      case FCPY_z_p_i:
        // The preferred disassembly for fcpy is "fmov".
        mnemonic = "fmov";
        form = "'Zd.'t, 'Pm/m, 'IFPSve";
        break;
      default:
        break;
    }
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVECopyGeneralRegisterToVector_Predicated(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVECopyGeneralRegisterToVector_Predicated)";

  switch (instr->Mask(SVECopyGeneralRegisterToVector_PredicatedMask)) {
    case CPY_z_p_r:
      // The preferred disassembly for cpy is "mov".
      mnemonic = "mov";
      form = "'Zd.'t, 'Pgl/m, 'Wns";
      if (instr->GetSVESize() == kXRegSizeInBytesLog2) {
        form = "'Zd.'t, 'Pgl/m, 'Xns";
      }
      break;
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVECopyIntImm_Predicated(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVECopyIntImm_Predicated)";
  const char *suffix = NULL;

  switch (instr->Mask(SVECopyIntImm_PredicatedMask)) {
    case CPY_z_p_i: {
      // The preferred disassembly for cpy is "mov".
      mnemonic = "mov";
      form = "'Zd.'t, 'Pm/'?14:mz, #'s1205";
      if (instr->ExtractBit(13) != 0) suffix = ", lsl #8";
      break;
    }
    default:
      break;
  }
  Format(instr, mnemonic, form, suffix);
}

void Disassembler::VisitSVECopySIMDFPScalarRegisterToVector_Predicated(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVECopySIMDFPScalarRegisterToVector_Predicated)";

  switch (instr->Mask(SVECopySIMDFPScalarRegisterToVector_PredicatedMask)) {
    case CPY_z_p_v:
      // The preferred disassembly for cpy is "mov".
      mnemonic = "mov";
      form = "'Zd.'t, 'Pgl/m, 'Vnv";
      break;
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEExtractElementToGeneralRegister(
    const Instruction *instr) {
  const char *form = "'Wd, 'Pgl, 'Zn.'t";
  if (instr->GetSVESize() == kDRegSizeInBytesLog2) {
    form = "'Xd, p'u1210, 'Zn.'t";
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEExtractElementToSIMDFPScalarRegister(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'t'u0400, 'Pgl, 'Zn.'t");
}

void Disassembler::VisitSVEFFRInitialise(const Instruction *instr) {
  DisassembleNoArgs(instr);
}

void Disassembler::VisitSVEFFRWriteFromPredicate(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pn.b");
}

void Disassembler::VisitSVEFPArithmeticWithImm_Predicated(
    const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl/m, 'Zd.'t, #";
  const char *suffix00 = "0.0";
  const char *suffix05 = "0.5";
  const char *suffix10 = "1.0";
  const char *suffix20 = "2.0";
  int i1 = instr->ExtractBit(5);
  const char *suffix = i1 ? suffix10 : suffix00;

  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
    return;
  }

  switch (form_hash_) {
    case "fadd_z_p_zs"_h:
    case "fsubr_z_p_zs"_h:
    case "fsub_z_p_zs"_h:
      suffix = i1 ? suffix10 : suffix05;
      break;
    case "fmul_z_p_zs"_h:
      suffix = i1 ? suffix20 : suffix05;
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEFPArithmetic_Predicated(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t");
  }
}

void Disassembler::VisitSVEFPConvertPrecision(const Instruction *instr) {
  const char *form = NULL;

  switch (form_hash_) {
    case "fcvt_z_p_z_d2h"_h:
      form = "'Zd.h, 'Pgl/m, 'Zn.d";
      break;
    case "fcvt_z_p_z_d2s"_h:
      form = "'Zd.s, 'Pgl/m, 'Zn.d";
      break;
    case "fcvt_z_p_z_h2d"_h:
      form = "'Zd.d, 'Pgl/m, 'Zn.h";
      break;
    case "fcvt_z_p_z_h2s"_h:
      form = "'Zd.s, 'Pgl/m, 'Zn.h";
      break;
    case "fcvt_z_p_z_s2d"_h:
      form = "'Zd.d, 'Pgl/m, 'Zn.s";
      break;
    case "fcvt_z_p_z_s2h"_h:
      form = "'Zd.h, 'Pgl/m, 'Zn.s";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEFPConvertToInt(const Instruction *instr) {
  const char *form = NULL;

  switch (form_hash_) {
    case "fcvtzs_z_p_z_d2w"_h:
    case "fcvtzu_z_p_z_d2w"_h:
      form = "'Zd.s, 'Pgl/m, 'Zn.d";
      break;
    case "fcvtzs_z_p_z_d2x"_h:
    case "fcvtzu_z_p_z_d2x"_h:
      form = "'Zd.d, 'Pgl/m, 'Zn.d";
      break;
    case "fcvtzs_z_p_z_fp162h"_h:
    case "fcvtzu_z_p_z_fp162h"_h:
      form = "'Zd.h, 'Pgl/m, 'Zn.h";
      break;
    case "fcvtzs_z_p_z_fp162w"_h:
    case "fcvtzu_z_p_z_fp162w"_h:
      form = "'Zd.s, 'Pgl/m, 'Zn.h";
      break;
    case "fcvtzs_z_p_z_fp162x"_h:
    case "fcvtzu_z_p_z_fp162x"_h:
      form = "'Zd.d, 'Pgl/m, 'Zn.h";
      break;
    case "fcvtzs_z_p_z_s2w"_h:
    case "fcvtzu_z_p_z_s2w"_h:
      form = "'Zd.s, 'Pgl/m, 'Zn.s";
      break;
    case "fcvtzs_z_p_z_s2x"_h:
    case "fcvtzu_z_p_z_s2x"_h:
      form = "'Zd.d, 'Pgl/m, 'Zn.s";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEFPExponentialAccelerator(const Instruction *instr) {
  unsigned size = instr->GetSVESize();
  if ((size == kHRegSizeInBytesLog2) || (size == kSRegSizeInBytesLog2) ||
      (size == kDRegSizeInBytesLog2)) {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'t");
  } else {
    VisitUnallocated(instr);
  }
}

void Disassembler::VisitSVEFPRoundToIntegralValue(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zn.'t");
  }
}

void Disassembler::VisitSVEFPTrigMulAddCoefficient(const Instruction *instr) {
  unsigned size = instr->GetSVESize();
  if ((size == kHRegSizeInBytesLog2) || (size == kSRegSizeInBytesLog2) ||
      (size == kDRegSizeInBytesLog2)) {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zd.'t, 'Zn.'t, #'u1816");
  } else {
    VisitUnallocated(instr);
  }
}

void Disassembler::VisitSVEFPTrigSelectCoefficient(const Instruction *instr) {
  unsigned size = instr->GetSVESize();
  if ((size == kHRegSizeInBytesLog2) || (size == kSRegSizeInBytesLog2) ||
      (size == kDRegSizeInBytesLog2)) {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'t, 'Zm.'t");
  } else {
    VisitUnallocated(instr);
  }
}

void Disassembler::VisitSVEFPUnaryOp(const Instruction *instr) {
  if (instr->GetSVESize() == kBRegSizeInBytesLog2) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zn.'t");
  }
}

static const char *IncDecFormHelper(const Instruction *instr,
                                    const char *reg_pat_mul_form,
                                    const char *reg_pat_form,
                                    const char *reg_form) {
  if (instr->ExtractBits(19, 16) == 0) {
    if (instr->ExtractBits(9, 5) == SVE_ALL) {
      // Use the register only form if the multiplier is one (encoded as zero)
      // and the pattern is SVE_ALL.
      return reg_form;
    }
    // Use the register and pattern form if the multiplier is one.
    return reg_pat_form;
  }
  return reg_pat_mul_form;
}

void Disassembler::VisitSVEIncDecRegisterByElementCount(
    const Instruction *instr) {
  const char *form =
      IncDecFormHelper(instr, "'Xd, 'Ipc, mul #'u1916+1", "'Xd, 'Ipc", "'Xd");
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEIncDecVectorByElementCount(
    const Instruction *instr) {
  const char *form = IncDecFormHelper(instr,
                                      "'Zd.'t, 'Ipc, mul #'u1916+1",
                                      "'Zd.'t, 'Ipc",
                                      "'Zd.'t");
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEInsertGeneralRegister(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Wn";
  if (instr->GetSVESize() == kDRegSizeInBytesLog2) {
    form = "'Zd.'t, 'Xn";
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEInsertSIMDFPScalarRegister(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Vnv");
}

void Disassembler::VisitSVEIntAddSubtractImm_Unpredicated(
    const Instruction *instr) {
  const char *form = (instr->ExtractBit(13) == 0)
                         ? "'Zd.'t, 'Zd.'t, #'u1205"
                         : "'Zd.'t, 'Zd.'t, #'u1205, lsl #8";
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEIntAddSubtractVectors_Predicated(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t");
}

void Disassembler::VisitSVEIntCompareScalarCountAndLimit(
    const Instruction *instr) {
  const char *form =
      (instr->ExtractBit(12) == 0) ? "'Pd.'t, 'Wn, 'Wm" : "'Pd.'t, 'Xn, 'Xm";
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEIntConvertToFP(const Instruction *instr) {
  const char *form = NULL;
  switch (form_hash_) {
    case "scvtf_z_p_z_h2fp16"_h:
    case "ucvtf_z_p_z_h2fp16"_h:
      form = "'Zd.h, 'Pgl/m, 'Zn.h";
      break;
    case "scvtf_z_p_z_w2d"_h:
    case "ucvtf_z_p_z_w2d"_h:
      form = "'Zd.d, 'Pgl/m, 'Zn.s";
      break;
    case "scvtf_z_p_z_w2fp16"_h:
    case "ucvtf_z_p_z_w2fp16"_h:
      form = "'Zd.h, 'Pgl/m, 'Zn.s";
      break;
    case "scvtf_z_p_z_w2s"_h:
    case "ucvtf_z_p_z_w2s"_h:
      form = "'Zd.s, 'Pgl/m, 'Zn.s";
      break;
    case "scvtf_z_p_z_x2d"_h:
    case "ucvtf_z_p_z_x2d"_h:
      form = "'Zd.d, 'Pgl/m, 'Zn.d";
      break;
    case "scvtf_z_p_z_x2fp16"_h:
    case "ucvtf_z_p_z_x2fp16"_h:
      form = "'Zd.h, 'Pgl/m, 'Zn.d";
      break;
    case "scvtf_z_p_z_x2s"_h:
    case "ucvtf_z_p_z_x2s"_h:
      form = "'Zd.s, 'Pgl/m, 'Zn.d";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEIntDivideVectors_Predicated(
    const Instruction *instr) {
  unsigned size = instr->GetSVESize();
  if ((size == kSRegSizeInBytesLog2) || (size == kDRegSizeInBytesLog2)) {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t");
  } else {
    VisitUnallocated(instr);
  }
}

void Disassembler::VisitSVEIntMinMaxDifference_Predicated(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t");
}

void Disassembler::VisitSVEIntMinMaxImm_Unpredicated(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zd.'t, #";
  const char *suffix = "'u1205";

  switch (form_hash_) {
    case "smax_z_zi"_h:
    case "smin_z_zi"_h:
      suffix = "'s1205";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEIntMulImm_Unpredicated(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zd.'t, #'s1205");
}

void Disassembler::VisitSVEIntMulVectors_Predicated(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t");
}

void Disassembler::VisitSVELoadAndBroadcastElement(const Instruction *instr) {
  const char *form = "(SVELoadAndBroadcastElement)";
  const char *suffix_b = ", #'u2116]";
  const char *suffix_h = ", #'u2116*2]";
  const char *suffix_w = ", #'u2116*4]";
  const char *suffix_d = ", #'u2116*8]";
  const char *suffix = NULL;

  switch (form_hash_) {
    case "ld1rb_z_p_bi_u8"_h:
      form = "{'Zt.b}, 'Pgl/z, ['Xns";
      suffix = suffix_b;
      break;
    case "ld1rb_z_p_bi_u16"_h:
    case "ld1rsb_z_p_bi_s16"_h:
      form = "{'Zt.h}, 'Pgl/z, ['Xns";
      suffix = suffix_b;
      break;
    case "ld1rb_z_p_bi_u32"_h:
    case "ld1rsb_z_p_bi_s32"_h:
      form = "{'Zt.s}, 'Pgl/z, ['Xns";
      suffix = suffix_b;
      break;
    case "ld1rb_z_p_bi_u64"_h:
    case "ld1rsb_z_p_bi_s64"_h:
      form = "{'Zt.d}, 'Pgl/z, ['Xns";
      suffix = suffix_b;
      break;
    case "ld1rh_z_p_bi_u16"_h:
      form = "{'Zt.h}, 'Pgl/z, ['Xns";
      suffix = suffix_h;
      break;
    case "ld1rh_z_p_bi_u32"_h:
    case "ld1rsh_z_p_bi_s32"_h:
      form = "{'Zt.s}, 'Pgl/z, ['Xns";
      suffix = suffix_h;
      break;
    case "ld1rh_z_p_bi_u64"_h:
    case "ld1rsh_z_p_bi_s64"_h:
      form = "{'Zt.d}, 'Pgl/z, ['Xns";
      suffix = suffix_h;
      break;
    case "ld1rw_z_p_bi_u32"_h:
      form = "{'Zt.s}, 'Pgl/z, ['Xns";
      suffix = suffix_w;
      break;
    case "ld1rsw_z_p_bi_s64"_h:
    case "ld1rw_z_p_bi_u64"_h:
      form = "{'Zt.d}, 'Pgl/z, ['Xns";
      suffix = suffix_w;
      break;
    case "ld1rd_z_p_bi_u64"_h:
      form = "{'Zt.d}, 'Pgl/z, ['Xns";
      suffix = suffix_d;
      break;
  }

  // Hide curly brackets if immediate is zero.
  if (instr->ExtractBits(21, 16) == 0) {
    suffix = "]";
  }

  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.'tmsz}, 'Pgl/z, ['Xns";
  const char *suffix = ", #'s1916*16]";

  switch (form_hash_) {
    case "ld1rob_z_p_bi_u8"_h:
    case "ld1rod_z_p_bi_u64"_h:
    case "ld1roh_z_p_bi_u16"_h:
    case "ld1row_z_p_bi_u32"_h:
      suffix = ", #'s1916*32]";
      break;
  }
  if (instr->ExtractBits(19, 16) == 0) suffix = "]";

  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVELoadAndBroadcastQOWord_ScalarPlusScalar(
    const Instruction *instr) {
  const char *form = "{'Zt.'tmsz}, 'Pgl/z, ['Xns, ";
  const char *suffix = "'Rm, lsl #'u2423]";

  switch (form_hash_) {
    case "ld1rqb_z_p_br_contiguous"_h:
    case "ld1rob_z_p_br_contiguous"_h:
      suffix = "'Rm]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVELoadMultipleStructures_ScalarPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.'tmsz, 'Zt2.'tmsz}";
  const char *form_3 = "{'Zt.'tmsz, 'Zt2.'tmsz, 'Zt3.'tmsz}";
  const char *form_4 = "{'Zt.'tmsz, 'Zt2.'tmsz, 'Zt3.'tmsz, 'Zt4.'tmsz}";
  const char *suffix = ", 'Pgl/z, ['Xns'ISveSvl]";

  switch (form_hash_) {
    case "ld3b_z_p_bi_contiguous"_h:
    case "ld3d_z_p_bi_contiguous"_h:
    case "ld3h_z_p_bi_contiguous"_h:
    case "ld3w_z_p_bi_contiguous"_h:
      form = form_3;
      break;
    case "ld4b_z_p_bi_contiguous"_h:
    case "ld4d_z_p_bi_contiguous"_h:
    case "ld4h_z_p_bi_contiguous"_h:
    case "ld4w_z_p_bi_contiguous"_h:
      form = form_4;
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVELoadMultipleStructures_ScalarPlusScalar(
    const Instruction *instr) {
  const char *form = "{'Zt.'tmsz, 'Zt2.'tmsz}";
  const char *form_3 = "{'Zt.'tmsz, 'Zt2.'tmsz, 'Zt3.'tmsz}";
  const char *form_4 = "{'Zt.'tmsz, 'Zt2.'tmsz, 'Zt3.'tmsz, 'Zt4.'tmsz}";
  const char *suffix = ", 'Pgl/z, ['Xns, 'Xm'NSveS]";

  switch (form_hash_) {
    case "ld3b_z_p_br_contiguous"_h:
    case "ld3d_z_p_br_contiguous"_h:
    case "ld3h_z_p_br_contiguous"_h:
    case "ld3w_z_p_br_contiguous"_h:
      form = form_3;
      break;
    case "ld4b_z_p_br_contiguous"_h:
    case "ld4d_z_p_br_contiguous"_h:
    case "ld4h_z_p_br_contiguous"_h:
    case "ld4w_z_p_br_contiguous"_h:
      form = form_4;
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVELoadPredicateRegister(const Instruction *instr) {
  const char *form = "'Pd, ['Xns, #'s2116:1210, mul vl]";
  if (instr->Mask(0x003f1c00) == 0) {
    form = "'Pd, ['Xns]";
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVELoadVectorRegister(const Instruction *instr) {
  const char *form = "'Zt, ['Xns, #'s2116:1210, mul vl]";
  if (instr->Mask(0x003f1c00) == 0) {
    form = "'Zd, ['Xns]";
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEPartitionBreakCondition(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.b, p'u1310/'?04:mz, 'Pn.b");
}

void Disassembler::VisitSVEPermutePredicateElements(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.'t, 'Pn.'t, 'Pm.'t");
}

void Disassembler::VisitSVEPredicateFirstActive(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.b, 'Pn, 'Pd.b");
}

void Disassembler::VisitSVEPredicateReadFromFFR_Unpredicated(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.b");
}

void Disassembler::VisitSVEPredicateTest(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "p'u1310, 'Pn.b");
}

void Disassembler::VisitSVEPredicateZero(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.b");
}

void Disassembler::VisitSVEPropagateBreakToNextPartition(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.b, p'u1310/z, 'Pn.b, 'Pd.b");
}

void Disassembler::VisitSVEReversePredicateElements(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.'t, 'Pn.'t");
}

void Disassembler::VisitSVEReverseVectorElements(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'t");
}

void Disassembler::VisitSVEReverseWithinElements(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "'Zd.'t, 'Pgl/m, 'Zn.'t";

  unsigned size = instr->GetSVESize();
  switch (instr->Mask(SVEReverseWithinElementsMask)) {
    case RBIT_z_p_z:
      mnemonic = "rbit";
      break;
    case REVB_z_z:
      if ((size == kHRegSizeInBytesLog2) || (size == kSRegSizeInBytesLog2) ||
          (size == kDRegSizeInBytesLog2)) {
        mnemonic = "revb";
      } else {
        form = "(SVEReverseWithinElements)";
      }
      break;
    case REVH_z_z:
      if ((size == kSRegSizeInBytesLog2) || (size == kDRegSizeInBytesLog2)) {
        mnemonic = "revh";
      } else {
        form = "(SVEReverseWithinElements)";
      }
      break;
    case REVW_z_z:
      if (size == kDRegSizeInBytesLog2) {
        mnemonic = "revw";
      } else {
        form = "(SVEReverseWithinElements)";
      }
      break;
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVESaturatingIncDecRegisterByElementCount(
    const Instruction *instr) {
  const char *form = IncDecFormHelper(instr,
                                      "'R20d, 'Ipc, mul #'u1916+1",
                                      "'R20d, 'Ipc",
                                      "'R20d");
  const char *form_sx = IncDecFormHelper(instr,
                                         "'Xd, 'Wd, 'Ipc, mul #'u1916+1",
                                         "'Xd, 'Wd, 'Ipc",
                                         "'Xd, 'Wd");

  switch (form_hash_) {
    case "sqdecb_r_rs_sx"_h:
    case "sqdecd_r_rs_sx"_h:
    case "sqdech_r_rs_sx"_h:
    case "sqdecw_r_rs_sx"_h:
    case "sqincb_r_rs_sx"_h:
    case "sqincd_r_rs_sx"_h:
    case "sqinch_r_rs_sx"_h:
    case "sqincw_r_rs_sx"_h:
      form = form_sx;
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVESaturatingIncDecVectorByElementCount(
    const Instruction *instr) {
  const char *form = IncDecFormHelper(instr,
                                      "'Zd.'t, 'Ipc, mul #'u1916+1",
                                      "'Zd.'t, 'Ipc",
                                      "'Zd.'t");
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEStoreMultipleStructures_ScalarPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.'tmsz, 'Zt2.'tmsz}";
  const char *form_3 = "{'Zt.'tmsz, 'Zt2.'tmsz, 'Zt3.'tmsz}";
  const char *form_4 = "{'Zt.'tmsz, 'Zt2.'tmsz, 'Zt3.'tmsz, 'Zt4.'tmsz}";
  const char *suffix = ", 'Pgl, ['Xns'ISveSvl]";

  switch (form_hash_) {
    case "st3b_z_p_bi_contiguous"_h:
    case "st3h_z_p_bi_contiguous"_h:
    case "st3w_z_p_bi_contiguous"_h:
    case "st3d_z_p_bi_contiguous"_h:
      form = form_3;
      break;
    case "st4b_z_p_bi_contiguous"_h:
    case "st4h_z_p_bi_contiguous"_h:
    case "st4w_z_p_bi_contiguous"_h:
    case "st4d_z_p_bi_contiguous"_h:
      form = form_4;
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEStoreMultipleStructures_ScalarPlusScalar(
    const Instruction *instr) {
  const char *form = "{'Zt.'tmsz, 'Zt2.'tmsz}";
  const char *form_3 = "{'Zt.'tmsz, 'Zt2.'tmsz, 'Zt3.'tmsz}";
  const char *form_4 = "{'Zt.'tmsz, 'Zt2.'tmsz, 'Zt3.'tmsz, 'Zt4.'tmsz}";
  const char *suffix = ", 'Pgl, ['Xns, 'Xm'NSveS]";

  switch (form_hash_) {
    case "st3b_z_p_br_contiguous"_h:
    case "st3d_z_p_br_contiguous"_h:
    case "st3h_z_p_br_contiguous"_h:
    case "st3w_z_p_br_contiguous"_h:
      form = form_3;
      break;
    case "st4b_z_p_br_contiguous"_h:
    case "st4d_z_p_br_contiguous"_h:
    case "st4h_z_p_br_contiguous"_h:
    case "st4w_z_p_br_contiguous"_h:
      form = form_4;
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEStorePredicateRegister(const Instruction *instr) {
  const char *form = "'Pd, ['Xns, #'s2116:1210, mul vl]";
  if (instr->Mask(0x003f1c00) == 0) {
    form = "'Pd, ['Xns]";
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEStoreVectorRegister(const Instruction *instr) {
  const char *form = "'Zt, ['Xns, #'s2116:1210, mul vl]";
  if (instr->Mask(0x003f1c00) == 0) {
    form = "'Zd, ['Xns]";
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVETableLookup(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, {'Zn.'t}, 'Zm.'t");
}

void Disassembler::VisitSVEUnpackPredicateElements(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.h, 'Pn.b");
}

void Disassembler::VisitSVEUnpackVectorElements(const Instruction *instr) {
  if (instr->GetSVESize() == 0) {
    // The lowest lane size of the destination vector is H-sized lane.
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'th");
  }
}

void Disassembler::VisitSVEVectorSplice(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl, 'Zd.'t, 'Zn.'t");
}

void Disassembler::VisitSVEAddressGeneration(const Instruction *instr) {
  const char *mnemonic = "adr";
  const char *form = "'Zd.d, ['Zn.d, 'Zm.d";
  const char *suffix = NULL;

  bool msz_is_zero = (instr->ExtractBits(11, 10) == 0);

  switch (instr->Mask(SVEAddressGenerationMask)) {
    case ADR_z_az_d_s32_scaled:
      suffix = msz_is_zero ? ", sxtw]" : ", sxtw #'u1110]";
      break;
    case ADR_z_az_d_u32_scaled:
      suffix = msz_is_zero ? ", uxtw]" : ", uxtw #'u1110]";
      break;
    case ADR_z_az_s_same_scaled:
    case ADR_z_az_d_same_scaled:
      form = "'Zd.'t, ['Zn.'t, 'Zm.'t";
      suffix = msz_is_zero ? "]" : ", lsl #'u1110]";
      break;
    default:
      mnemonic = "unimplemented";
      form = "(SVEAddressGeneration)";
      break;
  }
  Format(instr, mnemonic, form, suffix);
}

void Disassembler::VisitSVEBitwiseLogicalUnpredicated(
    const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "'Zd.d, 'Zn.d, 'Zm.d";

  switch (instr->Mask(SVEBitwiseLogicalUnpredicatedMask)) {
    case AND_z_zz:
      mnemonic = "and";
      break;
    case BIC_z_zz:
      mnemonic = "bic";
      break;
    case EOR_z_zz:
      mnemonic = "eor";
      break;
    case ORR_z_zz:
      mnemonic = "orr";
      if (instr->GetRn() == instr->GetRm()) {
        mnemonic = "mov";
        form = "'Zd.d, 'Zn.d";
      }
      break;
    default:
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEBitwiseShiftUnpredicated(const Instruction *instr) {
  const char *mnemonic = "unimplemented";
  const char *form = "(SVEBitwiseShiftUnpredicated)";
  unsigned tsize =
      (instr->ExtractBits(23, 22) << 2) | instr->ExtractBits(20, 19);
  unsigned lane_size = instr->GetSVESize();

  const char *suffix = NULL;
  const char *form_i = "'Zd.'tszs, 'Zn.'tszs, ";

  switch (form_hash_) {
    case "asr_z_zi"_h:
    case "lsr_z_zi"_h:
    case "sri_z_zzi"_h:
    case "srsra_z_zi"_h:
    case "ssra_z_zi"_h:
    case "ursra_z_zi"_h:
    case "usra_z_zi"_h:
      if (tsize != 0) {
        // The tsz field must not be zero.
        mnemonic = mnemonic_.c_str();
        form = form_i;
        suffix = "'ITriSves";
      }
      break;
    case "lsl_z_zi"_h:
    case "sli_z_zzi"_h:
      if (tsize != 0) {
        // The tsz field must not be zero.
        mnemonic = mnemonic_.c_str();
        form = form_i;
        suffix = "'ITriSver";
      }
      break;
    case "asr_z_zw"_h:
    case "lsl_z_zw"_h:
    case "lsr_z_zw"_h:
      if (lane_size <= kSRegSizeInBytesLog2) {
        mnemonic = mnemonic_.c_str();
        form = "'Zd.'t, 'Zn.'t, 'Zm.d";
      }
      break;
    default:
      break;
  }

  Format(instr, mnemonic, form, suffix);
}

void Disassembler::VisitSVEElementCount(const Instruction *instr) {
  const char *form =
      IncDecFormHelper(instr, "'Xd, 'Ipc, mul #'u1916+1", "'Xd, 'Ipc", "'Xd");
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEFPAccumulatingReduction(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'t'u0400, 'Pgl, 't'u0400, 'Zn.'t");
  }
}

void Disassembler::VisitSVEFPArithmeticUnpredicated(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'t, 'Zm.'t");
  }
}

void Disassembler::VisitSVEFPCompareVectors(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Pd.'t, 'Pgl/z, 'Zn.'t, 'Zm.'t");
  }
}

void Disassembler::VisitSVEFPCompareWithZero(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Pd.'t, 'Pgl/z, 'Zn.'t, #0.0");
  }
}

void Disassembler::VisitSVEFPComplexAddition(const Instruction *instr) {
  // Bit 15 is always set, so this gives 90 * 1 or 3.
  const char *form = "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t, #'u1615*90";
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, form);
  }
}

void Disassembler::VisitSVEFPComplexMulAdd(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl/m, 'Zn.'t, 'Zm.'t, #'u1413*90";
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, form);
  }
}

void Disassembler::VisitSVEFPComplexMulAddIndex(const Instruction *instr) {
  const char *form = "'Zd.h, 'Zn.h, z'u1816.h['u2019]";
  const char *suffix = ", #'u1110*90";
  switch (form_hash_) {
    case "fcmla_z_zzzi_s"_h:
      form = "'Zd.s, 'Zn.s, z'u1916.s['u2020]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEFPFastReduction(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'t'u0400, 'Pgl, 'Zn.'t");
  }
}

void Disassembler::VisitSVEFPMulIndex(const Instruction *instr) {
  const char *form = "'Zd.h, 'Zn.h, z'u1816.h['u2222:2019]";
  switch (form_hash_) {
    case "fmul_z_zzi_d"_h:
      form = "'Zd.d, 'Zn.d, z'u1916.d['u2020]";
      break;
    case "fmul_z_zzi_s"_h:
      form = "'Zd.s, 'Zn.s, z'u1816.s['u2019]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEFPMulAdd(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zn.'t, 'Zm.'t");
  }
}

void Disassembler::VisitSVEFPMulAddIndex(const Instruction *instr) {
  const char *form = "'Zd.h, 'Zn.h, z'u1816.h['u2222:2019]";
  switch (form_hash_) {
    case "fmla_z_zzzi_s"_h:
    case "fmls_z_zzzi_s"_h:
      form = "'Zd.s, 'Zn.s, z'u1816.s['u2019]";
      break;
    case "fmla_z_zzzi_d"_h:
    case "fmls_z_zzzi_d"_h:
      form = "'Zd.d, 'Zn.d, z'u1916.d['u2020]";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEFPUnaryOpUnpredicated(const Instruction *instr) {
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    VisitUnallocated(instr);
  } else {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'t");
  }
}

void Disassembler::VisitSVEIncDecByPredicateCount(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pn";
  switch (form_hash_) {
    // <Xdn>, <Pg>.<T>
    case "decp_r_p_r"_h:
    case "incp_r_p_r"_h:
      form = "'Xd, 'Pn.'t";
      break;
    // <Xdn>, <Pg>.<T>, <Wdn>
    case "sqdecp_r_p_r_sx"_h:
    case "sqincp_r_p_r_sx"_h:
      form = "'Xd, 'Pn.'t, 'Wd";
      break;
    // <Xdn>, <Pg>.<T>
    case "sqdecp_r_p_r_x"_h:
    case "sqincp_r_p_r_x"_h:
    case "uqdecp_r_p_r_x"_h:
    case "uqincp_r_p_r_x"_h:
      form = "'Xd, 'Pn.'t";
      break;
    // <Wdn>, <Pg>.<T>
    case "uqdecp_r_p_r_uw"_h:
    case "uqincp_r_p_r_uw"_h:
      form = "'Wd, 'Pn.'t";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEIndexGeneration(const Instruction *instr) {
  const char *form = "'Zd.'t, #'s0905, #'s2016";
  bool w_inputs =
      static_cast<unsigned>(instr->GetSVESize()) <= kWRegSizeInBytesLog2;

  switch (form_hash_) {
    case "index_z_ir"_h:
      form = w_inputs ? "'Zd.'t, #'s0905, 'Wm" : "'Zd.'t, #'s0905, 'Xm";
      break;
    case "index_z_ri"_h:
      form = w_inputs ? "'Zd.'t, 'Wn, #'s2016" : "'Zd.'t, 'Xn, #'s2016";
      break;
    case "index_z_rr"_h:
      form = w_inputs ? "'Zd.'t, 'Wn, 'Wm" : "'Zd.'t, 'Xn, 'Xm";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEIntArithmeticUnpredicated(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'t, 'Zm.'t");
}

void Disassembler::VisitSVEIntCompareSignedImm(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.'t, 'Pgl/z, 'Zn.'t, #'s2016");
}

void Disassembler::VisitSVEIntCompareUnsignedImm(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.'t, 'Pgl/z, 'Zn.'t, #'u2014");
}

void Disassembler::VisitSVEIntCompareVectors(const Instruction *instr) {
  const char *form = "'Pd.'t, 'Pgl/z, 'Zn.'t, 'Zm.";
  const char *suffix = "d";
  switch (form_hash_) {
    case "cmpeq_p_p_zz"_h:
    case "cmpge_p_p_zz"_h:
    case "cmpgt_p_p_zz"_h:
    case "cmphi_p_p_zz"_h:
    case "cmphs_p_p_zz"_h:
    case "cmpne_p_p_zz"_h:
      suffix = "'t";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEIntMulAddPredicated(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl/m, ";
  const char *suffix = "'Zn.'t, 'Zm.'t";
  switch (form_hash_) {
    case "mad_z_p_zzz"_h:
    case "msb_z_p_zzz"_h:
      suffix = "'Zm.'t, 'Zn.'t";
      break;
  }
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEIntMulAddUnpredicated(const Instruction *instr) {
  if (static_cast<unsigned>(instr->GetSVESize()) >= kSRegSizeInBytesLog2) {
    FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'tq, 'Zm.'tq");
  } else {
    VisitUnallocated(instr);
  }
}

void Disassembler::VisitSVEMovprfx(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/'?16:mz, 'Zn.'t");
}

void Disassembler::VisitSVEIntReduction(const Instruction *instr) {
  const char *form = "'Vdv, 'Pgl, 'Zn.'t";
  switch (form_hash_) {
    case "saddv_r_p_z"_h:
    case "uaddv_r_p_z"_h:
      form = "'Dd, 'Pgl, 'Zn.'t";
      break;
  }
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEIntUnaryArithmeticPredicated(
    const Instruction *instr) {
  VectorFormat vform = instr->GetSVEVectorFormat();

  switch (form_hash_) {
    case "sxtw_z_p_z"_h:
    case "uxtw_z_p_z"_h:
      if (vform == kFormatVnS) {
        VisitUnallocated(instr);
        return;
      }
      VIXL_FALLTHROUGH();
    case "sxth_z_p_z"_h:
    case "uxth_z_p_z"_h:
      if (vform == kFormatVnH) {
        VisitUnallocated(instr);
        return;
      }
      VIXL_FALLTHROUGH();
    case "sxtb_z_p_z"_h:
    case "uxtb_z_p_z"_h:
    case "fabs_z_p_z"_h:
    case "fneg_z_p_z"_h:
      if (vform == kFormatVnB) {
        VisitUnallocated(instr);
        return;
      }
      break;
  }

  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Pgl/m, 'Zn.'t");
}

void Disassembler::VisitSVEMulIndex(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.b, z'u1816.b['u2019]";

  switch (form_hash_) {
    case "sdot_z_zzzi_d"_h:
    case "udot_z_zzzi_d"_h:
      form = "'Zd.d, 'Zn.h, z'u1916.h['u2020]";
      break;
  }

  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEPermuteVectorExtract(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.b, 'Zd.b, 'Zn.b, #'u2016:1210");
}

void Disassembler::VisitSVEPermuteVectorInterleaving(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Zd.'t, 'Zn.'t, 'Zm.'t");
}

void Disassembler::VisitSVEPredicateCount(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Xd, p'u1310, 'Pn.'t");
}

void Disassembler::VisitSVEPredicateLogical(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Pd.b, p'u1310/z, 'Pn.b, 'Pm.b";

  int pd = instr->GetPd();
  int pn = instr->GetPn();
  int pm = instr->GetPm();
  int pg = instr->ExtractBits(13, 10);

  switch (form_hash_) {
    case "ands_p_p_pp_z"_h:
      if (pn == pm) {
        mnemonic = "movs";
        form = "'Pd.b, p'u1310/z, 'Pn.b";
      }
      break;
    case "and_p_p_pp_z"_h:
      if (pn == pm) {
        mnemonic = "mov";
        form = "'Pd.b, p'u1310/z, 'Pn.b";
      }
      break;
    case "eors_p_p_pp_z"_h:
      if (pm == pg) {
        mnemonic = "nots";
        form = "'Pd.b, 'Pm/z, 'Pn.b";
      }
      break;
    case "eor_p_p_pp_z"_h:
      if (pm == pg) {
        mnemonic = "not";
        form = "'Pd.b, 'Pm/z, 'Pn.b";
      }
      break;
    case "orrs_p_p_pp_z"_h:
      if ((pn == pm) && (pn == pg)) {
        mnemonic = "movs";
        form = "'Pd.b, 'Pn.b";
      }
      break;
    case "orr_p_p_pp_z"_h:
      if ((pn == pm) && (pn == pg)) {
        mnemonic = "mov";
        form = "'Pd.b, 'Pn.b";
      }
      break;
    case "sel_p_p_pp"_h:
      if (pd == pm) {
        mnemonic = "mov";
        form = "'Pd.b, p'u1310/m, 'Pn.b";
      } else {
        form = "'Pd.b, p'u1310, 'Pn.b, 'Pm.b";
      }
      break;
  }
  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEPredicateInitialize(const Instruction *instr) {
  const char *form = "'Pd.'t, 'Ipc";
  // Omit the pattern if it is the default ('ALL').
  if (instr->ExtractBits(9, 5) == SVE_ALL) form = "'Pd.'t";
  FormatWithDecodedMnemonic(instr, form);
}

void Disassembler::VisitSVEPredicateNextActive(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.'t, 'Pn, 'Pd.'t");
}

void Disassembler::VisitSVEPredicateReadFromFFR_Predicated(
    const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.b, 'Pn/z");
}

void Disassembler::VisitSVEPropagateBreak(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Pd.b, p'u1310/z, 'Pn.b, 'Pm.b");
}

void Disassembler::VisitSVEStackFrameAdjustment(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Xds, 'Xms, #'s1005");
}

void Disassembler::VisitSVEStackFrameSize(const Instruction *instr) {
  FormatWithDecodedMnemonic(instr, "'Xd, #'s1005");
}

void Disassembler::VisitSVEVectorSelect(const Instruction *instr) {
  const char *mnemonic = mnemonic_.c_str();
  const char *form = "'Zd.'t, p'u1310, 'Zn.'t, 'Zm.'t";

  if (instr->GetRd() == instr->GetRm()) {
    mnemonic = "mov";
    form = "'Zd.'t, p'u1310/m, 'Zn.'t";
  }

  Format(instr, mnemonic, form);
}

void Disassembler::VisitSVEContiguousLoad_ScalarPlusImm(
    const Instruction *instr) {
  const char *form = "{'Zt.'tlss}, 'Pgl/z, ['Xns";
  const char *suffix =
      (instr->ExtractBits(19, 16) == 0) ? "]" : ", #'s1916, mul vl]";
  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitSVEContiguousLoad_ScalarPlusScalar(
    const Instruction *instr) {
  const char *form = "{'Zt.'tlss}, 'Pgl/z, ['Xns, 'Xm";
  const char *suffix = "]";

  switch (form_hash_) {
    case "ld1h_z_p_br_u16"_h:
    case "ld1h_z_p_br_u32"_h:
    case "ld1h_z_p_br_u64"_h:
    case "ld1w_z_p_br_u32"_h:
    case "ld1w_z_p_br_u64"_h:
    case "ld1d_z_p_br_u64"_h:
      suffix = ", lsl #'u2423]";
      break;
    case "ld1sh_z_p_br_s32"_h:
    case "ld1sh_z_p_br_s64"_h:
      suffix = ", lsl #1]";
      break;
    case "ld1sw_z_p_br_s64"_h:
      suffix = ", lsl #2]";
      break;
  }

  FormatWithDecodedMnemonic(instr, form, suffix);
}

void Disassembler::VisitReserved(const Instruction *instr) {
  // UDF is the only instruction in this group, and the Decoder is precise.
  VIXL_ASSERT(instr->Mask(ReservedMask) == UDF);
  Format(instr, "udf", "'IUdf");
}

void Disassembler::VisitUnimplemented(const Instruction *instr) {
  Format(instr, "unimplemented", "(Unimplemented)");
}


void Disassembler::VisitUnallocated(const Instruction *instr) {
  Format(instr, "unallocated", "(Unallocated)");
}

void Disassembler::Visit(Metadata *metadata, const Instruction *instr) {
  VIXL_ASSERT(metadata->count("form") > 0);
  const std::string &form = (*metadata)["form"];
  form_hash_ = Hash(form.c_str());
  const FormToVisitorFnMap *fv = Disassembler::GetFormToVisitorFnMap();
  FormToVisitorFnMap::const_iterator it = fv->find(form_hash_);
  if (it == fv->end()) {
    VisitUnimplemented(instr);
  } else {
    SetMnemonicFromForm(form);
    (it->second)(this, instr);
  }
}

void Disassembler::Disassemble_PdT_PgZ_ZnT_ZmT(const Instruction *instr) {
  const char *form = "'Pd.'t, 'Pgl/z, 'Zn.'t, 'Zm.'t";
  VectorFormat vform = instr->GetSVEVectorFormat();

  if ((vform == kFormatVnS) || (vform == kFormatVnD)) {
    Format(instr, "unimplemented", "(PdT_PgZ_ZnT_ZmT)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::Disassemble_ZdB_Zn1B_Zn2B_imm(const Instruction *instr) {
  const char *form = "'Zd.b, {'Zn.b, 'Zn2.b}, #'u2016:1210";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdB_ZnB_ZmB(const Instruction *instr) {
  const char *form = "'Zd.b, 'Zn.b, 'Zm.b";
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    Format(instr, mnemonic_.c_str(), form);
  } else {
    Format(instr, "unimplemented", "(ZdB_ZnB_ZmB)");
  }
}

void Disassembler::Disassemble_ZdD_PgM_ZnS(const Instruction *instr) {
  const char *form = "'Zd.d, 'Pgl/m, 'Zn.s";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdD_ZnD_ZmD(const Instruction *instr) {
  const char *form = "'Zd.d, 'Zn.d, 'Zm.d";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdD_ZnD_ZmD_imm(const Instruction *instr) {
  const char *form = "'Zd.d, 'Zn.d, z'u1916.d['u2020]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdD_ZnS_ZmS_imm(const Instruction *instr) {
  const char *form = "'Zd.d, 'Zn.s, z'u1916.s['u2020:1111]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdH_PgM_ZnS(const Instruction *instr) {
  const char *form = "'Zd.h, 'Pgl/m, 'Zn.s";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdH_ZnH_ZmH_imm(const Instruction *instr) {
  const char *form = "'Zd.h, 'Zn.h, z'u1816.h['u2222:2019]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdS_PgM_ZnD(const Instruction *instr) {
  const char *form = "'Zd.s, 'Pgl/m, 'Zn.d";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdS_PgM_ZnH(const Instruction *instr) {
  const char *form = "'Zd.s, 'Pgl/m, 'Zn.h";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdS_PgM_ZnS(const Instruction *instr) {
  const char *form = "'Zd.s, 'Pgl/m, 'Zn.s";
  if (instr->GetSVEVectorFormat() == kFormatVnS) {
    Format(instr, mnemonic_.c_str(), form);
  } else {
    Format(instr, "unimplemented", "(ZdS_PgM_ZnS)");
  }
}

void Disassembler::Disassemble_ZdS_ZnH_ZmH_imm(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.h, z'u1816.h['u2019:1111]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdS_ZnS_ZmS(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.s, 'Zm.s";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdS_ZnS_ZmS_imm(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.s, z'u1816.s['u2019]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::DisassembleSVEFlogb(const Instruction *instr) {
  const char *form = "'Zd.'tf, 'Pgl/m, 'Zn.'tf";
  if (instr->GetSVEVectorFormat(17) == kFormatVnB) {
    Format(instr, "unimplemented", "(SVEFlogb)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::Disassemble_ZdT_PgM_ZnT(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl/m, 'Zn.'t";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdT_PgZ_ZnT_ZmT(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl/z, 'Zn.'t, 'Zm.'t";
  VectorFormat vform = instr->GetSVEVectorFormat();
  if ((vform == kFormatVnS) || (vform == kFormatVnD)) {
    Format(instr, mnemonic_.c_str(), form);
  } else {
    Format(instr, "unimplemented", "(ZdT_PgZ_ZnT_ZmT)");
  }
}

void Disassembler::Disassemble_ZdT_Pg_Zn1T_Zn2T(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl, {'Zn.'t, 'Zn2.'t}";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdT_Zn1T_Zn2T_ZmT(const Instruction *instr) {
  const char *form = "'Zd.'t, {'Zn.'t, 'Zn2.'t}, 'Zm.'t";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdT_ZnT_ZmT(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zn.'t, 'Zm.'t";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdT_ZnT_ZmTb(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zn.'t, 'Zm.'th";
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    Format(instr, "unimplemented", "(ZdT_ZnT_ZmTb)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::Disassemble_ZdT_ZnTb(const Instruction *instr) {
  const char *form = "'Zd.'tszs, 'Zn.'tszd";
  std::pair<int, int> shift_and_lane_size =
      instr->GetSVEImmShiftAndLaneSizeLog2(/* is_predicated = */ false);
  int shift_dist = shift_and_lane_size.first;
  int lane_size = shift_and_lane_size.second;
  // Convert shift_dist from a right to left shift. Valid xtn instructions
  // must have a left shift_dist equivalent of zero.
  shift_dist = (8 << lane_size) - shift_dist;
  if ((lane_size >= static_cast<int>(kBRegSizeInBytesLog2)) &&
      (lane_size <= static_cast<int>(kSRegSizeInBytesLog2)) &&
      (shift_dist == 0)) {
    Format(instr, mnemonic_.c_str(), form);
  } else {
    Format(instr, "unimplemented", "(ZdT_ZnTb)");
  }
}

void Disassembler::Disassemble_ZdT_ZnTb_ZmTb(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zn.'th, 'Zm.'th";
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    // TODO: This is correct for saddlbt, ssublbt, subltb, which don't have
    // b-lane sized form, and for pmull[b|t] as feature `SVEPmull128` isn't
    // supported, but may need changes for other instructions reaching here.
    Format(instr, "unimplemented", "(ZdT_ZnTb_ZmTb)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::DisassembleSVEAddSubHigh(const Instruction *instr) {
  const char *form = "'Zd.'th, 'Zn.'t, 'Zm.'t";
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    Format(instr, "unimplemented", "(SVEAddSubHigh)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::DisassembleSVEShiftLeftImm(const Instruction *instr) {
  const char *form = "'Zd.'tszd, 'Zn.'tszs, 'ITriSver";
  std::pair<int, int> shift_and_lane_size =
      instr->GetSVEImmShiftAndLaneSizeLog2(/* is_predicated = */ false);
  int lane_size = shift_and_lane_size.second;
  if ((lane_size >= static_cast<int>(kBRegSizeInBytesLog2)) &&
      (lane_size <= static_cast<int>(kSRegSizeInBytesLog2))) {
    Format(instr, mnemonic_.c_str(), form);
  } else {
    Format(instr, "unimplemented", "(SVEShiftLeftImm)");
  }
}

void Disassembler::DisassembleSVEShiftRightImm(const Instruction *instr) {
  const char *form = "'Zd.'tszs, 'Zn.'tszd, 'ITriSves";
  std::pair<int, int> shift_and_lane_size =
      instr->GetSVEImmShiftAndLaneSizeLog2(/* is_predicated = */ false);
  int lane_size = shift_and_lane_size.second;
  if ((lane_size >= static_cast<int>(kBRegSizeInBytesLog2)) &&
      (lane_size <= static_cast<int>(kSRegSizeInBytesLog2))) {
    Format(instr, mnemonic_.c_str(), form);
  } else {
    Format(instr, "unimplemented", "(SVEShiftRightImm)");
  }
}

void Disassembler::Disassemble_ZdaD_ZnD_ZmD_imm(const Instruction *instr) {
  const char *form = "'Zd.d, 'Zn.d, z'u1916.d['u2020]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaD_ZnH_ZmH_imm_const(
    const Instruction *instr) {
  const char *form = "'Zd.d, 'Zn.h, z'u1916.h['u2020], #'u1110*90";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaD_ZnS_ZmS_imm(const Instruction *instr) {
  const char *form = "'Zd.d, 'Zn.s, z'u1916.s['u2020:1111]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaH_ZnH_ZmH_imm(const Instruction *instr) {
  const char *form = "'Zd.h, 'Zn.h, z'u1816.h['u2222:2019]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaH_ZnH_ZmH_imm_const(
    const Instruction *instr) {
  const char *form = "'Zd.h, 'Zn.h, z'u1816.h['u2019], #'u1110*90";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaS_ZnB_ZmB(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.b, 'Zm.b";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaS_ZnB_ZmB_imm_const(
    const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.b, z'u1816.b['u2019], #'u1110*90";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaS_ZnH_ZmH(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.h, 'Zm.h";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaS_ZnH_ZmH_imm(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.h, z'u1816.h['u2019:1111]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaS_ZnS_ZmS_imm(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.s, z'u1816.s['u2019]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaS_ZnS_ZmS_imm_const(
    const Instruction *instr) {
  const char *form = "'Zd.s, 'Zn.s, z'u1916.s['u2020], #'u1110*90";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaT_PgM_ZnTb(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl/m, 'Zn.'th";

  if (instr->GetSVESize() == 0) {
    // The lowest lane size of the destination vector is H-sized lane.
    Format(instr, "unimplemented", "(Disassemble_ZdaT_PgM_ZnTb)");
    return;
  }

  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::DisassembleSVEAddSubCarry(const Instruction *instr) {
  const char *form = "'Zd.'?22:ds, 'Zn.'?22:ds, 'Zm.'?22:ds";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaT_ZnT_ZmT(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zn.'t, 'Zm.'t";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaT_ZnT_ZmT_const(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zn.'t, 'Zm.'t, #'u1110*90";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdaT_ZnTb_ZmTb(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zn.'th, 'Zm.'th";
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    Format(instr, "unimplemented", "(ZdaT_ZnTb_ZmTb)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::Disassemble_ZdaT_ZnTb_ZmTb_const(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zn.'tq, 'Zm.'tq, #'u1110*90";
  VectorFormat vform = instr->GetSVEVectorFormat();

  if ((vform == kFormatVnB) || (vform == kFormatVnH)) {
    Format(instr, "unimplemented", "(ZdaT_ZnTb_ZmTb_const)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::Disassemble_ZdnB_ZdnB(const Instruction *instr) {
  const char *form = "'Zd.b, 'Zd.b";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdnB_ZdnB_ZmB(const Instruction *instr) {
  const char *form = "'Zd.b, 'Zd.b, 'Zn.b";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::DisassembleSVEBitwiseTernary(const Instruction *instr) {
  const char *form = "'Zd.d, 'Zd.d, 'Zm.d, 'Zn.d";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_ZdnS_ZdnS_ZmS(const Instruction *instr) {
  const char *form = "'Zd.s, 'Zd.s, 'Zn.s";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::DisassembleSVEFPPair(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t";
  if (instr->GetSVEVectorFormat() == kFormatVnB) {
    Format(instr, "unimplemented", "(SVEFPPair)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::Disassemble_ZdnT_PgM_ZdnT_ZmT(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Pgl/m, 'Zd.'t, 'Zn.'t";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::DisassembleSVEComplexIntAddition(const Instruction *instr) {
  const char *form = "'Zd.'t, 'Zd.'t, 'Zn.'t, #";
  const char *suffix = (instr->ExtractBit(10) == 0) ? "90" : "270";
  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::Disassemble_ZdnT_ZdnT_ZmT_const(const Instruction *instr) {
  const char *form = "'Zd.'tszs, 'Zd.'tszs, 'Zn.'tszs, 'ITriSves";
  unsigned tsize =
      (instr->ExtractBits(23, 22) << 2) | instr->ExtractBits(20, 19);

  if (tsize == 0) {
    Format(instr, "unimplemented", "(ZdnT_ZdnT_ZmT_const)");
  } else {
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::Disassemble_ZtD_PgZ_ZnD_Xm(const Instruction *instr) {
  const char *form = "{'Zt.d}, 'Pgl/z, ['Zn.d";
  const char *suffix = instr->GetRm() == 31 ? "]" : ", 'Xm]";
  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::Disassemble_ZtD_Pg_ZnD_Xm(const Instruction *instr) {
  const char *form = "{'Zt.d}, 'Pgl, ['Zn.d";
  const char *suffix = instr->GetRm() == 31 ? "]" : ", 'Xm]";
  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::Disassemble_ZtS_PgZ_ZnS_Xm(const Instruction *instr) {
  const char *form = "{'Zt.s}, 'Pgl/z, ['Zn.s";
  const char *suffix = instr->GetRm() == 31 ? "]" : ", 'Xm]";
  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::Disassemble_ZtS_Pg_ZnS_Xm(const Instruction *instr) {
  const char *form = "{'Zt.s}, 'Pgl, ['Zn.s";
  const char *suffix = instr->GetRm() == 31 ? "]" : ", 'Xm]";
  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::Disassemble_XdSP_XnSP_Xm(const Instruction *instr) {
  const char *form = "'Xds, 'Xns";
  const char *suffix = instr->GetRm() == 31 ? "" : ", 'Xm";
  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::Disassemble_XdSP_XnSP_uimm6_uimm4(const Instruction *instr) {
  VIXL_STATIC_ASSERT(kMTETagGranuleInBytes == 16);
  const char *form = "'Xds, 'Xns, #'u2116*16, #'u1310";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_Xd_XnSP_Xm(const Instruction *instr) {
  const char *form = "'Rd, 'Xns, 'Rm";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::Disassemble_Xd_XnSP_XmSP(const Instruction *instr) {
  if ((form_hash_ == Hash("subps_64s_dp_2src")) && (instr->GetRd() == 31)) {
    Format(instr, "cmpp", "'Xns, 'Xms");
  } else {
    const char *form = "'Xd, 'Xns, 'Xms";
    Format(instr, mnemonic_.c_str(), form);
  }
}

void Disassembler::DisassembleMTEStoreTagPair(const Instruction *instr) {
  const char *form = "'Xt, 'Xt2, ['Xns";
  const char *suffix = NULL;
  switch (form_hash_) {
    case Hash("stgp_64_ldstpair_off"):
      suffix = ", #'s2115*16]";
      break;
    case Hash("stgp_64_ldstpair_post"):
      suffix = "], #'s2115*16";
      break;
    case Hash("stgp_64_ldstpair_pre"):
      suffix = ", #'s2115*16]!";
      break;
    default:
      mnemonic_ = "unimplemented";
      break;
  }

  if (instr->GetImmLSPair() == 0) {
    suffix = "]";
  }

  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::DisassembleMTEStoreTag(const Instruction *instr) {
  const char *form = "'Xds, ['Xns";
  const char *suffix = NULL;
  switch (form_hash_) {
    case Hash("st2g_64soffset_ldsttags"):
    case Hash("stg_64soffset_ldsttags"):
    case Hash("stz2g_64soffset_ldsttags"):
    case Hash("stzg_64soffset_ldsttags"):
      suffix = ", #'s2012*16]";
      break;
    case Hash("st2g_64spost_ldsttags"):
    case Hash("stg_64spost_ldsttags"):
    case Hash("stz2g_64spost_ldsttags"):
    case Hash("stzg_64spost_ldsttags"):
      suffix = "], #'s2012*16";
      break;
    case Hash("st2g_64spre_ldsttags"):
    case Hash("stg_64spre_ldsttags"):
    case Hash("stz2g_64spre_ldsttags"):
    case Hash("stzg_64spre_ldsttags"):
      suffix = ", #'s2012*16]!";
      break;
    default:
      mnemonic_ = "unimplemented";
      break;
  }

  if (instr->GetImmLS() == 0) {
    suffix = "]";
  }

  Format(instr, mnemonic_.c_str(), form, suffix);
}

void Disassembler::DisassembleMTELoadTag(const Instruction *instr) {
  const char *form =
      (instr->GetImmLS() == 0) ? "'Xt, ['Xns]" : "'Xt, ['Xns, #'s2012*16]";
  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::DisassembleCpy(const Instruction *instr) {
  const char *form = "['Xd]!, ['Xs]!, 'Xn!";

  int d = instr->GetRd();
  int n = instr->GetRn();
  int s = instr->GetRs();

  // Aliased registers and sp/zr are disallowed.
  if ((d == n) || (d == s) || (n == s) || (d == 31) || (n == 31) || (s == 31)) {
    form = NULL;
  }

  // Bits 31 and 30 must be zero.
  if (instr->ExtractBits(31, 30)) {
    form = NULL;
  }

  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::DisassembleSet(const Instruction *instr) {
  const char *form = "['Xd]!, 'Xn!, 'Xs";

  int d = instr->GetRd();
  int n = instr->GetRn();
  int s = instr->GetRs();

  // Aliased registers are disallowed. Only Xs may be xzr.
  if ((d == n) || (d == s) || (n == s) || (d == 31) || (n == 31)) {
    form = NULL;
  }

  // Bits 31 and 30 must be zero.
  if (instr->ExtractBits(31, 30)) {
    form = NULL;
  }

  Format(instr, mnemonic_.c_str(), form);
}

void Disassembler::ProcessOutput(const Instruction * /*instr*/) {
  // The base disasm does nothing more than disassembling into a buffer.
}


void Disassembler::AppendRegisterNameToOutput(const Instruction *instr,
                                              const CPURegister &reg) {
  USE(instr);
  VIXL_ASSERT(reg.IsValid());
  char reg_char;

  if (reg.IsRegister()) {
    reg_char = reg.Is64Bits() ? 'x' : 'w';
  } else {
    VIXL_ASSERT(reg.IsVRegister());
    switch (reg.GetSizeInBits()) {
      case kBRegSize:
        reg_char = 'b';
        break;
      case kHRegSize:
        reg_char = 'h';
        break;
      case kSRegSize:
        reg_char = 's';
        break;
      case kDRegSize:
        reg_char = 'd';
        break;
      default:
        VIXL_ASSERT(reg.Is128Bits());
        reg_char = 'q';
    }
  }

  if (reg.IsVRegister() || !(reg.Aliases(sp) || reg.Aliases(xzr))) {
    // A core or scalar/vector register: [wx]0 - 30, [bhsdq]0 - 31.
    AppendToOutput("%c%d", reg_char, reg.GetCode());
  } else if (reg.Aliases(sp)) {
    // Disassemble w31/x31 as stack pointer wsp/sp.
    AppendToOutput("%s", reg.Is64Bits() ? "sp" : "wsp");
  } else {
    // Disassemble w31/x31 as zero register wzr/xzr.
    AppendToOutput("%czr", reg_char);
  }
}


void Disassembler::AppendPCRelativeOffsetToOutput(const Instruction *instr,
                                                  int64_t offset) {
  USE(instr);
  if (offset < 0) {
    // Cast to uint64_t so that INT64_MIN is handled in a well-defined way.
    uint64_t abs_offset = UnsignedNegate(static_cast<uint64_t>(offset));
    AppendToOutput("#-0x%" PRIx64, abs_offset);
  } else {
    AppendToOutput("#+0x%" PRIx64, offset);
  }
}


void Disassembler::AppendAddressToOutput(const Instruction *instr,
                                         const void *addr) {
  USE(instr);
  AppendToOutput("(addr 0x%" PRIxPTR ")", reinterpret_cast<uintptr_t>(addr));
}


void Disassembler::AppendCodeAddressToOutput(const Instruction *instr,
                                             const void *addr) {
  AppendAddressToOutput(instr, addr);
}


void Disassembler::AppendDataAddressToOutput(const Instruction *instr,
                                             const void *addr) {
  AppendAddressToOutput(instr, addr);
}


void Disassembler::AppendCodeRelativeAddressToOutput(const Instruction *instr,
                                                     const void *addr) {
  USE(instr);
  int64_t rel_addr = CodeRelativeAddress(addr);
  if (rel_addr >= 0) {
    AppendToOutput("(addr 0x%" PRIx64 ")", rel_addr);
  } else {
    AppendToOutput("(addr -0x%" PRIx64 ")", -rel_addr);
  }
}


void Disassembler::AppendCodeRelativeCodeAddressToOutput(
    const Instruction *instr, const void *addr) {
  AppendCodeRelativeAddressToOutput(instr, addr);
}


void Disassembler::AppendCodeRelativeDataAddressToOutput(
    const Instruction *instr, const void *addr) {
  AppendCodeRelativeAddressToOutput(instr, addr);
}


void Disassembler::MapCodeAddress(int64_t base_address,
                                  const Instruction *instr_address) {
  set_code_address_offset(base_address -
                          reinterpret_cast<intptr_t>(instr_address));
}
int64_t Disassembler::CodeRelativeAddress(const void *addr) {
  return reinterpret_cast<intptr_t>(addr) + code_address_offset();
}


void Disassembler::Format(const Instruction *instr,
                          const char *mnemonic,
                          const char *format0,
                          const char *format1) {
  if ((mnemonic == NULL) || (format0 == NULL)) {
    VisitUnallocated(instr);
  } else {
    ResetOutput();
    Substitute(instr, mnemonic);
    if (format0[0] != 0) {  // Not a zero-length string.
      VIXL_ASSERT(buffer_pos_ < buffer_size_);
      buffer_[buffer_pos_++] = ' ';
      Substitute(instr, format0);
      // TODO: consider using a zero-length string here, too.
      if (format1 != NULL) {
        Substitute(instr, format1);
      }
    }
    VIXL_ASSERT(buffer_pos_ < buffer_size_);
    buffer_[buffer_pos_] = 0;
    ProcessOutput(instr);
  }
}

void Disassembler::FormatWithDecodedMnemonic(const Instruction *instr,
                                             const char *format0,
                                             const char *format1) {
  Format(instr, mnemonic_.c_str(), format0, format1);
}

void Disassembler::Substitute(const Instruction *instr, const char *string) {
  char chr = *string++;
  while (chr != '\0') {
    if (chr == '\'') {
      string += SubstituteField(instr, string);
    } else {
      VIXL_ASSERT(buffer_pos_ < buffer_size_);
      buffer_[buffer_pos_++] = chr;
    }
    chr = *string++;
  }
}


int Disassembler::SubstituteField(const Instruction *instr,
                                  const char *format) {
  switch (format[0]) {
    // NB. The remaining substitution prefix upper-case characters are: JU.
    case 'R':  // Register. X or W, selected by sf (or alternative) bit.
    case 'F':  // FP register. S or D, selected by type field.
    case 'V':  // Vector register, V, vector format.
    case 'Z':  // Scalable vector register.
    case 'W':
    case 'X':
    case 'B':
    case 'H':
    case 'S':
    case 'D':
    case 'Q':
      return SubstituteRegisterField(instr, format);
    case 'P':
      return SubstitutePredicateRegisterField(instr, format);
    case 'I':
      return SubstituteImmediateField(instr, format);
    case 'L':
      return SubstituteLiteralField(instr, format);
    case 'N':
      return SubstituteShiftField(instr, format);
    case 'C':
      return SubstituteConditionField(instr, format);
    case 'E':
      return SubstituteExtendField(instr, format);
    case 'A':
      return SubstitutePCRelAddressField(instr, format);
    case 'T':
      return SubstituteBranchTargetField(instr, format);
    case 'O':
      return SubstituteLSRegOffsetField(instr, format);
    case 'M':
      return SubstituteBarrierField(instr, format);
    case 'K':
      return SubstituteCrField(instr, format);
    case 'G':
      return SubstituteSysOpField(instr, format);
    case 'p':
      return SubstitutePrefetchField(instr, format);
    case 'u':
    case 's':
      return SubstituteIntField(instr, format);
    case 't':
      return SubstituteSVESize(instr, format);
    case '?':
      return SubstituteTernary(instr, format);
    default: {
      VIXL_UNREACHABLE();
      return 1;
    }
  }
}

std::pair<unsigned, unsigned> Disassembler::GetRegNumForField(
    const Instruction *instr, char reg_prefix, const char *field) {
  unsigned reg_num = UINT_MAX;
  unsigned field_len = 1;

  switch (field[0]) {
    case 'd':
      reg_num = instr->GetRd();
      break;
    case 'n':
      reg_num = instr->GetRn();
      break;
    case 'm':
      reg_num = instr->GetRm();
      break;
    case 'e':
      // This is register Rm, but using a 4-bit specifier. Used in NEON
      // by-element instructions.
      reg_num = instr->GetRmLow16();
      break;
    case 'f':
      // This is register Rm, but using an element size dependent number of bits
      // in the register specifier.
      reg_num =
          (instr->GetNEONSize() < 2) ? instr->GetRmLow16() : instr->GetRm();
      break;
    case 'a':
      reg_num = instr->GetRa();
      break;
    case 's':
      reg_num = instr->GetRs();
      break;
    case 't':
      reg_num = instr->GetRt();
      break;
    default:
      VIXL_UNREACHABLE();
  }

  switch (field[1]) {
    case '2':
    case '3':
    case '4':
      if ((reg_prefix == 'V') || (reg_prefix == 'Z')) {  // t2/3/4, n2/3/4
        VIXL_ASSERT((field[0] == 't') || (field[0] == 'n'));
        reg_num = (reg_num + field[1] - '1') % 32;
        field_len++;
      } else {
        VIXL_ASSERT((field[0] == 't') && (field[1] == '2'));
        reg_num = instr->GetRt2();
        field_len++;
      }
      break;
    case '+':  // Rt+, Rs+ (ie. Rt + 1, Rs + 1)
      VIXL_ASSERT((reg_prefix == 'W') || (reg_prefix == 'X'));
      VIXL_ASSERT((field[0] == 's') || (field[0] == 't'));
      reg_num++;
      field_len++;
      break;
    case 's':  // Core registers that are (w)sp rather than zr.
      VIXL_ASSERT((reg_prefix == 'W') || (reg_prefix == 'X'));
      reg_num = (reg_num == kZeroRegCode) ? kSPRegInternalCode : reg_num;
      field_len++;
      break;
  }

  VIXL_ASSERT(reg_num != UINT_MAX);
  return std::make_pair(reg_num, field_len);
}

int Disassembler::SubstituteRegisterField(const Instruction *instr,
                                          const char *format) {
  unsigned field_len = 1;  // Initially, count only the first character.

  // The first character of the register format field, eg R, X, S, etc.
  char reg_prefix = format[0];

  // Pointer to the character after the prefix. This may be one of the standard
  // symbols representing a register encoding, or a two digit bit position,
  // handled by the following code.
  const char *reg_field = &format[1];

  if (reg_prefix == 'R') {
    bool is_x = instr->GetSixtyFourBits() == 1;
    if (strspn(reg_field, "0123456789") == 2) {  // r20d, r31n, etc.
      // Core W or X registers where the type is determined by a specified bit
      // position, eg. 'R20d, 'R05n. This is like the 'Rd syntax, where bit 31
      // is implicitly used to select between W and X.
      int bitpos = ((reg_field[0] - '0') * 10) + (reg_field[1] - '0');
      VIXL_ASSERT(bitpos <= 31);
      is_x = (instr->ExtractBit(bitpos) == 1);
      reg_field = &format[3];
      field_len += 2;
    }
    reg_prefix = is_x ? 'X' : 'W';
  }

  std::pair<unsigned, unsigned> rn =
      GetRegNumForField(instr, reg_prefix, reg_field);
  unsigned reg_num = rn.first;
  field_len += rn.second;

  if (reg_field[0] == 'm') {
    switch (reg_field[1]) {
      // Handle registers tagged with b (bytes), z (instruction), or
      // r (registers), used for address updates in NEON load/store
      // instructions.
      case 'r':
      case 'b':
      case 'z': {
        VIXL_ASSERT(reg_prefix == 'X');
        field_len = 3;
        char *eimm;
        int imm = static_cast<int>(strtol(&reg_field[2], &eimm, 10));
        field_len += static_cast<unsigned>(eimm - &reg_field[2]);
        if (reg_num == 31) {
          switch (reg_field[1]) {
            case 'z':
              imm *= (1 << instr->GetNEONLSSize());
              break;
            case 'r':
              imm *= (instr->GetNEONQ() == 0) ? kDRegSizeInBytes
                                              : kQRegSizeInBytes;
              break;
            case 'b':
              break;
          }
          AppendToOutput("#%d", imm);
          return field_len;
        }
        break;
      }
    }
  }

  CPURegister::RegisterType reg_type = CPURegister::kRegister;
  unsigned reg_size = kXRegSize;

  if (reg_prefix == 'F') {
    switch (instr->GetFPType()) {
      case 3:
        reg_prefix = 'H';
        break;
      case 0:
        reg_prefix = 'S';
        break;
      default:
        reg_prefix = 'D';
    }
  }

  switch (reg_prefix) {
    case 'W':
      reg_type = CPURegister::kRegister;
      reg_size = kWRegSize;
      break;
    case 'X':
      reg_type = CPURegister::kRegister;
      reg_size = kXRegSize;
      break;
    case 'B':
      reg_type = CPURegister::kVRegister;
      reg_size = kBRegSize;
      break;
    case 'H':
      reg_type = CPURegister::kVRegister;
      reg_size = kHRegSize;
      break;
    case 'S':
      reg_type = CPURegister::kVRegister;
      reg_size = kSRegSize;
      break;
    case 'D':
      reg_type = CPURegister::kVRegister;
      reg_size = kDRegSize;
      break;
    case 'Q':
      reg_type = CPURegister::kVRegister;
      reg_size = kQRegSize;
      break;
    case 'V':
      if (reg_field[1] == 'v') {
        reg_type = CPURegister::kVRegister;
        reg_size = 1 << (instr->GetSVESize() + 3);
        field_len++;
        break;
      }
      AppendToOutput("v%d", reg_num);
      return field_len;
    case 'Z':
      AppendToOutput("z%d", reg_num);
      return field_len;
    default:
      VIXL_UNREACHABLE();
  }

  AppendRegisterNameToOutput(instr, CPURegister(reg_num, reg_size, reg_type));

  return field_len;
}

int Disassembler::SubstitutePredicateRegisterField(const Instruction *instr,
                                                   const char *format) {
  VIXL_ASSERT(format[0] == 'P');
  switch (format[1]) {
    // This field only supports P register that are always encoded in the same
    // position.
    case 'd':
    case 't':
      AppendToOutput("p%u", instr->GetPt());
      break;
    case 'n':
      AppendToOutput("p%u", instr->GetPn());
      break;
    case 'm':
      AppendToOutput("p%u", instr->GetPm());
      break;
    case 'g':
      VIXL_ASSERT(format[2] == 'l');
      AppendToOutput("p%u", instr->GetPgLow8());
      return 3;
    default:
      VIXL_UNREACHABLE();
  }
  return 2;
}

int Disassembler::SubstituteImmediateField(const Instruction *instr,
                                           const char *format) {
  VIXL_ASSERT(format[0] == 'I');

  switch (format[1]) {
    case 'M': {  // IMoveImm, IMoveNeg or IMoveLSL.
      if (format[5] == 'L') {
        AppendToOutput("#0x%" PRIx32, instr->GetImmMoveWide());
        if (instr->GetShiftMoveWide() > 0) {
          AppendToOutput(", lsl #%" PRId32, 16 * instr->GetShiftMoveWide());
        }
      } else {
        VIXL_ASSERT((format[5] == 'I') || (format[5] == 'N'));
        uint64_t imm = static_cast<uint64_t>(instr->GetImmMoveWide())
                       << (16 * instr->GetShiftMoveWide());
        if (format[5] == 'N') imm = ~imm;
        if (!instr->GetSixtyFourBits()) imm &= UINT64_C(0xffffffff);
        AppendToOutput("#0x%" PRIx64, imm);
      }
      return 8;
    }
    case 'L': {
      switch (format[2]) {
        case 'L': {  // ILLiteral - Immediate Load Literal.
          AppendToOutput("pc%+" PRId32,
                         instr->GetImmLLiteral() *
                             static_cast<int>(kLiteralEntrySize));
          return 9;
        }
        case 'S': {  // ILS - Immediate Load/Store.
                     // ILSi - As above, but an index field which must not be
                     // omitted even if it is zero.
          bool is_index = format[3] == 'i';
          if (is_index || (instr->GetImmLS() != 0)) {
            AppendToOutput(", #%" PRId32, instr->GetImmLS());
          }
          return is_index ? 4 : 3;
        }
        case 'P': {  // ILPx - Immediate Load/Store Pair, x = access size.
                     // ILPxi - As above, but an index field which must not be
                     // omitted even if it is zero.
          VIXL_ASSERT((format[3] >= '0') && (format[3] <= '9'));
          bool is_index = format[4] == 'i';
          if (is_index || (instr->GetImmLSPair() != 0)) {
            // format[3] is the scale value. Convert to a number.
            int scale = 1 << (format[3] - '0');
            AppendToOutput(", #%" PRId32, instr->GetImmLSPair() * scale);
          }
          return is_index ? 5 : 4;
        }
        case 'U': {  // ILU - Immediate Load/Store Unsigned.
          if (instr->GetImmLSUnsigned() != 0) {
            int shift = instr->GetSizeLS();
            AppendToOutput(", #%" PRId32, instr->GetImmLSUnsigned() << shift);
          }
          return 3;
        }
        case 'A': {  // ILA - Immediate Load with pointer authentication.
          if (instr->GetImmLSPAC() != 0) {
            AppendToOutput(", #%" PRId32, instr->GetImmLSPAC());
          }
          return 3;
        }
        default: {
          VIXL_UNIMPLEMENTED();
          return 0;
        }
      }
    }
    case 'C': {  // ICondB - Immediate Conditional Branch.
      int64_t offset = instr->GetImmCondBranch() << 2;
      AppendPCRelativeOffsetToOutput(instr, offset);
      return 6;
    }
    case 'A': {  // IAddSub.
      int64_t imm = instr->GetImmAddSub() << (12 * instr->GetImmAddSubShift());
      AppendToOutput("#0x%" PRIx64 " (%" PRId64 ")", imm, imm);
      return 7;
    }
    case 'F': {  // IFP, IFPNeon, IFPSve or IFPFBits.
      int imm8 = 0;
      size_t len = strlen("IFP");
      switch (format[3]) {
        case 'F':
          VIXL_ASSERT(strncmp(format, "IFPFBits", strlen("IFPFBits")) == 0);
          AppendToOutput("#%" PRId32, 64 - instr->GetFPScale());
          return static_cast<int>(strlen("IFPFBits"));
        case 'N':
          VIXL_ASSERT(strncmp(format, "IFPNeon", strlen("IFPNeon")) == 0);
          imm8 = instr->GetImmNEONabcdefgh();
          len += strlen("Neon");
          break;
        case 'S':
          VIXL_ASSERT(strncmp(format, "IFPSve", strlen("IFPSve")) == 0);
          imm8 = instr->ExtractBits(12, 5);
          len += strlen("Sve");
          break;
        default:
          VIXL_ASSERT(strncmp(format, "IFP", strlen("IFP")) == 0);
          imm8 = instr->GetImmFP();
          break;
      }
      AppendToOutput("#0x%" PRIx32 " (%.4f)",
                     imm8,
                     Instruction::Imm8ToFP32(imm8));
      return static_cast<int>(len);
    }
    case 'H': {  // IH - ImmHint
      AppendToOutput("#%" PRId32, instr->GetImmHint());
      return 2;
    }
    case 'T': {  // ITri - Immediate Triangular Encoded.
      if (format[4] == 'S') {
        VIXL_ASSERT((format[5] == 'v') && (format[6] == 'e'));
        switch (format[7]) {
          case 'l':
            // SVE logical immediate encoding.
            AppendToOutput("#0x%" PRIx64, instr->GetSVEImmLogical());
            return 8;
          case 'p': {
            // SVE predicated shift immediate encoding, lsl.
            std::pair<int, int> shift_and_lane_size =
                instr->GetSVEImmShiftAndLaneSizeLog2(
                    /* is_predicated = */ true);
            int lane_bits = 8 << shift_and_lane_size.second;
            AppendToOutput("#%" PRId32, lane_bits - shift_and_lane_size.first);
            return 8;
          }
          case 'q': {
            // SVE predicated shift immediate encoding, asr and lsr.
            std::pair<int, int> shift_and_lane_size =
                instr->GetSVEImmShiftAndLaneSizeLog2(
                    /* is_predicated = */ true);
            AppendToOutput("#%" PRId32, shift_and_lane_size.first);
            return 8;
          }
          case 'r': {
            // SVE unpredicated shift immediate encoding, left shifts.
            std::pair<int, int> shift_and_lane_size =
                instr->GetSVEImmShiftAndLaneSizeLog2(
                    /* is_predicated = */ false);
            int lane_bits = 8 << shift_and_lane_size.second;
            AppendToOutput("#%" PRId32, lane_bits - shift_and_lane_size.first);
            return 8;
          }
          case 's': {
            // SVE unpredicated shift immediate encoding, right shifts.
            std::pair<int, int> shift_and_lane_size =
                instr->GetSVEImmShiftAndLaneSizeLog2(
                    /* is_predicated = */ false);
            AppendToOutput("#%" PRId32, shift_and_lane_size.first);
            return 8;
          }
          default:
            VIXL_UNREACHABLE();
            return 0;
        }
      } else {
        AppendToOutput("#0x%" PRIx64, instr->GetImmLogical());
        return 4;
      }
    }
    case 'N': {  // INzcv.
      int nzcv = (instr->GetNzcv() << Flags_offset);
      AppendToOutput("#%c%c%c%c",
                     ((nzcv & NFlag) == 0) ? 'n' : 'N',
                     ((nzcv & ZFlag) == 0) ? 'z' : 'Z',
                     ((nzcv & CFlag) == 0) ? 'c' : 'C',
                     ((nzcv & VFlag) == 0) ? 'v' : 'V');
      return 5;
    }
    case 'P': {  // IP - Conditional compare.
      AppendToOutput("#%" PRId32, instr->GetImmCondCmp());
      return 2;
    }
    case 'B': {  // Bitfields.
      return SubstituteBitfieldImmediateField(instr, format);
    }
    case 'E': {  // IExtract.
      AppendToOutput("#%" PRId32, instr->GetImmS());
      return 8;
    }
    case 't': {  // It - Test and branch bit.
      AppendToOutput("#%" PRId32,
                     (instr->GetImmTestBranchBit5() << 5) |
                         instr->GetImmTestBranchBit40());
      return 2;
    }
    case 'S': {  // ISveSvl - SVE 'mul vl' immediate for structured ld/st.
      VIXL_ASSERT(strncmp(format, "ISveSvl", 7) == 0);
      int imm = instr->ExtractSignedBits(19, 16);
      if (imm != 0) {
        int reg_count = instr->ExtractBits(22, 21) + 1;
        AppendToOutput(", #%d, mul vl", imm * reg_count);
      }
      return 7;
    }
    case 's': {  // Is - Shift (immediate).
      switch (format[2]) {
        case 'R': {  // IsR - right shifts.
          int shift = 16 << HighestSetBitPosition(instr->GetImmNEONImmh());
          shift -= instr->GetImmNEONImmhImmb();
          AppendToOutput("#%d", shift);
          return 3;
        }
        case 'L': {  // IsL - left shifts.
          int shift = instr->GetImmNEONImmhImmb();
          shift -= 8 << HighestSetBitPosition(instr->GetImmNEONImmh());
          AppendToOutput("#%d", shift);
          return 3;
        }
        default: {
          VIXL_UNIMPLEMENTED();
          return 0;
        }
      }
    }
    case 'D': {  // IDebug - HLT and BRK instructions.
      AppendToOutput("#0x%" PRIx32, instr->GetImmException());
      return 6;
    }
    case 'U': {  // IUdf - UDF immediate.
      AppendToOutput("#0x%" PRIx32, instr->GetImmUdf());
      return 4;
    }
    case 'V': {  // Immediate Vector.
      switch (format[2]) {
        case 'E': {  // IVExtract.
          AppendToOutput("#%" PRId32, instr->GetImmNEONExt());
          return 9;
        }
        case 'B': {  // IVByElemIndex.
          int ret = static_cast<int>(strlen("IVByElemIndex"));
          uint32_t vm_index = instr->GetNEONH() << 2;
          vm_index |= instr->GetNEONL() << 1;
          vm_index |= instr->GetNEONM();

          static const char *format_rot = "IVByElemIndexRot";
          static const char *format_fhm = "IVByElemIndexFHM";
          if (strncmp(format, format_rot, strlen(format_rot)) == 0) {
            // FCMLA uses 'H' bit index when SIZE is 2, else H:L
            VIXL_ASSERT((instr->GetNEONSize() == 1) ||
                        (instr->GetNEONSize() == 2));
            vm_index >>= instr->GetNEONSize();
            ret = static_cast<int>(strlen(format_rot));
          } else if (strncmp(format, format_fhm, strlen(format_fhm)) == 0) {
            // Nothing to do - FMLAL and FMLSL use H:L:M.
            ret = static_cast<int>(strlen(format_fhm));
          } else {
            if (instr->GetNEONSize() == 2) {
              // S-sized elements use H:L.
              vm_index >>= 1;
            } else if (instr->GetNEONSize() == 3) {
              // D-sized elements use H.
              vm_index >>= 2;
            }
          }
          AppendToOutput("%d", vm_index);
          return ret;
        }
        case 'I': {  // INS element.
          if (strncmp(format, "IVInsIndex", strlen("IVInsIndex")) == 0) {
            unsigned rd_index, rn_index;
            unsigned imm5 = instr->GetImmNEON5();
            unsigned imm4 = instr->GetImmNEON4();
            int tz = CountTrailingZeros(imm5, 32);
            if (tz <= 3) {  // Defined for tz = 0 to 3 only.
              rd_index = imm5 >> (tz + 1);
              rn_index = imm4 >> tz;
              if (strncmp(format, "IVInsIndex1", strlen("IVInsIndex1")) == 0) {
                AppendToOutput("%d", rd_index);
                return static_cast<int>(strlen("IVInsIndex1"));
              } else if (strncmp(format,
                                 "IVInsIndex2",
                                 strlen("IVInsIndex2")) == 0) {
                AppendToOutput("%d", rn_index);
                return static_cast<int>(strlen("IVInsIndex2"));
              }
            }
            return 0;
          } else if (strncmp(format,
                             "IVInsSVEIndex",
                             strlen("IVInsSVEIndex")) == 0) {
            std::pair<int, int> index_and_lane_size =
                instr->GetSVEPermuteIndexAndLaneSizeLog2();
            AppendToOutput("%d", index_and_lane_size.first);
            return static_cast<int>(strlen("IVInsSVEIndex"));
          }
          VIXL_FALLTHROUGH();
        }
        case 'L': {  // IVLSLane[0123] - suffix indicates access size shift.
          AppendToOutput("%d", instr->GetNEONLSIndex(format[8] - '0'));
          return 9;
        }
        case 'M': {  // Modified Immediate cases.
          if (strncmp(format, "IVMIImm8", strlen("IVMIImm8")) == 0) {
            uint64_t imm8 = instr->GetImmNEONabcdefgh();
            AppendToOutput("#0x%" PRIx64, imm8);
            return static_cast<int>(strlen("IVMIImm8"));
          } else if (strncmp(format, "IVMIImm", strlen("IVMIImm")) == 0) {
            uint64_t imm8 = instr->GetImmNEONabcdefgh();
            uint64_t imm = 0;
            for (int i = 0; i < 8; ++i) {
              if (imm8 & (UINT64_C(1) << i)) {
                imm |= (UINT64_C(0xff) << (8 * i));
              }
            }
            AppendToOutput("#0x%" PRIx64, imm);
            return static_cast<int>(strlen("IVMIImm"));
          } else if (strncmp(format,
                             "IVMIShiftAmt1",
                             strlen("IVMIShiftAmt1")) == 0) {
            int cmode = instr->GetNEONCmode();
            int shift_amount = 8 * ((cmode >> 1) & 3);
            AppendToOutput("#%d", shift_amount);
            return static_cast<int>(strlen("IVMIShiftAmt1"));
          } else if (strncmp(format,
                             "IVMIShiftAmt2",
                             strlen("IVMIShiftAmt2")) == 0) {
            int cmode = instr->GetNEONCmode();
            int shift_amount = 8 << (cmode & 1);
            AppendToOutput("#%d", shift_amount);
            return static_cast<int>(strlen("IVMIShiftAmt2"));
          } else {
            VIXL_UNIMPLEMENTED();
            return 0;
          }
        }
        default: {
          VIXL_UNIMPLEMENTED();
          return 0;
        }
      }
    }
    case 'X': {  // IX - CLREX instruction.
      AppendToOutput("#0x%" PRIx32, instr->GetCRm());
      return 2;
    }
    case 'Y': {  // IY - system register immediate.
      switch (instr->GetImmSystemRegister()) {
        case NZCV:
          AppendToOutput("nzcv");
          break;
        case FPCR:
          AppendToOutput("fpcr");
          break;
        case RNDR:
          AppendToOutput("rndr");
          break;
        case RNDRRS:
          AppendToOutput("rndrrs");
          break;
        default:
          AppendToOutput("S%d_%d_c%d_c%d_%d",
                         instr->GetSysOp0(),
                         instr->GetSysOp1(),
                         instr->GetCRn(),
                         instr->GetCRm(),
                         instr->GetSysOp2());
          break;
      }
      return 2;
    }
    case 'R': {  // IR - Rotate right into flags.
      switch (format[2]) {
        case 'r': {  // IRr - Rotate amount.
          AppendToOutput("#%d", instr->GetImmRMIFRotation());
          return 3;
        }
        default: {
          VIXL_UNIMPLEMENTED();
          return 0;
        }
      }
    }
    case 'p': {  // Ipc - SVE predicate constraint specifier.
      VIXL_ASSERT(format[2] == 'c');
      unsigned pattern = instr->GetImmSVEPredicateConstraint();
      switch (pattern) {
        // VL1-VL8 are encoded directly.
        case SVE_VL1:
        case SVE_VL2:
        case SVE_VL3:
        case SVE_VL4:
        case SVE_VL5:
        case SVE_VL6:
        case SVE_VL7:
        case SVE_VL8:
          AppendToOutput("vl%u", pattern);
          break;
        // VL16-VL256 are encoded as log2(N) + c.
        case SVE_VL16:
        case SVE_VL32:
        case SVE_VL64:
        case SVE_VL128:
        case SVE_VL256:
          AppendToOutput("vl%u", 16 << (pattern - SVE_VL16));
          break;
        // Special cases.
        case SVE_POW2:
          AppendToOutput("pow2");
          break;
        case SVE_MUL4:
          AppendToOutput("mul4");
          break;
        case SVE_MUL3:
          AppendToOutput("mul3");
          break;
        case SVE_ALL:
          AppendToOutput("all");
          break;
        default:
          AppendToOutput("#0x%x", pattern);
          break;
      }
      return 3;
    }
    default: {
      VIXL_UNIMPLEMENTED();
      return 0;
    }
  }
}


int Disassembler::SubstituteBitfieldImmediateField(const Instruction *instr,
                                                   const char *format) {
  VIXL_ASSERT((format[0] == 'I') && (format[1] == 'B'));
  unsigned r = instr->GetImmR();
  unsigned s = instr->GetImmS();

  switch (format[2]) {
    case 'r': {  // IBr.
      AppendToOutput("#%d", r);
      return 3;
    }
    case 's': {  // IBs+1 or IBs-r+1.
      if (format[3] == '+') {
        AppendToOutput("#%d", s + 1);
        return 5;
      } else {
        VIXL_ASSERT(format[3] == '-');
        AppendToOutput("#%d", s - r + 1);
        return 7;
      }
    }
    case 'Z': {  // IBZ-r.
      VIXL_ASSERT((format[3] == '-') && (format[4] == 'r'));
      unsigned reg_size =
          (instr->GetSixtyFourBits() == 1) ? kXRegSize : kWRegSize;
      AppendToOutput("#%d", reg_size - r);
      return 5;
    }
    default: {
      VIXL_UNREACHABLE();
      return 0;
    }
  }
}


int Disassembler::SubstituteLiteralField(const Instruction *instr,
                                         const char *format) {
  VIXL_ASSERT(strncmp(format, "LValue", 6) == 0);
  USE(format);

  const void *address = instr->GetLiteralAddress<const void *>();
  switch (instr->Mask(LoadLiteralMask)) {
    case LDR_w_lit:
    case LDR_x_lit:
    case LDRSW_x_lit:
    case LDR_s_lit:
    case LDR_d_lit:
    case LDR_q_lit:
      AppendCodeRelativeDataAddressToOutput(instr, address);
      break;
    case PRFM_lit: {
      // Use the prefetch hint to decide how to print the address.
      switch (instr->GetPrefetchHint()) {
        case 0x0:  // PLD: prefetch for load.
        case 0x2:  // PST: prepare for store.
          AppendCodeRelativeDataAddressToOutput(instr, address);
          break;
        case 0x1:  // PLI: preload instructions.
          AppendCodeRelativeCodeAddressToOutput(instr, address);
          break;
        case 0x3:  // Unallocated hint.
          AppendCodeRelativeAddressToOutput(instr, address);
          break;
      }
      break;
    }
    default:
      VIXL_UNREACHABLE();
  }

  return 6;
}


int Disassembler::SubstituteShiftField(const Instruction *instr,
                                       const char *format) {
  VIXL_ASSERT(format[0] == 'N');
  VIXL_ASSERT(instr->GetShiftDP() <= 0x3);

  switch (format[1]) {
    case 'D': {  // NDP.
      VIXL_ASSERT(instr->GetShiftDP() != ROR);
      VIXL_FALLTHROUGH();
    }
    case 'L': {  // NLo.
      if (instr->GetImmDPShift() != 0) {
        const char *shift_type[] = {"lsl", "lsr", "asr", "ror"};
        AppendToOutput(", %s #%" PRId32,
                       shift_type[instr->GetShiftDP()],
                       instr->GetImmDPShift());
      }
      return 3;
    }
    case 'S': {  // NSveS (SVE structured load/store indexing shift).
      VIXL_ASSERT(strncmp(format, "NSveS", 5) == 0);
      int msz = instr->ExtractBits(24, 23);
      if (msz > 0) {
        AppendToOutput(", lsl #%d", msz);
      }
      return 5;
    }
    default:
      VIXL_UNIMPLEMENTED();
      return 0;
  }
}


int Disassembler::SubstituteConditionField(const Instruction *instr,
                                           const char *format) {
  VIXL_ASSERT(format[0] == 'C');
  const char *condition_code[] = {"eq",
                                  "ne",
                                  "hs",
                                  "lo",
                                  "mi",
                                  "pl",
                                  "vs",
                                  "vc",
                                  "hi",
                                  "ls",
                                  "ge",
                                  "lt",
                                  "gt",
                                  "le",
                                  "al",
                                  "nv"};
  int cond;
  switch (format[1]) {
    case 'B':
      cond = instr->GetConditionBranch();
      break;
    case 'I': {
      cond = InvertCondition(static_cast<Condition>(instr->GetCondition()));
      break;
    }
    default:
      cond = instr->GetCondition();
  }
  AppendToOutput("%s", condition_code[cond]);
  return 4;
}


int Disassembler::SubstitutePCRelAddressField(const Instruction *instr,
                                              const char *format) {
  VIXL_ASSERT((strcmp(format, "AddrPCRelByte") == 0) ||  // Used by `adr`.
              (strcmp(format, "AddrPCRelPage") == 0));   // Used by `adrp`.

  int64_t offset = instr->GetImmPCRel();

  // Compute the target address based on the effective address (after applying
  // code_address_offset). This is required for correct behaviour of adrp.
  const Instruction *base = instr + code_address_offset();
  if (format[9] == 'P') {
    offset *= kPageSize;
    base = AlignDown(base, kPageSize);
  }
  // Strip code_address_offset before printing, so we can use the
  // semantically-correct AppendCodeRelativeAddressToOutput.
  const void *target =
      reinterpret_cast<const void *>(base + offset - code_address_offset());

  AppendPCRelativeOffsetToOutput(instr, offset);
  AppendToOutput(" ");
  AppendCodeRelativeAddressToOutput(instr, target);
  return 13;
}


int Disassembler::SubstituteBranchTargetField(const Instruction *instr,
                                              const char *format) {
  VIXL_ASSERT(strncmp(format, "TImm", 4) == 0);

  int64_t offset = 0;
  switch (format[5]) {
    // BImmUncn - unconditional branch immediate.
    case 'n':
      offset = instr->GetImmUncondBranch();
      break;
    // BImmCond - conditional branch immediate.
    case 'o':
      offset = instr->GetImmCondBranch();
      break;
    // BImmCmpa - compare and branch immediate.
    case 'm':
      offset = instr->GetImmCmpBranch();
      break;
    // BImmTest - test and branch immediate.
    case 'e':
      offset = instr->GetImmTestBranch();
      break;
    default:
      VIXL_UNIMPLEMENTED();
  }
  offset *= static_cast<int>(kInstructionSize);
  const void *target_address = reinterpret_cast<const void *>(instr + offset);
  VIXL_STATIC_ASSERT(sizeof(*instr) == 1);

  AppendPCRelativeOffsetToOutput(instr, offset);
  AppendToOutput(" ");
  AppendCodeRelativeCodeAddressToOutput(instr, target_address);

  return 8;
}


int Disassembler::SubstituteExtendField(const Instruction *instr,
                                        const char *format) {
  VIXL_ASSERT(strncmp(format, "Ext", 3) == 0);
  VIXL_ASSERT(instr->GetExtendMode() <= 7);
  USE(format);

  const char *extend_mode[] =
      {"uxtb", "uxth", "uxtw", "uxtx", "sxtb", "sxth", "sxtw", "sxtx"};

  // If rd or rn is SP, uxtw on 32-bit registers and uxtx on 64-bit
  // registers becomes lsl.
  if (((instr->GetRd() == kZeroRegCode) || (instr->GetRn() == kZeroRegCode)) &&
      (((instr->GetExtendMode() == UXTW) && (instr->GetSixtyFourBits() == 0)) ||
       (instr->GetExtendMode() == UXTX))) {
    if (instr->GetImmExtendShift() > 0) {
      AppendToOutput(", lsl #%" PRId32, instr->GetImmExtendShift());
    }
  } else {
    AppendToOutput(", %s", extend_mode[instr->GetExtendMode()]);
    if (instr->GetImmExtendShift() > 0) {
      AppendToOutput(" #%" PRId32, instr->GetImmExtendShift());
    }
  }
  return 3;
}


int Disassembler::SubstituteLSRegOffsetField(const Instruction *instr,
                                             const char *format) {
  VIXL_ASSERT(strncmp(format, "Offsetreg", 9) == 0);
  const char *extend_mode[] = {"undefined",
                               "undefined",
                               "uxtw",
                               "lsl",
                               "undefined",
                               "undefined",
                               "sxtw",
                               "sxtx"};
  USE(format);

  unsigned shift = instr->GetImmShiftLS();
  Extend ext = static_cast<Extend>(instr->GetExtendMode());
  char reg_type = ((ext == UXTW) || (ext == SXTW)) ? 'w' : 'x';

  unsigned rm = instr->GetRm();
  if (rm == kZeroRegCode) {
    AppendToOutput("%czr", reg_type);
  } else {
    AppendToOutput("%c%d", reg_type, rm);
  }

  // Extend mode UXTX is an alias for shift mode LSL here.
  if (!((ext == UXTX) && (shift == 0))) {
    AppendToOutput(", %s", extend_mode[ext]);
    if (shift != 0) {
      AppendToOutput(" #%d", instr->GetSizeLS());
    }
  }
  return 9;
}


int Disassembler::SubstitutePrefetchField(const Instruction *instr,
                                          const char *format) {
  VIXL_ASSERT(format[0] == 'p');
  USE(format);

  bool is_sve =
      (strncmp(format, "prefSVEOp", strlen("prefSVEOp")) == 0) ? true : false;
  int placeholder_length = is_sve ? 9 : 6;
  static const char *stream_options[] = {"keep", "strm"};

  auto get_hints = [](bool want_sve_hint) -> std::vector<std::string> {
    static const std::vector<std::string> sve_hints = {"ld", "st"};
    static const std::vector<std::string> core_hints = {"ld", "li", "st"};
    return (want_sve_hint) ? sve_hints : core_hints;
  };

  std::vector<std::string> hints = get_hints(is_sve);
  unsigned hint =
      is_sve ? instr->GetSVEPrefetchHint() : instr->GetPrefetchHint();
  unsigned target = instr->GetPrefetchTarget() + 1;
  unsigned stream = instr->GetPrefetchStream();

  if ((hint >= hints.size()) || (target > 3)) {
    // Unallocated prefetch operations.
    if (is_sve) {
      std::bitset<4> prefetch_mode(instr->GetSVEImmPrefetchOperation());
      AppendToOutput("#0b%s", prefetch_mode.to_string().c_str());
    } else {
      std::bitset<5> prefetch_mode(instr->GetImmPrefetchOperation());
      AppendToOutput("#0b%s", prefetch_mode.to_string().c_str());
    }
  } else {
    VIXL_ASSERT(stream < ArrayLength(stream_options));
    AppendToOutput("p%sl%d%s",
                   hints[hint].c_str(),
                   target,
                   stream_options[stream]);
  }
  return placeholder_length;
}

int Disassembler::SubstituteBarrierField(const Instruction *instr,
                                         const char *format) {
  VIXL_ASSERT(format[0] == 'M');
  USE(format);

  static const char *options[4][4] = {{"sy (0b0000)", "oshld", "oshst", "osh"},
                                      {"sy (0b0100)", "nshld", "nshst", "nsh"},
                                      {"sy (0b1000)", "ishld", "ishst", "ish"},
                                      {"sy (0b1100)", "ld", "st", "sy"}};
  int domain = instr->GetImmBarrierDomain();
  int type = instr->GetImmBarrierType();

  AppendToOutput("%s", options[domain][type]);
  return 1;
}

int Disassembler::SubstituteSysOpField(const Instruction *instr,
                                       const char *format) {
  VIXL_ASSERT(format[0] == 'G');
  int op = -1;
  switch (format[1]) {
    case '1':
      op = instr->GetSysOp1();
      break;
    case '2':
      op = instr->GetSysOp2();
      break;
    default:
      VIXL_UNREACHABLE();
  }
  AppendToOutput("#%d", op);
  return 2;
}

int Disassembler::SubstituteCrField(const Instruction *instr,
                                    const char *format) {
  VIXL_ASSERT(format[0] == 'K');
  int cr = -1;
  switch (format[1]) {
    case 'n':
      cr = instr->GetCRn();
      break;
    case 'm':
      cr = instr->GetCRm();
      break;
    default:
      VIXL_UNREACHABLE();
  }
  AppendToOutput("C%d", cr);
  return 2;
}

int Disassembler::SubstituteIntField(const Instruction *instr,
                                     const char *format) {
  VIXL_ASSERT((format[0] == 'u') || (format[0] == 's'));

  // A generic signed or unsigned int field uses a placeholder of the form
  // 'sAABB and 'uAABB respectively where AA and BB are two digit bit positions
  // between 00 and 31, and AA >= BB. The placeholder is substituted with the
  // decimal integer represented by the bits in the instruction between
  // positions AA and BB inclusive.
  //
  // In addition, split fields can be represented using 'sAABB:CCDD, where CCDD
  // become the least-significant bits of the result, and bit AA is the sign bit
  // (if 's is used).
  int32_t bits = 0;
  int width = 0;
  const char *c = format;
  do {
    c++;  // Skip the 'u', 's' or ':'.
    VIXL_ASSERT(strspn(c, "0123456789") == 4);
    int msb = ((c[0] - '0') * 10) + (c[1] - '0');
    int lsb = ((c[2] - '0') * 10) + (c[3] - '0');
    c += 4;  // Skip the characters we just read.
    int chunk_width = msb - lsb + 1;
    VIXL_ASSERT((chunk_width > 0) && (chunk_width < 32));
    bits = (bits << chunk_width) | (instr->ExtractBits(msb, lsb));
    width += chunk_width;
  } while (*c == ':');
  VIXL_ASSERT(IsUintN(width, bits));

  if (format[0] == 's') {
    bits = ExtractSignedBitfield32(width - 1, 0, bits);
  }

  if (*c == '+') {
    // A "+n" trailing the format specifier indicates the extracted value should
    // be incremented by n. This is for cases where the encoding is zero-based,
    // but range of values is not, eg. values [1, 16] encoded as [0, 15]
    char *new_c;
    uint64_t value = strtoul(c + 1, &new_c, 10);
    c = new_c;
    VIXL_ASSERT(IsInt32(value));
    bits = static_cast<int32_t>(bits + value);
  } else if (*c == '*') {
    // Similarly, a "*n" trailing the format specifier indicates the extracted
    // value should be multiplied by n. This is for cases where the encoded
    // immediate is scaled, for example by access size.
    char *new_c;
    uint64_t value = strtoul(c + 1, &new_c, 10);
    c = new_c;
    VIXL_ASSERT(IsInt32(value));
    bits = static_cast<int32_t>(bits * value);
  }

  AppendToOutput("%d", bits);

  return static_cast<int>(c - format);
}

int Disassembler::SubstituteSVESize(const Instruction *instr,
                                    const char *format) {
  USE(format);
  VIXL_ASSERT(format[0] == 't');

  static const char sizes[] = {'b', 'h', 's', 'd', 'q'};
  unsigned size_in_bytes_log2 = instr->GetSVESize();
  int placeholder_length = 1;
  switch (format[1]) {
    case 'f':  // 'tf - FP size encoded in <18:17>
      placeholder_length++;
      size_in_bytes_log2 = instr->ExtractBits(18, 17);
      break;
    case 'l':
      placeholder_length++;
      if (format[2] == 's') {
        // 'tls: Loads and stores
        size_in_bytes_log2 = instr->ExtractBits(22, 21);
        placeholder_length++;
        if (format[3] == 's') {
          // Sign extension load.
          unsigned msize = instr->ExtractBits(24, 23);
          if (msize > size_in_bytes_log2) size_in_bytes_log2 ^= 0x3;
          placeholder_length++;
        }
      } else {
        // 'tl: Logical operations
        size_in_bytes_log2 = instr->GetSVEBitwiseImmLaneSizeInBytesLog2();
      }
      break;
    case 'm':  // 'tmsz
      VIXL_ASSERT(strncmp(format, "tmsz", 4) == 0);
      placeholder_length += 3;
      size_in_bytes_log2 = instr->ExtractBits(24, 23);
      break;
    case 'i': {  // 'ti: indices.
      std::pair<int, int> index_and_lane_size =
          instr->GetSVEPermuteIndexAndLaneSizeLog2();
      placeholder_length++;
      size_in_bytes_log2 = index_and_lane_size.second;
      break;
    }
    case 's':
      if (format[2] == 'z') {
        VIXL_ASSERT((format[3] == 'p') || (format[3] == 's') ||
                    (format[3] == 'd'));
        bool is_predicated = (format[3] == 'p');
        std::pair<int, int> shift_and_lane_size =
            instr->GetSVEImmShiftAndLaneSizeLog2(is_predicated);
        size_in_bytes_log2 = shift_and_lane_size.second;
        if (format[3] == 'd') {  // Double size lanes.
          size_in_bytes_log2++;
        }
        placeholder_length += 3;  // skip "sz(p|s|d)"
      }
      break;
    case 'h':
      // Half size of the lane size field.
      size_in_bytes_log2 -= 1;
      placeholder_length++;
      break;
    case 'q':
      // Quarter size of the lane size field.
      size_in_bytes_log2 -= 2;
      placeholder_length++;
      break;
    default:
      break;
  }

  VIXL_ASSERT(size_in_bytes_log2 < ArrayLength(sizes));
  AppendToOutput("%c", sizes[size_in_bytes_log2]);

  return placeholder_length;
}

int Disassembler::SubstituteTernary(const Instruction *instr,
                                    const char *format) {
  VIXL_ASSERT((format[0] == '?') && (format[3] == ':'));

  // The ternary substitution of the format "'?bb:TF" is replaced by a single
  // character, either T or F, depending on the value of the bit at position
  // bb in the instruction. For example, "'?31:xw" is substituted with "x" if
  // bit 31 is true, and "w" otherwise.
  VIXL_ASSERT(strspn(&format[1], "0123456789") == 2);
  char *c;
  uint64_t value = strtoul(&format[1], &c, 10);
  VIXL_ASSERT(value < (kInstructionSize * kBitsPerByte));
  VIXL_ASSERT((*c == ':') && (strlen(c) >= 3));  // Minimum of ":TF"
  c++;
  AppendToOutput("%c", c[1 - instr->ExtractBit(static_cast<int>(value))]);
  return 6;
}

void Disassembler::ResetOutput() {
  buffer_pos_ = 0;
  buffer_[buffer_pos_] = 0;
}


void Disassembler::AppendToOutput(const char *format, ...) {
  va_list args;
  va_start(args, format);
  buffer_pos_ += vsnprintf(&buffer_[buffer_pos_],
                           buffer_size_ - buffer_pos_,
                           format,
                           args);
  va_end(args);
}


void PrintDisassembler::Disassemble(const Instruction *instr) {
  Decoder decoder;
  if (cpu_features_auditor_ != NULL) {
    decoder.AppendVisitor(cpu_features_auditor_);
  }
  decoder.AppendVisitor(this);
  decoder.Decode(instr);
}

void PrintDisassembler::DisassembleBuffer(const Instruction *start,
                                          const Instruction *end) {
  Decoder decoder;
  if (cpu_features_auditor_ != NULL) {
    decoder.AppendVisitor(cpu_features_auditor_);
  }
  decoder.AppendVisitor(this);
  decoder.Decode(start, end);
}

void PrintDisassembler::DisassembleBuffer(const Instruction *start,
                                          uint64_t size) {
  DisassembleBuffer(start, start + size);
}


void PrintDisassembler::ProcessOutput(const Instruction *instr) {
  int64_t address = CodeRelativeAddress(instr);

  uint64_t abs_address;
  const char *sign;
  if (signed_addresses_) {
    if (address < 0) {
      sign = "-";
      abs_address = UnsignedNegate(static_cast<uint64_t>(address));
    } else {
      // Leave a leading space, to maintain alignment.
      sign = " ";
      abs_address = address;
    }
  } else {
    sign = "";
    abs_address = address;
  }

  int bytes_printed = fprintf(stream_,
                              "%s0x%016" PRIx64 "  %08" PRIx32 "\t\t%s",
                              sign,
                              abs_address,
                              instr->GetInstructionBits(),
                              GetOutput());
  if (cpu_features_auditor_ != NULL) {
    CPUFeatures needs = cpu_features_auditor_->GetInstructionFeatures();
    needs.Remove(cpu_features_auditor_->GetAvailableFeatures());
    if (needs != CPUFeatures::None()) {
      // Try to align annotations. This value is arbitrary, but based on looking
      // good with most instructions. Note that, for historical reasons, the
      // disassembly itself is printed with tab characters, so bytes_printed is
      // _not_ equivalent to the number of occupied screen columns. However, the
      // prefix before the tabs is always the same length, so the annotation
      // indentation does not change from one line to the next.
      const int indent_to = 70;
      // Always allow some space between the instruction and the annotation.
      const int min_pad = 2;

      int pad = std::max(min_pad, (indent_to - bytes_printed));
      fprintf(stream_, "%*s", pad, "");

      std::stringstream features;
      features << needs;
      fprintf(stream_,
              "%s%s%s",
              cpu_features_prefix_,
              features.str().c_str(),
              cpu_features_suffix_);
    }
  }
  fprintf(stream_, "\n");
}

}  // namespace aarch64
}  // namespace vixl
