/*
 * Print mips instructions.
 *
 * Copyright 1989, 1991, 1992, 1993, 1994, 1995, 1996, 1997, 1998, 1999,
 * 2000, 2001, 2002, 2003, 2005, 2006, 2007, 2008, 2009
 * Free Software Foundation, Inc.
 * Contributed by Nobuyuki Hikichi (hikichi@sra.co.jp).
 * Rewritten for VirtualMIPS by Serge Vakulenko.
 *
 * This file is part of the virtualmips distribution.
 * See LICENSE file for terms of the license.
 */
//#define NEED_MIPS16

#ifdef CROSS
#   include <stdio.h>
#else
#   include <stdio.h>
#endif
#include <stdlib.h>
#include <string.h>
#include "mips-opcode.h"
#include "mips-opc.c"

/* FIXME: These are needed to figure out if the code is mips16 or
   not. The low bit of the address is often a good indicator.  No
   symbol table is available when this code runs out in an embedded
   system as when it is used for disassembler support in a monitor.  */

/* Mips instructions are at maximum this many bytes long.  */
#define INSNLEN 4

struct mips_cp0sel_name {
    unsigned int cp0reg;
    unsigned int sel;
    const char *const name;
};

static const char *const mips_gpr_names[32] = {
    "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", "s8", "ra"
};

static const char *const mips_fpr_names[32] = {
    "$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", "$f31"
};

static const char *const mips_cp0_names[32] = {
    "c0_index", "c0_random", "c0_entrylo0", "c0_entrylo1",
    "c0_context", "c0_pagemask", "c0_wired", "c0_hwrena",
    "c0_badvaddr", "c0_count", "c0_entryhi", "c0_compare",
    "c0_status", "c0_cause", "c0_epc", "c0_prid",
    "c0_config", "c0_lladdr", "c0_watchlo", "c0_watchhi",
    "c0_xcontext", "$21", "$22", "c0_debug",
    "c0_depc", "c0_perfcnt", "c0_errctl", "c0_cacheerr",
    "c0_taglo", "c0_taghi", "c0_errorepc", "c0_desave",
};

static const struct mips_cp0sel_name mips_cp0sel_names[] = {
    {4, 1, "c0_contextconfig"},
    {0, 1, "c0_mvpcontrol"},
    {0, 2, "c0_mvpconf0"},
    {0, 3, "c0_mvpconf1"},
    {1, 1, "c0_vpecontrol"},
    {1, 2, "c0_vpeconf0"},
    {1, 3, "c0_vpeconf1"},
    {1, 4, "c0_yqmask"},
    {1, 5, "c0_vpeschedule"},
    {1, 6, "c0_vpeschefback"},
    {2, 1, "c0_tcstatus"},
    {2, 2, "c0_tcbind"},
    {2, 3, "c0_tcrestart"},
    {2, 4, "c0_tchalt"},
    {2, 5, "c0_tccontext"},
    {2, 6, "c0_tcschedule"},
    {2, 7, "c0_tcschefback"},
    {5, 1, "c0_pagegrain"},
    {6, 1, "c0_srsconf0"},
    {6, 2, "c0_srsconf1"},
    {6, 3, "c0_srsconf2"},
    {6, 4, "c0_srsconf3"},
    {6, 5, "c0_srsconf4"},
    {12, 1, "c0_intctl"},
    {12, 2, "c0_srsctl"},
    {12, 3, "c0_srsmap"},
    {15, 1, "c0_ebase"},
    {16, 1, "c0_config1"},
    {16, 2, "c0_config2"},
    {16, 3, "c0_config3"},
    {18, 1, "c0_watchlo,1"},
    {18, 2, "c0_watchlo,2"},
    {18, 3, "c0_watchlo,3"},
    {18, 4, "c0_watchlo,4"},
    {18, 5, "c0_watchlo,5"},
    {18, 6, "c0_watchlo,6"},
    {18, 7, "c0_watchlo,7"},
    {19, 1, "c0_watchhi,1"},
    {19, 2, "c0_watchhi,2"},
    {19, 3, "c0_watchhi,3"},
    {19, 4, "c0_watchhi,4"},
    {19, 5, "c0_watchhi,5"},
    {19, 6, "c0_watchhi,6"},
    {19, 7, "c0_watchhi,7"},
    {23, 1, "c0_tracecontrol"},
    {23, 2, "c0_tracecontrol2"},
    {23, 3, "c0_usertracedata"},
    {23, 4, "c0_tracebpc"},
    {25, 1, "c0_perfcnt,1"},
    {25, 2, "c0_perfcnt,2"},
    {25, 3, "c0_perfcnt,3"},
    {25, 4, "c0_perfcnt,4"},
    {25, 5, "c0_perfcnt,5"},
    {25, 6, "c0_perfcnt,6"},
    {25, 7, "c0_perfcnt,7"},
    {27, 1, "c0_cacheerr,1"},
    {27, 2, "c0_cacheerr,2"},
    {27, 3, "c0_cacheerr,3"},
    {28, 1, "c0_datalo"},
    {28, 2, "c0_taglo1"},
    {28, 3, "c0_datalo1"},
    {28, 4, "c0_taglo2"},
    {28, 5, "c0_datalo2"},
    {28, 6, "c0_taglo3"},
    {28, 7, "c0_datalo3"},
    {29, 1, "c0_datahi"},
    {29, 2, "c0_taghi1"},
    {29, 3, "c0_datahi1"},
    {29, 4, "c0_taghi2"},
    {29, 5, "c0_datahi2"},
    {29, 6, "c0_taghi3"},
    {29, 7, "c0_datahi3"},
};
static const int mips_cp0sel_names_len = sizeof (mips_cp0sel_names) / sizeof (*mips_cp0sel_names);

static const char *const mips_hwr_names[32] = {
    "$0", "$1", "$2", "$3", "$4", "$5", "$6", "$7",
    "$8", "$9", "$10", "$11", "$12", "$13", "$14", "$15",
    "$16", "$17", "$18", "$19", "$20", "$21", "$22", "$23",
    "$24", "$25", "$26", "$27", "$28", "$29", "$30", "$31"
};

/*
 * If set disassemble as most general inst.
 */
static int no_aliases = 0;

static int branch_delay_insns;
static int data_size;
static unsigned target;

enum dis_insn_type {
    dis_noninsn,		/* Not a valid instruction.  */
    dis_nonbranch,		/* Not a branch instruction.  */
    dis_branch,			/* Unconditional branch.  */
    dis_condbranch,		/* Conditional branch.  */
    dis_jsr,			/* Jump to subroutine.  */
    dis_condjsr,		/* Conditional jump to subroutine.  */
    dis_dref,			/* Data reference instruction.  */
    dis_dref2			/* Two data references in instruction.  */
};

static enum dis_insn_type insn_type;

#if 0
/*
 * Get LENGTH bytes from info's buffer, at target address memaddr.
 *  Transfer them to myaddr.
 */
int
read_memory (unsigned memaddr,
		    unsigned char *myaddr,
		    unsigned int nbytes)
{
    //unsigned char *buffer;
    //unsigned int buffer_length;
    //unsigned int buffer_vma;

    if (memaddr < buffer_vma ||
        memaddr - buffer_vma > buffer_length ||
        memaddr - buffer_vma + nbytes > buffer_length) {
        /* Out of bounds.  Use EIO because GDB uses it.  */
        return EIO;
    }
    memcpy (myaddr, info->buffer + memaddr - buffer_vma, nbytes);
    return 0;
}
#endif

static void
print_address (unsigned address, FILE *stream)
{
    if (address < 16)
        fprintf (stream, "%d", address);
    else
        fprintf (stream, "0x%x", address);
}

static const struct mips_cp0sel_name *
lookup_mips_cp0sel_name (const struct
    mips_cp0sel_name *names, unsigned int len, unsigned int cp0reg,
    unsigned int sel)
{
    unsigned int i;

    for (i = 0; i < len; i++)
        if (names[i].cp0reg == cp0reg && names[i].sel == sel)
            return &names[i];
    return NULL;
}

/* Print insn arguments for 32/64-bit code.  */

static void
print_insn_args (const char *d,
    register unsigned long int l,
    unsigned pc, FILE *stream, const struct mips_opcode *opp)
{
    int op, delta;
    unsigned int lsb, msb, msbd;

    lsb = 0;

    for (; *d != '\0'; d++) {
        switch (*d) {
        case ',':
        case '(':
        case ')':
        case '[':
        case ']':
            fprintf (stream, "%c", *d);
            break;

        case '+':
            /* Extension character; switch for second char.  */
            d++;
            switch (*d) {
            case '\0':
                /* xgettext:c-format */
                fprintf (stream,
                    "# internal error, incomplete extension sequence (+)");
                return;

            case 'A':
                lsb = (l >> OP_SH_SHAMT) & OP_MASK_SHAMT;
                fprintf (stream, "0x%x", lsb);
                break;

            case 'B':
                msb = (l >> OP_SH_INSMSB) & OP_MASK_INSMSB;
                fprintf (stream, "0x%x", msb - lsb + 1);
                break;

            case '1':
                fprintf (stream, "0x%lx",
                    (l >> OP_SH_UDI1) & OP_MASK_UDI1);
                break;

            case '2':
                fprintf (stream, "0x%lx",
                    (l >> OP_SH_UDI2) & OP_MASK_UDI2);
                break;

            case '3':
                fprintf (stream, "0x%lx",
                    (l >> OP_SH_UDI3) & OP_MASK_UDI3);
                break;

            case '4':
                fprintf (stream, "0x%lx",
                    (l >> OP_SH_UDI4) & OP_MASK_UDI4);
                break;

            case 'C':
            case 'H':
                msbd = (l >> OP_SH_EXTMSBD) & OP_MASK_EXTMSBD;
                fprintf (stream, "0x%x", msbd + 1);
                break;

            case 'D':
                {
                    const struct mips_cp0sel_name *n;
                    unsigned int cp0reg, sel;

                    cp0reg = (l >> OP_SH_RD) & OP_MASK_RD;
                    sel = (l >> OP_SH_SEL) & OP_MASK_SEL;

                    /* CP0 register including 'sel' code for mtcN (et al.), to be
                     * printed textually if known.  If not known, print both
                     * CP0 register name and sel numerically since CP0 register
                     * with sel 0 may have a name unrelated to register being
                     * printed.  */
                    n = lookup_mips_cp0sel_name (mips_cp0sel_names,
                        mips_cp0sel_names_len, cp0reg, sel);
                    if (n != NULL)
                        fprintf (stream, "%s", n->name);
                    else
                        fprintf (stream, "$%d,%d", cp0reg,
                            sel);
                    break;
                }

            case 'E':
                lsb = ((l >> OP_SH_SHAMT) & OP_MASK_SHAMT) + 32;
                fprintf (stream, "0x%x", lsb);
                break;

            case 'F':
                msb = ((l >> OP_SH_INSMSB) & OP_MASK_INSMSB) + 32;
                fprintf (stream, "0x%x", msb - lsb + 1);
                break;

            case 'G':
                msbd = ((l >> OP_SH_EXTMSBD) & OP_MASK_EXTMSBD) + 32;
                fprintf (stream, "0x%x", msbd + 1);
                break;

            case 't':          /* Coprocessor 0 reg name */
                fprintf (stream, "%s",
                    mips_cp0_names[(l >> OP_SH_RT) & OP_MASK_RT]);
                break;

            case 'T':          /* Coprocessor 0 reg name */
                {
                    const struct mips_cp0sel_name *n;
                    unsigned int cp0reg, sel;

                    cp0reg = (l >> OP_SH_RT) & OP_MASK_RT;
                    sel = (l >> OP_SH_SEL) & OP_MASK_SEL;

                    /* CP0 register including 'sel' code for mftc0, to be
                     * printed textually if known.  If not known, print both
                     * CP0 register name and sel numerically since CP0 register
                     * with sel 0 may have a name unrelated to register being
                     * printed.  */
                    n = lookup_mips_cp0sel_name (mips_cp0sel_names,
                        mips_cp0sel_names_len, cp0reg, sel);
                    if (n != NULL)
                        fprintf (stream, "%s", n->name);
                    else
                        fprintf (stream, "$%d,%d", cp0reg,
                            sel);
                    break;
                }

            case 'x':          /* bbit bit index */
                fprintf (stream, "0x%lx",
                    (l >> OP_SH_BBITIND) & OP_MASK_BBITIND);
                break;

            case 'p':          /* cins, cins32, exts and exts32 position */
                fprintf (stream, "0x%lx",
                    (l >> OP_SH_CINSPOS) & OP_MASK_CINSPOS);
                break;

            case 's':          /* cins and exts length-minus-one */
                fprintf (stream, "0x%lx",
                    (l >> OP_SH_CINSLM1) & OP_MASK_CINSLM1);
                break;

            case 'S':          /* cins32 and exts32 length-minus-one field */
                fprintf (stream, "0x%lx",
                    (l >> OP_SH_CINSLM1) & OP_MASK_CINSLM1);
                break;

            case 'Q':          /* seqi/snei immediate field */
                op = (l >> OP_SH_SEQI) & OP_MASK_SEQI;
                /* Sign-extend it.  */
                op = (op ^ 512) - 512;
                fprintf (stream, "%d", op);
                break;

            default:
                /* xgettext:c-format */
                fprintf (stream, "# internal error, undefined extension sequence (+%c)", *d);
                return;
            }
            break;

        case '2':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_BP) & OP_MASK_BP);
            break;

        case '3':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_SA3) & OP_MASK_SA3);
            break;

        case '4':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_SA4) & OP_MASK_SA4);
            break;

        case '5':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_IMM8) & OP_MASK_IMM8);
            break;

        case '6':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_RS) & OP_MASK_RS);
            break;

        case '7':
            fprintf (stream, "$ac%ld",
                (l >> OP_SH_DSPACC) & OP_MASK_DSPACC);
            break;

        case '8':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_WRDSP) & OP_MASK_WRDSP);
            break;

        case '9':
            fprintf (stream, "$ac%ld",
                (l >> OP_SH_DSPACC_S) & OP_MASK_DSPACC_S);
            break;

        case '0':              /* dsp 6-bit signed immediate in bit 20 */
            delta = ((l >> OP_SH_DSPSFT) & OP_MASK_DSPSFT);
            if (delta & 0x20)   /* test sign bit */
                delta |= ~OP_MASK_DSPSFT;
            fprintf (stream, "%d", delta);
            break;

        case ':':              /* dsp 7-bit signed immediate in bit 19 */
            delta = ((l >> OP_SH_DSPSFT_7) & OP_MASK_DSPSFT_7);
            if (delta & 0x40)   /* test sign bit */
                delta |= ~OP_MASK_DSPSFT_7;
            fprintf (stream, "%d", delta);
            break;

        case '\'':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_RDDSP) & OP_MASK_RDDSP);
            break;

        case '@':              /* dsp 10-bit signed immediate in bit 16 */
            delta = ((l >> OP_SH_IMM10) & OP_MASK_IMM10);
            if (delta & 0x200)  /* test sign bit */
                delta |= ~OP_MASK_IMM10;
            fprintf (stream, "%d", delta);
            break;

        case '!':
            fprintf (stream, "%ld",
                (l >> OP_SH_MT_U) & OP_MASK_MT_U);
            break;

        case '$':
            fprintf (stream, "%ld",
                (l >> OP_SH_MT_H) & OP_MASK_MT_H);
            break;

        case '*':
            fprintf (stream, "$ac%ld",
                (l >> OP_SH_MTACC_T) & OP_MASK_MTACC_T);
            break;

        case '&':
            fprintf (stream, "$ac%ld",
                (l >> OP_SH_MTACC_D) & OP_MASK_MTACC_D);
            break;

        case 'g':
            /* Coprocessor register for CTTC1, MTTC2, MTHC2, CTTC2.  */
            fprintf (stream, "$%ld",
                (l >> OP_SH_RD) & OP_MASK_RD);
            break;

        case 's':
        case 'b':
        case 'r':
        case 'v':
            fprintf (stream, "%s",
                mips_gpr_names[(l >> OP_SH_RS) & OP_MASK_RS]);
            break;

        case 't':
        case 'w':
            fprintf (stream, "%s",
                mips_gpr_names[(l >> OP_SH_RT) & OP_MASK_RT]);
            break;

        case 'i':
        case 'u':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_IMMEDIATE) & OP_MASK_IMMEDIATE);
            break;

        case 'j':              /* Same as i, but sign-extended.  */
        case 'o':
            delta = (l >> OP_SH_DELTA) & OP_MASK_DELTA;
            if (delta & 0x8000)
                delta |= ~0xffff;
            fprintf (stream, "%d", delta);
            break;

        case 'h':
            fprintf (stream, "0x%x",
                (unsigned int) ((l >> OP_SH_PREFX)
                    & OP_MASK_PREFX));
            break;

        case 'k':
            fprintf (stream, "0x%x",
                (unsigned int) ((l >> OP_SH_CACHE)
                    & OP_MASK_CACHE));
            break;

        case 'a':
            target = (((pc + 4) & ~(unsigned) 0x0fffffff)
                | (((l >> OP_SH_TARGET) & OP_MASK_TARGET) << 2));
            /* For gdb disassembler, force odd address on jalx.  */
            if (strcmp (opp->name, "jalx") == 0)
                target |= 1;
            print_address (target, stream);
            break;

        case 'p':
            /* Sign extend the displacement.  */
            delta = (l >> OP_SH_DELTA) & OP_MASK_DELTA;
            if (delta & 0x8000)
                delta |= ~0xffff;
            target = (delta << 2) + pc + INSNLEN;
            print_address (target, stream);
            break;

        case 'd':
            fprintf (stream, "%s",
                mips_gpr_names[(l >> OP_SH_RD) & OP_MASK_RD]);
            break;

        case 'U':
            {
                /* First check for both rd and rt being equal.  */
                unsigned int reg = (l >> OP_SH_RD) & OP_MASK_RD;
                if (reg == ((l >> OP_SH_RT) & OP_MASK_RT))
                    fprintf (stream, "%s",
                        mips_gpr_names[reg]);
                else {
                    /* If one is zero use the other.  */
                    if (reg == 0)
                        fprintf (stream, "%s",
                            mips_gpr_names[(l >> OP_SH_RT) & OP_MASK_RT]);
                    else if (((l >> OP_SH_RT) & OP_MASK_RT) == 0)
                        fprintf (stream, "%s",
                            mips_gpr_names[reg]);
                    else        /* Bogus, result depends on processor.  */
                        fprintf (stream, "%s or %s",
                            mips_gpr_names[reg],
                            mips_gpr_names[(l >> OP_SH_RT) & OP_MASK_RT]);
                }
            }
            break;

        case 'z':
            fprintf (stream, "%s", mips_gpr_names[0]);
            break;

        case '<':
        case '1':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_SHAMT) & OP_MASK_SHAMT);
            break;

        case 'c':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_CODE) & OP_MASK_CODE);
            break;

        case 'q':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_CODE2) & OP_MASK_CODE2);
            break;

        case 'C':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_COPZ) & OP_MASK_COPZ);
            break;

        case 'B':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_CODE20) & OP_MASK_CODE20);
            break;

        case 'J':
            fprintf (stream, "0x%lx",
                (l >> OP_SH_CODE19) & OP_MASK_CODE19);
            break;

        case 'S':
        case 'V':
            fprintf (stream, "%s",
                mips_fpr_names[(l >> OP_SH_FS) & OP_MASK_FS]);
            break;

        case 'T':
        case 'W':
            fprintf (stream, "%s",
                mips_fpr_names[(l >> OP_SH_FT) & OP_MASK_FT]);
            break;

        case 'D':
            fprintf (stream, "%s",
                mips_fpr_names[(l >> OP_SH_FD) & OP_MASK_FD]);
            break;

        case 'R':
            fprintf (stream, "%s",
                mips_fpr_names[(l >> OP_SH_FR) & OP_MASK_FR]);
            break;

        case 'E':
            /* Coprocessor register for lwcN instructions, et al.
             *
             * Note that there is no load/store cp0 instructions, and
             * that FPU (cp1) instructions disassemble this field using
             * 'T' format.  Therefore, until we gain understanding of
             * cp2 register names, we can simply print the register
             * numbers.  */
            fprintf (stream, "$%ld",
                (l >> OP_SH_RT) & OP_MASK_RT);
            break;

        case 'G':
            /* Coprocessor register for mtcN instructions, et al.  Note
             * that FPU (cp1) instructions disassemble this field using
             * 'S' format.  Therefore, we only need to worry about cp0,
             * cp2, and cp3.  */
            op = (l >> OP_SH_OP) & OP_MASK_OP;
            if (op == OP_OP_COP0)
                fprintf (stream, "%s",
                    mips_cp0_names[(l >> OP_SH_RD) & OP_MASK_RD]);
            else
                fprintf (stream, "$%ld",
                    (l >> OP_SH_RD) & OP_MASK_RD);
            break;

        case 'K':
            fprintf (stream, "%s",
                mips_hwr_names[(l >> OP_SH_RD) & OP_MASK_RD]);
            break;

        case 'N':
            fprintf (stream,
                ((opp->pinfo & (FP_D | FP_S)) != 0
                    ? "$fcc%ld" : "$cc%ld"), (l >> OP_SH_BCC) & OP_MASK_BCC);
            break;

        case 'M':
            fprintf (stream, "$fcc%ld",
                (l >> OP_SH_CCC) & OP_MASK_CCC);
            break;

        case 'P':
            fprintf (stream, "%ld",
                (l >> OP_SH_PERFREG) & OP_MASK_PERFREG);
            break;

        case 'e':
            fprintf (stream, "%ld",
                (l >> OP_SH_VECBYTE) & OP_MASK_VECBYTE);
            break;

        case '%':
            fprintf (stream, "%ld",
                (l >> OP_SH_VECALIGN) & OP_MASK_VECALIGN);
            break;

        case 'H':
            fprintf (stream, "%ld",
                (l >> OP_SH_SEL) & OP_MASK_SEL);
            break;

        case 'O':
            fprintf (stream, "%ld",
                (l >> OP_SH_ALN) & OP_MASK_ALN);
            break;

        case 'Q':
            {
                unsigned int vsel = (l >> OP_SH_VSEL) & OP_MASK_VSEL;

                if ((vsel & 0x10) == 0) {
                    int fmt;

                    vsel &= 0x0f;
                    for (fmt = 0; fmt < 3; fmt++, vsel >>= 1)
                        if ((vsel & 1) == 0)
                            break;
                    fprintf (stream, "$v%ld[%d]",
                        (l >> OP_SH_FT) & OP_MASK_FT, vsel >> 1);
                } else if ((vsel & 0x08) == 0) {
                    fprintf (stream, "$v%ld",
                        (l >> OP_SH_FT) & OP_MASK_FT);
                } else {
                    fprintf (stream, "0x%lx",
                        (l >> OP_SH_FT) & OP_MASK_FT);
                }
            }
            break;

        case 'X':
            fprintf (stream, "$v%ld",
                (l >> OP_SH_FD) & OP_MASK_FD);
            break;

        case 'Y':
            fprintf (stream, "$v%ld",
                (l >> OP_SH_FS) & OP_MASK_FS);
            break;

        case 'Z':
            fprintf (stream, "$v%ld",
                (l >> OP_SH_FT) & OP_MASK_FT);
            break;

        default:
            /* xgettext:c-format */
            fprintf (stream, "# internal error, undefined modifier (%c)", *d);
            return;
        }
    }
}

/*
 * Print the mips instruction at address MEMADDR in debugged memory.
 * Returns length of the instruction, in bytes, which is
 * always INSNLEN.
 */
int
print_insn_mips (unsigned memaddr,
    unsigned long int word, FILE *stream)
{
    const struct mips_opcode *op;
    static int init = 0;
    static const struct mips_opcode *mips_hash[OP_MASK_OP + 1];

    /* Build a hash table to shorten the search time.  */
    if (! init) {
        unsigned int i;

        for (i = 0; i <= OP_MASK_OP; i++) {
            for (op = mips_opcodes; op < &mips_opcodes[mips_num_opcodes]; op++) {
                if (no_aliases && (op->pinfo2 & INSN2_ALIAS))
                    continue;
                if (i == ((op->match >> OP_SH_OP) & OP_MASK_OP)) {
                    mips_hash[i] = op;
                    break;
                }
            }
        }

        init = 1;
    }

    branch_delay_insns = 0;
    data_size = 0;
    insn_type = dis_nonbranch;
    target = 0;

    op = mips_hash[(word >> OP_SH_OP) & OP_MASK_OP];
    if (op != NULL) {
        for (; op < &mips_opcodes[mips_num_opcodes]; op++) {
            if (! (no_aliases && (op->pinfo2 & INSN2_ALIAS))
                && (word & op->mask) == op->match) {
                const char *d;

                /* Figure out instruction type and branch delay information.  */
                if ((op->pinfo & INSN_UNCOND_BRANCH_DELAY) != 0) {
                    if ((op->pinfo & (INSN_WRITE_GPR_31
                                | INSN_WRITE_GPR_D)) != 0)
                        insn_type = dis_jsr;
                    else
                        insn_type = dis_branch;
                    branch_delay_insns = 1;
                } else if ((op->pinfo & (INSN_COND_BRANCH_DELAY
                            | INSN_COND_BRANCH_LIKELY)) != 0) {
                    if ((op->pinfo & INSN_WRITE_GPR_31) != 0)
                        insn_type = dis_condjsr;
                    else
                        insn_type = dis_condbranch;
                    branch_delay_insns = 1;
                } else if ((op->pinfo & (INSN_STORE_MEMORY
                            | INSN_LOAD_MEMORY_DELAY)) != 0)
                    insn_type = dis_dref;

                fprintf (stream, "%s", op->name);

                d = op->args;
                if (d != NULL && *d != '\0') {
                    fprintf (stream, "\t");
                    print_insn_args (d, word, memaddr, stream, op);
                }

                return INSNLEN;
            }
        }
    }

    /* Handle undefined instructions.  */
    insn_type = dis_noninsn;
    fprintf (stream, ".word   0x%lx", word);
    return INSNLEN;
}

#ifdef NEED_MIPS16

#include "mips16-opc.c"

/* The mips16 registers.  */
static const unsigned int mips16_to_32_reg_map[] = {
    16, 17, 2, 3, 4, 5, 6, 7
};
#define mips16_reg_names(rn)	mips_gpr_names[mips16_to_32_reg_map[rn]]

static int big_endian;

static unsigned
getb16 (const unsigned char *buf)
{
    return (buf[0] << 8) | buf[1];
}

static unsigned
getl16 (const unsigned char *buf)
{
    return (buf[1] << 8) | buf[0];
}

static void
memory_error (int status, unsigned memaddr, FILE *stream)
{
    fprintf (stream, "Error reading memory at 0x%08x: %s", memaddr, strerror (status));
}

/*
 * Disassemble an operand for a mips16 instruction.
 */
static void
print_mips16_insn_arg (char type,
    const struct mips_opcode *op,
    int l,
    int use_extend,
    int extend, unsigned memaddr, FILE *stream,
    int (*read_memory) (unsigned addr, unsigned char *buf, unsigned nbytes))
{
    switch (type) {
    case ',':
    case '(':
    case ')':
        fprintf (stream, "%c", type);
        break;

    case 'y':
    case 'w':
        fprintf (stream, "%s",
            mips16_reg_names (((l >> MIPS16OP_SH_RY)
                    & MIPS16OP_MASK_RY)));
        break;

    case 'x':
    case 'v':
        fprintf (stream, "%s",
            mips16_reg_names (((l >> MIPS16OP_SH_RX)
                    & MIPS16OP_MASK_RX)));
        break;

    case 'z':
        fprintf (stream, "%s",
            mips16_reg_names (((l >> MIPS16OP_SH_RZ)
                    & MIPS16OP_MASK_RZ)));
        break;

    case 'Z':
        fprintf (stream, "%s",
            mips16_reg_names (((l >> MIPS16OP_SH_MOVE32Z)
                    & MIPS16OP_MASK_MOVE32Z)));
        break;

    case '0':
        fprintf (stream, "%s", mips_gpr_names[0]);
        break;

    case 'S':
        fprintf (stream, "%s", mips_gpr_names[29]);
        break;

    case 'P':
        fprintf (stream, "$pc");
        break;

    case 'R':
        fprintf (stream, "%s", mips_gpr_names[31]);
        break;

    case 'X':
        fprintf (stream, "%s",
            mips_gpr_names[((l >> MIPS16OP_SH_REGR32)
                    & MIPS16OP_MASK_REGR32)]);
        break;

    case 'Y':
        fprintf (stream, "%s",
            mips_gpr_names[MIPS16OP_EXTRACT_REG32R (l)]);
        break;

    case '<':
    case '>':
    case '[':
    case ']':
    case '4':
    case '5':
    case 'H':
    case 'W':
    case 'D':
    case 'j':
    case '6':
    case '8':
    case 'V':
    case 'C':
    case 'U':
    case 'k':
    case 'K':
    case 'p':
    case 'q':
    case 'A':
    case 'B':
    case 'E':
        {
            int immed, nbits, shift, signedp, extbits, pcrel, extu, branch;

            shift = 0;
            signedp = 0;
            extbits = 16;
            pcrel = 0;
            extu = 0;
            branch = 0;
            switch (type) {
            case '<':
                nbits = 3;
                immed = (l >> MIPS16OP_SH_RZ) & MIPS16OP_MASK_RZ;
                extbits = 5;
                extu = 1;
                break;
            case '>':
                nbits = 3;
                immed = (l >> MIPS16OP_SH_RX) & MIPS16OP_MASK_RX;
                extbits = 5;
                extu = 1;
                break;
            case '[':
                nbits = 3;
                immed = (l >> MIPS16OP_SH_RZ) & MIPS16OP_MASK_RZ;
                extbits = 6;
                extu = 1;
                break;
            case ']':
                nbits = 3;
                immed = (l >> MIPS16OP_SH_RX) & MIPS16OP_MASK_RX;
                extbits = 6;
                extu = 1;
                break;
            case '4':
                nbits = 4;
                immed = (l >> MIPS16OP_SH_IMM4) & MIPS16OP_MASK_IMM4;
                signedp = 1;
                extbits = 15;
                break;
            case '5':
                nbits = 5;
                immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
                insn_type = dis_dref;
                data_size = 1;
                break;
            case 'H':
                nbits = 5;
                shift = 1;
                immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
                insn_type = dis_dref;
                data_size = 2;
                break;
            case 'W':
                nbits = 5;
                shift = 2;
                immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
                if ((op->pinfo & MIPS16_INSN_READ_PC) == 0
                    && (op->pinfo & MIPS16_INSN_READ_SP) == 0) {
                    insn_type = dis_dref;
                    data_size = 4;
                }
                break;
            case 'D':
                nbits = 5;
                shift = 3;
                immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
                insn_type = dis_dref;
                data_size = 8;
                break;
            case 'j':
                nbits = 5;
                immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
                signedp = 1;
                break;
            case '6':
                nbits = 6;
                immed = (l >> MIPS16OP_SH_IMM6) & MIPS16OP_MASK_IMM6;
                break;
            case '8':
                nbits = 8;
                immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
                break;
            case 'V':
                nbits = 8;
                shift = 2;
                immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
                /* FIXME: This might be lw, or it might be addiu to $sp or
                 * $pc.  We assume it's load.  */
                insn_type = dis_dref;
                data_size = 4;
                break;
            case 'C':
                nbits = 8;
                shift = 3;
                immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
                insn_type = dis_dref;
                data_size = 8;
                break;
            case 'U':
                nbits = 8;
                immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
                extu = 1;
                break;
            case 'k':
                nbits = 8;
                immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
                signedp = 1;
                break;
            case 'K':
                nbits = 8;
                shift = 3;
                immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
                signedp = 1;
                break;
            case 'p':
                nbits = 8;
                immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
                signedp = 1;
                pcrel = 1;
                branch = 1;
                break;
            case 'q':
                nbits = 11;
                immed = (l >> MIPS16OP_SH_IMM11) & MIPS16OP_MASK_IMM11;
                signedp = 1;
                pcrel = 1;
                branch = 1;
                break;
            case 'A':
                nbits = 8;
                shift = 2;
                immed = (l >> MIPS16OP_SH_IMM8) & MIPS16OP_MASK_IMM8;
                pcrel = 1;
                /* FIXME: This can be lw or la.  We assume it is lw.  */
                insn_type = dis_dref;
                data_size = 4;
                break;
            case 'B':
                nbits = 5;
                shift = 3;
                immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
                pcrel = 1;
                insn_type = dis_dref;
                data_size = 8;
                break;
            case 'E':
                nbits = 5;
                shift = 2;
                immed = (l >> MIPS16OP_SH_IMM5) & MIPS16OP_MASK_IMM5;
                pcrel = 1;
                break;
            default:
                fprintf (stream, "# internal disassembler error, unrecognised mips16 type (%c)", type);
                abort ();
            }

            if (!use_extend) {
                if (signedp && immed >= (1 << (nbits - 1)))
                    immed -= 1 << nbits;
                immed <<= shift;
                if ((type == '<' || type == '>' || type == '[' || type == ']')
                    && immed == 0)
                    immed = 8;
            } else {
                if (extbits == 16)
                    immed |= ((extend & 0x1f) << 11) | (extend & 0x7e0);
                else if (extbits == 15)
                    immed |= ((extend & 0xf) << 11) | (extend & 0x7f0);
                else
                    immed = ((extend >> 6) & 0x1f) | (extend & 0x20);
                immed &= (1 << extbits) - 1;
                if (!extu && immed >= (1 << (extbits - 1)))
                    immed -= 1 << extbits;
            }

            if (!pcrel)
                fprintf (stream, "%d", immed);
            else {
                unsigned baseaddr;

                if (branch) {
                    immed *= 2;
                    baseaddr = memaddr + 2;
                } else if (use_extend)
                    baseaddr = memaddr - 2;
                else {
                    int status;
                    unsigned char buffer[2];

                    baseaddr = memaddr;

                    /* If this instruction is in the delay slot of a jr
                     * instruction, the base address is the address of the
                     * jr instruction.  If it is in the delay slot of jalr
                     * instruction, the base address is the address of the
                     * jalr instruction.  This test is unreliable: we have
                     * no way of knowing whether the previous word is
                     * instruction or data.  */
                    status = read_memory (memaddr - 4, buffer, 2);
                    if (status == 0
                        && (((big_endian ? getb16 (buffer)
                                         : getl16 (buffer))
                                & 0xf800) == 0x1800))
                        baseaddr = memaddr - 4;
                    else {
                        status = read_memory (memaddr - 2, buffer, 2);
                        if (status == 0
                            && (((big_endian ? getb16 (buffer)
                                             : getl16 (buffer))
                                    & 0xf81f) == 0xe800))
                            baseaddr = memaddr - 2;
                    }
                }
                target = (baseaddr & ~((1 << shift) - 1)) + immed;
                if (pcrel && branch)
                    /* For gdb disassembler, maintain odd address.  */
                    target |= 1;
                print_address (target, stream);
            }
        }
        break;

    case 'a':
        {
            int jalx = l & 0x400;

            if (!use_extend)
                extend = 0;
            l = ((l & 0x1f) << 23) | ((l & 0x3e0) << 13) | (extend << 2);
            if (! jalx)
                /* For gdb disassembler, maintain odd address.  */
                l |= 1;
        }
        target = ((memaddr + 4) & ~(unsigned) 0x0fffffff) | l;
        print_address (target, stream);
        break;

    case 'l':
    case 'L':
        {
            int need_comma, amask, smask;

            need_comma = 0;

            l = (l >> MIPS16OP_SH_IMM6) & MIPS16OP_MASK_IMM6;

            amask = (l >> 3) & 7;

            if (amask > 0 && amask < 5) {
                fprintf (stream, "%s", mips_gpr_names[4]);
                if (amask > 1)
                    fprintf (stream, "-%s",
                        mips_gpr_names[amask + 3]);
                need_comma = 1;
            }

            smask = (l >> 1) & 3;
            if (smask == 3) {
                fprintf (stream, "%s??",
                    need_comma ? "," : "");
                need_comma = 1;
            } else if (smask > 0) {
                fprintf (stream, "%s%s",
                    need_comma ? "," : "", mips_gpr_names[16]);
                if (smask > 1)
                    fprintf (stream, "-%s",
                        mips_gpr_names[smask + 15]);
                need_comma = 1;
            }

            if (l & 1) {
                fprintf (stream, "%s%s",
                    need_comma ? "," : "", mips_gpr_names[31]);
                need_comma = 1;
            }

            if (amask == 5 || amask == 6) {
                fprintf (stream, "%s$f0",
                    need_comma ? "," : "");
                if (amask == 6)
                    fprintf (stream, "-$f1");
            }
        }
        break;

    case 'm':
    case 'M':
        /* MIPS16e save/restore.  */
        {
            int need_comma = 0;
            int amask, args, statics;
            int nsreg, smask;
            int framesz;
            int i, j;

            l = l & 0x7f;
            if (use_extend)
                l |= extend << 16;

            amask = (l >> 16) & 0xf;
            if (amask == MIPS16_ALL_ARGS) {
                args = 4;
                statics = 0;
            } else if (amask == MIPS16_ALL_STATICS) {
                args = 0;
                statics = 4;
            } else {
                args = amask >> 2;
                statics = amask & 3;
            }

            if (args > 0) {
                fprintf (stream, "%s", mips_gpr_names[4]);
                if (args > 1)
                    fprintf (stream, "-%s",
                        mips_gpr_names[4 + args - 1]);
                need_comma = 1;
            }

            framesz = (((l >> 16) & 0xf0) | (l & 0x0f)) * 8;
            if (framesz == 0 && !use_extend)
                framesz = 128;

            fprintf (stream, "%s%d",
                need_comma ? "," : "", framesz);

            if (l & 0x40)       /* $ra */
                fprintf (stream, ",%s",
                    mips_gpr_names[31]);

            nsreg = (l >> 24) & 0x7;
            smask = 0;
            if (l & 0x20)       /* $s0 */
                smask |= 1 << 0;
            if (l & 0x10)       /* $s1 */
                smask |= 1 << 1;
            if (nsreg > 0)      /* $s2-$s8 */
                smask |= ((1 << nsreg) - 1) << 2;

            /* Find first set static reg bit.  */
            for (i = 0; i < 9; i++) {
                if (smask & (1 << i)) {
                    fprintf (stream, ",%s",
                        mips_gpr_names[i == 8 ? 30 : (16 + i)]);
                    /* Skip over string of set bits.  */
                    for (j = i; smask & (2 << j); j++)
                        continue;
                    if (j > i)
                        fprintf (stream, "-%s",
                            mips_gpr_names[j == 8 ? 30 : (16 + j)]);
                    i = j + 1;
                }
            }

            /* Statics $ax - $a3.  */
            if (statics == 1)
                fprintf (stream, ",%s",
                    mips_gpr_names[7]);
            else if (statics > 0)
                fprintf (stream, ",%s-%s",
                    mips_gpr_names[7 - statics + 1], mips_gpr_names[7]);
        }
        break;

    default:
        /* xgettext:c-format */
        fprintf (stream, "# internal disassembler error, unrecognised modifier (%c)", type);
        abort ();
    }
}

/* Disassemble mips16 instructions.  */

int
print_insn_mips16 (unsigned memaddr, FILE *stream,
    int (*read_memory) (unsigned addr, unsigned char *buf, unsigned nbytes))
{
    int status;
    unsigned char buffer[2];
    int length;
    int insn;
    int use_extend;
    int extend = 0;
    const struct mips_opcode *op, *opend;

    branch_delay_insns = 0;
    data_size = 0;
    insn_type = dis_nonbranch;
    target = 0;

    status = read_memory (memaddr, buffer, 2);
    if (status != 0) {
        memory_error (status, memaddr, stream);
        return -1;
    }

    length = 2;

    if (big_endian)
        insn = getb16 (buffer);
    else
        insn = getl16 (buffer);

    /* Handle the extend opcode specially.  */
    use_extend = 0;
    if ((insn & 0xf800) == 0xf000) {
        use_extend = 1;
        extend = insn & 0x7ff;

        memaddr += 2;

        status = read_memory (memaddr, buffer, 2);
        if (status != 0) {
            fprintf (stream, "extend 0x%x",
                (unsigned int) extend);
            memory_error (status, memaddr, stream);
            return -1;
        }

        if (big_endian)
            insn = getb16 (buffer);
        else
            insn = getl16 (buffer);

        /* Check for an extend opcode followed by an extend opcode.  */
        if ((insn & 0xf800) == 0xf000) {
            fprintf (stream, "extend 0x%x",
                (unsigned int) extend);
            insn_type = dis_noninsn;
            return length;
        }

        length += 2;
    }

    /* FIXME: Should probably use a hash table on the major opcode here.  */

    opend = mips16_opcodes + mips16_num_opcodes;
    for (op = mips16_opcodes; op < opend; op++) {
        if (! (no_aliases && (op->pinfo2 & INSN2_ALIAS))
            && (insn & op->mask) == op->match) {
            const char *s;

            if (strchr (op->args, 'a') != NULL) {
                if (use_extend) {
                    fprintf (stream, "extend 0x%x",
                        (unsigned int) extend);
                    insn_type = dis_noninsn;
                    return length - 2;
                }

                use_extend = 0;

                memaddr += 2;

                status = read_memory (memaddr, buffer, 2);
                if (status == 0) {
                    use_extend = 1;
                    if (big_endian)
                        extend = getb16 (buffer);
                    else
                        extend = getl16 (buffer);
                    length += 2;
                }
            }

            fprintf (stream, "%s", op->name);
            if (op->args[0] != '\0')
                fprintf (stream, "\t");

            for (s = op->args; *s != '\0'; s++) {
                if (*s == ','
                    && s[1] == 'w'
                    && (((insn >> MIPS16OP_SH_RX) & MIPS16OP_MASK_RX)
                        == ((insn >> MIPS16OP_SH_RY) & MIPS16OP_MASK_RY))) {
                    /* Skip the register and the comma.  */
                    ++s;
                    continue;
                }
                if (*s == ','
                    && s[1] == 'v'
                    && (((insn >> MIPS16OP_SH_RZ) & MIPS16OP_MASK_RZ)
                        == ((insn >> MIPS16OP_SH_RX) & MIPS16OP_MASK_RX))) {
                    /* Skip the register and the comma.  */
                    ++s;
                    continue;
                }
                print_mips16_insn_arg (*s, op, insn, use_extend, extend,
                    memaddr, stream, read_memory);
            }

            /* Figure out branch instruction type and delay slot information.  */
            if ((op->pinfo & INSN_UNCOND_BRANCH_DELAY) != 0)
                branch_delay_insns = 1;
            if ((op->pinfo & (INSN_UNCOND_BRANCH_DELAY
                        | MIPS16_INSN_UNCOND_BRANCH)) != 0) {
                if ((op->pinfo & INSN_WRITE_GPR_31) != 0)
                    insn_type = dis_jsr;
                else
                    insn_type = dis_branch;
            } else if ((op->pinfo & MIPS16_INSN_COND_BRANCH) != 0)
                insn_type = dis_condbranch;

            return length;
        }
    }

    if (use_extend)
        fprintf (stream, "0x%x", extend | 0xf000);
    fprintf (stream, "0x%x", insn);
    insn_type = dis_noninsn;

    return length;
}
#endif
