FastLED 3.9.15
Loading...
Searching...
No Matches
allocator.cpp.hpp
Go to the documentation of this file.
1#include "platforms/is_platform.h"
2#include "fl/stl/allocator.h"
3#include "fl/stl/int.h"
4#include "fl/stl/singleton.h"
5#include "fl/stl/cstddef.h"
6#include "fl/stl/cstdlib.h"
7#include "fl/stl/string.h"
8#include "fl/stl/cstring.h"
10
11#ifdef FL_IS_ESP32
12#include "esp_heap_caps.h"
13#include "esp_system.h"
14// IWYU pragma: begin_keep
15#include "platforms/esp/esp_version.h" // ok platform headers
16#include "fl/stl/noexcept.h"
17// IWYU pragma: end_keep // ok platform headers
18#endif
19
20namespace fl {
21
22// Explicit instantiations for commonly used allocator types
23// This ensures the constexpr static members are defined for these types
24template struct allocator_traits<allocator<int>>;
28
29// Define constexpr static members for allocator_traits (C++11 requires this for ODR-used variables)
30template <typename Allocator>
32
33template <typename Allocator>
35
36namespace {
37
38#ifdef FL_IS_ESP32
39// On esp32, attempt to always allocate in psram first.
40void *DefaultAlloc(fl::size size) {
41#ifdef FL_VECTOR_PSRAM_ALWAYS_SRAM
42 // FL_VECTOR_PSRAM_ALWAYS_SRAM: skip PSRAM, use default (SRAM) only.
43 void *out = heap_caps_malloc(size, MALLOC_CAP_DEFAULT);
44#else
45 void *out = heap_caps_malloc(size, MALLOC_CAP_SPIRAM);
46 if (out == nullptr) {
47 // Fallback to default allocator.
48 out = heap_caps_malloc(size, MALLOC_CAP_DEFAULT);
49 }
50#endif
51 return out;
52}
53void DefaultFree(void *ptr) { heap_caps_free(ptr); }
54#else
55void *DefaultAlloc(fl::size size) { return malloc(size); }
56void DefaultFree(void *ptr) { free(ptr); }
57#endif
58
59void *(*Alloc)(fl::size) = DefaultAlloc;
60void (*Dealloc)(void *) = DefaultFree;
61
62#if defined(FASTLED_TESTING)
63// Test hook interface pointer
64MallocFreeHook* gMallocFreeHook = nullptr;
65
66int& tls_reintrancy_count() {
68}
69
70struct MemoryGuard {
71 int& reintrancy_count;
72 MemoryGuard() FL_NOEXCEPT : reintrancy_count(tls_reintrancy_count()) {
73 reintrancy_count++;
74 }
75 ~MemoryGuard() FL_NOEXCEPT {
76 reintrancy_count--;
77 }
78 bool enabled() const {
79 return reintrancy_count <= 1;
80 }
81};
82
83// Force early initialization of thread-local storage to avoid
84// static initialization order issues on macOS. The ThreadLocal<int>
85// uses pthread_setspecific which calls operator new during first access.
86// If the first access happens during a memory allocation callback, we get
87// recursion. By initializing during static construction, we ensure the
88// ThreadLocal is ready before any test code runs.
89void init_tls_reentrancy() {
90 (void)tls_reintrancy_count();
91}
92
93FL_INIT(allocator_init_wrapper, init_tls_reentrancy)
94
95#endif
96
97} // namespace
98
99#if defined(FASTLED_TESTING)
100void SetMallocFreeHook(MallocFreeHook* hook) {
101 // Pre-initialize reentrancy counter to avoid recursive malloc during first hook call.
102 // On macOS, ThreadLocal uses pthread_setspecific which calls operator new,
103 // triggering recursive Malloc() that would corrupt the reentrancy counter.
104 (void)tls_reintrancy_count();
105
106 gMallocFreeHook = hook;
107}
108
109void ClearMallocFreeHook() {
110 gMallocFreeHook = nullptr;
111}
112#endif
113
114void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *)) {
115 Alloc = alloc;
116 Dealloc = free;
117}
118
119void *PSRamAllocate(fl::size size, bool zero) {
120
121 void *ptr = Alloc(size);
122 if (ptr && zero) {
123 fl::memset(ptr, 0, size);
124 }
125
126#if defined(FASTLED_TESTING)
127 if (gMallocFreeHook && ptr) {
128 MemoryGuard allows_hook;
129 if (allows_hook.enabled()) {
130 gMallocFreeHook->onMalloc(ptr, size);
131 }
132 }
133#endif
134
135 return ptr;
136}
137
138void PSRamDeallocate(void *ptr) {
139#if defined(FASTLED_TESTING)
140 if (gMallocFreeHook && ptr) {
141 // gMallocFreeHook->onFree(ptr);
142 MemoryGuard allows_hook;
143 if (allows_hook.enabled()) {
144 gMallocFreeHook->onFree(ptr);
145 }
146 }
147#endif
148
149 Dealloc(ptr);
150}
151
152void* Malloc(fl::size size) {
153 void* ptr = Alloc(size);
154
155#if defined(FASTLED_TESTING)
156 if (gMallocFreeHook && ptr) {
157 MemoryGuard allows_hook;
158 if (allows_hook.enabled()) {
159 gMallocFreeHook->onMalloc(ptr, size);
160 }
161 }
162#endif
163
164 return ptr;
165}
166
167void Free(void *ptr) {
168#if defined(FASTLED_TESTING)
169 if (gMallocFreeHook && ptr) {
170 MemoryGuard allows_hook;
171 if (allows_hook.enabled()) {
172 gMallocFreeHook->onFree(ptr);
173 }
174 }
175#endif
176
177 Dealloc(ptr);
178}
179
180#ifdef FL_IS_ESP32
181// ESP32-specific memory allocation for RMT buffer pooling
182// These functions provide direct access to specific memory regions for performance
183
184void* InternalAlloc(fl::size size) {
185 // MALLOC_CAP_INTERNAL: Internal DRAM (fast, not in PSRAM)
186 // MALLOC_CAP_8BIT: Ensure byte-accessible memory
187 void* ptr = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
188 if (ptr) {
189 fl::memset(ptr, 0, size); // Zero-initialize
190 }
191 return ptr;
192}
193
194void* InternalRealloc(void* ptr, fl::size size) {
195 // Realloc in internal DRAM - may relocate or expand in-place
196 void* new_ptr = heap_caps_realloc(ptr, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
197
198 // Zero-initialize any newly allocated bytes (if expanded)
199 // Note: heap_caps_realloc preserves existing data, we only zero new space
200 // This is best-effort - we don't track old size, so we can't selectively zero
201 // For safety, caller should manage initialization if needed
202
203 return new_ptr;
204}
205
206void InternalFree(void* ptr) {
207 heap_caps_free(ptr);
208}
209
210void* DMAAlloc(fl::size size) {
211 // MALLOC_CAP_DMA: DMA-capable memory (limited on ESP32)
212 // MALLOC_CAP_INTERNAL: Must be in internal SRAM (not PSRAM) for most DMA
213 //
214 // CRITICAL: Use 64-byte aligned allocation for cache coherency
215 // ESP32-S3/C3/C6/H2 have data cache with 64-byte cache lines.
216 // Without alignment, esp_cache_msync() may not flush/invalidate correctly,
217 // causing DMA to read stale cached data.
218 //
219 // Round size up to 64-byte multiple for proper cache line alignment
220 fl::size aligned_size = ((size + 63) / 64) * 64;
221
222#if ESP_IDF_VERSION_4_OR_HIGHER
223 // heap_caps_aligned_alloc is available in ESP-IDF v4.0+
224 void* ptr = heap_caps_aligned_alloc(64, aligned_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
225#else
226 // ESP-IDF v3.x fallback: use regular allocation (no alignment guarantee)
227 void* ptr = heap_caps_malloc(aligned_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
228#endif
229 if (ptr) {
230 fl::memset(ptr, 0, aligned_size); // Zero-initialize full aligned buffer
231 }
232 return ptr;
233}
234
235void DMAFree(void* ptr) {
236 heap_caps_free(ptr);
237}
238#endif
239
240namespace detail {
241
242// Process-wide SlabAllocator registry.
243// Ensures all DLLs share the same SlabAllocator for a given (block_size, slab_size).
244// Simple fixed-size array since the number of unique allocator types is small.
245namespace {
247 fl::size block_size;
248 fl::size slab_size;
250 };
251 static constexpr int SLAB_REGISTRY_MAX = 64;
253 static int slab_registry_count = 0;
254} // anonymous namespace
255
256void* slab_allocator_registry_get(fl::size block_size, fl::size slab_size) {
257 for (int i = 0; i < slab_registry_count; i++) {
258 if (slab_registry[i].block_size == block_size &&
259 slab_registry[i].slab_size == slab_size) {
260 return slab_registry[i].allocator;
261 }
262 }
263 return nullptr;
264}
265
266void slab_allocator_registry_set(fl::size block_size, fl::size slab_size, void* allocator) {
267 // Check if already registered (update)
268 for (int i = 0; i < slab_registry_count; i++) {
269 if (slab_registry[i].block_size == block_size &&
270 slab_registry[i].slab_size == slab_size) {
271 slab_registry[i].allocator = allocator;
272 return;
273 }
274 }
275 // Add new entry
276 if (slab_registry_count < SLAB_REGISTRY_MAX) {
277 slab_registry[slab_registry_count++] = {block_size, slab_size, allocator};
278 }
279}
280
281} // namespace detail
282
283} // namespace fl
static T & instance() FL_NOEXCEPT
Definition singleton.h:133
static SlabRegistryEntry slab_registry[SLAB_REGISTRY_MAX]
void * slab_allocator_registry_get(fl::size block_size, fl::size slab_size)
void slab_allocator_registry_set(fl::size block_size, fl::size slab_size, void *allocator)
Compile-time linker keep-alive hook for a single fl::Bus.
Definition bus_traits.h:48
void Free(void *ptr)
void * memset(void *s, int c, size_t n) FL_NOEXCEPT
void * malloc(size_t size)
Definition malloc.cpp.hpp:9
void SetPSRamAllocator(void *(*alloc)(fl::size), void(*free)(void *))
void free(void *ptr)
void PSRamDeallocate(void *ptr)
void * Malloc(fl::size size)
void * PSRamAllocate(fl::size size, bool zero)
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NOEXCEPT
static constexpr bool has_allocate_at_least_v
Definition allocator.h:61
static constexpr bool has_reallocate_v
Definition allocator.h:49