ESP8266's older Arduino GCC doesn't optimize small memcpy into single
load/store instructions, making the memcpy path 16 bytes larger than
byte-at-a-time on that platform.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
libsodium's autoconf-generated config normally sets NATIVE_LITTLE_ENDIAN,
but PlatformIO skips autoconf. Without this define, libsodium falls back
to byte-at-a-time load/store operations in poly1305 and chacha20 instead
of optimized 32-bit memcpy. This saves ~340 bytes of flash and improves
encryption/decryption throughput on every API noise packet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
libsodium's autoconf-generated config normally sets NATIVE_LITTLE_ENDIAN,
but PlatformIO skips autoconf. Without this define, libsodium falls back
to byte-at-a-time load/store operations in poly1305 and chacha20 instead
of optimized 32-bit memcpy. This saves ~340 bytes of flash and improves
encryption/decryption throughput on every API noise packet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
libsodium's autoconf-generated config normally sets NATIVE_LITTLE_ENDIAN,
but PlatformIO skips autoconf. Without this define, libsodium falls back
to byte-at-a-time load/store operations in poly1305 and chacha20 instead
of optimized 32-bit memcpy. This saves ~340 bytes of flash and improves
encryption/decryption throughput on every API noise packet.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move the common-case checks (flags_.remove and can_write_without_blocking)
into an inline method in the header, leaving only the slow path (delay,
loop, retry) in the .cpp file. This avoids a function call on every
send_buffer() invocation when the TX buffer is already clear.
The noise frame helper was calling state_action_() (447 bytes) on every
read_packet() and write_protobuf_messages() call. In the DATA state
(steady-state after handshake), this function falls through all handshake
checks and returns OK — pure overhead on every encrypted packet.
Add check_data_state_() inline helper that replaces the call with a
single byte-load + branch instruction in the hot path. The handshake
state machine continues to be driven by loop() as before.
Also applies consistent state checking to the plaintext frame helper.
- LockFreeQueue: Add fast-path check to get_and_reset_dropped_count().
On Xtensa, uint16_t atomic exchange compiles to ~25 instructions with
a CAS retry loop and memory barriers. A relaxed load (single instruction)
short-circuits the common case where dropped_count is zero.
- BLEEvent: Remove redundant release() calls in load_*_event() methods.
EventPool::release() already calls event->release() before returning
events to the free list, so every event from allocate() is already clean.
- BLEEvent::release(): Only null heap_data on the delete path. Skip
unconditional zeroing of is_inline and heap_data when data was inline
(no heap allocation to clean up).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
GCC on Xtensa (ESP32) generates an indirect function call for
std::atomic<bool>::load() instead of inlining it. This adds unnecessary
call overhead on the scheduler hot path where the remove flag is checked
multiple times per loop iteration.
std::atomic<uint8_t>::load() inlines correctly on all platforms,
producing a simple load instruction with memory barrier. This eliminates
5 indirect calls and saves 30 bytes of flash on the scheduler hot path.
Apply the same linker wrap technique used on ESP32 (PR #14362) to RP2040.
ESPHome logging uses snprintf/vsnprintf, not libc printf, so the FILE*-based
printf path (_vfprintf_r) is dead code at runtime.
The stubs redirect printf/vprintf/fprintf through vsnprintf + fwrite,
allowing the linker to GC _vfprintf_r (~8.9 KB).
Apply the same linker wrap technique used on ESP32 (PR #14362) to ESP8266.
ESPHome logging uses ets_printf, not libc printf, so the FILE*-based printf
path is dead code. The stubs redirect through vsnprintf + fwrite, allowing
the linker to GC _vfprintf_r.
Savings are smaller than ESP32 (~900 bytes vs ~11 KB) because ESP8266's
newlib printf is more modular, but ESP8266 has much less flash headroom
so every byte counts.
The scheduler was already managing SchedulerItem lifecycle explicitly
through its object pool (recycle_item_main_loop_ / get_item_from_pool_locked_).
The unique_ptr wrapper added overhead (11 destructor call sites on the hot path)
without providing safety — if a lifecycle path was missed, the unique_ptr would
silently delete the item and cause needless heap allocations instead of pool reuse.
Replace unique_ptr<SchedulerItem, SchedulerItemDeleter> with raw SchedulerItem*
throughout. Every item is now explicitly recycled to the pool or deleted via
delete_item_(). This eliminates all 11 unique_ptr destructor calls from the hot
path and saves ~256 bytes of firmware.
Add debug leak detection under ESPHOME_DEBUG_SCHEDULER: a live-item counter
verified at the end of every call() cycle asserts that all allocated items are
accounted for in items_, to_add_, defer_queue_, or the pool. This turns silent
heap churn from missed lifecycle management into an immediate assert failure
caught by integration tests.
Also moves the retry-cancelled check before item allocation in set_timer_common_
to avoid needless alloc+delete on the cold retry path, and fixes a thread-safety
issue where recycle_item_main_loop_ (main-loop-only) was called from
set_timer_common_ which can run on non-main-loop threads.
Enable debug_scheduler: true in all 18 scheduler integration test fixtures.
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: pre-commit-ci-lite[bot] <117423508+pre-commit-ci-lite[bot]@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>