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