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