[wpiutil] Import foonathan memory (#4306)

This commit is contained in:
Peter Johnson
2022-09-02 20:32:21 -07:00
committed by GitHub
parent 2742662254
commit aa9d7f1cdc
70 changed files with 13442 additions and 2 deletions

View File

@@ -53,6 +53,10 @@ jobs:
run: |
cd upstream_utils
./update_stack_walker.py
- name: Run update_memory.py
run: |
cd upstream_utils
./update_memory.py
- name: Add untracked files to index so they count as changes
run: git add -A
- name: Check output

View File

@@ -115,6 +115,9 @@ doxygen {
// json
exclude 'wpi/json.h'
// memory
exclude 'wpi/memory/**'
// mpack
exclude 'wpi/mpack.h'

View File

@@ -0,0 +1,34 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
// Copyright (C) 2015-2020 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#pragma once
#include <cstddef>
//=== options ===//
#define WPI_MEMORY_CHECK_ALLOCATION_SIZE 1
#define WPI_MEMORY_IMPL_DEFAULT_ALLOCATOR heap_allocator
#ifdef NDEBUG
#define WPI_MEMORY_DEBUG_ASSERT 0
#define WPI_MEMORY_DEBUG_FILL 0
#define WPI_MEMORY_DEBUG_FENCE 0
#define WPI_MEMORY_DEBUG_LEAK_CHECK 0
#define WPI_MEMORY_DEBUG_POINTER_CHECK 0
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 0
#else
#define WPI_MEMORY_DEBUG_ASSERT 1
#define WPI_MEMORY_DEBUG_FILL 1
#define WPI_MEMORY_DEBUG_FENCE 8
#define WPI_MEMORY_DEBUG_LEAK_CHECK 1
#define WPI_MEMORY_DEBUG_POINTER_CHECK 1
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 1
#endif
#define WPI_MEMORY_EXTERN_TEMPLATE 1
#define WPI_MEMORY_TEMPORARY_STACK_MODE 2
#define WPI_MEMORY_NO_NODE_SIZE 1

104
upstream_utils/update_memory.py Executable file
View File

@@ -0,0 +1,104 @@
#!/usr/bin/env python3
import os
import shutil
from upstream_utils import (
get_repo_root,
clone_repo,
comment_out_invalid_includes,
walk_if,
copy_to,
)
def run_source_replacements(memory_files):
for wpi_file in memory_files:
with open(wpi_file) as f:
content = f.read()
# Fix #includes
content = content.replace('include "', 'include "wpi/memory/')
content = content.replace(
"wpi/memory/free_list_utils.hpp", "free_list_utils.hpp"
)
with open(wpi_file, "w") as f:
f.write(content)
def run_header_replacements(memory_files):
for wpi_file in memory_files:
if "detail" not in wpi_file:
continue
with open(wpi_file) as f:
content = f.read()
# Fix #includes
content = content.replace('include "config.hpp', 'include "../config.hpp')
with open(wpi_file, "w") as f:
f.write(content)
def run_global_replacements(memory_files):
for wpi_file in memory_files:
with open(wpi_file) as f:
content = f.read()
# Rename namespace from foonathan to wpi
content = content.replace("namespace foonathan", "namespace wpi")
content = content.replace("foonathan::", "wpi::")
content = content.replace("FOONATHAN_", "WPI_")
# Fix #includes
content = content.replace('include "foonathan', 'include "wpi')
with open(wpi_file, "w") as f:
f.write(content)
def main():
upstream_root = clone_repo("https://github.com/foonathan/memory", "v0.7-2")
wpilib_root = get_repo_root()
wpiutil = os.path.join(wpilib_root, "wpiutil")
# Delete old install
for d in [
"src/main/native/thirdparty/memory/src",
"src/main/native/thirdparty/memory/include",
]:
shutil.rmtree(os.path.join(wpiutil, d), ignore_errors=True)
# Copy sources
os.chdir(upstream_root)
src_files = walk_if("src", lambda dp, f: f.endswith(".cpp") or f.endswith(".hpp"))
src_files = copy_to(
src_files, os.path.join(wpiutil, "src/main/native/thirdparty/memory")
)
run_global_replacements(src_files)
run_source_replacements(src_files)
# Copy headers
os.chdir(os.path.join(upstream_root, "include", "foonathan"))
include_files = walk_if(".", lambda dp, f: f.endswith(".hpp"))
include_files = copy_to(
include_files,
os.path.join(wpiutil, "src/main/native/thirdparty/memory/include/wpi"),
)
os.chdir(os.path.join("..", ".."))
run_global_replacements(include_files)
run_header_replacements(include_files)
# Copy config_impl.hpp
shutil.copyfile(
os.path.join(wpilib_root, "upstream_utils/memory_files/config_impl.hpp"),
os.path.join(
wpiutil,
"src/main/native/thirdparty/memory/include/wpi/memory/config_impl.hpp",
),
)
if __name__ == "__main__":
main()

View File

@@ -1,5 +1,6 @@
cppHeaderFileInclude {
\.h$
\.hpp$
\.inc$
\.inl$
math$

View File

@@ -98,8 +98,9 @@ file(GLOB_RECURSE wpiutil_macos_src src/main/native/macOS/*.cpp)
file(GLOB_RECURSE wpiutil_windows_src src/main/native/windows/*.cpp)
file(GLOB fmtlib_native_src src/main/native/thirdparty/fmtlib/src/*.cpp)
file(GLOB_RECURSE memory_native_src src/main/native/thirdparty/memory/src/*.cpp)
add_library(wpiutil ${wpiutil_native_src} ${fmtlib_native_src} ${wpiutil_resources_src})
add_library(wpiutil ${wpiutil_native_src} ${fmtlib_native_src} ${memory_native_src} ${wpiutil_resources_src})
set_target_properties(wpiutil PROPERTIES DEBUG_POSTFIX "d")
set_property(TARGET wpiutil PROPERTY FOLDER "libraries")
@@ -141,6 +142,7 @@ endif()
target_include_directories(wpiutil PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/memory/include>
$<INSTALL_INTERFACE:${include_dest}/wpiutil>)
target_include_directories(wpiutil PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/ghc/include>

View File

@@ -70,6 +70,16 @@ ext {
srcDirs 'src/main/native/thirdparty/tcb_span/include'
}
}
memoryCpp(CppSourceSet) {
source {
srcDirs 'src/main/native/thirdparty/memory/src', 'src/main/native/thirdparty/memory/include/wpi/memory'
include '**/*.cpp'
}
exportedHeaders {
srcDirs 'src/main/native/thirdparty/memory/include'
include '**/*.hpp'
}
}
resourcesCpp(CppSourceSet) {
source {
srcDirs "$buildDir/generated/main/cpp", "$rootDir/shared/singlelib"
@@ -190,6 +200,9 @@ cppHeadersZip {
from('src/main/native/thirdparty/tcb_span/include') {
into '/'
}
from('src/main/native/thirdparty/memory/include') {
into '/'
}
}
cppSourcesZip {
@@ -199,6 +212,9 @@ cppSourcesZip {
from('src/main/native/thirdparty/json/cpp') {
into '/'
}
from('src/main/native/thirdparty/memory/src') {
into '/'
}
}
model {
@@ -206,7 +222,7 @@ model {
all {
it.sources.each {
it.exportedHeaders {
srcDirs 'src/main/native/include', 'src/main/native/thirdparty/fmtlib/include', 'src/main/native/thirdparty/llvm/include', 'src/main/native/thirdparty/sigslot/include', 'src/main/native/thirdparty/json/include', 'src/main/native/thirdparty/tcb_span/include', 'src/main/native/thirdparty/ghc/include'
srcDirs 'src/main/native/include', 'src/main/native/thirdparty/fmtlib/include', 'src/main/native/thirdparty/llvm/include', 'src/main/native/thirdparty/sigslot/include', 'src/main/native/thirdparty/json/include', 'src/main/native/thirdparty/tcb_span/include', 'src/main/native/thirdparty/ghc/include', 'src/main/native/thirdparty/memory/include'
}
}
}

View File

@@ -0,0 +1,197 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::aligned_allocator and related functions.
#include <type_traits>
#include "detail/assert.hpp"
#include "detail/utility.hpp"
#include "allocator_traits.hpp"
#include "config.hpp"
namespace wpi
{
namespace memory
{
/// A \concept{concept_rawallocator,RawAllocator} adapter that ensures a minimum alignment.
/// It adjusts the alignment value so that it is always larger than the minimum and forwards to the specified allocator.
/// \ingroup adapter
template <class RawAllocator>
class aligned_allocator : WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
{
using traits = allocator_traits<RawAllocator>;
using composable_traits = composable_allocator_traits<RawAllocator>;
using composable = is_composable_allocator<typename traits::allocator_type>;
public:
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
using is_stateful = std::true_type;
/// \effects Creates it passing it the minimum alignment value and the allocator object.
/// \requires \c min_alignment must be less than \c this->max_alignment().
explicit aligned_allocator(std::size_t min_alignment, allocator_type&& alloc = {})
: allocator_type(detail::move(alloc)), min_alignment_(min_alignment)
{
WPI_MEMORY_ASSERT(min_alignment_ <= max_alignment());
}
/// @{
/// \effects Moves the \c aligned_allocator object.
/// It simply moves the underlying allocator.
aligned_allocator(aligned_allocator&& other) noexcept
: allocator_type(detail::move(other)), min_alignment_(other.min_alignment_)
{
}
aligned_allocator& operator=(aligned_allocator&& other) noexcept
{
allocator_type::operator=(detail::move(other));
min_alignment_ = other.min_alignment_;
return *this;
}
/// @}
/// @{
/// \effects Forwards to the underlying allocator through the \ref allocator_traits.
/// If the \c alignment is less than the \c min_alignment(), it is set to the minimum alignment.
void* allocate_node(std::size_t size, std::size_t alignment)
{
if (min_alignment_ > alignment)
alignment = min_alignment_;
return traits::allocate_node(get_allocator(), size, alignment);
}
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
{
if (min_alignment_ > alignment)
alignment = min_alignment_;
return traits::allocate_array(get_allocator(), count, size, alignment);
}
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
if (min_alignment_ > alignment)
alignment = min_alignment_;
traits::deallocate_node(get_allocator(), ptr, size, alignment);
}
void deallocate_array(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
if (min_alignment_ > alignment)
alignment = min_alignment_;
traits::deallocate_array(get_allocator(), ptr, count, size, alignment);
}
/// @}
/// @{
/// \effects Forwards to the underlying allocator through the \ref composable_allocator_traits.
/// If the \c alignment is less than the \c min_alignment(), it is set to the minimum alignment.
/// \requires The underyling allocator must be composable.
WPI_ENABLE_IF(composable::value)
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
{
if (min_alignment_ > alignment)
alignment = min_alignment_;
return composable_traits::try_allocate_node(get_allocator(), size, alignment);
}
WPI_ENABLE_IF(composable::value)
void* try_allocate_array(std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
if (min_alignment_ > alignment)
alignment = min_alignment_;
return composable_traits::try_allocate_array(get_allocator(), count, size,
alignment);
}
WPI_ENABLE_IF(composable::value)
bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
if (min_alignment_ > alignment)
alignment = min_alignment_;
return composable_traits::try_deallocate_node(get_allocator(), ptr, size,
alignment);
}
WPI_ENABLE_IF(composable::value)
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
if (min_alignment_ > alignment)
alignment = min_alignment_;
return composable_traits::try_deallocate_array(get_allocator(), ptr, count, size,
alignment);
}
/// @}
/// @{
/// \returns The value returned by the \ref allocator_traits for the underlying allocator.
std::size_t max_node_size() const
{
return traits::max_node_size(get_allocator());
}
std::size_t max_array_size() const
{
return traits::max_array_size(get_allocator());
}
std::size_t max_alignment() const
{
return traits::max_alignment(get_allocator());
}
/// @}
/// @{
/// \returns A reference to the underlying allocator.
allocator_type& get_allocator() noexcept
{
return *this;
}
const allocator_type& get_allocator() const noexcept
{
return *this;
}
/// @}
/// \returns The minimum alignment.
std::size_t min_alignment() const noexcept
{
return min_alignment_;
}
/// \effects Sets the minimum alignment to a new value.
/// \requires \c min_alignment must be less than \c this->max_alignment().
void set_min_alignment(std::size_t min_alignment)
{
WPI_MEMORY_ASSERT(min_alignment <= max_alignment());
min_alignment_ = min_alignment;
}
private:
std::size_t min_alignment_;
};
/// \returns A new \ref aligned_allocator created by forwarding the parameters to the constructor.
/// \relates aligned_allocator
template <class RawAllocator>
auto make_aligned_allocator(std::size_t min_alignment, RawAllocator&& allocator) noexcept
-> aligned_allocator<typename std::decay<RawAllocator>::type>
{
return aligned_allocator<
typename std::decay<RawAllocator>::type>{min_alignment,
detail::forward<RawAllocator>(allocator)};
}
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,932 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED
#define WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED
/// \file
/// Class template \ref wpi::memory::allocator_storage, some policies and resulting typedefs.
#include <new>
#include <type_traits>
#include "detail/utility.hpp"
#include "config.hpp"
#include "allocator_traits.hpp"
#include "threading.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
template <class Alloc>
void* try_allocate_node(std::true_type, Alloc& alloc, std::size_t size,
std::size_t alignment) noexcept
{
return composable_allocator_traits<Alloc>::try_allocate_node(alloc, size,
alignment);
}
template <class Alloc>
void* try_allocate_array(std::true_type, Alloc& alloc, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
return composable_allocator_traits<Alloc>::try_allocate_array(alloc, count, size,
alignment);
}
template <class Alloc>
bool try_deallocate_node(std::true_type, Alloc& alloc, void* ptr, std::size_t size,
std::size_t alignment) noexcept
{
return composable_allocator_traits<Alloc>::try_deallocate_node(alloc, ptr, size,
alignment);
}
template <class Alloc>
bool try_deallocate_array(std::true_type, Alloc& alloc, void* ptr, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
return composable_allocator_traits<Alloc>::try_deallocate_array(alloc, ptr, count,
size, alignment);
}
template <class Alloc>
void* try_allocate_node(std::false_type, Alloc&, std::size_t, std::size_t) noexcept
{
WPI_MEMORY_UNREACHABLE("Allocator is not compositioning");
return nullptr;
}
template <class Alloc>
void* try_allocate_array(std::false_type, Alloc&, std::size_t, std::size_t,
std::size_t) noexcept
{
WPI_MEMORY_UNREACHABLE("Allocator is not compositioning");
return nullptr;
}
template <class Alloc>
bool try_deallocate_node(std::false_type, Alloc&, void*, std::size_t,
std::size_t) noexcept
{
WPI_MEMORY_UNREACHABLE("Allocator is not compositioning");
return false;
}
template <class Alloc>
bool try_deallocate_array(std::false_type, Alloc&, void*, std::size_t, std::size_t,
std::size_t) noexcept
{
WPI_MEMORY_UNREACHABLE("Allocator is not compositioning");
return false;
}
} // namespace detail
/// A \concept{concept_rawallocator,RawAllocator} that stores another allocator.
/// The \concept{concept_storagepolicy,StoragePolicy} defines the allocator type being stored and how it is stored.
/// The \c Mutex controls synchronization of the access.
/// \ingroup storage
template <class StoragePolicy, class Mutex>
class allocator_storage
: WPI_EBO(StoragePolicy,
detail::mutex_storage<
detail::mutex_for<typename StoragePolicy::allocator_type, Mutex>>)
{
using traits = allocator_traits<typename StoragePolicy::allocator_type>;
using composable_traits =
composable_allocator_traits<typename StoragePolicy::allocator_type>;
using composable = is_composable_allocator<typename StoragePolicy::allocator_type>;
using actual_mutex = const detail::mutex_storage<
detail::mutex_for<typename StoragePolicy::allocator_type, Mutex>>;
public:
using allocator_type = typename StoragePolicy::allocator_type;
using storage_policy = StoragePolicy;
using mutex = Mutex;
using is_stateful = typename traits::is_stateful;
/// \effects Creates it by default-constructing the \c StoragePolicy.
/// \requires The \c StoragePolicy must be default-constructible.
/// \notes The default constructor may create an invalid allocator storage not associated with any allocator.
/// If that is the case, it must not be used.
allocator_storage() = default;
/// \effects Creates it by passing it an allocator.
/// The allocator will be forwarded to the \c StoragePolicy, it decides whether it will be moved, its address stored or something else.
/// \requires The expression <tt>new storage_policy(std::forward<Alloc>(alloc))</tt> must be well-formed,
/// otherwise this constructor does not participate in overload resolution.
template <
class Alloc,
// MSVC seems to ignore access rights in SFINAE below
// use this to prevent this constructor being chosen instead of move for types inheriting from it
WPI_REQUIRES(
(!std::is_base_of<allocator_storage, typename std::decay<Alloc>::type>::value))>
allocator_storage(Alloc&& alloc,
WPI_SFINAE(new storage_policy(detail::forward<Alloc>(alloc))))
: storage_policy(detail::forward<Alloc>(alloc))
{
}
/// \effects Creates it by passing it another \c allocator_storage with a different \c StoragePolicy but the same \c Mutex type.
/// Initializes it with the result of \c other.get_allocator().
/// \requires The expression <tt>new storage_policy(other.get_allocator())</tt> must be well-formed,
/// otherwise this constructor does not participate in overload resolution.
template <class OtherPolicy>
allocator_storage(const allocator_storage<OtherPolicy, Mutex>& other,
WPI_SFINAE(new storage_policy(other.get_allocator())))
: storage_policy(other.get_allocator())
{
}
/// @{
/// \effects Moves the \c allocator_storage object.
/// A moved-out \c allocator_storage object must still store a valid allocator object.
allocator_storage(allocator_storage&& other) noexcept
: storage_policy(detail::move(other)),
detail::mutex_storage<
detail::mutex_for<typename StoragePolicy::allocator_type, Mutex>>(
detail::move(other))
{
}
allocator_storage& operator=(allocator_storage&& other) noexcept
{
storage_policy:: operator=(detail::move(other));
detail::mutex_storage<detail::mutex_for<typename StoragePolicy::allocator_type,
Mutex>>::operator=(detail::move(other));
return *this;
}
/// @}
/// @{
/// \effects Copies the \c allocator_storage object.
/// \requires The \c StoragePolicy must be copyable.
allocator_storage(const allocator_storage&) = default;
allocator_storage& operator=(const allocator_storage&) = default;
/// @}
/// @{
/// \effects Calls the function on the stored allocator.
/// The \c Mutex will be locked during the operation.
void* allocate_node(std::size_t size, std::size_t alignment)
{
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return traits::allocate_node(alloc, size, alignment);
}
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
{
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return traits::allocate_array(alloc, count, size, alignment);
}
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
traits::deallocate_node(alloc, ptr, size, alignment);
}
void deallocate_array(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
traits::deallocate_array(alloc, ptr, count, size, alignment);
}
std::size_t max_node_size() const
{
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return traits::max_node_size(alloc);
}
std::size_t max_array_size() const
{
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return traits::max_array_size(alloc);
}
std::size_t max_alignment() const
{
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return traits::max_alignment(alloc);
}
/// @}
/// @{
/// \effects Calls the function on the stored composable allocator.
/// The \c Mutex will be locked during the operation.
/// \requires The allocator must be composable,
/// i.e. \ref is_composable() must return `true`.
/// \note This check is done at compile-time where possible,
/// and at runtime in the case of type-erased storage.
WPI_ENABLE_IF(composable::value)
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
{
WPI_MEMORY_ASSERT(is_composable());
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return composable_traits::try_allocate_node(alloc, size, alignment);
}
WPI_ENABLE_IF(composable::value)
void* try_allocate_array(std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
WPI_MEMORY_ASSERT(is_composable());
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return composable_traits::try_allocate_array(alloc, count, size, alignment);
}
WPI_ENABLE_IF(composable::value)
bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
WPI_MEMORY_ASSERT(is_composable());
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return composable_traits::try_deallocate_node(alloc, ptr, size, alignment);
}
WPI_ENABLE_IF(composable::value)
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
WPI_MEMORY_ASSERT(is_composable());
std::lock_guard<actual_mutex> lock(*this);
auto&& alloc = get_allocator();
return composable_traits::try_deallocate_array(alloc, ptr, count, size, alignment);
}
/// @}
/// @{
/// \effects Forwards to the \c StoragePolicy.
/// \returns Returns a reference to the stored allocator.
/// \note This does not lock the \c Mutex.
auto get_allocator() noexcept
-> decltype(std::declval<storage_policy>().get_allocator())
{
return storage_policy::get_allocator();
}
auto get_allocator() const noexcept
-> decltype(std::declval<const storage_policy>().get_allocator())
{
return storage_policy::get_allocator();
}
/// @}
/// @{
/// \returns A proxy object that acts like a pointer to the stored allocator.
/// It cannot be reassigned to point to another allocator object and only moving is supported, which is destructive.
/// As long as the proxy object lives and is not moved from, the \c Mutex will be kept locked.
auto lock() noexcept -> WPI_IMPL_DEFINED(decltype(detail::lock_allocator(
std::declval<storage_policy>().get_allocator(), std::declval<actual_mutex&>())))
{
return detail::lock_allocator(get_allocator(), static_cast<actual_mutex&>(*this));
}
auto lock() const noexcept -> WPI_IMPL_DEFINED(decltype(
detail::lock_allocator(std::declval<const storage_policy>().get_allocator(),
std::declval<actual_mutex&>())))
{
return detail::lock_allocator(get_allocator(), static_cast<actual_mutex&>(*this));
}
/// @}.
/// \returns Whether or not the stored allocator is composable,
/// that is you can use the compositioning functions.
/// \note Due to type-erased allocators,
/// this function can not be `constexpr`.
bool is_composable() const noexcept
{
return StoragePolicy::is_composable();
}
};
/// Tag type that enables type-erasure in \ref reference_storage.
/// It can be used everywhere a \ref allocator_reference is used internally.
/// \ingroup storage
struct any_allocator
{
};
/// A \concept{concept_storagepolicy,StoragePolicy} that stores the allocator directly.
/// It embeds the allocator inside it, i.e. moving the storage policy will move the allocator.
/// \ingroup storage
template <class RawAllocator>
class direct_storage : WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
{
static_assert(!std::is_same<RawAllocator, any_allocator>::value,
"cannot type-erase in direct_storage");
public:
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
/// \effects Creates it by default-constructing the allocator.
/// \requires The \c RawAllcoator must be default constructible.
direct_storage() = default;
/// \effects Creates it by moving in an allocator object.
direct_storage(allocator_type&& allocator) noexcept
: allocator_type(detail::move(allocator))
{
}
/// @{
/// \effects Moves the \c direct_storage object.
/// This will move the stored allocator.
direct_storage(direct_storage&& other) noexcept : allocator_type(detail::move(other)) {}
direct_storage& operator=(direct_storage&& other) noexcept
{
allocator_type::operator=(detail::move(other));
return *this;
}
/// @}
/// @{
/// \returns A (\c const) reference to the stored allocator.
allocator_type& get_allocator() noexcept
{
return *this;
}
const allocator_type& get_allocator() const noexcept
{
return *this;
}
/// @}
protected:
~direct_storage() noexcept = default;
bool is_composable() const noexcept
{
return is_composable_allocator<allocator_type>::value;
}
};
/// An alias template for \ref allocator_storage using the \ref direct_storage policy without a mutex.
/// It has the effect of giving any \concept{concept_rawallocator,RawAllocator} the interface with all member functions,
/// avoiding the need to wrap it inside the \ref allocator_traits.
/// \ingroup storage
template <class RawAllocator>
WPI_ALIAS_TEMPLATE(allocator_adapter,
allocator_storage<direct_storage<RawAllocator>, no_mutex>);
/// \returns A new \ref allocator_adapter object created by forwarding to the constructor.
/// \relates allocator_adapter
template <class RawAllocator>
auto make_allocator_adapter(RawAllocator&& allocator) noexcept
-> allocator_adapter<typename std::decay<RawAllocator>::type>
{
return {detail::forward<RawAllocator>(allocator)};
}
/// An alias template for \ref allocator_storage using the \ref direct_storage policy with a mutex.
/// It has a similar effect as \ref allocator_adapter but performs synchronization.
/// The \c Mutex will default to \c std::mutex if threading is supported,
/// otherwise there is no default.
/// \ingroup storage
#if WPI_HOSTED_IMPLEMENTATION
template <class RawAllocator, class Mutex = std::mutex>
WPI_ALIAS_TEMPLATE(thread_safe_allocator,
allocator_storage<direct_storage<RawAllocator>, Mutex>);
#else
template <class RawAllocator, class Mutex>
WPI_ALIAS_TEMPLATE(thread_safe_allocator,
allocator_storage<direct_storage<RawAllocator>, Mutex>);
#endif
#if WPI_HOSTED_IMPLEMENTATION
/// \returns A new \ref thread_safe_allocator object created by forwarding to the constructor/
/// \relates thread_safe_allocator
template <class RawAllocator>
auto make_thread_safe_allocator(RawAllocator&& allocator)
-> thread_safe_allocator<typename std::decay<RawAllocator>::type>
{
return detail::forward<RawAllocator>(allocator);
}
#endif
/// \returns A new \ref thread_safe_allocator object created by forwarding to the constructor,
/// specifying a certain mutex type.
/// \requires It requires threading support from the implementation.
/// \relates thread_safe_allocator
template <class Mutex, class RawAllocator>
auto make_thread_safe_allocator(RawAllocator&& allocator)
-> thread_safe_allocator<typename std::decay<RawAllocator>::type, Mutex>
{
return detail::forward<RawAllocator>(allocator);
}
namespace detail
{
struct reference_stateful
{
};
struct reference_stateless
{
};
struct reference_shared
{
};
reference_stateful reference_type(std::true_type stateful, std::false_type shared);
reference_stateless reference_type(std::false_type stateful, std::true_type shared);
reference_stateless reference_type(std::false_type stateful, std::false_type shared);
reference_shared reference_type(std::true_type stateful, std::true_type shared);
template <class RawAllocator, class Tag>
class reference_storage_impl;
// reference to stateful: stores a pointer to an allocator
template <class RawAllocator>
class reference_storage_impl<RawAllocator, reference_stateful>
{
protected:
reference_storage_impl() noexcept : alloc_(nullptr) {}
reference_storage_impl(RawAllocator& allocator) noexcept : alloc_(&allocator) {}
bool is_valid() const noexcept
{
return alloc_ != nullptr;
}
RawAllocator& get_allocator() const noexcept
{
WPI_MEMORY_ASSERT(alloc_ != nullptr);
return *alloc_;
}
private:
RawAllocator* alloc_;
};
// reference to stateless: store in static storage
template <class RawAllocator>
class reference_storage_impl<RawAllocator, reference_stateless>
{
protected:
reference_storage_impl() noexcept = default;
reference_storage_impl(const RawAllocator&) noexcept {}
bool is_valid() const noexcept
{
return true;
}
RawAllocator& get_allocator() const noexcept
{
static RawAllocator alloc;
return alloc;
}
};
// reference to shared: stores RawAllocator directly
template <class RawAllocator>
class reference_storage_impl<RawAllocator, reference_shared>
{
protected:
reference_storage_impl() noexcept = default;
reference_storage_impl(const RawAllocator& alloc) noexcept : alloc_(alloc) {}
bool is_valid() const noexcept
{
return true;
}
RawAllocator& get_allocator() const noexcept
{
return alloc_;
}
private:
mutable RawAllocator alloc_;
};
} // namespace detail
/// Specifies whether or not a \concept{concept_rawallocator,RawAllocator} has shared semantics.
/// It is shared, if - like \ref allocator_reference - if multiple objects refer to the same internal allocator and if it can be copied.
/// This sharing is stateful, however, stateless allocators are not considered shared in the meaning of this traits. <br>
/// If a \c RawAllocator is shared, it will be directly embedded inside \ref reference_storage since it already provides \ref allocator_reference like semantics, so there is no need to add them manually,<br>
/// Specialize it for your own types, if they provide sharing semantics and can be copied.
/// They also must provide an `operator==` to check whether two allocators refer to the same shared one.
/// \note This makes no guarantees about the lifetime of the shared object, the sharing allocators can either own or refer to a shared object.
/// \ingroup storage
template <class RawAllocator>
struct is_shared_allocator : std::false_type
{
};
/// A \concept{concept_storagepolicy,StoragePolicy} that stores a reference to an allocator.
/// For stateful allocators it only stores a pointer to an allocator object and copying/moving only copies the pointer.
/// For stateless allocators it does not store anything, an allocator will be constructed as needed.
/// For allocators that are already shared (determined through \ref is_shared_allocator) it will store the allocator type directly.
/// \note It does not take ownership over the allocator in the stateful case, the user has to ensure that the allocator object stays valid.
/// In the other cases the lifetime does not matter.
/// \ingroup storage
template <class RawAllocator>
class reference_storage
#ifndef DOXYGEN
: WPI_EBO(detail::reference_storage_impl<
typename allocator_traits<RawAllocator>::allocator_type,
decltype(detail::reference_type(
typename allocator_traits<RawAllocator>::is_stateful{},
is_shared_allocator<RawAllocator>{}))>)
#endif
{
using storage = detail::reference_storage_impl<
typename allocator_traits<RawAllocator>::allocator_type,
decltype(
detail::reference_type(typename allocator_traits<RawAllocator>::is_stateful{},
is_shared_allocator<RawAllocator>{}))>;
public:
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
/// Default constructor.
/// \effects If the allocator is stateless, this has no effect and the object is usable as an allocator.
/// If the allocator is stateful, creates an invalid reference without any associated allocator.
/// Then it must not be used.
/// If the allocator is shared, default constructs the shared allocator.
/// If the shared allocator does not have a default constructor, this constructor is ill-formed.
reference_storage() noexcept = default;
/// \effects Creates it from a stateless or shared allocator.
/// It will not store anything, only creates the allocator as needed.
/// \requires The \c RawAllocator is stateless or shared.
reference_storage(const allocator_type& alloc) noexcept : storage(alloc) {}
/// \effects Creates it from a reference to a stateful allocator.
/// It will store a pointer to this allocator object.
/// \note The user has to take care that the lifetime of the reference does not exceed the allocator lifetime.
reference_storage(allocator_type& alloc) noexcept : storage(alloc) {}
/// @{
/// \effects Copies the \c allocator_reference object.
/// Only copies the pointer to it in the stateful case.
reference_storage(const reference_storage&) noexcept = default;
reference_storage& operator=(const reference_storage&) noexcept = default;
/// @}
/// \returns Whether or not the reference is valid.
/// It is only invalid, if it was created by the default constructor and the allocator is stateful.
explicit operator bool() const noexcept
{
return storage::is_valid();
}
/// \returns Returns a reference to the allocator.
/// \requires The reference must be valid.
allocator_type& get_allocator() const noexcept
{
return storage::get_allocator();
}
protected:
~reference_storage() noexcept = default;
bool is_composable() const noexcept
{
return is_composable_allocator<allocator_type>::value;
}
};
/// Specialization of the class template \ref reference_storage that is type-erased.
/// It is triggered by the tag type \ref any_allocator.
/// The specialization can store a reference to any allocator type.
/// \ingroup storage
template <>
class reference_storage<any_allocator>
{
class base_allocator
{
public:
using is_stateful = std::true_type;
virtual ~base_allocator() = default;
virtual void clone(void* storage) const noexcept = 0;
void* allocate_node(std::size_t size, std::size_t alignment)
{
return allocate_impl(1, size, alignment);
}
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
{
return allocate_impl(count, size, alignment);
}
void deallocate_node(void* node, std::size_t size, std::size_t alignment) noexcept
{
deallocate_impl(node, 1, size, alignment);
}
void deallocate_array(void* array, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
deallocate_impl(array, count, size, alignment);
}
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
{
return try_allocate_impl(1, size, alignment);
}
void* try_allocate_array(std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
return try_allocate_impl(count, size, alignment);
}
bool try_deallocate_node(void* node, std::size_t size,
std::size_t alignment) noexcept
{
return try_deallocate_impl(node, 1, size, alignment);
}
bool try_deallocate_array(void* array, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
return try_deallocate_impl(array, count, size, alignment);
}
// count 1 means node
virtual void* allocate_impl(std::size_t count, std::size_t size,
std::size_t alignment) = 0;
virtual void deallocate_impl(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept = 0;
virtual void* try_allocate_impl(std::size_t count, std::size_t size,
std::size_t alignment) noexcept = 0;
virtual bool try_deallocate_impl(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept = 0;
std::size_t max_node_size() const
{
return max(query::node_size);
}
std::size_t max_array_size() const
{
return max(query::array_size);
}
std::size_t max_alignment() const
{
return max(query::alignment);
}
virtual bool is_composable() const noexcept = 0;
protected:
enum class query
{
node_size,
array_size,
alignment
};
virtual std::size_t max(query q) const = 0;
};
public:
using allocator_type = WPI_IMPL_DEFINED(base_allocator);
/// \effects Creates it from a reference to any stateful \concept{concept_rawallocator,RawAllocator}.
/// It will store a pointer to this allocator object.
/// \note The user has to take care that the lifetime of the reference does not exceed the allocator lifetime.
template <class RawAllocator>
reference_storage(RawAllocator& alloc) noexcept
{
static_assert(sizeof(basic_allocator<RawAllocator>)
<= sizeof(basic_allocator<default_instantiation>),
"requires all instantiations to have certain maximum size");
::new (static_cast<void*>(&storage_)) basic_allocator<RawAllocator>(alloc);
}
// \effects Creates it from any stateless \concept{concept_rawallocator,RawAllocator}.
/// It will not store anything, only creates the allocator as needed.
/// \requires The \c RawAllocator is stateless.
template <class RawAllocator>
reference_storage(
const RawAllocator& alloc,
WPI_REQUIRES(!allocator_traits<RawAllocator>::is_stateful::value)) noexcept
{
static_assert(sizeof(basic_allocator<RawAllocator>)
<= sizeof(basic_allocator<default_instantiation>),
"requires all instantiations to have certain maximum size");
::new (static_cast<void*>(&storage_)) basic_allocator<RawAllocator>(alloc);
}
/// \effects Creates it from the internal base class for the type-erasure.
/// Has the same effect as if the actual stored allocator were passed to the other constructor overloads.
/// \note This constructor is used internally to avoid double-nesting.
reference_storage(const WPI_IMPL_DEFINED(base_allocator) & alloc) noexcept
{
alloc.clone(&storage_);
}
/// \effects Creates it from the internal base class for the type-erasure.
/// Has the same effect as if the actual stored allocator were passed to the other constructor overloads.
/// \note This constructor is used internally to avoid double-nesting.
reference_storage(WPI_IMPL_DEFINED(base_allocator) & alloc) noexcept
: reference_storage(static_cast<const base_allocator&>(alloc))
{
}
/// @{
/// \effects Copies the \c reference_storage object.
/// It only copies the pointer to the allocator.
reference_storage(const reference_storage& other) noexcept
{
other.get_allocator().clone(&storage_);
}
reference_storage& operator=(const reference_storage& other) noexcept
{
get_allocator().~allocator_type();
other.get_allocator().clone(&storage_);
return *this;
}
/// @}
/// \returns A reference to the allocator.
/// The actual type is implementation-defined since it is the base class used in the type-erasure,
/// but it provides the full \concept{concept_rawallocator,RawAllocator} member functions.
/// \note There is no way to access any custom member functions of the allocator type.
allocator_type& get_allocator() const noexcept
{
auto mem = static_cast<void*>(&storage_);
return *static_cast<base_allocator*>(mem);
}
protected:
~reference_storage() noexcept
{
get_allocator().~allocator_type();
}
bool is_composable() const noexcept
{
return get_allocator().is_composable();
}
private:
template <class RawAllocator>
class basic_allocator
: public base_allocator,
private detail::reference_storage_impl<
typename allocator_traits<RawAllocator>::allocator_type,
decltype(
detail::reference_type(typename allocator_traits<RawAllocator>::is_stateful{},
is_shared_allocator<RawAllocator>{}))>
{
using traits = allocator_traits<RawAllocator>;
using composable = is_composable_allocator<typename traits::allocator_type>;
using storage = detail::reference_storage_impl<
typename allocator_traits<RawAllocator>::allocator_type,
decltype(detail::reference_type(typename allocator_traits<
RawAllocator>::is_stateful{},
is_shared_allocator<RawAllocator>{}))>;
public:
// non stateful
basic_allocator(const RawAllocator& alloc) noexcept : storage(alloc) {}
// stateful
basic_allocator(RawAllocator& alloc) noexcept : storage(alloc) {}
private:
typename traits::allocator_type& get() const noexcept
{
return storage::get_allocator();
}
void clone(void* storage) const noexcept override
{
::new (storage) basic_allocator(get());
}
void* allocate_impl(std::size_t count, std::size_t size,
std::size_t alignment) override
{
auto&& alloc = get();
if (count == 1u)
return traits::allocate_node(alloc, size, alignment);
else
return traits::allocate_array(alloc, count, size, alignment);
}
void deallocate_impl(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept override
{
auto&& alloc = get();
if (count == 1u)
traits::deallocate_node(alloc, ptr, size, alignment);
else
traits::deallocate_array(alloc, ptr, count, size, alignment);
}
void* try_allocate_impl(std::size_t count, std::size_t size,
std::size_t alignment) noexcept override
{
auto&& alloc = get();
if (count == 1u)
return detail::try_allocate_node(composable{}, alloc, size, alignment);
else
return detail::try_allocate_array(composable{}, alloc, count, size,
alignment);
}
bool try_deallocate_impl(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept override
{
auto&& alloc = get();
if (count == 1u)
return detail::try_deallocate_node(composable{}, alloc, ptr, size,
alignment);
else
return detail::try_deallocate_array(composable{}, alloc, ptr, count, size,
alignment);
}
bool is_composable() const noexcept override
{
return composable::value;
}
std::size_t max(query q) const override
{
auto&& alloc = get();
if (q == query::node_size)
return traits::max_node_size(alloc);
else if (q == query::array_size)
return traits::max_array_size(alloc);
return traits::max_alignment(alloc);
}
};
// use a stateful instantiation to determine size and alignment
// base_allocator is stateful
using default_instantiation = basic_allocator<base_allocator>;
alignas(default_instantiation) mutable char storage_[sizeof(default_instantiation)];
};
/// An alias template for \ref allocator_storage using the \ref reference_storage policy.
/// It will store a reference to the given allocator type. The tag type \ref any_allocator enables type-erasure.
/// Wrap the allocator in a \ref thread_safe_allocator if you want thread safety.
/// \ingroup storage
template <class RawAllocator>
WPI_ALIAS_TEMPLATE(allocator_reference,
allocator_storage<reference_storage<RawAllocator>, no_mutex>);
/// \returns A new \ref allocator_reference object by forwarding the allocator to the constructor.
/// \relates allocator_reference
template <class RawAllocator>
auto make_allocator_reference(RawAllocator&& allocator) noexcept
-> allocator_reference<typename std::decay<RawAllocator>::type>
{
return {detail::forward<RawAllocator>(allocator)};
}
/// An alias for the \ref reference_storage specialization using type-erasure.
/// \ingroup storage
using any_reference_storage = reference_storage<any_allocator>;
/// An alias for \ref allocator_storage using the \ref any_reference_storage.
/// It will store a reference to any \concept{concept_rawallocator,RawAllocator}.
/// This is the same as passing the tag type \ref any_allocator to the alias \ref allocator_reference.
/// Wrap the allocator in a \ref thread_safe_allocator if you want thread safety.
/// \ingroup storage
using any_allocator_reference = allocator_storage<any_reference_storage, no_mutex>;
/// \returns A new \ref any_allocator_reference object by forwarding the allocator to the constructor.
/// \relates any_allocator_reference
template <class RawAllocator>
auto make_any_allocator_reference(RawAllocator&& allocator) noexcept
-> any_allocator_reference
{
return {detail::forward<RawAllocator>(allocator)};
}
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED

View File

@@ -0,0 +1,603 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_ALLOCATOR_TRAITS_HPP_INCLUDED
#define WPI_MEMORY_ALLOCATOR_TRAITS_HPP_INCLUDED
/// \file
/// The default specialization of the \ref wpi::memory::allocator_traits.
#include <cstddef>
#include <type_traits>
#include "detail/align.hpp"
#include "detail/utility.hpp"
#include "config.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include <memory>
#endif
namespace wpi
{
namespace memory
{
namespace detail
{
template <class Allocator>
std::true_type has_construct(int, WPI_SFINAE(std::declval<Allocator>().construct(
std::declval<typename Allocator::pointer>(),
std::declval<typename Allocator::value_type>())));
template <class Allocator>
std::false_type has_construct(short);
template <class Allocator>
std::true_type has_destroy(int, WPI_SFINAE(std::declval<Allocator>().destroy(
std::declval<typename Allocator::pointer>())));
template <class Allocator>
std::false_type has_destroy(short);
template <class Allocator>
struct check_standard_allocator
{
using custom_construct = decltype(has_construct<Allocator>(0));
using custom_destroy = decltype(has_destroy<Allocator>(0));
using valid = std::integral_constant<bool, !custom_construct::value
&& !custom_destroy::value>;
};
} // namespace detail
/// Traits class that checks whether or not a standard \c Allocator can be used as \concept{concept_rawallocator,RawAllocator}.
/// It checks the existence of a custom \c construct(), \c destroy() function, if provided,
/// it cannot be used since it would not be called.<br>
/// Specialize it for custom \c Allocator types to override this check.
/// \ingroup core
template <class Allocator>
struct allocator_is_raw_allocator
: WPI_EBO(detail::check_standard_allocator<Allocator>::valid)
{
};
/// Specialization of \ref allocator_is_raw_allocator that allows \c std::allocator again.
/// \ingroup core
template <typename T>
struct allocator_is_raw_allocator<std::allocator<T>> : std::true_type
{
};
namespace traits_detail // use seperate namespace to avoid name clashes
{
// full_concept has the best conversion rank, error the lowest
// used to give priority to the functions
struct error
{
operator void*() const noexcept
{
WPI_MEMORY_UNREACHABLE(
"this is just to hide an error and move static_assert to the front");
return nullptr;
}
};
struct std_concept : error
{
};
struct min_concept : std_concept
{
};
struct full_concept : min_concept
{
};
// used to delay assert in handle_error() until instantiation
template <typename T>
struct invalid_allocator_concept
{
static const bool error = false;
};
//=== allocator_type ===//
// if Allocator has a member template `rebind`, use that to rebind to `char`
// else if Allocator has a member `value_type`, rebind by changing argument
// else does nothing
template <class Allocator>
auto rebind_impl(int) -> typename Allocator::template rebind<char>::other&;
template <class Allocator, typename T>
struct allocator_rebinder
{
using type = Allocator&;
};
template <template <typename, typename...> class Alloc, typename U, typename... Args,
typename T>
struct allocator_rebinder<Alloc<U, Args...>, T>
{
using type = Alloc<T, Args...>&;
};
template <class Allocator, typename = typename Allocator::value_type>
auto rebind_impl(char) -> typename allocator_rebinder<Allocator, char>::type;
template <class Allocator>
auto rebind_impl(...) -> Allocator&;
template <class Allocator>
struct allocator_type_impl // required for MSVC
{
using type = decltype(rebind_impl<Allocator>(0));
};
template <class Allocator>
using allocator_type =
typename std::decay<typename allocator_type_impl<Allocator>::type>::type;
//=== is_stateful ===//
// first try to access Allocator::is_stateful,
// then use whether or not the type is empty
template <class Allocator>
auto is_stateful(full_concept) -> decltype(typename Allocator::is_stateful{});
template <class Allocator, bool IsEmpty>
struct is_stateful_impl;
template <class Allocator>
struct is_stateful_impl<Allocator, true>
{
static_assert(std::is_default_constructible<Allocator>::value,
"RawAllocator is empty but not default constructible ."
"This means it is not a stateless allocator. "
"If this is actually intended provide the appropriate is_stateful "
"typedef in your class.");
using type = std::false_type;
};
template <class Allocator>
struct is_stateful_impl<Allocator, false>
{
using type = std::true_type;
};
template <class Allocator>
auto is_stateful(min_concept) ->
typename is_stateful_impl<Allocator, std::is_empty<Allocator>::value>::type;
//=== allocate_node() ===//
// first try Allocator::allocate_node
// then assume std_allocator and call Allocator::allocate
// then error
template <class Allocator>
auto allocate_node(full_concept, Allocator& alloc, std::size_t size,
std::size_t alignment)
-> WPI_AUTO_RETURN_TYPE(alloc.allocate_node(size, alignment), void*)
template <class Allocator>
auto allocate_node(std_concept, Allocator& alloc, std::size_t size, std::size_t)
-> WPI_AUTO_RETURN(static_cast<void*>(alloc.allocate(size)))
template <class Allocator>
error allocate_node(error, Allocator&, std::size_t, std::size_t)
{
static_assert(invalid_allocator_concept<Allocator>::error,
"type is not a RawAllocator as it does not provide: void* "
"allocate_node(std::size_t, "
"std::size_t)");
return {};
}
//=== deallocate_node() ===//
// first try Allocator::deallocate_node
// then assume std_allocator and call Allocator::deallocate
// then error
template <class Allocator>
auto deallocate_node(full_concept, Allocator& alloc, void* ptr, std::size_t size,
std::size_t alignment) noexcept
-> WPI_AUTO_RETURN_TYPE(alloc.deallocate_node(ptr, size, alignment), void)
template <class Allocator>
auto deallocate_node(std_concept, Allocator& alloc, void* ptr, std::size_t size,
std::size_t) noexcept
-> WPI_AUTO_RETURN_TYPE(alloc.deallocate(static_cast<char*>(ptr), size), void)
template <class Allocator>
error deallocate_node(error, Allocator&, void*, std::size_t, std::size_t)
{
static_assert(invalid_allocator_concept<Allocator>::error,
"type is not a RawAllocator as it does not provide: void "
"deallocate_node(void*, std::size_t, "
"std::size_t)");
return error{};
}
//=== allocate_array() ===//
// first try Allocator::allocate_array
// then forward to allocate_node()
template <class Allocator>
auto allocate_array(full_concept, Allocator& alloc, std::size_t count, std::size_t size,
std::size_t alignment)
-> WPI_AUTO_RETURN_TYPE(alloc.allocate_array(count, size, alignment), void*)
template <class Allocator>
void* allocate_array(min_concept, Allocator& alloc, std::size_t count,
std::size_t size, std::size_t alignment)
{
return allocate_node(full_concept{}, alloc, count * size, alignment);
}
//=== deallocate_array() ===//
// first try Allocator::deallocate_array
// then forward to deallocate_node()
template <class Allocator>
auto deallocate_array(full_concept, Allocator& alloc, void* ptr, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
-> WPI_AUTO_RETURN_TYPE(alloc.deallocate_array(ptr, count, size, alignment),
void)
template <class Allocator>
void deallocate_array(min_concept, Allocator& alloc, void* ptr,
std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
deallocate_node(full_concept{}, alloc, ptr, count * size, alignment);
}
//=== max_node_size() ===//
// first try Allocator::max_node_size()
// then return maximum value
template <class Allocator>
auto max_node_size(full_concept, const Allocator& alloc)
-> WPI_AUTO_RETURN_TYPE(alloc.max_node_size(), std::size_t)
template <class Allocator>
std::size_t max_node_size(min_concept, const Allocator&) noexcept
{
return std::size_t(-1);
}
//=== max_node_size() ===//
// first try Allocator::max_array_size()
// then forward to max_node_size()
template <class Allocator>
auto max_array_size(full_concept, const Allocator& alloc)
-> WPI_AUTO_RETURN_TYPE(alloc.max_array_size(), std::size_t)
template <class Allocator>
std::size_t max_array_size(min_concept, const Allocator& alloc)
{
return max_node_size(full_concept{}, alloc);
}
//=== max_alignment() ===//
// first try Allocator::max_alignment()
// then return detail::max_alignment
template <class Allocator>
auto max_alignment(full_concept, const Allocator& alloc)
-> WPI_AUTO_RETURN_TYPE(alloc.max_alignment(), std::size_t)
template <class Allocator>
std::size_t max_alignment(min_concept, const Allocator&)
{
return detail::max_alignment;
}
} // namespace traits_detail
/// The default specialization of the allocator_traits for a \concept{concept_rawallocator,RawAllocator}.
/// See the last link for the requirements on types that do not specialize this class and the interface documentation.
/// Any specialization must provide the same interface.
/// \ingroup core
template <class Allocator>
class allocator_traits
{
public:
using allocator_type = traits_detail::allocator_type<Allocator>;
using is_stateful =
decltype(traits_detail::is_stateful<Allocator>(traits_detail::full_concept{}));
static void* allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment)
{
static_assert(allocator_is_raw_allocator<Allocator>::value,
"Allocator cannot be used as RawAllocator because it provides custom "
"construct()/destroy()");
return traits_detail::allocate_node(traits_detail::full_concept{}, state, size,
alignment);
}
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
std::size_t alignment)
{
static_assert(allocator_is_raw_allocator<Allocator>::value,
"Allocator cannot be used as RawAllocator because it provides custom "
"construct()/destroy()");
return traits_detail::allocate_array(traits_detail::full_concept{}, state, count,
size, alignment);
}
static void deallocate_node(allocator_type& state, void* node, std::size_t size,
std::size_t alignment) noexcept
{
static_assert(allocator_is_raw_allocator<Allocator>::value,
"Allocator cannot be used as RawAllocator because it provides custom "
"construct()/destroy()");
traits_detail::deallocate_node(traits_detail::full_concept{}, state, node, size,
alignment);
}
static void deallocate_array(allocator_type& state, void* array, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
static_assert(allocator_is_raw_allocator<Allocator>::value,
"Allocator cannot be used as RawAllocator because it provides custom "
"construct()/destroy()");
traits_detail::deallocate_array(traits_detail::full_concept{}, state, array, count,
size, alignment);
}
static std::size_t max_node_size(const allocator_type& state)
{
static_assert(allocator_is_raw_allocator<Allocator>::value,
"Allocator cannot be used as RawAllocator because it provides custom "
"construct()/destroy()");
return traits_detail::max_node_size(traits_detail::full_concept{}, state);
}
static std::size_t max_array_size(const allocator_type& state)
{
static_assert(allocator_is_raw_allocator<Allocator>::value,
"Allocator cannot be used as RawAllocator because it provides custom "
"construct()/destroy()");
return traits_detail::max_array_size(traits_detail::full_concept{}, state);
}
static std::size_t max_alignment(const allocator_type& state)
{
static_assert(allocator_is_raw_allocator<Allocator>::value,
"Allocator cannot be used as RawAllocator because it provides custom "
"construct()/destroy()");
return traits_detail::max_alignment(traits_detail::full_concept{}, state);
}
#if !defined(DOXYGEN)
using foonathan_memory_default_traits = std::true_type;
#endif
};
namespace detail
{
template <class RawAllocator>
typename allocator_traits<RawAllocator>::foonathan_memory_default_traits
alloc_uses_default_traits(RawAllocator&);
std::false_type alloc_uses_default_traits(...);
template <typename T>
struct has_invalid_alloc_function
: std::is_same<decltype(
traits_detail::allocate_node(traits_detail::full_concept{},
std::declval<typename allocator_traits<
T>::allocator_type&>(),
0, 0)),
traits_detail::error>
{
};
template <typename T>
struct has_invalid_dealloc_function
: std::is_same<
decltype(traits_detail::deallocate_node(traits_detail::full_concept{},
std::declval<typename allocator_traits<
T>::allocator_type&>(),
nullptr, 0, 0)),
traits_detail::error>
{
};
template <typename T, class DefaultTraits>
struct is_raw_allocator : std::true_type
{
};
template <typename T>
struct is_raw_allocator<T, std::integral_constant<bool, true>>
: std::integral_constant<bool, allocator_is_raw_allocator<T>::value
&& !(has_invalid_alloc_function<T>::value
|| has_invalid_dealloc_function<T>::value)>
{
};
} // namespace detail
/// Traits that check whether a type models concept \concept{concept_rawallocator,RawAllocator}.<br>
/// It must either provide the necessary functions for the default traits specialization or has specialized it.
/// \ingroup core
template <typename T>
struct is_raw_allocator
: detail::is_raw_allocator<T,
decltype(detail::alloc_uses_default_traits(std::declval<T&>()))>
{
};
namespace traits_detail
{
//=== try_allocate_node() ===//
// try Allocator::try_allocate_node
// otherwise error
template <class Allocator>
auto try_allocate_node(full_concept, Allocator& alloc, std::size_t size,
std::size_t alignment) noexcept
-> WPI_AUTO_RETURN_TYPE(alloc.try_allocate_node(size, alignment), void*)
template <class Allocator>
error try_allocate_node(error, Allocator&, std::size_t, std::size_t)
{
static_assert(invalid_allocator_concept<Allocator>::error,
"type is not a composable RawAllocator as it does not provide: void* "
"try_allocate_node(std::size_t, "
"std::size_t)");
return {};
}
//=== try_deallocate_node() ===//
// try Allocator::try_deallocate_node
// otherwise error
template <class Allocator>
auto try_deallocate_node(full_concept, Allocator& alloc, void* ptr, std::size_t size,
std::size_t alignment) noexcept
-> WPI_AUTO_RETURN_TYPE(alloc.try_deallocate_node(ptr, size, alignment), bool)
template <class Allocator>
error try_deallocate_node(error, Allocator&, void*, std::size_t, std::size_t)
{
static_assert(invalid_allocator_concept<Allocator>::error,
"type is not a composable RawAllocator as it does not provide: bool "
"try_deallocate_node(void*, std::size_t, "
"std::size_t)");
return error{};
}
//=== try_allocate_array() ===//
// first try Allocator::try_allocate_array
// then forward to try_allocate_node()
template <class Allocator>
auto try_allocate_array(full_concept, Allocator& alloc, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
-> WPI_AUTO_RETURN_TYPE(alloc.try_allocate_array(count, size, alignment),
void*)
template <class Allocator>
void* try_allocate_array(min_concept, Allocator& alloc, std::size_t count,
std::size_t size, std::size_t alignment)
{
return try_allocate_node(full_concept{}, alloc, count * size, alignment);
}
//=== try_deallocate_array() ===//
// first try Allocator::try_deallocate_array
// then forward to try_deallocate_node()
template <class Allocator>
auto try_deallocate_array(full_concept, Allocator& alloc, void* ptr, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
-> WPI_AUTO_RETURN_TYPE(alloc.try_deallocate_array(ptr, count, size,
alignment),
bool)
template <class Allocator>
bool try_deallocate_array(min_concept, Allocator& alloc, void* ptr,
std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
return try_deallocate_node(full_concept{}, alloc, ptr, count * size, alignment);
}
} // namespace traits_detail
/// The default specialization of the composable_allocator_traits for a \concept{concept_composableallocator,ComposableAllocator}.
/// See the last link for the requirements on types that do not specialize this class and the interface documentation.
/// Any specialization must provide the same interface.
/// \ingroup core
template <class Allocator>
class composable_allocator_traits
{
public:
using allocator_type = typename allocator_traits<Allocator>::allocator_type;
static void* try_allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment) noexcept
{
static_assert(is_raw_allocator<Allocator>::value,
"ComposableAllocator must be RawAllocator");
return traits_detail::try_allocate_node(traits_detail::full_concept{}, state, size,
alignment);
}
static void* try_allocate_array(allocator_type& state, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
static_assert(is_raw_allocator<Allocator>::value,
"ComposableAllocator must be RawAllocator");
return traits_detail::try_allocate_array(traits_detail::full_concept{}, state,
count, size, alignment);
}
static bool try_deallocate_node(allocator_type& state, void* node, std::size_t size,
std::size_t alignment) noexcept
{
static_assert(is_raw_allocator<Allocator>::value,
"ComposableAllocator must be RawAllocator");
return traits_detail::try_deallocate_node(traits_detail::full_concept{}, state,
node, size, alignment);
}
static bool try_deallocate_array(allocator_type& state, void* array, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
static_assert(is_raw_allocator<Allocator>::value,
"ComposableAllocator must be RawAllocator");
return traits_detail::try_deallocate_array(traits_detail::full_concept{}, state,
array, count, size, alignment);
}
#if !defined(DOXYGEN)
using foonathan_memory_default_traits = std::true_type;
#endif
};
namespace detail
{
template <class RawAllocator>
typename composable_allocator_traits<RawAllocator>::foonathan_memory_default_traits
composable_alloc_uses_default_traits(RawAllocator&);
std::false_type composable_alloc_uses_default_traits(...);
template <typename T>
struct has_invalid_try_alloc_function
: std::is_same<
decltype(traits_detail::try_allocate_node(traits_detail::full_concept{},
std::declval<typename allocator_traits<
T>::allocator_type&>(),
0, 0)),
traits_detail::error>
{
};
template <typename T>
struct has_invalid_try_dealloc_function
: std::is_same<
decltype(
traits_detail::try_deallocate_node(traits_detail::full_concept{},
std::declval<typename allocator_traits<
T>::allocator_type&>(),
nullptr, 0, 0)),
traits_detail::error>
{
};
template <typename T, class DefaultTraits>
struct is_composable_allocator : memory::is_raw_allocator<T>
{
};
template <typename T>
struct is_composable_allocator<T, std::integral_constant<bool, true>>
: std::integral_constant<bool, memory::is_raw_allocator<T>::value
&& !(has_invalid_try_alloc_function<T>::value
|| has_invalid_try_dealloc_function<T>::value)>
{
};
} // namespace detail
/// Traits that check whether a type models concept \concept{concept_rawallocator,ComposableAllocator}.<br>
/// It must be a \concept{concept_rawallocator,RawAllocator} and either provide the necessary functions for the default traits specialization or has specialized it.
/// \ingroup core
template <typename T>
struct is_composable_allocator
: detail::is_composable_allocator<T, decltype(detail::composable_alloc_uses_default_traits(
std::declval<T&>()))>
{
};
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_ALLOCATOR_TRAITS_HPP_INCLUDED

View File

@@ -0,0 +1,148 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
/// \file
/// Configuration macros.
#ifndef WPI_MEMORY_CONFIG_HPP_INCLUDED
#define WPI_MEMORY_CONFIG_HPP_INCLUDED
#include <cstddef>
#if !defined(DOXYGEN)
#define WPI_MEMORY_IMPL_IN_CONFIG_HPP
#include "config_impl.hpp"
#undef WPI_MEMORY_IMPL_IN_CONFIG_HPP
#endif
// exception support
#ifndef WPI_HAS_EXCEPTION_SUPPORT
#if defined(__GNUC__) && !defined(__EXCEPTIONS)
#define WPI_HAS_EXCEPTION_SUPPORT 0
#elif defined(_MSC_VER) && !_HAS_EXCEPTIONS
#define WPI_HAS_EXCEPTION_SUPPORT 0
#else
#define WPI_HAS_EXCEPTION_SUPPORT 1
#endif
#endif
#if WPI_HAS_EXCEPTION_SUPPORT
#define WPI_THROW(Ex) throw(Ex)
#else
#include <cstdlib>
#define WPI_THROW(Ex) ((Ex), std::abort())
#endif
// hosted implementation
#ifndef WPI_HOSTED_IMPLEMENTATION
#if !_MSC_VER && !__STDC_HOSTED__
#define WPI_HOSTED_IMPLEMENTATION 0
#else
#define WPI_HOSTED_IMPLEMENTATION 1
#endif
#endif
// log prefix
#define WPI_MEMORY_LOG_PREFIX "wpi::memory"
// version
#define WPI_MEMORY_VERSION \
(WPI_MEMORY_VERSION_MAJOR * 100 + WPI_MEMORY_VERSION_MINOR)
// use this macro to mark implementation-defined types
// gives it more semantics and useful with doxygen
// add PREDEFINED: WPI_IMPL_DEFINED():=implementation_defined
#ifndef WPI_IMPL_DEFINED
#define WPI_IMPL_DEFINED(...) __VA_ARGS__
#endif
// use this macro to mark base class which only purpose is EBO
// gives it more semantics and useful with doxygen
// add PREDEFINED: WPI_EBO():=
#ifndef WPI_EBO
#define WPI_EBO(...) __VA_ARGS__
#endif
#ifndef WPI_ALIAS_TEMPLATE
// defines a template alias
// usage:
// template <typename T>
// WPI_ALIAS_TEMPLATE(bar, foo<T, int>);
// useful for doxygen
#ifdef DOXYGEN
#define WPI_ALIAS_TEMPLATE(Name, ...) \
class Name : public __VA_ARGS__ \
{ \
}
#else
#define WPI_ALIAS_TEMPLATE(Name, ...) using Name = __VA_ARGS__
#endif
#endif
#ifdef DOXYGEN
// dummy definitions of config macros for doxygen
/// The major version number.
/// \ingroup core
#define WPI_MEMORY_VERSION_MAJOR 1
/// The minor version number.
/// \ingroup core
#define WPI_MEMORY_VERSION_MINOR 1
/// The total version number of the form \c Mmm.
/// \ingroup core
#define WPI_MEMORY_VERSION \
(WPI_MEMORY_VERSION_MAJOR * 100 + WPI_MEMORY_VERSION_MINOR)
/// Whether or not the allocation size will be checked,
/// i.e. the \ref wpi::memory::bad_allocation_size thrown.
/// \ingroup core
#define WPI_MEMORY_CHECK_ALLOCATION_SIZE 1
/// Whether or not internal assertions in the library are enabled.
/// \ingroup core
#define WPI_MEMORY_DEBUG_ASSERT 1
/// Whether or not allocated memory will be filled with special values.
/// \ingroup core
#define WPI_MEMORY_DEBUG_FILL 1
/// The size of the fence memory, it has no effect if \ref WPI_MEMORY_DEBUG_FILL is \c false.
/// \note For most allocators, the actual value doesn't matter and they use appropriate defaults to ensure alignment etc.
/// \ingroup core
#define WPI_MEMORY_DEBUG_FENCE 1
/// Whether or not leak checking is enabled.
/// \ingroup core
#define WPI_MEMORY_DEBUG_LEAK_CHECK 1
/// Whether or not the deallocation functions will check for pointers that were never allocated by an allocator.
/// \ingroup core
#define WPI_MEMORY_DEBUG_POINTER_CHECK 1
/// Whether or not the deallocation functions will check for double free errors.
/// This option makes no sense if \ref WPI_MEMORY_DEBUG_POINTER_CHECK is \c false.
/// \ingroup core
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 1
/// Whether or not everything is in namespace <tt>wpi::memory</tt>.
/// If \c false, a namespace alias <tt>namespace memory = wpi::memory</tt> is automatically inserted into each header,
/// allowing to qualify everything with <tt>wpi::</tt>.
/// \note This option breaks in combination with using <tt>using namespace wpi;</tt>.
/// \ingroup core
#define WPI_MEMORY_NAMESPACE_PREFIX 1
/// The mode of the automatic \ref wpi::memory::temporary_stack creation.
/// Set to `2` to enable automatic lifetime management of the per-thread stack through nifty counter.
/// Then all memory will be freed upon program termination automatically.
/// Set to `1` to disable automatic lifetime managment of the per-thread stack,
/// requires managing it through the \ref wpi::memory::temporary_stack_initializer.
/// Set to `0` to disable the per-thread stack completely.
/// \ref get_temporary_stack() will abort the program upon call.
/// \ingroup allocator
#define WPI_MEMORY_TEMPORARY_STACK_MODE 2
#endif
#endif // WPI_MEMORY_CONFIG_HPP_INCLUDED

View File

@@ -0,0 +1,34 @@
// Copyright (c) FIRST and other WPILib contributors.
// Open Source Software; you can modify and/or share it under the terms of
// the WPILib BSD license file in the root directory of this project.
// Copyright (C) 2015-2020 Jonathan Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#pragma once
#include <cstddef>
//=== options ===//
#define WPI_MEMORY_CHECK_ALLOCATION_SIZE 1
#define WPI_MEMORY_IMPL_DEFAULT_ALLOCATOR heap_allocator
#ifdef NDEBUG
#define WPI_MEMORY_DEBUG_ASSERT 0
#define WPI_MEMORY_DEBUG_FILL 0
#define WPI_MEMORY_DEBUG_FENCE 0
#define WPI_MEMORY_DEBUG_LEAK_CHECK 0
#define WPI_MEMORY_DEBUG_POINTER_CHECK 0
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 0
#else
#define WPI_MEMORY_DEBUG_ASSERT 1
#define WPI_MEMORY_DEBUG_FILL 1
#define WPI_MEMORY_DEBUG_FENCE 8
#define WPI_MEMORY_DEBUG_LEAK_CHECK 1
#define WPI_MEMORY_DEBUG_POINTER_CHECK 1
#define WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK 1
#endif
#define WPI_MEMORY_EXTERN_TEMPLATE 1
#define WPI_MEMORY_TEMPORARY_STACK_MODE 2
#define WPI_MEMORY_NO_NODE_SIZE 1

View File

@@ -0,0 +1,362 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_CONTAINER_HPP_INCLUDED
#define WPI_MEMORY_CONTAINER_HPP_INCLUDED
/// \file
/// Aliasas for STL containers using a certain \concept{concept_rawallocator,RawAllocator}.
/// \note Only available on a hosted implementation.
#include "config.hpp"
#if !WPI_HOSTED_IMPLEMENTATION
#error "This header is only available for a hosted implementation."
#endif
#include <functional>
#include <utility>
#include <deque>
#include <forward_list>
#include <list>
#include <map>
#include <queue>
#include <scoped_allocator>
#include <set>
#include <stack>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#include "std_allocator.hpp"
#include "threading.hpp"
namespace wpi
{
namespace memory
{
/// \ingroup adapter
/// @{
/// Alias template for an STL container that uses a certain
/// \concept{concept_rawallocator,RawAllocator}. It is just a shorthand for a passing in the \c
/// RawAllocator wrapped in a \ref wpi::memory::std_allocator.
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(vector, std::vector<T, std_allocator<T, RawAllocator>>);
/// Same as above but uses \c std::scoped_allocator_adaptor so the allocator is inherited by all
/// nested containers.
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
vector_scoped_alloc,
std::vector<T, std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(deque, std::deque<T, std_allocator<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
deque_scoped_alloc,
std::deque<T, std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(list, std::list<T, std_allocator<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
list_scoped_alloc,
std::list<T, std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(forward_list,
std::forward_list<T, std_allocator<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
forward_list_scoped_alloc,
std::forward_list<T, std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(set, std::set<T, std::less<T>, std_allocator<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
set_scoped_alloc,
std::set<T, std::less<T>,
std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(multiset,
std::multiset<T, std::less<T>, std_allocator<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
multiset_scoped_alloc,
std::multiset<T, std::less<T>,
std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
/// \copydoc vector
template <typename Key, typename Value, class RawAllocator>
WPI_ALIAS_TEMPLATE(
map, std::map<Key, Value, std::less<Key>,
std_allocator<std::pair<const Key, Value>, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename Key, typename Value, class RawAllocator>
WPI_ALIAS_TEMPLATE(
map_scoped_alloc,
std::map<Key, Value, std::less<Key>,
std::scoped_allocator_adaptor<
std_allocator<std::pair<const Key, Value>, RawAllocator>>>);
/// \copydoc vector
template <typename Key, typename Value, class RawAllocator>
WPI_ALIAS_TEMPLATE(
multimap, std::multimap<Key, Value, std::less<Key>,
std_allocator<std::pair<const Key, Value>, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename Key, typename Value, class RawAllocator>
WPI_ALIAS_TEMPLATE(
multimap_scoped_alloc,
std::multimap<Key, Value, std::less<Key>,
std::scoped_allocator_adaptor<
std_allocator<std::pair<const Key, Value>, RawAllocator>>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
unordered_set,
std::unordered_set<T, std::hash<T>, std::equal_to<T>, std_allocator<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
unordered_set_scoped_alloc,
std::unordered_set<T, std::hash<T>, std::equal_to<T>,
std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(unordered_multiset,
std::unordered_multiset<T, std::hash<T>, std::equal_to<T>,
std_allocator<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(
unordered_multiset_scoped_alloc,
std::unordered_multiset<T, std::hash<T>, std::equal_to<T>,
std::scoped_allocator_adaptor<std_allocator<T, RawAllocator>>>);
/// \copydoc vector
template <typename Key, typename Value, class RawAllocator>
WPI_ALIAS_TEMPLATE(
unordered_map,
std::unordered_map<Key, Value, std::hash<Key>, std::equal_to<Key>,
std_allocator<std::pair<const Key, Value>, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename Key, typename Value, class RawAllocator>
WPI_ALIAS_TEMPLATE(
unordered_map_scoped_alloc,
std::unordered_map<Key, Value, std::hash<Key>, std::equal_to<Key>,
std::scoped_allocator_adaptor<
std_allocator<std::pair<const Key, Value>, RawAllocator>>>);
/// \copydoc vector
template <typename Key, typename Value, class RawAllocator>
WPI_ALIAS_TEMPLATE(
unordered_multimap,
std::unordered_multimap<Key, Value, std::hash<Key>, std::equal_to<Key>,
std_allocator<std::pair<const Key, Value>, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename Key, typename Value, class RawAllocator>
WPI_ALIAS_TEMPLATE(
unordered_multimap_scoped_alloc,
std::unordered_multimap<Key, Value, std::hash<Key>, std::equal_to<Key>,
std::scoped_allocator_adaptor<
std_allocator<std::pair<const Key, Value>, RawAllocator>>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(stack, std::stack<T, deque<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(stack_scoped_alloc,
std::stack<T, deque_scoped_alloc<T, RawAllocator>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(queue, std::queue<T, deque<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(queue_scoped_alloc,
std::queue<T, deque_scoped_alloc<T, RawAllocator>>);
/// \copydoc vector
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(priority_queue, std::priority_queue<T, deque<T, RawAllocator>>);
/// \copydoc vector_scoped_alloc
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(priority_queue_scoped_alloc,
std::priority_queue<T, deque_scoped_alloc<T, RawAllocator>>);
/// \copydoc vector
template <class RawAllocator>
WPI_ALIAS_TEMPLATE(
string,
std::basic_string<char, std::char_traits<char>, std_allocator<char, RawAllocator>>);
/// @}
/// @{
/// Convenience function to create a container adapter using a certain
/// \concept{concept_rawallocator,RawAllocator}. \returns An empty adapter with an
/// implementation container using a reference to a given allocator. \ingroup adapter
template <typename T, class RawAllocator, class Container = deque<T, RawAllocator>>
std::stack<T, Container> make_stack(RawAllocator& allocator)
{
return std::stack<T, Container>{Container(allocator)};
}
/// \copydoc make_stack
template <typename T, class RawAllocator, class Container = deque<T, RawAllocator>>
std::queue<T, Container> make_queue(RawAllocator& allocator)
{
return std::queue<T, Container>{Container(allocator)};
}
/// \copydoc make_stack
template <typename T, class RawAllocator, class Container = deque<T, RawAllocator>,
class Compare = std::less<T>>
std::priority_queue<T, Container, Compare> make_priority_queue(RawAllocator& allocator,
Compare comp = {})
{
return std::priority_queue<T, Container, Compare>{detail::move(comp),
Container(allocator)};
}
/// @}
#if !defined(DOXYGEN)
#include "detail/container_node_sizes.hpp"
#if !defined(WPI_MEMORY_NO_NODE_SIZE)
/// \exclude
namespace detail
{
template <typename T, class StdAllocator>
struct shared_ptr_node_size
{
static_assert(sizeof(T) != sizeof(T), "unsupported allocator type");
};
template <typename T, class RawAllocator>
struct shared_ptr_node_size<T, std_allocator<T, RawAllocator>>
: std::conditional<allocator_traits<RawAllocator>::is_stateful::value,
memory::shared_ptr_stateful_node_size<T>,
memory::shared_ptr_stateless_node_size<T>>::type
{
static_assert(sizeof(std_allocator<T, RawAllocator>) <= sizeof(void*),
"fix node size debugger");
};
} // namespace detail
template <typename T, class StdAllocator>
struct shared_ptr_node_size : detail::shared_ptr_node_size<T, StdAllocator>
{
};
#endif
#else
/// \ingroup adapter
/// @{
/// Contains the node size of a node based STL container with a specific type.
/// These classes are auto-generated and only available if the tools are build and without
/// cross-compiling.
template <typename T>
struct forward_list_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct list_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct set_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct multiset_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct unordered_set_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct unordered_multiset_node_size
: std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct map_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct multimap_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct unordered_map_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T>
struct unordered_multimap_node_size
: std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc forward_list_node_size
template <typename T, class StdAllocator>
struct shared_ptr_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// @}
#endif
#if !defined(WPI_MEMORY_NO_NODE_SIZE)
/// The node size required by \ref allocate_shared.
/// \note This is similar to \ref shared_ptr_node_size but takes a
/// \concept{concept_rawallocator,RawAllocator} instead.
template <typename T, class RawAllocator>
struct allocate_shared_node_size : shared_ptr_node_size<T, std_allocator<T, RawAllocator>>
{
};
#endif
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_CONTAINER_HPP_INCLUDED

View File

@@ -0,0 +1,114 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DEBUGGING_HPP_INCLUDED
#define WPI_MEMORY_DEBUGGING_HPP_INCLUDED
/// \file
/// Debugging facilities.
#include "config.hpp"
namespace wpi
{
namespace memory
{
struct allocator_info;
/// The magic values that are used for debug filling.
/// If \ref WPI_MEMORY_DEBUG_FILL is \c true, memory will be filled to help detect use-after-free or missing initialization errors.
/// These are the constants for the different types.
/// \ingroup core
enum class debug_magic : unsigned char
{
/// Marks internal memory used by the allocator - "allocated block".
internal_memory = 0xAB,
/// Marks internal memory currently not used by the allocator - "freed block".
internal_freed_memory = 0xFB,
/// Marks allocated, but not yet used memory - "clean memory".
new_memory = 0xCD,
/// Marks freed memory - "dead memory".
freed_memory = 0xDD,
/// Marks buffer memory used to ensure proper alignment.
/// This memory can also serve as \ref debug_magic::fence_memory.
alignment_memory = 0xED,
/// Marks buffer memory used to protect against overflow - "fence memory".
/// The option \ref WPI_MEMORY_DEBUG_FENCE controls the size of a memory fence that will be placed before or after a memory block.
/// It helps catching buffer overflows.
fence_memory = 0xFD
};
/// The type of the handler called when a memory leak is detected.
/// Leak checking can be controlled via the option \ref WPI_MEMORY_DEBUG_LEAK_CHECK
/// and only affects calls through the \ref allocator_traits, not direct calls.
/// The handler gets the \ref allocator_info and the amount of memory leaked.
/// This can also be negative, meaning that more memory has been freed than allocated.
/// \requiredbe A leak handler shall log the leak, abort the program, do nothing or anything else that seems appropriate.
/// It must not throw any exceptions since it is called in the cleanup process.
/// \defaultbe On a hosted implementation it logs the leak to \c stderr and returns, continuing execution.
/// On a freestanding implementation it does nothing.
/// \ingroup core
using leak_handler = void (*)(const allocator_info& info, std::ptrdiff_t amount);
/// Exchanges the \ref leak_handler.
/// \effects Sets \c h as the new \ref leak_handler in an atomic operation.
/// A \c nullptr sets the default \ref leak_handler.
/// \returns The previous \ref leak_handler. This is never \c nullptr.
/// \ingroup core
leak_handler set_leak_handler(leak_handler h);
/// Returns the \ref leak_handler.
/// \returns The current \ref leak_handler. This is never \c nullptr.
/// \ingroup core
leak_handler get_leak_handler();
/// The type of the handler called when an invalid pointer is passed to a deallocation function.
/// Pointer checking can be controlled via the options \ref WPI_MEMORY_DEBUG_POINTER_CHECK and \ref WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK.
/// The handler gets the \ref allocator_info and the invalid pointer.
/// \requiredbe An invalid pointer handler shall terminate the program.
/// It must not throw any exceptions since it might be called in the cleanup process.
/// \defaultbe On a hosted implementation it logs the information to \c stderr and calls \c std::abort().
/// On a freestanding implementation it only calls \c std::abort().
/// \ingroup core
using invalid_pointer_handler = void (*)(const allocator_info& info, const void* ptr);
/// Exchanges the \ref invalid_pointer_handler.
/// \effects Sets \c h as the new \ref invalid_pointer_handler in an atomic operation.
/// A \c nullptr sets the default \ref invalid_pointer_handler.
/// \returns The previous \ref invalid_pointer_handler. This is never \c nullptr.
/// \ingroup core
invalid_pointer_handler set_invalid_pointer_handler(invalid_pointer_handler h);
/// Returns the \ref invalid_pointer_handler.
/// \returns The current \ref invalid_pointer_handler. This is never \c nullptr.
/// \ingroup core
invalid_pointer_handler get_invalid_pointer_handler();
/// The type of the handler called when a buffer under/overflow is detected.
/// If \ref WPI_MEMORY_DEBUG_FILL is \c true and \ref WPI_MEMORY_DEBUG_FENCE has a non-zero value
/// the allocator classes check if a write into the fence has occured upon deallocation.
/// The handler gets the memory block belonging to the corrupted fence, its size and the exact address.
/// \requiredbe A buffer overflow handler shall terminate the program.
/// It must not throw any exceptions since it me be called in the cleanup process.
/// \defaultbe On a hosted implementation it logs the information to \c stderr and calls \c std::abort().
/// On a freestanding implementation it only calls \c std::abort().
/// \ingroup core
using buffer_overflow_handler = void (*)(const void* memory, std::size_t size,
const void* write_ptr);
/// Exchanges the \ref buffer_overflow_handler.
/// \effects Sets \c h as the new \ref buffer_overflow_handler in an atomic operation.
/// A \c nullptr sets the default \ref buffer_overflow_handler.
/// \returns The previous \ref buffer_overflow_handler. This is never \c nullptr.
/// \ingroup core
buffer_overflow_handler set_buffer_overflow_handler(buffer_overflow_handler h);
/// Returns the \ref buffer_overflow_handler.
/// \returns The current \ref buffer_overflow_handler. This is never \c nullptr.
/// \ingroup core
buffer_overflow_handler get_buffer_overflow_handler();
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DEBUGGING_HPP_INCLUDED

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DEFAULT_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_DEFAULT_ALLOCATOR_HPP_INCLUDED
/// \file
/// The typedef \ref wpi::memory::default_allocator.
#include "config.hpp"
#include "heap_allocator.hpp"
#include "new_allocator.hpp"
#include "static_allocator.hpp"
#include "virtual_memory.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include "malloc_allocator.hpp"
#endif
namespace wpi
{
namespace memory
{
/// The default \concept{concept_rawallocator,RawAllocator} that will be used as \concept{concept_blockallocator,BlockAllocator} in memory arenas.
/// Arena allocators like \ref memory_stack or \ref memory_pool allocate memory by subdividing a huge block.
/// They get a \concept{concept_blockallocator,BlockAllocator} that will be used for their internal allocation,
/// this type is the default value.
/// \requiredbe Its type can be changed via the CMake option \c WPI_MEMORY_DEFAULT_ALLCOATOR,
/// but it must be one of the following: \ref heap_allocator, \ref new_allocator, \ref malloc_allocator, \ref static_allocator, \ref virtual_memory_allocator.
/// \defaultbe The default is \ref heap_allocator.
/// \ingroup allocator
using default_allocator = WPI_IMPL_DEFINED(WPI_MEMORY_IMPL_DEFAULT_ALLOCATOR);
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DEFAULT_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,308 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DELETER_HPP_INCLUDED
#define WPI_MEMORY_DELETER_HPP_INCLUDED
/// \file
/// \c Deleter classes using a \concept{concept_rawallocator,RawAllocator}.
#include <type_traits>
#include "allocator_storage.hpp"
#include "config.hpp"
#include "threading.hpp"
namespace wpi
{
namespace memory
{
/// A deleter class that deallocates the memory through a specified \concept{concept_rawallocator,RawAllocator}.
///
/// It deallocates memory for a specified type but does not call its destructors.
/// \ingroup adapter
template <typename Type, class RawAllocator>
class allocator_deallocator : WPI_EBO(allocator_reference<RawAllocator>)
{
static_assert(!std::is_abstract<Type>::value,
"use allocator_polymorphic_deallocator for storing base classes");
public:
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
using value_type = Type;
/// \effects Creates it without any associated allocator.
/// The deallocator must not be used if that is the case.
/// \notes This functions is useful if you have want to create an empty smart pointer without giving it an allocator.
allocator_deallocator() noexcept = default;
/// \effects Creates it by passing it an \ref allocator_reference.
/// It will store the reference to it and uses the referenced allocator object for the deallocation.
allocator_deallocator(allocator_reference<RawAllocator> alloc) noexcept
: allocator_reference<RawAllocator>(alloc)
{
}
/// \effects Deallocates the memory given to it.
/// Calls \c deallocate_node(pointer, sizeof(value_type), alignof(value_type)) on the referenced allocator object.
/// \requires The deallocator must not have been created by the default constructor.
void operator()(value_type* pointer) noexcept
{
this->deallocate_node(pointer, sizeof(value_type), alignof(value_type));
}
/// \returns The reference to the allocator.
/// It has the same type as the call to \ref allocator_reference::get_allocator().
/// \requires The deallocator must not be created by the default constructor.
auto get_allocator() const noexcept
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
{
return this->allocator_reference<allocator_type>::get_allocator();
}
};
/// Specialization of \ref allocator_deallocator for array types.
/// Otherwise the same behavior.
/// \ingroup adapter
template <typename Type, class RawAllocator>
class allocator_deallocator<Type[], RawAllocator>
: WPI_EBO(allocator_reference<RawAllocator>)
{
static_assert(!std::is_abstract<Type>::value, "must not create polymorphic arrays");
public:
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
using value_type = Type;
/// \effects Creates it without any associated allocator.
/// The deallocator must not be used if that is the case.
/// \notes This functions is useful if you have want to create an empty smart pointer without giving it an allocator.
allocator_deallocator() noexcept : size_(0u) {}
/// \effects Creates it by passing it an \ref allocator_reference and the size of the array that will be deallocated.
/// It will store the reference to the allocator and uses the referenced allocator object for the deallocation.
allocator_deallocator(allocator_reference<RawAllocator> alloc,
std::size_t size) noexcept
: allocator_reference<RawAllocator>(alloc), size_(size)
{
}
/// \effects Deallocates the memory given to it.
/// Calls \c deallocate_array(pointer, size, sizeof(value_type), alignof(value_type))
/// on the referenced allocator object with the size given in the constructor.
/// \requires The deallocator must not have been created by the default constructor.
void operator()(value_type* pointer) noexcept
{
this->deallocate_array(pointer, size_, sizeof(value_type), alignof(value_type));
}
/// \returns The reference to the allocator.
/// It has the same type as the call to \ref allocator_reference::get_allocator().
/// \requires The deallocator must not have been created by the default constructor.
auto get_allocator() const noexcept
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
{
return this->allocator_reference<allocator_type>::get_allocator();
}
/// \returns The size of the array that will be deallocated.
/// This is the same value as passed in the constructor, or `0` if it was created by the default constructor.
std::size_t array_size() const noexcept
{
return size_;
}
private:
std::size_t size_;
};
/// A deleter class that deallocates the memory of a derived type through a specified \concept{concept_rawallocator,RawAllocator}.
///
/// It can only be created from a \ref allocator_deallocator and thus must only be used for smart pointers initialized by derived-to-base conversion of the pointer.
/// \ingroup adapter
template <typename BaseType, class RawAllocator>
class allocator_polymorphic_deallocator : WPI_EBO(allocator_reference<RawAllocator>)
{
public:
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
using value_type = BaseType;
/// \effects Creates it from a deallocator for a derived type.
/// It will deallocate the memory as if done by the derived type.
template <typename T, WPI_REQUIRES((std::is_base_of<BaseType, T>::value))>
allocator_polymorphic_deallocator(allocator_deallocator<T, RawAllocator> dealloc)
: allocator_reference<RawAllocator>(dealloc.get_allocator()),
derived_size_(sizeof(T)),
derived_alignment_(alignof(T))
{
}
/// \effects Deallocates the memory given to it.
/// Calls \c deallocate_node(pointer, size, alignment) on the referenced allocator object,
/// where \c size and \c alignment are the values of the type it was created with.
void operator()(value_type* pointer) noexcept
{
this->deallocate_node(pointer, derived_size_, derived_alignment_);
}
/// \returns The reference to the allocator.
/// It has the same type as the call to \ref allocator_reference::get_allocator().
auto get_allocator() const noexcept
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
{
return this->allocator_reference<allocator_type>::get_allocator();
}
private:
std::size_t derived_size_, derived_alignment_;
};
/// Similar to \ref allocator_deallocator but calls the destructors of the object.
/// Otherwise behaves the same.
/// \ingroup adapter
template <typename Type, class RawAllocator>
class allocator_deleter : WPI_EBO(allocator_reference<RawAllocator>)
{
static_assert(!std::is_abstract<Type>::value,
"use allocator_polymorphic_deleter for storing base classes");
public:
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
using value_type = Type;
/// \effects Creates it without any associated allocator.
/// The deleter must not be used if that is the case.
/// \notes This functions is useful if you have want to create an empty smart pointer without giving it an allocator.
allocator_deleter() noexcept = default;
/// \effects Creates it by passing it an \ref allocator_reference.
/// It will store the reference to it and uses the referenced allocator object for the deallocation.
allocator_deleter(allocator_reference<RawAllocator> alloc) noexcept
: allocator_reference<RawAllocator>(alloc)
{
}
/// \effects Calls the destructor and deallocates the memory given to it.
/// Calls \c deallocate_node(pointer, sizeof(value_type), alignof(value_type))
/// on the referenced allocator object for the deallocation.
/// \requires The deleter must not have been created by the default constructor.
void operator()(value_type* pointer) noexcept
{
pointer->~value_type();
this->deallocate_node(pointer, sizeof(value_type), alignof(value_type));
}
/// \returns The reference to the allocator.
/// It has the same type as the call to \ref allocator_reference::get_allocator().
auto get_allocator() const noexcept
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
{
return this->allocator_reference<allocator_type>::get_allocator();
}
};
/// Specialization of \ref allocator_deleter for array types.
/// Otherwise the same behavior.
/// \ingroup adapter
template <typename Type, class RawAllocator>
class allocator_deleter<Type[], RawAllocator>
: WPI_EBO(allocator_reference<RawAllocator>)
{
static_assert(!std::is_abstract<Type>::value, "must not create polymorphic arrays");
public:
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
using value_type = Type;
/// \effects Creates it without any associated allocator.
/// The deleter must not be used if that is the case.
/// \notes This functions is useful if you have want to create an empty smart pointer without giving it an allocator.
allocator_deleter() noexcept : size_(0u) {}
/// \effects Creates it by passing it an \ref allocator_reference and the size of the array that will be deallocated.
/// It will store the reference to the allocator and uses the referenced allocator object for the deallocation.
allocator_deleter(allocator_reference<RawAllocator> alloc, std::size_t size) noexcept
: allocator_reference<RawAllocator>(alloc), size_(size)
{
}
/// \effects Calls the destructors and deallocates the memory given to it.
/// Calls \c deallocate_array(pointer, size, sizeof(value_type), alignof(value_type))
/// on the referenced allocator object with the size given in the constructor for the deallocation.
/// \requires The deleter must not have been created by the default constructor.
void operator()(value_type* pointer) noexcept
{
for (auto cur = pointer; cur != pointer + size_; ++cur)
cur->~value_type();
this->deallocate_array(pointer, size_, sizeof(value_type), alignof(value_type));
}
/// \returns The reference to the allocator.
/// It has the same type as the call to \ref allocator_reference::get_allocator().
/// \requires The deleter must not be created by the default constructor.
auto get_allocator() const noexcept
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
{
return this->allocator_reference<allocator_type>::get_allocator();
}
/// \returns The size of the array that will be deallocated.
/// This is the same value as passed in the constructor, or `0` if it was created by the default constructor.
std::size_t array_size() const noexcept
{
return size_;
}
private:
std::size_t size_;
};
/// Similar to \ref allocator_polymorphic_deallocator but calls the destructors of the object.
/// Otherwise behaves the same.
/// \note It has a relatively high space overhead, so only use it if you have to.
/// \ingroup adapter
template <typename BaseType, class RawAllocator>
class allocator_polymorphic_deleter : WPI_EBO(allocator_reference<RawAllocator>)
{
public:
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
using value_type = BaseType;
/// \effects Creates it from a deleter for a derived type.
/// It will deallocate the memory as if done by the derived type.
template <typename T, WPI_REQUIRES((std::is_base_of<BaseType, T>::value))>
allocator_polymorphic_deleter(allocator_deleter<T, RawAllocator> deleter)
: allocator_reference<RawAllocator>(deleter.get_allocator()),
derived_size_(sizeof(T)),
derived_alignment_(alignof(T))
{
WPI_MEMORY_ASSERT(std::size_t(derived_size_) == sizeof(T)
&& std::size_t(derived_alignment_) == alignof(T));
}
/// \effects Deallocates the memory given to it.
/// Calls \c deallocate_node(pointer, size, alignment) on the referenced allocator object,
/// where \c size and \c alignment are the values of the type it was created with.
void operator()(value_type* pointer) noexcept
{
pointer->~value_type();
this->deallocate_node(pointer, derived_size_, derived_alignment_);
}
/// \returns The reference to the allocator.
/// It has the same type as the call to \ref allocator_reference::get_allocator().
auto get_allocator() const noexcept
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
{
return this->allocator_reference<allocator_type>::get_allocator();
}
private:
unsigned short derived_size_,
derived_alignment_; // use unsigned short here to save space
};
} // namespace memory
} // namespace wpi
#endif //WPI_MEMORY_DELETER_HPP_INCLUDED

View File

@@ -0,0 +1,52 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_ALIGN_HPP_INCLUDED
#define WPI_MEMORY_DETAIL_ALIGN_HPP_INCLUDED
#include <cstdint>
#include "../config.hpp"
#include "assert.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
// whether or not an alignment is valid, i.e. a power of two not zero
constexpr bool is_valid_alignment(std::size_t alignment) noexcept
{
return alignment && (alignment & (alignment - 1)) == 0u;
}
// returns the offset needed to align ptr for given alignment
// alignment must be valid
inline std::size_t align_offset(std::uintptr_t address, std::size_t alignment) noexcept
{
WPI_MEMORY_ASSERT(is_valid_alignment(alignment));
auto misaligned = address & (alignment - 1);
return misaligned != 0 ? (alignment - misaligned) : 0;
}
inline std::size_t align_offset(void* ptr, std::size_t alignment) noexcept
{
return align_offset(reinterpret_cast<std::uintptr_t>(ptr), alignment);
}
// whether or not the pointer is aligned for given alignment
// alignment must be valid
bool is_aligned(void* ptr, std::size_t alignment) noexcept;
// maximum alignment value
constexpr std::size_t max_alignment = alignof(std::max_align_t);
static_assert(is_valid_alignment(max_alignment), "ehm..?");
// returns the minimum alignment required for a node of given size
std::size_t alignment_for(std::size_t size) noexcept;
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DETAIL_ALIGN_HPP_INCLUDED

View File

@@ -0,0 +1,57 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_ASSERT_HPP_INCLUDED
#define WPI_MEMORY_DETAIL_ASSERT_HPP_INCLUDED
#include <cstdlib>
#include "../config.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
// handles a failed assertion
void handle_failed_assert(const char* msg, const char* file, int line,
const char* fnc) noexcept;
void handle_warning(const char* msg, const char* file, int line,
const char* fnc) noexcept;
// note: debug assertion macros don't use fully qualified name
// because they should only be used in this library, where the whole namespace is available
// can be override via command line definitions
#if WPI_MEMORY_DEBUG_ASSERT && !defined(WPI_MEMORY_ASSERT)
#define WPI_MEMORY_ASSERT(Expr) \
static_cast<void>((Expr) \
|| (detail::handle_failed_assert("Assertion \"" #Expr "\" failed", __FILE__, \
__LINE__, __func__), \
true))
#define WPI_MEMORY_ASSERT_MSG(Expr, Msg) \
static_cast<void>((Expr) \
|| (detail::handle_failed_assert("Assertion \"" #Expr "\" failed: " Msg, \
__FILE__, __LINE__, __func__), \
true))
#define WPI_MEMORY_UNREACHABLE(Msg) \
detail::handle_failed_assert("Unreachable code reached: " Msg, __FILE__, __LINE__, __func__)
#define WPI_MEMORY_WARNING(Msg) detail::handle_warning(Msg, __FILE__, __LINE__, __func__)
#elif !defined(WPI_MEMORY_ASSERT)
#define WPI_MEMORY_ASSERT(Expr)
#define WPI_MEMORY_ASSERT_MSG(Expr, Msg)
#define WPI_MEMORY_UNREACHABLE(Msg) std::abort()
#define WPI_MEMORY_WARNING(Msg)
#endif
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DETAIL_ASSERT_HPP_INCLUDED

View File

@@ -0,0 +1,10 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_CONTAINER_NODE_SIZES_HPP_INCLUDED
#define WPI_MEMORY_DETAIL_CONTAINER_NODE_SIZES_HPP_INCLUDED
#include "container_node_sizes_impl.hpp"
#endif //WPI_MEMORY_DETAIL_CONTAINER_NODE_SIZES_HPP_INCLUDED

View File

@@ -0,0 +1,235 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DEBUG_HELPERS_HPP_INCLUDED
#define WPI_MEMORY_DEBUG_HELPERS_HPP_INCLUDED
#include <atomic>
#include <type_traits>
#include "../config.hpp"
namespace wpi
{
namespace memory
{
enum class debug_magic : unsigned char;
struct allocator_info;
namespace detail
{
using debug_fill_enabled = std::integral_constant<bool, WPI_MEMORY_DEBUG_FILL>;
constexpr std::size_t debug_fence_size =
WPI_MEMORY_DEBUG_FILL ? WPI_MEMORY_DEBUG_FENCE : 0u;
#if WPI_MEMORY_DEBUG_FILL
// fills size bytes of memory with debug_magic
void debug_fill(void* memory, std::size_t size, debug_magic m) noexcept;
// returns nullptr if memory is filled with debug_magic
// else returns pointer to mismatched byte
void* debug_is_filled(void* memory, std::size_t size, debug_magic m) noexcept;
// fills fence, new and fence
// returns after fence
void* debug_fill_new(void* memory, std::size_t node_size,
std::size_t fence_size = debug_fence_size) noexcept;
// fills free memory and returns memory starting at fence
void* debug_fill_free(void* memory, std::size_t node_size,
std::size_t fence_size = debug_fence_size) noexcept;
// fills internal memory
void debug_fill_internal(void* memory, std::size_t size, bool free) noexcept;
#else
inline void debug_fill(void*, std::size_t, debug_magic) noexcept {}
inline void* debug_is_filled(void*, std::size_t, debug_magic) noexcept
{
return nullptr;
}
inline void* debug_fill_new(void* memory, std::size_t, std::size_t) noexcept
{
return memory;
}
inline void* debug_fill_free(void* memory, std::size_t, std::size_t) noexcept
{
return static_cast<char*>(memory);
}
inline void debug_fill_internal(void*, std::size_t, bool) noexcept {}
#endif
void debug_handle_invalid_ptr(const allocator_info& info, void* ptr);
// validates given ptr by evaluating the Functor
// if the Functor returns false, calls the debug_leak_checker
// note: ptr is just used as the information passed to the invalid ptr handler
template <class Functor>
void debug_check_pointer(Functor condition, const allocator_info& info, void* ptr)
{
#if WPI_MEMORY_DEBUG_POINTER_CHECK
if (!condition())
debug_handle_invalid_ptr(info, ptr);
#else
(void)ptr;
(void)condition;
(void)info;
#endif
}
// validates ptr by using a more expensive double-dealloc check
template <class Functor>
void debug_check_double_dealloc(Functor condition, const allocator_info& info,
void* ptr)
{
#if WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK
debug_check_pointer(condition, info, ptr);
#else
(void)condition;
(void)info;
(void)ptr;
#endif
}
void debug_handle_memory_leak(const allocator_info& info, std::ptrdiff_t amount);
// does no leak checking, null overhead
template <class Handler>
class no_leak_checker
{
public:
no_leak_checker() noexcept {}
no_leak_checker(no_leak_checker&&) noexcept {}
~no_leak_checker() noexcept {}
no_leak_checker& operator=(no_leak_checker&&) noexcept
{
return *this;
}
void on_allocate(std::size_t) noexcept {}
void on_deallocate(std::size_t) noexcept {}
};
// does leak checking per-object
// leak is detected upon destructor
template <class Handler>
class object_leak_checker : Handler
{
public:
object_leak_checker() noexcept : allocated_(0) {}
object_leak_checker(object_leak_checker&& other) noexcept
: allocated_(other.allocated_)
{
other.allocated_ = 0;
}
~object_leak_checker() noexcept
{
if (allocated_ != 0)
this->operator()(allocated_);
}
object_leak_checker& operator=(object_leak_checker&& other) noexcept
{
allocated_ = other.allocated_;
other.allocated_ = 0;
return *this;
}
void on_allocate(std::size_t size) noexcept
{
allocated_ += std::ptrdiff_t(size);
}
void on_deallocate(std::size_t size) noexcept
{
allocated_ -= std::ptrdiff_t(size);
}
private:
std::ptrdiff_t allocated_;
};
// does leak checking on a global basis
// call macro WPI_MEMORY_GLOBAL_LEAK_CHECKER(handler, var_name) in the header
// when last counter gets destroyed, leak is detected
template <class Handler>
class global_leak_checker_impl
{
public:
struct counter : Handler
{
counter()
{
++no_counter_objects_;
}
~counter()
{
--no_counter_objects_;
if (no_counter_objects_ == 0u && allocated_ != 0u)
this->operator()(allocated_);
}
};
global_leak_checker_impl() noexcept {}
global_leak_checker_impl(global_leak_checker_impl&&) noexcept {}
~global_leak_checker_impl() noexcept {}
global_leak_checker_impl& operator=(global_leak_checker_impl&&) noexcept
{
return *this;
}
void on_allocate(std::size_t size) noexcept
{
allocated_ += std::ptrdiff_t(size);
}
void on_deallocate(std::size_t size) noexcept
{
allocated_ -= std::ptrdiff_t(size);
}
private:
static std::atomic<std::size_t> no_counter_objects_;
static std::atomic<std::ptrdiff_t> allocated_;
};
template <class Handler>
std::atomic<std::size_t> global_leak_checker_impl<Handler>::no_counter_objects_(0u);
template <class Handler>
std::atomic<std::ptrdiff_t> global_leak_checker_impl<Handler>::allocated_(0);
#if WPI_MEMORY_DEBUG_LEAK_CHECK
template <class Handler>
using global_leak_checker = global_leak_checker_impl<Handler>;
#define WPI_MEMORY_GLOBAL_LEAK_CHECKER(handler, var_name) \
static wpi::memory::detail::global_leak_checker<handler>::counter var_name;
#else
template <class Handler>
using global_leak_checker = no_leak_checker<int>; // only one instantiation
#define WPI_MEMORY_GLOBAL_LEAK_CHECKER(handler, var_name)
#endif
#if WPI_MEMORY_DEBUG_LEAK_CHECK
template <class Handler>
using default_leak_checker = object_leak_checker<Handler>;
#else
template <class Handler>
using default_leak_checker = no_leak_checker<Handler>;
#endif
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DEBUG_HELPERS_HPP_INCLUDED

View File

@@ -0,0 +1,42 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_EBO_STORAGE_HPP_INCLUDED
#define WPI_MEMORY_DETAIL_EBO_STORAGE_HPP_INCLUDED
#include "utility.hpp"
#include "../config.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
template <int Tag, typename T>
class ebo_storage : T
{
protected:
ebo_storage(const T& t) : T(t) {}
ebo_storage(T&& t) noexcept(std::is_nothrow_move_constructible<T>::value)
: T(detail::move(t))
{
}
T& get() noexcept
{
return *this;
}
const T& get() const noexcept
{
return *this;
}
};
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DETAIL_EBO_STORAGE_HPP_INCLUDED

View File

@@ -0,0 +1,228 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAILL_FREE_LIST_HPP_INCLUDED
#define WPI_MEMORY_DETAILL_FREE_LIST_HPP_INCLUDED
#include <cstddef>
#include <cstdint>
#include "align.hpp"
#include "utility.hpp"
#include "../config.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
// stores free blocks for a memory pool
// memory blocks are fragmented and stored in a list
// debug: fills memory and uses a bigger node_size for fence memory
class free_memory_list
{
public:
// minimum element size
static constexpr auto min_element_size = sizeof(char*);
// alignment
static constexpr auto min_element_alignment = alignof(char*);
// minimal size of the block that needs to be inserted
static constexpr std::size_t min_block_size(std::size_t node_size,
std::size_t number_of_nodes)
{
return (node_size < min_element_size ? min_element_size : node_size)
* number_of_nodes;
}
//=== constructor ===//
free_memory_list(std::size_t node_size) noexcept;
// calls other constructor plus insert
free_memory_list(std::size_t node_size, void* mem, std::size_t size) noexcept;
free_memory_list(free_memory_list&& other) noexcept;
~free_memory_list() noexcept = default;
free_memory_list& operator=(free_memory_list&& other) noexcept;
friend void swap(free_memory_list& a, free_memory_list& b) noexcept;
//=== insert/allocation/deallocation ===//
// inserts a new memory block, by splitting it up and setting the links
// does not own memory!
// mem must be aligned for alignment()
// pre: size != 0
void insert(void* mem, std::size_t size) noexcept;
// returns the usable size
// i.e. how many memory will be actually inserted and usable on a call to insert()
std::size_t usable_size(std::size_t size) const noexcept
{
// Round down to next multiple of node size.
return (size / node_size_) * node_size_;
}
// returns a single block from the list
// pre: !empty()
void* allocate() noexcept;
// returns a memory block big enough for n bytes
// might fail even if capacity is sufficient
void* allocate(std::size_t n) noexcept;
// deallocates a single block
void deallocate(void* ptr) noexcept;
// deallocates multiple blocks with n bytes total
void deallocate(void* ptr, std::size_t n) noexcept;
//=== getter ===//
std::size_t node_size() const noexcept
{
return node_size_;
}
// alignment of all nodes
std::size_t alignment() const noexcept;
// number of nodes remaining
std::size_t capacity() const noexcept
{
return capacity_;
}
bool empty() const noexcept
{
return first_ == nullptr;
}
private:
void insert_impl(void* mem, std::size_t size) noexcept;
char* first_;
std::size_t node_size_, capacity_;
};
void swap(free_memory_list& a, free_memory_list& b) noexcept;
// same as above but keeps the nodes ordered
// this allows array allocations, that is, consecutive nodes
// debug: fills memory and uses a bigger node_size for fence memory
class ordered_free_memory_list
{
public:
// minimum element size
static constexpr auto min_element_size = sizeof(char*);
// alignment
static constexpr auto min_element_alignment = alignof(char*);
// minimal size of the block that needs to be inserted
static constexpr std::size_t min_block_size(std::size_t node_size,
std::size_t number_of_nodes)
{
return (node_size < min_element_size ? min_element_size : node_size)
* number_of_nodes;
}
//=== constructor ===//
ordered_free_memory_list(std::size_t node_size) noexcept;
ordered_free_memory_list(std::size_t node_size, void* mem,
std::size_t size) noexcept
: ordered_free_memory_list(node_size)
{
insert(mem, size);
}
ordered_free_memory_list(ordered_free_memory_list&& other) noexcept;
~ordered_free_memory_list() noexcept = default;
ordered_free_memory_list& operator=(ordered_free_memory_list&& other) noexcept
{
ordered_free_memory_list tmp(detail::move(other));
swap(*this, tmp);
return *this;
}
friend void swap(ordered_free_memory_list& a, ordered_free_memory_list& b) noexcept;
//=== insert/allocation/deallocation ===//
// inserts a new memory block, by splitting it up and setting the links
// does not own memory!
// mem must be aligned for alignment()
// pre: size != 0
void insert(void* mem, std::size_t size) noexcept;
// returns the usable size
// i.e. how many memory will be actually inserted and usable on a call to insert()
std::size_t usable_size(std::size_t size) const noexcept
{
// Round down to next multiple of node size.
return (size / node_size_) * node_size_;
}
// returns a single block from the list
// pre: !empty()
void* allocate() noexcept;
// returns a memory block big enough for n bytes (!, not nodes)
// might fail even if capacity is sufficient
void* allocate(std::size_t n) noexcept;
// deallocates a single block
void deallocate(void* ptr) noexcept;
// deallocates multiple blocks with n bytes total
void deallocate(void* ptr, std::size_t n) noexcept;
//=== getter ===//
std::size_t node_size() const noexcept
{
return node_size_;
}
// alignment of all nodes
std::size_t alignment() const noexcept;
// number of nodes remaining
std::size_t capacity() const noexcept
{
return capacity_;
}
bool empty() const noexcept
{
return capacity_ == 0u;
}
private:
// returns previous pointer
char* insert_impl(void* mem, std::size_t size) noexcept;
char* begin_node() noexcept;
char* end_node() noexcept;
std::uintptr_t begin_proxy_, end_proxy_;
std::size_t node_size_, capacity_;
char * last_dealloc_, *last_dealloc_prev_;
};
void swap(ordered_free_memory_list& a, ordered_free_memory_list& b) noexcept;
#if WPI_MEMORY_DEBUG_DOUBLE_DEALLOC_CHECK
// use ordered version to allow pointer check
using node_free_memory_list = ordered_free_memory_list;
using array_free_memory_list = ordered_free_memory_list;
#else
using node_free_memory_list = free_memory_list;
using array_free_memory_list = ordered_free_memory_list;
#endif
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DETAILL_FREE_LIST_HPP_INCLUDED

View File

@@ -0,0 +1,126 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_FREE_LIST_ARRAY_HPP
#define WPI_MEMORY_DETAIL_FREE_LIST_ARRAY_HPP
#include "align.hpp"
#include "assert.hpp"
#include "memory_stack.hpp"
#include "../config.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
// an array of free_memory_list types
// indexed via size, AccessPolicy does necessary conversions
// requires trivial destructible FreeList type
template <class FreeList, class AccessPolicy>
class free_list_array
{
// not supported on GCC 4.7
//static_assert(std::is_trivially_destructible<FreeList>::value,
// "free list must be trivially destructible");
public:
// creates sufficient elements to support up to given maximum node size
// all lists are initially empty
// actual number is calculated via policy
// memory is taken from fixed_memory_stack, it must be sufficient
free_list_array(fixed_memory_stack& stack, const char* end,
std::size_t max_node_size) noexcept
: no_elements_(AccessPolicy::index_from_size(max_node_size) - min_size_index + 1)
{
array_ = static_cast<FreeList*>(
stack.allocate(end, no_elements_ * sizeof(FreeList), alignof(FreeList)));
WPI_MEMORY_ASSERT_MSG(array_, "insufficient memory for free lists");
for (std::size_t i = 0u; i != no_elements_; ++i)
{
auto node_size = AccessPolicy::size_from_index(i + min_size_index);
::new (static_cast<void*>(array_ + i)) FreeList(node_size);
}
}
// move constructor, does not actually move the elements, just the pointer
free_list_array(free_list_array&& other) noexcept
: array_(other.array_), no_elements_(other.no_elements_)
{
other.array_ = nullptr;
other.no_elements_ = 0u;
}
// destructor, does nothing, list must be trivially destructible!
~free_list_array() noexcept = default;
free_list_array& operator=(free_list_array&& other) noexcept
{
array_ = other.array_;
no_elements_ = other.no_elements_;
other.array_ = nullptr;
other.no_elements_ = 0u;
return *this;
}
// access free list for given size
FreeList& get(std::size_t node_size) const noexcept
{
auto i = AccessPolicy::index_from_size(node_size);
if (i < min_size_index)
i = min_size_index;
return array_[i - min_size_index];
}
// number of free lists
std::size_t size() const noexcept
{
return no_elements_;
}
// maximum supported node size
std::size_t max_node_size() const noexcept
{
return AccessPolicy::size_from_index(no_elements_ + min_size_index - 1);
}
private:
static const std::size_t min_size_index;
FreeList* array_;
std::size_t no_elements_;
};
template <class FL, class AP>
const std::size_t free_list_array<FL, AP>::min_size_index =
AP::index_from_size(FL::min_element_size);
// AccessPolicy that maps size to indices 1:1
// creates a free list for each size!
struct identity_access_policy
{
static std::size_t index_from_size(std::size_t size) noexcept
{
return size;
}
static std::size_t size_from_index(std::size_t index) noexcept
{
return index;
}
};
// AccessPolicy that maps sizes to the integral log2
// this creates more nodes and never wastes more than half the size
struct log2_access_policy
{
static std::size_t index_from_size(std::size_t size) noexcept;
static std::size_t size_from_index(std::size_t index) noexcept;
};
} // namespace detail
} // namespace memory
} // namespace wpi
#endif //WPI_MEMORY_DETAIL_FREE_LIST_ARRAY_HPP

View File

@@ -0,0 +1,69 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_ILOG2_HPP_INCLUDED
#define WPI_MEMORY_DETAIL_ILOG2_HPP_INCLUDED
#include <climits>
#include <cstdint>
#include "../config.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
// undefined for 0
template <typename UInt>
constexpr bool is_power_of_two(UInt x)
{
return (x & (x - 1)) == 0;
}
inline std::size_t ilog2_base(std::uint64_t x)
{
#if defined(__GNUC__)
unsigned long long value = x;
return sizeof(value) * CHAR_BIT - static_cast<unsigned>(__builtin_clzll(value));
#else
// Adapted from https://stackoverflow.com/a/40943402
std::size_t clz = 64;
std::size_t c = 32;
do
{
auto tmp = x >> c;
if (tmp != 0)
{
clz -= c;
x = tmp;
}
c = c >> 1;
} while (c != 0);
clz -= x ? 1 : 0;
return 64 - clz;
#endif
}
// ilog2() implementation, cuts part after the comma
// e.g. 1 -> 0, 2 -> 1, 3 -> 1, 4 -> 2, 5 -> 2
inline std::size_t ilog2(std::uint64_t x)
{
return ilog2_base(x) - 1;
}
// ceiling ilog2() implementation, adds one if part after comma
// e.g. 1 -> 0, 2 -> 1, 3 -> 2, 4 -> 2, 5 -> 3
inline std::size_t ilog2_ceil(std::uint64_t x)
{
// only subtract one if power of two
return ilog2_base(x) - std::size_t(is_power_of_two(x));
}
} // namespace detail
} // namespace memory
} // namespace wpi
#endif

View File

@@ -0,0 +1,86 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_LOWLEVEL_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_DETAIL_LOWLEVEL_ALLOCATOR_HPP_INCLUDED
#include <type_traits>
#include "../config.hpp"
#include "../error.hpp"
#include "align.hpp"
#include "debug_helpers.hpp"
#include "assert.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
template <class Functor>
struct lowlevel_allocator_leak_handler
{
void operator()(std::ptrdiff_t amount)
{
debug_handle_memory_leak(Functor::info(), amount);
}
};
// Functor controls low-level allocation:
// static allocator_info info()
// static void* allocate(std::size_t size, std::size_t alignment);
// static void deallocate(void *memory, std::size_t size, std::size_t alignment);
// static std::size_t max_node_size();
template <class Functor>
class lowlevel_allocator : global_leak_checker<lowlevel_allocator_leak_handler<Functor>>
{
public:
using is_stateful = std::false_type;
lowlevel_allocator() noexcept {}
lowlevel_allocator(lowlevel_allocator&&) noexcept {}
~lowlevel_allocator() noexcept {}
lowlevel_allocator& operator=(lowlevel_allocator&&) noexcept
{
return *this;
}
void* allocate_node(std::size_t size, std::size_t alignment)
{
auto actual_size = size + (debug_fence_size ? 2 * max_alignment : 0u);
auto memory = Functor::allocate(actual_size, alignment);
if (!memory)
WPI_THROW(out_of_memory(Functor::info(), actual_size));
this->on_allocate(actual_size);
return debug_fill_new(memory, size, max_alignment);
}
void deallocate_node(void* node, std::size_t size, std::size_t alignment) noexcept
{
auto actual_size = size + (debug_fence_size ? 2 * max_alignment : 0u);
auto memory = debug_fill_free(node, size, max_alignment);
Functor::deallocate(memory, actual_size, alignment);
this->on_deallocate(actual_size);
}
std::size_t max_node_size() const noexcept
{
return Functor::max_node_size();
}
};
#define WPI_MEMORY_LL_ALLOCATOR_LEAK_CHECKER(functor, var_name) \
WPI_MEMORY_GLOBAL_LEAK_CHECKER(lowlevel_allocator_leak_handler<functor>, var_name)
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DETAIL_LOWLEVEL_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,120 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_MEMORY_STACK_HPP_INCLUDED
#define WPI_MEMORY_DETAIL_MEMORY_STACK_HPP_INCLUDED
#include <cstddef>
#include "../config.hpp"
#include "align.hpp"
#include "debug_helpers.hpp"
#include "../debugging.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
// simple memory stack implementation that does not support growing
class fixed_memory_stack
{
public:
fixed_memory_stack() noexcept : fixed_memory_stack(nullptr) {}
// gives it the current pointer, the end pointer must be maintained seperataly
explicit fixed_memory_stack(void* memory) noexcept
: cur_(static_cast<char*>(memory))
{
}
fixed_memory_stack(fixed_memory_stack&& other) noexcept : cur_(other.cur_)
{
other.cur_ = nullptr;
}
~fixed_memory_stack() noexcept = default;
fixed_memory_stack& operator=(fixed_memory_stack&& other) noexcept
{
cur_ = other.cur_;
other.cur_ = nullptr;
return *this;
}
// bumps the top pointer without filling it
void bump(std::size_t offset) noexcept
{
cur_ += offset;
}
// bumps the top pointer by offset and fills
void bump(std::size_t offset, debug_magic m) noexcept
{
detail::debug_fill(cur_, offset, m);
bump(offset);
}
// same as bump(offset, m) but returns old value
void* bump_return(std::size_t offset,
debug_magic m = debug_magic::new_memory) noexcept
{
auto memory = cur_;
detail::debug_fill(memory, offset, m);
cur_ += offset;
return memory;
}
// allocates memory by advancing the stack, returns nullptr if insufficient
// debug: mark memory as new_memory, put fence in front and back
void* allocate(const char* end, std::size_t size, std::size_t alignment,
std::size_t fence_size = debug_fence_size) noexcept
{
if (cur_ == nullptr)
return nullptr;
auto remaining = std::size_t(end - cur_);
auto offset = align_offset(cur_ + fence_size, alignment);
if (fence_size + offset + size + fence_size > remaining)
return nullptr;
return allocate_unchecked(size, offset, fence_size);
}
// same as allocate() but does not check the size
// note: pass it the align OFFSET, not the alignment
void* allocate_unchecked(std::size_t size, std::size_t align_offset,
std::size_t fence_size = debug_fence_size) noexcept
{
bump(fence_size, debug_magic::fence_memory);
bump(align_offset, debug_magic::alignment_memory);
auto mem = bump_return(size);
bump(fence_size, debug_magic::fence_memory);
return mem;
}
// unwindws the stack to a certain older position
// debug: marks memory from new top to old top as freed
// doesn't check for invalid pointer
void unwind(char* top) noexcept
{
debug_fill(top, std::size_t(cur_ - top), debug_magic::freed_memory);
cur_ = top;
}
// returns the current top
char* top() const noexcept
{
return cur_;
}
private:
char* cur_;
};
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DETAIL_MEMORY_STACK_HPP_INCLUDED

View File

@@ -0,0 +1,164 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_SMALL_FREE_LIST_HPP_INCLUDED
#define WPI_MEMORY_DETAIL_SMALL_FREE_LIST_HPP_INCLUDED
#include <cstddef>
#include <climits>
#include "../config.hpp"
#include "utility.hpp"
#include "align.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
struct chunk_base
{
chunk_base* prev = this;
chunk_base* next = this;
unsigned char first_free = 0; // first free node for the linked list
unsigned char capacity = 0; // total number of free nodes available
unsigned char no_nodes = 0; // total number of nodes in memory
chunk_base() noexcept = default;
chunk_base(unsigned char no) noexcept : capacity(no), no_nodes(no) {}
};
constexpr std::size_t chunk_memory_offset =
sizeof(chunk_base) % detail::max_alignment == 0 ?
sizeof(chunk_base) :
(sizeof(chunk_base) / detail::max_alignment + 1) * detail::max_alignment;
constexpr std::size_t chunk_max_nodes = UCHAR_MAX;
struct chunk;
// the same as free_memory_list but optimized for small node sizes
// it is slower and does not support arrays
// but has very small overhead
// debug: allocate() and deallocate() mark memory as new and freed, respectively
// node_size is increased via two times fence size and fence is put in front and after
class small_free_memory_list
{
static constexpr std::size_t chunk_count(std::size_t number_of_nodes)
{
return number_of_nodes / chunk_max_nodes
+ (number_of_nodes % chunk_max_nodes == 0 ? 0 : 1);
}
public:
// minimum element size
static constexpr std::size_t min_element_size = 1;
// alignment
static constexpr std::size_t min_element_alignment = 1;
// minimal size of the block that needs to be inserted
static constexpr std::size_t min_block_size(std::size_t node_size,
std::size_t number_of_nodes)
{
return chunk_count(number_of_nodes)
* (chunk_memory_offset + chunk_max_nodes * node_size);
}
//=== constructor ===//
small_free_memory_list(std::size_t node_size) noexcept;
// does not own memory!
small_free_memory_list(std::size_t node_size, void* mem, std::size_t size) noexcept;
small_free_memory_list(small_free_memory_list&& other) noexcept;
~small_free_memory_list() noexcept = default;
small_free_memory_list& operator=(small_free_memory_list&& other) noexcept
{
small_free_memory_list tmp(detail::move(other));
swap(*this, tmp);
return *this;
}
friend void swap(small_free_memory_list& a, small_free_memory_list& b) noexcept;
//=== insert/alloc/dealloc ===//
// inserts new memory of given size into the free list
// mem must be aligned for maximum alignment
void insert(void* mem, std::size_t size) noexcept;
// returns the usable size
// i.e. how many memory will be actually inserted and usable on a call to insert()
std::size_t usable_size(std::size_t size) const noexcept;
// allocates a node big enough for the node size
// pre: !empty()
void* allocate() noexcept;
// always returns nullptr, because array allocations are not supported
void* allocate(std::size_t) noexcept
{
return nullptr;
}
// deallocates the node previously allocated via allocate()
void deallocate(void* node) noexcept;
// forwards to insert()
void deallocate(void* mem, std::size_t size) noexcept
{
insert(mem, size);
}
// hint for allocate() to be prepared to allocate n nodes
// it searches for a chunk that has n nodes free
// returns false, if there is none like that
// never fails for n == 1 if not empty()
// pre: capacity() >= n * node_size()
bool find_chunk(std::size_t n) noexcept
{
return find_chunk_impl(n) != nullptr;
}
//=== getter ===//
std::size_t node_size() const noexcept
{
return node_size_;
}
// the alignment of all nodes
std::size_t alignment() const noexcept;
// number of nodes remaining
std::size_t capacity() const noexcept
{
return capacity_;
}
bool empty() const noexcept
{
return capacity_ == 0u;
}
private:
chunk* find_chunk_impl(std::size_t n = 1) noexcept;
chunk* find_chunk_impl(unsigned char* node, chunk_base* first,
chunk_base* last) noexcept;
chunk* find_chunk_impl(unsigned char* node) noexcept;
chunk_base base_;
std::size_t node_size_, capacity_;
chunk_base *alloc_chunk_, *dealloc_chunk_;
};
// for some reason, this is required in order to define it
void swap(small_free_memory_list& a, small_free_memory_list& b) noexcept;
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DETAIL_SMALL_FREE_LIST_HPP_INCLUDED

View File

@@ -0,0 +1,118 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_DETAIL_UTILITY_HPP
#define WPI_MEMORY_DETAIL_UTILITY_HPP
// implementation of some functions from <utility> to prevent dependencies on it
#include <type_traits>
#include "../config.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include <utility>
#endif
namespace wpi
{
namespace memory
{
namespace detail
{
// move - taken from http://stackoverflow.com/a/7518365
template <typename T>
typename std::remove_reference<T>::type&& move(T&& arg) noexcept
{
return static_cast<typename std::remove_reference<T>::type&&>(arg);
}
// forward - taken from http://stackoverflow.com/a/27501759
template <class T>
T&& forward(typename std::remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T>
T&& forward(typename std::remove_reference<T>::type&& t) noexcept
{
static_assert(!std::is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}
namespace swap_
{
#if WPI_HOSTED_IMPLEMENTATION
using std::swap;
#else
template <typename T>
void swap(T& a, T& b)
{
T tmp = move(a);
a = move(b);
b = move(tmp);
}
#endif
} // namespace swap_
// ADL aware swap
template <typename T>
void adl_swap(T& a, T& b) noexcept
{
using swap_::swap;
swap(a, b);
}
// fancier syntax for enable_if
// used as (template) parameter
// also useful for doxygen
// define PREDEFINED: WPI_REQUIRES(x):=
#define WPI_REQUIRES(Expr) typename std::enable_if<(Expr), int>::type = 0
// same as above, but as return type
// also useful for doxygen:
// defined PREDEFINED: WPI_REQUIRES_RET(x,r):=r
#define WPI_REQUIRES_RET(Expr, ...) typename std::enable_if<(Expr), __VA_ARGS__>::type
// fancier syntax for enable_if on non-templated member function
#define WPI_ENABLE_IF(Expr) \
template <typename Dummy = std::true_type, WPI_REQUIRES(Dummy::value && (Expr))>
// fancier syntax for general expression SFINAE
// used as (template) parameter
// also useful for doxygen:
// define PREDEFINED: WPI_SFINAE(x):=
#define WPI_SFINAE(Expr) decltype((Expr), int()) = 0
// avoids code repetition for one-line forwarding functions
#define WPI_AUTO_RETURN(Expr) \
decltype(Expr) \
{ \
return Expr; \
}
// same as above, but requires certain type
#define WPI_AUTO_RETURN_TYPE(Expr, T) \
decltype(Expr) \
{ \
static_assert(std::is_same<decltype(Expr), T>::value, \
#Expr " does not have the return type " #T); \
return Expr; \
}
// whether or not a type is an instantiation of a template
template <template <typename...> class Template, typename T>
struct is_instantiation_of : std::false_type
{
};
template <template <typename...> class Template, typename... Args>
struct is_instantiation_of<Template, Template<Args...>> : std::true_type
{
};
} // namespace detail
} // namespace memory
} // namespace wpi
#endif //WPI_MEMORY_DETAIL_UTILITY_HPP

View File

@@ -0,0 +1,289 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
/// \file
/// The exception classes.
#ifndef WPI_MEMORY_ERROR_HPP_INCLUDED
#define WPI_MEMORY_ERROR_HPP_INCLUDED
#include <cstddef>
#include <new>
#include "config.hpp"
namespace wpi
{
namespace memory
{
/// Contains information about an allocator.
/// It can be used for logging in the various handler functions.
/// \ingroup core
struct allocator_info
{
/// The name of the allocator.
/// It is a NTBS whose lifetime is not managed by this object,
/// it must be stored elsewhere or be a string literal.
const char* name;
/// A pointer representing an allocator.
/// It does not necessarily point to the beginning of the allocator object,
/// the only guarantee is that different allocator objects result in a different pointer value.
/// For stateless allocators it is sometimes \c nullptr.
/// \note The pointer must not be cast back to any allocator type.
const void* allocator;
/// \effects Creates it by giving it the name of the allocator and a pointer.
constexpr allocator_info(const char* n, const void* alloc) noexcept
: name(n), allocator(alloc)
{
}
/// @{
/// \effects Compares two \ref allocator_info objects, they are equal, if the \ref allocator is the same.
/// \returns The result of the comparision.
friend constexpr bool operator==(const allocator_info& a,
const allocator_info& b) noexcept
{
return a.allocator == b.allocator;
}
friend constexpr bool operator!=(const allocator_info& a,
const allocator_info& b) noexcept
{
return a.allocator != b.allocator;
}
/// @}
};
/// The exception class thrown when a low level allocator runs out of memory.
/// It is derived from \c std::bad_alloc.
/// This can happen if a low level allocation function like \c std::malloc() runs out of memory.
/// Throwing can be prohibited by the handler function.
/// \ingroup core
class out_of_memory : public std::bad_alloc
{
public:
/// The type of the handler called in the constructor of \ref out_of_memory.
/// When an out of memory situation is encountered and the exception class created,
/// this handler gets called.
/// It is especially useful if exception support is disabled.
/// It gets the \ref allocator_info and the amount of memory that was tried to be allocated.
/// \requiredbe It can log the error, throw a different exception derived from \c std::bad_alloc or abort the program.
/// If it returns, this exception object will be created and thrown.
/// \defaultbe On a hosted implementation it logs the error on \c stderr and continues execution,
/// leading to this exception being thrown.
/// On a freestanding implementation it does nothing.
/// \note It is different from \c std::new_handler; it will not be called in a loop trying to allocate memory
/// or something like that. Its only job is to report the error.
using handler = void (*)(const allocator_info& info, std::size_t amount);
/// \effects Sets \c h as the new \ref handler in an atomic operation.
/// A \c nullptr sets the default \ref handler.
/// \returns The previous \ref handler. This is never \c nullptr.
static handler set_handler(handler h);
/// \returns The current \ref handler. This is never \c nullptr.
static handler get_handler();
/// \effects Creates it by passing it the \ref allocator_info and the amount of memory failed to be allocated.
/// It also calls the \ref handler to control whether or not it will be thrown.
out_of_memory(const allocator_info& info, std::size_t amount);
/// \returns A static NTBS that describes the error.
/// It does not contain any specific information since there is no memory for formatting.
const char* what() const noexcept override;
/// \returns The \ref allocator_info passed to it in the constructor.
const allocator_info& allocator() const noexcept
{
return info_;
}
/// \returns The amount of memory that was tried to be allocated.
/// This is the value passed in the constructor.
std::size_t failed_allocation_size() const noexcept
{
return amount_;
}
private:
allocator_info info_;
std::size_t amount_;
};
/// A special case of \ref out_of_memory errors
/// thrown when a low-level allocator with a fixed size runs out of memory.
/// For example, thrown by \ref fixed_block_allocator or \ref static_allocator.<br>
/// It is derived from \ref out_of_memory but does not provide its own handler.
/// \ingroup core
class out_of_fixed_memory : public out_of_memory
{
public:
/// \effects Just forwards to \ref out_of_memory.
out_of_fixed_memory(const allocator_info& info, std::size_t amount)
: out_of_memory(info, amount)
{
}
/// \returns A static NTBS that describes the error.
/// It does not contain any specific information since there is no memory for formatting.
const char* what() const noexcept override;
};
/// The exception class thrown when an allocation size is bigger than the supported maximum.
/// This size is either the node, array or alignment parameter in a call to an allocation function.
/// If those exceed the supported maximum returned by \c max_node_size(), \c max_array_size() or \c max_alignment(),
/// one of its derived classes will be thrown or this class if in a situation where the type is unknown.
/// It is derived from \c std::bad_alloc.
/// Throwing can be prohibited by the handler function.
/// \note Even if all parameters are less than the maximum, \ref out_of_memory or a similar exception can be thrown,
/// because the maximum functions return an upper bound and not the actual supported maximum size,
/// since it always depends on fence memory, alignment buffer and the like.
/// \note A user should only \c catch for \c bad_allocation_size, not the derived classes.
/// \note Most checks will only be done if \ref WPI_MEMORY_CHECK_ALLOCATION_SIZE is \c true.
/// \ingroup core
class bad_allocation_size : public std::bad_alloc
{
public:
/// The type of the handler called in the constructor of \ref bad_allocation_size.
/// When a bad allocation size is detected and the exception object created,
/// this handler gets called.
/// It is especially useful if exception support is disabled.
/// It gets the \ref allocator_info, the size passed to the function and the supported size
/// (the latter is still an upper bound).
/// \requiredbe It can log the error, throw a different exception derived from \c std::bad_alloc or abort the program.
/// If it returns, this exception object will be created and thrown.
/// \defaultbe On a hosted implementation it logs the error on \c stderr and continues execution,
/// leading to this exception being thrown.
/// On a freestanding implementation it does nothing.
using handler = void (*)(const allocator_info& info, std::size_t passed,
std::size_t supported);
/// \effects Sets \c h as the new \ref handler in an atomic operation.
/// A \c nullptr sets the default \ref handler.
/// \returns The previous \ref handler. This is never \c nullptr.
static handler set_handler(handler h);
/// \returns The current \ref handler. This is never \c nullptr.
static handler get_handler();
/// \effects Creates it by passing it the \ref allocator_info, the size passed to the allocation function
/// and an upper bound on the supported size.
/// It also calls the \ref handler to control whether or not it will be thrown.
bad_allocation_size(const allocator_info& info, std::size_t passed,
std::size_t supported);
/// \returns A static NTBS that describes the error.
/// It does not contain any specific information since there is no memory for formatting.
const char* what() const noexcept override;
/// \returns The \ref allocator_info passed to it in the constructor.
const allocator_info& allocator() const noexcept
{
return info_;
}
/// \returns The size or alignment value that was passed to the allocation function
/// which was too big. This is the same value passed to the constructor.
std::size_t passed_value() const noexcept
{
return passed_;
}
/// \returns An upper bound on the maximum supported size/alignment.
/// It is only an upper bound, values below can fail, but values above will always fail.
std::size_t supported_value() const noexcept
{
return supported_;
}
private:
allocator_info info_;
std::size_t passed_, supported_;
};
/// The exception class thrown when the node size exceeds the supported maximum,
/// i.e. it is bigger than \c max_node_size().
/// It is derived from \ref bad_allocation_size but does not override the handler.
/// \ingroup core
class bad_node_size : public bad_allocation_size
{
public:
/// \effects Just forwards to \ref bad_allocation_size.
bad_node_size(const allocator_info& info, std::size_t passed, std::size_t supported)
: bad_allocation_size(info, passed, supported)
{
}
/// \returns A static NTBS that describes the error.
/// It does not contain any specific information since there is no memory for formatting.
const char* what() const noexcept override;
};
/// The exception class thrown when the array size exceeds the supported maximum,
/// i.e. it is bigger than \c max_array_size().
/// It is derived from \ref bad_allocation_size but does not override the handler.
/// \ingroup core
class bad_array_size : public bad_allocation_size
{
public:
/// \effects Just forwards to \ref bad_allocation_size.
bad_array_size(const allocator_info& info, std::size_t passed, std::size_t supported)
: bad_allocation_size(info, passed, supported)
{
}
/// \returns A static NTBS that describes the error.
/// It does not contain any specific information since there is no memory for formatting.
const char* what() const noexcept override;
};
/// The exception class thrown when the alignment exceeds the supported maximum,
/// i.e. it is bigger than \c max_alignment().
/// It is derived from \ref bad_allocation_size but does not override the handler.
/// \ingroup core
class bad_alignment : public bad_allocation_size
{
public:
/// \effects Just forwards to \ref bad_allocation_size.
/// \c passed is <tt>count * size</tt>, \c supported the size in bytes.
bad_alignment(const allocator_info& info, std::size_t passed, std::size_t supported)
: bad_allocation_size(info, passed, supported)
{
}
/// \returns A static NTBS that describes the error.
/// It does not contain any specific information since there is no memory for formatting.
const char* what() const noexcept override;
};
namespace detail
{
template <class Ex, typename Func>
void check_allocation_size(std::size_t passed, Func f, const allocator_info& info)
{
#if WPI_MEMORY_CHECK_ALLOCATION_SIZE
auto supported = f();
if (passed > supported)
WPI_THROW(Ex(info, passed, supported));
#else
(void)passed;
(void)f;
(void)info;
#endif
}
template <class Ex>
void check_allocation_size(std::size_t passed, std::size_t supported,
const allocator_info& info)
{
check_allocation_size<Ex>(
passed, [&] { return supported; }, info);
}
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_ERROR_HPP_INCLUDED

View File

@@ -0,0 +1,212 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_FALLBACK_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_FALLBACK_ALLOCATOR_HPP_INCLUDED
/// \file
//// Class template \ref wpi::memory::fallback_allocator.
#include "detail/ebo_storage.hpp"
#include "detail/utility.hpp"
#include "allocator_traits.hpp"
#include "config.hpp"
namespace wpi
{
namespace memory
{
/// A \concept{raw_allocator,RawAllocator} with a fallback.
/// Allocation first tries `Default`, if it fails,
/// it uses `Fallback`.
/// \requires `Default` must be a composable \concept{concept_rawallocator,RawAllocator},
/// `Fallback` must be a \concept{concept_rawallocator,RawAllocator}.
/// \ingroup adapter
template <class Default, class Fallback>
class fallback_allocator
: WPI_EBO(detail::ebo_storage<0, typename allocator_traits<Default>::allocator_type>),
WPI_EBO(detail::ebo_storage<1, typename allocator_traits<Fallback>::allocator_type>)
{
using default_traits = allocator_traits<Default>;
using default_composable_traits = composable_allocator_traits<Default>;
using fallback_traits = allocator_traits<Fallback>;
using fallback_composable_traits = composable_allocator_traits<Fallback>;
using fallback_composable =
is_composable_allocator<typename fallback_traits::allocator_type>;
public:
using default_allocator_type = typename allocator_traits<Default>::allocator_type;
using fallback_allocator_type = typename allocator_traits<Fallback>::allocator_type;
using is_stateful =
std::integral_constant<bool, default_traits::is_stateful::value
|| fallback_traits::is_stateful::value>;
/// \effects Default constructs both allocators.
/// \notes This function only participates in overload resolution, if both allocators are not stateful.
WPI_ENABLE_IF(!is_stateful::value)
fallback_allocator()
: detail::ebo_storage<0, default_allocator_type>({}),
detail::ebo_storage<1, fallback_allocator_type>({})
{
}
/// \effects Constructs the allocator by passing in the two allocators it has.
explicit fallback_allocator(default_allocator_type&& default_alloc,
fallback_allocator_type&& fallback_alloc = {})
: detail::ebo_storage<0, default_allocator_type>(detail::move(default_alloc)),
detail::ebo_storage<1, fallback_allocator_type>(detail::move(fallback_alloc))
{
}
/// @{
/// \effects First calls the compositioning (de)allocation function on the `default_allocator_type`.
/// If that fails, uses the non-compositioning function of the `fallback_allocator_type`.
void* allocate_node(std::size_t size, std::size_t alignment)
{
auto ptr = default_composable_traits::try_allocate_node(get_default_allocator(),
size, alignment);
if (!ptr)
ptr = fallback_traits::allocate_node(get_fallback_allocator(), size, alignment);
return ptr;
}
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
{
auto ptr = default_composable_traits::try_allocate_array(get_default_allocator(),
count, size, alignment);
if (!ptr)
ptr = fallback_traits::allocate_array(get_fallback_allocator(), count, size,
alignment);
return ptr;
}
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
auto res = default_composable_traits::try_deallocate_node(get_default_allocator(),
ptr, size, alignment);
if (!res)
fallback_traits::deallocate_node(get_fallback_allocator(), ptr, size,
alignment);
}
void deallocate_array(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
auto res =
default_composable_traits::try_deallocate_array(get_default_allocator(), ptr,
count, size, alignment);
if (!res)
fallback_traits::deallocate_array(get_fallback_allocator(), ptr, count, size,
alignment);
}
/// @}
/// @{
/// \effects First calls the compositioning (de)allocation function on the `default_allocator_type`.
/// If that fails, uses the compositioning function of the `fallback_allocator_type`.
/// \requires The `fallback_allocator_type` msut be composable.
WPI_ENABLE_IF(fallback_composable::value)
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
{
auto ptr = default_composable_traits::try_allocate_node(get_default_allocator(),
size, alignment);
if (!ptr)
ptr = fallback_composable_traits::try_allocate_node(get_fallback_allocator(),
size, alignment);
return ptr;
}
WPI_ENABLE_IF(fallback_composable::value)
void* allocate_array(std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
auto ptr = default_composable_traits::try_allocate_array(get_default_allocator(),
count, size, alignment);
if (!ptr)
ptr = fallback_composable_traits::try_allocate_array(get_fallback_allocator(),
count, size, alignment);
return ptr;
}
WPI_ENABLE_IF(fallback_composable::value)
bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
auto res = default_composable_traits::try_deallocate_node(get_default_allocator(),
ptr, size, alignment);
if (!res)
res = fallback_composable_traits::try_deallocate_node(get_fallback_allocator(),
ptr, size, alignment);
return res;
}
WPI_ENABLE_IF(fallback_composable::value)
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
auto res =
default_composable_traits::try_deallocate_array(get_default_allocator(), ptr,
count, size, alignment);
if (!res)
res = fallback_composable_traits::try_deallocate_array(get_fallback_allocator(),
ptr, count, size,
alignment);
return res;
}
/// @}
/// @{
/// \returns The maximum of the two values from both allocators.
std::size_t max_node_size() const
{
auto def = default_traits::max_node_size(get_default_allocator());
auto fallback = fallback_traits::max_node_size(get_fallback_allocator());
return fallback > def ? fallback : def;
}
std::size_t max_array_size() const
{
auto def = default_traits::max_array_size(get_default_allocator());
auto fallback = fallback_traits::max_array_size(get_fallback_allocator());
return fallback > def ? fallback : def;
}
std::size_t max_alignment() const
{
auto def = default_traits::max_alignment(get_default_allocator());
auto fallback = fallback_traits::max_alignment(get_fallback_allocator());
return fallback > def ? fallback : def;
}
/// @}
/// @{
/// \returns A (`const`) reference to the default allocator.
default_allocator_type& get_default_allocator() noexcept
{
return detail::ebo_storage<0, default_allocator_type>::get();
}
const default_allocator_type& get_default_allocator() const noexcept
{
return detail::ebo_storage<0, default_allocator_type>::get();
}
/// @}
/// @{
/// \returns A (`const`) reference to the fallback allocator.
fallback_allocator_type& get_fallback_allocator() noexcept
{
return detail::ebo_storage<1, fallback_allocator_type>::get();
}
const fallback_allocator_type& get_fallback_allocator() const noexcept
{
return detail::ebo_storage<1, fallback_allocator_type>::get();
}
/// @}
};
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_FALLBACK_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,83 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_HEAP_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_HEAP_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::heap_allocator and related functions.
#include "detail/lowlevel_allocator.hpp"
#include "config.hpp"
#if WPI_MEMORY_EXTERN_TEMPLATE
#include "allocator_traits.hpp"
#endif
namespace wpi
{
namespace memory
{
struct allocator_info;
/// Allocates heap memory.
/// This function is used by the \ref heap_allocator to allocate the heap memory.
/// It is not defined on a freestanding implementation, a definition must be provided by the library user.
/// \requiredbe This function shall return a block of uninitialized memory that is aligned for \c max_align_t and has the given size.
/// The size parameter will not be zero.
/// It shall return a \c nullptr if no memory is available.
/// It must be thread safe.
/// \defaultbe On a hosted implementation this function uses OS specific facilities, \c std::malloc is used as fallback.
/// \ingroup allocator
void* heap_alloc(std::size_t size) noexcept;
/// Deallocates heap memory.
/// This function is used by the \ref heap_allocator to allocate the heap memory.
/// It is not defined on a freestanding implementation, a definition must be provided by the library user.
/// \requiredbe This function gets a pointer from a previous call to \ref heap_alloc with the same size.
/// It shall free the memory.
/// The pointer will not be zero.
/// It must be thread safe.
/// \defaultbe On a hosted implementation this function uses OS specific facilities, \c std::free is used as fallback.
/// \ingroup allocator
void heap_dealloc(void* ptr, std::size_t size) noexcept;
namespace detail
{
struct heap_allocator_impl
{
static allocator_info info() noexcept;
static void* allocate(std::size_t size, std::size_t) noexcept
{
return heap_alloc(size);
}
static void deallocate(void* ptr, std::size_t size, std::size_t) noexcept
{
heap_dealloc(ptr, size);
}
static std::size_t max_node_size() noexcept;
};
WPI_MEMORY_LL_ALLOCATOR_LEAK_CHECKER(heap_allocator_impl,
heap_alloator_leak_checker)
} // namespace detail
/// A stateless \concept{concept_rawallocator,RawAllocator} that allocates memory from the heap.
/// It uses the two functions \ref heap_alloc and \ref heap_dealloc for the allocation,
/// which default to \c std::malloc and \c std::free.
/// \ingroup allocator
using heap_allocator =
WPI_IMPL_DEFINED(detail::lowlevel_allocator<detail::heap_allocator_impl>);
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class detail::lowlevel_allocator<detail::heap_allocator_impl>;
extern template class allocator_traits<heap_allocator>;
#endif
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_HEAP_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,305 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_ITERATION_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_ITERATION_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class template \ref wpi::memory::iteration_allocator.
#include "detail/debug_helpers.hpp"
#include "detail/memory_stack.hpp"
#include "default_allocator.hpp"
#include "error.hpp"
#include "memory_arena.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
template <class BlockOrRawAllocator>
using iteration_block_allocator =
make_block_allocator_t<BlockOrRawAllocator, fixed_block_allocator>;
} // namespace detail
/// A stateful \concept{concept_rawallocator,RawAllocator} that is designed for allocations in a loop.
/// It uses `N` stacks for the allocation, one of them is always active.
/// Allocation uses the currently active stack.
/// Calling \ref iteration_allocator::next_iteration() at the end of the loop,
/// will make the next stack active for allocation,
/// effectively releasing all of its memory.
/// Any memory allocated will thus be usable for `N` iterations of the loop.
/// This type of allocator is a generalization of the double frame allocator.
/// \ingroup allocator
template <std::size_t N, class BlockOrRawAllocator = default_allocator>
class iteration_allocator
: WPI_EBO(detail::iteration_block_allocator<BlockOrRawAllocator>)
{
public:
using allocator_type = detail::iteration_block_allocator<BlockOrRawAllocator>;
/// \effects Creates it with a given initial block size and and other constructor arguments for the \concept{concept_blockallocator,BlockAllocator}.
/// It will allocate the first (and only) block and evenly divide it on all the stacks it uses.
template <typename... Args>
explicit iteration_allocator(std::size_t block_size, Args&&... args)
: allocator_type(block_size, detail::forward<Args>(args)...), cur_(0u)
{
block_ = get_allocator().allocate_block();
auto cur = static_cast<char*>(block_.memory);
auto size_each = block_.size / N;
for (auto i = 0u; i != N; ++i)
{
stacks_[i] = detail::fixed_memory_stack(cur);
cur += size_each;
}
}
iteration_allocator(iteration_allocator&& other) noexcept
: allocator_type(detail::move(other)),
block_(other.block_),
cur_(detail::move(other.cur_))
{
for (auto i = 0u; i != N; ++i)
stacks_[i] = detail::move(other.stacks_[i]);
other.cur_ = N;
}
~iteration_allocator() noexcept
{
if (cur_ < N)
get_allocator().deallocate_block(block_);
}
iteration_allocator& operator=(iteration_allocator&& other) noexcept
{
allocator_type::operator=(detail::move(other));
block_ = other.block_;
cur_ = other.cur_;
for (auto i = 0u; i != N; ++i)
stacks_[i] = detail::move(other.stacks_[i]);
other.cur_ = N;
return *this;
}
/// \effects Allocates a memory block of given size and alignment.
/// It simply moves the top marker of the currently active stack.
/// \returns A \concept{concept_node,node} with given size and alignment.
/// \throws \ref out_of_fixed_memory if the current stack does not have any memory left.
/// \requires \c size and \c alignment must be valid.
void* allocate(std::size_t size, std::size_t alignment)
{
auto& stack = stacks_[cur_];
auto fence = detail::debug_fence_size;
auto offset = detail::align_offset(stack.top() + fence, alignment);
if (!stack.top()
|| (fence + offset + size + fence > std::size_t(block_end(cur_) - stack.top())))
WPI_THROW(out_of_fixed_memory(info(), size));
return stack.allocate_unchecked(size, offset);
}
/// \effects Allocates a memory block of given size and alignment
/// similar to \ref allocate().
/// \returns A \concept{concept_node,node} with given size and alignment
/// or `nullptr` if the current stack does not have any memory left.
void* try_allocate(std::size_t size, std::size_t alignment) noexcept
{
auto& stack = stacks_[cur_];
return stack.allocate(block_end(cur_), size, alignment);
}
/// \effects Goes to the next internal stack.
/// This will clear the stack whose \ref max_iterations() lifetime has reached,
/// and use it for all allocations in this iteration.
/// \note This function should be called at the end of the loop.
void next_iteration() noexcept
{
WPI_MEMORY_ASSERT_MSG(cur_ != N, "moved-from allocator");
cur_ = (cur_ + 1) % N;
stacks_[cur_].unwind(block_start(cur_));
}
/// \returns The number of iteration each allocation will live.
/// This is the template parameter `N`.
static std::size_t max_iterations() noexcept
{
return N;
}
/// \returns The index of the current iteration.
/// This is modulo \ref max_iterations().
std::size_t cur_iteration() const noexcept
{
return cur_;
}
/// \returns A reference to the \concept{concept_blockallocator,BlockAllocator} used for managing the memory.
/// \requires It is undefined behavior to move this allocator out into another object.
allocator_type& get_allocator() noexcept
{
return *this;
}
/// \returns The amount of memory remaining in the stack with the given index.
/// This is the number of bytes that are available for allocation.
std::size_t capacity_left(std::size_t i) const noexcept
{
return std::size_t(block_end(i) - stacks_[i].top());
}
/// \returns The amount of memory remaining in the currently active stack.
std::size_t capacity_left() const noexcept
{
return capacity_left(cur_iteration());
}
private:
allocator_info info() const noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::iteration_allocator", this};
}
char* block_start(std::size_t i) const noexcept
{
WPI_MEMORY_ASSERT_MSG(i <= N, "moved from state");
auto ptr = static_cast<char*>(block_.memory);
return ptr + (i * block_.size / N);
}
char* block_end(std::size_t i) const noexcept
{
WPI_MEMORY_ASSERT_MSG(i < N, "moved from state");
return block_start(i + 1);
}
detail::fixed_memory_stack stacks_[N];
memory_block block_;
std::size_t cur_;
friend allocator_traits<iteration_allocator<N, BlockOrRawAllocator>>;
friend composable_allocator_traits<iteration_allocator<N, BlockOrRawAllocator>>;
};
/// An alias for \ref iteration_allocator for two iterations.
/// \ingroup allocator
template <class BlockOrRawAllocator = default_allocator>
WPI_ALIAS_TEMPLATE(double_frame_allocator,
iteration_allocator<2, BlockOrRawAllocator>);
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class iteration_allocator<2>;
#endif
/// Specialization of the \ref allocator_traits for \ref iteration_allocator.
/// \note It is not allowed to mix calls through the specialization and through the member functions,
/// i.e. \ref memory_stack::allocate() and this \c allocate_node().
/// \ingroup allocator
template <std::size_t N, class BlockAllocator>
class allocator_traits<iteration_allocator<N, BlockAllocator>>
{
public:
using allocator_type = iteration_allocator<N, BlockAllocator>;
using is_stateful = std::true_type;
/// \returns The result of \ref iteration_allocator::allocate().
static void* allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment)
{
return state.allocate(size, alignment);
}
/// \returns The result of \ref memory_stack::allocate().
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
std::size_t alignment)
{
return allocate_node(state, count * size, alignment);
}
/// @{
/// \effects Does nothing.
/// Actual deallocation can only be done via \ref memory_stack::unwind().
static void deallocate_node(allocator_type&, void*, std::size_t, std::size_t) noexcept
{
}
static void deallocate_array(allocator_type&, void*, std::size_t, std::size_t,
std::size_t) noexcept
{
}
/// @}
/// @{
/// \returns The maximum size which is \ref iteration_allocator::capacity_left().
static std::size_t max_node_size(const allocator_type& state) noexcept
{
return state.capacity_left();
}
static std::size_t max_array_size(const allocator_type& state) noexcept
{
return state.capacity_left();
}
/// @}
/// \returns The maximum possible value since there is no alignment restriction
/// (except indirectly through \ref memory_stack::next_capacity()).
static std::size_t max_alignment(const allocator_type&) noexcept
{
return std::size_t(-1);
}
};
/// Specialization of the \ref composable_allocator_traits for \ref iteration_allocator classes.
/// \ingroup allocator
template <std::size_t N, class BlockAllocator>
class composable_allocator_traits<iteration_allocator<N, BlockAllocator>>
{
public:
using allocator_type = iteration_allocator<N, BlockAllocator>;
/// \returns The result of \ref memory_stack::try_allocate().
static void* try_allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment) noexcept
{
return state.try_allocate(size, alignment);
}
/// \returns The result of \ref memory_stack::try_allocate().
static void* try_allocate_array(allocator_type& state, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
return state.try_allocate(count * size, alignment);
}
/// @{
/// \effects Does nothing.
/// \returns Whether the memory will be deallocated by \ref memory_stack::unwind().
static bool try_deallocate_node(allocator_type& state, void* ptr, std::size_t,
std::size_t) noexcept
{
return state.block_.contains(ptr);
}
static bool try_deallocate_array(allocator_type& state, void* ptr, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
return try_deallocate_node(state, ptr, count * size, alignment);
}
/// @}
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class allocator_traits<iteration_allocator<2>>;
extern template class composable_allocator_traits<iteration_allocator<2>>;
#endif
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_ITERATION_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,927 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_JOINT_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_JOINT_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class template \ref wpi::memory::joint_ptr, \ref wpi::memory::joint_allocator and related.
#include <initializer_list>
#include <new>
#include "detail/align.hpp"
#include "detail/memory_stack.hpp"
#include "detail/utility.hpp"
#include "allocator_storage.hpp"
#include "config.hpp"
#include "default_allocator.hpp"
#include "error.hpp"
namespace wpi
{
namespace memory
{
template <typename T, class RawAllocator>
class joint_ptr;
template <typename T>
class joint_type;
namespace detail
{
// the stack that allocates the joint memory
class joint_stack
{
public:
joint_stack(void* mem, std::size_t cap) noexcept
: stack_(static_cast<char*>(mem)), end_(static_cast<char*>(mem) + cap)
{
}
void* allocate(std::size_t size, std::size_t alignment) noexcept
{
return stack_.allocate(end_, size, alignment, 0u);
}
bool bump(std::size_t offset) noexcept
{
if (offset > std::size_t(end_ - stack_.top()))
return false;
stack_.bump(offset);
return true;
}
char* top() noexcept
{
return stack_.top();
}
const char* top() const noexcept
{
return stack_.top();
}
void unwind(void* ptr) noexcept
{
stack_.unwind(static_cast<char*>(ptr));
}
std::size_t capacity(const char* mem) const noexcept
{
return std::size_t(end_ - mem);
}
std::size_t capacity_left() const noexcept
{
return std::size_t(end_ - top());
}
std::size_t capacity_used(const char* mem) const noexcept
{
return std::size_t(top() - mem);
}
private:
detail::fixed_memory_stack stack_;
char* end_;
};
template <typename T>
detail::joint_stack& get_stack(joint_type<T>& obj) noexcept;
template <typename T>
const detail::joint_stack& get_stack(const joint_type<T>& obj) noexcept;
} // namespace detail
/// Tag type that can't be created.
///
/// It isued by \ref joint_ptr.
/// \ingroup allocator
class joint
{
joint(std::size_t cap) noexcept : capacity(cap) {}
std::size_t capacity;
template <typename T, class RawAllocator>
friend class joint_ptr;
template <typename T>
friend class joint_type;
};
/// Tag type to make the joint size more explicit.
///
/// It is used by \ref joint_ptr.
/// \ingroup allocator
struct joint_size
{
std::size_t size;
explicit joint_size(std::size_t s) noexcept : size(s) {}
};
/// CRTP base class for all objects that want to use joint memory.
///
/// This will disable default copy/move operations
/// and inserts additional members for the joint memory management.
/// \ingroup allocator
template <typename T>
class joint_type
{
protected:
/// \effects Creates the base class,
/// the tag type cannot be created by the user.
/// \note This ensures that you cannot create joint types yourself.
joint_type(joint j) noexcept;
joint_type(const joint_type&) = delete;
joint_type(joint_type&&) = delete;
private:
detail::joint_stack stack_;
template <typename U>
friend detail::joint_stack& detail::get_stack(joint_type<U>& obj) noexcept;
template <typename U>
friend const detail::joint_stack& detail::get_stack(const joint_type<U>& obj) noexcept;
};
namespace detail
{
template <typename T>
detail::joint_stack& get_stack(joint_type<T>& obj) noexcept
{
return obj.stack_;
}
template <typename T>
const detail::joint_stack& get_stack(const joint_type<T>& obj) noexcept
{
return obj.stack_;
}
template <typename T>
char* get_memory(joint_type<T>& obj) noexcept
{
auto mem = static_cast<void*>(&obj);
return static_cast<char*>(mem) + sizeof(T);
}
template <typename T>
const char* get_memory(const joint_type<T>& obj) noexcept
{
auto mem = static_cast<const void*>(&obj);
return static_cast<const char*>(mem) + sizeof(T);
}
} // namespace detail
template <typename T>
joint_type<T>::joint_type(joint j) noexcept : stack_(detail::get_memory(*this), j.capacity)
{
WPI_MEMORY_ASSERT(stack_.top() == detail::get_memory(*this));
WPI_MEMORY_ASSERT(stack_.capacity_left() == j.capacity);
}
/// A pointer to an object where all allocations are joint.
///
/// It can either own an object or not (be `nullptr`).
/// When it owns an object, it points to a memory block.
/// This memory block contains both the actual object (of the type `T`)
/// and space for allocations of `T`s members.
///
/// The type `T` must be derived from \ref joint_type and every constructor must take \ref joint
/// as first parameter.
/// This prevents that you create joint objects yourself,
/// without the additional storage.
/// The default copy and move constructors are also deleted,
/// you need to write them yourself.
///
/// You can only access the object through the pointer,
/// use \ref joint_allocator or \ref joint_array as members of `T`,
/// to enable the memory sharing.
/// If you are using \ref joint_allocator inside STL containers,
/// make sure that you do not call their regular copy/move constructors,
/// but instead the version where you pass an allocator.
///
/// The memory block will be managed by the given \concept{concept_rawallocator,RawAllocator},
/// it is stored in an \ref allocator_reference and not owned by the pointer directly.
/// \ingroup allocator
template <typename T, class RawAllocator>
class joint_ptr : WPI_EBO(allocator_reference<RawAllocator>)
{
static_assert(std::is_base_of<joint_type<T>, T>::value,
"T must be derived of joint_type<T>");
public:
using element_type = T;
using allocator_type = typename allocator_reference<RawAllocator>::allocator_type;
//=== constructors/destructor/assignment ===//
/// @{
/// \effects Creates it with a \concept{concept_rawallocator,RawAllocator}, but does not own a new object.
explicit joint_ptr(allocator_type& alloc) noexcept
: allocator_reference<RawAllocator>(alloc), ptr_(nullptr)
{
}
explicit joint_ptr(const allocator_type& alloc) noexcept
: allocator_reference<RawAllocator>(alloc), ptr_(nullptr)
{
}
/// @}
/// @{
/// \effects Reserves memory for the object and the additional size,
/// and creates the object by forwarding the arguments to its constructor.
/// The \concept{concept_rawallocator,RawAllocator} will be used for the allocation.
template <typename... Args>
joint_ptr(allocator_type& alloc, joint_size additional_size, Args&&... args)
: joint_ptr(alloc)
{
create(additional_size.size, detail::forward<Args>(args)...);
}
template <typename... Args>
joint_ptr(const allocator_type& alloc, joint_size additional_size, Args&&... args)
: joint_ptr(alloc)
{
create(additional_size.size, detail::forward<Args>(args)...);
}
/// @}
/// \effects Move-constructs the pointer.
/// Ownership will be transferred from `other` to the new object.
joint_ptr(joint_ptr&& other) noexcept
: allocator_reference<RawAllocator>(detail::move(other)), ptr_(other.ptr_)
{
other.ptr_ = nullptr;
}
/// \effects Destroys the object and deallocates its storage.
~joint_ptr() noexcept
{
reset();
}
/// \effects Move-assings the pointer.
/// The previously owned object will be destroyed,
/// and ownership of `other` transferred.
joint_ptr& operator=(joint_ptr&& other) noexcept
{
joint_ptr tmp(detail::move(other));
swap(*this, tmp);
return *this;
}
/// \effects Same as `reset()`.
joint_ptr& operator=(std::nullptr_t) noexcept
{
reset();
return *this;
}
/// \effects Swaps to pointers and their ownership and allocator.
friend void swap(joint_ptr& a, joint_ptr& b) noexcept
{
detail::adl_swap(static_cast<allocator_reference<RawAllocator>&>(a),
static_cast<allocator_reference<RawAllocator>&>(b));
detail::adl_swap(a.ptr_, b.ptr_);
}
//=== modifiers ===//
/// \effects Destroys the object it refers to,
/// if there is any.
void reset() noexcept
{
if (ptr_)
{
(**this).~element_type();
this->deallocate_node(ptr_,
sizeof(element_type)
+ detail::get_stack(*ptr_).capacity(
detail::get_memory(*ptr_)),
alignof(element_type));
ptr_ = nullptr;
}
}
//=== accessors ===//
/// \returns `true` if the pointer does own an object,
/// `false` otherwise.
explicit operator bool() const noexcept
{
return ptr_ != nullptr;
}
/// \returns A reference to the object it owns.
/// \requires The pointer must own an object,
/// i.e. `operator bool()` must return `true`.
element_type& operator*() const noexcept
{
WPI_MEMORY_ASSERT(ptr_);
return *get();
}
/// \returns A pointer to the object it owns.
/// \requires The pointer must own an object,
/// i.e. `operator bool()` must return `true`.
element_type* operator->() const noexcept
{
WPI_MEMORY_ASSERT(ptr_);
return get();
}
/// \returns A pointer to the object it owns
/// or `nullptr`, if it does not own any object.
element_type* get() const noexcept
{
return static_cast<element_type*>(ptr_);
}
/// \returns A reference to the allocator it will use for the deallocation.
auto get_allocator() const noexcept
-> decltype(std::declval<allocator_reference<allocator_type>>().get_allocator())
{
return this->allocator_reference<allocator_type>::get_allocator();
}
private:
template <typename... Args>
void create(std::size_t additional_size, Args&&... args)
{
auto mem = this->allocate_node(sizeof(element_type) + additional_size,
alignof(element_type));
element_type* ptr = nullptr;
#if WPI_HAS_EXCEPTION_SUPPORT
try
{
ptr = ::new (mem)
element_type(joint(additional_size), detail::forward<Args>(args)...);
}
catch (...)
{
this->deallocate_node(mem, sizeof(element_type) + additional_size,
alignof(element_type));
throw;
}
#else
ptr = ::new (mem)
element_type(joint(additional_size), detail::forward<Args>(args)...);
#endif
ptr_ = ptr;
}
joint_type<T>* ptr_;
friend class joint_allocator;
};
/// @{
/// \returns `!ptr`,
/// i.e. if `ptr` does not own anything.
/// \relates joint_ptr
template <typename T, class RawAllocator>
bool operator==(const joint_ptr<T, RawAllocator>& ptr, std::nullptr_t)
{
return !ptr;
}
template <typename T, class RawAllocator>
bool operator==(std::nullptr_t, const joint_ptr<T, RawAllocator>& ptr)
{
return ptr == nullptr;
}
/// @}
/// @{
/// \returns `ptr.get() == p`,
/// i.e. if `ptr` ownws the object referred to by `p`.
/// \relates joint_ptr
template <typename T, class RawAllocator>
bool operator==(const joint_ptr<T, RawAllocator>& ptr, T* p)
{
return ptr.get() == p;
}
template <typename T, class RawAllocator>
bool operator==(T* p, const joint_ptr<T, RawAllocator>& ptr)
{
return ptr == p;
}
/// @}
/// @{
/// \returns `!(ptr == nullptr)`,
/// i.e. if `ptr` does own something.
/// \relates joint_ptr
template <typename T, class RawAllocator>
bool operator!=(const joint_ptr<T, RawAllocator>& ptr, std::nullptr_t)
{
return !(ptr == nullptr);
}
template <typename T, class RawAllocator>
bool operator!=(std::nullptr_t, const joint_ptr<T, RawAllocator>& ptr)
{
return ptr != nullptr;
}
/// @}
/// @{
/// \returns `!(ptr == p)`,
/// i.e. if `ptr` does not ownw the object referred to by `p`.
/// \relates joint_ptr
template <typename T, class RawAllocator>
bool operator!=(const joint_ptr<T, RawAllocator>& ptr, T* p)
{
return !(ptr == p);
}
template <typename T, class RawAllocator>
bool operator!=(T* p, const joint_ptr<T, RawAllocator>& ptr)
{
return ptr != p;
}
/// @}
/// @{
/// \returns A new \ref joint_ptr as if created with the same arguments passed to the constructor.
/// \relatesalso joint_ptr
/// \ingroup allocator
template <typename T, class RawAllocator, typename... Args>
auto allocate_joint(RawAllocator& alloc, joint_size additional_size, Args&&... args)
-> joint_ptr<T, RawAllocator>
{
return joint_ptr<T, RawAllocator>(alloc, additional_size,
detail::forward<Args>(args)...);
}
template <typename T, class RawAllocator, typename... Args>
auto allocate_joint(const RawAllocator& alloc, joint_size additional_size, Args&&... args)
-> joint_ptr<T, RawAllocator>
{
return joint_ptr<T, RawAllocator>(alloc, additional_size,
detail::forward<Args>(args)...);
}
/// @}
/// @{
/// \returns A new \ref joint_ptr that points to a copy of `joint`.
/// It will allocate as much memory as needed and forward to the copy constructor.
/// \ingroup allocator
template <class RawAllocator, typename T>
auto clone_joint(RawAllocator& alloc, const joint_type<T>& joint)
-> joint_ptr<T, RawAllocator>
{
return joint_ptr<T, RawAllocator>(alloc,
joint_size(detail::get_stack(joint).capacity_used(
detail::get_memory(joint))),
static_cast<const T&>(joint));
}
template <class RawAllocator, typename T>
auto clone_joint(const RawAllocator& alloc, const joint_type<T>& joint)
-> joint_ptr<T, RawAllocator>
{
return joint_ptr<T, RawAllocator>(alloc,
joint_size(detail::get_stack(joint).capacity_used(
detail::get_memory(joint))),
static_cast<const T&>(joint));
}
/// @}
/// A \concept{concept_rawallocator,RawAllocator} that uses the additional joint memory for its allocation.
///
/// It is somewhat limited and allows only allocation once.
/// All joint allocators for an object share the joint memory and must not be used in multiple threads.
/// The memory it returns is owned by a \ref joint_ptr and will be destroyed through it.
/// \ingroup allocator
class joint_allocator
{
public:
#if defined(__GNUC__) && (!defined(_GLIBCXX_USE_CXX11_ABI) || _GLIBCXX_USE_CXX11_ABI == 0)
// std::string requires default constructor for the small string optimization when using gcc's old ABI
// so add one, but it must never be used for allocation
joint_allocator() noexcept : stack_(nullptr) {}
#endif
/// \effects Creates it using the joint memory of the given object.
template <typename T>
joint_allocator(joint_type<T>& j) noexcept : stack_(&detail::get_stack(j))
{
}
joint_allocator(const joint_allocator& other) noexcept = default;
joint_allocator& operator=(const joint_allocator& other) noexcept = default;
/// \effects Allocates a node with given properties.
/// \returns A pointer to the new node.
/// \throws \ref out_of_fixed_memory exception if this function has been called for a second time
/// or the joint memory block is exhausted.
void* allocate_node(std::size_t size, std::size_t alignment)
{
WPI_MEMORY_ASSERT(stack_);
auto mem = stack_->allocate(size, alignment);
if (!mem)
WPI_THROW(out_of_fixed_memory(info(), size));
return mem;
}
/// \effects Deallocates the node, if possible.
/// \note It is only possible if it was the last allocation.
void deallocate_node(void* ptr, std::size_t size, std::size_t) noexcept
{
WPI_MEMORY_ASSERT(stack_);
auto end = static_cast<char*>(ptr) + size;
if (end == stack_->top())
stack_->unwind(ptr);
}
private:
allocator_info info() const noexcept
{
return allocator_info(WPI_MEMORY_LOG_PREFIX "::joint_allocator", this);
}
detail::joint_stack* stack_;
friend bool operator==(const joint_allocator& lhs, const joint_allocator& rhs) noexcept;
};
/// @{
/// \returns Whether `lhs` and `rhs` use the same joint memory for the allocation.
/// \relates joint_allocator
inline bool operator==(const joint_allocator& lhs, const joint_allocator& rhs) noexcept
{
return lhs.stack_ == rhs.stack_;
}
inline bool operator!=(const joint_allocator& lhs, const joint_allocator& rhs) noexcept
{
return !(lhs == rhs);
}
/// @}
/// Specialization of \ref is_shared_allocator to mark \ref joint_allocator as shared.
/// This allows using it as \ref allocator_reference directly.
/// \ingroup allocator
template <>
struct is_shared_allocator<joint_allocator> : std::true_type
{
};
/// Specialization of \ref is_thread_safe_allocator to mark \ref joint_allocator as thread safe.
/// This is an optimization to get rid of the mutex in \ref allocator_storage,
/// as joint allocator must not be shared between threads.
/// \note The allocator is *not* thread safe, it just must not be shared.
template <>
struct is_thread_safe_allocator<joint_allocator> : std::true_type
{
};
#if !defined(DOXYGEN)
template <class RawAllocator>
struct propagation_traits;
#endif
/// Specialization of the \ref propagation_traits for the \ref joint_allocator.
/// A joint allocator does not propagate on assignment
/// and it is not allowed to use the regular copy/move constructor of allocator aware containers,
/// instead it needs the copy/move constructor with allocator.
/// \note This is required because the container constructor will end up copying/moving the allocator.
/// But this is not allowed as you need the allocator with the correct joined memory.
/// Copying can be customized (i.e. forbidden), but sadly not move, so keep that in mind.
/// \ingroup allocator
template <>
struct propagation_traits<joint_allocator>
{
using propagate_on_container_swap = std::false_type;
using propagate_on_container_move_assignment = std::false_type;
using propagate_on_container_copy_assignment = std::false_type;
template <class AllocReference>
static AllocReference select_on_container_copy_construction(const AllocReference&)
{
static_assert(always_false<AllocReference>::value,
"you must not use the regular copy constructor");
}
private:
template <typename T>
struct always_false : std::false_type
{
};
};
/// A zero overhead dynamic array using joint memory.
///
/// If you use, e.g. `std::vector` with \ref joint_allocator,
/// this has a slight additional overhead.
/// This type is joint memory aware and has no overhead.
///
/// It has a dynamic, but fixed size,
/// it cannot grow after it has been created.
/// \ingroup allocator
template <typename T>
class joint_array
{
public:
using value_type = T;
using iterator = value_type*;
using const_iterator = const value_type*;
//=== constructors ===//
/// \effects Creates with `size` default-constructed objects using the specified joint memory.
/// \throws \ref out_of_fixed_memory if `size` is too big
/// and anything thrown by `T`s constructor.
/// If an allocation is thrown, the memory will be released directly.
template <typename JointType>
joint_array(std::size_t size, joint_type<JointType>& j)
: joint_array(detail::get_stack(j), size)
{
}
/// \effects Creates with `size` copies of `val` using the specified joint memory.
/// \throws \ref out_of_fixed_memory if `size` is too big
/// and anything thrown by `T`s constructor.
/// If an allocation is thrown, the memory will be released directly.
template <typename JointType>
joint_array(std::size_t size, const value_type& val, joint_type<JointType>& j)
: joint_array(detail::get_stack(j), size, val)
{
}
/// \effects Creates with the copies of the objects in the initializer list using the specified joint memory.
/// \throws \ref out_of_fixed_memory if the size is too big
/// and anything thrown by `T`s constructor.
/// If an allocation is thrown, the memory will be released directly.
template <typename JointType>
joint_array(std::initializer_list<value_type> ilist, joint_type<JointType>& j)
: joint_array(detail::get_stack(j), ilist)
{
}
/// \effects Creates it by forwarding each element of the range to `T`s constructor using the specified joint memory.
/// \throws \ref out_of_fixed_memory if the size is too big
/// and anything thrown by `T`s constructor.
/// If an allocation is thrown, the memory will be released directly.
template <typename InIter, typename JointType,
typename = decltype(*std::declval<InIter&>()++)>
joint_array(InIter begin, InIter end, joint_type<JointType>& j)
: joint_array(detail::get_stack(j), begin, end)
{
}
joint_array(const joint_array&) = delete;
/// \effects Copy constructs each element from `other` into the storage of the specified joint memory.
/// \throws \ref out_of_fixed_memory if the size is too big
/// and anything thrown by `T`s constructor.
/// If an allocation is thrown, the memory will be released directly.
template <typename JointType>
joint_array(const joint_array& other, joint_type<JointType>& j)
: joint_array(detail::get_stack(j), other)
{
}
joint_array(joint_array&&) = delete;
/// \effects Move constructs each element from `other` into the storage of the specified joint memory.
/// \throws \ref out_of_fixed_memory if the size is too big
/// and anything thrown by `T`s constructor.
/// If an allocation is thrown, the memory will be released directly.
template <typename JointType>
joint_array(joint_array&& other, joint_type<JointType>& j)
: joint_array(detail::get_stack(j), detail::move(other))
{
}
/// \effects Destroys all objects,
/// but does not release the storage.
~joint_array() noexcept
{
for (std::size_t i = 0u; i != size_; ++i)
ptr_[i].~T();
}
joint_array& operator=(const joint_array&) = delete;
joint_array& operator=(joint_array&&) = delete;
//=== accessors ===//
/// @{
/// \returns A reference to the `i`th object.
/// \requires `i < size()`.
value_type& operator[](std::size_t i) noexcept
{
WPI_MEMORY_ASSERT(i < size_);
return ptr_[i];
}
const value_type& operator[](std::size_t i) const noexcept
{
WPI_MEMORY_ASSERT(i < size_);
return ptr_[i];
}
/// @}
/// @{
/// \returns A pointer to the first object.
/// It points to contiguous memory and can be used to access the objects directly.
value_type* data() noexcept
{
return ptr_;
}
const value_type* data() const noexcept
{
return ptr_;
}
/// @}
/// @{
/// \returns A random access iterator to the first element.
iterator begin() noexcept
{
return ptr_;
}
const_iterator begin() const noexcept
{
return ptr_;
}
/// @}
/// @{
/// \returns A random access iterator one past the last element.
iterator end() noexcept
{
return ptr_ + size_;
}
const_iterator end() const noexcept
{
return ptr_ + size_;
}
/// @}
/// \returns The number of elements in the array.
std::size_t size() const noexcept
{
return size_;
}
/// \returns `true` if the array is empty, `false` otherwise.
bool empty() const noexcept
{
return size_ == 0u;
}
private:
// allocate only
struct allocate_only
{
};
joint_array(allocate_only, detail::joint_stack& stack, std::size_t size)
: ptr_(nullptr), size_(0u)
{
ptr_ = static_cast<T*>(stack.allocate(size * sizeof(T), alignof(T)));
if (!ptr_)
WPI_THROW(out_of_fixed_memory(info(), size * sizeof(T)));
}
class builder
{
public:
builder(detail::joint_stack& stack, T* ptr) noexcept
: stack_(&stack), objects_(ptr), size_(0u)
{
}
~builder() noexcept
{
for (std::size_t i = 0u; i != size_; ++i)
objects_[i].~T();
if (size_)
stack_->unwind(objects_);
}
builder(builder&&) = delete;
builder& operator=(builder&&) = delete;
template <typename... Args>
T* create(Args&&... args)
{
auto ptr = ::new (static_cast<void*>(&objects_[size_]))
T(detail::forward<Args>(args)...);
++size_;
return ptr;
}
std::size_t size() const noexcept
{
return size_;
}
std::size_t release() noexcept
{
auto res = size_;
size_ = 0u;
return res;
}
private:
detail::joint_stack* stack_;
T* objects_;
std::size_t size_;
};
joint_array(detail::joint_stack& stack, std::size_t size)
: joint_array(allocate_only{}, stack, size)
{
builder b(stack, ptr_);
for (auto i = 0u; i != size; ++i)
b.create();
size_ = b.release();
}
joint_array(detail::joint_stack& stack, std::size_t size, const value_type& value)
: joint_array(allocate_only{}, stack, size)
{
builder b(stack, ptr_);
for (auto i = 0u; i != size; ++i)
b.create(value);
size_ = b.release();
}
joint_array(detail::joint_stack& stack, std::initializer_list<value_type> ilist)
: joint_array(allocate_only{}, stack, ilist.size())
{
builder b(stack, ptr_);
for (auto& elem : ilist)
b.create(elem);
size_ = b.release();
}
joint_array(detail::joint_stack& stack, const joint_array& other)
: joint_array(allocate_only{}, stack, other.size())
{
builder b(stack, ptr_);
for (auto& elem : other)
b.create(elem);
size_ = b.release();
}
joint_array(detail::joint_stack& stack, joint_array&& other)
: joint_array(allocate_only{}, stack, other.size())
{
builder b(stack, ptr_);
for (auto& elem : other)
b.create(detail::move(elem));
size_ = b.release();
}
template <typename InIter>
joint_array(detail::joint_stack& stack, InIter begin, InIter end)
: ptr_(nullptr), size_(0u)
{
if (begin == end)
return;
ptr_ = static_cast<T*>(stack.allocate(sizeof(T), alignof(T)));
if (!ptr_)
WPI_THROW(out_of_fixed_memory(info(), sizeof(T)));
builder b(stack, ptr_);
b.create(*begin++);
for (auto last = ptr_; begin != end; ++begin)
{
// just bump stack to get more memory
if (!stack.bump(sizeof(T)))
WPI_THROW(out_of_fixed_memory(info(), b.size() * sizeof(T)));
auto cur = b.create(*begin);
WPI_MEMORY_ASSERT(last + 1 == cur);
last = cur;
}
size_ = b.release();
}
allocator_info info() const noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::joint_array", this};
}
value_type* ptr_;
std::size_t size_;
};
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_JOINT_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,71 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_MALLOC_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_MALLOC_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::malloc_allocator.
/// \note Only available on a hosted implementation.
#include "config.hpp"
#if !WPI_HOSTED_IMPLEMENTATION
#error "This header is only available for a hosted implementation."
#endif
#include <cstdlib>
#include <memory>
#include "detail/lowlevel_allocator.hpp"
#if WPI_MEMORY_EXTERN_TEMPLATE
#include "allocator_traits.hpp"
#endif
namespace wpi
{
namespace memory
{
struct allocator_info;
namespace detail
{
struct malloc_allocator_impl
{
static allocator_info info() noexcept;
static void* allocate(std::size_t size, std::size_t) noexcept
{
return std::malloc(size);
}
static void deallocate(void* ptr, std::size_t, std::size_t) noexcept
{
std::free(ptr);
}
static std::size_t max_node_size() noexcept
{
return std::allocator_traits<std::allocator<char>>::max_size({});
}
};
WPI_MEMORY_LL_ALLOCATOR_LEAK_CHECKER(malloc_allocator_impl,
malloc_alloator_leak_checker)
} // namespace detail
/// A stateless \concept{concept_rawallocator,RawAllocator} that allocates memory using <tt>std::malloc()</tt>.
/// It throws \ref out_of_memory when the allocation fails.
/// \ingroup allocator
using malloc_allocator =
WPI_IMPL_DEFINED(detail::lowlevel_allocator<detail::malloc_allocator_impl>);
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class detail::lowlevel_allocator<detail::malloc_allocator_impl>;
extern template class allocator_traits<malloc_allocator>;
#endif
} // namespace memory
} // namespace wpi
#endif //WPI_MEMORY_MALLOC_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,693 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_MEMORY_ARENA_HPP_INCLUDED
#define WPI_MEMORY_MEMORY_ARENA_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::memory_arena and related functionality regarding \concept{concept_blockallocator,BlockAllocators}.
#include <type_traits>
#include "detail/debug_helpers.hpp"
#include "detail/assert.hpp"
#include "detail/utility.hpp"
#include "allocator_traits.hpp"
#include "config.hpp"
#include "default_allocator.hpp"
#include "error.hpp"
namespace wpi
{
namespace memory
{
/// A memory block.
/// It is defined by its starting address and size.
/// \ingroup core
struct memory_block
{
void* memory; ///< The address of the memory block (might be \c nullptr).
std::size_t size; ///< The size of the memory block (might be \c 0).
/// \effects Creates an invalid memory block with starting address \c nullptr and size \c 0.
memory_block() noexcept : memory_block(nullptr, std::size_t(0)) {}
/// \effects Creates a memory block from a given starting address and size.
memory_block(void* mem, std::size_t s) noexcept : memory(mem), size(s) {}
/// \effects Creates a memory block from a [begin,end) range.
memory_block(void* begin, void* end) noexcept
: memory_block(begin, static_cast<std::size_t>(static_cast<char*>(end)
- static_cast<char*>(begin)))
{
}
/// \returns Whether or not a pointer is inside the memory.
bool contains(const void* address) const noexcept
{
auto mem = static_cast<const char*>(memory);
auto addr = static_cast<const char*>(address);
return addr >= mem && addr < mem + size;
}
};
namespace detail
{
template <class BlockAllocator>
std::true_type is_block_allocator_impl(
int,
WPI_SFINAE(std::declval<memory_block&>() =
std::declval<BlockAllocator&>().allocate_block()),
WPI_SFINAE(std::declval<std::size_t&>() =
std::declval<BlockAllocator&>().next_block_size()),
WPI_SFINAE(std::declval<BlockAllocator>().deallocate_block(memory_block{})));
template <typename T>
std::false_type is_block_allocator_impl(short);
} // namespace detail
/// Traits that check whether a type models concept \concept{concept_blockallocator,BlockAllocator}.
/// \ingroup core
template <typename T>
struct is_block_allocator : decltype(detail::is_block_allocator_impl<T>(0))
{
};
#if !defined(DOXYGEN)
template <class BlockAllocator, bool Cached = true>
class memory_arena;
#endif
/// @{
/// Controls the caching of \ref memory_arena.
/// By default, deallocated blocks are put onto a cache, so they can be reused later;
/// this tag value enable/disable it..<br>
/// This can be useful, e.g. if there will never be blocks available for deallocation.
/// The (tiny) overhead for the cache can then be disabled.
/// An example is \ref memory_pool.
/// \ingroup core
constexpr bool cached_arena = true;
constexpr bool uncached_arena = false;
/// @}
namespace detail
{
// stores memory block in an intrusive linked list and allows LIFO access
class memory_block_stack
{
public:
memory_block_stack() noexcept : head_(nullptr) {}
~memory_block_stack() noexcept {}
memory_block_stack(memory_block_stack&& other) noexcept : head_(other.head_)
{
other.head_ = nullptr;
}
memory_block_stack& operator=(memory_block_stack&& other) noexcept
{
memory_block_stack tmp(detail::move(other));
swap(*this, tmp);
return *this;
}
friend void swap(memory_block_stack& a, memory_block_stack& b) noexcept
{
detail::adl_swap(a.head_, b.head_);
}
// the raw allocated block returned from an allocator
using allocated_mb = memory_block;
// the inserted block slightly smaller to allow for the fixup value
using inserted_mb = memory_block;
// how much an inserted block is smaller
static constexpr std::size_t implementation_offset() noexcept
{
// node size rounded up to the next multiple of max_alignment.
return (sizeof(node) / max_alignment + (sizeof(node) % max_alignment != 0))
* max_alignment;
}
// pushes a memory block
void push(allocated_mb block) noexcept;
// pops a memory block and returns the original block
allocated_mb pop() noexcept;
// steals the top block from another stack
void steal_top(memory_block_stack& other) noexcept;
// returns the last pushed() inserted memory block
inserted_mb top() const noexcept
{
WPI_MEMORY_ASSERT(head_);
auto mem = static_cast<void*>(head_);
return {static_cast<char*>(mem) + implementation_offset(), head_->usable_size};
}
bool empty() const noexcept
{
return head_ == nullptr;
}
bool owns(const void* ptr) const noexcept;
// O(n) size
std::size_t size() const noexcept;
private:
struct node
{
node* prev;
std::size_t usable_size;
node(node* p, std::size_t size) noexcept : prev(p), usable_size(size) {}
};
node* head_;
};
template <bool Cached>
class memory_arena_cache;
template <>
class memory_arena_cache<cached_arena>
{
protected:
bool cache_empty() const noexcept
{
return cached_.empty();
}
std::size_t cache_size() const noexcept
{
return cached_.size();
}
std::size_t cached_block_size() const noexcept
{
return cached_.top().size;
}
bool take_from_cache(detail::memory_block_stack& used) noexcept
{
if (cached_.empty())
return false;
used.steal_top(cached_);
return true;
}
template <class BlockAllocator>
void do_deallocate_block(BlockAllocator&, detail::memory_block_stack& used) noexcept
{
cached_.steal_top(used);
}
template <class BlockAllocator>
void do_shrink_to_fit(BlockAllocator& alloc) noexcept
{
detail::memory_block_stack to_dealloc;
// pop from cache and push to temporary stack
// this revers order
while (!cached_.empty())
to_dealloc.steal_top(cached_);
// now dealloc everything
while (!to_dealloc.empty())
alloc.deallocate_block(to_dealloc.pop());
}
private:
detail::memory_block_stack cached_;
};
template <>
class memory_arena_cache<uncached_arena>
{
protected:
bool cache_empty() const noexcept
{
return true;
}
std::size_t cache_size() const noexcept
{
return 0u;
}
std::size_t cached_block_size() const noexcept
{
return 0u;
}
bool take_from_cache(detail::memory_block_stack&) noexcept
{
return false;
}
template <class BlockAllocator>
void do_deallocate_block(BlockAllocator& alloc,
detail::memory_block_stack& used) noexcept
{
alloc.deallocate_block(used.pop());
}
template <class BlockAllocator>
void do_shrink_to_fit(BlockAllocator&) noexcept
{
}
};
} // namespace detail
/// A memory arena that manages huge memory blocks for a higher-level allocator.
/// Some allocators like \ref memory_stack work on huge memory blocks,
/// this class manages them fro those allocators.
/// It uses a \concept{concept_blockallocator,BlockAllocator} for the allocation of those blocks.
/// The memory blocks in use are put onto a stack like structure, deallocation will pop from the top,
/// so it is only possible to deallocate the last allocated block of the arena.
/// By default, blocks are not really deallocated but stored in a cache.
/// This can be disabled with the second template parameter,
/// passing it \ref uncached_arena (or \c false) disables it,
/// \ref cached_arena (or \c true) enables it explicitly.
/// \ingroup core
template <class BlockAllocator, bool Cached /* = true */>
class memory_arena : WPI_EBO(BlockAllocator),
WPI_EBO(detail::memory_arena_cache<Cached>)
{
static_assert(is_block_allocator<BlockAllocator>::value,
"BlockAllocator is not a BlockAllocator!");
using cache = detail::memory_arena_cache<Cached>;
public:
using allocator_type = BlockAllocator;
using is_cached = std::integral_constant<bool, Cached>;
/// \returns The minimum block size required for an arena containing the given amount of memory.
/// If an arena is created with the result of `min_block_size(n)`, the resulting capacity will be exactly `n`.
/// \requires `byte_size` must be a positive number.
static constexpr std::size_t min_block_size(std::size_t byte_size) noexcept
{
return detail::memory_block_stack::implementation_offset() + byte_size;
}
/// \effects Creates it by giving it the size and other arguments for the \concept{concept_blockallocator,BlockAllocator}.
/// It forwards these arguments to its constructor.
/// \requires \c block_size must be greater than \c min_block_size(0) and other requirements depending on the \concept{concept_blockallocator,BlockAllocator}.
/// \throws Anything thrown by the constructor of the \c BlockAllocator.
template <typename... Args>
explicit memory_arena(std::size_t block_size, Args&&... args)
: allocator_type(block_size, detail::forward<Args>(args)...)
{
WPI_MEMORY_ASSERT(block_size > min_block_size(0));
}
/// \effects Deallocates all memory blocks that where requested back to the \concept{concept_blockallocator,BlockAllocator}.
~memory_arena() noexcept
{
// clear cache
shrink_to_fit();
// now deallocate everything
while (!used_.empty())
allocator_type::deallocate_block(used_.pop());
}
/// @{
/// \effects Moves the arena.
/// The new arena takes ownership over all the memory blocks from the other arena object,
/// which is empty after that.
/// This does not invalidate any memory blocks.
memory_arena(memory_arena&& other) noexcept
: allocator_type(detail::move(other)),
cache(detail::move(other)),
used_(detail::move(other.used_))
{
}
memory_arena& operator=(memory_arena&& other) noexcept
{
memory_arena tmp(detail::move(other));
swap(*this, tmp);
return *this;
}
/// @}
/// \effects Swaps to memory arena objects.
/// This does not invalidate any memory blocks.
friend void swap(memory_arena& a, memory_arena& b) noexcept
{
detail::adl_swap(static_cast<allocator_type&>(a), static_cast<allocator_type&>(b));
detail::adl_swap(static_cast<cache&>(a), static_cast<cache&>(b));
detail::adl_swap(a.used_, b.used_);
}
/// \effects Allocates a new memory block.
/// It first uses a cache of previously deallocated blocks, if caching is enabled,
/// if it is empty, allocates a new one.
/// \returns The new \ref memory_block.
/// \throws Anything thrown by the \concept{concept_blockallocator,BlockAllocator} allocation function.
memory_block allocate_block()
{
if (!this->take_from_cache(used_))
used_.push(allocator_type::allocate_block());
auto block = used_.top();
detail::debug_fill_internal(block.memory, block.size, false);
return block;
}
/// \returns The current memory block.
/// This is the memory block that will be deallocated by the next call to \ref deallocate_block().
memory_block current_block() const noexcept
{
return used_.top();
}
/// \effects Deallocates the current memory block.
/// The current memory block is the block on top of the stack of blocks.
/// If caching is enabled, it does not really deallocate it but puts it onto a cache for later use,
/// use \ref shrink_to_fit() to purge that cache.
void deallocate_block() noexcept
{
auto block = used_.top();
detail::debug_fill_internal(block.memory, block.size, true);
this->do_deallocate_block(get_allocator(), used_);
}
/// \returns If `ptr` is in memory owned by the arena.
bool owns(const void* ptr) const noexcept
{
return used_.owns(ptr);
}
/// \effects Purges the cache of unused memory blocks by returning them.
/// The memory blocks will be deallocated in reversed order of allocation.
/// Does nothing if caching is disabled.
void shrink_to_fit() noexcept
{
this->do_shrink_to_fit(get_allocator());
}
/// \returns The capacity of the arena, i.e. how many blocks are used and cached.
std::size_t capacity() const noexcept
{
return size() + cache_size();
}
/// \returns The size of the cache, i.e. how many blocks can be allocated without allocation.
std::size_t cache_size() const noexcept
{
return cache::cache_size();
}
/// \returns The size of the arena, i.e. how many blocks are in use.
/// It is always smaller or equal to the \ref capacity().
std::size_t size() const noexcept
{
return used_.size();
}
/// \returns The size of the next memory block,
/// i.e. of the next call to \ref allocate_block().
/// If there are blocks in the cache, returns size of the next one.
/// Otherwise forwards to the \concept{concept_blockallocator,BlockAllocator} and subtracts an implementation offset.
std::size_t next_block_size() const noexcept
{
return this->cache_empty() ?
allocator_type::next_block_size()
- detail::memory_block_stack::implementation_offset() :
this->cached_block_size();
}
/// \returns A reference of the \concept{concept_blockallocator,BlockAllocator} object.
/// \requires It is undefined behavior to move this allocator out into another object.
allocator_type& get_allocator() noexcept
{
return *this;
}
private:
detail::memory_block_stack used_;
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class memory_arena<static_block_allocator, true>;
extern template class memory_arena<static_block_allocator, false>;
extern template class memory_arena<virtual_block_allocator, true>;
extern template class memory_arena<virtual_block_allocator, false>;
#endif
/// A \concept{concept_blockallocator,BlockAllocator} that uses a given \concept{concept_rawallocator,RawAllocator} for allocating the blocks.
/// It calls the \c allocate_array() function with a node of size \c 1 and maximum alignment on the used allocator for the block allocation.
/// The size of the next memory block will grow by a given factor after each allocation,
/// allowing an amortized constant allocation time in the higher level allocator.
/// The factor can be given as rational in the template parameter, default is \c 2.
/// \ingroup adapter
template <class RawAllocator = default_allocator, unsigned Num = 2, unsigned Den = 1>
class growing_block_allocator
: WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
{
static_assert(float(Num) / Den >= 1.0, "invalid growth factor");
using traits = allocator_traits<RawAllocator>;
public:
using allocator_type = typename traits::allocator_type;
/// \effects Creates it by giving it the initial block size, the allocator object and the growth factor.
/// By default, it uses a default-constructed allocator object and a growth factor of \c 2.
/// \requires \c block_size must be greater than 0.
explicit growing_block_allocator(std::size_t block_size,
allocator_type alloc = allocator_type()) noexcept
: allocator_type(detail::move(alloc)), block_size_(block_size)
{
}
/// \effects Allocates a new memory block and increases the block size for the next allocation.
/// \returns The new \ref memory_block.
/// \throws Anything thrown by the \c allocate_array() function of the \concept{concept_rawallocator,RawAllocator}.
memory_block allocate_block()
{
auto memory =
traits::allocate_array(get_allocator(), block_size_, 1, detail::max_alignment);
memory_block block(memory, block_size_);
block_size_ = grow_block_size(block_size_);
return block;
}
/// \effects Deallocates a previously allocated memory block.
/// This does not decrease the block size.
/// \requires \c block must be previously returned by a call to \ref allocate_block().
void deallocate_block(memory_block block) noexcept
{
traits::deallocate_array(get_allocator(), block.memory, block.size, 1,
detail::max_alignment);
}
/// \returns The size of the memory block returned by the next call to \ref allocate_block().
std::size_t next_block_size() const noexcept
{
return block_size_;
}
/// \returns A reference to the used \concept{concept_rawallocator,RawAllocator} object.
allocator_type& get_allocator() noexcept
{
return *this;
}
/// \returns The growth factor.
static float growth_factor() noexcept
{
static constexpr auto factor = float(Num) / Den;
return factor;
}
static std::size_t grow_block_size(std::size_t block_size) noexcept
{
return block_size * Num / Den;
}
private:
std::size_t block_size_;
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class growing_block_allocator<>;
extern template class memory_arena<growing_block_allocator<>, true>;
extern template class memory_arena<growing_block_allocator<>, false>;
#endif
/// A \concept{concept_blockallocator,BlockAllocator} that allows only one block allocation.
/// It can be used to prevent higher-level allocators from expanding.
/// The one block allocation is performed through the \c allocate_array() function of the given \concept{concept_rawallocator,RawAllocator}.
/// \ingroup adapter
template <class RawAllocator = default_allocator>
class fixed_block_allocator : WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
{
using traits = allocator_traits<RawAllocator>;
public:
using allocator_type = typename traits::allocator_type;
/// \effects Creates it by passing it the size of the block and the allocator object.
/// \requires \c block_size must be greater than 0,
explicit fixed_block_allocator(std::size_t block_size,
allocator_type alloc = allocator_type()) noexcept
: allocator_type(detail::move(alloc)), block_size_(block_size)
{
}
/// \effects Allocates a new memory block or throws an exception if there was already one allocation.
/// \returns The new \ref memory_block.
/// \throws Anything thrown by the \c allocate_array() function of the \concept{concept_rawallocator,RawAllocator} or \ref out_of_memory if this is not the first call.
memory_block allocate_block()
{
if (block_size_)
{
auto mem = traits::allocate_array(get_allocator(), block_size_, 1,
detail::max_alignment);
memory_block block(mem, block_size_);
block_size_ = 0u;
return block;
}
WPI_THROW(out_of_fixed_memory(info(), block_size_));
}
/// \effects Deallocates the previously allocated memory block.
/// It also resets and allows a new call again.
void deallocate_block(memory_block block) noexcept
{
detail::debug_check_pointer([&] { return block_size_ == 0u; }, info(),
block.memory);
traits::deallocate_array(get_allocator(), block.memory, block.size, 1,
detail::max_alignment);
block_size_ = block.size;
}
/// \returns The size of the next block which is either the initial size or \c 0.
std::size_t next_block_size() const noexcept
{
return block_size_;
}
/// \returns A reference to the used \concept{concept_rawallocator,RawAllocator} object.
allocator_type& get_allocator() noexcept
{
return *this;
}
private:
allocator_info info() noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::fixed_block_allocator", this};
}
std::size_t block_size_;
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class fixed_block_allocator<>;
extern template class memory_arena<fixed_block_allocator<>, true>;
extern template class memory_arena<fixed_block_allocator<>, false>;
#endif
namespace detail
{
template <class RawAlloc>
using default_block_wrapper = growing_block_allocator<RawAlloc>;
template <template <class...> class Wrapper, class BlockAllocator, typename... Args>
BlockAllocator make_block_allocator(std::true_type, std::size_t block_size,
Args&&... args)
{
return BlockAllocator(block_size, detail::forward<Args>(args)...);
}
template <template <class...> class Wrapper, class RawAlloc>
auto make_block_allocator(std::false_type, std::size_t block_size,
RawAlloc alloc = RawAlloc()) -> Wrapper<RawAlloc>
{
return Wrapper<RawAlloc>(block_size, detail::move(alloc));
}
} // namespace detail
/// Takes either a \concept{concept_blockallocator,BlockAllocator} or a \concept{concept_rawallocator,RawAllocator}.
/// In the first case simply aliases the type unchanged, in the second to \ref growing_block_allocator (or the template in `BlockAllocator`) with the \concept{concept_rawallocator,RawAllocator}.
/// Using this allows passing normal \concept{concept_rawallocator,RawAllocators} as \concept{concept_blockallocator,BlockAllocators}.
/// \ingroup core
template <class BlockOrRawAllocator,
template <typename...> class BlockAllocator = detail::default_block_wrapper>
using make_block_allocator_t = WPI_IMPL_DEFINED(
typename std::conditional<is_block_allocator<BlockOrRawAllocator>::value,
BlockOrRawAllocator,
BlockAllocator<BlockOrRawAllocator>>::type);
/// @{
/// Helper function make a \concept{concept_blockallocator,BlockAllocator}.
/// \returns A \concept{concept_blockallocator,BlockAllocator} of the given type created with the given arguments.
/// \requires Same requirements as the constructor.
/// \ingroup core
template <class BlockOrRawAllocator, typename... Args>
make_block_allocator_t<BlockOrRawAllocator> make_block_allocator(std::size_t block_size,
Args&&... args)
{
return detail::make_block_allocator<
detail::default_block_wrapper,
BlockOrRawAllocator>(is_block_allocator<BlockOrRawAllocator>{}, block_size,
detail::forward<Args>(args)...);
}
template <template <class...> class BlockAllocator, class BlockOrRawAllocator,
typename... Args>
make_block_allocator_t<BlockOrRawAllocator, BlockAllocator> make_block_allocator(
std::size_t block_size, Args&&... args)
{
return detail::make_block_allocator<
BlockAllocator, BlockOrRawAllocator>(is_block_allocator<BlockOrRawAllocator>{},
block_size, detail::forward<Args>(args)...);
}
/// @}
namespace literals
{
/// Syntax sugar to express sizes with unit prefixes.
/// \returns The number of bytes `value` is in the given unit.
/// \ingroup core
/// @{
constexpr std::size_t operator"" _KiB(unsigned long long value) noexcept
{
return std::size_t(value * 1024);
}
constexpr std::size_t operator"" _KB(unsigned long long value) noexcept
{
return std::size_t(value * 1000);
}
constexpr std::size_t operator"" _MiB(unsigned long long value) noexcept
{
return std::size_t(value * 1024 * 1024);
}
constexpr std::size_t operator"" _MB(unsigned long long value) noexcept
{
return std::size_t(value * 1000 * 1000);
}
constexpr std::size_t operator"" _GiB(unsigned long long value) noexcept
{
return std::size_t(value * 1024 * 1024 * 1024);
}
constexpr std::size_t operator"" _GB(unsigned long long value) noexcept
{
return std::size_t(value * 1000 * 1000 * 1000);
}
} // namespace literals
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_MEMORY_ARENA_HPP_INCLUDED

View File

@@ -0,0 +1,433 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_MEMORY_POOL_HPP_INCLUDED
#define WPI_MEMORY_MEMORY_POOL_HPP_INCLUDED
// Inform that wpi::memory::memory_pool::min_block_size API is available
#define WPI_MEMORY_MEMORY_POOL_HAS_MIN_BLOCK_SIZE
/// \file
/// Class \ref wpi::memory::memory_pool and its \ref wpi::memory::allocator_traits specialization.
#include <type_traits>
#include "detail/align.hpp"
#include "detail/debug_helpers.hpp"
#include "detail/assert.hpp"
#include "config.hpp"
#include "error.hpp"
#include "memory_arena.hpp"
#include "memory_pool_type.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
struct memory_pool_leak_handler
{
void operator()(std::ptrdiff_t amount);
};
} // namespace detail
/// A stateful \concept{concept_rawallocator,RawAllocator} that manages \concept{concept_node,nodes} of fixed size.
/// It uses a \ref memory_arena with a given \c BlockOrRawAllocator defaulting to \ref growing_block_allocator,
/// subdivides them in small nodes of given size and puts them onto a free list.
/// Allocation and deallocation simply remove or add nodes from this list and are thus fast.
/// The way the list is maintained can be controlled via the \c PoolType
/// which is either \ref node_pool, \ref array_pool or \ref small_node_pool.<br>
/// This kind of allocator is ideal for fixed size allocations and deallocations in any order,
/// for example in a node based container like \c std::list.
/// It is not so good for different allocation sizes and has some drawbacks for arrays
/// as described in \ref memory_pool_type.hpp.
/// \ingroup allocator
template <typename PoolType = node_pool, class BlockOrRawAllocator = default_allocator>
class memory_pool
: WPI_EBO(detail::default_leak_checker<detail::memory_pool_leak_handler>)
{
using free_list = typename PoolType::type;
using leak_checker = detail::default_leak_checker<detail::memory_pool_leak_handler>;
public:
using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
using pool_type = PoolType;
static constexpr std::size_t min_node_size =
WPI_IMPL_DEFINED(free_list::min_element_size);
/// \returns The minimum block size required for certain number of \concept{concept_node,node}.
/// \requires \c node_size must be a valid \concept{concept_node,node size}
/// and \c number_of_nodes must be a non-zero value.
/// \note MSVC's implementation of \c std::list for example is never empty and always allocates proxy nodes.
/// To get enough memory for \c N elements of a list, \c number_of_nodes needs to include the proxy count in addition to \c N.
static constexpr std::size_t min_block_size(std::size_t node_size,
std::size_t number_of_nodes) noexcept
{
return detail::memory_block_stack::implementation_offset()
+ free_list::min_block_size(node_size, number_of_nodes);
}
/// \effects Creates it by specifying the size each \concept{concept_node,node} will have,
/// the initial block size for the arena and other constructor arguments for the \concept{concept_blockallocator,BlockAllocator}.
/// If the \c node_size is less than the \c min_node_size, the \c min_node_size will be the actual node size.
/// It will allocate an initial memory block with given size from the \concept{concept_blockallocator,BlockAllocator}
/// and puts it onto the free list.
/// \requires \c node_size must be a valid \concept{concept_node,node size}
/// and \c block_size must be at least \c min_block_size(node_size, 1).
template <typename... Args>
memory_pool(std::size_t node_size, std::size_t block_size, Args&&... args)
: arena_(block_size, detail::forward<Args>(args)...), free_list_(node_size)
{
allocate_block();
}
/// \effects Destroys the \ref memory_pool by returning all memory blocks,
/// regardless of properly deallocated back to the \concept{concept_blockallocator,BlockAllocator}.
~memory_pool() noexcept {}
/// @{
/// \effects Moving a \ref memory_pool object transfers ownership over the free list,
/// i.e. the moved from pool is completely empty and the new one has all its memory.
/// That means that it is not allowed to call \ref deallocate_node() on a moved-from allocator
/// even when passing it memory that was previously allocated by this object.
memory_pool(memory_pool&& other) noexcept
: leak_checker(detail::move(other)),
arena_(detail::move(other.arena_)),
free_list_(detail::move(other.free_list_))
{
}
memory_pool& operator=(memory_pool&& other) noexcept
{
leak_checker::operator=(detail::move(other));
arena_ = detail::move(other.arena_);
free_list_ = detail::move(other.free_list_);
return *this;
}
/// @}
/// \effects Allocates a single \concept{concept_node,node} by removing it from the free list.
/// If the free list is empty, a new memory block will be allocated from the arena and put onto it.
/// The new block size will be \ref next_capacity() big.
/// \returns A node of size \ref node_size() suitable aligned,
/// i.e. suitable for any type where <tt>sizeof(T) < node_size()</tt>.
/// \throws Anything thrown by the used \concept{concept_blockallocator,BlockAllocator}'s allocation function if a growth is needed.
void* allocate_node()
{
if (free_list_.empty())
allocate_block();
WPI_MEMORY_ASSERT(!free_list_.empty());
return free_list_.allocate();
}
/// \effects Allocates a single \concept{concept_node,node} similar to \ref allocate_node().
/// But if the free list is empty, a new block will *not* be allocated.
/// \returns A suitable aligned node of size \ref node_size() or `nullptr`.
void* try_allocate_node() noexcept
{
return free_list_.empty() ? nullptr : free_list_.allocate();
}
/// \effects Allocates an \concept{concept_array,array} of nodes by searching for \c n continuous nodes on the list and removing them.
/// Depending on the \c PoolType this can be a slow operation or not allowed at all.
/// This can sometimes lead to a growth, even if technically there is enough continuous memory on the free list.
/// \returns An array of \c n nodes of size \ref node_size() suitable aligned.
/// \throws Anything thrown by the used \concept{concept_blockallocator,BlockAllocator}'s allocation function if a growth is needed,
/// or \ref bad_array_size if <tt>n * node_size()</tt> is too big.
/// \requires \c n must be valid \concept{concept_array,array count}.
void* allocate_array(std::size_t n)
{
detail::check_allocation_size<bad_array_size>(
n * node_size(), [&] { return pool_type::value ? next_capacity() : 0; },
info());
return allocate_array(n, node_size());
}
/// \effects Allocates an \concept{concept_array,array} of nodes similar to \ref allocate_array().
/// But it will never allocate a new memory block.
/// \returns An array of \c n nodes of size \ref node_size() suitable aligned
/// or `nullptr`.
void* try_allocate_array(std::size_t n) noexcept
{
return try_allocate_array(n, node_size());
}
/// \effects Deallocates a single \concept{concept_node,node} by putting it back onto the free list.
/// \requires \c ptr must be a result from a previous call to \ref allocate_node() on the same free list,
/// i.e. either this allocator object or a new object created by moving this to it.
void deallocate_node(void* ptr) noexcept
{
free_list_.deallocate(ptr);
}
/// \effects Deallocates a single \concept{concept_node,node} but it does not be a result of a previous call to \ref allocate_node().
/// \returns `true` if the node could be deallocated, `false` otherwise.
/// \note Some free list implementations can deallocate any memory,
/// doesn't matter where it is coming from.
bool try_deallocate_node(void* ptr) noexcept
{
if (!arena_.owns(ptr))
return false;
free_list_.deallocate(ptr);
return true;
}
/// \effects Deallocates an \concept{concept_array,array} by putting it back onto the free list.
/// \requires \c ptr must be a result from a previous call to \ref allocate_array() with the same \c n on the same free list,
/// i.e. either this allocator object or a new object created by moving this to it.
void deallocate_array(void* ptr, std::size_t n) noexcept
{
WPI_MEMORY_ASSERT_MSG(pool_type::value, "does not support array allocations");
free_list_.deallocate(ptr, n * node_size());
}
/// \effects Deallocates an \concept{concept_array,array} but it does not be a result of a previous call to \ref allocate_array().
/// \returns `true` if the node could be deallocated, `false` otherwise.
/// \note Some free list implementations can deallocate any memory,
/// doesn't matter where it is coming from.
bool try_deallocate_array(void* ptr, std::size_t n) noexcept
{
return try_deallocate_array(ptr, n, node_size());
}
/// \returns The size of each \concept{concept_node,node} in the pool,
/// this is either the same value as in the constructor or \c min_node_size if the value was too small.
std::size_t node_size() const noexcept
{
return free_list_.node_size();
}
/// \effects Returns the total amount of bytes remaining on the free list.
/// Divide it by \ref node_size() to get the number of nodes that can be allocated without growing the arena.
/// \note Array allocations may lead to a growth even if the capacity_left left is big enough.
std::size_t capacity_left() const noexcept
{
return free_list_.capacity() * node_size();
}
/// \returns The size of the next memory block after the free list gets empty and the arena grows.
/// \ref capacity_left() will increase by this amount.
/// \note Due to fence memory in debug mode this cannot be just divided by the \ref node_size() to get the number of nodes.
std::size_t next_capacity() const noexcept
{
return free_list_.usable_size(arena_.next_block_size());
}
/// \returns A reference to the \concept{concept_blockallocator,BlockAllocator} used for managing the arena.
/// \requires It is undefined behavior to move this allocator out into another object.
allocator_type& get_allocator() noexcept
{
return arena_.get_allocator();
}
private:
allocator_info info() const noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::memory_pool", this};
}
void allocate_block()
{
auto mem = arena_.allocate_block();
free_list_.insert(static_cast<char*>(mem.memory), mem.size);
}
void* allocate_array(std::size_t n, std::size_t node_size)
{
auto mem = free_list_.empty() ? nullptr : free_list_.allocate(n * node_size);
if (!mem)
{
allocate_block();
mem = free_list_.allocate(n * node_size);
if (!mem)
WPI_THROW(bad_array_size(info(), n * node_size, capacity_left()));
}
return mem;
}
void* try_allocate_array(std::size_t n, std::size_t node_size) noexcept
{
return !pool_type::value || free_list_.empty() ? nullptr :
free_list_.allocate(n * node_size);
}
bool try_deallocate_array(void* ptr, std::size_t n, std::size_t node_size) noexcept
{
if (!pool_type::value || !arena_.owns(ptr))
return false;
free_list_.deallocate(ptr, n * node_size);
return true;
}
memory_arena<allocator_type, false> arena_;
free_list free_list_;
friend allocator_traits<memory_pool<PoolType, BlockOrRawAllocator>>;
friend composable_allocator_traits<memory_pool<PoolType, BlockOrRawAllocator>>;
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class memory_pool<node_pool>;
extern template class memory_pool<array_pool>;
extern template class memory_pool<small_node_pool>;
#endif
template <class Type, class Alloc>
constexpr std::size_t memory_pool<Type, Alloc>::min_node_size;
/// Specialization of the \ref allocator_traits for \ref memory_pool classes.
/// \note It is not allowed to mix calls through the specialization and through the member functions,
/// i.e. \ref memory_pool::allocate_node() and this \c allocate_node().
/// \ingroup allocator
template <typename PoolType, class ImplRawAllocator>
class allocator_traits<memory_pool<PoolType, ImplRawAllocator>>
{
public:
using allocator_type = memory_pool<PoolType, ImplRawAllocator>;
using is_stateful = std::true_type;
/// \returns The result of \ref memory_pool::allocate_node().
/// \throws Anything thrown by the pool allocation function
/// or a \ref bad_allocation_size exception.
static void* allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment)
{
detail::check_allocation_size<bad_node_size>(size, max_node_size(state),
state.info());
detail::check_allocation_size<bad_alignment>(
alignment, [&] { return max_alignment(state); }, state.info());
auto mem = state.allocate_node();
state.on_allocate(size);
return mem;
}
/// \effects Forwards to \ref memory_pool::allocate_array()
/// with the number of nodes adjusted to be the minimum,
/// i.e. when the \c size is less than the \ref memory_pool::node_size().
/// \returns A \concept{concept_array,array} with specified properties.
/// \requires The \ref memory_pool has to support array allocations.
/// \throws Anything thrown by the pool allocation function.
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
std::size_t alignment)
{
detail::check_allocation_size<bad_node_size>(size, max_node_size(state),
state.info());
detail::check_allocation_size<bad_alignment>(
alignment, [&] { return max_alignment(state); }, state.info());
detail::check_allocation_size<bad_array_size>(count * size, max_array_size(state),
state.info());
auto mem = state.allocate_array(count, size);
state.on_allocate(count * size);
return mem;
}
/// \effects Just forwards to \ref memory_pool::deallocate_node().
static void deallocate_node(allocator_type& state, void* node, std::size_t size,
std::size_t) noexcept
{
state.deallocate_node(node);
state.on_deallocate(size);
}
/// \effects Forwards to \ref memory_pool::deallocate_array() with the same size adjustment.
static void deallocate_array(allocator_type& state, void* array, std::size_t count,
std::size_t size, std::size_t) noexcept
{
state.free_list_.deallocate(array, count * size);
state.on_deallocate(count * size);
}
/// \returns The maximum size of each node which is \ref memory_pool::node_size().
static std::size_t max_node_size(const allocator_type& state) noexcept
{
return state.node_size();
}
/// \returns An upper bound on the maximum array size which is \ref memory_pool::next_capacity().
static std::size_t max_array_size(const allocator_type& state) noexcept
{
return state.next_capacity();
}
/// \returns The maximum alignment which is the next bigger power of two if less than \c alignof(std::max_align_t)
/// or the maximum alignment itself otherwise.
static std::size_t max_alignment(const allocator_type& state) noexcept
{
return state.free_list_.alignment();
}
};
/// Specialization of the \ref composable_allocator_traits for \ref memory_pool classes.
/// \ingroup allocator
template <typename PoolType, class BlockOrRawAllocator>
class composable_allocator_traits<memory_pool<PoolType, BlockOrRawAllocator>>
{
using traits = allocator_traits<memory_pool<PoolType, BlockOrRawAllocator>>;
public:
using allocator_type = memory_pool<PoolType, BlockOrRawAllocator>;
/// \returns The result of \ref memory_pool::try_allocate_node()
/// or `nullptr` if the allocation size was too big.
static void* try_allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment) noexcept
{
if (size > traits::max_node_size(state) || alignment > traits::max_alignment(state))
return nullptr;
return state.try_allocate_node();
}
/// \effects Forwards to \ref memory_pool::try_allocate_array()
/// with the number of nodes adjusted to be the minimum,
/// if the \c size is less than the \ref memory_pool::node_size().
/// \returns A \concept{concept_array,array} with specified properties
/// or `nullptr` if it was unable to allocate.
static void* try_allocate_array(allocator_type& state, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
if (size > traits::max_node_size(state)
|| count * size > traits::max_array_size(state)
|| alignment > traits::max_alignment(state))
return nullptr;
return state.try_allocate_array(count, size);
}
/// \effects Just forwards to \ref memory_pool::try_deallocate_node().
/// \returns Whether the deallocation was successful.
static bool try_deallocate_node(allocator_type& state, void* node, std::size_t size,
std::size_t alignment) noexcept
{
if (size > traits::max_node_size(state) || alignment > traits::max_alignment(state))
return false;
return state.try_deallocate_node(node);
}
/// \effects Forwards to \ref memory_pool::deallocate_array() with the same size adjustment.
/// \returns Whether the deallocation was successful.
static bool try_deallocate_array(allocator_type& state, void* array, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
if (size > traits::max_node_size(state)
|| count * size > traits::max_array_size(state)
|| alignment > traits::max_alignment(state))
return false;
return state.try_deallocate_array(array, count, size);
}
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class allocator_traits<memory_pool<node_pool>>;
extern template class allocator_traits<memory_pool<array_pool>>;
extern template class allocator_traits<memory_pool<small_node_pool>>;
extern template class composable_allocator_traits<memory_pool<node_pool>>;
extern template class composable_allocator_traits<memory_pool<array_pool>>;
extern template class composable_allocator_traits<memory_pool<small_node_pool>>;
#endif
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_MEMORY_POOL_HPP_INCLUDED

View File

@@ -0,0 +1,569 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_MEMORY_POOL_COLLECTION_HPP_INCLUDED
#define WPI_MEMORY_MEMORY_POOL_COLLECTION_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::memory_pool_collection and related classes.
#include <type_traits>
#include "detail/align.hpp"
#include "detail/assert.hpp"
#include "detail/memory_stack.hpp"
#include "detail/free_list_array.hpp"
#include "config.hpp"
#include "debugging.hpp"
#include "error.hpp"
#include "memory_arena.hpp"
#include "memory_pool_type.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
struct memory_pool_collection_leak_handler
{
void operator()(std::ptrdiff_t amount);
};
} // namespace detail
/// A \c BucketDistribution for \ref memory_pool_collection defining that there is a bucket, i.e. pool, for each size.
/// That means that for each possible size up to an upper bound there will be a seperate free list.
/// Allocating a node will not waste any memory.
/// \ingroup allocator
struct identity_buckets
{
using type = detail::identity_access_policy;
};
/// A \c BucketDistribution for \ref memory_pool_collection defining that there is a bucket, i.e. pool, for each power of two.
/// That means for each power of two up to an upper bound there will be a separate free list.
/// Allocating a node will only waste half of the memory.
/// \ingroup allocator
struct log2_buckets
{
using type = detail::log2_access_policy;
};
/// A stateful \concept{concept_rawallocator,RawAllocator} that behaves as a collection of multiple \ref memory_pool objects.
/// It maintains a list of multiple free lists, whose types are controlled via the \c PoolType tags defined in \ref memory_pool_type.hpp,
/// each of a different size as defined in the \c BucketDistribution (\ref identity_buckets or \ref log2_buckets).
/// Allocating a node of given size will use the appropriate free list.<br>
/// This allocator is ideal for \concept{concept_node,node} allocations in any order but with a predefined set of sizes,
/// not only one size like \ref memory_pool.
/// \ingroup allocator
template <class PoolType, class BucketDistribution,
class BlockOrRawAllocator = default_allocator>
class memory_pool_collection
: WPI_EBO(detail::default_leak_checker<detail::memory_pool_collection_leak_handler>)
{
using free_list_array =
detail::free_list_array<typename PoolType::type, typename BucketDistribution::type>;
using leak_checker =
detail::default_leak_checker<detail::memory_pool_collection_leak_handler>;
public:
using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
using pool_type = PoolType;
using bucket_distribution = BucketDistribution;
/// \effects Creates it by giving it the maximum node size it should be able to allocate,
/// the size of the initial memory block and other constructor arguments for the \concept{concept_blockallocator,BlockAllocator}.
/// The \c BucketDistribution controls how many free lists are created,
/// but unlike in \ref memory_pool all free lists are initially empty and the first memory block queued.
/// \requires \c max_node_size must be a valid \concept{concept_node,node} size
/// and \c block_size must be non-zero.
template <typename... Args>
memory_pool_collection(std::size_t max_node_size, std::size_t block_size,
Args&&... args)
: arena_(block_size, detail::forward<Args>(args)...),
stack_(allocate_block()),
pools_(stack_, block_end(), max_node_size)
{
}
/// \effects Destroys the \ref memory_pool_collection by returning all memory blocks,
/// regardless of properly deallocated back to the \concept{concept_blockallocator,BlockAllocator}.
~memory_pool_collection() noexcept = default;
/// @{
/// \effects Moving a \ref memory_pool_collection object transfers ownership over the free lists,
/// i.e. the moved from pool is completely empty and the new one has all its memory.
/// That means that it is not allowed to call \ref deallocate_node() on a moved-from allocator
/// even when passing it memory that was previously allocated by this object.
memory_pool_collection(memory_pool_collection&& other) noexcept
: leak_checker(detail::move(other)),
arena_(detail::move(other.arena_)),
stack_(detail::move(other.stack_)),
pools_(detail::move(other.pools_))
{
}
memory_pool_collection& operator=(memory_pool_collection&& other) noexcept
{
leak_checker::operator=(detail::move(other));
arena_ = detail::move(other.arena_);
stack_ = detail::move(other.stack_);
pools_ = detail::move(other.pools_);
return *this;
}
/// @}
/// \effects Allocates a \concept{concept_node,node} of given size.
/// It first finds the appropriate free list as defined in the \c BucketDistribution.
/// If it is empty, it will use an implementation defined amount of memory from the arena
/// and inserts it in it.
/// If the arena is empty too, it will request a new memory block from the \concept{concept_blockallocator,BlockAllocator}
/// of size \ref next_capacity() and puts part of it onto this free list.
/// Then it removes a node from it.
/// \returns A \concept{concept_node,node} of given size suitable aligned,
/// i.e. suitable for any type where <tt>sizeof(T) < node_size</tt>.
/// \throws Anything thrown by the \concept{concept_blockallocator,BlockAllocator} if a growth is needed or a \ref bad_node_size exception if the node size is too big.
void* allocate_node(std::size_t node_size)
{
detail::check_allocation_size<bad_node_size>(
node_size, [&] { return max_node_size(); }, info());
auto& pool = pools_.get(node_size);
if (pool.empty())
{
auto block = reserve_memory(pool, def_capacity());
pool.insert(block.memory, block.size);
}
auto mem = pool.allocate();
WPI_MEMORY_ASSERT(mem);
return mem;
}
/// \effects Allocates a \concept{concept_node,node} of given size.
/// It is similar to \ref allocate_node() but will return `nullptr` on any failure,
/// instead of growing the arnea and possibly throwing.
/// \returns A \concept{concept_node,node} of given size suitable aligned
/// or `nullptr` in case of failure.
void* try_allocate_node(std::size_t node_size) noexcept
{
if (node_size > max_node_size())
return nullptr;
auto& pool = pools_.get(node_size);
if (pool.empty())
{
try_reserve_memory(pool, def_capacity());
return pool.empty() ? nullptr : pool.allocate();
}
else
return pool.allocate();
}
/// \effects Allocates an \concept{concept_array,array} of nodes by searching for \c n continuous nodes on the appropriate free list and removing them.
/// Depending on the \c PoolType this can be a slow operation or not allowed at all.
/// This can sometimes lead to a growth on the free list, even if technically there is enough continuous memory on the free list.
/// Otherwise has the same behavior as \ref allocate_node().
/// \returns An array of \c n nodes of size \c node_size suitable aligned.
/// \throws Anything thrown by the used \concept{concept_blockallocator,BlockAllocator}'s allocation function if a growth is needed,
/// or a \ref bad_allocation_size exception.
/// \requires \c count must be valid \concept{concept_array,array count} and
/// \c node_size must be valid \concept{concept_node,node size}.
void* allocate_array(std::size_t count, std::size_t node_size)
{
detail::check_allocation_size<bad_node_size>(
node_size, [&] { return max_node_size(); }, info());
auto& pool = pools_.get(node_size);
// try allocating if not empty
// for pools without array allocation support, allocate() will always return nullptr
auto mem = pool.empty() ? nullptr : pool.allocate(count * node_size);
if (!mem)
{
// reserve more memory
auto block = reserve_memory(pool, def_capacity());
pool.insert(block.memory, block.size);
mem = pool.allocate(count * node_size);
if (!mem)
{
// reserve more then the default capacity if that didn't work either
detail::check_allocation_size<bad_array_size>(
count * node_size,
[&] { return next_capacity() - pool.alignment() + 1; }, info());
block = reserve_memory(pool, count * node_size);
pool.insert(block.memory, block.size);
mem = pool.allocate(count * node_size);
WPI_MEMORY_ASSERT(mem);
}
}
return mem;
}
/// \effects Allocates a \concept{concept_array,array} of given size.
/// It is similar to \ref allocate_node() but will return `nullptr` on any failure,
/// instead of growing the arnea and possibly throwing.
/// \returns A \concept{concept_array,array} of given size suitable aligned
/// or `nullptr` in case of failure.
void* try_allocate_array(std::size_t count, std::size_t node_size) noexcept
{
if (!pool_type::value || node_size > max_node_size())
return nullptr;
auto& pool = pools_.get(node_size);
if (pool.empty())
{
try_reserve_memory(pool, def_capacity());
return pool.empty() ? nullptr : pool.allocate(count * node_size);
}
else
return pool.allocate(count * node_size);
}
/// \effects Deallocates a \concept{concept_node,node} by putting it back onto the appropriate free list.
/// \requires \c ptr must be a result from a previous call to \ref allocate_node() with the same size on the same free list,
/// i.e. either this allocator object or a new object created by moving this to it.
void deallocate_node(void* ptr, std::size_t node_size) noexcept
{
pools_.get(node_size).deallocate(ptr);
}
/// \effects Deallocates a \concept{concept_node,node} similar to \ref deallocate_node().
/// But it checks if it can deallocate this memory.
/// \returns `true` if the node could be deallocated,
/// `false` otherwise.
bool try_deallocate_node(void* ptr, std::size_t node_size) noexcept
{
if (node_size > max_node_size() || !arena_.owns(ptr))
return false;
pools_.get(node_size).deallocate(ptr);
return true;
}
/// \effects Deallocates an \concept{concept_array,array} by putting it back onto the free list.
/// \requires \c ptr must be a result from a previous call to \ref allocate_array() with the same sizes on the same free list,
/// i.e. either this allocator object or a new object created by moving this to it.
void deallocate_array(void* ptr, std::size_t count, std::size_t node_size) noexcept
{
pools_.get(node_size).deallocate(ptr, count * node_size);
}
/// \effects Deallocates a \concept{concept_array,array} similar to \ref deallocate_array().
/// But it checks if it can deallocate this memory.
/// \returns `true` if the array could be deallocated,
/// `false` otherwise.
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t node_size) noexcept
{
if (!pool_type::value || node_size > max_node_size() || !arena_.owns(ptr))
return false;
pools_.get(node_size).deallocate(ptr, count * node_size);
return true;
}
/// \effects Inserts more memory on the free list for nodes of given size.
/// It will try to put \c capacity_left bytes from the arena onto the free list defined over the \c BucketDistribution,
/// if the arena is empty, a new memory block is requested from the \concept{concept_blockallocator,BlockAllocator}
/// and it will be used.
/// \throws Anything thrown by the \concept{concept_blockallocator,BlockAllocator} if a growth is needed.
/// \requires \c node_size must be valid \concept{concept_node,node size} less than or equal to \ref max_node_size(),
/// \c capacity_left must be less than \ref next_capacity().
void reserve(std::size_t node_size, std::size_t capacity)
{
WPI_MEMORY_ASSERT_MSG(node_size <= max_node_size(), "node_size too big");
auto& pool = pools_.get(node_size);
reserve_memory(pool, capacity);
}
/// \returns The maximum node size for which is a free list.
/// This is the value passed to it in the constructor.
std::size_t max_node_size() const noexcept
{
return pools_.max_node_size();
}
/// \returns The amount of nodes available in the free list for nodes of given size
/// as defined over the \c BucketDistribution.
/// This is the number of nodes that can be allocated without the free list requesting more memory from the arena.
/// \note Array allocations may lead to a growth even if the capacity_left is big enough.
std::size_t pool_capacity_left(std::size_t node_size) const noexcept
{
WPI_MEMORY_ASSERT_MSG(node_size <= max_node_size(), "node_size too big");
return pools_.get(node_size).capacity();
}
/// \returns The amount of memory available in the arena not inside the free lists.
/// This is the number of bytes that can be inserted into the free lists
/// without requesting more memory from the \concept{concept_blockallocator,BlockAllocator}.
/// \note Array allocations may lead to a growth even if the capacity is big enough.
std::size_t capacity_left() const noexcept
{
return std::size_t(block_end() - stack_.top());
}
/// \returns The size of the next memory block after \ref capacity_left() arena grows.
/// This is the amount of memory that can be distributed in the pools.
/// \note If the `PoolType` is \ref small_node_pool, the exact usable memory is lower than that.
std::size_t next_capacity() const noexcept
{
return arena_.next_block_size();
}
/// \returns A reference to the \concept{concept_blockallocator,BlockAllocator} used for managing the arena.
/// \requires It is undefined behavior to move this allocator out into another object.
allocator_type& get_allocator() noexcept
{
return arena_.get_allocator();
}
private:
allocator_info info() const noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::memory_pool_collection", this};
}
std::size_t def_capacity() const noexcept
{
return arena_.next_block_size() / pools_.size();
}
detail::fixed_memory_stack allocate_block()
{
return detail::fixed_memory_stack(arena_.allocate_block().memory);
}
const char* block_end() const noexcept
{
auto block = arena_.current_block();
return static_cast<const char*>(block.memory) + block.size;
}
bool insert_rest(typename pool_type::type& pool) noexcept
{
if (auto remaining = std::size_t(block_end() - stack_.top()))
{
auto offset = detail::align_offset(stack_.top(), detail::max_alignment);
if (offset < remaining)
{
detail::debug_fill(stack_.top(), offset, debug_magic::alignment_memory);
pool.insert(stack_.top() + offset, remaining - offset);
return true;
}
}
return false;
}
void try_reserve_memory(typename pool_type::type& pool, std::size_t capacity) noexcept
{
auto mem = stack_.allocate(block_end(), capacity, detail::max_alignment);
if (!mem)
insert_rest(pool);
else
pool.insert(mem, capacity);
}
memory_block reserve_memory(typename pool_type::type& pool, std::size_t capacity)
{
auto mem = stack_.allocate(block_end(), capacity, detail::max_alignment);
if (!mem)
{
insert_rest(pool);
// get new block
stack_ = allocate_block();
// allocate ensuring alignment
mem = stack_.allocate(block_end(), capacity, detail::max_alignment);
WPI_MEMORY_ASSERT(mem);
}
return {mem, capacity};
}
memory_arena<allocator_type, false> arena_;
detail::fixed_memory_stack stack_;
free_list_array pools_;
friend allocator_traits<memory_pool_collection>;
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class memory_pool_collection<node_pool, identity_buckets>;
extern template class memory_pool_collection<array_pool, identity_buckets>;
extern template class memory_pool_collection<small_node_pool, identity_buckets>;
extern template class memory_pool_collection<node_pool, log2_buckets>;
extern template class memory_pool_collection<array_pool, log2_buckets>;
extern template class memory_pool_collection<small_node_pool, log2_buckets>;
#endif
/// An alias for \ref memory_pool_collection using the \ref identity_buckets policy
/// and a \c PoolType defaulting to \ref node_pool.
/// \ingroup allocator
template <class PoolType = node_pool, class ImplAllocator = default_allocator>
WPI_ALIAS_TEMPLATE(bucket_allocator,
memory_pool_collection<PoolType, identity_buckets, ImplAllocator>);
template <class Allocator>
class allocator_traits;
/// Specialization of the \ref allocator_traits for \ref memory_pool_collection classes.
/// \note It is not allowed to mix calls through the specialization and through the member functions,
/// i.e. \ref memory_pool_collection::allocate_node() and this \c allocate_node().
/// \ingroup allocator
template <class Pool, class BucketDist, class RawAllocator>
class allocator_traits<memory_pool_collection<Pool, BucketDist, RawAllocator>>
{
public:
using allocator_type = memory_pool_collection<Pool, BucketDist, RawAllocator>;
using is_stateful = std::true_type;
/// \returns The result of \ref memory_pool_collection::allocate_node().
/// \throws Anything thrown by the pool allocation function
/// or a \ref bad_allocation_size exception if \c size / \c alignment exceeds \ref max_node_size() / the suitable alignment value,
/// i.e. the node is over-aligned.
static void* allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment)
{
// node already checked
detail::check_allocation_size<bad_alignment>(
alignment, [&] { return detail::alignment_for(size); }, state.info());
auto mem = state.allocate_node(size);
state.on_allocate(size);
return mem;
}
/// \returns The result of \ref memory_pool_collection::allocate_array().
/// \throws Anything thrown by the pool allocation function or a \ref bad_allocation_size exception.
/// \requires The \ref memory_pool_collection has to support array allocations.
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
std::size_t alignment)
{
// node and array already checked
detail::check_allocation_size<bad_alignment>(
alignment, [&] { return detail::alignment_for(size); }, state.info());
auto mem = state.allocate_array(count, size);
state.on_allocate(count * size);
return mem;
}
/// \effects Calls \ref memory_pool_collection::deallocate_node().
static void deallocate_node(allocator_type& state, void* node, std::size_t size,
std::size_t) noexcept
{
state.deallocate_node(node, size);
state.on_deallocate(size);
}
/// \effects Calls \ref memory_pool_collection::deallocate_array().
/// \requires The \ref memory_pool_collection has to support array allocations.
static void deallocate_array(allocator_type& state, void* array, std::size_t count,
std::size_t size, std::size_t) noexcept
{
state.deallocate_array(array, count, size);
state.on_deallocate(count * size);
}
/// \returns The maximum size of each node which is \ref memory_pool_collection::max_node_size().
static std::size_t max_node_size(const allocator_type& state) noexcept
{
return state.max_node_size();
}
/// \returns An upper bound on the maximum array size which is \ref memory_pool::next_capacity().
static std::size_t max_array_size(const allocator_type& state) noexcept
{
return state.next_capacity();
}
/// \returns Just \c alignof(std::max_align_t) since the actual maximum alignment depends on the node size,
/// the nodes must not be over-aligned.
static std::size_t max_alignment(const allocator_type&) noexcept
{
return detail::max_alignment;
}
};
/// Specialization of the \ref composable_allocator_traits for \ref memory_pool_collection classes.
/// \ingroup allocator
template <class Pool, class BucketDist, class RawAllocator>
class composable_allocator_traits<memory_pool_collection<Pool, BucketDist, RawAllocator>>
{
using traits = allocator_traits<memory_pool_collection<Pool, BucketDist, RawAllocator>>;
public:
using allocator_type = memory_pool_collection<Pool, BucketDist, RawAllocator>;
/// \returns The result of \ref memory_pool_collection::try_allocate_node()
/// or `nullptr` if the allocation size was too big.
static void* try_allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment) noexcept
{
if (alignment > traits::max_alignment(state))
return nullptr;
return state.try_allocate_node(size);
}
/// \returns The result of \ref memory_pool_collection::try_allocate_array()
/// or `nullptr` if the allocation size was too big.
static void* try_allocate_array(allocator_type& state, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
if (count * size > traits::max_array_size(state)
|| alignment > traits::max_alignment(state))
return nullptr;
return state.try_allocate_array(count, size);
}
/// \effects Just forwards to \ref memory_pool_collection::try_deallocate_node().
/// \returns Whether the deallocation was successful.
static bool try_deallocate_node(allocator_type& state, void* node, std::size_t size,
std::size_t alignment) noexcept
{
if (alignment > traits::max_alignment(state))
return false;
return state.try_deallocate_node(node, size);
}
/// \effects Forwards to \ref memory_pool_collection::deallocate_array().
/// \returns Whether the deallocation was successful.
static bool try_deallocate_array(allocator_type& state, void* array, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
if (count * size > traits::max_array_size(state)
|| alignment > traits::max_alignment(state))
return false;
return state.try_deallocate_array(array, count, size);
}
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class allocator_traits<memory_pool_collection<node_pool, identity_buckets>>;
extern template class allocator_traits<
memory_pool_collection<array_pool, identity_buckets>>;
extern template class allocator_traits<
memory_pool_collection<small_node_pool, identity_buckets>>;
extern template class allocator_traits<memory_pool_collection<node_pool, log2_buckets>>;
extern template class allocator_traits<memory_pool_collection<array_pool, log2_buckets>>;
extern template class allocator_traits<
memory_pool_collection<small_node_pool, log2_buckets>>;
extern template class composable_allocator_traits<
memory_pool_collection<node_pool, identity_buckets>>;
extern template class composable_allocator_traits<
memory_pool_collection<array_pool, identity_buckets>>;
extern template class composable_allocator_traits<
memory_pool_collection<small_node_pool, identity_buckets>>;
extern template class composable_allocator_traits<
memory_pool_collection<node_pool, log2_buckets>>;
extern template class composable_allocator_traits<
memory_pool_collection<array_pool, log2_buckets>>;
extern template class composable_allocator_traits<
memory_pool_collection<small_node_pool, log2_buckets>>;
#endif
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_MEMORY_POOL_COLLECTION_HPP_INCLUDED

View File

@@ -0,0 +1,53 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_MEMORY_POOL_TYPE_HPP_INCLUDED
#define WPI_MEMORY_MEMORY_POOL_TYPE_HPP_INCLUDED
/// \file
/// The \c PoolType tag types.
#include <type_traits>
#include "detail/free_list.hpp"
#include "detail/small_free_list.hpp"
#include "config.hpp"
namespace wpi
{
namespace memory
{
/// Tag type defining a memory pool optimized for nodes.
/// It does not support array allocations that great and may trigger a growth even if there is enough memory.
/// But it is the fastest pool type.
/// \ingroup allocator
struct node_pool : WPI_EBO(std::true_type)
{
using type = detail::node_free_memory_list;
};
/// Tag type defining a memory pool optimized for arrays.
/// It keeps the nodes oredered inside the free list and searches the list for an appropriate memory block.
/// Array allocations are still pretty slow, if the array gets big enough it can get slower than \c new.
/// Node allocations are still fast, unless there is deallocation in random order.
/// \note Use this tag type only if you really need to have a memory pool!
/// \ingroup allocator
struct array_pool : WPI_EBO(std::true_type)
{
using type = detail::array_free_memory_list;
};
/// Tag type defining a memory pool optimized for small nodes.
/// The free list is intrusive and thus requires that each node has at least the size of a pointer.
/// This tag type does not have this requirement and thus allows zero-memory-overhead allocations of small nodes.
/// It is a little bit slower than \ref node_pool and does not support arrays.
/// \ingroup allocator
struct small_node_pool : WPI_EBO(std::false_type)
{
using type = detail::small_free_memory_list;
};
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_MEMORY_POOL_TYPE_HPP_INCLUDED

View File

@@ -0,0 +1,239 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_MEMORY_RESOURCE_ADAPTER_HPP_INCLUDED
#define WPI_MEMORY_MEMORY_RESOURCE_ADAPTER_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::memory_resource_adapter and \ref wpi::memory::memory_resource_allocator to allow usage of PMRs.
#include "detail/assert.hpp"
#include "detail/utility.hpp"
#include "config.hpp"
#include "allocator_traits.hpp"
#if defined(__has_include) && __has_include(<memory_resource>)
#if !defined(__GNUC__) || __cplusplus >= 201703L
// The experimental/memory_resource header lacks a check for C++17 on older GCC,
// so we have to do it for them.
#include <memory_resource>
#endif
#elif defined(__has_include) && __has_include(<experimental/memory_resource>)
#if !defined(__GNUC__) || __cplusplus >= 201402L
// The experimental/memory_resource header lacks a check for C++14 on older GCC,
// so we have to do it for them.
#include <experimental/memory_resource>
#endif
#endif
#if defined(__cpp_lib_memory_resource)
// We use std::pmr::memory_resource.
namespace wpi_memory_pmr = std::pmr;
#elif defined(__cpp_lib_experimental_memory_resources)
// We use std::experimental::pmr::memory_resource.
namespace wpi_memory_pmr = std::experimental::pmr;
#else
// We use our own implementation.
namespace wpi_memory_pmr
{
// see N3916 for documentation
class memory_resource
{
static const std::size_t max_alignment = alignof(std::max_align_t);
public:
virtual ~memory_resource() noexcept {}
void* allocate(std::size_t bytes, std::size_t alignment = max_alignment)
{
return do_allocate(bytes, alignment);
}
void deallocate(void* p, std::size_t bytes, std::size_t alignment = max_alignment)
{
do_deallocate(p, bytes, alignment);
}
bool is_equal(const memory_resource& other) const noexcept
{
return do_is_equal(other);
}
protected:
virtual void* do_allocate(std::size_t bytes, std::size_t alignment) = 0;
virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) = 0;
virtual bool do_is_equal(const memory_resource& other) const noexcept = 0;
};
inline bool operator==(const memory_resource& a, const memory_resource& b) noexcept
{
return &a == &b || a.is_equal(b);
}
inline bool operator!=(const memory_resource& a, const memory_resource& b) noexcept
{
return !(a == b);
}
} // namespace wpi_memory_pmr
#endif
namespace wpi
{
namespace memory
{
/// The \c memory_resource abstract base class used in the implementation.
/// \ingroup adapter
WPI_ALIAS_TEMPLATE(memory_resource, foonathan_memory_pmr::memory_resource);
/// Wraps a \concept{concept_rawallocator,RawAllocator} and makes it a \ref memory_resource.
/// \ingroup adapter
template <class RawAllocator>
class memory_resource_adapter
: public memory_resource,
WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
{
public:
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
/// \effects Creates the resource by moving in the allocator.
memory_resource_adapter(allocator_type&& other) noexcept
: allocator_type(detail::move(other))
{
}
/// @{
/// \returns A reference to the wrapped allocator.
allocator_type& get_allocator() noexcept
{
return *this;
}
const allocator_type& get_allocator() const noexcept
{
return *this;
}
/// @}
protected:
using traits_type = allocator_traits<RawAllocator>;
/// \effects Allocates raw memory with given size and alignment.
/// It forwards to \c allocate_node() or \c allocate_array() depending on the size.
/// \returns The new memory as returned by the \concept{concept_rawallocator,RawAllocator}.
/// \throws Anything thrown by the allocation function.
void* do_allocate(std::size_t bytes, std::size_t alignment) override
{
auto max = traits_type::max_node_size(*this);
if (bytes <= max)
return traits_type::allocate_node(*this, bytes, alignment);
auto div = bytes / max;
auto mod = bytes % max;
auto n = div + (mod != 0);
return traits_type::allocate_array(*this, n, max, alignment);
}
/// \effects Deallocates memory previously allocated by \ref do_allocate.
/// It forwards to \c deallocate_node() or \c deallocate_array() depending on the size.
/// \throws Nothing.
void do_deallocate(void* p, std::size_t bytes, std::size_t alignment) override
{
auto max = traits_type::max_node_size(*this);
if (bytes <= max)
traits_type::deallocate_node(*this, p, bytes, alignment);
else
{
auto div = bytes / max;
auto mod = bytes % max;
auto n = div + (mod != 0);
traits_type::deallocate_array(*this, p, n, max, alignment);
}
}
/// \returns Whether or not \c *this is equal to \c other
/// by comparing the addresses.
bool do_is_equal(const memory_resource& other) const noexcept override
{
return this == &other;
}
};
/// Wraps a \ref memory_resource and makes it a \concept{concept_rawallocator,RawAllocator}.
/// \ingroup adapter
class memory_resource_allocator
{
public:
/// \effects Creates it by giving it a pointer to the \ref memory_resource.
/// \requires \c ptr must not be \c nullptr.
memory_resource_allocator(memory_resource* ptr) noexcept : ptr_(ptr)
{
WPI_MEMORY_ASSERT(ptr);
}
/// \effects Allocates a node by forwarding to the \c allocate() function.
/// \returns The node as returned by the \ref memory_resource.
/// \throws Anything thrown by the \c allocate() function.
void* allocate_node(std::size_t size, std::size_t alignment)
{
return ptr_->allocate(size, alignment);
}
/// \effects Deallocates a node by forwarding to the \c deallocate() function.
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
ptr_->deallocate(ptr, size, alignment);
}
/// \returns The maximum alignment which is the maximum value of type \c std::size_t.
std::size_t max_alignment() const noexcept
{
return std::size_t(-1);
}
/// \returns A pointer to the used \ref memory_resource, this is never \c nullptr.
memory_resource* resource() const noexcept
{
return ptr_;
}
private:
memory_resource* ptr_;
};
/// @{
/// \returns Whether `lhs` and `rhs` share the same resource.
/// \relates memory_resource_allocator
inline bool operator==(const memory_resource_allocator& lhs,
const memory_resource_allocator& rhs) noexcept
{
return lhs.resource() == rhs.resource();
}
inline bool operator!=(const memory_resource_allocator& lhs,
const memory_resource_allocator& rhs) noexcept
{
return !(lhs == rhs);
}
/// @}
#if !defined(DOXYGEN)
template <class RawAllocator>
struct is_shared_allocator;
#endif
/// Specialization of \ref is_shared_allocator to mark \ref memory_resource_allocator as shared.
/// This allows using it as \ref allocator_reference directly.
/// \ingroup adapter
template <>
struct is_shared_allocator<memory_resource_allocator> : std::true_type
{
};
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_MEMORY_RESOURCE_ADAPTER_HPP_INCLUDED

View File

@@ -0,0 +1,489 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_MEMORY_STACK_HPP_INCLUDED
#define WPI_MEMORY_MEMORY_STACK_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::memory_stack and its \ref wpi::memory::allocator_traits specialization.
// Inform that wpi::memory::memory_stack::min_block_size API is available
#define WPI_MEMORY_MEMORY_STACK_HAS_MIN_BLOCK_SIZE
#include <cstdint>
#include <type_traits>
#include "detail/assert.hpp"
#include "detail/memory_stack.hpp"
#include "config.hpp"
#include "error.hpp"
#include "memory_arena.hpp"
namespace wpi
{
namespace memory
{
#if !defined(DOXYGEN)
template <class Impl>
class memory_stack;
#endif
namespace detail
{
class stack_marker
{
std::size_t index;
char* top;
const char* end;
stack_marker(std::size_t i, const detail::fixed_memory_stack& s,
const char* e) noexcept
: index(i), top(s.top()), end(e)
{
}
friend bool operator==(const stack_marker& lhs, const stack_marker& rhs) noexcept
{
if (lhs.index != rhs.index)
return false;
WPI_MEMORY_ASSERT_MSG(lhs.end == rhs.end, "you must not compare two "
"stack markers from different "
"stacks");
return lhs.top == rhs.top;
}
friend bool operator!=(const stack_marker& lhs, const stack_marker& rhs) noexcept
{
return !(rhs == lhs);
}
friend bool operator<(const stack_marker& lhs, const stack_marker& rhs) noexcept
{
if (lhs.index != rhs.index)
return lhs.index < rhs.index;
WPI_MEMORY_ASSERT_MSG(lhs.end == rhs.end, "you must not compare two "
"stack markers from different "
"stacks");
return lhs.top < rhs.top;
}
friend bool operator>(const stack_marker& lhs, const stack_marker& rhs) noexcept
{
return rhs < lhs;
}
friend bool operator<=(const stack_marker& lhs, const stack_marker& rhs) noexcept
{
return !(rhs < lhs);
}
friend bool operator>=(const stack_marker& lhs, const stack_marker& rhs) noexcept
{
return !(lhs < rhs);
}
template <class Impl>
friend class memory::memory_stack;
};
struct memory_stack_leak_handler
{
void operator()(std::ptrdiff_t amount);
};
} // namespace detail
/// A stateful \concept{concept_rawallocator,RawAllocator} that provides stack-like (LIFO) allocations.
/// It uses a \ref memory_arena with a given \c BlockOrRawAllocator defaulting to \ref growing_block_allocator to allocate huge blocks
/// and saves a marker to the current top.
/// Allocation simply moves this marker by the appropriate number of bytes and returns the pointer at the old marker position,
/// deallocation is not directly supported, only setting the marker to a previously queried position.
/// \ingroup allocator
template <class BlockOrRawAllocator = default_allocator>
class memory_stack
: WPI_EBO(detail::default_leak_checker<detail::memory_stack_leak_handler>)
{
public:
using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
/// \returns The minimum block size required for a stack containing the given amount of memory.
/// If a stack is created with the result of `min_block_size(n)`, the resulting capacity will be exactly `n`.
/// \requires `byte_size` must be a positive number.
/// \note Due to debug fence sizes, the actual amount of usable memory can vary.
/// However, this is impossible to compute without knowing the exact allocation pattern before,
/// so this is just a rough estimate.
static constexpr std::size_t min_block_size(std::size_t byte_size) noexcept
{
return detail::memory_block_stack::implementation_offset() + byte_size;
}
/// \effects Creates it with a given initial block size and and other constructor arguments for the \concept{concept_blockallocator,BlockAllocator}.
/// It will allocate the first block and sets the top to its beginning.
/// \requires \c block_size must be at least \c min_block_size(1).
template <typename... Args>
explicit memory_stack(std::size_t block_size, Args&&... args)
: arena_(block_size, detail::forward<Args>(args)...),
stack_(arena_.allocate_block().memory)
{
}
/// \effects Allocates a memory block of given size and alignment.
/// It simply moves the top marker.
/// If there is not enough space on the current memory block,
/// a new one will be allocated by the \concept{concept_blockallocator,BlockAllocator} or taken from a cache
/// and used for the allocation.
/// \returns A \concept{concept_node,node} with given size and alignment.
/// \throws Anything thrown by the \concept{concept_blockallocator,BlockAllocator} on growth
/// or \ref bad_allocation_size if \c size is too big.
/// \requires \c size and \c alignment must be valid.
void* allocate(std::size_t size, std::size_t alignment)
{
auto fence = detail::debug_fence_size;
auto offset = detail::align_offset(stack_.top() + fence, alignment);
if (!stack_.top()
|| fence + offset + size + fence > std::size_t(block_end() - stack_.top()))
{
// need to grow
auto block = arena_.allocate_block();
stack_ = detail::fixed_memory_stack(block.memory);
// new alignment required for over-aligned types
offset = detail::align_offset(stack_.top() + fence, alignment);
auto needed = fence + offset + size + fence;
detail::check_allocation_size<bad_allocation_size>(needed, block.size, info());
}
return stack_.allocate_unchecked(size, offset);
}
/// \effects Allocates a memory block of given size and alignment,
/// similar to \ref allocate().
/// But it does not attempt a growth if the arena is empty.
/// \returns A \concept{concept_node,node} with given size and alignment
/// or `nullptr` if there wasn't enough memory available.
void* try_allocate(std::size_t size, std::size_t alignment) noexcept
{
return stack_.allocate(block_end(), size, alignment);
}
/// The marker type that is used for unwinding.
/// The exact type is implementation defined,
/// it is only required that it is efficiently copyable
/// and has all the comparision operators defined for two markers on the same stack.
/// Two markers are equal, if they are copies or created from two `top()` calls without a call to `unwind()` or `allocate()`.
/// A marker `a` is less than marker `b`, if after `a` was obtained, there was one or more call to `allocate()` and no call to `unwind()`.
using marker = WPI_IMPL_DEFINED(detail::stack_marker);
/// \returns A marker to the current top of the stack.
marker top() const noexcept
{
return {arena_.size() - 1, stack_, block_end()};
}
/// \effects Unwinds the stack to a certain marker position.
/// This sets the top pointer of the stack to the position described by the marker
/// and has the effect of deallocating all memory allocated since the marker was obtained.
/// If any memory blocks are unused after the operation,
/// they are not deallocated but put in a cache for later use,
/// call \ref shrink_to_fit() to actually deallocate them.
/// \requires The marker must point to memory that is still in use and was the whole time,
/// i.e. it must have been pointed below the top at all time.
void unwind(marker m) noexcept
{
WPI_MEMORY_ASSERT(m <= top());
detail::debug_check_pointer([&] { return m.index <= arena_.size() - 1; }, info(),
m.top);
if (std::size_t to_deallocate = (arena_.size() - 1) - m.index) // different index
{
arena_.deallocate_block();
for (std::size_t i = 1; i != to_deallocate; ++i)
arena_.deallocate_block();
detail::debug_check_pointer(
[&] {
auto cur = arena_.current_block();
return m.end == static_cast<char*>(cur.memory) + cur.size;
},
info(), m.top);
// mark memory from new top to end of the block as freed
detail::debug_fill_free(m.top, std::size_t(m.end - m.top), 0);
stack_ = detail::fixed_memory_stack(m.top);
}
else // same index
{
detail::debug_check_pointer([&] { return stack_.top() >= m.top; }, info(),
m.top);
stack_.unwind(m.top);
}
}
/// \effects \ref unwind() does not actually do any deallocation of blocks on the \concept{concept_blockallocator,BlockAllocator},
/// unused memory is stored in a cache for later reuse.
/// This function clears that cache.
void shrink_to_fit() noexcept
{
arena_.shrink_to_fit();
}
/// \returns The amount of memory remaining in the current block.
/// This is the number of bytes that are available for allocation
/// before the cache or \concept{concept_blockallocator,BlockAllocator} needs to be used.
std::size_t capacity_left() const noexcept
{
return std::size_t(block_end() - stack_.top());
}
/// \returns The size of the next memory block after the current block is exhausted and the arena grows.
/// This function just forwards to the \ref memory_arena.
/// \note All of it is available for the stack to use, but due to fences and alignment buffers,
/// this may not be the exact amount of memory usable for the user.
std::size_t next_capacity() const noexcept
{
return arena_.next_block_size();
}
/// \returns A reference to the \concept{concept_blockallocator,BlockAllocator} used for managing the arena.
/// \requires It is undefined behavior to move this allocator out into another object.
allocator_type& get_allocator() noexcept
{
return arena_.get_allocator();
}
private:
allocator_info info() const noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::memory_stack", this};
}
const char* block_end() const noexcept
{
auto block = arena_.current_block();
return static_cast<const char*>(block.memory) + block.size;
}
memory_arena<allocator_type> arena_;
detail::fixed_memory_stack stack_;
friend allocator_traits<memory_stack<BlockOrRawAllocator>>;
friend composable_allocator_traits<memory_stack<BlockOrRawAllocator>>;
};
/// Simple utility that automatically unwinds a `Stack` to a previously saved location.
/// A `Stack` is anything that provides a `marker`, a `top()` function returning a `marker`
/// and an `unwind()` function to unwind to a `marker`,
/// like a \ref wpi::memory::memory_stack
/// \ingroup allocator
template <class Stack = memory_stack<>>
class memory_stack_raii_unwind
{
public:
using stack_type = Stack;
using marker_type = typename stack_type::marker;
/// \effects Same as `memory_stack_raii_unwind(stack, stack.top())`.
explicit memory_stack_raii_unwind(stack_type& stack) noexcept
: memory_stack_raii_unwind(stack, stack.top())
{
}
/// \effects Creates the unwinder by giving it the stack and the marker.
/// \requires The stack must live longer than this object.
memory_stack_raii_unwind(stack_type& stack, marker_type marker) noexcept
: marker_(marker), stack_(&stack)
{
}
/// \effects Move constructs the unwinder by taking the saved position from `other`.
/// `other.will_unwind()` will return `false` after it.
memory_stack_raii_unwind(memory_stack_raii_unwind&& other) noexcept
: marker_(other.marker_), stack_(other.stack_)
{
other.stack_ = nullptr;
}
/// \effects Unwinds to the previously saved location,
/// if there is any, by calling `unwind()`.
~memory_stack_raii_unwind() noexcept
{
if (stack_)
stack_->unwind(marker_);
}
/// \effects Move assigns the unwinder by taking the saved position from `other`.
/// `other.will_unwind()` will return `false` after it.
memory_stack_raii_unwind& operator=(memory_stack_raii_unwind&& other) noexcept
{
if (stack_)
stack_->unwind(marker_);
marker_ = other.marker_;
stack_ = other.stack_;
other.stack_ = nullptr;
return *this;
}
/// \effects Removes the location without unwinding it.
/// `will_unwind()` will return `false`.
void release() noexcept
{
stack_ = nullptr;
}
/// \effects Unwinds to the saved location explictly.
/// \requires `will_unwind()` must return `true`.
void unwind() noexcept
{
WPI_MEMORY_ASSERT(will_unwind());
stack_->unwind(marker_);
}
/// \returns Whether or not the unwinder will actually unwind.
/// \note It will not unwind if it is in the moved-from state.
bool will_unwind() const noexcept
{
return stack_ != nullptr;
}
/// \returns The saved marker, if there is any.
/// \requires `will_unwind()` must return `true`.
marker_type get_marker() const noexcept
{
WPI_MEMORY_ASSERT(will_unwind());
return marker_;
}
/// \returns The stack it will unwind.
/// \requires `will_unwind()` must return `true`.
stack_type& get_stack() const noexcept
{
WPI_MEMORY_ASSERT(will_unwind());
return *stack_;
}
private:
marker_type marker_;
stack_type* stack_;
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class memory_stack<>;
extern template class memory_stack_raii_unwind<memory_stack<>>;
#endif
/// Specialization of the \ref allocator_traits for \ref memory_stack classes.
/// \note It is not allowed to mix calls through the specialization and through the member functions,
/// i.e. \ref memory_stack::allocate() and this \c allocate_node().
/// \ingroup allocator
template <class BlockAllocator>
class allocator_traits<memory_stack<BlockAllocator>>
{
public:
using allocator_type = memory_stack<BlockAllocator>;
using is_stateful = std::true_type;
/// \returns The result of \ref memory_stack::allocate().
static void* allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment)
{
auto mem = state.allocate(size, alignment);
state.on_allocate(size);
return mem;
}
/// \returns The result of \ref memory_stack::allocate().
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
std::size_t alignment)
{
return allocate_node(state, count * size, alignment);
}
/// @{
/// \effects Does nothing besides bookmarking for leak checking, if that is enabled.
/// Actual deallocation can only be done via \ref memory_stack::unwind().
static void deallocate_node(allocator_type& state, void*, std::size_t size,
std::size_t) noexcept
{
state.on_deallocate(size);
}
static void deallocate_array(allocator_type& state, void* ptr, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
deallocate_node(state, ptr, count * size, alignment);
}
/// @}
/// @{
/// \returns The maximum size which is \ref memory_stack::next_capacity().
static std::size_t max_node_size(const allocator_type& state) noexcept
{
return state.next_capacity();
}
static std::size_t max_array_size(const allocator_type& state) noexcept
{
return state.next_capacity();
}
/// @}
/// \returns The maximum possible value since there is no alignment restriction
/// (except indirectly through \ref memory_stack::next_capacity()).
static std::size_t max_alignment(const allocator_type&) noexcept
{
return std::size_t(-1);
}
};
/// Specialization of the \ref composable_allocator_traits for \ref memory_stack classes.
/// \ingroup allocator
template <class BlockAllocator>
class composable_allocator_traits<memory_stack<BlockAllocator>>
{
public:
using allocator_type = memory_stack<BlockAllocator>;
/// \returns The result of \ref memory_stack::try_allocate().
static void* try_allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment) noexcept
{
return state.try_allocate(size, alignment);
}
/// \returns The result of \ref memory_stack::try_allocate().
static void* try_allocate_array(allocator_type& state, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
return state.try_allocate(count * size, alignment);
}
/// @{
/// \effects Does nothing.
/// \returns Whether the memory will be deallocated by \ref memory_stack::unwind().
static bool try_deallocate_node(allocator_type& state, void* ptr, std::size_t,
std::size_t) noexcept
{
return state.arena_.owns(ptr);
}
static bool try_deallocate_array(allocator_type& state, void* ptr, std::size_t count,
std::size_t size, std::size_t alignment) noexcept
{
return try_deallocate_node(state, ptr, count * size, alignment);
}
/// @}
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class allocator_traits<memory_stack<>>;
extern template class composable_allocator_traits<memory_stack<>>;
#endif
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_MEMORY_STACK_HPP_INCLUDED

View File

@@ -0,0 +1,37 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_NAMESPACE_ALIAS_HPP_INCLUDED
#define WPI_MEMORY_NAMESPACE_ALIAS_HPP_INCLUDED
/// \file
/// Convenient namespace alias.
/// \defgroup core Core components
/// \defgroup allocator Allocator implementations
/// \defgroup adapter Adapters and Wrappers
/// \defgroup storage Allocator storage
/// \namespace wpi
/// Foonathan namespace.
/// \namespace wpi::memory
/// Memory namespace.
/// \namespace wpi::memory::literals
/// Literals namespace.
namespace wpi
{
namespace memory
{
}
} // namespace wpi
namespace memory = wpi::memory;
#endif // WPI_MEMORY_NAMESPACE_ALIAS_HPP_INCLUDED

View File

@@ -0,0 +1,55 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_NEW_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_NEW_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::new_allocator.
#include "detail/lowlevel_allocator.hpp"
#include "config.hpp"
#if WPI_MEMORY_EXTERN_TEMPLATE
#include "allocator_traits.hpp"
#endif
namespace wpi
{
namespace memory
{
struct allocator_info;
namespace detail
{
struct new_allocator_impl
{
static allocator_info info() noexcept;
static void* allocate(std::size_t size, std::size_t) noexcept;
static void deallocate(void* ptr, std::size_t size, std::size_t) noexcept;
static std::size_t max_node_size() noexcept;
};
WPI_MEMORY_LL_ALLOCATOR_LEAK_CHECKER(new_allocator_impl,
new_alloator_leak_checker)
} // namespace detail
/// A stateless \concept{concept_rawallocator,RawAllocator} that allocates memory using (nothrow) <tt>operator new</tt>.
/// If the operator returns \c nullptr, it behaves like \c new and loops calling \c std::new_handler,
/// but instead of throwing a \c std::bad_alloc exception, it throws \ref out_of_memory.
/// \ingroup allocator
using new_allocator =
WPI_IMPL_DEFINED(detail::lowlevel_allocator<detail::new_allocator_impl>);
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class detail::lowlevel_allocator<detail::new_allocator_impl>;
extern template class allocator_traits<new_allocator>;
#endif
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_NEW_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,448 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_SEGREGATOR_HPP_INCLUDED
#define WPI_MEMORY_SEGREGATOR_HPP_INCLUDED
/// \file
/// Class template \ref wpi::memory::segregator and related classes.
#include "detail/ebo_storage.hpp"
#include "detail/utility.hpp"
#include "allocator_traits.hpp"
#include "config.hpp"
#include "error.hpp"
namespace wpi
{
namespace memory
{
/// A \concept{concept_segregatable,Segregatable} that allocates until a maximum size.
/// \ingroup adapter
template <class RawAllocator>
class threshold_segregatable : WPI_EBO(allocator_traits<RawAllocator>::allocator_type)
{
public:
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
/// \effects Creates it by passing the maximum size it will allocate
/// and the allocator it uses.
explicit threshold_segregatable(std::size_t max_size,
allocator_type alloc = allocator_type())
: allocator_type(detail::move(alloc)), max_size_(max_size)
{
}
/// \returns `true` if `size` is less then or equal to the maximum size,
/// `false` otherwise.
/// \note A return value of `true` means that the allocator will be used for the allocation.
bool use_allocate_node(std::size_t size, std::size_t) noexcept
{
return size <= max_size_;
}
/// \returns `true` if `count * size` is less then or equal to the maximum size,
/// `false` otherwise.
/// \note A return value of `true` means that the allocator will be used for the allocation.
bool use_allocate_array(std::size_t count, std::size_t size, std::size_t) noexcept
{
return count * size <= max_size_;
}
/// @{
/// \returns A reference to the allocator it owns.
allocator_type& get_allocator() noexcept
{
return *this;
}
const allocator_type& get_allocator() const noexcept
{
return *this;
}
/// @}
private:
std::size_t max_size_;
};
/// \returns A \ref threshold_segregatable with the same parameter.
template <class RawAllocator>
threshold_segregatable<typename std::decay<RawAllocator>::type> threshold(
std::size_t max_size, RawAllocator&& alloc)
{
return threshold_segregatable<
typename std::decay<RawAllocator>::type>(max_size,
std::forward<RawAllocator>(alloc));
}
/// A composable \concept{concept_rawallocator,RawAllocator} that will always fail.
/// This is useful for compositioning or as last resort in \ref binary_segregator.
/// \ingroup allocator
class null_allocator
{
public:
/// \effects Will always throw.
/// \throws A \ref out_of_fixed_memory exception.
void* allocate_node(std::size_t size, std::size_t)
{
throw out_of_fixed_memory(info(), size);
}
/// \requires Must not be called.
void deallocate_node(void*, std::size_t, std::size_t) noexcept
{
WPI_MEMORY_UNREACHABLE("cannot be called with proper values");
}
/// \effects Does nothing.
/// \returns Always returns `nullptr`.
void* try_allocate_node(std::size_t, std::size_t) noexcept
{
return nullptr;
}
/// \effects Does nothing.
/// \returns Always returns `false`.
bool try_deallocate_node(void*, std::size_t, std::size_t) noexcept
{
return false;
}
private:
allocator_info info() const noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::null_allocator", this};
}
};
/// A \concept{concept_rawallocator,RawAllocator} that either uses the \concept{concept_segregatable,Segregatable} or the other `RawAllocator`.
/// It is a faster alternative to \ref fallback_allocator that doesn't require a composable allocator
/// and decides about the allocator to use purely with the `Segregatable` based on size and alignment.
/// \ingroup adapter
template <class Segregatable, class RawAllocator>
class binary_segregator
: WPI_EBO(
detail::ebo_storage<1, typename allocator_traits<RawAllocator>::allocator_type>)
{
using segregatable_traits = allocator_traits<typename Segregatable::allocator_type>;
using fallback_traits = allocator_traits<RawAllocator>;
public:
using segregatable = Segregatable;
using segregatable_allocator_type = typename segregatable::allocator_type;
using fallback_allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
/// \effects Creates it by giving the \concept{concept_segregatable,Segregatable}
/// and the \concept{concept_rawallocator,RawAllocator}.
explicit binary_segregator(segregatable s,
fallback_allocator_type fallback = fallback_allocator_type())
: detail::ebo_storage<1, fallback_allocator_type>(detail::move(fallback)),
s_(detail::move(s))
{
}
/// @{
/// \effects Uses the \concept{concept_segregatable,Segregatable} to decide which allocator to use.
/// Then forwards to the chosen allocator.
void* allocate_node(std::size_t size, std::size_t alignment)
{
if (get_segregatable().use_allocate_node(size, alignment))
return segregatable_traits::allocate_node(get_segregatable_allocator(), size,
alignment);
else
return fallback_traits::allocate_node(get_fallback_allocator(), size,
alignment);
}
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
if (get_segregatable().use_allocate_node(size, alignment))
segregatable_traits::deallocate_node(get_segregatable_allocator(), ptr, size,
alignment);
else
fallback_traits::deallocate_node(get_fallback_allocator(), ptr, size,
alignment);
}
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
{
if (get_segregatable().use_allocate_array(count, size, alignment))
return segregatable_traits::allocate_array(get_segregatable_allocator(), count,
size, alignment);
else
return fallback_traits::allocate_array(get_fallback_allocator(), count, size,
alignment);
}
void deallocate_array(void* array, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
if (get_segregatable().use_allocate_array(count, size, alignment))
segregatable_traits::deallocate_array(get_segregatable_allocator(), array,
count, size, alignment);
else
fallback_traits::deallocate_array(get_fallback_allocator(), array, count, size,
alignment);
}
/// @}
/// @{
/// \returns The maximum value of the fallback.
/// \note It assumes that the fallback will be used for larger allocations,
/// and the `Segregatable` for smaller ones.
std::size_t max_node_size() const
{
return fallback_traits::max_node_size(get_fallback_allocator());
}
std::size_t max_array_size() const
{
return fallback_traits::max_array_size(get_fallback_allocator());
}
std::size_t max_alignemnt() const
{
return fallback_traits::max_alignment(get_fallback_allocator());
}
/// @}
/// @{
/// \returns A reference to the segregatable allocator.
/// This is the one primarily used.
segregatable_allocator_type& get_segregatable_allocator() noexcept
{
return get_segregatable().get_allocator();
}
const segregatable_allocator_type& get_segregatable_allocator() const noexcept
{
return get_segregatable().get_allocator();
}
/// @}
/// @{
/// \returns A reference to the fallback allocator.
/// It will be used if the \concept{concept_segregator,Segregator} doesn't want the alloction.
fallback_allocator_type& get_fallback_allocator() noexcept
{
return detail::ebo_storage<1, fallback_allocator_type>::get();
}
const fallback_allocator_type& get_fallback_allocator() const noexcept
{
return detail::ebo_storage<1, fallback_allocator_type>::get();
}
/// @}
private:
segregatable& get_segregatable() noexcept
{
return s_;
}
segregatable s_;
};
namespace detail
{
template <class... Segregatables>
struct make_segregator_t;
template <class Segregatable>
struct make_segregator_t<Segregatable>
{
using type = binary_segregator<Segregatable, null_allocator>;
};
template <class Segregatable, class RawAllocator>
struct make_segregator_t<Segregatable, RawAllocator>
{
using type = binary_segregator<Segregatable, RawAllocator>;
};
template <class Segregatable, class... Tail>
struct make_segregator_t<Segregatable, Tail...>
{
using type =
binary_segregator<Segregatable, typename make_segregator_t<Tail...>::type>;
};
template <class Segregator, class Fallback = null_allocator>
auto make_segregator(Segregator&& seg, Fallback&& f = null_allocator{})
-> binary_segregator<typename std::decay<Segregator>::type,
typename std::decay<Fallback>::type>
{
return binary_segregator<
typename std::decay<Segregator>::type,
typename std::decay<Fallback>::type>(std::forward<Segregator>(seg),
std::forward<Fallback>(f));
}
template <class Segregator, typename... Rest>
auto make_segregator(Segregator&& seg, Rest&&... rest)
-> binary_segregator<typename std::decay<Segregator>::type,
decltype(make_segregator(std::forward<Rest>(rest)...))>
{
return binary_segregator<typename std::decay<Segregator>::type,
decltype(make_segregator(std::forward<Rest>(
rest)...))>(std::forward<Segregator>(seg),
make_segregator(
std::forward<Rest>(rest)...));
}
template <std::size_t I, class Segregator>
struct segregatable_type;
template <class Segregator, class Fallback>
struct segregatable_type<0, binary_segregator<Segregator, Fallback>>
{
using type = typename Segregator::allocator_type;
static type& get(binary_segregator<Segregator, Fallback>& s)
{
return s.get_segregatable_allocator();
}
static const type& get(const binary_segregator<Segregator, Fallback>& s)
{
return s.get_segregatable_allocator();
}
};
template <std::size_t I, class Segregator, class Fallback>
struct segregatable_type<I, binary_segregator<Segregator, Fallback>>
{
using base = segregatable_type<I - 1, Fallback>;
using type = typename base::type;
static type& get(binary_segregator<Segregator, Fallback>& s)
{
return base::get(s.get_fallback_allocator());
}
static const type& get(const binary_segregator<Segregator, Fallback>& s)
{
return base::get(s.get_fallback_allocator());
}
};
template <class Fallback>
struct fallback_type
{
using type = Fallback;
static const std::size_t size = 0u;
static type& get(Fallback& f)
{
return f;
}
static const type& get(const Fallback& f)
{
return f;
}
};
template <class Segregator, class Fallback>
struct fallback_type<binary_segregator<Segregator, Fallback>>
{
using base = fallback_type<Fallback>;
using type = typename base::type;
static const std::size_t size = base::size + 1u;
static type& get(binary_segregator<Segregator, Fallback>& s)
{
return base::get(s.get_fallback_allocator());
}
static const type& get(const binary_segregator<Segregator, Fallback>& s)
{
return base::get(s.get_fallback_allocator());
}
};
} // namespace detail
/// Creates multiple nested \ref binary_segregator.
/// If you pass one type, it must be a \concept{concept_segregatable,Segregatable}.
/// Then the result is a \ref binary_segregator with that `Segregatable` and \ref null_allocator as fallback.
/// If you pass two types, the first one must be a `Segregatable`,
/// the second one a \concept{concept_rawallocator,RawAllocator}.
/// Then the result is a simple \ref binary_segregator with those arguments.
/// If you pass more than one, the last one must be a `RawAllocator` all others `Segregatable`,
/// the result is `binary_segregator<Head, segregator<Tail...>>`.
/// \note It will result in an allocator that tries each `Segregatable` in the order specified
/// using the last parameter as final fallback.
/// \ingroup adapter
template <class... Allocators>
WPI_ALIAS_TEMPLATE(segregator,
typename detail::make_segregator_t<Allocators...>::type);
/// \returns A \ref segregator created from the allocators `args`.
/// \relates segregator
template <typename... Args>
auto make_segregator(Args&&... args) -> segregator<typename std::decay<Args>::type...>
{
return detail::make_segregator(std::forward<Args>(args)...);
}
/// The number of \concept{concept_segregatable,Segregatable} a \ref segregator has.
/// \relates segregator
template <class Segregator>
struct segregator_size
{
static const std::size_t value = detail::fallback_type<Segregator>::size;
};
/// The type of the `I`th \concept{concept_segregatable,Segregatable}.
/// \relates segregator
template <std::size_t I, class Segregator>
using segregatable_allocator_type = typename detail::segregatable_type<I, Segregator>::type;
/// @{
/// \returns The `I`th \concept{concept_segregatable,Segregatable}.
/// \relates segregrator
template <std::size_t I, class Segregator, class Fallback>
auto get_segregatable_allocator(binary_segregator<Segregator, Fallback>& s)
-> segregatable_allocator_type<I, binary_segregator<Segregator, Fallback>>&
{
return detail::segregatable_type<I, binary_segregator<Segregator, Fallback>>::get(s);
}
template <std::size_t I, class Segregator, class Fallback>
auto get_segregatable_allocator(const binary_segregator<Segregator, Fallback>& s)
-> const segregatable_allocator_type<I, binary_segregator<Segregator, Fallback>>
{
return detail::segregatable_type<I, binary_segregator<Segregator, Fallback>>::get(s);
}
/// @}
/// The type of the final fallback \concept{concept_rawallocator,RawAllocator}.
/// \relates segregator
template <class Segregator>
using fallback_allocator_type = typename detail::fallback_type<Segregator>::type;
/// @{
/// \returns The final fallback \concept{concept_rawallocator,RawAllocator}.
/// \relates segregator
template <class Segregator, class Fallback>
auto get_fallback_allocator(binary_segregator<Segregator, Fallback>& s)
-> fallback_allocator_type<binary_segregator<Segregator, Fallback>>&
{
return detail::fallback_type<binary_segregator<Segregator, Fallback>>::get(s);
}
template <class Segregator, class Fallback>
auto get_fallback_allocator(const binary_segregator<Segregator, Fallback>& s)
-> const fallback_allocator_type<binary_segregator<Segregator, Fallback>>&
{
return detail::fallback_type<binary_segregator<Segregator, Fallback>>::get(s);
}
/// @}
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_SEGREGATOR_HPP_INCLUDED

View File

@@ -0,0 +1,210 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_SMART_PTR_HPP_INCLUDED
#define WPI_MEMORY_SMART_PTR_HPP_INCLUDED
/// \file
/// \c std::make_unique() / \c std::make_shared() replacement allocating memory through a \concept{concept_rawallocator,RawAllocator}.
/// \note Only available on a hosted implementation.
#include "config.hpp"
#if !WPI_HOSTED_IMPLEMENTATION
#error "This header is only available for a hosted implementation."
#endif
#include <memory>
#include <type_traits>
#include "detail/utility.hpp"
#include "deleter.hpp"
#include "std_allocator.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
template <typename T, class RawAllocator, typename... Args>
auto allocate_unique(allocator_reference<RawAllocator> alloc, Args&&... args)
-> std::unique_ptr<T, allocator_deleter<T, RawAllocator>>
{
using raw_ptr = std::unique_ptr<T, allocator_deallocator<T, RawAllocator>>;
auto memory = alloc.allocate_node(sizeof(T), alignof(T));
// raw_ptr deallocates memory in case of constructor exception
raw_ptr result(static_cast<T*>(memory), {alloc});
// call constructor
::new (memory) T(detail::forward<Args>(args)...);
// pass ownership to return value using a deleter that calls destructor
return {result.release(), {alloc}};
}
template <typename T, typename... Args>
void construct(std::true_type, T* cur, T* end, Args&&... args)
{
for (; cur != end; ++cur)
::new (static_cast<void*>(cur)) T(detail::forward<Args>(args)...);
}
template <typename T, typename... Args>
void construct(std::false_type, T* begin, T* end, Args&&... args)
{
#if WPI_HAS_EXCEPTION_SUPPORT
auto cur = begin;
try
{
for (; cur != end; ++cur)
::new (static_cast<void*>(cur)) T(detail::forward<Args>(args)...);
}
catch (...)
{
for (auto el = begin; el != cur; ++el)
el->~T();
throw;
}
#else
construct(std::true_type{}, begin, end, detail::forward<Args>(args)...);
#endif
}
template <typename T, class RawAllocator>
auto allocate_array_unique(std::size_t size, allocator_reference<RawAllocator> alloc)
-> std::unique_ptr<T[], allocator_deleter<T[], RawAllocator>>
{
using raw_ptr = std::unique_ptr<T[], allocator_deallocator<T[], RawAllocator>>;
auto memory = alloc.allocate_array(size, sizeof(T), alignof(T));
// raw_ptr deallocates memory in case of constructor exception
raw_ptr result(static_cast<T*>(memory), {alloc, size});
construct(std::integral_constant<bool, noexcept(T())>{}, result.get(),
result.get() + size);
// pass ownership to return value using a deleter that calls destructor
return {result.release(), {alloc, size}};
}
} // namespace detail
/// A \c std::unique_ptr that deletes using a \concept{concept_rawallocator,RawAllocator}.
///
/// It is an alias template using \ref allocator_deleter as \c Deleter class.
/// \ingroup adapter
template <typename T, class RawAllocator>
WPI_ALIAS_TEMPLATE(unique_ptr,
std::unique_ptr<T, allocator_deleter<T, RawAllocator>>);
/// A \c std::unique_ptr that deletes using a \concept{concept_rawallocator,RawAllocator} and allows polymorphic types.
///
/// It can only be created by converting a regular unique pointer to a pointer to a derived class,
/// and is meant to be used inside containers.
/// It is an alias template using \ref allocator_polymorphic_deleter as \c Deleter class.
/// \note It has a relatively high overhead, so only use it if you have to.
/// \ingroup adapter
template <class BaseType, class RawAllocator>
WPI_ALIAS_TEMPLATE(
unique_base_ptr,
std::unique_ptr<BaseType, allocator_polymorphic_deleter<BaseType, RawAllocator>>);
/// Creates a \c std::unique_ptr using a \concept{concept_rawallocator,RawAllocator} for the allocation.
/// \effects Allocates memory for the given type using the allocator
/// and creates a new object inside it passing the given arguments to its constructor.
/// \returns A \c std::unique_ptr owning that memory.
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the deleter,
/// the caller has to ensure that the object lives as long as the smart pointer.
/// \ingroup adapter
template <typename T, class RawAllocator, typename... Args>
auto allocate_unique(RawAllocator&& alloc, Args&&... args) -> WPI_REQUIRES_RET(
!std::is_array<T>::value,
std::unique_ptr<T, allocator_deleter<T, typename std::decay<RawAllocator>::type>>)
{
return detail::allocate_unique<T>(make_allocator_reference(
detail::forward<RawAllocator>(alloc)),
detail::forward<Args>(args)...);
}
/// Creates a \c std::unique_ptr using a type-erased \concept{concept_rawallocator,RawAllocator} for the allocation.
/// It is the same as the other overload but stores the reference to the allocator type-erased inside the \c std::unique_ptr.
/// \effects Allocates memory for the given type using the allocator
/// and creates a new object inside it passing the given arguments to its constructor.
/// \returns A \c std::unique_ptr with a type-erased allocator reference owning that memory.
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the deleter,
/// the caller has to ensure that the object lives as long as the smart pointer.
/// \ingroup adapter
template <typename T, class RawAllocator, typename... Args>
auto allocate_unique(any_allocator, RawAllocator&& alloc, Args&&... args)
-> WPI_REQUIRES_RET(!std::is_array<T>::value,
std::unique_ptr<T, allocator_deleter<T, any_allocator>>)
{
return detail::allocate_unique<T, any_allocator>(make_allocator_reference(
detail::forward<RawAllocator>(
alloc)),
detail::forward<Args>(args)...);
}
/// Creates a \c std::unique_ptr owning an array using a \concept{concept_rawallocator,RawAllocator} for the allocation.
/// \effects Allocates memory for an array of given size and value initializes each element inside of it.
/// \returns A \c std::unique_ptr owning that array.
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the deleter,
/// the caller has to ensure that the object lives as long as the smart pointer.
/// \ingroup adapter
template <typename T, class RawAllocator>
auto allocate_unique(RawAllocator&& alloc, std::size_t size) -> WPI_REQUIRES_RET(
std::is_array<T>::value,
std::unique_ptr<T, allocator_deleter<T, typename std::decay<RawAllocator>::type>>)
{
return detail::allocate_array_unique<
typename std::remove_extent<T>::type>(size,
make_allocator_reference(
detail::forward<RawAllocator>(alloc)));
}
/// Creates a \c std::unique_ptr owning an array using a type-erased \concept{concept_rawallocator,RawAllocator} for the allocation.
/// It is the same as the other overload but stores the reference to the allocator type-erased inside the \c std::unique_ptr.
/// \effects Allocates memory for an array of given size and value initializes each element inside of it.
/// \returns A \c std::unique_ptr with a type-erased allocator reference owning that array.
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the deleter,
/// the caller has to ensure that the object lives as long as the smart pointer.
/// \ingroup adapter
template <typename T, class RawAllocator>
auto allocate_unique(any_allocator, RawAllocator&& alloc, std::size_t size)
-> WPI_REQUIRES_RET(std::is_array<T>::value,
std::unique_ptr<T, allocator_deleter<T, any_allocator>>)
{
return detail::allocate_array_unique<typename std::remove_extent<T>::type,
any_allocator>(size,
make_allocator_reference(
detail::forward<RawAllocator>(
alloc)));
}
/// Creates a \c std::shared_ptr using a \concept{concept_rawallocator,RawAllocator} for the allocation.
/// It is similar to \c std::allocate_shared but uses a \c RawAllocator (and thus also supports any \c Allocator).
/// \effects Calls \ref std_allocator::make_std_allocator to wrap the allocator and forwards to \c std::allocate_shared.
/// \returns A \c std::shared_ptr created using \c std::allocate_shared.
/// \note If the allocator is stateful a reference to the \c RawAllocator will be stored inside the shared pointer,
/// the caller has to ensure that the object lives as long as the smart pointer.
/// \ingroup adapter
template <typename T, class RawAllocator, typename... Args>
std::shared_ptr<T> allocate_shared(RawAllocator&& alloc, Args&&... args)
{
return std::allocate_shared<T>(make_std_allocator<T>(
detail::forward<RawAllocator>(alloc)),
detail::forward<Args>(args)...);
}
#if !defined(DOXYGEN)
#include "detail/container_node_sizes.hpp"
#else
/// Contains the node size needed for a `std::shared_ptr`.
/// These classes are auto-generated and only available if the tools are build and without cross-compiling.
/// \ingroup adapter
template <typename T>
struct shared_ptr_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
#endif
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_SMART_PTR_HPP_INCLUDED

View File

@@ -0,0 +1,179 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_STATIC_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_STATIC_ALLOCATOR_HPP_INCLUDED
/// \file
/// Allocators using a static, fixed-sized storage.
#include <type_traits>
#include "detail/align.hpp"
#include "detail/assert.hpp"
#include "detail/memory_stack.hpp"
#include "detail/utility.hpp"
#include "config.hpp"
#if WPI_MEMORY_EXTERN_TEMPLATE
#include "allocator_traits.hpp"
#endif
namespace wpi
{
namespace memory
{
/// Storage for a \ref static_allocator.
/// Its constructor will take a reference to it and use it for its allocation.
/// The storage type is simply a \c char array aligned for maximum alignment.
/// \note It is not allowed to access the memory of the storage.
/// \ingroup allocator
template <std::size_t Size>
struct static_allocator_storage
{
alignas(detail::max_alignment) char storage[Size];
};
static_assert(sizeof(static_allocator_storage<1024>) == 1024, "");
static_assert(alignof(static_allocator_storage<1024>) == detail::max_alignment, "");
struct allocator_info;
/// A stateful \concept{concept_rawallocator,RawAllocator} that uses a fixed sized storage for the allocations.
/// It works on a \ref static_allocator_storage and uses its memory for all allocations.
/// Deallocations are not supported, memory cannot be marked as freed.<br>
/// \note It is not allowed to share an \ref static_allocator_storage between multiple \ref static_allocator objects.
/// \ingroup allocator
class static_allocator
{
public:
using is_stateful = std::true_type;
/// \effects Creates it by passing it a \ref static_allocator_storage by reference.
/// It will take the address of the storage and use its memory for the allocation.
/// \requires The storage object must live as long as the allocator object.
/// It must not be shared between multiple allocators,
/// i.e. the object must not have been passed to a constructor before.
template <std::size_t Size>
static_allocator(static_allocator_storage<Size>& storage) noexcept
: stack_(&storage), end_(stack_.top() + Size)
{
}
/// \effects A \concept{concept_rawallocator,RawAllocator} allocation function.
/// It uses the specified \ref static_allocator_storage.
/// \returns A pointer to a \concept{concept_node,node}, it will never be \c nullptr.
/// \throws An exception of type \ref out_of_memory or whatever is thrown by its handler if the storage is exhausted.
void* allocate_node(std::size_t size, std::size_t alignment);
/// \effects A \concept{concept_rawallocator,RawAllocator} deallocation function.
/// It does nothing, deallocation is not supported by this allocator.
void deallocate_node(void*, std::size_t, std::size_t) noexcept {}
/// \returns The maximum node size which is the capacity remaining inside the \ref static_allocator_storage.
std::size_t max_node_size() const noexcept
{
return static_cast<std::size_t>(end_ - stack_.top());
}
/// \returns The maximum possible value since there is no alignment restriction
/// (except indirectly through the size of the \ref static_allocator_storage).
std::size_t max_alignment() const noexcept
{
return std::size_t(-1);
}
private:
allocator_info info() const noexcept;
detail::fixed_memory_stack stack_;
const char* end_;
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class allocator_traits<static_allocator>;
#endif
struct memory_block;
/// A \concept{concept_blockallocator,BlockAllocator} that allocates the blocks from a fixed size storage.
/// It works on a \ref static_allocator_storage and uses it for all allocations,
/// deallocations are only allowed in reversed order which is guaranteed by \ref memory_arena.
/// \note It is not allowed to share an \ref static_allocator_storage between multiple \ref static_allocator objects.
/// \ingroup allocator
class static_block_allocator
{
public:
/// \effects Creates it by passing it the block size and a \ref static_allocator_storage by reference.
/// It will take the address of the storage and use it to allocate \c block_size'd blocks.
/// \requires The storage object must live as long as the allocator object.
/// It must not be shared between multiple allocators,
/// i.e. the object must not have been passed to a constructor before.
/// The size of the \ref static_allocator_storage must be a multiple of the (non-null) block size.
template <std::size_t Size>
static_block_allocator(std::size_t block_size,
static_allocator_storage<Size>& storage) noexcept
: cur_(static_cast<char*>(static_cast<void*>(&storage))),
end_(cur_ + Size),
block_size_(block_size)
{
WPI_MEMORY_ASSERT(block_size <= Size);
WPI_MEMORY_ASSERT(Size % block_size == 0u);
}
~static_block_allocator() noexcept = default;
/// @{
/// \effects Moves the block allocator, it transfers ownership over the \ref static_allocator_storage.
/// This does not invalidate any memory blocks.
static_block_allocator(static_block_allocator&& other) noexcept
: cur_(other.cur_), end_(other.end_), block_size_(other.block_size_)
{
other.cur_ = other.end_ = nullptr;
other.block_size_ = 0;
}
static_block_allocator& operator=(static_block_allocator&& other) noexcept
{
static_block_allocator tmp(detail::move(other));
swap(*this, tmp);
return *this;
}
/// @}
/// \effects Swaps the ownership over the \ref static_allocator_storage.
/// This does not invalidate any memory blocks.
friend void swap(static_block_allocator& a, static_block_allocator& b) noexcept
{
detail::adl_swap(a.cur_, b.cur_);
detail::adl_swap(a.end_, b.end_);
detail::adl_swap(a.block_size_, b.block_size_);
}
/// \effects Allocates a new block by returning the \ref next_block_size() bytes.
/// \returns The new memory block.
memory_block allocate_block();
/// \effects Deallocates the last memory block by marking the block as free again.
/// This block will be returned again by the next call to \ref allocate_block().
/// \requires \c block must be the current top block of the memory,
/// this is guaranteed by \ref memory_arena.
void deallocate_block(memory_block block) noexcept;
/// \returns The next block size, this is the size passed to the constructor.
std::size_t next_block_size() const noexcept
{
return block_size_;
}
private:
allocator_info info() const noexcept;
char * cur_, *end_;
std::size_t block_size_;
};
} // namespace memory
} // namespace wpi
#endif //WPI_MEMORY_STATIC_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,360 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_STD_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_STD_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::std_allocator and related classes and functions.
#include <new>
#include <type_traits>
#include "detail/utility.hpp"
#include "config.hpp"
#include "allocator_storage.hpp"
#include "threading.hpp"
namespace wpi
{
namespace memory
{
namespace traits_detail
{
template <class RawAllocator>
auto propagate_on_container_swap(std_concept) ->
typename RawAllocator::propagate_on_container_swap;
template <class RawAllocator>
auto propagate_on_container_swap(min_concept) -> std::true_type;
template <class RawAllocator>
auto propagate_on_container_move_assignment(std_concept) ->
typename RawAllocator::propagate_on_container_move_assignment;
template <class RawAllocator>
auto propagate_on_container_move_assignment(min_concept) -> std::true_type;
template <class RawAllocator>
auto propagate_on_container_copy_assignment(std_concept) ->
typename RawAllocator::propagate_on_container_copy_assignment;
template <class RawAllocator>
auto propagate_on_container_copy_assignment(min_concept) -> std::true_type;
} // namespace traits_detail
/// Controls the propagation of a \ref std_allocator for a certain \concept{concept_rawallocator,RawAllocator}.
/// \ingroup adapter
template <class RawAllocator>
struct propagation_traits
{
using propagate_on_container_swap =
decltype(traits_detail::propagate_on_container_swap<RawAllocator>(
traits_detail::full_concept{}));
using propagate_on_container_move_assignment =
decltype(traits_detail::propagate_on_container_move_assignment<RawAllocator>(
traits_detail::full_concept{}));
using propagate_on_container_copy_assignment =
decltype(traits_detail::propagate_on_container_copy_assignment<RawAllocator>(
traits_detail::full_concept{}));
template <class AllocReference>
static AllocReference select_on_container_copy_construction(const AllocReference& alloc)
{
return alloc;
}
};
/// Wraps a \concept{concept_rawallocator,RawAllocator} and makes it a "normal" \c Allocator.
/// It allows using a \c RawAllocator anywhere a \c Allocator is required.
/// \ingroup adapter
template <typename T, class RawAllocator>
class std_allocator :
#if defined _MSC_VER && defined __clang__
WPI_EBO(protected allocator_reference<RawAllocator>)
#else
WPI_EBO(allocator_reference<RawAllocator>)
#endif
{
using alloc_reference = allocator_reference<RawAllocator>;
// if it is any_allocator_reference an optimized implementation can be used
using is_any = std::is_same<alloc_reference, any_allocator_reference>;
using prop_traits = propagation_traits<RawAllocator>;
public:
//=== typedefs ===//
using value_type = T;
using pointer = T*;
using const_pointer = const T*;
using reference = T&;
using const_reference = const T&;
using size_type = std::size_t;
using difference_type = std::ptrdiff_t;
using propagate_on_container_swap = typename prop_traits::propagate_on_container_swap;
using propagate_on_container_move_assignment =
typename prop_traits::propagate_on_container_move_assignment;
using propagate_on_container_copy_assignment =
typename prop_traits::propagate_on_container_copy_assignment;
template <typename U>
struct rebind
{
using other = std_allocator<U, RawAllocator>;
};
using allocator_type = typename alloc_reference::allocator_type;
//=== constructor ===//
/// \effects Default constructs it by storing a default constructed, stateless \c RawAllocator inside the reference.
/// \requires The \c RawAllocator type is stateless, otherwise the body of this function will not compile.
std_allocator() noexcept : alloc_reference(allocator_type{})
{
#if !defined(__GNUC__) || (defined(_GLIBCXX_USE_CXX11_ABI) && _GLIBCXX_USE_CXX11_ABI != 0)
// std::string requires default constructor for the small string optimization when using gcc's old ABI
// so don't assert then to allow joint allocator
static_assert(!alloc_reference::is_stateful::value,
"default constructor must not be used for stateful allocators");
#endif
}
/// \effects Creates it from a reference to a \c RawAllocator.
/// It will store an \ref allocator_reference to it.
/// \requires The expression <tt>allocator_reference<RawAllocator>(alloc)</tt> is well-formed,
/// that is either \c RawAlloc is the same as \c RawAllocator or \c RawAllocator is the tag type \ref any_allocator.
/// If the requirement is not fulfilled this function does not participate in overload resolution.
/// \note The caller has to ensure that the lifetime of the \c RawAllocator is at least as long as the lifetime
/// of this \ref std_allocator object.
template <
class RawAlloc,
// MSVC seems to ignore access rights in decltype SFINAE below
// use this to prevent this constructor being chosen instead of move/copy for types inheriting from it
WPI_REQUIRES((!std::is_base_of<std_allocator, RawAlloc>::value))>
std_allocator(RawAlloc& alloc, WPI_SFINAE(alloc_reference(alloc))) noexcept
: alloc_reference(alloc)
{
}
/// \effects Creates it from a stateless, temporary \c RawAllocator object.
/// It will not store a reference but create it on the fly.
/// \requires The \c RawAllocator is stateless
/// and the expression <tt>allocator_reference<RawAllocator>(alloc)</tt> is well-formed as above,
/// otherwise this function does not participate in overload resolution.
template <
class RawAlloc,
// MSVC seems to ignore access rights in decltype SFINAE below
// use this to prevent this constructor being chosen instead of move/copy for types inheriting from it
WPI_REQUIRES((!std::is_base_of<std_allocator, RawAlloc>::value))>
std_allocator(const RawAlloc& alloc, WPI_SFINAE(alloc_reference(alloc))) noexcept
: alloc_reference(alloc)
{
}
/// \effects Creates it from another \ref allocator_reference using the same allocator type.
std_allocator(const alloc_reference& alloc) noexcept : alloc_reference(alloc) {}
/// \details Implicit conversion from any other \ref allocator_storage is forbidden
/// to prevent accidentally wrapping another \ref allocator_storage inside a \ref allocator_reference.
template <class StoragePolicy, class OtherMut>
std_allocator(const allocator_storage<StoragePolicy, OtherMut>&) = delete;
/// @{
/// \effects Creates it from another \ref std_allocator allocating a different type.
/// This is required by the \c Allcoator concept and simply takes the same \ref allocator_reference.
template <typename U>
std_allocator(const std_allocator<U, RawAllocator>& alloc) noexcept
: alloc_reference(alloc)
{
}
template <typename U>
std_allocator(std_allocator<U, RawAllocator>& alloc) noexcept : alloc_reference(alloc)
{
}
/// @}
/// \returns A copy of the allocator.
/// This is required by the \c Allocator concept and forwards to the \ref propagation_traits.
std_allocator<T, RawAllocator> select_on_container_copy_construction() const
{
return prop_traits::select_on_container_copy_construction(*this);
}
//=== allocation/deallocation ===//
/// \effects Allocates memory using the underlying \concept{concept_rawallocator,RawAllocator}.
/// If \c n is \c 1, it will call <tt>allocate_node(sizeof(T), alignof(T))</tt>,
/// otherwise <tt>allocate_array(n, sizeof(T), alignof(T))</tt>.
/// \returns A pointer to a memory block suitable for \c n objects of type \c T.
/// \throws Anything thrown by the \c RawAllocator.
pointer allocate(size_type n, void* = nullptr)
{
return static_cast<pointer>(allocate_impl(is_any{}, n));
}
/// \effects Deallcoates memory using the underlying \concept{concept_rawallocator,RawAllocator}.
/// It will forward to the deallocation function in the same way as in \ref allocate().
/// \requires The pointer must come from a previous call to \ref allocate() with the same \c n on this object or any copy of it.
void deallocate(pointer p, size_type n) noexcept
{
deallocate_impl(is_any{}, p, n);
}
//=== construction/destruction ===//
/// \effects Creates an object of type \c U at given address using the passed arguments.
template <typename U, typename... Args>
void construct(U* p, Args&&... args)
{
void* mem = p;
::new (mem) U(detail::forward<Args>(args)...);
}
/// \effects Calls the destructor for an object of type \c U at given address.
template <typename U>
void destroy(U* p) noexcept
{
// This is to avoid a MSVS 2015 'unreferenced formal parameter' warning
(void)p;
p->~U();
}
//=== getter ===//
/// \returns The maximum size for an allocation which is <tt>max_array_size() / sizeof(value_type)</tt>.
/// This is only an upper bound, not the exact maximum.
size_type max_size() const noexcept
{
return this->max_array_size() / sizeof(value_type);
}
/// @{
/// \effects Returns a reference to the referenced allocator.
/// \returns For stateful allocators: A (\c const) reference to the stored allocator.
/// For stateless allocators: A temporary constructed allocator.
auto get_allocator() noexcept
-> decltype(std::declval<alloc_reference>().get_allocator())
{
return alloc_reference::get_allocator();
}
auto get_allocator() const noexcept
-> decltype(std::declval<const alloc_reference>().get_allocator())
{
return alloc_reference::get_allocator();
}
/// @}
private:
// any_allocator_reference: use virtual function which already does a dispatch on node/array
void* allocate_impl(std::true_type, size_type n)
{
return get_allocator().allocate_impl(n, sizeof(T), alignof(T));
}
void deallocate_impl(std::true_type, void* ptr, size_type n)
{
get_allocator().deallocate_impl(ptr, n, sizeof(T), alignof(T));
}
// alloc_reference: decide between node/array
void* allocate_impl(std::false_type, size_type n)
{
if (n == 1)
return this->allocate_node(sizeof(T), alignof(T));
else
return this->allocate_array(n, sizeof(T), alignof(T));
}
void deallocate_impl(std::false_type, void* ptr, size_type n)
{
if (n == 1)
this->deallocate_node(ptr, sizeof(T), alignof(T));
else
this->deallocate_array(ptr, n, sizeof(T), alignof(T));
}
template <typename U> // stateful
bool equal_to_impl(std::true_type,
const std_allocator<U, RawAllocator>& other) const noexcept
{
return &get_allocator() == &other.get_allocator();
}
template <typename U> // non-stateful
bool equal_to_impl(std::false_type,
const std_allocator<U, RawAllocator>&) const noexcept
{
return true;
}
template <typename U> // shared
bool equal_to(std::true_type,
const std_allocator<U, RawAllocator>& other) const noexcept
{
return get_allocator() == other.get_allocator();
}
template <typename U> // not shared
bool equal_to(std::false_type,
const std_allocator<U, RawAllocator>& other) const noexcept
{
return equal_to_impl(typename allocator_traits<RawAllocator>::is_stateful{}, other);
}
template <typename T1, typename T2, class Impl>
friend bool operator==(const std_allocator<T1, Impl>& lhs,
const std_allocator<T2, Impl>& rhs) noexcept;
template <typename U, class OtherRawAllocator>
friend class std_allocator;
};
/// \effects Compares two \ref std_allocator object, they are equal if either stateless or reference the same allocator.
/// \returns The result of the comparision for equality.
/// \relates std_allocator
template <typename T, typename U, class Impl>
bool operator==(const std_allocator<T, Impl>& lhs,
const std_allocator<U, Impl>& rhs) noexcept
{
return lhs.equal_to(is_shared_allocator<Impl>{}, rhs);
}
/// \effects Compares two \ref std_allocator object, they are equal if either stateless or reference the same allocator.
/// \returns The result of the comparision for inequality.
/// \relates std_allocator
template <typename T, typename U, class Impl>
bool operator!=(const std_allocator<T, Impl>& lhs,
const std_allocator<U, Impl>& rhs) noexcept
{
return !(lhs == rhs);
}
/// \returns A new \ref std_allocator for a given type using a certain allocator object.
/// \relates std_allocator
template <typename T, class RawAllocator>
auto make_std_allocator(RawAllocator&& allocator) noexcept
-> std_allocator<T, typename std::decay<RawAllocator>::type>
{
return {detail::forward<RawAllocator>(allocator)};
}
/// An alias template for \ref std_allocator using a type-erased \concept{concept_rawallocator,RawAllocator}.
/// This is the same as using a \ref std_allocator with the tag type \ref any_allocator.
/// The implementation is optimized to call fewer virtual functions.
/// \ingroup adapter
template <typename T>
WPI_ALIAS_TEMPLATE(any_std_allocator, std_allocator<T, any_allocator>);
/// \returns A new \ref any_std_allocator for a given type using a certain allocator object.
/// \relates any_std_allocator
template <typename T, class RawAllocator>
any_std_allocator<T> make_any_std_allocator(RawAllocator&& allocator) noexcept
{
return {detail::forward<RawAllocator>(allocator)};
}
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_STD_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,335 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED
#define WPI_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::temporary_allocator and related functions.
#include "config.hpp"
#include "memory_stack.hpp"
#if WPI_MEMORY_TEMPORARY_STACK_MODE >= 2
#include <atomic>
#endif
namespace wpi
{
namespace memory
{
class temporary_allocator;
class temporary_stack;
namespace detail
{
class temporary_block_allocator
{
public:
explicit temporary_block_allocator(std::size_t block_size) noexcept;
memory_block allocate_block();
void deallocate_block(memory_block block);
std::size_t next_block_size() const noexcept
{
return block_size_;
}
using growth_tracker = void (*)(std::size_t size);
growth_tracker set_growth_tracker(growth_tracker t) noexcept;
growth_tracker get_growth_tracker() noexcept;
private:
growth_tracker tracker_;
std::size_t block_size_;
};
using temporary_stack_impl = memory_stack<temporary_block_allocator>;
class temporary_stack_list;
#if WPI_MEMORY_TEMPORARY_STACK_MODE >= 2
class temporary_stack_list_node
{
public:
// doesn't add into list
temporary_stack_list_node() noexcept : in_use_(true) {}
temporary_stack_list_node(int) noexcept;
~temporary_stack_list_node() noexcept {}
private:
temporary_stack_list_node* next_ = nullptr;
std::atomic<bool> in_use_;
friend temporary_stack_list;
};
static class temporary_allocator_dtor_t
{
public:
temporary_allocator_dtor_t() noexcept;
~temporary_allocator_dtor_t() noexcept;
} temporary_allocator_dtor;
#else
class temporary_stack_list_node
{
protected:
temporary_stack_list_node() noexcept {}
temporary_stack_list_node(int) noexcept {}
~temporary_stack_list_node() noexcept {}
};
#endif
} // namespace detail
/// A wrapper around the \ref memory_stack that is used by the \ref temporary_allocator.
/// There should be at least one per-thread.
/// \ingroup allocator
class temporary_stack : WPI_EBO(detail::temporary_stack_list_node)
{
public:
/// The type of the handler called when the internal \ref memory_stack grows.
/// It gets the size of the new block that will be allocated.
/// \requiredbe The handler shall log the growth, throw an exception or aborts the program.
/// If this function does not return, the growth is prevented but the allocator unusable until memory is freed.
/// \defaultbe The default handler does nothing.
using growth_tracker = detail::temporary_block_allocator::growth_tracker;
/// \effects Sets \c h as the new \ref growth_tracker.
/// A \c nullptr sets the default \ref growth_tracker.
/// Each thread has its own, separate tracker.
/// \returns The previous \ref growth_tracker. This is never \c nullptr.
growth_tracker set_growth_tracker(growth_tracker t) noexcept
{
return stack_.get_allocator().set_growth_tracker(t);
}
/// \returns The current \ref growth_tracker. This is never \c nullptr.
growth_tracker get_growth_tracker() noexcept
{
return stack_.get_allocator().get_growth_tracker();
}
/// \effects Creates it with a given initial size of the stack.
/// It can grow if needed, although that is expensive.
/// \requires `initial_size` must be greater than `0`.
explicit temporary_stack(std::size_t initial_size) : stack_(initial_size), top_(nullptr)
{
}
/// \returns `next_capacity()` of the internal `memory_stack`.
std::size_t next_capacity() const noexcept
{
return stack_.next_capacity();
}
private:
temporary_stack(int i, std::size_t initial_size)
: detail::temporary_stack_list_node(i), stack_(initial_size), top_(nullptr)
{
}
using marker = detail::temporary_stack_impl::marker;
marker top() const noexcept
{
return stack_.top();
}
void unwind(marker m) noexcept
{
stack_.unwind(m);
}
detail::temporary_stack_impl stack_;
temporary_allocator* top_;
#if !defined(DOXYGEN)
friend temporary_allocator;
friend memory_stack_raii_unwind<temporary_stack>;
friend detail::temporary_stack_list;
#endif
};
/// Manually takes care of the lifetime of the per-thread \ref temporary_stack.
/// The constructor will create it, if not already done, and the destructor will destroy it, if not already done.
/// \note If there are multiple objects in a thread,
/// this will lead to unnecessary construction and destruction of the stack.
/// It is thus adviced to create one object on the top-level function of the thread, e.g. in `main()`.
/// \note If `WPI_MEMORY_TEMPORARY_STACK_MODE == 2`, it is not necessary to use this class,
/// the nifty counter will clean everything upon program termination.
/// But it can still be used as an optimization if you have a thread that is terminated long before program exit.
/// The automatic clean up will only occur much later.
/// \note If `WPI_MEMORY_TEMPORARY_STACK_MODE == 0`, the use of this class has no effect,
/// because the per-thread stack is disabled.
/// \relatesalso temporary_stack
class temporary_stack_initializer
{
public:
static constexpr std::size_t default_stack_size = 4096u;
static const struct defer_create_t
{
defer_create_t() noexcept {}
} defer_create;
/// \effects Does not create the per-thread stack.
/// It will be created by the first call to \ref get_temporary_stack() in the current thread.
/// \note If `WPI_MEMORY_TEMPORARY_STACK_MODE == 0`, this function has no effect.
temporary_stack_initializer(defer_create_t) noexcept {}
/// \effects Creates the per-thread stack with the given default size if it wasn't already created.
/// \requires `initial_size` must not be `0` if `WPI_MEMORY_TEMPORARY_STACK_MODE != 0`.
/// \note If `WPI_MEMORY_TEMPORARY_STACK_MODE == 0`, this function will issue a warning in debug mode.
/// This can be disabled by passing `0` as the initial size.
temporary_stack_initializer(std::size_t initial_size = default_stack_size);
/// \effects Destroys the per-thread stack if it isn't already destroyed.
~temporary_stack_initializer() noexcept;
temporary_stack_initializer(temporary_stack_initializer&&) = delete;
temporary_stack_initializer& operator=(temporary_stack_initializer&&) = delete;
};
/// \effects Creates the per-thread \ref temporary_stack with the given initial size,
/// if it wasn't already created.
/// \returns The per-thread \ref temporary_stack.
/// \requires There must be a per-thread temporary stack (\ref WPI_MEMORY_TEMPORARY_STACK_MODE must not be equal to `0`).
/// \note If \ref WPI_MEMORY_TEMPORARY_STACK_MODE is equal to `1`,
/// this function can create the temporary stack.
/// But if there is no \ref temporary_stack_initializer, it won't be destroyed.
/// \relatesalso temporary_stack
temporary_stack& get_temporary_stack(
std::size_t initial_size = temporary_stack_initializer::default_stack_size);
/// A stateful \concept{concept_rawallocator,RawAllocator} that handles temporary allocations.
/// It works similar to \c alloca() but uses a seperate \ref memory_stack for the allocations,
/// instead of the actual program stack.
/// This avoids the stack overflow error and is portable,
/// with a similar speed.
/// All allocations done in the scope of the allocator object are automatically freed when the object is destroyed.
/// \ingroup allocator
class temporary_allocator
{
public:
/// \effects Creates it by using the \ref get_temporary_stack() to get the temporary stack.
/// \requires There must be a per-thread temporary stack (\ref WPI_MEMORY_TEMPORARY_STACK_MODE must not be equal to `0`).
temporary_allocator();
/// \effects Creates it by giving it the \ref temporary_stack it uses for allocation.
explicit temporary_allocator(temporary_stack& stack);
~temporary_allocator() noexcept;
temporary_allocator(temporary_allocator&&) = delete;
temporary_allocator& operator=(temporary_allocator&&) = delete;
/// \effects Allocates memory from the internal \ref memory_stack by forwarding to it.
/// \returns The result of \ref memory_stack::allocate().
/// \requires `is_active()` must return `true`.
void* allocate(std::size_t size, std::size_t alignment);
/// \returns Whether or not the allocator object is active.
/// \note The active allocator object is the last object created for one stack.
/// Moving changes the active allocator.
bool is_active() const noexcept;
/// \effects Instructs it to release unnecessary memory after automatic unwinding occurs.
/// This will effectively forward to \ref memory_stack::shrink_to_fit() of the internal stack.
/// \note Like the use of the \ref temporary_stack_initializer this can be used as an optimization,
/// to tell when the thread's \ref temporary_stack isn't needed anymore and can be destroyed.
/// \note It doesn't call shrink to fit immediately, only in the destructor!
void shrink_to_fit() noexcept;
/// \returns The internal stack the temporary allocator is using.
/// \requires `is_active()` must return `true`.
temporary_stack& get_stack() const noexcept
{
return unwind_.get_stack();
}
private:
memory_stack_raii_unwind<temporary_stack> unwind_;
temporary_allocator* prev_;
bool shrink_to_fit_;
};
template <class Allocator>
class allocator_traits;
/// Specialization of the \ref allocator_traits for \ref temporary_allocator classes.
/// \note It is not allowed to mix calls through the specialization and through the member functions,
/// i.e. \ref temporary_allocator::allocate() and this \c allocate_node().
/// \ingroup allocator
template <>
class allocator_traits<temporary_allocator>
{
public:
using allocator_type = temporary_allocator;
using is_stateful = std::true_type;
/// \returns The result of \ref temporary_allocator::allocate().
static void* allocate_node(allocator_type& state, std::size_t size,
std::size_t alignment)
{
detail::check_allocation_size<bad_node_size>(size,
[&] { return max_node_size(state); },
{WPI_MEMORY_LOG_PREFIX
"::temporary_allocator",
&state});
return state.allocate(size, alignment);
}
/// \returns The result of \ref temporary_allocator::allocate().
static void* allocate_array(allocator_type& state, std::size_t count, std::size_t size,
std::size_t alignment)
{
return allocate_node(state, count * size, alignment);
}
/// @{
/// \effects Does nothing besides bookmarking for leak checking, if that is enabled.
/// Actual deallocation will be done automatically if the allocator object goes out of scope.
static void deallocate_node(const allocator_type&, void*, std::size_t,
std::size_t) noexcept
{
}
static void deallocate_array(const allocator_type&, void*, std::size_t, std::size_t,
std::size_t) noexcept
{
}
/// @}
/// @{
/// \returns The maximum size which is \ref memory_stack::next_capacity() of the internal stack.
static std::size_t max_node_size(const allocator_type& state) noexcept
{
return state.get_stack().next_capacity();
}
static std::size_t max_array_size(const allocator_type& state) noexcept
{
return max_node_size(state);
}
/// @}
/// \returns The maximum possible value since there is no alignment restriction
/// (except indirectly through \ref memory_stack::next_capacity()).
static std::size_t max_alignment(const allocator_type&) noexcept
{
return std::size_t(-1);
}
};
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_TEMPORARY_ALLOCATOR_HPP_INCLUDED

View File

@@ -0,0 +1,155 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_THREADING_HPP_INCLUDED
#define WPI_MEMORY_THREADING_HPP_INCLUDED
/// \file
/// The mutex types.
#include <type_traits>
#include "allocator_traits.hpp"
#include "config.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include <mutex>
#endif
namespace wpi
{
namespace memory
{
/// A dummy \c Mutex class that does not lock anything.
/// It is a valid \c Mutex and can be used to disable locking anywhere a \c Mutex is requested.
/// \ingroup core
struct no_mutex
{
void lock() noexcept {}
bool try_lock() noexcept
{
return true;
}
void unlock() noexcept {}
};
/// Specifies whether or not a \concept{concept_rawallocator,RawAllocator} is thread safe as-is.
/// This allows to use \ref no_mutex as an optimization.
/// Note that stateless allocators are implictly thread-safe.
/// Specialize it only for your own stateful allocators.
/// \ingroup core
template <class RawAllocator>
struct is_thread_safe_allocator
: std::integral_constant<bool, !allocator_traits<RawAllocator>::is_stateful::value>
{
};
namespace detail
{
// selects a mutex for an Allocator
// stateless allocators don't need locking
template <class RawAllocator, class Mutex>
using mutex_for =
typename std::conditional<is_thread_safe_allocator<RawAllocator>::value, no_mutex,
Mutex>::type;
// storage for mutexes to use EBO
// it provides const lock/unlock function, inherit from it
template <class Mutex>
class mutex_storage
{
public:
mutex_storage() noexcept = default;
mutex_storage(const mutex_storage&) noexcept {}
mutex_storage& operator=(const mutex_storage&) noexcept
{
return *this;
}
void lock() const
{
mutex_.lock();
}
void unlock() const noexcept
{
mutex_.unlock();
}
protected:
~mutex_storage() noexcept = default;
private:
mutable Mutex mutex_;
};
template <>
class mutex_storage<no_mutex>
{
public:
mutex_storage() noexcept = default;
void lock() const noexcept {}
void unlock() const noexcept {}
protected:
~mutex_storage() noexcept = default;
};
// non changeable pointer to an Allocator that keeps a lock
// I don't think EBO is necessary here...
template <class Alloc, class Mutex>
class locked_allocator
{
public:
locked_allocator(Alloc& alloc, Mutex& m) noexcept : mutex_(&m), alloc_(&alloc)
{
mutex_->lock();
}
locked_allocator(locked_allocator&& other) noexcept
: mutex_(other.mutex_), alloc_(other.alloc_)
{
other.mutex_ = nullptr;
other.alloc_ = nullptr;
}
~locked_allocator() noexcept
{
if (mutex_)
mutex_->unlock();
}
locked_allocator& operator=(locked_allocator&& other) noexcept = delete;
Alloc& operator*() const noexcept
{
WPI_MEMORY_ASSERT(alloc_);
return *alloc_;
}
Alloc* operator->() const noexcept
{
WPI_MEMORY_ASSERT(alloc_);
return alloc_;
}
private:
Mutex* mutex_; // don't use unqiue_lock to avoid dependency
Alloc* alloc_;
};
template <class Alloc, class Mutex>
locked_allocator<Alloc, Mutex> lock_allocator(Alloc& a, Mutex& m)
{
return {a, m};
}
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_THREADING_HPP_INCLUDED

View File

@@ -0,0 +1,430 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_TRACKING_HPP_INCLUDED
#define WPI_MEMORY_TRACKING_HPP_INCLUDED
/// \file
/// Class \ref wpi::memory::tracked_allocator and related classes and functions.
#include "detail/utility.hpp"
#include "allocator_traits.hpp"
#include "memory_arena.hpp"
namespace wpi
{
namespace memory
{
namespace detail
{
template <class Allocator, class Tracker>
auto set_tracker(int, Allocator& allocator, Tracker* tracker) noexcept
-> decltype(allocator.get_allocator().set_tracker(tracker))
{
return allocator.get_allocator().set_tracker(tracker);
}
template <class Allocator, class Tracker>
void set_tracker(short, Allocator&, Tracker*) noexcept
{
}
// used with deeply_tracked_allocator
template <class Tracker, class BlockAllocator>
class deeply_tracked_block_allocator : WPI_EBO(BlockAllocator)
{
public:
template <typename... Args>
deeply_tracked_block_allocator(std::size_t block_size, Args&&... args)
: BlockAllocator(block_size, detail::forward<Args>(args)...), tracker_(nullptr)
{
}
memory_block allocate_block()
{
auto block = BlockAllocator::allocate_block();
if (tracker_) // on first call tracker_ is nullptr
tracker_->on_allocator_growth(block.memory, block.size);
return block;
}
void deallocate_block(memory_block block) noexcept
{
if (tracker_) // on last call tracker_ is nullptr again
tracker_->on_allocator_shrinking(block.memory, block.size);
BlockAllocator::deallocate_block(block);
}
std::size_t next_block_size() const noexcept
{
return BlockAllocator::next_block_size();
}
void set_tracker(Tracker* tracker) noexcept
{
tracker_ = tracker;
}
private:
Tracker* tracker_;
};
} // namespace detail
/// A \concept{concept_blockallocator,BlockAllocator} adapter that tracks another allocator using a \concept{concept_tracker,tracker}.
/// It wraps another \concept{concept_blockallocator,BlockAllocator} and calls the tracker function before forwarding to it.
/// The class can then be used anywhere a \concept{concept_blockallocator,BlockAllocator} is required and the memory usage will be tracked.<br>
/// It will only call the <tt>on_allocator_growth()</tt> and <tt>on_allocator_shrinking()</tt> tracking functions,
/// since a \concept{concept_blockallocator,BlockAllocator} is normally used inside higher allocators only.
/// \ingroup adapter
template <class Tracker, class BlockOrRawAllocator>
class tracked_block_allocator
: WPI_EBO(Tracker, make_block_allocator_t<BlockOrRawAllocator>)
{
public:
using allocator_type = make_block_allocator_t<BlockOrRawAllocator>;
using tracker = Tracker;
/// @{
/// \effects Creates it by giving it a \concept{concept_tracker,tracker} and the tracked \concept{concept_rawallocator,RawAllocator}.
/// It will embed both objects.
explicit tracked_block_allocator(tracker t = {}) noexcept : tracker(detail::move(t)) {}
tracked_block_allocator(tracker t, allocator_type&& alloc) noexcept
: tracker(detail::move(t)), allocator_type(detail::move(alloc))
{
}
/// @}
/// \effects Creates it in the form required by the concept.
/// The allocator will be constructed using \c block_size and \c args.
template <typename... Args>
tracked_block_allocator(std::size_t block_size, tracker t, Args&&... args)
: tracker(detail::move(t)), allocator_type(block_size, detail::forward<Args>(args)...)
{
}
/// \effects Calls <tt>Tracker::on_allocator_growth()</tt> after forwarding to the allocator.
/// \returns The block as the returned by the allocator.
memory_block allocate_block()
{
auto block = allocator_type::allocate_block();
this->on_allocator_growth(block.memory, block.size);
return block;
}
/// \effects Calls <tt>Tracker::on_allocator_shrinking()</tt> and forwards to the allocator.
void deallocate_block(memory_block block) noexcept
{
this->on_allocator_shrinking(block.memory, block.size);
allocator_type::deallocate_block(block);
}
/// \returns The next block size as returned by the allocator.
std::size_t next_block_size() const noexcept
{
return allocator_type::next_block_size();
}
/// @{
/// \returns A (const) reference to the used allocator.
allocator_type& get_allocator() noexcept
{
return *this;
}
const allocator_type& get_allocator() const noexcept
{
return *this;
}
/// @}
/// @{
/// \returns A (const) reference to the tracker.
tracker& get_tracker() noexcept
{
return *this;
}
const tracker& get_tracker() const noexcept
{
return *this;
}
/// @}
};
/// Similar to \ref tracked_block_allocator, but shares the tracker with the higher level allocator.
/// This allows tracking both (de-)allocations and growth with one tracker.
/// \note Due to implementation reasons, it cannot track growth and shrinking in the constructor/destructor of the higher level allocator.
/// \ingroup adapter
template <class Tracker, class BlockOrRawAllocator>
using deeply_tracked_block_allocator = WPI_IMPL_DEFINED(
detail::deeply_tracked_block_allocator<Tracker,
make_block_allocator_t<BlockOrRawAllocator>>);
/// A \concept{concept_rawallocator,RawAllocator} adapter that tracks another allocator using a \concept{concept_tracker,tracker}.
/// It wraps another \concept{concept_rawallocator,RawAllocator} and calls the tracker function before forwarding to it.
/// The class can then be used anywhere a \concept{concept_rawallocator,RawAllocator} is required and the memory usage will be tracked.<br>
/// If the \concept{concept_rawallocator,RawAllocator} uses \ref deeply_tracked_block_allocator as \concept{concept_blockallocator,BlockAllocator},
/// it will also track growth and shrinking of the allocator.
/// \ingroup adapter
template <class Tracker, class RawAllocator>
class tracked_allocator
: WPI_EBO(Tracker, allocator_traits<RawAllocator>::allocator_type)
{
using traits = allocator_traits<RawAllocator>;
using composable_traits = composable_allocator_traits<RawAllocator>;
public:
using allocator_type = typename allocator_traits<RawAllocator>::allocator_type;
using tracker = Tracker;
using is_stateful = std::integral_constant<bool, traits::is_stateful::value
|| !std::is_empty<Tracker>::value>;
/// @{
/// \effects Creates it by giving it a \concept{concept_tracker,tracker} and the tracked \concept{concept_rawallocator,RawAllocator}.
/// It will embed both objects.
/// \note This will never call the <tt>Tracker::on_allocator_growth()</tt> function.
explicit tracked_allocator(tracker t = {}) noexcept
: tracked_allocator(detail::move(t), allocator_type{})
{
}
tracked_allocator(tracker t, allocator_type&& allocator) noexcept
: tracker(detail::move(t)), allocator_type(detail::move(allocator))
{
detail::set_tracker(0, get_allocator(), &get_tracker());
}
/// @}
/// \effects Destroys both tracker and allocator.
/// \note This will never call the <tt>Tracker::on_allocator_shrinking()</tt> function.
~tracked_allocator() noexcept
{
detail::set_tracker(0, get_allocator(), static_cast<tracker*>(nullptr));
}
/// @{
/// \effects Moving moves both the tracker and the allocator.
tracked_allocator(tracked_allocator&& other) noexcept
: tracker(detail::move(other)), allocator_type(detail::move(other))
{
detail::set_tracker(0, get_allocator(), &get_tracker());
}
tracked_allocator& operator=(tracked_allocator&& other) noexcept
{
tracker:: operator=(detail::move(other));
allocator_type::operator=(detail::move(other));
detail::set_tracker(0, get_allocator(), &get_tracker());
return *this;
}
/// @}
/// \effects Calls <tt>Tracker::on_node_allocation()</tt> and forwards to the allocator.
/// If a growth occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_growth()</tt>.
/// \returns The result of <tt>allocate_node()</tt>
void* allocate_node(std::size_t size, std::size_t alignment)
{
auto mem = traits::allocate_node(get_allocator(), size, alignment);
this->on_node_allocation(mem, size, alignment);
return mem;
}
/// \effects Calls the composable node allocation function.
/// If allocation was successful, also calls `Tracker::on_node_allocation()`.
/// \returns The result of `try_allocate_node()`.
void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept
{
auto mem = composable_traits::try_allocate_node(get_allocator(), size, alignment);
if (mem)
this->on_node_allocation(mem, size, alignment);
return mem;
}
/// \effects Calls <tt>Tracker::on_array_allocation()</tt> and forwards to the allocator.
/// If a growth occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_growth()</tt>.
/// \returns The result of <tt>allocate_array()</tt>
void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment)
{
auto mem = traits::allocate_array(get_allocator(), count, size, alignment);
this->on_array_allocation(mem, count, size, alignment);
return mem;
}
/// \effects Calls the composable array allocation function.
/// If allocation was succesful, also calls `Tracker::on_array_allocation()`.
/// \returns The result of `try_allocate_array()`.
void* try_allocate_array(std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
auto mem =
composable_traits::try_allocate_array(get_allocator(), count, size, alignment);
if (mem)
this->on_array_allocation(mem, count, size, alignment);
return mem;
}
/// \effects Calls <tt>Tracker::on_node_deallocation()</tt> and forwards to the allocator's <tt>deallocate_node()</tt>.
/// If shrinking occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_shrinking()</tt>.
void deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
this->on_node_deallocation(ptr, size, alignment);
traits::deallocate_node(get_allocator(), ptr, size, alignment);
}
/// \effects Calls the composable node deallocation function.
/// If it was succesful, also calls `Tracker::on_node_deallocation()`.
/// \returns The result of `try_deallocate_node()`.
bool try_deallocate_node(void* ptr, std::size_t size, std::size_t alignment) noexcept
{
auto res =
composable_traits::try_deallocate_node(get_allocator(), ptr, size, alignment);
if (res)
this->on_node_deallocation(ptr, size, alignment);
return res;
}
/// \effects Calls <tt>Tracker::on_array_deallocation()</tt> and forwards to the allocator's <tt>deallocate_array()</tt>.
/// If shrinking occurs and the allocator is deeply tracked, also calls <tt>Tracker::on_allocator_shrinking()</tt>.
void deallocate_array(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
this->on_array_deallocation(ptr, count, size, alignment);
traits::deallocate_array(get_allocator(), ptr, count, size, alignment);
}
/// \effects Calls the composable array deallocation function.
/// If it was succesful, also calls `Tracker::on_array_deallocation()`.
/// \returns The result of `try_deallocate_array()`.
bool try_deallocate_array(void* ptr, std::size_t count, std::size_t size,
std::size_t alignment) noexcept
{
auto res = composable_traits::try_deallocate_array(ptr, count, size, alignment);
if (res)
this->on_array_deallocation(ptr, count, size, alignment);
return res;
}
/// @{
/// \returns The result of the corresponding function on the wrapped allocator.
std::size_t max_node_size() const
{
return traits::max_node_size(get_allocator());
}
std::size_t max_array_size() const
{
return traits::max_array_size(get_allocator());
}
std::size_t max_alignment() const
{
return traits::max_alignment(get_allocator());
}
/// @}
/// @{
/// \returns A (\c const) reference to the wrapped allocator.
allocator_type& get_allocator() noexcept
{
return *this;
}
const allocator_type& get_allocator() const noexcept
{
return *this;
}
/// @}
/// @{
/// \returns A (\c const) reference to the tracker.
tracker& get_tracker() noexcept
{
return *this;
}
const tracker& get_tracker() const noexcept
{
return *this;
}
/// @}
};
/// \effects Takes a \concept{concept_rawallocator,RawAllocator} and wraps it with a \concept{concept_tracker,tracker}.
/// \returns A \ref tracked_allocator with the corresponding parameters forwarded to the constructor.
/// \relates tracked_allocator
template <class Tracker, class RawAllocator>
auto make_tracked_allocator(Tracker t, RawAllocator&& alloc)
-> tracked_allocator<Tracker, typename std::decay<RawAllocator>::type>
{
return tracked_allocator<Tracker, typename std::decay<RawAllocator>::type>{detail::move(
t),
detail::move(
alloc)};
}
namespace detail
{
template <typename T, bool Block>
struct is_block_or_raw_allocator_impl : std::true_type
{
};
template <typename T>
struct is_block_or_raw_allocator_impl<T, false> : memory::is_raw_allocator<T>
{
};
template <typename T>
struct is_block_or_raw_allocator
: is_block_or_raw_allocator_impl<T, memory::is_block_allocator<T>::value>
{
};
template <class RawAllocator, class BlockAllocator>
struct rebind_block_allocator;
template <template <typename...> class RawAllocator, typename... Args,
class OtherBlockAllocator>
struct rebind_block_allocator<RawAllocator<Args...>, OtherBlockAllocator>
{
using type =
RawAllocator<typename std::conditional<is_block_or_raw_allocator<Args>::value,
OtherBlockAllocator, Args>::type...>;
};
template <class Tracker, class RawAllocator>
using deeply_tracked_block_allocator_for =
memory::deeply_tracked_block_allocator<Tracker,
typename RawAllocator::allocator_type>;
template <class Tracker, class RawAllocator>
using rebound_allocator = typename rebind_block_allocator<
RawAllocator, deeply_tracked_block_allocator_for<Tracker, RawAllocator>>::type;
} // namespace detail
/// A \ref tracked_allocator that has rebound any \concept{concept_blockallocator,BlockAllocator} to the corresponding \ref deeply_tracked_block_allocator.
/// This makes it a deeply tracked allocator.<br>
/// It replaces each template argument of the given \concept{concept_rawallocator,RawAllocator} for which \ref is_block_allocator or \ref is_raw_allocator is \c true with a \ref deeply_tracked_block_allocator.
/// \ingroup adapter
template <class Tracker, class RawAllocator>
WPI_ALIAS_TEMPLATE(
deeply_tracked_allocator,
tracked_allocator<Tracker, detail::rebound_allocator<Tracker, RawAllocator>>);
/// \effects Takes a \concept{concept_rawallocator,RawAllocator} and deeply wraps it with a \concept{concept_tracker,tracker}.
/// \returns A \ref deeply_tracked_allocator with the corresponding parameters forwarded to the constructor.
/// \relates deeply_tracked_allocator
template <class RawAllocator, class Tracker, typename... Args>
auto make_deeply_tracked_allocator(Tracker t, Args&&... args)
-> deeply_tracked_allocator<Tracker, RawAllocator>
{
return deeply_tracked_allocator<Tracker, RawAllocator>(detail::move(t),
{detail::forward<Args>(
args)...});
}
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_TRACKING_HPP_INCLUDED

View File

@@ -0,0 +1,202 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_VIRTUAL_MEMORY_HPP_INCLUDED
#define WPI_MEMORY_VIRTUAL_MEMORY_HPP_INCLUDED
/// \file
/// Virtual memory api and (low-level) allocator classes.
#include <cstddef>
#include <type_traits>
#include "detail/debug_helpers.hpp"
#include "detail/utility.hpp"
#include "config.hpp"
#if WPI_MEMORY_EXTERN_TEMPLATE
#include "allocator_traits.hpp"
#endif
namespace wpi
{
namespace memory
{
namespace detail
{
struct virtual_memory_allocator_leak_handler
{
void operator()(std::ptrdiff_t amount);
};
WPI_MEMORY_GLOBAL_LEAK_CHECKER(virtual_memory_allocator_leak_handler,
virtual_memory_allocator_leak_checker)
} // namespace detail
/// The page size of the virtual memory.
/// All virtual memory allocations must be multiple of this size.
/// It is usually 4KiB.
/// \ingroup allocator
/// \deprecated use \ref get_virtual_memory_page_size instead.
extern const std::size_t virtual_memory_page_size;
/// \returns the page size of the virtual memory.
/// All virtual memory allocations must be multiple of this size.
/// It is usually 4KiB.
/// \ingroup allocator
std::size_t get_virtual_memory_page_size() noexcept;
/// Reserves virtual memory.
/// \effects Reserves the given number of pages.
/// Each page is \ref virtual_memory_page_size big.
/// \returns The address of the first reserved page,
/// or \c nullptr in case of error.
/// \note The memory may not be used, it must first be commited.
/// \ingroup allocator
void* virtual_memory_reserve(std::size_t no_pages) noexcept;
/// Releases reserved virtual memory.
/// \effects Returns previously reserved pages to the system.
/// \requires \c pages must come from a previous call to \ref virtual_memory_reserve with the same \c calc_no_pages,
/// it must not be \c nullptr.
/// \ingroup allocator
void virtual_memory_release(void* pages, std::size_t no_pages) noexcept;
/// Commits reserved virtual memory.
/// \effects Marks \c calc_no_pages pages starting at the given address available for use.
/// \returns The beginning of the committed area, i.e. \c memory, or \c nullptr in case of error.
/// \requires The memory must be previously reserved.
/// \ingroup allocator
void* virtual_memory_commit(void* memory, std::size_t no_pages) noexcept;
/// Decommits commited virtual memory.
/// \effects Puts commited memory back in the reserved state.
/// \requires \c memory must come from a previous call to \ref virtual_memory_commit with the same \c calc_no_pages
/// it must not be \c nullptr.
/// \ingroup allocator
void virtual_memory_decommit(void* memory, std::size_t no_pages) noexcept;
/// A stateless \concept{concept_rawallocator,RawAllocator} that allocates memory using the virtual memory allocation functions.
/// It does not prereserve any memory and will always reserve and commit combined.
/// \ingroup allocator
class virtual_memory_allocator
: WPI_EBO(detail::global_leak_checker<detail::virtual_memory_allocator_leak_handler>)
{
public:
using is_stateful = std::false_type;
virtual_memory_allocator() noexcept = default;
virtual_memory_allocator(virtual_memory_allocator&&) noexcept {}
~virtual_memory_allocator() noexcept = default;
virtual_memory_allocator& operator=(virtual_memory_allocator&&) noexcept
{
return *this;
}
/// \effects A \concept{concept_rawallocator,RawAllocator} allocation function.
/// It uses \ref virtual_memory_reserve followed by \ref virtual_memory_commit for the allocation.
/// The number of pages allocated will be the minimum to hold \c size continuous bytes,
/// i.e. \c size will be rounded up to the next multiple.
/// If debug fences are activated, one additional page before and after the memory will be allocated.
/// \returns A pointer to a \concept{concept_node,node}, it will never be \c nullptr.
/// It will always be aligned on a fence boundary, regardless of the alignment parameter.
/// \throws An exception of type \ref out_of_memory or whatever is thrown by its handler if the allocation fails.
void* allocate_node(std::size_t size, std::size_t alignment);
/// \effects A \concept{concept_rawallocator,RawAllocator} deallocation function.
/// It calls \ref virtual_memory_decommit followed by \ref virtual_memory_release for the deallocation.
void deallocate_node(void* node, std::size_t size, std::size_t alignment) noexcept;
/// \returns The maximum node size by returning the maximum value.
std::size_t max_node_size() const noexcept;
/// \returns The maximum alignment which is the same as the \ref virtual_memory_page_size.
std::size_t max_alignment() const noexcept;
};
#if WPI_MEMORY_EXTERN_TEMPLATE
extern template class allocator_traits<virtual_memory_allocator>;
#endif
struct memory_block;
struct allocator_info;
/// A \concept{concept_blockallocator,BlockAllocator} that reserves virtual memory and commits it part by part.
/// It is similar to \ref memory_stack but does not support growing and uses virtual memory,
/// also meant for big blocks not small allocations.
/// \ingroup allocator
class virtual_block_allocator
{
public:
/// \effects Creates it giving it the block size and the total number of blocks it can allocate.
/// It reserves enough virtual memory for <tt>block_size * no_blocks</tt>.
/// \requires \c block_size must be non-zero and a multiple of the \ref virtual_memory_page_size.
/// \c no_blocks must be bigger than \c 1.
/// \throws \ref out_of_memory if it cannot reserve the virtual memory.
explicit virtual_block_allocator(std::size_t block_size, std::size_t no_blocks);
/// \effects Releases the reserved virtual memory.
~virtual_block_allocator() noexcept;
/// @{
/// \effects Moves the block allocator, it transfers ownership over the reserved area.
/// This does not invalidate any memory blocks.
virtual_block_allocator(virtual_block_allocator&& other) noexcept
: cur_(other.cur_), end_(other.end_), block_size_(other.block_size_)
{
other.cur_ = other.end_ = nullptr;
other.block_size_ = 0;
}
virtual_block_allocator& operator=(virtual_block_allocator&& other) noexcept
{
virtual_block_allocator tmp(detail::move(other));
swap(*this, tmp);
return *this;
}
/// @}
/// \effects Swaps the ownership over the reserved memory.
/// This does not invalidate any memory blocks.
friend void swap(virtual_block_allocator& a, virtual_block_allocator& b) noexcept
{
detail::adl_swap(a.cur_, b.cur_);
detail::adl_swap(a.end_, b.end_);
detail::adl_swap(a.block_size_, b.block_size_);
}
/// \effects Allocates a new memory block by committing the next \ref next_block_size() number of bytes.
/// \returns The \ref memory_block committed.
/// \throws \ref out_of_memory if it cannot commit the memory or the \ref capacity_left() is exhausted.
memory_block allocate_block();
/// \effects Deallocates the last allocated memory block by decommitting it.
/// This block will be returned again on the next call to \ref allocate_block().
/// \requires \c block must be the current top block of the memory,
/// this is guaranteed by \ref memory_arena.
void deallocate_block(memory_block block) noexcept;
/// \returns The next block size, this is the block size of the constructor.
std::size_t next_block_size() const noexcept
{
return block_size_;
}
/// \returns The number of blocks that can be committed until it runs out of memory.
std::size_t capacity_left() const noexcept
{
return static_cast<std::size_t>(end_ - cur_) / block_size_;
}
private:
allocator_info info() noexcept;
char * cur_, *end_;
std::size_t block_size_;
};
} // namespace memory
} // namespace wpi
#endif //WPI_MEMORY_VIRTUAL_MEMORY_HPP_INCLUDED

View File

@@ -0,0 +1,109 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/debugging.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include <cstdio>
#endif
#include <atomic>
#include <cstdlib>
#include "wpi/memory/error.hpp"
using namespace wpi::memory;
namespace
{
void default_leak_handler(const allocator_info& info, std::ptrdiff_t amount) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
if (amount > 0)
std::fprintf(stderr, "[%s] Allocator %s (at %p) leaked %zu bytes.\n",
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator,
std::size_t(amount));
else
std::fprintf(stderr,
"[%s] Allocator %s (at %p) has deallocated %zu bytes more than "
"ever allocated "
"(it's amazing you're able to see this message!).\n",
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator,
std::size_t(-amount));
#else
(void)info;
(void)amount;
#endif
}
std::atomic<leak_handler> leak_h(default_leak_handler);
} // namespace
leak_handler wpi::memory::set_leak_handler(leak_handler h)
{
return leak_h.exchange(h ? h : default_leak_handler);
}
leak_handler wpi::memory::get_leak_handler()
{
return leak_h;
}
namespace
{
void default_invalid_ptr_handler(const allocator_info& info, const void* ptr) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
std::fprintf(stderr,
"[%s] Deallocation function of allocator %s (at %p) received invalid "
"pointer %p\n",
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator, ptr);
#endif
(void)info;
(void)ptr;
std::abort();
}
std::atomic<invalid_pointer_handler> invalid_ptr_h(default_invalid_ptr_handler);
} // namespace
invalid_pointer_handler wpi::memory::set_invalid_pointer_handler(invalid_pointer_handler h)
{
return invalid_ptr_h.exchange(h ? h : default_invalid_ptr_handler);
}
invalid_pointer_handler wpi::memory::get_invalid_pointer_handler()
{
return invalid_ptr_h;
}
namespace
{
void default_buffer_overflow_handler(const void* memory, std::size_t node_size,
const void* ptr) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
std::fprintf(stderr,
"[%s] Buffer overflow at address %p detected, corresponding memory "
"block %p has only size %zu.\n",
WPI_MEMORY_LOG_PREFIX, ptr, memory, node_size);
#endif
(void)memory;
(void)node_size;
(void)ptr;
std::abort();
}
std::atomic<buffer_overflow_handler> buffer_overflow_h(default_buffer_overflow_handler);
} // namespace
buffer_overflow_handler wpi::memory::set_buffer_overflow_handler(buffer_overflow_handler h)
{
return buffer_overflow_h.exchange(h ? h : default_buffer_overflow_handler);
}
buffer_overflow_handler wpi::memory::get_buffer_overflow_handler()
{
return buffer_overflow_h;
}

View File

@@ -0,0 +1,22 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/detail/align.hpp"
#include "wpi/memory/detail/ilog2.hpp"
using namespace wpi::memory;
using namespace detail;
bool wpi::memory::detail::is_aligned(void* ptr, std::size_t alignment) noexcept
{
WPI_MEMORY_ASSERT(is_valid_alignment(alignment));
auto address = reinterpret_cast<std::uintptr_t>(ptr);
return address % alignment == 0u;
}
std::size_t wpi::memory::detail::alignment_for(std::size_t size) noexcept
{
return size >= max_alignment ? max_alignment : (std::size_t(1) << ilog2(size));
}

View File

@@ -0,0 +1,34 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/detail/assert.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include <cstdio>
#endif
#include <cstdlib>
#include "wpi/memory/error.hpp"
using namespace wpi::memory;
using namespace detail;
void detail::handle_failed_assert(const char* msg, const char* file, int line,
const char* fnc) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
std::fprintf(stderr, "[%s] Assertion failure in function %s (%s:%d): %s.\n",
WPI_MEMORY_LOG_PREFIX, fnc, file, line, msg);
#endif
std::abort();
}
void detail::handle_warning(const char* msg, const char* file, int line, const char* fnc) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
std::fprintf(stderr, "[%s] Warning triggered in function %s (%s:%d): %s.\n",
WPI_MEMORY_LOG_PREFIX, fnc, file, line, msg);
#endif
}

View File

@@ -0,0 +1,87 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/detail/debug_helpers.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include <cstring>
#endif
#include "wpi/memory/debugging.hpp"
using namespace wpi::memory;
using namespace detail;
#if WPI_MEMORY_DEBUG_FILL
void detail::debug_fill(void* memory, std::size_t size, debug_magic m) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
std::memset(memory, static_cast<int>(m), size);
#else
// do the naive loop :(
auto ptr = static_cast<unsigned char*>(memory);
for (std::size_t i = 0u; i != size; ++i)
*ptr++ = static_cast<unsigned char>(m);
#endif
}
void* detail::debug_is_filled(void* memory, std::size_t size, debug_magic m) noexcept
{
auto byte = static_cast<unsigned char*>(memory);
for (auto end = byte + size; byte != end; ++byte)
if (*byte != static_cast<unsigned char>(m))
return byte;
return nullptr;
}
void* detail::debug_fill_new(void* memory, std::size_t node_size, std::size_t fence_size) noexcept
{
if (!debug_fence_size)
fence_size = 0u; // force override of fence_size
auto mem = static_cast<char*>(memory);
debug_fill(mem, fence_size, debug_magic::fence_memory);
mem += fence_size;
debug_fill(mem, node_size, debug_magic::new_memory);
debug_fill(mem + node_size, fence_size, debug_magic::fence_memory);
return mem;
}
void* detail::debug_fill_free(void* memory, std::size_t node_size, std::size_t fence_size) noexcept
{
if (!debug_fence_size)
fence_size = 0u; // force override of fence_size
debug_fill(memory, node_size, debug_magic::freed_memory);
auto pre_fence = static_cast<unsigned char*>(memory) - fence_size;
if (auto pre_dirty = debug_is_filled(pre_fence, fence_size, debug_magic::fence_memory))
get_buffer_overflow_handler()(memory, node_size, pre_dirty);
auto post_mem = static_cast<unsigned char*>(memory) + node_size;
if (auto post_dirty = debug_is_filled(post_mem, fence_size, debug_magic::fence_memory))
get_buffer_overflow_handler()(memory, node_size, post_dirty);
return pre_fence;
}
void detail::debug_fill_internal(void* memory, std::size_t size, bool free) noexcept
{
debug_fill(memory, size,
free ? debug_magic::internal_freed_memory : debug_magic::internal_memory);
}
#endif
void detail::debug_handle_invalid_ptr(const allocator_info& info, void* ptr)
{
get_invalid_pointer_handler()(info, ptr);
}
void detail::debug_handle_memory_leak(const allocator_info& info, std::ptrdiff_t amount)
{
get_leak_handler()(info, amount);
}

View File

@@ -0,0 +1,557 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/detail/free_list.hpp"
#include "wpi/memory/detail/align.hpp"
#include "wpi/memory/detail/debug_helpers.hpp"
#include "wpi/memory/detail/assert.hpp"
#include "wpi/memory/debugging.hpp"
#include "wpi/memory/error.hpp"
#include "free_list_utils.hpp"
using namespace wpi::memory;
using namespace detail;
namespace
{
// i.e. array
struct interval
{
char* prev; // last before
char* first; // first in
char* last; // last in
char* next; // first after
// number of nodes in the interval
std::size_t size(std::size_t node_size) const noexcept
{
// last is inclusive, so add actual_size to it
// note: cannot use next, might not be directly after
auto end = last + node_size;
WPI_MEMORY_ASSERT(static_cast<std::size_t>(end - first) % node_size == 0u);
return static_cast<std::size_t>(end - first) / node_size;
}
};
// searches for n consecutive bytes
// begin and end are the proxy nodes
// assumes list is not empty
// similar to list_search_array()
interval list_search_array(char* first, std::size_t bytes_needed,
std::size_t node_size) noexcept
{
interval i;
i.prev = nullptr;
i.first = first;
// i.last/next are used as iterator for the end of the interval
i.last = first;
i.next = list_get_next(first);
auto bytes_so_far = node_size;
while (i.next)
{
if (i.last + node_size != i.next) // not continous
{
// restart at next
i.prev = i.last;
i.first = i.next;
i.last = i.next;
i.next = list_get_next(i.last);
bytes_so_far = node_size;
}
else
{
// extend interval
auto new_next = list_get_next(i.next);
i.last = i.next;
i.next = new_next;
bytes_so_far += node_size;
if (bytes_so_far >= bytes_needed)
return i;
}
}
// not enough continuous space
return {nullptr, nullptr, nullptr, nullptr};
}
// similar to list_search_array()
// begin/end are proxy nodes
interval xor_list_search_array(char* begin, char* end, std::size_t bytes_needed,
std::size_t node_size) noexcept
{
interval i;
i.prev = begin;
i.first = xor_list_get_other(begin, nullptr);
// i.last/next are used as iterator for the end of the interval
i.last = i.first;
i.next = xor_list_get_other(i.last, i.prev);
auto bytes_so_far = node_size;
while (i.next != end)
{
if (i.last + node_size != i.next) // not continous
{
// restart at i.next
i.prev = i.last;
i.first = i.next;
i.last = i.next;
i.next = xor_list_get_other(i.first, i.prev);
bytes_so_far = node_size;
}
else
{
// extend interval
auto new_next = xor_list_get_other(i.next, i.last);
i.last = i.next;
i.next = new_next;
bytes_so_far += node_size;
if (bytes_so_far >= bytes_needed)
return i;
}
}
// not enough continuous space
return {nullptr, nullptr, nullptr, nullptr};
}
} // namespace
constexpr std::size_t free_memory_list::min_element_size;
constexpr std::size_t free_memory_list::min_element_alignment;
free_memory_list::free_memory_list(std::size_t node_size) noexcept
: first_(nullptr),
node_size_(node_size > min_element_size ? node_size : min_element_size),
capacity_(0u)
{
}
free_memory_list::free_memory_list(std::size_t node_size, void* mem, std::size_t size) noexcept
: free_memory_list(node_size)
{
insert(mem, size);
}
free_memory_list::free_memory_list(free_memory_list&& other) noexcept
: first_(other.first_), node_size_(other.node_size_), capacity_(other.capacity_)
{
other.first_ = nullptr;
other.capacity_ = 0u;
}
free_memory_list& free_memory_list::operator=(free_memory_list&& other) noexcept
{
free_memory_list tmp(detail::move(other));
swap(*this, tmp);
return *this;
}
void wpi::memory::detail::swap(free_memory_list& a, free_memory_list& b) noexcept
{
detail::adl_swap(a.first_, b.first_);
detail::adl_swap(a.node_size_, b.node_size_);
detail::adl_swap(a.capacity_, b.capacity_);
}
void free_memory_list::insert(void* mem, std::size_t size) noexcept
{
WPI_MEMORY_ASSERT(mem);
WPI_MEMORY_ASSERT(is_aligned(mem, alignment()));
detail::debug_fill_internal(mem, size, false);
insert_impl(mem, size);
}
void* free_memory_list::allocate() noexcept
{
WPI_MEMORY_ASSERT(!empty());
--capacity_;
auto mem = first_;
first_ = list_get_next(first_);
return detail::debug_fill_new(mem, node_size_, 0);
}
void* free_memory_list::allocate(std::size_t n) noexcept
{
WPI_MEMORY_ASSERT(!empty());
if (n <= node_size_)
return allocate();
auto i = list_search_array(first_, n, node_size_);
if (i.first == nullptr)
return nullptr;
if (i.prev)
list_set_next(i.prev, i.next); // change next from previous to first after
else
first_ = i.next;
capacity_ -= i.size(node_size_);
return detail::debug_fill_new(i.first, n, 0);
}
void free_memory_list::deallocate(void* ptr) noexcept
{
++capacity_;
auto node = static_cast<char*>(detail::debug_fill_free(ptr, node_size_, 0));
list_set_next(node, first_);
first_ = node;
}
void free_memory_list::deallocate(void* ptr, std::size_t n) noexcept
{
if (n <= node_size_)
deallocate(ptr);
else
{
auto mem = detail::debug_fill_free(ptr, n, 0);
insert_impl(mem, n);
}
}
std::size_t free_memory_list::alignment() const noexcept
{
return alignment_for(node_size_);
}
void free_memory_list::insert_impl(void* mem, std::size_t size) noexcept
{
auto no_nodes = size / node_size_;
WPI_MEMORY_ASSERT(no_nodes > 0);
auto cur = static_cast<char*>(mem);
for (std::size_t i = 0u; i != no_nodes - 1; ++i)
{
list_set_next(cur, cur + node_size_);
cur += node_size_;
}
list_set_next(cur, first_);
first_ = static_cast<char*>(mem);
capacity_ += no_nodes;
}
namespace
{
// converts a block into a linked list
void xor_link_block(void* memory, std::size_t node_size, std::size_t no_nodes, char* prev,
char* next) noexcept
{
auto cur = static_cast<char*>(memory);
xor_list_change(prev, next, cur); // change next pointer of prev
auto last_cur = prev;
for (std::size_t i = 0u; i != no_nodes - 1; ++i)
{
xor_list_set(cur, last_cur,
cur + node_size); // cur gets last_cur and next node in continous memory
last_cur = cur;
cur += node_size;
}
xor_list_set(cur, last_cur, next); // last memory node gets next as next
xor_list_change(next, prev, cur); // change prev pointer of next
}
struct pos
{
char *prev, *next;
};
// finds position to insert memory to keep list ordered
// first_prev -> first -> ... (memory somewhere here) ... -> last -> last_next
pos find_pos_interval(const allocator_info& info, char* memory, char* first_prev, char* first,
char* last, char* last_next) noexcept
{
// note: first_prev/last_next can be the proxy nodes, then first_prev isn't necessarily less than first!
WPI_MEMORY_ASSERT(less(first, memory) && less(memory, last));
// need to insert somewhere in the middle
// search through the entire list
// search from both ends at once
auto cur_forward = first;
auto prev_forward = first_prev;
auto cur_backward = last;
auto prev_backward = last_next;
do
{
if (greater(cur_forward, memory))
return {prev_forward, cur_forward};
else if (less(cur_backward, memory))
// the next position is the previous backwards pointer
return {cur_backward, prev_backward};
debug_check_double_dealloc([&]
{ return cur_forward != memory && cur_backward != memory; },
info, memory);
xor_list_iter_next(cur_forward, prev_forward);
xor_list_iter_next(cur_backward, prev_backward);
} while (less(prev_forward, prev_backward));
// ran outside of list
debug_check_double_dealloc([] { return false; }, info, memory);
return {nullptr, nullptr};
}
// finds the position in the entire list
pos find_pos(const allocator_info& info, char* memory, char* begin_node, char* end_node,
char* last_dealloc, char* last_dealloc_prev) noexcept
{
auto first = xor_list_get_other(begin_node, nullptr);
auto last = xor_list_get_other(end_node, nullptr);
if (greater(first, memory))
// insert at front
return {begin_node, first};
else if (less(last, memory))
// insert at the end
return {last, end_node};
else if (less(last_dealloc_prev, memory) && less(memory, last_dealloc))
// insert before last_dealloc
return {last_dealloc_prev, last_dealloc};
else if (less(memory, last_dealloc))
// insert into [first, last_dealloc_prev]
return find_pos_interval(info, memory, begin_node, first, last_dealloc_prev,
last_dealloc);
else if (greater(memory, last_dealloc))
// insert into (last_dealloc, last]
return find_pos_interval(info, memory, last_dealloc_prev, last_dealloc, last, end_node);
WPI_MEMORY_UNREACHABLE("memory must be in some half or outside");
return {nullptr, nullptr};
}
} // namespace
constexpr std::size_t ordered_free_memory_list::min_element_size;
constexpr std::size_t ordered_free_memory_list::min_element_alignment;
ordered_free_memory_list::ordered_free_memory_list(std::size_t node_size) noexcept
: node_size_(node_size > min_element_size ? node_size : min_element_size),
capacity_(0u),
last_dealloc_(end_node()),
last_dealloc_prev_(begin_node())
{
xor_list_set(begin_node(), nullptr, end_node());
xor_list_set(end_node(), begin_node(), nullptr);
}
ordered_free_memory_list::ordered_free_memory_list(ordered_free_memory_list&& other) noexcept
: node_size_(other.node_size_), capacity_(other.capacity_)
{
if (!other.empty())
{
auto first = xor_list_get_other(other.begin_node(), nullptr);
auto last = xor_list_get_other(other.end_node(), nullptr);
xor_list_set(begin_node(), nullptr, first);
xor_list_change(first, other.begin_node(), begin_node());
xor_list_change(last, other.end_node(), end_node());
xor_list_set(end_node(), last, nullptr);
other.capacity_ = 0u;
xor_list_set(other.begin_node(), nullptr, other.end_node());
xor_list_set(other.end_node(), other.begin_node(), nullptr);
}
else
{
xor_list_set(begin_node(), nullptr, end_node());
xor_list_set(end_node(), begin_node(), nullptr);
}
// for programming convenience, last_dealloc is reset
last_dealloc_prev_ = begin_node();
last_dealloc_ = xor_list_get_other(last_dealloc_prev_, nullptr);
}
void wpi::memory::detail::swap(ordered_free_memory_list& a,
ordered_free_memory_list& b) noexcept
{
auto a_first = xor_list_get_other(a.begin_node(), nullptr);
auto a_last = xor_list_get_other(a.end_node(), nullptr);
auto b_first = xor_list_get_other(b.begin_node(), nullptr);
auto b_last = xor_list_get_other(b.end_node(), nullptr);
if (!a.empty())
{
xor_list_set(b.begin_node(), nullptr, a_first);
xor_list_change(a_first, a.begin_node(), b.begin_node());
xor_list_change(a_last, a.end_node(), b.end_node());
xor_list_set(b.end_node(), a_last, nullptr);
}
else
{
xor_list_set(b.begin_node(), nullptr, b.end_node());
xor_list_set(b.end_node(), b.begin_node(), nullptr);
}
if (!b.empty())
{
xor_list_set(a.begin_node(), nullptr, b_first);
xor_list_change(b_first, b.begin_node(), a.begin_node());
xor_list_change(b_last, b.end_node(), a.end_node());
xor_list_set(a.end_node(), b_last, nullptr);
}
else
{
xor_list_set(a.begin_node(), nullptr, a.end_node());
xor_list_set(a.end_node(), a.begin_node(), nullptr);
}
detail::adl_swap(a.node_size_, b.node_size_);
detail::adl_swap(a.capacity_, b.capacity_);
// for programming convenience, last_dealloc is reset
a.last_dealloc_prev_ = a.begin_node();
a.last_dealloc_ = xor_list_get_other(a.last_dealloc_prev_, nullptr);
b.last_dealloc_prev_ = b.begin_node();
b.last_dealloc_ = xor_list_get_other(b.last_dealloc_prev_, nullptr);
}
void ordered_free_memory_list::insert(void* mem, std::size_t size) noexcept
{
WPI_MEMORY_ASSERT(mem);
WPI_MEMORY_ASSERT(is_aligned(mem, alignment()));
detail::debug_fill_internal(mem, size, false);
insert_impl(mem, size);
}
void* ordered_free_memory_list::allocate() noexcept
{
WPI_MEMORY_ASSERT(!empty());
// remove first node
auto prev = begin_node();
auto node = xor_list_get_other(prev, nullptr);
auto next = xor_list_get_other(node, prev);
xor_list_set(prev, nullptr, next); // link prev to next
xor_list_change(next, node, prev); // change prev of next
--capacity_;
if (node == last_dealloc_)
{
// move last_dealloc_ one further in
last_dealloc_ = next;
WPI_MEMORY_ASSERT(last_dealloc_prev_ == prev);
}
else if (node == last_dealloc_prev_)
{
// now the previous node is the node before ours
last_dealloc_prev_ = prev;
WPI_MEMORY_ASSERT(last_dealloc_ == next);
}
return detail::debug_fill_new(node, node_size_, 0);
}
void* ordered_free_memory_list::allocate(std::size_t n) noexcept
{
WPI_MEMORY_ASSERT(!empty());
if (n <= node_size_)
return allocate();
auto i = xor_list_search_array(begin_node(), end_node(), n, node_size_);
if (i.first == nullptr)
return nullptr;
xor_list_change(i.prev, i.first, i.next); // change next pointer from i.prev to i.next
xor_list_change(i.next, i.last, i.prev); // change prev pointer from i.next to i.prev
capacity_ -= i.size(node_size_);
// if last_dealloc_ points into the array being removed
if ((less_equal(i.first, last_dealloc_) && less_equal(last_dealloc_, i.last)))
{
// move last_dealloc just outside range
last_dealloc_ = i.next;
last_dealloc_prev_ = i.prev;
}
// if the previous deallocation is the last element of the array
else if (last_dealloc_prev_ == i.last)
{
// it is now the last element before the array
WPI_MEMORY_ASSERT(last_dealloc_ == i.next);
last_dealloc_prev_ = i.prev;
}
return detail::debug_fill_new(i.first, n, 0);
}
void ordered_free_memory_list::deallocate(void* ptr) noexcept
{
auto node = static_cast<char*>(debug_fill_free(ptr, node_size_, 0));
auto p =
find_pos(allocator_info(WPI_MEMORY_LOG_PREFIX "::detail::ordered_free_memory_list",
this),
node, begin_node(), end_node(), last_dealloc_, last_dealloc_prev_);
xor_list_insert(node, p.prev, p.next);
++capacity_;
last_dealloc_ = node;
last_dealloc_prev_ = p.prev;
}
void ordered_free_memory_list::deallocate(void* ptr, std::size_t n) noexcept
{
if (n <= node_size_)
deallocate(ptr);
else
{
auto mem = detail::debug_fill_free(ptr, n, 0);
auto prev = insert_impl(mem, n);
last_dealloc_ = static_cast<char*>(mem);
last_dealloc_prev_ = prev;
}
}
std::size_t ordered_free_memory_list::alignment() const noexcept
{
return alignment_for(node_size_);
}
char* ordered_free_memory_list::insert_impl(void* mem, std::size_t size) noexcept
{
auto no_nodes = size / node_size_;
WPI_MEMORY_ASSERT(no_nodes > 0);
auto p =
find_pos(allocator_info(WPI_MEMORY_LOG_PREFIX "::detail::ordered_free_memory_list",
this),
static_cast<char*>(mem), begin_node(), end_node(), last_dealloc_,
last_dealloc_prev_);
xor_link_block(mem, node_size_, no_nodes, p.prev, p.next);
capacity_ += no_nodes;
if (p.prev == last_dealloc_prev_)
{
last_dealloc_ = static_cast<char*>(mem);
}
return p.prev;
}
char* ordered_free_memory_list::begin_node() noexcept
{
void* mem = &begin_proxy_;
return static_cast<char*>(mem);
}
char* ordered_free_memory_list::end_node() noexcept
{
void* mem = &end_proxy_;
return static_cast<char*>(mem);
}

View File

@@ -0,0 +1,22 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/detail/free_list_array.hpp"
#include "wpi/memory/detail/assert.hpp"
#include "wpi/memory/detail/ilog2.hpp"
using namespace wpi::memory;
using namespace detail;
std::size_t log2_access_policy::index_from_size(std::size_t size) noexcept
{
WPI_MEMORY_ASSERT_MSG(size, "size must not be zero");
return ilog2_ceil(size);
}
std::size_t log2_access_policy::size_from_index(std::size_t index) noexcept
{
return std::size_t(1) << index;
}

View File

@@ -0,0 +1,149 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#ifndef WPI_MEMORY_SRC_DETAIL_FREE_LIST_UTILS_HPP_INCLUDED
#define WPI_MEMORY_SRC_DETAIL_FREE_LIST_UTILS_HPP_INCLUDED
#include <cstdint>
#include "wpi/memory/config.hpp"
#include "wpi/memory/detail/align.hpp"
#include "wpi/memory/detail/assert.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include <cstring>
#include <functional>
#endif
namespace wpi
{
namespace memory
{
namespace detail
{
//=== storage ===///
// reads stored integer value
inline std::uintptr_t get_int(void* address) noexcept
{
WPI_MEMORY_ASSERT(address);
std::uintptr_t res;
#if WPI_HOSTED_IMPLEMENTATION
std::memcpy(&res, address, sizeof(std::uintptr_t));
#else
auto mem = static_cast<char*>(static_cast<void*>(&res));
for (auto i = 0u; i != sizeof(std::uintptr_t); ++i)
mem[i] = static_cast<char*>(address)[i];
#endif
return res;
}
// sets stored integer value
inline void set_int(void* address, std::uintptr_t i) noexcept
{
WPI_MEMORY_ASSERT(address);
#if WPI_HOSTED_IMPLEMENTATION
std::memcpy(address, &i, sizeof(std::uintptr_t));
#else
auto mem = static_cast<char*>(static_cast<void*>(&i));
for (auto i = 0u; i != sizeof(std::uintptr_t); ++i)
static_cast<char*>(address)[i] = mem[i];
#endif
}
// pointer to integer
inline std::uintptr_t to_int(char* ptr) noexcept
{
return reinterpret_cast<std::uintptr_t>(ptr);
}
// integer to pointer
inline char* from_int(std::uintptr_t i) noexcept
{
return reinterpret_cast<char*>(i);
}
//=== intrusive linked list ===//
// reads a stored pointer value
inline char* list_get_next(void* address) noexcept
{
return from_int(get_int(address));
}
// stores a pointer value
inline void list_set_next(void* address, char* ptr) noexcept
{
set_int(address, to_int(ptr));
}
//=== intrusive xor linked list ===//
// returns the other pointer given one pointer
inline char* xor_list_get_other(void* address, char* prev_or_next) noexcept
{
return from_int(get_int(address) ^ to_int(prev_or_next));
}
// sets the next and previous pointer (order actually does not matter)
inline void xor_list_set(void* address, char* prev, char* next) noexcept
{
set_int(address, to_int(prev) ^ to_int(next));
}
// changes other pointer given one pointer
inline void xor_list_change(void* address, char* old_ptr, char* new_ptr) noexcept
{
WPI_MEMORY_ASSERT(address);
auto other = xor_list_get_other(address, old_ptr);
xor_list_set(address, other, new_ptr);
}
// advances a pointer pair forward/backward
inline void xor_list_iter_next(char*& cur, char*& prev) noexcept
{
auto next = xor_list_get_other(cur, prev);
prev = cur;
cur = next;
}
// links new node between prev and next
inline void xor_list_insert(char* new_node, char* prev, char* next) noexcept
{
xor_list_set(new_node, prev, next);
xor_list_change(prev, next, new_node); // change prev's next to new_node
xor_list_change(next, prev, new_node); // change next's prev to new_node
}
//=== sorted list utils ===//
// if std::less/std::greater not available compare integer representation and hope it works
inline bool less(void* a, void* b) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
return std::less<void*>()(a, b);
#else
return to_int(a) < to_int(b);
#endif
}
inline bool less_equal(void* a, void* b) noexcept
{
return a == b || less(a, b);
}
inline bool greater(void* a, void* b) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
return std::greater<void*>()(a, b);
#else
return to_int(a) < to_int(b);
#endif
}
inline bool greater_equal(void* a, void* b) noexcept
{
return a == b || greater(a, b);
}
} // namespace detail
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_SRC_DETAIL_FREE_LIST_UTILS_HPP_INCLUDED

View File

@@ -0,0 +1,395 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/detail/small_free_list.hpp"
#include <new>
#include "wpi/memory/detail/debug_helpers.hpp"
#include "wpi/memory/detail/assert.hpp"
#include "wpi/memory/error.hpp"
#include "free_list_utils.hpp"
using namespace wpi::memory;
using namespace detail;
struct wpi::memory::detail::chunk : chunk_base
{
// gives it the size of the memory block it is created in and the size of a node
chunk(std::size_t total_memory, std::size_t node_size) noexcept
: chunk_base(static_cast<unsigned char>((total_memory - chunk_memory_offset) / node_size))
{
static_assert(sizeof(chunk) == sizeof(chunk_base), "chunk must not have members");
WPI_MEMORY_ASSERT((total_memory - chunk_memory_offset) / node_size
<= chunk_max_nodes);
WPI_MEMORY_ASSERT(capacity > 0);
auto p = list_memory();
for (unsigned char i = 0u; i != no_nodes; p += node_size)
*p = ++i;
}
// returns memory of the free list
unsigned char* list_memory() noexcept
{
auto mem = static_cast<void*>(this);
return static_cast<unsigned char*>(mem) + chunk_memory_offset;
}
// returns the nth node
unsigned char* node_memory(unsigned char i, std::size_t node_size) noexcept
{
WPI_MEMORY_ASSERT(i < no_nodes);
return list_memory() + i * node_size;
}
// checks whether a node came from this chunk
bool from(unsigned char* node, std::size_t node_size) noexcept
{
auto begin = list_memory();
auto end = list_memory() + no_nodes * node_size;
return (begin <= node) & (node < end);
}
// checks whether a node is already in this chunk
bool contains(unsigned char* node, std::size_t node_size) noexcept
{
auto cur_index = first_free;
while (cur_index != no_nodes)
{
auto cur_mem = node_memory(cur_index, node_size);
if (cur_mem == node)
return true;
cur_index = *cur_mem;
}
return false;
}
// allocates a single node
// chunk most not be empty
unsigned char* allocate(std::size_t node_size) noexcept
{
--capacity;
auto node = node_memory(first_free, node_size);
first_free = *node;
return node;
}
// deallocates a single node given its address and index
// it must be from this chunk
void deallocate(unsigned char* node, unsigned char node_index) noexcept
{
++capacity;
*node = first_free;
first_free = node_index;
}
};
namespace
{
// converts a chunk_base to a chunk (if it is one)
chunk* make_chunk(chunk_base* c) noexcept
{
return static_cast<chunk*>(c);
}
// same as above but also requires a certain size
chunk* make_chunk(chunk_base* c, std::size_t size_needed) noexcept
{
WPI_MEMORY_ASSERT(size_needed <= chunk_max_nodes);
return c->capacity >= size_needed ? make_chunk(c) : nullptr;
}
// checks if memory was from a chunk, assumes chunk isn't proxy
chunk* from_chunk(chunk_base* c, unsigned char* node, std::size_t node_size) noexcept
{
auto res = make_chunk(c);
return res->from(node, node_size) ? res : nullptr;
}
// inserts already interconnected chunks into the list
// list will be kept ordered
void insert_chunks(chunk_base* list, chunk_base* begin, chunk_base* end) noexcept
{
WPI_MEMORY_ASSERT(begin && end);
if (list->next == list) // empty
{
begin->prev = list;
end->next = list->next;
list->next = begin;
list->prev = end;
}
else if (less(list->prev, begin)) // insert at end
{
list->prev->next = begin;
begin->prev = list->prev;
end->next = list;
list->prev = end;
}
else
{
auto prev = list;
auto cur = list->next;
while (less(cur, begin))
{
prev = cur;
cur = cur->next;
}
WPI_MEMORY_ASSERT(greater(cur, end));
WPI_MEMORY_ASSERT(prev == list || less(prev, begin));
prev->next = begin;
begin->prev = prev;
end->next = cur;
cur->prev = end;
}
}
} // namespace
constexpr std::size_t small_free_memory_list::min_element_size;
constexpr std::size_t small_free_memory_list::min_element_alignment;
small_free_memory_list::small_free_memory_list(std::size_t node_size) noexcept
: node_size_(node_size), capacity_(0u), alloc_chunk_(&base_), dealloc_chunk_(&base_)
{
}
small_free_memory_list::small_free_memory_list(std::size_t node_size, void* mem,
std::size_t size) noexcept
: small_free_memory_list(node_size)
{
insert(mem, size);
}
small_free_memory_list::small_free_memory_list(small_free_memory_list&& other) noexcept
: node_size_(other.node_size_),
capacity_(other.capacity_),
// reset markers for simplicity
alloc_chunk_(&base_),
dealloc_chunk_(&base_)
{
if (!other.empty())
{
base_.next = other.base_.next;
base_.prev = other.base_.prev;
other.base_.next->prev = &base_;
other.base_.prev->next = &base_;
other.base_.next = &other.base_;
other.base_.prev = &other.base_;
other.capacity_ = 0u;
}
else
{
base_.next = &base_;
base_.prev = &base_;
}
}
void wpi::memory::detail::swap(small_free_memory_list& a, small_free_memory_list& b) noexcept
{
auto b_next = b.base_.next;
auto b_prev = b.base_.prev;
if (!a.empty())
{
b.base_.next = a.base_.next;
b.base_.prev = a.base_.prev;
b.base_.next->prev = &b.base_;
b.base_.prev->next = &b.base_;
}
else
{
b.base_.next = &b.base_;
b.base_.prev = &b.base_;
}
if (!b.empty())
{
a.base_.next = b_next;
a.base_.prev = b_prev;
a.base_.next->prev = &a.base_;
a.base_.prev->next = &a.base_;
}
else
{
a.base_.next = &a.base_;
a.base_.prev = &a.base_;
}
detail::adl_swap(a.node_size_, b.node_size_);
detail::adl_swap(a.capacity_, b.capacity_);
// reset markers for simplicity
a.alloc_chunk_ = a.dealloc_chunk_ = &a.base_;
b.alloc_chunk_ = b.dealloc_chunk_ = &b.base_;
}
void small_free_memory_list::insert(void* mem, std::size_t size) noexcept
{
WPI_MEMORY_ASSERT(mem);
WPI_MEMORY_ASSERT(is_aligned(mem, max_alignment));
debug_fill_internal(mem, size, false);
auto total_chunk_size = chunk_memory_offset + node_size_ * chunk_max_nodes;
auto align_buffer = align_offset(total_chunk_size, alignof(chunk));
auto no_chunks = size / (total_chunk_size + align_buffer);
auto remainder = size % (total_chunk_size + align_buffer);
auto memory = static_cast<char*>(mem);
auto construct_chunk = [&](std::size_t total_memory, std::size_t node_size)
{
WPI_MEMORY_ASSERT(align_offset(memory, alignof(chunk)) == 0);
return ::new (static_cast<void*>(memory)) chunk(total_memory, node_size);
};
auto prev = static_cast<chunk_base*>(nullptr);
for (auto i = std::size_t(0); i != no_chunks; ++i)
{
auto c = construct_chunk(total_chunk_size, node_size_);
c->prev = prev;
if (prev)
prev->next = c;
prev = c;
memory += total_chunk_size;
memory += align_buffer;
}
auto new_nodes = no_chunks * chunk_max_nodes;
if (remainder >= chunk_memory_offset + node_size_) // at least one node
{
auto c = construct_chunk(remainder, node_size_);
c->prev = prev;
if (prev)
prev->next = c;
prev = c;
new_nodes += c->no_nodes;
}
WPI_MEMORY_ASSERT_MSG(new_nodes > 0, "memory block too small");
insert_chunks(&base_, static_cast<chunk_base*>(mem), prev);
capacity_ += new_nodes;
}
std::size_t small_free_memory_list::usable_size(std::size_t size) const noexcept
{
auto total_chunk_size = chunk_memory_offset + node_size_ * chunk_max_nodes;
auto no_chunks = size / total_chunk_size;
auto remainder = size % total_chunk_size;
return no_chunks * chunk_max_nodes * node_size_
+ (remainder > chunk_memory_offset ? remainder - chunk_memory_offset : 0u);
}
void* small_free_memory_list::allocate() noexcept
{
auto chunk = find_chunk_impl(1);
alloc_chunk_ = chunk;
WPI_MEMORY_ASSERT(chunk && chunk->capacity >= 1);
--capacity_;
auto mem = chunk->allocate(node_size_);
WPI_MEMORY_ASSERT(mem);
return detail::debug_fill_new(mem, node_size_, 0);
}
void small_free_memory_list::deallocate(void* mem) noexcept
{
auto info =
allocator_info(WPI_MEMORY_LOG_PREFIX "::detail::small_free_memory_list", this);
auto node = static_cast<unsigned char*>(detail::debug_fill_free(mem, node_size_, 0));
auto chunk = find_chunk_impl(node);
dealloc_chunk_ = chunk;
// memory was never allocated from list
detail::debug_check_pointer([&] { return chunk != nullptr; }, info, mem);
auto offset = static_cast<std::size_t>(node - chunk->list_memory());
// memory is not at the right position
debug_check_pointer([&] { return offset % node_size_ == 0u; }, info, mem);
// double-free
debug_check_double_dealloc([&] { return !chunk->contains(node, node_size_); }, info, mem);
auto index = offset / node_size_;
WPI_MEMORY_ASSERT(index < chunk->no_nodes);
chunk->deallocate(node, static_cast<unsigned char>(index));
++capacity_;
}
std::size_t small_free_memory_list::alignment() const noexcept
{
return alignment_for(node_size_);
}
chunk* small_free_memory_list::find_chunk_impl(std::size_t n) noexcept
{
if (auto c = make_chunk(alloc_chunk_, n))
return c;
else if ((c = make_chunk(dealloc_chunk_, n)) != nullptr)
return c;
auto cur_forward = alloc_chunk_->next;
auto cur_backward = alloc_chunk_->prev;
do
{
if (auto c = make_chunk(cur_forward, n))
return c;
else if ((c = make_chunk(cur_backward, n)) != nullptr)
return c;
cur_forward = cur_forward->next;
cur_backward = cur_backward->prev;
WPI_MEMORY_ASSERT(cur_forward != alloc_chunk_);
WPI_MEMORY_ASSERT(cur_backward != alloc_chunk_);
} while (true);
WPI_MEMORY_UNREACHABLE("there is memory available somewhere...");
return nullptr;
}
chunk* small_free_memory_list::find_chunk_impl(unsigned char* node, chunk_base* first,
chunk_base* last) noexcept
{
do
{
if (auto c = from_chunk(first, node, node_size_))
return c;
else if ((c = from_chunk(last, node, node_size_)) != nullptr)
return c;
first = first->next;
last = last->prev;
} while (!greater(first, last));
return nullptr;
}
chunk* small_free_memory_list::find_chunk_impl(unsigned char* node) noexcept
{
if (auto c = from_chunk(dealloc_chunk_, node, node_size_))
return c;
else if ((c = from_chunk(alloc_chunk_, node, node_size_)) != nullptr)
return c;
else if (less(dealloc_chunk_, node))
{
// node is in (dealloc_chunk_, base_.prev]
return find_chunk_impl(node, dealloc_chunk_->next, base_.prev);
}
else if (greater(dealloc_chunk_, node))
{
// node is in [base.next, dealloc_chunk_)
return find_chunk_impl(node, base_.next, dealloc_chunk_->prev);
}
WPI_MEMORY_UNREACHABLE("must be in one half");
return nullptr;
}

View File

@@ -0,0 +1,106 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/error.hpp"
#include <atomic>
#if WPI_HOSTED_IMPLEMENTATION
#include <cstdio>
#endif
using namespace wpi::memory;
namespace
{
void default_out_of_memory_handler(const allocator_info& info, std::size_t amount) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
std::fprintf(stderr,
"[%s] Allocator %s (at %p) ran out of memory trying to allocate %zu bytes.\n",
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator, amount);
#endif
}
std::atomic<out_of_memory::handler> out_of_memory_h(default_out_of_memory_handler);
} // namespace
out_of_memory::handler out_of_memory::set_handler(out_of_memory::handler h)
{
return out_of_memory_h.exchange(h ? h : default_out_of_memory_handler);
}
out_of_memory::handler out_of_memory::get_handler()
{
return out_of_memory_h;
}
out_of_memory::out_of_memory(const allocator_info& info, std::size_t amount)
: info_(info), amount_(amount)
{
out_of_memory_h.load()(info, amount);
}
const char* out_of_memory::what() const noexcept
{
return "low-level allocator is out of memory";
}
const char* out_of_fixed_memory::what() const noexcept
{
return "fixed size allocator is out of memory";
}
namespace
{
void default_bad_alloc_size_handler(const allocator_info& info, std::size_t passed,
std::size_t supported) noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
std::fprintf(stderr,
"[%s] Allocator %s (at %p) received invalid size/alignment %zu, "
"max supported is %zu\n",
WPI_MEMORY_LOG_PREFIX, info.name, info.allocator, passed, supported);
#endif
}
std::atomic<bad_allocation_size::handler> bad_alloc_size_h(default_bad_alloc_size_handler);
} // namespace
bad_allocation_size::handler bad_allocation_size::set_handler(bad_allocation_size::handler h)
{
return bad_alloc_size_h.exchange(h ? h : default_bad_alloc_size_handler);
}
bad_allocation_size::handler bad_allocation_size::get_handler()
{
return bad_alloc_size_h;
}
bad_allocation_size::bad_allocation_size(const allocator_info& info, std::size_t passed,
std::size_t supported)
: info_(info), passed_(passed), supported_(supported)
{
bad_alloc_size_h.load()(info_, passed_, supported_);
}
const char* bad_allocation_size::what() const noexcept
{
return "allocation node size exceeds supported maximum of allocator";
}
const char* bad_node_size::what() const noexcept
{
return "allocation node size exceeds supported maximum of allocator";
}
const char* bad_array_size::what() const noexcept
{
return "allocation array size exceeds supported maximum of allocator";
}
const char* bad_alignment::what() const noexcept
{
return "allocation alignment exceeds supported maximum of allocator";
}

View File

@@ -0,0 +1,85 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/heap_allocator.hpp"
#include "wpi/memory/error.hpp"
using namespace wpi::memory;
#ifdef _WIN32
#include <malloc.h>
#include <windows.h>
namespace
{
HANDLE get_process_heap() noexcept
{
static auto heap = GetProcessHeap();
return heap;
}
std::size_t max_size() noexcept
{
return _HEAP_MAXREQ;
}
} // namespace
void* wpi::memory::heap_alloc(std::size_t size) noexcept
{
return HeapAlloc(get_process_heap(), 0, size);
}
void wpi::memory::heap_dealloc(void* ptr, std::size_t) noexcept
{
HeapFree(get_process_heap(), 0, ptr);
}
#elif WPI_HOSTED_IMPLEMENTATION
#include <cstdlib>
#include <memory>
void* wpi::memory::heap_alloc(std::size_t size) noexcept
{
return std::malloc(size);
}
void wpi::memory::heap_dealloc(void* ptr, std::size_t) noexcept
{
std::free(ptr);
}
namespace
{
std::size_t max_size() noexcept
{
return std::allocator_traits<std::allocator<char>>::max_size({});
}
} // namespace
#else
// no implementation for heap_alloc/heap_dealloc
namespace
{
std::size_t max_size() noexcept
{
return std::size_t(-1);
}
} // namespace
#endif
allocator_info detail::heap_allocator_impl::info() noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::heap_allocator", nullptr};
}
std::size_t detail::heap_allocator_impl::max_node_size() noexcept
{
return max_size();
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class detail::lowlevel_allocator<detail::heap_allocator_impl>;
template class wpi::memory::allocator_traits<heap_allocator>;
#endif

View File

@@ -0,0 +1,13 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/iteration_allocator.hpp"
using namespace wpi::memory;
#if WPI_MEMORY_EXTERN_TEMPLATE
template class wpi::memory::iteration_allocator<2>;
template class wpi::memory::allocator_traits<iteration_allocator<2>>;
template class wpi::memory::composable_allocator_traits<iteration_allocator<2>>;
#endif

View File

@@ -0,0 +1,24 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/config.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include "wpi/memory/malloc_allocator.hpp"
#include "wpi/memory/error.hpp"
using namespace wpi::memory;
allocator_info detail::malloc_allocator_impl::info() noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::malloc_allocator", nullptr};
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class detail::lowlevel_allocator<detail::malloc_allocator_impl>;
template class wpi::memory::allocator_traits<malloc_allocator>;
#endif
#endif // WPI_HOSTED_IMPLEMENTATION

View File

@@ -0,0 +1,73 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/memory_arena.hpp"
#include <new>
#include "wpi/memory/detail/align.hpp"
using namespace wpi::memory;
using namespace detail;
void memory_block_stack::push(allocated_mb block) noexcept
{
WPI_MEMORY_ASSERT(block.size >= sizeof(node));
WPI_MEMORY_ASSERT(is_aligned(block.memory, max_alignment));
auto next = ::new (block.memory) node(head_, block.size - implementation_offset());
head_ = next;
}
memory_block_stack::allocated_mb memory_block_stack::pop() noexcept
{
WPI_MEMORY_ASSERT(head_);
auto to_pop = head_;
head_ = head_->prev;
return {to_pop, to_pop->usable_size + implementation_offset()};
}
void memory_block_stack::steal_top(memory_block_stack& other) noexcept
{
WPI_MEMORY_ASSERT(other.head_);
auto to_steal = other.head_;
other.head_ = other.head_->prev;
to_steal->prev = head_;
head_ = to_steal;
}
bool memory_block_stack::owns(const void* ptr) const noexcept
{
auto address = static_cast<const char*>(ptr);
for (auto cur = head_; cur; cur = cur->prev)
{
auto mem = static_cast<char*>(static_cast<void*>(cur));
if (address >= mem && address < mem + cur->usable_size)
return true;
}
return false;
}
std::size_t memory_block_stack::size() const noexcept
{
std::size_t res = 0u;
for (auto cur = head_; cur; cur = cur->prev)
++res;
return res;
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class wpi::memory::memory_arena<static_block_allocator, true>;
template class wpi::memory::memory_arena<static_block_allocator, false>;
template class wpi::memory::memory_arena<virtual_block_allocator, true>;
template class wpi::memory::memory_arena<virtual_block_allocator, false>;
template class wpi::memory::growing_block_allocator<>;
template class wpi::memory::memory_arena<growing_block_allocator<>, true>;
template class wpi::memory::memory_arena<growing_block_allocator<>, false>;
template class wpi::memory::fixed_block_allocator<>;
template class wpi::memory::memory_arena<fixed_block_allocator<>, true>;
template class wpi::memory::memory_arena<fixed_block_allocator<>, false>;
#endif

View File

@@ -0,0 +1,28 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/memory_pool.hpp"
#include "wpi/memory/debugging.hpp"
using namespace wpi::memory;
void detail::memory_pool_leak_handler::operator()(std::ptrdiff_t amount)
{
get_leak_handler()({WPI_MEMORY_LOG_PREFIX "::memory_pool", this}, amount);
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class wpi::memory::memory_pool<node_pool>;
template class wpi::memory::memory_pool<array_pool>;
template class wpi::memory::memory_pool<small_node_pool>;
template class wpi::memory::allocator_traits<memory_pool<node_pool>>;
template class wpi::memory::allocator_traits<memory_pool<array_pool>>;
template class wpi::memory::allocator_traits<memory_pool<small_node_pool>>;
template class wpi::memory::composable_allocator_traits<memory_pool<node_pool>>;
template class wpi::memory::composable_allocator_traits<memory_pool<array_pool>>;
template class wpi::memory::composable_allocator_traits<memory_pool<small_node_pool>>;
#endif

View File

@@ -0,0 +1,51 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/memory_pool_collection.hpp"
#include "wpi/memory/debugging.hpp"
using namespace wpi::memory;
void detail::memory_pool_collection_leak_handler::operator()(std::ptrdiff_t amount)
{
get_leak_handler()({WPI_MEMORY_LOG_PREFIX "::memory_pool_collection", this}, amount);
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class wpi::memory::memory_pool_collection<node_pool, identity_buckets>;
template class wpi::memory::memory_pool_collection<array_pool, identity_buckets>;
template class wpi::memory::memory_pool_collection<small_node_pool, identity_buckets>;
template class wpi::memory::memory_pool_collection<node_pool, log2_buckets>;
template class wpi::memory::memory_pool_collection<array_pool, log2_buckets>;
template class wpi::memory::memory_pool_collection<small_node_pool, log2_buckets>;
template class wpi::memory::allocator_traits<
memory_pool_collection<node_pool, identity_buckets>>;
template class wpi::memory::allocator_traits<
memory_pool_collection<array_pool, identity_buckets>>;
template class wpi::memory::allocator_traits<
memory_pool_collection<small_node_pool, identity_buckets>>;
template class wpi::memory::allocator_traits<memory_pool_collection<node_pool, log2_buckets>>;
template class wpi::memory::allocator_traits<
memory_pool_collection<array_pool, log2_buckets>>;
template class wpi::memory::allocator_traits<
memory_pool_collection<small_node_pool, log2_buckets>>;
template class wpi::memory::composable_allocator_traits<
memory_pool_collection<node_pool, identity_buckets>>;
template class wpi::memory::composable_allocator_traits<
memory_pool_collection<array_pool, identity_buckets>>;
template class wpi::memory::composable_allocator_traits<
memory_pool_collection<small_node_pool, identity_buckets>>;
template class wpi::memory::composable_allocator_traits<
memory_pool_collection<node_pool, log2_buckets>>;
template class wpi::memory::composable_allocator_traits<
memory_pool_collection<array_pool, log2_buckets>>;
template class wpi::memory::composable_allocator_traits<
memory_pool_collection<small_node_pool, log2_buckets>>;
#endif

View File

@@ -0,0 +1,21 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/memory_stack.hpp"
#include "wpi/memory/debugging.hpp"
using namespace wpi::memory;
void detail::memory_stack_leak_handler::operator()(std::ptrdiff_t amount)
{
get_leak_handler()({WPI_MEMORY_LOG_PREFIX "::memory_stack", this}, amount);
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class wpi::memory::memory_stack<>;
template class wpi::memory::memory_stack_raii_unwind<memory_stack<>>;
template class wpi::memory::allocator_traits<memory_stack<>>;
template class wpi::memory::composable_allocator_traits<memory_stack<>>;
#endif

View File

@@ -0,0 +1,72 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/new_allocator.hpp"
#if WPI_HOSTED_IMPLEMENTATION
#include <memory>
#endif
#include <new>
#include "wpi/memory/error.hpp"
using namespace wpi::memory;
allocator_info detail::new_allocator_impl::info() noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::new_allocator", nullptr};
}
void* detail::new_allocator_impl::allocate(std::size_t size, size_t) noexcept
{
void* memory = nullptr;
while (true)
{
memory = ::operator new(size, std::nothrow);
if (memory)
break;
auto handler = std::get_new_handler();
if (handler)
{
#if WPI_HAS_EXCEPTION_SUPPORT
try
{
handler();
}
catch (...)
{
return nullptr;
}
#else
handler();
#endif
}
else
{
return nullptr;
}
}
return memory;
}
void detail::new_allocator_impl::deallocate(void* ptr, std::size_t, size_t) noexcept
{
::operator delete(ptr);
}
std::size_t detail::new_allocator_impl::max_node_size() noexcept
{
#if WPI_HOSTED_IMPLEMENTATION
return std::allocator_traits<std::allocator<char>>::max_size({});
#else
return std::size_t(-1);
#endif
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class detail::lowlevel_allocator<detail::new_allocator_impl>;
template class wpi::memory::allocator_traits<new_allocator>;
#endif

View File

@@ -0,0 +1,50 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/static_allocator.hpp"
#include "wpi/memory/detail/debug_helpers.hpp"
#include "wpi/memory/error.hpp"
#include "wpi/memory/memory_arena.hpp"
using namespace wpi::memory;
void* static_allocator::allocate_node(std::size_t size, std::size_t alignment)
{
auto mem = stack_.allocate(end_, size, alignment);
if (!mem)
WPI_THROW(out_of_fixed_memory(info(), size));
return mem;
}
allocator_info static_allocator::info() const noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::static_allocator", this};
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class wpi::memory::allocator_traits<static_allocator>;
#endif
memory_block static_block_allocator::allocate_block()
{
if (cur_ + block_size_ > end_)
WPI_THROW(out_of_fixed_memory(info(), block_size_));
auto mem = cur_;
cur_ += block_size_;
return {mem, block_size_};
}
void static_block_allocator::deallocate_block(memory_block block) noexcept
{
detail::
debug_check_pointer([&] { return static_cast<char*>(block.memory) + block.size == cur_; },
info(), block.memory);
cur_ -= block_size_;
}
allocator_info static_block_allocator::info() const noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::static_block_allocator", this};
}

View File

@@ -0,0 +1,322 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/temporary_allocator.hpp"
#include <new>
#include <type_traits>
#include "wpi/memory/detail/assert.hpp"
#include "wpi/memory/default_allocator.hpp"
#include "wpi/memory/error.hpp"
using namespace wpi::memory;
namespace
{
void default_growth_tracker(std::size_t) noexcept {}
using temporary_impl_allocator = default_allocator;
using temporary_impl_allocator_traits = allocator_traits<temporary_impl_allocator>;
} // namespace
detail::temporary_block_allocator::temporary_block_allocator(std::size_t block_size) noexcept
: tracker_(default_growth_tracker), block_size_(block_size)
{
}
detail::temporary_block_allocator::growth_tracker detail::temporary_block_allocator::
set_growth_tracker(growth_tracker t) noexcept
{
auto old = tracker_;
tracker_ = t;
return old;
}
detail::temporary_block_allocator::growth_tracker detail::temporary_block_allocator::
get_growth_tracker() noexcept
{
return tracker_;
}
memory_block detail::temporary_block_allocator::allocate_block()
{
auto alloc = temporary_impl_allocator();
auto memory = temporary_impl_allocator_traits::allocate_array(alloc, block_size_, 1,
detail::max_alignment);
auto block = memory_block(memory, block_size_);
block_size_ = growing_block_allocator<temporary_impl_allocator>::grow_block_size(block_size_);
return block;
}
void detail::temporary_block_allocator::deallocate_block(memory_block block)
{
auto alloc = temporary_impl_allocator();
temporary_impl_allocator_traits::deallocate_array(alloc, block.memory, block.size, 1,
detail::max_alignment);
}
#if WPI_MEMORY_TEMPORARY_STACK_MODE >= 2
// lifetime managment through the nifty counter and the list
// note: I could have used a simple `thread_local` variable for the temporary stack
// but this could lead to issues with destruction order
// and more importantly I have to support platforms that can't handle non-trivial thread local's
// hence I need to dynamically allocate the stack's and store them in a container
// on program exit the container is iterated and all stack's are properly destroyed
// if a thread exit can be detected, the dynamic memory of the stack is already released,
// but not the stack itself destroyed
#if !defined(__MINGW64__)
// only use the thread exit detector if we have thread local and are not running on MinGW due to a bug
// see: https://sourceforge.net/p/mingw-w64/bugs/527/
#define WPI_MEMORY_THREAD_EXIT_DETECTOR 1
#else
#define WPI_MEMORY_THREAD_EXIT_DETECTOR 0
#if defined(_MSC_VER)
#pragma message( \
"thread_local doesn't support destructors, need to use the temporary_stack_initializer to ensure proper cleanup of the temporary memory")
#else
#warning \
"thread_local doesn't support destructors, need to use the temporary_stack_initializer to ensure proper cleanup of the temporary memory"
#endif
#endif
static class detail::temporary_stack_list
{
public:
std::atomic<temporary_stack_list_node*> first;
temporary_stack* create_new(std::size_t size)
{
auto storage =
default_allocator().allocate_node(sizeof(temporary_stack), alignof(temporary_stack));
return ::new (storage) temporary_stack(0, size);
}
temporary_stack* find_unused()
{
for (auto ptr = first.load(); ptr; ptr = ptr->next_)
{
auto value = false;
if (ptr->in_use_.compare_exchange_strong(value, true))
return static_cast<temporary_stack*>(ptr);
}
return nullptr;
}
temporary_stack* create(std::size_t size)
{
if (auto ptr = find_unused())
{
WPI_MEMORY_ASSERT(ptr->in_use_);
ptr->stack_ = detail::temporary_stack_impl(size);
return ptr;
}
return create_new(size);
}
void clear(temporary_stack& stack)
{
// stack should be empty now, so shrink_to_fit() clears all memory
stack.stack_.shrink_to_fit();
stack.in_use_ = false; // mark as free
}
void destroy()
{
for (auto ptr = first.exchange(nullptr); ptr;)
{
auto stack = static_cast<temporary_stack*>(ptr);
auto next = ptr->next_;
stack->~temporary_stack();
default_allocator().deallocate_node(stack, sizeof(temporary_stack),
alignof(temporary_stack));
ptr = next;
}
WPI_MEMORY_ASSERT_MSG(!first.load(),
"destroy() called while other threads are still running");
}
} temporary_stack_list_obj;
namespace
{
thread_local std::size_t nifty_counter;
thread_local temporary_stack* temp_stack = nullptr;
#if WPI_MEMORY_THREAD_EXIT_DETECTOR
// don't use this on a bug
thread_local struct thread_exit_detector_t
{
~thread_exit_detector_t() noexcept
{
if (temp_stack)
// clear automatically on thread exit, as the initializer's destructor does
// note: if another's thread_local variable destructor is called after this one
// and that destructor uses the temporary allocator
// the stack needs to grow again
// but who does temporary allocation in a destructor?!
temporary_stack_list_obj.clear(*temp_stack);
}
} thread_exit_detector;
#endif
} // namespace
detail::temporary_stack_list_node::temporary_stack_list_node(int) noexcept : in_use_(true)
{
next_ = temporary_stack_list_obj.first.load();
while (!temporary_stack_list_obj.first.compare_exchange_weak(next_, this))
;
#if WPI_MEMORY_THREAD_EXIT_DETECTOR
(void)&thread_exit_detector; // ODR-use it, so it will be created
#endif
}
detail::temporary_allocator_dtor_t::temporary_allocator_dtor_t() noexcept
{
++nifty_counter;
}
detail::temporary_allocator_dtor_t::~temporary_allocator_dtor_t() noexcept
{
if (--nifty_counter == 0u && temp_stack)
temporary_stack_list_obj.destroy();
}
temporary_stack_initializer::temporary_stack_initializer(std::size_t initial_size)
{
if (!temp_stack)
temp_stack = temporary_stack_list_obj.create(initial_size);
}
temporary_stack_initializer::~temporary_stack_initializer() noexcept
{
// don't destroy, nifty counter does that
// but can get rid of all the memory
if (temp_stack)
temporary_stack_list_obj.clear(*temp_stack);
}
temporary_stack& wpi::memory::get_temporary_stack(std::size_t initial_size)
{
if (!temp_stack)
temp_stack = temporary_stack_list_obj.create(initial_size);
return *temp_stack;
}
#elif WPI_MEMORY_TEMPORARY_STACK_MODE == 1
namespace
{
WPI_THREAD_LOCAL alignas(
temporary_stack) char temporary_stack_storage[sizeof(temporary_stack)];
WPI_THREAD_LOCAL bool is_created = false;
temporary_stack& get() noexcept
{
WPI_MEMORY_ASSERT(is_created);
return *static_cast<temporary_stack*>(static_cast<void*>(&temporary_stack_storage));
}
void create(std::size_t initial_size)
{
if (!is_created)
{
::new (static_cast<void*>(&temporary_stack_storage)) temporary_stack(initial_size);
is_created = true;
}
}
} // namespace
// explicit lifetime managment
temporary_stack_initializer::temporary_stack_initializer(std::size_t initial_size)
{
create(initial_size);
}
temporary_stack_initializer::~temporary_stack_initializer()
{
if (is_created)
get().~temporary_stack();
}
temporary_stack& wpi::memory::get_temporary_stack(std::size_t initial_size)
{
create(initial_size);
return get();
}
#else
// no lifetime managment
temporary_stack_initializer::temporary_stack_initializer(std::size_t initial_size)
{
if (initial_size != 0u)
WPI_MEMORY_WARNING("temporary_stack_initializer() has no effect if "
"WPI_MEMORY_TEMPORARY_STACK == 0 (pass an initial size of 0 "
"to disable this message)");
}
temporary_stack_initializer::~temporary_stack_initializer() {}
temporary_stack& wpi::memory::get_temporary_stack(std::size_t)
{
WPI_MEMORY_UNREACHABLE("get_temporary_stack() called but stack is disabled by "
"WPI_MEMORY_TEMPORARY_STACK == 0");
std::abort();
}
#endif
const temporary_stack_initializer::defer_create_t temporary_stack_initializer::defer_create;
temporary_allocator::temporary_allocator() : temporary_allocator(get_temporary_stack()) {}
temporary_allocator::temporary_allocator(temporary_stack& stack)
: unwind_(stack), prev_(stack.top_), shrink_to_fit_(false)
{
WPI_MEMORY_ASSERT(!prev_ || prev_->is_active());
stack.top_ = this;
}
temporary_allocator::~temporary_allocator() noexcept
{
if (is_active())
{
auto& stack = unwind_.get_stack();
stack.top_ = prev_;
unwind_.unwind(); // manually call it now...
if (shrink_to_fit_)
// to call shrink_to_fit() afterwards
stack.stack_.shrink_to_fit();
}
}
void* temporary_allocator::allocate(std::size_t size, std::size_t alignment)
{
WPI_MEMORY_ASSERT_MSG(is_active(), "object isn't the active allocator");
return unwind_.get_stack().stack_.allocate(size, alignment);
}
void temporary_allocator::shrink_to_fit() noexcept
{
shrink_to_fit_ = true;
}
bool temporary_allocator::is_active() const noexcept
{
WPI_MEMORY_ASSERT(unwind_.will_unwind());
auto res = unwind_.get_stack().top_ == this;
// check that prev is actually before this
WPI_MEMORY_ASSERT(!res || !prev_ || prev_->unwind_.get_marker() <= unwind_.get_marker());
return res;
}

View File

@@ -0,0 +1,240 @@
// Copyright (C) 2015-2021 Müller <jonathanmueller.dev@gmail.com>
// This file is subject to the license terms in the LICENSE file
// found in the top-level directory of this distribution.
#include "wpi/memory/virtual_memory.hpp"
#include "wpi/memory/detail/debug_helpers.hpp"
#include "wpi/memory/error.hpp"
#include "wpi/memory/memory_arena.hpp"
using namespace wpi::memory;
void detail::virtual_memory_allocator_leak_handler::operator()(std::ptrdiff_t amount)
{
detail::debug_handle_memory_leak({WPI_MEMORY_LOG_PREFIX "::virtual_memory_allocator",
nullptr},
amount);
}
#if defined(_WIN32)
#include <windows.h>
namespace
{
std::size_t get_page_size() noexcept
{
static_assert(sizeof(std::size_t) >= sizeof(DWORD), "possible loss of data");
SYSTEM_INFO info;
GetSystemInfo(&info);
return std::size_t(info.dwPageSize);
}
} // namespace
const std::size_t wpi::memory::virtual_memory_page_size = get_page_size();
void* wpi::memory::virtual_memory_reserve(std::size_t no_pages) noexcept
{
auto pages =
#if (_MSC_VER <= 1900) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
VirtualAlloc(nullptr, no_pages * virtual_memory_page_size, MEM_RESERVE, PAGE_READWRITE);
#else
VirtualAllocFromApp(nullptr, no_pages * virtual_memory_page_size, MEM_RESERVE,
PAGE_READWRITE);
#endif
return pages;
}
void wpi::memory::virtual_memory_release(void* pages, std::size_t) noexcept
{
auto result = VirtualFree(pages, 0u, MEM_RELEASE);
WPI_MEMORY_ASSERT_MSG(result, "cannot release pages");
}
void* wpi::memory::virtual_memory_commit(void* memory, std::size_t no_pages) noexcept
{
auto region =
#if (_MSC_VER <= 1900) || WINAPI_FAMILY_PARTITION(WINAPI_PARTITION_DESKTOP)
VirtualAlloc(memory, no_pages * virtual_memory_page_size, MEM_COMMIT, PAGE_READWRITE);
#else
VirtualAllocFromApp(memory, no_pages * virtual_memory_page_size, MEM_COMMIT,
PAGE_READWRITE);
#endif
if (!region)
return nullptr;
WPI_MEMORY_ASSERT(region == memory);
return region;
}
void wpi::memory::virtual_memory_decommit(void* memory, std::size_t no_pages) noexcept
{
auto result = VirtualFree(memory, no_pages * virtual_memory_page_size, MEM_DECOMMIT);
WPI_MEMORY_ASSERT_MSG(result, "cannot decommit memory");
}
#elif defined(__unix__) || defined(__APPLE__) || defined(__VXWORKS__) \
|| defined(__QNXNTO__) // POSIX systems
#include <sys/mman.h>
#include <unistd.h>
#if defined(PAGESIZE)
const std::size_t wpi::memory::virtual_memory_page_size = PAGESIZE;
#elif defined(PAGE_SIZE)
const std::size_t wpi::memory::virtual_memory_page_size = PAGE_SIZE;
#else
const std::size_t wpi::memory::virtual_memory_page_size =
static_cast<std::size_t>(sysconf(_SC_PAGESIZE));
#endif
#ifndef MAP_ANONYMOUS
#define MAP_ANONYMOUS MAP_ANON
#endif
void* wpi::memory::virtual_memory_reserve(std::size_t no_pages) noexcept
{
auto pages = mmap(nullptr, no_pages * virtual_memory_page_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
return pages == MAP_FAILED ? nullptr : pages;
}
void wpi::memory::virtual_memory_release(void* pages, std::size_t no_pages) noexcept
{
auto result = munmap(pages, no_pages * virtual_memory_page_size);
WPI_MEMORY_ASSERT_MSG(result == 0, "cannot release pages");
(void)result;
}
void* wpi::memory::virtual_memory_commit(void* memory, std::size_t no_pages) noexcept
{
auto size = no_pages * virtual_memory_page_size;
auto result = mprotect(memory, size, PROT_WRITE | PROT_READ);
if (result != 0)
return nullptr;
// advise that the memory will be needed
#if defined(MADV_WILLNEED)
madvise(memory, size, MADV_WILLNEED);
#elif defined(POSIX_MADV_WILLNEED)
posix_madvise(memory, size, POSIX_MADV_WILLNEED);
#endif
return memory;
}
void wpi::memory::virtual_memory_decommit(void* memory, std::size_t no_pages) noexcept
{
auto size = no_pages * virtual_memory_page_size;
// advise that the memory won't be needed anymore
#if defined(MADV_FREE)
madvise(memory, size, MADV_FREE);
#elif defined(MADV_DONTNEED)
madvise(memory, size, MADV_DONTNEED);
#elif defined(POSIX_MADV_DONTNEED)
posix_madvise(memory, size, POSIX_MADV_DONTNEED);
#endif
auto result = mprotect(memory, size, PROT_NONE);
WPI_MEMORY_ASSERT_MSG(result == 0, "cannot decommit memory");
(void)result;
}
#else
#warning "virtual memory functions not available on your platform, define your own"
#endif
std::size_t wpi::memory::get_virtual_memory_page_size() noexcept
{
return virtual_memory_page_size;
}
namespace
{
std::size_t calc_no_pages(std::size_t size) noexcept
{
auto div = size / virtual_memory_page_size;
auto rest = size % virtual_memory_page_size;
return div + (rest != 0u) + (detail::debug_fence_size ? 2u : 1u);
}
} // namespace
void* virtual_memory_allocator::allocate_node(std::size_t size, std::size_t)
{
auto no_pages = calc_no_pages(size);
auto pages = virtual_memory_reserve(no_pages);
if (!pages || !virtual_memory_commit(pages, no_pages))
WPI_THROW(
out_of_memory({WPI_MEMORY_LOG_PREFIX "::virtual_memory_allocator", nullptr},
no_pages * virtual_memory_page_size));
on_allocate(size);
return detail::debug_fill_new(pages, size, virtual_memory_page_size);
}
void virtual_memory_allocator::deallocate_node(void* node, std::size_t size, std::size_t) noexcept
{
auto pages = detail::debug_fill_free(node, size, virtual_memory_page_size);
on_deallocate(size);
auto no_pages = calc_no_pages(size);
virtual_memory_decommit(pages, no_pages);
virtual_memory_release(pages, no_pages);
}
std::size_t virtual_memory_allocator::max_node_size() const noexcept
{
return std::size_t(-1);
}
std::size_t virtual_memory_allocator::max_alignment() const noexcept
{
return virtual_memory_page_size;
}
#if WPI_MEMORY_EXTERN_TEMPLATE
template class wpi::memory::allocator_traits<virtual_memory_allocator>;
#endif
virtual_block_allocator::virtual_block_allocator(std::size_t block_size, std::size_t no_blocks)
: block_size_(block_size)
{
WPI_MEMORY_ASSERT(block_size % virtual_memory_page_size == 0u);
WPI_MEMORY_ASSERT(no_blocks > 0);
auto total_size = block_size_ * no_blocks;
auto no_pages = total_size / virtual_memory_page_size;
cur_ = static_cast<char*>(virtual_memory_reserve(no_pages));
if (!cur_)
WPI_THROW(out_of_memory(info(), total_size));
end_ = cur_ + total_size;
}
virtual_block_allocator::~virtual_block_allocator() noexcept
{
virtual_memory_release(cur_, static_cast<std::size_t>(end_ - cur_) / virtual_memory_page_size);
}
memory_block virtual_block_allocator::allocate_block()
{
if (std::size_t(end_ - cur_) < block_size_)
WPI_THROW(out_of_fixed_memory(info(), block_size_));
auto mem = virtual_memory_commit(cur_, block_size_ / virtual_memory_page_size);
if (!mem)
WPI_THROW(out_of_fixed_memory(info(), block_size_));
cur_ += block_size_;
return {mem, block_size_};
}
void virtual_block_allocator::deallocate_block(memory_block block) noexcept
{
detail::debug_check_pointer([&]
{ return static_cast<char*>(block.memory) == cur_ - block_size_; },
info(), block.memory);
cur_ -= block_size_;
virtual_memory_decommit(cur_, block_size_);
}
allocator_info virtual_block_allocator::info() noexcept
{
return {WPI_MEMORY_LOG_PREFIX "::virtual_block_allocator", this};
}