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

#include "vm/globals.h"  // Needed here to get TARGET_ARCH_RISCV.
#if defined(TARGET_ARCH_RISCV32) || defined(TARGET_ARCH_RISCV64)

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

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

namespace dart {

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

// We deviate from objdump in two places:
//  - branches display displacements instead of targets so our output is
//    position independent for tests.
//  - auipc/lui display the decoded value instead of the encoded value
class RISCVDisassembler {
 public:
  explicit RISCVDisassembler(char* buffer,
                             size_t buffer_size,
                             ExtensionSet extensions)
      : extensions_(extensions),
        buffer_(buffer),
        buffer_size_(buffer_size),
        buffer_pos_(0) {}

  bool Supports(Extension extension) const {
    return extensions_.Includes(extension);
  }
  bool Supports(ExtensionSet extensions) const {
    return extensions_.IncludesAll(extensions);
  }

  intptr_t Disassemble(uword pc) {
    uint16_t parcel = *reinterpret_cast<uint16_t*>(pc);
    if (Supports(RV_C) && IsCInstruction(parcel)) {
      CInstr instr(parcel);
      DisassembleInstruction(instr);
      return instr.length();
    } else {
      uint32_t parcel = LoadUnaligned(reinterpret_cast<uint32_t*>(pc));
      Instr instr(parcel);
      DisassembleInstruction(instr);
      return instr.length();
    }
  }

 private:
  void DisassembleInstruction(Instr instr);
  void DisassembleInstruction(CInstr instr);
  void DisassembleLUI(Instr instr);
  void DisassembleAUIPC(Instr instr);
  void DisassembleJAL(Instr instr);
  void DisassembleJALR(Instr instr);
  void DisassembleBRANCH(Instr instr);
  void DisassembleLOAD(Instr instr);
  void DisassembleSTORE(Instr instr);
  void DisassembleOPIMM(Instr instr);
  void DisassembleOPIMM32(Instr instr);
  void DisassembleOP(Instr instr);
  void DisassembleOP_0(Instr instr);
  void DisassembleOP_SUB(Instr instr);
  void DisassembleOP_MULDIV(Instr instr);
  void DisassembleOP_SHADD(Instr instr);
  void DisassembleOP_MINMAXCLMUL(Instr instr);
  void DisassembleOP_ROTATE(Instr instr);
  void DisassembleOP_BCLRBEXT(Instr instr);
  void DisassembleOP_CZERO(Instr instr);
  void DisassembleOP32(Instr instr);
  void DisassembleOP32_0(Instr instr);
  void DisassembleOP32_SUB(Instr instr);
  void DisassembleOP32_MULDIV(Instr instr);
  void DisassembleOP32_SHADD(Instr instr);
  void DisassembleOP32_ADDUW(Instr instr);
  void DisassembleOP32_ROTATE(Instr instr);
  void DisassembleMISCMEM(Instr instr);
  void DisassembleSYSTEM(Instr instr);
  void DisassembleAMO(Instr instr);
  void DisassembleAMO8(Instr instr);
  void DisassembleAMO16(Instr instr);
  void DisassembleAMO32(Instr instr);
  void DisassembleAMO64(Instr instr);
  void DisassembleAMO128(Instr instr);
  void DisassembleLOADFP(Instr instr);
  void DisassembleSTOREFP(Instr instr);
  void DisassembleFMADD(Instr instr);
  void DisassembleFMSUB(Instr instr);
  void DisassembleFNMADD(Instr instr);
  void DisassembleFNMSUB(Instr instr);
  void DisassembleOPFP(Instr instr);
  void DisassembleOPV(Instr instr);
  void DisassembleOPV_CFG(Instr instr);
  void DisassembleOPV_IVX(Instr instr);

  void UnknownInstruction(Instr instr);
  void UnknownInstruction(CInstr instr);

  void Print(const char* format, Instr instr, ExtensionSet extension);
  void Print(const char* format, CInstr instr, ExtensionSet extension);
  const char* PrintOption(const char* format, Instr instr);
  const char* PrintOption(const char* format, CInstr instr);

  void Printf(const char* format, ...) PRINTF_ATTRIBUTE(2, 3) {
    va_list args;
    va_start(args, format);
    intptr_t len = Utils::VSNPrint(buffer_ + buffer_pos_,
                                   buffer_size_ - buffer_pos_, format, args);
    va_end(args);
    buffer_pos_ += len;
    buffer_[buffer_pos_] = '\0';
  }

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

void RISCVDisassembler::DisassembleInstruction(Instr instr) {
  switch (instr.opcode()) {
    case LUI:
      DisassembleLUI(instr);
      break;
    case AUIPC:
      DisassembleAUIPC(instr);
      break;
    case JAL:
      DisassembleJAL(instr);
      break;
    case JALR:
      DisassembleJALR(instr);
      break;
    case BRANCH:
      DisassembleBRANCH(instr);
      break;
    case LOAD:
      DisassembleLOAD(instr);
      break;
    case STORE:
      DisassembleSTORE(instr);
      break;
    case OPIMM:
      DisassembleOPIMM(instr);
      break;
    case OPIMM32:
      DisassembleOPIMM32(instr);
      break;
    case OP:
      DisassembleOP(instr);
      break;
    case OP32:
      DisassembleOP32(instr);
      break;
    case MISCMEM:
      DisassembleMISCMEM(instr);
      break;
    case SYSTEM:
      DisassembleSYSTEM(instr);
      break;
    case AMO:
      DisassembleAMO(instr);
      break;
    case LOADFP:
      DisassembleLOADFP(instr);
      break;
    case STOREFP:
      DisassembleSTOREFP(instr);
      break;
    case FMADD:
      DisassembleFMADD(instr);
      break;
    case FMSUB:
      DisassembleFMSUB(instr);
      break;
    case FNMADD:
      DisassembleFNMADD(instr);
      break;
    case FNMSUB:
      DisassembleFNMSUB(instr);
      break;
    case OPFP:
      DisassembleOPFP(instr);
      break;
    case OPV:
      DisassembleOPV(instr);
      break;
    default:
      if ((instr.encoding() == 0) ||
          (instr.encoding() == static_cast<uint32_t>(-1))) {
        Print("trap", instr, RV_I);
        break;
      }
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleInstruction(CInstr instr) {
  switch (instr.opcode()) {
    case C_LWSP:
      Print("lw 'rd, 'spload4imm(sp)", instr, RV_C);
      break;
#if XLEN == 32
    case C_FLWSP:
      Print("flw 'frd, 'spload4imm(sp)", instr, RV_C | RV_F);
      break;
#else
    case C_LDSP:
      Print("ld 'rd, 'spload8imm(sp)", instr, RV_C);
      break;
#endif
    case C_FLDSP:
      Print("fld 'frd, 'spload8imm(sp)", instr, RV_C | RV_D);
      break;
    case C_SWSP:
      Print("sw 'rs2, 'spstore4imm(sp)", instr, RV_C);
      break;
#if XLEN == 32
    case C_FSWSP:
      Print("fsw 'frs2, 'spstore4imm(sp)", instr, RV_C | RV_F);
      break;
#else
    case C_SDSP:
      Print("sd 'rs2, 'spstore8imm(sp)", instr, RV_C);
      break;
#endif
    case C_FSDSP:
      Print("fsd 'frs2, 'spstore8imm(sp)", instr, RV_C | RV_D);
      break;
    case C_LW:
      Print("lw 'rdp, 'mem4imm('rs1p)", instr, RV_C);
      break;
#if XLEN == 32
    case C_FLW:
      Print("flw 'frdp, 'mem4imm('rs1p)", instr, RV_C | RV_F);
      break;
#else
    case C_LD:
      Print("ld 'rdp, 'mem8imm('rs1p)", instr, RV_C);
      break;
#endif
    case C_FLD:
      Print("fld 'frdp, 'mem8imm('rs1p)", instr, RV_C | RV_D);
      break;
    case C_SW:
      Print("sw 'rs2p, 'mem4imm('rs1p)", instr, RV_C);
      break;
#if XLEN == 32
    case C_FSW:
      Print("fsw 'frs2p, 'mem4imm('rs1p)", instr, RV_C | RV_F);
      break;
#else
    case C_SD:
      Print("sd 'rs2p, 'mem8imm('rs1p)", instr, RV_C);
      break;
#endif
    case C_FSD:
      Print("fsd 'frs2p, 'mem8imm('rs1p)", instr, RV_C | RV_F);
      break;
    case C_J:
      Print("j 'jimm", instr, RV_C);
      break;
#if XLEN == 32
    case C_JAL:
      Print("jal 'jimm", instr, RV_C);
      break;
#endif
    case C_JR:
      if ((instr.encoding() & (C_JALR ^ C_JR)) != 0) {
        if ((instr.rs1() == ZR) && (instr.rs2() == ZR)) {
          Print("ebreak", instr, RV_C);
        } else if (instr.rs2() == ZR) {
          Print("jalr 'rs1", instr, RV_C);
        } else if ((instr.rs1() == ZR) && (instr.rs2() == 2)) {
          Print("ntl.p1", instr, RV_C);
        } else if ((instr.rs1() == ZR) && (instr.rs2() == 3)) {
          Print("ntl.pall", instr, RV_C);
        } else if ((instr.rs1() == ZR) && (instr.rs2() == 4)) {
          Print("ntl.s1", instr, RV_C);
        } else if ((instr.rs1() == ZR) && (instr.rs2() == 5)) {
          Print("ntl.all", instr, RV_C);
        } else {
          Print("add 'rd, 'rs1, 'rs2", instr, RV_C);
        }
      } else {
        if (instr.rd() != ZR && instr.rs2() != ZR) {
          Print("mv 'rd, 'rs2", instr, RV_C);
        } else if (instr.rs2() != ZR) {
          UnknownInstruction(instr);
        } else if (instr.rs1() == RA) {
          Print("ret", instr, RV_C);
        } else {
          Print("jr 'rs1", instr, RV_C);
        }
      }
      break;
    case C_BEQZ:
      Print("beqz 'rs1p, 'bimm", instr, RV_C);
      break;
    case C_BNEZ:
      Print("bnez 'rs1p, 'bimm", instr, RV_C);
      break;
    case C_LI:
      Print("li 'rd, 'iimm", instr, RV_C);
      break;
    case C_LUI:
      if (instr.rd() == SP) {
        Print("addi 'rd, 'rs1, 'i16imm", instr, RV_C);
      } else if ((instr.rd() != ZR) && (instr.u_imm() != 0)) {
        Print("lui 'rd, 'uimm", instr, RV_C);
      } else if (instr.encoding() == C_SSPUSH) {
        Print("sspush ra", instr, RV_Zicfiss | RV_C);
      } else if (instr.encoding() == C_SSPOPCHK) {
        Print("sspopchk t0", instr, RV_Zicfiss | RV_C);
      } else if ((instr.encoding() & C_MOP_MASK) == C_MOP) {
        Print("c.mop.'mopn", instr, RV_Zcmop);
      } else {
        UnknownInstruction(instr);
      }
      break;
    case C_ADDI:
      if ((instr.rd() == ZR) && (instr.rs1() == ZR) && (instr.i_imm() == 0)) {
        Print("nop", instr, RV_C);
      } else {
        Print("addi 'rd, 'rs1, 'iimm", instr, RV_C);
      }
      break;
#if XLEN >= 64
    case C_ADDIW:
      if (instr.i_imm() == 0) {
        Print("sext.w 'rd, 'rs1", instr, RV_C);
      } else {
        Print("addiw 'rd, 'rs1, 'iimm", instr, RV_C);
      }
      break;
#endif
    case C_ADDI4SPN:
      if (instr.i4spn_imm() == 0) {
        UnknownInstruction(instr);
      } else {
        Print("addi 'rdp, sp, 'i4spnimm", instr, RV_C);
      }
      break;
    case C_SLLI:
      if (instr.rd() == ZR) {
        UnknownInstruction(instr);
      } else {
        Print("slli 'rd, 'rs1, 'shamt", instr, RV_C);
      }
      break;
    case C_MISCALU:
      switch (instr.encoding() & C_MISCALU_MASK) {
        case C_SRLI:
          if (instr.shamt() >= XLEN) {
            UnknownInstruction(instr);
          } else {
            Print("srli 'rs1p, 'rs1p, 'shamt", instr, RV_C);
          }
          break;
        case C_SRAI:
          if (instr.shamt() >= XLEN) {
            UnknownInstruction(instr);
          } else {
            Print("srai 'rs1p, 'rs1p, 'shamt", instr, RV_C);
          }
          break;
        case C_ANDI:
          Print("andi 'rs1p, 'rs1p, 'iimm", instr, RV_C);
          break;
        case C_RR:
          switch (instr.encoding() & C_RR_MASK) {
            case C_AND:
              Print("and 'rs1p, 'rs1p, 'rs2p", instr, RV_C);
              break;
            case C_OR:
              Print("or 'rs1p, 'rs1p, 'rs2p", instr, RV_C);
              break;
            case C_XOR:
              Print("xor 'rs1p, 'rs1p, 'rs2p", instr, RV_C);
              break;
            case C_SUB:
              Print("sub 'rs1p, 'rs1p, 'rs2p", instr, RV_C);
              break;
#if XLEN >= 64
            case C_ADDW:
              Print("addw 'rs1p, 'rs1p, 'rs2p", instr, RV_C);
              break;
            case C_SUBW:
              Print("subw 'rs1p, 'rs1p, 'rs2p", instr, RV_C);
              break;
#endif
            case C_MUL:
              Print("mul 'rs1p, 'rs1p, 'rs2p", instr, RV_Zcb);
              break;
            case C_EXT:
              switch (instr.encoding() & C_EXT_MASK) {
                case C_ZEXTB:
                  Print("zext.b 'rs1p, 'rs1p", instr, RV_Zcb);
                  break;
                case C_SEXTB:
                  Print("sext.b 'rs1p, 'rs1p", instr, RV_Zcb);
                  break;
                case C_ZEXTH:
                  Print("zext.h 'rs1p, 'rs1p", instr, RV_Zcb);
                  break;
                case C_SEXTH:
                  Print("sext.h 'rs1p, 'rs1p", instr, RV_Zcb);
                  break;
#if XLEN >= 64
                case C_ZEXTW:
                  Print("zext.w 'rs1p, 'rs1p", instr, RV_Zcb);
                  break;
#endif
                case C_NOT:
                  Print("not 'rs1p, 'rs1p", instr, RV_Zcb);
                  break;
                default:
                  UnknownInstruction(instr);
              }
              break;
            default:
              UnknownInstruction(instr);
          }
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    case C_LBU:
      switch (instr.encoding() & 0b1111110000000011) {
        case C_LBU:
          Print("lbu 'rdp, 'mem1imm('rs1p)", instr, RV_Zcb);
          break;
        case C_LHU:
          if ((instr.encoding() & 0b1000000) == 0) {
            Print("lhu 'rdp, 'mem2imm('rs1p)", instr, RV_Zcb);
          } else {
            Print("lh 'rdp, 'mem2imm('rs1p)", instr, RV_Zcb);
          }
          break;
        case C_SB:
          Print("sb 'rs2p, 'mem1imm('rs1p)", instr, RV_Zcb);
          break;
        case C_SH:
          Print("sh 'rs2p, 'mem2imm('rs1p)", instr, RV_Zcb);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    default:
      if ((instr.encoding() == 0) ||
          (instr.encoding() == static_cast<uint16_t>(-1))) {
        Print("trap", instr, RV_C);
        break;
      }
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleLUI(Instr instr) {
  Print("lui 'rd, 'uimm", instr, RV_I);
}

void RISCVDisassembler::DisassembleAUIPC(Instr instr) {
  if (instr.rd() == ZR) {
    Print("lpad 'uimm", instr, RV_I);
  } else {
    Print("auipc 'rd, 'uimm", instr, RV_I);
  }
}

void RISCVDisassembler::DisassembleJAL(Instr instr) {
  if (instr.rd() == ZR) {
    Print("j 'jimm", instr, RV_I);
  } else if (instr.rd() == RA) {
    Print("jal 'jimm", instr, RV_I);
  } else {
    Print("jal 'rd, 'jimm", instr, RV_I);
  }
}

void RISCVDisassembler::DisassembleJALR(Instr instr) {
  if (instr.rd() == ZR) {
    if ((instr.rs1() == RA) && (instr.itype_imm() == 0)) {
      Print("ret", instr, RV_I);
    } else if (instr.itype_imm() == 0) {
      Print("jr 'rs1", instr, RV_I);
    } else {
      Print("jr 'iimm('rs1)", instr, RV_I);
    }
  } else if (instr.rd() == RA) {
    if (instr.itype_imm() == 0) {
      Print("jalr 'rs1", instr, RV_I);
    } else {
      Print("jalr 'iimm('rs1)", instr, RV_I);
    }
  } else {
    if (instr.itype_imm() == 0) {
      Print("jalr 'rd, 'rs1", instr, RV_I);
    } else {
      Print("jalr 'rd, 'iimm('rs1)", instr, RV_I);
    }
  }
}

void RISCVDisassembler::DisassembleBRANCH(Instr instr) {
  switch (instr.funct3()) {
    case BEQ:
      if (instr.rs2() == ZR) {
        Print("beqz 'rs1, 'bimm", instr, RV_I);
      } else {
        Print("beq 'rs1, 'rs2, 'bimm", instr, RV_I);
      }
      break;
    case BNE:
      if (instr.rs2() == ZR) {
        Print("bnez 'rs1, 'bimm", instr, RV_I);
      } else {
        Print("bne 'rs1, 'rs2, 'bimm", instr, RV_I);
      }
      break;
    case BLT:
      if (instr.rs2() == ZR) {
        Print("bltz 'rs1, 'bimm", instr, RV_I);
      } else if (instr.rs1() == ZR) {
        Print("bgtz 'rs2, 'bimm", instr, RV_I);
      } else {
        Print("blt 'rs1, 'rs2, 'bimm", instr, RV_I);
      }
      break;
    case BGE:
      if (instr.rs2() == ZR) {
        Print("bgez 'rs1, 'bimm", instr, RV_I);
      } else if (instr.rs1() == ZR) {
        Print("blez 'rs2, 'bimm", instr, RV_I);
      } else {
        Print("ble 'rs2, 'rs1, 'bimm", instr, RV_I);
      }
      break;
    case BLTU:
      Print("bltu 'rs1, 'rs2, 'bimm", instr, RV_I);
      break;
    case BGEU:
      Print("bleu 'rs2, 'rs1, 'bimm", instr, RV_I);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleLOAD(Instr instr) {
  switch (instr.funct3()) {
    case LB:
      Print("lb 'rd, 'iimm('rs1)", instr, RV_I);
      break;
    case LH:
      Print("lh 'rd, 'iimm('rs1)", instr, RV_I);
      break;
    case LW:
      Print("lw 'rd, 'iimm('rs1)", instr, RV_I);
      break;
    case LBU:
      Print("lbu 'rd, 'iimm('rs1)", instr, RV_I);
      break;
    case LHU:
      Print("lhu 'rd, 'iimm('rs1)", instr, RV_I);
      break;
#if XLEN >= 64
    case LWU:
      Print("lwu 'rd, 'iimm('rs1)", instr, RV_I);
      break;
    case LD:
      Print("ld 'rd, 'iimm('rs1)", instr, RV_I);
      break;
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleLOADFP(Instr instr) {
  switch (instr.funct3()) {
    case H:
      Print("flh 'frd, 'iimm('rs1)", instr, RV_Zfhmin);
      break;
    case S:
      Print("flw 'frd, 'iimm('rs1)", instr, RV_F);
      break;
    case D:
      Print("fld 'frd, 'iimm('rs1)", instr, RV_D);
      break;
    case Q:
      Print("flq 'frd, 'iimm('rs1)", instr, RV_Q);
      break;
    case E8:
      Print("vle8.v 'vd, ('rs1)'vm", instr, RV_V);
      break;
    case E16:
      Print("vle16.v 'vd, ('rs1)'vm", instr, RV_V);
      break;
    case E32:
      Print("vle32.v 'vd, ('rs1)'vm", instr, RV_V);
      break;
    case E64:
      Print("vle64.v 'vd, ('rs1)'vm", instr, RV_V);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleSTORE(Instr instr) {
  switch (instr.funct3()) {
    case SB:
      Print("sb 'rs2, 'simm('rs1)", instr, RV_I);
      break;
    case SH:
      Print("sh 'rs2, 'simm('rs1)", instr, RV_I);
      break;
    case SW:
      Print("sw 'rs2, 'simm('rs1)", instr, RV_I);
      break;
#if XLEN >= 64
    case SD:
      Print("sd 'rs2, 'simm('rs1)", instr, RV_I);
      break;
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleSTOREFP(Instr instr) {
  switch (instr.funct3()) {
    case H:
      Print("fsh 'frs2, 'simm('rs1)", instr, RV_Zfhmin);
      break;
    case S:
      Print("fsw 'frs2, 'simm('rs1)", instr, RV_F);
      break;
    case D:
      Print("fsd 'frs2, 'simm('rs1)", instr, RV_D);
      break;
    case Q:
      Print("fsq 'frs2, 'simm('rs1)", instr, RV_Q);
      break;
    case E8:
      Print("vse8.v 'vs3, ('rs1)'vm", instr, RV_V);
      break;
    case E16:
      Print("vse16.v 'vs3, ('rs1)'vm", instr, RV_V);
      break;
    case E32:
      Print("vse32.v 'vs3, ('rs1)'vm", instr, RV_V);
      break;
    case E64:
      Print("vse64.v 'vs3, ('rs1)'vm", instr, RV_V);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOPIMM(Instr instr) {
  switch (instr.funct3()) {
    case ADDI:
      if ((instr.rd() == ZR) && (instr.rs1() == ZR) &&
          (instr.itype_imm() == 0)) {
        Print("nop", instr, RV_I);  // The canonical nop.
      } else if (instr.itype_imm() == 0) {
        Print("mv 'rd, 'rs1", instr, RV_I);
      } else if (instr.rs1() == ZR) {
        Print("li 'rd, 'iimm", instr, RV_I);
      } else {
        Print("addi 'rd, 'rs1, 'iimm", instr, RV_I);
      }
      break;
    case SLTI:
      Print("slti 'rd, 'rs1, 'iimm", instr, RV_I);
      break;
    case SLTIU:
      if (instr.itype_imm() == 1) {
        Print("seqz 'rd, 'rs1", instr, RV_I);
      } else {
        Print("sltiu 'rd, 'rs1, 'iimm", instr, RV_I);
      }
      break;
    case XORI:
      if (instr.itype_imm() == -1) {
        Print("not 'rd, 'rs1", instr, RV_I);
      } else {
        Print("xori 'rd, 'rs1, 'iimm", instr, RV_I);
      }
      break;
    case ORI:
      if ((instr.rd() == ZR) && (instr.rs2() == 0)) {
        Print("prefetch.i 'prefetchimm('rs1)", instr, RV_I);
      } else if ((instr.rd() == ZR) && (instr.rs2() == 1)) {
        Print("prefetch.r 'prefetchimm('rs1)", instr, RV_I);
      } else if ((instr.rd() == ZR) && (instr.rs2() == 3)) {
        Print("prefetch.w 'prefetchimm('rs1)", instr, RV_I);
      } else {
        Print("ori 'rd, 'rs1, 'iimm", instr, RV_I);
      }
      break;
    case ANDI:
      Print("andi 'rd, 'rs1, 'iimm", instr, RV_I);
      break;
    case SLLI:
      if (instr.funct7() == COUNT) {
        if (instr.shamt() == 0b00000) {
          Print("clz 'rd, 'rs1", instr, RV_Zbb);
        } else if (instr.shamt() == 0b00001) {
          Print("ctz 'rd, 'rs1", instr, RV_Zbb);
        } else if (instr.shamt() == 0b00010) {
          Print("cpop 'rd, 'rs1", instr, RV_Zbb);
        } else if (instr.shamt() == 0b00100) {
          Print("sext.b 'rd, 'rs1", instr, RV_Zbb);
        } else if (instr.shamt() == 0b00101) {
          Print("sext.h 'rd, 'rs1", instr, RV_Zbb);
        } else {
          UnknownInstruction(instr);
        }
      } else if ((instr.funct7() & 0b1111110) == BCLRBEXT) {
        Print("bclri 'rd, 'rs1, 'shamt", instr, RV_Zbs);
      } else if ((instr.funct7() & 0b1111110) == BINV) {
        Print("binvi 'rd, 'rs1, 'shamt", instr, RV_Zbs);
      } else if ((instr.funct7() & 0b1111110) == BSET) {
        Print("bseti 'rd, 'rs1, 'shamt", instr, RV_Zbs);
      } else {
        Print("slli 'rd, 'rs1, 'shamt", instr, RV_I);
      }
      break;
    case SRI:
      if ((instr.funct7() & 0b1111110) == SRA) {
        Print("srai 'rd, 'rs1, 'shamt", instr, RV_I);
      } else if ((instr.funct7() & 0b1111110) == ROTATE) {
        Print("rori 'rd, 'rs1, 'shamt", instr, RV_Zbb);
      } else if (instr.funct7() == 0b0010100) {
        Print("orc.b 'rd, 'rs1", instr, RV_Zbb);
#if XLEN == 32
      } else if (instr.funct7() == 0b0110100) {
        Print("rev8 'rd, 'rs1", instr, RV_Zbb);
#else
      } else if (instr.funct7() == 0b0110101) {
        Print("rev8 'rd, 'rs1", instr, RV_Zbb);
#endif
      } else if ((instr.funct7() & 0b1111110) == BCLRBEXT) {
        Print("bexti 'rd, 'rs1, 'shamt", instr, RV_Zbs);
      } else {
        Print("srli 'rd, 'rs1, 'shamt", instr, RV_I);
      }
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOPIMM32(Instr instr) {
  switch (instr.funct3()) {
#if XLEN >= 64
    case ADDI:
      if (instr.itype_imm() == 0) {
        Print("sext.w 'rd, 'rs1", instr, RV_I);
      } else {
        Print("addiw 'rd, 'rs1, 'iimm", instr, RV_I);
      }
      break;
    case SLLI:
      if (instr.funct7() == SLLIUW) {
        Print("slli.uw 'rd, 'rs1, 'shamt", instr, RV_Zba);
      } else if (instr.funct7() == COUNT) {
        if (instr.shamt() == 0b00000) {
          Print("clzw 'rd, 'rs1", instr, RV_Zbb);
        } else if (instr.shamt() == 0b00001) {
          Print("ctzw 'rd, 'rs1", instr, RV_Zbb);
        } else if (instr.shamt() == 0b00010) {
          Print("cpopw 'rd, 'rs1", instr, RV_Zbb);
        } else {
          UnknownInstruction(instr);
        }
      } else {
        Print("slliw 'rd, 'rs1, 'shamt", instr, RV_I);
      }
      break;
    case SRI:
      if (instr.funct7() == SRA) {
        Print("sraiw 'rd, 'rs1, 'shamt", instr, RV_I);
      } else if (instr.funct7() == ROTATE) {
        Print("roriw 'rd, 'rs1, 'shamt", instr, RV_Zbb);
      } else {
        Print("srliw 'rd, 'rs1, 'shamt", instr, RV_I);
      }
      break;
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP(Instr instr) {
  switch (instr.funct7()) {
    case 0:
      DisassembleOP_0(instr);
      break;
    case SUB:
      DisassembleOP_SUB(instr);
      break;
    case MULDIV:
      DisassembleOP_MULDIV(instr);
      break;
    case SHADD:
      DisassembleOP_SHADD(instr);
      break;
    case MINMAXCLMUL:
      DisassembleOP_MINMAXCLMUL(instr);
      break;
    case ROTATE:
      DisassembleOP_ROTATE(instr);
      break;
    case BCLRBEXT:
      DisassembleOP_BCLRBEXT(instr);
      break;
    case BINV:
      Print("binv 'rd, 'rs1, 'rs2", instr, RV_Zbs);
      break;
    case BSET:
      Print("bset 'rd, 'rs1, 'rs2", instr, RV_Zbs);
      break;
#if XLEN == 32
    case 0b0000100:
      Print("zext.h 'rd, 'rs1", instr, RV_Zbb);
      break;
#endif
    case CZERO:
      DisassembleOP_CZERO(instr);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP_0(Instr instr) {
  switch (instr.funct3()) {
    case ADD:
      if (instr.rd() == ZR && instr.rs1() == ZR && instr.rs2() == 2) {
        Print("ntl.p1", instr, RV_I);
      } else if (instr.rd() == ZR && instr.rs1() == ZR && instr.rs2() == 3) {
        Print("ntl.pall", instr, RV_I);
      } else if (instr.rd() == ZR && instr.rs1() == ZR && instr.rs2() == 4) {
        Print("ntl.s1", instr, RV_I);
      } else if (instr.rd() == ZR && instr.rs1() == ZR && instr.rs2() == 5) {
        Print("ntl.all", instr, RV_I);
      } else {
        Print("add 'rd, 'rs1, 'rs2", instr, RV_I);
      }
      break;
    case SLL:
      Print("sll 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    case SLT:
      if (instr.rs2() == ZR) {
        Print("sltz 'rd, 'rs1", instr, RV_I);
      } else if (instr.rs1() == ZR) {
        Print("sgtz 'rd, 'rs2", instr, RV_I);
      } else {
        Print("slt 'rd, 'rs1, 'rs2", instr, RV_I);
      }
      break;
    case SLTU:
      if (instr.rs1() == ZR) {
        Print("snez 'rd, 'rs2", instr, RV_I);
      } else {
        Print("sltu 'rd, 'rs1, 'rs2", instr, RV_I);
      }
      break;
    case XOR:
      Print("xor 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    case SR:
      Print("srl 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    case OR:
      Print("or 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    case AND:
      Print("and 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP_SUB(Instr instr) {
  switch (instr.funct3()) {
    case ADD:
      if (instr.rs1() == ZR) {
        Print("neg 'rd, 'rs2", instr, RV_I);
      } else {
        Print("sub 'rd, 'rs1, 'rs2", instr, RV_I);
      }
      break;
    case SR:
      Print("sra 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    case AND:
      Print("andn 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    case OR:
      Print("orn 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    case XOR:
      Print("xnor 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP_MULDIV(Instr instr) {
  switch (instr.funct3()) {
    case MUL:
      Print("mul 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case MULH:
      Print("mulh 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case MULHSU:
      Print("mulhsu 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case MULHU:
      Print("mulhu 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case DIV:
      Print("div 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case DIVU:
      Print("divu 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case REM:
      Print("rem 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case REMU:
      Print("remu 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP_SHADD(Instr instr) {
  switch (instr.funct3()) {
    case SH1ADD:
      Print("sh1add 'rd, 'rs1, 'rs2", instr, RV_Zba);
      break;
    case SH2ADD:
      Print("sh2add 'rd, 'rs1, 'rs2", instr, RV_Zba);
      break;
    case SH3ADD:
      Print("sh3add 'rd, 'rs1, 'rs2", instr, RV_Zba);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP_MINMAXCLMUL(Instr instr) {
  switch (instr.funct3()) {
    case MAX:
      Print("max 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    case MAXU:
      Print("maxu 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    case MIN:
      Print("min 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    case MINU:
      Print("minu 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    case CLMUL:
      Print("clmul 'rd, 'rs1, 'rs2", instr, RV_Zbc);
      break;
    case CLMULH:
      Print("clmulh 'rd, 'rs1, 'rs2", instr, RV_Zbc);
      break;
    case CLMULR:
      Print("clmulr 'rd, 'rs1, 'rs2", instr, RV_Zbc);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP_ROTATE(Instr instr) {
  switch (instr.funct3()) {
    case ROR:
      Print("ror 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    case ROL:
      Print("rol 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP_BCLRBEXT(Instr instr) {
  switch (instr.funct3()) {
    case BCLR:
      Print("bclr 'rd, 'rs1, 'rs2", instr, RV_Zbs);
      break;
    case BEXT:
      Print("bext 'rd, 'rs1, 'rs2", instr, RV_Zbs);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP_CZERO(Instr instr) {
  switch (instr.funct3()) {
    case CZEROEQZ:
      Print("czero.eqz 'rd, 'rs1, 'rs2", instr, RV_Zicond);
      break;
    case CZERONEZ:
      Print("czero.nez 'rd, 'rs1, 'rs2", instr, RV_Zicond);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP32(Instr instr) {
  switch (instr.funct7()) {
    case 0:
      DisassembleOP32_0(instr);
      break;
    case SUB:
      DisassembleOP32_SUB(instr);
      break;
    case MULDIV:
      DisassembleOP32_MULDIV(instr);
      break;
    case SHADD:
      DisassembleOP32_SHADD(instr);
      break;
    case ADDUW:
      DisassembleOP32_ADDUW(instr);
      break;
    case ROTATE:
      DisassembleOP32_ROTATE(instr);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP32_0(Instr instr) {
  switch (instr.funct3()) {
#if XLEN >= 64
    case ADD:
      Print("addw 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    case SLL:
      Print("sllw 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    case SR: {
      Print("srlw 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
    }
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP32_SUB(Instr instr) {
  switch (instr.funct3()) {
#if XLEN >= 64
    case ADD:
      if (instr.rs1() == ZR) {
        Print("negw 'rd, 'rs2", instr, RV_I);
      } else {
        Print("subw 'rd, 'rs1, 'rs2", instr, RV_I);
      }
      break;
    case SR:
      Print("sraw 'rd, 'rs1, 'rs2", instr, RV_I);
      break;
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP32_MULDIV(Instr instr) {
  switch (instr.funct3()) {
#if XLEN >= 64
    case MULW:
      Print("mulw 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case DIVW:
      Print("divw 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case DIVUW:
      Print("divuw 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case REMW:
      Print("remw 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
    case REMUW:
      Print("remuw 'rd, 'rs1, 'rs2", instr, RV_M);
      break;
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP32_SHADD(Instr instr) {
  switch (instr.funct3()) {
    case SH1ADD:
      Print("sh1add.uw 'rd, 'rs1, 'rs2", instr, RV_Zba);
      break;
    case SH2ADD:
      Print("sh2add.uw 'rd, 'rs1, 'rs2", instr, RV_Zba);
      break;
    case SH3ADD:
      Print("sh3add.uw 'rd, 'rs1, 'rs2", instr, RV_Zba);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP32_ADDUW(Instr instr) {
  switch (instr.funct3()) {
#if XLEN >= 64
    case F3_0:
      if (instr.rs2() == ZR) {
        Print("zext.w 'rd, 'rs1", instr, RV_Zba);
      } else {
        Print("add.uw 'rd, 'rs1, 'rs2", instr, RV_Zba);
      }
      break;
    case ZEXT:
      Print("zext.h 'rd, 'rs1", instr, RV_Zbb);
      break;
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOP32_ROTATE(Instr instr) {
  switch (instr.funct3()) {
    case ROR:
      Print("rorw 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    case ROL:
      Print("rolw 'rd, 'rs1, 'rs2", instr, RV_Zbb);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleMISCMEM(Instr instr) {
  switch (instr.funct3()) {
    case FENCE:
      if (instr.encoding() == 0x0100000f) {
        Print("pause", instr, RV_I);  // fence w,0
      } else {
        Print("fence'predsucc", instr, RV_I);
      }
      break;
    case FENCEI:
      Print("fence.i", instr, RV_I);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleSYSTEM(Instr instr) {
  switch (instr.funct3()) {
    case PRIV:
      switch (instr.funct12()) {
        case ECALL:
          if (instr.rs1() == ZR) {
            Print("ecall", instr, RV_I);
          } else {
            Print("SimulatorPrintObject 'rs1", instr, RV_I);
          }
          break;
        case EBREAK:
          Print("ebreak", instr, RV_I);
          break;
        case WRS_NTO:
          Print("wrs.nto", instr, RV_Zawrs);
          break;
        case WRS_STO:
          Print("wrs.sto", instr, RV_Zawrs);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    case MOP: {
      if ((instr.funct7() == SSPUSH) && (instr.rd() == ZR) &&
          (instr.rs1() == ZR) &&
          ((instr.rs2() == Register(1)) || (instr.rs2() == Register(5)))) {
        Print("sspush 'rs2", instr, RV_Zicfiss);
      } else if ((instr.funct12() == SSPOPCHK) && (instr.rd() == ZR) &&
                 ((instr.rs1() == Register(1)) ||
                  (instr.rs1() == Register(5)))) {
        Print("sspopchk 'rs1", instr, RV_Zicfiss);
      } else if ((instr.funct12() == SSRDP) && (instr.rs1() == ZR)) {
        Print("ssrdp 'rd", instr, RV_Zicfiss);
      } else if ((instr.funct12() & MOP_R_MASK) == MOP_R) {
        Print("mop.r.'moprn 'rd, 'rs1", instr, RV_Zimop);
      } else if ((instr.funct7() & MOP_RR_MASK) == MOP_RR) {
        Print("mop.rr.'moprrn 'rd, 'rs1, 'rs2", instr, RV_Zimop);
      } else {
        UnknownInstruction(instr);
      }
      break;
    }
    case CSRRW:
      if (instr.rd() == ZR) {
        Print("csrw 'csr, 'rs1", instr, RV_I);
      } else {
        Print("csrrw 'rd, 'csr, 'rs1", instr, RV_I);
      }
      break;
    case CSRRS:
      if (instr.rs1() == ZR) {
        Print("csrr 'rd, 'csr", instr, RV_I);
      } else if (instr.rd() == ZR) {
        Print("csrs 'csr, 'rs1", instr, RV_I);
      } else {
        Print("csrrs 'rd, 'csr, 'rs1", instr, RV_I);
      }
      break;
    case CSRRC:
      if (instr.rd() == ZR) {
        Print("csrc 'csr, 'rs1", instr, RV_I);
      } else {
        Print("csrrc 'rd, 'csr, 'rs1", instr, RV_I);
      }
      break;
    case CSRRWI:
      if (instr.rd() == ZR) {
        Print("csrwi 'csr, 'zimm", instr, RV_I);
      } else {
        Print("csrrwi 'rd, 'csr, 'zimm", instr, RV_I);
      }
      break;
    case CSRRSI:
      if (instr.rd() == ZR) {
        Print("csrsi 'csr, 'zimm", instr, RV_I);
      } else {
        Print("csrrsi 'rd, 'csr, 'zimm", instr, RV_I);
      }
      break;
    case CSRRCI:
      if (instr.rd() == ZR) {
        Print("csrci 'csr, 'zimm", instr, RV_I);
      } else {
        Print("csrrci 'rd, 'csr, 'zimm", instr, RV_I);
      }
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleAMO(Instr instr) {
  switch (instr.funct3()) {
    case WIDTH8:
      DisassembleAMO8(instr);
      break;
    case WIDTH16:
      DisassembleAMO16(instr);
      break;
    case WIDTH32:
      DisassembleAMO32(instr);
      break;
    case WIDTH64:
      DisassembleAMO64(instr);
      break;
    case WIDTH128:
      DisassembleAMO128(instr);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleAMO8(Instr instr) {
  switch (instr.funct5()) {
    case AMOSWAP:
      Print("amoswap.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOADD:
      Print("amoadd.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOXOR:
      Print("amoxor.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOAND:
      Print("amoand.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOOR:
      Print("amoor.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOMIN:
      Print("amomin.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOMAX:
      Print("amomax.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOMINU:
      Print("amominu.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOMAXU:
      Print("amomaxu.b'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case LOADORDERED:
      Print("lb'order 'rd, ('rs1)", instr, RV_Zalasr);
      break;
    case STOREORDERED:
      Print("sb'order 'rs2, ('rs1)", instr, RV_Zalasr);
      break;
    case AMOCAS:
      Print("amocas.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zacas | RV_Zabha);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleAMO16(Instr instr) {
  switch (instr.funct5()) {
    case AMOSWAP:
      Print("amoswap.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOADD:
      Print("amoadd.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOXOR:
      Print("amoxor.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOAND:
      Print("amoand.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOOR:
      Print("amoor.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOMIN:
      Print("amomin.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOMAX:
      Print("amomax.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOMINU:
      Print("amominu.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case AMOMAXU:
      Print("amomaxu.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zabha);
      break;
    case LOADORDERED:
      Print("lh'order 'rd, ('rs1)", instr, RV_Zalasr);
      break;
    case STOREORDERED:
      Print("sh'order 'rs2, ('rs1)", instr, RV_Zalasr);
      break;
    case AMOCAS:
      Print("amocas.h'order 'rd, 'rs2, ('rs1)", instr, RV_Zacas | RV_Zabha);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleAMO32(Instr instr) {
  switch (instr.funct5()) {
    case LR:
      Print("lr.w'order 'rd, ('rs1)", instr, RV_A);
      break;
    case SC:
      Print("sc.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOSWAP:
      Print("amoswap.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOADD:
      Print("amoadd.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOXOR:
      Print("amoxor.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOAND:
      Print("amoand.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOOR:
      Print("amoor.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOMIN:
      Print("amomin.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOMAX:
      Print("amomax.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOMINU:
      Print("amominu.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOMAXU:
      Print("amomaxu.w'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case LOADORDERED:
      Print("lw'order 'rd, ('rs1)", instr, RV_Zalasr);
      break;
    case STOREORDERED:
      Print("sw'order 'rs2, ('rs1)", instr, RV_Zalasr);
      break;
    case SSAMOSWAP:
      Print("ssamoswap.w'order 'rd, 'rs2, ('rs1)", instr, RV_Zicfiss);
      break;
    case AMOCAS:
      Print("amocas.w'order 'rd, 'rs2, ('rs1)", instr, RV_Zacas);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleAMO64(Instr instr) {
  switch (instr.funct5()) {
#if XLEN >= 64
    case LR:
      Print("lr.d'order 'rd, ('rs1)", instr, RV_A);
      break;
    case SC:
      Print("sc.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOSWAP:
      Print("amoswap.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOADD:
      Print("amoadd.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOXOR:
      Print("amoxor.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOAND:
      Print("amoand.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOOR:
      Print("amoor.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOMIN:
      Print("amomin.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOMAX:
      Print("amomax.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOMINU:
      Print("amominu.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case AMOMAXU:
      Print("amomaxu.d'order 'rd, 'rs2, ('rs1)", instr, RV_A);
      break;
    case LOADORDERED:
      Print("ld'order 'rd, ('rs1)", instr, RV_Zalasr);
      break;
    case STOREORDERED:
      Print("sd'order 'rs2, ('rs1)", instr, RV_Zalasr);
      break;
    case SSAMOSWAP:
      Print("ssamoswap.d'order 'rd, 'rs2, ('rs1)", instr, RV_Zicfiss);
      break;
#endif
    case AMOCAS:
      Print("amocas.d'order 'rd, 'rs2, ('rs1)", instr, RV_Zacas);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleAMO128(Instr instr) {
  switch (instr.funct5()) {
#if XLEN >= 64
    case AMOCAS:
      Print("amocas.q'order 'rd, 'rs2, ('rs1)", instr, RV_Zacas);
      break;
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleFMADD(Instr instr) {
  switch (instr.funct2()) {
    case F2_S:
      Print("fmadd.s 'frd, 'frs1, 'frs2, 'frs3'round", instr, RV_F);
      break;
    case F2_D:
      Print("fmadd.d 'frd, 'frs1, 'frs2, 'frs3'round", instr, RV_D);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleFMSUB(Instr instr) {
  switch (instr.funct2()) {
    case F2_S:
      Print("fmsub.s 'frd, 'frs1, 'frs2, 'frs3'round", instr, RV_F);
      break;
    case F2_D:
      Print("fmsub.d 'frd, 'frs1, 'frs2, 'frs3'round", instr, RV_D);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleFNMADD(Instr instr) {
  switch (instr.funct2()) {
    case F2_S:
      Print("fnmadd.s 'frd, 'frs1, 'frs2, 'frs3'round", instr, RV_F);
      break;
    case F2_D:
      Print("fnmadd.d 'frd, 'frs1, 'frs2, 'frs3'round", instr, RV_D);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleFNMSUB(Instr instr) {
  switch (instr.funct2()) {
    case F2_S:
      Print("fnmsub.s 'frd, 'frs1, 'frs2, 'frs3'round", instr, RV_F);
      break;
    case F2_D:
      Print("fnmsub.d 'frd, 'frs1, 'frs2, 'frs3'round", instr, RV_D);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOPFP(Instr instr) {
  switch (instr.funct7()) {
    case FADDS:
      Print("fadd.s 'frd, 'frs1, 'frs2'round", instr, RV_F);
      break;
    case FSUBS:
      Print("fsub.s 'frd, 'frs1, 'frs2'round", instr, RV_F);
      break;
    case FMULS:
      Print("fmul.s 'frd, 'frs1, 'frs2'round", instr, RV_F);
      break;
    case FDIVS:
      Print("fdiv.s 'frd, 'frs1, 'frs2'round", instr, RV_F);
      break;
    case FSQRTS:
      Print("fsqrt.s 'frd, 'frs1'round", instr, RV_F);
      break;
    case FSGNJS: {
      switch (instr.funct3()) {
        case J:
          if (instr.frs1() == instr.frs2()) {
            Print("fmv.s 'frd, 'frs1", instr, RV_F);
          } else {
            Print("fsgnj.s 'frd, 'frs1, 'frs2", instr, RV_F);
          }
          break;
        case JN:
          if (instr.frs1() == instr.frs2()) {
            Print("fneg.s 'frd, 'frs1", instr, RV_F);
          } else {
            Print("fsgnjn.s 'frd, 'frs1, 'frs2", instr, RV_F);
          }
          break;
        case JX:
          if (instr.frs1() == instr.frs2()) {
            Print("fabs.s 'frd, 'frs1", instr, RV_F);
          } else {
            Print("fsgnjx.s 'frd, 'frs1, 'frs2", instr, RV_F);
          }
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    }
    case FMINMAXS: {
      switch (instr.funct3()) {
        case FMIN:
          Print("fmin.s 'frd, 'frs1, 'frs2", instr, RV_F);
          break;
        case FMAX:
          Print("fmax.s 'frd, 'frs1, 'frs2", instr, RV_F);
          break;
        case FMINM:
          Print("fminm.s 'frd, 'frs1, 'frs2", instr, RV_Zfa);
          break;
        case FMAXM:
          Print("fmaxm.s 'frd, 'frs1, 'frs2", instr, RV_Zfa);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    }
    case FCMPS: {
      switch (instr.funct3()) {
        case FEQ:
          Print("feq.s 'rd, 'frs1, 'frs2", instr, RV_F);
          break;
        case FLT:
          Print("flt.s 'rd, 'frs1, 'frs2", instr, RV_F);
          break;
        case FLE:
          Print("fle.s 'rd, 'frs1, 'frs2", instr, RV_F);
          break;
        case FLTQ:
          Print("fltq.s 'rd, 'frs1, 'frs2", instr, RV_Zfa);
          break;
        case FLEQ:
          Print("fleq.s 'rd, 'frs1, 'frs2", instr, RV_Zfa);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    }
    case FCLASSS:  // = FMVXW
      switch (instr.funct3()) {
        case 1:
          Print("fclass.s 'rd, 'frs1", instr, RV_F);
          break;
        case 0:
          Print("fmv.x.w 'rd, 'frs1", instr, RV_F);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    case FCVTintS:
      switch (static_cast<FcvtRs2>(instr.rs2())) {
        case W:
          Print("fcvt.w.s 'rd, 'frs1'round", instr, RV_F);
          break;
        case WU:
          Print("fcvt.wu.s 'rd, 'frs1'round", instr, RV_F);
          break;
#if XLEN >= 64
        case L:
          Print("fcvt.l.s 'rd, 'frs1'round", instr, RV_F);
          break;
        case LU:
          Print("fcvt.lu.s 'rd, 'frs1'round", instr, RV_F);
          break;
#endif
        default:
          UnknownInstruction(instr);
      }
      break;
    case FCVTSint:
      switch (static_cast<FcvtRs2>(instr.rs2())) {
        case W:
          Print("fcvt.s.w 'frd, 'rs1'round", instr, RV_F);
          break;
        case WU:
          Print("fcvt.s.wu 'frd, 'rs1'round", instr, RV_F);
          break;
#if XLEN >= 64
        case L:
          Print("fcvt.s.l 'frd, 'rs1'round", instr, RV_F);
          break;
        case LU:
          Print("fcvt.s.lu 'frd, 'rs1'round", instr, RV_F);
          break;
#endif
        default:
          UnknownInstruction(instr);
      }
      break;
    case FMVWX:
      switch (instr.frs2()) {
        case 0:
          Print("fmv.w.x 'frd, 'rs1", instr, RV_F);
          break;
        case 1:
          Print("flis 'frd, 'flis", instr, RV_Zfa);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    case FADDD:
      Print("fadd.d 'frd, 'frs1, 'frs2'round", instr, RV_D);
      break;
    case FSUBD:
      Print("fsub.d 'frd, 'frs1, 'frs2'round", instr, RV_D);
      break;
    case FMULD:
      Print("fmul.d 'frd, 'frs1, 'frs2'round", instr, RV_D);
      break;
    case FDIVD:
      Print("fdiv.d 'frd, 'frs1, 'frs2'round", instr, RV_D);
      break;
    case FSQRTD:
      Print("fsqrt.d 'frd, 'frs1'round", instr, RV_D);
      break;
    case FSGNJD: {
      switch (instr.funct3()) {
        case J:
          if (instr.frs1() == instr.frs2()) {
            Print("fmv.d 'frd, 'frs1", instr, RV_D);
          } else {
            Print("fsgnj.d 'frd, 'frs1, 'frs2", instr, RV_D);
          }
          break;
        case JN:
          if (instr.frs1() == instr.frs2()) {
            Print("fneg.d 'frd, 'frs1", instr, RV_D);
          } else {
            Print("fsgnjn.d 'frd, 'frs1, 'frs2", instr, RV_D);
          }
          break;
        case JX:
          if (instr.frs1() == instr.frs2()) {
            Print("fabs.d 'frd, 'frs1", instr, RV_D);
          } else {
            Print("fsgnjx.d 'frd, 'frs1, 'frs2", instr, RV_D);
          }
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    }
    case FMINMAXD: {
      switch (instr.funct3()) {
        case FMIN:
          Print("fmin.d 'frd, 'frs1, 'frs2", instr, RV_D);
          break;
        case FMAX:
          Print("fmax.d 'frd, 'frs1, 'frs2", instr, RV_D);
          break;
        case FMINM:
          Print("fminm.d 'frd, 'frs1, 'frs2", instr, RV_Zfa);
          break;
        case FMAXM:
          Print("fmaxm.d 'frd, 'frs1, 'frs2", instr, RV_Zfa);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    }
    case FCVTS: {
      switch (instr.rs2()) {
        case 1:
          Print("fcvt.s.d 'frd, 'frs1'round", instr, RV_D);
          break;
        case 4:
          Print("fround.s 'frd, 'frs1'round", instr, RV_Zfa);
          break;
        case 5:
          Print("froundnx.s 'frd, 'frs1'round", instr, RV_Zfa);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    }
    case FCVTD: {
      switch (instr.rs2()) {
        case 0:
          Print("fcvt.d.s 'frd, 'frs1'round", instr, RV_D);
          break;
        case 4:
          Print("fround.d 'frd, 'frs1'round", instr, RV_Zfa);
          break;
        case 5:
          Print("froundnx.d 'frd, 'frs1'round", instr, RV_Zfa);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    }
    case FCMPD: {
      switch (instr.funct3()) {
        case FEQ:
          Print("feq.d 'rd, 'frs1, 'frs2", instr, RV_D);
          break;
        case FLT:
          Print("flt.d 'rd, 'frs1, 'frs2", instr, RV_D);
          break;
        case FLE:
          Print("fle.d 'rd, 'frs1, 'frs2", instr, RV_D);
          break;
        case FLTQ:
          Print("fltq.d 'rd, 'frs1, 'frs2", instr, RV_Zfa);
          break;
        case FLEQ:
          Print("fleq.d 'rd, 'frs1, 'frs2", instr, RV_Zfa);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    }
    case FCLASSD:  // = FMVXD
      switch (instr.funct3()) {
        case 1:
          Print("fclass.d 'rd, 'frs1", instr, RV_D);
          break;
        case 0:
          switch (static_cast<int>(instr.rs2())) {
#if XLEN >= 64
            case 0:
              Print("fmv.x.d 'rd, 'frs1", instr, RV_D);
              break;
#endif
#if XLEN == 32
            case 1:
              Print("fmvh.x.d 'rd, 'frs1", instr, RV_Zfa);
              break;
#endif
            default:
              UnknownInstruction(instr);
          }
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
    case FCVTintD:
      switch (static_cast<int>(instr.rs2())) {
        case W:
          Print("fcvt.w.d 'rd, 'frs1'round", instr, RV_D);
          break;
        case 8:
          Print("fcvtmod.w.d 'rd, 'frs1'round", instr, RV_Zfa);
          break;
        case WU:
          Print("fcvt.wu.d 'rd, 'frs1'round", instr, RV_D);
          break;
#if XLEN >= 64
        case L:
          Print("fcvt.l.d 'rd, 'frs1'round", instr, RV_D);
          break;
        case LU:
          Print("fcvt.lu.d 'rd, 'frs1'round", instr, RV_D);
          break;
#endif
        default:
          UnknownInstruction(instr);
      }
      break;
    case FCVTDint:
      switch (static_cast<FcvtRs2>(instr.rs2())) {
        case W:
          Print("fcvt.d.w 'frd, 'rs1'round", instr, RV_D);
          break;
        case WU:
          Print("fcvt.d.wu 'frd, 'rs1'round", instr, RV_D);
          break;
#if XLEN >= 64
        case L:
          Print("fcvt.d.l 'frd, 'rs1'round", instr, RV_D);
          break;
        case LU:
          Print("fcvt.d.lu 'frd, 'rs1'round", instr, RV_D);
          break;
#endif
        default:
          UnknownInstruction(instr);
      }
      break;
    case FMVDX:
      switch (static_cast<int>(instr.frs2())) {
#if XLEN >= 64
        case 0:
          Print("fmv.d.x 'frd, 'rs1", instr, RV_D);
          break;
#endif
        case 1:
          Print("flid 'frd, 'flid", instr, RV_Zfa);
          break;
        default:
          UnknownInstruction(instr);
      }
      break;
#if XLEN == 32
    case FMVPDX:
      Print("fmvp.d.x 'frd, 'rs1, 'rs2", instr, RV_Zfa);
      break;
#endif
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOPV(Instr instr) {
  switch (instr.funct3()) {
    case OPCFG:
      DisassembleOPV_CFG(instr);
      break;
    case OPIVX:
      DisassembleOPV_IVX(instr);
      break;
    default:
      UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOPV_CFG(Instr instr) {
  if ((instr.encoding() & 0x80000000) == 0) {
    Print("vsetvli 'rd, 'rs1, 'vtypei", instr, RV_V);
  } else {
    // vsetivli
    // vsetvl
    UnknownInstruction(instr);
  }
}

void RISCVDisassembler::DisassembleOPV_IVX(Instr instr) {
  switch (instr.funct6()) {
    case VMV:
      Print("vmv.v.x 'vd, 'rs1'vm", instr, RV_V);
      break;
    default:
      UnknownInstruction(instr);
      break;
  }
}

void RISCVDisassembler::UnknownInstruction(Instr instr) {
  if (instr.encoding() == 0) {
    Print("trap", instr, RV_I);
  } else {
    Print("unknown", instr, ExtensionSet::Empty());
  }
}

void RISCVDisassembler::UnknownInstruction(CInstr instr) {
  if (instr.encoding() == 0) {
    Print("trap", instr, RV_I);
  } else {
    Print("unknown", instr, ExtensionSet::Empty());
  }
}

void RISCVDisassembler::Print(const char* format,
                              Instr instr,
                              ExtensionSet ex) {
  while (format[0] != '\0') {
    if (format[0] == '\'') {
      format = PrintOption(format + 1, instr);
    } else {
      Printf("%c", format[0]);
      format++;
    }
  }

  if (!Supports(ex)) {
    Printf(" ;; from unsupported extension");
  }
}

void RISCVDisassembler::Print(const char* format,
                              CInstr instr,
                              ExtensionSet ex) {
  while (format[0] != '\0') {
    if (format[0] == '\'') {
      format = PrintOption(format + 1, instr);
    } else {
      Printf("%c", format[0]);
      format++;
    }
  }

  if (!Supports(ex)) {
    Printf(" ;; from unsupported extension");
  }
}

#define STRING_STARTS_WITH(string, compare_string)                             \
  (strncmp(string, compare_string, strlen(compare_string)) == 0)

const char* RISCVDisassembler::PrintOption(const char* format, Instr instr) {
  if (STRING_STARTS_WITH(format, "rd")) {
    Printf("%s", cpu_reg_names[instr.rd()]);
    return format + 2;
  } else if (STRING_STARTS_WITH(format, "rs1")) {
    Printf("%s", cpu_reg_names[instr.rs1()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "rs2")) {
    Printf("%s", cpu_reg_names[instr.rs2()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "shamt")) {
    Printf("0x%x", instr.shamt());
    return format + 5;
  } else if (STRING_STARTS_WITH(format, "jimm")) {
    Printf("%+" Pd, static_cast<intptr_t>(instr.jtype_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "uimm")) {
    // objdump instead displays (imm >> 12) as hex.
    Printf("%" Pd, static_cast<intptr_t>(instr.utype_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "iimm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.itype_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "simm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.stype_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "bimm")) {
    Printf("%+" Pd, static_cast<intptr_t>(instr.btype_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "zimm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.zimm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "csr")) {
    Printf("0x%" Px, static_cast<intptr_t>(instr.csr()));
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "order")) {
    switch (instr.memory_order()) {
      case std::memory_order_relaxed:
        break;
      case std::memory_order_acquire:
        Printf(".aq");
        break;
      case std::memory_order_release:
        Printf(".rl");
        break;
      case std::memory_order_acq_rel:
        Printf(".aqrl");
        break;
      default:
        UNREACHABLE();
    }
    return format + 5;
  } else if (STRING_STARTS_WITH(format, "round")) {
    switch (instr.rounding()) {
      case RNE:
        // Printf(", rne");
        break;
      case RTZ:
        Printf(", rtz");
        break;
      case RDN:
        Printf(", rdn");
        break;
      case RUP:
        Printf(", rup");
        break;
      case RMM:
        Printf(", rmm");
        break;
      case DYN:
        Printf(", dyn");
        break;
      default:
        Printf("<invalid rounding mode>");
    }
    return format + 5;
  } else if (STRING_STARTS_WITH(format, "predsucc")) {
    HartEffects pred = static_cast<HartEffects>((instr.itype_imm() >> 4) & 0xF);
    HartEffects succ = static_cast<HartEffects>((instr.itype_imm() >> 0) & 0xF);
    if ((pred != HartEffects::kAll) || (succ != HartEffects::kAll)) {
      Printf(" ");
      if ((pred & HartEffects::kInput) != 0) Printf("i");
      if ((pred & HartEffects::kOutput) != 0) Printf("o");
      if ((pred & HartEffects::kRead) != 0) Printf("r");
      if ((pred & HartEffects::kWrite) != 0) Printf("w");
      if (pred == 0) Printf("0");
      Printf(",");
      if ((succ & HartEffects::kInput) != 0) Printf("i");
      if ((succ & HartEffects::kOutput) != 0) Printf("o");
      if ((succ & HartEffects::kRead) != 0) Printf("r");
      if ((succ & HartEffects::kWrite) != 0) Printf("w");
      if (succ == 0) Printf("0");
    }
    return format + 8;
  } else if (STRING_STARTS_WITH(format, "moprn")) {
    Printf("%" Pd, DecodeMoprn(instr.encoding()));
    return format + 5;
  } else if (STRING_STARTS_WITH(format, "moprrn")) {
    Printf("%" Pd, DecodeMoprrn(instr.encoding()));
    return format + 6;
  } else if (STRING_STARTS_WITH(format, "prefetchimm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.itype_imm()) & ~0b11111);
    return format + 11;
  } else if (STRING_STARTS_WITH(format, "frd")) {
    Printf("%s", fpu_reg_names[instr.frd()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "frs1")) {
    Printf("%s", fpu_reg_names[instr.frs1()]);
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "frs2")) {
    Printf("%s", fpu_reg_names[instr.frs2()]);
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "frs3")) {
    Printf("%s", fpu_reg_names[instr.frs3()]);
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "flis")) {
    intptr_t index = instr.frs1();
    switch (index) {
      case 1:
        Printf("min");
        break;
      case 30:
        Printf("inf");
        break;
      case 31:
        Printf("nan");
        break;
      default:
        Printf("%f", bit_cast<float>(kFlisConstants[index]));
        break;
    }
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "flid")) {
    intptr_t index = instr.frs1();
    switch (index) {
      case 1:
        Printf("min");
        break;
      case 30:
        Printf("inf");
        break;
      case 31:
        Printf("nan");
        break;
      default:
        Printf("%lf", bit_cast<double>(kFlidConstants[index]));
        break;
    }
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "vd")) {
    Printf("%s", vector_reg_names[instr.vd()]);
    return format + 2;
  } else if (STRING_STARTS_WITH(format, "vs1")) {
    Printf("%s", vector_reg_names[instr.vs1()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "vs2")) {
    Printf("%s", vector_reg_names[instr.vs2()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "vs3")) {
    Printf("%s", vector_reg_names[instr.vs3()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "vtypei")) {
    intx_t vtypei = instr.itype_imm();

    // SEW
    switch ((vtypei >> 3) & 0b111) {
      case e8:
        Printf("e8");
        break;
      case e16:
        Printf("e16");
        break;
      case e32:
        Printf("e32");
        break;
      case e64:
        Printf("e64");
        break;
      default:
        Printf("invalid sew");
        break;
    }

    // LMUL
    switch ((vtypei >> 0) & 0b111) {
      case mf8:
        Printf(", mf8");
        break;
      case mf4:
        Printf(", mf4");
        break;
      case mf2:
        Printf(", mf2");
        break;
      case m1:
        Printf(", m1");
        break;
      case m2:
        Printf(", m2");
        break;
      case m4:
        Printf(", m4");
        break;
      case m8:
        Printf(", m8");
        break;
      default:
        Printf(", invalid lmul");
        break;
    }

    // VTA
    if ((vtypei & (1 << 6)) == 0) {
      Printf(", tu");
    } else {
      Printf(", ta");
    }

    // VMA
    if ((vtypei & (1 << 7)) == 0) {
      Printf(", mu");
    } else {
      Printf(", ma");
    }

    return format + 6;
  } else if (STRING_STARTS_WITH(format, "vm")) {
    if (instr.vm()) {
      Printf(", v0.t");
    }
    return format + 2;
  }

  FATAL("Bad format %s\n", format);
  return nullptr;
}

const char* RISCVDisassembler::PrintOption(const char* format, CInstr instr) {
  if (STRING_STARTS_WITH(format, "rdp")) {
    Printf("%s", cpu_reg_names[instr.rdp()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "rs1p")) {
    Printf("%s", cpu_reg_names[instr.rs1p()]);
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "rs2p")) {
    Printf("%s", cpu_reg_names[instr.rs2p()]);
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "rd")) {
    Printf("%s", cpu_reg_names[instr.rd()]);
    return format + 2;
  } else if (STRING_STARTS_WITH(format, "rs1")) {
    Printf("%s", cpu_reg_names[instr.rs1()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "rs2")) {
    Printf("%s", cpu_reg_names[instr.rs2()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "frdp")) {
    Printf("%s", fpu_reg_names[instr.frdp()]);
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "frs1p")) {
    Printf("%s", fpu_reg_names[instr.frs1p()]);
    return format + 5;
  } else if (STRING_STARTS_WITH(format, "frs2p")) {
    Printf("%s", fpu_reg_names[instr.frs2p()]);
    return format + 5;
  } else if (STRING_STARTS_WITH(format, "frd")) {
    Printf("%s", fpu_reg_names[instr.frd()]);
    return format + 3;
  } else if (STRING_STARTS_WITH(format, "frs1")) {
    Printf("%s", fpu_reg_names[instr.frs1()]);
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "frs2")) {
    Printf("%s", fpu_reg_names[instr.frs2()]);
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "spload4imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.spload4_imm()));
    return format + 10;
  } else if (STRING_STARTS_WITH(format, "spload8imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.spload8_imm()));
    return format + 10;
  } else if (STRING_STARTS_WITH(format, "spstore4imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.spstore4_imm()));
    return format + 11;
  } else if (STRING_STARTS_WITH(format, "spstore8imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.spstore8_imm()));
    return format + 11;
  } else if (STRING_STARTS_WITH(format, "mem1imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.mem1_imm()));
    return format + 7;
  } else if (STRING_STARTS_WITH(format, "mem2imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.mem2_imm()));
    return format + 7;
  } else if (STRING_STARTS_WITH(format, "mem4imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.mem4_imm()));
    return format + 7;
  } else if (STRING_STARTS_WITH(format, "mem8imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.mem8_imm()));
    return format + 7;
  } else if (STRING_STARTS_WITH(format, "jimm")) {
    Printf("%+" Pd, static_cast<intptr_t>(instr.j_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "bimm")) {
    Printf("%+" Pd, static_cast<intptr_t>(instr.b_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "iimm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.i_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "uimm")) {
    // objdump instead displays (imm >> 12) as hex.
    Printf("%" Pd, static_cast<intptr_t>(instr.u_imm()));
    return format + 4;
  } else if (STRING_STARTS_WITH(format, "i16imm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.i16_imm()));
    return format + 6;
  } else if (STRING_STARTS_WITH(format, "i4spnimm")) {
    Printf("%" Pd, static_cast<intptr_t>(instr.i4spn_imm()));
    return format + 8;
  } else if (STRING_STARTS_WITH(format, "shamt")) {
    Printf("0x%x", instr.shamt());
    return format + 5;
  } else if (STRING_STARTS_WITH(format, "mopn")) {
    Printf("%" Pd, DecodeCMopn(instr.encoding()));
    return format + 4;
  }

  FATAL("Bad format %s\n", format);
  return nullptr;
}

ExtensionSet Disassembler::extensions_ = RV_baseline;

void Disassembler::DecodeInstruction(char* hex_buffer,
                                     intptr_t hex_size,
                                     char* human_buffer,
                                     intptr_t human_size,
                                     int* out_instr_size,
                                     const Code& code,
                                     Object** object,
                                     uword pc) {
  RISCVDisassembler decoder(human_buffer, human_size, extensions_);
  int instr_size = decoder.Disassemble(pc);
  if (instr_size == 2) {
    Utils::SNPrint(hex_buffer, hex_size, "    %04x",
                   *reinterpret_cast<uint16_t*>(pc));
  } else if (instr_size == 4) {
    Utils::SNPrint(hex_buffer, hex_size, "%08x",
                   LoadUnaligned(reinterpret_cast<uint32_t*>(pc)));
  }
  if (out_instr_size != nullptr) {
    *out_instr_size = instr_size;
  }

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

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

}  // namespace dart

#endif  // defined(TARGET_ARCH_RISCV)
