diff --git a/CMakeLists.txt b/CMakeLists.txt index 386cc725..a95f9bef 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,3 @@ - cmake_minimum_required(VERSION 3.5.1) cmake_policy(SET CMP0048 NEW) # enable project VERSION cmake_policy(SET CMP0056 NEW) # honor link flags in try_compile() @@ -6,20 +5,22 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") project(immer VERSION 0.8.0) -if (NOT MSVC) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-extended-offsetof -Wno-c++17-extensions -Wno-c++1z-extensions -Wno-unknown-warning-option -Wno-type-limits") +if(NOT MSVC) + set(CMAKE_CXX_FLAGS + "${CMAKE_CXX_FLAGS} -Wall -Wextra -pedantic -Wno-unused-parameter -Wno-extended-offsetof -Wno-c++17-extensions -Wno-c++1z-extensions -Wno-unknown-warning-option -Wno-type-limits" + ) endif() set(CMAKE_EXPORT_COMPILE_COMMANDS on) set(CMAKE_CXX_EXTENSIONS off) -if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") +if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Qunused-arguments") endif() include(GNUInstallDirs) include(ImmerUtils) -# Options -# ======= +# Options +# ======= option(ENABLE_ASAN "compile with address sanitizer enabled") option(ENABLE_MSAN "compile with memory sanitizer enabled") @@ -36,37 +37,40 @@ option(ENABLE_GUILE "enable building guile module" off) option(ENABLE_BOOST_COROUTINE "run benchmarks with boost coroutine" off) option(immer_BUILD_TESTS "Build tests" ON) +option(immer_BUILD_PERSIST_TESTS "Build experimental persist tests" off) option(immer_BUILD_EXAMPLES "Build examples" ON) option(immer_BUILD_DOCS "Build docs" ON) option(immer_BUILD_EXTRAS "Build extras" ON) option(immer_INSTALL_FUZZERS "Install fuzzers" off) -set(CXX_STANDARD 14 CACHE STRING "c++ standard number") +set(CXX_STANDARD + 14 + CACHE STRING "c++ standard number") set(CMAKE_CXX_STANDARD ${CXX_STANDARD}) set(CMAKE_CXX_STANDARD_REQUIRED on) -if (ENABLE_ASAN) +if(ENABLE_ASAN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address") endif() -if (ENABLE_LSAN) +if(ENABLE_LSAN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=leak") endif() -if (ENABLE_UBSAN) +if(ENABLE_UBSAN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined") endif() -if (ENABLE_MSAN) +if(ENABLE_MSAN) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=memory") endif() -if (NOT MSVC AND NOT DISABLE_WERROR) +if(NOT MSVC AND NOT DISABLE_WERROR) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Werror") endif() -# Dependencies -# ============ +# Dependencies +# ============ -if (ENABLE_BOOST_COROUTINE) +if(ENABLE_BOOST_COROUTINE) set(immer_boost_components coroutine) endif() @@ -75,7 +79,7 @@ find_package(BoehmGC) find_package(Boost 1.56 COMPONENTS ${immer_boost_components}) find_program(CCACHE ccache) -if (CCACHE) +if(CCACHE) message(STATUS "Using ccache: ${CCACHE}") set(CMAKE_CXX_COMPILER_LAUNCHER ${CCACHE}) set(CMAKE_CXX_LINKER_LAUNCHER ${CCACHE}) @@ -83,19 +87,20 @@ else() message(STATUS "Could not find ccache") endif() -if (NOT BOEHM_GC_FOUND) +if(NOT BOEHM_GC_FOUND) set(BOEHM_GC_LIBRARIES "") endif() -# Targets -# ======= +# Targets +# ======= # the library add_library(immer INTERFACE) -target_include_directories(immer INTERFACE - $ - $ - $) +target_include_directories( + immer + INTERFACE $ + $ + $) install(TARGETS immer EXPORT ImmerConfig) install(EXPORT ImmerConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer") install(DIRECTORY immer DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") @@ -104,62 +109,62 @@ include(CMakePackageConfigHelpers) write_basic_package_version_file( "${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake" VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion ) + COMPATIBILITY SameMajorVersion) -install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake" DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer" ) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer") # development target to be used in tests, examples, benchmarks... -immer_canonicalize_cmake_booleans( - DISABLE_FREE_LIST - DISABLE_THREAD_SAFETY - CHECK_SLOW_TESTS) +immer_canonicalize_cmake_booleans(DISABLE_FREE_LIST DISABLE_THREAD_SAFETY + CHECK_SLOW_TESTS) add_library(immer-dev INTERFACE) -target_include_directories(immer-dev SYSTEM INTERFACE - ${Boost_INCLUDE_DIR} - ${BOEHM_GC_INCLUDE_DIR} - ${CMAKE_CURRENT_SOURCE_DIR}/tools/include) -target_link_libraries(immer-dev INTERFACE - immer - ${BOEHM_GC_LIBRARIES} - ${CMAKE_THREAD_LIBS_INIT}) -target_compile_definitions(immer-dev INTERFACE - -DIMMER_CXX_STANDARD=${CXX_STANDARD} - -DIMMER_HAS_LIBGC=1 - -DIMMER_NO_FREE_LIST=${DISABLE_FREE_LIST} - -DIMMER_NO_THREAD_SAFETY=${DISABLE_THREAD_SAFETY} - -DIMMER_SLOW_TESTS=${CHECK_SLOW_TESTS} - -DFMT_HEADER_ONLY=1) -if (ENABLE_COVERAGE) +target_include_directories( + immer-dev SYSTEM INTERFACE ${Boost_INCLUDE_DIR} ${BOEHM_GC_INCLUDE_DIR} + ${CMAKE_CURRENT_SOURCE_DIR}/tools/include) +target_link_libraries(immer-dev INTERFACE immer ${BOEHM_GC_LIBRARIES} + ${CMAKE_THREAD_LIBS_INIT}) +target_compile_definitions( + immer-dev + INTERFACE -DIMMER_CXX_STANDARD=${CXX_STANDARD} + -DIMMER_HAS_LIBGC=1 + -DIMMER_NO_FREE_LIST=${DISABLE_FREE_LIST} + -DIMMER_NO_THREAD_SAFETY=${DISABLE_THREAD_SAFETY} + -DIMMER_SLOW_TESTS=${CHECK_SLOW_TESTS} + -DFMT_HEADER_ONLY=1) +if(ENABLE_COVERAGE) target_compile_options(immer-dev INTERFACE "--coverage") target_link_libraries(immer-dev INTERFACE "--coverage") endif() -# Testing -# ======= +# Testing +# ======= -if (immer_BUILD_TESTS OR immer_BUILD_EXAMPLES OR immer_BUILD_EXTRAS) - add_custom_target(check - COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure - WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} - COMMENT "Build and run all the tests and examples.") +if(immer_BUILD_TESTS + OR immer_BUILD_EXAMPLES + OR immer_BUILD_EXTRAS) + add_custom_target( + check + COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Build and run all the tests and examples.") endif() -if (immer_BUILD_TESTS) +if(immer_BUILD_TESTS) enable_testing() add_subdirectory(test) add_subdirectory(benchmark) endif() -if (immer_BUILD_EXAMPLES) +if(immer_BUILD_EXAMPLES) add_subdirectory(example) endif() -if (immer_BUILD_DOCS) +if(immer_BUILD_DOCS) add_subdirectory(doc) endif() -if (immer_BUILD_EXTRAS) +if(immer_BUILD_EXTRAS) add_subdirectory(extra/fuzzer) add_subdirectory(extra/python) add_subdirectory(extra/guile) diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 861157c5..2dac325d 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -1,35 +1,37 @@ - -# Config -# ====== +# Config +# ====== option(CHECK_BENCHMARKS "Run benchmarks on check target" off) option(BENCHMARK_DISABLE_GC "Disable gc during a measurement") -set(BENCHMARK_PARAM "N:1000" CACHE STRING "Benchmark parameters") -set(BENCHMARK_SAMPLES "20" CACHE STRING "Benchmark samples") +set(BENCHMARK_PARAM + "N:1000" + CACHE STRING "Benchmark parameters") +set(BENCHMARK_SAMPLES + "20" + CACHE STRING "Benchmark samples") -# Dependencies -# ============ +# Dependencies +# ============ find_package(RRB) -if (NOT RRB_FOUND) +if(NOT RRB_FOUND) message(STATUS "Disabling benchmarks") return() endif() -# These are expected to be in the include path, the nix-shell -# environment installs them: +# These are expected to be in the include path, the nix-shell environment +# installs them: # -# https://github.com/marcusz/steady -# https://github.com/deepsea-inria/chunkedseq.git -# https://github.com/rsms/immutable-cpp.git +# https://github.com/marcusz/steady +# https://github.com/deepsea-inria/chunkedseq.git +# https://github.com/rsms/immutable-cpp.git -# Targets -# ======= +# Targets +# ======= -add_custom_target(benchmarks - COMMENT "Build all benchmarks.") +add_custom_target(benchmarks COMMENT "Build all benchmarks.") execute_process( COMMAND git log -1 --format=%h @@ -43,7 +45,7 @@ execute_process( OUTPUT_VARIABLE immer_git_status OUTPUT_STRIP_TRAILING_WHITESPACE) -if (NOT immer_git_status STREQUAL "") +if(NOT immer_git_status STREQUAL "") set(immer_git_commit_hash "${immer_git_commit_hash}+") endif() @@ -52,7 +54,8 @@ site_name(immer_hostname) get_filename_component(immer_compiler_name "${CMAKE_CXX_COMPILER}" NAME) set(immer_benchmark_report_base_dir "${CMAKE_SOURCE_DIR}/reports") -set(immer_benchmark_report_dir "${immer_benchmark_report_base_dir}/report_\ +set(immer_benchmark_report_dir + "${immer_benchmark_report_base_dir}/report_\ ${immer_git_commit_hash}_\ ${immer_hostname}_\ ${immer_compiler_name}_\ @@ -75,9 +78,9 @@ if(CHECK_BENCHMARKS) add_dependencies(check benchmarks) endif() -add_custom_target(benchmark-report-dir - COMMAND ${CMAKE_COMMAND} - -E make_directory ${immer_benchmark_report_dir}) +add_custom_target( + benchmark-report-dir COMMAND ${CMAKE_COMMAND} -E make_directory + ${immer_benchmark_report_dir}) file(GLOB_RECURSE immer_benchmarks "*.cpp") foreach(_file IN LISTS immer_benchmarks) @@ -87,36 +90,41 @@ foreach(_file IN LISTS immer_benchmarks) add_dependencies(benchmarks ${_target}) add_dependencies(${_target} benchmark-report-dir) target_compile_options(${_target} PUBLIC -Wno-unused-function) - target_compile_definitions(${_target} PUBLIC - NONIUS_RUNNER - IMMER_BENCHMARK_LIBRRB=1 - IMMER_BENCHMARK_STEADY=1 - IMMER_BENCHMARK_EXPERIMENTAL=0 - IMMER_BENCHMARK_DISABLE_GC=${BENCHMARK_DISABLE_GC} - IMMER_BENCHMARK_BOOST_COROUTINE=${ENABLE_BOOST_COROUTINE}) - target_link_libraries(${_target} PUBLIC - immer-dev - ${RRB_LIBRARIES}) - target_include_directories(${_target} SYSTEM PUBLIC - ${RRB_INCLUDE_DIR}) + target_compile_definitions( + ${_target} + PUBLIC NONIUS_RUNNER + IMMER_BENCHMARK_LIBRRB=1 + IMMER_BENCHMARK_STEADY=1 + IMMER_BENCHMARK_EXPERIMENTAL=0 + IMMER_BENCHMARK_DISABLE_GC=${BENCHMARK_DISABLE_GC} + IMMER_BENCHMARK_BOOST_COROUTINE=${ENABLE_BOOST_COROUTINE}) + target_link_libraries(${_target} PUBLIC immer-dev ${RRB_LIBRARIES}) + target_include_directories(${_target} SYSTEM PUBLIC ${RRB_INCLUDE_DIR}) if(CHECK_BENCHMARKS) - add_test("benchmark/${_output}" "${CMAKE_SOURCE_DIR}/tools/with-tee.bash" + add_test( + "benchmark/${_output}" + "${CMAKE_SOURCE_DIR}/tools/with-tee.bash" ${immer_benchmark_report_dir}/${_target}.out - "${CMAKE_CURRENT_BINARY_DIR}/${_output}" -v - -t ${_target} - -r html - -s ${BENCHMARK_SAMPLES} - -p ${BENCHMARK_PARAM} - -o ${immer_benchmark_report_dir}/${_target}.html) + "${CMAKE_CURRENT_BINARY_DIR}/${_output}" + -v + -t + ${_target} + -r + html + -s + ${BENCHMARK_SAMPLES} + -p + ${BENCHMARK_PARAM} + -o + ${immer_benchmark_report_dir}/${_target}.html) endif() endforeach() -add_custom_target(upload-benchmark-reports - COMMAND - rsync -av ${immer_benchmark_report_base_dir} - root@sinusoid.es:/var/lib/syncthing/public/misc/immer/) +add_custom_target( + upload-benchmark-reports + COMMAND rsync -av ${immer_benchmark_report_base_dir} + root@sinusoid.es:/var/lib/syncthing/public/misc/immer/) -add_custom_target(copy-benchmark-reports - COMMAND - rsync -av ${immer_benchmark_report_base_dir} - ~/public/misc/immer/) +add_custom_target( + copy-benchmark-reports COMMAND rsync -av ${immer_benchmark_report_base_dir} + ~/public/misc/immer/) diff --git a/benchmark/extra/refcounting.cpp b/benchmark/extra/refcounting.cpp index c7c5183e..aeee3f60 100644 --- a/benchmark/extra/refcounting.cpp +++ b/benchmark/extra/refcounting.cpp @@ -8,15 +8,15 @@ #include -#include #include +#include +#include #include #include #include #include #include -#include #include NONIUS_PARAM(N, std::size_t{1000}) @@ -29,64 +29,55 @@ struct object_t : immer::detail::ref_count_base auto make_data() { auto objs = std::array, benchmark_size>(); - std::generate(objs.begin(), objs.end(), [] { - return std::make_unique(); - }); + std::generate( + objs.begin(), objs.end(), [] { return std::make_unique(); }); auto refs = std::array(); std::transform(objs.begin(), objs.end(), refs.begin(), [](auto& obj) { return obj.get(); }); - return make_pair(std::move(objs), - std::move(refs)); + return make_pair(std::move(objs), std::move(refs)); } -NONIUS_BENCHMARK("intrusive_ptr", [] (nonius::chronometer meter) -{ +NONIUS_BENCHMARK("intrusive_ptr", [](nonius::chronometer meter) { auto arr = std::array, benchmark_size>{}; - auto storage = std::vector< - nonius::storage_for< - std::array, benchmark_size>>> ( - meter.runs()); - std::generate(arr.begin(), arr.end(), [] { - return new object_t{}; - }); - meter.measure([&] (int i) { - storage[i].construct(arr); - }); + auto storage = std::vector, benchmark_size>>>( + meter.runs()); + std::generate(arr.begin(), arr.end(), [] { return new object_t{}; }); + meter.measure([&](int i) { storage[i].construct(arr); }); }) -NONIUS_BENCHMARK("generic", [] (nonius::chronometer meter) -{ - auto data = make_data(); +NONIUS_BENCHMARK("generic", [](nonius::chronometer meter) { + auto data = make_data(); auto& refs = data.second; object_t* r[benchmark_size]; meter.measure([&] { - std::transform(refs.begin(), refs.end(), r, [] (auto& p) { - if (p) p->ref_count.fetch_add(1, std::memory_order_relaxed); + std::transform(refs.begin(), refs.end(), r, [](auto& p) { + if (p) + p->ref_count.fetch_add(1, std::memory_order_relaxed); return p; }); return r; }); }) -NONIUS_BENCHMARK("manual", [] (nonius::chronometer meter) -{ - auto data = make_data(); +NONIUS_BENCHMARK("manual", [](nonius::chronometer meter) { + auto data = make_data(); auto& refs = data.second; object_t* r[benchmark_size]; meter.measure([&] { for (auto& p : refs) - if (p) p->ref_count.fetch_add(1, std::memory_order_relaxed); + if (p) + p->ref_count.fetch_add(1, std::memory_order_relaxed); std::copy(refs.begin(), refs.end(), r); return r; }); }) -NONIUS_BENCHMARK("manual - unroll", [] (nonius::chronometer meter) -{ - auto data = make_data(); +NONIUS_BENCHMARK("manual - unroll", [](nonius::chronometer meter) { + auto data = make_data(); auto& refs = data.second; object_t* r[benchmark_size]; @@ -103,9 +94,8 @@ NONIUS_BENCHMARK("manual - unroll", [] (nonius::chronometer meter) }); }) -NONIUS_BENCHMARK("manual - nocheck", [] (nonius::chronometer meter) -{ - auto data = make_data(); +NONIUS_BENCHMARK("manual - nocheck", [](nonius::chronometer meter) { + auto data = make_data(); auto& refs = data.second; object_t* r[benchmark_size]; @@ -117,9 +107,8 @@ NONIUS_BENCHMARK("manual - nocheck", [] (nonius::chronometer meter) }); }) -NONIUS_BENCHMARK("manual - constant", [] (nonius::chronometer meter) -{ - auto data = make_data(); +NONIUS_BENCHMARK("manual - constant", [](nonius::chronometer meter) { + auto data = make_data(); auto& refs = data.second; object_t* r[benchmark_size]; @@ -131,15 +120,15 @@ NONIUS_BENCHMARK("manual - constant", [] (nonius::chronometer meter) }); }) -NONIUS_BENCHMARK("manual - memcopy", [] (nonius::chronometer meter) -{ - auto data = make_data(); +NONIUS_BENCHMARK("manual - memcopy", [](nonius::chronometer meter) { + auto data = make_data(); auto& refs = data.second; object_t* r[benchmark_size]; meter.measure([&] { for (auto& p : refs) - if (p) p->ref_count.fetch_add(1, std::memory_order_relaxed); + if (p) + p->ref_count.fetch_add(1, std::memory_order_relaxed); std::memcpy(r, &refs[0], sizeof(object_t*) * benchmark_size); return r; }); diff --git a/benchmark/set/access.hpp b/benchmark/set/access.hpp index 8605c0ff..a4efc044 100644 --- a/benchmark/set/access.hpp +++ b/benchmark/set/access.hpp @@ -10,21 +10,21 @@ #include "benchmark/config.hpp" -#include -#include // Phil Nash #include +#include // Phil Nash +#include #include #include namespace { -template +template auto make_generator_ranged(std::size_t runs) { assert(runs > 0); auto engine = std::default_random_engine{13}; - auto dist = std::uniform_int_distribution{0, (T)runs-1}; - auto r = std::vector(runs); + auto dist = std::uniform_int_distribution{0, (T) runs - 1}; + auto r = std::vector(runs); std::generate_n(r.begin(), runs, std::bind(dist, engine)); return r; } @@ -32,8 +32,7 @@ auto make_generator_ranged(std::size_t runs) template auto benchmark_access_std() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto g1 = Generator{}(n); auto g2 = make_generator_ranged(n); @@ -55,9 +54,8 @@ auto benchmark_access_std() template auto benchmark_access_hamt() { - return [] (nonius::chronometer meter) - { - auto n = meter.param(); + return [](nonius::chronometer meter) { + auto n = meter.param(); auto g1 = Generator{}(n); auto g2 = make_generator_ranged(n); @@ -68,7 +66,7 @@ auto benchmark_access_hamt() measure(meter, [&] { auto c = 0u; for (auto i = 0u; i < n; ++i) { - auto& x = g1[g2[i]]; + auto& x = g1[g2[i]]; auto leaf = v.find(x).leaf(); c += !!(leaf && leaf->find(x)); } @@ -78,13 +76,11 @@ auto benchmark_access_hamt() }; } - template auto benchmark_access() { - return [] (nonius::chronometer meter) - { - auto n = meter.param(); + return [](nonius::chronometer meter) { + auto n = meter.param(); auto g1 = Generator{}(n); auto g2 = make_generator_ranged(n); @@ -105,10 +101,9 @@ auto benchmark_access() template auto benchmark_bad_access_std() { - return [] (nonius::chronometer meter) - { - auto n = meter.param(); - auto g1 = Generator{}(n*2); + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g1 = Generator{}(n * 2); auto v = Set{}; for (auto i = 0u; i < n; ++i) @@ -117,7 +112,7 @@ auto benchmark_bad_access_std() measure(meter, [&] { auto c = 0u; for (auto i = 0u; i < n; ++i) - c += v.count(g1[n+i]); + c += v.count(g1[n + i]); volatile auto r = c; return r; }); @@ -127,10 +122,9 @@ auto benchmark_bad_access_std() template auto benchmark_bad_access_hamt() { - return [] (nonius::chronometer meter) - { - auto n = meter.param(); - auto g1 = Generator{}(n*2); + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g1 = Generator{}(n * 2); auto v = Set{}; for (auto i = 0u; i < n; ++i) @@ -139,7 +133,7 @@ auto benchmark_bad_access_hamt() measure(meter, [&] { auto c = 0u; for (auto i = 0u; i < n; ++i) { - auto& x = g1[n+i]; + auto& x = g1[n + i]; auto leaf = v.find(x).leaf(); c += !!(leaf && leaf->find(x)); } @@ -149,14 +143,12 @@ auto benchmark_bad_access_hamt() }; } - template auto benchmark_bad_access() { - return [] (nonius::chronometer meter) - { - auto n = meter.param(); - auto g1 = Generator{}(n*2); + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g1 = Generator{}(n * 2); auto v = Set{}; for (auto i = 0u; i < n; ++i) @@ -165,7 +157,7 @@ auto benchmark_bad_access() measure(meter, [&] { auto c = 0u; for (auto i = 0u; i < n; ++i) - c += v.count(g1[n+i]); + c += v.count(g1[n + i]); volatile auto r = c; return r; }); diff --git a/benchmark/set/access.ipp b/benchmark/set/access.ipp index f026d9cf..69740a9e 100644 --- a/benchmark/set/access.ipp +++ b/benchmark/set/access.ipp @@ -13,7 +13,9 @@ #endif using generator__ = GENERATOR_T; -using t__ = typename decltype(generator__{}(0))::value_type; +using t__ = typename decltype(generator__{}(0))::value_type; + +// clang-format off NONIUS_BENCHMARK("std::set", benchmark_access_std>()) NONIUS_BENCHMARK("std::unordered_set", benchmark_access_std>()) @@ -28,3 +30,5 @@ NONIUS_BENCHMARK("bad/boost::flat_set", benchmark_bad_access_std>()) NONIUS_BENCHMARK("bad/immer::set/5B", benchmark_bad_access,std::equal_to,def_memory,5>>()) NONIUS_BENCHMARK("bad/immer::set/4B", benchmark_bad_access,std::equal_to,def_memory,4>>()) + +// clang-format on diff --git a/benchmark/set/iter.hpp b/benchmark/set/iter.hpp index 91dd4864..bba4ac66 100644 --- a/benchmark/set/iter.hpp +++ b/benchmark/set/iter.hpp @@ -10,30 +10,27 @@ #include "benchmark/config.hpp" -#include -#include -#include -#include // Phil Nash #include +#include // Phil Nash +#include +#include +#include +#include #include #include -#include namespace { template struct iter_step { - unsigned operator() (unsigned x, const T& y) const - { - return x + y; - } + unsigned operator()(unsigned x, const T& y) const { return x + y; } }; template <> struct iter_step { - unsigned operator() (unsigned x, const std::string& y) const + unsigned operator()(unsigned x, const std::string& y) const { return x + (unsigned) y.size(); } @@ -42,7 +39,7 @@ struct iter_step template <> struct iter_step> { - unsigned operator() (unsigned x, const immer::box& y) const + unsigned operator()(unsigned x, const immer::box& y) const { return x + (unsigned) y->size(); } @@ -51,8 +48,7 @@ struct iter_step> template auto benchmark_access_std_iter() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto g1 = Generator{}(n); @@ -71,9 +67,8 @@ auto benchmark_access_std_iter() template auto benchmark_access_reduce() { - return [] (nonius::chronometer meter) - { - auto n = meter.param(); + return [](nonius::chronometer meter) { + auto n = meter.param(); auto g1 = Generator{}(n); auto v = Set{}; @@ -91,9 +86,8 @@ auto benchmark_access_reduce() template auto benchmark_access_iter() { - return [] (nonius::chronometer meter) - { - auto n = meter.param(); + return [](nonius::chronometer meter) { + auto n = meter.param(); auto g1 = Generator{}(n); auto v = Set{}; diff --git a/benchmark/set/iter.ipp b/benchmark/set/iter.ipp index f8c3cb96..2c143107 100644 --- a/benchmark/set/iter.ipp +++ b/benchmark/set/iter.ipp @@ -13,7 +13,9 @@ #endif using generator__ = GENERATOR_T; -using t__ = typename decltype(generator__{}(0))::value_type; +using t__ = typename decltype(generator__{}(0))::value_type; + +// clang-format off NONIUS_BENCHMARK("iter/std::set", benchmark_access_std_iter>()) NONIUS_BENCHMARK("iter/std::unordered_set", benchmark_access_std_iter>()) @@ -23,3 +25,5 @@ NONIUS_BENCHMARK("iter/immer::set/5B", benchmark_access_iter,std::equal_to,def_memory,4>>()) NONIUS_BENCHMARK("reduce/immer::set/5B", benchmark_access_reduce,std::equal_to,def_memory,5>>()) NONIUS_BENCHMARK("reduce/immer::set/4B", benchmark_access_reduce,std::equal_to,def_memory,4>>()) + +// clang-format on diff --git a/benchmark/set/string-box/access.cpp b/benchmark/set/string-box/access.cpp index c3deffbb..13f87d83 100644 --- a/benchmark/set/string-box/access.cpp +++ b/benchmark/set/string-box/access.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../access.ipp" diff --git a/benchmark/set/string-box/insert.cpp b/benchmark/set/string-box/insert.cpp index c5762498..7b751877 100644 --- a/benchmark/set/string-box/insert.cpp +++ b/benchmark/set/string-box/insert.cpp @@ -8,4 +8,5 @@ #define DISABLE_GC_BENCHMARKS #include "generator.ipp" + #include "../insert.ipp" diff --git a/benchmark/set/string-box/iter.cpp b/benchmark/set/string-box/iter.cpp index 83e57e78..48356c38 100644 --- a/benchmark/set/string-box/iter.cpp +++ b/benchmark/set/string-box/iter.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../iter.ipp" diff --git a/benchmark/set/string-long/access.cpp b/benchmark/set/string-long/access.cpp index c3deffbb..13f87d83 100644 --- a/benchmark/set/string-long/access.cpp +++ b/benchmark/set/string-long/access.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../access.ipp" diff --git a/benchmark/set/string-long/insert.cpp b/benchmark/set/string-long/insert.cpp index c5762498..7b751877 100644 --- a/benchmark/set/string-long/insert.cpp +++ b/benchmark/set/string-long/insert.cpp @@ -8,4 +8,5 @@ #define DISABLE_GC_BENCHMARKS #include "generator.ipp" + #include "../insert.ipp" diff --git a/benchmark/set/string-long/iter.cpp b/benchmark/set/string-long/iter.cpp index 83e57e78..48356c38 100644 --- a/benchmark/set/string-long/iter.cpp +++ b/benchmark/set/string-long/iter.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../iter.ipp" diff --git a/benchmark/set/string-short/access.cpp b/benchmark/set/string-short/access.cpp index c3deffbb..13f87d83 100644 --- a/benchmark/set/string-short/access.cpp +++ b/benchmark/set/string-short/access.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../access.ipp" diff --git a/benchmark/set/string-short/insert.cpp b/benchmark/set/string-short/insert.cpp index 26dfbce2..718aaad4 100644 --- a/benchmark/set/string-short/insert.cpp +++ b/benchmark/set/string-short/insert.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../insert.ipp" diff --git a/benchmark/set/string-short/iter.cpp b/benchmark/set/string-short/iter.cpp index 83e57e78..48356c38 100644 --- a/benchmark/set/string-short/iter.cpp +++ b/benchmark/set/string-short/iter.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../iter.ipp" diff --git a/benchmark/set/unsigned/access.cpp b/benchmark/set/unsigned/access.cpp index c3deffbb..13f87d83 100644 --- a/benchmark/set/unsigned/access.cpp +++ b/benchmark/set/unsigned/access.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../access.ipp" diff --git a/benchmark/set/unsigned/insert.cpp b/benchmark/set/unsigned/insert.cpp index 26dfbce2..718aaad4 100644 --- a/benchmark/set/unsigned/insert.cpp +++ b/benchmark/set/unsigned/insert.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../insert.ipp" diff --git a/benchmark/set/unsigned/iter.cpp b/benchmark/set/unsigned/iter.cpp index 83e57e78..48356c38 100644 --- a/benchmark/set/unsigned/iter.cpp +++ b/benchmark/set/unsigned/iter.cpp @@ -7,4 +7,5 @@ // #include "generator.ipp" + #include "../iter.ipp" diff --git a/benchmark/vector/access.hpp b/benchmark/vector/access.hpp index 4dff7a66..7e23e543 100644 --- a/benchmark/vector/access.hpp +++ b/benchmark/vector/access.hpp @@ -21,15 +21,14 @@ namespace { template auto benchmark_access_reduce_chunkedseq() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; for (auto i = 0u; i < n; ++i) v.push_back(i); return [=] { auto init = 0u; - v.for_each_segment([&] (auto first, auto last) { + v.for_each_segment([&](auto first, auto last) { init = std::accumulate(first, last, init); }); return init; @@ -40,8 +39,7 @@ auto benchmark_access_reduce_chunkedseq() template auto benchmark_access_iter_std() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; for (auto i = 0u; i < n; ++i) @@ -56,8 +54,7 @@ auto benchmark_access_iter_std() template auto benchmark_access_idx_std() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; for (auto i = 0u; i < n; ++i) @@ -75,8 +72,7 @@ auto benchmark_access_idx_std() template auto benchmark_access_random_std() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; auto g = make_generator(n); @@ -92,11 +88,10 @@ auto benchmark_access_random_std() }; } -template +template auto benchmark_access_iter() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; @@ -111,11 +106,10 @@ auto benchmark_access_iter() } #if IMMER_BENCHMARK_BOOST_COROUTINE -template +template auto benchmark_access_coro() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { using coro_t = typename boost::coroutines2::coroutine; auto n = params.get(); @@ -125,7 +119,7 @@ auto benchmark_access_coro() v = PushFn{}(std::move(v), i); return [=] { - auto c = coro_t::pull_type { [&](auto& sink) { + auto c = coro_t::pull_type{[&](auto& sink) { v.for_each_chunk([&](auto f, auto l) { for (; f != l; ++f) sink(*f); @@ -138,12 +132,10 @@ auto benchmark_access_coro() } #endif -template +template auto benchmark_access_idx() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; @@ -160,12 +152,10 @@ auto benchmark_access_idx() }; } -template +template auto benchmark_access_reduce() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; @@ -179,12 +169,10 @@ auto benchmark_access_reduce() }; } -template +template auto benchmark_access_reduce_range() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; @@ -198,12 +186,10 @@ auto benchmark_access_reduce_range() }; } -template +template auto benchmark_access_random() { - return [] (nonius::parameters params) - { + return [](nonius::parameters params) { auto n = params.get(); auto v = Vektor{}; @@ -224,38 +210,34 @@ auto benchmark_access_random() template auto benchmark_access_librrb(Fn maker) { - return - [=] (nonius::parameters params) { - auto n = params.get(); - auto v = maker(n); - return - [=] { - auto r = 0u; - for (auto i = 0u; i < n; ++i) - r += reinterpret_cast(rrb_nth(v, i)); - volatile auto rr = r; - return rr; - }; + return [=](nonius::parameters params) { + auto n = params.get(); + auto v = maker(n); + return [=] { + auto r = 0u; + for (auto i = 0u; i < n; ++i) + r += reinterpret_cast(rrb_nth(v, i)); + volatile auto rr = r; + return rr; }; + }; } template auto benchmark_access_random_librrb(Fn maker) { - return - [=] (nonius::parameters params) { - auto n = params.get(); - auto v = maker(n); - auto g = make_generator(n); - return - [=] { - auto r = 0u; - for (auto i = 0u; i < n; ++i) - r += reinterpret_cast(rrb_nth(v, g[i])); - volatile auto rr = r; - return rr; - }; + return [=](nonius::parameters params) { + auto n = params.get(); + auto v = maker(n); + auto g = make_generator(n); + return [=] { + auto r = 0u; + for (auto i = 0u; i < n; ++i) + r += reinterpret_cast(rrb_nth(v, g[i])); + volatile auto rr = r; + return rr; }; + }; } } // anonymous namespace diff --git a/benchmark/vector/assoc.hpp b/benchmark/vector/assoc.hpp index 5619d7ee..84f214d8 100644 --- a/benchmark/vector/assoc.hpp +++ b/benchmark/vector/assoc.hpp @@ -15,49 +15,44 @@ namespace { template auto benchmark_assoc_std() { - return - [] (nonius::chronometer meter) - { - auto n = meter.param(); - auto v = Vektor(n, 0); - std::iota(v.begin(), v.end(), 0u); - auto all = std::vector(meter.runs(), v); - meter.measure([&] (int iter) { - auto& r = all[iter]; - for (auto i = 0u; i < n; ++i) - r[i] = n - i; - return r; - }); - }; + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto v = Vektor(n, 0); + std::iota(v.begin(), v.end(), 0u); + auto all = std::vector(meter.runs(), v); + meter.measure([&](int iter) { + auto& r = all[iter]; + for (auto i = 0u; i < n; ++i) + r[i] = n - i; + return r; + }); + }; } template auto benchmark_assoc_random_std() { - return - [](nonius::chronometer meter) - { - auto n = meter.param(); - auto g = make_generator(n); - auto v = Vektor(n, 0); - std::iota(v.begin(), v.end(), 0u); - auto all = std::vector(meter.runs(), v); - meter.measure([&] (int iter) { - auto& r = all[iter]; - for (auto i = 0u; i < n; ++i) - r[g[i]] = n - i; - return r; - }); - }; + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g = make_generator(n); + auto v = Vektor(n, 0); + std::iota(v.begin(), v.end(), 0u); + auto all = std::vector(meter.runs(), v); + meter.measure([&](int iter) { + auto& r = all[iter]; + for (auto i = 0u; i < n; ++i) + r[g[i]] = n - i; + return r; + }); + }; } template + typename PushFn = push_back_fn, + typename SetFn = set_fn> auto benchmark_assoc() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); @@ -75,12 +70,10 @@ auto benchmark_assoc() }; } -template +template auto benchmark_assoc_move() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); @@ -99,12 +92,11 @@ auto benchmark_assoc_move() } template + typename PushFn = push_back_fn, + typename SetFn = set_fn> auto benchmark_assoc_random() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); @@ -123,12 +115,10 @@ auto benchmark_assoc_random() }; } -template +template auto benchmark_assoc_mut() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); @@ -146,12 +136,10 @@ auto benchmark_assoc_mut() }; } -template +template auto benchmark_assoc_mut_random() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); @@ -173,73 +161,63 @@ auto benchmark_assoc_mut_random() template auto benchmark_assoc_librrb(Fn maker) { - return - [=] (nonius::chronometer meter) { - auto n = meter.param(); - auto v = maker(n); - measure( - meter, [&] { - auto r = v; - for (auto i = 0u; i < n; ++i) - r = rrb_update(r, i, reinterpret_cast(n - i)); - return r; - }); - }; + return [=](nonius::chronometer meter) { + auto n = meter.param(); + auto v = maker(n); + measure(meter, [&] { + auto r = v; + for (auto i = 0u; i < n; ++i) + r = rrb_update(r, i, reinterpret_cast(n - i)); + return r; + }); + }; } template auto benchmark_assoc_random_librrb(Fn maker) { - return - [=] (nonius::chronometer meter) { - auto n = meter.param(); - auto v = maker(n); - auto g = make_generator(n); - measure( - meter, [&] { - auto r = v; - for (auto i = 0u; i < n; ++i) - r = rrb_update(r, g[i], reinterpret_cast(i)); - return r; - }); - }; + return [=](nonius::chronometer meter) { + auto n = meter.param(); + auto v = maker(n); + auto g = make_generator(n); + measure(meter, [&] { + auto r = v; + for (auto i = 0u; i < n; ++i) + r = rrb_update(r, g[i], reinterpret_cast(i)); + return r; + }); + }; } template auto benchmark_assoc_mut_librrb(Fn maker) { - return - [=] (nonius::chronometer meter) { - auto n = meter.param(); - auto v = maker(n); - measure( - meter, [&] { - auto r = rrb_to_transient(v); - for (auto i = 0u; i < n; ++i) - r = transient_rrb_update( - r, i, reinterpret_cast(i)); - return r; - }); - }; + return [=](nonius::chronometer meter) { + auto n = meter.param(); + auto v = maker(n); + measure(meter, [&] { + auto r = rrb_to_transient(v); + for (auto i = 0u; i < n; ++i) + r = transient_rrb_update(r, i, reinterpret_cast(i)); + return r; + }); + }; } template auto benchmark_assoc_mut_random_librrb(Fn maker) { - return - [=] (nonius::chronometer meter) { - auto n = meter.param(); - auto v = maker(n); - auto g = make_generator(n); - measure( - meter, [&] { - auto r = rrb_to_transient(v); - for (auto i = 0u; i < n; ++i) - r = transient_rrb_update( - r, g[i], reinterpret_cast(i)); - return r; - }); - }; + return [=](nonius::chronometer meter) { + auto n = meter.param(); + auto v = maker(n); + auto g = make_generator(n); + measure(meter, [&] { + auto r = rrb_to_transient(v); + for (auto i = 0u; i < n; ++i) + r = transient_rrb_update(r, g[i], reinterpret_cast(i)); + return r; + }); + }; } } // anonymous namespace diff --git a/benchmark/vector/branching/access.ipp b/benchmark/vector/branching/access.ipp index bd7f985d..323e6217 100644 --- a/benchmark/vector/branching/access.ipp +++ b/benchmark/vector/branching/access.ipp @@ -13,8 +13,12 @@ #error "define the MEMORY_T" #endif +// clang-format off + NONIUS_BENCHMARK("flex/3", benchmark_access_idx>()) NONIUS_BENCHMARK("flex/4", benchmark_access_idx>()) NONIUS_BENCHMARK("flex/5", benchmark_access_idx>()) NONIUS_BENCHMARK("flex/6", benchmark_access_idx>()) NONIUS_BENCHMARK("flex/7", benchmark_access_idx>()) + +// clang-format on diff --git a/benchmark/vector/branching/assoc.ipp b/benchmark/vector/branching/assoc.ipp index 4aa9984f..f0289883 100644 --- a/benchmark/vector/branching/assoc.ipp +++ b/benchmark/vector/branching/assoc.ipp @@ -13,8 +13,12 @@ #error "define the MEMORY_T" #endif +// clang-format off + NONIUS_BENCHMARK("flex/3", benchmark_assoc>()) NONIUS_BENCHMARK("flex/4", benchmark_assoc>()) NONIUS_BENCHMARK("flex/5", benchmark_assoc>()) NONIUS_BENCHMARK("flex/6", benchmark_assoc>()) NONIUS_BENCHMARK("flex/7", benchmark_assoc>()) + +// clang-format on diff --git a/benchmark/vector/branching/concat.ipp b/benchmark/vector/branching/concat.ipp index 0e21ca0b..1f5a7297 100644 --- a/benchmark/vector/branching/concat.ipp +++ b/benchmark/vector/branching/concat.ipp @@ -13,8 +13,12 @@ #error "define the MEMORY_T" #endif +// clang-format off + NONIUS_BENCHMARK("flex/3", benchmark_concat>()) NONIUS_BENCHMARK("flex/4", benchmark_concat>()) NONIUS_BENCHMARK("flex/5", benchmark_concat>()) NONIUS_BENCHMARK("flex/6", benchmark_concat>()) NONIUS_BENCHMARK("flex/7", benchmark_concat>()) + +// clang-format on diff --git a/benchmark/vector/branching/push.ipp b/benchmark/vector/branching/push.ipp index 2f92d656..9a1389ab 100644 --- a/benchmark/vector/branching/push.ipp +++ b/benchmark/vector/branching/push.ipp @@ -13,8 +13,12 @@ #error "define the MEMORY_T" #endif +// clang-format off + NONIUS_BENCHMARK("flex/3", benchmark_push>()) NONIUS_BENCHMARK("flex/4", benchmark_push>()) NONIUS_BENCHMARK("flex/5", benchmark_push>()) NONIUS_BENCHMARK("flex/6", benchmark_push>()) NONIUS_BENCHMARK("flex/7", benchmark_push>()) + +// clang-format on diff --git a/benchmark/vector/common.hpp b/benchmark/vector/common.hpp index c96d6d01..79b087d0 100644 --- a/benchmark/vector/common.hpp +++ b/benchmark/vector/common.hpp @@ -8,14 +8,15 @@ #pragma once -#include #include #include +#include #include "benchmark/config.hpp" #if IMMER_BENCHMARK_LIBRRB -extern "C" { +extern "C" +{ #define restrict __restrict__ #include #undef restrict @@ -24,7 +25,8 @@ extern "C" { #endif namespace immer { -template class array; +template +class array; } // namespace immer namespace { @@ -33,8 +35,8 @@ auto make_generator(std::size_t runs) { assert(runs > 0); auto engine = std::default_random_engine{42}; - auto dist = std::uniform_int_distribution{0, runs-1}; - auto r = std::vector(runs); + auto dist = std::uniform_int_distribution{0, runs - 1}; + auto r = std::vector(runs); std::generate_n(r.begin(), runs, std::bind(dist, engine)); return r; } @@ -42,38 +44,49 @@ auto make_generator(std::size_t runs) struct push_back_fn { template - auto operator() (T&& v, U&& x) - { return std::forward(v).push_back(std::forward(x)); } + auto operator()(T&& v, U&& x) + { + return std::forward(v).push_back(std::forward(x)); + } }; struct push_front_fn { template - auto operator() (T&& v, U&& x) - { return std::forward(v).push_front(std::forward(x)); } + auto operator()(T&& v, U&& x) + { + return std::forward(v).push_front(std::forward(x)); + } }; struct set_fn { template - decltype(auto) operator() (T&& v, I i, U&& x) - { return std::forward(v).set(i, std::forward(x)); } + decltype(auto) operator()(T&& v, I i, U&& x) + { + return std::forward(v).set(i, std::forward(x)); + } }; struct store_fn { template - decltype(auto) operator() (T&& v, I i, U&& x) - { return std::forward(v).store(i, std::forward(x)); } + decltype(auto) operator()(T&& v, I i, U&& x) + { + return std::forward(v).store(i, std::forward(x)); + } }; template -struct get_limit : std::integral_constant< - std::size_t, std::numeric_limits::max()> {}; +struct get_limit + : std::integral_constant::max()> +{}; template -struct get_limit> : std::integral_constant< - std::size_t, 10000> {}; +struct get_limit> + : std::integral_constant +{}; auto make_librrb_vector(std::size_t n) { @@ -88,26 +101,26 @@ auto make_librrb_vector_f(std::size_t n) { auto v = rrb_create(); for (auto i = 0u; i < n; ++i) { - auto f = rrb_push(rrb_create(), - reinterpret_cast(i)); - v = rrb_concat(f, v); + auto f = rrb_push(rrb_create(), reinterpret_cast(i)); + v = rrb_concat(f, v); } return v; } - // copied from: // https://github.com/ivmai/bdwgc/blob/master/include/gc_allocator.h template struct GC_type_traits { - std::false_type GC_is_ptr_free; + std::false_type GC_is_ptr_free; }; -# define GC_DECLARE_PTRFREE(T) \ - template<> struct GC_type_traits { \ - std::true_type GC_is_ptr_free; \ +#define GC_DECLARE_PTRFREE(T) \ + template <> \ + struct GC_type_traits \ + { \ + std::true_type GC_is_ptr_free; \ } GC_DECLARE_PTRFREE(char); @@ -126,9 +139,7 @@ GC_DECLARE_PTRFREE(long double); template inline void* GC_selective_alloc(size_t n, IsPtrFree, bool ignore_off_page) { - return ignore_off_page - ? GC_MALLOC_IGNORE_OFF_PAGE(n) - : GC_MALLOC(n); + return ignore_off_page ? GC_MALLOC_IGNORE_OFF_PAGE(n) : GC_MALLOC(n); } template <> @@ -136,31 +147,34 @@ inline void* GC_selective_alloc(size_t n, std::true_type, bool ignore_off_page) { - return ignore_off_page - ? GC_MALLOC_ATOMIC_IGNORE_OFF_PAGE(n) - : GC_MALLOC_ATOMIC(n); + return ignore_off_page ? GC_MALLOC_ATOMIC_IGNORE_OFF_PAGE(n) + : GC_MALLOC_ATOMIC(n); } template class gc_allocator { public: - typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef T* pointer; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef T* pointer; typedef const T* const_pointer; - typedef T& reference; + typedef T& reference; typedef const T& const_reference; - typedef T value_type; + typedef T value_type; - template struct rebind { + template + struct rebind + { typedef gc_allocator other; }; - gc_allocator() {} + gc_allocator() {} gc_allocator(const gc_allocator&) throw() {} template - explicit gc_allocator(const gc_allocator&) throw() {} + explicit gc_allocator(const gc_allocator&) throw() + { + } ~gc_allocator() throw() {} pointer address(reference GC_x) const { return &GC_x; } @@ -171,42 +185,45 @@ class gc_allocator T* allocate(size_type GC_n, const void* = 0) { GC_type_traits traits; - return static_cast - (GC_selective_alloc(GC_n * sizeof(T), - traits.GC_is_ptr_free, false)); + return static_cast( + GC_selective_alloc(GC_n * sizeof(T), traits.GC_is_ptr_free, false)); } // p is not permitted to be a null pointer. - void deallocate(pointer p, size_type /* GC_n */) - { GC_FREE(p); } + void deallocate(pointer p, size_type /* GC_n */) { GC_FREE(p); } - size_type max_size() const throw() - { return size_t(-1) / sizeof(T); } + size_type max_size() const throw() { return size_t(-1) / sizeof(T); } - void construct(pointer p, const T& __val) { new(p) T(__val); } + void construct(pointer p, const T& __val) { new (p) T(__val); } void destroy(pointer p) { p->~T(); } }; -template<> +template <> class gc_allocator { - typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef void* pointer; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + typedef void* pointer; typedef const void* const_pointer; - typedef void value_type; + typedef void value_type; - template struct rebind { + template + struct rebind + { typedef gc_allocator other; }; }; template inline bool operator==(const gc_allocator&, const gc_allocator&) -{ return true; } +{ + return true; +} template inline bool operator!=(const gc_allocator&, const gc_allocator&) -{ return false; } +{ + return false; +} } // anonymous namespace diff --git a/benchmark/vector/concat.hpp b/benchmark/vector/concat.hpp index 8656587d..2040d477 100644 --- a/benchmark/vector/concat.hpp +++ b/benchmark/vector/concat.hpp @@ -14,153 +14,136 @@ namespace { constexpr auto concat_steps = 10u; -template +template auto benchmark_concat() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto v = Vektor{}; for (auto i = 0u; i < n; ++i) v = PushFn{}(std::move(v), i); - measure(meter, [&] { - return v + v; - }); + measure(meter, [&] { return v + v; }); }; } template auto benchmark_concat_librrb(Fn maker) { - return - [=] (nonius::chronometer meter) { - auto n = meter.param(); - auto v = maker(n); - measure(meter, [&] { - return rrb_concat(v, v); - }); - }; + return [=](nonius::chronometer meter) { + auto n = meter.param(); + auto v = maker(n); + measure(meter, [&] { return rrb_concat(v, v); }); + }; } -template +template auto benchmark_concat_incr() { - return - [] (nonius::chronometer meter) - { - auto n = meter.param(); - - auto v = Vektor{}; - for (auto i = 0u; i < n / concat_steps; ++i) - v = PushFn{}(std::move(v), i); - - measure(meter, [&] { - auto r = Vektor{}; - for (auto i = 0u; i < concat_steps; ++i) - r = r + v; - return r; - }); - }; + return [](nonius::chronometer meter) { + auto n = meter.param(); + + auto v = Vektor{}; + for (auto i = 0u; i < n / concat_steps; ++i) + v = PushFn{}(std::move(v), i); + + measure(meter, [&] { + auto r = Vektor{}; + for (auto i = 0u; i < concat_steps; ++i) + r = r + v; + return r; + }); + }; } template auto benchmark_concat_incr_mut() { - return - [] (nonius::chronometer meter) - { - auto n = meter.param(); - - auto v = Vektor{}.transient(); - for (auto i = 0u; i < n / concat_steps; ++i) - v.push_back(i); - - measure(meter, [&] (int run) { - auto r = Vektor{}.transient(); - for (auto i = 0u; i < concat_steps; ++i) - r.append(v); - return r; - }); - }; + return [](nonius::chronometer meter) { + auto n = meter.param(); + + auto v = Vektor{}.transient(); + for (auto i = 0u; i < n / concat_steps; ++i) + v.push_back(i); + + measure(meter, [&](int run) { + auto r = Vektor{}.transient(); + for (auto i = 0u; i < concat_steps; ++i) + r.append(v); + return r; + }); + }; } template auto benchmark_concat_incr_mut2() { - return - [] (nonius::chronometer meter) - { - auto n = meter.param(); - - using transient_t = typename Vektor::transient_type; - using steps_t = std::vector>; - auto vs = std::vector>(meter.runs()); - for (auto k = 0u; k < vs.size(); ++k) { - vs[k].reserve(concat_steps); - for (auto j = 0u; j < concat_steps; ++j) { - auto vv = Vektor{}.transient(); - for (auto i = 0u; i < n / concat_steps; ++i) - vv.push_back(i); - vs[k].push_back(std::move(vv)); - } + return [](nonius::chronometer meter) { + auto n = meter.param(); + + using transient_t = typename Vektor::transient_type; + using steps_t = std::vector>; + auto vs = std::vector>(meter.runs()); + for (auto k = 0u; k < vs.size(); ++k) { + vs[k].reserve(concat_steps); + for (auto j = 0u; j < concat_steps; ++j) { + auto vv = Vektor{}.transient(); + for (auto i = 0u; i < n / concat_steps; ++i) + vv.push_back(i); + vs[k].push_back(std::move(vv)); } - measure(meter, [&] (int run) { - auto& vr = vs[run]; - auto r = Vektor{}.transient(); - assert(vr.size() == concat_steps); - for (auto i = 0u; i < concat_steps; ++i) - r.append(std::move(vr[i])); - return r; - }); - }; + } + measure(meter, [&](int run) { + auto& vr = vs[run]; + auto r = Vektor{}.transient(); + assert(vr.size() == concat_steps); + for (auto i = 0u; i < concat_steps; ++i) + r.append(std::move(vr[i])); + return r; + }); + }; } template auto benchmark_concat_incr_chunkedseq() { - return - [] (nonius::chronometer meter) - { - auto n = meter.param(); - - using steps_t = std::vector; - auto vs = std::vector(meter.runs()); - for (auto k = 0u; k < vs.size(); ++k) { - for (auto j = 0u; j < concat_steps; ++j) { - auto vv = Vektor{}; - for (auto i = 0u; i < n / concat_steps; ++i) - vv.push_back(i); - vs[k].push_back(std::move(vv)); - } + return [](nonius::chronometer meter) { + auto n = meter.param(); + + using steps_t = std::vector; + auto vs = std::vector(meter.runs()); + for (auto k = 0u; k < vs.size(); ++k) { + for (auto j = 0u; j < concat_steps; ++j) { + auto vv = Vektor{}; + for (auto i = 0u; i < n / concat_steps; ++i) + vv.push_back(i); + vs[k].push_back(std::move(vv)); } - measure(meter, [&] (int run) { - auto& vr = vs[run]; - auto r = Vektor{}; - for (auto i = 0u; i < concat_steps; ++i) - r.concat(vr[i]); - return r; - }); - }; + } + measure(meter, [&](int run) { + auto& vr = vs[run]; + auto r = Vektor{}; + for (auto i = 0u; i < concat_steps; ++i) + r.concat(vr[i]); + return r; + }); + }; } template auto benchmark_concat_incr_librrb(Fn maker) { - return - [=] (nonius::chronometer meter) { - auto n = meter.param(); - auto v = maker(n / concat_steps); - measure(meter, [&] { - auto r = rrb_create(); - for (auto i = 0ul; i < concat_steps; ++i) - r = rrb_concat(r, v); - return r; - }); - }; + return [=](nonius::chronometer meter) { + auto n = meter.param(); + auto v = maker(n / concat_steps); + measure(meter, [&] { + auto r = rrb_create(); + for (auto i = 0ul; i < concat_steps; ++i) + r = rrb_concat(r, v); + return r; + }); + }; } } // anonymous namespace diff --git a/benchmark/vector/drop.hpp b/benchmark/vector/drop.hpp index 0a525f67..7576dfe1 100644 --- a/benchmark/vector/drop.hpp +++ b/benchmark/vector/drop.hpp @@ -12,12 +12,10 @@ namespace { -template +template auto benchmark_drop() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto v = Vektor{}; @@ -31,12 +29,10 @@ auto benchmark_drop() }; } -template +template auto benchmark_drop_lin() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto v = Vektor{}; @@ -52,12 +48,10 @@ auto benchmark_drop_lin() }; } -template +template auto benchmark_drop_move() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto v = Vektor{}; @@ -73,12 +67,10 @@ auto benchmark_drop_move() }; } -template +template auto benchmark_drop_mut() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto vv = Vektor{}; @@ -96,46 +88,43 @@ auto benchmark_drop_mut() template auto benchmark_drop_librrb(Fn make) { - return [=] (nonius::chronometer meter) - { + return [=](nonius::chronometer meter) { auto n = meter.param(); auto v = make(n); measure(meter, [&] { - for (auto i = 0u; i < n; ++i) - rrb_slice(v, i, n); - }); + for (auto i = 0u; i < n; ++i) + rrb_slice(v, i, n); + }); }; } template auto benchmark_drop_lin_librrb(Fn make) { - return [=] (nonius::chronometer meter) - { + return [=](nonius::chronometer meter) { auto n = meter.param(); auto v = make(n); measure(meter, [&] { - auto r = v; - for (auto i = 0u; i < n; ++i) - r = rrb_slice(r, 1, n); - return r; - }); + auto r = v; + for (auto i = 0u; i < n; ++i) + r = rrb_slice(r, 1, n); + return r; + }); }; } template auto benchmark_drop_mut_librrb(Fn make) { - return [=] (nonius::chronometer meter) - { + return [=](nonius::chronometer meter) { auto n = meter.param(); auto v = make(n); measure(meter, [&] { - auto r = rrb_to_transient(v); - for (auto i = 0u; i < n; ++i) - r = transient_rrb_slice(r, 1, n); - return r; - }); + auto r = rrb_to_transient(v); + for (auto i = 0u; i < n; ++i) + r = transient_rrb_slice(r, 1, n); + return r; + }); }; } diff --git a/benchmark/vector/misc/access.cpp b/benchmark/vector/misc/access.cpp index 9a00266a..6e446572 100644 --- a/benchmark/vector/misc/access.cpp +++ b/benchmark/vector/misc/access.cpp @@ -22,9 +22,11 @@ #include #endif -#include #include #include +#include + +// clang-format off NONIUS_BENCHMARK("std::vector", benchmark_access_iter_std>()) NONIUS_BENCHMARK("std::vector/idx", benchmark_access_idx_std>()) @@ -92,3 +94,5 @@ NONIUS_BENCHMARK("dvektor/6B/random", benchmark_access_random>()) #endif + +// clang-format on diff --git a/benchmark/vector/misc/assoc.cpp b/benchmark/vector/misc/assoc.cpp index 2b3d6d76..41f679bb 100644 --- a/benchmark/vector/misc/assoc.cpp +++ b/benchmark/vector/misc/assoc.cpp @@ -22,8 +22,8 @@ #include #include -#include #include +#include #if IMMER_BENCHMARK_STEADY #define QUARK_ASSERT_ON 0 @@ -31,13 +31,16 @@ #endif #if IMMER_BENCHMARK_LIBRRB -extern "C" { +extern "C" +{ #define restrict __restrict__ #include #undef restrict } #endif +// clang-format off + NONIUS_BENCHMARK("std::vector", benchmark_assoc_std>()) NONIUS_BENCHMARK("std::vector/random", benchmark_assoc_random_std>()) @@ -117,3 +120,5 @@ NONIUS_BENCHMARK("t/vector/GC/random", benchmark_assoc_mut_random>()) NONIUS_BENCHMARK("t/vector/UN/random", benchmark_assoc_mut_random>()) NONIUS_BENCHMARK("t/flex/F/5B/random", benchmark_assoc_mut_random,push_front_fn>()) + +// clang-format on diff --git a/benchmark/vector/misc/concat.cpp b/benchmark/vector/misc/concat.cpp index 9bc10c5c..6013f187 100644 --- a/benchmark/vector/misc/concat.cpp +++ b/benchmark/vector/misc/concat.cpp @@ -15,13 +15,16 @@ #include #if IMMER_BENCHMARK_LIBRRB -extern "C" { +extern "C" +{ #define restrict __restrict__ #include #undef restrict } #endif +// clang-format off + #if IMMER_BENCHMARK_LIBRRB NONIUS_BENCHMARK("librrb", benchmark_concat_librrb(make_librrb_vector)) NONIUS_BENCHMARK("librrb/F", benchmark_concat_librrb(make_librrb_vector_f)) @@ -47,3 +50,5 @@ NONIUS_BENCHMARK("i/flex/UN", benchmark_concat_incr>()) NONIUS_BENCHMARK("m/flex_s/GC", benchmark_concat_incr_mut>()) + +// clang-format on diff --git a/benchmark/vector/misc/drop.cpp b/benchmark/vector/misc/drop.cpp index c2ca4839..781022b4 100644 --- a/benchmark/vector/misc/drop.cpp +++ b/benchmark/vector/misc/drop.cpp @@ -15,13 +15,16 @@ #include #if IMMER_BENCHMARK_LIBRRB -extern "C" { +extern "C" +{ #define restrict __restrict__ #include #undef restrict } #endif +// clang-format off + #if IMMER_BENCHMARK_LIBRRB NONIUS_BENCHMARK("librrb", benchmark_drop_librrb(make_librrb_vector)) NONIUS_BENCHMARK("librrb/F", benchmark_drop_librrb(make_librrb_vector_f)) @@ -61,3 +64,5 @@ NONIUS_BENCHMARK("t/flex/GC", benchmark_drop_mut>()) NONIUS_BENCHMARK("t/flex/UN", benchmark_drop_mut>()) NONIUS_BENCHMARK("t/flex/F/5B", benchmark_drop_mut, push_front_fn>()) + +// clang-format on diff --git a/benchmark/vector/misc/push-front.cpp b/benchmark/vector/misc/push-front.cpp index 48b98c8e..6cad3d35 100644 --- a/benchmark/vector/misc/push-front.cpp +++ b/benchmark/vector/misc/push-front.cpp @@ -11,13 +11,16 @@ #include #if IMMER_BENCHMARK_LIBRRB -extern "C" { +extern "C" +{ #define restrict __restrict__ #include #undef restrict } #endif +// clang-format off + #if IMMER_BENCHMARK_LIBRRB NONIUS_BENCHMARK("librrb", benchmark_push_front_librrb) #endif @@ -28,3 +31,5 @@ NONIUS_BENCHMARK("flex/GC", bechmark_push_front>()) NONIUS_BENCHMARK("flex/NO", bechmark_push_front>()) NONIUS_BENCHMARK("flex/UN", bechmark_push_front>()) + +// clang-format on diff --git a/benchmark/vector/misc/push.cpp b/benchmark/vector/misc/push.cpp index 98f57600..058af64d 100644 --- a/benchmark/vector/misc/push.cpp +++ b/benchmark/vector/misc/push.cpp @@ -10,8 +10,8 @@ #include #include -#include #include +#include #if IMMER_BENCHMARK_EXPERIMENTAL #include @@ -21,9 +21,9 @@ #include #include -#include #include #include +#include #if IMMER_BENCHMARK_STEADY #define QUARK_ASSERT_ON 0 @@ -31,13 +31,16 @@ #endif #if IMMER_BENCHMARK_LIBRRB -extern "C" { +extern "C" +{ #define restrict __restrict__ #include #undef restrict } #endif +// clang-format off + #if IMMER_BENCHMARK_LIBRRB NONIUS_BENCHMARK("librrb", benchmark_push_librrb) NONIUS_BENCHMARK("t/librrb", benchmark_push_mut_librrb) @@ -81,3 +84,5 @@ NONIUS_BENCHMARK("array", benchmark_push>()) #if IMMER_BENCHMARK_STEADY NONIUS_BENCHMARK("steady", benchmark_push>()) #endif + +// clang-format on diff --git a/benchmark/vector/misc/take.cpp b/benchmark/vector/misc/take.cpp index 78f0e958..a8af321d 100644 --- a/benchmark/vector/misc/take.cpp +++ b/benchmark/vector/misc/take.cpp @@ -8,22 +8,25 @@ #include "benchmark/vector/take.hpp" -#include #include -#include #include #include #include #include +#include +#include #if IMMER_BENCHMARK_LIBRRB -extern "C" { +extern "C" +{ #define restrict __restrict__ #include #undef restrict } #endif +// clang-format off + #if IMMER_BENCHMARK_LIBRRB NONIUS_BENCHMARK("librrb", benchmark_take_librrb(make_librrb_vector)) NONIUS_BENCHMARK("librrb/F", benchmark_take_librrb(make_librrb_vector_f)) @@ -63,3 +66,5 @@ NONIUS_BENCHMARK("t/vector/GC", benchmark_take_mut>()) NONIUS_BENCHMARK("t/vector/UN", benchmark_take_mut>()) NONIUS_BENCHMARK("t/flex/F/5B", benchmark_take_mut, push_front_fn>()) + +// clang-format on diff --git a/benchmark/vector/paper/access.cpp b/benchmark/vector/paper/access.cpp index db677a1a..705404a7 100644 --- a/benchmark/vector/paper/access.cpp +++ b/benchmark/vector/paper/access.cpp @@ -7,8 +7,10 @@ // #include "benchmark/vector/access.hpp" -#include #include +#include + +// clang-format off NONIUS_BENCHMARK("idx owrs", benchmark_access_idx>()) NONIUS_BENCHMARK("idx librrb", benchmark_access_librrb(make_librrb_vector)) @@ -29,3 +31,5 @@ NONIUS_BENCHMARK("reduce chunkedseq32", benchmark_access_reduce_chunkedseq>()) NONIUS_BENCHMARK("reduce std::vector", benchmark_access_iter_std>()) NONIUS_BENCHMARK("reduce std::list", benchmark_access_iter_std>()) + +// clang-format on diff --git a/benchmark/vector/paper/assoc-random.cpp b/benchmark/vector/paper/assoc-random.cpp index becd4dc0..617c8c64 100644 --- a/benchmark/vector/paper/assoc-random.cpp +++ b/benchmark/vector/paper/assoc-random.cpp @@ -7,9 +7,11 @@ // #include "benchmark/vector/assoc.hpp" +#include #include #include -#include + +// clang-format off NONIUS_BENCHMARK("ours/basic", benchmark_assoc_random>()) NONIUS_BENCHMARK("ours/safe", benchmark_assoc_random>()) @@ -38,3 +40,5 @@ NONIUS_BENCHMARK("transient relaxed librrb", benchmark_assoc_mut_random_li NONIUS_BENCHMARK("transient std::vector", benchmark_assoc_random_std>()) NONIUS_BENCHMARK("transient chunkedseq32", benchmark_assoc_random_std>()) NONIUS_BENCHMARK("transient chunkedseq", benchmark_assoc_random_std>()) + +// clang-format on diff --git a/benchmark/vector/paper/assoc.cpp b/benchmark/vector/paper/assoc.cpp index fe682ce7..8454d8c3 100644 --- a/benchmark/vector/paper/assoc.cpp +++ b/benchmark/vector/paper/assoc.cpp @@ -7,9 +7,11 @@ // #include "benchmark/vector/assoc.hpp" +#include #include #include -#include + +// clang-format off NONIUS_BENCHMARK("ours/basic", benchmark_assoc>()) NONIUS_BENCHMARK("ours/safe", benchmark_assoc>()) @@ -38,3 +40,5 @@ NONIUS_BENCHMARK("transient relaxed librrb", benchmark_assoc_mut_librrb(ma NONIUS_BENCHMARK("transient std::vector", benchmark_assoc_std>()) NONIUS_BENCHMARK("transient chunkedseq32", benchmark_assoc_std>()) NONIUS_BENCHMARK("transient chunkedseq", benchmark_assoc_std>()) + +// clang-format on diff --git a/benchmark/vector/paper/concat.cpp b/benchmark/vector/paper/concat.cpp index af9b3d20..a7f668c8 100644 --- a/benchmark/vector/paper/concat.cpp +++ b/benchmark/vector/paper/concat.cpp @@ -7,9 +7,11 @@ // #include "benchmark/vector/concat.hpp" +#include #include #include -#include + +// clang-format off NONIUS_BENCHMARK("ours/basic", benchmark_concat_incr>()) NONIUS_BENCHMARK("ours/safe", benchmark_concat_incr>()) @@ -20,3 +22,5 @@ NONIUS_BENCHMARK("librrb", benchmark_concat_incr_librrb(make_librrb_vector NONIUS_BENCHMARK("transient ours/gc", benchmark_concat_incr_mut>()) NONIUS_BENCHMARK("transient chunkedseq32", benchmark_concat_incr_chunkedseq>()) NONIUS_BENCHMARK("transient chunkedseq", benchmark_concat_incr_chunkedseq>()) + +// clang-format on diff --git a/benchmark/vector/paper/push.cpp b/benchmark/vector/paper/push.cpp index 7c467985..210b7bb3 100644 --- a/benchmark/vector/paper/push.cpp +++ b/benchmark/vector/paper/push.cpp @@ -7,9 +7,11 @@ // #include "benchmark/vector/push.hpp" +#include #include #include -#include + +// clang-format off NONIUS_BENCHMARK("ours/basic", benchmark_push>()) NONIUS_BENCHMARK("ours/safe", benchmark_push>()) @@ -26,3 +28,5 @@ NONIUS_BENCHMARK("transient std::vector", benchmark_push_mut_std>()) NONIUS_BENCHMARK("transient chunkedseq32", benchmark_push_mut_std>()) NONIUS_BENCHMARK("transient chunkedseq", benchmark_push_mut_std>()) + +// clang-format on diff --git a/benchmark/vector/push.hpp b/benchmark/vector/push.hpp index 83f128c8..56a3592d 100644 --- a/benchmark/vector/push.hpp +++ b/benchmark/vector/push.hpp @@ -15,8 +15,7 @@ namespace { template auto benchmark_push_mut_std() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); @@ -33,8 +32,7 @@ auto benchmark_push_mut_std() template auto benchmark_push_mut() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); @@ -51,8 +49,7 @@ auto benchmark_push_mut() template auto benchmark_push_move() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); @@ -69,8 +66,7 @@ auto benchmark_push_move() template auto benchmark_push() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); if (n > get_limit{}) nonius::skip(); diff --git a/benchmark/vector/push_front.hpp b/benchmark/vector/push_front.hpp index 25ccbab0..a09a672e 100644 --- a/benchmark/vector/push_front.hpp +++ b/benchmark/vector/push_front.hpp @@ -15,8 +15,7 @@ namespace { template auto bechmark_push_front() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); measure(meter, [&] { @@ -35,9 +34,8 @@ auto benchmark_push_front_librrb(nonius::chronometer meter) measure(meter, [&] { auto v = rrb_create(); for (auto i = 0u; i < n; ++i) { - auto f = rrb_push(rrb_create(), - reinterpret_cast(i)); - v = rrb_concat(f, v); + auto f = rrb_push(rrb_create(), reinterpret_cast(i)); + v = rrb_concat(f, v); } return v; }); diff --git a/benchmark/vector/take.hpp b/benchmark/vector/take.hpp index bce5aceb..0cf5d9ac 100644 --- a/benchmark/vector/take.hpp +++ b/benchmark/vector/take.hpp @@ -12,12 +12,10 @@ namespace { -template +template auto benchmark_take() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto v = Vektor{}; @@ -31,12 +29,10 @@ auto benchmark_take() }; } -template +template auto benchmark_take_lin() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto v = Vektor{}; @@ -52,12 +48,10 @@ auto benchmark_take_lin() }; } -template +template auto benchmark_take_move() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto v = Vektor{}; @@ -73,12 +67,10 @@ auto benchmark_take_move() }; } -template +template auto benchmark_take_mut() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto vv = Vektor{}; @@ -96,48 +88,43 @@ auto benchmark_take_mut() template auto benchmark_take_librrb(Fn make) { - return [=] (nonius::chronometer meter) - { + return [=](nonius::chronometer meter) { auto n = meter.param(); auto v = make(n); measure(meter, [&] { - for (auto i = 0u; i < n; ++i) - rrb_slice(v, 0, i); - }); + for (auto i = 0u; i < n; ++i) + rrb_slice(v, 0, i); + }); }; } template auto benchmark_take_lin_librrb(Fn make) { - return [=] (nonius::chronometer meter) - { + return [=](nonius::chronometer meter) { auto n = meter.param(); auto v = make(n); - measure( - meter, [&] { - auto r = v; - for (auto i = n; i > 0; --i) - r = rrb_slice(r, 0, i); - return r; - }); + measure(meter, [&] { + auto r = v; + for (auto i = n; i > 0; --i) + r = rrb_slice(r, 0, i); + return r; + }); }; } template auto benchmark_take_mut_librrb(Fn make) { - return [=] (nonius::chronometer meter) - { + return [=](nonius::chronometer meter) { auto n = meter.param(); auto v = make(n); - measure( - meter, [&] { - auto r = rrb_to_transient(v); - for (auto i = n; i > 0; --i) - r = transient_rrb_slice(r, 0, i); - return r; - }); + measure(meter, [&] { + auto r = rrb_to_transient(v); + for (auto i = n; i > 0; --i) + r = transient_rrb_slice(r, 0, i); + return r; + }); }; } diff --git a/cmake/FindBoehmGC.cmake b/cmake/FindBoehmGC.cmake index bdb7240e..6a4985eb 100644 --- a/cmake/FindBoehmGC.cmake +++ b/cmake/FindBoehmGC.cmake @@ -1,108 +1,111 @@ -# - Try to find Boehm GC -# Once done, this will define +# * Try to find Boehm GC Once done, this will define +# +# BOEHM_GC_FOUND - system has Boehm GC BOEHM_GC_INCLUDE_DIR - the Boehm GC +# include directories BOEHM_GC_LIBRARIES - link these to use Boehm GC +# +# Copyright (c) 2010-2015 Takashi Kato +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 1. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE +# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# $Id: $ # -# BOEHM_GC_FOUND - system has Boehm GC -# BOEHM_GC_INCLUDE_DIR - the Boehm GC include directories -# BOEHM_GC_LIBRARIES - link these to use Boehm GC -# -# Copyright (c) 2010-2015 Takashi Kato -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions -# are met: -# -# 1. Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright -# notice, this list of conditions and the following disclaimer in the -# documentation and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED -# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR -# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING -# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -# $Id: $ -# # CMake module to find Boehm GC # use pkg-config if available -FIND_PACKAGE(PkgConfig) -PKG_CHECK_MODULES(PC_BDW_GC QUIET bdw-gc) +find_package(PkgConfig) +pkg_check_modules(PC_BDW_GC QUIET bdw-gc) # try newer one first in case of gc.h is overwritten. -FIND_PATH(BOEHM_GC_INCLUDE_DIR gc/gc.h - HINTS ${PC_BDW_GC_INCLUDEDIR} ${PC_BDW_GC_INCLUDE_DIRS}) +find_path(BOEHM_GC_INCLUDE_DIR gc/gc.h HINTS ${PC_BDW_GC_INCLUDEDIR} + ${PC_BDW_GC_INCLUDE_DIRS}) -IF (NOT BOEHM_GC_INCLUDE_DIR) - FIND_PATH(BOEHM_GC_INCLUDE_DIR gc.h - HINTS ${PC_BDW_GC_INCLUDEDIR} ${PC_BDW_GC_INCLUDE_DIRS}) - IF (BOEHM_GC_INCLUDE_DIR) - SET(HAVE_GC_H TRUE) - ENDIF() -ELSE() - SET(HAVE_GC_GC_H TRUE) -ENDIF() +if(NOT BOEHM_GC_INCLUDE_DIR) + find_path(BOEHM_GC_INCLUDE_DIR gc.h HINTS ${PC_BDW_GC_INCLUDEDIR} + ${PC_BDW_GC_INCLUDE_DIRS}) + if(BOEHM_GC_INCLUDE_DIR) + set(HAVE_GC_H TRUE) + endif() +else() + set(HAVE_GC_GC_H TRUE) +endif() # For FreeBSD we need to use gc-threaded -IF (${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") - # checks if 'gc' supports 'GC_get_parallel' and if it does - # then use it - INCLUDE(${CMAKE_ROOT}/Modules/CheckCSourceCompiles.cmake) +if(${CMAKE_SYSTEM_NAME} STREQUAL "FreeBSD") + # checks if 'gc' supports 'GC_get_parallel' and if it does then use it + include(${CMAKE_ROOT}/Modules/CheckCSourceCompiles.cmake) # not sure if this links properly... - FIND_LIBRARY(BOEHM_GC_LIBRARIES NAMES gc + find_library( + BOEHM_GC_LIBRARIES + NAMES gc HINTS ${PC_BDW_GC_LIBDIR} ${PC_BDW_GC_LIBRARY_DIRS}) - MESSAGE(STATUS "GC library ${BOEHM_GC_LIBRARIES}") - SET(CMAKE_REQUIRED_LIBRARIES "gc") - SET(CMAKE_REQUIRED_DEFINITIONS "-DGC_THREADS") - SET(CMAKE_REQUIRED_INCLUDES "${BOEHM_GC_INCLUDE_DIR}") - SET(CMAKE_REQUIRED_FLAGS "-L${PC_BDW_GC_LIBRARY_DIRS}") - MESSAGE(STATUS "Boehm GC include dir: ${CMAKE_REQUIRED_INCLUDES}") - CHECK_C_SOURCE_RUNS( + message(STATUS "GC library ${BOEHM_GC_LIBRARIES}") + set(CMAKE_REQUIRED_LIBRARIES "gc") + set(CMAKE_REQUIRED_DEFINITIONS "-DGC_THREADS") + set(CMAKE_REQUIRED_INCLUDES "${BOEHM_GC_INCLUDE_DIR}") + set(CMAKE_REQUIRED_FLAGS "-L${PC_BDW_GC_LIBRARY_DIRS}") + message(STATUS "Boehm GC include dir: ${CMAKE_REQUIRED_INCLUDES}") + check_c_source_runs( "#include int main() { int i= GC_get_parallel(); return 0; } -" GC_GET_PARALLEL_WORKS) - IF (NOT GC_GET_PARALLEL_WORKS) - MESSAGE(STATUS "Try gc-threaded") +" + GC_GET_PARALLEL_WORKS) + if(NOT GC_GET_PARALLEL_WORKS) + message(STATUS "Try gc-threaded") # bdw-gc-threaded is the proper name for FreeBSD pkg-config - PKG_CHECK_MODULES(PC_BDW_GC_THREADED bdw-gc-threaded) - FIND_LIBRARY(BOEHM_GC_THREADED_LIBRARIES NAMES gc-threaded + pkg_check_modules(PC_BDW_GC_THREADED bdw-gc-threaded) + find_library( + BOEHM_GC_THREADED_LIBRARIES + NAMES gc-threaded HINTS ${PC_BDW_GC_THREADED_LIBDIR} ${PC_BDW_GC_THREADED_THREADED_DIRS}) - MESSAGE(STATUS "GC threaded library ${BOEHM_GC_THREADED_LIBRARIES}") - IF (BOEHM_GC_THREADED_LIBRARIES) + message(STATUS "GC threaded library ${BOEHM_GC_THREADED_LIBRARIES}") + if(BOEHM_GC_THREADED_LIBRARIES) # OK just use it - SET(BOEHM_GC_LIBRARIES "${BOEHM_GC_THREADED_LIBRARIES}") - ENDIF() - ENDIF() -ELSE() - FIND_LIBRARY(BOEHM_GC_LIBRARIES NAMES gc + set(BOEHM_GC_LIBRARIES "${BOEHM_GC_THREADED_LIBRARIES}") + endif() + endif() +else() + find_library( + BOEHM_GC_LIBRARIES + NAMES gc HINTS ${PC_BDW_GC_LIBDIR} ${PC_BDW_GC_LIBRARY_DIRS}) - # OpenSolaris uses bgc as Boehm GC runtime in its package manager. - # so try it - IF (NOT BOEHM_GC_LIBRARIES) - FIND_LIBRARY(BOEHM_GC_LIBRARIES NAMES bgc + # OpenSolaris uses bgc as Boehm GC runtime in its package manager. so try it + if(NOT BOEHM_GC_LIBRARIES) + find_library( + BOEHM_GC_LIBRARIES + NAMES bgc HINTS ${PC_BDW_GC_LIBDIR} ${PC_BDW_GC_LIBRARY_DIRS}) - ENDIF() -ENDIF() + endif() +endif() -MESSAGE(STATUS "Found GC library: ${BOEHM_GC_LIBRARIES}") +message(STATUS "Found GC library: ${BOEHM_GC_LIBRARIES}") -INCLUDE(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Boehm_GC DEFAULT_MSG - BOEHM_GC_LIBRARIES BOEHM_GC_INCLUDE_DIR) +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Boehm_GC DEFAULT_MSG BOEHM_GC_LIBRARIES + BOEHM_GC_INCLUDE_DIR) -MARK_AS_ADVANCED(BOEHM_GC_LIBRARIES BOEHM_GC_INCLUDE_DIR) +mark_as_advanced(BOEHM_GC_LIBRARIES BOEHM_GC_INCLUDE_DIR) diff --git a/cmake/FindRRB.cmake b/cmake/FindRRB.cmake index 86b552f8..3540637f 100644 --- a/cmake/FindRRB.cmake +++ b/cmake/FindRRB.cmake @@ -1,15 +1,12 @@ -# - Try to find c-rrb -# Once done, this will define +# * Try to find c-rrb Once done, this will define # -# RRB_FOUND - system has RRB -# RRB_INCLUDE_DIR - the RRB include directories -# RRB_LIBRARIES - link these to use RRB +# RRB_FOUND - system has RRB RRB_INCLUDE_DIR - the RRB include directories +# RRB_LIBRARIES - link these to use RRB find_path(RRB_INCLUDE_DIR rrb.h) find_library(RRB_LIBRARIES NAMES rrb librrb) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args( - RRB DEFAULT_MSG RRB_LIBRARIES RRB_INCLUDE_DIR) +find_package_handle_standard_args(RRB DEFAULT_MSG RRB_LIBRARIES RRB_INCLUDE_DIR) mark_as_advanced(RRB_INCLUDE_DIR RRB_LIBRARIES) diff --git a/cmake/ImmerUtils.cmake b/cmake/ImmerUtils.cmake index 2af63ff6..d486ecc1 100644 --- a/cmake/ImmerUtils.cmake +++ b/cmake/ImmerUtils.cmake @@ -1,24 +1,31 @@ - function(immer_target_name_for out_target out_file file) get_filename_component(_extension ${_file} EXT) file(RELATIVE_PATH _relative ${PROJECT_SOURCE_DIR} ${file}) string(REPLACE "${_extension}" "" _name ${_relative}) string(REGEX REPLACE "/" "-" _name ${_name}) - set(${out_target} "${_name}" PARENT_SCOPE) + set(${out_target} + "${_name}" + PARENT_SCOPE) file(RELATIVE_PATH _relative ${CMAKE_CURRENT_LIST_DIR} ${file}) string(REPLACE "${_extension}" "" _name ${_relative}) string(REGEX REPLACE "/" "-" _name ${_name}) - set(${out_file} "${_name}" PARENT_SCOPE) + set(${out_file} + "${_name}" + PARENT_SCOPE) endfunction() function(immer_canonicalize_cmake_booleans) foreach(var ${ARGN}) if(${var}) - set(${var} 1 PARENT_SCOPE) + set(${var} + 1 + PARENT_SCOPE) else() - set(${var} 0 PARENT_SCOPE) + set(${var} + 0 + PARENT_SCOPE) endif() endforeach() endfunction() diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 86c1ab5b..97705e96 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -1,17 +1,17 @@ +# Targets +# ======= -# Targets -# ======= - -add_custom_target(doxygen +add_custom_target( + doxygen COMMAND doxygen doxygen.config WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") -add_custom_target(docs +add_custom_target( + docs COMMAND make html WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") add_dependencies(docs doxygen) -add_custom_target(upload-docs - COMMAND - rsync -av ${CMAKE_CURRENT_SOURCE_DIR}/_build/html/* - root@sinusoid.es:/var/lib/syncthing/public/immer/) +add_custom_target( + upload-docs COMMAND rsync -av ${CMAKE_CURRENT_SOURCE_DIR}/_build/html/* + root@sinusoid.es:/var/lib/syncthing/public/immer/) diff --git a/doc/doxygen.config b/doc/doxygen.config index b6002b8d..d8602293 100644 --- a/doc/doxygen.config +++ b/doc/doxygen.config @@ -9,7 +9,10 @@ INPUT = \ ../immer \ ../immer/heap \ ../immer/refcount \ - ../immer/transience + ../immer/transience \ + ../immer/extra/persist \ + ../immer/extra/persist/xxhash \ + ../immer/extra/persist/cereal INCLUDE_PATH = .. QUIET = YES diff --git a/doc/index.rst b/doc/index.rst index 311549c7..5c62503d 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -23,6 +23,7 @@ Contents python guile + persist ---- diff --git a/doc/persist.rst b/doc/persist.rst new file mode 100644 index 00000000..d6c215bb --- /dev/null +++ b/doc/persist.rst @@ -0,0 +1,558 @@ + +Persist +=============== + +This library allows to preserve structural sharing of ``immer`` containers while serializing and deserializing them. + + +Motivation: serialization +---------- + +Structural sharing allows ``immer`` containers to be efficient. At runtime, two distinct containers can be operated on +independently but internally they share nodes and use memory efficiently in that way. However when such containers are +serialized in a simple direct way, for example, as lists, this sharing is lost: they become truly independent, same data +is stored multiple times on disk and later, when it is read from disk, in memory. + +This library operates on the internal structure of ``immer`` containers: allowing it to be serialized, deserialized and +transformed. This enables more efficient storage, particularly when many nodes are reused, and, even more importantly, +preserving structural sharing after deserializing the containers. + + +Motivation: transformation +---------- + +Consider this scenario: an application has a document type that internally uses an ``immer`` container in multiple +places, for example, an ``immer::vector``. Some of these vectors would be completely identical, while +others would have just a few elements different (stored in an undo history, for example). The goal is to apply a +transformation function to these vectors. + +A direct approach would be to take each vector and create a new vector by applying the transformation function for each +element. However, after this process, all the structural sharing of the original containers would be lost: the result +would be multiple independent vectors without any structural sharing. + +This library enables the application of the transformation function directly on the nodes, preserving structural +sharing. Additionally, regardless of how many times a node is reused, the transformation needs to be performed only +once. + + +Dependencies +------------ + +In addition to the `dependencies `_ of ``immer``, this library makes use of **C++20**, +`Boost.Hana `_, `fmt `_ and `cereal `_. + + +.. _first-example: + +First example +------------- + +For this example, we'll use a `document` type that contains two ``immer`` vectors. + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: intro/start-types + :end-before: intro/end-types + +Let's make the ``document`` struct compatible with ``boost::hana``. This way, the ``persist`` library can determine what +pool types are needed and to name the pools. + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: intro/start-adapt-document-for-hana + :end-before: intro/end-adapt-document-for-hana + +Let's say we have two vectors ``v1`` and ``v2``, where ``v2`` is derived from ``v1`` so that it shares data with it: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: intro/start-prepare-value + :end-before: intro/end-prepare-value + +We can serialize the document using ``cereal`` with this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: intro/start-serialize-with-cereal + :end-before: intro/end-serialize-with-cereal + +Generating a JSON like this one: + +.. code-block:: c++ + + {"value0": {"ints": [1, 2, 3], "ints2": [1, 2, 3, 4, 5, 6]}} + +As you can see, ``ints`` and ``ints2`` contain the full linearization of each vector. +The structural sharing between these two data structures is not represented in its +serialized form. However, with ``immer-persist`` we can serialize it with: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: intro/start-serialize-with-persist + :end-before: intro/end-serialize-with-persist + +Which generates some JSON like this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: include:intro/start-persist-json + :end-before: include:intro/end-persist-json + +As you can see, the value is serialized with every ``immer`` container replaced by an identifier. +This identifier is a key into a pool, which is serialized just after. + +A pool represents a *set* of ``immer`` containers of a specific type. For example, we may have a pool that contains all +``immer::vector`` of our document. You can think of it as a small database of ``immer`` containers. When +serializing the pool, the internal structure of all those ``immer`` containers is written, preserving the structural +sharing between those containers. The nodes of the trees that make up the ``immer`` containers are directly represented +in the JSON and, because we are representing all the containers as a whole, those nodes that are referenced in +multiple trees can be stored only once. That same structure is preserved when reading the pool back from disk and +reconstructing the vectors (and other containers) from it, thus allowing us to preserve the structural sharing across +sessions. + +.. note:: + Currently, ``immer-persist`` makes a distiction between pools used for saving containers (*output* pools) and for loading containers (*input* pools), + similar to ``cereal`` with its ``InputArchive`` and ``OutputArchive`` distiction. + +Currently, ``immer-persist`` focuses on JSON as the serialization format and uses the ``cereal`` library internally. In principle, other formats +and serialization libraries could be supported in the future. + + +Custom policy +---------- + +We can use policy to control the names of the pools for each container. + +For this example, let's define a new document type ``doc_2``. It will also contain another type ``extra_data`` with a +``vector`` of ``strings`` in it. To demonstrate the responsibilities of the policy, the ``doc_2`` type will not be a +``boost::hana::Struct`` and will not allow for compile-time reflection. + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: include:start-doc_2-type + :end-before: include:end-doc_2-type + +We define the ``doc_2_policy`` as following: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: include:start-doc_2_policy + :end-before: include:end-doc_2_policy + +The ``get_pool_types`` function returns the types of containers that should be serialized with pools, in this case it's +both ``vector`` of ``ints`` and ``strings``. The ``save`` and ``load`` functions control the name of the document node, +in this case it is ``doc2_value``. And the ``get_pool_name`` overloaded functions supply the name of the pool for each +corresponding ``immer`` container. To create and serialize a value of ``doc_2``, you can use the following approach: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: include:start-doc_2-cereal_save_with_pools + :end-before: include:end-doc_2-cereal_save_with_pools + +The serialized JSON looks like this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: include:start-doc_2-json + :end-before: include:end-doc_2-json + +And it can also be loaded from JSON like this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: include:start-doc_2-load + :end-before: include:end-doc_2-load + +This example also demonstrates a scenario in which the main document type ``doc_2`` contains another type ``extra_data`` with a +``vector``. As you can see in the resulting JSON, nested types are also serialized with pools: ``"extra": {"comments": +1}``. Only the ID of the ``comments`` ``vector`` is serialized instead of its content. + +.. _transformations-with-pools: + +Transformations with pools +-------------------------- + +Suppose, we want to apply certain transforming functions to the ``immer`` containers inside a large document type. +The most straightforward way would be to simply create new containers with the new data, running the transforming +function over each element. However, this approach has some disadvantages: + +- All new containers will be independent, no structural sharing will be preserved, leading to the same data being stored + multiple times. +- The transformation would be applied more times than necessary when some of the data is shared. Example: one vector + is built by appending elements to the other vector. Transforming shared elements multiple times could be + unnecessary. + +Let's consider a simple case using the document from the :ref:`first-example`. The desired transformation would be to +multiply each element of the ``immer::vector`` by 10. + +First, the document value would be created in the same way: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: intro/start-prepare-value + :end-before: intro/end-prepare-value + +The next component we need is the pools of all the containers from the value: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-get_output_pools + :end-before: end-get_output_pools + +The ``get_output_pools`` function returns the output pools of all ``immer`` containers that would be serialized using +pools, as controlled by the policy. Here we use the default policy ``hana_struct_auto_policy`` which will use pools for +all ``immer`` containers inside the document type which must be a ``hana::Struct``. + +The other required component is the ``conversion_map``: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-conversion_map + :end-before: end-conversion_map + +This is a ``hana::map`` that describes the desired transformations to be applied. The key of the map is an ``immer`` +container and the value is the function to be applied to each element of the corresponding container type. In this case, +it will apply ``[](int val) { return val * 10; }`` to each ``int`` of the ``vector_one`` type, we have two of those in +the ``document``. + +Having these two parts, we can create new pools with the transformations: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-transformed_pools + :end-before: end-transformed_pools + +At this point, we can start converting the ``immer`` containers and create the transformed document value with them, +``new_value``: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-convert-containers + :end-before: end-convert-containers + +In order to confirm that the structural sharing has been preserved after applying the transformations, let's serialize +the ``new_value`` and inspect the JSON: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-save-new_value + :end-before: end-save-new_value + +And indeed, we can see in the JSON that the node ``[2, [10, 20]]`` is reused in both vectors. + + +Transformation into a different type +------------------------------------ + +The transforming function can even return a different type. In the following example, ``vector`` is transformed into +``vector``. The first two steps are the same as in the previous example: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: intro/start-prepare-value + :end-before: intro/end-prepare-value + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-get_output_pools + :end-before: end-get_output_pools + +Only this time the transforming function will convert an integer into a string: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-conversion_map-string + :end-before: end-conversion_map-string + +Then we convert the two vectors the same way as before: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-convert-vectors-of-strings + :end-before: end-convert-vectors-of-strings + +And in order to confirm that the structural sharing has been preserved, we can introduce a new document type with +the two vectors being ``vector``. + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-document_str + :end-before: end-document_str + +And serialize it with pools: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-save-new_value-str + :end-before: end-save-new_value-str + +In the resulting JSON we can confirm that the node ``[2, ["_1_", "_2_"]]`` is reused for both vectors. + +.. _transforming-hash-based-containers: + +Transforming hash-based containers +---------------------------------- + +As it was shown, converting ``vectors`` is conceptually simple: the transforming function is applied to each element of +each node, producing a new node with the transformed elements. When it comes to the hash-based containers, that is `set +`_, `map `_ and `table `_, their structure is defined +by the used hash function, so defining the transformation may become a bit more verbose. + +In the following example, we'll start with a simple case of transforming a map. For a map, only the hash of the key +matters and we will not modify the key yet. We will focus on transformations here and not on the structural sharing +within the document, so we will use the ``immer`` container itself as the document. Let's define the following policy to +indicate that we want to use pools only for our container: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-direct_container_policy + :end-before: end-direct_container_policy + +By default, ``immer`` uses ``std::hash`` for the hash-based containers. While this hash is sufficient for runtime use, it +can't be used for persistence, as noted in the `C++ reference `_: + +.. note:: + Hash functions are only required to produce the same result for the same input within a single execution of a program + +We will use `xxHash `_ as the hash for this example. Let's create a small map like this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-prepare-int-map + :end-before: end-prepare-int-map + +Our goal is to convert the value from ``int`` to ``std::string``. Let's create the ``conversion_map`` like this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-prepare-conversion_map + :end-before: end-prepare-conversion_map + +A few important details to note: + +- For maps, the transforming function accepts a pair of key and value, ``std::pair``. +- The transforming function must also be able to handle an argument of type + ``immer::persist::target_container_type_request``. This is achieved by using ``hana::overload`` to combine 2 lambdas + into one callable value. When called with that argument, it should return an empty container of the type we're + transforming to. This explicit approach is necessary because there is no reliable way to automatically determine the + hash algorithm for the new container. Even though in this case the type of the key doesn't change (and so the hash + remains the same), in other scenarios it might. + +Once the ``conversion_map`` is defined, the actual conversion is done as before: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-transform-map + :end-before: end-transform-map + +And we can see that the original map's values have been transformed into strings. + + +Transforming table's ID +------------------------ + +For this example, we'll transform the type of the ID of the table element while keeping the hash of it the same. +This can occur, for instance, if the member that serves as the ID gets wrapped in a wrapper type. + +To begin, let's define an item type for a table: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-old_item + :end-before: end-old_item + +We can create a table value with some data and get the pools for it like this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-prepare-table-value + :end-before: end-prepare-table-value + +In this example, we want to change the type of the ``old_item's`` ID, which is ``std::string``, while keeping its hash the same. +Let's define a wrapper for ``std::string`` and a ``new_item`` type like this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-new-table-types + :end-before: end-new-table-types + +We're also changing the type for ``data`` from ``int`` to ``std::string`` but this change doesn't affect the structure +of the table. We define the ``xx_hash_value`` function for the ``new_id_t`` type to make it compatible with the +``immer::persist::xx_hash`` hash. Then, we can define the target ``new_table_t`` type and the +``conversion_map`` that describes how to convert ``old_item`` into a ``new_item``. + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-prepare-new_table_t-type + :end-before: end-prepare-new_table_t-type + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-prepare-new_table_t-conversion_map + :end-before: end-prepare-new_table_t-conversion_map + +Finally, to convert the ``value`` using the defined ``conversion_map`` we prepare the converted pools with +``transform_output_pool`` and use ``convert_container`` to convert the ``value`` table. + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-new_table_t-transformation + :end-before: end-new_table_t-transformation + +We can see that the ``new_value`` table contains the transformed data from the original ``value`` table. + +.. _modifying-the-hash-of-the-id: + +Modifying the hash of the ID +---------------------------- + +If the key of a map, the ID of a table item or an element of a set changes its hash due to a transformation, the +transformed hash-based container can no longer keep its shape and it can't be efficiently transformed by simply applying +transformations to its nodes. + +``immer::persist`` validates every container it creates from a pool. If such a hash modification occurs, a runtime +exception will be thrown because it is not possible to detect this issue during compile-time. Let's modify the previous +example to also change the data of the ID: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-prepare-new_table_t-broken-conversion_map + :end-before: end-prepare-new_table_t-broken-conversion_map + +Now, if we attempt to convert the original table, a ``immer::persist::champ::hash_validation_failed_exception`` will be thrown: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-new_table_t-broken-transformation + :end-before: end-new_table_t-broken-transformation + +Even though such transformation can't be performed efficiently, on a node level, we can still request these +transformations to be applied. This will run for each value of the original container, creating a new independent +container that doesn't use structural sharing: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-prepare-new_table_t-new-hash-conversion_map + :end-before: end-prepare-new_table_t-new-hash-conversion_map + +We can request for such container-level (as opposed to per-node level) transformation to be performed by wrapping the +desired new container type ``new_table_t`` in a ``immer::persist::incompatible_hash_wrapper`` as the result of the +``immer::persist::target_container_type_request`` call. + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-new_table_t-new-hash-transformation + :end-before: end-new_table_t-new-hash-transformation + +We can see that the transformation has been applied, the keys have the ``_key`` suffix. + +.. note:: + While different transformed containers will not have structural sharing, transforming the same container multiple times will reuse previously transformed data. + In other words, transformation will be cached on the container level but not on the nodes level. + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-returned-transformed-container-is-the-same + :end-before: end-returned-transformed-container-is-the-same + + +.. _transforming-nested-containers: + +Transforming nested containers +------------------------------ + +Let's consider a scenario where a transforming function works on an item within an ``immer`` container and also needs to +transform another ``immer`` container. We define the types as follows: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-define-nested-types + :end-before: end-define-nested-types + +The important property here is that we have a ``vector`` where ``nested_t`` contains ``vector``, so we +can say a ``vector`` is nested inside another ``vector``. We can prepare a value with some structural sharing and then +serialize it: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-prepare-nested-value + :end-before: end-prepare-nested-value + +The resulting JSON looks like: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-nested-value-json + :end-before: end-nested-value-json + +Looking at the JSON we can confirm that the node ``[2, [1, 2]]`` is reused. +Let's define a ``conversion_map`` like this: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-nested-conversion_map + :end-before: end-nested-conversion_map + +The transforming function for ``vector_one`` is simple as it transforms an ``int`` into a ``std::string``. However, the +function for the ``vector`` is more involved. When we attempt to transform one item of that vector, +``nested_t``, we realize that inside that function we have a ``vector`` to deal with. This brings us back to the +problems described in the beginning of the :ref:`transformations-with-pools` section. To solve this issue, +``immer::persist`` provides an optional second argument to the transforming function, a function called +``convert_container``. This function can be called with two arguments: the desired container type and the ``immer`` +container to convert. This allows us to access the ``conversion_map`` we're defining. This transformation will be +performed using pools and will preserve structural sharing as expected. + +Having defined the ``conversion_map``, we apply it in the usual way and get the ``new_value``: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-apply-nested-transformations + :end-before: end-apply-nested-transformations + +We can verify that the ``new_value`` has the expected content: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-verify-nested-value + :end-before: end-verify-nested-value + +And we can serialize it again to confirm that the structural sharing of the nested vectors has been preserved: + +.. literalinclude:: ../test/extra/persist/test_for_docs.cpp + :language: c++ + :start-after: start-verify-structural-sharing-of-nested + :end-before: end-verify-structural-sharing-of-nested + +We can see that the ``[2, ["_1_", "_2_"]]`` node is still being reused in the two vectors. + + +Policy +------ + +.. doxygengroup:: Persist-policy + :project: immer + :content-only: + + +API Overview +------------ + +.. doxygengroup:: persist-api + :project: immer + :content-only: + + +Transform API +--------------- + +.. doxygengroup:: Persist-transform + :project: immer + :content-only: + + +Exceptions +---------- + +.. doxygengroup:: Persist-exceptions + :project: immer + :content-only: diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 2aa0f554..32f4807d 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -1,9 +1,7 @@ +# Targets +# ======= -# Targets -# ======= - -add_custom_target(examples - COMMENT "Build all examples.") +add_custom_target(examples COMMENT "Build all examples.") add_dependencies(check examples) file(GLOB_RECURSE immer_examples "*.cpp") diff --git a/example/vector/fizzbuzz.cpp b/example/vector/fizzbuzz.cpp index c7765a62..7638dd83 100644 --- a/example/vector/fizzbuzz.cpp +++ b/example/vector/fizzbuzz.cpp @@ -16,11 +16,11 @@ immer::vector fizzbuzz(immer::vector v, int first, int last) { for (auto i = first; i < last; ++i) - v = std::move(v).push_back( - i % 15 == 0 ? "FizzBuzz" - : i % 5 == 0 ? "Bizz" - : i % 3 == 0 ? "Fizz" : - /* else */ std::to_string(i)); + v = std::move(v).push_back(i % 15 == 0 ? "FizzBuzz" + : i % 5 == 0 ? "Bizz" + : i % 3 == 0 ? "Fizz" + : + /* else */ std::to_string(i)); return v; } // include:fizzbuzz/end diff --git a/extra/fuzzer/CMakeLists.txt b/extra/fuzzer/CMakeLists.txt index 2c160f79..7aea77e0 100644 --- a/extra/fuzzer/CMakeLists.txt +++ b/extra/fuzzer/CMakeLists.txt @@ -1,32 +1,41 @@ +add_custom_target(fuzzers COMMENT "Build all fuzzers.") -add_custom_target(fuzzers - COMMENT "Build all fuzzers.") - -if (CHECK_FUZZERS) +if(CHECK_FUZZERS) add_dependencies(tests fuzzers) endif() -# LIB_FUZZING_ENGINE is set by the Google OSS-Fuzz -# infrastructure. Otherwise we use Clang's LibFuzzer -if (DEFINED ENV{LIB_FUZZING_ENGINE}) +# LIB_FUZZING_ENGINE is set by the Google OSS-Fuzz infrastructure. Otherwise we +# use Clang's LibFuzzer +if(DEFINED ENV{LIB_FUZZING_ENGINE}) set(immer_fuzzing_engine $ENV{LIB_FUZZING_ENGINE}) else() set(immer_fuzzing_engine "-fsanitize=fuzzer") endif() file(GLOB_RECURSE immer_fuzzers "*.cpp") +foreach(TMP_PATH ${immer_fuzzers}) + string(FIND ${TMP_PATH} persist EXCLUDE_DIR_FOUND) + if(NOT ${EXCLUDE_DIR_FOUND} EQUAL -1) + list(REMOVE_ITEM immer_fuzzers ${TMP_PATH}) + endif() +endforeach(TMP_PATH) + foreach(_file IN LISTS immer_fuzzers) immer_target_name_for(_target _output "${_file}") add_executable(${_target} EXCLUDE_FROM_ALL "${_file}") set_target_properties(${_target} PROPERTIES OUTPUT_NAME ${_output}) target_compile_options(${_target} PUBLIC ${immer_fuzzing_engine}) - target_link_libraries(${_target} PUBLIC ${immer_fuzzing_engine} - immer-dev) + target_compile_definitions(${_target} PUBLIC IMMER_THROW_ON_INVALID_STATE=1) + target_link_libraries(${_target} PUBLIC ${immer_fuzzing_engine} immer-dev) add_dependencies(fuzzers ${_target}) - if (CHECK_FUZZERS) + if(CHECK_FUZZERS) add_test("fuzzer/${_output}" ${_output} -max_total_time=1) endif() - if (immer_INSTALL_FUZZERS) + if(immer_INSTALL_FUZZERS) install(TARGETS ${_target} DESTINATION bin) endif() endforeach() + +if(immer_BUILD_PERSIST_TESTS) + add_subdirectory(persist) +endif() diff --git a/extra/fuzzer/flex-vector-st.cpp b/extra/fuzzer/flex-vector-st.cpp index 1f2edb85..30f48ebd 100644 --- a/extra/fuzzer/flex-vector-st.cpp +++ b/extra/fuzzer/flex-vector-st.cpp @@ -42,8 +42,25 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, auto is_valid_size = [](auto& v) { return [&](auto idx) { return idx >= 0 && idx <= v.size(); }; }; - auto can_concat = [](auto&& v1, auto&& v2) { - return v1.size() + v2.size() < vector_t::max_size(); + auto can_concat = [](const auto& v1, const auto& v2) { + // First, check max_size + if (v1.size() + v2.size() > vector_t::max_size()) { + return false; + } + + // But just checking max_size is not sufficient, because there are other + // conditions for the validity of the tree, like shift constraints, for + // example. + try { + // Try to concat and catch an exception if it fails + const auto v3 = v1 + v2; + if (v3.size()) { + return true; + } + } catch (const immer::detail::rbts::invalid_tree&) { + return false; + } + return true; }; auto can_compare = [](auto&& v) { // avoid comparing vectors that are too big, and hence, slow to compare diff --git a/extra/fuzzer/fuzzer_input.hpp b/extra/fuzzer/fuzzer_input.hpp index 22def081..e1bc6954 100644 --- a/extra/fuzzer/fuzzer_input.hpp +++ b/extra/fuzzer/fuzzer_input.hpp @@ -53,7 +53,8 @@ struct fuzzer_input try { while (step(*this)) continue; - } catch (const no_more_input&) {}; + } catch (const no_more_input&) { + }; return 0; } }; diff --git a/extra/fuzzer/persist/CMakeLists.txt b/extra/fuzzer/persist/CMakeLists.txt new file mode 100644 index 00000000..fc8b4e46 --- /dev/null +++ b/extra/fuzzer/persist/CMakeLists.txt @@ -0,0 +1,18 @@ +find_package(fmt REQUIRED) + +file(GLOB_RECURSE immer_fuzzers "*.cpp") +foreach(_file IN LISTS immer_fuzzers) + immer_target_name_for(_target _output "${_file}") + add_executable(${_target} EXCLUDE_FROM_ALL "${_file}") + set_target_properties(${_target} PROPERTIES OUTPUT_NAME ${_output}) + target_compile_options(${_target} PUBLIC ${immer_fuzzing_engine}) + target_include_directories(${_target} PRIVATE ../..) + target_link_libraries(${_target} PUBLIC ${immer_fuzzing_engine} immer-dev) + add_dependencies(fuzzers ${_target}) + if(CHECK_FUZZERS) + add_test("fuzzer/${_output}" ${_output} -max_total_time=1) + endif() + if(immer_INSTALL_FUZZERS) + install(TARGETS ${_target} DESTINATION bin) + endif() +endforeach() diff --git a/extra/fuzzer/persist/flex-vector.cpp b/extra/fuzzer/persist/flex-vector.cpp new file mode 100644 index 00000000..9e41c139 --- /dev/null +++ b/extra/fuzzer/persist/flex-vector.cpp @@ -0,0 +1,184 @@ +// +// immer: immutable data structures for C++ +// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente +// +// This software is distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt +// + +#include + +#include +#include + +#include +#include +#include + +#include + +namespace { +void require_eq(const auto& a, const auto& b) +{ + if (a != b) { + throw std::runtime_error{ + fmt::format("{} != {}", fmt::join(a, ", "), fmt::join(b, ", "))}; + } +} +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 8; + constexpr auto bits = 2; + + using vector_t = + immer::flex_vector; + using size_t = std::uint8_t; + + const auto check_save_and_load = [&](const auto& vec) { + auto pool = immer::persist::rbts::make_output_pool_for(vec); + auto vector_id = immer::persist::container_id{}; + std::tie(pool, vector_id) = + immer::persist::rbts::add_to_pool(vec, pool); + + auto loader = + immer::persist::rbts::make_loader_for(vec, to_input_pool(pool)); + auto loaded = loader.load(vector_id); + require_eq(vec, loaded); + }; + + auto vars = std::array{}; + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + auto is_valid_var_neq = [](auto other) { + return [=](auto idx) { + return idx >= 0 && idx < var_count && idx != other; + }; + }; + auto is_valid_index = [](auto& v) { + return [&](auto idx) { return idx >= 0 && idx < v.size(); }; + }; + auto is_valid_size = [](auto& v) { + return [&](auto idx) { return idx >= 0 && idx <= v.size(); }; + }; + auto can_concat = [](auto&& v1, auto&& v2) { + return v1.size() + v2.size() < vector_t::max_size() / 4; + }; + auto can_compare = [](auto&& v) { + // avoid comparing vectors that are too big, and hence, slow to compare + return v.size() < (1 << 15); + }; + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_push_back, + op_update, + op_take, + op_drop, + op_concat, + op_push_back_move, + op_update_move, + op_take_move, + op_drop_move, + op_concat_move_l, + op_concat_move_r, + op_concat_move_lr, + op_insert, + op_erase, + op_compare, + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + const auto op = read(in); + switch (op) { + case op_push_back: { + vars[dst] = vars[src].push_back(42); + break; + } + case op_update: { + auto idx = read(in, is_valid_index(vars[src])); + vars[dst] = vars[src].update(idx, [](auto x) { return x + 1; }); + break; + } + case op_take: { + auto idx = read(in, is_valid_size(vars[src])); + vars[dst] = vars[src].take(idx); + break; + } + case op_drop: { + auto idx = read(in, is_valid_size(vars[src])); + vars[dst] = vars[src].drop(idx); + break; + } + case op_concat: { + auto src2 = read(in, is_valid_var); + if (can_concat(vars[src], vars[src2])) + vars[dst] = vars[src] + vars[src2]; + break; + } + case op_push_back_move: { + vars[dst] = std::move(vars[src]).push_back(21); + break; + } + case op_update_move: { + auto idx = read(in, is_valid_index(vars[src])); + vars[dst] = + std::move(vars[src]).update(idx, [](auto x) { return x + 1; }); + break; + } + case op_take_move: { + auto idx = read(in, is_valid_size(vars[src])); + vars[dst] = std::move(vars[src]).take(idx); + break; + } + case op_drop_move: { + auto idx = read(in, is_valid_size(vars[src])); + vars[dst] = std::move(vars[src]).drop(idx); + break; + } + case op_concat_move_l: { + auto src2 = read(in, is_valid_var_neq(src)); + if (can_concat(vars[src], vars[src2])) + vars[dst] = std::move(vars[src]) + vars[src2]; + break; + } + case op_concat_move_r: { + auto src2 = read(in, is_valid_var_neq(src)); + if (can_concat(vars[src], vars[src2])) + vars[dst] = vars[src] + std::move(vars[src2]); + break; + } + case op_concat_move_lr: { + auto src2 = read(in, is_valid_var_neq(src)); + if (can_concat(vars[src], vars[src2])) + vars[dst] = std::move(vars[src]) + std::move(vars[src2]); + break; + } + case op_compare: { + using std::swap; + if (can_compare(vars[src]) && vars[src] == vars[dst]) + swap(vars[src], vars[dst]); + break; + } + case op_erase: { + auto idx = read(in, is_valid_index(vars[src])); + vars[dst] = vars[src].erase(idx); + break; + } + case op_insert: { + auto idx = read(in, is_valid_size(vars[src])); + vars[dst] = vars[src].insert(idx, immer::box{42}); + break; + } + default: + break; + }; + + check_save_and_load(vars[src]); + check_save_and_load(vars[dst]); + + return true; + }); +} diff --git a/extra/fuzzer/persist/fuzz-set.cpp b/extra/fuzzer/persist/fuzz-set.cpp new file mode 100644 index 00000000..6ceab89f --- /dev/null +++ b/extra/fuzzer/persist/fuzz-set.cpp @@ -0,0 +1,117 @@ +#include + +#include + +#include +#include + +namespace { + +struct broken_hash +{ + std::size_t operator()(std::size_t map_key) const { return map_key & ~15; } +}; + +const auto into_set = [](const auto& set) { + using T = typename std::decay_t::value_type; + auto result = immer::set{}; + for (const auto& item : set) { + result = std::move(result).insert(item); + } + return result; +}; + +} // namespace + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using set_t = immer::set; + + auto vars = std::array{}; + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_insert, + op_erase, + op_insert_move, + op_erase_move, + op_iterate, + op_check, + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + switch (read(in)) { + case op_insert: { + auto value = read(in); + vars[dst] = vars[src].insert(value); + break; + } + case op_erase: { + auto value = read(in); + vars[dst] = vars[src].erase(value); + break; + } + case op_insert_move: { + auto value = read(in); + vars[dst] = std::move(vars[src]).insert(value); + break; + } + case op_erase_move: { + auto value = read(in); + vars[dst] = vars[src].erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].insert(v); + } + break; + } + case op_check: { + const auto bitset = std::bitset(read(in)); + auto ids = + std::vector>{}; + auto pool = immer::persist::champ::container_output_pool{}; + for (auto index = std::size_t{}; index < var_count; ++index) { + if (bitset[index]) { + auto set_id = immer::persist::node_id{}; + std::tie(pool, set_id) = + immer::persist::champ::add_to_pool(vars[index], pool); + ids.emplace_back(index, set_id); + } + } + + auto loader = immer::persist::champ::container_loader{ + to_input_pool(pool)}; + + for (const auto& [index, set_id] : ids) { + const auto& set = vars[index]; + const auto loaded = loader.load(set_id); + assert(into_set(loaded) == into_set(set)); + assert(into_set(set).size() == set.size()); + for (const auto& item : set) { + // This is the only thing that actually breaks if the hash + // of the loaded set is not the same as the hash function of + // the serialized set. + assert(loaded.count(item)); + } + for (const auto& item : loaded) { + assert(set.count(item)); + } + } + + break; + } + default: + break; + }; + return true; + }); +} diff --git a/extra/guile/CMakeLists.txt b/extra/guile/CMakeLists.txt index 99338060..3eeb9ab9 100644 --- a/extra/guile/CMakeLists.txt +++ b/extra/guile/CMakeLists.txt @@ -1,9 +1,8 @@ - find_package(PkgConfig) pkg_check_modules(Guile guile-2.2) -if (NOT Guile_FOUND) +if(NOT Guile_FOUND) message(STATUS "Disabling Guile modules") return() endif() @@ -11,14 +10,10 @@ endif() set(GUILE_EXTENSION_DIR ${CMAKE_CURRENT_BINARY_DIR}) configure_file(immer.scm.in immer.scm) -add_library(guile-immer SHARED EXCLUDE_FROM_ALL - src/immer.cpp) -target_include_directories(guile-immer PUBLIC - ${CMAKE_CURRENT_SOURCE_DIR} - ${CALLABLE_TRAITS_INCLUDE_DIR} - ${Guile_INCLUDE_DIRS}) -target_link_libraries(guile-immer PUBLIC - immer - ${Guile_LIBRARIES}) +add_library(guile-immer SHARED EXCLUDE_FROM_ALL src/immer.cpp) +target_include_directories( + guile-immer PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CALLABLE_TRAITS_INCLUDE_DIR} + ${Guile_INCLUDE_DIRS}) +target_link_libraries(guile-immer PUBLIC immer ${Guile_LIBRARIES}) add_custom_target(guile DEPENDS guile-immer) diff --git a/extra/guile/scm/detail/convert.hpp b/extra/guile/scm/detail/convert.hpp index 4c87ff18..40e9b07f 100644 --- a/extra/guile/scm/detail/convert.hpp +++ b/extra/guile/scm/detail/convert.hpp @@ -19,40 +19,39 @@ namespace scm { namespace detail { -template +template struct convert; template -auto to_scm(T&& v) - -> SCM_DECLTYPE_RETURN( - convert>::to_scm(std::forward(v))); +auto to_scm(T&& v) -> SCM_DECLTYPE_RETURN( + convert>::to_scm(std::forward(v))); template -auto to_cpp(SCM v) - -> SCM_DECLTYPE_RETURN( - convert>::to_cpp(v)); +auto to_cpp(SCM v) -> SCM_DECLTYPE_RETURN(convert>::to_cpp(v)); } // namespace detail } // namespace scm -#define SCM_DECLARE_NUMERIC_TYPE(cpp_name__, scm_name__) \ - namespace scm { \ - namespace detail { \ - template <> \ - struct convert { \ - static cpp_name__ to_cpp(SCM v) { return scm_to_ ## scm_name__(v); } \ - static SCM to_scm(cpp_name__ v) { return scm_from_ ## scm_name__(v); } \ - }; \ - }} /* namespace scm::detail */ \ +#define SCM_DECLARE_NUMERIC_TYPE(cpp_name__, scm_name__) \ + namespace scm { \ + namespace detail { \ + template <> \ + struct convert \ + { \ + static cpp_name__ to_cpp(SCM v) { return scm_to_##scm_name__(v); } \ + static SCM to_scm(cpp_name__ v) { return scm_from_##scm_name__(v); } \ + }; \ + } \ + } /* namespace scm::detail */ \ /**/ -SCM_DECLARE_NUMERIC_TYPE(float, double); -SCM_DECLARE_NUMERIC_TYPE(double, double); -SCM_DECLARE_NUMERIC_TYPE(std::int8_t, int8); -SCM_DECLARE_NUMERIC_TYPE(std::int16_t, int16); -SCM_DECLARE_NUMERIC_TYPE(std::int32_t, int32); -SCM_DECLARE_NUMERIC_TYPE(std::int64_t, int64); -SCM_DECLARE_NUMERIC_TYPE(std::uint8_t, uint8); +SCM_DECLARE_NUMERIC_TYPE(float, double); +SCM_DECLARE_NUMERIC_TYPE(double, double); +SCM_DECLARE_NUMERIC_TYPE(std::int8_t, int8); +SCM_DECLARE_NUMERIC_TYPE(std::int16_t, int16); +SCM_DECLARE_NUMERIC_TYPE(std::int32_t, int32); +SCM_DECLARE_NUMERIC_TYPE(std::int64_t, int64); +SCM_DECLARE_NUMERIC_TYPE(std::uint8_t, uint8); SCM_DECLARE_NUMERIC_TYPE(std::uint16_t, uint16); SCM_DECLARE_NUMERIC_TYPE(std::uint32_t, uint32); SCM_DECLARE_NUMERIC_TYPE(std::uint64_t, uint64); diff --git a/extra/guile/scm/detail/define.hpp b/extra/guile/scm/detail/define.hpp index 08b6e763..07029f31 100644 --- a/extra/guile/scm/detail/define.hpp +++ b/extra/guile/scm/detail/define.hpp @@ -21,11 +21,11 @@ namespace detail { template static void define_impl(const std::string& name, Fn fn) { - using args_t = function_args_t; + using args_t = function_args_t; constexpr auto args_size = pack_size_v; constexpr auto has_rest = std::is_same, scm::args>{}; constexpr auto arg_count = args_size - has_rest; - auto subr = (scm_t_subr) +subr_wrapper_aux(fn, args_t{}); + auto subr = (scm_t_subr) + subr_wrapper_aux(fn, args_t{}); scm_c_define_gsubr(name.c_str(), arg_count, 0, has_rest, subr); #if SCM_AUTO_EXPORT scm_c_export(name.c_str()); diff --git a/extra/guile/scm/detail/finalizer_wrapper.hpp b/extra/guile/scm/detail/finalizer_wrapper.hpp index 258249eb..03a5f5fc 100644 --- a/extra/guile/scm/detail/finalizer_wrapper.hpp +++ b/extra/guile/scm/detail/finalizer_wrapper.hpp @@ -8,9 +8,9 @@ #pragma once -#include -#include #include +#include +#include namespace scm { namespace detail { @@ -30,23 +30,21 @@ auto finalizer_wrapper_impl(Fn fn, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1) { invoke(fn_, to_cpp(a1)); }; + return [](SCM a1) { invoke(fn_, to_cpp(a1)); }; } template auto finalizer_wrapper_impl(Fn fn, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1, SCM a2) { - invoke(fn_, to_cpp(a1), to_cpp(a2)); - }; + return [](SCM a1, SCM a2) { invoke(fn_, to_cpp(a1), to_cpp(a2)); }; } template auto finalizer_wrapper_impl(Fn fn, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1, SCM a2, SCM a3) { + return [](SCM a1, SCM a2, SCM a3) { invoke(fn_, to_cpp(a1), to_cpp(a2), to_cpp(a3)); }; } diff --git a/extra/guile/scm/detail/function_args.hpp b/extra/guile/scm/detail/function_args.hpp index 809e3eb1..dbe44627 100644 --- a/extra/guile/scm/detail/function_args.hpp +++ b/extra/guile/scm/detail/function_args.hpp @@ -8,8 +8,8 @@ #pragma once -#include #include +#include namespace scm { namespace detail { diff --git a/extra/guile/scm/detail/invoke.hpp b/extra/guile/scm/detail/invoke.hpp index d9f2b37c..3956b4c8 100644 --- a/extra/guile/scm/detail/invoke.hpp +++ b/extra/guile/scm/detail/invoke.hpp @@ -11,25 +11,23 @@ // Adapted from the official std::invoke proposal: // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4169.html -#include #include +#include namespace scm { namespace detail { template -std::enable_if_t< - std::is_member_pointer>::value, - std::result_of_t> +std::enable_if_t>::value, + std::result_of_t> invoke(Functor&& f, Args&&... args) { return std::mem_fn(f)(std::forward(args)...); } template -std::enable_if_t< - !std::is_member_pointer>::value, - std::result_of_t> +std::enable_if_t>::value, + std::result_of_t> invoke(Functor&& f, Args&&... args) { return std::forward(f)(std::forward(args)...); diff --git a/extra/guile/scm/detail/pack.hpp b/extra/guile/scm/detail/pack.hpp index 9a181357..9a18d78c 100644 --- a/extra/guile/scm/detail/pack.hpp +++ b/extra/guile/scm/detail/pack.hpp @@ -14,7 +14,8 @@ namespace detail { struct none_t; template -struct pack {}; +struct pack +{}; template struct pack_size; @@ -34,9 +35,8 @@ struct pack_last using type = none_t; }; -template -struct pack_last> - : pack_last> +template +struct pack_last> : pack_last> {}; template diff --git a/extra/guile/scm/detail/subr_wrapper.hpp b/extra/guile/scm/detail/subr_wrapper.hpp index fc11ff1c..81ae49ca 100644 --- a/extra/guile/scm/detail/subr_wrapper.hpp +++ b/extra/guile/scm/detail/subr_wrapper.hpp @@ -8,9 +8,9 @@ #pragma once -#include -#include #include +#include +#include namespace scm { namespace detail { @@ -24,35 +24,37 @@ auto subr_wrapper_impl(Fn fn, pack, pack<>) { check_call_once(); static const Fn fn_ = fn; - return [] () -> SCM { return to_scm(invoke(fn_)); }; + return []() -> SCM { return to_scm(invoke(fn_)); }; } template auto subr_wrapper_impl(Fn fn, pack, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1) -> SCM { - return to_scm(invoke(fn_, to_cpp(a1))); - }; + return [](SCM a1) -> SCM { return to_scm(invoke(fn_, to_cpp(a1))); }; } template auto subr_wrapper_impl(Fn fn, pack, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1, SCM a2) -> SCM { + return [](SCM a1, SCM a2) -> SCM { return to_scm(invoke(fn_, to_cpp(a1), to_cpp(a2))); }; } -template auto subr_wrapper_impl(Fn fn, pack, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1, SCM a2, SCM a3) -> SCM { - return to_scm(invoke(fn_, to_cpp(a1), to_cpp(a2), - to_cpp(a3))); + return [](SCM a1, SCM a2, SCM a3) -> SCM { + return to_scm( + invoke(fn_, to_cpp(a1), to_cpp(a2), to_cpp(a3))); }; } @@ -61,15 +63,19 @@ auto subr_wrapper_impl(Fn fn, pack, pack<>) { check_call_once(); static const Fn fn_ = fn; - return [] () -> SCM { invoke(fn_); return SCM_UNSPECIFIED; }; + return []() -> SCM { + invoke(fn_); + return SCM_UNSPECIFIED; + }; } template auto subr_wrapper_impl(Fn fn, pack, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1) -> SCM { - invoke(fn_, to_cpp(a1)); return SCM_UNSPECIFIED; + return [](SCM a1) -> SCM { + invoke(fn_, to_cpp(a1)); + return SCM_UNSPECIFIED; }; } template @@ -77,7 +83,7 @@ auto subr_wrapper_impl(Fn fn, pack, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1, SCM a2) -> SCM { + return [](SCM a1, SCM a2) -> SCM { invoke(fn_, to_cpp(a1), to_cpp(a2)); return SCM_UNSPECIFIED; }; @@ -87,7 +93,7 @@ auto subr_wrapper_impl(Fn fn, pack, pack) { check_call_once(); static const Fn fn_ = fn; - return [] (SCM a1, SCM a2, SCM a3) -> SCM { + return [](SCM a1, SCM a2, SCM a3) -> SCM { invoke(fn_, to_cpp(a1), to_cpp(a2), to_cpp(a3)); return SCM_UNSPECIFIED; }; diff --git a/extra/guile/scm/detail/util.hpp b/extra/guile/scm/detail/util.hpp index fdc32372..510cc49f 100644 --- a/extra/guile/scm/detail/util.hpp +++ b/extra/guile/scm/detail/util.hpp @@ -13,9 +13,8 @@ namespace scm { namespace detail { -#define SCM_DECLTYPE_RETURN(...) \ - decltype(__VA_ARGS__) \ - { return __VA_ARGS__; } \ +#define SCM_DECLTYPE_RETURN(...) \ + decltype(__VA_ARGS__) { return __VA_ARGS__; } \ /**/ template @@ -28,19 +27,21 @@ template void check_call_once() { static bool called = false; - if (called) scm_misc_error (nullptr, "Double defined binding. \ + if (called) + scm_misc_error(nullptr, + "Double defined binding. \ This may be caused because there are multiple C++ binding groups in the same \ translation unit. You may solve this by using different type tags for each \ -binding group.", SCM_EOL); +binding group.", + SCM_EOL); called = true; } struct move_sequence { - move_sequence() = default; + move_sequence() = default; move_sequence(const move_sequence&) = delete; - move_sequence(move_sequence&& other) - { other.moved_from_ = true; }; + move_sequence(move_sequence&& other) { other.moved_from_ = true; }; bool moved_from_ = false; }; diff --git a/extra/guile/scm/group.hpp b/extra/guile/scm/group.hpp index 69cd3858..159a4592 100644 --- a/extra/guile/scm/group.hpp +++ b/extra/guile/scm/group.hpp @@ -14,7 +14,7 @@ namespace scm { namespace detail { -template +template struct definer { using this_t = definer; @@ -22,30 +22,30 @@ struct definer std::string group_name_ = {}; - definer() = default; + definer() = default; definer(definer&&) = default; - template > + template > definer(definer) - {} + { + } template next_t define(std::string name, Fn fn) && { define_impl(name, fn); - return { std::move(*this) }; + return {std::move(*this)}; } template next_t maker(Fn fn) && { define_impl("make", fn); - return { std::move(*this) }; + return {std::move(*this)}; } }; -template +template struct group_definer { using this_t = group_definer; @@ -54,35 +54,37 @@ struct group_definer std::string group_name_ = {}; group_definer(std::string name) - : group_name_{std::move(name)} {} + : group_name_{std::move(name)} + { + } group_definer(group_definer&&) = default; - template > + template > group_definer(group_definer) - {} + { + } template next_t define(std::string name, Fn fn) && { define_impl(group_name_ + "-" + name, fn); - return { std::move(*this) }; + return {std::move(*this)}; } }; } // namespace detail -template +template detail::definer group() { return {}; } -template +template detail::group_definer group(std::string name) { - return { std::move(name) }; + return {std::move(name)}; } } // namespace scm diff --git a/extra/guile/scm/list.hpp b/extra/guile/scm/list.hpp index dc162c20..c0f09cbf 100644 --- a/extra/guile/scm/list.hpp +++ b/extra/guile/scm/list.hpp @@ -8,8 +8,8 @@ #pragma once -#include #include +#include namespace scm { @@ -18,26 +18,27 @@ struct list : detail::wrapper using base_t = detail::wrapper; using base_t::base_t; - using iterator = list; + using iterator = list; using value_type = val; - list() : base_t{SCM_EOL} {}; + list() + : base_t{SCM_EOL} {}; list end() const { return {}; } list begin() const { return *this; } explicit operator bool() { return handle_ != SCM_EOL; } - val operator* () const { return val{scm_car(handle_)}; } + val operator*() const { return val{scm_car(handle_)}; } - list& operator++ () + list& operator++() { handle_ = scm_cdr(handle_); return *this; } - list operator++ (int) + list operator++(int) { - auto result = *this; + auto result = *this; result.handle_ = scm_cdr(handle_); return result; } diff --git a/extra/guile/scm/scm.hpp b/extra/guile/scm/scm.hpp index f4e4989a..a9c1a433 100644 --- a/extra/guile/scm/scm.hpp +++ b/extra/guile/scm/scm.hpp @@ -8,7 +8,7 @@ #pragma once -#include -#include #include +#include #include +#include diff --git a/extra/guile/scm/type.hpp b/extra/guile/scm/type.hpp index da53ed46..7b443e0c 100644 --- a/extra/guile/scm/type.hpp +++ b/extra/guile/scm/type.hpp @@ -8,8 +8,8 @@ #pragma once -#include #include +#include #include namespace scm { @@ -33,7 +33,7 @@ struct convert_foreign_type assert(storage_t::data != SCM_UNSPECIFIED && "can not convert to undefined type"); scm_assert_foreign_object_type(storage_t::data, v); - return *(T*)scm_foreign_object_ref(v, 0); + return *(T*) scm_foreign_object_ref(v, 0); } template @@ -41,10 +41,9 @@ struct convert_foreign_type { assert(storage_t::data != SCM_UNSPECIFIED && "can not convert from undefined type"); - return scm_make_foreign_object_1( - storage_t::data, - new (scm_gc_malloc(sizeof(T), "scmpp")) T( - std::forward(v))); + return scm_make_foreign_object_1(storage_t::data, + new (scm_gc_malloc(sizeof(T), "scmpp")) + T(std::forward(v))); } }; @@ -57,23 +56,23 @@ struct convert::value>> : convert_foreign_type -{ -}; +{}; -template +template struct type_definer : move_sequence { using this_t = type_definer; using next_t = type_definer; - std::string type_name_ = {}; + std::string type_name_ = {}; scm_t_struct_finalize finalizer_ = nullptr; type_definer(type_definer&&) = default; type_definer(std::string type_name) : type_name_(std::move(type_name)) - {} + { + } ~type_definer() { @@ -87,67 +86,68 @@ struct type_definer : move_sequence } } - template > + template > type_definer(type_definer r) : move_sequence{std::move(r)} , type_name_{std::move(r.type_name_)} , finalizer_{std::move(r.finalizer_)} - {} + { + } next_t constructor() && { define_impl(type_name_, [] { return T{}; }); - return { std::move(*this) }; + return {std::move(*this)}; } template next_t constructor(Fn fn) && { define_impl(type_name_, fn); - return { std::move(*this) }; + return {std::move(*this)}; } next_t finalizer() && { - finalizer_ = (scm_t_struct_finalize) +finalizer_wrapper( - [] (T& x) { x.~T(); }); - return { std::move(*this) }; + finalizer_ = (scm_t_struct_finalize) + + finalizer_wrapper([](T& x) { x.~T(); }); + return {std::move(*this)}; } template next_t finalizer(Fn fn) && { - finalizer_ = (scm_t_struct_finalize) +finalizer_wrapper(fn); - return { std::move(*this) }; + finalizer_ = (scm_t_struct_finalize) + finalizer_wrapper(fn); + return {std::move(*this)}; } next_t maker() && { define_impl("make-" + type_name_, [] { return T{}; }); - return { std::move(*this) }; + return {std::move(*this)}; } template next_t maker(Fn fn) && { define_impl("make-" + type_name_, fn); - return { std::move(*this) }; + return {std::move(*this)}; } template next_t define(std::string name, Fn fn) && { define_impl(type_name_ + "-" + name, fn); - return { std::move(*this) }; + return {std::move(*this)}; } }; } // namespace detail -template +template detail::type_definer type(std::string type_name) { - return { type_name }; + return {type_name}; } } // namespace scm diff --git a/extra/guile/scm/val.hpp b/extra/guile/scm/val.hpp index 63d71892..25876e92 100644 --- a/extra/guile/scm/val.hpp +++ b/extra/guile/scm/val.hpp @@ -23,9 +23,12 @@ struct convert_wrapper_type struct wrapper { wrapper() = default; - wrapper(SCM hdl) : handle_{hdl} {} + wrapper(SCM hdl) + : handle_{hdl} + { + } SCM get() const { return handle_; } - operator SCM () const { return handle_; } + operator SCM() const { return handle_; } bool operator==(wrapper other) { return handle_ == other.handle_; } bool operator!=(wrapper other) { return handle_ != other.handle_; } @@ -41,48 +44,61 @@ struct val : detail::wrapper using base_t = detail::wrapper; using base_t::base_t; - template , val>{} && - !std::is_same, SCM>{})>> + template < + typename T, + typename = std::enable_if_t<(!std::is_same, val>{} && + !std::is_same, SCM>{})>> val(T&& x) : base_t(detail::to_scm(std::forward(x))) - {} + { + } template (SCM{}))>{}>> - operator T() const { return detail::to_cpp(handle_); } + operator T() const + { + return detail::to_cpp(handle_); + } template (SCM{}))>{}>> - operator T& () const { return detail::to_cpp(handle_); } + operator T&() const + { + return detail::to_cpp(handle_); + } template (SCM{}))>{}>> - operator const T& () const { return detail::to_cpp(handle_); } - - val operator() () const - { return val{scm_call_0(get())}; } - val operator() (val a0) const - { return val{scm_call_1(get(), a0)}; } - val operator() (val a0, val a1) const - { return val{scm_call_2(get(), a0, a1)}; } - val operator() (val a0, val a1, val a3) const - { return val{scm_call_3(get(), a0, a1, a3)}; } + operator const T&() const + { + return detail::to_cpp(handle_); + } + + val operator()() const { return val{scm_call_0(get())}; } + val operator()(val a0) const { return val{scm_call_1(get(), a0)}; } + val operator()(val a0, val a1) const + { + return val{scm_call_2(get(), a0, a1)}; + } + val operator()(val a0, val a1, val a3) const + { + return val{scm_call_3(get(), a0, a1, a3)}; + } }; } // namespace scm -#define SCM_DECLARE_WRAPPER_TYPE(cpp_name__) \ - namespace scm { \ - namespace detail { \ - template <> \ - struct convert \ - : convert_wrapper_type {}; \ - }} /* namespace scm::detail */ \ +#define SCM_DECLARE_WRAPPER_TYPE(cpp_name__) \ + namespace scm { \ + namespace detail { \ + template <> \ + struct convert : convert_wrapper_type \ + {}; \ + } \ + } /* namespace scm::detail */ \ /**/ SCM_DECLARE_WRAPPER_TYPE(val); diff --git a/extra/python/CMakeLists.txt b/extra/python/CMakeLists.txt index 11d5fff2..dcc3ed0b 100644 --- a/extra/python/CMakeLists.txt +++ b/extra/python/CMakeLists.txt @@ -1,45 +1,35 @@ - option(USE_PYBIND "bind with pybind1" off) -option(USE_BOOST_PYTHON "bind with boost::python" off) +option(USE_BOOST_PYTHON "bind with boost::python" off) -if (USE_PYBIND) +if(USE_PYBIND) set(PYBIND11_CPP_STANDARD -std=c++14) find_package(Boost 1.56 REQUIRED) add_subdirectory(lib/pybind11) pybind11_add_module(immer_python_module src/immer-pybind.cpp) - target_link_libraries(immer_python_module PUBLIC - immer) + target_link_libraries(immer_python_module PUBLIC immer) elseif(USE_BOOST_PYTHON) find_package(PythonInterp) find_package(PythonLibs) find_package(Boost 1.56 COMPONENTS python) python_add_module(immer_python_module src/immer-boost.cpp) - include_directories(immer_python_module PUBLIC - ${immer_include_dir} - ${Boost_INCLUDE_DIRS} - ${PYTHON_INCLUDE_DIRS}) - target_link_libraries(immer_python_module PUBLIC - immer - ${Boost_LIBRARIES} - ${PYTHON_LIBRARIES}) + include_directories(immer_python_module PUBLIC ${immer_include_dir} + ${Boost_INCLUDE_DIRS} ${PYTHON_INCLUDE_DIRS}) + target_link_libraries(immer_python_module PUBLIC immer ${Boost_LIBRARIES} + ${PYTHON_LIBRARIES}) else() find_package(PythonInterp) find_package(PythonLibs) - if (NOT PYTHONLIBS_FOUND) + if(NOT PYTHONLIBS_FOUND) message(STATUS "Disabling Python modules") return() endif() - python_add_module(immer_python_module EXCLUDE_FROM_ALL - src/immer-raw.cpp) - target_include_directories(immer_python_module PUBLIC - ${PYTHON_INCLUDE_DIRS}) - target_link_libraries(immer_python_module PUBLIC - immer - ${PYTHON_LIBRARIES}) + python_add_module(immer_python_module EXCLUDE_FROM_ALL src/immer-raw.cpp) + target_include_directories(immer_python_module PUBLIC ${PYTHON_INCLUDE_DIRS}) + target_link_libraries(immer_python_module PUBLIC immer ${PYTHON_LIBRARIES}) endif() diff --git a/extra/python/src/immer-pybind.cpp b/extra/python/src/immer-pybind.cpp index 8f8aab12..131d42b9 100644 --- a/extra/python/src/immer-pybind.cpp +++ b/extra/python/src/immer-pybind.cpp @@ -8,29 +8,29 @@ #include -#include #include +#include namespace { struct heap_t { - template + template static void* allocate(std::size_t size, Tags...) { return PyMem_Malloc(size); } - template + template static void deallocate(std::size_t, void* obj, Tags...) { PyMem_Free(obj); } }; -using memory_t = immer::memory_policy< - immer::unsafe_free_list_heap_policy, - immer::unsafe_refcount_policy>; +using memory_t = + immer::memory_policy, + immer::unsafe_refcount_policy>; } // anonymous namespace @@ -53,19 +53,18 @@ PYBIND11_PLUGIN(immer_python_module) .def(py::init<>()) .def("__len__", &vector_t::size) .def("__getitem__", - [] (const vector_t& v, std::size_t i) { + [](const vector_t& v, std::size_t i) { if (i > v.size()) throw py::index_error{"Index out of range"}; return v[i]; }) .def("append", - [] (const vector_t& v, py::object x) { + [](const vector_t& v, py::object x) { return v.push_back(std::move(x)); }) - .def("set", - [] (const vector_t& v, std::size_t i, py::object x) { - return v.set(i, std::move(x)); - }); + .def("set", [](const vector_t& v, std::size_t i, py::object x) { + return v.set(i, std::move(x)); + }); #ifdef VERSION_INFO m.attr("__version__") = py::str(VERSION_INFO); diff --git a/extra/python/src/immer-raw.cpp b/extra/python/src/immer-raw.cpp index f7af7dc7..97ed3745 100644 --- a/extra/python/src/immer-raw.cpp +++ b/extra/python/src/immer-raw.cpp @@ -49,7 +49,8 @@ struct object_t explicit object_t(PyObject* p, wrap_t) : ptr_{p} - {} + { + } explicit object_t(PyObject* p, adopt_t) : ptr_{p} { diff --git a/flake.lock b/flake.lock index 523601b0..2baa426d 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,38 @@ { "nodes": { + "arximboldi-cereal-src": { + "flake": false, + "locked": { + "lastModified": 1650443560, + "narHash": "sha256-G8V5g0POddpukPmiWAX/MhnIhi+EEVE/P+MQGCGH/J0=", + "owner": "arximboldi", + "repo": "cereal", + "rev": "4bfaf5fee1cbc69db4614169092368a29c7607c4", + "type": "github" + }, + "original": { + "owner": "arximboldi", + "repo": "cereal", + "type": "github" + } + }, + "docs-nixpkgs": { + "flake": false, + "locked": { + "lastModified": 1504644969, + "narHash": "sha256-FCGfMedr9e5ePAEWOMhBmBs8CS0WiBqrlkGHGXV1Hc8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d0d905668c010b65795b57afdf7f0360aac6245b", + "type": "github" + }, + "original": { + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "d0d905668c010b65795b57afdf7f0360aac6245b", + "type": "github" + } + }, "flake-compat": { "flake": false, "locked": { @@ -118,6 +151,8 @@ }, "root": { "inputs": { + "arximboldi-cereal-src": "arximboldi-cereal-src", + "docs-nixpkgs": "docs-nixpkgs", "flake-compat": "flake-compat", "flake-utils": "flake-utils", "gitignore": "gitignore", diff --git a/flake.nix b/flake.nix index 18247fff..94750fab 100644 --- a/flake.nix +++ b/flake.nix @@ -21,24 +21,44 @@ nixpkgs.follows = "nixpkgs"; }; }; + arximboldi-cereal-src = { + url = "github:arximboldi/cereal"; + flake = false; + }; + docs-nixpkgs = { + url = "github:NixOS/nixpkgs/d0d905668c010b65795b57afdf7f0360aac6245b"; + flake = false; + }; }; outputs = { self, nixpkgs, + docs-nixpkgs, flake-utils, flake-compat, pre-commit-hooks, gitignore, + arximboldi-cereal-src, }: flake-utils.lib.eachDefaultSystem (system: let pkgs = nixpkgs.legacyPackages.${system}; + withLLVM = drv: if pkgs.stdenv.isLinux # Use LLVM for Linux to build fuzzers then drv.override {stdenv = pkgs.llvmPackages_latest.stdenv;} # macOS uses LLVM by default else drv; + + arximboldi-cereal = pkgs.callPackage ./nix/cereal.nix {inherit arximboldi-cereal-src;}; + + persist-inputs = with pkgs; [ + fmt + arximboldi-cereal + xxHash + nlohmann_json + ]; in { checks = { @@ -46,6 +66,11 @@ src = ./.; hooks = { alejandra.enable = true; + cmake-format.enable = true; + clang-format = { + enable = true; + types_or = pkgs.lib.mkForce ["c" "c++"]; + }; }; }; @@ -58,26 +83,75 @@ ninjaFlags = ["tests"]; checkPhase = '' ctest -D ExperimentalMemCheck + valgrind --quiet --error-exitcode=99 --leak-check=full --errors-for-leak-kinds=all \ + --suppressions=./test/extra/persist/valgrind.supp \ + ./test/extra/persist/persist-tests ''; }); }; - devShells.default = (withLLVM pkgs.mkShell) { - NIX_HARDENING_ENABLE = ""; - inputsFrom = [ - (import ./shell.nix { - inherit system nixpkgs; - }) - ]; + devShells = + { + default = (withLLVM pkgs.mkShell) { + NIX_HARDENING_ENABLE = ""; + inputsFrom = [ + (import ./shell.nix { + inherit system nixpkgs; + }) + ]; + + packages = with pkgs; + [ + # for the llvm-symbolizer binary, that allows to show stacks in ASAN and LeakSanitizer. + llvmPackages_latest.bintools-unwrapped + cmake-format + alejandra + just + fzf + starship + ] + ++ persist-inputs; + + shellHook = + self.checks.${system}.pre-commit-check.shellHook + + "\n" + + '' + alias j=just + eval "$(starship init bash)" + ''; + }; + } + // pkgs.lib.optionalAttrs pkgs.stdenv.isLinux { + # doxygen doesn't work on macOS currently + docs = let + docsPkgs = import docs-nixpkgs {inherit system;}; + docs = docsPkgs.callPackage ./nix/docs.nix {}; + in + pkgs.mkShell { + packages = [ + pkgs.just + pkgs.fzf + pkgs.starship + pkgs.cmake + pkgs.ninja - packages = [ - # for the llvm-symbolizer binary, that allows to show stacks in ASAN and LeakSanitizer. - pkgs.llvmPackages_latest.bintools-unwrapped - ]; + docsPkgs.doxygen + (docsPkgs.python.withPackages (ps: [ + ps.sphinx + docs.breathe + docs.recommonmark + ])) + ]; - shellHook = - self.checks.${system}.pre-commit-check.shellHook; - }; + shellHook = + self.checks.${system}.pre-commit-check.shellHook + + "\n" + + '' + alias j=just + eval "$(starship init bash)" + ''; + }; + }; packages = { immer = let @@ -112,7 +186,7 @@ unit-tests = (withLLVM self.packages.${system}.immer).overrideAttrs (prev: { name = "immer-unit-tests"; - buildInputs = with pkgs; [catch2_3 boehmgc boost fmt]; + buildInputs = with pkgs; [catch2_3 boehmgc boost fmt] ++ persist-inputs; nativeBuildInputs = with pkgs; [cmake ninja]; dontBuild = false; doCheck = true; @@ -124,7 +198,9 @@ cmakeFlags = [ "-DCMAKE_BUILD_TYPE=Debug" "-Dimmer_BUILD_TESTS=ON" + "-Dimmer_BUILD_PERSIST_TESTS=ON" "-Dimmer_BUILD_EXAMPLES=OFF" + "-DCXX_STANDARD=20" ]; }); }; diff --git a/immer/array.hpp b/immer/array.hpp index f71477c2..a986c199 100644 --- a/immer/array.hpp +++ b/immer/array.hpp @@ -75,7 +75,8 @@ class array */ array(std::initializer_list values) : impl_{impl_t::from_initializer_list(values)} - {} + { + } /*! * Constructs a array containing the elements in the range @@ -88,7 +89,8 @@ class array bool> = true> array(Iter first, Sent last) : impl_{impl_t::from_range(first, last)} - {} + { + } /*! * Constructs an array containing the element `val` repeated `n` @@ -96,7 +98,8 @@ class array */ array(size_type n, T v = {}) : impl_{impl_t::from_fill(n, v)} - {} + { + } /*! * Returns an iterator pointing at the first element of the @@ -325,7 +328,8 @@ class array array(impl_t impl) : impl_(std::move(impl)) - {} + { + } array&& push_back_move(std::true_type, value_type value) { diff --git a/immer/array_transient.hpp b/immer/array_transient.hpp index 08000b2d..0f1c96ff 100644 --- a/immer/array_transient.hpp +++ b/immer/array_transient.hpp @@ -196,7 +196,8 @@ class array_transient : MemoryPolicy::transience_t::owner array_transient(impl_t impl) : impl_(std::move(impl)) - {} + { + } impl_t impl_ = impl_t::empty(); }; diff --git a/immer/atom.hpp b/immer/atom.hpp index 2cf409b9..af92e04a 100644 --- a/immer/atom.hpp +++ b/immer/atom.hpp @@ -27,14 +27,15 @@ struct refcount_atom_impl using lock_t = typename MemoryPolicy::lock; using scoped_lock_t = typename lock_t::scoped_lock; - refcount_atom_impl(const refcount_atom_impl&) = delete; - refcount_atom_impl(refcount_atom_impl&&) = delete; + refcount_atom_impl(const refcount_atom_impl&) = delete; + refcount_atom_impl(refcount_atom_impl&&) = delete; refcount_atom_impl& operator=(const refcount_atom_impl&) = delete; - refcount_atom_impl& operator=(refcount_atom_impl&&) = delete; + refcount_atom_impl& operator=(refcount_atom_impl&&) = delete; refcount_atom_impl(box_type b) : impl_{std::move(b)} - {} + { + } box_type load() const { @@ -89,14 +90,15 @@ struct gc_atom_impl no_refcount_policy>::value, "gc_atom_impl can only be used when there is no refcount!"); - gc_atom_impl(const gc_atom_impl&) = delete; - gc_atom_impl(gc_atom_impl&&) = delete; + gc_atom_impl(const gc_atom_impl&) = delete; + gc_atom_impl(gc_atom_impl&&) = delete; gc_atom_impl& operator=(const gc_atom_impl&) = delete; - gc_atom_impl& operator=(gc_atom_impl&&) = delete; + gc_atom_impl& operator=(gc_atom_impl&&) = delete; gc_atom_impl(box_type b) : impl_{b.impl_} - {} + { + } box_type load() const { return {impl_.load()}; } @@ -138,8 +140,8 @@ struct gc_atom_impl * ``std::atomic`` interface closely, since it attempts to be a higher level * construction, most similar to Clojure's ``(atom)``. It is remarkable in * particular that, since ``box`` underlying object is immutable, using - * ``atom`` is fully thread-safe in ways that ``std::atomic_shared_ptr`` is - * not. This is so because dereferencing the underlying pointer in a + * ``atom`` is fully thread-safe in ways that ``std::atomic_shared_ptr`` + * is not. This is so because dereferencing the underlying pointer in a * ``std::atomic_share_ptr`` may require further synchronization, in * particular when invoking non-const methods. * @@ -153,17 +155,18 @@ class atom using value_type = T; using memory_policy = MemoryPolicy; - atom(const atom&) = delete; - atom(atom&&) = delete; + atom(const atom&) = delete; + atom(atom&&) = delete; void operator=(const atom&) = delete; - void operator=(atom&&) = delete; + void operator=(atom&&) = delete; /*! * Constructs an atom holding a value `b`; */ atom(box_type v = {}) : impl_{std::move(v)} - {} + { + } /*! * Sets a new value in the atom. diff --git a/immer/box.hpp b/immer/box.hpp index ddf03636..92e02909 100644 --- a/immer/box.hpp +++ b/immer/box.hpp @@ -45,7 +45,8 @@ class box template holder(Args&&... args) : value{std::forward(args)...} - {} + { + } }; using heap = typename MemoryPolicy::heap::type; @@ -54,7 +55,8 @@ class box box(holder* impl) : impl_{impl} - {} + { + } public: const holder* impl() const { return impl_; }; @@ -67,7 +69,8 @@ class box */ box() : impl_{detail::make()} - {} + { + } /*! * Constructs a box holding `T{arg}` @@ -77,7 +80,8 @@ class box !std::is_same>::value>> box(Arg&& arg) : impl_{detail::make(std::forward(arg))} - {} + { + } /*! * Constructs a box holding `T{arg1, arg2, args...}` @@ -87,7 +91,8 @@ class box : impl_{detail::make(std::forward(arg1), std::forward(arg2), std::forward(args)...)} - {} + { + } friend void swap(box& a, box& b) { diff --git a/immer/config.hpp b/immer/config.hpp index af34e5f1..9942c134 100644 --- a/immer/config.hpp +++ b/immer/config.hpp @@ -121,6 +121,10 @@ #endif #endif +#ifndef IMMER_THROW_ON_INVALID_STATE +#define IMMER_THROW_ON_INVALID_STATE 0 +#endif + namespace immer { const auto default_bits = 5; diff --git a/immer/detail/arrays/no_capacity.hpp b/immer/detail/arrays/no_capacity.hpp index e3fbbb7a..eaf3cf3d 100644 --- a/immer/detail/arrays/no_capacity.hpp +++ b/immer/detail/arrays/no_capacity.hpp @@ -42,7 +42,8 @@ struct no_capacity no_capacity(node_t* p, size_t s) : ptr{p} , size{s} - {} + { + } no_capacity(const no_capacity& other) : no_capacity{other.ptr, other.size} diff --git a/immer/detail/arrays/with_capacity.hpp b/immer/detail/arrays/with_capacity.hpp index 755d88ea..57546545 100644 --- a/immer/detail/arrays/with_capacity.hpp +++ b/immer/detail/arrays/with_capacity.hpp @@ -42,7 +42,8 @@ struct with_capacity : ptr{p} , size{s} , capacity{c} - {} + { + } with_capacity(const with_capacity& other) : with_capacity{other.ptr, other.size, other.capacity} diff --git a/immer/detail/hamts/bits.hpp b/immer/detail/hamts/bits.hpp index 0b4b9237..7ab413fb 100644 --- a/immer/detail/hamts/bits.hpp +++ b/immer/detail/hamts/bits.hpp @@ -60,7 +60,7 @@ template constexpr T max_depth = (sizeof(hash_t) * 8u + B - 1u) / B; template -constexpr T max_shift = max_depth* B; +constexpr T max_shift = max_depth * B; #define IMMER_HAS_BUILTIN_POPCOUNT 1 @@ -165,7 +165,8 @@ class set_bits_range public: set_bits_range(bitmap_t bitmap) : bitmap(bitmap) - {} + { + } set_bits_iterator begin() const { return set_bits_iterator(bitmap); } set_bits_iterator end() const { return set_bits_iterator(0); } }; diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 0e04d57e..da2cd6e7 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -139,9 +139,10 @@ struct champ static node_t* empty() { - static const auto empty_ = []{ + static const auto empty_ = [] { constexpr auto size = node_t::sizeof_inner_n(0); - static std::aligned_storage_t storage; + static std::aligned_storage_t + storage; return node_t::make_inner_n_into(&storage, size, 0u); }(); return empty_->inc(); @@ -150,7 +151,8 @@ struct champ champ(node_t* r, size_t sz = 0) noexcept : root{r} , size{sz} - {} + { + } champ(const champ& other) noexcept : champ{other.root, other.size} @@ -1304,13 +1306,15 @@ struct champ , data{a.data} , owned{false} , mutated{false} - {} + { + } sub_result_mut(sub_result a, bool m) : kind{a.kind} , data{a.data} , owned{false} , mutated{m} - {} + { + } sub_result_mut() : kind{kind_t::nothing} , mutated{false} {}; diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index 18019849..11398dd8 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -23,7 +23,11 @@ namespace hamts { // For C++14 support. // Calling the destructor inline breaks MSVC in some obscure // corner cases. -template constexpr void destroy_at(T* p) { p->~T(); } +template +constexpr void destroy_at(T* p) +{ + p->~T(); +} template void each_sub(Visitor v, Args&&... args) @@ -1574,7 +1577,8 @@ struct concat_merger , n_{n} , result_{ shift + B, node_t::make_inner_r_n(std::min(n_, branches)), 0} - {} + { + } node_t* to_ = {}; count_t to_offset_ = {}; diff --git a/immer/detail/rbts/position.hpp b/immer/detail/rbts/position.hpp index cbdd1c2f..305955d5 100644 --- a/immer/detail/rbts/position.hpp +++ b/immer/detail/rbts/position.hpp @@ -44,7 +44,8 @@ struct empty_regular_pos template void each(Visitor, Args&&...) - {} + { + } template bool each_pred(Visitor, Args&&...) { @@ -168,7 +169,8 @@ struct leaf_descent_pos template decltype(auto) descend(Args&&...) - {} + { + } template decltype(auto) visit(Visitor v, Args&&... args) @@ -698,16 +700,20 @@ struct null_sub_pos template void each_sub(Visitor, Args&&...) - {} + { + } template void each_right_sub(Visitor, Args&&...) - {} + { + } template void each_left_sub(Visitor, Args&&...) - {} + { + } template void visit(Visitor, Args&&...) - {} + { + } }; template @@ -735,10 +741,12 @@ struct singleton_regular_sub_pos template void each_left_sub(Visitor v, Args&&... args) - {} + { + } template void each(Visitor v, Args&&... args) - {} + { + } template decltype(auto) last_sub(Visitor v, Args&&... args) diff --git a/immer/detail/rbts/rbtree_iterator.hpp b/immer/detail/rbts/rbtree_iterator.hpp index 90613b10..2f0a5a5d 100644 --- a/immer/detail/rbts/rbtree_iterator.hpp +++ b/immer/detail/rbts/rbtree_iterator.hpp @@ -36,14 +36,16 @@ struct rbtree_iterator , i_{0} , base_{~size_t{}} , curr_{nullptr} - {} + { + } rbtree_iterator(const tree_t& v, end_t) : v_{&v} , i_{v.size} , base_{~size_t{}} , curr_{nullptr} - {} + { + } const tree_t& impl() const { return *v_; } size_t index() const { return i_; } diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 843921ba..f8ee3f84 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -25,6 +25,16 @@ namespace immer { namespace detail { namespace rbts { +#if IMMER_THROW_ON_INVALID_STATE +struct invalid_tree : std::exception +{}; +#define IMMER_INVALID_STATE_ASSERT(expr) \ + if (!(expr)) \ + IMMER_THROW(invalid_tree{}) +#else +#define IMMER_INVALID_STATE_ASSERT(expr) assert(expr) +#endif + template struct rrbtree_iterator; @@ -49,9 +59,10 @@ struct rrbtree static node_t* empty_root() { - static const auto empty_ = []{ + static const auto empty_ = [] { constexpr auto size = node_t::sizeof_inner_n(0); - static std::aligned_storage_t storage; + static std::aligned_storage_t + storage; return node_t::make_inner_n_into(&storage, size, 0u); }(); return empty_->inc(); @@ -59,9 +70,10 @@ struct rrbtree static node_t* empty_tail() { - static const auto empty_ = []{ + static const auto empty_ = [] { constexpr auto size = node_t::sizeof_leaf_n(0); - static std::aligned_storage_t storage; + static std::aligned_storage_t + storage; return node_t::make_leaf_n_into(&storage, size, 0u); }(); return empty_->inc(); @@ -107,13 +119,32 @@ struct rrbtree assert(check_tree()); } - rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) noexcept + rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) +#if IMMER_THROW_ON_INVALID_STATE +#else + noexcept +#endif : size{sz} , shift{sh} , root{r} , tail{t} { +#if IMMER_THROW_ON_INVALID_STATE + // assert only happens in the Debug build, but when + // IMMER_THROW_ON_INVALID_STATE is activated, we want to just check_tree + // even in Release. + try { + check_tree(); + } catch (...) { + // This not fully constructed rrbtree owns the nodes already, have + // to dec them. + if (r && t) + dec(); + throw; + } +#else assert(check_tree()); +#endif } rrbtree(const rrbtree& other) noexcept @@ -900,9 +931,9 @@ struct rrbtree r.shift, r.tail_offset()); l = {l.size + r.size, - concated.shift(), - concated.node(), - r.tail->inc()}; + concated.shift(), + concated.node(), + r.tail->inc()}; } } } @@ -961,9 +992,9 @@ struct rrbtree add_tail, branches); r = {l.size + r.size, - get<0>(new_root), - get<1>(new_root), - new_tail}; + get<0>(new_root), + get<1>(new_root), + new_tail}; return; } IMMER_CATCH (...) { @@ -1041,9 +1072,9 @@ struct rrbtree r.shift, r.tail_offset()); r = {l.size + r.size, - concated.shift(), - concated.node(), - r.tail->inc()}; + concated.shift(), + concated.node(), + r.tail->inc()}; return; } } @@ -1177,9 +1208,9 @@ struct rrbtree r.shift, r.tail_offset()); l = {l.size + r.size, - concated.shift(), - concated.node(), - r.tail->inc()}; + concated.shift(), + concated.node(), + r.tail->inc()}; return; } } @@ -1238,9 +1269,9 @@ struct rrbtree add_tail, branches); r = {l.size + r.size, - get<0>(new_root), - get<1>(new_root), - new_tail}; + get<0>(new_root), + get<1>(new_root), + new_tail}; return; } IMMER_CATCH (...) { @@ -1321,9 +1352,9 @@ struct rrbtree r.shift, r.tail_offset()); r = {l.size + r.size, - concated.shift(), - concated.node(), - r.tail->inc()}; + concated.shift(), + concated.node(), + r.tail->inc()}; } } } @@ -1339,13 +1370,13 @@ struct rrbtree bool check_tree() const { - assert(shift <= sizeof(size_t) * 8 - BL); - assert(shift >= BL); - assert(tail_offset() <= size); - assert(tail_size() <= branches); + IMMER_INVALID_STATE_ASSERT(shift <= sizeof(size_t) * 8 - BL); + IMMER_INVALID_STATE_ASSERT(shift >= BL); + IMMER_INVALID_STATE_ASSERT(tail_offset() <= size); + IMMER_INVALID_STATE_ASSERT(tail_size() <= branches); #if IMMER_DEBUG_DEEP_CHECK - assert(check_root()); - assert(check_tail()); + IMMER_INVALID_STATE_ASSERT(check_root()); + IMMER_INVALID_STATE_ASSERT(check_tail()); #endif return true; } diff --git a/immer/detail/rbts/rrbtree_iterator.hpp b/immer/detail/rbts/rrbtree_iterator.hpp index af967774..ba8d29ee 100644 --- a/immer/detail/rbts/rrbtree_iterator.hpp +++ b/immer/detail/rbts/rrbtree_iterator.hpp @@ -39,13 +39,15 @@ struct rrbtree_iterator : v_{&v} , i_{0} , curr_{nullptr, ~size_t{}, ~size_t{}} - {} + { + } rrbtree_iterator(const tree_t& v, end_t) : v_{&v} , i_{v.size} , curr_{nullptr, ~size_t{}, ~size_t{}} - {} + { + } private: friend iterator_core_access; diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index f1af9d03..21822bad 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -36,15 +36,16 @@ const T& as_const(T& x) return x; } -template +template struct aligned_storage { - struct type { + struct type + { alignas(Align) unsigned char data[Size]; }; }; -template +template using aligned_storage_t = typename aligned_storage::type; template @@ -113,9 +114,9 @@ auto destroy_n(Iter first, Size n) noexcept template constexpr bool can_trivially_copy = std::is_same::value_type, - typename std::iterator_traits::value_type>::value&& - std::is_trivially_copyable< - typename std::iterator_traits::value_type>::value; + typename std::iterator_traits::value_type>::value && + std::is_trivially_copyable< + typename std::iterator_traits::value_type>::value; template auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) noexcept @@ -244,7 +245,8 @@ auto static_if(F&& f) -> std::enable_if_t } template auto static_if(F&& f) -> std::enable_if_t -{} +{ +} template auto static_if(F1&& f1, F2&& f2) -> std::enable_if_t diff --git a/immer/experimental/dvektor.hpp b/immer/experimental/dvektor.hpp index a4227d64..ec98ee45 100644 --- a/immer/experimental/dvektor.hpp +++ b/immer/experimental/dvektor.hpp @@ -64,7 +64,8 @@ class dvektor private: dvektor(impl_t impl) : impl_(std::move(impl)) - {} + { + } impl_t impl_ = detail::dvektor::empty; }; diff --git a/immer/extra/cereal/immer_array.hpp b/immer/extra/cereal/immer_array.hpp new file mode 100644 index 00000000..786da0c3 --- /dev/null +++ b/immer/extra/cereal/immer_array.hpp @@ -0,0 +1,30 @@ +#pragma once + +#include +#include + +namespace cereal { + +template +void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::array& m) +{ + size_type size; + ar(make_size_tag(size)); + + for (auto i = size_type{}; i < size; ++i) { + T x; + ar(x); + m = std::move(m).push_back(std::move(x)); + } +} + +template +void CEREAL_SAVE_FUNCTION_NAME(Archive& ar, + const immer::array& m) +{ + ar(make_size_tag(static_cast(m.size()))); + for (auto&& v : m) + ar(v); +} + +} // namespace cereal diff --git a/immer/extra/cereal/immer_map.hpp b/immer/extra/cereal/immer_map.hpp new file mode 100644 index 00000000..88cbc3fa --- /dev/null +++ b/immer/extra/cereal/immer_map.hpp @@ -0,0 +1,100 @@ +#pragma once + +#include + +#include + +namespace cereal { + +template +auto get_auto_id(const Element& x) -> decltype(x.id) +{ + return x.id; +} + +template +struct has_auto_id : std::false_type +{}; + +template +struct has_auto_id()))>, T> + : std::true_type +{}; + +template +std::enable_if_t::value> +CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::map& m) +{ + size_type size; + ar(make_size_tag(size)); + + for (auto i = size_type{}; i < size; ++i) { + T x; + ar(x); + auto id = get_auto_id(x); + m = std::move(m).set(std::move(id), std::move(x)); + } + if (size != m.size()) + throw std::runtime_error{"duplicate ids?"}; +} + +template +std::enable_if_t::value> +CEREAL_SAVE_FUNCTION_NAME(Archive& ar, const immer::map& m) +{ + ar(make_size_tag(static_cast(m.size()))); + for (auto&& v : m) + ar(v.second); +} + +template +std::enable_if_t::value> +CEREAL_LOAD_FUNCTION_NAME(Archive& ar, immer::map& m) +{ + size_type size; + ar(make_size_tag(size)); + + for (auto i = size_type{}; i < size; ++i) { + K k; + T x; + ar(make_map_item(k, x)); + m = std::move(m).set(std::move(k), std::move(x)); + } + if (size != m.size()) + throw std::runtime_error{"duplicate ids?"}; +} + +template +std::enable_if_t::value> +CEREAL_SAVE_FUNCTION_NAME(Archive& ar, const immer::map& m) +{ + ar(make_size_tag(static_cast(m.size()))); + for (auto&& v : m) + ar(make_map_item(v.first, v.second)); +} + +} // namespace cereal diff --git a/immer/extra/cereal/immer_vector.hpp b/immer/extra/cereal/immer_vector.hpp new file mode 100644 index 00000000..93bef240 --- /dev/null +++ b/immer/extra/cereal/immer_vector.hpp @@ -0,0 +1,70 @@ +#pragma once + +#include +#include + +namespace cereal { + +template +void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, + immer::vector& m) +{ + size_type size; + ar(make_size_tag(size)); + + for (auto i = size_type{}; i < size; ++i) { + T x; + ar(x); + m = std::move(m).push_back(std::move(x)); + } +} + +template +void CEREAL_SAVE_FUNCTION_NAME(Archive& ar, + const immer::vector& m) +{ + ar(make_size_tag(static_cast(m.size()))); + for (auto&& v : m) + ar(v); +} + +template +void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, + immer::flex_vector& m) +{ + size_type size; + ar(make_size_tag(size)); + + for (auto i = size_type{}; i < size; ++i) { + T x; + ar(x); + m = std::move(m).push_back(std::move(x)); + } +} + +template +void CEREAL_SAVE_FUNCTION_NAME( + Archive& ar, const immer::flex_vector& m) +{ + ar(make_size_tag(static_cast(m.size()))); + for (auto&& v : m) + ar(v); +} + +} // namespace cereal diff --git a/immer/extra/io.hpp b/immer/extra/io.hpp new file mode 100644 index 00000000..1d6f084f --- /dev/null +++ b/immer/extra/io.hpp @@ -0,0 +1,37 @@ +#pragma once + +#include + +namespace immer::util { + +/*! + * Restores the iostream state. + * + * This is particularly handy for utilities that can be used to peek or query + * properties of a document read from a stream, while leaving the stream in the + * original state. + */ +struct istream_snapshot +{ + std::reference_wrapper stream; + std::istream::pos_type pos = stream.get().tellg(); + std::istream::iostate state = stream.get().rdstate(); + std::istream::iostate exceptions = stream.get().exceptions(); + + istream_snapshot(std::istream& is) + : stream{is} + { + } + + // It is not copyable nor movable + istream_snapshot(istream_snapshot&&) = delete; + + ~istream_snapshot() + { + stream.get().exceptions(exceptions); + stream.get().clear(state); + stream.get().seekg(pos); + } +}; + +} // namespace immer::util diff --git a/immer/extra/persist/cereal/archives.hpp b/immer/extra/persist/cereal/archives.hpp new file mode 100644 index 00000000..6d311738 --- /dev/null +++ b/immer/extra/persist/cereal/archives.hpp @@ -0,0 +1,420 @@ +#pragma once + +#include + +#include + +/** + * Special types of archives, working with JSON, that support providing extra + * context (Pools) to serialize immer data structures. + */ + +namespace immer::persist { + +namespace detail { + +/** + * @brief This struct has an interface of an output archive but does + * nothing when requested to serialize data. It is used when we only care about + * the pools and have no need to serialize the actual document. + * + * @ingroup persist-impl + */ +struct blackhole_output_archive +{ + void setNextName(const char* name) {} + + template + friend void CEREAL_SAVE_FUNCTION_NAME(blackhole_output_archive& ar, + cereal::NameValuePair const& t) + { + } + + friend void CEREAL_SAVE_FUNCTION_NAME(blackhole_output_archive& ar, + std::nullptr_t const& t) + { + } + + template ::value> = + cereal::traits::sfinae> + friend void CEREAL_SAVE_FUNCTION_NAME(blackhole_output_archive& ar, + T const& t) + { + } + + template + friend void CEREAL_SAVE_FUNCTION_NAME( + blackhole_output_archive& ar, + std::basic_string const& str) + { + } + + template + friend void CEREAL_SAVE_FUNCTION_NAME(blackhole_output_archive& ar, + cereal::SizeTag const& v) + { + } +}; + +// blackhole_output_archive doesn't care about names +struct empty_name_fn +{ + void operator()(const auto& container) const {} +}; + +template +constexpr bool is_pool_empty() +{ + using Result = + decltype(boost::hana::is_empty(boost::hana::keys(Pools{}.storage()))); + return Result::value; +} + +} // namespace detail + +/** + * @brief A wrapper type that wraps a `cereal::OutputArchive` (for example, + * `JSONOutputArchive`), provides access to the `Pools` object stored inside, + * and serializes the `pools` object alongside the user document. + * + * Normally, the function `cereal_save_with_pools` should be used instead of + * using this wrapper directly. + * + * @see cereal_save_with_pools + * + * @ingroup persist-api + */ +template +class output_pools_cereal_archive_wrapper + : public cereal::OutputArchive< + output_pools_cereal_archive_wrapper> + , public cereal::traits::TextArchive +{ +public: + using pool_name_fn = PoolNameFn; + + template + explicit output_pools_cereal_archive_wrapper(Args&&... args) + requires std::is_same_v + : cereal::OutputArchive{this} + , previous{std::forward(args)...} + { + } + + template + output_pools_cereal_archive_wrapper(Pools pools_, Args&&... args) + requires std::is_same_v + : cereal::OutputArchive{this} + , previous{std::forward(args)...} + , pools{std::move(pools_)} + { + } + + template + output_pools_cereal_archive_wrapper(Pools pools_, + WrapFn wrap_, + Args&&... args) + : cereal::OutputArchive{this} + , wrap{std::move(wrap_)} + , previous{std::forward(args)...} + , pools{std::move(pools_)} + { + } + + ~output_pools_cereal_archive_wrapper() { finalize(); } + + Pools& get_output_pools() & { return pools; } + Pools&& get_output_pools() && { return std::move(pools); } + + void finalize() + { + if constexpr (detail::is_pool_empty()) { + return; + } + + if (finalized) { + return; + } + finalized = true; + + save_pools_impl(); + + auto& self = *this; + self(CEREAL_NVP(pools)); + } + + template + friend void prologue(output_pools_cereal_archive_wrapper& ar, T&& v) + { + using cereal::prologue; + prologue(ar.previous, std::forward(v)); + } + + template + friend void epilogue(output_pools_cereal_archive_wrapper& ar, T&& v) + { + using cereal::epilogue; + epilogue(ar.previous, std::forward(v)); + } + + template + friend void + CEREAL_SAVE_FUNCTION_NAME(output_pools_cereal_archive_wrapper& ar, + cereal::NameValuePair const& t) + { + ar.previous.setNextName(t.name); + ar(ar.wrap(t.value)); + } + + friend void + CEREAL_SAVE_FUNCTION_NAME(output_pools_cereal_archive_wrapper& ar, + std::nullptr_t const& t) + { + using cereal::CEREAL_SAVE_FUNCTION_NAME; + CEREAL_SAVE_FUNCTION_NAME(ar.previous, t); + } + + template ::value> = + cereal::traits::sfinae> + friend void + CEREAL_SAVE_FUNCTION_NAME(output_pools_cereal_archive_wrapper& ar, + T const& t) + { + using cereal::CEREAL_SAVE_FUNCTION_NAME; + CEREAL_SAVE_FUNCTION_NAME(ar.previous, t); + } + + template + friend void CEREAL_SAVE_FUNCTION_NAME( + output_pools_cereal_archive_wrapper& ar, + std::basic_string const& str) + { + using cereal::CEREAL_SAVE_FUNCTION_NAME; + CEREAL_SAVE_FUNCTION_NAME(ar.previous, str); + } + + template + friend void + CEREAL_SAVE_FUNCTION_NAME(output_pools_cereal_archive_wrapper& ar, + cereal::SizeTag const& v) + { + using cereal::CEREAL_SAVE_FUNCTION_NAME; + CEREAL_SAVE_FUNCTION_NAME(ar.previous, v); + } + +private: + template + friend class output_pools_cereal_archive_wrapper; + + // Recursively serializes the pools but not calling finalize + void save_pools_impl() + { + const auto save_pool = [wrap = wrap](auto pools) { + auto ar = output_pools_cereal_archive_wrapper< + detail::blackhole_output_archive, + Pools, + decltype(wrap), + detail::empty_name_fn>{pools, wrap}; + // Do not try to serialize pools again inside of this temporary + // archive + ar.finalized = true; + ar(pools); + return std::move(ar).get_output_pools(); + }; + + auto prev = pools; + while (true) { + // Keep saving pools until everything is saved. + pools = save_pool(std::move(pools)); + if (prev == pools) { + break; + } + prev = pools; + } + } + +private: + WrapFn wrap; + Previous previous; + Pools pools; + bool finalized{false}; +}; + +template +struct has_has_name_t : std::false_type +{}; + +template +struct has_has_name_t().hasName(""))>> + : std::true_type +{}; + +/** + * @brief A wrapper type that wraps a `cereal::InputArchive` (for example, + * `JSONInputArchive`) and provides access to the `pools` object. + * + * Normally, the function `cereal_load_with_pools` should be used instead of + * using this wrapper directly. + * + * @see cereal_load_with_pools + * + * @ingroup persist-api + */ +template +class input_pools_cereal_archive_wrapper + : public cereal::InputArchive< + input_pools_cereal_archive_wrapper> + , public cereal::traits::TextArchive +{ +public: + using pool_name_fn = PoolNameFn; + + template + input_pools_cereal_archive_wrapper(Pools pools_, Args&&... args) + requires std::is_same_v + : cereal::InputArchive{this} + , previous{std::forward(args)...} + , pools{std::move(pools_)} + { + } + + template + input_pools_cereal_archive_wrapper(Pools pools_, + WrapFn wrap_, + Args&&... args) + : cereal::InputArchive{this} + , wrap{std::move(wrap_)} + , previous{std::forward(args)...} + , pools{std::move(pools_)} + { + } + + template + auto& get_loader() + { + auto& loader = loaders[boost::hana::type_c]; + if (!loader) { + const auto& type_pool = pools.template get_pool(); + loader.emplace(type_pool.pool, type_pool.transform); + } + return *loader; + } + + /** + * This is needed for optional_nvp in the custom version of cereal. + */ + std::enable_if_t::value, bool> + hasName(const char* name) + { + return previous.hasName(name); + } + + template + friend void prologue(input_pools_cereal_archive_wrapper& ar, T&& v) + { + using cereal::prologue; + prologue(ar.previous, std::forward(v)); + } + + template + friend void epilogue(input_pools_cereal_archive_wrapper& ar, T&& v) + { + using cereal::epilogue; + epilogue(ar.previous, std::forward(v)); + } + + template + friend void + CEREAL_LOAD_FUNCTION_NAME(input_pools_cereal_archive_wrapper& ar, + cereal::NameValuePair& t) + { + ar.previous.setNextName(t.name); + auto&& wrapped = ar.wrap(t.value); + ar(wrapped); + } + + friend void + CEREAL_LOAD_FUNCTION_NAME(input_pools_cereal_archive_wrapper& ar, + std::nullptr_t& t) + { + using cereal::CEREAL_LOAD_FUNCTION_NAME; + CEREAL_LOAD_FUNCTION_NAME(ar.previous, t); + } + + template ::value> = + cereal::traits::sfinae> + friend void + CEREAL_LOAD_FUNCTION_NAME(input_pools_cereal_archive_wrapper& ar, T& t) + { + using cereal::CEREAL_LOAD_FUNCTION_NAME; + CEREAL_LOAD_FUNCTION_NAME(ar.previous, t); + } + + template + friend void + CEREAL_LOAD_FUNCTION_NAME(input_pools_cereal_archive_wrapper& ar, + std::basic_string& str) + { + using cereal::CEREAL_LOAD_FUNCTION_NAME; + CEREAL_LOAD_FUNCTION_NAME(ar.previous, str); + } + + template + friend void + CEREAL_LOAD_FUNCTION_NAME(input_pools_cereal_archive_wrapper& ar, + cereal::SizeTag& st) + { + using cereal::CEREAL_LOAD_FUNCTION_NAME; + CEREAL_LOAD_FUNCTION_NAME(ar.previous, st); + } + + bool ignore_pool_exceptions = false; + +private: + WrapFn wrap; + Previous previous; + Pools pools; + + using Loaders = decltype(Pools::generate_loaders()); + Loaders loaders; +}; + +} // namespace immer::persist + +// tie input and output archives together +namespace cereal { +namespace traits { +namespace detail { +template +struct get_output_from_input< + immer::persist:: + input_pools_cereal_archive_wrapper> +{ + using type = + immer::persist::output_pools_cereal_archive_wrapper; +}; +template +struct get_input_from_output< + immer::persist::output_pools_cereal_archive_wrapper> +{ + using type = immer::persist:: + input_pools_cereal_archive_wrapper; +}; +} // namespace detail +} // namespace traits +} // namespace cereal diff --git a/immer/extra/persist/cereal/load.hpp b/immer/extra/persist/cereal/load.hpp new file mode 100644 index 00000000..7ff99835 --- /dev/null +++ b/immer/extra/persist/cereal/load.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include +#include +#include +#include + +namespace immer::persist { + +/** + * @brief Load a value of the given type `T` from the provided stream using + * pools. By default, `cereal::JSONInputArchive` is used but a different + * `cereal` input archive can be provided. + * + * @ingroup persist-api + */ +template Policy = default_policy, + class... Args> +T cereal_load_with_pools(std::istream& is, + const Policy& policy = Policy{}, + Args&&... args) +{ + using TypesSet = + decltype(boost::hana::to_set(policy.get_pool_types(std::declval()))); + using Pools = decltype(detail::generate_input_pools(TypesSet{})); + + auto get_pool_name_fn = [](const auto& value) { + return Policy{}.get_pool_name(value); + }; + using PoolNameFn = decltype(get_pool_name_fn); + + const auto wrap = + detail::wrap_known_types(TypesSet{}, detail::wrap_for_loading); + auto pools = load_pools(is, wrap); + + auto ar = immer::persist::input_pools_cereal_archive_wrapper{ + std::move(pools), wrap, is, std::forward(args)...}; + auto value0 = T{}; + policy.load(ar, value0); + return value0; +} + +/** + * @brief Load a value of the given type `T` from the provided string using + * pools. By default, `cereal::JSONInputArchive` is used but a different + * `cereal` input archive can be provided. + * + * @ingroup persist-api + */ +template Policy = default_policy> +T cereal_load_with_pools(const std::string& input, + const Policy& policy = Policy{}) +{ + auto is = std::istringstream{input}; + return cereal_load_with_pools(is, policy); +} + +} // namespace immer::persist diff --git a/immer/extra/persist/cereal/policy.hpp b/immer/extra/persist/cereal/policy.hpp new file mode 100644 index 00000000..2913b69f --- /dev/null +++ b/immer/extra/persist/cereal/policy.hpp @@ -0,0 +1,198 @@ +#pragma once + +#include +#include + +namespace immer::persist { + +/** + * @defgroup persist-policy + */ + +/** + * @brief Policy is a type that describes certain aspects of serialization for + * `immer-persist`. + * - How to call into the `cereal` archive to save and load the + * user-provided value. Can be used to serealize the value inline (without the + * `value0` node) by taking a dependency on cereal-inline, for + * example. + * - Types of `immer` containers that will be serialized using pools. One + * pool contains nodes of only one `immer` container type. + * - Names for each per-type pool. + * + * @ingroup persist-policy + */ +template +concept Policy = + requires(Value value, T policy) { policy.get_pool_types(value); }; + +template +auto get_pools_names(const T&) +{ + return boost::hana::make_map(); +} + +template +auto get_pools_types(const T&) +{ + return boost::hana::make_set(); +} + +/** + * @brief This struct provides functions that `immer-persist` uses to serialize + * the user-provided value using `cereal`. In this case, we use `cereal`'s + * default name, `value0`. It's used in all policies provided by + * `immer-persist`. + * + * Other possible way would be to use a third-party library to serialize the + * value inline (without the `value0` node) by taking a dependency on + * cereal-inline, + * for example. + * + * @ingroup persist-policy + */ +struct value0_serialize_t +{ + template + void save(Archive& ar, const T& value0) const + { + ar(CEREAL_NVP(value0)); + } + + template + void load(Archive& ar, T& value0) const + { + ar(CEREAL_NVP(value0)); + } +}; + +template +struct via_get_pools_names_policy_t : value0_serialize_t +{ + auto get_pool_types(const T& value) const + { + return boost::hana::keys(get_pools_names(value)); + } + + using Map = decltype(get_pools_names(std::declval())); + + template + auto get_pool_name(const Container& container) const + { + return detail::name_from_map_fn{}(container); + } +}; + +/** + * @brief Create an `immer-persist` policy that uses the user-provided + * `get_pools_names` function (located in the same namespace as the value user + * serializes) to determine: + * - the types of `immer` containers that should be serialized in a pool + * - the names of those pools in JSON (and possibly other formats). + * + * The `get_pools_names` function is expected to return a `boost::hana::map` + * where key is a container type and value is the name for this container's pool + * as a `BOOST_HANA_STRING`. + * + * @param value Value that is going to be serialized, only type of the value + * matters. + * + * @ingroup persist-policy + */ +auto via_get_pools_names_policy(const auto& value) +{ + return via_get_pools_names_policy_t>{}; +} + +/** + * @brief This struct is used in some policies to provide names to each pool + * by using a demangled name of the `immer` container corresponding to the pool. + * + * @ingroup persist-policy + */ +struct demangled_names_t +{ + template + auto get_pool_name(const T& value) const + { + return detail::get_demangled_name(value); + } +}; + +/** + * @brief An `immer-persist` policy that uses the user-provided + * `get_pools_types` function to determine the types of `immer` containers that + * should be serialized in a pool. + * + * The `get_pools_types` function is expected to return a `boost::hana::set` of + * types of the desired containers. + * + * The names for the pools are determined via `demangled_names_t`. + * + * @ingroup persist-policy + */ +struct via_get_pools_types_policy + : demangled_names_t + , value0_serialize_t +{ + template + auto get_pool_types(const T& value) const + { + return get_pools_types(value); + } +}; + +/** + * @brief An `immer-persist` policy that recursively finds all `immer` + * containers in a serialized value. The value must be a `boost::hana::Struct`. + * + * The names for the pools are determined via `demangled_names_t`. + * + * @ingroup persist-policy + */ +struct hana_struct_auto_policy : demangled_names_t +{ + template + auto get_pool_types(const T& value) const + { + return detail::get_pools_for_hana_type(); + } +}; + +template +struct hana_struct_auto_member_name_policy_t : value0_serialize_t +{ + auto get_pool_types(const T& value) const + { + return detail::get_pools_for_hana_type(); + } + + using map_t = decltype(detail::get_named_pools_for_hana_type()); + + template + auto get_pool_name(const Container& container) const + { + return detail::name_from_map_fn{}(container); + } +}; + +/** + * @brief Create an `immer-persist` policy that recursively finds all `immer` + * containers in a serialized value and uses member names to name the pools. + * The value must be a `boost::hana::Struct`. + * + * @param value Value that is going to be serialized, only type of the value + * matters. + * + * @ingroup persist-policy + */ +auto hana_struct_auto_member_name_policy(const auto& value) +{ + return hana_struct_auto_member_name_policy_t< + std::decay_t>{}; +} + +using default_policy = via_get_pools_types_policy; + +} // namespace immer::persist diff --git a/immer/extra/persist/cereal/save.hpp b/immer/extra/persist/cereal/save.hpp new file mode 100644 index 00000000..e37945ff --- /dev/null +++ b/immer/extra/persist/cereal/save.hpp @@ -0,0 +1,71 @@ +#pragma once + +#include +#include +#include + +namespace immer::persist { + +/** + * @defgroup persist-api + */ + +/** + * @brief Serialize the provided value with pools using the provided policy + * outputting into the provided stream. By default, `cereal::JSONOutputArchive` + * is used but a different `cereal` output archive can be provided. + * + * @see Policy + * @ingroup persist-api + */ +template Policy = default_policy, + class... Args> +void cereal_save_with_pools(std::ostream& os, + const T& value0, + const Policy& policy = Policy{}, + Args&&... args) +{ + const auto types = boost::hana::to_set(policy.get_pool_types(value0)); + auto pools = detail::generate_output_pools(types); + const auto wrap = detail::wrap_known_types(types, detail::wrap_for_saving); + using Pools = std::decay_t; + auto get_pool_name_fn = [](const auto& value) { + return Policy{}.get_pool_name(value); + }; + auto ar = immer::persist::output_pools_cereal_archive_wrapper< + Archive, + Pools, + decltype(wrap), + decltype(get_pool_name_fn)>{ + pools, wrap, os, std::forward(args)...}; + policy.save(ar, value0); + // Calling finalize explicitly, as it might throw on saving the pools, + // for example if pool names are not unique. + ar.finalize(); +} + +/** + * @brief Serialize the provided value with pools using the provided policy. By + * default, `cereal::JSONOutputArchive` is used but a different `cereal` output + * archive can be provided. + * + * @return std::string The resulting JSON. + * @ingroup persist-api + */ +template Policy = default_policy, + class... Args> +std::string cereal_save_with_pools(const T& value0, + const Policy& policy = Policy{}, + Args&&... args) +{ + auto os = std::ostringstream{}; + cereal_save_with_pools( + os, value0, policy, std::forward(args)...); + return os.str(); +} + +} // namespace immer::persist diff --git a/immer/extra/persist/detail/alias.hpp b/immer/extra/persist/detail/alias.hpp new file mode 100644 index 00000000..38b251f0 --- /dev/null +++ b/immer/extra/persist/detail/alias.hpp @@ -0,0 +1,65 @@ +#pragma once + +#include + +namespace immer::persist::detail { + +template +struct type_alias +{ + using rep_t = T; + + T value{}; + + type_alias() = default; + + explicit type_alias(T value_) + : value{std::move(value_)} + { + } + + friend bool operator==(const type_alias& left, + const type_alias& right) = default; + + /** + * This works only starting with fmt v10. + */ + friend auto format_as(const type_alias& v) { return v.value; } + + template + auto save_minimal(const Archive&) const + { + return value; + } + + template + auto load_minimal(const Archive&, const T& value_) + { + value = value_; + } +}; + +} // namespace immer::persist::detail + +/** + * Have to use this for fmt v9. + */ +template +struct fmt::formatter> : formatter +{ + template + auto format(const immer::persist::detail::type_alias& value, + FormatContext& ctx) const + { + return formatter::format(value.value, ctx); + } +}; + +template +struct std::hash> +{ + auto operator()(const immer::persist::detail::type_alias& x) const + { + return hash{}(x.value); + } +}; diff --git a/immer/extra/persist/detail/array/pool.hpp b/immer/extra/persist/detail/array/pool.hpp new file mode 100644 index 00000000..5cee43a3 --- /dev/null +++ b/immer/extra/persist/detail/array/pool.hpp @@ -0,0 +1,197 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace immer::persist::array { + +template +struct output_pool +{ + immer::map> arrays; + + immer::map ids; + + friend bool operator==(const output_pool& left, const output_pool& right) + { + return left.arrays == right.arrays; + } + + template + void save(Archive& ar) const + { + ar(cereal::make_size_tag( + static_cast(arrays.size()))); + for_each_ordered([&](const auto& array) { ar(array); }); + } + + template + void for_each_ordered(F&& f) const + { + for (auto index = std::size_t{}; index < arrays.size(); ++index) { + auto* p = arrays.find(container_id{index}); + assert(p); + f(*p); + } + } +}; + +template +std::pair, container_id> +get_id(output_pool pool, + const immer::array& array) +{ + auto* ptr_void = static_cast(array.identity()); + if (auto* maybe_id = pool.ids.find(ptr_void)) { + return {std::move(pool), *maybe_id}; + } + + const auto id = container_id{pool.ids.size()}; + pool.ids = std::move(pool.ids).set(ptr_void, id); + return {std::move(pool), id}; +} + +template +std::pair, container_id> +add_to_pool(immer::array array, + output_pool pool) +{ + auto id = container_id{}; + std::tie(pool, id) = get_id(std::move(pool), array); + + if (pool.arrays.count(id)) { + // Already been saved + return {std::move(pool), id}; + } + + pool.arrays = std::move(pool.arrays).set(id, std::move(array)); + return {std::move(pool), id}; +} + +template +struct input_pool +{ + immer::vector> arrays; + + friend bool operator==(const input_pool& left, + const input_pool& right) = default; + + template + void load(Archive& ar) + { + cereal::load(ar, arrays); + } + + void merge_previous(const input_pool& other) + { + if (arrays.size() != other.arrays.size()) { + return; + } + + auto result = immer::vector>{}; + for (auto i = std::size_t{}; i < arrays.size(); ++i) { + if (arrays[i] == other.arrays[i]) { + // While it's the same, prefer the old one + result = std::move(result).push_back(other.arrays[i]); + } else { + result = std::move(result).push_back(arrays[i]); + } + } + arrays = std::move(result); + } +}; + +template +input_pool +to_input_pool(const output_pool& pool) +{ + auto result = input_pool{}; + pool.for_each_ordered([&](const auto& array) { + result.arrays = std::move(result.arrays).push_back(array); + }); + return result; +} + +template , + typename TransformF = boost::hana::id_t> +class loader +{ +public: + explicit loader(Pool pool) + requires std::is_same_v + : pool_{std::move(pool)} + { + } + + explicit loader(Pool pool, TransformF transform) + : pool_{std::move(pool)} + , transform_{std::move(transform)} + { + } + + immer::array load(container_id id) + { + if (id.value >= pool_.arrays.size()) { + throw invalid_container_id{id}; + } + if constexpr (std::is_same_v) { + return pool_.arrays[id.value]; + } else { + if (auto* b = arrays_.find(id)) { + return *b; + } + const auto& old_array = pool_.arrays[id.value]; + auto new_array = immer::array{}; + for (const auto& item : old_array) { + new_array = std::move(new_array).push_back(transform_(item)); + } + arrays_ = std::move(arrays_).set(id, new_array); + return new_array; + } + } + +private: + const Pool pool_; + const TransformF transform_; + immer::map> arrays_; +}; + +template +loader make_loader_for(const immer::array&, + input_pool pool) +{ + return loader{std::move(pool)}; +} + +} // namespace immer::persist::array + +namespace immer::persist::detail { + +template +struct container_traits> +{ + using output_pool_t = array::output_pool; + using input_pool_t = array::input_pool; + + template + using loader_t = array::loader; + + using container_id = immer::persist::container_id; + + template + static auto transform(F&& func) + { + using U = std::decay_t()))>; + return immer::array{}; + } +}; + +} // namespace immer::persist::detail diff --git a/immer/extra/persist/detail/box/pool.hpp b/immer/extra/persist/detail/box/pool.hpp new file mode 100644 index 00000000..83a90a2f --- /dev/null +++ b/immer/extra/persist/detail/box/pool.hpp @@ -0,0 +1,211 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include + +#include + +namespace immer::persist::box { + +template +struct output_pool +{ + immer::map> boxes; + + immer::map ids; + + friend bool operator==(const output_pool& left, const output_pool& right) + { + return left.boxes == right.boxes; + } + + template + void save(Archive& ar) const + { + ar(cereal::make_size_tag(static_cast(boxes.size()))); + for_each_ordered([&](const auto& box) { ar(box.get()); }); + } + + template + void for_each_ordered(F&& f) const + { + for (auto index = std::size_t{}; index < boxes.size(); ++index) { + auto* p = boxes.find(container_id{index}); + assert(p); + f(*p); + } + } +}; + +template +std::pair, container_id> +get_box_id(output_pool pool, + const immer::box& box) +{ + auto* ptr_void = static_cast(box.impl()); + if (auto* maybe_id = pool.ids.find(ptr_void)) { + return {std::move(pool), *maybe_id}; + } + + const auto id = container_id{pool.ids.size()}; + pool.ids = std::move(pool.ids).set(ptr_void, id); + return {std::move(pool), id}; +} + +template +struct input_pool +{ + immer::vector> boxes; + + friend bool operator==(const input_pool& left, + const input_pool& right) = default; + + template + void load(Archive& ar) + { + cereal::size_type size; + ar(cereal::make_size_tag(size)); + + for (auto i = cereal::size_type{}; i < size; ++i) { + T x; + ar(x); + boxes = std::move(boxes).push_back(std::move(x)); + } + } + + /** + * merge_previous is a function where some processing might be done while + * reloading the pools. In the case of boxes, while we reload the pool + * continuously (it might take several passes to fully load), we carry over + * the older boxes (when they're equal) in order to achieve better + * structural sharing. + */ + void merge_previous(const input_pool& other) + { + if (boxes.size() != other.boxes.size()) { + return; + } + + auto result = immer::vector>{}; + for (auto i = std::size_t{}; i < boxes.size(); ++i) { + if (boxes[i] == other.boxes[i]) { + // While it's the same, prefer the old one + result = std::move(result).push_back(other.boxes[i]); + } else { + result = std::move(result).push_back(boxes[i]); + } + } + boxes = std::move(result); + } +}; + +template +input_pool +to_input_pool(const output_pool& pool) +{ + auto result = input_pool{}; + pool.for_each_ordered([&](const auto& box) { + result.boxes = std::move(result.boxes).push_back(box); + }); + return result; +} + +template +std::pair, container_id> +add_to_pool(immer::box box, output_pool pool) +{ + auto id = container_id{}; + std::tie(pool, id) = get_box_id(std::move(pool), box); + + if (pool.boxes.count(id)) { + // Already been saved + return {std::move(pool), id}; + } + + pool.boxes = std::move(pool.boxes).set(id, std::move(box)); + return {std::move(pool), id}; +} + +template , + typename TransformF = boost::hana::id_t> +class loader +{ +public: + explicit loader(Pool pool) + requires std::is_same_v + : pool_{std::move(pool)} + { + } + + explicit loader(Pool pool, TransformF transform) + : pool_{std::move(pool)} + , transform_{std::move(transform)} + { + } + + immer::box load(container_id id) + { + if (id.value >= pool_.boxes.size()) { + throw invalid_container_id{id}; + } + if constexpr (std::is_same_v) { + return pool_.boxes[id.value]; + } else { + if (auto* b = boxes_.find(id)) { + return *b; + } + const auto& old_box = pool_.boxes[id.value]; + auto new_box = + immer::box{transform_(old_box.get())}; + boxes_ = std::move(boxes_).set(id, new_box); + return new_box; + } + } + +private: + const Pool pool_; + const TransformF transform_; + immer::map> boxes_; +}; + +template +loader make_loader_for(const immer::box&, + input_pool pool) +{ + return loader{std::move(pool)}; +} + +} // namespace immer::persist::box + +namespace immer::persist::detail { + +template +struct container_traits> +{ + using output_pool_t = box::output_pool; + using input_pool_t = box::input_pool; + + template + using loader_t = box::loader; + + using container_id = immer::persist::container_id; + + template + static auto transform(F&& func) + { + using U = std::decay_t()))>; + return immer::box{}; + } +}; + +} // namespace immer::persist::detail diff --git a/immer/extra/persist/detail/cereal/compact_map.hpp b/immer/extra/persist/detail/cereal/compact_map.hpp new file mode 100644 index 00000000..6a63d4a6 --- /dev/null +++ b/immer/extra/persist/detail/cereal/compact_map.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include + +namespace immer::persist::detail { + +template +struct compact_pair +{ + K key; + T value; +}; + +template +struct compact_map +{ + immer::map& map; +}; + +template +compact_map make_compact_map(immer::map& map) +{ + return compact_map{map}; +} + +} // namespace immer::persist::detail + +namespace cereal { + +template +void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, + immer::persist::detail::compact_pair& m) +{ + size_type size; + ar(make_size_tag(size)); + if (size != 2) { + throw Exception{"A pair must be a list of 2 elements"}; + } + + ar(m.key); + ar(m.value); +} + +template +void CEREAL_SAVE_FUNCTION_NAME( + Archive& ar, const immer::persist::detail::compact_pair& m) +{ + ar(make_size_tag(static_cast(2))); + ar(m.key); + ar(m.value); +} + +template +void CEREAL_LOAD_FUNCTION_NAME(Archive& ar, + immer::persist::detail::compact_map& m) +{ + size_type size; + ar(make_size_tag(size)); + + for (auto i = size_type{}; i < size; ++i) { + auto pair = immer::persist::detail::compact_pair{}; + ar(pair); + + m.map = + std::move(m.map).set(std::move(pair.key), std::move(pair.value)); + } + if (size != m.map.size()) + throw Exception{"duplicate ids?"}; +} + +template +void CEREAL_SAVE_FUNCTION_NAME( + Archive& ar, const immer::persist::detail::compact_map& m) +{ + ar(make_size_tag(static_cast(m.map.size()))); + for (auto&& v : m.map) { + ar(immer::persist::detail::compact_pair{v.first, v.second}); + } +} + +} // namespace cereal diff --git a/immer/extra/persist/detail/cereal/input_archive_util.hpp b/immer/extra/persist/detail/cereal/input_archive_util.hpp new file mode 100644 index 00000000..af2decbd --- /dev/null +++ b/immer/extra/persist/detail/cereal/input_archive_util.hpp @@ -0,0 +1,57 @@ +#pragma once + +#include +#include + +namespace immer::persist::detail { + +template +auto load_pools(std::istream& is, const auto& wrap) +{ + const auto reload_pool = + [wrap](std::istream& is, Pools pools, bool ignore_pool_exceptions) { + auto restore = immer::util::istream_snapshot{is}; + const auto original_pools = pools; + auto ar = input_pools_cereal_archive_wrapper{ + std::move(pools), wrap, is}; + ar.ignore_pool_exceptions = ignore_pool_exceptions; + /** + * NOTE: Critical to clear the pools before loading into it + * again. I hit a bug when pools contained a vector and every + * load would append to it, instead of replacing the contents. + */ + pools = {}; + ar(CEREAL_NVP(pools)); + pools.merge_previous(original_pools); + return pools; + }; + + auto pools = Pools{}; + if constexpr (detail::is_pool_empty()) { + return pools; + } + + auto prev = pools; + while (true) { + // Keep reloading until everything is loaded. + // Reloading of the pool might trigger validation of some containers + // (hash-based, for example) because the elements actually come from + // other pools that are not yet loaded. + constexpr bool ignore_pool_exceptions = true; + pools = reload_pool(is, std::move(pools), ignore_pool_exceptions); + if (prev == pools) { + // Looks like we're done, reload one more time but do not ignore the + // exceptions, for the final validation. + pools = reload_pool(is, std::move(pools), false); + break; + } + prev = pools; + } + + return pools; +} + +} // namespace immer::persist::detail diff --git a/immer/extra/persist/detail/cereal/persistable.hpp b/immer/extra/persist/detail/cereal/persistable.hpp new file mode 100644 index 00000000..d0946110 --- /dev/null +++ b/immer/extra/persist/detail/cereal/persistable.hpp @@ -0,0 +1,159 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include + +namespace immer::persist::detail { + +/** + * @brief A wrapper that allows the library to serialize the wrapped container + * using a corresponding pool. + * + * When saving, it saves the container into the pool by performing the following + * steps: + * - request the output pool corresponding to the type of the ``Container`` + * - save the container to the pool by calling ``add_to_pool`` + * - acquire the ID from the pool for the just saved container + * - save the ID into the output archive. + * + * Similarly, the steps for loading are: + * - container ID is loaded from the input archive by ``cereal`` + * - request the input pool corresponding to the type of the ``Container`` + * - load the container with the required ID from the input pool. + * + * Consequently, instead of the container's actual data, ``persistable`` would + * serialize only the ID of the wrapped container. + * + * @tparam Container ``immer`` container that should be serialized using a pool. + * + * @ingroup persist-impl + */ +template +struct persistable +{ + Container container; + + persistable() = default; + + persistable(std::initializer_list values) + : container{std::move(values)} + { + } + + persistable(Container container_) + : container{std::move(container_)} + { + } + + friend bool operator==(const persistable& left, + const persistable& right) = default; +}; + +template +auto save_minimal( + const output_pools_cereal_archive_wrapper, + WrapFn, + PoolNameFn>& ar, + const persistable& value) +{ + auto& pool = + const_cast< + output_pools_cereal_archive_wrapper, + WrapFn, + PoolNameFn>&>(ar) + .get_output_pools() + .template get_output_pool(); + auto [pool2, id] = add_to_pool(value.container, std::move(pool)); + pool = std::move(pool2); + return id.value; +} + +// This function must exist because cereal does some checks and it's not +// possible to have only load_minimal for a type without having save_minimal. +template +auto save_minimal( + const output_pools_cereal_archive_wrapper, + WrapFn, + PoolNameFn>& ar, + const persistable& value) -> + typename container_traits::container_id::rep_t +{ + throw std::logic_error{"Should never be called"}; +} + +template +void load_minimal( + const input_pools_cereal_archive_wrapper& ar, + persistable& value, + const typename container_traits::container_id::rep_t& id) +{ + auto& loader = + const_cast&>(ar) + .template get_loader(); + + // Have to be specific because for vectors container_id is different from + // node_id, but for hash-based containers, a container is identified just by + // its root node. + using container_id_ = typename container_traits::container_id; + + try { + value.container = loader.load(container_id_{id}); + } catch (const pool_exception& ex) { + if (!ar.ignore_pool_exceptions) { + throw ::cereal::Exception{fmt::format( + "Failed to load a container ID {} from the pool of {}: {}", + id, + boost::core::demangle(typeid(Container).name()), + ex.what())}; + } + } +} + +// This function must exist because cereal does some checks and it's not +// possible to have only load_minimal for a type without having save_minimal. +template +auto save_minimal(const Archive& ar, const persistable& value) -> + typename container_traits::container_id::rep_t +{ + throw std::logic_error{ + "Should never be called. save_minimal(const Archive& ar..."}; +} + +template +void load_minimal( + const Archive& ar, + persistable& value, + const typename container_traits::container_id::rep_t& id) +{ + // This one is actually called while loading with not-yet-fully-loaded + // pool. +} + +} // namespace immer::persist::detail diff --git a/immer/extra/persist/detail/cereal/pools.hpp b/immer/extra/persist/detail/cereal/pools.hpp new file mode 100644 index 00000000..48de700b --- /dev/null +++ b/immer/extra/persist/detail/cereal/pools.hpp @@ -0,0 +1,469 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +#include + +namespace immer::persist::detail { + +namespace hana = boost::hana; + +/** + * Unimplemented class to generate a compile-time error and show what the type T + * is. + */ +template +class error_no_pool_for_the_given_type_check_get_pools_types_function; + +template +class error_missing_pool_for_type; + +template +struct storage_holder +{ + T value; + + auto& operator()() { return value; } + const auto& operator()() const { return value; } +}; + +template +auto make_storage_holder(T&& value) +{ + return storage_holder>{std::forward(value)}; +} + +template +struct shared_storage_holder +{ + std::shared_ptr ptr; + + auto& operator()() { return *ptr; } + const auto& operator()() const { return *ptr; } +}; + +template +auto make_shared_storage_holder(std::shared_ptr ptr) +{ + return shared_storage_holder{std::move(ptr)}; +} + +/** + * Pools and functions to serialize types that contain persistable data + * structures. + */ +template +struct output_pools +{ + Storage storage_; + + // To align the interface with input_pools + Storage& storage() { return storage_; } + const Storage& storage() const { return storage_; } + + template + void save(Archive& ar) const + { + using pool_name_fn = typename Archive::pool_name_fn; + using keys_t = decltype(hana::keys(storage())); + auto used_names = std::unordered_set{}; + hana::for_each(keys_t{}, [&](auto key) { + using Container = typename decltype(key)::type; + using NameType = decltype(pool_name_fn{}(Container{})); + if constexpr (std::is_void_v) { + ar(storage()[key]); + } else { + const auto& name = pool_name_fn{}(Container{}); + const auto [iterator, inserted] = used_names.insert(name); + if (!inserted) { + throw duplicate_name_pool_detected{name}; + } + ar(cereal::make_nvp(name, storage()[key])); + } + }); + } + + template + auto& get_output_pool() + { + using Contains = decltype(hana::contains(storage(), hana::type_c)); + if constexpr (!Contains::value) { + auto err = + error_no_pool_for_the_given_type_check_get_pools_types_function< + T>{}; + } + return storage()[hana::type_c]; + } + + template + const auto& get_output_pool() const + { + using Contains = decltype(hana::contains(storage(), hana::type_c)); + if constexpr (!Contains::value) { + auto err = + error_no_pool_for_the_given_type_check_get_pools_types_function< + T>{}; + } + return storage()[hana::type_c]; + } + + friend bool operator==(const output_pools& left, const output_pools& right) + { + return left.storage() == right.storage(); + } +}; + +template +struct no_loader +{}; + +template +struct with_loader +{ + std::optional loader; + + auto& get_loader_from_per_type_pool() + { + if (!loader) { + auto& self = static_cast(*this); + loader.emplace(self.pool, self.transform); + } + return *loader; + } +}; + +/** + * A pool for one container type. + * Normally, the pool does not contain a loader, which is located inside the + * input_pools_cereal_archive_wrapper. + * + * But in case of transformations, there is no + * input_pools_cereal_archive_wrapper involved and it becomes convenient to have + * the corresponding loader stored here, too, via with_loader. + */ +template ::input_pool_t, + class TransformF = boost::hana::id_t, + class OldContainerType = boost::hana::id_t, + template class LoaderMixin = no_loader> +struct input_pool + : LoaderMixin, + typename container_traits< + Container>::template loader_t> +{ + using container_t = Container; + using old_container_t = OldContainerType; + + Pool pool = {}; + TransformF transform; + + input_pool() = default; + + explicit input_pool(Pool pool_) + requires std::is_same_v + : pool{std::move(pool_)} + { + } + + explicit input_pool(Pool pool_, TransformF transform_) + : pool{std::move(pool_)} + , transform{std::move(transform_)} + { + } + + template + auto with_transform(Func&& func) const + { + using value_type = typename Container::value_type; + using new_value_type = + std::decay_t()))>; + using NewContainer = + std::decay_t::transform( + func))>; + using TransF = std::function; + // the transform function must be filled in later + return input_pool{ + pool, TransF{}}; + } + + friend bool operator==(const input_pool& left, const input_pool& right) + { + return left.pool == right.pool; + } + + void merge_previous(const input_pool& original) + { + pool.merge_previous(original.pool); + } + + static auto generate_loader() + { + return std::optional::template loader_t>{}; + } +}; + +/** + * Transforms a given function into another function that: + * - If the given function is a function of one argument, nothing changes. + * - Otherwise, passes the given argument as the second argument for the + * function. + * + * In other words, takes a function of maybe two arguments and returns a + * function of just one argument. + */ +constexpr auto inject_argument = [](auto arg, auto func) { + return [arg = std::move(arg), func = std::move(func)](auto&& old) { + const auto is_valid = hana::is_valid(func, old); + if constexpr (std::decay_t::value) { + return func(old); + } else { + return func(old, arg); + } + }; +}; + +template +class input_pools +{ +private: + StorageF storage_; + +public: + input_pools() = default; + + explicit input_pools(StorageF storage) + : storage_{std::move(storage)} + { + } + + auto& storage() { return storage_(); } + const auto& storage() const { return storage_(); } + + template + const auto& get_pool() + { + using Contains = + decltype(hana::contains(storage(), hana::type_c)); + if constexpr (!Contains::value) { + auto err = error_missing_pool_for_type{}; + } + return storage()[hana::type_c]; + } + + template + auto& get_loader_by_old_container() + { + constexpr auto find_key = [](const auto& storage) { + return hana::find_if(hana::keys(storage), [&](auto key) { + using type1 = typename std::decay_t< + decltype(storage[key])>::old_container_t; + return hana::type_c == hana::type_c; + }); + }; + using Key = decltype(find_key(storage())); + using IsJust = decltype(hana::is_just(Key{})); + if constexpr (!IsJust::value) { + auto err = error_missing_pool_for_type{}; + } + return storage()[Key{}.value()].get_loader_from_per_type_pool(); + } + + template + void load(Archive& ar) + { + using pool_name_fn = typename Archive::pool_name_fn; + using keys_t = decltype(hana::keys(storage())); + hana::for_each(keys_t{}, [&](auto key) { + using Container = typename decltype(key)::type; + const auto& name = pool_name_fn{}(Container{}); + ar(cereal::make_nvp(name, storage()[key].pool)); + }); + } + + friend bool operator==(const input_pools& left, const input_pools& right) + { + return left.storage() == right.storage(); + } + + /** + * ConversionMap is a map where keys are types of the original container + * (hana::type_c>) and the values are converting + * functions that are used to convert the pools. + * + * The main feature is that the converting function can also take the second + * argument, a function get_loader, which can be called with a container + * type (`get_loader(hana::type_c>)`) and will + * return a reference to a loader that can be used to load other containers + * that have already been converted. + * + * @see test/extra/persist/test_conversion.cpp + */ + template + auto transform_recursive(const ConversionMap& conversion_map, + const GetIdF& get_id) const + { + // This lambda is only used to determine types and should never be + // called. + constexpr auto fake_convert_container = [](auto new_type, + const auto& old_container) { + using NewContainerType = typename decltype(new_type)::type; + throw std::runtime_error{"This should never be called"}; + return NewContainerType{}; + }; + + // Return a pair where second is an optional. Optional is already + // populated if no transformation is required. + const auto transform_pair_initial = [fake_convert_container, + &conversion_map, + this](const auto& pair) { + using Contains = + decltype(hana::contains(conversion_map, hana::first(pair))); + if constexpr (Contains::value) { + // Look up the conversion function by the type from the + // original pool. + const auto& func = inject_argument( + fake_convert_container, conversion_map[hana::first(pair)]); + auto type_load = + storage()[hana::first(pair)].with_transform(func); + using NewContainer = typename decltype(type_load)::container_t; + return hana::make_pair(hana::type_c, + std::move(type_load)); + } else { + // If the conversion map doesn't mention the current type, + // we leave it as is. + return pair; + } + }; + + // I can't think of a better way yet to tie all the loaders/transforming + // functions together. + auto shared_storage = [&] { + auto new_storage = hana::fold_left( + storage(), + hana::make_map(), + [transform_pair_initial](auto map, auto pair) { + return hana::insert(map, transform_pair_initial(pair)); + }); + using NewStorage = decltype(new_storage); + return std::make_shared(new_storage); + }(); + + using NewStorage = std::decay_t; + + const auto convert_container = [get_id](auto get_data) { + return [get_id, get_data](auto new_type, + const auto& old_container) { + const auto id = get_id(old_container); + using Contains = decltype(hana::contains(get_data(), new_type)); + if constexpr (!Contains::value) { + auto err = error_missing_pool_for_type< + typename decltype(new_type)::type>{}; + } + auto& loader = + get_data()[new_type].get_loader_from_per_type_pool(); + return loader.load(id); + }; + }; + + // Important not to create a recursive reference to itself inside of the + // shared_storage. + const auto weak = std::weak_ptr{shared_storage}; + const auto get_data_weak = [weak]() -> auto& { + auto p = weak.lock(); + if (!p) { + throw std::logic_error{"weak ptr has expired"}; + } + return *p; + }; + + // Fill-in the transforming functions into shared_storage. + hana::for_each(hana::keys(*shared_storage), [&](auto key) { + using TypeLoad = std::decay_t; + const auto old_key = + hana::type_c; + using needs_conversion_t = + decltype(hana::contains(conversion_map, old_key)); + if constexpr (needs_conversion_t::value) { + const auto& old_func = conversion_map[old_key]; + (*shared_storage)[key].transform = + inject_argument(convert_container(get_data_weak), old_func); + } + }); + + auto holder = make_shared_storage_holder(std::move(shared_storage)); + return input_pools{std::move(holder)}; + } + + void merge_previous(const input_pools& original) + { + auto& s = storage(); + const auto& original_s = original.storage(); + hana::for_each(hana::keys(s), [&](auto key) { + s[key].merge_previous(original_s[key]); + }); + } + + static auto generate_loaders() + { + using Storage = std::decay_t()())>; + using Types = decltype(hana::keys(std::declval())); + auto storage = + hana::fold_left(Types{}, hana::make_map(), [](auto map, auto type) { + using TypePool = + std::decay_t()[type])>; + return hana::insert( + map, hana::make_pair(type, TypePool::generate_loader())); + }); + return storage; + } +}; + +inline auto generate_output_pools(auto types) +{ + auto storage = + hana::fold_left(types, hana::make_map(), [](auto map, auto type) { + using Type = typename decltype(type)::type; + return hana::insert( + map, + hana::make_pair( + type, typename container_traits::output_pool_t{})); + }); + + using Storage = decltype(storage); + return output_pools{storage}; +} + +inline auto generate_input_pools(auto types) +{ + auto storage = + hana::fold_left(types, hana::make_map(), [](auto map, auto type) { + using Type = typename decltype(type)::type; + return hana::insert(map, hana::make_pair(type, input_pool{})); + }); + + auto storage_f = detail::make_storage_holder(std::move(storage)); + return input_pools{std::move(storage_f)}; +} + +template +inline auto to_input_pools(const output_pools& output_pool) +{ + auto pool = generate_input_pools(boost::hana::keys(output_pool.storage())); + boost::hana::for_each(boost::hana::keys(pool.storage()), [&](auto key) { + pool.storage()[key].pool = to_input_pool(output_pool.storage()[key]); + }); + return pool; +} + +} // namespace immer::persist::detail diff --git a/immer/extra/persist/detail/cereal/wrap.hpp b/immer/extra/persist/detail/cereal/wrap.hpp new file mode 100644 index 00000000..3b5d4b07 --- /dev/null +++ b/immer/extra/persist/detail/cereal/wrap.hpp @@ -0,0 +1,135 @@ +#pragma once + +#include +#include + +// Bring in all known pools to be able to wrap all immer types +#include +#include +#include +#include + +namespace immer::persist::detail { + +/** + * This wrapper is used to load a given container via persistable. + */ +template +struct persistable_loader_wrapper +{ + Container& value; + + template + typename container_traits::container_id::rep_t + save_minimal(const Archive&) const + { + throw std::logic_error{ + "Should never be called. persistable_loader_wrapper::save_minimal"}; + } + + template + void load_minimal( + const Archive& ar, + const typename container_traits::container_id::rep_t& + container_id) + { + persistable arch; + immer::persist::detail::load_minimal(ar, arch, container_id); + value = std::move(arch).container; + } +}; + +constexpr auto is_persistable = boost::hana::is_valid( + [](auto&& obj) -> + typename container_traits>::output_pool_t {}); + +/** + * Make a function that operates conditionally on its single argument, based on + * the given predicate. If the predicate is not satisfied, the function forwards + * its argument unchanged. + */ +constexpr auto make_conditional_func = [](auto pred, auto func) { + return [pred, func](auto&& value) -> decltype(auto) { + return boost::hana::if_(pred(value), func, boost::hana::id)( + std::forward(value)); + }; +}; + +constexpr auto to_persistable = [](const auto& x) { + return persistable>(x); +}; + +constexpr auto to_persistable_loader = [](auto& value) { + using V = std::decay_t; + return persistable_loader_wrapper{value}; +}; + +/** + * This function will wrap a value in persistable if possible or will return a + * reference to its argument. + */ +constexpr auto wrap_for_saving = + make_conditional_func(is_persistable, to_persistable); + +constexpr auto wrap_for_loading = + make_conditional_func(is_persistable, to_persistable_loader); + +/** + * Returns a wrapping function that wraps only known types. + */ +inline auto wrap_known_types(auto types, auto wrap) +{ + static_assert(boost::hana::is_a); + using KnownSet = decltype(types); + const auto is_known = [](const auto& value) { + using result_t = decltype(boost::hana::contains( + KnownSet{}, boost::hana::typeid_(value))); + return result_t{}; + }; + return make_conditional_func(is_known, std::move(wrap)); +} + +static_assert(std::is_same_v< + decltype(wrap_for_saving(std::declval())), + const std::string&>, + "wrap must return a reference when it's not wrapping the type"); +static_assert(std::is_same_v{})), + persistable>>, + "and a value when it's wrapping"); + +/** + * Generate a hana set of types of persistable members for the given type, + * recursively. Example: [type_c>] + */ +template +auto get_pools_for_hana_type() +{ + namespace hana = boost::hana; + auto all_types_set = util::get_inner_types(hana::type_c); + auto persistable = + hana::filter(hana::to_tuple(all_types_set), [](auto type) { + using Type = typename decltype(type)::type; + return detail::is_persistable(Type{}); + }); + return persistable; +} + +/** + * Generate a hana map of persistable members for the given type, recursively. + * Example: + * [(type_c>, "tracks")] + */ +template +auto get_named_pools_for_hana_type() +{ + namespace hana = boost::hana; + auto all_types_map = util::get_inner_types_map(hana::type_c); + auto persistable = + hana::filter(hana::to_tuple(all_types_map), [](auto pair) { + using Type = typename decltype(+hana::first(pair))::type; + return detail::is_persistable(Type{}); + }); + return hana::to_map(persistable); +} + +} // namespace immer::persist::detail diff --git a/immer/extra/persist/detail/champ/champ.hpp b/immer/extra/persist/detail/champ/champ.hpp new file mode 100644 index 00000000..f8757276 --- /dev/null +++ b/immer/extra/persist/detail/champ/champ.hpp @@ -0,0 +1,187 @@ +#pragma once + +#include "input.hpp" +#include "output.hpp" +#include "pool.hpp" + +#include + +namespace immer::persist::champ { + +template +struct node_traits +{ + template + struct impl; + + template + struct impl> + { + using equal_t = Equal; + using hash_t = Hash; + using memory_t = MemoryPolicy; + static constexpr auto bits = B; + }; + + using Hash = typename impl::hash_t; + using Equal = typename impl::equal_t; + using MemoryPolicy = typename impl::memory_t; + static constexpr auto bits = impl::bits; +}; + +class hash_validation_failed_exception : public pool_exception +{ +public: + explicit hash_validation_failed_exception(const std::string& msg) + : pool_exception{"Hash validation failed, likely different hash " + "algos are used for saving and loading, " + + msg} + { + } +}; + +/** + * incompatible_hash_mode: + * When values are transformed in a way that changes how they are hashed, the + * structure of the champ can't be preserved. The only solution is to recreate + * the container from the values that it should contain. + * + * The mode can be enabled by returning incompatible_hash_wrapper from the + * function that handles the target_container_type_request. + */ +template , + typename TransformF = boost::hana::id_t, + bool enable_incompatible_hash_mode = false> +class container_loader +{ + using champ_t = std::decay_t().impl())>; + using node_t = typename champ_t::node_t; + using value_t = typename node_t::value_t; + using traits = node_traits; + using nodes_load = std::decay_t().nodes)>; + + struct project_value_ptr + { + const value_t* operator()(const value_t& v) const noexcept + { + return std::addressof(v); + } + }; + +public: + explicit container_loader(Pool pool) + requires std::is_same_v + : pool_{std::move(pool)} + , nodes_{pool_.nodes} + { + } + + explicit container_loader(Pool pool, TransformF transform) + : pool_{std::move(pool)} + , nodes_{pool_.nodes, std::move(transform)} + { + } + + Container load(node_id root_id) + { + if (root_id.value >= pool_.nodes.size()) { + throw invalid_node_id{root_id}; + } + + auto [root, values] = nodes_.load_inner(root_id); + + if constexpr (enable_incompatible_hash_mode) { + if (auto* p = loaded_.find(root_id)) { + return *p; + } + + auto result = Container{}; + for (const auto& items : values) { + for (const auto& item : items) { + result = std::move(result).insert(item); + } + } + loaded_ = std::move(loaded_).set(root_id, result); + return result; + } + + const auto items_count = [&values = values] { + auto count = std::size_t{}; + for (const auto& items : values) { + count += items.size(); + } + return count; + }(); + + auto impl = champ_t{std::move(root).release(), items_count}; + + // Validate the loaded champ by ensuring that all elements can be + // found. This verifies the hash function is the same as used while + // saving it. + for (const auto& items : values) { + for (const auto& item : items) { + const auto* p = impl.template get< + project_value_ptr, + immer::detail::constantly>(item); + if (!p) { + throw hash_validation_failed_exception{ + "Couldn't find an element"}; + } + if (!(*p == item)) { + throw hash_validation_failed_exception{ + "Found element is not equal to the one we were looking " + "for"}; + } + } + } + + // XXX This ctor is not public in immer. + return impl; + } + +private: + const Pool pool_; + nodes_loader + nodes_; + immer::map loaded_; +}; + +template +container_loader(container_input_pool pool) + -> container_loader; + +template +std::pair, node_id> +add_to_pool(Container container, container_output_pool pool) +{ + const auto& impl = container.impl(); + auto root_id = node_id{}; + std::tie(pool.nodes, root_id) = + get_node_id(std::move(pool.nodes), impl.root); + + if (pool.nodes.inners.count(root_id)) { + // Already been saved + return {std::move(pool), root_id}; + } + + pool.nodes = save_nodes(impl, std::move(pool.nodes)); + assert(pool.nodes.inners.count(root_id)); + + pool.containers = + std::move(pool.containers).push_back(std::move(container)); + + return {std::move(pool), root_id}; +} + +} // namespace immer::persist::champ diff --git a/immer/extra/persist/detail/champ/input.hpp b/immer/extra/persist/detail/champ/input.hpp new file mode 100644 index 00000000..f01e4e71 --- /dev/null +++ b/immer/extra/persist/detail/champ/input.hpp @@ -0,0 +1,250 @@ +#pragma once + +#include +#include +#include + +#include + +#include +#include + +namespace immer::persist::champ { + +class children_count_corrupted_exception : public pool_exception +{ +public: + children_count_corrupted_exception(node_id id, + std::uint64_t nodemap, + std::size_t expected_count, + std::size_t real_count) + : pool_exception{fmt::format( + "Loaded container is corrupted. Inner " + "node ID {} has nodemap {} which means it should have {} " + "children but it has {}", + id, + nodemap, + expected_count, + real_count)} + { + } +}; + +class data_count_corrupted_exception : public pool_exception +{ +public: + data_count_corrupted_exception(node_id id, + std::uint64_t datamap, + std::size_t expected_count, + std::size_t real_count) + : pool_exception{fmt::format( + "Loaded container is corrupted. Inner " + "node ID {} has datamap {} which means it should contain {} " + "values but it has {}", + id, + datamap, + expected_count, + real_count)} + { + } +}; + +template , + typename TransformF = boost::hana::id_t> +class nodes_loader +{ +public: + using champ_t = + immer::detail::hamts::champ; + using node_t = typename champ_t::node_t; + using node_ptr = immer::persist::detail::node_ptr; + + using values_t = immer::flex_vector>; + + explicit nodes_loader(NodesLoad pool) + requires std::is_same_v + : pool_{std::move(pool)} + { + } + + explicit nodes_loader(NodesLoad pool, TransformF transform) + : pool_{std::move(pool)} + , transform_{std::move(transform)} + { + } + + std::pair load_collision(node_id id) + { + if (auto* p = collisions_.find(id)) { + return *p; + } + + if (id.value >= pool_.size()) { + throw invalid_node_id{id}; + } + + const auto& node_info = pool_[id.value]; + const auto values = get_values(node_info.values.data); + + const auto n = values.size(); + auto node = node_ptr{node_t::make_collision_n(n), + [](auto* ptr) { node_t::delete_collision(ptr); }}; + immer::detail::uninitialized_copy( + values.begin(), values.end(), node.get()->collisions()); + auto result = std::make_pair(std::move(node), values_t{values}); + collisions_ = std::move(collisions_).set(id, result); + return result; + } + + std::pair load_inner(node_id id) + { + if (auto* p = inners_.find(id)) { + return *p; + } + + if (id.value >= pool_.size()) { + throw invalid_node_id{id}; + } + + const auto& node_info = pool_[id.value]; + + const auto children_count = node_info.children.size(); + const auto values_count = node_info.values.data.size(); + + // Loading validation + { + const auto expected_count = + immer::detail::hamts::popcount(node_info.nodemap); + if (expected_count != children_count) { + throw children_count_corrupted_exception{ + id, node_info.nodemap, expected_count, children_count}; + } + } + + { + const auto expected_count = + immer::detail::hamts::popcount(node_info.datamap); + if (expected_count != values_count) { + throw data_count_corrupted_exception{ + id, node_info.datamap, expected_count, values_count}; + } + } + + const auto node_values = get_values(node_info.values.data); + + auto values = values_t{}; + + // Load children + const auto children = [&values, &node_info, this] { + auto [children_ptrs, children_values] = + load_children(node_info.children); + + if (!children_values.empty()) { + values = std::move(values) + children_values; + } + + /** + * NOTE: Be careful with release_full and exceptions, nodes will not + * be freed automatically. + */ + auto result = immer::vector>{}; + for (auto& child : children_ptrs) { + result = std::move(result).push_back( + std::move(child).release_full()); + } + return result; + }(); + const auto delete_children = [children]() { + for (const auto& ptr : children) { + ptr.dec(); + } + }; + + auto inner = + node_ptr{node_t::make_inner_n(children_count, values_count), + [delete_children](auto* ptr) { + node_t::delete_inner(ptr); + delete_children(); + }}; + inner.get()->impl.d.data.inner.nodemap = node_info.nodemap; + inner.get()->impl.d.data.inner.datamap = node_info.datamap; + + // Values + if (values_count) { + immer::detail::uninitialized_copy( + node_values.begin(), node_values.end(), inner.get()->values()); + values = std::move(values).push_back(node_values); + } + + // Set children + for (const auto& [index, child_ptr] : + boost::adaptors::index(children)) { + inner.get()->children()[index] = child_ptr.ptr; + } + + inners_ = std::move(inners_).set(id, std::make_pair(inner, values)); + return {std::move(inner), std::move(values)}; + } + + std::pair load_some_node(node_id id) + { + if (id.value >= pool_.size()) { + throw invalid_node_id{id}; + } + + if (pool_[id.value].collisions) { + return load_collision(id); + } else { + return load_inner(id); + } + } + + std::pair, values_t> + load_children(const immer::vector& children_ids) + { + auto children = std::vector{}; + auto values = values_t{}; + for (const auto& child_node_id : children_ids) { + auto [child, child_values] = load_some_node(child_node_id); + if (!child) { + throw pool_exception{ + fmt::format("Failed to load node ID {}", child_node_id)}; + } + + if (!child_values.empty()) { + values = std::move(values) + child_values; + } + + children.push_back(std::move(child)); + } + return {std::move(children), std::move(values)}; + } + +private: + immer::array get_values(const auto& array) const + { + if constexpr (std::is_same_v) { + return array; + } else { + auto transformed_values = std::vector{}; + for (const auto& item : array) { + transformed_values.push_back(transform_(item)); + } + return immer::array{transformed_values.begin(), + transformed_values.end()}; + } + } + +private: + const NodesLoad pool_; + const TransformF transform_; + immer::map> collisions_; + immer::map> inners_; +}; + +} // namespace immer::persist::champ diff --git a/immer/extra/persist/detail/champ/output.hpp b/immer/extra/persist/detail/champ/output.hpp new file mode 100644 index 00000000..b8f8f8ee --- /dev/null +++ b/immer/extra/persist/detail/champ/output.hpp @@ -0,0 +1,99 @@ +#pragma once + +#include + +namespace immer::persist::champ { + +template +struct output_pool_builder +{ + nodes_save pool; + + void visit_inner(const auto* node, auto depth) + { + auto id = get_node_id(node); + if (pool.inners.count(id)) { + return; + } + + auto node_info = inner_node_save{ + .nodemap = node->nodemap(), + .datamap = node->datamap(), + }; + + if (node->datamap()) { + node_info.values = {node->values(), + node->values() + node->data_count()}; + } + if (node->nodemap()) { + auto fst = node->children(); + auto lst = fst + node->children_count(); + for (; fst != lst; ++fst) { + node_info.children = + std::move(node_info.children).push_back(get_node_id(*fst)); + visit(*fst, depth + 1); + } + } + + pool.inners = std::move(pool.inners).set(id, node_info); + } + + void visit_collision(const auto* node) + { + auto id = get_node_id(node); + if (pool.inners.count(id)) { + return; + } + + pool.inners = std::move(pool.inners) + .set(id, + inner_node_save{ + .values = {node->collisions(), + node->collisions() + + node->collision_count()}, + .collisions = true, + }); + } + + void visit(const auto* node, immer::detail::hamts::count_t depth) + { + using immer::detail::hamts::max_depth; + + if (depth < max_depth) { + visit_inner(node, depth); + } else { + visit_collision(node); + } + } + + node_id get_node_id(auto* ptr) + { + auto [pool2, id] = + immer::persist::champ::get_node_id(std::move(pool), ptr); + pool = std::move(pool2); + return id; + } +}; + +template +auto save_nodes( + const immer::detail::hamts::champ& champ, + Pool pool) +{ + using champ_t = std::decay_t; + using node_t = typename champ_t::node_t; + + auto save = output_pool_builder{ + .pool = std::move(pool), + }; + save.visit(champ.root, 0); + + return std::move(save.pool); +} + +} // namespace immer::persist::champ diff --git a/immer/extra/persist/detail/champ/pool.hpp b/immer/extra/persist/detail/champ/pool.hpp new file mode 100644 index 00000000..9a899601 --- /dev/null +++ b/immer/extra/persist/detail/champ/pool.hpp @@ -0,0 +1,188 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include + +#include + +namespace immer::persist::champ { + +template +struct inner_node_save +{ + using bitmap_t = typename immer::detail::hamts::get_bitmap_type::type; + + detail::values_save values; + immer::vector children; + bitmap_t nodemap; + bitmap_t datamap; + bool collisions{false}; + + friend bool operator==(const inner_node_save& left, + const inner_node_save& right) = default; + + template + void save(Archive& ar) const + { + ar(CEREAL_NVP(values), + CEREAL_NVP(children), + cereal::make_nvp("nodemap", boost::endian::native_to_big(nodemap)), + cereal::make_nvp("datamap", boost::endian::native_to_big(datamap)), + CEREAL_NVP(collisions)); + } +}; + +template +struct inner_node_load +{ + using bitmap_t = typename immer::detail::hamts::get_bitmap_type::type; + + detail::values_load values; + immer::vector children; + bitmap_t nodemap; + bitmap_t datamap; + bool collisions{false}; + + friend bool operator==(const inner_node_load& left, + const inner_node_load& right) = default; + + template + void load(Archive& ar) + { + ar(CEREAL_NVP(values), + CEREAL_NVP(children), + CEREAL_NVP(nodemap), + CEREAL_NVP(datamap), + CEREAL_NVP(collisions)); + boost::endian::big_to_native_inplace(nodemap); + boost::endian::big_to_native_inplace(datamap); + } +}; + +template +struct nodes_save +{ + // Saving is simpler with a map + immer::map> inners; + + immer::map node_ptr_to_id; + + friend bool operator==(const nodes_save& left, const nodes_save& right) + { + return left.inners == right.inners; + } +}; + +template +std::pair, node_id> get_node_id( + nodes_save nodes, + const immer::detail::hamts::node* ptr) +{ + auto* ptr_void = static_cast(ptr); + if (auto* maybe_id = nodes.node_ptr_to_id.find(ptr_void)) { + auto id = *maybe_id; + return {std::move(nodes), id}; + } + + const auto id = node_id{nodes.node_ptr_to_id.size()}; + nodes.node_ptr_to_id = std::move(nodes.node_ptr_to_id).set(ptr_void, id); + return {std::move(nodes), id}; +} + +template +using nodes_load = immer::vector>; + +template