From 0504f4af8b4afd5695b5e2d0a15d1f07e09d9e34 Mon Sep 17 00:00:00 2001 From: Kayne Ruse Date: Thu, 26 Sep 2024 17:17:18 +1000 Subject: [PATCH] Began writing Toy_VM, read more Toy_VM and Toy_Stack are both considered WIP, and neither has any tests yet. --- source/toy_routine.c | 14 +- source/toy_stack.c | 72 ++++++++++ source/toy_stack.h | 18 +++ source/toy_value.h | 2 +- source/toy_vm.c | 303 +++++++++++++++++++++++++++++++++++++++++++ source/toy_vm.h | 36 +++++ 6 files changed, 437 insertions(+), 8 deletions(-) create mode 100644 source/toy_stack.c create mode 100644 source/toy_stack.h create mode 100644 source/toy_vm.c create mode 100644 source/toy_vm.h diff --git a/source/toy_routine.c b/source/toy_routine.c index 09687ba..d9d20cf 100644 --- a/source/toy_routine.c +++ b/source/toy_routine.c @@ -86,7 +86,7 @@ static void writeInstructionValue(Toy_Routine** rt, Toy_AstValue ast) { EMIT_FLOAT(rt, code, TOY_VALUE_AS_FLOAT(ast.value)); } else { - fprintf(stderr, TOY_CC_ERROR "Invalid AST type found: Unknown value type\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST type found: Unknown value type\n" TOY_CC_RESET); exit(-1); } } @@ -104,7 +104,7 @@ static void writeInstructionUnary(Toy_Routine** rt, Toy_AstUnary ast) { EMIT_BYTE(rt, 0); } else { - fprintf(stderr, TOY_CC_ERROR "Invalid AST unary flag found\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST unary flag found\n" TOY_CC_RESET); exit(-1); } } @@ -196,7 +196,7 @@ static void writeInstructionBinary(Toy_Routine** rt, Toy_AstBinary ast) { EMIT_BYTE(rt, TOY_OPCODE_OR); } else { - fprintf(stderr, TOY_CC_ERROR "Invalid AST binary flag found\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST binary flag found\n" TOY_CC_RESET); exit(-1); } @@ -237,24 +237,24 @@ static void writeRoutineCode(Toy_Routine** rt, Toy_Ast* ast) { //other disallowed instructions case TOY_AST_GROUP: - fprintf(stderr, TOY_CC_ERROR "Invalid AST type found: Group shouldn't be used\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST type found: Group shouldn't be used\n" TOY_CC_RESET); exit(-1); break; case TOY_AST_PASS: //NOTE: this should be disallowed, but for now it's required for testing - // fprintf(stderr, TOY_CC_ERROR "Invalid AST type found: Unknown pass\n" TOY_CC_RESET); + // fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST type found: Unknown pass\n" TOY_CC_RESET); // exit(-1); break; //meta instructions are disallowed case TOY_AST_ERROR: - fprintf(stderr, TOY_CC_ERROR "Invalid AST type found: Unknown error\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST type found: Unknown error\n" TOY_CC_RESET); exit(-1); break; case TOY_AST_END: - fprintf(stderr, TOY_CC_ERROR "Invalid AST type found: Unknown end\n" TOY_CC_RESET); + fprintf(stderr, TOY_CC_ERROR "ERROR: Invalid AST type found: Unknown end\n" TOY_CC_RESET); exit(-1); break; } diff --git a/source/toy_stack.c b/source/toy_stack.c new file mode 100644 index 0000000..776dc44 --- /dev/null +++ b/source/toy_stack.c @@ -0,0 +1,72 @@ +#include "toy_stack.h" +#include "toy_console_colors.h" + +#include "toy_memory.h" + +#include +#include + +//a good chunk of space +#define MIN_SIZE 64 + +TOY_API void Toy_initStack(Toy_Stack* stack) { + stack->ptr = NULL; + stack->capacity = 0; + stack->count = 0; +} + +void Toy_preallocateStack(Toy_Stack* stack) { + stack->capacity = MIN_SIZE; + stack->count = 0; + + stack->ptr = TOY_ALLOCATE(Toy_Value, stack->capacity); +} + +void Toy_freeStack(Toy_Stack* stack) { + //TODO: slip in a call to free the complex values here + + TOY_FREE_ARRAY(Toy_Value, stack->ptr, stack->capacity); + + Toy_initStack(stack); +} + +void Toy_pushStack(Toy_Stack* stack, Toy_Value value) { + //don't go overboard - limit to 1mb + if (stack->count >= 1024 * 1024 / sizeof(Toy_Value)) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Stack overflow, exiting\n" TOY_CC_RESET); + exit(-1); + } + + //expand the capacity if needed + if (stack->count + 1 > stack->capacity) { + int oldCapacity = stack->capacity; + stack->capacity = TOY_GROW_CAPACITY(stack->capacity); + stack->ptr = TOY_GROW_ARRAY(Toy_Value, stack->ptr, oldCapacity, stack->capacity); + } + + stack->ptr[stack->count++] = value; +} + +Toy_Value Toy_peekStack(Toy_Stack* stack) { + if (stack->count <= 0) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Stack underflow, exiting\n" TOY_CC_RESET); + exit(-1); + } + + return stack->ptr[stack->count - 1]; +} + +Toy_Value Toy_popStack(Toy_Stack* stack) { + if (stack->count <= 0) { + fprintf(stderr, TOY_CC_ERROR "ERROR: Stack underflow, exiting\n" TOY_CC_RESET); + exit(-1); + } + + //shrink if possible + if (stack->count > MIN_SIZE && stack->count < stack->capacity / 4) { + stack->ptr = TOY_SHRINK_ARRAY(Toy_Value, stack->ptr, stack->capacity, stack->capacity / 2); + stack->capacity /= 2; + } + + return stack->ptr[--stack->count]; +} diff --git a/source/toy_stack.h b/source/toy_stack.h new file mode 100644 index 0000000..0efc123 --- /dev/null +++ b/source/toy_stack.h @@ -0,0 +1,18 @@ +#pragma once + +#include "toy_common.h" +#include "toy_value.h" + +typedef struct Toy_Stack { + Toy_Value* ptr; + int capacity; + int count; +} Toy_Stack; + +TOY_API void Toy_initStack(Toy_Stack* stack); //null memory +TOY_API void Toy_preallocateStack(Toy_Stack* stack); //non-null memory, ready to go +TOY_API void Toy_freeStack(Toy_Stack* stack); + +TOY_API void Toy_pushStack(Toy_Stack* stack, Toy_Value value); +TOY_API Toy_Value Toy_peekStack(Toy_Stack* stack); +TOY_API Toy_Value Toy_popStack(Toy_Stack* stack); diff --git a/source/toy_value.h b/source/toy_value.h index f7afab1..b0e696c 100644 --- a/source/toy_value.h +++ b/source/toy_value.h @@ -28,7 +28,7 @@ typedef struct Toy_Value { } as; //4 Toy_ValueType type; //4 -} Toy_Value; +} Toy_Value; //8 #define TOY_VALUE_IS_NULL(value) ((value).type == TOY_VALUE_NULL) #define TOY_VALUE_IS_BOOLEAN(value) ((value).type == TOY_VALUE_BOOLEAN) diff --git a/source/toy_vm.c b/source/toy_vm.c new file mode 100644 index 0000000..42d05af --- /dev/null +++ b/source/toy_vm.c @@ -0,0 +1,303 @@ +#include "toy_vm.h" +#include "toy_console_colors.h" + +#include "toy_memory.h" +#include "toy_opcodes.h" +#include "toy_value.h" + +#include +#include +#include + +//utilities +#define READ_BYTE(vm) \ + vm->program[vm->programCounter++] + +#define READ_INT(vm) \ + *((int*)(vm->program + _read_postfix(&(vm->programCounter), 4))) + +#define READ_FLOAT(vm) \ + *((float*)(vm->program + _read_postfix(&(vm->programCounter), 4))) + +static inline int _read_postfix(int* ptr, int amount) { + int ret = *ptr; + *ptr += amount; + return ret; +} + +static inline void fix_alignment(Toy_VM* vm) { + if (vm->programCounter % 4 != 0) { + vm->programCounter = (4 - vm->programCounter % 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 process(Toy_VM* vm) { + 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: + // + // break; + + case TOY_OPCODE_COMPARE_LESS: + // + // break; + + case TOY_OPCODE_COMPARE_LESS_EQUAL: + // + // break; + + case TOY_OPCODE_COMPARE_GREATER: + // + // break; + + case TOY_OPCODE_COMPARE_GREATER_EQUAL: + // + // break; + + case TOY_OPCODE_AND: + // + // break; + + case TOY_OPCODE_OR: + // + // break; + + case TOY_OPCODE_TRUTHY: + // + // break; + + case TOY_OPCODE_NEGATE: //TODO: squeeze into != + // + // 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; + } +} + +//exposed functions +void Toy_initVM(Toy_VM* vm) { + vm->program = NULL; + vm->programSize = 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->programCounter = 0; + + //init the scope & stack + Toy_initStack(&vm->stack); +} + +void Toy_bindVM(Toy_VM* vm, unsigned char* program) { + vm->program = program; + + //read the header metadata + vm->programSize = READ_INT(vm); + vm->paramCount = READ_INT(vm); + vm->jumpsCount = READ_INT(vm); + vm->dataCount = READ_INT(vm); + vm->subsCount = READ_INT(vm); + + //read the header addresses + if (vm->paramCount > 0) { + vm->paramAddr = READ_INT(vm); + } + + vm->codeAddr = READ_INT(vm); + + if (vm->jumpsCount > 0) { + vm->jumpsAddr = READ_INT(vm); + } + + if (vm->dataCount > 0) { + vm->dataAddr = READ_INT(vm); + } + + if (vm->subsCount > 0) { + vm->subsAddr = READ_INT(vm); + } + + //preallocate the scope & stack + Toy_preallocateStack(&vm->stack); +} + +void Toy_runVM(Toy_VM* vm) { + //TODO: read params into scope + + //prep the program counter for execution + vm->programCounter = 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 + TOY_FREE_ARRAY(unsigned char, vm->program, vm->programSize); + Toy_initVM(vm); +} diff --git a/source/toy_vm.h b/source/toy_vm.h new file mode 100644 index 0000000..6ae8f99 --- /dev/null +++ b/source/toy_vm.h @@ -0,0 +1,36 @@ +#pragma once + +#include "toy_common.h" + +#include "toy_stack.h" + +typedef struct Toy_VM { + //bytecode - raw instructions that are being executed + unsigned char* program; + int programSize; + + int paramCount; + int jumpsCount; + int dataCount; + int subsCount; + + int paramAddr; + int codeAddr; + int jumpsAddr; + int dataAddr; + int subsAddr; + + int programCounter; + + //scope - block-level key/value pairs + + //stack - immediate-level values only + Toy_Stack stack; +} Toy_VM; + +TOY_API void Toy_initVM(Toy_VM* vm); +TOY_API void Toy_bindVM(Toy_VM* vm, unsigned char* program); +TOY_API void Toy_runVM(Toy_VM* vm); +TOY_API void Toy_freeVM(Toy_VM* vm); + +//TODO: inject extra data