• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2024 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include "pw_allocator/capability.h"
17 #include "pw_allocator/layout.h"
18 #include "pw_allocator/unique_ptr.h"
19 #include "pw_result/result.h"
20 #include "pw_status/status.h"
21 #include "pw_status/status_with_size.h"
22 
23 namespace pw {
24 
25 /// Abstract interface for releasing memory.
26 class Deallocator {
27  protected:
28   // Alias types and variables needed for SFINAE below.
29   template <typename T>
30   static constexpr bool is_bounded_array_v =
31       allocator::internal::is_bounded_array_v<T>;
32 
33   template <typename T>
34   static constexpr bool is_unbounded_array_v =
35       allocator::internal::is_unbounded_array_v<T>;
36 
37  public:
38   using Capabilities = allocator::Capabilities;
39   using Capability = allocator::Capability;
40   using Layout = allocator::Layout;
41 
42   virtual ~Deallocator() = default;
43 
capabilities()44   constexpr const Capabilities& capabilities() const { return capabilities_; }
45 
46   /// Returns whether a given capabilityis enabled for this object.
HasCapability(Capability capability)47   bool HasCapability(Capability capability) const {
48     return capabilities_.has(capability);
49   }
50 
51   /// Releases a previously-allocated block of memory.
52   ///
53   /// The given pointer must have been previously provided by this memory
54   /// resource; otherwise the behavior is undefined.
55   ///
56   /// @param[in]  ptr           Pointer to previously-allocated memory.
Deallocate(void * ptr)57   void Deallocate(void* ptr) {
58     if (ptr != nullptr) {
59       DoDeallocate(ptr);
60     }
61   }
62 
63   /// Deprecated version of `Deallocate` that takes a `Layout`.
64   /// Do not use this method. It will be removed.
65   /// TODO(b/326509341): Remove when downstream consumers migrate.
Deallocate(void * ptr,Layout layout)66   void Deallocate(void* ptr, Layout layout) {
67     if (ptr != nullptr) {
68       DoDeallocate(ptr, layout);
69     }
70   }
71 
72   /// Destroys the object at ``ptr`` and deallocates the associated memory.
73   ///
74   /// The given pointer must have been previously obtained from a call to
75   /// ``New`` using the same object; otherwise the behavior is undefined.
76   ///
77   /// @param[in] ptr      Pointer to previously-allocated object.
78   template <typename T>
Delete(T * ptr)79   void Delete(T* ptr) {
80     if (!capabilities_.has(Capability::kSkipsDestroy)) {
81       std::destroy_at(ptr);
82     }
83     Deallocate(ptr);
84   }
85 
86   /// Returns the total amount of memory provided by this object.
87   ///
88   /// This is an optional method. Some memory providers may not have an easily
89   /// defined capacity, e.g. the system allocator. If implemented, the returned
90   /// capacity may be less than the memory originally given to an allocator,
91   /// e.g. if the allocator must align the region of memory, its capacity may be
92   /// reduced.
GetCapacity()93   StatusWithSize GetCapacity() const {
94     auto result = DoGetInfo(InfoType::kCapacity, nullptr);
95     return StatusWithSize(result.status(), Layout::Unwrap(result).size());
96   }
97 
98   /// Returns whether the given object is the same as this one.
99   ///
100   /// This method is used instead of ``operator==`` in keeping with
101   /// ``std::pmr::memory_resource::is_equal``. There currently is no
102   /// corresponding virtual ``DoIsEqual``, as objects that would
103   /// require ``dynamic_cast`` to properly determine equality are not supported.
104   /// This method will allow the interface to remain unchanged should a future
105   /// need for such objects arise.
106   ///
107   /// @param[in]  other       Object to compare with this object.
IsEqual(const Deallocator & other)108   bool IsEqual(const Deallocator& other) const { return this == &other; }
109 
110   // See WrapUnique below. Disallow calls with explicitly-sized array types like
111   // `T[kN]`.
112   template <typename T,
113             int&... kExplicitGuard,
114             std::enable_if_t<is_bounded_array_v<T>, int> = 0,
115             typename... Args>
116   void WrapUnique(Args&&...) = delete;
117 
118  protected:
119   /// TODO(b/326509341): Remove when downstream consumers migrate.
120   constexpr Deallocator() = default;
121 
Deallocator(const Capabilities & capabilities)122   explicit constexpr Deallocator(const Capabilities& capabilities)
123       : capabilities_(capabilities) {}
124 
125   /// Wraps an object of type ``T`` in a ``UniquePtr``
126   ///
127   /// @param[in]  ptr         Pointer to memory provided by this object.
128   template <typename T, std::enable_if_t<!std::is_array_v<T>, int> = 0>
WrapUnique(T * ptr)129   [[nodiscard]] UniquePtr<T> WrapUnique(T* ptr) {
130     return UniquePtr<T>(ptr, this);
131   }
132 
133   /// Wraps an array of type ``T`` in a ``UniquePtr``
134   ///
135   /// @param[in]  ptr         Pointer to memory provided by this object.
136   /// @param[in]  size        The size of the array.
137   template <typename T,
138             int&... kExplicitGuard,
139             typename ElementType = std::remove_extent_t<T>,
140             std::enable_if_t<is_unbounded_array_v<T>, int> = 0>
WrapUnique(ElementType * ptr,size_t size)141   [[nodiscard]] UniquePtr<T> WrapUnique(ElementType* ptr, size_t size) {
142     return UniquePtr<T>(ptr, size, this);
143   }
144 
145   /// Deprecated version of `WrapUnique` with a different name and templated on
146   /// the object type instead of the array type.
147   /// Do not use this method. It will be removed.
148   /// TODO(b/326509341): Remove when downstream consumers migrate.
149   template <typename T>
WrapUniqueArray(T * ptr,size_t size)150   [[nodiscard]] UniquePtr<T[]> WrapUniqueArray(T* ptr, size_t size) {
151     return WrapUnique<T[]>(ptr, size);
152   }
153 
154   /// Indicates what kind of information to retrieve using `GetInfo`.
155   ///
156   /// Note that this enum is considered open, and may be extended in the future.
157   /// As a result, implementers of `DoGetInfo` should include a default case
158   /// that handles unrecognized info types. If building with `-Wswitch-enum`,
159   /// you will also want to locally disable that diagnostic and build with
160   /// `-Wswitch-default` instead, e.g.:
161   ///
162   /// @code{.cpp}
163   /// class MyAllocator : public Allocator {
164   ///  private:
165   ///   Layout MyGetLayoutFromPointer(const void* ptr) { /* ... */ }
166   ///
167   ///   Result<Layout> DoGetInfo(InfoType info_type, const void* ptr) override {
168   ///     PW_MODIFY_DIAGNOSTICS_PUSH();
169   ///     PW_MODIFY_DIAGNOSTIC(ignored, "-Wswitch-enum");
170   ///     switch(info_type) {
171   ///       case InfoType::kAllocatedLayoutOf:
172   ///         return MyGetLayoutFromPointer(ptr);
173   ///       default:
174   ///         return Status::Unimplmented();
175   ///     }
176   ///     PW_MODIFY_DIAGNOSTICS_POP();
177   ///   }
178   /// };
179   /// @endcode
180   ///
181   /// See also `GetInfo`.
182   enum class InfoType {
183     /// If supported, `GetInfo` will return `OK` with the `Layout` of the
184     /// requested memory associated with the given pointer, or `NOT_FOUND` if
185     /// the pointer is not recognized.
186     ///
187     /// The requested layout may differ from either the layout of usable memory,
188     /// the layout of memory used to fulfill the request, or both.
189     ///
190     /// For example, it may have a smaller size than the usable memory if the
191     /// latter was padded to an alignment boundary, or may have a less strict
192     /// alignment than the actual memory.
193     kRequestedLayoutOf,
194 
195     /// If supported, `GetInfo` will return `OK` with the `Layout` of the
196     /// usable memory associated with the given pointer, or `NOT_FOUND` if
197     /// the pointer is not recognized.
198 
199     /// The usable layout may from either the requested layout, the layout of
200     /// memory used to fulfill the request, or both.
201     ///
202     /// For example, it may have a larger size than the requested layout if it
203     /// was padded to an alignment boundary, but may be less than the acutal
204     /// memory if the object includes some overhead for metadata.
205     kUsableLayoutOf,
206 
207     /// If supported, `GetInfo` will return `OK` with the `Layout` of the
208     /// allocated memory associated with the given pointer, or `NOT_FOUND` if
209     /// the pointer is not recognized.
210     ///
211     /// The layout of memory used to fulfill a request may differ from either
212     /// the requested layout, the layout of the usable memory, or both.
213     ///
214     /// For example, it may have a larger size than the requested layout or the
215     /// layout of usable memory if the object includes some overhead for
216     /// metadata.
217     kAllocatedLayoutOf,
218 
219     /// If supported, `GetInfo` will return `OK` with a `Layout` whose size
220     /// is the total number of bytes that can be allocated by this object, and
221     /// whose alignment is the minimum alignment of any allocation.
222     ///
223     /// The given pointer is ignored.
224     kCapacity,
225 
226     /// If supported, `GetInfo` will return `OK` with a default `Layout` if the
227     /// given pointer was provided by this object, or `NOT_FOUND`.
228     ///
229     /// This MUST only be used to dispatch between two or more objects
230     /// associated with non-overlapping regions of memory. Do NOT use it to
231     /// determine if this object can deallocate pointers. Callers MUST only
232     /// deallocate memory using the same ``Deallocator`` that provided it.
233     kRecognizes,
234   };
235 
236   /// Returns deallocator-specific information about allocations.
237   ///
238   /// Deallocators may support any number of `InfoType`s. See that type for what
239   /// each supported type returns. For unsupported types, this method returns
240   /// `UNIMPLEMENTED`.
GetInfo(InfoType info_type,const void * ptr)241   Result<Layout> GetInfo(InfoType info_type, const void* ptr) const {
242     return DoGetInfo(info_type, ptr);
243   }
244 
245   /// @copydoc GetInfo
246   ///
247   /// This method is protected in order to restrict it to object
248   /// implementations. It is static and takes an ``deallocator`` parameter in
249   /// order to allow forwarding allocators to call it on wrapped allocators.
GetInfo(const Deallocator & deallocator,InfoType info_type,const void * ptr)250   static Result<Layout> GetInfo(const Deallocator& deallocator,
251                                 InfoType info_type,
252                                 const void* ptr) {
253     return deallocator.DoGetInfo(info_type, ptr);
254   }
255 
256   /// Convenience wrapper of `DoGetInfo` for getting the requested layout
257   /// associated with a pointer.
GetRequestedLayout(const void * ptr)258   Result<Layout> GetRequestedLayout(const void* ptr) const {
259     return DoGetInfo(InfoType::kRequestedLayoutOf, ptr);
260   }
261 
262   /// Static version of `GetRequestedLayout` that allows forwarding allocators
263   /// to call it on wrapped allocators.
GetRequestedLayout(const Deallocator & deallocator,const void * ptr)264   static Result<Layout> GetRequestedLayout(const Deallocator& deallocator,
265                                            const void* ptr) {
266     return deallocator.GetRequestedLayout(ptr);
267   }
268 
269   /// Convenience wrapper of `DoGetInfo` for getting the usable layout
270   /// associated with a pointer.
GetUsableLayout(const void * ptr)271   Result<Layout> GetUsableLayout(const void* ptr) const {
272     return DoGetInfo(InfoType::kUsableLayoutOf, ptr);
273   }
274 
275   /// Static version of `GetUsableLayout` that allows forwarding allocators to
276   /// call it on wrapped allocators.
GetUsableLayout(const Deallocator & deallocator,const void * ptr)277   static Result<Layout> GetUsableLayout(const Deallocator& deallocator,
278                                         const void* ptr) {
279     return deallocator.GetUsableLayout(ptr);
280   }
281 
282   /// Convenience wrapper of `DoGetInfo` for getting the allocated layout
283   /// associated with a pointer.
GetAllocatedLayout(const void * ptr)284   Result<Layout> GetAllocatedLayout(const void* ptr) const {
285     return DoGetInfo(InfoType::kAllocatedLayoutOf, ptr);
286   }
287 
288   /// Static version of `GetAllocatedLayout` that allows forwarding allocators
289   /// to call it on wrapped allocators.
GetAllocatedLayout(const Deallocator & deallocator,const void * ptr)290   static Result<Layout> GetAllocatedLayout(const Deallocator& deallocator,
291                                            const void* ptr) {
292     return deallocator.GetAllocatedLayout(ptr);
293   }
294 
295   /// Convenience wrapper of `DoGetInfo` for getting whether the allocator
296   /// recognizes a pointer.
Recognizes(const void * ptr)297   bool Recognizes(const void* ptr) const {
298     return DoGetInfo(InfoType::kRecognizes, ptr).ok();
299   }
300 
301   /// Static version of `Recognizes` that allows forwarding allocators to call
302   /// it on wrapped allocators.
Recognizes(const Deallocator & deallocator,const void * ptr)303   static bool Recognizes(const Deallocator& deallocator, const void* ptr) {
304     return deallocator.Recognizes(ptr);
305   }
306 
307  private:
308   /// Virtual `Deallocate` function implemented by derived classes.
309   ///
310   /// @param[in]  ptr           Pointer to memory, guaranteed to not be null.
DoDeallocate(void *)311   virtual void DoDeallocate(void*) {
312     // This method will be pure virtual once consumer migrate from the deprected
313     // version that takes a `Layout` parameter. In the meantime, the check that
314     // this method is implemented is deferred to run-time.
315     PW_ASSERT(false);
316   }
317 
318   /// Deprecated version of `DoDeallocate` that takes a `Layout`.
319   /// Do not use this method. It will be removed.
DoDeallocate(void * ptr,Layout)320   virtual void DoDeallocate(void* ptr, Layout) { DoDeallocate(ptr); }
321 
322   /// Virtual `GetInfo` function that can be overridden by derived classes.
DoGetInfo(InfoType,const void *)323   virtual Result<Layout> DoGetInfo(InfoType, const void*) const {
324     return Status::Unimplemented();
325   }
326 
327   const Capabilities capabilities_;
328 };
329 
330 namespace allocator {
331 
332 // Alias for module consumers using the older name for the above type.
333 using Deallocator = ::pw::Deallocator;
334 
335 }  // namespace allocator
336 }  // namespace pw
337