diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000000..999e2fceb5 --- /dev/null +++ b/.gdbinit @@ -0,0 +1,2 @@ +# Load pretty printers +source tools/gdb_pretty_printers/autoload.py \ No newline at end of file diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 0000000000..9b1a2060df --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,26 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-latest + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'immer' + dry-run: false + language: c++ + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'immer' + fuzz-seconds: 300 + dry-run: false + language: c++ + - name: Upload Crash + uses: actions/upload-artifact@v3 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 830892969a..253215f16d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,27 +7,33 @@ jobs: - uses: actions/checkout@v2 with: fetch-depth: 0 # needed for fetchGit in default.nix - - uses: cachix/install-nix-action@v12 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - - uses: cachix/cachix-action@v8 + - uses: cachix/cachix-action@v12 with: name: arximboldi signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' - run: nix-build + build-spm: + runs-on: macos-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - run: swift build + docs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 with: submodules: true - - uses: cachix/install-nix-action@v12 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - - uses: cachix/cachix-action@v8 + - uses: cachix/cachix-action@v12 with: name: arximboldi signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' @@ -35,12 +41,12 @@ jobs: - run: nix-shell --run "cd build && cmake .." - run: nix-shell --run "cd build && make docs" - uses: shimataro/ssh-key-action@v2 - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' && github.repository_owner == 'arximboldi' with: key: ${{ secrets.SINUSOIDES_SSH_KEY }} known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }} - run: nix-shell --run "cd build && make upload-docs" - if: github.ref == 'refs/heads/master' + if: github.ref == 'refs/heads/master' && github.repository_owner == 'arximboldi' check: strategy: @@ -74,7 +80,7 @@ jobs: - type: Debug toolchain: llvm-8 std: 14 - opts: ['sanitizer'] + opts: ['sanitize'] # benchmarks - type: Release toolchain: gnu-9 @@ -87,11 +93,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: cachix/install-nix-action@v12 + - uses: cachix/install-nix-action@v20 with: nix_path: nixpkgs=channel:nixos-unstable - install_url: "https://releases.nixos.org/nix/nix-2.3.16/install" - - uses: cachix/cachix-action@v8 + - uses: cachix/cachix-action@v12 with: name: arximboldi signingKey: '${{ secrets.CACHIX_SIGNING_KEY }}' @@ -113,11 +118,11 @@ jobs: " - run: nix-shell --argstr toolchain ${{ matrix.toolchain }} --run "cd build && make check -j`nproc`" - run: nix-shell --argstr toolchain ${{ matrix.toolchain }} --run "bash <(curl -s https://codecov.io/bash)" - if: ${{ contains(matrix.opts, 'coverage') }} + if: contains(matrix.opts, 'coverage') - uses: shimataro/ssh-key-action@v2 - if: ${{ contains(matrix.opts, 'benchmark') }} + if: contains(matrix.opts, 'benchmark') && github.repository_owner == 'arximboldi' with: key: ${{ secrets.SINUSOIDES_SSH_KEY }} known_hosts: ${{ secrets.SINUSOIDES_KNOWN_HOSTS }} - run: nix-shell --run "cd build && make upload-benchmark-reports" - if: ${{ contains(matrix.opts, 'benchmark') }} + if: contains(matrix.opts, 'benchmark') && github.repository_owner == 'arximboldi' diff --git a/.gitignore b/.gitignore index 2074141ec2..46c3c7c29f 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,8 @@ __pycache__ tools/clojure/.lein* *.pyc + +/result* + +.build +.swiftpm diff --git a/CMakeLists.txt b/CMakeLists.txt index 2362ecb763..90ec3d9302 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_policy(SET CMP0048 NEW) # enable project VERSION cmake_policy(SET CMP0056 NEW) # honor link flags in try_compile() list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") -project(immer VERSION 0.7.0) +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") @@ -99,6 +99,14 @@ install(TARGETS immer EXPORT ImmerConfig) install(EXPORT ImmerConfig DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/Immer") install(DIRECTORY immer DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") +include(CMakePackageConfigHelpers) +write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/ImmerConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion ) + +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 @@ -131,6 +139,8 @@ endif() if (immer_BUILD_TESTS) enable_testing() + find_package(Catch2 REQUIRED) + add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000000..af8254e8c4 --- /dev/null +++ b/Package.swift @@ -0,0 +1,29 @@ +// swift-tools-version:5.5 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "immer", + products: [ + // Products define the executables and libraries a package produces, and make them visible to other packages. + .library( + name: "immer", + targets: ["immer"]), + ], + dependencies: [ + // Dependencies declare other packages that this package depends on. + // .package(url: /* package url */, from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package. A target can define a module or a test suite. + // Targets can depend on other targets in this package, and on products in packages this package depends on. + .target( + name: "immer", + dependencies: [], + path: ".", + sources: ["spm.cpp"], + publicHeadersPath: ".") + ], + cxxLanguageStandard: .cxx14 +) diff --git a/README.rst b/README.rst index abf4dceae1..834dbe6a30 100644 --- a/README.rst +++ b/README.rst @@ -1,6 +1,6 @@ .. image:: https://github.com/arximboldi/immer/workflows/test/badge.svg :target: https://github.com/arximboldi/immer/actions?query=workflow%3Atest+branch%3Amaster - :alt: Github Actions Badge + :alt: GitHub Actions Badge .. image:: https://codecov.io/gh/arximboldi/immer/branch/master/graph/badge.svg :target: https://codecov.io/gh/arximboldi/immer @@ -13,7 +13,10 @@ .. raw:: html - Logotype + + + Logotype + .. include:introduction/start @@ -74,7 +77,7 @@ Example For a **complete example** check `Ewig, a simple didactic text-editor `_ built with this library. You may also wanna check `Lager, a Redux-like library - `_ for writting interactive + `_ for writing interactive software in C++ using a value-oriented design. diff --git a/benchmark/set/erase.hpp b/benchmark/set/erase.hpp new file mode 100644 index 0000000000..a702fd25f8 --- /dev/null +++ b/benchmark/set/erase.hpp @@ -0,0 +1,85 @@ +// +// 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 +// + +#pragma once + +#include "benchmark/config.hpp" + +#include +#include // Phil Nash +#include +#include +#include +#include + +namespace { + +template +auto benchmark_erase_mut_std() +{ + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g = Generator{}(n); + auto v_ = [&] { + auto v = Set{}; + for (auto i = 0u; i < n; ++i) + v.insert(g[i]); + return v; + }(); + measure(meter, [&] { + auto v = v_; + for (auto i = 0u; i < n; ++i) + v.erase(g[i]); + return v; + }); + }; +} + +template +auto benchmark_erase() +{ + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g = Generator{}(n); + auto v_ = [&] { + auto v = Set{}.transient(); + for (auto i = 0u; i < n; ++i) + v.insert(g[i]); + return v.persistent(); + }(); + measure(meter, [&] { + auto v = v_; + for (auto i = 0u; i < n; ++i) + v = v.erase(g[i]); + return v; + }); + }; +} + +template +auto benchmark_erase_move() +{ + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g = Generator{}(n); + auto v_ = [&] { + auto v = Set{}.transient(); + for (auto i = 0u; i < n; ++i) + v.insert(g[i]); + return v.persistent(); + }(); + measure(meter, [&] { + auto v = v_; + for (auto i = 0u; i < n; ++i) + v = std::move(v).erase(g[i]); + return v; + }); + }; +} + +} // namespace diff --git a/benchmark/set/erase.ipp b/benchmark/set/erase.ipp new file mode 100644 index 0000000000..54197744f0 --- /dev/null +++ b/benchmark/set/erase.ipp @@ -0,0 +1,44 @@ +// +// 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 "erase.hpp" + +#ifndef GENERATOR_T +#error "you must define a GENERATOR_T" +#endif + +using generator__ = GENERATOR_T; +using t__ = typename decltype(generator__{}(0))::value_type; + +// clang-format off +NONIUS_BENCHMARK("std::set", benchmark_erase_mut_std>()) +NONIUS_BENCHMARK("std::unordered_set", benchmark_erase_mut_std>()) +NONIUS_BENCHMARK("boost::flat_set", benchmark_erase_mut_std>()) +// Phil Nash's hash_trie seems to not include an erase operation... at least at +// the version that we have included in the nix-shell here... +// NONIUS_BENCHMARK("hamt::hash_trie", benchmark_erase_mut_hash_trie>()) + +NONIUS_BENCHMARK("immer::set/5B", benchmark_erase,std::equal_to,def_memory,5>>()) +NONIUS_BENCHMARK("immer::set/4B", benchmark_erase,std::equal_to,def_memory,4>>()) +#ifndef DISABLE_GC_BENCHMARKS +NONIUS_BENCHMARK("immer::set/GC", benchmark_erase,std::equal_to,gc_memory,5>>()) +#endif +NONIUS_BENCHMARK("immer::set/UN", benchmark_erase,std::equal_to,unsafe_memory,5>>()) + +NONIUS_BENCHMARK("immer::set/move/5B", benchmark_erase_move,std::equal_to,def_memory,5>>()) +NONIUS_BENCHMARK("immer::set/move/4B", benchmark_erase_move,std::equal_to,def_memory,4>>()) +NONIUS_BENCHMARK("immer::set/move/UN", benchmark_erase_move,std::equal_to,unsafe_memory,5>>()) + +NONIUS_BENCHMARK("immer::set/tran/5B", benchmark_erase_mut_std,std::equal_to,def_memory,5>>()) +NONIUS_BENCHMARK("immer::set/tran/4B", benchmark_erase_mut_std,std::equal_to,def_memory,4>>()) +#ifndef DISABLE_GC_BENCHMARKS +NONIUS_BENCHMARK("immer::set/tran/GC", benchmark_erase_mut_std,std::equal_to,gc_memory,5>>()) +#endif +NONIUS_BENCHMARK("immer::set/tran/UN", benchmark_erase_mut_std,std::equal_to,unsafe_memory,5>>()) + +// clang-format on diff --git a/benchmark/set/insert.hpp b/benchmark/set/insert.hpp index be679b4de3..8950abfb82 100644 --- a/benchmark/set/insert.hpp +++ b/benchmark/set/insert.hpp @@ -10,9 +10,10 @@ #include "benchmark/config.hpp" -#include -#include // Phil Nash #include +#include // Phil Nash +#include +#include #include #include @@ -21,8 +22,7 @@ namespace { template auto benchmark_insert_mut_std() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto g = Generator{}(n); @@ -38,8 +38,7 @@ auto benchmark_insert_mut_std() template auto benchmark_insert() { - return [] (nonius::chronometer meter) - { + return [](nonius::chronometer meter) { auto n = meter.param(); auto g = Generator{}(n); @@ -52,4 +51,20 @@ auto benchmark_insert() }; } +template +auto benchmark_insert_move() +{ + return [](nonius::chronometer meter) { + auto n = meter.param(); + auto g = Generator{}(n); + + measure(meter, [&] { + auto v = Set{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).insert(g[i]); + return v; + }); + }; +} + } // namespace diff --git a/benchmark/set/insert.ipp b/benchmark/set/insert.ipp index 6717ccd57e..cc6b648af3 100644 --- a/benchmark/set/insert.ipp +++ b/benchmark/set/insert.ipp @@ -13,8 +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_insert_mut_std>()) NONIUS_BENCHMARK("std::unordered_set", benchmark_insert_mut_std>()) NONIUS_BENCHMARK("boost::flat_set", benchmark_insert_mut_std>()) @@ -26,3 +27,16 @@ NONIUS_BENCHMARK("immer::set/4B", benchmark_insert,std::equal_to,gc_memory,5>>()) #endif NONIUS_BENCHMARK("immer::set/UN", benchmark_insert,std::equal_to,unsafe_memory,5>>()) + +NONIUS_BENCHMARK("immer::set/move/5B", benchmark_insert_move,std::equal_to,def_memory,5>>()) +NONIUS_BENCHMARK("immer::set/move/4B", benchmark_insert_move,std::equal_to,def_memory,4>>()) +NONIUS_BENCHMARK("immer::set/move/UN", benchmark_insert_move,std::equal_to,unsafe_memory,5>>()) + +NONIUS_BENCHMARK("immer::set/tran/5B", benchmark_insert_mut_std,std::equal_to,def_memory,5>>()) +NONIUS_BENCHMARK("immer::set/tran/4B", benchmark_insert_mut_std,std::equal_to,def_memory,4>>()) +#ifndef DISABLE_GC_BENCHMARKS +NONIUS_BENCHMARK("immer::set/tran/GC", benchmark_insert_mut_std,std::equal_to,gc_memory,5>>()) +#endif +NONIUS_BENCHMARK("immer::set/tran/UN", benchmark_insert_mut_std,std::equal_to,unsafe_memory,5>>()) + +// clang-format on diff --git a/benchmark/set/memory/basic-string-long.cpp b/benchmark/set/memory/basic-string-long.cpp new file mode 100644 index 0000000000..cd39f7f6ee --- /dev/null +++ b/benchmark/set/memory/basic-string-long.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1 + +#include "memory.hpp" + +int main() { return main_basic(); } diff --git a/benchmark/set/memory/basic-string-short.cpp b/benchmark/set/memory/basic-string-short.cpp new file mode 100644 index 0000000000..2c4d47cceb --- /dev/null +++ b/benchmark/set/memory/basic-string-short.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1 + +#include "memory.hpp" + +int main() { return main_basic(); } diff --git a/benchmark/set/memory/basic-unsigned.cpp b/benchmark/set/memory/basic-unsigned.cpp new file mode 100644 index 0000000000..0b02c40424 --- /dev/null +++ b/benchmark/set/memory/basic-unsigned.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1 + +#include "memory.hpp" + +int main() { return main_basic(); } diff --git a/benchmark/set/memory/exp-string-long.cpp b/benchmark/set/memory/exp-string-long.cpp new file mode 100644 index 0000000000..866869d9e0 --- /dev/null +++ b/benchmark/set/memory/exp-string-long.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1 + +#include "memory.hpp" + +int main() { return main_exp(); } diff --git a/benchmark/set/memory/exp-string-short.cpp b/benchmark/set/memory/exp-string-short.cpp new file mode 100644 index 0000000000..e198c07056 --- /dev/null +++ b/benchmark/set/memory/exp-string-short.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1 + +#include "memory.hpp" + +int main() { return main_exp(); } diff --git a/benchmark/set/memory/exp-unsigned.cpp b/benchmark/set/memory/exp-unsigned.cpp new file mode 100644 index 0000000000..2bed0de5d9 --- /dev/null +++ b/benchmark/set/memory/exp-unsigned.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1 + +#include "memory.hpp" + +int main() { return main_exp(); } diff --git a/benchmark/set/memory/lin-string-long.cpp b/benchmark/set/memory/lin-string-long.cpp new file mode 100644 index 0000000000..ce3d5bd161 --- /dev/null +++ b/benchmark/set/memory/lin-string-long.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_LONG 1 + +#include "memory.hpp" + +int main() { return main_lin(); } diff --git a/benchmark/set/memory/lin-string-short.cpp b/benchmark/set/memory/lin-string-short.cpp new file mode 100644 index 0000000000..f1a610b854 --- /dev/null +++ b/benchmark/set/memory/lin-string-short.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_STRING_SHORT 1 + +#include "memory.hpp" + +int main() { return main_lin(); } diff --git a/benchmark/set/memory/lin-unsigned.cpp b/benchmark/set/memory/lin-unsigned.cpp new file mode 100644 index 0000000000..c3e9bad2f2 --- /dev/null +++ b/benchmark/set/memory/lin-unsigned.cpp @@ -0,0 +1,5 @@ +#define IMMER_BENCHMARK_MEMORY_UNSIGNED 1 + +#include "memory.hpp" + +int main() { return main_lin(); } diff --git a/benchmark/set/memory/memory.hpp b/benchmark/set/memory/memory.hpp new file mode 100644 index 0000000000..7b218ba15f --- /dev/null +++ b/benchmark/set/memory/memory.hpp @@ -0,0 +1,451 @@ +// +// 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 +// + +#pragma once + +// +// These are some experiments to get insights about memory usage with various +// data-structures and configurations. +// +// The idea is to run this inside valgrind's massif tool and see what comes +// out. The following is the libraries that we do check. +// + +// these are for "exp" tests +#include +#include // Phil Nash +#include +#include +#include +#include + +// these are for "lin" tests, which are map based actually +#include +#include // Phil Nash +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +struct generate_string_short +{ + static constexpr auto char_set = + "_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + static constexpr auto max_length = 15; + static constexpr auto min_length = 4; + + auto operator()() const + { + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + auto gen = std::bind(dist, engine); + + return [=]() mutable { + auto len = gen() % (max_length - min_length) + min_length; + auto str = std::string(len, ' '); + std::generate_n(str.begin(), len, [&] { + return char_set[gen() % sizeof(char_set)]; + }); + return str; + }; + } +}; + +struct generate_string_long +{ + static constexpr auto char_set = + "_-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + static constexpr auto max_length = 256; + static constexpr auto min_length = 32; + + auto operator()() const + { + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + auto gen = std::bind(dist, engine); + + return [=]() mutable { + auto len = gen() % (max_length - min_length) + min_length; + auto str = std::string(len, ' '); + std::generate_n(str.begin(), len, [&] { + return char_set[gen() % sizeof(char_set)]; + }); + return str; + }; + } +}; + +struct generate_unsigned +{ + auto operator()() const + { + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + auto gen = std::bind(dist, engine); + return gen; + } +}; + +namespace basic_params { +constexpr auto N = 1 << 20; + +void take_snapshot(std::size_t i) +{ + std::cerr << " snapshot " << i << " / " << N << std::endl; + // This is not doing what we thing it does.. it is better to control the + // snapshot generation with something like this: + // + // --max-snapshots=1000 --detailed-freq=1 + // + // VALGRIND_MONITOR_COMMAND("detailed_snapshot"); + // VALGRIND_MONITOR_COMMAND("all_snapshots"); +} +} // namespace basic_params + +template +auto benchmark_memory_basic_std() +{ + using namespace basic_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Set).name()) + << std::endl; + + auto rs = std::vector{}; + auto g = Generator{}(); + auto v = Set{}; + + take_snapshot(0); + for (auto i = 0u; i < N; ++i) { + v.insert(g()); + } + take_snapshot(N); + + volatile auto dont_optimize_ = v.size(); + return dont_optimize_; +} + +template +auto benchmark_memory_basic() +{ + using namespace basic_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Set).name()) + << std::endl; + + auto g = Generator{}(); + auto v = Set{}; + + take_snapshot(0); + for (auto i = 0u; i < N; ++i) { + v = std::move(v).insert(g()); + } + take_snapshot(N); + + volatile auto dont_optimize_ = v.size(); + return dont_optimize_; +} + +namespace exp_params { +constexpr auto N = 1 << 20; +constexpr auto E = 2; + +void take_snapshot(std::size_t i) +{ + std::cerr << " snapshot " << i << " / " << N << std::endl; + // This is not doing what we thing it does.. it is better to control the + // snapshot generation with something like this: + // + // --max-snapshots=1000 --detailed-freq=1 + // + // VALGRIND_MONITOR_COMMAND("detailed_snapshot"); + // VALGRIND_MONITOR_COMMAND("all_snapshots"); +} +} // namespace exp_params + +template +auto benchmark_memory_exp_std() +{ + using namespace exp_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Set).name()) + << std::endl; + + auto rs = std::vector{}; + auto g = Generator{}(); + auto v = Set{}; + + take_snapshot(0); + for (auto i = 0u, n = 1u; i < N; ++i) { + if (i == n) { + rs.push_back(v); + n *= E; + take_snapshot(i); + } + v.insert(g()); + } + take_snapshot(N); + + volatile auto dont_optimize_ = rs.data(); + return dont_optimize_; +} + +template +auto benchmark_memory_exp() +{ + using namespace exp_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Set).name()) + << std::endl; + + auto rs = std::vector{}; + auto g = Generator{}(); + auto v = Set{}; + + take_snapshot(0); + for (auto i = 0u, n = 1u; i < N; ++i) { + if (i == n) { + rs.push_back(v); + n *= E; + take_snapshot(i); + } + v = std::move(v).insert(g()); + } + take_snapshot(N); + + volatile auto dont_optimize_ = rs.data(); + return dont_optimize_; +} + +namespace lin_params { +constexpr auto N = 1 << 14; +constexpr auto M = 1 << 7; +constexpr auto S = 1; + +void take_snapshot(std::size_t i) +{ + std::cerr << " snapshot " << i << " / " << M << std::endl; + // This is not doing what we thing it does.. it is better to control the + // snapshot generation with something like this: + // + // --max-snapshots=1000 --detailed-freq=1 + // + // VALGRIND_MONITOR_COMMAND("detailed_snapshot"); + // VALGRIND_MONITOR_COMMAND("all_snapshots"); +} +} // namespace lin_params + +template +auto benchmark_memory_lin_std() +{ + using namespace lin_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Map).name()) + << std::endl; + + auto rs = std::vector{}; + auto ks = std::vector{}; + auto g = Generator{}(); + auto v = Map{}; + auto ug = generate_unsigned{}(); + + take_snapshot(0); + for (auto i = 0u; i < N; ++i) { + auto k = g(); + v.insert({k, 0u}); + ks.push_back(std::move(k)); + } + take_snapshot(N); + + take_snapshot(0); + for (auto i = 0u; i < M; ++i) { + for (auto j = 0u; j < S; ++j) { + auto&& k = ks[ug() % ks.size()]; + ++v.at(k); + } + rs.push_back(v); + take_snapshot(i); + } + take_snapshot(M); + + volatile auto dont_optimize_ = rs.data(); + return dont_optimize_; +} + +template +auto benchmark_memory_lin() +{ + using namespace lin_params; + + std::cerr << "running... " << boost::core::demangle(typeid(Map).name()) + << std::endl; + + auto rs = std::vector{}; + auto ks = std::vector{}; + auto g = Generator{}(); + auto v = Map{}; + auto ug = generate_unsigned{}(); + + take_snapshot(0); + for (auto i = 0u; i < N; ++i) { + auto k = g(); + v = std::move(v).insert({k, 0u}); + ks.push_back(std::move(k)); + } + take_snapshot(N); + + take_snapshot(0); + for (auto i = 0u; i < M; ++i) { + for (auto j = 0u; j < S; ++j) { + auto&& k = ks[ug() % ks.size()]; + v = std::move(v).update(k, [](auto x) { return ++x; }); + } + rs.push_back(v); + take_snapshot(i); + } + take_snapshot(M); + + volatile auto dont_optimize_ = rs.data(); + return dont_optimize_; +} + +#if IMMER_BENCHMARK_MEMORY_STRING_SHORT +using generator__ = generate_string_short; +using t__ = std::string; +#elif IMMER_BENCHMARK_MEMORY_STRING_LONG +using generator__ = generate_string_long; +using t__ = std::string; +#elif IMMER_BENCHMARK_MEMORY_UNSIGNED +using generator__ = generate_unsigned; +using t__ = unsigned; +#else +#error "choose some type!" +#endif + +int main_basic() +{ + benchmark_memory_basic_std>(); + benchmark_memory_basic_std>(); + + // too slow, why? + // benchmark_memory_basic_std>(); + + // very bad... just ignore... + // benchmark_memory_basic_std>(); + + using def_memory = immer::default_memory_policy; + + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 2>>(); + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 3>>(); + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 4>>(); + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 5>>(); + benchmark_memory_basic< + generator__, + immer::set, std::equal_to, def_memory, 6>>(); + + return 0; +} + +int main_exp() +{ + benchmark_memory_exp_std>(); + benchmark_memory_exp_std>(); + + // too slow, why? + // benchmark_memory_exp_std>(); + + // very bad... just ignore... + // benchmark_memory_exp_std>(); + + using def_memory = immer::default_memory_policy; + + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 2>>(); + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 3>>(); + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 4>>(); + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 5>>(); + benchmark_memory_exp< + generator__, + immer::set, std::equal_to, def_memory, 6>>(); + + return 0; +} + +int main_lin() +{ + benchmark_memory_lin_std>(); + benchmark_memory_lin_std>(); + + // too slow, why? + // benchmark_memory_lin_std>(); + + // very bad... just ignore... + // benchmark_memory_lin_std>(); + + using def_memory = immer::default_memory_policy; + + benchmark_memory_lin, + std::equal_to, + def_memory, + 2>>(); + benchmark_memory_lin, + std::equal_to, + def_memory, + 3>>(); + benchmark_memory_lin, + std::equal_to, + def_memory, + 4>>(); + benchmark_memory_lin, + std::equal_to, + def_memory, + 5>>(); + benchmark_memory_lin, + std::equal_to, + def_memory, + 6>>(); + + return 0; +} diff --git a/benchmark/set/string-box/erase.cpp b/benchmark/set/string-box/erase.cpp new file mode 100644 index 0000000000..88191adaeb --- /dev/null +++ b/benchmark/set/string-box/erase.cpp @@ -0,0 +1,11 @@ +// +// 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 "generator.ipp" + +#include "../erase.ipp" diff --git a/benchmark/set/string-long/erase.cpp b/benchmark/set/string-long/erase.cpp new file mode 100644 index 0000000000..88191adaeb --- /dev/null +++ b/benchmark/set/string-long/erase.cpp @@ -0,0 +1,11 @@ +// +// 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 "generator.ipp" + +#include "../erase.ipp" diff --git a/benchmark/set/string-short/erase.cpp b/benchmark/set/string-short/erase.cpp new file mode 100644 index 0000000000..88191adaeb --- /dev/null +++ b/benchmark/set/string-short/erase.cpp @@ -0,0 +1,11 @@ +// +// 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 "generator.ipp" + +#include "../erase.ipp" diff --git a/benchmark/set/unsigned/erase.cpp b/benchmark/set/unsigned/erase.cpp new file mode 100644 index 0000000000..88191adaeb --- /dev/null +++ b/benchmark/set/unsigned/erase.cpp @@ -0,0 +1,11 @@ +// +// 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 "generator.ipp" + +#include "../erase.ipp" diff --git a/cmake/FindGuile.cmake b/cmake/FindGuile.cmake deleted file mode 100644 index 2b8781c48f..0000000000 --- a/cmake/FindGuile.cmake +++ /dev/null @@ -1,326 +0,0 @@ -#[[.rst -# -# FindGuile -# --------- -# Find the development libraries for Guile. -# -# Exported Vars -# ~~~~~~~~~~~~~ -# -# .. variable:: Guile_FOUND -# -# Set to *true* if Guile was found. -# -# .. variable:: Guile_INCLUDE_DIRS -# -# A list of include directories. -# -# .. variable:: Guile_LIBRARIES -# -# A list of libraries needed to build you project. -# -# .. variable:: Guile_VERSION_STRING -# -# Guile full version. -# -# .. variable:: Guile_VERSION_MAJOR -# -# Guile major version. -# -# .. variable:: Guile_VERSION_MINOR -# -# Guile minor version. -# -# .. variable:: Guile_VERSION_PATCH -# -# Guile patch version. -# -# .. variable:: Guile_EXECUTABLE -# -# Guile executable (optional). -# -# .. variable:: Guile_CONFIG_EXECUTABLE -# -# Guile configuration executable (optional). -# -# .. variable:: Guile_ROOT_DIR -# -# Guile installation root dir (optional). -# -# .. variable:: Guile_SITE_DIR -# -# Guile installation module site dir (optional). -# -# .. variable:: Guile_EXTENSION_DIR -# -# Guile installation extension dir (optional). -# -# Control VARS -# ~~~~~~~~~~~~ -# :envvar:`Guile_ROOT_DIR` -# -# Use this variable to provide hints to :filename:`find_{*}` commands, -# you may pass it to :command:`cmake` or set the environtment variable. -# -# .. code-block:: cmake -# -# % cmake . -Bbuild -DGuile_ROOT_DIR= -# -# # or -# % export Guile_ROOT_DIR=; -# % cmake . -Bbuild -# -# # or -# % Guile_ROOT_DIR= cmake . -Bbuild -# -# -#]] - - -#[[.rst -# -# Copyright © 2016, Edelcides Gonçalves -# -# Permission to use, copy, modify, and/or distribute this software for -# any purpose with or without fee is hereby granted, provided that the -# above copyright notice and this permission notice appear in all -# copies. -# -# *THE SOFTWARE IS PROVIDED* **AS IS** *AND ISC DISCLAIMS ALL -# WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED -# WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL ISC BE -# LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES -# OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR -# PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER -# TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -# PERFORMANCE OF THIS SOFTWARE*. -# -# This file is not part of CMake -# -#]] - - -include (SelectLibraryConfigurations) -include (FindPackageHandleStandardArgs) - -function (_guile_find_component_include_dir component header) - find_path ("${component}_INCLUDE_DIR" - ${header} - HINTS - "${GUile_ROOT_DIR}" - ENV Guile_ROOT_DIR - PATH_SUFFIXES - Guile guile Guile-2.0 guile-2.0 Guile/2.0 guile/2.0 GC - gc) - - set ("${component}_INCLUDE_DIR" "${${component}_INCLUDE_DIR}" - PARENT_SCOPE) -endfunction () - -function (_guile_find_component_library component_name component) - - find_library ("${component_name}_LIBRARY_RELEASE" - NAMES "${component}" "${component}-2.0" - HINTS - "${GUile_ROOT_DIR}" - ENV Guile_ROOT_DIR - PATHS - /usr/lib/${CMAKE_LIBRARY_ARCHITECTURE} - /usr/lib64/${CMAKE_LIBRARY_ARCHITECTURE} - /usr/lib32/${CMAKE_LIBRARY_ARCHITECTURE}) - - if (${component_name}_LIBRARY_RELEASE) - select_library_configurations (${component_name}) - set ("${component_name}_LIBRARY_RELEASE" - "${${component_name}_LIBRARY_RELEASE}" PARENT_SCOPE) - set ("${component_name}_LIBRARY" - "${${component_name}_LIBRARY}" PARENT_SCOPE) - set ("${component_name}_LIBRARIES" - "${${component_name}_LIBRARIES}" PARENT_SCOPE) - endif () -endfunction () - -function (_guile_find_version_2 header_file macro_name) - file (STRINGS "${header_file}" _VERSION - REGEX "#define[\t ]+${macro_name}[\t ]+[0-9]+") - - if (_VERSION) - string (REGEX REPLACE - ".*#define[\t ]+${macro_name}[\t ]+([0-9]+).*" - "\\1" _VERSION_VALUE "${_VERSION}") - if ("${_VERSION}" STREQUAL "${_VERSION_VALUE}") - set (_VERSION_FOUND 0 PARENT_SCOPE) - else () - set (_VERSION_FOUND 1 PARENT_SCOPE) - set (_VERSION "${_VERSION_VALUE}" PARENT_SCOPE) - endif () - else () - set (_VERSION_FOUND 0 PARENT_SCOPE) - endif () -endfunction () - - -##### Entry Point ##### - -set (Guile_FOUND) -set (Guile_INCLUDE_DIRS) -set (Guile_LIBRARIES) -set (Guile_VERSION_STRING) -set (Guile_VERSION_MAJOR) -set (Guile_VERSION_MINOR) -set (Guile_VERSION_PATCH) -set (Guile_EXECUTABLE) - -_guile_find_component_include_dir (Guile "libguile.h") -if (Guile_INCLUDE_DIR) - _guile_find_version_2 ("${Guile_INCLUDE_DIR}/libguile/version.h" - SCM_MAJOR_VERSION) - if (_VERSION_FOUND) - set (Guile_VERSION_MAJOR "${_VERSION}") - else () - message (FATAL_ERROR "FindGuile: Failed to find Guile_MAJOR_VERSION.") - endif () - - _guile_find_version_2 ("${Guile_INCLUDE_DIR}/libguile/version.h" - SCM_MINOR_VERSION) - if (_VERSION_FOUND) - set (Guile_VERSION_MINOR "${_VERSION}") - else () - message (FATAL_ERROR "FindGuile: Failed to find Guile_MINOR_VERSION.") - endif () - - _guile_find_version_2 ("${Guile_INCLUDE_DIR}/libguile/version.h" - SCM_MICRO_VERSION) - if (_VERSION_FOUND) - set (Guile_VERSION_PATCH "${_VERSION}") - else () - message (FATAL_ERROR "FindGuile: Failed to find Guile_MICRO_VERSION.") - endif () - set (Guile_VERSION_STRING "${Guile_VERSION_MAJOR}.${Guile_VERSION_MINOR}.${Guile_VERSION_PATCH}") - - unset (_VERSION_FOUND) - unset (_VERSION) -endif () - -_guile_find_component_include_dir (Guile_GC "gc.h") -_guile_find_component_library (Guile guile) -_guile_find_component_library (Guile_GC gc) - -find_program (Guile_EXECUTABLE - guile - DOC "Guile executable.") - -if (Guile_EXECUTABLE) - execute_process (COMMAND ${Guile_EXECUTABLE} --version - RESULT_VARIABLE _status - OUTPUT_VARIABLE _output - OUTPUT_STRIP_TRAILING_WHITESPACE) - - string (REGEX REPLACE ".*\\(GNU Guile\\)[\t ]+([0-9]+)\\..*" "\\1" - _guile_ver_major "${_output}") - - string (REGEX REPLACE ".*\\(GNU Guile\\)[\t ]+[0-9]+\\.([0-9]+).*" "\\1" - _guile_ver_minor "${_output}") - - string (REGEX REPLACE ".*\\(GNU Guile\\)[\t ]+[0-9]+\\.[0-9]+\\.([0-9]+).*" "\\1" - _guile_ver_patch "${_output}") - - set (_version "${_guile_ver_major}.${_guile_ver_minor}.${_guile_ver_patch}") - - if (NOT Guile_FIND_QUIETLY) - if (NOT Guile_VERSION_STRING STREQUAL _version) - message (WARNING "FindGuile: Versions provided by library differs from the one provided by executable.") - endif () - - if (NOT _status STREQUAL "0") - message (WARNING "FindGuile: guile (1) process exited abnormally.") - endif () - endif () - - unset (_version) - unset (_status) - unset (_version) - unset (_guile_ver_major) - unset (_guile_ver_minor) - unset (_guile_ver_patch) -endif () - -find_package_handle_standard_args (GC - "FindGuile: Failed to find dependency GC." - Guile_GC_INCLUDE_DIR - Guile_GC_LIBRARY - Guile_GC_LIBRARIES - Guile_GC_LIBRARY_RELEASE) - -find_package_handle_standard_args (Guile - REQUIRED_VARS - Guile_INCLUDE_DIR - Guile_LIBRARY - Guile_LIBRARIES - Guile_LIBRARY_RELEASE - GC_FOUND - VERSION_VAR Guile_VERSION_STRING) - -if (Guile_FOUND) - list (APPEND Guile_INCLUDE_DIRS "${Guile_INCLUDE_DIR}" - "${Guile_GC_INCLUDE_DIR}") - - if (NOT TARGET Guile::Library AND NOT TARGET GC::Library) - add_library (Guile::GC::Library UNKNOWN IMPORTED) - set_property (TARGET Guile::GC::Library APPEND - PROPERTY IMPORTED_CONFIGURATIONS RELEASE) - - set_target_properties (Guile::GC::Library - PROPERTIES - INTERFACE_INCLUDE_DIRS - "${Guile_GC_INCLUDE_DIR}" - IMPORTED_LOCATION - "${Guile_GC_LIBRARY}" - IMPORTED_LOCATION_RELEASE - "${Guile_GC_LIBRARY_RELEASE}") - - add_library (Guile::Library UNKNOWN IMPORTED) - set_property (TARGET Guile::Library APPEND - PROPERTY IMPORTED_CONFIGURATIONS RELEASE) - set_property (TARGET Guile::Library APPEND - PROPERTY - INTERFACE_LINK_LIBRARIES - Guile::GC::Library) - - set_target_properties (Guile::Library - PROPERTIES - INTERFACE_INCLUDE_DIRSr - "${Guile_INCLUDE_DIR}" - IMPORTED_LOCATION "${Guile_LIBRARY}" - IMPORTED_LOCATION_RELEASE - "${Guile_LIBRARY_RELEASE}") - - set (Guile_LIBRARIES Guile::Library Guile::GC::Library) - endif () -endif () - -find_program(Guile_CONFIG_EXECUTABLE - NAMES guile-config - DOC "Guile configutration binary") - -if (Guile_CONFIG_EXECUTABLE) - execute_process (COMMAND ${Guile_CONFIG_EXECUTABLE} info prefix - OUTPUT_VARIABLE Guile_ROOT_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - - execute_process (COMMAND ${Guile_CONFIG_EXECUTABLE} info sitedir - OUTPUT_VARIABLE Guile_SITE_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) - - execute_process (COMMAND ${Guile_CONFIG_EXECUTABLE} info extensiondir - OUTPUT_VARIABLE Guile_EXTENSION_DIR - OUTPUT_STRIP_TRAILING_WHITESPACE) -endif () - -mark_as_advanced (Guile_EXECUTABLE - Guile_INCLUDE_DIR - Guile_LIBRARY - Guile_LIBRARY_RELEASE - Guile_GC_INCLUDE_DIR - Guile_GC_LIBRARY - Guile_GC_LIBRARY_RELEASE) diff --git a/default.nix b/default.nix index 1a6f029681..b2ab20d04d 100644 --- a/default.nix +++ b/default.nix @@ -1,11 +1,33 @@ -with import {}; +{ pkgs ? import {} }: +with pkgs; + +let + inherit (import (pkgs.fetchFromGitHub { + owner = "hercules-ci"; + repo = "gitignore.nix"; + rev = "80463148cd97eebacf80ba68cf0043598f0d7438"; + sha256 = "1l34rmh4lf4w8a1r8vsvkmg32l1chl0p593fl12r28xx83vn150v"; + }) {}) gitignoreSource; + + nixFilter = name: type: !(lib.hasSuffix ".nix" name); + srcFilter = src: lib.cleanSourceWith { + filter = nixFilter; + src = gitignoreSource src; + }; + +in stdenv.mkDerivation rec { name = "immer-git"; version = "git"; - src = fetchGit ./.; + src = srcFilter ./.; nativeBuildInputs = [ cmake ]; dontBuild = true; + dontUseCmakeBuildDir = true; + cmakeFlags = [ + "-Dimmer_BUILD_TESTS=OFF" + "-Dimmer_BUILD_EXAMPLES=OFF" + ]; meta = { homepage = "https://github.com/arximboldi/immer"; description = "Postmodern immutable data structures for C++"; diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 8558a3ecf6..7435dfe01b 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -4,11 +4,11 @@ add_custom_target(doxygen COMMAND doxygen doxygen.config - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/doc") + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") add_custom_target(docs COMMAND make html - WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}/doc") + WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}") add_dependencies(docs doxygen) set(immer_ssh_method @@ -18,5 +18,5 @@ set(immer_ssh_method add_custom_target(upload-docs COMMAND rsync -av -e \"${immer_ssh_method}\" - ${CMAKE_SOURCE_DIR}/doc/_build/html/* + ${CMAKE_CURRENT_SOURCE_DIR}/_build/html/* raskolnikov@sinusoid.es:public/immer/) diff --git a/doc/containers.rst b/doc/containers.rst index c6913ba4a5..5bec78896b 100644 --- a/doc/containers.rst +++ b/doc/containers.rst @@ -48,3 +48,10 @@ map .. doxygenclass:: immer::map :members: :undoc-members: + +table +----- + +.. doxygenclass:: immer::table + :members: + :undoc-members: diff --git a/doc/design.rst b/doc/design.rst index 27d29e9e61..f1848f534e 100644 --- a/doc/design.rst +++ b/doc/design.rst @@ -18,7 +18,7 @@ internally, common data with other objects. We sometimes refer to this property as **structural sharing**. This behaviour is transparent to the user. -Assigment +Assignment --------- We are sorry, we lied. These containers provide *one mutating @@ -79,14 +79,14 @@ removing it, as in: :end-before: move-good/end So, is it bad style then to use ``const`` as much as possible? I -wouldn't say so and it is advisable when ``std::move()`` is not used. -An alternative style is to not use ``const`` but adopt an `AAA-style -`_ (*Almost Always use Auto*). This way, it is easy to look for +wouldn't say so, and it is advisable when ``std::move()`` is not used. +An alternative style is to not use ``const`` but adopt an `AAA-style`_ +(*Almost Always use Auto*). This way, it is easy to look for mutations by looking for lines that contain ``=`` but no ``auto``. Remember that when using our immutable containers ``operator=`` is the only way to mutate a variable. -.. _aaa: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/ +.. _AAA-style: https://herbsutter.com/2013/08/12/gotw-94-solution-aaa-style-almost-always-auto/ .. admonition:: Why does ``const`` prevent move semantics? @@ -149,7 +149,7 @@ mutate part of the internal data structure in place when possible. If you don't like this syntax, :doc:`transients` may be used to obtain similar performance benefits. -.. admonition:: Assigment guarantees +.. admonition:: Assignment guarantees From the language point of view, the only requirement on moved from values is that they should still be destructible. We provide the @@ -208,7 +208,7 @@ Efficient batch manipulations Sometimes you may write a function that needs to do multiple changes to a container. Like most code you write with this library, this function is *pure*: it takes one container value in, and produces a -new container value out, no side-effects. +new container value out, no side effects. Let's say we want to write a function that inserts all integers in the range :math:`[first, last)` into an immutable vector: diff --git a/doc/memory.rst b/doc/memory.rst index 3138b249f3..ba8ec17569 100644 --- a/doc/memory.rst +++ b/doc/memory.rst @@ -1,7 +1,7 @@ Memory management ================= -Memory management is specially important for immutable data +Memory management is especially important for immutable data structures. This is mainly due to: #. In order to preserve the old value, new memory has to be allocated @@ -39,7 +39,7 @@ Memory policy Example: tracing garbage collection ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -It is note worthy that all aspects of a +It is noteworthy that all aspects of a :cpp:class:`immer::memory_policy` are not completely orthogonal. Let's say you want to use a `tracing garbage collector`_. Actually, we @@ -79,7 +79,7 @@ allocate objects of those sizes. .. _metafunction class: http://www.boost.org/doc/libs/1_62_0/libs/mpl/doc/refmanual/metafunction-class.html -A **heap** is a type with a methods ``void* allocate(std::size_t)`` +A **heap** is a type with methods ``void* allocate(std::size_t)`` and ``void deallocate(void*)`` that return and release raw memory. For a canonical model of this concept check the :cpp:class:`immer::cpp_heap`. @@ -95,8 +95,8 @@ For a canonical model of this concept check the On the other hand, having some **scoped state** does make sense for some use-cases of immutable data structures. For example, we might want to support variations of `region - based allocation`_. This interface might evolve to evolve - to support some kind of non-global state to accommodate + based allocation`_. This interface might evolve to support + some kind of non-global state to accommodate these use cases. .. _region based allocation: https://en.wikipedia.org/wiki/Region-based_memory_management @@ -143,7 +143,7 @@ Heap adaptors Inspired by `Andrei Alexandrescu's talk on allocators `_ and `Emery Berger's heap layers `_ we provide allocator adaptors that can be combined using C++ mixins. These -enable building more complex allocator out of simpler strategies, or +enable building more complex allocators out of simpler strategies, or provide application specific optimizations on top of general allocators. diff --git a/doc/transients.rst b/doc/transients.rst index 9b385d4db5..461bb4ac1c 100644 --- a/doc/transients.rst +++ b/doc/transients.rst @@ -4,9 +4,9 @@ Transients ========== *Transients* is a concept borrowed `from Clojure -`_, with some twists to turn make more idiomatic +`_, with some twists to make it more idiomatic in C++. Essentially, they are a mutable interface built on top of the -same data structures the implements the immutable containers under the +same data structures that implement the immutable containers under the hood. These can be useful for :ref:`performing efficient batch @@ -49,3 +49,10 @@ map_transient .. doxygenclass:: immer::map_transient :members: :undoc-members: + +table_transient +--------------- + +.. doxygenclass:: immer::table_transient + :members: + :undoc-members: diff --git a/example/table/intro.cpp b/example/table/intro.cpp new file mode 100644 index 0000000000..67c83f9fa8 --- /dev/null +++ b/example/table/intro.cpp @@ -0,0 +1,29 @@ +// +// 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:intro/start +#include + +int main() +{ + struct Item + { + std::string id; + int value; + }; + + const auto v0 = immer::table{}; + const auto v1 = v0.insert({"hello", 42}); + assert(v0["hello"].value == 0); + assert(v1["hello"].value == 42); + + const auto v2 = v1.erase("hello"); + assert(v1.find("hello")->value == 42); + assert(!v2.find("hello")); +} +// include:intro/end diff --git a/extra/fuzzer/fuzzer_input.hpp b/extra/fuzzer/fuzzer_input.hpp index 52d5c3901c..22def08157 100644 --- a/extra/fuzzer/fuzzer_input.hpp +++ b/extra/fuzzer/fuzzer_input.hpp @@ -12,6 +12,10 @@ #include #include +#if defined(__GNUC__) && (__GNUC__ == 9 || __GNUC__ == 8 || __GNUC__ == 10) +#define IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG 1 +#endif + struct no_more_input : std::exception {}; diff --git a/extra/fuzzer/map-gc.cpp b/extra/fuzzer/map-gc.cpp index e5bea5dad0..556df956f9 100644 --- a/extra/fuzzer/map-gc.cpp +++ b/extra/fuzzer/map-gc.cpp @@ -13,6 +13,8 @@ #include #include +#include + #include using gc_memory = immer::memory_policy, @@ -49,7 +51,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_erase_move, op_iterate, op_find, - op_update + op_update, + op_update_move, + op_update_if_exists, + op_update_if_exists_move, + op_diff }; auto src = read(in, is_valid_var); auto dst = read(in, is_valid_var); @@ -94,6 +100,43 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); break; } + case op_update_move: { + auto key = read(in); + vars[dst] = + std::move(vars[src]).update(key, [](int x) { return x + 1; }); + break; + } + case op_update_if_exists: { + auto key = read(in); + vars[dst] = + vars[src].update_if_exists(key, [](int x) { return x + 1; }); + break; + } + case op_update_if_exists_move: { + auto key = read(in); + vars[dst] = std::move(vars[src]).update_if_exists( + key, [](int x) { return x + 1; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } default: break; }; diff --git a/extra/fuzzer/map-st-str-conflict.cpp b/extra/fuzzer/map-st-str-conflict.cpp new file mode 100644 index 0000000000..7da685476d --- /dev/null +++ b/extra/fuzzer/map-st-str-conflict.cpp @@ -0,0 +1,136 @@ +// +// 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 "fuzzer_input.hpp" + +#include +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~((std::size_t{1} << 48) - 1); + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer::map, + colliding_hash_t, + std::equal_to<>, + st_memory, + 3>; + + 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_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_update_move, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_set: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].set(value, "foo"); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).set(value, "foo"); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + auto value = std::to_string(read(in)); + auto res = vars[src].find(value); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, "foo"); + } + break; + } + case op_update: { + auto key = std::to_string(read(in)); + vars[dst] = vars[src].update( + key, [](std::string x) { return std::move(x) + "bar"; }); + break; + } + case op_update_move: { + auto key = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).update( + key, [](std::string x) { return std::move(x) + "baz"; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} diff --git a/extra/fuzzer/map-st-str.cpp b/extra/fuzzer/map-st-str.cpp new file mode 100644 index 0000000000..407709614f --- /dev/null +++ b/extra/fuzzer/map-st-str.cpp @@ -0,0 +1,135 @@ +// +// 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 "fuzzer_input.hpp" + +#include +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~15; + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer::map, + colliding_hash_t, + std::equal_to<>, + st_memory>; + + 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_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_update_move, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_set: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].set(value, "foo"); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).set(value, "foo"); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + auto value = std::to_string(read(in)); + auto res = vars[src].find(value); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, "foo"); + } + break; + } + case op_update: { + auto key = std::to_string(read(in)); + vars[dst] = vars[src].update( + key, [](std::string x) { return std::move(x) + "bar"; }); + break; + } + case op_update_move: { + auto key = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).update( + key, [](std::string x) { return std::move(x) + "baz"; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} diff --git a/extra/fuzzer/map-st.cpp b/extra/fuzzer/map-st.cpp index d9abdeb284..6a0a96ff59 100644 --- a/extra/fuzzer/map-st.cpp +++ b/extra/fuzzer/map-st.cpp @@ -10,6 +10,8 @@ #include +#include + #include using st_memory = immer::memory_policy, @@ -44,7 +46,9 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_erase_move, op_iterate, op_find, - op_update + op_update, + op_update_move, + op_diff }; auto src = read(in, is_valid_var); auto dst = read(in, is_valid_var); @@ -89,6 +93,31 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); break; } + case op_update_move: { + auto key = read(in); + vars[dst] = + std::move(vars[src]).update(key, [](int x) { return x + 1; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } default: break; }; diff --git a/extra/fuzzer/map.cpp b/extra/fuzzer/map.cpp index 1112b969e1..e5d4ce33d8 100644 --- a/extra/fuzzer/map.cpp +++ b/extra/fuzzer/map.cpp @@ -10,6 +10,8 @@ #include +#include + #include struct colliding_hash_t @@ -37,7 +39,11 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_erase_move, op_iterate, op_find, - op_update + op_update, + op_update_move, + op_update_if_exists, + op_update_if_exists_move, + op_diff, }; auto src = read(in, is_valid_var); auto dst = read(in, is_valid_var); @@ -82,6 +88,43 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); break; } + case op_update_move: { + auto key = read(in); + vars[dst] = + std::move(vars[src]).update(key, [](int x) { return x + 1; }); + break; + } + case op_update_if_exists: { + auto key = read(in); + vars[dst] = + vars[src].update_if_exists(key, [](int x) { return x + 1; }); + break; + } + case op_update_if_exists_move: { + auto key = read(in); + vars[dst] = std::move(vars[src]).update_if_exists( + key, [](int x) { return x + 1; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } default: break; }; diff --git a/extra/fuzzer/set-gc.cpp b/extra/fuzzer/set-gc.cpp index 994daa8df5..55382e79c7 100644 --- a/extra/fuzzer/set-gc.cpp +++ b/extra/fuzzer/set-gc.cpp @@ -49,7 +49,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_erase, op_insert_move, op_erase_move, - op_iterate + op_iterate, + op_diff }; auto src = read(in, is_valid_var); auto dst = read(in, is_valid_var); @@ -81,6 +82,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, }); break; } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } default: break; }; diff --git a/extra/fuzzer/set-st-str-conflict.cpp b/extra/fuzzer/set-st-str-conflict.cpp new file mode 100644 index 0000000000..bf00873a65 --- /dev/null +++ b/extra/fuzzer/set-st-str-conflict.cpp @@ -0,0 +1,108 @@ +// +// 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 "fuzzer_input.hpp" + +#include +#include +#include +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~((std::size_t{1} << 48) - 1); + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using set_t = immer:: + set, st_memory, 3>; + + 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_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_insert: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].insert(value); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_insert_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).insert(value); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(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_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } + default: + break; + }; + return true; + }); +} diff --git a/extra/fuzzer/set-st-str.cpp b/extra/fuzzer/set-st-str.cpp new file mode 100644 index 0000000000..e5fc1137a8 --- /dev/null +++ b/extra/fuzzer/set-st-str.cpp @@ -0,0 +1,108 @@ +// +// 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 "fuzzer_input.hpp" + +#include +#include +#include +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~15; + } +}; + +extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, + std::size_t size) +{ + constexpr auto var_count = 4; + + using set_t = + immer::set, st_memory>; + + 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_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_insert: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].insert(value); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_insert_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).insert(value); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(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_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } + default: + break; + }; + return true; + }); +} diff --git a/extra/fuzzer/set-st.cpp b/extra/fuzzer/set-st.cpp index 07353f041a..fe7c820174 100644 --- a/extra/fuzzer/set-st.cpp +++ b/extra/fuzzer/set-st.cpp @@ -12,6 +12,8 @@ #include #include +#include + #include using st_memory = immer::memory_policy, @@ -44,7 +46,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_erase, op_insert_move, op_erase_move, - op_iterate + op_iterate, + op_diff }; auto src = read(in, is_valid_var); auto dst = read(in, is_valid_var); @@ -66,7 +69,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, } case op_erase_move: { auto value = read(in); - vars[dst] = vars[src].erase(value); + vars[dst] = std::move(vars[src]).erase(value); break; } case op_iterate: { @@ -76,6 +79,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, } break; } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } default: break; }; diff --git a/extra/fuzzer/set.cpp b/extra/fuzzer/set.cpp index a00f126fc1..c225a8ce6d 100644 --- a/extra/fuzzer/set.cpp +++ b/extra/fuzzer/set.cpp @@ -8,6 +8,7 @@ #include "fuzzer_input.hpp" +#include #include #include @@ -35,7 +36,8 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, op_erase, op_insert_move, op_erase_move, - op_iterate + op_iterate, + op_diff }; auto src = read(in, is_valid_var); auto dst = read(in, is_valid_var); @@ -67,6 +69,22 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t* data, } break; } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } default: break; }; diff --git a/extra/guile/README.rst b/extra/guile/README.rst index 09cf8be644..879850b9e0 100644 --- a/extra/guile/README.rst +++ b/extra/guile/README.rst @@ -2,7 +2,7 @@ Guile bindings ============== -This library includes experimental bindings bring efficient immutable +This library includes experimental bindings that provide efficient immutable vectors for the `GNU Guile`_ Scheme implementation. The interface is somewhat **incomplete**, but you can already do something interesting things like: @@ -15,7 +15,7 @@ things like: **Do you want to help** making these bindings complete and production ready? Drop a line at `immer@sinusoid.al - `_ or `open an issue on Github + `_ or `open an issue on GitHub `_ .. _GNU Guile: https://www.gnu.org/software/guile/ diff --git a/extra/python/README.rst b/extra/python/README.rst index 7447e99e62..4b6eb5754d 100644 --- a/extra/python/README.rst +++ b/extra/python/README.rst @@ -2,7 +2,7 @@ Python bindings =============== -This library includes experimental bindings bring efficient immutable +This library includes experimental bindings that provide efficient immutable vectors for the Python language. They were developed as part of the research for the `ICFP'17 paper`_. The interface is quite **incomplete**, yet you can already do some things like: @@ -15,7 +15,7 @@ research for the `ICFP'17 paper`_. The interface is quite **Do you want to help** making these bindings complete and production ready? Drop a line at `immer@sinusoid.al - `_ or `open an issue on Github + `_ or `open an issue on GitHub `_ Installation diff --git a/immer/algorithm.hpp b/immer/algorithm.hpp index 0b71c65c8e..382df02dbc 100644 --- a/immer/algorithm.hpp +++ b/immer/algorithm.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include @@ -92,6 +93,26 @@ bool for_each_chunk_p(const T* first, const T* last, Fn&& fn) return std::forward(fn)(first, last); } +namespace detail { + +template +T accumulate_move(Iter first, Iter last, T init) +{ + for (; first != last; ++first) + init = std::move(init) + *first; + return init; +} + +template +T accumulate_move(Iter first, Iter last, T init, Fn op) +{ + for (; first != last; ++first) + init = op(std::move(init), *first); + return init; +} + +} // namespace detail + /*! * Equivalent of `std::accumulate` applied to the range `r`. */ @@ -99,7 +120,7 @@ template T accumulate(Range&& r, T init) { for_each_chunk(r, [&](auto first, auto last) { - init = std::accumulate(first, last, init); + init = detail::accumulate_move(first, last, init); }); return init; } @@ -108,7 +129,7 @@ template T accumulate(Range&& r, T init, Fn fn) { for_each_chunk(r, [&](auto first, auto last) { - init = std::accumulate(first, last, init, fn); + init = detail::accumulate_move(first, last, init, fn); }); return init; } @@ -121,7 +142,7 @@ template T accumulate(Iterator first, Iterator last, T init) { for_each_chunk(first, last, [&](auto first, auto last) { - init = std::accumulate(first, last, init); + init = detail::accumulate_move(first, last, init); }); return init; } @@ -130,7 +151,7 @@ template T accumulate(Iterator first, Iterator last, T init, Fn fn) { for_each_chunk(first, last, [&](auto first, auto last) { - init = std::accumulate(first, last, init, fn); + init = detail::accumulate_move(first, last, init, fn); }); return init; } @@ -208,6 +229,97 @@ bool all_of(Iter first, Iter last, Pred p) }); } +/*! + * Object that can be used to process changes as computed by the @a diff + * algorithm. + * + * @tparam AddedFn Unary function that is be called whenever an added element is + * found. It is called with the added element as argument. + * + * @tparam RemovedFn Unary function that is called whenever a removed element is + * found. It is called with the removed element as argument. + * + * @tparam ChangedFn Unary function that is called whenever a changed element is + * found. It is called with the changed element as argument. + */ +template +struct differ +{ + AddedFn added; + RemovedFn removed; + ChangedFn changed; +}; + +/*! + * Produces a @a differ object with `added`, `removed` and `changed` functions. + */ +template +auto make_differ(AddedFn&& added, RemovedFn&& removed, ChangedFn&& changed) + -> differ, + std::decay_t, + std::decay_t> +{ + return {std::forward(added), + std::forward(removed), + std::forward(changed)}; +} + +/*! + * Produces a @a differ object with `added` and `removed` functions and no + * `changed` function. + */ +template +auto make_differ(AddedFn&& added, RemovedFn&& removed) +{ + return make_differ(std::forward(added), + std::forward(removed), + [](auto&&...) {}); +} + +/*! + * Compute the differences between `a` and `b`. + * + * Changes detected are notified via the differ object, which should support the + * following expressions: + * + * - `differ.added(x)`, invoked when element `x` is found in `b` but not in + * `a`. + * + * - `differ.removed(x)`, invoked when element `x` is found in `a` but not in + * `b`. + * + * - `differ.changed(x, y)`, invoked when element `x` and `y` from `a` and `b` + * share the same key but map to a different value. + * + * This method leverages structural sharing to offer a complexity @f$ O(|diff|) + * @f$ when `b` is derived from `a` by performing @f$ |diff| @f$ updates. This + * is, this function can detect changes in effectively constant time per update, + * as oposed to the @f$ O(|a|+|b|) @f$ complexity of a trivial implementation. + * + * @rst + * + * .. note:: This method is only implemented for ``map`` and ``set``. When sets + * are diffed, the ``changed`` function is never called. + * + * @endrst + */ +template +void diff(const T& a, const T& b, Differ&& differ) +{ + a.impl().template diff>( + b.impl(), std::forward(differ)); +} + +/*! + * Compute the differences between `a` and `b` using the callbacks in `fns` as + * differ. Equivalent to `diff(a, b, make_differ(fns)...)`. + */ +template +void diff(const T& a, const T& b, Fns&&... fns) +{ + diff(a, b, make_differ(std::forward(fns)...)); +} + /** @} */ // group: algorithm } // namespace immer diff --git a/immer/array.hpp b/immer/array.hpp index c86f4ea7ff..f71477c239 100644 --- a/immer/array.hpp +++ b/immer/array.hpp @@ -33,7 +33,7 @@ class array_transient; * .. tip:: Don't be fooled by the bad complexity of this data * structure. It is a great choice for short sequence or when it * is seldom or never changed. This depends on the ``sizeof(T)`` - * and the expensiveness of its ``T``'s copy constructor, in case + * and the expensiveness of its ``T``'s copy constructor. In case * of doubt, measure. For basic types, using an `array` when * :math:`n < 100` is a good heuristic. * @@ -91,7 +91,7 @@ public: {} /*! - * Constructs a array containing the element `val` repeated `n` + * Constructs an array containing the element `val` repeated `n` * times. */ array(size_type n, T v = {}) @@ -107,7 +107,8 @@ public: /*! * Returns an iterator pointing just after the last element of the - * collection. It does not allocate and its complexity is @f$ O(1) @f$. + * collection. It does not allocate memory and its complexity is @f$ O(1) + * @f$. */ IMMER_NODISCARD iterator end() const { return impl_.data() + impl_.size; } @@ -160,7 +161,7 @@ public: /*! * Returns a `const` reference to the element at position `index`. - * It is undefined when @f$ 0 index \geq size() @f$. It does not + * It is undefined when @f$ index \geq size() @f$. It does not * allocate memory and its complexity is *effectively* @f$ O(1) * @f$. */ @@ -308,6 +309,14 @@ public: return transient_type{std::move(impl_)}; } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + void* identity() const { return impl_.ptr; } + // Semi-private const impl_t& impl() const { return impl_; } diff --git a/immer/atom.hpp b/immer/atom.hpp index effde6ff23..2cf409b916 100644 --- a/immer/atom.hpp +++ b/immer/atom.hpp @@ -129,7 +129,7 @@ private: * @rst * * .. warning:: If memory policy used includes thread unsafe reference counting, - * no no thread safety is assumed, and the atom becomes thread unsafe too! + * no thread safety is assumed, and the atom becomes thread unsafe too! * * .. note:: ``box`` provides a value based box of type ``T``, this is, we * can think about it as a value-based version of ``std::shared_ptr``. In a @@ -138,7 +138,7 @@ private: * ``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::atmic_shared_ptr`` is + * ``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. diff --git a/immer/box.hpp b/immer/box.hpp index c0a6a97a03..711ac78f09 100644 --- a/immer/box.hpp +++ b/immer/box.hpp @@ -219,9 +219,9 @@ IMMER_NODISCARD auto operator!=(T2&& b, const box& a) template IMMER_NODISCARD auto operator<(T2&& b, const box& a) -> std::enable_if_t, std::decay_t>::value, - decltype(a.get() < b)> + decltype(b < a.get())> { - return a.get() < b; + return b < a.get(); } } // namespace immer diff --git a/immer/config.hpp b/immer/config.hpp index c1024fb9c6..2e8378f6d4 100644 --- a/immer/config.hpp +++ b/immer/config.hpp @@ -8,6 +8,10 @@ #pragma once +#if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) +#define IMMER_HAS_CPP17 1 +#endif + #if defined(__has_cpp_attribute) #if __has_cpp_attribute(nodiscard) #define IMMER_NODISCARD [[nodiscard]] @@ -66,6 +70,10 @@ #define IMMER_DEBUG_PRINT 0 #endif +#ifndef IMMER_DEBUG_STATS +#define IMMER_DEBUG_STATS 0 +#endif + #ifndef IMMER_DEBUG_DEEP_CHECK #define IMMER_DEBUG_DEEP_CHECK 0 #endif @@ -75,6 +83,10 @@ #include #endif +#if IMMER_DEBUG_STATS +#include +#endif + #if IMMER_DEBUG_TRACES #define IMMER_TRACE(...) std::cout << __VA_ARGS__ << std::endl #else diff --git a/immer/detail/arrays/node.hpp b/immer/detail/arrays/node.hpp index 2914bec94e..ac521e0335 100644 --- a/immer/detail/arrays/node.hpp +++ b/immer/detail/arrays/node.hpp @@ -61,7 +61,7 @@ struct node static void delete_n(node_t* p, size_t sz, size_t cap) { - destroy_n(p->data(), sz); + detail::destroy_n(p->data(), sz); heap::deallocate(sizeof_n(cap), p); } @@ -98,7 +98,7 @@ struct node { auto p = make_n(n); IMMER_TRY { - uninitialized_copy(first, last, p->data()); + detail::uninitialized_copy(first, last, p->data()); return p; } IMMER_CATCH (...) { diff --git a/immer/detail/arrays/with_capacity.hpp b/immer/detail/arrays/with_capacity.hpp index 610a16015b..c80f706821 100644 --- a/immer/detail/arrays/with_capacity.hpp +++ b/immer/detail/arrays/with_capacity.hpp @@ -178,17 +178,18 @@ struct with_capacity static size_t recommend_up(size_t sz, size_t cap) { auto max = std::numeric_limits::max(); - return sz <= cap ? cap - : cap >= max / 2 ? max - /* otherwise */ - : std::max(2 * cap, sz); + return sz <= cap ? cap + : cap >= max / 2 ? max + /* otherwise */ + : std::max(2 * cap, sz); } static size_t recommend_down(size_t sz, size_t cap) { - return sz == 0 ? 1 - : sz < cap / 2 ? sz * 2 : - /* otherwise */ cap; + return sz == 0 ? 1 + : sz < cap / 2 ? sz * 2 + : + /* otherwise */ cap; } with_capacity push_back(T value) const @@ -291,6 +292,7 @@ struct with_capacity with_capacity take(std::size_t sz) const { + assert(sz <= size); auto cap = recommend_down(sz, capacity); auto p = node_t::copy_n(cap, ptr, sz); return {p, sz, cap}; @@ -298,8 +300,9 @@ struct with_capacity void take_mut(edit_t e, std::size_t sz) { + assert(sz <= size); if (ptr->can_mutate(e)) { - destroy_n(data() + size, size - sz); + detail::destroy_n(data() + size, size - sz); size = sz; } else { auto cap = recommend_down(sz, capacity); diff --git a/immer/detail/hamts/bits.hpp b/immer/detail/hamts/bits.hpp index 430e23182d..0b4b923784 100644 --- a/immer/detail/hamts/bits.hpp +++ b/immer/detail/hamts/bits.hpp @@ -125,6 +125,51 @@ inline count_t popcount(std::uint8_t x) return popcount(static_cast(x)); } +template +class set_bits_range +{ + bitmap_t bitmap; + + class set_bits_iterator + { + bitmap_t bitmap; + + inline static bitmap_t clearlsbit(bitmap_t bitmap) + { + return bitmap & (bitmap - 1); + } + + inline static bitmap_t lsbit(bitmap_t bitmap) + { + return bitmap ^ clearlsbit(bitmap); + } + + public: + set_bits_iterator(bitmap_t bitmap) + : bitmap(bitmap){}; + + set_bits_iterator operator++() + { + bitmap = clearlsbit(bitmap); + return *this; + } + + bool operator!=(set_bits_iterator const& other) const + { + return bitmap != other.bitmap; + } + + bitmap_t operator*() const { return lsbit(bitmap); } + }; + +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); } +}; + } // namespace hamts } // namespace detail } // namespace immer diff --git a/immer/detail/hamts/champ.hpp b/immer/detail/hamts/champ.hpp index 77cf7e4776..64a8cb8248 100644 --- a/immer/detail/hamts/champ.hpp +++ b/immer/detail/hamts/champ.hpp @@ -17,6 +17,107 @@ namespace immer { namespace detail { namespace hamts { +#if IMMER_DEBUG_STATS +struct champ_debug_stats +{ + std::size_t bits = {}; + std::size_t value_size = {}; + std::size_t child_size = sizeof(void*); + + std::size_t inner_node_count = {}; + std::size_t inner_node_w_value_count = {}; + std::size_t inner_node_w_child_count = {}; + std::size_t collision_node_count = {}; + + std::size_t child_count = {}; + std::size_t value_count = {}; + std::size_t collision_count = {}; + + friend champ_debug_stats operator+(champ_debug_stats a, champ_debug_stats b) + { + if (a.bits != b.bits || a.value_size != b.value_size || + a.child_size != b.child_size) + throw std::runtime_error{"accumulating incompatible stats"}; + return { + a.bits, + a.value_size, + a.child_size, + a.inner_node_count + b.inner_node_count, + a.inner_node_w_value_count + b.inner_node_w_value_count, + a.inner_node_w_child_count + b.inner_node_w_child_count, + a.collision_node_count + b.collision_node_count, + a.child_count + b.child_count, + a.value_count + b.value_count, + a.collision_count + b.collision_count, + }; + } + + struct summary + { + double collision_ratio; + + double utilization; + double child_utilization; + double value_utilization; + + double dense_utilization; + double dense_value_utilization; + double dense_child_utilization; + + friend std::ostream& operator<<(std::ostream& os, const summary& s) + { + os << "---\n"; + os << "collisions\n" + << " ratio = " << s.collision_ratio << " %\n"; + os << "utilization\n" + << " total = " << s.utilization << " %\n" + << " children = " << s.child_utilization << " %\n" + << " values = " << s.value_utilization << " %\n"; + os << "utilization (dense)\n" + << " total = " << s.dense_utilization << " %\n" + << " children = " << s.dense_child_utilization << " %\n" + << " values = " << s.dense_value_utilization << " %\n"; + return os; + } + }; + + summary get_summary() const + { + auto m = std::size_t{1} << bits; + + auto collision_ratio = 100. * collision_count / value_count; + + auto capacity = m * inner_node_count; + auto child_utilization = 100. * child_count / capacity; + auto value_utilization = 100. * value_count / capacity; + auto utilization = + 100. * (value_count * value_size + child_count * child_size) / + (capacity * value_size + capacity * child_size); + + auto value_capacity = m * inner_node_w_value_count; + auto child_capacity = m * inner_node_w_child_count; + auto dense_child_utilization = + child_capacity == 0 ? 100. : 100. * child_count / child_capacity; + auto dense_value_utilization = + value_capacity == 0 ? 100. : 100. * value_count / value_capacity; + auto dense_utilization = + value_capacity + child_capacity == 0 + ? 100. + : 100. * (value_count * value_size + child_count * child_size) / + (value_capacity * value_size + + child_capacity * child_size); + + return {collision_ratio, + utilization, + child_utilization, + value_utilization, + dense_utilization, + dense_value_utilization, + dense_child_utilization}; + } +}; +#endif + template ; + using edit_t = typename MemoryPolicy::transience_t::edit; + using owner_t = typename MemoryPolicy::transience_t::owner; using bitmap_t = typename get_bitmap_type::type; static_assert(branches <= sizeof(bitmap_t) * 8, ""); @@ -87,6 +190,117 @@ struct champ node_t::delete_deep(root, 0); } + std::size_t do_check_champ(node_t* node, + count_t depth, + size_t path_hash, + size_t hash_mask) const + { + auto result = std::size_t{}; + if (depth < max_depth) { + auto nodemap = node->nodemap(); + if (nodemap) { + auto fst = node->children(); + for (auto idx = std::size_t{}; idx < branches; ++idx) { + if (nodemap & (1 << idx)) { + auto child = *fst++; + result += + do_check_champ(child, + depth + 1, + path_hash | (idx << (B * depth)), + (hash_mask << B) | mask); + } + } + } + auto datamap = node->datamap(); + if (datamap) { + auto fst = node->values(); + for (auto idx = std::size_t{}; idx < branches; ++idx) { + if (datamap & (1 << idx)) { + auto hash = Hash{}(*fst++); + auto check = (hash & hash_mask) == + (path_hash | (idx << (B * depth))); + // assert(check); + result += !!check; + } + } + } + } else { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) { + auto hash = Hash{}(*fst); + auto check = hash == path_hash; + // assert(check); + result += !!check; + } + } + return result; + } + + // Checks that the hashes of the values correspond with what has actually + // been inserted. If it doesn't it can mean that corruption has happened + // due some value being moved out of the champ when it should have not. + bool check_champ() const + { + auto r = do_check_champ(root, 0, 0, mask); + // assert(r == size); + return r == size; + } + +#if IMMER_DEBUG_STATS + void do_get_debug_stats(champ_debug_stats& stats, + node_t* node, + count_t depth) const + { + if (depth < max_depth) { + ++stats.inner_node_count; + stats.inner_node_w_value_count += node->data_count() > 0; + stats.inner_node_w_child_count += node->children_count() > 0; + stats.value_count += node->data_count(); + stats.child_count += node->children_count(); + auto nodemap = node->nodemap(); + if (nodemap) { + auto fst = node->children(); + auto lst = fst + node->children_count(); + for (; fst != lst; ++fst) + do_get_debug_stats(stats, *fst, depth + 1); + } + } else { + ++stats.collision_node_count; + stats.collision_count += node->collision_count(); + } + } + + champ_debug_stats get_debug_stats() const + { + auto stats = champ_debug_stats{B, sizeof(T)}; + do_get_debug_stats(stats, root, 0); + return stats; + } +#endif + + template + static auto from_initializer_list(std::initializer_list values) + { + auto e = owner_t{}; + auto result = champ{empty()}; + for (auto&& v : values) + result.add_mut(e, v); + return result; + } + + template , bool> = true> + static auto from_range(Iter first, Sent last) + { + auto e = owner_t{}; + auto result = champ{empty()}; + for (; first != last; ++first) + result.add_mut(e, *first); + return result; + } + template void for_each_chunk(Fn&& fn) const { @@ -94,7 +308,8 @@ struct champ } template - void for_each_chunk_traversal(node_t* node, count_t depth, Fn&& fn) const + void + for_each_chunk_traversal(const node_t* node, count_t depth, Fn&& fn) const { if (depth < max_depth) { auto datamap = node->datamap(); @@ -113,6 +328,204 @@ struct champ } } + template + void diff(const champ& new_champ, Differ&& differ) const + { + diff(root, new_champ.root, 0, std::forward(differ)); + } + + template + void diff(const node_t* old_node, + const node_t* new_node, + count_t depth, + Differ&& differ) const + { + if (old_node == new_node) + return; + if (depth < max_depth) { + auto old_nodemap = old_node->nodemap(); + auto new_nodemap = new_node->nodemap(); + auto old_datamap = old_node->datamap(); + auto new_datamap = new_node->datamap(); + auto old_bits = old_nodemap | old_datamap; + auto new_bits = new_nodemap | new_datamap; + auto changes = old_bits ^ new_bits; + + // added bits + for (auto bit : set_bits_range(new_bits & changes)) { + if (new_nodemap & bit) { + auto offset = new_node->children_count(bit); + auto child = new_node->children()[offset]; + for_each_chunk_traversal( + child, + depth + 1, + [&](auto const& begin, auto const& end) { + for (auto it = begin; it != end; it++) + differ.added(*it); + }); + } else if (new_datamap & bit) { + auto offset = new_node->data_count(bit); + auto const& value = new_node->values()[offset]; + differ.added(value); + } + } + + // removed bits + for (auto bit : set_bits_range(old_bits & changes)) { + if (old_nodemap & bit) { + auto offset = old_node->children_count(bit); + auto child = old_node->children()[offset]; + for_each_chunk_traversal( + child, + depth + 1, + [&](auto const& begin, auto const& end) { + for (auto it = begin; it != end; it++) + differ.removed(*it); + }); + } else if (old_datamap & bit) { + auto offset = old_node->data_count(bit); + auto const& value = old_node->values()[offset]; + differ.removed(value); + } + } + + // bits in both nodes + for (auto bit : set_bits_range(old_bits & new_bits)) { + if ((old_nodemap & bit) && (new_nodemap & bit)) { + auto old_offset = old_node->children_count(bit); + auto new_offset = new_node->children_count(bit); + auto old_child = old_node->children()[old_offset]; + auto new_child = new_node->children()[new_offset]; + diff(old_child, new_child, depth + 1, differ); + } else if ((old_datamap & bit) && (new_nodemap & bit)) { + diff_data_node( + old_node, new_node, bit, depth, differ); + } else if ((old_nodemap & bit) && (new_datamap & bit)) { + diff_node_data( + old_node, new_node, bit, depth, differ); + } else if ((old_datamap & bit) && (new_datamap & bit)) { + diff_data_data(old_node, new_node, bit, differ); + } + } + } else { + diff_collisions(old_node, new_node, differ); + } + } + + template + void diff_data_node(const node_t* old_node, + const node_t* new_node, + bitmap_t bit, + count_t depth, + Differ&& differ) const + { + auto old_offset = old_node->data_count(bit); + auto const& old_value = old_node->values()[old_offset]; + auto new_offset = new_node->children_count(bit); + auto new_child = new_node->children()[new_offset]; + + bool found = false; + for_each_chunk_traversal( + new_child, depth + 1, [&](auto const& begin, auto const& end) { + for (auto it = begin; it != end; it++) { + if (Equal{}(old_value, *it)) { + if (!EqualValue{}(old_value, *it)) + differ.changed(old_value, *it); + found = true; + } else { + differ.added(*it); + } + } + }); + if (!found) + differ.removed(old_value); + } + + template + void diff_node_data(const node_t* old_node, + const node_t* const new_node, + bitmap_t bit, + count_t depth, + Differ&& differ) const + { + auto old_offset = old_node->children_count(bit); + auto old_child = old_node->children()[old_offset]; + auto new_offset = new_node->data_count(bit); + auto const& new_value = new_node->values()[new_offset]; + + bool found = false; + for_each_chunk_traversal( + old_child, depth + 1, [&](auto const& begin, auto const& end) { + for (auto it = begin; it != end; it++) { + if (Equal{}(*it, new_value)) { + if (!EqualValue{}(*it, new_value)) + differ.changed(*it, new_value); + found = true; + } else { + differ.removed(*it); + } + } + }); + if (!found) + differ.added(new_value); + } + + template + void diff_data_data(const node_t* old_node, + const node_t* new_node, + bitmap_t bit, + Differ&& differ) const + { + auto old_offset = old_node->data_count(bit); + auto new_offset = new_node->data_count(bit); + auto const& old_value = old_node->values()[old_offset]; + auto const& new_value = new_node->values()[new_offset]; + if (!Equal{}(old_value, new_value)) { + differ.removed(old_value); + differ.added(new_value); + } else { + if (!EqualValue{}(old_value, new_value)) + differ.changed(old_value, new_value); + } + } + + template + void diff_collisions(const node_t* old_node, + const node_t* new_node, + Differ&& differ) const + { + auto old_begin = old_node->collisions(); + auto old_end = old_node->collisions() + old_node->collision_count(); + auto new_begin = new_node->collisions(); + auto new_end = new_node->collisions() + new_node->collision_count(); + // search changes and removals + for (auto old_it = old_begin; old_it != old_end; old_it++) { + bool found = false; + for (auto new_it = new_begin; new_it != new_end; new_it++) { + if (Equal{}(*old_it, *new_it)) { + if (!EqualValue{}(*old_it, *new_it)) + differ.changed(*old_it, *new_it); + found = true; + break; + } + } + if (!found) + differ.removed(*old_it); + } + // search new entries + for (auto new_it = new_begin; new_it != new_end; new_it++) { + bool found = false; + for (auto old_it = old_begin; old_it != old_end; old_it++) { + if (Equal{}(*old_it, *new_it)) { + found = true; + break; + } + } + if (!found) + differ.added(*new_it); + } + } + template decltype(auto) get(const K& k) const { @@ -143,8 +556,13 @@ struct champ return Default{}(); } - std::pair - do_add(node_t* node, T v, hash_t hash, shift_t shift) const + struct add_result + { + node_t* node; + bool added; + }; + + add_result do_add(node_t* node, T v, hash_t hash, shift_t shift) const { assert(node); if (shift == max_shift) { @@ -165,12 +583,12 @@ struct champ auto result = do_add( node->children()[offset], std::move(v), hash, shift + B); IMMER_TRY { - result.first = - node_t::copy_inner_replace(node, offset, result.first); + result.node = + node_t::copy_inner_replace(node, offset, result.node); return result; } IMMER_CATCH (...) { - node_t::delete_deep_shift(result.first, shift + B); + node_t::delete_deep_shift(result.node, shift + B); IMMER_RETHROW; } } else if (node->datamap() & bit) { @@ -205,16 +623,131 @@ struct champ { auto hash = Hash{}(v); auto res = do_add(root, std::move(v), hash, 0); - auto new_size = size + (res.second ? 1 : 0); - return {res.first, new_size}; + auto new_size = size + (res.added ? 1 : 0); + return {res.node, new_size}; } + struct add_mut_result + { + node_t* node; + bool added; + bool mutated; + }; + + add_mut_result + do_add_mut(edit_t e, node_t* node, T v, hash_t hash, shift_t shift) const + { + assert(node); + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, v)) { + if (node->can_mutate(e)) { + *fst = std::move(v); + return {node, false, true}; + } else { + auto r = node_t::copy_collision_replace( + node, fst, std::move(v)); + return {node_t::owned(r, e), false, false}; + } + } + auto mutate = node->can_mutate(e); + auto r = mutate ? node_t::move_collision_insert(node, std::move(v)) + : node_t::copy_collision_insert(node, std::move(v)); + return {node_t::owned(r, e), true, mutate}; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = node->children_count(bit); + auto child = node->children()[offset]; + if (node->can_mutate(e)) { + auto result = + do_add_mut(e, child, std::move(v), hash, shift + B); + node->children()[offset] = result.node; + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); + return {node, result.added, true}; + } else { + assert(node->children()[offset]); + auto result = do_add(child, std::move(v), hash, shift + B); + IMMER_TRY { + result.node = node_t::copy_inner_replace( + node, offset, result.node); + node_t::owned(result.node, e); + return {result.node, result.added, false}; + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(result.node, shift + B); + IMMER_RETHROW; + } + } + } else if (node->datamap() & bit) { + auto offset = node->data_count(bit); + auto val = node->values() + offset; + if (Equal{}(*val, v)) { + if (node->can_mutate(e)) { + auto vals = node->ensure_mutable_values(e); + vals[offset] = std::move(v); + return {node, false, true}; + } else { + auto r = node_t::copy_inner_replace_value( + node, offset, std::move(v)); + return {node_t::owned_values(r, e), false, false}; + } + } else { + auto mutate = node->can_mutate(e); + auto mutate_values = mutate && node->can_mutate_values(e); + auto hash2 = Hash{}(*val); + auto child = node_t::make_merged_e( + e, + shift + B, + std::move(v), + hash, + mutate_values ? std::move(*val) : *val, + hash2); + IMMER_TRY { + auto r = mutate ? node_t::move_inner_replace_merged( + e, node, bit, offset, child) + : node_t::copy_inner_replace_merged( + node, bit, offset, child); + return {node_t::owned_values_safe(r, e), true, mutate}; + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(child, shift + B); + IMMER_RETHROW; + } + } + } else { + auto mutate = node->can_mutate(e); + auto r = mutate ? node_t::move_inner_insert_value( + e, node, bit, std::move(v)) + : node_t::copy_inner_insert_value( + node, bit, std::move(v)); + return {node_t::owned_values(r, e), true, mutate}; + } + } + } + + void add_mut(edit_t e, T v) + { + auto hash = Hash{}(v); + auto res = do_add_mut(e, root, std::move(v), hash, 0); + if (!res.mutated && root->dec()) + node_t::delete_deep(root, 0); + root = res.node; + size += res.added ? 1 : 0; + } + + using update_result = add_result; + template - std::pair + update_result do_update(node_t* node, K&& k, Fn&& fn, hash_t hash, shift_t shift) const { if (shift == max_shift) { @@ -222,13 +755,13 @@ struct champ auto lst = fst + node->collision_count(); for (; fst != lst; ++fst) if (Equal{}(*fst, k)) - return { - node_t::copy_collision_replace( - node, - fst, - Combine{}(std::forward(k), - std::forward(fn)(Project{}(*fst)))), - false}; + return {node_t::copy_collision_replace( + node, + fst, + Combine{}(std::forward(k), + std::forward(fn)(Project{}( + detail::as_const(*fst))))), + false}; return {node_t::copy_collision_insert( node, Combine{}(std::forward(k), @@ -246,25 +779,25 @@ struct champ hash, shift + B); IMMER_TRY { - result.first = - node_t::copy_inner_replace(node, offset, result.first); + result.node = + node_t::copy_inner_replace(node, offset, result.node); return result; } IMMER_CATCH (...) { - node_t::delete_deep_shift(result.first, shift + B); + node_t::delete_deep_shift(result.node, shift + B); IMMER_RETHROW; } } else if (node->datamap() & bit) { auto offset = node->data_count(bit); auto val = node->values() + offset; if (Equal{}(*val, k)) - return { - node_t::copy_inner_replace_value( - node, - offset, - Combine{}(std::forward(k), - std::forward(fn)(Project{}(*val)))), - false}; + return {node_t::copy_inner_replace_value( + node, + offset, + Combine{}(std::forward(k), + std::forward(fn)(Project{}( + detail::as_const(*val))))), + false}; else { auto child = node_t::make_merged( shift + B, @@ -304,8 +837,326 @@ struct champ auto hash = Hash{}(k); auto res = do_update( root, k, std::forward(fn), hash, 0); - auto new_size = size + (res.second ? 1 : 0); - return {res.first, new_size}; + auto new_size = size + (res.added ? 1 : 0); + return {res.node, new_size}; + } + + template + node_t* do_update_if_exists( + node_t* node, K&& k, Fn&& fn, hash_t hash, shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, k)) + return node_t::copy_collision_replace( + node, + fst, + Combine{}(std::forward(k), + std::forward(fn)( + Project{}(detail::as_const(*fst))))); + return nullptr; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = node->children_count(bit); + auto result = do_update_if_exists( + node->children()[offset], + k, + std::forward(fn), + hash, + shift + B); + IMMER_TRY { + return result ? node_t::copy_inner_replace( + node, offset, result) + : nullptr; + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(result, shift + B); + IMMER_RETHROW; + } + } else if (node->datamap() & bit) { + auto offset = node->data_count(bit); + auto val = node->values() + offset; + if (Equal{}(*val, k)) + return node_t::copy_inner_replace_value( + node, + offset, + Combine{}(std::forward(k), + std::forward(fn)( + Project{}(detail::as_const(*val))))); + else { + return nullptr; + } + } else { + return nullptr; + } + } + } + + template + champ update_if_exists(const K& k, Fn&& fn) const + { + auto hash = Hash{}(k); + auto res = do_update_if_exists( + root, k, std::forward(fn), hash, 0); + if (res) { + return {res, size}; + } else { + return {root->inc(), size}; + }; + } + + using update_mut_result = add_mut_result; + + template + update_mut_result do_update_mut(edit_t e, + node_t* node, + K&& k, + Fn&& fn, + hash_t hash, + shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, k)) { + if (node->can_mutate(e)) { + *fst = Combine{}( + std::forward(k), + std::forward(fn)(Project{}(std::move(*fst)))); + return {node, false, true}; + } else { + auto r = node_t::copy_collision_replace( + node, + fst, + Combine{}(std::forward(k), + std::forward(fn)( + Project{}(detail::as_const(*fst))))); + return {node_t::owned(r, e), false, false}; + } + } + auto v = Combine{}(std::forward(k), + std::forward(fn)(Default{}())); + auto mutate = node->can_mutate(e); + auto r = mutate ? node_t::move_collision_insert(node, std::move(v)) + : node_t::copy_collision_insert(node, std::move(v)); + return {node_t::owned(r, e), true, mutate}; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = node->children_count(bit); + auto child = node->children()[offset]; + if (node->can_mutate(e)) { + auto result = do_update_mut( + e, child, k, std::forward(fn), hash, shift + B); + node->children()[offset] = result.node; + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); + return {node, result.added, true}; + } else { + auto result = do_update( + child, k, std::forward(fn), hash, shift + B); + IMMER_TRY { + result.node = node_t::copy_inner_replace( + node, offset, result.node); + node_t::owned(result.node, e); + return {result.node, result.added, false}; + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(result.node, shift + B); + IMMER_RETHROW; + } + } + } else if (node->datamap() & bit) { + auto offset = node->data_count(bit); + auto val = node->values() + offset; + if (Equal{}(*val, k)) { + if (node->can_mutate(e)) { + auto vals = node->ensure_mutable_values(e); + vals[offset] = Combine{}(std::forward(k), + std::forward(fn)(Project{}( + std::move(vals[offset])))); + return {node, false, true}; + } else { + auto r = node_t::copy_inner_replace_value( + node, + offset, + Combine{}(std::forward(k), + std::forward(fn)( + Project{}(detail::as_const(*val))))); + return {node_t::owned_values(r, e), false, false}; + } + } else { + auto mutate = node->can_mutate(e); + auto mutate_values = mutate && node->can_mutate_values(e); + auto hash2 = Hash{}(*val); + auto child = node_t::make_merged_e( + e, + shift + B, + Combine{}(std::forward(k), + std::forward(fn)(Default{}())), + hash, + mutate_values ? std::move(*val) : *val, + hash2); + IMMER_TRY { + auto r = mutate ? node_t::move_inner_replace_merged( + e, node, bit, offset, child) + : node_t::copy_inner_replace_merged( + node, bit, offset, child); + return {node_t::owned_values_safe(r, e), true, mutate}; + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(child, shift + B); + IMMER_RETHROW; + } + } + } else { + auto mutate = node->can_mutate(e); + auto v = Combine{}(std::forward(k), + std::forward(fn)(Default{}())); + auto r = mutate ? node_t::move_inner_insert_value( + e, node, bit, std::move(v)) + : node_t::copy_inner_insert_value( + node, bit, std::move(v)); + return {node_t::owned_values(r, e), true, mutate}; + } + } + } + + template + void update_mut(edit_t e, const K& k, Fn&& fn) + { + auto hash = Hash{}(k); + auto res = do_update_mut( + e, root, k, std::forward(fn), hash, 0); + if (!res.mutated && root->dec()) + node_t::delete_deep(root, 0); + root = res.node; + size += res.added ? 1 : 0; + } + + struct update_if_exists_mut_result + { + node_t* node; + bool mutated; + }; + + template + update_if_exists_mut_result do_update_if_exists_mut(edit_t e, + node_t* node, + K&& k, + Fn&& fn, + hash_t hash, + shift_t shift) const + { + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (; fst != lst; ++fst) + if (Equal{}(*fst, k)) { + if (node->can_mutate(e)) { + *fst = Combine{}( + std::forward(k), + std::forward(fn)(Project{}(std::move(*fst)))); + return {node, true}; + } else { + auto r = node_t::copy_collision_replace( + node, + fst, + Combine{}(std::forward(k), + std::forward(fn)( + Project{}(detail::as_const(*fst))))); + return {node_t::owned(r, e), false}; + } + } + return {nullptr, false}; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = node->children_count(bit); + auto child = node->children()[offset]; + if (node->can_mutate(e)) { + auto result = do_update_if_exists_mut( + e, child, k, std::forward(fn), hash, shift + B); + if (result.node) { + node->children()[offset] = result.node; + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); + return {node, true}; + } else { + return {nullptr, false}; + } + } else { + auto result = do_update_if_exists( + child, k, std::forward(fn), hash, shift + B); + IMMER_TRY { + if (result) { + result = node_t::copy_inner_replace( + node, offset, result); + node_t::owned(result, e); + return {result, false}; + } else { + return {nullptr, false}; + } + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(result, shift + B); + IMMER_RETHROW; + } + } + } else if (node->datamap() & bit) { + auto offset = node->data_count(bit); + auto val = node->values() + offset; + if (Equal{}(*val, k)) { + if (node->can_mutate(e)) { + auto vals = node->ensure_mutable_values(e); + vals[offset] = Combine{}(std::forward(k), + std::forward(fn)(Project{}( + std::move(vals[offset])))); + return {node, true}; + } else { + auto r = node_t::copy_inner_replace_value( + node, + offset, + Combine{}(std::forward(k), + std::forward(fn)( + Project{}(detail::as_const(*val))))); + return {node_t::owned_values(r, e), false}; + } + } else { + return {nullptr, false}; + } + } else { + return {nullptr, false}; + } + } + } + + template + void update_if_exists_mut(edit_t e, const K& k, Fn&& fn) + { + auto hash = Hash{}(k); + auto res = do_update_if_exists_mut( + e, root, k, std::forward(fn), hash, 0); + if (res.node) { + if (!res.mutated && root->dec()) + node_t::delete_deep(root, 0); + root = res.node; + } } // basically: @@ -355,7 +1206,20 @@ struct champ return node->collision_count() > 2 ? node_t::copy_collision_remove(node, cur) : sub_result{fst + (cur == fst)}; +#if !defined(_MSC_VER) +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif +#endif + // Apparently GCC is generating this warning sometimes when + // compiling the benchmarks. It makes however no sense at all. return {}; +#if !defined(_MSC_VER) +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif +#endif } else { auto idx = (hash & (mask << shift)) >> shift; auto bit = bitmap_t{1u} << idx; @@ -421,6 +1285,213 @@ struct champ } } + struct sub_result_mut + { + using kind_t = typename sub_result::kind_t; + using data_t = typename sub_result::data_t; + + kind_t kind; + data_t data; + bool owned; + bool mutated; + + sub_result_mut(sub_result a) + : kind{a.kind} + , 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} {}; + sub_result_mut(T* x, bool m) + : kind{kind_t::singleton} + , owned{m} + , mutated{m} + { + data.singleton = x; + }; + sub_result_mut(T* x, bool o, bool m) + : kind{kind_t::singleton} + , owned{o} + , mutated{m} + { + data.singleton = x; + }; + sub_result_mut(node_t* x, bool m) + : kind{kind_t::tree} + , owned{false} + , mutated{m} + { + data.tree = x; + }; + }; + + template + sub_result_mut do_sub_mut(edit_t e, + node_t* node, + const K& k, + hash_t hash, + shift_t shift, + void* store) const + { + auto mutate = node->can_mutate(e); + if (shift == max_shift) { + auto fst = node->collisions(); + auto lst = fst + node->collision_count(); + for (auto cur = fst; cur != lst; ++cur) { + if (Equal{}(*cur, k)) { + if (node->collision_count() <= 2) { + if (mutate) { + auto r = new (store) + T{std::move(node->collisions()[cur == fst])}; + node_t::delete_collision(node); + return sub_result_mut{r, true}; + } else { + return sub_result_mut{fst + (cur == fst), false}; + } + } else { + auto r = mutate + ? node_t::move_collision_remove(node, cur) + : node_t::copy_collision_remove(node, cur); + return {node_t::owned(r, e), mutate}; + } + } + } + return {}; + } else { + auto idx = (hash & (mask << shift)) >> shift; + auto bit = bitmap_t{1u} << idx; + if (node->nodemap() & bit) { + auto offset = node->children_count(bit); + auto children = node->children(); + auto child = children[offset]; + auto result = + mutate ? do_sub_mut(e, child, k, hash, shift + B, store) + : do_sub(child, k, hash, shift + B); + switch (result.kind) { + case sub_result::nothing: + return {}; + case sub_result::singleton: + if (node->datamap() == 0 && node->children_count() == 1 && + shift > 0) { + if (mutate) { + node_t::delete_inner(node); + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); + } + return {result.data.singleton, result.owned, mutate}; + } else { + auto r = + mutate ? node_t::move_inner_replace_inline( + e, + node, + bit, + offset, + result.owned + ? std::move(*result.data.singleton) + : *result.data.singleton) + : node_t::copy_inner_replace_inline( + node, + bit, + offset, + *result.data.singleton); + if (result.owned) + detail::destroy_at(result.data.singleton); + if (!result.mutated && mutate && child->dec()) + node_t::delete_deep_shift(child, shift + B); + return {node_t::owned_values(r, e), mutate}; + } + case sub_result::tree: + if (mutate) { + children[offset] = result.data.tree; + if (!result.mutated && child->dec()) + node_t::delete_deep_shift(child, shift + B); + return {node, true}; + } else { + IMMER_TRY { + auto r = node_t::copy_inner_replace( + node, offset, result.data.tree); + return {node_t::owned(r, e), false}; + } + IMMER_CATCH (...) { + node_t::delete_deep_shift(result.data.tree, + shift + B); + IMMER_RETHROW; + } + } + } + } else if (node->datamap() & bit) { + auto offset = node->data_count(bit); + auto val = node->values() + offset; + auto mutate_values = mutate && node->can_mutate_values(e); + if (Equal{}(*val, k)) { + auto nv = node->data_count(); + if (node->nodemap() || nv > 2) { + auto r = mutate ? node_t::move_inner_remove_value( + e, node, bit, offset) + : node_t::copy_inner_remove_value( + node, bit, offset); + return {node_t::owned_values_safe(r, e), mutate}; + } else if (nv == 2) { + if (shift > 0) { + if (mutate_values) { + auto r = new (store) + T{std::move(node->values()[!offset])}; + node_t::delete_inner(node); + return {r, true}; + } else { + return {node->values() + !offset, false}; + } + } else { + auto& v = node->values()[!offset]; + auto r = node_t::make_inner_n( + 0, + node->datamap() & ~bit, + mutate_values ? std::move(v) : v); + assert(!node->nodemap()); + if (mutate) + node_t::delete_inner(node); + return {node_t::owned_values(r, e), mutate}; + } + } else { + assert(shift == 0); + if (mutate) + node_t::delete_inner(node); + return {empty(), mutate}; + } + } + } + return {}; + } + } + + template + void sub_mut(edit_t e, const K& k) + { + auto store = aligned_storage_for{}; + auto hash = Hash{}(k); + auto res = do_sub_mut(e, root, k, hash, 0, &store); + switch (res.kind) { + case sub_result::nothing: + break; + case sub_result::tree: + if (!res.mutated && root->dec()) + node_t::delete_deep(root, 0); + root = res.data.tree; + --size; + break; + default: + IMMER_UNREACHABLE; + } + } + template bool equals(const champ& other) const { diff --git a/immer/detail/hamts/champ_iterator.hpp b/immer/detail/hamts/champ_iterator.hpp index 373198bb03..5ff3106024 100644 --- a/immer/detail/hamts/champ_iterator.hpp +++ b/immer/detail/hamts/champ_iterator.hpp @@ -27,6 +27,8 @@ struct champ_iterator using tree_t = champ; using node_t = typename tree_t::node_t; + champ_iterator() = default; + struct end_t {}; diff --git a/immer/detail/hamts/node.hpp b/immer/detail/hamts/node.hpp index e2607f013b..7f7dc8b1fe 100644 --- a/immer/detail/hamts/node.hpp +++ b/immer/detail/hamts/node.hpp @@ -20,6 +20,11 @@ namespace immer { namespace detail { 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 buffer; }; - using values_t = combine_standard_layout_t; + using values_t = combine_standard_layout_t; struct inner_t { @@ -80,7 +85,7 @@ struct node data_t data; }; - using impl_t = combine_standard_layout_t; + using impl_t = combine_standard_layout_t; impl_t impl; @@ -193,6 +198,10 @@ struct node } static const ownee_t& ownee(const values_t* x) { return get(*x); } static ownee_t& ownee(values_t* x) { return get(*x); } + static bool can_mutate(values_t* x, edit_t e) + { + return refs(x).unique() || ownee(x).can_mutate(e); + } static refs_t& refs(const node_t* x) { @@ -204,6 +213,15 @@ struct node } static ownee_t& ownee(node_t* x) { return get(x->impl); } + bool can_mutate(edit_t e) const + { + return refs(this).unique() || ownee(this).can_mutate(e); + } + bool can_mutate_values(edit_t e) const + { + return can_mutate(impl.d.data.inner.values, e); + } + static node_t* make_inner_n(count_t n) { assert(n <= branches); @@ -284,7 +302,7 @@ struct node new (vp + 1) T{std::move(x2)}; } IMMER_CATCH (...) { - vp->~T(); + immer::detail::hamts::destroy_at(vp); IMMER_RETHROW; } } @@ -337,6 +355,32 @@ struct node return p; } + T* ensure_mutable_values(edit_t e) + { + assert(can_mutate(e)); + auto old = impl.d.data.inner.values; + if (node_t::can_mutate(old, e)) + return values(); + else { + auto nv = data_count(); + auto nxt = new (heap::allocate(sizeof_values_n(nv))) values_t{}; + auto dst = (T*) &nxt->d.buffer; + auto src = values(); + ownee(nxt) = e; + IMMER_TRY { + detail::uninitialized_copy(src, src + nv, dst); + } + IMMER_CATCH (...) { + deallocate_values(nxt, nv); + IMMER_RETHROW; + } + impl.d.data.inner.values = nxt; + if (refs(old).dec()) + delete_values(old, nv); + return dst; + } + } + static node_t* copy_collision_insert(node_t* src, T v) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision); @@ -347,7 +391,7 @@ struct node IMMER_TRY { new (dstp) T{std::move(v)}; IMMER_TRY { - std::uninitialized_copy(srcp, srcp + n, dstp + 1); + detail::uninitialized_copy(srcp, srcp + n, dstp + 1); } IMMER_CATCH (...) { dstp->~T(); @@ -361,6 +405,31 @@ struct node return dst; } + static node_t* move_collision_insert(node_t* src, T v) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision); + auto n = src->collision_count(); + auto dst = make_collision_n(n + 1); + auto srcp = src->collisions(); + auto dstp = dst->collisions(); + IMMER_TRY { + new (dstp) T{std::move(v)}; + IMMER_TRY { + detail::uninitialized_move(srcp, srcp + n, dstp + 1); + } + IMMER_CATCH (...) { + dstp->~T(); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_collision(dst, n + 1); + IMMER_RETHROW; + } + delete_collision(src); + return dst; + } + static node_t* copy_collision_remove(node_t* src, T* v) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision); @@ -370,12 +439,12 @@ struct node auto srcp = src->collisions(); auto dstp = dst->collisions(); IMMER_TRY { - dstp = std::uninitialized_copy(srcp, v, dstp); + dstp = detail::uninitialized_copy(srcp, v, dstp); IMMER_TRY { - std::uninitialized_copy(v + 1, srcp + n, dstp); + detail::uninitialized_copy(v + 1, srcp + n, dstp); } IMMER_CATCH (...) { - destroy(dst->collisions(), dstp); + detail::destroy(dst->collisions(), dstp); IMMER_RETHROW; } } @@ -386,6 +455,32 @@ struct node return dst; } + static node_t* move_collision_remove(node_t* src, T* v) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision); + assert(src->collision_count() > 1); + auto n = src->collision_count(); + auto dst = make_collision_n(n - 1); + auto srcp = src->collisions(); + auto dstp = dst->collisions(); + IMMER_TRY { + dstp = detail::uninitialized_move(srcp, v, dstp); + IMMER_TRY { + detail::uninitialized_move(v + 1, srcp + n, dstp); + } + IMMER_CATCH (...) { + detail::destroy(dst->collisions(), dstp); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_collision(dst, n - 1); + IMMER_RETHROW; + } + delete_collision(src); + return dst; + } + static node_t* copy_collision_replace(node_t* src, T* pos, T v) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::collision); @@ -397,12 +492,12 @@ struct node IMMER_TRY { new (dstp) T{std::move(v)}; IMMER_TRY { - dstp = std::uninitialized_copy(srcp, pos, dstp + 1); + dstp = detail::uninitialized_copy(srcp, pos, dstp + 1); IMMER_TRY { - std::uninitialized_copy(pos + 1, srcp + n, dstp); + detail::uninitialized_copy(pos + 1, srcp + n, dstp); } IMMER_CATCH (...) { - destroy(dst->collisions(), dstp); + detail::destroy(dst->collisions(), dstp); IMMER_RETHROW; } } @@ -428,13 +523,34 @@ struct node auto dstp = dst->children(); dst->impl.d.data.inner.datamap = src->datamap(); dst->impl.d.data.inner.nodemap = src->nodemap(); - std::uninitialized_copy(srcp, srcp + n, dstp); - inc_nodes(srcp, n); - srcp[offset]->dec_unsafe(); + std::copy(srcp, srcp + n, dstp); + inc_nodes(srcp, offset); + inc_nodes(srcp + offset + 1, n - offset - 1); dstp[offset] = child; return dst; } + static node_t* owned(node_t* n, edit_t e) + { + ownee(n) = e; + return n; + } + + static node_t* owned_values(node_t* n, edit_t e) + { + ownee(n) = e; + ownee(n->impl.d.data.inner.values) = e; + return n; + } + + static node_t* owned_values_safe(node_t* n, edit_t e) + { + ownee(n) = e; + if (n->impl.d.data.inner.values) + ownee(n->impl.d.data.inner.values) = e; + return n; + } + static node_t* copy_inner_replace_value(node_t* src, count_t offset, T v) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); @@ -445,13 +561,13 @@ struct node dst->impl.d.data.inner.datamap = src->datamap(); dst->impl.d.data.inner.nodemap = src->nodemap(); IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + nv, dst->values()); IMMER_TRY { dst->values()[offset] = std::move(v); } IMMER_CATCH (...) { - destroy_n(dst->values(), nv); + detail::destroy_n(dst->values(), nv); IMMER_RETHROW; } } @@ -460,8 +576,7 @@ struct node IMMER_RETHROW; } inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + n, dst->children()); + std::copy(src->children(), src->children() + n, dst->children()); return dst; } @@ -482,15 +597,15 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap() | bit; if (nv > 1) { IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { - std::uninitialized_copy(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + detail::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); } IMMER_CATCH (...) { - destroy_n(dst->values(), voffset); + detail::destroy_n(dst->values(), voffset); IMMER_RETHROW; } } @@ -500,15 +615,66 @@ struct node } } inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + noffset, dst->children()); - std::uninitialized_copy(src->children() + noffset, - src->children() + n, - dst->children() + noffset + 1); + std::copy(src->children(), src->children() + noffset, dst->children()); + std::copy(src->children() + noffset, + src->children() + n, + dst->children() + noffset + 1); dst->children()[noffset] = node; return dst; } + static node_t* move_inner_replace_merged( + edit_t e, node_t* src, bitmap_t bit, count_t voffset, node_t* node) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + assert(!(src->nodemap() & bit)); + assert(src->datamap() & bit); + assert(voffset == src->data_count(bit)); + auto n = src->children_count(); + auto nv = src->data_count(); + auto dst = make_inner_n(n + 1, nv - 1); + auto noffset = src->children_count(bit); + dst->impl.d.data.inner.datamap = src->datamap() & ~bit; + dst->impl.d.data.inner.nodemap = src->nodemap() | bit; + if (nv > 1) { + auto mutate_values = can_mutate(src->impl.d.data.inner.values, e); + IMMER_TRY { + if (mutate_values) + detail::uninitialized_move( + src->values(), src->values() + voffset, dst->values()); + else + detail::uninitialized_copy( + src->values(), src->values() + voffset, dst->values()); + IMMER_TRY { + if (mutate_values) + detail::uninitialized_move(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); + else + detail::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); + } + IMMER_CATCH (...) { + detail::destroy_n(dst->values(), voffset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n + 1, nv - 1); + IMMER_RETHROW; + } + } + // inc_nodes(src->children(), n); + std::copy(src->children(), src->children() + noffset, dst->children()); + std::copy(src->children() + noffset, + src->children() + n, + dst->children() + noffset + 1); + dst->children()[noffset] = node; + delete_inner(src); + return dst; + } + static node_t* copy_inner_replace_inline(node_t* src, bitmap_t bit, count_t noffset, @@ -526,15 +692,15 @@ struct node dst->impl.d.data.inner.datamap = src->datamap() | bit; IMMER_TRY { if (nv) - std::uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { new (dst->values() + voffset) T{std::move(value)}; IMMER_TRY { if (nv) - std::uninitialized_copy(src->values() + voffset, - src->values() + nv, - dst->values() + voffset + 1); + detail::uninitialized_copy(src->values() + voffset, + src->values() + nv, + dst->values() + voffset + 1); } IMMER_CATCH (...) { dst->values()[voffset].~T(); @@ -542,7 +708,7 @@ struct node } } IMMER_CATCH (...) { - destroy_n(dst->values(), voffset); + detail::destroy_n(dst->values(), voffset); IMMER_RETHROW; } } @@ -550,13 +716,74 @@ struct node deallocate_inner(dst, n - 1, nv + 1); IMMER_RETHROW; } - inc_nodes(src->children(), n); - src->children()[noffset]->dec_unsafe(); - std::uninitialized_copy( - src->children(), src->children() + noffset, dst->children()); - std::uninitialized_copy(src->children() + noffset + 1, - src->children() + n, - dst->children() + noffset); + inc_nodes(src->children(), noffset); + inc_nodes(src->children() + noffset + 1, n - noffset - 1); + std::copy(src->children(), src->children() + noffset, dst->children()); + std::copy(src->children() + noffset + 1, + src->children() + n, + dst->children() + noffset); + return dst; + } + + static node_t* move_inner_replace_inline( + edit_t e, node_t* src, bitmap_t bit, count_t noffset, T value) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + assert(!(src->datamap() & bit)); + assert(src->nodemap() & bit); + assert(noffset == src->children_count(bit)); + auto n = src->children_count(); + auto nv = src->data_count(); + auto dst = make_inner_n(n - 1, nv + 1); + auto voffset = src->data_count(bit); + dst->impl.d.data.inner.nodemap = src->nodemap() & ~bit; + dst->impl.d.data.inner.datamap = src->datamap() | bit; + IMMER_TRY { + auto mutate_values = + nv && can_mutate(src->impl.d.data.inner.values, e); + if (nv) { + if (mutate_values) + detail::uninitialized_move( + src->values(), src->values() + voffset, dst->values()); + else + detail::uninitialized_copy( + src->values(), src->values() + voffset, dst->values()); + } + IMMER_TRY { + new (dst->values() + voffset) T{std::move(value)}; + IMMER_TRY { + if (nv) { + if (mutate_values) + detail::uninitialized_move(src->values() + voffset, + src->values() + nv, + dst->values() + voffset + + 1); + else + detail::uninitialized_copy(src->values() + voffset, + src->values() + nv, + dst->values() + voffset + + 1); + } + } + IMMER_CATCH (...) { + dst->values()[voffset].~T(); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + detail::destroy_n(dst->values(), voffset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n - 1, nv + 1); + IMMER_RETHROW; + } + std::copy(src->children(), src->children() + noffset, dst->children()); + std::copy(src->children() + noffset + 1, + src->children() + n, + dst->children() + noffset); + delete_inner(src); return dst; } @@ -574,15 +801,15 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap(); if (nv > 1) { IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + voffset, dst->values()); IMMER_TRY { - std::uninitialized_copy(src->values() + voffset + 1, - src->values() + nv, - dst->values() + voffset); + detail::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); } IMMER_CATCH (...) { - destroy_n(dst->values(), voffset); + detail::destroy_n(dst->values(), voffset); IMMER_RETHROW; } } @@ -592,8 +819,66 @@ struct node } } inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + n, dst->children()); + std::copy(src->children(), src->children() + n, dst->children()); + return dst; + } + + static node_t* move_inner_remove_value(edit_t e, + node_t* src, + bitmap_t bit, + count_t voffset) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + assert(!(src->nodemap() & bit)); + assert(src->datamap() & bit); + assert(voffset == src->data_count(bit)); + auto n = src->children_count(); + auto nv = src->data_count(); + auto dst = make_inner_n(n, nv - 1); + dst->impl.d.data.inner.datamap = src->datamap() & ~bit; + dst->impl.d.data.inner.nodemap = src->nodemap(); + if (nv > 1) { + auto mutate_values = can_mutate(src->impl.d.data.inner.values, e); + if (mutate_values) { + IMMER_TRY { + detail::uninitialized_move( + src->values(), src->values() + voffset, dst->values()); + IMMER_TRY { + detail::uninitialized_move(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); + } + IMMER_CATCH (...) { + detail::destroy_n(dst->values(), voffset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n, nv - 1); + IMMER_RETHROW; + } + } else { + IMMER_TRY { + detail::uninitialized_copy( + src->values(), src->values() + voffset, dst->values()); + IMMER_TRY { + detail::uninitialized_copy(src->values() + voffset + 1, + src->values() + nv, + dst->values() + voffset); + } + IMMER_CATCH (...) { + detail::destroy_n(dst->values(), voffset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n, nv - 1); + IMMER_RETHROW; + } + } + } + std::copy(src->children(), src->children() + n, dst->children()); + delete_inner(src); return dst; } @@ -608,15 +893,15 @@ struct node dst->impl.d.data.inner.nodemap = src->nodemap(); IMMER_TRY { if (nv) - std::uninitialized_copy( + detail::uninitialized_copy( src->values(), src->values() + offset, dst->values()); IMMER_TRY { new (dst->values() + offset) T{std::move(v)}; IMMER_TRY { if (nv) - std::uninitialized_copy(src->values() + offset, - src->values() + nv, - dst->values() + offset + 1); + detail::uninitialized_copy(src->values() + offset, + src->values() + nv, + dst->values() + offset + 1); } IMMER_CATCH (...) { dst->values()[offset].~T(); @@ -624,7 +909,7 @@ struct node } } IMMER_CATCH (...) { - destroy_n(dst->values(), offset); + detail::destroy_n(dst->values(), offset); IMMER_RETHROW; } } @@ -633,8 +918,63 @@ struct node IMMER_RETHROW; } inc_nodes(src->children(), n); - std::uninitialized_copy( - src->children(), src->children() + n, dst->children()); + std::copy(src->children(), src->children() + n, dst->children()); + return dst; + } + + static node_t* + move_inner_insert_value(edit_t e, node_t* src, bitmap_t bit, T v) + { + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + auto n = src->children_count(); + auto nv = src->data_count(); + auto offset = src->data_count(bit); + auto dst = make_inner_n(n, nv + 1); + dst->impl.d.data.inner.datamap = src->datamap() | bit; + dst->impl.d.data.inner.nodemap = src->nodemap(); + IMMER_TRY { + auto mutate_values = + nv && can_mutate(src->impl.d.data.inner.values, e); + if (nv) { + if (mutate_values) + detail::uninitialized_move( + src->values(), src->values() + offset, dst->values()); + else + detail::uninitialized_copy( + src->values(), src->values() + offset, dst->values()); + } + IMMER_TRY { + new (dst->values() + offset) T{std::move(v)}; + IMMER_TRY { + if (nv) { + if (mutate_values) + detail::uninitialized_move(src->values() + offset, + src->values() + nv, + dst->values() + offset + + 1); + else + detail::uninitialized_copy(src->values() + offset, + src->values() + nv, + dst->values() + offset + + 1); + } + } + IMMER_CATCH (...) { + dst->values()[offset].~T(); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + detail::destroy_n(dst->values(), offset); + IMMER_RETHROW; + } + } + IMMER_CATCH (...) { + deallocate_inner(dst, n, nv + 1); + IMMER_RETHROW; + } + std::copy(src->children(), src->children() + n, dst->children()); + delete_inner(src); return dst; } @@ -648,7 +988,8 @@ struct node auto merged = make_merged( shift + B, std::move(v1), hash1, std::move(v2), hash2); IMMER_TRY { - return make_inner_n(1, idx1 >> shift, merged); + return make_inner_n( + 1, static_cast(idx1 >> shift), merged); } IMMER_CATCH (...) { delete_deep_shift(merged, shift + B); @@ -656,9 +997,9 @@ struct node } } else { return make_inner_n(0, - idx1 >> shift, + static_cast(idx1 >> shift), std::move(v1), - idx2 >> shift, + static_cast(idx2 >> shift), std::move(v2)); } } else { @@ -666,6 +1007,38 @@ struct node } } + static node_t* make_merged_e( + edit_t e, shift_t shift, T v1, hash_t hash1, T v2, hash_t hash2) + { + if (shift < max_shift) { + auto idx1 = hash1 & (mask << shift); + auto idx2 = hash2 & (mask << shift); + if (idx1 == idx2) { + auto merged = make_merged_e( + e, shift + B, std::move(v1), hash1, std::move(v2), hash2); + IMMER_TRY { + return owned( + make_inner_n( + 1, static_cast(idx1 >> shift), merged), + e); + } + IMMER_CATCH (...) { + delete_deep_shift(merged, shift + B); + IMMER_RETHROW; + } + } else { + auto r = make_inner_n(0, + static_cast(idx1 >> shift), + std::move(v1), + static_cast(idx2 >> shift), + std::move(v2)); + return owned_values(r, e); + } + } else { + return owned(make_collision(std::move(v1), std::move(v2)), e); + } + } + node_t* inc() { refs(this).inc(); @@ -679,7 +1052,6 @@ struct node } bool dec() const { return refs(this).dec(); } - void dec_unsafe() const { refs(this).dec_unsafe(); } static void inc_nodes(node_t** p, count_t n) { @@ -690,6 +1062,7 @@ struct node static void delete_values(values_t* p, count_t n) { assert(p); + detail::destroy_n((T*) &p->d.buffer, n); deallocate_values(p, n); } @@ -708,6 +1081,7 @@ struct node assert(p); IMMER_ASSERT_TAGGED(p->kind() == kind_t::collision); auto n = p->collision_count(); + detail::destroy_n(p->collisions(), n); deallocate_collision(p, n); } @@ -741,13 +1115,11 @@ struct node static void deallocate_values(values_t* p, count_t n) { - destroy_n((T*) &p->d.buffer, n); heap::deallocate(node_t::sizeof_values_n(n), p); } static void deallocate_collision(node_t* p, count_t n) { - destroy_n(p->collisions(), n); heap::deallocate(node_t::sizeof_collision_n(n), p); } diff --git a/immer/detail/iterator_facade.hpp b/immer/detail/iterator_facade.hpp index 359f3d912c..1d5578c875 100644 --- a/immer/detail/iterator_facade.hpp +++ b/immer/detail/iterator_facade.hpp @@ -82,19 +82,6 @@ protected: std::is_base_of::value; - class reference_proxy - { - friend iterator_facade; - DerivedT iter_; - - reference_proxy(DerivedT iter) - : iter_{std::move(iter)} - {} - - public: - operator ReferenceT() const { return *iter_; } - }; - const DerivedT& derived() const { static_assert(std::is_base_of::value, @@ -111,10 +98,10 @@ protected: public: ReferenceT operator*() const { return access_t::dereference(derived()); } PointerT operator->() const { return &access_t::dereference(derived()); } - reference_proxy operator[](DifferenceTypeT n) const + ReferenceT operator[](DifferenceTypeT n) const { static_assert(is_random_access, ""); - return derived() + n; + return *(derived() + n); } friend bool operator==(const DerivedT& a, const DerivedT& b) diff --git a/immer/detail/rbts/node.hpp b/immer/detail/rbts/node.hpp index 67c9a29561..55a512b03d 100644 --- a/immer/detail/rbts/node.hpp +++ b/immer/detail/rbts/node.hpp @@ -199,17 +199,23 @@ struct node } static ownee_t& ownee(node_t* x) { return get(x->impl); } - static node_t* make_inner_n(count_t n) + static node_t* make_inner_n_into(void* buffer, std::size_t size, count_t n) { assert(n <= branches); - auto m = heap::allocate(sizeof_inner_n(n)); - auto p = new (m) node_t; + assert(size >= sizeof_inner_n(n)); + auto p = new (buffer) node_t; p->impl.d.data.inner.relaxed = nullptr; #if IMMER_TAGGED_NODE p->impl.d.kind = node_t::kind_t::inner; #endif return p; } + static node_t* make_inner_n(count_t n) + { + assert(n <= branches); + auto m = heap::allocate(sizeof_inner_n(n)); + return make_inner_n_into(m, sizeof_inner_n(n), n); + } static node_t* make_inner_e(edit_t e) { @@ -310,16 +316,24 @@ struct node }); } - static node_t* make_leaf_n(count_t n) + static node_t* make_leaf_n_into(void* buffer, std::size_t size, count_t n) { assert(n <= branches); - auto p = new (heap::allocate(sizeof_leaf_n(n))) node_t; + assert(size >= sizeof_leaf_n(n)); + auto p = new (buffer) node_t; #if IMMER_TAGGED_NODE p->impl.d.kind = node_t::kind_t::leaf; #endif return p; } + static node_t* make_leaf_n(count_t n) + { + assert(n <= branches); + auto m = heap::allocate(sizeof_leaf_n(n)); + return make_leaf_n_into(m, sizeof_leaf_n(n), n); + } + static node_t* make_leaf_e(edit_t e) { auto p = new (heap::allocate(max_sizeof_leaf)) node_t; @@ -511,7 +525,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); auto dst = make_inner_n(n); inc_nodes(src->inner(), n); - std::uninitialized_copy(src->inner(), src->inner() + n, dst->inner()); + std::copy(src->inner(), src->inner() + n, dst->inner()); return dst; } @@ -536,7 +550,20 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); auto p = src->inner(); inc_nodes(p, n); - std::uninitialized_copy(p, p + n, dst->inner()); + std::copy(p, p + n, dst->inner()); + return dst; + } + + static node_t* do_copy_inner_replace( + node_t* dst, node_t* src, count_t n, count_t offset, node_t* child) + { + IMMER_ASSERT_TAGGED(dst->kind() == kind_t::inner); + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + auto p = src->inner(); + inc_nodes(p, offset); + inc_nodes(p + offset + 1, n - offset - 1); + std::copy(p, p + n, dst->inner()); + dst->inner()[offset] = child; return dst; } @@ -582,6 +609,23 @@ struct node return dst; } + static node_t* do_copy_inner_replace_r( + node_t* dst, node_t* src, count_t n, count_t offset, node_t* child) + { + IMMER_ASSERT_TAGGED(dst->kind() == kind_t::inner); + IMMER_ASSERT_TAGGED(src->kind() == kind_t::inner); + auto src_r = src->relaxed(); + auto dst_r = dst->relaxed(); + auto p = src->inner(); + inc_nodes(p, offset); + inc_nodes(p + offset + 1, n - offset - 1); + std::copy(src->inner(), src->inner() + n, dst->inner()); + std::copy(src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + dst_r->d.count = n; + dst->inner()[offset] = child; + return dst; + } + static node_t* do_copy_inner_sr(node_t* dst, node_t* src, count_t n) { if (embed_relaxed) @@ -593,12 +637,28 @@ struct node } } + static node_t* do_copy_inner_replace_sr( + node_t* dst, node_t* src, count_t n, count_t offset, node_t* child) + { + if (embed_relaxed) + return do_copy_inner_replace_r(dst, src, n, offset, child); + else { + auto p = src->inner(); + inc_nodes(p, offset); + inc_nodes(p + offset + 1, n - offset - 1); + std::copy(p, p + n, dst->inner()); + dst->inner()[offset] = child; + return dst; + } + } + static node_t* copy_leaf(node_t* src, count_t n) { IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(n); IMMER_TRY { - std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + detail::uninitialized_copy( + src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::sizeof_leaf_n(n), dst); @@ -612,7 +672,8 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + detail::uninitialized_copy( + src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::max_sizeof_leaf, dst); @@ -627,7 +688,8 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(allocn); IMMER_TRY { - std::uninitialized_copy(src->leaf(), src->leaf() + n, dst->leaf()); + detail::uninitialized_copy( + src->leaf(), src->leaf() + n, dst->leaf()); } IMMER_CATCH (...) { heap::deallocate(node_t::sizeof_leaf_n(allocn), dst); @@ -642,7 +704,7 @@ struct node IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf); auto dst = make_leaf_n(n1 + n2); IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src1->leaf(), src1->leaf() + n1, dst->leaf()); } IMMER_CATCH (...) { @@ -650,11 +712,11 @@ struct node IMMER_RETHROW; } IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); } IMMER_CATCH (...) { - destroy_n(dst->leaf(), n1); + detail::destroy_n(dst->leaf(), n1); heap::deallocate(node_t::sizeof_leaf_n(n1 + n2), dst); IMMER_RETHROW; } @@ -668,7 +730,7 @@ struct node IMMER_ASSERT_TAGGED(src2->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src1->leaf(), src1->leaf() + n1, dst->leaf()); } IMMER_CATCH (...) { @@ -676,11 +738,11 @@ struct node IMMER_RETHROW; } IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src2->leaf(), src2->leaf() + n2, dst->leaf() + n1); } IMMER_CATCH (...) { - destroy_n(dst->leaf(), n1); + detail::destroy_n(dst->leaf(), n1); heap::deallocate(max_sizeof_leaf, dst); IMMER_RETHROW; } @@ -692,7 +754,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_e(e); IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src->leaf() + idx, src->leaf() + last, dst->leaf()); } IMMER_CATCH (...) { @@ -707,7 +769,7 @@ struct node IMMER_ASSERT_TAGGED(src->kind() == kind_t::leaf); auto dst = make_leaf_n(last - idx); IMMER_TRY { - std::uninitialized_copy( + detail::uninitialized_copy( src->leaf() + idx, src->leaf() + last, dst->leaf()); } IMMER_CATCH (...) { @@ -725,7 +787,7 @@ struct node new (dst->leaf() + n) T{std::forward(x)}; } IMMER_CATCH (...) { - destroy_n(dst->leaf(), n); + detail::destroy_n(dst->leaf(), n); heap::deallocate(node_t::sizeof_leaf_n(n + 1), dst); IMMER_RETHROW; } @@ -788,7 +850,7 @@ struct node static void delete_leaf(node_t* p, count_t n) { IMMER_ASSERT_TAGGED(p->kind() == kind_t::leaf); - destroy_n(p->leaf(), n); + detail::destroy_n(p->leaf(), n); heap::deallocate(ownee(p).owned() ? node_t::max_sizeof_leaf : node_t::sizeof_leaf_n(n), p); @@ -814,10 +876,12 @@ struct node auto dst_r = impl.d.data.inner.relaxed = new (heap::allocate(max_sizeof_relaxed)) relaxed_t; if (src_r) { - node_t::refs(src_r).dec_unsafe(); auto n = dst_r->d.count = src_r->d.count; std::copy( src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + if (node_t::refs(src_r).dec()) + heap::deallocate(node_t::sizeof_inner_r_n(n), + src_r); } node_t::ownee(dst_r) = e; return dst_r; @@ -839,10 +903,12 @@ struct node auto dst_r = impl.d.data.inner.relaxed = new (heap::allocate(max_sizeof_relaxed)) relaxed_t; if (src_r) { - node_t::refs(src_r).dec_unsafe(); auto n = dst_r->d.count = src_r->d.count; std::copy( src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + if (node_t::refs(src_r).dec()) + heap::deallocate(node_t::sizeof_inner_r_n(n), + src_r); } node_t::ownee(dst_r) = ec; return dst_r; @@ -863,9 +929,11 @@ struct node auto dst_r = new (heap::allocate(max_sizeof_relaxed)) relaxed_t; if (src_r) { - node_t::refs(src_r).dec_unsafe(); std::copy( src_r->d.sizes, src_r->d.sizes + n, dst_r->d.sizes); + if (node_t::refs(src_r).dec()) + heap::deallocate(node_t::sizeof_inner_r_n(n), + src_r); } dst_r->d.count = n; node_t::ownee(dst_r) = e; @@ -887,7 +955,6 @@ struct node } bool dec() const { return refs(this).dec(); } - void dec_unsafe() const { refs(this).dec_unsafe(); } static void inc_nodes(node_t** p, count_t n) { diff --git a/immer/detail/rbts/operations.hpp b/immer/detail/rbts/operations.hpp index 8543915aea..d5ff67934b 100644 --- a/immer/detail/rbts/operations.hpp +++ b/immer/detail/rbts/operations.hpp @@ -92,7 +92,7 @@ struct for_each_chunk_visitor : visitor_base static void visit_leaf(Pos&& pos, Fn&& fn) { auto data = pos.node()->leaf(); - fn(data, data + pos.count()); + fn(as_const(data), as_const(data) + pos.count()); } }; @@ -109,7 +109,7 @@ struct for_each_chunk_p_visitor : visitor_base template static bool visit_leaf(Pos&& pos, Fn&& fn) { - auto data = pos.node()->leaf(); + auto data = as_const(pos.node()->leaf()); return fn(data, data + pos.count()); } }; @@ -468,9 +468,8 @@ struct update_visitor : visitor_base> auto node = node_t::make_inner_sr_n(count, pos.relaxed()); IMMER_TRY { auto child = pos.towards_oh(this_t{}, idx, offset, fn); - node_t::do_copy_inner_sr(node, pos.node(), count); - node->inner()[offset]->dec_unsafe(); - node->inner()[offset] = child; + node_t::do_copy_inner_replace_sr( + node, pos.node(), count, offset, child); return node; } IMMER_CATCH (...) { @@ -487,9 +486,8 @@ struct update_visitor : visitor_base> auto node = node_t::make_inner_n(count); IMMER_TRY { auto child = pos.towards_oh_ch(this_t{}, idx, offset, count, fn); - node_t::do_copy_inner(node, pos.node(), count); - node->inner()[offset]->dec_unsafe(); - node->inner()[offset] = child; + node_t::do_copy_inner_replace( + node, pos.node(), count, offset, child); return node; } IMMER_CATCH (...) { @@ -1067,8 +1065,8 @@ struct slice_right_mut_visitor node->inc(); return std::make_tuple(0, nullptr, new_tail_size, node); } else if (mutate) { - destroy_n(node->leaf() + new_tail_size, - old_tail_size - new_tail_size); + detail::destroy_n(node->leaf() + new_tail_size, + old_tail_size - new_tail_size); return std::make_tuple(0, nullptr, new_tail_size, node); } else { auto new_tail = node_t::copy_leaf_e(e, node, new_tail_size); @@ -1183,8 +1181,8 @@ struct slice_right_visitor : visitor_base> auto old_tail_size = pos.count(); auto new_tail_size = pos.index(last) + 1; auto new_tail = new_tail_size == old_tail_size - ? pos.node()->inc() - : node_t::copy_leaf(pos.node(), new_tail_size); + ? pos.node()->inc() + : node_t::copy_leaf(pos.node(), new_tail_size); return std::make_tuple(0, nullptr, new_tail_size, new_tail); } }; @@ -1263,10 +1261,10 @@ struct slice_left_mut_visitor return r; } else { using std::get; - auto newn = mutate ? (node->ensure_mutable_relaxed(e), node) - : node_t::make_inner_r_e(e); - auto newr = newn->relaxed(); - auto newcount = count - idx; + auto newn = mutate ? (node->ensure_mutable_relaxed(e), node) + : node_t::make_inner_r_e(e); + auto newr = newn->relaxed(); + auto newcount = count - idx; auto new_child_size = child_size - child_dropped_size; IMMER_TRY { auto subs = @@ -1277,9 +1275,9 @@ struct slice_left_mut_visitor pos.each_left(dec_visitor{}, idx); pos.copy_sizes( idx + 1, newcount - 1, new_child_size, newr->d.sizes + 1); - std::uninitialized_copy(node->inner() + idx + 1, - node->inner() + count, - newn->inner() + 1); + std::copy(node->inner() + idx + 1, + node->inner() + count, + newn->inner() + 1); newn->inner()[0] = get<1>(subs); newr->d.sizes[0] = new_child_size; newr->d.count = newcount; @@ -1348,9 +1346,9 @@ struct slice_left_mut_visitor idx + 1, newcount - 1, newr->d.sizes[0], newr->d.sizes + 1); newr->d.count = newcount; newn->inner()[0] = get<1>(subs); - std::uninitialized_copy(node->inner() + idx + 1, - node->inner() + count, - newn->inner() + 1); + std::copy(node->inner() + idx + 1, + node->inner() + count, + newn->inner() + 1); if (!mutate) { node_t::inc_nodes(newn->inner() + 1, newcount - 1); if (Mutating) @@ -1386,7 +1384,7 @@ struct slice_left_mut_visitor auto data = node->leaf(); auto newcount = count - idx; std::move(data + idx, data + count, data); - destroy_n(data + newcount, idx); + detail::destroy_n(data + newcount, idx); return std::make_tuple(0, node); } else { auto newn = node_t::copy_leaf_e(e, node, idx, count); @@ -1439,9 +1437,9 @@ struct slice_left_visitor : visitor_base> assert(newr->d.sizes[newr->d.count - 1] == pos.size() - dropped_size); newn->inner()[0] = get<1>(subs); - std::uninitialized_copy(n->inner() + idx + 1, - n->inner() + count, - newn->inner() + 1); + std::copy(n->inner() + idx + 1, + n->inner() + count, + newn->inner() + 1); node_t::inc_nodes(newn->inner() + 1, newr->d.count - 1); return std::make_tuple(pos.shift(), newn); } @@ -1627,9 +1625,9 @@ struct concat_merger auto data = to_->leaf(); auto to_copy = std::min(from_count - from_offset, *curr_ - to_offset_); - std::uninitialized_copy(from_data + from_offset, - from_data + from_offset + to_copy, - data + to_offset_); + detail::uninitialized_copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); to_offset_ += to_copy; from_offset += to_copy; if (*curr_ == to_offset_) { @@ -1662,9 +1660,9 @@ struct concat_merger auto data = to_->inner(); auto to_copy = std::min(from_count - from_offset, *curr_ - to_offset_); - std::uninitialized_copy(from_data + from_offset, - from_data + from_offset + to_copy, - data + to_offset_); + std::copy(from_data + from_offset, + from_data + from_offset + to_copy, + data + to_offset_); node_t::inc_nodes(from_data + from_offset, to_copy); auto sizes = to_->relaxed()->d.sizes; p.copy_sizes( @@ -2124,10 +2122,10 @@ struct concat_merger_mut data + to_offset_); } else { if (!from_mutate) - std::uninitialized_copy(from_data + from_offset, - from_data + from_offset + - to_copy, - data + to_offset_); + detail::uninitialized_copy(from_data + from_offset, + from_data + from_offset + + to_copy, + data + to_offset_); else detail::uninitialized_move(from_data + from_offset, from_data + from_offset + diff --git a/immer/detail/rbts/rbtree.hpp b/immer/detail/rbts/rbtree.hpp index 0b016a0efd..c8fa4251f0 100644 --- a/immer/detail/rbts/rbtree.hpp +++ b/immer/detail/rbts/rbtree.hpp @@ -15,9 +15,9 @@ #include #include -#include #include #include +#include namespace immer { namespace detail { @@ -465,7 +465,7 @@ struct rbtree auto ts = size - tail_off; auto newts = new_size - tail_off; if (tail->can_mutate(e)) { - destroy_n(tail->leaf() + newts, ts - newts); + detail::destroy_n(tail->leaf() + newts, ts - newts); } else { auto new_tail = node_t::copy_leaf_e(e, tail, newts); dec_leaf(tail, ts); diff --git a/immer/detail/rbts/rrbtree.hpp b/immer/detail/rbts/rrbtree.hpp index 9288670beb..843921ba22 100644 --- a/immer/detail/rbts/rrbtree.hpp +++ b/immer/detail/rbts/rrbtree.hpp @@ -49,13 +49,21 @@ struct rrbtree static node_t* empty_root() { - static const auto empty_ = node_t::make_inner_n(0u); + static const auto empty_ = []{ + constexpr auto size = node_t::sizeof_inner_n(0); + static std::aligned_storage_t storage; + return node_t::make_inner_n_into(&storage, size, 0u); + }(); return empty_->inc(); } static node_t* empty_tail() { - static const auto empty_ = node_t::make_leaf_n(0u); + static const auto empty_ = []{ + constexpr auto size = node_t::sizeof_leaf_n(0); + static std::aligned_storage_t storage; + return node_t::make_leaf_n_into(&storage, size, 0u); + }(); return empty_->inc(); } @@ -90,7 +98,7 @@ struct rrbtree return result; } - rrbtree() + rrbtree() noexcept : size{0} , shift{BL} , root{empty_root()} @@ -99,7 +107,7 @@ struct rrbtree assert(check_tree()); } - rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) + rrbtree(size_t sz, shift_t sh, node_t* r, node_t* t) noexcept : size{sz} , shift{sh} , root{r} @@ -108,13 +116,13 @@ struct rrbtree assert(check_tree()); } - rrbtree(const rrbtree& other) + rrbtree(const rrbtree& other) noexcept : rrbtree{other.size, other.shift, other.root, other.tail} { inc(); } - rrbtree(rrbtree&& other) + rrbtree(rrbtree&& other) noexcept : rrbtree{} { swap(*this, other); @@ -127,13 +135,13 @@ struct rrbtree return *this; } - rrbtree& operator=(rrbtree&& other) + rrbtree& operator=(rrbtree&& other) noexcept { swap(*this, other); return *this; } - friend void swap(rrbtree& x, rrbtree& y) + friend void swap(rrbtree& x, rrbtree& y) noexcept { using std::swap; swap(x.size, y.size); @@ -574,7 +582,7 @@ struct rrbtree auto ts = size - tail_off; auto newts = new_size - tail_off; if (tail->can_mutate(e)) { - destroy_n(tail->leaf() + newts, ts - newts); + detail::destroy_n(tail->leaf() + newts, ts - newts); } else { auto new_tail = node_t::copy_leaf_e(e, tail, newts); dec_leaf(tail, ts); @@ -793,17 +801,17 @@ struct rrbtree return; } else if (tail_size + r.size <= branches) { l.ensure_mutable_tail(el, tail_size); - std::uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + r.size, - l.tail->leaf() + tail_size); + detail::uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + r.size, + l.tail->leaf() + tail_size); l.size += r.size; return; } else { auto remaining = branches - tail_size; l.ensure_mutable_tail(el, tail_size); - std::uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + remaining, - l.tail->leaf() + tail_size); + detail::uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + remaining, + l.tail->leaf() + tail_size); IMMER_TRY { auto new_tail = node_t::copy_leaf_e(el, r.tail, remaining, r.size); @@ -819,7 +827,7 @@ struct rrbtree } } IMMER_CATCH (...) { - destroy_n(r.tail->leaf() + tail_size, remaining); + detail::destroy_n(r.tail->leaf() + tail_size, remaining); IMMER_RETHROW; } } @@ -1066,9 +1074,9 @@ struct rrbtree r.tail->leaf() + r.size, l.tail->leaf() + tail_size); else - std::uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + r.size, - l.tail->leaf() + tail_size); + detail::uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + r.size, + l.tail->leaf() + tail_size); l.size += r.size; return; } else { @@ -1079,9 +1087,9 @@ struct rrbtree r.tail->leaf() + remaining, l.tail->leaf() + tail_size); else - std::uninitialized_copy(r.tail->leaf(), - r.tail->leaf() + remaining, - l.tail->leaf() + tail_size); + detail::uninitialized_copy(r.tail->leaf(), + r.tail->leaf() + remaining, + l.tail->leaf() + tail_size); IMMER_TRY { auto new_tail = node_t::copy_leaf_e(el, r.tail, remaining, r.size); @@ -1097,7 +1105,7 @@ struct rrbtree } } IMMER_CATCH (...) { - destroy_n(r.tail->leaf() + tail_size, remaining); + detail::destroy_n(r.tail->leaf() + tail_size, remaining); IMMER_RETHROW; } } diff --git a/immer/detail/type_traits.hpp b/immer/detail/type_traits.hpp index afb2652c55..091fe04dc2 100644 --- a/immer/detail/type_traits.hpp +++ b/immer/detail/type_traits.hpp @@ -201,23 +201,5 @@ struct std_distance_supports< template constexpr bool std_distance_supports_v = std_distance_supports::value; -template -struct std_uninitialized_copy_supports : std::false_type -{}; - -template -struct std_uninitialized_copy_supports< - T, - U, - V, - void_t(), std::declval(), std::declval()))>> - : std::true_type -{}; - -template -constexpr bool std_uninitialized_copy_supports_v = - std_uninitialized_copy_supports::value; - } // namespace detail } // namespace immer diff --git a/immer/detail/util.hpp b/immer/detail/util.hpp index b9e5041461..d6ae246b43 100644 --- a/immer/detail/util.hpp +++ b/immer/detail/util.hpp @@ -24,6 +24,18 @@ namespace immer { namespace detail { +template +const T* as_const(T* x) +{ + return x; +} + +template +const T& as_const(T& x) +{ + return x; +} + template using aligned_storage_for = typename std::aligned_storage::type; @@ -39,26 +51,110 @@ T&& auto_const_cast(const T&& x) return const_cast(std::move(x)); } -template -auto uninitialized_move(Iter1 in1, Iter1 in2, Iter2 out) +template +inline auto destroy_at(T* p) noexcept + -> std::enable_if_t::value> { - return std::uninitialized_copy( - std::make_move_iterator(in1), std::make_move_iterator(in2), out); + p->~T(); } template -void destroy(T* first, T* last) +inline auto destroy_at(T* p) noexcept + -> std::enable_if_t::value> { - for (; first != last; ++first) - first->~T(); + p->~T(); } -template -void destroy_n(T* p, Size n) +template +constexpr bool can_trivially_detroy = std::is_trivially_destructible< + typename std::iterator_traits::value_type>::value; + +template +auto destroy(Iter, Iter last) noexcept + -> std::enable_if_t, Iter> { - auto e = p + n; - for (; p != e; ++p) - p->~T(); + return last; +} +template +auto destroy(Iter first, Iter last) noexcept + -> std::enable_if_t, Iter> +{ + for (; first != last; ++first) + detail::destroy_at(std::addressof(*first)); + return first; +} + +template +auto destroy_n(Iter first, Size n) noexcept + -> std::enable_if_t, Iter> +{ + return first + n; +} +template +auto destroy_n(Iter first, Size n) noexcept + -> std::enable_if_t, Iter> +{ + for (; n > 0; (void) ++first, --n) + detail::destroy_at(std::addressof(*first)); + return first; +} + +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; + +template +auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) noexcept + -> std::enable_if_t, Iter2> +{ + return std::copy(first, last, out); +} +template +auto uninitialized_move(Iter1 first, Iter1 last, Iter2 out) + -> std::enable_if_t, Iter2> + +{ + using value_t = typename std::iterator_traits::value_type; + auto current = out; + IMMER_TRY { + for (; first != last; ++first, (void) ++current) { + ::new (const_cast(static_cast( + std::addressof(*current)))) value_t(std::move(*first)); + } + return current; + } + IMMER_CATCH (...) { + detail::destroy(out, current); + IMMER_RETHROW; + } +} + +template +auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) noexcept + -> std::enable_if_t, SinkIter> +{ + return std::copy(first, last, out); +} +template +auto uninitialized_copy(SourceIter first, Sent last, SinkIter out) + -> std::enable_if_t, SinkIter> +{ + using value_t = typename std::iterator_traits::value_type; + auto current = out; + IMMER_TRY { + for (; first != last; ++first, (void) ++current) { + ::new (const_cast(static_cast( + std::addressof(*current)))) value_t(*first); + } + return current; + } + IMMER_CATCH (...) { + detail::destroy(out, current); + IMMER_RETHROW; + } } template @@ -214,53 +310,5 @@ distance(Iterator first, Sentinel last) return last - first; } -/*! - * An alias to `std::uninitialized_copy` - */ -template < - typename Iterator, - typename Sentinel, - typename SinkIter, - std::enable_if_t< - detail::std_uninitialized_copy_supports_v, - bool> = true> -SinkIter uninitialized_copy(Iterator first, Sentinel last, SinkIter d_first) -{ - return std::uninitialized_copy(first, last, d_first); -} - -/*! - * Equivalent of the `std::uninitialized_copy` applied to the - * sentinel-delimited forward range @f$ [first, last) @f$ - */ -template ) &&detail:: - compatible_sentinel_v && - detail::is_forward_iterator_v, - bool> = true> -SinkIter uninitialized_copy(SourceIter first, Sent last, SinkIter d_first) -{ - auto current = d_first; - IMMER_TRY { - while (first != last) { - *current++ = *first; - ++first; - } - } - IMMER_CATCH (...) { - using Value = typename std::iterator_traits::value_type; - for (; d_first != current; ++d_first) { - d_first->~Value(); - } - IMMER_RETHROW; - } - return current; -} - } // namespace detail } // namespace immer diff --git a/immer/flex_vector.hpp b/immer/flex_vector.hpp index 47a82abe3d..f970db910a 100644 --- a/immer/flex_vector.hpp +++ b/immer/flex_vector.hpp @@ -249,7 +249,7 @@ public: } /*! - * Returns a flex_vector with `value` inserted at the frony. It may + * Returns a flex_vector with `value` inserted at the front. It may * allocate memory and its complexity is @f$ O(log(size)) @f$. * * @rst @@ -503,6 +503,17 @@ public: IMMER_NODISCARD transient_type transient() const& { return impl_; } IMMER_NODISCARD transient_type transient() && { return std::move(impl_); } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + std::pair identity() const + { + return {impl_.root, impl_.tail}; + } + // Semi-private const impl_t& impl() const { return impl_; } @@ -605,4 +616,9 @@ private: impl_t impl_ = {}; }; +static_assert(std::is_nothrow_move_constructible>::value, + "flex_vector is not nothrow move constructible"); +static_assert(std::is_nothrow_move_assignable>::value, + "flex_vector is not nothrow move assignable"); + } // namespace immer diff --git a/immer/heap/cpp_heap.hpp b/immer/heap/cpp_heap.hpp index 119fceecb4..3789754821 100644 --- a/immer/heap/cpp_heap.hpp +++ b/immer/heap/cpp_heap.hpp @@ -33,7 +33,7 @@ struct cpp_heap * `allocate`. One must not use nor deallocate again a memory * region that once it has been deallocated. */ - static void deallocate(std::size_t size, void* data) + static void deallocate(std::size_t, void* data) { ::operator delete(data); } diff --git a/immer/heap/heap_policy.hpp b/immer/heap/heap_policy.hpp index 582c113f33..1a858c5e12 100644 --- a/immer/heap/heap_policy.hpp +++ b/immer/heap/heap_policy.hpp @@ -85,8 +85,7 @@ struct enable_optimized_heap_policy * @rst * * .. tip:: For many applications that use immutable data structures - * significantly, this is actually the best heap policy, and it - * might become the default in the future. + * significantly, this is actually the best heap policy. * * Note that most our data structures internally use trees with the * same big branching factors. This means that all *vectors*, diff --git a/immer/map.hpp b/immer/map.hpp index e12d7ee098..cab8e5d5d8 100644 --- a/immer/map.hpp +++ b/immer/map.hpp @@ -14,8 +14,8 @@ #include #include -#include #include +#include namespace immer { @@ -41,7 +41,7 @@ class map_transient; * * @rst * - * This cotainer provides a good trade-off between cache locality, + * This container provides a good trade-off between cache locality, * search, update performance and structural sharing. It does so by * storing the data in contiguous chunks of :math:`2^{B}` elements. * When storing big objects, the size of these contiguous chunks can @@ -68,12 +68,19 @@ class map { using value_t = std::pair; + using move_t = + std::integral_constant; + struct project_value { const T& operator()(const value_t& v) const noexcept { return v.second; } + T&& operator()(value_t&& v) const noexcept + { + return std::move(v.second); + } }; struct project_value_ptr @@ -114,8 +121,11 @@ class map { auto operator()(const value_t& v) { return Hash{}(v.first); } - template - auto operator()(const Key& v) { return Hash{}(v); } + template + auto operator()(const Key& v) + { + return Hash{}(v); + } }; struct equal_key @@ -125,7 +135,7 @@ class map return Equal{}(a.first, b.first); } - template + template auto operator()(const value_t& a, const Key& b) { return Equal{}(a.first, b); @@ -160,8 +170,29 @@ public: using transient_type = map_transient; + using memory_policy_type = MemoryPolicy; + /*! - * Default constructor. It creates a set of `size() == 0`. It + * Constructs a map containing the elements in `values`. + */ + map(std::initializer_list values) + : impl_{impl_t::from_initializer_list(values)} + {} + + /*! + * Constructs a map containing the elements in the range + * defined by the input iterator `first` and range sentinel `last`. + */ + template , + bool> = true> + map(Iter first, Sent last) + : impl_{impl_t::from_range(first, last)} + {} + + /*! + * Default constructor. It creates a map of `size() == 0`. It * does not allocate memory and its complexity is @f$ O(1) @f$. */ map() = default; @@ -202,7 +233,9 @@ public: * This overload participates in overload resolution only if * `Hash::is_transparent` is valid and denotes a type. */ - template + template IMMER_NODISCARD size_type count(const Key& k) const { return impl_.template get, @@ -229,7 +262,9 @@ public: * This overload participates in overload resolution only if * `Hash::is_transparent` is valid and denotes a type. */ - template + template IMMER_NODISCARD const T& operator[](const Key& k) const { return impl_.template get(k); @@ -252,7 +287,9 @@ public: * `std::out_of_range` error. It does not allocate memory and its * complexity is *effectively* @f$ O(1) @f$. */ - template + template const T& at(const Key& k) const { return impl_.template get(k); @@ -297,7 +334,7 @@ public: * ``std::optional`` but this construction is not valid * in any current standard. As a compromise we return a * pointer, which has similar syntactic properties yet it is - * unfortunatelly unnecessarily unrestricted. + * unfortunately unnecessarily unrestricted. * * @endrst */ @@ -307,7 +344,6 @@ public: detail::constantly>(k); } - /*! * Returns a pointer to the value associated with the key `k`. If * the key is not contained in the map, a `nullptr` is returned. @@ -317,7 +353,9 @@ public: * This overload participates in overload resolution only if * `Hash::is_transparent` is valid and denotes a type. */ - template + template IMMER_NODISCARD const T* find(const Key& k) const { return impl_.template get - IMMER_NODISCARD map update(key_type k, Fn&& fn) const + IMMER_NODISCARD map update(key_type k, Fn&& fn) const& { return impl_ .template update( std::move(k), std::forward(fn)); } + template + IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) && + { + return update_move(move_t{}, std::move(k), std::forward(fn)); + } + + /*! + * Returns a map replacing the association `(k, v)` by the association new + * association `(k, fn(v))`, where `v` is the currently associated value for + * `k` in the map. It does nothing if `k` is not present in the map. It + * may allocate memory and its complexity is *effectively* @f$ O(1) @f$. + */ + template + IMMER_NODISCARD map update_if_exists(key_type k, Fn&& fn) const& + { + return impl_.template update_if_exists( + std::move(k), std::forward(fn)); + } + template + IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) && + { + return update_if_exists_move( + move_t{}, std::move(k), std::forward(fn)); + } /*! * Returns a map without the key `k`. If the key is not * associated in the map it returns the same map. It may allocate * memory and its complexity is *effectively* @f$ O(1) @f$. */ - IMMER_NODISCARD map erase(const K& k) const { return impl_.sub(k); } + IMMER_NODISCARD map erase(const K& k) const& { return impl_.sub(k); } + IMMER_NODISCARD decltype(auto) erase(const K& k) && + { + return erase_move(move_t{}, k); + } /*! - * Returns an @a transient form of this container, a + * Returns a @a transient form of this container, an * `immer::map_transient`. */ IMMER_NODISCARD transient_type transient() const& @@ -393,12 +467,79 @@ public: return transient_type{std::move(impl_)}; } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + void* identity() const { return impl_.root; } + // Semi-private const impl_t& impl() const { return impl_; } private: friend transient_type; + map&& insert_move(std::true_type, value_type value) + { + impl_.add_mut({}, std::move(value)); + return std::move(*this); + } + map insert_move(std::false_type, value_type value) + { + return impl_.add(std::move(value)); + } + + map&& set_move(std::true_type, key_type k, mapped_type m) + { + impl_.add_mut({}, {std::move(k), std::move(m)}); + return std::move(*this); + } + map set_move(std::false_type, key_type k, mapped_type m) + { + return impl_.add({std::move(k), std::move(m)}); + } + + template + map&& update_move(std::true_type, key_type k, Fn&& fn) + { + impl_.template update_mut( + {}, std::move(k), std::forward(fn)); + return std::move(*this); + } + template + map update_move(std::false_type, key_type k, Fn&& fn) + { + return impl_ + .template update( + std::move(k), std::forward(fn)); + } + + template + map&& update_if_exists_move(std::true_type, key_type k, Fn&& fn) + { + impl_.template update_if_exists_mut( + {}, std::move(k), std::forward(fn)); + return std::move(*this); + } + template + map update_if_exists_move(std::false_type, key_type k, Fn&& fn) + { + return impl_.template update_if_exists( + std::move(k), std::forward(fn)); + } + + map&& erase_move(std::true_type, const key_type& value) + { + impl_.sub_mut({}, value); + return std::move(*this); + } + map erase_move(std::false_type, const key_type& value) + { + return impl_.sub(value); + } + map(impl_t impl) : impl_(std::move(impl)) {} diff --git a/immer/map_transient.hpp b/immer/map_transient.hpp index 8821c5d12b..935f48e2b0 100644 --- a/immer/map_transient.hpp +++ b/immer/map_transient.hpp @@ -15,18 +15,21 @@ namespace immer { +template +class map; + /*! + * Mutable version of `immer::map`. + * * @rst * - * .. admonition:: Become a sponsor! - * :class: danger - * - * This component is planned but it has **not been implemented yet**. - * - * Transiens can critically improve the performance of applications - * intensively using ``set`` and ``map``. If you are working for an - * organization using the library in a commercial project, please consider - * **sponsoring this work**: juanpe@sinusoid.al + * Refer to :doc:`transients` to learn more about when and how to use + * the mutable versions of immutable containers. * * @endrst */ @@ -36,6 +39,280 @@ template , typename MemoryPolicy = default_memory_policy, detail::hamts::bits_t B = default_bits> -class map_transient; +class map_transient : MemoryPolicy::transience_t::owner +{ + using base_t = typename MemoryPolicy::transience_t::owner; + using owner_t = base_t; + +public: + using persistent_type = map; + + using key_type = K; + using mapped_type = T; + using value_type = std::pair; + using size_type = detail::hamts::size_t; + using diference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = Equal; + using reference = const value_type&; + using const_reference = const value_type&; + + using iterator = typename persistent_type::iterator; + using const_iterator = iterator; + + /*! + * Default constructor. It creates a map of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + map_transient() = default; + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + IMMER_NODISCARD iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD iterator end() const + { + return {impl_, typename iterator::end_t{}}; + } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD size_type size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD bool empty() const { return impl_.size == 0; } + + /*! + * Returns `1` when the key `k` is contained in the map or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD size_type count(const Key& k) const + { + return impl_.template get, + detail::constantly>(k); + } + + /*! + * Returns `1` when the key `k` is contained in the map or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD size_type count(const K& k) const + { + return impl_.template get, + detail::constantly>(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If the key is not contained in the map, it returns a + * default constructed value. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD const T& operator[](const Key& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If the key is not contained in the map, it returns a + * default constructed value. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD const T& operator[](const K& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If the key is not contained in the map, throws an + * `std::out_of_range` error. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + template + const T& at(const Key& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If the key is not contained in the map, throws an + * `std::out_of_range` error. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + const T& at(const K& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a pointer to the value associated with the key `k`. If + * the key is not contained in the map, a `nullptr` is returned. + * It does not allocate memory and its complexity is *effectively* + * @f$ O(1) @f$. + * + * @rst + * + * .. admonition:: Why doesn't this function return an iterator? + * + * Associative containers from the C++ standard library provide a + * ``find`` method that returns an iterator pointing to the + * element in the container or ``end()`` when the key is missing. + * In the case of an unordered container, the only meaningful + * thing one may do with it is to compare it with the end, to + * test if the find was succesfull, and dereference it. This + * comparison is cumbersome compared to testing for a non-empty + * optional value. Furthermore, for an immutable container, + * returning an iterator would have some additional performance + * cost, with no benefits otherwise. + * + * In our opinion, this function should return a + * ``std::optional`` but this construction is not valid + * in any current standard. As a compromise we return a + * pointer, which has similar syntactic properties yet it is + * unfortunately unnecessarily unrestricted. + * + * @endrst + */ + IMMER_NODISCARD const T* find(const K& k) const + { + return impl_.template get>(k); + } + + /*! + * Returns a pointer to the value associated with the key `k`. If + * the key is not contained in the map, a `nullptr` is returned. + * It does not allocate memory and its complexity is *effectively* + * @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD const T* find(const Key& k) const + { + return impl_.template get>(k); + } + + /*! + * Inserts the association `value`. If the key is already in the map, it + * replaces its association in the map. It may allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + void insert(value_type value) { impl_.add_mut(*this, std::move(value)); } + + /*! + * Inserts the association `(k, v)`. If the key is already in the map, it + * replaces its association in the map. It may allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + void set(key_type k, mapped_type v) + { + impl_.add_mut(*this, {std::move(k), std::move(v)}); + } + + /*! + * Replaces the association `(k, v)` by the association new association `(k, + * fn(v))`, where `v` is the currently associated value for `k` in the map + * or a default constructed value otherwise. It may allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + template + void update(key_type k, Fn&& fn) + { + impl_.template update_mut( + *this, std::move(k), std::forward(fn)); + } + + /*! + * Replaces the association `(k, v)` by the association new association `(k, + * fn(v))`, where `v` is the currently associated value for `k` in the map + * or does nothing if `k` is not present in the map. It may allocate memory + * and its complexity is *effectively* @f$ O(1) @f$. + */ + template + void update_if_exists(key_type k, Fn&& fn) + { + impl_.template update_if_exists_mut< + typename persistent_type::project_value, + typename persistent_type::combine_value>( + *this, std::move(k), std::forward(fn)); + } + + /*! + * Removes the key `k` from the k. Does nothing if the key is not + * associated in the map. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + void erase(const K& k) { impl_.sub_mut(*this, k); } + + /*! + * Returns an @a immutable form of this container, an + * `immer::map`. + */ + IMMER_NODISCARD persistent_type persistent() & + { + this->owner_t::operator=(owner_t{}); + return impl_; + } + IMMER_NODISCARD persistent_type persistent() && { return std::move(impl_); } + +private: + friend persistent_type; + using impl_t = typename persistent_type::impl_t; + + map_transient(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty(); + +public: + // Semi-private + const impl_t& impl() const { return impl_; } +}; } // namespace immer diff --git a/immer/refcount/no_refcount_policy.hpp b/immer/refcount/no_refcount_policy.hpp index d726d2c334..5706858b12 100644 --- a/immer/refcount/no_refcount_policy.hpp +++ b/immer/refcount/no_refcount_policy.hpp @@ -24,7 +24,6 @@ struct no_refcount_policy void inc() {} bool dec() { return false; } - void dec_unsafe() {} bool unique() { return false; } }; diff --git a/immer/refcount/refcount_policy.hpp b/immer/refcount/refcount_policy.hpp index 7a8e15a822..52b516e859 100644 --- a/immer/refcount/refcount_policy.hpp +++ b/immer/refcount/refcount_policy.hpp @@ -34,12 +34,6 @@ struct refcount_policy bool dec() { return 1 == refcount.fetch_sub(1, std::memory_order_acq_rel); } - void dec_unsafe() - { - assert(refcount.load() > 1); - refcount.fetch_sub(1, std::memory_order_relaxed); - } - bool unique() { return refcount == 1; } }; diff --git a/immer/refcount/unsafe_refcount_policy.hpp b/immer/refcount/unsafe_refcount_policy.hpp index 0d1417d1a8..df65019b42 100644 --- a/immer/refcount/unsafe_refcount_policy.hpp +++ b/immer/refcount/unsafe_refcount_policy.hpp @@ -31,7 +31,6 @@ struct unsafe_refcount_policy void inc() { ++refcount; } bool dec() { return --refcount == 0; } - void dec_unsafe() { --refcount; } bool unique() { return refcount == 1; } }; diff --git a/immer/set.hpp b/immer/set.hpp index 922b14f596..df4192f510 100644 --- a/immer/set.hpp +++ b/immer/set.hpp @@ -62,13 +62,15 @@ class set { using impl_t = detail::hamts::champ; + using move_t = + std::integral_constant; + struct project_value_ptr { const T* operator()(const T& v) const noexcept { return &v; } }; public: - using key_type = T; using value_type = T; using size_type = detail::hamts::size_t; using diference_type = std::ptrdiff_t; @@ -83,12 +85,33 @@ public: using transient_type = set_transient; + using memory_policy_type = MemoryPolicy; + /*! * Default constructor. It creates a set of `size() == 0`. It * does not allocate memory and its complexity is @f$ O(1) @f$. */ set() = default; + /*! + * Constructs a set containing the elements in `values`. + */ + set(std::initializer_list values) + : impl_{impl_t::from_initializer_list(values)} + {} + + /*! + * Constructs a set containing the elements in the range + * defined by the input iterator `first` and range sentinel `last`. + */ + template , + bool> = true> + set(Iter first, Sent last) + : impl_{impl_t::from_range(first, last)} + {} + /*! * Returns an iterator pointing at the first element of the * collection. It does not allocate memory and its complexity is @@ -125,7 +148,9 @@ public: * This overload participates in overload resolution only if * `Hash::is_transparent` is valid and denotes a type. */ - template + template IMMER_NODISCARD size_type count(const K& value) const { return impl_.template get, @@ -164,7 +189,9 @@ public: * This overload participates in overload resolution only if * `Hash::is_transparent` is valid and denotes a type. */ - template + template IMMER_NODISCARD const T* find(const K& value) const { return impl_.template get, typename MemoryPolicy = default_memory_policy, detail::hamts::bits_t B = default_bits> -class set_transient; +class set_transient : MemoryPolicy::transience_t::owner +{ + using base_t = typename MemoryPolicy::transience_t::owner; + using owner_t = base_t; + +public: + using persistent_type = set; + + using value_type = T; + using size_type = detail::hamts::size_t; + using diference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = Equal; + using reference = const T&; + using const_reference = const T&; + + using iterator = typename persistent_type::iterator; + using const_iterator = iterator; + + /*! + * Default constructor. It creates a set of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + set_transient() = default; + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + IMMER_NODISCARD iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD iterator end() const + { + return {impl_, typename iterator::end_t{}}; + } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD size_type size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD bool empty() const { return impl_.size == 0; } + + /*! + * Returns `1` when `value` is contained in the set or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD size_type count(const K& value) const + { + return impl_.template get, + detail::constantly>(value); + } + + /*! + * Returns `1` when `value` is contained in the set or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD size_type count(const T& value) const + { + return impl_.template get, + detail::constantly>(value); + } + + /*! + * Returns a pointer to the value if `value` is contained in the + * set, or nullptr otherwise. + * It does not allocate memory and its complexity is *effectively* + * @f$ O(1) @f$. + */ + IMMER_NODISCARD const T* find(const T& value) const + { + return impl_.template get>(value); + } + + /*! + * Returns a pointer to the value if `value` is contained in the + * set, or nullptr otherwise. + * It does not allocate memory and its complexity is *effectively* + * @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD const T* find(const K& value) const + { + return impl_.template get>(value); + } + + /*! + * Inserts `value` into the set, and does nothing if the value is already + * there It may allocate memory and its complexity is *effectively* @f$ + * O(1) @f$. + */ + void insert(T value) { impl_.add_mut(*this, std::move(value)); } + + /*! + * Removes the `value` from the set, doing nothing if the value is not in + * the set. It may allocate memory and its complexity is *effectively* @f$ + * O(1) @f$. + */ + void erase(const T& value) { impl_.sub_mut(*this, value); } + + /*! + * Returns an @a immutable form of this container, an + * `immer::set`. + */ + IMMER_NODISCARD persistent_type persistent() & + { + this->owner_t::operator=(owner_t{}); + return impl_; + } + IMMER_NODISCARD persistent_type persistent() && { return std::move(impl_); } + +private: + friend persistent_type; + using impl_t = typename persistent_type::impl_t; + + set_transient(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty(); + +public: + // Semi-private + const impl_t& impl() const { return impl_; } +}; } // namespace immer diff --git a/immer/table.hpp b/immer/table.hpp new file mode 100644 index 0000000000..0e977c6a23 --- /dev/null +++ b/immer/table.hpp @@ -0,0 +1,547 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace immer { + +template +class table_transient; + +/*! + * Function template to get the key in `immer::table_key_fn`. + * It assumes the key is `id` class member. + */ +template +auto get_table_key(T const& x) -> decltype(x.id) +{ + return x.id; +} + +/*! + * Function template to set the key in `immer::table_key_fn`. + * It assumes the key is `id` class member. + */ +template +auto set_table_key(T x, K&& k) -> T +{ + x.id = std::forward(k); + return x; +} + +/*! + * Default value for `KeyFn` in `immer::table`. + * It assumes the key is `id` class member. + */ +struct table_key_fn +{ + template + decltype(auto) operator()(T&& x) const + { + return get_table_key(std::forward(x)); + } + + template + auto operator()(T&& x, K&& k) const + { + return set_table_key(std::forward(x), std::forward(k)); + } +}; + +template +using table_key_t = std::decay_t()))>; + +/*! + * Immutable unordered set of values of type `T`. Values are indexed via + * `operator()(const T&)` from `KeyFn` template parameter. + * By default, key is `&T::id`. + * + * @tparam T The type of the values to be stored in the container. + * @tparam KeyFn Type which implements `operator()(const T&)` + * @tparam Hash The type of a function object capable of hashing + * values of type `T`. + * @tparam Equal The type of a function object capable of comparing + * values of type `T`. + * @tparam MemoryPolicy Memory management policy. See @ref + * memory_policy. + * + * @rst + * + * This container is based on the `immer::map` underlying data structure. + * + * This container provides a good trade-off between cache locality, + * search, update performance and structural sharing. It does so by + * storing the data in contiguous chunks of :math:`2^{B}` elements. + * When storing big objects, the size of these contiguous chunks can + * become too big, damaging performance. If this is measured to be + * problematic for a specific use-case, it can be solved by using a + * `immer::box` to wrap the type `T`. + * + * **Example** + * .. literalinclude:: ../example/table/intro.cpp + * :language: c++ + * :start-after: intro/start + * :end-before: intro/end + * + * @endrst + * + */ +template >, + typename Equal = std::equal_to>, + typename MemoryPolicy = default_memory_policy, + detail::hamts::bits_t B = default_bits> +class table +{ + using K = table_key_t; + using value_t = T; + + using move_t = + std::integral_constant; + + struct project_value + { + const T& operator()(const value_t& v) const noexcept { return v; } + T&& operator()(value_t&& v) const noexcept { return std::move(v); } + }; + + struct project_value_ptr + { + const T* operator()(const value_t& v) const noexcept + { + return std::addressof(v); + } + }; + + struct combine_value + { + template + auto operator()(Kf&& k, Tf&& v) const + { + return KeyFn{}(std::forward(v), std::forward(k)); + } + }; + + struct default_value + { + const T& operator()() const + { + static T v{}; + return v; + } + }; + + struct error_value + { + const T& operator()() const + { + IMMER_THROW(std::out_of_range{"key not found"}); + } + }; + + struct hash_key + { + std::size_t operator()(const value_t& v) const + { + return Hash{}(KeyFn{}(v)); + } + + template + std::size_t operator()(const Key& v) const + { + return Hash{}(v); + } + }; + + struct equal_key + { + bool operator()(const value_t& a, const value_t& b) const + { + auto ke = KeyFn{}; + return Equal{}(ke(a), ke(b)); + } + + template + bool operator()(const value_t& a, const Key& b) const + { + return Equal{}(KeyFn{}(a), b); + } + }; + + struct equal_value + { + bool operator()(const value_t& a, const value_t& b) const + { + return a == b; + } + }; + + using impl_t = + detail::hamts::champ; + +public: + using key_type = K; + using mapped_type = T; + using value_type = T; + using size_type = detail::hamts::size_t; + using diference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = Equal; + using reference = const value_type&; + using const_reference = const value_type&; + + using iterator = detail::hamts:: + champ_iterator; + using const_iterator = iterator; + + using transient_type = + table_transient; + + using memory_policy_type = MemoryPolicy; + + /*! + * Constructs a table containing the elements in `values`. + */ + table(std::initializer_list values) + : impl_{impl_t::from_initializer_list(values)} + {} + + /*! + * Constructs a table containing the elements in the range + * defined by the input iterator `first` and range sentinel `last`. + */ + template , + bool> = true> + table(Iter first, Sent last) + : impl_{impl_t::from_range(first, last)} + {} + + /*! + * Default constructor. It creates a table of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + table() = default; + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + IMMER_NODISCARD iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD iterator end() const + { + return {impl_, typename iterator::end_t{}}; + } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD size_type size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD bool empty() const { return impl_.size == 0; } + + /*! + * Returns `1` when the key `k` is contained in the table or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD size_type count(const Key& k) const + { + return impl_.template get, + detail::constantly>(k); + } + + /*! + * Returns `1` when the key `k` is contained in the table or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD size_type count(const K& k) const + { + return impl_.template get, + detail::constantly>(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If there is no entry with such a key in the table, it returns a + * default constructed value. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD const T& operator[](const Key& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If there is no entry with such a key in the table, it returns a + * default constructed value. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD const T& operator[](const K& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If there is no entry with such a key in the table, throws an + * `std::out_of_range` error. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + const T& at(const Key& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If there is no entry with such a key in the table, throws an + * `std::out_of_range` error. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + const T& at(const K& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a pointer to the value associated with the key `k`. + * If there is no entry with such a key in the table, + * a `nullptr` is returned. It does not allocate memory and + * its complexity is *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD const T* find(const K& k) const + { + return impl_.template get>(k); + } + + /*! + * Returns a pointer to the value associated with the key `k`. + * If there is no entry with such a key in the table, + * a `nullptr` is returned. It does not allocate memory and + * its complexity is *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD const T* find(const Key& k) const + { + return impl_.template get>(k); + } + + IMMER_NODISCARD bool operator==(const table& other) const + { + return impl_.template equals(other.impl_); + } + + IMMER_NODISCARD bool operator!=(const table& other) const + { + return !(*this == other); + } + + /*! + * Returns a table containing the `value`. + * If there is an entry with its key is already, + * it replaces this entry by `value`. + * It may allocate memory and its complexity is *effectively* @f$ + * O(1) @f$. + */ + IMMER_NODISCARD table insert(value_type value) const& + { + return impl_.add(std::move(value)); + } + + /*! + * Returns a table containing the `value`. + * If there is an entry with its key is already, + * it replaces this entry by `value`. + * It may allocate memory and its complexity is *effectively* @f$ + * O(1) @f$. + */ + IMMER_NODISCARD decltype(auto) insert(value_type value) && + { + return insert_move(move_t{}, std::move(value)); + } + + /*! + * Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps + * `T` to `T`. The key `k` will be replaced inside the value returned by + * `fn`. It may allocate memory and its complexity is *effectively* @f$ O(1) + * @f$. + */ + template + IMMER_NODISCARD table update(key_type k, Fn&& fn) const& + { + return impl_ + .template update( + std::move(k), std::forward(fn)); + } + template + IMMER_NODISCARD decltype(auto) update(key_type k, Fn&& fn) && + { + return update_move(move_t{}, std::move(k), std::forward(fn)); + } + + /*! + * Returns `this.count(k) ? this->insert(fn((*this)[k])) : *this`. In + * particular, `fn` maps `T` to `T`. The key `k` will be replaced inside the + * value returned by `fn`. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + template + IMMER_NODISCARD table update_if_exists(key_type k, Fn&& fn) const& + { + return impl_.template update_if_exists( + std::move(k), std::forward(fn)); + } + template + IMMER_NODISCARD decltype(auto) update_if_exists(key_type k, Fn&& fn) && + { + return update_if_exists_move( + move_t{}, std::move(k), std::forward(fn)); + } + + /*! + * Returns a table without entries with given key `k`. If the key is not + * present it returns `*this`. It may allocate + * memory and its complexity is *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD table erase(const K& k) const& { return impl_.sub(k); } + + /*! + * Returns a table without entries with given key `k`. If the key is not + * present it returns `*this`. It may allocate + * memory and its complexity is *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD decltype(auto) erase(const K& k) && + { + return erase_move(move_t{}, k); + } + + /*! + * Returns a @a transient form of this container, an + * `immer::table_transient`. + */ + IMMER_NODISCARD transient_type transient() const& + { + return transient_type{impl_}; + } + + /*! + * Returns a @a transient form of this container, an + * `immer::table_transient`. + */ + IMMER_NODISCARD transient_type transient() && + { + return transient_type{std::move(impl_)}; + } + + // Semi-private + const impl_t& impl() const { return impl_; } + +private: + friend transient_type; + + table&& insert_move(std::true_type, value_type value) + { + impl_.add_mut({}, std::move(value)); + return std::move(*this); + } + table insert_move(std::false_type, value_type value) + { + return impl_.add(std::move(value)); + } + + template + table&& update_move(std::true_type, key_type k, Fn&& fn) + { + impl_.template update_mut( + {}, std::move(k), std::forward(fn)); + return std::move(*this); + } + template + table update_move(std::false_type, key_type k, Fn&& fn) + { + return impl_ + .template update( + std::move(k), std::forward(fn)); + } + + template + table&& update_if_exists_move(std::true_type, key_type k, Fn&& fn) + { + impl_.template update_if_exists_mut( + {}, std::move(k), std::forward(fn)); + return std::move(*this); + } + template + table update_if_exists_move(std::false_type, key_type k, Fn&& fn) + { + return impl_.template update_if_exists( + std::move(k), std::forward(fn)); + } + + table&& erase_move(std::true_type, const key_type& value) + { + impl_.sub_mut({}, value); + return std::move(*this); + } + table erase_move(std::false_type, const key_type& value) + { + return impl_.sub(value); + } + + table(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty(); +}; + +} // namespace immer diff --git a/immer/table_transient.hpp b/immer/table_transient.hpp new file mode 100644 index 0000000000..30b0a8c689 --- /dev/null +++ b/immer/table_transient.hpp @@ -0,0 +1,281 @@ +#pragma once + +#include +#include +#include + +namespace immer { + +template +class table; + +/*! + * Mutable version of `immer::table`. + * + * @rst + * + * Refer to :doc:`transients` to learn more about when and how to use + * the mutable versions of immutable containers. + * + * @endrst + */ +template +class table_transient : MemoryPolicy::transience_t::owner +{ + using K = std::decay_t()))>; + using base_t = typename MemoryPolicy::transience_t::owner; + using owner_t = base_t; + +public: + using persistent_type = table; + using key_type = K; + using mapped_type = T; + using value_type = T; + using size_type = detail::hamts::size_t; + using diference_type = std::ptrdiff_t; + using hasher = Hash; + using key_equal = Equal; + using reference = const value_type&; + using const_reference = const value_type&; + + using iterator = typename persistent_type::iterator; + using const_iterator = iterator; + + /*! + * Default constructor. It creates a table of `size() == 0`. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + table_transient() = default; + + /*! + * Returns an iterator pointing at the first element of the + * collection. It does not allocate memory and its complexity is + * @f$ O(1) @f$. + */ + IMMER_NODISCARD iterator begin() const { return {impl_}; } + + /*! + * Returns an iterator pointing just after the last element of the + * collection. It does not allocate and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD iterator end() const + { + return {impl_, typename iterator::end_t{}}; + } + + /*! + * Returns the number of elements in the container. It does + * not allocate memory and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD size_type size() const { return impl_.size; } + + /*! + * Returns `true` if there are no elements in the container. It + * does not allocate memory and its complexity is @f$ O(1) @f$. + */ + IMMER_NODISCARD bool empty() const { return impl_.size == 0; } + + /*! + * Returns `1` when the key `k` is contained in the table or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD size_type count(const Key& k) const + { + return impl_.template get, + detail::constantly>(k); + } + + /*! + * Returns `1` when the key `k` is contained in the table or `0` + * otherwise. It won't allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD size_type count(const K& k) const + { + return impl_.template get, + detail::constantly>(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If there is no entry with such a key in the table, it returns a + * default constructed value. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD const T& operator[](const Key& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If there is no entry with such a key in the table, it returns a + * default constructed value. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD const T& operator[](const K& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If there is no entry with such a key in the table, throws an + * `std::out_of_range` error. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + const T& at(const Key& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a `const` reference to the values associated to the key + * `k`. If there is no entry with such a key in the table, throws an + * `std::out_of_range` error. It does not allocate memory and its + * complexity is *effectively* @f$ O(1) @f$. + */ + const T& at(const K& k) const + { + return impl_.template get(k); + } + + /*! + * Returns a pointer to the value associated with the key `k`. + * If there is no entry with such a key in the table, + * a `nullptr` is returned. It does not allocate memory and + * its complexity is *effectively* @f$ O(1) @f$. + */ + IMMER_NODISCARD const T* find(const K& k) const + { + return impl_.template get>(k); + } + + /*! + * Returns a pointer to the value associated with the key `k`. + * If there is no entry with such a key in the table, + * a `nullptr` is returned. It does not allocate memory and + * its complexity is *effectively* @f$ O(1) @f$. + * + * This overload participates in overload resolution only if + * `Hash::is_transparent` is valid and denotes a type. + */ + template + IMMER_NODISCARD const T* find(const Key& k) const + { + return impl_.template get>(k); + } + + /*! + * Inserts `value` to the table. + * If there is an entry with its key is already, + * it replaces this entry by `value`. + * It may allocate memory and its complexity is *effectively* @f$ + * O(1) @f$. + */ + void insert(value_type value) { impl_.add_mut(*this, std::move(value)); } + + /*! + * Returns `this->insert(fn((*this)[k]))`. In particular, `fn` maps `T` to + * `T`. The key `k` will be set into the value returned bu `fn`. It may + * allocate memory and its complexity is *effectively* @f$ O(1) @f$. + */ + template + void update(key_type k, Fn&& fn) + { + impl_.template update_mut( + *this, std::move(k), std::forward(fn)); + } + + /*! + * Returns `this->insert(fn((*this)[k]))` when `this->count(k) > 0`. In + * particular, `fn` maps `T` to `T`. The key `k` will be replaced into the + * value returned by `fn`. It may allocate memory and its complexity is + * *effectively* @f$ O(1) @f$. + */ + template + void update_if_exists(key_type k, Fn&& fn) + { + impl_.template update_if_exists_mut< + typename persistent_type::project_value, + typename persistent_type::combine_value>( + *this, std::move(k), std::forward(fn)); + } + + /*! + * Removes table entry by given key `k` if there is any. It may allocate + * memory and its complexity is *effectively* @f$ O(1) @f$. + */ + void erase(const K& k) { impl_.sub_mut(*this, k); } + + /*! + * Returns an @a immutable form of this container, an + * `immer::table`. + */ + IMMER_NODISCARD persistent_type persistent() & + { + this->owner_t::operator=(owner_t{}); + return impl_; + } + + /*! + * Returns an @a immutable form of this container, an + * `immer::table`. + */ + IMMER_NODISCARD persistent_type persistent() && { return std::move(impl_); } + +private: + friend persistent_type; + using impl_t = typename persistent_type::impl_t; + + table_transient(impl_t impl) + : impl_(std::move(impl)) + {} + + impl_t impl_ = impl_t::empty(); + +public: + // Semi-private + const impl_t& impl() const { return impl_; } +}; + +} // namespace immer diff --git a/immer/vector.hpp b/immer/vector.hpp index a8310bf7e2..8c8ab05d69 100644 --- a/immer/vector.hpp +++ b/immer/vector.hpp @@ -40,7 +40,7 @@ class vector_transient; * * @rst * - * This cotainer provides a good trade-off between cache locality, + * This container provides a good trade-off between cache locality, * random access, update performance and structural sharing. It does * so by storing the data in contiguous chunks of :math:`2^{BL}` * elements. By default, when ``sizeof(T) == sizeof(void*)`` then @@ -340,6 +340,17 @@ public: IMMER_NODISCARD transient_type transient() const& { return impl_; } IMMER_NODISCARD transient_type transient() && { return std::move(impl_); } + /*! + * Returns a value that can be used as identity for the container. If two + * values have the same identity, they are guaranteed to be equal and to + * contain the same objects. However, two equal containers are not + * guaranteed to have the same identity. + */ + std::pair identity() const + { + return {impl_.root, impl_.tail}; + } + // Semi-private const impl_t& impl() const { return impl_; } diff --git a/shell.nix b/shell.nix index 04fd7e92a5..1dfbe1eb82 100644 --- a/shell.nix +++ b/shell.nix @@ -49,6 +49,7 @@ tc.stdenv.mkDerivation rec { buildInputs = [ tc.cc git + catch2 cmake pkgconfig ninja @@ -58,6 +59,7 @@ tc.stdenv.mkDerivation rec { boost boehmgc fmt + valgrind benchmarks.c_rrb benchmarks.steady benchmarks.chunkedseq diff --git a/spm.cpp b/spm.cpp new file mode 100644 index 0000000000..17c88eb332 --- /dev/null +++ b/spm.cpp @@ -0,0 +1,4 @@ + +#include + +// Just so we can compile with SPM. diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4528a15799..7fec6adb5a 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -16,8 +16,7 @@ foreach(_file IN LISTS immer_unit_tests) add_dependencies(tests ${_target}) target_compile_definitions(${_target} PUBLIC -DIMMER_OSS_FUZZ_DATA_PATH="${CMAKE_CURRENT_SOURCE_DIR}/oss-fuzz/data" - DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CATCH_CONFIG_MAIN) - target_link_libraries(${_target} PUBLIC immer-dev) + target_link_libraries(${_target} PUBLIC immer-dev Catch2::Catch2) add_test("test/${_output}" ${_output}) endforeach() diff --git a/test/algorithm.cpp b/test/algorithm.cpp new file mode 100644 index 0000000000..6a785dbd90 --- /dev/null +++ b/test/algorithm.cpp @@ -0,0 +1,221 @@ +#include +#include +#include +#include +#include +#include + +#include + +#include + +struct thing +{ + int id = 0; +}; +bool operator==(const thing& a, const thing& b) { return a.id == b.id; } + +TEST_CASE("iteration exposes const") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::for_each(v, [](auto&& x) { + static_assert(std::is_same::value, ""); + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); + do_check(immer::map{}); + do_check(immer::set{}); + do_check(immer::table{}); +} + +TEST_CASE("chunked iteration exposes const") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::for_each_chunk(v, [](auto a, auto b) { + static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); + do_check(immer::map{}); + do_check(immer::set{}); + do_check(immer::table{}); +} + +TEST_CASE("accumulate") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::accumulate(v, value_t{}, [](auto&& a, auto&& b) { + static_assert(std::is_same::value, ""); + static_assert(std::is_same::value, ""); + return std::move(a); + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); + do_check(immer::map{}); + do_check(immer::set{}); + do_check(immer::table{}); +} + +TEST_CASE("diffing exposes const") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::diff( + v, + v, + [](auto&& x) { + static_assert(std::is_same::value, + ""); + }, + [](auto&& x) { + static_assert(std::is_same::value, + ""); + }, + [](auto&& x, auto&& y) { + static_assert(std::is_same::value, + ""); + static_assert(std::is_same::value, + ""); + }); + }; + + do_check(immer::map{}); + do_check(immer::set{}); + do_check(immer::table{}); +} + +TEST_CASE("all_of") +{ + auto do_check = [](auto v) { + using value_t = typename decltype(v)::value_type; + immer::all_of(v, [](auto&& x) { + static_assert(std::is_same::value, ""); + return true; + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); + // not supported + // do_check(immer::map{}); + // do_check(immer::set{}); + // do_check(immer::table{}); +} + +TEST_CASE("update vectors") +{ + auto do_check = [](auto v) { + if (false) + (void) v.update(0, [](auto&& x) { + using type_t = std::decay_t; + // vectors do copy first the whole array, and then move the + // copied value into the function + static_assert(std::is_same::value, ""); + return x; + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); +} + +TEST_CASE("update maps") +{ + auto do_check = [](auto v) { + (void) v.update(0, [](auto&& x) { + using type_t = std::decay_t; + // for maps, we actually do not make a copy at all but pase the + // original instance directly, as const.. + static_assert(std::is_same::value, ""); + return x; + }); + }; + + do_check(immer::map{}); + do_check(immer::table{}); +} + +TEST_CASE("update_if_exists maps") +{ + auto do_check = [](auto v) { + (void) v.update_if_exists(0, [](auto&& x) { + using type_t = std::decay_t; + // for maps, we actually do not make a copy at all but pase the + // original instance directly, as const.. + static_assert(std::is_same::value, ""); + return x; + }); + }; + + do_check(immer::map{}); + do_check(immer::table{}); +} + +TEST_CASE("update vectors move") +{ + auto do_check = [](auto v) { + if (false) + (void) std::move(v).update(0, [](auto&& x) { + using type_t = std::decay_t; + // vectors do copy first the whole array, and then move the + // copied value into the function + static_assert(std::is_same::value, ""); + return x; + }); + }; + + do_check(immer::vector{}); + do_check(immer::flex_vector{}); + do_check(immer::array{}); +} + +TEST_CASE("update maps move") +{ + auto do_check = [](auto v) { + (void) std::move(v).update(0, [](auto&& x) { + using type_t = std::decay_t; + // for maps, we actually do not make a copy at all but pase the + // original instance directly, as const.. + static_assert(std::is_same::value || + std::is_same::value, + ""); + return x; + }); + }; + + do_check(immer::map{}); + do_check(immer::table{}); +} + +TEST_CASE("update_if_exists maps move") +{ + auto do_check = [](auto v) { + (void) std::move(v).update_if_exists(0, [](auto&& x) { + using type_t = std::decay_t; + // for maps, we actually do not make a copy at all but pase the + // original instance directly, as const.. + static_assert(std::is_same::value || + std::is_same::value, + ""); + return x; + }); + }; + + do_check(immer::map{}); + do_check(immer::table{}); +} diff --git a/test/array_transient/default.cpp b/test/array_transient/default.cpp index aa603c7b0a..cfa789b3fa 100644 --- a/test/array_transient/default.cpp +++ b/test/array_transient/default.cpp @@ -14,6 +14,10 @@ #include "../vector_transient/generic.ipp" +IMMER_RANGES_CHECK(std::ranges::contiguous_range>); +IMMER_RANGES_CHECK( + std::ranges::contiguous_range>); + TEST_CASE("array_transient default constructor compiles") { immer::array_transient transient; diff --git a/test/atom/generic.ipp b/test/atom/generic.ipp index 21ff74d7ef..648da3159c 100644 --- a/test/atom/generic.ipp +++ b/test/atom/generic.ipp @@ -10,7 +10,7 @@ #error "define the box template to use in ATOM_T" #endif -#include +#include template using BOX_T = typename ATOM_T::box_type; diff --git a/test/box/generic.ipp b/test/box/generic.ipp index e221cdc827..72ab9c8b76 100644 --- a/test/box/generic.ipp +++ b/test/box/generic.ipp @@ -10,7 +10,7 @@ #error "define the box template to use in BOX_T" #endif -#include +#include TEST_CASE("construction and copy") { diff --git a/test/box/recursive.cpp b/test/box/recursive.cpp index 4c8fce07de..46d1204308 100644 --- a/test/box/recursive.cpp +++ b/test/box/recursive.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include struct rec_vec { diff --git a/test/box/vector-of-boxes-transient.cpp b/test/box/vector-of-boxes-transient.cpp index 623c8ba37d..399be5478d 100644 --- a/test/box/vector-of-boxes-transient.cpp +++ b/test/box/vector-of-boxes-transient.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include TEST_CASE("issue-33") { diff --git a/test/detail/type_traits.cpp b/test/detail/type_traits.cpp index 8809418cce..632a25c90f 100644 --- a/test/detail/type_traits.cpp +++ b/test/detail/type_traits.cpp @@ -6,7 +6,7 @@ // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // -#include +#include #include #include #include diff --git a/test/experimental/dvektor.cpp b/test/experimental/dvektor.cpp index 437384a727..ffdd2e0fc7 100644 --- a/test/experimental/dvektor.cpp +++ b/test/experimental/dvektor.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include using namespace immer; @@ -27,7 +27,7 @@ TEST_CASE("instantiation") TEST_CASE("push back one element") { - SUBCASE("one element") + SECTION("one element") { const auto v1 = dvektor{}; auto v2 = v1.push_back(42); @@ -36,7 +36,7 @@ TEST_CASE("push back one element") CHECK(v2[0] == 42); } - SUBCASE("many elements") + SECTION("many elements") { const auto n = 666u; auto v = dvektor{}; @@ -56,7 +56,7 @@ TEST_CASE("update") for (auto i = 0u; i < n; ++i) v = v.push_back(i); - SUBCASE("assoc") + SECTION("assoc") { const auto u = v.assoc(3u, 13u); CHECK(u.size() == v.size()); @@ -67,7 +67,7 @@ TEST_CASE("update") CHECK(v[3u] == 3u); } - SUBCASE("assoc further") + SECTION("assoc further") { for (auto i = n; i < 666; ++i) v = v.push_back(i); @@ -88,7 +88,7 @@ TEST_CASE("update") CHECK(v[200u] == 200u); } - SUBCASE("assoc further more") + SECTION("assoc further more") { auto v = immer::dvektor{}; @@ -101,7 +101,7 @@ TEST_CASE("update") } } - SUBCASE("update") + SECTION("update") { const auto u = v.update(10u, [](auto x) { return x + 10; }); CHECK(u.size() == v.size()); @@ -123,13 +123,13 @@ TEST_CASE("big") for (auto i = 0u; i < n; ++i) v = v.push_back(i); - SUBCASE("read") + SECTION("read") { for (auto i = 0u; i < n; ++i) CHECK(v[i] == i); } - SUBCASE("assoc") + SECTION("assoc") { for (auto i = 0u; i < n; ++i) { v = v.assoc(i, i + 1); @@ -146,7 +146,7 @@ TEST_CASE("iterator") for (auto i = 0u; i < n; ++i) v = v.push_back(i); - SUBCASE("works with range loop") + SECTION("works with range loop") { auto i = 0u; for (const auto& x : v) @@ -154,16 +154,16 @@ TEST_CASE("iterator") CHECK(i == v.size()); } - SUBCASE("works with standard algorithms") + SECTION("works with standard algorithms") { auto s = std::vector(n); std::iota(s.begin(), s.end(), 0u); std::equal(v.begin(), v.end(), s.begin(), s.end()); } - SUBCASE("can go back from end") { CHECK(n - 1 == *--v.end()); } + SECTION("can go back from end") { CHECK(n - 1 == *--v.end()); } - SUBCASE("works with reversed range adaptor") + SECTION("works with reversed range adaptor") { auto r = v | boost::adaptors::reversed; auto i = n; @@ -171,7 +171,7 @@ TEST_CASE("iterator") CHECK(x == --i); } - SUBCASE("works with strided range adaptor") + SECTION("works with strided range adaptor") { auto r = v | boost::adaptors::strided(5); auto i = 0u; @@ -179,14 +179,14 @@ TEST_CASE("iterator") CHECK(x == 5 * i++); } - SUBCASE("works reversed") + SECTION("works reversed") { auto i = n; for (auto iter = v.rbegin(), last = v.rend(); iter != last; ++iter) CHECK(*iter == --i); } - SUBCASE("advance and distance") + SECTION("advance and distance") { auto i1 = v.begin(); auto i2 = i1 + 100; diff --git a/test/flex_vector/fuzzed-0.cpp b/test/flex_vector/fuzzed-0.cpp index 7bf283d804..ed0c5a6607 100644 --- a/test/flex_vector/fuzzed-0.cpp +++ b/test/flex_vector/fuzzed-0.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include @@ -174,7 +174,7 @@ TEST_CASE("bug: concatenate too big vectors") var4 = var4.push_back(42); } -#if __GNUC__ != 9 && __GNUC__ != 8 +#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG // Assertion `!p->relaxed()' failed SECTION("") { diff --git a/test/flex_vector/fuzzed-1.cpp b/test/flex_vector/fuzzed-1.cpp index 0eb25e7216..162c78eb5a 100644 --- a/test/flex_vector/fuzzed-1.cpp +++ b/test/flex_vector/fuzzed-1.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include @@ -159,7 +159,7 @@ TEST_CASE("bug: memory leak because of move update") var0 = std::move(var0).push_back(21); } -#if __GNUC__ != 9 && __GNUC__ != 8 +#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG SECTION("") { constexpr std::uint8_t input[] = { @@ -263,7 +263,8 @@ TEST_CASE("non-bug: crash") var4 = var4 + var4; var4 = var4.update(4, [](auto x) { return x + 1; }); } -#if __GNUC__ != 9 && __GNUC__ != 8 + +#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG SECTION("") { constexpr std::uint8_t input[] = { diff --git a/test/flex_vector/fuzzed-2.cpp b/test/flex_vector/fuzzed-2.cpp index 2ca33b12b9..a058ad6119 100644 --- a/test/flex_vector/fuzzed-2.cpp +++ b/test/flex_vector/fuzzed-2.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include @@ -172,7 +172,7 @@ TEST_CASE("bug: use after free on move-take") var0 = std::move(var0).take(68); } -#if __GNUC__ != 9 && __GNUC__ != 8 +#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG SECTION("") { constexpr std::uint8_t input[] = { diff --git a/test/flex_vector/fuzzed-3.cpp b/test/flex_vector/fuzzed-3.cpp index 6b020e6bdd..422497d37d 100644 --- a/test/flex_vector/fuzzed-3.cpp +++ b/test/flex_vector/fuzzed-3.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include @@ -205,7 +205,7 @@ TEST_CASE("bug: concat with moving the right side") var0 = var0 + std::move(var1); } -#if __GNUC__ != 9 && __GNUC__ != 8 +#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG SECTION("vm") { constexpr std::uint8_t input[] = { diff --git a/test/flex_vector/fuzzed-4.cpp b/test/flex_vector/fuzzed-4.cpp index 3df3c6fb46..0a726bef1a 100644 --- a/test/flex_vector/fuzzed-4.cpp +++ b/test/flex_vector/fuzzed-4.cpp @@ -8,7 +8,7 @@ #include "extra/fuzzer/fuzzer_input.hpp" #include -#include +#include #include #include #include @@ -231,7 +231,7 @@ TEST_CASE("bug: concatenating transients") t1.append(t0); } -#if __GNUC__ != 9 && __GNUC__ != 8 +#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG SECTION("") { constexpr std::uint8_t input[] = { @@ -271,7 +271,7 @@ TEST_CASE("bug: concatenating moved transients") t2.append(std::move(t0)); } -#if __GNUC__ != 9 && __GNUC__ != 8 +#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG SECTION("") { constexpr std::uint8_t input[] = { @@ -309,7 +309,7 @@ TEST_CASE("bug: concatenating moved transients") t0 = {}; } -#if __GNUC__ != 9 && __GNUC__ != 8 +#if !IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG SECTION("") { return; @@ -348,7 +348,7 @@ TEST_CASE("bug: aegsdas") t1 = {}; } -#if __GNUC__ != 9 && __GNUC__ != 8 +#ifndef IMMER_DISABLE_FUZZER_DUE_TO_GCC_BUG SECTION("") { constexpr std::uint8_t input[] = { diff --git a/test/flex_vector/generic.ipp b/test/flex_vector/generic.ipp index a3fee2644b..f01f48ca3d 100644 --- a/test/flex_vector/generic.ipp +++ b/test/flex_vector/generic.ipp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include @@ -29,6 +29,9 @@ #error "define the vector template to use in VECTOR_T" #endif +IMMER_RANGES_CHECK( + std::ranges::random_access_range>); + template > auto make_test_flex_vector(unsigned min, unsigned max) { @@ -106,6 +109,18 @@ TEST_CASE("push_front") } } +TEST_CASE("random_access iteration") +{ + auto v = make_test_flex_vector(0, 10); + auto iter = v.begin(); + CHECK(*iter == 0); + CHECK(iter[0] == 0); + CHECK(iter[3] == 3); + CHECK(iter[9] == 9); + iter += 4; + CHECK(iter[-4] == 0); +} + TEST_CASE("concat") { #if IMMER_SLOW_TESTS diff --git a/test/flex_vector/issue-45.cpp b/test/flex_vector/issue-45.cpp index 76bd77b2e2..3ff670e705 100644 --- a/test/flex_vector/issue-45.cpp +++ b/test/flex_vector/issue-45.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include #if IMMER_CXX_STANDARD >= 17 diff --git a/test/flex_vector_transient/generic.ipp b/test/flex_vector_transient/generic.ipp index 8c93cd163e..200e7d0a89 100644 --- a/test/flex_vector_transient/generic.ipp +++ b/test/flex_vector_transient/generic.ipp @@ -14,7 +14,7 @@ #include #include -#include +#include #include #include @@ -33,6 +33,11 @@ #error "define the vector template to use in VECTOR_T" #endif +IMMER_RANGES_CHECK( + std::ranges::random_access_range>); +IMMER_RANGES_CHECK( + std::ranges::random_access_range>); + template > auto make_test_flex_vector(unsigned min, unsigned max) { diff --git a/test/map/default.cpp b/test/map/default.cpp index cb453c4f81..71567c2307 100644 --- a/test/map/default.cpp +++ b/test/map/default.cpp @@ -6,6 +6,8 @@ // See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt // +#define IMMER_DEBUG_STATS 1 + #include #define MAP_T ::immer::map diff --git a/test/map/gc.cpp b/test/map/gc.cpp index 9a4eaebdcf..26646ee2fc 100644 --- a/test/map/gc.cpp +++ b/test/map/gc.cpp @@ -23,4 +23,5 @@ template ; #define MAP_T test_map_t +#define IMMER_IS_LIBGC_TEST 1 #include "generic.ipp" diff --git a/test/map/generic.ipp b/test/map/generic.ipp index fc9c6037d0..e9b6cf586b 100644 --- a/test/map/generic.ipp +++ b/test/map/generic.ipp @@ -13,15 +13,21 @@ #endif #include +#include #include "test/dada.hpp" #include "test/util.hpp" -#include +#include #include +#include #include +IMMER_RANGES_CHECK(std::ranges::forward_range>); + +using memory_policy_t = MAP_T::memory_policy_type; + template auto make_generator() { @@ -66,7 +72,7 @@ auto make_test_map(unsigned n) { auto s = MAP_T{}; for (auto i = 0u; i < n; ++i) - s = s.insert({i, i}); + s = std::move(s).insert({i, i}); return s; } @@ -74,7 +80,7 @@ auto make_test_map(const std::vector>& vals) { auto s = MAP_T{}; for (auto&& v : vals) - s = s.insert(v); + s = std::move(s).insert(v); return s; } @@ -84,6 +90,7 @@ TEST_CASE("instantiation") { auto v = MAP_T{}; CHECK(v.size() == 0u); + CHECK(v.identity() == MAP_T{}.identity()); } } @@ -102,6 +109,19 @@ TEST_CASE("basic insertion") CHECK(v3.count(42) == 1); } +TEST_CASE("initializer list and range constructors") +{ + auto v0 = std::unordered_map{ + {{"foo", 42}, {"bar", 13}, {"baz", 18}, {"zab", 64}}}; + auto v1 = MAP_T{ + {{"foo", 42}, {"bar", 13}, {"baz", 18}, {"zab", 64}}}; + auto v2 = MAP_T{v0.begin(), v0.end()}; + CHECK(v1.size() == 4); + CHECK(v1.count(std::string{"foo"}) == 1); + CHECK(v1.at(std::string{"bar"}) == 13); + CHECK(v1 == v2); +} + TEST_CASE("accessor") { const auto n = 666u; @@ -151,8 +171,39 @@ TEST_CASE("equals and setting") CHECK(v.set(1234, 42) == v.insert({1234, 42})); CHECK(v.update(1234, [](auto&& x) { return x + 1; }) == v.set(1234, 1)); CHECK(v.update(42, [](auto&& x) { return x + 1; }) == v.set(42, 43)); + + CHECK(v.update_if_exists(1234, [](auto&& x) { return x + 1; }) == v); + CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }) == + v.set(42, 43)); + + CHECK(v.update_if_exists(1234, [](auto&& x) { return x + 1; }).identity() == + v.identity()); + CHECK(v.update_if_exists(42, [](auto&& x) { return x + 1; }).identity() != + v.set(42, 43).identity()); + +#if IMMER_DEBUG_STATS + std::cout << (v.impl().get_debug_stats() + v.impl().get_debug_stats()) + .get_summary(); +#endif } +#if IMMER_DEBUG_STATS +TEST_CASE("debug stats") +{ + { + std::cout + << immer::map{}.impl().get_debug_stats().get_summary(); + } + { + immer::map map; + for (int i = 0; i <= 10; i++) { + map = std::move(map).set(i, i); + } + std::cout << map.impl().get_debug_stats().get_summary(); + } +} +#endif + TEST_CASE("iterator") { const auto N = 666u; @@ -216,12 +267,158 @@ TEST_CASE("update a lot") { auto v = make_test_map(666u); - for (decltype(v.size()) i = 0; i < v.size(); ++i) { - v = v.update(i, [](auto&& x) { return x + 1; }); - CHECK(v[i] == i + 1); + SECTION("immutable") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = v.update(i, [](auto&& x) { return x + 1; }); + CHECK(v[i] == i + 1); + } + } + SECTION("move") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = std::move(v).update(i, [](auto&& x) { return x + 1; }); + CHECK(v[i] == i + 1); + } + } + + SECTION("erase") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = std::move(v).erase(i); + CHECK(v.count(i) == 0); + } } } +TEST_CASE("update_if_exists a lot") +{ + auto v = make_test_map(666u); + + SECTION("immutable") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = v.update_if_exists(i, [](auto&& x) { return x + 1; }); + CHECK(v[i] == i + 1); + } + } + SECTION("move") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = std::move(v).update_if_exists(i, + [](auto&& x) { return x + 1; }); + CHECK(v[i] == i + 1); + } + } +} + +#if !IMMER_IS_LIBGC_TEST +TEST_CASE("update boxed move string") +{ + constexpr auto N = 666u; + constexpr auto S = 7; + auto s = MAP_T>{}; + SECTION("preserve immutability") + { + auto s0 = s; + auto i0 = 0u; + // insert + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).update(std::to_string(i), + [&](auto&&) { return std::to_string(i); }); + { + CHECK(s.size() == i + 1); + for (auto j : test_irange(0u, i + 1)) { + CHECK(s.count(std::to_string(j)) == 1); + CHECK(*s.find(std::to_string(j)) == std::to_string(j)); + } + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(std::to_string(j)) == 0); + } + { + CHECK(s0.size() == i0); + for (auto j : test_irange(0u, i0)) { + CHECK(s0.count(std::to_string(j)) == 1); + CHECK(*s0.find(std::to_string(j)) == std::to_string(j)); + } + for (auto j : test_irange(i0, N)) + CHECK(s0.count(std::to_string(j)) == 0); + } + } + // update + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).update(std::to_string(i), [&](auto&&) { + return std::to_string(i + 1); + }); + { + CHECK(s.size() == N); + for (auto j : test_irange(0u, i + 1)) + CHECK(*s.find(std::to_string(j)) == std::to_string(j + 1)); + for (auto j : test_irange(i + 1u, N)) + CHECK(*s.find(std::to_string(j)) == std::to_string(j)); + } + { + CHECK(s0.size() == N); + for (auto j : test_irange(0u, i0)) + CHECK(*s0.find(std::to_string(j)) == std::to_string(j + 1)); + for (auto j : test_irange(i0, N)) + CHECK(*s0.find(std::to_string(j)) == std::to_string(j)); + } + } + } +} +#endif + +#if !IMMER_IS_LIBGC_TEST +TEST_CASE("update_if_exists boxed move string") +{ + constexpr auto N = 666u; + constexpr auto S = 7; + auto s = MAP_T>{}; + SECTION("preserve immutability") + { + auto s0 = s; + auto i0 = 0u; + // insert + for (auto i = 0u; i < N; ++i) { + s = std::move(s).set(std::to_string(i), std::to_string(i)); + } + // update + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).update_if_exists(std::to_string(i), [&](auto&&) { + return std::to_string(i + 1); + }); + { + CHECK(s.size() == N); + for (auto j : test_irange(0u, i + 1)) + CHECK(*s.find(std::to_string(j)) == std::to_string(j + 1)); + for (auto j : test_irange(i + 1u, N)) + CHECK(*s.find(std::to_string(j)) == std::to_string(j)); + } + { + CHECK(s0.size() == N); + for (auto j : test_irange(0u, i0)) + CHECK(*s0.find(std::to_string(j)) == std::to_string(j + 1)); + for (auto j : test_irange(i0, N)) + CHECK(*s0.find(std::to_string(j)) == std::to_string(j)); + } + } + } +} +#endif + TEST_CASE("exception safety") { constexpr auto n = 2666u; @@ -231,12 +428,12 @@ TEST_CASE("exception safety") using dadaist_conflictor_map_t = typename dadaist_wrapper< MAP_T>::type; - SECTION("update collisions") + SECTION("update") { auto v = dadaist_map_t{}; auto d = dadaism{}; for (auto i = 0u; i < n; ++i) - v = v.set(i, i); + v = std::move(v).set(i, i); for (auto i = 0u; i < v.size();) { try { auto s = d.next(); @@ -252,6 +449,27 @@ TEST_CASE("exception safety") IMMER_TRACE_E(d.happenings); } + SECTION("update_if_exists") + { + auto v = dadaist_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).set(i, i); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = v.update_if_exists(i, [](auto x) { return x + 1; }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(i) == i + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(i) == i); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + SECTION("update collisisions") { auto vals = make_values_with_collisions(n); @@ -273,6 +491,143 @@ TEST_CASE("exception safety") CHECK(d.happenings > 0); IMMER_TRACE_E(d.happenings); } + + SECTION("update_if_exists collisisions") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = v.insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = v.update_if_exists(vals[i].first, + [](auto x) { return x + 1; }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first) == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + + SECTION("set collisisions") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = v.insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + auto x = vals[i].second; + v = v.set(vals[i].first, x + 1); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first) == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + + SECTION("set collisisions move") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = v.insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + auto x = vals[i].second; + v = std::move(v).set(vals[i].first, x + 1); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first) == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + + SECTION("update collisisions move") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = std::move(v).update(vals[i].first, + [](auto x) { return x + 1; }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first) == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + + SECTION("update_if_exists collisisions move") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = std::move(v).update_if_exists(vals[i].first, + [](auto x) { return x + 1; }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first) == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + + SECTION("erase collisisions move") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + // auto s = d.next(); + v = std::move(v).erase(vals[i].first); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.count(vals[i].first) == 0); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first) == vals[i].second); + } + CHECK(d.happenings == 0); + IMMER_TRACE_E(d.happenings); + } } namespace { @@ -363,3 +718,75 @@ TEST_CASE("issue 134") } } // namespace + +void test_diff(unsigned old_num, + unsigned add_num, + unsigned remove_num, + unsigned change_num) +{ + auto values = make_values_with_collisions(old_num + add_num); + std::vector> initial_values( + values.begin(), values.begin() + old_num); + std::vector> new_values( + values.begin() + old_num, values.end()); + auto map = make_test_map(initial_values); + + std::vector old_keys; + for (auto const& val : map) + old_keys.push_back(val.first); + + auto first_snapshot = map; + CHECK(old_num == first_snapshot.size()); + + // remove + auto shuffle = old_keys; + std::random_shuffle(shuffle.begin(), shuffle.end()); + std::vector remove_keys(shuffle.begin(), + shuffle.begin() + remove_num); + std::vector rest_keys(shuffle.begin() + remove_num, + shuffle.end()); + + using key_set = std::unordered_set; + key_set removed_keys(remove_keys.begin(), remove_keys.end()); + for (auto const& key : remove_keys) + map = map.erase(key); + CHECK(old_num - remove_num == map.size()); + + // add + key_set added_keys; + for (auto const& data : new_values) { + map = map.set(data.first, data.second); + added_keys.insert(data.first); + } + + // change + key_set changed_keys; + for (auto i = 0u; i < change_num; i++) { + auto key = rest_keys[i]; + map = map.update(key, [](auto val) { return ++val; }); + changed_keys.insert(key); + } + + diff( + first_snapshot, + map, + [&](auto const& data) { REQUIRE(added_keys.erase(data.first) > 0); }, + [&](auto const& data) { REQUIRE(removed_keys.erase(data.first) > 0); }, + [&](auto const& old_data, auto const& new_data) { + (void) old_data; + REQUIRE(changed_keys.erase(new_data.first) > 0); + }); + + CHECK(added_keys.empty()); + CHECK(changed_keys.empty()); + CHECK(removed_keys.empty()); +} + +TEST_CASE("diff") +{ + test_diff(16, 10, 10, 3); + test_diff(100, 10, 10, 10); + test_diff(1500, 10, 1000, 100); + test_diff(16, 1500, 10, 3); + test_diff(100, 0, 0, 50); +} diff --git a/test/map/issue-56.cpp b/test/map/issue-56.cpp index 447c623dda..8658121c4e 100644 --- a/test/map/issue-56.cpp +++ b/test/map/issue-56.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include TEST_CASE("const map") { diff --git a/test/map_transient/B3.cpp b/test/map_transient/B3.cpp new file mode 100644 index 0000000000..d08ab91651 --- /dev/null +++ b/test/map_transient/B3.cpp @@ -0,0 +1,28 @@ +// +// 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 + +template , + typename Eq = std::equal_to> +using test_map_t = immer::map; + +template , + typename Eq = std::equal_to> +using test_map_transient_t = + immer::map_transient; + +#define MAP_T test_map_t +#define MAP_TRANSIENT_T test_map_transient_t + +#include "generic.ipp" diff --git a/test/map_transient/B6.cpp b/test/map_transient/B6.cpp new file mode 100644 index 0000000000..84a732c5d8 --- /dev/null +++ b/test/map_transient/B6.cpp @@ -0,0 +1,28 @@ +// +// 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 + +template , + typename Eq = std::equal_to> +using test_map_t = immer::map; + +template , + typename Eq = std::equal_to> +using test_map_transient_t = + immer::map_transient; + +#define MAP_T test_map_t +#define MAP_TRANSIENT_T test_map_transient_t + +#include "generic.ipp" diff --git a/test/map_transient/default.cpp b/test/map_transient/default.cpp new file mode 100644 index 0000000000..7454f4f28a --- /dev/null +++ b/test/map_transient/default.cpp @@ -0,0 +1,15 @@ +// +// 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 + +#define MAP_T ::immer::map +#define MAP_TRANSIENT_T ::immer::map_transient + +#include "generic.ipp" diff --git a/test/map_transient/gc.cpp b/test/map_transient/gc.cpp new file mode 100644 index 0000000000..2708cb1949 --- /dev/null +++ b/test/map_transient/gc.cpp @@ -0,0 +1,36 @@ +// +// 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 + +using gc_memory = immer::memory_policy, + immer::no_refcount_policy, + immer::default_lock_policy, + immer::gc_transience_policy, + false>; + +template , + typename Eq = std::equal_to> +using test_map_t = immer::map; + +template , + typename Eq = std::equal_to> +using test_map_transient_t = + immer::map_transient; + +#define MAP_T test_map_t +#define MAP_TRANSIENT_T test_map_transient_t + +#include "generic.ipp" diff --git a/test/map_transient/generic.ipp b/test/map_transient/generic.ipp new file mode 100644 index 0000000000..5d0a154096 --- /dev/null +++ b/test/map_transient/generic.ipp @@ -0,0 +1,116 @@ +// +// 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 "test/util.hpp" + +#include + +#ifndef MAP_T +#error "define the map template to use in MAP_T" +#endif + +#ifndef MAP_TRANSIENT_T +#error "define the map template to use in MAP_TRANSIENT_T" +#endif + +IMMER_RANGES_CHECK(std::ranges::forward_range>); +IMMER_RANGES_CHECK( + std::ranges::forward_range>); + +TEST_CASE("instantiate") +{ + auto t = MAP_TRANSIENT_T{}; + auto m = MAP_T{}; + CHECK(t.persistent() == m); + CHECK(t.persistent() == m.transient().persistent()); +} + +TEST_CASE("access") +{ + auto m = MAP_T{{"foo", 12}, {"bar", 42}}; + auto t = m.transient(); + CHECK(t.size() == 2); + CHECK(t.count("foo") == 1); + CHECK(t["foo"] == 12); + CHECK(t.at("foo") == 12); + CHECK(t.find("foo") == m.find("foo")); + CHECK(std::accumulate(t.begin(), t.end(), 0, [](auto acc, auto&& x) { + return acc + x.second; + }) == 54); +} + +TEST_CASE("insert") +{ + auto t = MAP_TRANSIENT_T{}; + + t.insert({"foo", 42}); + CHECK(t["foo"] == 42); + CHECK(t.size() == 1); + + t.insert({"bar", 13}); + CHECK(t["bar"] == 13); + CHECK(t.size() == 2); + + t.insert({"foo", 6}); + CHECK(t["foo"] == 6); + CHECK(t.size() == 2); +} + +TEST_CASE("set") +{ + auto t = MAP_TRANSIENT_T{}; + + t.set("foo", 42); + CHECK(t["foo"] == 42); + CHECK(t.size() == 1); + + t.set("bar", 13); + CHECK(t["bar"] == 13); + CHECK(t.size() == 2); + + t.set("foo", 6); + CHECK(t["foo"] == 6); + CHECK(t.size() == 2); +} + +TEST_CASE("update") +{ + auto t = MAP_TRANSIENT_T{}; + + t.update("foo", [](auto x) { return x + 42; }); + CHECK(t["foo"] == 42); + CHECK(t.size() == 1); + + t.update("bar", [](auto x) { return x + 13; }); + CHECK(t["bar"] == 13); + CHECK(t.size() == 2); + + t.update("foo", [](auto x) { return x + 6; }); + CHECK(t["foo"] == 48); + CHECK(t.size() == 2); + + t.update_if_exists("foo", [](auto x) { return x + 42; }); + CHECK(t["foo"] == 90); + CHECK(t.size() == 2); + + t.update_if_exists("manolo", [](auto x) { return x + 42; }); + CHECK(t["manolo"] == 0); + CHECK(t.size() == 2); +} + +TEST_CASE("erase") +{ + auto t = MAP_T{{"foo", 12}, {"bar", 42}}.transient(); + + t.erase("foo"); + CHECK(t.find("foo") == nullptr); + CHECK(t.count("foo") == 0); + CHECK(t.find("bar") != nullptr); + CHECK(t.count("bar") == 1); + CHECK(t.size() == 1); +} diff --git a/test/memory/heaps.cpp b/test/memory/heaps.cpp index 94af2be208..a43c99b7e7 100644 --- a/test/memory/heaps.cpp +++ b/test/memory/heaps.cpp @@ -12,7 +12,7 @@ #include #include -#include +#include #include void do_stuff_to(void* buf, std::size_t size) diff --git a/test/memory/refcounts.cpp b/test/memory/refcounts.cpp index f9278ca779..f8a82e82ae 100644 --- a/test/memory/refcounts.cpp +++ b/test/memory/refcounts.cpp @@ -10,7 +10,7 @@ #include #include -#include +#include TEST_CASE("no refcount has no data") { @@ -42,16 +42,6 @@ void test_refcount() CHECK(!elem.dec()); CHECK(elem.dec()); } - - SECTION("inc dec unsafe") - { - refcount elem{}; - elem.inc(); - CHECK(!elem.dec()); - elem.inc(); - elem.dec_unsafe(); - CHECK(elem.dec()); - } } TEST_CASE("basic refcount") { test_refcount(); } diff --git a/test/oss-fuzz/array-0.cpp b/test/oss-fuzz/array-0.cpp index d0c2962a2e..162b015472 100644 --- a/test/oss-fuzz/array-0.cpp +++ b/test/oss-fuzz/array-0.cpp @@ -10,7 +10,7 @@ #include -#include +#include namespace { diff --git a/test/oss-fuzz/array-gc-0.cpp b/test/oss-fuzz/array-gc-0.cpp index 40a29331f3..7480665334 100644 --- a/test/oss-fuzz/array-gc-0.cpp +++ b/test/oss-fuzz/array-gc-0.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include using gc_memory = immer::memory_policy, immer::no_refcount_policy, diff --git a/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-6457979420934144 b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-6457979420934144 new file mode 100644 index 0000000000..3165e60d1b Binary files /dev/null and b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-6457979420934144 differ diff --git a/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-5313188008165376 b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-5313188008165376 new file mode 100644 index 0000000000..290b5da078 Binary files /dev/null and b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-5313188008165376 differ diff --git a/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-6242663155761152 b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-6242663155761152 new file mode 100644 index 0000000000..3cdc39d005 Binary files /dev/null and b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-6242663155761152 differ diff --git a/test/oss-fuzz/data/clusterfuzz-testcase-minimized-set-st-4717454829420544 b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-set-st-4717454829420544 new file mode 100644 index 0000000000..dd17c1f570 --- /dev/null +++ b/test/oss-fuzz/data/clusterfuzz-testcase-minimized-set-st-4717454829420544 @@ -0,0 +1 @@ +   ÿ \ No newline at end of file diff --git a/test/oss-fuzz/data/crash-2838943da19b47c02dcff313e523eead0e2e8635 b/test/oss-fuzz/data/crash-2838943da19b47c02dcff313e523eead0e2e8635 new file mode 100644 index 0000000000..d9acbc51f8 Binary files /dev/null and b/test/oss-fuzz/data/crash-2838943da19b47c02dcff313e523eead0e2e8635 differ diff --git a/test/oss-fuzz/data/crash-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b b/test/oss-fuzz/data/crash-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b new file mode 100644 index 0000000000..b39c03e13b Binary files /dev/null and b/test/oss-fuzz/data/crash-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b differ diff --git a/test/oss-fuzz/flex-vector-0.cpp b/test/oss-fuzz/flex-vector-0.cpp index f29eeb9fbf..2ce5291746 100644 --- a/test/oss-fuzz/flex-vector-0.cpp +++ b/test/oss-fuzz/flex-vector-0.cpp @@ -12,12 +12,12 @@ #include #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 0 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/flex-vector-bo-0.cpp b/test/oss-fuzz/flex-vector-bo-0.cpp index b821734fd4..f9526cacda 100644 --- a/test/oss-fuzz/flex-vector-bo-0.cpp +++ b/test/oss-fuzz/flex-vector-bo-0.cpp @@ -12,12 +12,12 @@ #include #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 1 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/flex-vector-gc-0.cpp b/test/oss-fuzz/flex-vector-gc-0.cpp index 75dd1ae1ad..0b9e6d6a20 100644 --- a/test/oss-fuzz/flex-vector-gc-0.cpp +++ b/test/oss-fuzz/flex-vector-gc-0.cpp @@ -13,12 +13,12 @@ #include #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 0 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/map-gc-0.cpp b/test/oss-fuzz/map-gc-0.cpp index a97baf7dfd..fd0540dacc 100644 --- a/test/oss-fuzz/map-gc-0.cpp +++ b/test/oss-fuzz/map-gc-0.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include using gc_memory = immer::memory_policy, immer::no_refcount_policy, diff --git a/test/oss-fuzz/map-st-0.cpp b/test/oss-fuzz/map-st-0.cpp index 4435fb046f..59aa0c6fc7 100644 --- a/test/oss-fuzz/map-st-0.cpp +++ b/test/oss-fuzz/map-st-0.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include using st_memory = immer::memory_policy, immer::unsafe_refcount_policy, @@ -111,3 +111,13 @@ TEST_CASE("https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=24466") CHECK(run_input(input.data(), input.size()) == 0); } } + +TEST_CASE("https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=47700") +{ + SECTION("fuzzer") + { + auto input = + load_input("clusterfuzz-testcase-minimized-map-6457979420934144"); + CHECK(run_input(input.data(), input.size()) == 0); + } +} diff --git a/test/oss-fuzz/map-st-1.cpp b/test/oss-fuzz/map-st-1.cpp new file mode 100644 index 0000000000..4041ea96c9 --- /dev/null +++ b/test/oss-fuzz/map-st-1.cpp @@ -0,0 +1,134 @@ +// +// 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 "input.hpp" + +#include "extra/fuzzer/fuzzer_gc_guard.hpp" + +#include +#include +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(std::size_t x) const { return x & ~15; } +}; + +namespace { + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer:: + map, st_memory>; + + 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_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + switch (read(in)) { + case op_set: { + auto value = read(in); + vars[dst] = vars[src].set(value, 42); + break; + } + case op_erase: { + auto value = read(in); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = read(in); + vars[dst] = std::move(vars[src]).set(value, 42); + break; + } + case op_erase_move: { + auto value = read(in); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + auto value = read(in); + auto res = vars[src].find(value); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, 42); + } + break; + } + case op_update: { + auto key = read(in); + vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=48207") +{ + SECTION("fuzzer") + { + auto input = load_input( + "clusterfuzz-testcase-minimized-map-st-5313188008165376"); + CHECK(run_input(input.data(), input.size()) == 0); + } +} diff --git a/test/oss-fuzz/map-st-2.cpp b/test/oss-fuzz/map-st-2.cpp new file mode 100644 index 0000000000..782e2af4a4 --- /dev/null +++ b/test/oss-fuzz/map-st-2.cpp @@ -0,0 +1,201 @@ +// +// 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 "input.hpp" + +#include "extra/fuzzer/fuzzer_gc_guard.hpp" + +#include +#include +#include + +#include + +#define IMMER_FUZZED_TRACE_ENABLE 0 + +#if IMMER_FUZZED_TRACE_ENABLE +#include +#define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) +#else +#define IMMER_FUZZED_TRACE(...) +#endif + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(std::size_t x) const { return x & ~15; } +}; + +namespace { + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer:: + map, st_memory>; + + auto vars = std::array{}; + +#if IMMER_FUZZED_TRACE_ENABLE + IMMER_FUZZED_TRACE("/// new test run\n"); + IMMER_FUZZED_TRACE("using map_t = immer::map," + "immer::default_memory_policy, {}>;\n", + immer::default_bits); + for (auto i = 0u; i < var_count; ++i) + IMMER_FUZZED_TRACE("auto v{} = map_t{{}};\n", i); +#endif + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_update_move, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + switch (read(in)) { + case op_set: { + auto value = read(in); + IMMER_FUZZED_TRACE("v{} = v{}.set({}, 42);\n", +dst, +src, +value); + vars[dst] = vars[src].set(value, 42); + break; + } + case op_erase: { + auto value = read(in); + IMMER_FUZZED_TRACE("v{} = v{}.erase({});\n", +dst, +src, +value); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = read(in); + IMMER_FUZZED_TRACE( + "v{} = std::move(v{}).set({}, 42);\n", +dst, +src, +value); + vars[dst] = std::move(vars[src]).set(value, 42); + break; + } + case op_erase_move: { + auto value = read(in); + IMMER_FUZZED_TRACE( + "v{} = std::move(v{}).erase({});\n", +dst, +src, +value); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + auto srcv = vars[src]; + IMMER_FUZZED_TRACE("{auto srcv = {}; for (const auto& v : srcv) " + "v{} = v{}.set(v.first, v.second); }\n", + +dst, + +src); + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + auto value = read(in); + auto res = vars[src].find(value); + IMMER_FUZZED_TRACE("if (auto res = v{}.find({}); res) v{} = " + "v{}.set(*res, 42);\n", + +src, + +value, + +dst, + +dst); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, 42); + } + break; + } + case op_update: { + auto key = read(in); + IMMER_FUZZED_TRACE("v{} = v{}.update(key, [](int x) { " + "return x + 1; });\n", + +dst, + +src, + +key); + vars[dst] = vars[src].update(key, [](int x) { return x + 1; }); + break; + } + case op_update_move: { + auto key = read(in); + IMMER_FUZZED_TRACE("v{} = std::move(v{}).update(key, [](int x) { " + "return x + 1; });\n", + +dst, + +src, + +key); + vars[dst] = + std::move(vars[src]).update(key, [](int x) { return x + 1; }); + break; + } + case op_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=48398") +{ + SECTION("fuzzer") + { + auto input = load_input( + "clusterfuzz-testcase-minimized-map-st-6242663155761152"); + CHECK(run_input(input.data(), input.size()) == 0); + } + + SECTION("translated") + { + using map_t = immer::map, + immer::default_memory_policy, + 5>; + auto v0 = map_t{}; + auto v1 = map_t{}; + v0 = v0.set(0, 42); + v1 = v0; + v0 = std::move(v1).erase(0); + } +} diff --git a/test/oss-fuzz/map-st-str-0.cpp b/test/oss-fuzz/map-st-str-0.cpp new file mode 100644 index 0000000000..fdf5204cab --- /dev/null +++ b/test/oss-fuzz/map-st-str-0.cpp @@ -0,0 +1,218 @@ +// +// 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 "input.hpp" + +#include +#include +#include + +#include + +#include + +#include + +#define IMMER_FUZZED_TRACE_ENABLE 1 + +#if IMMER_FUZZED_TRACE_ENABLE +#include +#define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) +#else +#define IMMER_FUZZED_TRACE(...) +#endif + +namespace { + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~15; + } +}; + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using map_t = immer::map, + st_memory>; + + auto vars = std::array{}; + +#if IMMER_FUZZED_TRACE_ENABLE + IMMER_FUZZED_TRACE("/// new test run\n"); + IMMER_FUZZED_TRACE("using map_t = immer::map, st_memory>;\n"); + for (auto i = 0u; i < var_count; ++i) + IMMER_FUZZED_TRACE("auto v{} = map_t{{}};\n", i); +#endif + + auto is_valid_var = [&](auto idx) { return idx >= 0 && idx < var_count; }; + + return fuzzer_input{data, size}.run([&](auto& in) { + enum ops + { + op_set, + op_erase, + op_set_move, + op_erase_move, + op_iterate, + op_find, + op_update, + op_update_move, + op_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + IMMER_FUZZED_TRACE("CHECK(v{}.impl().check_champ());\n", +src); + CHECK(vars[src].impl().check_champ()); + switch (read(in)) { + case op_set: { + auto value = std::to_string(read(in)); + IMMER_FUZZED_TRACE( + "v{} = v{}.set(\"{}\", \"foo\");\n", +dst, +src, value); + vars[dst] = vars[src].set(value, "foo"); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + IMMER_FUZZED_TRACE("v{} = v{}.erase(\"{}\");\n", +dst, +src, value); + vars[dst] = vars[src].erase(value); + break; + } + case op_set_move: { + auto value = std::to_string(read(in)); + IMMER_FUZZED_TRACE("v{} = std::move(v{}).set(\"{}\", \"foo\");\n", + +dst, + +src, + value); + vars[dst] = std::move(vars[src]).set(value, "foo"); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + IMMER_FUZZED_TRACE( + "v{} = std::move(v{}).erase(\"{}\");\n", +dst, +src, value); + vars[dst] = std::move(vars[src]).erase(value); + break; + } + case op_iterate: { + IMMER_FUZZED_TRACE("!!!TODO;\n"); + auto srcv = vars[src]; + for (const auto& v : srcv) { + vars[dst] = vars[dst].set(v.first, v.second); + } + break; + } + case op_find: { + IMMER_FUZZED_TRACE("!!!TODO;\n"); + auto value = std::to_string(read(in)); + auto res = vars[src].find(value); + if (res != nullptr) { + vars[dst] = vars[dst].set(*res, "foo"); + } + break; + } + case op_update: { + auto key = std::to_string(read(in)); + IMMER_FUZZED_TRACE("v{} = v{}.update(\"{}\", [] (auto x) {{ " + "return x + \"bar\"; }});\n", + +dst, + +src, + key); + vars[dst] = vars[src].update( + key, [](std::string x) { return std::move(x) + "bar"; }); + break; + } + case op_update_move: { + auto key = std::to_string(read(in)); + IMMER_FUZZED_TRACE( + "v{} = std::move(v{}).update(\"{}\", [] (auto x) {{ " + "return x + \"bar\"; }});\n", + +dst, + +src, + key); + vars[dst] = std::move(vars[src]).update( + key, [](std::string x) { return std::move(x) + "baz"; }); + break; + } + case op_diff: { + IMMER_FUZZED_TRACE("!!!TODO;\n"); + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x.first)); + assert(b.count(x.first)); + }, + [&](auto&& x) { + assert(a.count(x.first)); + assert(!b.count(x.first)); + }, + [&](auto&& x, auto&& y) { + assert(x.first == y.first); + assert(x.second != y.second); + }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("local test runs") +{ + SECTION("fuzzer") + { + auto input = + load_input("crash-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b"); + CHECK(run_input(input.data(), input.size()) == 0); + (void) run_input; + } + + SECTION("simplify") + { + using map_t = immer::map, + st_memory>; + auto v0 = map_t{}; + auto v3 = map_t{}; + + v0 = std::move(v0) + .set("256", "foo") + .set("217020539959771201", "foo") + .set("91201394110889985", "foo") + .set("217020518514230019", "foo"); + v3 = v0; + v0 = v0.set("0", "foo"); + CHECK(v0.impl().check_champ()); + CHECK(v3.impl().check_champ()); + + v3 = std::move(v3).erase("217020518514230019"); // here + CHECK(v3.impl().check_champ()); + CHECK(v0.impl().check_champ()); + } +} diff --git a/test/oss-fuzz/set-gc-0.cpp b/test/oss-fuzz/set-gc-0.cpp index 7e88b0bbef..c6abebcc99 100644 --- a/test/oss-fuzz/set-gc-0.cpp +++ b/test/oss-fuzz/set-gc-0.cpp @@ -14,7 +14,7 @@ #include #include -#include +#include using gc_memory = immer::memory_policy, immer::no_refcount_policy, diff --git a/test/oss-fuzz/set-gc-1.cpp b/test/oss-fuzz/set-gc-1.cpp index 19b318e67c..d07901a22f 100644 --- a/test/oss-fuzz/set-gc-1.cpp +++ b/test/oss-fuzz/set-gc-1.cpp @@ -16,12 +16,12 @@ #include -#include +#include #define IMMER_FUZZED_TRACE_ENABLE 0 #if IMMER_FUZZED_TRACE_ENABLE -#include +#include #define IMMER_FUZZED_TRACE(...) fmt::print(std::cerr, __VA_ARGS__) #else #define IMMER_FUZZED_TRACE(...) diff --git a/test/oss-fuzz/set-st-0.cpp b/test/oss-fuzz/set-st-0.cpp new file mode 100644 index 0000000000..4e56f21173 --- /dev/null +++ b/test/oss-fuzz/set-st-0.cpp @@ -0,0 +1,118 @@ +// +// 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 "input.hpp" + +#include +#include +#include + +#include + +#include + +#include + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(std::size_t x) const { return x & ~15; } +}; + +namespace { + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using set_t = + immer::set, st_memory>; + + 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_diff + }; + 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_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=47693") +{ + SECTION("fuzzer") + { + auto input = load_input( + "clusterfuzz-testcase-minimized-set-st-4717454829420544"); + CHECK(run_input(input.data(), input.size()) == 0); + } +} diff --git a/test/oss-fuzz/set-st-str-0.cpp b/test/oss-fuzz/set-st-str-0.cpp new file mode 100644 index 0000000000..538abf708c --- /dev/null +++ b/test/oss-fuzz/set-st-str-0.cpp @@ -0,0 +1,122 @@ +// +// 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 "input.hpp" + +#include +#include +#include + +#include + +#include + +#include + +namespace { + +using st_memory = immer::memory_policy, + immer::unsafe_refcount_policy, + immer::no_lock_policy, + immer::no_transience_policy, + false>; + +struct colliding_hash_t +{ + std::size_t operator()(const std::string& x) const + { + return std::hash{}(x) & ~15; + } +}; + +int run_input(const std::uint8_t* data, std::size_t size) +{ + constexpr auto var_count = 4; + + using set_t = + immer::set, st_memory>; + + 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_diff + }; + auto src = read(in, is_valid_var); + auto dst = read(in, is_valid_var); + assert(vars[src].impl().check_champ()); + switch (read(in)) { + case op_insert: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].insert(value); + break; + } + case op_erase: { + auto value = std::to_string(read(in)); + vars[dst] = vars[src].erase(value); + break; + } + case op_insert_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(vars[src]).insert(value); + break; + } + case op_erase_move: { + auto value = std::to_string(read(in)); + vars[dst] = std::move(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_diff: { + auto&& a = vars[src]; + auto&& b = vars[dst]; + diff( + a, + b, + [&](auto&& x) { + assert(!a.count(x)); + assert(b.count(x)); + }, + [&](auto&& x) { + assert(a.count(x)); + assert(!b.count(x)); + }, + [&](auto&& x, auto&& y) { assert(false); }); + } + default: + break; + }; + return true; + }); +} + +} // namespace + +TEST_CASE("local test runs") +{ + SECTION("fuzzer") + { + auto input = + load_input("crash-2838943da19b47c02dcff313e523eead0e2e8635"); + CHECK(run_input(input.data(), input.size()) == 0); + } +} diff --git a/test/set/gc.cpp b/test/set/gc.cpp index 65963d2c82..e4bc47e5fc 100644 --- a/test/set/gc.cpp +++ b/test/set/gc.cpp @@ -22,4 +22,6 @@ template ; #define SET_T test_set_t +#define IMMER_IS_LIBGC_TEST 1 + #include "generic.ipp" diff --git a/test/set/generic.ipp b/test/set/generic.ipp index 08de14cba7..d8bde72586 100644 --- a/test/set/generic.ipp +++ b/test/set/generic.ipp @@ -16,12 +16,18 @@ #include "test/util.hpp" #include +#include -#include +#include #include #include +using memory_policy_t = SET_T::memory_policy_type; + +IMMER_RANGES_CHECK(std::input_iterator::iterator>); +IMMER_RANGES_CHECK(std::ranges::forward_range>); + template auto make_generator() { @@ -30,6 +36,15 @@ auto make_generator() return std::bind(dist, engine); } +template +auto make_generator_s() +{ + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + return + [g = std::bind(dist, engine)]() mutable { return std::to_string(g()); }; +} + struct conflictor { unsigned v1; @@ -65,7 +80,7 @@ auto make_test_set(unsigned n) { auto s = SET_T{}; for (auto i = 0u; i < n; ++i) - s = s.insert(i); + s = std::move(s).insert(i); return s; } @@ -73,7 +88,7 @@ auto make_test_set(const std::vector& vals) { auto s = SET_T{}; for (auto&& v : vals) - s = s.insert(v); + s = std::move(s).insert(v); return s; } @@ -144,16 +159,31 @@ TEST_CASE("instantiation") } } +TEST_CASE("initializer list and range constructors") +{ + auto v0 = std::unordered_set{{"foo", "bar", "baz", "zab"}}; + auto v1 = SET_T{{"foo", "bar", "baz", "zab"}}; + auto v2 = SET_T{v0.begin(), v0.end()}; + CHECK(v1.size() == 4); + CHECK(v1.count(std::string{"foo"}) == 1); + CHECK(v1.count(std::string{"bar"}) == 1); + CHECK(v1 == v2); +} + TEST_CASE("basic insertion") { auto v1 = SET_T{}; CHECK(v1.count(42) == 0); + CHECK(v1.identity() == SET_T{}.identity()); auto v2 = v1.insert(42); CHECK(v1.count(42) == 0); CHECK(v2.count(42) == 1); + CHECK(v1.identity() != v2.identity()); auto v3 = v2.insert(42); + // it would maybe be nice if this was not the case, but it is... + CHECK(v2.identity() != v3.identity()); CHECK(v1.count(42) == 0); CHECK(v2.count(42) == 1); CHECK(v3.count(42) == 1); @@ -168,13 +198,27 @@ TEST_CASE("insert a lot") generate_n(back_inserter(vals), N, gen); auto s = SET_T{}; - for (auto i = 0u; i < N; ++i) { - s = s.insert(vals[i]); - CHECK(s.size() == i + 1); - for (auto j : test_irange(0u, i + 1)) - CHECK(s.count(vals[j]) == 1); - for (auto j : test_irange(i + 1u, N)) - CHECK(s.count(vals[j]) == 0); + SECTION("immutable") + { + for (auto i = 0u; i < N; ++i) { + s = s.insert(vals[i]); + CHECK(s.size() == i + 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 1); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 0); + } + } + SECTION("move") + { + for (auto i = 0u; i < N; ++i) { + s = std::move(s).insert(vals[i]); + CHECK(s.size() == i + 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 1); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 0); + } } } @@ -183,16 +227,65 @@ TEST_CASE("insert conflicts") constexpr auto N = 666u; auto vals = make_values_with_collisions(N); auto s = SET_T{}; - for (auto i = 0u; i < N; ++i) { - s = s.insert(vals[i]); - CHECK(s.size() == i + 1); - for (auto j : test_irange(0u, i + 1)) - CHECK(s.count(vals[j]) == 1); - for (auto j : test_irange(i + 1u, N)) - CHECK(s.count(vals[j]) == 0); + SECTION("immutable") + { + for (auto i = 0u; i < N; ++i) { + s = s.insert(vals[i]); + CHECK(s.size() == i + 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 1); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 0); + } + } + SECTION("move") + { + for (auto i = 0u; i < N; ++i) { + s = std::move(s).insert(vals[i]); + CHECK(s.size() == i + 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 1); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 0); + } } } +#if !IMMER_IS_LIBGC_TEST +TEST_CASE("insert boxed move string") +{ + constexpr auto N = 666u; + constexpr auto S = 7; + auto s = SET_T>{}; + SECTION("preserve immutability") + { + auto s0 = s; + auto i0 = 0u; + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).insert(std::to_string(i)); + { + CHECK(s.size() == i + 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(std::to_string(j)) == 1); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(std::to_string(j)) == 0); + } + { + CHECK(s0.size() == i0); + for (auto j : test_irange(0u, i0)) + CHECK(s0.count(std::to_string(j)) == 1); + for (auto j : test_irange(i0, N)) + CHECK(s0.count(std::to_string(j)) == 0); + } + } + } +} +#endif + TEST_CASE("erase a lot") { constexpr auto N = 666u; @@ -202,33 +295,125 @@ TEST_CASE("erase a lot") auto s = SET_T{}; for (auto i = 0u; i < N; ++i) - s = s.insert(vals[i]); + s = std::move(s).insert(vals[i]); - for (auto i = 0u; i < N; ++i) { - s = s.erase(vals[i]); - CHECK(s.size() == N - i - 1); - for (auto j : test_irange(0u, i + 1)) - CHECK(s.count(vals[j]) == 0); - for (auto j : test_irange(i + 1u, N)) - CHECK(s.count(vals[j]) == 1); + SECTION("immutable") + { + for (auto i = 0u; i < N; ++i) { + s = s.erase(vals[i]); + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } + } + SECTION("move") + { + for (auto i = 0u; i < N; ++i) { + s = std::move(s).erase(vals[i]); + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } } } +#if !IMMER_IS_LIBGC_TEST +TEST_CASE("erase a lot boxed string") +{ + constexpr auto N = 666u; + auto gen = make_generator_s(); + auto vals = std::vector>{}; + generate_n(back_inserter(vals), N, gen); + + auto s = SET_T>{}; + for (auto i = 0u; i < N; ++i) + s = std::move(s).insert(vals[i]); + + SECTION("immutable") + { + for (auto i = 0u; i < N; ++i) { + s = s.erase(vals[i]); + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } + } + SECTION("move") + { + for (auto i = 0u; i < N; ++i) { + s = std::move(s).erase(vals[i]); + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } + } + SECTION("move preserve immutability") + { + constexpr auto S = 7; + auto s0 = s; + auto i0 = 0u; + for (auto i = 0u; i < N; ++i) { + if (i % S == 0) { + s0 = s; + i0 = i; + } + s = std::move(s).erase(vals[i]); + { + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } + { + CHECK(s0.size() == N - i0); + for (auto j : test_irange(0u, i0)) + CHECK(s0.count(vals[j]) == 0); + for (auto j : test_irange(i0, N)) + CHECK(s0.count(vals[j]) == 1); + } + } + } +} +#endif + TEST_CASE("erase conflicts") { constexpr auto N = 666u; auto vals = make_values_with_collisions(N); auto s = SET_T{}; for (auto i = 0u; i < N; ++i) - s = s.insert(vals[i]); + s = std::move(s).insert(vals[i]); - for (auto i = 0u; i < N; ++i) { - s = s.erase(vals[i]); - CHECK(s.size() == N - i - 1); - for (auto j : test_irange(0u, i + 1)) - CHECK(s.count(vals[j]) == 0); - for (auto j : test_irange(i + 1u, N)) - CHECK(s.count(vals[j]) == 1); + SECTION("immutable") + { + for (auto i = 0u; i < N; ++i) { + s = s.erase(vals[i]); + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } + } + SECTION("move") + { + for (auto i = 0u; i < N; ++i) { + s = std::move(s).erase(vals[i]); + CHECK(s.size() == N - i - 1); + for (auto j : test_irange(0u, i + 1)) + CHECK(s.count(vals[j]) == 0); + for (auto j : test_irange(i + 1u, N)) + CHECK(s.count(vals[j]) == 1); + } } } @@ -511,3 +696,51 @@ TEST_CASE("lookup with transparent hash") CHECK(m.count(LookupType{2}) == 0); } } + +void test_diff(unsigned old_num, unsigned add_num, unsigned remove_num) +{ + auto values = make_values_with_collisions(old_num + add_num); + std::vector initial_values(values.begin(), + values.begin() + old_num); + std::vector new_values(values.begin() + old_num, values.end()); + auto set = make_test_set(initial_values); + + auto first_snapshot = set; + CHECK(old_num == first_snapshot.size()); + + // remove + auto shuffle = initial_values; + std::random_shuffle(shuffle.begin(), shuffle.end()); + std::vector remove_keys(shuffle.begin(), + shuffle.begin() + remove_num); + + using key_set = std::unordered_set; + key_set removed_keys(remove_keys.begin(), remove_keys.end()); + for (auto const& key : remove_keys) + set = set.erase(key); + CHECK(old_num - remove_num == set.size()); + + // add + key_set added_keys; + for (auto const& data : new_values) { + set = set.insert(data); + added_keys.insert(data); + } + + diff( + first_snapshot, + set, + [&](auto const& data) { REQUIRE(added_keys.erase(data) > 0); }, + [&](auto const& data) { REQUIRE(removed_keys.erase(data) > 0); }); + + CHECK(added_keys.empty()); + CHECK(removed_keys.empty()); +} + +TEST_CASE("diff") +{ + test_diff(16, 10, 10); + test_diff(100, 10, 10); + test_diff(1500, 10, 1000); + test_diff(16, 1500, 10); +} diff --git a/test/set_transient/B3.cpp b/test/set_transient/B3.cpp new file mode 100644 index 0000000000..2a176a983d --- /dev/null +++ b/test/set_transient/B3.cpp @@ -0,0 +1,26 @@ +// +// 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 + +template , + typename Eq = std::equal_to> +using test_set_t = immer::set; + +template , + typename Eq = std::equal_to> +using test_set_transient_t = + immer::set_transient; + +#define SET_T test_set_t +#define SET_TRANSIENT_T test_set_transient_t + +#include "generic.ipp" diff --git a/test/set_transient/B6.cpp b/test/set_transient/B6.cpp new file mode 100644 index 0000000000..6448b10150 --- /dev/null +++ b/test/set_transient/B6.cpp @@ -0,0 +1,26 @@ +// +// 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 + +template , + typename Eq = std::equal_to> +using test_set_t = immer::set; + +template , + typename Eq = std::equal_to> +using test_set_transient_t = + immer::set_transient; + +#define SET_T test_set_t +#define SET_TRANSIENT_T test_set_transient_t + +#include "generic.ipp" diff --git a/test/set_transient/default.cpp b/test/set_transient/default.cpp new file mode 100644 index 0000000000..8b5fed4df4 --- /dev/null +++ b/test/set_transient/default.cpp @@ -0,0 +1,15 @@ +// +// 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 + +#define SET_T ::immer::set +#define SET_TRANSIENT_T ::immer::set_transient + +#include "generic.ipp" diff --git a/test/set_transient/gc.cpp b/test/set_transient/gc.cpp new file mode 100644 index 0000000000..8d5543dde6 --- /dev/null +++ b/test/set_transient/gc.cpp @@ -0,0 +1,33 @@ +// +// 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 + +using gc_memory = immer::memory_policy, + immer::no_refcount_policy, + immer::default_lock_policy, + immer::gc_transience_policy, + false>; + +template , + typename Eq = std::equal_to> +using test_set_t = immer::set; + +template , + typename Eq = std::equal_to> +using test_set_transient_t = immer::set_transient; + +#define SET_T test_set_t +#define SET_TRANSIENT_T test_set_transient_t + +#include "generic.ipp" diff --git a/test/set_transient/generic.ipp b/test/set_transient/generic.ipp new file mode 100644 index 0000000000..29f7b0b983 --- /dev/null +++ b/test/set_transient/generic.ipp @@ -0,0 +1,104 @@ +// +// 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 "test/util.hpp" + +#include + +#ifndef SET_T +#error "define the set template to use in SET_T" +#endif + +#ifndef SET_TRANSIENT_T +#error "define the set template to use in SET_TRANSIENT_T" +#endif + +IMMER_RANGES_CHECK(std::ranges::forward_range>); +IMMER_RANGES_CHECK(std::ranges::forward_range>); + +TEST_CASE("instantiate") +{ + auto t = SET_TRANSIENT_T{}; + auto m = SET_T{}; + CHECK(t.persistent() == m); + CHECK(t.persistent() == m.transient().persistent()); +} + +TEST_CASE("access") +{ + auto m = SET_T{12, 42}; + auto t = m.transient(); + CHECK(t.size() == 2); + CHECK(t.count(42) == 1); + CHECK(*t.find(12) == 12); + CHECK(std::accumulate(t.begin(), t.end(), 0) == 54); +} + +TEST_CASE("insert") +{ + auto t = SET_TRANSIENT_T{}; + + t.insert(42); + CHECK(*t.find(42) == 42); + CHECK(t.size() == 1); + + t.insert(13); + CHECK(*t.find(13) == 13); + CHECK(t.size() == 2); + + t.insert(42); + CHECK(*t.find(42) == 42); + CHECK(t.size() == 2); +} + +TEST_CASE("erase") +{ + auto t = SET_T{12, 42}.transient(); + + t.erase(42); + CHECK(t.find(42) == nullptr); + CHECK(t.count(42) == 0); + CHECK(t.find(12) != nullptr); + CHECK(t.count(12) == 1); + CHECK(t.size() == 1); +} + +TEST_CASE("insert erase many") +{ + auto t = SET_T{}.transient(); + auto n = 1000; + for (auto i = 0; i < n; ++i) { + t.insert(i); + CHECK(t.find(i) != nullptr); + CHECK(t.size() == static_cast(i + 1)); + } + for (auto i = 0; i < n; ++i) { + t.erase(i); + CHECK(t.find(i) == nullptr); + CHECK(t.size() == static_cast(n - i - 1)); + } +} + +TEST_CASE("erase many from non transient") +{ + const auto n = 10000; + auto t = [] { + auto t = SET_T{}; + for (auto i = 0; i < n; ++i) { + t = t.insert(i); + CHECK(t.find(i) != nullptr); + CHECK(t.size() == static_cast(i + 1)); + } + return t.transient(); + }(); + for (auto i = 0; i < n; ++i) { + t.erase(i); + CHECK(t.find(i) == nullptr); + CHECK(t.size() == static_cast(n - i - 1)); + } +} diff --git a/test/table/B3.cpp b/test/table/B3.cpp new file mode 100644 index 0000000000..f6327ab29c --- /dev/null +++ b/test/table/B3.cpp @@ -0,0 +1,20 @@ +// +// 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 + +struct setup_t +{ + using memory_policy = immer::default_memory_policy; + + static constexpr auto bits = 3u; +}; + +#define SETUP_T setup_t +#include "generic.ipp" diff --git a/test/table/B6.cpp b/test/table/B6.cpp new file mode 100644 index 0000000000..910fa99561 --- /dev/null +++ b/test/table/B6.cpp @@ -0,0 +1,20 @@ +// +// 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 + +struct setup_t +{ + using memory_policy = immer::default_memory_policy; + + static constexpr auto bits = 6u; +}; + +#define SETUP_T setup_t +#include "generic.ipp" diff --git a/test/table/default.cpp b/test/table/default.cpp new file mode 100644 index 0000000000..3ea016bb2a --- /dev/null +++ b/test/table/default.cpp @@ -0,0 +1,20 @@ +// +// 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 + +struct setup_t +{ + using memory_policy = immer::default_memory_policy; + + static constexpr auto bits = immer::default_bits; +}; + +#define SETUP_T setup_t +#include "generic.ipp" diff --git a/test/table/gc.cpp b/test/table/gc.cpp new file mode 100644 index 0000000000..67613474bd --- /dev/null +++ b/test/table/gc.cpp @@ -0,0 +1,18 @@ +#include +#include +#include + +struct setup_t +{ + using memory_policy = + immer::memory_policy, + immer::no_refcount_policy, + immer::default_lock_policy, + immer::gc_transience_policy, + false>; + + static constexpr auto bits = immer::default_bits; +}; + +#define SETUP_T setup_t +#include "generic.ipp" diff --git a/test/table/generic.ipp b/test/table/generic.ipp new file mode 100644 index 0000000000..f943e51f18 --- /dev/null +++ b/test/table/generic.ipp @@ -0,0 +1,570 @@ +// +// 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 "immer/memory_policy.hpp" +#ifndef SETUP_T +#error "define the map template to use in MAP_T" +#endif + +#include +#include + +#include "test/dada.hpp" +#include "test/util.hpp" + +#include + +#include +#include +#include + +struct pair_key_fn +{ + template + F operator()(const std::pair& p) const + { + return p.first; + } + + template + auto operator()(std::pair p, F k) const + { + p.first = std::move(k); + return p; + } + + template + F operator()(const dadaist>& p) const + { + return p.value.first; + } + + template + auto operator()(dadaist> p, F k) const + { + p.value.first = std::move(k); + return p; + } +}; + +template , + typename Eq = std::equal_to> +using table_map = immer::table, + pair_key_fn, + Hash, + Eq, + SETUP_T::memory_policy, + SETUP_T::bits>; + +IMMER_RANGES_CHECK(std::ranges::forward_range>); + +template +auto make_generator() +{ + auto engine = std::default_random_engine{42}; + auto dist = std::uniform_int_distribution{}; + return std::bind(dist, engine); +} + +struct conflictor +{ + uint32_t v1; + uint32_t v2; + + bool operator==(const conflictor& x) const + { + return v1 == x.v1 && v2 == x.v2; + } +}; + +struct hash_conflictor +{ + std::size_t operator()(const conflictor& x) const { return x.v1; } +}; + +auto make_values_with_collisions(uint32_t n) +{ + auto gen = make_generator(); + auto vals = std::vector>{}; + auto vals_ = std::unordered_set{}; + auto i = 0u; + generate_n(back_inserter(vals), n, [&] { + auto newv = conflictor{}; + do { + newv = {uint32_t(gen() % (n / 2)), gen()}; + } while (!vals_.insert(newv).second); + return std::pair{newv, i++}; + }); + return vals; +} + +auto make_test_map(uint32_t n) +{ + auto s = table_map{}; + for (auto i = 0u; i < n; ++i) + s = std::move(s).insert({i, i}); + return s; +} + +auto make_test_map(const std::vector>& vals) +{ + auto s = table_map{}; + for (auto&& v : vals) + s = std::move(s).insert(v); + return s; +} + +TEST_CASE("instantiation") +{ + SECTION("default") + { + auto v = table_map{}; + CHECK(v.size() == 0u); + } +} + +TEST_CASE("basic insertion") +{ + auto v1 = table_map{}; + CHECK(v1.count(42) == 0); + + auto v2 = v1.insert({42, {}}); + CHECK(v1.count(42) == 0); + CHECK(v2.count(42) == 1); + + auto v3 = v2.insert({42, {}}); + CHECK(v1.count(42) == 0); + CHECK(v2.count(42) == 1); + CHECK(v3.count(42) == 1); +} + +TEST_CASE("initializer list and range constructors") +{ + auto v0 = std::unordered_map{ + {{"foo", 42}, {"bar", 13}, {"baz", 18}, {"zab", 64}}}; + auto v1 = table_map{ + {{"foo", 42}, {"bar", 13}, {"baz", 18}, {"zab", 64}}}; + auto v2 = table_map{v0.begin(), v0.end()}; + CHECK(v1.size() == 4); + CHECK(v1.count(std::string{"foo"}) == 1); + CHECK(v1.at(std::string{"bar"}).second == 13); + CHECK(v1 == v2); +} + +TEST_CASE("accessor") +{ + const auto n = 666u; + auto v = make_test_map(n); + CHECK(v[0].second == 0); + CHECK(v[42].second == 42); + CHECK(v[665].second == 665); + CHECK(v[666].second == 0); + CHECK(v[1234].second == 0); +} + +TEST_CASE("at") +{ + const auto n = 666u; + auto v = make_test_map(n); + CHECK(v.at(0).second == 0); + CHECK(v.at(42).second == 42); + CHECK(v.at(665).second == 665); +#ifndef IMMER_NO_EXCEPTIONS + CHECK_THROWS_AS(v.at(666), std::out_of_range); + CHECK_THROWS_AS(v.at(1234), std::out_of_range); +#endif +} + +TEST_CASE("find") +{ + const auto n = 666u; + auto v = make_test_map(n); + CHECK(v.find(0)->second == 0); + CHECK(v.find(42)->second == 42); + CHECK(v.find(665)->second == 665); + CHECK(v.find(666) == nullptr); + CHECK(v.find(1234) == nullptr); +} + +TEST_CASE("equals and insert") +{ + const auto n = 666u; + auto v = make_test_map(n); + + CHECK(v == v); + CHECK(v != v.insert({1234, 42})); + CHECK(v != v.erase(32)); + CHECK(v == v.insert({1234, 42}).erase(1234)); + CHECK(v == v.erase(32).insert({32, 32})); +} + +TEST_CASE("iterator") +{ + const auto N = 666u; + auto v = make_test_map(N); + + SECTION("empty set") + { + auto s = table_map{}; + CHECK(s.begin() == s.end()); + } + + SECTION("works with range loop") + { + auto seen = std::unordered_set{}; + for (const auto& x : v) + CHECK(seen.insert(x.first).second); + CHECK(seen.size() == v.size()); + } + + SECTION("iterator and collisions") + { + auto vals = make_values_with_collisions(N); + auto s = make_test_map(vals); + auto seen = std::unordered_set{}; + for (const auto& x : s) + CHECK(seen.insert(x.first).second); + CHECK(seen.size() == s.size()); + } +} + +TEST_CASE("accumulate") +{ + const auto n = 666u; + auto v = make_test_map(n); + + auto expected_n = [](auto n) { return n * (n - 1) / 2; }; + + SECTION("sum collection") + { + auto acc = [](uint32_t acc, const std::pair& x) { + return acc + x.first + x.second; + }; + auto sum = immer::accumulate(v, 0u, acc); + CHECK(sum == 2 * expected_n(v.size())); + } + + SECTION("sum collisions") + { + auto vals = make_values_with_collisions(n); + auto s = make_test_map(vals); + auto acc = [](uint32_t r, std::pair x) { + return r + x.first.v1 + x.first.v2 + x.second; + }; + auto sum1 = std::accumulate(vals.begin(), vals.end(), 0u, acc); + auto sum2 = immer::accumulate(s, 0u, acc); + CHECK(sum1 == sum2); + } +} + +TEST_CASE("update a lot") +{ + auto v = make_test_map(666u); + auto incr_id = [](auto&& p) { + return std::make_pair(p.first, p.second + 1); + }; + SECTION("immutable") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = v.update(i, incr_id); + CHECK(v[i].second == i + 1); + } + } + SECTION("move") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = std::move(v).update(i, incr_id); + CHECK(v[i].second == i + 1); + } + } + SECTION("if_exists immutable") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = v.update_if_exists(i, incr_id); + CHECK(v[i].second == i + 1); + } + } + SECTION("if_exists move") + { + for (decltype(v.size()) i = 0; i < v.size(); ++i) { + v = std::move(v).update_if_exists(i, incr_id); + CHECK(v[i].second == i + 1); + } + } +} + +TEST_CASE("exception safety") +{ + constexpr auto n = 2666u; + + using dadaist_map_t = + immer::table>, // + pair_key_fn, // + std::hash, // + std::equal_to, // + dadaist_memory_policy, // + SETUP_T::bits // + >; + + auto make_pair = [](auto f, auto s) { + return dadaist>( + std::make_pair(f, s)); + }; + SECTION("update collisions") + { + auto v = dadaist_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).insert(make_pair(i, i)); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = v.update(i, [&make_pair](auto x) { + return make_pair(x.value.first, x.value.second + 1); + }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(i).value.second == i + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(i).value.second == i); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + using dadaist_conflictor_map_t = + immer::table>, // + pair_key_fn, // + hash_conflictor, // + std::equal_to, // + dadaist_memory_policy, // + SETUP_T::bits // + >; + + SECTION("update collisisions") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = v.insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = v.update(vals[i].first, [&make_pair](auto x) { + return make_pair(x.value.first, x.value.second + 1); + }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first).value.second == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first).value.second == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + + SECTION("update collisisions move") + { + auto vals = make_values_with_collisions(n); + auto v = dadaist_conflictor_map_t{}; + auto d = dadaism{}; + for (auto i = 0u; i < n; ++i) + v = std::move(v).insert(vals[i]); + for (auto i = 0u; i < v.size();) { + try { + auto s = d.next(); + v = v.update(vals[i].first, [&make_pair](auto x) { + return make_pair(x.value.first, x.value.second + 1); + }); + ++i; + } catch (dada_error) {} + for (auto i : test_irange(0u, i)) + CHECK(v.at(vals[i].first).value.second == vals[i].second + 1); + for (auto i : test_irange(i, n)) + CHECK(v.at(vals[i].first).value.second == vals[i].second); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } +} + +namespace { + +struct KeyType +{ + explicit KeyType(uint32_t v) + : value(v) + {} + uint32_t value; +}; + +struct LookupType +{ + explicit LookupType(uint32_t v) + : value(v) + {} + uint32_t value; +}; + +struct TransparentHash +{ + using hash_type = std::hash; + using is_transparent = void; + + size_t operator()(KeyType const& k) const { return hash_type{}(k.value); } + size_t operator()(LookupType const& k) const + { + return hash_type{}(k.value); + } +}; + +bool operator==(KeyType const& k, KeyType const& l) +{ + return k.value == l.value; +} + +bool operator==(KeyType const& k, LookupType const& l) +{ + return k.value == l.value; +} + +} // namespace + +TEST_CASE("lookup with transparent hash") +{ + SECTION("default") + { + auto m = table_map>{}; + m = m.insert({KeyType{1}, 12}); + + auto const& v = m.at(LookupType{1}); + CHECK(v.second == 12); + } +} + +namespace { + +class KElem +{ +public: + KElem(int* elem) { this->elem = elem; } + + bool operator==(const KElem& other) const + { + return this->elem == other.elem; + } + + bool operator!=(const KElem& other) const { return !(*this == other); } + + int* elem; +}; + +struct HashBlock +{ + size_t operator()(const KElem& block) const noexcept + { + return (uintptr_t) block.elem & 0xffffffff00000000; + } +}; + +} // namespace + +TEST_CASE("issue 134") +{ + int a[100]; + table_map m; + for (int i = 0; i < 100; i++) { + m = m.insert({KElem(a + i), KElem(a + i)}); + } +} + +void test_diff(uint32_t old_num, + uint32_t add_num, + uint32_t remove_num, + uint32_t change_num) +{ + auto values = make_values_with_collisions(old_num + add_num); + std::vector> initial_values( + values.begin(), values.begin() + old_num); + std::vector> new_values( + values.begin() + old_num, values.end()); + auto map = make_test_map(initial_values); + + std::vector old_keys; + for (auto const& val : map) + old_keys.push_back(val.first); + + auto first_snapshot = map; + CHECK(old_num == first_snapshot.size()); + + // remove + auto shuffle = old_keys; + std::random_shuffle(shuffle.begin(), shuffle.end()); + std::vector remove_keys(shuffle.begin(), + shuffle.begin() + remove_num); + std::vector rest_keys(shuffle.begin() + remove_num, + shuffle.end()); + + using key_set = std::unordered_set; + key_set removed_keys(remove_keys.begin(), remove_keys.end()); + for (auto const& key : remove_keys) + map = map.erase(key); + CHECK(old_num - remove_num == map.size()); + + // add + key_set added_keys; + for (auto const& data : new_values) { + map = map.insert({data.first, data.second}); + added_keys.insert(data.first); + } + + // change + key_set changed_keys; + for (auto i = 0u; i < change_num; i++) { + auto key = rest_keys[i]; + map = map.update(key, [](auto val) { + return std::make_pair(val.first, val.second + 1); + }); + changed_keys.insert(key); + } + + diff( + first_snapshot, + map, + [&](auto const& data) { REQUIRE(added_keys.erase(data.first) > 0); }, + [&](auto const& data) { REQUIRE(removed_keys.erase(data.first) > 0); }, + [&](auto const& old_data, auto const& new_data) { + (void) old_data; + REQUIRE(changed_keys.erase(new_data.first) > 0); + }); + + CHECK(added_keys.empty()); + CHECK(changed_keys.empty()); + CHECK(removed_keys.empty()); +} + +TEST_CASE("diff") +{ + test_diff(16, 10, 10, 3); + test_diff(100, 10, 10, 10); + test_diff(1500, 10, 1000, 100); + test_diff(16, 1500, 10, 3); + test_diff(100, 0, 0, 50); +} + +TEST_CASE("const map") +{ + const auto x = table_map{}.insert({"A", 1}); + auto it = x.begin(); + CHECK(it->first == "A"); + CHECK(it->second == 1); +} diff --git a/test/table_transient/B3.cpp b/test/table_transient/B3.cpp new file mode 100644 index 0000000000..ff9b17cf04 --- /dev/null +++ b/test/table_transient/B3.cpp @@ -0,0 +1,34 @@ +// +// 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 + +struct setup_t +{ + template + using table_transient = immer::table_transient< + T, + immer::table_key_fn, + std::hash>, + std::equal_to>, + immer::default_memory_policy, + 3u>; + + template + using table = + immer::table>, + std::equal_to>, + immer::default_memory_policy, + 3u>; +}; +#define SETUP_T setup_t + +#include "generic.ipp" diff --git a/test/table_transient/B6.cpp b/test/table_transient/B6.cpp new file mode 100644 index 0000000000..8dacd058c4 --- /dev/null +++ b/test/table_transient/B6.cpp @@ -0,0 +1,34 @@ +// +// 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 + +struct setup_t +{ + template + using table_transient = immer::table_transient< + T, + immer::table_key_fn, + std::hash>, + std::equal_to>, + immer::default_memory_policy, + 6u>; + + template + using table = + immer::table>, + std::equal_to>, + immer::default_memory_policy, + 6u>; +}; +#define SETUP_T setup_t + +#include "generic.ipp" diff --git a/test/table_transient/default.cpp b/test/table_transient/default.cpp new file mode 100644 index 0000000000..3ccf1ad312 --- /dev/null +++ b/test/table_transient/default.cpp @@ -0,0 +1,34 @@ +// +// 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 + +struct setup_t +{ + template + using table_transient = immer::table_transient< + T, + immer::table_key_fn, + std::hash>, + std::equal_to>, + immer::default_memory_policy, + immer::default_bits>; + + template + using table = + immer::table>, + std::equal_to>, + immer::default_memory_policy, + immer::default_bits>; +}; +#define SETUP_T setup_t + +#include "generic.ipp" diff --git a/test/table_transient/gc.cpp b/test/table_transient/gc.cpp new file mode 100644 index 0000000000..477099326a --- /dev/null +++ b/test/table_transient/gc.cpp @@ -0,0 +1,42 @@ +// +// 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 + +struct setup_t +{ + using gc_memory = immer::memory_policy, + immer::no_refcount_policy, + immer::default_lock_policy, + immer::gc_transience_policy, + false>; + + template + using table_transient = immer::table_transient< + T, + immer::table_key_fn, + std::hash>, + std::equal_to>, + gc_memory, + immer::default_bits>; + + template + using table = + immer::table>, + std::equal_to>, + gc_memory, + immer::default_bits>; +}; +#define SETUP_T setup_t + +#include "generic.ipp" diff --git a/test/table_transient/generic.ipp b/test/table_transient/generic.ipp new file mode 100644 index 0000000000..73725be58b --- /dev/null +++ b/test/table_transient/generic.ipp @@ -0,0 +1,103 @@ +// +// 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 "test/util.hpp" + +#include + +#ifndef SETUP_T +#error "define the table types via SETUP_T macro" +#endif + +#include + +struct Item +{ + std::string id{}; + int value{}; + + bool operator==(const Item& other) const + { + return value == other.value && id == other.id; + } +}; + +IMMER_RANGES_CHECK(std::ranges::forward_range>); +IMMER_RANGES_CHECK(std::ranges::forward_range>); + +TEST_CASE("instantiate") +{ + auto t = SETUP_T::table_transient{}; + auto m = SETUP_T::table{}; + CHECK(t.persistent() == m); + CHECK(t.persistent() == m.transient().persistent()); +} + +TEST_CASE("access") +{ + auto m = SETUP_T::table{Item{"foo", 12}, Item{"bar", 42}}; + auto t = m.transient(); + CHECK(t.size() == 2); + CHECK(t.count("foo") == 1); + CHECK(t["foo"].value == 12); + CHECK(t.at("foo").value == 12); + CHECK(t.find("foo") == m.find("foo")); + CHECK(std::accumulate(t.begin(), t.end(), 0, [](auto acc, auto&& x) { + return acc + x.value; + }) == 54); +} + +TEST_CASE("insert") +{ + auto t = SETUP_T::table_transient{}; + + t.insert(Item{"foo", 42}); + CHECK(t["foo"].value == 42); + CHECK(t.size() == 1); + + t.insert(Item{"bar", 13}); + CHECK(t["bar"].value == 13); + CHECK(t.size() == 2); + + t.insert(Item{"foo", 6}); + CHECK(t["foo"].value == 6); + CHECK(t.size() == 2); + + t.update("foo", [](auto item) { + item.value += 1; + return item; + }); + CHECK(t["foo"].value == 7); + CHECK(t.size() == 2); + + t.update("lol", [](auto item) { + item.value += 1; + return item; + }); + CHECK(t["lol"].value == 1); + CHECK(t.size() == 3); + + t.update_if_exists("foo", [](auto item) { + item.value += 1; + return item; + }); + CHECK(t["foo"].value == 8); + CHECK(t.size() == 3); +} + +TEST_CASE("erase") +{ + auto t = SETUP_T::table{{"foo", 12}, {"bar", 42}}.transient(); + + t.erase("foo"); + CHECK(t.find("foo") == nullptr); + CHECK(t.count("foo") == 0); + CHECK(t.find("bar") != nullptr); + CHECK(t.count("bar") == 1); + CHECK(t.size() == 1); +} diff --git a/test/util.hpp b/test/util.hpp index 41904f5a89..30100dd007 100644 --- a/test/util.hpp +++ b/test/util.hpp @@ -12,6 +12,18 @@ #include #include +#include // For __cpp_lib_ranges + +// If we have ranges, include the header for the concepts, and define +// IMMER_RANGES_CHECK() to expand to a static_assert of the argument, otherwise +// define it as a no-op static_assert. +#if __cpp_lib_ranges +#include +#define IMMER_RANGES_CHECK(...) static_assert(__VA_ARGS__) +#else +#define IMMER_RANGES_CHECK(...) static_assert(true, "") +#endif + namespace { struct identity_t diff --git a/test/vector/generic.ipp b/test/vector/generic.ipp index 38392b97bf..ccce07aee2 100644 --- a/test/vector/generic.ipp +++ b/test/vector/generic.ipp @@ -12,7 +12,7 @@ #include #include -#include +#include #include #include @@ -25,6 +25,8 @@ using namespace std::string_literals; #error "define the vector template to use in VECTOR_T" #endif +IMMER_RANGES_CHECK(std::ranges::random_access_range>); + template > auto make_test_vector(unsigned min, unsigned max) { @@ -118,6 +120,18 @@ TEST_CASE("at") #endif } +TEST_CASE("random_access iteration") +{ + auto v = VECTOR_T{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; + auto iter = v.begin(); + CHECK(*iter == 0); + CHECK(iter[0] == 0); + CHECK(iter[3] == 3); + CHECK(iter[9] == 9); + iter += 4; + CHECK(iter[-4] == 0); +} + TEST_CASE("push back one element") { SECTION("one element") @@ -127,6 +141,12 @@ TEST_CASE("push back one element") CHECK(v1.size() == 0u); CHECK(v2.size() == 1u); CHECK(v2[0] == 42); + + // basic identity rules + auto v3 = v2; + CHECK(v1.identity() != v2.identity()); + CHECK(v3.identity() == v2.identity()); + CHECK(v1.identity() == VECTOR_T{}.identity()); } SECTION("many elements") @@ -456,6 +476,22 @@ TEST_CASE("exception safety") IMMER_TRACE_E(d.happenings); } + SECTION("push back move") + { + auto v = dadaist_vector_t{}; + auto d = dadaism{}; + for (auto i = 0u; v.size() < static_cast(n);) { + auto s = d.next(); + try { + v = std::move(v).push_back({i}); + ++i; + } catch (dada_error) {} + CHECK_VECTOR_EQUALS(v, boost::irange(0u, i)); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + SECTION("update") { auto v = make_test_vector(0, n); @@ -473,6 +509,24 @@ TEST_CASE("exception safety") IMMER_TRACE_E(d.happenings); } + SECTION("update move") + { + auto v = make_test_vector(0, n); + auto d = dadaism{}; + for (auto i = 0u; i < n;) { + auto s = d.next(); + try { + v = std::move(v).update(i, + [](auto x) { return dada(), x + 1; }); + ++i; + } catch (dada_error) {} + CHECK_VECTOR_EQUALS( + v, boost::join(boost::irange(1u, 1u + i), boost::irange(i, n))); + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } + SECTION("take") { auto v = make_test_vector(0, n); @@ -490,4 +544,23 @@ TEST_CASE("exception safety") CHECK(d.happenings > 0); IMMER_TRACE_E(d.happenings); } + + SECTION("take move") + { + auto v = make_test_vector(0, n); + auto d = dadaism{}; + auto r = dadaist_vector_t{v}; + for (auto i = 0u; i < n - 1;) { + auto s = d.next(); + try { + r = std::move(r).take(n - i - 1); + CHECK_VECTOR_EQUALS(r, boost::irange(0u, n - i - 1)); + ++i; + } catch (dada_error) { + CHECK_VECTOR_EQUALS(r, boost::irange(0u, n - i)); + } + } + CHECK(d.happenings > 0); + IMMER_TRACE_E(d.happenings); + } } diff --git a/test/vector/issue-177.cpp b/test/vector/issue-177.cpp index 09dff26247..ac953d86e5 100644 --- a/test/vector/issue-177.cpp +++ b/test/vector/issue-177.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include struct object; diff --git a/test/vector/issue-46.cpp b/test/vector/issue-46.cpp index 6475be210d..0032b71f61 100644 --- a/test/vector/issue-46.cpp +++ b/test/vector/issue-46.cpp @@ -13,7 +13,7 @@ #include #include -#include +#include TEST_CASE("operator==() may return bad result") { diff --git a/test/vector_transient/generic.ipp b/test/vector_transient/generic.ipp index 6cd25a567d..6b3e6dadbe 100644 --- a/test/vector_transient/generic.ipp +++ b/test/vector_transient/generic.ipp @@ -10,7 +10,7 @@ #include "test/transient_tester.hpp" #include "test/util.hpp" -#include +#include #ifndef VECTOR_T #error "define the vector template to use in VECTOR_T" @@ -20,6 +20,8 @@ #error "define the vector template to use in VECTOR_TRANSIENT_T" #endif +IMMER_RANGES_CHECK(std::ranges::random_access_range>); + template > auto make_test_vector(unsigned min, unsigned max) { diff --git a/tools/gdb_pretty_printers/__init__.py b/tools/gdb_pretty_printers/__init__.py new file mode 100644 index 0000000000..1bb8bf6d7f --- /dev/null +++ b/tools/gdb_pretty_printers/__init__.py @@ -0,0 +1 @@ +# empty diff --git a/tools/gdb_pretty_printers/autoload.py b/tools/gdb_pretty_printers/autoload.py new file mode 100644 index 0000000000..e3d6d05985 --- /dev/null +++ b/tools/gdb_pretty_printers/autoload.py @@ -0,0 +1,14 @@ +import gdb.printing +import os +import sys +import inspect + +filename = inspect.getframeinfo(inspect.currentframe()).filename +path = os.path.dirname(os.path.abspath(filename)) +if not path in sys.path: + sys.path.append(path) +from printers import immer_lookup_function + +gdb.printing.register_pretty_printer(gdb.current_objfile(), immer_lookup_function) + +print("immer gdb pretty-printers loaded") diff --git a/tools/gdb_pretty_printers/printers.py b/tools/gdb_pretty_printers/printers.py new file mode 100644 index 0000000000..c33e7efdf6 --- /dev/null +++ b/tools/gdb_pretty_printers/printers.py @@ -0,0 +1,485 @@ +# Sourced from https://gist.github.com/dwightguth/283afe96b60b3793f3c02036701457f8 +# with light modifications. + +import gdb.printing +import decimal +import traceback +import re + +MAX = 1 << 64 - 1 + + +class ArrayIter: + def __init__(self, val): + self.val_ptr = val.type.template_argument(0).pointer() + self.v = val['impl_'] + self.size = self.v['size'] + self.i = 0 + + def __iter__(self): + return self + + def __next__(self): + if self.i == self.size: + raise StopIteration + ptr = self.v['ptr'] + data = ptr.dereference()['impl']['d']['buffer'].address.reinterpret_cast(self.val_ptr) + result = ('[%d]' % self.i, data[self.i]) + self.i += 1 + return result + + +class Relaxed: + def __init__(self, node, shift, relaxed, it): + self.node = node + self.shift = shift + self.relaxed = relaxed + self.it = it + self.B = self.node.type.target().template_argument(2) + self.BL = self.node.type.target().template_argument(3) + + def index(self, idx): + offset = idx >> self.shift + while self.relaxed.dereference()['d']['sizes'][offset] <= idx: + offset += 1 + return offset + + def towards(self, idx): + offset = self.index(idx) + left_size = self.relaxed.dereference()['d']['sizes'][offset - 1] if offset else 0 + child = self.it.inner(self.node)[offset] + is_leaf = self.shift == self.BL + next_size = self.relaxed.dereference()['d']['sizes'][offset] - left_size + next_idx = idx - left_size + if is_leaf: + return self.it.visit_leaf(LeafSub(child, next_size), next_idx) + else: + return self.it.visit_maybe_relaxed_sub(child, self.shift - self.B, next_size, next_idx) + + +class LeafSub: + def __init__(self, node, count): + self.node = node + self.count_ = count + self.BL = self.node.type.target().template_argument(3) + self.MASK = (1 << self.BL) - 1 + + def index(self, idx): + return idx & self.MASK + + def count(self): + return self.count_ + + +class FullLeaf: + def __init__(self, node): + self.node = node + self.BL = self.node.type.target().template_argument(3) + self.BRANCHES = 1 << self.BL + self.MASK = self.BRANCHES - 1 + + def index(self, idx): + return idx & self.MASK + + def count(self): + return self.BRANCHES + + +class Leaf: + def __init__(self, node, size): + self.node = node + self.size = size + self.BL = self.node.type.target().template_argument(3) + self.MASK = (1 << self.BL) - 1 + + def index(self, idx): + return idx & self.MASK + + def count(self): + return self.index(self.size - 1) + 1 + + +class RegularSub: + def __init__(self, node, shift, size, it): + self.node = node + self.shift = shift + self.size = size + self.it = it + self.B = self.node.type.target().template_argument(2) + self.MASK = (1 << self.B) - 1 + + def towards(self, idx): + offset = self.index(idx) + count = self.count() + return self.it.towards_regular(self, idx, offset, count) + + def index(self, idx): + return (idx >> self.shift) & self.MASK + + def count(self): + return self.subindex(self.size - 1) + 1 + + def subindex(self, idx): + return idx >> self.shift + + +class Regular: + def __init__(self, node, shift, size, it): + self.node = node + self.shift = shift + self.size = size + self.it = it + self.B = self.node.type.target().template_argument(2) + self.MASK = (1 << self.B) - 1 + + def index(self, idx): + return (idx >> self.shift) & self.MASK + + def count(self): + return self.index(self.size - 1) + 1 + + def towards(self, idx): + offset = self.index(idx) + count = self.count() + return self.it.towards_regular(self, idx, offset, count) + + +class Full: + def __init__(self, node, shift, it): + self.node = node + self.shift = shift + self.it = it + self.B = self.node.type.target().template_argument(2) + self.BL = self.node.type.target().template_argument(3) + self.MASK = (1 << self.B) - 1 + + def index(self, idx): + return (idx >> self.shift) & self.MASK + + def towards(self, idx): + offset = self.index(idx) + is_leaf = self.shift == self.BL + child = self.it.inner(self.node)[offset] + if is_leaf: + return self.it.visit_leaf(FullLeaf(child), idx) + else: + return Full(child, self.shift - self.B, self.it).towards(idx) + + +class ListIter: + def __init__(self, val): + self.v = val['impl_'] + self.size = self.v['size'] + self.i = 0 + self.curr = (None, MAX, MAX) + self.node_ptr_ptr = self.v['root'].type.pointer() + self.B = self.v['root'].type.target().template_argument(2) + self.BL = self.v['root'].type.target().template_argument(3) + + def __iter__(self): + return self + + def __next__(self): + if self.i == self.size: + raise StopIteration + if self.i < self.curr[1] or self.i >= self.curr[2]: + self.curr = self.region() + result = ('[%d]' % self.i, self.curr[0][self.i - self.curr[1]].cast( + gdb.lookup_type(self.v.type.template_argument(0).name))) + self.i += 1 + return result + + def region(self): + tail_off = self.tail_offset() + if self.i >= tail_off: + return (self.leaf(self.v['tail']), tail_off, self.size) + else: + subs = self.visit_maybe_relaxed_sub(self.v['root'], self.v['shift'], tail_off, self.i) + first = self.i - subs[1] + end = first + subs[2] + return (subs[0], first, end) + + def tail_offset(self): + r = self.relaxed(self.v['root']) + if r: + return r.dereference()['d']['sizes'][r.dereference()['d']['count'] - 1] + elif self.size: + return (self.size - 1) & ~self.leaf_mask() + else: + return 0 + + def relaxed(self, node): + return node.dereference()['impl']['d']['data']['inner']['relaxed'] + + def leaf(self, node): + return node.dereference()['impl']['d']['data']['leaf']['buffer'].address + + def inner(self, node): + return node.dereference()['impl']['d']['data']['inner']['buffer'].address.reinterpret_cast( + self.node_ptr_ptr) + + def visit_maybe_relaxed_sub(self, node, shift, size, idx): + relaxed = self.relaxed(node) + if relaxed: + return Relaxed(node, shift, relaxed, self).towards(idx) + else: + return RegularSub(node, shift, size, self).towards(idx) + + def visit_leaf(self, pos, idx): + return (self.leaf(pos.node), pos.index(idx), pos.count()) + + # pos = node, idx = full, offset = shifted & masked, count = shifted + def towards_regular(self, pos, idx, offset, count): + is_leaf = pos.shift == self.BL + child = self.inner(pos.node)[offset] + is_full = offset + 1 != count + if is_full: + if is_leaf: + return self.visit_leaf(FullLeaf(child), idx) + else: + return Full(child, pos.shift - self.B, self).towards(idx) + elif is_leaf: + return self.visit_leaf(Leaf(child, pos.size), idx) + else: + return Regular(child, pos.shift - self.B, pos.size, self).towards(idx) + + def leaf_mask(self): + return (1 << self.BL) - 1 + + +def popcount(x): + b = 0 + while x > 0: + x &= x - 1 + b += 1 + return b + + +class ChampIter: + def __init__(self, val): + self.depth = 0 + self.count = 0 + v = val['impl_']['root'] + self.node_ptr_ptr = v.type.pointer() + m = self.datamap(v) + if m: + self.cur = self.values(v) + self.end = self.values(v) + popcount(m) + else: + self.cur = None + self.end = None + self.path = [v.address] + self.B = v.type.target().template_argument(4) + self.MAX_DEPTH = ((8 * 8) + self.B - 1) / 8 + self.ensure_valid() + + def __iter__(self): + return self + + def __next__(self): + if self.cur == None: + raise StopIteration + result = self.cur.dereference() + self.cur += 1 + self.count += 1 + self.ensure_valid() + return result + + def ensure_valid(self): + while self.cur == self.end: + while self.step_down(): + if self.cur != self.end: + return + if not self.step_right(): + self.cur = None + self.end = None + return + + def step_down(self): + if self.depth < self.MAX_DEPTH: + parent = self.path[self.depth].dereference() + if self.nodemap(parent): + self.depth += 1 + self.path.append(self.children(parent)) + child = self.path[self.depth] + if self.depth < self.MAX_DEPTH: + m = self.datamap(child) + if m: + self.cur = self.values(child) + self.end = self.cur + popcount(m) + else: + self.cur = self.collisions(child) + self.end = self.cur = self.collision_count(child) + return True + return False + + def step_right(self): + while self.depth > 0: + parent = self.path[self.depth - 1].dereference() + last = self.children(parent) + popcount(self.nodemap(parent)) + next_ = self.path[self.depth] + 1 + if next_ < last: + self.path[self.depth] = next_ + child = self.path[self.depth].dereference() + if self.depth < self.MAX_DEPTH: + m = self.datamap(child) + if m: + self.cur = self.values(child) + self.end = self.cur + popcount(m) + else: + self.cur = self.collisions(child) + self.end = self.cur + self.collision_count(child) + return True + self.depth -= 1 + self.path.pop() + return False + + def values(self, node): + return node.dereference()['impl']['d']['data']['inner']['values'].dereference( + )['d']['buffer'].address.cast(self.T_ptr) + + def children(self, node): + return node.dereference()['impl']['d']['data']['inner']['buffer'].address.cast( + self.node_ptr_ptr) + + def datamap(self, node): + return node.dereference()['impl']['d']['data']['inner']['datamap'] + + def nodemap(self, node): + return node.dereference()['impl']['d']['data']['inner']['nodemap'] + + def collision_count(self, node): + return node.dereference()['impl']['d']['data']['collision']['count'] + + def collisions(self, node): + return node.dereference()['impl']['d']['data']['collision']['buffer'].address.cast( + self.T_ptr) + + +class MapIter(ChampIter): + def __init__(self, val): + self.T_ptr = gdb.lookup_type("std::pair<" + val.type.template_argument(0).name + ", " + + val.type.template_argument(1).name + ">").pointer() + ChampIter.__init__(self, val) + self.pair = None + + def __next__(self): + if self.pair: + result = ('[%d]' % self.count, self.pair['second']) + self.pair = None + return result + self.pair = super().__next__() + return ('[%d]' % self.count, self.pair['first']) + + +class SetIter(ChampIter): + def __init__(self, val): + self.T_ptr = gdb.lookup_type(val.type.template_argument(0).name).pointer() + ChampIter.__init__(self, val) + + def __next__(self): + return ('[%d]' % self.count, super().__next__()) + + +def num_elements(num): + return '1 element' if num == 1 else '%d elements' % num + + +class ArrayPrinter: + "Prints an immer::array" + + def __init__(self, val): + self.val = val + + def to_string(self): + return 'immer::array with %s' % num_elements(self.val['impl_']['size']) + + def children(self): + return ArrayIter(self.val) + + def display_hint(self): + return 'array' + + +class MapPrinter: + "Print an immer::map" + + def __init__(self, val): + self.val = val + + def to_string(self): + return 'immer::map with %s' % num_elements(self.val['impl_']['size']) + + def children(self): + return MapIter(self.val) + + def display_hint(self): + return 'map' + + +class SetPrinter: + "Prints an immer::set" + + def __init__(self, val): + self.val = val + + def to_string(self): + return 'immer::set with %s' % num_elements(self.val['impl_']['size']) + + def children(self): + return SetIter(self.val) + + +class TablePrinter: + "Prints an immer::table" + + def __init__(self, val): + self.val = val + + def to_string(self): + return 'immer::table with %s' % num_elements(self.val['impl_']['size']) + + def children(self): + return SetIter(self.val) + + +class ListPrinter: + "Prints an immer::vector or immer::flex_vector" + + def __init__(self, val, typename): + self.val = val + self.typename = typename + + def to_string(self): + return '%s of length %d' % (self.typename, int(self.val['impl_']['size'])) + + def children(self): + return ListIter(self.val) + + def display_hint(self): + return 'array' + + +def immer_lookup_function(val): + compiled_rx = re.compile('^([a-zA-Z0-9_:]+)(<.*>)?$') + typename = gdb.types.get_basic_type(val.type).tag + if not typename: + return None + match = compiled_rx.match(typename) + if not match: + return None + + basename = match.group(1) + if basename == "immer::array": + return ArrayPrinter(val) + elif basename == "immer::map": + return MapPrinter(val) + elif basename == "immer::set": + return SetPrinter(val) + elif basename == "immer::table": + return TablePrinter(val) + elif basename == "immer::vector": + return ListPrinter(val, "immer::vector") + elif basename == "immer::flex_vector": + return ListPrinter(val, "immer::flex_vector") + return None diff --git a/tools/include/catch.hpp b/tools/include/catch.hpp deleted file mode 100644 index 7e706f947a..0000000000 --- a/tools/include/catch.hpp +++ /dev/null @@ -1,17959 +0,0 @@ -/* - * Catch v2.13.7 - * Generated: 2021-07-28 20:29:27.753164 - * ---------------------------------------------------------- - * This file has been merged from multiple headers. Please don't edit it directly - * Copyright (c) 2021 Two Blue Cubes Ltd. All rights reserved. - * - * Distributed under the Boost Software License, Version 1.0. (See accompanying - * file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) - */ -#ifndef TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -#define TWOBLUECUBES_SINGLE_INCLUDE_CATCH_HPP_INCLUDED -// start catch.hpp - - -#define CATCH_VERSION_MAJOR 2 -#define CATCH_VERSION_MINOR 13 -#define CATCH_VERSION_PATCH 7 - -#ifdef __clang__ -# pragma clang system_header -#elif defined __GNUC__ -# pragma GCC system_header -#endif - -// start catch_suppress_warnings.h - -#ifdef __clang__ -# ifdef __ICC // icpc defines the __clang__ macro -# pragma warning(push) -# pragma warning(disable: 161 1682) -# else // __ICC -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wpadded" -# pragma clang diagnostic ignored "-Wswitch-enum" -# pragma clang diagnostic ignored "-Wcovered-switch-default" -# endif -#elif defined __GNUC__ - // Because REQUIREs trigger GCC's -Wparentheses, and because still - // supported version of g++ have only buggy support for _Pragmas, - // Wparentheses have to be suppressed globally. -# pragma GCC diagnostic ignored "-Wparentheses" // See #674 for details - -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wunused-variable" -# pragma GCC diagnostic ignored "-Wpadded" -#endif -// end catch_suppress_warnings.h -#if defined(CATCH_CONFIG_MAIN) || defined(CATCH_CONFIG_RUNNER) -# define CATCH_IMPL -# define CATCH_CONFIG_ALL_PARTS -#endif - -// In the impl file, we want to have access to all parts of the headers -// Can also be used to sanely support PCHs -#if defined(CATCH_CONFIG_ALL_PARTS) -# define CATCH_CONFIG_EXTERNAL_INTERFACES -# if defined(CATCH_CONFIG_DISABLE_MATCHERS) -# undef CATCH_CONFIG_DISABLE_MATCHERS -# endif -# if !defined(CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER) -# define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -# endif -#endif - -#if !defined(CATCH_CONFIG_IMPL_ONLY) -// start catch_platform.h - -// See e.g.: -// https://opensource.apple.com/source/CarbonHeaders/CarbonHeaders-18.1/TargetConditionals.h.auto.html -#ifdef __APPLE__ -# include -# if (defined(TARGET_OS_OSX) && TARGET_OS_OSX == 1) || \ - (defined(TARGET_OS_MAC) && TARGET_OS_MAC == 1) -# define CATCH_PLATFORM_MAC -# elif (defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE == 1) -# define CATCH_PLATFORM_IPHONE -# endif - -#elif defined(linux) || defined(__linux) || defined(__linux__) -# define CATCH_PLATFORM_LINUX - -#elif defined(WIN32) || defined(__WIN32__) || defined(_WIN32) || defined(_MSC_VER) || defined(__MINGW32__) -# define CATCH_PLATFORM_WINDOWS -#endif - -// end catch_platform.h - -#ifdef CATCH_IMPL -# ifndef CLARA_CONFIG_MAIN -# define CLARA_CONFIG_MAIN_NOT_DEFINED -# define CLARA_CONFIG_MAIN -# endif -#endif - -// start catch_user_interfaces.h - -namespace Catch { - unsigned int rngSeed(); -} - -// end catch_user_interfaces.h -// start catch_tag_alias_autoregistrar.h - -// start catch_common.h - -// start catch_compiler_capabilities.h - -// Detect a number of compiler features - by compiler -// The following features are defined: -// -// CATCH_CONFIG_COUNTER : is the __COUNTER__ macro supported? -// CATCH_CONFIG_WINDOWS_SEH : is Windows SEH supported? -// CATCH_CONFIG_POSIX_SIGNALS : are POSIX signals supported? -// CATCH_CONFIG_DISABLE_EXCEPTIONS : Are exceptions enabled? -// **************** -// Note to maintainers: if new toggles are added please document them -// in configuration.md, too -// **************** - -// In general each macro has a _NO_ form -// (e.g. CATCH_CONFIG_NO_POSIX_SIGNALS) which disables the feature. -// Many features, at point of detection, define an _INTERNAL_ macro, so they -// can be combined, en-mass, with the _NO_ forms later. - -#ifdef __cplusplus - -# if (__cplusplus >= 201402L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402L) -# define CATCH_CPP14_OR_GREATER -# endif - -# if (__cplusplus >= 201703L) || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -# define CATCH_CPP17_OR_GREATER -# endif - -#endif - -// Only GCC compiler should be used in this block, so other compilers trying to -// mask themselves as GCC should be ignored. -#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "GCC diagnostic pop" ) - -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) - -#endif - -#if defined(__clang__) - -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic push" ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION _Pragma( "clang diagnostic pop" ) - -// As of this writing, IBM XL's implementation of __builtin_constant_p has a bug -// which results in calls to destructors being emitted for each temporary, -// without a matching initialization. In practice, this can result in something -// like `std::string::~string` being called on an uninitialized value. -// -// For example, this code will likely segfault under IBM XL: -// ``` -// REQUIRE(std::string("12") + "34" == "1234") -// ``` -// -// Therefore, `CATCH_INTERNAL_IGNORE_BUT_WARN` is not implemented. -# if !defined(__ibmxl__) && !defined(__CUDACC__) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) (void)__builtin_constant_p(__VA_ARGS__) /* NOLINT(cppcoreguidelines-pro-type-vararg, hicpp-vararg) */ -# endif - -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wexit-time-destructors\"" ) \ - _Pragma( "clang diagnostic ignored \"-Wglobal-constructors\"") - -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wparentheses\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-variable\"" ) - -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wgnu-zero-variadic-macro-arguments\"" ) - -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS \ - _Pragma( "clang diagnostic ignored \"-Wunused-template\"" ) - -#endif // __clang__ - -//////////////////////////////////////////////////////////////////////////////// -// Assume that non-Windows platforms support posix signals by default -#if !defined(CATCH_PLATFORM_WINDOWS) - #define CATCH_INTERNAL_CONFIG_POSIX_SIGNALS -#endif - -//////////////////////////////////////////////////////////////////////////////// -// We know some environments not to support full POSIX signals -#if defined(__CYGWIN__) || defined(__QNX__) || defined(__EMSCRIPTEN__) || defined(__DJGPP__) - #define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -#endif - -#ifdef __OS400__ -# define CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS -# define CATCH_CONFIG_COLOUR_NONE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Android somehow still does not support std::to_string -#if defined(__ANDROID__) -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING -# define CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Not all Windows environments support SEH properly -#if defined(__MINGW32__) -# define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH -#endif - -//////////////////////////////////////////////////////////////////////////////// -// PS4 -#if defined(__ORBIS__) -# define CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE -#endif - -//////////////////////////////////////////////////////////////////////////////// -// Cygwin -#ifdef __CYGWIN__ - -// Required for some versions of Cygwin to declare gettimeofday -// see: http://stackoverflow.com/questions/36901803/gettimeofday-not-declared-in-this-scope-cygwin -# define _BSD_SOURCE -// some versions of cygwin (most) do not support std::to_string. Use the libstd check. -// https://gcc.gnu.org/onlinedocs/gcc-4.8.2/libstdc++/api/a01053_source.html line 2812-2813 -# if !((__cplusplus >= 201103L) && defined(_GLIBCXX_USE_C99) \ - && !defined(_GLIBCXX_HAVE_BROKEN_VSWPRINTF)) - -# define CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING - -# endif -#endif // __CYGWIN__ - -//////////////////////////////////////////////////////////////////////////////// -// Visual C++ -#if defined(_MSC_VER) - -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION __pragma( warning(push) ) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION __pragma( warning(pop) ) - -// Universal Windows platform does not support SEH -// Or console colours (or console at all...) -# if defined(WINAPI_FAMILY) && (WINAPI_FAMILY == WINAPI_FAMILY_APP) -# define CATCH_CONFIG_COLOUR_NONE -# else -# define CATCH_INTERNAL_CONFIG_WINDOWS_SEH -# endif - -// MSVC traditional preprocessor needs some workaround for __VA_ARGS__ -// _MSVC_TRADITIONAL == 0 means new conformant preprocessor -// _MSVC_TRADITIONAL == 1 means old traditional non-conformant preprocessor -# if !defined(__clang__) // Handle Clang masquerading for msvc -# if !defined(_MSVC_TRADITIONAL) || (defined(_MSVC_TRADITIONAL) && _MSVC_TRADITIONAL) -# define CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -# endif // MSVC_TRADITIONAL -# endif // __clang__ - -#endif // _MSC_VER - -#if defined(_REENTRANT) || defined(_MSC_VER) -// Enable async processing, as -pthread is specified or no additional linking is required -# define CATCH_INTERNAL_CONFIG_USE_ASYNC -#endif // _MSC_VER - -//////////////////////////////////////////////////////////////////////////////// -// Check if we are compiled with -fno-exceptions or equivalent -#if defined(__EXCEPTIONS) || defined(__cpp_exceptions) || defined(_CPPUNWIND) -# define CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED -#endif - -//////////////////////////////////////////////////////////////////////////////// -// DJGPP -#ifdef __DJGPP__ -# define CATCH_INTERNAL_CONFIG_NO_WCHAR -#endif // __DJGPP__ - -//////////////////////////////////////////////////////////////////////////////// -// Embarcadero C++Build -#if defined(__BORLANDC__) - #define CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// Use of __COUNTER__ is suppressed during code analysis in -// CLion/AppCode 2017.2.x and former, because __COUNTER__ is not properly -// handled by it. -// Otherwise all supported compilers support COUNTER macro, -// but user still might want to turn it off -#if ( !defined(__JETBRAINS_IDE__) || __JETBRAINS_IDE__ >= 20170300L ) - #define CATCH_INTERNAL_CONFIG_COUNTER -#endif - -//////////////////////////////////////////////////////////////////////////////// - -// RTX is a special version of Windows that is real time. -// This means that it is detected as Windows, but does not provide -// the same set of capabilities as real Windows does. -#if defined(UNDER_RTSS) || defined(RTX64_BUILD) - #define CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH - #define CATCH_INTERNAL_CONFIG_NO_ASYNC - #define CATCH_CONFIG_COLOUR_NONE -#endif - -#if !defined(_GLIBCXX_USE_C99_MATH_TR1) -#define CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Various stdlib support checks that require __has_include -#if defined(__has_include) - // Check if string_view is available and usable - #if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW - #endif - - // Check if optional is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # define CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if byte is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # include - # if defined(__cpp_lib_byte) && (__cpp_lib_byte > 0) - # define CATCH_INTERNAL_CONFIG_CPP17_BYTE - # endif - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) - - // Check if variant is available and usable - # if __has_include() && defined(CATCH_CPP17_OR_GREATER) - # if defined(__clang__) && (__clang_major__ < 8) - // work around clang bug with libstdc++ https://bugs.llvm.org/show_bug.cgi?id=31852 - // fix should be in clang 8, workaround in libstdc++ 8.2 - # include - # if defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # define CATCH_CONFIG_NO_CPP17_VARIANT - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__GLIBCXX__) && defined(_GLIBCXX_RELEASE) && (_GLIBCXX_RELEASE < 9) - # else - # define CATCH_INTERNAL_CONFIG_CPP17_VARIANT - # endif // defined(__clang__) && (__clang_major__ < 8) - # endif // __has_include() && defined(CATCH_CPP17_OR_GREATER) -#endif // defined(__has_include) - -#if defined(CATCH_INTERNAL_CONFIG_COUNTER) && !defined(CATCH_CONFIG_NO_COUNTER) && !defined(CATCH_CONFIG_COUNTER) -# define CATCH_CONFIG_COUNTER -#endif -#if defined(CATCH_INTERNAL_CONFIG_WINDOWS_SEH) && !defined(CATCH_CONFIG_NO_WINDOWS_SEH) && !defined(CATCH_CONFIG_WINDOWS_SEH) && !defined(CATCH_INTERNAL_CONFIG_NO_WINDOWS_SEH) -# define CATCH_CONFIG_WINDOWS_SEH -#endif -// This is set by default, because we assume that unix compilers are posix-signal-compatible by default. -#if defined(CATCH_INTERNAL_CONFIG_POSIX_SIGNALS) && !defined(CATCH_INTERNAL_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_NO_POSIX_SIGNALS) && !defined(CATCH_CONFIG_POSIX_SIGNALS) -# define CATCH_CONFIG_POSIX_SIGNALS -#endif -// This is set by default, because we assume that compilers with no wchar_t support are just rare exceptions. -#if !defined(CATCH_INTERNAL_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_NO_WCHAR) && !defined(CATCH_CONFIG_WCHAR) -# define CATCH_CONFIG_WCHAR -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_NO_CPP11_TO_STRING) && !defined(CATCH_CONFIG_CPP11_TO_STRING) -# define CATCH_CONFIG_CPP11_TO_STRING -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_NO_CPP17_OPTIONAL) && !defined(CATCH_CONFIG_CPP17_OPTIONAL) -# define CATCH_CONFIG_CPP17_OPTIONAL -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_NO_CPP17_STRING_VIEW) && !defined(CATCH_CONFIG_CPP17_STRING_VIEW) -# define CATCH_CONFIG_CPP17_STRING_VIEW -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_VARIANT) && !defined(CATCH_CONFIG_NO_CPP17_VARIANT) && !defined(CATCH_CONFIG_CPP17_VARIANT) -# define CATCH_CONFIG_CPP17_VARIANT -#endif - -#if defined(CATCH_INTERNAL_CONFIG_CPP17_BYTE) && !defined(CATCH_CONFIG_NO_CPP17_BYTE) && !defined(CATCH_CONFIG_CPP17_BYTE) -# define CATCH_CONFIG_CPP17_BYTE -#endif - -#if defined(CATCH_CONFIG_EXPERIMENTAL_REDIRECT) -# define CATCH_INTERNAL_CONFIG_NEW_CAPTURE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_NEW_CAPTURE) && !defined(CATCH_INTERNAL_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NO_NEW_CAPTURE) && !defined(CATCH_CONFIG_NEW_CAPTURE) -# define CATCH_CONFIG_NEW_CAPTURE -#endif - -#if !defined(CATCH_INTERNAL_CONFIG_EXCEPTIONS_ENABLED) && !defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -# define CATCH_CONFIG_DISABLE_EXCEPTIONS -#endif - -#if defined(CATCH_INTERNAL_CONFIG_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_NO_POLYFILL_ISNAN) && !defined(CATCH_CONFIG_POLYFILL_ISNAN) -# define CATCH_CONFIG_POLYFILL_ISNAN -#endif - -#if defined(CATCH_INTERNAL_CONFIG_USE_ASYNC) && !defined(CATCH_INTERNAL_CONFIG_NO_ASYNC) && !defined(CATCH_CONFIG_NO_USE_ASYNC) && !defined(CATCH_CONFIG_USE_ASYNC) -# define CATCH_CONFIG_USE_ASYNC -#endif - -#if defined(CATCH_INTERNAL_CONFIG_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_NO_ANDROID_LOGWRITE) && !defined(CATCH_CONFIG_ANDROID_LOGWRITE) -# define CATCH_CONFIG_ANDROID_LOGWRITE -#endif - -#if defined(CATCH_INTERNAL_CONFIG_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_NO_GLOBAL_NEXTAFTER) && !defined(CATCH_CONFIG_GLOBAL_NEXTAFTER) -# define CATCH_CONFIG_GLOBAL_NEXTAFTER -#endif - -// Even if we do not think the compiler has that warning, we still have -// to provide a macro that can be used by the code. -#if !defined(CATCH_INTERNAL_START_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_START_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION) -# define CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_PARENTHESES_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_WARNINGS -#endif -#if !defined(CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_ZERO_VARIADIC_WARNINGS -#endif - -// The goal of this macro is to avoid evaluation of the arguments, but -// still have the compiler warn on problems inside... -#if !defined(CATCH_INTERNAL_IGNORE_BUT_WARN) -# define CATCH_INTERNAL_IGNORE_BUT_WARN(...) -#endif - -#if defined(__APPLE__) && defined(__apple_build_version__) && (__clang_major__ < 10) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#elif defined(__clang__) && (__clang_major__ < 5) -# undef CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if !defined(CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS) -# define CATCH_INTERNAL_SUPPRESS_UNUSED_TEMPLATE_WARNINGS -#endif - -#if defined(CATCH_CONFIG_DISABLE_EXCEPTIONS) -#define CATCH_TRY if ((true)) -#define CATCH_CATCH_ALL if ((false)) -#define CATCH_CATCH_ANON(type) if ((false)) -#else -#define CATCH_TRY try -#define CATCH_CATCH_ALL catch (...) -#define CATCH_CATCH_ANON(type) catch (type) -#endif - -#if defined(CATCH_INTERNAL_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_NO_TRADITIONAL_MSVC_PREPROCESSOR) && !defined(CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR) -#define CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#endif - -// end catch_compiler_capabilities.h -#define INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) name##line -#define INTERNAL_CATCH_UNIQUE_NAME_LINE( name, line ) INTERNAL_CATCH_UNIQUE_NAME_LINE2( name, line ) -#ifdef CATCH_CONFIG_COUNTER -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __COUNTER__ ) -#else -# define INTERNAL_CATCH_UNIQUE_NAME( name ) INTERNAL_CATCH_UNIQUE_NAME_LINE( name, __LINE__ ) -#endif - -#include -#include -#include - -// We need a dummy global operator<< so we can bring it into Catch namespace later -struct Catch_global_namespace_dummy {}; -std::ostream& operator<<(std::ostream&, Catch_global_namespace_dummy); - -namespace Catch { - - struct CaseSensitive { enum Choice { - Yes, - No - }; }; - - class NonCopyable { - NonCopyable( NonCopyable const& ) = delete; - NonCopyable( NonCopyable && ) = delete; - NonCopyable& operator = ( NonCopyable const& ) = delete; - NonCopyable& operator = ( NonCopyable && ) = delete; - - protected: - NonCopyable(); - virtual ~NonCopyable(); - }; - - struct SourceLineInfo { - - SourceLineInfo() = delete; - SourceLineInfo( char const* _file, std::size_t _line ) noexcept - : file( _file ), - line( _line ) - {} - - SourceLineInfo( SourceLineInfo const& other ) = default; - SourceLineInfo& operator = ( SourceLineInfo const& ) = default; - SourceLineInfo( SourceLineInfo&& ) noexcept = default; - SourceLineInfo& operator = ( SourceLineInfo&& ) noexcept = default; - - bool empty() const noexcept { return file[0] == '\0'; } - bool operator == ( SourceLineInfo const& other ) const noexcept; - bool operator < ( SourceLineInfo const& other ) const noexcept; - - char const* file; - std::size_t line; - }; - - std::ostream& operator << ( std::ostream& os, SourceLineInfo const& info ); - - // Bring in operator<< from global namespace into Catch namespace - // This is necessary because the overload of operator<< above makes - // lookup stop at namespace Catch - using ::operator<<; - - // Use this in variadic streaming macros to allow - // >> +StreamEndStop - // as well as - // >> stuff +StreamEndStop - struct StreamEndStop { - std::string operator+() const; - }; - template - T const& operator + ( T const& value, StreamEndStop ) { - return value; - } -} - -#define CATCH_INTERNAL_LINEINFO \ - ::Catch::SourceLineInfo( __FILE__, static_cast( __LINE__ ) ) - -// end catch_common.h -namespace Catch { - - struct RegistrarForTagAliases { - RegistrarForTagAliases( char const* alias, char const* tag, SourceLineInfo const& lineInfo ); - }; - -} // end namespace Catch - -#define CATCH_REGISTER_TAG_ALIAS( alias, spec ) \ - CATCH_INTERNAL_START_WARNINGS_SUPPRESSION \ - CATCH_INTERNAL_SUPPRESS_GLOBALS_WARNINGS \ - namespace{ Catch::RegistrarForTagAliases INTERNAL_CATCH_UNIQUE_NAME( AutoRegisterTagAlias )( alias, spec, CATCH_INTERNAL_LINEINFO ); } \ - CATCH_INTERNAL_STOP_WARNINGS_SUPPRESSION - -// end catch_tag_alias_autoregistrar.h -// start catch_test_registry.h - -// start catch_interfaces_testcase.h - -#include - -namespace Catch { - - class TestSpec; - - struct ITestInvoker { - virtual void invoke () const = 0; - virtual ~ITestInvoker(); - }; - - class TestCase; - struct IConfig; - - struct ITestCaseRegistry { - virtual ~ITestCaseRegistry(); - virtual std::vector const& getAllTests() const = 0; - virtual std::vector const& getAllTestsSorted( IConfig const& config ) const = 0; - }; - - bool isThrowSafe( TestCase const& testCase, IConfig const& config ); - bool matchTest( TestCase const& testCase, TestSpec const& testSpec, IConfig const& config ); - std::vector filterTests( std::vector const& testCases, TestSpec const& testSpec, IConfig const& config ); - std::vector const& getAllTestCasesSorted( IConfig const& config ); - -} - -// end catch_interfaces_testcase.h -// start catch_stringref.h - -#include -#include -#include -#include - -namespace Catch { - - /// A non-owning string class (similar to the forthcoming std::string_view) - /// Note that, because a StringRef may be a substring of another string, - /// it may not be null terminated. - class StringRef { - public: - using size_type = std::size_t; - using const_iterator = const char*; - - private: - static constexpr char const* const s_empty = ""; - - char const* m_start = s_empty; - size_type m_size = 0; - - public: // construction - constexpr StringRef() noexcept = default; - - StringRef( char const* rawChars ) noexcept; - - constexpr StringRef( char const* rawChars, size_type size ) noexcept - : m_start( rawChars ), - m_size( size ) - {} - - StringRef( std::string const& stdString ) noexcept - : m_start( stdString.c_str() ), - m_size( stdString.size() ) - {} - - explicit operator std::string() const { - return std::string(m_start, m_size); - } - - public: // operators - auto operator == ( StringRef const& other ) const noexcept -> bool; - auto operator != (StringRef const& other) const noexcept -> bool { - return !(*this == other); - } - - auto operator[] ( size_type index ) const noexcept -> char { - assert(index < m_size); - return m_start[index]; - } - - public: // named queries - constexpr auto empty() const noexcept -> bool { - return m_size == 0; - } - constexpr auto size() const noexcept -> size_type { - return m_size; - } - - // Returns the current start pointer. If the StringRef is not - // null-terminated, throws std::domain_exception - auto c_str() const -> char const*; - - public: // substrings and searches - // Returns a substring of [start, start + length). - // If start + length > size(), then the substring is [start, size()). - // If start > size(), then the substring is empty. - auto substr( size_type start, size_type length ) const noexcept -> StringRef; - - // Returns the current start pointer. May not be null-terminated. - auto data() const noexcept -> char const*; - - constexpr auto isNullTerminated() const noexcept -> bool { - return m_start[m_size] == '\0'; - } - - public: // iterators - constexpr const_iterator begin() const { return m_start; } - constexpr const_iterator end() const { return m_start + m_size; } - }; - - auto operator += ( std::string& lhs, StringRef const& sr ) -> std::string&; - auto operator << ( std::ostream& os, StringRef const& sr ) -> std::ostream&; - - constexpr auto operator "" _sr( char const* rawChars, std::size_t size ) noexcept -> StringRef { - return StringRef( rawChars, size ); - } -} // namespace Catch - -constexpr auto operator "" _catch_sr( char const* rawChars, std::size_t size ) noexcept -> Catch::StringRef { - return Catch::StringRef( rawChars, size ); -} - -// end catch_stringref.h -// start catch_preprocessor.hpp - - -#define CATCH_RECURSION_LEVEL0(...) __VA_ARGS__ -#define CATCH_RECURSION_LEVEL1(...) CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(CATCH_RECURSION_LEVEL0(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL2(...) CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(CATCH_RECURSION_LEVEL1(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL3(...) CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(CATCH_RECURSION_LEVEL2(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL4(...) CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(CATCH_RECURSION_LEVEL3(__VA_ARGS__))) -#define CATCH_RECURSION_LEVEL5(...) CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(CATCH_RECURSION_LEVEL4(__VA_ARGS__))) - -#ifdef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_EXPAND_VARGS(...) __VA_ARGS__ -// MSVC needs more evaluations -#define CATCH_RECURSION_LEVEL6(...) CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(CATCH_RECURSION_LEVEL5(__VA_ARGS__))) -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL6(CATCH_RECURSION_LEVEL6(__VA_ARGS__)) -#else -#define CATCH_RECURSE(...) CATCH_RECURSION_LEVEL5(__VA_ARGS__) -#endif - -#define CATCH_REC_END(...) -#define CATCH_REC_OUT - -#define CATCH_EMPTY() -#define CATCH_DEFER(id) id CATCH_EMPTY() - -#define CATCH_REC_GET_END2() 0, CATCH_REC_END -#define CATCH_REC_GET_END1(...) CATCH_REC_GET_END2 -#define CATCH_REC_GET_END(...) CATCH_REC_GET_END1 -#define CATCH_REC_NEXT0(test, next, ...) next CATCH_REC_OUT -#define CATCH_REC_NEXT1(test, next) CATCH_DEFER ( CATCH_REC_NEXT0 ) ( test, next, 0) -#define CATCH_REC_NEXT(test, next) CATCH_REC_NEXT1(CATCH_REC_GET_END test, next) - -#define CATCH_REC_LIST0(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1(f, x, peek, ...) , f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0) ) ( f, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2(f, x, peek, ...) f(x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1) ) ( f, peek, __VA_ARGS__ ) - -#define CATCH_REC_LIST0_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST1_UD(f, userdata, x, peek, ...) , f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST0_UD) ) ( f, userdata, peek, __VA_ARGS__ ) -#define CATCH_REC_LIST2_UD(f, userdata, x, peek, ...) f(userdata, x) CATCH_DEFER ( CATCH_REC_NEXT(peek, CATCH_REC_LIST1_UD) ) ( f, userdata, peek, __VA_ARGS__ ) - -// Applies the function macro `f` to each of the remaining parameters, inserts commas between the results, -// and passes userdata as the first parameter to each invocation, -// e.g. CATCH_REC_LIST_UD(f, x, a, b, c) evaluates to f(x, a), f(x, b), f(x, c) -#define CATCH_REC_LIST_UD(f, userdata, ...) CATCH_RECURSE(CATCH_REC_LIST2_UD(f, userdata, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define CATCH_REC_LIST(f, ...) CATCH_RECURSE(CATCH_REC_LIST2(f, __VA_ARGS__, ()()(), ()()(), ()()(), 0)) - -#define INTERNAL_CATCH_EXPAND1(param) INTERNAL_CATCH_EXPAND2(param) -#define INTERNAL_CATCH_EXPAND2(...) INTERNAL_CATCH_NO## __VA_ARGS__ -#define INTERNAL_CATCH_DEF(...) INTERNAL_CATCH_DEF __VA_ARGS__ -#define INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE(...) INTERNAL_CATCH_STRINGIZE2(__VA_ARGS__) -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_STRINGIZE2(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) -#else -// MSVC is adding extra space and needs another indirection to expand INTERNAL_CATCH_NOINTERNAL_CATCH_DEF -#define INTERNAL_CATCH_STRINGIZE2(...) INTERNAL_CATCH_STRINGIZE3(__VA_ARGS__) -#define INTERNAL_CATCH_STRINGIZE3(...) #__VA_ARGS__ -#define INTERNAL_CATCH_STRINGIZE_WITHOUT_PARENS(param) (INTERNAL_CATCH_STRINGIZE(INTERNAL_CATCH_REMOVE_PARENS(param)) + 1) -#endif - -#define INTERNAL_CATCH_MAKE_NAMESPACE2(...) ns_##__VA_ARGS__ -#define INTERNAL_CATCH_MAKE_NAMESPACE(name) INTERNAL_CATCH_MAKE_NAMESPACE2(name) - -#define INTERNAL_CATCH_REMOVE_PARENS(...) INTERNAL_CATCH_EXPAND1(INTERNAL_CATCH_DEF __VA_ARGS__) - -#ifndef CATCH_CONFIG_TRADITIONAL_MSVC_PREPROCESSOR -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) decltype(get_wrapper()) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__)) -#else -#define INTERNAL_CATCH_MAKE_TYPE_LIST2(...) INTERNAL_CATCH_EXPAND_VARGS(decltype(get_wrapper())) -#define INTERNAL_CATCH_MAKE_TYPE_LIST(...) INTERNAL_CATCH_EXPAND_VARGS(INTERNAL_CATCH_MAKE_TYPE_LIST2(INTERNAL_CATCH_REMOVE_PARENS(__VA_ARGS__))) -#endif - -#define INTERNAL_CATCH_MAKE_TYPE_LISTS_FROM_TYPES(...)\ - CATCH_REC_LIST(INTERNAL_CATCH_MAKE_TYPE_LIST,__VA_ARGS__) - -#define INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_0) INTERNAL_CATCH_REMOVE_PARENS(_0) -#define INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_0, _1) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_1_ARG(_1) -#define INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_0, _1, _2) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_2_ARG(_1, _2) -#define INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_0, _1, _2, _3) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_3_ARG(_1, _2, _3) -#define INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_0, _1, _2, _3, _4) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_4_ARG(_1, _2, _3, _4) -#define INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_0, _1, _2, _3, _4, _5) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_5_ARG(_1, _2, _3, _4, _5) -#define INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_0, _1, _2, _3, _4, _5, _6) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_6_ARG(_1, _2, _3, _4, _5, _6) -#define INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_0, _1, _2, _3, _4, _5, _6, _7) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_7_ARG(_1, _2, _3, _4, _5, _6, _7) -#define INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_8_ARG(_1, _2, _3, _4, _5, _6, _7, _8) -#define INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_9_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9) -#define INTERNAL_CATCH_REMOVE_PARENS_11_ARG(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10) INTERNAL_CATCH_REMOVE_PARENS(_0), INTERNAL_CATCH_REMOVE_PARENS_10_ARG(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10) - -#define INTERNAL_CATCH_VA_NARGS_IMPL(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, _10, N, ...) N - -#define INTERNAL_CATCH_TYPE_GEN\ - template struct TypeList {};\ - template\ - constexpr auto get_wrapper() noexcept -> TypeList { return {}; }\ - template class...> struct TemplateTypeList{};\ - template class...Cs>\ - constexpr auto get_wrapper() noexcept -> TemplateTypeList { return {}; }\ - template\ - struct append;\ - template\ - struct rewrap;\ - template class, typename...>\ - struct create;\ - template class, typename>\ - struct convert;\ - \ - template \ - struct append { using type = T; };\ - template< template class L1, typename...E1, template class L2, typename...E2, typename...Rest>\ - struct append, L2, Rest...> { using type = typename append, Rest...>::type; };\ - template< template class L1, typename...E1, typename...Rest>\ - struct append, TypeList, Rest...> { using type = L1; };\ - \ - template< template class Container, template class List, typename...elems>\ - struct rewrap, List> { using type = TypeList>; };\ - template< template class Container, template class List, class...Elems, typename...Elements>\ - struct rewrap, List, Elements...> { using type = typename append>, typename rewrap, Elements...>::type>::type; };\ - \ - template