FE 0.13.1
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 // clang-format off
70 template<class U> constexpr bool operator==(const Allocator<U>&) const noexcept { return true; }
71 template<class U> constexpr bool operator!=(const Allocator<U>&) const noexcept { return false; }
72 // clang-format on
73
75 };
76
77 template<class T>
78 struct Deleter {
79 constexpr Deleter() noexcept = default;
80 template<class U, std::enable_if_t<std::is_convertible_v<U*, T*>, int> = 0>
81 constexpr Deleter(const Deleter<U>&) noexcept {}
82
83 constexpr void operator()(T* ptr) const noexcept(noexcept(ptr->~T())) { ptr->~T(); }
84 };
85
86 template<class T>
87 using Ptr = std::unique_ptr<T, Deleter<T>>;
88 using State = std::pair<size_t, size_t>;
89
90 /// @name Construction
91 ///@{
92 Arena(const Arena&) = delete;
93 explicit Arena(size_t page_size = Default_Page_Size)
94 : page_size_(page_size) {
95 pages_.emplace_back();
96 }
97 Arena(Arena&& other) noexcept
98 : Arena() {
99 swap(*this, other);
100 }
102
103 /// Create Allocator from Arena.
104 template<class T>
105 constexpr Allocator<T> allocator() noexcept {
106 return Allocator<T>(*this);
107 }
108
109 std::pmr::memory_resource* resource() noexcept { return &resource_; }
110 const std::pmr::memory_resource* resource() const noexcept { return &resource_; }
111
112 /// This is a [std::unique_ptr](https://en.cppreference.com/w/cpp/memory/unique_ptr)
113 /// that uses the Arena under the hood
114 /// and whose Deleter will *only* invoke the destructor but *not* `delete` anything;
115 /// memory will be released upon destruction of the Arena.
116 ///
117 /// Use like this:
118 /// ```
119 /// auto ptr = arena.mk<Foo>(a, b, c); // new Foo(a, b, c) placed into arena
120 /// ```
121 template<class T, class... Args>
122 constexpr Ptr<T> mk(Args&&... args) {
123 auto ptr = new (allocate<std::remove_const_t<T>>(1)) T(std::forward<Args>(args)...);
124 return Ptr<T>(ptr, Deleter<T>());
125 }
126 ///@}
127
128 /// @name Allocate
129 ///@{
130
131 /// Get @p n bytes of fresh memory.
132 /// @note When a fresh page is allocated, its base is only aligned to the @p align of the allocation that
133 /// triggered it. A *later* allocation in the same page that requests a *larger* alignment has its offset
134 /// aligned but may still be under-aligned relative to its request. This is a non-issue for the default
135 /// (max-aligned) page size and for arenas with uniform alignment; only tiny custom arenas mixing alignments
136 /// can hit it.
137 [[nodiscard]] constexpr void* allocate(size_t num_bytes, size_t align) {
138 if (num_bytes == 0) return nullptr;
139 assert(align != 0);
140
141 auto aligned_index = Arena::align(index_, align);
142 if (aligned_index + num_bytes > pages_.back().size) {
143 pages_.emplace_back(std::max(page_size_, num_bytes), align);
144 aligned_index = 0;
145 }
146
147 auto result = pages_.back().buffer + aligned_index;
148 index_ = aligned_index + num_bytes;
149 return result;
150 }
151
152 template<class T>
153 [[nodiscard]] constexpr T* allocate(size_t num_elems) {
154 return static_cast<T*>(allocate(num_elems * sizeof(T), alignof(T)));
155 }
156 ///@}
157
158 /// @name Deallocate
159 /// Deallocate memory again in reverse order.
160 /// Use like this:
161 /// ```
162 /// auto state = arena.state();
163 /// auto ptr = arena.allocate(n);
164 /// if (/* I don't want that */) arena.deallocate(state);
165 /// ```
166 /// @warning Only use, if you really know what you are doing.
167 ///@{
168
169 /// Removes @p num_bytes again.
170 constexpr void deallocate(size_t num_bytes) noexcept {
171 assert(num_bytes <= index_);
172 index_ -= num_bytes;
173 }
174 [[nodiscard]] State state() const noexcept { return {pages_.size(), index_}; }
175
176 void deallocate(State state) noexcept {
177 assert(state.first > 0);
178 assert(state.first <= pages_.size());
179 while (pages_.size() > state.first)
180 pages_.pop_back();
181 assert(state.second <= pages_.back().size);
182 index_ = state.second;
183 }
184 ///@}
185
186 friend void swap(Arena& a1, Arena& a2) noexcept {
187 using std::swap;
188 // clang-format off
189 swap(a1.pages_, a2.pages_);
190 swap(a1.page_size_, a2.page_size_);
191 swap(a1.index_, a2.index_);
192 // clang-format on
193 }
194
195 /// Align @p i to @p a.
196 static constexpr size_t align(size_t i, size_t a) noexcept { return (i + (a - 1)) & ~(a - 1); }
197
198private:
199 constexpr Arena& align(size_t a) noexcept { return index_ = align(index_, a), *this; }
200
201 struct Page {
202 constexpr Page() noexcept = default;
203 Page(size_t size, size_t align)
204 : size(size)
205 , align(align)
206 , buffer((char*)::operator new[](size, std::align_val_t(align))) {}
207 constexpr ~Page() noexcept {
208 if (buffer) ::operator delete[](buffer, std::align_val_t(align));
209 }
210
211 const size_t size = 0;
212 const size_t align = 0;
213 char* buffer = nullptr;
214 };
215
216 std::list<Page> pages_;
217 size_t page_size_;
218 size_t index_ = 0;
219 MemoryResource resource_{*this};
220};
221
222} // 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:170
void deallocate(State state) noexcept
Definition arena.h:176
Arena(const Arena &)=delete
Arena(Arena &&other) noexcept
Definition arena.h:97
constexpr void * allocate(size_t num_bytes, size_t align)
Get n bytes of fresh memory.
Definition arena.h:137
static constexpr size_t align(size_t i, size_t a) noexcept
Align i to a.
Definition arena.h:196
std::pmr::memory_resource * resource() noexcept
Definition arena.h:109
std::pair< size_t, size_t > State
Definition arena.h:88
Arena(size_t page_size=Default_Page_Size)
Definition arena.h:93
friend void swap(Arena &a1, Arena &a2) noexcept
Definition arena.h:186
Arena & operator=(Arena)=delete
std::unique_ptr< T, Deleter< T > > Ptr
Definition arena.h:87
const std::pmr::memory_resource * resource() const noexcept
Definition arena.h:110
constexpr T * allocate(size_t num_elems)
Definition arena.h:153
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:122
constexpr Allocator< T > allocator() noexcept
Create Allocator from Arena.
Definition arena.h:105
static constexpr size_t Default_Page_Size
1MB.
Definition arena.h:23
State state() const noexcept
Definition arena.h:174
Definition algo.h:17
Definition span.h:129
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:71
constexpr bool operator==(const Allocator< U > &) const noexcept
All Arena::Allocators compare equal.
Definition arena.h:70
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:83