This commit is contained in:
J. Nick Koston
2026-03-29 15:40:30 -10:00
parent 7bc8edcf3e
commit 067cdb2ca2
6 changed files with 33 additions and 132 deletions
-23
View File
@@ -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
+1 -1
View File
@@ -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).
+18 -21
View File
@@ -419,48 +419,44 @@ template<typename... Ts> class Action {
template<typename... Ts> class ActionList {
public:
void add_action(Action<Ts...> *action) {
// Walk to end of chain - action lists are short and only built during setup()
Action<Ts...> **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<Action<Ts...> *> &actions) {
// Find tail once, then append all actions in a single pass
Action<Ts...> **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<Ts...> &tuple) {
this->play_tuple_(tuple, std::make_index_sequence<sizeof...(Ts)>{});
}
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<typename... Ts> class ActionList {
this->play(std::get<S>(tuple)...);
}
Action<Ts...> *actions_{nullptr};
Action<Ts...> *actions_begin_{nullptr};
Action<Ts...> *actions_end_{nullptr};
};
template<typename... Ts> class Automation {
-1
View File
@@ -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
+4 -18
View File
@@ -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<uint8_t> &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.
+10 -68
View File
@@ -270,9 +270,6 @@ template<typename T, size_t N> 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<uint8_t> &data);
/// Causes heap fragmentation on long-running devices.
template<typename T, enable_if_t<std::is_unsigned<T>::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<uint8_t *>(&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::size_t N> std::string format_hex(const std::array<uint8_t, N> &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<typename... Ts> struct Callback<void(Ts...)> {
}
};
/// 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<typename... X> 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<typename... Ts> class CallbackManager<void(Ts...)> {
using CbType = Callback<void(Ts...)>;
static_assert(std::is_trivially_copyable_v<CbType>, "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<typename F> void add(F &&callback) { this->add_(CbType::create(std::forward<F>(callback))); }
template<typename F> void add(F &&callback) { this->add_(Callback<void(Ts...)>::create(std::forward<F>(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<typename...> 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<CbType *>(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<void(Ts...)> cb) { this->callbacks_.push_back(cb); }
std::vector<Callback<void(Ts...)>> callbacks_;
};
template<typename... X> class LazyCallbackManager;
@@ -1897,7 +1839,7 @@ template<typename... X> 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.