1 // Copyright 2023 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 <cstddef> 17 18 #include "pw_allocator/capability.h" 19 #include "pw_allocator/deallocator.h" 20 #include "pw_allocator/layout.h" 21 #include "pw_allocator/unique_ptr.h" 22 #include "pw_numeric/checked_arithmetic.h" 23 #include "pw_result/result.h" 24 25 namespace pw { 26 27 /// Abstract interface for variable-layout memory allocation. 28 /// 29 /// The interface makes no guarantees about its implementation. Consumers of the 30 /// generic interface must not make any assumptions around allocator behavior, 31 /// thread safety, or performance. 32 class Allocator : public Deallocator { 33 public: 34 /// Allocates a block of memory with the specified size and alignment. 35 /// 36 /// Returns `nullptr` if the allocation cannot be made, or the `layout` has a 37 /// size of 0. 38 /// 39 /// @param[in] layout Describes the memory to be allocated. Allocate(Layout layout)40 void* Allocate(Layout layout) { 41 return layout.size() != 0 ? DoAllocate(layout) : nullptr; 42 } 43 44 /// Constructs an object of type `T` from the given `args` 45 /// 46 /// The return value is nullable, as allocating memory for the object may 47 /// fail. Callers must check for this error before using the resulting 48 /// pointer. 49 /// 50 /// @param[in] args... Arguments passed to the object constructor. 51 template <typename T, int&... kExplicitGuard, typename... Args> New(Args &&...args)52 [[nodiscard]] std::enable_if_t<!std::is_array_v<T>, T*> New(Args&&... args) { 53 void* ptr = Allocate(Layout::Of<T>()); 54 if (ptr == nullptr) { 55 return nullptr; 56 } 57 return new (ptr) T(std::forward<Args>(args)...); 58 } 59 60 /// Constructs an array of `count` objects of type `T` 61 /// 62 /// The return value is nullable, as allocating memory for the object may 63 /// fail. Callers must check for this error before using the resulting 64 /// pointer. 65 /// 66 /// @param[in] count Number of objects to allocate. 67 template <typename T, 68 int&... kExplicitGuard, 69 typename ElementType = std::remove_extent_t<T>, 70 std::enable_if_t<is_unbounded_array_v<T>, int> = 0> New(size_t count)71 [[nodiscard]] ElementType* New(size_t count) { 72 return New<T>(count, alignof(ElementType)); 73 } 74 75 /// Constructs an `alignment`-byte aligned array of `count` objects of type 76 /// `T` 77 /// 78 /// The return value is nullable, as allocating memory for the object may 79 /// fail. Callers must check for this error before using the resulting 80 /// pointer. 81 /// 82 /// @param[in] count Number of objects to allocate. 83 /// @param[in] alignment Alignment to use for the start of the array. 84 template <typename T, 85 int&... kExplicitGuard, 86 typename ElementType = std::remove_extent_t<T>, 87 std::enable_if_t<is_unbounded_array_v<T>, int> = 0> New(size_t count,size_t alignment)88 [[nodiscard]] ElementType* New(size_t count, size_t alignment) { 89 void* ptr = Allocate(Layout::Of<T>(count).Align(alignment)); 90 return ptr != nullptr ? new (ptr) ElementType[count] : nullptr; 91 } 92 93 /// Deprecated version of `New` with a different name and templated on 94 /// the object type instead of the array type. 95 /// Do not use this method. It will be removed. 96 /// TODO(b/326509341): Remove when downstream consumers migrate. 97 template <typename T> NewArray(size_t count)98 T* NewArray(size_t count) { 99 return New<T[]>(count, alignof(T)); 100 } 101 102 /// Deprecated version of `New` with a different name and templated on 103 /// the object type instead of the array type. 104 /// Do not use this method. It will be removed. 105 /// TODO(b/326509341): Remove when downstream consumers migrate. 106 template <typename T> NewArray(size_t count,size_t alignment)107 T* NewArray(size_t count, size_t alignment) { 108 return New<T[]>(count, alignment); 109 } 110 111 /// Constructs and object of type `T` from the given `args`, and wraps it in a 112 /// `UniquePtr` 113 /// 114 /// The returned value may contain null if allocating memory for the object 115 /// fails. Callers must check for null before using the `UniquePtr`. 116 /// 117 /// @param[in] args... Arguments passed to the object constructor. 118 template <typename T, 119 int&... kExplicitGuard, 120 std::enable_if_t<!std::is_array_v<T>, int> = 0, 121 typename... Args> MakeUnique(Args &&...args)122 [[nodiscard]] UniquePtr<T> MakeUnique(Args&&... args) { 123 return Deallocator::WrapUnique<T>(New<T>(std::forward<Args>(args)...)); 124 } 125 126 /// Constructs an `alignment`-byte aligned array of `count` objects, and wraps 127 /// it in a `UniquePtr` 128 /// 129 /// The returned value may contain null if allocating memory for the object 130 /// fails. Callers must check for null before using the `UniquePtr`. 131 /// 132 /// @tparam T An array type. 133 /// @param[in] count Number of objects to allocate. 134 template <typename T, 135 int&... kExplicitGuard, 136 std::enable_if_t<is_unbounded_array_v<T>, int> = 0> MakeUnique(size_t size)137 [[nodiscard]] UniquePtr<T> MakeUnique(size_t size) { 138 return MakeUnique<T>(size, alignof(std::remove_extent_t<T>)); 139 } 140 141 /// Constructs an `alignment`-byte aligned array of `count` objects of type 142 /// `T`, and wraps it in a `UniquePtr` 143 /// 144 /// The returned value may contain null if allocating memory for the object 145 /// fails. Callers must check for null before using the `UniquePtr`. 146 /// 147 /// @tparam T An array type. 148 /// @param[in] count Number of objects to allocate. 149 /// @param[in] alignment Object alignment. 150 template <typename T, 151 int&... kExplicitGuard, 152 std::enable_if_t<is_unbounded_array_v<T>, int> = 0> MakeUnique(size_t size,size_t alignment)153 [[nodiscard]] UniquePtr<T> MakeUnique(size_t size, size_t alignment) { 154 return Deallocator::WrapUnique<T>(New<T>(size, alignment), size); 155 } 156 157 /// Deprecated version of `MakeUnique` with a different name and templated on 158 /// the object type instead of the array type. 159 /// Do not use this method. It will be removed. 160 /// TODO(b/326509341): Remove when downstream consumers migrate. 161 template <typename T> MakeUniqueArray(size_t size)162 [[nodiscard]] UniquePtr<T[]> MakeUniqueArray(size_t size) { 163 return MakeUnique<T[]>(size, alignof(std::remove_extent_t<T>)); 164 } 165 166 /// Deprecated version of `MakeUnique` with a different name and templated on 167 /// the object type instead of the array type. 168 /// Do not use this method. It will be removed. 169 /// TODO(b/326509341): Remove when downstream consumers migrate. 170 template <typename T> MakeUniqueArray(size_t size,size_t alignment)171 [[nodiscard]] UniquePtr<T[]> MakeUniqueArray(size_t size, size_t alignment) { 172 return MakeUnique<T[]>(size, alignment); 173 } 174 175 template <typename T, 176 int&... kExplicitGuard, 177 std::enable_if_t<is_bounded_array_v<T>, int> = 0, 178 typename... Args> 179 void MakeUnique(Args&&...) = delete; 180 181 /// Modifies the size of an previously-allocated block of memory without 182 /// copying any data. 183 /// 184 /// Returns true if its size was changed without copying data to a new 185 /// allocation; otherwise returns false. 186 /// 187 /// In particular, it always returns true if the `old_layout.size()` equals 188 /// `new_size`, and always returns false if the given pointer is null, the 189 /// `old_layout.size()` is 0, or the `new_size` is 0. 190 /// 191 /// @param[in] ptr Pointer to previously-allocated memory. 192 /// @param[in] new_size Requested new size for the memory allocation. Resize(void * ptr,size_t new_size)193 bool Resize(void* ptr, size_t new_size) { 194 return ptr != nullptr && new_size != 0 && DoResize(ptr, new_size); 195 } 196 197 /// Deprecated version of `Resize` that takes a `Layout`. 198 /// Do not use this method. It will be removed. 199 /// TODO(b/326509341): Remove when downstream consumers migrate. Resize(void * ptr,Layout layout,size_t new_size)200 bool Resize(void* ptr, Layout layout, size_t new_size) { 201 return ptr != nullptr && new_size != 0 && DoResize(ptr, layout, new_size); 202 } 203 204 /// Modifies the size of a previously-allocated block of memory. 205 /// 206 /// Returns pointer to the modified block of memory, or `nullptr` if the 207 /// memory could not be modified. 208 /// 209 /// The data stored by the memory being modified must be trivially 210 /// copyable. If it is not, callers should themselves attempt to `Resize`, 211 /// then `Allocate`, move the data, and `Deallocate` as needed. 212 /// 213 /// If `nullptr` is returned, the block of memory is unchanged. In particular, 214 /// if the `new_layout` has a size of 0, the given pointer will NOT be 215 /// deallocated. 216 /// 217 /// TODO(b/331290408): This error condition needs to be better communicated to 218 /// module users, who may assume the pointer is freed. 219 /// 220 /// Unlike `Resize`, providing a null pointer will return a new allocation. 221 /// 222 /// If the request can be satisfied using `Resize`, the `alignment` parameter 223 /// may be ignored. 224 /// 225 /// @param[in] ptr Pointer to previously-allocated memory. 226 /// @param[in] new_layout Describes the memory to be allocated. Reallocate(void * ptr,Layout new_layout)227 void* Reallocate(void* ptr, Layout new_layout) { 228 if (new_layout.size() == 0) { 229 return nullptr; 230 } 231 if (ptr == nullptr) { 232 return Allocate(new_layout); 233 } 234 return DoReallocate(ptr, new_layout); 235 } 236 237 /// Deprecated version of `Reallocate` that takes a `Layout`. 238 /// Do not use this method. It will be removed. 239 /// TODO(b/326509341): Remove when downstream consumers migrate. Reallocate(void * ptr,Layout old_layout,size_t new_size)240 void* Reallocate(void* ptr, Layout old_layout, size_t new_size) { 241 if (new_size == 0) { 242 return nullptr; 243 } 244 if (ptr == nullptr) { 245 return Allocate(Layout(new_size, old_layout.alignment())); 246 } 247 return DoReallocate(ptr, old_layout, new_size); 248 } 249 250 /// Returns the total bytes that have been allocated by this allocator, or 251 /// `size_t(-1)` if this allocator does not track its total allocated bytes. GetAllocated()252 size_t GetAllocated() const { return DoGetAllocated(); } 253 254 protected: 255 /// TODO(b/326509341): Remove when downstream consumers migrate. 256 constexpr Allocator() = default; 257 Allocator(const Capabilities & capabilities)258 explicit constexpr Allocator(const Capabilities& capabilities) 259 : Deallocator(capabilities) {} 260 261 private: 262 /// Virtual `Allocate` function implemented by derived classes. 263 /// 264 /// @param[in] layout Describes the memory to be allocated. Guaranteed 265 /// to have a non-zero size. 266 virtual void* DoAllocate(Layout layout) = 0; 267 268 /// Virtual `Resize` function implemented by derived classes. 269 /// 270 /// The default implementation simply returns `false`, indicating that 271 /// resizing is not supported. 272 /// 273 /// @param[in] ptr Pointer to memory, guaranteed to not be null. 274 /// @param[in] new_size Requested size, guaranteed to be non-zero.. DoResize(void *,size_t)275 virtual bool DoResize(void* /*ptr*/, size_t /*new_size*/) { return false; } 276 277 /// Deprecated version of `DoResize` that takes a `Layout`. 278 /// Do not use this method. It will be removed. 279 /// TODO(b/326509341): Remove when downstream consumers migrate. DoResize(void *,Layout,size_t)280 virtual bool DoResize(void*, Layout, size_t) { return false; } 281 282 /// Virtual `Reallocate` function that can be overridden by derived classes. 283 /// 284 /// The default implementation will first try to `Resize` the data. If that is 285 /// unsuccessful, it will allocate an entirely new block, copy existing data, 286 /// and deallocate the given block. 287 /// 288 /// @param[in] ptr Pointer to memory, guaranteed to not be null. 289 /// @param[in] new_layout Describes the memory to be allocated. Guaranteed 290 /// to have a non-zero size. 291 virtual void* DoReallocate(void* ptr, Layout new_layout); 292 293 /// Deprecated version of `DoReallocate` that takes a `Layout`. 294 /// Do not use this method. It will be removed. 295 /// TODO(b/326509341): Remove when downstream consumers migrate. 296 virtual void* DoReallocate(void* ptr, Layout old_layout, size_t new_size); 297 298 /// Virtual `GetAllocated` function that can be overridden by derived classes. 299 /// 300 /// The default implementation simply returns `size_t(-1)`, indicating that 301 /// tracking total allocated bytes is not supported. DoGetAllocated()302 virtual size_t DoGetAllocated() const { return size_t(-1); } 303 }; 304 305 namespace allocator { 306 307 // Alias for module consumers using the older name for the above type. 308 using Allocator = ::pw::Allocator; 309 310 } // namespace allocator 311 } // namespace pw 312