| // Copyright 2021 The Dawn & Tint Authors | |
| // | |
| // Redistribution and use in source and binary forms, with or without | |
| // modification, are permitted provided that the following conditions are met: | |
| // | |
| // 1. Redistributions of source code must retain the above copyright notice, this | |
| // list of conditions and the following disclaimer. | |
| // | |
| // 2. Redistributions in binary form must reproduce the above copyright notice, | |
| // this list of conditions and the following disclaimer in the documentation | |
| // and/or other materials provided with the distribution. | |
| // | |
| // 3. Neither the name of the copyright holder nor the names of its | |
| // contributors may be used to endorse or promote products derived from | |
| // this software without specific prior written permission. | |
| // | |
| // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" | |
| // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | |
| // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | |
| // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE | |
| // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | |
| // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR | |
| // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER | |
| // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, | |
| // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | |
| // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
| #ifndef SRC_TINT_UTILS_MATH_HASH_H_ | |
| #define SRC_TINT_UTILS_MATH_HASH_H_ | |
| #include <stdint.h> | |
| #include <cstdio> | |
| #include <functional> | |
| #include <optional> | |
| #include <string> | |
| #include <tuple> | |
| #include <type_traits> | |
| #include <unordered_map> | |
| #include <unordered_set> | |
| #include <utility> | |
| #include <variant> | |
| #include <vector> | |
| #include "src/tint/utils/math/crc32.h" | |
| namespace tint { | |
| namespace detail { | |
| template <typename T, typename = void> | |
| struct HasHashCodeMember : std::false_type {}; | |
| template <typename T> | |
| struct HasHashCodeMember< | |
| T, | |
| std::enable_if_t<std::is_member_function_pointer_v<decltype(&T::HashCode)>>> : std::true_type { | |
| }; | |
| } // namespace detail | |
| /// The type of a hash code | |
| using HashCode = uint32_t; | |
| /// Forward declarations (see below) | |
| template <typename... ARGS> | |
| HashCode Hash(const ARGS&... values); | |
| template <typename... ARGS> | |
| HashCode HashCombine(HashCode hash, const ARGS&... values); | |
| /// A STL-compatible hasher that does a more thorough job than most implementations of std::hash. | |
| /// Hasher has been optimized for a better quality hash at the expense of increased computation | |
| /// costs. | |
| /// Hasher is specialized for various core Tint data types. The default implementation will use a | |
| /// `HashCode HashCode()` method on the `T` type, and will fallback to `std::hash<T>` if | |
| /// `T::HashCode` is missing. | |
| template <typename T> | |
| struct Hasher { | |
| /// @param value the value to hash | |
| /// @returns a hash of the value | |
| HashCode operator()(const T& value) const { | |
| if constexpr (detail::HasHashCodeMember<T>::value) { | |
| auto hash = value.HashCode(); | |
| static_assert(std::is_same_v<decltype(hash), HashCode>, | |
| "T::HashCode() must return HashCode"); | |
| return hash; | |
| } else { | |
| return static_cast<HashCode>(std::hash<T>()(value)); | |
| } | |
| } | |
| }; | |
| /// Hasher specialization for pointers | |
| template <typename T> | |
| struct Hasher<T*> { | |
| /// @param ptr the pointer to hash | |
| /// @returns a hash of the pointer | |
| HashCode operator()(T* ptr) const { | |
| auto hash = reinterpret_cast<uintptr_t>(ptr); | |
| #ifdef TINT_HASH_SEED | |
| hash ^= static_cast<uint32_t>(TINT_HASH_SEED); | |
| #endif | |
| if constexpr (sizeof(hash) > 4) { | |
| return static_cast<HashCode>(hash >> 4 | hash >> 32); | |
| } else { | |
| return static_cast<HashCode>(hash >> 4); | |
| } | |
| } | |
| }; | |
| /// Hasher specialization for std::vector | |
| template <typename T> | |
| struct Hasher<std::vector<T>> { | |
| /// @param vector the vector to hash | |
| /// @returns a hash of the vector | |
| HashCode operator()(const std::vector<T>& vector) const { | |
| auto hash = Hash(vector.size()); | |
| for (auto& el : vector) { | |
| hash = HashCombine(hash, el); | |
| } | |
| return hash; | |
| } | |
| }; | |
| /// Hasher specialization for std::tuple | |
| template <typename... TYPES> | |
| struct Hasher<std::tuple<TYPES...>> { | |
| /// @param tuple the tuple to hash | |
| /// @returns a hash of the tuple | |
| HashCode operator()(const std::tuple<TYPES...>& tuple) const { | |
| return std::apply(Hash<TYPES...>, tuple); | |
| } | |
| }; | |
| /// Hasher specialization for std::pair | |
| template <typename A, typename B> | |
| struct Hasher<std::pair<A, B>> { | |
| /// @param tuple the tuple to hash | |
| /// @returns a hash of the tuple | |
| HashCode operator()(const std::pair<A, B>& tuple) const { | |
| return std::apply(Hash<A, B>, tuple); | |
| } | |
| }; | |
| /// Hasher specialization for std::optional | |
| template <typename T> | |
| struct Hasher<std::optional<T>> { | |
| /// @param optional the std::optional to hash | |
| /// @returns a hash of the std::optional | |
| HashCode operator()(const std::optional<T>& optional) const { | |
| auto hash = Hash(optional.has_value()); | |
| if (optional.has_value()) { | |
| hash = HashCombine(hash, optional.value()); | |
| } | |
| return hash; | |
| } | |
| }; | |
| /// Hasher specialization for std::variant | |
| template <typename... TYPES> | |
| struct Hasher<std::variant<TYPES...>> { | |
| /// @param variant the variant to hash | |
| /// @returns a hash of the tuple | |
| HashCode operator()(const std::variant<TYPES...>& variant) const { | |
| return std::visit([](auto&& val) { return Hash(val); }, variant); | |
| } | |
| }; | |
| /// Hasher specialization for std::string, which also supports hashing of const char* and | |
| /// std::string_view without first constructing a std::string. | |
| template <> | |
| struct Hasher<std::string> { | |
| /// @param str the string to hash | |
| /// @returns a hash of the string | |
| HashCode operator()(const std::string& str) const { | |
| return static_cast<HashCode>(std::hash<std::string_view>()(std::string_view(str))); | |
| } | |
| /// @param str the string to hash | |
| /// @returns a hash of the string | |
| HashCode operator()(const char* str) const { | |
| return static_cast<HashCode>(std::hash<std::string_view>()(std::string_view(str))); | |
| } | |
| /// @param str the string to hash | |
| /// @returns a hash of the string | |
| HashCode operator()(const std::string_view& str) const { | |
| return static_cast<HashCode>(std::hash<std::string_view>()(str)); | |
| } | |
| }; | |
| /// Hasher specialization for std::unordered_map | |
| template <typename K, typename V> | |
| struct Hasher<std::unordered_map<K, V>> { | |
| /// @param map the std::unordered_map to hash | |
| /// @returns a hash of the map | |
| HashCode operator()(const std::unordered_map<K, V>& map) const { | |
| auto hash = Hash(map.size()); | |
| for (const auto& [key, value] : map) { | |
| // Use an XOR to ensure that the non-deterministic ordering of the map still produces | |
| // the same hash value for the same entries. | |
| hash ^= Hash(key, value); | |
| } | |
| return hash; | |
| } | |
| }; | |
| /// Hasher specialization for std::unordered_set | |
| template <typename K> | |
| struct Hasher<std::unordered_set<K>> { | |
| /// @param set the std::unordered_set to hash | |
| /// @returns a hash of the set | |
| HashCode operator()(const std::unordered_set<K>& set) const { | |
| auto hash = Hash(set.size()); | |
| for (const auto& key : set) { | |
| // Use an XOR to ensure that the non-deterministic ordering of the map still produces | |
| // the same hash value for the same entries. | |
| hash ^= Hash(key); | |
| } | |
| return hash; | |
| } | |
| }; | |
| /// @param args the arguments to hash | |
| /// @returns a hash of the variadic list of arguments. | |
| /// The returned hash is dependent on the order of the arguments. | |
| template <typename... ARGS> | |
| HashCode Hash(const ARGS&... args) { | |
| if constexpr (sizeof...(ARGS) == 0) { | |
| return 0; | |
| } else if constexpr (sizeof...(ARGS) == 1) { | |
| using T = std::tuple_element_t<0, std::tuple<ARGS...>>; | |
| return Hasher<T>()(args...); | |
| } else { | |
| HashCode hash = 102931; // seed with an arbitrary prime | |
| return HashCombine(hash, args...); | |
| } | |
| } | |
| /// @param hash the hash value to combine with | |
| /// @param values the values to hash | |
| /// @returns a hash of the variadic list of arguments. | |
| /// The returned hash is dependent on the order of the arguments. | |
| template <typename... ARGS> | |
| HashCode HashCombine(HashCode hash, const ARGS&... values) { | |
| #ifdef TINT_HASH_SEED | |
| constexpr uint32_t offset = 0x7f4a7c16 ^ static_cast<uint32_t>(TINT_HASH_SEED); | |
| #else | |
| constexpr uint32_t offset = 0x7f4a7c16; | |
| #endif | |
| ((hash ^= Hash(values) + (offset ^ (hash >> 2))), ...); | |
| return hash; | |
| } | |
| /// A STL-compatible equal_to implementation that specializes for types. | |
| template <typename T> | |
| struct EqualTo { | |
| /// @param lhs the left hand side value | |
| /// @param rhs the right hand side value | |
| /// @returns true if the two values are equal | |
| constexpr bool operator()(const T& lhs, const T& rhs) const { | |
| return std::equal_to<T>()(lhs, rhs); | |
| } | |
| }; | |
| /// A specialization for EqualTo for std::string, which supports additional comparision with | |
| /// std::string_view and const char*. | |
| template <> | |
| struct EqualTo<std::string> { | |
| /// @param lhs the left hand side value | |
| /// @param rhs the right hand side value | |
| /// @returns true if the two values are equal | |
| bool operator()(const std::string& lhs, const std::string& rhs) const { return lhs == rhs; } | |
| /// @param lhs the left hand side value | |
| /// @param rhs the right hand side value | |
| /// @returns true if the two values are equal | |
| bool operator()(const std::string& lhs, const char* rhs) const { return lhs == rhs; } | |
| /// @param lhs the left hand side value | |
| /// @param rhs the right hand side value | |
| /// @returns true if the two values are equal | |
| bool operator()(const std::string& lhs, std::string_view rhs) const { return lhs == rhs; } | |
| /// @param lhs the left hand side value | |
| /// @param rhs the right hand side value | |
| /// @returns true if the two values are equal | |
| bool operator()(const char* lhs, const std::string& rhs) const { return lhs == rhs; } | |
| /// @param lhs the left hand side value | |
| /// @param rhs the right hand side value | |
| /// @returns true if the two values are equal | |
| bool operator()(std::string_view lhs, const std::string& rhs) const { return lhs == rhs; } | |
| }; | |
| /// Wrapper for a hashable type enabling the wrapped value to be used as a key | |
| /// for an unordered_map or unordered_set. | |
| template <typename T> | |
| struct UnorderedKeyWrapper { | |
| /// The wrapped value | |
| T value; | |
| /// The hash of value | |
| HashCode hash; | |
| /// Constructor | |
| /// @param v the value to wrap | |
| explicit UnorderedKeyWrapper(const T& v) : value(v), hash(Hash(v)) {} | |
| /// Move constructor | |
| /// @param v the value to wrap | |
| explicit UnorderedKeyWrapper(T&& v) : value(std::move(v)), hash(Hash(value)) {} | |
| /// @returns true if this wrapper comes before other | |
| /// @param other the RHS of the operator | |
| bool operator<(const UnorderedKeyWrapper& other) const { return hash < other.hash; } | |
| /// @returns true if this wrapped value is equal to the other wrapped value | |
| /// @param other the RHS of the operator | |
| bool operator==(const UnorderedKeyWrapper& other) const { return value == other.value; } | |
| }; | |
| } // namespace tint | |
| namespace std { | |
| /// Custom std::hash specialization for tint::UnorderedKeyWrapper | |
| template <typename T> | |
| class hash<tint::UnorderedKeyWrapper<T>> { | |
| public: | |
| /// @param w the UnorderedKeyWrapper | |
| /// @return the hash value | |
| inline std::size_t operator()(const tint::UnorderedKeyWrapper<T>& w) const { return w.hash; } | |
| }; | |
| } // namespace std | |
| #endif // SRC_TINT_UTILS_MATH_HASH_H_ |