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.