Files
nunuhara_xsystem4/src/page.c
T
Nunuhara Cabbage 4b1257fc13 Fix delegate memory management
Although delegate pages store references to objects on the heap,
adding/removing objects from a delegate does not affect their reference
counts. When an object that is a member of a delegate is deleted, it is
automatically removed from the delegate.

In practice, this means that every object needs to keep a list of
delegates that it belongs to. This increases the size of the page header
on 64 bit systems, but not by much since the additional metadata can be
stored in a union with the array-specific metadata.
2023-04-13 16:38:13 -07:00

791 lines
20 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 <string.h>
#include "system4.h"
#include "system4/ain.h"
#include "system4/string.h"
#include "vm.h"
#include "vm/heap.h"
#include "vm/page.h"
#define NR_CACHES 8
#define CACHE_SIZE 64
static const char *pagetype_strtab[] = {
[GLOBAL_PAGE] = "GLOBAL_PAGE",
[LOCAL_PAGE] = "LOCAL_PAGE",
[STRUCT_PAGE] = "STRUCT_PAGE",
[ARRAY_PAGE] = "ARRAY_PAGE",
[DELEGATE_PAGE] = "DELEGATE_PAGE",
};
const char *pagetype_string(enum page_type type)
{
if (type < NR_PAGE_TYPES)
return pagetype_strtab[type];
return "INVALID PAGE TYPE";
}
struct page_cache {
unsigned int cached;
struct page *pages[CACHE_SIZE];
};
struct page_cache page_cache[NR_CACHES];
struct page *_alloc_page(int nr_vars)
{
int cache_nr = nr_vars - 1;
if (cache_nr >= 0 && cache_nr < NR_CACHES && page_cache[cache_nr].cached) {
struct page *page = page_cache[cache_nr].pages[--page_cache[cache_nr].cached];
memset(page->values, 0, sizeof(union vm_value) * nr_vars);
return page;
}
return xcalloc(1, sizeof(struct page) + sizeof(union vm_value) * nr_vars);
}
void free_page(struct page *page)
{
int cache_no = page->nr_vars - 1;
if (cache_no < 0 || cache_no >= NR_CACHES || page_cache[cache_no].cached >= CACHE_SIZE) {
free(page);
return;
}
page_cache[cache_no].pages[page_cache[cache_no].cached++] = page;
}
struct page *alloc_page(enum page_type type, int type_index, int nr_vars)
{
struct page *page = _alloc_page(nr_vars);
page->type = type;
page->index = type_index;
page->nr_vars = nr_vars;
return page;
}
union vm_value variable_initval(enum ain_data_type type)
{
int slot;
switch (type) {
case AIN_STRING:
slot = heap_alloc_slot(VM_STRING);
heap[slot].s = string_ref(&EMPTY_STRING);
return (union vm_value) { .i = slot };
case AIN_STRUCT:
case AIN_REF_TYPE:
return (union vm_value) { .i = -1 };
case AIN_ARRAY_TYPE:
case AIN_DELEGATE:
slot = heap_alloc_slot(VM_PAGE);
heap_set_page(slot, NULL);
return (union vm_value) { .i = slot };
default:
return (union vm_value) { .i = 0 };
}
}
void variable_fini(union vm_value v, enum ain_data_type type)
{
switch (type) {
case AIN_STRING:
case AIN_STRUCT:
case AIN_DELEGATE:
case AIN_ARRAY_TYPE:
case AIN_REF_TYPE:
if (v.i == -1)
break;
heap_unref(v.i);
break;
default:
break;
}
}
enum ain_data_type array_type(enum ain_data_type type)
{
switch (type) {
case AIN_ARRAY_INT:
case AIN_REF_ARRAY_INT:
return AIN_INT;
case AIN_ARRAY_FLOAT:
case AIN_REF_ARRAY_FLOAT:
return AIN_FLOAT;
case AIN_ARRAY_STRING:
case AIN_REF_ARRAY_STRING:
return AIN_STRING;
case AIN_ARRAY_STRUCT:
case AIN_REF_ARRAY_STRUCT:
return AIN_STRUCT;
case AIN_ARRAY_FUNC_TYPE:
case AIN_REF_ARRAY_FUNC_TYPE:
return AIN_FUNC_TYPE;
case AIN_ARRAY_BOOL:
case AIN_REF_ARRAY_BOOL:
return AIN_BOOL;
case AIN_ARRAY_LONG_INT:
case AIN_REF_ARRAY_LONG_INT:
return AIN_LONG_INT;
case AIN_ARRAY_DELEGATE:
case AIN_REF_ARRAY_DELEGATE:
return AIN_DELEGATE;
default:
WARNING("Unknown/invalid array type: %d", type);
return type;
}
}
enum ain_data_type variable_type(struct page *page, int varno, int *struct_type, int *array_rank)
{
switch (page->type) {
case GLOBAL_PAGE:
if (struct_type)
*struct_type = ain->globals[varno].type.struc;
if (array_rank)
*array_rank = ain->globals[varno].type.rank;
return ain->globals[varno].type.data;
case LOCAL_PAGE:
if (struct_type)
*struct_type = ain->functions[page->index].vars[varno].type.struc;
if (array_rank)
*array_rank = ain->functions[page->index].vars[varno].type.rank;
return ain->functions[page->index].vars[varno].type.data;
case STRUCT_PAGE:
if (struct_type)
*struct_type = ain->structures[page->index].members[varno].type.struc;
if (array_rank)
*array_rank = ain->structures[page->index].members[varno].type.rank;
return ain->structures[page->index].members[varno].type.data;
case ARRAY_PAGE:
if (struct_type)
*struct_type = page->array.struct_type;
if (array_rank)
*array_rank = page->array.rank - 1;
return page->array.rank > 1 ? page->a_type : array_type(page->a_type);
case DELEGATE_PAGE:
// XXX: we return void here because objects in a delegate page aren't
// reference counted
return AIN_VOID;
}
return AIN_VOID;
}
void variable_set(struct page *page, int varno, enum ain_data_type type, union vm_value val)
{
variable_fini(page->values[varno], type);
page->values[varno] = val;
}
void delete_page_vars(struct page *page)
{
for (int i = 0; i < page->nr_vars; i++) {
variable_fini(page->values[i], variable_type(page, i, NULL, NULL));
}
}
static void delegate_delete_object(int dg_i, int obj);
void delete_page(int slot)
{
struct page *page = heap_get_page(slot);
if (!page)
return;
if (page->type == STRUCT_PAGE) {
struct ain_struct *s = &ain->structures[page->index];
if (s->destructor > 0) {
vm_call(s->destructor, slot);
}
// remove the object from all delegates
for (int i = 0; i < page->struc.nr_delegates; i++) {
delegate_delete_object(page->struc.delegates[i], slot);
}
free(page->struc.delegates);
page->struc.delegates = NULL;
page->struc.nr_delegates = 0;
}
if (page->type == DELEGATE_PAGE) {
for (int i = 0; i < page->nr_vars; i += 2) {
if (page->values[i].i < 0)
continue;
struct_unregister_delegate(page->values[i].i, slot);
}
}
delete_page_vars(page);
free_page(page);
}
/*
* Recursively copy a page.
*/
struct page *copy_page(struct page *src)
{
if (!src)
return NULL;
struct page *dst = alloc_page(src->type, src->index, src->nr_vars);
if (src->type == ARRAY_PAGE) {
dst->array = src->array;
} else if (src->type == STRUCT_PAGE) {
dst->struc.delegates = NULL;
dst->struc.nr_delegates = 0;
}
for (int i = 0; i < src->nr_vars; i++) {
dst->values[i] = vm_copy(src->values[i], variable_type(src, i, NULL, NULL));
}
return dst;
}
int alloc_struct(int no)
{
struct ain_struct *s = &ain->structures[no];
int slot = heap_alloc_slot(VM_PAGE);
struct page *page = alloc_page(STRUCT_PAGE, no, s->nr_members);
page->struc.delegates = NULL;
page->struc.nr_delegates = 0;
for (int i = 0; i < s->nr_members; i++) {
if (s->members[i].type.data == AIN_STRUCT) {
page->values[i].i = alloc_struct(s->members[i].type.struc);
} else {
page->values[i] = variable_initval(s->members[i].type.data);
}
}
heap_set_page(slot, page);
return slot;
}
void init_struct(int no, int slot)
{
struct ain_struct *s = &ain->structures[no];
for (int i = 0; i < s->nr_members; i++) {
if (s->members[i].type.data == AIN_STRUCT) {
init_struct(s->members[i].type.struc, heap[slot].page->values[i].i);
}
}
if (s->constructor > 0) {
vm_call(s->constructor, slot);
}
}
void create_struct(int no, union vm_value *var)
{
var->i = alloc_struct(no);
init_struct(no, var->i);
}
static enum ain_data_type unref_array_type(enum ain_data_type type)
{
switch (type) {
case AIN_REF_ARRAY_INT: return AIN_ARRAY_INT;
case AIN_REF_ARRAY_FLOAT: return AIN_ARRAY_FLOAT;
case AIN_REF_ARRAY_STRING: return AIN_ARRAY_STRING;
case AIN_REF_ARRAY_STRUCT: return AIN_ARRAY_STRUCT;
case AIN_REF_ARRAY_FUNC_TYPE: return AIN_ARRAY_FUNC_TYPE;
case AIN_REF_ARRAY_BOOL: return AIN_ARRAY_BOOL;
case AIN_REF_ARRAY_LONG_INT: return AIN_ARRAY_LONG_INT;
case AIN_REF_ARRAY_DELEGATE: return AIN_ARRAY_DELEGATE;
case AIN_ARRAY_TYPE: return type;
default: VM_ERROR("Attempt to array allocate non-array type");
}
}
struct page *alloc_array(int rank, union vm_value *dimensions, enum ain_data_type data_type, int struct_type, bool init_structs)
{
if (rank < 1)
return NULL;
data_type = unref_array_type(data_type);
enum ain_data_type type = array_type(data_type);
struct page *page = alloc_page(ARRAY_PAGE, data_type, dimensions->i);
page->array.struct_type = struct_type;
page->array.rank = rank;
for (int i = 0; i < dimensions->i; i++) {
if (rank == 1) {
if (type == AIN_STRUCT && init_structs)
create_struct(struct_type, &page->values[i]);
else
page->values[i] = variable_initval(type);
} else {
struct page *child = alloc_array(rank - 1, dimensions + 1, data_type, struct_type, init_structs);
int slot = heap_alloc_slot(VM_PAGE);
heap_set_page(slot, child);
page->values[i].i = slot;
}
}
return page;
}
struct page *realloc_array(struct page *src, int rank, union vm_value *dimensions, enum ain_data_type data_type, int struct_type, bool init_structs)
{
if (rank < 1)
VM_ERROR("Tried to allocate 0-rank array");
if (!src && !dimensions->i)
return NULL;
if (!src)
return alloc_array(rank, dimensions, data_type, struct_type, init_structs);
if (src->type != ARRAY_PAGE)
VM_ERROR("Not an array");
if (src->array.rank != rank)
VM_ERROR("Attempt to reallocate array with different rank");
if (!dimensions->i) {
delete_page_vars(src);
free_page(src);
return NULL;
}
// if shrinking array, unref orphaned children
if (dimensions->i < src->nr_vars) {
for (int i = dimensions->i; i < src->nr_vars; i++) {
variable_fini(src->values[i], variable_type(src, i, NULL, NULL));
}
}
src = xrealloc(src, sizeof(struct page) + sizeof(union vm_value) * dimensions->i);
// if growing array, init new children
enum ain_data_type type = array_type(data_type);
if (dimensions->i > src->nr_vars) {
for (int i = src->nr_vars; i < dimensions->i; i++) {
if (rank == 1) {
if (type == AIN_STRUCT && init_structs)
create_struct(struct_type, &src->values[i]);
else
src->values[i] = variable_initval(type);
} else {
struct page *child = alloc_array(rank - 1, dimensions + 1, data_type, struct_type, init_structs);
int slot = heap_alloc_slot(VM_PAGE);
heap_set_page(slot, child);
src->values[i].i = slot;
}
}
}
src->nr_vars = dimensions->i;
return src;
}
int array_numof(struct page *page, int rank)
{
if (!page)
return 0;
if (rank < 1 || rank > page->array.rank)
return 0;
if (rank == 1) {
return page->nr_vars;
}
return array_numof(heap[page->values[0].i].page, rank - 1);
}
static bool array_index_ok(struct page *array, int i)
{
return i >= 0 && i < array->nr_vars;
}
void array_copy(struct page *dst, int dst_i, struct page *src, int src_i, int n)
{
if (n <= 0)
return;
if (!dst || !src)
VM_ERROR("Array is NULL");
if (dst->type != ARRAY_PAGE || src->type != ARRAY_PAGE)
VM_ERROR("Not an array");
if (!array_index_ok(dst, dst_i) || !array_index_ok(src, src_i))
VM_ERROR("Out of bounds array access");
if (!array_index_ok(dst, dst_i + n - 1) || !array_index_ok(src, src_i + n - 1))
VM_ERROR("Out of bounds array access");
if (dst->array.rank != 1 || src->array.rank != 1)
VM_ERROR("Tried to copy to/from a multi-dimensional array");
if (dst->a_type != src->a_type)
VM_ERROR("Array types do not match");
for (int i = 0; i < n; i++) {
enum ain_data_type type = array_type(dst->a_type);
variable_set(dst, dst_i+i, type, vm_copy(src->values[src_i+i], type));
}
}
int array_fill(struct page *dst, int dst_i, int n, union vm_value v)
{
if (!dst)
return 0;
if (dst->type != ARRAY_PAGE)
VM_ERROR("Not an array");
// clamp (dst_i, dst_i+n) to range of array
if (dst_i < 0) {
n += dst_i;
dst_i = 0;
}
if (dst_i >= dst->nr_vars)
return 0;
if (dst_i + n >= dst->nr_vars)
n = dst->nr_vars - dst_i;
enum ain_data_type type = array_type(dst->a_type);
for (int i = 0; i < n; i++) {
variable_set(dst, dst_i+i, type, vm_copy(v, type));
}
variable_fini(v, type);
return n;
}
struct page *array_pushback(struct page *dst, union vm_value v, enum ain_data_type data_type, int struct_type)
{
if (dst) {
if (dst->type != ARRAY_PAGE)
VM_ERROR("Not an array");
if (dst->array.rank != 1)
VM_ERROR("Tried pushing to a multi-dimensional array");
int index = dst->nr_vars;
union vm_value dims[1] = { (union vm_value) { .i = index + 1 } };
dst = realloc_array(dst, 1, dims, dst->a_type, dst->array.struct_type, false);
variable_set(dst, index, array_type(data_type), v);
} else {
union vm_value dims[1] = { (union vm_value) { .i = 1 } };
dst = alloc_array(1, dims, data_type, struct_type, false);
variable_set(dst, 0, array_type(data_type), v);
}
return dst;
}
struct page *array_popback(struct page *dst)
{
if (!dst)
return NULL;
if (dst->type != ARRAY_PAGE)
VM_ERROR("Not an array");
if (dst->array.rank != 1)
VM_ERROR("Tried popping from a multi-dimensional array");
union vm_value dims[1] = { (union vm_value) { .i = dst->nr_vars - 1 } };
dst = realloc_array(dst, 1, dims, dst->a_type, dst->array.struct_type, false);
return dst;
}
struct page *array_erase(struct page *page, int i, bool *success)
{
*success = false;
if (!page)
return NULL;
if (page->type != ARRAY_PAGE)
VM_ERROR("Not an array");
if (page->array.rank != 1)
VM_ERROR("Tried erasing from a multi-dimensional array");
if (!array_index_ok(page, i))
return page;
// if array will be empty...
if (page->nr_vars == 1) {
delete_page_vars(page);
free_page(page);
*success = true;
return NULL;
}
// delete variable, shift subsequent variables, then realloc page
variable_fini(page->values[i], array_type(page->a_type));
for (int j = i + 1; j < page->nr_vars; j++) {
page->values[j-1] = page->values[j];
}
page->nr_vars--;
page = xrealloc(page, sizeof(struct page) + sizeof(union vm_value) * page->nr_vars);
*success = true;
return page;
}
struct page *array_insert(struct page *page, int i, union vm_value v, enum ain_data_type data_type, int struct_type)
{
if (!page) {
return array_pushback(NULL, v, data_type, struct_type);
}
if (page->type != ARRAY_PAGE)
VM_ERROR("Not an array");
if (page->array.rank != 1)
VM_ERROR("Tried inserting into a multi-dimensional array");
// NOTE: you cannot insert at the end of an array due to how i is clamped
if (i >= page->nr_vars)
i = page->nr_vars - 1;
if (i < 0)
i = 0;
page->nr_vars++;
page = xrealloc(page, sizeof(struct page) + sizeof(union vm_value) * page->nr_vars);
for (int j = page->nr_vars - 1; j > i; j--) {
page->values[j] = page->values[j-1];
}
page->values[i] = v;
return page;
}
static int current_sort_function;
static int array_compare(const void *_a, const void *_b)
{
union vm_value a = *((union vm_value*)_a);
union vm_value b = *((union vm_value*)_b);
if (!current_sort_function) {
return a.i - b.i;
}
stack_push(a);
stack_push(b);
vm_call(current_sort_function, -1);
return stack_pop().i;
}
void array_sort(struct page *page, int compare_fno)
{
if (!page)
return;
current_sort_function = compare_fno;
qsort(page->values, page->nr_vars, sizeof(union vm_value), array_compare);
}
static int current_sort_member;
static int array_compare_member(const void *_a, const void *_b)
{
union vm_value a_slot = *((union vm_value*)_a);
union vm_value b_slot = *((union vm_value*)_b);
struct page *a = heap_get_page(a_slot.i);
struct page *b = heap_get_page(b_slot.i);
return a->values[current_sort_member].i - b->values[current_sort_member].i;
}
void array_sort_mem(struct page *page, int member_no)
{
if (!page)
return;
if (page->type != ARRAY_PAGE || array_type(page->a_type) != AIN_STRUCT)
VM_ERROR("A_SORT_MEM called on something other than an array of structs");
current_sort_member = member_no;
qsort(page->values, page->nr_vars, sizeof(union vm_value), array_compare_member);
}
int array_find(struct page *page, int start, int end, union vm_value v, int compare_fno)
{
if (!page)
return -1;
start = max(start, 0);
end = min(end, page->nr_vars);
// if no compare function given, compare integer/string values
if (!compare_fno) {
if (array_type(page->a_type) == AIN_STRING) {
struct string *v_str = heap_get_string(v.i);
for (int i = start; i < end; i++) {
if (!strcmp(v_str->text, heap_get_string(page->values[i].i)->text))
return i;
}
} else {
for (int i = start; i < end; i++) {
if (page->values[i].i == v.i)
return i;
}
}
return -1;
}
for (int i = start; i < end; i++) {
stack_push(v);
stack_push(page->values[i]);
vm_call(compare_fno, -1);
if (stack_pop().i)
return i;
}
return -1;
}
void array_reverse(struct page *page)
{
if (!page)
return;
for (int start = 0, end = page->nr_vars-1; start < end; start++, end--) {
union vm_value tmp = page->values[start];
page->values[start] = page->values[end];
page->values[end] = tmp;
}
}
void struct_register_delegate(int obj, int dg_i)
{
struct page *page = heap_get_struct_page(obj);
// don't add duplicates
for (int i = 0; i < page->struc.nr_delegates; i++) {
if (page->struc.delegates[i] == obj)
return;
}
page->struc.delegates = xrealloc(page->struc.delegates,
(page->struc.nr_delegates + 1) * sizeof(int));
page->struc.delegates[page->struc.nr_delegates++] = dg_i;
}
void struct_unregister_delegate(int obj, int dg_i)
{
struct page *page = heap_get_struct_page(obj);
for (int i = 0; i < page->struc.nr_delegates; i++) {
if (page->struc.delegates[i] != dg_i)
continue;
if (page->struc.nr_delegates > 1) {
// swap last entry into delegates[i]
page->struc.delegates[i] = page->struc.delegates[page->struc.nr_delegates - 1];
}
page->struc.nr_delegates--;
return;
}
VM_ERROR("delegate is not registered to object");
}
static bool _delegate_contains(struct page *dst, int obj, int fun)
{
if (!dst)
return false;
for (int i = 0; i < dst->nr_vars; i += 2) {
if (dst->values[i].i == obj && dst->values[i+1].i == fun)
return true;
}
return false;
}
bool delegate_contains(int dg_i, int obj, int fun)
{
return _delegate_contains(heap_get_delegate_page(dg_i), obj, fun);
}
void delegate_append(int dg_i, int obj, int fun)
{
struct page *dg = heap_get_delegate_page(dg_i);
if (dg && _delegate_contains(dg, obj, fun))
return;
if (!dg) {
dg = alloc_page(DELEGATE_PAGE, 0, 2);
dg->values[0].i = obj;
dg->values[1].i = fun;
} else {
dg = xrealloc(dg, sizeof(struct page) + sizeof(union vm_value) * (dg->nr_vars + 2));
dg->values[dg->nr_vars+0].i = obj;
dg->values[dg->nr_vars+1].i = fun;
dg->nr_vars += 2;
}
if (obj >= 0)
struct_register_delegate(obj, dg_i);
heap_set_page(dg_i, dg);
}
int delegate_numof(int dg_i)
{
struct page *dg = heap_get_delegate_page(dg_i);
if (!dg)
return 0;
return dg->nr_vars / 2;
}
static void delegate_delete_object(int dg_i, int obj)
{
struct page *dg = heap_get_delegate_page(dg_i);
if (!dg) {
WARNING("Tried to delete object from empty delegate");
return;
}
for (int i = 0; i < dg->nr_vars; i += 2) {
if (dg->values[i].i != obj)
continue;
for (int j = i+2; j < dg->nr_vars; j += 2) {
dg->values[j-2].i = dg->values[j+0].i;
dg->values[j-1].i = dg->values[j+1].i;
}
dg->nr_vars -= 2;
i -= 2;
}
}
void delegate_erase(int dg_i, int obj, int fun)
{
struct page *dg = heap_get_delegate_page(dg_i);
if (!dg)
return;
for (int i = 0; i < dg->nr_vars; i += 2) {
if (dg->values[i].i != obj || dg->values[i+1].i != fun)
continue;
if (dg->values[i].i >= 0)
struct_unregister_delegate(obj, dg_i);
for (int j = i+2; j < dg->nr_vars; j += 2) {
dg->values[j-2].i = dg->values[j+0].i;
dg->values[j-1].i = dg->values[j+1].i;
}
dg->nr_vars -= 2;
break;
}
}
void delegate_plusa(int dg_i, int add_i)
{
struct page *add = heap_get_delegate_page(add_i);
if (!add)
return;
for (int i = 0; i < add->nr_vars; i += 2) {
delegate_append(dg_i, add->values[i].i, add->values[i+1].i);
}
}
void delegate_minusa(int dg_i, int minus_i)
{
struct page *minus = heap_get_delegate_page(minus_i);
if (!minus)
return;
for (int i = 0; i < minus->nr_vars; i += 2) {
delegate_erase(dg_i, minus->values[i].i, minus->values[i+1].i);
}
}
void delegate_clear(int dg_i)
{
struct page *page = heap_get_delegate_page(dg_i);
if (!page)
return;
for (int i = 0; i < page->nr_vars; i += 2) {
if (page->values[i].i >= 0)
struct_unregister_delegate(page->values[i].i, dg_i);
page->values[i+0].i = -1;
page->values[i+1].i = -1;
}
page->index = 0;
page->nr_vars = 0;
}
void delegate_get(int dg_i, int i, int *obj_out, int *fun_out)
{
struct page *dg = heap_get_delegate_page(dg_i);
if (i * 2 >= dg->nr_vars)
VM_ERROR("Invalid delegate index: %d", i);
*obj_out = dg->values[i*2+0].i;
*fun_out = dg->values[i*2+1].i;
}