Helios Engine 0.1.0
A modular ECS based data-oriented C++23 game engine
 
Loading...
Searching...
No Matches
helios::memory::StackAllocator Class Referencefinal

Stack/linear allocator with LIFO deallocation support. More...

#include <stack_allocator.hpp>

Public Member Functions

 StackAllocator (size_t capacity)
 Constructs a stack allocator with specified capacity.
 
 StackAllocator (StackAllocator &&other) noexcept
 
 StackAllocator (const StackAllocator &)=delete
 
 ~StackAllocator () noexcept
 
StackAllocatoroperator= (const StackAllocator &)=delete
 
StackAllocatoroperator= (StackAllocator &&other) noexcept
 
AllocationResult Allocate (size_t size, size_t alignment=kDefaultAlignment) noexcept
 Allocates memory with specified size and alignment.
 
template<typename T >
T * Allocate () noexcept
 
template<typename T >
T * Allocate (size_t count) noexcept
 
template<typename T , typename... Args>
requires std::constructible_from<T, Args...>
T * AllocateAndConstruct (Args &&... args) noexcept(std::is_nothrow_constructible_v< T, Args... >)
 
template<typename T >
requires std::default_initializable<T>
T * AllocateAndConstructArray (size_t count) noexcept(std::is_nothrow_default_constructible_v< T >)
 
void Deallocate (void *ptr, size_t size) noexcept
 Deallocates memory in LIFO order.
 
void Reset () noexcept
 Resets the allocator, freeing all allocations.
 
void RewindToMarker (size_t marker) noexcept
 Rewinds the stack to a previous marker position.
 
bool Empty () const noexcept
 Checks if the allocator is empty.
 
bool Full () const noexcept
 Checks if the allocator is full.
 
bool Owns (const void *ptr) const noexcept
 Checks if a pointer belongs to this allocator.
 
AllocatorStats Stats () const noexcept
 Gets current allocator statistics.
 
size_t Marker () const noexcept
 Gets a marker for the current stack position.
 
size_t Capacity () const noexcept
 Gets the total capacity of the allocator.
 
size_t CurrentOffset () const noexcept
 Gets the current offset (amount of memory used).
 
size_t FreeSpace () const noexcept
 Gets the amount of free space remaining.
 

Detailed Description

Stack/linear allocator with LIFO deallocation support.

Allocates memory sequentially using a bump pointer, but unlike FrameAllocator, supports LIFO (stack-like) deallocations. Each allocation stores a header with the previous offset, allowing proper unwinding.

Ideal for hierarchical/scoped allocations where deallocation follows allocation order (e.g., call stacks, recursive algorithms).

Each allocation has a small header overhead for tracking.

Uses lock-free atomic operations for thread-safe allocations.

Note
Thread-safe: Multiple threads can safely call Allocate() concurrently.
Warning
Deallocations must follow LIFO order (stack discipline).
Move operations (move constructor and move assignment) are NOT thread-safe and must be externally synchronized. Do not move an allocator while other threads are accessing it.

Definition at line 39 of file stack_allocator.hpp.

Constructor & Destructor Documentation

◆ StackAllocator() [1/3]

helios::memory::StackAllocator::StackAllocator ( size_t  capacity)
inlineexplicit

Constructs a stack allocator with specified capacity.

Warning
Triggers assertion if capacity is 0.
Parameters
capacityTotal size of the memory buffer in bytes
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 233 of file stack_allocator.hpp.

233 : capacity_{capacity} {
234 HELIOS_ASSERT(capacity > 0, "Failed to construct StackAllocator: capacity must be greater than 0!");
235
236 // Allocate aligned buffer
237 buffer_ = AlignedAlloc(kDefaultAlignment, capacity);
238 HELIOS_VERIFY(buffer_ != nullptr, "Failed to construct StackAllocator: Allocation of StackAllocator buffer failed!");
239}
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:140
#define HELIOS_VERIFY(condition,...)
Verify macro that always checks the condition.
Definition assert.hpp:196
constexpr size_t kDefaultAlignment
Default alignment for allocations (cache line size for most modern CPUs).
void * AlignedAlloc(size_t alignment, size_t size)
Allocate memory with the specified alignment.
Definition common.hpp:30

◆ StackAllocator() [2/3]

helios::memory::StackAllocator::StackAllocator ( StackAllocator &&  other)
inlinenoexcept

Definition at line 247 of file stack_allocator.hpp.

248 : buffer_(other.buffer_),
249 capacity_{other.capacity_.load(std::memory_order_relaxed)},
250 offset_(other.offset_.load(std::memory_order_acquire)),
251 peak_offset_(other.peak_offset_.load(std::memory_order_acquire)),
252 allocation_count_(other.allocation_count_.load(std::memory_order_acquire)),
253 total_allocations_(other.total_allocations_.load(std::memory_order_acquire)),
254 total_deallocations_(other.total_deallocations_.load(std::memory_order_acquire)),
255 alignment_waste_(other.alignment_waste_.load(std::memory_order_acquire)) {
256 other.buffer_ = nullptr;
257 other.capacity_.store(0, std::memory_order_relaxed);
258 other.offset_.store(0, std::memory_order_release);
259 other.peak_offset_.store(0, std::memory_order_release);
260 other.allocation_count_.store(0, std::memory_order_release);
261 other.total_allocations_.store(0, std::memory_order_release);
262 other.total_deallocations_.store(0, std::memory_order_release);
263 other.alignment_waste_.store(0, std::memory_order_release);
264}

◆ StackAllocator() [3/3]

helios::memory::StackAllocator::StackAllocator ( const StackAllocator )
delete

◆ ~StackAllocator()

helios::memory::StackAllocator::~StackAllocator ( )
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 241 of file stack_allocator.hpp.

241 {
242 if (buffer_ != nullptr) {
243 AlignedFree(buffer_);
244 }
245}
void AlignedFree(void *ptr)
Free memory allocated with AlignedAlloc.
Definition common.hpp:53

Member Function Documentation

◆ Allocate() [1/3]

template<typename T >
T * helios::memory::StackAllocator::Allocate ( )
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 445 of file stack_allocator.hpp.

445 {
446 constexpr size_t size = sizeof(T);
447 constexpr size_t alignment = std::max(alignof(T), kMinAlignment);
448 auto result = Allocate(size, alignment);
449 return static_cast<T*>(result.ptr);
450}
constexpr size_t kMinAlignment
Minimum alignment for any allocation.

◆ Allocate() [2/3]

template<typename T >
T * helios::memory::StackAllocator::Allocate ( size_t  count)
inlinenoexcept

Definition at line 453 of file stack_allocator.hpp.

453 {
454 if (count == 0) [[unlikely]] {
455 return nullptr;
456 }
457 constexpr size_t alignment = std::max(alignof(T), kMinAlignment);
458 const size_t size = sizeof(T) * count;
459 auto result = Allocate(size, alignment);
460 return static_cast<T*>(result.ptr);
461}

◆ Allocate() [3/3]

AllocationResult helios::memory::StackAllocator::Allocate ( size_t  size,
size_t  alignment = kDefaultAlignment 
)
inlinenoexcept

Allocates memory with specified size and alignment.

Stores allocation header for LIFO deallocation support

Warning
Triggers assertion in next cases:
  • Alignment is not a power of 2.
  • Alignment is less than kMinAlignment.
Parameters
sizeNumber of bytes to allocate
alignmentAlignment requirement (must be power of 2)
Returns
AllocationResult with pointer and actual allocated size, or {nullptr, 0} on failure

Definition at line 300 of file stack_allocator.hpp.

300 {
301 HELIOS_ASSERT(IsPowerOfTwo(alignment), "Failed to allocate memory: alignment must be power of 2, got '{}'!",
302 alignment);
303 HELIOS_ASSERT(alignment >= kMinAlignment, "Failed to allocate memory: alignment must be at least '{}', got '{}'!",
304 kMinAlignment, alignment);
305
306 if (size == 0) [[unlikely]] {
307 return {.ptr = nullptr, .allocated_size = 0};
308 }
309
310 // Lock-free allocation using compare-and-swap
311 size_t current_offset = offset_.load(std::memory_order_acquire);
312 size_t new_offset = 0;
313 size_t header_padding = 0;
314 uint8_t* current_ptr = nullptr;
315
316 do {
317 // Calculate space needed for header + alignment
318 current_ptr = static_cast<uint8_t*>(buffer_) + current_offset;
319 header_padding = CalculatePaddingWithHeader(current_ptr, alignment, sizeof(AllocationHeader));
320 const size_t required_space = header_padding + size;
321
322 // Check if we have enough space
323 if (current_offset + required_space > capacity_.load(std::memory_order_relaxed)) {
324 return {.ptr = nullptr, .allocated_size = 0};
325 }
326
327 new_offset = current_offset + required_space;
328
329 // Try to atomically update offset
330 } while (
331 !offset_.compare_exchange_weak(current_offset, new_offset, std::memory_order_release, std::memory_order_acquire));
332
333 // Successfully reserved space - now write the header
334 // This is safe because we own the range [current_offset, new_offset)
335 auto* header_ptr = current_ptr + header_padding - sizeof(AllocationHeader);
336 auto* header = reinterpret_cast<AllocationHeader*>(header_ptr);
337 header->previous_offset = current_offset;
338 header->padding = header_padding;
339
340 // Update stats
341 allocation_count_.fetch_add(1, std::memory_order_relaxed);
342 total_allocations_.fetch_add(1, std::memory_order_relaxed);
343 alignment_waste_.fetch_add(header_padding - sizeof(AllocationHeader), std::memory_order_relaxed);
344
345 // Update peak offset
346 size_t current_peak = peak_offset_.load(std::memory_order_acquire);
347 while (new_offset > current_peak) {
348 if (peak_offset_.compare_exchange_weak(current_peak, new_offset, std::memory_order_release,
349 std::memory_order_acquire)) {
350 break;
351 }
352 }
353
354 // Calculate aligned data pointer
355 uint8_t* data_ptr = current_ptr + header_padding;
356 return {data_ptr, size};
357}
constexpr bool IsPowerOfTwo(size_t size) noexcept
Helper function to check if a size is a power of 2.
size_t CalculatePaddingWithHeader(const void *ptr, size_t alignment, size_t header_size) noexcept
Calculate padding with header for alignment.

◆ AllocateAndConstruct()

template<typename T , typename... Args>
requires std::constructible_from<T, Args...>
T * helios::memory::StackAllocator::AllocateAndConstruct ( Args &&...  args)
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 465 of file stack_allocator.hpp.

465 {
466 T* ptr = Allocate<T>();
467 if (ptr != nullptr) [[likely]] {
468 std::construct_at(ptr, std::forward<Args>(args)...);
469 }
470 return ptr;
471}

◆ AllocateAndConstructArray()

template<typename T >
requires std::default_initializable<T>
T * helios::memory::StackAllocator::AllocateAndConstructArray ( size_t  count)
inlinenoexcept
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 475 of file stack_allocator.hpp.

475 {
476 T* ptr = Allocate<T>(count);
477 if (ptr != nullptr) [[likely]] {
478 for (size_t i = 0; i < count; ++i) {
479 std::construct_at(ptr + i);
480 }
481 }
482 return ptr;
483}

◆ Capacity()

size_t helios::memory::StackAllocator::Capacity ( ) const
inlinenoexcept

Gets the total capacity of the allocator.

Returns
Capacity in bytes
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/allocator_resources.hpp, and /home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 199 of file stack_allocator.hpp.

199{ return capacity_.load(std::memory_order_relaxed); }

◆ CurrentOffset()

size_t helios::memory::StackAllocator::CurrentOffset ( ) const
inlinenoexcept

Gets the current offset (amount of memory used).

Returns
Current offset in bytes
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 205 of file stack_allocator.hpp.

205{ return offset_.load(std::memory_order_relaxed); }

◆ Deallocate()

void helios::memory::StackAllocator::Deallocate ( void *  ptr,
size_t  size 
)
inlinenoexcept

Deallocates memory in LIFO order.

Warning
Triggers assertion in next cases:
  • Pointer does not belong to this allocator.
  • Deallocation violates LIFO order.
Parameters
ptrPointer to deallocate (must be the most recent allocation)
sizeSize of allocation (used for validation)
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 359 of file stack_allocator.hpp.

359 {
360 if (ptr == nullptr) [[unlikely]] {
361 return;
362 }
363
364 HELIOS_ASSERT(Owns(ptr), "Failed to deallocate memory: ptr does not belong to this allocator!");
365
366 // Get the header stored before the allocation
367 auto* header = reinterpret_cast<AllocationHeader*>(static_cast<uint8_t*>(ptr) - sizeof(AllocationHeader));
368
369#ifdef HELIOS_DEBUG_MODE
370 {
371 // Verify this is the most recent allocation (LIFO check)
372 const size_t current_offset = offset_.load(std::memory_order_acquire);
373 HELIOS_ASSERT(static_cast<uint8_t*>(ptr) + size <= static_cast<uint8_t*>(buffer_) + current_offset,
374 "Failed to deallocate memory: Deallocation violates LIFO order!");
375 }
376#endif
377
378 // Rewind to previous offset
379 // Note: LIFO deallocations are expected to be single-threaded per stack or externally synchronized
380 offset_.store(header->previous_offset, std::memory_order_release);
381
382 // Update stats
383 allocation_count_.fetch_sub(1, std::memory_order_relaxed);
384 total_deallocations_.fetch_add(1, std::memory_order_relaxed);
385}
bool Owns(const void *ptr) const noexcept
Checks if a pointer belongs to this allocator.

◆ Empty()

bool helios::memory::StackAllocator::Empty ( ) const
inlinenoexcept

Checks if the allocator is empty.

Returns
True if no allocations have been made since last reset
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/allocator_resources.hpp, and /home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 167 of file stack_allocator.hpp.

167{ return offset_.load(std::memory_order_relaxed) == 0; }

◆ FreeSpace()

size_t helios::memory::StackAllocator::FreeSpace ( ) const
inlinenoexcept

Gets the amount of free space remaining.

Returns
Free space in bytes
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 438 of file stack_allocator.hpp.

438 {
439 const size_t current = offset_.load(std::memory_order_relaxed);
440 const size_t capacity = capacity_.load(std::memory_order_relaxed);
441 return current < capacity ? capacity - current : 0;
442}

◆ Full()

bool helios::memory::StackAllocator::Full ( ) const
inlinenoexcept

Checks if the allocator is full.

Returns
True if no more allocations can be made without reset
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/allocator_resources.hpp, and /home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 173 of file stack_allocator.hpp.

173{ return offset_.load(std::memory_order_relaxed) >= capacity_; }

◆ Marker()

size_t helios::memory::StackAllocator::Marker ( ) const
inlinenoexcept

Gets a marker for the current stack position.

Note
Can be used with RewindToMarker for bulk deallocations.
Returns
Current offset as a marker
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 193 of file stack_allocator.hpp.

193{ return offset_.load(std::memory_order_relaxed); }

◆ operator=() [1/2]

◆ operator=() [2/2]

StackAllocator & helios::memory::StackAllocator::operator= ( StackAllocator &&  other)
inlinenoexcept

Definition at line 266 of file stack_allocator.hpp.

266 {
267 if (this == &other) [[unlikely]] {
268 return *this;
269 }
270
271 // Free current buffer
272 if (buffer_ != nullptr) {
273 AlignedFree(buffer_);
274 }
275
276 // Move from other
277 buffer_ = other.buffer_;
278 capacity_.store(other.capacity_.load(std::memory_order_relaxed), std::memory_order_relaxed);
279
280 offset_.store(other.offset_.load(std::memory_order_acquire), std::memory_order_release);
281 peak_offset_.store(other.peak_offset_.load(std::memory_order_acquire), std::memory_order_release);
282 allocation_count_.store(other.allocation_count_.load(std::memory_order_acquire), std::memory_order_release);
283 total_allocations_.store(other.total_allocations_.load(std::memory_order_acquire), std::memory_order_release);
284 total_deallocations_.store(other.total_deallocations_.load(std::memory_order_acquire), std::memory_order_release);
285 alignment_waste_.store(other.alignment_waste_.load(std::memory_order_acquire), std::memory_order_release);
286
287 // Reset other
288 other.buffer_ = nullptr;
289 other.capacity_.store(0, std::memory_order_relaxed);
290 other.offset_.store(0, std::memory_order_release);
291 other.peak_offset_.store(0, std::memory_order_release);
292 other.allocation_count_.store(0, std::memory_order_release);
293 other.total_allocations_.store(0, std::memory_order_release);
294 other.total_deallocations_.store(0, std::memory_order_release);
295 other.alignment_waste_.store(0, std::memory_order_release);
296
297 return *this;
298}

◆ Owns()

bool helios::memory::StackAllocator::Owns ( const void *  ptr) const
inlinenoexcept

Checks if a pointer belongs to this allocator.

Parameters
ptrPointer to check
Returns
True if pointer is within allocator's memory range
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 407 of file stack_allocator.hpp.

407 {
408 if (ptr == nullptr || buffer_ == nullptr) {
409 return false;
410 }
411
412 const uintptr_t start = reinterpret_cast<uintptr_t>(buffer_);
413 const auto addr = reinterpret_cast<uintptr_t>(ptr);
414 const uintptr_t end = start + capacity_.load(std::memory_order_relaxed);
415
416 return addr >= start && addr < end;
417}

◆ Reset()

void helios::memory::StackAllocator::Reset ( )
inlinenoexcept

Resets the allocator, freeing all allocations.

Resets the internal offset to 0, effectively freeing all memory.

Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/allocator_resources.hpp, and /home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 387 of file stack_allocator.hpp.

387 {
388 offset_.store(0, std::memory_order_release);
389 allocation_count_.store(0, std::memory_order_release);
390 alignment_waste_.store(0, std::memory_order_release);
391}

◆ RewindToMarker()

void helios::memory::StackAllocator::RewindToMarker ( size_t  marker)
inlinenoexcept

Rewinds the stack to a previous marker position.

Invalidates all allocations made after the marker

Warning
Triggers assertion in next cases:
  • Marker is ahead of current offset.
  • Marker exceeds capacity.
Parameters
markerPreviously obtained marker from Marker()
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 393 of file stack_allocator.hpp.

393 {
394 [[maybe_unused]] const size_t current_offset = offset_.load(std::memory_order_acquire);
395 HELIOS_ASSERT(marker <= current_offset, "Failed to rewind to marker: marker '{}' is ahead of current offset '{}'!",
396 marker, current_offset);
397
398 const size_t capacity = capacity_.load(std::memory_order_relaxed);
399 HELIOS_ASSERT(marker <= capacity, "Failed to rewind to marker: marker '{}' exceeds capacity '{}'!", marker, capacity);
400
401 offset_.store(marker, std::memory_order_release);
402
403 // Note: allocation_count_ becomes inaccurate after rewind, but that's acceptable
404 // since we're doing bulk deallocation
405}

◆ Stats()

AllocatorStats helios::memory::StackAllocator::Stats ( ) const
inlinenoexcept

Gets current allocator statistics.

Returns
AllocatorStats with current usage information
Examples
/home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/allocator_resources.hpp, and /home/runner/work/HeliosEngine/HeliosEngine/src/core/include/helios/core/memory/stack_allocator.hpp.

Definition at line 419 of file stack_allocator.hpp.

419 {
420 const size_t current_offset = offset_.load(std::memory_order_relaxed);
421 const size_t peak = peak_offset_.load(std::memory_order_relaxed);
422 const size_t alloc_count = allocation_count_.load(std::memory_order_relaxed);
423 const size_t total_allocs = total_allocations_.load(std::memory_order_relaxed);
424 const size_t total_deallocs = total_deallocations_.load(std::memory_order_relaxed);
425 const size_t waste = alignment_waste_.load(std::memory_order_relaxed);
426
427 return {
428 .total_allocated = current_offset,
429 .total_freed = 0,
430 .peak_usage = peak,
431 .allocation_count = alloc_count,
432 .total_allocations = total_allocs,
433 .total_deallocations = total_deallocs,
434 .alignment_waste = waste,
435 };
436}