// 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 . 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 #define attrPage 0x0004 #define attrNoCross 0x0010 #define attrNoSpec 0x0020 #define attrFixed 0x4000 #define attrLocked 0x8000 #endif #include #include #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; }