blob: 09b08b74fddb3537cb93a5331a74f426c473dd0f [file] [log] [blame]
// Copyright 2020 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.
// WARNING: This code is a experimental WIP & exploration only, and is far from
// usable.
#pragma once
#include <cstdint>
#include "pw_span/span.h"
#include "pw_status/status.h"
namespace pw::allocator {
#if defined(PW_ALLOCATOR_POISON_ENABLE) && PW_ALLOCATOR_POISON_ENABLE
// Add poison offset of sizeof(void*) bytes before and after usable space in all
// Blocks.
#define PW_ALLOCATOR_POISON_OFFSET sizeof(void*)
#else
// Set the poison offset to 0 bytes; will not add poisson space before and
// after usable space in all Blocks.
#define PW_ALLOCATOR_POISON_OFFSET static_cast<size_t>(0)
#endif // PW_ALLOCATOR_POISON_ENABLE
// The "Block" type is intended to be a building block component for
// allocators. In the this design, there is an explicit pointer to next and
// prev from the block header; the size is not encoded. The below diagram shows
// what this would look like for two blocks.
//
// .------+---------------------------------.-----------------------------
// | Block A (first) | Block B (second)
//
// +------+------+--------------------------+------+------+---------------
// | Next | Prev | usable space | Next | Prev | usable space..
// +------+------+--------------------------+------+--+---+---------------
// ^ | ^ |
// | '-------------------------------------' |
// | |
// '----------- Block B's prev points to Block A -----'
//
// One use for these blocks is to use them as allocations, where each block
// represents an allocation handed out by malloc(). These blocks could also be
// used as part of a slab or buddy allocator.
//
// Each block also contains flags for whether it is the last block (i.e. whether
// the "next" pointer points to a valid block, or just denotes the end of this
// block), and whether the block is in use. These are encoded into the last two
// bits of the "next" pointer, as follows:
//
// .-----------------------------------------------------------------------.
// | Block |
// +-----------------------------------------------------------------------+
// | Next | Prev | usable space |
// +----------------+------+------+ + |
// | Ptr[N..2] | Last | Used | | |
// +----------------+------+------+------+---------------------------------+
// ^
// |
// '----------- Next() = Next & ~0x3 --------------------------------->
//
// The first block in a chain is denoted by a nullptr "prev" field, and the last
// block is denoted by the "Last" bit being set.
//
// Note, This block class requires that the given block is aligned to a
// alignof(Block*) boundary. Because of this alignment requirement, each
// returned block will also be aligned to a alignof(Block*) boundary, and the
// size will always be rounded up to a multiple of alignof(Block*).
//
// This class must be constructed using the static Init call.
class Block final {
public:
// No copy/move
Block(const Block& other) = delete;
Block& operator=(const Block& other) = delete;
Block(Block&& other) = delete;
Block& operator=(Block&& other) = delete;
// Create the first block for a given memory region.
// Note that the start of the given memory region must be aligned to an
// alignof(Block) boundary.
// Returns:
// INVALID_ARGUMENT if the given region is unaligned to too small, or
// OK otherwise.
static Status Init(const span<std::byte> region, Block** block);
// Returns a pointer to a Block, given a pointer to the start of the usable
// space inside the block (i.e. the opposite operation to UsableSpace()). In
// reality, this method just subtracts the appropriate amount from
// usable_space to point to the start of the owning block.
//
// Be aware that this method does not do any checking; passing a random
// pointer will return a non-null pointer.
static Block* FromUsableSpace(std::byte* usable_space) {
return reinterpret_cast<Block*>(usable_space - sizeof(Block) -
PW_ALLOCATOR_POISON_OFFSET);
}
// Size including the header.
size_t OuterSize() const {
return reinterpret_cast<intptr_t>(Next()) -
reinterpret_cast<intptr_t>(this);
}
// Usable bytes inside the block.
size_t InnerSize() const {
return OuterSize() - sizeof(*this) - 2 * PW_ALLOCATOR_POISON_OFFSET;
}
// Return the usable space inside this block.
std::byte* UsableSpace() {
return reinterpret_cast<std::byte*>(this) + sizeof(*this) +
PW_ALLOCATOR_POISON_OFFSET;
}
// Split this block, such that this block has an inner size of
// `head_block_inner_size`, and return a new block in the remainder of the
// space in `new_block`.
//
// The "remainder" block will be aligned to a alignof(Block*) boundary (and
// `head_block_inner_size` will be rounded up). If the remaining space is not
// large enough to store a new `Block` after rounding, no splitting will
// occur.
//
// This may return the following:
// OK: The split completed successfully.
// INVALID_ARGUMENT: new_block is null
// FAILED_PRECONDITION: This block is in use and cannot be split.
// OUT_OF_RANGE: The requested size for "this" block is greater than the
// current inner_size.
// RESOURCE_EXHAUSTED: The split cannot occur because the "remainder" block
// would not be large enough to store a block header.
Status Split(size_t head_block_inner_size, Block** new_block);
// Merge this block with the one that comes after it.
// This function will not merge blocks if either are in use.
//
// This may return the following:
// OK: Merge was successful.
// OUT_OF_RANGE: Attempting to merge the "last" block.
// FAILED_PRECONDITION: The blocks could not be merged because one of them
// was in use.
Status MergeNext();
// Merge this block with the one that comes before it.
// This function will not merge blocks if either are in use.
//
// Warning: merging with a previous block will invalidate this block instance.
// do not perform any operations on this instance after merging.
//
// This may return the following:
// OK: Merge was successful.
// OUT_OF_RANGE: Attempting to merge the "first" block.
// FAILED_PRECONDITION: The blocks could not be merged because one of them
// was in use.
Status MergePrev();
// Returns whether this block is in-use or not
bool Used() const { return (NextAsUIntPtr() & kInUseFlag) == kInUseFlag; }
// Returns whether this block is the last block or
// not (i.e. whether NextBlock points to a valid block or not).
// This is needed because NextBlock points to the end of this block,
// whether there is a valid block there or not.
bool Last() const { return (NextAsUIntPtr() & kLastFlag) == kLastFlag; }
// Mark this block as in-use
void MarkUsed() {
next_ = reinterpret_cast<Block*>((NextAsUIntPtr() | kInUseFlag));
}
// Mark this block as free
void MarkFree() {
next_ = reinterpret_cast<Block*>((NextAsUIntPtr() & ~kInUseFlag));
}
// Mark this block as the last one in the chain.
void MarkLast() {
next_ = reinterpret_cast<Block*>((NextAsUIntPtr() | kLastFlag));
}
// Clear the "last" bit from this block.
void ClearLast() {
next_ = reinterpret_cast<Block*>((NextAsUIntPtr() & ~kLastFlag));
}
// Fetch the block immediately after this one.
// Note: you should also check Last(); this function may return a valid
// block, even if one does not exist.
Block* Next() const {
return reinterpret_cast<Block*>(
(NextAsUIntPtr() & ~(kInUseFlag | kLastFlag)));
}
// Return the block immediately before this one. This will return nullptr
// if this is the "first" block.
Block* Prev() const { return prev_; }
// Return true if the block is aligned, the prev/next field matches with the
// previous and next block, and the poisoned bytes is not damaged. Otherwise,
// return false to indicate this block is corrupted.
bool IsValid() const { return CheckStatus() == BlockStatus::VALID; }
// Uses PW_DCHECK to log information about the reason if a block is invalid.
// This function will do nothing if the block is valid.
void CrashIfInvalid();
private:
static constexpr uintptr_t kInUseFlag = 0x1;
static constexpr uintptr_t kLastFlag = 0x2;
static constexpr std::byte POISON_PATTERN[8] = {std::byte{0x92},
std::byte{0x88},
std::byte{0x0a},
std::byte{0x00},
std::byte{0xec},
std::byte{0xdc},
std::byte{0xae},
std::byte{0x4e}};
enum BlockStatus {
VALID,
MISALIGNED,
PREV_MISMATCHED,
NEXT_MISMATCHED,
POISON_CORRUPTED
};
Block() = default;
// Helper to reduce some of the casting nesting in the block management
// functions.
uintptr_t NextAsUIntPtr() const { return reinterpret_cast<uintptr_t>(next_); }
void PoisonBlock();
bool CheckPoisonBytes() const;
BlockStatus CheckStatus() const;
// Note: Consider instead making these next/prev offsets from the current
// block, with templated type for the offset size. There are some interesting
// tradeoffs here; perhaps a pool of small allocations could use 1-byte
// next/prev offsets to reduce size further.
Block* next_;
Block* prev_;
};
} // namespace pw::allocator