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_result/result.h" 23 24 namespace pw { 25 26 /// Abstract interface for variable-layout memory allocation. 27 /// 28 /// The interface makes no guarantees about its implementation. Consumers of the 29 /// generic interface must not make any assumptions around allocator behavior, 30 /// thread safety, or performance. 31 class Allocator : public Deallocator { 32 public: 33 /// Allocates a block of memory with the specified size and alignment. 34 /// 35 /// Returns `nullptr` if the allocation cannot be made, or the `layout` has a 36 /// size of 0. 37 /// 38 /// @param[in] layout Describes the memory to be allocated. Allocate(Layout layout)39 void* Allocate(Layout layout) { 40 return layout.size() != 0 ? DoAllocate(layout) : nullptr; 41 } 42 43 /// Constructs and object of type `T` from the given `args` 44 /// 45 /// The return value is nullable, as allocating memory for the object may 46 /// fail. Callers must check for this error before using the resulting 47 /// pointer. 48 /// 49 /// @param[in] args... Arguments passed to the object constructor. 50 template <typename T, int&... ExplicitGuard, typename... Args> New(Args &&...args)51 T* New(Args&&... args) { 52 void* ptr = Allocate(Layout::Of<T>()); 53 return ptr != nullptr ? new (ptr) T(std::forward<Args>(args)...) : nullptr; 54 } 55 56 /// Constructs an array of type `T` from the given `args` and size 57 /// 58 /// The return value is nullable, as allocating memory for the object may 59 /// fail. Callers must check for this error before using the resulting 60 /// pointer. 61 /// 62 /// @param[in] size The size of the array to allocate. 63 template <typename T, int&... ExplicitGuard> NewArray(size_t size)64 T* NewArray(size_t size) { 65 Layout layout(sizeof(T) * size, alignof(T)); 66 void* ptr = Allocate(layout); 67 return ptr != nullptr ? new (ptr) T[size] : nullptr; 68 } 69 70 /// Constructs and object of type `T` from the given `args`, and wraps it in a 71 /// `UniquePtr` 72 /// 73 /// The returned value may contain null if allocating memory for the object 74 /// fails. Callers must check for null before using the `UniquePtr`. 75 /// 76 /// @param[in] args... Arguments passed to the object constructor. 77 template <typename T, int&... ExplicitGuard, typename... Args> MakeUnique(Args &&...args)78 [[nodiscard]] UniquePtr<T> MakeUnique(Args&&... args) { 79 return Deallocator::WrapUnique<T>(New<T>(std::forward<Args>(args)...)); 80 } 81 82 /// Constructs an array of type `T` from the given `args` and size, and 83 /// wraps it in a `UniquePtr` 84 /// 85 /// The returned value may contain null if allocating memory for the object 86 /// fails. Callers must check for null before using the `UniquePtr`. 87 /// 88 /// @param[in] size The size of the array to allocate. 89 template <typename T, int&... ExplicitGuard> MakeUniqueArray(size_t size)90 [[nodiscard]] UniquePtr<T[]> MakeUniqueArray(size_t size) { 91 return Deallocator::WrapUniqueArray<T>(NewArray<T>(size), size); 92 } 93 94 /// Modifies the size of an previously-allocated block of memory without 95 /// copying any data. 96 /// 97 /// Returns true if its size was changed without copying data to a new 98 /// allocation; otherwise returns false. 99 /// 100 /// In particular, it always returns true if the `old_layout.size()` equals 101 /// `new_size`, and always returns false if the given pointer is null, the 102 /// `old_layout.size()` is 0, or the `new_size` is 0. 103 /// 104 /// @param[in] ptr Pointer to previously-allocated memory. 105 /// @param[in] new_size Requested new size for the memory allocation. Resize(void * ptr,size_t new_size)106 bool Resize(void* ptr, size_t new_size) { 107 return ptr != nullptr && new_size != 0 && DoResize(ptr, new_size); 108 } 109 110 /// Deprecated version of `Resize` that takes a `Layout`. 111 /// Do not use this method. It will be removed. 112 /// TODO(b/326509341): Remove when downstream consumers migrate. Resize(void * ptr,Layout layout,size_t new_size)113 bool Resize(void* ptr, Layout layout, size_t new_size) { 114 return ptr != nullptr && new_size != 0 && DoResize(ptr, layout, new_size); 115 } 116 117 /// Modifies the size of a previously-allocated block of memory. 118 /// 119 /// Returns pointer to the modified block of memory, or `nullptr` if the 120 /// memory could not be modified. 121 /// 122 /// The data stored by the memory being modified must be trivially 123 /// copyable. If it is not, callers should themselves attempt to `Resize`, 124 /// then `Allocate`, move the data, and `Deallocate` as needed. 125 /// 126 /// If `nullptr` is returned, the block of memory is unchanged. In particular, 127 /// if the `new_layout` has a size of 0, the given pointer will NOT be 128 /// deallocated. 129 /// 130 /// TODO(b/331290408): This error condition needs to be better communicated to 131 /// module users, who may assume the pointer is freed. 132 /// 133 /// Unlike `Resize`, providing a null pointer will return a new allocation. 134 /// 135 /// If the request can be satisfied using `Resize`, the `alignment` parameter 136 /// may be ignored. 137 /// 138 /// @param[in] ptr Pointer to previously-allocated memory. 139 /// @param[in] new_layout Describes the memory to be allocated. Reallocate(void * ptr,Layout new_layout)140 void* Reallocate(void* ptr, Layout new_layout) { 141 if (new_layout.size() == 0) { 142 return nullptr; 143 } 144 if (ptr == nullptr) { 145 return Allocate(new_layout); 146 } 147 return DoReallocate(ptr, new_layout); 148 } 149 150 /// Deprecated version of `Reallocate` that takes a `Layout`. 151 /// Do not use this method. It will be removed. 152 /// TODO(b/326509341): Remove when downstream consumers migrate. Reallocate(void * ptr,Layout old_layout,size_t new_size)153 void* Reallocate(void* ptr, Layout old_layout, size_t new_size) { 154 if (new_size == 0) { 155 return nullptr; 156 } 157 if (ptr == nullptr) { 158 return Allocate(Layout(new_size, old_layout.alignment())); 159 } 160 return DoReallocate(ptr, old_layout, new_size); 161 } 162 163 /// Returns the total bytes that have been allocated by this allocator, or 164 /// `size_t(-1)` if this allocator does not track its total allocated bytes. GetAllocated()165 size_t GetAllocated() const { return DoGetAllocated(); } 166 167 protected: 168 /// TODO(b/326509341): Remove when downstream consumers migrate. 169 constexpr Allocator() = default; 170 Allocator(const Capabilities & capabilities)171 explicit constexpr Allocator(const Capabilities& capabilities) 172 : Deallocator(capabilities) {} 173 174 private: 175 /// Virtual `Allocate` function implemented by derived classes. 176 /// 177 /// @param[in] layout Describes the memory to be allocated. Guaranteed 178 /// to have a non-zero size. 179 virtual void* DoAllocate(Layout layout) = 0; 180 181 /// Virtual `Resize` function implemented by derived classes. 182 /// 183 /// The default implementation simply returns `false`, indicating that 184 /// resizing is not supported. 185 /// 186 /// @param[in] ptr Pointer to memory, guaranteed to not be null. 187 /// @param[in] new_size Requested size, guaranteed to be non-zero.. DoResize(void *,size_t)188 virtual bool DoResize(void* /*ptr*/, size_t /*new_size*/) { return false; } 189 190 /// Deprecated version of `DoResize` that takes a `Layout`. 191 /// Do not use this method. It will be removed. 192 /// TODO(b/326509341): Remove when downstream consumers migrate. DoResize(void *,Layout,size_t)193 virtual bool DoResize(void*, Layout, size_t) { return false; } 194 195 /// Virtual `Reallocate` function that can be overridden by derived classes. 196 /// 197 /// The default implementation will first try to `Resize` the data. If that is 198 /// unsuccessful, it will allocate an entirely new block, copy existing data, 199 /// and deallocate the given block. 200 /// 201 /// @param[in] ptr Pointer to memory, guaranteed to not be null. 202 /// @param[in] new_layout Describes the memory to be allocated. Guaranteed 203 /// to have a non-zero size. 204 virtual void* DoReallocate(void* ptr, Layout new_layout); 205 206 /// Deprecated version of `DoReallocate` that takes a `Layout`. 207 /// Do not use this method. It will be removed. 208 /// TODO(b/326509341): Remove when downstream consumers migrate. 209 virtual void* DoReallocate(void* ptr, Layout old_layout, size_t new_size); 210 211 /// Virtual `GetAllocated` function that can be overridden by derived classes. 212 /// 213 /// The default implementation simply returns `size_t(-1)`, indicating that 214 /// tracking total allocated bytes is not supported. DoGetAllocated()215 virtual size_t DoGetAllocated() const { return size_t(-1); } 216 }; 217 218 namespace allocator { 219 220 // Alias for module consumers using the older name for the above type. 221 using Allocator = ::pw::Allocator; 222 223 } // namespace allocator 224 } // namespace pw 225