// 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 #include #endif #include #include #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; }