// Copyright (c) 2014, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

#include "vm/globals.h"  // Needed here to get TARGET_ARCH_ARM64.
#if defined(TARGET_ARCH_ARM64)

#include "vm/compiler/assembler/disassembler.h"

#include "platform/assert.h"
#include "vm/instructions.h"

namespace dart {

#if !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)

class ARM64Decoder : public ValueObject {
 public:
  ARM64Decoder(char* buffer, size_t buffer_size)
      : buffer_(buffer), buffer_size_(buffer_size), buffer_pos_(0) {
    buffer_[buffer_pos_] = '\0';
  }

  ~ARM64Decoder() {}

  // Writes one disassembled instruction into 'buffer' (0-terminated).
  // Returns true if the instruction was successfully decoded, false otherwise.
  void InstructionDecode(uword pc);

 private:
  // Bottleneck functions to print into the out_buffer.
  void Print(const char* str);
  void PrintInt(int value);

  // Printing of common values.
  void PrintRegister(int reg, R31Type r31t);
  void PrintVRegister(int reg);
  void PrintShiftExtendRm(Instr* instr);
  void PrintMemOperand(Instr* instr);
  void PrintPairMemOperand(Instr* instr);
  void PrintS(Instr* instr);
  void PrintCondition(Instr* instr);
  void PrintInvertedCondition(Instr* instr);

  // Handle formatting of instructions and their options.
  int FormatRegister(Instr* instr, const char* option);
  int FormatVRegister(Instr* instr, const char* option);
  int FormatOption(Instr* instr, const char* format);
  void Format(Instr* instr, const char* format);
  void Unknown(Instr* instr);

// Decode instructions.
#define DECODE_OP(op) void Decode##op(Instr* instr);
  APPLY_OP_LIST(DECODE_OP)
#undef DECODE_OP

  // Convenience functions.
  char* get_buffer() const { return buffer_; }
  char* current_position_in_buffer() { return buffer_ + buffer_pos_; }
  size_t remaining_size_in_buffer() { return buffer_size_ - buffer_pos_; }

  char* buffer_;        // Decode instructions into this buffer.
  size_t buffer_size_;  // The size of the character buffer.
  size_t buffer_pos_;   // Current character position in buffer.

  DISALLOW_ALLOCATION();
  DISALLOW_COPY_AND_ASSIGN(ARM64Decoder);
};

// Support for assertions in the ARM64Decoder formatting functions.
#define STRING_STARTS_WITH(string, compare_string)                             \
  (strncmp(string, compare_string, strlen(compare_string)) == 0)

// Append the str to the output buffer.
void ARM64Decoder::Print(const char* str) {
  char cur = *str++;
  while (cur != '\0' && (buffer_pos_ < (buffer_size_ - 1))) {
    buffer_[buffer_pos_++] = cur;
    cur = *str++;
  }
  buffer_[buffer_pos_] = '\0';
}

void ARM64Decoder::PrintInt(int value) {
  buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                remaining_size_in_buffer(), "%d", value);
  buffer_[buffer_pos_] = '\0';
}

// Print the register name according to the active name converter.
void ARM64Decoder::PrintRegister(int reg, R31Type r31t) {
  ASSERT(0 <= reg);
  ASSERT(reg < kNumberOfCpuRegisters);
  if (reg == 31) {
    const char* rstr = (r31t == R31IsZR) ? "zr" : "csp";
    Print(rstr);
  } else {
    Print(cpu_reg_names[reg]);
  }
}

void ARM64Decoder::PrintVRegister(int reg) {
  ASSERT(0 <= reg);
  ASSERT(reg < kNumberOfVRegisters);
  buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                remaining_size_in_buffer(), "v%d", reg);
}

// These shift names are defined in a way to match the native disassembler
// formatting. See for example the command "objdump -d <binary file>".
static const char* shift_names[kMaxShift] = {"lsl", "lsr", "asr", "ror"};

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

// These condition names are defined in a way to match the native disassembler
// formatting. See for example the command "objdump -d <binary file>".
static const char* cond_names[kNumberOfConditions] = {
    "eq", "ne", "cs", "cc", "mi", "pl", "vs", "vc",
    "hi", "ls", "ge", "lt", "gt", "le", "",   "invalid",
};

// Print the condition guarding the instruction.
void ARM64Decoder::PrintCondition(Instr* instr) {
  if (instr->IsConditionalSelectOp()) {
    Print(cond_names[instr->SelectConditionField()]);
  } else {
    Print(cond_names[instr->ConditionField()]);
  }
}

// Print the inverse of the condition guarding the instruction.
void ARM64Decoder::PrintInvertedCondition(Instr* instr) {
  if (instr->IsConditionalSelectOp()) {
    Print(cond_names[InvertCondition(instr->SelectConditionField())]);
  } else {
    Print(cond_names[InvertCondition(instr->ConditionField())]);
  }
}

// Print the register shift operands for the instruction. Generally used for
// data processing instructions.
void ARM64Decoder::PrintShiftExtendRm(Instr* instr) {
  int rm = instr->RmField();
  Shift shift = instr->ShiftTypeField();
  int shift_amount = instr->ShiftAmountField();
  Extend extend = instr->ExtendTypeField();
  int extend_shift_amount = instr->ExtShiftAmountField();

  PrintRegister(rm, R31IsZR);

  if (instr->IsShift() && (shift == LSL) && (shift_amount == 0)) {
    // Special case for using rm only.
    return;
  }
  if (instr->IsShift()) {
    // by immediate
    if ((shift == ROR) && (shift_amount == 0)) {
      Print(" RRX");
      return;
    } else if (((shift == LSR) || (shift == ASR)) && (shift_amount == 0)) {
      shift_amount = 32;
    }
    buffer_pos_ +=
        Utils::SNPrint(current_position_in_buffer(), remaining_size_in_buffer(),
                       " %s #%d", shift_names[shift], shift_amount);
  } else {
    ASSERT(instr->IsExtend());
    // by register
    buffer_pos_ +=
        Utils::SNPrint(current_position_in_buffer(), remaining_size_in_buffer(),
                       " %s", extend_names[extend]);
    if (((instr->SFField() == 1) && (extend == UXTX)) ||
        ((instr->SFField() == 0) && (extend == UXTW))) {
      // Shift amount.
      buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                    remaining_size_in_buffer(), " %d",
                                    extend_shift_amount);
    }
  }
}

void ARM64Decoder::PrintMemOperand(Instr* instr) {
  const Register rn = instr->RnField();
  if (instr->Bit(24) == 1) {
    // rn + scaled unsigned 12-bit immediate offset.
    const uint32_t scale = instr->SzField();
    const uint32_t imm12 = instr->Imm12Field();
    const uint32_t off = imm12 << scale;
    Print("[");
    PrintRegister(rn, R31IsSP);
    if (off != 0) {
      buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                    remaining_size_in_buffer(), ", #%d", off);
    }
    Print("]");
  } else {
    switch (instr->Bits(10, 2)) {
      case 0: {
        // rn + signed 9-bit immediate, pre-index, no writeback.
        const int32_t imm9 = instr->SImm9Field();
        Print("[");
        PrintRegister(rn, R31IsSP);
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), ", #%d", imm9);
        Print("]");
        break;
      }
      case 1: {
        const int32_t imm9 = instr->SImm9Field();
        // rn + signed 9-bit immediate, post-index, writeback.
        Print("[");
        PrintRegister(rn, R31IsSP);
        Print("]");
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), ", #%d !", imm9);
        break;
      }
      case 2: {
        const Register rm = instr->RmField();
        const Extend ext = instr->ExtendTypeField();
        const int s = instr->Bit(12);
        Print("[");
        PrintRegister(rn, R31IsSP);
        Print(", ");
        PrintRegister(rm, R31IsZR);
        buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                      remaining_size_in_buffer(), " %s",
                                      extend_names[ext]);
        if (s == 1) {
          Print(" scaled");
        }
        Print("]");
        break;
      }
      case 3: {
        const int32_t imm9 = instr->SImm9Field();
        // rn + signed 9-bit immediate, pre-index, writeback.
        Print("[");
        PrintRegister(rn, R31IsSP);
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), ", #%d", imm9);
        Print("]!");
        break;
      }
      default: {
        Print("???");
      }
    }
  }
}

void ARM64Decoder::PrintPairMemOperand(Instr* instr) {
  const Register rn = instr->RnField();
  const uint32_t simm7 = instr->SImm7Field();
  const intptr_t shift =
      (instr->Bit(26) == 1) ? 2 + instr->SzField() : 2 + instr->SFField();
  const int32_t offset = simm7 << shift;
  Print("[");
  PrintRegister(rn, R31IsSP);
  switch (instr->Bits(23, 3)) {
    case 1:
      // rn + (imm7 << (2 + B31)), post-index, writeback.
      buffer_pos_ +=
          Utils::SNPrint(current_position_in_buffer(),
                         remaining_size_in_buffer(), "], #%d !", offset);
      break;
    case 2:
      // rn + (imm7 << (2 + B31)), pre-index, no writeback.
      buffer_pos_ +=
          Utils::SNPrint(current_position_in_buffer(),
                         remaining_size_in_buffer(), ", #%d]", offset);
      break;
    case 3:
      // rn + (imm7 << (2 + B31)), pre-index, writeback.
      buffer_pos_ +=
          Utils::SNPrint(current_position_in_buffer(),
                         remaining_size_in_buffer(), ", #%d]!", offset);
      break;
    default:
      Print(", ???]");
      break;
  }
}

// Handle all register based formatting in these functions to reduce the
// complexity of FormatOption.
int ARM64Decoder::FormatRegister(Instr* instr, const char* format) {
  ASSERT(format[0] == 'r');
  if (format[1] == 'n') {  // 'rn: Rn register
    int reg = instr->RnField();
    PrintRegister(reg, instr->RnMode());
    return 2;
  } else if (format[1] == 'd') {  // 'rd: Rd register
    int reg = instr->RdField();
    PrintRegister(reg, instr->RdMode());
    return 2;
  } else if (format[1] == 'm') {  // 'rm: Rm register
    int reg = instr->RmField();
    PrintRegister(reg, R31IsZR);
    return 2;
  } else if (format[1] == 't') {  // 'rt: Rt register
    if (format[2] == '2') {
      int reg = instr->Rt2Field();
      PrintRegister(reg, R31IsZR);
      return 3;
    }
    int reg = instr->RtField();
    PrintRegister(reg, R31IsZR);
    return 2;
  } else if (format[1] == 'a') {  // 'ra: Ra register
    int reg = instr->RaField();
    PrintRegister(reg, R31IsZR);
    return 2;
  } else if (format[1] == 's') {  // 'rs: Rs register
    int reg = instr->RsField();
    PrintRegister(reg, R31IsZR);
    return 2;
  }
  UNREACHABLE();
  return -1;
}

int ARM64Decoder::FormatVRegister(Instr* instr, const char* format) {
  ASSERT(format[0] == 'v');
  if (format[1] == 'd') {
    int reg = instr->VdField();
    PrintVRegister(reg);
    return 2;
  } else if (format[1] == 'n') {
    int reg = instr->VnField();
    PrintVRegister(reg);
    return 2;
  } else if (format[1] == 'm') {
    int reg = instr->VmField();
    PrintVRegister(reg);
    return 2;
  } else if (format[1] == 't') {
    if (format[2] == '2') {
      int reg = instr->Vt2Field();
      PrintVRegister(reg);
      return 3;
    }
    int reg = instr->VtField();
    PrintVRegister(reg);
    return 2;
  }
  UNREACHABLE();
  return -1;
}

// FormatOption takes a formatting string and interprets it based on
// the current instructions. The format string points to the first
// character of the option string (the option escape has already been
// consumed by the caller.)  FormatOption returns the number of
// characters that were consumed from the formatting string.
int ARM64Decoder::FormatOption(Instr* instr, const char* format) {
  switch (format[0]) {
    case 'b': {
      if (format[3] == 'i') {
        ASSERT(STRING_STARTS_WITH(format, "bitimm"));
        const uint64_t imm = instr->ImmLogical();
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), "0x%" Px64, imm);
        return 6;
      } else {
        ASSERT(STRING_STARTS_WITH(format, "bitpos"));
        int bitpos = instr->Bits(19, 5) | (instr->Bit(31) << 5);
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), "#%d", bitpos);
        return 6;
      }
    }
    case 'c': {
      if (format[1] == 's') {
        ASSERT(STRING_STARTS_WITH(format, "csz"));
        const int32_t imm5 = instr->Bits(16, 5);
        char const* typ = "??";
        if ((imm5 & 0x1) != 0) {
          typ = "b";
        } else if ((imm5 & 0x2) != 0) {
          typ = "h";
        } else if ((imm5 & 0x4) != 0) {
          typ = "s";
        } else if ((imm5 & 0x8) != 0) {
          typ = "d";
        }
        buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                      remaining_size_in_buffer(), "%s", typ);
        return 3;
      } else {
        ASSERT(STRING_STARTS_WITH(format, "cond"));
        if (format[4] == 'i') {
          ASSERT(STRING_STARTS_WITH(format, "condinverted"));
          PrintInvertedCondition(instr);
          return 12;
        } else {
          PrintCondition(instr);
          return 4;
        }
      }
    }
    case 'd': {
      int64_t off;
      if (format[4] == '2') {
        ASSERT(STRING_STARTS_WITH(format, "dest26"));
        off = static_cast<uint64_t>(instr->SImm26Field()) << 2;
      } else {
        if (format[5] == '4') {
          ASSERT(STRING_STARTS_WITH(format, "dest14"));
          off = static_cast<uint64_t>(instr->SImm14Field()) << 2;
        } else {
          ASSERT(STRING_STARTS_WITH(format, "dest19"));
          off = static_cast<uint64_t>(instr->SImm19Field()) << 2;
        }
      }
      if (FLAG_disassemble_relative) {
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), "%+" Pd64 "", off);
      } else {
        uword destination = reinterpret_cast<uword>(instr) + off;
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), "%#" Px "", destination);
      }
      return 6;
    }
    case 'f': {
      ASSERT(STRING_STARTS_WITH(format, "fsz"));
      const int sz = instr->SzField();
      char const* sz_str;
      switch (sz) {
        case 0:
          if (instr->Bit(23) == 1) {
            sz_str = "q";
          } else {
            sz_str = "b";
          }
          break;
        case 1:
          sz_str = "h";
          break;
        case 2:
          sz_str = "s";
          break;
        case 3:
          sz_str = "d";
          break;
        default:
          sz_str = "?";
          break;
      }
      buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                    remaining_size_in_buffer(), "%s", sz_str);
      return 3;
    }
    case 'h': {
      ASSERT(STRING_STARTS_WITH(format, "hw"));
      const int shift = instr->HWField() << 4;
      if (shift != 0) {
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), " lsl %d", shift);
      }
      return 2;
    }
    case 'i': {  // 'imm12, 'imm16, 'immd
      if (format[1] == 'd') {
        // Element index for a SIMD copy instruction.
        ASSERT(STRING_STARTS_WITH(format, "idx"));
        const int32_t imm4 = instr->Bits(11, 4);
        const int32_t imm5 = instr->Bits(16, 5);
        int32_t shift = 0;
        int32_t imm = -1;
        if (format[3] == '4') {
          imm = imm4;
        } else if (format[3] == '5') {
          imm = imm5;
          shift = 1;
        }
        int32_t idx = -1;
        if ((imm5 & 0x1) != 0) {
          idx = imm >> shift;
        } else if ((imm5 & 0x2) != 0) {
          idx = imm >> (shift + 1);
        } else if ((imm5 & 0x4) != 0) {
          idx = imm >> (shift + 2);
        } else if ((imm5 & 0x8) != 0) {
          idx = imm >> (shift + 3);
        }
        buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                      remaining_size_in_buffer(), "[%d]", idx);
        return 4;
      } else if (format[3] == '1') {
        uint64_t imm;
        int ret = 5;
        if (format[4] == '2') {
          ASSERT(STRING_STARTS_WITH(format, "imm12"));
          imm = instr->Imm12Field();
          if (format[5] == 's') {
            // shifted immediate.
            if (instr->Imm12ShiftField() == 1) {
              imm = imm << 12;
            } else if ((instr->Imm12ShiftField() & 0x2) != 0) {
              Print("Unknown Shift");
            }
            ret = 6;
          }
        } else {
          ASSERT(STRING_STARTS_WITH(format, "imm16"));
          imm = instr->Imm16Field();
        }
        buffer_pos_ +=
            Utils::SNPrint(current_position_in_buffer(),
                           remaining_size_in_buffer(), "#0x%" Px64, imm);
        return ret;
      } else {
        ASSERT(STRING_STARTS_WITH(format, "imm"));
        if (format[3] == 'd') {
          double dimm = bit_cast<double, int64_t>(
              Instr::VFPExpandImm(instr->Imm8Field()));
          buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                        remaining_size_in_buffer(), "%f", dimm);
          return 4;
        } else if (format[3] == 'r') {
          int immr = instr->ImmRField();
          buffer_pos_ +=
              Utils::SNPrint(current_position_in_buffer(),
                             remaining_size_in_buffer(), "#%d", immr);
          return 4;
        } else {
          ASSERT(format[3] == 's');
          int imms = instr->ImmSField();
          buffer_pos_ +=
              Utils::SNPrint(current_position_in_buffer(),
                             remaining_size_in_buffer(), "#%d", imms);
          return 4;
        }
      }
    }
    case 'm': {
      ASSERT(STRING_STARTS_WITH(format, "memop"));
      PrintMemOperand(instr);
      return 5;
    }
    case 'o': {
      ASSERT(STRING_STARTS_WITH(format, "opc"));
      if (instr->Bit(26) == 0) {
        if (instr->Bit(31) == 0) {
          if (instr->Bit(30) == 1) {
            Print("sw");
          } else {
            Print("w");
          }
        } else {
          // 64-bit width is most commonly used, no need to print "x".
        }
      } else {
        switch (instr->Bits(30, 2)) {
          case 0:
            Print("s");
            break;
          case 1:
            Print("d");
            break;
          case 2:
            Print("q");
            break;
          case 3:
            Print("?");
            break;
        }
      }
      return 3;
    }
    case 'p': {
      if (format[1] == 'c') {
        int64_t off;
        if (format[2] == 'a') {
          ASSERT(STRING_STARTS_WITH(format, "pcadr"));
          const uint64_t immhi = instr->SImm19Field();
          const uint64_t immlo = instr->Bits(29, 2);
          off = (immhi << 2) | immlo;
        } else {
          ASSERT(STRING_STARTS_WITH(format, "pcldr"));
          off = instr->SImm19Field() << 2;
        }
        if (FLAG_disassemble_relative) {
          buffer_pos_ +=
              Utils::SNPrint(current_position_in_buffer(),
                             remaining_size_in_buffer(), "%+" Pd64 "", off);
        } else {
          const uint64_t pc = reinterpret_cast<int64_t>(instr);
          const uint64_t dest = pc + off;
          buffer_pos_ +=
              Utils::SNPrint(current_position_in_buffer(),
                             remaining_size_in_buffer(), "0x%" Px64, dest);
        }
        return 5;
      } else {
        ASSERT(STRING_STARTS_WITH(format, "pmemop"));
        PrintPairMemOperand(instr);
        return 6;
      }
    }
    case 'r': {
      return FormatRegister(instr, format);
    }
    case 'v': {
      if (format[1] == 's') {
        ASSERT(STRING_STARTS_WITH(format, "vsz"));
        char const* sz_str = nullptr;
        if (instr->Bits(14, 2) == 3) {
          switch (instr->Bit(22)) {
            case 0:
              sz_str = "s";
              break;
            case 1:
              sz_str = "d";
              break;
            default:
              UNREACHABLE();
              break;
          }
        } else {
          switch (instr->Bit(22)) {
            case 0:
              sz_str = "w";
              break;
            case 1:
              sz_str = "x";
              break;
            default:
              UNREACHABLE();
              break;
          }
        }
        buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                      remaining_size_in_buffer(), "%s", sz_str);
        return 3;
      } else {
        return FormatVRegister(instr, format);
      }
    }
    case 's': {  // 's: S flag.
      if (format[1] == 'h') {
        ASSERT(STRING_STARTS_WITH(format, "shift_op"));
        PrintShiftExtendRm(instr);
        return 8;
      } else if (format[1] == 'f') {
        ASSERT(STRING_STARTS_WITH(format, "sf"));
        if (instr->SFField() == 1) {
          // 64-bit width is most commonly used, no need to print "x".
        } else {
          Print("w");
        }
        return 2;
      } else if (format[1] == 'z') {
        ASSERT(STRING_STARTS_WITH(format, "sz"));
        const int sz = instr->SzField();
        char const* sz_str;
        switch (sz) {
          case 0:
            sz_str = "b";
            break;
          case 1:
            sz_str = "h";
            break;
          case 2:
            sz_str = "w";
            break;
          case 3:
            // 64-bit width is most commonly used, no need to print "x".
            sz_str = "";
            break;
          default:
            sz_str = "?";
            break;
        }
        buffer_pos_ += Utils::SNPrint(current_position_in_buffer(),
                                      remaining_size_in_buffer(), "%s", sz_str);
        return 2;
      } else if (format[1] == ' ') {
        if (instr->HasS()) {
          Print("s");
        }
        return 1;
      } else {
        UNREACHABLE();
      }
    }
    default: {
      UNREACHABLE();
      break;
    }
  }
  UNREACHABLE();
  return -1;
}

// Format takes a formatting string for a whole instruction and prints it into
// the output buffer. All escaped options are handed to FormatOption to be
// parsed further.
void ARM64Decoder::Format(Instr* instr, const char* format) {
  char cur = *format++;
  while ((cur != 0) && (buffer_pos_ < (buffer_size_ - 1))) {
    if (cur == '\'') {  // Single quote is used as the formatting escape.
      format += FormatOption(instr, format);
    } else {
      buffer_[buffer_pos_++] = cur;
    }
    cur = *format++;
  }
  buffer_[buffer_pos_] = '\0';
}

// For currently unimplemented decodings the disassembler calls Unknown(instr)
// which will just print "unknown" of the instruction bits.
void ARM64Decoder::Unknown(Instr* instr) {
  Format(instr, "unknown");
}

void ARM64Decoder::DecodeMoveWide(Instr* instr) {
  switch (instr->Bits(29, 2)) {
    case 0:
      Format(instr, "movn'sf 'rd, 'imm16'hw");
      break;
    case 2:
      Format(instr, "movz'sf 'rd, 'imm16'hw");
      break;
    case 3:
      Format(instr, "movk'sf 'rd, 'imm16'hw");
      break;
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodeLoadStoreReg(Instr* instr) {
  if (instr->Bit(26) == 1) {
    // SIMD or FP src/dst.
    if (instr->Bit(22) == 1) {
      Format(instr, "fldr'fsz 'vt, 'memop");
    } else {
      Format(instr, "fstr'fsz 'vt, 'memop");
    }
  } else {
    // Integer src/dst.
    if (instr->Bits(22, 2) == 0) {
      Format(instr, "str'sz 'rt, 'memop");
    } else if (instr->Bits(23, 1) == 1) {
      Format(instr, "ldrs'sz 'rt, 'memop");
    } else {
      Format(instr, "ldr'sz 'rt, 'memop");
    }
  }
}

void ARM64Decoder::DecodeLoadStoreRegPair(Instr* instr) {
  if (instr->Bit(26) == 1) {
    // SIMD or FP src/dst.
    if (instr->Bit(22) == 1) {
      Format(instr, "fldp'opc 'vt, 'vt2, 'pmemop");
    } else {
      Format(instr, "fstp'opc 'vt, 'vt2, 'pmemop");
    }
  } else {
    // Integer src/dst.
    if (instr->Bit(22) == 1) {
      Format(instr, "ldp'opc 'rt, 'rt2, 'pmemop");
    } else {
      Format(instr, "stp'opc 'rt, 'rt2, 'pmemop");
    }
  }
}

void ARM64Decoder::DecodeLoadRegLiteral(Instr* instr) {
  if ((instr->Bit(31) != 0) || (instr->Bit(29) != 0) ||
      (instr->Bits(24, 3) != 0)) {
    Unknown(instr);
  }
  if (instr->Bit(30) != 0) {
    Format(instr, "ldrx 'rt, 'pcldr");
  } else {
    Format(instr, "ldrw 'rt, 'pcldr");
  }
}

void ARM64Decoder::DecodeLoadStoreExclusive(Instr* instr) {
  if (instr->Bit(31) != 1 || instr->Bit(21) != 0 ||
      instr->Bit(23) != instr->Bit(15)) {
    Unknown(instr);
  }

  const bool is_load = instr->Bit(22) == 1;
  const bool is_exclusive = instr->Bit(23) == 0;
  const bool is_ordered = instr->Bit(15) == 1;
  if (is_load) {
    const bool is_load_acquire = !is_exclusive && is_ordered;
    if (is_load_acquire) {
      Format(instr, "ldar'sz 'rt, ['rn]");
    } else {
      Format(instr, "ldxr'sz 'rt, ['rn]");
    }
  } else {
    const bool is_store_release = !is_exclusive && is_ordered;
    if (is_store_release) {
      Format(instr, "stlr'sz 'rt, ['rn]");
    } else {
      Format(instr, "stxr'sz 'rs, 'rt, ['rn]");
    }
  }
}

void ARM64Decoder::DecodeAtomicMemory(Instr* instr) {
  switch (instr->Bits(12, 3)) {
    case 1:
      Format(instr, "ldclr'sz 'rs, 'rt, ['rn]");
      break;
    case 3:
      Format(instr, "ldset'sz 'rs, 'rt, ['rn]");
      break;
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodeAddSubImm(Instr* instr) {
  switch (instr->Bit(30)) {
    case 0: {
      if ((instr->RdField() == R31) && (instr->SField() == 1)) {
        Format(instr, "cmn'sf 'rn, 'imm12s");
      } else {
        if (((instr->RdField() == R31) || (instr->RnField() == R31)) &&
            (instr->Imm12Field() == 0) && (instr->Bit(29) == 0)) {
          Format(instr, "mov'sf 'rd, 'rn");
        } else {
          Format(instr, "add'sf's 'rd, 'rn, 'imm12s");
        }
      }
      break;
    }
    case 1: {
      if ((instr->RdField() == R31) && (instr->SField() == 1)) {
        Format(instr, "cmp'sf 'rn, 'imm12s");
      } else {
        Format(instr, "sub'sf's 'rd, 'rn, 'imm12s");
      }
      break;
    }
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodeBitfield(Instr* instr) {
  int reg_size = instr->SFField() == 0 ? 32 : 64;
  int op = instr->Bits(29, 2);
  int r_imm = instr->ImmRField();
  int s_imm = instr->ImmSField();
  switch (op) {
    case 0:
      if (r_imm == 0) {
        if (s_imm == 7) {
          Format(instr, "sxtb 'rd, 'rn");
          break;
        } else if (s_imm == 15) {
          Format(instr, "sxth 'rd, 'rn");
          break;
        } else if (s_imm == 31) {
          Format(instr, "sxtw 'rd, 'rn");
          break;
        }
      }
      if (s_imm == (reg_size - 1)) {
        Format(instr, "asr'sf 'rd, 'rn, 'immr");
        break;
      }
      Format(instr, "sbfm'sf 'rd, 'rn, 'immr, 'imms");
      break;
    case 1:
      Format(instr, "bfm'sf 'rd, 'rn, 'immr, 'imms");
      break;
    case 2:
      if (r_imm == 0) {
        if (s_imm == 7) {
          Format(instr, "uxtb 'rd, 'rn");
          break;
        } else if (s_imm == 15) {
          Format(instr, "uxth 'rd, 'rn");
          break;
        }
      }
      if ((s_imm != (reg_size - 1)) && ((s_imm + 1) == r_imm)) {
        int shift = reg_size - s_imm - 1;
        Format(instr, "lsl'sf 'rd, 'rn, #");
        PrintInt(shift);
        break;
      } else if (s_imm == (reg_size - 1)) {
        Format(instr, "lsr'sf 'rd, 'rn, 'immr");
        break;
      }
      Format(instr, "ubfm'sf 'rd, 'rn, 'immr, 'imms");
      break;
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodeLogicalImm(Instr* instr) {
  int op = instr->Bits(29, 2);
  switch (op) {
    case 0:
      Format(instr, "and'sf 'rd, 'rn, 'bitimm");
      break;
    case 1: {
      if (instr->RnField() == R31) {
        Format(instr, "mov'sf 'rd, 'bitimm");
      } else {
        Format(instr, "orr'sf 'rd, 'rn, 'bitimm");
      }
      break;
    }
    case 2:
      Format(instr, "eor'sf 'rd, 'rn, 'bitimm");
      break;
    case 3:
      if (instr->RdField() == R31) {
        Format(instr, "tst'sf 'rn, 'bitimm");
      } else {
        Format(instr, "and'sfs 'rd, 'rn, 'bitimm");
      }
      break;
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodePCRel(Instr* instr) {
  const int op = instr->Bit(31);
  if (op == 0) {
    Format(instr, "adr 'rd, 'pcadr");
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeDPImmediate(Instr* instr) {
  if (instr->IsMoveWideOp()) {
    DecodeMoveWide(instr);
  } else if (instr->IsAddSubImmOp()) {
    DecodeAddSubImm(instr);
  } else if (instr->IsBitfieldOp()) {
    DecodeBitfield(instr);
  } else if (instr->IsLogicalImmOp()) {
    DecodeLogicalImm(instr);
  } else if (instr->IsPCRelOp()) {
    DecodePCRel(instr);
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeExceptionGen(Instr* instr) {
  if ((instr->Bits(0, 2) == 1) && (instr->Bits(2, 3) == 0) &&
      (instr->Bits(21, 3) == 0)) {
    Format(instr, "svc 'imm16");
  } else if ((instr->Bits(0, 2) == 0) && (instr->Bits(2, 3) == 0) &&
             (instr->Bits(21, 3) == 1)) {
    Format(instr, "brk 'imm16");
  } else if ((instr->Bits(0, 2) == 0) && (instr->Bits(2, 3) == 0) &&
             (instr->Bits(21, 3) == 2)) {
    Format(instr, "hlt 'imm16");
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeSystem(Instr* instr) {
  if (instr->InstructionBits() == CLREX) {
    Format(instr, "clrex");
    return;
  }

  if (instr->InstructionBits() == kDMB_ISH) {
    Format(instr, "dmb ish");
    return;
  }
  if (instr->InstructionBits() == kDMB_ISHST) {
    Format(instr, "dmb ishst");
    return;
  }

  if ((instr->Bits(0, 8) == 0x1f) && (instr->Bits(12, 4) == 2) &&
      (instr->Bits(16, 3) == 3) && (instr->Bits(19, 2) == 0) &&
      (instr->Bit(21) == 0)) {
    if (instr->Bits(8, 4) == 0) {
      Format(instr, "nop");
    } else {
      Unknown(instr);
    }
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeUnconditionalBranchReg(Instr* instr) {
  if ((instr->Bits(0, 5) == 0) && (instr->Bits(10, 5) == 0) &&
      (instr->Bits(16, 5) == 0x1f)) {
    switch (instr->Bits(21, 4)) {
      case 0:
        Format(instr, "br 'rn");
        break;
      case 1:
        Format(instr, "blr 'rn");
        break;
      case 2:
        if (instr->RnField() == LINK_REGISTER) {
          Format(instr, "ret");
        } else {
          Format(instr, "ret 'rn");
        }
        break;
      default:
        Unknown(instr);
        break;
    }
  }
}

void ARM64Decoder::DecodeCompareAndBranch(Instr* instr) {
  const int op = instr->Bit(24);
  if (op == 0) {
    Format(instr, "cbz'sf 'rt, 'dest19");
  } else {
    Format(instr, "cbnz'sf 'rt, 'dest19");
  }
}

void ARM64Decoder::DecodeConditionalBranch(Instr* instr) {
  if ((instr->Bit(24) != 0) || (instr->Bit(4) != 0)) {
    Unknown(instr);
    return;
  }
  Format(instr, "b'cond 'dest19");
}

void ARM64Decoder::DecodeTestAndBranch(Instr* instr) {
  const int op = instr->Bit(24);
  if (op == 0) {
    Format(instr, "tbz'sf 'rt, 'bitpos, 'dest14");
  } else {
    Format(instr, "tbnz'sf 'rt, 'bitpos, 'dest14");
  }
}

void ARM64Decoder::DecodeUnconditionalBranch(Instr* instr) {
  const int op = instr->Bit(31);
  if (op == 0) {
    Format(instr, "b 'dest26");
  } else {
    Format(instr, "bl 'dest26");
  }
}

void ARM64Decoder::DecodeCompareBranch(Instr* instr) {
  if (instr->IsExceptionGenOp()) {
    DecodeExceptionGen(instr);
  } else if (instr->IsSystemOp()) {
    DecodeSystem(instr);
  } else if (instr->IsUnconditionalBranchRegOp()) {
    DecodeUnconditionalBranchReg(instr);
  } else if (instr->IsCompareAndBranchOp()) {
    DecodeCompareAndBranch(instr);
  } else if (instr->IsConditionalBranchOp()) {
    DecodeConditionalBranch(instr);
  } else if (instr->IsTestAndBranchOp()) {
    DecodeTestAndBranch(instr);
  } else if (instr->IsUnconditionalBranchOp()) {
    DecodeUnconditionalBranch(instr);
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeLoadStore(Instr* instr) {
  if (instr->IsAtomicMemoryOp()) {
    DecodeAtomicMemory(instr);
  } else if (instr->IsLoadStoreRegOp()) {
    DecodeLoadStoreReg(instr);
  } else if (instr->IsLoadStoreRegPairOp()) {
    DecodeLoadStoreRegPair(instr);
  } else if (instr->IsLoadRegLiteralOp()) {
    DecodeLoadRegLiteral(instr);
  } else if (instr->IsLoadStoreExclusiveOp()) {
    DecodeLoadStoreExclusive(instr);
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeAddSubShiftExt(Instr* instr) {
  switch (instr->Bit(30)) {
    case 0: {
      if ((instr->RdField() == R31) && (instr->SField() == 1)) {
        Format(instr, "cmn'sf 'rn, 'shift_op");
      } else {
        Format(instr, "add'sf's 'rd, 'rn, 'shift_op");
      }
      break;
    }
    case 1: {
      if ((instr->RdField() == R31) && (instr->SField() == 1)) {
        Format(instr, "cmp'sf 'rn, 'shift_op");
      } else {
        if (instr->RnField() == R31) {
          Format(instr, "neg'sf's 'rd, 'shift_op");
        } else {
          Format(instr, "sub'sf's 'rd, 'rn, 'shift_op");
        }
      }
      break;
    }
    default:
      UNREACHABLE();
      break;
  }
}

void ARM64Decoder::DecodeAddSubWithCarry(Instr* instr) {
  switch (instr->Bit(30)) {
    case 0: {
      Format(instr, "adc'sf's 'rd, 'rn, 'rm");
      break;
    }
    case 1: {
      Format(instr, "sbc'sf's 'rd, 'rn, 'rm");
      break;
    }
    default:
      UNREACHABLE();
      break;
  }
}

void ARM64Decoder::DecodeLogicalShift(Instr* instr) {
  const int op = (instr->Bits(29, 2) << 1) | instr->Bit(21);
  switch (op) {
    case 0:
      Format(instr, "and'sf 'rd, 'rn, 'shift_op");
      break;
    case 1:
      Format(instr, "bic'sf 'rd, 'rn, 'shift_op");
      break;
    case 2: {
      if ((instr->RnField() == R31) && (instr->IsShift()) &&
          (instr->ShiftTypeField() == LSL)) {
        if (instr->ShiftAmountField() == 0) {
          Format(instr, "mov'sf 'rd, 'rm");
        } else {
          Format(instr, "lsl'sf 'rd, 'rm, 'imms");
        }
      } else {
        Format(instr, "orr'sf 'rd, 'rn, 'shift_op");
      }
      break;
    }
    case 3:
      Format(instr, "orn'sf 'rd, 'rn, 'shift_op");
      break;
    case 4:
      Format(instr, "eor'sf 'rd, 'rn, 'shift_op");
      break;
    case 5:
      Format(instr, "eon'sf 'rd, 'rn, 'shift_op");
      break;
    case 6:
      if (instr->RdField() == R31) {
        Format(instr, "tst'sf 'rn, 'shift_op");
      } else {
        Format(instr, "and'sfs 'rd, 'rn, 'shift_op");
      }
      break;
    case 7:
      Format(instr, "bic'sfs 'rd, 'rn, 'shift_op");
      break;
    default:
      UNREACHABLE();
      break;
  }
}

void ARM64Decoder::DecodeMiscDP1Source(Instr* instr) {
  if (instr->Bit(29) != 0) {
    Unknown(instr);
  }

  const int op = instr->Bits(10, 10);
  switch (op) {
    case 0:
      Format(instr, "rbit'sf 'rd, 'rn");
      break;
    case 4:
      Format(instr, "clz'sf 'rd, 'rn");
      break;
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodeMiscDP2Source(Instr* instr) {
  if (instr->Bit(29) != 0) {
    Unknown(instr);
  }

  const int op = instr->Bits(10, 5);
  switch (op) {
    case 2:
      Format(instr, "udiv'sf 'rd, 'rn, 'rm");
      break;
    case 3:
      Format(instr, "sdiv'sf 'rd, 'rn, 'rm");
      break;
    case 8:
      Format(instr, "lsl'sf 'rd, 'rn, 'rm");
      break;
    case 9:
      Format(instr, "lsr'sf 'rd, 'rn, 'rm");
      break;
    case 10:
      Format(instr, "asr'sf 'rd, 'rn, 'rm");
      break;
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodeMiscDP3Source(Instr* instr) {
  bool zero_operand = instr->RaField() == R31;
  int32_t mask = B31 | B30 | B29 | B23 | B22 | B21 | B15 | MiscDP3SourceMask;
  int32_t bits = instr->InstructionBits() & mask;

  if (bits == MADD || bits == MADDW) {
    if (zero_operand) {
      Format(instr, "mul'sf 'rd, 'rn, 'rm");
    } else {
      Format(instr, "madd'sf 'rd, 'rn, 'rm, 'ra");
    }
  } else if (bits == MSUB || bits == MSUBW) {
    if (zero_operand) {
      Format(instr, "mneg'sf 'rd, 'rn, 'rm");
    } else {
      Format(instr, "msub'sf 'rd, 'rn, 'rm, 'ra");
    }
  } else if (bits == SMULH) {
    Format(instr, "smulh 'rd, 'rn, 'rm");
  } else if (bits == UMULH) {
    Format(instr, "umulh 'rd, 'rn, 'rm");
  } else if (bits == UMADDL) {
    if (zero_operand) {
      Format(instr, "umull 'rd, 'rn, 'rm");
    } else {
      Format(instr, "umaddl 'rd, 'rn, 'rm, 'ra");
    }
  } else if (bits == SMADDL) {
    if (zero_operand) {
      Format(instr, "smull 'rd, 'rn, 'rm");
    } else {
      Format(instr, "smaddl 'rd, 'rn, 'rm, 'ra");
    }
  } else if (bits == SMSUBL) {
    if (zero_operand) {
      Format(instr, "smnegl 'rd, 'rn, 'rm");
    } else {
      Format(instr, "smsubl 'rd, 'rn, 'rm, 'ra");
    }
  } else if (bits == UMSUBL) {
    if (zero_operand) {
      Format(instr, "umnegl 'rd, 'rn, 'rm");
    } else {
      Format(instr, "umsubl 'rd, 'rn, 'rm, 'ra");
    }
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeConditionalSelect(Instr* instr) {
  int cond = instr->SelectConditionField();
  bool non_select =
      (instr->RnField() == instr->RmField()) && ((cond & 0xe) != 0xe);
  if ((instr->Bits(29, 2) == 0) && (instr->Bits(10, 2) == 0)) {
    Format(instr, "csel'sf 'rd, 'rn, 'rm, 'cond");
  } else if ((instr->Bits(29, 2) == 0) && (instr->Bits(10, 2) == 1)) {
    if (non_select) {
      if (instr->RnField() == 31 && instr->RmField() == 31) {
        Format(instr, "cset'sf 'rd, 'condinverted");
      } else {
        Format(instr, "cinc'sf 'rd, 'rn, 'condinverted");
      }
    } else {
      Format(instr, "csinc'sf 'rd, 'rn, 'rm, 'cond");
    }
  } else if ((instr->Bits(29, 2) == 2) && (instr->Bits(10, 2) == 0)) {
    if (non_select) {
      if (instr->RnField() == 31 && instr->RmField() == 31) {
        Format(instr, "csetm'sf 'rd, 'condinverted");
      } else {
        Format(instr, "cinv'sf 'rd, 'rn, 'condinverted");
      }
    } else {
      Format(instr, "csinv'sf 'rd, 'rn, 'rm, 'cond");
    }
  } else if ((instr->Bits(29, 2) == 2) && (instr->Bits(10, 2) == 1)) {
    if (non_select) {
      Format(instr, "cneg'sf 'rd, 'rn, 'condinverted");
    } else {
      Format(instr, "csneg'sf 'rd, 'rn, 'rm, 'cond");
    }
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeDPRegister(Instr* instr) {
  if (instr->IsAddSubShiftExtOp()) {
    DecodeAddSubShiftExt(instr);
  } else if (instr->IsAddSubWithCarryOp()) {
    DecodeAddSubWithCarry(instr);
  } else if (instr->IsLogicalShiftOp()) {
    DecodeLogicalShift(instr);
  } else if (instr->IsMiscDP1SourceOp()) {
    DecodeMiscDP1Source(instr);
  } else if (instr->IsMiscDP2SourceOp()) {
    DecodeMiscDP2Source(instr);
  } else if (instr->IsMiscDP3SourceOp()) {
    DecodeMiscDP3Source(instr);
  } else if (instr->IsConditionalSelectOp()) {
    DecodeConditionalSelect(instr);
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeSIMDCopy(Instr* instr) {
  const int32_t Q = instr->Bit(30);
  const int32_t op = instr->Bit(29);
  const int32_t imm4 = instr->Bits(11, 4);

  if ((op == 0) && (imm4 == 7)) {
    if (Q == 0) {
      Format(instr, "vmovrs 'rd, 'vn'idx5");
    } else {
      Format(instr, "vmovrd 'rd, 'vn'idx5");
    }
  } else if ((Q == 1) && (op == 0) && (imm4 == 0)) {
    Format(instr, "vdup'csz 'vd, 'vn'idx5");
  } else if ((Q == 1) && (op == 0) && (imm4 == 3)) {
    Format(instr, "vins'csz 'vd'idx5, 'rn");
  } else if ((Q == 1) && (op == 0) && (imm4 == 1)) {
    Format(instr, "vdup'csz 'vd, 'rn");
  } else if ((Q == 1) && (op == 1)) {
    Format(instr, "vins'csz 'vd'idx5, 'vn'idx4");
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeSIMDThreeSame(Instr* instr) {
  const int32_t Q = instr->Bit(30);
  const int32_t U = instr->Bit(29);
  const int32_t opcode = instr->Bits(11, 5);

  if (Q == 0) {
    Unknown(instr);
    return;
  }

  if ((U == 0) && (opcode == 0x3)) {
    if (instr->Bit(23) == 0) {
      Format(instr, "vand 'vd, 'vn, 'vm");
    } else {
      Format(instr, "vorr 'vd, 'vn, 'vm");
    }
  } else if ((U == 1) && (opcode == 0x3)) {
    Format(instr, "veor 'vd, 'vn, 'vm");
  } else if ((U == 0) && (opcode == 0x10)) {
    Format(instr, "vadd'vsz 'vd, 'vn, 'vm");
  } else if ((U == 1) && (opcode == 0x10)) {
    Format(instr, "vsub'vsz 'vd, 'vn, 'vm");
  } else if ((U == 0) && (opcode == 0x1a)) {
    if (instr->Bit(23) == 0) {
      Format(instr, "vadd'vsz 'vd, 'vn, 'vm");
    } else {
      Format(instr, "vsub'vsz 'vd, 'vn, 'vm");
    }
  } else if ((U == 1) && (opcode == 0x1b)) {
    Format(instr, "vmul'vsz 'vd, 'vn, 'vm");
  } else if ((U == 1) && (opcode == 0x1f)) {
    Format(instr, "vdiv'vsz 'vd, 'vn, 'vm");
  } else if ((U == 0) && (opcode == 0x1c)) {
    Format(instr, "vceq'vsz 'vd, 'vn, 'vm");
  } else if ((U == 1) && (opcode == 0x1c)) {
    if (instr->Bit(23) == 1) {
      Format(instr, "vcgt'vsz 'vd, 'vn, 'vm");
    } else {
      Format(instr, "vcge'vsz 'vd, 'vn, 'vm");
    }
  } else if ((U == 0) && (opcode == 0x1e)) {
    if (instr->Bit(23) == 1) {
      Format(instr, "vmin'vsz 'vd, 'vn, 'vm");
    } else {
      Format(instr, "vmax'vsz 'vd, 'vn, 'vm");
    }
  } else if ((U == 0) && (opcode == 0x1f)) {
    if (instr->Bit(23) == 1) {
      Format(instr, "vrsqrt'vsz 'vd, 'vn, 'vm");
    } else {
      Format(instr, "vrecps'vsz 'vd, 'vn, 'vm");
    }
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeSIMDTwoReg(Instr* instr) {
  const int32_t Q = instr->Bit(30);
  const int32_t U = instr->Bit(29);
  const int32_t op = instr->Bits(12, 5);
  const int32_t sz = instr->Bits(22, 2);

  if (Q == 0) {
    Unknown(instr);
    return;
  }

  if ((U == 1) && (op == 0x5)) {
    Format(instr, "vnot 'vd, 'vn");
  } else if ((U == 0) && (op == 0xf)) {
    if (sz == 2) {
      Format(instr, "vabss 'vd, 'vn");
    } else if (sz == 3) {
      Format(instr, "vabsd 'vd, 'vn");
    } else {
      Unknown(instr);
    }
  } else if ((U == 1) && (op == 0xf)) {
    if (sz == 2) {
      Format(instr, "vnegs 'vd, 'vn");
    } else if (sz == 3) {
      Format(instr, "vnegd 'vd, 'vn");
    } else {
      Unknown(instr);
    }
  } else if ((U == 1) && (op == 0x1f)) {
    if (sz == 2) {
      Format(instr, "vsqrts 'vd, 'vn");
    } else if (sz == 3) {
      Format(instr, "vsqrtd 'vd, 'vn");
    } else {
      Unknown(instr);
    }
  } else if ((U == 0) && (op == 0x1d)) {
    if (sz != 2) {
      Unknown(instr);
      return;
    }
    Format(instr, "vrecpes 'vd, 'vn");
  } else if ((U == 1) && (op == 0x1d)) {
    if (sz != 2) {
      Unknown(instr);
      return;
    }
    Format(instr, "vrsqrtes 'vd, 'vn");
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeDPSimd1(Instr* instr) {
  if (instr->IsSIMDCopyOp()) {
    DecodeSIMDCopy(instr);
  } else if (instr->IsSIMDThreeSameOp()) {
    DecodeSIMDThreeSame(instr);
  } else if (instr->IsSIMDTwoRegOp()) {
    DecodeSIMDTwoReg(instr);
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeFPImm(Instr* instr) {
  if ((instr->Bit(31) != 0) || (instr->Bit(29) != 0) || (instr->Bit(23) != 0) ||
      (instr->Bits(5, 5) != 0)) {
    Unknown(instr);
    return;
  }
  if (instr->Bit(22) == 1) {
    // Double.
    Format(instr, "fmovd 'vd, 'immd");
  } else {
    // Single.
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeFPIntCvt(Instr* instr) {
  if ((instr->Bit(29) != 0)) {
    Unknown(instr);
    return;
  }

  if ((instr->SFField() == 0) && (instr->Bits(22, 2) == 0)) {
    if (instr->Bits(16, 5) == 6) {
      Format(instr, "fmovrs'sf 'rd, 'vn");
    } else if (instr->Bits(16, 5) == 7) {
      Format(instr, "fmovsr'sf 'vd, 'rn");
    } else {
      Unknown(instr);
    }
  } else if (instr->Bits(22, 2) == 1) {
    if (instr->Bits(16, 5) == 2) {
      Format(instr, "scvtfd'sf 'vd, 'rn");
    } else if (instr->Bits(16, 5) == 6) {
      Format(instr, "fmovrd'sf 'rd, 'vn");
    } else if (instr->Bits(16, 5) == 7) {
      Format(instr, "fmovdr'sf 'vd, 'rn");
    } else if (instr->Bits(16, 5) == 8) {
      Format(instr, "fcvtps'sf 'rd, 'vn");
    } else if (instr->Bits(16, 5) == 16) {
      Format(instr, "fcvtms'sf 'rd, 'vn");
    } else if (instr->Bits(16, 5) == 24) {
      Format(instr, "fcvtzs'sf 'rd, 'vn");
    } else {
      Unknown(instr);
    }
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeFPOneSource(Instr* instr) {
  const int opc = instr->Bits(15, 6);

  if ((opc != 5) && (instr->Bit(22) != 1)) {
    // Source is interpreted as single-precision only if we're doing a
    // conversion from single -> double.
    Unknown(instr);
    return;
  }

  switch (opc) {
    case 0:
      Format(instr, "fmovdd 'vd, 'vn");
      break;
    case 1:
      Format(instr, "fabsd 'vd, 'vn");
      break;
    case 2:
      Format(instr, "fnegd 'vd, 'vn");
      break;
    case 3:
      Format(instr, "fsqrtd 'vd, 'vn");
      break;
    case 4:
      Format(instr, "fcvtsd 'vd, 'vn");
      break;
    case 5:
      Format(instr, "fcvtds 'vd, 'vn");
      break;
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodeFPTwoSource(Instr* instr) {
  if (instr->Bits(22, 2) != 1) {
    Unknown(instr);
    return;
  }
  const int opc = instr->Bits(12, 4);

  switch (opc) {
    case 0:
      Format(instr, "fmuld 'vd, 'vn, 'vm");
      break;
    case 1:
      Format(instr, "fdivd 'vd, 'vn, 'vm");
      break;
    case 2:
      Format(instr, "faddd 'vd, 'vn, 'vm");
      break;
    case 3:
      Format(instr, "fsubd 'vd, 'vn, 'vm");
      break;
    default:
      Unknown(instr);
      break;
  }
}

void ARM64Decoder::DecodeFPCompare(Instr* instr) {
  if ((instr->Bit(22) == 1) && (instr->Bits(3, 2) == 0)) {
    Format(instr, "fcmpd 'vn, 'vm");
  } else if ((instr->Bit(22) == 1) && (instr->Bits(3, 2) == 1)) {
    if (instr->VmField() == V0) {
      Format(instr, "fcmpd 'vn, #0.0");
    } else {
      Unknown(instr);
    }
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeFP(Instr* instr) {
  if (instr->IsFPImmOp()) {
    DecodeFPImm(instr);
  } else if (instr->IsFPIntCvtOp()) {
    DecodeFPIntCvt(instr);
  } else if (instr->IsFPOneSourceOp()) {
    DecodeFPOneSource(instr);
  } else if (instr->IsFPTwoSourceOp()) {
    DecodeFPTwoSource(instr);
  } else if (instr->IsFPCompareOp()) {
    DecodeFPCompare(instr);
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::DecodeDPSimd2(Instr* instr) {
  if (instr->IsFPOp()) {
    DecodeFP(instr);
  } else {
    Unknown(instr);
  }
}

void ARM64Decoder::InstructionDecode(uword pc) {
  Instr* instr = Instr::At(pc);

  if (instr->IsDPImmediateOp()) {
    DecodeDPImmediate(instr);
  } else if (instr->IsCompareBranchOp()) {
    DecodeCompareBranch(instr);
  } else if (instr->IsLoadStoreOp()) {
    DecodeLoadStore(instr);
  } else if (instr->IsDPRegisterOp()) {
    DecodeDPRegister(instr);
  } else if (instr->IsDPSimd1Op()) {
    DecodeDPSimd1(instr);
  } else if (instr->IsDPSimd2Op()) {
    DecodeDPSimd2(instr);
  } else {
    Unknown(instr);
  }
}

void Disassembler::DecodeInstruction(char* hex_buffer,
                                     intptr_t hex_size,
                                     char* human_buffer,
                                     intptr_t human_size,
                                     int* out_instr_size,
                                     const Code& code,
                                     Object** object,
                                     uword pc) {
  ARM64Decoder decoder(human_buffer, human_size);
  decoder.InstructionDecode(pc);
  int32_t instruction_bits = Instr::At(pc)->InstructionBits();
  Utils::SNPrint(hex_buffer, hex_size, "%08x", instruction_bits);
  if (out_instr_size != nullptr) {
    *out_instr_size = Instr::kInstrSize;
  }

  *object = nullptr;
  if (!code.IsNull()) {
    *object = &Object::Handle();
    if (!DecodeLoadObjectFromPoolOrThread(pc, code, *object)) {
      *object = nullptr;
    }
  }
}

#endif  // !defined(PRODUCT) || defined(FORCE_INCLUDE_DISASSEMBLER)

}  // namespace dart

#endif  // defined(TARGET_ARCH_ARM64)
