feat: implement vm

This commit is contained in:
samsonium
2026-10-10 05:38:16 +03:00
parent 96115b347e
commit cdf929dfc4
4 changed files with 315 additions and 3 deletions
+4 -3
View File
@@ -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)
+38
View File
@@ -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
View File
@@ -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);
}
+90
View File
@@ -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;
}