diff --git a/esphome/analyze_memory/cli.py b/esphome/analyze_memory/cli.py index 106bb30b11..8438457db6 100644 --- a/esphome/analyze_memory/cli.py +++ b/esphome/analyze_memory/cli.py @@ -672,28 +672,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer): return "\n".join(lines) - def to_json(self) -> str: - """Export analysis results as JSON.""" - data = { - "components": { - name: { - "text": mem.text_size, - "rodata": mem.rodata_size, - "data": mem.data_size, - "bss": mem.bss_size, - "flash_total": mem.flash_total, - "ram_total": mem.ram_total, - "symbol_count": mem.symbol_count, - } - for name, mem in self.components.items() - }, - "totals": { - "flash": sum(c.flash_total for c in self.components.values()), - "ram": sum(c.ram_total for c in self.components.values()), - }, - } - return json.dumps(data, indent=2) - def dump_uncategorized_symbols(self, output_file: str | None = None) -> None: """Dump uncategorized symbols for analysis.""" # Sort by size descending @@ -759,7 +737,6 @@ def main(): build_dir = sys.argv[1] # Load build directory - import json from pathlib import Path from esphome.platformio_api import IDEData diff --git a/esphome/core/application.h b/esphome/core/application.h index 5659680fb1..06ff30e81f 100644 --- a/esphome/core/application.h +++ b/esphome/core/application.h @@ -574,7 +574,7 @@ class Application { /// Detects the calling context and uses the appropriate FreeRTOS API. static void IRAM_ATTR wake_loop_any_context() { esphome_lwip_wake_main_loop_any_context(); } #endif -#endif // USE_LWIP_FAST_SELECT +#endif #if defined(USE_ESP8266) && defined(USE_SOCKET_IMPL_LWIP_TCP) /// Wake the main event loop from any context (ISR, thread, or main loop). diff --git a/esphome/core/automation.h b/esphome/core/automation.h index 05c7f19588..fc2cad99be 100644 --- a/esphome/core/automation.h +++ b/esphome/core/automation.h @@ -419,48 +419,44 @@ template class Action { template class ActionList { public: void add_action(Action *action) { - // Walk to end of chain - action lists are short and only built during setup() - Action **tail = &this->actions_; - while (*tail != nullptr) - tail = &(*tail)->next_; - *tail = action; + if (this->actions_end_ == nullptr) { + this->actions_begin_ = action; + } else { + this->actions_end_->next_ = action; + } + this->actions_end_ = action; } void add_actions(const std::initializer_list *> &actions) { - // Find tail once, then append all actions in a single pass - Action **tail = &this->actions_; - while (*tail != nullptr) - tail = &(*tail)->next_; for (auto *action : actions) { - *tail = action; - tail = &action->next_; + this->add_action(action); } } // Force-inline: part of the Trigger→Automation→ActionList forwarding // chain collapsed to reduce automation call stack depth. inline void play(const Ts &...x) ESPHOME_ALWAYS_INLINE { - if (this->actions_ != nullptr) - this->actions_->play_complex(x...); + if (this->actions_begin_ != nullptr) + this->actions_begin_->play_complex(x...); } void play_tuple(const std::tuple &tuple) { this->play_tuple_(tuple, std::make_index_sequence{}); } void stop() { - if (this->actions_ != nullptr) - this->actions_->stop_complex(); + if (this->actions_begin_ != nullptr) + this->actions_begin_->stop_complex(); } - bool empty() const { return this->actions_ == nullptr; } + bool empty() const { return this->actions_begin_ == nullptr; } /// Check if any action in this action list is currently running. bool is_running() { - if (this->actions_ == nullptr) + if (this->actions_begin_ == nullptr) return false; - return this->actions_->is_running(); + return this->actions_begin_->is_running(); } /// Return the number of actions in this action list that are currently running. int num_running() { - if (this->actions_ == nullptr) + if (this->actions_begin_ == nullptr) return 0; - return this->actions_->num_running_total(); + return this->actions_begin_->num_running_total(); } protected: @@ -468,7 +464,8 @@ template class ActionList { this->play(std::get(tuple)...); } - Action *actions_{nullptr}; + Action *actions_begin_{nullptr}; + Action *actions_end_{nullptr}; }; template class Automation { diff --git a/esphome/core/defines.h b/esphome/core/defines.h index d95fb6f090..7259167a52 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -295,7 +295,6 @@ #define USE_ETHERNET_W5100 #define USE_ETHERNET_W5500 #define USE_ETHERNET_DM9051 -#define USE_ETHERNET_ENC28J60 #define CONFIG_ETH_SPI_ETHERNET_W5500 1 #define CONFIG_ETH_SPI_ETHERNET_DM9051 1 #define CONFIG_ETH_USE_ESP32_EMAC 1 diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index ba5fcffa9a..1732fc72e8 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -22,19 +22,6 @@ namespace esphome { static const char *const TAG = "helpers"; -__attribute__((noinline, cold)) void *callback_manager_grow(void *data, uint16_t size, uint16_t &capacity, - size_t elem_size) { - ESPHOME_DEBUG_ASSERT(size < UINT16_MAX); - uint16_t new_cap = size + 1; - auto *new_data = ::operator new(new_cap *elem_size); - if (data) { - __builtin_memcpy(new_data, data, size * elem_size); - ::operator delete(data); - } - capacity = new_cap; - return new_data; -} - static const uint16_t CRC16_A001_LE_LUT_L[] = {0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601, 0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440}; static const uint16_t CRC16_A001_LE_LUT_H[] = {0x0000, 0xcc01, 0xd801, 0x1400, 0xf001, 0x3c00, 0x2800, 0xe401, @@ -387,10 +374,7 @@ std::string format_hex(const uint8_t *data, size_t length) { format_hex_to(&ret[0], length * 2 + 1, data, length); return ret; } -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" std::string format_hex(const std::vector &data) { return format_hex(data.data(), data.size()); } -#pragma GCC diagnostic pop char *format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator) { return format_hex_internal(buffer, buffer_size, data, length, separator, 'A'); @@ -566,8 +550,10 @@ int8_t step_to_accuracy_decimals(float step) { return str.length() - dot_pos - 1; } -// Store BASE64 characters as array - automatically placed in flash/ROM on embedded platforms -static const char BASE64_CHARS[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +// Use C-style string constant to store in ROM instead of RAM (saves 24 bytes) +static constexpr const char *BASE64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; // Helper function to find the index of a base64/base64url character in the lookup table. // Returns the character's position (0-63) if found, or 0 if not found. diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index b542f9a67a..66ba166445 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -270,9 +270,6 @@ template class StaticVector { size_t size() const { return count_; } bool empty() const { return count_ == 0; } - // Direct access to size counter for efficient in-place construction - size_t &count() { return count_; } - // Direct access to underlying data T *data() { return data_.data(); } const T *data() const { return data_.data(); } @@ -1446,17 +1443,13 @@ std::string format_hex(const std::vector &data); /// Causes heap fragmentation on long-running devices. template::value, int> = 0> std::string format_hex(T val) { val = convert_big_endian(val); -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wdeprecated-declarations" return format_hex(reinterpret_cast(&val), sizeof(T)); -#pragma GCC diagnostic pop } /// Format the std::array \p data in lowercased hex. /// @warning Allocates heap memory. Use format_hex_to() with a stack buffer instead. /// Causes heap fragmentation on long-running devices. template std::string format_hex(const std::array &data) { return format_hex(data.data(), data.size()); -#pragma GCC diagnostic pop } /** Format a byte array in pretty-printed, human-readable hex format. @@ -1808,84 +1801,33 @@ template struct Callback { } }; -/// Grow a CallbackManager's backing array to exactly size+1. Defined in helpers.cpp. -void *callback_manager_grow(void *data, uint16_t size, uint16_t &capacity, size_t elem_size); - template class CallbackManager; /** Helper class to allow having multiple subscribers to a callback. - * - * Uses a trivial-copyable-specialized container instead of std::vector to avoid - * template bloat (_M_realloc_insert, exception-safe copies). Since Callback is - * trivially copyable (just {fn_ptr, ctx_ptr}), reallocation is a plain memcpy. - * Uses uint16_t for size/capacity (8 bytes on 32-bit vs 12 for std::vector). - * Grows to exact size on each add — callbacks are registered during setup() - * and most instances have only 1-2 callbacks, so slack capacity is wasteful. * * @tparam Ts The arguments for the callbacks, wrapped in void(). */ template class CallbackManager { - using CbType = Callback; - static_assert(std::is_trivially_copyable_v, "Callback must be trivially copyable"); - public: - CallbackManager() = default; - ~CallbackManager() { ::operator delete(this->data_); } - - // Non-copyable (would alias data_), movable (for std::map support) - CallbackManager(const CallbackManager &) = delete; - CallbackManager &operator=(const CallbackManager &) = delete; - CallbackManager(CallbackManager &&other) noexcept - : data_(other.data_), size_(other.size_), capacity_(other.capacity_) { - other.data_ = nullptr; - other.size_ = 0; - other.capacity_ = 0; - } - CallbackManager &operator=(CallbackManager &&other) noexcept { - if (this != &other) { - ::operator delete(this->data_); - this->data_ = other.data_; - this->size_ = other.size_; - this->capacity_ = other.capacity_; - other.data_ = nullptr; - other.size_ = 0; - other.capacity_ = 0; - } - return *this; - } - /// Add any callable. Small trivially-copyable callables (like [this] lambdas) /// are stored inline without heap allocation or std::function. - template void add(F &&callback) { this->add_(CbType::create(std::forward(callback))); } + template void add(F &&callback) { this->add_(Callback::create(std::forward(callback))); } - /// Call all callbacks in this manager. - inline void ESPHOME_ALWAYS_INLINE call(Ts... args) { - if (this->size_ == 0) { - return; - } - for (auto *it = this->data_, *end = it + this->size_; it != end; ++it) { - it->call(args...); - } + /// Call all callbacks in this manager. No null check on invoke. + void call(Ts... args) { + for (auto &cb : this->callbacks_) + cb.call(args...); } - uint16_t size() const { return this->size_; } + size_t size() const { return this->callbacks_.size(); } /// Call all callbacks in this manager. - void operator()(Ts... args) { this->call(args...); } + void operator()(Ts... args) { call(args...); } protected: template friend class LazyCallbackManager; /// Non-template core to avoid code duplication per lambda type. - /// Inline fast path; cold growth path is in helpers.cpp via callback_manager_grow(). - void add_(CbType cb) { - if (this->size_ == this->capacity_) { - this->data_ = - static_cast(callback_manager_grow(this->data_, this->size_, this->capacity_, sizeof(CbType))); - } - this->data_[this->size_++] = cb; - } - CbType *data_{nullptr}; - uint16_t size_{0}; - uint16_t capacity_{0}; + void add_(Callback cb) { this->callbacks_.push_back(cb); } + std::vector> callbacks_; }; template class LazyCallbackManager; @@ -1897,7 +1839,7 @@ template class LazyCallbackManager; * from API and web_server components). * * Memory overhead comparison (32-bit systems): - * - CallbackManager: 8 bytes (pointer + uint16 size + uint16 capacity) + * - CallbackManager: 12 bytes (empty std::vector) * - LazyCallbackManager: 4 bytes (nullptr pointer) * * Uses plain pointer instead of unique_ptr to avoid template instantiation overhead.