blob: 740a381af9feb27f358dffaf81e629176f751abb [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SRC_LIB_TRIVIAL_ALLOCATOR_INCLUDE_LIB_TRIVIAL_ALLOCATOR_PAGE_ALLOCATOR_H_
#define SRC_LIB_TRIVIAL_ALLOCATOR_INCLUDE_LIB_TRIVIAL_ALLOCATOR_PAGE_ALLOCATOR_H_
#include <cstddef>
#include <limits>
#include <type_traits>
#include <utility>
namespace trivial_allocator {
// trivial_allocator::PageAllocator is an AllocateFunction compatible with
// trivial_allocator::BasicLeakyAllocator. It uses the Memory object to do
// whole-page allocations. Its constructor forwards arguments to the Memory
// constructor, so the PageAllocator object is copyable and/or movable if the
// Memory object is. Some Memory object implementations are provided by
// <lib/trivial-allocator/posix.h> and <lib/trivial-allocator/zircon.h>. The
// Memory object must define a type Memory::Capability, and these methods:
//
// * `size_t page_size() const;`
// * `std::pair<void*, Capability> Allocate(size_t);`
// * `void Deallocate(Capability, void*, size_t);`
// * `void Seal(Capability, void*, size_t);`
//
// The page_size() returned must be a power of two. The size passed to
// Allocate will always be a multiple of that size.
//
// The Capability is some default-constructible, movable object. It's passed
// back in the Deallocate or Seal call, or just destroyed if the memory is
// leaked without being sealed. Either Deallocate or Seal (but not both) may
// be called with the same capability, pointer, and size from an Allocate call.
// Deallocate returns the memory. Seal makes the memory read-only.
template <class Memory>
class PageAllocator {
public:
static_assert(std::is_default_constructible_v<typename Memory::Capability>);
static_assert(std::is_move_constructible_v<typename Memory::Capability>);
static_assert(std::is_move_assignable_v<typename Memory::Capability>);
class Allocation {
public:
Allocation() = default;
Allocation(const Allocation&) = delete;
Allocation(Allocation&& other) noexcept
: allocator_(std::exchange(other.allocator_, nullptr)),
capability_(std::exchange(other.capability_, {})),
ptr_(std::exchange(other.ptr_, nullptr)),
size_(std::exchange(other.size_, 0)) {}
Allocation& operator=(const Allocation&) = delete;
Allocation& operator=(Allocation&& other) noexcept {
reset();
allocator_ = std::exchange(other.allocator_, nullptr);
capability_ = std::exchange(other.capability_, {});
ptr_ = std::exchange(other.ptr_, nullptr);
size_ = std::exchange(other.size_, 0);
return *this;
}
~Allocation() { reset(); }
void* get() const { return ptr_; }
explicit operator bool() const { return ptr_; }
size_t size_bytes() const { return size_; }
void reset() {
if (ptr_) {
allocator_->memory().Deallocate(std::exchange(capability_, {}),
std::exchange(ptr_, nullptr), std::exchange(size_, 0));
}
}
void* release() {
capability_ = {};
size_ = 0;
return std::exchange(ptr_, nullptr);
}
// Seal the memory and then leak it.
void Seal() && {
allocator_->memory().Seal(std::exchange(capability_, {}), std::exchange(ptr_, nullptr),
std::exchange(size_, 0));
}
PageAllocator& allocator() const { return *allocator_; }
private:
friend PageAllocator;
PageAllocator* allocator_ = nullptr;
[[no_unique_address]] typename Memory::Capability capability_;
void* ptr_ = nullptr;
size_t size_ = 0;
};
static_assert(std::is_default_constructible_v<Allocation>);
static_assert(std::is_nothrow_move_constructible_v<Allocation>);
static_assert(std::is_nothrow_move_assignable_v<Allocation>);
static_assert(!std::is_copy_constructible_v<Allocation>);
static_assert(!std::is_copy_assignable_v<Allocation>);
constexpr PageAllocator() = default;
constexpr PageAllocator(const PageAllocator&) = default;
constexpr PageAllocator(PageAllocator&&) noexcept = default;
constexpr PageAllocator& operator=(PageAllocator&&) noexcept = default;
template <typename... Args>
constexpr explicit PageAllocator(Args&&... args) : memory_(std::forward<Args>(args)...) {}
Allocation operator()(size_t& size, size_t alignment) {
Allocation result;
if (size <= std::numeric_limits<size_t>::max() - memory_.page_size() + 1) [[likely]] {
size = (size + memory_.page_size() - 1) & -memory_.page_size();
auto [ptr, capability] = memory_.Allocate(size);
if (ptr) {
result.allocator_ = this;
result.capability_ = std::move(capability);
result.ptr_ = ptr;
result.size_ = size;
}
}
return result;
}
Memory& memory() { return memory_; }
const Memory& memory() const { return memory_; }
private:
Memory memory_;
};
// Deduction guide.
template <class Memory>
PageAllocator(Memory) -> PageAllocator<std::decay_t<Memory>>;
} // namespace trivial_allocator
#endif // SRC_LIB_TRIVIAL_ALLOCATOR_INCLUDE_LIB_TRIVIAL_ALLOCATOR_PAGE_ALLOCATOR_H_