/* VAX disassembler for DDT.
 *
 * Written Sept. 1985 by David Bridgham.
 */

#ifndef DDTCAT	/* don't reget headers if catting when all routines internal */
#include	"pdefs.h"
#include	"ptypes.h"
#include	"mdep.h"
#include	"ddt.h"

ext char d_crlf[];

#endif

struct op_ent {
    char *op_name;		/* Name of opcode */
    intf op_noands;		/* Number of operands */
    char op_oand[6];		/* Operand size (negative is branch size) */
};

#include "vax-op.h"

#define CASEB 0x8f
#define CASEW 0xaf
#define CASEL 0xcf
#define BUGL 0xfd
#define BUGW 0xfe
#define ESCD 0xfd
#define ESCF 0xff

/* Dummy routine for hysterical reasons */
mkioptab()
{}

unsb md_prinst(dsp, addr)
ddtst	*dsp;
unsw	addr;
{
    unsb	ins;
    int	ret;
    reg struct op_ent *opp;
    reg	unsw	new_addr;
    int	i;

    new_addr = addr;
    d_fetch(dsp, A_INST, sizeof(unsb), new_addr++, &ins);
    if (ins == ESCD) {
	d_fetch(dsp, A_INST, sizeof(unsb), new_addr++, &ins);
	opp = &escdtbl[ins];
    }
    else if (ins == ESCF) {
	d_fetch(dsp, A_INST, sizeof(unsb), new_addr++, &ins);
	if (ins == BUGL) {
	    dsp->d_puts("bugl\t");
	    new_addr += pr_gen(dsp, new_addr);
	    return new_addr - addr;
	}
	else if (ins == BUGW) {
	    dsp->d_puts("bugw\t");
	    new_addr += pr_gen(dsp, new_addr);
	    return new_addr - addr;
	}
	else {
	    d_lprrdx(dsp, 0xff00 | ins, dsp->d_obase);
	    return 2;
	}
    }
    else 
      opp = &maintbl[ins];
    
    dsp->d_puts(opp->op_name);
    dsp->d_putc('\t');

    switch (ins) {
    case CASEB:
	new_addr += pr_case(dsp, new_addr, sizeof(byte));
	break;
    case CASEW:
	new_addr += pr_case(dsp, new_addr, sizeof(short));
	break;
    case CASEL:
	new_addr += pr_case(dsp, new_addr, sizeof(long));
	break;
    default:
	for (i = 0; i < opp->op_noands; i++) {
	    if (i != 0)
	      dsp->d_putc(',');
	    
	    if (opp->op_oand[i] > 0)
	      new_addr += pr_gen(dsp, new_addr, opp->op_oand[i]);
	    else
	      new_addr += pr_const(dsp, new_addr, - opp->op_oand[i],
				   new_addr - opp->op_oand[i]);
	}
    }
    return (new_addr - addr);
}

static char *regs[16] = {
    "r0", "r1", "r2", "r3",
    "r4", "r5", "r6", "r7",
    "r8", "r9", "r10", "r11",
    "ap", "fp", "sp", "pc" };

/* Print a general operand.  Returns the size of the operand in bytes. */
static pr_gen(dsp, addr, size)
ddtst *dsp;
unsw addr;
unsb size;
{
    unsb oand;
    unsb reg_num;

    d_fetch(dsp, A_INST, sizeof(unsb), addr++, &oand);
    reg_num = oand & 0x0f;
    switch((oand >> 4) & 0x0f) {
    case 0:
    case 1:			/* Literal */
    case 2:
    case 3:
	dsp->d_putc('#');
	d_lprrdx(dsp, oand, dsp->d_obase);
	return 1;
    case 4:			/* Indexed */
	size = pr_gen(dsp, addr, size);
	dsp->d_putc('[');
	dsp->d_puts(regs[reg_num]);
	dsp->d_putc(']');
	return size + 1;
    case 5:			/* Register */
	dsp->d_puts(regs[reg_num]);
	return 1;
    case 7:			/* Autodecrement */
	dsp->d_putc('-');	/*   notice fall through to next case */
    case 6:			/* Register Deferred */
	dsp->d_putc('(');
	dsp->d_puts(regs[reg_num]);
	dsp->d_putc(')');
	return 1;
    case 9:			/* Autoinc Deferred or Absolute */
	dsp->d_putc('@');
    case 8:
	if (reg_num == 15) {	/* Immediate */
	    dsp->d_putc('#');
	    pr_const(dsp, addr, size, 0);
	    return size + 1;
	}
	else {			/* Autoincrement */
	    dsp->d_putc('(');
	    dsp->d_puts(regs[reg_num]);
	    dsp->d_puts(")+");
	}
	return 1;
    case 11:
	dsp->d_putc('@');
    case 10:
	pr_disp_addr(dsp, reg_num, addr, sizeof(byte));
	return 2;
    case 13:
	dsp->d_putc('@');
    case 12:
	pr_disp_addr(dsp, reg_num, addr, sizeof(short));
	return 3;
    case 15:
	dsp->d_putc('@');
    case 14:
	pr_disp_addr(dsp, reg_num, addr, sizeof(long));
	return 5;
    }
}


/* Print displacement (or relative) addressing mode. */
static pr_disp_addr(dsp, reg_num, addr, size)
ddtst *dsp;
unsb reg_num;
unsw addr;
unsb size;
{
    if (reg_num == 15) {	/* Relative */
	pr_const(dsp, addr, size, addr + size);
    }
    else {			/* Displacement */
	pr_const(dsp, addr, size, 0);
	dsp->d_putc('(');
	dsp->d_puts(regs[reg_num]);
	dsp->d_putc(')');
    }
}

/* Print a constant of size size.  Offset is added to the constant before
 * printing.
 */
static pr_const(dsp, addr, size, offset)
ddtst *dsp;
unsw addr;
unsb size;
unsw offset;
{
    intb bdisp;
    ints sdisp;
    intl ldisp;
    
    switch (size) {
    case sizeof(byte):
	d_fetch(dsp, A_INST, sizeof(intb), addr, &bdisp);
	d_prloc(dsp, bdisp + offset);
	break;
    case sizeof(short):
	d_fetch(dsp, A_INST, sizeof(ints), addr, &sdisp);
	d_prloc(dsp, sdisp + offset);
	break;
    case sizeof(long):
	d_fetch(dsp, A_INST, sizeof(intl), addr, &ldisp);
	d_prloc(dsp, ldisp + offset);
	break;
    }
    return size;
}

/* Print a case instruction.  The instruction name has already been
 * printed.  This is special cased so that it can print out the jump
 * vector if there is enough information.
 */
pr_case(dsp, addr, size)
ddtst *dsp;
unsw addr;
unsb size;
{
    reg int i;
    unsw new_addr;
    unsw addr_base;
    unsw base;
    unsw addr_limit;
    unsw limit;
    unsw addr_displ;

    new_addr = addr;
    new_addr += pr_gen(dsp, new_addr, size);
    dsp->d_putc(',');
    addr_base = new_addr;
    new_addr += pr_gen(dsp, new_addr, size);
    dsp->d_putc(',');
    addr_limit = new_addr;
    new_addr += pr_gen(dsp, new_addr, size);
    addr_displ = new_addr;

    if (get_if_const(dsp, addr_base, size, &base))
      return (new_addr - addr);
    if (get_if_const(dsp, addr_limit, size, &limit))
      return (new_addr - addr);
    for (i = 0; i <= limit; i++) {
	dsp->d_puts(d_crlf);
	dsp->d_putc('\t');
	d_lprrdx(dsp, base + i, dsp->d_obase);
	dsp->d_puts(":\t");
	pr_const(dsp, new_addr, sizeof(short), addr_displ);
	new_addr += sizeof(short);
    }
    return (new_addr - addr);
}

/* Places the constant operand pointed to by addr into result.
 * If the operand is not a constant then it returns -1, else returns 0.
 */
get_if_const(dsp, addr, size, result)
ddtst *dsp;
unsw addr;
unsb size;
intl *result;
{
    unsb oand;
    intb bdisp;
    ints sdisp;
    intl ldisp;

    d_fetch(dsp, A_INST, sizeof(byte), addr, &oand);
    switch ((oand >> 4) & 0x0f) {
    case 0:
    case 1:
    case 2:
    case 3:
	*result = oand;
	return 0;
    case 8:
	if ((oand & 0x0f) != 0x0f)
	  return -1;
	switch (size) {
	case sizeof(byte):
	    d_fetch(dsp, A_INST, sizeof(intb), addr, &bdisp);
	    *result = bdisp;
	    break;
	case sizeof(short):
	    d_fetch(dsp, A_INST, sizeof(ints), addr, &sdisp);
	    *result = sdisp;
	    break;
	case sizeof(long):
	    d_fetch(dsp, A_INST, sizeof(intl), addr, &ldisp);
	    *result = ldisp;
	    break;
	}
	return 0;
    default:
	return -1;
    }
}
