aboutsummaryrefslogtreecommitdiff
path: root/vm
diff options
context:
space:
mode:
Diffstat (limited to 'vm')
-rw-r--r--vm/base.h70
-rw-r--r--vm/darr.c77
-rw-r--r--vm/darr.h39
-rw-r--r--vm/fib.c79
-rw-r--r--vm/inst.c399
-rw-r--r--vm/inst.h156
-rw-r--r--vm/main.c62
-rw-r--r--vm/runtime.c777
-rw-r--r--vm/runtime.h165
9 files changed, 1824 insertions, 0 deletions
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 <stdint.h>
+
+#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 <assert.h>
+#include <malloc.h>
+#include <string.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+
+#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 <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <assert.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <stdio.h>
+#include <stdlib.h>
+
+#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 <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <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));
+}
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 <stdio.h>
+#include <stdlib.h>
+
+#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