xref: /petsc/src/sys/objects/device/impls/segmentedmempool.hpp (revision 0e6b6b5985dd9b1172860d21fb88bd3966bf7c54)
1*0e6b6b59SJacob Faibussowitsch #ifndef PETSC_SEGMENTEDMEMPOOL_HPP
2*0e6b6b59SJacob Faibussowitsch #define PETSC_SEGMENTEDMEMPOOL_HPP
3*0e6b6b59SJacob Faibussowitsch 
4*0e6b6b59SJacob Faibussowitsch #include <petsc/private/deviceimpl.h>
5*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/macros.hpp>
6*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/type_traits.hpp>
7*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/utility.hpp>
8*0e6b6b59SJacob Faibussowitsch #include <petsc/private/cpp/register_finalize.hpp>
9*0e6b6b59SJacob Faibussowitsch 
10*0e6b6b59SJacob Faibussowitsch #include <limits>
11*0e6b6b59SJacob Faibussowitsch #include <deque>
12*0e6b6b59SJacob Faibussowitsch #include <vector>
13*0e6b6b59SJacob Faibussowitsch 
14*0e6b6b59SJacob Faibussowitsch namespace Petsc {
15*0e6b6b59SJacob Faibussowitsch 
16*0e6b6b59SJacob Faibussowitsch namespace device {
17*0e6b6b59SJacob Faibussowitsch 
18*0e6b6b59SJacob Faibussowitsch template <typename T>
19*0e6b6b59SJacob Faibussowitsch class StreamBase {
20*0e6b6b59SJacob Faibussowitsch public:
21*0e6b6b59SJacob Faibussowitsch   using id_type      = int;
22*0e6b6b59SJacob Faibussowitsch   using derived_type = T;
23*0e6b6b59SJacob Faibussowitsch 
24*0e6b6b59SJacob Faibussowitsch   static const id_type INVALID_ID;
25*0e6b6b59SJacob Faibussowitsch 
26*0e6b6b59SJacob Faibussowitsch   // needed so that dependent auto works, see veccupmimpl.h for a detailed discussion
27*0e6b6b59SJacob Faibussowitsch   template <typename U = T>
28*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD auto get_stream() const noexcept PETSC_DECLTYPE_AUTO_RETURNS(static_cast<const U &>(*this).get_stream_());
29*0e6b6b59SJacob Faibussowitsch 
30*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD id_type get_id() const noexcept { return static_cast<const T &>(*this).get_id_(); }
31*0e6b6b59SJacob Faibussowitsch 
32*0e6b6b59SJacob Faibussowitsch   template <typename E>
33*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode record_event(E &&event) const noexcept {
34*0e6b6b59SJacob Faibussowitsch     return static_cast<const T &>(*this).record_event_(std::forward<E>(event));
35*0e6b6b59SJacob Faibussowitsch   }
36*0e6b6b59SJacob Faibussowitsch 
37*0e6b6b59SJacob Faibussowitsch   template <typename E>
38*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode wait_for_event(E &&event) const noexcept {
39*0e6b6b59SJacob Faibussowitsch     return static_cast<const T &>(*this).wait_for_(std::forward<E>(event));
40*0e6b6b59SJacob Faibussowitsch   }
41*0e6b6b59SJacob Faibussowitsch 
42*0e6b6b59SJacob Faibussowitsch protected:
43*0e6b6b59SJacob Faibussowitsch   constexpr StreamBase() noexcept = default;
44*0e6b6b59SJacob Faibussowitsch 
45*0e6b6b59SJacob Faibussowitsch   struct default_event_type { };
46*0e6b6b59SJacob Faibussowitsch   using default_stream_type = std::nullptr_t;
47*0e6b6b59SJacob Faibussowitsch 
48*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static constexpr default_stream_type get_stream_() noexcept { return nullptr; }
49*0e6b6b59SJacob Faibussowitsch 
50*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static constexpr id_type get_id_() noexcept { return 0; }
51*0e6b6b59SJacob Faibussowitsch 
52*0e6b6b59SJacob Faibussowitsch   template <typename U = T>
53*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static constexpr PetscErrorCode record_event_(const typename U::event_type &) noexcept {
54*0e6b6b59SJacob Faibussowitsch     return 0;
55*0e6b6b59SJacob Faibussowitsch   }
56*0e6b6b59SJacob Faibussowitsch 
57*0e6b6b59SJacob Faibussowitsch   template <typename U = T>
58*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static constexpr PetscErrorCode wait_for_(const typename U::event_type &) noexcept {
59*0e6b6b59SJacob Faibussowitsch     return 0;
60*0e6b6b59SJacob Faibussowitsch   }
61*0e6b6b59SJacob Faibussowitsch };
62*0e6b6b59SJacob Faibussowitsch 
63*0e6b6b59SJacob Faibussowitsch template <typename T>
64*0e6b6b59SJacob Faibussowitsch const typename StreamBase<T>::id_type StreamBase<T>::INVALID_ID = -1;
65*0e6b6b59SJacob Faibussowitsch 
66*0e6b6b59SJacob Faibussowitsch struct DefaultStream : StreamBase<DefaultStream> {
67*0e6b6b59SJacob Faibussowitsch   using stream_type = typename StreamBase<DefaultStream>::default_stream_type;
68*0e6b6b59SJacob Faibussowitsch   using id_type     = typename StreamBase<DefaultStream>::id_type;
69*0e6b6b59SJacob Faibussowitsch   using event_type  = typename StreamBase<DefaultStream>::default_event_type;
70*0e6b6b59SJacob Faibussowitsch };
71*0e6b6b59SJacob Faibussowitsch 
72*0e6b6b59SJacob Faibussowitsch } // namespace device
73*0e6b6b59SJacob Faibussowitsch 
74*0e6b6b59SJacob Faibussowitsch namespace memory {
75*0e6b6b59SJacob Faibussowitsch 
76*0e6b6b59SJacob Faibussowitsch namespace impl {
77*0e6b6b59SJacob Faibussowitsch 
78*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
79*0e6b6b59SJacob Faibussowitsch // MemoryChunk
80*0e6b6b59SJacob Faibussowitsch //
81*0e6b6b59SJacob Faibussowitsch // Represents a checked-out region of a MemoryBlock. Tracks the offset into the owning
82*0e6b6b59SJacob Faibussowitsch // MemoryBlock and its size/capacity
83*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
84*0e6b6b59SJacob Faibussowitsch 
85*0e6b6b59SJacob Faibussowitsch template <typename EventType>
86*0e6b6b59SJacob Faibussowitsch class MemoryChunk {
87*0e6b6b59SJacob Faibussowitsch public:
88*0e6b6b59SJacob Faibussowitsch   using event_type = EventType;
89*0e6b6b59SJacob Faibussowitsch   using size_type  = std::size_t;
90*0e6b6b59SJacob Faibussowitsch 
91*0e6b6b59SJacob Faibussowitsch   MemoryChunk(size_type, size_type) noexcept;
92*0e6b6b59SJacob Faibussowitsch   explicit MemoryChunk(size_type) noexcept;
93*0e6b6b59SJacob Faibussowitsch 
94*0e6b6b59SJacob Faibussowitsch   MemoryChunk(MemoryChunk &&) noexcept;
95*0e6b6b59SJacob Faibussowitsch   MemoryChunk &operator=(MemoryChunk &&) noexcept;
96*0e6b6b59SJacob Faibussowitsch 
97*0e6b6b59SJacob Faibussowitsch   MemoryChunk(const MemoryChunk &) noexcept            = delete;
98*0e6b6b59SJacob Faibussowitsch   MemoryChunk &operator=(const MemoryChunk &) noexcept = delete;
99*0e6b6b59SJacob Faibussowitsch 
100*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD size_type start() const noexcept { return start_; }
101*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD size_type size() const noexcept { return size_; }
102*0e6b6b59SJacob Faibussowitsch   // REVIEW ME:
103*0e6b6b59SJacob Faibussowitsch   // make this an actual field, normally each chunk shrinks_to_fit() on begin claimed, but in
104*0e6b6b59SJacob Faibussowitsch   // theory only the last chunk needs to do this
105*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD size_type capacity() const noexcept { return size_; }
106*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD size_type total_offset() const noexcept { return start() + size(); }
107*0e6b6b59SJacob Faibussowitsch 
108*0e6b6b59SJacob Faibussowitsch   template <typename U>
109*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode release(const device::StreamBase<U> *) noexcept;
110*0e6b6b59SJacob Faibussowitsch   template <typename U>
111*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode claim(const device::StreamBase<U> *, size_type, bool *, bool = false) noexcept;
112*0e6b6b59SJacob Faibussowitsch   template <typename U>
113*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD bool           can_claim(const device::StreamBase<U> *, size_type, bool) const noexcept;
114*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode resize(size_type) noexcept;
115*0e6b6b59SJacob Faibussowitsch 
116*0e6b6b59SJacob Faibussowitsch private:
117*0e6b6b59SJacob Faibussowitsch   // clang-format off
118*0e6b6b59SJacob Faibussowitsch   event_type      event_{};          // event recorded when the chunk was released
119*0e6b6b59SJacob Faibussowitsch   bool            open_      = true; // is this chunk open?
120*0e6b6b59SJacob Faibussowitsch   // id of the last stream to use the chunk, populated on release
121*0e6b6b59SJacob Faibussowitsch   int             stream_id_ = device::DefaultStream::INVALID_ID;
122*0e6b6b59SJacob Faibussowitsch   size_type       size_      = 0;    // size of the chunk
123*0e6b6b59SJacob Faibussowitsch   const size_type start_     = 0;    // offset from the start of the owning block
124*0e6b6b59SJacob Faibussowitsch 
125*0e6b6b59SJacob Faibussowitsch   // clang-format on
126*0e6b6b59SJacob Faibussowitsch   template <typename U>
127*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD bool stream_compat_(const device::StreamBase<U> *) const noexcept;
128*0e6b6b59SJacob Faibussowitsch };
129*0e6b6b59SJacob Faibussowitsch 
130*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
131*0e6b6b59SJacob Faibussowitsch // MemoryChunk - Private API
132*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
133*0e6b6b59SJacob Faibussowitsch 
134*0e6b6b59SJacob Faibussowitsch // asks and answers the question: can this stream claim this chunk without serializing?
135*0e6b6b59SJacob Faibussowitsch template <typename E>
136*0e6b6b59SJacob Faibussowitsch template <typename U>
137*0e6b6b59SJacob Faibussowitsch inline bool MemoryChunk<E>::stream_compat_(const device::StreamBase<U> *strm) const noexcept {
138*0e6b6b59SJacob Faibussowitsch   return (stream_id_ == strm->INVALID_ID) || (stream_id_ == strm->get_id());
139*0e6b6b59SJacob Faibussowitsch }
140*0e6b6b59SJacob Faibussowitsch 
141*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
142*0e6b6b59SJacob Faibussowitsch // MemoryChunk - Public API
143*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
144*0e6b6b59SJacob Faibussowitsch 
145*0e6b6b59SJacob Faibussowitsch template <typename E>
146*0e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(size_type start, size_type size) noexcept : size_(size), start_(start) { }
147*0e6b6b59SJacob Faibussowitsch 
148*0e6b6b59SJacob Faibussowitsch template <typename E>
149*0e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(size_type size) noexcept : MemoryChunk(0, size) { }
150*0e6b6b59SJacob Faibussowitsch 
151*0e6b6b59SJacob Faibussowitsch template <typename E>
152*0e6b6b59SJacob Faibussowitsch inline MemoryChunk<E>::MemoryChunk(MemoryChunk<E> &&other) noexcept :
153*0e6b6b59SJacob Faibussowitsch   event_(std::move(other.event_)), open_(util::exchange(other.open_, false)), stream_id_(util::exchange(other.stream_id_, device::DefaultStream::INVALID_ID)), size_(util::exchange(other.size_, 0)), start_(std::move(other.start_)) { }
154*0e6b6b59SJacob Faibussowitsch 
155*0e6b6b59SJacob Faibussowitsch template <typename E>
156*0e6b6b59SJacob Faibussowitsch inline MemoryChunk<E> &MemoryChunk<E>::operator=(MemoryChunk<E> &&other) noexcept {
157*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
158*0e6b6b59SJacob Faibussowitsch   if (this != &other) {
159*0e6b6b59SJacob Faibussowitsch     event_     = std::move(other.event_);
160*0e6b6b59SJacob Faibussowitsch     open_      = util::exchange(other.open_, false);
161*0e6b6b59SJacob Faibussowitsch     stream_id_ = util::exchange(other.stream_id_, device::DefaultStream::INVALID_ID);
162*0e6b6b59SJacob Faibussowitsch     size_      = util::exchange(other.size_, 0);
163*0e6b6b59SJacob Faibussowitsch     start_     = std::move(other.start_);
164*0e6b6b59SJacob Faibussowitsch   }
165*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(*this);
166*0e6b6b59SJacob Faibussowitsch }
167*0e6b6b59SJacob Faibussowitsch 
168*0e6b6b59SJacob Faibussowitsch /*
169*0e6b6b59SJacob Faibussowitsch   MemoryChunk::release - release a chunk on a stream
170*0e6b6b59SJacob Faibussowitsch 
171*0e6b6b59SJacob Faibussowitsch   Input Parameter:
172*0e6b6b59SJacob Faibussowitsch . stream - the stream to release the chunk with
173*0e6b6b59SJacob Faibussowitsch 
174*0e6b6b59SJacob Faibussowitsch   Notes:
175*0e6b6b59SJacob Faibussowitsch   Inserts a release operation on stream and records the state of stream at the time this
176*0e6b6b59SJacob Faibussowitsch   routine was called.
177*0e6b6b59SJacob Faibussowitsch 
178*0e6b6b59SJacob Faibussowitsch   Future allocation requests which attempt to claim the chunk on the same stream may re-acquire
179*0e6b6b59SJacob Faibussowitsch   the chunk without serialization.
180*0e6b6b59SJacob Faibussowitsch 
181*0e6b6b59SJacob Faibussowitsch   If another stream attempts to claim the chunk they must wait for the recorded event before
182*0e6b6b59SJacob Faibussowitsch   claiming the chunk.
183*0e6b6b59SJacob Faibussowitsch */
184*0e6b6b59SJacob Faibussowitsch template <typename E>
185*0e6b6b59SJacob Faibussowitsch template <typename U>
186*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::release(const device::StreamBase<U> *stream) noexcept {
187*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
188*0e6b6b59SJacob Faibussowitsch   open_      = true;
189*0e6b6b59SJacob Faibussowitsch   stream_id_ = stream->get_id();
190*0e6b6b59SJacob Faibussowitsch   PetscCall(stream->record_event(event_));
191*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
192*0e6b6b59SJacob Faibussowitsch }
193*0e6b6b59SJacob Faibussowitsch 
194*0e6b6b59SJacob Faibussowitsch /*
195*0e6b6b59SJacob Faibussowitsch   MemoryChunk::claim - attempt to claim a particular chunk
196*0e6b6b59SJacob Faibussowitsch 
197*0e6b6b59SJacob Faibussowitsch   Input Parameters:
198*0e6b6b59SJacob Faibussowitsch + stream    - the stream on which to attempt to claim
199*0e6b6b59SJacob Faibussowitsch . req_size  - the requested size (in elements) to attempt to claim
200*0e6b6b59SJacob Faibussowitsch - serialize - (optional, false) whether the claimant allows serialization
201*0e6b6b59SJacob Faibussowitsch 
202*0e6b6b59SJacob Faibussowitsch   Output Parameter:
203*0e6b6b59SJacob Faibussowitsch . success - true if the chunk was claimed, false otherwise
204*0e6b6b59SJacob Faibussowitsch */
205*0e6b6b59SJacob Faibussowitsch template <typename E>
206*0e6b6b59SJacob Faibussowitsch template <typename U>
207*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::claim(const device::StreamBase<U> *stream, size_type req_size, bool *success, bool serialize) noexcept {
208*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
209*0e6b6b59SJacob Faibussowitsch   if ((*success = can_claim(stream, req_size, serialize))) {
210*0e6b6b59SJacob Faibussowitsch     if (serialize && !stream_compat_(stream)) PetscCall(stream->wait_for_event(event_));
211*0e6b6b59SJacob Faibussowitsch     PetscCall(resize(req_size));
212*0e6b6b59SJacob Faibussowitsch     open_ = false;
213*0e6b6b59SJacob Faibussowitsch   }
214*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
215*0e6b6b59SJacob Faibussowitsch }
216*0e6b6b59SJacob Faibussowitsch 
217*0e6b6b59SJacob Faibussowitsch /*
218*0e6b6b59SJacob Faibussowitsch   MemoryChunk::can_claim - test whether a particular chunk can be claimed
219*0e6b6b59SJacob Faibussowitsch 
220*0e6b6b59SJacob Faibussowitsch   Input Parameters:
221*0e6b6b59SJacob Faibussowitsch + stream    - the stream on which to attempt to claim
222*0e6b6b59SJacob Faibussowitsch . req_size  - the requested size (in elements) to attempt to claim
223*0e6b6b59SJacob Faibussowitsch - serialize - whether the claimant allows serialization
224*0e6b6b59SJacob Faibussowitsch 
225*0e6b6b59SJacob Faibussowitsch   Output:
226*0e6b6b59SJacob Faibussowitsch . [return] - true if the chunk is claimable given the configuration, false otherwise
227*0e6b6b59SJacob Faibussowitsch */
228*0e6b6b59SJacob Faibussowitsch template <typename E>
229*0e6b6b59SJacob Faibussowitsch template <typename U>
230*0e6b6b59SJacob Faibussowitsch inline bool MemoryChunk<E>::can_claim(const device::StreamBase<U> *stream, size_type req_size, bool serialize) const noexcept {
231*0e6b6b59SJacob Faibussowitsch   if (open_ && (req_size <= capacity())) {
232*0e6b6b59SJacob Faibussowitsch     // fully compatible
233*0e6b6b59SJacob Faibussowitsch     if (stream_compat_(stream)) return true;
234*0e6b6b59SJacob Faibussowitsch     // stream wasn't compatible, but could claim if we serialized
235*0e6b6b59SJacob Faibussowitsch     if (serialize) return true;
236*0e6b6b59SJacob Faibussowitsch     // incompatible stream and did not want to serialize
237*0e6b6b59SJacob Faibussowitsch   }
238*0e6b6b59SJacob Faibussowitsch   return false;
239*0e6b6b59SJacob Faibussowitsch }
240*0e6b6b59SJacob Faibussowitsch 
241*0e6b6b59SJacob Faibussowitsch /*
242*0e6b6b59SJacob Faibussowitsch   MemoryChunk::resize - grow a chunk to new size
243*0e6b6b59SJacob Faibussowitsch 
244*0e6b6b59SJacob Faibussowitsch   Input Parameter:
245*0e6b6b59SJacob Faibussowitsch . newsize - the new size Requested
246*0e6b6b59SJacob Faibussowitsch 
247*0e6b6b59SJacob Faibussowitsch   Notes:
248*0e6b6b59SJacob Faibussowitsch   newsize cannot be larger than capacity
249*0e6b6b59SJacob Faibussowitsch */
250*0e6b6b59SJacob Faibussowitsch template <typename E>
251*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryChunk<E>::resize(size_type newsize) noexcept {
252*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
253*0e6b6b59SJacob Faibussowitsch   PetscAssert(newsize <= capacity(), PETSC_COMM_SELF, PETSC_ERR_ARG_SIZ, "New size %zu larger than capacity %zu", newsize, capacity());
254*0e6b6b59SJacob Faibussowitsch   size_ = newsize;
255*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
256*0e6b6b59SJacob Faibussowitsch }
257*0e6b6b59SJacob Faibussowitsch 
258*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
259*0e6b6b59SJacob Faibussowitsch // MemoryBlock
260*0e6b6b59SJacob Faibussowitsch //
261*0e6b6b59SJacob Faibussowitsch // A "memory block" manager, which owns the pointer to a particular memory range. Retrieving
262*0e6b6b59SJacob Faibussowitsch // and restoring a block is thread-safe (so may be used by multiple device streams).
263*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
264*0e6b6b59SJacob Faibussowitsch 
265*0e6b6b59SJacob Faibussowitsch template <typename T, typename AllocatorType, typename StreamType>
266*0e6b6b59SJacob Faibussowitsch class MemoryBlock {
267*0e6b6b59SJacob Faibussowitsch public:
268*0e6b6b59SJacob Faibussowitsch   using value_type      = T;
269*0e6b6b59SJacob Faibussowitsch   using allocator_type  = AllocatorType;
270*0e6b6b59SJacob Faibussowitsch   using stream_type     = StreamType;
271*0e6b6b59SJacob Faibussowitsch   using event_type      = typename stream_type::event_type;
272*0e6b6b59SJacob Faibussowitsch   using chunk_type      = MemoryChunk<event_type>;
273*0e6b6b59SJacob Faibussowitsch   using size_type       = typename chunk_type::size_type;
274*0e6b6b59SJacob Faibussowitsch   using chunk_list_type = std::vector<chunk_type>;
275*0e6b6b59SJacob Faibussowitsch 
276*0e6b6b59SJacob Faibussowitsch   template <typename U>
277*0e6b6b59SJacob Faibussowitsch   MemoryBlock(allocator_type *, size_type, const device::StreamBase<U> *) noexcept;
278*0e6b6b59SJacob Faibussowitsch 
279*0e6b6b59SJacob Faibussowitsch   ~MemoryBlock() noexcept(std::is_nothrow_destructible<chunk_list_type>::value);
280*0e6b6b59SJacob Faibussowitsch 
281*0e6b6b59SJacob Faibussowitsch   MemoryBlock(MemoryBlock &&) noexcept;
282*0e6b6b59SJacob Faibussowitsch   MemoryBlock &operator=(MemoryBlock &&) noexcept;
283*0e6b6b59SJacob Faibussowitsch 
284*0e6b6b59SJacob Faibussowitsch   // memory blocks are not copyable
285*0e6b6b59SJacob Faibussowitsch   MemoryBlock(const MemoryBlock &)            = delete;
286*0e6b6b59SJacob Faibussowitsch   MemoryBlock &operator=(const MemoryBlock &) = delete;
287*0e6b6b59SJacob Faibussowitsch 
288*0e6b6b59SJacob Faibussowitsch   /* --- actual functions --- */
289*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode try_allocate_chunk(size_type, T **, const stream_type *, bool *) noexcept;
290*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode try_deallocate_chunk(T **, const stream_type *, bool *) noexcept;
291*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode try_find_chunk(const T *, chunk_type **) noexcept;
292*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD bool           owns_pointer(const T *) const noexcept;
293*0e6b6b59SJacob Faibussowitsch 
294*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD size_type size() const noexcept { return size_; }
295*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD size_type bytes() const noexcept { return sizeof(value_type) * size(); }
296*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD size_type num_chunks() const noexcept { return chunks_.size(); }
297*0e6b6b59SJacob Faibussowitsch 
298*0e6b6b59SJacob Faibussowitsch private:
299*0e6b6b59SJacob Faibussowitsch   value_type     *mem_{};
300*0e6b6b59SJacob Faibussowitsch   allocator_type *allocator_{};
301*0e6b6b59SJacob Faibussowitsch   size_type       size_{};
302*0e6b6b59SJacob Faibussowitsch   chunk_list_type chunks_{};
303*0e6b6b59SJacob Faibussowitsch 
304*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode clear_(const stream_type *) noexcept;
305*0e6b6b59SJacob Faibussowitsch };
306*0e6b6b59SJacob Faibussowitsch 
307*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
308*0e6b6b59SJacob Faibussowitsch // MemoryBlock - Private API
309*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
310*0e6b6b59SJacob Faibussowitsch 
311*0e6b6b59SJacob Faibussowitsch // clear the memory block, called from destructors and move assignment/construction
312*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
313*0e6b6b59SJacob Faibussowitsch PETSC_NODISCARD PetscErrorCode MemoryBlock<T, A, S>::clear_(const stream_type *stream) noexcept {
314*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
315*0e6b6b59SJacob Faibussowitsch   if (PetscLikely(mem_)) {
316*0e6b6b59SJacob Faibussowitsch     PetscCall(allocator_->deallocate(mem_, stream));
317*0e6b6b59SJacob Faibussowitsch     mem_ = nullptr;
318*0e6b6b59SJacob Faibussowitsch   }
319*0e6b6b59SJacob Faibussowitsch   size_ = 0;
320*0e6b6b59SJacob Faibussowitsch   PetscCallCXX(chunks_.clear());
321*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
322*0e6b6b59SJacob Faibussowitsch }
323*0e6b6b59SJacob Faibussowitsch 
324*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
325*0e6b6b59SJacob Faibussowitsch // MemoryBlock - Public API
326*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
327*0e6b6b59SJacob Faibussowitsch 
328*0e6b6b59SJacob Faibussowitsch // default constructor, allocates memory immediately
329*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
330*0e6b6b59SJacob Faibussowitsch template <typename U>
331*0e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S>::MemoryBlock(allocator_type *alloc, size_type s, const device::StreamBase<U> *stream) noexcept : size_(s), allocator_(alloc) {
332*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
333*0e6b6b59SJacob Faibussowitsch   PetscCallAbort(PETSC_COMM_SELF, alloc->allocate(&mem_, s, stream));
334*0e6b6b59SJacob Faibussowitsch   PetscAssertAbort(mem_, PETSC_COMM_SELF, PETSC_ERR_MEM, "Failed to allocate memory block of size %zu", s);
335*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturnVoid();
336*0e6b6b59SJacob Faibussowitsch }
337*0e6b6b59SJacob Faibussowitsch 
338*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
339*0e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S>::~MemoryBlock() noexcept(std::is_nothrow_destructible<chunk_list_type>::value) {
340*0e6b6b59SJacob Faibussowitsch   stream_type stream;
341*0e6b6b59SJacob Faibussowitsch 
342*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
343*0e6b6b59SJacob Faibussowitsch   PetscCallAbort(PETSC_COMM_SELF, clear_(&stream));
344*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturnVoid();
345*0e6b6b59SJacob Faibussowitsch }
346*0e6b6b59SJacob Faibussowitsch 
347*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
348*0e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S>::MemoryBlock(MemoryBlock &&other) noexcept : mem_(util::exchange(other.mem_, nullptr)), allocator_(other.allocator_), size_(util::exchange(other.size_, 0)), chunks_(std::move(other.chunks_)) { }
349*0e6b6b59SJacob Faibussowitsch 
350*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
351*0e6b6b59SJacob Faibussowitsch MemoryBlock<T, A, S> &MemoryBlock<T, A, S>::operator=(MemoryBlock &&other) noexcept {
352*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
353*0e6b6b59SJacob Faibussowitsch   if (this != &other) {
354*0e6b6b59SJacob Faibussowitsch     stream_type stream;
355*0e6b6b59SJacob Faibussowitsch 
356*0e6b6b59SJacob Faibussowitsch     PetscCallAbort(PETSC_COMM_SELF, clear_(&stream));
357*0e6b6b59SJacob Faibussowitsch     mem_       = util::exchange(other.mem_, nullptr);
358*0e6b6b59SJacob Faibussowitsch     allocator_ = other.allocator_;
359*0e6b6b59SJacob Faibussowitsch     size_      = util::exchange(other.size_, 0);
360*0e6b6b59SJacob Faibussowitsch     chunks_    = std::move(other.chunks_);
361*0e6b6b59SJacob Faibussowitsch   }
362*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(*this);
363*0e6b6b59SJacob Faibussowitsch }
364*0e6b6b59SJacob Faibussowitsch 
365*0e6b6b59SJacob Faibussowitsch /*
366*0e6b6b59SJacob Faibussowitsch   MemoryBock::owns_pointer - returns true if this block owns a pointer, false otherwise
367*0e6b6b59SJacob Faibussowitsch */
368*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
369*0e6b6b59SJacob Faibussowitsch inline bool MemoryBlock<T, A, S>::owns_pointer(const T *ptr) const noexcept {
370*0e6b6b59SJacob Faibussowitsch   // each pool is linear in memory, so it suffices to check the bounds
371*0e6b6b59SJacob Faibussowitsch   return (ptr >= mem_) && (ptr < std::next(mem_, size()));
372*0e6b6b59SJacob Faibussowitsch }
373*0e6b6b59SJacob Faibussowitsch 
374*0e6b6b59SJacob Faibussowitsch /*
375*0e6b6b59SJacob Faibussowitsch   MemoryBlock::try_allocate_chunk - try to get a chunk from this MemoryBlock
376*0e6b6b59SJacob Faibussowitsch 
377*0e6b6b59SJacob Faibussowitsch   Input Parameters:
378*0e6b6b59SJacob Faibussowitsch + req_size - the requested size of the allocation (in elements)
379*0e6b6b59SJacob Faibussowitsch . ptr      - ptr to fill
380*0e6b6b59SJacob Faibussowitsch - stream   - stream to fill the pointer on
381*0e6b6b59SJacob Faibussowitsch 
382*0e6b6b59SJacob Faibussowitsch   Output Parameter:
383*0e6b6b59SJacob Faibussowitsch . success  - true if chunk was gotten, false otherwise
384*0e6b6b59SJacob Faibussowitsch 
385*0e6b6b59SJacob Faibussowitsch   Notes:
386*0e6b6b59SJacob Faibussowitsch   If the current memory could not satisfy the memory request, ptr is unchanged
387*0e6b6b59SJacob Faibussowitsch */
388*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
389*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_allocate_chunk(size_type req_size, T **ptr, const stream_type *stream, bool *success) noexcept {
390*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
391*0e6b6b59SJacob Faibussowitsch   *success = false;
392*0e6b6b59SJacob Faibussowitsch   if (req_size <= size()) {
393*0e6b6b59SJacob Faibussowitsch     const auto try_create_chunk = [&]() {
394*0e6b6b59SJacob Faibussowitsch       const auto was_empty     = chunks_.empty();
395*0e6b6b59SJacob Faibussowitsch       const auto block_alloced = was_empty ? 0 : chunks_.back().total_offset();
396*0e6b6b59SJacob Faibussowitsch 
397*0e6b6b59SJacob Faibussowitsch       PetscFunctionBegin;
398*0e6b6b59SJacob Faibussowitsch       if (block_alloced + req_size <= size()) {
399*0e6b6b59SJacob Faibussowitsch         PetscCallCXX(chunks_.emplace_back(block_alloced, req_size));
400*0e6b6b59SJacob Faibussowitsch         PetscCall(chunks_.back().claim(stream, req_size, success));
401*0e6b6b59SJacob Faibussowitsch         *ptr = mem_ + block_alloced;
402*0e6b6b59SJacob Faibussowitsch         if (was_empty) PetscAssert(*success, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Failed to claim chunk (of size %zu) even though block (of size %zu) was empty!", req_size, size());
403*0e6b6b59SJacob Faibussowitsch       }
404*0e6b6b59SJacob Faibussowitsch       PetscFunctionReturn(0);
405*0e6b6b59SJacob Faibussowitsch     };
406*0e6b6b59SJacob Faibussowitsch     const auto try_find_open_chunk = [&](bool serialize = false) {
407*0e6b6b59SJacob Faibussowitsch       PetscFunctionBegin;
408*0e6b6b59SJacob Faibussowitsch       for (auto &chunk : chunks_) {
409*0e6b6b59SJacob Faibussowitsch         PetscCall(chunk.claim(stream, req_size, success, serialize));
410*0e6b6b59SJacob Faibussowitsch         if (*success) {
411*0e6b6b59SJacob Faibussowitsch           *ptr = mem_ + chunk.start();
412*0e6b6b59SJacob Faibussowitsch           break;
413*0e6b6b59SJacob Faibussowitsch         }
414*0e6b6b59SJacob Faibussowitsch       }
415*0e6b6b59SJacob Faibussowitsch       PetscFunctionReturn(0);
416*0e6b6b59SJacob Faibussowitsch     };
417*0e6b6b59SJacob Faibussowitsch 
418*0e6b6b59SJacob Faibussowitsch     // search previously distributed chunks, but only claim one if it is on the same stream
419*0e6b6b59SJacob Faibussowitsch     // as us
420*0e6b6b59SJacob Faibussowitsch     PetscCall(try_find_open_chunk());
421*0e6b6b59SJacob Faibussowitsch 
422*0e6b6b59SJacob Faibussowitsch     // if we are here we couldn't reuse one of our own chunks so check first if the pool
423*0e6b6b59SJacob Faibussowitsch     // has room for a new one
424*0e6b6b59SJacob Faibussowitsch     if (!*success) PetscCall(try_create_chunk());
425*0e6b6b59SJacob Faibussowitsch 
426*0e6b6b59SJacob Faibussowitsch     // try pruning dead chunks off the back, note we do this regardless of whether we are
427*0e6b6b59SJacob Faibussowitsch     // successful
428*0e6b6b59SJacob Faibussowitsch     while (chunks_.back().can_claim(stream, 0, false)) {
429*0e6b6b59SJacob Faibussowitsch       PetscCallCXX(chunks_.pop_back());
430*0e6b6b59SJacob Faibussowitsch       if (chunks_.empty()) {
431*0e6b6b59SJacob Faibussowitsch         // if chunks are empty it implies we have managed to claim (and subsequently destroy)
432*0e6b6b59SJacob Faibussowitsch         // our own chunk twice! something has gone wrong
433*0e6b6b59SJacob Faibussowitsch         PetscAssert(!*success, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Successfully claimed a chunk (of size %zu, from block of size %zu) but have now managed to claim it for a second time (and destroyed it)!", req_size, size());
434*0e6b6b59SJacob Faibussowitsch         break;
435*0e6b6b59SJacob Faibussowitsch       }
436*0e6b6b59SJacob Faibussowitsch     }
437*0e6b6b59SJacob Faibussowitsch 
438*0e6b6b59SJacob Faibussowitsch     // if previously unsuccessful see if enough space has opened up due to pruning. note that
439*0e6b6b59SJacob Faibussowitsch     // if the chunk list was emptied from the pruning this call must succeed in allocating a
440*0e6b6b59SJacob Faibussowitsch     // chunk, otherwise something is wrong
441*0e6b6b59SJacob Faibussowitsch     if (!*success) PetscCall(try_create_chunk());
442*0e6b6b59SJacob Faibussowitsch 
443*0e6b6b59SJacob Faibussowitsch     // last resort, iterate over all chunks and see if we can steal one by waiting on the
444*0e6b6b59SJacob Faibussowitsch     // current owner to finish using it
445*0e6b6b59SJacob Faibussowitsch     if (!*success) PetscCall(try_find_open_chunk(true));
446*0e6b6b59SJacob Faibussowitsch 
447*0e6b6b59SJacob Faibussowitsch     // sets memory to NaN or infinity depending on the type to catch out uninitialized memory
448*0e6b6b59SJacob Faibussowitsch     // accesses.
449*0e6b6b59SJacob Faibussowitsch     if (PetscDefined(USE_DEBUG) && *success) PetscCall(allocator_->set_canary(*ptr, req_size, stream));
450*0e6b6b59SJacob Faibussowitsch   }
451*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
452*0e6b6b59SJacob Faibussowitsch }
453*0e6b6b59SJacob Faibussowitsch 
454*0e6b6b59SJacob Faibussowitsch /*
455*0e6b6b59SJacob Faibussowitsch   MemoryBlock::try_deallocate_chunk - try to restore a chunk to this MemoryBlock
456*0e6b6b59SJacob Faibussowitsch 
457*0e6b6b59SJacob Faibussowitsch   Input Parameters:
458*0e6b6b59SJacob Faibussowitsch + ptr     - ptr to restore
459*0e6b6b59SJacob Faibussowitsch - stream  - stream to restore the pointer on
460*0e6b6b59SJacob Faibussowitsch 
461*0e6b6b59SJacob Faibussowitsch   Output Parameter:
462*0e6b6b59SJacob Faibussowitsch . success - true if chunk was restored, false otherwise
463*0e6b6b59SJacob Faibussowitsch 
464*0e6b6b59SJacob Faibussowitsch   Notes:
465*0e6b6b59SJacob Faibussowitsch   ptr is set to nullptr on successful restore, and is unchanged otherwise. If the ptr is owned
466*0e6b6b59SJacob Faibussowitsch   by this MemoryBlock then it is restored on stream. The same stream may recieve ptr again
467*0e6b6b59SJacob Faibussowitsch   without synchronization, but other streams may not do so until either serializing or the
468*0e6b6b59SJacob Faibussowitsch   stream is idle again.
469*0e6b6b59SJacob Faibussowitsch */
470*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
471*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_deallocate_chunk(T **ptr, const stream_type *stream, bool *success) noexcept {
472*0e6b6b59SJacob Faibussowitsch   chunk_type *chunk = nullptr;
473*0e6b6b59SJacob Faibussowitsch 
474*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
475*0e6b6b59SJacob Faibussowitsch   PetscCall(try_find_chunk(*ptr, &chunk));
476*0e6b6b59SJacob Faibussowitsch   if (chunk) {
477*0e6b6b59SJacob Faibussowitsch     PetscCall(chunk->release(stream));
478*0e6b6b59SJacob Faibussowitsch     *ptr     = nullptr;
479*0e6b6b59SJacob Faibussowitsch     *success = true;
480*0e6b6b59SJacob Faibussowitsch   } else {
481*0e6b6b59SJacob Faibussowitsch     *success = false;
482*0e6b6b59SJacob Faibussowitsch   }
483*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
484*0e6b6b59SJacob Faibussowitsch }
485*0e6b6b59SJacob Faibussowitsch 
486*0e6b6b59SJacob Faibussowitsch /*
487*0e6b6b59SJacob Faibussowitsch   MemoryBlock::try_find_chunk - try to find the chunk which owns ptr
488*0e6b6b59SJacob Faibussowitsch 
489*0e6b6b59SJacob Faibussowitsch   Input Parameter:
490*0e6b6b59SJacob Faibussowitsch . ptr - the pointer to lookk for
491*0e6b6b59SJacob Faibussowitsch 
492*0e6b6b59SJacob Faibussowitsch   Output Parameter:
493*0e6b6b59SJacob Faibussowitsch . ret_chunk - pointer to the owning chunk or nullptr if not found
494*0e6b6b59SJacob Faibussowitsch */
495*0e6b6b59SJacob Faibussowitsch template <typename T, typename A, typename S>
496*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode MemoryBlock<T, A, S>::try_find_chunk(const T *ptr, chunk_type **ret_chunk) noexcept {
497*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
498*0e6b6b59SJacob Faibussowitsch   *ret_chunk = nullptr;
499*0e6b6b59SJacob Faibussowitsch   if (owns_pointer(ptr)) {
500*0e6b6b59SJacob Faibussowitsch     const auto offset = static_cast<size_type>(ptr - mem_);
501*0e6b6b59SJacob Faibussowitsch 
502*0e6b6b59SJacob Faibussowitsch     for (auto &chunk : chunks_) {
503*0e6b6b59SJacob Faibussowitsch       if (chunk.start() == offset) {
504*0e6b6b59SJacob Faibussowitsch         *ret_chunk = &chunk;
505*0e6b6b59SJacob Faibussowitsch         break;
506*0e6b6b59SJacob Faibussowitsch       }
507*0e6b6b59SJacob Faibussowitsch     }
508*0e6b6b59SJacob Faibussowitsch 
509*0e6b6b59SJacob Faibussowitsch     PetscAssert(*ret_chunk, PETSC_COMM_SELF, PETSC_ERR_PLIB, "Failed to find %zu in block, even though it is within block range [%zu, %zu)", reinterpret_cast<uintptr_t>(ptr), reinterpret_cast<uintptr_t>(mem_), reinterpret_cast<uintptr_t>(std::next(mem_, size())));
510*0e6b6b59SJacob Faibussowitsch   }
511*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
512*0e6b6b59SJacob Faibussowitsch }
513*0e6b6b59SJacob Faibussowitsch 
514*0e6b6b59SJacob Faibussowitsch namespace detail {
515*0e6b6b59SJacob Faibussowitsch 
516*0e6b6b59SJacob Faibussowitsch template <typename T>
517*0e6b6b59SJacob Faibussowitsch struct real_type {
518*0e6b6b59SJacob Faibussowitsch   using type = T;
519*0e6b6b59SJacob Faibussowitsch };
520*0e6b6b59SJacob Faibussowitsch 
521*0e6b6b59SJacob Faibussowitsch template <>
522*0e6b6b59SJacob Faibussowitsch struct real_type<PetscScalar> {
523*0e6b6b59SJacob Faibussowitsch   using type = PetscReal;
524*0e6b6b59SJacob Faibussowitsch };
525*0e6b6b59SJacob Faibussowitsch 
526*0e6b6b59SJacob Faibussowitsch } // namespace detail
527*0e6b6b59SJacob Faibussowitsch 
528*0e6b6b59SJacob Faibussowitsch template <typename T>
529*0e6b6b59SJacob Faibussowitsch struct SegmentedMemoryPoolAllocatorBase {
530*0e6b6b59SJacob Faibussowitsch   using value_type      = T;
531*0e6b6b59SJacob Faibussowitsch   using size_type       = std::size_t;
532*0e6b6b59SJacob Faibussowitsch   using real_value_type = typename detail::real_type<T>::type;
533*0e6b6b59SJacob Faibussowitsch 
534*0e6b6b59SJacob Faibussowitsch   template <typename U>
535*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static PetscErrorCode allocate(value_type **, size_type, const device::StreamBase<U> *) noexcept;
536*0e6b6b59SJacob Faibussowitsch   template <typename U>
537*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static PetscErrorCode deallocate(value_type *, const device::StreamBase<U> *) noexcept;
538*0e6b6b59SJacob Faibussowitsch   template <typename U>
539*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static PetscErrorCode zero(value_type *, size_type, const device::StreamBase<U> *) noexcept;
540*0e6b6b59SJacob Faibussowitsch   template <typename U>
541*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static PetscErrorCode uninitialized_copy(value_type *, const value_type *, size_type, const device::StreamBase<U> *) noexcept;
542*0e6b6b59SJacob Faibussowitsch   template <typename U>
543*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD static PetscErrorCode set_canary(value_type *, size_type, const device::StreamBase<U> *) noexcept;
544*0e6b6b59SJacob Faibussowitsch };
545*0e6b6b59SJacob Faibussowitsch 
546*0e6b6b59SJacob Faibussowitsch template <typename T>
547*0e6b6b59SJacob Faibussowitsch template <typename U>
548*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::allocate(value_type **ptr, size_type n, const device::StreamBase<U> *) noexcept {
549*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
550*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscMalloc1(n, ptr));
551*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
552*0e6b6b59SJacob Faibussowitsch }
553*0e6b6b59SJacob Faibussowitsch 
554*0e6b6b59SJacob Faibussowitsch template <typename T>
555*0e6b6b59SJacob Faibussowitsch template <typename U>
556*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::deallocate(value_type *ptr, const device::StreamBase<U> *) noexcept {
557*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
558*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscFree(ptr));
559*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
560*0e6b6b59SJacob Faibussowitsch }
561*0e6b6b59SJacob Faibussowitsch 
562*0e6b6b59SJacob Faibussowitsch template <typename T>
563*0e6b6b59SJacob Faibussowitsch template <typename U>
564*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::zero(value_type *ptr, size_type n, const device::StreamBase<U> *) noexcept {
565*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
566*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscArrayzero(ptr, n));
567*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
568*0e6b6b59SJacob Faibussowitsch }
569*0e6b6b59SJacob Faibussowitsch 
570*0e6b6b59SJacob Faibussowitsch template <typename T>
571*0e6b6b59SJacob Faibussowitsch template <typename U>
572*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::uninitialized_copy(value_type *dest, const value_type *src, size_type n, const device::StreamBase<U> *) noexcept {
573*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
574*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscArraycpy(dest, src, n));
575*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
576*0e6b6b59SJacob Faibussowitsch }
577*0e6b6b59SJacob Faibussowitsch 
578*0e6b6b59SJacob Faibussowitsch template <typename T>
579*0e6b6b59SJacob Faibussowitsch template <typename U>
580*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPoolAllocatorBase<T>::set_canary(value_type *ptr, size_type n, const device::StreamBase<U> *) noexcept {
581*0e6b6b59SJacob Faibussowitsch   using limit_type            = std::numeric_limits<real_value_type>;
582*0e6b6b59SJacob Faibussowitsch   constexpr value_type canary = limit_type::has_signaling_NaN ? limit_type::signaling_NaN() : limit_type::max();
583*0e6b6b59SJacob Faibussowitsch 
584*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
585*0e6b6b59SJacob Faibussowitsch   for (size_type i = 0; i < n; ++i) ptr[i] = canary;
586*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
587*0e6b6b59SJacob Faibussowitsch }
588*0e6b6b59SJacob Faibussowitsch 
589*0e6b6b59SJacob Faibussowitsch } // namespace impl
590*0e6b6b59SJacob Faibussowitsch 
591*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
592*0e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool
593*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
594*0e6b6b59SJacob Faibussowitsch 
595*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType = device::DefaultStream, typename AllocType = impl::SegmentedMemoryPoolAllocatorBase<MemType>, std::size_t DefaultChunkSize = 256>
596*0e6b6b59SJacob Faibussowitsch class SegmentedMemoryPool;
597*0e6b6b59SJacob Faibussowitsch 
598*0e6b6b59SJacob Faibussowitsch // The actual memory pool class. It is in essence just a wrapper for a list of MemoryBlocks.
599*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize>
600*0e6b6b59SJacob Faibussowitsch class SegmentedMemoryPool : public RegisterFinalizeable<SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>> {
601*0e6b6b59SJacob Faibussowitsch public:
602*0e6b6b59SJacob Faibussowitsch   using value_type     = MemType;
603*0e6b6b59SJacob Faibussowitsch   using stream_type    = StreamType;
604*0e6b6b59SJacob Faibussowitsch   using allocator_type = AllocType;
605*0e6b6b59SJacob Faibussowitsch   using block_type     = impl::MemoryBlock<value_type, allocator_type, stream_type>;
606*0e6b6b59SJacob Faibussowitsch   using pool_type      = std::deque<block_type>;
607*0e6b6b59SJacob Faibussowitsch   using size_type      = typename block_type::size_type;
608*0e6b6b59SJacob Faibussowitsch 
609*0e6b6b59SJacob Faibussowitsch   explicit SegmentedMemoryPool(AllocType = AllocType{}, std::size_t = DefaultChunkSize) noexcept(std::is_nothrow_default_constructible<pool_type>::value);
610*0e6b6b59SJacob Faibussowitsch 
611*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode allocate(PetscInt, value_type **, const stream_type *) noexcept;
612*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode deallocate(value_type **, const stream_type *) noexcept;
613*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode reallocate(PetscInt, value_type **, const stream_type *) noexcept;
614*0e6b6b59SJacob Faibussowitsch 
615*0e6b6b59SJacob Faibussowitsch private:
616*0e6b6b59SJacob Faibussowitsch   pool_type      pool_;
617*0e6b6b59SJacob Faibussowitsch   allocator_type allocator_;
618*0e6b6b59SJacob Faibussowitsch   size_type      chunk_size_;
619*0e6b6b59SJacob Faibussowitsch 
620*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode make_block_(size_type, const stream_type *) noexcept;
621*0e6b6b59SJacob Faibussowitsch 
622*0e6b6b59SJacob Faibussowitsch   friend class RegisterFinalizeable<SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>>;
623*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode register_finalize_(const stream_type *) noexcept;
624*0e6b6b59SJacob Faibussowitsch   PETSC_NODISCARD PetscErrorCode finalize_() noexcept;
625*0e6b6b59SJacob Faibussowitsch };
626*0e6b6b59SJacob Faibussowitsch 
627*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
628*0e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool - Private API
629*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
630*0e6b6b59SJacob Faibussowitsch 
631*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize>
632*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::make_block_(size_type size, const stream_type *stream) noexcept {
633*0e6b6b59SJacob Faibussowitsch   const auto block_size = std::max(size, chunk_size_);
634*0e6b6b59SJacob Faibussowitsch 
635*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
636*0e6b6b59SJacob Faibussowitsch   PetscCallCXX(pool_.emplace_back(&allocator_, block_size, stream));
637*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscInfo(nullptr, "Allocated new block of size %zu, total %zu blocks\n", block_size, pool_.size()));
638*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
639*0e6b6b59SJacob Faibussowitsch }
640*0e6b6b59SJacob Faibussowitsch 
641*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize>
642*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::register_finalize_(const stream_type *stream) noexcept {
643*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
644*0e6b6b59SJacob Faibussowitsch   PetscCall(make_block_(chunk_size_, stream));
645*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
646*0e6b6b59SJacob Faibussowitsch }
647*0e6b6b59SJacob Faibussowitsch 
648*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize>
649*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::finalize_() noexcept {
650*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
651*0e6b6b59SJacob Faibussowitsch   PetscCallCXX(pool_.clear());
652*0e6b6b59SJacob Faibussowitsch   chunk_size_ = DefaultChunkSize;
653*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
654*0e6b6b59SJacob Faibussowitsch }
655*0e6b6b59SJacob Faibussowitsch 
656*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
657*0e6b6b59SJacob Faibussowitsch // SegmentedMemoryPool - Public API
658*0e6b6b59SJacob Faibussowitsch // ==========================================================================================
659*0e6b6b59SJacob Faibussowitsch 
660*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize>
661*0e6b6b59SJacob Faibussowitsch inline SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::SegmentedMemoryPool(AllocType alloc, std::size_t size) noexcept(std::is_nothrow_default_constructible<pool_type>::value) :
662*0e6b6b59SJacob Faibussowitsch   allocator_(std::move(alloc)), chunk_size_(size) { }
663*0e6b6b59SJacob Faibussowitsch 
664*0e6b6b59SJacob Faibussowitsch /*
665*0e6b6b59SJacob Faibussowitsch   SegmentedMemoryPool::allocate - get an allocation from the memory pool
666*0e6b6b59SJacob Faibussowitsch 
667*0e6b6b59SJacob Faibussowitsch   Input Parameters:
668*0e6b6b59SJacob Faibussowitsch + req_size - size (in elements) to get
669*0e6b6b59SJacob Faibussowitsch . ptr      - the pointer to hold the allocation
670*0e6b6b59SJacob Faibussowitsch - stream   - the stream on which to get the allocation
671*0e6b6b59SJacob Faibussowitsch 
672*0e6b6b59SJacob Faibussowitsch   Output Parameter:
673*0e6b6b59SJacob Faibussowitsch . ptr - the pointer holding the allocation
674*0e6b6b59SJacob Faibussowitsch 
675*0e6b6b59SJacob Faibussowitsch   Notes:
676*0e6b6b59SJacob Faibussowitsch   req_size cannot be negative. If req_size if zero, ptr is set to nullptr
677*0e6b6b59SJacob Faibussowitsch */
678*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize>
679*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::allocate(PetscInt req_size, value_type **ptr, const stream_type *stream) noexcept {
680*0e6b6b59SJacob Faibussowitsch   const auto size  = static_cast<size_type>(req_size);
681*0e6b6b59SJacob Faibussowitsch   auto       found = false;
682*0e6b6b59SJacob Faibussowitsch 
683*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
684*0e6b6b59SJacob Faibussowitsch   PetscAssert(req_size >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Requested memory amount (%" PetscInt_FMT ") must be >= 0", req_size);
685*0e6b6b59SJacob Faibussowitsch   PetscValidPointer(ptr, 2);
686*0e6b6b59SJacob Faibussowitsch   PetscValidPointer(stream, 3);
687*0e6b6b59SJacob Faibussowitsch   *ptr = nullptr;
688*0e6b6b59SJacob Faibussowitsch   if (!req_size) PetscFunctionReturn(0);
689*0e6b6b59SJacob Faibussowitsch   PetscCall(this->register_finalize(PETSC_COMM_SELF, stream));
690*0e6b6b59SJacob Faibussowitsch   for (auto &block : pool_) {
691*0e6b6b59SJacob Faibussowitsch     PetscCall(block.try_allocate_chunk(size, ptr, stream, &found));
692*0e6b6b59SJacob Faibussowitsch     if (PetscLikely(found)) PetscFunctionReturn(0);
693*0e6b6b59SJacob Faibussowitsch   }
694*0e6b6b59SJacob Faibussowitsch 
695*0e6b6b59SJacob Faibussowitsch   PetscCall(PetscInfo(nullptr, "Could not find an open block in the pool (%zu blocks) (requested size %zu), allocating new block\n", pool_.size(), size));
696*0e6b6b59SJacob Faibussowitsch   // if we are here we couldn't find an open block in the pool, so make a new block
697*0e6b6b59SJacob Faibussowitsch   PetscCall(make_block_(size, stream));
698*0e6b6b59SJacob Faibussowitsch   // and assign it
699*0e6b6b59SJacob Faibussowitsch   PetscCall(pool_.back().try_allocate_chunk(size, ptr, stream, &found));
700*0e6b6b59SJacob Faibussowitsch   PetscAssert(found, PETSC_COMM_SELF, PETSC_ERR_MEM, "Failed to get a suitable memory chunk (of size %zu) from newly allocated memory block (size %zu)", size, pool_.back().size());
701*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
702*0e6b6b59SJacob Faibussowitsch }
703*0e6b6b59SJacob Faibussowitsch 
704*0e6b6b59SJacob Faibussowitsch /*
705*0e6b6b59SJacob Faibussowitsch   SegmentedMemoryPool::deallocate - release a pointer back to the memory pool
706*0e6b6b59SJacob Faibussowitsch 
707*0e6b6b59SJacob Faibussowitsch   Input Parameters:
708*0e6b6b59SJacob Faibussowitsch + ptr    - the pointer to release
709*0e6b6b59SJacob Faibussowitsch - stream - the stream to release it on
710*0e6b6b59SJacob Faibussowitsch 
711*0e6b6b59SJacob Faibussowitsch   Notes:
712*0e6b6b59SJacob Faibussowitsch   If ptr is not owned by the pool it is unchanged.
713*0e6b6b59SJacob Faibussowitsch */
714*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize>
715*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::deallocate(value_type **ptr, const stream_type *stream) noexcept {
716*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
717*0e6b6b59SJacob Faibussowitsch   PetscValidPointer(ptr, 1);
718*0e6b6b59SJacob Faibussowitsch   PetscValidPointer(stream, 2);
719*0e6b6b59SJacob Faibussowitsch   // nobody owns a nullptr, and if they do then they have bigger problems
720*0e6b6b59SJacob Faibussowitsch   if (!*ptr) PetscFunctionReturn(0);
721*0e6b6b59SJacob Faibussowitsch   for (auto &block : pool_) {
722*0e6b6b59SJacob Faibussowitsch     auto found = false;
723*0e6b6b59SJacob Faibussowitsch 
724*0e6b6b59SJacob Faibussowitsch     PetscCall(block.try_deallocate_chunk(ptr, stream, &found));
725*0e6b6b59SJacob Faibussowitsch     if (PetscLikely(found)) break;
726*0e6b6b59SJacob Faibussowitsch   }
727*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
728*0e6b6b59SJacob Faibussowitsch }
729*0e6b6b59SJacob Faibussowitsch 
730*0e6b6b59SJacob Faibussowitsch /*
731*0e6b6b59SJacob Faibussowitsch   SegmentedMemoryPool::reallocate - Resize an allocated buffer
732*0e6b6b59SJacob Faibussowitsch 
733*0e6b6b59SJacob Faibussowitsch   Input Parameters:
734*0e6b6b59SJacob Faibussowitsch + new_req_size - the new buffer size
735*0e6b6b59SJacob Faibussowitsch . ptr          - pointer to the buffer
736*0e6b6b59SJacob Faibussowitsch - stream       - stream to resize with
737*0e6b6b59SJacob Faibussowitsch 
738*0e6b6b59SJacob Faibussowitsch   Ouput Parameter:
739*0e6b6b59SJacob Faibussowitsch . ptr - pointer to the new region
740*0e6b6b59SJacob Faibussowitsch 
741*0e6b6b59SJacob Faibussowitsch   Notes:
742*0e6b6b59SJacob Faibussowitsch   ptr must have been allocated by the pool.
743*0e6b6b59SJacob Faibussowitsch 
744*0e6b6b59SJacob Faibussowitsch   It's OK to shrink the buffer, even down to 0 (in which case it is just deallocated).
745*0e6b6b59SJacob Faibussowitsch */
746*0e6b6b59SJacob Faibussowitsch template <typename MemType, typename StreamType, typename AllocType, std::size_t DefaultChunkSize>
747*0e6b6b59SJacob Faibussowitsch inline PetscErrorCode SegmentedMemoryPool<MemType, StreamType, AllocType, DefaultChunkSize>::reallocate(PetscInt new_req_size, value_type **ptr, const stream_type *stream) noexcept {
748*0e6b6b59SJacob Faibussowitsch   using chunk_type = typename block_type::chunk_type;
749*0e6b6b59SJacob Faibussowitsch 
750*0e6b6b59SJacob Faibussowitsch   const auto  new_size = static_cast<size_type>(new_req_size);
751*0e6b6b59SJacob Faibussowitsch   const auto  old_ptr  = *ptr;
752*0e6b6b59SJacob Faibussowitsch   chunk_type *chunk    = nullptr;
753*0e6b6b59SJacob Faibussowitsch 
754*0e6b6b59SJacob Faibussowitsch   PetscFunctionBegin;
755*0e6b6b59SJacob Faibussowitsch   PetscAssert(new_req_size >= 0, PETSC_COMM_SELF, PETSC_ERR_ARG_OUTOFRANGE, "Requested memory amount (%" PetscInt_FMT ") must be >= 0", new_req_size);
756*0e6b6b59SJacob Faibussowitsch   PetscValidPointer(ptr, 2);
757*0e6b6b59SJacob Faibussowitsch   PetscValidPointer(stream, 3);
758*0e6b6b59SJacob Faibussowitsch 
759*0e6b6b59SJacob Faibussowitsch   // if reallocating to zero, just free
760*0e6b6b59SJacob Faibussowitsch   if (PetscUnlikely(new_size == 0)) {
761*0e6b6b59SJacob Faibussowitsch     PetscCall(deallocate(ptr, stream));
762*0e6b6b59SJacob Faibussowitsch     PetscFunctionReturn(0);
763*0e6b6b59SJacob Faibussowitsch   }
764*0e6b6b59SJacob Faibussowitsch 
765*0e6b6b59SJacob Faibussowitsch   // search the blocks for the owning chunk
766*0e6b6b59SJacob Faibussowitsch   for (auto &block : pool_) {
767*0e6b6b59SJacob Faibussowitsch     PetscCall(block.try_find_chunk(old_ptr, &chunk));
768*0e6b6b59SJacob Faibussowitsch     if (chunk) break; // found
769*0e6b6b59SJacob Faibussowitsch   }
770*0e6b6b59SJacob Faibussowitsch   PetscAssert(chunk, PETSC_COMM_SELF, PETSC_ERR_ARG_WRONG, "Memory pool does not own %p, so cannot reallocate it", *ptr);
771*0e6b6b59SJacob Faibussowitsch 
772*0e6b6b59SJacob Faibussowitsch   if (chunk->capacity() < new_size) {
773*0e6b6b59SJacob Faibussowitsch     // chunk does not have enough room, need to grab a fresh chunk and copy to it
774*0e6b6b59SJacob Faibussowitsch     *ptr = nullptr;
775*0e6b6b59SJacob Faibussowitsch     PetscCall(chunk->release(stream));
776*0e6b6b59SJacob Faibussowitsch     PetscCall(allocate(new_size, ptr, stream));
777*0e6b6b59SJacob Faibussowitsch     PetscCall(allocator_.uninitialized_copy(*ptr, old_ptr, new_size, stream));
778*0e6b6b59SJacob Faibussowitsch   } else {
779*0e6b6b59SJacob Faibussowitsch     // chunk had enough room we can simply grow (or shrink) to fit the new size
780*0e6b6b59SJacob Faibussowitsch     PetscCall(chunk->resize(new_size));
781*0e6b6b59SJacob Faibussowitsch   }
782*0e6b6b59SJacob Faibussowitsch   PetscFunctionReturn(0);
783*0e6b6b59SJacob Faibussowitsch }
784*0e6b6b59SJacob Faibussowitsch 
785*0e6b6b59SJacob Faibussowitsch } // namespace memory
786*0e6b6b59SJacob Faibussowitsch 
787*0e6b6b59SJacob Faibussowitsch } // namespace Petsc
788*0e6b6b59SJacob Faibussowitsch 
789*0e6b6b59SJacob Faibussowitsch #endif // PETSC_SEGMENTEDMEMPOOL_HPP
790