mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-29 10:27:58 +03:00
After this, xsystem4 uses the global/group save format used by the original System4, instead of the JSON format. Existing JSON save files can still be loaded. This makes save files interoperable between System4 and xsystem4 in some games, including Sengoku Rance and Toushin Toshi III.
2390 lines
59 KiB
C
2390 lines
59 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/>.
|
|
*/
|
|
|
|
#define VM_PRIVATE
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <setjmp.h>
|
|
#include <assert.h>
|
|
#include <SDL.h> // for system.MsgBox
|
|
|
|
#include "system4.h"
|
|
#include "system4/ain.h"
|
|
#include "system4/instructions.h"
|
|
#include "system4/file.h"
|
|
#include "system4/string.h"
|
|
#include "system4/utfsjis.h"
|
|
|
|
#include "debugger.h"
|
|
#include "little_endian.h"
|
|
#include "input.h"
|
|
#include "savedata.h"
|
|
#include "vm.h"
|
|
#include "vm/heap.h"
|
|
#include "vm/page.h"
|
|
#include "xsystem4.h"
|
|
|
|
static inline int32_t lint_clamp(int64_t n)
|
|
{
|
|
if (n < 0)
|
|
return 0;
|
|
if (n > INT32_MAX)
|
|
return INT32_MAX;
|
|
return (int32_t)n;
|
|
}
|
|
|
|
#define INITIAL_STACK_SIZE 4096
|
|
|
|
// 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.
|
|
*/
|
|
|
|
// 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
|
|
|
|
// Stack of function call frames
|
|
struct function_call call_stack[4096];
|
|
int32_t call_stack_ptr = 0;
|
|
|
|
struct ain *ain;
|
|
size_t instr_ptr = 0;
|
|
|
|
// Read the opcode at ADDR.
|
|
static int16_t get_opcode(size_t addr)
|
|
{
|
|
return LittleEndian_getW(ain->code, addr);
|
|
}
|
|
|
|
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";
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
struct page *get_local_page(int frame_no)
|
|
{
|
|
if (frame_no < 0 || frame_no >= call_stack_ptr)
|
|
return NULL;
|
|
int slot = call_stack[call_stack_ptr - (frame_no + 1)].page_slot;
|
|
return slot < 1 ? NULL : heap[slot].page;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
struct page *global_page(void)
|
|
{
|
|
return heap[0].page;
|
|
}
|
|
|
|
union vm_value global_get(int varno)
|
|
{
|
|
return heap[0].page->values[varno];
|
|
}
|
|
|
|
void global_set(int varno, union vm_value val, bool call_dtors)
|
|
{
|
|
switch (ain->globals[varno].type.data) {
|
|
case AIN_STRING:
|
|
case AIN_STRUCT:
|
|
case AIN_ARRAY_TYPE:
|
|
if (heap[0].page->values[varno].i > 0) {
|
|
if (call_dtors)
|
|
heap_unref(heap[0].page->values[varno].i);
|
|
else
|
|
exit_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 struct page *struct_page(void)
|
|
{
|
|
return heap[struct_page_slot()].page;
|
|
}
|
|
|
|
struct page *get_struct_page(int frame_no)
|
|
{
|
|
if (frame_no < 0 || frame_no >= call_stack_ptr)
|
|
return NULL;
|
|
int slot = call_stack[call_stack_ptr - (frame_no + 1)].struct_page;
|
|
return slot < 1 ? NULL : heap[slot].page;
|
|
}
|
|
|
|
union vm_value member_get(int varno)
|
|
{
|
|
return struct_page()->values[varno];
|
|
}
|
|
|
|
static void member_set(int varno, int32_t value)
|
|
{
|
|
struct_page()->values[varno].i = value;
|
|
}
|
|
|
|
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 (unlikely(!heap_index_valid(heap_index)))
|
|
VM_ERROR("Out of bounds heap index: %d/%d", heap_index, page_index);
|
|
if (unlikely(!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];
|
|
}
|
|
|
|
union vm_value *stack_peek_var(void)
|
|
{
|
|
int32_t page_index = stack_peek(0).i;
|
|
int32_t heap_index = stack_peek(1).i;
|
|
if (unlikely(!heap_index_valid(heap_index)))
|
|
VM_ERROR("Out of bounds heap index: %d/%d", heap_index, page_index);
|
|
if (unlikely(!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_get_string(v.i)) };
|
|
case AIN_STRUCT:
|
|
case AIN_DELEGATE:
|
|
case AIN_ARRAY_TYPE:
|
|
return (union vm_value) { .i = vm_copy_page(heap_get_page(v.i)) };
|
|
case AIN_REF_TYPE:
|
|
heap_ref(v.i);
|
|
return v;
|
|
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", display_sjis0(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].type.data);
|
|
}
|
|
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,
|
|
.call_address = instr_ptr,
|
|
.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 int _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,
|
|
.call_address = instr_ptr,
|
|
.return_address = return_address,
|
|
.page_slot = slot,
|
|
.struct_page = -1
|
|
};
|
|
// initialize local variables
|
|
for (int i = f->nr_args; i < f->nr_vars; i++) {
|
|
heap[slot].page->values[i] = variable_initval(f->vars[i].type.data);
|
|
}
|
|
// jump to function start
|
|
instr_ptr = ain->functions[fno].address;
|
|
|
|
return slot;
|
|
}
|
|
|
|
static void function_call(int fno, int return_address)
|
|
{
|
|
int slot = _function_call(fno, return_address);
|
|
|
|
// pop arguments, store in local page
|
|
struct ain_function *f = &ain->functions[fno];
|
|
for (int i = f->nr_args - 1; i >= 0; i--) {
|
|
heap[slot].page->values[i] = stack_pop();
|
|
switch (f->vars[i].type.data) {
|
|
case AIN_REF_TYPE:
|
|
heap_ref(heap[slot].page->values[i].i);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
static void delegate_call(int dg_no)
|
|
{
|
|
size_t saved_ip = instr_ptr;
|
|
|
|
// stack: [arg0, ..., dg_page, dg_index]
|
|
int dg_page = stack_peek(1).i;
|
|
int dg_index = stack_peek(0).i;
|
|
|
|
int obj, fun;
|
|
delegate_get(heap_get_delegate_page(dg_page), dg_index, &obj, &fun);
|
|
|
|
int slot = _function_call(fun, VM_RETURN);
|
|
|
|
// copy arguments into local page
|
|
struct ain_function_type *dg = &ain->delegates[dg_no];
|
|
for (int i = 0; i < dg->nr_arguments; i++) {
|
|
union vm_value arg = stack_peek((dg->nr_arguments + 1) - i);
|
|
heap[slot].page->values[i] = vm_copy(arg, dg->variables[i].type.data);
|
|
}
|
|
|
|
call_stack[call_stack_ptr-1].struct_page = obj;
|
|
vm_execute();
|
|
instr_ptr = saved_ip;
|
|
}
|
|
|
|
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)
|
|
{
|
|
heap_unref(call_stack[call_stack_ptr-1].page_slot);
|
|
instr_ptr = call_stack[call_stack_ptr-1].return_address;
|
|
call_stack_ptr--;
|
|
}
|
|
|
|
static const SDL_MessageBoxButtonData buttons[] = {
|
|
{ SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, 1, "OK" },
|
|
{ SDL_MESSAGEBOX_BUTTON_ESCAPEKEY_DEFAULT, 0, "Cancel" },
|
|
};
|
|
|
|
static _Noreturn void vm_reset(void);
|
|
|
|
static struct string *get_func_stack_name(int index)
|
|
{
|
|
int i = call_stack_ptr - (1 + index);
|
|
if (i < 0 || i >= call_stack_ptr) {
|
|
return string_ref(&EMPTY_STRING);
|
|
}
|
|
struct function_call *call = &call_stack[i];
|
|
struct ain_function *fun = &ain->functions[call->fno];
|
|
return cstr_to_string(fun->name);
|
|
}
|
|
|
|
static void system_call(enum syscall_code code)
|
|
{
|
|
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_get_string(keyname)->text, heap_get_string(filename)->text, NULL, NULL));
|
|
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_get_string(keyname)->text, heap_get_string(filename)->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_RESET: {
|
|
vm_reset();
|
|
break;
|
|
}
|
|
case SYS_OUTPUT: {// system.Output(string szText)
|
|
struct string *str = stack_peek_string(0);
|
|
sys_message("%s", display_sjis0(str->text));
|
|
// XXX: caller S_POPs
|
|
break;
|
|
}
|
|
case SYS_MSGBOX: {
|
|
struct string *str = stack_peek_string(0);
|
|
SDL_ShowSimpleMessageBox(0, "xsystem4", display_sjis0(str->text), NULL);
|
|
// XXX: caller S_POPs
|
|
break;
|
|
}
|
|
case SYS_MSGBOX_OK_CANCEL: {
|
|
int result = 0;
|
|
struct string *str = stack_peek_string(0);
|
|
|
|
const SDL_MessageBoxData mbox = {
|
|
SDL_MESSAGEBOX_INFORMATION,
|
|
NULL,
|
|
"xsystem4",
|
|
display_sjis0(str->text),
|
|
SDL_arraysize(buttons),
|
|
buttons,
|
|
NULL
|
|
};
|
|
if (SDL_ShowMessageBox(&mbox, &result)) {
|
|
WARNING("Error displaying message box");
|
|
}
|
|
heap_unref(stack_pop().i);
|
|
stack_push(result);
|
|
break;
|
|
}
|
|
case SYS_RESUME_SAVE: {
|
|
union vm_value *success = stack_pop_var();
|
|
struct string *filename = stack_peek_string(0);
|
|
struct string *keyname = stack_peek_string(1);
|
|
success->i = vm_save_image(keyname->text, filename->text);
|
|
heap_unref(stack_pop().i);
|
|
heap_unref(stack_pop().i);
|
|
stack_push(1);
|
|
break;
|
|
}
|
|
case SYS_RESUME_LOAD: {
|
|
int filename_slot = stack_pop().i;
|
|
int key_slot = stack_pop().i;
|
|
vm_load_image(heap_get_string(key_slot)->text, heap_get_string(filename_slot)->text);
|
|
//heap_unref(stack_pop().i);
|
|
//heap_unref(stack_pop().i);
|
|
stack_pop();
|
|
stack_pop();
|
|
stack_push(0);
|
|
break;
|
|
}
|
|
case SYS_EXISTS_FILE: { // system.ExistsFile(string szFileName)
|
|
int str = stack_pop().i;
|
|
char *path = gamedir_path(heap_get_string(str)->text);
|
|
stack_push(file_exists(path));
|
|
heap_unref(str);
|
|
free(path);
|
|
break;
|
|
}
|
|
case SYS_OPEN_WEB: {
|
|
#if !SDL_VERSION_ATLEAST(2, 0, 14)
|
|
WARNING("SDL_OpenURL not available");
|
|
#else
|
|
struct string *url = stack_peek_string(0);
|
|
if (SDL_OpenURL(url->text) < 0) {
|
|
WARNING("SDL_OpenURL failed: %s", SDL_GetError());
|
|
}
|
|
#endif
|
|
heap_unref(stack_pop().i);
|
|
break;
|
|
};
|
|
case SYS_GET_SAVE_FOLDER_NAME: {// system.GetSaveFolderName(void)
|
|
if (config.save_dir) {
|
|
char *sjis = utf2sjis(config.save_dir, strlen(config.save_dir));
|
|
stack_push_string(make_string(sjis, strlen(sjis)));
|
|
free(sjis);
|
|
} else {
|
|
stack_push_string(string_ref(&EMPTY_STRING));
|
|
}
|
|
break;
|
|
}
|
|
case SYS_GET_TIME: {// system.GetTime(void)
|
|
stack_push(vm_time());
|
|
break;
|
|
}
|
|
case SYS_GET_GAME_NAME: {// system.GetGameName(void)
|
|
stack_push_string(make_string(config.game_name, strlen(config.game_name)));
|
|
break;
|
|
}
|
|
case SYS_ERROR: {// system.Error(string szText)
|
|
struct string *str = stack_peek_string(0);
|
|
sys_warning("*GAME ERROR*: %s\n", display_sjis0(str->text));
|
|
// XXX: caller S_POPs
|
|
break;
|
|
}
|
|
case SYS_EXISTS_SAVE_FILE: {
|
|
int slot = stack_pop().i;
|
|
char *path = savedir_path(heap_get_string(slot)->text);
|
|
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)
|
|
stack_push_string(get_func_stack_name(stack_pop().i));
|
|
break;
|
|
}
|
|
case SYS_PEEK: {// system.Peek(void)
|
|
handle_events();
|
|
break;
|
|
}
|
|
case SYS_SLEEP: {// system.Sleep(int nSleep)
|
|
int ms = stack_pop().i;
|
|
vm_sleep(ms);
|
|
break;
|
|
}
|
|
case SYS_RESUME_READ_COMMENT: {// system.ResumeReadComment(string szKeyName, string szFileName, ref array@string aszComment)
|
|
int success;
|
|
int comment = stack_pop().i;
|
|
int filename = stack_pop().i;
|
|
int keyname = stack_pop().i;
|
|
// FIXME: free ref'd array if allocated
|
|
heap_set_page(comment, vm_load_image_comments(heap_get_string(keyname)->text,
|
|
heap_get_string(filename)->text,
|
|
&success));
|
|
heap_unref(filename);
|
|
heap_unref(keyname);
|
|
stack_push(success);
|
|
break;
|
|
}
|
|
case SYS_RESUME_WRITE_COMMENT: { // system.ResumeWriteComment(string szKeyName, string szFileName, ref array@string aszComment)
|
|
int comment = stack_pop().i;
|
|
int filename = stack_pop().i;
|
|
int keyname = stack_pop().i;
|
|
stack_push(vm_write_image_comments(heap_get_string(keyname)->text,
|
|
heap_get_string(filename)->text,
|
|
heap_get_page(comment)));
|
|
heap_unref(filename);
|
|
heap_unref(keyname);
|
|
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_globals(heap_get_string(keyname)->text,
|
|
heap_get_string(filename)->text,
|
|
heap_get_string(groupname)->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_get_string(keyname)->text,
|
|
heap_get_string(filename)->text,
|
|
heap_get_string(groupname)->text,
|
|
&n->i));
|
|
heap_unref(groupname);
|
|
heap_unref(filename);
|
|
heap_unref(keyname);
|
|
break;
|
|
}
|
|
case SYS_DELETE_SAVE_FILE: { // system.DeleteSaveFile(string szFileName)
|
|
int filename = stack_pop().i;
|
|
stack_push(delete_save_file(heap_get_string(filename)->text));
|
|
heap_unref(filename);
|
|
break;
|
|
}
|
|
case SYS_EXIST_FUNC: { // system.ExistFunc(string szFuncName)
|
|
int funcname = stack_pop().i;
|
|
stack_push(ain_get_function(ain, heap_get_string(funcname)->text) > 0);
|
|
heap_unref(funcname);
|
|
break;
|
|
}
|
|
case SYS_COPY_SAVE_FILE: { // system.CopySaveFile(string szDestFileName, string szSourceFileName)
|
|
int src = stack_pop().i;
|
|
int dst = stack_pop().i;
|
|
char *u_src = savedir_path(heap_get_string(src)->text);
|
|
char *u_dst = savedir_path(heap_get_string(dst)->text);
|
|
stack_push(file_copy(u_src, u_dst));
|
|
free(u_src);
|
|
free(u_dst);
|
|
heap_unref(src);
|
|
heap_unref(dst);
|
|
break;
|
|
}
|
|
default:
|
|
VM_ERROR("Unimplemented syscall: 0x%X", code);
|
|
}
|
|
}
|
|
|
|
uint32_t get_switch_address(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) {
|
|
return s->cases[i].address;
|
|
}
|
|
}
|
|
if (s->default_address > 0)
|
|
return s->default_address;
|
|
else
|
|
return instr_ptr + instruction_width(SWITCH);
|
|
}
|
|
|
|
uint32_t get_strswitch_address(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)) {
|
|
return s->cases[i].address;
|
|
}
|
|
}
|
|
if (s->default_address > 0)
|
|
return s->default_address;
|
|
else
|
|
return instr_ptr + instruction_width(STRSWITCH);
|
|
}
|
|
|
|
static void echo_message(int i)
|
|
{
|
|
NOTICE("MSG %d: %s", i, display_sjis0(ain->messages[i]->text));
|
|
}
|
|
|
|
static enum opcode 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(member_get(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;
|
|
struct page *dst = heap_get_page(stack_pop().i);
|
|
page_set_var(dst, dst_var, src_page);
|
|
page_set_var(dst, dst_var+1, src_var);
|
|
stack_push(src_page);
|
|
stack_push(src_var);
|
|
break;
|
|
}
|
|
case R_EQUALE: {
|
|
int rhs_var = stack_pop().i;
|
|
int rhs_page = stack_pop().i;
|
|
int lhs_var = stack_pop().i;
|
|
int lhs_page = stack_pop().i;
|
|
stack_push(lhs_page == rhs_page && lhs_var == rhs_var ? 1 : 0);
|
|
break;
|
|
}
|
|
case R_NOTE: {
|
|
int rhs_var = stack_pop().i;
|
|
int rhs_page = stack_pop().i;
|
|
int lhs_var = stack_pop().i;
|
|
int lhs_page = stack_pop().i;
|
|
stack_push(lhs_page == rhs_page && lhs_var == rhs_var ? 0 : 1);
|
|
break;
|
|
}
|
|
case NEW: {
|
|
union vm_value v;
|
|
create_struct(stack_pop().i, &v);
|
|
stack_push(v);
|
|
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_get_string(str)->text));
|
|
heap_unref(str);
|
|
break;
|
|
}
|
|
case SJUMP: {
|
|
scenario_call(stack_pop().i);
|
|
break;
|
|
}
|
|
case _MSG: {
|
|
if (config.echo)
|
|
echo_message(get_argument(0));
|
|
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: {
|
|
instr_ptr = get_switch_address(get_argument(0), stack_pop().i);
|
|
break;
|
|
}
|
|
case STRSWITCH: {
|
|
int str = stack_pop().i;
|
|
instr_ptr = get_strswitch_address(get_argument(0), heap_get_string(str));
|
|
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) {
|
|
sys_message("Assertion failed at %s:%d: %s\n",
|
|
display_sjis0(heap_get_string(file)->text),
|
|
line,
|
|
display_sjis0(heap_get_string(expr)->text));
|
|
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: {
|
|
if (!stack[stack_ptr-1].i) {
|
|
stack[stack_ptr-2].i = 0;
|
|
} else {
|
|
stack[stack_ptr-2].i /= stack[stack_ptr-1].i;
|
|
}
|
|
stack_ptr--;
|
|
break;
|
|
}
|
|
case MOD: {
|
|
if (!stack[stack_ptr-1].i) {
|
|
stack[stack_ptr-2].i = 0;
|
|
} else {
|
|
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(n ? stack_pop_var()->i /= n : 0);
|
|
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, lint_clamp(stack_peek(0).i));
|
|
break;
|
|
}
|
|
case LI_ADD: {
|
|
int64_t a = stack[stack_ptr-2].i;
|
|
int64_t b = stack[stack_ptr-1].i;
|
|
stack[stack_ptr-2].i = lint_clamp(a + b);
|
|
stack_ptr--;
|
|
break;
|
|
}
|
|
case LI_SUB: {
|
|
int64_t a = stack[stack_ptr-2].i;
|
|
int64_t b = stack[stack_ptr-1].i;
|
|
stack[stack_ptr-2].i = lint_clamp(a - b);
|
|
stack_ptr--;
|
|
break;
|
|
}
|
|
case LI_MUL: {
|
|
int64_t a = stack[stack_ptr-2].i;
|
|
int64_t b = stack[stack_ptr-1].i;
|
|
stack[stack_ptr-2].i = lint_clamp(a * b);
|
|
stack_ptr--;
|
|
break;
|
|
}
|
|
case LI_DIV: {
|
|
int64_t a = stack[stack_ptr-2].i;
|
|
int64_t b = stack[stack_ptr-1].i;
|
|
stack[stack_ptr-2].i = b ? lint_clamp(a / b) : 0;
|
|
stack_ptr--;
|
|
break;
|
|
}
|
|
case LI_MOD: {
|
|
int64_t a = stack[stack_ptr-2].i;
|
|
int64_t b = stack[stack_ptr-1].i;
|
|
stack[stack_ptr-2].i = lint_clamp(a % b);
|
|
stack_ptr--;
|
|
break;
|
|
}
|
|
case LI_ASSIGN: {
|
|
int64_t v = stack_pop().i;
|
|
stack_push(stack_pop_var()->i = lint_clamp(v));
|
|
break;
|
|
}
|
|
case LI_PLUSA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i + n));
|
|
break;
|
|
}
|
|
case LI_MINUSA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i - n));
|
|
break;
|
|
}
|
|
case LI_MULA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i * n));
|
|
break;
|
|
}
|
|
case LI_DIVA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = (n ? lint_clamp((int64_t)v->i / n) : 0));
|
|
break;
|
|
}
|
|
case LI_MODA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i % n));
|
|
break;
|
|
}
|
|
case LI_ANDA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i & n));
|
|
break;
|
|
}
|
|
case LI_ORA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i | n));
|
|
break;
|
|
}
|
|
case LI_XORA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i ^ n));
|
|
break;
|
|
}
|
|
case LI_LSHIFTA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i << n));
|
|
break;
|
|
}
|
|
case LI_RSHIFTA: {
|
|
int64_t n = stack_pop().i;
|
|
union vm_value *v = stack_pop_var();
|
|
stack_push(v->i = lint_clamp((int64_t)v->i >> n));
|
|
break;
|
|
}
|
|
case LI_INC: {
|
|
union vm_value *v = stack_pop_var();
|
|
v->i = lint_clamp((int64_t)v->i + (int64_t)1);
|
|
break;
|
|
}
|
|
case LI_DEC: {
|
|
union vm_value *v = stack_pop_var();
|
|
v->i = lint_clamp((int64_t)v->i - (int64_t)1);
|
|
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_get_string(str)));
|
|
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;
|
|
heap_string_assign(lval, heap_get_string(rval));
|
|
// 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_get_string(a), heap_get_string(b)));
|
|
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_get_string(str)->text));
|
|
break;
|
|
}
|
|
case S_LENGTH2: {
|
|
int str = stack_pop().i;
|
|
stack_push(sjis_count_char(heap_get_string(str)->text));
|
|
heap_unref(str);
|
|
break;
|
|
}
|
|
case S_LENGTHBYTE: {
|
|
int str = stack_pop_var()->i;
|
|
stack_push(heap_get_string(str)->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: {
|
|
int type = stack_pop().i;
|
|
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, type + 8);
|
|
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;
|
|
int fno = get_function_by_name(heap_get_string(str)->text);
|
|
stack_pop_var()->i = fno > 0 ? fno : 0;
|
|
stack_push(str);
|
|
break;
|
|
}
|
|
// --- Characters ---
|
|
case C_REF: {
|
|
int i = stack_pop().i;
|
|
int str = stack_pop().i;
|
|
stack_push(string_get_char(heap_get_string(str), 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_REF2: {
|
|
stack_push(vm_copy_page(heap[stack_pop().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;
|
|
heap_struct_assign(lval, rval);
|
|
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);
|
|
if (heap[array].page) {
|
|
delete_page_vars(heap[array].page);
|
|
free_page(heap[array].page);
|
|
}
|
|
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_vars(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);
|
|
heap_set_page(array, array_pushback(heap[array].page, val, data_type, struct_type));
|
|
break;
|
|
}
|
|
case A_POPBACK: {
|
|
int array = stack_pop_var()->i;
|
|
heap_set_page(array, array_popback(heap[array].page));
|
|
break;
|
|
}
|
|
case A_EMPTY: {
|
|
struct page *array = heap_get_page(stack_pop_var()->i);
|
|
stack_push(!array || !array->nr_vars);
|
|
break;
|
|
}
|
|
case A_ERASE: {
|
|
int i = stack_pop().i;
|
|
int array = stack_pop_var()->i;
|
|
bool success = false;
|
|
heap_set_page(array, array_erase(heap[array].page, i, &success));
|
|
stack_push(success);
|
|
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);
|
|
heap_set_page(array, 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;
|
|
struct page *array = heap_get_page(stack_pop_var()->i);
|
|
stack_push(array_find(array, start, end, v, fno));
|
|
// FIXME: string key isn't freed if array is empty
|
|
if (array && array_type(array->a_type) == AIN_STRING) {
|
|
heap_unref(v.i);
|
|
}
|
|
break;
|
|
}
|
|
case A_REVERSE: {
|
|
int array = stack_pop_var()->i;
|
|
array_reverse(heap[array].page);
|
|
break;
|
|
}
|
|
//
|
|
// -- Shorthand Instructions (added in Alice 2010) ---
|
|
//
|
|
case SH_SR_ASSIGN: {
|
|
int rval = stack_pop_var()->i;
|
|
int lval = stack_pop().i;
|
|
heap_struct_assign(lval, rval);
|
|
break;
|
|
}
|
|
case SH_MEM_ASSIGN_LOCAL: {
|
|
member_set(get_argument(0), local_get(get_argument(1)).i);
|
|
break;
|
|
}
|
|
case A_NUMOF_GLOB_1: {
|
|
int array = global_get(get_argument(0)).i;
|
|
stack_push(array_numof(heap_get_page(array), 1));
|
|
break;
|
|
}
|
|
case A_NUMOF_STRUCT_1: {
|
|
int array = member_get(get_argument(0)).i;
|
|
stack_push(array_numof(heap_get_page(array), 1));
|
|
break;
|
|
}
|
|
case SH_MEM_ASSIGN_IMM: {
|
|
member_set(get_argument(0), get_argument(1));
|
|
break;
|
|
}
|
|
case SH_LOCALREFREF: {
|
|
stack_push(local_get(get_argument(0)));
|
|
stack_push(local_get(get_argument(0)+1));
|
|
break;
|
|
}
|
|
case SH_LOCALASSIGN_SUB_IMM: {
|
|
int n = get_argument(0);
|
|
local_set(n, local_get(n).i - get_argument(1));
|
|
break;
|
|
}
|
|
case SH_IF_LOC_LT_IMM: {
|
|
if (local_get(get_argument(0)).i < get_argument(1))
|
|
instr_ptr = get_argument(2);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_LOC_LT_IMM);
|
|
break;
|
|
}
|
|
case SH_IF_LOC_GE_IMM: {
|
|
if (local_get(get_argument(0)).i >= get_argument(1))
|
|
instr_ptr = get_argument(2);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_LOC_GE_IMM);
|
|
break;
|
|
}
|
|
case SH_LOCREF_ASSIGN_MEM: {
|
|
struct page *page = heap_get_page(local_get(get_argument(0)).i);
|
|
int index = local_get(get_argument(0)+1).i;
|
|
page_set_var(page, index, member_get(get_argument(1)));
|
|
break;
|
|
}
|
|
case PAGE_REF: {
|
|
struct page *page = heap_get_page(stack_pop().i);
|
|
stack_push(page_get_var(page, get_argument(0)));
|
|
break;
|
|
}
|
|
case SH_GLOBAL_ASSIGN_LOCAL: {
|
|
global_set(get_argument(0), local_get(get_argument(1)), true);
|
|
break;
|
|
}
|
|
case SH_STRUCTREF_GT_IMM: {
|
|
stack_push(member_get(get_argument(0)).i > get_argument(1) ? 1 : 0);
|
|
break;
|
|
}
|
|
case SH_STRUCT_ASSIGN_LOCALREF_ITOB: {
|
|
member_set(get_argument(0), !!local_get(get_argument(1)).i);
|
|
break;
|
|
}
|
|
case SH_LOCAL_ASSIGN_STRUCTREF: {
|
|
local_set(get_argument(0), member_get(get_argument(1)).i);
|
|
break;
|
|
}
|
|
case SH_IF_STRUCTREF_NE_LOCALREF: {
|
|
if (member_get(get_argument(0)).i != local_get(get_argument(1)).i)
|
|
instr_ptr = get_argument(2);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_STRUCTREF_NE_LOCALREF);
|
|
break;
|
|
}
|
|
case SH_IF_STRUCTREF_GT_IMM: {
|
|
if (member_get(get_argument(0)).i > get_argument(1))
|
|
instr_ptr = get_argument(2);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_STRUCTREF_GT_IMM);
|
|
break;
|
|
}
|
|
case SH_STRUCTREF_CALLMETHOD_NO_PARAM: {
|
|
int memb_page = member_get(get_argument(0)).i;
|
|
function_call(get_argument(1), instr_ptr + instruction_width(SH_STRUCTREF_CALLMETHOD_NO_PARAM));
|
|
call_stack[call_stack_ptr-1].struct_page = memb_page;
|
|
break;
|
|
}
|
|
case SH_STRUCTREF2: {
|
|
int memb = member_get(get_argument(0)).i;
|
|
stack_push(page_get_var(heap_get_page(memb), get_argument(1)));
|
|
break;
|
|
}
|
|
case SH_REF_STRUCTREF2: {
|
|
int page = stack_pop().i;
|
|
int memb = page_get_var(heap_get_page(page), get_argument(0)).i;
|
|
stack_push(page_get_var(heap_get_page(memb), get_argument(1)));
|
|
break;
|
|
}
|
|
case SH_STRUCTREF3: {
|
|
int memb0 = member_get(get_argument(0)).i;
|
|
int memb1 = page_get_var(heap_get_page(memb0), get_argument(1)).i;
|
|
stack_push(page_get_var(heap_get_page(memb1), get_argument(2)));
|
|
break;
|
|
}
|
|
case SH_STRUCTREF2_CALLMETHOD_NO_PARAM: {
|
|
int memb1 = member_get(get_argument(0)).i;
|
|
int memb2 = page_get_var(heap_get_page(memb1), get_argument(1)).i;
|
|
function_call(get_argument(2), instr_ptr + instruction_width(SH_STRUCTREF2_CALLMETHOD_NO_PARAM));
|
|
call_stack[call_stack_ptr-1].struct_page = memb2;
|
|
break;
|
|
}
|
|
case SH_IF_STRUCTREF_Z: {
|
|
if (!member_get(get_argument(0)).i)
|
|
instr_ptr = get_argument(1);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_STRUCTREF_Z);
|
|
break;
|
|
}
|
|
case SH_IF_STRUCT_A_NOT_EMPTY: {
|
|
struct page *array = heap_get_page(member_get(get_argument(0)).i);
|
|
if (array && array->nr_vars)
|
|
instr_ptr = get_argument(1);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_STRUCT_A_NOT_EMPTY);
|
|
break;
|
|
}
|
|
case SH_IF_LOC_GT_IMM: {
|
|
if (local_get(get_argument(0)).i > get_argument(1))
|
|
instr_ptr = get_argument(2);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_LOC_GT_IMM);
|
|
break;
|
|
}
|
|
case SH_IF_STRUCTREF_NE_IMM: {
|
|
if (member_get(get_argument(0)).i != get_argument(1))
|
|
instr_ptr = get_argument(2);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_STRUCTREF_NE_IMM);
|
|
break;
|
|
}
|
|
case THISCALLMETHOD_NOPARAM: {
|
|
int this_page = struct_page_slot();
|
|
function_call(get_argument(0), instr_ptr + instruction_width(THISCALLMETHOD_NOPARAM));
|
|
call_stack[call_stack_ptr-1].struct_page = this_page;
|
|
break;
|
|
}
|
|
case SH_IF_LOC_NE_IMM: {
|
|
if (local_get(get_argument(0)).i != get_argument(1))
|
|
instr_ptr = get_argument(2);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_LOC_NE_IMM);
|
|
break;
|
|
}
|
|
case SH_IF_STRUCTREF_EQ_IMM: {
|
|
if (member_get(get_argument(0)).i == get_argument(1))
|
|
instr_ptr = get_argument(2);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_STRUCTREF_EQ_IMM);
|
|
break;
|
|
}
|
|
case SH_GLOBAL_ASSIGN_IMM: {
|
|
global_set(get_argument(0), (union vm_value) { .i = get_argument(1) }, false);
|
|
break;
|
|
}
|
|
case SH_LOCALSTRUCT_ASSIGN_IMM: {
|
|
struct page *page = heap_get_page(local_get(get_argument(0)).i);
|
|
page_set_var(page, get_argument(1), get_argument(2));
|
|
break;
|
|
}
|
|
case SH_STRUCT_A_PUSHBACK_LOCAL_STRUCT: {
|
|
int struct_type;
|
|
int array = member_get(get_argument(0)).i;
|
|
union vm_value val = vm_copy(local_get(get_argument(1)), AIN_STRUCT);
|
|
enum ain_data_type data_type = variable_type(struct_page(), get_argument(0), &struct_type, NULL);
|
|
heap_set_page(array, array_pushback(heap_get_page(array), val, data_type, struct_type));
|
|
break;
|
|
}
|
|
case SH_GLOBAL_A_PUSHBACK_LOCAL_STRUCT: {
|
|
int struct_type;
|
|
int array = global_get(get_argument(0)).i;
|
|
union vm_value val = vm_copy(local_get(get_argument(1)), AIN_STRUCT);
|
|
enum ain_data_type data_type = variable_type(global_page(), get_argument(0), &struct_type, NULL);
|
|
heap_set_page(array, array_pushback(heap_get_page(array), val, data_type, struct_type));
|
|
break;
|
|
}
|
|
case SH_LOCAL_A_PUSHBACK_LOCAL_STRUCT: {
|
|
int struct_type;
|
|
int array = local_get(get_argument(0)).i;
|
|
union vm_value val = vm_copy(local_get(get_argument(1)), AIN_STRUCT);
|
|
enum ain_data_type data_type = variable_type(local_page(), get_argument(0), &struct_type, NULL);
|
|
heap_set_page(array, array_pushback(heap_get_page(array), val, data_type, struct_type));
|
|
break;
|
|
}
|
|
case SH_IF_SREF_NE_STR0: {
|
|
struct string *a = heap_get_string(stack_pop_var()->i);
|
|
struct string *b = ain->strings[get_argument(0)];
|
|
if (strcmp(a->text, b->text))
|
|
instr_ptr = get_argument(1);
|
|
else
|
|
instr_ptr += instruction_width(SH_IF_SREF_NE_STR0);
|
|
break;
|
|
}
|
|
case SH_S_ASSIGN_REF: {
|
|
int rval = stack_pop_var()->i;
|
|
int lval = stack_pop().i;
|
|
heap_string_assign(lval, heap_get_string(rval));
|
|
break;
|
|
}
|
|
case SH_A_FIND_SREF: {
|
|
union vm_value *v = stack_pop_var();
|
|
int end = stack_pop().i;
|
|
int start = stack_pop().i;
|
|
int array = stack_pop_var()->i;
|
|
stack_push(array_find(heap_get_page(array), start, end, *v, 0));
|
|
break;
|
|
}
|
|
case SH_SREF_EMPTY: {
|
|
stack_push(!heap_get_string(stack_pop_var()->i)->size);
|
|
break;
|
|
}
|
|
case SH_STRUCTSREF_EQ_LOCALSREF: {
|
|
struct string *a = heap_get_string(member_get(get_argument(0)).i);
|
|
struct string *b = heap_get_string(local_get(get_argument(1)).i);
|
|
stack_push(!strcmp(a->text, b->text));
|
|
break;
|
|
}
|
|
case SH_LOCALSREF_EQ_STR0: {
|
|
struct string *a = heap_get_string(local_get(get_argument(0)).i);
|
|
struct string *b = ain->strings[get_argument(1)];
|
|
stack_push(!strcmp(a->text, b->text));
|
|
break;
|
|
}
|
|
case SH_STRUCTSREF_NE_LOCALSREF: {
|
|
struct string *a = heap_get_string(member_get(get_argument(0)).i);
|
|
struct string *b = heap_get_string(local_get(get_argument(1)).i);
|
|
stack_push(!!strcmp(a->text, b->text));
|
|
break;
|
|
}
|
|
case SH_LOCALSREF_NE_STR0: {
|
|
struct string *a = heap_get_string(local_get(get_argument(0)).i);
|
|
struct string *b = ain->strings[get_argument(1)];
|
|
stack_push(!!strcmp(a->text, b->text));
|
|
break;
|
|
}
|
|
case SH_STRUCT_SR_REF: {
|
|
int sr = member_get(get_argument(0)).i;
|
|
stack_push(vm_copy_page(heap_get_page(sr)));
|
|
// NOTE: argument 1 (struct type) not used
|
|
break;
|
|
}
|
|
case SH_STRUCT_S_REF: {
|
|
int str = member_get(get_argument(0)).i;
|
|
stack_push_string(string_ref(heap_get_string(str)));
|
|
break;
|
|
}
|
|
case S_REF2: {
|
|
struct page *page = heap_get_page(stack_pop().i);
|
|
struct string *s = heap_get_string(page_get_var(page, get_argument(0)).i);
|
|
stack_push_string(string_ref(s));
|
|
break;
|
|
}
|
|
case SH_REF_LOCAL_ASSIGN_STRUCTREF2: {
|
|
struct page *memb = heap_get_page(member_get(get_argument(0)).i);
|
|
int page = local_get(get_argument(1)).i;
|
|
int var = local_get(get_argument(1) + 1).i;
|
|
page_set_var(heap_get_page(page), var, page_get_var(memb, get_argument(2)));
|
|
break;
|
|
}
|
|
case SH_GLOBAL_S_REF: {
|
|
int str = global_get(get_argument(0)).i;
|
|
stack_push_string(string_ref(heap_get_string(str)));
|
|
break;
|
|
}
|
|
case SH_LOCAL_S_REF: {
|
|
int str = local_get(get_argument(0)).i;
|
|
stack_push_string(string_ref(heap_get_string(str)));
|
|
break;
|
|
}
|
|
case SH_LOCALREF_SASSIGN_LOCALSREF: {
|
|
int lval = local_get(get_argument(0)).i;
|
|
int rval = local_get(get_argument(1)).i;
|
|
heap_string_assign(lval, heap_get_string(rval));
|
|
break;
|
|
}
|
|
case SH_LOCAL_APUSHBACK_LOCALSREF: {
|
|
int array = local_get(get_argument(0)).i;
|
|
union vm_value val = vm_copy(local_get(get_argument(1)), AIN_STRING);
|
|
heap_set_page(array, array_pushback(heap_get_page(array), val, AIN_ARRAY_STRING, -1));
|
|
break;
|
|
}
|
|
case SH_S_ASSIGN_CALLSYS19: {
|
|
struct string *name = get_func_stack_name(stack_pop().i);
|
|
heap_string_assign(stack_pop().i, name);
|
|
free_string(name);
|
|
break;
|
|
}
|
|
case SH_S_ASSIGN_STR0: {
|
|
int lval = stack_pop().i;
|
|
heap_string_assign(lval, ain->strings[get_argument(0)]);
|
|
break;
|
|
}
|
|
case SH_SASSIGN_LOCALSREF: {
|
|
int lval = stack_pop().i;
|
|
struct string *rval = heap_get_string(local_get(get_argument(0)).i);
|
|
heap_string_assign(lval, rval);
|
|
break;
|
|
}
|
|
case SH_STRUCTREF_SASSIGN_LOCALSREF: {
|
|
int lval = member_get(get_argument(0)).i;
|
|
int rval = local_get(get_argument(1)).i;
|
|
heap_string_assign(lval, heap_get_string(rval));
|
|
break;
|
|
}
|
|
case SH_LOCALSREF_EMPTY: {
|
|
stack_push(!heap_get_string(local_get(get_argument(0)).i)->size);
|
|
break;
|
|
}
|
|
case SH_GLOBAL_APUSHBACK_LOCALSREF: {
|
|
int array = global_get(get_argument(0)).i;
|
|
union vm_value val = vm_copy(local_get(get_argument(1)), AIN_STRING);
|
|
heap_set_page(array, array_pushback(heap_get_page(array), val, AIN_ARRAY_STRING, -1));
|
|
break;
|
|
}
|
|
case SH_STRUCT_APUSHBACK_LOCALSREF: {
|
|
int array = member_get(get_argument(0)).i;
|
|
union vm_value val = vm_copy(local_get(get_argument(1)), AIN_STRING);
|
|
heap_set_page(array, array_pushback(heap_get_page(array), val, AIN_ARRAY_STRING, -1));
|
|
break;
|
|
}
|
|
case SH_STRUCTSREF_EMPTY: {
|
|
stack_push(!heap_get_string(member_get(get_argument(0)).i)->size);
|
|
break;
|
|
}
|
|
case SH_GLOBALSREF_EMPTY: {
|
|
stack_push(!heap_get_string(global_get(get_argument(0)).i)->size);
|
|
break;
|
|
}
|
|
case SH_SASSIGN_STRUCTSREF: {
|
|
int lval = stack_pop().i;
|
|
int rval = member_get(get_argument(0)).i;
|
|
heap_string_assign(lval, heap_get_string(rval));
|
|
break;
|
|
}
|
|
case SH_SASSIGN_GLOBALSREF: {
|
|
int lval = stack_pop().i;
|
|
int rval = global_get(get_argument(0)).i;
|
|
heap_string_assign(lval, heap_get_string(rval));
|
|
break;
|
|
}
|
|
case SH_STRUCTSREF_NE_STR0: {
|
|
struct string *a = heap_get_string(member_get(get_argument(0)).i);
|
|
struct string *b = ain->strings[get_argument(1)];
|
|
stack_push(!!strcmp(a->text, b->text));
|
|
break;
|
|
}
|
|
case SH_GLOBALSREF_NE_STR0: {
|
|
struct string *a = heap_get_string(global_get(get_argument(0)).i);
|
|
struct string *b = ain->strings[get_argument(1)];
|
|
stack_push(!!strcmp(a->text, b->text));
|
|
break;
|
|
}
|
|
case SH_LOC_LT_IMM_OR_LOC_GE_IMM: {
|
|
int i = local_get(get_argument(0)).i;
|
|
stack_push(i < get_argument(1) || i >= get_argument(2));
|
|
break;
|
|
}
|
|
case A_SORT_MEM: {
|
|
int mno = stack_pop().i;
|
|
int array = stack_pop_var()->i;
|
|
array_sort_mem(heap[array].page, mno);
|
|
break;
|
|
}
|
|
case DG_ADD: {
|
|
int fun = stack_pop().i;
|
|
int obj = stack_pop().i;
|
|
int dg_i = stack_pop().i;
|
|
delete_page(dg_i);
|
|
heap_set_page(dg_i, delegate_new_from_method(obj, fun));
|
|
break;
|
|
}
|
|
case DG_SET: {
|
|
int fun = stack_pop().i;
|
|
int obj = stack_pop().i;
|
|
int dg_i = stack_pop().i;
|
|
struct page *dg = heap_get_delegate_page(dg_i);
|
|
heap_set_page(dg_i, delegate_append(dg, obj, fun));
|
|
break;
|
|
}
|
|
case DG_CALL: { // DG_TYPE, ADDR
|
|
int dg = get_argument(0);
|
|
if (dg < 0 || dg >= ain->nr_delegates)
|
|
VM_ERROR("Invalid delegate index");
|
|
|
|
// stack: [arg0, ..., dg_page, dg_index, [return_value]]
|
|
int return_values = (ain->delegates[dg].return_type.data != AIN_VOID) ? 1 : 0;
|
|
int dg_page = stack_peek(1 + return_values).i;
|
|
int dg_index = stack_peek(0 + return_values).i;
|
|
if (dg_index < delegate_numof(heap_get_page(dg_page))) {
|
|
int obj, fun;
|
|
delegate_get(heap_get_delegate_page(dg_page), dg_index, &obj, &fun);
|
|
// pop previous return value
|
|
if (ain->delegates[dg].return_type.data != AIN_VOID) {
|
|
stack_pop();
|
|
}
|
|
delegate_call(dg);
|
|
if (ain->delegates[dg].return_type.data != AIN_VOID) {
|
|
stack[stack_ptr-2].i++;
|
|
} else {
|
|
stack[stack_ptr-1].i++;
|
|
}
|
|
instr_ptr += 10;
|
|
} else {
|
|
// call finished: clean up stack and jump to return address
|
|
union vm_value r;
|
|
if (return_values) {
|
|
r = stack_pop();
|
|
}
|
|
stack_pop(); // dg_index
|
|
stack_pop(); // dg_page
|
|
for (int i = ain->delegates[dg].nr_variables - 1; i >= 0; i--) {
|
|
variable_fini(stack_pop(), ain->delegates[dg].variables[i].type.data);
|
|
}
|
|
if (return_values) {
|
|
stack_push(r);
|
|
}
|
|
instr_ptr = get_argument(1);
|
|
}
|
|
break;
|
|
}
|
|
case DG_NUMOF: {
|
|
int dg = stack_pop().i;
|
|
stack_push(delegate_numof(heap_get_delegate_page(dg)));
|
|
break;
|
|
}
|
|
case DG_EXIST: {
|
|
int fun = stack_pop().i;
|
|
int obj = stack_pop().i;
|
|
int dg_i = stack_pop().i;
|
|
stack_push(delegate_contains(heap_get_delegate_page(dg_i), obj, fun));
|
|
break;
|
|
}
|
|
case DG_ERASE: {
|
|
int fun = stack_pop().i;
|
|
int obj = stack_pop().i;
|
|
int dg_i = stack_pop().i;
|
|
delegate_erase(heap_get_delegate_page(dg_i), obj, fun);
|
|
break;
|
|
}
|
|
case DG_CLEAR: {
|
|
int slot = stack_pop().i;
|
|
if (!slot)
|
|
break;
|
|
heap_set_page(slot, delegate_clear(heap_get_delegate_page(slot)));
|
|
break;
|
|
}
|
|
case DG_COPY: {
|
|
stack_push(vm_copy_page(heap_get_delegate_page(stack_pop().i)));
|
|
break;
|
|
}
|
|
case DG_ASSIGN: {
|
|
int set_i = stack_pop().i;
|
|
int dst_i = stack_pop().i;
|
|
struct page *set = heap_get_delegate_page(set_i);
|
|
struct page *new_dg = copy_page(set);
|
|
delete_page(dst_i);
|
|
heap_set_page(dst_i, new_dg);
|
|
stack_push(set_i);
|
|
break;
|
|
}
|
|
case DG_PLUSA: {
|
|
int add_i = stack_pop().i;
|
|
int dst_i = stack_pop().i;
|
|
struct page *add = heap_get_delegate_page(add_i);
|
|
struct page *dst = heap_get_delegate_page(dst_i);
|
|
heap_set_page(dst_i, delegate_plusa(dst, add));
|
|
stack_push(add_i);
|
|
break;
|
|
}
|
|
case DG_MINUSA: {
|
|
int minus_i = stack_pop().i;
|
|
int dst_i = stack_pop().i;
|
|
struct page *minus = heap_get_delegate_page(minus_i);
|
|
struct page *dst = heap_get_delegate_page(dst_i);
|
|
heap_set_page(dst_i, delegate_minusa(dst, minus));
|
|
stack_push(minus_i);
|
|
break;
|
|
}
|
|
case DG_POP: {
|
|
heap_unref(stack_pop().i);
|
|
break;
|
|
}
|
|
case DG_NEW_FROM_METHOD: {
|
|
int fun = stack_pop().i;
|
|
int obj = stack_pop().i;
|
|
stack_push(heap_alloc_page(delegate_new_from_method(obj, fun)));
|
|
break;
|
|
}
|
|
case DG_CALLBEGIN: { // DG_TYPE
|
|
int dg_no = get_argument(0);
|
|
if (dg_no < 0 || dg_no >= ain->nr_delegates)
|
|
VM_ERROR("Invalid delegate index");
|
|
struct ain_function_type *dg = &ain->delegates[dg_no];
|
|
|
|
// Stack before: [dg_page, arg0, ...]
|
|
// Stack after: [arg0, ..., dg_page, 0(dg_index)]
|
|
int dg_page = stack_peek(dg->nr_arguments).i;
|
|
for (int i = 0; i < dg->nr_arguments; i++) {
|
|
int pos = (stack_ptr - dg->nr_arguments) + i;
|
|
stack[pos-1] = stack[pos];
|
|
}
|
|
stack[stack_ptr-1].i = dg_page;
|
|
stack_push(0);
|
|
|
|
// XXX: If the delegate has a return value, we push a dummy value
|
|
// so that DG_CALL can replace it
|
|
if (dg->return_type.data != AIN_VOID) {
|
|
stack_push(0);
|
|
}
|
|
break;
|
|
}
|
|
//case DG_NEW:
|
|
//case DG_STR_TO_METHOD:
|
|
// -- NOOPs ---
|
|
case FUNC:
|
|
break;
|
|
default:
|
|
#ifdef DEBUGGER_ENABLED
|
|
if ((opcode & OPTYPE_MASK) == BREAKPOINT) {
|
|
dbg_handle_breakpoint();
|
|
return execute_instruction(opcode & ~OPTYPE_MASK);
|
|
}
|
|
#endif
|
|
VM_ERROR("Illegal opcode: 0x%04x", opcode);
|
|
}
|
|
return opcode;
|
|
}
|
|
|
|
static void vm_execute(void)
|
|
{
|
|
for (;;) {
|
|
uint16_t opcode;
|
|
if (instr_ptr == VM_RETURN)
|
|
return;
|
|
if (unlikely(instr_ptr >= ain->code_size)) {
|
|
VM_ERROR("Illegal instruction pointer: 0x%08lX", instr_ptr);
|
|
}
|
|
opcode = get_opcode(instr_ptr);
|
|
opcode = execute_instruction(opcode);
|
|
instr_ptr += instructions[opcode].ip_inc;
|
|
}
|
|
}
|
|
|
|
static void vm_free(void)
|
|
{
|
|
// call library exit routines
|
|
exit_libraries();
|
|
// flush call stack
|
|
for (int i = call_stack_ptr - 1; i >= 0; i--) {
|
|
exit_unref(call_stack[i].page_slot);
|
|
}
|
|
// free globals
|
|
exit_unref(0);
|
|
}
|
|
|
|
static jmp_buf reset_buf;
|
|
|
|
static _Noreturn void vm_reset(void)
|
|
{
|
|
vm_free();
|
|
longjmp(reset_buf, 1);
|
|
}
|
|
|
|
int vm_execute_ain(struct ain *program)
|
|
{
|
|
ain = program;
|
|
setjmp(reset_buf);
|
|
|
|
// initialize VM state
|
|
if (!stack) {
|
|
stack_size = INITIAL_STACK_SIZE;
|
|
stack = xmalloc(INITIAL_STACK_SIZE * sizeof(union vm_value));
|
|
}
|
|
stack_ptr = 0;
|
|
call_stack_ptr = 0;
|
|
|
|
heap_init();
|
|
init_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].type.data == AIN_STRUCT) {
|
|
// XXX: need to allocate storage for global structs BEFORE calling
|
|
// constructors.
|
|
heap[0].page->values[i].i = alloc_struct(ain->globals[i].type.struc);
|
|
} else {
|
|
heap[0].page->values[i] = variable_initval(ain->globals[i].type.data);
|
|
}
|
|
}
|
|
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;
|
|
}
|
|
}
|
|
|
|
if (ain->alloc >= 0)
|
|
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].type.data == AIN_STRUCT)
|
|
init_struct(ain->globals[i].type.struc, heap[0].page->values[i].i);
|
|
}
|
|
|
|
vm_call(ain->main, -1);
|
|
return stack_pop().i;
|
|
}
|
|
|
|
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));
|
|
uint32_t addr = (i == call_stack_ptr - 1) ? instr_ptr : call_stack[i+1].call_address;
|
|
sys_warning("\t0x%08x in %s\n", addr, 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)
|
|
{
|
|
struct timespec ts;
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
return (ts.tv_sec * 1000) + (ts.tv_nsec / 1000000);
|
|
}
|
|
|
|
void vm_sleep(int ms)
|
|
{
|
|
SDL_Delay(ms);
|
|
}
|
|
|
|
_Noreturn void vm_exit(int code)
|
|
{
|
|
vm_free();
|
|
#ifdef DEBUG_HEAP
|
|
for (size_t i = 0; i < heap_size; i++) {
|
|
if (heap[i].ref > 0)
|
|
heap_describe_slot(i);
|
|
}
|
|
sys_message("Number of leaked objects: %d\n", heap_free_ptr);
|
|
#endif
|
|
sys_exit(code);
|
|
}
|