302 lines
8.6 KiB
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;
|
|
}
|