Files
nunuhara_xsystem4/vm.c
T
Nunuhara Cabbage b3a660f8ec strings: copy-on-write
Only copy strings when they are mutated. This avoids copying strings in
cases where neither the copy nor the original would be modified during
the lifetime of the copy, e.g. when passing strings by-value.

This is purely an optimization.
2019-10-20 21:26:51 -07:00

923 lines
21 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 "system4.h"
#include "vm.h"
#include "vm_string.h"
#include "page.h"
#include "ain.h"
#include "instructions.h"
#include "little_endian.h"
#include "utfsjis.h"
#define INITIAL_STACK_SIZE 1024
#define INITIAL_HEAP_SIZE 4096
#define INITIAL_PAGES_SIZE 4096
// When the IP is set to VM_RETURN, the VM halts
#define VM_RETURN 0xFFFFFFFF
#define CURRENT_INSTRUCTION (instructions[get_opcode(instr_ptr)])
#define EXECUTION_ERROR(msg, ...) \
ERROR("%s (0x%X): " msg, CURRENT_INSTRUCTION.name, instr_ptr, ##__VA_ARGS__)
/*
* 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 page_ptr;
};
// The stack
static union vm_value *stack = NULL; // the stack
static 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 int32_t heap_free_ptr = 0;
// Memory for global page + local pages
static union vm_value *page_stack;
static int32_t page_ptr = 0; // points to start of current local page
static int32_t pages_size;
// Stack of function call frames
static struct function_call call_stack[4096];
static int32_t call_stack_ptr = 1; // 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;
}
int32_t heap_alloc_slot(enum vm_pointer_type type)
{
int32_t slot = heap_free_stack[heap_free_ptr++];
heap[slot].ref = 1;
heap[slot].type = type;
return slot;
}
static void heap_free_slot(int32_t slot)
{
heap_free_stack[--heap_free_ptr] = slot;
}
void heap_ref(int32_t slot)
{
heap[slot].ref++;
}
void heap_unref(int slot)
{
if (heap[slot].ref <= 0) {
EXECUTION_ERROR("double free (slot %d)", slot);
}
if (--heap[slot].ref <= 0) {
switch (heap[slot].type) {
case VM_FRAME:
break;
case VM_PAGE:
free(heap[slot].page);
break;
case VM_STRING:
free_string(heap[slot].s);
break;
}
heap_free_slot(slot);
}
}
static union vm_value _vm_id(union vm_value v)
{
return v;
}
static union vm_value vm_int(int32_t v)
{
return (union vm_value) { .i = v };
}
static union vm_value vm_long(int64_t v)
{
return (union vm_value) { .i64 = v };
}
static union vm_value vm_float(float v)
{
return (union vm_value) { .f = v };
}
#define vm_value_cast(v) _Generic((v), \
union vm_value: _vm_id, \
int32_t: vm_int, \
int64_t: vm_long, \
float: vm_float)(v)
static int32_t local_get(int varno)
{
return page_stack[page_ptr + varno].i;
}
static void local_set(int varno, int32_t value)
{
page_stack[page_ptr + varno].i = value;
}
static union vm_value *local_ptr(int varno)
{
return &page_stack[page_ptr + varno];
}
static int32_t global_get(int varno)
{
return heap[0].page[varno].i;
}
static int32_t struct_page(void)
{
return page_stack[page_ptr - 1].i;
}
static union vm_value stack_peek(int n)
{
return stack[stack_ptr - (1 + n)];
}
// Set the Nth value from the top of the stack to V.
#define stack_set(n, v) (stack[stack_ptr - (1 + (n))] = vm_value_cast(v))
#define stack_push(v) (stack[stack_ptr++] = vm_value_cast(v))
static union vm_value stack_pop(void)
{
stack_ptr--;
return stack[stack_ptr];
}
// 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;
return &heap[heap_index].page[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;
}
/*
* 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 call(int fno, int new_pp, int return_address)
{
struct ain_function *f = &ain->functions[fno];
int page_slot = heap_alloc_slot(VM_FRAME);
call_stack[call_stack_ptr++] = (struct function_call) {
.fno = fno,
.return_address = return_address,
.page_slot = page_slot,
.page_ptr = page_ptr
};
// create local page
heap[page_slot].page = page_stack + new_pp;
// pop arguments, store in local page
for (int i = f->nr_args - 1; i >= 0; i--) {
page_stack[new_pp + i] = stack_pop();
}
// initialize local variables
for (int i = f->nr_args; i < f->nr_vars; i++) {
int slot;
switch (f->vars[i].data_type) {
case AIN_STRING:
slot = heap_alloc_slot(VM_STRING);
heap[slot].s = NULL;
page_stack[new_pp + i].i = slot;
break;
case AIN_STRUCT:
page_stack[new_pp + i].i = -1;
break;
default:
page_stack[new_pp + i].i = 0;
break;
}
}
// update stack/instruction pointers
page_ptr = new_pp;
instr_ptr = ain->functions[fno].address;
}
static void function_call(int fno, int return_address)
{
int cur_fno = call_stack[call_stack_ptr-1].fno;
int new_pp = page_ptr + ain->functions[cur_fno].nr_vars;
call(fno, new_pp, return_address);
}
static void method_call(int fno, int return_address)
{
int cur_fno = call_stack[call_stack_ptr-1].fno;
int new_pp = page_ptr + ain->functions[cur_fno].nr_vars + 1;
call(fno, new_pp, return_address);
page_stack[new_pp-1] = stack_pop(); // struct page
}
static void vm_execute(void);
static 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 function_return(void)
{
call_stack_ptr--;
// unref slots for heap-backed variables
struct ain_function *f = &ain->functions[call_stack[call_stack_ptr].fno];
delete_page(page_stack + page_ptr, f->vars, f->nr_vars);
heap_free_slot(call_stack[call_stack_ptr].page_slot);
instr_ptr = call_stack[call_stack_ptr].return_address;
page_ptr = call_stack[call_stack_ptr].page_ptr;
}
static void system_call(int32_t code)
{
char *utf;
struct string *str;
switch (code) {
case 0x0: // system.Exit(int nResult)
sys_exit(stack_pop().i);
break;
case 0x6: // 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 0x14: // system.Peek()
break;
case 0x15: // system.Sleep(int nSleep)
stack_pop();
break;
default:
WARNING("Unimplemented syscall: 0x%X", code);
}
}
static int struct_copy(int no, int src_slot)
{
int dst_slot = heap_alloc_slot(VM_PAGE);
struct ain_struct *s = &ain->structures[no];
heap[dst_slot].page = copy_page(heap[src_slot].page, s->members, s->nr_members);
return dst_slot;
}
static struct string EMPTY_STRING = {
.cow = true,
.ref = 1,
.size = 0,
.text = ""
};
static void create_struct(int no, union vm_value *var)
{
struct ain_struct *s = &ain->structures[no];
int slot = alloc_page(s->nr_members);
for (int i = 0; i < s->nr_members; i++) {
int memb;
switch (s->members[i].data_type) {
case AIN_STRING:
memb = heap_alloc_slot(VM_STRING);
heap[memb].s = string_ref(&EMPTY_STRING);
heap[slot].page[i].i = memb;
break;
case AIN_STRUCT:
create_struct(s->members[i].struct_type, &heap[slot].page[i]);
break;
default:
heap[slot].page[i].i = 0;
break;
}
}
if (s->constructor > 0) {
vm_call(s->constructor, slot);
}
var->i = slot;
}
static void execute_instruction(int16_t opcode)
{
int32_t index, a, b, c, v;
float f;
struct string *s;
union vm_value val;
union vm_value *ref;
const char *opcode_name = "UNKNOWN";
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.
index = stack_pop_var()[0].i;
stack_push(index);
break;
case REFREF:
//case S_REFREF: // ???
// Dereference a reference to a reference.
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
a = stack_peek(1).i;
b = stack_peek(0).i;
stack_push(a);
stack_push(b);
break;
case DUP_X2:
// ABC -> CABC
a = stack_peek(2).i;
b = stack_peek(1).i;
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
a = stack_peek(2).i;
b = stack_peek(1).i;
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 PUSHGLOBALPAGE:
stack_push(0);
break;
case PUSHLOCALPAGE:
stack_push(call_stack[call_stack_ptr-1].page_slot);
break;
case PUSHSTRUCTPAGE:
stack_push(struct_page());
break;
case ASSIGN:
case F_ASSIGN:
val = stack_pop();
stack_pop_var()[0] = val;
stack_push(val);
break;
case SH_GLOBALREF: // VARNO
stack_push(global_get(get_argument(0)));
break;
case SH_LOCALREF: // VARNO
stack_push(local_get(get_argument(0)));
break;
case SH_STRUCTREF: // VARNO
stack_push(heap[struct_page()].page[get_argument(0)]);
break;
case SH_LOCALASSIGN: // VARNO, VALUE
local_set(get_argument(0), get_argument(1));
break;
case SH_LOCALINC: // VARNO
index = get_argument(0);
local_set(index, local_get(index)+1);
break;
case SH_LOCALDEC: // VARNO
index = get_argument(0);
local_set(index, local_get(index)-1);
break;
case SH_LOCALDELETE:
if ((index = local_get(get_argument(0))) != -1) {
heap_unref(index);
local_set(get_argument(0), -1);
}
break;
case SH_LOCALCREATE: // VARNO, STRUCTNO
create_struct(get_argument(1), local_ptr(get_argument(0)));
break;
//
// --- Control Flow ---
//
case CALLFUNC:
function_call(get_argument(0), instr_ptr + instruction_width(CALLFUNC));
break;
case CALLMETHOD:
method_call(get_argument(0), instr_ptr + instruction_width(CALLMETHOD));
break;
case RETURN:
function_return();
break;
case CALLSYS:
system_call(get_argument(0));
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;
//
// --- 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:
b = stack_pop().i;
a = stack_pop().i;
stack_push(a < b ? 1 : 0);
break;
case GT:
b = stack_pop().i;
a = stack_pop().i;
stack_push(a > b ? 1 : 0);
break;
case LTE:
b = stack_pop().i;
a = stack_pop().i;
stack_push(a <= b ? 1 : 0);
break;
case GTE:
b = stack_pop().i;
a = stack_pop().i;
stack_push(a >= b ? 1 : 0);
break;
case NOTE:
b = stack_pop().i;
a = stack_pop().i;
stack_push(a != b ? 1 : 0);
break;
case EQUALE:
b = stack_pop().i;
a = stack_pop().i;
stack_push(a == b ? 1 : 0);
break;
// +=, -=, etc.
case PLUSA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i += v);
break;
case MINUSA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i -= v);
break;
case MULA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i *= v);
break;
case DIVA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i /= v);
break;
case MODA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i %= v);
break;
case ANDA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i &= v);
break;
case ORA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i |= v);
break;
case XORA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i ^= v);
break;
case LSHIFTA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i <<= v);
break;
case RSHIFTA:
v = stack_pop().i;
stack_push(stack_pop_var()[0].i >>= v);
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;
//
// --- 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:
f = stack_pop().f;
stack_set(0, stack_peek(0).f + f);
break;
case F_SUB:
f = stack_pop().f;
stack_set(0, stack_peek(0).f - f);
break;
case F_MUL:
f = stack_pop().f;
stack_set(0, stack_peek(0).f * f);
break;
case F_DIV:
f = stack_pop().f;
stack_set(0, stack_peek(0).f / f);
break;
// floating point comparison
case F_LT:
f = stack_pop().f;
stack_set(0, stack_peek(0).f < f ? 1 : 0);
break;
case F_GT:
f = stack_pop().f;
stack_set(0, stack_peek(0).f > f ? 1 : 0);
break;
case F_LTE:
f = stack_pop().f;
stack_set(0, stack_peek(0).f <= f ? 1 : 0);
break;
case F_GTE:
f = stack_pop().f;
stack_set(0, stack_peek(0).f >= f ? 1 : 0);
break;
case F_NOTE:
f = stack_pop().f;
stack_set(0, stack_peek(0).f != f ? 1 : 0);
break;
case F_EQUALE:
f = stack_pop().f;
stack_set(0, stack_peek(0).f == f ? 1 : 0);
break;
//
// --- Strings ---
//
case S_PUSH:
stack_push_string(string_ref(ain->strings[get_argument(0)]));
break;
case S_POP:
index = stack_pop().i;
heap_unref(index);
break;
case S_REF:
// Dereference a reference to a string
index = stack_pop_var()->i;
stack_push_string(string_ref(heap[index].s));
break;
case S_ASSIGN: // A = B
b = stack_peek(0).i;
a = stack_peek(1).i;
if (heap[a].s) {
free_string(heap[a].s);
}
heap[a].s = string_ref(heap[b].s);
// remove A from the stack, but leave B
stack_set(1, b);
stack_pop();
break;
case S_PLUSA2:
a = stack_peek(1).i;
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:
b = stack_pop().i;
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:
v = strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text) < 0;
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(v);
break;
case S_GT:
v = strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text) > 0;
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(v);
break;
case S_LTE:
v = strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text) <= 0;
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(v);
break;
case S_GTE:
v = strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text) >= 0;
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(v);
break;
case S_NOTE:
v = !!strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text);
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(v);
break;
case S_EQUALE:
v = !strcmp(stack_peek_string(1)->text, stack_peek_string(0)->text);
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(v);
break;
case S_LENGTH:
a = stack_pop_var()->i;
stack_push(sjis_count_char(heap[a].s->text));
break;
case S_LENGTHBYTE:
a = stack_pop_var()->i;
stack_push(heap[a].s->size);
break;
case S_EMPTY:
v = !stack_peek_string(0)->size;
heap_unref(stack_pop().i);
stack_push(v);
break;
case S_FIND:
v = string_find(stack_peek_string(1), stack_peek_string(0));
heap_unref(stack_pop().i);
heap_unref(stack_pop().i);
stack_push(v);
break;
case S_GETPART:
b = stack_pop().i; // length
a = stack_pop().i; // index
s = string_copy(stack_peek_string(0), a, b);
heap_unref(stack_pop().i);
stack_push_string(s);
break;
//case S_PUSHBACK: // ???
case S_PUSHBACK2:
v = stack_pop().i;
string_push_back(&heap[stack_pop().i].s, v);
break;
//case S_POPBACK: // ???
case S_POPBACK2:
index = stack_pop().i;
string_pop_back(&heap[index].s);
break;
//case S_ERASE: // ???
case S_ERASE2:
b = stack_pop().i; // ???
a = stack_pop().i; // index
index = stack_pop().i;
string_erase(&heap[index].s, a);
break;
case I_STRING:
stack_push_string(integer_to_string(stack_pop().i));
break;
case FTOS:
v = stack_pop().i; // precision
stack_push_string(float_to_string(stack_pop().f, v));
break;
//
// --- Structs/Classes ---
//
case SR_REF:
stack_push(struct_copy(get_argument(0), stack_pop_var()->i));
break;
// -- NOOPs ---
case FUNC:
break;
default:
if (opcode >= 0 && opcode < NR_OPCODES && instructions[opcode].name) {
opcode_name = instructions[opcode].name;
}
WARNING("Unimplemented instruction: 0x%X(%s)", opcode, opcode_name);
}
}
static void vm_execute(void)
{
for (;;) {
uint16_t opcode;
if (instr_ptr == VM_RETURN)
return;
if (instr_ptr >= ain->code_size) {
EXECUTION_ERROR("Illegal instruction pointer: 0x%08lX", instr_ptr);
}
opcode = get_opcode(instr_ptr);
if (opcode >= NR_OPCODES) {
EXECUTION_ERROR("Illegal opcode: 0x%04X", opcode);
}
execute_instruction(opcode);
instr_ptr += instructions[opcode].ip_inc;
}
}
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
pages_size = INITIAL_PAGES_SIZE;
page_stack = xmalloc(INITIAL_PAGES_SIZE * sizeof(union vm_value));
page_ptr = 0;
ain = program;
// Initialize globals
heap[0].page = xmalloc(sizeof(union vm_value) * ain->nr_globals);
for (int i = 0; i < ain->nr_globals; i++) {
switch (ain->globals[i].data_type) {
case AIN_STRING:
heap[0].page[i].i = heap_alloc_slot(VM_STRING);
break;
default:
break;
}
}
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[v->global_index].i = index;
heap[index].s = make_string(v->string_value, strlen(v->string_value));
break;
default:
heap[0].page[v->global_index].i = v->int_value;
break;
}
}
vm_call(ain->main, -1);
}