Files
nunuhara_xsystem4/vm.c
T

445 lines
9.8 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_string.h"
#include "ain.h"
#include "instructions.h"
#include "little_endian.h"
union vm_value {
int32_t i;
int64_t i64;
float f;
struct string *s;
};
/*
* NOTE: The current implementation is a simple bytecode interpreter.
* System40.exe uses a JIT compiler, and we should too.
*/
static union vm_value *stack = NULL;
static size_t stack_size;
static int stack_ptr = 0;
static union vm_value frame_stack[4096];
static int frame_ptr = 0;
#define FRAME_FN_OFF 0 // offset to function number
#define FRAME_IP_OFF 1 // offset to return address
#define FRAME_FP_OFF 2 // offset to frame pointer
#define FRAME_VAR_OFF 3 // offset to variables
static struct ain *ain;
static size_t instr_ptr = 0;
static int32_t local_ref(int varno)
{
return frame_stack[frame_ptr + FRAME_VAR_OFF + varno].i;
}
static void local_set(int varno, int32_t value)
{
frame_stack[frame_ptr + FRAME_VAR_OFF + varno].i = value;
}
// 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);
}
static void stack_push(int32_t v)
{
stack[stack_ptr++].i = 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 int32_t stack_pop_ref(void)
{
int32_t index = stack_pop().i;
int32_t frame = stack_pop().i;
return frame + FRAME_VAR_OFF + index;
}
static void stack_push_string_literal(int32_t no)
{
stack[stack_ptr++].s = ain->strings[no];
}
static void stack_push_string(struct string *str)
{
stack[stack_ptr++].s = str;
}
static struct string *stack_pop_string(void)
{
stack_ptr--;
return stack[stack_ptr].s;
}
static void stack_toss_string(void)
{
stack_ptr--;
free_string(stack[stack_ptr].s);
}
static struct string *stack_peek_string(void)
{
return stack[stack_ptr-1].s;
}
/*
* System 4 calling convention:
* - caller pushes arguments, in order
* - CALLFUNC creates stack frame ("page", on separate stack), pops arguments
* - callee pushes return value on the stack
* - RETURN jumps to return address (saved in stack frame)
*/
static void function_call(int32_t no)
{
int32_t cur_fno = frame_stack[frame_ptr + FRAME_FN_OFF].i;
int32_t new_fp = frame_ptr + FRAME_VAR_OFF + ain->functions[cur_fno].nr_vars;
// create new stack frame
frame_stack[new_fp + FRAME_FN_OFF].i = no;
frame_stack[new_fp + FRAME_IP_OFF].i = instr_ptr + instruction_width(CALLFUNC);
frame_stack[new_fp + FRAME_FP_OFF].i = frame_ptr;
for (int i = ain->functions[no].nr_args - 1; i >= 0; i--) {
frame_stack[new_fp + FRAME_VAR_OFF + i] = stack_pop();
}
// update frame & instruction pointers
frame_ptr = new_fp;
instr_ptr = ain->functions[no].address;
}
static void function_return(void)
{
instr_ptr = frame_stack[frame_ptr + FRAME_IP_OFF].i;
frame_ptr = frame_stack[frame_ptr + FRAME_FP_OFF].i;
}
static void system_call(int32_t code)
{
switch (code) {
case 0x0: // system.Exit(int nResult)
sys_exit(stack_pop().i);
break;
case 0x6: // system.Output(string szText)
sys_message("%s", stack_peek_string()->text);
// 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 void execute_instruction(int16_t opcode)
{
int32_t index, a, b, v;
struct string *sa, *sb;
const char *opcode_name = "UNKNOWN";
switch (opcode) {
//
// --- Stack Management ---
//
case PUSH:
stack_push(get_argument(0));
break;
case S_PUSH:
stack_push_string_literal(get_argument(0));
break;
case POP:
stack_pop();
break;
case S_POP:
stack_toss_string();
break;
case REF:
// Dereference a reference to a value.
stack_push(frame_stack[stack_pop_ref()].i);
break;
case S_REF:
// Dereference a reference to a string
stack_push_string(frame_stack[stack_pop_ref()].s);
break;
case REFREF:
// Dereference a reference to a reference.
index = stack_pop_ref();
stack_push(frame_stack[index].i);
stack_push(frame_stack[index + 1].i);
break;
case PUSHLOCALPAGE:
stack_push(frame_ptr);
break;
case ASSIGN:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i = v;
break;
case SH_LOCALREF: // VARNO
// Push the value of VARNO to the stack
stack_push(local_ref(get_argument(0)));
break;
case SH_LOCALASSIGN: // VARNO, VALUE
// Assign VALUE to local VARNO
local_set(get_argument(0), get_argument(1));
break;
case SH_LOCALINC: // VARNO
index = get_argument(0);
local_set(index, local_ref(index)+1);
break;
case SH_LOCALDEC: // VARNO
index = get_argument(0);
local_set(index, local_ref(index)-1);
break;
//
// --- Function Calls ---
//
case CALLFUNC:
function_call(get_argument(0));
break;
case RETURN:
function_return();
break;
case CALLSYS:
system_call(get_argument(0));
break;
//
// --- Control Flow ---
//
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;
index = stack_pop_ref();
frame_stack[index].i += v;
break;
case MINUSA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i -= v;
break;
case MULA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i *= v;
break;
case DIVA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i /= v;
break;
case MODA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i %= v;
break;
case ANDA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i &= v;
break;
case ORA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i |= v;
break;
case XORA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i ^= v;
break;
case LSHIFTA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i <<= v;
break;
case RSHIFTA:
v = stack_pop().i;
index = stack_pop_ref();
frame_stack[index].i >>= v;
break;
//
// --- Strings ---
//
case S_ADD:
sb = stack_pop_string();
sa = stack_pop_string();
sa = string_append(sa, sb);
stack_push_string(sa);
break;
case I_STRING:
stack_push_string(integer_to_string(stack_pop().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);
}
}
void vm_execute(struct ain *program)
{
// initialize machine state
stack_ptr = 0;
frame_ptr = 0;
stack_size = 1024;
stack = xmalloc(stack_size);
ain = program;
// Jump to main. We set up a stack frame so that when main returns,
// the first instruction past the end of the code section is executed.
// ()When we read the AIN file, CALLSYS 0x0 was placed there.)
instr_ptr = ain->code_size - instruction_width(CALLFUNC);
function_call(ain->main);
// fetch/decode/execute loop
for (;;)
{
if (instr_ptr >= ain->code_size + 6) {
ERROR("Illegal instruction pointer: 0x%lX", instr_ptr);
}
int16_t opcode = get_opcode(instr_ptr);
execute_instruction(opcode);
instr_ptr += instructions[opcode].ip_inc;
}
}