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.
This commit is contained in:
2023-10-23 03:58:34 +01:00
parent 587f31a63b
commit b44a61be41
10 changed files with 39 additions and 26 deletions

70
vm/base.h Normal file
View File

@@ -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

77
vm/darr.c Normal file
View File

@@ -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;
}

39
vm/darr.h Normal file
View File

@@ -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

79
vm/fib.c Normal file
View File

@@ -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;
}

399
vm/inst.c Normal file
View File

@@ -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;
}

156
vm/inst.h Normal file
View File

@@ -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

62
vm/main.c Normal file
View File

@@ -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);
}

777
vm/runtime.c Normal file
View File

@@ -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));
}

165
vm/runtime.h Normal file
View File

@@ -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