joeylib2/src/core/codegenArena.c

302 lines
8.6 KiB
C

// Codegen arena: free-list allocator with adjacent-hole coalescing
// and manual compaction. See codegenArenaInternal.h for the contract.
#include "joey/platform.h"
#if defined(JOEYLIB_PLATFORM_IIGS)
// On the IIgs the arena holds 65816 machine code that callers JSL
// into. ORCA-C's malloc returns memory from the C heap (often bank 0,
// which is system RAM) so JSL'ing into it lands on whatever happens
// to live there -> instant crash. Memory Manager NewHandle with
// attrFixed | attrLocked | attrPage | attrNoCross gives us a fixed
// page-aligned region in a single bank we can safely jump into.
//
// types.h must be included before our stdbool shim because ORCA's
// types.h defines true/false as #define ... without #ifndef guards
// and would re-#define our shim's macros.
#include <types.h>
#include <memory.h>
#endif
#include <stdlib.h>
#include <string.h>
#include "codegenArenaInternal.h"
// ----- Module state -----
// gCodegenArenaBase / gCodegenArenaBaseAddr are non-static so spriteCompile.c can read them
// directly via extern instead of paying a JSL/RTL per access through
// the codegenArenaBase() / codegenArenaBaseAddr() wrappers. Both are
// set once at codegenArenaInit and never moved (the underlying
// Memory Manager handle is locked-in-place on IIgs). Callers MUST
// treat them as read-only.
uint8_t *gCodegenArenaBase = NULL;
// gCodegenArenaBaseAddr mirrors gCodegenArenaBase as a 24-bit
// absolute address. ORCA-C's (uint32_t)pointer cast on the IIgs
// zeros the bank byte for some pointer expressions, so JSL targets
// read this field directly.
uint32_t gCodegenArenaBaseAddr = 0;
static uint32_t gTotalBytes = 0;
static uint32_t gUsedBytes = 0;
static ArenaSlotT *gFirstSlot = NULL;
#if defined(JOEYLIB_PLATFORM_IIGS)
static Handle gCodegenArenaBaseHandle = NULL;
#endif
// ----- Prototypes -----
static ArenaSlotT *newSlot(uint32_t offset, uint32_t size, bool used);
static void coalesceWithNext(ArenaSlotT *slot);
// ----- Internal helpers (alphabetical) -----
// If `slot` is free and slot->next is also free, merge them into a
// single free slot. Caller must already have ensured slot is free.
static void coalesceWithNext(ArenaSlotT *slot) {
ArenaSlotT *victim;
if (slot == NULL || slot->next == NULL) {
return;
}
if (slot->used || slot->next->used) {
return;
}
victim = slot->next;
slot->size += victim->size;
slot->next = victim->next;
if (slot->next != NULL) {
slot->next->prev = slot;
}
free(victim);
}
static ArenaSlotT *newSlot(uint32_t offset, uint32_t size, bool used) {
ArenaSlotT *s;
s = (ArenaSlotT *)malloc(sizeof(ArenaSlotT));
if (s == NULL) {
return NULL;
}
s->offset = offset;
s->size = size;
s->used = used;
s->next = NULL;
s->prev = NULL;
return s;
}
// ----- Public-internal API (alphabetical) -----
ArenaSlotT *codegenArenaAlloc(uint32_t bytes) {
ArenaSlotT *slot;
ArenaSlotT *remainder;
if (gCodegenArenaBase == NULL || bytes == 0) {
return NULL;
}
for (slot = gFirstSlot; slot != NULL; slot = slot->next) {
if (slot->used || slot->size < bytes) {
continue;
}
// First fit. If there's slack, split: shrink this slot to
// exactly `bytes` and insert a new free slot for the rest.
if (slot->size > bytes) {
remainder = newSlot(slot->offset + bytes, slot->size - bytes, false);
if (remainder == NULL) {
return NULL;
}
remainder->prev = slot;
remainder->next = slot->next;
if (slot->next != NULL) {
slot->next->prev = remainder;
}
slot->next = remainder;
slot->size = bytes;
}
slot->used = true;
gUsedBytes += bytes;
return slot;
}
return NULL;
}
// codegenArenaBase() / codegenArenaBaseAddr() are now header-only
// macros that read gCodegenArenaBase / gCodegenArenaBaseAddr
// directly, so the C function bodies that used to live here are
// gone. The wrappers cost ~30 cyc per call on IIgs and were hit
// 3x per sprite frame.
uint32_t codegenArenaBytesTotal(void) {
return gTotalBytes;
}
uint32_t codegenArenaBytesUsed(void) {
return gUsedBytes;
}
void codegenArenaCompact(void) {
ArenaSlotT *slot;
ArenaSlotT *next;
ArenaSlotT *trailing;
uint32_t cursor;
if (gCodegenArenaBase == NULL) {
return;
}
cursor = 0;
slot = gFirstSlot;
while (slot != NULL) {
next = slot->next;
if (slot->used) {
if (slot->offset != cursor) {
memmove(gCodegenArenaBase + cursor, gCodegenArenaBase + slot->offset, slot->size);
slot->offset = cursor;
}
cursor += slot->size;
slot = next;
continue;
}
// Free slot: drop from the list. The caller-side ArenaSlotT*
// for any free slot was already invalidated when it was
// freed (coalesce released the struct); nothing live points
// at it.
if (slot->prev != NULL) {
slot->prev->next = next;
} else {
gFirstSlot = next;
}
if (next != NULL) {
next->prev = slot->prev;
}
free(slot);
slot = next;
}
if (cursor < gTotalBytes) {
trailing = newSlot(cursor, gTotalBytes - cursor, false);
if (trailing == NULL) {
return; // Compaction succeeded; just skip the free-slot record.
}
trailing->prev = NULL;
for (slot = gFirstSlot; slot != NULL && slot->next != NULL; slot = slot->next) {
// walk to last
}
if (slot == NULL) {
gFirstSlot = trailing;
} else {
slot->next = trailing;
trailing->prev = slot;
}
}
}
void codegenArenaFree(ArenaSlotT *slot) {
if (slot == NULL || gCodegenArenaBase == NULL) {
return;
}
if (!slot->used) {
return; // double-free; ignore
}
slot->used = false;
gUsedBytes -= slot->size;
coalesceWithNext(slot);
coalesceWithNext(slot->prev);
}
bool codegenArenaInit(uint32_t totalBytes) {
if (gCodegenArenaBase != NULL) {
return true;
}
if (totalBytes == 0) {
return false;
}
#if defined(JOEYLIB_PLATFORM_IIGS)
gCodegenArenaBaseHandle = NewHandle(totalBytes, _ownerid,
attrFixed | attrLocked | attrPage | attrNoCross,
NULL);
if (gCodegenArenaBaseHandle == NULL || _toolErr != 0) {
gCodegenArenaBaseHandle = NULL;
return false;
}
HLock(gCodegenArenaBaseHandle);
// Capture the 24-bit absolute address by copying the Pointer's
// raw bytes -- (uint32_t)pointer through a chain of expressions
// has been observed to drop the bank byte under ORCA-C's
// memorymodel 1, but a memcpy of the underlying 4 bytes is
// reliable. The high byte (bytes[3]) is undefined and masked off.
{
Pointer p;
uint8_t bytes[4];
p = *gCodegenArenaBaseHandle;
gCodegenArenaBase = (uint8_t *)p;
memcpy(bytes, &p, 4);
gCodegenArenaBaseAddr = (uint32_t)bytes[0]
| ((uint32_t)bytes[1] << 8)
| ((uint32_t)bytes[2] << 16);
}
if (gCodegenArenaBase == NULL) {
DisposeHandle(gCodegenArenaBaseHandle);
gCodegenArenaBaseHandle = NULL;
return false;
}
#else
gCodegenArenaBase = (uint8_t *)malloc(totalBytes);
if (gCodegenArenaBase == NULL) {
return false;
}
gCodegenArenaBaseAddr = (uint32_t)gCodegenArenaBase;
#endif
gFirstSlot = newSlot(0, totalBytes, false);
if (gFirstSlot == NULL) {
#if defined(JOEYLIB_PLATFORM_IIGS)
DisposeHandle(gCodegenArenaBaseHandle);
gCodegenArenaBaseHandle = NULL;
#else
free(gCodegenArenaBase);
#endif
gCodegenArenaBase = NULL;
gCodegenArenaBaseAddr = 0;
return false;
}
gTotalBytes = totalBytes;
gUsedBytes = 0;
return true;
}
void codegenArenaShutdown(void) {
ArenaSlotT *slot;
ArenaSlotT *next;
if (gCodegenArenaBase == NULL) {
return;
}
for (slot = gFirstSlot; slot != NULL; slot = next) {
next = slot->next;
free(slot);
}
#if defined(JOEYLIB_PLATFORM_IIGS)
DisposeHandle(gCodegenArenaBaseHandle);
gCodegenArenaBaseHandle = NULL;
#else
free(gCodegenArenaBase);
#endif
gCodegenArenaBase = NULL;
gCodegenArenaBaseAddr = 0;
gFirstSlot = NULL;
gTotalBytes = 0;
gUsedBytes = 0;
}