From b44a61be41e44b415b1293fcbc1f1e8c4ce3373d Mon Sep 17 00:00:00 2001 From: Aryadev Chavali Date: Mon, 23 Oct 2023 03:58:34 +0100 Subject: src->vm, Makefile is now a bit more abstracted and pretty colours Changed folder names for sake of clarity (will be introducing a new build target soon), and Makefile can now easily support more targets. --- vm/base.h | 70 ++++++ vm/darr.c | 77 ++++++ vm/darr.h | 39 +++ vm/fib.c | 79 ++++++ vm/inst.c | 399 ++++++++++++++++++++++++++++++ vm/inst.h | 156 ++++++++++++ vm/main.c | 62 +++++ vm/runtime.c | 777 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vm/runtime.h | 165 +++++++++++++ 9 files changed, 1824 insertions(+) create mode 100644 vm/base.h create mode 100644 vm/darr.c create mode 100644 vm/darr.h create mode 100644 vm/fib.c create mode 100644 vm/inst.c create mode 100644 vm/inst.h create mode 100644 vm/main.c create mode 100644 vm/runtime.c create mode 100644 vm/runtime.h (limited to 'vm') diff --git a/vm/base.h b/vm/base.h new file mode 100644 index 0000000..dbeec80 --- /dev/null +++ b/vm/base.h @@ -0,0 +1,70 @@ +/* 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: Basic types and routines + */ + +#ifndef BASE_H +#define BASE_H + +#include + +#define ARR_SIZE(xs) (sizeof(xs) / sizeof(xs[0])) +#define MAX(a, b) ((a) > (b) ? (a) : (b)) +#define MIN(a, b) ((a) > (b) ? (b) : (a)) + +// Flags +#ifndef VERBOSE +#define VERBOSE 0 +#endif + +typedef uint64_t u64; +typedef uint32_t u32; +typedef int32_t i32; +typedef int64_t i64; +typedef float f32; +typedef double f64; + +typedef uint8_t byte; +typedef u32 hword; +typedef u64 word; + +typedef union +{ + byte as_byte; + hword as_hword; + word as_word; +} data_t; + +typedef enum +{ + DATA_TYPE_NIL = 0, + DATA_TYPE_BYTE, + DATA_TYPE_HWORD, + DATA_TYPE_WORD, +} data_type_t; + +#define DBYTE(BYTE) ((data_t){.as_byte = (BYTE)}) +#define DHWORD(HWORD) ((data_t){.as_hword = (HWORD)}) +#define DWORD(WORD) ((data_t){.as_word = (WORD)}) + +#define HWORD_SIZE sizeof(hword) +#define WORD_SIZE sizeof(word) + +// Macros to extract the nth byte or nth hword from a word +#define WORD_NTH_BYTE(WORD, N) (((WORD) >> ((N)*8)) & 0xff) +#define WORD_NTH_HWORD(WORD, N) (((WORD) >> ((N)*2)) & 0xFFFFFFFF) + +// Assume array contains 4 bytes. +hword convert_bytes_to_hword(byte *); +void convert_hword_to_bytes(hword, byte *); +// Assume array contains 8 bytes. +word convert_bytes_to_word(byte *); +void convert_word_to_bytes(word, byte *); + +#endif diff --git a/vm/darr.c b/vm/darr.c new file mode 100644 index 0000000..4393c4b --- /dev/null +++ b/vm/darr.c @@ -0,0 +1,77 @@ +/* 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: Dynamically sized byte array + */ + +#include +#include +#include + +#include "./darr.h" + +void darr_init(darr_t *darr, size_t size) +{ + if (size == 0) + size = DARR_DEFAULT_SIZE; + *darr = (darr_t){ + .data = calloc(size, 1), + .used = 0, + .available = size, + }; +} + +void darr_ensure_capacity(darr_t *darr, size_t requested) +{ + if (darr->used + requested >= darr->available) + { + darr->available = + MAX(darr->used + requested, darr->available * DARR_REALLOC_MULT); + darr->data = realloc(darr->data, darr->available); + } +} + +void darr_append_byte(darr_t *darr, byte byte) +{ + darr_ensure_capacity(darr, 1); + darr->data[darr->used++] = byte; +} + +void darr_append_bytes(darr_t *darr, byte *bytes, size_t n) +{ + darr_ensure_capacity(darr, n); + memcpy(darr->data + darr->used, bytes, n); + darr->used += n; +} + +byte darr_at(darr_t *darr, size_t index) +{ + if (index >= darr->used) + // TODO: Error (index is out of bounds) + return 0; + return darr->data[index]; +} + +void darr_write_file(darr_t *bytes, FILE *fp) +{ + size_t size = fwrite(bytes->data, bytes->used, 1, fp); + assert(size == 1); +} + +darr_t darr_read_file(FILE *fp) +{ + darr_t darr = {0}; + fseek(fp, 0, SEEK_END); + long size = ftell(fp); + darr_init(&darr, size); + fseek(fp, 0, SEEK_SET); + size_t read = fread(darr.data, size, 1, fp); + assert(read == 1); + return darr; +} diff --git a/vm/darr.h b/vm/darr.h new file mode 100644 index 0000000..1d5820b --- /dev/null +++ b/vm/darr.h @@ -0,0 +1,39 @@ +/* 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: Dynamically sized byte array + */ + +#ifndef DARR_H +#define DARR_H + +#include +#include + +#include "./base.h" + +typedef struct +{ + byte *data; + size_t used, available; +} darr_t; + +#define DARR_DEFAULT_SIZE 8 +#define DARR_REALLOC_MULT 1.5 + +void darr_init(darr_t *, size_t); +void darr_ensure_capacity(darr_t *, size_t); +void darr_append_byte(darr_t *, byte); +void darr_append_bytes(darr_t *, byte *, size_t); +byte darr_at(darr_t *, size_t); + +void darr_write_file(darr_t *, FILE *); +darr_t darr_read_file(FILE *); + +#endif diff --git a/vm/fib.c b/vm/fib.c new file mode 100644 index 0000000..7107c3e --- /dev/null +++ b/vm/fib.c @@ -0,0 +1,79 @@ +/* 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-23 + * Author: Aryadev Chavali + * Description: An example virtual machine program which computes and + * prints fibonacci numbers. Note that by default the virtual machine + * just rolls overflows over, so this program will never terminate. + */ + +#include +#include +#include + +#include "./inst.h" +#include "./runtime.h" + +int main(void) +{ + inst_t instructions[] = { + // MOV the values 1 and 1 to REG[0] and REG[1] respectively + INST_PUSH(WORD, 1), + INST_MOV(WORD, 0), + INST_PUSH(WORD, 1), + INST_MOV(WORD, 1), + + // Print value at register 0 with newline. + INST_PUSH_REG(WORD, 0), // <-- # + INST_PRINT(WORD), + INST_PUSH(BYTE, '\n'), + INST_PRINT(CHAR), + + // Print value at register 1 with newline + INST_PUSH_REG(WORD, 1), + INST_PRINT(WORD), + INST_PUSH(BYTE, '\n'), + INST_PRINT(CHAR), + + /* Compute the next pair of fibonacci numbers */ + // REG[0] + REG[1] + INST_PUSH_REG(WORD, 0), + INST_PUSH_REG(WORD, 1), + INST_PLUS(WORD), + + // Mov REG[0] + REG[1] to REG[0] + INST_MOV(WORD, 0), + + // REG[0] + REG[1] + INST_PUSH_REG(WORD, 0), + INST_PUSH_REG(WORD, 1), + INST_PLUS(WORD), + + // Mov REG[0] + REG[1] to REG[1] + INST_MOV(WORD, 1), + + // Jump to the point # + INST_JUMP_ABS(4), + INST_HALT, + }; + + byte stack[256]; + vm_t vm = {0}; + vm_load_stack(&vm, stack, ARR_SIZE(stack)); + vm_load_program(&vm, instructions, ARR_SIZE(instructions)); + err_t err = vm_execute_all(&vm); + + if (err) + { + const char *error_str = err_as_cstr(err); + fprintf(stderr, "[ERROR]: %s\n", error_str); + fprintf(stderr, "[ERROR]: VM Trace:\n"); + vm_print_all(&vm, stderr); + return 255 - err; + } + return 0; +} diff --git a/vm/inst.c b/vm/inst.c new file mode 100644 index 0000000..3980173 --- /dev/null +++ b/vm/inst.c @@ -0,0 +1,399 @@ +/* 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: Implementation of bytecode for instructions + */ + +#include +#include +#include +#include + +#include "./inst.h" + +const char *opcode_as_cstr(opcode_t code) +{ + switch (code) + { + case OP_NOOP: + return "NOOP"; + break; + case OP_PUSH_BYTE: + return "PUSH_BYTE"; + break; + case OP_PUSH_WORD: + return "PUSH_WORD"; + break; + case OP_PUSH_HWORD: + return "PUSH_HWORD"; + break; + case OP_PUSH_REGISTER_BYTE: + return "PUSH_REGISTER_BYTE"; + break; + case OP_PUSH_REGISTER_WORD: + return "PUSH_REGISTER_WORD"; + break; + case OP_PUSH_REGISTER_HWORD: + return "PUSH_REGISTER_HWORD"; + break; + case OP_POP_BYTE: + return "POP_BYTE"; + break; + case OP_POP_WORD: + return "POP_WORD"; + break; + case OP_POP_HWORD: + return "POP_HWORD"; + break; + case OP_MOV_BYTE: + return "MOV_BYTE"; + break; + case OP_MOV_WORD: + return "MOV_WORD"; + break; + case OP_MOV_HWORD: + return "MOV_HWORD"; + break; + case OP_DUP_BYTE: + return "DUP_BYTE"; + break; + case OP_DUP_HWORD: + return "DUP_HWORD"; + break; + case OP_DUP_WORD: + return "DUP_WORD"; + break; + case OP_NOT_BYTE: + return "NOT_BYTE"; + break; + case OP_NOT_HWORD: + return "NOT_HWORD"; + break; + case OP_NOT_WORD: + return "NOT_WORD"; + break; + case OP_OR_BYTE: + return "OR_BYTE"; + break; + case OP_OR_HWORD: + return "OR_HWORD"; + break; + case OP_OR_WORD: + return "OR_WORD"; + break; + case OP_AND_BYTE: + return "AND_BYTE"; + break; + case OP_AND_HWORD: + return "AND_HWORD"; + break; + case OP_AND_WORD: + return "AND_WORD"; + break; + case OP_XOR_BYTE: + return "XOR_BYTE"; + break; + case OP_XOR_HWORD: + return "XOR_HWORD"; + break; + case OP_XOR_WORD: + return "XOR_WORD"; + break; + case OP_EQ_BYTE: + return "EQ_BYTE"; + break; + case OP_EQ_HWORD: + return "EQ_HWORD"; + break; + case OP_EQ_WORD: + return "EQ_WORD"; + break; + case OP_PLUS_BYTE: + return "PLUS_BYTE"; + break; + case OP_PLUS_HWORD: + return "PLUS_HWORD"; + break; + case OP_PLUS_WORD: + return "PLUS_WORD"; + break; + case OP_JUMP_ABS: + return "JUMP_ABS"; + break; + case OP_JUMP_STACK: + return "JUMP_STACK"; + break; + case OP_JUMP_REGISTER: + return "JUMP_REGISTER"; + break; + case OP_JUMP_IF_BYTE: + return "JUMP_IF_BYTE"; + break; + case OP_JUMP_IF_HWORD: + return "JUMP_IF_HWORD"; + break; + case OP_JUMP_IF_WORD: + return "JUMP_IF_WORD"; + break; + case OP_PRINT_CHAR: + return "PRINT_CHAR"; + break; + case OP_PRINT_BYTE: + return "PRINT_BYTE"; + break; + case OP_PRINT_INT: + return "PRINT_INT"; + break; + case OP_PRINT_HWORD: + return "PRINT_HWORD"; + break; + case OP_PRINT_LONG: + return "PRINT_LONG"; + break; + case OP_PRINT_WORD: + return "PRINT_WORD"; + break; + case OP_HALT: + return "HALT"; + break; + case NUMBER_OF_OPCODES: + return ""; + break; + } + return ""; +} + +void data_print(data_t datum, data_type_t type, FILE *fp) +{ + switch (type) + { + case DATA_TYPE_NIL: + break; + case DATA_TYPE_BYTE: + fprintf(fp, "%X", datum.as_byte); + break; + case DATA_TYPE_HWORD: + fprintf(fp, "%d", datum.as_hword); + break; + case DATA_TYPE_WORD: + fprintf(fp, "%lX", datum.as_word); + break; + } +} + +hword convert_bytes_to_hword(byte *bytes) +{ + hword h = 0; + memcpy(&h, bytes, HWORD_SIZE); + return h; +} + +void convert_hword_to_bytes(hword w, byte *bytes) +{ + memcpy(bytes, &w, HWORD_SIZE); +} + +void convert_word_to_bytes(word w, byte *bytes) +{ + memcpy(bytes, &w, WORD_SIZE); +} + +word convert_bytes_to_word(byte *bytes) +{ + word w = 0; + memcpy(&w, bytes, WORD_SIZE); + return w; +} + +void inst_print(inst_t instruction, FILE *fp) +{ + static_assert(NUMBER_OF_OPCODES == 46, "inst_bytecode_size: Out of date"); + fprintf(fp, "%s(", opcode_as_cstr(instruction.opcode)); + if (OPCODE_IS_TYPE(instruction.opcode, OP_PUSH)) + { + data_type_t type = (data_type_t)instruction.opcode; + fprintf(fp, "datum=0x"); + data_print(instruction.operand, type, fp); + } + else if (OPCODE_IS_TYPE(instruction.opcode, OP_PUSH_REGISTER) || + OPCODE_IS_TYPE(instruction.opcode, OP_MOV) || + instruction.opcode == OP_JUMP_REGISTER) + { + fprintf(fp, "reg=0x"); + data_print(instruction.operand, DATA_TYPE_BYTE, fp); + } + else if (OPCODE_IS_TYPE(instruction.opcode, OP_DUP)) + { + fprintf(fp, "n=%lu", instruction.operand.as_word); + } + else if (instruction.opcode == OP_JUMP_ABS || + OPCODE_IS_TYPE(instruction.opcode, OP_JUMP_IF)) + { + fprintf(fp, "address=0x"); + data_print(instruction.operand, DATA_TYPE_WORD, fp); + } + fprintf(fp, ")"); +} + +size_t inst_bytecode_size(inst_t inst) +{ + static_assert(NUMBER_OF_OPCODES == 46, "inst_bytecode_size: Out of date"); + size_t size = 1; // for opcode + if (OPCODE_IS_TYPE(inst.opcode, OP_PUSH)) + { + if (inst.opcode == OP_PUSH_BYTE) + ++size; + else if (inst.opcode == OP_PUSH_HWORD) + size += HWORD_SIZE; + else if (inst.opcode == OP_PUSH_WORD) + size += WORD_SIZE; + } + else if (OPCODE_IS_TYPE(inst.opcode, OP_PUSH_REGISTER) || + OPCODE_IS_TYPE(inst.opcode, OP_MOV) || + inst.opcode == OP_JUMP_REGISTER) + // Only need a byte for the register + ++size; + else if (OPCODE_IS_TYPE(inst.opcode, OP_DUP) || inst.opcode == OP_JUMP_ABS || + OPCODE_IS_TYPE(inst.opcode, OP_JUMP_IF)) + size += WORD_SIZE; + return size; +} + +void inst_write_bytecode(inst_t inst, darr_t *darr) +{ + static_assert(NUMBER_OF_OPCODES == 46, "inst_write_bytecode: Out of date"); + // Append opcode + darr_append_byte(darr, inst.opcode); + // Then append 0 or more operands + data_type_t to_append = DATA_TYPE_NIL; + if (OPCODE_IS_TYPE(inst.opcode, OP_PUSH)) + to_append = (data_type_t)inst.opcode; + else if (OPCODE_IS_TYPE(inst.opcode, OP_PUSH_REGISTER) || + OPCODE_IS_TYPE(inst.opcode, OP_MOV) || + inst.opcode == OP_JUMP_REGISTER) + to_append = DATA_TYPE_BYTE; + else if (OPCODE_IS_TYPE(inst.opcode, OP_DUP) || inst.opcode == OP_JUMP_ABS || + OPCODE_IS_TYPE(inst.opcode, OP_JUMP_IF)) + to_append = DATA_TYPE_WORD; + + switch (to_append) + { + case DATA_TYPE_NIL: + break; + case DATA_TYPE_BYTE: + darr_append_byte(darr, inst.operand.as_byte); + break; + case DATA_TYPE_HWORD: + darr_append_bytes(darr, (byte *)&inst.operand.as_hword, HWORD_SIZE); + break; + case DATA_TYPE_WORD: + darr_append_bytes(darr, (byte *)&inst.operand.as_word, WORD_SIZE); + break; + } +} + +void insts_write_bytecode(inst_t *insts, size_t size, darr_t *darr) +{ + for (size_t i = 0; i < size; ++i) + inst_write_bytecode(insts[i], darr); +} + +data_t read_type_from_darr(darr_t *darr, data_type_t type) +{ + switch (type) + { + case DATA_TYPE_NIL: + break; + case DATA_TYPE_BYTE: + if (darr->used >= darr->available) + // TODO: Error (darr has no space left) + return DBYTE(0); + return DBYTE(darr->data[darr->used++]); + break; + case DATA_TYPE_HWORD: + if (darr->used + HWORD_SIZE >= darr->available) + // TODO: Error (darr has no space left) + return DWORD(0); + hword u = 0; + memcpy(&u, darr->data + darr->used, HWORD_SIZE); + darr->used += HWORD_SIZE; + return DHWORD(u); + break; + case DATA_TYPE_WORD: + if (darr->used + WORD_SIZE >= darr->available) + // TODO: Error (darr has no space left) + return DWORD(0); + word w = 0; + memcpy(&w, darr->data + darr->used, WORD_SIZE); + darr->used += WORD_SIZE; + return DWORD(w); + break; + } + // TODO: Error (unrecognised type) + return DBYTE(0); +} + +inst_t inst_read_bytecode(darr_t *darr) +{ + static_assert(NUMBER_OF_OPCODES == 46, "inst_read_bytecode: Out of date"); + if (darr->used >= darr->available) + return (inst_t){0}; + inst_t inst = {0}; + opcode_t opcode = darr->data[darr->used++]; + if (opcode > OP_HALT || opcode == NUMBER_OF_OPCODES || opcode < OP_NOOP) + // Translate to NOOP + return inst; + // Read operands + if (OPCODE_IS_TYPE(opcode, OP_PUSH)) + inst.operand = read_type_from_darr(darr, (data_type_t)opcode); + // Read register (as a byte) + else if (OPCODE_IS_TYPE(opcode, OP_PUSH_REGISTER) || + OPCODE_IS_TYPE(opcode, OP_MOV) || inst.opcode == OP_JUMP_STACK) + inst.operand = read_type_from_darr(darr, DATA_TYPE_BYTE); + else if (OPCODE_IS_TYPE(opcode, OP_DUP) || opcode == OP_JUMP_ABS || + OPCODE_IS_TYPE(opcode, OP_JUMP_IF)) + inst.operand = read_type_from_darr(darr, DATA_TYPE_WORD); + // Otherwise opcode doesn't take operands + + inst.opcode = opcode; + + return inst; +} + +inst_t *insts_read_bytecode(darr_t *bytes, size_t *ret_size) +{ + *ret_size = 0; + // NOTE: Here we use the darr as a dynamic array of inst_t. + darr_t instructions = {0}; + darr_init(&instructions, sizeof(inst_t)); + while (bytes->used < bytes->available) + { + inst_t instruction = inst_read_bytecode(bytes); + darr_append_bytes(&instructions, (byte *)&instruction, sizeof(instruction)); + } + *ret_size = instructions.used / sizeof(inst_t); + return (inst_t *)instructions.data; +} + +void insts_write_bytecode_file(inst_t *instructions, size_t size, FILE *fp) +{ + darr_t darr = {0}; + darr_init(&darr, 0); + insts_write_bytecode(instructions, size, &darr); + darr_write_file(&darr, fp); + free(darr.data); +} + +inst_t *insts_read_bytecode_file(FILE *fp, size_t *ret) +{ + darr_t darr = darr_read_file(fp); + inst_t *instructions = insts_read_bytecode(&darr, ret); + free(darr.data); + return instructions; +} diff --git a/vm/inst.h b/vm/inst.h new file mode 100644 index 0000000..8902757 --- /dev/null +++ b/vm/inst.h @@ -0,0 +1,156 @@ +/* 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: Instructions and opcodes + */ + +#ifndef INST_H +#define INST_H + +#include +#include + +#include "./base.h" +#include "./darr.h" + +typedef enum +{ + OP_NOOP = 0, + + // Dealing with data and registers + OP_PUSH_BYTE, + OP_PUSH_HWORD, + OP_PUSH_WORD, + + OP_POP_BYTE, + OP_POP_HWORD, + OP_POP_WORD, + + OP_PUSH_REGISTER_BYTE, + OP_PUSH_REGISTER_HWORD, + OP_PUSH_REGISTER_WORD, + + OP_MOV_BYTE, + OP_MOV_HWORD, + OP_MOV_WORD, + + OP_DUP_BYTE, + OP_DUP_HWORD, + OP_DUP_WORD, + + // Boolean operations + OP_NOT_BYTE, + OP_NOT_HWORD, + OP_NOT_WORD, + + OP_OR_BYTE, + OP_OR_HWORD, + OP_OR_WORD, + + OP_AND_BYTE, + OP_AND_HWORD, + OP_AND_WORD, + + OP_XOR_BYTE, + OP_XOR_HWORD, + OP_XOR_WORD, + + OP_EQ_BYTE, + OP_EQ_HWORD, + OP_EQ_WORD, + + // Mathematical operations + OP_PLUS_BYTE, + OP_PLUS_HWORD, + OP_PLUS_WORD, + + // Simple I/O + OP_PRINT_CHAR, + OP_PRINT_BYTE, + OP_PRINT_INT, + OP_PRINT_HWORD, + OP_PRINT_LONG, + OP_PRINT_WORD, + + // Program control flow + OP_JUMP_ABS, + OP_JUMP_STACK, + OP_JUMP_REGISTER, + OP_JUMP_IF_BYTE, + OP_JUMP_IF_HWORD, + OP_JUMP_IF_WORD, + + // Should not be an opcode + NUMBER_OF_OPCODES, + OP_HALT = 0b11111111, // top of the byte is a HALT +} opcode_t; + +const char *opcode_as_cstr(opcode_t); + +#define OPCODE_IS_TYPE(OPCODE, OP_TYPE) \ + (((OPCODE) >= OP_TYPE##_BYTE) && ((OPCODE) <= OP_TYPE##_WORD)) + +#define OPCODE_DATA_TYPE(OPCODE, OP_TYPE) \ + ((OPCODE) == OP_TYPE##_BYTE ? DATA_TYPE_BYTE \ + : ((OPCODE) == OP_TYPE##_HWORD) ? DATA_TYPE_HWORD \ + : DATA_TYPE_WORD) + +typedef struct +{ + opcode_t opcode; + data_t operand; +} inst_t; + +void inst_print(inst_t, FILE *); + +size_t inst_bytecode_size(inst_t); +void inst_write_bytecode(inst_t, darr_t *); +void insts_write_bytecode(inst_t *, size_t, darr_t *); +// Here the dynamic array is a preloaded buffer of bytes, where +// darr.available is the number of overall bytes and used is the +// cursor (where we are in the buffer). +inst_t inst_read_bytecode(darr_t *); +inst_t *insts_read_bytecode(darr_t *, size_t *); + +void insts_write_bytecode_file(inst_t *, size_t, FILE *); +inst_t *insts_read_bytecode_file(FILE *, size_t *); + +#define INST_NOOP ((inst_t){0}) +#define INST_HALT ((inst_t){.opcode = OP_HALT}) + +#define INST_PUSH(TYPE, OP) \ + ((inst_t){.opcode = OP_PUSH_##TYPE, .operand = D##TYPE(OP)}) + +#define INST_MOV(TYPE, OP) \ + ((inst_t){.opcode = OP_MOV_##TYPE, .operand = D##TYPE(OP)}) + +#define INST_POP(TYPE) ((inst_t){.opcode = OP_POP_##TYPE}) + +#define INST_PUSH_REG(TYPE, REG) \ + ((inst_t){.opcode = OP_PUSH_REGISTER_##TYPE, .operand = D##TYPE(REG)}) + +#define INST_DUP(TYPE, OP) \ + ((inst_t){.opcode = OP_DUP_##TYPE, .operand = DWORD(OP)}) + +#define INST_NOT(TYPE) ((inst_t){.opcode = OP_NOT_##TYPE}) +#define INST_OR(TYPE) ((inst_t){.opcode = OP_OR_##TYPE}) +#define INST_AND(TYPE) ((inst_t){.opcode = OP_AND_##TYPE}) +#define INST_XOR(TYPE) ((inst_t){.opcode = OP_XOR_##TYPE}) +#define INST_EQ(TYPE) ((inst_t){.opcode = OP_EQ_##TYPE}) +#define INST_PLUS(TYPE) ((inst_t){.opcode = OP_PLUS_##TYPE}) + +#define INST_JUMP_ABS(OP) \ + ((inst_t){.opcode = OP_JUMP_ABS, .operand = DWORD(OP)}) +#define INST_JUMP_STACK ((inst_t){.opcode = OP_JUMP_STACK}) +#define INST_JUMP_REGISTER ((inst_t){.opcode = OP_JUMP_REGISTER}) +#define INST_JUMP_IF(TYPE, OP) \ + ((inst_t){.opcode = OP_JUMP_IF_##TYPE, .operand = DWORD(OP)}) + +#define INST_PRINT(TYPE) ((inst_t){.opcode = OP_PRINT_##TYPE}) +#endif diff --git a/vm/main.c b/vm/main.c new file mode 100644 index 0000000..005b61e --- /dev/null +++ b/vm/main.c @@ -0,0 +1,62 @@ +/* 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: Entrypoint to program + */ + +#include +#include +#include + +#include "./inst.h" +#include "./runtime.h" + +int interpret_bytecode(const char *filepath) +{ + FILE *fp = fopen(filepath, "rb"); + size_t number = 0; + inst_t *instructions = insts_read_bytecode_file(fp, &number); + fclose(fp); + + byte stack[256]; + vm_t vm = {0}; + vm_load_stack(&vm, stack, ARR_SIZE(stack)); + vm_load_program(&vm, instructions, number); + err_t err = vm_execute_all(&vm); + + int ret = 0; + if (err) + { + const char *error_str = err_as_cstr(err); + fprintf(stderr, "[ERROR]: %s\n", error_str); + vm_print_all(&vm, stderr); + ret = 255 - err; + } + free(instructions); + return ret; +} + +int assemble_instructions(inst_t *instructions, size_t number, + const char *filepath) +{ + FILE *fp = fopen(filepath, "wb"); + insts_write_bytecode_file(instructions, number, fp); + fclose(fp); + return 0; +} + +int main(int argc, char *argv[]) +{ + const char *filename = "out.bin"; + if (argc >= 2) + filename = argv[1]; + inst_t instructions[] = {INST_HALT}; + assemble_instructions(instructions, ARR_SIZE(instructions), filename); + return interpret_bytecode(filename); +} 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 +#include +#include +#include +#include + +#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)); +} diff --git a/vm/runtime.h b/vm/runtime.h new file mode 100644 index 0000000..83ffa17 --- /dev/null +++ b/vm/runtime.h @@ -0,0 +1,165 @@ +/* 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 + */ + +#ifndef RUNTIME_H +#define RUNTIME_H + +#include +#include + +#include "./base.h" +#include "./inst.h" + +typedef enum +{ + ERR_OK = 0, + ERR_STACK_UNDERFLOW, + ERR_STACK_OVERFLOW, + ERR_INVALID_OPCODE, + ERR_INVALID_REGISTER_BYTE, + ERR_INVALID_REGISTER_HWORD, + ERR_INVALID_REGISTER_WORD, + ERR_INVALID_PROGRAM_ADDRESS, + ERR_END_OF_PROGRAM, +} err_t; + +const char *err_as_cstr(err_t); + +#define VM_REGISTERS 8 +typedef struct +{ + struct Registers + { + word reg[VM_REGISTERS]; + } registers; + struct Stack + { + byte *data; + word ptr, max; + } stack; + struct Program + { + inst_t *instructions; + word ptr, max; + } program; +} vm_t; + +#define VM_REG_BYTE(REG) ((REG)&0b11111111) +#define VM_REG_HWORD(REG) ((REG)&0b11111111111111111111111111111111) +#define VM_REG_WORD(REG) ((REG)) + +err_t vm_execute(vm_t *); +err_t vm_execute_all(vm_t *); + +void vm_load_stack(vm_t *, byte *, size_t); +void vm_load_program(vm_t *, inst_t *, size_t); + +// Print routines +#define VM_PRINT_PROGRAM_EXCERPT 5 +void vm_print_registers(vm_t *, FILE *); +void vm_print_stack(vm_t *, FILE *); +void vm_print_program(vm_t *, FILE *); +void vm_print_all(vm_t *, FILE *); + +// Execution routines +err_t vm_jump(vm_t *, word); + +err_t vm_pop_byte(vm_t *, data_t *); +err_t vm_pop_hword(vm_t *, data_t *); +err_t vm_pop_word(vm_t *, data_t *); + +err_t vm_push_byte(vm_t *, data_t); +err_t vm_push_hword(vm_t *, data_t); +err_t vm_push_word(vm_t *, data_t); + +typedef err_t (*push_f)(vm_t *, data_t); +static const push_f PUSH_ROUTINES[] = { + [OP_PUSH_BYTE] = vm_push_byte, + [OP_PUSH_HWORD] = vm_push_hword, + [OP_PUSH_WORD] = vm_push_word, +}; + +err_t vm_push_byte_register(vm_t *, byte); +err_t vm_push_hword_register(vm_t *, byte); +err_t vm_push_word_register(vm_t *, byte); + +err_t vm_mov_byte(vm_t *, byte); +err_t vm_mov_hword(vm_t *, byte); +err_t vm_mov_word(vm_t *, byte); + +typedef err_t (*reg_f)(vm_t *, byte); +static const reg_f REG_ROUTINES[] = { + [OP_PUSH_REGISTER_BYTE] = vm_push_byte_register, + [OP_PUSH_REGISTER_HWORD] = vm_push_hword_register, + [OP_PUSH_REGISTER_WORD] = vm_push_word_register, + [OP_MOV_BYTE] = vm_mov_byte, + [OP_MOV_HWORD] = vm_mov_hword, + [OP_MOV_WORD] = vm_mov_word, +}; + +err_t vm_dup_byte(vm_t *, word); +err_t vm_dup_hword(vm_t *, word); +err_t vm_dup_word(vm_t *, word); + +typedef err_t (*dup_f)(vm_t *, word); +static const dup_f DUP_ROUTINES[] = { + [OP_DUP_BYTE] = vm_dup_byte, + [OP_DUP_HWORD] = vm_dup_hword, + [OP_DUP_WORD] = vm_dup_word, +}; + +err_t vm_not_byte(vm_t *); +err_t vm_not_hword(vm_t *); +err_t vm_not_word(vm_t *); + +err_t vm_or_byte(vm_t *); +err_t vm_or_hword(vm_t *); +err_t vm_or_word(vm_t *); + +err_t vm_and_byte(vm_t *); +err_t vm_and_hword(vm_t *); +err_t vm_and_word(vm_t *); + +err_t vm_xor_byte(vm_t *); +err_t vm_xor_hword(vm_t *); +err_t vm_xor_word(vm_t *); + +err_t vm_eq_byte(vm_t *); +err_t vm_eq_hword(vm_t *); +err_t vm_eq_word(vm_t *); + +err_t vm_plus_byte(vm_t *); +err_t vm_plus_hword(vm_t *); +err_t vm_plus_word(vm_t *); + +typedef err_t (*stack_f)(vm_t *); +static const stack_f STACK_ROUTINES[] = { + [OP_NOT_BYTE] = vm_not_byte, [OP_NOT_HWORD] = vm_not_hword, + [OP_NOT_WORD] = vm_not_word, + + [OP_OR_BYTE] = vm_or_byte, [OP_OR_HWORD] = vm_or_hword, + [OP_OR_WORD] = vm_or_word, + + [OP_AND_BYTE] = vm_and_byte, [OP_AND_HWORD] = vm_and_hword, + [OP_AND_WORD] = vm_and_word, + + [OP_XOR_BYTE] = vm_xor_byte, [OP_XOR_HWORD] = vm_xor_hword, + [OP_XOR_WORD] = vm_xor_word, + + [OP_EQ_BYTE] = vm_eq_byte, [OP_EQ_HWORD] = vm_eq_hword, + [OP_EQ_WORD] = vm_eq_word, + + [OP_PLUS_BYTE] = vm_plus_byte, [OP_PLUS_HWORD] = vm_plus_hword, + [OP_PLUS_WORD] = vm_plus_word, +}; + +#endif -- cgit v1.2.3-13-gbd6f