blob: adb724ea1ebd628d857ce12ec0373433ec7f80e3 [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#pragma once
#include <cstddef>
#include "pw_allocator/capability.h"
#include "pw_allocator/deallocator.h"
#include "pw_allocator/layout.h"
#include "pw_allocator/unique_ptr.h"
#include "pw_result/result.h"
namespace pw {
/// Abstract interface for variable-layout memory allocation.
///
/// The interface makes no guarantees about its implementation. Consumers of the
/// generic interface must not make any assumptions around allocator behavior,
/// thread safety, or performance.
class Allocator : public Deallocator {
public:
/// Allocates a block of memory with the specified size and alignment.
///
/// Returns `nullptr` if the allocation cannot be made, or the `layout` has a
/// size of 0.
///
/// @param[in] layout Describes the memory to be allocated.
void* Allocate(Layout layout) {
return layout.size() != 0 ? DoAllocate(layout) : nullptr;
}
/// Constructs and object of type `T` from the given `args`
///
/// The return value is nullable, as allocating memory for the object may
/// fail. Callers must check for this error before using the resulting
/// pointer.
///
/// @param[in] args... Arguments passed to the object constructor.
template <typename T, int&... ExplicitGuard, typename... Args>
T* New(Args&&... args) {
void* ptr = Allocate(Layout::Of<T>());
return ptr != nullptr ? new (ptr) T(std::forward<Args>(args)...) : nullptr;
}
/// Constructs an array of type `T` from the given `args` and size
///
/// The return value is nullable, as allocating memory for the object may
/// fail. Callers must check for this error before using the resulting
/// pointer.
///
/// @param[in] size The size of the array to allocate.
template <typename T, int&... ExplicitGuard>
T* NewArray(size_t size) {
Layout layout(sizeof(T) * size, alignof(T));
void* ptr = Allocate(layout);
return ptr != nullptr ? new (ptr) T[size] : nullptr;
}
/// Constructs and object of type `T` from the given `args`, and wraps it in a
/// `UniquePtr`
///
/// The returned value may contain null if allocating memory for the object
/// fails. Callers must check for null before using the `UniquePtr`.
///
/// @param[in] args... Arguments passed to the object constructor.
template <typename T, int&... ExplicitGuard, typename... Args>
[[nodiscard]] UniquePtr<T> MakeUnique(Args&&... args) {
return Deallocator::WrapUnique<T>(New<T>(std::forward<Args>(args)...));
}
/// Constructs an array of type `T` from the given `args` and size, and
/// wraps it in a `UniquePtr`
///
/// The returned value may contain null if allocating memory for the object
/// fails. Callers must check for null before using the `UniquePtr`.
///
/// @param[in] size The size of the array to allocate.
template <typename T, int&... ExplicitGuard>
[[nodiscard]] UniquePtr<T[]> MakeUniqueArray(size_t size) {
return Deallocator::WrapUniqueArray<T>(NewArray<T>(size), size);
}
/// Modifies the size of an previously-allocated block of memory without
/// copying any data.
///
/// Returns true if its size was changed without copying data to a new
/// allocation; otherwise returns false.
///
/// In particular, it always returns true if the `old_layout.size()` equals
/// `new_size`, and always returns false if the given pointer is null, the
/// `old_layout.size()` is 0, or the `new_size` is 0.
///
/// @param[in] ptr Pointer to previously-allocated memory.
/// @param[in] new_size Requested new size for the memory allocation.
bool Resize(void* ptr, size_t new_size) {
return ptr != nullptr && new_size != 0 && DoResize(ptr, new_size);
}
/// Deprecated version of `Resize` that takes a `Layout`.
/// Do not use this method. It will be removed.
/// TODO(b/326509341): Remove when downstream consumers migrate.
bool Resize(void* ptr, Layout layout, size_t new_size) {
return ptr != nullptr && new_size != 0 && DoResize(ptr, layout, new_size);
}
/// Modifies the size of a previously-allocated block of memory.
///
/// Returns pointer to the modified block of memory, or `nullptr` if the
/// memory could not be modified.
///
/// The data stored by the memory being modified must be trivially
/// copyable. If it is not, callers should themselves attempt to `Resize`,
/// then `Allocate`, move the data, and `Deallocate` as needed.
///
/// If `nullptr` is returned, the block of memory is unchanged. In particular,
/// if the `new_layout` has a size of 0, the given pointer will NOT be
/// deallocated.
///
/// TODO(b/331290408): This error condition needs to be better communicated to
/// module users, who may assume the pointer is freed.
///
/// Unlike `Resize`, providing a null pointer will return a new allocation.
///
/// If the request can be satisfied using `Resize`, the `alignment` parameter
/// may be ignored.
///
/// @param[in] ptr Pointer to previously-allocated memory.
/// @param[in] new_layout Describes the memory to be allocated.
void* Reallocate(void* ptr, Layout new_layout) {
if (new_layout.size() == 0) {
return nullptr;
}
if (ptr == nullptr) {
return Allocate(new_layout);
}
return DoReallocate(ptr, new_layout);
}
/// Deprecated version of `Reallocate` that takes a `Layout`.
/// Do not use this method. It will be removed.
/// TODO(b/326509341): Remove when downstream consumers migrate.
void* Reallocate(void* ptr, Layout old_layout, size_t new_size) {
if (new_size == 0) {
return nullptr;
}
if (ptr == nullptr) {
return Allocate(Layout(new_size, old_layout.alignment()));
}
return DoReallocate(ptr, old_layout, new_size);
}
/// Returns the total bytes that have been allocated by this allocator, or
/// `size_t(-1)` if this allocator does not track its total allocated bytes.
size_t GetAllocated() const { return DoGetAllocated(); }
protected:
/// TODO(b/326509341): Remove when downstream consumers migrate.
constexpr Allocator() = default;
explicit constexpr Allocator(const Capabilities& capabilities)
: Deallocator(capabilities) {}
private:
/// Virtual `Allocate` function implemented by derived classes.
///
/// @param[in] layout Describes the memory to be allocated. Guaranteed
/// to have a non-zero size.
virtual void* DoAllocate(Layout layout) = 0;
/// Virtual `Resize` function implemented by derived classes.
///
/// The default implementation simply returns `false`, indicating that
/// resizing is not supported.
///
/// @param[in] ptr Pointer to memory, guaranteed to not be null.
/// @param[in] new_size Requested size, guaranteed to be non-zero..
virtual bool DoResize(void* /*ptr*/, size_t /*new_size*/) { return false; }
/// Deprecated version of `DoResize` that takes a `Layout`.
/// Do not use this method. It will be removed.
/// TODO(b/326509341): Remove when downstream consumers migrate.
virtual bool DoResize(void*, Layout, size_t) { return false; }
/// Virtual `Reallocate` function that can be overridden by derived classes.
///
/// The default implementation will first try to `Resize` the data. If that is
/// unsuccessful, it will allocate an entirely new block, copy existing data,
/// and deallocate the given block.
///
/// @param[in] ptr Pointer to memory, guaranteed to not be null.
/// @param[in] new_layout Describes the memory to be allocated. Guaranteed
/// to have a non-zero size.
virtual void* DoReallocate(void* ptr, Layout new_layout);
/// Deprecated version of `DoReallocate` that takes a `Layout`.
/// Do not use this method. It will be removed.
/// TODO(b/326509341): Remove when downstream consumers migrate.
virtual void* DoReallocate(void* ptr, Layout old_layout, size_t new_size);
/// Virtual `GetAllocated` function that can be overridden by derived classes.
///
/// The default implementation simply returns `size_t(-1)`, indicating that
/// tracking total allocated bytes is not supported.
virtual size_t DoGetAllocated() const { return size_t(-1); }
};
namespace allocator {
// Alias for module consumers using the older name for the above type.
using Allocator = ::pw::Allocator;
} // namespace allocator
} // namespace pw