/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2005 Ludwig Nussel <ludwig.nussel@web.de>
Copyright (C) 2006 Robert Beckebans <trebor_7@users.sourceforge.net>

This file is part of XreaL source code.

XreaL 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.

XreaL 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 XreaL 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/mman.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

#define Dfprintf(args...)

/*
  eax   scratch
  ebx   scratch
  ecx   scratch (required for shifts)
  edx   scratch (required for divisions)
  rsi   stack pointer
  rdi   program frame pointer
  r8    pointer to begin of real stack memory
  r9    return address to real program
  r10   start of generated code
*/

static long callAsmCall(long callProgramStack, long callSyscallNum)
{
        vm_t *savedVM;
        long ret = 0x77;
        long args[11];
        int iargs[11];
        int i;

        savedVM = currentVM;

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

        args[0] = callSyscallNum;
        iargs[0] = callSyscallNum;
        for(i = 0; i < 10; ++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;

        return ret;
}

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,
};

#define emit(x...) \
        do { FS_Printf(fh_s, ##x); FS_Write("\n", 1, fh_s); } while(0)

// integer compare and jump
#define IJ(op) \
        emit("subq $8, %%rsi"); \
        emit("movl 4(%%rsi), %%eax"); \
        emit("cmpl 8(%%rsi), %%eax"); \
        emit(op " i_%08x", instruction+1); \
        emit("jmp i_%08x", iarg);

#ifdef USE_X87
#define FJ(bits, op) \
        emit("subq $8, %%rsi");\
        emit("flds 4(%%rsi)");\
        emit("fcomps 8(%%rsi)");\
        emit("fnstsw %%ax");\
        emit("testb $" #bits ", %%ah");\
        emit(op " i_%08x", instruction+1);\
        emit("jmp i_%08x", iarg);
#define XJ(x)
#else
#define FJ(x, y)
#define XJ(op) \
        emit("subq $8, %%rsi");\
        emit("movss 4(%%rsi), %%xmm0");\
        emit("ucomiss 8(%%rsi), %%xmm0");\
        emit("jp i_%08x", instruction+1);\
        emit(op " i_%08x", instruction+1);\
        emit("jmp i_%08x", iarg);
#endif

#define SIMPLE(op) \
        emit("subq $4, %%rsi"); \
        emit("movl 4(%%rsi), %%eax"); \
        emit(op " %%eax, 0(%%rsi)");

#ifdef USE_X87
#define FSIMPLE(op) \
        emit("subq $4, %%rsi"); \
        emit("flds 0(%%rsi)"); \
        emit(op " 4(%%rsi)"); \
        emit("fstps 0(%%rsi)");
#define XSIMPLE(op)
#else
#define FSIMPLE(op)
#define XSIMPLE(op) \
        emit("subq $4, %%rsi"); \
        emit("movss 0(%%rsi), %%xmm0"); \
        emit(op " 4(%%rsi), %%xmm0"); \
        emit("movss %%xmm0, 0(%%rsi)");
#endif

#define SHIFT(op) \
        emit("subq $4, %%rsi"); \
        emit("movl 4(%%rsi), %%ecx"); \
        emit("movl 0(%%rsi), %%eax"); \
        emit(op " %%cl, %%eax"); \
        emit("movl %%eax, 0(%%rsi)");

#if 1
#define RANGECHECK(reg) \
        emit("andl $0x%x, %%" #reg, vm->dataMask);
#else
#define RANGECHECK(reg)
#endif

#define NOTIMPL(x) \
        do { Com_Error(ERR_DROP, "instruction not implemented: %x\n", x); } while(0)

static void* getentrypoint(vm_t* vm)
{
       return vm->codeBase+64; // skip ELF header
}

char* mmapfile(const char* fn, size_t* size)
{
        int fd = -1;
        char* mem = NULL;
        struct stat stb;

        fd = open(fn, O_RDONLY);
        if(fd == -1)
                goto out;

        if(fstat(fd, &stb) == -1)
                goto out;

        *size = stb.st_size;

        mem = mmap(NULL, stb.st_size, PROT_READ|PROT_EXEC, MAP_SHARED, fd, 0);
        if(mem == (void*)-1)
                mem = NULL;

out:
        if(fd != -1)
                close(fd);

        return mem;
}

static int doas(const char* in, const char* out, unsigned char** compiledcode)
{
        char rin[4096];
        char rout[4096];
        char* buf;
        char* mem;
        cvar_t* homedir;
        size_t size = -1, allocsize;
        int ps;
        pid_t pid;

        homedir = Cvar_Get("fs_homepath", "", 0);

        buf = FS_BuildOSPath(homedir->string, NULL, in);
        strcpy(rin, buf);

        buf = FS_BuildOSPath(homedir->string, NULL, out);
        strcpy(rout, buf);

        Com_Printf("running assembler\n");
        pid = fork();
        if(pid == -1)
                Com_Error(ERR_FATAL, "can't fork\n");

        if(!pid)
        {
                char* const argv[] = {
                        "as",
                        "-o",
                        rout,
                        rin,
                        NULL
                };

                execvp(argv[0], argv);
                _exit(-1);
        }
        else
        {
                int status;
                if(waitpid(pid, &status, 0) == -1)
                        Com_Error(ERR_FATAL, "can't wait for as: %s\n", strerror(errno));

                if(!WIFEXITED(status))
                        Com_Error(ERR_FATAL, "as died\n");
                if(WEXITSTATUS(status))
                        Com_Error(ERR_FATAL, "as failed with status %d\n", WEXITSTATUS(status));
        }

        Com_Printf("done\n");

        mem = mmapfile(rout, &size);
        if(!mem)
                Com_Error(ERR_FATAL, "can't mmap object file %s: %s\n", rout, strerror(errno));

        ps = sysconf(_SC_PAGE_SIZE);
        if(ps == -1)
                Com_Error(ERR_FATAL, "can't determine page size: %s\n", strerror(errno));

        --ps;

        allocsize = (size+ps)&~ps;
        buf = Hunk_Alloc(allocsize, h_high);

        buf = (void*)(((unsigned long)buf+ps)&~ps);

        memcpy(buf, mem, size);

        munmap(mem, 0);

        if((*compiledcode = (unsigned char*)buf))
        {
			return size;
        }

        return -1;
}

/*
=================
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;
        void* entryPoint;
        char fn_s[MAX_QPATH]; // output file for assembler code
        char fn_o[MAX_QPATH]; // file written by as
        fileHandle_t fh_s;
        byte* compiledcode;
        int   compiledsize;

        Com_Printf("compiling %s\n", vm->name);

        strcpy(fn_s,vm->name);
        strcpy(fn_o,vm->name);
        strcat(fn_s, ".s");
        strcat(fn_o, ".o");

        fh_s = FS_FOpenFileWrite(fn_s);
        if(fh_s == -1)
                Com_Error(ERR_DROP, "can't write %s\n", fn_s);

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

        emit("start:");
        emit("or %%r8, %%r8"); // check whether to set up instruction pointers
        emit("jnz main");
        emit("jmp setupinstructionpointers");
        emit("exit:");
        emit("jmp *%%r9");

        emit("main:");

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

                vm->instructionPointers[instruction] = pc;

                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 %8hhu\n", opnames[op], barg);
                }
                else
                {
                        Dfprintf(qdasmout, "%s\n", opnames[op]);
                }
                emit("i_%08x:", instruction);
                switch ( op )
                {
                        case OP_UNDEF:
                                NOTIMPL(op);
                                break;
                        case OP_IGNORE:
                                emit("nop");
                                break;
                        case OP_BREAK:
                                emit("int3");
                                break;
                        case OP_ENTER:
                                emit("subl $%d, %%edi", iarg);
                                RANGECHECK(edi);
                                break;
                        case OP_LEAVE:
                                emit("addl $%d, %%edi", iarg);          // get rid of stack frame
                                RANGECHECK(edi);
                                emit("movl 0(%%r8, %%rdi, 1), %%eax");  // get return address
                                emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers);
                                emit("cmp $-1, %%eax");
                                emit("je jumptoexit%d", instruction);
                                emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address
                                emit("addq %%r10, %%rax");
                                emit("jmp *%%rax");
                                emit("jumptoexit%d:", instruction);
                                emit("jmp exit");
                                break;
                        case OP_CALL:
                                emit("movl 0(%%rsi), %%eax");  // get instr from stack
                                emit("subq $4, %%rsi");
                                emit("movl $%d, 0(%%r8, %%rdi, 1)", instruction+1);  // save next instruction
                                emit("orl %%eax, %%eax");
                                emit("jl callSyscall%d", instruction);
                                emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers);
                                emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address
                                emit("addq %%r10, %%rax");
                                emit("jmp *%%rax");
                                emit("callSyscall%d:", instruction);
//                              emit("fnsave 4(%%rsi)");
                                emit("push %%rsi");
                                emit("push %%rdi");
                                emit("push %%r8");
                                emit("push %%r9");
                                emit("push %%r10");
                                emit("negl %%eax");        // convert to actual number
                                emit("decl %%eax");
                                                           // first argument already in rdi
                                emit("movq %%rax, %%rsi"); // second argument in rsi
                                emit("movq $%ld, %%rax", (unsigned long)callAsmCall);
                                emit("callq *%%rax");
                                emit("pop %%r10");
                                emit("pop %%r9");
                                emit("pop %%r8");
                                emit("pop %%rdi");
                                emit("pop %%rsi");
//                              emit("frstor 4(%%rsi)");
                                emit("addq $4, %%rsi");
                                emit("movl %%eax, (%%rsi)");
                                break;
                        case OP_PUSH:
                                emit("addq $4, %%rsi");
                                break;
                        case OP_POP:
                                emit("subq $4, %%rsi");
                                break;
                        case OP_CONST:
                                emit("addq $4, %%rsi");
                                emit("movl $%d, 0(%%rsi)", iarg);
                                break;
                        case OP_LOCAL:
                                emit("movl %%edi, %%ebx");
                                emit("addl $%d,%%ebx", iarg);
                                emit("addq $4, %%rsi");
                                emit("movl %%ebx, 0(%%rsi)");
                                break;
                        case OP_JUMP:
                                emit("movl 0(%%rsi), %%eax"); // get instr from stack
                                emit("subq $4, %%rsi");
                                emit("movq $%lu, %%rbx", (unsigned long)vm->instructionPointers);
                                emit("movl (%%rbx, %%rax, 4), %%eax"); // load new relative jump address
                                emit("addq %%r10, %%rax");
                                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
                                emit("subq $8, %%rsi");
                                emit("movss 4(%%rsi), %%xmm0");
                                emit("ucomiss 8(%%rsi), %%xmm0");
                                emit("jp dojump_i_%08x", instruction);
                                emit("jz i_%08x", instruction+1);
                                emit("dojump_i_%08x:", instruction);
                                emit("jmp i_%08x", iarg);
#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:
                                emit("movl 0(%%rsi), %%eax"); // get pointer from stack
                                RANGECHECK(eax);
                                emit("movb 0(%%r8, %%rax, 1), %%al"); // deref into eax
                                emit("andq $255, %%rax");
                                emit("movl %%eax, 0(%%rsi)"); // store on stack
                                break;
                        case OP_LOAD2:
                                emit("movl 0(%%rsi), %%eax"); // get pointer from stack
                                RANGECHECK(eax);
                                emit("movw 0(%%r8, %%rax, 1), %%rax"); // deref into eax
                                emit("movl %%eax, 0(%%rsi)"); // store on stack
                                break;
                        case OP_LOAD4:
                                emit("movl 0(%%rsi), %%eax"); // get pointer from stack
                                RANGECHECK(eax);
                                emit("movl 0(%%r8, %%rax, 1), %%eax"); // deref into eax
                                emit("movl %%eax, 0(%%rsi)"); // store on stack
                                break;
                        case OP_STORE1:
                                emit("movl 0(%%rsi), %%eax"); // get value from stack
                                emit("andq $255, %%rax");
                                emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
                                RANGECHECK(ebx);
                                emit("movb %%al, 0(%%r8, %%rbx, 1)"); // store in memory
                                emit("subq $8, %%rsi");
                                break;
                        case OP_STORE2:
                                emit("movl 0(%%rsi), %%eax"); // get value from stack
                                emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
                                RANGECHECK(ebx);
                                emit("movw %%rax, 0(%%r8, %%rbx, 1)"); // store in memory
                                emit("subq $8, %%rsi");
                                break;
                        case OP_STORE4:
                                emit("movl -4(%%rsi), %%ebx"); // get pointer from stack
                                RANGECHECK(ebx);
                                emit("movl 0(%%rsi), %%ecx"); // get value from stack
                                emit("movl %%ecx, 0(%%r8, %%rbx, 1)"); // store in memory
                                emit("subq $8, %%rsi");
                                break;
                        case OP_ARG:
                                emit("subq $4, %%rsi");
                                emit("movl 4(%%rsi), %%eax"); // get value from stack
                                emit("movl $0x%hhx, %%ebx", barg);
                                emit("addl %%edi, %%ebx");
                                RANGECHECK(ebx);
                                emit("movl %%eax, 0(%%r8,%%rbx, 1)"); // store in args space
                                break;
                        case OP_BLOCK_COPY:
                                if(iarg % 4) Com_Error(ERR_DROP,
                                                "argument to OP_BLOCK_COPY not multiple of 4\n");

                                emit("subq $8, %%rsi");
                                emit("movl 8(%%rsi), %%ebx"); // get pointer from stack

                                emit("movl %%ebx, %%ecx");
                                RANGECHECK(ecx);
                                emit("cmp %%ebx, %%ecx");
                                emit("jne broken%d", instruction);

                                emit("movl %%ecx, %%edx");
                                emit("addl $%d, %%edx", iarg);
                                emit("addl $%d, %%ecx", iarg);
                                RANGECHECK(edx);
                                emit("cmp %%ecx, %%edx");
                                emit("jne broken%d", instruction);

                                emit("movl 4(%%rsi), %%eax"); // get pointer from stack

                                emit("movl %%eax, %%ecx");
                                RANGECHECK(ecx);
                                emit("cmp %%eax, %%ecx");
                                emit("jne broken%d", instruction);

                                emit("movl %%ecx, %%edx");
                                emit("addl $%d, %%edx", iarg);
                                emit("addl $%d, %%ecx", iarg);
                                RANGECHECK(edx);
                                emit("cmp %%ecx, %%edx");
                                emit("jne broken%d", instruction);

                                emit("addq %%r8, %%rax");     // calc real address
                                emit("addq %%r8, %%rbx");     // calc real address
                                emit("movl $%d, %%ecx", iarg);
                                emit("shrl $2, %%ecx");
                                emit("block_copy_loop_%d:", instruction);
                                emit("decl %%ecx");
                                emit("movl 0(%%rbx, %%rcx, 4), %%edx");
                                emit("movl %%edx, 0(%%rax, %%rcx, 4)");
                                emit("orl %%ecx, %%ecx");
                                emit("jnz block_copy_loop_%d", instruction);
                                emit("jmp i_%08x", instruction+1);

                                emit("broken%d:", instruction);
                                emit("int3");

                                break;
                        case OP_SEX8:
                                emit("movw 0(%%rsi), %%rax");
                                emit("andq $255, %%rax");
                                emit("cbw");
                                emit("cwde");
                                emit("movl %%eax, 0(%%rsi)");
                                break;
                        case OP_SEX16:
                                emit("movw 0(%%rsi), %%rax");
                                emit("cwde");
                                emit("movl %%eax, 0(%%rsi)");
                                break;
                        case OP_NEGI:
                                emit("negl 0(%%rsi)");
                                break;
                        case OP_ADD:
                                SIMPLE("addl");
                                break;
                        case OP_SUB:
                                SIMPLE("subl");
                                break;
                        case OP_DIVI:
                                emit("subq $4, %%rsi");
                                emit("movl 0(%%rsi), %%eax");
                                emit("cdq");
                                emit("idivl 4(%%rsi)");
                                emit("movl %%eax, 0(%%rsi)");
                                break;
                        case OP_DIVU:
                                emit("subq $4, %%rsi");
                                emit("movl 0(%%rsi), %%eax");
                                emit("xorq %%rdx, %%rdx");
                                emit("divl 4(%%rsi)");
                                emit("movl %%eax, 0(%%rsi)");
                                break;
                        case OP_MODI:
                                emit("subq $4, %%rsi");
                                emit("movl 0(%%rsi), %%eax");
                                emit("xorl %%edx, %%edx");
                                emit("divl 4(%%rsi)");
                                emit("movl %%edx, 0(%%rsi)");
                                break;
                        case OP_MODU:
                                emit("subq $4, %%rsi");
                                emit("movl 0(%%rsi), %%eax");
                                emit("xorl %%edx, %%edx");
                                emit("idivl 4(%%rsi)");
                                emit("movl %%edx, 0(%%rsi)");
                                break;
                        case OP_MULI:
                                emit("subq $4, %%rsi");
                                emit("movl 0(%%rsi), %%eax");
                                emit("imull 4(%%rsi)");
                                emit("movl %%eax, 0(%%rsi)");
                                break;
                        case OP_MULU:
                                emit("subq $4, %%rsi");
                                emit("movl 0(%%rsi), %%eax");
                                emit("mull 4(%%rsi)");
                                emit("movl %%eax, 0(%%rsi)");
                                break;
                        case OP_BAND:
                                SIMPLE("andl");
                                break;
                        case OP_BOR:
                                SIMPLE("orl");
                                break;
                        case OP_BXOR:
                                SIMPLE("xorl");
                                break;
                        case OP_BCOM:
                                emit("notl 0(%%rsi)");
                                break;
                        case OP_LSH:
                                SHIFT("shl");
                                break;
                        case OP_RSHI:
                                SHIFT("sarl");
                                break;
                        case OP_RSHU:
                                SHIFT("shrl");
                                break;
                        case OP_NEGF:
#ifdef USE_X87
                                emit("flds 0(%%rsi)");
                                emit("fchs");
                                emit("fstps 0(%%rsi)");
#else
                                emit("movl $0x80000000, %%eax");
                                emit("xorl %%eax, 0(%%rsi)");
#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:
#ifdef USE_X87
                                emit("filds 0(%%rsi)");
                                emit("fstps 0(%%rsi)");
#else
                                emit("movl 0(%%rsi), %%eax");
                                emit("cvtsi2ss %%eax, %%xmm0");
                                emit("movss %%xmm0, 0(%%rsi)");
#endif
                                break;
                        case OP_CVFI:
#ifdef USE_X87
                                emit("flds 0(%%rsi)");
                                emit("fnstcw 4(%%rsi)");
                                emit("movw $0x0F7F, 8(%%rsi)"); // round toward zero
                                emit("fldcw 8(%%rsi)");
                                emit("fistpl 0(%%rsi)");
                                emit("fldcw 4(%%rsi)");
#else
                                emit("movss 0(%%rsi), %%xmm0");
                                emit("cvttss2si %%xmm0, %%eax");
                                emit("movl %%eax, 0(%%rsi)");
#endif
                                break;
                        default:
                                NOTIMPL(op);
                                break;
                }
        }


        emit("setupinstructionpointers:");
        emit("movq $%lu, %%rax", (unsigned long)vm->instructionPointers);
        for ( instruction = 0; instruction < header->instructionCount; ++instruction )
        {
                emit("movl $i_%08x-start, %d(%%rax)", instruction, instruction*4);
        }
        emit("jmp exit");

        emit("debugger:");
        if(1);
        {
                int i = 6;
                while(i--)
                {
                        emit("nop");
                        emit("int3");
                }
        }

        FS_Flush(fh_s);
        FS_FCloseFile(fh_s);

        compiledsize = doas(fn_s, fn_o, &compiledcode);

        vm->codeBase   = compiledcode; // remember to skip ELF header!
        vm->codeLength = compiledsize;
        
        entryPoint = getentrypoint(vm);

//      __asm__ __volatile__ ("int3");
        Com_Printf("computing jump table\n");

        // call code with r8 set to zero to set up instruction pointers
        __asm__ __volatile__ (
                "       xorq %%r8,%%r8          \r\n" \
                "       movq $doneinit,%%r9     \r\n" \
                "       movq %0,%%r10           \r\n" \
                "       jmp *%%r10              \r\n" \
                "doneinit:                      \r\n" \
                :
                : "m" (entryPoint)
                : "%r8", "%r9", "%r10", "%rax"
        );

        Com_Printf( "VM file %s compiled to %i bytes of code (0x%lx - 0x%lx)\n", vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength );
}

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

This function is called directly by the generated code
==============
*/
int VM_CallCompiled( vm_t *vm, int *args ) {
        int             programCounter;
        int             programStack;
        int             stackOnEntry;
        byte    *image;
        void    *entryPoint;
        void    *opStack;
        int stack[1024] = { 0xDEADBEEF };

        currentVM = vm;

        ++vm->callLevel;
//      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;

        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 = &stack;

        __asm__ __volatile__ (
                "       movq %5,%%rsi           \r\n" \
                "       movl %4,%%edi           \r\n" \
                "       movq $done,%%r9         \r\n" \
                "       movq %2,%%r10           \r\n" \
                "       movq %3,%%r8            \r\n" \
                "       jmp *%%r10              \r\n" \
                "done:                          \r\n" \
                "       movl %%edi, %0          \r\n" \
                "       movq %%rsi, %1          \r\n" \
                : "=m" (programStack), "=m" (opStack)
                : "m" (entryPoint), "m" (vm->dataBase), "m" (programStack), "m" (opStack)
                : "%rsi", "%rdi", "%rax", "%rbx", "%rcx", "%rdx", "%r8", "%r9", "%r10", "%r15", "%xmm0"
        );

        if ( opStack != &stack[1] ) {
                Com_Error( ERR_DROP, "opStack corrupted in compiled code (offset %d)\n", (void*)&stack[1] - opStack);
        }
        if ( programStack != stackOnEntry - 48 ) {
                Com_Error( ERR_DROP, "programStack corrupted in compiled code\n" );
        }

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

        return *(int *)opStack;
}

