Files
nunuhara_xsystem4/vm.c
T
Nunuhara Cabbage fd57ca7845 Fix loading of arrays
Array rank can't be inferred from JSON data since arrays and structs are
indistinguishable. So we get it from the ain file instead.

Sengoku Rance is mostly playable as of this commit.
2019-12-21 11:52:48 -08:00

1834 lines
42 KiB
C

/* Copyright (C) 2019 Nunuhara Cabbage <nunuhara@haniwa.technology>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://gnu.org/licenses/>.
*/
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include "system4.h"
#include "vm.h"
#include "vm_string.h"
#include "page.h"
#include "ain.h"
#include "file.h"
#include "instructions.h"
#include "little_endian.h"
#include "savedata.h"
#include "utfsjis.h"
#include "debugger.h"
#define INITIAL_STACK_SIZE 4096
#define INITIAL_HEAP_SIZE 4096
#define HEAP_ALLOC_STEP 4096
#define HLL_MAX_ARGS 64
// When the IP is set to VM_RETURN, the VM halts
#define VM_RETURN 0xFFFFFFFF
/*
* NOTE: The current implementation is a simple bytecode interpreter.
* System40.exe uses a JIT compiler, and we should too.
*/
struct function_call {
int32_t fno;
uint32_t return_address;
int32_t page_slot;
int32_t struct_page;
};
// The stack
union vm_value *stack = NULL; // the stack
int32_t stack_ptr = 0; // pointer to the top of the stack
static size_t stack_size; // current size of the stack
// The heap
// An array of pointers to heap-allocated objects, plus reference counts.
struct vm_pointer *heap;
static size_t heap_size;
// Heap free list
// This is a list of unused indices into the 'heap' array.
static int32_t *heap_free_stack;
static size_t heap_free_ptr = 0;
// Stack of function call frames
static struct function_call call_stack[4096];
static int32_t call_stack_ptr = 0; // 0 = imaginary frame before main()
struct ain *ain;
static size_t instr_ptr = 0;
// Read the opcode at ADDR.
static int16_t get_opcode(size_t addr)
{
return LittleEndian_getW(ain->code, addr);
}
// Read argument N for the current instruction.
static int32_t get_argument(int n)
{
return LittleEndian_getDW(ain->code, instr_ptr + 2 + n*4);
}
// XXX: not strictly portable
static float get_argument_float(int n)
{
union vm_value v;
v.i = LittleEndian_getDW(ain->code, instr_ptr + 2 + n*4);
return v.f;
}
static const char *current_instruction_name(void)
{
int16_t opcode = get_opcode(instr_ptr);
if (opcode >= 0 && opcode < NR_OPCODES)
return instructions[opcode].name;
return "UNKNOWN OPCODE";
}
int32_t heap_alloc_slot(enum vm_pointer_type type)
{
// grow heap if needed
if (heap_free_ptr >= heap_size) {
heap = xrealloc(heap, sizeof(struct vm_pointer) * (heap_size+HEAP_ALLOC_STEP));
heap_free_stack = xrealloc(heap_free_stack, sizeof(int32_t) * (heap_size+HEAP_ALLOC_STEP));
for (size_t i = heap_size; i < heap_size+HEAP_ALLOC_STEP; i++) {
heap_free_stack[i] = i;
}
heap_size += HEAP_ALLOC_STEP;
}
int32_t slot = heap_free_stack[heap_free_ptr++];
heap[slot].ref = 1;
heap[slot].type = type;
#ifdef DEBUG_HEAP
heap[slot].alloc_addr = instr_ptr;
heap[slot].ref_addr = 0;
#endif
return slot;
}
static void heap_free_slot(int32_t slot)
{
heap_free_stack[--heap_free_ptr] = slot;
}
void heap_ref(int32_t slot)
{
if (slot == -1)
return;
heap[slot].ref++;
#ifdef DEBUG_HEAP
heap[slot].ref_addr = instr_ptr;
#endif
}
static const char *vm_ptrtype_strtab[] = {
[VM_PAGE] = "VM_PAGE",
[VM_STRING] = "VM_STRING",
};
static const char *vm_ptrtype_string(enum vm_pointer_type type) {
if (type < NR_VM_POINTER_TYPES)
return vm_ptrtype_strtab[type];
return "INVALID POINTER TYPE";
}
void heap_unref(int slot)
{
if (heap[slot].ref <= 0) {
#ifdef DEBUG_HEAP
VM_ERROR("double free of slot %d (%s)\nOriginally allocd at %X\nOriginally freed at %X",
slot, vm_ptrtype_string(heap[slot].type), heap[slot].alloc_addr, heap[slot].free_addr);
#endif
VM_ERROR("double free of slot %d (%s)", slot, vm_ptrtype_string(heap[slot].type));
}
if (--heap[slot].ref <= 0) {
#ifdef DEBUG_HEAP
heap[slot].free_addr = instr_ptr;
#endif
switch (heap[slot].type) {
case VM_PAGE:
if (heap[slot].page) {
delete_page(heap[slot].page);
free_page(heap[slot].page);
}
break;
case VM_STRING:
free_string(heap[slot].s);
break;
}
heap_free_slot(slot);
}
}
void heap_set_page(int slot, struct page *page)
{
heap[slot].page = page;
}
static int local_page_slot(void)
{
return call_stack[call_stack_ptr-1].page_slot;
}
struct page *local_page(void)
{
return heap[local_page_slot()].page;
}
static union vm_value local_get(int varno)
{
return local_page()->values[varno];
}
static void local_set(int varno, int32_t value)
{
local_page()->values[varno].i = value;
}
static union vm_value *local_ptr(int varno)
{
return local_page()->values + varno;
}
union vm_value global_get(int varno)
{
return heap[0].page->values[varno];
}
void global_set(int varno, union vm_value val)
{
switch (ain->globals[varno].data_type) {
case AIN_STRING:
case AIN_STRUCT:
case AIN_ARRAY_TYPE:
if (heap[0].page->values[varno].i > 0) {
heap_unref(heap[0].page->values[varno].i);
}
default:
break;
}
heap[0].page->values[varno] = val;
}
static int32_t struct_page_slot(void)
{
return call_stack[call_stack_ptr-1].struct_page;
}
static union vm_value *struct_page(void)
{
return heap[struct_page_slot()].page->values;
}
bool page_index_valid(int index)
{
return index >= 0 && (size_t)index < heap_size && heap[index].ref > 0 && heap[index].type == VM_PAGE;
}
struct page *vm_get_page(int index)
{
if (!page_index_valid(index))
VM_ERROR("Invalid page index: %d", index);
return heap[index].page;
}
static union vm_value stack_peek(int n)
{
return stack[stack_ptr - (1 + n)];
}
union vm_value stack_pop(void)
{
stack_ptr--;
return stack[stack_ptr];
}
static union vm_value *stack_peek_ptr(int n)
{
return &stack[stack_ptr - (1 + n)];
}
// Pop a reference off the stack, returning the address of the referenced object.
static union vm_value *stack_pop_var(void)
{
int32_t page_index = stack_pop().i;
int32_t heap_index = stack_pop().i;
if (heap_index < 0 || (size_t)heap_index >= heap_size)
VM_ERROR("Out of bounds heap index: %d/%d", heap_index, page_index);
if (!heap[heap_index].page || page_index >= heap[heap_index].page->nr_vars)
VM_ERROR("Out of bounds page index: %d/%d", heap_index, page_index);
return &heap[heap_index].page->values[page_index];
}
static void stack_push_string(struct string *s)
{
int32_t heap_slot = heap_alloc_slot(VM_STRING);
heap[heap_slot].s = s;
stack[stack_ptr++].i = heap_slot;
}
static struct string *stack_peek_string(int n)
{
return heap[stack_peek(n).i].s;
}
int vm_string_ref(struct string *s)
{
int slot = heap_alloc_slot(VM_STRING);
heap[slot].s = string_ref(s);
return slot;
}
int vm_copy_page(struct page *page)
{
int slot = heap_alloc_slot(VM_PAGE);
heap_set_page(slot, copy_page(page));
return slot;
}
union vm_value vm_copy(union vm_value v, enum ain_data_type type)
{
switch (type) {
case AIN_STRING:
return (union vm_value) { .i = vm_string_ref(heap[v.i].s) };
case AIN_STRUCT:
case AIN_ARRAY_TYPE:
return (union vm_value) { .i = vm_copy_page(heap[v.i].page) };
default:
return v;
}
}
static int get_function_by_name(const char *name)
{
for (int i = 0; i < ain->nr_functions; i++) {
if (!strcmp(name, ain->functions[i].name))
return i;
}
return -1;
}
static int alloc_scenario_page(const char *fname)
{
int fno, slot;
struct ain_function *f;
if ((fno = get_function_by_name(fname)) < 0)
VM_ERROR("Invalid scenario function: %s", fname);
f = &ain->functions[fno];
slot = heap_alloc_slot(VM_PAGE);
heap_set_page(slot, alloc_page(LOCAL_PAGE, fno, f->nr_vars));
for (int i = 0; i < f->nr_vars; i++) {
heap[slot].page->values[i] = variable_initval(f->vars[i].data_type);
}
return slot;
}
static void scenario_call(int slot)
{
int fno = heap[slot].page->index;
// flush call stack
for (int i = call_stack_ptr - 1; i >= 0; i--) {
heap_unref(call_stack[i].page_slot);
}
call_stack[0] = (struct function_call) {
.fno = fno,
.return_address = VM_RETURN,
.page_slot = slot,
.struct_page = -1
};
call_stack_ptr = 1;
instr_ptr = ain->functions[fno].address;
}
/*
* System 4 calling convention:
* - caller pushes arguments, in order
* - CALLFUNC creates stack frame, pops arguments into local page
* - callee pushes return value on the stack
* - RETURN jumps to return address (saved in stack frame)
*/
static void function_call(int fno, int return_address)
{
struct ain_function *f = &ain->functions[fno];
int slot = heap_alloc_slot(VM_PAGE);
heap_set_page(slot, alloc_page(LOCAL_PAGE, fno, f->nr_vars));
call_stack[call_stack_ptr++] = (struct function_call) {
.fno = fno,
.return_address = return_address,
.page_slot = slot,
.struct_page = -1
};
// pop arguments, store in local page
for (int i = f->nr_args - 1; i >= 0; i--) {
heap[slot].page->values[i] = stack_pop();
switch (f->vars[i].data_type) {
case AIN_REF_TYPE:
heap_ref(heap[slot].page->values[i].i);
break;
}
}
// initialize local variables
for (int i = f->nr_args; i < f->nr_vars; i++) {
heap[slot].page->values[i] = variable_initval(f->vars[i].data_type);
}
// jump to function start
instr_ptr = ain->functions[fno].address;
}
static void method_call(int fno, int return_address)
{
function_call(fno, return_address);
call_stack[call_stack_ptr-1].struct_page = stack_pop().i;
}
static void vm_execute(void);
void vm_call(int fno, int struct_page)
{
size_t saved_ip = instr_ptr;
if (struct_page < 0) {
function_call(fno, VM_RETURN);
} else {
stack_push(struct_page);
method_call(fno, VM_RETURN);
}
vm_execute();
instr_ptr = saved_ip;
}
static void hll_call(int libno, int fno)
{
struct ain_hll_function *f = &ain->libraries[libno].functions[fno];
if (!f->fun)
VM_ERROR("Unimplemented HLL function: %s.%s", ain->libraries[libno].name, f->name);
union vm_value args[HLL_MAX_ARGS];
for (int i = f->nr_arguments - 1; i >= 0; i--) {
int pageno, varno;
switch(f->arguments[i].data_type) {
case AIN_REF_INT:
case AIN_REF_BOOL:
case AIN_REF_FLOAT:
stack_ptr -= 2;
pageno = stack[stack_ptr].i;
varno = stack[stack_ptr+1].i;
args[i].ref = &heap[pageno].page->values[varno];
break;
default:
stack_ptr--;
args[i] = stack[stack_ptr];
}
}
union vm_value r = f->fun(args);
for (int i = 0; i < f->nr_arguments; i++) {
// XXX: We don't increase the ref count when passing ref arguments to HLL
// functions, so we need to avoid decreasing it via variable_fini
switch (f->arguments[i].data_type) {
case AIN_REF_TYPE:
break;
default:
variable_fini(stack[stack_ptr + i], f->arguments[i].data_type);
break;
}
}
if (f->data_type != AIN_VOID)
stack_push(r);
}
static void function_return(void)
{
heap_unref(call_stack[call_stack_ptr-1].page_slot);
instr_ptr = call_stack[call_stack_ptr-1].return_address;
call_stack_ptr--;
}
enum syscall_code {
SYS_EXIT = 0x00,
SYS_GLOBAL_SAVE = 0x01,
SYS_GLOBAL_LOAD = 0x02,
SYS_LOCK_PEEK = 0x03,
SYS_UNLOCK_PEEK = 0x04,
SYS_OUTPUT = 0x06,
SYS_EXISTS_FILE = 0x0A,
SYS_GET_SAVE_FOLDER_NAME = 0x0C,
SYS_GET_TIME = 0x0D,
SYS_ERROR = 0x0F,
SYS_EXISTS_SAVE_FILE = 0x10,
SYS_IS_DEBUG_MODE = 0x11,
SYS_GET_FUNC_STACK_NAME = 0x13,
SYS_PEEK = 0x14,
SYS_SLEEP = 0x15,
SYS_GROUP_SAVE = 0x18,
SYS_GROUP_LOAD = 0x19
};
static void system_call(int32_t code)
{
char *utf;
struct string *str;
switch (code) {
case SYS_EXIT: // system.Exit(int nResult)
vm_exit(stack_pop().i);
break;
case SYS_GLOBAL_SAVE: { // system.GlobalSave(string szKeyName, string szFileName)
int filename = stack_pop().i;
int keyname = stack_pop().i;
stack_push(save_globals(heap[keyname].s->text, heap[filename].s->text));
heap_unref(filename);
heap_unref(keyname);
break;
}
case SYS_GLOBAL_LOAD: { // system.GlobalLoad(string szKeyName, string szFileName)
int filename = stack_pop().i;
int keyname = stack_pop().i;
stack_push(load_globals(heap[keyname].s->text, heap[filename].s->text, NULL, NULL));
heap_unref(filename);
heap_unref(keyname);
break;
}
case SYS_LOCK_PEEK: // system.LockPeek(void)
case SYS_UNLOCK_PEEK: // system.UnlockPeek(void)
stack_push(1);
break;
case SYS_OUTPUT: // system.Output(string szText)
str = stack_peek_string(0);
utf = sjis2utf(str->text, str->size);
sys_message("%s", utf);
free(utf);
// XXX: caller S_POPs
break;
case SYS_EXISTS_FILE: { // system.ExistsFile(string szFileName)
int str = stack_pop().i;
char *path = unix_path(heap[str].s->text);
stack_push(file_exists(path));
heap_unref(str);
free(path);
break;
}
case SYS_GET_SAVE_FOLDER_NAME: // system.GetSaveFolderName(void)
if (config.save_dir)
stack_push_string(make_string(config.save_dir, strlen(config.save_dir)));
else
stack_push_string(string_ref(&EMPTY_STRING));
break;
case SYS_GET_TIME: // system.GetTime(void)
stack_push(vm_time());
break;
case SYS_ERROR: // system.Error(string szText)
str = stack_peek_string(0);
utf = sjis2utf(str->text, str->size);
sys_warning("*GAME ERROR*: %s\n", utf);
free(utf);
// XXX: caller S_POPs
break;
case SYS_EXISTS_SAVE_FILE: {
int slot = stack_pop().i;
struct string *name = heap[slot].s;
size_t dir_len = strlen(config.save_dir);
char *path = xmalloc(dir_len + 1 + name->size + 1);
strncpy(path, config.save_dir, dir_len);
path[dir_len] = '/';
strncpy(path+dir_len+1, name->text, name->size);
path[dir_len+1+name->size] = '\0';
stack_push(file_exists(path));
heap_unref(slot);
free(path);
break;
}
case SYS_IS_DEBUG_MODE: // system.IsDebugMode(void)
stack_push(0);
break;
case SYS_GET_FUNC_STACK_NAME: { // system.GetFuncStackName(int nIndex)
int i = call_stack_ptr - (1 + stack_pop().i);
if (i < 0 || i >= call_stack_ptr) {
const char *msg = "Invalid stack index";
stack_push_string(make_string(msg, strlen(msg)));
return;
}
struct function_call *call = &call_stack[i];
struct ain_function *fun = &ain->functions[call->fno];
stack_push_string(make_string(fun->name, strlen(fun->name)));
break;
}
case SYS_PEEK: // system.Peek(void)
break;
case SYS_SLEEP: // system.Sleep(int nSleep)
stack_pop();
break;
case SYS_GROUP_SAVE: { // system.GroupSave(string szKeyName, string szFileName, string szGroupName, ref int nNumofLoad)
union vm_value *n = stack_pop_var();
int groupname = stack_pop().i;
int filename = stack_pop().i;
int keyname = stack_pop().i;
stack_push(save_group(heap[keyname].s->text, heap[filename].s->text, heap[groupname].s->text, &n->i));
heap_unref(groupname);
heap_unref(filename);
heap_unref(keyname);
break;
}
case SYS_GROUP_LOAD: { // system.GroupLoad(string szKeyName, string szFileName, string szGroupName, ref int nNumofLoad)
union vm_value *n = stack_pop_var();
int groupname = stack_pop().i;
int filename = stack_pop().i;
int keyname = stack_pop().i;
stack_push(load_globals(heap[keyname].s->text, heap[filename].s->text, heap[groupname].s->text, &n->i));
heap_unref(groupname);
heap_unref(filename);
heap_unref(keyname);
break;
}
default:
VM_ERROR("Unimplemented syscall: 0x%X", code);
}
}
void exec_switch(int no, int val)
{
struct ain_switch *s = &ain->switches[no];
for (int i = 0; i < s->nr_cases; i++) {
if (s->cases[i].value == val) {
instr_ptr = s->cases[i].address;
return;
}
}
if (s->default_address > 0)
instr_ptr = s->default_address;
else
instr_ptr += instruction_width(SWITCH);
}
void exec_strswitch(int no, struct string *str)
{
struct ain_switch *s = &ain->switches[no];
for (int i = 0; i < s->nr_cases; i++) {
if (!strcmp(str->text, ain->strings[s->cases[i].value]->text)) {
instr_ptr = s->cases[i].address;
return;
}
}
if (s->default_address > 0)
instr_ptr = s->default_address;
else
instr_ptr += instruction_width(STRSWITCH);
}
static void execute_instruction(enum opcode opcode)
{
switch (opcode) {
//
// --- Stack Management ---
//
case PUSH: {
stack_push(get_argument(0));
break;
}
case POP: {
stack_pop();
break;
}
case F_PUSH: {
stack_push(get_argument_float(0));
break;
}
case REF: {
// Dereference a reference to a value.
stack_push(stack_pop_var()->i);
break;
}
case REFREF: {
// Dereference a reference to a reference.
union vm_value *ref = stack_pop_var();
stack_push(ref[0].i);
stack_push(ref[1].i);
break;
}
case DUP: {
// A -> AA
stack_push(stack_peek(0).i);
break;
}
case DUP2: {
// AB -> ABAB
int a = stack_peek(1).i;
int b = stack_peek(0).i;
stack_push(a);
stack_push(b);
break;
}
case DUP_X2: {
// ABC -> CABC
int a = stack_peek(2).i;
int b = stack_peek(1).i;
int c = stack_peek(0).i;
stack_set(2, c);
stack_set(1, a);
stack_set(0, b);
stack_push(c);
break;
}
case DUP2_X1: {
// ABC -> BCABC
int a = stack_peek(2).i;
int b = stack_peek(1).i;
int c = stack_peek(0).i;
stack_set(2, b);
stack_set(1, c);
stack_set(0, a);
stack_push(b);
stack_push(c);
break;
}
case DUP_U2: {
// AB -> ABA
stack_push(stack_peek(1).i);
break;
}
case SWAP: {
int a = stack_peek(1).i;
stack_set(1, stack_peek(0));
stack_set(0, a);
break;
}
//
// --- Variables ---
//
case PUSHGLOBALPAGE: {
stack_push(0);
break;
}
case PUSHLOCALPAGE: {
stack_push(local_page_slot());
break;
}
case PUSHSTRUCTPAGE: {
stack_push(struct_page_slot());
break;
}
case ASSIGN:
case F_ASSIGN: {
union vm_value val = stack_pop();
stack_pop_var()[0] = val;
stack_push(val);
break;
}
case SH_GLOBALREF: { // VARNO
stack_push(global_get(get_argument(0)).i);
break;
}
case SH_LOCALREF: { // VARNO
stack_push(local_get(get_argument(0)).i);
break;
}
case SH_STRUCTREF: { // VARNO
stack_push(struct_page()[get_argument(0)]);
break;
}
case SH_LOCALASSIGN: { // VARNO, VALUE
local_set(get_argument(0), get_argument(1));
break;
}
case SH_LOCALINC: { // VARNO
int varno = get_argument(0);
local_set(varno, local_get(varno).i+1);
break;
}
case SH_LOCALDEC: { // VARNO
int varno = get_argument(0);
local_set(varno, local_get(varno).i-1);
break;
}
case SH_LOCALDELETE: {
int slot = local_get(get_argument(0)).i;
if (slot != -1) {
heap_unref(slot);
local_set(get_argument(0), -1);
}
break;
}
case SH_LOCALCREATE: { // VARNO, STRUCTNO
create_struct(get_argument(1), local_ptr(get_argument(0)));
break;
}
case R_ASSIGN: {
int src_var = stack_pop().i;
int src_page = stack_pop().i;
int dst_var = stack_pop().i;
int dst_page = stack_pop().i;
heap[dst_page].page->values[dst_var].i = src_page;
heap[dst_page].page->values[dst_var+1].i = src_var;
stack_push(src_page);
stack_push(src_var);
break;
}
case DELETE: {
int slot = stack_pop().i;
if (slot != -1)
heap_unref(slot);
break;
}
case SP_INC: {
heap_ref(stack_pop().i);
break;
}
case OBJSWAP: {
stack_pop(); // type
union vm_value *b = stack_pop_var();
union vm_value *a = stack_pop_var();
union vm_value tmp = *a;
*a = *b;
*b = tmp;
break;
}
//
// --- Control Flow ---
//
case CALLFUNC: {
function_call(get_argument(0), instr_ptr + instruction_width(CALLFUNC));
break;
}
case CALLFUNC2: {
stack_pop(); // function-type index (only needed for compilation)
function_call(stack_pop().i, instr_ptr + instruction_width(CALLFUNC2));
break;
}
case CALLMETHOD: {
method_call(get_argument(0), instr_ptr + instruction_width(CALLMETHOD));
break;
}
case CALLHLL: {
hll_call(get_argument(0), get_argument(1));
break;
}
case RETURN: {
function_return();
break;
}
case CALLSYS: {
system_call(get_argument(0));
break;
}
case CALLONJUMP: {
int str = stack_pop().i;
// XXX: I am GUESSING that the VM pre-allocates the scenario function's
// local page here. It certainly pushes what appears to be a page
// index to the stack.
stack_push(alloc_scenario_page(heap[str].s->text));
heap_unref(str);
break;
}
case SJUMP: {
scenario_call(stack_pop().i);
break;
}
case MSG: {
if (ain->msgf < 0)
break;
stack_push(get_argument(0));
stack_push(ain->nr_messages);
stack_push_string(string_ref(ain->messages[get_argument(0)]));
function_call(ain->msgf, instr_ptr + instruction_width(MSG));
break;
}
case JUMP: { // ADDR
instr_ptr = get_argument(0);
break;
}
case IFZ: { // ADDR
if (!stack_pop().i)
instr_ptr = get_argument(0);
else
instr_ptr += instruction_width(IFZ);
break;
}
case IFNZ: { // ADDR
if (stack_pop().i)
instr_ptr = get_argument(0);
else
instr_ptr += instruction_width(IFNZ);
break;
}
case SWITCH: {
exec_switch(get_argument(0), stack_pop().i);
break;
}
case STRSWITCH: {
int str = stack_pop().i;
exec_strswitch(get_argument(0), heap[str].s);
heap_unref(str);
break;
}
case ASSERT: {
int line = stack_pop().i; // line number
int file = stack_pop().i; // filename
int expr = stack_pop().i; // expression
if (!stack_pop().i) {
char *filename = sjis2utf(heap[file].s->text, heap[file].s->size);
char *value = sjis2utf(heap[expr].s->text, heap[expr].s->size);
sys_message("Assertion failed at %s:%d: %s\n", filename, line, value);
free(filename);
free(value);
vm_exit(1);
}
heap_unref(file);
heap_unref(expr);
break;
}
//
// --- Arithmetic ---
//
case INV: {
stack[stack_ptr-1].i = -stack[stack_ptr-1].i;
break;
}
case NOT: {
stack[stack_ptr-1].i = !stack[stack_ptr-1].i;
break;
}
case COMPL: {
stack[stack_ptr-1].i = ~stack[stack_ptr-1].i;
break;
}
case ADD: {
stack[stack_ptr-2].i += stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case SUB: {
stack[stack_ptr-2].i -= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case MUL: {
stack[stack_ptr-2].i *= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case DIV: {
stack[stack_ptr-2].i /= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case MOD: {
stack[stack_ptr-2].i %= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case AND: {
stack[stack_ptr-2].i &= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case OR: {
stack[stack_ptr-2].i |= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case XOR: {
stack[stack_ptr-2].i ^= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case LSHIFT: {
stack[stack_ptr-2].i <<= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
case RSHIFT: {
stack[stack_ptr-2].i >>= stack[stack_ptr-1].i;
stack_ptr--;
break;
}
// Numeric Comparisons
case LT: {
int32_t b = stack_pop().i;
int32_t a = stack_pop().i;
stack_push(a < b ? 1 : 0);
break;
}
case GT: {
int32_t b = stack_pop().i;
int32_t a = stack_pop().i;
stack_push(a > b ? 1 : 0);
break;
}
case LTE: {
int32_t b = stack_pop().i;
int32_t a = stack_pop().i;
stack_push(a <= b ? 1 : 0);
break;
}
case GTE: {
int32_t b = stack_pop().i;
int32_t a = stack_pop().i;
stack_push(a >= b ? 1 : 0);
break;
}
case NOTE: {
int32_t b = stack_pop().i;
int32_t a = stack_pop().i;
stack_push(a != b ? 1 : 0);
break;
}
case EQUALE: {
int32_t b = stack_pop().i;
int32_t a = stack_pop().i;
stack_push(a == b ? 1 : 0);
break;
}
// +=, -=, etc.
case PLUSA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i += n);
break;
}
case MINUSA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i -= n);
break;
}
case MULA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i *= n);
break;
}
case DIVA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i /= n);
break;
}
case MODA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i %= n);
break;
}
case ANDA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i &= n);
break;
}
case ORA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i |= n);
break;
}
case XORA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i ^= n);
break;
}
case LSHIFTA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i <<= n);
break;
}
case RSHIFTA: {
int32_t n = stack_pop().i;
stack_push(stack_pop_var()->i >>= n);
break;
}
case INC: {
stack_pop_var()[0].i++;
break;
}
case DEC: {
stack_pop_var()[0].i--;
break;
}
case ITOB: {
stack_set(0, !!stack_peek(0).i);
break;
}
//
// --- 64-bit integers ---
//
case ITOLI: {
stack_set(0, (int64_t)stack_peek(0).i);
break;
}
case LI_ADD: {
stack[stack_ptr-2].li += stack[stack_ptr-1].li;
stack_ptr--;
break;
}
case LI_SUB: {
stack[stack_ptr-2].li -= stack[stack_ptr-1].li;
stack_ptr--;
break;
}
case LI_MUL: {
stack[stack_ptr-2].li *= stack[stack_ptr-1].li;
stack_ptr--;
break;
}
case LI_DIV: {
stack[stack_ptr-2].li /= stack[stack_ptr-1].li;
stack_ptr--;
break;
}
case LI_MOD: {
stack[stack_ptr-2].li %= stack[stack_ptr-1].li;
stack_ptr--;
break;
}
case LI_ASSIGN: {
int64_t v = stack_pop().li;
stack_push(stack_pop_var()->li = v);
break;
}
case LI_PLUSA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li += n);
break;
}
case LI_MINUSA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li -= n);
break;
}
case LI_MULA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li *= n);
break;
}
case LI_DIVA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li /= n);
break;
}
case LI_MODA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li %= n);
break;
}
case LI_ANDA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li &= n);
break;
}
case LI_ORA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li |= n);
break;
}
case LI_XORA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li ^= n);
break;
}
case LI_LSHIFTA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li <<= n);
break;
}
case LI_RSHIFTA: {
int64_t n = stack_pop().i;
stack_push(stack_pop_var()->li >>= n);
break;
}
case LI_INC: {
stack_pop_var()->li++;
break;
}
case LI_DEC: {
stack_pop_var()->li--;
break;
}
//
// --- Floating Point Arithmetic ---
//
case FTOI: {
stack_set(0, (int32_t)stack_peek(0).f);
break;
}
case ITOF: {
stack_set(0, (float)stack_peek(0).i);
break;
}
case F_INV: {
stack_set(0, -stack_peek(0).f);
break;
}
case F_ADD: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f + f);
break;
}
case F_SUB: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f - f);
break;
}
case F_MUL: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f * f);
break;
}
case F_DIV: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f / f);
break;
}
// floating point comparison
case F_LT: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f < f ? 1 : 0);
break;
}
case F_GT: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f > f ? 1 : 0);
break;
}
case F_LTE: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f <= f ? 1 : 0);
break;
}
case F_GTE: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f >= f ? 1 : 0);
break;
}
case F_NOTE: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f != f ? 1 : 0);
break;
}
case F_EQUALE: {
float f = stack_pop().f;
stack_set(0, stack_peek(0).f == f ? 1 : 0);
break;
}
case F_PLUSA: {
float n = stack_pop().f;
stack_push(stack_pop_var()->f += n);
break;
}
case F_MINUSA: {
float n = stack_pop().f;
stack_push(stack_pop_var()->f -= n);
break;
}
case F_MULA: {
float n = stack_pop().f;
stack_push(stack_pop_var()->f *= n);
break;
}
case F_DIVA: {
float n = stack_pop().f;
stack_push(stack_pop_var()->f /= n);
break;
}
//
// --- Strings ---
//
case S_PUSH: {
stack_push_string(string_ref(ain->strings[get_argument(0)]));
break;
}
case S_POP: {
heap_unref(stack_pop().i);
break;
}
case S_REF: {
// Dereference a reference to a string
int str = stack_pop_var()->i;
stack_push_string(string_ref(heap[str].s));
break;
}
//case S_REFREF: // ???: why/how is this different from regular REFREF?
case S_ASSIGN: { // A = B
int rval = stack_peek(0).i;
int lval = stack_peek(1).i;
if (heap[lval].s) {
free_string(heap[lval].s);
}
heap[lval].s = string_ref(heap[rval].s);
// remove A from the stack, but leave B
stack_set(1, rval);
stack_pop();
break;
}
case S_PLUSA2: {
int a = stack_peek(1).i;
int b = stack_peek(0).i;
string_append(&heap[a].s, heap[b].s);
heap_unref(b);
stack_pop();
stack_pop();
stack_push_string(string_ref(heap[a].s));
break;
}
case S_ADD: {
int b = stack_pop().i;
int a = stack_pop().i;
// TODO: can use string_append here?
stack_push_string(string_concatenate(heap[a].s, heap[b].s));
heap_unref(a);
heap_unref(b);
break;
}
case S_LT: {
bool lt = strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text) < 0;
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(lt);
break;
}
case S_GT: {
bool gt = strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text) > 0;
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(gt);
break;
}
case S_LTE: {
bool lte = strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text) <= 0;
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(lte);
break;
}
case S_GTE: {
bool gte = strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text) >= 0;
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(gte);
break;
}
case S_NOTE: {
bool noteq = !!strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text);
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(noteq);
break;
}
case S_EQUALE: {
bool eq = !strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text);
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(eq);
break;
}
case S_LENGTH: {
int str = stack_pop_var()->i;
stack_push(sjis_count_char(heap[str].s->text));
break;
}
case S_LENGTH2: {
int str = stack_pop().i;
stack_push(sjis_count_char(heap[str].s->text));
heap_unref(str);
break;
}
case S_LENGTHBYTE: {
int str = stack_pop_var()->i;
stack_push(heap[str].s->size);
break;
}
case S_EMPTY: {
bool empty = !stack_peek_string(0)->size;
heap_unref(stack_pop().i);
stack_push(empty);
break;
}
case S_FIND: {
int i = string_find(stack_peek_string(1), stack_peek_string(0));
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(i);
break;
}
case S_GETPART: {
int len = stack_pop().i; // length
int i = stack_pop().i; // index
struct string *s = string_copy(stack_peek_string(0), i, len);
heap_unref(stack_pop().i);
stack_push_string(s);
break;
}
//case S_PUSHBACK: // ???
case S_PUSHBACK2: {
int c = stack_pop().i;
int str = stack_pop().i;
string_push_back(&heap[str].s, c);
break;
}
//case S_POPBACK: // ???
case S_POPBACK2: {
int str = stack_pop().i;
string_pop_back(&heap[str].s);
break;
}
//case S_ERASE: // ???
case S_ERASE2: {
stack_pop(); // ???
int i = stack_pop().i; // index
int str = stack_pop().i;
string_erase(&heap[str].s, i);
break;
}
case S_MOD: {
stack_pop(); // ???
union vm_value val = stack_pop();
int fmt = stack_pop().i;
int dst = heap_alloc_slot(VM_STRING);
heap[dst].s = string_format(heap[fmt].s, val);
heap_unref(fmt);
stack_push(dst);
break;
}
case I_STRING: {
stack_push_string(integer_to_string(stack_pop().i));
break;
}
case FTOS: {
int precision = stack_pop().i;
stack_push_string(float_to_string(stack_pop().f, precision));
break;
}
case STOI: {
int str = stack_pop().i;
stack_push(string_to_integer(heap[str].s));
heap_unref(str);
break;
}
case FT_ASSIGNS: {
//int functype = stack_pop().i;
stack_pop();
int str = stack_pop().i;
stack_pop_var()->i = get_function_by_name(heap[str].s->text);
stack_push(str);
break;
}
// --- Characters ---
case C_REF: {
int i = stack_pop().i;
int str = stack_pop().i;
stack_push(string_get_char(heap[str].s, i));
break;
}
case C_ASSIGN: {
int c = stack_pop().i;
int i = stack_pop().i;
int str = stack_pop().i;
string_set_char(&heap[str].s, i, c);
stack_push(c);
break;
}
//
// --- Structs/Classes ---
//
case SR_REF: {
stack_push(vm_copy_page(heap[stack_pop_var()->i].page));
break;
}
case SR_POP: {
heap_unref(stack_pop().i);
break;
}
case SR_ASSIGN: {
stack_pop(); // struct type
int rval = stack_pop().i;
int lval = stack_pop().i;
if (lval == -1)
VM_ERROR("Assignment to null-pointer");
if (heap[lval].page) {
delete_page(heap[lval].page);
free_page(heap[lval].page);
}
heap_set_page(lval, copy_page(heap[rval].page));
stack_push(rval);
break;
}
//
// -- Arrays --
//
case A_ALLOC: {
int struct_type;
int rank = stack_pop().i;
int varno = stack_peek(rank).i;
int pageno = stack_peek(rank+1).i;
int array = heap[pageno].page->values[varno].i;
enum ain_data_type data_type = variable_type(heap[pageno].page, varno, &struct_type, NULL);
heap_set_page(array, alloc_array(rank, stack_peek_ptr(rank-1), data_type, struct_type, true));
stack_ptr -= rank + 2;
break;
}
case A_REALLOC: {
int struct_type;
int rank = stack_pop().i; // rank
int varno = stack_peek(rank).i;
int pageno = stack_peek(rank+1).i;
int array = heap[pageno].page->values[varno].i;
enum ain_data_type data_type = variable_type(heap[pageno].page, varno, &struct_type, NULL);
heap_set_page(array, realloc_array(heap[array].page, rank, stack_peek_ptr(rank-1), data_type, struct_type, true));
stack_ptr -= rank + 2;
break;
}
case A_FREE: {
int array = stack_pop_var()->i;
if (heap[array].page) {
delete_page(heap[array].page);
free_page(heap[array].page);
heap_set_page(array, NULL);
}
break;
}
case A_REF: {
int array = stack_pop().i;
int slot = heap_alloc_slot(VM_PAGE);
heap_set_page(slot, copy_page(heap[array].page));
stack_push(slot);
break;
}
case A_NUMOF: {
int rank = stack_pop().i; // rank
int array = stack_pop_var()->i;
stack_push(array_numof(heap[array].page, rank));
break;
}
case A_COPY: {
int n = stack_pop().i;
int src_i = stack_pop().i;
int src = stack_pop().i;
int dst_i = stack_pop().i;
int dst = stack_pop_var()->i;
array_copy(heap[dst].page, dst_i, heap[src].page, src_i, n);
stack_push(n);
break;
}
case A_FILL: {
union vm_value val = stack_pop();
int n = stack_pop().i;
int i = stack_pop().i;
int array = stack_pop_var()->i;
stack_push(array_fill(heap[array].page, i, n, val));
break;
}
case A_PUSHBACK: {
int struct_type;
union vm_value val = stack_pop();
int varno = stack_pop().i;
int pageno = stack_pop().i;
int array = heap[pageno].page->values[varno].i;
enum ain_data_type data_type = variable_type(heap[pageno].page, varno, &struct_type, NULL);
array_pushback(&heap[array].page, val, data_type, struct_type);
break;
}
case A_POPBACK: {
array_popback(&heap[stack_pop_var()->i].page);
break;
}
case A_EMPTY: {
int array = stack_pop_var()->i;
stack_push(!heap[array].page);
break;
}
case A_ERASE: {
int i = stack_pop().i;
int array = stack_pop_var()->i;
stack_push(array_erase(&heap[array].page, i));
break;
}
case A_INSERT: {
int struct_type;
union vm_value val = stack_pop();
int i = stack_pop().i;
int varno = stack_pop().i;
int pageno = stack_pop().i;
int array = heap[pageno].page->values[varno].i;
enum ain_data_type data_type = variable_type(heap[pageno].page, varno, &struct_type, NULL);
array_insert(&heap[array].page, i, val, data_type, struct_type);
break;
}
case A_SORT: {
int fno = stack_pop().i;
int array = stack_pop_var()->i;
array_sort(heap[array].page, fno);
break;
}
case A_FIND: {
int fno = stack_pop().i;
union vm_value v = stack_pop();
int end = stack_pop().i;
int start = stack_pop().i;
int array = stack_pop_var()->i;
stack_push(array_find(heap[array].page, start, end, v, fno));
break;
}
case A_REVERSE: {
int array = stack_pop_var()->i;
array_reverse(heap[array].page);
break;
}
// -- NOOPs ---
case FUNC:
break;
default:
VM_ERROR("Unimplemented instruction");
}
}
static void vm_execute(void)
{
for (;;) {
uint16_t opcode;
if (instr_ptr == VM_RETURN)
return;
if (instr_ptr >= ain->code_size) {
VM_ERROR("Illegal instruction pointer: 0x%08lX", instr_ptr);
}
opcode = get_opcode(instr_ptr);
if (opcode >= NR_OPCODES) {
VM_ERROR("Illegal opcode: 0x%04X", opcode);
}
execute_instruction(opcode);
instr_ptr += instructions[opcode].ip_inc;
}
}
extern struct library lib_ACXLoader;
extern struct library lib_AliceLogo;
extern struct library lib_AliceLogo2;
extern struct library lib_AliceLogo3;
extern struct library lib_Confirm2;
extern struct library lib_DrawGraph;
extern struct library lib_DrawPluginManager;
extern struct library lib_File;
extern struct library lib_Math;
extern struct library lib_MsgLogManager;
extern struct library lib_MsgSkip;
extern struct library lib_OutputLog;
extern struct library lib_PlayMovie;
extern struct library lib_SACT2;
extern struct library lib_SystemServiceEx;
struct library *libraries[] = {
&lib_ACXLoader,
&lib_AliceLogo,
&lib_AliceLogo2,
&lib_AliceLogo3,
&lib_Confirm2,
&lib_DrawGraph,
&lib_DrawPluginManager,
&lib_File,
&lib_Math,
&lib_MsgLogManager,
&lib_MsgSkip,
&lib_OutputLog,
&lib_PlayMovie,
&lib_SACT2,
&lib_SystemServiceEx,
NULL
};
static void link_library(struct ain_library *ainlib, struct library *lib)
{
for (int i = 0; i < ainlib->nr_functions; i++) {
bool linked = false;
for (int j = 0; lib->functions[j]; j++) {
if (!strcmp(ainlib->functions[i].name, lib->functions[j]->name)) {
ainlib->functions[i].fun = lib->functions[j]->fun;
linked = true;
break;
}
}
if (!linked)
WARNING("Unimplemented library function: %s", ainlib->functions[i].name);
else if (ainlib->functions[i].nr_arguments >= HLL_MAX_ARGS)
ERROR("Too many arguments to library function: %s", ainlib->functions[i].name);
}
}
static void link_libraries(void)
{
for (int i = 0; i < ain->nr_libraries; i++) {
bool linked = false;
for (int j = 0; libraries[j]; j++) {
if (!strcmp(ain->libraries[i].name, libraries[j]->name)) {
link_library(&ain->libraries[i], libraries[j]);
linked = true;
break;
}
}
if (!linked)
WARNING("Unimplemented library: %s", ain->libraries[i].name);
}
}
void vm_execute_ain(struct ain *program)
{
// initialize VM state
stack_size = INITIAL_STACK_SIZE;
stack = xmalloc(INITIAL_STACK_SIZE * sizeof(union vm_value));
stack_ptr = 0;
heap_size = INITIAL_HEAP_SIZE;
heap = xmalloc(INITIAL_HEAP_SIZE * sizeof(struct vm_pointer));
heap_free_stack = xmalloc(INITIAL_HEAP_SIZE * sizeof(int32_t));
for (size_t i = 0; i < INITIAL_HEAP_SIZE; i++) {
heap_free_stack[i] = i;
}
heap_free_ptr = 1; // global page at index 0
ain = program;
link_libraries();
// Initialize globals
heap[0].ref = 1;
heap_set_page(0, alloc_page(GLOBAL_PAGE, 0, ain->nr_globals));
for (int i = 0; i < ain->nr_globals; i++) {
if (ain->globals[i].data_type == AIN_STRUCT) {
// XXX: need to allocate storage for global structs BEFORE calling
// constructors.
heap[0].page->values[i].i = alloc_struct(ain->globals[i].struct_type);
} else {
heap[0].page->values[i] = variable_initval(ain->globals[i].data_type);
}
}
for (int i = 0; i < ain->nr_initvals; i++) {
int32_t index;
struct ain_initval *v = &ain->global_initvals[i];
switch (v->data_type) {
case AIN_STRING:
index = heap_alloc_slot(VM_STRING);
heap[0].page->values[v->global_index].i = index;
heap[index].s = make_string(v->string_value, strlen(v->string_value));
break;
default:
heap[0].page->values[v->global_index].i = v->int_value;
break;
}
}
vm_call(ain->alloc, -1); // function "0": allocate global arrays
// XXX: global constructors must be called AFTER initializing non-struct variables
// otherwise a global set in a constructor will be clobbered by its initval
for (int i = 0; i < ain->nr_globals; i++) {
if (ain->globals[i].data_type == AIN_STRUCT)
init_struct(ain->globals[i].struct_type, heap[0].page->values[i].i);
}
vm_call(ain->main, -1);
}
void vm_stack_trace(void)
{
for (int i = call_stack_ptr - 1; i >= 0; i--) {
struct ain_function *f = &ain->functions[call_stack[i].fno];
char *u = sjis2utf(f->name, strlen(f->name));
sys_warning("\t%s\n", u);
free(u);
}
}
noreturn void _vm_error(const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
sys_vwarning(fmt, ap);
va_end(ap);
sys_warning("at %s (0x%X) in:\n", current_instruction_name(), instr_ptr);
vm_stack_trace();
#ifdef DEBUGGER_ENABLED
dbg_repl();
#endif
sys_exit(1);
}
int vm_time(void)
{
return clock() / (CLOCKS_PER_SEC / 1000);
}
#ifdef DEBUG_HEAP
static void describe_page(struct page *page)
{
if (!page) {
sys_message("NULL_PAGE\n");
return;
}
switch (page->type) {
case GLOBAL_PAGE:
sys_message("GLOBAL_PAGE\n");
break;
case LOCAL_PAGE:
sys_message("LOCAL_PAGE: %s\n", ain->functions[page->index].name);
break;
case STRUCT_PAGE:
sys_message("STRUCT_PAGE: %s\n", ain->structures[page->index].name);
break;
case ARRAY_PAGE:
sys_message("ARRAY_PAGE: %s\n", ain_strtype(ain, page->a_type, page->struct_type));
break;
}
}
static void describe_slot(size_t slot)
{
sys_message("[%d](%d)(%08X)(%08X) = ", slot, heap[slot].ref, heap[slot].alloc_addr, heap[slot].ref_addr);
switch (heap[slot].type) {
case VM_PAGE:
describe_page(heap[slot].page);
break;
case VM_STRING:
if (heap[slot].s) {
char *u = sjis2utf(heap[slot].s->text, heap[slot].s->size);
sys_message("STRING: %s\n", u);
free(u);
} else {
sys_message("STRING: NULL\n");
}
break;
default:
sys_message("???\n");
break;
}
}
#endif
noreturn void vm_exit(int code)
{
// flush call stack
for (int i = call_stack_ptr - 1; i >= 0; i--) {
heap_unref(call_stack[i].page_slot);
}
// free globals
heap_unref(0);
#ifdef DEBUG_HEAP
for (size_t i = 0; i < heap_size; i++) {
if (heap[i].ref > 0)
describe_slot(i);
}
sys_message("Number of leaked objects: %d\n", heap_free_ptr);
#endif
sys_exit(code);
}