Skip to content

bloat: consolidate log sites in Rmt5EncoderImpl::initialize + extract esp-idf-error-wrap helper #2977

@zackees

Description

@zackees

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

  • initialize() size drops below ~700 B in default release build (was 1,466 B)
  • FASTLED_LOG_VERBOSITY=1 default: WARN/ERROR/INFO still fire; only the demoted DBG sites are quiet
  • FASTLED_LOG_VERBOSITY=2+ : all previous diagnostic output restored
  • checkEspIdfOk helper lives in a shared rmt_5 source file (not the .hpp body) so format/emit machinery is paid once
  • No regression in error reporting: a failing rmt_new_*_encoder still emits the API name + esp_err_to_name(err) at WARN level

Refs

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions