blob: af7c8866a335f1835afc966028227b0f26c61e87 [file] [log] [blame]
// Copyright 2024 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 "pw_allocator/allocator.h"
#include "pw_allocator/block.h"
#include "pw_allocator/capability.h"
#include "pw_bytes/span.h"
#include "pw_result/result.h"
#include "pw_status/status.h"
namespace pw::allocator {
namespace internal {
/// Block-independent base class of ``BlockAllocator``.
///
/// This class contains static methods which do not depend on the template
/// parameters of ``BlockAllocator`` that are used to determine block/ type.
/// This allows the methods to be defined in a separate source file and use
/// macros that cannot be used in headers, e.g. PW_CHECK.
///
/// This class should not be used directly. Instead, use ``BlockAllocator`` or
/// one of its specializations.
class GenericBlockAllocator : public Allocator {
public:
static constexpr Capabilities kCapabilities = kImplementsGetUsableLayout |
kImplementsGetAllocatedLayout |
kImplementsQuery;
// Not copyable or movable.
GenericBlockAllocator(const GenericBlockAllocator&) = delete;
GenericBlockAllocator& operator=(const GenericBlockAllocator&) = delete;
GenericBlockAllocator(GenericBlockAllocator&&) = delete;
GenericBlockAllocator& operator=(GenericBlockAllocator&&) = delete;
protected:
constexpr GenericBlockAllocator() : Allocator(kCapabilities) {}
/// Crashes with an informational method that the given block is allocated.
///
/// This method is meant to be called by ``SplitFreeListAllocator``s
/// destructor. There must not be any outstanding allocations from an
/// when it is destroyed.
static void CrashOnAllocated(void* allocated);
};
} // namespace internal
/// A memory allocator that uses a list of blocks.
///
/// This class does not implement `ChooseBlock` and cannot be used directly.
/// Instead, use one of its specializations.
///
/// NOTE!! Do NOT use memory returned from this allocator as the backing for
/// another allocator. If this is done, the `Query` method may incorrectly
/// think pointers returned by that allocator were created by this one, and
/// report that this allocator can de/reallocate them.
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
class BlockAllocator : public internal::GenericBlockAllocator {
public:
using BlockType = Block<OffsetType, kAlign, kPoisonInterval != 0>;
using Range = typename BlockType::Range;
/// Constexpr constructor. Callers must explicitly call `Init`.
constexpr BlockAllocator() : internal::GenericBlockAllocator() {}
/// Non-constexpr constructor that automatically calls `Init`.
///
/// Errors are fatal.
///
/// @param[in] region The memory region for this allocator.
explicit BlockAllocator(ByteSpan region) : BlockAllocator() {
PW_ASSERT(Init(region).ok());
}
~BlockAllocator() override { Reset(); }
/// Returns a ``Range`` of blocks tracking the memory of this allocator.
Range blocks() const;
/// Sets the memory region to be used by this allocator.
///
/// This method will instantiate an initial block using the memory region.
///
/// TODO(b/338389412): Make infallible.
///
/// @param[in] region The memory region for this allocator.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The allocator is initialized.
///
/// INVALID_ARGUMENT: The memory region is null.
///
/// RESOURCE_EXHAUSTED: The region is too small for ``BlockType``.
///
/// OUT_OF_RANGE: The region too large for ``BlockType``.
///
/// @endrst
Status Init(ByteSpan region);
/// Sets the blocks to be used by this allocator.
///
/// This method will use the sequence blocks as-is, which must be valid. The
/// sequence extends to a block marked "last".
///
/// TODO(b/338389412): Make infallible.
///
/// @param[in] begin The first block for this allocator.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The allocator is initialized.
///
/// INVALID_ARGUMENT: The first block is null.
///
/// @endrst
Status Init(BlockType* begin) { return Init(begin, nullptr); }
/// Sets the blocks to be used by this allocator.
///
/// This method will use the sequence blocks as-is, which must be valid.
///
/// TODO(b/338389412): Make infallible.
///
/// @param[in] begin The first block for this allocator.
/// @param[in] end The last block for this allocator. May be
/// null, in which case the sequence ends with
/// the first block marked "last".
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: The allocator is initialized.
///
/// INVALID_ARGUMENT: The block sequence is empty.
///
/// @endrst
virtual Status Init(BlockType* begin, BlockType* end);
/// Initializes the allocator with preconfigured blocks.
///
/// This method does not
/// Resets the allocator to an uninitialized state.
///
/// At the time of the call, there MUST NOT be any outstanding allocated
/// blocks from this allocator.
void Reset();
protected:
using ReverseRange = typename BlockType::ReverseRange;
/// Returns a ``ReverseRange`` of blocks tracking the memory of this
/// allocator.
ReverseRange rblocks();
private:
/// @copydoc Allocator::Allocate
void* DoAllocate(Layout layout) override;
/// @copydoc Allocator::Deallocate
void DoDeallocate(void* ptr) override;
/// @copydoc Allocator::Deallocate
void DoDeallocate(void* ptr, Layout) override { DoDeallocate(ptr); }
/// @copydoc Allocator::Resize
bool DoResize(void* ptr, size_t new_size) override;
/// @copydoc Allocator::GetUsableLayout
StatusWithSize DoGetCapacity() const override {
return StatusWithSize(capacity_);
}
/// @copydoc Allocator::GetUsableLayout
Result<Layout> DoGetUsableLayout(const void* ptr) const override;
/// @copydoc Allocator::GetAllocatedLayout
Result<Layout> DoGetAllocatedLayout(const void* ptr) const override;
/// @copydoc Allocator::Query
Status DoQuery(const void* ptr) const override;
/// Selects a free block to allocate from.
///
/// This method represents the allocator-specific strategy of choosing which
/// block should be used to satisfy allocation requests.
///
/// @param layout Same as ``Allocator::Allocate``.
virtual BlockType* ChooseBlock(Layout layout) = 0;
/// Makes this block available for allocation again.
///
/// This method is called when a block is deallocated. By default, it does
/// nothing.
///
/// @param block The block beind deallocated.
virtual void RecycleBlock(BlockType* block);
/// Returns the block associated with a pointer.
///
/// If the given pointer is to this allocator's memory region, but not to a
/// valid block, the memory is corrupted and this method will crash to assist
/// in uncovering the underlying bug.
///
/// @param ptr Pointer to an allocated block's usable space.
///
/// @returns @rst
///
/// .. pw-status-codes::
///
/// OK: Result contains a pointer to the block.
///
/// OUT_OF_RANGE: Given pointer is outside the allocator's memory.
///
/// @endrst
template <typename PtrType,
typename BlockPtrType = std::conditional_t<
std::is_const_v<std::remove_pointer_t<PtrType>>,
const BlockType*,
BlockType*>>
Result<BlockPtrType> FromUsableSpace(PtrType ptr) const;
/// Ensures the pointer to the last block is correct after the given block is
/// allocated or freed.
void UpdateLast(BlockType* block);
// Represents the range of blocks for this allocator.
size_t capacity_ = 0;
BlockType* first_ = nullptr;
BlockType* last_ = nullptr;
uint16_t unpoisoned_ = 0;
};
// Template method implementations
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
typename BlockAllocator<OffsetType, kPoisonInterval, kAlign>::Range
BlockAllocator<OffsetType, kPoisonInterval, kAlign>::blocks() const {
return Range(first_);
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
typename BlockAllocator<OffsetType, kPoisonInterval, kAlign>::ReverseRange
BlockAllocator<OffsetType, kPoisonInterval, kAlign>::rblocks() {
while (last_ != nullptr && !last_->Last()) {
last_ = last_->Next();
}
return ReverseRange(last_);
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
Status BlockAllocator<OffsetType, kPoisonInterval, kAlign>::Init(
ByteSpan region) {
Result<BlockType*> result = BlockType::Init(region);
if (!result.ok()) {
return result.status();
}
return Init(*result);
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
Status BlockAllocator<OffsetType, kPoisonInterval, kAlign>::Init(
BlockType* begin, BlockType* end) {
if (begin == nullptr) {
return Status::InvalidArgument();
}
if (end == nullptr) {
end = begin;
while (!end->Last()) {
end = end->Next();
}
} else if (begin < end) {
end->MarkLast();
} else {
return Status::InvalidArgument();
}
first_ = begin;
last_ = end;
for (const auto& block : blocks()) {
capacity_ += block->OuterSize();
}
return OkStatus();
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::Reset() {
for (auto* block : blocks()) {
if (block->Used()) {
CrashOnAllocated(block);
}
}
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
void* BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoAllocate(
Layout layout) {
BlockType* block = ChooseBlock(layout);
if (block == nullptr) {
return nullptr;
}
UpdateLast(block);
return block->UsableSpace();
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoDeallocate(
void* ptr) {
auto result = FromUsableSpace(ptr);
if (!result.ok()) {
return;
}
BlockType* block = *result;
// Free the block and merge it with its neighbors, if possible.
BlockType::Free(block);
UpdateLast(block);
if constexpr (kPoisonInterval != 0) {
++unpoisoned_;
if (unpoisoned_ >= kPoisonInterval) {
block->Poison();
unpoisoned_ = 0;
}
}
RecycleBlock(block);
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
bool BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoResize(
void* ptr, size_t new_size) {
auto result = FromUsableSpace(ptr);
if (!result.ok()) {
return false;
}
BlockType* block = *result;
if (!BlockType::Resize(block, new_size).ok()) {
return false;
}
UpdateLast(block);
return true;
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
Result<Layout>
BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoGetUsableLayout(
const void* ptr) const {
auto result = FromUsableSpace(ptr);
if (!result.ok()) {
return Status::NotFound();
}
const BlockType* block = result.value();
if (!block->Used()) {
return Status::FailedPrecondition();
}
return Layout(block->InnerSize(), block->Alignment());
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
Result<Layout>
BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoGetAllocatedLayout(
const void* ptr) const {
auto result = FromUsableSpace(ptr);
if (!result.ok()) {
return Status::NotFound();
}
const BlockType* block = result.value();
if (!block->Used()) {
return Status::FailedPrecondition();
}
return Layout(block->OuterSize(), block->Alignment());
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
Status BlockAllocator<OffsetType, kPoisonInterval, kAlign>::DoQuery(
const void* ptr) const {
return FromUsableSpace(ptr).status();
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::RecycleBlock(
BlockType*) {}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
template <typename PtrType, typename BlockPtrType>
Result<BlockPtrType>
BlockAllocator<OffsetType, kPoisonInterval, kAlign>::FromUsableSpace(
PtrType ptr) const {
if (ptr < first_->UsableSpace() || last_->UsableSpace() < ptr) {
return Status::OutOfRange();
}
BlockPtrType block = BlockType::FromUsableSpace(ptr);
block->CrashIfInvalid();
return block;
}
template <typename OffsetType, uint16_t kPoisonInterval, uint16_t kAlign>
void BlockAllocator<OffsetType, kPoisonInterval, kAlign>::UpdateLast(
BlockType* block) {
if (block->Last()) {
last_ = block;
} else if (block->Next()->Last()) {
last_ = block->Next();
}
}
} // namespace pw::allocator