/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2005 Ludwig Nussel <ludwig.nussel@web.de>

This file is part of Quake III Arena source code.

Quake III Arena source code 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.

Quake III Arena source code 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 Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
// vm_x86_64.c -- load time compiler and execution environment for x86-64

#include "vm_local.h"
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/time.h>
#include <time.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <stdarg.h>

#include <inttypes.h>

#ifdef __WIN64__
	#include <windows.h>
	#define CROSSCALL __attribute__ ((sysv_abi))//fool the vm we're SYSV ABI
	//#define __USE_MINGW_ANSI_STDIO 1 //very slow - avoid if possible
#else
	#include <sys/mman.h>
	#include <sys/wait.h>
	#define VM_X86_64_MMAP
	#define CROSSCALL
#endif

//#define DEBUG_VM

#ifdef DEBUG_VM
#define Dfprintf(fd, args...) fprintf(fd, ##args)
static FILE* qdasmout;
#else
#define Dfprintf(args...)
#endif

#define VM_FREEBUFFERS(vm) do {assembler_init(0); VM_Destroy_Compiled(vm);} while(0)

void assembler_set_output(char* buf);
size_t assembler_get_code_size(void);
void assembler_init(int pass);
void assemble_line(const char* input, size_t len);

static void VM_Destroy_Compiled(vm_t* self);

/*
 
  |=====================|
  ^       dataMask      ^- programStack rdi
  |
  +- r8

  eax		scratch
  rbx/bl	opStack offset
  ecx		scratch (required for shifts)
  edx		scratch (required for divisions)
  rsi		scratch
  rdi		program frame pointer (programStack)
  r8		pointer data (vm->dataBase)
  r9		opStack base (opStack)
  r10		start of generated code
*/


static intptr_t CROSSCALL callAsmCall(intptr_t callProgramStack, int64_t callSyscallNum)
{
	vm_t *savedVM;
	intptr_t ret = 0x77;
	intptr_t args[16];
//	int iargs[16];
	int i;

//	Dfprintf(stderr, "callAsmCall(%ld, %ld)\n", callProgramStack, callSyscallNum);
//	Com_Printf("-> callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum);

	savedVM = currentVM;

	// save the stack to allow recursive VM entry
	currentVM->programStack = callProgramStack - 4;

	args[0] = callSyscallNum;
//	iargs[0] = callSyscallNum;
	for(i = 0; i < ARRAY_LEN(args)-1; ++i)
	{
//		iargs[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i);
		args[i+1] = *(int *)((byte *)currentVM->dataBase + callProgramStack + 8 + 4*i);
	}
	ret = currentVM->systemCall(args);

 	currentVM = savedVM;
//	Com_Printf("<- callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum);

	return ret;
}

#ifdef DEBUG_VM
static 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 // DEBUG_VM

static unsigned char op_argsize[256] = 
{
	[OP_ENTER]      = 4,
	[OP_LEAVE]      = 4,
	[OP_CONST]      = 4,
	[OP_LOCAL]      = 4,
	[OP_EQ]         = 4,
	[OP_NE]         = 4,
	[OP_LTI]        = 4,
	[OP_LEI]        = 4,
	[OP_GTI]        = 4,
	[OP_GEI]        = 4,
	[OP_LTU]        = 4,
	[OP_LEU]        = 4,
	[OP_GTU]        = 4,
	[OP_GEU]        = 4,
	[OP_EQF]        = 4,
	[OP_NEF]        = 4,
	[OP_LTF]        = 4,
	[OP_LEF]        = 4,
	[OP_GTF]        = 4,
	[OP_GEF]        = 4,
	[OP_ARG]        = 1,
	[OP_BLOCK_COPY] = 4,
};

static __attribute__ ((format (printf, 1, 2))) void emit(const char* fmt, ...)
{
	va_list ap;
	char line[4096];
	va_start(ap, fmt);
	Q_vsnprintf(line, sizeof(line), fmt, ap);
	va_end(ap);
	assemble_line(line, strlen(line));
}

#ifdef DEBUG_VM
#define RANGECHECK(reg, bytes) \
	emit("movl %%" #reg ", %%ecx"); \
	emit("andl $0x%x, %%ecx", vm->dataMask &~(bytes-1)); \
	emit("cmpl %%" #reg ", %%ecx"); \
	emit("jz rc_ok_i_%08x", instruction); \
	emit("movq $%"PRIu64", %%rax", (intptr_t) memviolation); \
	emit("callq *%%rax"); \
	emit("rc_ok_i_%08x:", instruction)
#elif 1
// check is too expensive, so just confine memory access
#define RANGECHECK(reg, bytes) \
	emit("andl $0x%x, %%" #reg, vm->dataMask &~(bytes-1))
#else
#define RANGECHECK(reg, bytes)
#endif

#define STACK_PUSH(bytes) \
	emit("addb $0x%x, %%bl", bytes >> 2); \

#define STACK_POP(bytes) \
	emit("subb $0x%x, %%bl", bytes >> 2); \

#define CHECK_INSTR_REG(reg) \
	emit("cmpl $%u, %%"#reg, header->instructionCount); \
	emit("jb jmp_ok_i_%08x", instruction); \
	emit("movq $%"PRIu64", %%rax", (intptr_t)jmpviolation); \
	emit("callq *%%rax"); \
	emit("jmp_ok_i_%08x:", instruction)

#define PREPARE_JMP(reg) \
	CHECK_INSTR_REG(reg); \
	emit("movq $%"PRIu64", %%rsi", (intptr_t)vm->instructionPointers); \
	emit("movl (%%rsi, %%rax, 8), %%eax"); \
	emit("addq %%r10, %%rax")

#define CHECK_INSTR(nr) \
	do { if(nr < 0 || nr >= header->instructionCount) { \
		VM_FREEBUFFERS(vm); \
		Com_Error( ERR_DROP, \
			"%s: jump target 0x%x out of range at offset %d", __func__, nr, pc ); \
	} } while(0)

#define JMPIARG() \
	CHECK_INSTR(iarg); \
	emit("movq $%"PRIu64", %%rax", vm->codeBase+vm->instructionPointers[iarg]); \
	emit("jmpq *%%rax")
 
#define CONST_OPTIMIZE
#ifdef CONST_OPTIMIZE
#define MAYBE_EMIT_CONST() \
	if (got_const) \
	{ \
		got_const = 0; \
		vm->instructionPointers[instruction-1] = assembler_get_code_size(); \
		STACK_PUSH(4); \
		emit("movl $%d, (%%r9, %%rbx, 4)", const_value); \
	}
#else
#define MAYBE_EMIT_CONST()
#endif

// integer compare and jump
#define IJ(op) \
	MAYBE_EMIT_CONST(); \
	STACK_POP(8); \
	emit("movl 4(%%r9, %%rbx, 4), %%eax"); \
	emit("cmpl 8(%%r9, %%rbx, 4), %%eax"); \
	emit(op " i_%08x", instruction+1); \
	JMPIARG(); \
	neednilabel = 1

#ifdef USE_X87
#define FJ(bits, op) \
	MAYBE_EMIT_CONST(); \
	STACK_POP(8); \
	emit("flds 4(%%r9, %%rbx, 4)");\
	emit("fcomps 8(%%r9, %%rbx, 4)");\
	emit("fnstsw %%ax");\
	emit("testb $" #bits ", %%ah");\
	emit(op " i_%08x", instruction+1);\
	JMPIARG(); \
	neednilabel = 1
#define XJ(x)
#else
#define FJ(x, y)
#define XJ(op) \
	MAYBE_EMIT_CONST(); \
	STACK_POP(8); \
	emit("movss 4(%%r9, %%rbx, 4), %%xmm0");\
	emit("ucomiss 8(%%r9, %%rbx, 4), %%xmm0");\
	emit("jp i_%08x", instruction+1);\
	emit(op " i_%08x", instruction+1);\
	JMPIARG(); \
	neednilabel = 1
#endif

#define SIMPLE(op) \
	MAYBE_EMIT_CONST(); \
	emit("movl (%%r9, %%rbx, 4), %%eax"); \
	STACK_POP(4); \
	emit(op " %%eax, (%%r9, %%rbx, 4)")

#ifdef USE_X87
#define FSIMPLE(op) \
	MAYBE_EMIT_CONST(); \
	STACK_POP(4); \
	emit("flds (%%r9, %%rbx, 4)"); \
	emit(op " 4(%%r9, %%rbx, 4)"); \
	emit("fstps (%%r9, %%rbx, 4)")
#define XSIMPLE(op)
#else
#define FSIMPLE(op)
#define XSIMPLE(op) \
	MAYBE_EMIT_CONST(); \
	STACK_POP(4); \
	emit("movss (%%r9, %%rbx, 4), %%xmm0"); \
	emit(op " 4(%%r9, %%rbx, 4), %%xmm0"); \
	emit("movss %%xmm0, (%%r9, %%rbx, 4)")
#endif

#define SHIFT(op) \
	MAYBE_EMIT_CONST(); \
	STACK_POP(4); \
	emit("movl 4(%%r9, %%rbx, 4), %%ecx"); \
	emit("movl (%%r9, %%rbx, 4), %%eax"); \
	emit(op " %%cl, %%eax"); \
	emit("movl %%eax, (%%r9, %%rbx, 4)")

#ifdef DEBUG_VM
#define NOTIMPL(x) \
	do { Com_Error(ERR_DROP, "instruction not implemented: %s", opnames[x]); } while(0)
#else
#define NOTIMPL(x) \
	do { Com_Printf(S_COLOR_RED "instruction not implemented: %x\n", x); vm->compiled = qfalse; return; } while(0)
#endif

static void* getentrypoint(vm_t* vm)
{
       return vm->codeBase;
}

static __attribute__ ((noreturn)) void CROSSCALL eop(void)
{
	Com_Error(ERR_DROP, "End of program reached without return!");
	exit(1);
}

static __attribute__ ((noreturn)) void CROSSCALL jmpviolation(void)
{
	Com_Error(ERR_DROP, "Program tried to execute code outside VM");
	exit(1);
}

#ifdef DEBUG_VM
static __attribute__ ((noreturn)) void CROSSCALL memviolation(void)
{
	Com_Error(ERR_DROP, "Program tried to access memory outside VM, or unaligned memory access");
	exit(1);
}

static __attribute__ ((noreturn)) void CROSSCALL opstackviolation(void)
{
	Com_Error(ERR_DROP, "Program corrupted the VM opStack");
	exit(1);
}
#endif

/*
=================
VM_Compile
=================
*/
void VM_Compile( vm_t *vm, vmHeader_t *header ) {
	unsigned char op;
	int pc;
	unsigned instruction;
	char* code;
	unsigned iarg = 0;
	unsigned char barg = 0;
	int neednilabel = 0;
	struct timeval tvstart =  {0, 0};
#ifdef DEBUG_VM
	char fn_d[MAX_QPATH]; // disassembled
#endif

	int pass;
	size_t compiledOfs = 0;

	// const optimization
	unsigned got_const = 0, const_value = 0;
	
	vm->codeBase = NULL;

	gettimeofday(&tvstart, NULL);

	for (pass = 0; pass < 2; ++pass) {

	if(pass)
	{
		compiledOfs = assembler_get_code_size();
		vm->codeLength = compiledOfs;

		#ifdef VM_X86_64_MMAP
			vm->codeBase = mmap(NULL, compiledOfs, PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
			if(vm->codeBase == MAP_FAILED)
				Com_Error(ERR_FATAL, "VM_CompileX86_64: can't mmap memory");
		#elif __WIN64__
			// allocate memory with write permissions under windows.
			vm->codeBase = VirtualAlloc(NULL, compiledOfs, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE);
			if(!vm->codeBase)
				Com_Error(ERR_FATAL, "VM_CompileX86_64: VirtualAlloc failed");
		#else
			vm->codeBase = malloc(compiledOfs);
			if(!vm_codeBase)
				Com_Error(ERR_FATAL, "VM_CompileX86_64: Failed to allocate memory");
		#endif

		assembler_set_output((char*)vm->codeBase);
	}

	assembler_init(pass);

#ifdef DEBUG_VM
	strcpy(fn_d,vm->name);
	strcat(fn_d, ".qdasm");

	qdasmout = fopen(fn_d, "w");
#endif

	// translate all instructions
	pc = 0;
	code = (char *)header + header->codeOffset;

	for ( instruction = 0; instruction < header->instructionCount; ++instruction )
	{
		op = code[ pc ];
		++pc;

		vm->instructionPointers[instruction] = assembler_get_code_size();

		/* store current instruction number in r15 for debugging */
#if DEBUG_VM0
		emit("nop");
		emit("movq $%d, %%r15", instruction);
		emit("nop");
#endif

		if(op_argsize[op] == 4)
		{
			iarg = *(int*)(code+pc);
			pc += 4;
			Dfprintf(qdasmout, "%s %8u\n", opnames[op], iarg);
		}
		else if(op_argsize[op] == 1)
		{
			barg = code[pc++];
			Dfprintf(qdasmout, "%s %8hu\n", opnames[op], barg);
		}
		else
		{
			Dfprintf(qdasmout, "%s\n", opnames[op]);
		}

		if(neednilabel)
		{
			emit("i_%08x:", instruction);
			neednilabel = 0;
		}

		switch ( op )
		{
			case OP_UNDEF:
				NOTIMPL(op);
				break;
			case OP_IGNORE:
				MAYBE_EMIT_CONST();
				emit("nop");
				break;
			case OP_BREAK:
				MAYBE_EMIT_CONST();
				emit("int3");
				break;
			case OP_ENTER:
				MAYBE_EMIT_CONST();
				emit("subl $%d, %%edi", iarg);
				break;
			case OP_LEAVE:
				MAYBE_EMIT_CONST();
				emit("addl $%d, %%edi", iarg);          // get rid of stack frame
				emit("ret");
				break;
			case OP_CALL:
				RANGECHECK(edi, 4);
				emit("movl $%d, (%%r8, %%rdi, 1)", instruction+1);  // save next instruction

				if(got_const)
				{
					if ((int) const_value >= 0)
					{
						CHECK_INSTR(const_value);
						emit("movq $%"PRIu64", %%rax", vm->codeBase+vm->instructionPointers[const_value]);
						emit("callq *%%rax");
						got_const = 0;
						break;
					}
				}
				else
				{
					MAYBE_EMIT_CONST();
					emit("movl (%%r9, %%rbx, 4), %%eax");  // get instr from stack
					STACK_POP(4);

					emit("orl %%eax, %%eax");
					emit("jl callSyscall%d", instruction);

					PREPARE_JMP(eax);
					emit("callq *%%rax");

					emit("jmp i_%08x", instruction+1);
					emit("callSyscall%d:", instruction);
				}

//				emit("fnsave 4(%%r9, %%rsi, 1)");
				emit("push %%rdi");
				emit("push %%r8");
				emit("push %%r9");
				emit("push %%r10");
				emit("movq %%rsp, %%rsi"); // we need to align the stack pointer
				emit("subq $8, %%rsi");    //   |
				emit("andq $127, %%rsi");  //   |
				emit("subq %%rsi, %%rsp"); // <-+
				emit("push %%rsi");
				if(got_const) {
					got_const = 0;
					emit("movq $%u, %%rsi", -1-const_value); // second argument in rsi
				} else {
					emit("notl %%eax");        // convert to actual number
					// first argument already in rdi
					emit("movq %%rax, %%rsi"); // second argument in rsi
				}
				emit("movq $%"PRIu64", %%rax", (intptr_t) callAsmCall);
				emit("callq *%%rax");
				emit("pop %%rsi");
				emit("addq %%rsi, %%rsp");
				emit("pop %%r10");
				emit("pop %%r9");
				emit("pop %%r8");
				emit("pop %%rdi");
//				emit("frstor 4(%%r9, %%rsi, 1)");
				STACK_PUSH(4);
				emit("movl %%eax, (%%r9, %%rbx, 4)"); // store return value
				neednilabel = 1;
				break;
			case OP_PUSH:
				MAYBE_EMIT_CONST();
				STACK_PUSH(4);
				break;
			case OP_POP:
				MAYBE_EMIT_CONST();
				STACK_POP(4);
				break;
			case OP_CONST:
				MAYBE_EMIT_CONST();
#ifdef CONST_OPTIMIZE
				got_const = 1;
				const_value = iarg;
#else
				STACK_PUSH(4);
				emit("movl $%d, (%%r9, %%rbx, 4)", iarg);
#endif
				break;
			case OP_LOCAL:
				MAYBE_EMIT_CONST();
				emit("movl %%edi, %%esi");
				emit("addl $%d,%%esi", iarg);
				STACK_PUSH(4);
				emit("movl %%esi, (%%r9, %%rbx, 4)");
				break;
			case OP_JUMP:
				if(got_const) {
					iarg = const_value;
					got_const = 0;
					JMPIARG();
				} else {
					emit("movl (%%r9, %%rbx, 4), %%eax"); // get instr from stack
					STACK_POP(4);

					PREPARE_JMP(eax);
					emit("jmp *%%rax");
				}
				break;
			case OP_EQ:
				IJ("jne");
				break;
			case OP_NE:
				IJ("je");
				break;
			case OP_LTI:
				IJ("jnl");
				break;
			case OP_LEI:
				IJ("jnle");
				break;
			case OP_GTI:
				IJ("jng");
				break;
			case OP_GEI:
				IJ("jnge");
				break;
			case OP_LTU:
				IJ("jnb");
				break;
			case OP_LEU:
				IJ("jnbe");
				break;
			case OP_GTU:
				IJ("jna");
				break;
			case OP_GEU:
				IJ("jnae");
				break;
			case OP_EQF:
				FJ(0x40, "jz");
				XJ("jnz");
				break;
			case OP_NEF:
				FJ(0x40, "jnz");
#ifndef USE_X87
				MAYBE_EMIT_CONST();
				STACK_POP(8);
				emit("movss 4(%%r9, %%rbx, 4), %%xmm0");
				emit("ucomiss 8(%%r9, %%rbx, 4), %%xmm0");
				emit("jp dojump_i_%08x", instruction);
				emit("jz i_%08x", instruction+1);
				emit("dojump_i_%08x:", instruction);
				JMPIARG();
				neednilabel = 1;
#endif
				break;
			case OP_LTF:
				FJ(0x01, "jz");
				XJ("jnc");
				break;
			case OP_LEF:
				FJ(0x41, "jz");
				XJ("ja");
				break;
			case OP_GTF:
				FJ(0x41, "jnz");
				XJ("jbe");
				break;
			case OP_GEF:
				FJ(0x01, "jnz");
				XJ("jb");
				break;
			case OP_LOAD1:
				MAYBE_EMIT_CONST();
				emit("movl (%%r9, %%rbx, 4), %%eax"); // get value from stack
				RANGECHECK(eax, 1);
				emit("movb (%%r8, %%rax, 1), %%al"); // deref into eax
				emit("andq $255, %%rax");
				emit("movl %%eax, (%%r9, %%rbx, 4)"); // store on stack
				break;
			case OP_LOAD2:
				MAYBE_EMIT_CONST();
				emit("movl (%%r9, %%rbx, 4), %%eax"); // get value from stack
				RANGECHECK(eax, 2);
				emit("movw (%%r8, %%rax, 1), %%ax"); // deref into eax
				emit("movl %%eax, (%%r9, %%rbx, 4)"); // store on stack
				break;
			case OP_LOAD4:
				MAYBE_EMIT_CONST();
				emit("movl (%%r9, %%rbx, 4), %%eax"); // get value from stack
				RANGECHECK(eax, 4); // not a pointer!?
				emit("movl (%%r8, %%rax, 1), %%eax"); // deref into eax
				emit("movl %%eax, (%%r9, %%rbx, 4)"); // store on stack
				break;
			case OP_STORE1:
				MAYBE_EMIT_CONST();
				emit("movl (%%r9, %%rbx, 4), %%eax"); // get value from stack
				STACK_POP(8);
				emit("andq $255, %%rax");
				emit("movl 4(%%r9, %%rbx, 4), %%esi"); // get pointer from stack
				RANGECHECK(esi, 1);
				emit("movb %%al, (%%r8, %%rsi, 1)"); // store in memory
				break;
			case OP_STORE2:
				MAYBE_EMIT_CONST();
				emit("movl (%%r9, %%rbx, 4), %%eax"); // get value from stack
				STACK_POP(8);
				emit("movl 4(%%r9, %%rbx, 4), %%esi"); // get pointer from stack
				RANGECHECK(esi, 2);
				emit("movw %%ax, (%%r8, %%rsi, 1)"); // store in memory
				break;
			case OP_STORE4:
				MAYBE_EMIT_CONST();
				emit("movl (%%r9, %%rbx, 4), %%eax"); // get value from stack
				STACK_POP(8);
				emit("movl 4(%%r9, %%rbx, 4), %%esi"); // get pointer from stack
				RANGECHECK(esi, 4);
				emit("movl %%eax, (%%r8, %%rsi, 1)"); // store in memory
				break;
			case OP_ARG:
				MAYBE_EMIT_CONST();
				emit("movl (%%r9, %%rbx, 4), %%eax"); // get value from stack
				STACK_POP(4);
				emit("movl $0x%hx, %%esi", barg);
				emit("addl %%edi, %%esi");
				RANGECHECK(esi, 4);
				emit("movl %%eax, (%%r8,%%rsi, 1)"); // store in args space
				break;
			case OP_BLOCK_COPY:

				MAYBE_EMIT_CONST();
				STACK_POP(8);
				emit("push %%rdi");
				emit("push %%r8");
				emit("push %%r9");
				emit("push %%r10");
				emit("movq %%rsp, %%rsi"); // we need to align the stack pointer
				emit("subq $8, %%rsi");    //   |
				emit("andq $127, %%rsi");  //   |
				emit("subq %%rsi, %%rsp"); // <-+
				emit("push %%rsi");
				emit("movl 4(%%r9, %%rbx, 4), %%edi");  // 1st argument dest
				emit("movl 8(%%r9, %%rbx, 4), %%rsi");  // 2nd argument src
				emit("movl $%d, %%edx", iarg); // 3rd argument count
				emit("movq $%"PRIu64", %%rax", (intptr_t) VM_BlockCopy);
				emit("callq *%%rax");
				emit("pop %%rsi");
				emit("addq %%rsi, %%rsp");
				emit("pop %%r10");
				emit("pop %%r9");
				emit("pop %%r8");
				emit("pop %%rdi");

				break;
			case OP_SEX8:
				MAYBE_EMIT_CONST();
				emit("movw (%%r9, %%rbx, 4), %%ax");
				emit("andq $255, %%rax");
				emit("cbw");
				emit("cwde");
				emit("movl %%eax, (%%r9, %%rbx, 4)");
				break;
			case OP_SEX16:
				MAYBE_EMIT_CONST();
				emit("movw (%%r9, %%rbx, 4), %%ax");
				emit("cwde");
				emit("movl %%eax, (%%r9, %%rbx, 4)");
				break;
			case OP_NEGI:
				MAYBE_EMIT_CONST();
				emit("negl (%%r9, %%rbx, 4)");
				break;
			case OP_ADD:
				SIMPLE("addl");
				break;
			case OP_SUB:
				SIMPLE("subl");
				break;
			case OP_DIVI:
				MAYBE_EMIT_CONST();
				STACK_POP(4);
				emit("movl (%%r9, %%rbx, 4), %%eax");
				emit("cdq");
				emit("idivl 4(%%r9, %%rbx, 4)");
				emit("movl %%eax, (%%r9, %%rbx, 4)");
				break;
			case OP_DIVU:
				MAYBE_EMIT_CONST();
				STACK_POP(4);
				emit("movl (%%r9, %%rbx, 4), %%eax");
				emit("xorq %%rdx, %%rdx");
				emit("divl 4(%%r9, %%rbx, 4)");
				emit("movl %%eax, (%%r9, %%rbx, 4)");
				break;
			case OP_MODI:
				MAYBE_EMIT_CONST();
				STACK_POP(4);
				emit("movl (%%r9, %%rbx, 4), %%eax");
				emit("xorl %%edx, %%edx");
				emit("cdq");
				emit("idivl 4(%%r9, %%rbx, 4)");
				emit("movl %%edx, (%%r9, %%rbx, 4)");
				break;
			case OP_MODU:
				MAYBE_EMIT_CONST();
				STACK_POP(4);
				emit("movl (%%r9, %%rbx, 4), %%eax");
				emit("xorl %%edx, %%edx");
				emit("divl 4(%%r9, %%rbx, 4)");
				emit("movl %%edx, (%%r9, %%rbx, 4)");
				break;
			case OP_MULI:
				MAYBE_EMIT_CONST();
				STACK_POP(4);
				emit("movl (%%r9, %%rbx, 4), %%eax");
				emit("imull 4(%%r9, %%rbx, 4)");
				emit("movl %%eax, (%%r9, %%rbx, 4)");
				break;
			case OP_MULU:
				MAYBE_EMIT_CONST();
				STACK_POP(4);
				emit("movl (%%r9, %%rbx, 4), %%eax");
				emit("mull 4(%%r9, %%rbx, 4)");
				emit("movl %%eax, (%%r9, %%rbx, 4)");
				break;
			case OP_BAND:
				SIMPLE("andl");
				break;
			case OP_BOR:
				SIMPLE("orl");
				break;
			case OP_BXOR:
				SIMPLE("xorl");
				break;
			case OP_BCOM:
				MAYBE_EMIT_CONST();
				emit("notl (%%r9, %%rbx, 4)");
				break;
			case OP_LSH:
				SHIFT("shl");
				break;
			case OP_RSHI:
				SHIFT("sarl");
				break;
			case OP_RSHU:
				SHIFT("shrl");
				break;
			case OP_NEGF:
				MAYBE_EMIT_CONST();
#ifdef USE_X87
				emit("flds (%%r9, %%rbx, 4)");
				emit("fchs");
				emit("fstps (%%r9, %%rbx, 4)");
#else
				emit("movl $0x80000000, %%eax");
				emit("xorl %%eax, (%%r9, %%rbx, 4)");
#endif
				break;
			case OP_ADDF:
				FSIMPLE("fadds");
				XSIMPLE("addss");
				break;
			case OP_SUBF:
				FSIMPLE("fsubs");
				XSIMPLE("subss");
				break;
			case OP_DIVF:
				FSIMPLE("fdivs");
				XSIMPLE("divss");
				break;
			case OP_MULF:
				FSIMPLE("fmuls");
				XSIMPLE("mulss");
				break;
			case OP_CVIF:
				MAYBE_EMIT_CONST();
#ifdef USE_X87
				emit("filds (%%r9, %%rbx, 4)");
				emit("fstps (%%r9, %%rbx, 4)");
#else
				emit("movl (%%r9, %%rbx, 4), %%eax");
				emit("cvtsi2ss %%eax, %%xmm0");
				emit("movss %%xmm0, (%%r9, %%rbx, 4)");
#endif
				break;
			case OP_CVFI:
				MAYBE_EMIT_CONST();
#ifdef USE_X87
				emit("flds (%%r9, %%rbx, 4)");
				emit("fnstcw 4(%%r9, %%rbx, 4)");
				emit("movw $0x0F7F, 8(%%r9, %%rbx, 4)"); // round toward zero
				emit("fldcw 8(%%r9, %%rbx, 4)");
				emit("fistpl (%%r9, %%rbx, 4)");
				emit("fldcw 4(%%r9, %%rbx, 4)");
#else
				emit("movss (%%r9, %%rbx, 4), %%xmm0");
				emit("cvttss2si %%xmm0, %%eax");
				emit("movl %%eax, (%%r9, %%rbx, 4)");
#endif
				break;
			default:
				NOTIMPL(op);
				break;
		}


	}

	if(got_const)
	{
		VM_FREEBUFFERS(vm);
		Com_Error(ERR_DROP, "leftover const");
	}

	emit("movq $%"PRIu64", %%rax", (intptr_t) eop);
	emit("callq *%%rax");

	} // pass loop

	assembler_init(0);

	#ifdef VM_X86_64_MMAP
		if(mprotect(vm->codeBase, compiledOfs, PROT_READ|PROT_EXEC))
			Com_Error(ERR_FATAL, "VM_CompileX86_64: mprotect failed");
	#elif __WIN64__
		{
			DWORD oldProtect = 0;
			
			// remove write permissions; give exec permision
			if(!VirtualProtect(vm->codeBase, compiledOfs, PAGE_EXECUTE_READ, &oldProtect))
				Com_Error(ERR_FATAL, "VM_CompileX86_64: VirtualProtect failed");
		}
	#endif

	vm->destroy = VM_Destroy_Compiled;

#ifdef DEBUG_VM
	fflush(qdasmout);
	fclose(qdasmout);

#if 0
	strcpy(fn_d,vm->name);
	strcat(fn_d, ".bin");
	qdasmout = fopen(fn_d, "w");
	fwrite(vm->codeBase, compiledOfs, 1, qdasmout);
	fflush(qdasmout);
	fclose(qdasmout);
#endif
#endif

	#ifndef __WIN64__ //timersub and gettimeofday
		if(vm->compiled && com_developer->integer)
		{
			struct timeval tvdone =  {0, 0};
			struct timeval dur =  {0, 0};
			Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n", vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength );

			gettimeofday(&tvdone, NULL);
			timersub(&tvdone, &tvstart, &dur);
			Com_Printf( "compilation took %"PRIu64".%06"PRIu64" seconds\n", dur.tv_sec, dur.tv_usec );
		}
	#endif
}


void VM_Destroy_Compiled(vm_t* self)
{
	if(self && self->codeBase)
	{
#ifdef VM_X86_64_MMAP
		munmap(self->codeBase, self->codeLength);
#elif __WIN64__
		VirtualFree(self->codeBase, 0, MEM_RELEASE);
#else
		free(self->codeBase);
#endif
	}
}

/*
==============
VM_CallCompiled

This function is called directly by the generated code
==============
*/

#ifdef DEBUG_VM
static char* memData;
#endif

int VM_CallCompiled(vm_t *vm, int *args)
{
	int stack[OPSTACK_SIZE + 15];
	int		programCounter;
	int		programStack;
	int		stackOnEntry;
	long		opStackRet;
	byte	*image;
	void	*entryPoint;
	int	*opStack;

	currentVM = vm;
	
//	Com_Printf("entering %s level %d, call %d, arg1 = 0x%x\n", vm->name, vm->callLevel, args[0], args[1]);

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

//	callMask = vm->dataMask;

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

	// set up the stack frame 
	image = vm->dataBase;
#ifdef DEBUG_VM
	memData = (char*)image;
#endif

	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 ] = 0x77777777;	// return stack
	*(int *)&image[ programStack ] = -1;	// will terminate the loop on return

	// off we go into generated code...
	entryPoint = getentrypoint(vm);
	opStack = PADP(stack, 16);

	*opStack = 0xDEADBEEF;
	opStackRet = 0;

	__asm__ __volatile__ (
		"	movq %4,%%r8		\r\n" \
		"	movq %3,%%r9		\r\n" \
		"	movq %2,%%r10		\r\n" \
		"	push %%r15		\r\n" \
		"	push %%r14		\r\n" \
		"	push %%r13		\r\n" \
		"	push %%r12		\r\n" \
		"       subq $24, %%rsp # fix alignment as call pushes one value \r\n" \
		"	callq *%%r10		\r\n" \
		"       addq $24, %%rsp         \r\n" \
		"	pop %%r12		\r\n" \
		"	pop %%r13		\r\n" \
		"	pop %%r14		\r\n" \
		"	pop %%r15		\r\n"
		: "+D" (programStack), "+b" (opStackRet)
		: "g" (entryPoint), "g" (opStack), "g" (vm->dataBase), "g" (programStack)
		: "%rsi", "%rax", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r11", "%xmm0"
	);

	if(opStackRet != 1 || *opStack != 0xDEADBEEF)
		Com_Error(ERR_DROP, "opStack corrupted in compiled code (offset %ld)", opStackRet);

	if ( programStack != stackOnEntry - 48 ) {
		Com_Error( ERR_DROP, "programStack corrupted in compiled code" );
	}

//	Com_Printf("exiting %s level %d\n", vm->name, vm->callLevel);
	vm->programStack = stackOnEntry;

	return stack[1];
}
