On ESP a growing string asks for double its size and runs out of heap
Found 2026-11-01 while measuring append cost after the Copy fix (bug-b-builtin-copy-builds-its-result-a-char-at-a-time).
PXXStrAppend grew a full block to need * 2. On a PC that costs nothing
(untouched capacity is virtual memory), but on ESP it is real RAM. Under
QEMU, one string grown in 1 KB appends died with "out of memory" at about
90 KB on an S3, while the heap had 295 KB free and a 164 KB largest block:
the next growth asked for about 190 KB.
Fix (owner's call: no doubling on ESP, a slack of 8-16 KB is plenty because
ESP code is mostly single-threaded and strings die at function return). On
either ESP profile the growth is need + min(need, 16 KB). A short string still
doubles, so it never carries more slack than its own size. A flat 16 KB would
have cost every short string that is appended once and kept in a list. PCs keep
doubling.
Trap on the way: in builtinheap.pas, PXX_ESP means the BARE profile only
(line 18), so the first cut under {$ifdef PXX_ESP} never reached an IDF
build. The S3 trace was identical to the pin's until the condition became
defined(PXX_ESP_BARE) or defined(PXX_ESP_IDF).
Measured (QEMU, IDF 6.0.1): the largest string the S3 reaches went from ~90 KB to ~160 KB. Past that, copy-on-grow needs the old and new blocks at once, so the old block plus 16 KB must fit beside the old block. The C3 reaches ~120 KB.
Test. test/esp_a_string_grows_past_its_doubled_size.pas grows to 112 KB, in test-esp-idf on C3 and S3. Output identical to x86-64.
Not done: no in-place realloc (heap_caps_realloc could extend without the copy, which would lift the old+new limit), and no change to string loops in app or library code (owner: not yet).