Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
15b6907
Improvements to heap-memory and PSRAM handling (#4791)
DedeHai Sep 16, 2025
c6a4e28
WLED-MM adaptations
softhack007 Feb 15, 2026
d273835
fix an ancient compiler warning in DateStrings.cpp
softhack007 Feb 15, 2026
a861a31
use new p_malloc functions with ArduinoJSON
softhack007 Feb 15, 2026
b78d5e6
use new memory allocator utilities instead of malloc() callloc() and …
softhack007 Feb 15, 2026
e714ede
p -> d in AsyncJSON
softhack007 Feb 15, 2026
98c9788
fallback if !psramFound()
softhack007 Feb 15, 2026
31e56ae
two bugfixes
softhack007 Feb 15, 2026
4c93146
correct alloc size in d_calloc
softhack007 Feb 15, 2026
9408564
Merge branch 'mdev' into dedehai_malloc
softhack007 Feb 15, 2026
735047f
bugfix: allow user override for MIN_HEAP_SIZE
softhack007 Feb 15, 2026
78a964c
add RTCRAM statistics to WLED_DEBUG output
softhack007 Feb 15, 2026
62e4cab
Merge branch 'mdev' into dedehai_malloc
softhack007 Feb 16, 2026
1eaad1b
work in progress
softhack007 Feb 16, 2026
60f8648
(experimental) glitch-free heap size measurement
softhack007 Feb 16, 2026
67c70a2
add missing DRAM debug code for classic esp32
softhack007 Feb 16, 2026
68dbd5e
re-enable more relaxed heap checks in HUB75 builds
softhack007 Feb 16, 2026
69946e4
add early heap capacity checking before allocating
softhack007 Feb 16, 2026
381ad56
revert unintended semantic change for "minfreeheap"
softhack007 Feb 16, 2026
2d71ab3
USER_PRINT memory alloc failure messages
softhack007 Feb 16, 2026
b62b40b
AR: better handling of alloc failure
softhack007 Feb 16, 2026
ffe718b
ARTIFX and rotary UM robustness improvements
softhack007 Feb 16, 2026
66847a3
printf format string correction
softhack007 Feb 16, 2026
ccc96af
bugfix for C3 and S2
softhack007 Feb 16, 2026
26a4abe
AR: centralize memory cleanup
softhack007 Feb 16, 2026
6f99aa0
typo
softhack007 Feb 16, 2026
75cbe6c
UI "free heap" was sometimes behind -> back to ESP.getFreeHeap()
softhack007 Feb 16, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 22 additions & 3 deletions usermods/artifx/arti.h
Original file line number Diff line number Diff line change
Expand Up @@ -2562,14 +2562,24 @@ class ARTI {

//open programFile
char * programText = nullptr;
uint16_t programFileSize;
size_t programFileSize;
#if ARTI_PLATFORM == ARTI_ARDUINO
programFileSize = programFile.size();
programText = (char *)malloc(programFileSize+1);
programText = (char *)d_malloc(programFileSize+1);
if (programText == nullptr) {
ERROR_ARTI("ARTI-FX: Failed to allocate memory for program file (%u bytes)\n", programFileSize+1);
programFile.close();
return false;
}
programFile.read((byte *)programText, programFileSize);
programText[programFileSize] = '\0';
#else
programText = (char *)malloc(programTextSize);
if (programText == nullptr) {
ERROR_ARTI("ARTI-FX: Failed to allocate memory for program file (%u bytes)\n", programTextSize);
programFile.close();
return false;
}
programFile.read(programText, programTextSize);
DEBUG_ARTI("programFile size %lu bytes\n", programFile.gcount());
programText[programFile.gcount()] = '\0';
Expand Down Expand Up @@ -2607,7 +2617,13 @@ class ARTI {
#endif

if (stages < 1) {
if (nullptr != programText) free(programText); // softhack007 prevent memory leak
// softhack007 prevent memory leak
#if ARTI_PLATFORM == ARTI_ARDUINO
if (nullptr != programText) d_free(programText);
#else
if (nullptr != programText) free(programText);
#endif
programText = nullptr;
close();
return true;
}
Expand Down Expand Up @@ -2666,8 +2682,11 @@ class ARTI {
#endif
}
#if ARTI_PLATFORM == ARTI_ARDUINO //not on windows as cause crash???
d_free(programText);
#else
free(programText);
#endif
programText = nullptr;

if (stages >= 3)
{
Expand Down
41 changes: 31 additions & 10 deletions usermods/audioreactive/audio_reactive.h
Original file line number Diff line number Diff line change
Expand Up @@ -454,13 +454,13 @@ static bool alocateFFTBuffers(void) {
USER_PRINT(F("\nFree heap ")); USER_PRINTLN(ESP.getFreeHeap());
#endif

if (vReal) free(vReal); // should not happen
if (vImag) free(vImag); // should not happen
if ((vReal = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false; // calloc or die
if ((vImag = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false;
if (vReal) d_free(vReal); // should not happen
if (vImag) d_free(vImag); // should not happen
if ((vReal = (float*) d_calloc(samplesFFT, sizeof(float))) == nullptr) return false; // calloc or die
if ((vImag = (float*) d_calloc(samplesFFT, sizeof(float))) == nullptr) return false;
#ifdef FFT_MAJORPEAK_HUMAN_EAR
if (pinkFactors) free(pinkFactors);
if ((pinkFactors = (float*) calloc(samplesFFT, sizeof(float))) == nullptr) return false;
if (pinkFactors) p_free(pinkFactors);
if ((pinkFactors = (float*) p_calloc(samplesFFT, sizeof(float))) == nullptr) return false;
#endif

#ifdef SR_DEBUG
Expand All @@ -472,6 +472,21 @@ static bool alocateFFTBuffers(void) {
return(true); // success
}

// de-allocate FFT sample buffers from heap
static void destroyFFTBuffers(void) {
#ifdef FFT_MAJORPEAK_HUMAN_EAR
if (pinkFactors) p_free(pinkFactors); pinkFactors = nullptr;
#endif
if (vImag) d_free(vImag); vImag = nullptr;
if (vReal) d_free(vReal); vReal = nullptr;
#ifdef SR_DEBUG
USER_PRINTLN("\ndestroyFFTBuffers() completed successfully.");
USER_PRINT(F("Free heap: ")); USER_PRINTLN(ESP.getFreeHeap());
USER_FLUSH();
#endif
}


// High-Pass "DC blocker" filter
// see https://www.dsprelated.com/freebooks/filters/DC_Blocker.html
static void runDCBlocker(uint_fast16_t numSamples, float *sampleBuffer) {
Expand Down Expand Up @@ -511,13 +526,18 @@ void FFTcode(void * parameter)
static float* oldSamples = nullptr; // previous 50% of samples
static bool haveOldSamples = false; // for sliding window FFT
bool usingOldSamples = false;
if (!oldSamples) oldSamples = (float*) calloc(samplesFFT_2, sizeof(float)); // allocate on first run
if (!oldSamples) { disableSoundProcessing = true; return; } // no memory -> die
if (!oldSamples) oldSamples = (float*) d_calloc(samplesFFT_2, sizeof(float)); // allocate on first run
if (!oldSamples) { disableSoundProcessing = true; haveOldSamples = false; destroyFFTBuffers(); return; } // no memory -> die
#endif

bool success = true;
if ((vReal == nullptr) || (vImag == nullptr)) success = alocateFFTBuffers(); // allocate sample buffers on first run
if (success == false) { disableSoundProcessing = true; return; } // no memory -> die
if (success == false) {
// no memory -> clean up heap, then suspend
disableSoundProcessing = true;
destroyFFTBuffers();
return;
}

// create FFT object - we have to do if after allocating buffers
#if defined(FFT_LIB_REV) && FFT_LIB_REV > 0x19
Expand All @@ -527,7 +547,8 @@ void FFTcode(void * parameter)
// recommended version optimized by @softhack007 (API version 1.9)
#if defined(WLED_ENABLE_HUB75MATRIX) && defined(CONFIG_IDF_TARGET_ESP32)
static float* windowWeighingFactors = nullptr;
if (!windowWeighingFactors) windowWeighingFactors = (float*) calloc(samplesFFT, sizeof(float)); // cache for FFT windowing factors - use heap
if (!windowWeighingFactors) windowWeighingFactors = (float*) d_calloc(samplesFFT, sizeof(float)); // cache for FFT windowing factors - use heap
if (!windowWeighingFactors) { disableSoundProcessing = true; haveOldSamples = false; destroyFFTBuffers(); return; } // alloc failed
#else
static float windowWeighingFactors[samplesFFT] = {0.0f}; // cache for FFT windowing factors - use global RAM
#endif
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,8 @@ void RotaryEncoderUIUsermod::sortModesAndPalettes() {
}

byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
byte *indexes = (byte *)malloc(sizeof(byte) * numModes);
byte* indexes = (byte *)p_calloc(numModes, sizeof(byte));
if (!indexes) return nullptr; // avoid OOM crash
for (byte i = 0; i < numModes; i++) {
indexes[i] = i;
}
Expand All @@ -364,7 +365,9 @@ byte *RotaryEncoderUIUsermod::re_initIndexArray(int numModes) {
* They don't end in '\0', they end in '"'.
*/
const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int numModes) {
const char **modeStrings = (const char **)malloc(sizeof(const char *) * numModes);
const char** modeStrings = (const char **)p_calloc(numModes, sizeof(const char *));
if (!modeStrings) return nullptr; // avoid OOM crash

uint8_t modeIndex = 0;
bool insideQuotes = false;
// advance past the mark for markLineNum that may exist.
Expand All @@ -380,7 +383,7 @@ const char **RotaryEncoderUIUsermod::re_findModeStrings(const char json[], int n
insideQuotes = !insideQuotes;
if (insideQuotes) {
// We have a new mode or palette
modeStrings[modeIndex] = (char *)(json + i + 1);
if (modeIndex < numModes) modeStrings[modeIndex] = (char *)(json + i + 1); //WLEDMM prevent array bounds violation
}
break;
case '[':
Expand Down
6 changes: 3 additions & 3 deletions wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -609,7 +609,7 @@ typedef struct Segment {
endImagePlayback(this);
#endif

if ((Segment::_globalLeds == nullptr) && !strip_uses_global_leds() && (ledsrgb != nullptr)) {free(ledsrgb); ledsrgb = nullptr;} // WLEDMM we need "!strip_uses_global_leds()" to avoid crashes (#104)
if ((Segment::_globalLeds == nullptr) && !strip_uses_global_leds() && (ledsrgb != nullptr)) {d_free(ledsrgb); ledsrgb = nullptr;} // WLEDMM we need "!strip_uses_global_leds()" to avoid crashes (#104)
if (name) { delete[] name; name = nullptr; }
if (_t) { transitional = false; delete _t; _t = nullptr; }
deallocateData();
Expand Down Expand Up @@ -1009,15 +1009,15 @@ class WS2812FX { // 96 bytes
#ifdef WLED_DEBUG
if (Serial) Serial.println(F("~WS2812FX destroying strip.")); // WLEDMM can't use DEBUG_PRINTLN here
#endif
if (customMappingTable) delete[] customMappingTable;
if (customMappingTable) d_free(customMappingTable); customMappingTable = nullptr;
_mode.clear();
_modeData.clear();
_segments.clear();
#ifndef WLED_DISABLE_2D
panel.clear();
#endif
customPalettes.clear();
if (useLedsArray && Segment::_globalLeds) free(Segment::_globalLeds);
if (useLedsArray && Segment::_globalLeds) d_free(Segment::_globalLeds);
}

static WS2812FX* getInstance(void) { return instance; }
Expand Down
11 changes: 6 additions & 5 deletions wled00/FX_2Dfcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,11 +76,12 @@ void WS2812FX::setUpMatrix() {

// don't use new / delete
if ((size > 0) && (customMappingTable != nullptr)) { // resize
customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
//customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
customMappingTable = (uint16_t*) d_realloc_malloc(customMappingTable, sizeof(uint16_t) * size); // will free memory if it cannot resize
}
if ((size > 0) && (customMappingTable == nullptr)) { // second try
DEBUG_PRINTLN("setUpMatrix: trying to get fresh memory block.");
customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t));
customMappingTable = (uint16_t*) d_calloc(size, sizeof(uint16_t));
if (customMappingTable == nullptr) {
USER_PRINTLN("setUpMatrix: alloc failed");
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
Expand Down Expand Up @@ -122,7 +123,7 @@ void WS2812FX::setUpMatrix() {
JsonArray map = doc.as<JsonArray>();
gapSize = map.size();
if (!map.isNull() && (gapSize > 0) && gapSize >= customMappingSize) { // not an empty map //softhack also check gapSize>0
gapTable = new(std::nothrow) int8_t[gapSize];
gapTable = static_cast<int8_t*>(p_malloc(gapSize));
if (gapTable) for (size_t i = 0; i < gapSize; i++) {
gapTable[i] = constrain(map[i], -1, 1);
}
Expand Down Expand Up @@ -152,7 +153,7 @@ void WS2812FX::setUpMatrix() {
}

// delete gap array as we no longer need it
if (gapTable) {delete[] gapTable; gapTable=nullptr;} // softhack prevent dangling pointer
if (gapTable) {p_free(gapTable); gapTable=nullptr;} // softhack prevent dangling pointer

#ifdef WLED_DEBUG_MAPS
DEBUG_PRINTF("Matrix ledmap: \n");
Expand Down Expand Up @@ -185,7 +186,7 @@ void WS2812FX::setUpMatrix() {
if (customMappingTable[i] != (uint16_t)i ) isIdentity = false;
}
if (isIdentity) {
free(customMappingTable); customMappingTable = nullptr;
d_free(customMappingTable); customMappingTable = nullptr;
USER_PRINTF("!setupmatrix: customMappingTable is not needed. Dropping %d bytes.\n", customMappingTableSize * sizeof(uint16_t));
customMappingTableSize = 0;
customMappingSize = 0;
Expand Down
25 changes: 13 additions & 12 deletions wled00/FX_fcn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ void Segment::allocLeds() {
DEBUG_PRINTF("allocLeds warning: size == %u !!\n", size);
if (ledsrgb && (ledsrgbSize == 0)) {
USER_PRINTLN("allocLeds warning: ledsrgbSize == 0 but ledsrgb!=NULL");
free(ledsrgb); ledsrgb=nullptr;
d_free(ledsrgb); ledsrgb=nullptr;
} // softhack007 clean up buffer
}
if ((size > 0) && (!ledsrgb || size > ledsrgbSize)) { //softhack dont allocate zero bytes
Expand All @@ -124,8 +124,8 @@ void Segment::allocLeds() {
ledsrgb = nullptr;
portEXIT_CRITICAL(&ledsrgb_mux);

if (oldLedsRgb) free(oldLedsRgb); // we need a bigger buffer, so free the old one first
CRGB* newLedsRgb = (CRGB*)calloc(size, 1); // WLEDMM This is an OS call, so we should not wrap it in portEnterCRITICAL
if (oldLedsRgb) d_free(oldLedsRgb); // we need a bigger buffer, so free the old one first
CRGB* newLedsRgb = (CRGB*)d_calloc(size, 1); // WLEDMM This is an OS call, so we should not wrap it in portEnterCRITICAL

portENTER_CRITICAL(&ledsrgb_mux);
ledsrgb = newLedsRgb;
Expand Down Expand Up @@ -175,7 +175,7 @@ Segment& Segment::operator= (const Segment &orig) {
if (_t) delete _t;
CRGB* oldLeds = ledsrgb;
size_t oldLedsSize = ledsrgbSize;
if (ledsrgb && !Segment::_globalLeds) free(ledsrgb);
if (ledsrgb && !Segment::_globalLeds) d_free(ledsrgb);
deallocateData();
// copy source
memcpy((void*)this, (void*)&orig, sizeof(Segment));
Expand Down Expand Up @@ -212,7 +212,7 @@ Segment& Segment::operator= (Segment &&orig) noexcept {
if (name) { delete[] name; name = nullptr; } // free old name
deallocateData(); // free old runtime data
if (_t) { delete _t; _t = nullptr; }
if (ledsrgb && !Segment::_globalLeds) free(ledsrgb); //WLEDMM: not needed anymore as we will use leds from copy. no need to nullify ledsrgb as it gets new value in memcpy
if (ledsrgb && !Segment::_globalLeds) d_free(ledsrgb); //WLEDMM: not needed anymore as we will use leds from copy. no need to nullify ledsrgb as it gets new value in memcpy

// WLEDMM temporarily prevent any fast draw calls to old and new segment
orig._isSimpleSegment = false;
Expand Down Expand Up @@ -265,7 +265,7 @@ bool Segment::allocateData(size_t len, bool allowOverdraft) { // WLEDMM allowOv
// data = (byte*) ps_malloc(len);
//else
//#endif
data = (byte*) malloc(len);
data = (byte*) d_malloc(len);
if (!data) {
_dataLen = 0; // WLEDMM reset dataLen
if ((errorFlag != ERR_LOW_MEM) && (errorFlag != ERR_LOW_SEG_MEM)) { // spam filter
Expand All @@ -292,7 +292,7 @@ void Segment::deallocateData() {
_dataLen = 0;
return;
} // WLEDMM reset dataLen
free(data);
d_free(data);
data = nullptr;
DEBUG_PRINTF("Segment::deallocateData: free'd %d bytes.\n", _dataLen);
Segment::addUsedSegmentData(-_dataLen);
Expand All @@ -308,7 +308,7 @@ void Segment::deallocateData() {
*/
void Segment::resetIfRequired() {
if (reset) {
if (ledsrgb && !Segment::_globalLeds) { free(ledsrgb); ledsrgb = nullptr; ledsrgbSize=0;} // WLEDMM segment has changed, so we need a fresh buffer.
if (ledsrgb && !Segment::_globalLeds) { d_free(ledsrgb); ledsrgb = nullptr; ledsrgbSize=0;} // WLEDMM segment has changed, so we need a fresh buffer.
if (transitional && _t) { transitional = false; delete _t; _t = nullptr; }
deallocateData();
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
Expand Down Expand Up @@ -1866,7 +1866,7 @@ void WS2812FX::finalizeInit(void)
portENTER_CRITICAL(&ledsrgb_mux);
Segment::_globalLeds = nullptr;
portEXIT_CRITICAL(&ledsrgb_mux);
free(oldGLeds);
d_free(oldGLeds);
purgeSegments(true); // WLEDMM moved here, because it seems to improve stability.
}
if (useLedsArray && getLengthTotal()>0) { // WLEDMM avoid malloc(0)
Expand All @@ -1877,7 +1877,7 @@ void WS2812FX::finalizeInit(void)
// Segment::_globalLeds = (CRGB*) ps_malloc(arrSize);
//else
//#endif
if (arrSize > 0) Segment::_globalLeds = (CRGB*) malloc(arrSize); // WLEDMM avoid malloc(0)
if (arrSize > 0) Segment::_globalLeds = (CRGB*) d_malloc(arrSize); // WLEDMM avoid malloc(0)
if ((Segment::_globalLeds != nullptr) && (arrSize > 0)) memset(Segment::_globalLeds, 0, arrSize); // WLEDMM avoid dereferencing nullptr
if ((Segment::_globalLeds == nullptr) && (arrSize > 0)) errorFlag = ERR_NORAM_PX; // WLEDMM raise errorflag
}
Expand Down Expand Up @@ -2703,11 +2703,12 @@ bool WS2812FX::deserializeMap(uint8_t n) {

// don't use new / delete
if ((size > 0) && (customMappingTable != nullptr)) {
customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
//customMappingTable = (uint16_t*) reallocf(customMappingTable, sizeof(uint16_t) * size); // reallocf will free memory if it cannot resize
customMappingTable = (uint16_t*) d_realloc_malloc(customMappingTable, sizeof(uint16_t) * size); // will free memory if it cannot resize
}
if ((size > 0) && (customMappingTable == nullptr)) { // second try
DEBUG_PRINTLN("deserializeMap: trying to get fresh memory block.");
customMappingTable = (uint16_t*) calloc(size, sizeof(uint16_t));
customMappingTable = (uint16_t*) d_calloc(size, sizeof(uint16_t));
if (customMappingTable == nullptr) {
DEBUG_PRINTLN("deserializeMap: alloc failed!");
errorFlag = ERR_LOW_MEM; // WLEDMM raise errorflag
Expand Down
Loading