diff options
Diffstat (limited to 'vm/runtime.c')
-rw-r--r-- | vm/runtime.c | 777 |
1 files changed, 777 insertions, 0 deletions
diff --git a/vm/runtime.c b/vm/runtime.c new file mode 100644 index 0000000..ccda9d0 --- /dev/null +++ b/vm/runtime.c @@ -0,0 +1,777 @@ +/* Copyright (C) 2023 Aryadev Chavali + + * You may distribute and modify this code under the terms of the + * GPLv2 license. You should have received a copy of the GPLv2 + * license with this file. If not, please write to: + * aryadev@aryadevchavali.com. + + * Created: 2023-10-15 + * Author: Aryadev Chavali + * Description: Virtual machine implementation + */ + +#include <assert.h> +#include <inttypes.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "./runtime.h" + +const char *err_as_cstr(err_t err) +{ + switch (err) + { + case ERR_OK: + return "OK"; + break; + case ERR_STACK_UNDERFLOW: + return "STACK_UNDERFLOW"; + break; + case ERR_STACK_OVERFLOW: + return "STACK_OVERFLOW"; + break; + case ERR_INVALID_OPCODE: + return "INVALID_OPCODE"; + break; + case ERR_INVALID_REGISTER_BYTE: + return "INVALID_REGISTER_BYTE"; + break; + case ERR_INVALID_REGISTER_HWORD: + return "INVALID_REGISTER_HWORD"; + break; + case ERR_INVALID_REGISTER_WORD: + return "INVALID_REGISTER_WORD"; + break; + case ERR_INVALID_PROGRAM_ADDRESS: + return "INVALID_PROGRAM_ADDRESS"; + case ERR_END_OF_PROGRAM: + return "END_OF_PROGRAM"; + break; + default: + return ""; + } +} + +err_t vm_execute(vm_t *vm) +{ + static_assert(NUMBER_OF_OPCODES == 46, "vm_execute: Out of date"); + struct Program *prog = &vm->program; + if (prog->ptr >= prog->max) + return ERR_END_OF_PROGRAM; + inst_t instruction = prog->instructions[prog->ptr]; + + if (OPCODE_IS_TYPE(instruction.opcode, OP_PUSH)) + { + prog->ptr++; + return PUSH_ROUTINES[instruction.opcode](vm, instruction.operand); + } + else if (OPCODE_IS_TYPE(instruction.opcode, OP_MOV) || + OPCODE_IS_TYPE(instruction.opcode, OP_PUSH_REGISTER)) + { + prog->ptr++; + return REG_ROUTINES[instruction.opcode](vm, instruction.operand.as_byte); + } + else if (OPCODE_IS_TYPE(instruction.opcode, OP_POP)) + { + // NOTE: We use the first register to hold the result of this pop + data_type_t type = OPCODE_DATA_TYPE(instruction.opcode, OP_POP); + prog->ptr++; + switch (type) + { + case DATA_TYPE_NIL: + break; + case DATA_TYPE_BYTE: + return vm_mov_byte(vm, 0); + break; + case DATA_TYPE_HWORD: + return vm_mov_hword(vm, 0); + break; + case DATA_TYPE_WORD: + return vm_mov_word(vm, 0); + break; + } + return ERR_OK; + } + else if (OPCODE_IS_TYPE(instruction.opcode, OP_DUP)) + { + prog->ptr++; + return DUP_ROUTINES[instruction.opcode](vm, instruction.operand.as_word); + } + else if (OPCODE_IS_TYPE(instruction.opcode, OP_NOT) || + OPCODE_IS_TYPE(instruction.opcode, OP_OR) || + OPCODE_IS_TYPE(instruction.opcode, OP_AND) || + OPCODE_IS_TYPE(instruction.opcode, OP_XOR) || + OPCODE_IS_TYPE(instruction.opcode, OP_EQ) || + OPCODE_IS_TYPE(instruction.opcode, OP_PLUS)) + { + prog->ptr++; + return STACK_ROUTINES[instruction.opcode](vm); + } + else if (instruction.opcode == OP_JUMP_ABS) + return vm_jump(vm, instruction.operand.as_word); + else if (instruction.opcode == OP_JUMP_STACK) + { + // Set prog->ptr to the word on top of the stack + data_t ret = {0}; + err_t err = vm_pop_word(vm, &ret); + if (err) + return err; + return vm_jump(vm, ret.as_word); + } + else if (instruction.opcode == OP_JUMP_REGISTER) + { + if (instruction.operand.as_byte >= 8) + return ERR_INVALID_REGISTER_WORD; + word addr = vm->registers.reg[instruction.operand.as_byte]; + return vm_jump(vm, addr); + } + else if (OPCODE_IS_TYPE(instruction.opcode, OP_JUMP_IF)) + { + data_t datum = {0}; + err_t err = ERR_OK; + if (instruction.opcode == OP_JUMP_IF_BYTE) + err = vm_pop_byte(vm, &datum); + else if (instruction.opcode == OP_JUMP_IF_HWORD) + err = vm_pop_hword(vm, &datum); + else if (instruction.opcode == OP_JUMP_IF_WORD) + err = vm_pop_word(vm, &datum); + + if (err) + return err; + + // If datum != 0 then jump, else go to the next instruction + if (datum.as_word != 0) + return vm_jump(vm, instruction.operand.as_word); + else + ++prog->ptr; + } + else if (instruction.opcode >= OP_PRINT_CHAR && + instruction.opcode <= OP_PRINT_WORD) + { + data_t datum = {0}; + enum + { + TYPE_CHAR, + TYPE_BYTE, + TYPE_INT, + TYPE_HWORD, + TYPE_LONG, + TYPE_WORD + } print_type; + err_t err = ERR_OK; + if (instruction.opcode == OP_PRINT_BYTE || + instruction.opcode == OP_PRINT_CHAR) + { + print_type = instruction.opcode == OP_PRINT_BYTE ? TYPE_BYTE : TYPE_CHAR; + err = vm_pop_byte(vm, &datum); + } + else if (instruction.opcode == OP_PRINT_HWORD || + instruction.opcode == OP_PRINT_INT) + { + print_type = instruction.opcode == OP_PRINT_HWORD ? TYPE_HWORD : TYPE_INT; + err = vm_pop_hword(vm, &datum); + } + else if (instruction.opcode == OP_PRINT_WORD || + instruction.opcode == OP_PRINT_LONG) + { + print_type = instruction.opcode == OP_PRINT_WORD ? TYPE_WORD : TYPE_LONG; + err = vm_pop_word(vm, &datum); + } + + if (err) + return err; + + switch (print_type) + { + case TYPE_CHAR: { + char c = 0; + memcpy(&c, &datum.as_byte, 1); + printf("%c", c); + break; + } + case TYPE_BYTE: + printf("0x%x", datum.as_byte); + break; + case TYPE_INT: { + int32_t i = 0; + memcpy(&i, &datum.as_hword, HWORD_SIZE); + printf("%" PRId32, i); + break; + } + case TYPE_HWORD: + printf("%" PRIu32, datum.as_hword); + break; + case TYPE_LONG: { + int64_t i = 0; + memcpy(&i, &datum.as_word, WORD_SIZE); + printf("%" PRId64, i); + break; + } + case TYPE_WORD: + printf("%" PRIu64, datum.as_word); + break; + } + + prog->ptr++; + } + else if (instruction.opcode == OP_HALT) + { + // Do nothing here. Should be caught by callers of vm_execute + } + else + return ERR_INVALID_OPCODE; + return ERR_OK; +} + +err_t vm_execute_all(vm_t *vm) +{ + struct Program *program = &vm->program; + err_t err = ERR_OK; +#if VERBOSE == 1 + struct Registers prev_registers = vm->registers; + size_t cycles = 0; + size_t prev_sptr = 0; +#endif + while (program->instructions[program->ptr].opcode != OP_HALT && + program->ptr < program->max) + { +#if VERBOSE >= 1 + fprintf(stdout, "[vm_execute_all]: Trace(Cycle %lu)\n", cycles); + fputs( + "----------------------------------------------------------------------" + "----------\n", + stdout); + vm_print_program(vm, stdout); + fputs( + "----------------------------------------------------------------------" + "----------\n", + stdout); + if (memcmp(prev_registers.reg, vm->registers.reg, + ARR_SIZE(vm->registers.reg)) != 0) + { + vm_print_registers(vm, stdout); + prev_registers = vm->registers; + fputs("------------------------------------------------------------------" + "----" + "----------\n", + stdout); + } + if (prev_sptr != vm->stack.ptr) + { + vm_print_stack(vm, stdout); + prev_sptr = vm->stack.ptr; + fputs("------------------------------------------------------------------" + "----" + "----------\n", + stdout); + } + ++cycles; +#endif + err = vm_execute(vm); + if (err) + return err; + } + +#if VERBOSE >= 1 + fprintf(stdout, "[vm_execute_all]: Final VM state(Cycle %lu)\n", cycles); + vm_print_all(vm, stdout); +#endif + return err; +} + +void vm_load_stack(vm_t *vm, byte *bytes, size_t size) +{ + vm->stack.data = bytes; + vm->stack.max = size; + vm->stack.ptr = 0; +} + +void vm_load_program(vm_t *vm, inst_t *instructions, size_t size) +{ + vm->program.instructions = instructions; + vm->program.max = size; + vm->program.ptr = 0; +} + +void vm_print_registers(vm_t *vm, FILE *fp) +{ + struct Registers reg = vm->registers; + fprintf(fp, "Registers.reg = ["); + for (size_t i = 0; i < VM_REGISTERS; ++i) + { + fprintf(fp, "{%lu:%lX}", i, reg.reg[i]); + if (i != VM_REGISTERS - 1) + fprintf(fp, ", "); + } + fprintf(fp, "]\n"); +} + +void vm_print_stack(vm_t *vm, FILE *fp) +{ + struct Stack stack = vm->stack; + fprintf(fp, "Stack.max = %lu\nStack.ptr = %lu\nStack.data = [", stack.max, + stack.ptr); + if (stack.ptr == 0) + { + fprintf(fp, "]\n"); + return; + } + printf("\n"); + for (size_t i = stack.ptr; i > 0; --i) + { + byte b = stack.data[i - 1]; + fprintf(fp, "\t%lu: %X", stack.ptr - i, b); + if (i != 1) + fprintf(fp, ", "); + fprintf(fp, "\n"); + } + fprintf(fp, "]\n"); +} + +void vm_print_program(vm_t *vm, FILE *fp) +{ + struct Program program = vm->program; + fprintf(fp, + "Program.max = %lu\nProgram.ptr = " + "%lu\nProgram.instructions = [\n", + program.max, program.ptr); + size_t beg = 0; + if (program.ptr >= VM_PRINT_PROGRAM_EXCERPT) + { + fprintf(fp, "\t...\n"); + beg = program.ptr - VM_PRINT_PROGRAM_EXCERPT; + } + else + beg = 0; + size_t end = MIN(program.ptr + VM_PRINT_PROGRAM_EXCERPT, program.max); + for (size_t i = beg; i < end; ++i) + { + fprintf(fp, "\t%lu: ", i); + inst_print(program.instructions[i], fp); + if (i == program.ptr) + fprintf(fp, " <---"); + fprintf(fp, "\n"); + } + if (end != program.max) + fprintf(fp, "\t...\n"); + fprintf(fp, "]\n"); +} + +void vm_print_all(vm_t *vm, FILE *fp) +{ + fputs("----------------------------------------------------------------------" + "----------\n", + fp); + vm_print_program(vm, fp); + fputs("----------------------------------------------------------------------" + "----------\n", + fp); + vm_print_registers(vm, fp); + fputs("----------------------------------------------------------------------" + "----------\n", + fp); + vm_print_stack(vm, fp); + fputs("----------------------------------------------------------------------" + "----------\n", + fp); +} + +err_t vm_jump(vm_t *vm, word w) +{ + if (w >= vm->program.max) + return ERR_INVALID_PROGRAM_ADDRESS; + vm->program.ptr = w; + return ERR_OK; +} + +err_t vm_push_byte(vm_t *vm, data_t b) +{ + if (vm->stack.ptr >= vm->stack.max) + return ERR_STACK_OVERFLOW; + vm->stack.data[vm->stack.ptr++] = b.as_byte; + return ERR_OK; +} + +err_t vm_push_hword(vm_t *vm, data_t f) +{ + if (vm->stack.ptr + HWORD_SIZE >= vm->stack.max) + return ERR_STACK_OVERFLOW; + byte bytes[HWORD_SIZE] = {0}; + convert_hword_to_bytes(f.as_hword, bytes); + for (size_t i = 0; i < HWORD_SIZE; ++i) + { + byte b = bytes[HWORD_SIZE - i - 1]; + vm_push_byte(vm, DBYTE(b)); + } + return ERR_OK; +} + +err_t vm_push_word(vm_t *vm, data_t w) +{ + if (vm->stack.ptr + WORD_SIZE >= vm->stack.max) + return ERR_STACK_OVERFLOW; + byte bytes[WORD_SIZE] = {0}; + convert_word_to_bytes(w.as_word, bytes); + for (size_t i = 0; i < WORD_SIZE; ++i) + { + byte b = bytes[WORD_SIZE - i - 1]; + vm_push_byte(vm, DBYTE(b)); + } + return ERR_OK; +} + +err_t vm_push_byte_register(vm_t *vm, byte reg) +{ + if (reg >= VM_REGISTERS * 8) + return ERR_INVALID_REGISTER_BYTE; + + // Interpret each word based register as 8 byte registers + byte b = WORD_NTH_BYTE(vm->registers.reg[reg / 8], reg % 8); + + return vm_push_byte(vm, DBYTE(b)); +} + +err_t vm_push_hword_register(vm_t *vm, byte reg) +{ + if (reg >= VM_REGISTERS * 2) + return ERR_INVALID_REGISTER_HWORD; + else if (vm->stack.ptr >= vm->stack.max) + return ERR_STACK_OVERFLOW; + // Interpret each word based register as 2 hword registers + hword hw = WORD_NTH_HWORD(vm->registers.reg[reg / 2], reg % 2); + return vm_push_hword(vm, DHWORD(hw)); +} + +err_t vm_push_word_register(vm_t *vm, byte reg) +{ + if (reg >= VM_REGISTERS) + return ERR_INVALID_REGISTER_WORD; + else if (vm->stack.ptr >= vm->stack.max) + return ERR_STACK_OVERFLOW; + return vm_push_word(vm, DWORD(vm->registers.reg[reg])); +} + +err_t vm_mov_byte(vm_t *vm, byte reg) +{ + if (reg >= (VM_REGISTERS * 8)) + return ERR_INVALID_REGISTER_BYTE; + data_t ret = {0}; + err_t err = vm_pop_byte(vm, &ret); + if (err) + return err; + word *reg_ptr = &vm->registers.reg[reg / 8]; + size_t shift = (reg % 8) * 8; + // This resets the bits in the specific byte register + *reg_ptr = *reg_ptr & ~(0xFF << shift); + // This sets the bits + *reg_ptr = (*reg_ptr) | (ret.as_word << shift); + return ERR_OK; +} + +err_t vm_mov_hword(vm_t *vm, byte reg) +{ + if (reg >= (VM_REGISTERS * 2)) + return ERR_INVALID_REGISTER_HWORD; + else if (vm->stack.ptr < sizeof(f64)) + return ERR_STACK_UNDERFLOW; + data_t ret = {0}; + err_t err = vm_pop_hword(vm, &ret); + if (err) + return err; + word *reg_ptr = &vm->registers.reg[reg / 2]; + size_t shift = (reg % 2) * 2; + // This resets the bits in the specific hword register + *reg_ptr = *reg_ptr & ~(0xFFFFFFFF << shift); + // This sets the bits + *reg_ptr = (*reg_ptr) | (ret.as_word << shift); + return ERR_OK; +} + +err_t vm_mov_word(vm_t *vm, byte reg) +{ + if (reg >= VM_REGISTERS) + return ERR_INVALID_REGISTER_WORD; + else if (vm->stack.ptr < sizeof(word)) + return ERR_STACK_UNDERFLOW; + data_t ret = {0}; + err_t err = vm_pop_word(vm, &ret); + if (err) + return err; + vm->registers.reg[reg] = ret.as_word; + return ERR_OK; +} + +err_t vm_dup_byte(vm_t *vm, word w) +{ + if (vm->stack.ptr < w + 1) + return ERR_STACK_UNDERFLOW; + return vm_push_byte(vm, DBYTE(vm->stack.data[vm->stack.ptr - 1 - w])); +} + +err_t vm_dup_hword(vm_t *vm, word w) +{ + if (vm->stack.ptr < HWORD_SIZE * (w + 1)) + return ERR_STACK_UNDERFLOW; + byte bytes[HWORD_SIZE] = {0}; + for (size_t i = 0; i < HWORD_SIZE; ++i) + bytes[HWORD_SIZE - i - 1] = + vm->stack.data[vm->stack.ptr - (HWORD_SIZE * (w + 1)) + i]; + return vm_push_hword(vm, DHWORD(convert_bytes_to_hword(bytes))); +} + +err_t vm_dup_word(vm_t *vm, word w) +{ + if (vm->stack.ptr < WORD_SIZE * (w + 1)) + return ERR_STACK_UNDERFLOW; + byte bytes[WORD_SIZE] = {0}; + for (size_t i = 0; i < WORD_SIZE; ++i) + bytes[WORD_SIZE - i - 1] = + vm->stack.data[vm->stack.ptr - (WORD_SIZE * (w + 1)) + i]; + return vm_push_word(vm, DWORD(convert_bytes_to_word(bytes))); +} + +err_t vm_pop_byte(vm_t *vm, data_t *ret) +{ + if (vm->stack.ptr == 0) + return ERR_STACK_UNDERFLOW; + *ret = DBYTE(vm->stack.data[--vm->stack.ptr]); + return ERR_OK; +} + +err_t vm_pop_hword(vm_t *vm, data_t *ret) +{ + if (vm->stack.ptr < HWORD_SIZE) + return ERR_STACK_UNDERFLOW; + byte bytes[HWORD_SIZE] = {0}; + for (size_t i = 0; i < HWORD_SIZE; ++i) + { + data_t b = {0}; + vm_pop_byte(vm, &b); + bytes[i] = b.as_byte; + } + *ret = DWORD(convert_bytes_to_hword(bytes)); + return ERR_OK; +} + +err_t vm_pop_word(vm_t *vm, data_t *ret) +{ + if (vm->stack.ptr < WORD_SIZE) + return ERR_STACK_UNDERFLOW; + byte bytes[WORD_SIZE] = {0}; + for (size_t i = 0; i < WORD_SIZE; ++i) + { + data_t b = {0}; + vm_pop_byte(vm, &b); + bytes[i] = b.as_byte; + } + *ret = DWORD(convert_bytes_to_word(bytes)); + return ERR_OK; +} + +err_t vm_not_byte(vm_t *vm) +{ + data_t a = {0}; + err_t err = vm_pop_byte(vm, &a); + if (err) + return err; + return vm_push_byte(vm, DBYTE(!a.as_byte)); +} + +err_t vm_not_hword(vm_t *vm) +{ + data_t a = {0}; + err_t err = vm_pop_hword(vm, &a); + if (err) + return err; + return vm_push_hword(vm, DHWORD(!a.as_hword)); +} + +err_t vm_not_word(vm_t *vm) +{ + data_t a = {0}; + err_t err = vm_pop_word(vm, &a); + if (err) + return err; + return vm_push_word(vm, DWORD(!a.as_word)); +} + +err_t vm_or_byte(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_byte(vm, &a); + if (err) + return err; + err = vm_pop_byte(vm, &b); + if (err) + return err; + return vm_push_byte(vm, DBYTE(a.as_byte | b.as_byte)); +} + +err_t vm_or_hword(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_hword(vm, &a); + if (err) + return err; + err = vm_pop_hword(vm, &b); + if (err) + return err; + return vm_push_hword(vm, DHWORD(a.as_hword | b.as_hword)); +} + +err_t vm_or_word(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_word(vm, &a); + if (err) + return err; + err = vm_pop_word(vm, &b); + if (err) + return err; + return vm_push_word(vm, DWORD(a.as_word | b.as_word)); +} + +err_t vm_and_byte(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_byte(vm, &a); + if (err) + return err; + err = vm_pop_byte(vm, &b); + if (err) + return err; + return vm_push_byte(vm, DBYTE(a.as_byte & b.as_byte)); +} + +err_t vm_and_hword(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_hword(vm, &a); + if (err) + return err; + err = vm_pop_hword(vm, &b); + if (err) + return err; + return vm_push_hword(vm, DHWORD(a.as_hword & b.as_hword)); +} + +err_t vm_and_word(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_word(vm, &a); + if (err) + return err; + err = vm_pop_word(vm, &b); + if (err) + return err; + return vm_push_word(vm, DWORD(a.as_word & b.as_word)); +} + +err_t vm_xor_byte(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_byte(vm, &a); + if (err) + return err; + err = vm_pop_byte(vm, &b); + if (err) + return err; + return vm_push_byte(vm, DBYTE(a.as_byte ^ b.as_byte)); +} + +err_t vm_xor_hword(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_hword(vm, &a); + if (err) + return err; + err = vm_pop_hword(vm, &b); + if (err) + return err; + return vm_push_hword(vm, DHWORD(a.as_hword ^ b.as_hword)); +} + +err_t vm_xor_word(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_word(vm, &a); + if (err) + return err; + err = vm_pop_word(vm, &b); + if (err) + return err; + return vm_push_word(vm, DWORD(a.as_word ^ b.as_word)); +} + +err_t vm_eq_byte(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_byte(vm, &a); + if (err) + return err; + err = vm_pop_byte(vm, &b); + if (err) + return err; + return vm_push_byte(vm, DBYTE(a.as_byte == b.as_byte)); +} + +err_t vm_eq_hword(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_hword(vm, &a); + if (err) + return err; + err = vm_pop_hword(vm, &b); + if (err) + return err; + return vm_push_hword(vm, DHWORD(a.as_hword == b.as_hword)); +} + +err_t vm_eq_word(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_word(vm, &a); + if (err) + return err; + err = vm_pop_word(vm, &b); + if (err) + return err; + return vm_push_word(vm, DWORD(a.as_word == b.as_word)); +} + +err_t vm_plus_byte(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_byte(vm, &a); + if (err) + return err; + err = vm_pop_byte(vm, &b); + if (err) + return err; + return vm_push_byte(vm, DBYTE(a.as_byte + b.as_byte)); +} + +err_t vm_plus_hword(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_hword(vm, &a); + if (err) + return err; + err = vm_pop_hword(vm, &b); + if (err) + return err; + return vm_push_hword(vm, DHWORD(a.as_hword + b.as_hword)); +} + +err_t vm_plus_word(vm_t *vm) +{ + data_t a = {0}, b = {0}; + err_t err = vm_pop_word(vm, &a); + if (err) + return err; + err = vm_pop_word(vm, &b); + if (err) + return err; + return vm_push_word(vm, DWORD(a.as_word + b.as_word)); +} |