From fb00300f30217a857aacee4120f5bdd9cb44eef1 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:58:50 +0000 Subject: [PATCH 1/5] revert: drop symlinks in immer subtree reverts: - c09981d8addef057d4504ff20745f96b11ae99d5. --- src/immer/doc/guile.rst | 1 + src/immer/doc/python.rst | 1 + 2 files changed, 2 insertions(+) create mode 120000 src/immer/doc/guile.rst create mode 120000 src/immer/doc/python.rst diff --git a/src/immer/doc/guile.rst b/src/immer/doc/guile.rst new file mode 120000 index 0000000000..dfda6df878 --- /dev/null +++ b/src/immer/doc/guile.rst @@ -0,0 +1 @@ +../extra/guile/README.rst \ No newline at end of file diff --git a/src/immer/doc/python.rst b/src/immer/doc/python.rst new file mode 120000 index 0000000000..4266420dfb --- /dev/null +++ b/src/immer/doc/python.rst @@ -0,0 +1 @@ +../extra/python/README.rst \ No newline at end of file From e4ee302d6e971bb6c5d733eb91e6776e39c8fc10 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Fri, 1 Nov 2024 15:59:53 +0000 Subject: [PATCH 2/5] revert: fix gitian builds reverts: - 47b32efef240b47c53ff00a851bd4cc4ee8d343b. --- src/immer/.gitmodules | 6 ++++++ src/immer/extra/python/lib/pybind11 | 1 + src/immer/tools/sinusoidal-sphinx-theme | 1 + 3 files changed, 8 insertions(+) create mode 160000 src/immer/extra/python/lib/pybind11 create mode 160000 src/immer/tools/sinusoidal-sphinx-theme diff --git a/src/immer/.gitmodules b/src/immer/.gitmodules index e69de29bb2..53d1686014 100644 --- a/src/immer/.gitmodules +++ b/src/immer/.gitmodules @@ -0,0 +1,6 @@ +[submodule "extra/python/lib/pybind11"] + path = extra/python/lib/pybind11 + url = https://github.com/pybind/pybind11.git +[submodule "tools/sinusoidal-sphinx-theme"] + path = tools/sinusoidal-sphinx-theme + url = https://github.com/arximboldi/sinusoidal-sphinx-theme.git diff --git a/src/immer/extra/python/lib/pybind11 b/src/immer/extra/python/lib/pybind11 new file mode 160000 index 0000000000..1eaacd19f6 --- /dev/null +++ b/src/immer/extra/python/lib/pybind11 @@ -0,0 +1 @@ +Subproject commit 1eaacd19f6de9a053570c21de6d173efc2304bc2 diff --git a/src/immer/tools/sinusoidal-sphinx-theme b/src/immer/tools/sinusoidal-sphinx-theme new file mode 160000 index 0000000000..b5adfa2a6d --- /dev/null +++ b/src/immer/tools/sinusoidal-sphinx-theme @@ -0,0 +1 @@ +Subproject commit b5adfa2a6def8aa55d95dedc4e1bfde214a5e36c From a9f46b32c36f9e048248f2653f90b5d028965466 Mon Sep 17 00:00:00 2001 From: Kittywhiskers Van Gogh <63189531+kwvg@users.noreply.github.com> Date: Sat, 2 Nov 2024 17:50:50 +0000 Subject: [PATCH 3/5] Squashed 'src/immer/' changes from 9cb6a5a845..5875f7739a 5875f7739a Merge pull request #269 from pinotree/cmake-current-dir e0c4b1793d cmake: use ${CMAKE_CURRENT_SOURCE_DIR} where possible d0b79d5cbd Merge pull request #268 from pinotree/drop-findguile 5612ec803d cmake: drop FindGuile.cmake b60403ca83 Merge pull request #267 from pinotree/system-catch2 eec85078f2 Drop unused copy of Catch2 ac49b71e42 Switch to system Catch2 615ed31635 nix: disable tests & examples b70f1c27f1 Merge pull request #266 from pinotree/drop-doctest b133b774d4 Drop the unused doctest copy 168768c5de tests: port dvektor to catch2 8872e233c3 Merge pull request #257 from vector-of-bool/cxx20-ranges-fixes 0b3aaf699b Merge pull request #263 from colugomusic/fix-msvc-explicit-destructor-call 10cd1f3312 Merge pull request #262 from colugomusic/fix-msvc-c2975 daff65041c Trying to satisfy LLVM. 88076f7329 Remove unneeded include 4585522553 Merge branch 'fix-msvc-explicit-destructor-call' of https://github.com/colugomusic/immer into fix-msvc-explicit-destructor-call 5516624d67 Manual destroy_at for C++14 205180bb13 Add missing #include required by gcc 77c2f422fe Fix whitespace 6672e57e26 Remove unused capture default 30fb619ce7 Use destroy_at to call explicit destructor in make_inner_n 813e891e25 Fix MSVC compile error C2975 21cc60ef11 Merge pull request #261 from colugomusic/fix-msvc-c2059 5125b83afb Fix MSVC compile error C2059 9549696683 Merge pull request #260 from arximboldi/fmt-fixes 4871c7455a Merge pull request #256 from dhly-etc/robust-loading 8a165e7bd2 Upgrade Nix in Actions 1b66f4ad2a Fix includes 4513c2f80d Upgrade install-nix-action 5f96a39d0d Merge pull request #259 from tocic/fix/dark_theme_logo 04c7d90fc4 Use alternative logo in dark mode ae8abe0ce0 Return a direct reference from iterator operator[] 5722567343 Merge pull request #258 from bobkocisko/master e382feab06 Fixing annoying unused local variable warning ffb051721b Fixes and tests for C++20 Range compatibility a983d6fe1a Make pretty-printer loading more robust d98a68cd6c Merge pull request #255 from dhly-etc/gdb-pretty-printers 7ff66e0934 Add .gdbinit file to autoload pretty printers 9b8e1b340e Add array printer 0220ba4389 Add table printer 17915a5881 Add GDB pretty-printing support adbb4cd311 Merge pull request #253 from tocic/fix/typos c69ad466a1 Fix typos 9dad616455 Merge pull request #250 from LilyWangLL/AddConfigVersion 3630ec4080 Add quotes 54ba35145b Add export CMake Config version file 3c527e434b Merge pull request #246 from maierlars/feature/nothrow-move-constructible-flex-vector 119f9d858a Merge pull request #247 from urbanwe/urbanwe-patch-1 ec81042a26 Fix wrong order in operator<(T2, box) b7756f5e85 Use lambda for initialization. 9c67e60b44 Fixing assertions. 38edbd8be4 Attempt to make flex_vector nothrow move constructible/assignable. a5991efbe4 Merge pull request #243 from arximboldi/memory-usage ffef2712f3 Lower constants for memory usage so CI does not timeout 20eb876b80 Add benchmarks for checking CHAMP memory usage 58036b4eae Merge pull request #239 from arximboldi/release-v0.8 a814bcd592 Do not install specific old version of Nix 6b05223054 Upgrade install-nix-action ad5acab104 Upgrade cachix action de91bdb28e Prepare release 0.8.0 459854dc80 Merge pull request #238 from arximboldi/add-identity-api 2c8ceb6160 Add identity API ca78d685ad Merge pull request #236 from DavidKorczynski/cifuzz-int e870de6c0b Add CIFuzz GitHub action 4100eff397 Merge pull request #235 from arximboldi/more-fuzz-conflict 6e8a4f8f83 Add fuzzers that produce more conflicts 53b1e37e38 Merge pull request #233 from asa/make_gcc_pragma_ignore_clang 75cc8984e2 make clang ignore gcc pragma warning for champ 93ab7151cd Merge pull request #231 from arximboldi/fuzz-move d5b6bf6b45 Strengthen the fuzzers even further 35c94037d4 Fix issues in map/set erase() found by fuzzer b43a1ca2ac Fix fuzzer was not doing what we thought it did 048891443c Add set fuzzer that can find if the tree is corrupted by moves 8f105e275c Add map fuzzer that can find if the tree is corrupted by moves 54f0d543dc Merge pull request #230 from Kraks/patch-1 8acee494ba Fix typo in comment 006461c8ec Merge pull request #227 from arximboldi/fix-debug-stats 0e07630e22 Fix debug stats d98e82d66a Merge pull request #226 from arximboldi/fix-dec-unsafe ad759c4783 Remove dec_unsafe() from the policies 4a8fd59e35 Remove dec_unsafe() usage in HAMT data-structures a7b0e23771 Remove usage of dec_unsafe() in RBTS data-structures e02cbd795e Merge pull request #225 from arximboldi/fix-champ-move db3ea447ad Fix sometimes HAMTs would move out values that it should not! 34deab8781 Fix iteration over data-structures would sometimes allow mutation! e5d79ed80e Merge pull request #222 from arximboldi/more-meta-improvements 6b57f13664 Add update_if_exists method to map and table and related transients f3cebc63a1 Fix table and table_transient update() bd1f0c00c0 Improve debug stats API ad73ea14bf Merge pull request #221 from arximboldi/champ-stats 893c08461a Add tools for printing statistics about trees 037b34afe3 Fix memory leak 79ebe07f31 Merge pull request #220 from arximboldi/map-leak c4be744a49 Try something c37693ac4a Remove vestige 1c780ba19b Fix memory leak 30ce53b4df Add test for memory leak found by oss-fuzz 2a32d5db4d Add update_move operation to map fuzzer d22262967a Merge pull request #216 from arximboldi/hamt-transients-faster-rc-sub 1897252813 Remove vestige f0defde7dd Ensure that champ update operations move the payload in the value 175c05fb3a Fix #219, ambiguous calls to destroy_n c25a272ac7 Move objects whenever possible during champ erasure operations 95889a7aa2 Merge pull request #218 from cyrilRomain/master 898f7d8e82 Fix -fno-exceptions build (regression from 35147686) 1bbfb46bf3 Merge pull request #217 from arximboldi/hamt-transients-faster-rc 9caf11101a Fix ambiguities when compiling in C++17 mode 27132d5183 Mark a bunch of functions as noexcept d004c6e4bf Fix implementation of unordered_copy e3ddbb7a98 Optimize our own implementation of uninitialized_copy 778ad6718f Use std::copy when possible, our own uninitialized_copy elsewhere 265d79a5ba Our own implementations for uninitialied_... are better, use always 54ff4a21a7 Improve implementation of std shims 3514768617 Use standard library implementations of shims whenever possible 98d4f983cb Move values when node can be mutated but needs realloc (champ::update_mut) a89f7c98c6 Move values when node can be mutated but needs realloc (champ::add_mut) 5e9fe0b347 Fix table documentation header 72be85b931 Merge pull request #214 from arximboldi/table-fixes 1f41bbf0ac Improve sequential initialization of HAMTs 4736697d3b Update the documentation of HAMT-based transients efc91fe724 Make immer::table and table_transient use mutation when possible 00f9e49882 Add missing link table_transient in the documentation b8dbc6ab96 Merge pull request #213 from arximboldi/sub-transients f5ebe48b13 Add benchmarks for HAMT erasure 9e62f8ccd5 Ensure that edit markers are set properly during transient updates 175edad908 Sketch mutable removal from HAMTs 815541445d Merge pull request #210 from deadb0d4/single_hash_index 6cc2b39159 Merge pull request #212 from arximboldi/more-transients 9985fc131e review: add to docs/container & fix update docs 002f4e6ba5 Fix mutable HAMT insertion with conflicts dd8a3ac8c5 Test more exception safety of mutable operations on vectors 0f72f261aa Style: clang-format 416f4c71c4 Implement transients for map update() 8f0b8a043f Merge branch 'master' into single_hash_index 3a38bd7662 add docs & run clang-format dbc96d6fc4 add test stub for table_transient db88f93ed5 cosmetics + table tests 70a3620c4e Merge pull request #211 from arximboldi/fix-map-leak 54037e1aaf Fix sanitizers were not properly enabled in CI cf20d291e5 Fix memory leak when mutating inner nodes 88cf946cfc Merge pull request #209 from arximboldi/map-add-transience cb76e80313 add headers + examples 8f01c1babe cosmetic fixes 982aa1f6dc Fix insertion benchmarks 5f42f9069d Mutate collision nodes in-place when possible 7fd0ec4d8d Mutate champ nodes values in place when possible 0c0d313c40 Fix potential destruction of uninitialized values 91d065c9bf Reuse same-sized inner nodes during HAMT insertions 3ea3ae9c45 Sketch implementation interface for transient insertion e3744032a8 Merge pull request #208 from arximboldi/map-transients 22323150fb Add move and transient versions for the HAMT insertion benchmarks a56e6f8007 Style: clang-format a9a672bfb0 Add naive implementation of map and set transients ca255452c3 Add move-based updates on immutable maps and sets eb5056c4cb Add map and set range constructors 440f7519b2 Sketch map and set transient types 6b27170212 Fix compiling some tests would crash b38c67b3a1 Merge pull request #206 from audulus/spm b3139b4055 Immer is compatible with C++14 380dc52dfc Add spm job 9370c89597 Work on SPM support. a1271fa712 Merge pull request #202 from philipcraig/patch-1 9af8724378 Update README.rst 9c7bb910a2 Update README.rst 88887fd9a5 Update README.rst 9da861dea5 Update memory.rst fa61a3ed9d Update atom.hpp d7dd8a729f Update transients.rst 3ceb4be6a1 Update map.hpp e1d29d0dad Update vector.hpp 3d12e12cbb Update array.hpp 749035b5c9 Update array.hpp 1f9910eae1 fix typo d2648aceea Merge pull request #201 from mvtec-richter/patch-1 70d6b6eb16 Fix typo in documentation of diff 0fcf66743e Tweak diff docs ef80e6c5f7 Merge pull request #200 from arximboldi/diff-polish 7ba2196e4c Add missing header 36178a7aa6 Add the diffing algorithm to the fuzzers af99b84519 Document diff algorithm 25b20e77c1 Simplify diff algorithm interface 3db544b73a Avoid passing Differ template param, use universal references d372683b7a Merge pull request #199 from mvtec-richter/add-diff-method-to-map f23dc6cdf7 Remove incorrect usage of std::forward a4482d56e0 Introduce struct differ f91922ec95 Add diff to set and avoid std::pair in champ 0f8eae238c Add diffing algorithm to maps 42e6beafed Merge pull request #197 from bollu/master 926098ccc8 Typo fix: frony -> front 24214158f5 Merge pull request #196 from arximboldi/nix-build-issues 9b770ec715 Do not try to use a build dir, because we have a BUILD file 98a8f1b7a5 Do not rely on git to fetch the code locally 2f12be4d65 Allow changing nixpkgs version d6d27de64a Merge pull request #193 from arximboldi/fix-github-actions 6b299fc0a5 Upgrade Nix related actions a3617d5f3b Disable ssh stuff when it's not me 5adcd67e55 Ignore Nix result links 36d1000deb Fix comment git-subtree-dir: src/immer git-subtree-split: 5875f7739a6c642ad58cbedadb509c86d421217e --- .gdbinit | 2 + .github/workflows/cifuzz.yml | 26 + .github/workflows/test.yml | 35 +- .gitignore | 5 + CMakeLists.txt | 12 +- Package.swift | 29 + README.rst | 9 +- benchmark/set/erase.hpp | 85 + benchmark/set/erase.ipp | 44 + benchmark/set/insert.hpp | 27 +- benchmark/set/insert.ipp | 16 +- benchmark/set/memory/basic-string-long.cpp | 5 + benchmark/set/memory/basic-string-short.cpp | 5 + benchmark/set/memory/basic-unsigned.cpp | 5 + benchmark/set/memory/exp-string-long.cpp | 5 + benchmark/set/memory/exp-string-short.cpp | 5 + benchmark/set/memory/exp-unsigned.cpp | 5 + benchmark/set/memory/lin-string-long.cpp | 5 + benchmark/set/memory/lin-string-short.cpp | 5 + benchmark/set/memory/lin-unsigned.cpp | 5 + benchmark/set/memory/memory.hpp | 451 + benchmark/set/string-box/erase.cpp | 11 + benchmark/set/string-long/erase.cpp | 11 + benchmark/set/string-short/erase.cpp | 11 + benchmark/set/unsigned/erase.cpp | 11 + cmake/FindGuile.cmake | 326 - default.nix | 26 +- doc/CMakeLists.txt | 6 +- doc/containers.rst | 7 + doc/design.rst | 14 +- doc/memory.rst | 12 +- doc/transients.rst | 11 +- example/table/intro.cpp | 29 + extra/fuzzer/fuzzer_input.hpp | 4 + extra/fuzzer/map-gc.cpp | 45 +- extra/fuzzer/map-st-str-conflict.cpp | 136 + extra/fuzzer/map-st-str.cpp | 135 + extra/fuzzer/map-st.cpp | 31 +- extra/fuzzer/map.cpp | 45 +- extra/fuzzer/set-gc.cpp | 19 +- extra/fuzzer/set-st-str-conflict.cpp | 108 + extra/fuzzer/set-st-str.cpp | 108 + extra/fuzzer/set-st.cpp | 23 +- extra/fuzzer/set.cpp | 20 +- extra/guile/README.rst | 4 +- extra/python/README.rst | 4 +- immer/algorithm.hpp | 120 +- immer/array.hpp | 17 +- immer/atom.hpp | 4 +- immer/box.hpp | 4 +- immer/config.hpp | 12 + immer/detail/arrays/node.hpp | 4 +- immer/detail/arrays/with_capacity.hpp | 19 +- immer/detail/hamts/bits.hpp | 45 + immer/detail/hamts/champ.hpp | 1127 +- immer/detail/hamts/champ_iterator.hpp | 2 + immer/detail/hamts/node.hpp | 490 +- immer/detail/iterator_facade.hpp | 17 +- immer/detail/rbts/node.hpp | 115 +- immer/detail/rbts/operations.hpp | 70 +- immer/detail/rbts/rbtree.hpp | 4 +- immer/detail/rbts/rrbtree.hpp | 54 +- immer/detail/type_traits.hpp | 18 - immer/detail/util.hpp | 168 +- immer/flex_vector.hpp | 18 +- immer/heap/cpp_heap.hpp | 2 +- immer/heap/heap_policy.hpp | 3 +- immer/map.hpp | 177 +- immer/map_transient.hpp | 297 +- immer/refcount/no_refcount_policy.hpp | 1 - immer/refcount/refcount_policy.hpp | 6 - immer/refcount/unsafe_refcount_policy.hpp | 1 - immer/set.hpp | 76 +- immer/set_transient.hpp | 164 +- immer/table.hpp | 547 + immer/table_transient.hpp | 281 + immer/vector.hpp | 13 +- shell.nix | 2 + spm.cpp | 4 + test/CMakeLists.txt | 3 +- test/algorithm.cpp | 221 + test/array_transient/default.cpp | 4 + test/atom/generic.ipp | 2 +- test/box/generic.ipp | 2 +- test/box/recursive.cpp | 2 +- test/box/vector-of-boxes-transient.cpp | 2 +- test/detail/type_traits.cpp | 2 +- test/experimental/dvektor.cpp | 32 +- test/flex_vector/fuzzed-0.cpp | 4 +- test/flex_vector/fuzzed-1.cpp | 7 +- test/flex_vector/fuzzed-2.cpp | 4 +- test/flex_vector/fuzzed-3.cpp | 4 +- test/flex_vector/fuzzed-4.cpp | 10 +- test/flex_vector/generic.ipp | 17 +- test/flex_vector/issue-45.cpp | 2 +- test/flex_vector_transient/generic.ipp | 7 +- test/map/default.cpp | 2 + test/map/gc.cpp | 1 + test/map/generic.ipp | 443 +- test/map/issue-56.cpp | 2 +- test/map_transient/B3.cpp | 28 + test/map_transient/B6.cpp | 28 + test/map_transient/default.cpp | 15 + test/map_transient/gc.cpp | 36 + test/map_transient/generic.ipp | 116 + test/memory/heaps.cpp | 2 +- test/memory/refcounts.cpp | 12 +- test/oss-fuzz/array-0.cpp | 2 +- test/oss-fuzz/array-gc-0.cpp | 2 +- ...zz-testcase-minimized-map-6457979420934144 | Bin 0 -> 475 bytes ...testcase-minimized-map-st-5313188008165376 | Bin 0 -> 1025 bytes ...testcase-minimized-map-st-6242663155761152 | Bin 0 -> 48 bytes ...testcase-minimized-set-st-4717454829420544 | 1 + ...h-2838943da19b47c02dcff313e523eead0e2e8635 | Bin 0 -> 114 bytes ...h-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b | Bin 0 -> 667 bytes test/oss-fuzz/flex-vector-0.cpp | 4 +- test/oss-fuzz/flex-vector-bo-0.cpp | 4 +- test/oss-fuzz/flex-vector-gc-0.cpp | 4 +- test/oss-fuzz/map-gc-0.cpp | 2 +- test/oss-fuzz/map-st-0.cpp | 12 +- test/oss-fuzz/map-st-1.cpp | 134 + test/oss-fuzz/map-st-2.cpp | 201 + test/oss-fuzz/map-st-str-0.cpp | 218 + test/oss-fuzz/set-gc-0.cpp | 2 +- test/oss-fuzz/set-gc-1.cpp | 4 +- test/oss-fuzz/set-st-0.cpp | 118 + test/oss-fuzz/set-st-str-0.cpp | 122 + test/set/gc.cpp | 2 + test/set/generic.ipp | 299 +- test/set_transient/B3.cpp | 26 + test/set_transient/B6.cpp | 26 + test/set_transient/default.cpp | 15 + test/set_transient/gc.cpp | 33 + test/set_transient/generic.ipp | 104 + test/table/B3.cpp | 20 + test/table/B6.cpp | 20 + test/table/default.cpp | 20 + test/table/gc.cpp | 18 + test/table/generic.ipp | 570 + test/table_transient/B3.cpp | 34 + test/table_transient/B6.cpp | 34 + test/table_transient/default.cpp | 34 + test/table_transient/gc.cpp | 42 + test/table_transient/generic.ipp | 103 + test/util.hpp | 12 + test/vector/generic.ipp | 75 +- test/vector/issue-177.cpp | 2 +- test/vector/issue-46.cpp | 2 +- test/vector_transient/generic.ipp | 4 +- tools/gdb_pretty_printers/__init__.py | 1 + tools/gdb_pretty_printers/autoload.py | 14 + tools/gdb_pretty_printers/printers.py | 485 + tools/include/catch.hpp | 17959 ---------------- tools/include/doctest.h | 5696 ----- 154 files changed, 8823 insertions(+), 24492 deletions(-) create mode 100644 .gdbinit create mode 100644 .github/workflows/cifuzz.yml create mode 100644 Package.swift create mode 100644 benchmark/set/erase.hpp create mode 100644 benchmark/set/erase.ipp create mode 100644 benchmark/set/memory/basic-string-long.cpp create mode 100644 benchmark/set/memory/basic-string-short.cpp create mode 100644 benchmark/set/memory/basic-unsigned.cpp create mode 100644 benchmark/set/memory/exp-string-long.cpp create mode 100644 benchmark/set/memory/exp-string-short.cpp create mode 100644 benchmark/set/memory/exp-unsigned.cpp create mode 100644 benchmark/set/memory/lin-string-long.cpp create mode 100644 benchmark/set/memory/lin-string-short.cpp create mode 100644 benchmark/set/memory/lin-unsigned.cpp create mode 100644 benchmark/set/memory/memory.hpp create mode 100644 benchmark/set/string-box/erase.cpp create mode 100644 benchmark/set/string-long/erase.cpp create mode 100644 benchmark/set/string-short/erase.cpp create mode 100644 benchmark/set/unsigned/erase.cpp delete mode 100644 cmake/FindGuile.cmake create mode 100644 example/table/intro.cpp create mode 100644 extra/fuzzer/map-st-str-conflict.cpp create mode 100644 extra/fuzzer/map-st-str.cpp create mode 100644 extra/fuzzer/set-st-str-conflict.cpp create mode 100644 extra/fuzzer/set-st-str.cpp create mode 100644 immer/table.hpp create mode 100644 immer/table_transient.hpp create mode 100644 spm.cpp create mode 100644 test/algorithm.cpp create mode 100644 test/map_transient/B3.cpp create mode 100644 test/map_transient/B6.cpp create mode 100644 test/map_transient/default.cpp create mode 100644 test/map_transient/gc.cpp create mode 100644 test/map_transient/generic.ipp create mode 100644 test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-6457979420934144 create mode 100644 test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-5313188008165376 create mode 100644 test/oss-fuzz/data/clusterfuzz-testcase-minimized-map-st-6242663155761152 create mode 100644 test/oss-fuzz/data/clusterfuzz-testcase-minimized-set-st-4717454829420544 create mode 100644 test/oss-fuzz/data/crash-2838943da19b47c02dcff313e523eead0e2e8635 create mode 100644 test/oss-fuzz/data/crash-dc9dad6beae69a6bb8ffd6d203b95032f445ec9b create mode 100644 test/oss-fuzz/map-st-1.cpp create mode 100644 test/oss-fuzz/map-st-2.cpp create mode 100644 test/oss-fuzz/map-st-str-0.cpp create mode 100644 test/oss-fuzz/set-st-0.cpp create mode 100644 test/oss-fuzz/set-st-str-0.cpp create mode 100644 test/set_transient/B3.cpp create mode 100644 test/set_transient/B6.cpp create mode 100644 test/set_transient/default.cpp create mode 100644 test/set_transient/gc.cpp create mode 100644 test/set_transient/generic.ipp create mode 100644 test/table/B3.cpp create mode 100644 test/table/B6.cpp create mode 100644 test/table/default.cpp create mode 100644 test/table/gc.cpp create mode 100644 test/table/generic.ipp create mode 100644 test/table_transient/B3.cpp create mode 100644 test/table_transient/B6.cpp create mode 100644 test/table_transient/default.cpp create mode 100644 test/table_transient/gc.cpp create mode 100644 test/table_transient/generic.ipp create mode 100644 tools/gdb_pretty_printers/__init__.py create mode 100644 tools/gdb_pretty_printers/autoload.py create mode 100644 tools/gdb_pretty_printers/printers.py delete mode 100644 tools/include/catch.hpp delete mode 100644 tools/include/doctest.h 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 0000000000000000000000000000000000000000..3165e60d1b61b3a8797d2cbbb849b753cb0b8a7e GIT binary patch literal 475 zcmY%M7h+&#V4BUu#N=qi03?~17}yvXSQr=>m>5Ac3dsZ(0m>~L z82bs+kO{*-Z6JG){j&^a x1M&Vz#pfRepnn+P{sFlc>>fmT{ztVR9;(FK4>S;Wc!F#LdkN@!sO3QK0su!47WDuC literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..290b5da078ea24dc8802aefe453d01d34e118cd5 GIT binary patch literal 1025 zcmcIhyH3ME5FGD0c4Co+f}Ri>DyY+hf)*hmLBS7%o{9>IFM$u>GvFr>?}WF)(~3mG zF+N}7Q|urbR-W7Q&hB`39aOOmV_GlhMGa3Rk{S@eM?)g|y74L)w?l*~SHRsqiGFNjOxintaJH}nBe-Gt}ynwlwf z-se&Ol~40oD^_3)t1+;S1BlFyg41G6iIA`s4lnd=B3d#cZ0gXeophB?>Csk_| zId+y)!iEyQ?9i%!cj;n2%u*)S&kS2qMq@WC$@jRD>oLoAW*YmJ$r+2~we$14+QsuZ d8ULd{u}u>0F1W+QJReCP=B}H@*<->F{{cT+TXz5e literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..3cdc39d005888bd3cff01893b3ecb13256d41f9d GIT binary patch literal 48 acmY#T00IUiz{m&^fdfVb24)ZkrUC#-{GUEUY%mNjcfB*jnGy$v*Y9g4D z0}C #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