/*
 * Copyright (C) 2023 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "disassembler_riscv64.h"

#include "android-base/logging.h"
#include "android-base/stringprintf.h"

#include "base/bit_utils.h"
#include "base/casts.h"

using android::base::StringPrintf;

namespace art {
namespace riscv64 {

class DisassemblerRiscv64::Printer {
 public:
  Printer(DisassemblerRiscv64* disassembler, std::ostream& os)
      : disassembler_(disassembler), os_(os) {}

  void Dump32(const uint8_t* insn);
  void Dump16(const uint8_t* insn);
  void Dump2Byte(const uint8_t* data);
  void DumpByte(const uint8_t* data);

 private:
  // This enumeration should mirror the declarations in runtime/arch/riscv64/registers_riscv64.h.
  // We do not include that file to avoid a dependency on libart.
  enum {
    Zero = 0,
    RA = 1,
    FP  = 8,
    TR  = 9,
  };

  enum class MemAddressMode : uint32_t {
    kUnitStride = 0b00,
    kIndexedUnordered = 0b01,
    kStrided = 0b10,
    kIndexedOrdered = 0b11,
  };

  enum class Nf : uint32_t {
    k1 = 0b000,
    k2 = 0b001,
    k3 = 0b010,
    k4 = 0b011,
    k5 = 0b100,
    k6 = 0b101,
    k7 = 0b110,
    k8 = 0b111,
  };

  enum class VAIEncodings : uint32_t {
    kOpIVV = 0b000,
    kOpFVV = 0b001,
    kOpMVV = 0b010,
    kOpIVI = 0b011,
    kOpIVX = 0b100,
    kOpFVF = 0b101,
    kOpMVX = 0b110,
    kOpCFG = 0b111,
  };

  class ScopedNewLinePrinter {
    std::ostream& os_;

   public:
    explicit ScopedNewLinePrinter(std::ostream& os) : os_(os) {}
    ~ScopedNewLinePrinter() { os_ << '\n'; }
  };

  static const char* XRegName(uint32_t regno);
  static const char* FRegName(uint32_t regno);
  static const char* VRegName(uint32_t regno);
  static const char* RoundingModeName(uint32_t rm);

  // Regular instruction immediate utils

  static int32_t Decode32Imm12(uint32_t insn32) {
    uint32_t sign = (insn32 >> 31);
    uint32_t imm12 = (insn32 >> 20);
    return static_cast<int32_t>(imm12) - static_cast<int32_t>(sign << 12);  // Sign-extend.
  }

  static uint32_t Decode32UImm7(uint32_t insn32) { return (insn32 >> 25) & 0x7Fu; }

  static uint32_t Decode32UImm12(uint32_t insn32) { return (insn32 >> 20) & 0xFFFu; }

  static int32_t Decode32StoreOffset(uint32_t insn32) {
    uint32_t bit11 = insn32 >> 31;
    uint32_t bits5_11 = insn32 >> 25;
    uint32_t bits0_4 = (insn32 >> 7) & 0x1fu;
    uint32_t imm = (bits5_11 << 5) + bits0_4;
    return static_cast<int32_t>(imm) - static_cast<int32_t>(bit11 << 12);  // Sign-extend.
  }

  // Compressed instruction immediate utils

  // Extracts the offset from a compressed instruction
  // where `offset[5:3]` is in bits `[12:10]` and `offset[2|6]` is in bits `[6:5]`
  static uint32_t Decode16CMOffsetW(uint32_t insn16) {
    DCHECK(IsUint<16>(insn16));
    return BitFieldExtract(insn16, 5, 1) << 6 | BitFieldExtract(insn16, 10, 3) << 3 |
           BitFieldExtract(insn16, 6, 1) << 2;
  }

  // Extracts the offset from a compressed instruction
  // where `offset[5:3]` is in bits `[12:10]` and `offset[7:6]` is in bits `[6:5]`
  static uint32_t Decode16CMOffsetD(uint32_t insn16) {
    DCHECK(IsUint<16>(insn16));
    return BitFieldExtract(insn16, 5, 2) << 6 | BitFieldExtract(insn16, 10, 3) << 3;
  }

  // Re-orders raw immediatate into real value
  // where `imm[5:3]` is in bits `[5:3]` and `imm[8:6]` is in bits `[2:0]`
  static uint32_t Uimm6ToOffsetD16(uint32_t uimm6) {
    DCHECK(IsUint<6>(uimm6));
    return (BitFieldExtract(uimm6, 3, 3) << 3) | (BitFieldExtract(uimm6, 0, 3) << 6);
  }

  // Re-orders raw immediatate to form real value
  // where `imm[5:2]` is in bits `[5:2]` and `imm[7:6]` is in bits `[1:0]`
  static uint32_t Uimm6ToOffsetW16(uint32_t uimm6) {
    DCHECK(IsUint<6>(uimm6));
    return (BitFieldExtract(uimm6, 2, 4) << 2) | (BitFieldExtract(uimm6, 0, 2) << 6);
  }

  // Re-orders raw immediatate to form real value
  // where `imm[1]` is in bit `[0]` and `imm[0]` is in bit `[1]`
  static uint32_t Uimm2ToOffset10(uint32_t uimm2) {
    DCHECK(IsUint<2>(uimm2));
    return (uimm2 >> 1) | (uimm2 & 0x1u) << 1;
  }

  // Re-orders raw immediatate to form real value
  // where `imm[1]` is in bit `[0]` and `imm[0]` is `0`
  static uint32_t Uimm2ToOffset1(uint32_t uimm2) {
    DCHECK(IsUint<2>(uimm2));
    return (uimm2 & 0x1u) << 1;
  }

  template <size_t kWidth>
  static constexpr int32_t SignExtendBits(uint32_t bits) {
    static_assert(kWidth < BitSizeOf<uint32_t>());
    const uint32_t sign_bit = (bits >> kWidth) & 1u;
    return static_cast<int32_t>(bits) - static_cast<int32_t>(sign_bit << kWidth);
  }

  // Extracts the immediate from a compressed instruction
  // where `imm[5]` is in bit `[12]` and `imm[4:0]` is in bits `[6:2]`
  // and performs sign-extension if required
  template <typename T>
  static T Decode16Imm6(uint32_t insn16) {
    DCHECK(IsUint<16>(insn16));
    static_assert(std::is_integral_v<T>, "T must be integral");
    const T bits =
        BitFieldInsert(BitFieldExtract(insn16, 2, 5), BitFieldExtract(insn16, 12, 1), 5, 1);
    const T checked_bits = dchecked_integral_cast<T>(bits);
    if (std::is_unsigned_v<T>) {
      return checked_bits;
    }
    return SignExtendBits<6>(checked_bits);
  }

  // Regular instruction register utils

  static uint32_t GetRd(uint32_t insn32) { return (insn32 >> 7) & 0x1fu; }
  static uint32_t GetRs1(uint32_t insn32) { return (insn32 >> 15) & 0x1fu; }
  static uint32_t GetRs2(uint32_t insn32) { return (insn32 >> 20) & 0x1fu; }
  static uint32_t GetRs3(uint32_t insn32) { return insn32 >> 27; }
  static uint32_t GetRoundingMode(uint32_t insn32) { return (insn32 >> 12) & 7u; }

  // Compressed instruction register utils

  static uint32_t GetRs1Short16(uint32_t insn16) { return BitFieldExtract(insn16, 7, 3) + 8u; }
  static uint32_t GetRs2Short16(uint32_t insn16) { return BitFieldExtract(insn16, 2, 3) + 8u; }
  static uint32_t GetRs1_16(uint32_t insn16) { return BitFieldExtract(insn16, 7, 5); }
  static uint32_t GetRs2_16(uint32_t insn16) { return BitFieldExtract(insn16, 2, 5); }

  void PrintBranchOffset(int32_t offset);
  void PrintLoadStoreAddress(uint32_t rs1, int32_t offset);

  void Print32Lui(uint32_t insn32);
  void Print32Auipc(const uint8_t* insn, uint32_t insn32);
  void Print32Jal(const uint8_t* insn, uint32_t insn32);
  void Print32Jalr(const uint8_t* insn, uint32_t insn32);
  void Print32BCond(const uint8_t* insn, uint32_t insn32);
  void Print32Load(uint32_t insn32);
  void Print32Store(uint32_t insn32);
  void Print32FLoad(uint32_t insn32);
  void Print32FStore(uint32_t insn32);
  void Print32BinOpImm(uint32_t insn32);
  void Print32BinOp(uint32_t insn32);
  void Print32Atomic(uint32_t insn32);
  void Print32FpOp(uint32_t insn32);
  void Print32RVVOp(uint32_t insn32);
  void Print32FpFma(uint32_t insn32);
  void Print32Zicsr(uint32_t insn32);
  void Print32Fence(uint32_t insn32);

  void AppendVType(uint32_t zimm);
  static const char* DecodeRVVMemMnemonic(const uint32_t insn32,
                                          bool is_load,
                                          /*out*/ const char** rs2);

  DisassemblerRiscv64* const disassembler_;
  std::ostream& os_;
};

const char* DisassemblerRiscv64::Printer::XRegName(uint32_t regno) {
  static const char* const kXRegisterNames[] = {
      "zero",
      "ra",
      "sp",
      "gp",
      "tp",
      "t0",
      "t1",
      "t2",
      "fp",  // s0/fp
      "tr",  // s1/tr - ART thread register
      "a0",
      "a1",
      "a2",
      "a3",
      "a4",
      "a5",
      "a6",
      "a7",
      "s2",
      "s3",
      "s4",
      "s5",
      "s6",
      "s7",
      "s8",
      "s9",
      "s10",
      "s11",
      "t3",
      "t4",
      "t5",
      "t6",
  };
  static_assert(std::size(kXRegisterNames) == 32);
  DCHECK_LT(regno, 32u);
  return kXRegisterNames[regno];
}

const char* DisassemblerRiscv64::Printer::FRegName(uint32_t regno) {
  static const char* const kFRegisterNames[] = {
      "ft0",
      "ft1",
      "ft2",
      "ft3",
      "ft4",
      "ft5",
      "ft6",
      "ft7",
      "fs0",
      "fs1",
      "fa0",
      "fa1",
      "fa2",
      "fa3",
      "fa4",
      "fa5",
      "fa6",
      "fa7",
      "fs2",
      "fs3",
      "fs4",
      "fs5",
      "fs6",
      "fs7",
      "fs8",
      "fs9",
      "fs10",
      "fs11",
      "ft8",
      "ft9",
      "ft10",
      "ft11",
  };
  static_assert(std::size(kFRegisterNames) == 32);
  DCHECK_LT(regno, 32u);
  return kFRegisterNames[regno];
}

const char* DisassemblerRiscv64::Printer::VRegName(uint32_t regno) {
  static const char* const kVRegisterNames[] = {
      "V0",
      "V1",
      "V2",
      "V3",
      "V4",
      "V5",
      "V6",
      "V7",
      "V8",
      "V9",
      "V10",
      "V11",
      "V12",
      "V13",
      "V14",
      "V15",
      "V16",
      "V17",
      "V18",
      "V19",
      "V20",
      "V21",
      "V22",
      "V23",
      "V24",
      "V25",
      "V26",
      "V27",
      "V28",
      "V29",
      "V30",
      "V31",
  };
  static_assert(std::size(kVRegisterNames) == 32);
  DCHECK_LT(regno, 32u);
  return kVRegisterNames[regno];
}

const char* DisassemblerRiscv64::Printer::RoundingModeName(uint32_t rm) {
  // Note: We do not print the rounding mode for DYN.
  static const char* const kRoundingModeNames[] = {
      ".rne", ".rtz", ".rdn", ".rup", ".rmm", ".<reserved-rm>", ".<reserved-rm>", /*DYN*/ ""
  };
  static_assert(std::size(kRoundingModeNames) == 8);
  DCHECK_LT(rm, 8u);
  return kRoundingModeNames[rm];
}

void DisassemblerRiscv64::Printer::PrintBranchOffset(int32_t offset) {
  os_ << (offset >= 0 ? "+" : "") << offset;
}

void DisassemblerRiscv64::Printer::PrintLoadStoreAddress(uint32_t rs1, int32_t offset) {
  if (offset != 0) {
    os_ << StringPrintf("%d", offset);
  }
  os_ << "(" << XRegName(rs1) << ")";

  if (rs1 == TR && offset >= 0) {
    // Add entrypoint name.
    os_ << " ; ";
    disassembler_->GetDisassemblerOptions()->thread_offset_name_function_(
        os_, dchecked_integral_cast<uint32_t>(offset));
  }
}

void DisassemblerRiscv64::Printer::Print32Lui(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x37u);
  // TODO(riscv64): Should we also print the actual sign-extend value?
  os_ << StringPrintf("lui %s, %u", XRegName(GetRd(insn32)), insn32 >> 12);
}

void DisassemblerRiscv64::Printer::Print32Auipc([[maybe_unused]] const uint8_t* insn,
                                                uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x17u);
  // TODO(riscv64): Should we also print the calculated address?
  os_ << StringPrintf("auipc %s, %u", XRegName(GetRd(insn32)), insn32 >> 12);
}

void DisassemblerRiscv64::Printer::Print32Jal(const uint8_t* insn, uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x6fu);
  // Print an alias if available.
  uint32_t rd = GetRd(insn32);
  os_ << (rd == Zero ? "j " : "jal ");
  if (rd != Zero && rd != RA) {
    os_ << XRegName(rd) << ", ";
  }
  uint32_t bit20 = (insn32 >> 31);
  uint32_t bits1_10 = (insn32 >> 21) & 0x3ffu;
  uint32_t bit11 = (insn32 >> 20) & 1u;
  uint32_t bits12_19 = (insn32 >> 12) & 0xffu;
  uint32_t imm = (bits1_10 << 1) + (bit11 << 11) + (bits12_19 << 12) + (bit20 << 20);
  int32_t offset = static_cast<int32_t>(imm) - static_cast<int32_t>(bit20 << 21);  // Sign-extend.
  PrintBranchOffset(offset);
  os_ << " ; " << disassembler_->FormatInstructionPointer(insn + offset);

  // TODO(riscv64): When we implement shared thunks to reduce AOT slow-path code size,
  // check if this JAL lands at an entrypoint load from TR and, if so, print its name.
}

void DisassemblerRiscv64::Printer::Print32Jalr([[maybe_unused]] const uint8_t* insn,
                                               uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x67u);
  DCHECK_EQ((insn32 >> 12) & 7u, 0u);
  uint32_t rd = GetRd(insn32);
  uint32_t rs1 = GetRs1(insn32);
  int32_t imm12 = Decode32Imm12(insn32);
  // Print shorter macro instruction notation if available.
  if (rd == Zero && rs1 == RA && imm12 == 0) {
    os_ << "ret";
  } else if (rd == Zero && imm12 == 0) {
    os_ << "jr " << XRegName(rs1);
  } else if (rd == RA && imm12 == 0) {
    os_ << "jalr " << XRegName(rs1);
  } else {
    // TODO(riscv64): Should we also print the calculated address if the preceding
    // instruction is AUIPC? (We would need to record the previous instruction.)
    os_ << "jalr " << XRegName(rd) << ", ";
    // Use the same format as llvm-objdump: "rs1" if `imm12` is zero, otherwise "imm12(rs1)".
    if (imm12 == 0) {
      os_ << XRegName(rs1);
    } else {
      os_ << imm12 << "(" << XRegName(rs1) << ")";
    }
  }
}

void DisassemblerRiscv64::Printer::Print32BCond(const uint8_t* insn, uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x63u);
  static const char* const kOpcodes[] = {
      "beq", "bne", nullptr, nullptr, "blt", "bge", "bltu", "bgeu"
  };
  uint32_t funct3 = (insn32 >> 12) & 7u;
  const char* opcode = kOpcodes[funct3];
  if (opcode == nullptr) {
    os_ << "<unknown32>";
    return;
  }

  // Print shorter macro instruction notation if available.
  uint32_t rs1 = GetRs1(insn32);
  uint32_t rs2 = GetRs2(insn32);
  if (rs2 == Zero) {
    os_ << opcode << "z " << XRegName(rs1);
  } else if (rs1 == Zero && (funct3 == 4u || funct3 == 5u)) {
    // blt zero, rs2, offset ... bgtz rs2, offset
    // bge zero, rs2, offset ... blez rs2, offset
    os_ << (funct3 == 4u ? "bgtz " : "blez ") << XRegName(rs2);
  } else {
    os_ << opcode << " " << XRegName(rs1) << ", " << XRegName(rs2);
  }
  os_ << ", ";

  uint32_t bit12 = insn32 >> 31;
  uint32_t bits5_10 = (insn32 >> 25) & 0x3fu;
  uint32_t bits1_4 = (insn32 >> 8) & 0xfu;
  uint32_t bit11 = (insn32 >> 7) & 1u;
  uint32_t imm = (bit12 << 12) + (bit11 << 11) + (bits5_10 << 5) + (bits1_4 << 1);
  int32_t offset = static_cast<int32_t>(imm) - static_cast<int32_t>(bit12 << 13);  // Sign-extend.
  PrintBranchOffset(offset);
  os_ << " ; " << disassembler_->FormatInstructionPointer(insn + offset);
}

void DisassemblerRiscv64::Printer::Print32Load(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x03u);
  static const char* const kOpcodes[] = {
      "lb", "lh", "lw", "ld", "lbu", "lhu", "lwu", nullptr
  };
  uint32_t funct3 = (insn32 >> 12) & 7u;
  const char* opcode = kOpcodes[funct3];
  if (opcode == nullptr) {
    os_ << "<unknown32>";
    return;
  }

  os_ << opcode << " " << XRegName(GetRd(insn32)) << ", ";
  PrintLoadStoreAddress(GetRs1(insn32), Decode32Imm12(insn32));

  // TODO(riscv64): If previous instruction is AUIPC for current `rs1` and we load
  // from the range specified by assembler options, print the loaded literal.
}

void DisassemblerRiscv64::Printer::Print32Store(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x23u);
  static const char* const kOpcodes[] = {
      "sb", "sh", "sw", "sd", nullptr, nullptr, nullptr, nullptr
  };
  uint32_t funct3 = (insn32 >> 12) & 7u;
  const char* opcode = kOpcodes[funct3];
  if (opcode == nullptr) {
    os_ << "<unknown32>";
    return;
  }

  os_ << opcode << " " << XRegName(GetRs2(insn32)) << ", ";
  PrintLoadStoreAddress(GetRs1(insn32), Decode32StoreOffset(insn32));
}

const char* DisassemblerRiscv64::Printer::DecodeRVVMemMnemonic(const uint32_t insn32,
                                                               bool is_load,
                                                               /*out*/ const char** rs2) {
  const uint32_t width_index = (insn32 >> 12) & 3u;
  DCHECK_EQ(width_index != 0u, (insn32 & 0x4000u) != 0u);
  const uint32_t imm7 = Decode32UImm7(insn32);
  const enum Nf nf = static_cast<enum Nf>((imm7 >> 4) & 0x7u);
  const enum MemAddressMode mop = static_cast<enum MemAddressMode>((imm7 >> 1) & 0x3u);
  const uint32_t mew = (insn32 >> 28) & 1u;

  if (mew == 1u) {
    // 7.3. Vector Load/Store Width Encoding
    // The mew bit (inst[28]) when set is expected to be used to encode
    // expanded memory sizes of 128 bits and above,
    // but these encodings are currently reserved.
    return nullptr;
  }

  switch (mop) {
    case MemAddressMode::kUnitStride: {
      const uint32_t umop = GetRs2(insn32);
      switch (umop) {
        case 0b00000:  // Vector Unit-Stride Load/Store
          static constexpr const char* kVUSMnemonics[8][4] = {
              {"e8.v", "e16.v", "e32.v", "e64.v"},
              {"seg2e8.v", "seg2e16.v", "seg2e32.v", "seg2e64.v"},
              {"seg3e8.v", "seg3e16.v", "seg3e32.v", "seg3e64.v"},
              {"seg4e8.v", "seg4e16.v", "seg4e32.v", "seg4e64.v"},
              {"seg5e8.v", "seg5e16.v", "seg5e32.v", "seg5e64.v"},
              {"seg6e8.v", "seg6e16.v", "seg6e32.v", "seg6e64.v"},
              {"seg7e8.v", "seg7e16.v", "seg7e32.v", "seg7e64.v"},
              {"seg8e8.v", "seg8e16.v", "seg8e32.v", "seg8e64.v"},
          };
          return kVUSMnemonics[enum_cast<uint32_t>(nf)][width_index];
        case 0b01000: {  // Vector Whole Register Load/Store
          if (is_load) {
            static constexpr const char* kVWRLMnemonics[8][4] = {
                {"1re8.v", "1re16.v", "1re32.v", "1re64.v"},
                {"2re8.v", "2re16.v", "2re32.v", "2re64.v"},
                {nullptr, nullptr, nullptr, nullptr},
                {"4re8.v", "4re16.v", "4re32.v", "4re64.v"},
                {nullptr, nullptr, nullptr, nullptr},
                {nullptr, nullptr, nullptr, nullptr},
                {nullptr, nullptr, nullptr, nullptr},
                {"8re8.v", "8re16.v", "8re32.v", "8re64.v"},
            };
            return kVWRLMnemonics[enum_cast<uint32_t>(nf)][width_index];
          } else {
            if (width_index != 0) {
              return nullptr;
            }
            static constexpr const char* kVWRSMnemonics[8] = {
                "1r", "2r", nullptr, "4r", nullptr, nullptr, nullptr, "8r"
            };
            return kVWRSMnemonics[enum_cast<uint32_t>(nf)];
          }
        }
        case 0b01011:  // Vector Unit-Stride Mask Load/Store
          if (nf == Nf::k1 && width_index == 0 && (imm7 & 1u) == 1u) {
            return "m.v";
          } else {
            return nullptr;
          }
        case 0b10000:  // Vector Unit-Stride Fault-Only-First Load
          static constexpr const char* kVUSFFLMnemonics[8][4] = {
              {"e8ff.v", "e16ff.v", "e32ff.v", "e64ff.v"},
              {"seg2e8ff.v", "seg2e16ff.v", "seg2e32ff.v", "seg2e64ff.v"},
              {"seg3e8ff.v", "seg3e16ff.v", "seg3e32ff.v", "seg3e64ff.v"},
              {"seg4e8ff.v", "seg4e16ff.v", "seg4e32ff.v", "seg4e64ff.v"},
              {"seg5e8ff.v", "seg5e16ff.v", "seg5e32ff.v", "seg5e64ff.v"},
              {"seg6e8ff.v", "seg6e16ff.v", "seg6e32ff.v", "seg6e64ff.v"},
              {"seg7e8ff.v", "seg7e16ff.v", "seg7e32ff.v", "seg7e64ff.v"},
              {"seg8e8ff.v", "seg8e16ff.v", "seg8e32ff.v", "seg8e64ff.v"},
          };
          return is_load ? kVUSFFLMnemonics[enum_cast<uint32_t>(nf)][width_index] : nullptr;
        default:  // Unknown
          return nullptr;
      }
    }
    case MemAddressMode::kIndexedUnordered: {
      static constexpr const char* kVIUMnemonics[8][4] = {
          {"uxei8.v", "uxei16.v", "uxei32.v", "uxei64.v"},
          {"uxseg2ei8.v", "uxseg2ei16.v", "uxseg2ei32.v", "uxseg2ei64.v"},
          {"uxseg3ei8.v", "uxseg3ei16.v", "uxseg3ei32.v", "uxseg3ei64.v"},
          {"uxseg4ei8.v", "uxseg4ei16.v", "uxseg4ei32.v", "uxseg4ei64.v"},
          {"uxseg5ei8.v", "uxseg5ei16.v", "uxseg5ei32.v", "uxseg5ei64.v"},
          {"uxseg6ei8.v", "uxseg6ei16.v", "uxseg6ei32.v", "uxseg6ei64.v"},
          {"uxseg7ei8.v", "uxseg7ei16.v", "uxseg7ei32.v", "uxseg7ei64.v"},
          {"uxseg8ei8.v", "uxseg8ei16.v", "uxseg8ei32.v", "uxseg8ei64.v"},
      };
      *rs2 = VRegName(GetRs2(insn32));
      return kVIUMnemonics[enum_cast<uint32_t>(nf)][width_index];
    }
    case MemAddressMode::kStrided: {
      static constexpr const char* kVSMnemonics[8][4] = {
          {"se8.v", "se16.v", "se32.v", "se64.v"},
          {"sseg2e8.v", "sseg2e16.v", "sseg2e32.v", "sseg2e64.v"},
          {"sseg3e8.v", "sseg3e16.v", "sseg3e32.v", "sseg3e64.v"},
          {"sseg4e8.v", "sseg4e16.v", "sseg4e32.v", "sseg4e64.v"},
          {"sseg5e8.v", "sseg5e16.v", "sseg5e32.v", "sseg5e64.v"},
          {"sseg6e8.v", "sseg6e16.v", "sseg6e32.v", "sseg6e64.v"},
          {"sseg7e8.v", "sseg7e16.v", "sseg7e32.v", "sseg7e64.v"},
          {"sseg8e8.v", "sseg8e16.v", "sseg8e32.v", "sseg8e64.v"},
      };
      *rs2 = XRegName(GetRs2(insn32));
      return kVSMnemonics[enum_cast<uint32_t>(nf)][width_index];
    }
    case MemAddressMode::kIndexedOrdered: {
      static constexpr const char* kVIOMnemonics[8][4] = {
          {"oxei8.v", "oxei16.v", "oxei32.v", "oxei64.v"},
          {"oxseg2ei8.v", "oxseg2ei16.v", "oxseg2ei32.v", "oxseg2ei64.v"},
          {"oxseg3ei8.v", "oxseg3ei16.v", "oxseg3ei32.v", "oxseg3ei64.v"},
          {"oxseg4ei8.v", "oxseg4ei16.v", "oxseg4ei32.v", "oxseg4ei64.v"},
          {"oxseg5ei8.v", "oxseg5ei16.v", "oxseg5ei32.v", "oxseg5ei64.v"},
          {"oxseg6ei8.v", "oxseg6ei16.v", "oxseg6ei32.v", "oxseg6ei64.v"},
          {"oxseg7ei8.v", "oxseg7ei16.v", "oxseg7ei32.v", "oxseg7ei64.v"},
          {"oxseg8ei8.v", "oxseg8ei16.v", "oxseg8ei32.v", "oxseg8ei64.v"},
      };
      *rs2 = VRegName(GetRs2(insn32));
      return kVIOMnemonics[enum_cast<uint32_t>(nf)][width_index];
    }
  }
}

static constexpr const char* kFpMemMnemonics[] = {
    nullptr, "h", "w", "d", "q", nullptr, nullptr, nullptr
};

void DisassemblerRiscv64::Printer::Print32FLoad(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x07u);
  int32_t offset = 0;
  const char* rd = nullptr;
  const char* rs2 = nullptr;
  const char* vm = "";
  const uint32_t funct3 = (insn32 >> 12) & 7u;
  const char* mnemonic = kFpMemMnemonics[funct3];
  const char* prefix = "f";
  if (mnemonic == nullptr) {
    // Vector Loads
    prefix = "v";
    mnemonic = DecodeRVVMemMnemonic(insn32, /*is_load=*/true, &rs2);
    rd = VRegName(GetRd(insn32));

    if ((Decode32UImm7(insn32) & 0x1U) == 0) {
      vm = ", v0.t";
    }
  } else {
    rd = FRegName(GetRd(insn32));
    offset = Decode32Imm12(insn32);
  }

  if (mnemonic == nullptr) {
    os_ << "<unknown32>";
    return;
  }

  os_ << prefix << "l" << mnemonic << " " << rd << ", ";
  PrintLoadStoreAddress(GetRs1(insn32), offset);

  if (rs2) {
    os_ << ", " << rs2;
  }

  os_ << vm;

  // TODO(riscv64): If previous instruction is AUIPC for current `rs1` and we load
  // from the range specified by assembler options, print the loaded literal.
}

void DisassemblerRiscv64::Printer::Print32FStore(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x27u);
  uint32_t funct3 = (insn32 >> 12) & 3u;
  const char* prefix = "f";
  const char* mnemonic = kFpMemMnemonics[funct3];

  if (mnemonic == nullptr) {
    // Vector Stores
    const char* rs2 = nullptr;
    prefix = "v";
    mnemonic = DecodeRVVMemMnemonic(insn32, /*is_load=*/false, &rs2);

    if (mnemonic == nullptr) {
      os_ << "<unknown32>";
      return;
    }

    os_ << prefix << "s" << mnemonic << " " << VRegName(GetRd(insn32)) << ", ";
    PrintLoadStoreAddress(GetRs1(insn32), 0);

    if (rs2) {
      os_ << ", " << rs2;
    }

    if ((Decode32UImm7(insn32) & 0x1U) == 0) {
      os_ << ", v0.t";
    }
  } else {
    os_ << prefix << "s" << mnemonic << " " << FRegName(GetRs2(insn32)) << ", ";
    PrintLoadStoreAddress(GetRs1(insn32), Decode32StoreOffset(insn32));
  }
}

void DisassemblerRiscv64::Printer::Print32BinOpImm(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x77u, 0x13u);  // Note: Bit 0x8 selects narrow binop.
  bool narrow = (insn32 & 0x8u) != 0u;
  uint32_t funct3 = (insn32 >> 12) & 7u;
  uint32_t funct6 = (insn32 >> 26) & 0x3Fu;
  uint32_t rd = GetRd(insn32);
  uint32_t rs1 = GetRs1(insn32);
  int32_t imm = Decode32Imm12(insn32);

  // Print shorter macro instruction notation if available.
  if (funct3 == /*ADDI*/ 0u && imm == 0u) {
    if (narrow) {
      os_ << "sextw " << XRegName(rd) << ", " << XRegName(rs1);
    } else if (rd == Zero && rs1 == Zero) {
      os_ << "nop";  // Only canonical nop. Non-Zero `rd == rs1` nops are printed as "mv".
    } else {
      os_ << "mv " << XRegName(rd) << ", " << XRegName(rs1);
    }
  } else if (!narrow && funct3 == /*XORI*/ 4u && imm == -1) {
    os_ << "not " << XRegName(rd) << ", " << XRegName(rs1);
  } else if (!narrow && funct3 == /*ANDI*/ 7u && imm == 0xff) {
    os_ << "zextb " << XRegName(rd) << ", " << XRegName(rs1);
  } else if (!narrow && funct3 == /*SLTIU*/ 3u && imm == 1) {
    os_ << "seqz " << XRegName(rd) << ", " << XRegName(rs1);
  } else if ((insn32 & 0xfc00707fu) == 0x0800101bu) {
    os_ << "slli.uw " << XRegName(rd) << ", " << XRegName(rs1) << ", " << (imm & 0x3fu);
  } else if ((imm ^ 0x600u) < 3u && funct3 == 1u) {
    static const char* const kBitOpcodes[] = { "clz", "ctz", "cpop" };
    os_ << kBitOpcodes[imm ^ 0x600u] << (narrow ? "w " : " ")
        << XRegName(rd) << ", " << XRegName(rs1);
  } else if ((imm ^ 0x600u) < (narrow ? 32 : 64) && funct3 == 5u) {
    os_ << "rori" << (narrow ? "w " : " ")
        << XRegName(rd) << ", " << XRegName(rs1) << ", " << (imm ^ 0x600u);
  } else if (imm == 0x287u && !narrow && funct3 == 5u) {
    os_ << "orc.b " << XRegName(rd) << ", " << XRegName(rs1);
  } else if (imm == 0x6b8u && !narrow && funct3 == 5u) {
    os_ << "rev8 " << XRegName(rd) << ", " << XRegName(rs1);
  } else if (!narrow && funct6 == 0x12u && (funct3 == /*BCLRI*/ 1u || funct3 == /*BEXTI*/ 5u)) {
    os_ << ((funct3 == /*BCLRI*/ 1u) ? "bclri" : "bexti");
  } else if (!narrow && funct6 == 0x1Au && funct3 == 0x1u /*BINVI*/) {
    os_ << "binvi";
  } else if (!narrow && funct6 == 0xAu && funct3 == 0x1u /*BSETI*/) {
    os_ << "bseti";
  } else {
    bool bad_high_bits = false;
    if (funct3 == /*SLLI*/ 1u || funct3 == /*SRLI/SRAI*/ 5u) {
      imm &= (narrow ? 0x1fu : 0x3fu);
      uint32_t high_bits = insn32 & (narrow ? 0xfe000000u : 0xfc000000u);
      if (high_bits == 0x40000000u && funct3 == /*SRAI*/ 5u) {
        os_ << "srai";
      } else {
        os_ << ((funct3 == /*SRLI*/ 5u) ? "srli" : "slli");
        bad_high_bits = (high_bits != 0u);
      }
    } else if (!narrow || funct3 == /*ADDI*/ 0u) {
      static const char* const kOpcodes[] = {
          "addi", nullptr, "slti", "sltiu", "xori", nullptr, "ori", "andi"
      };
      DCHECK(kOpcodes[funct3] != nullptr);
      os_ << kOpcodes[funct3];
    } else {
      os_ << "<unknown32>";  // There is no SLTIW/SLTIUW/XORIW/ORIW/ANDIW.
      return;
    }
    os_ << (narrow ? "w " : " ") << XRegName(rd) << ", " << XRegName(rs1) << ", " << imm;
    if (bad_high_bits) {
      os_ << " (invalid high bits)";
    }
  }
}

void DisassemblerRiscv64::Printer::Print32BinOp(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x77u, 0x33u);  // Note: Bit 0x8 selects narrow binop.
  bool narrow = (insn32 & 0x8u) != 0u;
  uint32_t funct3 = (insn32 >> 12) & 7u;
  uint32_t funct7 = (insn32 >> 25) & 0x7Fu;
  uint32_t rd = GetRd(insn32);
  uint32_t rs1 = GetRs1(insn32);
  uint32_t rs2 = GetRs2(insn32);

  // Print shorter macro instruction notation if available.
  if (funct7 == 0x20u && funct3 == /*SUB*/ 0u && rs1 == Zero) {
    os_ << (narrow ? "negw " : "neg ") << XRegName(rd) << ", " << XRegName(rs2);
  } else if (!narrow && funct3 == /*SLT*/ 2u && rs2 == Zero) {
    os_ << "sltz " << XRegName(rd) << ", " << XRegName(rs1);
  } else if (!narrow && funct3 == /*SLT*/ 2u && rs1 == Zero) {
    os_ << "sgtz " << XRegName(rd) << ", " << XRegName(rs2);
  } else if (!narrow && funct3 == /*SLTU*/ 3u && rs1 == Zero) {
    os_ << "snez " << XRegName(rd) << ", " << XRegName(rs2);
  } else if (narrow && funct7 == 4u && funct3 == /*ADD.UW*/ 0u && rs2 == Zero) {
    os_ << "zext.w " << XRegName(rd) << ", " << XRegName(rs1);
  } else if (!narrow && funct7 == 0x24u &&
             (funct3 == /*BCLR*/ 1u || funct3 == /*BEXT*/ 5u)) {
    os_ << ((funct3 == /*BCLR*/ 1u) ? "bclr" : "bext");
  } else if (!narrow && funct7 == 0x34u && funct3 == /*BINV*/ 1u) {
    os_ << "binv";
  } else if (!narrow && funct7 == 0x14u && funct3 == /*BSET*/ 1u) {
    os_ << "bset";
  } else {
    bool bad_high_bits = false;
    if (funct7 == 0x20u && (funct3 == /*SUB*/ 0u || funct3 == /*SRA*/ 5u)) {
      os_ << ((funct3 == /*SUB*/ 0u) ? "sub" : "sra");
    } else if (funct7 == 1u &&
               (!narrow || (funct3 == /*MUL*/ 0u || funct3 >= /*DIV/DIVU/REM/REMU*/ 4u))) {
      static const char* const kOpcodes[] = {
          "mul", "mulh", "mulhsu", "mulhu", "div", "divu", "rem", "remu"
      };
      os_ << kOpcodes[funct3];
    } else if (funct7 == 4u && narrow && funct3 == /*ADD.UW*/ 0u) {
      os_ << "add.u";  // "w" is added below.
    } else if (funct7 == 0x10u && (funct3 & 1u) == 0u && funct3 != 0u) {
      static const char* const kZbaOpcodes[] = { nullptr, "sh1add", "sh2add", "sh3add" };
      DCHECK(kZbaOpcodes[funct3 >> 1] != nullptr);
      os_ << kZbaOpcodes[funct3 >> 1] << (narrow ? ".u" /* "w" is added below. */ : "");
    } else if (funct7 == 0x20u && !narrow && funct3 >= 4u && funct3 != 5u) {
      static const char* const kZbbNegOpcodes[] = { "xnor", nullptr, "orn", "andn" };
      DCHECK(kZbbNegOpcodes[funct3 - 4u] != nullptr);
      os_ << kZbbNegOpcodes[funct3 - 4u];
    } else if (funct7 == 0x5u && !narrow && funct3 >= 4u) {
      static const char* const kZbbMinMaxOpcodes[] = { "min", "minu", "max", "maxu" };
      DCHECK(kZbbMinMaxOpcodes[funct3 - 4u] != nullptr);
      os_ << kZbbMinMaxOpcodes[funct3 - 4u];
    } else if (funct7 == 0x30u && (funct3 == /*ROL*/ 1u || funct3 == /*ROL*/ 5u)) {
      os_ << (funct3 == /*ROL*/ 1u ? "rol" : "ror");
    } else if (!narrow || (funct3 == /*ADD*/ 0u || funct3 == /*SLL*/ 1u || funct3 == /*SRL*/ 5u)) {
      static const char* const kOpcodes[] = {
          "add", "sll", "slt", "sltu", "xor", "srl", "or", "and"
      };
      os_ << kOpcodes[funct3];
      bad_high_bits = funct7 != 0u;
    } else {
      DCHECK(narrow);
      os_ << "<unknown32>";  // Some of the above instructions do not have a narrow version.
      return;
    }
    os_ << (narrow ? "w " : " ") << XRegName(rd) << ", " << XRegName(rs1) << ", " << XRegName(rs2);
    if (bad_high_bits) {
      os_ << " (invalid high bits)";
    }
  }
}

void DisassemblerRiscv64::Printer::Print32Atomic(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x2fu);
  uint32_t funct3 = (insn32 >> 12) & 7u;
  uint32_t funct5 = (insn32 >> 27);
  if ((funct3 != 2u && funct3 != 3u) ||  // There are only 32-bit and 64-bit LR/SC/AMO*.
      (((funct5 & 3u) != 0u) && funct5 >= 4u)) {  // Only multiples of 4, or 1-3.
    os_ << "<unknown32>";
    return;
  }
  static const char* const kMul4Opcodes[] = {
      "amoadd", "amoxor", "amoor", "amoand", "amomin", "amomax", "amominu", "amomaxu"
  };
  static const char* const kOtherOpcodes[] = {
      nullptr, "amoswap", "lr", "sc"
  };
  const char* opcode = ((funct5 & 3u) == 0u) ? kMul4Opcodes[funct5 >> 2] : kOtherOpcodes[funct5];
  DCHECK(opcode != nullptr);
  uint32_t rd = GetRd(insn32);
  uint32_t rs1 = GetRs1(insn32);
  uint32_t rs2 = GetRs2(insn32);
  const char* type = (funct3 == 2u) ? ".w" : ".d";
  const char* aq = (((insn32 >> 26) & 1u) != 0u) ? ".aq" : "";
  const char* rl = (((insn32 >> 25) & 1u) != 0u) ? ".rl" : "";
  os_ << opcode << type << aq << rl << " " << XRegName(rd) << ", " << XRegName(rs1);
  if (funct5 == /*LR*/ 2u) {
    if (rs2 != 0u) {
      os_ << " (bad rs2)";
    }
  } else {
    os_ << ", " << XRegName(rs2);
  }
}

void DisassemblerRiscv64::Printer::Print32FpOp(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x53u);
  uint32_t rd = GetRd(insn32);
  uint32_t rs1 = GetRs1(insn32);
  uint32_t rs2 = GetRs2(insn32);  // Sometimes used to to differentiate opcodes.
  uint32_t rm = GetRoundingMode(insn32);  // Sometimes used to to differentiate opcodes.
  uint32_t funct7 = insn32 >> 25;
  const char* type = ((funct7 & 1u) != 0u) ? ".d" : ".s";
  if ((funct7 & 2u) != 0u) {
    os_ << "<unknown32>";  // Note: This includes the "H" and "Q" extensions.
    return;
  }
  switch (funct7 >> 2) {
    case 0u:
    case 1u:
    case 2u:
    case 3u: {
      static const char* const kOpcodes[] = { "fadd", "fsub", "fmul", "fdiv" };
      os_ << kOpcodes[funct7 >> 2] << type << RoundingModeName(rm) << " "
          << FRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2);
      return;
    }
    case 4u: {  // FSGN*
      // Print shorter macro instruction notation if available.
      static const char* const kOpcodes[] = { "fsgnj", "fsgnjn", "fsgnjx" };
      if (rm < std::size(kOpcodes)) {
        if (rs1 == rs2) {
          static const char* const kAltOpcodes[] = { "fmv", "fneg", "fabs" };
          static_assert(std::size(kOpcodes) == std::size(kAltOpcodes));
          os_ << kAltOpcodes[rm] << type << " " << FRegName(rd) << ", " << FRegName(rs1);
        } else {
          os_ << kOpcodes[rm] << type << " "
              << FRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2);
        }
        return;
      }
      break;
    }
    case 5u: {  // FMIN/FMAX
      static const char* const kOpcodes[] = { "fmin", "fmax" };
      if (rm < std::size(kOpcodes)) {
        os_ << kOpcodes[rm] << type << " "
            << FRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2);
        return;
      }
      break;
    }
    case 0x8u:  // FCVT between FP numbers.
      if ((rs2 ^ 1u) == (funct7 & 1u)) {
        os_ << ((rs2 != 0u) ? "fcvt.s.d" : "fcvt.d.s") << RoundingModeName(rm) << " "
            << FRegName(rd) << ", " << FRegName(rs1);
      }
      break;
    case 0xbu:
      if (rs2 == 0u) {
        os_ << "fsqrt" << type << RoundingModeName(rm) << " "
            << FRegName(rd) << ", " << FRegName(rs1);
        return;
      }
      break;
    case 0x14u: {  // FLE/FLT/FEQ
      static const char* const kOpcodes[] = { "fle", "flt", "feq" };
      if (rm < std::size(kOpcodes)) {
        os_ << kOpcodes[rm] << type << " "
            << XRegName(rd) << ", " << FRegName(rs1) << ", " << FRegName(rs2);
        return;
      }
      break;
    }
    case 0x18u: {  // FCVT from floating point numbers to integers
      static const char* const kIntTypes[] = { "w", "wu", "l", "lu" };
      if (rs2 < std::size(kIntTypes)) {
        os_ << "fcvt." << kIntTypes[rs2] << type << RoundingModeName(rm) << " "
            << XRegName(rd) << ", " << FRegName(rs1);
        return;
      }
      break;
    }
    case 0x1au: {  // FCVT from integers to floating point numbers
      static const char* const kIntTypes[] = { "w", "wu", "l", "lu" };
      if (rs2 < std::size(kIntTypes)) {
        os_ << "fcvt" << type << "." << kIntTypes[rs2] << RoundingModeName(rm) << " "
            << FRegName(rd) << ", " << XRegName(rs1);
        return;
      }
      break;
    }
    case 0x1cu:  // FMV from FPR to GPR, or FCLASS
      if (rs2 == 0u && rm == 0u) {
        os_ << (((funct7 & 1u) != 0u) ? "fmv.x.d " : "fmv.x.w ")
            << XRegName(rd) << ", " << FRegName(rs1);
        return;
      } else if (rs2 == 0u && rm == 1u) {
        os_ << "fclass" << type << " " << XRegName(rd) << ", " << FRegName(rs1);
        return;
      }
      break;
    case 0x1eu:  // FMV from GPR to FPR
      if (rs2 == 0u && rm == 0u) {
        os_ << (((funct7 & 1u) != 0u) ? "fmv.d.x " : "fmv.w.x ")
            << FRegName(rd) << ", " << XRegName(rs1);
        return;
      }
      break;
    default:
      break;
  }
  os_ << "<unknown32>";
}

void DisassemblerRiscv64::Printer::AppendVType(uint32_t vtype) {
  const uint32_t lmul_v = vtype & 0x7U;
  const uint32_t vsew_v = (vtype >> 3) & 0x7U;
  const uint32_t vta_v = (vtype >> 6) & 0x1U;
  const uint32_t vma_v = (vtype >> 7) & 0x1U;

  if ((vsew_v & 0x4U) == 0u) {
    if (lmul_v != 0b100) {
      static const char* const vsews[] = {"e8", "e16", "e32", "e64"};
      static const char* const lmuls[] = {
          "m1", "m2", "m4", "m8", nullptr, "mf8", "mf4", "mf2"
      };

      const char* vma = vma_v ? "ma" : "mu";
      const char* vta = vta_v ? "ta" : "tu";
      const char* vsew = vsews[vsew_v & 0x3u];
      const char* lmul = lmuls[lmul_v];

      os_ << vsew << ", " << lmul << ", " << vta << ", " << vma;
      return;
    }
  }

  os_ << StringPrintf("0x%08x", vtype) << "\t# incorrect VType literal";
}

static constexpr uint32_t VWXUNARY0 = 0b010000;
static constexpr uint32_t VRXUNARY0 = 0b010000;
static constexpr uint32_t VXUNARY0 = 0b010010;
static constexpr uint32_t VMUNARY0 = 0b010100;

static constexpr uint32_t VWFUNARY0 = 0b010000;
static constexpr uint32_t VRFUNARY0 = 0b010000;
static constexpr uint32_t VFUNARY0 = 0b010010;
static constexpr uint32_t VFUNARY1 = 0b010011;

static void MaybeSwapOperands(uint32_t funct6,
                              /*inout*/ const char*& rs1,
                              /*inout*/ const char*& rs2) {
  if ((0x28u <= funct6 && funct6 < 0x30u) || funct6 >= 0x3Cu) {
    std::swap(rs1, rs2);
  }
}

void DisassemblerRiscv64::Printer::Print32RVVOp(uint32_t insn32) {
  // TODO(riscv64): Print pseudo-instruction aliases when applicable.
  DCHECK_EQ(insn32 & 0x7fu, 0x57u);
  const enum VAIEncodings vai = static_cast<enum VAIEncodings>((insn32 >> 12) & 7u);
  const uint32_t funct7 = Decode32UImm7(insn32);
  const uint32_t funct6 = funct7 >> 1;
  const bool masked = (funct7 & 1) == 0;
  const char* vm = masked ? ", v0.t" : "";
  const char* opcode = nullptr;
  const char* rd = nullptr;
  const char* rs1 = nullptr;
  const char* rs2 = nullptr;

  switch (vai) {
    case VAIEncodings::kOpIVV: {
      static constexpr const char* kOPIVVOpcodes[64] = {
          "vadd.vv",      nullptr,       "vsub.vv",         nullptr,
          "vminu.vv",     "vmin.vv",     "vmaxu.vv",        "vmax.vv",
          nullptr,        "vand.vv",     "vor.vv",          "vxor.vv",
          "vrgather.vv",  nullptr,       "vrgatherei16.vv", nullptr,
          "vadc.vvm",     "vmadc.vvm",   "vsbc.vvm",        "vmsbc.vvm",
          nullptr,        nullptr,       nullptr,           "<vmerge/vmv>",
          "vmseq.vv",     "vmsne.vv",    "vmsltu.vv",       "vmslt.vv",
          "vmsleu.vv",    "vmsle.vv",    nullptr,           nullptr,
          "vsaddu.vv",    "vsadd.vv",    "vssubu.vv",       "vssub.vv",
          nullptr,        "vsll.vv",     nullptr,           "vsmul.vv",
          "vsrl.vv",      "vsra.vv",     "vssrl.vv",        "vssra.vv",
          "vnsrl.wv",     "vnsra.wv",    "vnclipu.wv",      "vnclip.wv",
          "vwredsumu.vs", "vwredsum.vs", nullptr,           nullptr,
          nullptr,        nullptr,       nullptr,           nullptr,
          nullptr,        nullptr,       nullptr,           nullptr,
          nullptr,        nullptr,       nullptr,           nullptr,
      };

      rs2 = VRegName(GetRs2(insn32));
      if (funct6 == 0b010111) {
        // vmerge/vmv
        if (masked) {
          opcode = "vmerge.vvm";
          vm = ", v0";
        } else if (GetRs2(insn32) == 0) {
          opcode = "vmv.v.v";
          rs2 = nullptr;
        } else {
          opcode = nullptr;
        }
      } else {
        opcode = kOPIVVOpcodes[funct6];
      }

      rd = VRegName(GetRd(insn32));
      rs1 = VRegName(GetRs1(insn32));
      break;
    }
    case VAIEncodings::kOpIVX: {
      static constexpr const char* kOPIVXOpcodes[64] = {
          "vadd.vx",     nullptr,     "vsub.vx",     "vrsub.vx",
          "vminu.vx",    "vmin.vx",   "vmaxu.vx",    "vmax.vx",
          nullptr,       "vand.vx",   "vor.vx",      "vxor.vx",
          "vrgather.vx", nullptr,     "vslideup.vx", "vslidedown.vx",
          "vadc.vxm",    "vmadc.vxm", "vsbc.vxm",    "vmsbc.vxm",
          nullptr,       nullptr,     nullptr,       "<vmerge/vmv>",
          "vmseq.vx",    "vmsne.vx",  "vmsltu.vx",   "vmslt.vx",
          "vmsleu.vx",   "vmsle.vx",  "vmsgtu.vx",   "vmsgt.vx",
          "vsaddu.vx",   "vsadd.vx",  "vssubu.vx",   "vssub.vx",
          nullptr,       "vsll.vx",   nullptr,       "vsmul.vx",
          "vsrl.vx",     "vsra.vx",   "vssrl.vx",    "vssra.vx",
          "vnsrl.wx",    "vnsra.wx",  "vnclipu.wx",  "vnclip.wx",
          nullptr,       nullptr,     nullptr,       nullptr,
          nullptr,       nullptr,     nullptr,       nullptr,
          nullptr,       nullptr,     nullptr,       nullptr,
          nullptr,       nullptr,     nullptr,       nullptr,
      };

      rs2 = VRegName(GetRs2(insn32));
      if (funct6 == 0b010111) {
        // vmerge/vmv
        if (masked) {
          opcode = "vmerge.vxm";
          vm = ", v0";
        } else if (GetRs2(insn32) == 0) {
          opcode = "vmv.v.x";
          rs2 = nullptr;
        } else {
          opcode = nullptr;
        }
      } else {
        opcode = kOPIVXOpcodes[funct6];
      }

      rd = VRegName(GetRd(insn32));
      rs1 = XRegName(GetRs1(insn32));
      break;
    }
    case VAIEncodings::kOpIVI: {
      static constexpr const char* kOPIVIOpcodes[64] = {
          "vadd.vi",     nullptr,     nullptr,       "vrsub.vi",
          nullptr,       nullptr,     nullptr,       nullptr,
          nullptr,       "vand.vi",   "vor.vi",      "vxor.vi",
          "vrgather.vi", nullptr,     "vslideup.vi", "vslidedown.vi",
          "vadc.vim",    "vmadc.vim", nullptr,       nullptr,
          nullptr,       nullptr,     nullptr,       "<vmerge/vmv>",
          "vmseq.vi",    "vmsne.vi",  nullptr,       nullptr,
          "vmsleu.vi",   "vmsle.vi",  "vmsgtu.vi",   "vmsgt.vi",
          "vsaddu.vi",   "vsadd.vi",  nullptr,       nullptr,
          nullptr,       "vsll.vi",   nullptr,       "<vmvNr.v>",
          "vsrl.vi",     "vsra.vi",   "vssrl.vi",    "vssra.vi",
          "vnsrl.wi",    "vnsra.wi",  "vnclipu.wi",  "vnclip.wi",
          nullptr,       nullptr,     nullptr,       nullptr,
          nullptr,       nullptr,     nullptr,       nullptr,
          nullptr,       nullptr,     nullptr,       nullptr,
          nullptr,       nullptr,     nullptr,       nullptr,
      };

      rs2 = VRegName(GetRs2(insn32));

      if (funct6 == 0b010111) {
        // vmerge/vmv
        if (masked) {
          opcode = "vmerge.vim";
          vm = ", v0";
        } else if (GetRs2(insn32) == 0) {
          opcode = "vmv.v.i";
          rs2 = nullptr;
        } else {
          opcode = nullptr;
        }
      } else if (funct6 == 0b100111) {
        uint32_t rs1V = GetRs1(insn32);
        static constexpr const char* kVmvnrOpcodes[8] = {
            "vmv1r.v", "vmv2r.v", nullptr, "vmv4r.v",
            nullptr,   nullptr,   nullptr, "vmv8r.v",
        };
        if (IsUint<3>(rs1V)) {
          opcode = kVmvnrOpcodes[rs1V];
        }
      } else {
        opcode = kOPIVIOpcodes[funct6];
      }

      rd = VRegName(GetRd(insn32));
      break;
    }
    case VAIEncodings::kOpMVV: {
      switch (funct6) {
        case VWXUNARY0: {
          static constexpr const char* kVWXUNARY0Opcodes[32] = {
              "vmv.x.s", nullptr,    nullptr, nullptr,
              nullptr,   nullptr,    nullptr, nullptr,
              nullptr,   nullptr,    nullptr, nullptr,
              nullptr,   nullptr,    nullptr, nullptr,
              "vcpop.m", "vfirst.m", nullptr, nullptr,
              nullptr,   nullptr,    nullptr, nullptr,
              nullptr,   nullptr,    nullptr, nullptr,
              nullptr,   nullptr,    nullptr, nullptr,
          };
          opcode = kVWXUNARY0Opcodes[GetRs1(insn32)];
          rd = XRegName(GetRd(insn32));
          rs2 = VRegName(GetRs2(insn32));
          break;
        }
        case VXUNARY0: {
          static constexpr const char* kVXUNARY0Opcodes[32] = {
              nullptr,     nullptr,     "vzext.vf8", "vsext.vf8",
              "vzext.vf4", "vsext.vf4", "vzext.vf2", "vsext.vf2",
              nullptr,     nullptr,     nullptr,     nullptr,
              nullptr,     nullptr,     nullptr,     nullptr,
              nullptr,     nullptr,     nullptr,     nullptr,
              nullptr,     nullptr,     nullptr,     nullptr,
              nullptr,     nullptr,     nullptr,     nullptr,
              nullptr,     nullptr,     nullptr,     nullptr,
          };
          opcode = kVXUNARY0Opcodes[GetRs1(insn32)];
          rd = VRegName(GetRd(insn32));
          rs2 = VRegName(GetRs2(insn32));
          break;
        }
        case VMUNARY0: {
          static constexpr const char* kVMUNARY0Opcodes[32] = {
              nullptr,   "vmsbf.m", "vmsof.m", "vmsif.m",
              nullptr,   nullptr,   nullptr,   nullptr,
              nullptr,   nullptr,   nullptr,   nullptr,
              nullptr,   nullptr,   nullptr,   nullptr,
              "viota.m", "vid.v",   nullptr,   nullptr,
              nullptr,   nullptr,   nullptr,   nullptr,
              nullptr,   nullptr,   nullptr,   nullptr,
              nullptr,   nullptr,   nullptr,   nullptr,
          };
          opcode = kVMUNARY0Opcodes[GetRs1(insn32)];
          rd = VRegName(GetRd(insn32));
          rs2 = VRegName(GetRs2(insn32));
          break;
        }
        default: {
          static constexpr const char* kOPMVVOpcodes[64] = {
              "vredsum.vs",  "vredand.vs", "vredor.vs",   "vredxor.vs",
              "vredminu.vs", "vredmin.vs", "vredmaxu.vs", "vredmax.vs",
              "vaaddu.vv",   "vaadd.vv",   "vasubu.vv",   "vasub.vv",
              nullptr,       nullptr,      nullptr,       nullptr,
              "<VWXUNARY0>", nullptr,      "<VXUNARY0>",  nullptr,
              "<VMUNARY0>",  nullptr,      nullptr,       "vcompress.vm",
              "vmandn.mm",   "vmand.mm",   "vmor.mm",     "vmxor.mm",
              "vmorn.mm",    "vmnand.mm",  "vmnor.mm",    "vmxnor.mm",
              "vdivu.vv",    "vdiv.vv",    "vremu.vv",    "vrem.vv",
              "vmulhu.vv",   "vmul.vv",    "vmulhsu.vv",  "vmulh.vv",
              nullptr,       "vmadd.vv",   nullptr,       "vnmsub.vv",
              nullptr,       "vmacc.vv",   nullptr,       "vnmsac.vv",
              "vwaddu.vv",   "vwadd.vv",   "vwsubu.vv",   "vwsub.vv",
              "vwaddu.wv",   "vwadd.wv",   "vwsubu.wv",   "vwsub.wv",
              "vwmulu.vv",   nullptr,      "vwmulsu.vv",  "vwmul.vv",
              "vwmaccu.vv", "vwmacc.vv",   nullptr,       "vwmaccsu.vv",
          };

          opcode = kOPMVVOpcodes[funct6];
          rd = VRegName(GetRd(insn32));
          rs1 = VRegName(GetRs1(insn32));
          rs2 = VRegName(GetRs2(insn32));

          if (0x17u <= funct6 && funct6 <= 0x1Fu) {
            if (masked) {
              // for vcompress.vm and *.mm encodings with vm=0 are reserved
              opcode = nullptr;
            }
          }

          MaybeSwapOperands(funct6, rs1, rs2);

          break;
        }
      }
      break;
    }
    case VAIEncodings::kOpMVX: {
      switch (funct6) {
        case VRXUNARY0: {
          opcode = GetRs2(insn32) == 0u ? "vmv.s.x" : nullptr;
          rd = VRegName(GetRd(insn32));
          rs1 = XRegName(GetRs1(insn32));
          break;
        }
        default: {
          static constexpr const char* kOPMVXOpcodes[64] = {
              nullptr,      nullptr,      nullptr,        nullptr,
              nullptr,      nullptr,      nullptr,        nullptr,
              "vaaddu.vx",  "vaadd.vx",   "vasubu.vx",    "vasub.vx",
              nullptr,       nullptr,     "vslide1up.vx", "vslide1down.vx",
              "<VRXUNARY0>", nullptr,     nullptr,        nullptr,
              nullptr,       nullptr,     nullptr,        nullptr,
              nullptr,       nullptr,     nullptr,        nullptr,
              nullptr,       nullptr,     nullptr,        nullptr,
              "vdivu.vx",    "vdiv.vx",   "vremu.vx",     "vrem.vx",
              "vmulhu.vx",   "vmul.vx",   "vmulhsu.vx",   "vmulh.vx",
              nullptr,       "vmadd.vx",  nullptr,        "vnmsub.vx",
              nullptr,       "vmacc.vx",  nullptr,        "vnmsac.vx",
              "vwaddu.vx",   "vwadd.vx",  "vwsubu.vx",    "vwsub.vx",
              "vwaddu.wv",   "vwadd.wv",  "vwsubu.wv",    "vwsub.wv",
              "vwmulu.vx",   nullptr,     "vwmulsu.vx",   "vwmul.vx",
              "vwmaccu.vx",  "vwmacc.vx", "vwmaccus.vx",  "vwmaccsu.vx",
          };
          opcode = kOPMVXOpcodes[funct6];
          rd = VRegName(GetRd(insn32));
          rs1 = XRegName(GetRs1(insn32));
          rs2 = VRegName(GetRs2(insn32));

          MaybeSwapOperands(funct6, rs1, rs2);

          break;
        }
      }
      break;
    }
    case VAIEncodings::kOpFVV: {
      switch (funct6) {
        case VWFUNARY0: {
          opcode = GetRs1(insn32) == 0u ? "vfmv.f.s" : nullptr;
          rd = XRegName(GetRd(insn32));
          rs2 = VRegName(GetRs2(insn32));
          break;
        }
        case VFUNARY0: {
          static constexpr const char* kVFUNARY0Opcodes[32] = {
              "vfcvt.xu.f.v",  "vfcvt.x.f.v",      "vfcvt.f.xu.v",      "vfcvt.f.x.v",
              nullptr,         nullptr,            "vfcvt.rtz.xu.f.v",  "vfcvt.rtz.x.f.v",
              "vfwcvt.xu.f.v", "vfwcvt.x.f.v",     "vfwcvt.f.xu.v",     "vfwcvt.f.x.v",
              "vfwcvt.f.f.v",  nullptr,            "vfwcvt.rtz.xu.f.v", "vfwcvt.rtz.x.f.v",
              "vfncvt.xu.f.w", "vfncvt.x.f.w",     "vfncvt.f.xu.w",     "vfncvt.f.x.w",
              "vfncvt.f.f.w",  "vfncvt.rod.f.f.w", "vfncvt.rtz.xu.f.w", "vfncvt.rtz.x.f.w",
              nullptr,         nullptr,            nullptr,             nullptr,
              nullptr,         nullptr,            nullptr,             nullptr,
          };
          opcode = kVFUNARY0Opcodes[GetRs1(insn32)];
          rd = VRegName(GetRd(insn32));
          rs2 = VRegName(GetRs2(insn32));
          break;
        }
        case VFUNARY1: {
          static constexpr const char* kVFUNARY1Opcodes[32] = {
              "vfsqrt.v",   nullptr,    nullptr, nullptr,
              "vfrsqrt7.v", "vfrec7.v", nullptr, nullptr,
              nullptr,      nullptr,    nullptr, nullptr,
              nullptr,      nullptr,    nullptr, nullptr,
              "vfclass.v",  nullptr,    nullptr, nullptr,
              nullptr,      nullptr,    nullptr, nullptr,
              nullptr,      nullptr,    nullptr, nullptr,
              nullptr,      nullptr,    nullptr, nullptr,
          };
          opcode = kVFUNARY1Opcodes[GetRs1(insn32)];
          rd = VRegName(GetRd(insn32));
          rs2 = VRegName(GetRs2(insn32));
          break;
        }
        default: {
          static constexpr const char* kOPFVVOpcodes[64] = {
              "vfadd.vv",    "vfredusum.vs",  "vfsub.vv",   "vfredosum.vs",
              "vfmin.vv",    "vfredmin.vs",   "vfmax.vv",   "vfredmax.vs",
              "vfsgnj.vv",   "vfsgnjn.vv",    "vfsgnjx.vv", nullptr,
              nullptr,       nullptr,         nullptr,      nullptr,
              "<VWFUNARY0>", nullptr,         "<VFUNARY0>", "<VFUNARY1>",
              nullptr,       nullptr,         nullptr,      nullptr,
              "vmfeq.vv",    "vmfle.vv",      nullptr,      "vmflt.vv",
              "vmfne.vv",    nullptr,         nullptr,      nullptr,
              "vfdiv.vv",    nullptr,         nullptr,      nullptr,
              "vfmul.vv",    nullptr,         nullptr,      nullptr,
              "vfmadd.vv",   "vfnmadd.vv",    "vfmsub.vv",  "vfnmsub.vv",
              "vfmacc.vv",   "vfnmacc.vv",    "vfmsac.vv",  "vfnmsac.vv",
              "vfwadd.vv",   "vfwredusum.vs", "vfwsub.vv",  "vfwredosum.vs",
              "vfwadd.wv",   nullptr,         "vfwsub.wv",  nullptr,
              "vfwmul.vv",   nullptr,         nullptr,      nullptr,
              "vfwmacc.vv",  "vfwnmacc.vv",   "vfwmsac.vv", "vfwnmsac.vv",
          };
          opcode = kOPFVVOpcodes[funct6];
          rd = VRegName(GetRd(insn32));
          rs1 = VRegName(GetRs1(insn32));
          rs2 = VRegName(GetRs2(insn32));

          MaybeSwapOperands(funct6, rs1, rs2);

          break;
        }
      }
      break;
    }
    case VAIEncodings::kOpFVF: {
      switch (funct6) {
        case VRFUNARY0: {
          opcode = GetRs2(insn32) == 0u ? "vfmv.s.f" : nullptr;
          rd = VRegName(GetRd(insn32));
          rs1 = FRegName(GetRs1(insn32));
          break;
        }
        default: {
          static constexpr const char* kOPFVFOpcodes[64] = {
              "vfadd.vf",    nullptr,       "vfsub.vf",      nullptr,
              "vfmin.vf",    nullptr,       "vfmax.vf",      nullptr,
              "vfsgnj.vf",   "vfsgnjn.vf",  "vfsgnjx.vf",    nullptr,
              nullptr,       nullptr,       "vfslide1up.vf", "vfslide1down.vf",
              "<VRFUNARY0>", nullptr,       nullptr,         nullptr,
              nullptr,       nullptr,       nullptr,         "<vfmerge.vfm/vfmv>",
              "vmfeq.vf",    "vmfle.vf",    nullptr,         "vmflt.vf",
              "vmfne.vf",    "vmfgt.vf",    nullptr,         "vmfge.vf",
              "vfdiv.vf",    "vfrdiv.vf",   nullptr,         nullptr,
              "vfmul.vf",    nullptr,       nullptr,         "vfrsub.vf",
              "vfmadd.vf",   "vfnmadd.vf",  "vfmsub.vf",     "vfnmsub.vf",
              "vfmacc.vf",   "vfnmacc.vf",  "vfmsac.vf",     "vfnmsac.vf",
              "vfwadd.vf",   nullptr,       "vfwsub.vf",     nullptr,
              "vfwadd.wf",   nullptr,       "vfwsub.wf",     nullptr,
              "vfwmul.vf",   nullptr,       nullptr,         nullptr,
              "vfwmacc.vf",  "vfwnmacc.vf", "vfwmsac.vf",    "vfwnmsac.vf",
          };

          rs2 = VRegName(GetRs2(insn32));
          if (funct6 == 0b010111) {
            // vfmerge/vfmv
            if (masked) {
              opcode = "vfmerge.vfm";
              vm = ", v0";
            } else if (GetRs2(insn32) == 0) {
              opcode = "vfmv.v.f";
              rs2 = nullptr;
            } else {
              opcode = nullptr;
            }
          } else {
            opcode = kOPFVFOpcodes[funct6];
          }

          rd = VRegName(GetRd(insn32));
          rs1 = FRegName(GetRs1(insn32));

          MaybeSwapOperands(funct6, rs1, rs2);

          break;
        }
      }
      break;
    }
    case VAIEncodings::kOpCFG: {  // vector ALU control instructions
      if ((insn32 >> 31) != 0u) {
        if (((insn32 >> 30) & 0x1U) != 0u) {  // vsetivli
          const uint32_t zimm = Decode32UImm12(insn32) & ~0xC00U;
          const uint32_t imm5 = GetRs1(insn32);
          os_ << "vsetivli " << XRegName(GetRd(insn32)) << ", " << StringPrintf("0x%08x", imm5)
              << ", ";
          AppendVType(zimm);
        } else {  // vsetvl
          os_ << "vsetvl " << XRegName(GetRd(insn32)) << ", " << XRegName(GetRs1(insn32)) << ", "
              << XRegName(GetRs2(insn32));
          if ((Decode32UImm7(insn32) & 0x40u) != 0u) {
            os_ << "\t# incorrect funct7 literal : "
                << StringPrintf("0x%08x", Decode32UImm7(insn32));
          }
        }
      } else {  // vsetvli
        const uint32_t zimm = Decode32UImm12(insn32) & ~0x800U;
        os_ << "vsetvli " << XRegName(GetRd(insn32)) << ", " << XRegName(GetRs1(insn32)) << ", ";
        AppendVType(zimm);
      }
      return;
    }
  }

  if (opcode == nullptr) {
    os_ << "<unknown32>";
    return;
  }

  os_ << opcode << " " << rd;

  if (rs2 != nullptr) {
    os_ << ", " << rs2;
  }

  if (rs1 != nullptr) {
    os_ << ", " << rs1;
  } else if (vai == VAIEncodings::kOpIVI) {
    os_ << StringPrintf(", 0x%08x", GetRs1(insn32));
  }

  os_ << vm;
}

void DisassemblerRiscv64::Printer::Print32FpFma(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x73u, 0x43u);  // Note: Bits 0xc select the FMA opcode.
  uint32_t funct2 = (insn32 >> 25) & 3u;
  if (funct2 >= 2u) {
    os_ << "<unknown32>";  // Note: This includes the "H" and "Q" extensions.
    return;
  }
  static const char* const kOpcodes[] = { "fmadd", "fmsub", "fnmsub", "fnmadd" };
  os_ << kOpcodes[(insn32 >> 2) & 3u] << ((funct2 != 0u) ? ".d" : ".s")
      << RoundingModeName(GetRoundingMode(insn32)) << " "
      << FRegName(GetRd(insn32)) << ", " << FRegName(GetRs1(insn32)) << ", "
      << FRegName(GetRs2(insn32)) << ", " << FRegName(GetRs3(insn32));
}

void DisassemblerRiscv64::Printer::Print32Zicsr(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x73u);
  uint32_t funct3 = (insn32 >> 12) & 7u;
  static const char* const kOpcodes[] = {
      nullptr, "csrrw", "csrrs", "csrrc", nullptr, "csrrwi", "csrrsi", "csrrci"
  };
  const char* opcode = kOpcodes[funct3];
  if (opcode == nullptr) {
    os_ << "<unknown32>";
    return;
  }
  uint32_t rd = GetRd(insn32);
  uint32_t rs1_or_uimm = GetRs1(insn32);
  uint32_t csr = insn32 >> 20;
  // Print shorter macro instruction notation if available.
  if (funct3 == /*CSRRW*/ 1u && rd == 0u && rs1_or_uimm == 0u && csr == 0xc00u) {
    os_ << "unimp";
    return;
  } else if (funct3 == /*CSRRS*/ 2u && rs1_or_uimm == 0u) {
    if (csr == 0xc00u) {
      os_ << "rdcycle " << XRegName(rd);
    } else if (csr == 0xc01u) {
      os_ << "rdtime " << XRegName(rd);
    } else if (csr == 0xc02u) {
      os_ << "rdinstret " << XRegName(rd);
    } else {
      os_ << "csrr " << XRegName(rd) << ", " << csr;
    }
    return;
  }

  if (rd == 0u) {
    static const char* const kAltOpcodes[] = {
        nullptr, "csrw", "csrs", "csrc", nullptr, "csrwi", "csrsi", "csrci"
    };
    DCHECK(kAltOpcodes[funct3] != nullptr);
    os_ << kAltOpcodes[funct3] << " " << csr << ", ";
  } else {
    os_ << opcode << " " << XRegName(rd) << ", " << csr << ", ";
  }
  if (funct3 >= /*CSRRWI/CSRRSI/CSRRCI*/ 4u) {
    os_ << rs1_or_uimm;
  } else {
    os_ << XRegName(rs1_or_uimm);
  }
}

void DisassemblerRiscv64::Printer::Print32Fence(uint32_t insn32) {
  DCHECK_EQ(insn32 & 0x7fu, 0x0fu);
  if ((insn32 & 0xf00fffffu) == 0x0000000fu) {
    auto print_flags = [&](uint32_t flags) {
      if (flags == 0u) {
        os_ << "0";
      } else {
        DCHECK_LT(flags, 0x10u);
        static const char kFlagNames[] = "wroi";
        for (size_t bit : { 3u, 2u, 1u, 0u }) {  // Print in the "iorw" order.
          if ((flags & (1u << bit)) != 0u) {
            os_ << kFlagNames[bit];
          }
        }
      }
    };
    os_ << "fence.";
    print_flags((insn32 >> 24) & 0xfu);
    os_ << ".";
    print_flags((insn32 >> 20) & 0xfu);
  } else if (insn32 == 0x8330000fu) {
    os_ << "fence.tso";
  } else if (insn32 == 0x0000100fu) {
    os_ << "fence.i";
  } else {
    os_ << "<unknown32>";
  }
}

void DisassemblerRiscv64::Printer::Dump32(const uint8_t* insn) {
  uint32_t insn32 = static_cast<uint32_t>(insn[0]) +
                    (static_cast<uint32_t>(insn[1]) << 8) +
                    (static_cast<uint32_t>(insn[2]) << 16) +
                    (static_cast<uint32_t>(insn[3]) << 24);
  CHECK_EQ(insn32 & 3u, 3u);
  os_ << disassembler_->FormatInstructionPointer(insn) << StringPrintf(": %08x\t", insn32);
  switch (insn32 & 0x7fu) {
    case 0x37u:
      Print32Lui(insn32);
      break;
    case 0x17u:
      Print32Auipc(insn, insn32);
      break;
    case 0x6fu:
      Print32Jal(insn, insn32);
      break;
    case 0x67u:
      switch ((insn32 >> 12) & 7u) {  // funct3
        case 0:
          Print32Jalr(insn, insn32);
          break;
        default:
          os_ << "<unknown32>";
          break;
      }
      break;
    case 0x63u:
      Print32BCond(insn, insn32);
      break;
    case 0x03u:
      Print32Load(insn32);
      break;
    case 0x23u:
      Print32Store(insn32);
      break;
    case 0x07u:
      Print32FLoad(insn32);
      break;
    case 0x27u:
      Print32FStore(insn32);
      break;
    case 0x13u:
    case 0x1bu:
      Print32BinOpImm(insn32);
      break;
    case 0x33u:
    case 0x3bu:
      Print32BinOp(insn32);
      break;
    case 0x2fu:
      Print32Atomic(insn32);
      break;
    case 0x53u:
      Print32FpOp(insn32);
      break;
    case 0x57u:
      Print32RVVOp(insn32);
      break;
    case 0x43u:
    case 0x47u:
    case 0x4bu:
    case 0x4fu:
      Print32FpFma(insn32);
      break;
    case 0x73u:
      if ((insn32 & 0xffefffffu) == 0x00000073u) {
        os_ << ((insn32 == 0x00000073u) ? "ecall" : "ebreak");
      } else {
        Print32Zicsr(insn32);
      }
      break;
    case 0x0fu:
      Print32Fence(insn32);
      break;
    default:
      // TODO(riscv64): Disassemble more instructions.
      os_ << "<unknown32>";
      break;
  }
  os_ << "\n";
}

void DisassemblerRiscv64::Printer::Dump16(const uint8_t* insn) {
  uint32_t insn16 = static_cast<uint32_t>(insn[0]) + (static_cast<uint32_t>(insn[1]) << 8);
  ScopedNewLinePrinter nl(os_);
  CHECK_NE(insn16 & 3u, 3u);
  os_ << disassembler_->FormatInstructionPointer(insn) << StringPrintf(": %04x    \t", insn16);

  uint32_t funct3 = BitFieldExtract(insn16, 13, 3);
  int32_t offset = -1;

  switch (insn16 & 3u) {
    case 0b00u:  // Quadrant 0
      switch (funct3) {
        case 0b000u:
          if (insn16 == 0u) {
            os_ << "c.unimp";
          } else {
            uint32_t nzuimm = BitFieldExtract(insn16, 5, 8);
            if (nzuimm != 0u) {
              uint32_t decoded =
                  BitFieldExtract(nzuimm, 0, 1) << 3 | BitFieldExtract(nzuimm, 1, 1) << 2 |
                  BitFieldExtract(nzuimm, 2, 4) << 6 | BitFieldExtract(nzuimm, 6, 2) << 4;
              os_ << "c.addi4spn " << XRegName(GetRs2Short16(insn16)) << ", sp, " << decoded;
            } else {
              os_ << "<unknown16>";
            }
          }
          return;
        case 0b001u:
          offset = Decode16CMOffsetD(insn16);
          os_ << "c.fld " << FRegName(GetRs2Short16(insn16));
          break;
        case 0b010u:
          offset = Decode16CMOffsetW(insn16);
          os_ << "c.lw " << XRegName(GetRs2Short16(insn16));
          break;
        case 0b011u:
          offset = Decode16CMOffsetD(insn16);
          os_ << "c.ld " << XRegName(GetRs2Short16(insn16));
          break;
        case 0b100u: {
          uint32_t opcode2 = BitFieldExtract(insn16, 10, 3);
          uint32_t imm = BitFieldExtract(insn16, 5, 2);
          switch (opcode2) {
            case 0b000:
              offset = Uimm2ToOffset10(imm);
              os_ << "c.lbu " << XRegName(GetRs2Short16(insn16));
              break;
            case 0b001:
              offset = Uimm2ToOffset1(imm);
              os_ << (BitFieldExtract(imm, 1, 1) == 0u ? "c.lhu " : "c.lh ");
              os_ << XRegName(GetRs2Short16(insn16));
              break;
            case 0b010:
              offset = Uimm2ToOffset10(imm);
              os_ << "c.sb " << XRegName(GetRs2Short16(insn16));
              break;
            case 0b011:
              if (BitFieldExtract(imm, 1, 1) == 0u) {
                offset = Uimm2ToOffset1(imm);
                os_ << "c.sh " << XRegName(GetRs2Short16(insn16));
                break;
              }
              FALLTHROUGH_INTENDED;
            default:
              os_ << "<unknown16>";
              return;
          }
          break;
        }
        case 0b101u:
          offset = Decode16CMOffsetD(insn16);
          os_ << "c.fsd " << FRegName(GetRs2Short16(insn16));
          break;
        case 0b110u:
          offset = Decode16CMOffsetW(insn16);
          os_ << "c.sw " << XRegName(GetRs2Short16(insn16));
          break;
        case 0b111u:
          offset = Decode16CMOffsetD(insn16);
          os_ << "c.sd " << XRegName(GetRs2Short16(insn16));
          break;
        default:
          LOG(FATAL) << "Unreachable";
          UNREACHABLE();
      }
      os_ << ", ";
      PrintLoadStoreAddress(GetRs1Short16(insn16), offset);
      return;
    case 0b01u:  // Quadrant 1
      switch (funct3) {
        case 0b000u: {
          uint32_t rd = GetRs1_16(insn16);
          if (rd == 0) {
            if (Decode16Imm6<uint32_t>(insn16) != 0u) {
              os_ << "<hint16>";
            } else {
              os_ << "c.nop";
            }
          } else {
            int32_t imm = Decode16Imm6<int32_t>(insn16);
            if (imm != 0) {
              os_ << "c.addi " << XRegName(rd) << ", " << imm;
            } else {
              os_ << "<hint16>";
            }
          }
          break;
        }
        case 0b001u: {
          uint32_t rd = GetRs1_16(insn16);
          if (rd != 0) {
            os_ << "c.addiw " << XRegName(rd) << ", " << Decode16Imm6<int32_t>(insn16);
          } else {
            os_ << "<unknown16>";
          }
          break;
        }
        case 0b010u: {
          uint32_t rd = GetRs1_16(insn16);
          if (rd != 0) {
            os_ << "c.li " << XRegName(rd) << ", " << Decode16Imm6<int32_t>(insn16);
          } else {
            os_ << "<hint16>";
          }
          break;
        }
        case 0b011u: {
          uint32_t rd = GetRs1_16(insn16);
          uint32_t imm6_bits = Decode16Imm6<uint32_t>(insn16);
          if (imm6_bits != 0u) {
            if (rd == 2) {
              int32_t nzimm =
                  BitFieldExtract(insn16, 6, 1) << 4 | BitFieldExtract(insn16, 2, 1) << 5 |
                  BitFieldExtract(insn16, 5, 1) << 6 | BitFieldExtract(insn16, 3, 2) << 7 |
                  BitFieldExtract(insn16, 12, 1) << 9;
              os_ << "c.addi16sp sp, " << SignExtendBits<10>(nzimm);
            } else if (rd != 0) {
              // sign-extend bits and mask with 0xfffff as llvm-objdump does
              uint32_t mask = MaskLeastSignificant<uint32_t>(20);
              os_ << "c.lui " << XRegName(rd) << ", " << (SignExtendBits<6>(imm6_bits) & mask);
            } else {
              os_ << "<hint16>";
            }
          } else {
            os_ << "<unknown16>";
          }
          break;
        }
        case 0b100u: {
          uint32_t funct2 = BitFieldExtract(insn16, 10, 2);
          switch (funct2) {
            case 0b00: {
              int32_t nzuimm = Decode16Imm6<uint32_t>(insn16);
              if (nzuimm != 0) {
                os_ << "c.srli " << XRegName(GetRs1Short16(insn16)) << ", " << nzuimm;
              } else {
                os_ << "<hint16>";
              }
              break;
            }
            case 0b01: {
              int32_t nzuimm = Decode16Imm6<uint32_t>(insn16);
              if (nzuimm != 0) {
                os_ << "c.srai " << XRegName(GetRs1Short16(insn16)) << ", " << nzuimm;
              } else {
                os_ << "<hint16>";
              }
              break;
            }
            case 0b10:
              os_ << "c.andi " << XRegName(GetRs1Short16(insn16)) << ", "
                  << Decode16Imm6<int32_t>(insn16);
              break;
            case 0b11: {
              constexpr static const char* mnemonics[] = {
                  "c.sub", "c.xor", "c.or", "c.and", "c.subw", "c.addw", "c.mul", nullptr
              };
              uint32_t opc = BitFieldInsert(
                  BitFieldExtract(insn16, 5, 2), BitFieldExtract(insn16, 12, 1), 2, 1);
              DCHECK(IsUint<3>(opc));
              const char* mnem = mnemonics[opc];
              if (mnem != nullptr) {
                os_ << mnem << " " << XRegName(GetRs1Short16(insn16)) << ", "
                    << XRegName(GetRs2Short16(insn16));
              } else {
                constexpr static const char* zbc_mnemonics[] = {
                    "c.zext.b", "c.sext.b", "c.zext.h", "c.sext.h",
                    "c.zext.w", "c.not",    nullptr,    nullptr,
                };
                mnem = zbc_mnemonics[BitFieldExtract(insn16, 2, 3)];
                if (mnem != nullptr) {
                  os_ << mnem << " " << XRegName(GetRs1Short16(insn16));
                } else {
                  os_ << "<unknown16>";
                }
              }
              break;
            }
            default:
              LOG(FATAL) << "Unreachable";
              UNREACHABLE();
          }
          break;
        }
        case 0b101u: {
          int32_t disp = BitFieldExtract(insn16, 3, 3) << 1  | BitFieldExtract(insn16, 11, 1) << 4 |
                         BitFieldExtract(insn16, 2, 1) << 5  | BitFieldExtract(insn16, 7,  1) << 6 |
                         BitFieldExtract(insn16, 6, 1) << 7  | BitFieldExtract(insn16, 9,  2) << 8 |
                         BitFieldExtract(insn16, 8, 1) << 10 | BitFieldExtract(insn16, 12, 1) << 11;
          os_ << "c.j ";
          PrintBranchOffset(SignExtendBits<12>(disp));
          break;
        }
        case 0b110u:
        case 0b111u: {
          int32_t disp = BitFieldExtract(insn16, 3, 2) << 1 | BitFieldExtract(insn16, 10, 2) << 3 |
                         BitFieldExtract(insn16, 2, 1) << 5 | BitFieldExtract(insn16, 5,  2) << 6 |
                         BitFieldExtract(insn16, 12, 1) << 8;

          os_ << (funct3 == 0b110u ? "c.beqz " : "c.bnez ");
          os_ << XRegName(GetRs1Short16(insn16)) << ", ";
          PrintBranchOffset(SignExtendBits<9>(disp));
          break;
        }
        default:
          LOG(FATAL) << "Unreachable";
          UNREACHABLE();
      }
      break;
    case 0b10u:  // Quadrant 2
      switch (funct3) {
        case 0b000u: {
          uint32_t nzuimm = Decode16Imm6<uint32_t>(insn16);
          uint32_t rd = GetRs1_16(insn16);
          if (rd == 0 || nzuimm == 0) {
            os_ << "<hint16>";
          } else {
            os_ << "c.slli " << XRegName(rd) << ", " << nzuimm;
          }
          return;
        }
        case 0b001u: {
          offset = Uimm6ToOffsetD16(Decode16Imm6<uint32_t>(insn16));
          os_ << "c.fldsp " << FRegName(GetRs1_16(insn16));
          break;
        }
        case 0b010u: {
          uint32_t rd = GetRs1_16(insn16);
          if (rd != 0) {
            offset = Uimm6ToOffsetW16(Decode16Imm6<uint32_t>(insn16));
            os_ << "c.lwsp " << XRegName(GetRs1_16(insn16));
          } else {
            os_ << "<unknown16>";
            return;
          }
          break;
        }
        case 0b011u: {
          uint32_t rd = GetRs1_16(insn16);
          if (rd != 0) {
            offset = Uimm6ToOffsetD16(Decode16Imm6<uint32_t>(insn16));
            os_ << "c.ldsp " << XRegName(GetRs1_16(insn16));
          } else {
            os_ << "<unknown16>";
            return;
          }
          break;
        }
        case 0b100u: {
          uint32_t rd_rs1 = GetRs1_16(insn16);
          uint32_t rs2 = GetRs2_16(insn16);
          uint32_t b12 = BitFieldExtract(insn16, 12, 1);
          if (b12 == 0) {
            if (rd_rs1 != 0 && rs2 != 0) {
              os_ << "c.mv " << XRegName(rd_rs1) << ", " << XRegName(rs2);
            } else if (rd_rs1 != 0) {
              os_ << "c.jr " << XRegName(rd_rs1);
            } else if (rs2 != 0) {
              os_ << "<hint16>";
            } else {
              os_ << "<unknown16>";
            }
          } else {
            if (rd_rs1 != 0 && rs2 != 0) {
              os_ << "c.add " << XRegName(rd_rs1) << ", " << XRegName(rs2);
            } else if (rd_rs1 != 0) {
              os_ << "c.jalr " << XRegName(rd_rs1);
            } else if (rs2 != 0) {
              os_ << "<hint16>";
            } else {
              os_ << "c.ebreak";
            }
          }
          return;
        }
        case 0b101u:
          offset = BitFieldExtract(insn16, 7, 3) << 6 | BitFieldExtract(insn16, 10, 3) << 3;
          os_ << "c.fsdsp " << FRegName(GetRs2_16(insn16));
          break;
        case 0b110u:
          offset = BitFieldExtract(insn16, 7, 2) << 6 | BitFieldExtract(insn16, 9, 4) << 2;
          os_ << "c.swsp " << XRegName(GetRs2_16(insn16));
          break;
        case 0b111u:
          offset = BitFieldExtract(insn16, 7, 3) << 6 | BitFieldExtract(insn16, 10, 3) << 3;
          os_ << "c.sdsp " << XRegName(GetRs2_16(insn16));
          break;
        default:
          LOG(FATAL) << "Unreachable";
          UNREACHABLE();
      }

      os_ << ", ";
      PrintLoadStoreAddress(/* sp */ 2, offset);

      break;
    default:
      LOG(FATAL) << "Unreachable";
      UNREACHABLE();
  }
}

void DisassemblerRiscv64::Printer::Dump2Byte(const uint8_t* data) {
  uint32_t value = data[0] + (data[1] << 8);
  os_ << disassembler_->FormatInstructionPointer(data)
      << StringPrintf(": %04x    \t.2byte %u\n", value, value);
}

void DisassemblerRiscv64::Printer::DumpByte(const uint8_t* data) {
  uint32_t value = *data;
  os_ << disassembler_->FormatInstructionPointer(data)
      << StringPrintf(": %02x      \t.byte %u\n", value, value);
}

size_t DisassemblerRiscv64::Dump(std::ostream& os, const uint8_t* begin) {
  if (begin < GetDisassemblerOptions()->base_address_ ||
      begin >= GetDisassemblerOptions()->end_address_) {
    return 0u;  // Outside the range.
  }
  Printer printer(this, os);
  if (!IsAligned<2u>(begin) || GetDisassemblerOptions()->end_address_ - begin == 1) {
    printer.DumpByte(begin);
    return 1u;
  }
  if ((*begin & 3u) == 3u) {
    if (GetDisassemblerOptions()->end_address_ - begin >= 4) {
      printer.Dump32(begin);
      return 4u;
    } else {
      printer.Dump2Byte(begin);
      return 2u;
    }
  } else {
    printer.Dump16(begin);
    return 2u;
  }
}

void DisassemblerRiscv64::Dump(std::ostream& os, const uint8_t* begin, const uint8_t* end) {
  Printer printer(this, os);
  const uint8_t* cur = begin;
  if (cur < end && !IsAligned<2u>(cur)) {
    // Unaligned, dump as a `.byte` to get to an aligned address.
    printer.DumpByte(cur);
    cur += 1;
  }
  if (cur >= end) {
    return;
  }
  while (end - cur >= 4) {
    if ((*cur & 3u) == 3u) {
      printer.Dump32(cur);
      cur += 4;
    } else {
      printer.Dump16(cur);
      cur += 2;
    }
  }
  if (end - cur >= 2) {
    if ((*cur & 3u) == 3u) {
      // Not enough data for a 32-bit instruction. Dump as `.2byte`.
      printer.Dump2Byte(cur);
    } else {
      printer.Dump16(cur);
    }
    cur += 2;
  }
  if (end != cur) {
    CHECK_EQ(end - cur, 1);
    printer.DumpByte(cur);
  }
}

}  // namespace riscv64
}  // namespace art
