3DS: the runtime moves to os/ctr, linear allocator in C

The tree's 3DS layer (the custom crt0 and link spec, system and memory
setup, svchax, the old GPU command layer and the 3dsx/CIA launchers)
lives under os/ctr, beside os/gekko and os/wiiu.

The linear heap allocator is C: the same first-fit pool, so buffers
land where they did, with live allocations found by address in an
open-addressing table instead of a per-allocation tree node, and list
nodes taken from cache-line-aligned slabs and reused. On the same
trace it runs about half the instructions and practically no
malloc/free calls. With the whole heap allocated, a further allocation
now fails instead of starting the heap over and handing out its first
address again, and the free-space queries no longer fault.

samples/frontend/ctr_linear checks placement, alignment, coalescing,
the full-heap case and pages handed back from the heap's end.
This commit is contained in:
LibretroAdmin authored and libretroadmin committed 2026-10-29 18:10:30 +01:00
1 parent 66977c726a
commit bae87174dc
31 files changed
+738 -311

No files matched your search

+13 -14
View File
@@ -30,15 +30,15 @@ APP_USE_SVCHAX = 0
include pkg/ctr/Makefile.cores
OBJ :=
OBJ += ctr/ctr_system.o
OBJ += ctr/ctr_memory.o
OBJ += ctr/ctr_linear.o
OBJ += ctr/gpu_old.o
OBJ += ctr/exec-3dsx/exec_cia.o \
ctr/exec-3dsx/exec_3dsx.o \
ctr/exec-3dsx/mini-hb-menu/launch.o \
ctr/exec-3dsx/mini-hb-menu/loaders/rosalina.o \
ctr/exec-3dsx/mini-hb-menu/loaders/hax2.o
OBJ += os/ctr/ctr_system.o
OBJ += os/ctr/ctr_memory.o
OBJ += os/ctr/ctr_linear.o
OBJ += os/ctr/gpu_old.o
OBJ += os/ctr/exec-3dsx/exec_cia.o \
os/ctr/exec-3dsx/exec_3dsx.o \
os/ctr/exec-3dsx/mini-hb-menu/launch.o \
os/ctr/exec-3dsx/mini-hb-menu/loaders/rosalina.o \
os/ctr/exec-3dsx/mini-hb-menu/loaders/hax2.o
ifeq ($(APP_BIG_TEXT_SECTION), 1)
APP_USE_SVCHAX = 1
@@ -46,7 +46,7 @@ ifeq ($(APP_BIG_TEXT_SECTION), 1)
endif
ifeq ($(APP_USE_SVCHAX), 1)
OBJ += ctr/ctr_svchax.o
OBJ += os/ctr/ctr_svchax.o
endif
DEFINES :=
@@ -208,7 +208,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
CFLAGS += -Werror=implicit-function-declaration
ASFLAGS := -g $(ARCH) -O3
LDFLAGS += -specs=ctr/3dsx_custom.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LDFLAGS += -specs=os/ctr/3dsx_custom.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
CFLAGS += -std=gnu99
CFLAGS += -ffunction-sections -fdata-sections
@@ -366,7 +366,7 @@ else
endif
$(DEVKITTOOLS)/bin/3dsxtool $< $@ $(_3DSXFLAGS)
$(TARGET).elf: ctr/3dsx_custom_crt0.o
$(TARGET).elf: os/ctr/3dsx_custom_crt0.o
@$(if $(Q), $(shell echo echo LD $@),)
$(Q)$(LD) $(LDFLAGS) $(OBJ) $(LIBDIRS) $(LIBS) -o $@
$(NM) -CSn $@ > $(notdir $*.lst)
@@ -393,8 +393,7 @@ clean:
$(Q)rm -f $(TARGET).smdh
$(Q)rm -f $(TARGET).bnr
$(Q)rm -f $(TARGET).icn
$(Q)rm -f ctr/ctr_config_*.o
$(Q)rm -f ctr/3dsx_custom_crt0.o
$(Q)rm -f os/ctr/3dsx_custom_crt0.o
$(Q)rm -f gfx/drivers/ctr_shaders/*.shbin gfx/drivers/ctr_shaders/*.shbin.s
.PHONY: clean
+11 -12
View File
@@ -20,9 +20,9 @@ APP_RSF = pkg/ctr/tools/template.rsf
APP_SYSTEM_MODE = 64MB
APP_SYSTEM_MODE_EXT = 124MB
OBJ := ctr/ctr_system.o \
ctr/ctr_memory.o \
ctr/ctr_linear.o \
OBJ := os/ctr/ctr_system.o \
os/ctr/ctr_memory.o \
os/ctr/ctr_linear.o \
frontend/frontend_salamander.o \
frontend/frontend_driver.o \
frontend/drivers/platform_ctr.o \
@@ -45,11 +45,11 @@ OBJ := ctr/ctr_system.o \
libretro-common/time/rtime.o \
verbosity.o
OBJ += ctr/exec-3dsx/exec_cia.o \
ctr/exec-3dsx/exec_3dsx.o \
ctr/exec-3dsx/mini-hb-menu/launch.o \
ctr/exec-3dsx/mini-hb-menu/loaders/rosalina.o \
ctr/exec-3dsx/mini-hb-menu/loaders/hax2.o
OBJ += os/ctr/exec-3dsx/exec_cia.o \
os/ctr/exec-3dsx/exec_3dsx.o \
os/ctr/exec-3dsx/mini-hb-menu/launch.o \
os/ctr/exec-3dsx/mini-hb-menu/loaders/rosalina.o \
os/ctr/exec-3dsx/mini-hb-menu/loaders/hax2.o
ifeq ($(strip $(DEVKITPRO)),)
$(error "Please set DEVKITPRO in your environment. export DEVKITPRO=<path to>devkitpro")
@@ -110,7 +110,7 @@ CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
CFLAGS += -Werror=implicit-function-declaration
ASFLAGS := -g $(ARCH) -O3
LDFLAGS += -specs=ctr/3dsx_custom.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LDFLAGS += -specs=os/ctr/3dsx_custom.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
CFLAGS += -std=gnu99
@@ -209,7 +209,7 @@ else
endif
$(DEVKITTOOLS)/bin/3dsxtool $< $@ $(_3DSXFLAGS)
$(TARGET).elf: ctr/3dsx_custom_crt0.o
$(TARGET).elf: os/ctr/3dsx_custom_crt0.o
$(LD) $(LDFLAGS) $(OBJ) $(LIBDIRS) $(LIBS) -o $@
$(NM) -CSn $@ > $(notdir $*.lst)
@@ -234,7 +234,6 @@ clean:
rm -f $(TARGET).smdh
rm -f $(TARGET).bnr
rm -f $(TARGET).icn
rm -f ctr/ctr_config_*.o
rm -f ctr/3dsx_custom_crt0.o
rm -f os/ctr/3dsx_custom_crt0.o
.PHONY: clean
+1 -1
View File
@@ -21,7 +21,7 @@
#include <malloc.h>
#include "../audio_driver.h"
#include "../../ctr/ctr_debug.h"
#include "../../os/ctr/ctr_debug.h"
#include "../../retroarch.h"
#include "../../verbosity.h"
-276
View File
@@ -1,276 +0,0 @@
/* from https://github.com/smealum/ctrulib
* modified to allow reducing __linear_heap_size at runtime */
#include <3ds.h>
#include <stdlib.h>
#include <3ds/util/rbtree.h>
#include "ctr_debug.h"
struct MemChunk
{
u8* addr;
u32 size;
};
struct MemBlock
{
MemBlock *prev, *next;
u8* base;
u32 size;
static MemBlock* Create(u8* base, u32 size)
{
auto b = (MemBlock*)malloc(sizeof(MemBlock));
if (!b)
return nullptr;
b->prev = nullptr;
b->next = nullptr;
b->base = base;
b->size = size;
return b;
}
};
struct MemPool
{
MemBlock *first, *last;
bool Ready() { return first != nullptr; }
void AddBlock(MemBlock* blk)
{
blk->prev = last;
if (last) last->next = blk;
if (!first) first = blk;
last = blk;
}
void DelBlock(MemBlock* b)
{
auto prev = b->prev, &pNext = prev ? prev->next : first;
auto next = b->next, &nPrev = next ? next->prev : last;
pNext = next;
nPrev = prev;
free(b);
}
void InsertBefore(MemBlock* b, MemBlock* p)
{
auto prev = b->prev, &pNext = prev ? prev->next : first;
b->prev = p;
p->next = b;
p->prev = prev;
pNext = p;
}
void InsertAfter(MemBlock* b, MemBlock* n)
{
auto next = b->next, &nPrev = next ? next->prev : last;
b->next = n;
n->prev = b;
n->next = next;
nPrev = n;
}
void CoalesceRight(MemBlock* b);
bool Allocate(MemChunk& chunk, u32 size, int align);
void Deallocate(const MemChunk& chunk);
void Destroy()
{
MemBlock* next = nullptr;
for (auto b = first; b; b = next)
{
next = b->next;
free(b);
}
first = nullptr;
last = nullptr;
}
#if 0
void Dump(const char* title);
#endif
u32 GetFreeSpace();
};
static rbtree_t sAddrMap;
struct addrMapNode
{
rbtree_node node;
MemChunk chunk;
};
#define getAddrMapNode(x) rbtree_item((x), addrMapNode, node)
static int addrMapNodeComparator(const rbtree_node_t* _lhs, const rbtree_node_t* _rhs)
{
auto lhs = getAddrMapNode(_lhs)->chunk.addr;
auto rhs = getAddrMapNode(_rhs)->chunk.addr;
if (lhs < rhs)
return -1;
if (lhs > rhs)
return 1;
return 0;
}
static void addrMapNodeDestructor(rbtree_node_t* a)
{
free(getAddrMapNode(a));
}
static addrMapNode* getNode(void* addr)
{
addrMapNode n;
n.chunk.addr = (u8*)addr;
auto p = rbtree_find(&sAddrMap, &n.node);
return p ? getAddrMapNode(p) : nullptr;
}
static addrMapNode* newNode(const MemChunk& chunk)
{
auto p = (addrMapNode*)malloc(sizeof(addrMapNode));
if (!p) return nullptr;
p->chunk = chunk;
return p;
}
static void delNode(addrMapNode* node)
{
rbtree_remove(&sAddrMap, &node->node, addrMapNodeDestructor);
}
extern u32 __linear_heap, __linear_heap_size;
static MemPool sLinearPool;
static u32 sLinearPool_maxaddr;
static bool linearInit(void)
{
auto blk = MemBlock::Create((u8*)__linear_heap, __linear_heap_size);
if (blk)
{
sLinearPool.AddBlock(blk);
sLinearPool_maxaddr = __linear_heap;
rbtree_init(&sAddrMap, addrMapNodeComparator);
return true;
}
return false;
}
void* linearMemAlign(size_t len, size_t alignment)
{
int shift;
/* Enforce minimum alignment */
if (alignment < 16)
alignment = 16;
/* Convert alignment to shift amount */
for (shift = 4; shift < 32; shift ++)
{
if ((1U<<shift) == alignment)
break;
}
if (shift == 32) /* Invalid alignment */
return nullptr;
/* Initialize the pool if it is not ready */
if (!sLinearPool.Ready() && !linearInit())
return nullptr;
/* Allocate the chunk */
MemChunk chunk;
if (!sLinearPool.Allocate(chunk, len, shift))
return nullptr;
auto node = newNode(chunk);
if (!node)
{
sLinearPool.Deallocate(chunk);
return nullptr;
}
if (!rbtree_insert(&sAddrMap, &node->node)) {
sLinearPool.Deallocate(chunk);
return nullptr;
}
if (sLinearPool_maxaddr < (u32)sLinearPool.last->base)
sLinearPool_maxaddr = (u32)sLinearPool.last->base;
return chunk.addr;
}
void* linearAlloc(size_t len)
{
#if 0
if (ctrConsole && ctrConsole->consoleInitialised)
{
printf("linearAlloc : 0x%08X\n", len);
DEBUG_HOLD();
}
#endif
return linearMemAlign(len, 0x80);
}
void* linearRealloc(void* mem, size_t len) { return NULL; }
void linearFree(void* mem)
{
auto node = getNode(mem);
if (!node)
return;
/* Free the chunk */
sLinearPool.Deallocate(node->chunk);
/* Free the node */
delNode(node);
}
u32 linearSpaceFree(void)
{
return sLinearPool.GetFreeSpace();
}
extern "C" u32 ctr_get_linear_free(void)
{
if (sLinearPool.last->base + sLinearPool.last->size != (u8*)__linear_heap + __linear_heap_size)
return 0;
return sLinearPool.last->size;
}
extern "C" u32 ctr_get_linear_unused(void)
{
return __linear_heap + __linear_heap_size - sLinearPool_maxaddr;
}
extern "C" void ctr_linear_free_pages(u32 pages)
{
u32 tmp, size;
if (sLinearPool.last->base + sLinearPool.last->size != (u8*)__linear_heap + __linear_heap_size)
return;
size = pages << 12;
if (size > sLinearPool.last->size)
return;
sLinearPool.last->size -= size;
__linear_heap_size -= size;
svcControlMemory(&tmp, __linear_heap + __linear_heap_size, 0x0, size,
MEMOP_FREE, (MemPerm)(MEMPERM_READ | MEMPERM_WRITE));
#if 0
printf("l:0x%08X-->0x%08X(-0x%08X) \n", sLinearPool.last->size + size, sLinearPool.last->size, size);
DEBUG_HOLD();
#endif
}
extern "C" void ctr_linear_get_stats(void)
{
printf("last:\n");
printf("0x%08X --> 0x%08X (0x%08X) \n", sLinearPool.last->base,
sLinearPool.last->base + sLinearPool.last->size, sLinearPool.last->size);
printf("free: 0x%08X unused: 0x%08X \n", ctr_get_linear_unused(), ctr_get_linear_free());
}
+3 -3
View File
@@ -47,9 +47,9 @@
#include "../../retroarch.h"
#include "../../verbosity.h"
#include "ctr/ctr_debug.h"
#include "ctr/exec-3dsx/exec_3dsx.h"
#include "ctr/exec-3dsx/exec_cia.h"
#include "os/ctr/ctr_debug.h"
#include "os/ctr/exec-3dsx/exec_3dsx.h"
#include "os/ctr/exec-3dsx/exec_cia.h"
#ifndef IS_SALAMANDER
#ifdef HAVE_MENU
+1 -1
View File
@@ -43,7 +43,7 @@
#include "../font_driver.h"
#include "../video_views.h"
#define DEPRECATED
#include "../../ctr/gpu_old.h"
#include "../../os/ctr/gpu_old.h"
#undef DEPRECATED
#include "ctr_gu.h"
+1 -1
View File
@@ -23,7 +23,7 @@
#include <string.h>
#include <retro_inline.h>
#include "ctr/ctr_debug.h"
#include "os/ctr/ctr_debug.h"
#define VIRT_TO_PHYS(vaddr) \
(((u32)(vaddr)) >= 0x14000000 && ((u32)(vaddr)) < 0x1c000000)?(void*)((u32)(vaddr) + 0x0c000000):\
@@ -4,4 +4,4 @@
%(old_link) -T 3dsx.ld%s -d --emit-relocs --use-blx --gc-sections
*startfile:
ctr/3dsx_custom_crt0%O%s crti%O%s crtbegin%O%s
os/ctr/3dsx_custom_crt0%O%s crti%O%s crtbegin%O%s
File renamed without changes.
File renamed without changes.
+435
View File
@@ -0,0 +1,435 @@
/* The linear heap allocator, in place of libctru's, so that the end of
* the linear heap can be handed back to the system at runtime
* (ctr_linear_free_pages).
*
* Free space is an address-ordered list of blocks, allocated first fit,
* the way libctru's MemPool does, so buffers land where they always
* have. Live allocations are found by address in an open-addressing
* table. List nodes come from slabs, so a walk of the list stays within
* a few cache lines, and a node a split or a free gives up is kept for
* the next one rather than going back to malloc. */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <3ds.h>
#include <boolean.h>
#include <retro_inline.h>
#define LINEAR_LIVE_MIN 64u
#define LINEAR_SLAB 64u /* nodes per slab */
#define LINEAR_LINE 32u /* the ARM11's L1 line */
#if defined(__GNUC__)
#define LINEAR_COLD __attribute__((noinline, cold))
#else
#define LINEAR_COLD
#endif
/* What a search reads - next, base, size - leads */
typedef struct linear_block
{
struct linear_block *next;
u32 base;
u32 size;
struct linear_block *prev;
} linear_block_t;
/* addr 0 marks a free slot; the linear heap is never at 0 */
typedef struct
{
u32 addr;
u32 size;
} linear_live_t;
extern u32 __linear_heap, __linear_heap_size;
/* The free blocks form a ring through end, a block at 0xFFFFFFFF that
* every allocation fits (a padded size and its alignment waste come to
* less than its 0xFFFFFFFF bytes) and that lies past every address, so
* neither a search nor a free needs to test for the end of the list: a
* search stops at end at the latest, a free goes in before it at the
* latest, and no chunk ends where it starts to join it. end.next is the
* first block and end.prev the last, end itself when there is none. */
static struct
{
linear_block_t end;
linear_block_t *spare; /* nodes kept for reuse, chained by next */
void *slabs; /* each slab's first word is the next */
linear_live_t *live;
u32 live_mask;
u32 live_count;
u32 maxaddr;
bool ready;
} linear;
#define LINEAR_END (&linear.end)
u32 ctr_get_linear_free(void);
u32 ctr_get_linear_unused(void);
void ctr_linear_free_pages(u32 pages);
void ctr_linear_get_stats(void);
/* ---- free list ---- */
/* A slab of nodes, starting on a cache line so none straddles two,
* onto the spare list; the slabs last as long as the heap does */
static LINEAR_COLD linear_block_t *linear_slab_new(void)
{
u32 i;
linear_block_t *node;
void **slab = (void**)malloc(sizeof(void*) + LINEAR_LINE - 1
+ LINEAR_SLAB * sizeof(linear_block_t));
if (!slab)
return NULL;
*slab = linear.slabs;
linear.slabs = slab;
node = (linear_block_t*)(((uintptr_t)(slab + 1) + LINEAR_LINE - 1)
& ~(uintptr_t)(LINEAR_LINE - 1));
for (i = 0; i < LINEAR_SLAB - 1; i++)
node[i].next = &node[i + 1];
node[LINEAR_SLAB - 1].next = NULL;
return node;
}
static INLINE linear_block_t *linear_block_new(u32 base, u32 size)
{
linear_block_t *b = linear.spare;
if (!b && !(b = linear_slab_new()))
return NULL;
linear.spare = b->next;
b->base = base;
b->size = size;
return b;
}
static INLINE void linear_block_link(linear_block_t *n, linear_block_t *before)
{
n->next = before;
n->prev = before->prev;
before->prev->next = n;
before->prev = n;
}
static INLINE void linear_block_del(linear_block_t *b)
{
b->prev->next = b->next;
b->next->prev = b->prev;
b->next = linear.spare;
linear.spare = b;
}
/* Takes size bytes aligned to mask + 1 from the first block they fit
* in; 0 when none has room. size is a multiple of mask + 1, so the
* alignment waste added to it cannot overflow. *got is what the
* allocation holds, which is more than size when no node was to be had
* for the tail. */
static u32 linear_take(u32 size, u32 mask, u32 *got)
{
linear_block_t *b = linear.end.next;
u32 waste, addr, rest;
while ((waste = (0u - b->base) & mask) + size > b->size)
b = b->next;
if (b == LINEAR_END)
return 0;
addr = b->base + waste;
rest = b->size - waste - size;
*got = size;
if (!waste)
{
b->base += size;
b->size = rest;
if (!rest)
linear_block_del(b);
}
else
{
b->size = waste;
if (rest)
{
/* The tail goes back on the list after b */
linear_block_t *n = linear_block_new(addr + size, rest);
if (n)
linear_block_link(n, b->next);
else
*got += rest; /* no choice but to waste the space */
}
}
return addr;
}
static void linear_give(u32 addr, u32 size)
{
linear_block_t *b, *n;
for (b = linear.end.next; ; b = b->next)
{
if (b->base > addr)
{
/* Joins the block it ends at, or goes in before it - before
* end when it lies past the last block */
if (addr + size == b->base)
{
b->base = addr;
b->size += size;
}
else if ((n = linear_block_new(addr, size)))
linear_block_link(n, b);
return;
}
if (b->base + b->size == addr)
{
/* Joins the block before it, and then those it now reaches */
u32 end = addr + size;
b->size += size;
while ((n = b->next)->base == end)
{
b->size += n->size;
end += n->size;
linear_block_del(n);
}
return;
}
}
}
/* ---- live allocations ---- */
static INLINE u32 linear_live_home(u32 addr, u32 mask)
{
u32 h = (addr >> 4) * 0x9E3779B1u;
return (h ^ (h >> 15)) & mask;
}
/* The slot holding addr, or the free one it would go in */
static INLINE u32 linear_live_slot(const linear_live_t *live, u32 mask,
u32 addr)
{
u32 i = linear_live_home(addr, mask);
while (live[i].addr && live[i].addr != addr)
i = (i + 1) & mask;
return i;
}
static LINEAR_COLD bool linear_live_grow(void)
{
u32 i;
u32 old_mask = linear.live_mask;
u32 mask = old_mask * 2 + 1;
linear_live_t *old = linear.live;
linear_live_t *live = (linear_live_t*)calloc(mask + 1, sizeof(*live));
if (!live)
return false;
for (i = 0; i <= old_mask; i++)
if (old[i].addr)
live[linear_live_slot(live, mask, old[i].addr)] = old[i];
free(old);
linear.live = live;
linear.live_mask = mask;
return true;
}
/* An address already live keeps what it holds */
static bool linear_live_add(u32 addr, u32 size)
{
u32 i;
if ((linear.live_count + 1) * 2 > linear.live_mask + 1
&& !linear_live_grow())
return false;
i = linear_live_slot(linear.live, linear.live_mask, addr);
if (!linear.live[i].addr)
{
linear.live[i].addr = addr;
linear.live[i].size = size;
linear.live_count++;
}
return true;
}
static void linear_live_del(u32 i)
{
linear_live_t *live = linear.live;
u32 mask = linear.live_mask;
u32 j = i;
/* Pulls back each entry after it that would no longer be found */
for (;;)
{
u32 k;
j = (j + 1) & mask;
if (!live[j].addr)
break;
k = linear_live_home(live[j].addr, mask);
if (i <= j ? (i < k && k <= j) : (i < k || k <= j))
continue;
live[i] = live[j];
i = j;
}
live[i].addr = 0;
linear.live_count--;
}
/* ---- libctru's linear heap interface ---- */
static LINEAR_COLD bool linear_init(void)
{
linear_block_t *b;
if (!(linear.live = (linear_live_t*)calloc(LINEAR_LIVE_MIN,
sizeof(*linear.live))))
return false;
if (!(b = linear_block_new(__linear_heap, __linear_heap_size)))
{
free(linear.live);
linear.live = NULL;
return false;
}
linear.end.base = 0xFFFFFFFFu;
linear.end.size = 0xFFFFFFFFu;
linear.end.next = LINEAR_END;
linear.end.prev = LINEAR_END;
linear_block_link(b, LINEAR_END);
linear.live_mask = LINEAR_LIVE_MIN - 1;
linear.maxaddr = __linear_heap;
linear.ready = true;
return true;
}
void *linearMemAlign(size_t len, size_t alignment)
{
u32 mask, size, got, addr;
if (alignment < 16)
alignment = 16;
else if ((alignment & (alignment - 1)) || alignment > 0x80000000u)
return NULL;
mask = (u32)alignment - 1;
if ((size_t)(u32)len != len)
return NULL;
size = (u32)len;
if (size & mask)
{
if (size > 0xFFFFFFFFu - mask)
return NULL;
size = (size + mask) & ~mask;
}
if (!linear.ready && !linear_init())
return NULL;
if (!(addr = linear_take(size, mask, &got)))
return NULL;
if (!linear_live_add(addr, got))
{
linear_give(addr, got);
return NULL;
}
if (linear.end.prev == LINEAR_END)
linear.maxaddr = __linear_heap + __linear_heap_size;
else if (linear.maxaddr < linear.end.prev->base)
linear.maxaddr = linear.end.prev->base;
return (void*)(uintptr_t)addr;
}
void *linearAlloc(size_t len)
{
return linearMemAlign(len, 0x80);
}
void *linearRealloc(void *mem, size_t len)
{
(void)mem;
(void)len;
return NULL;
}
size_t linearGetSize(void *mem)
{
u32 i, addr = (u32)(uintptr_t)mem;
if (!linear.ready || !addr)
return 0;
i = linear_live_slot(linear.live, linear.live_mask, addr);
return linear.live[i].addr ? linear.live[i].size : 0;
}
void linearFree(void *mem)
{
u32 i, addr = (u32)(uintptr_t)mem;
linear_live_t chunk;
if (!linear.ready || !addr)
return;
i = linear_live_slot(linear.live, linear.live_mask, addr);
if (!linear.live[i].addr)
return;
chunk = linear.live[i];
linear_live_del(i);
linear_give(chunk.addr, chunk.size);
}
u32 linearSpaceFree(void)
{
u32 total = 0;
const linear_block_t *b;
if (!linear.ready)
return 0;
for (b = linear.end.next; b != LINEAR_END; b = b->next)
total += b->size;
return total;
}
/* ---- the end of the heap ---- */
u32 ctr_get_linear_free(void)
{
const linear_block_t *b = linear.end.prev;
if (!linear.ready || b == LINEAR_END || b->base + b->size != __linear_heap + __linear_heap_size)
return 0;
return b->size;
}
u32 ctr_get_linear_unused(void)
{
return __linear_heap + __linear_heap_size - linear.maxaddr;
}
void ctr_linear_free_pages(u32 pages)
{
u32 tmp, size;
linear_block_t *b = linear.end.prev;
if (!linear.ready || b == LINEAR_END || b->base + b->size != __linear_heap + __linear_heap_size)
return;
size = pages << 12;
if (size > b->size)
return;
b->size -= size;
__linear_heap_size -= size;
svcControlMemory(&tmp, __linear_heap + __linear_heap_size, 0x0, size,
MEMOP_FREE, (MemPerm)(MEMPERM_READ | MEMPERM_WRITE));
}
void ctr_linear_get_stats(void)
{
const linear_block_t *b = linear.end.prev;
printf("last:\n");
if (linear.ready && b != LINEAR_END)
printf("0x%08X --> 0x%08X (0x%08X) \n", (unsigned)b->base,
(unsigned)(b->base + b->size), (unsigned)b->size);
printf("free: 0x%08X unused: 0x%08X \n",
(unsigned)ctr_get_linear_unused(), (unsigned)ctr_get_linear_free());
}
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
View File
File renamed without changes.
View File
File renamed without changes.
+1 -1
View File
@@ -1,2 +1,2 @@
/* <3ds/types.h>, as ctr/ctr_debug.h asks for it: the stand-in's types. */
/* <3ds/types.h>, as os/ctr/ctr_debug.h asks for it: the stand-in's types. */
#include "../3ds.h"
+15
View File
@@ -0,0 +1,15 @@
/* Stand-in for libctru's <3ds.h>: what os/ctr/ctr_linear.c names. */
#ifndef CTR_LINEAR_STUB_3DS_H
#define CTR_LINEAR_STUB_3DS_H
#include <3ds/types.h>
typedef enum { MEMOP_FREE = 1 } MemOp;
typedef enum { MEMPERM_READ = 1, MEMPERM_WRITE = 2 } MemPerm;
Result svcControlMemory(u32 *addr_out, u32 addr0, u32 addr1, u32 size,
MemOp op, MemPerm perm);
void *linearAlloc(size_t size);
void *linearMemAlign(size_t size, size_t alignment);
void *linearRealloc(void *mem, size_t size);
size_t linearGetSize(void *mem);
void linearFree(void *mem);
u32 linearSpaceFree(void);
#endif
+11
View File
@@ -0,0 +1,11 @@
/* Stand-in for libctru's <3ds/types.h> */
#ifndef CTR_LINEAR_STUB_3DS_TYPES_H
#define CTR_LINEAR_STUB_3DS_TYPES_H
#include <stddef.h>
#include <stdint.h>
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef int32_t s32;
typedef int32_t Result;
#endif
+28
View File
@@ -0,0 +1,28 @@
# os/ctr/ctr_linear.c, the 3DS linear heap allocator, against a stand-in
# <3ds.h> and a linear heap at an address it hands out but never
# touches. Builds for Linux and, with CC=x86_64-w64-mingw32-gcc EXE=.exe
# RUNNER=wine, for Windows.
TARGET := ctr_linear_test$(EXE)
CORE_DIR := ../../..
SOURCES := ctr_linear_test.c $(CORE_DIR)/os/ctr/ctr_linear.c
CFLAGS += -std=c89 -Wall -g1 -O1 -I. -I$(CORE_DIR)/libretro-common/include
ifneq ($(SANITIZER),)
CFLAGS := -fsanitize=$(SANITIZER) -fno-omit-frame-pointer $(CFLAGS)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
all: $(TARGET)
$(TARGET): $(SOURCES) 3ds.h 3ds/types.h
$(CC) $(CFLAGS) -o $@ $(SOURCES) $(LDFLAGS)
check: all
$(RUNNER) ./$(TARGET)
clean:
rm -f ctr_linear_test ctr_linear_test.exe
.PHONY: all check clean
@@ -0,0 +1,216 @@
/* os/ctr/ctr_linear.c, the allocator for the 3DS linear heap that the
* GPU and DSP buffers come from.
*
* Allocations are placed first fit in address order and aligned as
* asked, freed space joins its neighbours until the heap is one block
* again, every live allocation is found by its address however many
* there are, and pages handed back from the end of the heap leave it.
*
* An allocation that fits no free block fails. With the whole heap
* allocated, no free block is left at all: a further
* allocation must fail rather than start the heap over and hand out
* its first address a second time, and the free-space queries must
* answer rather than fault. */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <3ds.h>
#define HEAP 0x14000000ul
#define HEAP_SIZE 0x00400000ul
u32 __linear_heap = HEAP;
u32 __linear_heap_size = HEAP_SIZE;
u32 ctr_get_linear_free(void);
u32 ctr_get_linear_unused(void);
void ctr_linear_free_pages(u32 pages);
void ctr_linear_get_stats(void);
static int failures;
static unsigned long svc_addr, svc_size, svc_calls;
#define CHECK(c) do { if (!(c)) { \
fprintf(stderr, "FAIL %s:%d: %s\n", __FILE__, __LINE__, #c); \
failures++; } } while (0)
Result svcControlMemory(u32 *addr_out, u32 addr0, u32 addr1, u32 size,
MemOp op, MemPerm perm)
{
(void)addr_out;
(void)addr1;
(void)perm;
if (op == MEMOP_FREE)
{
svc_addr = addr0;
svc_size = size;
svc_calls++;
}
return 0;
}
static unsigned long addr_of(const void *p)
{
return (unsigned long)(size_t)p;
}
static unsigned long rng = 1;
static unsigned long next_rand(void)
{
rng = (rng * 1103515245ul + 12345ul) & 0xFFFFFFFFul;
return rng >> 8;
}
/* The whole heap is free as one block at its start */
static int heap_whole(void)
{
void *all = linearMemAlign(__linear_heap_size, 16);
int ok = addr_of(all) == HEAP && linearSpaceFree() == 0;
linearFree(all);
return ok && linearSpaceFree() == __linear_heap_size;
}
#define STRESS 2000
static void *live[STRESS];
static unsigned long live_size[STRESS];
static int by_addr(const void *a, const void *b)
{
unsigned long x = addr_of(*(void* const*)a);
unsigned long y = addr_of(*(void* const*)b);
return x < y ? -1 : x > y;
}
static void stress(void)
{
static const size_t aligns[] = { 0, 16, 0x40, 0x80, 0x200, 0x1000 };
unsigned long used = 0;
void *sorted[STRESS];
int n = 0, i, overlaps = 0, lost = 0;
for (i = 0; i < STRESS; i++)
{
size_t size = 1 + next_rand() % 3000;
size_t align = aligns[next_rand() % 6];
void *p = linearMemAlign(size, align);
if (!p)
continue;
if (align && addr_of(p) % align)
lost++;
live[n] = p;
live_size[n] = linearGetSize(p);
if (live_size[n] < size)
lost++;
n++;
/* Free about one in three as it goes */
if (next_rand() % 3 == 0)
{
int k = (int)(next_rand() % (unsigned long)n);
linearFree(live[k]);
live[k] = live[n - 1];
live_size[k] = live_size[n - 1];
n--;
}
}
CHECK(n > 256); /* well past the table's first size */
CHECK(lost == 0);
memcpy(sorted, live, n * sizeof(*sorted));
qsort(sorted, n, sizeof(*sorted), by_addr);
for (i = 0; i < n; i++)
{
used += linearGetSize(sorted[i]);
if (i && addr_of(sorted[i - 1]) + linearGetSize(sorted[i - 1])
> addr_of(sorted[i]))
overlaps++;
}
CHECK(overlaps == 0);
CHECK(used + linearSpaceFree() == HEAP_SIZE);
while (n)
{
int k = (int)(next_rand() % (unsigned long)n);
linearFree(live[k]);
live[k] = live[--n];
}
CHECK(linearSpaceFree() == HEAP_SIZE);
CHECK(heap_whole());
}
int main(void)
{
void *a, *b, *c, *d, *all;
/* Before anything is allocated */
CHECK(linearSpaceFree() == 0);
CHECK(ctr_get_linear_free() == 0);
linearFree(NULL);
linearFree((void*)(size_t)(HEAP + 0x80));
CHECK(linearMemAlign(64, 0x30) == NULL);
CHECK(linearMemAlign(64, 0x300) == NULL);
/* First fit, aligned */
a = linearAlloc(100); /* 0x80-aligned, 0x80 bytes */
b = linearMemAlign(10, 0); /* at least 16-aligned */
c = linearMemAlign(0x100, 0x1000);
d = linearAlloc(16); /* into the gap before c */
CHECK(addr_of(a) == HEAP);
CHECK(addr_of(b) == HEAP + 0x80);
CHECK(addr_of(c) == HEAP + 0x1000);
CHECK(addr_of(d) == HEAP + 0x100);
CHECK(linearGetSize(a) == 0x80);
CHECK(linearGetSize(b) == 0x10);
CHECK(linearGetSize(c) == 0x1000); /* sizes round up to the alignment */
CHECK(linearGetSize(d) == 0x80);
CHECK(linearGetSize((void*)(size_t)(HEAP + 0x40)) == 0);
CHECK(linearSpaceFree() == HEAP_SIZE - 0x80 - 0x10 - 0x1000 - 0x80);
CHECK(linearRealloc(a, 0x100) == NULL);
CHECK(linearMemAlign(HEAP_SIZE, 16) == NULL); /* no block that big */
CHECK(linearMemAlign(0xFFFFFF00ul, 0x100) == NULL);
/* Freed space joins back into one block */
linearFree(b);
linearFree(c);
linearFree(a);
linearFree(a); /* already free: ignored */
linearFree(d);
CHECK(linearSpaceFree() == HEAP_SIZE);
CHECK(ctr_get_linear_free() == HEAP_SIZE);
CHECK(heap_whole());
/* The whole heap taken: nothing more to give */
all = linearMemAlign(HEAP_SIZE, 16);
CHECK(addr_of(all) == HEAP);
CHECK(linearAlloc(16) == NULL);
CHECK(linearMemAlign(16, 16) == NULL);
CHECK(linearSpaceFree() == 0);
CHECK(ctr_get_linear_free() == 0);
CHECK(ctr_get_linear_unused() == 0);
ctr_linear_get_stats();
linearFree(all);
CHECK(heap_whole());
stress();
/* Pages handed back leave the end of the heap */
ctr_linear_free_pages(2);
CHECK(svc_calls == 1);
CHECK(__linear_heap_size == HEAP_SIZE - 0x2000);
CHECK(svc_addr == HEAP + HEAP_SIZE - 0x2000 && svc_size == 0x2000);
CHECK(ctr_get_linear_free() == HEAP_SIZE - 0x2000);
ctr_linear_free_pages(HEAP_SIZE >> 12); /* more than there is */
CHECK(svc_calls == 1);
CHECK(heap_whole());
if (failures)
{
fprintf(stderr, "%d failure(s)\n", failures);
return 1;
}
printf("ctr_linear: ok\n");
return 0;
}
+1 -1
View File
@@ -24,7 +24,7 @@
# fix those with memmove, a staging buffer, or an alias-guarded
# helper instead.
audio/drivers/wiiu_audio.c|memcpy|offsets|offsets
ctr/ctr_svchax.c|memcpy|flush_buffer|flush_buffer
os/ctr/ctr_svchax.c|memcpy|flush_buffer|flush_buffer
frontend/drivers/platform_gx_libogc.c|strlcpy|g_defaults.dirs|g_defaults.dirs
gfx/drivers/sdl_rs90_gfx.c|memcpy|target|target
gfx/video_filters/ntsc_crt.c|memcpy|v->out|v->out