Files
nunuhara_xsystem4/page.c
T
Nunuhara Cabbage 470c051124 Implement full suite of array instructions
All array instructions supported by the SDK compiler, anyway. There are
a few instructions that were added in later versions, like A_FIND and
A_REVERSE.
2019-10-28 20:31:50 -07:00

430 lines
11 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 "system4.h"
#include "ain.h"
#include "vm.h"
#include "vm_string.h"
#include "page.h"
#define NR_CACHES 8
#define CACHE_SIZE 64
static const char *pagetype_strtab[] = {
[LOCAL_PAGE] = "LOCAL_PAGE",
[STRUCT_PAGE] = "STRUCT_PAGE",
[ARRAY_PAGE] = "ARRAY_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 < NR_CACHES && page_cache[cache_nr].cached) {
return page_cache[cache_nr].pages[--page_cache[cache_nr].cached];
}
return xmalloc(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 >= 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:
return (union vm_value) { .i = -1 };
case AIN_ARRAY_TYPE:
slot = heap_alloc_slot(VM_PAGE);
heap[slot].page = 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_ARRAY_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: return AIN_INT;
case AIN_ARRAY_FLOAT: return AIN_FLOAT;
case AIN_ARRAY_STRING: return AIN_STRING;
case AIN_ARRAY_STRUCT: return AIN_STRUCT;
case AIN_ARRAY_FUNC_TYPE: return AIN_FUNC_TYPE;
case AIN_ARRAY_BOOL: return AIN_BOOL;
case AIN_ARRAY_LONG_INT: return AIN_LONG_INT;
case AIN_ARRAY_DELEGATE: return AIN_DELEGATE;
default: return type;
}
}
enum ain_data_type variable_type(struct page *page, int varno, enum ain_data_type *struct_type)
{
switch (page->type) {
case LOCAL_PAGE:
if (struct_type)
*struct_type = ain->functions[page->index].vars[varno].struct_type;
return ain->functions[page->index].vars[varno].data_type;
case STRUCT_PAGE:
if (struct_type)
*struct_type = ain->structures[page->index].members[varno].struct_type;
return ain->structures[page->index].members[varno].data_type;
case ARRAY_PAGE:
if (struct_type)
*struct_type = page->struct_type;
return page->rank > 1 ? page->a_type : array_type(page->a_type);
}
return AIN_VOID;
}
void delete_page(struct page *page)
{
for (int i = 0; i < page->nr_vars; i++) {
variable_fini(page->values[i], variable_type(page, i, NULL));
}
}
/*
* Recursively copy a page.
*/
struct page *copy_page(struct page *src)
{
struct page *dst = alloc_page(src->type, src->index, src->nr_vars);
dst->struct_type = src->struct_type;
dst->rank = src->rank;
for (int i = 0; i < src->nr_vars; i++) {
dst->values[i] = vm_copy(src->values[i], variable_type(src, i, NULL));
}
return dst;
}
void create_struct(int no, union vm_value *var)
{
struct ain_struct *s = &ain->structures[no];
int slot = heap_alloc_slot(VM_PAGE);
heap[slot].page = alloc_page(STRUCT_PAGE, no, 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 = string_ref(&EMPTY_STRING);
heap[slot].page->values[i].i = memb;
break;
case AIN_STRUCT:
create_struct(s->members[i].struct_type, &heap[slot].page->values[i]);
break;
default:
heap[slot].page->values[i].i = 0;
break;
}
}
if (s->constructor > 0) {
vm_call(s->constructor, slot);
}
var->i = slot;
}
struct page *alloc_array(int rank, union vm_value *dimensions, int data_type, int struct_type)
{
if (rank < 1)
return NULL;
enum ain_data_type type = array_type(data_type);
struct page *page = alloc_page(ARRAY_PAGE, data_type, dimensions->i);
page->struct_type = struct_type;
page->rank = rank;
for (int i = 0; i < dimensions->i; i++) {
if (rank == 1) {
if (type == AIN_STRUCT)
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);
int slot = heap_alloc_slot(VM_PAGE);
heap[slot].page = child;
page->values[i].i = slot;
}
}
return page;
}
struct page *realloc_array(struct page *src, int rank, union vm_value *dimensions, int data_type, int struct_type)
{
if (rank < 1)
ERROR("Tried to allocate 0-rank array");
if (!src && !dimensions->i)
return NULL;
if (!src)
return alloc_array(rank, dimensions, data_type, struct_type);
if (src->type != ARRAY_PAGE)
ERROR("Not an array");
if (src->rank != rank)
ERROR("Attempt to reallocate array with different rank");
if (!dimensions->i) {
delete_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));
}
}
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)
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);
int slot = heap_alloc_slot(VM_PAGE);
heap[slot].page = 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->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 (!dst || !src)
ERROR("Array is NULL");
if (dst->type != ARRAY_PAGE || src->type != ARRAY_PAGE)
ERROR("Not an array");
if (!array_index_ok(dst, dst_i) || !array_index_ok(src, src_i))
ERROR("Out of bounds array access");
if (dst->rank != 1 || src->rank != 1)
ERROR("Tried to copy to/from a multi-dimensional array");
if (dst->a_type != src->a_type)
ERROR("Array types do not match");
for (int i = 0; i < n; i++) {
dst->values[dst_i + i] = vm_copy(src->values[src_i + i], array_type(dst->a_type));
}
}
int array_fill(struct page *dst, int dst_i, int n, union vm_value v)
{
if (!dst)
ERROR("Array is NULL");
if (dst->type != ARRAY_PAGE)
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;
for (int i = 0; i < n; i++) {
dst->values[dst_i + i] = vm_copy(v, array_type(dst->a_type));
}
return n;
}
void array_pushback(struct page **dst, union vm_value v, int data_type, int struct_type)
{
if (*dst) {
if ((*dst)->type != ARRAY_PAGE)
ERROR("Not an array");
if ((*dst)->rank != 1)
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)->struct_type);
(*dst)->values[index] = vm_copy(v, array_type((*dst)->a_type));
} else {
union vm_value dims[1] = { (union vm_value) { .i = 1 } };
*dst = alloc_array(1, dims, data_type, struct_type);
(*dst)->values[0] = vm_copy(v, array_type((*dst)->a_type));
}
}
void array_popback(struct page **dst)
{
if (!(*dst))
return;
if ((*dst)->type != ARRAY_PAGE)
ERROR("Not an array");
if ((*dst)->rank != 1)
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)->struct_type);
}
bool array_erase(struct page **_page, int i)
{
struct page *page = *_page;
if (!page)
return false;
if (page->type != ARRAY_PAGE)
ERROR("Not an array");
if (page->rank != 1)
ERROR("Tried erasing from a multi-dimensional array");
if (!array_index_ok(page, i))
return false;
// if array will be empty...
if (page->nr_vars == 1) {
delete_page(page);
*_page = NULL;
return true;
}
// 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 = page = xrealloc(page, sizeof(struct page) + sizeof(union vm_value) * page->nr_vars);
return true;
}
void array_insert(struct page **_page, int i, union vm_value v, int data_type, int struct_type)
{
struct page *page = *_page;
if (!page) {
array_pushback(_page, v, data_type, struct_type);
return;
}
if (page->type != ARRAY_PAGE)
ERROR("Not an array");
if (page->rank != 1)
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 = 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] = vm_copy(v, array_type(page->a_type));
}
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);
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, sizeof(union vm_value), page->nr_vars, array_compare);
}