diff --git a/wpiutil/CMakeLists.txt b/wpiutil/CMakeLists.txt index 3d6344f7d47..c4326a3bff7 100644 --- a/wpiutil/CMakeLists.txt +++ b/wpiutil/CMakeLists.txt @@ -254,3 +254,253 @@ if(WITH_TESTS) target_compile_options(wpiutil_test PRIVATE /utf-8) endif() endif() + +# Copied from https://github.com/foonathan/memory/blob/016c9fbd61b57ed2058551dcf225c15a0e716cce/cmake/get_container_node_sizes.cmake#L7 +# We need to capture this outside of a function as +# CMAKE_CURRENT_LIST_DIR reflects the current CMakeLists.txt file when +# a function is executed, but reflects this directory while this file +# is being processed. +set(_THIS_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR}) + +if(NOT DEFINED _DEBUG_GET_CONTAINER_NODE_SIZES) + set(_DEBUG_GET_CONTAINER_NODE_SIZES OFF) +endif() + +function(_gcns_debug_message) + if(_DEBUG_GET_CONTAINER_NODE_SIZES) + message("${ARGV}") + endif() +endfunction() + +# This function will return the alignment of the C++ type specified in +# 'type', the result will be in 'result_var'. +function(get_alignof_type type result_var) + # We expect this compilation to fail - the purpose of this is to + # generate a compile error on a generated tyoe + # "align_of" that is the alignment of the specified + # type. + # + # See the contents of get_align_of.cpp for more details. + try_compile( + align_result + ${CMAKE_CURRENT_BINARY_DIR} + ${_THIS_MODULE_DIR}/src/main/native/thirdparty/memory/src/get_align_of.cpp + COMPILE_DEFINITIONS "-DTEST_TYPE=${type}" + OUTPUT_VARIABLE align_output + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED TRUE + ) + + # Look for the align_of<..., ##> in the compiler error output + string(REGEX MATCH "align_of<.*,[ ]*([0-9]+)[ul ]*>" align_of_matched ${align_output}) + + if(align_of_matched) + set(${result_var} ${CMAKE_MATCH_1} PARENT_SCOPE) + else() + message( + FATAL_ERROR + "Unable to determine alignment of C++ type ${type} - no error text matching align_of<..., ##> in compiler output |${align_output}|" + ) + endif() +endfunction() + +# This function will return a list of C++ types with unique alignment +# values, covering all possible alignments supported by the currently +# configured C++ compiler. +# +# The variable named in 'result_types' will contain a list of types, +# and 'result_alignments' will contain a parallel list of the same +# size that is the aligment of each of the matching types. +function(unique_aligned_types result_types result_alignments) + # These two lists will contain a set of types with unique alignments. + set(alignments) + set(types) + + set(all_types + char + bool + short + int + long + LONG_LONG + float + double + LONG_DOUBLE + ) + foreach(type IN LISTS all_types) + get_alignof_type("${type}" alignment) + _gcns_debug_message("Alignment of '${type}' is '${alignment}'") + + if(NOT ${alignment} IN_LIST alignments) + list(APPEND alignments ${alignment}) + list(APPEND types ${type}) + endif() + endforeach() + + set(${result_types} ${types} PARENT_SCOPE) + set(${result_alignments} ${alignments} PARENT_SCOPE) +endfunction() + +# This function will return node sizes for the requested container +# when created with the specified set of types. +# +# 'container' must be one of the container types supported by +# get_node_size.cpp (see that file for details) +# +# 'types' is a list of C++ types to hold in the container to measure +# the node size +# +# 'align_result_var' will contain the list of alignments of contained +# types used. +# +# 'nodesize_result_var' will contain the list of node sizes, one entry +# for each alignment/type +function(get_node_sizes_of container types align_result_var nodesize_result_var) + set(alignments) + set(node_sizes) + + foreach(type IN LISTS types) + # We expect this to fail - the purpose of this is to generate + # a compile error on a generated type + # "node_size_of" that is the + # alignment of the specified type. + try_compile( + nodesize_result + ${CMAKE_CURRENT_BINARY_DIR} + ${_THIS_MODULE_DIR}/src/main/native/thirdparty/memory/src/get_node_size.cpp + COMPILE_DEFINITIONS "-D${container}=1" "-DTEST_TYPE=${type}" + OUTPUT_VARIABLE nodesize_output + CXX_STANDARD 11 + CXX_STANDARD_REQUIRED TRUE + ) + + if(NOT nodesize_output) + message( + FATAL_ERROR + "Unable to determine node size of C++ container ${container} holding type ${type} - no error text matching node_size_of<##, ##, true> in compiler output |${nodesize_output}|" + ) + endif() + + # Find the instance of node_size_of<##, ##, true> in the + # compiler error output - the first number is the alignment, + # and the second is the node size. + string( + REGEX MATCH + "node_size_of<[ ]*([0-9]+)[ul ]*,[ ]*([0-9]+)[ul ]*,[ ]*true[ ]*>" + node_size_of_match + ${nodesize_output} + ) + + if(node_size_of_match) + # Extract the alignment and node size + if(NOT ${CMAKE_MATCH_1} IN_LIST alignments) + list(APPEND alignments ${CMAKE_MATCH_1}) + list(APPEND node_sizes ${CMAKE_MATCH_2}) + endif() + else() + message( + FATAL_ERROR + "Unable to determine node size of C++ container ${container} holding type ${type} - no error text matching node_size_of<##, ##, true> in compiler output |${nodesize_output}|" + ) + endif() + endforeach() + + # Return output to caller + set(${align_result_var} ${alignments} PARENT_SCOPE) + set(${nodesize_result_var} ${node_sizes} PARENT_SCOPE) +endfunction() + +# This will write the container node sizes to an output header file +# that can be used to calculate the node size of a container holding +# the specified type. +function(get_container_node_sizes outfile) + message(STATUS "Getting container node sizes") + + # Build up the file contents in the variable NODE_SIZE_CONTENTS, + # as requested in container_node_sizes_impl.hpp.in + set(NODE_SIZE_CONTENTS "") + + # Get the set of uniquely aligned types to work with + unique_aligned_types(types alignments) + _gcns_debug_message("=> alignments |${alignments}| types |${types}|") + + set(container_types + forward_list + list + set + multiset + unordered_set + unordered_multiset + map + multimap + unordered_map + unordered_multimap + shared_ptr_stateless + shared_ptr_stateful + ) + + foreach(container IN LISTS container_types) + string(TOUPPER "${container}_container" container_macro_name) + get_node_sizes_of("${container_macro_name}" "${types}" alignments node_sizes) + _gcns_debug_message("node size of |${container_macro_name}| holding types |${types}| : alignments |${alignments}| node sizes |${node_sizes}|") + + # Generate the contents for this container type + string( + APPEND + NODE_SIZE_CONTENTS + "\ + +namespace detail +{ + template + struct ${container}_node_size; +" + ) + + list(LENGTH alignments n_alignments) + math(EXPR last_alignment "${n_alignments}-1") + foreach(index RANGE ${last_alignment}) + list(GET alignments ${index} alignment) + list(GET node_sizes ${index} node_size) + + # Generate content for this alignment/node size in this container + string( + APPEND + NODE_SIZE_CONTENTS + "\ + + template <> + struct ${container}_node_size<${alignment}> + : std::integral_constant + {}; +" + ) + endforeach() + + # End contents for this container type + string( + APPEND + NODE_SIZE_CONTENTS + "\ +} // namespace detail + +template +struct ${container}_node_size +: std::integral_constant::value + sizeof(T)> +{}; +" + ) + endforeach() + + # Finally, write the file. As a reminder, configure_file() will + # substitute in any CMake variables wrapped in @VAR@ in the inpout + # file and write them to the output file; and will only rewrite + # the file and update its timestamp if the contents have changed. + # The only variable that will be substituted is NODE_SIZE_CONTENTS + configure_file("${_THIS_MODULE_DIR}/container_node_sizes_impl.hpp.in" ${outfile}) +endfunction() + +if(NOT EXISTS ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp) + get_container_node_sizes(${CMAKE_CURRENT_BINARY_DIR}/src/generated/container_node_sizes_impl.hpp) +endif() diff --git a/wpiutil/container_node_sizes_impl.hpp.in b/wpiutil/container_node_sizes_impl.hpp.in new file mode 100644 index 00000000000..8853ac6ba7e --- /dev/null +++ b/wpiutil/container_node_sizes_impl.hpp.in @@ -0,0 +1,4 @@ +// The following section was autogenerated by get_container_node_sizes.cmake +//=== BEGIN AUTOGENERATED SECTION ===// +@NODE_SIZE_CONTENTS@ +//=== END AUTOGENERATED SECTION ===// diff --git a/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/test_types.hpp b/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/test_types.hpp new file mode 100644 index 00000000000..c2c50c75222 --- /dev/null +++ b/wpiutil/src/main/native/thirdparty/memory/include/wpi/memory/test_types.hpp @@ -0,0 +1,207 @@ +// Copyright (C) 2015-2023 Jonathan Müller and foonathan/memory contributors +// SPDX-License-Identifier: Zlib + +#ifndef FOONATHAN_MEMORY_TOOL_TEST_TYPES_HPP_INCLUDED +#define FOONATHAN_MEMORY_TOOL_TEST_TYPES_HPP_INCLUDED + +#include +#include + +#if !defined(_MSC_VER) + +// erases duplicate alignments +// adopted from https://github.com/irrequietus/clause/blob/alignutil/clause/ample/storage/alignutil.hh +// Copyright (C) 2013 - 2016 George Makrydakis +namespace detail +{ + template + using M0 = typename T::type; + + /*~ + * @note Forward declarations for several utility templates that are to be used + * for emulating high order functions over a pack without using the rest + * of the clause::ample library for two reasons: (1) alignof allows for + * special optimizations when applied over a pack of types range for the + * "sorting by alignof" step; (2) a single header solution was required + * and depending on other parts would mean bring increasing compoments + * of full-fledged metaprogramming library features. This header is to + * provide utilities for aligned storage and it came up when a challenge + * was thrown to me during a discussion with my fellow C++ programmers + * Jonathan Müller and Manu Sánchez. Purpose of inclusion to `clause is + * simple: it can be of use when analyzing boilerplate generation for + * runtime containers and memory allocators by template metaprogramming. + * + * tl;dr: fully standalone header for getting a duplicate-free, sorted by + * alignment list of types unique by alignment. + */ + template + struct M1; // Insert by alignof (map) + template + struct M2; // Remove by alignof (M1 map) + template + struct M3; // A pack wrap instead of M1 + template + struct M4; // 'foldl,fmap' dups to M1<> + template + struct M5; // Remove M1<> + template + struct M6; // Sort by alignof + + /*~ + * @note Both `M1,`M2 are used as a mutable compile-time "map"; `M1 inheritance + * of function signature of the kind: + * + * static auto C(int(*)[alignof(X)]) -> X + * + * is used as a key/value store in the first "fold", while `M2 is used for + * a lookup removing occurences of duplicates in the second "fold" by + * substituting each with `M1<>; this is orchestrated by `M4 while cleanup + * is handled by `M5 (removal of those `M1<> markers). + */ + template + struct M1 : M1 + { + using M1::C; + + static auto C(int (*)[alignof(X)]) -> X; + + static std::size_t constexpr min_val = + alignof(X) < M1::min_val ? alignof(X) : M1::min_val; + + static std::size_t constexpr max_val = + alignof(X) > M1::max_val ? alignof(X) : M1::max_val; + + template