#include "toy_vm.h" #include "toy_console_colors.h" #include "toy_opcodes.h" #include "toy_value.h" #include #include #include //utilities #define READ_BYTE(vm) \ vm->routine[vm->routineCounter++] #define READ_UNSIGNED_INT(vm) \ *((unsigned int*)(vm->routine + _read_postfix(&(vm->routineCounter), 4))) #define READ_INT(vm) \ *((int*)(vm->routine + _read_postfix(&(vm->routineCounter), 4))) #define READ_FLOAT(vm) \ *((float*)(vm->routine + _read_postfix(&(vm->routineCounter), 4))) static inline int _read_postfix(unsigned int* ptr, int amount) { int ret = *ptr; *ptr += amount; return ret; } static inline void fix_alignment(Toy_VM* vm) { if (vm->routineCounter % 4 != 0) { vm->routineCounter += (4 - vm->routineCounter % 4); } } //instruction handlers static void processRead(Toy_VM* vm) { Toy_ValueType type = READ_BYTE(vm); Toy_Value value = TOY_VALUE_TO_NULL(); switch(type) { case TOY_VALUE_NULL: { //No-op break; } case TOY_VALUE_BOOLEAN: { value = TOY_VALUE_TO_BOOLEAN((bool)READ_BYTE(vm)); break; } case TOY_VALUE_INTEGER: { fix_alignment(vm); value = TOY_VALUE_TO_INTEGER(READ_INT(vm)); break; } case TOY_VALUE_FLOAT: { fix_alignment(vm); value = TOY_VALUE_TO_FLOAT(READ_FLOAT(vm)); break; } case TOY_VALUE_STRING: { // // break; } case TOY_VALUE_ARRAY: { // // break; } case TOY_VALUE_DICTIONARY: { // // break; } case TOY_VALUE_FUNCTION: { // // break; } case TOY_VALUE_OPAQUE: { // // break; } default: fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid value type %d found, exiting\n" TOY_CC_RESET, type); exit(-1); } //push onto the stack Toy_pushStack(&vm->stack, value); //leave the counter in a good spot fix_alignment(vm); } static void processArithmetic(Toy_VM* vm, Toy_OpcodeType opcode) { Toy_Value right = Toy_popStack(&vm->stack); Toy_Value left = Toy_popStack(&vm->stack); //check types if ((!TOY_VALUE_IS_INTEGER(left) && !TOY_VALUE_IS_FLOAT(left)) || (!TOY_VALUE_IS_INTEGER(right) && !TOY_VALUE_IS_FLOAT(right))) { fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid types %d and %d passed to processArithmetic, exiting\n" TOY_CC_RESET, left.type, right.type); exit(-1); } //check for divide by zero if (opcode == TOY_OPCODE_DIVIDE || opcode == TOY_OPCODE_MODULO) { if ((TOY_VALUE_IS_INTEGER(right) && TOY_VALUE_AS_INTEGER(right) == 0) || (TOY_VALUE_IS_FLOAT(right) && TOY_VALUE_AS_FLOAT(right) == 0)) { fprintf(stderr, TOY_CC_ERROR "ERROR: Can't divide by zero, exiting\n" TOY_CC_RESET); exit(-1); } } //check for modulo by a float if (opcode == TOY_OPCODE_MODULO && TOY_VALUE_IS_FLOAT(right)) { fprintf(stderr, TOY_CC_ERROR "ERROR: Can't modulo by a float, exiting\n" TOY_CC_RESET); exit(-1); } //coerce ints into floats if needed if (TOY_VALUE_IS_INTEGER(left) && TOY_VALUE_IS_FLOAT(right)) { left = TOY_VALUE_TO_FLOAT( (float)TOY_VALUE_AS_INTEGER(left) ); } else if (TOY_VALUE_IS_FLOAT(left) && TOY_VALUE_IS_INTEGER(right)) { right = TOY_VALUE_TO_FLOAT( (float)TOY_VALUE_AS_INTEGER(right) ); } //apply operation Toy_Value result = TOY_VALUE_TO_NULL(); if (opcode == TOY_OPCODE_ADD) { result = TOY_VALUE_IS_FLOAT(left) ? TOY_VALUE_TO_FLOAT( TOY_VALUE_AS_FLOAT(left) + TOY_VALUE_AS_FLOAT(right)) : TOY_VALUE_TO_INTEGER( TOY_VALUE_AS_INTEGER(left) + TOY_VALUE_AS_INTEGER(right) ); } else if (opcode == TOY_OPCODE_SUBTRACT) { result = TOY_VALUE_IS_FLOAT(left) ? TOY_VALUE_TO_FLOAT( TOY_VALUE_AS_FLOAT(left) - TOY_VALUE_AS_FLOAT(right)) : TOY_VALUE_TO_INTEGER( TOY_VALUE_AS_INTEGER(left) - TOY_VALUE_AS_INTEGER(right) ); } else if (opcode == TOY_OPCODE_MULTIPLY) { result = TOY_VALUE_IS_FLOAT(left) ? TOY_VALUE_TO_FLOAT( TOY_VALUE_AS_FLOAT(left) * TOY_VALUE_AS_FLOAT(right)) : TOY_VALUE_TO_INTEGER( TOY_VALUE_AS_INTEGER(left) * TOY_VALUE_AS_INTEGER(right) ); } else if (opcode == TOY_OPCODE_DIVIDE) { result = TOY_VALUE_IS_FLOAT(left) ? TOY_VALUE_TO_FLOAT( TOY_VALUE_AS_FLOAT(left) / TOY_VALUE_AS_FLOAT(right)) : TOY_VALUE_TO_INTEGER( TOY_VALUE_AS_INTEGER(left) / TOY_VALUE_AS_INTEGER(right) ); } else if (opcode == TOY_OPCODE_MODULO) { result = TOY_VALUE_TO_INTEGER( TOY_VALUE_AS_INTEGER(left) % TOY_VALUE_AS_INTEGER(right) ); } else { fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid opcode %d passed to processArithmetic, exiting\n" TOY_CC_RESET, opcode); exit(-1); } //finally Toy_pushStack(&vm->stack, result); } static void processComparison(Toy_VM* vm, Toy_OpcodeType opcode) { Toy_Value right = Toy_popStack(&vm->stack); Toy_Value left = Toy_popStack(&vm->stack); //most things can be equal, so handle it separately if (opcode == TOY_OPCODE_COMPARE_EQUAL) { Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN(TOY_VALUE_IS_EQUAL(left, right)) ); return; } //coerce ints into floats if needed if (TOY_VALUE_IS_INTEGER(left) && TOY_VALUE_IS_FLOAT(right)) { left = TOY_VALUE_TO_FLOAT( (float)TOY_VALUE_AS_INTEGER(left) ); } else if (TOY_VALUE_IS_FLOAT(left) && TOY_VALUE_IS_INTEGER(right)) { right = TOY_VALUE_TO_FLOAT( (float)TOY_VALUE_AS_INTEGER(right) ); } //other opcodes if (opcode == TOY_OPCODE_COMPARE_LESS) { Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN(TOY_VALUE_IS_FLOAT(left) ? TOY_VALUE_AS_FLOAT(left) < TOY_VALUE_AS_FLOAT(right) : TOY_VALUE_AS_INTEGER(left) < TOY_VALUE_AS_INTEGER(right)) ); } else if (opcode == TOY_OPCODE_COMPARE_LESS_EQUAL) { Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN(TOY_VALUE_IS_FLOAT(left) ? TOY_VALUE_AS_FLOAT(left) <= TOY_VALUE_AS_FLOAT(right) : TOY_VALUE_AS_INTEGER(left) <= TOY_VALUE_AS_INTEGER(right)) ); } else if (opcode == TOY_OPCODE_COMPARE_GREATER) { Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN(TOY_VALUE_IS_FLOAT(left) ? TOY_VALUE_AS_FLOAT(left) > TOY_VALUE_AS_FLOAT(right) : TOY_VALUE_AS_INTEGER(left) > TOY_VALUE_AS_INTEGER(right)) ); } else if (opcode == TOY_OPCODE_COMPARE_GREATER_EQUAL) { Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN(TOY_VALUE_IS_FLOAT(left) ? TOY_VALUE_AS_FLOAT(left) >= TOY_VALUE_AS_FLOAT(right) : TOY_VALUE_AS_INTEGER(left) >= TOY_VALUE_AS_INTEGER(right)) ); } else { fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid opcode %d passed to processComparison, exiting\n" TOY_CC_RESET, opcode); exit(-1); } } static void processLogical(Toy_VM* vm, Toy_OpcodeType opcode) { if (opcode == TOY_OPCODE_AND) { Toy_Value right = Toy_popStack(&vm->stack); Toy_Value left = Toy_popStack(&vm->stack); Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN( TOY_VALUE_IS_TRUTHY(left) && TOY_VALUE_IS_TRUTHY(right) )); } else if (opcode == TOY_OPCODE_OR) { Toy_Value right = Toy_popStack(&vm->stack); Toy_Value left = Toy_popStack(&vm->stack); Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN( TOY_VALUE_IS_TRUTHY(left) || TOY_VALUE_IS_TRUTHY(right) )); } else if (opcode == TOY_OPCODE_TRUTHY) { Toy_Value top = Toy_popStack(&vm->stack); Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN( TOY_VALUE_IS_TRUTHY(top) )); } else if (opcode == TOY_OPCODE_NEGATE) { Toy_Value top = Toy_popStack(&vm->stack); Toy_pushStack(&vm->stack, TOY_VALUE_TO_BOOLEAN( !TOY_VALUE_IS_TRUTHY(top) )); } else { fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid opcode %d passed to processLogical, exiting\n" TOY_CC_RESET, opcode); exit(-1); } } static void process(Toy_VM* vm) { while(true) { Toy_OpcodeType opcode = READ_BYTE(vm); switch(opcode) { case TOY_OPCODE_READ: processRead(vm); break; case TOY_OPCODE_ADD: case TOY_OPCODE_SUBTRACT: case TOY_OPCODE_MULTIPLY: case TOY_OPCODE_DIVIDE: case TOY_OPCODE_MODULO: processArithmetic(vm, opcode); break; case TOY_OPCODE_COMPARE_EQUAL: case TOY_OPCODE_COMPARE_LESS: case TOY_OPCODE_COMPARE_LESS_EQUAL: case TOY_OPCODE_COMPARE_GREATER: case TOY_OPCODE_COMPARE_GREATER_EQUAL: processComparison(vm, opcode); break; case TOY_OPCODE_AND: case TOY_OPCODE_OR: case TOY_OPCODE_TRUTHY: case TOY_OPCODE_NEGATE: //TODO: squeeze into != processLogical(vm, opcode); break; case TOY_OPCODE_LOAD: case TOY_OPCODE_LOAD_LONG: case TOY_OPCODE_DECLARE: case TOY_OPCODE_ASSIGN: case TOY_OPCODE_ACCESS: case TOY_OPCODE_PASS: case TOY_OPCODE_ERROR: case TOY_OPCODE_EOF: fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid opcode %d found, exiting\n" TOY_CC_RESET, opcode); exit(-1); case TOY_OPCODE_RETURN: //temp terminator, temp position // return; } //prepare for the next instruction fix_alignment(vm); } } //exposed functions void Toy_bindVM(Toy_VM* vm, unsigned char* bytecode, unsigned int bytecodeSize) { if (bytecode[0] != TOY_VERSION_MAJOR || bytecode[1] > TOY_VERSION_MINOR) { fprintf(stderr, TOY_CC_ERROR "ERROR: Wrong bytecode version found: expected %d.%d.%d found %d.%d.%d, exiting\n" TOY_CC_RESET, TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH, bytecode[0], bytecode[1], bytecode[2]); exit(-1); } if (bytecode[2] != TOY_VERSION_PATCH) { fprintf(stderr, TOY_CC_WARN "WARNING: Wrong bytecode version found: expected %d.%d.%d found %d.%d.%d, continuing\n" TOY_CC_RESET, TOY_VERSION_MAJOR, TOY_VERSION_MINOR, TOY_VERSION_PATCH, bytecode[0], bytecode[1], bytecode[2]); } if (strcmp((char*)(bytecode + 3), TOY_VERSION_BUILD) != 0) { fprintf(stderr, TOY_CC_WARN "WARNING: Wrong bytecode build info found: expected '%s' found '%s', continuing\n" TOY_CC_RESET, TOY_VERSION_BUILD, (char*)(bytecode + 3)); } //offset by the header size int offset = 3 + strlen(TOY_VERSION_BUILD) + 1; if (offset % 4 != 0) { offset += 4 - (offset % 4); //ceil } //delegate Toy_bindVMToRoutine(vm, bytecode + offset); //cache these vm->bc = bytecode; vm->bcSize = bytecodeSize; } void Toy_bindVMToRoutine(Toy_VM* vm, unsigned char* routine) { Toy_resetVM(vm); vm->routine = routine; //read the header metadata vm->routineSize = READ_UNSIGNED_INT(vm); vm->paramCount = READ_UNSIGNED_INT(vm); vm->jumpsCount = READ_UNSIGNED_INT(vm); vm->dataCount = READ_UNSIGNED_INT(vm); vm->subsCount = READ_UNSIGNED_INT(vm); //read the header addresses if (vm->paramCount > 0) { vm->paramAddr = READ_UNSIGNED_INT(vm); } vm->codeAddr = READ_UNSIGNED_INT(vm); //required if (vm->jumpsCount > 0) { vm->jumpsAddr = READ_UNSIGNED_INT(vm); } if (vm->dataCount > 0) { vm->dataAddr = READ_UNSIGNED_INT(vm); } if (vm->subsCount > 0) { vm->subsAddr = READ_UNSIGNED_INT(vm); } //preallocate the scope & stack vm->stack = Toy_allocateStack(); } void Toy_runVM(Toy_VM* vm) { //TODO: read params into scope //prep the routine counter for execution vm->routineCounter = vm->codeAddr; //begin process(vm); } void Toy_freeVM(Toy_VM* vm) { //clear the stack Toy_freeStack(vm->stack); //TODO: clear the scope //free the bytecode free(vm->bc); Toy_resetVM(vm); } void Toy_resetVM(Toy_VM* vm) { vm->bc = NULL; vm->bcSize = 0; vm->routine = NULL; vm->routineSize = 0; vm->paramCount = 0; vm->jumpsCount = 0; vm->dataCount = 0; vm->subsCount = 0; vm->paramAddr = 0; vm->codeAddr = 0; vm->jumpsAddr = 0; vm->dataAddr = 0; vm->subsAddr = 0; vm->routineCounter = 0; //init the scope & stack vm->stack = NULL; }