LwIPLock was introduced for RP2040 WiFi in #14679 to prevent race
conditions between lwip callbacks and the main loop. However, on
platforms without lwIP core locking (ESP8266, LibreTiny, Zephyr,
RP2040 without WiFi), the constructor/destructor were empty stubs
in .cpp files that the compiler could not see through, generating
unnecessary function calls at every call site.
Move the no-op implementation inline into helpers.h so the compiler
can eliminate all LwIPLock overhead on these platforms. ESP32 and
RP2040+WiFi retain their out-of-line implementations with real
locking.
Move get_name() and get_object_id_hash() definitions from
entity_base.cpp to entity_base.h so the compiler can inline
these trivial member accessors, eliminating call overhead at
every call site.
Force-inline calc_uint64_force, calc_sint32_force, calc_length_force,
and calc_message_force so the varint fast path (value < 128) is
expanded at each call site instead of going through a function call.
These are the size-calculation methods used by BLE proxy messages
(BluetoothLERawAdvertisement, BluetoothGATTService/Characteristic/
Descriptor) which are called 12x per advertisement batch in the
hot path.
Benchmarked on ESP32 with 12-advertisement batches (10k iterations):
- calculate_size: 10381 -> 9718 ns/op (6.4% faster)
- Full calc+encode: 49886 -> 47599 ns/op (4.6% faster)
Flash cost: +40 bytes (ESP32 IDF), +64 bytes (ESP8266).
Add a "Top Called Functions" section to the analyze-memory report
that shows the most frequently called functions by call site count.
This helps identify inlining candidates by showing which functions
are called most often alongside their code size.
The analysis parses objdump disassembly output to count direct and
indirect call instructions across architectures (Xtensa call0/callx0,
ARM bl/blx).
Also fixes _batch_demangle_symbols to merge into the existing cache
instead of replacing it.
Replace brace-initialization `{}` with explicit `nullopt` for
optional<size_t> returns and assignments. Older GCC on ESP8266
falsely warns about uninitialized values with `return {}`.
message_type is uint8_t (max 255), which fits in at most 2 varint
bytes. Simplify the ternary to reflect this constraint. Also revert
unnecessary comment split.
- Replace ProtoSize::varint() call for message_type with inline
ternary using named constants, avoiding the noinline varint_slow
call overhead in the write_protobuf_messages loop (-32 bytes on
ESP8266 Xtensa).
- Add named varint threshold constants (VARINT_MAX_1_BYTE, etc.)
to ProtoSize class, used by both varint() and varint_wide().
- Devirtualize write_protobuf_packet by moving it to the base class
as a non-virtual inline method. The plaintext and noise
implementations only differed in footer handling, which is now
unified via a conditional resize based on frame_footer_size_.
This eliminates one vtable slot and allows the compiler to fully
inline the thin wrapper into callers (-192 bytes total flash on
ESP8266).
- Include proto.h from api_frame_helper.h (no circular dependency)
to support the inline definition, replacing the forward declaration
of ProtoWriteBuffer.
GCC's std::function::operator=(function&&) implements move-assignment as
a swap dance: construct temporary, swap with *this, destroy temporary.
This generates two std::swap<_Any_data> calls plus a destructor even
when the target is known to be empty.
Since scheduler items returned from the pool or freshly allocated always
have an empty callback, we can use explicit destroy + placement
move-construct to bypass the swap overhead.
Measured savings in set_timer_common_:
- ESP8266 (Xtensa LX106): 473 → 421 bytes (-52 B, -11%)
- ESP32-S3 (Xtensa LX7): 412 → 376 bytes (-36 B, -9%)
When USE_ESP8266 or USE_RP2040 is defined, provide a fully inline
no-op Mutex class in the header. This allows the compiler to
eliminate all lock/unlock call overhead instead of generating
calls to empty function stubs.
Replace floating-point random offset calculation with integer
multiply-and-shift, eliminating soft-float calls on ESP8266 and
FPU instructions on ESP32/RP2040.
Add generate-rp2040-boards.py script that clones the arduino-pico
repository at the recommended framework version and regenerates
boards.py, matching the existing ESP32 board generation CI check
pattern.
This ensures boards.py stays in sync when the arduino-pico
framework version is updated.
Follow-up to #14528.
TextSaver::save() returned false for both unchanged values and
values that are too long, causing a misleading warning on every
duplicate save. Return true early when the value hasn't changed.
Add integration test for template text save/restore persistence.
Increase LOG_NAGLE_COUNT from 2 to 3 on ESP32 and RP2040, which have
larger TCP send buffers (64KB and 11.7KB respectively). This coalesces
4 log messages per Nagle cycle instead of 3, reducing the number of
individual write() calls and significantly reducing EWOULDBLOCK hits
on the log subscriber connection.
Tested on ESP32 and RP2040: buffered writes dropped from ~15% to near
zero with this change. ESP8266 and LibreTiny remain at LOG_NAGLE_COUNT=2
due to tighter buffer constraints.
On embedded targets (ESP32, RP2040, ESP8266), errno expands to
(*__errno()) - a function call returning a pointer to thread-local
storage. The compiler cannot optimize away repeated accesses since
errno is treated as volatile. Cache it once into a const int local
to avoid redundant calls.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>