dbltw/vm/vm.c
2020-05-13 19:08:25 -05:00

613 lines
16 KiB
C

#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; y<item->value; 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; x<WORKING_SIZE; x++) {
context->working[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; x<WORKING_SIZE; x++) {
jlFree(context->working[x].text);
}
jlFree(context->working);
// Program space
jlFree(context->private.memory);
// Native function pointers
jlFree(context->private.functions);
// Label Jump Table
for (x=0; x<context->private.labelCount; x++) {
jlFree(context->private.labels[x].text);
}
jlFree(context->private.labels);
// Exported Varaibles Index
for (x=0; x<context->private.variableTableCount; x++) {
jlFree(context->private.variableTable[x].text);
}
jlFree(context->private.variableTable);
// All variables
for (x=0; x<context->private.variablesCount; x++) {
jlFree(context->private.variables[x].text);
}
jlFree(context->private.variables);
// Stack
for (x=0; x<context->private.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; x<context->private.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; x<context->private.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; x<context->private.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; x<context->private.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; x<context->private.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;
}