// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "runtime.h"

void	abort(void);

static void
fabort(void)
{
	if (1) {
		printf("Unsupported floating point instruction\n");
		abort();
	}
}

static uint32 doabort = 0;
static uint32 trace = 0;

#define DOUBLE_EXPBIAS 1023
#define DOUBLE_MANT_MASK 0xfffffffffffffll
#define DOUBLE_MANT_TOP_BIT 0x10000000000000ll
#define DZERO 0x0000000000000000ll
#define DNZERO 0x8000000000000000ll
#define DONE 0x3ff0000000000000ll
#define DINF 0x7ff0000000000000ll
#define DNINF 0xfff0000000000000ll

#define SINGLE_EXPBIAS 127


static const int8* opnames[] = {
	// binary
	"adf",
	"muf",
	"suf",
	"rsf",
	"dvf",
	"rdf",
	"pow",
	"rpw",
	"rmf",
	"fml",
	"fdv",
	"frd",
	"pol",
	"UNDEFINED",
	"UNDEFINED",
	"UNDEFINED",

	// unary
	"mvf",
	"mnf",
	"abs",
	"rnd",
	"sqt",
	"log",
	"lgn",
	"exp",
	"sin",
	"cos",
	"tan",
	"asn",
	"acs",
	"atn",
	"urd",
	"nrm"
};

static const int8* fpconst[] = {
	"0.0", "1.0", "2.0", "3.0", "4.0", "5.0", "0.5", "10.0",
};

static const uint64 fpdconst[] = {
	0x0000000000000000ll,
	0x3ff0000000000000ll,
	0x4000000000000000ll,
	0x4008000000000000ll,
	0x4010000000000000ll,
	0x4014000000000000ll,
	0x3fe0000000000000ll,
	0x4024000000000000ll
};

static const int8* fpprec[] = {
	"s", "d", "e", "?"
};

static uint32
precision(uint32 i)
{
	switch (i&0x00080080) {
	case 0:
		return 0;
	case 0x80:
		return 1;
	default:
		fabort();
	}
	return 0;
}

static uint64
frhs(uint32 rhs)
{
	if (rhs & 0x8) {
		return  fpdconst[rhs&0x7];
	} else {
		return m->freg[rhs&0x7];
	}
}

static int32
fexp(uint64 f)
{
	return (int32)((uint32)(f >> 52) & 0x7ff) - DOUBLE_EXPBIAS;
}

static uint32
fsign(uint64 f)
{
	return (uint32)(f >> 63) & 0x1;
}

static uint64
fmantissa(uint64 f)
{
	return f &0x000fffffffffffffll;
}

static void
fprint(void)
{
	uint32 i;
	for (i = 0; i < 8; i++) {
		printf("\tf%d:\t%X\n", i, m->freg[i]);
	}
}

static uint32
d2s(uint64 d)
{
	return (d>>32 & 0x80000000) |	//sign
		((uint32)(fexp(d) + SINGLE_EXPBIAS) & 0xff) << 23 |	// exponent
		(d >> 29 & 0x7fffff);	// mantissa
}

static uint64
s2d(uint32 s)
{
	return (uint64)(s & 0x80000000) << 63 |	// sign
		(uint64)((s >> 23 &0xff) + (DOUBLE_EXPBIAS - SINGLE_EXPBIAS)) << 52  |	// exponent
		(uint64)(s & 0x7fffff) << 29;	// mantissa
}

static int64
rsh(int64 f, int32 s)
{
	if (s >= 0)
		return f>>s;
	else
		return f<<-s;
}

// cdp, data processing instructions
static void
dataprocess(uint32* pc)
{
	uint32 i, opcode, unary, dest, lhs, rhs, prec;
	uint32 high;
	int32 expd, exp0, exp1;
	uint64 fraw0, fraw1, exp, sign;
	uint64 fd, f0, f1;
	int64 fsd, fs0, fs1;

	i = *pc;

	// data processing
	opcode = i>>20 & 15;
	unary = i>>15 & 1;

	dest = i>>12 & 7;		
	lhs = i>>16 & 7;
	rhs = i & 15;

	prec = precision(i);
//	if (prec != 1)
//		goto undef;

	if (unary) {
		switch (opcode) {
		case 0: // mvf
			m->freg[dest] = frhs(rhs);
			goto ret;
		default:
			goto undef;
		}
	} else {
		fraw0 = m->freg[lhs];
		fraw1 = frhs(rhs);
		switch (opcode) {
		case 2: // suf
			fraw1 ^= 0x1ll << 63;
			// fallthrough
		case 0: // adf
			if (fraw0 == DZERO || fraw0 == DNZERO) {
				m->freg[dest] = fraw1;
				goto ret;
			}
			if (fraw1 == DZERO || fraw1 == DNZERO) {
				m->freg[dest] = fraw0;
				goto ret;
			}
			fs0 = fraw0 & DOUBLE_MANT_MASK | DOUBLE_MANT_TOP_BIT;
			fs1 = fraw1 & DOUBLE_MANT_MASK | DOUBLE_MANT_TOP_BIT;
			exp0 = fexp(fraw0);
			exp1 = fexp(fraw1);
			if (exp0 > exp1)
				fs1 = rsh(fs1, exp0-exp1);
			else
				fs0 = rsh(fs0, exp1-exp0);
			if (fraw0 & 0x1ll<<63)
				fs0 = -fs0;
			if (fraw1 & 0x1ll<<63)
				fs1 = -fs1;
			fsd = fs0 + fs1;
			if (fsd == 0) {
				m->freg[dest] = DZERO;
				goto ret;
			}
			sign = (uint64)fsd & 0x1ll<<63;
			if (fsd < 0)
				fsd = -fsd;
			for (expd = 55; expd > 0; expd--) {
				if (0x1ll<<expd & fsd)
					break;
			}
			if (exp0 > exp1)
				exp = expd + exp0 - 52;
			else
				exp = expd + exp1 - 52;
			// too small value, can't represent
			if (1<<31 & expd) {
				m->freg[dest] = DZERO;
				goto ret;
			}
			// infinity
			if (expd > 1<<12) {
				m->freg[dest] = DINF;
				goto ret;
			}
			fd = sign | (exp + DOUBLE_EXPBIAS)<<52 | (uint64)fsd & DOUBLE_MANT_MASK;
			m->freg[dest] = fd;
			goto ret;

		case 4: //dvf
			// reciprocal for fraw1
			if (fraw1 == DONE)
				goto muf;
			f0 = 0x1ll << 63;
			f1 = fraw1 & DOUBLE_MANT_MASK | DOUBLE_MANT_TOP_BIT;
			f1 >>= 21;
			fd = f0/f1;
			fd <<= 21;
			fd &= DOUBLE_MANT_MASK;
			exp1 = -fexp(fraw1) - 1;
			sign = fraw1 & 0x1ll<<63;
			fraw1 = sign | (uint64)(exp1 + DOUBLE_EXPBIAS)<<52 | fd;
			// fallthrough
		case 1: // muf
muf:			
			if (fraw0 == DNZERO || fraw1 == DNZERO) {
				m->freg[dest] = DNZERO;
				goto ret;
			}
			if (fraw0 == DZERO || fraw1 == DZERO) {
				m->freg[dest] = DZERO;
				goto ret;
			}
			if (fraw0 == DONE) {
				m->freg[dest] = fraw1;
				goto ret;
			}
			if (fraw1 == DONE) {
				m->freg[dest] = fraw0;
				goto ret;
			}
			f0 = fraw0>>21 & 0x7fffffff | 0x1ll<<31;
			f1 = fraw1>>21 & 0x7fffffff | 0x1ll<<31;
			fd = f0*f1;
			high = fd >> 63;
			if (high)
				fd = fd >> 11 & DOUBLE_MANT_MASK;
			else
				fd = fd >> 10 & DOUBLE_MANT_MASK;
			exp = (uint64)(fexp(fraw0) + fexp(fraw1) + !!high + DOUBLE_EXPBIAS) & 0x7ff;
			sign = fraw0 >> 63 ^ fraw1 >> 63;
			fd = sign<<63 | exp<<52 | fd;
			m->freg[dest] = fd;
			goto ret;

		default:
			goto undef;
		}
	}


undef:
	doabort = 1;

ret:
	if (trace || doabort) {
		printf(" %p %x\t%s%s\tf%d, ", pc, *pc, opnames[opcode | unary<<4],
			fpprec[prec], dest);
		if (!unary)
			printf("f%d, ", lhs);
		if (rhs & 0x8)
			printf("#%s\n", fpconst[rhs&0x7]);
		else
			printf("f%d\n", rhs&0x7);
		fprint();
	}
	if (doabort)
		fabort();
}

#define CPSR 14
#define FLAGS_N (1 << 31)
#define FLAGS_Z (1 << 30)
#define FLAGS_C (1 << 29)
#define FLAGS_V (1 << 28)

// cmf, compare floating point
static void
compare(uint32 *pc, uint32 *regs) {
	uint32 i, flags, lhs, rhs, sign0, sign1;
	uint64 f0, f1, mant0, mant1;
	int32 exp0, exp1;

	i = *pc;
	flags = 0;
	lhs = i>>16 & 0x7;
	rhs = i & 0xf;

	f0 = m->freg[lhs];
	f1 = frhs(rhs);
	if (isNaN(float64frombits(f0)) || isNaN(float64frombits(f1))) {
		flags = FLAGS_C | FLAGS_V;
		goto ret;
	}
	if (f0 == f1) {
		flags = FLAGS_Z | FLAGS_C;
		goto ret;
	}

	sign0 = fsign(f0);
	sign1 = fsign(f1);
	if (sign0 == 1 && sign1 == 0) {
		flags = FLAGS_N;
		goto ret;
	}
	if (sign0 == 0 && sign1 == 1) {
		flags = FLAGS_C;
		goto ret;
	}

	if (sign0 == 0) {
		exp0 = fexp(f0);
		exp1 = fexp(f1);
		mant0 = fmantissa(f0);
		mant1 = fmantissa(f1);
	} else {
		exp0 = fexp(f1);
		exp1 = fexp(f0);
		mant0 = fmantissa(f1);
		mant1 = fmantissa(f0);
	}

	if (exp0 > exp1) {
		flags = FLAGS_C;
	} else if (exp0 < exp1) {
		flags = FLAGS_N;
	} else {
		if (mant0 > mant1)
			flags = FLAGS_C;
		else
			flags = FLAGS_N;
	}

ret:
	if (trace) {
		printf(" %p %x\tcmf\tf%d, ", pc, *pc, lhs);
		if (rhs & 0x8)
			printf("#%s\n", fpconst[rhs&0x7]);
		else
			printf("f%d\n", rhs&0x7);
	}
	regs[CPSR] = regs[CPSR] & 0x0fffffff | flags;
}

// ldf/stf, load/store floating
static void
loadstore(uint32 *pc, uint32 *regs)
{
	uint32 i, isload, coproc, ud, wb, tlen, p, reg, freg, offset;
	uint32 addr;

	i = *pc;
	coproc = i>>8&0xf;
	isload = i>>20&1;
	p = i>>24&1;
	ud = i>>23&1;
	tlen = i>>(22 - 1)&1 | i>>15&1;
	wb = i>>21&1;
	reg = i>>16 &0xf;
	freg = i>>12 &0x7;
	offset = (i&0xff) << 2;
	
	if (coproc != 1 || p != 1 || wb != 0 || tlen > 1)
		goto undef;
	if (reg > 13)
		goto undef;

	if (ud)
		addr = regs[reg] + offset;
	else
		addr = regs[reg] - offset;

	if (isload)
		if (tlen)
			m->freg[freg] = *((uint64*)addr);
		else
			m->freg[freg] = s2d(*((uint32*)addr));
	else
		if (tlen)
			*((uint64*)addr) = m->freg[freg];
		else
			*((uint32*)addr) = d2s(m->freg[freg]);
	goto ret;

undef:
	doabort = 1;

ret:
	if (trace || doabort) {
		if (isload)
			printf(" %p %x\tldf", pc, *pc);
		else
			printf(" %p %x\tstf", pc, *pc);
		printf("%s\t\tf%d, %s%d(r%d)", fpprec[tlen], freg, ud ? "" : "-", offset, reg);
		printf("\t\t// %p", regs[reg] + (ud ? offset : -offset));
		if (coproc != 1 || p != 1 || wb != 0)
			printf(" coproc: %d pre: %d wb %d", coproc, p, wb);
		printf("\n");
		fprint();
	}
	if (doabort)
		fabort();
}

static void
loadconst(uint32 *pc, uint32 *regs)
{
	uint32 offset;
	uint32 *addr;

	if (*pc & 0xfffff000 != 0xe59fb838 ||
		*(pc+1) != 0xe08bb00c ||
		*(pc+2) & 0xffff8fff != 0xed9b0100)
		goto undef;

	offset = *pc & 0xfff;
	addr = (uint32*)((uint8*)pc + offset + 8);
//printf("DEBUG: addr %p *addr %x final %p\n", addr, *addr, *addr + regs[12]);
	regs[11] = *addr + regs[12];
	loadstore(pc + 2, regs);
	goto ret;

undef:
	doabort = 1;

ret:
	if (trace || doabort) {
		printf(" %p coproc const %x %x %x\n", pc, *pc, *(pc+1), *(pc+2));
	}
	if (doabort)
		fabort();
}

static void
fltfix(uint32 *pc, uint32 *regs)
{
	uint32 i, toarm, freg, reg, sign, val, prec;
	int32 rd, exp;
	uint64 fd, f0;
	
	i = *pc;
	toarm = i>>20 & 0x1;
	freg = i>>16 & 0x7;
	reg = i>>12 & 0xf;
	prec = precision(i);

	if (toarm) { //fix
		f0 = m->freg[freg];
		fd = f0 & DOUBLE_MANT_MASK | DOUBLE_MANT_TOP_BIT;
		exp = fexp(f0) - 52;
		if (exp < 0)
			fd = fd>>(-exp);
		else
			fd = fd<<exp;
		rd = ((int32)fd & 0x7fffffff);
		if (f0 & 0x1ll<<63)
			rd = -rd;
		regs[reg] = (uint32)rd;
	} else { // flt
		if (regs[reg] == 0) {
			m->freg[freg] = DZERO;
			goto ret;
		}
		sign = regs[reg] >> 31 & 0x1;
		val = regs[reg];
		if (sign) val = -val;
		for (exp = 31; exp >= 0; exp--) {
			if (1<<(exp) & val)
				break;
		}
		fd = (uint64)val<<(52-exp) & DOUBLE_MANT_MASK;
		m->freg[freg] = (uint64)(sign) << 63 | 
			(uint64)(exp + DOUBLE_EXPBIAS) << 52 | fd;
	}
	goto ret;
	
ret:
	if (trace || doabort) {
		if (toarm)
			printf(" %p %x\tfix%s\t\tr%d, f%d\n", pc, *pc, fpprec[prec], reg, freg);
		else
			printf(" %p %x\tflt%s\t\tf%d, r%d\n", pc, *pc, fpprec[prec], freg, reg);
		fprint();
	}
	if (doabort)
		fabort();
}

// returns number of words that the fp instruction is occupying, 0 if next instruction isn't float.
// TODO(kaib): insert sanity checks for coproc 1
static uint32
stepflt(uint32 *pc, uint32 *regs)
{
	uint32 i, c;

	i = *pc;

	// unconditional forward branches.
	// inserted by linker after we instrument the code.
	if ((i & 0xff000000) == 0xea000000) {
		if (i & 0x00800000) {
			return 0;
		}
		return i & 0x007ffffff + 2;
	}
	
	c = i >> 25 & 7;
	switch(c) {
	case 6: // 110
		loadstore(pc, regs);
		return 1;
	case 7: // 111
		if (i>>24 & 1) return 0; // ignore swi

		if (i>>4 & 1) { //data transfer
			if ((i&0x00f0ff00) == 0x0090f100) {
				compare(pc, regs);
			} else if ((i&0x00e00f10) == 0x00000110) {
				fltfix(pc, regs);
			} else {
				printf(" %p %x\t// case 7 fail\n", pc, i);
				fabort();
			}
		} else {
			dataprocess(pc);
		}
		return 1;
	}

	// lookahead for virtual instructions that span multiple arm instructions
	c = ((*pc & 0x0f000000) >> 16) |
		((*(pc + 1)  & 0x0f000000) >> 20) |
		((*(pc + 2) & 0x0f000000) >> 24);
	if(c == 0x50d) { // 0101 0000 1101
		loadconst(pc, regs);
		return 3;
	}

	return 0;
}

#pragma textflag 7
uint32*
_sfloat2(uint32 *lr, uint32 r0)
{
	uint32 skip;
//	uint32 cpsr;

	while(skip = stepflt(lr, &r0)) {
		lr += skip;
	}
	return lr;
}

