FE 0.17.1
Fast, Effecient FrontEnds
Loading...
Searching...
No Matches
arena.h
Go to the documentation of this file.
1#pragma once
2
3#include <concepts>
4
5#include <algorithm>
6#include <list>
7#include <memory>
8#include <memory_resource>
9#include <new>
10#include <type_traits>
11#include <utility>
12
13#include "fe/assert.h"
14#include "fe/span.h"
15#include "fe/vla.h"
16
17namespace fe {
18
19/// An arena pre-allocates so-called *pages* of size Arena::page_size_.
20/// You can use Arena::allocate to obtain memory from this.
21/// When a page runs out of memory, the next page will be (pre-)allocated.
22/// You cannot directly release memory obtained via this method.
23/// Instead, *all* memory acquired via this Arena will be released as soon as this Arena will be destroyed.
24/// As an exception, you can Arena::deallocate memory that has just been acquired.
25class Arena {
26public:
27 static constexpr size_t Default_Page_Size = 1024 * 1024; ///< 1MB.
28
29 /// A [memory resource](https://en.cppreference.com/w/cpp/memory/memory_resource) bridge in order to use this
30 /// Arena for [pmr containers](https://en.cppreference.com/w/cpp/memory/polymorphic_allocator).
31 /// Access it via Arena::resource.
33 public:
34 explicit MemoryResource(Arena& arena) noexcept
35 : arena_(arena) {}
36
37 private:
38 void* do_allocate(size_t bytes, size_t alignment) override { return arena_.allocate(bytes, alignment); }
39 void do_deallocate(void*, size_t, size_t) override {}
40 bool do_is_equal(const std::pmr::memory_resource& other) const noexcept override {
41 if (this == &other) return true;
42 auto resource = dynamic_cast<const MemoryResource*>(&other);
43 return resource != nullptr && &arena_ == &resource->arena_;
44 }
45
46 Arena& arena_;
47 };
48
49 /// An [allocator](https://en.cppreference.com/w/cpp/named_req/Allocator) in order to use this Arena for
50 /// [containers](https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer).
51 /// Construct it via Arena::allocator.
52 template<class T>
53 struct Allocator {
54 using value_type = T;
55
56 Allocator() = delete;
57
58 template<class U>
59 constexpr Allocator(const Arena::Allocator<U>& allocator) noexcept
60 : arena(allocator.arena) {}
61 constexpr Allocator(Arena& arena) noexcept
62 : arena(arena) {}
63
64 [[nodiscard]] T* allocate(size_t num_elems) { return arena.allocate<T>(num_elems); }
65
66 constexpr void deallocate(T*, size_t) noexcept {}
67
68 /// All Arena::Allocator%s compare equal.
69 /// Allocator equality denotes deallocation-compatibility, and Arena::Allocator::deallocate is a no-op,
70 /// so storage from any instance can be "freed" through any other.
71 /// This also keeps allocator-aware container `swap` (e.g. in SymPool::swap) well-defined without requiring
72 /// `propagate_on_container_swap`, which we cannot enable here because `arena` is a non-rebindable reference.
73 // clang-format off
74 template<class U> constexpr bool operator==(const Allocator<U>&) const noexcept { return true; }
75 // clang-format on
76
78 };
79
80 template<class T>
81 struct Deleter {
82 constexpr Deleter() noexcept = default;
83 template<class U>
84 requires std::convertible_to<U*, T*> constexpr Deleter(const Deleter<U>&) noexcept {}
85
86 constexpr void operator()(T* ptr) const noexcept(noexcept(ptr->~T())) { ptr->~T(); }
87 };
88
89 /// A non-owning pointer into an Arena.
90 /// The Arena outlives it and releases everything at once, so nothing is ever destroyed
91 /// through it - unlike Arena::Ptr, which at least runs the destructor.
92 template<class T>
93 class Ref {
94 public:
95 constexpr Ref() noexcept = default;
96 constexpr Ref(std::nullptr_t) noexcept {}
97 constexpr explicit Ref(T* ptr) noexcept
98 : ptr_(ptr) {}
99 template<class U>
100 requires std::convertible_to<U*, T*> constexpr Ref(Ref<U> ref) noexcept
101 : ptr_(ref.get()) {}
102
103 constexpr T* get() const noexcept { return ptr_; }
104 constexpr T* operator->() const noexcept { return ptr_; }
105 constexpr T& operator*() const noexcept { return *ptr_; }
106 constexpr explicit operator bool() const noexcept { return ptr_ != nullptr; }
107 constexpr bool operator==(const Ref&) const noexcept = default;
108
109 private:
110 T* ptr_ = nullptr;
111 };
112
113 template<class T>
114 using Ptr = std::unique_ptr<T, Deleter<T>>;
115 using State = std::pair<size_t, size_t>;
116
117 /// @name Construction
118 ///@{
119 Arena(const Arena&) = delete;
120 explicit Arena(size_t page_size = Default_Page_Size)
121 : page_size_(page_size) {
122 pages_.emplace_back();
123 }
124 Arena(Arena&& other) noexcept
125 : Arena() {
126 swap(*this, other);
127 }
129
130 /// Create Allocator from Arena.
131 template<class T>
133 return Allocator<T>(*this);
134 }
135
136 std::pmr::memory_resource* resource() noexcept { return &resource_; }
137 const std::pmr::memory_resource* resource() const noexcept { return &resource_; }
138
139 /// This is a [std::unique_ptr](https://en.cppreference.com/w/cpp/memory/unique_ptr)
140 /// that uses the Arena under the hood
141 /// and whose Deleter will *only* invoke the destructor but *not* `delete` anything;
142 /// memory will be released upon destruction of the Arena.
143 ///
144 /// Use like this:
145 /// ```
146 /// auto ptr = arena.mk<Foo>(a, b, c); // new Foo(a, b, c) placed into arena
147 /// ```
148 template<class T, class... Args>
149 Ptr<T> mk(Args&&... args) {
150 return Ptr<T>(create<T>(std::forward<Args>(args)...), Deleter<T>());
151 }
152
153 /// Like Arena::mk, but yields a Ref: nothing will ever destroy the object.
154 /// Use like this:
155 /// ```
156 /// auto ref = arena.ref<Foo>(a, b, c); // new Foo(a, b, c) placed into arena, never destroyed
157 /// ```
158 template<class T, class... Args>
159 Ref<T> ref(Args&&... args) {
160 static_assert(std::is_trivially_destructible_v<std::remove_const_t<T>>,
161 "a Ref never destroys - use Arena::mk for a type with a destructor");
162 return Ref<T>(create<T>(std::forward<Args>(args)...));
163 }
164
165 /// An Arena-allocated copy of @p range.
166 template<std::ranges::input_range R, class T = std::ranges::range_value_t<R>>
167 [[nodiscard]] Span<T> copy(const R& range) {
168 static_assert(std::is_trivially_destructible_v<T>);
169 auto n = std::ranges::size(range);
170 auto ptr = allocate<T>(n);
171 std::uninitialized_copy(std::ranges::begin(range), std::ranges::end(range), ptr);
172 return {ptr, n};
173 }
174 ///@}
175
176 /// @name Allocate
177 ///@{
178
179 /// Get @p n bytes of fresh memory.
180 [[nodiscard]] void* allocate(size_t num_bytes, size_t align) {
181 if (num_bytes == 0) return nullptr;
182 assert(align != 0);
183
184 // A page's base is only aligned to whatever allocation created it.
185 auto base = reinterpret_cast<uintptr_t>(pages_.back().buffer);
186 auto aligned_index = Arena::align(base + index_, align) - base;
187 if (aligned_index + num_bytes > pages_.back().size) {
188 pages_.emplace_back(std::max(page_size_, num_bytes), align);
189 aligned_index = 0;
190 }
191
192 auto result = pages_.back().buffer + aligned_index;
193 index_ = aligned_index + num_bytes;
194 return result;
195 }
196
197 template<class T>
198 [[nodiscard]] T* allocate(size_t num_elems) {
199 return static_cast<T*>(allocate(num_elems * sizeof(T), alignof(T)));
200 }
201 ///@}
202
203 /// @name Deallocate
204 /// Deallocate memory again in reverse order.
205 /// Use like this:
206 /// ```
207 /// auto state = arena.state();
208 /// auto ptr = arena.allocate(n);
209 /// if (/* I don't want that */) arena.deallocate(state);
210 /// ```
211 /// @warning Only use, if you really know what you are doing.
212 ///@{
213
214 /// Removes @p num_bytes again.
215 void deallocate(size_t num_bytes) noexcept {
216 assert(num_bytes <= index_);
217 index_ -= num_bytes;
218 }
219 [[nodiscard]] State state() const noexcept { return {pages_.size(), index_}; }
220
221 void deallocate(State state) noexcept {
222 assert(state.first > 0);
223 assert(state.first <= pages_.size());
224 while (pages_.size() > state.first)
225 pages_.pop_back();
226 assert(state.second <= pages_.back().size);
227 index_ = state.second;
228 }
229 ///@}
230
231 friend void swap(Arena& a1, Arena& a2) noexcept {
232 using std::swap;
233 // clang-format off
234 swap(a1.pages_, a2.pages_);
235 swap(a1.page_size_, a2.page_size_);
236 swap(a1.index_, a2.index_);
237 // clang-format on
238 }
239
240 /// Align @p i to @p a.
241 static constexpr size_t align(size_t i, size_t a) noexcept { return (i + (a - 1)) & ~(a - 1); }
242
243private:
244 /// Placement-new%s a `T`, allocating and filling its fe::VLA%s, if it has any.
245 template<class T, class... Args>
246 std::remove_const_t<T>* create(Args&&... args) {
247 using U = std::remove_const_t<T>;
248 if constexpr (VLAed<U>) {
249 if constexpr (sizeof...(Args) < U::num_vlas()) {
250 static_assert(false, "one range per VLA, as the last arguments");
251 } else {
252 constexpr auto n = sizeof...(Args) - U::num_vlas();
253 return create_vla<U>(std::make_index_sequence<n>(), std::make_index_sequence<U::num_vlas()>(),
254 std::forward_as_tuple(std::forward<Args>(args)...));
255 }
256 } else {
257 static_assert(
258 !requires { typename U::VLA_Self; },
259 "this inherits the fe::VLA of a base class, so its arrays would sit at that base's "
260 "offset - only the most derived class may declare VLA_Types");
261 return new (allocate<U>(1)) U(std::forward<Args>(args)...);
262 }
263 }
264
265 template<class U, size_t... Hs, size_t... Ts, class Tuple>
266 U* create_vla(std::index_sequence<Hs...>, std::index_sequence<Ts...>, Tuple&& tuple) {
267 auto counts = std::array<size_t, sizeof...(Ts)>{std::ranges::size(std::get<sizeof...(Hs) + Ts>(tuple))...};
268 auto align = std::max(alignof(U), U::vla_align());
269 auto ptr = new (allocate(U::vla_bytes(counts), align)) U(std::get<Hs>(std::forward<Tuple>(tuple))...);
270 ptr->fill_vla(std::get<sizeof...(Hs) + Ts>(tuple)...);
271 return ptr;
272 }
273
274 struct Page {
275 constexpr Page() noexcept = default;
276 Page(size_t size, size_t align)
277 : size(size)
278 , align(align)
279 , buffer((char*)::operator new[](size, std::align_val_t(align))) {}
280 constexpr ~Page() noexcept {
281 if (buffer) ::operator delete[](buffer, std::align_val_t(align));
282 }
283
284 const size_t size = 0;
285 const size_t align = 0;
286 char* buffer = nullptr;
287 };
288
289 std::list<Page> pages_;
290 size_t page_size_;
291 size_t index_ = 0;
292 MemoryResource resource_{*this};
293};
294
295} // namespace fe
A memory resource bridge in order to use this Arena for pmr containers.
Definition arena.h:32
bool do_is_equal(const std::pmr::memory_resource &other) const noexcept override
Definition arena.h:40
MemoryResource(Arena &arena) noexcept
Definition arena.h:34
void do_deallocate(void *, size_t, size_t) override
Definition arena.h:39
void * do_allocate(size_t bytes, size_t alignment) override
Definition arena.h:38
A non-owning pointer into an Arena.
Definition arena.h:93
constexpr bool operator==(const Ref &) const noexcept=default
constexpr Ref() noexcept=default
constexpr Ref(T *ptr) noexcept
Definition arena.h:97
constexpr T * operator->() const noexcept
Definition arena.h:104
constexpr T & operator*() const noexcept
Definition arena.h:105
constexpr T * get() const noexcept
Definition arena.h:103
constexpr Ref(Ref< U > ref) noexcept
Definition arena.h:100
void * allocate(size_t num_bytes, size_t align)
Get n bytes of fresh memory.
Definition arena.h:180
void deallocate(State state) noexcept
Definition arena.h:221
Ref< T > ref(Args &&... args)
Like Arena::mk, but yields a Ref: nothing will ever destroy the object.
Definition arena.h:159
Arena(const Arena &)=delete
Arena(Arena &&other) noexcept
Definition arena.h:124
Span< T > copy(const R &range)
An Arena-allocated copy of range.
Definition arena.h:167
static constexpr size_t align(size_t i, size_t a) noexcept
Align i to a.
Definition arena.h:241
void deallocate(size_t num_bytes) noexcept
Removes num_bytes again.
Definition arena.h:215
std::pmr::memory_resource * resource() noexcept
Definition arena.h:136
std::pair< size_t, size_t > State
Definition arena.h:115
Arena(size_t page_size=Default_Page_Size)
Definition arena.h:120
T * allocate(size_t num_elems)
Definition arena.h:198
Allocator< T > allocator() noexcept
Create Allocator from Arena.
Definition arena.h:132
friend void swap(Arena &a1, Arena &a2) noexcept
Definition arena.h:231
Arena & operator=(Arena)=delete
std::unique_ptr< T, Deleter< T > > Ptr
Definition arena.h:114
const std::pmr::memory_resource * resource() const noexcept
Definition arena.h:137
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:149
static constexpr size_t Default_Page_Size
1MB.
Definition arena.h:27
State state() const noexcept
Definition arena.h:219
This is a thin wrapper for std::span<T, N> with the following additional features:
Definition span.h:33
Does T carry VLAs?
Definition vla.h:124
Definition algo.h:17
Definition span.h:150
An allocator in order to use this Arena for containers.
Definition arena.h:53
constexpr bool operator==(const Allocator< U > &) const noexcept
All Arena::Allocators compare equal.
Definition arena.h:74
T * allocate(size_t num_elems)
Definition arena.h:64
constexpr void deallocate(T *, size_t) noexcept
Definition arena.h:66
constexpr Allocator(const Arena::Allocator< U > &allocator) noexcept
Definition arena.h:59
constexpr Allocator(Arena &arena) noexcept
Definition arena.h:61
constexpr Deleter() noexcept=default
constexpr void operator()(T *ptr) const noexcept(noexcept(ptr->~T()))
Definition arena.h:86