TPDE
Loading...
Searching...
No Matches
BumpAllocator.hpp
1// SPDX-FileCopyrightText: 2025 Contributors to TPDE <https://tpde.org>
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
3#pragma once
4
5#include "tpde/util/AddressSanitizer.hpp"
6#include "tpde/util/SmallVector.hpp"
7#include "tpde/util/misc.hpp"
8
9#include <algorithm>
10#include <cstddef>
11#include <new>
12
13namespace tpde::util {
14
15template <size_t SlabSize = 4096>
16class BumpAllocator {
17 uintptr_t cur = 0;
18 uintptr_t end = 0;
19 SmallVector<void *> slabs;
20 SmallVector<std::pair<void *, size_t>, 0> large_slabs;
21
22 static constexpr auto SLAB_ALIGNMENT =
23 std::align_val_t(alignof(std::max_align_t));
24
25public:
26 BumpAllocator() = default;
27 ~BumpAllocator() noexcept {
28 deallocate_slabs();
29 deallocate_large_slabs();
30 }
31
32 BumpAllocator(const BumpAllocator &) = delete;
33
34 BumpAllocator &operator=(const BumpAllocator &) = delete;
35
36 /// Deallocate all but the first slab and reset current pointer to beginning.
37 void reset() noexcept {
38 if (!slabs.empty()) {
39 deallocate_slabs(1);
40 slabs.resize(1);
41 util::poison_memory_region(slabs[0], SlabSize);
42 cur = reinterpret_cast<uintptr_t>(slabs[0]);
43 end = cur + SlabSize;
44 }
45 deallocate_large_slabs();
46 }
47
48 void *allocate(size_t size, size_t align) noexcept {
49 assert(size > 0 && "cannot perform zero-sized allocation");
50 uintptr_t aligned = align_up(cur, align);
51 uintptr_t alloc_end = aligned + size;
52 if (alloc_end <= end) [[likely]] {
53 cur = alloc_end;
54 util::unpoison_memory_region(reinterpret_cast<void *>(aligned), size);
55 return reinterpret_cast<void *>(aligned);
56 }
57 return allocate_slab(size, align);
58 }
59
60 void *allocate_slab(size_t size, [[maybe_unused]] size_t align) noexcept {
61 assert(align <= alignof(std::max_align_t) && "alignment type unsupported");
62 if (size > SlabSize) [[unlikely]] {
63 void *slab = allocate_mem(size, SLAB_ALIGNMENT);
64 large_slabs.emplace_back(slab, size);
65 return slab;
66 }
67
68 void *slab = allocate_mem(SlabSize, SLAB_ALIGNMENT);
69 util::poison_memory_region(slab, SlabSize);
70 slabs.push_back(slab);
71 cur = reinterpret_cast<uintptr_t>(slab) + size;
72 end = reinterpret_cast<uintptr_t>(slab) + SlabSize;
73 util::unpoison_memory_region(slab, size);
74 return slab;
75 }
76
77private:
78 void *allocate_mem(size_t size, std::align_val_t align) noexcept {
79 return ::operator new(size, align, std::nothrow);
80 }
81
82 void deallocate_mem(void *ptr,
83 [[maybe_unused]] size_t size,
84 std::align_val_t align) noexcept {
85#ifdef __cpp_sized_deallocation
86 ::operator delete(ptr, size, align);
87#else
88 ::operator delete(ptr, align);
89#endif
90 }
91
92 void deallocate_slabs(size_t skip = 0) noexcept {
93 for (size_t i = skip; i < slabs.size(); ++i) {
94 deallocate_mem(slabs[i], SlabSize, SLAB_ALIGNMENT);
95 }
96 }
97
98 void deallocate_large_slabs() noexcept {
99 for (auto [slab, size] : large_slabs) {
100 deallocate_mem(slab, size, SLAB_ALIGNMENT);
101 }
102 }
103};
104
105template <typename T>
106struct BumpAllocatorDeleter {
107 constexpr BumpAllocatorDeleter() noexcept = default;
108 void operator()(T *ptr) const { std::destroy_at(ptr); }
109};
110
111template <typename T>
112using BumpAllocUniquePtr = std::unique_ptr<T, BumpAllocatorDeleter<T>>;
113
114} // namespace tpde::util
115
116template <size_t SlabSize>
117void *operator new(size_t s, tpde::util::BumpAllocator<SlabSize> &a) noexcept {
118 return a.allocate(s, std::min(s, alignof(std::max_align_t)));
119}
120
121template <size_t SlabSize>
122void *operator new[](size_t s,
123 tpde::util::BumpAllocator<SlabSize> &a) noexcept {
124 return a.allocate(s, std::min(s, alignof(std::max_align_t)));
125}
126
127template <size_t SlabSize>
128void operator delete(void *, tpde::util::BumpAllocator<SlabSize> &) noexcept {}
129
130template <size_t SlabSize>
131void operator delete[](void *, tpde::util::BumpAllocator<SlabSize> &) noexcept {
132}