From f1d3be4bdaaa1d6fcf7119d075b41cdc57ea31eb Mon Sep 17 00:00:00 2001 From: Kevin Ahrendt Date: Thu, 30 Apr 2026 12:03:40 -0400 Subject: [PATCH] [core] Simplify RAMAllocator and add internal fallback to external mode (#16171) --- esphome/core/helpers.h | 61 ++++++++++++++++++++++++++++-------------- 1 file changed, 41 insertions(+), 20 deletions(-) diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 355db6c7f4..07bcb7a74f 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -2045,7 +2045,8 @@ void delay_microseconds_safe(uint32_t us); * Returns `nullptr` in case no memory is available. * * By setting flags, it can be configured to: - * - perform external allocation falling back to main memory if SPI RAM is full or unavailable + * - perform external allocation falling back to internal memory if SPI RAM is full or unavailable (default) + * - perform internal allocation falling back to external memory (with PREFER_INTERNAL) * - perform external allocation only * - perform internal allocation only */ @@ -2054,16 +2055,26 @@ template class RAMAllocator { using value_type = T; enum Flags { - NONE = 0, // Perform external allocation and fall back to internal memory - ALLOC_EXTERNAL = 1 << 0, // Perform external allocation only. - ALLOC_INTERNAL = 1 << 1, // Perform internal allocation only. - ALLOW_FAILURE = 1 << 2, // Does nothing. Kept for compatibility. + NONE = 0, // Perform external allocation and fall back to internal memory + ALLOC_EXTERNAL = 1 << 0, // Perform external allocation only. + ALLOC_INTERNAL = 1 << 1, // Perform internal allocation only. + ALLOW_FAILURE = 1 << 2, // Does nothing. Kept for compatibility. + PREFER_INTERNAL = 1 << 3, // Perform internal allocation and fall back to external memory }; constexpr RAMAllocator() = default; - constexpr RAMAllocator(uint8_t flags) - : flags_((flags & (ALLOC_INTERNAL | ALLOC_EXTERNAL)) != 0 ? (flags & (ALLOC_INTERNAL | ALLOC_EXTERNAL)) - : (ALLOC_INTERNAL | ALLOC_EXTERNAL)) {} + constexpr RAMAllocator(uint8_t flags) { + if (flags & PREFER_INTERNAL) { + this->flags_ = ALLOC_INTERNAL | ALLOC_EXTERNAL | PREFER_INTERNAL; + return; + } + const uint8_t alloc_bits = flags & (ALLOC_INTERNAL | ALLOC_EXTERNAL); + if (alloc_bits != 0) { + this->flags_ = alloc_bits; + return; + } + this->flags_ = ALLOC_INTERNAL | ALLOC_EXTERNAL; + } template constexpr RAMAllocator(const RAMAllocator &other) : flags_{other.flags_} {} T *allocate(size_t n) { return this->allocate(n, sizeof(T)); } @@ -2072,12 +2083,8 @@ template class RAMAllocator { size_t size = n * manual_size; T *ptr = nullptr; #ifdef USE_ESP32 - if (this->flags_ & Flags::ALLOC_EXTERNAL) { - ptr = static_cast(heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); - } - if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) { - ptr = static_cast(heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); - } + const auto caps = this->get_caps_(); + ptr = static_cast(heap_caps_malloc_prefer(size, 2, caps[0], caps[1])); #else // Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported ptr = static_cast(malloc(size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) @@ -2091,12 +2098,8 @@ template class RAMAllocator { size_t size = n * manual_size; T *ptr = nullptr; #ifdef USE_ESP32 - if (this->flags_ & Flags::ALLOC_EXTERNAL) { - ptr = static_cast(heap_caps_realloc(p, size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT)); - } - if (ptr == nullptr && this->flags_ & Flags::ALLOC_INTERNAL) { - ptr = static_cast(heap_caps_realloc(p, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT)); - } + const auto caps = this->get_caps_(); + ptr = static_cast(heap_caps_realloc_prefer(p, size, 2, caps[0], caps[1])); #else // Ignore ALLOC_EXTERNAL/ALLOC_INTERNAL flags if external allocation is not supported ptr = static_cast(realloc(p, size)); // NOLINT(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) @@ -2147,6 +2150,24 @@ template class RAMAllocator { } private: +#ifdef USE_ESP32 + /// Returns {primary_caps, fallback_caps} for heap_caps_*_prefer based on the configured flags. + /// PREFER_INTERNAL implies both regions are enabled (enforced by the constructor), so when it is set + /// the primary is internal and the fallback is external. Otherwise the primary is whichever region + /// is enabled (external preferred when both are enabled), and the fallback is the other region (or + /// the same region when only one is enabled, making the second attempt a no-op). + std::array get_caps_() const { + constexpr uint32_t external_caps = MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT; + constexpr uint32_t internal_caps = MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT; + if (this->flags_ & PREFER_INTERNAL) { + return {internal_caps, external_caps}; + } + const uint32_t primary = (this->flags_ & ALLOC_EXTERNAL) ? external_caps : internal_caps; + const uint32_t fallback = (this->flags_ & ALLOC_INTERNAL) ? internal_caps : external_caps; + return {primary, fallback}; + } +#endif + uint8_t flags_{ALLOC_INTERNAL | ALLOC_EXTERNAL}; };