#include "glulxe.h"
typedef struct heapblock_struct {
glui32 addr;
glui32 len;
int isfree;
struct heapblock_struct *next;
struct heapblock_struct *prev;
} heapblock_t;
static glui32 heap_start = 0;
static int alloc_count = 0;
static heapblock_t *heap_head = NULL;
static heapblock_t *heap_tail = NULL;
void heap_clear()
{
while (heap_head) {
heapblock_t *blo = heap_head;
heap_head = blo->next;
blo->next = NULL;
blo->prev = NULL;
glulx_free(blo);
}
heap_tail = NULL;
if (heap_start) {
glui32 res = change_memsize(heap_start, TRUE);
if (res)
fatal_error_i("Unable to revert memory size when deactivating heap.",
heap_start);
}
heap_start = 0;
alloc_count = 0;
}
int heap_is_active() {
return (heap_start != 0);
}
glui32 heap_get_start() {
return heap_start;
}
glui32 heap_alloc(glui32 len)
{
heapblock_t *blo, *newblo;
if (len <= 0)
fatal_error("Heap allocation length must be positive.");
blo = heap_head;
while (blo) {
if (blo->isfree && blo->len >= len)
break;
if (!blo->isfree) {
blo = blo->next;
continue;
}
if (!blo->next || !blo->next->isfree) {
blo = blo->next;
continue;
}
newblo = blo->next;
blo->len += newblo->len;
if (newblo->next) {
blo->next = newblo->next;
newblo->next->prev = blo;
}
else {
blo->next = NULL;
heap_tail = blo;
}
newblo->next = NULL;
newblo->prev = NULL;
glulx_free(newblo);
newblo = NULL;
continue;
}
if (!blo) {
glui32 res;
glui32 extension;
glui32 oldendmem = endmem;
extension = 0;
if (heap_start)
extension = endmem - heap_start;
if (extension < len)
extension = len;
if (extension < 256)
extension = 256;
extension = (extension + 0xFF) & (~(glui32)0xFF);
res = change_memsize(endmem+extension, TRUE);
if (res)
return 0;
if (heap_start == 0)
heap_start = oldendmem;
if (heap_tail && heap_tail->isfree) {
blo = heap_tail;
blo->len += extension;
}
else {
newblo = glulx_malloc(sizeof(heapblock_t));
if (!newblo)
fatal_error("Unable to allocate record for heap block.");
newblo->addr = oldendmem;
newblo->len = extension;
newblo->isfree = TRUE;
newblo->next = NULL;
newblo->prev = NULL;
if (!heap_tail) {
heap_head = newblo;
heap_tail = newblo;
}
else {
blo = heap_tail;
heap_tail = newblo;
blo->next = newblo;
newblo->prev = blo;
}
blo = newblo;
newblo = NULL;
}
}
if (!blo || !blo->isfree || blo->len < len)
return 0;
if (blo->len == len) {
blo->isfree = FALSE;
}
else {
newblo = glulx_malloc(sizeof(heapblock_t));
if (!newblo)
fatal_error("Unable to allocate record for heap block.");
newblo->isfree = TRUE;
newblo->addr = blo->addr + len;
newblo->len = blo->len - len;
blo->len = len;
blo->isfree = FALSE;
newblo->next = blo->next;
if (newblo->next)
newblo->next->prev = newblo;
newblo->prev = blo;
blo->next = newblo;
if (heap_tail == blo)
heap_tail = newblo;
}
alloc_count++;
return blo->addr;
}
void heap_free(glui32 addr)
{
heapblock_t *blo;
for (blo = heap_head; blo; blo = blo->next) {
if (blo->addr == addr)
break;
};
if (!blo || blo->isfree)
fatal_error_i("Attempt to free unallocated address from heap.", addr);
blo->isfree = TRUE;
alloc_count--;
if (alloc_count <= 0) {
heap_clear();
}
}
int heap_get_summary(glui32 *valcount, glui32 **summary)
{
glui32 *arr, len, pos;
heapblock_t *blo;
*valcount = 0;
*summary = NULL;
if (heap_start == 0)
return 0;
len = 2 + 2*alloc_count;
arr = glulx_malloc(len * sizeof(glui32));
if (!arr)
return 1;
pos = 0;
arr[pos++] = heap_start;
arr[pos++] = alloc_count;
for (blo = heap_head; blo; blo = blo->next) {
if (blo->isfree)
continue;
arr[pos++] = blo->addr;
arr[pos++] = blo->len;
}
if (pos != len)
fatal_error("Wrong number of active blocks in heap");
*valcount = len;
*summary = arr;
return 0;
}
int heap_apply_summary(glui32 valcount, glui32 *summary)
{
glui32 lx, jx, lastend;
if (heap_start)
fatal_error("Heap active when heap_apply_summary called");
if (valcount == 0 || summary == NULL)
return 0;
if (valcount == 2 && summary[0] == 0 && summary[1] == 0)
return 0;
lx = 0;
heap_start = summary[lx++];
alloc_count = summary[lx++];
for (jx=lx; jx+2<valcount; jx+=2) {
if (summary[jx] >= summary[jx+2])
fatal_error("Heap block summary is out of order.");
}
lastend = heap_start;
while (lx < valcount || lastend < endmem) {
heapblock_t *blo;
blo = glulx_malloc(sizeof(heapblock_t));
if (!blo)
fatal_error("Unable to allocate record for heap block.");
if (lx >= valcount) {
blo->addr = lastend;
blo->len = endmem - lastend;
blo->isfree = TRUE;
}
else {
if (lastend < summary[lx]) {
blo->addr = lastend;
blo->len = summary[lx] - lastend;
blo->isfree = TRUE;
}
else {
blo->addr = summary[lx++];
blo->len = summary[lx++];
blo->isfree = FALSE;
}
}
blo->prev = NULL;
blo->next = NULL;
if (!heap_head) {
heap_head = blo;
heap_tail = blo;
}
else {
heap_tail->next = blo;
blo->prev = heap_tail;
heap_tail = blo;
}
lastend = blo->addr + blo->len;
}
return 0;
}