diff --git a/.github/workflows/upstream-utils.yml b/.github/workflows/upstream-utils.yml index 820576e768..ef3d7d4341 100644 --- a/.github/workflows/upstream-utils.yml +++ b/.github/workflows/upstream-utils.yml @@ -53,6 +53,10 @@ jobs: run: | cd upstream_utils ./update_stack_walker.py + - name: Run update_memory.py + run: | + cd upstream_utils + ./update_memory.py - name: Add untracked files to index so they count as changes run: git add -A - name: Check output diff --git a/docs/build.gradle b/docs/build.gradle index 7a6ca584c1..122b3f6df2 100644 --- a/docs/build.gradle +++ b/docs/build.gradle @@ -115,6 +115,9 @@ doxygen { // json exclude 'wpi/json.h' + // memory + exclude 'wpi/memory/**' + // mpack exclude 'wpi/mpack.h' diff --git a/upstream_utils/memory_files/config_impl.hpp b/upstream_utils/memory_files/config_impl.hpp new file mode 100644 index 0000000000..1f72375524 --- /dev/null +++ b/upstream_utils/memory_files/config_impl.hpp @@ -0,0 +1,34 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +// Copyright (C) 2015-2020 Jonathan Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#pragma once + +#include + +//=== 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 diff --git a/upstream_utils/update_memory.py b/upstream_utils/update_memory.py new file mode 100755 index 0000000000..e72717a792 --- /dev/null +++ b/upstream_utils/update_memory.py @@ -0,0 +1,104 @@ +#!/usr/bin/env python3 + +import os +import shutil + +from upstream_utils import ( + get_repo_root, + clone_repo, + comment_out_invalid_includes, + walk_if, + copy_to, +) + + +def run_source_replacements(memory_files): + for wpi_file in memory_files: + with open(wpi_file) as f: + content = f.read() + + # Fix #includes + content = content.replace('include "', 'include "wpi/memory/') + content = content.replace( + "wpi/memory/free_list_utils.hpp", "free_list_utils.hpp" + ) + + with open(wpi_file, "w") as f: + f.write(content) + + +def run_header_replacements(memory_files): + for wpi_file in memory_files: + if "detail" not in wpi_file: + continue + with open(wpi_file) as f: + content = f.read() + + # Fix #includes + content = content.replace('include "config.hpp', 'include "../config.hpp') + + with open(wpi_file, "w") as f: + f.write(content) + + +def run_global_replacements(memory_files): + for wpi_file in memory_files: + with open(wpi_file) as f: + content = f.read() + + # Rename namespace from foonathan to wpi + content = content.replace("namespace foonathan", "namespace wpi") + content = content.replace("foonathan::", "wpi::") + content = content.replace("FOONATHAN_", "WPI_") + + # Fix #includes + content = content.replace('include "foonathan', 'include "wpi') + + with open(wpi_file, "w") as f: + f.write(content) + + +def main(): + upstream_root = clone_repo("https://github.com/foonathan/memory", "v0.7-2") + wpilib_root = get_repo_root() + wpiutil = os.path.join(wpilib_root, "wpiutil") + + # Delete old install + for d in [ + "src/main/native/thirdparty/memory/src", + "src/main/native/thirdparty/memory/include", + ]: + shutil.rmtree(os.path.join(wpiutil, d), ignore_errors=True) + + # Copy sources + os.chdir(upstream_root) + src_files = walk_if("src", lambda dp, f: f.endswith(".cpp") or f.endswith(".hpp")) + src_files = copy_to( + src_files, os.path.join(wpiutil, "src/main/native/thirdparty/memory") + ) + run_global_replacements(src_files) + run_source_replacements(src_files) + + # Copy headers + os.chdir(os.path.join(upstream_root, "include", "foonathan")) + include_files = walk_if(".", lambda dp, f: f.endswith(".hpp")) + include_files = copy_to( + include_files, + os.path.join(wpiutil, "src/main/native/thirdparty/memory/include/wpi"), + ) + os.chdir(os.path.join("..", "..")) + run_global_replacements(include_files) + run_header_replacements(include_files) + + # Copy config_impl.hpp + shutil.copyfile( + os.path.join(wpilib_root, "upstream_utils/memory_files/config_impl.hpp"), + os.path.join( + wpiutil, + "src/main/native/thirdparty/memory/include/wpi/memory/config_impl.hpp", + ), + ) + + +if __name__ == "__main__": + main() diff --git a/wpiutil/.styleguide b/wpiutil/.styleguide index 6e8b294ebb..0b08cf4318 100644 --- a/wpiutil/.styleguide +++ b/wpiutil/.styleguide @@ -1,5 +1,6 @@ cppHeaderFileInclude { \.h$ + \.hpp$ \.inc$ \.inl$ math$ diff --git a/wpiutil/CMakeLists.txt b/wpiutil/CMakeLists.txt index e89c18129e..fd751da479 100644 --- a/wpiutil/CMakeLists.txt +++ b/wpiutil/CMakeLists.txt @@ -98,8 +98,9 @@ file(GLOB_RECURSE wpiutil_macos_src src/main/native/macOS/*.cpp) file(GLOB_RECURSE wpiutil_windows_src src/main/native/windows/*.cpp) file(GLOB fmtlib_native_src src/main/native/thirdparty/fmtlib/src/*.cpp) +file(GLOB_RECURSE memory_native_src src/main/native/thirdparty/memory/src/*.cpp) -add_library(wpiutil ${wpiutil_native_src} ${fmtlib_native_src} ${wpiutil_resources_src}) +add_library(wpiutil ${wpiutil_native_src} ${fmtlib_native_src} ${memory_native_src} ${wpiutil_resources_src}) set_target_properties(wpiutil PROPERTIES DEBUG_POSTFIX "d") set_property(TARGET wpiutil PROPERTY FOLDER "libraries") @@ -141,6 +142,7 @@ endif() target_include_directories(wpiutil PUBLIC $ + $ $) target_include_directories(wpiutil PUBLIC $ diff --git a/wpiutil/build.gradle b/wpiutil/build.gradle index efd9183cac..a8c1665aee 100644 --- a/wpiutil/build.gradle +++ b/wpiutil/build.gradle @@ -70,6 +70,16 @@ ext { srcDirs 'src/main/native/thirdparty/tcb_span/include' } } + memoryCpp(CppSourceSet) { + source { + srcDirs 'src/main/native/thirdparty/memory/src', 'src/main/native/thirdparty/memory/include/wpi/memory' + include '**/*.cpp' + } + exportedHeaders { + srcDirs 'src/main/native/thirdparty/memory/include' + include '**/*.hpp' + } + } resourcesCpp(CppSourceSet) { source { srcDirs "$buildDir/generated/main/cpp", "$rootDir/shared/singlelib" @@ -190,6 +200,9 @@ cppHeadersZip { from('src/main/native/thirdparty/tcb_span/include') { into '/' } + from('src/main/native/thirdparty/memory/include') { + into '/' + } } cppSourcesZip { @@ -199,6 +212,9 @@ cppSourcesZip { from('src/main/native/thirdparty/json/cpp') { into '/' } + from('src/main/native/thirdparty/memory/src') { + into '/' + } } model { @@ -206,7 +222,7 @@ model { all { it.sources.each { it.exportedHeaders { - srcDirs 'src/main/native/include', 'src/main/native/thirdparty/fmtlib/include', 'src/main/native/thirdparty/llvm/include', 'src/main/native/thirdparty/sigslot/include', 'src/main/native/thirdparty/json/include', 'src/main/native/thirdparty/tcb_span/include', 'src/main/native/thirdparty/ghc/include' + srcDirs 'src/main/native/include', 'src/main/native/thirdparty/fmtlib/include', 'src/main/native/thirdparty/llvm/include', 'src/main/native/thirdparty/sigslot/include', 'src/main/native/thirdparty/json/include', 'src/main/native/thirdparty/tcb_span/include', 'src/main/native/thirdparty/ghc/include', 'src/main/native/thirdparty/memory/include' } } } diff --git a/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/aligned_allocator.hpp b/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/aligned_allocator.hpp new file mode 100644 index 0000000000..8118ec4827 --- /dev/null +++ b/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/aligned_allocator.hpp @@ -0,0 +1,197 @@ +// Copyright (C) 2015-2021 Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#ifndef WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED +#define WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED + +/// \file +/// Class \ref wpi::memory::aligned_allocator and related functions. + +#include + +#include "detail/assert.hpp" +#include "detail/utility.hpp" +#include "allocator_traits.hpp" +#include "config.hpp" + +namespace wpi +{ + namespace memory + { + /// A \concept{concept_rawallocator,RawAllocator} adapter that ensures a minimum alignment. + /// It adjusts the alignment value so that it is always larger than the minimum and forwards to the specified allocator. + /// \ingroup adapter + template + class aligned_allocator : WPI_EBO(allocator_traits::allocator_type) + { + using traits = allocator_traits; + using composable_traits = composable_allocator_traits; + using composable = is_composable_allocator; + + public: + using allocator_type = typename allocator_traits::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 + auto make_aligned_allocator(std::size_t min_alignment, RawAllocator&& allocator) noexcept + -> aligned_allocator::type> + { + return aligned_allocator< + typename std::decay::type>{min_alignment, + detail::forward(allocator)}; + } + } // namespace memory +} // namespace wpi + +#endif // WPI_MEMORY_ALIGNED_ALLOCATOR_HPP_INCLUDED diff --git a/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/allocator_storage.hpp b/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/allocator_storage.hpp new file mode 100644 index 0000000000..8dab2e0b86 --- /dev/null +++ b/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/allocator_storage.hpp @@ -0,0 +1,932 @@ +// Copyright (C) 2015-2021 Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#ifndef WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED +#define WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED + +/// \file +/// Class template \ref wpi::memory::allocator_storage, some policies and resulting typedefs. + +#include +#include + +#include "detail/utility.hpp" +#include "config.hpp" +#include "allocator_traits.hpp" +#include "threading.hpp" + +namespace wpi +{ + namespace memory + { + namespace detail + { + template + void* try_allocate_node(std::true_type, Alloc& alloc, std::size_t size, + std::size_t alignment) noexcept + { + return composable_allocator_traits::try_allocate_node(alloc, size, + alignment); + } + + template + 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::try_allocate_array(alloc, count, size, + alignment); + } + + template + bool try_deallocate_node(std::true_type, Alloc& alloc, void* ptr, std::size_t size, + std::size_t alignment) noexcept + { + return composable_allocator_traits::try_deallocate_node(alloc, ptr, size, + alignment); + } + + template + 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::try_deallocate_array(alloc, ptr, count, + size, alignment); + } + + template + 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 + 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 + 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 + bool try_deallocate_array(std::false_type, Alloc&, void*, std::size_t, std::size_t, + std::size_t) noexcept + { + WPI_MEMORY_UNREACHABLE("Allocator is not compositioning"); + return false; + } + } // namespace detail + + /// A \concept{concept_rawallocator,RawAllocator} that stores another allocator. + /// The \concept{concept_storagepolicy,StoragePolicy} defines the allocator type being stored and how it is stored. + /// The \c Mutex controls synchronization of the access. + /// \ingroup storage + template + class allocator_storage + : WPI_EBO(StoragePolicy, + detail::mutex_storage< + detail::mutex_for>) + { + using traits = allocator_traits; + using composable_traits = + composable_allocator_traits; + using composable = is_composable_allocator; + using actual_mutex = const detail::mutex_storage< + detail::mutex_for>; + + 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 new storage_policy(std::forward(alloc)) 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::type>::value))> + allocator_storage(Alloc&& alloc, + WPI_SFINAE(new storage_policy(detail::forward(alloc)))) + : storage_policy(detail::forward(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 new storage_policy(other.get_allocator()) must be well-formed, + /// otherwise this constructor does not participate in overload resolution. + template + allocator_storage(const allocator_storage& other, + WPI_SFINAE(new storage_policy(other.get_allocator()))) + : storage_policy(other.get_allocator()) + { + } + + /// @{ + /// \effects Moves the \c allocator_storage object. + /// A moved-out \c allocator_storage object must still store a valid allocator object. + allocator_storage(allocator_storage&& other) noexcept + : storage_policy(detail::move(other)), + detail::mutex_storage< + detail::mutex_for>( + detail::move(other)) + { + } + + allocator_storage& operator=(allocator_storage&& other) noexcept + { + storage_policy:: operator=(detail::move(other)); + detail::mutex_storage>::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 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 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 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 lock(*this); + auto&& alloc = get_allocator(); + traits::deallocate_array(alloc, ptr, count, size, alignment); + } + + std::size_t max_node_size() const + { + std::lock_guard lock(*this); + auto&& alloc = get_allocator(); + return traits::max_node_size(alloc); + } + + std::size_t max_array_size() const + { + std::lock_guard lock(*this); + auto&& alloc = get_allocator(); + return traits::max_array_size(alloc); + } + + std::size_t max_alignment() const + { + std::lock_guard 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 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 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 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 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().get_allocator()) + { + return storage_policy::get_allocator(); + } + + auto get_allocator() const noexcept + -> decltype(std::declval().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().get_allocator(), std::declval()))) + { + return detail::lock_allocator(get_allocator(), static_cast(*this)); + } + + auto lock() const noexcept -> WPI_IMPL_DEFINED(decltype( + detail::lock_allocator(std::declval().get_allocator(), + std::declval()))) + { + return detail::lock_allocator(get_allocator(), static_cast(*this)); + } + /// @}. + + /// \returns Whether or not the stored allocator is composable, + /// that is you can use the compositioning functions. + /// \note Due to type-erased allocators, + /// this function can not be `constexpr`. + bool is_composable() const noexcept + { + return StoragePolicy::is_composable(); + } + }; + + /// Tag type that enables type-erasure in \ref reference_storage. + /// It can be used everywhere a \ref allocator_reference is used internally. + /// \ingroup storage + struct any_allocator + { + }; + + /// A \concept{concept_storagepolicy,StoragePolicy} that stores the allocator directly. + /// It embeds the allocator inside it, i.e. moving the storage policy will move the allocator. + /// \ingroup storage + template + class direct_storage : WPI_EBO(allocator_traits::allocator_type) + { + static_assert(!std::is_same::value, + "cannot type-erase in direct_storage"); + + public: + using allocator_type = typename allocator_traits::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::value; + } + }; + + /// An alias template for \ref allocator_storage using the \ref direct_storage policy without a mutex. + /// It has the effect of giving any \concept{concept_rawallocator,RawAllocator} the interface with all member functions, + /// avoiding the need to wrap it inside the \ref allocator_traits. + /// \ingroup storage + template + WPI_ALIAS_TEMPLATE(allocator_adapter, + allocator_storage, no_mutex>); + + /// \returns A new \ref allocator_adapter object created by forwarding to the constructor. + /// \relates allocator_adapter + template + auto make_allocator_adapter(RawAllocator&& allocator) noexcept + -> allocator_adapter::type> + { + return {detail::forward(allocator)}; + } + +/// An alias template for \ref allocator_storage using the \ref direct_storage policy with a mutex. +/// It has a similar effect as \ref allocator_adapter but performs synchronization. +/// The \c Mutex will default to \c std::mutex if threading is supported, +/// otherwise there is no default. +/// \ingroup storage +#if WPI_HOSTED_IMPLEMENTATION + template + WPI_ALIAS_TEMPLATE(thread_safe_allocator, + allocator_storage, Mutex>); +#else + template + WPI_ALIAS_TEMPLATE(thread_safe_allocator, + allocator_storage, 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 + auto make_thread_safe_allocator(RawAllocator&& allocator) + -> thread_safe_allocator::type> + { + return detail::forward(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 + auto make_thread_safe_allocator(RawAllocator&& allocator) + -> thread_safe_allocator::type, Mutex> + { + return detail::forward(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 reference_storage_impl; + + // reference to stateful: stores a pointer to an allocator + template + class reference_storage_impl + { + 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 reference_storage_impl + { + 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 reference_storage_impl + { + protected: + reference_storage_impl() noexcept = default; + + reference_storage_impl(const RawAllocator& alloc) noexcept : alloc_(alloc) {} + + bool is_valid() const noexcept + { + return true; + } + + RawAllocator& get_allocator() const noexcept + { + return alloc_; + } + + private: + mutable RawAllocator alloc_; + }; + } // namespace detail + + /// Specifies whether or not a \concept{concept_rawallocator,RawAllocator} has shared semantics. + /// It is shared, if - like \ref allocator_reference - if multiple objects refer to the same internal allocator and if it can be copied. + /// This sharing is stateful, however, stateless allocators are not considered shared in the meaning of this traits.
+ /// 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,
+ /// Specialize it for your own types, if they provide sharing semantics and can be copied. + /// They also must provide an `operator==` to check whether two allocators refer to the same shared one. + /// \note This makes no guarantees about the lifetime of the shared object, the sharing allocators can either own or refer to a shared object. + /// \ingroup storage + template + struct is_shared_allocator : std::false_type + { + }; + + /// A \concept{concept_storagepolicy,StoragePolicy} that stores a reference to an allocator. + /// For stateful allocators it only stores a pointer to an allocator object and copying/moving only copies the pointer. + /// For stateless allocators it does not store anything, an allocator will be constructed as needed. + /// For allocators that are already shared (determined through \ref is_shared_allocator) it will store the allocator type directly. + /// \note It does not take ownership over the allocator in the stateful case, the user has to ensure that the allocator object stays valid. + /// In the other cases the lifetime does not matter. + /// \ingroup storage + template + class reference_storage +#ifndef DOXYGEN + : WPI_EBO(detail::reference_storage_impl< + typename allocator_traits::allocator_type, + decltype(detail::reference_type( + typename allocator_traits::is_stateful{}, + is_shared_allocator{}))>) +#endif + { + using storage = detail::reference_storage_impl< + typename allocator_traits::allocator_type, + decltype( + detail::reference_type(typename allocator_traits::is_stateful{}, + is_shared_allocator{}))>; + + public: + using allocator_type = typename allocator_traits::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::value; + } + }; + + /// Specialization of the class template \ref reference_storage that is type-erased. + /// It is triggered by the tag type \ref any_allocator. + /// The specialization can store a reference to any allocator type. + /// \ingroup storage + template <> + class reference_storage + { + class base_allocator + { + public: + using is_stateful = std::true_type; + + virtual ~base_allocator() = default; + + virtual void clone(void* storage) const noexcept = 0; + + void* allocate_node(std::size_t size, std::size_t alignment) + { + return allocate_impl(1, size, alignment); + } + + void* allocate_array(std::size_t count, std::size_t size, std::size_t alignment) + { + return allocate_impl(count, size, alignment); + } + + void deallocate_node(void* node, std::size_t size, std::size_t alignment) noexcept + { + deallocate_impl(node, 1, size, alignment); + } + + void deallocate_array(void* array, std::size_t count, std::size_t size, + std::size_t alignment) noexcept + { + deallocate_impl(array, count, size, alignment); + } + + void* try_allocate_node(std::size_t size, std::size_t alignment) noexcept + { + return try_allocate_impl(1, size, alignment); + } + + void* try_allocate_array(std::size_t count, std::size_t size, + std::size_t alignment) noexcept + { + return try_allocate_impl(count, size, alignment); + } + + bool try_deallocate_node(void* node, std::size_t size, + std::size_t alignment) noexcept + { + return try_deallocate_impl(node, 1, size, alignment); + } + + bool try_deallocate_array(void* array, std::size_t count, std::size_t size, + std::size_t alignment) noexcept + { + return try_deallocate_impl(array, count, size, alignment); + } + + // count 1 means node + virtual void* allocate_impl(std::size_t count, std::size_t size, + std::size_t alignment) = 0; + virtual void deallocate_impl(void* ptr, std::size_t count, std::size_t size, + std::size_t alignment) noexcept = 0; + + virtual void* try_allocate_impl(std::size_t count, std::size_t size, + std::size_t alignment) noexcept = 0; + + virtual bool try_deallocate_impl(void* ptr, std::size_t count, std::size_t size, + std::size_t alignment) noexcept = 0; + + std::size_t max_node_size() const + { + return max(query::node_size); + } + + std::size_t max_array_size() const + { + return max(query::array_size); + } + + std::size_t max_alignment() const + { + return max(query::alignment); + } + + virtual bool is_composable() const noexcept = 0; + + protected: + enum class query + { + node_size, + array_size, + alignment + }; + + virtual std::size_t max(query q) const = 0; + }; + + public: + using allocator_type = WPI_IMPL_DEFINED(base_allocator); + + /// \effects Creates it from a reference to any stateful \concept{concept_rawallocator,RawAllocator}. + /// It will store a pointer to this allocator object. + /// \note The user has to take care that the lifetime of the reference does not exceed the allocator lifetime. + template + reference_storage(RawAllocator& alloc) noexcept + { + static_assert(sizeof(basic_allocator) + <= sizeof(basic_allocator), + "requires all instantiations to have certain maximum size"); + ::new (static_cast(&storage_)) basic_allocator(alloc); + } + + // \effects Creates it from any stateless \concept{concept_rawallocator,RawAllocator}. + /// It will not store anything, only creates the allocator as needed. + /// \requires The \c RawAllocator is stateless. + template + reference_storage( + const RawAllocator& alloc, + WPI_REQUIRES(!allocator_traits::is_stateful::value)) noexcept + { + static_assert(sizeof(basic_allocator) + <= sizeof(basic_allocator), + "requires all instantiations to have certain maximum size"); + ::new (static_cast(&storage_)) basic_allocator(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(alloc)) + { + } + + /// @{ + /// \effects Copies the \c reference_storage object. + /// It only copies the pointer to the allocator. + reference_storage(const reference_storage& other) noexcept + { + other.get_allocator().clone(&storage_); + } + + reference_storage& operator=(const reference_storage& other) noexcept + { + get_allocator().~allocator_type(); + other.get_allocator().clone(&storage_); + return *this; + } + /// @} + + /// \returns A reference to the allocator. + /// The actual type is implementation-defined since it is the base class used in the type-erasure, + /// but it provides the full \concept{concept_rawallocator,RawAllocator} member functions. + /// \note There is no way to access any custom member functions of the allocator type. + allocator_type& get_allocator() const noexcept + { + auto mem = static_cast(&storage_); + return *static_cast(mem); + } + + protected: + ~reference_storage() noexcept + { + get_allocator().~allocator_type(); + } + + bool is_composable() const noexcept + { + return get_allocator().is_composable(); + } + + private: + template + class basic_allocator + : public base_allocator, + private detail::reference_storage_impl< + typename allocator_traits::allocator_type, + decltype( + detail::reference_type(typename allocator_traits::is_stateful{}, + is_shared_allocator{}))> + { + using traits = allocator_traits; + using composable = is_composable_allocator; + using storage = detail::reference_storage_impl< + typename allocator_traits::allocator_type, + decltype(detail::reference_type(typename allocator_traits< + RawAllocator>::is_stateful{}, + is_shared_allocator{}))>; + + 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; + alignas(default_instantiation) mutable char storage_[sizeof(default_instantiation)]; + }; + + /// An alias template for \ref allocator_storage using the \ref reference_storage policy. + /// It will store a reference to the given allocator type. The tag type \ref any_allocator enables type-erasure. + /// Wrap the allocator in a \ref thread_safe_allocator if you want thread safety. + /// \ingroup storage + template + WPI_ALIAS_TEMPLATE(allocator_reference, + allocator_storage, no_mutex>); + + /// \returns A new \ref allocator_reference object by forwarding the allocator to the constructor. + /// \relates allocator_reference + template + auto make_allocator_reference(RawAllocator&& allocator) noexcept + -> allocator_reference::type> + { + return {detail::forward(allocator)}; + } + + /// An alias for the \ref reference_storage specialization using type-erasure. + /// \ingroup storage + using any_reference_storage = reference_storage; + + /// An alias for \ref allocator_storage using the \ref any_reference_storage. + /// It will store a reference to any \concept{concept_rawallocator,RawAllocator}. + /// This is the same as passing the tag type \ref any_allocator to the alias \ref allocator_reference. + /// Wrap the allocator in a \ref thread_safe_allocator if you want thread safety. + /// \ingroup storage + using any_allocator_reference = allocator_storage; + + /// \returns A new \ref any_allocator_reference object by forwarding the allocator to the constructor. + /// \relates any_allocator_reference + template + auto make_any_allocator_reference(RawAllocator&& allocator) noexcept + -> any_allocator_reference + { + return {detail::forward(allocator)}; + } + } // namespace memory +} // namespace wpi + +#endif // WPI_MEMORY_ALLOCATOR_STORAGE_HPP_INCLUDED diff --git a/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/allocator_traits.hpp b/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/allocator_traits.hpp new file mode 100644 index 0000000000..47b6ffb11a --- /dev/null +++ b/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/allocator_traits.hpp @@ -0,0 +1,603 @@ +// Copyright (C) 2015-2021 Müller +// This file is subject to the license terms in the LICENSE file +// found in the top-level directory of this distribution. + +#ifndef WPI_MEMORY_ALLOCATOR_TRAITS_HPP_INCLUDED +#define WPI_MEMORY_ALLOCATOR_TRAITS_HPP_INCLUDED + +/// \file +/// The default specialization of the \ref wpi::memory::allocator_traits. + +#include +#include + +#include "detail/align.hpp" +#include "detail/utility.hpp" +#include "config.hpp" + +#if WPI_HOSTED_IMPLEMENTATION +#include +#endif + +namespace wpi +{ + namespace memory + { + namespace detail + { + template + std::true_type has_construct(int, WPI_SFINAE(std::declval().construct( + std::declval(), + std::declval()))); + + template + std::false_type has_construct(short); + + template + std::true_type has_destroy(int, WPI_SFINAE(std::declval().destroy( + std::declval()))); + + template + std::false_type has_destroy(short); + + template + struct check_standard_allocator + { + using custom_construct = decltype(has_construct(0)); + using custom_destroy = decltype(has_destroy(0)); + + using valid = std::integral_constant; + }; + } // namespace detail + + /// Traits class that checks whether or not a standard \c Allocator can be used as \concept{concept_rawallocator,RawAllocator}. + /// It checks the existence of a custom \c construct(), \c destroy() function, if provided, + /// it cannot be used since it would not be called.
+ /// Specialize it for custom \c Allocator types to override this check. + /// \ingroup core + template + struct allocator_is_raw_allocator + : WPI_EBO(detail::check_standard_allocator::valid) + { + }; + + /// Specialization of \ref allocator_is_raw_allocator that allows \c std::allocator again. + /// \ingroup core + template + struct allocator_is_raw_allocator> : 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 + 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 + auto rebind_impl(int) -> typename Allocator::template rebind::other&; + + template + struct allocator_rebinder + { + using type = Allocator&; + }; + + template