FastLED 3.10.8
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) FL_NO_EXCEPT { return malloc(size); }
56void DefaultFree(void *ptr) FL_NO_EXCEPT { 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. Storage lives inside a Singleton so
64// `--gc-sections` can drop it together with Singleton<T>::instance() when no
65// test installs a hook (FastLED#3484 / #3481).
66// Already inside this file's anonymous namespace.
67//
68// Safe to call from inside Malloc()/Free(): Singleton<T>::instance()
69// placement-news into a static char buffer and never touches the heap, so
70// routing the hook lookup through it cannot recurse back into the allocator.
71struct MallocFreeHookState {
72 MallocFreeHook* hook = nullptr;
73};
74inline MallocFreeHook*& malloc_free_hook() FL_NO_EXCEPT {
76}
77
78int& tls_reintrancy_count() FL_NO_EXCEPT {
80}
81
82struct MemoryGuard {
83 int& reintrancy_count;
84 MemoryGuard() FL_NO_EXCEPT : reintrancy_count(tls_reintrancy_count()) {
85 reintrancy_count++;
86 }
87 ~MemoryGuard() FL_NO_EXCEPT {
88 reintrancy_count--;
89 }
90 bool enabled() const FL_NO_EXCEPT {
91 return reintrancy_count <= 1;
92 }
93};
94
95// Force early initialization of thread-local storage to avoid
96// static initialization order issues on macOS. The ThreadLocal<int>
97// uses pthread_setspecific which calls operator new during first access.
98// If the first access happens during a memory allocation callback, we get
99// recursion. By initializing during static construction, we ensure the
100// ThreadLocal is ready before any test code runs.
101void init_tls_reentrancy() FL_NO_EXCEPT {
102 (void)tls_reintrancy_count();
103}
104
105FL_INIT(allocator_init_wrapper, init_tls_reentrancy)
106
107#endif
108
109} // namespace
110
111#if defined(FASTLED_TESTING)
112void SetMallocFreeHook(MallocFreeHook* hook) FL_NO_EXCEPT {
113 // Pre-initialize reentrancy counter to avoid recursive malloc during first hook call.
114 // On macOS, ThreadLocal uses pthread_setspecific which calls operator new,
115 // triggering recursive Malloc() that would corrupt the reentrancy counter.
116 (void)tls_reintrancy_count();
117
118 malloc_free_hook() = hook;
119}
120
121void ClearMallocFreeHook() FL_NO_EXCEPT {
122 malloc_free_hook() = nullptr;
123}
124#endif
125
126void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *)) FL_NO_EXCEPT {
127 Alloc = alloc;
128 Dealloc = free;
129}
130
131void *PSRamAllocate(fl::size size, bool zero) FL_NO_EXCEPT {
132
133 void *ptr = Alloc(size);
134 if (ptr && zero) {
135 fl::memset(ptr, 0, size);
136 }
137
138#if defined(FASTLED_TESTING)
139 if (malloc_free_hook() && ptr) {
140 MemoryGuard allows_hook;
141 if (allows_hook.enabled()) {
142 malloc_free_hook()->onMalloc(ptr, size);
143 }
144 }
145#endif
146
147 return ptr;
148}
149
151#if defined(FASTLED_TESTING)
152 if (malloc_free_hook() && ptr) {
153 // malloc_free_hook()->onFree(ptr);
154 MemoryGuard allows_hook;
155 if (allows_hook.enabled()) {
156 malloc_free_hook()->onFree(ptr);
157 }
158 }
159#endif
160
161 Dealloc(ptr);
162}
163
164void* Malloc(fl::size size) FL_NO_EXCEPT {
165 void* ptr = Alloc(size);
166
167#if defined(FASTLED_TESTING)
168 if (malloc_free_hook() && ptr) {
169 MemoryGuard allows_hook;
170 if (allows_hook.enabled()) {
171 malloc_free_hook()->onMalloc(ptr, size);
172 }
173 }
174#endif
175
176 return ptr;
177}
178
179void Free(void *ptr) FL_NO_EXCEPT {
180#if defined(FASTLED_TESTING)
181 if (malloc_free_hook() && ptr) {
182 MemoryGuard allows_hook;
183 if (allows_hook.enabled()) {
184 malloc_free_hook()->onFree(ptr);
185 }
186 }
187#endif
188
189 Dealloc(ptr);
190}
191
192#ifdef FL_IS_ESP32
193// ESP32-specific memory allocation for RMT buffer pooling
194// These functions provide direct access to specific memory regions for performance
195
196void* InternalAlloc(fl::size size) {
197 // MALLOC_CAP_INTERNAL: Internal DRAM (fast, not in PSRAM)
198 // MALLOC_CAP_8BIT: Ensure byte-accessible memory
199 void* ptr = heap_caps_malloc(size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
200 if (ptr) {
201 fl::memset(ptr, 0, size); // Zero-initialize
202 }
203 return ptr;
204}
205
206void* InternalRealloc(void* ptr, fl::size size) {
207 // Realloc in internal DRAM - may relocate or expand in-place
208 void* new_ptr = heap_caps_realloc(ptr, size, MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
209
210 // Zero-initialize any newly allocated bytes (if expanded)
211 // Note: heap_caps_realloc preserves existing data, we only zero new space
212 // This is best-effort - we don't track old size, so we can't selectively zero
213 // For safety, caller should manage initialization if needed
214
215 return new_ptr;
216}
217
218void InternalFree(void* ptr) {
219 heap_caps_free(ptr);
220}
221
222void* DMAAlloc(fl::size size) {
223 // MALLOC_CAP_DMA: DMA-capable memory (limited on ESP32)
224 // MALLOC_CAP_INTERNAL: Must be in internal SRAM (not PSRAM) for most DMA
225 //
226 // CRITICAL: Use 64-byte aligned allocation for cache coherency
227 // ESP32-S3/C3/C6/H2 have data cache with 64-byte cache lines.
228 // Without alignment, esp_cache_msync() may not flush/invalidate correctly,
229 // causing DMA to read stale cached data.
230 //
231 // Round size up to 64-byte multiple for proper cache line alignment
232 fl::size aligned_size = ((size + 63) / 64) * 64;
233
234#if ESP_IDF_VERSION_4_OR_HIGHER
235 // heap_caps_aligned_alloc is available in ESP-IDF v4.0+
236 void* ptr = heap_caps_aligned_alloc(64, aligned_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
237#else
238 // ESP-IDF v3.x fallback: use regular allocation (no alignment guarantee)
239 void* ptr = heap_caps_malloc(aligned_size, MALLOC_CAP_DMA | MALLOC_CAP_INTERNAL);
240#endif
241 if (ptr) {
242 fl::memset(ptr, 0, aligned_size); // Zero-initialize full aligned buffer
243 }
244 return ptr;
245}
246
247void DMAFree(void* ptr) {
248 heap_caps_free(ptr);
249}
250#endif
251
252namespace detail {
253
254// Process-wide SlabAllocator registry.
255// Ensures all DLLs share the same SlabAllocator for a given (block_size, slab_size).
256// State lives inside a Singleton so `--gc-sections` can drop the 64-entry table
257// (~1.5 KB `.bss`) when no slab allocator is ever created (FastLED#3481).
258namespace {
260 fl::size block_size;
261 fl::size slab_size;
263 };
265 static constexpr int MAX = 64;
267 int count = 0;
268 };
272} // anonymous namespace
273
274void* slab_allocator_registry_get(fl::size block_size, fl::size slab_size) FL_NO_EXCEPT {
275 auto& s = slab_registry_state();
276 for (int i = 0; i < s.count; i++) {
277 if (s.entries[i].block_size == block_size &&
278 s.entries[i].slab_size == slab_size) {
279 return s.entries[i].allocator;
280 }
281 }
282 return nullptr;
283}
284
285void slab_allocator_registry_set(fl::size block_size, fl::size slab_size, void* allocator) FL_NO_EXCEPT {
286 auto& s = slab_registry_state();
287 // Check if already registered (update)
288 for (int i = 0; i < s.count; i++) {
289 if (s.entries[i].block_size == block_size &&
290 s.entries[i].slab_size == slab_size) {
291 s.entries[i].allocator = allocator;
292 return;
293 }
294 }
295 // Add new entry
296 if (s.count < SlabRegistryState::MAX) {
297 s.entries[s.count++] = {block_size, slab_size, allocator};
298 }
299}
300
301} // namespace detail
302
303} // namespace fl
static T & instance() FL_NO_EXCEPT
Definition singleton.h:41
static T & instance() FL_NO_EXCEPT
Definition singleton.h:133
void * DefaultAlloc(fl::size size) FL_NO_EXCEPT
void DefaultFree(void *ptr) FL_NO_EXCEPT
SlabRegistryState & slab_registry_state() FL_NO_EXCEPT
void * slab_allocator_registry_get(fl::size block_size, fl::size slab_size) FL_NO_EXCEPT
void slab_allocator_registry_set(fl::size block_size, fl::size slab_size, void *allocator) FL_NO_EXCEPT
Compile-time linker keep-alive hook for a single fl::Bus.
Definition bus_info.h:57
u8 u8 fract8 blur_amount FL_NO_EXCEPT
Definition blur.h:186
FASTLED_FORCE_INLINE void free(void *ptr) FL_NO_EXCEPT
Definition malloc.h:23
void SetPSRamAllocator(void *(*alloc)(fl::size), void(*free)(void *)) FL_NO_EXCEPT
void Free(void *ptr) FL_NO_EXCEPT
FASTLED_FORCE_INLINE void * malloc(size_t size) FL_NO_EXCEPT
Definition malloc.h:21
FASTLED_FORCE_INLINE void * memset(void *s, int c, size_t n) FL_NO_EXCEPT
Definition cstring.h:148
void * PSRamAllocate(fl::size size, bool zero) FL_NO_EXCEPT
void * Malloc(fl::size size) FL_NO_EXCEPT
void PSRamDeallocate(void *ptr) FL_NO_EXCEPT
Base definition for an LED controller.
Definition crgb.hpp:179
#define FL_NO_EXCEPT
static constexpr bool has_allocate_at_least_v
Definition allocator.h:62
static constexpr bool has_reallocate_v
Definition allocator.h:50