mirror of
https://github.com/nunuhara/xsystem4.git
synced 2026-10-02 20:08:03 +03:00
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.
383 lines
9.0 KiB
C
383 lines
9.0 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/>.
|
|
*/
|
|
|
|
#define VM_PRIVATE
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <assert.h>
|
|
#include "system4/string.h"
|
|
#include "vm.h"
|
|
#include "vm/heap.h"
|
|
#include "vm/page.h"
|
|
#include "xsystem4.h"
|
|
|
|
#define INITIAL_HEAP_SIZE 4096
|
|
#define HEAP_ALLOC_STEP 4096
|
|
|
|
struct vm_pointer *heap = NULL;
|
|
size_t heap_size = 0;
|
|
|
|
// Heap free list
|
|
// This is a list of unused indices into the 'heap' array.
|
|
int32_t *heap_free_stack = NULL;
|
|
size_t heap_free_ptr = 0;
|
|
|
|
static const char *vm_ptrtype_strtab[] = {
|
|
[VM_PAGE] = "VM_PAGE",
|
|
[VM_STRING] = "VM_STRING",
|
|
};
|
|
|
|
static const char *vm_ptrtype_string(enum vm_pointer_type type) {
|
|
if (type < NR_VM_POINTER_TYPES)
|
|
return vm_ptrtype_strtab[type];
|
|
return "INVALID POINTER TYPE";
|
|
}
|
|
|
|
void heap_grow(size_t new_size)
|
|
{
|
|
assert(new_size > heap_size);
|
|
heap = xrealloc(heap, sizeof(struct vm_pointer) * new_size);
|
|
heap_free_stack = xrealloc(heap_free_stack, sizeof(int32_t) * new_size);
|
|
for (size_t i = heap_size; i < new_size; i++) {
|
|
heap[i].ref = 0;
|
|
heap_free_stack[i] = i;
|
|
}
|
|
heap_size = new_size;
|
|
}
|
|
|
|
void heap_guarantee(unsigned headroom)
|
|
{
|
|
if (heap_size - heap_free_ptr >= headroom)
|
|
return;
|
|
|
|
size_t new_size = heap_size;
|
|
while (new_size - heap_free_ptr < headroom) {
|
|
new_size += HEAP_ALLOC_STEP;
|
|
}
|
|
|
|
heap_grow(new_size);
|
|
}
|
|
|
|
void heap_init(void)
|
|
{
|
|
if (!heap) {
|
|
heap_size = INITIAL_HEAP_SIZE;
|
|
heap = xcalloc(1, INITIAL_HEAP_SIZE * sizeof(struct vm_pointer));
|
|
heap_free_stack = xmalloc(INITIAL_HEAP_SIZE * sizeof(int32_t));
|
|
} else {
|
|
memset(heap, 0, heap_size * sizeof(struct vm_pointer*));
|
|
}
|
|
|
|
for (size_t i = 0; i < heap_size; i++) {
|
|
heap_free_stack[i] = i;
|
|
}
|
|
heap_free_ptr = 1; // global page at index 0
|
|
}
|
|
|
|
int32_t heap_alloc_slot(enum vm_pointer_type type)
|
|
{
|
|
if (heap_free_ptr >= heap_size) {
|
|
heap_grow(heap_size+HEAP_ALLOC_STEP);
|
|
}
|
|
|
|
int32_t slot = heap_free_stack[heap_free_ptr++];
|
|
heap[slot].ref = 1;
|
|
heap[slot].type = type;
|
|
#ifdef DEBUG_HEAP
|
|
heap[slot].alloc_addr = instr_ptr;
|
|
memset(heap[slot].ref_addr, 0, sizeof(heap[slot].ref_addr));
|
|
heap[slot].ref_nr = 0;
|
|
memset(heap[slot].deref_addr, 0, sizeof(heap[slot].deref_addr));
|
|
heap[slot].deref_nr = 0;
|
|
heap[slot].free_addr = 0;
|
|
#endif
|
|
return slot;
|
|
}
|
|
|
|
static void heap_free_slot(int32_t slot)
|
|
{
|
|
heap_free_stack[--heap_free_ptr] = slot;
|
|
}
|
|
|
|
static void heap_double_free(int32_t slot)
|
|
{
|
|
#ifdef DEBUG_HEAP
|
|
WARNING("double free of slot %d (%s)\nOriginally allocated at %X\nOriginally freed at %X",
|
|
slot, vm_ptrtype_string(heap[slot].type),
|
|
heap[slot].alloc_addr, heap[slot].free_addr);
|
|
#else
|
|
WARNING("double free of slot %d (%s)", slot, vm_ptrtype_string(heap[slot].type));
|
|
#endif
|
|
}
|
|
|
|
void heap_ref(int32_t slot)
|
|
{
|
|
if (slot == -1)
|
|
return;
|
|
heap[slot].ref++;
|
|
#ifdef DEBUG_HEAP
|
|
heap[slot].ref_addr[heap[slot].ref_nr++ % 16] = instr_ptr;
|
|
#endif
|
|
}
|
|
|
|
void heap_unref(int slot)
|
|
{
|
|
if (unlikely(heap[slot].ref <= 0)) {
|
|
heap_double_free(slot);
|
|
VM_ERROR("double free");
|
|
}
|
|
if (heap[slot].ref > 1) {
|
|
#ifdef DEBUG_HEAP
|
|
heap[slot].deref_addr[heap[slot].deref_nr++ % 16] = instr_ptr;
|
|
#endif
|
|
heap[slot].ref--;
|
|
return;
|
|
}
|
|
#ifdef DEBUG_HEAP
|
|
heap[slot].free_addr = instr_ptr;
|
|
#endif
|
|
switch (heap[slot].type) {
|
|
case VM_PAGE:
|
|
if (heap[slot].page) {
|
|
delete_page(slot);
|
|
}
|
|
break;
|
|
case VM_STRING:
|
|
free_string(heap[slot].s);
|
|
break;
|
|
}
|
|
heap[slot].ref = 0;
|
|
heap_free_slot(slot);
|
|
}
|
|
|
|
// XXX: special version of heap_unref which avoids calling destructors
|
|
void exit_unref(int slot)
|
|
{
|
|
if (slot < 0 || (size_t)slot >= heap_size) {
|
|
WARNING("out of bounds heap index: %d", slot);
|
|
return;
|
|
}
|
|
if (heap[slot].ref <= 0) {
|
|
heap_double_free(slot);
|
|
return;
|
|
}
|
|
if (heap[slot].ref > 1) {
|
|
#ifdef DEBUG_HEAP
|
|
heap[slot].deref_addr[heap[slot].deref_nr++ % 16] = 0xDEADC0DE;
|
|
#endif
|
|
heap[slot].ref--;
|
|
return;
|
|
}
|
|
switch (heap[slot].type) {
|
|
case VM_PAGE:
|
|
if (heap[slot].page) {
|
|
struct page *page = heap[slot].page;
|
|
for (int i = 0; i < page->nr_vars; i++) {
|
|
switch (variable_type(page, i, NULL, NULL)) {
|
|
case AIN_STRING:
|
|
case AIN_STRUCT:
|
|
case AIN_DELEGATE:
|
|
case AIN_ARRAY_TYPE:
|
|
case AIN_REF_TYPE:
|
|
if (page->values[i].i == -1)
|
|
break;
|
|
exit_unref(page->values[i].i);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
free_page(page);
|
|
}
|
|
break;
|
|
case VM_STRING:
|
|
free_string(heap[slot].s);
|
|
break;
|
|
}
|
|
heap[slot].ref = 0;
|
|
heap_free_slot(slot);
|
|
}
|
|
|
|
bool heap_index_valid(int index)
|
|
{
|
|
return index >= 0 && (size_t)index < heap_size && heap[index].ref > 0;
|
|
}
|
|
|
|
bool page_index_valid(int index)
|
|
{
|
|
return heap_index_valid(index) && heap[index].type == VM_PAGE;
|
|
}
|
|
|
|
bool string_index_valid(int index)
|
|
{
|
|
return heap_index_valid(index) && heap[index].type == VM_STRING;
|
|
}
|
|
|
|
struct page *heap_get_page(int index)
|
|
{
|
|
if (unlikely(!page_index_valid(index)))
|
|
VM_ERROR("Invalid page index: %d", index);
|
|
return heap[index].page;
|
|
}
|
|
|
|
struct string *heap_get_string(int index)
|
|
{
|
|
if (unlikely(!string_index_valid(index)))
|
|
VM_ERROR("Invalid string index: %d", index);
|
|
return heap[index].s;
|
|
}
|
|
|
|
struct page *heap_get_struct_page(int index)
|
|
{
|
|
struct page *page = heap_get_page(index);
|
|
if (unlikely(!page || page->type != STRUCT_PAGE))
|
|
VM_ERROR("Not a struct page: %d", index);
|
|
return page;
|
|
}
|
|
|
|
struct page *heap_get_delegate_page(int index)
|
|
{
|
|
struct page *page = heap_get_page(index);
|
|
if (unlikely(page && page->type != DELEGATE_PAGE))
|
|
VM_ERROR("Not a delegate page: %d", index);
|
|
return page;
|
|
}
|
|
|
|
void heap_set_page(int slot, struct page *page)
|
|
{
|
|
#ifdef DEBUG_HEAP
|
|
if (unlikely(!page_index_valid(slot)))
|
|
VM_ERROR("Invalid page index: %d", index);
|
|
#endif
|
|
heap[slot].page = page;
|
|
}
|
|
|
|
void heap_string_assign(int slot, struct string *string)
|
|
{
|
|
#ifdef DEBUG_HEAP
|
|
if (unlikely(!string_index_valid(slot)))
|
|
VM_ERROR("Tried to assign string to non-string slot");
|
|
#endif
|
|
if (heap[slot].s) {
|
|
free_string(heap[slot].s);
|
|
}
|
|
heap[slot].s = string_ref(string);
|
|
}
|
|
|
|
void heap_struct_assign(int lval, int rval)
|
|
{
|
|
if (unlikely(lval == -1))
|
|
VM_ERROR("Assignment to null-pointer");
|
|
if (lval == rval)
|
|
return;
|
|
#ifdef DEBUG_HEAP
|
|
if (unlikely(!page_index_valid(lval)))
|
|
VM_ERROR("Invalid page index: %d", lval);
|
|
if (unlikely(!page_index_valid(rval)))
|
|
VM_ERROR("Invalid page index: %d", rval);
|
|
if (unlikely(heap[lval].page && heap[lval].page->type != STRUCT_PAGE))
|
|
VM_ERROR("SR_ASSIGN to non-struct page");
|
|
if (unlikely(heap[rval].page && heap[rval].page->type != STRUCT_PAGE))
|
|
VM_ERROR("SR_ASSIGN from non-struct page");
|
|
if (unlikely(heap[lval].page && heap[rval].page && heap[lval].page->index != heap[rval].page->index))
|
|
VM_ERROR("SR_ASSIGN with different struct types");
|
|
#endif
|
|
if (heap[lval].page) {
|
|
delete_page(lval);
|
|
}
|
|
heap_set_page(lval, copy_page(heap[rval].page));
|
|
}
|
|
|
|
int32_t heap_alloc_string(struct string *s)
|
|
{
|
|
int slot = heap_alloc_slot(VM_STRING);
|
|
heap[slot].s = s;
|
|
return slot;
|
|
}
|
|
|
|
int32_t heap_alloc_page(struct page *page)
|
|
{
|
|
int slot = heap_alloc_slot(VM_PAGE);
|
|
heap[slot].page = page;
|
|
return slot;
|
|
}
|
|
|
|
static void describe_page(struct page *page)
|
|
{
|
|
if (!page) {
|
|
sys_message("NULL_PAGE\n");
|
|
return;
|
|
}
|
|
|
|
switch (page->type) {
|
|
case GLOBAL_PAGE:
|
|
sys_message("GLOBAL_PAGE\n");
|
|
break;
|
|
case LOCAL_PAGE:
|
|
sys_message("LOCAL_PAGE: %s\n", display_sjis0(ain->functions[page->index].name));
|
|
break;
|
|
case STRUCT_PAGE:
|
|
sys_message("STRUCT_PAGE: %s\n", display_sjis0(ain->structures[page->index].name));
|
|
break;
|
|
case ARRAY_PAGE:
|
|
sys_message("ARRAY_PAGE: %s\n", display_sjis0(ain_strtype(ain, page->a_type, page->array.struct_type)));
|
|
break;
|
|
case DELEGATE_PAGE:
|
|
// TODO: list function names
|
|
sys_message("DELEGATE_PAGE\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
void heap_describe_slot(int slot)
|
|
{
|
|
if (heap[slot].type == VM_STRING && heap[slot].s == &EMPTY_STRING)
|
|
return;
|
|
#ifdef DEBUG_HEAP
|
|
sys_message("[%d](%d)(%08X)[", slot, heap[slot].ref, heap[slot].alloc_addr);
|
|
for (int i = 0; i < heap[slot].ref_nr && i < 16; i++) {
|
|
if (i > 0)
|
|
sys_message(",");
|
|
sys_message("%08X", heap[slot].ref_addr[i]);
|
|
}
|
|
sys_message("][");
|
|
for (int i = 0; i < heap[slot].deref_nr && i < 16; i++) {
|
|
if (i > 0)
|
|
sys_message(",");
|
|
sys_message("%08X", heap[slot].deref_addr[i]);
|
|
}
|
|
sys_message("] = ");
|
|
#else
|
|
sys_message("[%d](%d) = ", slot, heap[slot].ref);
|
|
#endif
|
|
switch (heap[slot].type) {
|
|
case VM_PAGE:
|
|
describe_page(heap[slot].page);
|
|
break;
|
|
case VM_STRING:
|
|
if (heap[slot].s) {
|
|
sys_message("STRING: %s\n", display_sjis0(heap[slot].s->text));
|
|
} else {
|
|
sys_message("STRING: NULL\n");
|
|
}
|
|
break;
|
|
default:
|
|
sys_message("???\n");
|
|
break;
|
|
}
|
|
}
|