Files
nunuhara_xsystem4/src/page.c
T
kichikuou 7dc6760928 Do not call destructors for structs passed by value to HLL functions
Some classes in Dungeons & Dolls serialize themselves using
`File.Write(this)` in their destructors. Because `File.Write` takes the
argument by value, a copy of `this` is created. If the destructor is
called on the copied `this`, it falls into an an infinite loop.
2025-07-13 13:48:30 +09:00

840 lines
22 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, bool call_dtor)
{
switch (type) {
case AIN_STRING:
case AIN_STRUCT:
case AIN_DELEGATE:
case AIN_ARRAY_TYPE:
case AIN_REF_TYPE:
if (v.i == -1)
break;
if (call_dtor)
heap_unref(v.i);
else
exit_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, true);
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), true);
}
}
void delete_page(int slot)
{
struct page *page = heap_get_page(slot);
if (!page)
return;
if (page->type == STRUCT_PAGE) {
delete_struct(page->index, 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);
dst->array = src->array;
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);
heap_set_page(slot, alloc_page(STRUCT_PAGE, no, s->nr_members));
for (int i = 0; i < s->nr_members; i++) {
if (s->members[i].type.data == AIN_STRUCT) {
heap[slot].page->values[i].i = alloc_struct(s->members[i].type.struc);
} else {
heap[slot].page->values[i] = variable_initval(s->members[i].type.data);
}
}
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 delete_struct(int no, int slot)
{
struct ain_struct *s = &ain->structures[no];
if (s->destructor > 0) {
vm_call(s->destructor, 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), true);
}
}
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, true);
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), true);
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 array_compare_int(const void *_a, const void *_b)
{
union vm_value a = *((union vm_value*)_a);
union vm_value b = *((union vm_value*)_b);
return (a.i > b.i) - (a.i < b.i);
}
static int array_compare_float(const void *_a, const void *_b)
{
union vm_value a = *((union vm_value*)_a);
union vm_value b = *((union vm_value*)_b);
return (a.f > b.f) - (a.f < b.f);
}
static int array_compare_string(const void *_a, const void *_b)
{
union vm_value a = *((union vm_value*)_a);
union vm_value b = *((union vm_value*)_b);
return strcmp(heap_get_string(a.i)->text, heap_get_string(b.i)->text);
}
// Used for stable sorting arrays with qsort()
struct sortable {
union vm_value v;
int index;
};
static int current_sort_function;
static int array_compare_custom(const void *_a, const void *_b)
{
const struct sortable *a = _a;
const struct sortable *b = _b;
stack_push(a->v);
stack_push(b->v);
vm_call(current_sort_function, -1);
int d = stack_pop().i;
return d ? d : a->index - b->index;
}
void array_sort(struct page *page, int compare_fno)
{
if (!page)
return;
if (compare_fno) {
struct sortable *values = xcalloc(page->nr_vars, sizeof(struct sortable));
for (int i = 0; i < page->nr_vars; i++) {
values[i].v = page->values[i];
values[i].index = i;
}
current_sort_function = compare_fno;
qsort(values, page->nr_vars, sizeof(struct sortable), array_compare_custom);
for (int i = 0; i < page->nr_vars; i++) {
page->values[i] = values[i].v;
}
free(values);
} else {
switch (page->a_type) {
case AIN_ARRAY_INT:
case AIN_ARRAY_LONG_INT:
qsort(page->values, page->nr_vars, sizeof(union vm_value), array_compare_int);
break;
case AIN_ARRAY_FLOAT:
qsort(page->values, page->nr_vars, sizeof(union vm_value), array_compare_float);
break;
case AIN_ARRAY_STRING:
qsort(page->values, page->nr_vars, sizeof(union vm_value), array_compare_string);
break;
default:
VM_ERROR("A_SORT(&NULL) called on ain_data_type %d", page->a_type);
}
}
}
static int current_sort_member;
static int array_compare_member(const void *_a, const void *_b)
{
const struct sortable *a = _a;
const struct sortable *b = _b;
int32_t a_i = heap_get_page(a->v.i)->values[current_sort_member].i;
int32_t b_i = heap_get_page(b->v.i)->values[current_sort_member].i;
int d = (a_i > b_i) - (a_i < b_i);
return d ? d : a->index - b->index;
}
static int array_compare_member_string(const void *_a, const void *_b)
{
const struct sortable *a = _a;
const struct sortable *b = _b;
int32_t a_i = heap_get_page(a->v.i)->values[current_sort_member].i;
int32_t b_i = heap_get_page(b->v.i)->values[current_sort_member].i;
int d = strcmp(heap_get_string(a_i)->text, heap_get_string(b_i)->text);
return d ? d : a->index - b->index;
}
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");
struct ain_struct *s = &ain->structures[page->array.struct_type];
if (member_no < 0 || member_no >= s->nr_members)
VM_ERROR("A_SORT_MEM called with invalid member index");
struct sortable *values = xcalloc(page->nr_vars, sizeof(struct sortable));
for (int i = 0; i < page->nr_vars; i++) {
values[i].v = page->values[i];
values[i].index = i;
}
current_sort_member = member_no;
if (s->members[member_no].type.data == AIN_STRING)
qsort(values, page->nr_vars, sizeof(struct sortable), array_compare_member_string);
else
qsort(values, page->nr_vars, sizeof(struct sortable), array_compare_member);
for (int i = 0; i < page->nr_vars; i++) {
page->values[i] = values[i].v;
}
free(values);
}
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;
}
}
struct page *delegate_new_from_method(int obj, int fun)
{
struct page *page = alloc_page(DELEGATE_PAGE, 0, 3);
page->values[0].i = obj;
page->values[1].i = fun;
page->values[2].i = heap_get_seq(obj);
return page;
}
bool delegate_contains(struct page *dst, int obj, int fun)
{
if (!dst)
return false;
for (int i = 0; i < dst->nr_vars; i += 3) {
if (dst->values[i].i == obj &&
dst->values[i+1].i == fun &&
dst->values[i+2].i == heap_get_seq(obj))
return true;
}
return false;
}
struct page *delegate_append(struct page *dst, int obj, int fun)
{
if (!dst)
return delegate_new_from_method(obj, fun);
if (dst->type != DELEGATE_PAGE)
VM_ERROR("Not a delegate");
if (delegate_contains(dst, obj, fun))
return dst;
dst = xrealloc(dst, sizeof(struct page) + sizeof(union vm_value) * (dst->nr_vars + 3));
dst->values[dst->nr_vars+0].i = obj;
dst->values[dst->nr_vars+1].i = fun;
dst->values[dst->nr_vars+2].i = heap_get_seq(obj);
dst->nr_vars += 3;
return dst;
}
int delegate_numof(struct page *page)
{
if (!page)
return 0;
if (page->type != DELEGATE_PAGE)
VM_ERROR("Not a delegate");
// garbage collection
for (int i = 0; i < page->nr_vars; i += 3) {
if (heap_get_seq(page->values[i].i) != page->values[i+2].i) {
for (int j = i+3; j < page->nr_vars; j += 3) {
page->values[j-3].i = page->values[j+0].i;
page->values[j-2].i = page->values[j+1].i;
page->values[j-1].i = page->values[j+2].i;
}
page->nr_vars -= 3;
i -= 3;
}
}
return page->nr_vars / 3;
}
void delegate_erase(struct page *page, int obj, int fun)
{
if (!page)
return;
if (page->type != DELEGATE_PAGE)
VM_ERROR("Not a delegate");
for (int i = 0; i < page->nr_vars; i += 3) {
if (page->values[i].i == obj && page->values[i+1].i == fun) {
for (int j = i+3; j < page->nr_vars; j += 3) {
page->values[j-3].i = page->values[j+0].i;
page->values[j-2].i = page->values[j+1].i;
page->values[j-1].i = page->values[j+2].i;
}
page->nr_vars -= 3;
break;
}
}
}
struct page *delegate_plusa(struct page *dst, struct page *add)
{
if (!add)
return dst;
if ((dst && dst->type != DELEGATE_PAGE) || add->type != DELEGATE_PAGE)
VM_ERROR("Not a delegate");
for (int i = 0; i < add->nr_vars; i += 3) {
if (heap_get_seq(add->values[i].i) == add->values[i+2].i)
dst = delegate_append(dst, add->values[i].i, add->values[i+1].i);
}
return dst;
}
struct page *delegate_minusa(struct page *dst, struct page *minus)
{
if (!dst)
return NULL;
if (!minus)
return dst;
if (dst->type != DELEGATE_PAGE || minus->type != DELEGATE_PAGE)
VM_ERROR("Not a delegate");
for (int i = 0; i < minus->nr_vars; i += 3) {
if (heap_get_seq(minus->values[i].i) == minus->values[i+2].i)
delegate_erase(dst, minus->values[i].i, minus->values[i+1].i);
}
return dst;
}
struct page *delegate_clear(struct page *page)
{
if (!page)
return NULL;
if (page->type != DELEGATE_PAGE)
VM_ERROR("Not a delegate");
for (int i = 0; i < page->nr_vars; i += 3) {
page->values[i].i = -1;
page->values[i+1].i = -1;
page->values[i+2].i = 0;
}
page->index = 0;
page->nr_vars = 0;
return page;
}
bool delegate_get(struct page *page, int i, int *obj_out, int *fun_out)
{
if (!page)
return false;
if (page->type != DELEGATE_PAGE)
VM_ERROR("Not a delegate");
while (i*3 < page->nr_vars) {
if (heap_get_seq(page->values[i*3].i) == page->values[i*3+2].i) {
*obj_out = page->values[i*3].i;
*fun_out = page->values[i*3+1].i;
return true;
}
for (int j = (i + 1) * 3; j < page->nr_vars; j += 3) {
page->values[j-3].i = page->values[j+0].i;
page->values[j-2].i = page->values[j+1].i;
page->values[j-1].i = page->values[j+2].i;
}
page->nr_vars -= 3;
}
return false;
}