From cd6c193e812f5fcef18e19d673b7a6740422d59c Mon Sep 17 00:00:00 2001 From: Simon Robertshaw Date: Sun, 9 Sep 2012 12:51:37 +0100 Subject: [PATCH] Virtual machine --- src/tests/VirtualMachineTest.cpp | 11 + src/virtualmachine/Exceptions.h | 69 +++ src/virtualmachine/OpCodes.inl | 60 +++ src/virtualmachine/Traps.inl | 57 +++ src/virtualmachine/VirtualMachine.cpp | 664 ++++++++++++++++++++++++++ src/virtualmachine/VirtualMachine.h | 243 ++++++++++ 6 files changed, 1104 insertions(+) create mode 100644 src/tests/VirtualMachineTest.cpp create mode 100644 src/virtualmachine/Exceptions.h create mode 100644 src/virtualmachine/OpCodes.inl create mode 100644 src/virtualmachine/Traps.inl create mode 100644 src/virtualmachine/VirtualMachine.cpp create mode 100644 src/virtualmachine/VirtualMachine.h diff --git a/src/tests/VirtualMachineTest.cpp b/src/tests/VirtualMachineTest.cpp new file mode 100644 index 000000000..fef05381a --- /dev/null +++ b/src/tests/VirtualMachineTest.cpp @@ -0,0 +1,11 @@ +#include +#include "virtualmachine/VirtualMachine.h" + +int main(int argc, char * argv[]) +{ + vm::VirtualMachine * vm = new vm::VirtualMachine(2); + vm->LoadProgram("test.qvm"); + vm->Call(0/*, 0, 88, 12*/); + std::cout << "Return value: " << vm->Pop() << std::endl; + //vm->Run(); +} \ No newline at end of file diff --git a/src/virtualmachine/Exceptions.h b/src/virtualmachine/Exceptions.h new file mode 100644 index 000000000..2dca4614e --- /dev/null +++ b/src/virtualmachine/Exceptions.h @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include "Format.h" + +namespace vm +{ + class RuntimeException: public std::exception + { + public: + RuntimeException() {} + const char * what() const throw() + { + return "VirtualMachine runtime exception"; + } + ~RuntimeException() throw() {}; + }; + + class StackOverflowException: public RuntimeException + { + public: + StackOverflowException() {} + const char * what() const throw() + { + return "VirtualMachine Stack overflow"; + } + ~StackOverflowException() throw() {}; + }; + + class StackUnderflowException: public RuntimeException + { + public: + StackUnderflowException() {} + const char * what() const throw() + { + return "VirtualMachine Stack underflow"; + } + ~StackUnderflowException() throw() {}; + }; + + class AccessViolationException: public RuntimeException + { + int address; + char * _what; + public: + AccessViolationException(int address = 0) : address(address) + { + _what = strdup(std::string("VirtualMachine Access violation at "+format::NumberToString(address)).c_str()); + } + const char * what() const throw() + { + if(address) + return _what; + return "VirtualMachine Access violation"; + } + ~AccessViolationException() throw() {}; + }; + + class OutOfMemoryException: public RuntimeException + { + public: + OutOfMemoryException() {} + const char * what() const throw() + { + return "VirtualMachine Out of memory"; + } + ~OutOfMemoryException() throw() {}; + }; +} \ No newline at end of file diff --git a/src/virtualmachine/OpCodes.inl b/src/virtualmachine/OpCodes.inl new file mode 100644 index 000000000..a1d4b4324 --- /dev/null +++ b/src/virtualmachine/OpCodes.inl @@ -0,0 +1,60 @@ +OPDEF(UNDEF) +OPDEF(IGNORE) /* no-op */ +OPDEF(BREAK) /* ??? */ +OPDEF(ENTER) /* Begin subroutine. */ +OPDEF(LEAVE) /* End subroutine. */ +OPDEF(CALL) /* Call subroutine. */ +OPDEF(PUSH) /* push to stack. */ +OPDEF(POP) /* discard top-of-stack. */ +OPDEF(CONST) /* load constant to stack. */ +OPDEF(LOCAL) /* get local variable. */ +OPDEF(JUMP) /* unconditional jump. */ +OPDEF(EQ) /* compare integers, jump if equal. */ +OPDEF(NE) /* compare integers, jump if not equal. */ +OPDEF(LTI) /* compare integers, jump if less-than. */ +OPDEF(LEI) /* compare integers, jump if less-than-or-equal. */ +OPDEF(GTI) /* compare integers, jump if greater-than. */ +OPDEF(GEI) /* compare integers, jump if greater-than-or-equal. */ +OPDEF(LTU) /* compare unsigned integers, jump if less-than */ +OPDEF(LEU) /* compare unsigned integers, jump if less-than-or-equal */ +OPDEF(GTU) /* compare unsigned integers, jump if greater-than */ +OPDEF(GEU) /* compare unsigned integers, jump if greater-than-or-equal */ +OPDEF(EQF) /* compare floats, jump if equal */ +OPDEF(NEF) /* compare floats, jump if not-equal */ +OPDEF(LTF) /* compare floats, jump if less-than */ +OPDEF(LEF) /* compare floats, jump if less-than-or-equal */ +OPDEF(GTF) /* compare floats, jump if greater-than */ +OPDEF(GEF) /* compare floats, jump if greater-than-or-equal */ +OPDEF(LOAD1) /* load 1-byte from memory */ +OPDEF(LOAD2) /* load 2-byte from memory */ +OPDEF(LOAD4) /* load 4-byte from memory */ +OPDEF(STORE1) /* store 1-byte to memory */ +OPDEF(STORE2) /* store 2-byte to memory */ +OPDEF(STORE4) /* store 4-byte to memory */ +OPDEF(ARG) /* marshal argument */ +OPDEF(BLOCK_COPY) /* block copy... */ +OPDEF(SEX8) /* Pedophilia */ +OPDEF(SEX16) /* Sign-Extend 16-bit */ +OPDEF(NEGI) /* Negate integer. */ +OPDEF(ADD) /* Add integers (two's complement). */ +OPDEF(SUB) /* Subtract integers (two's complement). */ +OPDEF(DIVI) /* Divide signed integers. */ +OPDEF(DIVU) /* Divide unsigned integers. */ +OPDEF(MODI) /* Modulus (signed). */ +OPDEF(MODU) /* Modulus (unsigned). */ +OPDEF(MULI) /* Multiply signed integers. */ +OPDEF(MULU) /* Multiply unsigned integers. */ +OPDEF(BAND) /* Bitwise AND */ +OPDEF(BOR) /* Bitwise OR */ +OPDEF(BXOR) /* Bitwise eXclusive-OR */ +OPDEF(BCOM) /* Bitwise COMplement */ +OPDEF(LSH) /* Left-shift */ +OPDEF(RSHI) /* Right-shift (algebraic; preserve sign) */ +OPDEF(RSHU) /* Right-shift (bitwise; ignore sign) */ +OPDEF(NEGF) /* Negate float */ +OPDEF(ADDF) /* Add floats */ +OPDEF(SUBF) /* Subtract floats */ +OPDEF(DIVF) /* Divide floats */ +OPDEF(MULF) /* Multiply floats */ +OPDEF(CVIF) /* Convert to integer from float */ +OPDEF(CVFI) /* Convert to float from integer */ \ No newline at end of file diff --git a/src/virtualmachine/Traps.inl b/src/virtualmachine/Traps.inl new file mode 100644 index 000000000..0da71d2c9 --- /dev/null +++ b/src/virtualmachine/Traps.inl @@ -0,0 +1,57 @@ +TRAPDEF(-1, Print) +TRAPDEF(-2, Error) +/*MAPTRAP(-3, Milliseconds) + MAPTRAP(-4, Cvar_Register) + MAPTRAP(-5, Cvar_Update) + MAPTRAP(-6, Cvar_Set) + MAPTRAP(-7, Cvar_VariableIntegerValue) + MAPTRAP(-8, Cvar_VariableStringBuffer) + MAPTRAP(-9, Argc) + MAPTRAP(-10, Argv) + MAPTRAP(-11, FS_FOpenFile) + MAPTRAP(-12, FS_Read) + MAPTRAP(-13, FS_Write) + MAPTRAP(-14, FS_FCloseFile) + MAPTRAP(-15, SendConsoleCommand) + MAPTRAP(-16, LocateGameData) + MAPTRAP(-17, DropClient) + MAPTRAP(-18, SendServerCommand) + MAPTRAP(-19, SetConfigstring) + MAPTRAP(-20, GetConfigstring) + MAPTRAP(-21, GetUserinfo) + MAPTRAP(-22, SetUserinfo) + MAPTRAP(-23, GetServerinfo) + MAPTRAP(-24, SetBrushModel) + MAPTRAP(-25, Trace) + MAPTRAP(-26, PointContents) + MAPTRAP(-27, InPVS) + MAPTRAP(-28, InPVSIgnorePortals) + MAPTRAP(-29, AdjustAreaPortalState) + MAPTRAP(-30, AreasConnected) + MAPTRAP(-31, LinkEntity) + MAPTRAP(-32, UnlinkEntity) + MAPTRAP(-33, EntitiesInBox) + MAPTRAP(-34, EntityContact) + MAPTRAP(-35, BotAllocateClient) + MAPTRAP(-36, BotFreeClient) + MAPTRAP(-37, GetUsercmd) + MAPTRAP(-38, GetEntityToken) + MAPTRAP(-39, FS_GetFileList) + MAPTRAP(-40, DebugPolygonCreate) + MAPTRAP(-41, DebugPolygonDelete) + MAPTRAP(-42, RealTime) + MAPTRAP(-43, SnapVector) + MAPTRAP(-44, TraceCapsule) + MAPTRAP(-45, EntityContactCapsule) + MAPTRAP(-46, FS_Seek) + MAPTRAP(-101, memset) + MAPTRAP(-102, memcpy) + MAPTRAP(-103, strncpy) + MAPTRAP(-104, sin) + MAPTRAP(-105, cos) + MAPTRAP(-106, atan2) + MAPTRAP(-107, sqrt) + MAPTRAP(-111, floor) + MAPTRAP(-112, ceil) + MAPTRAP(-113, testPrintInt) + MAPTRAP(-114, testPrintFloat)*/ \ No newline at end of file diff --git a/src/virtualmachine/VirtualMachine.cpp b/src/virtualmachine/VirtualMachine.cpp new file mode 100644 index 000000000..ce12b8774 --- /dev/null +++ b/src/virtualmachine/VirtualMachine.cpp @@ -0,0 +1,664 @@ +#include +#include +#include +#include +#include "VirtualMachine.h" + +namespace vm +{ + + VirtualMachine::VirtualMachine(int hunkMbytes): + bigEndian(false), + hunk(NULL), + hunkSize(1048576), + hunkFree(0), + rom(NULL), + romSize(0), + ram(NULL), + ramSize(0), + dataStack(0), + returnStack(0), + DP(0), /* Datastack pointer. */ + RP(0), /* Return stack pointer. */ + PC(0), + cm(0), + cycles(0) + { + hunk = new char[hunkSize]; + std::fill(hunk, hunk+hunkSize, 0); + } + + VirtualMachine::~VirtualMachine() + { + delete[] hunk; + } + + #define crumb printf + + int VirtualMachine::opcodeParameterSize(int opcode) + { + #define OP(n) OP##n + switch (opcode) + { + case OP(ENTER): + case OP(LEAVE): + case OP(LOCAL): + 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(CONST): + case OP(BLOCK_COPY): + return sizeof(uint4_t); + break; + case OP(ARG): + return sizeof(uint1_t); + break; + } + return 0; + #undef OP + } + + /* Read one octet from file. */ + int VirtualMachine::readByte(FILE *qvmfile) + { + int o; + o = fgetc(qvmfile); + if (o < 0) o = 0; /* EOF (hack) */ + return o; + } + + /* Read little-endian 32-bit integer from file. */ + int VirtualMachine::readInt(FILE *qvmfile) + { + int a, b, c, d, n; + + a = readByte(qvmfile); + b = readByte(qvmfile); + c = readByte(qvmfile); + d = readByte(qvmfile); + n = (a) | (b << 8) | (c << 16) | (d << 24); + return n; + } + + int VirtualMachine::LoadProgram(char * filename) + { + FILE * qvmfile = fopen(filename, "rb"); + qvm_header_t qvminfo; + int i, n; + uint1_t x[4]; + word w; + + crumb("Loading file...\n"); + qvminfo.magic = readInt(qvmfile); /* magic. */ + if (qvminfo.magic != QVM_MAGIC) + { + crumb("Invalid magic"); + //q3vm_error("Does not appear to be a QVM file."); + /* XXX: option to force continue. */ + return 0; + } + crumb("Magic OK\n"); + /* variable-length instructions mean instruction count != code length */ + qvminfo.inscount = readInt(qvmfile); + qvminfo.codeoff = readInt(qvmfile); + qvminfo.codelen = readInt(qvmfile); + qvminfo.dataoff = readInt(qvmfile); + qvminfo.datalen = readInt(qvmfile); + qvminfo.litlen = readInt(qvmfile); + qvminfo.bsslen = readInt(qvmfile); + + /* Code segment should follow... */ + /* XXX: use fseek with SEEK_CUR? */ + crumb("Searching for .code @ %d from %d\n", qvminfo.codeoff, ftell(qvmfile)); + // rom = (q3vm_rom_t*)(hunk); /* ROM-in-hunk */ + rom = (Instruction*)calloc(qvminfo.inscount, sizeof(rom[0])); + while (ftell(qvmfile) < qvminfo.codeoff) + readByte(qvmfile); + while (romSize < qvminfo.inscount) + { + n = readByte(qvmfile); + w.int4 = 0; + if ((i = opcodeParameterSize(n))) + { + x[0] = x[1] = x[2] = x[3] = 0; + fread(&x, 1, i, qvmfile); + w.uint4 = (x[0]) | (x[1] << 8) | (x[2] << 16) | (x[3] << 24); + } + rom[romSize].Operation = n; + rom[romSize].Parameter = w; + romSize++; + } + crumb("After loading code: at %d, should be %d\n", ftell(qvmfile), qvminfo.codeoff + qvminfo.codelen); + + /* Then data segment. */ + // ram = hunk + ((romlen + 3) & ~3); /* RAM-in-hunk */ + ram = hunk; + crumb("Searching for .data @ %d from %d\n", qvminfo.dataoff, ftell(qvmfile)); + while (ftell(qvmfile) < qvminfo.dataoff) + readByte(qvmfile); + for (n = 0; n < (qvminfo.datalen / sizeof(uint1_t)); n++) + { + i = fread(&x, 1, sizeof(x), qvmfile); + w.uint4 = (x[0]) | (x[1] << 8) | (x[2] << 16) | (x[3] << 24); + *((word*)(ram + ramSize)) = w; + ramSize += sizeof(word); + } + /* lit segment follows data segment. */ + /* Assembler should have already padded properly. */ + crumb("Loading .lit\n"); + for (n = 0; n < (qvminfo.litlen / sizeof(uint1_t)); n++) + { + i = fread(&x, 1, sizeof(x), qvmfile); + memcpy(&(w.uint1), &x, sizeof(x)); /* no byte-swapping. */ + *((word*)(ram + ramSize)) = w; + ramSize += sizeof(word); + } + /* bss segment. */ + crumb("Allocating .bss %d (%X) bytes\n", qvminfo.bsslen, qvminfo.bsslen); + /* huge empty chunk. */ + ramSize += qvminfo.bsslen; + + hunkFree = hunkSize - ((ramSize * sizeof(uint1_t)) + 4); + + crumb("VM hunk has %d of %d bytes free (RAM = %d B).\n", hunkFree, hunkSize, ramSize); + if (ramSize > hunkSize) + { + throw OutOfMemoryException(); + return 0; + } + + /* set up stack. */ + { + int stacksize = 0x10000; + returnStack = ramSize; + dataStack = ramSize - (stacksize / 2); + RP = returnStack; + DP = dataStack; + } + + /* set up PC for return-to-termination. */ + PC = romSize + 1; + + return 1; + } + + int VirtualMachine::Call(int address) + { + word w; + int i, argCount = 13; + + /* Set up call. */ + opPUSH(w); + crumb("Starting with PC=%d, DP=%d, RP=%d to %d\n", PC, DP, RP, address); + w.int4 = (argCount + 2) * sizeof(word); + opENTER(w); + i = 8; + /**w.int4 = arg0; Marshal(i, w); i += 4; + w.int4 = arg1; Marshal(i, w); i += 4; + w.int4 = arg2; Marshal(i, w); i += 4; + w.int4 = arg3; Marshal(i, w); i += 4; + w.int4 = arg4; Marshal(i, w); i += 4; + w.int4 = arg5; Marshal(i, w); i += 4; + w.int4 = arg6; Marshal(i, w); i += 4; + w.int4 = arg7; Marshal(i, w); i += 4; + w.int4 = arg8; Marshal(i, w); i += 4; + w.int4 = arg9; Marshal(i, w); i += 4; + w.int4 = arg10; Marshal(i, w); i += 4; + w.int4 = arg11; Marshal(i, w); i += 4; + w.int4 = arg12; Marshal(i, w); i += 4;*/ + w.int4 = address; + Push(w); + opCALL(w); + printf("Upon running PC=%d, DP=%d, RP=%d\n", PC, DP, RP); + Run(); + printf("At finish PC=%d, DP=%d, RP=%d\n", PC, DP, RP); + w.int4 = (argCount + 2) * sizeof(word); + opLEAVE(w); + return 0; + } + + int VirtualMachine::Run() + { + bool running = true; + int operation; + word parameter; + while(running) + { + cycles++; + if(PC > romSize) + { + running = false; + continue; + } + if (PC < 0) + { + syscall(PC); + continue; + } + operation = rom[PC].Operation; + parameter = rom[PC].Parameter; + PC++; + (this->*operations[operation])(parameter); + } + return 1; + } + + + + int VirtualMachine::syscall(int trap) + { + int retval; + word w; + + retval = 0; + switch (trap) + { + #define TRAPDEF(n, f) case n: retval = trap##f(); break; + #include "Traps.inl" + #undef TRAPDEF + } + + w = Pop(); + PC = w.int4; + w.int4 = retval; + Push(w); + return 1; + } + + #define ARG(n) (Get(RP + ((2 + n) * sizeof(word)))) + + #define TRAPDEF(f) int VirtualMachine::trap##f() + + TRAPDEF(Print) + { + char *text; + + //crumb("SYSCALL Print [%d]\n", ARG(0)); + text = (char*)(ram) + ARG(0); + //crumb("PRINTING [%s]\n", text); + printf("%s", text); + return 0; + } + + + TRAPDEF(Error) + { + char *msg; + + msg = (char*)(ram) + ARG(0); + printf("%s", msg); + PC = romSize + 1; + return 0; + } + + #define OPDEF(n) &VirtualMachine::op##n, + OperationFunction VirtualMachine::operations[] = + { + #include "OpCodes.inl" + }; + #undef OPDEF + + #define OPDEF(n) int VirtualMachine::op##n(word parameter) + + #define R0 (r[0]) + #define R1 (r[1]) + #define R2 (r[2]) + + + OPDEF(UNDEF) + { + /* Die horribly. */ + throw RuntimeException(); + return -1; + } + + OPDEF(IGNORE) + { + /* NOP */ + throw RuntimeException(); + return 0; + } + + OPDEF(BREAK) + { + /* Usage never spotted. */ + /* Die horribly? */ + throw RuntimeException(); + return -1; + } + + /* + Stack on entering... + + no locals: ENTER 8 + 1 words locals: ENTER 16 + 2 words locals: ENTER 20 + 3 words locals: ENTER 24 + etc. + + address of argument: + ADDRFP4 v => OP_LOCAL (16 + currentLocals + currentArgs + v) + address of local: + ADDRLP4 v => OP_LOCAL (8 + currentArgs + v) + + RP [ ] ??? (oldPC?) + [ ] ??? + [ ] \ + ... > locals (args marshalling) + [ ] / + [ ] \ + ... > locals + [ ] / (ADDRLP4 v => OP_LOCAL (8 + currentArgs + v)) + (oldRP?) [ ] ??? + [ ] ??? + [ ] (my args?) + ... + [ ] + */ + + OPDEF(ENTER) /* ??? */ + { + while (parameter.int4 > (2 * sizeof(word))) + { + RPush(0); /* init zero */ + parameter.int4 -= sizeof(word); + } + RPush(Pop()); //Program Counter + RPush(0); //Unknown + return 0; + } + + OPDEF(LEAVE) /* ??? */ + { + RPop(); //Unknown + parameter.int4 -= sizeof(word); + PC = RPop(); //Program counter + parameter.int4 -= sizeof(word); + while (parameter.int4 > 0) + { + RPop(); + parameter.int4 -= sizeof(word); + } + return 0; + } + + OPDEF(CALL) /* Call subroutine. */ + { + R0 = Pop(); + Push(PC); + PC = R0.int4; + return 0; + } + + OPDEF(PUSH) /* [DP] <- 0; DP++ */ + { + Push(0); + return 0; + } + + OPDEF(POP) /* DP-- */ + { + Pop(); + return 0; + } + + OPDEF(CONST) /* [DP] <- parm; DP++ */ + { + Push(parameter); + return 0; + } + + OPDEF(LOCAL) /* [DP] <- [RP-n] */ + { + Push(RP + parameter.int4); + return 0; + } + + OPDEF(JUMP) /* PC <- [DP] */ + { + PC = Pop(); + return 0; + } + + #define CMP(type, op) \ + { \ + R0 = Pop(); \ + cm = (Pop() op R0.type); \ + if (cm) \ + PC = parameter.uint4; \ + return 0; \ + } + + OPDEF(EQ) /* if [DP] == [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(int4, ==) + + OPDEF(NE) /* if [DP] == [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(int4, !=) + + OPDEF(LTI) /* if [DP] < [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(int4, <) + + OPDEF(LEI) /* if [DP] <= [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(int4, <=) + + OPDEF(GTI) /* if [DP] > [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(int4, >) + + OPDEF(GEI) /* if [DP] >= [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(int4, >=) + + OPDEF(LTU) /* if [DP] < [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(uint4, <) + + OPDEF(LEU) /* if [DP] <= [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(uint4, <=) + + OPDEF(GTU) /* if [DP] > [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(uint4, >) + + OPDEF(GEU) /* if [DP] >= [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(uint4, >=) + + OPDEF(EQF) /* if [DP] == [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(float4, ==) + + OPDEF(NEF) /* if [DP] != [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(float4, !=) + + OPDEF(LTF) /* if [DP] < [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(float4, <) + + OPDEF(LEF) /* if [DP] <= [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(float4, <=) + + OPDEF(GTF) /* if [DP] > [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(float4, >) + + OPDEF(GEF) /* if [DP] >= [DP-1] then PC <- parm; DP <- DP-2 */ + CMP(float4, >=) + + + OPDEF(LOAD1) /* [DP] <- [[DP]] */ + { + Push(Get(Pop())); + return 0; + } + + OPDEF(LOAD2) /* [DP] <- [[DP]] */ + { + Push(Get(Pop())); + return 0; + } + + OPDEF(LOAD4) /* [DP] <- [[DP]] */ + { + Push(Get(Pop())); + return 0; + } + + OPDEF(STORE1) /* [DP-1] <- [DP]; DP <- DP-2 */ + { + Set(Pop(), Pop()); + return 0; + } + + OPDEF(STORE2) /* [DP-1] <- [DP]; DP <- DP-2 */ + { + Set(Pop(), Pop()); + return 0; + } + + OPDEF(STORE4) /* [DP-1] <- [DP]; DP <- DP-2 */ + { + Set(Pop(), Pop()); + return 0; + } + + OPDEF(ARG) /* Marshal TOS to to-call argument list */ + { + Marshal(parameter.int4, Pop()); + return 0; + } + + OPDEF(BLOCK_COPY) /* XXX */ + { + R1 = Pop(); + R0 = Pop(); + if(R0.int4 >= 0 && R0.int4 + parameter.int4 < ramSize && R1.int4 >= 0 && R1.int4 + parameter.int4 < ramSize) + memcpy(ram + R0.int4, ram + R1.int4, parameter.int4); + else + throw AccessViolationException(); + return -1; + } + + OPDEF(SEX8) /* Sign-extend 8-bit */ + { + R0 = Pop(); + if(R0.uint4 & 0x80) + R0.uint4 |= 0xFFFFFF80; + Push(R0); + return 0; + } + + OPDEF(SEX16) /* Sign-extend 16-bit */ + { + R0 = Pop(); + if(R0.uint4 & 0x8000) + R0.uint4 |= 0xFFFF8000; + Push(R0); + return 0; + } + + #define UNOP(type, op) \ + { \ + Push(op Pop()); \ + return 0; \ + } + + #define BINOP(type, op) \ + { \ + R0 = Pop(); \ + Push(Pop() op R0.type); \ + return 0; \ + } + + OPDEF(NEGI) /* [DP] <- -[DP] */ + UNOP(int4, -) + + OPDEF(ADD) /* [DP-1] <- [DP-1] + [DP]; DP <- DP-1 */ + BINOP(int4, +) + + OPDEF(SUB) /* [DP-1] <- [DP-1] - [DP]; DP <- DP-1 */ + BINOP(int4, -) + + OPDEF(DIVI) /* [DP-1] <- [DP-1] / [DP]; DP <- DP-1 */ + BINOP(int4, /) + + OPDEF(DIVU) /* [DP-1] <- [DP-1] / [DP]; DP <- DP-1 */ + BINOP(uint4, /) + + OPDEF(MODI) /* [DP-1] <- [DP-1] % [DP]; DP <- DP-1 */ + BINOP(int4, %) + + OPDEF(MODU) /* [DP-1] <- [DP-1] % [DP]; DP <- DP-1 */ + BINOP(uint4, %) + + OPDEF(MULI) /* [DP-1] <- [DP-1] * [DP]; DP <- DP-1 */ + BINOP(int4, *) + + OPDEF(MULU) /* [DP-1] <- [DP-1] * [DP]; DP <- OP-1 */ + BINOP(uint4, *) + + OPDEF(BAND) /* [DP-1] <- [DP-1] & [DP]; DP <- DP-1 */ + BINOP(uint4, &) + + OPDEF(BOR) /* [DP-1] <- [DP-1] | [DP]; DP <- DP-1 */ + BINOP(uint4, |) + + OPDEF(BXOR) /* [DP-1] <- [DP-1] ^ [DP]; DP <- DP-1 */ + BINOP(uint4, ^) + + OPDEF(BCOM) /* [DP] <- ~[DP] */ + UNOP(uint4, ~) + + OPDEF(LSH) /* [DP-1] <- [DP-1] << [DP]; DP <- DP-1 */ + BINOP(uint4, <<) + + OPDEF(RSHI) /* [DP-1] <- [DP-1] >> [DP]; DP <- DP-1 */ + { + R1.int4 = Pop(); + R0.int4 = Pop(); + #if 0 + while (R1.int4-- > 0) + R0.int4 /= 2; + R2 = R0; + #else + R2.int4 = R0.int4 >> R1.int4; + #endif + Push(R2); + return 0; + } + + OPDEF(RSHU) /* [DP-1] <- [DP-1] >> [DP]; DP <- DP-1 */ + BINOP(uint4, >>) + + OPDEF(NEGF) /* [DP] <- -[DP] */ + UNOP(float4, -) + + OPDEF(ADDF) /* [DP-1] <- [DP-1] + [DP]; DP <- DP-1 */ + BINOP(float4, +) + + OPDEF(SUBF) /* [DP-1] <- [DP-1] - [DP]; DP <- DP-1 */ + BINOP(float4, -) + + OPDEF(DIVF) /* [DP-1] <- [DP-1] / [DP]; DP <- DP-1 */ + BINOP(float4, /) + + OPDEF(MULF) /* [DP-1] <- [DP-1] / [DP]; DP <- DP-1 */ + BINOP(float4, *) + + OPDEF(CVIF) /* [DP] <- [DP] */ + { + Push(Pop()); + return 0; + } + + OPDEF(CVFI) /* [DP] <- [DP] */ + { + Push(Pop()); + return 0; + } +} \ No newline at end of file diff --git a/src/virtualmachine/VirtualMachine.h b/src/virtualmachine/VirtualMachine.h new file mode 100644 index 000000000..9a41e24bc --- /dev/null +++ b/src/virtualmachine/VirtualMachine.h @@ -0,0 +1,243 @@ +#pragma once + +#include "Exceptions.h" + +namespace vm +{ + + class VirtualMachine; + + typedef char ram_t; + + + + typedef unsigned int uint4_t; + typedef signed int int4_t; + + typedef unsigned short uint2_t; + typedef signed short int2_t; + + typedef unsigned char uint1_t; + typedef signed char int1_t; + + typedef float float4_t; + + union word + { + uint4_t uint4; + int4_t int4; + uint2_t uint2; + int2_t int2; + uint1_t uint1; + int1_t int1; + float4_t float4; + }; + + typedef int (VirtualMachine::*OperationFunction)(word parameter); + + struct Instruction + { + int Operation; + word Parameter; + //opfunc opfunc; + }; + + enum + { + QVM_MAGIC = 0x12721444, + }; + + struct qvm_header_t + { + int magic; + /* not-entirely-RISC ISA, so instruction count != codelen */ + int inscount; /* instruction count. */ + int codeoff; /* file offset of code segment. */ + int codelen; /* length of code segment, in octets. */ + int dataoff; /* file offset of data segment. */ + int datalen; /* length of data segment, in octets. */ + int litlen; /* length of lit segment (which is embedded in data segment). */ + int bsslen; /* length of bss segment. */ + }; + + class VirtualMachine + { + bool bigEndian; /* host is big-endian (requires byte-swapping). */ + + /* Memory spaces. */ + char * hunk; /* hunk space (malloc'd). */ + int hunkSize; /* total hunk size. */ + int hunkFree; /* free pointer. */ + + /* Read-Only Memory (code). */ + Instruction * rom; + int romSize; + + /* Random-Access Memory (data). */ + ram_t *ram; + int ramSize; + + int dataStack; + int returnStack; + + word r[4]; /* registers. */ + int DP; /* Datastack pointer. */ + int RP; /* Return stack pointer. */ + int PC; /* Program Counter. */ + // int AP; /* Argument pointer. (hrm...) */ + + /* various flags. */ + int cm:1; + + /* Execution time */ + int cycles; + + #define TRAPDEF(n, f) int trap##f(); + #include "Traps.inl" + #undef TRAPDEF + + static OperationFunction operations[]; + + #define OPDEF(n) int op##n(word parameter); + #include "OpCodes.inl" + #undef OPDEF + + #define OPDEF(n) OP##n, + enum { + #include "OpCodes.inl" + }; + #undef OPDEF + + int readByte(FILE *qvmfile); + int readInt(FILE *qvmfile); + int opcodeParameterSize(int opcode); + int syscall(int programCounter); +public: + VirtualMachine(int hunkMbytes); + virtual ~VirtualMachine(); + + int LoadProgram(char * filename); + int Run(); + int Call(int address); + void Marshal(int address, word element) + { + ram_t * ptr = ram+RP+address; + if(ptr < ram || ptr > ram+ramSize - sizeof(word)) + throw AccessViolationException(RP+address); + *((word*)ptr) = element; + } + + template T Get(int address) + { + ram_t * ptr = ram+address; + if(ptr < ram || ptr > ram+ramSize - sizeof(word)) + throw AccessViolationException(address); + return *((T*)ptr); + } + + template void Set(int address, T value) + { + ram_t * ptr = ram+address; + if(ptr < ram || ptr > ram+ramSize - sizeof(word)) + throw AccessViolationException(address); + *((T*)ptr) = value; + } + + template T Pop () + { + ram_t * ptr = ram+DP; + if(DP + sizeof(word) < hunkSize) + DP += sizeof(word); + else + throw StackUnderflowException(); + return *((T*)ptr); + }; + + template T RPop () + { + ram_t * ptr = ram+RP; + if(RP + sizeof(word) < hunkSize) + RP += sizeof(word); + else + throw StackUnderflowException(); + return *((T*)ptr); + }; + + template void Push(T value) + { + if(DP - sizeof(word) >= 0) + DP -= sizeof(word); + else + throw StackOverflowException(); + ram_t * ptr = ram+DP; + *((T*)ptr) = value; + }; + + template void RPush(T value) + { + if(RP - sizeof(word) >= 0) + RP -= sizeof(word); + else + throw StackOverflowException(); + ram_t * ptr = ram+RP; + *((T*)ptr) = value; + }; + + word Get(int address) + { + ram_t * ptr = ram+address; + if(ptr < ram || ptr > ram+ramSize - sizeof(word)) + throw AccessViolationException(address); + return *((word*)ptr); + } + + void Set(int address, word value) + { + ram_t * ptr = ram+address; + if(ptr < ram || ptr > ram+ramSize - sizeof(word)) + throw AccessViolationException(address); + *((word*)ptr) = value; + } + + word Pop() + { + ram_t * ptr = ram+DP; + if(DP + sizeof(word) < hunkSize) + DP += sizeof(word); + else + throw StackUnderflowException(); + return *((word*)ptr); + }; + + void Push(word value) + { + if(DP - sizeof(word) >= 0) + DP -= sizeof(word); + else + throw StackOverflowException(); + ram_t * ptr = ram+DP; + *((word*)ptr) = value; + }; + + word RPop() + { + ram_t * ptr = ram+RP; + if(RP + sizeof(word) < hunkSize) + RP += sizeof(word); + else + throw StackUnderflowException(); + return *((word*)ptr); + }; + + void RPush(word value) + { + if(RP - sizeof(word) >= 0) + RP -= sizeof(word); + else + throw StackOverflowException(); + ram_t * ptr = ram+RP; + *((word*)ptr) = value; + }; + }; + +} \ No newline at end of file