568 lines
13 KiB
C
568 lines
13 KiB
C
#include <ultra64.h>
|
|
#include "constants.h"
|
|
#include "bss.h"
|
|
#include "game/debug.h"
|
|
#include "lib/debughud.h"
|
|
#include "lib/mema.h"
|
|
#include "lib/memp.h"
|
|
#include "data.h"
|
|
#include "types.h"
|
|
|
|
/**
|
|
* mema - memory (ad hoc) allocation system.
|
|
*
|
|
* Mema's heap is 300KB and is itself allocated out of memp's stage pool.
|
|
* Memp resets its stage pool each time a new stage is loaded, which means mema
|
|
* is also reset each time a stage is loaded.
|
|
*
|
|
* Unlike memp, mema supports freeing of individual allocations. This makes it
|
|
* a good system to use when the allocation is somewhat temporary and should be
|
|
* freed without having to load a new stage. It's used by the (inaccessible)
|
|
* Perfect Head editor, file listings and room code.
|
|
*
|
|
* Mema tracks what has been allocated by storing references to free spaces in
|
|
* its spaces array. The allocations themselves are not referenced. When
|
|
* initialising the spaces array, the first element is set to the entire heap
|
|
* and the remaining elements are set to 0.
|
|
*
|
|
* This creates a bit of a terminology problem. Just remember that a memaspace
|
|
* is not an allocation; it's a free space that is available for allocation.
|
|
*
|
|
* Due to the ability to free individual allocations, both the heap and the
|
|
* spaces array can become fragmented. Mema supports defragmenting the spaces
|
|
* array: entries are ordered by address, and back to back entries are merged.
|
|
* The data in the heap itself is never moved, as that would require updating
|
|
* pointers throughout the game code which mema cannot do.
|
|
*/
|
|
|
|
#define MAX_SPACES 124
|
|
|
|
struct memaspace {
|
|
s32 addr;
|
|
u32 size;
|
|
};
|
|
|
|
/**
|
|
* This structure contains dummy entries before and after the spaces array.
|
|
* These are used as start and end markers, but could have been avoided by
|
|
* using loop counters (eg. a typical i < numspaces loop).
|
|
*/
|
|
struct memaheap {
|
|
u32 unk000;
|
|
struct memaspace start;
|
|
struct memaspace spaces[MAX_SPACES];
|
|
struct memaspace end1;
|
|
struct memaspace end2;
|
|
};
|
|
|
|
s32 g_MemaHeapStart;
|
|
s32 g_MemaHeapSize;
|
|
struct memaheap g_MemaHeap;
|
|
|
|
#if VERSION == VERSION_PAL_BETA
|
|
u32 g_MemaLeastEverFree = 1000000;
|
|
#endif
|
|
|
|
void mema_swap(struct memaspace *a, struct memaspace *b)
|
|
{
|
|
u32 tempaddr = a->addr;
|
|
u32 tempsize = a->size;
|
|
a->addr = b->addr;
|
|
a->size = b->size;
|
|
b->addr = tempaddr;
|
|
b->size = tempsize;
|
|
}
|
|
|
|
void mema_merge(struct memaspace *a, struct memaspace *b)
|
|
{
|
|
a->size += b->size;
|
|
b->addr = 0;
|
|
b->size = 0;
|
|
}
|
|
|
|
bool mema_defrag_pass(struct memaheap *heap)
|
|
{
|
|
bool merged = false;
|
|
struct memaspace *prev = &heap->start;
|
|
struct memaspace *curr = &heap->spaces[0];
|
|
struct memaspace *last = &heap->spaces[MAX_SPACES - 1];
|
|
u32 addr = 0;
|
|
|
|
while (curr <= last) {
|
|
if (curr->size != 0) {
|
|
if (curr->addr < addr) {
|
|
mema_swap(curr, prev);
|
|
}
|
|
|
|
if (prev->size + addr == curr->addr) {
|
|
mema_merge(prev, curr);
|
|
curr = prev;
|
|
merged = true;
|
|
}
|
|
|
|
prev = curr;
|
|
addr = curr->addr;
|
|
}
|
|
|
|
curr++;
|
|
}
|
|
|
|
return merged;
|
|
}
|
|
|
|
void mema_defrag(void)
|
|
{
|
|
while (mema_defrag_pass(&g_MemaHeap));
|
|
}
|
|
|
|
/**
|
|
* Defrag the spaces list in an attempt to free up any slot.
|
|
*
|
|
* If none can be found, return the smallest run of free space so it can be
|
|
* overwritten by the caller.
|
|
*/
|
|
struct memaspace *mema_make_slot(struct memaheap *heap)
|
|
{
|
|
struct memaspace *curr = &heap->spaces[0];
|
|
struct memaspace *best;
|
|
u32 min;
|
|
s32 i;
|
|
|
|
// Do 124 passes over the list. This ensures the list is in order by the
|
|
// end. Though in most cases it's roughly in order anyway, and the excessive
|
|
// looping is just wasting CPU cycles. In reality this situation probably
|
|
// never occurs.
|
|
for (i = 0; i < MAX_SPACES; i++) {
|
|
while (curr <= &heap->spaces[MAX_SPACES - 1]) {
|
|
if (curr->size == 0) {
|
|
return curr;
|
|
}
|
|
|
|
if ((uintptr_t) curr[1].addr < (uintptr_t) curr[0].addr) {
|
|
mema_swap(&curr[0], &curr[1]);
|
|
}
|
|
|
|
if (curr[1].addr == curr[0].size + curr[0].addr) {
|
|
// Found two that can be merged
|
|
curr[0].size += curr[1].size;
|
|
curr[1].addr = 0;
|
|
curr[1].size = 0;
|
|
|
|
return &curr[1];
|
|
}
|
|
|
|
curr++;
|
|
}
|
|
|
|
curr = &heap->spaces[0];
|
|
}
|
|
|
|
// If this code is reached then the spaces list is so badly and unrepairably
|
|
// fragmented that we can't find any slot to record the free space in.
|
|
// Find the smallest run of free space and use that instead.
|
|
// The caller will overwrite it with its own free allocation, causing the
|
|
// original run of free space to be unusable until the mema heap is reset.
|
|
min = 0xffffffff;
|
|
best = curr;
|
|
|
|
while (curr <= &heap->spaces[MAX_SPACES - 1]) {
|
|
if (curr->size < min) {
|
|
best = curr;
|
|
min = curr->size;
|
|
}
|
|
|
|
curr++;
|
|
}
|
|
|
|
return best;
|
|
}
|
|
|
|
void _mema_free(s32 addr, s32 size)
|
|
{
|
|
// Choose an index in the spaces array which we'll mark a space as free,
|
|
// based on how far into the heap the allocation is. This is a rough
|
|
// estimate and doesn't need to be any particular index, but the defrag
|
|
// function tries to order the spaces by address so the closer we get to it
|
|
// the less work the defrag function will have to do should it be called.
|
|
s32 index = (addr - g_MemaHeapStart) * MAX_SPACES / g_MemaHeapSize;
|
|
struct memaspace *curr = &g_MemaHeap.spaces[index];
|
|
|
|
// If the entry is taken, keep moving forward until a zero is found.
|
|
while (curr->size != 0) {
|
|
curr++;
|
|
}
|
|
|
|
// If we reached the end of the spaces list, go backwards instead
|
|
if (curr->addr == -1) {
|
|
curr = &g_MemaHeap.spaces[index];
|
|
|
|
while (curr->size != 0) {
|
|
curr--;
|
|
}
|
|
|
|
if (curr->addr == 0) {
|
|
curr = mema_make_slot(&g_MemaHeap);
|
|
}
|
|
}
|
|
|
|
// Mark this space as free
|
|
curr->addr = addr;
|
|
curr->size = size;
|
|
}
|
|
|
|
void mema_init(void)
|
|
{
|
|
// empty
|
|
}
|
|
|
|
void mema_reset(void *heapaddr, u32 heapsize)
|
|
{
|
|
struct memaspace *space;
|
|
|
|
#ifndef DEBUG
|
|
// Adding an amount to the heap size here means that mema can allocate past
|
|
// the end of its heap. This would overflow into the gun names language
|
|
// file. Maybe the developers had an ifndef directive like we do, but they
|
|
// meant for it to be ifdef instead?
|
|
// @bug @dangerous
|
|
heapsize += 0x8e0;
|
|
#endif
|
|
|
|
g_MemaHeap.unk000 = 0;
|
|
|
|
g_MemaHeap.start.addr = 0;
|
|
g_MemaHeap.start.size = 0;
|
|
|
|
g_MemaHeap.end1.addr = 0xffffffff;
|
|
g_MemaHeap.end1.size = 0;
|
|
g_MemaHeap.end2.addr = 0xffffffff;
|
|
g_MemaHeap.end2.size = 0xffffffff;
|
|
|
|
for (space = &g_MemaHeap.spaces[0]; space <= &g_MemaHeap.spaces[MAX_SPACES - 1]; space++) {
|
|
space->addr = 0;
|
|
space->size = 0;
|
|
}
|
|
|
|
g_MemaHeap.spaces[0].addr = g_MemaHeapStart = (uintptr_t) heapaddr;
|
|
g_MemaHeap.spaces[0].size = g_MemaHeapSize = heapsize;
|
|
|
|
#if VERSION == VERSION_PAL_BETA
|
|
g_MemaLeastEverFree = 1000000;
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Example printout of figures:
|
|
*
|
|
* Mem Info
|
|
* memp: MP_LF_LEV
|
|
* F: 0 722352
|
|
* S: 972080 3668704
|
|
* Over: 2946352
|
|
* memp: MP_LF_ETER
|
|
* F: 0 0
|
|
* S: 601728 0
|
|
* mema:
|
|
* LF: 391728
|
|
* Audio Free: 13184
|
|
*
|
|
* Where two figures are shown in one line, the left refers to onboard memory
|
|
* and the right refers to expansion pak memory.
|
|
*
|
|
* F means free.
|
|
* S means size.
|
|
*
|
|
* "Over" shows how much it's over 4MB, if they were to try to fit the game into
|
|
* onboard memory only. This shows "Free" if under 4MB.
|
|
*
|
|
* The ETER (permanent) pool has 0 free space because it's shrunk to fit once
|
|
* the permanent allocations are done during startup. This pool fits entirely
|
|
* in onboard memory, so the expansion size is 0.
|
|
*/
|
|
void mema_print(void)
|
|
{
|
|
s32 onboard;
|
|
s32 expansion;
|
|
s32 line = 1;
|
|
s32 over;
|
|
char buffer[124];
|
|
|
|
mema_defrag_pass(&g_MemaHeap);
|
|
|
|
#ifdef DEBUG
|
|
#if VERSION == VERSION_PAL_BETA
|
|
if (debug_is_mem_info_enabled()) {
|
|
dhud_set_fg_colour(0xff, 0xff, 0xff, 0xff);
|
|
dhud_set_bg_colour(0, 0, 0, 0xff);
|
|
|
|
sprintf(buffer, "Lev0: %d", memp_get_pool_free(MEMPOOL_STAGE, MEMBANK_ONBOARD));
|
|
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
|
|
sprintf(buffer, "Lev1: %d", memp_get_pool_free(MEMPOOL_STAGE, MEMBANK_EXPANSION));
|
|
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
|
|
if (mema_get_longest_free() < g_MemaLeastEverFree) {
|
|
g_MemaLeastEverFree = mema_get_longest_free();
|
|
}
|
|
|
|
sprintf(buffer, "mema: %d (%d)", mema_get_longest_free(), g_MemaLeastEverFree);
|
|
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
}
|
|
#endif
|
|
|
|
#if VERSION == VERSION_NTSC_BETA
|
|
if (debug_is_mem_info_enabled()) {
|
|
dhud_set_fg_colour(0xff, 0xff, 0xff, 0xff);
|
|
dhud_set_bg_colour(0, 0, 0, 0xff);
|
|
|
|
dhud_set_pos(30, line);
|
|
dhud_print_string("Mem Info");
|
|
line++;
|
|
|
|
dhud_set_pos(30, line);
|
|
dhud_print_string("memp: MP_LF_LEV");
|
|
line++;
|
|
|
|
onboard = memp_get_pool_free(MEMPOOL_STAGE, MEMBANK_ONBOARD);
|
|
expansion = memp_get_pool_free(MEMPOOL_STAGE, MEMBANK_EXPANSION);
|
|
sprintf(buffer, "F: %d %d", onboard, expansion);
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
|
|
onboard = memp_get_pool_size(MEMPOOL_STAGE, MEMBANK_ONBOARD);
|
|
expansion = memp_get_pool_size(MEMPOOL_STAGE, MEMBANK_EXPANSION);
|
|
sprintf(buffer, "S: %d %d", onboard, expansion);
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
|
|
over = memp_get_pool_size(MEMPOOL_STAGE, MEMBANK_EXPANSION)
|
|
- memp_get_pool_free(MEMPOOL_STAGE, MEMBANK_EXPANSION)
|
|
- memp_get_pool_free(MEMPOOL_STAGE, MEMBANK_ONBOARD);
|
|
|
|
if (over >= 0) {
|
|
sprintf(buffer, "Over: %d", over);
|
|
} else {
|
|
sprintf(buffer, "Free: %d", -over);
|
|
}
|
|
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
|
|
dhud_set_pos(30, line);
|
|
dhud_print_string("memp: MP_LF_ETER");
|
|
line++;
|
|
|
|
onboard = memp_get_pool_free(MEMPOOL_PERMANENT, MEMBANK_ONBOARD);
|
|
expansion = memp_get_pool_free(MEMPOOL_PERMANENT, MEMBANK_EXPANSION);
|
|
sprintf(buffer, "F: %d %d", onboard, expansion);
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
|
|
onboard = memp_get_pool_size(MEMPOOL_PERMANENT, MEMBANK_ONBOARD);
|
|
expansion = memp_get_pool_size(MEMPOOL_PERMANENT, MEMBANK_EXPANSION);
|
|
sprintf(buffer, "S: %d %d", onboard, expansion);
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
|
|
dhud_set_pos(30, line);
|
|
dhud_print_string("mema:");
|
|
line++;
|
|
|
|
sprintf(buffer, "LF: %d", mema_get_longest_free());
|
|
dhud_set_pos(31, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
|
|
sprintf(buffer, "Audio Free: %d", g_SndHeap.base + (g_SndHeap.len - (uintptr_t) g_SndHeap.cur));
|
|
dhud_set_pos(30, line);
|
|
dhud_print_string(buffer);
|
|
line++;
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
void *mema_alloc(u32 size)
|
|
{
|
|
u32 addr;
|
|
u32 diff;
|
|
s32 i;
|
|
|
|
struct memaspace *curr;
|
|
u32 bestdiff;
|
|
struct memaspace *best;
|
|
|
|
if (1);
|
|
|
|
curr = &g_MemaHeap.spaces[0];
|
|
bestdiff = 0xffffffff;
|
|
best = NULL;
|
|
|
|
// Iterate up to the first 16 spaces, looking for the
|
|
// smallest space that will accommodate the requested size.
|
|
for (i = 0; i < 16; i++, curr++) {
|
|
if (curr->size < size) {
|
|
continue;
|
|
}
|
|
|
|
if (curr->addr == 0xffffffff) {
|
|
// Reached the end
|
|
break;
|
|
}
|
|
|
|
diff = curr->size - size;
|
|
|
|
if (diff < bestdiff) {
|
|
bestdiff = diff;
|
|
best = curr;
|
|
|
|
// Stop looking if the space is small enough
|
|
#if VERSION >= VERSION_NTSC_1_0
|
|
if (diff < 64 || (IS8MB() && diff < size / 4))
|
|
#else
|
|
if (diff < 64 || diff < size / 4)
|
|
#endif
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (best == NULL) {
|
|
// Keep iterating until we find a space that is big enough to fit.
|
|
// The last space is marked as size 0xffffffff which prevents this loop
|
|
// from iterating past the end of the spaces array.
|
|
while (curr->size < size) {
|
|
curr++;
|
|
}
|
|
|
|
if (curr->addr == 0xffffffff) {
|
|
// There was no space, so attempt to free up some space
|
|
// by doing several defrag passes
|
|
for (i = 0; i < 8; i++) {
|
|
mema_defrag_pass(&g_MemaHeap);
|
|
}
|
|
|
|
curr = &g_MemaHeap.spaces[0];
|
|
|
|
while (curr->size < size) {
|
|
curr++;
|
|
}
|
|
|
|
if (curr->addr == 0xffffffff) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
best = curr;
|
|
}
|
|
|
|
addr = best->addr;
|
|
best->addr += size;
|
|
best->size -= size;
|
|
|
|
if (best->size == 0) {
|
|
best->addr = 0;
|
|
}
|
|
|
|
return (void *)addr;
|
|
}
|
|
|
|
/**
|
|
* Grow the allocation which currently *ends at* the given address.
|
|
*/
|
|
s32 mema_grow(s32 addr, u32 amount)
|
|
{
|
|
struct memaspace *curr = &g_MemaHeap.spaces[0];
|
|
|
|
while (curr->addr != -1) {
|
|
if (curr->addr == addr && curr->size >= amount) {
|
|
goto found;
|
|
}
|
|
|
|
curr++;
|
|
}
|
|
|
|
return 0;
|
|
|
|
found:
|
|
curr->addr += amount;
|
|
curr->size -= amount;
|
|
|
|
if (curr->size == 0) {
|
|
curr->addr = 0;
|
|
}
|
|
|
|
return addr;
|
|
}
|
|
|
|
void mema_free(void *addr, s32 size)
|
|
{
|
|
_mema_free((uintptr_t) addr, size);
|
|
}
|
|
|
|
void mema00012cd4(void)
|
|
{
|
|
// empty
|
|
}
|
|
|
|
/**
|
|
* Find and return the largest amount of contiguous free space in the pool.
|
|
* ie. the biggest allocation that mema can currently make.
|
|
*/
|
|
s32 mema_get_longest_free(void)
|
|
{
|
|
struct memaspace *curr;
|
|
s32 biggest = 0;
|
|
|
|
mema_defrag();
|
|
|
|
curr = &g_MemaHeap.spaces[0];
|
|
|
|
while (curr->addr != -1) {
|
|
if (curr->size > biggest) {
|
|
biggest = curr->size;
|
|
}
|
|
|
|
curr++;
|
|
}
|
|
|
|
if (biggest) {
|
|
return biggest;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool mema_realloc(s32 addr, u32 oldsize, u32 newsize)
|
|
{
|
|
if (newsize > oldsize) {
|
|
if (!mema_grow(addr + oldsize, newsize - oldsize)) {
|
|
return false;
|
|
}
|
|
} else if (oldsize > newsize) {
|
|
mema_free((void *)(addr + newsize), oldsize - newsize);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
u32 mema_get_size(void)
|
|
{
|
|
return g_MemaHeapSize;
|
|
}
|