// very simple heap allocation // todo (maybe): // - guard marks around blocks // - linked list // // by oggzee // note: // some regions of MEM2 get reset during IOS reload // MEM1 will be reset before game load #include #include #include #include #include #include #include "mem.h" #include "util.h" // required for memcheck #include "console.h" #include "cache.h" static heap mem1; static heap mem2; static void *mem2_start = NULL; inline size_t xalign_up(int a, size_t s) { if (s == 0) return a; s += a - 1; s &= ~(a - 1); return s; } inline size_t xalign_down(int a, size_t s) { s &= ~(a - 1); return s; } inline size_t align_up(size_t s) { return xalign_up(ALIGN_VAL, s); } inline size_t align_down(size_t s) { return xalign_down(ALIGN_VAL, s); } mem_blk* blk_find_size(blk_list *bl, int size) { int i; for (i=0; i < bl->num; i++) { if (size <= bl->list[i].size) { return &bl->list[i]; } } return NULL; } mem_blk* blk_find_ptr(blk_list *bl, void *ptr) { int i; if (ptr == NULL) return NULL; for (i=0; i < bl->num; i++) { if (ptr == bl->list[i].ptr) { return &bl->list[i]; } } return NULL; } mem_blk* blk_find_ptr_end(blk_list *bl, void *ptr) { int i; for (i=0; i < bl->num; i++) { if (ptr == bl->list[i].ptr + bl->list[i].size) { return &bl->list[i]; } } return NULL; } // find first blk that is after ptr // (so that we can insert before it to keep blocks sorted) mem_blk* blk_find_ptr_after(blk_list *bl, void *ptr) { int i; for (i=0; i < bl->num; i++) { if (ptr > bl->list[i].ptr) { return &bl->list[i]; } } if (bl->num >= MAX_MEM_BLK) return NULL; return &bl->list[bl->num]; // return one after last } // inser before blk b mem_blk* blk_insert(blk_list *bl, mem_blk *b) { int i, n; if (!b) return NULL; if (bl->num >= MAX_MEM_BLK) { // list full // fatal error return NULL; } i = b - bl->list; // index of b in list if (i < 0 || i > bl->num) { // out of range // fatal error return NULL; } n = bl->num - i; // num of blks after b bl->num++; if (n) { // move blks from b up memmove(b+1, b, n*sizeof(mem_blk)); } // clear current one (now unused) memset(b, 0, sizeof(mem_blk)); return b; } void blk_remove(blk_list *bl, mem_blk *b) { int i, n; if (!b) { // fatal error return; } if (bl->num == 0) { // fatal error return; } i = b - bl->list; // index of b in list if (i < 0 || i >= bl->num) { // out of range // fatal error return; } bl->num--; n = bl->num - i; // num of blks after b if (n) { // move blks after b down memmove(b, b+1, n*sizeof(mem_blk)); // clear last one (now unused) memset(b+n, 0, sizeof(mem_blk)); } } mem_blk* blk_merge_add(blk_list *list, mem_blk *ab) { mem_blk *fb; // try to extend start of an existing free block fb = blk_find_ptr(list, ab->ptr + ab->size); if (fb) { fb->ptr -= ab->size; fb->size += ab->size; } else { // try to extend end of an existing free block fb = blk_find_ptr_end(list, ab->ptr); if (fb) { fb->size += ab->size; } else { // insert a new free block fb = blk_find_ptr_after(list, ab->ptr); if (!fb) { // fatal return NULL; } fb = blk_insert(list, fb); if (!fb) { // fatal return NULL; } *fb = *ab; } } return fb; } void *_heap_alloc(heap *h, int size) { mem_blk *ab, *fb; // align size size = align_up(size); if (h->used_list.num >= MAX_MEM_BLK - 1) return NULL; fb = blk_find_size(&h->free_list, size); if (!fb) return NULL; ab = &h->used_list.list[ h->used_list.num ]; h->used_list.num++; ab->ptr = fb->ptr; ab->size = size; fb->ptr += size; fb->size -= size; if (fb->size == 0) { blk_remove(&h->free_list, fb); } return ab->ptr; } void *heap_alloc(heap *h, int size) { LWP_MutexLock(h->mutex); void *ret = _heap_alloc(h, size); LWP_MutexUnlock(h->mutex); return ret; } int _heap_free(heap *h, void *ptr) { mem_blk *ab, *fb; if (!ptr) return 0; ab = blk_find_ptr(&h->used_list, ptr); if (!ab) return -1; // try to extend start of an existing free block fb = blk_merge_add(&h->free_list, ab); if (!fb) { // fatal return -1; } blk_remove(&h->used_list, ab); return 0; } int heap_free(heap *h, void *ptr) { LWP_MutexLock(h->mutex); int ret = _heap_free(h, ptr); LWP_MutexUnlock(h->mutex); return ret; } // resize an existing allocation, without changing location // return: // 0 on success // -1 on error // >0 the max delta that is possible to resize by int _heap_resize(heap *h, void *ptr, int size) { mem_blk *ab, *fb; mem_blk bb; int delta; // align size size = align_up(size); // new allocation if (ptr == NULL) return -1; // find existing ab = blk_find_ptr(&h->used_list, ptr); if (!ab) return -1; // invalid ptr // size equal? - do nothing if (size == ab->size) return 0; // size smaller if (size < ab->size) { // insert or merge a free block bb.ptr = ab->ptr + size; bb.size = ab->size - size; fb = blk_merge_add(&h->free_list, &bb); if (!fb) { // fatal return -1; } ab->size = size; return 0; } // size larger delta = size - ab->size; // find a free block at the end fb = blk_find_ptr(&h->free_list, ptr + ab->size); if (!fb) return -1; if (delta > fb->size) { // can't extend // return by how much it's possible to extend if (fb->size > 0) return fb->size; return -1; } // extend memset(fb->ptr, 0, delta); fb->ptr += delta; fb->size -= delta; ab->size = size; if (fb->size == 0) { blk_remove(&h->free_list, fb); } return 0; } int heap_resize(heap *h, void *ptr, int size) { LWP_MutexLock(h->mutex); int ret = _heap_resize(h, ptr, size); LWP_MutexUnlock(h->mutex); return ret; } void *_heap_realloc(heap *h, void *ptr, int size) { mem_blk *ab; void *new_ptr; int delta; // free if size 0 if (size == 0) { heap_free(h, ptr); return NULL; } // align size size = align_up(size); // new allocation if (ptr == NULL) { return heap_alloc(h, size); } // try resize delta = heap_resize(h, ptr, size); if (delta == 0) { // ok return ptr; } // can't resize, try realloc ab = blk_find_ptr(&h->used_list, ptr); if (!ab) { // invalid ptr return NULL; } new_ptr = heap_alloc(h, size); if (!new_ptr) { // out of mem return NULL; } // copy to new location delta = size - ab->size; if (delta > 0) { memcpy(new_ptr, ptr, ab->size); memset(new_ptr + ab->size, 0, delta); } else { memcpy(new_ptr, ptr, size); } // free old location heap_free(h, ptr); return new_ptr; } void *heap_realloc(heap *h, void *ptr, int size) { LWP_MutexLock(h->mutex); void *ret = _heap_realloc(h, ptr, size); LWP_MutexUnlock(h->mutex); return ret; } void heap_init(heap *h, void *ptr, int size) { int d; // init memset(h, 0, sizeof(heap)); h->start = ptr; h->size = size; // align ptr void *a_ptr = (void*)align_up((size_t)ptr); d = a_ptr - ptr; size -= d; size = align_down(size); h->free_list.num = 1; h->free_list.list[0].ptr = a_ptr; h->free_list.list[0].size = size; h->mutex = LWP_MUTEX_NULL; LWP_MutexInit(&h->mutex, true); // true: allow recursive // 0.4 sec for 60mb memset(ptr, 0, size); DCFlushRange(ptr, size); } int heap_ptr_inside(heap *h, void *ptr) { return (ptr >= h->start && ptr < h->start + h->size); } void heap_stat(heap *h, heap_stats *s) { int i; void *ptr; memset(s, 0, sizeof(heap_stats)); s->highptr = h->start; for (i=0; iused_list.num; i++) { s->used += h->used_list.list[i].size; ptr = h->used_list.list[i].ptr + h->used_list.list[i].size; if (ptr > s->highptr) s->highptr = ptr; } for (i=0; ifree_list.num; i++) { s->free += h->free_list.list[i].size; } s->size = s->used + s->free; } void mem_init() { void *m1_start = (void*)0x80004000; void *m1_end = (void*)0x80a00000; void *m2_start; u32 m2_size; //m2_size = SYS_GetArena2Hi() - SYS_GetArena2Lo(); m2_size = SYS_GetArena2Size(); // leave 2MB of mem2 (sys_arena2) free // 211k will be used by wpad, 64k net // if less is free and devkit>17 it crashes at net download m2_size -= 2*1024*1024; // align to 32 bytes m2_size = align_down(m2_size); m2_start = SYS_AllocArena2MemLo(m2_size, 32); heap_init(&mem1, m1_start, m1_end - m1_start); heap_init(&mem2, m2_start, m2_size); } bool mem_inside(int pool, void *ptr) { if (pool & 1) { if (heap_ptr_inside(&mem1, ptr)) { return true; } } if (pool & 2) { if (heap_ptr_inside(&mem2, ptr)) { return true; } } return false; } void *mem1_alloc(int size) { return heap_alloc(&mem1, size); } void *mem2_alloc(int size) { return heap_alloc(&mem2, size); } void *mem_alloc(int size) { void *ptr; // align size size = align_up(size); // mem2 ptr = mem2_alloc(size); if (ptr) return ptr; // mem1 ptr = mem1_alloc(size); if (ptr) return ptr; // sys ptr = memalign(32, size); return ptr; } void *mem_calloc(int size) { void *ptr = mem_alloc(size); if (ptr) memset(ptr, 0, size); return ptr; } // defaults to MEM2 void *mem_realloc(void *ptr, int size) { // free if size 0 if (size == 0) { mem_free(ptr); return NULL; } // align size size = align_up(size); // first time if (ptr == NULL) { return mem_alloc(size); } // mem2 if (heap_ptr_inside(&mem2, ptr)) { return heap_realloc(&mem2, ptr, size); } // mem1 if (heap_ptr_inside(&mem1, ptr)) { return heap_realloc(&mem1, ptr, size); } // sys // note, this one doesn't clean up newly allocated // part of mem while the above do return realloc(ptr, size); } // defaults to MEM1 void *mem1_realloc(void *ptr, int size) { if (ptr) return mem_realloc(ptr, size); return heap_alloc(&mem1, size); } void mem_free(void *ptr) { if (ptr == NULL) return; // mem2 if (heap_ptr_inside(&mem2, ptr)) { heap_free(&mem2, ptr); return; } // mem1 if (heap_ptr_inside(&mem1, ptr)) { heap_free(&mem1, ptr); return; } // sys free(ptr); } int mem_resize(void *ptr, int size) { if (ptr == NULL) return -1; // mem2 if (heap_ptr_inside(&mem2, ptr)) { return heap_resize(&mem2, ptr, size); } // mem1 if (heap_ptr_inside(&mem1, ptr)) { return heap_resize(&mem1, ptr, size); } // sys return -1; } #if 0 struct mallinfo { int arena; /* total space allocated from system */ int ordblks; /* number of non-inuse chunks */ int smblks; /* unused -- always zero */ int hblks; /* number of mmapped regions */ int hblkhd; /* total space in mmapped regions */ int usmblks; /* unused -- always zero */ int fsmblks; /* unused -- always zero */ int uordblks; /* total allocated space */ int fordblks; /* total non-inuse space */ int keepcost; /* top-most, releasable (via malloc_trim) space */ }; #endif void print_mallinfo() { struct mallinfo m = mallinfo(); #define PM(X) printf("%8s : %d\n", #X, m.X) PM(arena); PM(ordblks); PM(smblks); PM(hblks); PM(hblkhd); PM(usmblks); PM(fsmblks); PM(uordblks); PM(fordblks); PM(keepcost); #undef PM } void mem_stat_str(char *buffer, int size) { heap_stats hs1, hs2; heap_stat(&mem1, &hs1); heap_stat(&mem2, &hs2); #define fMB (1024.0 * 1024.0) void *p; int msize; struct mallinfo m = mallinfo(); for (msize = 10*1024*1024; msize > 0; msize -= 16*1024) { p = memalign(32, msize); if (p) { m = mallinfo(); free(p); break; } } *buffer = 0; snprintf(buffer, size, "%slibc: s:%5.2f u:%5.2f f:%5.2f mx:%.2f\n", buffer, m.arena / fMB, (m.uordblks-msize) / fMB, (m.fordblks+msize) / fMB, msize / fMB); snprintf(buffer, size, "%smem1: s:%5.2f u:%5.2f f:%5.2f t:%d,%d\n", buffer, hs1.size / fMB, hs1.used / fMB, hs1.free / fMB, mem1.used_list.num, mem1.free_list.num); snprintf(buffer, size, "%smem2: s:%5.2f u:%5.2f f:%5.2f t:%d,%d\n", buffer, hs2.size / fMB, hs2.used / fMB, hs2.free / fMB, mem2.used_list.num, mem2.free_list.num); snprintf(buffer, size, "%sm1+2: s:%5.2f u:%5.2f f:%5.2f\n", buffer, (hs1.size+hs2.size) / fMB, (hs1.used+hs2.used) / fMB, (hs1.free+hs2.free) / fMB); snprintf(buffer, size, "%stotl: s:%5.2f u:%5.2f f:%5.2f\n", buffer, (hs1.size+hs2.size + m.arena) / fMB, (hs1.used+hs2.used + m.uordblks-msize) / fMB, (hs1.free+hs2.free + m.fordblks+msize) / fMB); } void lib_info_str(char *str, int size) { #ifdef _V_OGC_SVN snprintf(str, size, "libOGC %s ", "svn" _V_OGC_SVN); str_seek_end(&str, &size); #else snprintf(str, size, "libOGC %d.%d.%d ", _V_MAJOR_, _V_MINOR_, _V_PATCH_); str_seek_end(&str, &size); #endif snprintf(str, size, "devkitPPC %d ", DEVKITPPCVER); str_seek_end(&str, &size); snprintf(str, size, "(gcc%d.%d.%d)", __GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__); str_seek_end(&str, &size); //__VERSION__ snprintf(str, size, " %s\n", CCOPT); str_seek_end(&str, &size); } void lib_mem_stat_str(char *str, int size) { lib_info_str(str, size); str_seek_end(&str, &size); mem_stat_str(str, size); str_seek_end(&str, &size); cache_stats(str, size); } void mem_statf(FILE *f) { char buffer[1000]; lib_mem_stat_str(buffer, sizeof(buffer)); fprintf(f, "\n%s", buffer); } void mem_stat() { mem_statf(stdout); } // moved from util.c: void* LARGE_memalign(size_t align, size_t size) { return mem_alloc(size); } void* LARGE_alloc(size_t size) { return mem_alloc(size); } void* LARGE_realloc(void *ptr, size_t size) { return mem_realloc(ptr, size); } void LARGE_free(void *ptr) { return mem_free(ptr); } /* size_t LARGE_used() { size_t size = SYS_GetArena2Lo() - (void*)0x90000000; return size; } */ void memstat2() { void *m2base = (void*)0x90000000; void *m2lo = SYS_GetArena2Lo(); void *m2hi = SYS_GetArena2Hi(); void *ipclo = __SYS_GetIPCBufferLo(); void *ipchi = __SYS_GetIPCBufferHi(); size_t isize = ipchi - ipclo; printf("\n"); printf("MEM2: %p %p %p\n", m2base, m2lo, m2hi); printf("s:%d u:%d f:%d\n", m2hi - m2base, m2lo - m2base, m2hi - m2lo); printf("s:%.2f MB u:%.2f MB f:%.2f MB\n", (float)(m2hi - m2base)/1024/1024, (float)(m2lo - m2base)/1024/1024, (float)(m2hi - m2lo)/1024/1024); printf("IPC: %p %p %d\n", ipclo, ipchi, isize); } // save M2 ptr void util_init() { void _con_alloc_buf(s32 *conW, s32 *conH); _con_alloc_buf(NULL, NULL); //mem2_start = SYS_GetArena2Lo(); heap_stats hs; heap_stat(&mem2, &hs); mem2_start = hs.highptr; } void util_clear() { // game start: 0x80004000 // game end: 0x80a00000 approx void *game_start = (void*)0x80004000; void *game_end = (void*)0x80a00000; u32 size; // unload unifont console_set_unifont(NULL, 0); // clear mem1 main size = game_end - game_start; //printf("Clear %p [%x]\n", game_start, size); __console_flush(0); memset(game_start, 0, size); DCFlushRange(game_start, size); // clear mem2 if (mem2_start == NULL) return; size = SYS_GetArena2Lo() - mem2_start; //printf("Clear %p [%x]\n", mem2_start, size); __console_flush(0); sleep(2); memset(mem2_start, 0, size); DCFlushRange(mem2_start, size); // clear mem1 libc heap // find appropriate size void *p; for (size = 10*1024*1024; size > 0; size -= 128*1024) { p = memalign(32, size); if (p) { //printf("Clear %p [%x] %p\n", p, size, p+size); //__console_flush(0); sleep(2); memset(p, 0, size); DCFlushRange(p, size); free(p); break; } } } void obb_init(obj_block *obb) { memset(obb, 0, sizeof(obj_block)); } void *obb_alloc(obj_block *obb, obj_allocator *alloc, int size) { void *ptr; int reqsize; int newsize; int x; size = xalign_up(4, size); reqsize = obb->used + size; if (!obb->ptr) { // alloc newsize = reqsize + alloc->chunk; ptr = alloc->m_realloc(NULL, newsize); if (ptr == NULL) { newsize = reqsize; ptr = alloc->m_realloc(NULL, newsize); } if (ptr == NULL) return NULL; obb->size = newsize; obb->ptr = ptr; } if (reqsize > obb->size) { // resize newsize = reqsize + alloc->chunk; x = alloc->m_resize(obb->ptr, newsize); if (x > 0 && x >= size) { newsize = obb->size + x; x = alloc->m_resize(obb->ptr, newsize); } if (x != 0) return NULL; // resize failed obb->size = newsize; } ptr = obb->ptr + obb->used; obb->used += size; return ptr; } void obb_freeall(obj_block *obb, obj_allocator *alloc) { if (obb->ptr) { alloc->m_realloc(obb->ptr, 0); } memset(obb, 0, sizeof(obj_block)); } void obs_init(obj_stack *obs, int chunk, void* (*m_realloc)(void *ptr, int size), int (*m_resize)(void *ptr, int size)) { memset(obs, 0, sizeof(obj_stack)); obs->alloc.chunk = chunk; obs->alloc.m_realloc = m_realloc; obs->alloc.m_resize = m_resize; obb_init(&obs->block[0]); obs->num = 1; } void *obs_alloc(obj_stack *obs, int size) { void *ptr; obj_block *obb; if (obs->num <= 0) return NULL; obb = &obs->block[obs->num - 1]; ptr = obb_alloc(obb, &obs->alloc, size); if (ptr) return ptr; // create new block if (obs->num >= OBS_MAX_BLOCKS) return NULL; obs->num++; obb = &obs->block[obs->num - 1]; obb_init(obb); ptr = obb_alloc(obb, &obs->alloc, size); if (ptr) return ptr; // release block obb_freeall(obb, &obs->alloc); obs->num--; return NULL; } void obs_freeall(obj_stack *obs) { int i; for (i=0; inum; i++) { obb_freeall(&obs->block[i], &obs->alloc); } memset(obs, 0, sizeof(obj_stack)); }