mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-09-30 19:08:12 +03:00
443 lines
9.9 KiB
C
443 lines
9.9 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 "sys4_string.h"
|
|
#include "ain.h"
|
|
#include "instructions.h"
|
|
#include "little_endian.h"
|
|
|
|
/*
|
|
* NOTE: The current implementation is a simple bytecode interpreter.
|
|
* System40.exe uses a JIT compiler, and we should too.
|
|
*/
|
|
|
|
static int32_t *stack = NULL;
|
|
static size_t stack_size;
|
|
static int stack_ptr = 0;
|
|
|
|
// Separate stack for strings
|
|
// Not sure exactly how System40.exe handles strings.
|
|
// Further testing required to ensure this approach doesn't break anything.
|
|
static struct string string_stack[1024];
|
|
static int string_stack_ptr = 0;
|
|
|
|
//static struct frame *frame_stack[1024];
|
|
static int32_t 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];
|
|
}
|
|
|
|
static void local_set(int varno, int32_t value)
|
|
{
|
|
frame_stack[frame_ptr + FRAME_VAR_OFF + varno] = 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++] = v;
|
|
}
|
|
|
|
static int32_t 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();
|
|
int32_t frame = stack_pop();
|
|
return frame + FRAME_VAR_OFF + index;
|
|
}
|
|
|
|
static void stack_push_string_literal(int32_t no)
|
|
{
|
|
// FIXME: should store string length when reading AIN
|
|
string_stack[string_stack_ptr++] = make_string(ain->strings[no], strlen(ain->strings[no]));
|
|
}
|
|
|
|
static void stack_push_string(struct string *str)
|
|
{
|
|
string_stack[string_stack_ptr++] = *str;
|
|
}
|
|
|
|
static struct string stack_pop_string(void)
|
|
{
|
|
string_stack_ptr--;
|
|
return string_stack[string_stack_ptr];
|
|
}
|
|
|
|
static void stack_toss_string(void)
|
|
{
|
|
string_stack_ptr--;
|
|
free_string(&string_stack[string_stack_ptr]);
|
|
}
|
|
|
|
static struct string *stack_peek_string(void)
|
|
{
|
|
return &string_stack[string_stack_ptr-1];
|
|
}
|
|
|
|
/*
|
|
* 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];
|
|
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] = no;
|
|
frame_stack[new_fp + FRAME_IP_OFF] = instr_ptr + instruction_width(CALLFUNC);
|
|
frame_stack[new_fp + FRAME_FP_OFF] = 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];
|
|
frame_ptr = frame_stack[frame_ptr + FRAME_FP_OFF];
|
|
}
|
|
|
|
static void system_call(int32_t code)
|
|
{
|
|
switch (code) {
|
|
case 0x0: // system.Exit(int nResult)
|
|
sys_exit(stack_pop());
|
|
break;
|
|
case 0x6: // system.Output(string szText)
|
|
sys_message("%s", stack_peek_string()->str);
|
|
// 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 POP:
|
|
stack_pop();
|
|
break;
|
|
case REF:
|
|
// Dereference a reference to a value.
|
|
stack_push(frame_stack[stack_pop_ref()]);
|
|
break;
|
|
case REFREF:
|
|
// Dereference a reference to a reference.
|
|
index = stack_pop_ref();
|
|
stack_push(frame_stack[index]);
|
|
stack_push(frame_stack[index + 1]);
|
|
break;
|
|
case PUSHLOCALPAGE:
|
|
stack_push(frame_ptr);
|
|
break;
|
|
case ASSIGN:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] = 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())
|
|
instr_ptr = get_argument(0);
|
|
else
|
|
instr_ptr += instruction_width(IFZ);
|
|
break;
|
|
case IFNZ: // ADDR
|
|
if (stack_pop())
|
|
instr_ptr = get_argument(0);
|
|
else
|
|
instr_ptr += instruction_width(IFNZ);
|
|
break;
|
|
//
|
|
// --- Arithmetic ---
|
|
//
|
|
case INV:
|
|
stack[stack_ptr-1] = -stack[stack_ptr-1];
|
|
break;
|
|
case NOT:
|
|
stack[stack_ptr-1] = !stack[stack_ptr-1];
|
|
break;
|
|
case COMPL:
|
|
stack[stack_ptr-1] = ~stack[stack_ptr-1];
|
|
break;
|
|
case ADD:
|
|
stack[stack_ptr-2] += stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case SUB:
|
|
stack[stack_ptr-2] -= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case MUL:
|
|
stack[stack_ptr-2] *= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case DIV:
|
|
stack[stack_ptr-2] /= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case MOD:
|
|
stack[stack_ptr-2] %= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case AND:
|
|
stack[stack_ptr-2] &= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case OR:
|
|
stack[stack_ptr-2] |= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case XOR:
|
|
stack[stack_ptr-2] ^= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case LSHIFT:
|
|
stack[stack_ptr-2] <<= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
case RSHIFT:
|
|
stack[stack_ptr-2] >>= stack[stack_ptr-1];
|
|
stack_ptr--;
|
|
break;
|
|
// Numeric Comparisons
|
|
case LT:
|
|
b = stack_pop();
|
|
a = stack_pop();
|
|
stack_push(a < b ? 1 : 0);
|
|
break;
|
|
case GT:
|
|
b = stack_pop();
|
|
a = stack_pop();
|
|
stack_push(a > b ? 1 : 0);
|
|
break;
|
|
case LTE:
|
|
b = stack_pop();
|
|
a = stack_pop();
|
|
stack_push(a <= b ? 1 : 0);
|
|
break;
|
|
case GTE:
|
|
b = stack_pop();
|
|
a = stack_pop();
|
|
stack_push(a >= b ? 1 : 0);
|
|
break;
|
|
case NOTE:
|
|
b = stack_pop();
|
|
a = stack_pop();
|
|
stack_push(a != b ? 1 : 0);
|
|
break;
|
|
case EQUALE:
|
|
b = stack_pop();
|
|
a = stack_pop();
|
|
stack_push(a == b ? 1 : 0);
|
|
break;
|
|
// +=, -=, etc.
|
|
case PLUSA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] += v;
|
|
break;
|
|
case MINUSA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] -= v;
|
|
break;
|
|
case MULA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] *= v;
|
|
break;
|
|
case DIVA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] /= v;
|
|
break;
|
|
case MODA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] %= v;
|
|
break;
|
|
case ANDA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] &= v;
|
|
break;
|
|
case ORA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] |= v;
|
|
break;
|
|
case XORA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] ^= v;
|
|
break;
|
|
case LSHIFTA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] <<= v;
|
|
break;
|
|
case RSHIFTA:
|
|
v = stack_pop();
|
|
index = stack_pop_ref();
|
|
frame_stack[index] >>= v;
|
|
break;
|
|
//
|
|
// --- Strings ---
|
|
//
|
|
case S_PUSH:
|
|
stack_push_string_literal(get_argument(0));
|
|
break;
|
|
case S_POP:
|
|
stack_toss_string();
|
|
break;
|
|
case S_ADD:
|
|
sb = stack_pop_string();
|
|
sa = stack_pop_string();
|
|
string_add(&sa, &sb);
|
|
stack_push_string(&sa);
|
|
break;
|
|
case I_STRING:
|
|
sa = integer_to_string(stack_pop());
|
|
stack_push_string(&sa);
|
|
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;
|
|
}
|
|
}
|