Squashed 'externals/mcl/' changes from 761b7c05e..0172df743
0172df743 CMakeLists: Only add tests if MASTER_PROJECT 52e8dff62 0.1.11 fc8d745cc container: hmap fixups 5b5c0130d memory: Add overaligned_unique_ptr c7c9bbd17 mcl: Increment version to 0.1.10 678aa32a8 assert: Handle expr strings separately b38a9d2ef tests: Update to Catch 3.0.1 8aeacfe32 mcl: Increment version to 0.1.9 b468a2ab5 mcl: meta_byte: Split off meta_byte_group d3ae1ae47 mcl: ihmap: Implement inline variant of hmap 5cbfe6eed mcl: hmap: Split detail into headers ee7467677 mcl: hmap: Better default hash f1d902ce9 mcl: hash: Add xmrx 322a221f0 mcl: hmap: Bugfix skip_empty_or_tombstone 689f393f7 mcl: hmap: x64 implementation fa6ff746a mcl: hmap: Add generic meta_byte_group implementation 91e3073ad mcl: hmap: Add more member functions 4998335a5 mcl: Install only if master project 7ff4d2549 mcl: hmap prototype 416a2c6b5 mcl: clang-format: Adopt WebKit style bracing d5a46fa70 mcl/assert: Flush stderr e3b6cc79e externals: Update mcl to 0.1.7 190c68475 mcl: Build as PIC git-subtree-dir: externals/mcl git-subtree-split: 0172df74316351868c215f735e5a2538b10d71fb
This commit is contained in:
parent
5da4668a0d
commit
78bb1d1571
25 changed files with 1834 additions and 143 deletions
|
@ -34,7 +34,7 @@ BraceWrapping:
|
||||||
AfterClass: false
|
AfterClass: false
|
||||||
AfterControlStatement: Never
|
AfterControlStatement: Never
|
||||||
AfterEnum: false
|
AfterEnum: false
|
||||||
AfterFunction: false
|
AfterFunction: true
|
||||||
AfterNamespace: false
|
AfterNamespace: false
|
||||||
AfterObjCDeclaration: false
|
AfterObjCDeclaration: false
|
||||||
AfterStruct: false
|
AfterStruct: false
|
||||||
|
@ -62,7 +62,7 @@ ColumnLimit: 0
|
||||||
CommentPragmas: '^ IWYU pragma:'
|
CommentPragmas: '^ IWYU pragma:'
|
||||||
CompactNamespaces: false
|
CompactNamespaces: false
|
||||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||||
ConstructorInitializerIndentWidth: 8
|
ConstructorInitializerIndentWidth: 4
|
||||||
ContinuationIndentWidth: 4
|
ContinuationIndentWidth: 4
|
||||||
Cpp11BracedListStyle: true
|
Cpp11BracedListStyle: true
|
||||||
DeriveLineEnding: true
|
DeriveLineEnding: true
|
||||||
|
|
|
@ -1,10 +1,18 @@
|
||||||
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
|
cmake_minimum_required(VERSION 3.12 FATAL_ERROR)
|
||||||
include(GNUInstallDirs)
|
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
|
# 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
|
# Default to a Release build
|
||||||
if (NOT CMAKE_BUILD_TYPE)
|
if (NOT CMAKE_BUILD_TYPE)
|
||||||
|
@ -85,7 +93,7 @@ endif()
|
||||||
# Dependencies
|
# Dependencies
|
||||||
|
|
||||||
if (NOT TARGET Catch2::Catch2)
|
if (NOT TARGET Catch2::Catch2)
|
||||||
find_package(Catch2 QUIET)
|
find_package(Catch2 3 QUIET)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT TARGET fmt::fmt)
|
if (NOT TARGET fmt::fmt)
|
||||||
|
@ -95,32 +103,33 @@ endif()
|
||||||
# Project files
|
# Project files
|
||||||
|
|
||||||
add_subdirectory(src)
|
add_subdirectory(src)
|
||||||
if (TARGET Catch2::Catch2)
|
if (TARGET Catch2::Catch2 AND MASTER_PROJECT)
|
||||||
add_subdirectory(tests)
|
add_subdirectory(tests)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Install instructions
|
# Install instructions
|
||||||
|
if (MCL_INSTALL)
|
||||||
|
include(GNUInstallDirs)
|
||||||
|
include(CMakePackageConfigHelpers)
|
||||||
|
|
||||||
include(GNUInstallDirs)
|
install(TARGETS mcl EXPORT mclTargets)
|
||||||
include(CMakePackageConfigHelpers)
|
install(EXPORT mclTargets
|
||||||
|
|
||||||
install(TARGETS mcl EXPORT mclTargets)
|
|
||||||
install(EXPORT mclTargets
|
|
||||||
NAMESPACE merry::
|
NAMESPACE merry::
|
||||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl"
|
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl"
|
||||||
)
|
)
|
||||||
|
|
||||||
configure_package_config_file(CMakeModules/mclConfig.cmake.in
|
configure_package_config_file(CMakeModules/mclConfig.cmake.in
|
||||||
mclConfig.cmake
|
mclConfig.cmake
|
||||||
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl"
|
INSTALL_DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl"
|
||||||
)
|
)
|
||||||
write_basic_package_version_file(mclConfigVersion.cmake
|
write_basic_package_version_file(mclConfigVersion.cmake
|
||||||
COMPATIBILITY SameMajorVersion
|
COMPATIBILITY SameMajorVersion
|
||||||
)
|
)
|
||||||
install(FILES
|
install(FILES
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/mclConfig.cmake"
|
"${CMAKE_CURRENT_BINARY_DIR}/mclConfig.cmake"
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/mclConfigVersion.cmake"
|
"${CMAKE_CURRENT_BINARY_DIR}/mclConfigVersion.cmake"
|
||||||
DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mcl"
|
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()
|
||||||
|
|
|
@ -13,11 +13,12 @@
|
||||||
|
|
||||||
namespace mcl::detail {
|
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<typename... Ts>
|
template<typename... Ts>
|
||||||
[[noreturn]] void assert_terminate(fmt::string_view msg, Ts... args) {
|
[[noreturn]] void assert_terminate(const char* expr_str, fmt::string_view msg, Ts... args)
|
||||||
assert_terminate_impl(msg, fmt::make_format_args(args...));
|
{
|
||||||
|
assert_terminate_impl(expr_str, msg, fmt::make_format_args(args...));
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace mcl::detail
|
} // namespace mcl::detail
|
||||||
|
@ -32,7 +33,7 @@ template<typename... Ts>
|
||||||
} \
|
} \
|
||||||
} else { \
|
} else { \
|
||||||
if (!(expr)) [[unlikely]] { \
|
if (!(expr)) [[unlikely]] { \
|
||||||
::mcl::detail::assert_terminate(#expr); \
|
::mcl::detail::assert_terminate(#expr, "(none)"); \
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
}()
|
}()
|
||||||
|
@ -45,12 +46,12 @@ template<typename... Ts>
|
||||||
} \
|
} \
|
||||||
} else { \
|
} else { \
|
||||||
if (!(expr)) [[unlikely]] { \
|
if (!(expr)) [[unlikely]] { \
|
||||||
::mcl::detail::assert_terminate(#expr "\nMessage: " __VA_ARGS__); \
|
::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)
|
#if defined(NDEBUG) || defined(MCL_IGNORE_ASSERTS)
|
||||||
# define DEBUG_ASSERT(expr) ASSUME(expr)
|
# define DEBUG_ASSERT(expr) ASSUME(expr)
|
||||||
|
|
|
@ -13,12 +13,14 @@
|
||||||
namespace mcl::bit {
|
namespace mcl::bit {
|
||||||
|
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
inline size_t count_ones(T x) {
|
inline size_t count_ones(T x)
|
||||||
|
{
|
||||||
return std::bitset<bitsizeof<T>>(x).count();
|
return std::bitset<bitsizeof<T>>(x).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr size_t count_leading_zeros(T x) {
|
constexpr size_t count_leading_zeros(T x)
|
||||||
|
{
|
||||||
size_t result = bitsizeof<T>;
|
size_t result = bitsizeof<T>;
|
||||||
while (x != 0) {
|
while (x != 0) {
|
||||||
x >>= 1;
|
x >>= 1;
|
||||||
|
@ -28,7 +30,8 @@ constexpr size_t count_leading_zeros(T x) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr int highest_set_bit(T x) {
|
constexpr int highest_set_bit(T x)
|
||||||
|
{
|
||||||
int result = -1;
|
int result = -1;
|
||||||
while (x != 0) {
|
while (x != 0) {
|
||||||
x >>= 1;
|
x >>= 1;
|
||||||
|
@ -38,7 +41,8 @@ constexpr int highest_set_bit(T x) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr size_t lowest_set_bit(T x) {
|
constexpr size_t lowest_set_bit(T x)
|
||||||
|
{
|
||||||
if (x == 0) {
|
if (x == 0) {
|
||||||
return bitsizeof<T>;
|
return bitsizeof<T>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,8 @@ namespace mcl::bit {
|
||||||
|
|
||||||
/// Create a mask with `count` number of one bits.
|
/// Create a mask with `count` number of one bits.
|
||||||
template<size_t count, BitIntegral T>
|
template<size_t count, BitIntegral T>
|
||||||
constexpr T ones() {
|
constexpr T ones()
|
||||||
|
{
|
||||||
static_assert(count <= bitsizeof<T>, "count larger than bitsize of T");
|
static_assert(count <= bitsizeof<T>, "count larger than bitsize of T");
|
||||||
|
|
||||||
if constexpr (count == 0) {
|
if constexpr (count == 0) {
|
||||||
|
@ -25,7 +26,8 @@ constexpr T ones() {
|
||||||
|
|
||||||
/// Create a mask with `count` number of one bits.
|
/// Create a mask with `count` number of one bits.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr T ones(size_t count) {
|
constexpr T ones(size_t count)
|
||||||
|
{
|
||||||
ASSERT_MSG(count <= bitsizeof<T>, "count larger than bitsize of T");
|
ASSERT_MSG(count <= bitsizeof<T>, "count larger than bitsize of T");
|
||||||
|
|
||||||
if (count == 0) {
|
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.
|
/// Create a mask of type T for bits [begin_bit, end_bit] inclusive.
|
||||||
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
||||||
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 <= end_bit, "invalid bit range (position of beginning bit cannot be greater than that of end bit)");
|
||||||
static_assert(begin_bit < bitsizeof<T>, "begin_bit must be smaller than size of T");
|
static_assert(begin_bit < bitsizeof<T>, "begin_bit must be smaller than size of T");
|
||||||
static_assert(end_bit < bitsizeof<T>, "end_bit must be smaller than size of T");
|
static_assert(end_bit < bitsizeof<T>, "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.
|
/// Create a mask of type T for bits [begin_bit, end_bit] inclusive.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
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 <= end_bit, "invalid bit range (position of beginning bit cannot be greater than that of end bit)");
|
||||||
ASSERT_MSG(begin_bit < bitsizeof<T>, "begin_bit must be smaller than size of T");
|
ASSERT_MSG(begin_bit < bitsizeof<T>, "begin_bit must be smaller than size of T");
|
||||||
ASSERT_MSG(end_bit < bitsizeof<T>, "end_bit must be smaller than size of T");
|
ASSERT_MSG(end_bit < bitsizeof<T>, "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.
|
/// Extract bits [begin_bit, end_bit] inclusive from value of type T.
|
||||||
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
||||||
constexpr T get_bits(T value) {
|
constexpr T get_bits(T value)
|
||||||
|
{
|
||||||
constexpr T m = mask<begin_bit, end_bit, T>();
|
constexpr T m = mask<begin_bit, end_bit, T>();
|
||||||
return (value & m) >> begin_bit;
|
return (value & m) >> begin_bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract bits [begin_bit, end_bit] inclusive from value of type T.
|
/// Extract bits [begin_bit, end_bit] inclusive from value of type T.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
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<T>(begin_bit, end_bit);
|
const T m = mask<T>(begin_bit, end_bit);
|
||||||
return (value & m) >> begin_bit;
|
return (value & m) >> begin_bit;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears bits [begin_bit, end_bit] inclusive of value of type T.
|
/// Clears bits [begin_bit, end_bit] inclusive of value of type T.
|
||||||
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
||||||
constexpr T clear_bits(T value) {
|
constexpr T clear_bits(T value)
|
||||||
|
{
|
||||||
constexpr T m = mask<begin_bit, end_bit, T>();
|
constexpr T m = mask<begin_bit, end_bit, T>();
|
||||||
return value & ~m;
|
return value & ~m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears bits [begin_bit, end_bit] inclusive of value of type T.
|
/// Clears bits [begin_bit, end_bit] inclusive of value of type T.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
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<T>(begin_bit, end_bit);
|
const T m = mask<T>(begin_bit, end_bit);
|
||||||
return value & ~m;
|
return value & ~m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifies bits [begin_bit, end_bit] inclusive of value of type T.
|
/// Modifies bits [begin_bit, end_bit] inclusive of value of type T.
|
||||||
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
template<size_t begin_bit, size_t end_bit, BitIntegral T>
|
||||||
constexpr T set_bits(T value, T new_bits) {
|
constexpr T set_bits(T value, T new_bits)
|
||||||
|
{
|
||||||
constexpr T m = mask<begin_bit, end_bit, T>();
|
constexpr T m = mask<begin_bit, end_bit, T>();
|
||||||
return (value & ~m) | ((new_bits << begin_bit) & m);
|
return (value & ~m) | ((new_bits << begin_bit) & m);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifies bits [begin_bit, end_bit] inclusive of value of type T.
|
/// Modifies bits [begin_bit, end_bit] inclusive of value of type T.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
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<T>(begin_bit, end_bit);
|
const T m = mask<T>(begin_bit, end_bit);
|
||||||
return (value & ~m) | ((new_bits << begin_bit) & m);
|
return (value & ~m) | ((new_bits << begin_bit) & m);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract bit at bit_position from value of type T.
|
/// Extract bit at bit_position from value of type T.
|
||||||
template<size_t bit_position, BitIntegral T>
|
template<size_t bit_position, BitIntegral T>
|
||||||
constexpr bool get_bit(T value) {
|
constexpr bool get_bit(T value)
|
||||||
|
{
|
||||||
constexpr T m = mask<bit_position, bit_position, T>();
|
constexpr T m = mask<bit_position, bit_position, T>();
|
||||||
return (value & m) != 0;
|
return (value & m) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extract bit at bit_position from value of type T.
|
/// Extract bit at bit_position from value of type T.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr bool get_bit(size_t bit_position, T value) {
|
constexpr bool get_bit(size_t bit_position, T value)
|
||||||
|
{
|
||||||
const T m = mask<T>(bit_position, bit_position);
|
const T m = mask<T>(bit_position, bit_position);
|
||||||
return (value & m) != 0;
|
return (value & m) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears bit at bit_position of value of type T.
|
/// Clears bit at bit_position of value of type T.
|
||||||
template<size_t bit_position, BitIntegral T>
|
template<size_t bit_position, BitIntegral T>
|
||||||
constexpr T clear_bit(T value) {
|
constexpr T clear_bit(T value)
|
||||||
|
{
|
||||||
constexpr T m = mask<bit_position, bit_position, T>();
|
constexpr T m = mask<bit_position, bit_position, T>();
|
||||||
return value & ~m;
|
return value & ~m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Clears bit at bit_position of value of type T.
|
/// Clears bit at bit_position of value of type T.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr T clear_bit(size_t bit_position, T value) {
|
constexpr T clear_bit(size_t bit_position, T value)
|
||||||
|
{
|
||||||
const T m = mask<T>(bit_position, bit_position);
|
const T m = mask<T>(bit_position, bit_position);
|
||||||
return value & ~m;
|
return value & ~m;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifies bit at bit_position of value of type T.
|
/// Modifies bit at bit_position of value of type T.
|
||||||
template<size_t bit_position, BitIntegral T>
|
template<size_t bit_position, BitIntegral T>
|
||||||
constexpr T set_bit(T value, bool new_bit) {
|
constexpr T set_bit(T value, bool new_bit)
|
||||||
|
{
|
||||||
constexpr T m = mask<bit_position, bit_position, T>();
|
constexpr T m = mask<bit_position, bit_position, T>();
|
||||||
return (value & ~m) | (new_bit ? m : static_cast<T>(0));
|
return (value & ~m) | (new_bit ? m : static_cast<T>(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Modifies bit at bit_position of value of type T.
|
/// Modifies bit at bit_position of value of type T.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
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<T>(bit_position, bit_position);
|
const T m = mask<T>(bit_position, bit_position);
|
||||||
return (value & ~m) | (new_bit ? m : static_cast<T>(0));
|
return (value & ~m) | (new_bit ? m : static_cast<T>(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sign-extends a value that has bit_count bits to the full bitwidth of type T.
|
/// Sign-extends a value that has bit_count bits to the full bitwidth of type T.
|
||||||
template<size_t bit_count, BitIntegral T>
|
template<size_t bit_count, BitIntegral T>
|
||||||
constexpr T sign_extend(T value) {
|
constexpr T sign_extend(T value)
|
||||||
|
{
|
||||||
static_assert(bit_count != 0, "cannot sign-extend zero-sized value");
|
static_assert(bit_count != 0, "cannot sign-extend zero-sized value");
|
||||||
|
|
||||||
using S = std::make_signed_t<T>;
|
using S = std::make_signed_t<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.
|
/// Sign-extends a value that has bit_count bits to the full bitwidth of type T.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
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");
|
ASSERT_MSG(bit_count != 0, "cannot sign-extend zero-sized value");
|
||||||
|
|
||||||
using S = std::make_signed_t<T>;
|
using S = std::make_signed_t<T>;
|
||||||
|
@ -160,7 +178,8 @@ constexpr T sign_extend(size_t bit_count, T value) {
|
||||||
|
|
||||||
/// Replicate an element across a value of type T.
|
/// Replicate an element across a value of type T.
|
||||||
template<size_t element_size, BitIntegral T>
|
template<size_t element_size, BitIntegral T>
|
||||||
constexpr T replicate_element(T value) {
|
constexpr T replicate_element(T value)
|
||||||
|
{
|
||||||
static_assert(element_size <= bitsizeof<T>, "element_size is too large");
|
static_assert(element_size <= bitsizeof<T>, "element_size is too large");
|
||||||
static_assert(bitsizeof<T> % element_size == 0, "bitsize of T not divisible by element_size");
|
static_assert(bitsizeof<T> % 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.
|
/// Replicate an element of type U across a value of type T.
|
||||||
template<BitIntegral U, BitIntegral T>
|
template<BitIntegral U, BitIntegral T>
|
||||||
constexpr T replicate_element(T value) {
|
constexpr T replicate_element(T value)
|
||||||
|
{
|
||||||
static_assert(bitsizeof<U> <= bitsizeof<T>, "element_size is too large");
|
static_assert(bitsizeof<U> <= bitsizeof<T>, "element_size is too large");
|
||||||
|
|
||||||
return replicate_element<bitsizeof<U>, T>(value);
|
return replicate_element<bitsizeof<U>, T>(value);
|
||||||
|
@ -181,7 +201,8 @@ constexpr T replicate_element(T value) {
|
||||||
|
|
||||||
/// Replicate an element across a value of type T.
|
/// Replicate an element across a value of type T.
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
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<T>, "element_size is too large");
|
ASSERT_MSG(element_size <= bitsizeof<T>, "element_size is too large");
|
||||||
ASSERT_MSG(bitsizeof<T> % element_size == 0, "bitsize of T not divisible by element_size");
|
ASSERT_MSG(bitsizeof<T> % 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<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr bool most_significant_bit(T value) {
|
constexpr bool most_significant_bit(T value)
|
||||||
|
{
|
||||||
return get_bit<bitsizeof<T> - 1, T>(value);
|
return get_bit<bitsizeof<T> - 1, T>(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,8 @@
|
||||||
namespace mcl::bit {
|
namespace mcl::bit {
|
||||||
|
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr T rotate_right(T x, size_t amount) {
|
constexpr T rotate_right(T x, size_t amount)
|
||||||
|
{
|
||||||
amount %= bitsizeof<T>;
|
amount %= bitsizeof<T>;
|
||||||
if (amount == 0) {
|
if (amount == 0) {
|
||||||
return x;
|
return x;
|
||||||
|
@ -20,7 +21,8 @@ constexpr T rotate_right(T x, size_t amount) {
|
||||||
}
|
}
|
||||||
|
|
||||||
template<BitIntegral T>
|
template<BitIntegral T>
|
||||||
constexpr T rotate_left(T x, size_t amount) {
|
constexpr T rotate_left(T x, size_t amount)
|
||||||
|
{
|
||||||
amount %= bitsizeof<T>;
|
amount %= bitsizeof<T>;
|
||||||
if (amount == 0) {
|
if (amount == 0) {
|
||||||
return x;
|
return x;
|
||||||
|
|
|
@ -8,18 +8,21 @@
|
||||||
|
|
||||||
namespace mcl::bit {
|
namespace mcl::bit {
|
||||||
|
|
||||||
constexpr u16 swap_bytes_16(u16 value) {
|
constexpr u16 swap_bytes_16(u16 value)
|
||||||
|
{
|
||||||
return static_cast<u16>(u32{value} >> 8 | u32{value} << 8);
|
return static_cast<u16>(u32{value} >> 8 | u32{value} << 8);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u32 swap_bytes_32(u32 value) {
|
constexpr u32 swap_bytes_32(u32 value)
|
||||||
|
{
|
||||||
return ((value & 0xff000000u) >> 24)
|
return ((value & 0xff000000u) >> 24)
|
||||||
| ((value & 0x00ff0000u) >> 8)
|
| ((value & 0x00ff0000u) >> 8)
|
||||||
| ((value & 0x0000ff00u) << 8)
|
| ((value & 0x0000ff00u) << 8)
|
||||||
| ((value & 0x000000ffu) << 24);
|
| ((value & 0x000000ffu) << 24);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u64 swap_bytes_64(u64 value) {
|
constexpr u64 swap_bytes_64(u64 value)
|
||||||
|
{
|
||||||
return ((value & 0xff00000000000000ull) >> 56)
|
return ((value & 0xff00000000000000ull) >> 56)
|
||||||
| ((value & 0x00ff000000000000ull) >> 40)
|
| ((value & 0x00ff000000000000ull) >> 40)
|
||||||
| ((value & 0x0000ff0000000000ull) >> 24)
|
| ((value & 0x0000ff0000000000ull) >> 24)
|
||||||
|
@ -30,19 +33,22 @@ constexpr u64 swap_bytes_64(u64 value) {
|
||||||
| ((value & 0x00000000000000ffull) << 56);
|
| ((value & 0x00000000000000ffull) << 56);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u32 swap_halves_32(u32 value) {
|
constexpr u32 swap_halves_32(u32 value)
|
||||||
|
{
|
||||||
return ((value & 0xffff0000u) >> 16)
|
return ((value & 0xffff0000u) >> 16)
|
||||||
| ((value & 0x0000ffffu) << 16);
|
| ((value & 0x0000ffffu) << 16);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u64 swap_halves_64(u64 value) {
|
constexpr u64 swap_halves_64(u64 value)
|
||||||
|
{
|
||||||
return ((value & 0xffff000000000000ull) >> 48)
|
return ((value & 0xffff000000000000ull) >> 48)
|
||||||
| ((value & 0x0000ffff00000000ull) >> 16)
|
| ((value & 0x0000ffff00000000ull) >> 16)
|
||||||
| ((value & 0x00000000ffff0000ull) << 16)
|
| ((value & 0x00000000ffff0000ull) << 16)
|
||||||
| ((value & 0x000000000000ffffull) << 48);
|
| ((value & 0x000000000000ffffull) << 48);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u64 swap_words_64(u64 value) {
|
constexpr u64 swap_words_64(u64 value)
|
||||||
|
{
|
||||||
return ((value & 0xffffffff00000000ull) >> 32)
|
return ((value & 0xffffffff00000000ull) >> 32)
|
||||||
| ((value & 0x00000000ffffffffull) << 32);
|
| ((value & 0x00000000ffffffffull) << 32);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,8 @@ namespace mcl {
|
||||||
|
|
||||||
/// Reinterpret objects of one type as another by bit-casting between object representations.
|
/// Reinterpret objects of one type as another by bit-casting between object representations.
|
||||||
template<class Dest, class Source>
|
template<class Dest, class Source>
|
||||||
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(sizeof(Dest) == sizeof(Source), "size of destination and source objects must be equal");
|
||||||
static_assert(std::is_trivially_copyable_v<Dest>, "destination type must be trivially copyable.");
|
static_assert(std::is_trivially_copyable_v<Dest>, "destination type must be trivially copyable.");
|
||||||
static_assert(std::is_trivially_copyable_v<Source>, "source type must be trivially copyable");
|
static_assert(std::is_trivially_copyable_v<Source>, "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.
|
/// 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.
|
/// Note that here we do not verify if source pointed to by source_ptr has enough bytes to read from.
|
||||||
template<class Dest, class SourcePtr>
|
template<class Dest, class SourcePtr>
|
||||||
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(sizeof(SourcePtr) == sizeof(void*), "source pointer must have size of a pointer");
|
||||||
static_assert(std::is_trivially_copyable_v<Dest>, "destination type must be trivially copyable.");
|
static_assert(std::is_trivially_copyable_v<Dest>, "destination type must be trivially copyable.");
|
||||||
|
|
||||||
|
|
35
include/mcl/container/detail/meta_byte.hpp
Normal file
35
include/mcl/container/detail/meta_byte.hpp
Normal file
|
@ -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<u8>(mb) & 0x80) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline meta_byte meta_byte_from_hash(size_t hash)
|
||||||
|
{
|
||||||
|
return static_cast<meta_byte>(hash >> (bitsizeof<size_t> - 7));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t group_index_from_hash(size_t hash, size_t group_index_mask)
|
||||||
|
{
|
||||||
|
return hash & group_index_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mcl::detail
|
263
include/mcl/container/detail/meta_byte_group.hpp
Normal file
263
include/mcl/container/detail/meta_byte_group.hpp
Normal file
|
@ -0,0 +1,263 @@
|
||||||
|
// This file is part of the mcl project.
|
||||||
|
// Copyright (c) 2022 merryhime
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <bit>
|
||||||
|
|
||||||
|
#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 <arm_neon.h>
|
||||||
|
#elif defined(MCL_ARCHITECTURE_X86_64)
|
||||||
|
# include <emmintrin.h>
|
||||||
|
|
||||||
|
# include "mcl/bit_cast.hpp"
|
||||||
|
#else
|
||||||
|
# include <cstring>
|
||||||
|
#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<u8*>(ptr))}
|
||||||
|
{}
|
||||||
|
|
||||||
|
explicit meta_byte_group(const std::array<meta_byte, 16>& array)
|
||||||
|
: data{vld1q_u8(reinterpret_cast<const u8*>(array.data()))}
|
||||||
|
{}
|
||||||
|
|
||||||
|
uint64x2_t match(meta_byte cmp) const
|
||||||
|
{
|
||||||
|
return vreinterpretq_u64_u8(vandq_u8(vceqq_u8(data,
|
||||||
|
vdupq_n_u8(static_cast<u8>(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<meta_byte>(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<meta_byte>(data[index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(size_t index, meta_byte value)
|
||||||
|
{
|
||||||
|
data[index] = static_cast<u8>(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<size_t>(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<size_t>(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<size_t>(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<size_t>(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<meta_byte, 16>& 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<u8>(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<std::array<meta_byte, max_group_size>>(data)[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(size_t index, meta_byte value)
|
||||||
|
{
|
||||||
|
auto array = mcl::bit_cast<std::array<meta_byte, max_group_size>>(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<size_t>(std::countr_zero(match_result))}; \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
# define MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(MATCH, ...) \
|
||||||
|
{ \
|
||||||
|
for (u16 match_result{static_cast<u16>((MATCH) & (0x7fff))}; match_result != 0; match_result &= match_result - 1) { \
|
||||||
|
const size_t match_index{static_cast<size_t>(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<meta_byte, 16>& array)
|
||||||
|
: data{array}
|
||||||
|
{}
|
||||||
|
|
||||||
|
std::array<u64, 2> match(meta_byte cmp) const
|
||||||
|
{
|
||||||
|
DEBUG_ASSERT(is_full(cmp));
|
||||||
|
|
||||||
|
const u64 vcmp{lsb * static_cast<u64>(cmp)};
|
||||||
|
return {(msb - ((data[0] ^ vcmp) & not_msb)) & ~data[0] & msb, (msb - ((data[1] ^ vcmp) & not_msb)) & ~data[1] & msb};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<u64, 2> match_empty_or_tombstone() const
|
||||||
|
{
|
||||||
|
return {data[0] & msb, data[1] & msb};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_any_empty() const
|
||||||
|
{
|
||||||
|
static_assert((static_cast<u8>(meta_byte::empty) & 0xc0) == 0xc0);
|
||||||
|
static_assert((static_cast<u8>(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<std::array<meta_byte, max_group_size>>(data)[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(size_t index, meta_byte value)
|
||||||
|
{
|
||||||
|
auto array = mcl::bit_cast<std::array<meta_byte, max_group_size>>(data);
|
||||||
|
array[index] = value;
|
||||||
|
data = mcl::bit_cast<std::array<u64, 2>>(array);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::array<u64, 2> data;
|
||||||
|
};
|
||||||
|
|
||||||
|
# define MCL_HMAP_MATCH_META_BYTE_GROUP(MATCH, ...) \
|
||||||
|
{ \
|
||||||
|
const std::array<u64, 2> 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<size_t>(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<size_t>(8 + std::countr_zero(match_result_v) / 8)}; \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
# define MCL_HMAP_MATCH_META_BYTE_GROUP_EXCEPT_LAST(MATCH, ...) \
|
||||||
|
{ \
|
||||||
|
const std::array<u64, 2> 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<size_t>(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<size_t>(8 + std::countr_zero(match_result_v) / 8)}; \
|
||||||
|
__VA_ARGS__ \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
} // namespace mcl::detail
|
16
include/mcl/container/detail/slot_union.hpp
Normal file
16
include/mcl/container/detail/slot_union.hpp
Normal file
|
@ -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<typename ValueType>
|
||||||
|
union slot_union {
|
||||||
|
slot_union() {}
|
||||||
|
~slot_union() {}
|
||||||
|
ValueType value;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mcl::detail
|
532
include/mcl/container/hmap.hpp
Normal file
532
include/mcl/container/hmap.hpp
Normal file
|
@ -0,0 +1,532 @@
|
||||||
|
// This file is part of the mcl project.
|
||||||
|
// Copyright (c) 2022 merryhime
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#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<typename KeyType, typename MappedType, typename Hash, typename Pred>
|
||||||
|
class hmap;
|
||||||
|
|
||||||
|
template<bool IsConst, typename KeyType, typename MappedType, typename Hash, typename Pred>
|
||||||
|
class hmap_iterator {
|
||||||
|
using base_value_type = std::pair<const KeyType, MappedType>;
|
||||||
|
using slot_type = detail::slot_union<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<IsConst, std::add_const_t<base_value_type>, 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<reference>(slot_ptr->value);
|
||||||
|
}
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
return std::addressof(operator*());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class hmap<KeyType, MappedType, Hash, Pred>;
|
||||||
|
|
||||||
|
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 KeyType, typename MappedType, typename Hash = hash::avalanche_xmrx<KeyType>, typename Pred = std::equal_to<KeyType>>
|
||||||
|
class hmap {
|
||||||
|
public:
|
||||||
|
using key_type = KeyType;
|
||||||
|
using mapped_type = MappedType;
|
||||||
|
using hasher = Hash;
|
||||||
|
using key_equal = Pred;
|
||||||
|
using value_type = std::pair<const key_type, mapped_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 = hmap_iterator<false, key_type, mapped_type, hasher, key_equal>;
|
||||||
|
using const_iterator = hmap_iterator<true, key_type, mapped_type, hasher, key_equal>;
|
||||||
|
|
||||||
|
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<value_type>;
|
||||||
|
using slot_ptr = std::unique_ptr<slot_type[]>;
|
||||||
|
using meta_byte_ptr = overaligned_unique_ptr<group_size, detail::meta_byte[]>;
|
||||||
|
static_assert(!std::is_reference_v<key_type>);
|
||||||
|
static_assert(!std::is_reference_v<mapped_type>);
|
||||||
|
|
||||||
|
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<size_type>(std::numeric_limits<difference_type>::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<typename K = key_type, typename... Args>
|
||||||
|
std::pair<iterator, bool> 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>(k)),
|
||||||
|
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
return {iterator_at(item_index), !item_found};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename K = key_type, typename V = mapped_type>
|
||||||
|
std::pair<iterator, bool> 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>(v);
|
||||||
|
} else {
|
||||||
|
new (&slots[item_index].value) value_type(
|
||||||
|
std::forward<K>(k),
|
||||||
|
std::forward<V>(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::size_t>(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::size_t>(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<typename K = key_type>
|
||||||
|
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<typename K = key_type>
|
||||||
|
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<typename K = key_type>
|
||||||
|
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<typename K = key_type>
|
||||||
|
bool contains(const K& key) const
|
||||||
|
{
|
||||||
|
return find(key) != end();
|
||||||
|
}
|
||||||
|
template<typename K = key_type>
|
||||||
|
size_t count(const K& key) const
|
||||||
|
{
|
||||||
|
return contains(key) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename K = key_type>
|
||||||
|
mapped_type& operator[](K&& k)
|
||||||
|
{
|
||||||
|
return try_emplace(std::forward<K>(k)).first->second;
|
||||||
|
}
|
||||||
|
template<typename K = key_type>
|
||||||
|
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<typename K = key_type>
|
||||||
|
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<std::size_t, bool> 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_size, detail::meta_byte>(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<int>(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
|
549
include/mcl/container/ihmap.hpp
Normal file
549
include/mcl/container/ihmap.hpp
Normal file
|
@ -0,0 +1,549 @@
|
||||||
|
// This file is part of the mcl project.
|
||||||
|
// Copyright (c) 2022 merryhime
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <functional>
|
||||||
|
#include <limits>
|
||||||
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
#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<typename KeyType, typename MappedType, typename Hash, typename Pred>
|
||||||
|
class ihmap;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
constexpr std::array<meta_byte, 16> 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<typename KeyType, typename MappedType>
|
||||||
|
struct ihmap_group {
|
||||||
|
using base_value_type = std::pair<const KeyType, MappedType>;
|
||||||
|
using slot_type = detail::slot_union<base_value_type>;
|
||||||
|
|
||||||
|
static constexpr std::size_t group_size{meta_byte_group::max_group_size - 1};
|
||||||
|
|
||||||
|
meta_byte_group meta{ihmap_default_meta};
|
||||||
|
std::array<slot_type, group_size> slots{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template<bool IsConst, typename KeyType, typename MappedType, typename Hash, typename Pred>
|
||||||
|
class ihmap_iterator {
|
||||||
|
using group_type = detail::ihmap_group<KeyType, MappedType>;
|
||||||
|
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<IsConst, std::add_const_t<base_value_type>, 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<reference>(group_ptr->slots[slot_index].value);
|
||||||
|
}
|
||||||
|
pointer operator->() const
|
||||||
|
{
|
||||||
|
return std::addressof(operator*());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class ihmap<KeyType, MappedType, Hash, Pred>;
|
||||||
|
|
||||||
|
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 KeyType, typename MappedType, typename Hash = hash::avalanche_xmrx<KeyType>, typename Pred = std::equal_to<KeyType>>
|
||||||
|
class ihmap {
|
||||||
|
using group_type = detail::ihmap_group<KeyType, MappedType>;
|
||||||
|
|
||||||
|
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<false, key_type, mapped_type, hasher, key_equal>;
|
||||||
|
using const_iterator = ihmap_iterator<true, key_type, mapped_type, hasher, key_equal>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static_assert(!std::is_reference_v<key_type>);
|
||||||
|
static_assert(!std::is_reference_v<mapped_type>);
|
||||||
|
|
||||||
|
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<size_type>(std::numeric_limits<difference_type>::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<typename K = key_type, typename... Args>
|
||||||
|
std::pair<iterator, bool> 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>(k)),
|
||||||
|
std::forward_as_tuple(std::forward<Args>(args)...));
|
||||||
|
}
|
||||||
|
return {iterator_at(pos), !item_found};
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename K = key_type, typename V = mapped_type>
|
||||||
|
std::pair<iterator, bool> 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>(v);
|
||||||
|
} else {
|
||||||
|
new (&groups[pos.group_index].slots[pos.slot_index].value) value_type(
|
||||||
|
std::forward<K>(k),
|
||||||
|
std::forward<V>(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::size_t>(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::size_t>(std::distance(groups.get(), iter.group_ptr))};
|
||||||
|
|
||||||
|
erase_impl({group_index, iter.slot_index});
|
||||||
|
}
|
||||||
|
template<typename K = key_type>
|
||||||
|
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<typename K = key_type>
|
||||||
|
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<typename K = key_type>
|
||||||
|
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<typename K = key_type>
|
||||||
|
bool contains(const K& key) const
|
||||||
|
{
|
||||||
|
return find(key) != end();
|
||||||
|
}
|
||||||
|
template<typename K = key_type>
|
||||||
|
std::size_t count(const K& key) const
|
||||||
|
{
|
||||||
|
return contains(key) ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename K = key_type>
|
||||||
|
mapped_type& operator[](K&& k)
|
||||||
|
{
|
||||||
|
return try_emplace(std::forward<K>(k)).first->second;
|
||||||
|
}
|
||||||
|
template<typename K = key_type>
|
||||||
|
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<typename K = key_type>
|
||||||
|
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<position, bool> 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<group_type[]>{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<group_type[]> groups;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mcl
|
|
@ -21,7 +21,8 @@ class intrusive_list_iterator;
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class intrusive_list_node {
|
class intrusive_list_node {
|
||||||
public:
|
public:
|
||||||
bool is_sentinel() const {
|
bool is_sentinel() const
|
||||||
|
{
|
||||||
return is_sentinel_;
|
return is_sentinel_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +43,8 @@ class intrusive_list_sentinel final : public intrusive_list_node<T> {
|
||||||
using intrusive_list_node<T>::is_sentinel_;
|
using intrusive_list_node<T>::is_sentinel_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
intrusive_list_sentinel() {
|
intrusive_list_sentinel()
|
||||||
|
{
|
||||||
next = this;
|
next = this;
|
||||||
prev = this;
|
prev = this;
|
||||||
is_sentinel_ = true;
|
is_sentinel_ = true;
|
||||||
|
@ -72,50 +74,56 @@ public:
|
||||||
intrusive_list_iterator& operator=(const intrusive_list_iterator& other) = default;
|
intrusive_list_iterator& operator=(const intrusive_list_iterator& other) = default;
|
||||||
|
|
||||||
explicit intrusive_list_iterator(node_pointer list_node)
|
explicit intrusive_list_iterator(node_pointer list_node)
|
||||||
: node(list_node) {
|
: node(list_node) {}
|
||||||
}
|
|
||||||
explicit intrusive_list_iterator(pointer data)
|
explicit intrusive_list_iterator(pointer data)
|
||||||
: node(data) {
|
: node(data) {}
|
||||||
}
|
|
||||||
explicit intrusive_list_iterator(reference data)
|
explicit intrusive_list_iterator(reference data)
|
||||||
: node(&data) {
|
: node(&data) {}
|
||||||
}
|
|
||||||
|
|
||||||
intrusive_list_iterator& operator++() {
|
intrusive_list_iterator& operator++()
|
||||||
|
{
|
||||||
node = node->next;
|
node = node->next;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
intrusive_list_iterator& operator--() {
|
intrusive_list_iterator& operator--()
|
||||||
|
{
|
||||||
node = node->prev;
|
node = node->prev;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
intrusive_list_iterator operator++(int) {
|
intrusive_list_iterator operator++(int)
|
||||||
|
{
|
||||||
intrusive_list_iterator it(*this);
|
intrusive_list_iterator it(*this);
|
||||||
++*this;
|
++*this;
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
intrusive_list_iterator operator--(int) {
|
intrusive_list_iterator operator--(int)
|
||||||
|
{
|
||||||
intrusive_list_iterator it(*this);
|
intrusive_list_iterator it(*this);
|
||||||
--*this;
|
--*this;
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool operator==(const intrusive_list_iterator& other) const {
|
bool operator==(const intrusive_list_iterator& other) const
|
||||||
|
{
|
||||||
return node == other.node;
|
return node == other.node;
|
||||||
}
|
}
|
||||||
bool operator!=(const intrusive_list_iterator& other) const {
|
bool operator!=(const intrusive_list_iterator& other) const
|
||||||
|
{
|
||||||
return !operator==(other);
|
return !operator==(other);
|
||||||
}
|
}
|
||||||
|
|
||||||
reference operator*() const {
|
reference operator*() const
|
||||||
|
{
|
||||||
DEBUG_ASSERT(!node->is_sentinel());
|
DEBUG_ASSERT(!node->is_sentinel());
|
||||||
return static_cast<reference>(*node);
|
return static_cast<reference>(*node);
|
||||||
}
|
}
|
||||||
pointer operator->() const {
|
pointer operator->() const
|
||||||
|
{
|
||||||
return std::addressof(operator*());
|
return std::addressof(operator*());
|
||||||
}
|
}
|
||||||
|
|
||||||
node_pointer AsNodePointer() const {
|
node_pointer AsNodePointer() const
|
||||||
|
{
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -145,7 +153,8 @@ public:
|
||||||
* @param location The location to insert the node.
|
* @param location The location to insert the node.
|
||||||
* @param new_node The node to add.
|
* @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);
|
return insert_before(location, new_node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +165,8 @@ public:
|
||||||
* @param location The location to insert the new node.
|
* @param location The location to insert the new node.
|
||||||
* @param new_node The node to insert into the list.
|
* @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();
|
auto existing_node = location.AsNodePointer();
|
||||||
|
|
||||||
new_node->next = existing_node;
|
new_node->next = existing_node;
|
||||||
|
@ -173,7 +183,8 @@ public:
|
||||||
* @param position Location to insert the node in front of.
|
* @param position Location to insert the node in front of.
|
||||||
* @param new_node The node to be inserted into the list.
|
* @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())
|
if (empty())
|
||||||
return insert(begin(), new_node);
|
return insert(begin(), new_node);
|
||||||
|
|
||||||
|
@ -184,7 +195,8 @@ public:
|
||||||
* Add an entry to the start of the list.
|
* Add an entry to the start of the list.
|
||||||
* @param node Node to add to the list.
|
* @param node Node to add to the list.
|
||||||
*/
|
*/
|
||||||
void push_front(pointer node) {
|
void push_front(pointer node)
|
||||||
|
{
|
||||||
insert(begin(), node);
|
insert(begin(), node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +204,8 @@ public:
|
||||||
* Add an entry to the end of the list
|
* Add an entry to the end of the list
|
||||||
* @param node Node to add to the list.
|
* @param node Node to add to the list.
|
||||||
*/
|
*/
|
||||||
void push_back(pointer node) {
|
void push_back(pointer node)
|
||||||
|
{
|
||||||
insert(end(), node);
|
insert(end(), node);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -200,7 +213,8 @@ public:
|
||||||
* Erases the node at the front of the list.
|
* Erases the node at the front of the list.
|
||||||
* @note Must not be called on an empty list.
|
* @note Must not be called on an empty list.
|
||||||
*/
|
*/
|
||||||
void pop_front() {
|
void pop_front()
|
||||||
|
{
|
||||||
DEBUG_ASSERT(!empty());
|
DEBUG_ASSERT(!empty());
|
||||||
erase(begin());
|
erase(begin());
|
||||||
}
|
}
|
||||||
|
@ -209,7 +223,8 @@ public:
|
||||||
* Erases the node at the back of the list.
|
* Erases the node at the back of the list.
|
||||||
* @note Must not be called on an empty list.
|
* @note Must not be called on an empty list.
|
||||||
*/
|
*/
|
||||||
void pop_back() {
|
void pop_back()
|
||||||
|
{
|
||||||
DEBUG_ASSERT(!empty());
|
DEBUG_ASSERT(!empty());
|
||||||
erase(--end());
|
erase(--end());
|
||||||
}
|
}
|
||||||
|
@ -218,7 +233,8 @@ public:
|
||||||
* Removes a node from this list
|
* Removes a node from this list
|
||||||
* @param it An iterator that points to the node to remove from 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());
|
DEBUG_ASSERT(it != end());
|
||||||
|
|
||||||
pointer node = &*it++;
|
pointer node = &*it++;
|
||||||
|
@ -237,7 +253,8 @@ public:
|
||||||
* Removes a node from this list
|
* Removes a node from this list
|
||||||
* @param it A constant iterator that points to the node to remove from 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;
|
iterator copy = it;
|
||||||
return remove(copy);
|
return remove(copy);
|
||||||
}
|
}
|
||||||
|
@ -246,7 +263,8 @@ public:
|
||||||
* Removes a node from this list.
|
* Removes a node from this list.
|
||||||
* @param node A pointer to the node to remove.
|
* @param node A pointer to the node to remove.
|
||||||
*/
|
*/
|
||||||
pointer remove(pointer node) {
|
pointer remove(pointer node)
|
||||||
|
{
|
||||||
return remove(iterator(node));
|
return remove(iterator(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -254,7 +272,8 @@ public:
|
||||||
* Removes a node from this list.
|
* Removes a node from this list.
|
||||||
* @param node A reference to the node to remove.
|
* @param node A reference to the node to remove.
|
||||||
*/
|
*/
|
||||||
pointer remove(reference node) {
|
pointer remove(reference node)
|
||||||
|
{
|
||||||
return remove(iterator(node));
|
return remove(iterator(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,7 +281,8 @@ public:
|
||||||
* Is this list empty?
|
* Is this list empty?
|
||||||
* @returns true if there are no nodes in this list.
|
* @returns true if there are no nodes in this list.
|
||||||
*/
|
*/
|
||||||
bool empty() const {
|
bool empty() const
|
||||||
|
{
|
||||||
return root->next == root.get();
|
return root->next == root.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,7 +290,8 @@ public:
|
||||||
* Gets the total number of elements within this list.
|
* Gets the total number of elements within this list.
|
||||||
* @return the number of elements in this list.
|
* @return the number of elements in this list.
|
||||||
*/
|
*/
|
||||||
size_type size() const {
|
size_type size() const
|
||||||
|
{
|
||||||
return static_cast<size_type>(std::distance(begin(), end()));
|
return static_cast<size_type>(std::distance(begin(), end()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -278,7 +299,8 @@ public:
|
||||||
* Retrieves a reference to the node at the front of the list.
|
* Retrieves a reference to the node at the front of the list.
|
||||||
* @note Must not be called on an empty list.
|
* @note Must not be called on an empty list.
|
||||||
*/
|
*/
|
||||||
reference front() {
|
reference front()
|
||||||
|
{
|
||||||
DEBUG_ASSERT(!empty());
|
DEBUG_ASSERT(!empty());
|
||||||
return *begin();
|
return *begin();
|
||||||
}
|
}
|
||||||
|
@ -287,7 +309,8 @@ public:
|
||||||
* Retrieves a constant reference to the node at the front of the list.
|
* Retrieves a constant reference to the node at the front of the list.
|
||||||
* @note Must not be called on an empty list.
|
* @note Must not be called on an empty list.
|
||||||
*/
|
*/
|
||||||
const_reference front() const {
|
const_reference front() const
|
||||||
|
{
|
||||||
DEBUG_ASSERT(!empty());
|
DEBUG_ASSERT(!empty());
|
||||||
return *begin();
|
return *begin();
|
||||||
}
|
}
|
||||||
|
@ -296,7 +319,8 @@ public:
|
||||||
* Retrieves a reference to the node at the back of the list.
|
* Retrieves a reference to the node at the back of the list.
|
||||||
* @note Must not be called on an empty list.
|
* @note Must not be called on an empty list.
|
||||||
*/
|
*/
|
||||||
reference back() {
|
reference back()
|
||||||
|
{
|
||||||
DEBUG_ASSERT(!empty());
|
DEBUG_ASSERT(!empty());
|
||||||
return *--end();
|
return *--end();
|
||||||
}
|
}
|
||||||
|
@ -305,7 +329,8 @@ public:
|
||||||
* Retrieves a constant reference to the node at the back of the list.
|
* Retrieves a constant reference to the node at the back of the list.
|
||||||
* @note Must not be called on an empty list.
|
* @note Must not be called on an empty list.
|
||||||
*/
|
*/
|
||||||
const_reference back() const {
|
const_reference back() const
|
||||||
|
{
|
||||||
DEBUG_ASSERT(!empty());
|
DEBUG_ASSERT(!empty());
|
||||||
return *--end();
|
return *--end();
|
||||||
}
|
}
|
||||||
|
@ -331,7 +356,8 @@ public:
|
||||||
* Erases a node from the list, indicated by an iterator.
|
* Erases a node from the list, indicated by an iterator.
|
||||||
* @param it The iterator that points to the node to erase.
|
* @param it The iterator that points to the node to erase.
|
||||||
*/
|
*/
|
||||||
iterator erase(iterator it) {
|
iterator erase(iterator it)
|
||||||
|
{
|
||||||
remove(it);
|
remove(it);
|
||||||
return it;
|
return it;
|
||||||
}
|
}
|
||||||
|
@ -340,7 +366,8 @@ public:
|
||||||
* Erases a node from this list.
|
* Erases a node from this list.
|
||||||
* @param node A pointer to the node to erase 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));
|
return erase(iterator(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -348,7 +375,8 @@ public:
|
||||||
* Erases a node from this list.
|
* Erases a node from this list.
|
||||||
* @param node A reference to the node to erase 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));
|
return erase(iterator(node));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -356,7 +384,8 @@ public:
|
||||||
* Exchanges contents of this list with another list instance.
|
* Exchanges contents of this list with another list instance.
|
||||||
* @param other The other list to swap with.
|
* @param other The other list to swap with.
|
||||||
*/
|
*/
|
||||||
void swap(intrusive_list& other) noexcept {
|
void swap(intrusive_list& other) noexcept
|
||||||
|
{
|
||||||
root.swap(other.root);
|
root.swap(other.root);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -371,7 +400,8 @@ private:
|
||||||
* @param rhs The second list.
|
* @param rhs The second list.
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template<typename T>
|
||||||
void swap(intrusive_list<T>& lhs, intrusive_list<T>& rhs) noexcept {
|
void swap(intrusive_list<T>& lhs, intrusive_list<T>& rhs) noexcept
|
||||||
|
{
|
||||||
lhs.swap(rhs);
|
lhs.swap(rhs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
32
include/mcl/hash/xmrx.hpp
Normal file
32
include/mcl/hash/xmrx.hpp
Normal file
|
@ -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 <functional>
|
||||||
|
|
||||||
|
#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<typename T>
|
||||||
|
struct avalanche_xmrx {
|
||||||
|
size_t operator()(const T& value)
|
||||||
|
{
|
||||||
|
return xmrx(std::hash<T>{}(value));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace mcl::hash
|
|
@ -13,12 +13,14 @@ template<typename T>
|
||||||
struct reverse_adapter {
|
struct reverse_adapter {
|
||||||
T& iterable;
|
T& iterable;
|
||||||
|
|
||||||
constexpr auto begin() {
|
constexpr auto begin()
|
||||||
|
{
|
||||||
using namespace std;
|
using namespace std;
|
||||||
return rbegin(iterable);
|
return rbegin(iterable);
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr auto end() {
|
constexpr auto end()
|
||||||
|
{
|
||||||
using namespace std;
|
using namespace std;
|
||||||
return rend(iterable);
|
return rend(iterable);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +29,8 @@ struct reverse_adapter {
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
template<typename T>
|
template<typename T>
|
||||||
constexpr detail::reverse_adapter<T> reverse(T&& iterable) {
|
constexpr detail::reverse_adapter<T> reverse(T&& iterable)
|
||||||
|
{
|
||||||
return detail::reverse_adapter<T>{iterable};
|
return detail::reverse_adapter<T>{iterable};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
46
include/mcl/memory/overaligned_unique_ptr.hpp
Normal file
46
include/mcl/memory/overaligned_unique_ptr.hpp
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
// This file is part of the mcl project.
|
||||||
|
// Copyright (c) 2022 merryhime
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
# include <malloc.h>
|
||||||
|
#else
|
||||||
|
# include <cstdlib>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace mcl {
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
struct aligned_alloc_deleter {
|
||||||
|
template<typename T>
|
||||||
|
void operator()(T* p) const
|
||||||
|
{
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
_aligned_free(const_cast<std::remove_const_t<T>*>(p));
|
||||||
|
#else
|
||||||
|
std::free(const_cast<std::remove_const_t<T>*>(p));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template<size_t, typename T>
|
||||||
|
using overaligned_unique_ptr = std::unique_ptr<T, detail::aligned_alloc_deleter>;
|
||||||
|
|
||||||
|
template<size_t alignment, typename T>
|
||||||
|
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<alignment, T[]>{static_cast<T*>(_aligned_malloc(alloc_size, alignment))};
|
||||||
|
#else
|
||||||
|
return overaligned_unique_ptr<alignment, T[]>{static_cast<T*>(std::aligned_alloc(alignment, alloc_size))};
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace mcl
|
|
@ -21,7 +21,9 @@ class scope_exit final {
|
||||||
public:
|
public:
|
||||||
explicit scope_exit(Function&& fn)
|
explicit scope_exit(Function&& fn)
|
||||||
: function(std::move(fn)) {}
|
: function(std::move(fn)) {}
|
||||||
~scope_exit() noexcept {
|
|
||||||
|
~scope_exit() noexcept
|
||||||
|
{
|
||||||
function();
|
function();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +36,9 @@ class scope_fail final {
|
||||||
public:
|
public:
|
||||||
explicit scope_fail(Function&& fn)
|
explicit scope_fail(Function&& fn)
|
||||||
: function(std::move(fn)), exception_count(std::uncaught_exceptions()) {}
|
: function(std::move(fn)), exception_count(std::uncaught_exceptions()) {}
|
||||||
~scope_fail() noexcept {
|
|
||||||
|
~scope_fail() noexcept
|
||||||
|
{
|
||||||
if (std::uncaught_exceptions() > exception_count) {
|
if (std::uncaught_exceptions() > exception_count) {
|
||||||
function();
|
function();
|
||||||
}
|
}
|
||||||
|
@ -50,7 +54,9 @@ class scope_success final {
|
||||||
public:
|
public:
|
||||||
explicit scope_success(Function&& fn)
|
explicit scope_success(Function&& fn)
|
||||||
: function(std::move(fn)), exception_count(std::uncaught_exceptions()) {}
|
: function(std::move(fn)), exception_count(std::uncaught_exceptions()) {}
|
||||||
~scope_success() {
|
|
||||||
|
~scope_success()
|
||||||
|
{
|
||||||
if (std::uncaught_exceptions() <= exception_count) {
|
if (std::uncaught_exceptions() <= exception_count) {
|
||||||
function();
|
function();
|
||||||
}
|
}
|
||||||
|
@ -64,17 +70,20 @@ private:
|
||||||
// We use ->* here as it has the highest precedence of the operators we can use.
|
// We use ->* here as it has the highest precedence of the operators we can use.
|
||||||
|
|
||||||
template<typename Function>
|
template<typename Function>
|
||||||
auto operator->*(scope_exit_tag, Function&& function) {
|
auto operator->*(scope_exit_tag, Function&& function)
|
||||||
|
{
|
||||||
return scope_exit<std::decay_t<Function>>{std::forward<Function>(function)};
|
return scope_exit<std::decay_t<Function>>{std::forward<Function>(function)};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Function>
|
template<typename Function>
|
||||||
auto operator->*(scope_fail_tag, Function&& function) {
|
auto operator->*(scope_fail_tag, Function&& function)
|
||||||
|
{
|
||||||
return scope_fail<std::decay_t<Function>>{std::forward<Function>(function)};
|
return scope_fail<std::decay_t<Function>>{std::forward<Function>(function)};
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename Function>
|
template<typename Function>
|
||||||
auto operator->*(scope_success_tag, Function&& function) {
|
auto operator->*(scope_success_tag, Function&& function)
|
||||||
|
{
|
||||||
return scope_success<std::decay_t<Function>>{std::forward<Function>(function)};
|
return scope_success<std::decay_t<Function>>{std::forward<Function>(function)};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,9 +11,11 @@
|
||||||
|
|
||||||
namespace mcl::detail {
|
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)
|
||||||
fmt::print(stderr, "assertion failed: ");
|
{
|
||||||
|
fmt::print(stderr, "assertion failed: {}\nMessage:", expr_str);
|
||||||
fmt::vprint(stderr, msg, args);
|
fmt::vprint(stderr, msg, args);
|
||||||
|
std::fflush(stderr);
|
||||||
std::terminate();
|
std::terminate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,14 @@
|
||||||
add_executable(mcl-tests
|
add_executable(mcl-tests
|
||||||
bit/bit_field_tests.cpp
|
bit/bit_field_tests.cpp
|
||||||
main.cpp
|
container/hmap.cpp
|
||||||
|
container/ihmap.cpp
|
||||||
mp/metavalue_tests.cpp
|
mp/metavalue_tests.cpp
|
||||||
mp/typelist_tests.cpp
|
mp/typelist_tests.cpp
|
||||||
type_traits/type_traits_tests.cpp
|
type_traits/type_traits_tests.cpp
|
||||||
)
|
)
|
||||||
target_include_directories(mcl-tests PUBLIC .)
|
target_include_directories(mcl-tests PUBLIC .)
|
||||||
target_compile_options(mcl-tests PRIVATE ${STAMINA_CXX_FLAGS})
|
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(CTest)
|
||||||
include(Catch)
|
include(Catch)
|
||||||
|
|
|
@ -5,11 +5,12 @@
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
|
|
||||||
#include <catch2/catch.hpp>
|
#include <catch2/catch_test_macros.hpp>
|
||||||
#include <mcl/bit/bit_field.hpp>
|
#include <mcl/bit/bit_field.hpp>
|
||||||
#include <mcl/stdint.hpp>
|
#include <mcl/stdint.hpp>
|
||||||
|
|
||||||
TEST_CASE("mcl::bit::ones", "[bit]") {
|
TEST_CASE("mcl::bit::ones", "[bit]")
|
||||||
|
{
|
||||||
const std::array cases{
|
const std::array cases{
|
||||||
std::make_tuple<size_t, u8>(0, 0x00),
|
std::make_tuple<size_t, u8>(0, 0x00),
|
||||||
std::make_tuple<size_t, u8>(1, 0x01),
|
std::make_tuple<size_t, u8>(1, 0x01),
|
||||||
|
|
66
tests/container/hmap.cpp
Normal file
66
tests/container/hmap.cpp
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// This file is part of the mcl project.
|
||||||
|
// Copyright (c) 2022 merryhime
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <mcl/container/hmap.hpp>
|
||||||
|
#include <mcl/stdint.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("mcl::hmap", "[hmap]")
|
||||||
|
{
|
||||||
|
mcl::hmap<u64, u64> 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<u64, size_t> 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);
|
||||||
|
}
|
||||||
|
}
|
66
tests/container/ihmap.cpp
Normal file
66
tests/container/ihmap.cpp
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
// This file is part of the mcl project.
|
||||||
|
// Copyright (c) 2022 merryhime
|
||||||
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <catch2/catch_test_macros.hpp>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <mcl/container/ihmap.hpp>
|
||||||
|
#include <mcl/stdint.hpp>
|
||||||
|
|
||||||
|
TEST_CASE("mcl::ihmap", "[ihmap]")
|
||||||
|
{
|
||||||
|
mcl::ihmap<u64, u64> 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<u64, size_t> 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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"
|
|
Loading…
Reference in a new issue