mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-10 00:08:23 +03:00
Fix memory managment of objects on stack
A few things.
The S_REF instruction creates a _copy_ of the referenced string. Proof:
FUNC foo
// string local_string = "foo"
SH_LOCALREF local_string
S_PUSH "foo"
S_ASSIGN
S_POP
// prepare argument for bar()
PUSHLOCALPAGE
PUSH local_string
S_REF
// stealthily alter local_string
SH_LOCALREF local_string
S_PUSH "bar"
S_ASSIGN
S_POP
// bar(local_string)
CALLFUNC bar // local_string is still "foo" in the body of bar()!
Thus, it is not CALLFUNC's responsibility to copy by-value string
arguments. CALLFUNC need only copy the index from the stack into the
local page.
Objects pushed to the stack with SH_LOCALREF, SH_GLOBALREF and REF do
not gain a reference. They are already referenced by the page they
belong to.
I believe the implementation of S_REF is still not quite right. What
I've noticed is that System40.exe is able to distinguish between strings
pushed with S_REF/S_PUSH and strings pushed with
SH_LOCALREF/SH_GLOBALREF/REF. It seems the former are r-values and the
latter are l-values (i.e. the latter can be assigned to, but not the
former), and the VM will throw an error if you try to assign to an
r-value.
The underlying implementation is not clear to me. Both types appear to
be indices into the heap, which suggests that the distinguishing factor
must be stored either in the object itself, or as part of the pointer to
an object. Perhaps its as simple as a flag marking an object as an
r-value?
This commit is contained in:
@@ -25,10 +25,10 @@
|
||||
#define noreturn _Noreturn
|
||||
|
||||
#define ERROR(fmt, ...) \
|
||||
sys_error("*ERROR*(%s): " fmt "\n", __func__, ##__VA_ARGS__)
|
||||
sys_error("*ERROR*(%s:%s:%d): " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
#define WARNING(fmt, ...) \
|
||||
sys_error("*ERROR*(%s): " fmt "\n", __func__, ##__VA_ARGS__)
|
||||
sys_message("*WARNING*(%s:%s:%d): " fmt "\n", __FILE__, __func__, __LINE__, ##__VA_ARGS__)
|
||||
|
||||
#define NOTICE(fmt, ...) \
|
||||
sys_message(fmt "\n", ##__VA_ARGS__)
|
||||
|
||||
@@ -20,6 +20,7 @@ void test_strings(void)
|
||||
test_bool("\"a\" >= \"a\"", "a" >= "a", true);
|
||||
test_bool("\"b\" >= \"a\"", "b" >= "a", true);
|
||||
test_bool("var = \"str\"", (s = "str", s == "str"), true);
|
||||
test_string("foo += \"bar\"", (s = "foo", s += "bar"), "foobar");
|
||||
// S_ADD
|
||||
test_string("\"a\" + \"b\"", "a" + "b", "ab");
|
||||
// S_LENGTH
|
||||
|
||||
@@ -28,6 +28,11 @@
|
||||
#define INITIAL_HEAP_SIZE 4096
|
||||
#define INITIAL_PAGES_SIZE 4096
|
||||
|
||||
#define INSTR (instructions[get_opcode(instr_ptr)])
|
||||
|
||||
#define EXECUTION_ERROR(msg, ...) \
|
||||
ERROR("%s (0x%X): " msg, INSTR.name, instr_ptr, ##__VA_ARGS__)
|
||||
|
||||
/*
|
||||
* NOTE: The current implementation is a simple bytecode interpreter.
|
||||
* System40.exe uses a JIT compiler, and we should too.
|
||||
@@ -90,6 +95,26 @@ static int32_t call_stack_ptr = 0;
|
||||
static struct ain *ain;
|
||||
static size_t instr_ptr = 0;
|
||||
|
||||
// Read the opcode at ADDR.
|
||||
static int16_t get_opcode(size_t addr)
|
||||
{
|
||||
return LittleEndian_getW(ain->code, addr);
|
||||
}
|
||||
|
||||
// Read argument N for the current instruction.
|
||||
static int32_t get_argument(int n)
|
||||
{
|
||||
return LittleEndian_getDW(ain->code, instr_ptr + 2 + n*4);
|
||||
}
|
||||
|
||||
// XXX: not strictly portable
|
||||
static float get_argument_float(int n)
|
||||
{
|
||||
union vm_value v;
|
||||
v.i = LittleEndian_getDW(ain->code, instr_ptr + 2 + n*4);
|
||||
return v.f;
|
||||
}
|
||||
|
||||
static int32_t heap_alloc_slot(enum vm_pointer_type type)
|
||||
{
|
||||
int32_t slot = heap_free_stack[heap_free_ptr++];
|
||||
@@ -103,13 +128,16 @@ 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++;
|
||||
}
|
||||
//static void heap_ref(int32_t slot)
|
||||
//{
|
||||
// heap[slot].ref++;
|
||||
//}
|
||||
|
||||
static void heap_unref(int32_t slot)
|
||||
{
|
||||
if (heap[slot].ref <= 0) {
|
||||
EXECUTION_ERROR("double free (slot %d)", slot);
|
||||
}
|
||||
if (--heap[slot].ref <= 0) {
|
||||
switch (heap[slot].type) {
|
||||
case VM_FRAME:
|
||||
@@ -161,42 +189,11 @@ static void local_set(int varno, int32_t value)
|
||||
page_stack[page_ptr + varno].i = value;
|
||||
}
|
||||
|
||||
static enum ain_data_type local_type(int varno)
|
||||
{
|
||||
struct ain_function *f = &ain->functions[call_stack[call_stack_ptr-1].fno];
|
||||
return f->vars[varno].data_type;
|
||||
}
|
||||
|
||||
static int32_t global_get(int varno)
|
||||
{
|
||||
return heap[0].page[varno].i;
|
||||
}
|
||||
|
||||
static enum ain_data_type global_type(int varno)
|
||||
{
|
||||
return ain->globals[varno].data_type;
|
||||
}
|
||||
|
||||
// Read the opcode at ADDR.
|
||||
static int16_t get_opcode(size_t addr)
|
||||
{
|
||||
return LittleEndian_getW(ain->code, addr);
|
||||
}
|
||||
|
||||
// Read argument N for the current instruction.
|
||||
static int32_t get_argument(int n)
|
||||
{
|
||||
return LittleEndian_getDW(ain->code, instr_ptr + 2 + n*4);
|
||||
}
|
||||
|
||||
// XXX: not strictly portable
|
||||
static float get_argument_float(int n)
|
||||
{
|
||||
union vm_value v;
|
||||
v.i = LittleEndian_getDW(ain->code, instr_ptr + 2 + n*4);
|
||||
return v.f;
|
||||
}
|
||||
|
||||
static union vm_value stack_peek(int n)
|
||||
{
|
||||
return stack[stack_ptr - (1 + n)];
|
||||
@@ -214,7 +211,7 @@ static union vm_value stack_pop(void)
|
||||
}
|
||||
|
||||
// Pop a reference off the stack, returning the address of the referenced object.
|
||||
static union vm_value *stack_pop_ref(void)
|
||||
static union vm_value *stack_pop_var(void)
|
||||
{
|
||||
int32_t page_index = stack_pop().i;
|
||||
int32_t heap_index = stack_pop().i;
|
||||
@@ -233,6 +230,11 @@ static struct string *stack_peek_string(int n)
|
||||
return heap[stack_peek(n).i].s;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -241,47 +243,6 @@ static int32_t alloc_page(int nr_vars)
|
||||
return slot;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy NR_ARGS values from the page SRC to the page DST, recursively.
|
||||
*/
|
||||
static void copy_page(union vm_value *dst, union vm_value *src, struct ain_variable *vars, int nr_args)
|
||||
{
|
||||
for (int i = 0; i < nr_args; i++) {
|
||||
int32_t slot;
|
||||
struct ain_struct *s;
|
||||
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];
|
||||
slot = alloc_page(s->nr_members);
|
||||
copy_page(heap[slot].page, heap[src[i].i].page, s->members, s->nr_members);
|
||||
break;
|
||||
default:
|
||||
dst[i] = src[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void init_page(union vm_value *dst, struct ain_variable *vars, int nr_vars)
|
||||
{
|
||||
for (int i = 0; i < nr_vars; i++) {
|
||||
int32_t slot;
|
||||
switch (vars[i].data_type) {
|
||||
case AIN_STRING:
|
||||
slot = heap_alloc_slot(VM_STRING);
|
||||
heap[slot].s = NULL;
|
||||
dst[i].i = slot;
|
||||
break;
|
||||
default:
|
||||
dst[i].i = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void delete_page(union vm_value *page, struct ain_variable *vars, int nr_vars)
|
||||
{
|
||||
for (int i = 0; i < nr_vars; i++) {
|
||||
@@ -301,6 +262,12 @@ static void delete_page(union vm_value *page, struct ain_variable *vars, int nr_
|
||||
}
|
||||
}
|
||||
|
||||
static struct string EMPTY_STRING = {
|
||||
.literal = true,
|
||||
.size = 0,
|
||||
.text = ""
|
||||
};
|
||||
|
||||
static int create_struct(int no)
|
||||
{
|
||||
struct ain_struct *s = &ain->structures[no];
|
||||
@@ -310,7 +277,7 @@ static int create_struct(int no)
|
||||
switch (s->members[i].data_type) {
|
||||
case AIN_STRING:
|
||||
memb = heap_alloc_slot(VM_STRING);
|
||||
heap[memb].s = make_string("", 0);
|
||||
heap[memb].s = &EMPTY_STRING;
|
||||
heap[slot].page[i].i = memb;
|
||||
break;
|
||||
case AIN_STRUCT:
|
||||
@@ -350,10 +317,27 @@ static void function_call(int32_t no)
|
||||
|
||||
// create local page
|
||||
heap[page_slot].page = page_stack + new_pp;
|
||||
memset(page_stack + new_pp + f->nr_args, 0, f->nr_vars - f->nr_args);
|
||||
copy_page(page_stack + new_pp, stack + (stack_ptr - f->nr_args), f->vars, f->nr_args);
|
||||
init_page(page_stack + new_pp + f->nr_args, f->vars + f->nr_args, f->nr_vars - f->nr_args);
|
||||
stack_ptr -= f->nr_args;
|
||||
// pop arguments, store in local page
|
||||
for (int i = f->nr_args - 1; i >= 0; i--) {
|
||||
page_stack[new_pp + i] = stack_pop();
|
||||
}
|
||||
// initialize local variables
|
||||
for (int i = f->nr_args; i < f->nr_vars; i++) {
|
||||
int slot;
|
||||
switch (f->vars[i].data_type) {
|
||||
case AIN_STRING:
|
||||
slot = heap_alloc_slot(VM_STRING);
|
||||
heap[slot].s = NULL;
|
||||
page_stack[new_pp + i].i = slot;
|
||||
break;
|
||||
case AIN_STRUCT:
|
||||
page_stack[new_pp + i].i = -1;
|
||||
break;
|
||||
default:
|
||||
page_stack[new_pp + i].i = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// update stack/instruction pointers
|
||||
page_ptr = new_pp;
|
||||
@@ -430,22 +414,20 @@ static void execute_instruction(int16_t opcode)
|
||||
break;
|
||||
case REF:
|
||||
// Dereference a reference to a value.
|
||||
index = stack_pop_ref()[0].i;
|
||||
heap_ref(index);
|
||||
index = stack_pop_var()[0].i;
|
||||
stack_push(index);
|
||||
break;
|
||||
case REFREF:
|
||||
//case S_REFREF: // ???
|
||||
// Dereference a reference to a reference.
|
||||
ref = stack_pop_ref();
|
||||
ref = stack_pop_var();
|
||||
stack_push(ref[0].i);
|
||||
stack_push(ref[1].i);
|
||||
break;
|
||||
case S_REF:
|
||||
// Dereference a reference to a string
|
||||
index = stack_pop_ref()->i;
|
||||
heap_ref(index);
|
||||
stack_push(index);
|
||||
index = stack_pop_var()->i;
|
||||
stack_push_string(string_dup(heap[index].s));
|
||||
break;
|
||||
case DUP:
|
||||
// A -> AA
|
||||
@@ -488,32 +470,14 @@ static void execute_instruction(int16_t opcode)
|
||||
case ASSIGN:
|
||||
case F_ASSIGN:
|
||||
val = stack_pop();
|
||||
stack_pop_ref()[0] = val;
|
||||
stack_pop_var()[0] = val;
|
||||
stack_push(val);
|
||||
break;
|
||||
case SH_GLOBALREF: // VARNO
|
||||
index = get_argument(0);
|
||||
stack_push(global_get(index));
|
||||
switch (global_type(index)) {
|
||||
case AIN_STRING:
|
||||
case AIN_STRUCT:
|
||||
heap_ref(global_get(index));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
stack_push(global_get(get_argument(0)));
|
||||
break;
|
||||
case SH_LOCALREF: // VARNO
|
||||
index = get_argument(0);
|
||||
stack_push(local_get(index));
|
||||
switch (local_type(index)) {
|
||||
case AIN_STRING:
|
||||
case AIN_STRUCT:
|
||||
heap_ref(local_get(index));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
stack_push(local_get(get_argument(0)));
|
||||
break;
|
||||
case SH_LOCALASSIGN: // VARNO, VALUE
|
||||
local_set(get_argument(0), get_argument(1));
|
||||
@@ -527,9 +491,9 @@ static void execute_instruction(int16_t opcode)
|
||||
local_set(index, local_get(index)-1);
|
||||
break;
|
||||
case SH_LOCALDELETE:
|
||||
if ((index = local_get(get_argument(0)))) {
|
||||
if ((index = local_get(get_argument(0))) != -1) {
|
||||
heap_unref(index);
|
||||
local_set(get_argument(0), 0);
|
||||
local_set(get_argument(0), -1);
|
||||
}
|
||||
break;
|
||||
case SH_LOCALCREATE: // VARNO, STRUCTNO
|
||||
@@ -652,49 +616,49 @@ static void execute_instruction(int16_t opcode)
|
||||
// +=, -=, etc.
|
||||
case PLUSA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i += v);
|
||||
stack_push(stack_pop_var()[0].i += v);
|
||||
break;
|
||||
case MINUSA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i -= v);
|
||||
stack_push(stack_pop_var()[0].i -= v);
|
||||
break;
|
||||
case MULA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i *= v);
|
||||
stack_push(stack_pop_var()[0].i *= v);
|
||||
break;
|
||||
case DIVA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i /= v);
|
||||
stack_push(stack_pop_var()[0].i /= v);
|
||||
break;
|
||||
case MODA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i %= v);
|
||||
stack_push(stack_pop_var()[0].i %= v);
|
||||
break;
|
||||
case ANDA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i &= v);
|
||||
stack_push(stack_pop_var()[0].i &= v);
|
||||
break;
|
||||
case ORA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i |= v);
|
||||
stack_push(stack_pop_var()[0].i |= v);
|
||||
break;
|
||||
case XORA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i ^= v);
|
||||
stack_push(stack_pop_var()[0].i ^= v);
|
||||
break;
|
||||
case LSHIFTA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i <<= v);
|
||||
stack_push(stack_pop_var()[0].i <<= v);
|
||||
break;
|
||||
case RSHIFTA:
|
||||
v = stack_pop().i;
|
||||
stack_push(stack_pop_ref()[0].i >>= v);
|
||||
stack_push(stack_pop_var()[0].i >>= v);
|
||||
break;
|
||||
case INC:
|
||||
stack_pop_ref()[0].i++;
|
||||
stack_pop_var()[0].i++;
|
||||
break;
|
||||
case DEC:
|
||||
stack_pop_ref()[0].i--;
|
||||
stack_pop_var()[0].i--;
|
||||
break;
|
||||
case ITOB:
|
||||
stack_set(0, !!stack_peek(0).i);
|
||||
@@ -756,19 +720,24 @@ static void execute_instruction(int16_t opcode)
|
||||
// --- Strings ---
|
||||
//
|
||||
case S_ASSIGN: // A = B
|
||||
b = stack_pop().i;
|
||||
a = stack_peek(0).i;
|
||||
b = stack_peek(0).i;
|
||||
a = stack_peek(1).i;
|
||||
if (heap[a].s) {
|
||||
free_string(heap[a].s);
|
||||
}
|
||||
heap[a].s = string_dup(heap[b].s);
|
||||
heap_unref(b);
|
||||
// remove A from the stack, but leave B
|
||||
stack_set(1, b);
|
||||
stack_pop();
|
||||
break;
|
||||
case S_PLUSA2:
|
||||
b = stack_pop().i;
|
||||
a = stack_peek(0).i;
|
||||
b = stack_peek(0).i;
|
||||
a = stack_peek(1).i;
|
||||
string_append(&heap[a].s, heap[b].s);
|
||||
heap_unref(b);
|
||||
stack_pop();
|
||||
stack_pop();
|
||||
stack_push_string(string_dup(heap[a].s));
|
||||
break;
|
||||
case S_ADD:
|
||||
b = stack_pop().i;
|
||||
@@ -814,11 +783,11 @@ static void execute_instruction(int16_t opcode)
|
||||
stack_push(v);
|
||||
break;
|
||||
case S_LENGTH:
|
||||
a = stack_pop_ref()->i;
|
||||
a = stack_pop_var()->i;
|
||||
stack_push(sjis_count_char(heap[a].s->text));
|
||||
break;
|
||||
case S_LENGTHBYTE:
|
||||
a = stack_pop_ref()->i;
|
||||
a = stack_pop_var()->i;
|
||||
stack_push(heap[a].s->size);
|
||||
break;
|
||||
case S_EMPTY:
|
||||
@@ -829,6 +798,7 @@ static void execute_instruction(int16_t opcode)
|
||||
case S_FIND:
|
||||
v = string_find(stack_peek_string(1), stack_peek_string(0));
|
||||
heap_unref(stack_pop().i);
|
||||
heap_unref(stack_pop().i);
|
||||
stack_push(v);
|
||||
break;
|
||||
case S_GETPART:
|
||||
@@ -841,20 +811,17 @@ static void execute_instruction(int16_t opcode)
|
||||
//case S_PUSHBACK: // ???
|
||||
case S_PUSHBACK2:
|
||||
v = stack_pop().i;
|
||||
string_push_back(&heap[stack_peek(0).i].s, v);
|
||||
heap_unref(stack_pop().i);
|
||||
string_push_back(&heap[stack_pop().i].s, v);
|
||||
break;
|
||||
//case S_POPBACK: // ???
|
||||
case S_POPBACK2:
|
||||
string_pop_back(stack_peek_string(0));
|
||||
heap_unref(stack_pop().i);
|
||||
string_pop_back(stack_pop_string());
|
||||
break;
|
||||
//case S_ERASE: // ???
|
||||
case S_ERASE2:
|
||||
b = stack_pop().i; // ???
|
||||
a = stack_pop().i; // index
|
||||
string_erase(stack_peek_string(0), a);
|
||||
heap_unref(stack_pop().i);
|
||||
string_erase(stack_pop_string(), a);
|
||||
break;
|
||||
case I_STRING:
|
||||
stack_push_string(integer_to_string(stack_pop().i));
|
||||
|
||||
Reference in New Issue
Block a user