Helios Engine
A modular ECS based data-oriented C++23 game engine framework
Loading...
Searching...
No Matches
fixed_stack_allocator.cpp
Go to the documentation of this file.
1#include <pch.hpp>
2
4
5#include <helios/assert.hpp>
8#include <helios/memory/details/profile.hpp>
9
10#include <algorithm>
11#include <atomic>
12#include <cstddef>
13#include <utility>
14
15namespace helios::mem {
16
17void FixedStackAllocator::ClearStats() noexcept {
18 offset_.store(0, std::memory_order_release);
19 peak_usage_.store(0, std::memory_order_relaxed);
20 allocation_count_.store(0, std::memory_order_relaxed);
21 total_allocations_.store(0, std::memory_order_relaxed);
22 total_deallocations_.store(0, std::memory_order_relaxed);
23 alignment_waste_.store(0, std::memory_order_relaxed);
24}
25
26void FixedStackAllocator::Release() noexcept {
27 if (buffer_ != nullptr) {
28 HELIOS_MEMORY_PROFILE_FREE(buffer_, "FixedStackAllocator");
29 AlignedFree(buffer_, false);
30 buffer_ = nullptr;
31 }
32}
33
34void FixedStackAllocator::MoveFrom(FixedStackAllocator& other) noexcept {
35 capacity_ = std::exchange(other.capacity_, 0);
36 buffer_ = std::exchange(other.buffer_, nullptr);
37 offset_.store(other.offset_.exchange(0, std::memory_order_acq_rel),
38 std::memory_order_release);
39 peak_usage_.store(other.peak_usage_.exchange(0, std::memory_order_relaxed),
40 std::memory_order_relaxed);
41 allocation_count_.store(
42 other.allocation_count_.exchange(0, std::memory_order_relaxed),
43 std::memory_order_relaxed);
44 total_allocations_.store(
45 other.total_allocations_.exchange(0, std::memory_order_relaxed),
46 std::memory_order_relaxed);
47 total_deallocations_.store(
48 other.total_deallocations_.exchange(0, std::memory_order_relaxed),
49 std::memory_order_relaxed);
50 alignment_waste_.store(
51 other.alignment_waste_.exchange(0, std::memory_order_relaxed),
52 std::memory_order_relaxed);
53}
54
55void* FixedStackAllocator::do_allocate(size_t bytes, size_t alignment) {
56 HELIOS_MEMORY_PROFILE_SCOPE_N(
57 "helios::mem::FixedStackAllocator::do_allocate");
58 HELIOS_MEMORY_PROFILE_ZONE_VALUE(bytes);
59
60 if (bytes == 0) [[unlikely]] {
61 return nullptr;
62 }
63
64 HELIOS_VERIFY(IsPowerOfTwo(alignment),
65 "alignment must be a power of two, got {}!", alignment);
66
67 const size_t effective_alignment = std::max(alignment, kMinAlignment);
68 const size_t header_alignment =
69 std::max(effective_alignment, alignof(AllocationHeader));
70 constexpr size_t kHeaderSize = sizeof(AllocationHeader);
71 size_t observed = offset_.load(std::memory_order_relaxed);
72 for (;;) {
73 const size_t padding = CalculatePaddingWithHeader(
74 buffer_ + observed, header_alignment, kHeaderSize);
75 const size_t user_offset = SaturatingAdd(observed, padding);
76 const size_t next = SaturatingAdd(user_offset, bytes);
77
78 HELIOS_VERIFY(next <= capacity_, "Fixed stack allocator exhausted!");
79
80 if (offset_.compare_exchange_weak(observed, next, std::memory_order_acq_rel,
81 std::memory_order_relaxed)) {
82 auto* const user_ptr = buffer_ + user_offset;
83 auto* const header = std::launder(
84 reinterpret_cast<AllocationHeader*>(user_ptr - kHeaderSize));
85 header->previous_offset = observed;
86 header->end_offset = next;
87
88 const size_t waste = padding - kHeaderSize;
89 alignment_waste_.fetch_add(waste, std::memory_order_relaxed);
90 allocation_count_.fetch_add(1, std::memory_order_relaxed);
91 total_allocations_.fetch_add(1, std::memory_order_relaxed);
92
93 size_t peak = peak_usage_.load(std::memory_order_relaxed);
94 while (next > peak && !peak_usage_.compare_exchange_weak(
95 peak, next, std::memory_order_relaxed)) {
96 }
97 return user_ptr;
98 }
99 }
100}
101
102void FixedStackAllocator::do_deallocate(void* ptr, size_t /*bytes*/,
103 size_t /*alignment*/) {
104 HELIOS_MEMORY_PROFILE_SCOPE_N(
105 "helios::mem::FixedStackAllocator::do_deallocate");
106
107 if (ptr == nullptr) [[unlikely]] {
108 return;
109 }
110
111 HELIOS_ASSERT(Owns(ptr), "ptr does not belong to fixed stack!");
112
113 constexpr size_t kHeaderSize = sizeof(AllocationHeader);
114 auto* const header = std::launder(reinterpret_cast<AllocationHeader*>(
115 static_cast<std::byte*>(ptr) - kHeaderSize));
116 size_t expected = header->end_offset;
117 if (offset_.compare_exchange_strong(expected, header->previous_offset,
118 std::memory_order_acq_rel)) {
119 allocation_count_.fetch_sub(1, std::memory_order_relaxed);
120 total_deallocations_.fetch_add(1, std::memory_order_relaxed);
121 const auto user_offset =
122 static_cast<size_t>(static_cast<std::byte*>(ptr) - buffer_);
123 const size_t waste = user_offset - header->previous_offset - kHeaderSize;
124 alignment_waste_.fetch_sub(
125 std::min(waste, alignment_waste_.load(std::memory_order_relaxed)),
126 std::memory_order_relaxed);
127 }
128}
129
130} // namespace helios::mem
#define HELIOS_ASSERT(condition,...)
Assertion macro that aborts execution in debug builds.
Definition assert.hpp:259
#define HELIOS_VERIFY(condition,...)
Verify macro that always checks the condition.
Definition assert.hpp:323
PMR stack allocator with fixed runtime capacity.
bool Owns(const void *ptr) const noexcept
Checks whether pointer belongs to this stack's backing buffer.
constexpr bool IsPowerOfTwo(size_t value) noexcept
Checks if a value is a power of two.
Definition common.hpp:215
constexpr size_t kMinAlignment
Minimum alignment used by memory resources.
Definition common.hpp:17
size_t CalculatePaddingWithHeader(const void *ptr, size_t alignment, size_t header_size) noexcept
Computes padding for aligned storage with a prepended header.
Definition common.hpp:288
void AlignedFree(void *ptr, bool enable_profile=true) noexcept
Frees memory allocated with AlignedAlloc.
constexpr size_t SaturatingAdd(size_t lhs, size_t rhs) noexcept
Saturating add for size_t.
Definition common.hpp:25