Skip to content

Add -fno-asynchronous-unwind-tables / -fno-unwind-tables to ESP32 build_flags (~225 KB savings on FastLED Blink) #6

@zackees

Description

@zackees

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:

  1. C++ exceptions — disabled by default on ESP-IDF (and FastLED uses -fno-exceptions)
  2. Async-signal sampling profilers — not part of the Arduino-ESP32 toolchain
  3. 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-tables

The 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=y in sdkconfig (RISC-V, opt-in only)
  • Uses -fexceptions somewhere in their project
  • Builds with build_type = debug and 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 .ini in platforms/
  • 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-tables already, 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.bin is 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 KB

Related

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