# Register IDs
MIPS_REG_R0 = MIPS_REG_F0 = 0
MIPS_REG_AT = MIPS_REG_F1 = 1
MIPS_REG_V0 = MIPS_REG_F2 = 2
MIPS_REG_V1 = MIPS_REG_F3 = 3
MIPS_REG_A0 = MIPS_REG_F4 = 4
MIPS_REG_A1 = MIPS_REG_F5 = 5
MIPS_REG_A2 = MIPS_REG_F6 = 6
MIPS_REG_A3 = MIPS_REG_F7 = 7
MIPS_REG_T0 = MIPS_REG_F8 = 8
MIPS_REG_T1 = MIPS_REG_F9 = 9
MIPS_REG_T2 = MIPS_REG_F10 = 10
MIPS_REG_T3 = MIPS_REG_F11 = 11
MIPS_REG_T4 = MIPS_REG_F12 = 12
MIPS_REG_T5 = MIPS_REG_F13 = 13
MIPS_REG_T6 = MIPS_REG_F14 = 14
MIPS_REG_T7 = MIPS_REG_F15 = 15
MIPS_REG_S0 = MIPS_REG_F16 = 16
MIPS_REG_S1 = MIPS_REG_F17 = 17
MIPS_REG_S2 = MIPS_REG_F18 = 18
MIPS_REG_S3 = MIPS_REG_F19 = 19
MIPS_REG_S4 = MIPS_REG_F20 = 20
MIPS_REG_S5 = MIPS_REG_F21 = 21
MIPS_REG_S6 = MIPS_REG_F22 = 22
MIPS_REG_S7 = MIPS_REG_F23 = 23
MIPS_REG_T8 = MIPS_REG_F24 = 24
MIPS_REG_T9 = MIPS_REG_F25 = 25
MIPS_REG_K0 = MIPS_REG_F26 = 26
MIPS_REG_K1 = MIPS_REG_F27 = 27
MIPS_REG_GP = MIPS_REG_F28 = 28
MIPS_REG_SP = MIPS_REG_F29 = 29
MIPS_REG_FP = MIPS_REG_F30 = 30
MIPS_REG_RA = MIPS_REG_F31 = 31

# Instruction Unique IDs
MIPS_INS_SLL       = 0
MIPS_INS_SRL       = 1
MIPS_INS_SRA       = 2
MIPS_INS_SLLV      = 3
MIPS_INS_SRLV      = 4
MIPS_INS_SRAV      = 5
MIPS_INS_JR        = 6
MIPS_INS_JALR      = 7
MIPS_INS_SYSCALL   = 8
MIPS_INS_BREAK     = 9
MIPS_INS_SYNC      = 10
MIPS_INS_MFHI      = 11
MIPS_INS_MTHI      = 12
MIPS_INS_MFLO      = 13
MIPS_INS_MTLO      = 14
MIPS_INS_DSLLV     = 15
MIPS_INS_DSRLV     = 16
MIPS_INS_DSRAV     = 17
MIPS_INS_MULT      = 18
MIPS_INS_MULTU     = 19
MIPS_INS_DIV       = 20
MIPS_INS_DIVU      = 21
MIPS_INS_DMULT     = 22
MIPS_INS_DMULTU    = 23
MIPS_INS_DDIV      = 24
MIPS_INS_DDIVU     = 25
MIPS_INS_ADD       = 26
MIPS_INS_ADDU      = 27
MIPS_INS_SUB       = 28
MIPS_INS_SUBU      = 29
MIPS_INS_AND       = 30
MIPS_INS_OR        = 31
MIPS_INS_XOR       = 32
MIPS_INS_NOR       = 33
MIPS_INS_SLT       = 34
MIPS_INS_SLTU      = 35
MIPS_INS_DADD      = 36
MIPS_INS_DADDU     = 37
MIPS_INS_DSUB      = 38
MIPS_INS_DSUBU     = 39
MIPS_INS_TGE       = 40
MIPS_INS_TGEU      = 41
MIPS_INS_TLT       = 42
MIPS_INS_TLTU      = 43
MIPS_INS_TEQ       = 44
MIPS_INS_TNE       = 45
MIPS_INS_DSLL      = 46
MIPS_INS_DSRL      = 47
MIPS_INS_DSRA      = 48
MIPS_INS_DSLL32    = 49
MIPS_INS_DSRL32    = 50
MIPS_INS_DSRA32    = 51
MIPS_INS_BLTZ      = 52
MIPS_INS_BGEZ      = 53
MIPS_INS_BLTZL     = 54
MIPS_INS_BGEZL     = 55
MIPS_INS_TGEI      = 56
MIPS_INS_TGEIU     = 57
MIPS_INS_TLTI      = 58
MIPS_INS_TLTIU     = 59
MIPS_INS_TEQI      = 60
MIPS_INS_TNEI      = 61
MIPS_INS_BLTZAL    = 62
MIPS_INS_BGEZAL    = 63
MIPS_INS_BLTZALL   = 64
MIPS_INS_BGEZALL   = 65
MIPS_INS_J         = 66
MIPS_INS_JAL       = 67
MIPS_INS_BEQ       = 68
MIPS_INS_BNE       = 69
MIPS_INS_BLEZ      = 70
MIPS_INS_BGTZ      = 71
MIPS_INS_ADDI      = 72
MIPS_INS_ADDIU     = 73
MIPS_INS_SLTI      = 74
MIPS_INS_SLTIU     = 75
MIPS_INS_ANDI      = 76
MIPS_INS_ORI       = 77
MIPS_INS_XORI      = 78
MIPS_INS_LUI       = 79
MIPS_INS_MFC0      = 80
MIPS_INS_MTC0      = 81
MIPS_INS_TLBR      = 82
MIPS_INS_TLBWI     = 83
MIPS_INS_TLBWR     = 84
MIPS_INS_TLBP      = 85
MIPS_INS_ERET      = 86
MIPS_INS_MFC1      = 87
MIPS_INS_DMFC1     = 88
MIPS_INS_CFC1      = 89
MIPS_INS_MTC1      = 90
MIPS_INS_DMTC1     = 91
MIPS_INS_CTC1      = 92
MIPS_INS_BC1F      = 93
MIPS_INS_BC1T      = 94
MIPS_INS_BC1FL     = 95
MIPS_INS_BC1TL     = 96
MIPS_INS_ADD_S     = 97
MIPS_INS_SUB_S     = 98
MIPS_INS_MUL_S     = 99
MIPS_INS_DIV_S     = 100
MIPS_INS_SQRT_S    = 101
MIPS_INS_ABS_S     = 102
MIPS_INS_MOV_S     = 103
MIPS_INS_NEG_S     = 104
MIPS_INS_ROUND_L_S = 105
MIPS_INS_TRUNC_L_S = 106
MIPS_INS_CEIL_L_S  = 107
MIPS_INS_FLOOR_L_S = 108
MIPS_INS_ROUND_W_S = 109
MIPS_INS_TRUNC_W_S = 110
MIPS_INS_CEIL_W_S  = 111
MIPS_INS_FLOOR_W_S = 112
MIPS_INS_CVT_D_S   = 113
MIPS_INS_CVT_W_S   = 114
MIPS_INS_CVT_L_S   = 115
MIPS_INS_C_F_S     = 116
MIPS_INS_C_UN_S    = 117
MIPS_INS_C_EQ_S    = 118
MIPS_INS_C_UEQ_S   = 119
MIPS_INS_C_OLT_S   = 120
MIPS_INS_C_ULT_S   = 121
MIPS_INS_C_OLE_S   = 122
MIPS_INS_C_ULE_S   = 123
MIPS_INS_C_SF_S    = 124
MIPS_INS_C_NGLE_S  = 125
MIPS_INS_C_SEQ_S   = 126
MIPS_INS_C_NGL_S   = 127
MIPS_INS_C_LT_S    = 128
MIPS_INS_C_NGE_S   = 129
MIPS_INS_C_LE_S    = 130
MIPS_INS_C_NGT_S   = 131
MIPS_INS_ADD_D     = 132
MIPS_INS_SUB_D     = 133
MIPS_INS_MUL_D     = 134
MIPS_INS_DIV_D     = 135
MIPS_INS_SQRT_D    = 136
MIPS_INS_ABS_D     = 137
MIPS_INS_MOV_D     = 138
MIPS_INS_NEG_D     = 139
MIPS_INS_ROUND_L_D = 140
MIPS_INS_TRUNC_L_D = 141
MIPS_INS_CEIL_L_D  = 142
MIPS_INS_FLOOR_L_D = 143
MIPS_INS_ROUND_W_D = 144
MIPS_INS_TRUNC_W_D = 145
MIPS_INS_CEIL_W_D  = 146
MIPS_INS_FLOOR_W_D = 147
MIPS_INS_CVT_S_D   = 148
MIPS_INS_CVT_W_D   = 149
MIPS_INS_CVT_L_D   = 150
MIPS_INS_C_F_D     = 151
MIPS_INS_C_UN_D    = 152
MIPS_INS_C_EQ_D    = 153
MIPS_INS_C_UEQ_D   = 154
MIPS_INS_C_OLT_D   = 155
MIPS_INS_C_ULT_D   = 156
MIPS_INS_C_OLE_D   = 157
MIPS_INS_C_ULE_D   = 158
MIPS_INS_C_SF_D    = 159
MIPS_INS_C_NGLE_D  = 160
MIPS_INS_C_SEQ_D   = 161
MIPS_INS_C_NGL_D   = 162
MIPS_INS_C_LT_D    = 163
MIPS_INS_C_NGE_D   = 164
MIPS_INS_C_LE_D    = 165
MIPS_INS_C_NGT_D   = 166
MIPS_INS_CVT_S_W   = 167
MIPS_INS_CVT_D_W   = 168
MIPS_INS_CVT_S_L   = 169
MIPS_INS_CVT_D_L   = 170
MIPS_INS_BEQL      = 171
MIPS_INS_BNEL      = 172
MIPS_INS_BLEZL     = 173
MIPS_INS_BGTZL     = 174
MIPS_INS_DADDI     = 175
MIPS_INS_DADDIU    = 176
MIPS_INS_LDL       = 177
MIPS_INS_LDR       = 178
MIPS_INS_LB        = 179
MIPS_INS_LH        = 180
MIPS_INS_LWL       = 181
MIPS_INS_LW        = 182
MIPS_INS_LBU       = 183
MIPS_INS_LHU       = 184
MIPS_INS_LWR       = 185
MIPS_INS_LWU       = 186
MIPS_INS_SB        = 187
MIPS_INS_SH        = 188
MIPS_INS_SWL       = 189
MIPS_INS_SW        = 190
MIPS_INS_SDL       = 191
MIPS_INS_SDR       = 192
MIPS_INS_SWR       = 193
MIPS_INS_CACHE     = 194
MIPS_INS_LL        = 195
MIPS_INS_LWC1      = 196
MIPS_INS_LLD       = 197
MIPS_INS_LDC1      = 198
#MIPS_INS_INVALID   = 199
MIPS_INS_LD        = 200
MIPS_INS_SC        = 201
MIPS_INS_SWC1      = 202
MIPS_INS_SCD       = 203
MIPS_INS_SDC1      = 204
MIPS_INS_SD        = 206

# Pseudo-Instruction Unique IDs
MIPS_INS_BEQZ = 207
MIPS_INS_BNEZ = 208
MIPS_INS_B    = 209
MIPS_INS_NOP  = 210
MIPS_INS_MOVE = 211
MIPS_INS_NEGU = 212
MIPS_INS_NOT  = 213

# Invalid Instruction Unique ID
MIPS_INS_INVALID   = -1

# Op IDs
# MIPS_OP_RS  = 0
# MIPS_OP_RT  = 0
# MIPS_OP_RD  = 0
# MIPS_OP_IMM = 0

# Register Names

mips_gpr_names = [
    "$zero",
    "$at",
    "$v0", "$v1",
    "$a0", "$a1", "$a2", "$a3",
    "$t0", "$t1", "$t2", "$t3", "$t4", "$t5", "$t6", "$t7",
    "$s0", "$s1", "$s2", "$s3", "$s4", "$s5", "$s6", "$s7",
    "$t8", "$t9",
    "$k0", "$k1",
    "$gp",
    "$sp",
    "$fp",
    "$ra",
]

mips_cop0_names = [
    "Index"     , "Random"    , "EntryLo0"  , "EntryLo1"  ,
    "Context"   , "PageMask"  , "Wired"     , "Reserved07",
    "BadVaddr"  , "Count"     , "EntryHi"   , "Compare"   ,
    "Status"    , "Cause"     , "EPC"       , "PRevID"    ,
    "Config"    , "LLAddr"    , "WatchLo"   , "WatchHi"   ,
    "XContext"  , "Reserved21", "Reserved22", "Reserved23",
    "Reserved24", "Reserved25", "PErr"      , "CacheErr"  ,
    "TagLo"     , "TagHi"     , "ErrorEPC"  , "Reserved31",
]

mips_fpr_names = [
    "$f0", "$f1", "$f2", "$f3",
    "$f4", "$f5", "$f6", "$f7", "$f8", "$f9", "$f10", "$f11",
    "$f12", "$f13", "$f14", "$f15",
    "$f16", "$f17", "$f18", "$f19",
    "$f20", "$f21", "$f22", "$f23", "$f24", "$f25", "$f26", "$f27", "$f28", "$f29", "$f30",
    "$31",
]

# Instruction field fetching

def sign_extend_16(value):
    return (value & 0x7FFF) - (value & 0x8000)

def mask_shift(v, s, w):
    return (v >> s) & ((1 << w) - 1)

mips_get_field    = lambda raw,vaddr : mask_shift(raw, 26,  6)
mips_get_special  = lambda raw,vaddr : mask_shift(raw,  0,  6)
mips_get_cop0     = lambda raw,vaddr : mask_shift(raw, 21,  5)
mips_get_cop1     = lambda raw,vaddr : mask_shift(raw, 21,  5)
mips_get_regimm   = lambda raw,vaddr : mask_shift(raw, 16,  5)
mips_get_tlb      = lambda raw,vaddr : mask_shift(raw,  0,  5)
mips_get_function = lambda raw,vaddr : mask_shift(raw,  0,  6)

mips_get_cond    = lambda raw,vaddr : mask_shift(raw,  0,  4)
mips_get_fc      = lambda raw,vaddr : mask_shift(raw,  2,  2)
mips_get_fd      = lambda raw,vaddr : mask_shift(raw,  6,  5)
mips_get_fs      = lambda raw,vaddr : mask_shift(raw, 11,  5)
mips_get_ft      = lambda raw,vaddr : mask_shift(raw, 16,  5)
mips_get_fmt     = lambda raw,vaddr : mask_shift(raw, 21,  5)
mips_get_ndtf    = lambda raw,vaddr : mask_shift(raw, 16,  2)

mips_get_target  = lambda raw,vaddr : (0x80000000 | (mask_shift(raw, 0, 26) << 2))
mips_get_offset  = lambda raw,vaddr : vaddr + 4 + sign_extend_16(mask_shift(raw, 0, 16)) * 4
mips_get_imm     = lambda raw,vaddr : mask_shift(raw,  0, 16)

mips_get_base    = lambda raw,vaddr : mask_shift(raw, 21,  5)

mips_get_cd      = lambda raw,vaddr : mask_shift(raw, 11,  5)

mips_get_code    = lambda raw,vaddr : (mask_shift(raw,  6, 20) << 6) >> 16
mips_get_op      = lambda raw,vaddr : mask_shift(raw, 16,  5)

mips_get_sa      = lambda raw,vaddr : mask_shift(raw,  6,  5)

mips_get_rd      = lambda raw,vaddr : mask_shift(raw, 11,  5)
mips_get_rs      = lambda raw,vaddr : mask_shift(raw, 21,  5)
mips_get_rt      = lambda raw,vaddr : mask_shift(raw, 16,  5)

# Formatting

class MipsInsn:

    def __init__(self, raw, vaddr, values):
        self.raw = raw
        self.vaddr = vaddr

        if values is None:
            values = MIPS_INS_INVALID, f"/* Invalid Instruction: 0x{raw:08X} */", ()

        self.id, self.mnemonic, self.fields = values

        self.code = self.sa = self.op = self.cd = self.rd = self.rs = self.rt = self.fd = self.fs = self.ft = self.imm = self.offset = self.base = self.target = None

        for field in self.fields:
            self.value_forname(field)

        self.op_str = self.format_insn()

        if self.id == MIPS_INS_SLL and self.rd == MIPS_REG_R0 and self.rt == MIPS_REG_R0 and self.sa == 0:
            self.id = MIPS_INS_NOP
            self.mnemonic = "nop"
            self.fields = ()
        elif self.id == MIPS_INS_BEQ and self.rs == MIPS_REG_R0 and self.rt == MIPS_REG_R0:
            self.id = MIPS_INS_B
            self.mnemonic = "b"
            self.fields = ("offset",)
        elif self.id == MIPS_INS_OR and self.rt == MIPS_REG_R0:
            self.id = MIPS_INS_MOVE
            self.mnemonic = "move"
            self.fields = ("rd","rs")
        elif self.id == MIPS_INS_BEQ and self.rt == MIPS_REG_R0:
            self.id = MIPS_INS_BEQZ
            self.mnemonic = "beqz"
            self.fields = ("rs","offset")
        elif self.id == MIPS_INS_BNE and self.rt == MIPS_REG_R0:
            self.id = MIPS_INS_BNEZ
            self.mnemonic = "bnez"
            self.fields = ("rs","offset")
        elif self.id == MIPS_INS_SUBU and self.rs == MIPS_REG_R0:
            self.id = MIPS_INS_NEGU
            self.mnemonic = "negu"
            self.fields = ("rd","rt")
        elif self.id == MIPS_INS_NOR and self.rt == MIPS_REG_R0:
            self.id = MIPS_INS_NOT
            self.mnemonic = "not"
            self.fields = ("rd","rs")

        self.op_str = self.format_insn()

    def value_forname(self, name):
        field_setters = {
            'code'   : self.set_code,
            'sa'     : self.set_sa,
            'op'     : self.set_op,
            'cd'     : self.set_cd,
            'rd'     : self.set_rd,
            'rs'     : self.set_rs,
            'rt'     : self.set_rt,
            'fd'     : self.set_fd,
            'fs'     : self.set_fs,
            'ft'     : self.set_ft,
            'imm'    : self.set_imm,
            'offset' : self.set_offset,
            'base'   : self.set_base,
            'offset(base)' : self.set_offset_base,
            'target' : self.set_target,
        }

        return field_setters[name]()

    def format_field(self, field):
        def format_hex(v, signed, zeros, no_zero):
            if abs(v) < 10:
                if v == 0 and no_zero:
                    return ""
                return f"{v}"
            elif not signed:
                return f"0x{v:{f'0{zeros}' if zeros > 0 else ''}x}"
            else:
                return f"{v:#x}"

        format_handlers = {
            'code' : (lambda insn : f'{insn.code}' if insn.code != 0 else ''),
            'cd' : (lambda insn : mips_cop0_names[insn.cd]),
            'rd' : (lambda insn : mips_gpr_names[insn.rd]),
            'rs' : (lambda insn : mips_gpr_names[insn.rs]),
            'rt' : (lambda insn : mips_gpr_names[insn.rt]),
            'fd' : (lambda insn : mips_fpr_names[insn.fd]),
            'fs' : (lambda insn : mips_fpr_names[insn.fs]),
            'ft' : (lambda insn : mips_fpr_names[insn.ft]),
            'sa' : (lambda insn : format_hex(insn.sa, True, 0, False)),
            'op' : (lambda insn : format_hex(insn.op, False, 0, False)),
            'imm' : (lambda insn : format_hex(insn.imm, True, 0, False)),
            'offset(base)' : (lambda insn : f'{format_hex(insn.imm, True, 0, True)}({mips_gpr_names[insn.base]})'),
            'offset'       : (lambda insn : f'{format_hex(insn.offset, True, 0, False)}'),
            'target'       : (lambda insn : f'{format_hex(insn.target, False, 0, False)}')
        }

        return format_handlers[field](self)

    def format_insn(self):
        return ", ".join([self.format_field(field) for field in self.fields])

    def __str__(self):
        return f"{self.mnemonic:12}{self.op_str}"

    def set_code(self):
        self.code = mips_get_code(self.raw, self.vaddr)
        return self.code

    def set_sa(self):
        self.sa = mips_get_sa(self.raw, self.vaddr)
        return self.sa

    def set_op(self):
        self.op = mips_get_op(self.raw, self.vaddr)
        return self.op

    def set_cd(self):
        self.cd = mips_get_cd(self.raw, self.vaddr)
        return self.cd

    def set_rd(self):
        self.rd = mips_get_rd(self.raw, self.vaddr)
        return self.rd

    def set_rs(self):
        self.rs = mips_get_rs(self.raw, self.vaddr)
        return self.rs

    def set_rt(self):
        self.rt = mips_get_rt(self.raw, self.vaddr)
        return self.rt

    def set_fd(self):
        self.fd = mips_get_fd(self.raw, self.vaddr)
        return self.fd

    def set_fs(self):
        self.fs = mips_get_fs(self.raw, self.vaddr)
        return self.fs

    def set_ft(self):
        self.ft = mips_get_ft(self.raw, self.vaddr)
        return self.ft

    def set_imm(self):
        do_sign_extend = [  MIPS_INS_ADDIU, MIPS_INS_SLTI, MIPS_INS_ADDI, MIPS_INS_DADDIU, 
                            MIPS_INS_LB, MIPS_INS_LBU, 
                            MIPS_INS_LH, MIPS_INS_LHU, 
                            MIPS_INS_LW, MIPS_INS_LWL, MIPS_INS_LWR, MIPS_INS_LWU, 
                            MIPS_INS_LWC1, 
                            MIPS_INS_LD, MIPS_INS_LDL, MIPS_INS_LDR, 
                            MIPS_INS_LDC1, 
                            MIPS_INS_LL, MIPS_INS_LLD, 
                            MIPS_INS_SB, 
                            MIPS_INS_SH, 
                            MIPS_INS_SW, MIPS_INS_SWL, MIPS_INS_SWR, 
                            MIPS_INS_SWC1, 
                            MIPS_INS_SD, MIPS_INS_SDL, MIPS_INS_SDR, 
                            MIPS_INS_SDC1, 
                            MIPS_INS_SC, MIPS_INS_SCD,
                        ]

        self.imm = mips_get_imm(self.raw, self.vaddr)
        if self.id in do_sign_extend: # sign extended immediates
            self.imm = sign_extend_16(self.imm)
        return self.imm

    def set_offset(self):
        self.offset = mips_get_offset(self.raw, self.vaddr)
        return self.offset

    def set_base(self):
        self.base = mips_get_base(self.raw, self.vaddr)
        return self.base

    def set_offset_base(self):
        self.set_imm()
        self.set_base()
        return self.imm, self.base

    def set_target(self):
        self.target = mips_get_target(self.raw, self.vaddr)
        return self.target

def fetch_insn(raw, vaddr, insn_set, func):
    insn = insn_set.get(func(raw, vaddr), None) # default none for invalid instruction encoding

    if insn is not None and type(insn[1]) == dict: # extra decoding required
        insn = fetch_insn(raw, vaddr, insn[1], insn[0])
    return insn

def decode_insn(raw, vaddr):
    return MipsInsn(raw, vaddr, fetch_insn(raw, vaddr, mips_insns, mips_get_field))

mips_insns = {
    0b000000: (mips_get_special, {
        0b000000: (MIPS_INS_SLL,     "sll",     ("rd","rt","sa")),
        0b000010: (MIPS_INS_SRL,     "srl",     ("rd","rt","sa")),
        0b000011: (MIPS_INS_SRA,     "sra",     ("rd","rt","sa")),
        0b000100: (MIPS_INS_SLLV,    "sllv",    ("rd","rt","rs")),
        0b000110: (MIPS_INS_SRLV,    "srlv",    ("rd","rt","rs")),
        0b000111: (MIPS_INS_SRAV,    "srav",    ("rd","rt","rs")),
        0b001000: (MIPS_INS_JR,      "jr",      ("rs",         )),
        0b001001: (MIPS_INS_JALR,    "jalr",    ("rs",         )), # technically also rd but it's always $ra
        0b001100: (MIPS_INS_SYSCALL, "syscall", (              )),
        0b001101: (MIPS_INS_BREAK,   "break",   ("code",       )),
        0b001111: (MIPS_INS_SYNC,    "sync",    (              )),
        0b010000: (MIPS_INS_MFHI,    "mfhi",    ("rd",         )),
        0b010001: (MIPS_INS_MTHI,    "mthi",    ("rs",         )),
        0b010010: (MIPS_INS_MFLO,    "mflo",    ("rd",         )),
        0b010011: (MIPS_INS_MTLO,    "mtlo",    ("rs",         )),
        0b010100: (MIPS_INS_DSLLV,   "dsllv",   ("rd","rt","rs")),
        0b010110: (MIPS_INS_DSRLV,   "dsrlv",   ("rd","rt","rs")),
        0b010111: (MIPS_INS_DSRAV,   "dsrav",   ("rd","rt","rs")),
        0b011000: (MIPS_INS_MULT,    "mult",    ("rs","rt"     )),
        0b011001: (MIPS_INS_MULTU,   "multu",   ("rs","rt"     )),
        0b011010: (MIPS_INS_DIV,     "div",     ("rd","rs","rt")), # for some reason gas hates div instructions 
        0b011011: (MIPS_INS_DIVU,    "divu",    ("rd","rs","rt")), #    with the correct number of operands
        0b011100: (MIPS_INS_DMULT,   "dmult",   ("rs","rt"     )),
        0b011101: (MIPS_INS_DMULTU,  "dmultu",  ("rs","rt"     )),
        0b011110: (MIPS_INS_DDIV,    "ddiv",    ("rd","rs","rt")),
        0b011111: (MIPS_INS_DDIVU,   "ddivu",   ("rd","rs","rt")),
        0b100000: (MIPS_INS_ADD,     "add",     ("rd","rs","rt")),
        0b100001: (MIPS_INS_ADDU,    "addu",    ("rd","rs","rt")),
        0b100010: (MIPS_INS_SUB,     "sub",     ("rd","rs","rt")),
        0b100011: (MIPS_INS_SUBU,    "subu",    ("rd","rs","rt")),
        0b100100: (MIPS_INS_AND,     "and",     ("rd","rs","rt")),
        0b100101: (MIPS_INS_OR,      "or",      ("rd","rs","rt")),
        0b100110: (MIPS_INS_XOR,     "xor",     ("rd","rs","rt")),
        0b100111: (MIPS_INS_NOR,     "nor",     ("rd","rs","rt")),
        0b101010: (MIPS_INS_SLT,     "slt",     ("rd","rs","rt")),
        0b101011: (MIPS_INS_SLTU,    "sltu",    ("rd","rs","rt")),
        0b101100: (MIPS_INS_DADD,    "dadd",    ("rd","rs","rt")),
        0b101101: (MIPS_INS_DADDU,   "daddu",   ("rd","rs","rt")),
        0b101110: (MIPS_INS_DSUB,    "dsub",    ("rd","rs","rt")),
        0b101111: (MIPS_INS_DSUBU,   "dsubu",   ("rd","rs","rt")),
        0b110000: (MIPS_INS_TGE,     "tge",     ("rs","rt"     )),
        0b110001: (MIPS_INS_TGEU,    "tgeu",    ("rs","rt"     )),
        0b110010: (MIPS_INS_TLT,     "tlt",     ("rs","rt"     )),
        0b110011: (MIPS_INS_TLTU,    "tltu",    ("rs","rt"     )),
        0b110100: (MIPS_INS_TEQ,     "teq",     ("rs","rt"     )),
        0b110110: (MIPS_INS_TNE,     "tne",     ("rs","rt"     )),
        0b111000: (MIPS_INS_DSLL,    "dsll",    ("rd","rt","sa")),
        0b111010: (MIPS_INS_DSRL,    "dsrl",    ("rd","rt","sa")),
        0b111011: (MIPS_INS_DSRA,    "dsra",    ("rd","rt","sa")),
        0b111100: (MIPS_INS_DSLL32,  "dsll32",  ("rd","rt","sa")),
        0b111110: (MIPS_INS_DSRL32,  "dsrl32",  ("rd","rt","sa")),
        0b111111: (MIPS_INS_DSRA32,  "dsra32",  ("rd","rt","sa")),
    }),
    0b000001: (mips_get_regimm, {
        0b00000: (MIPS_INS_BLTZ,    "bltz",    ("rs","offset")),
        0b00001: (MIPS_INS_BGEZ,    "bgez",    ("rs","offset")),
        0b00010: (MIPS_INS_BLTZL,   "bltzl",   ("rs","offset")),
        0b00011: (MIPS_INS_BGEZL,   "bgezl",   ("rs","offset")),
        0b01000: (MIPS_INS_TGEI,    "tgei",    ("rs","imm"   )),
        0b01001: (MIPS_INS_TGEIU,   "tgeiu",   ("rs","imm"   )),
        0b01010: (MIPS_INS_TLTI,    "tlti",    ("rs","imm"   )),
        0b01011: (MIPS_INS_TLTIU,   "tltiu",   ("rs","imm"   )),
        0b01100: (MIPS_INS_TEQI,    "teqi",    ("rs","imm"   )),
        0b01110: (MIPS_INS_TNEI,    "tnei",    ("rs","imm"   )),
        0b10000: (MIPS_INS_BLTZAL,  "bltzal",  ("rs","offset")),
        0b10001: (MIPS_INS_BGEZAL,  "bgezal",  ("rs","offset")),
        0b10010: (MIPS_INS_BLTZALL, "bltzall", ("rs","offset")),
        0b10011: (MIPS_INS_BGEZALL, "bgezall", ("rs","offset")),
    }),
    0b000010: (MIPS_INS_J,     "j",     ("target",         )),
    0b000011: (MIPS_INS_JAL,   "jal",   ("target",         )),
    0b000100: (MIPS_INS_BEQ,   "beq",   ("rs","rt","offset")),
    0b000101: (MIPS_INS_BNE,   "bne",   ("rs","rt","offset")),
    0b000110: (MIPS_INS_BLEZ,  "blez",  ("rs","offset"     )),
    0b000111: (MIPS_INS_BGTZ,  "bgtz",  ("rs","offset"     )),
    0b001000: (MIPS_INS_ADDI,  "addi",  ("rt","rs","imm"   )),
    0b001001: (MIPS_INS_ADDIU, "addiu", ("rt","rs","imm"   )),
    0b001010: (MIPS_INS_SLTI,  "slti",  ("rt","rs","imm"   )),
    0b001011: (MIPS_INS_SLTIU, "sltiu", ("rt","rs","imm"   )),
    0b001100: (MIPS_INS_ANDI,  "andi",  ("rt","rs","imm"   )),
    0b001101: (MIPS_INS_ORI,   "ori",   ("rt","rs","imm"   )),
    0b001110: (MIPS_INS_XORI,  "xori",  ("rt","rs","imm"   )),
    0b001111: (MIPS_INS_LUI,   "lui",   ("rt","imm"        )),
    0b010000: (mips_get_cop0, {
        0b00000: (MIPS_INS_MFC0, "mfc0", ("rt","cd")),
        0b00100: (MIPS_INS_MTC0, "mtc0", ("rt","cd")),
        0b10000: (mips_get_tlb, {
            0b000001: (MIPS_INS_TLBR,  "tlbr",  ()),
            0b000010: (MIPS_INS_TLBWI, "tlbwi", ()),
            0b000110: (MIPS_INS_TLBWR, "tlbwr", ()),
            0b001000: (MIPS_INS_TLBP,  "tlbp",  ()),
            0b011000: (MIPS_INS_ERET,  "eret",  ()),
        }),
    }),
    0b010001: (mips_get_cop1, {
        0b00000: (MIPS_INS_MFC1,  "mfc1",  ("rt","fs")),
        0b00001: (MIPS_INS_DMFC1, "dmfc1", ("rt","fs")),
        0b00010: (MIPS_INS_CFC1,  "cfc1",  ("rt","fs")),
        0b00100: (MIPS_INS_MTC1,  "mtc1",  ("rt","fs")),
        0b00101: (MIPS_INS_DMTC1, "dmtc1", ("rt","fs")),
        0b00110: (MIPS_INS_CTC1,  "ctc1",  ("rt","fs")),
        0b01000: (mips_get_ndtf, {
            0b00: (MIPS_INS_BC1F,  "bc1f",  ("offset",)),
            0b01: (MIPS_INS_BC1T,  "bc1t",  ("offset",)),
            0b10: (MIPS_INS_BC1FL, "bc1fl", ("offset",)),
            0b11: (MIPS_INS_BC1TL, "bc1tl", ("offset",)),
        }),
        0b010000: (mips_get_function, {
            0b000000: (MIPS_INS_ADD_S,     "add.s",     ("fd","fs","ft")),
            0b000001: (MIPS_INS_SUB_S,     "sub.s",     ("fd","fs","ft")),
            0b000010: (MIPS_INS_MUL_S,     "mul.s",     ("fd","fs","ft")),
            0b000011: (MIPS_INS_DIV_S,     "div.s",     ("fd","fs","ft")),
            0b000100: (MIPS_INS_SQRT_S,    "sqrt.s",    ("fd","fs"     )),
            0b000101: (MIPS_INS_ABS_S,     "abs.s",     ("fd","fs"     )),
            0b000110: (MIPS_INS_MOV_S,     "mov.s",     ("fd","fs"     )),
            0b000111: (MIPS_INS_NEG_S,     "neg.s",     ("fd","fs"     )),
            0b001000: (MIPS_INS_ROUND_L_S, "round.l.s", ("fd","fs"     )),
            0b001001: (MIPS_INS_TRUNC_L_S, "trunc.l.s", ("fd","fs"     )),
            0b001010: (MIPS_INS_CEIL_L_S,  "ceil.l.s",  ("fd","fs"     )),
            0b001011: (MIPS_INS_FLOOR_L_S, "floor.l.s", ("fd","fs"     )),
            0b001100: (MIPS_INS_ROUND_W_S, "round.w.s", ("fd","fs"     )),
            0b001101: (MIPS_INS_TRUNC_W_S, "trunc.w.s", ("fd","fs"     )),
            0b001110: (MIPS_INS_CEIL_W_S,  "ceil.w.s",  ("fd","fs"     )),
            0b001111: (MIPS_INS_FLOOR_W_S, "floor.w.s", ("fd","fs"     )),
            0b100001: (MIPS_INS_CVT_D_S,   "cvt.d.s",   ("fd","fs"     )),
            0b100100: (MIPS_INS_CVT_W_S,   "cvt.w.s",   ("fd","fs"     )),
            0b100101: (MIPS_INS_CVT_L_S,   "cvt.l.s",   ("fd","fs"     )),
            0b110000: (MIPS_INS_C_F_S,     "c.f.s",     ("fs","ft"     )),
            0b110001: (MIPS_INS_C_UN_S,    "c.un.s",    ("fs","ft"     )),
            0b110010: (MIPS_INS_C_EQ_S,    "c.eq.s",    ("fs","ft"     )),
            0b110011: (MIPS_INS_C_UEQ_S,   "c.ueq.s",   ("fs","ft"     )),
            0b110100: (MIPS_INS_C_OLT_S,   "c.olt.s",   ("fs","ft"     )),
            0b110101: (MIPS_INS_C_ULT_S,   "c.ult.s",   ("fs","ft"     )),
            0b110110: (MIPS_INS_C_OLE_S,   "c.ole.s",   ("fs","ft"     )),
            0b110111: (MIPS_INS_C_ULE_S,   "c.ule.s",   ("fs","ft"     )),
            0b111000: (MIPS_INS_C_SF_S,    "c.sf.s",    ("fs","ft"     )),
            0b111001: (MIPS_INS_C_NGLE_S,  "c.ngle.s",  ("fs","ft"     )),
            0b111010: (MIPS_INS_C_SEQ_S,   "c.seq.s",   ("fs","ft"     )),
            0b111011: (MIPS_INS_C_NGL_S,   "c.ngl.s",   ("fs","ft"     )),
            0b111100: (MIPS_INS_C_LT_S,    "c.lt.s",    ("fs","ft"     )),
            0b111101: (MIPS_INS_C_NGE_S,   "c.nge.s",   ("fs","ft"     )),
            0b111110: (MIPS_INS_C_LE_S,    "c.le.s",    ("fs","ft"     )),
            0b111111: (MIPS_INS_C_NGT_S,   "c.ngt.s",   ("fs","ft"     )),
        }),
        0b010001: (mips_get_function, {
            0b000000: (MIPS_INS_ADD_D,     "add.d",     ("fd","fs","ft")),
            0b000001: (MIPS_INS_SUB_D,     "sub.d",     ("fd","fs","ft")),
            0b000010: (MIPS_INS_MUL_D,     "mul.d",     ("fd","fs","ft")),
            0b000011: (MIPS_INS_DIV_D,     "div.d",     ("fd","fs","ft")),
            0b000100: (MIPS_INS_SQRT_D,    "sqrt.d",    ("fd","fs"     )),
            0b000101: (MIPS_INS_ABS_D,     "abs.d",     ("fd","fs"     )),
            0b000110: (MIPS_INS_MOV_D,     "mov.d",     ("fd","fs"     )),
            0b000111: (MIPS_INS_NEG_D,     "neg.d",     ("fd","fs"     )),
            0b001000: (MIPS_INS_ROUND_L_D, "round.l.d", ("fd","fs"     )),
            0b001001: (MIPS_INS_TRUNC_L_D, "trunc.l.d", ("fd","fs"     )),
            0b001010: (MIPS_INS_CEIL_L_D,  "ceil.l.d",  ("fd","fs"     )),
            0b001011: (MIPS_INS_FLOOR_L_D, "floor.l.d", ("fd","fs"     )),
            0b001100: (MIPS_INS_ROUND_W_D, "round.w.d", ("fd","fs"     )),
            0b001101: (MIPS_INS_TRUNC_W_D, "trunc.w.d", ("fd","fs"     )),
            0b001110: (MIPS_INS_CEIL_W_D,  "ceil.w.d",  ("fd","fs"     )),
            0b001111: (MIPS_INS_FLOOR_W_D, "floor.w.d", ("fd","fs"     )),
            0b100000: (MIPS_INS_CVT_S_D,   "cvt.s.d",   ("fd","fs"     )),
            0b100100: (MIPS_INS_CVT_W_D,   "cvt.w.d",   ("fd","fs"     )),
            0b100101: (MIPS_INS_CVT_L_D,   "cvt.l.d",   ("fd","fs"     )),
            0b110000: (MIPS_INS_C_F_D,     "c.f.d",     ("fs","ft"     )),
            0b110001: (MIPS_INS_C_UN_D,    "c.un.d",    ("fs","ft"     )),
            0b110010: (MIPS_INS_C_EQ_D,    "c.eq.d",    ("fs","ft"     )),
            0b110011: (MIPS_INS_C_UEQ_D,   "c.ueq.d",   ("fs","ft"     )),
            0b110100: (MIPS_INS_C_OLT_D,   "c.olt.d",   ("fs","ft"     )),
            0b110101: (MIPS_INS_C_ULT_D,   "c.ult.d",   ("fs","ft"     )),
            0b110110: (MIPS_INS_C_OLE_D,   "c.ole.d",   ("fs","ft"     )),
            0b110111: (MIPS_INS_C_ULE_D,   "c.ule.d",   ("fs","ft"     )),
            0b111000: (MIPS_INS_C_SF_D,    "c.sf.d",    ("fs","ft"     )),
            0b111001: (MIPS_INS_C_NGLE_D,  "c.ngle.d",  ("fs","ft"     )),
            0b111010: (MIPS_INS_C_SEQ_D,   "c.seq.d",   ("fs","ft"     )),
            0b111011: (MIPS_INS_C_NGL_D,   "c.ngl.d",   ("fs","ft"     )),
            0b111100: (MIPS_INS_C_LT_D,    "c.lt.d",    ("fs","ft"     )),
            0b111101: (MIPS_INS_C_NGE_D,   "c.nge.d",   ("fs","ft"     )),
            0b111110: (MIPS_INS_C_LE_D,    "c.le.d",    ("fs","ft"     )),
            0b111111: (MIPS_INS_C_NGT_D,   "c.ngt.d",   ("fs","ft"     )),
        }),
        0b010100: (mips_get_function, {
            0b100000: (MIPS_INS_CVT_S_W, "cvt.s.w", ("fd","fs")),
            0b100001: (MIPS_INS_CVT_D_W, "cvt.d.w", ("fd","fs")),
        }),
        0b010101: (mips_get_function, {
            0b100000: (MIPS_INS_CVT_S_L, "cvt.s.l", ("fd","fs")),
            0b100001: (MIPS_INS_CVT_D_L, "cvt.d.l", ("fd","fs")),
        }),
    }),
    0b010100: (MIPS_INS_BEQL,   "beql",   ("rs","rt","offset" )),
    0b010101: (MIPS_INS_BNEL,   "bnel",   ("rs","rt","offset" )),
    0b010110: (MIPS_INS_BLEZL,  "blezl",  ("rs","offset"      )),
    0b010111: (MIPS_INS_BGTZL,  "bgtzl",  ("rs","offset"      )),
    0b011000: (MIPS_INS_DADDI,  "daddi",  ("rt","rs","imm"    )),
    0b011001: (MIPS_INS_DADDIU, "daddiu", ("rt","rs","imm"    )),
    0b011010: (MIPS_INS_LDL,    "ldl",    ("rt","offset(base)")),
    0b011011: (MIPS_INS_LDR,    "ldr",    ("rt","offset(base)")),
    0b100000: (MIPS_INS_LB,     "lb",     ("rt","offset(base)")),
    0b100001: (MIPS_INS_LH,     "lh",     ("rt","offset(base)")),
    0b100010: (MIPS_INS_LWL,    "lwl",    ("rt","offset(base)")),
    0b100011: (MIPS_INS_LW,     "lw",     ("rt","offset(base)")),
    0b100100: (MIPS_INS_LBU,    "lbu",    ("rt","offset(base)")),
    0b100101: (MIPS_INS_LHU,    "lhu",    ("rt","offset(base)")),
    0b100110: (MIPS_INS_LWR,    "lwr",    ("rt","offset(base)")),
    0b100111: (MIPS_INS_LWU,    "lwu",    ("rt","offset(base)")),
    0b101000: (MIPS_INS_SB,     "sb",     ("rt","offset(base)")),
    0b101001: (MIPS_INS_SH,     "sh",     ("rt","offset(base)")),
    0b101010: (MIPS_INS_SWL,    "swl",    ("rt","offset(base)")),
    0b101011: (MIPS_INS_SW,     "sw",     ("rt","offset(base)")),
    0b101100: (MIPS_INS_SDL,    "sdl",    ("rt","offset(base)")),
    0b101101: (MIPS_INS_SDR,    "sdr",    ("rt","offset(base)")),
    0b101110: (MIPS_INS_SWR,    "swr",    ("rt","offset(base)")),
    0b101111: (MIPS_INS_CACHE,  "cache",  ("op","offset(base)")),
    0b110000: (MIPS_INS_LL,     "ll",     ("rt","offset(base)")),
    0b110001: (MIPS_INS_LWC1,   "lwc1",   ("ft","offset(base)")),
    0b110100: (MIPS_INS_LLD,    "lld",    ("rt","offset(base)")),
    0b110101: (MIPS_INS_LDC1,   "ldc1",   ("ft","offset(base)")),
    # ldc2
    0b110111: (MIPS_INS_LD,     "ld",     ("rt","offset(base)")),
    0b111000: (MIPS_INS_SC,     "sc",     ("rt","offset(base)")),
    0b111001: (MIPS_INS_SWC1,   "swc1",   ("ft","offset(base)")),
    0b111100: (MIPS_INS_SCD,    "scd",    ("rt","offset(base)")),
    0b111101: (MIPS_INS_SDC1,   "sdc1",   ("ft","offset(base)")),
    # sdc2
    0b111111: (MIPS_INS_SD,     "sd",     ("rt","offset(base)")),
}
