Skip to content

bash bloat / fbuild symbols: no arm-none-eabi path; can't measure LPC845 bloat from FastLED's wrapper #594

Closed
FastLED/FastLED
#3225

Description

@zackees

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 symbols already supports --build-info for this — build_info.json carries nm_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-analysis that bypasses the LD region check (or auto-patches an INPUT(...) override)
  • fbuild symbols --over-budget <env> that orchestrates the build + analyze in one step
  • Or simply: keep .lto-tmp/*.ltrans*.ltrans.o after a failed link so external nm can 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

  1. Edited platformio.ini to add -DFASTLED_LPC_RX_SCT_WS2812=1
  2. Edited ~/.fbuild/prod/cache/.../lpc845_flash.ld to bump FLASH from 64K → 128K
  3. Ran fbuild build -e lpc845brk — got a complete firmware.map even when the first attempt failed at link time
  4. 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.o chunks, but the map's 0x... <demangled> lines are still per-symbol)
  5. 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 .text with RX_SCT_WS2812=1: 76,272 bytes vs 65,232 budget → +11,040 bytes over (matches linker's +11,112 report 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: __adddf3 2,088 + __muldf3 1,392 + __subsf3 1,172 ≈ 5 KB despite the "no soft-FP" invariant
  • fl::string::trim() const = 2,966 bytes
  • fl::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 lpc845brk runs 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-nm from 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)

Activity

  1. zackees commented on Jun 15, 2026

    @zackees
    MemberAuthor

    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.

  2. zackees commented on Jun 15, 2026

    @zackees
    MemberAuthor

    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.map and 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×:

    Claimed Actual nm --print-size
    fl::CRGB::computeAdjustment 9,516 B 54 B
    fl::string::trim 2,966 B 164 B
    fl::basic_string::operator[] 1,572 B 54 B
    fl::sstream::operator<<(char) 1,250 B 28 B

    Had 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 symbols already calls nm --print-size correctly 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.map is not a substitute for nm-based bloat measurement. Anyone trying to work around this tooling gap by hand should run nm --print-size --size-sort against 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.

  3. zackees commented on Jun 18, 2026

    @zackees
    MemberAuthor

    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 lpc845brk working 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.

  4. zackees commented on Jun 18, 2026

    @zackees
    MemberAuthor

    FYI: arm-none-eabi support landed via FastLED/FastLED#3225 + the --allow-overflow flag. Workflow is documented in the FastLED-side Phase 3 burn-down meta at FastLED/FastLED#3244.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions