diff --git a/.conanfile.txt b/.conanfile.txt new file mode 100644 index 0000000..18f3c8e --- /dev/null +++ b/.conanfile.txt @@ -0,0 +1,2 @@ +[requires] +gtest/1.8.1@bincrafters/stable \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 6d504a0..f693399 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,28 +2,83 @@ cmake_minimum_required(VERSION 3.10) # For CXX_STANDARD 17 property on Visual St project(WaitFreeHashMap) enable_language(CXX) -include(cmake/common.cmake) -include(cmake/get_external_dependency.cmake) +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) + +include(ctulu) +include(conan) +include(setup_output_dirs) set(CMAKE_DEBUG_POSTFIX _d) # By default build in Release mode -if( NOT CMAKE_BUILD_TYPE ) +if (NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Release") endif() -get_filename_component(root_dir ${CMAKE_SOURCE_DIR} ABSOLUTE) +get_filename_component(root_dir ${CMAKE_CURRENT_SOURCE_DIR} ABSOLUTE) get_filename_component(include_dir ${root_dir}/include ABSOLUTE) -make_target(WaitFreeHashMapLibrary "WaitFreeHashMap" INCLUDES ${include_dir} OPTIONS cxx interface) +get_filename_component(tests_dir ${root_dir}/tests ABSOLUTE) +get_filename_component(example_dir ${root_dir}/examples ABSOLUTE) + +# set parameter INTERFACE since the library is header only. +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + message(STATUS "Activating warning on library") + ctulu_create_target(WaitFreeCollections "WaitFreeCollections" INTERFACE_INCLUDES ${include_dir} CXX 17 INTERFACE) +else() + ctulu_create_target(WaitFreeCollections "WaitFreeCollections" INTERFACE_INCLUDES SYSTEM ${include_dir} CXX 17 INTERFACE) +endif() + +option(WFC_BUILD_EXAMPLES "Build project examples" OFF) +option(WFC_BUILD_TESTS "Build project tests" OFF) +option(WFC_BUILD_CLANG_FORMAT_TARGET "Build clang-format target" OFF) +option(WFC_BUILD_ALL "Activate all previous options" OFF) + +if (WFC_BUILD_ALL) + set(WFC_BUILD_EXAMPLES ON) + set(WFC_BUILD_TESTS ON) + set(WFC_BUILD_CLANG_FORMAT_TARGET ON) +endif() + +if (WFC_BUILD_EXAMPLES) + find_package(Threads REQUIRED) + + ctulu_create_target(UnorderedMapExample1 "WaitFreeCollections" FILES ${example_dir}/unordered_map_example.cpp CXX 17 EXECUTABLE W_LEVEL 2) + ctulu_target_warning_from_file(UnorderedMapExample1 ${root_dir}/cmake/warnings.txt) + target_link_libraries(UnorderedMapExample1 Threads::Threads WaitFreeCollections) +endif() -find_package(Threads REQUIRED) -get_external_dependency(googletest external_libs/google_test.cmake) +if (WFC_BUILD_CLANG_FORMAT_TARGET) + set(DIRS "${include_dir}") + if (BUILD_TESTS) + list(APPEND DIRS ${test_dir}) + endif() + if (BUILD_EXAMPLES) + list(APPEND DIRS ${example_dir}) + endif() -make_target(Sample1 "WaitFreeHashMap" example/main.cpp OPTIONS cxx executable) -set_property(TARGET Sample1 PROPERTY CXX_STANDARD 17) -target_link_libraries(Sample1 ${CMAKE_THREAD_LIBS_INIT} WaitFreeHashMapLibrary gtest_main) + ctulu_generate_clang_format("Clang-format" DIRS ${DIRS}) +endif() + +if (CMAKE_BUILD_TYPE MATCHES "Debug") + set(WFC_BUILD_TESTS ON) +endif() + +if (WFC_BUILD_TESTS) + message(STATUS "Enabling testing") + enable_testing() + + find_package(Threads REQUIRED) + + conan_check(REQUIRED) + conan_cmake_run(CONANFILE .conanfile.txt BASIC_SETUP CMAKE_TARGETS BUILD missing) + + ctulu_create_target(UnorderedMapTests "WaitFreeCollections" DIRS ${tests_dir}/unordered_map/ TEST CXX 17 W_LEVEL 2) + ctulu_target_warning_from_file(UnorderedMapTests ${root_dir}/cmake/warnings.txt) + target_link_libraries(UnorderedMapTests WaitFreeCollections CONAN_PKG::gtest Threads::Threads) + + ctulu_create_target(UtilityTests "WaitFreeCollections" DIRS ${tests_dir}/utility/ TEST CXX 17 W_LEVEL 2) + ctulu_target_warning_from_file(UtilityTests ${root_dir}/cmake/warnings.txt) + target_link_libraries(UtilityTests WaitFreeCollections CONAN_PKG::gtest) +endif() -make_target(unit_tests "WaitFreeHashMap" test/single_thread_test.cpp OPTIONS cxx executable) -set_property(TARGET unit_tests PROPERTY CXX_STANDARD 17) -target_link_libraries(unit_tests WaitFreeHashMapLibrary gtest_main) -add_test(NAME example_test COMMAND unit_tests) \ No newline at end of file +#set(CMAKE_VERBOSE_MAKEFILE 1) diff --git a/README.md b/README.md index b665d1c..56fe5d9 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,183 @@ -# WaitFreeHashMap +# WaitFreeCollections + + + + + + + + + + + + + + +
master branch + +
develop branch + +
License + +
+ +A header-only library providing wait-free collections such as hash map and double ended queue (deque). + +## Wait-Free Hash Map + +An implementation of a wait-free hash map as described in the article -An implementation of an hashmap described in the article: > P. Laborde, S. Feldman et D. Dechev, « A Wait-Free Hash Map »,
> International Journal of Parallel Programming, t. 45, n o 3, p. 421-448, 2017, issn : 1573-7640.
> doi : https://doi.org/10.1007/s10766-015-0376-3 -# Copyright +See [unordered_map example](./examples/unordered_map_example.cpp) in `examples` folder. -License: MIT +## Double ended queue (Deque) + +WIP -# Requirements +## Requirements - Compiler which supports C++17 - CMake ≥ 3.10 + +## Minimal unordered map example + +```cpp +#include +#include + +#include + +constexpr int nbr_threads = 16; + +int main() +{ + wfc::unordered_map m(4, nbr_threads, nbr_threads); + + std::array threads; + for (std::size_t i = 0; i < nbr_threads; ++i) + { + threads[i] = std::thread([&m, i]() { + m.insert(i, i); + }); + } + + for (auto& t: threads) + { + t.join(); + } + + m.visit([](std::pair p) { + std::cout << '[' << p.first << '-' << p.second << "]\n"; + }); + + return 0; +} +``` + +This should output: + +``` +[0-0] +[1-1] +[2-2] +[3-3] +[4-4] +[5-5] +[6-6] +[7-7] +[8-8] +[9-9] +[10-10] +[11-11] +[12-12] +[13-13] +[14-14] +[15-15] +``` + +## How to import the library using CMake + +To include the library, you may copy / paste the content of this repository in a subfolder (such as `externals`) or use a git submodule. + +Then, in you CMakeLists.txt file, you just have to do something similar to: + +```cmake +cmake_minimum_required(VERSION 3.10) +project(MyProject) +enable_language(CXX) +set(CMAKE_CXX_STANDARD 17) + +add_subdirectory(externals/WaitFreeCollections) + +find_package(Threads REQUIRED) # you'll probably need that one too + +add_executable(MyTarget src/main.cpp) +target_link_libraries(MyTarget Threads::Threads WaitFreeCollections) +``` + +At this point, the project hierarchy looks like: + +``` +|- CMakeLists.txt +|- src/ + |- main.cpp +|- externals/ + |- WaitFreeCollections/ + |- ... +``` + +You may try to use the minimal example above as your `main.cpp`. + +Also, since this is a header-only library, you may copy / paste the content of the include folder in your +project to achieve similar results (maybe a little bit less clean though). + +## Build targets + +You don't need to actually build the library beforehand to use it in your project due to the "header-only" nature of it. +The build targets in this repository are for tests, exemples and code formatting. +These are good to know should you contribute to this project or play with the exemples. + +First, you need to create a sub-directory to run `cmake` from it. Then you can build using `make`. + +``` +$ mkdir build +$ cmake .. -DWFC_BUILD_ALL=1 +``` + +By default, everything is build in Release mode. You can pass `-DCMAKE_BUILD_TYPE=Debug` to `cmake` to ask Debug builds. +This will also activate useful features for developing purposes such as the holy warnings. +The `-DWFC_BUILD_ALL=1` parameter will tell CMake to include all our targets in the build process. + +CMake will generate several targets that you can build separately using the `--target` parameter: + +- `UnorderedMapExample1` +- `UtilityTests` +- `UnorderedMapTests` +- `Clang-format` + +For instance, to build the `UnorderedMapTests` run + +``` +$ cmake --build . --target UnorderedMapTests +``` + +You can also build everything by not giving any specific target. +Produced executables are inside the `bin` folder. + +Note that the `Clang-format` target does not produce anything. +It just run the clang formatter. +Also this target needs to be called explicitly. + +For more details, see the [CMakeLists.txt](CMakeLists.txt) file. + +## Copyright + +License: MIT + +Main contributors: + +- [Jérôme Boulmier](https://github.com/Lomadriel) +- [Benoît Cortier](https://github.com/CBenoit) diff --git a/appveyor.yml b/appveyor.yml new file mode 100644 index 0000000..62cc4dd --- /dev/null +++ b/appveyor.yml @@ -0,0 +1,35 @@ +image: ubuntu + +install: + # install conan + - sudo pip install conan --upgrade + +before_build: + # update gcc and g++ alternatives + - sudo update-alternatives --set gcc /usr/bin/gcc-8 + + - gcc --version + - g++ --version + - cmake --version + + - mkdir ${APPVEYOR_BUILD_FOLDER}/debug_build + - cd ${APPVEYOR_BUILD_FOLDER}/debug_build + - cmake .. -DWFC_BUILD_ALL=1 -DCMAKE_BUILD_TYPE=Debug + + - mkdir ${APPVEYOR_BUILD_FOLDER}/release_build + - cd ${APPVEYOR_BUILD_FOLDER}/release_build + - cmake .. -DWFC_BUILD_ALL=1 -DCMAKE_BUILD_TYPE=Release + +build_script: + - cd ${APPVEYOR_BUILD_FOLDER}/debug_build + - make + + - cd ${APPVEYOR_BUILD_FOLDER}/release_build + - make + +test_script: + - ${APPVEYOR_BUILD_FOLDER}/debug_build/bin/UnorderedMapTests + - ${APPVEYOR_BUILD_FOLDER}/debug_build/bin/UtilityTests + - ${APPVEYOR_BUILD_FOLDER}/release_build/bin/UnorderedMapTests + - ${APPVEYOR_BUILD_FOLDER}/release_build/bin/UtilityTests + diff --git a/cmake/common.cmake b/cmake/common.cmake deleted file mode 100644 index dbdf8da..0000000 --- a/cmake/common.cmake +++ /dev/null @@ -1,1115 +0,0 @@ -################################################################################## -# MIT License # -# # -# Copyright (c) 2017 Maxime Pinard # -# # -# Permission is hereby granted, free of charge, to any person obtaining a copy # -# of this software and associated documentation files (the "Software"), to deal # -# in the Software without restriction, including without limitation the rights # -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # -# copies of the Software, and to permit persons to whom the Software is # -# furnished to do so, subject to the following conditions: # -# # -# The above copyright notice and this permission notice shall be included in all # -# copies or substantial portions of the Software. # -# # -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR # -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, # -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE # -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # -# 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. # -################################################################################## - -include(CheckCXXCompilerFlag) -include(CheckCCompilerFlag) - -## check_variable_name(function_name var_names... USED used_var_names...) -# Check if variables passed to a function don't have names conflicts with variables used in the function. -# Generate a fatal error on variable name conflict. -# {value} [in] function_name: Function name -# {value} [in] var_names: Names of the variables passed as parameters of the function -# {value} [in] used_var_names: Names of the function internal variables -function(check_variable_name _function_name) - split_args(_var_names USED _used_var_names ${ARGN}) - foreach(_var_name ${_var_names}) - foreach(_used_var_name ${_used_var_names}) - if("${_var_name}" STREQUAL "${_used_var_name}") - message(FATAL_ERROR "parameter ${_var_name} passed to function ${_function_name} has the same name than an internal variable of the function and can't be accessed") - endif() - endforeach() - endforeach() -endfunction() - -## directory_is_empty(output dir) -# Check if a directory is empty. -# If the input directory doesn't exist or is not a directory, it is considered as empty. -# {variable} [out] output: true if the directory is empty, false otherwise -# {value} [in] dir: Directory to check -function(directory_is_empty output dir) - set(tmp_output false) - get_filename_component(dir_path ${dir} REALPATH) - if(EXISTS "${dir_path}") - if(IS_DIRECTORY "${dir_path}") - file(GLOB files "${dir_path}/*") - list(LENGTH files len) - if(len EQUAL 0) - set(tmp_output true) - endif() - else() - set(tmp_output true) - endif() - else() - set(tmp_output true) - endif() - set(${output} ${tmp_output} PARENT_SCOPE) -endfunction() - -## split_args(left delimiter right args...) -# Split arguments into left and right parts on delimiter token. -# {variable} [out] left: Arguments at the left (before) the delimiter -# {value} [in] delimiter: Delimiter of the left and right part -# {variable} [out] right: Arguments at the right (after) the delimiter -# {value} [in] args: Arguments to split -function(split_args left delimiter right) - set(delimiter_found false) - set(tmp_left) - set(tmp_right) - foreach(it ${ARGN}) - if("${it}" STREQUAL ${delimiter}) - set(delimiter_found true) - elseif(delimiter_found) - list(APPEND tmp_right ${it}) - else() - list(APPEND tmp_left ${it}) - endif() - endforeach() - set(${left} ${tmp_left} PARENT_SCOPE) - set(${right} ${tmp_right} PARENT_SCOPE) -endfunction() - -## has_item(output item args...) -# Determine if arguments list contains an item. -# {variable} [out] output: true if the list contains the item, false otherwise -# {value} [in] item: Item to search in the list -# {value} [in] args: Arguments list which may contain the item -function(has_item output item) - set(tmp_output false) - foreach(it ${ARGN}) - if("${it}" STREQUAL "${item}") - set(tmp_output true) - break() - endif() - endforeach() - set(${output} ${tmp_output} PARENT_SCOPE) -endfunction() - -## flatten(property) -# Remove leading, trailing and multiple from a property. -# Useful to prevent unnecessary rebuilds. -# {variable} [in,out] property: Property to flatten -function(flatten _property) - # variables names are prefixed with'_' lower chances of parent scope variable hiding - check_variable_name(flatten ${_property} USED _property _tmp_property) - string(REPLACE " " " " _tmp_property "${${_property}}") - string(STRIP "${_tmp_property}" _tmp_property) - set(${_property} "${_tmp_property}" PARENT_SCOPE) -endfunction() - -## manage_flag(property regexp flag) -# Add a flag to a property or replace an existing flag matching a regular expression. -# If a flag of the property matches the regular expression, it is replaced by the input flag, -# else, the input flag is added to the property. -# Update the property in the cache. -# {variable} [in] property: Property to manage -# {value} [in] regexp: Regular expression to match -# {value} [in] flag: Flag to add (or use to replace regexp) -function(manage_flag _property _regexp _flag) - # variables names are prefixed with'_' lower chances of parent scope variable hiding - check_variable_name(manage_flag ${_property} USED _property _regexp _flag) - if("${${_property}}" MATCHES ${_regexp}) - string(REGEX REPLACE ${_regexp} ${_flag} ${_property} "${${_property}}") - else() - set(${_property} "${${_property}} ${_flag}") - endif() - flatten(${_property}) - set(${_property} "${${_property}}" CACHE STRING "" FORCE) -endfunction() - -## add_flag(property flag [configs...]) -# If not already present, add a flag to a property for the specified configs. -# {value} [in] property: Property to change (|(EXE|MODULE|SHARED|STATIC)_LINKER) -# {value} [in] value: Flag to add -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(add_flag property flag) - if(ARGN) - foreach(config ${ARGN}) - manage_flag(CMAKE_${property}_FLAGS_${config} ${flag} ${flag}) - endforeach() - else() - manage_flag(CMAKE_${property}_FLAGS ${flag} ${flag}) - endif() -endfunction() - -## remove_flag(property regexp [configs...]) -# Remove an existing flag of a property matching a regular expression for the specified configs. -# {value} [in] property: Property to change (|(EXE|MODULE|SHARED|STATIC)_LINKER) -# {value} [in] regexp: Regular expression to match -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(remove_flag property regexp) - if(ARGN) - foreach(config ${ARGN}) - manage_flag(CMAKE_${property}_FLAGS_${config} ${regexp} " ") - endforeach() - else() - manage_flag(CMAKE_${property}_FLAGS ${regexp} " ") - endif() -endfunction() - -## add_linker_flag(flag [configs...]) -# Add a flag to the linker arguments property for the specified configs. -# {value} [in] flag: Flag to add -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(add_linker_flag flag) - foreach(item IN ITEMS EXE MODULE SHARED STATIC) - add_flag(${item}_LINKER ${flag} ${ARGN}) - endforeach() -endfunction() - -## remove_linker_flag(flag [configs...]) -# Remove a flag from the linker arguments property for the specified configs. -# {value} [in] flag: Flag to remove -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(remove_linker_flag regexp) - foreach(item IN ITEMS EXE MODULE SHARED STATIC) - remove_flag(${item}_LINKER ${regexp} ${ARGN}) - endforeach() -endfunction() - -## add_cx_flag(flag [configs...]) -# Add a flag to the C and CXX compilers arguments property for the specified configs. -# {value} [in] flag: Flag to add -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(add_cx_flag flag) - CHECK_CXX_COMPILER_FLAG(${flag} has${flag}) - if(has${flag}) - add_flag(C ${flag} ${ARGN}) - add_flag(CXX ${flag} ${ARGN}) - endif() -endfunction() - -## remove_cx_flag(flag [configs...]) -# Remove a flag from the C and CXX compilers arguments property for the specified configs. -# {value} [in] flag: Flag to remove -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(remove_cx_flag regexp) - remove_flag(C ${regexp} ${ARGN}) - remove_flag(CXX ${regexp} ${ARGN}) -endfunction() - -## group_files(group root files...) -# Group files in IDE project generation (by calling source_group) relatively to a root. -# {value} [in] group: Group, files will be grouped in -# {value} [in] root: Root, files will be grouped relative to it -# {value} [in] files: Files to group -function(group_files group root) - foreach(it ${ARGN}) - get_filename_component(dir ${it} PATH) - file(RELATIVE_PATH relative ${root} ${dir}) - set(local ${group}) - if(NOT "${relative}" STREQUAL "") - set(local "${group}/${relative}") - endif() - # replace '/' and '\' (and repetitions) by '\\' - string(REGEX REPLACE "[\\\\\\/]+" "\\\\\\\\" local ${local}) - source_group("${local}" FILES ${it}) - endforeach() -endfunction() - -## filter_list(keep list regexp...) -# Include/exclude items from a list matching one or more regular expressions. -# Include: keep only the list items that matches regular expressions. -# Exclude: keep only the list items that doesn't matches regular expressions. -# {value} [in] keep: 1 for include, 0 for exclude -# {variable} [in,out] list: List to filter -# {value} [in] regexp: Regular expressions which list items may match -function(filter_list _keep _list) - # variables names are prefixed with'_' lower chances of parent scope variable hiding - check_variable_name(filter_list ${_list} USED _list _tmp_list) - set(_tmp_list) - foreach(_it ${${_list}}) - set(_touch) - foreach(_regexp ${ARGN}) - if(${_it} MATCHES ${_regexp}) - set(_touch true) - break() - endif() - endforeach() - if((_keep EQUAL 1 AND DEFINED _touch) OR (_keep EQUAL 0 AND NOT DEFINED _touch)) - list(APPEND _tmp_list ${_it}) - endif() - endforeach() - set(${_list} ${_tmp_list} PARENT_SCOPE) -endfunction() - -## filter_out(list regexp...) -# Exclude items from a list matching one or more regular expressions. -# Exclude: keep only the list items that doesn't matches regular expressions. -# {variable} [in,out] list: List to filter -# {value} [in] regexp: Regular expressions which list items may match -macro(filter_out list) - filter_list(0 ${list} ${ARGN}) -endmacro() - -## filter_in(list regexp...) -# Include items from a list matching one or more regular expressions. -# Include: keep only the list items that matches regular expressions. -# {variable} [in,out] list: List to filter -# {value} [in] regexp: Regular expressions which list items may match -macro(filter_in list) - filter_list(1 ${list} ${ARGN}) -endmacro() - -## join_list(output sep items...) -# Join items with a separator into a variable. -# {variable} [out] output: Output variable, contain the item joined -# {value} [in] sep: Separator to insert between items -# {value} [in] items: Items to join -function(join_list output sep) - set(tmp_output) - set(first) - foreach(it ${ARGN}) - if(NOT DEFINED first) - set(tmp_output ${it}) - set(first true) - else() - set(tmp_output "${tmp_output}${sep}${it}") - endif() - endforeach() - set(${output} ${tmp_output} PARENT_SCOPE) -endfunction() - -## get_files(output_files directories... [OPTIONS [recurse]]) -# Get (recursively or not) C and C++ sources files form input directories. -# Also group the files into Sources with the group_files function relatively to their root directory. -# {variable} [out] output: Output variable, contain the sources files -# {value} [in] directories: Directory to search files -# {option} [in] recurse: If present, search is recursive -function(get_files output) - split_args(dirs "OPTIONS" options ${ARGN}) - set(glob GLOB) - has_item(has_recurse "recurse" ${options}) - if(has_recurse) - set(glob GLOB_RECURSE) - endif() - set(files) - foreach(it ${dirs}) - if(IS_DIRECTORY ${it}) - set(patterns - "${it}/*.c" - "${it}/*.cc" - "${it}/*.cpp" - "${it}/*.cxx" - "${it}/*.h" - "${it}/*.hpp" - ) - file(${glob} tmp_files ${patterns}) - list(APPEND files ${tmp_files}) - get_filename_component(parent_dir ${it} DIRECTORY) - group_files(Sources "${parent_dir}" ${tmp_files}) - else() - list(APPEND files ${it}) - get_filename_component(dir ${it} DIRECTORY) - group_files(Sources "${dir}" ${it}) - endif() - endforeach() - set(${output} ${files} PARENT_SCOPE) -endfunction() - -## target_add_includes(target includes...) -# Add input includes (files or directories) to target. -# {value} [in] target: Target to add includes -# {value} [in] includes: Includes to add -function(target_add_includes target) - list(LENGTH ARGN size) - if(NOT ${size} GREATER 0) - return() - endif() - list(REMOVE_DUPLICATES ARGN) - list(SORT ARGN) - target_include_directories(${target} PRIVATE ${ARGN}) -endfunction() - -## target_add_system_includes(target includes...) -# Add input system includes (files or directories) to target. -# {value} [in] target: Target to add includes -# {value} [in] includes: Includes to add -function(target_add_system_includes target) - list(LENGTH ARGN size) - if(NOT ${size} GREATER 0) - return() - endif() - list(REMOVE_DUPLICATES ARGN) - list(SORT ARGN) - target_include_directories(${target} SYSTEM PRIVATE ${ARGN}) -endfunction() - -## target_add_compile_definition(target definition [configs...]) -# Add a private compile definition to the target for the specified configs. -# {value} [in] target: Target to add flag -# {value} [in] definition: Definition to add -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(target_add_compile_definition target definition) - if(${ARGC} GREATER 2) - foreach(config ${ARGN}) - string(TOLOWER "${config}" config_lower) - set(config_name) - foreach(valid_config IN ITEMS "Debug" "RelWithDebInfo" "Release") - string(TOLOWER "${valid_config}" valid_config_lower) - if(${config_lower} STREQUAL ${valid_config_lower}) - set(config_name ${valid_config}) - endif() - endforeach() - if(DEFINED config_name) - target_compile_definitions(${target} PRIVATE "$<$:${definition}>") - endif() - endforeach() - else() - target_compile_definitions(${target} PRIVATE "${definition}") - endif() -endfunction() - -## target_add_compiler_flag(target flag [configs...]) -# Add a flag to the compiler arguments of the target for the specified configs. -# Add the flag only if the compiler support it (checked with CHECK_CXX_COMPILER_FLAG). -# {value} [in] target: Target to add flag -# {value} [in] flag: Flag to add -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(target_add_compiler_flag target flag) - CHECK_CXX_COMPILER_FLAG(${flag} has${flag}) - if(has${flag}) - if(${ARGC} GREATER 2) - foreach(config ${ARGN}) - string(TOLOWER "${config}" config_lower) - set(config_name) - foreach(valid_config IN ITEMS "Debug" "RelWithDebInfo" "Release") - string(TOLOWER "${valid_config}" valid_config_lower) - if(${config_lower} STREQUAL ${valid_config_lower}) - set(config_name ${valid_config}) - endif() - endforeach() - if(DEFINED config_name) - target_compile_options(${target} PRIVATE "$<$:${flag}>") - endif() - endforeach() - else() - target_compile_options(${target} PRIVATE "${flag}") - endif() - endif() -endfunction() - -## append_to_target_property(target property [values...]) -# Append values to a target property. -# {value} [in] target: Target to modify -# {value} [in] property: Property to append values to -# {value} [in] values: Values to append to the property -function(append_to_target_property target property) - set(new_values ${ARGN}) - get_target_property(existing_values ${target} ${property}) - if(existing_values) - set(new_values "${existing_values} ${new_values}") - endif() - set_target_properties(${target} PROPERTIES ${property} ${new_values}) -endfunction() - -## __target_link_flag_property(target flag [configs...]) -# Add a flag to the linker arguments of the target for the specified configs using LINK_FLAGS properties of the target. -# Function made for CMake 3.12 or less, future CMake version will have target_link_options() with cmake-generator-expressions. -# {value} [in] target: Target to add flag -# {value} [in] flag: Flag to add -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(__target_link_flag_property target flag) - if(${ARGC} GREATER 2) - foreach(config ${ARGN}) - append_to_target_property(${target} LINK_FLAGS_${config} ${flag}) - endforeach() - else() - append_to_target_property(${target} LINK_FLAGS ${flag}) - endif() -endfunction() - -## target_add_linker_flag(target flag [configs...]) -# Add a flag to the linker arguments of the target for the specified configs. -# Add the flag only if the linker support it (checked with CHECK_CXX_COMPILER_FLAG). -# {value} [in] target: Target to add flag -# {value} [in] flag: Flag to add -# {value} [in] configs: Configs for the property to change (DEBUG|RELEASE|RELWITHDEBINFO) -function(target_add_linker_flag target flag) - CHECK_CXX_COMPILER_FLAG(${flag} has${flag}) - if(has${flag}) - if(${ARGC} GREATER 2) - foreach(config ${ARGN}) - string(TOLOWER "${config}" config_lower) - set(config_name) - foreach(valid_config IN ITEMS "Debug" "RelWithDebInfo" "Release") - string(TOLOWER "${valid_config}" valid_config_lower) - if(${config_lower} STREQUAL ${valid_config_lower}) - set(config_name ${valid_config}) - endif() - endforeach() - if(DEFINED config_name) - if(COMMAND target_link_options) - target_link_options(${target} PRIVATE "$<$:${flag}>") - else() - string(TOUPPER "${config_name}" config_name_upper) - __target_link_flag_property(${target} ${flag} ${config_name_upper}) - endif() - endif() - endforeach() - else() - if(COMMAND target_link_options) - target_link_options(${target} PRIVATE "${flag}") - else() - __target_link_flag_property(${target} "${flag}") - endif() - endif() - endif() -endfunction() - -## target_set_output_directory(target directory) -# Set the target runtime, library and archive output directory to the input directory. -# {value} [in] target: Target to configure -# {value} [in] directory: Output directory -function(target_set_output_directory target directory) - target_set_runtime_output_directory(${target} "${directory}") - target_set_library_output_directory(${target} "${directory}") - target_set_archive_output_directory(${target} "${directory}") -endfunction() - -## target_set_runtime_output_directory(target directory) -# Set the target runtime output directory to the input directory. -# {value} [in] target: Target to configure -# {value} [in] directory: Output directory -function(target_set_runtime_output_directory target directory) - set_target_properties(${target} PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${directory}" - RUNTIME_OUTPUT_DIRECTORY_DEBUG "${directory}" - RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO "${directory}" - RUNTIME_OUTPUT_DIRECTORY_RELEASE "${directory}" - ) -endfunction() - -## target_set_library_output_directory(target directory) -# Set the target library output directory to the input directory. -# {value} [in] target: Target to configure -# {value} [in] directory: Output directory -function(target_set_library_output_directory target directory) - set_target_properties(${target} PROPERTIES - LIBRARY_OUTPUT_DIRECTORY "${directory}" - LIBRARY_OUTPUT_DIRECTORY_DEBUG "${directory}" - LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO "${directory}" - LIBRARY_OUTPUT_DIRECTORY_RELEASE "${directory}" - ) -endfunction() - -## target_set_archive_output_directory(target directory) -# Set the target archive output directory to the input directory. -# {value} [in] target: Target to configure -# {value} [in] directory: Output directory -function(target_set_archive_output_directory target directory) - set_target_properties(${target} PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY "${directory}" - ARCHIVE_OUTPUT_DIRECTORY_DEBUG "${directory}" - ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO "${directory}" - ARCHIVE_OUTPUT_DIRECTORY_RELEASE "${directory}" - ) -endfunction() - -## setup_msvc(target [OPTIONS [static_runtime] [no_warnings] [low_warnings]]) -# Set up msvc-specific options of the target. -# Without options: maximum warnings are enabled. -# {value} [in] target: Target to configure -# {option} [in] static_runtime: If present, C runtime library is statically linked -# {option} [in] no_warnings: If present, warnings are disabled (useful for external projects) -# {option} [in] low_warnings: If present, low/normal warnings are enabled -function(setup_msvc target) - split_args(ignore "OPTIONS" options ${ARGN}) - has_item(option_static_runtime "static_runtime" ${options}) - has_item(option_no_warnings "no_warnings" ${options}) - has_item(option_low_warnings "low_warnings" ${options}) - - # set Source and Executable character sets to UTF-8 - target_add_compiler_flag(${target} "/utf-8") - - # enable parallel compilation - target_add_compiler_flag(${target} "/MP") - - # generates complete debugging information - target_add_compiler_flag(${target} "/Zi" DEBUG RELWITHDEBINFO) - target_add_linker_flag(${target} "/DEBUG:FULL" DEBUG RELWITHDEBINFO) - - # set optimization - target_add_compiler_flag(${target} "/Od" DEBUG) - target_add_compiler_flag(${target} "/O2" RELWITHDEBINFO) - target_add_compiler_flag(${target} "/Ox" RELEASE) - - # enables automatic parallelization of loops - target_add_compiler_flag(${target} "/Qpar" RELEASE) - - # enable runtime checks - target_add_compiler_flag(${target} "/RTC1" DEBUG) - - # disable incremental compilations - target_add_linker_flag(${target} "/INCREMENTAL:NO" RELEASE RELWITHDEBINFO) - - # remove unused symbols - target_add_linker_flag(${target} "/OPT:REF" RELEASE RELWITHDEBINFO) - target_add_linker_flag(${target} "/OPT:ICF" RELEASE RELWITHDEBINFO) - - # disable manifests - target_add_linker_flag(${target} "/MANIFEST:NO" RELEASE RELWITHDEBINFO) - - # enable function-level linking - #target_add_compiler_flag(${target} "/Gy" RELEASE RELWITHDEBINFO) - - # sets the Checksum in the .exe header - #target_add_linker_flag(${target} "/RELEASE" RELEASE RELWITHDEBINFO) - - # statically link C runtime library to static_runtime targets - if(option_static_runtime) - target_add_compiler_flag(${target} "/MTd" DEBUG) - target_add_compiler_flag(${target} "/MT" RELWITHDEBINFO RELEASE) - else() - target_add_compiler_flag(${target} "/MDd" DEBUG) - target_add_compiler_flag(${target} "/MD" RELWITHDEBINFO RELEASE) - endif() - - # manage warnings - set(flags) - if(option_no_warnings) - set(flags "/W0") - elseif(option_low_warnings) - set(flags "/W3") - else() - set(flags - ## Base flags: - "/W4" - - ## Extra flags: - "/w44263" # 'function': member function does not override any base class virtual member function - "/w44265" # 'class': class has virtual functions, but destructor is not virtual - "/w44287" # 'operator': unsigned/negative constant mismatch - "/w44289" # nonstandard extension used : 'var' : loop control variable declared in the for-loop is used outside the for-loop scope - "/w44296" # 'operator': expression is always false - "/w44355" # 'this' : used in base member initializer list - "/w44365" # 'action': conversion from 'type_1' to 'type_2', signed/unsigned mismatch - "/w44412" # 'function': function signature contains type 'type'; C++ objects are unsafe to pass between pure code and mixed or native - "/w44431" # missing type specifier - int assumed. Note: C no longer supports default-int - "/w44536" # 'type name': type-name exceeds meta-data limit of 'limit' characters - "/w44545" # expression before comma evaluates to a function which is missing an argument list - "/w44546" # function call before comma missing argument list - "/w44547" # 'operator': operator before comma has no effect; expected operator with side-effect - "/w44548" # expression before comma has no effect; expected expression with side-effect - "/w44549" # 'operator': operator before comma has no effect; did you intend 'operator'? - "/w44555" # expression has no effect; expected expression with side-effect - "/w44619" # #pragma warning: there is no warning number 'number' - #"/w44623" # 'derived class': default constructor could not be generated because a base class default constructor is inaccessible - #"/w44625" # 'derived class': copy constructor could not be generated because a base class copy constructor is inaccessible - #"/w44626" # 'derived class': assignment operator could not be generated because a base class assignment operator is inaccessible - #"/w44640" # 'instance': construction of local static object is not thread-safe - "/w44917" # 'declarator': a GUID can only be associated with a class, interface, or namespace - "/w44946" # reinterpret_cast used between related classes: 'class1' and 'class2' - "/w44986" # 'symbol': exception specification does not match previous declaration - "/w44987" # nonstandard extension used: 'throw (...)' - "/w44988" # 'symbol': variable declared outside class/function scope - "/w45022" # 'type': multiple move constructors specified - "/w45023" # 'type': multiple move assignment operators specified - "/w45029" # nonstandard extension used: alignment attributes in C++ apply to variables, data members and tag types only - "/w45031" # #pragma warning(pop): likely mismatch, popping warning state pushed in different file - "/w45032" # detected #pragma warning(push) with no corresponding #pragma warning(pop) - "/w45034" # use of intrinsic 'intrinsic' causes function function to be compiled as guest code - "/w45035" # use of feature 'feature' causes function function to be compiled as guest code - "/w45036" # varargs function pointer conversion when compiling with /hybrid:x86arm64 'type1' to 'type2' - "/w45038" # data member 'member1' will be initialized after data member 'member2' - "/w45039" # 'function': pointer or reference to potentially throwing function passed to extern C function under -EHc. Undefined behavior may occur if this function throws an exception. - "/w45042" # 'function': function declarations at block scope cannot be specified 'inline' in standard C++; remove 'inline' specifier - - ## Apocalypse flags: - #"/Wall" - #"/WX" - ) - endif() - foreach(flag ${flags}) - target_add_compiler_flag(${target} ${flag}) - endforeach() -endfunction() - -## setup_gcc(target [OPTIONS [static_runtime] [c] [cxx] [no_warnings] [low_warnings]]) -# Set up gcc-specific options of the target. -# Without options: maximum warnings are enabled. -# {value} [in] target: Target to configure -# {option} [in] static_runtime: If present, C runtime library is statically linked -# {option} [in] c: If present, the target is written in C, add warnings if no warnings option is specified -# {option} [in] cxx: If present, the target is written in C++, add warnings if no warnings option is specified -# {option} [in] no_warnings: If present, warnings are disabled (useful for external projects) -# {option} [in] low_warnings: If present, low/normal warnings are enabled -function(setup_gcc target) - split_args(ignore "OPTIONS" options ${ARGN}) - has_item(option_static_runtime "static_runtime" ${options}) - has_item(option_c "c" ${options}) - has_item(option_cxx "cxx" ${options}) - has_item(option_no_warnings "no_warnings" ${options}) - has_item(option_low_warnings "low_warnings" ${options}) - - # generates complete debugging information - target_add_compiler_flag(${target} "-g3" DEBUG RELWITHDEBINFO) - - # set optimization - target_add_compiler_flag(${target} "-O0" DEBUG) - target_add_compiler_flag(${target} "-O2" RELWITHDEBINFO) - target_add_compiler_flag(${target} "-O3" RELEASE) - - # statically link C runtime library to static_runtime targets - if(option_static_runtime) - target_add_compiler_flag(${target} "-static-libgcc") - target_add_compiler_flag(${target} "-static-libstdc++") - endif() - - # enable sanitizers - #target_add_linker_flag(${target} "-fsanitize=address" DEBUG RELWITHDEBINFO) - #target_add_linker_flag(${target} "-fsanitize=thread" DEBUG RELWITHDEBINFO) - #target_add_linker_flag(${target} "-fsanitize=memory" DEBUG RELWITHDEBINFO) - #target_add_linker_flag(${target} "-fsanitize=undefined" DEBUG RELWITHDEBINFO) - #target_add_linker_flag(${target} "-fsanitize=leak" DEBUG RELWITHDEBINFO) - - # enable libstdc++ "debug" mode - # warning: changes the size of some standard class templates - # you cannot pass containers between translation units compiled - # with and without libstdc++ "debug" mode - #target_add_compile_definition(${target} _GLIBCXX_DEBUG DEBUG) - #target_add_compile_definition(${target} _GLIBCXX_DEBUG_PEDANTIC DEBUG) - - # manage warnings - set(flags) - set(c_flags) - set(cxx_flags) - if(option_no_warnings) - set(flags "--no-warnings") - elseif(option_low_warnings) - set(flags "-pedantic" "-Wall") - else() - set(flags - ## Base flags: - "-pedantic" - "-pedantic-errors" - "-Wall" - "-Wextra" - - ## Extra flags: - "-Wdouble-promotion" - "-Wnull-dereference" - "-Wimplicit-fallthrough" - "-Wif-not-aligned" - "-Wmissing-include-dirs" - "-Wswitch-bool" - "-Wswitch-unreachable" - "-Walloc-zero" - "-Wduplicated-branches" - "-Wduplicated-cond" - "-Wfloat-equal" - "-Wshadow" - "-Wundef" - "-Wexpansion-to-defined" - #"-Wunused-macros" - "-Wcast-qual" - "-Wcast-align" - "-Wwrite-strings" - "-Wconversion" - "-Wsign-conversion" - "-Wdate-time" - "-Wextra-semi" - "-Wlogical-op" - "-Wmissing-declarations" - "-Wredundant-decls" - "-Wrestrict" - #"-Winline" - "-Winvalid-pch" - "-Woverlength-strings" - "-Wformat=2" - "-Wformat-signedness" - "-Winit-self" - - ## Optimisation dependant flags - "-Wstrict-overflow=5" - - ## Info flags - #"-Winvalid-pch" - #"-Wvolatile-register-var" - #"-Wdisabled-optimization" - #"-Woverlength-strings" - #"-Wunsuffixed-float-constants" - #"-Wvector-operation-performance" - - ## Apocalypse flags: - #"-Wsystem-headers" - #"-Werror" - - ## Exit on first error - "-Wfatal-errors" - ) - if(option_c) - set(c_flags - "-Wdeclaration-after-statement" - "-Wbad-function-cast" - "-Wjump-misses-init" - "-Wstrict-prototypes" - "-Wold-style-definition" - "-Wmissing-prototypes" - "-Woverride-init-side-effects" - "-Wnested-externs" - #"-Wc90-c99-compat" - #"-Wc99-c11-compat" - #"-Wc++-compat" - ) - endif() - if(option_cxx) - set(cxx_flags - "-Wzero-as-null-pointer-constant" - "-Wsubobject-linkage" - "-Wdelete-incomplete" - "-Wuseless-cast" - "-Wctor-dtor-privacy" - "-Wnoexcept" - "-Wregister" - "-Wstrict-null-sentinel" - "-Wold-style-cast" - "-Woverloaded-virtual" - - ## Lifetime - "-Wlifetime" - - ## Suggestions - "-Wsuggest-override" - #"-Wsuggest-final-types" - #"-Wsuggest-final-methods" - #"-Wsuggest-attribute=pure" - #"-Wsuggest-attribute=const" - #"-Wsuggest-attribute=noreturn" - #"-Wsuggest-attribute=format" - - ## Guidelines from Scott Meyers’ Effective C++ series of books - #"-Weffc++" - - ## Special purpose - #"-Wsign-promo" - #"-Wtemplates" - #"-Wmultiple-inheritance" - #"-Wvirtual-inheritance" - #"-Wnamespaces" - - ## Standard versions - #"-Wc++11-compat" - #"-Wc++14-compat" - #"-Wc++17-compat" - ) - endif() - endif() - foreach(flag IN ITEMS ${flags} ${c_flags} ${cxx_flags}) - target_add_compiler_flag(${target} ${flag}) - endforeach() -endfunction() - -## setup_clang(target [OPTIONS [static_runtime] [no_warnings] [low_warnings]]) -# Set up clang-specific options of the target. -# Without options: maximum warnings are enabled. -# {value} [in] target: Target to configure -# {option} [in] static_runtime: If present, C runtime library is statically linked -# {option} [in] no_warnings: If present, warnings are disabled (useful for external projects) -# {option} [in] low_warnings: If present, low/normal warnings are enabled -function(setup_clang target) - split_args(ignore "OPTIONS" options ${ARGN}) - has_item(option_static_runtime "static_runtime" ${options}) - has_item(option_no_warnings "no_warnings" ${options}) - has_item(option_low_warnings "low_warnings" ${options}) - - # generates complete debugging information - target_add_compiler_flag(${target} "-g3" DEBUG RELWITHDEBINFO) - - # set optimization - target_add_compiler_flag(${target} "-O0" DEBUG) - target_add_compiler_flag(${target} "-O2" RELWITHDEBINFO) - target_add_compiler_flag(${target} "-O3" RELEASE) - - # statically link C runtime library to static_runtime targets - if(option_static_runtime) - target_add_compiler_flag(${target} "-static-libgcc") - target_add_compiler_flag(${target} "-static-libstdc++") - endif() - - # enable sanitizers - #target_add_linker_flag(${target} "-fsanitize=address" DEBUG RELWITHDEBINFO) - #target_add_linker_flag(${target} "-fsanitize=thread" DEBUG RELWITHDEBINFO) - #target_add_linker_flag(${target} "-fsanitize=memory" DEBUG RELWITHDEBINFO) - #target_add_linker_flag(${target} "-fsanitize=undefined" DEBUG RELWITHDEBINFO) - #target_add_linker_flag(${target} "-fsanitize=leak" DEBUG RELWITHDEBINFO) - - # enable libstdc++ "debug" mode - # warning: changes the size of some standard class templates - # you cannot pass containers between translation units compiled - # with and without libstdc++ "debug" mode - #target_add_compile_definition(${target} _GLIBCXX_DEBUG DEBUG) - #target_add_compile_definition(${target} _GLIBCXX_DEBUG_PEDANTIC DEBUG) - - # manage warnings - set(flags) - if(option_no_warnings) - set(flags "-Wno-everything") - elseif(option_low_warnings) - set(flags "-pedantic" "-Wall") - else() - set(flags - ## Base flags: - "-pedantic" - "-pedantic-errors" - "-Wall" - "-Wextra" - - ## Extra flags: - "-Wbad-function-cast" - "-Wcomplex-component-init" - "-Wconditional-uninitialized" - "-Wcovered-switch-default" - "-Wcstring-format-directive" - "-Wdelete-non-virtual-dtor" - "-Wdeprecated" - "-Wdollar-in-identifier-extension" - "-Wdouble-promotion" - "-Wduplicate-enum" - "-Wduplicate-method-arg" - "-Wembedded-directive" - "-Wexpansion-to-defined" - "-Wextended-offsetof" - "-Wfloat-conversion" - "-Wfloat-equal" - "-Wfor-loop-analysis" - "-Wformat-pedantic" - "-Wgnu" - "-Wimplicit-fallthrough" - "-Winfinite-recursion" - "-Winvalid-or-nonexistent-directory" - "-Wkeyword-macro" - "-Wmain" - "-Wmethod-signatures" - "-Wmicrosoft" - "-Wmismatched-tags" - "-Wmissing-field-initializers" - "-Wmissing-method-return-type" - "-Wmissing-prototypes" - "-Wmissing-variable-declarations" - "-Wnested-anon-types" - "-Wnon-virtual-dtor" - "-Wnonportable-system-include-path" - "-Wnull-pointer-arithmetic" - "-Wnullability-extension" - "-Wold-style-cast" - "-Woverriding-method-mismatch" - "-Wpacked" - "-Wpedantic" - "-Wpessimizing-move" - "-Wredundant-move" - "-Wreserved-id-macro" - "-Wself-assign" - "-Wself-move" - "-Wsemicolon-before-method-body" - "-Wshadow" - "-Wshadow-field" - "-Wshadow-field-in-constructor" - "-Wshadow-uncaptured-local" - "-Wshift-sign-overflow" - "-Wshorten-64-to-32" - #"-Wsign-compare" - #"-Wsign-conversion" - "-Wsigned-enum-bitfield" - "-Wstatic-in-inline" - #"-Wstrict-prototypes" - #"-Wstring-conversion" - #"-Wswitch-enum" - "-Wtautological-compare" - "-Wtautological-overlap-compare" - "-Wthread-safety" - "-Wundefined-reinterpret-cast" - "-Wuninitialized" - #"-Wunknown-pragmas" - "-Wunreachable-code" - "-Wunreachable-code-aggressive" - #"-Wunused" - "-Wunused-const-variable" - "-Wunused-lambda-capture" - "-Wunused-local-typedef" - "-Wunused-parameter" - "-Wunused-private-field" - "-Wunused-template" - "-Wunused-variable" - "-Wused-but-marked-unused" - "-Wzero-as-null-pointer-constant" - "-Wzero-length-array" - - ## Lifetime - "-Wlifetime" - - ## Info flags - "-Wcomma" - "-Wcomment" - - ## Exit on first error - "-Wfatal-errors" - ) - endif() - foreach(flag ${flags}) - target_add_compiler_flag(${target} ${flag}) - endforeach() -endfunction() - -## setup_target(target [OPTIONS [options...]]) -# Set up options of the target depending of the compiler. -# To know possible option, see the setup_ functions documentation. -# Currently supported compilers: msvc, gcc. -# {value} [in] target: Target to configure -# {option} [in] options: Configuration options -function(setup_target target) - if(MSVC) - setup_msvc(${target} OPTIONS ${ARGN}) - elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "GNU") - setup_gcc(${target} OPTIONS ${ARGN}) - elseif("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" OR "${CMAKE_C_COMPILER_ID}" STREQUAL "Clang") - setup_clang(${target} OPTIONS ${ARGN}) - else() - message(WARNING "Unsupported compiler (${CMAKE_CXX_COMPILER_ID}) setup") - endif() -endfunction() - -## make_target(target group files... [INCLUDES includes...] [EXT_INCLUDES ext_includes...] -## [OPTIONS [executable] [test] [shared] [static_runtime] [c] [cxx] [no_warnings] [low_warnings]]) -# Make a new target with the input options -# By default targets are static libraries. -# {value} [in] target: Target name -# {value} [in] group: Group of the target (can contain '/'' for subgroups) -# {value} [in] files: Source files -# {value} [in] includes: Include files -# {value} [in] ext_includes: External include files (no warnings) -# {option} [in] executable: If present, build an executable -# {option} [in] test: If present, build a test executable -# {option} [in] shared: If present, build a shared library -# {option} [in] static_runtime: If present, C runtime library is statically linked -# {option} [in] c: If present, the target is written in C, add warnings if no warnings option is specified -# {option} [in] cxx: If present, the target is written in C++, add warnings if no warnings option is specified -# {option} [in] no_warnings: If present, warnings are disabled (useful for external projects) -# {option} [in] low_warnings: If present, low/normal warnings are enabled -# {option} [in] interface If present, defines the target as an interface -function(make_target target group) - message(STATUS "Configuring ${group}/${target}") - - # initialize additional include directory list - set(includes) - set(ext_includes) - - # get options - split_args(inputs "OPTIONS" options ${ARGN}) - split_args(inputs2 "EXT_INCLUDES" ext_includes ${inputs}) - split_args(files "INCLUDES" includes ${inputs2}) - has_item(is_executable "executable" ${options}) - has_item(is_test "test" ${options}) - has_item(is_shared "shared" ${options}) - has_item(is_interface "interface" ${options}) - - # sort files - if (files) - list(SORT files) - endif() - - # add the target - if(is_executable OR is_test) - add_executable(${target} "" ${files}) - if(is_test) - add_test(NAME ${target} COMMAND ${target}) - #add_custom_command(TARGET ${target} POST_BUILD COMMAND $) - endif() - elseif(is_interface) - add_library(${target} INTERFACE) - elseif(is_shared) - add_library(${target} SHARED ${files}) - else() - add_library(${target} STATIC ${files}) - endif() - - # setup compiler dependent options - if (NOT is_interface) - setup_target(${target} ${options}) - endif() - - # add all additional include directories - if(NOT is_interface) - target_add_includes(${target} ${includes}) - target_add_system_includes(${target} ${ext_includes}) - else() - target_include_directories(${target} INTERFACE ${includes}) - endif() - - # set directories for IDE - source_group(CMake REGULAR_EXPRESSION ".*[.](cmake|rule)$") - source_group(CMake FILES "CMakeLists.txt") - if (NOT is_interface) - set_target_properties(${target} PROPERTIES FOLDER ${group}) - endif() -endfunction() - -## configure_folder(input_folder output_folder [args...]) -# Recursively copy all files from an input folder to an output folder -# Copy is made with CMake configure_file(), see documentation for more information: -# https://cmake.org/cmake/help/latest/command/configure_file.html -# {value} [in] input_folder: Input folder -# {value} [in] output_folder: Output folder -# {value} [in] args: CMake configure_file() additional arguments -function(configure_folder input_folder output_folder) - file(GLOB_RECURSE files "${input_folder}/*") - foreach(file ${files}) - file(RELATIVE_PATH relative_file ${input_folder} ${file}) - configure_file(${file} "${output_folder}/${relative_file}" ${ARGN}) - endforeach() -endfunction() - - -# disable compiler specific extensions -set(CMAKE_C_EXTENSIONS OFF) -set(CMAKE_CXX_EXTENSIONS OFF) - -# set C_STANDARD/CXX_STANDARD as a requirement -set(CMAKE_C_STANDARD_REQUIRED ON) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -# don't allow to build in sources otherwise a makefile not generated by CMake can be overridden -set(CMAKE_DISABLE_SOURCE_CHANGES ON) -set(CMAKE_DISABLE_IN_SOURCE_BUILD ON) - -# place generated binaries in build/bin -file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/build/bin) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build/bin) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/build/bin/) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/build/bin/) -set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/build/bin/) - -# place generated libs in build/lib -file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/build/lib) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build/lib) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/build/lib) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/build/lib/) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/build/lib/) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/build/lib/) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/build/lib/) -set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/build/lib/) -set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/build/lib/) - -# enable IDE folders -set_property(GLOBAL PROPERTY USE_FOLDERS ON) -set_property(GLOBAL PROPERTY PREDEFINED_TARGETS_FOLDER "_CMake") \ No newline at end of file diff --git a/cmake/conan.cmake b/cmake/conan.cmake new file mode 100644 index 0000000..58ed248 --- /dev/null +++ b/cmake/conan.cmake @@ -0,0 +1,518 @@ +# The MIT License (MIT) + +# Copyright (c) 2018 JFrog + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# 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. + + + +# This file comes from: https://github.com/conan-io/cmake-conan. Please refer +# to this repository for issues and documentation. + +# Its purpose is to wrap and launch Conan C/C++ Package Manager when cmake is called. +# It will take CMake current settings (os, compiler, compiler version, architecture) +# and translate them to conan settings for installing and retrieving dependencies. + +# It is intended to facilitate developers building projects that have conan dependencies, +# but it is only necessary on the end-user side. It is not necessary to create conan +# packages, in fact it shouldn't be use for that. Check the project documentation. + + +include(CMakeParseArguments) + +function(_get_msvc_ide_version result) + set(${result} "" PARENT_SCOPE) + if(NOT MSVC_VERSION VERSION_LESS 1400 AND MSVC_VERSION VERSION_LESS 1500) + set(${result} 8 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1500 AND MSVC_VERSION VERSION_LESS 1600) + set(${result} 9 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1600 AND MSVC_VERSION VERSION_LESS 1700) + set(${result} 10 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1700 AND MSVC_VERSION VERSION_LESS 1800) + set(${result} 11 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1800 AND MSVC_VERSION VERSION_LESS 1900) + set(${result} 12 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1900 AND MSVC_VERSION VERSION_LESS 1910) + set(${result} 14 PARENT_SCOPE) + elseif(NOT MSVC_VERSION VERSION_LESS 1910 AND MSVC_VERSION VERSION_LESS 1920) + set(${result} 15 PARENT_SCOPE) + else() + message(FATAL_ERROR "Conan: Unknown MSVC compiler version [${MSVC_VERSION}]") + endif() +endfunction() + +function(conan_cmake_settings result) + #message(STATUS "COMPILER " ${CMAKE_CXX_COMPILER}) + #message(STATUS "COMPILER " ${CMAKE_CXX_COMPILER_ID}) + #message(STATUS "VERSION " ${CMAKE_CXX_COMPILER_VERSION}) + #message(STATUS "FLAGS " ${CMAKE_LANG_FLAGS}) + #message(STATUS "LIB ARCH " ${CMAKE_CXX_LIBRARY_ARCHITECTURE}) + #message(STATUS "BUILD TYPE " ${CMAKE_BUILD_TYPE}) + #message(STATUS "GENERATOR " ${CMAKE_GENERATOR}) + #message(STATUS "GENERATOR WIN64 " ${CMAKE_CL_64}) + + message(STATUS "Conan: Automatic detection of conan settings from cmake") + + parse_arguments(${ARGV}) + + if(ARGUMENTS_BUILD_TYPE) + set(_CONAN_SETTING_BUILD_TYPE ${ARGUMENTS_BUILD_TYPE}) + elseif(CMAKE_BUILD_TYPE) + set(_CONAN_SETTING_BUILD_TYPE ${CMAKE_BUILD_TYPE}) + else() + message(FATAL_ERROR "Please specify in command line CMAKE_BUILD_TYPE (-DCMAKE_BUILD_TYPE=Release)") + endif() + if(ARGUMENTS_ARCH) + set(_CONAN_SETTING_ARCH ${ARGUMENTS_ARCH}) + endif() + #handle -s os setting + if(CMAKE_SYSTEM_NAME) + #use default conan os setting if CMAKE_SYSTEM_NAME is not defined + set(CONAN_SYSTEM_NAME ${CMAKE_SYSTEM_NAME}) + if(${CMAKE_SYSTEM_NAME} STREQUAL "Darwin") + set(CONAN_SYSTEM_NAME Macos) + endif() + set(CONAN_SUPPORTED_PLATFORMS Windows Linux Macos Android iOS FreeBSD WindowsStore) + list (FIND CONAN_SUPPORTED_PLATFORMS "${CONAN_SYSTEM_NAME}" _index) + if (${_index} GREATER -1) + #check if the cmake system is a conan supported one + set(_CONAN_SETTING_OS ${CONAN_SYSTEM_NAME}) + else() + message(FATAL_ERROR "cmake system ${CONAN_SYSTEM_NAME} is not supported by conan. Use one of ${CONAN_SUPPORTED_PLATFORMS}") + endif() + endif() + + get_property(_languages GLOBAL PROPERTY ENABLED_LANGUAGES) + if (";${_languages};" MATCHES ";CXX;") + set(LANGUAGE CXX) + set(USING_CXX 1) + elseif (";${_languages};" MATCHES ";C;") + set(LANGUAGE C) + set(USING_CXX 0) + else () + message(FATAL_ERROR "Conan: Neither C or C++ was detected as a language for the project. Unabled to detect compiler version.") + endif() + + if (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL GNU) + # using GCC + # TODO: Handle other params + string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) + list(GET VERSION_LIST 0 MAJOR) + list(GET VERSION_LIST 1 MINOR) + set(COMPILER_VERSION ${MAJOR}.${MINOR}) + if(${MAJOR} GREATER 4) + set(COMPILER_VERSION ${MAJOR}) + endif() + set(_CONAN_SETTING_COMPILER gcc) + set(_CONAN_SETTING_COMPILER_VERSION ${COMPILER_VERSION}) + if (USING_CXX) + conan_cmake_detect_gnu_libcxx(_LIBCXX) + set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) + endif () + elseif (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL AppleClang) + # using AppleClang + string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) + list(GET VERSION_LIST 0 MAJOR) + list(GET VERSION_LIST 1 MINOR) + set(_CONAN_SETTING_COMPILER apple-clang) + set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}.${MINOR}) + if (USING_CXX) + set(_CONAN_SETTING_COMPILER_LIBCXX libc++) + endif () + elseif (${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL Clang) + string(REPLACE "." ";" VERSION_LIST ${CMAKE_${LANGUAGE}_COMPILER_VERSION}) + list(GET VERSION_LIST 0 MAJOR) + list(GET VERSION_LIST 1 MINOR) + if(APPLE) + cmake_policy(GET CMP0025 APPLE_CLANG_POLICY_ENABLED) + if(NOT APPLE_CLANG_POLICY_ENABLED) + message(STATUS "Conan: APPLE and Clang detected. Assuming apple-clang compiler. Set CMP0025 to avoid it") + set(_CONAN_SETTING_COMPILER apple-clang) + set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}.${MINOR}) + else() + set(_CONAN_SETTING_COMPILER clang) + set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}.${MINOR}) + endif() + if (USING_CXX) + set(_CONAN_SETTING_COMPILER_LIBCXX libc++) + endif () + else() + set(_CONAN_SETTING_COMPILER clang) + if(${MAJOR} GREATER 7) + set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}) + else() + set(_CONAN_SETTING_COMPILER_VERSION ${MAJOR}.${MINOR}) + endif() + if (USING_CXX) + conan_cmake_detect_gnu_libcxx(_LIBCXX) + set(_CONAN_SETTING_COMPILER_LIBCXX ${_LIBCXX}) + endif () + endif() + elseif(${CMAKE_${LANGUAGE}_COMPILER_ID} STREQUAL MSVC) + set(_VISUAL "Visual Studio") + _get_msvc_ide_version(_VISUAL_VERSION) + if("${_VISUAL_VERSION}" STREQUAL "") + message(FATAL_ERROR "Conan: Visual Studio not recognized") + else() + set(_CONAN_SETTING_COMPILER ${_VISUAL}) + set(_CONAN_SETTING_COMPILER_VERSION ${_VISUAL_VERSION}) + endif() + + if(NOT _CONAN_SETTING_ARCH) + if (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "64") + set(_CONAN_SETTING_ARCH x86_64) + elseif (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "^ARM") + message(STATUS "Conan: Using default ARM architecture from MSVC") + set(_CONAN_SETTING_ARCH armv6) + elseif (MSVC_${LANGUAGE}_ARCHITECTURE_ID MATCHES "86") + set(_CONAN_SETTING_ARCH x86) + else () + message(FATAL_ERROR "Conan: Unknown MSVC architecture [${MSVC_${LANGUAGE}_ARCHITECTURE_ID}]") + endif() + endif() + + conan_cmake_detect_vs_runtime(_vs_runtime) + message(STATUS "Conan: Detected VS runtime: ${_vs_runtime}") + set(_CONAN_SETTING_COMPILER_RUNTIME ${_vs_runtime}) + + if (CMAKE_GENERATOR_TOOLSET) + set(_CONAN_SETTING_COMPILER_TOOLSET ${CMAKE_VS_PLATFORM_TOOLSET}) + elseif(CMAKE_VS_PLATFORM_TOOLSET AND (CMAKE_GENERATOR STREQUAL "Ninja")) + set(_CONAN_SETTING_COMPILER_TOOLSET ${CMAKE_VS_PLATFORM_TOOLSET}) + endif() + else() + message(FATAL_ERROR "Conan: compiler setup not recognized") + endif() + + # If profile is defined it is used + if(CMAKE_BUILD_TYPE STREQUAL "Debug" AND ARGUMENTS_DEBUG_PROFILE) + set(_SETTINGS -pr ${ARGUMENTS_DEBUG_PROFILE}) + elseif(CMAKE_BUILD_TYPE STREQUAL "Release" AND ARGUMENTS_RELEASE_PROFILE) + set(_SETTINGS -pr ${ARGUMENTS_RELEASE_PROFILE}) + elseif(CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo" AND ARGUMENTS_RELWITHDEBINFO_PROFILE) + set(_SETTINGS -pr ${ARGUMENTS_RELWITHDEBINFO_PROFILE}) + elseif(CMAKE_BUILD_TYPE STREQUAL "MinSizeRel" AND ARGUMENTS_MINSIZEREL_PROFILE) + set(_SETTINGS -pr ${ARGUMENTS_MINSIZEREL_PROFILE}) + elseif(ARGUMENTS_PROFILE) + set(_SETTINGS -pr ${ARGUMENTS_PROFILE}) + endif() + + if(NOT _SETTINGS OR ARGUMENTS_PROFILE_AUTO STREQUAL "ALL") + set(ARGUMENTS_PROFILE_AUTO arch build_type compiler compiler.version + compiler.runtime compiler.libcxx compiler.toolset) + endif() + + # Automatic from CMake + foreach(ARG ${ARGUMENTS_PROFILE_AUTO}) + string(TOUPPER ${ARG} _arg_name) + string(REPLACE "." "_" _arg_name ${_arg_name}) + if(_CONAN_SETTING_${_arg_name}) + set(_SETTINGS ${_SETTINGS} -s ${ARG}=${_CONAN_SETTING_${_arg_name}}) + endif() + endforeach() + + foreach(ARG ${ARGUMENTS_SETTINGS}) + set(_SETTINGS ${_SETTINGS} -s ${ARG}) + endforeach() + + message(STATUS "Conan: Settings= ${_SETTINGS}") + + set(${result} ${_SETTINGS} PARENT_SCOPE) +endfunction() + + +function(conan_cmake_detect_gnu_libcxx result) + # Allow -D_GLIBCXX_USE_CXX11_ABI=ON/OFF as argument to cmake + if(DEFINED _GLIBCXX_USE_CXX11_ABI) + if(_GLIBCXX_USE_CXX11_ABI) + set(${result} libstdc++11 PARENT_SCOPE) + return() + else() + set(${result} libstdc++ PARENT_SCOPE) + return() + endif() + endif() + + # Check if there's any add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0) + get_directory_property(defines DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} COMPILE_DEFINITIONS) + foreach(define ${defines}) + if(define STREQUAL "_GLIBCXX_USE_CXX11_ABI=0") + set(${result} libstdc++ PARENT_SCOPE) + return() + endif() + endforeach() + + # Use C++11 stdlib as default if gcc is 5.1+ + if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.1") + set(${result} libstdc++ PARENT_SCOPE) + else() + set(${result} libstdc++11 PARENT_SCOPE) + endif() +endfunction() + + +function(conan_cmake_detect_vs_runtime result) + string(TOUPPER ${CMAKE_BUILD_TYPE} build_type) + set(variables CMAKE_CXX_FLAGS_${build_type} CMAKE_C_FLAGS_${build_type} CMAKE_CXX_FLAGS CMAKE_C_FLAGS) + foreach(variable ${variables}) + string(REPLACE " " ";" flags ${${variable}}) + foreach (flag ${flags}) + if(${flag} STREQUAL "/MD" OR ${flag} STREQUAL "/MDd" OR ${flag} STREQUAL "/MT" OR ${flag} STREQUAL "/MTd") + string(SUBSTRING ${flag} 1 -1 runtime) + set(${result} ${runtime} PARENT_SCOPE) + return() + endif() + endforeach() + endforeach() + if(${build_type} STREQUAL "DEBUG") + set(${result} "MDd" PARENT_SCOPE) + else() + set(${result} "MD" PARENT_SCOPE) + endif() +endfunction() + + +macro(parse_arguments) + set(options BASIC_SETUP CMAKE_TARGETS UPDATE KEEP_RPATHS NO_OUTPUT_DIRS OUTPUT_QUIET NO_IMPORTS) + set(oneValueArgs CONANFILE DEBUG_PROFILE RELEASE_PROFILE RELWITHDEBINFO_PROFILE MINSIZEREL_PROFILE + PROFILE ARCH BUILD_TYPE INSTALL_FOLDER CONAN_COMMAND) + set(multiValueArgs REQUIRES OPTIONS IMPORTS SETTINGS BUILD ENV GENERATORS PROFILE_AUTO INSTALL_ARGS) + cmake_parse_arguments(ARGUMENTS "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) +endmacro() + +function(conan_cmake_install) + # Calls "conan install" + # Argument BUILD is equivalant to --build={missing, PkgName,...} or + # --build when argument is 'BUILD all' (which builds all packages from source) + # Argument CONAN_COMMAND, to specify the conan path, e.g. in case of running from source + # cmake does not identify conan as command, even if it is +x and it is in the path + parse_arguments(${ARGV}) + + if(CONAN_CMAKE_MULTI) + set(ARGUMENTS_GENERATORS ${ARGUMENTS_GENERATORS} cmake_multi) + else() + set(ARGUMENTS_GENERATORS ${ARGUMENTS_GENERATORS} cmake) + endif() + + set(CONAN_BUILD_POLICY "") + foreach(ARG ${ARGUMENTS_BUILD}) + if(${ARG} STREQUAL "all") + set(CONAN_BUILD_POLICY ${CONAN_BUILD_POLICY} --build) + break() + else() + set(CONAN_BUILD_POLICY ${CONAN_BUILD_POLICY} --build=${ARG}) + endif() + endforeach() + if(ARGUMENTS_CONAN_COMMAND) + set(conan_command ${ARGUMENTS_CONAN_COMMAND}) + else() + set(conan_command conan) + endif() + set(CONAN_OPTIONS "") + if(ARGUMENTS_CONANFILE) + set(CONANFILE ${CMAKE_CURRENT_SOURCE_DIR}/${ARGUMENTS_CONANFILE}) + # A conan file has been specified - apply specified options as well if provided + foreach(ARG ${ARGUMENTS_OPTIONS}) + set(CONAN_OPTIONS ${CONAN_OPTIONS} -o=${ARG}) + endforeach() + else() + set(CONANFILE ".") + endif() + if(ARGUMENTS_UPDATE) + set(CONAN_INSTALL_UPDATE --update) + endif() + if(ARGUMENTS_NO_IMPORTS) + set(CONAN_INSTALL_NO_IMPORTS --no-imports) + endif() + set(CONAN_INSTALL_FOLDER "") + if(ARGUMENTS_INSTALL_FOLDER) + set(CONAN_INSTALL_FOLDER -if=${ARGUMENTS_INSTALL_FOLDER}) + endif() + foreach(ARG ${ARGUMENTS_GENERATORS}) + set(CONAN_GENERATORS ${CONAN_GENERATORS} -g=${ARG}) + endforeach() + foreach(ARG ${ARGUMENTS_ENV}) + set(CONAN_ENV_VARS ${CONAN_ENV_VARS} -e=${ARG}) + endforeach() + set(conan_args install ${CONANFILE} ${settings} ${CONAN_ENV_VARS} ${CONAN_GENERATORS} ${CONAN_BUILD_POLICY} ${CONAN_INSTALL_UPDATE} ${CONAN_INSTALL_NO_IMPORTS} ${CONAN_OPTIONS} ${CONAN_INSTALL_FOLDER} ${ARGUMENTS_INSTALL_ARGS}) + + string (REPLACE ";" " " _conan_args "${conan_args}") + message(STATUS "Conan executing: ${conan_command} ${_conan_args}") + + if(ARGUMENTS_OUTPUT_QUIET) + set(OUTPUT_CONTROL OUTPUT_QUIET) + endif() + + execute_process(COMMAND ${conan_command} ${conan_args} + RESULT_VARIABLE return_code + OUTPUT_VARIABLE conan_output + ERROR_VARIABLE conan_output + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + + message(STATUS "${conan_output}") + + if(NOT "${return_code}" STREQUAL "0") + message(FATAL_ERROR "Conan install failed='${return_code}'") + endif() + +endfunction() + + +function(conan_cmake_setup_conanfile) + parse_arguments(${ARGV}) + if(ARGUMENTS_CONANFILE) + # configure_file will make sure cmake re-runs when conanfile is updated + configure_file(${ARGUMENTS_CONANFILE} ${ARGUMENTS_CONANFILE}.junk) + file(REMOVE ${CMAKE_CURRENT_BINARY_DIR}/${ARGUMENTS_CONANFILE}.junk) + else() + conan_cmake_generate_conanfile(${ARGV}) + endif() +endfunction() + +function(conan_cmake_generate_conanfile) + # Generate, writing in disk a conanfile.txt with the requires, options, and imports + # specified as arguments + # This will be considered as temporary file, generated in CMAKE_CURRENT_BINARY_DIR) + parse_arguments(${ARGV}) + set(_FN "${CMAKE_CURRENT_BINARY_DIR}/conanfile.txt") + + file(WRITE ${_FN} "[generators]\ncmake\n\n[requires]\n") + foreach(ARG ${ARGUMENTS_REQUIRES}) + file(APPEND ${_FN} ${ARG} "\n") + endforeach() + + file(APPEND ${_FN} ${ARG} "\n[options]\n") + foreach(ARG ${ARGUMENTS_OPTIONS}) + file(APPEND ${_FN} ${ARG} "\n") + endforeach() + + file(APPEND ${_FN} ${ARG} "\n[imports]\n") + foreach(ARG ${ARGUMENTS_IMPORTS}) + file(APPEND ${_FN} ${ARG} "\n") + endforeach() +endfunction() + + +macro(conan_load_buildinfo) + if(CONAN_CMAKE_MULTI) + set(_CONANBUILDINFO conanbuildinfo_multi.cmake) + else() + set(_CONANBUILDINFO conanbuildinfo.cmake) + endif() + # Checks for the existence of conanbuildinfo.cmake, and loads it + # important that it is macro, so variables defined at parent scope + if(EXISTS "${CMAKE_CURRENT_BINARY_DIR}/${_CONANBUILDINFO}") + message(STATUS "Conan: Loading ${_CONANBUILDINFO}") + include(${CMAKE_CURRENT_BINARY_DIR}/${_CONANBUILDINFO}) + else() + message(FATAL_ERROR "${_CONANBUILDINFO} doesn't exist in ${CMAKE_CURRENT_BINARY_DIR}") + endif() +endmacro() + + +macro(conan_cmake_run) + parse_arguments(${ARGV}) + + if(CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE AND NOT CONAN_EXPORTED + AND NOT ARGUMENTS_BUILD_TYPE) + set(CONAN_CMAKE_MULTI ON) + message(STATUS "Conan: Using cmake-multi generator") + else() + set(CONAN_CMAKE_MULTI OFF) + endif() + + if(NOT CONAN_EXPORTED) + conan_cmake_setup_conanfile(${ARGV}) + if(CONAN_CMAKE_MULTI) + foreach(CMAKE_BUILD_TYPE "Release" "Debug") + set(ENV{CONAN_IMPORT_PATH} ${CMAKE_BUILD_TYPE}) + conan_cmake_settings(settings ${ARGV}) + conan_cmake_install(SETTINGS ${settings} ${ARGV}) + endforeach() + set(CMAKE_BUILD_TYPE) + else() + conan_cmake_settings(settings ${ARGV}) + conan_cmake_install(SETTINGS ${settings} ${ARGV}) + endif() + endif() + + conan_load_buildinfo() + + if(ARGUMENTS_BASIC_SETUP) + foreach(_option CMAKE_TARGETS KEEP_RPATHS NO_OUTPUT_DIRS) + if(ARGUMENTS_${_option}) + if(${_option} STREQUAL "CMAKE_TARGETS") + list(APPEND _setup_options "TARGETS") + else() + list(APPEND _setup_options ${_option}) + endif() + endif() + endforeach() + conan_basic_setup(${_setup_options}) + endif() +endmacro() + +macro(conan_check) + # Checks conan availability in PATH + # Arguments REQUIRED and VERSION are optional + # Example usage: + # conan_check(VERSION 1.0.0 REQUIRED) + message(STATUS "Conan: checking conan executable in path") + set(options REQUIRED) + set(oneValueArgs VERSION) + cmake_parse_arguments(CONAN "${options}" "${oneValueArgs}" "" ${ARGN}) + + find_program(CONAN_CMD conan) + if(NOT CONAN_CMD AND CONAN_REQUIRED) + message(FATAL_ERROR "Conan executable not found!") + endif() + message(STATUS "Conan: Found program ${CONAN_CMD}") + execute_process(COMMAND ${CONAN_CMD} --version + OUTPUT_VARIABLE CONAN_VERSION_OUTPUT + ERROR_VARIABLE CONAN_VERSION_OUTPUT) + message(STATUS "Conan: Version found ${CONAN_VERSION_OUTPUT}") + + if(DEFINED CONAN_VERSION) + string(REGEX MATCH ".*Conan version ([0-9]+\.[0-9]+\.[0-9]+)" FOO + "${CONAN_VERSION_OUTPUT}") + if(${CMAKE_MATCH_1} VERSION_LESS ${CONAN_VERSION}) + message(FATAL_ERROR "Conan outdated. Installed: ${CONAN_VERSION}, \ + required: ${CONAN_VERSION_REQUIRED}. Consider updating via 'pip \ + install conan --upgrade'.") + endif() + endif() +endmacro() + +macro(conan_add_remote) + # Adds a remote + # Arguments URL and NAME are required, INDEX is optional + # Example usage: + # conan_add_remote(NAME bincrafters INDEX 1 + # URL https://api.bintray.com/conan/bincrafters/public-conan) + set(oneValueArgs URL NAME INDEX) + cmake_parse_arguments(CONAN "" "${oneValueArgs}" "" ${ARGN}) + + if(DEFINED CONAN_INDEX) + set(CONAN_INDEX_ARG "-i ${CONAN_INDEX}") + endif() + + message(STATUS "Conan: Adding ${CONAN_NAME} remote repositoy (${CONAN_URL})") + execute_process(COMMAND ${CONAN_CMD} remote add ${CONAN_NAME} ${CONAN_URL} + ${CONAN_INDEX_ARG} -f) +endmacro() diff --git a/cmake/ctulu.cmake b/cmake/ctulu.cmake new file mode 100644 index 0000000..ba2c14c --- /dev/null +++ b/cmake/ctulu.cmake @@ -0,0 +1,8 @@ +list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}) + +include(ctulu_private) +include(ctulu_target) +include(ctulu_utils) +include(ctulu_clang-format) +include(ctulu_warnings) +include(ctulu_setup_cmake) diff --git a/cmake/ctulu_clang-format.cmake b/cmake/ctulu_clang-format.cmake new file mode 100644 index 0000000..8be0c64 --- /dev/null +++ b/cmake/ctulu_clang-format.cmake @@ -0,0 +1,70 @@ +## target_generate_clang_format(target_name clang_format_target_name [FILES files...] [DIRS [NORECURSE] directories...]) +# Generate a format target (${target_name}) which format the given files +# The generated target lanch clang-format on all the given files with -style=file +# {value} [in] target_name: Name of the format target +# {value} [in] clang_format_target_name: Name of the generated target +# {value} [in] files: Sources files +# {value} [in] directories: Directories from which sources files are generated +function(ctulu_generate_clang_format clang_format_target_name) + find_program(CLANG_FORMAT clang-format + NAMES clang-format-9 clang-format-8 clang-format-7 clang-format-6) + + set(options NORECURSE) + set(multiValueArgs DIRS FILES) + cmake_parse_arguments(ctulu_generate_clang_format "${options}" "" "${multiValueArgs}" ${ARGN}) + + if(${CLANG_FORMAT} STREQUAL CLANG_FORMAT-NOTFOUND) + message(WARNING "Ctulu -- clang-format not found, ${format-target} not generated") + return() + else() + message(STATUS "Ctulu -- clang-format found: ${CLANG_FORMAT}") + endif() + + if(ctulu_generate_clang_format_DIRS) + foreach(it ${ctulu_generate_clang_format_DIRS}) + if(ctulu_generate_clang_format_NORECURSE) + ctulu_list_files(tmp_files ${it} NORECURSE) + else() + ctulu_list_files(tmp_files ${it}) + endif() + set(ctulu_generate_clang_format_FILES ${ctulu_generate_clang_format_FILES} ${tmp_files}) + endforeach() + endif() + + add_custom_target( + ${clang_format_target_name} + COMMAND "${CLANG_FORMAT}" -style=file -i ${ctulu_generate_clang_format_FILES} + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + VERBATIM) + + message(STATUS "Ctulu -- Format target ${clang_format_target_name} generated") +endfunction() + +## target_generate_clang_format(target) +# Generate a format target named ${clang_format_target_name} for the target (${target_name}). +# The generated target lanch clang-format on all the target sources with -style=file +# {value} [in] target: Target from wich generate format target +# {value} [in] clang_format_target_name: Name of the generated target +function(ctulu_generate_clang_format_from_target target_name clang_format_target_name) + if(NOT TARGET ${target_name}) + message(FATAL_ERROR "Ctulu -- ${target_name} is not a target)") + endif() + + find_program(CLANG_FORMAT clang-format + NAMES clang-format-9 clang-format-8 clang-format-7 clang-format-6) + if(${CLANG_FORMAT} STREQUAL CLANG_FORMAT-NOTFOUND) + message(WARNING "Ctulu -- clang-format not found, ${clang_format_target_name} not generated") + return() + else() + message(STATUS "Ctulu -- clang-format found: ${CLANG_FORMAT}") + endif() + + get_target_property(target_sources ${target_name} SOURCES) + add_custom_target( + ${clang_format_target_name} + COMMAND "${CLANG_FORMAT}" -style=file -i ${target_sources} + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + VERBATIM) + + message(STATUS "Ctulu -- Format target \"${clang_format_target_name}\" generated") +endfunction() \ No newline at end of file diff --git a/cmake/ctulu_private.cmake b/cmake/ctulu_private.cmake new file mode 100644 index 0000000..fa000fc --- /dev/null +++ b/cmake/ctulu_private.cmake @@ -0,0 +1,160 @@ +######################################## +## Private function of ctulu library. ## +######################################## + +#ctulu_check_options_coherence(target_name group_name +function(ctulu_check_options_coherence target_name group_name) + set(options EXECUTABLE TEST SHARED STATIC INTERFACE) + set(oneValueArgs C CXX W_LEVEL) + set(multiValueArgs FILES DIRS PRIVATE_INCLUDES PUBLIC_INCLUDES INTERFACE_INCLUDES EXT_INCLUDES) + cmake_parse_arguments(ctulu_check_options_coherence "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(target_types_options) + foreach(it ${options}) + set(insert_list target_types_options) + if("${it}" STREQUAL "NO_WARNINGS" OR "${it}" STREQUAL "LOW_WARNINGS") + set(insert_list warning_levels) + endif() + if(${ctulu_check_options_coherence_${it}}) + list(APPEND ${insert_list} ${it}) + endif() + endforeach() + + set(options_list target_types_options warning_levels) + + foreach(it ${options_list}) + list(LENGTH ${it} len) + if(${len} GREATER 1) + string(REPLACE ";" "," print_list "${${it}}") + message(FATAL_ERROR "Ctulu -- When creating ${group_name}/${target_name}: \"${print_list}\" can't be activated at the same time.") + endif() + endforeach() + + if(ctulu_check_options_coherence_HEADER_ONLY) + list(LENGTH ctulu_check_options_coherence_PRIVATE_INCLUDES len) + if(${len} GREATER 0) + message(FATAL_ERROR "Ctulu -- When creating ${group_name}/${target_name}: Header only libraries can't have private includes") + endif() + endif() +endfunction() + +#ctulu_list_files(output dir [NORECURSE]) +function(ctulu_list_files output dir) + set(options NORECURSE) + set(oneValueArgs) + set(multiValueArgs) + cmake_parse_arguments(ctulu_list_files "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(glob GLOB_RECURSE) + if(ctulu_list_files_NORECURSE) + set(glob GLOB) + endif() + + set(patterns + "${dir}/*.c" + "${dir}/*.C" + "${dir}/*.c++" + "${dir}/*.cc" + "${dir}/*.cpp" + "${dir}/*.cxx" + "${dir}/*.h" + "${dir}/*.hh" + "${dir}/*.h++" + "${dir}/*.hpp" + "${dir}/*.hxx" + "${dir}/*.txx" + "${dir}/*.tpp") + + file(${glob} tmp_files ${patterns}) + set(${output} ${tmp_files} PARENT_SCOPE) +endfunction() + +function(ctulu_target_include_wrapper target_name) + set(options SYSTEM) + set(oneValueArgs) + set(multiValueArgs) + cmake_parse_arguments(ctulu_target_include_wrapper "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(ctulu_target_include_wrapper_SYSTEM) + ctulu_target_include_directories_impl(${target_name} ${ctulu_target_include_wrapper_UNPARSED_ARGUMENTS} SYSTEM) + else() + ctulu_target_include_directories_impl(${target_name} ${ctulu_target_include_wrapper_UNPARSED_ARGUMENTS}) + endif() +endfunction() + +#ctulu_create_file_architecture(output dirs... [NORECURSE]) +function(ctulu_create_file_architecture output) + set(options NORECURSE) + set(oneValueArgs) + set(multiValueArgs) + cmake_parse_arguments(ctulu_create_file_architecture "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(files) + foreach(it ${ctulu_create_file_architecture_UNPARSED_ARGUMENTS}) + if(IS_DIRECTORY ${it}) + if(ctulu_create_file_architecture_NORECURSE) + ctulu_list_files(tmp_files ${it} NORECURSE) + else() + ctulu_list_files(tmp_files ${it}) + endif() + list(APPEND files ${tmp_files}) + get_filename_component(parent_dir ${it} DIRECTORY) + ctulu_assign_files(Sources "${parent_dir}" ${tmp_files}) + else() + list(APPEND files ${it}) + get_filename_component(dir ${it} DIRECTORY) + ctulu_assign_files(Sources "${dir}" ${it}) + endif() + endforeach() + set(${output} ${files} PARENT_SCOPE) +endfunction() + +function(ctulu_assign_files group root) + foreach(it ${ARGN}) + get_filename_component(dir ${it} PATH) + file(RELATIVE_PATH relative ${root} ${dir}) + set(local ${group}) + if(NOT "${relative}" STREQUAL "") + set(local "${group}/${relative}") + endif() + # replace '/' and '\' (and repetitions) by '\\' + string(REGEX REPLACE "[\\\\\\/]+" "\\\\\\\\" local ${local}) + source_group("${local}" FILES ${it}) + endforeach() +endfunction() + +function(ctulu_target_include_directories_impl target_name) + set(options SYSTEM INTERFACE PUBLIC PRIVATE) + set(oneValueArgs) + set(multiValueArgs) + cmake_parse_arguments(ctulu_target_include_directories_impl "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + list(LENGTH ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS size) + if(NOT ${size} GREATER 0) + return() + endif() + + list(REMOVE_DUPLICATES ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS) + list(SORT ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS) + + if(ctulu_target_include_directories_impl_INTERFACE) + if(ctulu_target_include_directories_impl_SYSTEM) + target_include_directories(${target_name} SYSTEM INTERFACE ${ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS}) + else() + target_include_directories(${target_name} INTERFACE ${ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS}) + endif() + elseif(ctulu_target_include_directories_impl_PUBLIC) + if(ctulu_target_include_directories_impl_SYSTEM) + target_include_directories(${target_name} SYSTEM PUBLIC ${ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS}) + else() + target_include_directories(${target_name} PUBLIC ${ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS}) + endif() + else() + if(ctulu_target_include_directories_impl_SYSTEM) + target_include_directories(${target_name} SYSTEM PRIVATE ${ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS}) + else() + target_include_directories(${target_name} PRIVATE ${ctulu_target_include_directories_impl_UNPARSED_ARGUMENTS}) + endif() + endif() +endfunction() + diff --git a/cmake/ctulu_setup_cmake.cmake b/cmake/ctulu_setup_cmake.cmake new file mode 100644 index 0000000..2bdd5dd --- /dev/null +++ b/cmake/ctulu_setup_cmake.cmake @@ -0,0 +1,30 @@ +# ctulu_setup_cmake([DISABLE_SRC_BUILD] [REQUIRE_STANDARD languages...]) +# Configure some variables of CMake. +# {option} [in] DISABLE_SRC_BUILD: Set ${CMAKE_DISABLE_SOURCE_CHANGES} and ${CMAKE_DISABLE_IN_SOURCE_BUILD} to ON. +# {option} [in] languages: Disable compiler extension and requires exact standard the given language. +# (C and CXX supported) +function(ctulu_setup_cmake) + set(options DISABLE_SRC_BUILD) + set(oneValueArgs) + set(multiValueArgs REQUIRE_STANDARD) + cmake_parse_arguments(ctulu_setup_cmake "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(ctulu_setup_cmake_REQUIRE_STANDARD) + message(STATUS "Ctulu -- Disabling Compiler extension") + endif() + + foreach(language ${ctulu_setup_cmake_REQUIRE_STANDARD}) + # disable compiler specific extensions + set(CMAKE_${language}_EXTENSIONS OFF PARENT_SCOPE) + # set STANDARD as a requirement + set(CMAKE_${language}_STANDARD_REQUIRED ON PARENT_SCOPE) + endforeach() + + if (ctulu_setup_cmake_DISABLE_SRC_BUILD) + message(STATUS "Ctulu -- Disabling build in source") + + # don't allow to build in sources otherwise a makefile not generated by CMake can be overridden + set(CMAKE_DISABLE_SOURCE_CHANGES ON PARENT_SCOPE) + set(CMAKE_DISABLE_IN_SOURCE_BUILD ON PARENT_SCOPE) + endif() +endfunction() \ No newline at end of file diff --git a/cmake/ctulu_target.cmake b/cmake/ctulu_target.cmake new file mode 100644 index 0000000..764b78a --- /dev/null +++ b/cmake/ctulu_target.cmake @@ -0,0 +1,113 @@ +## ctulu_create_target(target_name group_name [FILES files...] [DIRS [NORECURSE] dirs...] +## [PRIVATE_INCLUDES [SYSTEM] pr_includes...] [PUBLIC_INCLUDES [SYSTEM] pu_includes...] +## [INTERFACE_INCLUDES [SYSTEM] in_includes...] [EXT_INCLUDES ext_includes...] +## [C c_version] [CXX cxx_version] +## [] +## [W_LEVEL wlevel]) +# Make a new target with the input options +# By default targets are static libraries. +# {value} [in] target_name: Name of the target. +# {value} [in] group_name: Group of the target. +# {value} [in] files: Files of the target. Please note that if your want the folder in VS +# you need to use ${dirs}. +# {value} [in] dirs Add all files in the directory. Use "NORECURSE" if you don't want to include +# sources from the subdirectories. +# {value} [in] pr_includes: Private includes. Use system to disable warning for these includes. +# {value} [in] pu_includes: Public includes. Use system to disable warning for these includes. +# {value} [in] in_includes: Interface includes. Use system to disable warning for these includes. +# {value} [in] ext_includes: Externals includes. Shortcut for "PRIVATE_INCLUDES SYSTEM ${ext_includes}" +# {value} [in] c_version: C Standard to use. +# {value} [in] cxx_version: C++ Standard to use. +# {option} [in] target_type: Defines the type of the target. Could be one of +# {value} [in] wlevel Level of warning to activate. (See ctulu_add_warning_from_file function in ctulu_warnings.cmake) +function(ctulu_create_target target_name group_name) + message(STATUS "Ctulu -- Configuring \"${group_name}/${target_name}\"") + + set(options EXECUTABLE TEST SHARED STATIC INTERFACE) + set(oneValueArgs C CXX W_LEVEL) + set(multiValueArgs FILES DIRS PRIVATE_INCLUDES PUBLIC_INCLUDES INTERFACE_INCLUDES EXT_INCLUDES) + cmake_parse_arguments(ctulu_create_target "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + ctulu_check_options_coherence(${target_name} ${group_name} ${ARGN}) + + if(NOT ctulu_create_target_W_LEVEL) + set(ctulu_create_target_W_LEVEL "0") + endif() + + set(ctulu_${target_name}_warning_level ${ctulu_create_target_W_LEVEL} CACHE INTERNAL "Level of warning used for the target ${target_name}" FORCE) + + if(ctulu_create_target_EXECUTABLE OR ctulu_create_target_TEST) + add_executable(${target_name}) + set(ctulu_target_list_${target_name}_TYPE "EXECUTABLE" PARENT_SCOPE) + + if(ctulu_create_target_TEST) + set(ctulu_target_list_${target_name}_TYPE "TEST" PARENT_SCOPE) + add_test(NAME ${target_name} COMMAND ${target_name}) + endif() + elseif(ctulu_create_target_INTERFACE) + set(ctulu_target_list_${target_name}_TYPE "INTERFACE" PARENT_SCOPE) + add_library(${target_name} INTERFACE) + elseif(ctulu_create_target_SHARED) + set(ctulu_target_list_${target_name}_TYPE "SHARED" PARENT_SCOPE) + add_library(${target_name} SHARED) + else() + set(ctulu_target_list_${target_name}_TYPE "STATIC" PARENT_SCOPE) + add_library(${target_name} STATIC) + endif() + + if(NOT ctulu_create_target_INTERFACE) + ctulu_target_language(${target_name} C ${ctulu_create_target_C} CXX ${ctulu_create_target_CXX}) + endif() + + if(ctulu_create_target_FILES OR ctulu_create_target_UNPARSED_ARGUMENTS) + message(STATUS "Ctulu -- Adding sources") + target_sources(${target_name} PRIVATE ${ctulu_create_target_FILES} ${ctulu_create_target_UNPARSED_ARGUMENTS}) + endif() + if(ctulu_create_target_DIRS) + message(STATUS "Ctulu -- Adding sources from directories") + ctulu_create_file_architecture(sources ${ctulu_create_target_DIRS}) + target_sources(${target_name} PRIVATE ${sources}) + endif() + + ctulu_target_include_directories(${target_name} + ${ctulu_create_target_EXT_INCLUDES} + INTERFACE ${ctulu_create_target_INTERFACE_INCLUDES} + PUBLIC ${ctulu_create_target_PUBLIC_INCLUDES} + PRIVATE ${ctulu_create_target_PRIVATE_INCLUDES}) + + source_group(CMake REGULAR_EXPRESSION ".*[.](cmake|rule)$") + source_group(CMake FILES "CMakeLists.txt") + if(NOT ctulu_create_target_INTERFACE) + set_target_properties(${target_name} PROPERTIES FOLDER ${group_name}) + else() + # Beautiful workaround + if(MSVC AND ctulu_create_target_INTERFACE_INCLUDES) + ctulu_list_files(tmp_files ${ctulu_create_target_INTERFACE_INCLUDES}) + add_custom_target("${target_name}.headers" SOURCES ${tmp_files}) + endif() + endif() +endfunction() + +# ctulu_target_compiler_flag(target_name w_flag language configuration) +# Add the given flag (${w_flag}) to the target (${target_name}) for the given configuration (${configuration}) +# for the given language (${language}) +# {value} [in] target_name: Name of the target +# {value} [in] w_flag: File where the warning are +# {value} [in] language: Language of the warning (C or CXX) +# {value} [in] configuration: Build type +function(ctulu_target_compiler_flag target_name w_flag language configuration) + string(TOUPPER ${language} language) + if(language STREQUAL "CXX") + check_cxx_compiler_flag(${w_flag} has${w_flag}) + if(has${w_flag}) + target_compile_options(${target_name} PRIVATE "$<$,$>:${w_flag}>") + endif() + elseif(language STREQUAL "C") + check_c_compiler_flag(${w_flag} has${w_flag}) + if(has${w_flag}) + target_compile_options(${target_name} PRIVATE "$<$,$>:${w_flag}>") + endif() + else() + message(WARNING "Ctulu -- For target \"${target_name}:${configuration}\" language \"${language}\" unknown") + endif() +endfunction() diff --git a/cmake/ctulu_utils.cmake b/cmake/ctulu_utils.cmake new file mode 100644 index 0000000..66312df --- /dev/null +++ b/cmake/ctulu_utils.cmake @@ -0,0 +1,89 @@ +## ctulu_target_include_directories(target_name [PRIVATE [SYSTEM] pr_includes...] +## [PUBLIC [SYSTEM] pu_includes...] [INTERFACE [SYSTEM] in_includes...] [EXT_INCLUDES ext_includes...]) +# Set includes for the given target +# {value} [in] pr_includes: Private includes. Use system to disable warning for these includes. +# {value} [in] pu_includes: Public includes. Use system to disable warning for these includes. +# {value} [in] in_includes: Interface includes. Use system to disable warning for these includes. +# {value} [in] ext_includes: Externals includes. Shortcut for "PRIVATE_INCLUDES SYSTEM ${ext_includes}" +function(ctulu_target_include_directories target_name) + set(options) + set(oneValueArgs) + set(multiValueArgs PRIVATE PUBLIC EXT_INCLUDES INTERFACE) + cmake_parse_arguments(ctulu_target_include_directories "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + ctulu_target_include_wrapper(${target_name} ${ctulu_target_include_directories_EXT_INCLUDES} SYSTEM) + ctulu_target_include_wrapper(${target_name} ${ctulu_target_include_directories_PRIVATE} ${ctulu_target_include_directories_UNPARSED_ARGUMENTS} PRIVATE) + ctulu_target_include_wrapper(${target_name} ${ctulu_target_include_directories_PUBLIC} PUBLIC) + ctulu_target_include_wrapper(${target_name} ${ctulu_target_include_directories_INTERFACE} INTERFACE) + + if(NOT "${ctulu_target_list_${target_name}_TYPE}" STREQUAL "INTERFACE") + ctulu_create_file_architecture(ignored + ${ctulu_target_include_directories_EXT_INCLUDES} + ${ctulu_target_include_directories_PRIVATE} + ${ctulu_target_include_directories_PUBLIC}) + else() + if(MSVC OR XCODE) + ctulu_list_files(tmp_files ${ctulu_target_include_directories_INTERFACE}) + add_custom_target("${target_name}.headers" SOURCES ${tmp_files}) + endif() + endif() +endfunction() + +## ctulu_target_language(target_name [C c_version] [CXX cxx_version]) +## Set language standard for the given target. +# {value} [in] target_name: Name of the target +# {value} [in] c_version: C Standard to use. +# {value} [in] cxx_version: C++ Standard to use. +function(ctulu_target_language target_name) + set(options) + set(oneValueArgs C CXX) + set(multiValueArgs) + cmake_parse_arguments(ctulu_target_language "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + foreach(it ${oneValueArgs}) + if(ctulu_target_language_${it}) + message(STATUS "Ctulu -- \"${target_name}\" ${it} standard version: ${ctulu_target_language_${it}}") + set_property(TARGET ${target_name} PROPERTY ${it}_STANDARD ${ctulu_target_language_${it}}) + endif() + endforeach() +endfunction() + +## ctulu_target_sources(target_name files... [FILES files...] [DIRS dirs...] +## [] [NORECURSE]) +# Set sources for the given target +# {value} [in] files: Files of the target. Please note that if your want the folder in VS +# you need to use ${dirs}. +# {value} [in] dirs Add all files in the directory. Use "NORECURSE" if you don't want to include +# sources from the subdirectories. +# {option} [in] visibility Set the visibility of the sources. Could be one of +function(ctulu_target_sources target_name) + set(options PRIVATE PUBLIC INTERFACE NORECURSE) + set(oneValueArgs) + set(multiValueArgs FILES DIRS) + cmake_parse_arguments(ctulu_target_sources "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + set(target_option PRIVATE) + foreach(it ${options}) + if("${it}" STREQUAL "NORECURSE") + continue() + endif() + if(ctulu_target_sources_${it}) + set(target_option ${it}) + endif () + endforeach() + + if(ctulu_target_sources_FILES OR ctulu_target_sources_UNPARSED_ARGUMENTS) + message(STATUS "Ctulu -- Adding sources from files") + target_sources(${target_name} ${target_option} ${ctulu_target_sources_FILES} ${ctulu_target_sources_UNPARSED_ARGUMENTS}) + endif() + + if(ctulu_target_sources_DIRS) + message(STATUS "Ctulu -- Adding sources from directories") + if(ctulu_target_sources_NORECURSE) + ctulu_create_file_architecture(sources ${ctulu_target_sources_DIRS} NORECURSE) + else() + ctulu_create_file_architecture(sources ${ctulu_target_sources_DIRS}) + endif() + target_sources(${target_name} ${target_option} ${sources}) + endif() +endfunction() \ No newline at end of file diff --git a/cmake/ctulu_warnings.cmake b/cmake/ctulu_warnings.cmake new file mode 100644 index 0000000..e43dd04 --- /dev/null +++ b/cmake/ctulu_warnings.cmake @@ -0,0 +1,89 @@ +include(CheckCXXCompilerFlag) +include(CheckCCompilerFlag) + +## ctulu_target_warning_from_file(target_name warning_file) +## Add warnings from file to the given target. +# {value} [in] target_name: Name of the target +# {value} [in] warning_file: File where the warning are. +# If the file is updated, cmake when a new build is launched. +function(ctulu_target_warning_from_file target_name warning_file) + get_filename_component(warning_filename ${warning_file} NAME) + configure_file(${warning_file} ${CMAKE_BINARY_DIR}/ctulu/${warning_filename} COPYONLY) + file(STRINGS ${warning_file} warning_file_content) + set(configurations) + set(languages) + + message(STATUS "Ctulu -- Stating parsing warnings in \"${warning_file}\"") + foreach(line ${warning_file_content}) + # 'begin-line' 'spaces*' [ 'spaces*' 'string' 'spaces*' 'number' ] 'spaces*' 'end-line' + string(REGEX MATCH "^[ ]*\\[[ ]*([A-Za-z0-9_]*)[ ]*[ ]*][ ]*$" tmp_line ${line}) + if(tmp_line) + if(compiler) + message(STATUS "Ctulu -- Parsing warnings for \"${compiler}\" finished") + endif() + message(STATUS "Ctulu -- Parsing warnings for \"${CMAKE_MATCH_1}\"") + set(compiler ${CMAKE_MATCH_1}) + continue() + else() + if(NOT DEFINED warning_printed AND NOT DEFINED compiler) + message(WARNING "Ctulu -- Please specify compiler first.\nLines will be ignored until compiler is specified.") + set(warning_printed ON) + endif() + endif() + + # 'begin-line' 'spaces*' [ 'spaces*' 'string*' 'spaces*' level 'spaces*' 'number' ] 'spaces*' 'end-line' + string(REGEX MATCH "^[ ]*\\[[ ]*([A-Za-z0-9_;]*)[ ]*level[ ]*([0-9]*)[ ]*[:]?[ ]*([A-Za-z0-9_;]*)[ ]*][ ]*$" tmp_line ${line}) + if(tmp_line) + set(list_to_inserts) + set(current_languages ${CMAKE_MATCH_1}) + set(warning_level ${CMAKE_MATCH_2}) + set(build_type_list ${CMAKE_MATCH_3}) + + if(NOT current_languages) + set(current_languages c cxx) + endif() + + foreach(language ${current_languages}) + if(NOT ${language} IN_LIST languages) + list(APPEND languages ${language}) + endif() + + foreach(build_type ${build_type_list}) + string(TOUPPER ${build_type} build_type) + if(NOT ${build_type} IN_LIST configurations) + list(APPEND configurations ${build_type}) + endif() + if(NOT ${warning_level} IN_LIST ctulu_${target_name}_${compiler}_${language}_${build_type}_warnings_levels) + list(APPEND ctulu_${target_name}_${compiler}_${language}_${build_type}_warnings_levels ${warning_level}) + endif() + + list(APPEND list_to_inserts ctulu_${target_name}_${compiler}_${language}_${build_type}_warnings_level_${warning_level}) + endforeach() + endforeach() + else() + string(REGEX MATCH "^[ ]*#" tmp_line ${line}) + if(NOT tmp_line) + string(STRIP ${line} line) + foreach(list ${list_to_inserts}) + list(APPEND ${list} ${line}) + endforeach() + endif() + endif() + endforeach() + if(compiler) + message(STATUS "Ctulu -- Parsing warnings for \"${compiler}\" finished") + endif() + message(STATUS "Ctulu -- Parsing finished") + + foreach(language ${languages}) + foreach(configuration ${configurations}) + foreach(it ${ctulu_${target_name}_gcc_${language}_${configuration}_warnings_levels}) + if(ctulu_${target_name}_warning_level GREATER_EQUAL it) + foreach(warning ${ctulu_${target_name}_gcc_${language}_${configuration}_warnings_level_${it}}) + ctulu_target_compiler_flag(${target_name} ${warning} ${language} ${configuration}) + endforeach() + endif() + endforeach() + endforeach() + endforeach() +endfunction() diff --git a/cmake/get_external_dependency.cmake b/cmake/get_external_dependency.cmake deleted file mode 100644 index 9d941bc..0000000 --- a/cmake/get_external_dependency.cmake +++ /dev/null @@ -1,20 +0,0 @@ - -function(get_external_dependency projectname external_file) - configure_file(${external_file} ${projectname}-download/CMakeLists.txt) - execute_process(COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . - RESULT_VARIABLE result - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${projectname}-download) - if(result) - message(FATAL_ERROR "CMake step failed: ${result}") - endif() - execute_process(COMMAND ${CMAKE_COMMAND} --build . - RESULT_VARIABLE result - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/${projectname}-download) - if(result) - message(FATAL_ERROR "Build step failed: ${result}") - endif() - - add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/${projectname}-src - ${CMAKE_CURRENT_BINARY_DIR}/${projectname}-build - EXCLUDE_FROM_ALL) -endfunction() \ No newline at end of file diff --git a/cmake/setup_output_dirs.cmake b/cmake/setup_output_dirs.cmake new file mode 100644 index 0000000..cbcea00 --- /dev/null +++ b/cmake/setup_output_dirs.cmake @@ -0,0 +1,17 @@ +# place generated binaries in bin +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/bin/) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/bin/) +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/bin/) + +# place generated libs in lib +file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/lib/) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${CMAKE_BINARY_DIR}/lib/) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/lib/) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${CMAKE_BINARY_DIR}/lib/) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/lib/) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${CMAKE_BINARY_DIR}/lib/) \ No newline at end of file diff --git a/cmake/warnings.txt b/cmake/warnings.txt new file mode 100644 index 0000000..9fb6ed5 --- /dev/null +++ b/cmake/warnings.txt @@ -0,0 +1,159 @@ +[gcc] + [cxx level 1 Debug;Release] + -pedantic + -Werror=pedantic + -Wall + -Wextra + + [cxx level 2 Debug] + ## Extra flags: + -Wdouble-promotion + -Wnull-dereference + -Wimplicit-fallthrough + -Wif-not-aligned + -Wmissing-include-dirs + -Wswitch-bool + -Wswitch-unreachable + -Walloc-zero + -Wduplicated-branches + -Wduplicated-cond + -Wfloat-equal + -Wshadow + -Wundef + -Wexpansion-to-defined + #-Wunused-macros + -Wcast-qual + -Wcast-align + -Wwrite-strings + -Wconversion + -Wsign-conversion + -Wdate-time + -Wextra-semi + -Wlogical-op + -Wmissing-declarations + -Wredundant-decls + -Wrestrict + #-Winline + -Winvalid-pch + -Woverlength-strings + -Wformat=2 + -Wformat-signedness + -Winit-self + -Wold-style-cast + + ## Optimisation dependant flags + -Wstrict-overflow=5 + + ## Info flags + #-Winvalid-pch + #-Wvolatile-register-var + -Wdisabled-optimization + #-Woverlength-strings + #-Wunsuffixed-float-constants + #-Wvector-operation-performance + + ## Apocalypse flags: + #-Wsystem-headers + #-Werror + + ## Exit on first error + -Wfatal-errors + +[clang] + [cxx level 1 Release;Debug] + ## Base flags: + -pedantic + -Werror=pedantic + -Wall + -Wextra + + [cxx level 2 Debug] + ## Extra flags: + -Wbad-function-cast + -Wcomplex-component-init + -Wconditional-uninitialized + -Wcovered-switch-default + -Wcstring-format-directive + -Wdelete-non-virtual-dtor + -Wdeprecated + -Wdollar-in-identifier-extension + -Wdouble-promotion + -Wduplicate-enum + -Wduplicate-method-arg + -Wembedded-directive + -Wexpansion-to-defined + -Wextended-offsetof + -Wfloat-conversion + -Wfloat-equal + -Wfor-loop-analysis + -Wformat-pedantic + -Wgnu + -Wimplicit-fallthrough + -Winfinite-recursion + -Winvalid-or-nonexistent-directory + -Wkeyword-macro + -Wmain + -Wmethod-signatures + -Wmicrosoft + -Wmismatched-tags + -Wmissing-field-initializers + -Wmissing-method-return-type + -Wmissing-prototypes + -Wmissing-variable-declarations + -Wnested-anon-types + -Wnon-virtual-dtor + -Wnonportable-system-include-path + -Wnull-pointer-arithmetic + -Wnullability-extension + -Wold-style-cast + -Woverriding-method-mismatch + -Wpacked + -Wpedantic + -Wpessimizing-move + -Wredundant-move + -Wreserved-id-macro + -Wself-assign + -Wself-move + -Wsemicolon-before-method-body + -Wshadow + -Wshadow-field + -Wshadow-field-in-constructor + -Wshadow-uncaptured-local + -Wshift-sign-overflow + -Wshorten-64-to-32 + #-Wsign-compare + #-Wsign-conversion + -Wsigned-enum-bitfield + -Wstatic-in-inline + #-Wstrict-prototypes + #-Wstring-conversion + #-Wswitch-enum + -Wtautological-compare + -Wtautological-overlap-compare + -Wthread-safety + -Wundefined-reinterpret-cast + -Wuninitialized + #-Wunknown-pragmas + -Wunreachable-code + -Wunreachable-code-aggressive + #-Wunused + -Wunused-const-variable + -Wunused-lambda-capture + -Wunused-local-typedef + -Wunused-parameter + -Wunused-private-field + -Wunused-template + -Wunused-variable + -Wused-but-marked-unused + -Wzero-as-null-pointer-constant + -Wzero-length-array + + ## Lifetime + -Wlifetime + + ## Info flags + -Wcomma + -Wcomment + + ## Exit on first error + -Wfatal-errors \ No newline at end of file diff --git a/example/main.cpp b/example/main.cpp deleted file mode 100644 index 01e85d3..0000000 --- a/example/main.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -int main() -{ - constexpr int nbrThreads = 64; - - { - std::shared_future f; - std::promise ready_promise; - f = std::shared_future(ready_promise.get_future()); - - wf::unordered_map m(8); - - std::array threads; - std::array insertion_times = {}; - for (std::size_t i = 0; i < nbrThreads; ++i) - { - threads[i] = std::thread([&m, i, &f, &insertion_times]() { - f.wait(); - - clock_t t_start = std::clock(); - bool inserted = m.insert(i, i); - clock_t t_end = std::clock(); - insertion_times[i] = 1000.0 * static_cast(t_end - t_start) / CLOCKS_PER_SEC; - if (!inserted) - { - printf("%zu, %d\n", i, inserted); - } - }); - } - - { - using namespace std::chrono_literals; - std::this_thread::sleep_for(1s); - } - - ready_promise.set_value(); - - for (auto& t: threads) - { - t.join(); - } - - double max = *std::max_element(insertion_times.begin(), insertion_times.end()); - std::cout << "Max: " << max << "ms\n"; - std::cout << "Mean: " - << std::accumulate(insertion_times.begin(), insertion_times.end(), 0.0) / insertion_times.size() - << "ms\n"; - std::cout << "Min: " << *std::min_element(insertion_times.begin(), insertion_times.end()) << "ms\n"; - } - - return 0; -} \ No newline at end of file diff --git a/examples/unordered_map_example.cpp b/examples/unordered_map_example.cpp new file mode 100644 index 0000000..9fcd8e4 --- /dev/null +++ b/examples/unordered_map_example.cpp @@ -0,0 +1,63 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +constexpr int nbr_threads = 64; + +int main() +{ + std::promise ready_promise; + std::shared_future f = {ready_promise.get_future()}; + + wfc::unordered_map m(8, nbr_threads, nbr_threads); + + std::array threads; + std::array insertion_times = {}; + for (std::size_t i = 0; i < nbr_threads; ++i) + { + threads[i] = std::thread([&m, i, &f, &insertion_times]() { + f.wait(); + + clock_t t_start = std::clock(); + wfc::operation_result result = m.insert(i, i); + clock_t t_end = std::clock(); + insertion_times[i] = 1000.0 * static_cast(t_end - t_start) / CLOCKS_PER_SEC; + if (failed(result)) + { + std::stringstream output; + output << "Not inserted: " << i << '\n'; + std::cout << output.str(); + } + }); + } + + { + using namespace std::chrono_literals; + std::this_thread::sleep_for(1s); + } + + ready_promise.set_value(); + + for (auto& t: threads) + { + t.join(); + } + + m.visit( + [](std::pair p) { std::cout << '[' << p.first << '-' << p.second << "]\n"; }); + + double max = *std::max_element(insertion_times.begin(), insertion_times.end()); + std::cout << "Max: " << max << "ms\n"; + std::cout << "Mean: " + << std::accumulate(insertion_times.begin(), insertion_times.end(), 0.0) / insertion_times.size() + << "ms\n"; + std::cout << "Min: " << *std::min_element(insertion_times.begin(), insertion_times.end()) << "ms\n"; + + return 0; +} diff --git a/external_libs/google_test.cmake b/external_libs/google_test.cmake deleted file mode 100644 index d3c1d55..0000000 --- a/external_libs/google_test.cmake +++ /dev/null @@ -1,15 +0,0 @@ -cmake_minimum_required(VERSION 2.8.2) - -project(googletest-download NONE) - -include(ExternalProject) -ExternalProject_Add(googletest - GIT_REPOSITORY https://github.com/google/googletest.git - GIT_TAG master - SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" - BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" - CONFIGURE_COMMAND "" - BUILD_COMMAND "" - INSTALL_COMMAND "" - TEST_COMMAND "" - ) \ No newline at end of file diff --git a/include/wait_free_unordered_map.hpp b/include/wait_free_unordered_map.hpp deleted file mode 100644 index 46790e2..0000000 --- a/include/wait_free_unordered_map.hpp +++ /dev/null @@ -1,623 +0,0 @@ -#ifndef WF_WAIT_FREE_UNORDERED_MAP -#define WF_WAIT_FREE_UNORDERED_MAP - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace wf -{ - template - T clz(T x) - { - if constexpr (std::is_same_v) - { - return static_cast(__builtin_clz(x)); - } - else if constexpr (std::is_same_v) - { - return static_cast(__builtin_clzl(x)); - } - else if constexpr (std::is_same_v) - { - return static_cast(__builtin_clzll(x)); - } - else - { - static_assert(!std::is_same_v); - } - } - - template - class identity_hash - { - public: - std::size_t operator()(const Key& k) const - { - return static_cast(k); - } - }; - - template > - class unordered_map - { - public: - using hash_t = std::invoke_result_t; - using value_t = Value; - - explicit unordered_map(std::size_t initialSize); - - unordered_map(const unordered_map&) = delete; - - unordered_map& operator=(const unordered_map&) = delete; - - bool insert(const Key& key, const Value& value); - - std::optional get(const Key& key); - - template - void visit(VisitorFun&& fun) noexcept( - noexcept(std::is_nothrow_invocable_v>)); - - std::size_t size() const noexcept; - - bool is_empty() const noexcept; - - private: - struct node__; - struct arraynode__; - union node_ptr; - - using node_t = node__; - using arraynode_t = arraynode__; - - struct node__ - { - hash_t hash; - value_t value; - }; - - struct arraynode__ - { - using value_t = std::atomic; - using reference_t = value_t&; - - explicit arraynode__(std::size_t size); - - arraynode__(const arraynode__&) = delete; - ~arraynode__(); - - arraynode__& operator=(const arraynode__&) = delete; - - reference_t operator[](std::size_t i); - - private: - value_t* m_ptr; - std::size_t m_size; - }; - - union node_ptr - { - node_ptr() noexcept; - node_ptr(node_t* datanode) noexcept; - node_ptr(arraynode_t* arraynode) noexcept; - - node_t* datanode_ptr; - - arraynode_t* arraynode_ptr; - - std::uintptr_t ptr_int; - }; - - node_ptr allocate_node(hash_t hash, value_t value) const; - - node_ptr expandNode(node_ptr arraynode, std::size_t position, std::size_t level) noexcept; - - template - void visit_array_node(node_ptr node, std::size_t level, VisitorFun&& fun) noexcept( - noexcept(std::is_nothrow_invocable_v>)); - - constexpr static std::size_t log2_power_two(std::size_t x) noexcept; - - static bool is_power_of_two(std::size_t nbr) noexcept; - - static node_ptr mark_datanode(node_ptr arraynode, std::size_t position) noexcept; - - static void mark_datanode(node_ptr& node) noexcept; - - static void unmark_datanode(node_ptr& node) noexcept; - - static bool is_marked(node_ptr node) noexcept; - - static void mark_arraynode(node_ptr& node) noexcept; - - static void unmark_arraynode(node_ptr& node) noexcept; - - static bool is_array_node(node_ptr node) noexcept; - - static node_ptr get_node(node_ptr arraynode, std::size_t pos) noexcept; - - static node_ptr sanitize_ptr(node_ptr arraynode) noexcept; - - arraynode_t m_head; - std::size_t m_head_size; - std::size_t m_arrayLength; - std::atomic m_size; - static constexpr std::size_t hash_size_in_bits = sizeof(hash_t) * std::numeric_limits::digits; - }; - - template - unordered_map::node_ptr::node_ptr() noexcept : datanode_ptr{nullptr} - { - } - - template - unordered_map::node_ptr::node_ptr(node_t* datanode) noexcept : datanode_ptr{datanode} - { - } - - template - unordered_map::node_ptr::node_ptr(arraynode_t* arraynode) noexcept - : arraynode_ptr{arraynode} - { - } - - template - unordered_map::arraynode__::arraynode__(std::size_t size) - : m_ptr{new value_t[size]}, m_size(size) - { - for (std::size_t i = 0; i < size; ++i) - { - new (&m_ptr[i]) value_t(); - } - } - - template - unordered_map::arraynode__::~arraynode__() - { - for (std::size_t i = 0; i < m_size; ++i) - { - node_ptr child = m_ptr[i].load(); - if (child.arraynode_ptr != nullptr) - { - if (is_array_node(child)) - { - delete sanitize_ptr(child).arraynode_ptr; - } - else - { - delete child.datanode_ptr; - } - } - } - - delete[] m_ptr; - } - - template - auto unordered_map::arraynode__::operator[](std::size_t i) -> value_t& - { - return m_ptr[i]; - } - - template - unordered_map::unordered_map(std::size_t initialSize) - : m_head(2UL << initialSize), m_head_size(2UL << initialSize), m_arrayLength(initialSize), m_size(0UL) - { - static_assert(std::atomic::is_always_lock_free, "Atomic implementation is not lock free"); - static_assert(std::atomic::is_always_lock_free, "Atomic implementation is not lock free"); - - if (!is_power_of_two(initialSize)) - { - throw std::runtime_error("Size should be a power of four"); - } - } - - template - bool unordered_map::insert(const Key& key, const Value& value) - { - std::size_t array_pow = log2_power_two(m_arrayLength); - - std::size_t position; - std::size_t failCount; - node_ptr local; - local.arraynode_ptr = &m_head; - mark_arraynode(local); - - hash_t fullhash = HashFunction{}(key); - hash_t hash = fullhash; - - for (std::size_t r = 0; r < hash_size_in_bits - array_pow; r += array_pow) - { - if (r == 0) - { - position = hash & (m_head_size - 1); - hash >>= m_arrayLength; - } - else - { - position = hash & (m_arrayLength - 1); - hash >>= array_pow; - } - - failCount = 0; - - node_ptr node = get_node(local, position); - - while (true) - { - if (failCount > 8) - { // FIXME - node = mark_datanode(local, position); - } - if (node.datanode_ptr == nullptr) - { - node_ptr node_to_insert = allocate_node(fullhash, value); - - node_ptr null; - null.datanode_ptr = nullptr; - - if ((*sanitize_ptr(local).arraynode_ptr)[position].compare_exchange_weak(null, node_to_insert)) - { - // watch(nullptr); - ++m_size; - return true; - } - else - { - delete node_to_insert.datanode_ptr; - } - } - - if (is_marked(node)) - { - node = expandNode(local, position, r); - } - if (is_array_node(node)) - { - local = node; - break; - } - else - { - node_ptr node2 = get_node(local, position); - if (node.ptr_int != node2.ptr_int) - { - ++failCount; - node = node2; - } - else - { - if (node.datanode_ptr->hash == fullhash) - { - // watch(nullptr); - return false; - } - else - { - node = expandNode(local, position, r); - if (is_array_node(node)) - { - local = node; - break; - } - else - { - ++failCount; - } - } - } - } - } - } - - // watch(nullptr); - position = hash & (m_arrayLength - 1); - node_ptr node = get_node(local, position); - - if (node.datanode_ptr == nullptr) - { - node_ptr node_to_insert = allocate_node(fullhash, value); - - node_ptr null; - null.datanode_ptr = nullptr; - - return (*sanitize_ptr(local).arraynode_ptr)[position].compare_exchange_weak(null, node_to_insert); - } - else - { - return false; - } - } - - template - auto unordered_map::allocate_node(hash_t hash, value_t value) const - -> unordered_map::node_ptr - { - node_ptr node_to_insert; - - node_to_insert.datanode_ptr = new (std::align_val_t{8}) node_t{}; - node_to_insert.datanode_ptr->hash = hash; - node_to_insert.datanode_ptr->value = value; - - return node_to_insert; - } - - template - std::optional unordered_map::get(const Key& key) - { - std::size_t array_pow = log2_power_two(m_arrayLength); - - std::size_t position; - std::size_t failCount; - node_ptr local; - local.arraynode_ptr = &m_head; - mark_arraynode(local); - - hash_t fullhash = HashFunction{}(key); - hash_t hash = fullhash; - - for (std::size_t r = 0; r < hash_size_in_bits - array_pow; r += array_pow) - { - if (r == 0) - { - position = hash & (m_head_size - 1); - hash >>= m_arrayLength; - } - else - { - position = hash & (m_arrayLength - 1); - hash >>= array_pow; - } - - failCount = 0; - - node_ptr node = get_node(local, position); - - while (true) - { - if (failCount > 8) - { // FIXME - node = mark_datanode(local, position); - } - if (node.datanode_ptr == nullptr) - { - return {}; - } - - if (is_marked(node)) - { - node = expandNode(local, position, r); - } - if (is_array_node(node)) - { - local = node; - break; - } - else - { - node_ptr node2 = get_node(local, position); - if (node.ptr_int != node2.ptr_int) - { - ++failCount; - node = node2; - continue; - } - else - { - if (node.datanode_ptr->hash == fullhash) - { - // watch(nullptr); - return {node.datanode_ptr->value}; - } - else - { - node = expandNode(local, position, r); - if (is_array_node(node)) - { - local = node; - break; - } - else - { - ++failCount; - } - } - } - } - } - } - - return {}; - } - - template - bool unordered_map::is_power_of_two(std::size_t nbr) noexcept - { - return nbr && !(nbr & (nbr - 1)); - } - - template - constexpr std::size_t unordered_map::log2_power_two(std::size_t x) noexcept - { - return hash_size_in_bits - clz(x) - 1UL; - } - - template - auto unordered_map::mark_datanode(node_ptr arraynode, std::size_t position) noexcept - -> unordered_map::node_ptr - { - node_ptr oldValue = get_node(arraynode, position); - node_ptr value = oldValue; - mark_datanode(value); - - (*sanitize_ptr(arraynode).arraynode_ptr)[position].compare_exchange_weak(oldValue, value); - - return get_node(arraynode, position); - } - - template - bool unordered_map::is_marked(node_ptr node) noexcept - { - return static_cast(node.ptr_int & 0b1UL); - } - - template - bool unordered_map::is_array_node(node_ptr node) noexcept - { - return static_cast(node.ptr_int & 0b10UL); - } - - template - auto unordered_map::expandNode(node_ptr arraynode, - std::size_t position, - std::size_t level) noexcept -> unordered_map::node_ptr - { - std::atomic& node_atomic = (*sanitize_ptr(arraynode).arraynode_ptr)[position]; - node_ptr old_value = node_atomic.load(); - - // watch(value); - if (is_array_node(old_value)) - { - return old_value; - } - node_ptr value = node_atomic.load(); - - if (value.ptr_int != old_value.ptr_int) - { - return value; - } - - node_ptr new_value; - new_value.arraynode_ptr = new (std::align_val_t{8}) arraynode_t{m_arrayLength}; - - std::size_t new_pos = value.datanode_ptr->hash >> (m_arrayLength + level) & (m_arrayLength - 1); - unmark_datanode(value); - - (*new_value.arraynode_ptr)[new_pos] = value; - mark_arraynode(new_value); - - if (!node_atomic.compare_exchange_weak(old_value, new_value)) - { - new_value = sanitize_ptr(new_value); - (*new_value.arraynode_ptr)[new_pos] = node_ptr{}; - delete new_value.arraynode_ptr; - } - - return new_value; - } - - template - template - void unordered_map::visit(VisitorFun&& fun) noexcept( - noexcept(std::is_nothrow_invocable_v>)) - { - for (std::size_t i = 0; i < m_head_size; ++i) - { - node_ptr node = m_head[i].load(); - if (node.datanode_ptr != nullptr) - { - if (is_array_node(node)) - { - visit_array_node(node, 1, fun); - } - else - { - std::invoke( - fun, - std::pair(node.datanode_ptr->hash, node.datanode_ptr->value)); - } - } - } - } - - template - template - void unordered_map::visit_array_node( - node_ptr node, - std::size_t level, - VisitorFun&& - fun) noexcept(noexcept(std::is_nothrow_invocable_v>)) - { - for (std::size_t i = 0; i < m_arrayLength; ++i) - { - node_ptr child = get_node(node, i); - if (child.datanode_ptr != nullptr) - { - if (is_array_node(child)) - { - visit_array_node(child, level + 1); - } - else - { - std::invoke( - fun, - std::pair(child.datanode_ptr->hash, child.datanode_ptr->value)); - } - } - } - } - - template - auto unordered_map::get_node(node_ptr arraynode, std::size_t pos) noexcept - -> unordered_map::node_ptr - { - assert(is_array_node(arraynode)); - - node_ptr accessor = sanitize_ptr(arraynode); - - return (*accessor.arraynode_ptr)[pos].load(); - } - - template - auto unordered_map::sanitize_ptr(node_ptr arraynode) noexcept -> node_ptr - { - unmark_arraynode(arraynode); - return arraynode; - } - - template - void unordered_map::mark_datanode(node_ptr& node) noexcept - { - node.ptr_int |= 0b01UL; - } - - template - void unordered_map::unmark_datanode(node_ptr& node) noexcept - { - node.ptr_int &= ~0b01UL; - } - - template - void unordered_map::mark_arraynode(node_ptr& node) noexcept - { - node.ptr_int |= 0b10UL; - } - - template - void unordered_map::unmark_arraynode(node_ptr& node) noexcept - { - node.ptr_int &= ~0b10UL; - } - - template - std::size_t unordered_map::size() const noexcept - { - return m_size; - } - - template - bool unordered_map::is_empty() const noexcept - { - return size() == 0; - } - -} // namespace wf - -#endif \ No newline at end of file diff --git a/include/wfc/details/unordered_map/nodes.hpp b/include/wfc/details/unordered_map/nodes.hpp new file mode 100644 index 0000000..545b0cd --- /dev/null +++ b/include/wfc/details/unordered_map/nodes.hpp @@ -0,0 +1,209 @@ +#ifndef WFC_NODES_HPP +#define WFC_NODES_HPP + +#include +#include + +namespace wfc +{ + namespace details + { + template + union node_union; + + template + struct node_t + { + Hash hash; + Key key; + Value value; + }; + + template + struct arraynode_t + { + using value_t = std::atomic>; + using reference_t = value_t&; + using const_reference_t = const value_t&; + + explicit arraynode_t(std::size_t size); + + arraynode_t(const arraynode_t&) noexcept = delete; + ~arraynode_t() noexcept; + + arraynode_t& operator=(const arraynode_t&) = delete; + + reference_t operator[](std::size_t i) noexcept; + + const_reference_t operator[](std::size_t i) const noexcept; + + private: + value_t* m_ptr; + std::size_t m_size; + }; + + template + union node_union + { + node_union() noexcept; + explicit node_union(NodeT* datanode) noexcept; + explicit node_union(arraynode_t* arraynode) noexcept; + + NodeT* datanode_ptr; + arraynode_t* arraynode_ptr; + std::uintptr_t ptr_int; + }; + + template + bool is_array_node(node_union node) noexcept; + + template + node_union mark_datanode(node_union arraynode, std::size_t position) noexcept; + + template + void mark_datanode(node_union& node) noexcept; + + template + void unmark_datanode(node_union& node) noexcept; + + template + bool is_marked(node_union node) noexcept; + + template + void mark_arraynode(node_union& node) noexcept; + + template + void unmark_arraynode(node_union& node) noexcept; + + template + node_union get_node(node_union arraynode, std::size_t pos) noexcept; + + template + node_union sanitize_ptr(node_union arraynode) noexcept; + + template + bool is_array_node(node_union node) noexcept + { + return static_cast(node.ptr_int & 0b10UL); + } + + template + auto mark_datanode(node_union arraynode, std::size_t position) noexcept -> node_union + { + node_union oldValue = get_node(arraynode, position); + node_union value = oldValue; + mark_datanode(value); + + (*sanitize_ptr(arraynode).arraynode_ptr)[position].compare_exchange_weak(oldValue, value); + + return get_node(arraynode, position); + } + + template + void mark_datanode(node_union& node) noexcept + { + node.ptr_int |= 0b01UL; + } + + template + void unmark_datanode(node_union& node) noexcept + { + node.ptr_int &= ~0b01UL; + } + + template + bool is_marked(node_union node) noexcept + { + return static_cast(node.ptr_int & 0b1UL); + } + + template + void mark_arraynode(node_union& node) noexcept + { + node.ptr_int |= 0b10UL; + } + + template + void unmark_arraynode(node_union& node) noexcept + { + node.ptr_int &= ~0b10UL; + } + + template + auto get_node(node_union arraynode, std::size_t pos) noexcept -> node_union + { + assert(is_array_node(arraynode)); + + node_union accessor = sanitize_ptr(arraynode); + + return (*accessor.arraynode_ptr)[pos].load(); + } + + template + auto sanitize_ptr(node_union arraynode) noexcept -> node_union + { + unmark_arraynode(arraynode); + return arraynode; + } + + template + node_union::node_union() noexcept : datanode_ptr{nullptr} + { + } + + template + node_union::node_union(NodeT* datanode) noexcept : datanode_ptr{datanode} + { + } + + template + node_union::node_union(arraynode_t* arraynode) noexcept : arraynode_ptr{arraynode} + { + } + + template + arraynode_t::arraynode_t(std::size_t size) : m_ptr{new value_t[size]}, m_size(size) + { + for (std::size_t i = 0; i < size; ++i) + { + new (&m_ptr[i]) value_t(); + } + } + + template + arraynode_t::~arraynode_t() noexcept + { + for (std::size_t i = 0; i < m_size; ++i) + { + node_union child = m_ptr[i].load(); + if (child.arraynode_ptr != nullptr) + { + if (is_array_node(child)) + { + delete sanitize_ptr(child).arraynode_ptr; + } + else + { + delete child.datanode_ptr; + } + } + m_ptr[i].store({}); + } + + delete[] m_ptr; + } + + template + auto arraynode_t::operator[](std::size_t i) noexcept -> reference_t + { + return m_ptr[i]; + } + + template + auto arraynode_t::operator[](std::size_t i) const noexcept -> const_reference_t + { + return m_ptr[i]; + } + } // namespace details +} // namespace wfc +#endif // WFC_NODES_HPP diff --git a/include/wfc/unordered_map.hpp b/include/wfc/unordered_map.hpp new file mode 100644 index 0000000..b2a3271 --- /dev/null +++ b/include/wfc/unordered_map.hpp @@ -0,0 +1,787 @@ +#ifndef WFC_UNORDERED_MAP_HPP +#define WFC_UNORDERED_MAP_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utility/math.hpp" +#include "details/unordered_map/nodes.hpp" + +namespace wfc +{ + /** + * @brief represents the return status of the operation. + */ + enum class operation_result + { + success, /**< operation successful */ + expected_value_mismatch, /**< the key's associated value doesn't match the expected value */ + element_not_found, /**< the key is not present in the hash map */ + already_present /**< the key is already present in the hash map */ + }; + + /** + * Helper function that returns true if the operation_result is a success. + */ + [[nodiscard]] constexpr bool succeeded(operation_result e) noexcept + { + return e == operation_result::success; + } + + /** + * Helper function that returns true if the operation_result correspond to a failed state. + */ + [[nodiscard]] constexpr bool failed(operation_result e) noexcept + { + return !succeeded(e); + } + + /** + * Default hash function. This function is the identity. + * @tparam Key - Type of the key + */ + template + class identity_hash + { + public: + static_assert(std::is_fundamental_v, "Key should be a fundamental type to use the default hash"); + Key operator()(const Key& k) const noexcept + { + return k; + } + }; + + /** + * A wait free hash map + * + * @details This map could be seen as a n-ary tree, (except that the head has 2^n children) + * In the case of this map, n is array_length. + * Each node of this map could be an array or a datanode. If two datanodes should go in the same place + * the existing datanode is transformed into an arraynode to allow the insertion of the two datanodes. + * It is noteworthy that this expending of the map will be repeated until the hash of the two nodes differ. + * + * @tparam Key type of the key in the map + * @tparam Value type of the value in the map + * @tparam HashFunction Functor that holds the hash function to be used on keys. + * The hash function has to be collision-free. + * Consequently, output size should be at least as large as input size. + */ + template > + class unordered_map + { + public: + using key_t = Key; + using hash_t = std::invoke_result_t; + using value_t = Value; + + /** + * Constructs a wait free hash map. + * + * @param array_length size of the array containing the elements (head size is 2^array_length) + * @param max_fail_count this value should match to the number of threads using this map + * @param max_nbr_threads maximal number of thread using the hashmap. + */ + explicit unordered_map(std::size_t array_length, + std::size_t max_fail_count = 8, + std::size_t max_nbr_threads = 8); + unordered_map(const unordered_map&) = delete; + ~unordered_map() noexcept = default; + + unordered_map& operator=(const unordered_map&) = delete; + + /** + * Inserts the key and the value in the hash map. + * + * @return Returns operation_result::already_present if the key is already in the map. + * Otherwise it returns operation_result::success + */ + operation_result insert(const Key& key, const Value& value); + + /** + * Tries to retrieve the value associated to the key. + * + * @return an empty optional if the key in not in the map. The associated value otherwise. + */ + std::optional get(const Key& key); + + /** + * Update the value associated with the given key if the current + * value matches expected_value. + * + * @param key + * @param value + * @param expected_value + * @return @see operation_result + */ + operation_result update(const Key& key, const Value& new_value, const Value& expected_value); + + /** + * Update the value associated with the given key. + * + * @param key + * @param value + * @return @see operation_result + */ + operation_result update(const Key& key, const Value& value); + + /** + * Remove the element associated to the given key if the + * expected_value matches the current value. + * + * @param key + * @return @see operation_result + */ + operation_result remove(const Key& key, const Value& expected_value); + + /** + * Remove the element associated to the given key. + * + * @param key + * @return @see operation_result + */ + operation_result remove(const Key& key); + + /** + * Applies functor on every element in the map. + * This function is NOT thread safe. + * @tparam VisitorFun The type should be compatible with this prototype + * void(const std::pair&); + * @param fun + */ + template + void visit(VisitorFun&& fun) noexcept( + noexcept(std::is_nothrow_invocable_v>)); + + /** + * Returns the number of elements into the collection + */ + std::size_t size() const noexcept; + + /** + * Returns true if the map is empty, false otherwise. + */ + bool is_empty() const noexcept; + + private: + using node_t = details::node_t; + using node_union = details::node_union; + using arraynode_t = details::arraynode_t; + + node_union allocate_node(hash_t hash, key_t key, value_t value) const; + + node_union expand_node(node_union arraynode, std::size_t position, std::size_t level) noexcept; + + bool try_node_insertion(node_union arraynode, std::size_t position, node_union& datanode); + + template + operation_result update_impl(const Key& key, const Value& value, Fun&& compare_expected_value); + + template + operation_result remove_impl(const Key& key, Fun&& compare_expected_value); + + template + operation_result update_or_remove_impl(const Key& key, + CmpFun&& compare_expected_value, + AllocFun&& replacing_node); + + void ensure_not_replaced(node_union& local, size_t position, size_t r, node_union& node); + + template + void visit_array_node(node_union node, VisitorFun&& fun) noexcept( + noexcept(std::is_nothrow_invocable_v>)); + + std::tuple compute_pos_and_hash(size_t array_pow, hash_t lasthash, size_t level) const; + + void safe_delete(node_union node_to_free); + + void watch_node(node_union node) noexcept; + + void clear_watched_node() noexcept; + + arraynode_t m_head; + std::size_t m_head_size; + std::size_t m_arrayLength; + std::size_t m_max_fail_count; + std::size_t m_max_nbr_threads; + std::atomic m_size; + std::shared_ptr[]> m_watched_nodes; + + static constexpr std::size_t hash_size_in_bits = sizeof(hash_t) * std::numeric_limits::digits; + }; + + template + unordered_map::unordered_map(std::size_t array_length, + std::size_t max_fail_count, + std::size_t max_nbr_threads) + : m_head(1UL << array_length) + , m_head_size(1UL << array_length) + , m_arrayLength(array_length) + , m_max_fail_count(max_fail_count) + , m_max_nbr_threads(max_nbr_threads) + , m_size(0UL) + , m_watched_nodes(new std::atomic[max_nbr_threads]) + { + static_assert(std::atomic::is_always_lock_free, "Atomic implementation is not lock free"); + static_assert(std::atomic::is_always_lock_free, "Atomic implementation is not lock free"); + + for (std::ptrdiff_t i = 0, n = static_cast(m_max_nbr_threads); i < n; ++i) + { + m_watched_nodes[i] = 0; + } + + if (!is_power_of_two(array_length)) + { + throw std::runtime_error("Array length should be a power of two"); + } + } + + template + operation_result unordered_map::insert(const Key& key, const Value& value) + { + std::size_t nbr_bits_to_shift = log2_of_power_of_two(m_arrayLength); + + std::size_t position; + std::size_t fail_count; + node_union local{&m_head}; + mark_arraynode(local); + + hash_t fullhash = HashFunction{}(key); + hash_t hash = fullhash; + + for (std::size_t r = 0; r < hash_size_in_bits - nbr_bits_to_shift; r += nbr_bits_to_shift) + { + fail_count = 0; + std::tie(position, hash) = compute_pos_and_hash(nbr_bits_to_shift, hash, r); + node_union node = get_node(local, position); + + while (true) + { + if (fail_count > m_max_fail_count) + { + node = mark_datanode(local, position); + } + + if (node.datanode_ptr == nullptr) + { + node_union new_node = allocate_node(fullhash, key, value); + if (try_node_insertion(local, position, new_node)) + { + clear_watched_node(); + + return operation_result::success; + } + else + { + node = new_node; + } + } + + if (is_marked(node)) + { + node = expand_node(local, position, r); + } + + if (is_array_node(node)) + { + local = node; + break; + } + else + { + watch_node(node); + node_union node2 = get_node(local, position); + if (node.ptr_int != node2.ptr_int) + { + ++fail_count; + node = node2; + continue; + } + else if (node.datanode_ptr->hash == fullhash) + { + clear_watched_node(); + return operation_result::already_present; + } + else + { + node = expand_node(local, position, r); + if (is_array_node(node)) + { + local = node; + break; + } + else + { + ++fail_count; + } + } + } + } + } + + clear_watched_node(); + std::tie(position, std::ignore) = compute_pos_and_hash(position, hash, hash_size_in_bits - nbr_bits_to_shift); + node_union node = get_node(local, position); + + node_union new_node = allocate_node(fullhash, key, value); + if (node.datanode_ptr == nullptr && try_node_insertion(local, position, new_node)) + { + return operation_result::success; + } + + return operation_result::already_present; + } + + template + std::optional unordered_map::get(const Key& key) + { + std::size_t array_pow = log2_of_power_of_two(m_arrayLength); + + std::size_t position; + node_union local{&m_head}; + mark_arraynode(local); + + hash_t fullhash = HashFunction{}(key); + hash_t hash = fullhash; + + for (std::size_t r = 0; r < hash_size_in_bits - array_pow; r += array_pow) + { + std::tie(position, hash) = compute_pos_and_hash(array_pow, hash, r); + node_union node = get_node(local, position); + + if (is_array_node(node)) + { + local = node; + } + else if (node.datanode_ptr == nullptr) + { + clear_watched_node(); + return std::nullopt; + } + else + { + watch_node(node); + if (node.ptr_int != get_node(local, position).ptr_int) + { + ensure_not_replaced(local, position, r, node); + + if (is_array_node(node)) + { + local = node; + } + else if (is_marked(node)) + { + local = expand_node(local, position, r); + } + else if (node.datanode_ptr == nullptr) + { + clear_watched_node(); + return std::nullopt; + } + } + else if (node.datanode_ptr->hash == fullhash) + { + std::optional result = node.datanode_ptr->value; + clear_watched_node(); + + return result; + } + else + { + break; + } + } + } + + clear_watched_node(); + return std::nullopt; + } + + template + operation_result unordered_map::update(const Key& key, + const Value& new_value, + const Value& expected_value) + { + return update_impl(key, new_value, [&expected_value](node_t* node) { return node->value == expected_value; }); + } + + template + operation_result unordered_map::update(const Key& key, const Value& value) + { + return update_impl(key, value, [](auto) { return true; }); + } + + template + operation_result unordered_map::remove(const Key& key, const Value& expected_value) + { + return remove_impl(key, [&expected_value](node_t* node) { return node->value == expected_value; }); + } + + template + operation_result unordered_map::remove(const Key& key) + { + return remove_impl(key, [](auto) { return true; }); + } + + template + template + void unordered_map::visit(VisitorFun&& fun) noexcept( + noexcept(std::is_nothrow_invocable_v>)) + { + static_assert(std::is_invocable_v>, + "Visitor doesn't respect the concept"); + + for (std::size_t i = 0; i < m_head_size; ++i) + { + node_union node = m_head[i].load(); + if (node.datanode_ptr != nullptr) + { + if (is_array_node(node)) + { + visit_array_node(node, fun); + } + else + { + std::invoke(fun, std::pair(node.datanode_ptr->key, node.datanode_ptr->value)); + } + } + } + } + + template + std::size_t unordered_map::size() const noexcept + { + return m_size; + } + + template + bool unordered_map::is_empty() const noexcept + { + return size() == 0; + } + + template + auto unordered_map::allocate_node(hash_t hash, key_t key, value_t value) const + -> node_union + { + if (alignof(node_t*) < 4) + { + return node_union{new (std::align_val_t{8}) node_t{hash, key, value}}; + } + else + { + return node_union{new node_t{hash, key, value}}; + } + } + + template + auto unordered_map::expand_node(node_union arraynode, + std::size_t position, + std::size_t level) noexcept -> node_union + { + std::atomic& node_atomic = (*sanitize_ptr(arraynode).arraynode_ptr)[position]; + node_union old_value = node_atomic.load(); + + watch_node(old_value); + + if (is_array_node(old_value)) + { + return old_value; + } + node_union value = node_atomic.load(); + + if (value.ptr_int != old_value.ptr_int) + { + return value; + } + + if (value.datanode_ptr != nullptr) + { + node_union array_node; + + if (alignof(arraynode_t*) < 4) + { + array_node = node_union{new (std::align_val_t{8}) arraynode_t{m_arrayLength}}; + } + else + { + array_node = node_union{new arraynode_t{m_arrayLength}}; + } + + std::size_t new_pos = value.datanode_ptr->hash >> (m_arrayLength + level) & (m_arrayLength - 1); + unmark_datanode(value); + + (*array_node.arraynode_ptr)[new_pos] = value; + mark_arraynode(array_node); + + if (!node_atomic.compare_exchange_weak(old_value, array_node)) + { + array_node = sanitize_ptr(array_node); + (*array_node.arraynode_ptr)[new_pos] = node_union{}; + delete array_node.arraynode_ptr; + } + } + + return node_atomic.load(); + } + + template + bool unordered_map::try_node_insertion(node_union arraynode, + std::size_t position, + node_union& datanode) + { + node_union null{static_cast(nullptr)}; + + arraynode_t& array = (*sanitize_ptr(arraynode).arraynode_ptr); + + if (array[position].compare_exchange_weak(null, datanode)) + { + datanode = array[position].load(); + ++m_size; + return true; + } + + delete datanode.datanode_ptr; + datanode = array[position].load(); + + return false; + } + + template + template + operation_result unordered_map::update_impl(const Key& key, + const Value& value, + Fun&& compare_expected_value) + { + return update_or_remove_impl(key, compare_expected_value, [&value, &key, this](hash_t fullhash) { + return this->allocate_node(fullhash, key, value); + }); + } + + template + template + operation_result unordered_map::remove_impl(const Key& key, Fun&& compare_expected_value) + { + return update_or_remove_impl(key, compare_expected_value, [](hash_t) { return node_union{}; }); + } + + template + template + operation_result unordered_map::update_or_remove_impl(const Key& key, + CmpFun&& compare_expected_value, + AllocFun&& replacing_node) + { + std::size_t array_pow = log2_of_power_of_two(m_arrayLength); + + std::size_t position; + node_union local{&m_head}; + mark_arraynode(local); + + hash_t fullhash = HashFunction{}(key); + hash_t hash = fullhash; + + for (std::size_t r = 0; r < hash_size_in_bits - array_pow; r += array_pow) + { + std::tie(position, hash) = compute_pos_and_hash(array_pow, hash, r); + node_union node = get_node(local, position); + + if (is_array_node(node)) + { + local = node; + } + else if (is_marked(node)) + { + local = expand_node(local, position, r); + } + else if (node.datanode_ptr == nullptr) + { + clear_watched_node(); + return operation_result::element_not_found; + } + else + { + watch_node(node); + if (node.ptr_int != get_node(local, position).ptr_int) + { + ensure_not_replaced(local, position, r, node); + + if (is_array_node(node)) + { + local = node; + continue; + } + else if (is_marked(node)) + { + local = expand_node(local, position, r); + continue; + } + else if (node.datanode_ptr == nullptr) + { + clear_watched_node(); + return operation_result::element_not_found; + } + } + + if (node.datanode_ptr->hash == fullhash) + { + if (!compare_expected_value(node.datanode_ptr)) + { + clear_watched_node(); + return operation_result::expected_value_mismatch; + } + + node_union new_node = replacing_node(fullhash); + if ((*sanitize_ptr(local).arraynode_ptr)[position].compare_exchange_weak(node, new_node)) + { + safe_delete(node); + //delete node.datanode_ptr; + + clear_watched_node(); + return operation_result::success; + } + else + { + delete new_node.datanode_ptr; + + node = get_node(local, position); + if (is_array_node(node)) + { + local = node; + } + else if (is_marked(node)) + { + local = expand_node(local, position, r); + } + else + { + clear_watched_node(); + return operation_result::element_not_found; + } + } + } + else + { + clear_watched_node(); + return operation_result::element_not_found; + } + } + } + + clear_watched_node(); + return operation_result::element_not_found; + } + + template + void unordered_map::ensure_not_replaced(node_union& local, + size_t position, + size_t r, + node_union& node) + { + std::size_t fail_count = 0; + do + { + node = get_node(local, position); + watch_node(node); + ++fail_count; + + if (fail_count > m_max_fail_count) + { + mark_datanode(node); + local = expand_node(local, position, r); + break; + } + } while (node.ptr_int != get_node(local, position).ptr_int); + } + + template + template + void unordered_map::visit_array_node(node_union node, VisitorFun&& fun) noexcept( + noexcept(std::is_nothrow_invocable_v>)) + { + for (std::size_t i = 0; i < m_arrayLength; ++i) + { + node_union child = get_node(node, i); + if (child.datanode_ptr != nullptr) + { + if (is_array_node(child)) + { + visit_array_node(child, fun); + } + else + { + std::invoke(fun, std::pair(child.datanode_ptr->key, child.datanode_ptr->value)); + } + } + } + } + + template + auto unordered_map::compute_pos_and_hash(size_t array_pow, + hash_t lasthash, + size_t level) const + -> std::tuple + { + size_t position; + + if (level == 0) + { + position = lasthash & (m_head_size - 1); + lasthash >>= m_arrayLength; + } + else + { + position = lasthash & (m_arrayLength - 1); + lasthash >>= array_pow; + } + + return {position, lasthash}; + } + + template + void unordered_map::safe_delete(node_union node_to_free) + { + bool freeable; + + do + { + freeable = true; + for (std::ptrdiff_t i = 0; i < m_max_nbr_threads; ++i) + { + if (static_cast(i) == details::get_thread_id()) + { + continue; + } + else if (m_watched_nodes[i] == node_to_free.ptr_int) + { + freeable = false; + break; + } + } + + if (freeable) + { + delete node_to_free.datanode_ptr; + } + } while (!freeable); + } + + template + void unordered_map::watch_node(node_union node) noexcept + { + m_watched_nodes[static_cast(details::get_thread_id())] = node.ptr_int; + } + + template + void unordered_map::clear_watched_node() noexcept + { + m_watched_nodes[static_cast(details::get_thread_id())] = 0; + } +} // namespace wfc + +#endif // WFC_UNORDERED_MAP_HPP diff --git a/include/wfc/utility/math.hpp b/include/wfc/utility/math.hpp new file mode 100644 index 0000000..1b886a9 --- /dev/null +++ b/include/wfc/utility/math.hpp @@ -0,0 +1,89 @@ +#ifndef WFC_MATH_HPP +#define WFC_MATH_HPP + +#include +#include + +#ifndef __has_builtin +# define WFC__has_builtin(x) 0 +#else +# define WFC__has_builtin(x) __has_builtin(x) +#endif + +namespace wfc +{ + namespace details + { +#if WFC__has_builtin(__builtin_clz) && WFC__has_builtin(__builtin_clzl) && WFC__has_builtin(__builtin_clzll) + template + T clz(T x) + { + assert(x != 0); // clz is undefined for 0 + + if constexpr (std::is_same_v) + { + return static_cast(__builtin_clz(x)); + } + else if constexpr (std::is_same_v) + { + return static_cast(__builtin_clzl(x)); + } + else if constexpr (std::is_same_v) + { + return static_cast(__builtin_clzll(x)); + } + else + { + static_assert(!std::is_same_v); + } + } +#else + template + T clz(T x) + { + static_assert(std::is_unsigned_v, "T should be unsigned"); + + T mask = T(1) << T(std::numeric_limits::digits - 1); + + T result = 0; + + for (std::size_t i = 0; i < std::numeric_limits::digits; ++i) + { + if ((mask & x) != 0) + { + return result; + } + ++result; + mask >>= 1; + } + + return result; + } +#endif + } // namespace details + + template + constexpr std::size_t log2_of_power_of_two(T x) noexcept; + + template + constexpr bool is_power_of_two(T nbr) noexcept; + + template + constexpr std::size_t log2_of_power_of_two(T x) noexcept + { + static_assert(std::is_integral_v, "T should be an integral type"); + assert(is_power_of_two(x)); + + return sizeof(T) * std::numeric_limits::digits - details::clz(x) - 1UL; + } + + template + constexpr bool is_power_of_two(T nbr) noexcept + { + return nbr && !(nbr & (nbr - 1)); + } +} // namespace wfc + +#undef WFC__has_builtin + +#endif // WFC_MATH_HPP diff --git a/include/wfc/utility/thread_manipulation.hpp b/include/wfc/utility/thread_manipulation.hpp new file mode 100644 index 0000000..4a6baea --- /dev/null +++ b/include/wfc/utility/thread_manipulation.hpp @@ -0,0 +1,19 @@ +#ifndef WAITFREEHASHMAP_THREAD_MANIPULATION_HPP +#define WAITFREEHASHMAP_THREAD_MANIPULATION_HPP + +#include + +namespace wfc +{ + namespace details + { + inline std::size_t get_thread_id() + { + static std::atomic i = 0; + thread_local static std::size_t id = i++; + + return id; + } + } // namespace details +} // namespace wfc +#endif //WAITFREEHASHMAP_THREAD_MANIPULATION_HPP diff --git a/test/single_thread_test.cpp b/test/single_thread_test.cpp deleted file mode 100644 index 083437f..0000000 --- a/test/single_thread_test.cpp +++ /dev/null @@ -1,11 +0,0 @@ -#include - -#include - -TEST(WaitFreeHashMap, Construction) -{ - wf::unordered_map map(8); - - EXPECT_TRUE(map.is_empty()); - ASSERT_EQ(map.size(), 0); -} diff --git a/tests/unordered_map/multi_thread_test.cpp b/tests/unordered_map/multi_thread_test.cpp new file mode 100644 index 0000000..f86754e --- /dev/null +++ b/tests/unordered_map/multi_thread_test.cpp @@ -0,0 +1,255 @@ +#include + +#include +#include +#include +#include + +#include + +class WaitFreeHashMapMultiThreadTest : public ::testing::TestWithParam +{ +protected: + void SetUp() override + { + futur = std::shared_future(ready_promise.get_future()); + + for (std::size_t i = 0; i < map_size; ++i) + { + map.insert(static_cast(i), i + 1); + } + } + + void wait_threads() + { + for (auto& t: threads) + { + t.join(); + } + } + + static constexpr std::size_t block_low(std::size_t threadIdx, std::size_t nbrThreads, std::size_t dataSize) noexcept + { + return threadIdx * dataSize / nbrThreads; + } + + static constexpr std::size_t block_high(std::size_t threadIdx, + std::size_t nbrThreads, + std::size_t dataSize) noexcept + { + return block_low(threadIdx + 1, nbrThreads, dataSize) - 1; + } + + static constexpr std::size_t block_size(std::size_t threadIdx, + std::size_t nbrThreads, + std::size_t dataSize) noexcept + { + return block_high(threadIdx, nbrThreads, dataSize) - block_low(threadIdx, nbrThreads, dataSize) + 1; + } + + static constexpr std::size_t map_size = std::numeric_limits::max() + 1; + std::vector threads; + std::shared_future futur; + std::promise ready_promise; + wfc::unordered_map map{4, 16, 65535}; +}; + +TEST_P(WaitFreeHashMapMultiThreadTest, UpdateNoConflict) +{ + constexpr std::size_t nbr_threads = 8; + + for (std::size_t i = 0; i < nbr_threads; ++i) + { + threads.emplace_back([this, i]() { + futur.wait(); + + for (std::size_t j = block_low(i, nbr_threads, map_size), n = block_high(i, nbr_threads, map_size); j <= n; + ++j) + { + ASSERT_EQ(map.update(static_cast(j), 2 * (j + 1), j + 1), + wfc::operation_result::success); + } + }); + } + + ready_promise.set_value(); + wait_threads(); + + for (std::size_t i = 0; i < map_size; ++i) + { + ASSERT_EQ(map.get(static_cast(i)).value(), 2 * (i + 1)); + } +} + +TEST_P(WaitFreeHashMapMultiThreadTest, UpdateConflict) +{ + constexpr std::size_t nbr_threads = 16; + std::array, nbr_threads / 2> fails{}; + + for (std::size_t i = 0; i < nbr_threads; ++i) + { + threads.emplace_back([this, i, &fails]() { + futur.wait(); + std::size_t idx = i % 8; + + for (std::size_t j = block_low(idx, nbr_threads / 2, map_size), + n = block_high(idx, nbr_threads / 2, map_size); + j <= n; + ++j) + { + if (failed(map.update(static_cast(j), 2 * (j + 1), j + 1))) + { + ++fails[idx]; + } + } + }); + } + + ready_promise.set_value(); + wait_threads(); + + for (std::size_t i = 0; i < fails.size(); ++i) + { + ASSERT_EQ(fails[i].load(), block_size(i, nbr_threads / 2, map_size)); + } + + for (std::size_t i = 0; i < map_size; ++i) + { + ASSERT_EQ(map.get(static_cast(i)).value(), 2 * (i + 1)); + } +} + +TEST_P(WaitFreeHashMapMultiThreadTest, RemoveNoConflict) +{ + constexpr std::size_t nbr_threads = 8; + + for (std::size_t i = 0; i < nbr_threads; ++i) + { + threads.emplace_back([this, i]() { + futur.wait(); + + for (std::size_t j = block_low(i, nbr_threads, map_size), n = block_high(i, nbr_threads, map_size); j <= n; + ++j) + { + ASSERT_EQ(map.remove(static_cast(j), j + 1), wfc::operation_result::success); + } + }); + } + + ready_promise.set_value(); + wait_threads(); + + for (std::size_t i = 0; i < map_size; ++i) + { + ASSERT_FALSE(map.get(static_cast(i)).has_value()); + } +} + +TEST_P(WaitFreeHashMapMultiThreadTest, RemoveConflict) +{ + constexpr std::size_t nbr_threads = 16; + std::array, nbr_threads / 2> fails{}; + + for (std::size_t i = 0; i < nbr_threads; ++i) + { + threads.emplace_back([this, i, &fails]() { + futur.wait(); + std::size_t idx = i % 8; + + for (std::size_t j = block_low(idx, nbr_threads / 2, map_size), + n = block_high(idx, nbr_threads / 2, map_size); + j <= n; + ++j) + { + if (failed(map.remove(static_cast(j), j + 1))) + { + ++fails[idx]; + } + } + }); + } + + ready_promise.set_value(); + wait_threads(); + + for (std::size_t i = 0; i < fails.size(); ++i) + { + ASSERT_EQ(fails[i].load(), block_size(i, nbr_threads / 2, map_size)); + } + + for (std::size_t i = 0; i < map_size; ++i) + { + ASSERT_FALSE(map.get(static_cast(i)).has_value()); + } +} + +TEST_P(WaitFreeHashMapMultiThreadTest, MixedOperation) +{ + wfc::unordered_map custom_map{4, 16, 65535}; + constexpr std::size_t nbr_threads = 3; + std::array updated{}; + std::array removed{}; + + for (std::size_t i = 0; i < nbr_threads; ++i) + { + threads.emplace_back([this, &custom_map, i]() { + futur.wait(); + std::size_t idx = i; + + for (std::size_t j = block_low(idx, nbr_threads, map_size - 1), + n = block_high(idx, nbr_threads, map_size - 1); + j <= n; + ++j) + { + ASSERT_EQ(custom_map.insert(static_cast(j), 2 * j), wfc::operation_result::success); + } + }); + + threads.emplace_back([this, &custom_map, i, &updated]() { + futur.wait(); + std::size_t idx = i; + + for (std::size_t j = block_low(idx, nbr_threads, map_size - 1), + n = block_high(idx, nbr_threads, map_size - 1); + j <= n; + ++j) + { + if (succeeded(custom_map.update(static_cast(j), 4 * j))) + { + updated[j] = true; + } + } + }); + + threads.emplace_back([this, &custom_map, i, &removed]() { + futur.wait(); + std::size_t idx = i; + + for (std::size_t j = block_low(idx, nbr_threads, map_size - 1), + n = block_high(idx, nbr_threads, map_size - 1); + j <= n; + ++j) + { + if (succeeded(custom_map.remove(static_cast(j)))) + { + removed[j] = true; + } + } + }); + } + + ready_promise.set_value(); + wait_threads(); + + for (std::size_t i = 0; i < map_size - 1; ++i) { + if (updated[i] && !removed[i]) { + ASSERT_EQ(custom_map.get(static_cast(i)).value(), 4 * i); + } else if (removed[i]) { + ASSERT_FALSE(custom_map.get(static_cast(i)).has_value()); + } else { + ASSERT_EQ(custom_map.get(static_cast(i)).value(), 2 * i); + } + } +} + +INSTANTIATE_TEST_CASE_P(Instantiation, WaitFreeHashMapMultiThreadTest, ::testing::Range(1, 100),); diff --git a/tests/unordered_map/single_thread_test.cpp b/tests/unordered_map/single_thread_test.cpp new file mode 100644 index 0000000..d375945 --- /dev/null +++ b/tests/unordered_map/single_thread_test.cpp @@ -0,0 +1,241 @@ +#include + +#include + +#include + +TEST(WaitFreeHashMap, Construction) +{ + wfc::unordered_map map(4); + + ASSERT_TRUE(map.is_empty()); + ASSERT_EQ(map.size(), 0); +} + +TEST(WaitFreeHashMap, Insertion) +{ + wfc::unordered_map map(4); + + ASSERT_EQ(map.insert(0, 0), wfc::operation_result::success); + ASSERT_EQ(map.insert(0, 0), wfc::operation_result::already_present); + + ASSERT_FALSE(map.is_empty()); + ASSERT_EQ(map.size(), 1); + + map.insert(1, 0); + ASSERT_EQ(map.size(), 2); +} + +TEST(WaitFreeHashMap, EmptyGet) +{ + wfc::unordered_map map(4); + + std::optional r = map.get(0); + + ASSERT_FALSE(r.has_value()); +} + +TEST(WaitFreeHashMap, Get) +{ + wfc::unordered_map map(4); + + map.insert(0, 1); + std::optional r = map.get(0); + + ASSERT_TRUE(r.has_value()); + ASSERT_EQ(*r, 1); +} + +TEST(WaitFreeHashMap, Update) +{ + wfc::unordered_map map(4); + + ASSERT_EQ(map.update(0, 5), wfc::operation_result::element_not_found); + + map.insert(0, 1); + ASSERT_EQ(*map.get(0), 1); + ASSERT_EQ(map.update(0, 5), wfc::operation_result::success); + ASSERT_EQ(*map.get(0), 5); + + map.insert(2, 15); + ASSERT_EQ(map.update(2, 15, 15), wfc::operation_result::success); + ASSERT_EQ(map.update(2, 5, 15), wfc::operation_result::success); + ASSERT_EQ(map.update(2, 0, 0), wfc::operation_result::expected_value_mismatch); + ASSERT_EQ(*map.get(2), 5); + + map.remove(2); + ASSERT_EQ(map.update(2, 0), wfc::operation_result::element_not_found); +} + +TEST(WaitFreeHashMap, Remove) +{ + wfc::unordered_map map(4); + + ASSERT_EQ(map.remove(0, 5), wfc::operation_result::element_not_found); + + map.insert(0, 3); + map.insert(1, 2); + + ASSERT_EQ(map.remove(0), wfc::operation_result::success); + ASSERT_EQ(map.remove(0), wfc::operation_result::element_not_found); + ASSERT_FALSE(map.get(0).has_value()); + + ASSERT_EQ(map.remove(1, 3), wfc::operation_result::expected_value_mismatch); + ASSERT_TRUE(map.get(1).has_value()); + ASSERT_EQ(map.remove(1, 2), wfc::operation_result::success); + ASSERT_FALSE(map.get(1).has_value()); +} + +TEST(WaitFreeHashMap, FullHashMapUpdate) +{ + wfc::unordered_map map(4); + + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + map.insert(static_cast(i), i); + } + + ASSERT_EQ(map.size(), std::numeric_limits::max() + 1); + + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + ASSERT_EQ(map.update(static_cast(i), i * 2, i), wfc::operation_result::success); + } + + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + ASSERT_EQ(*map.get(static_cast(i)), i * 2); + } +} + +TEST(WaitFreeHashMap, FullHashMapRemove) +{ + wfc::unordered_map map(4); + + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + map.insert(static_cast(i), i); + } + + ASSERT_EQ(map.size(), std::numeric_limits::max() + 1); + + // remove half of the elements (alternate) + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + if (i % 2 == 0) + { + ASSERT_EQ(map.remove(static_cast(i), i), wfc::operation_result::success); + } + } + + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + if (i % 2 == 0) + { + ASSERT_FALSE(map.get(static_cast(i)).has_value()); + } + else + { + ASSERT_TRUE(map.get(static_cast(i)).has_value()); + } + } +} + +TEST(WaitFreeHashMap, FullHashMapGet) +{ + wfc::unordered_map map(4); + + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + ASSERT_EQ(map.insert(static_cast(i), static_cast(i)), + wfc::operation_result::success); + } + + ASSERT_EQ(map.size(), std::numeric_limits::max() + 1); + + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + unsigned char value = map.get(static_cast(i)).value(); + ASSERT_EQ(value, i); + } +} + +TEST(WaitFreeHashMap, FullHashMapVisist) +{ + wfc::unordered_map map(4); + + for (std::size_t i = 0; i <= std::numeric_limits::max(); ++i) + { + map.insert(static_cast(i), static_cast(i)); + } + + ASSERT_EQ(map.size(), std::numeric_limits::max() + 1); + + int nbr_value = 0; + map.visit([&nbr_value](std::pair p) { + ++nbr_value; + ASSERT_EQ(p.first, p.second); + }); + + ASSERT_EQ(nbr_value, std::numeric_limits::max() + 1); +} + +struct FatKey +{ + std::size_t a; + std::size_t b; +}; + +struct FatKeyHash +{ + std::size_t p1; + std::size_t p2; + + FatKeyHash operator>>(int shift) + { + return FatKeyHash{p1 >> shift, (p1 << (64 - shift)) + (p2 >> shift)}; + } + + FatKeyHash& operator>>=(int shift) + { + *this = *this >> shift; + return *this; + } + + std::size_t operator&(std::size_t arg) + { + return p2 & (arg); + } +}; + +bool operator==(const FatKeyHash& a, const FatKeyHash& b); + +bool operator==(const FatKeyHash& a, const FatKeyHash& b) +{ + return a.p1 == b.p1 && a.p2 == b.p2; +} + +struct Hash +{ + FatKeyHash operator()(const FatKey& k) const noexcept + { + return FatKeyHash{k.a, k.b}; + } +}; + +TEST(WaitFreeHashMap, BigHash) +{ + wfc::unordered_map m(4); + + ASSERT_EQ(m.insert(FatKey{0, 0}, 1), wfc::operation_result::success); + ASSERT_EQ(m.get(FatKey{0, 0}).value(), 1); + ASSERT_EQ(m.update(FatKey{0, 0}, 2), wfc::operation_result::success); + + ASSERT_EQ(m.remove(FatKey{0, 0}, 2), wfc::operation_result::success); + + ASSERT_EQ(m.insert(FatKey{0, 0}, 1), wfc::operation_result::success); + ASSERT_EQ(m.insert(FatKey{1, 0}, 10), wfc::operation_result::success); + + ASSERT_EQ(m.get(FatKey{0, 0}).value(), 1); + ASSERT_EQ(m.get(FatKey{1, 0}).value(), 10); +} diff --git a/tests/utility/math.cpp b/tests/utility/math.cpp new file mode 100644 index 0000000..1196550 --- /dev/null +++ b/tests/utility/math.cpp @@ -0,0 +1,27 @@ +#include + +#include + +TEST(Utilty, IsPowerOfTwo) +{ + ASSERT_TRUE(wfc::is_power_of_two(2)); + ASSERT_FALSE(wfc::is_power_of_two(3)); +} + +TEST(Utilty, Log2OfPowerOfTwo) +{ + ASSERT_EQ(wfc::log2_of_power_of_two(2U), 1U); + ASSERT_EQ(wfc::log2_of_power_of_two(4U), 2U); + ASSERT_EQ(wfc::log2_of_power_of_two(8U), 3U); +} + +#ifndef NDEBUG +# define Log2OfPowerOfTwoDeath Log2OfPowerOfTwoDeath +#else +# define Log2OfPowerOfTwoDeath DISABLED_Log2OfPowerOfTwoDeath +#endif + +TEST(Utility, Log2OfPowerOfTwoDeath) +{ + EXPECT_DEATH(wfc::log2_of_power_of_two(3U), ""); +} \ No newline at end of file