mirror of
https://github.com/DrBeef/ioq3quest.git
synced 2024-11-14 08:31:00 +00:00
1255 lines
26 KiB
C
1255 lines
26 KiB
C
/*
|
|
===========================================================================
|
|
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/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/types.h>
|
|
#include <sys/wait.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
|
|
#ifdef VM_X86_64_STANDALONE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <time.h>
|
|
|
|
static vmInterpret_t interpret = VMI_COMPILED;
|
|
|
|
#define DEBUG_VM
|
|
|
|
static FILE* _asout;
|
|
vm_t* currentVM;
|
|
|
|
static cvar_t _com_developer;
|
|
cvar_t *com_developer = &_com_developer;
|
|
|
|
char* mmapfile(const char* fn, size_t* size);
|
|
|
|
long printsyscall(long* args)
|
|
{
|
|
printf("callnr %ld, args: %ld %ld %ld %ld\n",
|
|
args[0],
|
|
args[1],
|
|
args[2],
|
|
args[3],
|
|
args[4]);
|
|
|
|
switch( args[0] ) {
|
|
case 0:
|
|
case 1:
|
|
{
|
|
char * s = VMA(1)?VMA(1):"(NULL)\n";
|
|
fputs(s, stderr);
|
|
if(s[strlen(s)-1] != '\n')
|
|
putc('\n', stderr);
|
|
}
|
|
return 0;
|
|
case 999:
|
|
{
|
|
int a[5];
|
|
a[0] = 3;
|
|
a[1] = args[1];
|
|
a[2] = args[2];
|
|
a[3] = args[3];
|
|
a[4] = args[4];
|
|
if(!currentVM->compiled)
|
|
return VM_CallInterpreted(currentVM, a);
|
|
else
|
|
return VM_CallCompiled(currentVM, a);
|
|
}
|
|
case 1000:
|
|
{
|
|
int a[5];
|
|
a[0] = 4;
|
|
a[1] = args[1];
|
|
a[2] = args[2];
|
|
a[3] = args[3];
|
|
a[4] = args[4];
|
|
if(!currentVM->compiled)
|
|
return VM_CallInterpreted(currentVM, a);
|
|
else
|
|
return VM_CallCompiled(currentVM, a);
|
|
}
|
|
case 1001:
|
|
printf("got buffer with content '%s', length %d\n", (char*)VMA(1), (int)args[2]);
|
|
strncpy(VMA(1), "blah\n", args[2]);
|
|
return 0;
|
|
}
|
|
|
|
return 0x66;
|
|
}
|
|
|
|
fileHandle_t FS_FOpenFileWrite( const char *filename )
|
|
{
|
|
_asout = fopen(filename, "w");
|
|
return 0;
|
|
}
|
|
|
|
int FS_Write( const void *buffer, int len, fileHandle_t h )
|
|
{
|
|
return fwrite(buffer, 1, len, _asout);
|
|
}
|
|
|
|
void FS_Printf( fileHandle_t h, const char *fmt, ... )
|
|
{
|
|
va_list ap;
|
|
va_start(ap, fmt);
|
|
vfprintf(_asout, fmt, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
void FS_Flush( fileHandle_t f )
|
|
{
|
|
fflush(_asout);
|
|
}
|
|
|
|
void FS_FCloseFile( fileHandle_t f )
|
|
{
|
|
fclose(_asout);
|
|
}
|
|
|
|
char *FS_BuildOSPath( const char *base, const char *game, const char *qpath )
|
|
{
|
|
static char buf[4096];
|
|
strcpy(buf, "./");
|
|
strcat(buf, qpath);
|
|
return buf;
|
|
}
|
|
|
|
cvar_t *Cvar_Get( const char *var_name, const char *var_value, int flags ) {
|
|
static cvar_t c = { .string = "" };
|
|
return &c;
|
|
}
|
|
#endif // VM_X86_64_STANDALONE
|
|
|
|
#ifdef DEBUG_VM
|
|
#define Dfprintf(fd, args...) fprintf(fd, ##args)
|
|
static FILE* qdasmout;
|
|
#else
|
|
#define Dfprintf(args...)
|
|
#endif
|
|
|
|
/*
|
|
|
|
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;
|
|
|
|
// 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 { 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
|
|
|
|
#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_Error(ERR_DROP, "instruction not implemented: %x\n", x); } 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(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 < %s > %s\n", rin, rout);
|
|
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))
|
|
{
|
|
#ifdef VM_X86_64_STANDALONE // no idea why
|
|
if(mprotect(buf, allocsize, PROT_READ|PROT_EXEC) == -1)
|
|
Com_Error(ERR_FATAL, "mprotect failed on %p+%x: %s\n", buf, allocsize, strerror(errno));
|
|
#endif
|
|
return size;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
// Com_Printf("OP_BLOCK_COPY %s %x %x %d\n", currentVM->name, dest, src, count);
|
|
// __asm__ __volatile__ ("int3");
|
|
|
|
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
|
|
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");
|
|
#ifdef DEBUG_VM
|
|
strcpy(fn_d,vm->name);
|
|
strcat(fn_d, ".qdasm");
|
|
|
|
qdasmout = fopen(fn_d, "w");
|
|
#endif
|
|
|
|
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 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
|
|
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 $%lu, %%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:
|
|
|
|
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), %%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"
|
|
);
|
|
|
|
#ifdef DEBUG_VM
|
|
fflush(qdasmout);
|
|
#endif
|
|
|
|
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 );
|
|
|
|
#if 0
|
|
if(!com_developer->integer)
|
|
{
|
|
unlink(fn_o);
|
|
unlink(fn_s);
|
|
}
|
|
#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 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 $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;
|
|
}
|
|
|
|
#ifdef VM_X86_64_STANDALONE
|
|
|
|
#include <tests.c>
|
|
|
|
int testops(vm_t* vm)
|
|
{
|
|
int i1, i2, vmres;
|
|
int i;
|
|
float f1, f2, fres, fvmres;
|
|
int numitests = 32;
|
|
int numftests = 11;
|
|
int ret = 0;
|
|
int testno;
|
|
int res = 0xC0DEDBAD;
|
|
|
|
srand(time(NULL));
|
|
|
|
i1 = 1 + (int) (1000.0 * (rand() / (RAND_MAX + 1.0)));
|
|
i2 = 1 + (int) (1000.0 * (rand() / (RAND_MAX + 1.0)));
|
|
|
|
if(i1 < i2)
|
|
{
|
|
i = i1;
|
|
i1 = i2;
|
|
i2 = i;
|
|
}
|
|
|
|
f1 = i1/1.5;
|
|
f2 = i2/1.5;
|
|
|
|
if(!i2) i2=i1;
|
|
if(!f2) f2=f1;
|
|
|
|
printf("i1: %d i2: %d\n", i1, i2);
|
|
printf("f1: %f f2: %f\n", f1, f2);
|
|
|
|
testintops:
|
|
for (testno = 1; testno < numitests; ++testno)
|
|
{
|
|
printf("int test %d ... ", testno);
|
|
fflush(stdout);
|
|
|
|
res = test(testno, i1, i2);
|
|
vmres = VM_Call(vm, testno, i1, i2);
|
|
|
|
if(vmres == res)
|
|
{
|
|
printf("ok: %d == %d\n", res, vmres);
|
|
}
|
|
else
|
|
{
|
|
printf("failed: %d != %d\n", res, vmres);
|
|
ret = 1;
|
|
}
|
|
}
|
|
if(i1 != i2)
|
|
{
|
|
i2 = i1;
|
|
goto testintops;
|
|
}
|
|
|
|
testfops:
|
|
i1 = *(int*)&f1;
|
|
i2 = *(int*)&f2;
|
|
|
|
for (testno = 100; testno < 100+numftests; ++testno)
|
|
{
|
|
printf("float test %d ... ", testno);
|
|
fflush(stdout);
|
|
|
|
res = test(testno, i1, i2);
|
|
vmres = VM_Call(vm, testno, i1, i2);
|
|
|
|
fres = *(float*)&res;
|
|
fvmres = *(float*)&vmres;
|
|
|
|
if(fvmres == fres)
|
|
{
|
|
printf("ok: %f == %f\n", fres, fvmres);
|
|
}
|
|
else
|
|
{
|
|
printf("failed: %f != %f\n", fres, fvmres);
|
|
ret = 1;
|
|
}
|
|
}
|
|
if(f1 > f2)
|
|
{
|
|
float t = f1;
|
|
f1 = f2;
|
|
f2 = t;
|
|
goto testfops;
|
|
}
|
|
else if(f1 < f2)
|
|
{
|
|
f2 = f1;
|
|
goto testfops;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
void VM_VmInfo_f( void );
|
|
|
|
int main(int argc, char* argv[])
|
|
{
|
|
vm_t* vm[3];
|
|
int i;
|
|
long args[11] = {0};
|
|
int ret = 0xDEADBEEF;
|
|
|
|
char* file = argv[1];
|
|
|
|
char module[128];
|
|
|
|
if(argc < 2)
|
|
return -1;
|
|
|
|
strcpy(module, file);
|
|
*strchr(module, '.') = '\0';
|
|
|
|
vm[0] = VM_Create( module, printsyscall, interpret );
|
|
|
|
VM_VmInfo_f();
|
|
|
|
if(argc > 2)
|
|
{
|
|
for(i = 2; i < argc; ++i)
|
|
{
|
|
args[i-2] = strtol(argv[i],NULL,0);
|
|
}
|
|
|
|
ret = VM_Call(vm[0], args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7]);
|
|
}
|
|
else
|
|
{
|
|
ret = testops(vm[0]);
|
|
}
|
|
|
|
|
|
#ifdef DEBUG_VM
|
|
printf("ret: %d [%X]\n", ret, ret);
|
|
#endif
|
|
|
|
return 0;
|
|
}
|
|
#endif
|