Skip to content

feat(stl): FL_NO_INLINE portable macro + cold-helper split for showPixels (#2773 item 2.1)#2830

Merged
zackees merged 1 commit into
masterfrom
feat/2773-2.1-showpixels-fastpath
Jun 5, 2026
Merged

feat(stl): FL_NO_INLINE portable macro + cold-helper split for showPixels (#2773 item 2.1)#2830
zackees merged 1 commit into
masterfrom
feat/2773-2.1-showpixels-fastpath

Conversation

@zackees
Copy link
Copy Markdown
Member

@zackees zackees commented Jun 5, 2026

Summary

Two coupled changes from #2773 item 2.1's punch list:

  1. Portable FL_NO_INLINE macro + lint guard — sweep 12 bare __attribute__((noinline)) uses in src/ to the portable macro, add a lint check (ci/lint_cpp/bare_noinline_checker.py) that prevents future regressions.
  2. Channel::showPixels cold-helper split — extract resolveDynamicDriver() so the hot legacy addLeds<> path stays compact.

User feedback that prompted #1: "we should FL_NO_INLINE, and add a lint for __attribute__((noinline)) so we don't have this again".

Why the macro

The bare __attribute__((noinline)) form silently no-ops under MSVC, so a host build through the Windows toolchain (CI's WASM/host tests) loses the noinline semantics — exactly the kind of latent regression #2773 item 2.1 needs to prevent. The hot/cold split this PR introduces collapses back into the hot path if any future change strips the attribute on one toolchain.

Why the showPixels split

The meta tracker described 2.1 as an "iterator scaling" issue. Disassembling the current 3.8 KB Channel::showPixels showed the bulk is per-frame driver-resolution boilerplate, not iterator code: fl::string busKey construction, slow selectDriverForChannel path, three formatted FL_ERROR stream chains for the typed-Bus-miss hints, etc.

On stock Blink (legacy addLeds<NEOPIXEL>ClocklessIdf5 constructor pre-binds the driver), mDriverPreBound == true and the entire dynamic-resolution + diagnostic block is dead-at-runtime but linked. Extracting it into a FL_NO_INLINE resolveDynamicDriver() helper gives the compiler the seam it needs to keep the hot path compact.

Measured on ESP32-S3 Blink (PlatformIO + IDF 5.4)

Build firmware.bin
master (pre-this-PR) 659,920 B
this PR (default verbosity) 659,776 B
this PR + -DFASTLED_LOG_VERBOSITY=0 616,144 B

Net default delta: −144 B. Much smaller than the original ~3 KB projection — confirms the meta-issue's assumption that the slow path "dead-strips" on legacy builds was off (LTO + -Og doesn't lift the cold body on its own; the explicit hot/cold split gives the compiler the seam it needs). The -DFASTLED_LOG_VERBOSITY=0 path is unchanged — verifying no regression on the opt-in slim build.

The architectural cleanup is load-bearing for the next iterations: once resolveDynamicDriver is its own function, follow-up work can extract the diagnostic strings entirely behind a verbosity gate without touching the hot path, and the slow path can be moved to a separate TU and gated for tree-shaking.

Lint guard (item 2.1 follow-up)

ci/lint_cpp/bare_noinline_checker.py:

  • Bans __attribute__((noinline)) and __declspec(noinline) in src/
  • Exempts the macro shim itself (fl/stl/compiler_control.{h,cpp.hpp})
  • Exempts src/third_party/ (upstream code we don't control)
  • Suppression: // ok noinline at end of line

Migrated all 12 existing call sites:

File Sites
src/fl/gfx/blur.cpp.hpp 3
src/fl/math/memmove.h 2 (AVR-only memcpy8/memset8)
src/platforms/arm/stm32/fastspi_arm_stm32.h 1
src/platforms/esp/32/core/fastspi_esp32_arduino.h 1
src/platforms/esp/32/core/fastspi_esp32_idf.h 1
src/platforms/esp/32/drivers/rmt/rmt_4/channel_driver_rmt4.h 7
src/platforms/esp/32/drivers/rmt/rmt_5/channel_driver_rmt.cpp.hpp 2
src/platforms/esp/8266/fastspi_esp8266.h 1
src/platforms/shared/spi_bitbang/generic_software_spi.h 1

Plus #include \"fl/stl/compiler_control.h\" added to src/fl/math/memmove.h (the only file that wasn't pulling it in transitively).

Tests

  • bash compile esp32s3 --examples Blink --platformio — succeeds, 659,776 B
  • bash compile esp32s3 --examples Blink --platformio --defines FASTLED_LOG_VERBOSITY=0 — 616,144 B (identical to master at the same flag)
  • uv run python ci/lint_cpp/bare_noinline_checker.py — clean on src/ after the migration

Refs: #2773 #2473 #2421

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Refactor

    • Improved Channel driver resolution by separating dynamic driver selection into a dedicated helper, optimizing the fast path for legacy controllers.
    • Standardized compiler directives across the codebase using a new portable FL_NO_INLINE macro for better cross-platform compatibility.
  • Chores

    • Added linter enforcement to ensure consistent use of the FL_NO_INLINE macro instead of bare compiler-specific directives.

…xels (#2773 item 2.1)

Two coupled changes for the binary-size meta tracker (#2773):

## 1. Portable `FL_NO_INLINE` macro + lint guard

The codebase had 12+ bare `__attribute__((noinline))` uses scattered
across `src/`. The bare GCC/Clang form silently no-ops under MSVC, so a
host build through the Windows toolchain (CI's WASM/host tests) was
quietly losing the `noinline` semantics — exactly the kind of latent
regression #2773 item 2.1 needs to prevent. The hot/cold split that
this PR introduces collapses straight back into the hot path if any
future change strips the attribute on one toolchain.

- `src/fl/stl/compiler_control.h`: new `FL_NO_INLINE` macro that
  expands to `__attribute__((noinline))` on GCC/Clang and
  `__declspec(noinline)` on MSVC. Pattern mirrors the existing
  `FL_NO_INLINE_IF_AVR` and `FL_CONSTRUCTOR` shims in the same file.
- `ci/lint_cpp/bare_noinline_checker.py`: new lint check that bans
  `__attribute__((noinline))` and `__declspec(noinline)` in `src/`
  unless the file is `fl/stl/compiler_control.{h,cpp.hpp}` (the macro
  shim itself), under `third_party/`, or carries a `// ok noinline`
  suppression. Wired into `run_all_checkers.py` next to the existing
  `BareSnprintfChecker` (the same regression-guard pattern from #2773
  item 1.5).
- Migrated all 12 existing call sites to `FL_NO_INLINE`:
  - `src/fl/gfx/blur.cpp.hpp` (3 sites)
  - `src/fl/math/memmove.h` (2 sites — AVR-only memcpy8/memset8 prototypes)
  - `src/platforms/arm/stm32/fastspi_arm_stm32.h` (1 site)
  - `src/platforms/esp/32/core/fastspi_esp32_arduino.h` (1 site)
  - `src/platforms/esp/32/core/fastspi_esp32_idf.h` (1 site)
  - `src/platforms/esp/32/drivers/rmt/rmt_4/channel_driver_rmt4.h` (7 sites)
  - `src/platforms/esp/32/drivers/rmt/rmt_5/channel_driver_rmt.cpp.hpp` (2 sites)
  - `src/platforms/esp/8266/fastspi_esp8266.h` (1 site)
  - `src/platforms/shared/spi_bitbang/generic_software_spi.h` (1 site)
- Added `#include "fl/stl/compiler_control.h"` to
  `src/fl/math/memmove.h` (the only migrated file that didn't already
  pull it in transitively).

## 2. `Channel::showPixels` cold-helper split (#2773 item 2.1)

The meta tracker described item 2.1 as a `Channel::showPixels`
"iterator scaling" tier. Disassembly of the current 3.8 KB function
showed something different — the bulk of the size is per-frame
**driver-resolution boilerplate**:

- `fl::string busKey` constructor + literal initialization
- `if (mDriverPreBound)` short-circuit AND the slow `selectDriverForChannel` path
- One-shot `FL_ERROR` diagnostic with three formatted stream chains for
  the typed-Bus-miss hint (`fl::enableDrivers<>()` /
  `FastLED.enableAllDrivers()` / `addLeds<..., fl::Bus::X>()`)
- One-shot `FL_ERROR` for the bus/chipset-mismatch case
- One-shot `FL_ERROR` for the no-driver case

On the stock Blink workload mDriverPreBound is `true` (legacy
`addLeds<NEOPIXEL>` route via `ClocklessIdf5`'s constructor), so the
entire dynamic-resolution + diagnostic block is dead-at-runtime but
linked-into-binary. Extracting it into a `FL_NO_INLINE
resolveDynamicDriver()` helper keeps the hot path compact and lets the
compiler keep the cold body separately optimizable.

### Measured on ESP32-S3 Blink (PlatformIO + IDF 5.4)

| Build | `firmware.bin` |
|---|---:|
| master (pre-this-PR) | 659,920 B |
| this PR (default verbosity) | 659,776 B |
| this PR + `-DFASTLED_LOG_VERBOSITY=0` | 616,144 B |

Net default delta: **−144 B**. Modest vs the original ~3 KB
projection — confirms the meta-issue's assumption that the slow path
"dead-strips" on legacy builds was off (LTO+`-Og` doesn't lift the
cold body out of the hot icache line on its own; the explicit hot/cold
split gives the compiler the seam it needs to make conservative
choices). The `-DFASTLED_LOG_VERBOSITY=0` path is unchanged at
616,144 B — verifying no regression on the opt-in slim build.

The architectural cleanup is load-bearing for the next iterations:
once `resolveDynamicDriver` is its own function, follow-up work can
move the diagnostic strings entirely behind `FASTLED_LOG_VERBOSITY > 1`
without touching the hot path, and the slow path itself can be
extracted to a separate TU and gated for tree-shaking.

## Tests

- [x] `bash compile esp32s3 --examples Blink --platformio` — succeeds
- [x] `bash compile esp32s3 --examples Blink --platformio --defines
  FASTLED_LOG_VERBOSITY=0` — 616,144 B, identical to master at the
  same flag
- [x] `uv run python ci/lint_cpp/bare_noinline_checker.py` — clean on
  src/ after the migration

Refs: #2773 #2473 #2421

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 5, 2026

PR changed again? Review this PR in Change Stack to compare snapshots and stay oriented.

Review Change Stack

📝 Walkthrough

Walkthrough

This PR standardizes noinline directives across FastLED by introducing a portable FL_NO_INLINE macro, enforcing its adoption via a new CI lint checker, refactoring Channel driver resolution to optimize hot paths with the macro, and systematically migrating all existing bare compiler-specific noinline attributes to the standard.

Changes

Noinline Standardization & Driver Resolution Refactoring

Layer / File(s) Summary
FL_NO_INLINE macro definition
src/fl/stl/compiler_control.h
Adds FL_NO_INLINE that expands to __attribute__((noinline)) on GCC/Clang, __declspec(noinline) on MSVC, and no-op on other platforms, with documentation directing src/ adoption.
Lint checker implementation
ci/lint_cpp/bare_noinline_checker.py, ci/lint_cpp/run_all_checkers.py
BareNoInlineChecker scans src/ for bare noinline attributes (skipping exempt paths and third-party), handles block comments and per-line suppression, and reports violations. Registered in the global checker list.
Channel driver resolution refactoring
src/fl/channels/channel.h, src/fl/channels/channel.cpp.hpp
Extracts dynamic driver selection and bus-key miss diagnostics into a cold resolveDynamicDriver() helper marked FL_NO_INLINE. showPixels() now uses a fast path for prebound drivers and delegates to the helper otherwise, enabling early silent bailout.
Noinline annotation standardization
src/fl/math/memmove.h, src/fl/gfx/blur.cpp.hpp, src/platforms/arm/stm32/fastspi_arm_stm32.h, src/platforms/esp/32/core/fastspi_esp32_arduino.h, src/platforms/esp/32/core/fastspi_esp32_idf.h, src/platforms/esp/32/drivers/rmt/rmt_4/channel_driver_rmt4.h, src/platforms/esp/32/drivers/rmt/rmt_5/channel_driver_rmt.cpp.hpp, src/platforms/esp/8266/fastspi_esp8266.h, src/platforms/shared/spi_bitbang/generic_software_spi.h
Replaces bare __attribute__((noinline)) and __declspec(noinline) with FL_NO_INLINE across utility functions (memcpy8, memset8, blur passes), all platform SPI drivers, and RMT ISR/helper functions. Function signatures and behavior unchanged.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • FastLED/FastLED#2826: Both PRs refactor cold-path diagnostic message emission in src/platforms/esp/32/drivers/rmt/rmt_5/channel_driver_rmt.cpp.hpp into noinline helpers, with this PR additionally standardizing the annotation to FL_NO_INLINE.
  • FastLED/FastLED#2441: Both PRs modify src/fl/channels/channel.h/.cpp.hpp driver resolution logic, with this PR extracting the slow path into a separate resolveDynamicDriver() helper to optimize the hot path for prebound drivers.
  • FastLED/FastLED#2252: Both PRs modify RMT4 IRAM ISR functions in src/platforms/esp/32/drivers/rmt/rmt_4/channel_driver_rmt4.h, with this PR standardizing noinline annotations to FL_NO_INLINE for consistency.

Poem

A rabbit hops through compiler gates,
with FL_NO_INLINE that standardizes fate,
refactoring drivers cold and clean,
the linter now keeps directives keen. 🐰
From bare attributes, we now abstain,
one portable macro ends the strain!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.22% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes both main changes: introducing FL_NO_INLINE macro and splitting showPixels, with specific reference to the tracked issue.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/2773-2.1-showpixels-fastpath

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ci/lint_cpp/bare_noinline_checker.py`:
- Around line 78-85: The block-comment handling currently skips entire lines
when toggling in_block_comment, which misses banned tokens that appear before a
"/*" or after a "*/"; update the logic around in_block_comment (the code that
checks for "/*" and "*/" and the variable in_block_comment) to scan and operate
on the parts of the line outside block-comment spans instead of continuing
immediately: when a "/*" is found, check the substring before it for banned
tokens, then set in_block_comment and continue scanning after any matching "*/"
(and when "*/" is found, check the substring after it for banned tokens and
clear in_block_comment); apply the same substring-aware approach to the other
occurrence of this logic later in the file (the block handling around the second
in_block_comment check) so tokens on the same line as comment delimiters are
still inspected.
- Line 54: The __init__ method is missing an explicit return type annotation;
update the constructor signature (the __init__ function in
bare_noinline_checker.py) to include an explicit None return type (i.e., change
def __init__(self): to def __init__(self) -> None:) to satisfy the project rule
that all function return types must be annotated.

In `@src/fl/channels/channel.cpp.hpp`:
- Around line 343-344: The comment currently references the wrong macro name
`FL_NOINLINE`; update the comment to use the correct macro `FL_NO_INLINE` (the
sentence describing why the helper is marked noinline that references
`showPixels` should read `FL_NO_INLINE`) so the documentation matches the actual
symbol used in the code.

In `@src/fl/stl/compiler_control.h`:
- Around line 333-341: The new comment block for the FL_NO_INLINE macro contains
non-ASCII punctuation (e.g., em-dashes and other Unicode punctuation); edit the
comment around the FL_NO_INLINE usage description in compiler_control.h to
replace all non-ASCII punctuation with plain ASCII equivalents (use
hyphen/minus/double-hyphen for dashes, straight quotes, plain ellipsis or three
dots, etc.) so the file is 7-bit ASCII compliant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 16e63967-6707-4d06-97df-2a33cd7e885d

📥 Commits

Reviewing files that changed from the base of the PR and between 6dc3073 and 8b251d7.

📒 Files selected for processing (14)
  • ci/lint_cpp/bare_noinline_checker.py
  • ci/lint_cpp/run_all_checkers.py
  • src/fl/channels/channel.cpp.hpp
  • src/fl/channels/channel.h
  • src/fl/gfx/blur.cpp.hpp
  • src/fl/math/memmove.h
  • src/fl/stl/compiler_control.h
  • src/platforms/arm/stm32/fastspi_arm_stm32.h
  • src/platforms/esp/32/core/fastspi_esp32_arduino.h
  • src/platforms/esp/32/core/fastspi_esp32_idf.h
  • src/platforms/esp/32/drivers/rmt/rmt_4/channel_driver_rmt4.h
  • src/platforms/esp/32/drivers/rmt/rmt_5/channel_driver_rmt.cpp.hpp
  • src/platforms/esp/8266/fastspi_esp8266.h
  • src/platforms/shared/spi_bitbang/generic_software_spi.h

class BareNoInlineChecker(FileContentChecker):
"""Bans bare `noinline` compiler attributes in src/ (#2773 item 2.1 follow-up)."""

def __init__(self):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add explicit return annotation for __init__.

Use def __init__(self) -> None: to satisfy the Python annotation rule.

As per coding guidelines, "**/*.py: All function return types MUST have explicit type annotations."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/lint_cpp/bare_noinline_checker.py` at line 54, The __init__ method is
missing an explicit return type annotation; update the constructor signature
(the __init__ function in bare_noinline_checker.py) to include an explicit None
return type (i.e., change def __init__(self): to def __init__(self) -> None:) to
satisfy the project rule that all function return types must be annotated.

Comment on lines +78 to +85
if in_block_comment:
if "*/" in line:
in_block_comment = False
continue
if "/*" in line and "*/" not in line:
in_block_comment = True
continue

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Block-comment handling can miss real violations.

The current flow skips whole lines when block comments open/close, so banned tokens can slip through when they appear before /* or after */ on the same line. That creates false negatives in enforcement.

Proposed fix sketch
-            if in_block_comment:
-                if "*/" in line:
-                    in_block_comment = False
-                continue
-            if "/*" in line and "*/" not in line:
-                in_block_comment = True
-                continue
+            # Strip block comments while preserving code outside comments.
+            code_line = line
+            if in_block_comment:
+                end = code_line.find("*/")
+                if end == -1:
+                    continue
+                in_block_comment = False
+                code_line = code_line[end + 2 :]
+
+            while "/*" in code_line:
+                start = code_line.find("/*")
+                end = code_line.find("*/", start + 2)
+                if end == -1:
+                    code_line = code_line[:start]
+                    in_block_comment = True
+                    break
+                code_line = code_line[:start] + code_line[end + 2 :]
 
             if stripped.startswith("//"):
                 continue
             if _SUPPRESS in line:
                 continue
 
-            code = line.split("//")[0]
+            code = code_line.split("//", 1)[0]
             if _BANNED_PATTERN.search(code):

Also applies to: 91-93

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@ci/lint_cpp/bare_noinline_checker.py` around lines 78 - 85, The block-comment
handling currently skips entire lines when toggling in_block_comment, which
misses banned tokens that appear before a "/*" or after a "*/"; update the logic
around in_block_comment (the code that checks for "/*" and "*/" and the variable
in_block_comment) to scan and operate on the parts of the line outside
block-comment spans instead of continuing immediately: when a "/*" is found,
check the substring before it for banned tokens, then set in_block_comment and
continue scanning after any matching "*/" (and when "*/" is found, check the
substring after it for banned tokens and clear in_block_comment); apply the same
substring-aware approach to the other occurrence of this logic later in the file
(the block handling around the second in_block_comment check) so tokens on the
same line as comment delimiters are still inspected.

Comment on lines +343 to +344
/// Marked `noinline` (via `FL_NOINLINE`) so the compiler doesn't fold the
/// cold body back into `showPixels`. The whole helper is reachable only
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix macro name typo in the comment (FL_NO_INLINE).

The comment says FL_NOINLINE, but the macro is FL_NO_INLINE. Please align the text to the actual symbol name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/fl/channels/channel.cpp.hpp` around lines 343 - 344, The comment
currently references the wrong macro name `FL_NOINLINE`; update the comment to
use the correct macro `FL_NO_INLINE` (the sentence describing why the helper is
marked noinline that references `showPixels` should read `FL_NO_INLINE`) so the
documentation matches the actual symbol used in the code.

Comment on lines +333 to +341
// Unconditional "do not inline this function" attribute, portable across
// GCC/Clang/MSVC. Prefer this over a bare `__attribute__((noinline))` in
// `src/` — a lint check in `ci/lint_cpp/bare_noinline_checker.py` enforces
// the use of the macro so the GCC-only syntax doesn't sneak in.
//
// Typical use: cold helpers extracted out of a hot path so the compiler
// can't fold them back via inline expansion. Pair with the lint guard.
//
// Usage: FL_NO_INLINE void coldHelper() { ... }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Replace non-ASCII punctuation in the new comment block.

The added comments include Unicode punctuation (for example em dashes). Please convert these to plain ASCII to satisfy the repo's source encoding rule.

As per coding guidelines, "ASCII-only constraint: new/modified C++ and Python source must be 7-bit ASCII."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/fl/stl/compiler_control.h` around lines 333 - 341, The new comment block
for the FL_NO_INLINE macro contains non-ASCII punctuation (e.g., em-dashes and
other Unicode punctuation); edit the comment around the FL_NO_INLINE usage
description in compiler_control.h to replace all non-ASCII punctuation with
plain ASCII equivalents (use hyphen/minus/double-hyphen for dashes, straight
quotes, plain ellipsis or three dots, etc.) so the file is 7-bit ASCII
compliant.

@zackees zackees merged commit e2772c9 into master Jun 5, 2026
11 of 13 checks passed
@fastled-project-sync fastled-project-sync Bot moved this from Triage to Done in FastLED Tracker Jun 5, 2026
zackees added a commit that referenced this pull request Jun 6, 2026
…NLINE cold helper (#2773 item 2.5 — easy share)

Same cold-helper pattern that worked for #2773 items 2.1 (PR #2830) and
2.2 (PR #2838).

Hot path of allocateTx (initial tryAllocateWords succeeds — the common
Blink boot case where there's plenty of TX memory) was bloated by the
single-buffer fallback retry + 4 FL_WARN diagnostic lines + 3 conditional
suggestion blocks, all of which reach for fl::sstream operator<< chains.

Extract them into handleAllocateTxFailure(), an FL_NO_INLINE member
helper. Hot path now ends at:

    if (!tryAllocateWords(words_needed, true)) {
        return handleAllocateTxFailure(channel_id, mem_blocks,
                                       words_needed, networkActive);
    }

Cold path is behaviorally unchanged.

This captures the easy share of #2773 item 2.5's ~3-4 KB target. The
full template-trait refactor (skip the runtime planner entirely when
addLeds<> is called exactly once with a known length) is still TODO
but is a multi-day effort with template-instantiation risk.
pull Bot pushed a commit to manmuqingshan/FastLED that referenced this pull request Jun 6, 2026
…lper (FastLED#2773 item 2.2)

Same pattern that worked for FastLED#2773 item 2.1 in PR FastLED#2830: the progressive
memory-reduction retry loop inside ChannelEngineRMTImpl::createChannel is
a cold path (only reached when the initial allocation fails — typically
never, in the static addLeds<>-at-boot scenario), but its body contains
FL_LOG_RMT/FL_WARN operator<< instantiations plus a per-iteration
Rmt5ChannelConfig constructor that the linker has to keep live inside the
hot function body.

Move the retry block into attemptAllocationRecovery(), an FL_NO_INLINE
member helper. The hot path (initial allocation succeeds) shrinks
proportionally; the cold path is unchanged behaviorally.

Also fix a pre-existing C++17 nested-namespace error in
src/fl/net/network_detector.h that broke local quick-mode test builds
under -Wc++17-extensions. (CI uses C++17+ so it didn't catch it.)

Target: ~1-2 KB Blink savings on ESP32-S3.
zackees added a commit that referenced this pull request Jun 6, 2026
… into FL_NO_INLINE helper (#2773 follow-up to #2832)

The two-branch `FL_ERROR(... << ...)` chain in `Channel::showPixels`
that fires when a bound driver has been runtime-disabled (typically
by `FastLED.setExclusiveDriver<OtherBus>()`) was still inlined into
the hot path. The branch is one-shot (`mDisabledDriverWarned`) and
unreachable in the steady state, so the ~10 `operator<<`
instantiations were pure cold-path bloat sitting in showPixels'
prologue/epilogue.

Move into an anonymous-namespace helper marked `FL_NO_INLINE` (per
the `bare_noinline_checker` introduced in #2830, not bare
`__attribute__((noinline))`), matching the shape the maintainer
adopted for `resolveDynamicDriver()` in PR #2830.

This is the leftover piece from PR #2832, which the maintainer
flagged in the closing comment as worth a follow-up:

> The two pieces this PR has that #2830 didn't (emitDisabledDriverError
> and the #if FASTLED_HAS_DBG gate on ChannelManager::addDriver's
> capStr builder) are small enough (~couple hundred bytes) that a
> rebase round trip isn't worth the churn vs just reopening them in
> a follow-up.

The `addDriver` capStr DBG gate piece of #2832 was independently
shipped by the maintainer as iter 6 (commit `ea96b373b5`,
`perf(channels): gate addDriver capStr builder behind FASTLED_HAS_DBG
(#2773 iter 6)`), so this rebase drops that half and keeps only the
unique `emitDisabledDriverError` extraction.

Refs #2773.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant