/*!
 * @file InstructionDecode.cpp
 * The Instruction Decoder - converts a LinkedWord into a Instruction.
 * This is the part of the disassembler that decodes MIPS instructions.
 */

#include "InstructionDecode.h"

#include "common/util/Assert.h"

#include "decompiler/ObjectFile/LinkedObjectFile.h"

#include "third-party/fmt/core.h"

namespace decompiler {
// utility class to extract fields of an opcode.
struct OpcodeFields {
  OpcodeFields(uint32_t _data) : data(_data) {}

  // 26 - 31
  uint32_t op() { return (data >> 26); }

  //////////////
  // R - Type //
  //////////////

  // 21 - 25
  uint32_t rs() { return (data >> 21) & 0x1f; }

  // 16 - 20
  uint32_t rt() { return (data >> 16) & 0x1f; }

  // 11 - 15
  uint32_t rd() { return (data >> 11) & 0x1f; }

  //  6 - 10
  uint32_t sa() { return (data >> 6) & 0x1f; }

  // 0 - 5
  uint32_t function() { return (data)&0x3f; }

  ////////////////
  // Immediates //
  ////////////////

  int32_t simm16() { return (int16_t)(data); }
  int32_t zimm16() { return (uint16_t)(data); }
  uint32_t imm5() { return (data >> 6) & 0x1f; }
  uint32_t imm15() { return (data >> 6) & 0b111111111111111; }

  ////////////////////
  // Floating Point //
  ////////////////////

  uint32_t cop_func() { return (data >> 21) & 0x1f; }
  uint32_t ft() { return (data >> 16) & 0x1f; }
  uint32_t fs() { return (data >> 11) & 0x1f; }
  uint32_t fd() { return (data >> 6) & 0x1f; }

  ////////////
  // Others //
  ////////////

  uint32_t pcreg() { return (data >> 1) & 0x1f; }
  uint32_t syscall() { return (data >> 6) & 0xfffff; }
  uint32_t MMI_func() { return (data >> 6) & 0x1f; }
  uint32_t lower11() { return (uint32_t)(data & 0x7ff); }
  uint32_t lower6() { return (uint32_t)(data & 0b111111); }
  uint32_t dest() { return (data >> 21) & 0b1111; }
  uint32_t fs_f() { return (data >> 21) & 0b11; }
  uint32_t ft_f() { return (data >> 23) & 0b11; }

  uint32_t data;
};

//////////////////
// OPCODE DECODE
//////////////////

static InstructionKind decode_cop2(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.lower11()) {
    case 0b0:
    case 0b1:
      switch (fields.cop_func()) {
        case 0b00001:
          return IK::QMFC2;

        case 0b00101:
          ASSERT(((fields.data >> 1) & (0b1111111111)) == 0);
          return IK::QMTC2;

        case 0b00010:
          return IK::CFC2;

        case 0b00110:
          return IK::CTC2;

        default:
          ASSERT(false);
          return IK::UNKNOWN;
      }
      break;

    case 0b00010111100:
    case 0b00010111101:
    case 0b00010111110:
    case 0b00010111111:
      ASSERT(fields.data & (1 << 25));
      return IK::VMADDA_BC;

    case 0b00000111100:
    case 0b00000111101:
    case 0b00000111110:
    case 0b00000111111:
      ASSERT(fields.data & (1 << 25));
      return IK::VADDA_BC;

    case 0b00110111100:
    case 0b00110111101:
    case 0b00110111110:
    case 0b00110111111:
      ASSERT(fields.data & (1 << 25));
      return IK::VMULA_BC;

    case 0b01010111110:
      ASSERT(fields.data & (1 << 25));
      return IK::VMULA;

    case 0b01010111100:
      ASSERT(fields.data & (1 << 25));
      return IK::VADDA;

    case 0b01010111101:
      ASSERT(fields.data & (1 << 25));
      return IK::VMADDA;

    case 0b00011111100:
    case 0b00011111101:
    case 0b00011111110:
    case 0b00011111111:
      ASSERT(fields.data & (1 << 25));
      return IK::VMSUBA_BC;

    case 0b00101111100:
      ASSERT(fields.data & (1 << 25));
      return IK::VFTOI0;

    case 0b00101111101:
      ASSERT(fields.data & (1 << 25));
      return IK::VFTOI4;

    case 0b00101111110:
      ASSERT(fields.data & (1 << 25));
      return IK::VFTOI12;

    case 0b00101111111:
      ASSERT(fields.data & (1 << 25));
      return IK::VFTOI15;

    case 0b00100111100:
      ASSERT(fields.data & (1 << 25));
      return IK::VITOF0;

    case 0b00100111110:
      ASSERT(fields.data & (1 << 25));
      return IK::VITOF12;

    case 0b00100111111:
      ASSERT(fields.data & (1 << 25));
      return IK::VITOF15;

    case 0b00111111100:
      ASSERT(fields.data & (1 << 25));
      ASSERT(fields.ft() == 0);
      return IK::VMULAQ;

    case 0b00111111101:
      ASSERT(fields.data & (1 << 25));
      return IK::VABS;

    case 0b00111111111:
      ASSERT(fields.data & (1 << 25));
      ASSERT(fields.dest() == 0b1110);
      return IK::VCLIP;

    case 0b01011111111:
      ASSERT(fields.dest() == 0);
      ASSERT(fields.ft() == 0);
      ASSERT(fields.fs() == 0);
      return IK::VNOP;
    case 0b01101111101:
      ASSERT(fields.data & (1 << 25));
      return IK::VSQI;
    case 0b01101111100:
      ASSERT(fields.data & (1 << 25));
      return IK::VLQI;
    case 0b01110111111:
      ASSERT(fields.dest() == 0);
      ASSERT(fields.ft() == 0);
      ASSERT(fields.fs() == 0);
      return IK::VWAITQ;

    case 0b01011111110:
      ASSERT(fields.dest() == 0b1110);
      ASSERT(fields.data & (1 << 25));
      return IK::VOPMULA;

    case 0b01011111101:
      return IK::VMSUBA;

    case 0b01100111100:
      ASSERT(fields.data & (1 << 25));
      return IK::VMOVE;

    case 0b01100111101:
      return IK::VMR32;

    case 0b01110111100:
      ASSERT(fields.data & (1 << 25));
      return IK::VDIV;

    case 0b01110111101:
      ASSERT(fields.fs() == 0);
      ASSERT(((fields.data >> 21) & 3) == 0);
      ASSERT(fields.data & (1 << 25));
      return IK::VSQRT;

    case 0b01111111100:
      ASSERT(((fields.data >> 23) & 3) == 0);
      ASSERT(fields.data & (1 << 25));
      return IK::VMTIR;

    case 0b01110111110:
      ASSERT(fields.data & (1 << 25));
      return IK::VRSQRT;

    case 0b10000111100:
      ASSERT(fields.fs() == 0);
      ASSERT(fields.data & (1 << 25));
      return IK::VRNEXT;

    case 0b10000111101:
      ASSERT(fields.fs() == 0);
      ASSERT(fields.data & (1 << 25));
      return IK::VRGET;

    case 0b10000111111:
      ASSERT(fields.ft() == 0);
      ASSERT(fields.data & (1 << 25));
      ASSERT(((fields.data >> 23) & 3) == 0);
      return IK::VRXOR;
    default:

      switch (fields.lower6()) {
        case 0b000000:
        case 0b000001:
        case 0b000010:
        case 0b000011:
          ASSERT(fields.data & (1 << 25));
          return IK::VADD_BC;

        case 0b000100:
        case 0b000101:
        case 0b000110:
        case 0b000111:
          ASSERT(fields.data & (1 << 25));
          return IK::VSUB_BC;

        case 0b001000:
        case 0b001001:
        case 0b001010:
        case 0b001011:
          ASSERT(fields.data & (1 << 25));
          return IK::VMADD_BC;

        case 0b001100:
        case 0b001101:
        case 0b001110:
        case 0b001111:
          ASSERT(fields.data & (1 << 25));
          return IK::VMSUB_BC;

        case 0b010000:
        case 0b010001:
        case 0b010010:
        case 0b010011:
          ASSERT(fields.data & (1 << 25));
          return IK::VMAX_BC;

        case 0b010100:
        case 0b010101:
        case 0b010110:
        case 0b010111:
          ASSERT(fields.data & (1 << 25));
          return IK::VMINI_BC;

        case 0b011000:
        case 0b011001:
        case 0b011010:
        case 0b011011:
          ASSERT(fields.data & (1 << 25));
          return IK::VMUL_BC;

        case 0b011100:
          ASSERT(fields.ft() == 0);
          ASSERT(fields.data & (1 << 25));
          return IK::VMULQ;

        case 0b100000:
          ASSERT(fields.data & (1 << 25));
          return IK::VADDQ;

        case 0b100100:
          ASSERT(fields.data & (1 << 25));
          return IK::VSUBQ;

        case 0b100101:
          ASSERT(fields.ft() == 0);
          ASSERT(fields.data & (1 << 25));
          return IK::VMSUBQ;

        case 0b101000:
          ASSERT(fields.data & (1 << 25));
          return IK::VADD;
        case 0b101001:
          ASSERT(fields.data & (1 << 25));
          return IK::VMADD;
        case 0b101010:
          ASSERT(fields.data & (1 << 25));
          return IK::VMUL;
        case 0b101011:
          ASSERT(fields.data & (1 << 25));
          return IK::VMAX;
        case 0b101100:
          ASSERT(fields.data & (1 << 25));
          return IK::VSUB;
        case 0b101101:
          ASSERT(fields.data & (1 << 25));
          return IK::VMSUB;
        case 0b101110:
          ASSERT(fields.data & (1 << 25));
          ASSERT(fields.dest() == 0b1110);
          return IK::VOPMSUB;
        case 0b101111:
          ASSERT(fields.data & (1 << 25));
          return IK::VMINI;
        case 0b110010:
          ASSERT(fields.data & (1 << 25));
          ASSERT(fields.dest() == 0b0);
          return IK::VIADDI;
        case 0b110100:
          ASSERT(fields.data & (1 << 25));
          ASSERT(fields.dest() == 0b0);
          return IK::VIAND;
        case 0b111000:
          ASSERT(fields.data & (1 << 25));
          ASSERT(fields.dest() == 0b0);
          return IK::VCALLMS;
        default:
          ASSERT_MSG(false, fmt::format("unknown cop2 lower11 case 0b{:b}\n", fields.lower11()));
          return IK::UNKNOWN;
      }
  }
}

static InstructionKind decode_W(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.function()) {
    case 0b100000:
      ASSERT(fields.ft() == 0);
      return IK::CVTSW;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_S(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.function()) {
    case 0b000000:
      return IK::ADDS;
    case 0b000001:
      return IK::SUBS;
    case 0b000010:
      return IK::MULS;
    case 0b000011:
      return IK::DIVS;
    case 0b000101:
      return IK::ABSS;
    case 0b000110:
      ASSERT(fields.ft() == 0);
      return IK::MOVS;
    case 0b000111:
      ASSERT(fields.ft() == 0);
      return IK::NEGS;
    case 0b000100:
      ASSERT(fields.fs() == 0);
      return IK::SQRTS;
    case 0b010110:
      return IK::RSQRTS;
    case 0b011000:
      ASSERT(fields.fd() == 0);
      return IK::ADDAS;
    case 0b011010:
      ASSERT(fields.fd() == 0);
      return IK::MULAS;
    case 0b011100:
      return IK::MADDS;
    case 0b011101:
      return IK::MSUBS;
    case 0b011110:
      ASSERT(fields.fd() == 0);
      return IK::MADDAS;
    case 0b011111:
      ASSERT(fields.fd() == 0);
      return IK::MSUBAS;
    case 0b100100:
      ASSERT(fields.ft() == 0);
      return IK::CVTWS;
    case 0b101000:
      return IK::MAXS;
    case 0b101001:
      return IK::MINS;
    case 0b110010:
      ASSERT(fields.fd() == 0);
      return IK::CEQS;
    case 0b110100:
      ASSERT(fields.fd() == 0);
      return IK::CLTS;
    case 0b110110:
      ASSERT(fields.fd() == 0);
      return IK::CLES;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_BC1(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.ft()) {
    case 0b00000:
      return IK::BC1F;
    case 0b00001:
      return IK::BC1T;
    case 0b00010:
      return IK::BC1FL;
    case 0b00011:
      return IK::BC1TL;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_cop1(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.cop_func()) {
    case 0b00000:
      ASSERT(fields.sa() == 0);
      ASSERT(fields.function() == 0);
      return IK::MFC1;
    case 0b00100:
      ASSERT(fields.sa() == 0);
      ASSERT(fields.function() == 0);
      return IK::MTC1;
    case 0b01000:
      return decode_BC1(fields);
    case 0b10000:
      return decode_S(fields);
    case 0b10100:
      return decode_W(fields);
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_c0(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.function()) {
    case 0b011000:
      return IK::ERET;
    case 0b111000:
      ASSERT(fields.sa() == 0 && fields.rd() == 0 && fields.rt() == 0);
      return IK::EI;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_mt0(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.lower11()) {
    case 0b00000000000:
      return IK::MTC0;
    case 0b00000000100:
      ASSERT(fields.rd() == 0b11000);
      return IK::MTDAB;
    case 0b00000000101:
      ASSERT(fields.rd() == 0b11000);
      return IK::MTDABM;
    default:
      if (fields.rd() == 0b11001 && fields.sa() == 0 && (fields.data & 1) == 1) {
        return IK::MTPC;
      } else {
        ASSERT(false);
        return IK::UNKNOWN;
      }
  }
}

static InstructionKind decode_mf0(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.lower11()) {
    case 0b0:
      return IK::MFC0;
    default:
      if (fields.rd() == 0b11001 && fields.sa() == 0 && (fields.data & 1) == 1) {
        return IK::MFPC;
      } else {
        ASSERT(false);
        return IK::UNKNOWN;
      }
  }
}

static InstructionKind decode_cop0(OpcodeFields fields) {
  switch (fields.cop_func()) {
    case 0b00000:
      return decode_mf0(fields);
    case 0b00100:
      return decode_mt0(fields);
    case 0b10000:
      return decode_c0(fields);
    default:
      ASSERT(false);
      return InstructionKind::UNKNOWN;
  }
}

static InstructionKind decode_mmi3(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.MMI_func()) {
    case 0b01010:
      return IK::PINTEH;
    case 0b01110:
      return IK::PCPYUD;
    case 0b10010:
      return IK::POR;
    case 0b10011:
      return IK::PNOR;
    case 0b11011:
      ASSERT(fields.rs() == 0);
      return IK::PCPYH;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_mmi2(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.MMI_func()) {
    case 0b01110:
      return IK::PCPYLD;
    case 0b10000:
      return IK::PMADDH;
    case 0b10010:
      return IK::PAND;
    case 0b10011:
      return IK::PXOR;
    case 0b11100:
      return IK::PMULTH;
    case 0b11110:
      return IK::PEXEW;
    case 0b11111:
      return IK::PROT3W;
    default:
      ASSERT_MSG(false, fmt::format("unknown mmi2: 0b{:b}\n", fields.MMI_func()));
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_mmi1(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.MMI_func()) {
    case 0b00001:
      return IK::PABSW;
    case 0b00010:
      return IK::PCEQW;
    case 0b00011:
      return IK::PMINW;
    case 0b00111:
      return IK::PMINH;
    case 0b01010:
      return IK::PCEQB;
    case 0b10010:
      return IK::PEXTUW;
    case 0b10110:
      return IK::PEXTUH;
    case 0b11010:
      return IK::PEXTUB;
    case 0b11011:
      return IK::QFSRV;
    default:
      ASSERT_MSG(false, fmt::format("unknown mmi1: 0b{:b}\n", fields.MMI_func()));
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_mmi0(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.MMI_func()) {
    case 0b00000:
      return IK::PADDW;
    case 0b00001:
      return IK::PSUBW;
    case 0b00010:
      return IK::PCGTW;
    case 0b00011:
      return IK::PMAXW;
    case 0b00100:
      return IK::PADDH;
    case 0b00101:
      return IK::PSUBH;
    case 0b00111:
      return IK::PMAXH;
    case 0b01000:
      return IK::PADDB;
    case 0b01010:
      return IK::PCGTB;
    case 0b10010:
      return IK::PEXTLW;
    case 0b10011:
      return IK::PPACW;
    case 0b10111:
      return IK::PPACH;
    case 0b10110:
      return IK::PEXTLH;
    case 0b11010:
      return IK::PEXTLB;
    case 0b11011:
      return IK::PPACB;
    default:
      ASSERT_MSG(false, fmt::format("unknown mmi0: 0b{:b}\n", fields.MMI_func()));
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_pmfhl(OpcodeFields fields) {
  // the PMFHL instruction is split into several types, and we create different instructions for
  // each.
  typedef InstructionKind IK;
  switch (fields.sa()) {
    case 0b00001:
      ASSERT(fields.rs() == 0);
      ASSERT(fields.rt() == 0);
      return IK::PMFHL_UW;
    case 0b00000:
      ASSERT(fields.rs() == 0);
      ASSERT(fields.rt() == 0);
      return IK::PMFHL_LW;
    case 0b00011:
      ASSERT(fields.rs() == 0);
      ASSERT(fields.rt() == 0);
      return IK::PMFHL_LH;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_mmi(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.function()) {
    case 0b000100:
      ASSERT(fields.sa() == 0);
      ASSERT(fields.rt() == 0);
      return IK::PLZCW;
    case 0b001000:
      return decode_mmi0(fields);
    case 0b001001:
      return decode_mmi2(fields);

    case 0b010011:
      ASSERT(fields.sa() == 0);
      ASSERT(fields.rd() == 0);
      ASSERT(fields.rt() == 0);
      return IK::MTLO1;
    case 0b010010:
      ASSERT(fields.sa() == 0);
      ASSERT(fields.rs() == 0);
      ASSERT(fields.rt() == 0);
      return IK::MFLO1;

    case 0b101000:
      return decode_mmi1(fields);
    case 0b101001:
      return decode_mmi3(fields);
    case 0b110000:
      return decode_pmfhl(fields);
    case 0b110100:
      return IK::PSLLH;
    case 0b110110:
      return IK::PSRLH;
    case 0b110111:
      return IK::PSRAH;
    case 0b111100:
      return IK::PSLLW;
    case 0b111111:
      return IK::PSRAW;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_regimm(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.rt()) {
    case 0b00000:
      return IK::BLTZ;
    case 0b00001:
      return IK::BGEZ;
    case 0b00010:
      return IK::BLTZL;
    case 0b00011:
      return IK::BGEZL;
    case 0b10001:
      return IK::BGEZAL;
    case 0b11000:
      return IK::MTSAB;
    default:
      ASSERT_MSG(false, fmt::format("unknown regimm: 0b{:b}\n", fields.rt()));
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_sync(OpcodeFields fields) {
  // the "sync" opcode has a "stype" field which picks between P and L type syncs.
  // to avoid implementing this, we just split SYNC into two separate instructions.
  typedef InstructionKind IK;
  auto stype = fields.sa();
  ASSERT(fields.rt() == 0);
  ASSERT(fields.rs() == 0);
  ASSERT(fields.rd() == 0);
  if (stype == 0b00000) {
    return IK::SYNCL;
  } else if (stype == 0b10000) {
    return IK::SYNCP;
  } else {
    ASSERT(false);
    return IK::UNKNOWN;
  }
}

static InstructionKind decode_special(OpcodeFields fields) {
  typedef InstructionKind IK;
  switch (fields.function()) {
    case 0b000000:
      ASSERT(fields.rs() == 0);
      return IK::SLL;
    // RESERVED
    case 0b000010:
      ASSERT(fields.rs() == 0);
      return IK::SRL;
    case 0b000011:
      ASSERT(fields.rs() == 0);
      return IK::SRA;
    case 0b000100:
      ASSERT(fields.sa() == 0);
      return IK::SLLV;
    // RESERVED
    // SRLV
    // SRAV
    case 0b001000:
      ASSERT(fields.sa() == 0);
      ASSERT(fields.rd() == 0);
      ASSERT(fields.rt() == 0);
      return IK::JR;
    case 0b001001:
      ASSERT(fields.rt() == 0);
      ASSERT(fields.sa() == 0);
      return IK::JALR;
    case 0b001010:
      ASSERT(fields.sa() == 0);
      return IK::MOVZ;
    case 0b001011:
      ASSERT(fields.sa() == 0);
      return IK::MOVN;
    case 0b001100:
      return IK::SYSCALL;
    // BREAK
    // RESERVED
    case 0b001111:
      return decode_sync(fields);

    case 0b010000:
      ASSERT(fields.rs() == 0);
      ASSERT(fields.rt() == 0);
      ASSERT(fields.sa() == 0);
      return IK::MFHI;
    // MTHI
    case 0b010010:
      ASSERT(fields.rs() == 0);
      ASSERT(fields.rt() == 0);
      ASSERT(fields.sa() == 0);
      return IK::MFLO;
    // MTLO
    case 0b010100:
      ASSERT(fields.sa() == 0);
      return IK::DSLLV;
    // RESERVED
    case 0b010110:
      ASSERT(fields.sa() == 0);
      return IK::DSRLV;
    case 0b010111:
      ASSERT(fields.sa() == 0);
      return IK::DSRAV;
    case 0b011000:
      ASSERT(fields.sa() == 0);
      return IK::MULT3;
    case 0b011001:
      ASSERT(fields.sa() == 0);
      return IK::MULTU3;
    case 0b011010:
      ASSERT(fields.sa() == 0);
      ASSERT(fields.rd() == 0);
      return IK::DIV;
    case 0b011011:
      ASSERT(fields.sa() == 0);
      ASSERT(fields.rd() == 0);
      return IK::DIVU;
    // 4x UNSUPPORTED
    // ADD
    case 0b100001:
      ASSERT(fields.sa() == 0);
      return IK::ADDU;
    // SUB
    case 0b100011:
      ASSERT(fields.sa() == 0);
      return IK::SUBU;
    case 0b100100:
      ASSERT(fields.sa() == 0);
      return IK::AND;
    case 0b100101:
      ASSERT(fields.sa() == 0);
      return IK::OR;
    case 0b100110:
      ASSERT(fields.sa() == 0);
      return IK::XOR;
    case 0b100111:
      ASSERT(fields.sa() == 0);
      return IK::NOR;
    // MFSA
    // MTSA
    case 0b101010:
      ASSERT(fields.sa() == 0);
      return IK::SLT;
    case 0b101011:
      ASSERT(fields.sa() == 0);
      return IK::SLTU;
    // DADD
    case 0b101101:
      return IK::DADDU;
    // DSUB
    case 0b101111:
      return IK::DSUBU;
    // TGE
    // TGEU
    // TLT
    // TLTU
    // TEQ
    // RESERVED
    // TNE
    // RESERVED
    case 0b111000:
      ASSERT(fields.rs() == 0);
      return IK::DSLL;
    // RESERVED
    case 0b111010:
      ASSERT(fields.rs() == 0);
      return IK::DSRL;
    case 0b111011:
      ASSERT(fields.rs() == 0);
      return IK::DSRA;
    case 0b111100:
      ASSERT(fields.rs() == 0);
      return IK::DSLL32;
    // RESERVED
    case 0b111110:
      ASSERT(fields.rs() == 0);
      return IK::DSRL32;
    case 0b111111:
      ASSERT(fields.rs() == 0);
      return IK::DSRA32;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

static InstructionKind decode_cache(OpcodeFields fields) {
  typedef InstructionKind IK;
  // there's only one cache instruction used (DXWBIN), so we just use a CACHE DXWBIN instruction
  // to avoid having to implement the full cache instruction decoding.
  switch (fields.rt()) {
    case 0b10100:
      return IK::CACHE_DXWBIN;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
  }
}

/*!
 * Top level opcode decode
 */
static InstructionKind decode_opcode(uint32_t code) {
  OpcodeFields fields(code);
  typedef InstructionKind IK;
  switch (fields.op()) {
    case 0b000000:
      return decode_special(fields);
    case 0b000001:
      return decode_regimm(fields);
    // J      010
    // JAL    011
    case 0b000100:
      return IK::BEQ;
    case 0b000101:
      return IK::BNE;
    case 0b000110:
      return IK::BLEZ;
    case 0b000111:
      return IK::BGTZ;
    // ADDI  1000
    case 0b001001:
      return IK::ADDIU;
    case 0b001010:
      return IK::SLTI;
    case 0b001011:
      return IK::SLTIU;
    case 0b001100:
      return IK::ANDI;
    case 0b001101:
      return IK::ORI;
    case 0b001110:
      return IK::XORI;
    case 0b001111:
      ASSERT(fields.rs() == 0);
      return IK::LUI;
    case 0b010000:
      return decode_cop0(fields);
    case 0b010001:
      return decode_cop1(fields);
    case 0b010010:
      return decode_cop2(fields);
    //     010011:
    //  reserved
    case 0b010100:
      return IK::BEQL;
    case 0b010101:
      return IK::BNEL;
    //     010110
    //  blezl
    case 0b010111:
      ASSERT(fields.rt() == 0);
      return IK::BGTZL;
    //   0b011000:
    //  daddi
    case 0b011001:
      return IK::DADDIU;
    case 0b011010:
      return IK::LDL;
    case 0b011011:
      return IK::LDR;
    case 0b011100:
      return decode_mmi(fields);
    //   0b011101:
    // reserved
    case 0b011110:
      return IK::LQ;
    case 0b011111:
      return IK::SQ;
    case 0b100000:
      return IK::LB;
    case 0b100001:
      return IK::LH;
    case 0b100010:
      return IK::LWL;
    case 0b100011:
      return IK::LW;
    case 0b100100:
      return IK::LBU;
    case 0b100101:
      return IK::LHU;
    case 0b100110:
      return IK::LWR;
    case 0b100111:
      return IK::LWU;
    case 0b101000:
      return IK::SB;
    case 0b101001:
      return IK::SH;
    case 0b101011:
      return IK::SW;
    // SDL
    // SDR
    // SWR
    case 0b101111:
      return decode_cache(fields);

    // unsupported
    case 0b110001:
      return IK::LWC1;
    // unsupported
    case 0b110011:
      return IK::PREF;
    // unsupported
    // unsupported
    case 0b110110:
      return IK::LQC2;
    case 0b110111:
      return IK::LD;
    case 0b111001:
      return IK::SWC1;
    case 0b111110:
      return IK::SQC2;
    case 0b111111:
      return IK::SD;
    default:
      ASSERT(false);
      return IK::UNKNOWN;
      break;
  }
}

/*!
 * Top level decode function.
 */
Instruction decode_instruction(LinkedWord& word, LinkedObjectFile& file, int seg_id, int word_id) {
  // determine the opcode, and get info for it
  Instruction i;
  auto op = decode_opcode(word.data);
  auto& info = gOpcodeInfo[(int)op];
  if (!info.defined) {
    return i;
  }
  i.kind = op;
  OpcodeFields fields(word.data);

  // loop through decoding steps to extract a value
  for (int j = 0; j < info.step_count; j++) {
    auto& step = info.steps[j];
    int32_t value;
    switch (step.field) {
      case FieldType::RS:
        value = fields.rs();
        break;
      case FieldType::RT:
        value = fields.rt();
        break;
      case FieldType::RD:
        value = fields.rd();
        break;
      case FieldType::FT:
        value = fields.ft();
        break;
      case FieldType::FS:
        value = fields.fs();
        break;
      case FieldType::FD:
        value = fields.fd();
        break;
      case FieldType::SIMM16:
        value = fields.simm16();
        break;
      case FieldType::ZIMM16:
        value = fields.zimm16();
        break;
      case FieldType::SA:
        value = fields.sa();
        break;
      case FieldType::SYSCALL:
        value = fields.syscall();
        break;
      case FieldType::PCR:
        value = fields.pcreg();
        break;
      case FieldType::DEST:
        value = fields.dest();
        break;
      case FieldType::BC:
        value = fields.data & 0b11;
        break;
      case FieldType::IMM5:
        value = fields.imm5();
        break;
      case FieldType::IL:
        value = fields.data & 1;
        break;
      case FieldType::IMM15:
        value = fields.imm15();
        break;
      case FieldType::ZERO:
        value = 0;
        break;
      case FieldType::FT_F:
        value = fields.ft_f();
        break;
      case FieldType::FS_F:
        value = fields.fs_f();
        break;
      default:
        ASSERT(false);
    }

    // use the value, to possibly add an atom
    InstructionAtom atom;
    switch (step.decode) {
      case DecodeType::GPR:
        atom.set_reg(Register(Reg::GPR, value));
        break;
      case DecodeType::FPR:
        atom.set_reg(Register(Reg::FPR, value));
        break;
      case DecodeType::COP0:
        atom.set_reg(Register(Reg::COP0, value));
        break;
      case DecodeType::PCR:
        atom.set_reg(Register(Reg::SPECIAL, Reg::PCR0 + value));
        break;
      case DecodeType::IMM:
        atom.set_imm(value);
        break;
      case DecodeType::VF:
        atom.set_reg(Register(Reg::VF, value));
        break;
      case DecodeType::VI:
        atom.set_reg(Register(Reg::VI, value));
        break;
      case DecodeType::VU_ACC:
        atom.set_vu_acc();
        break;
      case DecodeType::VU_Q:
        atom.set_vu_q();
        break;
      case DecodeType::VCALLMS_TARGET:
        atom.set_imm(value);
        break;
      case DecodeType::BRANCH_TARGET:
        atom.set_label(file.get_label_id_for(seg_id, (word_id + value + 1) * 4));
        break;
      case DecodeType::VF_F:
        atom.set_vf_field(value);
        break;

        // NOTE - these change a property of the instruction and don't add an atom.
      case DecodeType::IL:
        i.il = value;
        continue;
      case DecodeType::DEST:
        i.cop2_dest = value;
        continue;
      case DecodeType::BC:
        i.cop2_bc = value;
        continue;

      default:
        ASSERT(false);
    }

    if (step.is_src) {
      i.add_src(atom);
    } else {
      i.add_dst(atom);
    }
  }

  if (word.kind() == LinkedWord::SYM_OFFSET) {
    bool fixed = false;
    for (int j = 0; j < i.n_src; j++) {
      if (i.src[j].kind == InstructionAtom::IMM) {
        fixed = true;
        i.src[j].set_sym(word.symbol_name());
      }
    }
    ASSERT(fixed);
  }

  if (word.kind() == LinkedWord::HI_PTR) {
    ASSERT(i.kind == InstructionKind::LUI);
    bool fixed = false;
    for (int j = 0; j < i.n_src; j++) {
      if (i.src[j].kind == InstructionAtom::IMM) {
        fixed = true;
        i.src[j].set_label(word.label_id());
      }
    }
    ASSERT(fixed);
  }

  if (word.kind() == LinkedWord::LO_PTR) {
    ASSERT(i.kind == InstructionKind::ORI);
    bool fixed = false;
    for (int j = 0; j < i.n_src; j++) {
      if (i.src[j].kind == InstructionAtom::IMM) {
        fixed = true;
        i.src[j].set_label(word.label_id());
      }
    }
    ASSERT(fixed);
  }

  return i;
}
}  // namespace decompiler
