/* =========================================================================== Copyright (C) 1999-2005 Id Software, Inc. 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 =========================================================================== */ #include "vm_local.h" //#define DEBUG_VM #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 #if idppc //FIXME: these, um... look the same to me #if defined(__GNUC__) static ID_INLINE unsigned int loadWord(void *addr) { unsigned int word; asm("lwbrx %0,0,%1" : "=r" (word) : "r" (addr)); return word; } #else static ID_INLINE unsigned int __lwbrx(register void *addr, register int offset) { register unsigned int word; asm("lwbrx %0,%2,%1" : "=r" (word) : "r" (addr), "b" (offset)); return word; } #define loadWord(addr) __lwbrx(addr,0) #endif #else static ID_INLINE int loadWord(void *addr) { int word; memcpy(&word, addr, 4); return LittleLong(word); } #endif char *VM_Indent( vm_t *vm ) { static char *string = " "; if ( vm->callLevel > 20 ) { return string; } return string + 2 * ( 20 - vm->callLevel ); } void VM_StackTrace( vm_t *vm, int programCounter, int programStack ) { int count; count = 0; do { Com_Printf( "%s\n", VM_ValueToSymbol( vm, programCounter ) ); programStack = *(int *)&vm->dataBase[programStack+4]; programCounter = *(int *)&vm->dataBase[programStack]; } while ( programCounter != -1 && ++count < 32 ); } /* ==================== VM_PrepareInterpreter ==================== */ void VM_PrepareInterpreter( vm_t *vm, vmHeader_t *header ) { int op; int byte_pc; int int_pc; byte *code; int instruction; int *codeBase; vm->codeBase = Hunk_Alloc( vm->codeLength*4, h_high ); // we're now int aligned // memcpy( vm->codeBase, (byte *)header + header->codeOffset, vm->codeLength ); // we don't need to translate the instructions, but we still need // to find each instructions starting point for jumps int_pc = byte_pc = 0; instruction = 0; code = (byte *)header + header->codeOffset; codeBase = (int *)vm->codeBase; // Copy and expand instructions to words while building instruction table while ( instruction < header->instructionCount ) { vm->instructionPointers[ instruction ] = int_pc; instruction++; op = (int)code[ byte_pc ]; codeBase[int_pc] = op; if(byte_pc > header->codeLength) Com_Error(ERR_DROP, "VM_PrepareInterpreter: pc > header->codeLength"); byte_pc++; int_pc++; // these are the only opcodes that aren't a single byte switch ( op ) { case OP_ENTER: case OP_CONST: case OP_LOCAL: case OP_LEAVE: case OP_EQ: case OP_NE: case OP_LTI: case OP_LEI: case OP_GTI: case OP_GEI: case OP_LTU: case OP_LEU: case OP_GTU: case OP_GEU: case OP_EQF: case OP_NEF: case OP_LTF: case OP_LEF: case OP_GTF: case OP_GEF: case OP_BLOCK_COPY: codeBase[int_pc] = loadWord(&code[byte_pc]); byte_pc += 4; int_pc++; break; case OP_ARG: codeBase[int_pc] = (int)code[byte_pc]; byte_pc++; int_pc++; break; default: break; } } int_pc = 0; instruction = 0; // Now that the code has been expanded to int-sized opcodes, we'll translate instruction index //into an index into codeBase[], which contains opcodes and operands. while ( instruction < header->instructionCount ) { op = codeBase[ int_pc ]; instruction++; int_pc++; switch ( op ) { // These ops need to translate addresses in jumps from instruction index to int index case OP_EQ: case OP_NE: case OP_LTI: case OP_LEI: case OP_GTI: case OP_GEI: case OP_LTU: case OP_LEU: case OP_GTU: case OP_GEU: case OP_EQF: case OP_NEF: case OP_LTF: case OP_LEF: case OP_GTF: case OP_GEF: if(codeBase[int_pc] < 0 || codeBase[int_pc] > vm->instructionCount) Com_Error(ERR_DROP, "VM_PrepareInterpreter: Jump to invalid instruction number"); // codeBase[pc] is the instruction index. Convert that into an offset into //the int-aligned codeBase[] by the lookup table. codeBase[int_pc] = vm->instructionPointers[codeBase[int_pc]]; int_pc++; break; // These opcodes have an operand that isn't an instruction index case OP_ENTER: case OP_CONST: case OP_LOCAL: case OP_LEAVE: case OP_BLOCK_COPY: case OP_ARG: int_pc++; break; default: break; } } } /* ============== VM_Call Upon a system call, the stack will look like: sp+32 parm1 sp+28 parm0 sp+24 return stack sp+20 return address sp+16 local1 sp+14 local0 sp+12 arg1 sp+8 arg0 sp+4 return stack sp return address An interpreted function will immediately execute an OP_ENTER instruction, which will subtract space for locals from sp ============== */ #define DEBUGSTR va("%s%i", VM_Indent(vm), opStackOfs) int VM_CallInterpreted( vm_t *vm, int *args ) { byte stack[OPSTACK_SIZE + 15]; int *opStack; uint8_t opStackOfs; int programCounter; int programStack; int stackOnEntry; byte *image; int *codeImage; int v1; int dataMask; int arg; #ifdef DEBUG_VM vmSymbol_t *profileSymbol; #endif // interpret the code vm->currentlyInterpreting = qtrue; // we might be called recursively, so this might not be the very top programStack = stackOnEntry = vm->programStack; #ifdef DEBUG_VM profileSymbol = VM_ValueToFunctionSymbol( vm, 0 ); // uncomment this for debugging breakpoints vm->breakFunction = 0; #endif // set up the stack frame image = vm->dataBase; codeImage = (int *)vm->codeBase; dataMask = vm->dataMask; programCounter = 0; programStack -= ( 8 + 4 * MAX_VMMAIN_ARGS ); for ( arg = 0; arg < MAX_VMMAIN_ARGS; arg++ ) *(int *)&image[ programStack + 8 + arg * 4 ] = args[ arg ]; *(int *)&image[ programStack + 4 ] = 0; // return stack *(int *)&image[ programStack ] = -1; // will terminate the loop on return VM_Debug(0); // leave a free spot at start of stack so // that as long as opStack is valid, opStack-1 will // not corrupt anything opStack = PADP(stack, 16); *opStack = 0xDEADBEEF; opStackOfs = 0; // vm_debugLevel=2; // main interpreter loop, will exit when a LEAVE instruction // grabs the -1 program counter #define r2 codeImage[programCounter] while ( 1 ) { int opcode, r0, r1; // unsigned int r2; nextInstruction: r0 = opStack[opStackOfs]; r1 = opStack[(uint8_t) (opStackOfs - 1)]; nextInstruction2: #ifdef DEBUG_VM if ( (unsigned)programCounter >= vm->codeLength ) { Com_Error( ERR_DROP, "VM pc out of range" ); return 0; } if ( programStack <= vm->stackBottom ) { Com_Error( ERR_DROP, "VM stack overflow" ); return 0; } if ( programStack & 3 ) { Com_Error( ERR_DROP, "VM program stack misaligned" ); return 0; } if ( vm_debugLevel > 1 ) { Com_Printf( "%s %s\n", DEBUGSTR, opnames[opcode] ); } profileSymbol->profileCount++; #endif opcode = codeImage[ programCounter++ ]; switch ( opcode ) { #ifdef DEBUG_VM default: Com_Error( ERR_DROP, "Bad VM instruction" ); // this should be scanned on load! return 0; #endif case OP_BREAK: vm->breakCount++; goto nextInstruction2; case OP_CONST: opStackOfs++; r1 = r0; r0 = opStack[opStackOfs] = r2; programCounter += 1; goto nextInstruction2; case OP_LOCAL: opStackOfs++; r1 = r0; r0 = opStack[opStackOfs] = r2+programStack; programCounter += 1; goto nextInstruction2; case OP_LOAD4: #ifdef DEBUG_VM if(opStack[opStackOfs] & 3) { Com_Error( ERR_DROP, "OP_LOAD4 misaligned" ); return 0; } #endif r0 = opStack[opStackOfs] = *(int *) &image[r0 & dataMask & ~3 ]; goto nextInstruction2; case OP_LOAD2: r0 = opStack[opStackOfs] = *(unsigned short *)&image[ r0&dataMask&~1 ]; goto nextInstruction2; case OP_LOAD1: r0 = opStack[opStackOfs] = image[ r0&dataMask ]; goto nextInstruction2; case OP_STORE4: *(int *)&image[ r1&(dataMask & ~3) ] = r0; opStackOfs -= 2; goto nextInstruction; case OP_STORE2: *(short *)&image[ r1&(dataMask & ~1) ] = r0; opStackOfs -= 2; goto nextInstruction; case OP_STORE1: image[ r1&dataMask ] = r0; opStackOfs -= 2; goto nextInstruction; case OP_ARG: // single byte offset from programStack *(int *)&image[ (codeImage[programCounter] + programStack)&dataMask&~3 ] = r0; opStackOfs--; programCounter += 1; goto nextInstruction; case OP_BLOCK_COPY: VM_BlockCopy(r1, r0, r2); programCounter += 1; opStackOfs -= 2; goto nextInstruction; case OP_CALL: // save current program counter *(int *)&image[ programStack ] = programCounter; // jump to the location on the stack programCounter = r0; opStackOfs--; if ( programCounter < 0 ) { // system call int r; // int temp; #ifdef DEBUG_VM int stomped; if ( vm_debugLevel ) { Com_Printf( "%s---> systemcall(%i)\n", DEBUGSTR, -1 - programCounter ); } #endif // save the stack to allow recursive VM entry // temp = vm->callLevel; vm->programStack = programStack - 4; #ifdef DEBUG_VM stomped = *(int *)&image[ programStack + 4 ]; #endif *(int *)&image[ programStack + 4 ] = -1 - programCounter; //VM_LogSyscalls( (int *)&image[ programStack + 4 ] ); { // the vm has ints on the stack, we expect // pointers so we might have to convert it if (sizeof(intptr_t) != sizeof(int)) { intptr_t argarr[ MAX_VMSYSCALL_ARGS ]; int *imagePtr = (int *)&image[ programStack ]; int i; for (i = 0; i < ARRAY_LEN(argarr); ++i) { argarr[i] = *(++imagePtr); } r = vm->systemCall( argarr ); } else { intptr_t* argptr = (intptr_t *)&image[ programStack + 4 ]; r = vm->systemCall( argptr ); } } #ifdef DEBUG_VM // this is just our stack frame pointer, only needed // for debugging *(int *)&image[ programStack + 4 ] = stomped; #endif // save return value opStackOfs++; opStack[opStackOfs] = r; programCounter = *(int *)&image[ programStack ]; // vm->callLevel = temp; #ifdef DEBUG_VM if ( vm_debugLevel ) { Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); } #endif } else if ( (unsigned)programCounter >= vm->instructionCount ) { Com_Error( ERR_DROP, "VM program counter out of range in OP_CALL" ); return 0; } else { programCounter = vm->instructionPointers[ programCounter ]; } goto nextInstruction; // push and pop are only needed for discarded or bad function return values case OP_PUSH: opStackOfs++; goto nextInstruction; case OP_POP: opStackOfs--; goto nextInstruction; case OP_ENTER: #ifdef DEBUG_VM profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); #endif // get size of stack frame v1 = r2; programCounter += 1; programStack -= v1; #ifdef DEBUG_VM // save old stack frame for debugging traces *(int *)&image[programStack+4] = programStack + v1; if ( vm_debugLevel ) { Com_Printf( "%s---> %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter - 5 ) ); if ( vm->breakFunction && programCounter - 5 == vm->breakFunction ) { // this is to allow setting breakpoints here in the debugger vm->breakCount++; // vm_debugLevel = 2; // VM_StackTrace( vm, programCounter, programStack ); } // vm->callLevel++; } #endif goto nextInstruction; case OP_LEAVE: // remove our stack frame v1 = r2; programStack += v1; // grab the saved program counter programCounter = *(int *)&image[ programStack ]; #ifdef DEBUG_VM profileSymbol = VM_ValueToFunctionSymbol( vm, programCounter ); if ( vm_debugLevel ) { // vm->callLevel--; Com_Printf( "%s<--- %s\n", DEBUGSTR, VM_ValueToSymbol( vm, programCounter ) ); } #endif // check for leaving the VM if ( programCounter == -1 ) { goto done; } else if ( (unsigned)programCounter >= vm->codeLength ) { Com_Error( ERR_DROP, "VM program counter out of range in OP_LEAVE" ); return 0; } goto nextInstruction; /* =================================================================== BRANCHES =================================================================== */ case OP_JUMP: if ( (unsigned)r0 >= vm->instructionCount ) { Com_Error( ERR_DROP, "VM program counter out of range in OP_JUMP" ); return 0; } programCounter = vm->instructionPointers[ r0 ]; opStackOfs--; goto nextInstruction; case OP_EQ: opStackOfs -= 2; if ( r1 == r0 ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_NE: opStackOfs -= 2; if ( r1 != r0 ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_LTI: opStackOfs -= 2; if ( r1 < r0 ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_LEI: opStackOfs -= 2; if ( r1 <= r0 ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_GTI: opStackOfs -= 2; if ( r1 > r0 ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_GEI: opStackOfs -= 2; if ( r1 >= r0 ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_LTU: opStackOfs -= 2; if ( ((unsigned)r1) < ((unsigned)r0) ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_LEU: opStackOfs -= 2; if ( ((unsigned)r1) <= ((unsigned)r0) ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_GTU: opStackOfs -= 2; if ( ((unsigned)r1) > ((unsigned)r0) ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_GEU: opStackOfs -= 2; if ( ((unsigned)r1) >= ((unsigned)r0) ) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_EQF: opStackOfs -= 2; if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] == ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_NEF: opStackOfs -= 2; if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] != ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_LTF: opStackOfs -= 2; if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] < ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_LEF: opStackOfs -= 2; if(((float *) opStack)[(uint8_t) ((uint8_t) (opStackOfs + 1))] <= ((float *) opStack)[(uint8_t) ((uint8_t) (opStackOfs + 2))]) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_GTF: opStackOfs -= 2; if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] > ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } case OP_GEF: opStackOfs -= 2; if(((float *) opStack)[(uint8_t) (opStackOfs + 1)] >= ((float *) opStack)[(uint8_t) (opStackOfs + 2)]) { programCounter = r2; //vm->instructionPointers[r2]; goto nextInstruction; } else { programCounter += 1; goto nextInstruction; } //=================================================================== case OP_NEGI: opStack[opStackOfs] = -r0; goto nextInstruction; case OP_ADD: opStackOfs--; opStack[opStackOfs] = r1 + r0; goto nextInstruction; case OP_SUB: opStackOfs--; opStack[opStackOfs] = r1 - r0; goto nextInstruction; case OP_DIVI: opStackOfs--; opStack[opStackOfs] = r1 / r0; goto nextInstruction; case OP_DIVU: opStackOfs--; opStack[opStackOfs] = ((unsigned) r1) / ((unsigned) r0); goto nextInstruction; case OP_MODI: opStackOfs--; opStack[opStackOfs] = r1 % r0; goto nextInstruction; case OP_MODU: opStackOfs--; opStack[opStackOfs] = ((unsigned) r1) % ((unsigned) r0); goto nextInstruction; case OP_MULI: opStackOfs--; opStack[opStackOfs] = r1 * r0; goto nextInstruction; case OP_MULU: opStackOfs--; opStack[opStackOfs] = ((unsigned) r1) * ((unsigned) r0); goto nextInstruction; case OP_BAND: opStackOfs--; opStack[opStackOfs] = ((unsigned) r1) & ((unsigned) r0); goto nextInstruction; case OP_BOR: opStackOfs--; opStack[opStackOfs] = ((unsigned) r1) | ((unsigned) r0); goto nextInstruction; case OP_BXOR: opStackOfs--; opStack[opStackOfs] = ((unsigned) r1) ^ ((unsigned) r0); goto nextInstruction; case OP_BCOM: opStack[opStackOfs] = ~((unsigned) r0); goto nextInstruction; case OP_LSH: opStackOfs--; opStack[opStackOfs] = r1 << r0; goto nextInstruction; case OP_RSHI: opStackOfs--; opStack[opStackOfs] = r1 >> r0; goto nextInstruction; case OP_RSHU: opStackOfs--; opStack[opStackOfs] = ((unsigned) r1) >> r0; goto nextInstruction; case OP_NEGF: ((float *) opStack)[opStackOfs] = -((float *) opStack)[opStackOfs]; goto nextInstruction; case OP_ADDF: opStackOfs--; ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] + ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; goto nextInstruction; case OP_SUBF: opStackOfs--; ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] - ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; goto nextInstruction; case OP_DIVF: opStackOfs--; ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] / ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; goto nextInstruction; case OP_MULF: opStackOfs--; ((float *) opStack)[opStackOfs] = ((float *) opStack)[opStackOfs] * ((float *) opStack)[(uint8_t) (opStackOfs + 1)]; goto nextInstruction; case OP_CVIF: ((float *) opStack)[opStackOfs] = (float) opStack[opStackOfs]; goto nextInstruction; case OP_CVFI: opStack[opStackOfs] = Q_ftol(((float *) opStack)[opStackOfs]); goto nextInstruction; case OP_SEX8: opStack[opStackOfs] = (signed char) opStack[opStackOfs]; goto nextInstruction; case OP_SEX16: opStack[opStackOfs] = (short) opStack[opStackOfs]; goto nextInstruction; } } done: vm->currentlyInterpreting = qfalse; if (opStackOfs != 1 || *opStack != 0xDEADBEEF) Com_Error(ERR_DROP, "Interpreter error: opStack[0] = %X, opStackOfs = %d", opStack[0], opStackOfs); vm->programStack = stackOnEntry; // return the result return opStack[opStackOfs]; }