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