[upstream_utils] Remove memory library (#8035)

We added this in 2022. If we haven't used it by now, we probably never
will and it's just wasting space.
This commit is contained in:
Tyler Veness
2025-06-24 22:35:30 -07:00
committed by GitHub
parent 676f2f84d7
commit ddc5220ed4
74 changed files with 3 additions and 16329 deletions

View File

@@ -120,12 +120,6 @@ jobs:
./mpack.py clone
./mpack.py copy-src
./mpack.py format-patch
- name: Run memory.py
run: |
cd upstream_utils
./memory.py clone
./memory.py copy-src
./memory.py format-patch
- name: Run protobuf.py
run: |
cd upstream_utils

View File

@@ -48,14 +48,13 @@ apriltag apriltag/src/main/native/thirdparty/apriltag
glfw thirdparty/imgui_suite/glfw
Dear ImGui thirdparty/imgui_suite/imgui
implot thirdparty/imgui_suite/implot
memory wpiutil/src/main/native/thirdparty/memory
nanopb wpiutil/src/main/native/thirdparty/nanopb
protobuf wpiutil/src/main/native/thirdparty/protobuf
mrcal wpical/src/main/native/thirdparty/mrcal
libdogleg wpical/src/main/native/thirdparty/libdogleg
Simd hal/src/main/native/athena/simd
Additionally, glfw, memory, and nanopb were all modified for use in WPILib.
Additionally, glfw and nanopb were modified for use in WPILib.
==============================================================================
Google Test License
@@ -1331,31 +1330,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
==============
memory License
==============
Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
This software is provided 'as-is', without any express or
implied warranty. In no event will the authors be held
liable for any damages arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute
it freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented;
you must not claim that you wrote the original software.
If you use this software in a product, an acknowledgment
in the product documentation would be appreciated but
is not required.
2. Altered source versions must be plainly marked as such,
and must not be misrepresented as being the original software.
3. This notice may not be removed or altered from any
source distribution.
==============
nanopb License
==============

View File

@@ -138,9 +138,6 @@ doxygen {
exclude 'wpi/ordered_map.h'
exclude 'wpi/thirdparty/**'
// memory
exclude 'wpi/memory/**'
// mpack
exclude 'wpi/mpack.h'

View File

@@ -1,104 +0,0 @@
#!/usr/bin/env python3
import os
import shutil
from pathlib import Path
from upstream_utils import Lib, walk_cwd_and_copy_if
def run_source_replacements(memory_files: list[Path]):
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: list[Path]):
for wpi_file in memory_files:
if "detail" not in wpi_file.parts:
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: list[Path]):
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 copy_upstream_src(wpilib_root: Path):
upstream_root = Path(".").absolute()
wpiutil = wpilib_root / "wpiutil"
# Delete old install
for d in [
"src/main/native/thirdparty/memory/src",
"src/main/native/thirdparty/memory/include",
]:
shutil.rmtree(wpiutil / d, ignore_errors=True)
# Copy sources
os.chdir(upstream_root / "src")
src_files = walk_cwd_and_copy_if(
lambda dp, f: f.endswith(".cpp") or f.endswith(".hpp"),
wpiutil / "src/main/native/thirdparty/memory/src",
)
run_global_replacements(src_files)
run_source_replacements(src_files)
# Copy headers
os.chdir(upstream_root / "include/foonathan")
include_files = walk_cwd_and_copy_if(
lambda dp, f: f.endswith(".hpp"),
wpiutil / "src/main/native/thirdparty/memory/include/wpi",
)
os.chdir(upstream_root)
run_global_replacements(include_files)
run_header_replacements(include_files)
# Copy config_impl.hpp
shutil.copyfile(
wpilib_root / "upstream_utils/memory_files/config_impl.hpp",
wpiutil
/ "src/main/native/thirdparty/memory/include/wpi/memory/config_impl.hpp",
)
def main():
name = "memory"
url = "https://github.com/foonathan/memory"
tag = "v0.7-3"
memory = Lib(name, url, tag, copy_upstream_src)
memory.main()
if __name__ == "__main__":
main()

View File

@@ -1,34 +0,0 @@
// 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

@@ -1,52 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Tyler Veness <calcmogul@gmail.com>
Date: Tue, 8 Apr 2025 15:30:06 -0700
Subject: [PATCH 3/3] Fix deprecation warning for UDLs
---
include/foonathan/memory/memory_arena.hpp | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/include/foonathan/memory/memory_arena.hpp b/include/foonathan/memory/memory_arena.hpp
index eb969a677329b5b2d536f39f9a15817f040cf79f..d91f2a58cef56278cdb091daf34cebba7ec5b92c 100644
--- a/include/foonathan/memory/memory_arena.hpp
+++ b/include/foonathan/memory/memory_arena.hpp
@@ -656,32 +656,32 @@ namespace foonathan
/// \returns The number of bytes `value` is in the given unit.
/// \ingroup memory_core
/// @{
- constexpr std::size_t operator"" _KiB(unsigned long long value) noexcept
+ 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
+ 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
+ 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
+ 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
+ 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
+ constexpr std::size_t operator""_GB(unsigned long long value) noexcept
{
return std::size_t(value * 1000 * 1000 * 1000);
}

View File

@@ -91,22 +91,6 @@ filegroup(
visibility = ["//wpiutil:__subpackages__"],
)
cc_library(
name = "memory-headers",
hdrs = glob([
"src/main/native/thirdparty/memory/include/**/*.hpp",
]),
includes = ["src/main/native/thirdparty/memory/include"],
strip_include_prefix = "src/main/native/thirdparty/memory/include",
visibility = ["//wpiutil:__subpackages__"],
)
filegroup(
name = "memory-srcs",
srcs = glob(["src/main/native/thirdparty/memory/src/**"]),
visibility = ["//wpiutil:__subpackages__"],
)
cc_library(
name = "mpack-headers",
hdrs = glob([
@@ -195,7 +179,6 @@ cc_library(
":fmtlib-srcs",
":generate-resources",
":llvm-srcs",
":memory-srcs",
":mpack-srcs",
":nanopb-srcs",
":native-srcs",
@@ -213,7 +196,6 @@ cc_library(
":fmtlib-headers",
":json-headers",
":llvm-headers",
":memory-headers",
":mpack-headers",
":nanopb-headers",
":protobuf-headers",

View File

@@ -134,9 +134,8 @@ 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} ${memory_native_src} ${wpiutil_resources_src})
add_library(wpiutil ${wpiutil_native_src} ${wpiutil_resources_src})
set_target_properties(wpiutil PROPERTIES DEBUG_POSTFIX "d")
set_property(TARGET wpiutil PROPERTY FOLDER "libraries")
@@ -203,7 +202,6 @@ install(
src/main/native/thirdparty/expected/include/
src/main/native/thirdparty/json/include/
src/main/native/thirdparty/llvm/include/
src/main/native/thirdparty/memory/include/
src/main/native/thirdparty/mpack/include/
src/main/native/thirdparty/nanopb/include/
src/main/native/thirdparty/sigslot/include/
@@ -218,7 +216,6 @@ target_include_directories(
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/expected/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/json/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/llvm/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/memory/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/mpack/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/nanopb/include>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src/main/native/thirdparty/sigslot/include>

View File

@@ -80,16 +80,6 @@ ext {
srcDirs 'src/main/native/thirdparty/sigslot/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'
}
}
protobufCpp(CppSourceSet) {
source {
srcDirs 'src/main/native/thirdparty/protobuf/src'
@@ -201,7 +191,6 @@ cppHeadersZip {
'src/main/native/thirdparty/mpack/include',
'src/main/native/thirdparty/nanopb/include',
'src/main/native/thirdparty/sigslot/include',
'src/main/native/thirdparty/memory/include',
'src/main/native/thirdparty/protobuf/include'
]
@@ -223,9 +212,6 @@ cppSourcesZip {
from('src/main/native/thirdparty/llvm/cpp') {
into '/'
}
from('src/main/native/thirdparty/memory/src') {
into '/'
}
from('src/main/native/thirdparty/mpack/src') {
into '/'
}
@@ -245,7 +231,7 @@ model {
all {
it.sources.each {
it.exportedHeaders {
srcDirs 'src/main/native/include', 'src/main/native/thirdparty/argparse/include/', 'src/main/native/thirdparty/debugging/include', 'src/main/native/thirdparty/expected/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/memory/include', 'src/main/native/thirdparty/mpack/include', 'src/main/native/thirdparty/protobuf/include', 'src/main/native/thirdparty/nanopb/include'
srcDirs 'src/main/native/include', 'src/main/native/thirdparty/argparse/include/', 'src/main/native/thirdparty/debugging/include', 'src/main/native/thirdparty/expected/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/mpack/include', 'src/main/native/thirdparty/protobuf/include', 'src/main/native/thirdparty/nanopb/include'
}
}
}

View File

@@ -1,196 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 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 memory_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

@@ -1,933 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 RawAllocator that stores another allocator.
/// The StoragePolicy defines the allocator type being stored and how it is stored.
/// The \c Mutex controls synchronization of the access.
/// \ingroup memory_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(std::declval<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(
std::declval<const allocator_storage<OtherPolicy, Mutex>&>().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 memory_storage
struct any_allocator
{
};
/// A StoragePolicy that stores the allocator directly.
/// It embeds the allocator inside it, i.e. moving the storage policy will move the allocator.
/// \ingroup memory_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 RawAllocator the interface with all member functions,
/// avoiding the need to wrap it inside the \ref allocator_traits.
/// \ingroup memory_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 memory_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 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 memory_storage
template <class RawAllocator>
struct is_shared_allocator : std::false_type
{
};
/// A 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 memory_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 memory_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 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 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 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 memory_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 memory_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 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 memory_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

@@ -1,601 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 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 memory_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 memory_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 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 memory_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 RawAllocator.<br>
/// It must either provide the necessary functions for the default traits specialization or has specialized it.
/// \ingroup memory_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 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 memory_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 ComposableAllocator.<br>
/// It must be a RawAllocator and either provide the necessary functions for the default traits specialization or has specialized it.
/// \ingroup memory_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

@@ -1,147 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
/// \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 memory_core
#define WPI_MEMORY_VERSION_MAJOR 1
/// The minor version number.
/// \ingroup memory_core
#define WPI_MEMORY_VERSION_MINOR 1
/// The total version number of the form \c Mmm.
/// \ingroup memory_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 memory_core
#define WPI_MEMORY_CHECK_ALLOCATION_SIZE 1
/// Whether or not internal assertions in the library are enabled.
/// \ingroup memory_core
#define WPI_MEMORY_DEBUG_ASSERT 1
/// Whether or not allocated memory will be filled with special values.
/// \ingroup memory_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 memory_core
#define WPI_MEMORY_DEBUG_FENCE 1
/// Whether or not leak checking is enabled.
/// \ingroup memory_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 memory_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 memory_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 memory_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 wpi::memory::get_temporary_stack() will abort the program upon call.
/// \ingroup memory_allocator
#define WPI_MEMORY_TEMPORARY_STACK_MODE 2
#endif
#endif // WPI_MEMORY_CONFIG_HPP_INCLUDED

View File

@@ -1,34 +0,0 @@
// 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

@@ -1,367 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#ifndef WPI_MEMORY_CONTAINER_HPP_INCLUDED
#define WPI_MEMORY_CONTAINER_HPP_INCLUDED
/// \file
/// Aliasas for STL containers using a certain 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 memory_adapter
/// @{
/// Alias template for an STL container that uses a certain
/// 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
/// RawAllocator. \returns An empty adapter with an
/// implementation container using a reference to a given allocator. \ingroup memory_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 memory_adapter
/// @{
/// Contains the node size of a node based STL container with a specific type.
///
/// This trait is auto-generated and may not be available depending on the build configuration,
/// especially when doing cross compilation.
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>
{
};
/// Contains the node size of a node based STL container with a specific type.
///
/// This trait is auto-generated and may not be available depending on the build configuration,
/// especially when doing cross compilation.
///
/// \notes `T` is always the `value_type` of the container, e.g. `std::pair<const Key, Value>`.
template <typename T>
struct map_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc map_node_size
template <typename T>
struct multimap_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc map_node_size
template <typename T>
struct unordered_map_node_size : std::integral_constant<std::size_t, implementation_defined>
{
};
/// \copydoc map_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
/// 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

@@ -1,113 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_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 memory_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 memory_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 memory_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 memory_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 memory_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 memory_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 memory_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 memory_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 memory_core
buffer_overflow_handler get_buffer_overflow_handler();
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_DEBUGGING_HPP_INCLUDED

View File

@@ -1,36 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 RawAllocator that will be used as BlockAllocator in memory arenas.
/// Arena allocators like \ref memory_stack or \ref memory_pool allocate memory by subdividing a huge block.
/// They get a 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 memory_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

@@ -1,307 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#ifndef WPI_MEMORY_DELETER_HPP_INCLUDED
#define WPI_MEMORY_DELETER_HPP_INCLUDED
/// \file
/// \c Deleter classes using a 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 RawAllocator.
///
/// It deallocates memory for a specified type but does not call its destructors.
/// \ingroup memory_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 memory_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 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 memory_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 memory_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 memory_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 memory_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

@@ -1,51 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,56 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,9 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,234 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,41 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,227 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,125 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,68 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,85 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,119 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,163 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,117 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,288 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
/// \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 memory_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 memory_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 memory_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 memory_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 memory_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 memory_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 memory_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

@@ -1,211 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 RawAllocator with a fallback.
/// Allocation first tries `Default`, if it fails,
/// it uses `Fallback`.
/// \requires `Default` must be a composable RawAllocator,
/// `Fallback` must be a RawAllocator.
/// \ingroup memory_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

@@ -1,82 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_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 memory_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 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 memory_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

@@ -1,304 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 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 memory_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 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 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 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 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 memory_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 memory_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 memory_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

@@ -1,926 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_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 memory_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 memory_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 RawAllocator,
/// it is stored in an \ref allocator_reference and not owned by the pointer directly.
/// \ingroup memory_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 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 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 memory_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 memory_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 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 memory_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 memory_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 memory_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 memory_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

@@ -1,70 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 RawAllocator that allocates memory using <tt>std::malloc()</tt>.
/// It throws \ref out_of_memory when the allocation fails.
/// \ingroup memory_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

@@ -1,692 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 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 memory_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 BlockAllocator.
/// \ingroup memory_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 memory_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 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 memory_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 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 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 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 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 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 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 BlockAllocator that uses a given 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 memory_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 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 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 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 RawAllocator.
/// \ingroup memory_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 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 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 BlockAllocator or a 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 RawAllocator.
/// Using this allows passing normal RawAllocators as BlockAllocators.
/// \ingroup memory_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 BlockAllocator.
/// \returns A BlockAllocator of the given type created with the given arguments.
/// \requires Same requirements as the constructor.
/// \ingroup memory_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 memory_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

@@ -1,432 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 RawAllocator that manages 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 memory_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 node.
/// \requires \c node_size must be a valid 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 node will have,
/// the initial block size for the arena and other constructor arguments for the 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 BlockAllocator
/// and puts it onto the free list.
/// \requires \c node_size must be a valid 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 memory_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 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 memory_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 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

@@ -1,568 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_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 memory_allocator
struct log2_buckets
{
using type = detail::log2_access_policy;
};
/// A stateful 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 node allocations in any order but with a predefined set of sizes,
/// not only one size like \ref memory_pool.
/// \ingroup memory_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 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 block_size must be non-zero and \c max_node_size must be a valid node size and smaller than \c block_size divided by the number of pools.
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)
{
detail::check_allocation_size<bad_node_size>(max_node_size, def_capacity(), info());
}
/// \effects Destroys the \ref memory_pool_collection by returning all memory blocks,
/// regardless of properly deallocated back to the 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 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 BlockAllocator
/// of size \ref next_capacity() and puts part of it onto this free list.
/// Then it removes a node from it.
/// \returns A node of given size suitable aligned,
/// i.e. suitable for any type where <tt>sizeof(T) < node_size</tt>.
/// \throws Anything thrown by the 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 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 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 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 BlockAllocator's allocation function if a growth is needed,
/// or a \ref bad_allocation_size exception.
/// \requires \c count must be valid array count and
/// \c node_size must be valid 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 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 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 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 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 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 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 BlockAllocator
/// and it will be used.
/// \throws Anything thrown by the BlockAllocator if a growth is needed.
/// \requires \c node_size must be valid 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 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 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 memory_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 memory_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 memory_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

@@ -1,52 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_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 ordered 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 memory_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 memory_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

@@ -1,238 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_adapter
WPI_ALIAS_TEMPLATE(memory_resource, foonathan_memory_pmr::memory_resource);
/// Wraps a RawAllocator and makes it a \ref memory_resource.
/// \ingroup memory_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 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 RawAllocator.
/// \ingroup memory_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 memory_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

@@ -1,489 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 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 memory_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 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 BlockAllocator or taken from a cache
/// and used for the allocation.
/// \returns A node with given size and alignment.
/// \throws Anything thrown by the 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 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 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 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 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 memory_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 memory_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 memory_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

@@ -1,41 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#ifndef WPI_MEMORY_NAMESPACE_ALIAS_HPP_INCLUDED
#define WPI_MEMORY_NAMESPACE_ALIAS_HPP_INCLUDED
/// \file
/// Convenient namespace alias.
/// \defgroup memory Memory Allocator Library
/// @{
/// \defgroup memory_core Core components
/// \defgroup memory_allocator Allocator implementations
/// \defgroup memory_adapter Adapters and Wrappers
/// \defgroup memory_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

@@ -1,54 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 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 memory_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

@@ -1,447 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 Segregatable that allocates until a maximum size.
/// \ingroup memory_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 RawAllocator that will always fail.
/// This is useful for compositioning or as last resort in \ref binary_segregator.
/// \ingroup memory_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 RawAllocator that either uses the 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 memory_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 Segregatable
/// and the 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 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 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 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 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 memory_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 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 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 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 RawAllocator.
/// \relates segregator
template <class Segregator>
using fallback_allocator_type = typename detail::fallback_type<Segregator>::type;
/// @{
/// \returns The final fallback 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

@@ -1,197 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 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 RawAllocator.
///
/// It is an alias template using \ref allocator_deleter as \c Deleter class.
/// \ingroup memory_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 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 memory_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 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 memory_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 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 memory_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 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 memory_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 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 memory_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 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 memory_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)...);
}
} // namespace memory
} // namespace wpi
#endif // WPI_MEMORY_SMART_PTR_HPP_INCLUDED

View File

@@ -1,178 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_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 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 memory_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 RawAllocator allocation function.
/// It uses the specified \ref static_allocator_storage.
/// \returns A pointer to a 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 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 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 memory_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

@@ -1,361 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 RawAllocator.
/// \ingroup memory_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 RawAllocator and makes it a "normal" \c Allocator.
/// It allows using a \c RawAllocator anywhere a \c Allocator is required.
/// \ingroup memory_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(std::declval<RawAlloc&>()))) 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(
std::declval<const RawAlloc&>()))) 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 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 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 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 memory_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

@@ -1,334 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_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 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 memory_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 memory_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

@@ -1,154 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_core
struct no_mutex
{
void lock() noexcept {}
bool try_lock() noexcept
{
return true;
}
void unlock() noexcept {}
};
/// Specifies whether or not a 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 memory_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

@@ -1,429 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 BlockAllocator adapter that tracks another allocator using a tracker.
/// It wraps another BlockAllocator and calls the tracker function before forwarding to it.
/// The class can then be used anywhere a 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 BlockAllocator is normally used inside higher allocators only.
/// \ingroup memory_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 tracker and the tracked 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 memory_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 RawAllocator adapter that tracks another allocator using a tracker.
/// It wraps another RawAllocator and calls the tracker function before forwarding to it.
/// The class can then be used anywhere a RawAllocator is required and the memory usage will be tracked.<br>
/// If the RawAllocator uses \ref deeply_tracked_block_allocator as BlockAllocator,
/// it will also track growth and shrinking of the allocator.
/// \ingroup memory_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 tracker and the tracked 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 RawAllocator and wraps it with a 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 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 RawAllocator for which \ref is_block_allocator or \ref is_raw_allocator is \c true with a \ref deeply_tracked_block_allocator.
/// \ingroup memory_adapter
template <class Tracker, class RawAllocator>
WPI_ALIAS_TEMPLATE(
deeply_tracked_allocator,
tracked_allocator<Tracker, detail::rebound_allocator<Tracker, RawAllocator>>);
/// \effects Takes a RawAllocator and deeply wraps it with a 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

@@ -1,201 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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 memory_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 memory_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 memory_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 memory_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 memory_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 memory_allocator
void virtual_memory_decommit(void* memory, std::size_t no_pages) noexcept;
/// A stateless RawAllocator that allocates memory using the virtual memory allocation functions.
/// It does not prereserve any memory and will always reserve and commit combined.
/// \ingroup memory_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 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 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 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 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 memory_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

@@ -1,108 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,21 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,33 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,86 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,556 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,21 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,148 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,394 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,105 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,84 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,12 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,23 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,72 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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)) + implementation_offset();
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

@@ -1,27 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,50 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,20 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,71 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,49 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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

@@ -1,320 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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
{
thread_local alignas(temporary_stack) char temporary_stack_storage[sizeof(temporary_stack)];
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

@@ -1,239 +0,0 @@
// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors
// SPDX-License-Identifier: Zlib
#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};
}