feat(f4): more free memory on handsets with CCM RAM (#5804)

Alternative Lua memory allocator for radios with CCM RAM - primarily GX12 at present
This commit is contained in:
philmoz
2025-01-20 10:32:20 +10:00
committed by GitHub
parent a3c0e0a007
commit a6542e1448
10 changed files with 251 additions and 29 deletions
+230
View File
@@ -0,0 +1,230 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <stddef.h>
#include "edgetx.h"
template <int SIZE_SLOT, int NUM_BINS>
class BinAllocator
{
private:
union Bin {
char data[SIZE_SLOT];
union Bin* next;
};
union Bin bins[NUM_BINS];
union Bin* freeList;
int freeCount;
public:
BinAllocator()
{
freeCount = NUM_BINS;
memclear(bins, sizeof(bins));
freeList = &bins[0];
for (int i = 0; i < NUM_BINS - 1; i += 1) {
bins[i].next = &bins[i+1];
}
}
bool is_member(void * ptr) {
return (ptr >= bins[0].data && ptr <= bins[NUM_BINS-1].data);
}
bool free(void * ptr)
{
if (is_member(ptr)) {
union Bin* b = (union Bin*)ptr;
b->next = freeList;
freeList = b;
freeCount += 1;
// TRACE("\tBinAllocator<%d> free %p", SIZE_SLOT, ptr);
return true;
}
// TRACE("\tBinAllocator<%d> free - %p NOT FOUND", SIZE_SLOT, ptr);
return false;
}
void * malloc(size_t size)
{
if (size > SIZE_SLOT) {
// TRACE("BinAllocator<%d> malloc [%lu] size > SIZE_SLOT", SIZE_SLOT, size);
return nullptr;
}
if (freeList) {
union Bin* b = freeList;
freeList = freeList->next;
freeCount -= 1;
// TRACE("\tBinAllocator<%d> malloc [%lu] - %p", SIZE_SLOT, size, b);
return b->data;
}
// TRACE("BinAllocator<%d> malloc [%lu] no free slots", SIZE_SLOT , size);
return nullptr;
}
size_t size(void * ptr) { return is_member(ptr) ? SIZE_SLOT : 0; }
bool can_fit(void * ptr, size_t size)
{
return is_member(ptr) && size <= SIZE_SLOT;
}
int freeSlots() { return freeCount; }
int avail() { return freeCount * SIZE_SLOT; }
};
#if defined(SIMU)
typedef BinAllocator<40,300> BinAllocator_slots1;
typedef BinAllocator<80,100> BinAllocator_slots2;
#else
typedef BinAllocator<28,200> BinAllocator_slots1;
typedef BinAllocator<92,50> BinAllocator_slots2;
#endif
BinAllocator_slots1 slots1;
BinAllocator_slots2 slots2;
#if defined(DEBUG)
int SimulateMallocFailure = 0; //set this to simulate allocation failure
int totalAllocated = 0;
int totalFreed = 0;
int missedAlloc = 0;
int missedFree = 0;
#endif
int custom_avail()
{
return slots1.avail() + slots2.avail();
}
static bool bin_free(void * ptr)
{
// return TRUE if ours
bool res = slots1.free(ptr);
if (!res) res = slots2.free(ptr);
#if defined(DEBUG)
if (res) totalFreed += 1;
else missedFree += 1;
#endif
return res;
}
static void * bin_malloc(size_t size) {
// try to allocate from our space
void* res = slots1.malloc(size);
if (!res) res = slots2.malloc(size);
#if defined(DEBUG)
if (res) totalAllocated += 1;
else missedAlloc += 1;
#endif
return res;
}
static void * bin_realloc(void * ptr, size_t size)
{
if (ptr == nullptr) {
// no previous data, try our malloc
return bin_malloc(size);
}
else {
if (! (slots1.is_member(ptr) || slots2.is_member(ptr)) ) {
// not our data, leave it to libc realloc
return nullptr;
}
// we have existing data
// if it fits in current slot, return it
// TODO if new size is smaller, try to relocate in smaller slot
if ( slots1.can_fit(ptr, size) ) {
// TRACE("OUR realloc %p[%lu] fits in slot1", ptr, size);
return ptr;
}
if ( slots2.can_fit(ptr, size) ) {
// TRACE("OUR realloc %p[%lu] fits in slot2", ptr, size);
return ptr;
}
// we need a bigger slot
void * res = bin_malloc(size);
if (res == nullptr) {
// we don't have the space, use libc malloc
// TRACE("bin_malloc [%lu] FAILURE", size);
res = malloc(size);
if (res == nullptr) {
// TRACE("libc malloc [%lu] FAILURE", size);
return nullptr;
}
}
// copy data
memcpy(res, ptr, slots1.size(ptr) + slots2.size(ptr));
bin_free(ptr);
return res;
}
}
void *custom_l_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
{
// (void)ud; (void)osize; /* not used */
void* res = nullptr;
if (nsize == 0) {
if (ptr) { // avoid a bunch of NULL pointer free calls
if (!bin_free(ptr)) {
// not our range, use libc allocator
// TRACE("libc free %p", ptr);
free(ptr);
}
}
} else {
#if defined(DEBUG)
if (SimulateMallocFailure < 0) {
// delayed failure
if (++SimulateMallocFailure == 0)
SimulateMallocFailure = 1;
}
// normal alloc if <= 0, otherwise will return nullptr
if ( SimulateMallocFailure <= 0) {
#endif
// try our allocator, if it fails use libc allocator
res = bin_realloc(ptr, nsize);
if (res && ptr) {
// TRACE("OUR realloc %p[%lu] -> %p[%lu]", ptr, osize, res, nsize);
}
if (res == nullptr) {
res = realloc(ptr, nsize);
// TRACE("libc realloc %p[%lu] -> %p[%lu]", ptr, osize, res, nsize);
// if (res == 0 ){
// TRACE("realloc FAILURE %lu", nsize);
// dumpFreeMemory();
// }
}
#if defined(DEBUG)
}
#endif
}
#if defined(DEBUG)
TRACE("bin_l_alloc(%p,%p,%lu,%lu) - S1F %d, S2F %d, TA %d, TF %d, MA %d, MF %d",ud,ptr,osize,nsize,slots1.freeSlots(),slots2.freeSlots(),totalAllocated,totalFreed,missedAlloc,missedFree);
#endif
return res;
}
+202
View File
@@ -0,0 +1,202 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <stdlib.h>
#include "edgetx.h"
/*
Custom memory allocator for Lua that adds all available CCM RAM to the memory pool
- maintains a free list of available CCM RAM, order by address
- allocates from the smallest available free block
- coalesces adjacent blocks on free
*/
typedef struct _memblk {
int size; // actual size of block. Available / allocated size = 'size' - sizeof(int)
struct _memblk* next;
} memblk;
#define MIN_SIZE 12 // Minimum size for free block (4 for 'size' field, 8 for data)
// CCM RAM available region
extern memblk _ccm_heap_start;
extern int _ccm_heap_end;
// Init flag
static bool started = false;
// Free list
static memblk* ccm_list = nullptr;
// CCM region for checking if blocks belong here
static unsigned char* ccm_start;
static unsigned char* ccm_end;
// Initialise free list
static void init()
{
if (!started) {
started = true;
ccm_start = (unsigned char*)&_ccm_heap_start;
ccm_end = (unsigned char*)&_ccm_heap_end;
ccm_list = &_ccm_heap_start;
ccm_list->next = nullptr;
ccm_list->size = ccm_end - ccm_start;
}
}
// Return free memory available
uint32_t custom_avail()
{
uint32_t free = 0;
for (memblk* b = ccm_list; b; b = b->next) {
free += (b->size - sizeof(int));
}
return free;
}
// Free memory block
static void ccm_free(void* ptr)
{
// Check that block belongs in CCM RAM
if (ptr >= ccm_start && ptr < ccm_end) {
memblk* prev = nullptr;
memblk* next = ccm_list;
memblk* p = (memblk*)(((unsigned char*)ptr) - sizeof(int));
// Find place in list
while (next && (p > next)) {
prev = next;
next = next->next;
}
// Link into list
p->next = next;
if (prev == nullptr) {
ccm_list = p;
} else {
prev->next = p;
if ((((unsigned char*)prev) + prev->size) == (unsigned char*)p) {
// Merge adjacent
prev->next = p->next;
prev->size += p->size;
p = prev;
}
}
if ((((unsigned char*)p) + p->size) == (unsigned char*)next) {
// Merge adjacent
p->next = next->next;
p->size += next->size;
}
} else {
// Not CCM RAM - let system handle it.
free(ptr);
}
}
// Allocate new block
static void* ccm_malloc(size_t nsize)
{
// Add space for 'size' field and align to 32 bit size
int asize = (nsize + sizeof(int) + 3) & 0xFFFFFFFC;
memblk* prev = nullptr;
memblk* next = ccm_list;
memblk* p = nullptr;
memblk* pprev = nullptr;
// Find smallest block that will fit
while (next) {
if (next->size >= asize) {
if (!p || (p && (p->size < next->size))) {
pprev = prev;
p = next;
if (p->size == asize) break; // exact fit
}
}
prev = next;
next = next->next;
}
void* res;
if (p) {
if (p->size <= (asize + MIN_SIZE)) {
// Using entire block - unlink it
if (pprev)
pprev->next = p->next;
else
ccm_list = p->next;
} else {
// reduce size of block and return end part
p->size -= asize;
p = (memblk*)(((unsigned char*)p) + p->size);
p->size = asize;
}
res = ((unsigned char*)p) + sizeof(int);
} else {
// No space - let system handle it
res = malloc(nsize);
}
return res;
}
// Re-allocate existing block
static void* ccm_realloc(void* ptr, size_t osize, size_t nsize)
{
// Add space for 'size' field and align to 32 bit size
int asize = (nsize + sizeof(int) + 3) & 0xFFFFFFFC;
// Check that block belongs in CCM RAM
if (ptr >= ccm_start && ptr < ccm_end) {
memblk* p = (memblk*)(((unsigned char*)ptr) - sizeof(int));
if (asize <= p->size)
return ptr; // fits in already allocated space
void* np = ccm_malloc(nsize);
memcpy(np, ptr, osize);
ccm_free(ptr);
return np;
} else {
// In theory nsize might fit in available CCM RAM; but since block is
// already in normal RAM we just let system handle it.
return realloc(ptr, nsize);
}
}
void* custom_l_alloc(void *ud, void *ptr, size_t osize, size_t nsize)
{
(void)ud; /* not used */
init();
if (nsize == 0) {
if (ptr) { // avoid a bunch of NULL pointer free calls
ccm_free(ptr);
}
return nullptr;
}
else if (ptr) {
return ccm_realloc(ptr, osize, nsize);
} else {
return ccm_malloc(nsize);
}
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#if defined(STM32F4)
#include "ccm_allocator.cpp"
#else
#include "bin_allocator.cpp"
#endif
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright (C) EdgeTX
*
* Based on code named
* opentx - https://github.com/opentx/opentx
* th9x - http://code.google.com/p/th9x
* er9x - http://code.google.com/p/er9x
* gruvin9x - http://code.google.com/p/gruvin9x
*
* License GPLv2: http://www.gnu.org/licenses/gpl-2.0.html
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#pragma once
#if defined(USE_CUSTOM_ALLOCATOR)
// wrapper for our custom allocator for Lua
void *custom_l_alloc(void *ud, void *ptr, size_t osize, size_t nsize);
int custom_avail();
#endif
+3 -3
View File
@@ -26,7 +26,7 @@
#include <algorithm>
#include "edgetx.h"
#include "bin_allocator.h"
#include "custom_allocator.h"
#include "lua_api.h"
#include "lua_event.h"
@@ -1351,8 +1351,8 @@ void luaInit()
L = nullptr;
if (luaState != INTERPRETER_PANIC) {
#if defined(USE_BIN_ALLOCATOR)
L = lua_newstate(bin_l_alloc, nullptr); //we use our own allocator!
#if defined(USE_CUSTOM_ALLOCATOR)
L = lua_newstate(custom_l_alloc, nullptr); //we use our own allocator!
#elif defined(LUA_ALLOCATOR_TRACER)
memclear(&lsScriptsTrace, sizeof(lsScriptsTrace));
lsScriptsTrace.script = "lua_newstate(scripts)";
+2 -2
View File
@@ -343,8 +343,8 @@ void luaInitThemesAndWidgets()
{
TRACE("luaInitThemesAndWidgets");
#if defined(USE_BIN_ALLOCATOR)
lsWidgets = lua_newstate(bin_l_alloc, NULL); //we use our own allocator!
#if defined(USE_CUSTOM_ALLOCATOR)
lsWidgets = lua_newstate(custom_l_alloc, NULL); //we use our own allocator!
#elif defined(LUA_ALLOCATOR_TRACER)
memclear(&lsWidgetsTrace, sizeof(lsWidgetsTrace));
lsWidgetsTrace.script = "lua_newstate(widgets)";