Skip to content

Commit d3eddeb

Browse files
authored
Ensure xPortGetFreeHeapSize reports DRAM (#8680)
Create dedicated function for xPortGetFreeHeapSize() that only reports on DRAM. NONOS SDK API system_get_free_heap_size() relies on xPortGetFreeHeapSize() for the free Heap size. Possible breaking change for multiple Heap Sketches calling system_get_free_heap_size(); it will now always report free DRAM Heap size. Update and export umm_free_heap_size_lw() to report the free Heap size of the current Heap. Updated ESP.getFreeHeap() to use umm_free_heap_size_lw(). Updated build options to supply exported umm_free_heap_size_lw() via either UMM_STATS or UMM_INFO. Improved build option support via the SketchName.ino.globals.h method for Heap options: UMM_INFO, UMM_INLINE_METRICS, UMM_STATS, UMM_STATS_FULL, UMM_BEST_FIT, and UMM_FIRST_FIT. While uncommon to change from the defaults, you can review umm_malloc_cfgport.h for more details, which may help reduce your Sketch's size in dire situations. Assuming you are willing to give up some functionality. For debugging UMM_STATS_FULL can offer additional stats, like Heap low water mark (umm_free_heap_size_min()).
1 parent 7b2c627 commit d3eddeb

14 files changed

+386
-150
lines changed

cores/esp8266/Arduino.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ extern "C" {
3333
#include <string.h>
3434
#include <math.h>
3535

36+
#include "umm_malloc/umm_malloc_cfgport.h"
3637
#include "stdlib_noniso.h"
3738
#include "binary.h"
3839
#include "esp8266_peri.h"
@@ -311,7 +312,8 @@ void configTime(const char* tz, String server1,
311312
#endif
312313

313314
#ifdef DEBUG_ESP_OOM
314-
// reinclude *alloc redefinition because of <cstdlib> undefining them
315-
// this is mandatory for allowing OOM *alloc definitions in .ino files
316-
#include "umm_malloc/umm_malloc_cfg.h"
315+
// Position *alloc redefinition at the end of Arduino.h because <cstdlib> would
316+
// have undefined them. Mandatory for supporting OOM and other debug alloc
317+
// definitions in .ino files
318+
#include "heap_api_debug.h"
317319
#endif

cores/esp8266/Esp-frag.cpp

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
*/
2020

2121
#include "umm_malloc/umm_malloc.h"
22-
#include "umm_malloc/umm_malloc_cfg.h"
2322
#include "coredecls.h"
2423
#include "Esp.h"
2524

25+
#if defined(UMM_INFO)
2626
void EspClass::getHeapStats(uint32_t* hfree, uint32_t* hmax, uint8_t* hfrag)
2727
{
2828
// L2 / Euclidean norm of free block sizes.
@@ -60,3 +60,4 @@ uint8_t EspClass::getHeapFragmentation()
6060
{
6161
return (uint8_t)umm_fragmentation_metric();
6262
}
63+
#endif

cores/esp8266/Esp.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,15 @@ uint16_t EspClass::getVcc(void)
219219

220220
uint32_t EspClass::getFreeHeap(void)
221221
{
222-
return system_get_free_heap_size();
222+
return umm_free_heap_size_lw();
223223
}
224224

225+
#if defined(UMM_INFO)
225226
uint32_t EspClass::getMaxFreeBlockSize(void)
226227
{
227228
return umm_max_block_size();
228229
}
230+
#endif
229231

230232
uint32_t EspClass::getFreeContStack()
231233
{

cores/esp8266/Esp.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,12 @@ class EspClass {
114114
static uint32_t getChipId();
115115

116116
static uint32_t getFreeHeap();
117+
#if defined(UMM_INFO)
117118
static uint32_t getMaxFreeBlockSize();
118119
static uint8_t getHeapFragmentation(); // in %
119120
static void getHeapStats(uint32_t* free = nullptr, uint16_t* max = nullptr, uint8_t* frag = nullptr) __attribute__((deprecated("Use 'uint32_t*' on max, 2nd argument")));
120121
static void getHeapStats(uint32_t* free = nullptr, uint32_t* max = nullptr, uint8_t* frag = nullptr);
121-
122+
#endif
122123
static uint32_t getFreeContStack();
123124
static void resetFreeContStack();
124125

cores/esp8266/heap.cpp

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ extern "C" size_t umm_umul_sat(const size_t a, const size_t b);
1111
extern "C" void z2EapFree(void *ptr, const char* file, int line) __attribute__((weak, alias("vPortFree"), nothrow));
1212
// I don't understand all the compiler noise around this alias.
1313
// Adding "__attribute__ ((nothrow))" seems to resolve the issue.
14-
// This may be relevant: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81824
14+
// This may be relevant: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=81824
1515

1616
// Need FORCE_ALWAYS_INLINE to put HeapSelect class constructor/deconstructor in IRAM
1717
#define FORCE_ALWAYS_INLINE_HEAP_SELECT
@@ -342,8 +342,10 @@ size_t IRAM_ATTR xPortWantedSizeAlign(size_t size)
342342

343343
void system_show_malloc(void)
344344
{
345+
#ifdef UMM_INFO
345346
HeapSelectDram ephemeral;
346347
umm_info(NULL, true);
348+
#endif
347349
}
348350

349351
/*

cores/esp8266/heap_api_debug.h

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
/*
2+
* Issolated heap debug helper code from from umm_malloc/umm_malloc_cfg.h.
3+
* Updated umm_malloc/umm_malloc.h and Arduino.h to reference.
4+
* No #ifdef fenceing was used before. From its previous location, this content
5+
* was reassert multiple times through Arduino.h. In case there are legacy
6+
* projects that depend on the previous unfenced behavior, no fencing has been
7+
* added.
8+
*/
9+
10+
/*
11+
* *alloc redefinition - Included from Arduino.h for DEBUG_ESP_OOM support.
12+
*
13+
* It can also be directly include by the sketch for UMM_POISON_CHECK or
14+
* UMM_POISON_CHECK_LITE builds to get more info about the caller when they
15+
* report on a fail.
16+
*/
17+
18+
#ifdef __cplusplus
19+
extern "C" {
20+
#endif
21+
22+
#ifdef DEBUG_ESP_OOM
23+
#define MEMLEAK_DEBUG
24+
25+
#include "umm_malloc/umm_malloc_cfg.h"
26+
27+
#include <pgmspace.h>
28+
// Reuse pvPort* calls, since they already support passing location information.
29+
// Specifically the debug version (heap_...) that does not force DRAM heap.
30+
void *IRAM_ATTR heap_pvPortMalloc(size_t size, const char *file, int line);
31+
void *IRAM_ATTR heap_pvPortCalloc(size_t count, size_t size, const char *file, int line);
32+
void *IRAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char *file, int line);
33+
void *IRAM_ATTR heap_pvPortZalloc(size_t size, const char *file, int line);
34+
void IRAM_ATTR heap_vPortFree(void *ptr, const char *file, int line);
35+
36+
#define malloc(s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortMalloc(s, mem_debug_file, __LINE__); })
37+
#define calloc(n,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortCalloc(n, s, mem_debug_file, __LINE__); })
38+
#define realloc(p,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortRealloc(p, s, mem_debug_file, __LINE__); })
39+
40+
#if defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE)
41+
#define dbg_heap_free(p) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_vPortFree(p, mem_debug_file, __LINE__); })
42+
#else
43+
#define dbg_heap_free(p) free(p)
44+
#endif
45+
46+
#elif defined(UMM_POISON_CHECK) || defined(UMM_POISON_CHECK_LITE) // #elif for #ifdef DEBUG_ESP_OOM
47+
#include <pgmspace.h>
48+
void *IRAM_ATTR heap_pvPortRealloc(void *ptr, size_t size, const char *file, int line);
49+
#define realloc(p,s) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_pvPortRealloc(p, s, mem_debug_file, __LINE__); })
50+
51+
void IRAM_ATTR heap_vPortFree(void *ptr, const char *file, int line);
52+
// C - to be discussed
53+
/*
54+
Problem, I would like to report the file and line number with the umm poison
55+
event as close as possible to the event. The #define method works for malloc,
56+
calloc, and realloc those names are not as generic as free. A #define free
57+
captures too much. Classes with methods called free are included :(
58+
Inline functions would report the address of the inline function in the .h
59+
not where they are called.
60+
61+
Anybody know a trick to make this work?
62+
63+
Create dbg_heap_free() as an alternative for free() when you need a little
64+
more help in debugging the more challenging problems.
65+
*/
66+
#define dbg_heap_free(p) ({ static const char mem_debug_file[] PROGMEM STORE_ATTR = __FILE__; heap_vPortFree(p, mem_debug_file, __LINE__); })
67+
68+
#else
69+
#define dbg_heap_free(p) free(p)
70+
#endif /* DEBUG_ESP_OOM */
71+
72+
#ifdef __cplusplus
73+
}
74+
#endif

cores/esp8266/mmu_iram.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ extern "C" {
5656
#define DEV_DEBUG_PRINT
5757
*/
5858

59-
#if defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)
59+
#if (defined(DEV_DEBUG_PRINT) || defined(DEBUG_ESP_MMU)) && !defined(HOST_MOCK)
60+
// Errors follow when `#include <esp8266_peri.h>` is present when running CI HOST
6061
#include <esp8266_peri.h>
6162

6263
#define DBG_MMU_FLUSH(a) while((USS(a) >> USTXC) & 0xff) {}

cores/esp8266/umm_malloc/Notes.h

+66
Original file line numberDiff line numberDiff line change
@@ -276,4 +276,70 @@ Enhancement ideas:
276276
#endif
277277
```
278278
*/
279+
280+
/*
281+
Sep 26, 2022
282+
283+
History/Overview
284+
285+
ESP.getFreeHeap() needs a function it can call for free Heap size. The legacy
286+
method was the SDK function `system_get_free_heap_size()` which is in IRAM.
287+
288+
`system_get_free_heap_size()` calls `xPortGetFreeHeapSize()` to get free heap
289+
size. Our old legacy implementation used umm_info(), employing a
290+
time-consuming method for getting free Heap size and runs with interrupts
291+
blocked.
292+
293+
Later we used a distributed method that maintained the free heap size with
294+
each malloc API call that changed the Heap. (enabled by build option UMM_STATS
295+
or UMM_STATS_FULL) We used an internally function `umm_free_heap_size_lw()` to
296+
report free heap size. We satisfied the requirements for
297+
`xPortGetFreeHeapSize()` with an alias to `umm_free_heap_size_lw()`
298+
in replacement for the legacy umm_info() call wrapper.
299+
300+
The upstream umm_malloc later implemented a similar method enabled by build
301+
option UMM_INLINE_METRICS and introduced the function `umm_free_heap_size()`.
302+
303+
The NONOS SDK alloc request must use the DRAM Heap. Need to Ensure DRAM Heap
304+
results when multiple Heap support is enabled. Since the SDK uses portable
305+
malloc calls pvPortMalloc, ... we leveraged that for a solution - force
306+
pvPortMalloc, ... APIs to serve DRAM only.
307+
308+
In an oversight, `xPortGetFreeHeapSize()` was left reporting the results for
309+
the current heap selection via `umm_free_heap_size_lw()`. Thus, if an SDK
310+
function like os_printf_plus were called when the current heap selection was
311+
IRAM, it would get the results for the IRAM Heap. Then would receive DRAM with
312+
an alloc request. However, when the free IRAM size is too small, it would
313+
skip the Heap alloc request and use stack space.
314+
315+
Solution
316+
317+
The resolution is to rely on build UMM_STATS(default) or UMM_STATS_FULL for
318+
free heap size information. When not available in the build, fallback to the
319+
upstream umm_malloc's `umm_free_heap_size()` and require the build option
320+
UMM_INLINE_METRICS. Otherwise, fail the build.
321+
322+
Use function name `umm_free_heap_size_lw()` to support external request for
323+
current heap size. When build options result in fallback using umm_info.c,
324+
ensure UMM_INLINE_METRICS enabled and alias to `umm_free_heap_size()`.
325+
326+
For the multiple Heap case, `xPortGetFreeHeapSize()` becomes a unique function
327+
and reports only DRAM free heap size. Now `system_get_free_heap_size()` will
328+
always report DRAM free Heap size. This might be a breaking change.
329+
330+
Specifics:
331+
332+
* Support `umm_free_heap_size_lw()` as an `extern`.
333+
334+
* When the build options UMM_STATS/UMM_STATS_FULL are not used, fallback to
335+
the upstream umm_malloc's `umm_free_heap_size()` function in umm_info.c
336+
* require the UMM_INLINE_METRICS build option.
337+
* assign `umm_free_heap_size_lw()` as an alias to `umm_free_heap_size()`
338+
339+
* `xPortGetFreeHeapSize()`
340+
* For single heap builds, alias to `umm_free_heap_size_lw()`
341+
* For multiple Heaps builds, add a dedicated function that always reports
342+
DRAM results.
343+
344+
*/
279345
#endif

cores/esp8266/umm_malloc/umm_info.c

+8
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ size_t umm_free_heap_size_core(umm_heap_context_t *_context) {
174174
return (size_t)_context->info.freeBlocks * sizeof(umm_block);
175175
}
176176

177+
/*
178+
When used as the fallback option for supporting exported function
179+
`umm_free_heap_size_lw()`, the build option UMM_INLINE_METRICS is required.
180+
Otherwise, umm_info() would be used to complete the operation, which uses a
181+
time-consuming method for getting free Heap and runs with interrupts off,
182+
which can negatively impact WiFi operations. Also, it cannot support calls
183+
from ISRs, `umm_info()` runs from flash.
184+
*/
177185
size_t umm_free_heap_size(void) {
178186
#ifndef UMM_INLINE_METRICS
179187
umm_info(NULL, false);

cores/esp8266/umm_malloc/umm_local.c

+70-8
Original file line numberDiff line numberDiff line change
@@ -161,35 +161,97 @@ void umm_poison_free_fl(void *ptr, const char *file, int line) {
161161
/* ------------------------------------------------------------------------ */
162162

163163
#if defined(UMM_STATS) || defined(UMM_STATS_FULL) || defined(UMM_INFO)
164+
/*
165+
For internal, mainly used by UMM_STATS_FULL; exported so external components
166+
can perform Heap related calculations.
167+
*/
164168
size_t umm_block_size(void) {
165169
return sizeof(umm_block);
166170
}
167171
#endif
168172

173+
/*
174+
Need to expose a function to support getting the current free heap size.
175+
Export `size_t umm_free_heap_size_lw(void)` for this purpose.
176+
Used by ESP.getFreeHeap().
177+
178+
For an expanded discussion see Notes.h, entry dated "Sep 26, 2022"
179+
*/
169180
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
170-
// Keep complete call path in IRAM
181+
/*
182+
Default build option to support export.
183+
184+
Keep complete call path in IRAM.
185+
*/
171186
size_t umm_free_heap_size_lw(void) {
172187
UMM_CHECK_INITIALIZED();
173188

174189
umm_heap_context_t *_context = umm_get_current_heap();
175190
return (size_t)_context->UMM_FREE_BLOCKS * sizeof(umm_block);
176191
}
177-
#endif
178192

193+
#elif defined(UMM_INLINE_METRICS)
179194
/*
180-
I assume xPortGetFreeHeapSize needs to be in IRAM. Since
181-
system_get_free_heap_size is in IRAM. Which would mean, umm_free_heap_size()
182-
in flash, was not a safe alternative for returning the same information.
195+
For the fallback option using `size_t umm_free_heap_size(void)`, we must have
196+
the UMM_INLINE_METRICS build option enabled to support free heap size
197+
reporting without the use of `umm_info()`.
183198
*/
199+
size_t umm_free_heap_size_lw(void) __attribute__ ((alias("umm_free_heap_size")));
200+
201+
#else
202+
/*
203+
We require a resource to track and report free Heap size with low overhead.
204+
For an expanded discussion see Notes.h, entry dated "Sep 26, 2022"
205+
*/
206+
#error UMM_INLINE_METRICS, UMM_STATS, or UMM_STATS_FULL needs to be defined.
207+
#endif
208+
184209
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
185-
size_t xPortGetFreeHeapSize(void) __attribute__ ((alias("umm_free_heap_size_lw")));
210+
size_t umm_free_heap_size_core_lw(umm_heap_context_t *_context) {
211+
return (size_t)_context->UMM_FREE_BLOCKS * sizeof(umm_block);
212+
}
213+
186214
#elif defined(UMM_INFO)
187-
#ifndef UMM_INLINE_METRICS
188-
#warning "No ISR safe function available to implement xPortGetFreeHeapSize()"
215+
// Backfill support for umm_free_heap_size_core_lw()
216+
size_t umm_free_heap_size_core_lw(umm_heap_context_t *_context) __attribute__ ((alias("umm_free_heap_size_core")));
189217
#endif
218+
219+
/*
220+
This API is called by `system_get_free_heap_size()` which is in IRAM. Driving
221+
the assumption the callee may be in an ISR or Cache_Read_Disable state. Use
222+
IRAM to ensure that the complete call chain is in IRAM.
223+
224+
To satisfy this requirement, we need UMM_STATS... or UMM_INLINE_METRICS
225+
defined. These support an always available without intense computation
226+
free-Heap value.
227+
228+
Like the other vPort... APIs used by the SDK, this must always report on the
229+
DRAM Heap not the current Heap.
230+
*/
231+
#if (UMM_NUM_HEAPS == 1)
232+
// Reduce IRAM usage for the single Heap case
233+
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
234+
size_t xPortGetFreeHeapSize(void) __attribute__ ((alias("umm_free_heap_size_lw")));
235+
#else
190236
size_t xPortGetFreeHeapSize(void) __attribute__ ((alias("umm_free_heap_size")));
191237
#endif
192238

239+
#else
240+
size_t xPortGetFreeHeapSize(void) {
241+
#if defined(UMM_STATS) || defined(UMM_STATS_FULL) || defined(UMM_INLINE_METRICS)
242+
UMM_CHECK_INITIALIZED();
243+
umm_heap_context_t *_context = umm_get_heap_by_id(UMM_HEAP_DRAM);
244+
245+
return umm_free_heap_size_core_lw(_context);
246+
#else
247+
// At this time, this build path is not reachable. In case things change,
248+
// keep build check.
249+
// Not in IRAM, umm_info() would have been used to complete this operation.
250+
#error "No ISR safe function available to implement xPortGetFreeHeapSize()"
251+
#endif
252+
}
253+
#endif
254+
193255
#if defined(UMM_STATS) || defined(UMM_STATS_FULL)
194256
void umm_print_stats(int force) {
195257
umm_heap_context_t *_context = umm_get_current_heap();

cores/esp8266/umm_malloc/umm_malloc.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010

1111
#include <stdint.h>
1212

13-
// C This include is not in upstream
13+
// C These includes are not in the upstream
1414
#include "umm_malloc_cfg.h" /* user-dependent */
15+
#include <osapi.h>
16+
#include <heap_api_debug.h>
1517

1618
#ifdef __cplusplus
1719
extern "C" {

0 commit comments

Comments
 (0)