feat: implement vm
This commit is contained in:
+4
-3
@@ -17,7 +17,8 @@ add_executable(fzc
|
||||
src/compiler/utils/fzl_const_pool.c
|
||||
src/compiler/main.c)
|
||||
|
||||
# VM
|
||||
# add_executable(fzvm
|
||||
# src/vm/main.c)
|
||||
# Virtual machine
|
||||
add_executable(fz
|
||||
src/vm/fzl_vm.c
|
||||
src/vm/main.c)
|
||||
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
#ifndef FZL_VM_H
|
||||
#define FZL_VM_H
|
||||
|
||||
#include <compiler/utils/fzl_const_pool.h> // NOTE: move outside compiler scope to common
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
// VM descriptor
|
||||
typedef struct
|
||||
{
|
||||
fzl_const_t *consts;
|
||||
size_t consts_count;
|
||||
|
||||
uint64_t *ops;
|
||||
size_t ops_count;
|
||||
|
||||
size_t ip;
|
||||
|
||||
fzl_const_t stack[256];
|
||||
size_t stack_top;
|
||||
|
||||
fzl_const_t arg_stack[256];
|
||||
size_t arg_top;
|
||||
} fzl_vm_t;
|
||||
|
||||
// Allocate FZL VM
|
||||
fzl_vm_t *fzl_mkvm(const char *filepath);
|
||||
|
||||
// Load and run code
|
||||
int fzl_vm_run(fzl_vm_t *vm);
|
||||
|
||||
// Get last VM error message
|
||||
char *fzl_vm_geterror();
|
||||
|
||||
// Free VM
|
||||
void fzl_freevm(fzl_vm_t *vm);
|
||||
|
||||
#endif // FZL_VM_H
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
#include "common/fzl_builtins.h"
|
||||
#include "common/fzl_opcodes.h"
|
||||
#include "compiler/utils/fzl_const_pool.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vm/fzl_vm.h>
|
||||
|
||||
char last_err[256];
|
||||
|
||||
static char *expect_hdr = "FZL\0\1";
|
||||
|
||||
fzl_vm_t *fzl_mkvm(const char *filepath)
|
||||
{
|
||||
fzl_vm_t *vm = malloc(sizeof(fzl_vm_t));
|
||||
if (!vm)
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err), "failed to allocate vm");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
vm->consts = NULL;
|
||||
vm->consts_count = 0;
|
||||
vm->ops = NULL;
|
||||
vm->ops_count = 0;
|
||||
vm->ip = 0;
|
||||
memset(vm->stack, 0, sizeof(vm->stack));
|
||||
vm->stack_top = 0;
|
||||
memset(vm->arg_stack, 0, sizeof(vm->arg_stack));
|
||||
vm->arg_top = 0;
|
||||
|
||||
FILE *fd = fopen(filepath, "rb");
|
||||
if (!fd)
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err), "failed to open file '%s'", filepath);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
char hdr[5];
|
||||
fread(hdr, sizeof(char), 5, fd);
|
||||
if (memcmp(hdr, expect_hdr, 5) != 0)
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err), "magic check error");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t consts_count, ops_count;
|
||||
fread(&consts_count, sizeof(size_t), 1, fd);
|
||||
fread(&ops_count, sizeof(size_t), 1, fd);
|
||||
|
||||
if (ops_count < 1)
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err), "there is nothing to do (0 ops)");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if (consts_count > 0)
|
||||
{
|
||||
vm->consts_count = consts_count;
|
||||
vm->consts = malloc(consts_count * sizeof(fzl_const_t));
|
||||
|
||||
for (size_t i = 0; i < consts_count; i++)
|
||||
{
|
||||
fzl_consttype_t type;
|
||||
fread(&type, sizeof(fzl_consttype_t), 1, fd);
|
||||
|
||||
if (type == FZL_CONSTTYPE_STRING)
|
||||
{
|
||||
size_t length;
|
||||
char *chars;
|
||||
|
||||
fread(&length, sizeof(size_t), 1, fd);
|
||||
|
||||
chars = malloc(length * sizeof(char) + 1);
|
||||
fread(chars, sizeof(char), length, fd);
|
||||
chars[length] = 0;
|
||||
|
||||
vm->consts[i] = (fzl_const_t){
|
||||
.type = type, .as.string.length = length, .as.string.chars = chars};
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err), "unknown const type at index %ld",
|
||||
i);
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vm->ops_count = ops_count;
|
||||
vm->ops = malloc(ops_count * sizeof(uint64_t));
|
||||
for (size_t i = 0; i < ops_count; i++)
|
||||
{
|
||||
uint64_t op;
|
||||
fread(&op, sizeof(uint64_t), 1, fd);
|
||||
vm->ops[i] = op;
|
||||
}
|
||||
|
||||
return vm;
|
||||
}
|
||||
|
||||
int fzl_vm_run(fzl_vm_t *vm)
|
||||
{
|
||||
if (!vm)
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err), "vm is null");
|
||||
return -1;
|
||||
}
|
||||
|
||||
vm->ip = 0;
|
||||
while (vm->ip < vm->ops_count)
|
||||
{
|
||||
uint64_t opcode = vm->ops[vm->ip];
|
||||
|
||||
switch (opcode)
|
||||
{
|
||||
case FZL_OPCODE_CALL_BUILTIN:
|
||||
uint64_t fn_id = vm->ops[++vm->ip];
|
||||
if (fn_id == FZL_BUILTIN_PRINTLINE)
|
||||
{
|
||||
fzl_const_t arg = vm->arg_stack[--vm->arg_top];
|
||||
printf("%s\n", arg.as.string.chars);
|
||||
}
|
||||
else
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err),
|
||||
"runtime error - undefined builtin %lu", fn_id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case FZL_OPCODE_LOADCONST:
|
||||
uint64_t cn = vm->ops[++vm->ip];
|
||||
if (vm->consts_count <= cn)
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err), "runtime error - unknown const");
|
||||
return -1;
|
||||
}
|
||||
fzl_const_t c = vm->consts[cn];
|
||||
vm->stack[vm->stack_top++] = c;
|
||||
break;
|
||||
|
||||
case FZL_OPCODE_PUSHARG:
|
||||
if (vm->stack_top < 1)
|
||||
{
|
||||
snprintf(last_err, sizeof(last_err),
|
||||
"runtime error - cannot push argument: stack is empty");
|
||||
return -1;
|
||||
}
|
||||
vm->arg_stack[vm->arg_top++] = vm->stack[--vm->stack_top];
|
||||
break;
|
||||
|
||||
default:
|
||||
snprintf(last_err, sizeof(last_err), "runtime error - unknown opcode %lu",
|
||||
opcode);
|
||||
return -1;
|
||||
}
|
||||
|
||||
vm->ip++;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *fzl_vm_geterror() { return last_err; }
|
||||
|
||||
void fzl_freevm(fzl_vm_t *vm)
|
||||
{
|
||||
if (!vm)
|
||||
return;
|
||||
|
||||
for (size_t i = 0; i < vm->consts_count; i++)
|
||||
{
|
||||
if (vm->consts[i].type == FZL_CONSTTYPE_STRING)
|
||||
free((void *)vm->consts[i].as.string.chars);
|
||||
}
|
||||
|
||||
free(vm->consts);
|
||||
free(vm->ops);
|
||||
free(vm);
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
#include <stdbool.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <vm/fzl_vm.h>
|
||||
|
||||
typedef struct
|
||||
{
|
||||
char *filepath;
|
||||
bool help;
|
||||
} run_args_t;
|
||||
|
||||
void print_hint()
|
||||
{
|
||||
printf("Usage: fz [flags] <file path>\n");
|
||||
printf("Flags:\n");
|
||||
printf("\tno flags available yet :(\n");
|
||||
}
|
||||
|
||||
// Parse passed arguments and build args setup structure
|
||||
run_args_t *parse_args(int argc, char **argv)
|
||||
{
|
||||
if (argc < 2)
|
||||
{
|
||||
print_hint();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
run_args_t *args = malloc(sizeof(run_args_t));
|
||||
args->filepath = NULL;
|
||||
args->help = false;
|
||||
|
||||
for (size_t i = 0; i < argc; i++)
|
||||
{
|
||||
if (argv[i][0] != '-')
|
||||
continue;
|
||||
|
||||
if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0)
|
||||
{
|
||||
args->help = true;
|
||||
return args;
|
||||
}
|
||||
else
|
||||
{
|
||||
printf("Error: unknown flag '%s'.\n", argv[i]);
|
||||
printf("Use 'fz --help' to read the usage\n");
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (argv[argc - 1][0] == '-')
|
||||
{
|
||||
printf("Error: last argument MUST be a file path\n");
|
||||
printf("Use 'fz --help' for help\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
args->filepath = argv[argc - 1];
|
||||
|
||||
return args;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
run_args_t *args = parse_args(argc, argv);
|
||||
if (!args)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
fzl_vm_t *vm = fzl_mkvm(args->filepath);
|
||||
if (!vm)
|
||||
{
|
||||
printf("Error: %s\n", fzl_vm_geterror());
|
||||
return -1;
|
||||
}
|
||||
|
||||
int result = fzl_vm_run(vm);
|
||||
if (result != 0)
|
||||
{
|
||||
printf("Error: %s\n", fzl_vm_geterror());
|
||||
return -1;
|
||||
}
|
||||
|
||||
free(args);
|
||||
fzl_freevm(vm);
|
||||
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user