diff --git a/CMakeLists.txt b/CMakeLists.txt index 386cc725..39350993 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -36,6 +36,7 @@ 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_ARCHIVE_TESTS "Build experimental archive tests" off) option(immer_BUILD_EXAMPLES "Build examples" ON) option(immer_BUILD_DOCS "Build docs" ON) option(immer_BUILD_EXTRAS "Build extras" ON) diff --git a/extra/fuzzer/CMakeLists.txt b/extra/fuzzer/CMakeLists.txt index 2c160f79..2b81bceb 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} immer-archive 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_ARCHIVE_TESTS) + add_subdirectory(immer-archive) +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/immer-archive/CMakeLists.txt b/extra/fuzzer/immer-archive/CMakeLists.txt new file mode 100644 index 00000000..d3ddbd82 --- /dev/null +++ b/extra/fuzzer/immer-archive/CMakeLists.txt @@ -0,0 +1,19 @@ +find_package(spdlog 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 + spdlog::spdlog) + 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/immer-archive/flex-vector.cpp b/extra/fuzzer/immer-archive/flex-vector.cpp new file mode 100644 index 00000000..5c208dd1 --- /dev/null +++ b/extra/fuzzer/immer-archive/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 ar = immer_archive::rbts::make_save_archive_for(vec); + auto vector_id = immer_archive::container_id{}; + std::tie(ar, vector_id) = immer_archive::rbts::save_to_archive(vec, ar); + + auto loader = + immer_archive::rbts::make_loader_for(vec, fix_leaf_nodes(ar)); + 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); + SPDLOG_DEBUG("op = {}", static_cast(op)); + 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/immer-archive/fuzz-set.cpp b/extra/fuzzer/immer-archive/fuzz-set.cpp new file mode 100644 index 00000000..db2472ca --- /dev/null +++ b/extra/fuzzer/immer-archive/fuzz-set.cpp @@ -0,0 +1,119 @@ +#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 archive = + immer_archive::champ::container_archive_save{}; + for (auto index = std::size_t{}; index < var_count; ++index) { + if (bitset[index]) { + auto set_id = immer_archive::node_id{}; + std::tie(archive, set_id) = + immer_archive::champ::save_to_archive(vars[index], + archive); + ids.emplace_back(index, set_id); + } + } + + auto loader = immer_archive::champ::container_loader{ + to_load_archive(archive)}; + + 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/flake.lock b/flake.lock index 523601b0..68bbc90b 100644 --- a/flake.lock +++ b/flake.lock @@ -1,5 +1,21 @@ { "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" + } + }, "flake-compat": { "flake": false, "locked": { @@ -118,6 +134,7 @@ }, "root": { "inputs": { + "arximboldi-cereal-src": "arximboldi-cereal-src", "flake-compat": "flake-compat", "flake-utils": "flake-utils", "gitignore": "gitignore", diff --git a/flake.nix b/flake.nix index 18247fff..4777ef42 100644 --- a/flake.nix +++ b/flake.nix @@ -21,6 +21,10 @@ nixpkgs.follows = "nixpkgs"; }; }; + arximboldi-cereal-src = { + url = "github:arximboldi/cereal"; + flake = false; + }; }; outputs = { @@ -30,15 +34,26 @@ 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;}; + + immer-archive-inputs = with pkgs; [ + spdlog + arximboldi-cereal + xxHash + nlohmann_json + ]; in { checks = { @@ -58,6 +73,9 @@ ninjaFlags = ["tests"]; checkPhase = '' ctest -D ExperimentalMemCheck + valgrind --quiet --error-exitcode=99 --leak-check=full --errors-for-leak-kinds=all \ + --suppressions=./test/experimental/immer-archive/valgrind.supp \ + ./test/experimental/immer-archive/immer-archive-tests ''; }); }; @@ -70,10 +88,14 @@ }) ]; - packages = [ - # for the llvm-symbolizer binary, that allows to show stacks in ASAN and LeakSanitizer. - pkgs.llvmPackages_latest.bintools-unwrapped - ]; + packages = with pkgs; + [ + # for the llvm-symbolizer binary, that allows to show stacks in ASAN and LeakSanitizer. + llvmPackages_latest.bintools-unwrapped + cmake-format + alejandra + ] + ++ immer-archive-inputs; shellHook = self.checks.${system}.pre-commit-check.shellHook; @@ -112,7 +134,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] ++ immer-archive-inputs; nativeBuildInputs = with pkgs; [cmake ninja]; dontBuild = false; doCheck = true; @@ -124,7 +146,9 @@ cmakeFlags = [ "-DCMAKE_BUILD_TYPE=Debug" "-Dimmer_BUILD_TESTS=ON" + "-Dimmer_BUILD_ARCHIVE_TESTS=ON" "-Dimmer_BUILD_EXAMPLES=OFF" + "-DCXX_STANDARD=20" ]; }); }; 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/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 843921ba..ab02a20a 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; @@ -107,13 +117,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 @@ -1339,13 +1368,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/experimental/immer-archive/alias.hpp b/immer/experimental/immer-archive/alias.hpp new file mode 100644 index 00000000..0c5f8cf3 --- /dev/null +++ b/immer/experimental/immer-archive/alias.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include + +namespace immer_archive { + +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) + { + return left.value == right.value; + } + + /** + * 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_archive + +/** + * Have to use this for fmt v9. + */ +template +struct fmt::formatter> : formatter +{ + template + auto format(const immer_archive::type_alias& value, + FormatContext& ctx) const + { + return formatter::format(value.value, ctx); + } +}; + +template +struct std::hash> +{ + auto operator()(const immer_archive::type_alias& x) const + { + return hash{}(x.value); + } +}; diff --git a/immer/experimental/immer-archive/cereal/immer_map.hpp b/immer/experimental/immer-archive/cereal/immer_map.hpp new file mode 100644 index 00000000..88cbc3fa --- /dev/null +++ b/immer/experimental/immer-archive/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/experimental/immer-archive/cereal/immer_vector.hpp b/immer/experimental/immer-archive/cereal/immer_vector.hpp new file mode 100644 index 00000000..93bef240 --- /dev/null +++ b/immer/experimental/immer-archive/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/experimental/immer-archive/champ/archive.hpp b/immer/experimental/immer-archive/champ/archive.hpp new file mode 100644 index 00000000..6f287689 --- /dev/null +++ b/immer/experimental/immer-archive/champ/archive.hpp @@ -0,0 +1,186 @@ +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include + +#include + +namespace immer_archive { +namespace champ { + +template +struct inner_node_save +{ + using bitmap_t = typename immer::detail::hamts::get_bitmap_type::type; + + values_save values; + immer::vector children; + bitmap_t nodemap; + bitmap_t datamap; + bool collisions{false}; + + 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; + + values_load values; + immer::vector children; + bitmap_t nodemap; + bitmap_t datamap; + bool collisions{false}; + + auto tie() const + { + return std::tie(values, children, nodemap, datamap, collisions); + } + + friend bool operator==(const inner_node_load& left, + const inner_node_load& right) + { + return left.tie() == right.tie(); + } + + 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; +}; + +template +std::pair, node_id> get_node_id( + nodes_save ar, + const immer::detail::hamts::node* ptr) +{ + auto* ptr_void = static_cast(ptr); + if (auto* maybe_id = ar.node_ptr_to_id.find(ptr_void)) { + auto id = *maybe_id; + return {std::move(ar), id}; + } + + const auto id = node_id{ar.node_ptr_to_id.size()}; + ar.node_ptr_to_id = std::move(ar.node_ptr_to_id).set(ptr_void, id); + return {std::move(ar), id}; +} + +template +using nodes_load = immer::vector>; + +template