FE 0.6.0
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 <new>
7
8#include "fe/assert.h"
9
10namespace fe {
11
12/// An arena pre-allocates so-called *pages* of size Arena::page_size_.
13/// You can use Arena::allocate to obtain memory from this.
14/// When a page runs out of memory, the next page will be (pre-)allocated.
15/// You cannot directly release memory obtained via this method.
16/// Instead, *all* memory acquired via this Arena will be released as soon as this Arena will be destroyed.
17/// As an exception, you can Arena::deallocate memory that just as been acquired.
18class Arena {
19public:
20 static constexpr size_t Default_Page_Size = 1024 * 1024; ///< 1MB.
21
22 /// An [allocator](https://en.cppreference.com/w/cpp/named_req/Allocator) in order to use this Arena for
23 /// [containers](https://en.cppreference.com/w/cpp/named_req/AllocatorAwareContainer).
24 /// Construct it via Arena::allocator.
25 template<class T>
26 struct Allocator {
27 using value_type = T;
28
29 Allocator() = delete;
30
31 template<class U>
32 constexpr Allocator(const Arena::Allocator<U>& allocator) noexcept
33 : arena(allocator.arena) {}
34 constexpr Allocator(Arena& arena) noexcept
35 : arena(arena) {}
36
37 [[nodiscard]] constexpr T* allocate(size_t num_elems) { return arena.allocate<T>(num_elems); }
38
39 constexpr void deallocate(T*, size_t) noexcept {}
40
41 template<class U>
42 constexpr bool operator==(const Allocator<U>& a) const noexcept {
43 return &arena == &a.arena;
44 }
45 template<class U>
46 constexpr bool operator!=(const Allocator<U>& a) const noexcept {
47 return &arena != &a.arena;
48 }
49
51 };
52
53 template<class T>
54 struct Deleter {
55 constexpr Deleter() noexcept = default;
56 template<class U, std::enable_if_t<std::is_convertible_v<U*, T*>, int> = 0>
57 constexpr Deleter(const Deleter<U>&) noexcept {}
58
59 constexpr void operator()(T* ptr) const noexcept(noexcept(ptr->~T())) { ptr->~T(); }
60 };
61
62 template<class T>
63 using Ptr = std::unique_ptr<T, Deleter<T>>;
64 using State = std::pair<size_t, size_t>;
65
66 /// @name Construction
67 ///@{
68 Arena(const Arena&) = delete;
69 Arena(size_t page_size = Default_Page_Size)
70 : page_size_(page_size) {
71 pages_.emplace_back(Page{});
72 }
73 Arena(Arena&& other) noexcept
74 : Arena() {
75 swap(*this, other);
76 }
77 Arena& operator=(Arena) = delete;
78
79 /// Create Allocator from Arena.
80 template<class T>
81 constexpr Allocator<T> allocator() noexcept {
82 return Allocator<T>(*this);
83 }
84
85 /// This is a [std::unique_ptr](https://en.cppreference.com/w/cpp/memory/unique_ptr)
86 /// that uses the Arena under the hood
87 /// and whose Deleter will *only* invoke the destructor but *not* `delete` anything;
88 /// memory will be released upon destruction of the Arena.
89 ///
90 /// Use like this:
91 /// ```
92 /// auto ptr = arena.mk<Foo>(a, b, c); // new Foo(a, b, c) placed into arena
93 /// ```
94 template<class T, class... Args>
95 constexpr Ptr<T> mk(Args&&... args) {
96 auto ptr = new (allocate<std::remove_const_t<T>>(1)) T(std::forward<Args>(args)...);
97 return Ptr<T>(ptr, Deleter<T>());
98 }
99 ///@}
100
101 /// @name Allocate
102 ///@{
103
104 /// Get @p n bytes of fresh memory.
105 [[nodiscard]] constexpr void* allocate(size_t num_bytes, size_t align) {
106 if (num_bytes == 0) return nullptr;
107
108 if (index_ + num_bytes > pages_.back().size) {
109 pages_.emplace_back(std::max(page_size_, num_bytes), align);
110 index_ = 0;
111 } else {
112 this->align(align);
113 }
114
115 auto result = pages_.back().buffer + index_;
116 index_ += num_bytes;
117 return result;
118 }
119
120 template<class T>
121 [[nodiscard]] constexpr T* allocate(size_t num_elems) {
122 return static_cast<T*>(allocate(num_elems * std::max(sizeof(T), alignof(T)), alignof(T)));
123 }
124 ///@}
125
126 /// @name Deallocate
127 /// Deallocate memory again in reverse order.
128 /// Use like this:
129 /// ```
130 /// auto state = arena.state();
131 /// auto ptr = arena.allocate(n);
132 /// if (/* I don't want that */) arena.deallocate(state);
133 /// ```
134 /// @warning Only use, if you really know what you are doing.
135 ///@{
136
137 /// Removes @p num_bytes again.
138 constexpr void deallocate(size_t num_bytes) noexcept { index_ -= num_bytes; }
139 State state() const noexcept { return {pages_.size(), index_}; }
140
141 void deallocate(State state) noexcept {
142 if (state.first == pages_.size())
143 index_ = state.second; // don't care otherwise
144 else
145 index_ = 0;
146 }
147 ///@}
148
149 friend void swap(Arena& a1, Arena& a2) noexcept {
150 using std::swap;
151 // clang-format off
152 swap(a1.pages_, a2.pages_);
153 swap(a1.page_size_, a2.page_size_);
154 swap(a1.index_, a2.index_);
155 // clang-format on
156 }
157
158 /// Align @p i to @p a.
159 static constexpr size_t align(size_t i, size_t a) noexcept { return (i + (a - 1)) & ~(a - 1); }
160
161private:
162 constexpr Arena& align(size_t a) noexcept { return index_ = align(index_, a), *this; }
163
164 struct Page {
165 constexpr Page() noexcept = default;
166 Page(size_t size, size_t align)
167 : size(size)
168 , align(align)
169 , buffer((char*)::operator new[](size, std::align_val_t(align))) {}
170 constexpr ~Page() noexcept {
171 if (buffer) ::operator delete[](buffer, std::align_val_t(align));
172 }
173
174 const size_t size = 0;
175 const size_t align = 0;
176 char* buffer = nullptr;
177 };
178
179 std::list<Page> pages_;
180 size_t page_size_;
181 size_t index_ = 0;
182};
183
184} // namespace fe
An arena pre-allocates so-called pages of size Arena::page_size_.
Definition arena.h:18
constexpr void deallocate(size_t num_bytes) noexcept
Removes num_bytes again.
Definition arena.h:138
void deallocate(State state) noexcept
Definition arena.h:141
Arena(const Arena &)=delete
Arena(Arena &&other) noexcept
Definition arena.h:73
constexpr void * allocate(size_t num_bytes, size_t align)
Get n bytes of fresh memory.
Definition arena.h:105
static constexpr size_t align(size_t i, size_t a) noexcept
Align i to a.
Definition arena.h:159
std::pair< size_t, size_t > State
Definition arena.h:64
Arena(size_t page_size=Default_Page_Size)
Definition arena.h:69
friend void swap(Arena &a1, Arena &a2) noexcept
Definition arena.h:149
Arena & operator=(Arena)=delete
std::unique_ptr< T, Deleter< T > > Ptr
Definition arena.h:63
constexpr T * allocate(size_t num_elems)
Definition arena.h:121
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:95
constexpr Allocator< T > allocator() noexcept
Create Allocator from Arena.
Definition arena.h:81
static constexpr size_t Default_Page_Size
1MB.
Definition arena.h:20
State state() const noexcept
Definition arena.h:139
Definition arena.h:10
An allocator in order to use this Arena for containers.
Definition arena.h:26
constexpr bool operator!=(const Allocator< U > &a) const noexcept
Definition arena.h:46
constexpr T * allocate(size_t num_elems)
Definition arena.h:37
constexpr bool operator==(const Allocator< U > &a) const noexcept
Definition arena.h:42
constexpr void deallocate(T *, size_t) noexcept
Definition arena.h:39
constexpr Allocator(const Arena::Allocator< U > &allocator) noexcept
Definition arena.h:32
constexpr Allocator(Arena &arena) noexcept
Definition arena.h:34
constexpr Deleter() noexcept=default
constexpr void operator()(T *ptr) const noexcept(noexcept(ptr->~T()))
Definition arena.h:59