#ifndef SLANG_CORE_HASH_H #define SLANG_CORE_HASH_H #include "slang-math.h" #include "slang.h" #include #include #include namespace Slang { // // Types // // A fixed 64bit wide hash on all targets. typedef uint64_t HashCode64; typedef HashCode64 HashCode; // A fixed 32bit wide hash on all targets. typedef uint32_t HashCode32; // // Some helpers to determine which hash to use for a type // // Forward declare Hash template struct Hash; template constexpr static bool HasSlangHash = false; template constexpr static bool HasSlangHash< T, std::enable_if_t< std::is_convertible_v()).getHashCode()), HashCode64>>> = true; // Does the hashmap implementation provide a uniform hash for this type. template constexpr static bool HasWyhash = false; template constexpr static bool HasWyhash::is_avalanching> = true; // We want to have an associated type 'is_avalanching = void' iff we have a // hash with good uniformity, the two specializations here add that member // when appropriate (since we can't declare an associated type with // constexpr if or something terse like that) template struct DetectAvalanchingHash { }; template struct DetectAvalanchingHash>> { using is_avalanching = void; }; // Have we marked 'getHashCode' as having good uniformity properties. template struct DetectAvalanchingHash> { using is_avalanching = void; }; // A helper for hashing according to the bit representation template struct BitCastHash : DetectAvalanchingHash { auto operator()(const T& t) const { // Doesn't discard or invent bits static_assert(sizeof(T) == sizeof(U)); // Can we copy bytes to and fro static_assert(std::is_trivially_copyable_v); static_assert(std::is_trivially_copyable_v); // Because we construct a U to memcpy into static_assert(std::is_trivially_constructible_v); U u; memcpy(&u, &t, sizeof(T)); return Hash{}(u); } }; // // Our hashing functor which disptaches to the most appropriate hashing // function for the type // template struct Hash : DetectAvalanchingHash { auto operator()(const T& t) const { // Our preference is for any hash we've defined ourselves if constexpr (HasSlangHash) return t.getHashCode(); // Otherwise fall back to any good hash provided by the hashmap // library else if constexpr (HasWyhash) return ankerl::unordered_dense::hash{}(t); // Otherwise fail else { // !sizeof(T*) is a 'false' which is dependent on T (pending P2593R0) static_assert(!sizeof(T*), "No hash implementation found for this type"); // This is to avoid the return type being deduced as 'void' and creating further errors. return HashCode64(0); } } }; // Specializations for float and double which hash 0 and -0 to distinct values template<> struct Hash : BitCastHash { }; template<> struct Hash : BitCastHash { }; // // Utility functions for using hashes // // A wrapper for Hash template auto getHashCode(const TKey& key) { return Hash{}(key); } inline HashCode64 getHashCode(const char* buffer, std::size_t len) { return ankerl::unordered_dense::detail::wyhash::hash(buffer, len); } template HashCode64 hashObjectBytes(const T& t) { static_assert( std::has_unique_object_representations_v, "This type must have a unique object representation to use hashObjectBytes"); return getHashCode(reinterpret_cast(&t), sizeof(t)); } // Use in a struct to declare a uniform hash which doens't care about the // structure of the members. #define SLANG_BYTEWISE_HASHABLE \ static constexpr bool kHasUniformHash = true; \ ::Slang::HashCode64 getHashCode() const \ { \ return ::Slang::hashObjectBytes(*this); \ } #define SLANG_COMPONENTWISE_HASHABLE_1 \ auto getHashCode() const \ { \ const auto& [m1] = *this; \ return Slang::getHashCode(m1); \ } #define SLANG_COMPONENTWISE_HASHABLE_2 \ auto getHashCode() const \ { \ const auto& [m1, m2] = *this; \ return combineHash(::Slang::getHashCode(m1), ::Slang::getHashCode(m2)); \ } inline HashCode64 combineHash(HashCode64 h) { return h; } inline HashCode32 combineHash(HashCode32 h) { return h; } // A left fold of a mixing operation template auto combineHash(H1 n, H2 m, Hs... args) { // TODO: restrict the types here more, currently we tend to throw // unhashed integers in here along with proper hashes of objects. static_assert(std::is_convertible_v || std::is_convertible_v); static_assert(std::is_convertible_v || std::is_convertible_v); return combineHash((n * 16777619) ^ m, args...); } struct Hasher { public: Hasher() {} /// Hash the given `value` and combine it into this hash state template void hashValue(T const& value) { // TODO: Eventually, we should replace `getHashCode` // with a "hash into" operation that takes the value // and a `Hasher`. m_hashCode = combineHash(m_hashCode, getHashCode(value)); } /// Combine the given `hash` code into the hash state. /// /// Note: users should prefer to use `hashValue` or `hashObject` /// when possible, as they may be able to ensure a higher-quality /// hash result (e.g., by using more bits to represent the state /// during hashing than are used for the final hash code). /// void addHash(HashCode hash) { m_hashCode = combineHash(m_hashCode, hash); } HashCode getResult() const { return m_hashCode; } private: HashCode m_hashCode = 0; }; } // namespace Slang #endif