Skip to content

Binary Sections report overstates footprint: bss includes NSX's fill-to-end .heap reservation #24

Description

@apage224

Summary

`hpx profile`'s "Binary Sections" report (and `summary.json`'s
`memory.binary_sections`) computes text/data/bss via
`arm-none-eabi-size` (see `toolchain_probe.py::binary_sections()`).
That tool's classic Berkeley output lumps every allocated
`SHT_NOBITS` section into the "bss" column — including linker-reserved
regions that are never touched at runtime, not just real zero-initialized
globals.

On AP5 boards (apollo510_evb, apollo510b_evb, ...), the NSX-provided
linker script (`nsx-ambiq-sdk/modules/nsx-core/src/apollo510/gcc/linker_script_nbl.ld`)
deliberately fills all remaining DTCM as `.heap` (NOLOAD):

```ld
/* Heap occupies all remaining TCM between .bss and the top of the region.

  • _sbrk() in nsx-core enforces __HeapBase <= heap_end <= __HeapLimit so
  • unbounded malloc cannot silently corrupt .data/.bss. /
    .heap (NOLOAD):
    {
    . = ALIGN(8);
    __HeapBase = .;
    ...
    KEEP(
    (.heap))
    . = ORIGIN(MCU_TCM) + LENGTH(MCU_TCM); /* <- fills to end of DTCM */
    __HeapLimit = .;
    heap_end = .;
    } > MCU_TCM

This is a reasonable safety convention upstream (it lets a link-time
\`ASSERT\` catch \"stack+data+bss leave < 1 KiB of heap\"), and it costs
nothing at runtime (NOLOAD, no flash bytes, no init cost) — but it means
\`hpx profile\`'s reported \`bss\` figure for a small KWS model on AP510 is
**~476 KB**, when the real zero-initialized data (\`.bss\` proper) is only
~147 KB and the rest (~312 KB) is just unused, reserved-but-never-touched
heap address space.

## Impact

- Misleading at a glance: a user comparing two configs/boards, or
  comparing HPX's reported footprint against another tool's (e.g. a
  hand-rolled AmbiqSuite/neuralSPOT project that reserves a small fixed
  heap instead of filling-to-end), will see HPX's \"total\" binary size
  look far larger than the real utilized memory, with no indication that
  most of the delta is an inert reservation.
- Confirmed while investigating an AP510 KWS power/energy gap against
  AutoDeploy: an early size comparison mistakenly treated this bss/heap
  inflation as a real code-size difference before the NOLOAD nature of
  \`.heap\` was traced back to the linker script.

## Suggested fix (one or more of)

1. In \`toolchain_probe.py\`'s binary-size computation, additionally parse
   section headers (e.g. via \`arm-none-eabi-readelf -S\` /
   \`objdump -h\`) and separately report NOLOAD reservation sections
   (\`.heap\`, \`.stack\`, \`.sram_bss\`, \`.shared\`) from true
   zero-initialized \`.bss\`, so the CLI/summary can show something like
   \`bss (real): 147 KB, heap (reserved): 312 KB\`.
2. Document the fill-to-end heap convention in
   \`docs/guide/output.md\` / \`docs/architecture/pipeline.md\` next to the
   existing \"Binary section sizes\" description, so readers know not to
   read \`bss\` as \"all real static memory usage\" on AP5 boards.
3. Optionally, consider whether NSX's linker script itself should reserve
   a smaller, fixed heap by default instead of filling to the end of DTCM
   — but that's an upstream \`nsx-ambiq-sdk\` change, out of scope for this
   repo; (1)/(2) are the actionable items here.

## Where this was found

Repo memory: \`ap510-autodeploy-power-methodology-gap-resolved.md\`,
section \"ITCM/linker-wildcard hypothesis (2026-07-03)\".

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions