// Copyright 2011 the V8 project 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 Google Inc. 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 AND 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 <assert.h>
#include <stdio.h>
#include <stdarg.h>

#include "v8.h"

#if V8_TARGET_ARCH_X64

#include "disasm.h"
#include "lazy-instance.h"

namespace disasm {

enum OperandType {
  UNSET_OP_ORDER = 0,
  // Operand size decides between 16, 32 and 64 bit operands.
  REG_OPER_OP_ORDER = 1,  // Register destination, operand source.
  OPER_REG_OP_ORDER = 2,  // Operand destination, register source.
  // Fixed 8-bit operands.
  BYTE_SIZE_OPERAND_FLAG = 4,
  BYTE_REG_OPER_OP_ORDER = REG_OPER_OP_ORDER | BYTE_SIZE_OPERAND_FLAG,
  BYTE_OPER_REG_OP_ORDER = OPER_REG_OP_ORDER | BYTE_SIZE_OPERAND_FLAG
};


//------------------------------------------------------------------
// Tables
//------------------------------------------------------------------
struct ByteMnemonic {
  int b;  // -1 terminates, otherwise must be in range (0..255)
  OperandType op_order_;
  const char* mnem;
};


static const ByteMnemonic two_operands_instr[] = {
  { 0x00, BYTE_OPER_REG_OP_ORDER, "add" },
  { 0x01, OPER_REG_OP_ORDER,      "add" },
  { 0x02, BYTE_REG_OPER_OP_ORDER, "add" },
  { 0x03, REG_OPER_OP_ORDER,      "add" },
  { 0x08, BYTE_OPER_REG_OP_ORDER, "or" },
  { 0x09, OPER_REG_OP_ORDER,      "or" },
  { 0x0A, BYTE_REG_OPER_OP_ORDER, "or" },
  { 0x0B, REG_OPER_OP_ORDER,      "or" },
  { 0x10, BYTE_OPER_REG_OP_ORDER, "adc" },
  { 0x11, OPER_REG_OP_ORDER,      "adc" },
  { 0x12, BYTE_REG_OPER_OP_ORDER, "adc" },
  { 0x13, REG_OPER_OP_ORDER,      "adc" },
  { 0x18, BYTE_OPER_REG_OP_ORDER, "sbb" },
  { 0x19, OPER_REG_OP_ORDER,      "sbb" },
  { 0x1A, BYTE_REG_OPER_OP_ORDER, "sbb" },
  { 0x1B, REG_OPER_OP_ORDER,      "sbb" },
  { 0x20, BYTE_OPER_REG_OP_ORDER, "and" },
  { 0x21, OPER_REG_OP_ORDER,      "and" },
  { 0x22, BYTE_REG_OPER_OP_ORDER, "and" },
  { 0x23, REG_OPER_OP_ORDER,      "and" },
  { 0x28, BYTE_OPER_REG_OP_ORDER, "sub" },
  { 0x29, OPER_REG_OP_ORDER,      "sub" },
  { 0x2A, BYTE_REG_OPER_OP_ORDER, "sub" },
  { 0x2B, REG_OPER_OP_ORDER,      "sub" },
  { 0x30, BYTE_OPER_REG_OP_ORDER, "xor" },
  { 0x31, OPER_REG_OP_ORDER,      "xor" },
  { 0x32, BYTE_REG_OPER_OP_ORDER, "xor" },
  { 0x33, REG_OPER_OP_ORDER,      "xor" },
  { 0x38, BYTE_OPER_REG_OP_ORDER, "cmp" },
  { 0x39, OPER_REG_OP_ORDER,      "cmp" },
  { 0x3A, BYTE_REG_OPER_OP_ORDER, "cmp" },
  { 0x3B, REG_OPER_OP_ORDER,      "cmp" },
  { 0x63, REG_OPER_OP_ORDER,      "movsxl" },
  { 0x84, BYTE_REG_OPER_OP_ORDER, "test" },
  { 0x85, REG_OPER_OP_ORDER,      "test" },
  { 0x86, BYTE_REG_OPER_OP_ORDER, "xchg" },
  { 0x87, REG_OPER_OP_ORDER,      "xchg" },
  { 0x88, BYTE_OPER_REG_OP_ORDER, "mov" },
  { 0x89, OPER_REG_OP_ORDER,      "mov" },
  { 0x8A, BYTE_REG_OPER_OP_ORDER, "mov" },
  { 0x8B, REG_OPER_OP_ORDER,      "mov" },
  { 0x8D, REG_OPER_OP_ORDER,      "lea" },
  { -1, UNSET_OP_ORDER, "" }
};


static const ByteMnemonic zero_operands_instr[] = {
  { 0xC3, UNSET_OP_ORDER, "ret" },
  { 0xC9, UNSET_OP_ORDER, "leave" },
  { 0xF4, UNSET_OP_ORDER, "hlt" },
  { 0xFC, UNSET_OP_ORDER, "cld" },
  { 0xCC, UNSET_OP_ORDER, "int3" },
  { 0x60, UNSET_OP_ORDER, "pushad" },
  { 0x61, UNSET_OP_ORDER, "popad" },
  { 0x9C, UNSET_OP_ORDER, "pushfd" },
  { 0x9D, UNSET_OP_ORDER, "popfd" },
  { 0x9E, UNSET_OP_ORDER, "sahf" },
  { 0x99, UNSET_OP_ORDER, "cdq" },
  { 0x9B, UNSET_OP_ORDER, "fwait" },
  { 0xA4, UNSET_OP_ORDER, "movs" },
  { 0xA5, UNSET_OP_ORDER, "movs" },
  { 0xA6, UNSET_OP_ORDER, "cmps" },
  { 0xA7, UNSET_OP_ORDER, "cmps" },
  { -1, UNSET_OP_ORDER, "" }
};


static const ByteMnemonic call_jump_instr[] = {
  { 0xE8, UNSET_OP_ORDER, "call" },
  { 0xE9, UNSET_OP_ORDER, "jmp" },
  { -1, UNSET_OP_ORDER, "" }
};


static const ByteMnemonic short_immediate_instr[] = {
  { 0x05, UNSET_OP_ORDER, "add" },
  { 0x0D, UNSET_OP_ORDER, "or" },
  { 0x15, UNSET_OP_ORDER, "adc" },
  { 0x1D, UNSET_OP_ORDER, "sbb" },
  { 0x25, UNSET_OP_ORDER, "and" },
  { 0x2D, UNSET_OP_ORDER, "sub" },
  { 0x35, UNSET_OP_ORDER, "xor" },
  { 0x3D, UNSET_OP_ORDER, "cmp" },
  { -1, UNSET_OP_ORDER, "" }
};


static const char* const conditional_code_suffix[] = {
  "o", "no", "c", "nc", "z", "nz", "na", "a",
  "s", "ns", "pe", "po", "l", "ge", "le", "g"
};


enum InstructionType {
  NO_INSTR,
  ZERO_OPERANDS_INSTR,
  TWO_OPERANDS_INSTR,
  JUMP_CONDITIONAL_SHORT_INSTR,
  REGISTER_INSTR,
  PUSHPOP_INSTR,  // Has implicit 64-bit operand size.
  MOVE_REG_INSTR,
  CALL_JUMP_INSTR,
  SHORT_IMMEDIATE_INSTR
};


enum Prefixes {
  ESCAPE_PREFIX = 0x0F,
  OPERAND_SIZE_OVERRIDE_PREFIX = 0x66,
  ADDRESS_SIZE_OVERRIDE_PREFIX = 0x67,
  REPNE_PREFIX = 0xF2,
  REP_PREFIX = 0xF3,
  REPEQ_PREFIX = REP_PREFIX
};


struct InstructionDesc {
  const char* mnem;
  InstructionType type;
  OperandType op_order_;
  bool byte_size_operation;  // Fixed 8-bit operation.
};


class InstructionTable {
 public:
  InstructionTable();
  const InstructionDesc& Get(byte x) const {
    return instructions_[x];
  }

 private:
  InstructionDesc instructions_[256];
  void Clear();
  void Init();
  void CopyTable(const ByteMnemonic bm[], InstructionType type);
  void SetTableRange(InstructionType type, byte start, byte end, bool byte_size,
                     const char* mnem);
  void AddJumpConditionalShort();
};


InstructionTable::InstructionTable() {
  Clear();
  Init();
}


void InstructionTable::Clear() {
  for (int i = 0; i < 256; i++) {
    instructions_[i].mnem = "(bad)";
    instructions_[i].type = NO_INSTR;
    instructions_[i].op_order_ = UNSET_OP_ORDER;
    instructions_[i].byte_size_operation = false;
  }
}


void InstructionTable::Init() {
  CopyTable(two_operands_instr, TWO_OPERANDS_INSTR);
  CopyTable(zero_operands_instr, ZERO_OPERANDS_INSTR);
  CopyTable(call_jump_instr, CALL_JUMP_INSTR);
  CopyTable(short_immediate_instr, SHORT_IMMEDIATE_INSTR);
  AddJumpConditionalShort();
  SetTableRange(PUSHPOP_INSTR, 0x50, 0x57, false, "push");
  SetTableRange(PUSHPOP_INSTR, 0x58, 0x5F, false, "pop");
  SetTableRange(MOVE_REG_INSTR, 0xB8, 0xBF, false, "mov");
}


void InstructionTable::CopyTable(const ByteMnemonic bm[],
                                 InstructionType type) {
  for (int i = 0; bm[i].b >= 0; i++) {
    InstructionDesc* id = &instructions_[bm[i].b];
    id->mnem = bm[i].mnem;
    OperandType op_order = bm[i].op_order_;
    id->op_order_ =
        static_cast<OperandType>(op_order & ~BYTE_SIZE_OPERAND_FLAG);
    ASSERT_EQ(NO_INSTR, id->type);  // Information not already entered
    id->type = type;
    id->byte_size_operation = ((op_order & BYTE_SIZE_OPERAND_FLAG) != 0);
  }
}


void InstructionTable::SetTableRange(InstructionType type,
                                     byte start,
                                     byte end,
                                     bool byte_size,
                                     const char* mnem) {
  for (byte b = start; b <= end; b++) {
    InstructionDesc* id = &instructions_[b];
    ASSERT_EQ(NO_INSTR, id->type);  // Information not already entered
    id->mnem = mnem;
    id->type = type;
    id->byte_size_operation = byte_size;
  }
}


void InstructionTable::AddJumpConditionalShort() {
  for (byte b = 0x70; b <= 0x7F; b++) {
    InstructionDesc* id = &instructions_[b];
    ASSERT_EQ(NO_INSTR, id->type);  // Information not already entered
    id->mnem = NULL;  // Computed depending on condition code.
    id->type = JUMP_CONDITIONAL_SHORT_INSTR;
  }
}


static v8::internal::LazyInstance<InstructionTable>::type instruction_table =
    LAZY_INSTANCE_INITIALIZER;


static InstructionDesc cmov_instructions[16] = {
  {"cmovo", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovno", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovc", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovnc", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovz", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovnz", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovna", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmova", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovs", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovns", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovpe", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovpo", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovl", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovge", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovle", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false},
  {"cmovg", TWO_OPERANDS_INSTR, REG_OPER_OP_ORDER, false}
};


//------------------------------------------------------------------------------
// DisassemblerX64 implementation.

enum UnimplementedOpcodeAction {
  CONTINUE_ON_UNIMPLEMENTED_OPCODE,
  ABORT_ON_UNIMPLEMENTED_OPCODE
};


// A new DisassemblerX64 object is created to disassemble each instruction.
// The object can only disassemble a single instruction.
class DisassemblerX64 {
 public:
  DisassemblerX64(const NameConverter& converter,
                  UnimplementedOpcodeAction unimplemented_action =
                      ABORT_ON_UNIMPLEMENTED_OPCODE)
      : converter_(converter),
        tmp_buffer_pos_(0),
        abort_on_unimplemented_(
            unimplemented_action == ABORT_ON_UNIMPLEMENTED_OPCODE),
        rex_(0),
        operand_size_(0),
        group_1_prefix_(0),
        byte_size_operand_(false),
        instruction_table_(instruction_table.Pointer()) {
    tmp_buffer_[0] = '\0';
  }

  virtual ~DisassemblerX64() {
  }

  // Writes one disassembled instruction into 'buffer' (0-terminated).
  // Returns the length of the disassembled machine instruction in bytes.
  int InstructionDecode(v8::internal::Vector<char> buffer, byte* instruction);

 private:
  enum OperandSize {
    OPERAND_BYTE_SIZE = 0,
    OPERAND_WORD_SIZE = 1,
    OPERAND_DOUBLEWORD_SIZE = 2,
    OPERAND_QUADWORD_SIZE = 3
  };

  const NameConverter& converter_;
  v8::internal::EmbeddedVector<char, 128> tmp_buffer_;
  unsigned int tmp_buffer_pos_;
  bool abort_on_unimplemented_;
  // Prefixes parsed
  byte rex_;
  byte operand_size_;  // 0x66 or (if no group 3 prefix is present) 0x0.
  byte group_1_prefix_;  // 0xF2, 0xF3, or (if no group 1 prefix is present) 0.
  // Byte size operand override.
  bool byte_size_operand_;
  const InstructionTable* const instruction_table_;

  void setRex(byte rex) {
    ASSERT_EQ(0x40, rex & 0xF0);
    rex_ = rex;
  }

  bool rex() { return rex_ != 0; }

  bool rex_b() { return (rex_ & 0x01) != 0; }

  // Actual number of base register given the low bits and the rex.b state.
  int base_reg(int low_bits) { return low_bits | ((rex_ & 0x01) << 3); }

  bool rex_x() { return (rex_ & 0x02) != 0; }

  bool rex_r() { return (rex_ & 0x04) != 0; }

  bool rex_w() { return (rex_ & 0x08) != 0; }

  OperandSize operand_size() {
    if (byte_size_operand_) return OPERAND_BYTE_SIZE;
    if (rex_w()) return OPERAND_QUADWORD_SIZE;
    if (operand_size_ != 0) return OPERAND_WORD_SIZE;
    return OPERAND_DOUBLEWORD_SIZE;
  }

  char operand_size_code() {
    return "bwlq"[operand_size()];
  }

  const char* NameOfCPURegister(int reg) const {
    return converter_.NameOfCPURegister(reg);
  }

  const char* NameOfByteCPURegister(int reg) const {
    return converter_.NameOfByteCPURegister(reg);
  }

  const char* NameOfXMMRegister(int reg) const {
    return converter_.NameOfXMMRegister(reg);
  }

  const char* NameOfAddress(byte* addr) const {
    return converter_.NameOfAddress(addr);
  }

  // Disassembler helper functions.
  void get_modrm(byte data,
                 int* mod,
                 int* regop,
                 int* rm) {
    *mod = (data >> 6) & 3;
    *regop = ((data & 0x38) >> 3) | (rex_r() ? 8 : 0);
    *rm = (data & 7) | (rex_b() ? 8 : 0);
  }

  void get_sib(byte data,
               int* scale,
               int* index,
               int* base) {
    *scale = (data >> 6) & 3;
    *index = ((data >> 3) & 7) | (rex_x() ? 8 : 0);
    *base = (data & 7) | (rex_b() ? 8 : 0);
  }

  typedef const char* (DisassemblerX64::*RegisterNameMapping)(int reg) const;

  int PrintRightOperandHelper(byte* modrmp,
                              RegisterNameMapping register_name);
  int PrintRightOperand(byte* modrmp);
  int PrintRightByteOperand(byte* modrmp);
  int PrintRightXMMOperand(byte* modrmp);
  int PrintOperands(const char* mnem,
                    OperandType op_order,
                    byte* data);
  int PrintImmediate(byte* data, OperandSize size);
  int PrintImmediateOp(byte* data);
  const char* TwoByteMnemonic(byte opcode);
  int TwoByteOpcodeInstruction(byte* data);
  int F6F7Instruction(byte* data);
  int ShiftInstruction(byte* data);
  int JumpShort(byte* data);
  int JumpConditional(byte* data);
  int JumpConditionalShort(byte* data);
  int SetCC(byte* data);
  int FPUInstruction(byte* data);
  int MemoryFPUInstruction(int escape_opcode, int regop, byte* modrm_start);
  int RegisterFPUInstruction(int escape_opcode, byte modrm_byte);
  void AppendToBuffer(const char* format, ...);

  void UnimplementedInstruction() {
    if (abort_on_unimplemented_) {
      CHECK(false);
    } else {
      AppendToBuffer("'Unimplemented Instruction'");
    }
  }
};


void DisassemblerX64::AppendToBuffer(const char* format, ...) {
  v8::internal::Vector<char> buf = tmp_buffer_ + tmp_buffer_pos_;
  va_list args;
  va_start(args, format);
  int result = v8::internal::OS::VSNPrintF(buf, format, args);
  va_end(args);
  tmp_buffer_pos_ += result;
}


int DisassemblerX64::PrintRightOperandHelper(
    byte* modrmp,
    RegisterNameMapping direct_register_name) {
  int mod, regop, rm;
  get_modrm(*modrmp, &mod, &regop, &rm);
  RegisterNameMapping register_name = (mod == 3) ? direct_register_name :
      &DisassemblerX64::NameOfCPURegister;
  switch (mod) {
    case 0:
      if ((rm & 7) == 5) {
        int32_t disp = *reinterpret_cast<int32_t*>(modrmp + 1);
        AppendToBuffer("[0x%x]", disp);
        return 5;
      } else if ((rm & 7) == 4) {
        // Codes for SIB byte.
        byte sib = *(modrmp + 1);
        int scale, index, base;
        get_sib(sib, &scale, &index, &base);
        if (index == 4 && (base & 7) == 4 && scale == 0 /*times_1*/) {
          // index == rsp means no index. Only use sib byte with no index for
          // rsp and r12 base.
          AppendToBuffer("[%s]", NameOfCPURegister(base));
          return 2;
        } else if (base == 5) {
          // base == rbp means no base register (when mod == 0).
          int32_t disp = *reinterpret_cast<int32_t*>(modrmp + 2);
          AppendToBuffer("[%s*%d%s0x%x]",
                         NameOfCPURegister(index),
                         1 << scale,
                         disp < 0 ? "-" : "+",
                         disp < 0 ? -disp : disp);
          return 6;
        } else if (index != 4 && base != 5) {
          // [base+index*scale]
          AppendToBuffer("[%s+%s*%d]",
                         NameOfCPURegister(base),
                         NameOfCPURegister(index),
                         1 << scale);
          return 2;
        } else {
          UnimplementedInstruction();
          return 1;
        }
      } else {
        AppendToBuffer("[%s]", NameOfCPURegister(rm));
        return 1;
      }
      break;
    case 1:  // fall through
    case 2:
      if ((rm & 7) == 4) {
        byte sib = *(modrmp + 1);
        int scale, index, base;
        get_sib(sib, &scale, &index, &base);
        int disp = (mod == 2) ? *reinterpret_cast<int32_t*>(modrmp + 2)
                              : *reinterpret_cast<int8_t*>(modrmp + 2);
        if (index == 4 && (base & 7) == 4 && scale == 0 /*times_1*/) {
          AppendToBuffer("[%s%s0x%x]",
                         NameOfCPURegister(base),
                         disp < 0 ? "-" : "+",
                         disp < 0 ? -disp : disp);
        } else {
          AppendToBuffer("[%s+%s*%d%s0x%x]",
                         NameOfCPURegister(base),
                         NameOfCPURegister(index),
                         1 << scale,
                         disp < 0 ? "-" : "+",
                         disp < 0 ? -disp : disp);
        }
        return mod == 2 ? 6 : 3;
      } else {
        // No sib.
        int disp = (mod == 2) ? *reinterpret_cast<int32_t*>(modrmp + 1)
                              : *reinterpret_cast<int8_t*>(modrmp + 1);
        AppendToBuffer("[%s%s0x%x]",
                       NameOfCPURegister(rm),
                       disp < 0 ? "-" : "+",
                       disp < 0 ? -disp : disp);
        return (mod == 2) ? 5 : 2;
      }
      break;
    case 3:
      AppendToBuffer("%s", (this->*register_name)(rm));
      return 1;
    default:
      UnimplementedInstruction();
      return 1;
  }
  UNREACHABLE();
}


int DisassemblerX64::PrintImmediate(byte* data, OperandSize size) {
  int64_t value;
  int count;
  switch (size) {
    case OPERAND_BYTE_SIZE:
      value = *data;
      count = 1;
      break;
    case OPERAND_WORD_SIZE:
      value = *reinterpret_cast<int16_t*>(data);
      count = 2;
      break;
    case OPERAND_DOUBLEWORD_SIZE:
      value = *reinterpret_cast<uint32_t*>(data);
      count = 4;
      break;
    case OPERAND_QUADWORD_SIZE:
      value = *reinterpret_cast<int32_t*>(data);
      count = 4;
      break;
    default:
      UNREACHABLE();
      value = 0;  // Initialize variables on all paths to satisfy the compiler.
      count = 0;
  }
  AppendToBuffer("%" V8_PTR_PREFIX "x", value);
  return count;
}


int DisassemblerX64::PrintRightOperand(byte* modrmp) {
  return PrintRightOperandHelper(modrmp,
                                 &DisassemblerX64::NameOfCPURegister);
}


int DisassemblerX64::PrintRightByteOperand(byte* modrmp) {
  return PrintRightOperandHelper(modrmp,
                                 &DisassemblerX64::NameOfByteCPURegister);
}


int DisassemblerX64::PrintRightXMMOperand(byte* modrmp) {
  return PrintRightOperandHelper(modrmp,
                                 &DisassemblerX64::NameOfXMMRegister);
}


// Returns number of bytes used including the current *data.
// Writes instruction's mnemonic, left and right operands to 'tmp_buffer_'.
int DisassemblerX64::PrintOperands(const char* mnem,
                                   OperandType op_order,
                                   byte* data) {
  byte modrm = *data;
  int mod, regop, rm;
  get_modrm(modrm, &mod, &regop, &rm);
  int advance = 0;
  const char* register_name =
      byte_size_operand_ ? NameOfByteCPURegister(regop)
                         : NameOfCPURegister(regop);
  switch (op_order) {
    case REG_OPER_OP_ORDER: {
      AppendToBuffer("%s%c %s,",
                     mnem,
                     operand_size_code(),
                     register_name);
      advance = byte_size_operand_ ? PrintRightByteOperand(data)
                                   : PrintRightOperand(data);
      break;
    }
    case OPER_REG_OP_ORDER: {
      AppendToBuffer("%s%c ", mnem, operand_size_code());
      advance = byte_size_operand_ ? PrintRightByteOperand(data)
                                   : PrintRightOperand(data);
      AppendToBuffer(",%s", register_name);
      break;
    }
    default:
      UNREACHABLE();
      break;
  }
  return advance;
}


// Returns number of bytes used by machine instruction, including *data byte.
// Writes immediate instructions to 'tmp_buffer_'.
int DisassemblerX64::PrintImmediateOp(byte* data) {
  bool byte_size_immediate = (*data & 0x02) != 0;
  byte modrm = *(data + 1);
  int mod, regop, rm;
  get_modrm(modrm, &mod, &regop, &rm);
  const char* mnem = "Imm???";
  switch (regop) {
    case 0:
      mnem = "add";
      break;
    case 1:
      mnem = "or";
      break;
    case 2:
      mnem = "adc";
      break;
    case 3:
      mnem = "sbb";
      break;
    case 4:
      mnem = "and";
      break;
    case 5:
      mnem = "sub";
      break;
    case 6:
      mnem = "xor";
      break;
    case 7:
      mnem = "cmp";
      break;
    default:
      UnimplementedInstruction();
  }
  AppendToBuffer("%s%c ", mnem, operand_size_code());
  int count = PrintRightOperand(data + 1);
  AppendToBuffer(",0x");
  OperandSize immediate_size =
      byte_size_immediate ? OPERAND_BYTE_SIZE : operand_size();
  count += PrintImmediate(data + 1 + count, immediate_size);
  return 1 + count;
}


// Returns number of bytes used, including *data.
int DisassemblerX64::F6F7Instruction(byte* data) {
  ASSERT(*data == 0xF7 || *data == 0xF6);
  byte modrm = *(data + 1);
  int mod, regop, rm;
  get_modrm(modrm, &mod, &regop, &rm);
  if (mod == 3 && regop != 0) {
    const char* mnem = NULL;
    switch (regop) {
      case 2:
        mnem = "not";
        break;
      case 3:
        mnem = "neg";
        break;
      case 4:
        mnem = "mul";
        break;
      case 5:
        mnem = "imul";
        break;
      case 7:
        mnem = "idiv";
        break;
      default:
        UnimplementedInstruction();
    }
    AppendToBuffer("%s%c %s",
                   mnem,
                   operand_size_code(),
                   NameOfCPURegister(rm));
    return 2;
  } else if (regop == 0) {
    AppendToBuffer("test%c ", operand_size_code());
    int count = PrintRightOperand(data + 1);  // Use name of 64-bit register.
    AppendToBuffer(",0x");
    count += PrintImmediate(data + 1 + count, operand_size());
    return 1 + count;
  } else {
    UnimplementedInstruction();
    return 2;
  }
}


int DisassemblerX64::ShiftInstruction(byte* data) {
  byte op = *data & (~1);
  if (op != 0xD0 && op != 0xD2 && op != 0xC0) {
    UnimplementedInstruction();
    return 1;
  }
  byte modrm = *(data + 1);
  int mod, regop, rm;
  get_modrm(modrm, &mod, &regop, &rm);
  regop &= 0x7;  // The REX.R bit does not affect the operation.
  int imm8 = -1;
  int num_bytes = 2;
  if (mod != 3) {
    UnimplementedInstruction();
    return num_bytes;
  }
  const char* mnem = NULL;
  switch (regop) {
    case 0:
      mnem = "rol";
      break;
    case 1:
      mnem = "ror";
      break;
    case 2:
      mnem = "rcl";
      break;
    case 3:
      mnem = "rcr";
      break;
    case 4:
      mnem = "shl";
      break;
    case 5:
      mnem = "shr";
      break;
    case 7:
      mnem = "sar";
      break;
    default:
      UnimplementedInstruction();
      return num_bytes;
  }
  ASSERT_NE(NULL, mnem);
  if (op == 0xD0) {
    imm8 = 1;
  } else if (op == 0xC0) {
    imm8 = *(data + 2);
    num_bytes = 3;
  }
  AppendToBuffer("%s%c %s,",
                 mnem,
                 operand_size_code(),
                 byte_size_operand_ ? NameOfByteCPURegister(rm)
                                    : NameOfCPURegister(rm));
  if (op == 0xD2) {
    AppendToBuffer("cl");
  } else {
    AppendToBuffer("%d", imm8);
  }
  return num_bytes;
}


// Returns number of bytes used, including *data.
int DisassemblerX64::JumpShort(byte* data) {
  ASSERT_EQ(0xEB, *data);
  byte b = *(data + 1);
  byte* dest = data + static_cast<int8_t>(b) + 2;
  AppendToBuffer("jmp %s", NameOfAddress(dest));
  return 2;
}


// Returns number of bytes used, including *data.
int DisassemblerX64::JumpConditional(byte* data) {
  ASSERT_EQ(0x0F, *data);
  byte cond = *(data + 1) & 0x0F;
  byte* dest = data + *reinterpret_cast<int32_t*>(data + 2) + 6;
  const char* mnem = conditional_code_suffix[cond];
  AppendToBuffer("j%s %s", mnem, NameOfAddress(dest));
  return 6;  // includes 0x0F
}


// Returns number of bytes used, including *data.
int DisassemblerX64::JumpConditionalShort(byte* data) {
  byte cond = *data & 0x0F;
  byte b = *(data + 1);
  byte* dest = data + static_cast<int8_t>(b) + 2;
  const char* mnem = conditional_code_suffix[cond];
  AppendToBuffer("j%s %s", mnem, NameOfAddress(dest));
  return 2;
}


// Returns number of bytes used, including *data.
int DisassemblerX64::SetCC(byte* data) {
  ASSERT_EQ(0x0F, *data);
  byte cond = *(data + 1) & 0x0F;
  const char* mnem = conditional_code_suffix[cond];
  AppendToBuffer("set%s%c ", mnem, operand_size_code());
  PrintRightByteOperand(data + 2);
  return 3;  // includes 0x0F
}


// Returns number of bytes used, including *data.
int DisassemblerX64::FPUInstruction(byte* data) {
  byte escape_opcode = *data;
  ASSERT_EQ(0xD8, escape_opcode & 0xF8);
  byte modrm_byte = *(data+1);

  if (modrm_byte >= 0xC0) {
    return RegisterFPUInstruction(escape_opcode, modrm_byte);
  } else {
    return MemoryFPUInstruction(escape_opcode, modrm_byte, data+1);
  }
}

int DisassemblerX64::MemoryFPUInstruction(int escape_opcode,
                                           int modrm_byte,
                                           byte* modrm_start) {
  const char* mnem = "?";
  int regop = (modrm_byte >> 3) & 0x7;  // reg/op field of modrm byte.
  switch (escape_opcode) {
    case 0xD9: switch (regop) {
        case 0: mnem = "fld_s"; break;
        case 3: mnem = "fstp_s"; break;
        case 7: mnem = "fstcw"; break;
        default: UnimplementedInstruction();
      }
      break;

    case 0xDB: switch (regop) {
        case 0: mnem = "fild_s"; break;
        case 1: mnem = "fisttp_s"; break;
        case 2: mnem = "fist_s"; break;
        case 3: mnem = "fistp_s"; break;
        default: UnimplementedInstruction();
      }
      break;

    case 0xDD: switch (regop) {
        case 0: mnem = "fld_d"; break;
        case 3: mnem = "fstp_d"; break;
        default: UnimplementedInstruction();
      }
      break;

    case 0xDF: switch (regop) {
        case 5: mnem = "fild_d"; break;
        case 7: mnem = "fistp_d"; break;
        default: UnimplementedInstruction();
      }
      break;

    default: UnimplementedInstruction();
  }
  AppendToBuffer("%s ", mnem);
  int count = PrintRightOperand(modrm_start);
  return count + 1;
}

int DisassemblerX64::RegisterFPUInstruction(int escape_opcode,
                                             byte modrm_byte) {
  bool has_register = false;  // Is the FPU register encoded in modrm_byte?
  const char* mnem = "?";

  switch (escape_opcode) {
    case 0xD8:
      UnimplementedInstruction();
      break;

    case 0xD9:
      switch (modrm_byte & 0xF8) {
        case 0xC0:
          mnem = "fld";
          has_register = true;
          break;
        case 0xC8:
          mnem = "fxch";
          has_register = true;
          break;
        default:
          switch (modrm_byte) {
            case 0xE0: mnem = "fchs"; break;
            case 0xE1: mnem = "fabs"; break;
            case 0xE3: mnem = "fninit"; break;
            case 0xE4: mnem = "ftst"; break;
            case 0xE8: mnem = "fld1"; break;
            case 0xEB: mnem = "fldpi"; break;
            case 0xED: mnem = "fldln2"; break;
            case 0xEE: mnem = "fldz"; break;
            case 0xF0: mnem = "f2xm1"; break;
            case 0xF1: mnem = "fyl2x"; break;
            case 0xF2: mnem = "fptan"; break;
            case 0xF5: mnem = "fprem1"; break;
            case 0xF7: mnem = "fincstp"; break;
            case 0xF8: mnem = "fprem"; break;
            case 0xFC: mnem = "frndint"; break;
            case 0xFD: mnem = "fscale"; break;
            case 0xFE: mnem = "fsin"; break;
            case 0xFF: mnem = "fcos"; break;
            default: UnimplementedInstruction();
          }
      }
      break;

    case 0xDA:
      if (modrm_byte == 0xE9) {
        mnem = "fucompp";
      } else {
        UnimplementedInstruction();
      }
      break;

    case 0xDB:
      if ((modrm_byte & 0xF8) == 0xE8) {
        mnem = "fucomi";
        has_register = true;
      } else if (modrm_byte  == 0xE2) {
        mnem = "fclex";
      } else if (modrm_byte == 0xE3) {
        mnem = "fninit";
      } else {
        UnimplementedInstruction();
      }
      break;

    case 0xDC:
      has_register = true;
      switch (modrm_byte & 0xF8) {
        case 0xC0: mnem = "fadd"; break;
        case 0xE8: mnem = "fsub"; break;
        case 0xC8: mnem = "fmul"; break;
        case 0xF8: mnem = "fdiv"; break;
        default: UnimplementedInstruction();
      }
      break;

    case 0xDD:
      has_register = true;
      switch (modrm_byte & 0xF8) {
        case 0xC0: mnem = "ffree"; break;
        case 0xD8: mnem = "fstp"; break;
        default: UnimplementedInstruction();
      }
      break;

    case 0xDE:
      if (modrm_byte  == 0xD9) {
        mnem = "fcompp";
      } else {
        has_register = true;
        switch (modrm_byte & 0xF8) {
          case 0xC0: mnem = "faddp"; break;
          case 0xE8: mnem = "fsubp"; break;
          case 0xC8: mnem = "fmulp"; break;
          case 0xF8: mnem = "fdivp"; break;
          default: UnimplementedInstruction();
        }
      }
      break;

    case 0xDF:
      if (modrm_byte == 0xE0) {
        mnem = "fnstsw_ax";
      } else if ((modrm_byte & 0xF8) == 0xE8) {
        mnem = "fucomip";
        has_register = true;
      }
      break;

    default: UnimplementedInstruction();
  }

  if (has_register) {
    AppendToBuffer("%s st%d", mnem, modrm_byte & 0x7);
  } else {
    AppendToBuffer("%s", mnem);
  }
  return 2;
}



// Handle all two-byte opcodes, which start with 0x0F.
// These instructions may be affected by an 0x66, 0xF2, or 0xF3 prefix.
// We do not use any three-byte opcodes, which start with 0x0F38 or 0x0F3A.
int DisassemblerX64::TwoByteOpcodeInstruction(byte* data) {
  byte opcode = *(data + 1);
  byte* current = data + 2;
  // At return, "current" points to the start of the next instruction.
  const char* mnemonic = TwoByteMnemonic(opcode);
  if (operand_size_ == 0x66) {
    // 0x66 0x0F prefix.
    int mod, regop, rm;
    if (opcode == 0x3A) {
      byte third_byte = *current;
      current = data + 3;
      if (third_byte == 0x17) {
        get_modrm(*current, &mod, &regop, &rm);
        AppendToBuffer("extractps ");  // reg/m32, xmm, imm8
        current += PrintRightOperand(current);
        AppendToBuffer(",%s,%d", NameOfXMMRegister(regop), (*current) & 3);
        current += 1;
      } else if (third_byte == 0x0b) {
        get_modrm(*current, &mod, &regop, &rm);
         // roundsd xmm, xmm/m64, imm8
        AppendToBuffer("roundsd %s,", NameOfXMMRegister(regop));
        current += PrintRightXMMOperand(current);
        AppendToBuffer(",%d", (*current) & 3);
        current += 1;
      } else {
        UnimplementedInstruction();
      }
    } else {
      get_modrm(*current, &mod, &regop, &rm);
      if (opcode == 0x1f) {
        current++;
        if (rm == 4) {  // SIB byte present.
          current++;
        }
        if (mod == 1) {  // Byte displacement.
          current += 1;
        } else if (mod == 2) {  // 32-bit displacement.
          current += 4;
        }  // else no immediate displacement.
        AppendToBuffer("nop");
      } else if (opcode == 0x28) {
        AppendToBuffer("movapd %s,", NameOfXMMRegister(regop));
        current += PrintRightXMMOperand(current);
      } else if (opcode == 0x29) {
        AppendToBuffer("movapd ");
        current += PrintRightXMMOperand(current);
        AppendToBuffer(",%s", NameOfXMMRegister(regop));
      } else if (opcode == 0x6E) {
        AppendToBuffer("mov%c %s,",
                       rex_w() ? 'q' : 'd',
                       NameOfXMMRegister(regop));
        current += PrintRightOperand(current);
      } else if (opcode == 0x6F) {
        AppendToBuffer("movdqa %s,",
                       NameOfXMMRegister(regop));
        current += PrintRightXMMOperand(current);
      } else if (opcode == 0x7E) {
        AppendToBuffer("mov%c ",
                       rex_w() ? 'q' : 'd');
        current += PrintRightOperand(current);
        AppendToBuffer(",%s", NameOfXMMRegister(regop));
      } else if (opcode == 0x7F) {
        AppendToBuffer("movdqa ");
        current += PrintRightXMMOperand(current);
        AppendToBuffer(",%s", NameOfXMMRegister(regop));
      } else if (opcode == 0xD6) {
        AppendToBuffer("movq ");
        current += PrintRightXMMOperand(current);
        AppendToBuffer(",%s", NameOfXMMRegister(regop));
      } else if (opcode == 0x50) {
        AppendToBuffer("movmskpd %s,", NameOfCPURegister(regop));
        current += PrintRightXMMOperand(current);
      } else if (opcode == 0x73) {
        current += 1;
        ASSERT(regop == 6);
        AppendToBuffer("psllq,%s,%d", NameOfXMMRegister(rm), *current & 0x7f);
        current += 1;
      } else {
        const char* mnemonic = "?";
        if (opcode == 0x54) {
          mnemonic = "andpd";
        } else  if (opcode == 0x56) {
          mnemonic = "orpd";
        } else  if (opcode == 0x57) {
          mnemonic = "xorpd";
        } else if (opcode == 0x2E) {
          mnemonic = "ucomisd";
        } else if (opcode == 0x2F) {
          mnemonic = "comisd";
        } else {
          UnimplementedInstruction();
        }
        AppendToBuffer("%s %s,", mnemonic, NameOfXMMRegister(regop));
        current += PrintRightXMMOperand(current);
      }
    }
  } else if (group_1_prefix_ == 0xF2) {
    // Beginning of instructions with prefix 0xF2.

    if (opcode == 0x11 || opcode == 0x10) {
      // MOVSD: Move scalar double-precision fp to/from/between XMM registers.
      AppendToBuffer("movsd ");
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      if (opcode == 0x11) {
        current += PrintRightXMMOperand(current);
        AppendToBuffer(",%s", NameOfXMMRegister(regop));
      } else {
        AppendToBuffer("%s,", NameOfXMMRegister(regop));
        current += PrintRightXMMOperand(current);
      }
    } else if (opcode == 0x2A) {
      // CVTSI2SD: integer to XMM double conversion.
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      AppendToBuffer("%sd %s,", mnemonic, NameOfXMMRegister(regop));
      current += PrintRightOperand(current);
    } else if (opcode == 0x2C) {
      // CVTTSD2SI:
      // Convert with truncation scalar double-precision FP to integer.
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      AppendToBuffer("cvttsd2si%c %s,",
          operand_size_code(), NameOfCPURegister(regop));
      current += PrintRightXMMOperand(current);
    } else if (opcode == 0x2D) {
      // CVTSD2SI: Convert scalar double-precision FP to integer.
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      AppendToBuffer("cvtsd2si%c %s,",
          operand_size_code(), NameOfCPURegister(regop));
      current += PrintRightXMMOperand(current);
    } else if ((opcode & 0xF8) == 0x58 || opcode == 0x51) {
      // XMM arithmetic. Mnemonic was retrieved at the start of this function.
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      AppendToBuffer("%s %s,", mnemonic, NameOfXMMRegister(regop));
      current += PrintRightXMMOperand(current);
    } else if (opcode == 0xC2) {
      // Intel manual 2A, Table 3-18.
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      const char* const pseudo_op[] = {
        "cmpeqsd",
        "cmpltsd",
        "cmplesd",
        "cmpunordsd",
        "cmpneqsd",
        "cmpnltsd",
        "cmpnlesd",
        "cmpordsd"
      };
      AppendToBuffer("%s %s,%s",
                     pseudo_op[current[1]],
                     NameOfXMMRegister(regop),
                     NameOfXMMRegister(rm));
      current += 2;
    } else {
      UnimplementedInstruction();
    }
  } else if (group_1_prefix_ == 0xF3) {
    // Instructions with prefix 0xF3.
    if (opcode == 0x11 || opcode == 0x10) {
      // MOVSS: Move scalar double-precision fp to/from/between XMM registers.
      AppendToBuffer("movss ");
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      if (opcode == 0x11) {
        current += PrintRightOperand(current);
        AppendToBuffer(",%s", NameOfXMMRegister(regop));
      } else {
        AppendToBuffer("%s,", NameOfXMMRegister(regop));
        current += PrintRightOperand(current);
      }
    } else if (opcode == 0x2A) {
      // CVTSI2SS: integer to XMM single conversion.
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      AppendToBuffer("%ss %s,", mnemonic, NameOfXMMRegister(regop));
      current += PrintRightOperand(current);
    } else if (opcode == 0x2C) {
      // CVTTSS2SI:
      // Convert with truncation scalar single-precision FP to dword integer.
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      AppendToBuffer("cvttss2si%c %s,",
          operand_size_code(), NameOfCPURegister(regop));
      current += PrintRightXMMOperand(current);
    } else if (opcode == 0x5A) {
      // CVTSS2SD:
      // Convert scalar single-precision FP to scalar double-precision FP.
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      AppendToBuffer("cvtss2sd %s,", NameOfXMMRegister(regop));
      current += PrintRightXMMOperand(current);
    } else if (opcode == 0x7E) {
      int mod, regop, rm;
      get_modrm(*current, &mod, &regop, &rm);
      AppendToBuffer("movq %s,", NameOfXMMRegister(regop));
      current += PrintRightXMMOperand(current);
    } else {
      UnimplementedInstruction();
    }
  } else if (opcode == 0x1F) {
    // NOP
    int mod, regop, rm;
    get_modrm(*current, &mod, &regop, &rm);
    current++;
    if (rm == 4) {  // SIB byte present.
      current++;
    }
    if (mod == 1) {  // Byte displacement.
      current += 1;
    } else if (mod == 2) {  // 32-bit displacement.
      current += 4;
    }  // else no immediate displacement.
    AppendToBuffer("nop");

  } else if (opcode == 0x28) {
    // movaps xmm, xmm/m128
    int mod, regop, rm;
    get_modrm(*current, &mod, &regop, &rm);
    AppendToBuffer("movaps %s,", NameOfXMMRegister(regop));
    current += PrintRightXMMOperand(current);

  } else if (opcode == 0x29) {
    // movaps xmm/m128, xmm
    int mod, regop, rm;
    get_modrm(*current, &mod, &regop, &rm);
    AppendToBuffer("movaps ");
    current += PrintRightXMMOperand(current);
    AppendToBuffer(",%s", NameOfXMMRegister(regop));

  } else if (opcode == 0xA2) {
    // CPUID
    AppendToBuffer("%s", mnemonic);

  } else if ((opcode & 0xF0) == 0x40) {
    // CMOVcc: conditional move.
    int condition = opcode & 0x0F;
    const InstructionDesc& idesc = cmov_instructions[condition];
    byte_size_operand_ = idesc.byte_size_operation;
    current += PrintOperands(idesc.mnem, idesc.op_order_, current);

  } else if (opcode >= 0x53 && opcode <= 0x5F) {
    const char* const pseudo_op[] = {
      "rcpps",
      "andps",
      "andnps",
      "orps",
      "xorps",
      "addps",
      "mulps",
      "cvtps2pd",
      "cvtdq2ps",
      "subps",
      "minps",
      "divps",
      "maxps",
    };
    int mod, regop, rm;
    get_modrm(*current, &mod, &regop, &rm);
    AppendToBuffer("%s %s,",
                   pseudo_op[opcode - 0x53],
                   NameOfXMMRegister(regop));
    current += PrintRightXMMOperand(current);

  } else if (opcode == 0xC6) {
    // shufps xmm, xmm/m128, imm8
    int mod, regop, rm;
    get_modrm(*current, &mod, &regop, &rm);
    AppendToBuffer("shufps %s, ", NameOfXMMRegister(regop));
    current += PrintRightXMMOperand(current);
    AppendToBuffer(", %d", (*current) & 3);
    current += 1;

  } else if (opcode == 0x50) {
    // movmskps reg, xmm
    int mod, regop, rm;
    get_modrm(*current, &mod, &regop, &rm);
    AppendToBuffer("movmskps %s,", NameOfCPURegister(regop));
    current += PrintRightXMMOperand(current);

  } else if ((opcode & 0xF0) == 0x80) {
    // Jcc: Conditional jump (branch).
    current = data + JumpConditional(data);

  } else if (opcode == 0xBE || opcode == 0xBF || opcode == 0xB6 ||
             opcode == 0xB7 || opcode == 0xAF) {
    // Size-extending moves, IMUL.
    current += PrintOperands(mnemonic, REG_OPER_OP_ORDER, current);

  } else if ((opcode & 0xF0) == 0x90) {
    // SETcc: Set byte on condition. Needs pointer to beginning of instruction.
    current = data + SetCC(data);

  } else if (opcode == 0xAB || opcode == 0xA5 || opcode == 0xAD) {
    // SHLD, SHRD (double-precision shift), BTS (bit set).
    AppendToBuffer("%s ", mnemonic);
    int mod, regop, rm;
    get_modrm(*current, &mod, &regop, &rm);
    current += PrintRightOperand(current);
    if (opcode == 0xAB) {
      AppendToBuffer(",%s", NameOfCPURegister(regop));
    } else {
      AppendToBuffer(",%s,cl", NameOfCPURegister(regop));
    }
  } else if (opcode == 0xBD) {
    AppendToBuffer("%s%c ", mnemonic, operand_size_code());
    int mod, regop, rm;
    get_modrm(*current, &mod, &regop, &rm);
    AppendToBuffer("%s,", NameOfCPURegister(regop));
    current += PrintRightOperand(current);
  } else {
    UnimplementedInstruction();
  }
  return static_cast<int>(current - data);
}


// Mnemonics for two-byte opcode instructions starting with 0x0F.
// The argument is the second byte of the two-byte opcode.
// Returns NULL if the instruction is not handled here.
const char* DisassemblerX64::TwoByteMnemonic(byte opcode) {
  switch (opcode) {
    case 0x1F:
      return "nop";
    case 0x2A:  // F2/F3 prefix.
      return "cvtsi2s";
    case 0x51:  // F2 prefix.
      return "sqrtsd";
    case 0x58:  // F2 prefix.
      return "addsd";
    case 0x59:  // F2 prefix.
      return "mulsd";
    case 0x5A:  // F2 prefix.
      return "cvtsd2ss";
    case 0x5C:  // F2 prefix.
      return "subsd";
    case 0x5E:  // F2 prefix.
      return "divsd";
    case 0xA2:
      return "cpuid";
    case 0xA5:
      return "shld";
    case 0xAB:
      return "bts";
    case 0xAD:
      return "shrd";
    case 0xAF:
      return "imul";
    case 0xB6:
      return "movzxb";
    case 0xB7:
      return "movzxw";
    case 0xBD:
      return "bsr";
    case 0xBE:
      return "movsxb";
    case 0xBF:
      return "movsxw";
    default:
      return NULL;
  }
}


// Disassembles the instruction at instr, and writes it into out_buffer.
int DisassemblerX64::InstructionDecode(v8::internal::Vector<char> out_buffer,
                                       byte* instr) {
  tmp_buffer_pos_ = 0;  // starting to write as position 0
  byte* data = instr;
  bool processed = true;  // Will be set to false if the current instruction
                          // is not in 'instructions' table.
  byte current;

  // Scan for prefixes.
  while (true) {
    current = *data;
    if (current == OPERAND_SIZE_OVERRIDE_PREFIX) {  // Group 3 prefix.
      operand_size_ = current;
    } else if ((current & 0xF0) == 0x40) {  // REX prefix.
      setRex(current);
      if (rex_w()) AppendToBuffer("REX.W ");
    } else if ((current & 0xFE) == 0xF2) {  // Group 1 prefix (0xF2 or 0xF3).
      group_1_prefix_ = current;
    } else {  // Not a prefix - an opcode.
      break;
    }
    data++;
  }

  const InstructionDesc& idesc = instruction_table_->Get(current);
  byte_size_operand_ = idesc.byte_size_operation;
  switch (idesc.type) {
    case ZERO_OPERANDS_INSTR:
      if (current >= 0xA4 && current <= 0xA7) {
        // String move or compare operations.
        if (group_1_prefix_ == REP_PREFIX) {
          // REP.
          AppendToBuffer("rep ");
        }
        if (rex_w()) AppendToBuffer("REX.W ");
        AppendToBuffer("%s%c", idesc.mnem, operand_size_code());
      } else {
        AppendToBuffer("%s", idesc.mnem, operand_size_code());
      }
      data++;
      break;

    case TWO_OPERANDS_INSTR:
      data++;
      data += PrintOperands(idesc.mnem, idesc.op_order_, data);
      break;

    case JUMP_CONDITIONAL_SHORT_INSTR:
      data += JumpConditionalShort(data);
      break;

    case REGISTER_INSTR:
      AppendToBuffer("%s%c %s",
                     idesc.mnem,
                     operand_size_code(),
                     NameOfCPURegister(base_reg(current & 0x07)));
      data++;
      break;
    case PUSHPOP_INSTR:
      AppendToBuffer("%s %s",
                     idesc.mnem,
                     NameOfCPURegister(base_reg(current & 0x07)));
      data++;
      break;
    case MOVE_REG_INSTR: {
      byte* addr = NULL;
      switch (operand_size()) {
        case OPERAND_WORD_SIZE:
          addr = reinterpret_cast<byte*>(*reinterpret_cast<int16_t*>(data + 1));
          data += 3;
          break;
        case OPERAND_DOUBLEWORD_SIZE:
          addr =
              reinterpret_cast<byte*>(*reinterpret_cast<uint32_t*>(data + 1));
          data += 5;
          break;
        case OPERAND_QUADWORD_SIZE:
          addr = reinterpret_cast<byte*>(*reinterpret_cast<int64_t*>(data + 1));
          data += 9;
          break;
        default:
          UNREACHABLE();
      }
      AppendToBuffer("mov%c %s,%s",
                     operand_size_code(),
                     NameOfCPURegister(base_reg(current & 0x07)),
                     NameOfAddress(addr));
      break;
    }

    case CALL_JUMP_INSTR: {
      byte* addr = data + *reinterpret_cast<int32_t*>(data + 1) + 5;
      AppendToBuffer("%s %s", idesc.mnem, NameOfAddress(addr));
      data += 5;
      break;
    }

    case SHORT_IMMEDIATE_INSTR: {
      byte* addr =
          reinterpret_cast<byte*>(*reinterpret_cast<int32_t*>(data + 1));
      AppendToBuffer("%s rax,%s", idesc.mnem, NameOfAddress(addr));
      data += 5;
      break;
    }

    case NO_INSTR:
      processed = false;
      break;

    default:
      UNIMPLEMENTED();  // This type is not implemented.
  }

  // The first byte didn't match any of the simple opcodes, so we
  // need to do special processing on it.
  if (!processed) {
    switch (*data) {
      case 0xC2:
        AppendToBuffer("ret 0x%x", *reinterpret_cast<uint16_t*>(data + 1));
        data += 3;
        break;

      case 0x69:  // fall through
      case 0x6B: {
        int mod, regop, rm;
        get_modrm(*(data + 1), &mod, &regop, &rm);
        int32_t imm = *data == 0x6B ? *(data + 2)
            : *reinterpret_cast<int32_t*>(data + 2);
        AppendToBuffer("imul%c %s,%s,0x%x",
                       operand_size_code(),
                       NameOfCPURegister(regop),
                       NameOfCPURegister(rm), imm);
        data += 2 + (*data == 0x6B ? 1 : 4);
        break;
      }

      case 0x81:  // fall through
      case 0x83:  // 0x81 with sign extension bit set
        data += PrintImmediateOp(data);
        break;

      case 0x0F:
        data += TwoByteOpcodeInstruction(data);
        break;

      case 0x8F: {
        data++;
        int mod, regop, rm;
        get_modrm(*data, &mod, &regop, &rm);
        if (regop == 0) {
          AppendToBuffer("pop ");
          data += PrintRightOperand(data);
        }
      }
        break;

      case 0xFF: {
        data++;
        int mod, regop, rm;
        get_modrm(*data, &mod, &regop, &rm);
        const char* mnem = NULL;
        switch (regop) {
          case 0:
            mnem = "inc";
            break;
          case 1:
            mnem = "dec";
            break;
          case 2:
            mnem = "call";
            break;
          case 4:
            mnem = "jmp";
            break;
          case 6:
            mnem = "push";
            break;
          default:
            mnem = "???";
        }
        AppendToBuffer(((regop <= 1) ? "%s%c " : "%s "),
                       mnem,
                       operand_size_code());
        data += PrintRightOperand(data);
      }
        break;

      case 0xC7:  // imm32, fall through
      case 0xC6:  // imm8
      {
        bool is_byte = *data == 0xC6;
        data++;
        if (is_byte) {
          AppendToBuffer("movb ");
          data += PrintRightByteOperand(data);
          int32_t imm = *data;
          AppendToBuffer(",0x%x", imm);
          data++;
        } else {
          AppendToBuffer("mov%c ", operand_size_code());
          data += PrintRightOperand(data);
          if (operand_size() == OPERAND_WORD_SIZE) {
            int16_t imm = *reinterpret_cast<int16_t*>(data);
            AppendToBuffer(",0x%x", imm);
            data += 2;
          } else {
            int32_t imm = *reinterpret_cast<int32_t*>(data);
            AppendToBuffer(",0x%x", imm);
            data += 4;
          }
        }
      }
        break;

      case 0x80: {
        data++;
        AppendToBuffer("cmpb ");
        data += PrintRightByteOperand(data);
        int32_t imm = *data;
        AppendToBuffer(",0x%x", imm);
        data++;
      }
        break;

      case 0x88:  // 8bit, fall through
      case 0x89:  // 32bit
      {
        bool is_byte = *data == 0x88;
        int mod, regop, rm;
        data++;
        get_modrm(*data, &mod, &regop, &rm);
        if (is_byte) {
          AppendToBuffer("movb ");
          data += PrintRightByteOperand(data);
          AppendToBuffer(",%s", NameOfByteCPURegister(regop));
        } else {
          AppendToBuffer("mov%c ", operand_size_code());
          data += PrintRightOperand(data);
          AppendToBuffer(",%s", NameOfCPURegister(regop));
        }
      }
        break;

      case 0x90:
      case 0x91:
      case 0x92:
      case 0x93:
      case 0x94:
      case 0x95:
      case 0x96:
      case 0x97: {
        int reg = (*data & 0x7) | (rex_b() ? 8 : 0);
        if (reg == 0) {
          AppendToBuffer("nop");  // Common name for xchg rax,rax.
        } else {
          AppendToBuffer("xchg%c rax,%s",
                         operand_size_code(),
                         NameOfCPURegister(reg));
        }
        data++;
      }
        break;
      case 0xB0:
      case 0xB1:
      case 0xB2:
      case 0xB3:
      case 0xB4:
      case 0xB5:
      case 0xB6:
      case 0xB7:
      case 0xB8:
      case 0xB9:
      case 0xBA:
      case 0xBB:
      case 0xBC:
      case 0xBD:
      case 0xBE:
      case 0xBF: {
        // mov reg8,imm8 or mov reg32,imm32
        byte opcode = *data;
        data++;
        bool is_32bit = (opcode >= 0xB8);
        int reg = (opcode & 0x7) | (rex_b() ? 8 : 0);
        if (is_32bit) {
          AppendToBuffer("mov%c %s,",
                         operand_size_code(),
                         NameOfCPURegister(reg));
          data += PrintImmediate(data, OPERAND_DOUBLEWORD_SIZE);
        } else {
          AppendToBuffer("movb %s,",
                         NameOfByteCPURegister(reg));
          data += PrintImmediate(data, OPERAND_BYTE_SIZE);
        }
        break;
      }
      case 0xFE: {
        data++;
        int mod, regop, rm;
        get_modrm(*data, &mod, &regop, &rm);
        if (regop == 1) {
          AppendToBuffer("decb ");
          data += PrintRightByteOperand(data);
        } else {
          UnimplementedInstruction();
        }
        break;
      }
      case 0x68:
        AppendToBuffer("push 0x%x", *reinterpret_cast<int32_t*>(data + 1));
        data += 5;
        break;

      case 0x6A:
        AppendToBuffer("push 0x%x", *reinterpret_cast<int8_t*>(data + 1));
        data += 2;
        break;

      case 0xA1:  // Fall through.
      case 0xA3:
        switch (operand_size()) {
          case OPERAND_DOUBLEWORD_SIZE: {
            const char* memory_location = NameOfAddress(
                reinterpret_cast<byte*>(
                    *reinterpret_cast<int32_t*>(data + 1)));
            if (*data == 0xA1) {  // Opcode 0xA1
              AppendToBuffer("movzxlq rax,(%s)", memory_location);
            } else {  // Opcode 0xA3
              AppendToBuffer("movzxlq (%s),rax", memory_location);
            }
            data += 5;
            break;
          }
          case OPERAND_QUADWORD_SIZE: {
            // New x64 instruction mov rax,(imm_64).
            const char* memory_location = NameOfAddress(
                *reinterpret_cast<byte**>(data + 1));
            if (*data == 0xA1) {  // Opcode 0xA1
              AppendToBuffer("movq rax,(%s)", memory_location);
            } else {  // Opcode 0xA3
              AppendToBuffer("movq (%s),rax", memory_location);
            }
            data += 9;
            break;
          }
          default:
            UnimplementedInstruction();
            data += 2;
        }
        break;

      case 0xA8:
        AppendToBuffer("test al,0x%x", *reinterpret_cast<uint8_t*>(data + 1));
        data += 2;
        break;

      case 0xA9: {
        int64_t value = 0;
        switch (operand_size()) {
          case OPERAND_WORD_SIZE:
            value = *reinterpret_cast<uint16_t*>(data + 1);
            data += 3;
            break;
          case OPERAND_DOUBLEWORD_SIZE:
            value = *reinterpret_cast<uint32_t*>(data + 1);
            data += 5;
            break;
          case OPERAND_QUADWORD_SIZE:
            value = *reinterpret_cast<int32_t*>(data + 1);
            data += 5;
            break;
          default:
            UNREACHABLE();
        }
        AppendToBuffer("test%c rax,0x%" V8_PTR_PREFIX "x",
                       operand_size_code(),
                       value);
        break;
      }
      case 0xD1:  // fall through
      case 0xD3:  // fall through
      case 0xC1:
        data += ShiftInstruction(data);
        break;
      case 0xD0:  // fall through
      case 0xD2:  // fall through
      case 0xC0:
        byte_size_operand_ = true;
        data += ShiftInstruction(data);
        break;

      case 0xD9:  // fall through
      case 0xDA:  // fall through
      case 0xDB:  // fall through
      case 0xDC:  // fall through
      case 0xDD:  // fall through
      case 0xDE:  // fall through
      case 0xDF:
        data += FPUInstruction(data);
        break;

      case 0xEB:
        data += JumpShort(data);
        break;

      case 0xF6:
        byte_size_operand_ = true;  // fall through
      case 0xF7:
        data += F6F7Instruction(data);
        break;

      case 0x3C:
        AppendToBuffer("cmp al,0x%x", *reinterpret_cast<int8_t*>(data + 1));
        data +=2;
        break;

      default:
        UnimplementedInstruction();
        data += 1;
    }
  }  // !processed

  if (tmp_buffer_pos_ < sizeof tmp_buffer_) {
    tmp_buffer_[tmp_buffer_pos_] = '\0';
  }

  int instr_len = static_cast<int>(data - instr);
  ASSERT(instr_len > 0);  // Ensure progress.

  int outp = 0;
  // Instruction bytes.
  for (byte* bp = instr; bp < data; bp++) {
    outp += v8::internal::OS::SNPrintF(out_buffer + outp, "%02x", *bp);
  }
  for (int i = 6 - instr_len; i >= 0; i--) {
    outp += v8::internal::OS::SNPrintF(out_buffer + outp, "  ");
  }

  outp += v8::internal::OS::SNPrintF(out_buffer + outp, " %s",
                                     tmp_buffer_.start());
  return instr_len;
}


//------------------------------------------------------------------------------


static const char* cpu_regs[16] = {
  "rax", "rcx", "rdx", "rbx", "rsp", "rbp", "rsi", "rdi",
  "r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15"
};


static const char* byte_cpu_regs[16] = {
  "al", "cl", "dl", "bl", "spl", "bpl", "sil", "dil",
  "r8l", "r9l", "r10l", "r11l", "r12l", "r13l", "r14l", "r15l"
};


static const char* xmm_regs[16] = {
  "xmm0", "xmm1", "xmm2", "xmm3", "xmm4", "xmm5", "xmm6", "xmm7",
  "xmm8", "xmm9", "xmm10", "xmm11", "xmm12", "xmm13", "xmm14", "xmm15"
};


const char* NameConverter::NameOfAddress(byte* addr) const {
  v8::internal::OS::SNPrintF(tmp_buffer_, "%p", addr);
  return tmp_buffer_.start();
}


const char* NameConverter::NameOfConstant(byte* addr) const {
  return NameOfAddress(addr);
}


const char* NameConverter::NameOfCPURegister(int reg) const {
  if (0 <= reg && reg < 16)
    return cpu_regs[reg];
  return "noreg";
}


const char* NameConverter::NameOfByteCPURegister(int reg) const {
  if (0 <= reg && reg < 16)
    return byte_cpu_regs[reg];
  return "noreg";
}


const char* NameConverter::NameOfXMMRegister(int reg) const {
  if (0 <= reg && reg < 16)
    return xmm_regs[reg];
  return "noxmmreg";
}


const char* NameConverter::NameInCode(byte* addr) const {
  // X64 does not embed debug strings at the moment.
  UNREACHABLE();
  return "";
}


//------------------------------------------------------------------------------

Disassembler::Disassembler(const NameConverter& converter)
    : converter_(converter) { }

Disassembler::~Disassembler() { }


int Disassembler::InstructionDecode(v8::internal::Vector<char> buffer,
                                    byte* instruction) {
  DisassemblerX64 d(converter_, CONTINUE_ON_UNIMPLEMENTED_OPCODE);
  return d.InstructionDecode(buffer, instruction);
}


// The X64 assembler does not use constant pools.
int Disassembler::ConstantPoolSizeAt(byte* instruction) {
  return -1;
}


void Disassembler::Disassemble(FILE* f, byte* begin, byte* end) {
  NameConverter converter;
  Disassembler d(converter);
  for (byte* pc = begin; pc < end;) {
    v8::internal::EmbeddedVector<char, 128> buffer;
    buffer[0] = '\0';
    byte* prev_pc = pc;
    pc += d.InstructionDecode(buffer, pc);
    fprintf(f, "%p", prev_pc);
    fprintf(f, "    ");

    for (byte* bp = prev_pc; bp < pc; bp++) {
      fprintf(f, "%02x", *bp);
    }
    for (int i = 6 - static_cast<int>(pc - prev_pc); i >= 0; i--) {
      fprintf(f, "  ");
    }
    fprintf(f, "  %s\n", buffer.start());
  }
}

}  // namespace disasm

#endif  // V8_TARGET_ARCH_X64
