#include "vm.h" #include "stdio.h" #include "stdarg.h" #include "string.h" // Number of scratch items for opcodes to use as working space. // Depends on VM implementation so it's declared here. #define WORKING_SIZE 4 typedef void (*VMOpCodeT)(VMContextT *context); void copyItem(PairT *item, PairT *source); void op_add(VMContextT *context); void op_boc(VMContextT *context); void op_exe(VMContextT *context); void op_jmp(VMContextT *context); void op_hlt(VMContextT *context); void op_pop(VMContextT *context); void op_puc(VMContextT *context); void op_puv(VMContextT *context); void op_sub(VMContextT *context); void readItemFromPC(VMContextT *context, PairT *item); void setStringFormat(PairT *item, char *format, ...); static VMOpCodeT opcodes[OPCODE_LAST]; void copyItem(PairT *item, PairT *source) { vmSetItem(item, source->text, &source->value); } void op_add(VMContextT *context) { bool isString0; bool isString1; vmPop(context, &context->working[0]); vmPop(context, &context->working[1]); isString0 = !vmIsItemNumber(&context->working[0]); isString1 = !vmIsItemNumber(&context->working[1]); // Both operands need to be numbers to result in a number. if (isString0 || isString1) { // Are both strings? if (isString0 && isString1) { setStringFormat(&context->working[2], "%s%s", context->working[0].text, context->working[1].text); } else { // Is the first a string? if (isString0) { setStringFormat(&context->working[2], "%s%d", context->working[0].text, context->working[1].value); } else { setStringFormat(&context->working[2], "%d%s", context->working[0].value, context->working[1].text); } } context->working[2].value = (jint16)strlen(context->working[2].text); } else { // Number context->working[2].value = context->working[0].value + context->working[1].value; } vmPush(context, &context->working[2]); } void op_boc(VMContextT *context) { bool isNumber0; bool isNumber2; int result; vmPop(context, &context->working[0]); // arg1 vmPop(context, &context->working[1]); // compairson vmPop(context, &context->working[2]); // arg2 vmPop(context, &context->working[3]); // target pc isNumber0 = vmIsItemNumber(&context->working[0]); isNumber2 = vmIsItemNumber(&context->working[2]); // Both Numbers? if (isNumber0 && isNumber2) { switch (context->working[1].value) { case 0: // = if (context->working[0].value == context->working[2].value) { context->private.pc = (juint16)context->working[3].value; } break; case 1: // ! if (context->working[0].value != context->working[2].value) { context->private.pc = (juint16)context->working[3].value; } break; case 2: // < if (context->working[0].value < context->working[2].value) { context->private.pc = (juint16)context->working[3].value; } break; case 3: // > if (context->working[0].value > context->working[2].value) { context->private.pc = (juint16)context->working[3].value; } break; } } else { // Which side is a number? if (isNumber0) { setStringFormat(&context->working[2], "%d", context->working[2].value); } else { setStringFormat(&context->working[0], "%d", context->working[0].value); } result = strcmp(context->working[0].text, context->working[2].text); switch (context->working[1].value) { case 0: // = if (result == 0) { context->private.pc = (juint16)context->working[3].value; } break; case 1: // ! if (result != 0) { context->private.pc = (juint16)context->working[3].value; } break; case 2: // < if (result < 0) { context->private.pc = (juint16)context->working[3].value; } break; case 3: // > if (result > 0) { context->private.pc = (juint16)context->working[3].value; } break; } } } void op_exe(VMContextT *context) { vmPop(context, &context->working[0]); // Arg count vmPop(context, &context->working[1]); // Function number context->private.functions[context->working[1].value](context, (juint16)(context->working[0].value) - 1); } void op_jmp(VMContextT *context) { vmPop(context, &context->working[0]); //printf("Jumping to %04x\n", (juint16)context->working[0].value); context->private.pc = (juint16)context->working[0].value; } void op_hlt(VMContextT *context) { (void)(context); // We don't actually need to do anything for HLT. } void op_pop(VMContextT *context) { readItemFromPC(context, &context->working[0]); vmPop(context, &context->working[1]); copyItem(&context->private.variables[context->working[0].value], &context->working[1]); } void op_puc(VMContextT *context) { readItemFromPC(context, &context->working[0]); vmPush(context, &context->working[0]); } void op_puv(VMContextT *context) { readItemFromPC(context, &context->working[0]); vmPush(context, &context->private.variables[context->working[0].value]); } void op_sub(VMContextT *context) { vmPop(context, &context->working[0]); vmPop(context, &context->working[1]); // Number context->working[2].value = context->working[0].value - context->working[1].value; vmPush(context, &context->working[2]); } void readItemFromPC(VMContextT *context, PairT *item) { byte b1; byte b2; int y; //printf("Reading item at offset %04x - %02x\n", context->private.pc + context->private.headerBytes, context->private.memory[context->private.pc]); // Get the length/value of this item b1 = context->private.memory[context->private.pc++]; b2 = context->private.memory[context->private.pc++]; #ifdef JOEY_BIG_ENDIAN item->value = (jint16)((b1 << 8) + b2); #else item->value = (jint16)((b2 << 8) + b1); #endif // If this is a zero, it's a number. If it's not, it's a string if (context->private.memory[context->private.pc] > 0) { if (item->allocated < item->value + 1) { item->text = (char *)jlRealloc(item->text, (size_t)(item->value + 1)); item->allocated = item->value + 1; } for (y=0; yvalue; y++) { item->text[y] = (char)context->private.memory[context->private.pc++]; } item->text[y] = 0; } else { context->private.pc++; item->text[0] = 0; } //printf("Ending read (inclusive) at offset %04x - %02x\n", context->private.pc + context->private.headerBytes - 1, context->private.memory[context->private.pc - 1]); } void vmSetItem(PairT *item, char *text, jint16 *value) { juint16 len; if ((text != 0) && (text[0] != 0)) { len = (juint16)strlen(text) + 1; if (item->allocated < len) { item->text = (char *)jlRealloc(item->text, len); item->allocated = (jint16)len; } strcpy(item->text, text); item->value = (jint16)len - 1; } else { if (item->text != 0) { item->text[0] = 0; } } if (value != 0) { item->value = *value; } } #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wformat-nonliteral" void setStringFormat(PairT *item, char *format, ...) { int len; va_list args; // Do we have a buffer at all? if (item->allocated == 0) { item->text = (char *)jlMalloc(16 * sizeof(char)); item->allocated = 16; } // Attempt to write formatted string into buffer. va_start(args, format); len = vsnprintf(item->text, (unsigned long)item->allocated, format, args); va_end(args); // Did it succeed? if (len >= item->allocated) { // Nope! Allocate more memory and do it again. item->text = (char *)jlRealloc(item->text, (unsigned long)len); item->allocated = (jint16)len; va_start(args, format); len = vsnprintf(item->text, (unsigned long)item->allocated, format, args); va_end(args); } } #pragma GCC diagnostic pop bool vmAddFunction(VMContextT *context, VMFuncT function) { context->private.functionCount++; context->private.functions = (VMFuncT *)jlRealloc(context->private.functions, sizeof(VMFuncT) * context->private.functionCount); context->private.functions[context->private.functionCount - 1] = function; return true; } bool vmCreate(VMContextT *context) { juint16 x; // This can be redundant but it doesn't hurt anything. opcodes[OPCODE_ADD - 1] = op_add; opcodes[OPCODE_BOC - 1] = op_boc; opcodes[OPCODE_EXE - 1] = op_exe; opcodes[OPCODE_JMP - 1] = op_jmp; opcodes[OPCODE_HLT - 1] = op_hlt; opcodes[OPCODE_POP - 1] = op_pop; opcodes[OPCODE_PUC - 1] = op_puc; opcodes[OPCODE_PUV - 1] = op_puv; opcodes[OPCODE_SUB - 1] = op_sub; context->private.sp = 0; context->private.pc = 0; context->private.headerBytes = 0; // Stack context->private.stack = 0; context->private.stackCount = 0; // All variables context->private.variables = 0; context->private.variablesCount = 0; // Native function pointers context->private.functions = 0; context->private.functionCount = 0; // Program space context->private.memory = 0; context->private.programSize = 0; // Exported Labels context->private.labels = 0; context->private.labelCount = 0; // Exported Variables context->private.variableTable = 0; context->private.variableTableCount = 0; // Working RAM context->working = (PairT *)jlMalloc(sizeof(PairT) * WORKING_SIZE); for (x=0; xworking[x].text = 0; context->working[x].allocated = 0; context->working[x].value = 0; } return true; } bool vmDestroy(VMContextT *context) { juint16 x; // Working RAM for (x=0; xworking[x].text); } jlFree(context->working); // Program space jlFree(context->private.memory); // Native function pointers jlFree(context->private.functions); // Label Jump Table for (x=0; xprivate.labelCount; x++) { jlFree(context->private.labels[x].text); } jlFree(context->private.labels); // Exported Varaibles Index for (x=0; xprivate.variableTableCount; x++) { jlFree(context->private.variableTable[x].text); } jlFree(context->private.variableTable); // All variables for (x=0; xprivate.variablesCount; x++) { jlFree(context->private.variables[x].text); } jlFree(context->private.variables); // Stack for (x=0; xprivate.stackCount; x++) { jlFree(context->private.stack[x].text); } jlFree(context->private.stack); return true; } bool vmGetPCIndex(VMContextT *context, char *label, juint16 *pc) { juint16 x; bool result = false; for (x=0; xprivate.labelCount; x++) { if (strcmp(context->private.labels[x].text, label) == 0) { *pc = context->private.labels[x].value; result = true; break; } } return result; } bool vmGetVariable(VMContextT *context, char *name, PairT *item) { juint16 x; item = NULL; if (vmGetVariableIndex(context, name, &x)) { *item = context->private.variables[x]; } return (item != NULL); } bool vmGetVariableIndex(VMContextT *context, char *name, juint16 *index) { juint16 x; for (x=0; xprivate.variableTableCount; x++) { if (strcmp(context->private.variableTable[x].text, name) == 0) { *index = context->private.variableTable[x].value; return true; } } return false; } bool vmIsItemNumber(PairT *item) { return (item->text == 0) || (item->text[0] == 0); } bool vmLoad(VMContextT *context, char *filename) { char temp[4]; FILE *binFile; juint16 tempUnsigned; juint16 x; juint16 y; int length; binFile = fopen(filename, "rb"); if (!binFile) { return false; } // Get file size for later fseek(binFile, 0, SEEK_END); length = (int)ftell(binFile); fseek(binFile, 0, SEEK_SET); // Read header & Version Number fread(temp, 4, 1, binFile); if ((temp[0] != 'D') || (temp[1] != 'B') || (temp[2] != 'L') || (temp[3] != 0)) { fclose(binFile); return false; } // Read exported label entries fread(&tempUnsigned, sizeof(juint16), 1, binFile); #ifdef JOEY_BIG_ENDIAN context->private.labelCount = jlByteSwap(tempUnsigned); #else context->private.labelCount = tempUnsigned; #endif context->private.labels = (UPairT *)jlMalloc(sizeof(UPairT) * context->private.labelCount); for (x=0; xprivate.labelCount; x++) { fread(&tempUnsigned, sizeof(juint16), 1, binFile); // Address of label #ifdef JOEY_BIG_ENDIAN context->private.labels[x].value = jlByteSwap(tempUnsigned); #else context->private.labels[x].value = tempUnsigned; #endif y = (juint16)fgetc(binFile); // Length of label name // Label Name context->private.labels[x].text = (char *)jlMalloc((y + 1) * sizeof(char)); fread(context->private.labels[x].text, sizeof(char), y, binFile); context->private.labels[x].text[y] = 0; context->private.labels[x].allocated = (jint16)(y + 1); } // Read exported variable entries fread(&tempUnsigned, sizeof(juint16), 1, binFile); #ifdef JOEY_BIG_ENDIAN context->private.variableTableCount = jlByteSwap(tempUnsigned); #else context->private.variableTableCount = tempUnsigned; #endif context->private.variableTable = (UPairT *)jlMalloc(sizeof(UPairT) * context->private.variableTableCount); for (x=0; xprivate.variableTableCount; x++) { fread(&tempUnsigned, sizeof(juint16), 1, binFile); #ifdef JOEY_BIG_ENDIAN context->private.variableTable[x].value = jlByteSwap(tempUnsigned); #else context->private.variableTable[x].value = tempUnsigned; #endif y = (juint16)fgetc(binFile); // Length of variable name // Variable Name context->private.variableTable[x].text = (char *)jlMalloc((y + 1) * sizeof(char)); fread(context->private.variableTable[x].text, sizeof(char), y, binFile); context->private.variableTable[x].text[y] = 0; context->private.variableTable[x].allocated = (jint16)(y + 1); } // Read total variable count fread(&tempUnsigned, sizeof(juint16), 1, binFile); #ifdef JOEY_BIG_ENDIAN context->private.variablesCount = jlByteSwap(tempUnsigned); #else context->private.variablesCount = tempUnsigned; #endif context->private.variables = (PairT *)jlMalloc(sizeof(PairT) * context->private.variablesCount); for (x=0; xprivate.variablesCount; x++) { context->private.variables->text = 0; context->private.variables->allocated = 0; context->private.variables->value = 0; } // Record position in file for debugging purposes context->private.headerBytes = (juint16)ftell(binFile); //printf("PC offset is %04x\n", context->private.headerBytes); // Read program byte stream context->private.programSize = (juint16)(length - ftell(binFile)); context->private.memory = (byte *)jlMalloc(sizeof(byte) * context->private.programSize); fread(context->private.memory, sizeof(char) * context->private.programSize, 1, binFile); fclose(binFile); return true; } bool vmPop(VMContextT *context, PairT *item) { if (context->private.sp > 0) { context->private.sp--; copyItem(item, &context->private.stack[context->private.sp]); return true; } return false; } bool vmPush(VMContextT *context, PairT *item) { // Grow stack if needed. if (context->private.sp + 1 > context->private.stackCount) { context->private.stackCount = context->private.sp + 1; context->private.stack = (PairT *)jlRealloc(context->private.stack, sizeof(PairT) * context->private.stackCount); context->private.stack[context->private.sp].text = 0; context->private.stack[context->private.sp].allocated = 0; context->private.stack[context->private.sp].value = 0; } copyItem(&context->private.stack[context->private.sp], item); context->private.sp++; return true; } bool vmSetPC(VMContextT *context, char *label) { juint16 pc; bool result = false; if (label == NULL) { context->private.pc = 0; result = true; } else { if (vmGetPCIndex(context, label, &pc)) { context->private.pc = pc; result = true; } } return result; } bool vmSetPCByIndex(VMContextT *context, juint16 index) { context->private.pc = index; // Currently, the PC is the index we return return true; } bool vmSetVariable(VMContextT *context, char *name, PairT *item) { PairT *target = NULL; if (vmGetVariable(context, name, target)) { copyItem(target, item); return true; } return false; } bool vmSetVariableByIndex(VMContextT *context, jint16 index, PairT *item) { copyItem(&context->private.variables[index], item); return true; } bool vmStep(VMContextT *context) { byte opcode = context->private.memory[context->private.pc++]; //printf("Fetched opcode at file offset %04x (PC %04x): %02x\n", context->private.pc + context->private.headerBytes - 1, context->private.pc - 1, opcode); opcodes[opcode - 1](context); return (opcode != OPCODE_HLT); } bool vmRun(VMContextT *context) { while (vmStep(context)) ; return true; }