#include <u.h>
#include <libc.h>
#include <bio.h>
#include <mach.h>

static int debug = 0;

#define	BITS(a, b)	((1<<(b+1))-(1<<a))

#define LSR(v, s)	((ulong)(v) >> (s))
#define ASR(v, s)	((long)(v) >> (s))
#define ROR(v, s)	(LSR((v), (s)) | (((v) & ((1 << (s))-1)) << (32 - (s))))



typedef struct	Instr	Instr;
struct	Instr
{
	Map	*map;
	ulong	w;
	uvlong	addr;
	uchar	op;			/* super opcode */

	uchar	cond;			/* bits 28-31 */
	uchar	store;			/* bit 20 */

	uchar	rd;			/* bits 12-15 */
	uchar	rn;			/* bits 16-19 */
	uchar	rs;			/* bits 0-11 (shifter operand) */

	long	imm;			/* rotated imm */
	char*	curr;			/* fill point in buffer */
	char*	end;			/* end of buffer */
	char*	err;			/* error message */
};

typedef struct Opcode Opcode;
struct Opcode
{
	char*	o;
	void	(*fmt)(Opcode*, Instr*);
	uvlong	(*foll)(Map*, Rgetter, Instr*, uvlong);
	char*	a;
};

static	void	format(char*, Instr*, char*);
static	char	FRAMENAME[] = ".frame";

/*
 * Arm-specific debugger interface
 */

static	char	*armexcep(Map*, Rgetter);
static	int	armfoll(Map*, uvlong, Rgetter, uvlong*);
static	int	arminst(Map*, uvlong, char, char*, int);
static	int	armdas(Map*, uvlong, char*, int);
static	int	arminstlen(Map*, uvlong);

/*
 *	Debugger interface
 */
Machdata armmach =
{
	{0x70, 0x00, 0x20, 0xE1},		/* break point */	/* E1200070 */
	4,			/* break point size */

	leswab,			/* short to local byte order */
	leswal,			/* long to local byte order */
	leswav,			/* long to local byte order */
	risctrace,		/* C traceback */
	riscframe,		/* Frame finder */
	armexcep,			/* print exception */
	0,			/* breakpoint fixup */
	0,			/* single precision float printer */
	0,			/* double precision float printer */
	armfoll,		/* following addresses */
	arminst,		/* print instruction */
	armdas,			/* dissembler */
	arminstlen,		/* instruction size */
};

static char*
armexcep(Map *map, Rgetter rget)
{
	uvlong c;

	c = (*rget)(map, "TYPE");
	switch ((int)c&0x1f) {
	case 0x11:
		return "Fiq interrupt";
	case 0x12:
		return "Mirq interrupt";
	case 0x13:
		return "SVC/SWI Exception";
	case 0x17:
		return "Prefetch Abort/Breakpoint";
	case 0x18:
		return "Data Abort";
	case 0x1b:
		return "Undefined instruction/Breakpoint";
	case 0x1f:
		return "Sys trap";
	default:
		return "Undefined trap";
	}
}

static
char*	cond[16] =
{
	"EQ",	"NE",	"CS",	"CC",
	"MI",	"PL",	"VS",	"VC",
	"HI",	"LS",	"GE",	"LT",
	"GT",	"LE",	0,	"NV"
};

static
char*	shtype[4] =
{
	"<<",	">>",	"->",	"@>"
};

static
char *hb[4] =
{
	"???",	"HU", "B", "H"
};

static
char*	addsub[2] =
{
	"-",	"+",
};

int
armclass(long w)
{
	int op, done, cp;

	op = (w >> 25) & 0x7;
	switch(op) {
	case 0:	/* data processing r,r,r */
		if((w & 0x0ff00080) == 0x01200000) {
			op = (w >> 4) & 0x7;
			if(op == 7)
				op = 124;	/* bkpt */
			else if (op > 0 && op < 4)
				op += 124;	/* bx, blx */
			else
				op = 92;	/* unk */
			break;
		}
		op = ((w >> 4) & 0xf);
		if(op == 0x9) {
			op = 48+16;		/* mul, swp or *rex */
			if((w & 0x0ff00fff) == 0x01900f9f) {
				op = 93;	/* ldrex */
				break;
			}
			if((w & 0x0ff00ff0) == 0x01800f90) {
				op = 94;	/* strex */
				break;
			}
			if(w & (1<<24)) {
				op += 2;
				if(w & (1<<22))
					op++;	/* swpb */
				break;
			}
			if(w & (1<<23)) {	/* mullu */
				op = (48+24+4+4+2+2+4);
				if(w & (1<<22))	/* mull */
					op += 2;
			}
			if(w & (1<<21))
				op++;		/* mla */
			break;
		}
		if((op & 0x9) == 0x9)		/* ld/st byte/half s/u */
		{
			op = (48+16+4) + ((w >> 22) & 0x1) + ((w >> 19) & 0x2);
			break;
		}
		op = (w >> 21) & 0xf;
		if(w & (1<<4))
			op += 32;
		else
		if(w & (31<<7 | 3<<5))
			op += 16;
		break;
	case 1:	/* data processing i,r,r */
		op = (48) + ((w >> 21) & 0xf);
		break;
	case 2:	/* load/store byte/word i(r) */
		if ((w & 0xffffff80) == 0xf57ff000) {	/* barriers, clrex */
			done = 1;
			switch ((w >> 4) & 7) {
			case 1:
				op = 95;	/* clrex */
				break;
			case 4:
				op = 96;	/* dsb */
				break;
			case 5:
				op = 97;	/* dmb */
				break;
			case 6:
				op = 98;	/* isb */
				break;
			default:
				done = 0;
				break;
			}
			if (done)
				break;
		}
		op = (48+24) + ((w >> 22) & 0x1) + ((w >> 19) & 0x2);
		break;
	case 3:	/* load/store byte/word (r)(r) */
		op = (48+24+4) + ((w >> 22) & 0x1) + ((w >> 19) & 0x2);
		break;
	case 4:	/* block data transfer (r)(r) */
		if ((w & 0xfe50ffff) == 0xf8100a00) {	/* v7 RFE */
			op = 99;
			break;
		}
		op = (48+24+4+4) + ((w >> 20) & 0x1);
		break;
	case 5:	/* branch / branch link */
		op = (48+24+4+4+2) + ((w >> 24) & 0x1);
		break;
	case 7:	/* coprocessor crap */
		cp = (w >> 8) & 0xF;
		if(cp == 10 || cp == 11){	/* vfp */
			if((w >> 4) & 0x1){
				/* vfp register transfer */
				switch((w >> 21) & 0x7){
				case 0:
					op = 118 + ((w >> 20) & 0x1);
					break;
				case 7:
					op = 118+2 + ((w >> 20) & 0x1);
					break;
				default:
					op = (48+24+4+4+2+2+4+4);
					break;
				}
				break;
			}
			/* vfp data processing */
			if(((w >> 23) & 0x1) == 0){
				op = 100 + ((w >> 19) & 0x6) + ((w >> 6) & 0x1);
				break;
			}
			switch(((w >> 19) & 0x6) + ((w >> 6) & 0x1)){
			case 0:
				op = 108;
				break;
			case 7:
				if(((w >> 19) & 0x1) == 0){
					if(((w >> 17) & 0x1) == 0)
						op = 109 + ((w >> 16) & 0x4) +
							((w >> 15) & 0x2) +
							((w >> 7) & 0x1);
					else if(((w >> 16) & 0x7) == 0x7)
						op = 117;
				}
				else
					switch((w >> 16) & 0x7){
					case 0:
					case 4:
					case 5:
						op = 117;
						break;
					}
				break;
			}
			if(op == 7)
				op = (48+24+4+4+2+2+4+4);
			break;
		}
		op = (48+24+4+4+2+2) + ((w >> 3) & 0x2) + ((w >> 20) & 0x1);
		break;
	case 6:	/* vfp load / store */
		if(((w >> 21) &0x9) == 0x8){
			op = 122 + ((w >> 20) & 0x1);
			break;
		}
		/* fall through */
	default:	  
		op = (48+24+4+4+2+2+4+4);
		break;
	}
	return op;
}

static int
decode(Map *map, uvlong pc, Instr *i)
{
	ulong w;

	if(get4(map, pc, &w) < 0) {
		werrstr("can't read instruction: %r");
		return -1;
	}
	i->w = w;
	i->addr = pc;
	i->cond = (w >> 28) & 0xF;
	i->op = armclass(w);
	i->map = map;
	return 1;
}

#pragma	varargck	argpos	bprint		2

static void
bprint(Instr *i, char *fmt, ...)
{
	va_list arg;

	va_start(arg, fmt);
	i->curr = vseprint(i->curr, i->end, fmt, arg);
	va_end(arg);
}

static int
plocal(Instr *i)
{
	char *reg;
	Symbol s;
	char *fn;
	int class;
	int offset;

	if(!findsym(i->addr, CTEXT, &s)) {
		if(debug)fprint(2,"fn not found @%llux: %r\n", i->addr);
		return 0;
	}
	fn = s.name;
	if (!findlocal(&s, FRAMENAME, &s)) {
		if(debug)fprint(2,"%s.%s not found @%s: %r\n", fn, FRAMENAME, s.name);
			return 0;
	}
	if(s.value > i->imm) {
		class = CAUTO;
		offset = s.value-i->imm;
		reg = "(SP)";
	} else {
		class = CPARAM;
		offset = i->imm-s.value-4;
		reg = "(FP)";
	}
	if(!getauto(&s, offset, class, &s)) {
		if(debug)fprint(2,"%s %s not found @%ux: %r\n", fn,
			class == CAUTO ? " auto" : "param", offset);
		return 0;
	}
	bprint(i, "%s%c%lld%s", s.name, class == CPARAM ? '+' : '-', s.value, reg);
	return 1;
}

/*
 * Print value v as name[+offset]
 */
static int
gsymoff(char *buf, int n, ulong v, int space)
{
	Symbol s;
	int r;
	long delta;

	r = delta = 0;		/* to shut compiler up */
	if (v) {
		r = findsym(v, space, &s);
		if (r)
			delta = v-s.value;
		if (delta < 0)
			delta = -delta;
	}
	if (v == 0 || r == 0 || delta >= 4096)
		return snprint(buf, n, "#%lux", v);
	if (strcmp(s.name, ".string") == 0)
		return snprint(buf, n, "#%lux", v);
	if (!delta)
		return snprint(buf, n, "%s", s.name);
	if (s.type != 't' && s.type != 'T')
		return snprint(buf, n, "%s+%llux", s.name, v-s.value);
	else
		return snprint(buf, n, "#%lux", v);
}

static void
armdps(Opcode *o, Instr *i)
{
	i->store = (i->w >> 20) & 1;
	i->rn = (i->w >> 16) & 0xf;
	i->rd = (i->w >> 12) & 0xf;
	i->rs = (i->w >> 0) & 0xf;
	if(i->rn == 15 && i->rs == 0) {
		if(i->op == 8) {
			format("MOVW", i,"CPSR, R%d");
			return;
		} else
		if(i->op == 10) {
			format("MOVW", i,"SPSR, R%d");
			return;
		}
	} else
	if(i->rn == 9 && i->rd == 15) {
		if(i->op == 9) {
			format("MOVW", i, "R%s, CPSR");
			return;
		} else
		if(i->op == 11) {
			format("MOVW", i, "R%s, SPSR");
			return;
		}
	}
	if(i->rd == 15) {
		if(i->op == 120) {
			format("MOVW", i, "PSR, %x");
			return;
		} else 
		if(i->op == 121) {
			format("MOVW", i, "%x, PSR");
			return;
		}
	}
	format(o->o, i, o->a);
}

static void
armdpi(Opcode *o, Instr *i)
{
	ulong v;
	int c;

	v = (i->w >> 0) & 0xff;
	c = (i->w >> 8) & 0xf;
	while(c) {
		v = (v<<30) | (v>>2);
		c--;
	}
	i->imm = v;
	i->store = (i->w >> 20) & 1;
	i->rn = (i->w >> 16) & 0xf;
	i->rd = (i->w >> 12) & 0xf;
	i->rs = i->w&0x0f;

		/* RET is encoded as ADD #0,R14,R15 */
	if((i->w & 0x0fffffff) == 0x028ef000){
		format("RET%C", i, "");
		return;
	}
	if((i->w & 0x0ff0ffff) == 0x0280f000){
		format("B%C", i, "0(R%n)");
		return;
	}
	format(o->o, i, o->a);
}

static void
armsdti(Opcode *o, Instr *i)
{
	ulong v;

	v = i->w & 0xfff;
	if(!(i->w & (1<<23)))
		v = -v;
	i->store = ((i->w >> 23) & 0x2) | ((i->w >>21) & 0x1);
	i->imm = v;
	i->rn = (i->w >> 16) & 0xf;
	i->rd = (i->w >> 12) & 0xf;
		/* RET is encoded as LW.P x,R13,R15 */
	if ((i->w & 0x0ffff000) == 0x049df000)
	{
		format("RET%C%p", i, "%I");
		return;
	}
	format(o->o, i, o->a);
}

static void
armvstdi(Opcode *o, Instr *i)
{
	ulong v;

	v = (i->w & 0xff) << 2;
	if(!(i->w & (1<<23)))
		v = -v;
	i->imm = v;
	i->rn = (i->w >> 16) & 0xf;
	i->rd = (i->w >> 12) & 0xf;
	format(o->o, i, o->a);
}

/* arm V4 ld/st halfword, signed byte */
static void
armhwby(Opcode *o, Instr *i)
{
	i->store = ((i->w >> 23) & 0x2) | ((i->w >>21) & 0x1);
	i->imm = (i->w & 0xf) | ((i->w >> 4) & 0xf0);
	if (!(i->w & (1 << 23)))
		i->imm = - i->imm;
	i->rn = (i->w >> 16) & 0xf;
	i->rd = (i->w >> 12) & 0xf;
	i->rs = (i->w >> 0) & 0xf;
	format(o->o, i, o->a);
}

static void
armsdts(Opcode *o, Instr *i)
{
	i->store = ((i->w >> 23) & 0x2) | ((i->w >>21) & 0x1);
	i->rs = (i->w >> 0) & 0xf;
	i->rn = (i->w >> 16) & 0xf;
	i->rd = (i->w >> 12) & 0xf;
	format(o->o, i, o->a);
}

static void
armbdt(Opcode *o, Instr *i)
{
	i->store = (i->w >> 21) & 0x3;		/* S & W bits */
	i->rn = (i->w >> 16) & 0xf;
	i->imm = i->w & 0xffff;
	if(i->w == 0xe8fd8000)
		format("RFE", i, "");
	else
		format(o->o, i, o->a);
}

static void
armund(Opcode *o, Instr *i)
{
	format(o->o, i, o->a);
}

static void
armcdt(Opcode *o, Instr *i)
{
	format(o->o, i, o->a);
}

static void
armunk(Opcode *o, Instr *i)
{
	format(o->o, i, o->a);
}

static void
armb(Opcode *o, Instr *i)
{
	ulong v;

	v = i->w & 0xffffff;
	if(v & 0x800000)
		v |= ~0xffffff;
	i->imm = (v<<2) + i->addr + 8;
	format(o->o, i, o->a);
}

static void
armbpt(Opcode *o, Instr *i)
{
	i->imm = ((i->w >> 4) & 0xfff0) | (i->w &0xf);
	format(o->o, i, o->a);
}

static void
armco(Opcode *o, Instr *i)		/* coprocessor instructions */
{
	int op, p, cp;

	char buf[1024];

	i->rn = (i->w >> 16) & 0xf;
	i->rd = (i->w >> 12) & 0xf;
	i->rs = i->w&0xf;
	cp = (i->w >> 8) & 0xf;
	p = (i->w >> 5) & 0x7;
	if(i->w&(1<<4)) {
		op = (i->w >> 21) & 0x07;
		snprint(buf, sizeof(buf), "#%x, #%x, R%d, C(%d), C(%d), #%x", cp, op, i->rd, i->rn, i->rs, p);
	} else {
		op = (i->w >> 20) & 0x0f;
		snprint(buf, sizeof(buf), "#%x, #%x, C(%d), C(%d), C(%d), #%x", cp, op, i->rd, i->rn, i->rs, p);
	}
	format(o->o, i, buf);
}

static void
armdmb(Opcode *o, Instr *i)
{
	i->imm = (i->w & 0xf);
	format(o->o, i, o->a);
}

static int
armcondpass(Map *map, Rgetter rget, uchar cond)
{
	uvlong psr;
	uchar n;
	uchar z;
	uchar c;
	uchar v;

	psr = rget(map, "PSR");
	n = (psr >> 31) & 1;
	z = (psr >> 30) & 1;
	c = (psr >> 29) & 1;
	v = (psr >> 28) & 1;

	switch(cond) {
	default:
	case 0:		return z;
	case 1:		return !z;
	case 2:		return c;
	case 3:		return !c;
	case 4:		return n;
	case 5:		return !n;
	case 6:		return v;
	case 7:		return !v;
	case 8:		return c && !z;
	case 9:		return !c || z;
	case 10:	return n == v;
	case 11:	return n != v;
	case 12:	return !z && (n == v);
	case 13:	return z || (n != v);
	case 14:	return 1;
	case 15:	return 0;
	}
}

static ulong
armshiftval(Map *map, Rgetter rget, Instr *i)
{
	if(i->w & (1 << 25)) {				/* immediate */
		ulong imm = i->w & BITS(0, 7);
		ulong s = (i->w & BITS(8, 11)) >> 7; /* this contains the *2 */
		return ROR(imm, s);
	} else {
		char buf[8];
		ulong v;
		ulong s = (i->w & BITS(7,11)) >> 7;

		sprint(buf, "R%ld", i->w & 0xf);
		v = rget(map, buf);

		switch((i->w & BITS(4, 6)) >> 4) {
		default:
		case 0:					/* LSLIMM */
			return v << s;
		case 1:					/* LSLREG */
			sprint(buf, "R%lud", s >> 1);
			s = rget(map, buf) & 0xFF;
			if(s >= 32) return 0;
			return v << s;
		case 2:					/* LSRIMM */
			return LSR(v, s);
		case 3:					/* LSRREG */
			sprint(buf, "R%ld", s >> 1);
			s = rget(map, buf) & 0xFF;
			if(s >= 32) return 0;
			return LSR(v, s);
		case 4:					/* ASRIMM */
			if(s == 0) {
				if((v & (1U<<31)) == 0)
					return 0;
				return 0xFFFFFFFF;
			}
			return ASR(v, s);
		case 5:					/* ASRREG */
			sprint(buf, "R%ld", s >> 1);
			s = rget(map, buf) & 0xFF;
			if(s >= 32) {
				if((v & (1U<<31)) == 0)
					return 0;
				return 0xFFFFFFFF;
			}
			return ASR(v, s);
		case 6:					/* RORIMM */
			if(s == 0) {
				ulong c = (rget(map, "PSR") >> 29) & 1;

				return (c << 31) | LSR(v, 1);
			}
			return ROR(v, s);
		case 7:					/* RORREG */
			sprint(buf, "R%ld", s >> 1);
			s = rget(map, buf) & 0x1F;
			if(s == 0)
				return v;
			return ROR(v, s);
		}
	}
}

static int
nbits(ulong v)
{
	int n = 0;
	int i;

	for(i=0; i < 32 ; i++) {
		if(v & 1) ++n;
		v >>= 1;
	}

	return n;
}

static ulong
armmaddr(Map *map, Rgetter rget, Instr *i)
{
	ulong v;
	ulong nb;
	char buf[8];
	ulong rn;

	rn = (i->w >> 16) & 0xf;
	sprint(buf,"R%ld", rn);

	v = rget(map, buf);
	nb = nbits(i->w & ((1 << 15) - 1));

	switch((i->w >> 23) & 3) {
	default:
	case 0: return (v - (nb*4)) + 4;
	case 1: return v;
	case 2: return v - (nb*4);
	case 3: return v + 4;
	}
}

static uvlong
armaddr(Map *map, Rgetter rget, Instr *i)
{
	char buf[8];
	ulong rn;

	snprint(buf, sizeof(buf), "R%ld", (i->w >> 16) & 0xf);
	rn = rget(map, buf);

	if((i->w & (1<<24)) == 0)			/* POSTIDX */
		return rn;

	if((i->w & (1<<25)) == 0) {			/* OFFSET */
		if(i->w & (1U<<23))
			return rn + (i->w & BITS(0,11));
		return rn - (i->w & BITS(0,11));
	} else {					/* REGOFF */
		ulong index = 0;
		uchar c;
		uchar rm;

		sprint(buf, "R%ld", i->w & 0xf);
		rm = rget(map, buf);

		switch((i->w & BITS(5,6)) >> 5) {
		case 0: index = rm << ((i->w & BITS(7,11)) >> 7);	break;
		case 1: index = LSR(rm, ((i->w & BITS(7,11)) >> 7));	break;
		case 2: index = ASR(rm, ((i->w & BITS(7,11)) >> 7));	break;
		case 3:
			if((i->w & BITS(7,11)) == 0) {
				c = (rget(map, "PSR") >> 29) & 1;
				index = c << 31 | LSR(rm, 1);
			} else {
				index = ROR(rm, ((i->w & BITS(7,11)) >> 7));
			}
			break;
		}
		if(i->w & (1<<23))
			return rn + index;
		return rn - index;
	}
}

static uvlong
armfadd(Map *map, Rgetter rget, Instr *i, uvlong pc)
{
	char buf[8];
	int r;

	r = (i->w >> 12) & 0xf;
	if(r != 15 || !armcondpass(map, rget, (i->w >> 28) & 0xf))
		return pc+4;

	r = (i->w >> 16) & 0xf;
	sprint(buf, "R%d", r);

	return rget(map, buf) + armshiftval(map, rget, i);
}

static uvlong
armfbx(Map *map, Rgetter rget, Instr *i, uvlong pc)
{
	char buf[8];
	int r;

	if(!armcondpass(map, rget, (i->w>>28)&0xf))
		return pc+4;
	r = (i->w >> 0) & 0xf;
	sprint(buf, "R%d", r);
	return rget(map, buf);
}

static uvlong
armfmovm(Map *map, Rgetter rget, Instr *i, uvlong pc)
{
	ulong v;
	ulong addr;

	v = i->w & 1<<15;
	if(!v || !armcondpass(map, rget, (i->w>>28)&0xf))
		return pc+4;

	addr = armmaddr(map, rget, i) + nbits(i->w & BITS(0,15));
	if(get4(map, addr, &v) < 0) {
		werrstr("can't read addr: %r");
		return -1;
	}
	return v;
}

static uvlong
armfbranch(Map *map, Rgetter rget, Instr *i, uvlong pc)
{
	if(!armcondpass(map, rget, (i->w >> 28) & 0xf))
		return pc+4;

	return pc + (((signed long)i->w << 8) >> 6) + 8;
}

static uvlong
armfmov(Map *map, Rgetter rget, Instr *i, uvlong pc)
{
	ulong rd, v;

	rd = (i->w >> 12) & 0xf;
	if(rd != 15 || !armcondpass(map, rget, (i->w>>28)&0xf))
		return pc+4;

	 /* LDR */
	/* BUG: Needs LDH/B, too */
	if(((i->w>>26)&0x3) == 1) {
		if(get4(map, armaddr(map, rget, i), &v) < 0) {
			werrstr("can't read instruction: %r");
			return pc+4;
		}
		return v;
	}

	 /* MOV */
	v = armshiftval(map, rget, i);

	return v;
}

static Opcode opcodes[] =
{
	"AND%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"EOR%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"SUB%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"RSB%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"ADD%C%S",	armdps, armfadd,	"R%s,R%n,R%d",
	"ADC%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"SBC%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"RSC%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"TST%C%S",	armdps, 0,	"R%s,R%n",
	"TEQ%C%S",	armdps, 0,	"R%s,R%n",
	"CMP%C%S",	armdps, 0,	"R%s,R%n",
	"CMN%C%S",	armdps, 0,	"R%s,R%n",
	"ORR%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"MOVW%C%S",	armdps, armfmov,	"R%s,R%d",
	"BIC%C%S",	armdps, 0,	"R%s,R%n,R%d",
	"MVN%C%S",	armdps, 0,	"R%s,R%d",

/* 16 */
	"AND%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"EOR%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"SUB%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"RSB%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"ADD%C%S",	armdps, armfadd,	"(R%s%h%m),R%n,R%d",
	"ADC%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"SBC%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"RSC%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"TST%C%S",	armdps, 0,	"(R%s%h%m),R%n",
	"TEQ%C%S",	armdps, 0,	"(R%s%h%m),R%n",
	"CMP%C%S",	armdps, 0,	"(R%s%h%m),R%n",
	"CMN%C%S",	armdps, 0,	"(R%s%h%m),R%n",
	"ORR%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"MOVW%C%S",	armdps, armfmov,	"(R%s%h%m),R%d",
	"BIC%C%S",	armdps, 0,	"(R%s%h%m),R%n,R%d",
	"MVN%C%S",	armdps, 0,	"(R%s%h%m),R%d",

/* 32 */
	"AND%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"EOR%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"SUB%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"RSB%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"ADD%C%S",	armdps, armfadd,	"(R%s%hR%M),R%n,R%d",
	"ADC%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"SBC%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"RSC%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"TST%C%S",	armdps, 0,	"(R%s%hR%M),R%n",
	"TEQ%C%S",	armdps, 0,	"(R%s%hR%M),R%n",
	"CMP%C%S",	armdps, 0,	"(R%s%hR%M),R%n",
	"CMN%C%S",	armdps, 0,	"(R%s%hR%M),R%n",
	"ORR%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"MOVW%C%S",	armdps, armfmov,	"(R%s%hR%M),R%d",
	"BIC%C%S",	armdps, 0,	"(R%s%hR%M),R%n,R%d",
	"MVN%C%S",	armdps, 0,	"(R%s%hR%M),R%d",

/* 48 */
	"AND%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"EOR%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"SUB%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"RSB%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"ADD%C%S",	armdpi, armfadd,	"$#%i,R%n,R%d",
	"ADC%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"SBC%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"RSC%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"TST%C%S",	armdpi, 0,	"$#%i,R%n",
	"TEQ%C%S",	armdpi, 0,	"$#%i,R%n",
	"CMP%C%S",	armdpi, 0,	"$#%i,R%n",
	"CMN%C%S",	armdpi, 0,	"$#%i,R%n",
	"ORR%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"MOVW%C%S",	armdpi, armfmov,	"$#%i,R%d",
	"BIC%C%S",	armdpi, 0,	"$#%i,R%n,R%d",
	"MVN%C%S",	armdpi, 0,	"$#%i,R%d",

/* 48+16 */
	"MUL%C%S",	armdpi, 0,	"R%M,R%s,R%n",
	"MULA%C%S",	armdpi, 0,	"R%M,R%s,R%n,R%d",
	"SWPW",		armdpi, 0,	"R%s,(R%n),R%d",
	"SWPB",		armdpi, 0,	"R%s,(R%n),R%d",

/* 48+16+4 */
	"MOV%u%C%p",	armhwby, 0,	"R%d,(R%n%UR%s)",
	"MOV%u%C%p",	armhwby, 0,	"R%d,%I",
	"MOV%u%C%p",	armhwby, armfmov,	"(R%n%UR%s),R%d",
	"MOV%u%C%p",	armhwby, armfmov,	"%I,R%d",

/* 48+24 */
	"MOVW%C%p",	armsdti, 0,	"R%d,%I",
	"MOVB%C%p",	armsdti, 0,	"R%d,%I",
	"MOVW%C%p",	armsdti, armfmov,	"%I,R%d",
	"MOVBU%C%p",	armsdti, armfmov,	"%I,R%d",

	"MOVW%C%p",	armsdts, 0,	"R%d,(R%s%h%m)(R%n)",
	"MOVB%C%p",	armsdts, 0,	"R%d,(R%s%h%m)(R%n)",
	"MOVW%C%p",	armsdts, armfmov,	"(R%s%h%m)(R%n),R%d",
	"MOVBU%C%p",	armsdts, armfmov,	"(R%s%h%m)(R%n),R%d",

	"MOVM%C%P%a",	armbdt, armfmovm,		"[%r],(R%n)",
	"MOVM%C%P%a",	armbdt, armfmovm,		"(R%n),[%r]",

	"B%C",		armb, armfbranch,		"%b",
	"BL%C",		armb, armfbranch,		"%b",

	"CDP%C",	armco, 0,		"",
	"CDP%C",	armco, 0,		"",
	"MCR%C",	armco, 0,		"",
	"MRC%C",	armco, 0,		"",

/* 48+24+4+4+2+2+4 */
	"MULLU%C%S",	armdpi, 0,	"R%M,R%s,(R%n,R%d)",
	"MULALU%C%S",	armdpi, 0,	"R%M,R%s,(R%n,R%d)",
	"MULL%C%S",	armdpi, 0,	"R%M,R%s,(R%n,R%d)",
	"MULAL%C%S",	armdpi, 0,	"R%M,R%s,(R%n,R%d)",

/* 48+24+4+4+2+2+4+4 = 92 */
	"UNK",		armunk, 0,	"",

	/* new v7 arch instructions */
/* 93 */
	"LDREX",	armdpi, 0,	"(R%n),R%d",
	"STREX",	armdpi, 0,	"R%s,(R%n),R%d",
	"CLREX",	armunk, 0,	"",

/* 96 */
	"DSB",		armdmb, 0,	"$%i",
	"DMB",		armdmb, 0,	"$%i",
	"ISB",		armdmb, 0,	"$%i",

/* 99 */
	"RFEV7%P%a",	armbdt, 0,	"(R%n)",

/* 100 */
	"MLA%f%C",	armdps,	0,	"F%s,F%n,F%d",
	"MLS%f%C",	armdps,	0,	"F%s,F%n,F%d",
	"NMLS%f%C",	armdps,	0,	"F%s,F%n,F%d",
	"NMLA%f%C",	armdps,	0,	"F%s,F%n,F%d",
	"MUL%f%C",	armdps,	0,	"F%s,F%n,F%d",
	"NMUL%f%C",	armdps,	0,	"F%s,F%n,F%d",
	"ADD%f%C",	armdps,	0,	"F%s,F%n,F%d",
	"SUB%f%C",	armdps,	0,	"F%s,F%n,F%d",
	"DIV%f%C",	armdps,	0,	"F%s,F%n,F%d",

/* 109 */
	"MOV%f%C",	armdps,	0,	"F%s,F%d",
	"ABS%f%C",	armdps,	0,	"F%s,F%d",
	"NEG%f%C",	armdps,	0,	"F%s,F%d",
	"SQRT%f%C",	armdps,	0,	"F%s,F%d",
	"CMP%f%C",	armdps,	0,	"F%s,F%d",
	"CMPE%f%C",	armdps,	0,	"F%s,F%d",
	"CMP%f%C",	armdps,	0,	"$0.0,F%d",
	"CMPE%f%C",	armdps,	0,	"$0.0,F%d",

/* 117 */
	"MOV%F%R%C",	armdps, 0,	"F%s,F%d",

/* 118 */
	"MOVW%C",	armdps, 0,	"R%d,F%n",
	"MOVW%C",	armdps, 0,	"F%n,R%d",
	"MOVW%C",	armdps, 0,	"R%d,%x",
	"MOVW%C",	armdps, 0,	"%x,R%d",

/* 122 */
	"MOV%f%C",	armvstdi,	0,	"F%d,%I",
	"MOV%f%C",	armvstdi,	0,	"%I,F%d",

/* 124 */
	"BKPT%C",	armbpt,		0,	"$#%i",
	"BX%C",		armdps,		armfbx,	"(R%s)",
	"BXJ%C",	armdps,		armfbx,	"(R%s)",
	"BLX%C",	armdps,		armfbx,	"(R%s)",
};

static void
gaddr(Instr *i)
{
	*i->curr++ = '$';
	i->curr += gsymoff(i->curr, i->end-i->curr, i->imm, CANY);
}

static	char *mode[] = { 0, "IA", "DB", "IB" };
static	char *pw[] = { "P", "PW", 0, "W" };
static	char *sw[] = { 0, "W", "S", "SW" };

static void
format(char *mnemonic, Instr *i, char *f)
{
	int j, k, m, n;
	int g;
	char *fmt;

	if(mnemonic)
		format(0, i, mnemonic);
	if(f == 0)
		return;
	if(mnemonic)
		if(i->curr < i->end)
			*i->curr++ = '\t';
	for ( ; *f && i->curr < i->end; f++) {
		if(*f != '%') {
			*i->curr++ = *f;
			continue;
		}
		switch (*++f) {

		case 'C':	/* .CONDITION */
			if(cond[i->cond])
				bprint(i, ".%s", cond[i->cond]);
			break;

		case 'S':	/* .STORE */
			if(i->store)
				bprint(i, ".S");
			break;

		case 'P':	/* P & U bits for block move */
			n = (i->w >>23) & 0x3;
			if (mode[n])
				bprint(i, ".%s", mode[n]);
			break;

		case 'p':	/* P & W bits for single data xfer*/
			if (pw[i->store])
				bprint(i, ".%s", pw[i->store]);
			break;

		case 'a':	/* S & W bits for single data xfer*/
			if (sw[i->store])
				bprint(i, ".%s", sw[i->store]);
			break;

		case 's':
			bprint(i, "%d", i->rs & 0xf);
			break;
				
		case 'M':
			bprint(i, "%lud", (i->w>>8) & 0xf);
			break;
				
		case 'm':
			n = (i->w>>7) & 0x1f;
			if (n == 0 && (i->w & (3<<5)) != 0)
				n = 32;
			bprint(i, "%d", n);
			break;

		case 'h':
			bprint(i, shtype[(i->w>>5) & 0x3]);
			break;

		case 'u':		/* Signed/unsigned Byte/Halfword */
			bprint(i, hb[(i->w>>5) & 0x3]);
			break;

		case 'I':
			if (i->rn == 13) {
				if (plocal(i))
					break;
			}
			g = 0;
			fmt = "#%lx(R%d)";
			if (i->rn == 15) {
				/* convert load of offset(PC) to a load immediate */
				if (get4(i->map, i->addr+i->imm+8, (ulong*)&i->imm) > 0)
				{
					g = 1;
					fmt = "";
				}
			}
			if (mach->sb)
			{
				if (i->rd == 11) {
					ulong nxti;

					if (get4(i->map, i->addr+4, &nxti) > 0) {
						if ((nxti & 0x0e0f0fff) == 0x060c000b) {
							i->imm += mach->sb;
							g = 1;
							fmt = "-SB";
						}
					}
				}
				if (i->rn == 12)
				{
					i->imm += mach->sb;
					g = 1;
					fmt = "-SB(SB)";
				}
			}
			if (g)
			{
				gaddr(i);
				bprint(i, fmt, i->rn);
			}
			else
				bprint(i, fmt, i->imm, i->rn);
			break;
		case 'U':		/* Add/subtract from base */
			bprint(i, addsub[(i->w >> 23) & 1]);
			break;

		case 'n':
			bprint(i, "%d", i->rn);
			break;

		case 'd':
			bprint(i, "%d", i->rd);
			break;

		case 'i':
			bprint(i, "%lux", i->imm);
			break;

		case 'b':
			i->curr += symoff(i->curr, i->end-i->curr,
				(ulong)i->imm, CTEXT);
			break;

		case 'g':
			i->curr += gsymoff(i->curr, i->end-i->curr,
				i->imm, CANY);
			break;

		case 'f':
			switch((i->w >> 8) & 0xF){
			case 10:
				bprint(i, "F");
				break;
			case 11:
				bprint(i, "D");
				break;
			}
			break;

		case 'F':
			switch(((i->w >> 15) & 0xE) + ((i->w >> 8) & 0x1)){
			case 0x0:
				bprint(i, ((i->w >> 7) & 0x1)? "WF" : "WF.U");
				break;
			case 0x1:
				bprint(i, ((i->w >> 7) & 0x1)? "WD" : "WD.U");
				break;
			case 0x8:
				bprint(i, "FW.U");
				break;
			case 0x9:
				bprint(i, "DW.U");
				break;
			case 0xA:
				bprint(i, "FW");
				break;
			case 0xB:
				bprint(i, "DW");
				break;
			case 0xE:
				bprint(i, "FD");
				break;
			case 0xF:
				bprint(i, "DF");
				break;
			}
			break;

		case 'R':
			if(((i->w >> 7) & 0x1) == 0)
				bprint(i, "R");
			break;

		case 'x':
			switch(i->rn){
			case 0:
				bprint(i, "FPSID");
				break;
			case 1:
				bprint(i, "FPSCR");
				break;
			case 2:
				bprint(i, "FPEXC");
				break;
			default:
				bprint(i, "FPS(%d)", i->rn);
				break;
			}
			break;

		case 'r':
			n = i->imm&0xffff;
			j = 0;
			k = 0;
			while(n) {
				m = j;
				while(n&0x1) {
					j++;
					n >>= 1;
				}
				if(j != m) {
					if(k)
						bprint(i, ",");
					if(j == m+1)
						bprint(i, "R%d", m);
					else
						bprint(i, "R%d-R%d", m, j-1);
					k = 1;
				}
				j++;
				n >>= 1;
			}
			break;

		case '\0':
			*i->curr++ = '%';
			return;

		default:
			bprint(i, "%%%c", *f);
			break;
		}
	}
	*i->curr = 0;
}

extern Machdata thumbmach;

static int
printins(Map *map, uvlong pc, char *buf, int n)
{
	Instr i;

	i.curr = buf;
	i.end = buf+n-1;
	if(decode(map, pc, &i) < 0)
		return -1;

	(*opcodes[i.op].fmt)(&opcodes[i.op], &i);
	return 4;
}

static int
arminst(Map *map, uvlong pc, char modifier, char *buf, int n)
{
	if(thumbpclookup(pc))
		return thumbmach.das(map, pc, modifier, buf, n);
	return printins(map, pc, buf, n);
}

static int
armdas(Map *map, uvlong pc, char *buf, int n)
{
	Instr i;

	if(thumbpclookup(pc))
		return thumbmach.hexinst(map, pc, buf, n);

	i.curr = buf;
	i.end = buf+n;
	if(decode(map, pc, &i) < 0)
		return -1;
	if(i.end-i.curr > 8)
		i.curr = _hexify(buf, i.w, 7);
	*i.curr = 0;
	return 4;
}

static int
arminstlen(Map *map, uvlong pc)
{
	Instr i;

	if(thumbpclookup(pc))
		return thumbmach.instsize(map, pc);

	if(decode(map, pc, &i) < 0)
		return -1;
	return 4;
}

static int
armfoll(Map *map, uvlong pc, Rgetter rget, uvlong *foll)
{
	uvlong d;
	Instr i;

	if(decode(map, pc, &i) < 0)
		return -1;

	if(opcodes[i.op].foll) {
		d = (*opcodes[i.op].foll)(map, rget, &i, pc);
		if(d == -1)
			return -1;
	} else
		d = pc+4;

	foll[0] = d;
	return 1;
}
