Baseline: static allocation throughout, and pools sized from measurement - #26
Conversation
Every task the application creates — log, service, harness and the Ethernet RX task — moves from xTaskCreate to xTaskCreateStatic, with its stack and TCB in .bss. configKERNEL_PROVIDED_STATIC_MEMORY was already set, so the idle and timer tasks were static already. Measured on the same tip, before and after: heap_used 57,024 -> 25,872 (-31,152) heap_peak 59,168 -> 28,000 (-31,168) static_bss 169,148 -> 200,204 (+31,056) A relocation, not a saving: 96 bytes net, which is the heap_4 per-block overhead no longer paid. What it buys is determinism — no allocation can fail and no fragmentation is possible in the part of the device we control. Measure gains heap_peak, from xPortGetMinimumEverFreeHeapSize. heap_used is sampled after the handshake has completed and freed its working state, so it understates what the device had to provision; before this change the gap was 2,144 bytes. What remains on the heap is lwIP's tcpip thread and its mailboxes, created inside lwIP's own FreeRTOS port, and mbedTLS's session. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
mbedTLS sub-allocates from a static buffer via MBEDTLS_MEMORY_BUFFER_ALLOC_C instead of pvPortMalloc, and MEMORY_DEBUG reports its high-water mark so the buffer is sized from what was measured. The FreeRTOS heap, now serving only lwIP's tcpip thread and mailboxes, drops from 96 KiB to 12 KiB. Measured across this branch, same tip: static RAM 169,804 -> 147,660 (-22,144) heap_used 57,024 -> 4,888 heap_peak 59,168 -> 4,888 mbedtls_peak - -> 22,184 Not a relocation this time: 22 KiB of committed RAM comes back, because the 96 KiB heap reservation was sized for allocations that are now static and bounded. What remains is deterministic — nothing the device provisions for logging can fail on a fragmented heap. The mbedTLS buffer is 32 KiB against a 22,184-byte peak, and the headroom is not generosity: 24 KiB fails. buffer_alloc needs contiguous space, so a peak that fits numerically can still be unsatisfiable. The device reports the peak precisely so this stays a measurement rather than a guess, and so a step that asks more of mbedTLS grows the buffer and is charged for it. The failure is loud, which is worth knowing: at 24 KiB the error handler reports MbedTlsStream 0x0402 on every attempt and run.sh fails the run because nothing reached the collector. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The log, service and harness stacks were sized generously and never revisited. Static allocation makes them visible in static RAM, so they are now sized from what the device reports using. log 4 KiB -> 2 KiB (832 bytes used) service 12 KiB -> 8 KiB (3,852 bytes used) harness 12 KiB -> 4 KiB static_bss 147,004 -> 132,668 (-14,336) The reported stack figures do not move: they are high-water usage, which does not depend on the allocation. What changes is the allocation, which is the part that costs RAM. Headroom is roughly two-fold on both application seams, and configCHECK_FOR_STACK_OVERFLOW is 2 with a hook, so a bad estimate fails loudly rather than corrupting memory. The service task is the one to watch: its depth comes from the TLS handshake, so it is the seam that grows when the transport does. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The four binary semaphores the log and service seams use for idle and emit signalling move to xSemaphoreCreateBinaryStatic. heap_used 4,888 -> 4,568 (-320) static_bss 132,668 -> 132,956 (+288) This is the last dynamic allocation the application makes. What remains on the FreeRTOS heap is lwIP's tcpip thread and its mailboxes, created inside lwIP's own FreeRTOS port, which would mean patching an upstream tree to change. SolidSyslog itself never allocated: SolidSyslogFreeRtosMutex_Create takes xSemaphoreCreateMutexStatic against a buffer in the library's own static pool, so the library's no-dynamic-allocation claim holds without qualification. The heap in these figures has always belonged to the device around it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (6)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthroughThe change reduces RTOS heap usage, converts application-created objects to static storage, moves simulated mbedTLS allocation to a fixed buffer, exposes peak usage, and updates measurement baseline handling and reports. ChangesStatic memory and measurement updates
Estimated code review effort: 3 (Moderate) | ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…he heap The netif bring-up semaphore and FatFs's volume mutex were the last objects the application created dynamically. Both are now static, and the netif semaphore no longer needs deleting. heap_used 4,568 -> 4,488 The application now calls vPortFree nowhere. Deleting a statically allocated semaphore does not reach the allocator either — FreeRTOS checks how the object was created — so the FatFs unmount path stays correct without being an exception. What is left on the heap is entirely lwIP's, created inside its FreeRTOS port: a recursive mutex for SYS_ARCH_PROTECT, a bring-up semaphore, the tcpip mailbox, and the tcpip thread. The port calls xSemaphoreCreateRecursiveMutex, xSemaphoreCreateBinary, xQueueCreate and xTaskCreate directly and offers no static variants, so removing it means replacing an upstream source file rather than setting an option. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@app/config/FreeRTOSConfig.h`:
- Around line 25-28: Update the allocation comments at
app/config/FreeRTOSConfig.h lines 25-28 to describe only lwIP’s tcpip thread and
mailbox allocations, removing RX, interactive, and service tasks and obsolete 96
KiB or xTaskCreate references. At app/config/mbedtls_user_config.h lines 83-90,
replace the FreeRTOS-heap allocator description with the static-buffer
initialization performed in app/sim/SimulatedExistingApp.c; no code behavior
changes are required.
In `@app/measure/Measure.h`:
- Around line 21-25: Regenerate the frozen baseline for the expanded measurement
schema so heap_peak and mbedtls_peak are represented instead of defaulting
missing values to zero. Update app/measure/Measure.h lines 21-25 and
app/measure/Measure.c lines 55-59 consistently with the schema, refresh
measurements/Baseline.csv with both fields, and update run-report.txt lines
14-15 to match the regenerated output; alternatively make app/measure/Baseline.c
reject baselines missing required keys.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: a93cde4f-d7dc-41ef-8b2a-311ba87dddd2
📒 Files selected for processing (12)
app/AppConfig.happ/config/FreeRTOSConfig.happ/config/mbedtls_user_config.happ/main.capp/measure/Measure.capp/measure/Measure.happ/net/EthernetIf.capp/sim/SimulatedExistingApp.capp/sim/SimulatedExistingApp.happ/tasks/LogTask.capp/tasks/ServiceTask.crun-report.txt
heap_1 replaces heap_4. Every allocation the device makes now happens once during bring-up and is never returned, so an allocator that cannot free is sufficient — and fragmentation stops being possible rather than merely unlikely. flash_text 362,032 -> 361,616 (-416) heap_used 4,488 -> 4,440 heap_1's vPortFree is configASSERT(pv == NULL), so the invariant is enforced rather than assumed: anything that frees stops the device instead of quietly leaking. heap_peak goes with it. heap_1 only moves a pointer forward, so free memory decreases monotonically and the current figure is the high-water mark by construction — a separate peak would be the same number with a different name. mbedtls_peak stays, because buffer_alloc does free internally. What this constrains: nothing in this device may ever free, including anything built on it. Creating and destroying a netif, or deleting a task, would assert. That is a deliberate trade for a device whose allocation is all at init. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ation comments A key the frozen baseline predates was reported as current-minus-zero, which reads as a delta and is not one — mbedtls_peak was claiming 22,236 bytes above a baseline that has never measured it. Baseline_Load now says which keys the file carried, and the report prints "-" for the rest. The allocation comments in FreeRTOSConfig.h and mbedtls_user_config.h still described xTaskCreate on a 96 KiB heap_4 region and a runtime calloc/free pair. Both are wrong after this branch, and the code under them is what changed. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
What this adds
Four commits making the device's allocation static and bounded, so the
no-dynamic-allocation claim holds end to end and the RAM it costs is chosen up front
rather than observed afterwards. All of it belongs to the baseline and folds into the
Baseline commit at the next regeneration.
.bss+.data)36,192 bytes of committed RAM recovered, and what remains cannot fail on a fragmented
heap.
The commits
heap_peak. All four application tasks move toxTaskCreateStatic.Measuregainsheap_peakfromxPortGetMinimumEverFreeHeapSize()—heap_usedis sampled after the handshake hasfreed its working state, so it understated what the device must provision, by 2,144 B.
MBEDTLS_MEMORY_BUFFER_ALLOC_C, withMEMORY_DEBUGreporting the high-water mark so the buffer is sized from measurement.The FreeRTOS heap, now serving only lwIP, drops 96 KiB → 12 KiB.
(3,852 B used), harness 12→4 KiB.
Two findings worth keeping
24 KiB of mbedTLS buffer fails despite a 22,252 B peak.
buffer_allocneeds contiguousspace, so a peak that fits numerically can still be unsatisfiable — the headroom at 32 KiB
is not generosity. The failure is loud: the error handler reports
MbedTlsStream 0x0402onevery attempt and
run.shfails the run because nothing reached the collector.SolidSyslog never allocated dynamically.
SolidSyslogFreeRtosMutex_CreateusesxSemaphoreCreateMutexStaticagainst a buffer in the library's own static pool. The heap inevery figure this repository has published belonged to the device around it, not to the
library.
Note on the figures
Baseline.csvpredatesheap_peakandmbedtls_peak, so theirused_above_baselinecolumns are meaningless until the baseline is regenerated. The self-check is skipped
regardless at this tag, so nothing is masked.
🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Bug Fixes