FE 0.9.2
Header-only C++ frontend library
Loading...
Searching...
No Matches
arena.h
Go to the documentation of this file.
1#pragma once
2
3#include <algorithm>
4#include <list>
5#include <memory>
6#include <memory_resource>
7#include <new>
8#include <type_traits>
9#include <utility>
10
11#include "fe/assert.h"
12
13namespace fe {
14
15/// An arena pre-allocates so-called *pages* of size Arena::page_size_.
16/// You can use Arena::allocate to obtain memory from this.
17/// When a page runs out of memory, the next page will be (pre-)allocated.
18/// You cannot directly release memory obtained via this method.
19/// Instead, *all* memory acquired via this Arena will be released as soon as this Arena will be destroyed.
20/// As an exception, you can Arena::deallocate memory that has just been acquired.
21class Arena {
22public:
23 static constexpr size_t Default_Page_Size = 1024 * 1024; ///< 1MB.
24
25 /// A [memory resource](https://en.cppreference.com/w/cpp/memory/memory_resource) bridge in order to use this
26 /// Arena for [pmr containers](https://en.cppreference.com/w/cpp/memory/polymorphic_allocator).
27 /// Access it via Arena::resource.
29 public:
30 explicit MemoryResource(Arena& arena) noexcept
31 : arena_(arena) {}
32
33 private:
34 void* do_allocate(size_t bytes, size_t alignment) override { return arena_.allocate(bytes, alignment); }
35 void do_deallocate(void*, size_t, size_t) override {}
36 bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
37 if (this == &other) return true;
38 auto resource = dynamic_cast<const MemoryResource*>(&other);
39 return resource != nullptr && &arena_ == &resource->arena_;
40 }
41
42 Arena& arena_;
43 };
44
45 /// An [allocator](https://en.cppreference.com/w/cpp/named_req/Allocator) in order to use this Arena for
46 /// [containers](https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer).
47 /// Construct it via Arena::allocator.
48 template<class T>
49 struct Allocator {
50 using value_type = T;
51
52 Allocator() = delete;
53
54 template<class U>
55 constexpr Allocator(const Arena::Allocator<U>& allocator) noexcept
56 : arena(allocator.arena) {}
57 constexpr Allocator(Arena& arena) noexcept
58 : arena(arena) {}
59
60 [[nodiscard]] constexpr T* allocate(size_t num_elems) { return arena.allocate<T>(num_elems); }
61
62 constexpr void deallocate(T*, size_t) noexcept {}
63
64 /// All Arena::Allocator%s compare equal.
65 /// Allocator equality denotes deallocation-compatibility, and Arena::Allocator::deallocate is a no-op,
66 /// so storage from any instance can be "freed" through any other.
67 /// This also keeps allocator-aware container `swap` (e.g. in SymPool::swap) well-defined without requiring
68 /// `propagate_on_container_swap`, which we cannot enable here because `arena` is a non-rebindable reference.
69 template<class U> constexpr bool operator==(const Allocator<U>&) const noexcept { return true; }
70 template<class U> constexpr bool operator!=(const Allocator<U>&) const noexcept { return false; }
71
73 };
74
75 template<class T>
76 struct Deleter {
77 constexpr Deleter() noexcept = default;
78 template<class U, std::enable_if_t<std::is_convertible_v<U*, T*>, int> = 0>
79 constexpr Deleter(const Deleter<U>&) noexcept {}
80
81 constexpr void operator()(T* ptr) const noexcept(noexcept(ptr->~T())) { ptr->~T(); }
82 };
83
84 template<class T>
85 using Ptr = std::unique_ptr<T, Deleter<T>>;
86 using State = std::pair<size_t, size_t>;
87
88 /// @name Construction
89 ///@{
90 Arena(const Arena&) = delete;
91 explicit Arena(size_t page_size = Default_Page_Size)
92 : page_size_(page_size) {
93 pages_.emplace_back();
94 }
95 Arena(Arena&& other) noexcept
96 : Arena() {
97 swap(*this, other);
98 }
99 Arena& operator=(Arena) = delete;
100
101 /// Create Allocator from Arena.
102 template<class T>
103 constexpr Allocator<T> allocator() noexcept {
104 return Allocator<T>(*this);
105 }
106
107 std::pmr::memory_resource* resource() noexcept { return &resource_; }
108 const std::pmr::memory_resource* resource() const noexcept { return &resource_; }
109
110 /// This is a [std::unique_ptr](https://en.cppreference.com/w/cpp/memory/unique_ptr)
111 /// that uses the Arena under the hood
112 /// and whose Deleter will *only* invoke the destructor but *not* `delete` anything;
113 /// memory will be released upon destruction of the Arena.
114 ///
115 /// Use like this:
116 /// ```
117 /// auto ptr = arena.mk<Foo>(a, b, c); // new Foo(a, b, c) placed into arena
118 /// ```
119 template<class T, class... Args>
120 constexpr Ptr<T> mk(Args&&... args) {
121 auto ptr = new (allocate<std::remove_const_t<T>>(1)) T(std::forward<Args>(args)...);
122 return Ptr<T>(ptr, Deleter<T>());
123 }
124 ///@}
125
126 /// @name Allocate
127 ///@{
128
129 /// Get @p n bytes of fresh memory.
130 /// @note When a fresh page is allocated, its base is only aligned to the @p align of the allocation that
131 /// triggered it. A *later* allocation in the same page that requests a *larger* alignment has its offset
132 /// aligned but may still be under-aligned relative to its request. This is a non-issue for the default
133 /// (max-aligned) page size and for arenas with uniform alignment; only tiny custom arenas mixing alignments
134 /// can hit it.
135 [[nodiscard]] constexpr void* allocate(size_t num_bytes, size_t align) {
136 if (num_bytes == 0) return nullptr;
137 assert(align != 0);
138
139 auto aligned_index = Arena::align(index_, align);
140 if (aligned_index + num_bytes > pages_.back().size) {
141 pages_.emplace_back(std::max(page_size_, num_bytes), align);
142 aligned_index = 0;
143 }
144
145 auto result = pages_.back().buffer + aligned_index;
146 index_ = aligned_index + num_bytes;
147 return result;
148 }
149
150 template<class T>
151 [[nodiscard]] constexpr T* allocate(size_t num_elems) {
152 return static_cast<T*>(allocate(num_elems * sizeof(T), alignof(T)));
153 }
154 ///@}
155
156 /// @name Deallocate
157 /// Deallocate memory again in reverse order.
158 /// Use like this:
159 /// ```
160 /// auto state = arena.state();
161 /// auto ptr = arena.allocate(n);
162 /// if (/* I don't want that */) arena.deallocate(state);
163 /// ```
164 /// @warning Only use, if you really know what you are doing.
165 ///@{
166
167 /// Removes @p num_bytes again.
168 constexpr void deallocate(size_t num_bytes) noexcept {
169 assert(num_bytes <= index_);
170 index_ -= num_bytes;
171 }
172 [[nodiscard]] State state() const noexcept { return {pages_.size(), index_}; }
173
174 void deallocate(State state) noexcept {
175 assert(state.first > 0);
176 assert(state.first <= pages_.size());
177 while (pages_.size() > state.first)
178 pages_.pop_back();
179 assert(state.second <= pages_.back().size);
180 index_ = state.second;
181 }
182 ///@}
183
184 friend void swap(Arena& a1, Arena& a2) noexcept {
185 using std::swap;
186 // clang-format off
187 swap(a1.pages_, a2.pages_);
188 swap(a1.page_size_, a2.page_size_);
189 swap(a1.index_, a2.index_);
190 // clang-format on
191 }
192
193 /// Align @p i to @p a.
194 static constexpr size_t align(size_t i, size_t a) noexcept { return (i + (a - 1)) & ~(a - 1); }
195
196private:
197 constexpr Arena& align(size_t a) noexcept { return index_ = align(index_, a), *this; }
198
199 struct Page {
200 constexpr Page() noexcept = default;
201 Page(size_t size, size_t align)
202 : size(size)
203 , align(align)
204 , buffer((char*)::operator new[](size, std::align_val_t(align))) {}
205 constexpr ~Page() noexcept {
206 if (buffer) ::operator delete[](buffer, std::align_val_t(align));
207 }
208
209 const size_t size = 0;
210 const size_t align = 0;
211 char* buffer = nullptr;
212 };
213
214 std::list<Page> pages_;
215 size_t page_size_;
216 size_t index_ = 0;
217 MemoryResource resource_{*this};
218};
219
220} // namespace fe
A memory resource bridge in order to use this Arena for pmr containers.
Definition arena.h:28
bool do_is_equal(const std::pmr::memory_resource &other) const noexcept override
Definition arena.h:36
MemoryResource(Arena &arena) noexcept
Definition arena.h:30
void do_deallocate(void *, size_t, size_t) override
Definition arena.h:35
void * do_allocate(size_t bytes, size_t alignment) override
Definition arena.h:34
An arena pre-allocates so-called pages of size Arena::page_size_.
Definition arena.h:21
constexpr void deallocate(size_t num_bytes) noexcept
Removes num_bytes again.
Definition arena.h:168
void deallocate(State state) noexcept
Definition arena.h:174
Arena(const Arena &)=delete
Arena(Arena &&other) noexcept
Definition arena.h:95
constexpr void * allocate(size_t num_bytes, size_t align)
Get n bytes of fresh memory.
Definition arena.h:135
static constexpr size_t align(size_t i, size_t a) noexcept
Align i to a.
Definition arena.h:194
std::pmr::memory_resource * resource() noexcept
Definition arena.h:107
std::pair< size_t, size_t > State
Definition arena.h:86
Arena(size_t page_size=Default_Page_Size)
Definition arena.h:91
friend void swap(Arena &a1, Arena &a2) noexcept
Definition arena.h:184
Arena & operator=(Arena)=delete
std::unique_ptr< T, Deleter< T > > Ptr
Definition arena.h:85
const std::pmr::memory_resource * resource() const noexcept
Definition arena.h:108
constexpr T * allocate(size_t num_elems)
Definition arena.h:151
constexpr Ptr< T > mk(Args &&... args)
This is a std::unique_ptr that uses the Arena under the hood and whose Deleter will only invoke the d...
Definition arena.h:120
constexpr Allocator< T > allocator() noexcept
Create Allocator from Arena.
Definition arena.h:103
static constexpr size_t Default_Page_Size
1MB.
Definition arena.h:23
State state() const noexcept
Definition arena.h:172
Definition arena.h:13
An allocator in order to use this Arena for containers.
Definition arena.h:49
constexpr bool operator!=(const Allocator< U > &) const noexcept
Definition arena.h:70
constexpr bool operator==(const Allocator< U > &) const noexcept
All Arena::Allocators compare equal.
Definition arena.h:69
constexpr T * allocate(size_t num_elems)
Definition arena.h:60
constexpr void deallocate(T *, size_t) noexcept
Definition arena.h:62
constexpr Allocator(const Arena::Allocator< U > &allocator) noexcept
Definition arena.h:55
constexpr Allocator(Arena &arena) noexcept
Definition arena.h:57
constexpr Deleter() noexcept=default
constexpr void operator()(T *ptr) const noexcept(noexcept(ptr->~T()))
Definition arena.h:81