/*
===========================================================================
Copyright (C) 2009 David S. Miller <davem@davemloft.net>
Copyright (C) 2000-2013 Darklegion Development

This file is part of Tremulous.

Tremulous is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Tremulous is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/

/* This code is based almost entirely upon the vm_powerpc.c code by
 * Przemyslaw Iskra.  All I did was make it work on Sparc :-) -DaveM
 */

#include <sys/types.h>
#include <sys/mman.h>
#include <sys/time.h>
#include <time.h>
#include <stddef.h>

#include "vm_local.h"
#include "vm_sparc.h"

/* exit() won't be called but use it because it is marked with noreturn */
#define DIE( reason ) \
	do { \
		Com_Error(ERR_DROP, "vm_sparc compiler error: " reason); \
		exit(1); \
	} while(0)

/* Select Length - first value on 32 bits, second on 64 */
#ifdef __arch64__
#define SL(a, b) (b)
#else
#define SL(a, b) (a)
#endif

#define rTMP		G1
#define rVMDATA		G2
#define rPSTACK		G3
#define rDATABASE	G4
#define rDATAMASK	G5

struct sparc_opcode {
	const char	*name;
	unsigned int	opcode;
	unsigned int	mask;
	unsigned char	args[4];
#define ARG_NONE	0
#define ARG_RS1		1
#define ARG_RS2		2
#define ARG_RD		3
#define ARG_SIMM13	4
#define ARG_DISP30	5
#define ARG_IMM22	6
#define ARG_DISP22	7
#define ARG_SWTRAP	8
};

#define ARG_RS1_RS2_RD		{ ARG_RS1, ARG_RS2, ARG_RD }
#define ARG_RS1_SIMM13_RD	{ ARG_RS1, ARG_SIMM13, ARG_RD }
#define ARG_RS1_RS2		{ ARG_RS1, ARG_RS2 }
#define ARG_RS2_RD		{ ARG_RS2, ARG_RD }

#define OP_MASK		0xc0000000
#define OP2_MASK	0x01c00000
#define OP3_MASK	0x01f80000
#define OPF_MASK	0x00003fe0

#define IMM		0x00002000

#define FMT1(op)		((op) << 30), OP_MASK
#define FMT2(op,op2)		((op) << 30)|((op2)<<22), (OP_MASK | OP2_MASK)
#define FMT3(op,op3)		((op) << 30)|((op3)<<19), (OP_MASK | OP3_MASK | IMM)
#define FMT3I(op,op3)		((op) << 30)|((op3)<<19)|IMM, (OP_MASK | OP3_MASK | IMM)
#define FMT3F(op,op3,opf)	((op) << 30)|((op3)<<19)|((opf)<<5), \
				(OP_MASK | OP3_MASK | OPF_MASK)

#define BICC(A,COND)		FMT2(0,((A<<7)|(COND<<3)|0x2))
#define BFCC(A,COND)		FMT2(0,((A<<7)|(COND<<3)|0x6))
#define TICC(COND)		FMT3I(0,((COND<<6)|0x3a))

enum sparc_iname {
	CALL, NOP, SETHI,

	BA, BN, BNE, BE, BG, BLE, BGE, BL, BGU, BLEU, BCC, BCS,
	BPOS, BNEG, BVC, BVS,

	ADDI, ADD,
	ANDI, AND,
	ORI, OR,
	XORI, XOR,
	SUBI, SUB,
	ANDNI, ANDN,
	ORNI, ORN,
	XNORI, XNOR,

	UMULI, UMUL,
	SMULI, SMUL,
	UDIVI, UDIV,
	SDIVI, SDIV,

	SUBCCI, SUBCC,

	SLLI, SLL,
	SRLI, SRL,
	SRAI, SRA,

	WRI, WR,

	SAVEI, SAVE,
	RESTOREI, RESTORE,

	TA,

	JMPLI, JMPL,

	LDXI, LDX,
	LDUWI, LDUW,
	LDUHI, LDUH,
	LDUBI, LDUB,

	STXI, STX,
	STWI, STW,
	STHI, STH,
	STBI, STB,

	LDFI, LDF,
	STFI, STF,

	FADD, FSUB, FCMP, FSTOI, FITOS, FNEG, FDIV, FMUL,
	FBE, FBNE, FBL, FBGE, FBG, FBLE,
};

#define LDLI	SL(LDUWI, LDXI)
#define LDL	SL(LDUW, LDX)
#define STLI	SL(STWI, STXI)
#define STL	SL(STW, STX)

#define SPARC_NOP	0x01000000

static const struct sparc_opcode sparc_opcodes[] = {
	{ "call",	FMT1(1), { ARG_DISP30 }, },
	{ "nop",	SPARC_NOP, 0xffffffff, { ARG_NONE }, }, /* sethi %hi(0), %g0 */
	{ "sethi",	FMT2(0,4), { ARG_IMM22, ARG_RD }, },
	{ "ba",		BICC(0,8), { ARG_DISP22 }, },
	{ "bn",		BICC(0,0), { ARG_DISP22 }, },
	{ "bne",	BICC(0,9), { ARG_DISP22 }, },
	{ "be",		BICC(0,1), { ARG_DISP22 }, },
	{ "bg",		BICC(0,10), { ARG_DISP22 }, },
	{ "ble",	BICC(0,2), { ARG_DISP22 }, },
	{ "bge",	BICC(0,11), { ARG_DISP22 }, },
	{ "bl",		BICC(0,3), { ARG_DISP22 }, },
	{ "bgu",	BICC(0,12), { ARG_DISP22 }, },
	{ "bleu",	BICC(0,4), { ARG_DISP22 }, },
	{ "bcc",	BICC(0,13), { ARG_DISP22 }, },
	{ "bcs",	BICC(0,5), { ARG_DISP22 }, },
	{ "bpos",	BICC(0,14), { ARG_DISP22 }, },
	{ "bneg",	BICC(0,6), { ARG_DISP22 }, },
	{ "bvc",	BICC(0,15), { ARG_DISP22 }, },
	{ "bvs",	BICC(0,7), { ARG_DISP22 }, },

	{ "add",	FMT3I(2, 0x00), ARG_RS1_SIMM13_RD, },
	{ "add",	FMT3 (2, 0x00), ARG_RS1_RS2_RD,    },
	{ "and",	FMT3I(2, 0x01), ARG_RS1_SIMM13_RD, },
	{ "and",	FMT3 (2, 0x01), ARG_RS1_RS2_RD,    },
	{ "or",		FMT3I(2, 0x02), ARG_RS1_SIMM13_RD, },
	{ "or",		FMT3 (2, 0x02), ARG_RS1_RS2_RD,    },
	{ "xor",	FMT3I(2, 0x03), ARG_RS1_SIMM13_RD, },
	{ "xor",	FMT3 (2, 0x03), ARG_RS1_RS2_RD,    },
	{ "sub",	FMT3I(2, 0x04), ARG_RS1_SIMM13_RD, },
	{ "sub",	FMT3 (2, 0x04), ARG_RS1_RS2_RD,    },
	{ "andn",	FMT3I(2, 0x05), ARG_RS1_SIMM13_RD, },
	{ "andn",	FMT3 (2, 0x05), ARG_RS1_RS2_RD,    },
	{ "orn",	FMT3I(2, 0x06), ARG_RS1_SIMM13_RD, },
	{ "orn",	FMT3 (2, 0x06), ARG_RS1_RS2_RD,    },
	{ "xnor",	FMT3I(2, 0x07), ARG_RS1_SIMM13_RD, },
	{ "xnor",	FMT3 (2, 0x07), ARG_RS1_RS2_RD,    },

	{ "umul",	FMT3I(2, 0x0a), ARG_RS1_SIMM13_RD, },
	{ "umul",	FMT3 (2, 0x0a), ARG_RS1_RS2_RD,    },
	{ "smul",	FMT3I(2, 0x0b), ARG_RS1_SIMM13_RD, },
	{ "smul",	FMT3 (2, 0x0b), ARG_RS1_RS2_RD,    },
	{ "udiv",	FMT3I(2, 0x0e), ARG_RS1_SIMM13_RD, },
	{ "udiv",	FMT3 (2, 0x0e), ARG_RS1_RS2_RD,    },
	{ "sdiv",	FMT3I(2, 0x0f), ARG_RS1_SIMM13_RD, },
	{ "sdiv",	FMT3 (2, 0x0f), ARG_RS1_RS2_RD,    },

	{ "subcc",	FMT3I(2, 0x14), ARG_RS1_SIMM13_RD, },
	{ "subcc",	FMT3 (2, 0x14), ARG_RS1_RS2_RD,    },

	{ "sll",	FMT3I(2, 0x25), ARG_RS1_SIMM13_RD, },
	{ "sll",	FMT3 (2, 0x25), ARG_RS1_RS2_RD,    },
	{ "srl",	FMT3I(2, 0x26), ARG_RS1_SIMM13_RD, },
	{ "srl",	FMT3 (2, 0x26), ARG_RS1_RS2_RD,    },
	{ "sra",	FMT3I(2, 0x27), ARG_RS1_SIMM13_RD, },
	{ "sra",	FMT3 (2, 0x27), ARG_RS1_RS2_RD,    },

	{ "wr",		FMT3I(2, 0x30), ARG_RS1_SIMM13_RD, },
	{ "wr",		FMT3 (2, 0x30), ARG_RS1_SIMM13_RD, },

	{ "save",	FMT3I(2,0x3c), ARG_RS1_SIMM13_RD, },
	{ "save",	FMT3 (2,0x3c), ARG_RS1_RS2_RD,    },
	{ "restore",	FMT3I(2,0x3d), ARG_RS1_SIMM13_RD, },
	{ "restore",	FMT3 (2,0x3d), ARG_RS1_RS2_RD,    },
	{ "ta",		TICC(8), { ARG_SWTRAP, ARG_NONE }, },
	{ "jmpl",	FMT3I(2,0x38), ARG_RS1_SIMM13_RD, },
	{ "jmpl",	FMT3 (2,0x38), ARG_RS1_RS2_RD,    },

	{ "ldx",	FMT3I(3,0x0b), ARG_RS1_SIMM13_RD, },
	{ "ldx",	FMT3 (3,0x0b), ARG_RS1_RS2_RD,    },
	{ "lduw",	FMT3I(3,0x00), ARG_RS1_SIMM13_RD, },
	{ "lduw",	FMT3 (3,0x00), ARG_RS1_RS2_RD,    },
	{ "lduh",	FMT3I(3,0x02), ARG_RS1_SIMM13_RD, },
	{ "lduh",	FMT3 (3,0x02), ARG_RS1_RS2_RD,    },
	{ "ldub",	FMT3I(3,0x01), ARG_RS1_SIMM13_RD, },
	{ "ldub",	FMT3 (3,0x01), ARG_RS1_RS2_RD,    },

	{ "stx",	FMT3I(3,0x0e), ARG_RS1_SIMM13_RD, },
	{ "stx",	FMT3 (3,0x0e), ARG_RS1_RS2_RD,    },
	{ "stw",	FMT3I(3,0x04), ARG_RS1_SIMM13_RD, },
	{ "stw",	FMT3 (3,0x04), ARG_RS1_RS2_RD,    },
	{ "sth",	FMT3I(3,0x06), ARG_RS1_SIMM13_RD, },
	{ "sth",	FMT3 (3,0x06), ARG_RS1_RS2_RD,    },
	{ "stb",	FMT3I(3,0x05), ARG_RS1_SIMM13_RD, },
	{ "stb",	FMT3 (3,0x05), ARG_RS1_RS2_RD,    },

	{ "ldf",	FMT3I(3,0x20), ARG_RS1_SIMM13_RD, },
	{ "ldf",	FMT3 (3,0x20), ARG_RS1_RS2_RD,    },
	{ "stf",	FMT3I(3,0x24), ARG_RS1_SIMM13_RD, },
	{ "stf",	FMT3 (3,0x24), ARG_RS1_RS2_RD, },

	{ "fadd",	FMT3F(2,0x34,0x041), ARG_RS1_RS2_RD, },
	{ "fsub",	FMT3F(2,0x34,0x045), ARG_RS1_RS2_RD, },
	{ "fcmp",	FMT3F(2,0x35,0x051), ARG_RS1_RS2, },
	{ "fstoi",	FMT3F(2,0x34,0x0d1), ARG_RS2_RD, },
	{ "fitos",	FMT3F(2,0x34,0x0c4), ARG_RS2_RD, },

	{ "fneg",	FMT3F(2,0x34,0x005), ARG_RS2_RD, },
	{ "fdiv",	FMT3F(2,0x34,0x04d), ARG_RS1_RS2_RD, },
	{ "fmul",	FMT3F(2,0x34,0x049), ARG_RS1_RS2_RD, },

	{ "fbe",	BFCC(0,9), { ARG_DISP22 }, },
	{ "fbne",	BFCC(0,1), { ARG_DISP22 }, },
	{ "fbl",	BFCC(0,4), { ARG_DISP22 }, },
	{ "fbge",	BFCC(0,11), { ARG_DISP22 }, },
	{ "fbg",	BFCC(0,6), { ARG_DISP22 }, },
	{ "fble",	BFCC(0,13), { ARG_DISP22 }, },
};
#define SPARC_NUM_OPCODES (ARRAY_LEN(sparc_opcodes))

#define RS1(X)			(((X) & 0x1f) << 14)
#define RS2(X)			(((X) & 0x1f) << 0)
#define RD(X)			(((X) & 0x1f) << 25)
#define SIMM13(X)		(((X) & 0x1fff) << 0)
#define IMM22(X)		(((X) & 0x3fffff) << 0)
#define DISP30(X)		((((X) >> 2) & 0x3fffffff) << 0)
#define DISP22(X)		((((X) >> 2) & 0x3fffff) << 0)
#define SWTRAP(X)		(((X) & 0x7f) << 0)

#define SIMM13_P(X)		((unsigned int) (X) + 0x1000 < 0x2000)

static void vimm(unsigned int val, int bits, int shift, int sgned, int arg_index)
{
	unsigned int orig_val = val;
	int orig_bits = bits;

	if (sgned) {
		int x = (int) val;
		if (x < 0)
			x = -x;
		val = (unsigned int) x;
		bits--;
	}
	if (val & ~((1U << bits) - 1U)) {
		Com_Printf("VM ERROR: immediate value 0x%08x out of %d bit range\n",
			   orig_val, orig_bits);
		DIE("sparc VM bug");
	}
}

static unsigned int sparc_assemble(enum sparc_iname iname, const int argc, const int *argv)
{
	const struct sparc_opcode *op = &sparc_opcodes[iname];
	unsigned int insn = op->opcode;
	int i, flt, rd_flt;

	flt = (op->name[0] == 'f');
	rd_flt = flt || (op->name[2] == 'f');

	for (i = 0; op->args[i] != ARG_NONE; i++) {
		int val = argv[i];

		switch (op->args[i]) {
		case ARG_RS1: insn |= RS1(val); break;
		case ARG_RS2: insn |= RS2(val); break;
		case ARG_RD:  insn |= RD(val); break;
		case ARG_SIMM13: insn |= SIMM13(val); vimm(val,13,0,1,i); break;
		case ARG_DISP30: insn |= DISP30(val); vimm(val,30,0,1,i); break;
		case ARG_IMM22: insn |= IMM22(val); vimm(val,22,0,0,i); break;
		case ARG_DISP22: insn |= DISP22(val); vimm(val,22,0,1,i); break;
		case ARG_SWTRAP: insn |= SWTRAP(val); vimm(val,7,0,0,i); break;
		}
	}

	return insn;
}

#define IN(inst, args...) \
({	const int argv[] = { args }; \
	const int argc = ARRAY_LEN(argv); \
	sparc_assemble(inst, argc, argv); \
})

#if 0
static void pgreg(int reg_num, int arg_index, int flt)
{
	if (!flt) {
		const char *fmt[] = { "%g", "%o", "%l", "%i" };

		Com_Printf("%s%s%d",
			   (arg_index ? ", " : ""),
			   fmt[reg_num >> 3], reg_num & 7);
	} else
		Com_Printf("%s%%f%d", (arg_index ? ", " : ""), reg_num);
}

static void pimm(unsigned int val, int bits, int shift, int sgned, int arg_index)
	
{
	val >>= shift;
	val &= ((1 << bits) - 1);
	if (sgned) {
		int sval = val << (32 - bits);
		sval >>= (32 - bits);
		Com_Printf("%s%d",
			   (arg_index ? ", " : ""), sval);
	} else
		Com_Printf("%s0x%08x",
			   (arg_index ? ", " : ""), val);
}

static void sparc_disassemble(unsigned int insn)
{
	int op_idx;

	for (op_idx = 0; op_idx < SPARC_NUM_OPCODES; op_idx++) {
		const struct sparc_opcode *op = &sparc_opcodes[op_idx];
		int i, flt, rd_flt;

		if ((insn & op->mask) != op->opcode)
			continue;

		flt = (op->name[0] == 'f');
		rd_flt = flt || (op->name[2] == 'f');

		Com_Printf("ASM: %7s\t", op->name);
		for (i = 0; op->args[i] != ARG_NONE; i++) {
			switch (op->args[i]) {
			case ARG_RS1: pgreg((insn >> 14) & 0x1f, i, flt); break;
			case ARG_RS2: pgreg((insn >> 0) & 0x1f, i, flt); break;
			case ARG_RD:  pgreg((insn >> 25) & 0x1f, i, rd_flt); break;
			case ARG_SIMM13: pimm(insn, 13, 0, 1, i); break;
			case ARG_DISP30: pimm(insn, 30, 0, 0, i); break;
			case ARG_IMM22: pimm(insn, 22, 0, 0, i); break;
			case ARG_DISP22: pimm(insn, 22, 0, 0, i); break;
			case ARG_SWTRAP: pimm(insn, 7, 0, 0, i); break;
			}
		}
		Com_Printf("\n");
		return;
	}
}
#endif

/*
 * opcode information table:
 * - length of immediate value
 * - returned register type
 * - required register(s) type
 */
#define opImm0	0x0000 /* no immediate */
#define opImm1	0x0001 /* 1 byte immadiate value after opcode */
#define opImm4	0x0002 /* 4 bytes immediate value after opcode */

#define opRet0	0x0000 /* returns nothing */
#define opRetI	0x0004 /* returns integer */
#define opRetF	0x0008 /* returns float */
#define opRetIF	(opRetI | opRetF) /* returns integer or float */

#define opArg0	0x0000 /* requires nothing */
#define opArgI	0x0010 /* requires integer(s) */
#define opArgF	0x0020 /* requires float(s) */
#define opArgIF	(opArgI | opArgF) /* requires integer or float */

#define opArg2I	0x0040 /* requires second argument, integer */
#define opArg2F	0x0080 /* requires second argument, float */
#define opArg2IF (opArg2I | opArg2F) /* requires second argument, integer or float */

static const unsigned char vm_opInfo[256] =
{
	[OP_UNDEF]	= opImm0,
	[OP_IGNORE]	= opImm0,
	[OP_BREAK]	= opImm0,
	[OP_ENTER]	= opImm4,
			/* OP_LEAVE has to accept floats, they will be converted to ints */
	[OP_LEAVE]	= opImm4 | opRet0 | opArgIF,
			/* only STORE4 and POP use values from OP_CALL,
			 * no need to convert floats back */
	[OP_CALL]	= opImm0 | opRetI | opArgI,
	[OP_PUSH]	= opImm0 | opRetIF,
	[OP_POP]	= opImm0 | opRet0 | opArgIF,
	[OP_CONST]	= opImm4 | opRetIF,
	[OP_LOCAL]	= opImm4 | opRetI,
	[OP_JUMP]	= opImm0 | opRet0 | opArgI,

	[OP_EQ]		= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_NE]		= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_LTI]	= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_LEI]	= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_GTI]	= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_GEI]	= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_LTU]	= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_LEU]	= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_GTU]	= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_GEU]	= opImm4 | opRet0 | opArgI | opArg2I,
	[OP_EQF]	= opImm4 | opRet0 | opArgF | opArg2F,
	[OP_NEF]	= opImm4 | opRet0 | opArgF | opArg2F,
	[OP_LTF]	= opImm4 | opRet0 | opArgF | opArg2F,
	[OP_LEF]	= opImm4 | opRet0 | opArgF | opArg2F,
	[OP_GTF]	= opImm4 | opRet0 | opArgF | opArg2F,
	[OP_GEF]	= opImm4 | opRet0 | opArgF | opArg2F,

	[OP_LOAD1]	= opImm0 | opRetI | opArgI,
	[OP_LOAD2]	= opImm0 | opRetI | opArgI,
	[OP_LOAD4]	= opImm0 | opRetIF| opArgI,
	[OP_STORE1]	= opImm0 | opRet0 | opArgI | opArg2I,
	[OP_STORE2]	= opImm0 | opRet0 | opArgI | opArg2I,
	[OP_STORE4]	= opImm0 | opRet0 | opArgIF| opArg2I,
	[OP_ARG]	= opImm1 | opRet0 | opArgIF,
	[OP_BLOCK_COPY]	= opImm4 | opRet0 | opArgI | opArg2I,

	[OP_SEX8]	= opImm0 | opRetI | opArgI,
	[OP_SEX16]	= opImm0 | opRetI | opArgI,
	[OP_NEGI]	= opImm0 | opRetI | opArgI,
	[OP_ADD]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_SUB]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_DIVI]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_DIVU]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_MODI]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_MODU]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_MULI]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_MULU]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_BAND]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_BOR]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_BXOR]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_BCOM]	= opImm0 | opRetI | opArgI,
	[OP_LSH]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_RSHI]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_RSHU]	= opImm0 | opRetI | opArgI | opArg2I,
	[OP_NEGF]	= opImm0 | opRetF | opArgF,
	[OP_ADDF]	= opImm0 | opRetF | opArgF | opArg2F,
	[OP_SUBF]	= opImm0 | opRetF | opArgF | opArg2F,
	[OP_DIVF]	= opImm0 | opRetF | opArgF | opArg2F,
	[OP_MULF]	= opImm0 | opRetF | opArgF | opArg2F,
	[OP_CVIF]	= opImm0 | opRetF | opArgI,
	[OP_CVFI]	= opImm0 | opRetI | opArgF,
};

static const char *opnames[256] = {
	"OP_UNDEF", "OP_IGNORE", "OP_BREAK", "OP_ENTER", "OP_LEAVE", "OP_CALL",
	"OP_PUSH", "OP_POP", "OP_CONST", "OP_LOCAL", "OP_JUMP",
	"OP_EQ", "OP_NE", "OP_LTI", "OP_LEI", "OP_GTI", "OP_GEI",
	"OP_LTU", "OP_LEU", "OP_GTU", "OP_GEU", "OP_EQF", "OP_NEF",
	"OP_LTF", "OP_LEF", "OP_GTF", "OP_GEF",
	"OP_LOAD1", "OP_LOAD2", "OP_LOAD4", "OP_STORE1", "OP_STORE2",
	"OP_STORE4", "OP_ARG", "OP_BLOCK_COPY",
	"OP_SEX8", "OP_SEX16",
	"OP_NEGI", "OP_ADD", "OP_SUB", "OP_DIVI", "OP_DIVU",
	"OP_MODI", "OP_MODU", "OP_MULI", "OP_MULU", "OP_BAND",
	"OP_BOR", "OP_BXOR", "OP_BCOM", "OP_LSH", "OP_RSHI", "OP_RSHU",
	"OP_NEGF", "OP_ADDF", "OP_SUBF", "OP_DIVF", "OP_MULF",
	"OP_CVIF", "OP_CVFI",
};

static void VM_Destroy_Compiled(vm_t *vm)
{
	if (vm->codeBase) {
		if (munmap(vm->codeBase, vm->codeLength))
			Com_Printf(S_COLOR_RED "Memory unmap failed, possible memory leak\n");
	}
	vm->codeBase = NULL;
}

typedef struct VM_Data {
	unsigned int dataLength;
	unsigned int codeLength;
	unsigned int *CallThunk;
	int (*AsmCall)(int, int);
	void (*BlockCopy)(unsigned int, unsigned int, unsigned int);
	unsigned int *iPointers;
	void (*ErrJump)(void);
	unsigned int data[0];
} vm_data_t;

#ifdef offsetof
# define VM_Data_Offset(field)		offsetof(vm_data_t, field)
#else
# define OFFSET(structName, field) \
	((void *)&(((structName *)NULL)->field) - NULL)
# define VM_Data_Offset(field)		OFFSET(vm_data_t, field)
#endif

struct src_insn {
	unsigned char		op;
	unsigned int		i_count;

	union {
		unsigned int	i;
		signed int	si;
		signed short	ss[2];
		unsigned short	us[2];
		unsigned char	b;
	} arg;

	unsigned char		dst_reg_flags;
	unsigned char		src1_reg_flags;
	unsigned char		src2_reg_flags;
#define REG_FLAGS_FLOAT		0x1

	struct src_insn		*next;
};

struct dst_insn;
struct jump_insn {
	enum sparc_iname	jump_iname;
	int			jump_dest_insn;
	struct dst_insn		*parent;
	struct jump_insn	*next;
};

struct dst_insn {
	struct dst_insn		*next;

	unsigned int		count;
	unsigned int		i_count;

	struct jump_insn	*jump;
	unsigned int		length;
	unsigned int		code[0];
};

#define HUNK_SIZE		29
struct data_hunk {
	struct data_hunk *next;
	int count;
	unsigned int data[HUNK_SIZE];
};

struct func_info {
	struct src_insn		*first;
	struct src_insn		*last;
	int			has_call;
	int			need_float_tmp;

	struct src_insn		*cached_const;

	int			stack_space;
	int			gpr_pos;
#define rFIRST(fp)		((fp)->gpr_pos - 1)
#define rSECOND(fp)		((fp)->gpr_pos - 2)
#define POP_GPR(fp)		((fp)->gpr_pos--)
#define PUSH_GPR(fp)		((fp)->gpr_pos++)

	int			fpr_pos;
#define fFIRST(fp)		((fp)->fpr_pos - 1)
#define fSECOND(fp)		((fp)->fpr_pos - 2)
#define POP_FPR(fp)		((fp)->fpr_pos--)
#define PUSH_FPR(fp)		((fp)->fpr_pos++)

#define INSN_BUF_SIZE		50
	unsigned int		insn_buf[INSN_BUF_SIZE];
	int			insn_index;

	int			saved_icount;
	int			force_emit;

	struct jump_insn	*jump_first;
	struct jump_insn	*jump_last;

	struct dst_insn		*dst_first;
	struct dst_insn		*dst_last;
	int			dst_count;

	struct dst_insn		**dst_by_i_count;

	struct data_hunk	*data_first;
	int			data_num;
};

#define THUNK_ICOUNT		-1

static unsigned int sparc_push_data(struct func_info * const fp, unsigned int val)
{
	struct data_hunk *last, *dp = fp->data_first;
	int off = 0;

	last = NULL;
	while (dp) {
		int i;

		for (i = 0; i < dp->count; i++) {
			if (dp->data[i] == val) {
				off += i;
				return VM_Data_Offset(data[off]);
			}
		}
		off += dp->count;
		last = dp;
		dp = dp->next;
	}

	dp = last;
	if (!dp || dp->count >= HUNK_SIZE) {
		struct data_hunk *new = Z_Malloc(sizeof(*new));
		if (!dp)
			fp->data_first = new;
		else
			dp->next = new;
		dp = new;
		dp->count = 0;
		dp->next = NULL;
	}
	dp->data[dp->count++] = val;
	fp->data_num = off + 1;
	return VM_Data_Offset(data[off]);
}

static void dst_insn_insert_tail(struct func_info * const fp,
				 struct dst_insn *dp)
{
	if (!fp->dst_first) {
		fp->dst_first = fp->dst_last = dp;
	} else {
		fp->dst_last->next = dp;
		fp->dst_last = dp;
	}
}

static void jump_insn_insert_tail(struct func_info * const fp,
				  struct jump_insn *jp)
{
	if (!fp->jump_first) {
		fp->jump_first = fp->jump_last = jp;
	} else {
		fp->jump_last->next = jp;
		fp->jump_last = jp;
	}
}

static struct dst_insn *dst_new(struct func_info * const fp, unsigned int length,
				struct jump_insn *jp, int insns_size)
{
	struct dst_insn *dp = Z_Malloc(sizeof(struct dst_insn) + insns_size);

	dp->length = length;
	dp->jump = jp;
	dp->count = fp->dst_count++;
	dp->i_count = fp->saved_icount;
	dp->next = NULL;
	if (fp->saved_icount != THUNK_ICOUNT)
		fp->dst_by_i_count[fp->saved_icount] = dp;

	return dp;
}

static void dst_insn_append(struct func_info * const fp)
{
	int insns_size = (sizeof(unsigned int) * fp->insn_index);
	struct dst_insn *dp;

	dp = dst_new(fp, fp->insn_index, NULL, insns_size);
	if (insns_size)
		memcpy(&dp->code[0], fp->insn_buf, insns_size);
	dst_insn_insert_tail(fp, dp);

	fp->insn_index = 0;
}

static void ErrJump(void)
{ 
	Com_Error(ERR_DROP, "program tried to execute code outside VM");
	exit(1);
}

static void jump_insn_append(vm_t *vm, struct func_info * const fp, enum sparc_iname iname, int dest)
{
	struct jump_insn *jp = Z_Malloc(sizeof(*jp));
	struct dst_insn *dp;

	if (dest < 0 || dest >= vm->instructionCount)
		ErrJump();

	dp = dst_new(fp, 2, jp, 0);

	jp->jump_iname = iname;
	jp->jump_dest_insn = dest;
	jp->parent = dp;
	jp->next = NULL;

	jump_insn_insert_tail(fp, jp);
	dst_insn_insert_tail(fp, dp);
}

static void start_emit(struct func_info * const fp, int i_count)
{
	fp->saved_icount = i_count;
	fp->insn_index = 0;
	fp->force_emit = 0;
}

static void __do_emit_one(struct func_info * const fp, unsigned int insn)
{
	fp->insn_buf[fp->insn_index++] = insn;
}

#define in(inst, args...) __do_emit_one(fp,  IN(inst, args))

static void end_emit(struct func_info * const fp)
{
	if (fp->insn_index || fp->force_emit)
		dst_insn_append(fp);
}

static void emit_jump(vm_t *vm, struct func_info * const fp, enum sparc_iname iname, int dest)
{
	end_emit(fp);
	jump_insn_append(vm, fp, iname, dest);
}

static void analyze_function(struct func_info * const fp)
{
	struct src_insn *value_provider[20] = { NULL };
	struct src_insn *sp = fp->first;
	int opstack_depth = 0;

	while ((sp = sp->next) != NULL) {
		unsigned char opi, op = sp->op;

		opi = vm_opInfo[op];
		if (opi & opArgIF) {
			struct src_insn *vp = value_provider[--opstack_depth];
			unsigned char vpopi = vm_opInfo[vp->op];

			if ((opi & opArgI) && (vpopi & opRetI)) {
				/* src1 and dst are integers */
			} else if ((opi & opArgF) && (vpopi & opRetF)) {
				/* src1 and dst are floats */
				vp->dst_reg_flags |= REG_FLAGS_FLOAT;
				sp->src1_reg_flags = REG_FLAGS_FLOAT;
			} else {
				/* illegal combination */
				DIE("unrecognized instruction combination");
			}
		}
		if (opi & opArg2IF) {
			struct src_insn *vp = value_provider[--opstack_depth];
			unsigned char vpopi = vm_opInfo[vp->op];

			if ((opi & opArg2I) && (vpopi & opRetI)) {
				/* src2 and dst are integers */
			} else if ( (opi & opArg2F) && (vpopi & opRetF) ) {
				/* src2 and dst are floats */
				vp->dst_reg_flags |= REG_FLAGS_FLOAT;
				sp->src2_reg_flags = REG_FLAGS_FLOAT;
			} else {
				/* illegal combination */
				DIE("unrecognized instruction combination");
			}
		}
		if (opi & opRetIF) {
			value_provider[opstack_depth] = sp;
			opstack_depth++;
		}
	}
}

static int asmcall(int call, int pstack)
{
	vm_t *savedVM = currentVM;
	int i, ret;

	currentVM->programStack = pstack - 4;
	if (sizeof(intptr_t) == sizeof(int)) {
		intptr_t *argPosition = (intptr_t *)((byte *)currentVM->dataBase + pstack + 4);
		argPosition[0] = -1 - call;
		ret = currentVM->systemCall(argPosition);
	} else {
		intptr_t args[MAX_VMSYSCALL_ARGS];

		args[0] = -1 - call;
		int *argPosition = (int *)((byte *)currentVM->dataBase + pstack + 4);
		for( i = 1; i < ARRAY_LEN(args); i++ )
			args[i] = argPosition[i];

		ret = currentVM->systemCall(args);
	}

	currentVM = savedVM;

	return ret;
}

static void blockcopy(unsigned int dest, unsigned int src, unsigned int count)
{
	unsigned int dataMask = currentVM->dataMask;

	if ((dest & dataMask) != dest ||
	    (src & dataMask) != src ||
	    ((dest+count) & dataMask) != dest + count ||
	    ((src+count) & dataMask) != src + count) {
		DIE("OP_BLOCK_COPY out of range!");
	}

	memcpy(currentVM->dataBase+dest, currentVM->dataBase+src, count);
}

static void do_emit_const(struct func_info * const fp, struct src_insn *sp)
{
	start_emit(fp, sp->i_count);
	if (sp->dst_reg_flags & REG_FLAGS_FLOAT) {
		in(LDFI, rVMDATA, sparc_push_data(fp, sp->arg.i), fFIRST(fp));
	} else {
		if ((sp->arg.i & ~0x3ff) == 0) {
			in(ORI, G0, sp->arg.i & 0x3ff, rFIRST(fp));
		} else if ((sp->arg.i & 0x3ff) == 0) {
			in(SETHI, sp->arg.i >> 10, rFIRST(fp));
		} else {
			in(SETHI, sp->arg.i >> 10, rFIRST(fp));
			in(ORI, rFIRST(fp), sp->arg.i & 0x3ff, rFIRST(fp));
		}
	}
	end_emit(fp);
}

#define MAYBE_EMIT_CONST(fp)	\
do {	if ((fp)->cached_const) {	       \
		int saved_i_count = (fp)->saved_icount; \
		do_emit_const(fp, (fp)->cached_const); \
		(fp)->saved_icount = saved_i_count; \
	} \
} while (0)

#define EMIT_FALSE_CONST(fp)					\
do {	int saved_i_count = (fp)->saved_icount;			\
	(fp)->saved_icount = (fp)->cached_const->i_count;	\
	dst_insn_append(fp);					\
	(fp)->saved_icount = saved_i_count;			\
} while (0)

static void compile_one_insn(vm_t *vm, struct func_info * const fp, struct src_insn *sp)
{
	start_emit(fp, sp->i_count);

	switch (sp->op) {
	default:
		Com_Printf("VM: Unhandled opcode 0x%02x[%s]\n",
			   sp->op,
			   opnames[sp->op] ? opnames[sp->op] : "UNKNOWN");
		DIE("Unsupported opcode");
		break;

	case OP_ENTER: {
		int stack = SL(64, 128);

		if (fp->need_float_tmp)
			stack += 16;

		in(SAVEI, O6, -stack, O6);
		if (!SIMM13_P(sp->arg.si)) {
			in(SETHI, sp->arg.i >> 10, rTMP);
			in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP);
			in(SUB, rPSTACK, rTMP, rPSTACK);
		} else
			in(SUBI, rPSTACK, sp->arg.si, rPSTACK);
		break;
	}
	case OP_LEAVE:
		if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) {
			EMIT_FALSE_CONST(fp);
			if (fp->cached_const->src1_reg_flags & REG_FLAGS_FLOAT)
				DIE("constant float in OP_LEAVE");

			if (!SIMM13_P(sp->arg.si)) {
				in(SETHI, sp->arg.i >> 10, rTMP);
				in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP);
				in(ADD, rPSTACK, rTMP, rPSTACK);
			} else
				in(ADDI, rPSTACK, sp->arg.si, rPSTACK);
			in(JMPLI, I7, 8, G0);
			in(RESTOREI, G0, fp->cached_const->arg.si, O0);
			POP_GPR(fp);
		} else {
			MAYBE_EMIT_CONST(fp);
			if (!SIMM13_P(sp->arg.si)) {
				in(SETHI, sp->arg.i >> 10, rTMP);
				in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP);
				in(ADD, rPSTACK, rTMP, rPSTACK);
			} else
				in(ADDI, rPSTACK, sp->arg.si, rPSTACK);
			if (sp->src1_reg_flags & REG_FLAGS_FLOAT) {
				in(STFI, O6, SL(64, 128), fFIRST(fp));
				in(LDUWI, O6, SL(64, 128), O0);
				in(JMPLI, I7, 8, G0);
				in(RESTORE, O0, G0, O0);
				POP_FPR(fp);
			} else {
				in(JMPLI, I7, 8, G0);
				in(RESTORE, rFIRST(fp), G0, O0);
				POP_GPR(fp);
			}
		}
		assert(fp->gpr_pos == L0);
		assert(fp->fpr_pos == F0);
		break;
	case OP_JUMP:
		if (fp->cached_const) {
			EMIT_FALSE_CONST(fp);
			emit_jump(vm, fp, BA, fp->cached_const->arg.i);
		} else {
			MAYBE_EMIT_CONST(fp);
			in(SETHI, vm->instructionCount >> 10, rTMP);
			in(ORI, rTMP, vm->instructionCount & 0x3ff, rTMP);
			in(SUBCC, rTMP, rFIRST(fp), G0);
			in(BLEU, +4*5);
			in(LDLI, rVMDATA, VM_Data_Offset(ErrJump), rTMP);

			in(SLLI, rFIRST(fp), 2, rFIRST(fp));
			in(LDLI, rVMDATA, VM_Data_Offset(iPointers), rTMP);
			in(LDL, rTMP, rFIRST(fp), rTMP);
			in(JMPL, rTMP, G0, G0);
			in(NOP);
		}
		POP_GPR(fp);
		break;
	case OP_CALL:
		if (fp->cached_const) {
			EMIT_FALSE_CONST(fp);
			if (fp->cached_const->arg.si >= 0) {
				emit_jump(vm, fp, CALL, fp->cached_const->arg.i);
			} else {
				in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP);
				in(LDLI, rVMDATA, VM_Data_Offset(AsmCall), O3);
				in(ORI, G0, fp->cached_const->arg.si, O0);
				in(JMPL, rTMP, G0, O7);
				in(OR, G0, rPSTACK, O1);
			}
			in(OR, G0, O0, rFIRST(fp));
		} else {
			MAYBE_EMIT_CONST(fp);
			in(SUBCCI, rFIRST(fp), 0, G0);
			in(BL, +4*7);
			in(NOP);

			/* normal call */
			in(SETHI, vm->instructionCount >> 10, rTMP);
			in(ORI, rTMP, vm->instructionCount & 0x3ff, rTMP);
			in(SUBCC, rTMP, rFIRST(fp), G0);
			in(BLEU, +4*9);
			in(LDLI, rVMDATA, VM_Data_Offset(ErrJump), rTMP);
			in(LDLI, rVMDATA, VM_Data_Offset(iPointers), O5);
			in(SLLI, rFIRST(fp), 2, rFIRST(fp));
			in(LDL, O5, rFIRST(fp), rTMP);
			in(BA, +4*4);
			in(NOP);

			/* syscall */
			in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP);
			in(LDLI, rVMDATA, VM_Data_Offset(AsmCall), O3);

			in(OR, G0, rFIRST(fp), O0);
			in(JMPL, rTMP, G0, O7);
			in(OR, G0, rPSTACK, O1);

			/* return value */
			in(OR, G0, O0, rFIRST(fp));
		}
		break;
	case OP_BLOCK_COPY:
		MAYBE_EMIT_CONST(fp);
		in(LDLI, rVMDATA, VM_Data_Offset(CallThunk), rTMP);
		in(LDLI, rVMDATA, VM_Data_Offset(BlockCopy), O3);
		in(OR, G0, rSECOND(fp), O0);
		in(OR, G0, rFIRST(fp), O1);
		if ((sp->arg.i & ~0x3ff) == 0) {
			in(ORI, G0, sp->arg.i & 0x3ff, O2);
		} else if ((sp->arg.i & 0x3ff) == 0) {
			in(SETHI, sp->arg.i >> 10, O2);
		} else {
			in(SETHI, sp->arg.i >> 10, O2);
			in(ORI, O2, sp->arg.i & 0x3ff, O2);
		}
		in(JMPL, rTMP, G0, O7);
		in(NOP);
		POP_GPR(fp);
		POP_GPR(fp);
		break;

	case OP_PUSH:
		MAYBE_EMIT_CONST(fp);
		if (sp->dst_reg_flags & REG_FLAGS_FLOAT)
			PUSH_FPR(fp);
		else
			PUSH_GPR(fp);
		fp->force_emit = 1;
		break;
	case OP_POP:
		MAYBE_EMIT_CONST(fp);
		if (sp->src1_reg_flags & REG_FLAGS_FLOAT)
			POP_FPR(fp);
		else
			POP_GPR(fp);
		fp->force_emit = 1;
		break;
	case OP_ARG:
		MAYBE_EMIT_CONST(fp);
		in(ADDI, rPSTACK, sp->arg.b, rTMP);
		if (sp->src1_reg_flags & REG_FLAGS_FLOAT) {
			in(STF, rDATABASE, rTMP, fFIRST(fp));
			POP_FPR(fp);
		} else {
			in(STW, rDATABASE, rTMP, rFIRST(fp));
			POP_GPR(fp);
		}
		break;
	case OP_IGNORE:
		MAYBE_EMIT_CONST(fp);
		in(NOP);
		break;
	case OP_BREAK:
		MAYBE_EMIT_CONST(fp);
		in(TA, 0x5);
		break;
	case OP_LOCAL:
		MAYBE_EMIT_CONST(fp);
		PUSH_GPR(fp);
		if (!SIMM13_P(sp->arg.i)) {
			in(SETHI, sp->arg.i >> 10, rTMP);
			in(ORI, rTMP, sp->arg.i & 0x3ff, rTMP);
			in(ADD, rPSTACK, rTMP, rFIRST(fp));
		} else
			in(ADDI, rPSTACK, sp->arg.i, rFIRST(fp));
		break;
	case OP_CONST:
		MAYBE_EMIT_CONST(fp);
		break;
	case OP_LOAD4:
		MAYBE_EMIT_CONST(fp);
		in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp));
		if (sp->dst_reg_flags & REG_FLAGS_FLOAT) {
			PUSH_FPR(fp);
			in(LDF, rFIRST(fp), rDATABASE, fFIRST(fp));
			POP_GPR(fp);
		} else {
			in(LDUW, rFIRST(fp), rDATABASE, rFIRST(fp));
		}
		break;
	case OP_LOAD2:
		MAYBE_EMIT_CONST(fp);
		in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp));
		in(LDUH, rFIRST(fp), rDATABASE, rFIRST(fp));
		break;
	case OP_LOAD1:
		MAYBE_EMIT_CONST(fp);
		in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp));
		in(LDUB, rFIRST(fp), rDATABASE, rFIRST(fp));
		break;
	case OP_STORE4:
		MAYBE_EMIT_CONST(fp);
		if (sp->src1_reg_flags & REG_FLAGS_FLOAT) {
			in(AND, rFIRST(fp), rDATAMASK, rFIRST(fp));
			in(STF, rFIRST(fp), rDATABASE, fFIRST(fp));
			POP_FPR(fp);
		} else {
			in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp));
			in(STW, rSECOND(fp), rDATABASE, rFIRST(fp));
			POP_GPR(fp);
		}
		POP_GPR(fp);
		break;
	case OP_STORE2:
		MAYBE_EMIT_CONST(fp);
		in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp));
		in(STH, rSECOND(fp), rDATABASE, rFIRST(fp));
		POP_GPR(fp);
		POP_GPR(fp);
		break;
	case OP_STORE1:
		MAYBE_EMIT_CONST(fp);
		in(AND, rSECOND(fp), rDATAMASK, rSECOND(fp));
		in(STB, rSECOND(fp), rDATABASE, rFIRST(fp));
		POP_GPR(fp);
		POP_GPR(fp);
		break;
	case OP_EQ:
	case OP_NE:
	case OP_LTI:
	case OP_GEI:
	case OP_GTI:
	case OP_LEI:
	case OP_LTU:
	case OP_GEU:
	case OP_GTU:
	case OP_LEU: {
		enum sparc_iname iname = BA;

		if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) {
			EMIT_FALSE_CONST(fp);
			in(SUBCCI, rSECOND(fp), fp->cached_const->arg.si, G0);
		} else {
			MAYBE_EMIT_CONST(fp);
			in(SUBCC, rSECOND(fp), rFIRST(fp), G0);
		}
		switch(sp->op) {
		case OP_EQ: iname = BE; break;
		case OP_NE: iname = BNE; break;
		case OP_LTI: iname = BL; break;
		case OP_GEI: iname = BGE; break;
		case OP_GTI: iname = BG; break;
		case OP_LEI: iname = BLE; break;
		case OP_LTU: iname = BCS; break;
		case OP_GEU: iname = BCC; break;
		case OP_GTU: iname = BGU; break;
		case OP_LEU: iname = BLEU; break;
		}
		emit_jump(vm, fp, iname, sp->arg.i);
		POP_GPR(fp);
		POP_GPR(fp);
		break;
	}

	case OP_SEX8:
		MAYBE_EMIT_CONST(fp);
		in(SLLI, rFIRST(fp), 24, rFIRST(fp));
		in(SRAI, rFIRST(fp), 24, rFIRST(fp));
		break;
	case OP_SEX16:
		MAYBE_EMIT_CONST(fp);
		in(SLLI, rFIRST(fp), 16, rFIRST(fp));
		in(SRAI, rFIRST(fp), 16, rFIRST(fp));
		break;
	case OP_NEGI:
		MAYBE_EMIT_CONST(fp);
		in(SUB, G0, rFIRST(fp), rFIRST(fp));
		break;
	case OP_ADD:
		if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) {
			EMIT_FALSE_CONST(fp);
			in(ADDI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp));
		} else {
			MAYBE_EMIT_CONST(fp);
			in(ADD, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		}
		POP_GPR(fp);
		break;
	case OP_SUB:
		if (fp->cached_const && SIMM13_P(fp->cached_const->arg.si)) {
			EMIT_FALSE_CONST(fp);
			in(SUBI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp));
		} else {
			MAYBE_EMIT_CONST(fp);
			in(SUB, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		}
		POP_GPR(fp);
		break;
	case OP_DIVI:
		MAYBE_EMIT_CONST(fp);
		in(SRAI, rSECOND(fp), 31, rTMP);
		in(WRI, rTMP, 0, Y_REG);
		in(SDIV, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_DIVU:
		MAYBE_EMIT_CONST(fp);
		in(WRI, G0, 0, Y_REG);
		in(UDIV, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_MODI:
		MAYBE_EMIT_CONST(fp);
		in(SRAI, rSECOND(fp), 31, rTMP);
		in(WRI, rTMP, 0, Y_REG);
		in(SDIV, rSECOND(fp), rFIRST(fp), rTMP);
		in(SMUL, rTMP, rFIRST(fp), rTMP);
		in(SUB, rSECOND(fp), rTMP, rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_MODU:
		MAYBE_EMIT_CONST(fp);
		in(WRI, G0, 0, Y_REG);
		in(UDIV, rSECOND(fp), rFIRST(fp), rTMP);
		in(SMUL, rTMP, rFIRST(fp), rTMP);
		in(SUB, rSECOND(fp), rTMP, rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_MULI:
		MAYBE_EMIT_CONST(fp);
		in(SMUL, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_MULU:
		MAYBE_EMIT_CONST(fp);
		in(UMUL, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_BAND:
		MAYBE_EMIT_CONST(fp);
		in(AND, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_BOR:
		MAYBE_EMIT_CONST(fp);
		in(OR, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_BXOR:
		MAYBE_EMIT_CONST(fp);
		in(XOR, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		POP_GPR(fp);
		break;
	case OP_BCOM:
		MAYBE_EMIT_CONST(fp);
		in(XNOR, rFIRST(fp), G0, rFIRST(fp));
		break;
	case OP_LSH:
		if (fp->cached_const) {
			EMIT_FALSE_CONST(fp);
			in(SLLI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp));
		} else {
			MAYBE_EMIT_CONST(fp);
			in(SLL, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		}
		POP_GPR(fp);
		break;
	case OP_RSHI:
		if (fp->cached_const) {
			EMIT_FALSE_CONST(fp);
			in(SRAI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp));
		} else {
			MAYBE_EMIT_CONST(fp);
			in(SRA, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		}
		POP_GPR(fp);
		break;
	case OP_RSHU:
		if (fp->cached_const) {
			EMIT_FALSE_CONST(fp);
			in(SRLI, rSECOND(fp), fp->cached_const->arg.si, rSECOND(fp));
		} else {
			MAYBE_EMIT_CONST(fp);
			in(SRL, rSECOND(fp), rFIRST(fp), rSECOND(fp));
		}
		POP_GPR(fp);
		break;

	case OP_NEGF:
		MAYBE_EMIT_CONST(fp);
		in(FNEG, fFIRST(fp), fFIRST(fp));
		break;
	case OP_ADDF:
		MAYBE_EMIT_CONST(fp);
		in(FADD, fSECOND(fp), fFIRST(fp), fSECOND(fp));
		POP_FPR(fp);
		break;
	case OP_SUBF:
		MAYBE_EMIT_CONST(fp);
		in(FSUB, fSECOND(fp), fFIRST(fp), fSECOND(fp));
		POP_FPR(fp);
		break;
	case OP_DIVF:
		MAYBE_EMIT_CONST(fp);
		in(FDIV, fSECOND(fp), fFIRST(fp), fSECOND(fp));
		POP_FPR(fp);
		break;
	case OP_MULF:
		MAYBE_EMIT_CONST(fp);
		in(FMUL, fSECOND(fp), fFIRST(fp), fSECOND(fp));
		POP_FPR(fp);
		break;

	case OP_EQF:
	case OP_NEF:
	case OP_LTF:
	case OP_GEF:
	case OP_GTF:
	case OP_LEF: {
		enum sparc_iname iname = FBE;

		MAYBE_EMIT_CONST(fp);
		in(FCMP, fSECOND(fp), fFIRST(fp));
		switch(sp->op) {
		case OP_EQF: iname = FBE; break;
		case OP_NEF: iname = FBNE; break;
		case OP_LTF: iname = FBL; break;
		case OP_GEF: iname = FBGE; break;
		case OP_GTF: iname = FBG; break;
		case OP_LEF: iname = FBLE; break;
		}
		emit_jump(vm, fp, iname, sp->arg.i);
		POP_FPR(fp);
		POP_FPR(fp);
		break;
	}
	case OP_CVIF:
		MAYBE_EMIT_CONST(fp);
		PUSH_FPR(fp);
		in(STWI, O6, SL(64, 128), rFIRST(fp));
		in(LDFI, O6, SL(64, 128), fFIRST(fp));
		in(FITOS, fFIRST(fp), fFIRST(fp));
		POP_GPR(fp);
		break;
	case OP_CVFI:
		MAYBE_EMIT_CONST(fp);
		PUSH_GPR(fp);
		in(FSTOI, fFIRST(fp), fFIRST(fp));
		in(STFI, O6, SL(64, 128), fFIRST(fp));
		in(LDUWI, O6, SL(64, 128), rFIRST(fp));
		POP_FPR(fp);
		break;
	}
	if (sp->op != OP_CONST) {
		fp->cached_const = NULL;
		end_emit(fp);
	} else {
		fp->cached_const = sp;
		if (sp->dst_reg_flags & REG_FLAGS_FLOAT) {
			PUSH_FPR(fp);
		} else {
			PUSH_GPR(fp);
		}
	}
	end_emit(fp);
}

static void free_source_insns(struct func_info * const fp)
{
	struct src_insn *sp = fp->first->next;

	while (sp) {
		struct src_insn *next = sp->next;
		Z_Free(sp);
		sp = next;
	}
}

static void compile_function(vm_t *vm, struct func_info * const fp)
{
	struct src_insn *sp;

	analyze_function(fp);

	fp->gpr_pos = L0;
	fp->fpr_pos = F0;
	fp->insn_index = 0;

	fp->stack_space = SL(64, 128);
	fp->cached_const = NULL;

	sp = fp->first;
	while ((sp = sp->next) != NULL)
		compile_one_insn(vm, fp, sp);

	free_source_insns(fp);
}

/* We have two thunks for sparc.  The first is for the entry into
 * the VM, where setup the fixed global registers.  The second is
 * for calling out to C code from the VM, where we need to preserve
 * those fixed globals across the call.
 */
static void emit_vm_thunk(struct func_info * const fp)
{
	/* int vm_thunk(void *vmdata, int programstack, void *database, int datamask) */
	start_emit(fp, THUNK_ICOUNT);

	in(OR, G0, O0, rVMDATA);
	in(OR, G0, O1, rPSTACK);
	in(OR, G0, O2, rDATABASE);
	in(BA, +4*17);
	in(OR, G0, O3, rDATAMASK);

	/* int call_thunk(int arg0, int arg1, int arg2, int (*func)(int int int)) */
#define CALL_THUNK_INSN_OFFSET		5
	in(SAVEI, O6, -SL(64, 128), O6);

	in(OR, G0, rVMDATA, L0);
	in(OR, G0, rPSTACK, L1);
	in(OR, G0, rDATABASE, L2);
	in(OR, G0, rDATAMASK, L3);

	in(OR, G0, I0, O0);
	in(OR, G0, I1, O1);
	in(JMPL, I3, G0, O7);
	in(OR, G0, I2, O2);

	in(OR, G0, L0, rVMDATA);
	in(OR, G0, L1, rPSTACK);
	in(OR, G0, L2, rDATABASE);
	in(OR, G0, L3, rDATAMASK);

	in(JMPLI, I7, 8, G0);
	in(RESTORE, O0, G0, O0);

	end_emit(fp);
}

static void sparc_compute_code(vm_t *vm, struct func_info * const fp)
{
	struct dst_insn *dp = fp->dst_first;
	unsigned int *code_now, *code_begin;
	unsigned char *data_and_code;
	unsigned int code_length;
	int code_insns = 0, off;
	struct data_hunk *dhp;
	struct jump_insn *jp;
	vm_data_t *data;

	while (dp) {
		code_insns += dp->length;
		dp = dp->next;
	}

	code_length = (sizeof(vm_data_t) +
		       (fp->data_num * sizeof(unsigned int)) +
		       (code_insns * sizeof(unsigned int)));

	data_and_code = mmap(NULL, code_length, PROT_READ | PROT_WRITE,
			     MAP_SHARED | MAP_ANONYMOUS, -1, 0);
	if (data_and_code == MAP_FAILED)
		DIE("Not enough memory");

	code_now = code_begin = (unsigned int *)
		(data_and_code + VM_Data_Offset(data[fp->data_num]));

	dp = fp->dst_first;
	while (dp) {
		int i_count = dp->i_count;

		if (i_count != THUNK_ICOUNT) {
			if (!fp->dst_by_i_count[i_count])
				fp->dst_by_i_count[i_count] = (void *) code_now;
		}
		if (!dp->jump) {
			memcpy(code_now, &dp->code[0], dp->length * sizeof(unsigned int));
			code_now += dp->length;
		} else {
			int i;

			dp->jump->parent = (void *) code_now;

			for (i = 0; i < dp->length; i++)
				code_now[i] = SPARC_NOP;
			code_now += dp->length;
		}

		dp = dp->next;
	}

	jp = fp->jump_first;
	while (jp) {
		unsigned int *from = (void *) jp->parent;
		unsigned int *to = (void *) fp->dst_by_i_count[jp->jump_dest_insn];
		signed int disp = (to - from);

		*from = IN(jp->jump_iname, disp << 2);

		jp = jp->next;
	}

	vm->codeBase = data_and_code;
	vm->codeLength = code_length;

	data = (vm_data_t *) data_and_code;
	data->CallThunk = code_begin + CALL_THUNK_INSN_OFFSET;
	data->AsmCall = asmcall;
	data->BlockCopy = blockcopy;
	data->iPointers = (unsigned int *) vm->instructionPointers;
	data->dataLength = VM_Data_Offset(data[fp->data_num]);
	data->codeLength = (code_now - code_begin) * sizeof(unsigned int);
	data->ErrJump = ErrJump;

#if 0
	{
		unsigned int *insn = code_begin;
		int i;

		Com_Printf("INSN DUMP\n");
		for (i = 0; i < data->codeLength / 4; i+= 8) {
			Com_Printf("\t.word\t0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x, 0x%08x\n",
				   insn[i + 0], insn[i + 1],
				   insn[i + 2], insn[i + 3],
				   insn[i + 4], insn[i + 5],
				   insn[i + 6], insn[i + 7]);
		}
	}
#endif

	dhp = fp->data_first;
	off = 0;
	while (dhp) {
		struct data_hunk *next = dhp->next;
		int i;

		for (i = 0; i < dhp->count; i++)
			data->data[off + i] = dhp->data[i];

		off += dhp->count;

		Z_Free(dhp);

		dhp = next;
	}
	fp->data_first = NULL;
	fp->data_num = 0;

	dp = fp->dst_first;
	while (dp) {
		struct dst_insn *next = dp->next;
		if (dp->jump)
			Z_Free(dp->jump);
		Z_Free(dp);
		dp = next;
	}
	fp->dst_first = fp->dst_last = NULL;
}

void VM_Compile(vm_t *vm, vmHeader_t *header)
{
	struct func_info fi;
	unsigned char *code;
	int i_count, pc, i;

	memset(&fi, 0, sizeof(fi));

	fi.first = Z_Malloc(sizeof(struct src_insn));
	fi.first->next = NULL;

#ifdef __arch64__
	Z_Free(vm->instructionPointers);
	vm->instructionPointers = Z_Malloc(header->instructionCount *
					   sizeof(void *));
#endif

	fi.dst_by_i_count = (struct dst_insn **) vm->instructionPointers;
	memset(fi.dst_by_i_count, 0, header->instructionCount * sizeof(void *));

	vm->compiled = qfalse;

	emit_vm_thunk(&fi);

	code = (unsigned char *) header + header->codeOffset;
	pc = 0;

	for (i_count = 0; i_count < header->instructionCount; i_count++) {
		unsigned char opi, op = code[pc++];
		struct src_insn *sp;

		if (op == OP_CALL || op == OP_BLOCK_COPY)
			fi.has_call = 1;
		opi = vm_opInfo[op];
		if (op == OP_CVIF || op == OP_CVFI ||
		    (op == OP_LEAVE && (opi & opArgF)))
			fi.need_float_tmp = 1;

		if (op == OP_ENTER) {
			if (fi.first->next)
				compile_function(vm, &fi);
			fi.first->next = NULL;
			fi.last = fi.first;
			fi.has_call = fi.need_float_tmp = 0;
		}

		sp = Z_Malloc(sizeof(*sp));
		sp->op = op;
		sp->i_count = i_count;
		sp->arg.i = 0;
		sp->next = NULL;

		if (vm_opInfo[op] & opImm4) {
			union {
				unsigned char b[4];
				unsigned int i;
			} c = { { code[ pc + 3 ], code[ pc + 2 ],
				  code[ pc + 1 ], code[ pc + 0 ] }, };

			sp->arg.i = c.i;
			pc += 4;
		} else if (vm_opInfo[op] & opImm1) {
			sp->arg.b = code[pc++];
		}

		fi.last->next = sp;
		fi.last = sp;
	}
	compile_function(vm, &fi);

	Z_Free(fi.first);

	memset(fi.dst_by_i_count, 0, header->instructionCount * sizeof(void *));
	sparc_compute_code(vm, &fi);

	for (i = 0; i < header->instructionCount; i++) {
		if (!fi.dst_by_i_count[i]) {
			Com_Printf(S_COLOR_RED "Pointer %d not initialized !\n", i);
			DIE("sparc JIT bug");
		}
	}

	if (mprotect(vm->codeBase, vm->codeLength, PROT_READ|PROT_EXEC)) {
		VM_Destroy_Compiled(vm);
		DIE("mprotect failed");
	}

	vm->destroy = VM_Destroy_Compiled;
	vm->compiled = qtrue;
}

int VM_CallCompiled(vm_t *vm, int *args)
{
	vm_data_t *vm_dataAndCode = (void *) vm->codeBase;
	int programStack = vm->programStack;
	int stackOnEntry = programStack;
	byte *image = vm->dataBase;
	int *argPointer;
	int retVal;

	currentVM = vm;

	vm->currentlyInterpreting = qtrue;

	programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS );
	argPointer = (int *)&image[ programStack + 8 ];
	memcpy( argPointer, args, 4 * MAX_VMMAIN_ARGS );
	argPointer[-1] = 0;
	argPointer[-2] = -1;

	/* call generated code */
	{
		int (*entry)(void *, int, void *, int);
		entry = (void *)(vm->codeBase + vm_dataAndCode->dataLength);
		retVal = entry(vm->codeBase, programStack, vm->dataBase, vm->dataMask);
	}

	vm->programStack = stackOnEntry;
	vm->currentlyInterpreting = qfalse;

	return retVal;
}
