Repository navigation
Description
Summary
bash bloat <board> (ci/bloat.py) is wired only for ESP32-family chips. Running it on lpc845brk fails immediately because there's no arm-none-eabi entry in BOARD_CHIP_MAP. The underlying fbuild symbols already supports any ELF + matching nm, so the gap is purely in the shim.
This blocks per-symbol bloat analysis on the LPC8xx low-memory bring-up — the very target where every byte matters and where the -DFASTLED_LPC_RX_SCT_WS2812=1 config currently overflows the 64 KB FLASH region by 11,112 bytes.
Reproduction
$ bash bloat lpc845brk
Bloat: board 'lpc845brk' has no nm mapping. Add it to BOARD_CHIP_MAP in
ci/bloat.py — entries are mechanical (arch + chip prefix). Supported
boards today: esp32, esp32c3, esp32c6, esp32h2, esp32s2, esp32s3.
ci/bloat.py:75 only registers Xtensa + RISC-V ESP32 chips:
BOARD_CHIP_MAP = {
"esp32": ("xtensa", "esp32"),
"esp32s2": ("xtensa", "esp32s2"),
"esp32s3": ("xtensa", "esp32s3"),
"esp32c3": ("riscv", "esp32c3"),
"esp32c6": ("riscv", "esp32c6"),
"esp32h2": ("riscv", "esp32h2"),
}And PIO_NM_BY_ARCH (above) only knows about toolchain-xtensa-* and toolchain-riscv32-esp-elf PIO packages.
What's missing
Three independent gaps:
1. ci/bloat.py BOARD_CHIP_MAP — no arm-none-eabi entry
Need an "arm" arch family that points at the developer-gnu toolchain. The toolchain lives at:
~/.fbuild/prod/cache/toolchains/developer-gnu/<hash>/15.2.Rel1/bin/arm-none-eabi-nm.exe
That's a fbuild cache path, not a ~/.platformio/packages path, so pio_packages_dir() won't find it. The shim needs to either:
- Query fbuild for the resolved nm path (
fbuild symbolsalready supports--build-infofor this —build_info.jsoncarriesnm_path), or - Add an alternate "fbuild-managed" toolchain lookup branch alongside the existing PIO branch.
2. fbuild build has no "emit ELF on overflow" mode
When you want to measure over-budget bloat, the build has to succeed enough to produce an ELF. Today, an LD region overflowed by N bytes error is fatal — no ELF emitted, LTO temp objects deleted on exit. The only workaround is to hand-patch the LD script in the fbuild cache (~/.fbuild/prod/cache/platforms/.../lpc845_flash.ld → bump LENGTH = 64K to 128K), build, then revert. That is the workflow I had to use to gather this report.
Reasonable surfaces:
fbuild build --no-flash-check/--bloat-analysisthat bypasses the LD region check (or auto-patches anINPUT(...)override)fbuild symbols --over-budget <env>that orchestrates the build + analyze in one step- Or simply: keep
.lto-tmp/*.ltrans*.ltrans.oafter a failed link so externalnmcan be run on the LTO partitions directly
3. bash bloat doesn't speak fbuild-native builds
The shim looks for ELFs at:
build_root / "pio" / board / ".pio" / "build" / board / "firmware.elf"
build_root / board / "firmware.elf"fbuild emits to .fbuild/build/<env>/release/firmware.elf — that path isn't in the candidate list, so even on green builds bash bloat lpc845brk would still fail to locate the ELF.
What I actually did to work around it
- Edited
platformio.inito add-DFASTLED_LPC_RX_SCT_WS2812=1 - Edited
~/.fbuild/prod/cache/.../lpc845_flash.ldto bump FLASH from 64K → 128K - Ran
fbuild build -e lpc845brk— got a completefirmware.mapeven when the first attempt failed at link time - Parsed the linker map with a one-off Python script to compute per-symbol sizes via address deltas (LTO partitions merge into 2 giant
ltrans*.ltrans.ochunks, but the map's0x... <demangled>lines are still per-symbol) - Reverted everything
This worked but is the wrong shape — there should be a one-command path.
Findings the workaround surfaced (separate from the tooling gap)
For tracking, the symbol-level results of the workaround run:
- Total
.textwithRX_SCT_WS2812=1: 76,272 bytes vs 65,232 budget → +11,040 bytes over (matches linker's+11,112report within fixup math) - Top single symbol:
fl::CRGB::computeAdjustment(unsigned char, fl::CRGB const&, fl::CRGB const&)= 9,516 bytes (12.5% of.text) - libgcc double-precision soft-FP cascade pulled in:
__adddf32,088 +__muldf31,392 +__subsf31,172 ≈ 5 KB despite the "no soft-FP" invariant fl::string::trim() const= 2,966 bytesfl::variant<fl::function<…(fl::json const&)>::…>copy/move/destroy clones across the FreeFunction / InlinedLambda / NonConstMember / ConstMember alternatives ≈ 12 KB combined
These are separate FastLED-side bug-class issues and should be filed against FastLED, not fbuild. This issue is purely about the toolchain gap.
Acceptance criteria
-
bash bloat lpc845brkruns end-to-end against a fresh build and prints a per-symbol top-N table - When the build is over-budget, the bloat command surfaces the overage and still produces an ELF + report (degenerate-case workflow)
- The
arm-none-eabi-nmfrom fbuild's developer-gnu toolchain is auto-resolved (no PIO dependency)
Out of scope
- Fixing the actual FastLED-side bloat (file separate issues against FastLED for
CRGB::computeAdjustment, soft-FP cascade in low-memory mode,fl::string::trim, etc.) - Adding more ESP32 chips / adding non-LPC ARM boards (this issue is about the arch family, LPC845 is the test case)
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
zackees commentedon Jun 15, 2026
Tracked from FastLED side as meta FastLED/FastLED#3079 (LPC845 64 KB flash bloat hunt). The four FastLED sub-issues (FastLED/FastLED#3075 #3076 #3077 #3078) each need this tooling to iterate efficiently — they will close via measurable .text reductions, but every PR currently has to repeat the LD-script-patching workaround documented in #3079 to get the measurement.
zackees commentedon Jun 15, 2026
2026-06-15 addendum: methodology hazard surfaced
While working FastLED/FastLED#3079 (the LPC845 bloat hunt that depends on this issue), I made the predictable mistake that the missing tooling enables: I worked around the gap by writing a one-off Python script that parsed
firmware.mapand attributed the byte-delta between consecutive symbol addresses as the size of the earlier symbol. That methodology is invalid — the linker map only lists a sparse subset of symbols, so the address gap between two listed names often contains many unlisted intermediates. My parser attributed all that intervening code to the visible neighbor.Concrete fallout: I filed four sub-issues against FastLED/FastLED#3079 claiming overstatements of 18-176×:
nm --print-sizefl::CRGB::computeAdjustment9,516 Bfl::string::trim2,966 Bfl::basic_string::operator[]1,572 Bfl::sstream::operator<<(char)1,250 BHad to close two of those issues (FastLED/FastLED#3075, FastLED/FastLED#3078) as filed-in-error and re-do the analysis. The actual top contributors were completely different — RPC dispatch monoliths and JSON codec, not CRGB / string code.
This kind of mistake is what a real
bash bloat <arm-board>would have prevented:fbuild symbolsalready callsnm --print-sizecorrectly and emits a per-symbol table. The only thing keeping it from being trivially usable on LPC845 is the three gaps documented in the original report (BOARD_CHIP_MAP missing arm entry, no ELF-on-overflow mode,.fbuild/build/<env>/release/not in candidate path).Adding a callout to the issue: address-delta parsing of
firmware.mapis not a substitute for nm-based bloat measurement. Anyone trying to work around this tooling gap by hand should runnm --print-size --size-sortagainst the ELF, not parse the map. The map is useful for call-graph attribution (archive provenance, LTO partition membership) but not for per-symbol sizes.perf(fl::json): route i64->float conversions through i32 to eliminate…
perf(fl::json): route i64->float conversions through i32 to eliminate…
zackees commentedon Jun 18, 2026
Phase 2 meta filed on FastLED side: FastLED/FastLED#3224 — "LPC845 flash-budget Phase 2".
It explicitly names this tooling gap as the single biggest velocity multiplier for the next reduction arc. 8 sub-issues will be filed under it; every one would benefit from
bash bloat lpc845brkworking end-to-end (BOARD_CHIP_MAP arm entry + ELF-on-overflow mode +.fbuild/build/candidate path). Per FastLED/FastLED#3224 acceptance, the meta does not block on this issue but the per-PR iteration cost will roughly 10× without it.feat(bloat): delegate nm + ELF path resolution to fbuild symbols (arm…
feat(build): --bloat-analysis adds -Wl,--noinhibit-exec for over-budg…
zackees commentedon Jun 18, 2026
FYI: arm-none-eabi support landed via FastLED/FastLED#3225 + the
--allow-overflowflag. Workflow is documented in the FastLED-side Phase 3 burn-down meta at FastLED/FastLED#3244.