1054 lines
29 KiB
C
1054 lines
29 KiB
C
#include <common/fzl_builtins.h>
|
|
#include <common/fzl_opcodes.h>
|
|
#include <common/fzl_types.h>
|
|
#include <common/fzl_value.h>
|
|
#include <compiler/utils/fzl_const_pool.h>
|
|
#include <stdbool.h>
|
|
#include <stddef.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <vm/fzl_vm.h>
|
|
#include <vm/fzl_vm_env.h>
|
|
#include <vm/fzl_vm_object.h>
|
|
|
|
char last_err[256];
|
|
size_t last_jmp_ip = 0;
|
|
size_t total_jmp_on_same = 0;
|
|
|
|
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;
|
|
vm->dtop = 0;
|
|
vm->ctop = 0;
|
|
vm->etop = 0;
|
|
|
|
memset(vm->dstack, 0, sizeof(vm->dstack));
|
|
memset(vm->cstack, 0, sizeof(vm->cstack));
|
|
memset(vm->estack, 0, sizeof(vm->estack));
|
|
|
|
FILE *fd = fopen(filepath, "rb");
|
|
if (!fd)
|
|
{
|
|
snprintf(last_err, sizeof(last_err), "failed to open file '%s'", filepath);
|
|
free(vm);
|
|
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");
|
|
fclose(fd);
|
|
free(vm);
|
|
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)");
|
|
fclose(fd);
|
|
free(vm);
|
|
return NULL;
|
|
}
|
|
|
|
if (consts_count > 0)
|
|
{
|
|
vm->consts_count = consts_count;
|
|
vm->consts = malloc(consts_count * sizeof(fzl_value_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
|
|
{
|
|
fclose(fd);
|
|
free(vm->consts);
|
|
free(vm);
|
|
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;
|
|
}
|
|
fclose(fd);
|
|
|
|
return vm;
|
|
}
|
|
|
|
// Escape objects with nested objects check
|
|
static void escape_recursive(fzl_vm_env_t *current, fzl_vm_env_t *parent,
|
|
fzl_vm_object_t *obj)
|
|
{
|
|
if (!current || !parent || !obj)
|
|
return;
|
|
|
|
bool found_in_current = false;
|
|
for (size_t i = 0; i < current->owned_objects_count; i++)
|
|
{
|
|
if (current->owned_objects[i] == obj)
|
|
{
|
|
current->owned_objects[i] = NULL;
|
|
found_in_current = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (found_in_current)
|
|
{
|
|
bool found_in_parent = false;
|
|
for (size_t i = 0; i < parent->owned_objects_count; i++)
|
|
{
|
|
if (parent->owned_objects[i] == obj)
|
|
{
|
|
found_in_parent = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!found_in_parent)
|
|
{
|
|
if (parent->owned_objects_remainder <= parent->owned_objects_count)
|
|
{
|
|
parent->owned_objects_remainder *= 2;
|
|
parent->owned_objects =
|
|
realloc(parent->owned_objects, parent->owned_objects_remainder *
|
|
sizeof(fzl_vm_object_t *));
|
|
}
|
|
|
|
parent->owned_objects[parent->owned_objects_count++] = obj;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < obj->values_count; i++)
|
|
{
|
|
if (obj->values[i].type == FZL_TYPE_OBJECT && obj->values[i].as.obj != NULL)
|
|
escape_recursive(current, parent, obj->values[i].as.obj);
|
|
}
|
|
}
|
|
|
|
void print_object(fzl_vm_object_t *obj)
|
|
{
|
|
if (!obj)
|
|
{
|
|
printf("<null>\n");
|
|
return;
|
|
}
|
|
|
|
printf("obj<");
|
|
for (size_t i = 0; i < obj->values_count; i++)
|
|
{
|
|
fzl_value_t arg = obj->values[i];
|
|
|
|
if (i > 0)
|
|
printf(", ");
|
|
|
|
if (arg.type == FZL_TYPE_STRING)
|
|
printf("str(%s)", arg.as.s.value);
|
|
else if (arg.type == FZL_TYPE_CHAR)
|
|
printf("char(%c)", arg.as.c);
|
|
else if (arg.type == FZL_TYPE_INT)
|
|
printf("int(%ld)", arg.as.i);
|
|
else if (arg.type == FZL_TYPE_UINT)
|
|
printf("uint(%lu)", arg.as.u);
|
|
else if (arg.type == FZL_TYPE_BOOL)
|
|
printf("bool(%s)", arg.as.b ? "true" : "false");
|
|
else if (arg.type == FZL_TYPE_NULL)
|
|
printf("<null>");
|
|
else if (arg.type == FZL_TYPE_OBJECT)
|
|
print_object(arg.as.obj);
|
|
else
|
|
printf("<unknown>");
|
|
}
|
|
|
|
printf(">");
|
|
}
|
|
|
|
void print_err_data(fzl_vm_t *vm)
|
|
{
|
|
printf("===== ERROR =====\n");
|
|
printf("> Caused on IP: %zu\n", vm->ip);
|
|
|
|
printf("\n> Data stack:\n");
|
|
if (vm->dtop > 0)
|
|
{
|
|
for (ssize_t i = vm->dtop - 1; i >= 0; i--)
|
|
{
|
|
printf("\t> [%d] ", vm->dstack[i].type);
|
|
switch (vm->dstack[i].type)
|
|
{
|
|
case FZL_TYPE_OBJECT:
|
|
printf("ref: %p\n", vm->dstack[i].as.obj);
|
|
break;
|
|
case FZL_TYPE_STRING:
|
|
printf("str: '%s'\n", vm->dstack[i].as.s.value);
|
|
break;
|
|
case FZL_TYPE_NULL:
|
|
printf("<null>\n");
|
|
break;
|
|
default:
|
|
printf("%lu\n", vm->dstack[i].as.u);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
printf("\tEmpty\n");
|
|
}
|
|
|
|
printf("\n> Environments:\n");
|
|
for (ssize_t i = vm->etop - 1; i >= 0; i--)
|
|
{
|
|
fzl_vm_env_t *env = vm->estack[i];
|
|
printf("\t> [%zd] %p:\n", i, env);
|
|
|
|
printf("\t\t> Owned objects (%zu/%zu):\n", env->owned_objects_count,
|
|
env->owned_objects_remainder);
|
|
for (size_t j = 0; j < env->owned_objects_count; j++)
|
|
printf("\t\t\t> ref: %p (entries: %zu)\n", env->owned_objects[j],
|
|
env->owned_objects[j]->values_count);
|
|
|
|
printf("\t\t> Local entries (%zu/%zu):\n", env->size, env->remainder);
|
|
for (size_t j = 0; j < env->size; j++)
|
|
{
|
|
switch (env->table[j].value.type)
|
|
{
|
|
case FZL_TYPE_OBJECT:
|
|
printf("\t\t\t> [%d] %s = ref %p\n", env->table[j].value.type,
|
|
env->table[j].name, env->table[j].value.as.obj);
|
|
break;
|
|
|
|
default:
|
|
printf("\t\t\t> [%d] %s = %lu\n", env->table[j].value.type,
|
|
env->table[j].name, env->table[j].value.as.u);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
printf("\n> Call stack:\n");
|
|
for (ssize_t i = vm->ctop - 1; i >= 0; i--)
|
|
printf("\t> [%zd] %lu\n", i, vm->cstack[i]);
|
|
}
|
|
|
|
int fzl_vm_run(fzl_vm_t *vm, bool allow_infinite, uint64_t max_iter)
|
|
{
|
|
if (!vm)
|
|
{
|
|
snprintf(last_err, sizeof(last_err), "vm is null");
|
|
return -1;
|
|
}
|
|
|
|
vm->ip = 0;
|
|
while (vm->ip < vm->ops_count)
|
|
{
|
|
fzl_opcode_t opcode = vm->ops[vm->ip];
|
|
|
|
switch (opcode)
|
|
{
|
|
case FZL_OPCODE_DEREFERENCE:
|
|
{
|
|
if (vm->dtop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected value in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t addr = vm->dstack[--vm->dtop];
|
|
if (addr.type < FZL_TYPE_INT8)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected address");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
if (addr.as.u == 0)
|
|
{
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_OBJECT, .as.obj = NULL};
|
|
break;
|
|
}
|
|
|
|
vm->dstack[vm->dtop++] = (fzl_value_t){
|
|
.type = FZL_TYPE_OBJECT, .as.obj = (fzl_vm_object_t *)addr.as.u};
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_CMP_EQ_STR:
|
|
{
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t left = vm->dstack[--vm->dtop];
|
|
fzl_value_t right = vm->dstack[--vm->dtop];
|
|
|
|
int result = strcmp(left.as.s.value, right.as.s.value);
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = result == 0};
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_CMP_NEQ_STR:
|
|
{
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t left = vm->dstack[--vm->dtop];
|
|
fzl_value_t right = vm->dstack[--vm->dtop];
|
|
|
|
int result = strcmp(left.as.s.value, right.as.s.value);
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = result != 0};
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_CMP_EQ_OBJ:
|
|
{
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t left = vm->dstack[--vm->dtop];
|
|
fzl_value_t right = vm->dstack[--vm->dtop];
|
|
|
|
vm->dstack[vm->dtop++] = (fzl_value_t){
|
|
.type = FZL_TYPE_BOOL, .as.b = left.as.obj == right.as.obj};
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_CMP_NEQ_OBJ:
|
|
{
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t left = vm->dstack[--vm->dtop];
|
|
fzl_value_t right = vm->dstack[--vm->dtop];
|
|
|
|
vm->dstack[vm->dtop++] = (fzl_value_t){
|
|
.type = FZL_TYPE_BOOL, .as.b = left.as.obj != right.as.obj};
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_ESCAPE_OBJECT:
|
|
{
|
|
if (vm->dtop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - missing value in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t obj = vm->dstack[--vm->dtop];
|
|
if (obj.type != FZL_TYPE_OBJECT)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - cannot escape non-object type");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_vm_env_t *parent = vm->estack[vm->etop - 2];
|
|
fzl_vm_env_t *current = vm->estack[vm->etop - 1];
|
|
if (vm->etop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - cannot escape object from global env");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
escape_recursive(current, parent, obj.as.obj);
|
|
vm->dstack[vm->dtop++] = obj;
|
|
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_ALLOC_OBJECT:
|
|
{
|
|
uint64_t values_count = vm->ops[++vm->ip];
|
|
fzl_vm_object_t *obj = malloc(sizeof(fzl_vm_object_t));
|
|
obj->values = malloc(values_count * sizeof(fzl_value_t));
|
|
obj->values_count = values_count;
|
|
memset(obj->values, 0, values_count * sizeof(fzl_value_t));
|
|
|
|
fzl_vm_env_t *current_env = vm->estack[vm->etop - 1];
|
|
if (current_env->owned_objects_remainder <=
|
|
current_env->owned_objects_count)
|
|
{
|
|
current_env->owned_objects_remainder *= 2;
|
|
current_env->owned_objects = realloc(
|
|
current_env->owned_objects,
|
|
current_env->owned_objects_remainder * sizeof(fzl_vm_object_t *));
|
|
}
|
|
|
|
current_env->owned_objects[current_env->owned_objects_count++] = obj;
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_OBJECT, .as.obj = obj};
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_GET_FIELD:
|
|
{
|
|
if (vm->dtop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected value in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
uint64_t field_slot = vm->ops[++vm->ip];
|
|
fzl_value_t val = vm->dstack[--vm->dtop];
|
|
if (val.type != FZL_TYPE_OBJECT)
|
|
{
|
|
snprintf(
|
|
last_err, sizeof(last_err),
|
|
"runtime error - cannot access field #%lu of non-object type",
|
|
field_slot);
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_vm_object_t *obj = val.as.obj;
|
|
if (obj->values_count <= field_slot)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - cannot access field #%lu", field_slot);
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
vm->dstack[vm->dtop++] = obj->values[field_slot];
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_SET_FIELD:
|
|
{
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
uint64_t field_slot = vm->ops[++vm->ip];
|
|
fzl_value_t objval = vm->dstack[--vm->dtop];
|
|
fzl_value_t wrval = vm->dstack[--vm->dtop];
|
|
if (objval.type != FZL_TYPE_OBJECT)
|
|
{
|
|
snprintf(
|
|
last_err, sizeof(last_err),
|
|
"runtime error - cannot access field #%lu of non-object type",
|
|
field_slot);
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_vm_object_t *obj = objval.as.obj;
|
|
if (obj->values_count <= field_slot)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - cannot access field #%lu", field_slot);
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
obj->values[field_slot] = wrval;
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_CALL:
|
|
{
|
|
uint64_t ip = vm->ops[++vm->ip];
|
|
vm->cstack[vm->ctop++] = vm->ip;
|
|
vm->ip = ip - 1;
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_RET:
|
|
{
|
|
if (vm->ctop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - no place to jump");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
vm->ip = vm->cstack[--vm->ctop];
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_CMP_EQ:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t req = vm->dstack[--vm->dtop];
|
|
fzl_value_t leq = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = leq.as.u == req.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_CMP_NEQ:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t rneq = vm->dstack[--vm->dtop];
|
|
fzl_value_t lneq = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] = (fzl_value_t){.type = FZL_TYPE_BOOL,
|
|
.as.b = lneq.as.u != rneq.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_CMP_LT:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t rlt = vm->dstack[--vm->dtop];
|
|
fzl_value_t llt = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = llt.as.u < rlt.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_CMP_LTEQ:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t rlteq = vm->dstack[--vm->dtop];
|
|
fzl_value_t llteq = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] = (fzl_value_t){
|
|
.type = FZL_TYPE_BOOL, .as.b = llteq.as.u <= rlteq.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_CMP_GT:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t rgt = vm->dstack[--vm->dtop];
|
|
fzl_value_t lgt = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = lgt.as.u > rgt.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_CMP_GTEQ:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t rgteq = vm->dstack[--vm->dtop];
|
|
fzl_value_t lgteq = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] = (fzl_value_t){
|
|
.type = FZL_TYPE_BOOL, .as.b = lgteq.as.u >= rgteq.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_NOT:
|
|
if (vm->dtop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected value in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t notval = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = !notval.as.b};
|
|
break;
|
|
|
|
case FZL_OPCODE_JZ:
|
|
if (vm->dtop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected value in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t jzval = vm->dstack[--vm->dtop];
|
|
uint64_t jzip = vm->ops[++vm->ip] - 1;
|
|
if (jzval.as.b == 0)
|
|
vm->ip = jzip;
|
|
break;
|
|
|
|
case FZL_OPCODE_JMP:
|
|
{
|
|
if (vm->ops_count <= vm->ip)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected JMP addr");
|
|
return -1;
|
|
}
|
|
|
|
uint64_t jmp_ip = vm->ops[++vm->ip];
|
|
if (!allow_infinite)
|
|
{
|
|
if (jmp_ip == last_jmp_ip)
|
|
total_jmp_on_same++;
|
|
else
|
|
total_jmp_on_same = 0;
|
|
|
|
if (total_jmp_on_same > max_iter)
|
|
{
|
|
snprintf(
|
|
last_err, sizeof(last_err),
|
|
"runtime error - exceeded max loop jumps (infinite loop?)\n"
|
|
"If you thinks there's mistake, change jumps limit with "
|
|
"--max-iter=N");
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
vm->ip = jmp_ip - 1;
|
|
last_jmp_ip = jmp_ip;
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_AND:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t land = vm->dstack[--vm->dtop];
|
|
fzl_value_t rand = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] = (fzl_value_t){.type = FZL_TYPE_BOOL,
|
|
.as.b = land.as.b && rand.as.b};
|
|
break;
|
|
|
|
case FZL_OPCODE_OR:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected two values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t lor = vm->dstack[--vm->dtop];
|
|
fzl_value_t ror = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = lor.as.b || ror.as.b};
|
|
break;
|
|
|
|
case FZL_OPCODE_STORE:
|
|
if (vm->dtop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected var name in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t varname = vm->dstack[--vm->dtop];
|
|
fzl_env_storevalue(vm->estack[vm->etop - 1], (char *)varname.as.s.value,
|
|
(fzl_value_t){});
|
|
|
|
break;
|
|
|
|
case FZL_OPCODE_WRITE:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected 2 values in data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t writevar = vm->dstack[--vm->dtop];
|
|
fzl_value_t value = vm->dstack[--vm->dtop];
|
|
|
|
char *wrname = (char *)writevar.as.s.value;
|
|
bool wrfound;
|
|
fzl_value_t wrenv;
|
|
ssize_t wrlvl = -1;
|
|
for (ssize_t i = vm->etop - 1; i >= 0; i--)
|
|
{
|
|
if ((wrfound = fzl_env_lookup(vm->estack[i], wrname, &wrenv)))
|
|
{
|
|
wrlvl = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (wrlvl <= -1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - name '%s' not found in this scope", wrname);
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_vm_env_wresult_t wresult =
|
|
fzl_env_writevalue(vm->estack[wrlvl], (char *)wrname, value);
|
|
if (wresult != FZL_VM_ENV_WRESULT_OK)
|
|
{
|
|
switch (wresult)
|
|
{
|
|
case FZL_VM_ENV_WRESULT_ERR:
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - failed to write variable");
|
|
print_err_data(vm);
|
|
return -1;
|
|
|
|
case FZL_VM_ENV_WRESULT_ERR_EXISTS:
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - redefenition of '%s'",
|
|
writevar.as.s.value);
|
|
print_err_data(vm);
|
|
return -1;
|
|
|
|
case FZL_VM_ENV_WRESULT_ERR_NOT_FOUND:
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - cannot find name '%s'",
|
|
writevar.as.s.value);
|
|
print_err_data(vm);
|
|
return -1;
|
|
|
|
default:
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
// vm->dstack[vm->dtop++] = value;
|
|
// NOTE: add POP opcode to prevent
|
|
// stack overflow!
|
|
break;
|
|
|
|
case FZL_OPCODE_MK_ENVTABLE:
|
|
vm->estack[vm->etop++] = fzl_mkenv();
|
|
break;
|
|
|
|
case FZL_OPCODE_RM_ENVTABLE:
|
|
fzl_freeenv(vm->estack[--vm->etop]);
|
|
break;
|
|
|
|
case FZL_OPCODE_LOOKUP_ENV:
|
|
{
|
|
if (vm->dtop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - LOOKUP_ENV: empty data stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
fzl_value_t val = vm->dstack[--vm->dtop];
|
|
fzl_value_t env_val;
|
|
bool result;
|
|
for (ssize_t i = vm->etop - 1; i >= 0; i--)
|
|
{
|
|
if ((result = fzl_env_lookup(vm->estack[i], (char *)val.as.s.value,
|
|
&env_val)))
|
|
break;
|
|
}
|
|
vm->dstack[vm->dtop++] = env_val;
|
|
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_CALL_BUILTIN:
|
|
{
|
|
uint64_t fn_id = vm->ops[++vm->ip];
|
|
if (fn_id == FZL_BUILTIN_PRINTLINE)
|
|
{
|
|
fzl_value_t arg = vm->dstack[--vm->dtop];
|
|
if (arg.type == FZL_TYPE_STRING)
|
|
printf("%s\n", arg.as.s.value);
|
|
else if (arg.type == FZL_TYPE_CHAR)
|
|
printf("%c\n", arg.as.c);
|
|
else if (arg.type == FZL_TYPE_INT)
|
|
printf("%ld\n", arg.as.i);
|
|
else if (arg.type == FZL_TYPE_UINT)
|
|
printf("%lu\n", arg.as.u);
|
|
else if (arg.type == FZL_TYPE_BOOL)
|
|
printf("%s\n", arg.as.b ? "true" : "false");
|
|
else if (arg.type == FZL_TYPE_NULL)
|
|
printf("<null>\n");
|
|
else if (arg.type == FZL_TYPE_OBJECT)
|
|
{
|
|
print_object(arg.as.obj);
|
|
printf("\n");
|
|
}
|
|
}
|
|
else if (fn_id == FZL_BUILTIN_EXIT)
|
|
{
|
|
fzl_value_t arg = vm->dstack[--vm->dtop];
|
|
return arg.as.i;
|
|
}
|
|
else
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - undefined builtin %lu", fn_id);
|
|
print_err_data(vm);
|
|
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");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_const_t c = vm->consts[cn];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_STRING,
|
|
.as.s.length = c.as.string.length,
|
|
.as.s.value = c.as.string.chars};
|
|
}
|
|
break;
|
|
|
|
case FZL_OPCODE_ADD:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected 2 values in stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t add1 = vm->dstack[--vm->dtop];
|
|
fzl_value_t add2 = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = add1.type, .as.u = add1.as.u + add2.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_SUB:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected 2 values in stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t sub2 = vm->dstack[--vm->dtop];
|
|
fzl_value_t sub1 = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = sub1.type, .as.u = sub1.as.u - sub2.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_MUL:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected 2 values in stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t mul1 = vm->dstack[--vm->dtop];
|
|
fzl_value_t mul2 = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = mul1.type, .as.u = mul1.as.u * mul2.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_DIV:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected 2 values in stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t div1 = vm->dstack[--vm->dtop];
|
|
fzl_value_t div2 = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = div1.type, .as.u = div2.as.u / div1.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_POW:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected 2 values in stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t pow2 = vm->dstack[--vm->dtop];
|
|
fzl_value_t pow1 = vm->dstack[--vm->dtop];
|
|
|
|
uint64_t pow_n = pow1.as.u;
|
|
uint64_t pow_res = pow_n;
|
|
uint64_t pow_times = pow2.as.u;
|
|
for (size_t i = 1; i < pow_times; i++)
|
|
pow_res *= pow_n;
|
|
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = pow1.type, .as.u = pow_res};
|
|
break;
|
|
|
|
case FZL_OPCODE_MOD:
|
|
if (vm->dtop < 2)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected 2 values in stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t mod1 = vm->dstack[--vm->dtop];
|
|
fzl_value_t mod2 = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = mod1.type, .as.u = mod2.as.u % mod1.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_NEG:
|
|
if (vm->dtop < 1)
|
|
{
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - expected value in stack");
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
fzl_value_t neg_n = vm->dstack[--vm->dtop];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = neg_n.type, .as.u = -neg_n.as.u};
|
|
break;
|
|
|
|
case FZL_OPCODE_PUSHNUM:
|
|
{
|
|
uint64_t push_n = vm->ops[++vm->ip];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_UINT, .as.u = push_n};
|
|
break;
|
|
}
|
|
|
|
case FZL_OPCODE_PUSHBOOL:
|
|
{
|
|
uint64_t push_b = vm->ops[++vm->ip];
|
|
vm->dstack[vm->dtop++] =
|
|
(fzl_value_t){.type = FZL_TYPE_BOOL, .as.b = push_b};
|
|
break;
|
|
}
|
|
|
|
default:
|
|
snprintf(last_err, sizeof(last_err),
|
|
"runtime error - unknown opcode %u", opcode);
|
|
print_err_data(vm);
|
|
return -1;
|
|
}
|
|
|
|
vm->ip++;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
char *fzl_vm_geterror() { return last_err; }
|
|
|
|
void fzl_freevm(fzl_vm_t *vm)
|
|
{
|
|
if (!vm)
|
|
return;
|
|
|
|
while (vm->etop > 0)
|
|
fzl_freeenv(vm->estack[--vm->etop]);
|
|
|
|
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);
|
|
}
|