Skip to content

mpdecode_core: slice one shared subs pool per c/v-node array - #77

Open
LA7LKA wants to merge 1 commit into
drowe67:mainfrom
LA7LKA:fix/mpdecode-heap-fragmentation
Open

mpdecode_core: slice one shared subs pool per c/v-node array#77
LA7LKA wants to merge 1 commit into
drowe67:mainfrom
LA7LKA:fix/mpdecode-heap-fragmentation

Conversation

@LA7LKA

@LA7LKA LA7LKA commented Aug 5, 2026

Copy link
Copy Markdown

init_c_v_nodes() called CALLOC() once per c_node and once per v_node for their subs arrays - potentially thousands of small, irregularly-sized allocations per decoded frame, all freed again at the end of the same run_ldpc_decoder() call. On a memory-constrained embedded target (STM32F7, ~16 KB of heap actually available under a real application's other static usage) that churn fragments the heap badly enough to fail a request well before the nominal total bytes are exhausted - confirmed on hardware: a 24-byte allocation failing with a 16200-byte allocation (DecodedBits) having just succeeded moments earlier in the same call. Every CALLOC()/MALLOC() in this file is followed by an unchecked assert(), so a failed allocation is fatal in any build without -DNDEBUG.

Node degree is already fully known before any subs allocation happens, so this sums the total up front and hands out each node's subs as a slice of one shared CALLOC() instead of its own. Same total bytes, same per-node layout and population logic (untouched), just contiguous rather than thousands of separately-managed chunks - fragmentation between nodes is no longer possible. c_nodes[0].subs/v_nodes[0].subs are now the only real allocation base pointers; run_ldpc_decoder()'s cleanup is updated to match (freeing any other node's subs would corrupt the heap, since it's no longer a real malloc()/calloc() return value).

Reproduced and fixed against FreeDV 700D (H_16200_9720) on an STM32F746 running the full application (USB Audio Class, HMI, OLED, etc. all resident) - RX decode now completes cleanly where it previously hung every time on the first real frame.

init_c_v_nodes() called CALLOC() once per c_node and once per v_node for
their subs arrays - potentially thousands of small, irregularly-sized
allocations per decoded frame, all freed again at the end of the same
run_ldpc_decoder() call. On a memory-constrained embedded target (STM32F7,
~16 KB of heap actually available under a real application's other
static usage) that churn fragments the heap badly enough to fail a
request well before the nominal total bytes are exhausted - confirmed on
hardware: a 24-byte allocation failing with a 16200-byte allocation
(DecodedBits) having just succeeded moments earlier in the same call.
Every CALLOC()/MALLOC() in this file is followed by an unchecked
assert(), so a failed allocation is fatal in any build without
-DNDEBUG.

Node degree is already fully known before any subs allocation happens,
so this sums the total up front and hands out each node's subs as a
slice of one shared CALLOC() instead of its own. Same total bytes, same
per-node layout and population logic (untouched), just contiguous rather
than thousands of separately-managed chunks - fragmentation between
nodes is no longer possible. c_nodes[0].subs/v_nodes[0].subs are now the
only real allocation base pointers; run_ldpc_decoder()'s cleanup is
updated to match (freeing any other node's subs would corrupt the heap,
since it's no longer a real malloc()/calloc() return value).

Reproduced and fixed against FreeDV 700D (H_16200_9720) on an STM32F746
running the full application (USB Audio Class, HMI, OLED, etc. all
resident) - RX decode now completes cleanly where it previously hung
every time on the first real frame.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant