319 lines
9.2 KiB
C
319 lines
9.2 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. A normal C heap allocation returns memory from bank 0 (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.
|
|
// The IIgs toolbox bindings live in <iigs/toolbox.h>. Handles are typed
|
|
// as void* and the owner ID comes from MMStartUp(); the Memory Manager
|
|
// attribute bits we use are defined here (values from Apple's Memory
|
|
// Manager reference).
|
|
#include <iigs/toolbox.h>
|
|
#define attrPage 0x0004
|
|
#define attrNoCross 0x0010
|
|
#define attrNoSpec 0x0020
|
|
#define attrFixed 0x4000
|
|
#define attrLocked 0x8000
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "codegenArenaInternal.h"
|
|
|
|
|
|
|
|
// ----- Module state -----
|
|
|
|
// gCodegenArenaBase / gCodegenArenaBaseAddr are non-static so jlSpriteCompile.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 that JSL targets read directly (captured by copying the
|
|
// handle pointer's raw bytes -- see codegenArenaInit).
|
|
uint32_t gCodegenArenaBaseAddr = 0;
|
|
static uint32_t gTotalBytes = 0;
|
|
static uint32_t gUsedBytes = 0;
|
|
static ArenaSlotT *gFirstSlot = NULL;
|
|
#if defined(JOEYLIB_PLATFORM_IIGS)
|
|
static void *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)
|
|
// attrNoCross forbids the block from crossing a 64KB bank boundary,
|
|
// so any request larger than one bank (65536 bytes) is unsatisfiable
|
|
// and NewHandle would always return NULL. Clamp to a single bank.
|
|
// The clamped request can still fail below -- a 64KB attrNoCross
|
|
// block needs a completely free bank -- and the caller's error
|
|
// message covers that.
|
|
if (totalBytes > 65536UL) {
|
|
totalBytes = 65536UL;
|
|
}
|
|
// MMStartUp() returns this application's master user ID (idempotent;
|
|
// the system starts the Memory Manager before the app runs). A NULL
|
|
// handle is the failure signal.
|
|
{
|
|
uint16_t ownerId;
|
|
|
|
ownerId = MMStartUp();
|
|
gCodegenArenaBaseHandle = NewHandle((unsigned long)totalBytes, ownerId,
|
|
attrFixed | attrLocked | attrPage | attrNoCross,
|
|
(void *)0);
|
|
}
|
|
if (gCodegenArenaBaseHandle == NULL) {
|
|
return false;
|
|
}
|
|
HLock(gCodegenArenaBaseHandle);
|
|
// Capture the 24-bit absolute address by copying the master
|
|
// pointer's raw bytes; bytes[3] (bank-high/flags) is masked off.
|
|
{
|
|
void *p;
|
|
uint8_t bytes[4];
|
|
|
|
p = *(void **)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;
|
|
}
|