FastLED 3.10.6
Loading...
Searching...
No Matches
allocator.h
Go to the documentation of this file.
1#pragma once
2
3#include "fl/stl/cstddef.h"
4#include "fl/stl/cstring.h"
7#include "fl/stl/bit_cast.h"
8#include "fl/stl/stdint.h"
9#include "fl/stl/bitset.h"
11#include "fl/stl/malloc.h"
12#include "fl/stl/align.h"
13#include "fl/stl/noexcept.h"
15
16#ifndef FASTLED_DEFAULT_SLAB_SIZE
17#define FASTLED_DEFAULT_SLAB_SIZE 8
18#endif
19
20namespace fl {
21
22// Forward declaration for allocate_result
23template <typename Pointer, typename SizeType>
25 Pointer ptr;
26 SizeType count; // Actual allocated count (may be > requested)
27};
28
29// Allocator traits for compile-time capability detection
30// Allows containers to use optimized code paths if allocators support them
31template <typename Allocator>
33 using allocator_type = Allocator;
34 using value_type = typename Allocator::value_type;
35 using pointer = typename Allocator::pointer;
36 using size_type = typename Allocator::size_type;
37
38 // Detect if allocator has reallocate() method
39 // Allows in-place memory resizing for POD types without copy
40 template <typename A = Allocator, typename = void>
42
43 template <typename A>
44 struct has_reallocate<A, decltype((void)fl::declval<A>().reallocate(
45 fl::declval<typename A::pointer>(),
46 fl::declval<typename A::size_type>(),
47 fl::declval<typename A::size_type>()
48 ))> : fl::true_type {};
49
51
52 // Detect if allocator has allocate_at_least() method
53 // Allows allocator to return more memory than requested to reduce reallocations
54 template <typename A = Allocator, typename = void>
56
57 template <typename A>
58 struct has_allocate_at_least<A, decltype((void)fl::declval<A>().allocate_at_least(
59 fl::declval<typename A::size_type>()
60 ))> : fl::true_type {};
61
63};
64
65// Test hooks for malloc/free operations
66#if defined(FASTLED_TESTING)
67// Interface class for malloc/free test hooks
68class MallocFreeHook {
69public:
70 virtual ~MallocFreeHook() FL_NO_EXCEPT = default;
71 virtual void onMalloc(void* ptr, fl::size size) FL_NO_EXCEPT = 0;
72 virtual void onFree(void* ptr) FL_NO_EXCEPT = 0;
73};
74
75// Set test hooks for malloc and free operations
76void SetMallocFreeHook(MallocFreeHook* hook) FL_NO_EXCEPT;
77
78// Clear test hooks (set to nullptr)
79void ClearMallocFreeHook() FL_NO_EXCEPT;
80#endif
81
82void SetPSRamAllocator(void *(*alloc)(fl::size), void (*free)(void *)) FL_NO_EXCEPT;
83void *PSRamAllocate(fl::size size, bool zero = true) FL_NO_EXCEPT;
84void PSRamDeallocate(void *ptr) FL_NO_EXCEPT;
85
86// Deleter for memory allocated via fl::PSRamAllocator (uses fl::PSRamDeallocate)
87template <typename T> struct PSRamDeleter {
89 void operator()(T *ptr) FL_NO_EXCEPT {
90 if (ptr) {
91 PSRamDeallocate(ptr);
92 }
93 }
94};
95
96void* Malloc(fl::size size) FL_NO_EXCEPT;
97void Free(void *ptr) FL_NO_EXCEPT;
98
99// SlabAllocator registry for cross-DLL shared slab allocators.
100// On Windows DLLs, inline functions with static locals create per-DLL copies.
101// This registry ensures all DLLs in a process share the same SlabAllocator
102// for a given (block_size, slab_size) pair, preventing cross-DLL slab
103// deallocation errors (freeing a pointer inside another DLL's slab).
104namespace detail {
105 void* slab_allocator_registry_get(fl::size block_size, fl::size slab_size) FL_NO_EXCEPT;
106 void slab_allocator_registry_set(fl::size block_size, fl::size slab_size, void* allocator) FL_NO_EXCEPT;
107} // namespace detail
108
109#ifdef FL_IS_ESP32
110// ESP32-specific memory allocation functions for RMT buffer pooling
111void* InternalAlloc(fl::size size); // MALLOC_CAP_INTERNAL - fast DRAM
112void* InternalRealloc(void* ptr, fl::size size); // Realloc for DRAM
113void InternalFree(void* ptr);
114void* DMAAlloc(fl::size size); // MALLOC_CAP_DMA - DMA-capable memory
115void DMAFree(void* ptr);
116#endif
117
118template <typename T> class PSRamAllocator {
119 public:
120 static T *Alloc(fl::size n) FL_NO_EXCEPT {
121 void *ptr = PSRamAllocate(sizeof(T) * n, true);
122 return fl::bit_cast_ptr<T>(ptr);
123 }
124
125 static void Free(T *p) FL_NO_EXCEPT {
126 if (p == nullptr) {
127 return;
128 }
130 }
131};
132
133// std compatible allocator.
134template <typename T> class allocator {
135 public:
136 // Type definitions required by STL
137 using value_type = T;
138 using pointer = T*;
139 using const_pointer = const T*;
140 using reference = T&;
141 using const_reference = const T&;
142 using size_type = fl::size;
144
145 // Rebind allocator to type U
146 template <typename U>
147 struct rebind {
149 };
150
151 // Default constructor
153
154 // Copy constructor
155 template <typename U>
157
158 // Destructor
160
161 // Optional: allocate_at_least() for optimized resizing
162 // Allows allocator to return more memory than requested to reduce reallocations
163 // Returns allocation_result with actual allocated count (may be > requested)
165 if (n == 0) {
166 return {nullptr, 0};
167 }
168 // Default implementation: just allocate exactly what's requested
169 // Specialized allocators may override to return more for efficiency
170 return {allocate(n), n};
171 }
172
173 // Optional: reallocate() for in-place resizing
174 // Automatically uses fl::realloc() for trivially copyable types
175 // Returns nullptr on failure or if type is not trivially copyable
176 pointer reallocate(pointer ptr, fl::size old_count, fl::size new_count) FL_NO_EXCEPT {
177 return reallocate_impl(ptr, old_count, new_count,
179 }
180
181private:
182 // SFINAE: Use fl::realloc() for trivially copyable types
183 pointer reallocate_impl(pointer ptr, fl::size old_count, fl::size new_count, fl::true_type) FL_NO_EXCEPT {
184 if (new_count == 0) {
185 if (ptr) {
186 deallocate(ptr, old_count);
187 }
188 return nullptr;
189 }
190
191 // Safe to use realloc() - type is trivially copyable
192 void* result = fl::realloc(ptr, new_count * sizeof(T));
193 if (!result) {
194 return nullptr; // Realloc failed
195 }
196
197 T* new_ptr = static_cast<T*>(result);
198
199 // Zero-initialize any newly allocated memory
200 if (new_count > old_count) {
201 fl::memset(new_ptr + old_count, 0, (new_count - old_count) * sizeof(T));
202 }
203
204 return new_ptr;
205 }
206
207 // SFINAE: Don't use realloc() for non-trivially-copyable types
208 pointer reallocate_impl(pointer ptr, fl::size old_count, fl::size new_count, fl::false_type) FL_NO_EXCEPT {
209 FASTLED_UNUSED(ptr);
210 FASTLED_UNUSED(old_count);
211 FASTLED_UNUSED(new_count);
212 return nullptr; // Signal: not supported, use standard allocate-copy-deallocate
213 }
214
215public:
216
217 // Use this to allocate large blocks of memory for T.
218 // This is useful for large arrays or objects that need to be allocated
219 // in a single block.
220 T* allocate(fl::size n) FL_NO_EXCEPT {
221 if (n == 0) {
222 return nullptr; // Handle zero allocation
223 }
224 fl::size size = sizeof(T) * n;
225 void *ptr = Malloc(size);
226 if (ptr == nullptr) {
227 return nullptr; // Handle allocation failure
228 }
229 fl::memset(ptr, 0, sizeof(T) * n); // Zero-initialize the memory
230 return static_cast<T*>(ptr);
231 }
232
233 void deallocate(T* p, fl::size n) FL_NO_EXCEPT {
235 if (p == nullptr) {
236 return; // Handle null pointer
237 }
238 Free(p); // Free the allocated memory
239 }
240
241 // Construct an object at the specified address
242 template <typename U, typename... Args>
243 void construct(U* p, Args&&... args) FL_NO_EXCEPT {
244 if (p == nullptr) return;
245 new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
246 }
247
248 // Destroy an object at the specified address
249 template <typename U>
251 if (p == nullptr) return;
252 p->~U();
253 }
254};
255
256// DEPRECATED: allocator_realloc is now REDUNDANT
257//
258// ℹ️ The default fl::allocator<T> now automatically uses realloc() for trivially
259// copyable types, making this specialized allocator unnecessary.
260//
261// ✅ NEW RECOMMENDED USAGE:
262// fl::vector<int> vec; // Automatically uses realloc() optimization
263// fl::vector<CRGB> leds; // Automatically uses realloc() optimization
264// fl::vector<fl::string> strs; // Automatically uses safe allocate-copy-deallocate
265//
266// This class is kept for backwards compatibility but provides no additional benefit.
267// The compile-time safety check below ensures this allocator is only used with
268// trivially copyable types (same restriction as fl::allocator now has).
269//
270template <typename T>
272private:
273 // SAFETY CHECK: Compile-time verification that T is trivially copyable
274 // This prevents undefined behavior from using realloc() with non-trivially-copyable types
276 "allocator_realloc<T> requires T to be trivially copyable. "
277 "NOTE: This allocator is now redundant - fl::allocator<T> automatically "
278 "optimizes trivially copyable types. Just use fl::vector<T> instead.");
279
280public:
281 // Type definitions required by STL
282 using value_type = T;
283 using pointer = T*;
284 using const_pointer = const T*;
285 using reference = T&;
286 using const_reference = const T&;
287 using size_type = fl::size;
289
290 // Rebind allocator to type U
291 template <typename U>
292 struct rebind {
294 };
295
296 // Default constructor
298
299 // Copy constructor
300 template <typename U>
302
303 // Destructor
305
306 // Standard allocate() - allocates new memory
307 T* allocate(fl::size n) FL_NO_EXCEPT {
308 if (n == 0) {
309 return nullptr;
310 }
311 fl::size size = sizeof(T) * n;
312 void *ptr = Malloc(size);
313 if (ptr == nullptr) {
314 return nullptr;
315 }
316 fl::memset(ptr, 0, sizeof(T) * n);
317 return static_cast<T*>(ptr);
318 }
319
320 // Standard deallocate()
321 void deallocate(T* p, fl::size n) FL_NO_EXCEPT {
323 if (p == nullptr) {
324 return;
325 }
326 Free(p);
327 }
328
329 // Standard construct()
330 template <typename U, typename... Args>
331 void construct(U* p, Args&&... args) FL_NO_EXCEPT {
332 if (p == nullptr) return;
333 new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
334 }
335
336 // Standard destroy()
337 template <typename U>
339 if (p == nullptr) return;
340 p->~U();
341 }
342
343 // OPTIMIZED: allocate_at_least() - can return more than requested
344 // This gives the vector a hint to allocate extra space and reduce future reallocations
346 if (n == 0) {
347 return {nullptr, 0};
348 }
349 // Ask for 1.5x to reduce future reallocations
350 fl::size requested = (3 * n) / 2;
351 T* ptr = allocate(requested);
352 if (ptr) {
353 return {ptr, requested};
354 }
355 // Fallback: try exact size if 1.5x failed
356 ptr = allocate(n);
357 return {ptr, ptr ? n : 0};
358 }
359
360 // OPTIMIZED: reallocate() - in-place resize using fl::realloc()
361 // Returns the new pointer if successful, nullptr if not supported/failed
362 // The caller (fl::vector) handles fallback to allocate-copy-deallocate
363 pointer reallocate(pointer ptr, fl::size old_count, fl::size new_count) FL_NO_EXCEPT {
364 if (new_count == 0) {
365 if (ptr) {
366 deallocate(ptr, old_count);
367 }
368 return nullptr;
369 }
370
371 // Use fl::realloc() for in-place resize
372 void* result = fl::realloc(ptr, new_count * sizeof(T));
373 if (!result) {
374 return nullptr; // Realloc failed
375 }
376
377 T* new_ptr = static_cast<T*>(result);
378
379 // Zero-initialize any newly allocated memory
380 if (new_count > old_count) {
381 fl::memset(new_ptr + old_count, 0, (new_count - old_count) * sizeof(T));
382 }
383
384 return new_ptr;
385 }
386};
387
388template <typename T> class allocator_psram {
389 public:
390 // Type definitions required by STL
391 using value_type = T;
392 using pointer = T*;
393 using const_pointer = const T*;
394 using reference = T&;
395 using const_reference = const T&;
396 using size_type = fl::size;
398
399 // Rebind allocator to type U
400 template <typename U>
401 struct rebind {
403 };
404
405 // Default constructor
407
408 // Copy constructor
409 template <typename U>
411
412 // Destructor
414
415 // Allocate memory for n objects of type T
416 T* allocate(fl::size n) FL_NO_EXCEPT {
417 return PSRamAllocator<T>::Alloc(n);
418 }
419
420 // Deallocate memory for n objects of type T
421 void deallocate(T* p, fl::size n) FL_NO_EXCEPT {
424 }
425
426 // Construct an object at the specified address
427 template <typename U, typename... Args>
428 void construct(U* p, Args&&... args) FL_NO_EXCEPT {
429 if (p == nullptr) return;
430 new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
431 }
432
433 // Destroy an object at the specified address
434 template <typename U>
436 if (p == nullptr) return;
437 p->~U();
438 }
439
440 // Optional: allocate_at_least() with default implementation
442 if (n == 0) {
443 return {nullptr, 0};
444 }
445 // Default: just allocate exactly what's requested
446 return {allocate(n), n};
447 }
448
449 // Optional: reallocate() - no-op for PSRAM
450 pointer reallocate(pointer ptr, fl::size old_count, fl::size new_count) FL_NO_EXCEPT {
451 FASTLED_UNUSED(ptr);
452 FASTLED_UNUSED(old_count);
453 FASTLED_UNUSED(new_count);
454 return nullptr; // Signal: not supported
455 }
456};
457
458// Slab allocator for fixed-size objects
459// Optimized for frequent allocation/deallocation of objects of the same size
460// Uses pre-allocated memory slabs with free lists to reduce fragmentation
461template <typename T, fl::size SLAB_SIZE = FASTLED_DEFAULT_SLAB_SIZE>
463private:
464
465 static constexpr fl::size SLAB_BLOCK_SIZE = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);
466 static constexpr fl::size BLOCKS_PER_SLAB = SLAB_SIZE;
467 static constexpr fl::size SLAB_MEMORY_SIZE = SLAB_BLOCK_SIZE * BLOCKS_PER_SLAB;
468
469 struct Slab {
473 fl::bitset_fixed<BLOCKS_PER_SLAB> allocated_blocks; // Track which blocks are allocated
474
475 Slab() FL_NO_EXCEPT : next(nullptr), memory(nullptr), allocated_count(0) {}
476
478 if (memory) {
479 Free(memory);
480 }
481 }
482 };
483
485 // Guards the slab list + block bitsets (FastLED#3588). A no-op on
486 // single-threaded platforms.
490
492 Slab* slab = static_cast<Slab*>(Malloc(sizeof(Slab)));
493 if (!slab) {
494 return nullptr;
495 }
496
497 // Use placement new to properly initialize the Slab
498 new(slab) Slab();
499
500 slab->memory = static_cast<u8*>(Malloc(SLAB_MEMORY_SIZE));
501 if (!slab->memory) {
502 slab->~Slab();
503 Free(slab);
504 return nullptr;
505 }
506
507 // Initialize all blocks in the slab as free
508 slab->allocated_blocks.reset(); // All blocks start as free
509
510 // Add slab to the slab list
511 slab->next = mSlabs;
512 mSlabs = slab;
513
514 return slab;
515 }
516
517 void* allocateFromSlab(fl::size n = 1) FL_NO_EXCEPT {
518 // Try to find n contiguous free blocks in existing slabs
519 for (Slab* slab = mSlabs; slab; slab = slab->next) {
520 void* ptr = findContiguousBlocks(slab, n);
521 if (ptr) {
522 return ptr;
523 }
524 }
525
526 // No contiguous blocks found, create new slab if n fits
527 if (n <= BLOCKS_PER_SLAB) {
528 if (!createSlab()) {
529 return nullptr; // Out of memory
530 }
531
532 // Try again with the new slab
533 return findContiguousBlocks(mSlabs, n);
534 }
535
536 // Request too large for slab, fall back to malloc
537 return nullptr;
538 }
539
540 void* findContiguousBlocks(Slab* slab, fl::size n) FL_NO_EXCEPT {
541 // Check if allocation is too large for this slab
542 if (n > BLOCKS_PER_SLAB) {
543 return nullptr;
544 }
545
546 // Use bitset's find_run to find n contiguous free blocks (false = free)
547 fl::i32 start = slab->allocated_blocks.find_run(false, static_cast<fl::u32>(n));
548 if (start >= 0) {
549 // Mark blocks as allocated
550 for (fl::size i = 0; i < n; ++i) {
551 slab->allocated_blocks.set(static_cast<fl::u32>(start + i), true);
552 }
553 slab->allocated_count += n;
554 mTotalAllocated += n;
555
556 // Return pointer to the first block
557 return slab->memory + static_cast<fl::size>(start) * SLAB_BLOCK_SIZE;
558 }
559
560 return nullptr;
561 }
562
563 void deallocateToSlab(void* ptr, fl::size n = 1) FL_NO_EXCEPT {
564 if (!ptr) {
565 return;
566 }
567
568 // Find which slab this block belongs to
569 for (Slab* slab = mSlabs; slab; slab = slab->next) {
570 u8* slab_start = slab->memory;
571 u8* slab_end = slab_start + SLAB_MEMORY_SIZE;
572 u8* block_ptr = fl::bit_cast_ptr<u8>(ptr);
573
574 if (block_ptr >= slab_start && block_ptr < slab_end) {
575 fl::size block_index = (block_ptr - slab_start) / SLAB_BLOCK_SIZE;
576
577 // Mark blocks as free in the bitset
578 for (fl::size i = 0; i < n; ++i) {
579 if (block_index + i < BLOCKS_PER_SLAB) {
580 slab->allocated_blocks.set(block_index + i, false);
581 }
582 }
583
584 slab->allocated_count -= n;
586 break;
587 }
588 }
589 }
590
591public:
592 // Constructor
594
595 // Destructor
599
600 // Non-copyable
603
604 // Movable
606 : mSlabs(other.mSlabs), mTotalAllocated(other.mTotalAllocated), mTotalDeallocated(other.mTotalDeallocated) {
607 other.mSlabs = nullptr;
608 other.mTotalAllocated = 0;
609 other.mTotalDeallocated = 0;
610 }
611
613 if (this != &other) {
614 cleanup();
615 mSlabs = other.mSlabs;
616 mTotalAllocated = other.mTotalAllocated;
617 mTotalDeallocated = other.mTotalDeallocated;
618 other.mSlabs = nullptr;
619 other.mTotalAllocated = 0;
620 other.mTotalDeallocated = 0;
621 }
622 return *this;
623 }
624
625 T* allocate(fl::size n = 1) FL_NO_EXCEPT {
626 if (n == 0) {
627 return nullptr;
628 }
629
630 // Thread safety (FastLED#3588): slab-backed containers
631 // (fl::map/set via allocator_slab) share a static per-type
632 // SlabAllocator across FreeRTOS tasks — e.g. the esp_http_server
633 // task building Response headers while the main loop mutates its
634 // own maps. Unsynchronized free-list/bitset updates corrupted
635 // the slab and crashed classic ESP32 under WiFi traffic.
637
638 // Try to allocate from slab first
639 void* ptr = allocateFromSlab(n);
640 if (ptr) {
641 fl::memset(ptr, 0, sizeof(T) * n);
642 return static_cast<T*>(ptr);
643 }
644
645 // Fall back to regular malloc for large allocations
646 ptr = Malloc(sizeof(T) * n);
647 if (ptr) {
648 fl::memset(ptr, 0, sizeof(T) * n);
649 }
650 return static_cast<T*>(ptr);
651 }
652
653 void deallocate(T* ptr, fl::size n = 1) FL_NO_EXCEPT {
654 if (!ptr) {
655 return;
656 }
657 fl::detail::slab_lock_guard guard(mMutex); // see allocate()
658
659 // Try to deallocate from slab first
660 bool found_in_slab = false;
661 for (Slab* slab = mSlabs; slab; slab = slab->next) {
662 u8* slab_start = slab->memory;
663 u8* slab_end = slab_start + SLAB_MEMORY_SIZE;
664 u8* block_ptr = fl::bit_cast_ptr<u8>(static_cast<void*>(ptr));
665
666 if (block_ptr >= slab_start && block_ptr < slab_end) {
667 deallocateToSlab(ptr, n);
668 found_in_slab = true;
669 break;
670 }
671 }
672
673 if (!found_in_slab) {
674 // This was allocated with regular malloc
675 Free(ptr);
676 }
677 }
678
679 // Get allocation statistics
680 fl::size getTotalAllocated() const FL_NO_EXCEPT { return mTotalAllocated; }
683
684 // Get number of slabs
685 fl::size getSlabCount() const FL_NO_EXCEPT {
686 fl::size count = 0;
687 for (Slab* slab = mSlabs; slab; slab = slab->next) {
688 ++count;
689 }
690 return count;
691 }
692
693 // Cleanup all slabs
695 fl::detail::slab_lock_guard guard(mMutex); // see allocate()
696 while (mSlabs) {
697 Slab* next = mSlabs->next;
698 mSlabs->~Slab();
699 Free(mSlabs);
700 mSlabs = next;
701 }
702 mTotalAllocated = 0;
704 }
705};
706
707// STL-compatible slab allocator
708template <typename T, fl::size SLAB_SIZE = FASTLED_DEFAULT_SLAB_SIZE>
710public:
711 // Type definitions required by STL
712 using value_type = T;
713 using pointer = T*;
714 using const_pointer = const T*;
715 using reference = T&;
716 using const_reference = const T&;
717 using size_type = fl::size;
719
720 // Rebind allocator to type U
721 template <typename U>
729
730 // Default constructor
732
733 // Copy constructor
735 FASTLED_UNUSED(other);
736 }
737
738 // Copy assignment
740 FASTLED_UNUSED(other);
741 return *this;
742 }
743
744 // Template copy constructor
745 template <typename U>
749
750 // Destructor
752
753private:
754 // Get the shared process-wide allocator instance.
755 // Uses a DLL-exported registry to ensure all DLLs in the process share
756 // the same SlabAllocator for a given (block_size, slab_size) pair.
758 constexpr fl::size block_size = sizeof(T) > sizeof(void*) ? sizeof(T) : sizeof(void*);
759 void* ptr = detail::slab_allocator_registry_get(block_size, SLAB_SIZE);
760 if (ptr) {
761 return *static_cast<SlabAllocator<T, SLAB_SIZE>*>(ptr);
762 }
763 // First time for this (block_size, slab_size) pair - create and register
765 detail::slab_allocator_registry_set(block_size, SLAB_SIZE, &allocator);
766 return allocator;
767 }
768
769public:
770 // Allocate memory for n objects of type T
771 T* allocate(fl::size n) FL_NO_EXCEPT {
772 // Use a static allocator instance per type/size combination
774 return allocator.allocate(n);
775 }
776
777 // Deallocate memory for n objects of type T
778 void deallocate(T* p, fl::size n) FL_NO_EXCEPT {
779 // Use the same static allocator instance
781 allocator.deallocate(p, n);
782 }
783
784 // Construct an object at the specified address
785 template <typename U, typename... Args>
786 void construct(U* p, Args&&... args) FL_NO_EXCEPT {
787 if (p == nullptr) return;
788 new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
789 }
790
791 // Destroy an object at the specified address
792 template <typename U>
794 if (p == nullptr) return;
795 p->~U();
796 }
797
798 // Cleanup method to clean up the static slab allocator
800 // Access the same static allocator instance and clean it up
802 allocator.cleanup();
803 }
804
805 // Equality comparison
806 bool operator==(const allocator_slab& other) const FL_NO_EXCEPT {
807 FASTLED_UNUSED(other);
808 return true; // All instances are equivalent
809 }
810
811 bool operator!=(const allocator_slab& other) const FL_NO_EXCEPT {
812 return !(*this == other);
813 }
814};
815
816// Inlined allocator that stores the first N elements inline
817// Falls back to the base allocator for additional elements
818template <typename T, fl::size N, typename BaseAllocator = fl::allocator<T>>
820private:
821
822 // Inlined storage block
824 FL_ALIGN_AS(T) u8 data[N * sizeof(T)];
825
827 fl::memset(data, 0, sizeof(data));
828 }
829 };
830
831 InlinedStorage mInlinedStorage;
832 BaseAllocator mBaseAllocator;
833 fl::size mInlinedUsed = 0;
834 fl::bitset_fixed<N> mFreeBits; // Track free slots for inlined memory only
835 fl::size mActiveAllocations = 0; // Track current active allocations
836
837public:
838 // Type definitions required by STL
839 using value_type = T;
840 using pointer = T*;
841 using const_pointer = const T*;
842 using reference = T&;
843 using const_reference = const T&;
844 using size_type = fl::size;
846
847 // Rebind allocator to type U
848 template <typename U>
852
853 // Default constructor
855
856 // Copy constructor
858 // Copy inlined data
859 mInlinedUsed = other.mInlinedUsed;
860 for (fl::size i = 0; i < mInlinedUsed; ++i) {
861 new (&get_inlined_ptr()[i]) T(other.get_inlined_ptr()[i]);
862 }
863
864 // Copy free bits
865 mFreeBits = other.mFreeBits;
866
867 // Note: Heap allocations are not copied, only inlined data
868
869 // Copy active allocations count
870 mActiveAllocations = other.mActiveAllocations;
871 }
872
873 // Copy assignment
875 if (this != &other) {
876 clear();
877
878 // Copy inlined data
879 mInlinedUsed = other.mInlinedUsed;
880 for (fl::size i = 0; i < mInlinedUsed; ++i) {
881 new (&get_inlined_ptr()[i]) T(other.get_inlined_ptr()[i]);
882 }
883
884 // Copy free bits
885 mFreeBits = other.mFreeBits;
886
887 // Note: Heap allocations are not copied, only inlined data
888
889 // Copy active allocations count
890 mActiveAllocations = other.mActiveAllocations;
891 }
892 return *this;
893 }
894
895 // Template copy constructor
896 template <typename U>
897 allocator_inlined(const allocator_inlined<U, N, typename BaseAllocator::template rebind<U>::other>& other) FL_NO_EXCEPT {
898 FASTLED_UNUSED(other);
899 }
900
901 // Destructor
905
906 // Allocate memory for n objects of type T
907 T* allocate(fl::size n) FL_NO_EXCEPT {
908 if (n == 0) {
909 return nullptr;
910 }
911
912 // For large allocations (n > 1), use base allocator directly
913 if (n > 1) {
914 T* ptr = mBaseAllocator.allocate(n);
915 if (ptr) {
917 }
918 return ptr;
919 }
920
921 // For single allocations, first try inlined memory
922 // Find first free inlined slot
923 fl::i32 free_slot = mFreeBits.find_first(false);
924 if (free_slot >= 0 && static_cast<fl::size>(free_slot) < N) {
925 // Mark the inlined slot as used
926 mFreeBits.set(static_cast<fl::u32>(free_slot), true);
927
928 // Update inlined usage tracking
929 if (static_cast<fl::size>(free_slot) + 1 > mInlinedUsed) {
930 mInlinedUsed = static_cast<fl::size>(free_slot) + 1;
931 }
933 return &get_inlined_ptr()[static_cast<fl::size>(free_slot)];
934 }
935
936 // No inlined slots available, use heap allocation
937 T* ptr = mBaseAllocator.allocate(1);
938 if (ptr) {
940 }
941 return ptr;
942 }
943
944 // Deallocate memory for n objects of type T
945 void deallocate(T* p, fl::size n) FL_NO_EXCEPT {
946 if (!p || n == 0) {
947 return;
948 }
949
950 // Check if this is inlined memory
951 T* inlined_start = get_inlined_ptr();
952 T* inlined_end = inlined_start + N;
953
954 if (p >= inlined_start && p < inlined_end) {
955 // This is inlined memory, mark slots as free
956 fl::size slot_index = (p - inlined_start);
957 for (fl::size i = 0; i < n; ++i) {
958 if (slot_index + i < N) {
959 mFreeBits.set(slot_index + i, false); // Mark as free
960 }
961 }
963 return;
964 }
965
966 // Fallback to base allocator for heap allocations
967 mBaseAllocator.deallocate(p, n);
969 }
970
971 // Construct an object at the specified address
972 template <typename U, typename... Args>
973 void construct(U* p, Args&&... args) FL_NO_EXCEPT {
974 if (p == nullptr) return;
975 new(static_cast<void*>(p)) U(fl::forward<Args>(args)...);
976 }
977
978 // Destroy an object at the specified address
979 template <typename U>
981 if (p == nullptr) return;
982 p->~U();
983 }
984
985 // Clear all allocated memory
987 // Destroy inlined objects
988 for (fl::size i = 0; i < mInlinedUsed; ++i) {
989 get_inlined_ptr()[i].~T();
990 }
991 mInlinedUsed = 0;
992 mFreeBits.reset();
994
995 // Clean up the base allocator (for SlabAllocator, this clears slabs and free lists)
997 }
998
999 // Get total allocated size
1000 fl::size total_size() const FL_NO_EXCEPT {
1001 return mActiveAllocations;
1002 }
1003
1004 // Get inlined capacity
1006 return N;
1007 }
1008
1009 // Check if using inlined storage
1012 }
1013
1014private:
1018
1021 }
1022
1023 // SFINAE helper to detect if base allocator has cleanup() method
1024 template<typename U>
1025 static auto has_cleanup_impl(int) FL_NO_EXCEPT -> decltype(fl::declval<U>().cleanup(), fl::true_type{});
1026
1027 template<typename U>
1029
1030 using has_cleanup = decltype(has_cleanup_impl<BaseAllocator>(0));
1031
1032 // Call cleanup on base allocator if it has the method
1036
1040
1042 // Base allocator doesn't have cleanup method, do nothing
1043 }
1044
1045 // Equality comparison
1046 bool operator==(const allocator_inlined& other) const FL_NO_EXCEPT {
1047 FASTLED_UNUSED(other);
1048 return true; // All instances are equivalent for now
1049 }
1050
1051 bool operator!=(const allocator_inlined& other) const FL_NO_EXCEPT {
1052 return !(*this == other);
1053 }
1054};
1055
1056// Inlined allocator that uses PSRam for heap allocation
1057template <typename T, fl::size N>
1059
1060// Inlined allocator that uses slab allocator for heap allocation
1061template <typename T, fl::size N, fl::size SLAB_SIZE = 8>
1063
1064template <typename T, fl::size N>
1066
1067} // namespace fl
Alignment macros and utilities for FastLED.
static T * Alloc(fl::size n) FL_NO_EXCEPT
Definition allocator.h:120
static void Free(T *p) FL_NO_EXCEPT
Definition allocator.h:125
Slab * createSlab() FL_NO_EXCEPT
Definition allocator.h:491
static constexpr fl::size BLOCKS_PER_SLAB
Definition allocator.h:466
T * allocate(fl::size n=1) FL_NO_EXCEPT
Definition allocator.h:625
SlabAllocator & operator=(const SlabAllocator &) FL_NO_EXCEPT=delete
void cleanup() FL_NO_EXCEPT
Definition allocator.h:694
SlabAllocator(const SlabAllocator &) FL_NO_EXCEPT=delete
SlabAllocator & operator=(SlabAllocator &&other) FL_NO_EXCEPT
Definition allocator.h:612
fl::size mTotalDeallocated
Definition allocator.h:489
void * findContiguousBlocks(Slab *slab, fl::size n) FL_NO_EXCEPT
Definition allocator.h:540
fl::size getTotalDeallocated() const FL_NO_EXCEPT
Definition allocator.h:681
static constexpr fl::size SLAB_MEMORY_SIZE
Definition allocator.h:467
SlabAllocator() FL_NO_EXCEPT
Definition allocator.h:593
static constexpr fl::size SLAB_BLOCK_SIZE
Definition allocator.h:465
void * allocateFromSlab(fl::size n=1) FL_NO_EXCEPT
Definition allocator.h:517
fl::size getActiveAllocations() const FL_NO_EXCEPT
Definition allocator.h:682
void deallocate(T *ptr, fl::size n=1) FL_NO_EXCEPT
Definition allocator.h:653
~SlabAllocator() FL_NO_EXCEPT
Definition allocator.h:596
fl::size mTotalAllocated
Definition allocator.h:488
SlabAllocator(SlabAllocator &&other) FL_NO_EXCEPT
Definition allocator.h:605
fl::size getTotalAllocated() const FL_NO_EXCEPT
Definition allocator.h:680
fl::detail::slab_mutex mMutex
Definition allocator.h:487
void deallocateToSlab(void *ptr, fl::size n=1) FL_NO_EXCEPT
Definition allocator.h:563
fl::size getSlabCount() const FL_NO_EXCEPT
Definition allocator.h:685
static auto has_cleanup_impl(int) FL_NO_EXCEPT -> decltype(fl::declval< U >().cleanup(), fl::true_type{})
static fl::false_type has_cleanup_impl(...) FL_NO_EXCEPT
bool operator!=(const allocator_inlined &other) const FL_NO_EXCEPT
Definition allocator.h:1051
T * allocate(fl::size n) FL_NO_EXCEPT
Definition allocator.h:907
allocator_inlined & operator=(const allocator_inlined &other) FL_NO_EXCEPT
Definition allocator.h:874
void cleanup_base_allocator_impl(fl::true_type) FL_NO_EXCEPT
Definition allocator.h:1037
bool operator==(const allocator_inlined &other) const FL_NO_EXCEPT
Definition allocator.h:1046
void clear() FL_NO_EXCEPT
Definition allocator.h:986
const T & const_reference
Definition allocator.h:843
const T * get_inlined_ptr() const FL_NO_EXCEPT
Definition allocator.h:1019
allocator_inlined() FL_NO_EXCEPT=default
allocator_inlined(const allocator_inlined< U, N, typename BaseAllocator::template rebind< U >::other > &other) FL_NO_EXCEPT
Definition allocator.h:897
void cleanup_base_allocator() FL_NO_EXCEPT
Definition allocator.h:1033
T * get_inlined_ptr() FL_NO_EXCEPT
Definition allocator.h:1015
~allocator_inlined() FL_NO_EXCEPT
Definition allocator.h:902
void destroy(U *p) FL_NO_EXCEPT
Definition allocator.h:980
void construct(U *p, Args &&... args) FL_NO_EXCEPT
Definition allocator.h:973
fl::ptrdiff_t difference_type
Definition allocator.h:845
const T * const_pointer
Definition allocator.h:841
bool is_using_inlined() const FL_NO_EXCEPT
Definition allocator.h:1010
fl::size inlined_capacity() const FL_NO_EXCEPT
Definition allocator.h:1005
void cleanup_base_allocator_impl(fl::false_type) FL_NO_EXCEPT
Definition allocator.h:1041
fl::size total_size() const FL_NO_EXCEPT
Definition allocator.h:1000
void deallocate(T *p, fl::size n) FL_NO_EXCEPT
Definition allocator.h:945
allocator_inlined< U, N, typename BaseAllocator::template rebind< U >::other > other
Definition allocator.h:850
void construct(U *p, Args &&... args) FL_NO_EXCEPT
Definition allocator.h:428
void deallocate(T *p, fl::size n) FL_NO_EXCEPT
Definition allocator.h:421
allocation_result< pointer, size_type > allocate_at_least(fl::size n) FL_NO_EXCEPT
Definition allocator.h:441
allocator_psram(const allocator_psram< U > &) FL_NO_EXCEPT
Definition allocator.h:410
const T & const_reference
Definition allocator.h:395
T * allocate(fl::size n) FL_NO_EXCEPT
Definition allocator.h:416
const T * const_pointer
Definition allocator.h:393
pointer reallocate(pointer ptr, fl::size old_count, fl::size new_count) FL_NO_EXCEPT
Definition allocator.h:450
~allocator_psram() FL_NO_EXCEPT
Definition allocator.h:413
allocator_psram() FL_NO_EXCEPT
Definition allocator.h:406
void destroy(U *p) FL_NO_EXCEPT
Definition allocator.h:435
fl::ptrdiff_t difference_type
Definition allocator.h:397
allocator_psram< U > other
Definition allocator.h:402
~allocator_realloc() FL_NO_EXCEPT
Definition allocator.h:304
const T & const_reference
Definition allocator.h:286
allocation_result< pointer, size_type > allocate_at_least(fl::size n) FL_NO_EXCEPT
Definition allocator.h:345
pointer reallocate(pointer ptr, fl::size old_count, fl::size new_count) FL_NO_EXCEPT
Definition allocator.h:363
allocator_realloc(const allocator_realloc< U > &) FL_NO_EXCEPT
Definition allocator.h:301
T * allocate(fl::size n) FL_NO_EXCEPT
Definition allocator.h:307
allocator_realloc() FL_NO_EXCEPT
Definition allocator.h:297
void destroy(U *p) FL_NO_EXCEPT
Definition allocator.h:338
FL_STATIC_ASSERT(fl::is_trivially_copyable< T >::value, "allocator_realloc<T> requires T to be trivially copyable. " "NOTE: This allocator is now redundant - fl::allocator<T> automatically " "optimizes trivially copyable types. Just use fl::vector<T> instead.")
const T * const_pointer
Definition allocator.h:284
void deallocate(T *p, fl::size n) FL_NO_EXCEPT
Definition allocator.h:321
void construct(U *p, Args &&... args) FL_NO_EXCEPT
Definition allocator.h:331
fl::ptrdiff_t difference_type
Definition allocator.h:288
allocator_realloc< U > other
Definition allocator.h:293
const T * const_pointer
Definition allocator.h:714
allocator_slab() FL_NO_EXCEPT
Definition allocator.h:731
static SlabAllocator< T, SLAB_SIZE > & get_allocator() FL_NO_EXCEPT
Definition allocator.h:757
bool operator==(const allocator_slab &other) const FL_NO_EXCEPT
Definition allocator.h:806
void deallocate(T *p, fl::size n) FL_NO_EXCEPT
Definition allocator.h:778
fl::ptrdiff_t difference_type
Definition allocator.h:718
void destroy(U *p) FL_NO_EXCEPT
Definition allocator.h:793
~allocator_slab() FL_NO_EXCEPT
Definition allocator.h:751
allocator_slab(const allocator_slab &other) FL_NO_EXCEPT
Definition allocator.h:734
allocator_slab(const allocator_slab< U, SLAB_SIZE > &other) FL_NO_EXCEPT
Definition allocator.h:746
const T & const_reference
Definition allocator.h:716
bool operator!=(const allocator_slab &other) const FL_NO_EXCEPT
Definition allocator.h:811
void cleanup() FL_NO_EXCEPT
Definition allocator.h:799
allocator_slab & operator=(const allocator_slab &other) FL_NO_EXCEPT
Definition allocator.h:739
void construct(U *p, Args &&... args) FL_NO_EXCEPT
Definition allocator.h:786
T * allocate(fl::size n) FL_NO_EXCEPT
Definition allocator.h:771
typename fl::conditional< fl::is_same< U, void >::value, allocator_slab< char, SLAB_SIZE >, allocator_slab< U, SLAB_SIZE > >::type other
Definition allocator.h:723
void destroy(U *p) FL_NO_EXCEPT
Definition allocator.h:250
pointer reallocate_impl(pointer ptr, fl::size old_count, fl::size new_count, fl::true_type) FL_NO_EXCEPT
Definition allocator.h:183
const T * const_pointer
Definition allocator.h:139
fl::ptrdiff_t difference_type
Definition allocator.h:143
~allocator() FL_NO_EXCEPT
Definition allocator.h:159
void deallocate(T *p, fl::size n) FL_NO_EXCEPT
Definition allocator.h:233
T * allocate(fl::size n) FL_NO_EXCEPT
Definition allocator.h:220
fl::size size_type
Definition allocator.h:142
void construct(U *p, Args &&... args) FL_NO_EXCEPT
Definition allocator.h:243
allocator() FL_NO_EXCEPT
Definition allocator.h:152
const T & const_reference
Definition allocator.h:141
allocation_result< pointer, size_type > allocate_at_least(fl::size n) FL_NO_EXCEPT
Definition allocator.h:164
pointer reallocate(pointer ptr, fl::size old_count, fl::size new_count) FL_NO_EXCEPT
Definition allocator.h:176
allocator(const allocator< U > &) FL_NO_EXCEPT
Definition allocator.h:156
pointer reallocate_impl(pointer ptr, fl::size old_count, fl::size new_count, fl::false_type) FL_NO_EXCEPT
Definition allocator.h:208
allocator< U > other
Definition allocator.h:148
RAII guard for slab_mutex.
Definition slab_mutex.h:118
Single-threaded MCUs: no-op.
Definition slab_mutex.h:102
#define FASTLED_UNUSED(x)
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)
long ptrdiff_t
Definition s16x16x4.h:22
void Free(void *ptr)
unsigned char u8
Definition stdint.h:131
add_rvalue_reference< T >::type declval() FL_NO_EXCEPT
integral_constant< bool, false > false_type
Definition type_traits.h:28
void SetPSRamAllocator(void *(*alloc)(fl::size), void(*free)(void *))
void free(void *ptr)
expected< T, E > result
Alias for expected (Rust-style naming)
Definition result.h:31
void PSRamDeallocate(void *ptr)
void * memset(void *s, int c, size_t n) FL_NO_EXCEPT
constexpr T && forward(typename remove_reference< T >::type &t) FL_NO_EXCEPT
void * Malloc(fl::size size)
InputGamut g FL_NO_EXCEPT
Definition rgbw.h:121
void * realloc(void *ptr, size_t new_size)
allocator_inlined< T, N, fl::allocator_slab< T > > allocator_inlined_slab
Definition allocator.h:1065
void * PSRamAllocate(fl::size size, bool zero)
To * bit_cast_ptr(void *storage) FL_NO_EXCEPT
Definition bit_cast.h:60
integral_constant< bool, true > true_type
Definition type_traits.h:27
allocator_inlined< T, N, fl::allocator_slab< T, SLAB_SIZE > > allocator_inlined_slab_psram
Definition allocator.h:1062
allocator_inlined< T, N, fl::allocator_psram< T > > allocator_inlined_psram
Definition allocator.h:1058
Base definition for an LED controller.
Definition crgb.hpp:179
corkscrew_args args
Definition old.h:149
#define FL_NO_EXCEPT
Minimal mutex for the allocator layer (FastLED#3588).
Portable compile-time assertion wrapper.
PSRamDeleter() FL_NO_EXCEPT=default
~Slab() FL_NO_EXCEPT
Definition allocator.h:477
fl::bitset_fixed< BLOCKS_PER_SLAB > allocated_blocks
Definition allocator.h:473
Slab() FL_NO_EXCEPT
Definition allocator.h:475
FL_ALIGN_AS(T) u8 data[N *sizeof(T)]
typename Allocator::pointer pointer
Definition allocator.h:35
static constexpr bool has_allocate_at_least_v
Definition allocator.h:62
typename Allocator::value_type value_type
Definition allocator.h:34
static constexpr bool has_reallocate_v
Definition allocator.h:50
Allocator allocator_type
Definition allocator.h:33
typename Allocator::size_type size_type
Definition allocator.h:36