//**************************************************************************
//**
//**	See jlquake.txt for copyright info.
//**
//**	This program 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.
//**
//**	This program 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
//**  included (gnu.txt) GNU General Public License for more details.
//**
//**************************************************************************

#include "../core.h"
#include "local.h"

/*
Upon a system call, the stack will look like:

sp+32	parm1
sp+28	parm0
sp+24	return stack
sp+20	return address
sp+16	local1
sp+14	local0
sp+12	arg1
sp+8	arg0
sp+4	return stack
sp		return address

An interpreted function will immediately execute
an OP_ENTER instruction, which will subtract space for
locals from sp
*/

#define MAX_STACK	256
#define STACK_MASK	(MAX_STACK - 1)
//#define DEBUG_VM

#define DEBUGSTR va("%s%i", VM_Indent(vm), (int)(opStack - stack))

#ifdef DEBUG_VM
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"
};
#endif

#if idppc
#if defined(__GNUC__)
static inline unsigned int loadWord(void* addr)
{
	unsigned int word;

	asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr));
	return word;
}
#else
#define loadWord(addr) __lwbrx(addr,0)
#endif
#else
#define	loadWord(addr) *((int *)addr)
#endif

void VM_PrepareInterpreter(vm_t* vm, vmHeader_t* header)
{
	vm->codeBase = new byte[vm->codeLength * 4];			// we're now int aligned
	Com_Memset(vm->codeBase, 0, vm->codeLength * 4);

	// we don't need to translate the instructions, but we still need
	// to find each instructions starting point for jumps
	int pc = 0;
	int instruction = 0;
	byte* code = (byte*)header + header->codeOffset;
	int* codeBase = (int*)vm->codeBase;

	while (instruction < header->instructionCount)
	{
		vm->instructionPointers[instruction] = pc;
		instruction++;

		int op = code[pc];
		codeBase[pc] = op;
		if (pc > header->codeLength)
		{
			throw Exception("VM_PrepareInterpreter: pc > header->codeLength");
		}

		pc++;

		// these are the only opcodes that aren't a single byte
		switch (op)
		{
		case OP_ENTER:
		case OP_CONST:
		case OP_LOCAL:
		case OP_LEAVE:
		case OP_EQ:
		case OP_NE:
		case OP_LTI:
		case OP_LEI:
		case OP_GTI:
		case OP_GEI:
		case OP_LTU:
		case OP_LEU:
		case OP_GTU:
		case OP_GEU:
		case OP_EQF:
		case OP_NEF:
		case OP_LTF:
		case OP_LEF:
		case OP_GTF:
		case OP_GEF:
		case OP_BLOCK_COPY:
			codeBase[pc + 0] = loadWord(&code[pc]);
			pc += 4;
			break;
		case OP_ARG:
			codeBase[pc + 0] = code[pc];
			pc += 1;
			break;
		default:
			break;
		}
	}

	pc = 0;
	instruction = 0;
	code = (byte*)header + header->codeOffset;
	codeBase = (int*)vm->codeBase;

	while (instruction < header->instructionCount)
	{
		int op = code[pc];
		instruction++;
		pc++;
		switch (op)
		{
		case OP_EQ:
		case OP_NE:
		case OP_LTI:
		case OP_LEI:
		case OP_GTI:
		case OP_GEI:
		case OP_LTU:
		case OP_LEU:
		case OP_GTU:
		case OP_GEU:
		case OP_EQF:
		case OP_NEF:
		case OP_LTF:
		case OP_LEF:
		case OP_GTF:
		case OP_GEF:
			codeBase[pc] = vm->instructionPointers[codeBase[pc]];
			pc += 4;
			break;
		case OP_ENTER:
		case OP_CONST:
		case OP_LOCAL:
		case OP_LEAVE:
		case OP_BLOCK_COPY:
			pc += 4;
			break;
		case OP_ARG:
			pc += 1;
			break;
		default:
			break;
		}
	}
}

#ifdef DEBUG_VM
static const char* VM_Indent(vm_t* vm)
{
	static const char* string = "                                        ";
	if (vm->callLevel > 20)
	{
		return string;
	}
	return string + 2 * (20 - vm->callLevel);
}

static void VM_StackTrace(vm_t* vm, int programCounter, int programStack)
{
	int count = 0;
	do
	{
		Log::write("%s\n", VM_ValueToSymbol(vm, programCounter));
		programStack =  *(int*)&vm->dataBase[programStack + 4];
		programCounter = *(int*)&vm->dataBase[programStack];
	}
	while (programCounter != -1 && ++count < 32);
}
#endif

int VM_CallInterpreted(vm_t* vm, int* args)
{
	int		v1;
#ifdef DEBUG_VM
	vmSymbol_t* profileSymbol;
#endif

	// interpret the code
	vm->currentlyInterpreting = true;

	// we might be called recursively, so this might not be the very top
	int programStack = vm->programStack;
	int stackOnEntry = vm->programStack;

#ifdef DEBUG_VM
	profileSymbol = VM_ValueToFunctionSymbol(vm, 0);
	// uncomment this for debugging breakpoints
	vm->breakFunction = 0;
#endif
	// set up the stack frame 

	byte* image = vm->dataBase;
	int* codeImage = (int*)vm->codeBase;
	int dataMask = vm->dataMask;

	// leave a free spot at start of stack so
	// that as long as opStack is valid, opStack-1 will
	// not corrupt anything
	int stack[MAX_STACK];
	int* opStack = stack;
	int programCounter = 0;

	programStack -= 48;

	*(int*)&image[programStack + 44] = args[9];
	*(int*)&image[programStack + 40] = args[8];
	*(int*)&image[programStack + 36] = args[7];
	*(int*)&image[programStack + 32] = args[6];
	*(int*)&image[programStack + 28] = args[5];
	*(int*)&image[programStack + 24] = args[4];
	*(int*)&image[programStack + 20] = args[3];
	*(int*)&image[programStack + 16] = args[2];
	*(int*)&image[programStack + 12] = args[1];
	*(int*)&image[programStack + 8] = args[0];
	*(int*)&image[programStack + 4] = 0;	// return stack
	*(int*)&image[programStack] = -1;	// will terminate the loop on return

	vm->callLevel = 0;

	VM_Debug(0);

//	vm_debugLevel=2;
	// main interpreter loop, will exit when a LEAVE instruction
	// grabs the -1 program counter

#define r2 codeImage[programCounter]

	while (1)
	{
nextInstruction:
		int r0 = ((int *)opStack)[0];
		int r1 = ((int *)opStack)[-1];
nextInstruction2:
		int opcode = codeImage[programCounter++];
#ifdef DEBUG_VM
		if ((unsigned)programCounter > vm->codeLength)
		{
			throw DropException("VM pc out of range");
		}

		if (opStack < stack)
		{
			throw DropException("VM opStack underflow");
		}
		if (opStack >= stack+MAX_STACK)
		{
			throw DropException("VM opStack overflow");
		}

		if (programStack <= vm->stackBottom)
		{
			throw DropException("VM stack overflow");
		}

		if (programStack & 3)
		{
			throw DropException("VM program stack misaligned");
		}

		if (vm_debugLevel > 1)
		{
			Log::write("%s %s\n", DEBUGSTR, opnames[opcode]);
		}
		profileSymbol->profileCount++;
#endif

		switch (opcode)
		{
#ifdef DEBUG_VM
		default:
			throw DropException("Bad VM instruction");  // this should be scanned on load!
#endif
		case OP_BREAK:
			vm->breakCount++;
			goto nextInstruction2;

		case OP_CONST:
			opStack++;
			r1 = r0;
			r0 = *opStack = r2;
			programCounter += 4;
			goto nextInstruction2;

		case OP_LOCAL:
			opStack++;
			r1 = r0;
			r0 = *opStack = r2 + programStack;
			programCounter += 4;
			goto nextInstruction2;

		case OP_LOAD4:
#ifdef DEBUG_VM
			if (*opStack & 3)
			{
				throw DropException("OP_LOAD4 misaligned");
			}
#endif
			r0 = *opStack = *(int*)&image[r0 & dataMask];
			goto nextInstruction2;

		case OP_LOAD2:
			r0 = *opStack = *(unsigned short*)&image[r0 & dataMask];
			goto nextInstruction2;

		case OP_LOAD1:
			r0 = *opStack = image[r0 & dataMask];
			goto nextInstruction2;

		case OP_STORE4:
			*(int*)&image[r1 & (dataMask & ~3)] = r0;
			opStack -= 2;
			goto nextInstruction;

		case OP_STORE2:
			*(short*)&image[r1 & (dataMask & ~1)] = r0;
			opStack -= 2;
			goto nextInstruction;

		case OP_STORE1:
			image[r1 & dataMask] = r0;
			opStack -= 2;
			goto nextInstruction;

		case OP_ARG:
			// single byte offset from programStack
			*(int*)&image[codeImage[programCounter] + programStack] = r0;
			opStack--;
			programCounter += 1;
			goto nextInstruction;

		case OP_BLOCK_COPY:
			{
				int count = r2;
				// MrE: copy range check
				int srci = r0 & dataMask;
				int desti = r1 & dataMask;
				count = ((srci + count) & dataMask) - srci;
				count = ((desti + count) & dataMask) - desti;

				int* src = (int*)&image[r0 & dataMask];
				int* dest = (int*)&image[r1 & dataMask];
				if (((qintptr)src | (qintptr)dest | count) & 3)
				{
					throw DropException("OP_BLOCK_COPY not dword aligned");
				}
				count >>= 2;
				for (int i = count - 1; i >= 0; i--)
				{
					dest[i] = src[i];
				}
				programCounter += 4;
				opStack -= 2;
			}
			goto nextInstruction;

		case OP_CALL:
			// save current program counter
			*(int*)&image[programStack] = programCounter;
			
			// jump to the location on the stack
			programCounter = r0;
			opStack--;
			if (programCounter < 0)
			{
				// system call
				int		r;
				int		temp;
#ifdef DEBUG_VM
				if (vm_debugLevel)
				{
					Log::write("%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter);
				}
#endif
				// save the stack to allow recursive VM entry
				temp = vm->callLevel;
				vm->programStack = programStack - 4;
#ifdef DEBUG_VM
				int stomped = *(int*)&image[programStack + 4];
#endif
				*(int*)&image[programStack + 4] = -1 - programCounter;

				//VM_LogSyscalls((int*)&image[programStack + 4]);
				r = vm->systemCall((int*)&image[programStack + 4]);

#ifdef DEBUG_VM
				// this is just our stack frame pointer, only needed
				// for debugging
				*(int*)&image[programStack + 4] = stomped;
#endif

				// save return value
				opStack++;
				*opStack = r;
				programCounter = *(int*)&image[programStack];
				vm->callLevel = temp;
#ifdef DEBUG_VM
				if (vm_debugLevel)
				{
					Log::write("%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol(vm, programCounter));
				}
#endif
			}
			else
			{
				programCounter = vm->instructionPointers[programCounter];
			}
			goto nextInstruction;

		// push and pop are only needed for discarded or bad function return values
		case OP_PUSH:
			opStack++;
			goto nextInstruction;

		case OP_POP:
			opStack--;
			goto nextInstruction;

		case OP_ENTER:
#ifdef DEBUG_VM
			profileSymbol = VM_ValueToFunctionSymbol(vm, programCounter);
#endif
			// get size of stack frame
			v1 = r2;

			programCounter += 4;
			programStack -= v1;
#ifdef DEBUG_VM
			// save old stack frame for debugging traces
			*(int*)&image[programStack + 4] = programStack + v1;
			if (vm_debugLevel)
			{
				Log::write("%s---> %s\n", DEBUGSTR, VM_ValueToSymbol(vm, programCounter - 5));
				if (vm->breakFunction && programCounter - 5 == vm->breakFunction)
				{
					// this is to allow setting breakpoints here in the debugger
					vm->breakCount++;
//					vm_debugLevel = 2;
//					VM_StackTrace( vm, programCounter, programStack );
				}
				vm->callLevel++;
			}
#endif
			goto nextInstruction;

		case OP_LEAVE:
			// remove our stack frame
			v1 = r2;

			programStack += v1;

			// grab the saved program counter
			programCounter = *(int*)&image[programStack];
#ifdef DEBUG_VM
			profileSymbol = VM_ValueToFunctionSymbol(vm, programCounter);
			if (vm_debugLevel)
			{
				vm->callLevel--;
				Log::write("%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol(vm, programCounter));
			}
#endif
			// check for leaving the VM
			if (programCounter == -1)
			{
				goto done;
			}
			goto nextInstruction;

		/*
		===================================================================
		BRANCHES
		===================================================================
		*/

		case OP_JUMP:
			programCounter = r0;
			programCounter = vm->instructionPointers[programCounter];
			opStack--;
			goto nextInstruction;

		case OP_EQ:
			opStack -= 2;
			if (r1 == r0)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_NE:
			opStack -= 2;
			if (r1 != r0)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_LTI:
			opStack -= 2;
			if (r1 < r0)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_LEI:
			opStack -= 2;
			if (r1 <= r0)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_GTI:
			opStack -= 2;
			if (r1 > r0)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_GEI:
			opStack -= 2;
			if (r1 >= r0)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_LTU:
			opStack -= 2;
			if (((unsigned)r1) < ((unsigned)r0))
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_LEU:
			opStack -= 2;
			if (((unsigned)r1) <= ((unsigned)r0))
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_GTU:
			opStack -= 2;
			if (((unsigned)r1) > ((unsigned)r0))
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_GEU:
			opStack -= 2;
			if (((unsigned)r1) >= ((unsigned)r0))
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				goto nextInstruction;
			}

		case OP_EQF:
			if (((float*)opStack)[-1] == *(float*)opStack)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				opStack -= 2;
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				opStack -= 2;
				goto nextInstruction;
			}

		case OP_NEF:
			if (((float*)opStack)[-1] != *(float *)opStack)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				opStack -= 2;
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				opStack -= 2;
				goto nextInstruction;
			}

		case OP_LTF:
			if (((float*)opStack)[-1] < *(float *)opStack)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				opStack -= 2;
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				opStack -= 2;
				goto nextInstruction;
			}

		case OP_LEF:
			if (((float*)opStack)[-1] <= *(float *)opStack)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				opStack -= 2;
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				opStack -= 2;
				goto nextInstruction;
			}

		case OP_GTF:
			if (((float*)opStack)[-1] > *(float*)opStack)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				opStack -= 2;
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				opStack -= 2;
				goto nextInstruction;
			}

		case OP_GEF:
			if (((float*)opStack)[-1] >= *(float*)opStack)
			{
				programCounter = r2;	//vm->instructionPointers[r2];
				opStack -= 2;
				goto nextInstruction;
			}
			else
			{
				programCounter += 4;
				opStack -= 2;
				goto nextInstruction;
			}


		//===================================================================

		case OP_NEGI:
			*opStack = -r0;
			goto nextInstruction;

		case OP_ADD:
			opStack[-1] = r1 + r0;
			opStack--;
			goto nextInstruction;

		case OP_SUB:
			opStack[-1] = r1 - r0;
			opStack--;
			goto nextInstruction;

		case OP_DIVI:
			opStack[-1] = r1 / r0;
			opStack--;
			goto nextInstruction;

		case OP_DIVU:
			opStack[-1] = ((unsigned)r1) / ((unsigned)r0);
			opStack--;
			goto nextInstruction;

		case OP_MODI:
			opStack[-1] = r1 % r0;
			opStack--;
			goto nextInstruction;

		case OP_MODU:
			opStack[-1] = ((unsigned)r1) % (unsigned)r0;
			opStack--;
			goto nextInstruction;

		case OP_MULI:
			opStack[-1] = r1 * r0;
			opStack--;
			goto nextInstruction;

		case OP_MULU:
			opStack[-1] = ((unsigned)r1) * ((unsigned)r0);
			opStack--;
			goto nextInstruction;

		case OP_BAND:
			opStack[-1] = ((unsigned)r1) & ((unsigned)r0);
			opStack--;
			goto nextInstruction;

		case OP_BOR:
			opStack[-1] = ((unsigned)r1) | ((unsigned)r0);
			opStack--;
			goto nextInstruction;

		case OP_BXOR:
			opStack[-1] = ((unsigned)r1) ^ ((unsigned)r0);
			opStack--;
			goto nextInstruction;

		case OP_BCOM:
			opStack[-1] = ~ ((unsigned)r0);
			goto nextInstruction;

		case OP_LSH:
			opStack[-1] = r1 << r0;
			opStack--;
			goto nextInstruction;

		case OP_RSHI:
			opStack[-1] = r1 >> r0;
			opStack--;
			goto nextInstruction;

		case OP_RSHU:
			opStack[-1] = ((unsigned)r1) >> r0;
			opStack--;
			goto nextInstruction;

		case OP_NEGF:
			*(float*)opStack =  -*(float*)opStack;
			goto nextInstruction;

		case OP_ADDF:
			*(float *)(opStack-1) = *(float*)(opStack - 1) + *(float*)opStack;
			opStack--;
			goto nextInstruction;

		case OP_SUBF:
			*(float *)(opStack-1) = *(float*)(opStack - 1) - *(float*)opStack;
			opStack--;
			goto nextInstruction;

		case OP_DIVF:
			*(float *)(opStack-1) = *(float*)(opStack - 1) / *(float*)opStack;
			opStack--;
			goto nextInstruction;

		case OP_MULF:
			*(float *)(opStack-1) = *(float*)(opStack - 1) * *(float*)opStack;
			opStack--;
			goto nextInstruction;

		case OP_CVIF:
			*(float *)opStack =  (float)*opStack;
			goto nextInstruction;

		case OP_CVFI:
			*opStack = (int)*(float*)opStack;
			goto nextInstruction;

		case OP_SEX8:
			*opStack = (signed char)*opStack;
			goto nextInstruction;

		case OP_SEX16:
			*opStack = (short)*opStack;
			goto nextInstruction;
		}
	}

done:
	vm->currentlyInterpreting = false;

	if (opStack != &stack[1])
	{
		throw DropException(va("Interpreter error: opStack = %i", (int)(opStack - stack)));
	}

	vm->programStack = stackOnEntry;

	// return the result
	return *opStack;
}
