1 #pragma once
2
3 #include <petsc/private/petscimpl.h> // PetscAssertPointer()
4 #include <petsc/private/mempoison.h> // PetscPoison/UnpoisonMemoryRegion()
5
6 #include <petsc/private/cpp/register_finalize.hpp>
7 #include <petsc/private/cpp/utility.hpp> // util::exchange(), std::pair
8 #include <petsc/private/cpp/unordered_map.hpp>
9 #include <petsc/private/cpp/memory.hpp> // std::align(), std::unique_ptr
10
11 #include <cstddef> // std::size_t
12 #include <vector> // std::take_a_wild_guess
13
14 namespace Petsc
15 {
16
17 namespace memory
18 {
19
20 enum class align_val_t : std::size_t {
21 };
22
23 } // namespace memory
24
25 } // namespace Petsc
26
27 namespace std
28 {
29
30 template <>
31 struct hash<::Petsc::memory::align_val_t> {
32 #if PETSC_CPP_VERSION < 17
33 using argument_type = ::Petsc::memory::align_val_t;
34 using result_type = size_t;
35 #endif
36
operator ()std::hash37 constexpr size_t operator()(const ::Petsc::memory::align_val_t &x) const noexcept { return static_cast<size_t>(x); }
38 };
39
40 } // namespace std
41
42 namespace Petsc
43 {
44
45 namespace memory
46 {
47
48 // ==========================================================================================
49 // PoolAllocator
50 //
51 // A general purpose memory pool. It internally maintains an array of allocated memory regions
52 // and their sizes. Currently does not prune the allocated memory in any way.
53 // ==========================================================================================
54
55 class PoolAllocator : public RegisterFinalizeable<PoolAllocator> {
56 using base_type = RegisterFinalizeable<PoolAllocator>;
57 friend base_type;
58
59 public:
60 // define the size and alignment as separate types, this helps to disambiguate them at the
61 // callsite!
62 using size_type = std::size_t;
63 using align_type = align_val_t;
64 using pool_type = std::vector<std::pair<align_type, UnorderedMap<size_type, std::vector<void *>>>>;
65
66 PoolAllocator() noexcept = default;
67 PoolAllocator(PoolAllocator &&) noexcept = default;
68 PoolAllocator &operator=(PoolAllocator &&) noexcept = default;
69
70 // the pool carries raw memory and is not copyable
71 PoolAllocator(const PoolAllocator &) = delete;
72 PoolAllocator &operator=(const PoolAllocator &) = delete;
73
74 ~PoolAllocator() noexcept;
75
76 PetscErrorCode try_allocate(void **, size_type, align_type, bool *) noexcept;
77 PetscErrorCode allocate(void **, size_type, align_type, bool * = nullptr) noexcept;
78 PetscErrorCode deallocate(void **, size_type, align_type) noexcept;
79
80 static PetscErrorCode get_attributes(const void *, size_type *, align_type *) noexcept;
81 static PetscErrorCode unpoison(const void *, size_type *) noexcept;
82 static PetscErrorCode repoison(const void *, size_type) noexcept;
83
84 template <typename T>
85 PetscErrorCode for_each(T &&) noexcept;
86
87 class LockGuard {
88 public:
89 LockGuard() = delete;
90
91 private:
92 friend class PoolAllocator;
93
LockGuard(PoolAllocator * pool)94 explicit LockGuard(PoolAllocator *pool) noexcept : pool_{pool} { ++pool_->locked_; }
95
96 struct PoolUnlocker {
operator ()Petsc::memory::PoolAllocator::LockGuard::PoolUnlocker97 void operator()(PoolAllocator *pool) const noexcept { --pool->locked_; }
98 };
99
100 std::unique_ptr<PoolAllocator, PoolUnlocker> pool_{};
101 };
102
103 LockGuard lock_guard() noexcept;
104
105 private:
106 pool_type pool_;
107 int locked_ = 0;
108
109 struct AllocationHeader;
110
111 PETSC_NODISCARD static constexpr size_type total_size_(size_type, align_type) noexcept;
112
113 static PetscErrorCode valid_alignment_(align_type) noexcept;
114 static PetscErrorCode extract_header_(void *, AllocationHeader **, bool = true) noexcept;
115 static PetscErrorCode allocate_ptr_(size_type, align_type, void **) noexcept;
116
117 static PetscErrorCode delete_ptr_(void **) noexcept;
118
pool()119 PETSC_NODISCARD pool_type &pool() noexcept { return pool_; }
pool() const120 PETSC_NODISCARD const pool_type &pool() const noexcept { return pool_; }
121
122 PETSC_NODISCARD typename pool_type::iterator find_align_(align_type) noexcept;
123 PETSC_NODISCARD typename pool_type::const_iterator find_align_(align_type) const noexcept;
124
125 public:
126 PetscErrorCode clear_(size_type * = nullptr) noexcept;
127 PetscErrorCode finalize_() noexcept;
128 };
129
130 /*
131 PoolAllocator::for_each - Perform an action on every allocation in the currently in the pool
132
133 Input Parameter:
134 . callable - The callable used to perform the action, should accept a void *&.
135
136 Notes:
137 The callable may delete the pointer, but MUST set the pointer to nullptr in this case. If the
138 pointer is deleted, it is removed from the pool.
139
140 The pointers are walked in LIFO order from most recent first to least recent deallocation
141 last.
142 */
143 template <typename T>
for_each(T && callable)144 inline PetscErrorCode PoolAllocator::for_each(T &&callable) noexcept
145 {
146 PetscFunctionBegin;
147 for (auto &&align_it : pool()) {
148 for (auto &&size_it : align_it.second) {
149 auto &&ptr_stack = size_it.second;
150
151 for (auto it = ptr_stack.rbegin(); it != ptr_stack.rend();) {
152 size_type size;
153 auto &&ptr = *it;
154
155 PetscCall(unpoison(ptr, &size));
156 PetscCall(callable(ptr));
157 if (ptr) {
158 PetscCall(repoison(ptr, size));
159 ++it;
160 } else {
161 // the callable has deleted the pointer, so we should remove it. it is a reverse
162 // iterator though so we:
163 //
164 // 1. std::next(it).base() -> convert to forward iterator
165 // 2. it = decltype(it){...} -> convert returned iterator back to reverse iterator
166 PetscCallCXX(it = decltype(it){ptr_stack.erase(std::next(it).base())});
167 }
168 }
169 }
170 }
171 PetscFunctionReturn(PETSC_SUCCESS);
172 }
173
174 // ==========================================================================================
175 // PoolAllocated
176 //
177 // A simple mixin to enable allocating a class from a Pool. That is, it provides a static
178 // operator new() and operator delete() member functions such that
179 //
180 // auto foo = new ClassDerivedFromPoolAllocated(args...);
181 //
182 // Allocates the new object from a pool and
183 //
184 // delete foo;
185 //
186 // Returns the memory to the pool.
187 // ==========================================================================================
188
189 class PoolAllocated {
190 public:
191 using allocator_type = PoolAllocator;
192 using size_type = typename allocator_type::size_type;
193 using align_type = typename allocator_type::align_type;
194
195 PETSC_NODISCARD static void *operator new(size_type) noexcept;
196 static void operator delete(void *) noexcept;
197
198 #if PETSC_CPP_VERSION >= 17
199 PETSC_NODISCARD static void *operator new(size_type, std::align_val_t) noexcept;
200 static void operator delete(void *, std::align_val_t) noexcept;
201 #endif
202
203 protected:
204 PETSC_NODISCARD static allocator_type &pool() noexcept;
205
206 private:
207 static allocator_type pool_;
208 };
209
210 } // namespace memory
211
212 // ==========================================================================================
213 // ConstructorInterface
214 //
215 // Provides a common interface for constructors and destructors for use with the object
216 // pools. Specifically, each interface may provide the following functions:
217 //
218 // construct_(T *):
219 // Given a pointer to an allocated object, construct the object in that memory. This may
220 // allocate memory *within* the object, but must not reallocate the pointer itself. Defaults to
221 // placement new.
222 //
223 // destroy_(T *):
224 // Given a pointer to an object, destroy the object completely. This should clean up the
225 // pointed-to object but not deallocate the pointer itself. Any resources not cleaned up by
226 // this function will be leaked. Defaults to calling the objects destructor.
227 //
228 // invalidate_(T *):
229 // Similar to destroy_(), but you are allowed to leave resources behind in the
230 // object. Essentially puts the object into a "zombie" state to be reused later. Defaults to
231 // calling destroy_().
232 //
233 // reset_():
234 // Revives a previously invalidated object. Should restore the object to a "factory new" state
235 // as-if it had just been newly allocated, but may take advantage of the fact that some
236 // resources need not be re-aquired from scratch. Defaults to calling construct_().
237 // ==========================================================================================
238
239 template <typename T, typename Derived>
240 class ConstructorInterface {
241 public:
242 using value_type = T;
243
244 template <typename... Args>
construct(value_type * ptr,Args &&...args) const245 PetscErrorCode construct(value_type *ptr, Args &&...args) const noexcept
246 {
247 PetscFunctionBegin;
248 PetscCall(this->underlying().construct_(ptr, std::forward<Args>(args)...));
249 PetscFunctionReturn(PETSC_SUCCESS);
250 }
251
destroy(value_type * ptr) const252 PetscErrorCode destroy(value_type *ptr) const noexcept
253 {
254 const Derived &underlying = this->underlying();
255
256 PetscFunctionBegin;
257 PetscCall(underlying.destroy_(ptr));
258 PetscFunctionReturn(PETSC_SUCCESS);
259 }
260
261 template <typename... Args>
reset(value_type * val,Args &&...args) const262 PetscErrorCode reset(value_type *val, Args &&...args) const noexcept
263 {
264 const Derived &underlying = this->underlying();
265
266 PetscFunctionBegin;
267 PetscCall(underlying.reset_(val, std::forward<Args>(args)...));
268 PetscFunctionReturn(PETSC_SUCCESS);
269 }
270
invalidate(value_type * ptr) const271 PetscErrorCode invalidate(value_type *ptr) const noexcept
272 {
273 const Derived &underlying = this->underlying();
274
275 PetscFunctionBegin;
276 PetscCall(underlying.invalidate_(ptr));
277 PetscFunctionReturn(PETSC_SUCCESS);
278 }
279
280 protected:
281 template <typename... Args>
construct_(value_type * ptr,Args &&...args)282 static PetscErrorCode construct_(value_type *ptr, Args &&...args) noexcept
283 {
284 PetscFunctionBegin;
285 PetscAssertPointer(ptr, 1);
286 PetscCallCXX(util::construct_at(ptr, std::forward<Args>(args)...));
287 PetscFunctionReturn(PETSC_SUCCESS);
288 }
289
destroy_(value_type * ptr)290 static PetscErrorCode destroy_(value_type *ptr) noexcept
291 {
292 PetscFunctionBegin;
293 if (ptr) {
294 PetscAssertPointer(ptr, 1);
295 PetscCallCXX(util::destroy_at(ptr));
296 }
297 PetscFunctionReturn(PETSC_SUCCESS);
298 }
299
300 template <typename... Args>
reset_(value_type * val,Args &&...args) const301 PetscErrorCode reset_(value_type *val, Args &&...args) const noexcept
302 {
303 PetscFunctionBegin;
304 PetscCall(this->underlying().construct(val, std::forward<Args>(args)...));
305 PetscFunctionReturn(PETSC_SUCCESS);
306 }
307
invalidate_(value_type * ptr) const308 PetscErrorCode invalidate_(value_type *ptr) const noexcept
309 {
310 PetscFunctionBegin;
311 PetscCall(this->underlying().destroy(ptr));
312 PetscFunctionReturn(PETSC_SUCCESS);
313 }
314
underlying()315 PETSC_NODISCARD Derived &underlying() noexcept { return static_cast<Derived &>(*this); }
underlying() const316 PETSC_NODISCARD const Derived &underlying() const noexcept { return static_cast<const Derived &>(*this); }
317 };
318
319 template <typename T>
320 struct DefaultConstructor : ConstructorInterface<T, DefaultConstructor<T>> { };
321
322 // ==========================================================================================
323 // ObjectPool
324 //
325 // multi-purpose basic object-pool, useful for recirculating old "destroyed" objects. Registers
326 // all objects to be cleaned up on PetscFinalize()
327 // ==========================================================================================
328
329 template <typename T, typename Constructor = DefaultConstructor<T>>
330 class ObjectPool;
331
332 template <typename T, typename Constructor>
333 class ObjectPool : public RegisterFinalizeable<ObjectPool<T, Constructor>> {
334 using base_type = RegisterFinalizeable<ObjectPool<T, Constructor>>;
335
336 public:
337 using value_type = T;
338 using constructor_type = Constructor;
339 using allocator_type = memory::PoolAllocator;
340
341 ObjectPool() = default;
342 ObjectPool(ObjectPool &&) noexcept = default;
343 ObjectPool &operator=(ObjectPool &&) noexcept = default;
344
345 ~ObjectPool() noexcept;
346
347 template <typename... Args>
348 PetscErrorCode allocate(value_type **, Args &&...) noexcept;
349 PetscErrorCode deallocate(value_type **) noexcept;
350
constructor()351 PETSC_NODISCARD constructor_type &constructor() noexcept { return pair_.first(); }
constructor() const352 PETSC_NODISCARD const constructor_type &constructor() const noexcept { return pair_.first(); }
allocator()353 PETSC_NODISCARD allocator_type &allocator() noexcept { return pair_.second(); }
allocator() const354 PETSC_NODISCARD const allocator_type &allocator() const noexcept { return pair_.second(); }
355
356 private:
357 util::compressed_pair<constructor_type, allocator_type> pair_{};
358
359 using align_type = typename allocator_type::align_type;
360 using size_type = typename allocator_type::size_type;
361
362 friend base_type;
363 PetscErrorCode finalize_() noexcept;
364 };
365
366 // ==========================================================================================
367 // ObjectPool -- Private API
368 // ==========================================================================================
369
370 template <typename T, typename Constructor>
finalize_()371 inline PetscErrorCode ObjectPool<T, Constructor>::finalize_() noexcept
372 {
373 PetscFunctionBegin;
374 {
375 auto _ = this->allocator().lock_guard();
376
377 // clang-format off
378 PetscCall(
379 this->allocator().for_each([&, this](void *ptr)
380 {
381 PetscFunctionBegin;
382 PetscCall(this->constructor().destroy(static_cast<value_type *>(ptr)));
383 PetscFunctionReturn(PETSC_SUCCESS);
384 })
385 );
386 // clang-format on
387 }
388 PetscCall(this->allocator().clear_());
389 PetscFunctionReturn(PETSC_SUCCESS);
390 }
391
392 // ==========================================================================================
393 // ObjectPool -- Public API
394 // ==========================================================================================
395
396 template <typename T, typename Constructor>
~ObjectPool()397 inline ObjectPool<T, Constructor>::~ObjectPool() noexcept
398 {
399 PetscFunctionBegin;
400 PetscCallAbort(PETSC_COMM_SELF, this->finalize());
401 PetscFunctionReturnVoid();
402 }
403
404 /*
405 ObjectPool::allocate - Allocate an object from the pool
406
407 Input Parameters:
408 . args... - The arguments to be passed to the constructor for the object
409
410 Output Parameter:
411 . obj - The pointer to the constructed object
412
413 Notes:
414 The user must deallocate the object using deallocate() and cannot free it themselves.
415 */
416 template <typename T, typename Constructor>
417 template <typename... Args>
allocate(value_type ** obj,Args &&...args)418 inline PetscErrorCode ObjectPool<T, Constructor>::allocate(value_type **obj, Args &&...args) noexcept
419 {
420 auto allocated_from_pool = true;
421 void *mem = nullptr;
422
423 PetscFunctionBegin;
424 PetscAssertPointer(obj, 1);
425 // order is deliberate! We register our finalizer before the pool does so since we need to
426 // destroy the objects within it before it deletes their memory.
427 PetscCall(this->allocator().allocate(&mem, sizeof(value_type), static_cast<align_type>(alignof(value_type)), &allocated_from_pool));
428 PetscCall(this->register_finalize());
429 *obj = static_cast<value_type *>(mem);
430 if (allocated_from_pool) {
431 // if the allocation reused memory from the pool then this indicates the object is resettable.
432 PetscCall(this->constructor().reset(*obj, std::forward<Args>(args)...));
433 } else {
434 PetscCall(this->constructor().construct(*obj, std::forward<Args>(args)...));
435 }
436 PetscFunctionReturn(PETSC_SUCCESS);
437 }
438
439 /*
440 ObjectPool::deallocate - Return an object to the pool
441
442 Input Parameter:
443 . obj - The pointer to the object to return
444
445 Notes:
446 Sets obj to nullptr on return. obj must have been allocated by the pool in order to be
447 deallocated this way.
448 */
449 template <typename T, typename Constructor>
deallocate(value_type ** obj)450 inline PetscErrorCode ObjectPool<T, Constructor>::deallocate(value_type **obj) noexcept
451 {
452 PetscFunctionBegin;
453 PetscAssertPointer(obj, 1);
454 PetscCall(this->register_finalize());
455 PetscCall(this->constructor().invalidate(*obj));
456 PetscCall(this->allocator().deallocate(reinterpret_cast<void **>(obj), sizeof(value_type), static_cast<align_type>(alignof(value_type))));
457 PetscFunctionReturn(PETSC_SUCCESS);
458 }
459
460 } // namespace Petsc
461