diff --git a/.clang-format b/.clang-format index 5c5555ea..28884fb5 100644 --- a/.clang-format +++ b/.clang-format @@ -34,7 +34,7 @@ BraceWrapping: AfterClass: false AfterControlStatement: Never AfterEnum: false - AfterFunction: false + AfterFunction: true AfterNamespace: false AfterObjCDeclaration: false AfterStruct: false @@ -62,7 +62,7 @@ ColumnLimit: 0 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerAllOnOneLineOrOnePerLine: true -ConstructorInitializerIndentWidth: 8 +ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 3bf31637..9f9b9195 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,10 +1,18 @@ cmake_minimum_required(VERSION 3.12 FATAL_ERROR) include(GNUInstallDirs) -project(mcl LANGUAGES CXX VERSION 0.1.7) +project(mcl LANGUAGES CXX VERSION 0.1.11) + +# Determine if we're built as a subproject (using add_subdirectory) +# or if this is the master project. +set(MASTER_PROJECT OFF) +if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) + set(MASTER_PROJECT ON) +endif() # Project options -option(MCL_WARNINGS_AS_ERRORS "Warnings as errors" ON) +option(MCL_WARNINGS_AS_ERRORS "Warnings as errors" ${MASTER_PROJECT}) +option(MCL_INSTALL "Enable installation" ${MASTER_PROJECT}) # Default to a Release build if (NOT CMAKE_BUILD_TYPE) @@ -85,7 +93,7 @@ endif() # Dependencies if (NOT TARGET Catch2::Catch2) - find_package(Catch2 QUIET) + find_package(Catch2 3 QUIET) endif() if (NOT TARGET fmt::fmt) @@ -95,32 +103,33 @@ endif() # Project files add_subdirectory(src) -if (TARGET Catch2::Catch2) +if (TARGET Catch2::Catch2 AND MASTER_PROJECT) add_subdirectory(tests) endif() # Install instructions +if (MCL_INSTALL) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) -include(GNUInstallDirs) -include(CMakePackageConfigHelpers) + install(TARGETS mcl EXPORT mclTargets) + install(EXPORT mclTargets + NAMESPACE merry:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl" + ) -install(TARGETS mcl EXPORT mclTargets) -install(EXPORT mclTargets - NAMESPACE merry:: - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl" -) + configure_package_config_file(CMakeModules/mclConfig.cmake.in + mclConfig.cmake + INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl" + ) + write_basic_package_version_file(mclConfigVersion.cmake + COMPATIBILITY SameMajorVersion + ) + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/mclConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/mclConfigVersion.cmake" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl" + ) -configure_package_config_file(CMakeModules/mclConfig.cmake.in - mclConfig.cmake - INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl" -) -write_basic_package_version_file(mclConfigVersion.cmake - COMPATIBILITY SameMajorVersion -) -install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/mclConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/mclConfigVersion.cmake" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl" -) - -install(DIRECTORY include/ TYPE INCLUDE FILES_MATCHING PATTERN "*.hpp") + install(DIRECTORY include/ TYPE INCLUDE FILES_MATCHING PATTERN "*.hpp") +endif() diff --git a/include/mcl/assert.hpp b/include/mcl/assert.hpp index 3b9377a7..f77dbe71 100644 --- a/include/mcl/assert.hpp +++ b/include/mcl/assert.hpp @@ -13,11 +13,12 @@ namespace mcl::detail { -[[noreturn]] void assert_terminate_impl(fmt::string_view msg, fmt::format_args args); +[[noreturn]] void assert_terminate_impl(const char* expr_str, fmt::string_view msg, fmt::format_args args); template -[[noreturn]] void assert_terminate(fmt::string_view msg, Ts... args) { - assert_terminate_impl(msg, fmt::make_format_args(args...)); +[[noreturn]] void assert_terminate(const char* expr_str, fmt::string_view msg, Ts... args) +{ + assert_terminate_impl(expr_str, msg, fmt::make_format_args(args...)); } } // namespace mcl::detail @@ -32,25 +33,25 @@ template } \ } else { \ if (!(expr)) [[unlikely]] { \ - ::mcl::detail::assert_terminate(#expr); \ + ::mcl::detail::assert_terminate(#expr, "(none)"); \ } \ } \ }() -#define ASSERT_MSG(expr, ...) \ - [&] { \ - if (std::is_constant_evaluated()) { \ - if (!(expr)) { \ - throw std::logic_error{"ASSERT_MSG failed at compile time"}; \ - } \ - } else { \ - if (!(expr)) [[unlikely]] { \ - ::mcl::detail::assert_terminate(#expr "\nMessage: " __VA_ARGS__); \ - } \ - } \ +#define ASSERT_MSG(expr, ...) \ + [&] { \ + if (std::is_constant_evaluated()) { \ + if (!(expr)) { \ + throw std::logic_error{"ASSERT_MSG failed at compile time"}; \ + } \ + } else { \ + if (!(expr)) [[unlikely]] { \ + ::mcl::detail::assert_terminate(#expr, __VA_ARGS__); \ + } \ + } \ }() -#define ASSERT_FALSE(...) ::mcl::detail::assert_terminate("false\nMessage: " __VA_ARGS__) +#define ASSERT_FALSE(...) ::mcl::detail::assert_terminate("false", __VA_ARGS__) #if defined(NDEBUG) || defined(MCL_IGNORE_ASSERTS) # define DEBUG_ASSERT(expr) ASSUME(expr) diff --git a/include/mcl/bit/bit_count.hpp b/include/mcl/bit/bit_count.hpp index c08abd00..104af9c8 100644 --- a/include/mcl/bit/bit_count.hpp +++ b/include/mcl/bit/bit_count.hpp @@ -13,12 +13,14 @@ namespace mcl::bit { template -inline size_t count_ones(T x) { +inline size_t count_ones(T x) +{ return std::bitset>(x).count(); } template -constexpr size_t count_leading_zeros(T x) { +constexpr size_t count_leading_zeros(T x) +{ size_t result = bitsizeof; while (x != 0) { x >>= 1; @@ -28,7 +30,8 @@ constexpr size_t count_leading_zeros(T x) { } template -constexpr int highest_set_bit(T x) { +constexpr int highest_set_bit(T x) +{ int result = -1; while (x != 0) { x >>= 1; @@ -38,7 +41,8 @@ constexpr int highest_set_bit(T x) { } template -constexpr size_t lowest_set_bit(T x) { +constexpr size_t lowest_set_bit(T x) +{ if (x == 0) { return bitsizeof; } diff --git a/include/mcl/bit/bit_field.hpp b/include/mcl/bit/bit_field.hpp index b967ed4b..91e27498 100644 --- a/include/mcl/bit/bit_field.hpp +++ b/include/mcl/bit/bit_field.hpp @@ -13,7 +13,8 @@ namespace mcl::bit { /// Create a mask with `count` number of one bits. template -constexpr T ones() { +constexpr T ones() +{ static_assert(count <= bitsizeof, "count larger than bitsize of T"); if constexpr (count == 0) { @@ -25,7 +26,8 @@ constexpr T ones() { /// Create a mask with `count` number of one bits. template -constexpr T ones(size_t count) { +constexpr T ones(size_t count) +{ ASSERT_MSG(count <= bitsizeof, "count larger than bitsize of T"); if (count == 0) { @@ -36,7 +38,8 @@ constexpr T ones(size_t count) { /// Create a mask of type T for bits [begin_bit, end_bit] inclusive. template -constexpr T mask() { +constexpr T mask() +{ static_assert(begin_bit <= end_bit, "invalid bit range (position of beginning bit cannot be greater than that of end bit)"); static_assert(begin_bit < bitsizeof, "begin_bit must be smaller than size of T"); static_assert(end_bit < bitsizeof, "end_bit must be smaller than size of T"); @@ -46,7 +49,8 @@ constexpr T mask() { /// Create a mask of type T for bits [begin_bit, end_bit] inclusive. template -constexpr T mask(size_t begin_bit, size_t end_bit) { +constexpr T mask(size_t begin_bit, size_t end_bit) +{ ASSERT_MSG(begin_bit <= end_bit, "invalid bit range (position of beginning bit cannot be greater than that of end bit)"); ASSERT_MSG(begin_bit < bitsizeof, "begin_bit must be smaller than size of T"); ASSERT_MSG(end_bit < bitsizeof, "end_bit must be smaller than size of T"); @@ -56,91 +60,104 @@ constexpr T mask(size_t begin_bit, size_t end_bit) { /// Extract bits [begin_bit, end_bit] inclusive from value of type T. template -constexpr T get_bits(T value) { +constexpr T get_bits(T value) +{ constexpr T m = mask(); return (value & m) >> begin_bit; } /// Extract bits [begin_bit, end_bit] inclusive from value of type T. template -constexpr T get_bits(size_t begin_bit, size_t end_bit, T value) { +constexpr T get_bits(size_t begin_bit, size_t end_bit, T value) +{ const T m = mask(begin_bit, end_bit); return (value & m) >> begin_bit; } /// Clears bits [begin_bit, end_bit] inclusive of value of type T. template -constexpr T clear_bits(T value) { +constexpr T clear_bits(T value) +{ constexpr T m = mask(); return value & ~m; } /// Clears bits [begin_bit, end_bit] inclusive of value of type T. template -constexpr T clear_bits(size_t begin_bit, size_t end_bit, T value) { +constexpr T clear_bits(size_t begin_bit, size_t end_bit, T value) +{ const T m = mask(begin_bit, end_bit); return value & ~m; } /// Modifies bits [begin_bit, end_bit] inclusive of value of type T. template -constexpr T set_bits(T value, T new_bits) { +constexpr T set_bits(T value, T new_bits) +{ constexpr T m = mask(); return (value & ~m) | ((new_bits << begin_bit) & m); } /// Modifies bits [begin_bit, end_bit] inclusive of value of type T. template -constexpr T set_bits(size_t begin_bit, size_t end_bit, T value, T new_bits) { +constexpr T set_bits(size_t begin_bit, size_t end_bit, T value, T new_bits) +{ const T m = mask(begin_bit, end_bit); return (value & ~m) | ((new_bits << begin_bit) & m); } /// Extract bit at bit_position from value of type T. template -constexpr bool get_bit(T value) { +constexpr bool get_bit(T value) +{ constexpr T m = mask(); return (value & m) != 0; } /// Extract bit at bit_position from value of type T. template -constexpr bool get_bit(size_t bit_position, T value) { +constexpr bool get_bit(size_t bit_position, T value) +{ const T m = mask(bit_position, bit_position); return (value & m) != 0; } /// Clears bit at bit_position of value of type T. template -constexpr T clear_bit(T value) { +constexpr T clear_bit(T value) +{ constexpr T m = mask(); return value & ~m; } /// Clears bit at bit_position of value of type T. template -constexpr T clear_bit(size_t bit_position, T value) { +constexpr T clear_bit(size_t bit_position, T value) +{ const T m = mask(bit_position, bit_position); return value & ~m; } /// Modifies bit at bit_position of value of type T. template -constexpr T set_bit(T value, bool new_bit) { +constexpr T set_bit(T value, bool new_bit) +{ constexpr T m = mask(); return (value & ~m) | (new_bit ? m : static_cast(0)); } /// Modifies bit at bit_position of value of type T. template -constexpr T set_bit(size_t bit_position, T value, bool new_bit) { +constexpr T set_bit(size_t bit_position, T value, bool new_bit) +{ const T m = mask(bit_position, bit_position); return (value & ~m) | (new_bit ? m : static_cast(0)); } /// Sign-extends a value that has bit_count bits to the full bitwidth of type T. template -constexpr T sign_extend(T value) { +constexpr T sign_extend(T value) +{ static_assert(bit_count != 0, "cannot sign-extend zero-sized value"); using S = std::make_signed_t; @@ -150,7 +167,8 @@ constexpr T sign_extend(T value) { /// Sign-extends a value that has bit_count bits to the full bitwidth of type T. template -constexpr T sign_extend(size_t bit_count, T value) { +constexpr T sign_extend(size_t bit_count, T value) +{ ASSERT_MSG(bit_count != 0, "cannot sign-extend zero-sized value"); using S = std::make_signed_t; @@ -160,7 +178,8 @@ constexpr T sign_extend(size_t bit_count, T value) { /// Replicate an element across a value of type T. template -constexpr T replicate_element(T value) { +constexpr T replicate_element(T value) +{ static_assert(element_size <= bitsizeof, "element_size is too large"); static_assert(bitsizeof % element_size == 0, "bitsize of T not divisible by element_size"); @@ -173,7 +192,8 @@ constexpr T replicate_element(T value) { /// Replicate an element of type U across a value of type T. template -constexpr T replicate_element(T value) { +constexpr T replicate_element(T value) +{ static_assert(bitsizeof <= bitsizeof, "element_size is too large"); return replicate_element, T>(value); @@ -181,7 +201,8 @@ constexpr T replicate_element(T value) { /// Replicate an element across a value of type T. template -constexpr T replicate_element(size_t element_size, T value) { +constexpr T replicate_element(size_t element_size, T value) +{ ASSERT_MSG(element_size <= bitsizeof, "element_size is too large"); ASSERT_MSG(bitsizeof % element_size == 0, "bitsize of T not divisible by element_size"); @@ -192,7 +213,8 @@ constexpr T replicate_element(size_t element_size, T value) { } template -constexpr bool most_significant_bit(T value) { +constexpr bool most_significant_bit(T value) +{ return get_bit - 1, T>(value); } diff --git a/include/mcl/bit/rotate.hpp b/include/mcl/bit/rotate.hpp index 649e5e22..cb7e7bdd 100644 --- a/include/mcl/bit/rotate.hpp +++ b/include/mcl/bit/rotate.hpp @@ -11,7 +11,8 @@ namespace mcl::bit { template -constexpr T rotate_right(T x, size_t amount) { +constexpr T rotate_right(T x, size_t amount) +{ amount %= bitsizeof; if (amount == 0) { return x; @@ -20,7 +21,8 @@ constexpr T rotate_right(T x, size_t amount) { } template -constexpr T rotate_left(T x, size_t amount) { +constexpr T rotate_left(T x, size_t amount) +{ amount %= bitsizeof; if (amount == 0) { return x; diff --git a/include/mcl/bit/swap.hpp b/include/mcl/bit/swap.hpp index 0df6bac4..f96b6af3 100644 --- a/include/mcl/bit/swap.hpp +++ b/include/mcl/bit/swap.hpp @@ -8,18 +8,21 @@ namespace mcl::bit { -constexpr u16 swap_bytes_16(u16 value) { +constexpr u16 swap_bytes_16(u16 value) +{ return static_cast(u32{value} >> 8 | u32{value} << 8); } -constexpr u32 swap_bytes_32(u32 value) { +constexpr u32 swap_bytes_32(u32 value) +{ return ((value & 0xff000000u) >> 24) | ((value & 0x00ff0000u) >> 8) | ((value & 0x0000ff00u) << 8) | ((value & 0x000000ffu) << 24); } -constexpr u64 swap_bytes_64(u64 value) { +constexpr u64 swap_bytes_64(u64 value) +{ return ((value & 0xff00000000000000ull) >> 56) | ((value & 0x00ff000000000000ull) >> 40) | ((value & 0x0000ff0000000000ull) >> 24) @@ -30,19 +33,22 @@ constexpr u64 swap_bytes_64(u64 value) { | ((value & 0x00000000000000ffull) << 56); } -constexpr u32 swap_halves_32(u32 value) { +constexpr u32 swap_halves_32(u32 value) +{ return ((value & 0xffff0000u) >> 16) | ((value & 0x0000ffffu) << 16); } -constexpr u64 swap_halves_64(u64 value) { +constexpr u64 swap_halves_64(u64 value) +{ return ((value & 0xffff000000000000ull) >> 48) | ((value & 0x0000ffff00000000ull) >> 16) | ((value & 0x00000000ffff0000ull) << 16) | ((value & 0x000000000000ffffull) << 48); } -constexpr u64 swap_words_64(u64 value) { +constexpr u64 swap_words_64(u64 value) +{ return ((value & 0xffffffff00000000ull) >> 32) | ((value & 0x00000000ffffffffull) << 32); } diff --git a/include/mcl/bit_cast.hpp b/include/mcl/bit_cast.hpp index cfa8860a..3eda2657 100644 --- a/include/mcl/bit_cast.hpp +++ b/include/mcl/bit_cast.hpp @@ -11,7 +11,8 @@ namespace mcl { /// Reinterpret objects of one type as another by bit-casting between object representations. template -inline Dest bit_cast(const Source& source) noexcept { +inline Dest bit_cast(const Source& source) noexcept +{ static_assert(sizeof(Dest) == sizeof(Source), "size of destination and source objects must be equal"); static_assert(std::is_trivially_copyable_v, "destination type must be trivially copyable."); static_assert(std::is_trivially_copyable_v, "source type must be trivially copyable"); @@ -24,7 +25,8 @@ inline Dest bit_cast(const Source& source) noexcept { /// Reinterpret objects of any arbitrary type as another type by bit-casting between object representations. /// Note that here we do not verify if source pointed to by source_ptr has enough bytes to read from. template -inline Dest bit_cast_pointee(const SourcePtr source_ptr) noexcept { +inline Dest bit_cast_pointee(const SourcePtr source_ptr) noexcept +{ static_assert(sizeof(SourcePtr) == sizeof(void*), "source pointer must have size of a pointer"); static_assert(std::is_trivially_copyable_v, "destination type must be trivially copyable."); diff --git a/include/mcl/container/detail/meta_byte.hpp b/include/mcl/container/detail/meta_byte.hpp new file mode 100644 index 00000000..c3fc8c4e --- /dev/null +++ b/include/mcl/container/detail/meta_byte.hpp @@ -0,0 +1,35 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +#pragma once + +#include "mcl/bitsizeof.hpp" +#include "mcl/stdint.hpp" + +namespace mcl::detail { + +/// if MSB is 0, this is a full slot. remaining 7 bits is a partial hash of the key. +/// if MSB is 1, this is a non-full slot. +enum class meta_byte : u8 { + empty = 0xff, + tombstone = 0x80, + end_sentinel = 0x88, +}; + +inline bool is_full(meta_byte mb) +{ + return (static_cast(mb) & 0x80) == 0; +} + +inline meta_byte meta_byte_from_hash(size_t hash) +{ + return static_cast(hash >> (bitsizeof - 7)); +} + +inline size_t group_index_from_hash(size_t hash, size_t group_index_mask) +{ + return hash & group_index_mask; +} + +} // namespace mcl::detail diff --git a/include/mcl/container/detail/meta_byte_group.hpp b/include/mcl/container/detail/meta_byte_group.hpp new file mode 100644 index 00000000..05f9ad1a --- /dev/null +++ b/include/mcl/container/detail/meta_byte_group.hpp @@ -0,0 +1,263 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include + +#include "mcl/assert.hpp" +#include "mcl/container/detail/meta_byte.hpp" +#include "mcl/macro/architecture.hpp" +#include "mcl/stdint.hpp" + +#if defined(MCL_ARCHITECTURE_ARM64) +# include +#elif defined(MCL_ARCHITECTURE_X86_64) +# include + +# include "mcl/bit_cast.hpp" +#else +# include +#endif + +namespace mcl::detail { + +#if defined(MCL_ARCHITECTURE_ARM64) + +struct meta_byte_group { + static constexpr size_t max_group_size{16}; + + explicit meta_byte_group(meta_byte* ptr) + : data{vld1q_u8(reinterpret_cast(ptr))} + {} + + explicit meta_byte_group(const std::array& array) + : data{vld1q_u8(reinterpret_cast(array.data()))} + {} + + uint64x2_t match(meta_byte cmp) const + { + return vreinterpretq_u64_u8(vandq_u8(vceqq_u8(data, + vdupq_n_u8(static_cast(cmp))), + vdupq_n_u8(0x80))); + } + + uint64x2_t match_empty_or_tombstone() const + { + return vreinterpretq_u64_u8(vandq_u8(data, + vdupq_n_u8(0x80))); + } + + bool is_any_empty() const + { + static_assert(meta_byte::empty == static_cast(0xff), "empty must be maximal u8 value"); + return vmaxvq_u8(data) == 0xff; + } + + bool is_all_empty_or_tombstone() const + { + return vminvq_u8(vandq_u8(data, vdupq_n_u8(0x80))) == 0x80; + } + + meta_byte get(size_t index) const + { + return static_cast(data[index]); + } + + void set(size_t index, meta_byte value) + { + data[index] = static_cast(value); + } + + uint8x16_t data; +}; + +# define MCL_HMAP_MATCH_META_BYTE_GROUP(MATCH, ...) \ + { \ + const uint64x2_t match_result{MATCH}; \ + \ + for (u64 match_result_v{match_result[0]}; match_result_v != 0; match_result_v &= match_result_v - 1) { \ + const size_t match_index{static_cast(std::countr_zero(match_result_v) / 8)}; \ + __VA_ARGS__ \ + } \ + \ + for (u64 match_result_v{match_result[1]}; match_result_v != 0; match_result_v &= match_result_v - 1) { \ + const size_t match_index{static_cast(8 + std::countr_zero(match_result_v) / 8)}; \ + __VA_ARGS__ \ + } \ + } + +# define MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(MATCH, ...) \ + { \ + const uint64x2_t match_result{MATCH}; \ + \ + for (u64 match_result_v{match_result[0]}; match_result_v != 0; match_result_v &= match_result_v - 1) { \ + const size_t match_index{static_cast(std::countr_zero(match_result_v) / 8)}; \ + __VA_ARGS__ \ + } \ + \ + for (u64 match_result_v{match_result[1] & 0x00ffffffffffffff}; match_result_v != 0; match_result_v &= match_result_v - 1) { \ + const size_t match_index{static_cast(8 + std::countr_zero(match_result_v) / 8)}; \ + __VA_ARGS__ \ + } \ + } + +#elif defined(MCL_ARCHITECTURE_X86_64) + +struct meta_byte_group { + static constexpr size_t max_group_size{16}; + + explicit meta_byte_group(meta_byte* ptr) + : data{_mm_load_si128(reinterpret_cast<__m128i const*>(ptr))} + {} + + explicit meta_byte_group(const std::array& array) + : data{_mm_loadu_si128(reinterpret_cast<__m128i const*>(array.data()))} + {} + + u16 match(meta_byte cmp) const + { + return _mm_movemask_epi8(_mm_cmpeq_epi8(data, _mm_set1_epi8(static_cast(cmp)))); + } + + u16 match_empty_or_tombstone() const + { + return _mm_movemask_epi8(data); + } + + bool is_any_empty() const + { + return match(meta_byte::empty); + } + + bool is_all_empty_or_tombstone() const + { + return match_empty_or_tombstone() == 0xffff; + } + + meta_byte get(size_t index) const + { + return mcl::bit_cast>(data)[index]; + } + + void set(size_t index, meta_byte value) + { + auto array = mcl::bit_cast>(data); + array[index] = value; + data = mcl::bit_cast<__m128i>(array); + } + + __m128i data; +}; + +# define MCL_HMAP_MATCH_META_BYTE_GROUP(MATCH, ...) \ + { \ + for (u16 match_result{MATCH}; match_result != 0; match_result &= match_result - 1) { \ + const size_t match_index{static_cast(std::countr_zero(match_result))}; \ + __VA_ARGS__ \ + } \ + } + +# define MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(MATCH, ...) \ + { \ + for (u16 match_result{static_cast((MATCH) & (0x7fff))}; match_result != 0; match_result &= match_result - 1) { \ + const size_t match_index{static_cast(std::countr_zero(match_result))}; \ + __VA_ARGS__ \ + } \ + } + +#else + +struct meta_byte_group { + static constexpr size_t max_group_size{16}; + + static constexpr u64 msb{0x8080808080808080}; + static constexpr u64 lsb{0x0101010101010101}; + static constexpr u64 not_msb{0x7f7f7f7f7f7f7f7f}; + static constexpr u64 not_lsb{0xfefefefefefefefe}; + + explicit meta_byte_group(meta_byte* ptr) + { + std::memcpy(data.data(), ptr, sizeof(data)); + } + + explicit meta_byte_group(const std::array& array) + : data{array} + {} + + std::array match(meta_byte cmp) const + { + DEBUG_ASSERT(is_full(cmp)); + + const u64 vcmp{lsb * static_cast(cmp)}; + return {(msb - ((data[0] ^ vcmp) & not_msb)) & ~data[0] & msb, (msb - ((data[1] ^ vcmp) & not_msb)) & ~data[1] & msb}; + } + + std::array match_empty_or_tombstone() const + { + return {data[0] & msb, data[1] & msb}; + } + + bool is_any_empty() const + { + static_assert((static_cast(meta_byte::empty) & 0xc0) == 0xc0); + static_assert((static_cast(meta_byte::tombstone) & 0xc0) == 0x80); + + return (data[0] & (data[0] << 1) & msb) || (data[1] & (data[1] << 1) & msb); + } + + bool is_all_empty_or_tombstone() const + { + return (data[0] & data[1] & msb) == msb; + } + + meta_byte get(size_t index) const + { + return mcl::bit_cast>(data)[index]; + } + + void set(size_t index, meta_byte value) + { + auto array = mcl::bit_cast>(data); + array[index] = value; + data = mcl::bit_cast>(array); + } + + std::array data; +}; + +# define MCL_HMAP_MATCH_META_BYTE_GROUP(MATCH, ...) \ + { \ + const std::array match_result{MATCH}; \ + \ + for (u64 match_result_v{match_result[0]}; match_result_v != 0; match_result_v &= match_result_v - 1) { \ + const size_t match_index{static_cast(std::countr_zero(match_result_v) / 8)}; \ + __VA_ARGS__ \ + } \ + \ + for (u64 match_result_v{match_result[1]}; match_result_v != 0; match_result_v &= match_result_v - 1) { \ + const size_t match_index{static_cast(8 + std::countr_zero(match_result_v) / 8)}; \ + __VA_ARGS__ \ + } \ + } + +# define MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(MATCH, ...) \ + { \ + const std::array match_result{MATCH}; \ + \ + for (u64 match_result_v{match_result[0]}; match_result_v != 0; match_result_v &= match_result_v - 1) { \ + const size_t match_index{static_cast(std::countr_zero(match_result_v) / 8)}; \ + __VA_ARGS__ \ + } \ + \ + for (u64 match_result_v{match_result[1] & 0x00ffffffffffffff}; match_result_v != 0; match_result_v &= match_result_v - 1) { \ + const size_t match_index{static_cast(8 + std::countr_zero(match_result_v) / 8)}; \ + __VA_ARGS__ \ + } \ + } + +#endif + +} // namespace mcl::detail diff --git a/include/mcl/container/detail/slot_union.hpp b/include/mcl/container/detail/slot_union.hpp new file mode 100644 index 00000000..50385037 --- /dev/null +++ b/include/mcl/container/detail/slot_union.hpp @@ -0,0 +1,16 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +#pragma once + +namespace mcl::detail { + +template +union slot_union { + slot_union() {} + ~slot_union() {} + ValueType value; +}; + +} // namespace mcl::detail diff --git a/include/mcl/container/hmap.hpp b/include/mcl/container/hmap.hpp new file mode 100644 index 00000000..f0616c89 --- /dev/null +++ b/include/mcl/container/hmap.hpp @@ -0,0 +1,532 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include +#include + +#include "mcl/assert.hpp" +#include "mcl/container/detail/meta_byte.hpp" +#include "mcl/container/detail/meta_byte_group.hpp" +#include "mcl/container/detail/slot_union.hpp" +#include "mcl/hash/xmrx.hpp" +#include "mcl/hint/assume.hpp" +#include "mcl/memory/overaligned_unique_ptr.hpp" + +namespace mcl { + +template +class hmap; + +template +class hmap_iterator { + using base_value_type = std::pair; + using slot_type = detail::slot_union; + +public: + using key_type = KeyType; + using mapped_type = MappedType; + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::conditional_t, base_value_type>; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + + hmap_iterator() = default; + hmap_iterator(const hmap_iterator& other) = default; + hmap_iterator& operator=(const hmap_iterator& other) = default; + + hmap_iterator& operator++() + { + if (mb_ptr == nullptr) + return *this; + + ++mb_ptr; + ++slot_ptr; + + skip_empty_or_tombstone(); + + return *this; + } + hmap_iterator operator++(int) + { + hmap_iterator it(*this); + ++*this; + return it; + } + + bool operator==(const hmap_iterator& other) const + { + return std::tie(mb_ptr, slot_ptr) == std::tie(other.mb_ptr, other.slot_ptr); + } + bool operator!=(const hmap_iterator& other) const + { + return !operator==(other); + } + + reference operator*() const + { + return static_cast(slot_ptr->value); + } + pointer operator->() const + { + return std::addressof(operator*()); + } + +private: + friend class hmap; + + hmap_iterator(detail::meta_byte* mb_ptr, slot_type* slot_ptr) + : mb_ptr{mb_ptr}, slot_ptr{slot_ptr} + { + ASSUME(mb_ptr != nullptr); + ASSUME(slot_ptr != nullptr); + } + + void skip_empty_or_tombstone() + { + if (!mb_ptr) + return; + + while (*mb_ptr == detail::meta_byte::empty || *mb_ptr == detail::meta_byte::tombstone) { + ++mb_ptr; + ++slot_ptr; + } + + if (*mb_ptr == detail::meta_byte::end_sentinel) { + mb_ptr = nullptr; + slot_ptr = nullptr; + } + } + + detail::meta_byte* mb_ptr{nullptr}; + slot_type* slot_ptr{nullptr}; +}; + +template, typename Pred = std::equal_to> +class hmap { +public: + using key_type = KeyType; + using mapped_type = MappedType; + using hasher = Hash; + using key_equal = Pred; + using value_type = std::pair; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using iterator = hmap_iterator; + using const_iterator = hmap_iterator; + +private: + static constexpr size_t group_size{detail::meta_byte_group::max_group_size}; + static constexpr size_t average_max_group_load{group_size - 2}; + + using slot_type = detail::slot_union; + using slot_ptr = std::unique_ptr; + using meta_byte_ptr = overaligned_unique_ptr; + static_assert(!std::is_reference_v); + static_assert(!std::is_reference_v); + +public: + hmap() + { + initialize_members(1); + } + hmap(const hmap& other) + { + deep_copy(other); + } + hmap(hmap&& other) + : group_index_mask{std::exchange(other.group_index_mask, 0)} + , empty_slots{std::exchange(other.empty_slots, 0)} + , full_slots{std::exchange(other.full_slots, 0)} + , mbs{std::move(other.mbs)} + , slots{std::move(other.slots)} + { + } + hmap& operator=(const hmap& other) + { + deep_copy(other); + return *this; + } + hmap& operator=(hmap&& other) + { + group_index_mask = std::exchange(other.group_index_mask, 0); + empty_slots = std::exchange(other.empty_slots, 0); + full_slots = std::exchange(other.full_slots, 0); + mbs = std::move(other.mbs); + slots = std::move(other.slots); + return *this; + } + + ~hmap() + { + if (!mbs) + return; + + clear(); + } + + [[nodiscard]] bool empty() const noexcept { return full_slots == 0; } + size_type size() const noexcept { return full_slots; } + size_type max_size() const noexcept { return static_cast(std::numeric_limits::max()); } + + iterator begin() + { + iterator result{iterator_at(0)}; + result.skip_empty_or_tombstone(); + return result; + } + iterator end() + { + return {}; + } + const_iterator cbegin() const + { + const_iterator result{const_iterator_at(0)}; + result.skip_empty_or_tombstone(); + return result; + } + const_iterator cend() const + { + return {}; + } + const_iterator begin() const + { + return cbegin(); + } + const_iterator end() const + { + return cend(); + } + + template + std::pair try_emplace(K&& k, Args&&... args) + { + auto [item_index, item_found] = find_key_or_empty_slot(k); + if (!item_found) { + new (&slots[item_index].value) value_type( + std::piecewise_construct, + std::forward_as_tuple(std::forward(k)), + std::forward_as_tuple(std::forward(args)...)); + } + return {iterator_at(item_index), !item_found}; + } + + template + std::pair insert_or_assign(K&& k, V&& v) + { + auto [item_index, item_found] = find_key_or_empty_slot(k); + if (item_found) { + slots[item_index].value.second = std::forward(v); + } else { + new (&slots[item_index].value) value_type( + std::forward(k), + std::forward(v)); + } + return {iterator_at(item_index), !item_found}; + } + + void erase(const_iterator position) + { + if (position == cend()) { + return; + } + + const std::size_t item_index{static_cast(std::distance(mbs.get(), position.mb_ptr))}; + const std::size_t group_index{item_index / group_size}; + const detail::meta_byte_group g{mbs.get() + group_index * group_size}; + + erase_impl(item_index, std::move(g)); + } + void erase(iterator position) + { + if (position == end()) { + return; + } + + const std::size_t item_index{static_cast(std::distance(mbs.get(), position.mb_ptr))}; + const std::size_t group_index{item_index / group_size}; + const detail::meta_byte_group g{mbs.get() + group_index * group_size}; + + erase_impl(item_index, std::move(g)); + } + template + size_t erase(const K& key) + { + const std::size_t hash{hasher{}(key)}; + const detail::meta_byte mb{detail::meta_byte_from_hash(hash)}; + + size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + detail::meta_byte_group g{mbs.get() + group_index * group_size}; + + MCL_HMAP_MATCH_META_BYTE_GROUP(g.match(mb), { + const std::size_t item_index{group_index * group_size + match_index}; + + if (key_equal{}(slots[item_index].value.first, key)) [[likely]] { + erase_impl(item_index, std::move(g)); + return 1; + } + }); + + if (g.is_any_empty()) [[likely]] { + return 0; + } + + group_index = (group_index + 1) & group_index_mask; + } + } + + template + iterator find(const K& key) + { + const std::size_t hash{hasher{}(key)}; + const detail::meta_byte mb{detail::meta_byte_from_hash(hash)}; + + size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + detail::meta_byte_group g{mbs.get() + group_index * group_size}; + + MCL_HMAP_MATCH_META_BYTE_GROUP(g.match(mb), { + const std::size_t item_index{group_index * group_size + match_index}; + + if (key_equal{}(slots[item_index].value.first, key)) [[likely]] { + return iterator_at(item_index); + } + }); + + if (g.is_any_empty()) [[likely]] { + return {}; + } + + group_index = (group_index + 1) & group_index_mask; + } + } + template + const_iterator find(const K& key) const + { + const std::size_t hash{hasher{}(key)}; + const detail::meta_byte mb{detail::meta_byte_from_hash(hash)}; + + size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + detail::meta_byte_group g{mbs.get() + group_index * group_size}; + + MCL_HMAP_MATCH_META_BYTE_GROUP(g.match(mb), { + const std::size_t item_index{group_index * group_size + match_index}; + + if (key_equal{}(slots[item_index].value.first, key)) [[likely]] { + return const_iterator_at(item_index); + } + }); + + if (g.is_any_empty()) [[likely]] { + return {}; + } + + group_index = (group_index + 1) & group_index_mask; + } + } + template + bool contains(const K& key) const + { + return find(key) != end(); + } + template + size_t count(const K& key) const + { + return contains(key) ? 1 : 0; + } + + template + mapped_type& operator[](K&& k) + { + return try_emplace(std::forward(k)).first->second; + } + template + mapped_type& at(K&& k) + { + const auto iter{find(k)}; + if (iter == end()) { + throw std::out_of_range("hmap::at: key not found"); + } + return iter->second; + } + template + const mapped_type& at(K&& k) const + { + const auto iter{find(k)}; + if (iter == end()) { + throw std::out_of_range("hmap::at: key not found"); + } + return iter->second; + } + + void clear() + { + for (auto iter{begin()}; iter != end(); ++iter) { + iter->~value_type(); + } + + clear_metadata(); + } + +private: + iterator iterator_at(std::size_t item_index) + { + return {mbs.get() + item_index, slots.get() + item_index}; + } + const_iterator const_iterator_at(std::size_t item_index) const + { + return {mbs.get() + item_index, slots.get() + item_index}; + } + + std::pair find_key_or_empty_slot(const key_type& key) + { + const std::size_t hash{hasher{}(key)}; + const detail::meta_byte mb{detail::meta_byte_from_hash(hash)}; + + std::size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + detail::meta_byte_group g{mbs.get() + group_index * group_size}; + + MCL_HMAP_MATCH_META_BYTE_GROUP(g.match(mb), { + const std::size_t item_index{group_index * group_size + match_index}; + + if (key_equal{}(slots[item_index].value.first, key)) [[likely]] { + return {item_index, true}; + } + }); + + if (g.is_any_empty()) [[likely]] { + return {find_empty_slot_to_insert(hash), false}; + } + + group_index = (group_index + 1) & group_index_mask; + } + } + + std::size_t find_empty_slot_to_insert(const std::size_t hash) + { + if (empty_slots == 0) [[unlikely]] { + grow_and_rehash(); + } + + std::size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + detail::meta_byte_group g{mbs.get() + group_index * group_size}; + + MCL_HMAP_MATCH_META_BYTE_GROUP(g.match_empty_or_tombstone(), { + const std::size_t item_index{group_index * group_size + match_index}; + + if (mbs[item_index] == detail::meta_byte::empty) [[likely]] { + --empty_slots; + } + ++full_slots; + + mbs[item_index] = detail::meta_byte_from_hash(hash); + + return item_index; + }); + + group_index = (group_index + 1) & group_index_mask; + } + } + + void erase_impl(std::size_t item_index, detail::meta_byte_group&& g) + { + slots[item_index].value->~value_type(); + + --full_slots; + if (g.is_any_empty()) { + mbs[item_index] = detail::meta_byte::empty; + ++empty_slots; + } else { + mbs[item_index] = detail::meta_byte::tombstone; + } + } + + void grow_and_rehash() + { + const std::size_t new_group_count{2 * (group_index_mask + 1)}; + + pow2_resize(new_group_count); + } + + void pow2_resize(std::size_t new_group_count) + { + auto iter{begin()}; + + const auto old_mbs{std::move(mbs)}; + const auto old_slots{std::move(slots)}; + + initialize_members(new_group_count); + + for (; iter != end(); ++iter) { + const std::size_t hash{hasher{}(iter->first)}; + const std::size_t item_index{find_empty_slot_to_insert(hash)}; + + new (&slots[item_index].value) value_type(std::move(iter.slot_ptr->value)); + iter.slot_ptr->value.~value_type(); + } + } + + void deep_copy(const hmap& other) + { + initialize_members(other.group_index_mask + 1); + + for (auto iter = other.begin(); iter != other.end(); ++iter) { + const std::size_t hash{hasher{}(iter->first)}; + const std::size_t item_index{find_empty_slot_to_insert(hash)}; + + new (&slots[item_index].value) value_type(iter.slot_ptr->value); + } + } + + void initialize_members(std::size_t group_count) + { + // DEBUG_ASSERT(group_count != 0 && std::ispow2(group_count)); + + group_index_mask = group_count - 1; + mbs = make_overaligned_unique_ptr_array(group_count * group_size + 1); + slots = slot_ptr{new slot_type[group_count * group_size]}; + + clear_metadata(); + } + + void clear_metadata() + { + const std::size_t group_count{group_index_mask + 1}; + + empty_slots = group_count * average_max_group_load; + full_slots = 0; + + std::memset(mbs.get(), static_cast(detail::meta_byte::empty), group_count * group_size); + mbs[group_count * group_size] = detail::meta_byte::end_sentinel; + } + + std::size_t group_index_mask; + std::size_t empty_slots; + std::size_t full_slots; + meta_byte_ptr mbs; + slot_ptr slots; +}; + +} // namespace mcl diff --git a/include/mcl/container/ihmap.hpp b/include/mcl/container/ihmap.hpp new file mode 100644 index 00000000..b50229e0 --- /dev/null +++ b/include/mcl/container/ihmap.hpp @@ -0,0 +1,549 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "mcl/assert.hpp" +#include "mcl/container/detail/meta_byte.hpp" +#include "mcl/container/detail/meta_byte_group.hpp" +#include "mcl/container/detail/slot_union.hpp" +#include "mcl/hash/xmrx.hpp" +#include "mcl/hint/assume.hpp" + +namespace mcl { + +template +class ihmap; + +namespace detail { + +constexpr std::array ihmap_default_meta{ + meta_byte::empty, meta_byte::empty, meta_byte::empty, meta_byte::empty, + meta_byte::empty, meta_byte::empty, meta_byte::empty, meta_byte::empty, + meta_byte::empty, meta_byte::empty, meta_byte::empty, meta_byte::empty, + meta_byte::empty, meta_byte::empty, meta_byte::empty, meta_byte::tombstone}; + +template +struct ihmap_group { + using base_value_type = std::pair; + using slot_type = detail::slot_union; + + static constexpr std::size_t group_size{meta_byte_group::max_group_size - 1}; + + meta_byte_group meta{ihmap_default_meta}; + std::array slots{}; +}; + +} // namespace detail + +template +class ihmap_iterator { + using group_type = detail::ihmap_group; + using base_value_type = typename group_type::base_value_type; + +public: + using key_type = KeyType; + using mapped_type = MappedType; + using iterator_category = std::forward_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = std::conditional_t, base_value_type>; + using pointer = value_type*; + using const_pointer = const value_type*; + using reference = value_type&; + using const_reference = const value_type&; + + ihmap_iterator() = default; + ihmap_iterator(const ihmap_iterator& other) = default; + ihmap_iterator& operator=(const ihmap_iterator& other) = default; + + ihmap_iterator& operator++() + { + if (group_ptr == nullptr) + return *this; + + ++slot_index; + + skip_empty_or_tombstone(); + + return *this; + } + ihmap_iterator operator++(int) + { + ihmap_iterator it(*this); + ++*this; + return it; + } + + bool operator==(const ihmap_iterator& other) const + { + return std::tie(group_ptr, slot_index) == std::tie(other.group_ptr, other.slot_index); + } + bool operator!=(const ihmap_iterator& other) const + { + return !operator==(other); + } + + reference operator*() const + { + return static_cast(group_ptr->slots[slot_index].value); + } + pointer operator->() const + { + return std::addressof(operator*()); + } + +private: + friend class ihmap; + + ihmap_iterator(group_type* group_ptr, size_t slot_index) + : group_ptr{group_ptr}, slot_index{slot_index} + { + ASSUME(group_ptr != nullptr); + } + + void skip_empty_or_tombstone() + { + if (!group_ptr) + return; + + while (true) { + const detail::meta_byte mb = group_ptr->meta.get(slot_index); + if (slot_index == group_type::group_size) { + slot_index = 0; + ++group_ptr; + + if (mb == detail::meta_byte::end_sentinel) { + group_ptr = nullptr; + return; + } + + continue; + } + if (is_full(mb)) { + break; + } + ++slot_index; + } + } + + group_type* group_ptr{nullptr}; + std::size_t slot_index{0}; +}; + +template, typename Pred = std::equal_to> +class ihmap { + using group_type = detail::ihmap_group; + +public: + using key_type = KeyType; + using mapped_type = MappedType; + using hasher = Hash; + using key_equal = Pred; + using value_type = typename group_type::base_value_type; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + + using iterator = ihmap_iterator; + using const_iterator = ihmap_iterator; + +private: + static_assert(!std::is_reference_v); + static_assert(!std::is_reference_v); + + static constexpr std::size_t group_size{group_type::group_size}; + static constexpr std::size_t average_max_group_load{group_size - 2}; + + struct position { + std::size_t group_index; + std::size_t slot_index; + }; + +public: + ihmap() + { + initialize_members(1); + } + ihmap(const ihmap& other) + { + deep_copy(other); + } + ihmap(ihmap&& other) + : group_index_mask{std::exchange(other.group_index_mask, 0)} + , empty_slots{std::exchange(other.empty_slots, 0)} + , full_slots{std::exchange(other.full_slots, 0)} + , groups{std::move(other.groups)} + { + } + ihmap& operator=(const ihmap& other) + { + deep_copy(other); + return *this; + } + ihmap& operator=(ihmap&& other) + { + group_index_mask = std::exchange(other.group_index_mask, 0); + empty_slots = std::exchange(other.empty_slots, 0); + full_slots = std::exchange(other.full_slots, 0); + groups = std::move(other.groups); + return *this; + } + + ~ihmap() + { + if (!groups) + return; + + clear(); + } + + [[nodiscard]] bool empty() const noexcept { return full_slots == 0; } + size_type size() const noexcept { return full_slots; } + size_type max_size() const noexcept { return static_cast(std::numeric_limits::max()); } + + iterator begin() + { + iterator result{iterator_at({0, 0})}; + result.skip_empty_or_tombstone(); + return result; + } + iterator end() + { + return {}; + } + const_iterator cbegin() const + { + const_iterator result{const_iterator_at({0, 0})}; + result.skip_empty_or_tombstone(); + return result; + } + const_iterator cend() const + { + return {}; + } + const_iterator begin() const + { + return cbegin(); + } + const_iterator end() const + { + return cend(); + } + + template + std::pair try_emplace(K&& k, Args&&... args) + { + auto [pos, item_found] = find_key_or_empty_slot(k); + if (!item_found) { + new (&groups[pos.group_index].slots[pos.slot_index].value) value_type( + std::piecewise_construct, + std::forward_as_tuple(std::forward(k)), + std::forward_as_tuple(std::forward(args)...)); + } + return {iterator_at(pos), !item_found}; + } + + template + std::pair insert_or_assign(K&& k, V&& v) + { + auto [pos, item_found] = find_key_or_empty_slot(k); + if (item_found) { + groups[pos.group_index].slots[pos.slot_index].value.second = std::forward(v); + } else { + new (&groups[pos.group_index].slots[pos.slot_index].value) value_type( + std::forward(k), + std::forward(v)); + } + return {iterator_at(pos), !item_found}; + } + + void erase(const_iterator iter) + { + if (iter == cend()) { + return; + } + + const std::size_t group_index{static_cast(std::distance(groups.get(), iter.group_ptr))}; + + erase_impl({group_index, iter.slot_index}); + } + void erase(iterator iter) + { + if (iter == end()) { + return; + } + + const std::size_t group_index{static_cast(std::distance(groups.get(), iter.group_ptr))}; + + erase_impl({group_index, iter.slot_index}); + } + template + std::size_t erase(const K& key) + { + const std::size_t hash{hasher{}(key)}; + const detail::meta_byte mb{detail::meta_byte_from_hash(hash)}; + + std::size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + const group_type& g{groups[group_index]}; + + MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(g.meta.match(mb), { + if (key_equal{}(g.slots[match_index].value.first, key)) [[likely]] { + erase_impl({group_index, match_index}); + return 1; + } + }); + + if (g.meta.is_any_empty()) [[likely]] { + return 0; + } + + group_index = (group_index + 1) & group_index_mask; + } + } + + template + iterator find(const K& key) + { + const std::size_t hash{hasher{}(key)}; + const detail::meta_byte mb{detail::meta_byte_from_hash(hash)}; + + std::size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + const group_type& g{groups[group_index]}; + + MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(g.meta.match(mb), { + if (key_equal{}(g.slots[match_index].value.first, key)) [[likely]] { + return iterator_at({group_index, match_index}); + } + }); + + if (g.meta.is_any_empty()) [[likely]] { + return {}; + } + + group_index = (group_index + 1) & group_index_mask; + } + } + template + const_iterator find(const K& key) const + { + const std::size_t hash{hasher{}(key)}; + const detail::meta_byte mb{detail::meta_byte_from_hash(hash)}; + + std::size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + const group_type& g{groups[group_index]}; + + MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(g.meta.match(mb), { + if (key_equal{}(g.slots[match_index].value.first, key)) [[likely]] { + return const_iterator_at({group_index, match_index}); + } + }); + + if (g.meta.is_any_empty()) [[likely]] { + return {}; + } + + group_index = (group_index + 1) & group_index_mask; + } + } + template + bool contains(const K& key) const + { + return find(key) != end(); + } + template + std::size_t count(const K& key) const + { + return contains(key) ? 1 : 0; + } + + template + mapped_type& operator[](K&& k) + { + return try_emplace(std::forward(k)).first->second; + } + template + mapped_type& at(K&& k) + { + const auto iter{find(k)}; + if (iter == end()) { + throw std::out_of_range("ihmap::at: key not found"); + } + return iter->second; + } + template + const mapped_type& at(K&& k) const + { + const auto iter{find(k)}; + if (iter == end()) { + throw std::out_of_range("ihmap::at: key not found"); + } + return iter->second; + } + + void clear() + { + for (auto iter{begin()}; iter != end(); ++iter) { + iter->~value_type(); + } + + clear_metadata(); + } + +private: + iterator iterator_at(position pos) + { + return {groups.get() + pos.group_index, pos.slot_index}; + } + const_iterator const_iterator_at(position pos) const + { + return {groups.get() + pos.group_index, pos.slot_index}; + } + + std::pair find_key_or_empty_slot(const key_type& key) + { + const std::size_t hash{hasher{}(key)}; + const detail::meta_byte mb{detail::meta_byte_from_hash(hash)}; + + std::size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + const group_type& g{groups[group_index]}; + + MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(g.meta.match(mb), { + if (key_equal{}(g.slots[match_index].value.first, key)) [[likely]] { + return {{group_index, match_index}, true}; + } + }); + + if (g.meta.is_any_empty()) [[likely]] { + return {find_empty_slot_to_insert(hash), false}; + } + + group_index = (group_index + 1) & group_index_mask; + } + } + + position find_empty_slot_to_insert(const std::size_t hash) + { + if (empty_slots == 0) [[unlikely]] { + grow_and_rehash(); + } + + std::size_t group_index{detail::group_index_from_hash(hash, group_index_mask)}; + + while (true) { + group_type& g{groups[group_index]}; + + MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(g.meta.match_empty_or_tombstone(), { + if (g.meta.get(match_index) == detail::meta_byte::empty) [[likely]] { + --empty_slots; + } + ++full_slots; + + g.meta.set(match_index, detail::meta_byte_from_hash(hash)); + + return {group_index, match_index}; + }); + + group_index = (group_index + 1) & group_index_mask; + } + } + + void erase_impl(position pos) + { + group_type& g{groups[pos.group_index]}; + + g.slots[pos.slot_index].value.~value_type(); + + --full_slots; + if (g.meta.is_any_empty()) { + g.meta.set(pos.slot_index, detail::meta_byte::empty); + ++empty_slots; + } else { + g.meta.set(pos.slot_index, detail::meta_byte::tombstone); + } + } + + void grow_and_rehash() + { + const std::size_t new_group_count{2 * (group_index_mask + 1)}; + + pow2_resize(new_group_count); + } + + void pow2_resize(std::size_t new_group_count) + { + auto iter{begin()}; + + const auto old_groups{std::move(groups)}; + + initialize_members(new_group_count); + + for (; iter != end(); ++iter) { + const std::size_t hash{hasher{}(iter->first)}; + const position pos{find_empty_slot_to_insert(hash)}; + + new (&groups[pos.group_index].slots[pos.slot_index].value) value_type(std::move(iter.group_ptr->slots[iter.slot_index].value)); + iter.group_ptr->slots[iter.slot_index].value.~value_type(); + } + } + + void deep_copy(const ihmap& other) + { + initialize_members(other.group_index_mask + 1); + + for (auto iter = other.begin(); iter != other.end(); ++iter) { + const std::size_t hash{hasher{}(iter->first)}; + const position pos{find_empty_slot_to_insert(hash)}; + + new (&groups[pos.group_index].slots[pos.slot_index].value) value_type(iter.group_ptr->slots[iter.slot_index].value); + } + } + + void initialize_members(std::size_t group_count) + { + // DEBUG_ASSERT(group_count != 0 && std::ispow2(group_count)); + + group_index_mask = group_count - 1; + groups = std::unique_ptr{new group_type[group_count]}; + + clear_metadata(); + } + + void clear_metadata() + { + const std::size_t group_count{group_index_mask + 1}; + + empty_slots = group_count * average_max_group_load; + full_slots = 0; + + for (size_t i{0}; i < group_count; ++i) { + groups[i].meta = detail::meta_byte_group{detail::ihmap_default_meta}; + } + groups[group_count - 1].meta.set(group_size, detail::meta_byte::end_sentinel); + } + + std::size_t group_index_mask; + std::size_t empty_slots; + std::size_t full_slots; + std::unique_ptr groups; +}; + +} // namespace mcl diff --git a/include/mcl/container/intrusive_list.hpp b/include/mcl/container/intrusive_list.hpp index b757f214..515fd891 100644 --- a/include/mcl/container/intrusive_list.hpp +++ b/include/mcl/container/intrusive_list.hpp @@ -21,7 +21,8 @@ class intrusive_list_iterator; template class intrusive_list_node { public: - bool is_sentinel() const { + bool is_sentinel() const + { return is_sentinel_; } @@ -42,7 +43,8 @@ class intrusive_list_sentinel final : public intrusive_list_node { using intrusive_list_node::is_sentinel_; public: - intrusive_list_sentinel() { + intrusive_list_sentinel() + { next = this; prev = this; is_sentinel_ = true; @@ -72,50 +74,56 @@ public: intrusive_list_iterator& operator=(const intrusive_list_iterator& other) = default; explicit intrusive_list_iterator(node_pointer list_node) - : node(list_node) { - } + : node(list_node) {} explicit intrusive_list_iterator(pointer data) - : node(data) { - } + : node(data) {} explicit intrusive_list_iterator(reference data) - : node(&data) { - } + : node(&data) {} - intrusive_list_iterator& operator++() { + intrusive_list_iterator& operator++() + { node = node->next; return *this; } - intrusive_list_iterator& operator--() { + intrusive_list_iterator& operator--() + { node = node->prev; return *this; } - intrusive_list_iterator operator++(int) { + intrusive_list_iterator operator++(int) + { intrusive_list_iterator it(*this); ++*this; return it; } - intrusive_list_iterator operator--(int) { + intrusive_list_iterator operator--(int) + { intrusive_list_iterator it(*this); --*this; return it; } - bool operator==(const intrusive_list_iterator& other) const { + bool operator==(const intrusive_list_iterator& other) const + { return node == other.node; } - bool operator!=(const intrusive_list_iterator& other) const { + bool operator!=(const intrusive_list_iterator& other) const + { return !operator==(other); } - reference operator*() const { + reference operator*() const + { DEBUG_ASSERT(!node->is_sentinel()); return static_cast(*node); } - pointer operator->() const { + pointer operator->() const + { return std::addressof(operator*()); } - node_pointer AsNodePointer() const { + node_pointer AsNodePointer() const + { return node; } @@ -145,7 +153,8 @@ public: * @param location The location to insert the node. * @param new_node The node to add. */ - iterator insert(iterator location, pointer new_node) { + iterator insert(iterator location, pointer new_node) + { return insert_before(location, new_node); } @@ -156,7 +165,8 @@ public: * @param location The location to insert the new node. * @param new_node The node to insert into the list. */ - iterator insert_before(iterator location, pointer new_node) { + iterator insert_before(iterator location, pointer new_node) + { auto existing_node = location.AsNodePointer(); new_node->next = existing_node; @@ -173,7 +183,8 @@ public: * @param position Location to insert the node in front of. * @param new_node The node to be inserted into the list. */ - iterator insert_after(iterator position, pointer new_node) { + iterator insert_after(iterator position, pointer new_node) + { if (empty()) return insert(begin(), new_node); @@ -184,7 +195,8 @@ public: * Add an entry to the start of the list. * @param node Node to add to the list. */ - void push_front(pointer node) { + void push_front(pointer node) + { insert(begin(), node); } @@ -192,7 +204,8 @@ public: * Add an entry to the end of the list * @param node Node to add to the list. */ - void push_back(pointer node) { + void push_back(pointer node) + { insert(end(), node); } @@ -200,7 +213,8 @@ public: * Erases the node at the front of the list. * @note Must not be called on an empty list. */ - void pop_front() { + void pop_front() + { DEBUG_ASSERT(!empty()); erase(begin()); } @@ -209,7 +223,8 @@ public: * Erases the node at the back of the list. * @note Must not be called on an empty list. */ - void pop_back() { + void pop_back() + { DEBUG_ASSERT(!empty()); erase(--end()); } @@ -218,7 +233,8 @@ public: * Removes a node from this list * @param it An iterator that points to the node to remove from list. */ - pointer remove(iterator& it) { + pointer remove(iterator& it) + { DEBUG_ASSERT(it != end()); pointer node = &*it++; @@ -237,7 +253,8 @@ public: * Removes a node from this list * @param it A constant iterator that points to the node to remove from list. */ - pointer remove(const iterator& it) { + pointer remove(const iterator& it) + { iterator copy = it; return remove(copy); } @@ -246,7 +263,8 @@ public: * Removes a node from this list. * @param node A pointer to the node to remove. */ - pointer remove(pointer node) { + pointer remove(pointer node) + { return remove(iterator(node)); } @@ -254,7 +272,8 @@ public: * Removes a node from this list. * @param node A reference to the node to remove. */ - pointer remove(reference node) { + pointer remove(reference node) + { return remove(iterator(node)); } @@ -262,7 +281,8 @@ public: * Is this list empty? * @returns true if there are no nodes in this list. */ - bool empty() const { + bool empty() const + { return root->next == root.get(); } @@ -270,7 +290,8 @@ public: * Gets the total number of elements within this list. * @return the number of elements in this list. */ - size_type size() const { + size_type size() const + { return static_cast(std::distance(begin(), end())); } @@ -278,7 +299,8 @@ public: * Retrieves a reference to the node at the front of the list. * @note Must not be called on an empty list. */ - reference front() { + reference front() + { DEBUG_ASSERT(!empty()); return *begin(); } @@ -287,7 +309,8 @@ public: * Retrieves a constant reference to the node at the front of the list. * @note Must not be called on an empty list. */ - const_reference front() const { + const_reference front() const + { DEBUG_ASSERT(!empty()); return *begin(); } @@ -296,7 +319,8 @@ public: * Retrieves a reference to the node at the back of the list. * @note Must not be called on an empty list. */ - reference back() { + reference back() + { DEBUG_ASSERT(!empty()); return *--end(); } @@ -305,7 +329,8 @@ public: * Retrieves a constant reference to the node at the back of the list. * @note Must not be called on an empty list. */ - const_reference back() const { + const_reference back() const + { DEBUG_ASSERT(!empty()); return *--end(); } @@ -331,7 +356,8 @@ public: * Erases a node from the list, indicated by an iterator. * @param it The iterator that points to the node to erase. */ - iterator erase(iterator it) { + iterator erase(iterator it) + { remove(it); return it; } @@ -340,7 +366,8 @@ public: * Erases a node from this list. * @param node A pointer to the node to erase from this list. */ - iterator erase(pointer node) { + iterator erase(pointer node) + { return erase(iterator(node)); } @@ -348,7 +375,8 @@ public: * Erases a node from this list. * @param node A reference to the node to erase from this list. */ - iterator erase(reference node) { + iterator erase(reference node) + { return erase(iterator(node)); } @@ -356,7 +384,8 @@ public: * Exchanges contents of this list with another list instance. * @param other The other list to swap with. */ - void swap(intrusive_list& other) noexcept { + void swap(intrusive_list& other) noexcept + { root.swap(other.root); } @@ -371,7 +400,8 @@ private: * @param rhs The second list. */ template -void swap(intrusive_list& lhs, intrusive_list& rhs) noexcept { +void swap(intrusive_list& lhs, intrusive_list& rhs) noexcept +{ lhs.swap(rhs); } diff --git a/include/mcl/hash/xmrx.hpp b/include/mcl/hash/xmrx.hpp new file mode 100644 index 00000000..3ba38cae --- /dev/null +++ b/include/mcl/hash/xmrx.hpp @@ -0,0 +1,32 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +// Reference: http://jonkagstrom.com/bit-mixer-construction/ + +#pragma once + +#include + +#include "mcl/bit/rotate.hpp" +#include "mcl/stdint.hpp" + +namespace mcl::hash { + +constexpr size_t xmrx(size_t x) +{ + x ^= x >> 32; + x *= 0xff51afd7ed558ccd; + x ^= bit::rotate_right(x, 47) ^ bit::rotate_right(x, 23); + return x; +} + +template +struct avalanche_xmrx { + size_t operator()(const T& value) + { + return xmrx(std::hash{}(value)); + } +}; + +} // namespace mcl::hash diff --git a/include/mcl/iterator/reverse.hpp b/include/mcl/iterator/reverse.hpp index eb21b9e0..d7bdab63 100644 --- a/include/mcl/iterator/reverse.hpp +++ b/include/mcl/iterator/reverse.hpp @@ -13,12 +13,14 @@ template struct reverse_adapter { T& iterable; - constexpr auto begin() { + constexpr auto begin() + { using namespace std; return rbegin(iterable); } - constexpr auto end() { + constexpr auto end() + { using namespace std; return rend(iterable); } @@ -27,7 +29,8 @@ struct reverse_adapter { } // namespace detail template -constexpr detail::reverse_adapter reverse(T&& iterable) { +constexpr detail::reverse_adapter reverse(T&& iterable) +{ return detail::reverse_adapter{iterable}; } diff --git a/include/mcl/memory/overaligned_unique_ptr.hpp b/include/mcl/memory/overaligned_unique_ptr.hpp new file mode 100644 index 00000000..1e645efe --- /dev/null +++ b/include/mcl/memory/overaligned_unique_ptr.hpp @@ -0,0 +1,46 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +#pragma once + +#include + +#ifdef _MSC_VER +# include +#else +# include +#endif + +namespace mcl { + +namespace detail { +struct aligned_alloc_deleter { + template + void operator()(T* p) const + { +#ifdef _MSC_VER + _aligned_free(const_cast*>(p)); +#else + std::free(const_cast*>(p)); +#endif + } +}; +} // namespace detail + +template +using overaligned_unique_ptr = std::unique_ptr; + +template +auto make_overaligned_unique_ptr_array(size_t element_count) +{ + const size_t min_size = element_count * sizeof(T); + const size_t alloc_size = (min_size + alignment - 1) / alignment * alignment; +#ifdef _MSC_VER + return overaligned_unique_ptr{static_cast(_aligned_malloc(alloc_size, alignment))}; +#else + return overaligned_unique_ptr{static_cast(std::aligned_alloc(alignment, alloc_size))}; +#endif +} + +} // namespace mcl diff --git a/include/mcl/mp/typelist/contains.hpp b/include/mcl/mp/typelist/contains.hpp index f8d1fbf8..1b7a0ed6 100644 --- a/include/mcl/mp/typelist/contains.hpp +++ b/include/mcl/mp/typelist/contains.hpp @@ -14,7 +14,7 @@ struct contains; template class LT, class... Ts, class T> struct contains, T> - : bool_value<(false || ... || std::is_same_v)> {}; + : bool_value<(false || ... || std::is_same_v)> {}; /// Does list L contain an element which is same as type T? template diff --git a/include/mcl/scope_exit.hpp b/include/mcl/scope_exit.hpp index d6d11ed2..d3772e90 100644 --- a/include/mcl/scope_exit.hpp +++ b/include/mcl/scope_exit.hpp @@ -20,8 +20,10 @@ template class scope_exit final { public: explicit scope_exit(Function&& fn) - : function(std::move(fn)) {} - ~scope_exit() noexcept { + : function(std::move(fn)) {} + + ~scope_exit() noexcept + { function(); } @@ -33,8 +35,10 @@ template class scope_fail final { public: explicit scope_fail(Function&& fn) - : function(std::move(fn)), exception_count(std::uncaught_exceptions()) {} - ~scope_fail() noexcept { + : function(std::move(fn)), exception_count(std::uncaught_exceptions()) {} + + ~scope_fail() noexcept + { if (std::uncaught_exceptions() > exception_count) { function(); } @@ -49,8 +53,10 @@ template class scope_success final { public: explicit scope_success(Function&& fn) - : function(std::move(fn)), exception_count(std::uncaught_exceptions()) {} - ~scope_success() { + : function(std::move(fn)), exception_count(std::uncaught_exceptions()) {} + + ~scope_success() + { if (std::uncaught_exceptions() <= exception_count) { function(); } @@ -64,17 +70,20 @@ private: // We use ->* here as it has the highest precedence of the operators we can use. template -auto operator->*(scope_exit_tag, Function&& function) { +auto operator->*(scope_exit_tag, Function&& function) +{ return scope_exit>{std::forward(function)}; } template -auto operator->*(scope_fail_tag, Function&& function) { +auto operator->*(scope_fail_tag, Function&& function) +{ return scope_fail>{std::forward(function)}; } template -auto operator->*(scope_success_tag, Function&& function) { +auto operator->*(scope_success_tag, Function&& function) +{ return scope_success>{std::forward(function)}; } diff --git a/src/assert.cpp b/src/assert.cpp index e30697e2..8772439b 100644 --- a/src/assert.cpp +++ b/src/assert.cpp @@ -11,9 +11,11 @@ namespace mcl::detail { -[[noreturn]] void assert_terminate_impl(fmt::string_view msg, fmt::format_args args) { - fmt::print(stderr, "assertion failed: "); +[[noreturn]] void assert_terminate_impl(const char* expr_str, fmt::string_view msg, fmt::format_args args) +{ + fmt::print(stderr, "assertion failed: {}\nMessage:", expr_str); fmt::vprint(stderr, msg, args); + std::fflush(stderr); std::terminate(); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1619f34c..d5bbbde1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,13 +1,14 @@ add_executable(mcl-tests bit/bit_field_tests.cpp - main.cpp + container/hmap.cpp + container/ihmap.cpp mp/metavalue_tests.cpp mp/typelist_tests.cpp type_traits/type_traits_tests.cpp ) target_include_directories(mcl-tests PUBLIC .) target_compile_options(mcl-tests PRIVATE ${STAMINA_CXX_FLAGS}) -target_link_libraries(mcl-tests PRIVATE Catch2::Catch2 mcl) +target_link_libraries(mcl-tests PRIVATE Catch2::Catch2WithMain mcl) include(CTest) include(Catch) diff --git a/tests/bit/bit_field_tests.cpp b/tests/bit/bit_field_tests.cpp index fba31eb5..4a5a4ab5 100644 --- a/tests/bit/bit_field_tests.cpp +++ b/tests/bit/bit_field_tests.cpp @@ -5,11 +5,12 @@ #include #include -#include +#include #include #include -TEST_CASE("mcl::bit::ones", "[bit]") { +TEST_CASE("mcl::bit::ones", "[bit]") +{ const std::array cases{ std::make_tuple(0, 0x00), std::make_tuple(1, 0x01), diff --git a/tests/container/hmap.cpp b/tests/container/hmap.cpp new file mode 100644 index 00000000..052a1d1b --- /dev/null +++ b/tests/container/hmap.cpp @@ -0,0 +1,66 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include +#include + +TEST_CASE("mcl::hmap", "[hmap]") +{ + mcl::hmap double_map; + + constexpr int count = 100000; + + REQUIRE(double_map.empty()); + + for (int i = 0; i < count; ++i) { + double_map[i] = i * 2; + REQUIRE(double_map.size() == i + 1); + } + + for (int i = 0; i < count; ++i) { + REQUIRE(double_map[i] == i * 2); + REQUIRE(double_map.contains(i)); + } + + for (int i = 0; i < count; ++i) { + auto iter = double_map.find(i); + REQUIRE(iter->first == i); + REQUIRE(iter->second == i * 2); + } + + for (int i = count; i < count * 2; ++i) { + REQUIRE(!double_map.contains(i)); + } + + for (int i = 0; i < count; ++i) { + auto result = double_map.try_emplace(i, 0); + REQUIRE(!result.second); + } + + for (auto [k, v] : double_map) { + REQUIRE(k * 2 == v); + } + + std::unordered_map indexes_count; + for (auto [k, v] : double_map) { + (void)v; + indexes_count[k]++; + } + for (auto [k, v] : indexes_count) { + (void)k; + REQUIRE(v == 1); + } + + REQUIRE(!double_map.empty()); + double_map.clear(); + REQUIRE(double_map.empty()); + + for (auto [k, v] : double_map) { + REQUIRE(false); + } +} diff --git a/tests/container/ihmap.cpp b/tests/container/ihmap.cpp new file mode 100644 index 00000000..5815b7ec --- /dev/null +++ b/tests/container/ihmap.cpp @@ -0,0 +1,66 @@ +// This file is part of the mcl project. +// Copyright (c) 2022 merryhime +// SPDX-License-Identifier: MIT + +#include + +#include +#include +#include +#include + +TEST_CASE("mcl::ihmap", "[ihmap]") +{ + mcl::ihmap double_map; + + constexpr int count = 100000; + + REQUIRE(double_map.empty()); + + for (int i = 0; i < count; ++i) { + double_map[i] = i * 2; + REQUIRE(double_map.size() == i + 1); + } + + for (int i = 0; i < count; ++i) { + REQUIRE(double_map[i] == i * 2); + REQUIRE(double_map.contains(i)); + } + + for (int i = 0; i < count; ++i) { + auto iter = double_map.find(i); + REQUIRE(iter->first == i); + REQUIRE(iter->second == i * 2); + } + + for (int i = count; i < count * 2; ++i) { + REQUIRE(!double_map.contains(i)); + } + + for (int i = 0; i < count; ++i) { + auto result = double_map.try_emplace(i, 0); + REQUIRE(!result.second); + } + + for (auto [k, v] : double_map) { + REQUIRE(k * 2 == v); + } + + std::unordered_map indexes_count; + for (auto [k, v] : double_map) { + (void)v; + indexes_count[k]++; + } + for (auto [k, v] : indexes_count) { + (void)k; + REQUIRE(v == 1); + } + + REQUIRE(!double_map.empty()); + double_map.clear(); + REQUIRE(double_map.empty()); + + for (auto [k, v] : double_map) { + REQUIRE(false); + } +} diff --git a/tests/main.cpp b/tests/main.cpp deleted file mode 100644 index 14b471b4..00000000 --- a/tests/main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -// This file is part of the mcl project. -// Copyright (c) 2022 merryhime -// SPDX-License-Identifier: MIT - -#define CATCH_CONFIG_MAIN -#include "catch2/catch.hpp"