Implement constructors and methods

Implement CALLMETHOD, SH_STRUCTREF and PUSHSTRUCTPAGE instructions.

Constructors are a bit tricky since SH_LOCALCREATE has to implicitly
call an arbitrary number of functions before it returns (just changing
the instruction pointer only allows for calling a single function).

To get around this, there is now a vm_call() function which is used to
call a function synchronously, before returning from the current
instruction.

Methods are implemented as functions, except that they get a struct page
as local variable -1.

Destructors are still not implemented.
This commit is contained in:
Nunuhara Cabbage
2019-10-20 17:33:22 -07:00
parent 2210696f93
commit e5ef9f4d2d
9 changed files with 333 additions and 169 deletions
+1 -1
View File
@@ -181,6 +181,6 @@ struct ain {
const char *ain_strerror(int error);
struct ain *ain_open(const char *path, int *error);
void ain_free(struct ain *ain);
void vm_execute(struct ain *program);
void vm_execute_ain(struct ain *program);
#endif /* SYSTEM4_AIN_H */
+3 -3
View File
@@ -125,7 +125,7 @@ const struct instruction instructions[NR_OPCODES] = {
OP(SR_POP, 0),
OP(SR_ASSIGN, 0),
OP(SR_REF, 1),
OP(SR_REF, 1, INSTR_INT),
OP(SR_REFREF, 0),
OP(A_ALLOC, 0),
OP(A_REALLOC, 0),
@@ -138,7 +138,7 @@ const struct instruction instructions[NR_OPCODES] = {
OP(MSG, 0),
OP(CALLHLL, 0),
OP(PUSHSTRUCTPAGE, 0),
OP(CALLMETHOD, 0),
JMP(CALLMETHOD, 1, INSTR_FUN),
OP(SH_GLOBALREF, 1, INSTR_INT),
OP(SH_LOCALREF, 1, INSTR_INT),
OP(SWITCH, 0),
@@ -149,7 +149,7 @@ const struct instruction instructions[NR_OPCODES] = {
OP(SJUMP, 0),
OP(CALLONJUMP, 0),
OP(SWAP, 0),
OP(SH_STRUCTREF, 0),
OP(SH_STRUCTREF, 1, INSTR_INT),
OP(S_LENGTH, 0),
OP(S_LENGTHBYTE, 0),
OP(I_STRING, 0),
+1 -1
View File
@@ -1,4 +1,4 @@
project('system4', 'c', default_options : ['c_std=c11'])
system4 = ['system4.c', 'ain.c', 'instructions.c', 'vm.c', 'string.c', 'utfsjis.c']
system4 = ['system4.c', 'ain.c', 'instructions.c', 'vm.c', 'string.c', 'page.c', 'utfsjis.c']
zdep = dependency('zlib')
executable('system4', system4, dependencies: zdep)
+81
View File
@@ -0,0 +1,81 @@
/* 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 "system4.h"
#include "ain.h"
#include "vm.h"
#include "vm_string.h"
#include "page.h"
int32_t alloc_page(int nr_vars)
{
union vm_value *page = xmalloc(sizeof(union vm_value) * nr_vars);
int32_t slot = heap_alloc_slot(VM_PAGE);
heap[slot].page = page;
return slot;
}
void delete_page(union vm_value *page, struct ain_variable *vars, int nr_vars)
{
for (int i = 0; i < nr_vars; i++) {
struct ain_struct *s;
switch (vars[i].data_type) {
case AIN_STRING:
heap_unref(page[i].i);
break;
case AIN_STRUCT:
if (page[i].i == -1)
break;
s = &ain->structures[vars[i].struct_type];
delete_page(heap[page[i].i].page, s->members, s->nr_members);
heap_unref(page[i].i);
break;
default:
break;
}
}
}
/*
* Recursively copy a page.
*/
union vm_value *copy_page(union vm_value *src, struct ain_variable *vars, int nr_vars)
{
union vm_value *dst = xmalloc(sizeof(union vm_value) * nr_vars);
for (int i = 0; i < nr_vars; i++) {
int slot;
struct ain_struct *s;
union vm_value *page;
switch (vars[i].data_type) {
case AIN_STRING:
slot = heap_alloc_slot(VM_STRING);
heap[slot].s = string_dup(heap[src[i].i].s);
dst[i].i = slot;
break;
case AIN_STRUCT:
s = &ain->structures[vars[i].struct_type];
page = copy_page(heap[src[i].i].page, s->members, s->nr_members);
slot = heap_alloc_slot(VM_PAGE);
heap[slot].page = page;
dst[i].i = slot;
break;
default:
dst[i] = src[i];
break;
}
}
return dst;
}
+29
View File
@@ -0,0 +1,29 @@
/* 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/>.
*/
#ifndef SYSTEM4_PAGE_H
#define SYSTEM4_PAGE_H
#include <stdint.h>
union vm_value;
struct ain_variable;
int32_t alloc_page(int nr_vars);
union vm_value *copy_page(union vm_value *src, struct ain_variable *vars, int nr_vars);
void delete_page(union vm_value *page, struct ain_variable *vars, int nr_vars);
#endif /* SYSTEM4_PAGE_H */
+1 -1
View File
@@ -96,6 +96,6 @@ int main(int argc, char *argv[])
ERROR("%s", ain_strerror(err));
}
vm_execute(ain);
vm_execute_ain(ain);
sys_exit(0);
}
+29
View File
@@ -11,9 +11,34 @@ struct my_struct {
inner_struct inner;
};
struct my_class_inner {
int a;
my_class_inner() {
a = 99;
}
};
struct my_class {
int a;
int b;
my_class_inner inner;
my_class() {
a = 42;
b = inner.a + 1;
}
void set_a(int a_);
};
void my_class::set_a(int a_) {
a = a_;
}
void test_structs(void)
{
my_struct s;
my_class c;
test_equal("s.a (uninitialized)", s.a, 0);
test_string("s.s (uninitialized)", s.s, "");
test_equal("s.inner.a (uninitialized)", s.inner.a, 0);
@@ -21,4 +46,8 @@ void test_structs(void)
test_equal("s.b = 13", (s.b = 13, s.b), 13);
test_bool("s.s = \"test\"", (s.s = "test", s.s == "test"), true);
test_equal("s.inner.a = 14", (s.inner.a = 14, s.inner.a), 14);
test_equal("constructor", c.a, 42);
test_equal("nested constructor", c.inner.a, 99);
test_equal("nested constructor precedence", c.b, 100);
test_equal("c.set_a(22)", (c.set_a(22), c.a), 22);
}
+134 -163
View File
@@ -18,7 +18,9 @@
#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"
@@ -28,42 +30,22 @@
#define INITIAL_HEAP_SIZE 4096
#define INITIAL_PAGES_SIZE 4096
#define INSTR (instructions[get_opcode(instr_ptr)])
// 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, INSTR.name, instr_ptr, ##__VA_ARGS__)
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.
*/
// Non-heap values. Stored in pages and on the stack.
union vm_value {
int32_t i;
int64_t i64;
float f;
};
enum vm_pointer_type {
VM_FRAME,
VM_PAGE,
VM_STRING
};
// Heap-backed objects. Reference counted.
struct vm_pointer {
int ref;
enum vm_pointer_type type;
union {
struct string *s;
union vm_value *page;
};
};
struct function_call {
int32_t fno;
int32_t return_address;
uint32_t return_address;
int32_t page_slot;
int32_t page_ptr;
};
@@ -75,7 +57,7 @@ static size_t stack_size; // current size of the stack
// The heap
// An array of pointers to heap-allocated objects, plus reference counts.
static struct vm_pointer *heap;
struct vm_pointer *heap;
static size_t heap_size;
// Heap free list
@@ -90,9 +72,9 @@ static int32_t pages_size;
// Stack of function call frames
static struct function_call call_stack[4096];
static int32_t call_stack_ptr = 0;
static int32_t call_stack_ptr = 1; // 0 = imaginary frame before main()
static struct ain *ain;
struct ain *ain;
static size_t instr_ptr = 0;
// Read the opcode at ADDR.
@@ -115,7 +97,7 @@ static float get_argument_float(int n)
return v.f;
}
static int32_t heap_alloc_slot(enum vm_pointer_type type)
int32_t heap_alloc_slot(enum vm_pointer_type type)
{
int32_t slot = heap_free_stack[heap_free_ptr++];
heap[slot].ref = 1;
@@ -128,12 +110,12 @@ static void heap_free_slot(int32_t slot)
heap_free_stack[--heap_free_ptr] = slot;
}
//static void heap_ref(int32_t slot)
//{
// heap[slot].ref++;
//}
void heap_ref(int32_t slot)
{
heap[slot].ref++;
}
static void heap_unref(int32_t slot)
void heap_unref(int slot)
{
if (heap[slot].ref <= 0) {
EXECUTION_ERROR("double free (slot %d)", slot);
@@ -189,11 +171,21 @@ 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)];
@@ -235,103 +227,6 @@ static struct string *stack_pop_string(void)
return heap[stack[--stack_ptr].i].s;
}
static int32_t alloc_page(int nr_vars)
{
union vm_value *page = xmalloc(sizeof(union vm_value) * nr_vars);
int32_t slot = heap_alloc_slot(VM_PAGE);
heap[slot].page = page;
return slot;
}
static void delete_page(union vm_value *page, struct ain_variable *vars, int nr_vars)
{
for (int i = 0; i < nr_vars; i++) {
struct ain_struct *s;
switch (vars[i].data_type) {
case AIN_STRING:
heap_unref(page[i].i);
break;
case AIN_STRUCT:
s = &ain->structures[vars[i].struct_type];
delete_page(heap[page[i].i].page, s->members, s->nr_members);
heap_unref(page[i].i);
break;
default:
break;
}
}
}
/*
* Recursively copy a page.
*/
static union vm_value *copy_page(union vm_value *src, struct ain_variable *vars, int nr_vars)
{
union vm_value *dst = xmalloc(sizeof(union vm_value) * nr_vars);
for (int i = 0; i < nr_vars; i++) {
int slot;
struct ain_struct *s;
union vm_value *page;
switch (vars[i].data_type) {
case AIN_STRING:
slot = heap_alloc_slot(VM_STRING);
heap[slot].s = string_dup(heap[src[i].i].s);
dst[i].i = slot;
break;
case AIN_STRUCT:
s = &ain->structures[vars[i].struct_type];
page = copy_page(heap[src[i].i].page, s->members, s->nr_members);
slot = heap_alloc_slot(VM_PAGE);
heap[slot].page = page;
dst[i].i = slot;
break;
default:
dst[i] = src[i];
break;
}
}
return dst;
}
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 = {
.literal = true,
.size = 0,
.text = ""
};
static int create_struct(int no)
{
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 = &EMPTY_STRING;
heap[slot].page[i].i = memb;
break;
case AIN_STRUCT:
memb = create_struct(s->members[i].struct_type);
heap[slot].page[i].i = memb;
break;
default:
heap[slot].page[i].i = 0;
break;
}
}
// TODO: call constructor
return slot;
}
/*
* System 4 calling convention:
* - caller pushes arguments, in order
@@ -339,17 +234,14 @@ static int create_struct(int no)
* - callee pushes return value on the stack
* - RETURN jumps to return address (saved in stack frame)
*/
static void function_call(int32_t no)
static void call(int fno, int new_pp, int return_address)
{
struct ain_function *f = &ain->functions[no];
int32_t cur_fno = call_stack[call_stack_ptr-1].fno;
int32_t page_slot = heap_alloc_slot(VM_FRAME);
int32_t new_pp = page_ptr + ain->functions[cur_fno].nr_vars;
struct ain_function *f = &ain->functions[fno];
int page_slot = heap_alloc_slot(VM_FRAME);
// create new stack frame
call_stack[call_stack_ptr++] = (struct function_call) {
.fno = no,
.return_address = instr_ptr + instruction_width(CALLFUNC),
.fno = fno,
.return_address = return_address,
.page_slot = page_slot,
.page_ptr = page_ptr
};
@@ -380,7 +272,37 @@ static void function_call(int32_t no)
// update stack/instruction pointers
page_ptr = new_pp;
instr_ptr = ain->functions[no].address;
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)
@@ -394,8 +316,6 @@ static void function_return(void)
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)
@@ -423,6 +343,46 @@ static void system_call(int32_t 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 = {
.literal = true,
.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 = &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;
@@ -506,6 +466,9 @@ static void execute_instruction(int16_t opcode)
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();
@@ -518,6 +481,9 @@ static void execute_instruction(int16_t opcode)
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;
@@ -536,14 +502,16 @@ static void execute_instruction(int16_t opcode)
}
break;
case SH_LOCALCREATE: // VARNO, STRUCTNO
index = create_struct(get_argument(1));
local_set(get_argument(0), index);
create_struct(get_argument(1), local_ptr(get_argument(0)));
break;
//
// --- Function Calls ---
//
case CALLFUNC:
function_call(get_argument(0));
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();
@@ -886,7 +854,25 @@ static void execute_instruction(int16_t opcode)
}
}
void vm_execute(struct ain *program)
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;
@@ -934,20 +920,5 @@ void vm_execute(struct 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;
}
vm_call(ain->main, -1);
}
+54
View File
@@ -0,0 +1,54 @@
/* 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/>.
*/
#ifndef SYSTEM4_VM_H
#define SYSTEM4_VM_H
#include <stdint.h>
// Non-heap values. Stored in pages and on the stack.
union vm_value {
int32_t i;
int64_t i64;
float f;
};
enum vm_pointer_type {
VM_FRAME,
VM_PAGE,
VM_STRING
};
// Heap-backed objects. Reference counted.
struct vm_pointer {
int ref;
enum vm_pointer_type type;
union {
struct string *s;
union vm_value *page;
};
};
struct ain;
struct vm_pointer *heap;
struct ain *ain;
int32_t heap_alloc_slot(enum vm_pointer_type type);
void heap_ref(int slot);
void heap_unref(int slot);
#endif /* SYSTEM4_VM_H */