xref: /petsc/src/sys/objects/cxx/memory/object_pool.cxx (revision a336c15037c72f93cd561f5a5e11e93175f2efd9)
1 #include <petsc/private/cpp/object_pool.hpp>
2 
3 #include <new>       // std::nothrow
4 #include <limits>    // std::numeric_limits
5 #include <algorithm> // std::lower_bound()
6 #include <cstdio>    // std::printf
7 
8 namespace Petsc
9 {
10 
11 namespace memory
12 {
13 
14 // ==========================================================================================
15 // PoolAllocator -- Private API -- AllocationHeader
16 //
17 // The header inserted for each allocated pointer. It stores:
18 //
19 // - size - the size (in bytes) of the allocation. This includes ONLY the size as requested by
20 //          the user. i.e. if a user requested 10 bytes but alignment, padding and header
21 //          overhead results in the actual allocation being 30 bytes, then size = 10.
22 // - align - the alignment (in bytes) of the allocated pointer.
23 // ==========================================================================================
24 
25 struct PoolAllocator::AllocationHeader {
26   constexpr AllocationHeader(size_type, align_type) noexcept;
27 
28   PETSC_NODISCARD static constexpr align_type max_alignment() noexcept;
29   PETSC_NODISCARD static constexpr size_type  header_size() noexcept;
30   PETSC_NODISCARD static constexpr size_type  buffer_zone_size() noexcept;
31 
32   size_type  size;
33   align_type align;
34 };
35 
36 // ==========================================================================================
37 // PoolAllocator -- Private API -- AllocationHeader -- Public API
38 // ==========================================================================================
39 
40 /*
41   PoolAllocator::AllocationHeader::AllocationHeader
42 */
43 constexpr PoolAllocator::AllocationHeader::AllocationHeader(size_type size, align_type align) noexcept : size{size}, align{align} { }
44 
45 /*
46   PoolAllocator::AllocationHeader::max_alignment
47 
48   Returns the maximum supported alignment (in bytes) of the memory pool.
49 */
50 constexpr PoolAllocator::align_type PoolAllocator::AllocationHeader::max_alignment() noexcept
51 {
52 #if PETSC_CPP_VERSION >= 14
53   constexpr auto max_align = std::numeric_limits<unsigned char>::max() + 1;
54   static_assert(!(max_align & (max_align - 1)), "Maximum alignment must be a power of 2");
55   return static_cast<align_type>(max_align);
56 #else
57   return static_cast<align_type>(std::numeric_limits<unsigned char>::max() + 1);
58 #endif
59 }
60 
61 /*
62   PoolAllocator::AllocationHeader::buffer_zone_size
63 
64   Notes:
65   Returns the number of bytes between the allocated pointer and the location where the
66   alignment diff is stored. i.e. size of the buffer zone + 1.
67 
68   If ASAN is enabled then this buffer zone is poisoned, so any overrun on part of the user is
69   potentially caught. The larger the buffer zone, the more likely that the user lands in
70   poisoned memory. Turned off in optimized builds.
71 */
72 constexpr PoolAllocator::size_type PoolAllocator::AllocationHeader::buffer_zone_size() noexcept
73 {
74   return (PetscDefined(USE_DEBUG) ? 32 : 0) + 1;
75 }
76 
77 /*
78   PoolAllocator::AllocationHeader::header_size
79 
80   Notes:
81   Returns the minimum size of the allocation header in bytes. Essentially (literally) the size
82   of the header object + size of the buffer zone. Does not include padding due to alignment
83   offset itself though.
84 */
85 constexpr PoolAllocator::size_type PoolAllocator::AllocationHeader::header_size() noexcept
86 {
87   return sizeof(AllocationHeader) + buffer_zone_size();
88 }
89 
90 /*
91   PoolAllocator::total_size_ - Compute the maximum total size for an allocation
92 
93   Input Parameters:
94 + size  - the size (in bytes) requested by the user
95 - align - the alignment (in bytes) requested by the user
96 
97   Notes:
98   Returns a size so that std::malloc(total_size_(size, align)) allocates enough memory to store
99   the allocation header, buffer zone, alignment offset and requested size including any
100   potential changes due to alignment.
101 */
102 constexpr PoolAllocator::size_type PoolAllocator::total_size_(size_type size, align_type align) noexcept
103 {
104   // align - 1 because aligning the pointer up by align bytes just returns it to its original
105   // alignment, so we can save the byte
106   return AllocationHeader::header_size() + size + util::to_underlying(align) - 1;
107 }
108 
109 /*
110   PoolAllocator::delete_ptr_ - deletes a pointer and the corresponding header
111 
112   Input Parameter:
113 + in_ptr - the pointer to user-memory which to delete
114 
115   Notes:
116   in_ptr may point to nullptr (in which case this does nothing), otherwise it must have been
117   allocated by the pool.
118 
119   in_ptr may point to poisoned memory, this routine will remove any poisoning before
120   deallocating.
121 
122   in_ptr is set to nullptr on return.
123 */
124 PetscErrorCode PoolAllocator::delete_ptr_(void **in_ptr) noexcept
125 {
126   PetscFunctionBegin;
127   PetscAssertPointer(in_ptr, 1);
128   if (const auto ptr = util::exchange(*in_ptr, nullptr)) {
129     AllocationHeader *header = nullptr;
130 
131     PetscCall(extract_header_(ptr, &header, false));
132     // must unpoison the header itself before we can access the members
133     PetscCall(PetscUnpoisonMemoryRegion(header, sizeof(*header)));
134     PetscCall(PetscUnpoisonMemoryRegion(header, total_size_(header->size, header->align)));
135     PetscCallCXX(::delete[] reinterpret_cast<unsigned char *>(header));
136   }
137   PetscFunctionReturn(PETSC_SUCCESS);
138 }
139 
140 /*
141   PoolAllocator::find_align_ - Return an iterator to the memory pool for a particular alignment
142 
143   Input Parameter:
144 . align - The alignment (in bytes) to search for
145 
146   Notes:
147   returns pool().end() if alignment not found.
148 */
149 PoolAllocator::pool_type::iterator PoolAllocator::find_align_(align_type align) noexcept
150 {
151   return std::lower_bound(this->pool().begin(), this->pool().end(), align, [](const pool_type::value_type &pair, const align_type &align) { return pair.first < align; });
152 }
153 
154 PoolAllocator::pool_type::const_iterator PoolAllocator::find_align_(align_type align) const noexcept
155 {
156   return std::lower_bound(this->pool().begin(), this->pool().end(), align, [](const pool_type::value_type &pair, const align_type &align) { return pair.first < align; });
157 }
158 
159 /*
160   PoolAllocator::clear_ - Clear the memory pool
161 
162   Output Parameter:
163 . remaining - The number of remaining allocations in the pool, nullptr if not needed
164 
165   Notes:
166   This will clean up the pool, deallocating any memory checked back into the pool. This does
167   not delete allocations that were allocated by the pool but not yet returned to it.
168 
169   remaining is useful in determining if any allocations were "leaked". Suppose an object
170   internally manages a resource and uses the pool to allocate said resource. On destruction the
171   object expects to have the pool be empty, i.e. have remaining = 0. This implies all resources
172   were returned to the pool.
173 */
174 PetscErrorCode PoolAllocator::clear_(size_type *remaining) noexcept
175 {
176   size_type remain = 0;
177 
178   PetscFunctionBegin;
179   if (remaining) PetscAssertPointer(remaining, 1);
180   // clang-format off
181   PetscCall(
182     this->for_each([&](void *&ptr)
183     {
184       PetscFunctionBegin;
185       ++remain;
186       PetscCall(delete_ptr_(&ptr));
187       PetscFunctionReturn(PETSC_SUCCESS);
188     })
189   );
190   // clang-format on
191   PetscCallCXX(this->pool().clear());
192   if (remaining) *remaining = remain;
193   PetscFunctionReturn(PETSC_SUCCESS);
194 }
195 
196 /*
197   PoolAllocator::finalize_ - Routine automatically called during PetscFinalize()
198 
199   Notes:
200   This will go through and reap any regions that it owns in the underlying container. If it
201   owns all regions, it resets the container.
202 
203   There currently is no way to ensure that objects remaining in the pool aren't leaked, since
204   this routine cannot actually re-register the pool for finalizations without causing an
205   infinite loop...
206 
207   Thus it is up to the owned object to ensure that the pool is properly finalized.
208 */
209 PetscErrorCode PoolAllocator::finalize_() noexcept
210 {
211   PetscFunctionBegin;
212   PetscCall(clear_());
213   PetscFunctionReturn(PETSC_SUCCESS);
214 }
215 
216 // a quick sanity check that the alignment is valid, does nothing in optimized builds
217 PetscErrorCode PoolAllocator::valid_alignment_(align_type in_align) noexcept
218 {
219   constexpr auto max_align = util::to_underlying(AllocationHeader::max_alignment());
220   const auto     align     = util::to_underlying(in_align);
221 
222   PetscFunctionBegin;
223   PetscAssert((align > 0) && (align <= max_align), PETSC_COMM_SELF, PETSC_ERR_MEMC, "Alignment %zu must be (0, %zu]", align, max_align);
224   PetscAssert(!(align & (align - 1)), PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Alignment %zu must be a power of 2", align);
225   PetscFunctionReturn(PETSC_SUCCESS);
226 }
227 
228 /*
229   PoolAllocator::extract_header_ - Extract the header pointer from the aligned pointer
230 
231   Input Parameters:
232 + user_ptr     - the pointer to the aligned user memory
233 - check_in_ptr - whether to test the validity of aligned_ptr
234 
235   Output Parameter:
236 . header - the pointer to the header
237 
238   Notes:
239   Setting check_in_ptr to false disabled the PetscAssertPointer() check in the function
240   preamble. This allows the method to be used even if the aligned_ptr is poisoned (for example
241   when extracting the header from a pointer that is checked into the pool).
242 
243   aligned_ptr must have been allocated by the pool.
244 
245   The returned header is still poisoned, the user is responsible for unpoisoning it.
246 */
247 PetscErrorCode PoolAllocator::extract_header_(void *user_ptr, AllocationHeader **header, bool check_in_ptr) noexcept
248 {
249   PetscFunctionBegin;
250   if (check_in_ptr) PetscAssertPointer(user_ptr, 1);
251   PetscAssertPointer(header, 2);
252   {
253     //       AllocationHeader::alignment_offset() (at least 1)
254     //                        |
255     // header                 |  user_ptr/aligned_ptr
256     // |                      |          |
257     // v                  v~~~~~~~~~~~~~~v
258     // A==============B===C==============D--------------------- ...
259     // ^~~~~~~~~~~~~~~^^~~^              ^~~~~~~~~~~~~~~~~~~~~~ ...
260     //       |             \______             user memory
261     //       |                    buffer_zone_end
262     // sizeof(AllocationHeader)
263     //
264     // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
265     //                poisoned
266     //
267     const auto aligned_ptr     = reinterpret_cast<unsigned char *>(user_ptr);
268     const auto buffer_zone_end = aligned_ptr - AllocationHeader::buffer_zone_size();
269 
270     PetscCall(PetscUnpoisonMemoryRegion(buffer_zone_end, sizeof(*buffer_zone_end)));
271     {
272       // offset added to original pointer due to alignment, B -> C above (may be zero)
273       const auto alignment_offset = *buffer_zone_end;
274 
275       *header = reinterpret_cast<AllocationHeader *>(buffer_zone_end - alignment_offset - sizeof(AllocationHeader));
276     }
277     PetscCall(PetscPoisonMemoryRegion(buffer_zone_end, sizeof(*buffer_zone_end)));
278   }
279   PetscFunctionReturn(PETSC_SUCCESS);
280 }
281 
282 /*
283   PoolAllocator::allocate_ptr_ - Allocate a pointer and header given requested size and
284   alignment
285 
286   Input Parameters:
287 + size  - the size (in bytes) to allocate
288 - align - the size (in bytes) to align the pointer to
289 
290   Output Parameter:
291 . ret_ptr - the resulting pointer to user-memory
292 
293   Notes:
294   Both size and align must be > 0. align must be a power of 2.
295   This both allocates the user memory and the corresponding metadata region.
296 */
297 PetscErrorCode PoolAllocator::allocate_ptr_(size_type size, align_type align, void **ret_ptr) noexcept
298 {
299   constexpr auto header_size = AllocationHeader::header_size();
300   const auto     total_size  = total_size_(size, align);
301   const auto     size_before = total_size - header_size;
302   auto           usable_size = size_before;
303   void          *aligned_ptr = nullptr;
304   unsigned char *base_ptr    = nullptr;
305 
306   PetscFunctionBegin;
307   PetscAssertPointer(ret_ptr, 1);
308   PetscCall(valid_alignment_(align));
309   // memory is laid out as follows:
310   //
311   //                            aligned_ptr     ret_ptr (and aligned_ptr after std::align())
312   // base_ptr     buffer_zone       |     _____/
313   // |                      |       |    /     user memory
314   // v                  v~~~~~~~~~~~x~~~v~~~~~~~~~~~~~~~~~~~~~ ...
315   // A==============B===C===========D===E--------------------- ...
316   // ^~~~~~~~~~~~~~~^^~~^
317   //        |            \_________
318   // sizeof(AllocationHeader)      |
319   //                               alignment_offset (may be 0)
320   //
321   // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
322   //                poisoned
323   //                                ^~~~~~~~~~~~~~~~~~~~~~~~~~ ...
324   //                                          usable_size
325   // ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ ...
326   //                       total_size_()
327   //
328   base_ptr = ::new (std::nothrow) unsigned char[total_size];
329   PetscAssert(base_ptr, PETSC_COMM_SELF, PETSC_ERR_MEM, "operator new() failed to allocate %zu bytes", total_size);
330   PetscCallCXX(base_ptr = reinterpret_cast<unsigned char *>(util::construct_at(reinterpret_cast<AllocationHeader *>(base_ptr), size, align)));
331   aligned_ptr = base_ptr + header_size;
332   // storing to ret_ptr and not aligned_ptr is deliberate! std::align() returns nullptr if it
333   // fails, so we do not want to clobber aligned_ptr
334   *ret_ptr = std::align(util::to_underlying(align), size, aligned_ptr, usable_size);
335   // note usable_size is has now shrunk by alignment_offset
336   PetscAssert(*ret_ptr, PETSC_COMM_SELF, PETSC_ERR_LIB, "std::align() failed to align pointer %p (size %zu, alignment %zu)", aligned_ptr, size, util::to_underlying(align));
337   {
338     constexpr auto max_align        = util::to_underlying(AllocationHeader::max_alignment());
339     const auto     alignment_offset = size_before - usable_size;
340 
341     PetscAssert(alignment_offset <= max_align, PETSC_COMM_SELF, PETSC_ERR_MEMC, "Computed alignment offset %zu > maximum allowed alignment %zu", alignment_offset, max_align);
342     *(reinterpret_cast<unsigned char *>(aligned_ptr) - AllocationHeader::buffer_zone_size()) = static_cast<unsigned char>(alignment_offset);
343     if (PetscDefined(USE_DEBUG)) {
344       const auto computed_aligned_ptr = base_ptr + header_size + alignment_offset;
345 
346       PetscCheck(computed_aligned_ptr == aligned_ptr, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Base pointer %p + header size %zu + alignment offset %zu = %p != aligned pointer %p", static_cast<void *>(base_ptr), header_size, alignment_offset, static_cast<void *>(computed_aligned_ptr), aligned_ptr);
347     }
348   }
349   // Poison the entire region first, then unpoison only the user region. This ensures that
350   // any extra space on *either* ends of the array are poisoned
351   PetscCall(PetscPoisonMemoryRegion(base_ptr, total_size));
352   PetscCall(PetscUnpoisonMemoryRegion(aligned_ptr, size));
353   PetscFunctionReturn(PETSC_SUCCESS);
354 }
355 
356 // ==========================================================================================
357 // PoolAllocator -- Public API
358 // ==========================================================================================
359 
360 PoolAllocator::~PoolAllocator() noexcept
361 {
362   size_type leaked{};
363   PetscBool init;
364 
365   PetscFunctionBegin;
366   PetscCallAbort(PETSC_COMM_SELF, clear_(&leaked));
367   PetscCallAbort(PETSC_COMM_SELF, PetscInitialized(&init));
368   if (init) PetscCheckAbort(leaked == 0, PETSC_COMM_SELF, PETSC_ERR_MEM_LEAK, "%zu objects remaining in the pool are leaked", leaked);
369   PetscFunctionReturnVoid();
370 }
371 
372 /*
373   PoolAllocator::get_attributes - Get the size and alignment of an allocated pointer
374 
375   Input Parameter:
376 . ptr - the pointer to query
377 
378   Output Parameters:
379 + size - the size (in bytes) of the allocated area, nullptr if not needed
380 - align - the alignment (in bytes) of the allocated, nullptr if not needed
381 
382   Note:
383   ptr must have been allocated by the pool, and is exactly the pointer returned by either
384   allocate() or try_allocate() (if successful).
385 */
386 PetscErrorCode PoolAllocator::get_attributes(const void *ptr, size_type *size, align_type *align) noexcept
387 {
388   PetscFunctionBegin;
389   // ptr may be poisoned, so cannot check it here
390   // PetscAssertPointer(out_ptr, 1);
391   if (size) PetscAssertPointer(size, 2);
392   if (align) PetscAssertPointer(align, 3);
393   if (PetscLikely(size || align)) {
394     AllocationHeader *header = nullptr;
395 
396     PetscCall(extract_header_(const_cast<void *>(ptr), &header, /* check ptr = */ false));
397     PetscCall(PetscUnpoisonMemoryRegion(header, sizeof(*header)));
398     if (size) *size = header->size;
399     if (align) *align = header->align;
400     PetscCall(PetscPoisonMemoryRegion(header, sizeof(*header)));
401   }
402   PetscFunctionReturn(PETSC_SUCCESS);
403 }
404 
405 /*
406   PoolAllocator::try_allocate - Attempt to allocate memory from the pool
407 
408   Input Parameter:
409 + size  - the size (in bytes) to attempt to allocate
410 - align - the alignment (in bytes) of the requested allocation
411 
412   Output Parameters:
413 + out_ptr - the pointer to return the allocated memory in
414 - success - set to true if out_ptr was successfully is allocated
415 
416   Notes:
417   Differs from allocate() insofar that this routine does not allocate new memory if it does not
418   find a suitable memory chunk in the pool.
419 
420   align must be a power of 2, and > 0.
421 
422   If size is 0, out_ptr is set to nullptr and success set to false
423 */
424 PetscErrorCode PoolAllocator::try_allocate(void **out_ptr, size_type size, align_type align, bool *success) noexcept
425 {
426   void *ptr{};
427   bool  found{};
428 
429   PetscFunctionBegin;
430   PetscAssertPointer(out_ptr, 1);
431   PetscAssertPointer(success, 3);
432   PetscCall(valid_alignment_(align));
433   PetscCall(this->register_finalize());
434   if (PetscLikely(size)) {
435     const auto align_it = find_align_(align);
436 
437     if (align_it != this->pool().end() && align_it->first == align) {
438       auto     &&size_map = align_it->second;
439       const auto size_it  = size_map.find(size);
440 
441       if (size_it != size_map.end()) {
442         auto &&ptr_list = size_it->second;
443 
444         if (!ptr_list.empty()) {
445           found = true;
446           ptr   = ptr_list.back();
447           PetscCallCXX(ptr_list.pop_back());
448           PetscCall(PetscUnpoisonMemoryRegion(ptr, size));
449         }
450       }
451     }
452   }
453   *out_ptr = ptr;
454   *success = found;
455   PetscFunctionReturn(PETSC_SUCCESS);
456 }
457 
458 /*
459   PoolAllocator::allocate - Allocate a chunk of memory from the pool
460 
461   Input Parameters:
462 + size  - The size (in bytes) to allocate
463 - align - The alignment (in bytes) to align the allocation to
464 
465   Output Parameters:
466 + out_ptr             - A pointer containing the beginning of the allocated region
467 - allocated_from_pool - True if the region was allocated from the pool, false otherwise
468 
469   Notes:
470   If size is 0, out_ptr is set to nullptr and was_allocated is set to false.
471 */
472 PetscErrorCode PoolAllocator::allocate(void **out_ptr, size_type size, align_type align, bool *allocated_from_pool) noexcept
473 {
474   bool success{};
475 
476   PetscFunctionBegin;
477   PetscAssertPointer(out_ptr, 1);
478   if (allocated_from_pool) PetscAssertPointer(allocated_from_pool, 3);
479   PetscCall(try_allocate(out_ptr, size, align, &success));
480   if (!success) PetscCall(allocate_ptr_(size, align, out_ptr));
481   if (allocated_from_pool) *allocated_from_pool = success;
482   PetscFunctionReturn(PETSC_SUCCESS);
483 }
484 
485 /*
486   PoolAllocate::deallocate - Return a pointer to the pool
487 
488   Input Parameter:
489 . in_ptr - A pointer to the beginning of the allocated region
490 
491   Notes:
492   On success the region pointed to by in_ptr is poisoned. Any further attempts to access
493   the memory pointed to by in_ptr will result in an error.
494 
495   in_ptr must have been allocated by the pool, and must point to the beginning of the allocated
496   region.
497 
498   The value in_ptr points to may be nullptr, in which case this routine does nothing.
499 */
500 PetscErrorCode PoolAllocator::deallocate(void **in_ptr, size_type size, align_type align) noexcept
501 {
502   PetscFunctionBegin;
503   PetscAssertPointer(in_ptr, 1);
504   if (auto ptr = util::exchange(*in_ptr, nullptr)) {
505     if (locked_) {
506       // This is necessary if an object is "reclaimed" within another PetscFinalize()
507       // registered cleanup after this pool has returned from its finalizer. In this case,
508       // instead of pushing onto the stack we just delete the pointer directly.
509       //
510       // However this path is *only* valid if we have already finalized!
511       PetscCall(delete_ptr_(&ptr));
512     } else {
513       auto it = find_align_(align);
514 
515       if (it == this->pool().end() || it->first != align) PetscCallCXX(it = this->pool().insert(it, {align, {}}));
516       PetscCallCXX(it->second[size].emplace_back(ptr));
517       PetscCall(PetscPoisonMemoryRegion(ptr, size));
518     }
519   }
520   PetscFunctionReturn(PETSC_SUCCESS);
521 }
522 
523 /*
524   PoolAllocator::unpoison - Unpoison a pool-allocated pointer
525 
526   Input Parameter:
527 . ptr - the pointer to poison
528 
529   Output Parameter:
530 . size - the size (in bytes) of the region pointed to by ptr
531 
532   Notes:
533   ptr must not be nullptr.
534 
535   size should be passed to the corresponding repoison() to undo the effects of this
536   routine.
537 
538   Using this routine in conjunction with unpoison() allows a user to temporarily push and pop
539   the poisoning state of a given pointer. The pool does not repoison the pointer for you, so
540   use at your own risk!
541 */
542 PetscErrorCode PoolAllocator::unpoison(const void *ptr, size_type *size) noexcept
543 {
544   PetscFunctionBegin;
545   // ptr may be poisoned, so cannot check it here
546   // PetscAssertPointer(ptr, 1);
547   PetscAssertPointer(size, 2);
548   PetscCall(get_attributes(ptr, size, nullptr));
549   PetscCall(PetscUnpoisonMemoryRegion(ptr, *size));
550   PetscFunctionReturn(PETSC_SUCCESS);
551 }
552 
553 /*
554   PoolAllocator::repoison - Poison a pointer previously unpoisoned via unpoison()
555 
556   Input Parameters:
557 + ptr  - the pointer to the unpoisoned region
558 - size - the size of the region
559 
560   Notes:
561   size must be exactly the value returned by unpoison().
562 
563   ptr cannot be nullptr
564 */
565 PetscErrorCode PoolAllocator::repoison(const void *ptr, size_type size) noexcept
566 {
567   PetscFunctionBegin;
568   PetscAssertPointer(ptr, 1);
569   PetscCall(PetscPoisonMemoryRegion(ptr, size));
570   PetscFunctionReturn(PETSC_SUCCESS);
571 }
572 
573 PoolAllocator::LockGuard PoolAllocator::lock_guard() noexcept
574 {
575   return LockGuard{this};
576 }
577 
578 // ==========================================================================================
579 // PoolAllocated -- Public API
580 // ==========================================================================================
581 
582 void *PoolAllocated::operator new(size_type size) noexcept
583 {
584   void *ptr{};
585 
586   PetscFunctionBegin;
587   PetscCallAbort(PETSC_COMM_SELF, pool().allocate(&ptr, size, static_cast<align_type>(alignof(std::max_align_t))));
588   PetscFunctionReturn(ptr);
589 }
590 
591 void PoolAllocated::operator delete(void *ptr) noexcept
592 {
593   PetscFunctionBegin;
594   if (PetscLikely(ptr)) {
595     size_type       size{};
596     align_type      align{};
597     allocator_type &allocated = pool();
598 
599     PetscCallAbort(PETSC_COMM_SELF, allocated.get_attributes(ptr, &size, &align));
600     PetscCallAbort(PETSC_COMM_SELF, allocated.deallocate(&ptr, size, align));
601   }
602   PetscFunctionReturnVoid();
603 }
604 
605 #if PETSC_CPP_VERSION >= 17
606 void *PoolAllocated::operator new(size_type size, std::align_val_t align) noexcept
607 {
608   void *ptr{};
609 
610   PetscFunctionBegin;
611   PetscCallAbort(PETSC_COMM_SELF, pool().allocate(&ptr, size, static_cast<align_type>(align)));
612   PetscFunctionReturn(ptr);
613 }
614 
615 void PoolAllocated::operator delete(void *ptr, std::align_val_t align) noexcept
616 {
617   PetscFunctionBegin;
618   if (PetscLikely(ptr)) {
619     size_type size{};
620 
621     PetscCallAbort(PETSC_COMM_SELF, pool().get_attributes(ptr, &size, nullptr));
622     PetscCallAbort(PETSC_COMM_SELF, pool().deallocate(&ptr, size, static_cast<align_type>(align)));
623   }
624   PetscFunctionReturnVoid();
625 }
626 #endif
627 
628 // ==========================================================================================
629 // PoolAllocated -- Protected API
630 // ==========================================================================================
631 
632 PoolAllocated::allocator_type &PoolAllocated::pool() noexcept
633 {
634   return pool_;
635 }
636 
637 } // namespace memory
638 
639 } // namespace Petsc
640