blob: 1c1c5a5e1890218090ff32f143d9695904845b54 [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::allocator {
/// 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 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)...));
}
/// 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 layout used to allocate a given pointer.
///
/// NOTE: This method will eventually be deprecated. Use `GetAllocatedLayout`
/// instead.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: Returns the actual layout of allocated memory.
///
/// NOT_FOUND: The allocator does not recognize the pointer as one of
/// its allocations.
///
/// UNIMPLEMENTED: Allocator cannot recover allocation details.
///
/// @endrst
Result<Layout> GetLayout(const void* ptr) const {
return GetAllocatedLayout(*this, ptr);
}
protected:
/// TODO(b/326509341): Remove when downstream consumers migrate.
constexpr Allocator() = default;
explicit constexpr Allocator(const Capabilities& capabilities)
: Deallocator(capabilities) {}
/// Returns the layout that was requested when allocating a given pointer.
///
/// This optional method can recover details about what memory was requested
/// from a pointer previously allocated by a given object. The
/// requested layout may differ from either the layout of usable memory, the
/// layout of memory used to fulfill the request, or both.
///
/// For example, it may have a smaller size than the usable memory if the
/// latter was padded to an alignment boundary, or may have a less strict
/// alignment than the actual memory.
///
/// This method is protected in order to restrict it to object
/// implementations. It is static and takes an ``allocator`` parameter in
/// order to allow forwarding allocators to call it on wrapped allocators.
///
/// @param[in] allocator The object that provided ``ptr``.
/// @param[in] ptr A pointer to previously allocated memory.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: Returns the originally requested layout.
///
/// NOT_FOUND: The allocator does not recognize the pointer
/// as one of its allocations.
///
/// UNIMPLEMENTED: Implementation cannot recover allocation details.
///
/// @endrst
static Result<Layout> GetRequestedLayout(const Allocator& allocator,
const void* ptr);
/// Returns the layout of the usable memory associated with a given pointer.
///
/// This optional method can recover details about what memory is usable for a
/// pointer previously allocated by this object. The usable layout
/// may from either the requested layout, the layout of memory used to fulfill
/// the request, or both.
///
/// For example, it may have a larger size than the requested layout if it
/// was padded to an alignment boundary, but may be less than the acutal
/// memory if the object includes some overhead for metadata.
///
/// This method is protected in order to restrict it to object
/// implementations. It is static and takes an ``allocator`` parameter in
/// order to allow forwarding allocators to call it on wrapped allocators.
///
/// @param[in] allocator The object that provided ``ptr``.
/// @param[in] ptr A pointer to previously allocated memory.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: Returns the layout of usable memory.
///
/// NOT_FOUND: The allocator does not recognize the pointer as one of its
/// allocations.
///
/// UNIMPLEMENTED: Implementation cannot recover allocation details.
///
/// @endrst
static Result<Layout> GetUsableLayout(const Allocator& allocator,
const void* ptr);
/// Returns the layout of the memory used to allocate a given pointer.
///
/// This optional method can recover details about what memory is usable for a
/// pointer previously allocated by this object. The layout of memory
/// used to fulfill a request may differ from either the requested layout, the
/// layout of the usable memory, or both.
///
/// For example, it may have a larger size than the requested layout or the
/// layout of usable memory if the object includes some overhead for
/// metadata.
///
/// This method is protected in order to restrict it to object
/// implementations. It is static and takes an ``allocator`` parameter in
/// order to allow forwarding allocators to call it on wrapped allocators.
///
/// @param[in] allocator The object that provided ``ptr``.
/// @param[in] ptr A pointer to previously allocated memory.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: Returns the layout of usable memory.
///
/// NOT_FOUND: The allocator does not recognize the pointer as one of
/// its allocations.
///
/// UNIMPLEMENTED: Implementation cannot recover allocation details.
///
/// @endrst
static Result<Layout> GetAllocatedLayout(const Allocator& allocator,
const void* ptr);
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 `GetRequested` function that can be overridden by derived classes.
///
/// The default implementation of this method simply returns `UNIMPLEMENTED`,
/// indicating the allocator cannot recover layouts from allocated pointers.
///
/// @param[in] ptr Pointer to memory, guaranteed to not be null.
virtual Result<Layout> DoGetRequestedLayout(const void*) const;
/// Virtual `Query` function that can be overridden by derived classes.
virtual Result<Layout> DoGetUsableLayout(const void*) const;
/// Virtual `Query` function that can be overridden by derived classes.
virtual Result<Layout> DoGetAllocatedLayout(const void*) const;
};
} // namespace pw::allocator