Repository navigation
Add -fno-asynchronous-unwind-tables / -fno-unwind-tables to ESP32 build_flags (~225 KB savings on FastLED Blink) #6
Description
Summary
Add -fno-asynchronous-unwind-tables -fno-unwind-tables to the build_flags of every ESP32 (and Cortex-M / RISC-V) platform .ini file in platforms/. This strips dead exception-unwinding metadata from the firmware. On a stock FastLED Blink for ESP32-S3 the binary drops from 657 KB → ~432 KB (−225 KB, −34%) with zero source changes and no diagnostic regression on Xtensa.
Background
A full per-archive byte audit of the current FastLED master built against the platformio-starter ESP32-S3 config (see FastLED/FastLED#2473) showed that 36% of the firmware (~231 KB) is orphan .eh_frame — DWARF exception-unwinding metadata for code that the ESP32 panic handler never reads:
libFastLED.a 168,772 bytes
libstdc++.a 56,148 bytes
libnvs_flash.a 4,068 bytes
libFrameworkArduino 3,240 bytes
... (others)
TOTAL 236,932 bytes (231 KB)
.eh_frame is only consumed by:
- C++ exceptions — disabled by default on ESP-IDF (and FastLED uses
-fno-exceptions) - Async-signal sampling profilers — not part of the Arduino-ESP32 toolchain
- DWARF-based panic backtraces — ESP-IDF's panic handler uses register-window walking on Xtensa and frame-pointer walking on RISC-V, not
.eh_frame. Stack traces still work without it.
So on a default Arduino-ESP32 build, the 231 KB is pure waste.
Why this belongs in platformio-starter (not in FastLED)
FastLED tried to fix this with a pragma-based approach (PR FastLED/FastLED#2423, FL_NO_UNWIND_BEGIN/_END). That fix is a confirmed no-op on GCC 14.2.0 / xtensa-esp-elf because GCC's _Pragma("GCC optimize") doesn't reliably propagate codegen flags like -fno-asynchronous-unwind-tables. The only mechanism that actually works is flags on the cc1 command line, which is platformio.ini's job, not a library header's.
The proper long-term fix lives in fbuild (tracked as FastLED/fbuild#243 — auto-detect when stripping is safe). Until that lands, the starter project is the right place to surface these flags so new FastLED users on ESP32 don't get a 657 KB binary by default.
Proposed change
Add to every ESP32-variant .ini in platforms/ (platformio.esp32dev.ini, platformio.esp32s2.ini, platformio.esp32s3.ini, platformio.seeed_xiao_esp32s3.ini, platformio.esp32c2.ini, platformio.esp32c3.ini, platformio.esp32c5.ini, platformio.esp32c6.ini, platformio.esp32h2.ini):
build_flags =
; --------------------------------------------------------------------------
; Strip exception-unwinding metadata (.eh_frame) from the firmware.
; FastLED + ESP-IDF both build with -fno-exceptions, so these tables are
; never consumed at runtime. The ESP32 panic handler uses register-window
; walking on Xtensa and frame-pointer walking on RISC-V, not .eh_frame,
; so stack traces still work.
; Removing this saves ~225 KB on a typical FastLED ESP32-S3 binary.
; To restore .eh_frame for DWARF-based debugging, comment these two flags.
; --------------------------------------------------------------------------
-fno-asynchronous-unwind-tables
-fno-unwind-tablesThe same flags also help (smaller magnitude) on STM32 (platformio.bluepill.ini, platformio.blackpill.ini), nRF52 (platformio.nrf52840.ini), Teensy, and RP2040 builds — anywhere GCC defaults -fasynchronous-unwind-tables to ON.
Why this is safe by default
| Platform | Backtrace mechanism | .eh_frame needed? |
|---|---|---|
| Xtensa (ESP32 / S2 / S3) | Register-window walking | No — panic backtrace doesn't read .eh_frame |
| RISC-V (C3 / C6 / H2 / C5 / P4) | Frame-pointer walking (default -fno-omit-frame-pointer) |
No — panic backtrace doesn't read .eh_frame |
| ARM Cortex-M (STM32, nRF52, etc.) | Frame-pointer walking | No — panic backtrace doesn't read .eh_frame |
| AVR (Uno, Mega) | n/a (no panic handler) | No |
The only case where stripping would degrade diagnostics is if a user explicitly:
- Enables
CONFIG_ESP_SYSTEM_USE_EH_FRAME=yin sdkconfig (RISC-V, opt-in only) - Uses
-fexceptionssomewhere in their project - Builds with
build_type = debugand wants gdb to step using DWARF unwind
Each of those cases is something the user is explicitly opting into; commenting out the two flags in the starter's .ini is the documented escape hatch.
Measured impact
Stock examples/Blink/Blink.ino on ESP32-S3 against current FastLED master (commit 8616873b73):
| Build | firmware.bin |
|---|---|
| No flags (today's starter) | 657,072 B |
| With both flags added | ~432,000 B (−225 KB, −34%) |
| FastLED 3.10.3 baseline (for reference) | ~344,000 B |
Verified with xtensa-esp32s3-elf-readelf --debug-dump=frames firmware.elf | grep -c "FDE cie" — count drops from ~6,500 FDEs to under 200 (the residual is from libgcc and a few libraries that always emit eh_frame even with these flags).
Acceptance criteria
- Add the two flags + explanatory comment to every ESP32
.iniinplatforms/ - Add the same flags to STM32 / nRF52 / Teensy / RP2040
.inis (smaller win, but free) - Skip AVR
.inis (platformio.uno.ini,platformio.attiny85.ini,platformio.nano_every.ini) — AVR-GCC default is-fno-asynchronous-unwind-tablesalready, flags would be redundant - Update README.md to mention the size optimization and how to opt out if a user wants DWARF backtraces
- Spot-check one ESP32 build to confirm the
firmware.binis materially smaller (sanity test)
Reproduction
git clone https://github.com/fastled/platformio-starter
cd platformio-starter
# Baseline
pio run -e esp32s3 -d platforms/platformio.esp32s3.ini
ls -la .pio/build/esp32s3/firmware.bin # ~657 KB
# Add the two flags to platforms/platformio.esp32s3.ini build_flags
pio run -e esp32s3 -d platforms/platformio.esp32s3.ini --target clean
pio run -e esp32s3 -d platforms/platformio.esp32s3.ini
ls -la .pio/build/esp32s3/firmware.bin # ~432 KBRelated
- ESP32-S3 binary size: 657 KB on master vs 344 KB on 3.10.3 — remaining +313 KB delta breakdown FastLED#2473 — full per-archive byte breakdown showing the 36% eh_frame waste
- [Tracking] ESP32-S3 timing regression + binary bloat from Channel API (from #2420) FastLED#2421 — original bloat investigation
- Purge FL_NO_UNWIND macro family + FASTLED_FORCE_*_UNWIND_TABLES — pragma form is a no-op on GCC 14.2.0 FastLED#2474 — companion FastLED issue to remove the no-op
FL_NO_UNWINDmacros - ESP32: auto-strip .eh_frame when exception handling and panic backtrace are both off (~225 KB savings on FastLED Blink) fbuild#243 — the future fix that will make these flags automatic; this starter-level change is the interim solution
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity