/* $NetBSD: db_disasm.c,v 1.20 2023/11/21 22:25:16 thorpej Exp $ */

/*
 * Mach Operating System
 * Copyright (c) 1991,1990,1989,1988,1987 Carnegie Mellon University
 * All Rights Reserved.
 *
 * Permission to use, copy, modify and distribute this software and its
 * documentation is hereby granted, provided that both the copyright
 * notice and this permission notice appear in all copies of the
 * software, derivative works or modified versions, and any portions
 * thereof, and that both notices appear in supporting documentation.
 *
 * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS "AS IS"
 * CONDITION.  CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR
 * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE.
 *
 * Carnegie Mellon requests users of this software to return to
 *
 *  Software Distribution Coordinator  or  Software.Distribution@CS.CMU.EDU
 *  School of Computer Science
 *  Carnegie Mellon University
 *  Pittsburgh PA 15213-3890
 *
 * any improvements or extensions that they make and grant Carnegie Mellon
 * the rights to redistribute these changes.
 */

/*
 *	File: db_disasm.c
 * 	Author: Alessandro Forin, Carnegie Mellon University
 *	Date:	11/91
 *
 *	Disassembler for Alpha
 *
 *	Modified for NetBSD/alpha by:
 *
 *	Christopher G. Demetriou, Carnegie Mellon University
 *
 *	Jason R. Thorpe, Numerical Aerospace Simulation Facility,
 *	NASA Ames Research Center
 *
 *	This code was derived exclusively from information available in
 *	"Alpha Architecture Reference Manual", Richard L. Sites ed.
 *	Digital Press, Burlington, MA 01803
 *	ISBN 1-55558-098-X, Order no. EY-L520E-DP
 */

#include <sys/cdefs.h>			/* RCS ID & Copyright macro defns */

__KERNEL_RCSID(0, "$NetBSD: db_disasm.c,v 1.20 2023/11/21 22:25:16 thorpej Exp $");

#include <sys/param.h>
#include <sys/systm.h>
#include <sys/proc.h>
#include <machine/db_machdep.h>
#include <machine/alpha_instruction.h>

#include <machine/pal.h>

#include <ddb/db_access.h>
#include <ddb/db_sym.h>
#include <ddb/db_output.h>
#include <ddb/db_interface.h>

/*
 * This would belong in a header file, except noone else needs it
 *
 * XXX THESE SHOULD BE CONVERTED TO ra, rb, rc FORMAT.
 */
typedef union {
	/*
	 *	All instructions are 32 bits wide, PAL included
	 */
	unsigned int	bits;

	/*
	 *	Internal processor register access instrs
	 *	specify the IPR index, doubly specify the
	 *	(same) GP register as src/dest, and qualifiers
	 *	for the IPR set involved (abox/ibox/tmp)
	 */
	struct {
		unsigned	index : 5,
				regset : 3, /* a,i,p */
				xxx : 8,
				rs : 5,
				rd : 5,
				opcode : 6;
	} mXpr_format;

	/*
	 *	Load/store instructions have a 12 bit displacement,
	 *	and two register specifiers just as normal ld/st.
	 *	Four bits have special meanings:
	 *		phy: bypass the MMU (physical access)
	 *		alt: use mode in ALT register for checks,
	 *		     or if PHY is also on locked/linked access
	 *		rwc: read-with-write-check (probew)
	 *		qw:  quadword access
	 */
	struct {
		signed int	displacement : 12;
		unsigned	qw : 1,
				qualif : 3,
				rs : 5,
				rd : 5,
				opcode : 6;
	} mem_format;

	/*
	 *	Return from exception or interrupt has
	 *	a branch-like encoding, but only one
	 *	instantiation is actually usable.
	 */
	struct {
		unsigned	xxx : 14,
				zero : 1,	/* branch prediction! */
				one : 1,
				rb : 5,		/* r31 or stall */
				ra : 5,		/* r31 or stall */
				opcode : 6;
	} rei_format;

} pal_instruction;

/*
 * Major opcodes
 */
static const char * const op_name[64] = {
/* 0 */	"call_pal", "op1", "op2", "op3", "op4",	"op5",	"op6",	"op7",
/* 8 */	"lda",	"ldah",	"ldbu",	"ldq_u","ldwu",	"stw",	"stb",	"stq_u",
/*16 */	"arit",	"logical","bit","mul",	"op20",	"vaxf",	"ieeef","anyf",
/*24 */	"spec",	"hw_mfpr","jump","hw_ld","intmisc","hw_mtpr","hw_rei","hw_st",
/*32 */	"ldf",	"ldg",	"lds",	"ldt",	"stf",	"stg",	"sts",	"stt",
/*40 */	"ldl",	"ldq",	"ldl_l","ldq_l","stl",	"stq",	"stl_c","stq_c",
/*48 */	"br",	"fbeq",	"fblt",	"fble",	"bsr",	"fbne",	"fbge",	"fbgt",
/*56 */	"blbc",	"beq",	"blt",	"ble",	"blbs",	"bne",	"bge",	"bgt"
};

/*
 * The function field is too big (7 or 11 bits), so the sub-tables
 * are addressed in a somewhat complicated manner to save
 * space.  After all, alu operations is what RISCs are good at.
 */

struct tbl {
	const char	*name;
	int		code;
};

static const struct tbl pal_op_tbl[] = {
	/* Common PAL function codes. */
	{ "halt",		PAL_halt },
	{ "cflush",		PAL_cflush },
	{ "draina",		PAL_draina },
	{ "cserve",		PAL_cserve, },
	{ "swppal",		PAL_swppal },
	{ "ipir",		PAL_ipir },
	{ "bpt",		PAL_bpt },
	{ "bugchk",		PAL_bugchk },
	{ "imb",		PAL_imb },
	{ "rdunique",		PAL_rdunique },
	{ "wrunique",		PAL_wrunique },
	{ "gentrap",		PAL_gentrap },

	/* OSF/1 PAL function codes. */
	{ "osf1_rdmces",	PAL_OSF1_rdmces },
	{ "osf1_wrmces",	PAL_OSF1_wrmces },
	{ "osf1_wrfen",		PAL_OSF1_wrfen },
	{ "osf1_wrvptptr",	PAL_OSF1_wrvptptr },
	{ "osf1_swpctx",	PAL_OSF1_swpctx },
	{ "osf1_wrval",		PAL_OSF1_wrval },
	{ "osf1_rdval",		PAL_OSF1_rdval },
	{ "osf1_tbi",		PAL_OSF1_tbi },
	{ "osf1_wrent",		PAL_OSF1_wrent },
	{ "osf1_swpipl",	PAL_OSF1_swpipl },
	{ "osf1_rdps",		PAL_OSF1_rdps },
	{ "osf1_wrkgp",		PAL_OSF1_wrkgp },
	{ "osf1_wrusp",		PAL_OSF1_wrusp },
	{ "osf1_wrperfmon",	PAL_OSF1_wrperfmon },
	{ "osf1_rdusp",		PAL_OSF1_rdusp },
	{ "osf1_whami",		PAL_OSF1_whami },
	{ "osf1_retsys",	PAL_OSF1_retsys },
	{ "osf1_rti",		PAL_OSF1_rti },
	{ "osf1_callsys",	PAL_OSF1_callsys },

	{ NULL,			-1 },
};

static const char *
pal_opname(int op)
{
	static char unk[11];
	int i;

	for (i = 0; pal_op_tbl[i].name != NULL; i++) {
		if (pal_op_tbl[i].code == op)
			return (pal_op_tbl[i].name);
	}

	snprintf(unk, sizeof(unk), "0x%08x", op);
	return (unk);
}

/* HW (PAL) instruction qualifiers, stright tables */
static const char * const mXpr_name[8] = {
	"", "/i", "/a", "/ai", "/p", "/pi", "/pa", "/pai"
};
static const char * const hwlds_name[8] = {
	"", "/r", "/a", "/ar", "/p", "/p?r", "_l-c", "_l-c/?r"
};

/*
 * For this one we take the low nibble (valid values 0/2/9/b/d)
 * and shift it down one to get the row index.  Within a row
 * we can just take the high nibble deprived of the high bit
 * (valid values 0/1/2/3/4/6).  We could have used a flat 64
 * entry array, but in this way we use just 48 pointers.
 * BUGFIX: the 'cmpbge 0x0f' opcode fits in here too
 */
static const char * const arit_c0[8] = {
	"addl", 0, "addq", 0, "addl/v", 0, "addq/v",
};
static const char * const arit_c2[8] = {
	"s4addl", "s8addl", "s4addq", "s8addq",
};
static const char * const arit_c9[8] = {
	"subl", 0, "subq", 0, "subl/v", 0, "subq/v",
};
static const char * const arit_cB[8] = {
	"s4subl", "s8subl", "s4subq", "s8subq",
};
static const char * const arit_cD[8] = {
	0, "cmpult", "cmpeq", "cmpule", "cmplt", 0, "cmple",
};
static const char * const arit_cF[1] = {
	"cmpbge"
};
static const char * const * const arit_opname[8] = {
	arit_c0, arit_c2, 0, 0, arit_c9, arit_cB, arit_cD, arit_cF
};

static const char *
arit_name(int op)
{
	static char unk[32];
	const char *name = NULL;

	if (arit_opname[((op)&0xe)>>1])
		name = arit_opname[((op)&0xe)>>1][((op)&0x70)>>4];

	if (name != NULL)
		return (name);

	snprintf(unk, sizeof(unk), "?arit 0x%x?", op);
	return (unk);
}

/*
 * Something similar for this one, except there are only
 * 16 entries so the row indexing is done by enumeration
 * of the low nibble (valid values 0/4/6/8).  Then we can
 * just shift the high nibble to index inside the row
 * (valid values are 0/2/4 or 1/2/4/6)
 *
 * There are two functions that don't play by these simple rules,
 * so we special-case them.
 */
static const char * const logical_c0[4] = {
	"and", "or", "xor", 0
};
static const char * const logical_c4[4] = {
	"cmovlbs", "cmoveq", "cmovlt", "cmovle"
};
static const char * const logical_c6[4] = {
	"cmovlbc", "cmovne", "cmovge", "cmovgt"
};
static const char * const logical_c8[4] = {
	"andnot", "ornot", "xornot", 0
};

static const char *
logical_name(int op)
{
	static char unk[32];
	const char *name = NULL;

	if (op == op_amask)
		return ("amask");
	else if (op == op_implver)
		return ("implver");

	switch (op & 0xf) {
	case 0: name = logical_c0[((op)>>5)&3]; break;
	case 4: name = logical_c4[((op)>>5)&3]; break;
	case 6: name = logical_c6[((op)>>5)&3]; break;
	case 8: name = logical_c8[((op)>>5)&3]; break;
	}

	if (name != NULL)
		return (name);

	snprintf(unk, sizeof(unk), "?logical 0x%x?", op);
	return (unk);
}

/*
 * This is the messy one. First, we single out the dense
 * case of a 3 in the high nibble (valid values 0/1/2/4/6/9/b/c).
 * Then the case of a 2 in the low nibble (valid values 0/1/2/5/6/7).
 * For the remaining codes (6/7/a/b) we do as above: high
 * nibble has valid values 0/1/2 or 5/6/7.  The low nibble
 * can be used as row index picking bits 0 and 2, for the
 * high one just the lower two bits.
 */
static const char * const bitop_c3[8] = {
	"zapnot", "mskql", "srl", "extql", "sll", "insql", "sra", 0
};
static const char * const bitop_c2[8] = {
	"mskbl", "mskwl", "mskll", 0/*mskql*/, 0, "mskwh", "msklh", "mskqh"
};
static const char * const bitop_c67ab[4][4] = {
/* a */	{ 0, "extwh", "extlh", "extqh"},
/* b */	{ "insbl", "inswl", "insll", 0 },
/* 6 */	{ "extbl", "extwl", "extll", 0 },
/* 7 */	{ 0, "inswh", "inslh", "insqh" },
};

static const char *
bitop_name(int op)
{
	static char unk[32];
	const char *name = NULL;

	if ((op & 0x70) == 0x30)
		name = (op == op_zap) ? "zap" : bitop_c3[((op)&0xe)>>1];
	else if ((op & 0xf) == 0x02)
		name = bitop_c2[(op)>>4];
	else
		name =
		    bitop_c67ab[(((op)&1)|(((op)&0x4)>>1))][(((op)&0x30)>>4)];

	if (name != NULL)
		return (name);

	snprintf(unk, sizeof(unk), "?bit 0x%x?", op);
	return (unk);
}

/*
 * Only 4 entries in this one
 */
static const char * const mul_opname[4] = {
	"mull", "mulq", "mull/v", "mulq/v"
};

static const char *
mul_name(int op)
{
	static char unk[32];
	const char *name = NULL;

	name = (op == op_umulh) ? "umulh" : mul_opname[((op)>>5)&3];

	if (name != NULL)
		return (name);

	snprintf(unk, sizeof(unk), "?mul 0x%x?", op);
	return (unk);
}

/*
 * These are few, the high nibble is usually enough to dispatch.
 * We single out the `f' case to halve the table size, as
 * well as the cases in which the high nibble isn't enough.
 */
static const char * const special_opname[8] = {
	"trapb", 0, "mb", 0, "fetch", "fetch_m", "rpcc", "rc"
};

static const char *
special_name(int op)
{
	static char unk[32];
	const char *name;

	switch (op) {
	case op_excb:		name = "excb";		break;
	case op_wmb:		name = "wmb";		break;
	case op_ecb:		name = "ecb";		break;
	case op_rs:		name = "rs";		break;
	case op_wh64:		name = "wh64";		break;
	default:
		name = special_opname[(op)>>13];
	}

	if (name != NULL)
		return (name);

	snprintf(unk, sizeof(unk), "?special 0x%x?", op);
	return (unk);
}

/*
 * This is trivial
 */
static const char * const jump_opname[4] = {
	"jmp", "jsr", "ret", "jcr"
};
#define jump_name(ix)	jump_opname[ix]

/*
 * For all but 4 of these, we can dispatch on the lower nibble of
 * the "function".
 */
static const char * const intmisc_opname_3x[16] = {
	"ctpop", "perr", "ctlz", "cttz", "unpkbw", "unpkbl", "pkwb",
	"pklb", "minsb8", "minsw4", "minub8", "minuw4", "maxub8",
	"maxuw4", "maxsb8", "maxsw4",
};

static const char *
intmisc_name(int op)
{
	static char unk[32];

	if ((op & 0xf0) == 0x30)
		return (intmisc_opname_3x[op & 0x0f]);

	switch (op) {
	case op_sextb: return ("sextb");
	case op_sextw: return ("sextw");
	case op_ftoit: return ("ftoit");
	case op_ftois: return ("ftois");
	}

	snprintf(unk, sizeof(unk), "?intmisc 0x%x?", op);
	return (unk);
}

static const char *
float_name(const struct tbl *tbl, int op, const char *type)
{
	static char unk[32];
	int i;

	for (i = 0; tbl[i].name != NULL; i++) {
		if (tbl[i].code == op)
			return (tbl[i].name);
	}

	snprintf(unk, sizeof(unk), "?%s 0x%x?", type, op);
	return (unk);
}

#define vaxf_name(op)	float_name(vaxf_tbl, op, "vaxfl")
#define ieeef_name(op)	float_name(ieeef_tbl, op, "ieeefl")
#define anyf_name(op)	float_name(anyf_tbl, op, "anyfl")

static const struct tbl anyf_tbl[] = {
	{ "cvtlq",	0x010},
	{ "cpys",	0x020},
	{ "cpysn",	0x021},
	{ "cpyse",	0x022},
	{ "mt_fpcr",	0x024},
	{ "mf_fpcr",	0x025},
	{ "fcmoveq",	0x02a},
	{ "fcmovne",	0x02b},
	{ "fcmovlt",	0x02c},
	{ "fcmovge",	0x02d},
	{ "fcmovle",	0x02e},
	{ "fcmovgt",	0x02f},
	{ "cvtql",	0x030},
	{ "cvtql/v",	0x130},
	{ "cvtql/sv",	0x330},
	{ 0, 0},
};

static const struct tbl ieeef_tbl[] = {
	{ "adds/c",	0x000},
	{ "subs/c",	0x001},
	{ "muls/c",	0x002},
	{ "divs/c",	0x003},
	{ "addt/c",	0x020},
	{ "subt/c",	0x021},
	{ "mult/c",	0x022},
	{ "divt/c",	0x023},
	{ "cvtts/c",	0x02c},
	{ "cvttq/c",	0x02f},
	{ "cvtqs/c",	0x03c},
	{ "cvtqt/c",	0x03e},
	{ "adds/m",	0x040},
	{ "subs/m",	0x041},
	{ "muls/m",	0x042},
	{ "divs/m",	0x043},
	{ "addt/m",	0x060},
	{ "subt/m",	0x061},
	{ "mult/m",	0x062},
	{ "divt/m",	0x063},
	{ "cvtts/m",	0x06c},
	{ "cvtqs/m",	0x07c},
	{ "cvtqt/m",	0x07e},
	{ "adds",	0x080},
	{ "subs",	0x081},
	{ "muls",	0x082},
	{ "divs",	0x083},
	{ "addt",	0x0a0},
	{ "subt",	0x0a1},
	{ "mult",	0x0a2},
	{ "divt",	0x0a3},
	{ "cmptun",	0x0a4},
	{ "cmpteq",	0x0a5},
	{ "cmptlt",	0x0a6},
	{ "cmptle",	0x0a7},
	{ "cvtts",	0x0ac},
	{ "cvttq",	0x0af},
	{ "cvtqs",	0x0bc},
	{ "cvtqt",	0x0be},
	{ "adds/d",	0x0c0},
	{ "subs/d",	0x0c1},
	{ "muls/d",	0x0c2},
	{ "divs/d",	0x0c3},
	{ "addt/d",	0x0e0},
	{ "subt/d",	0x0e1},
	{ "mult/d",	0x0e2},
	{ "divt/d",	0x0e3},
	{ "cvtts/d",	0x0ec},
	{ "cvtqs/d",	0x0fc},
	{ "cvtqt/d",	0x0fe},
	{ "adds/uc",	0x100},
	{ "subs/uc",	0x101},
	{ "muls/uc",	0x102},
	{ "divs/uc",	0x103},
	{ "addt/uc",	0x120},
	{ "subt/uc",	0x121},
	{ "mult/uc",	0x122},
	{ "divt/uc",	0x123},
	{ "cvtts/uc",	0x12c},
	{ "cvttq/vc",	0x12f},
	{ "adds/um",	0x140},
	{ "subs/um",	0x141},
	{ "muls/um",	0x142},
	{ "divs/um",	0x143},
	{ "addt/um",	0x160},
	{ "subt/um",	0x161},
	{ "mult/um",	0x162},
	{ "divt/um",	0x163},
	{ "cvtts/um",	0x16c},
	{ "adds/u",	0x180},
	{ "subs/u",	0x181},
	{ "muls/u",	0x182},
	{ "divs/u",	0x183},
	{ "addt/u",	0x1a0},
	{ "subt/u",	0x1a1},
	{ "mult/u",	0x1a2},
	{ "divt/u",	0x1a3},
	{ "cvtts/u",	0x1ac},
	{ "cvttq/v",	0x1af},
	{ "adds/ud",	0x1c0},
	{ "subs/ud",	0x1c1},
	{ "muls/ud",	0x1c2},
	{ "divs/ud",	0x1c3},
	{ "addt/ud",	0x1e0},
	{ "subt/ud",	0x1e1},
	{ "mult/ud",	0x1e2},
	{ "divt/ud",	0x1e3},
	{ "cvtts/ud",	0x1ec},
	{ "adds/suc",	0x500},
	{ "subs/suc",	0x501},
	{ "muls/suc",	0x502},
	{ "divs/suc",	0x503},
	{ "addt/suc",	0x520},
	{ "subt/suc",	0x521},
	{ "mult/suc",	0x522},
	{ "divt/suc",	0x523},
	{ "cvtts/suc",	0x52c},
	{ "cvttq/svc",	0x52f},
	{ "adds/sum",	0x540},
	{ "subs/sum",	0x541},
	{ "muls/sum",	0x542},
	{ "divs/sum",	0x543},
	{ "addt/sum",	0x560},
	{ "subt/sum",	0x561},
	{ "mult/sum",	0x562},
	{ "divt/sum",	0x563},
	{ "cvtts/sum",	0x56c},
	{ "adds/su",	0x580},
	{ "subs/su",	0x581},
	{ "muls/su",	0x582},
	{ "divs/su",	0x583},
	{ "addt/su",	0x5a0},
	{ "subt/su",	0x5a1},
	{ "mult/su",	0x5a2},
	{ "divt/su",	0x5a3},
	{ "cmptun/su",	0x5a4},
	{ "cmpteq/su",	0x5a5},
	{ "cmptlt/su",	0x5a6},
	{ "cmptle/su",	0x5a7},
	{ "cvtts/su",	0x5ac},
	{ "cvttq/sv",	0x5af},
	{ "adds/sud",	0x5c0},
	{ "subs/sud",	0x5c1},
	{ "muls/sud",	0x5c2},
	{ "divs/sud",	0x5c3},
	{ "addt/sud",	0x5e0},
	{ "subt/sud",	0x5e1},
	{ "mult/sud",	0x5e2},
	{ "divt/sud",	0x5e3},
	{ "cvtts/sud",	0x5ec},
	{ "adds/suic",	0x700},
	{ "subs/suic",	0x701},
	{ "muls/suic",	0x702},
	{ "divs/suic",	0x703},
	{ "addt/suic",	0x720},
	{ "subt/suic",	0x721},
	{ "mult/suic",	0x722},
	{ "divt/suic",	0x723},
	{ "cvtts/suic",	0x72c},
	{ "cvttq/svic",	0x72f},
	{ "cvtqs/suic",	0x73c},
	{ "cvtqt/suic",	0x73e},
	{ "adds/suim",	0x740},
	{ "subs/suim",	0x741},
	{ "muls/suim",	0x742},
	{ "divs/suim",	0x743},
	{ "addt/suim",	0x760},
	{ "subt/suim",	0x761},
	{ "mult/suim",	0x762},
	{ "divt/suim",	0x763},
	{ "cvtts/suim",	0x76c},
	{ "cvtqs/suim",	0x77c},
	{ "cvtqt/suim",	0x77e},
	{ "adds/sui",	0x780},
	{ "subs/sui",	0x781},
	{ "muls/sui",	0x782},
	{ "divs/sui",	0x783},
	{ "addt/sui",	0x7a0},
	{ "subt/sui",	0x7a1},
	{ "mult/sui",	0x7a2},
	{ "divt/sui",	0x7a3},
	{ "cvtts/sui",	0x7ac},
	{ "cvttq/svi",	0x7af},
	{ "cvtqs/sui",	0x7bc},
	{ "cvtqt/sui",	0x7be},
	{ "adds/suid",	0x7c0},
	{ "subs/suid",	0x7c1},
	{ "muls/suid",	0x7c2},
	{ "divs/suid",	0x7c3},
	{ "addt/suid",	0x7e0},
	{ "subt/suid",	0x7e1},
	{ "mult/suid",	0x7e2},
	{ "divt/suid",	0x7e3},
	{ "cvtts/suid",	0x7ec},
	{ "cvtqs/suid",	0x7fc},
	{ "cvtqt/suid",	0x7fe},
	{ 0, 0}
};

static const struct tbl vaxf_tbl[] = {
	{ "addf/c",	0x000},
	{ "subf/c",	0x001},
	{ "mulf/c",	0x002},
	{ "divf/c",	0x003},
	{ "cvtdg/c",	0x01e},
	{ "addg/c",	0x020},
	{ "subg/c",	0x021},
	{ "mulg/c",	0x022},
	{ "divg/c",	0x023},
	{ "cvtgf/c",	0x02c},
	{ "cvtgd/c",	0x02d},
	{ "cvtgq/c",	0x02f},
	{ "cvtqf/c",	0x03c},
	{ "cvtqg/c",	0x03e},
	{ "addf",	0x080},
	{ "subf",	0x081},
	{ "mulf",	0x082},
	{ "divf",	0x083},
	{ "cvtdg",	0x09e},
	{ "addg",	0x0a0},
	{ "subg",	0x0a1},
	{ "mulg",	0x0a2},
	{ "divg",	0x0a3},
	{ "cmpgeq",	0x0a5},
	{ "cmpglt",	0x0a6},
	{ "cmpgle",	0x0a7},
	{ "cvtgf",	0x0ac},
	{ "cvtgd",	0x0ad},
	{ "cvtgq",	0x0af},
	{ "cvtqf",	0x0bc},
	{ "cvtqg",	0x0be},
	{ "addf/uc",	0x100},
	{ "subf/uc",	0x101},
	{ "mulf/uc",	0x102},
	{ "divf/uc",	0x103},
	{ "cvtdg/uc",	0x11e},
	{ "addg/uc",	0x120},
	{ "subg/uc",	0x121},
	{ "mulg/uc",	0x122},
	{ "divg/uc",	0x123},
	{ "cvtgf/uc",	0x12c},
	{ "cvtgd/uc",	0x12d},
	{ "cvtgq/vc",	0x12f},
	{ "addf/u",	0x180},
	{ "subf/u",	0x181},
	{ "mulf/u",	0x182},
	{ "divf/u",	0x183},
	{ "cvtdg/u",	0x19e},
	{ "addg/u",	0x1a0},
	{ "subg/u",	0x1a1},
	{ "mulg/u",	0x1a2},
	{ "divg/u",	0x1a3},
	{ "cvtgf/u",	0x1ac},
	{ "cvtgd/u",	0x1ad},
	{ "cvtgq/v",	0x1af},
	{ "addf/sc",	0x400},
	{ "subf/sc",	0x401},
	{ "mulf/sc",	0x402},
	{ "divf/sc",	0x403},
	{ "cvtdg/sc",	0x41e},
	{ "addg/sc",	0x420},
	{ "subg/sc",	0x421},
	{ "mulg/sc",	0x422},
	{ "divg/sc",	0x423},
	{ "cvtgf/sc",	0x42c},
	{ "cvtgd/sc",	0x42d},
	{ "cvtgq/sc",	0x42f},
	{ "cvtqf/sc",	0x43c},
	{ "cvtqg/sc",	0x43e},
	{ "addf/s",	0x480},
	{ "subf/s",	0x481},
	{ "mulf/s",	0x482},
	{ "divf/s",	0x483},
	{ "cvtdg/s",	0x49e},
	{ "addg/s",	0x4a0},
	{ "subg/s",	0x4a1},
	{ "mulg/s",	0x4a2},
	{ "divg/s",	0x4a3},
	{ "cmpgeq/s",	0x4a5},
	{ "cmpglt/s",	0x4a6},
	{ "cmpgle/s",	0x4a7},
	{ "cvtgf/s",	0x4ac},
	{ "cvtgd/s",	0x4ad},
	{ "cvtgq/s",	0x4af},
	{ "cvtqf/s",	0x4bc},
	{ "cvtqg/s",	0x4be},
	{ "addf/suc",	0x500},
	{ "subf/suc",	0x501},
	{ "mulf/suc",	0x502},
	{ "divf/suc",	0x503},
	{ "cvtdg/suc",	0x51e},
	{ "addg/suc",	0x520},
	{ "subg/suc",	0x521},
	{ "mulg/suc",	0x522},
	{ "divg/suc",	0x523},
	{ "cvtgf/suc",	0x52c},
	{ "cvtgd/suc",	0x52d},
	{ "cvtgq/svc",	0x52f},
	{ "addf/su",	0x580},
	{ "subf/su",	0x581},
	{ "mulf/su",	0x582},
	{ "divf/su",	0x583},
	{ "cvtdg/su",	0x59e},
	{ "addg/su",	0x5a0},
	{ "subg/su",	0x5a1},
	{ "mulg/su",	0x5a2},
	{ "divg/su",	0x5a3},
	{ "cvtgf/su",	0x5ac},
	{ "cvtgd/su",	0x5ad},
	{ "cvtgq/sv",	0x5af},
	{ 0, 0}
};

/*
 * General purpose registers
 */
static const char * const name_of_register[32] = {
	"v0",	"t0",	"t1",	"t2",	"t3",	"t4",	"t5",	"t6",
	"t7",	"s0",	"s1",	"s2",	"s3",	"s4",	"s5",	"s6",
	"a0",	"a1",	"a2",	"a3",	"a4",	"a5",	"t8",	"t9",
	"t10",	"t11",	"ra",	"pv",	"at",	"gp",	"sp",	"zero"
};

static const char *
register_name(struct alpha_print_instruction_context *ctx, int ireg)
{
	return (name_of_register[ireg]);
}

static void
insn_printf(struct alpha_print_instruction_context *ctx,
    const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);

	if (ctx->buf != NULL) {
		if (ctx->cursor < ctx->bufsize) {
			ctx->cursor += vsnprintf(ctx->buf + ctx->cursor,
			    ctx->bufsize - ctx->cursor, fmt, ap);
		}
	} else {
		db_vprintf(fmt, ap);
	}

	va_end(ap);
}

/*
 * Disassemble instruction at 'loc'.  'altfmt' specifies an
 * (optional) alternate format.  Return address of start of
 * next instruction.
 */

#ifndef _KERNEL
static
#endif
int
alpha_print_instruction(struct alpha_print_instruction_context *ctx)
{
	const char	*opcode;
	long		signed_immediate;
	bool		fstore;
	pal_instruction	p;

	fstore = false;
	opcode = op_name[ctx->insn.mem_format.opcode];

	/*
	 *	Dispatch directly on the opcode, save code
	 *	duplication sometimes via "harmless gotos".
	 */
	switch (ctx->insn.mem_format.opcode) {
	case op_pal:
		/* "call_pal" is a long string; just use a space. */
		insn_printf(ctx, "%s %s", opcode,
		    pal_opname(ctx->insn.pal_format.function));
		break;
	case op_lda:
	case op_ldah:
	case op_ldbu:
	case op_ldq_u:
	case op_ldwu:
	case op_stw:
	case op_stb:
	case op_stq_u:
		/*
		 * These loadstores are here to make compiling the
		 * switch a bit easier.  Could embellish the output
		 * someday, too.
		 */
		goto loadstore;
		break;
	case op_arit:
		/*
		 * For this and the following three groups we
		 * just need different opcode strings
		 */
		opcode = arit_name(ctx->insn.operate_lit_format.function);
		goto operate;
		break;
	case op_logical:
		opcode = logical_name(ctx->insn.operate_lit_format.function);
		goto operate;
		break;
	case op_bit:
		opcode = bitop_name(ctx->insn.operate_lit_format.function);
		goto operate;
		break;
	case op_mul:
		opcode = mul_name(ctx->insn.operate_lit_format.function);
operate:
		/*
		 * Nice and uniform, just check for literals
		 */
		insn_printf(ctx, "%s\t%s,", opcode,
		    register_name(ctx, ctx->insn.operate_lit_format.ra));
		if (ctx->insn.operate_lit_format.one) {
			insn_printf(ctx, "#0x%x",
			    ctx->insn.operate_lit_format.literal);
		} else {
			insn_printf(ctx, "%s",
			    register_name(ctx,
					  ctx->insn.operate_reg_format.rb));
		}
		insn_printf(ctx, ",%s",
		    register_name(ctx, ctx->insn.operate_lit_format.rc));
		break;
	case op_vax_float:
		/*
		 * The three floating point groups are even simpler
		 */
		opcode = vaxf_name(ctx->insn.float_format.function);
		goto foperate;
		break;
	case op_ieee_float:
		opcode = ieeef_name(ctx->insn.float_format.function);
		goto foperate;
		break;
	case op_any_float:
		opcode = anyf_name(ctx->insn.float_format.function);
foperate:
		insn_printf(ctx, "%s\tf%d,f%d,f%d", opcode,
			ctx->insn.float_format.fa,
			ctx->insn.float_format.fb,
			ctx->insn.float_format.fc);
		break;
	case op_special:
		/*
		 * Miscellaneous.
		 */
		{
			register unsigned int code;

			code = (ctx->insn.mem_format.displacement)&0xffff;
			opcode = special_name(code);

			switch (code) {
			case op_ecb:
				insn_printf(ctx, "%s\t(%s)", opcode,
				    register_name(ctx,
						  ctx->insn.mem_format.rb));
				break;
			case op_fetch:
			case op_fetch_m:
				insn_printf(ctx, "%s\t0(%s)", opcode,
				    register_name(ctx,
						  ctx->insn.mem_format.rb));
				break;
			case op_rpcc:
			case op_rc:
			case op_rs:
				insn_printf(ctx, "%s\t%s", opcode,
				    register_name(ctx,
						  ctx->insn.mem_format.ra));
				break;
			default:
				insn_printf(ctx, "%s", opcode);
			break;
			}
		}
		break;
	case op_j:
		/*
		 * Jump instructions really are of two sorts,
		 * depending on the use of the hint info.
		 */
		opcode = jump_name(ctx->insn.jump_format.action);
		switch (ctx->insn.jump_format.action) {
		case op_jmp:
		case op_jsr:
			insn_printf(ctx, "%s\t%s,(%s),", opcode,
			    register_name(ctx, ctx->insn.jump_format.ra),
			    register_name(ctx, ctx->insn.jump_format.rb));
			signed_immediate = ctx->insn.jump_format.hint;
			goto branch_displacement;
			break;
		case op_ret:
		case op_jcr:
			insn_printf(ctx, "%s\t%s,(%s)", opcode,
			    register_name(ctx, ctx->insn.jump_format.ra),
			    register_name(ctx, ctx->insn.jump_format.rb));
			break;
		}
		break;
	case op_intmisc:
		/*
		 * These are just in "operate" format.
		 */
		opcode = intmisc_name(ctx->insn.operate_lit_format.function);
		goto operate;
		break;
			/* HW instructions, possibly chip-specific XXXX */
	case op_pal19:	/* "hw_mfpr" */
	case op_pal1d:	/* "hw_mtpr" */
		p.bits = ctx->insn.bits;
		insn_printf(ctx, "\t%s%s\t%s, %d", opcode,
			mXpr_name[p.mXpr_format.regset],
			register_name(ctx, p.mXpr_format.rd),
			p.mXpr_format.index);
		break;
	case op_pal1b:	/* "hw_ld" */
	case op_pal1f:	/* "hw_st" */
		p.bits = ctx->insn.bits;
		insn_printf(ctx, "\t%s%c%s\t%s,", opcode,
			(p.mem_format.qw) ? 'q' : 'l',
			hwlds_name[p.mem_format.qualif],
			register_name(ctx, p.mem_format.rd));
		signed_immediate = (long)p.mem_format.displacement;
		goto loadstore_address;

	case op_pal1e:	/* "hw_rei" */
		insn_printf(ctx, "\t%s", opcode);
		break;

	case op_ldf:
	case op_ldg:
	case op_lds:
	case op_ldt:
	case op_stf:
	case op_stg:
	case op_sts:
	case op_stt:
		fstore = true;
		/* fall through */
	case op_ldl:
	case op_ldq:
	case op_ldl_l:
	case op_ldq_l:
	case op_stl:
	case op_stq:
	case op_stl_c:
	case op_stq_c:
		/*
		 * Memory operations, including floats
		 */
loadstore:
		if (fstore) {
			insn_printf(ctx, "%s\tf%d,", opcode,
			    ctx->insn.mem_format.ra);
		} else {
			insn_printf(ctx, "%s\t%s,", opcode,
			    register_name(ctx, ctx->insn.mem_format.ra));
		}
		signed_immediate = (long)ctx->insn.mem_format.displacement;
loadstore_address:
		{
			char tbuf[24];

			db_format_hex(tbuf, 24, signed_immediate, false);
			insn_printf(ctx, "%s(%s)", tbuf,
			    register_name(ctx, ctx->insn.mem_format.rb));
		}
		break;
	case op_br:
	case op_fbeq:
	case op_fblt:
	case op_fble:
	case op_bsr:
	case op_fbne:
	case op_fbge:
	case op_fbgt:
	case op_blbc:
	case op_beq:
	case op_blt:
	case op_ble:
	case op_blbs:
	case op_bne:
	case op_bge:
	case op_bgt:
		/*
		 * We want to know where we are branching to
		 */
		signed_immediate = (long)ctx->insn.branch_format.displacement;
		insn_printf(ctx, "%s\t%s,", opcode,
		    register_name(ctx, ctx->insn.branch_format.ra));
branch_displacement:
		if (ctx->buf == NULL) {
			db_printsym(ctx->pc + sizeof(alpha_instruction) +
			    (signed_immediate << 2), DB_STGY_PROC, db_printf);
		}
		break;
	default:
		/*
		 * Shouldn't happen
		 */
		insn_printf(ctx, "? 0x%x ?", ctx->insn.bits);
	}

	/* If printing into a buffer, skip the newline. */
	if (ctx->buf == NULL) {
		insn_printf(ctx, "\n");
	}

	return (sizeof(alpha_instruction));
}

db_addr_t
db_disasm(db_addr_t loc, bool altfmt __unused)
{
	struct alpha_print_instruction_context ctx = {
		.insn.bits = db_get_value(loc, 4, 0),
		.pc = loc,
	};

	loc += alpha_print_instruction(&ctx);
	return loc;
}
