diff --git a/CMakeLists.txt b/CMakeLists.txt index 73de5ae..ed6d029 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,7 @@ endif() include(cmake/configuration.cmake) include(cmake/atomic.cmake) +include(cmake/get_container_node_sizes.cmake) # subdirectories add_subdirectory(src) diff --git a/cmake/container_node_sizes_impl.hpp.in b/cmake/container_node_sizes_impl.hpp.in new file mode 100644 index 0000000..8853ac6 --- /dev/null +++ b/cmake/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/cmake/get_align_of.cpp b/cmake/get_align_of.cpp new file mode 100644 index 0000000..236faac --- /dev/null +++ b/cmake/get_align_of.cpp @@ -0,0 +1,12 @@ +#include + +template +struct align_of +{ + static_assert(alignment == 0, "this should fail, the purpose of this is to generate a compile error on this type that contains the alignment value on this target"); +}; + +template +using get_align_of = align_of; + +get_align_of dummy; diff --git a/cmake/get_container_node_sizes.cmake b/cmake/get_container_node_sizes.cmake new file mode 100644 index 0000000..772ca12 --- /dev/null +++ b/cmake/get_container_node_sizes.cmake @@ -0,0 +1,193 @@ +# 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}) + +set(_DEBUG_GET_CONTAINER_NODE_SIZES OFF) + +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}/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}/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() diff --git a/cmake/get_node_size.cpp b/cmake/get_node_size.cpp new file mode 100644 index 0000000..dc6475c --- /dev/null +++ b/cmake/get_node_size.cpp @@ -0,0 +1,217 @@ +#include +#include + +#if FORWARD_LIST_CONTAINER +#include +#endif +#if LIST_CONTAINER +#include +#endif +#if MAP_CONTAINER || MULTIMAP_CONTAINER +#include +#endif +#if SHARED_PTR_STATELESS_CONTAINER || SHARED_PTR_STATEFUL_CONTAINER +#include +#endif +#if SET_CONTAINER || MULTISET_CONTAINER +#include +#endif +#if UNORDERED_MAP_CONTAINER || UNORDERED_MULTIMAP_CONTAINER +#include +#endif +#if UNORDERED_SET_CONTAINER || UNORDERED_MULTISET_CONTAINER +#include +#endif + +// This will fail to compile when is_node_size is true, which will +// cause the compiler to print this type with the calculated numbers +// in corresponding parameters. +template +struct node_size_of +{ + static_assert(!is_node_size, "Expected to fail"); +}; + +struct empty_state {}; + +template +struct is_same +{ + static constexpr bool value = false; +}; + +template +struct is_same +{ + static constexpr bool value = true; +}; + +// This is a partially implemented allocator type, whose whole purpose +// is to be derived from node_size_of to cause a compiler error when +// this allocator is rebound to the node type. +template +struct debug_allocator : + public node_size_of::value>, + private State +{ + template + struct rebind + { + using other = debug_allocator; + }; + + using value_type = T; + + T* allocate(size_t); + void deallocate(T*, size_t); +}; + +// Dummy hash implementation for containers that need it +struct dummy_hash +{ + // note: not noexcept! this leads to a cached hash value + template + std::size_t operator()(const T&) const + { + // quality doesn't matter + return 0; + } +}; + +// Functions to use the debug_allocator for the specified container +// and containee type. We use the preprocessor to select which one to +// compile to reduce the time this takes. + +#if FORWARD_LIST_CONTAINER +template +int test_container() +{ + std::forward_list> list = {T()}; + return 0; +} +#endif // FORWARD_LIST_CONTAINER + +#if LIST_CONTAINER +template +int test_container() +{ + std::list> list = {T()}; + return 0; +} +#endif // LIST_CONTAINER + +#if SET_CONTAINER +template +int test_container() +{ + std::set, debug_allocator> set = {T()}; + return 0; +} +#endif // SET_CONTAINER + +#if MULTISET_CONTAINER +template +int test_container() +{ + std::multiset, debug_allocator> set = {T()}; + return 0; +} +#endif // MULTISET_CONTAINER + +#if UNORDERED_SET_CONTAINER +template +int test_container() +{ + std::unordered_set, debug_allocator> set = {T()}; + return 0; +} +#endif // UNORDERED_SET_CONTAINER + +#if UNORDERED_MULTISET_CONTAINER +template +int test_container() +{ + std::unordered_multiset, debug_allocator> set = {T()}; + return 0; +} +#endif // UNORDERED_MULTISET_CONTAINER + +#if MAP_CONTAINER +template +int test_container() +{ + using type = std::pair; + std::map, debug_allocator> map = {{T(),T()}}; + return 0; +} +#endif // MAP_CONTAINER + +#if MULTIMAP_CONTAINER +template +int test_container() +{ + using type = std::pair; + std::multimap, debug_allocator> map = {{T(),T()}}; + return 0; +} +#endif // MULTIMAP_CONTAINER + +#if UNORDERED_MAP_CONTAINER +template +int test_container() +{ + using type = std::pair; + std::unordered_map, debug_allocator> map = {{T(),T()}}; + return 0; +} +#endif // UNORDERED_MAP_CONTAINER + +#if UNORDERED_MULTIMAP_CONTAINER +template +int test_container() +{ + using type = std::pair; + std::unordered_multimap, debug_allocator> map = {{T(),T()}}; + return 0; +} +#endif // UNORDERED_MULTIMAP_CONTAINER + +#if SHARED_PTR_STATELESS_CONTAINER +template +int test_container() +{ + auto ptr = std::allocate_shared(debug_allocator()); + return 0; +} +#endif // SHARED_PTR_STATELESS_CONTAINER + +#if SHARED_PTR_STATEFUL_CONTAINER +template +int test_container() +{ + struct allocator_reference_payload + { + void* ptr; + }; + + auto ptr = std::allocate_shared(debug_allocator()); + return 0; +} +#endif // SHARED_PTR_STATEFUL_CONTAINER + +#ifdef TEST_TYPES +template +int test_all(std::tuple) +{ + int dummy[] = {(test_container())...}; + return 0; +} + +int foo = test_all(std::tuple()); +#endif + +#ifdef TEST_TYPE +int foo = test_container(); +#endif diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5a512c9..2735c1e 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -77,44 +77,7 @@ set(src configure_file("config.hpp.in" "${CMAKE_CURRENT_BINARY_DIR}/config_impl.hpp") # generate container_node_sizes.hpp -# don't run it while cross-compiling and CMAKE_CROSSCOMPILING_EMULATOR is not defined -if(FOONATHAN_MEMORY_BUILD_TOOLS) - if(NOT CMAKE_CROSSCOMPILING) - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp - COMMAND $ --code ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp - DEPENDS foonathan_memory_node_size_debugger - VERBATIM) - elseif(DEFINED CMAKE_CROSSCOMPILING_EMULATOR) - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp - COMMAND ${CMAKE_CROSSCOMPILING_EMULATOR} $ --code ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp - DEPENDS foonathan_memory_node_size_debugger - VERBATIM) - elseif(QNX OR QNXNTO) - if(EXISTS "${FOONATHAN_MEMORY_CONTAINER_NODE_SIZES_IMPL}") - message("-- Using the pre-generated file: ${FOONATHAN_MEMORY_CONTAINER_NODE_SIZES_IMPL}") - add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp - COMMAND cp ${FOONATHAN_MEMORY_CONTAINER_NODE_SIZES_IMPL} ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp ) - else() - message(FATAL_ERROR "\nError: Cannot find pre-generated file container_node_sizes_impl.hpp\n" - "Please pre-generate the header file container_node_sizes_impl.hpp by following the steps below:\n" - "- Build nodesize_dbg from source:\n" - " ${PROJECT_SOURCE_DIR}/tool/node_size_debugger.cpp \n" - "- Transfer nodesize_dbg to QNX target and execute:\n" - " nodesize_dbg --code container_node_sizes_impl.hpp \n" - "- Transfer generated header file back to your development system \n" - "- Set FOONATHAN_MEMORY_CONTAINER_NODE_SIZES_IMPL to the path of the pre-generated file and pass it to cmake as an argument\n") - endif() - else() - message(WARNING "cross-compiling, but emulator is not defined, " - "cannot generate container_node_sizes_impl.hpp, node size information will be unavailable") - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp "#define FOONATHAN_MEMORY_NO_NODE_SIZE") - set(FOONATHAN_MEMORY_NO_NODE_SIZE 1 PARENT_SCOPE) - endif() -else() - message(WARNING "cannot generate container_node_sizes_impl.hpp, node size information will be unavailable") - file(WRITE ${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp "#define FOONATHAN_MEMORY_NO_NODE_SIZE") - set(FOONATHAN_MEMORY_NO_NODE_SIZE 1 PARENT_SCOPE) -endif() +get_container_node_sizes(${CMAKE_CURRENT_BINARY_DIR}/container_node_sizes_impl.hpp) add_library(foonathan_memory ${detail_header} ${header} ${src}) target_include_directories(foonathan_memory PUBLIC $ # for client in subdirectory