summaryrefslogtreecommitdiff
path: root/src/qcommon/vm_x86_64.c
diff options
context:
space:
mode:
authorPaweł Redman <pawel.redman@gmail.com>2017-03-22 17:56:34 +0100
committerPaweł Redman <pawel.redman@gmail.com>2017-03-22 17:56:34 +0100
commit6a777afc079c2a8d3af3ecd2145fe8dd50567a39 (patch)
tree520f4489cebf8564ef6cb27064ceea45cbc005b3 /src/qcommon/vm_x86_64.c
Funko sources as released by Rotacak.HEADmaster
Diffstat (limited to 'src/qcommon/vm_x86_64.c')
-rw-r--r--src/qcommon/vm_x86_64.c1023
1 files changed, 1023 insertions, 0 deletions
diff --git a/src/qcommon/vm_x86_64.c b/src/qcommon/vm_x86_64.c
new file mode 100644
index 0000000..e8e827e
--- /dev/null
+++ b/src/qcommon/vm_x86_64.c
@@ -0,0 +1,1023 @@
+/*
+===========================================================================
+Copyright (C) 1999-2005 Id Software, Inc.
+Copyright (C) 2000-2006 Tim Angus
+Copyright (C) 2005 Ludwig Nussel <ludwig.nussel@web.de>
+
+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
+===========================================================================
+*/
+// 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>
+
+#ifdef DEBUG_VM
+#define Dfprintf(fd, args...) fprintf(fd, ##args)
+static FILE* qdasmout;
+#else
+#define Dfprintf(args...)
+#endif
+
+static void VM_Destroy_Compiled(vm_t* self);
+
+/*
+
+ |=====================|
+ ^ dataMask ^- programStack rdi
+ |
+ +- r8
+
+ eax scratch
+ ebx scratch
+ ecx scratch (required for shifts)
+ edx scratch (required for divisions)
+ rsi stack pointer (opStack)
+ rdi program frame pointer (programStack)
+ r8 pointer data (vm->dataBase)
+ 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;
+
+// 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 < 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;
+// Com_Printf("<- callAsmCall %s, level %d, num %ld\n", currentVM->name, currentVM->callLevel, callSyscallNum);
+
+ return ret;
+}
+
+#ifdef DEBUG_VM // bk001204
+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,
+};
+
+#define emit(x...) \
+ do { fprintf(fh_s, ##x); fputc('\n', 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);
+#elif 0
+#define RANGECHECK(reg) \
+ emit("pushl %%" #reg); \
+ emit("andl $0x%x, %%" #reg, ~vm->dataMask); \
+ emit("jz rangecheck_ok_i_%08x", instruction); \
+ emit("int3"); \
+ emit("rangecheck_ok_i_%08x:", instruction); \
+ emit("popl %%" #reg); \
+ emit("andl $0x%x, %%" #reg, vm->dataMask);
+#else
+#define RANGECHECK(reg)
+#endif
+
+#ifdef DEBUG_VM
+#define NOTIMPL(x) \
+ do { Com_Error(ERR_DROP, "instruction not implemented: %s\n", 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+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(char* in, char* out, unsigned char** compiledcode)
+{
+ unsigned char* mem;
+ size_t size = -1;
+ pid_t pid;
+
+ Com_Printf("running assembler < %s > %s\n", in, out);
+ pid = fork();
+ if(pid == -1)
+ {
+ Com_Printf(S_COLOR_RED "can't fork\n");
+ return -1;
+ }
+
+ if(!pid)
+ {
+ char* const argv[] = {
+ "as",
+ "-o",
+ out,
+ in,
+ NULL
+ };
+
+ execvp(argv[0], argv);
+ _exit(-1);
+ }
+ else
+ {
+ int status;
+ if(waitpid(pid, &status, 0) == -1)
+ {
+ Com_Printf(S_COLOR_RED "can't wait for as: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if(!WIFEXITED(status))
+ {
+ Com_Printf(S_COLOR_RED "as died\n");
+ return -1;
+ }
+ if(WEXITSTATUS(status))
+ {
+ Com_Printf(S_COLOR_RED "as failed with status %d\n", WEXITSTATUS(status));
+ return -1;
+ }
+ }
+
+ Com_Printf("done\n");
+
+ mem = (unsigned char*)mmapfile(out, &size);
+ if(!mem)
+ {
+ Com_Printf(S_COLOR_RED "can't mmap object file %s: %s\n", out, strerror(errno));
+ return -1;
+ }
+
+ *compiledcode = mem;
+
+ return size;
+}
+
+static void block_copy_vm(unsigned dest, unsigned src, unsigned count)
+{
+ unsigned dataMask = currentVM->dataMask;
+
+ if ((dest & dataMask) != dest
+ || (src & dataMask) != src
+ || ((dest+count) & dataMask) != dest + count
+ || ((src+count) & dataMask) != src + count)
+ {
+ Com_Error(ERR_DROP, "OP_BLOCK_COPY out of range!\n");
+ }
+
+ memcpy(currentVM->dataBase+dest, currentVM->dataBase+src, count);
+}
+
+/*
+=================
+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[2*MAX_QPATH]; // output file for assembler code
+ char fn_o[2*MAX_QPATH]; // file written by as
+#ifdef DEBUG_VM
+ char fn_d[MAX_QPATH]; // disassembled
+#endif
+ FILE* fh_s;
+ int fd_s, fd_o;
+ byte* compiledcode;
+ int compiledsize;
+
+ Com_Printf("compiling %s\n", vm->name);
+
+#ifdef DEBUG_VM
+ snprintf(fn_s, sizeof(fn_s), "%.63s.s", vm->name);
+ snprintf(fn_o, sizeof(fn_o), "%.63s.o", vm->name);
+ fd_s = open(fn_s, O_CREAT|O_WRONLY, 0644);
+ fd_o = open(fn_o, O_CREAT|O_WRONLY, 0644);
+#else
+ snprintf(fn_s, sizeof(fn_s), "/tmp/%.63s.s_XXXXXX", vm->name);
+ snprintf(fn_o, sizeof(fn_o), "/tmp/%.63s.o_XXXXXX", vm->name);
+ fd_s = mkstemp(fn_s);
+ fd_o = mkstemp(fn_o);
+#endif
+ if(fd_s == -1 || fd_o == -1)
+ {
+ if(fd_s != -1) close(fd_s);
+ if(fd_o != -1) close(fd_o);
+ unlink(fn_s);
+ unlink(fn_o);
+
+ Com_Printf(S_COLOR_RED "can't create temporary file %s for vm\n", fn_s);
+ vm->compiled = qfalse;
+ return;
+ }
+
+#ifdef DEBUG_VM
+ strcpy(fn_d,vm->name);
+ strcat(fn_d, ".qdasm");
+
+ qdasmout = fopen(fn_d, "w");
+#endif
+
+ fh_s = fdopen(fd_s, "wb");
+ if(!fh_s)
+ {
+ Com_Printf(S_COLOR_RED "can't write %s\n", fn_s);
+ vm->compiled = qfalse;
+ return;
+ }
+
+ // 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("main:");
+
+ for ( instruction = 0; instruction < header->instructionCount; ++instruction )
+ {
+ op = code[ pc ];
+ ++pc;
+
+ vm->instructionPointers[instruction] = pc;
+
+#if 0
+ 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 %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
+ emit("ret");
+ 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("callq *%%rax");
+ emit("jmp i_%08x", instruction+1);
+ emit("callSyscall%d:", instruction);
+// emit("fnsave 4(%%rsi)");
+ emit("push %%rsi");
+ emit("push %%rdi");
+ emit("push %%r8");
+ emit("push %%r9");
+ emit("push %%r10");
+ emit("movq %%rsp, %%rbx"); // we need to align the stack pointer
+ emit("subq $8, %%rbx"); // |
+ emit("andq $127, %%rbx"); // |
+ emit("subq %%rbx, %%rsp"); // <-+
+ emit("push %%rbx");
+ 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 $%lu, %%rax", (unsigned long)callAsmCall);
+ emit("callq *%%rax");
+ emit("pop %%rbx");
+ emit("addq %%rbx, %%rsp");
+ 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)"); // store return value
+ 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 value 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 value from stack
+ RANGECHECK(eax);
+ emit("movw 0(%%r8, %%rax, 1), %%ax"); // deref into eax
+ emit("movl %%eax, 0(%%rsi)"); // store on stack
+ break;
+ case OP_LOAD4:
+ emit("movl 0(%%rsi), %%eax"); // get value from stack
+ RANGECHECK(eax); // not a pointer!?
+ 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 %%ax, 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:
+
+ emit("subq $8, %%rsi");
+ emit("push %%rsi");
+ emit("push %%rdi");
+ emit("push %%r8");
+ emit("push %%r9");
+ emit("push %%r10");
+ emit("movl 4(%%rsi), %%edi"); // 1st argument dest
+ emit("movl 8(%%rsi), %%esi"); // 2nd argument src
+ emit("movl $%d, %%edx", iarg); // 3rd argument count
+ emit("movq $%lu, %%rax", (unsigned long)block_copy_vm);
+ emit("callq *%%rax");
+ emit("pop %%r10");
+ emit("pop %%r9");
+ emit("pop %%r8");
+ emit("pop %%rdi");
+ emit("pop %%rsi");
+
+ break;
+ case OP_SEX8:
+ emit("movw 0(%%rsi), %%ax");
+ emit("andq $255, %%rax");
+ emit("cbw");
+ emit("cwde");
+ emit("movl %%eax, 0(%%rsi)");
+ break;
+ case OP_SEX16:
+ emit("movw 0(%%rsi), %%ax");
+ 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("cdq");
+ emit("idivl 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("divl 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("ret");
+
+ emit("debugger:");
+ if(1);
+ {
+ int i = 6;
+ while(i--)
+ {
+ emit("nop");
+ emit("int3");
+ }
+ }
+
+ fflush(fh_s);
+ fclose(fh_s);
+
+ compiledsize = doas(fn_s, fn_o, &compiledcode);
+ if(compiledsize == -1)
+ {
+ vm->compiled = qfalse;
+ goto out;
+ }
+
+ vm->codeBase = compiledcode; // remember to skip ELF header!
+ vm->codeLength = compiledsize;
+
+ vm->destroy = VM_Destroy_Compiled;
+
+ 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 %0,%%r10 \r\n" \
+ " callq *%%r10 \r\n" \
+ :
+ : "m" (entryPoint)
+ : "%r8", "%r10", "%rax"
+ );
+
+#ifdef DEBUG_VM
+ fflush(qdasmout);
+ fclose(qdasmout);
+#endif
+
+ Com_Printf( "VM file %s compiled to %i bytes of code (%p - %p)\n", vm->name, vm->codeLength, vm->codeBase, vm->codeBase+vm->codeLength );
+
+out:
+ close(fd_o);
+
+#ifndef DEBUG_VM
+ if(!com_developer->integer)
+ {
+ unlink(fn_o);
+ unlink(fn_s);
+ }
+#endif
+}
+
+
+void VM_Destroy_Compiled(vm_t* self)
+{
+ munmap(self->codeBase, self->codeLength);
+}
+
+/*
+==============
+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 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;
+#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 = &stack;
+
+ __asm__ __volatile__ (
+ " movq %5,%%rsi \r\n" \
+ " movl %4,%%edi \r\n" \
+ " movq %2,%%r10 \r\n" \
+ " movq %3,%%r8 \r\n" \
+ " subq $24, %%rsp # fix alignment as call pushes one value \r\n" \
+ " callq *%%r10 \r\n" \
+ " addq $24, %%rsp \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", "%r10", "%r15", "%xmm0"
+ );
+
+ if ( opStack != &stack[1] ) {
+ Com_Error( ERR_DROP, "opStack corrupted in compiled code (offset %ld)\n", (long int) ((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;
+}