Tracked in #2974 (Reduce per-symbol bloat in the FastLED 1-4KB function band on ESP32-S3).
Target
fl::detail::Rmt5EncoderImpl::initialize(fl::ChipsetTiming const&, unsigned long) — currently 1,466 B on ESP32-S3 Blink.
File: src/platforms/esp/32/drivers/rmt/rmt_5/rmt5_peripheral_esp.cpp.hpp (lines 672-724).
Log-site breakdown
initialize() currently contains 7 FL_WARN call sites, each of which materializes the full sstream ctor + <<-chain + log_emit + landing-pad sequence (~200-300 B inline + ~100-200 B EH metadata per site, as documented in #2974):
| # |
Line |
Site |
Category |
| 1 |
685 |
Timing config: resolution=... ns_per_tick=... |
INFO-class (non-actionable diagnostic) |
| 2 |
687 |
Bit0: high=... low=... |
INFO-class (non-actionable diagnostic) |
| 3 |
688 |
Bit1: high=... low=... |
INFO-class (non-actionable diagnostic) |
| 4 |
689 |
Reset: ... ticks |
INFO-class (non-actionable diagnostic) |
| 5 |
704 |
Failed to create bytes encoder: <err> |
esp-idf error wrap |
| 6 |
711 |
Failed to create copy encoder: <err> |
esp-idf error wrap |
| 7 |
722 |
Encoder created successfully |
INFO-class success log |
Plus a 8th site in the private ctor at line 597 (Initialization failed: <err>) which is a downstream wrapper of the same esp_err_t returned from initialize().
The top-callees fingerprint matches the meta-issue's "smoking-gun" pattern exactly: log_emit (222 B), _Unwind_Resume (156 B), rmt_new_bytes_encoder (151 B), rmt_new_copy_encoder (126 B), fl::fastled_file_offset (113 B), sstream::appendFormatted (90+83 B), rmt_del_encoder (55 B), esp_err_to_name (38 B), string ctor/dtor/append (~87 B).
Proposal
1. Demote the 5 non-actionable INFO sites to FL_DBG
Sites #1-#4 and #7 are pure "happy-path / configuration dump" diagnostics — they only matter when debugging timing math or confirming the encoder built. Constraint per #2974 is logging stays on, but FL_DBG (a verbose level) is still logging. Behavior under FASTLED_LOG_VERBOSITY=1 (default) keeps WARN/ERROR/INFO active; DBG fires only at higher verbosity.
Estimated savings: 5 sites × ~200 B inline + ~100 B EH metadata = ~1,000-1,500 B in default release builds (each FL_DBG site compiles to an empty stub or guarded path at the default verbosity).
2. Extract the esp-idf error-wrap pattern into a single [[gnu::cold]] helper
The pattern at sites #5, #6 (and elsewhere across the rmt_5 driver) is repetitive:
esp_err_t err = rmt_new_X_encoder(&cfg, &handle);
if (err != ESP_OK) {
FL_WARN(\"[RMT5_ENCODER] Failed to create X encoder: \" << esp_err_to_name(err));
// optional cleanup
return err;
}
Replace with a single out-of-line cold helper:
// In a shared rmt_5 .cpp (not in the .hpp body, so the format chain ships ONCE)
[[gnu::cold]] [[gnu::noinline]]
bool checkEspIdfOk(esp_err_t err, const char* api, const char* file, int line) FL_NOEXCEPT {
if (FL_LIKELY(err == ESP_OK)) return true;
fl::detail::log_emit(fl::log_kind::warn, file, line, fl::sstream{}
<< \"[RMT5] \" << api << \" failed: \" << esp_err_to_name(err));
return false;
}
#define FL_CHECK_ESP_IDF(call) checkEspIdfOk((call), #call, __FILE__, __LINE__)
Call-site shrinks from a full sstream-format-emit burst (~200-300 B + landing pad) to a single call + bnez + j (~20-30 B). The full sstream chain + esp_err_to_name + log_emit invocation lives once in the cold helper, paid for once globally.
Estimated savings on initialize alone: ~400-500 B (2 wrap sites × ~200 B saved + EH landing pads collapsed).
Reusable across: every other ESP-IDF API call in rmt5_peripheral_esp.cpp.hpp, channel_driver_rmt.cpp.hpp, RmtMemoryManager (TBD-G in #2974 has the same pattern), and ChannelEngineRMTImpl::createChannel (TBD-D). Combined savings multiplied across the rmt_5 layer likely 2-3 KB additional flash.
Combined estimate for this sub-issue
- DBG demotion of 5 informational sites: ~1,000-1,500 B off
initialize
- esp-idf-wrap helper applied to 2 error sites in
initialize: ~400-500 B
- Total on
initialize: ~1,400-2,000 B (could approach zero residual logging overhead in the function body itself, leaving only the underlying esp-idf API calls).
The helper is then available for #2974 sub-issues D and G to use as well.
Acceptance criteria
Refs
Tracked in #2974 (Reduce per-symbol bloat in the FastLED 1-4KB function band on ESP32-S3).
Target
fl::detail::Rmt5EncoderImpl::initialize(fl::ChipsetTiming const&, unsigned long)— currently 1,466 B on ESP32-S3 Blink.File:
src/platforms/esp/32/drivers/rmt/rmt_5/rmt5_peripheral_esp.cpp.hpp(lines 672-724).Log-site breakdown
initialize()currently contains 7FL_WARNcall sites, each of which materializes the fullsstreamctor +<<-chain +log_emit+ landing-pad sequence (~200-300 B inline + ~100-200 B EH metadata per site, as documented in #2974):Timing config: resolution=... ns_per_tick=...Bit0: high=... low=...Bit1: high=... low=...Reset: ... ticksFailed to create bytes encoder: <err>Failed to create copy encoder: <err>Encoder created successfullyPlus a 8th site in the private ctor at line 597 (
Initialization failed: <err>) which is a downstream wrapper of the sameesp_err_treturned frominitialize().The top-callees fingerprint matches the meta-issue's "smoking-gun" pattern exactly:
log_emit(222 B),_Unwind_Resume(156 B),rmt_new_bytes_encoder(151 B),rmt_new_copy_encoder(126 B),fl::fastled_file_offset(113 B),sstream::appendFormatted(90+83 B),rmt_del_encoder(55 B),esp_err_to_name(38 B), string ctor/dtor/append (~87 B).Proposal
1. Demote the 5 non-actionable INFO sites to
FL_DBGSites #1-#4 and #7 are pure "happy-path / configuration dump" diagnostics — they only matter when debugging timing math or confirming the encoder built. Constraint per #2974 is logging stays on, but
FL_DBG(a verbose level) is still logging. Behavior underFASTLED_LOG_VERBOSITY=1(default) keeps WARN/ERROR/INFO active; DBG fires only at higher verbosity.Estimated savings: 5 sites × ~200 B inline + ~100 B EH metadata = ~1,000-1,500 B in default release builds (each
FL_DBGsite compiles to an empty stub or guarded path at the default verbosity).2. Extract the esp-idf error-wrap pattern into a single
[[gnu::cold]]helperThe pattern at sites #5, #6 (and elsewhere across the rmt_5 driver) is repetitive:
Replace with a single out-of-line cold helper:
Call-site shrinks from a full sstream-format-emit burst (~200-300 B + landing pad) to a single
call+bnez+j(~20-30 B). The full sstream chain +esp_err_to_name+log_emitinvocation lives once in the cold helper, paid for once globally.Estimated savings on
initializealone: ~400-500 B (2 wrap sites × ~200 B saved + EH landing pads collapsed).Reusable across: every other ESP-IDF API call in
rmt5_peripheral_esp.cpp.hpp,channel_driver_rmt.cpp.hpp,RmtMemoryManager(TBD-G in #2974 has the same pattern), andChannelEngineRMTImpl::createChannel(TBD-D). Combined savings multiplied across the rmt_5 layer likely 2-3 KB additional flash.Combined estimate for this sub-issue
initializeinitialize: ~400-500 Binitialize: ~1,400-2,000 B (could approach zero residual logging overhead in the function body itself, leaving only the underlying esp-idf API calls).The helper is then available for #2974 sub-issues D and G to use as well.
Acceptance criteria
initialize()size drops below ~700 B in default release build (was 1,466 B)FASTLED_LOG_VERBOSITY=1default: WARN/ERROR/INFO still fire; only the demoted DBG sites are quietFASTLED_LOG_VERBOSITY=2+ : all previous diagnostic output restoredcheckEspIdfOkhelper lives in a shared rmt_5 source file (not the .hpp body) so format/emit machinery is paid oncermt_new_*_encoderstill emits the API name +esp_err_to_name(err)at WARN levelRefs