yum-mirror/slang

Making it easier to work with shaders

git clone https://git.yummers.dev/yum-mirror/slang

jarcherNVUpdate build to allow setting external paths (#6528)9d7d943db

master
6.5 KiB226 linesraw
1#ifndef SLANG_CORE_HASH_H
2#define SLANG_CORE_HASH_H
3
4#include "slang-math.h"
5#include "slang.h"
6
7#include <ankerl/unordered_dense.h>
8#include <cstring>
9#include <type_traits>
10
11namespace Slang
12{
13//
14// Types
15//
16
17// A fixed 64bit wide hash on all targets.
18typedef uint64_t HashCode64;
19typedef HashCode64 HashCode;
20// A fixed 32bit wide hash on all targets.
21typedef uint32_t HashCode32;
22
23//
24// Some helpers to determine which hash to use for a type
25//
26
27// Forward declare Hash
28template<typename T>
29struct Hash;
30
31template<typename T, typename = void>
32constexpr static bool HasSlangHash = false;
33template<typename T>
34constexpr static bool HasSlangHash<
35    T,
36    std::enable_if_t<
37        std::is_convertible_v<decltype((std::declval<const T&>()).getHashCode()), HashCode64>>> =
38    true;
39
40// Does the hashmap implementation provide a uniform hash for this type.
41template<typename T, typename = void>
42constexpr static bool HasWyhash = false;
43template<typename T>
44constexpr static bool HasWyhash<T, typename ankerl::unordered_dense::hash<T>::is_avalanching> =
45    true;
46
47// We want to have an associated type 'is_avalanching = void' iff we have a
48// hash with good uniformity, the two specializations here add that member
49// when appropriate (since we can't declare an associated type with
50// constexpr if or something terse like that)
51template<typename T, typename = void>
52struct DetectAvalanchingHash
53{
54};
55template<typename T>
56struct DetectAvalanchingHash<T, std::enable_if_t<HasWyhash<T>>>
57{
58    using is_avalanching = void;
59};
60// Have we marked 'getHashCode' as having good uniformity properties.
61template<typename T>
62struct DetectAvalanchingHash<T, std::enable_if_t<T::kHasUniformHash>>
63{
64    using is_avalanching = void;
65};
66
67// A helper for hashing according to the bit representation
68template<typename T, typename U>
69struct BitCastHash : DetectAvalanchingHash<U>
70{
71    auto operator()(const T& t) const
72    {
73        // Doesn't discard or invent bits
74        static_assert(sizeof(T) == sizeof(U));
75        // Can we copy bytes to and fro
76        static_assert(std::is_trivially_copyable_v<T>);
77        static_assert(std::is_trivially_copyable_v<U>);
78        // Because we construct a U to memcpy into
79        static_assert(std::is_trivially_constructible_v<U>);
80
81        U u;
82        memcpy(&u, &t, sizeof(T));
83        return Hash<U>{}(u);
84    }
85};
86
87//
88// Our hashing functor which disptaches to the most appropriate hashing
89// function for the type
90//
91
92template<typename T>
93struct Hash : DetectAvalanchingHash<T>
94{
95    auto operator()(const T& t) const
96    {
97        // Our preference is for any hash we've defined ourselves
98        if constexpr (HasSlangHash<T>)
99            return t.getHashCode();
100        // Otherwise fall back to any good hash provided by the hashmap
101        // library
102        else if constexpr (HasWyhash<T>)
103            return ankerl::unordered_dense::hash<T>{}(t);
104        // Otherwise fail
105        else
106        {
107            // !sizeof(T*) is a 'false' which is dependent on T (pending P2593R0)
108            static_assert(!sizeof(T*), "No hash implementation found for this type");
109            // This is to avoid the return type being deduced as 'void' and creating further errors.
110            return HashCode64(0);
111        }
112    }
113};
114
115// Specializations for float and double which hash 0 and -0 to distinct values
116template<>
117struct Hash<float> : BitCastHash<float, uint32_t>
118{
119};
120template<>
121struct Hash<double> : BitCastHash<double, uint64_t>
122{
123};
124
125//
126// Utility functions for using hashes
127//
128
129// A wrapper for Hash<TKey>
130template<typename TKey>
131auto getHashCode(const TKey& key)
132{
133    return Hash<TKey>{}(key);
134}
135
136inline HashCode64 getHashCode(const char* buffer, std::size_t len)
137{
138    return ankerl::unordered_dense::detail::wyhash::hash(buffer, len);
139}
140
141template<typename T>
142HashCode64 hashObjectBytes(const T& t)
143{
144    static_assert(
145        std::has_unique_object_representations_v<T>,
146        "This type must have a unique object representation to use hashObjectBytes");
147    return getHashCode(reinterpret_cast<const char*>(&t), sizeof(t));
148}
149
150// Use in a struct to declare a uniform hash which doens't care about the
151// structure of the members.
152#define SLANG_BYTEWISE_HASHABLE                   \
153    static constexpr bool kHasUniformHash = true; \
154    ::Slang::HashCode64 getHashCode() const       \
155    {                                             \
156        return ::Slang::hashObjectBytes(*this);   \
157    }
158
159#define SLANG_COMPONENTWISE_HASHABLE_1 \
160    auto getHashCode() const           \
161    {                                  \
162        const auto& [m1] = *this;      \
163        return Slang::getHashCode(m1); \
164    }
165
166#define SLANG_COMPONENTWISE_HASHABLE_2                                          \
167    auto getHashCode() const                                                    \
168    {                                                                           \
169        const auto& [m1, m2] = *this;                                           \
170        return combineHash(::Slang::getHashCode(m1), ::Slang::getHashCode(m2)); \
171    }
172
173inline HashCode64 combineHash(HashCode64 h)
174{
175    return h;
176}
177
178inline HashCode32 combineHash(HashCode32 h)
179{
180    return h;
181}
182
183// A left fold of a mixing operation
184template<typename H1, typename H2, typename... Hs>
185auto combineHash(H1 n, H2 m, Hs... args)
186{
187    // TODO: restrict the types here more, currently we tend to throw
188    // unhashed integers in here along with proper hashes of objects.
189    static_assert(std::is_convertible_v<H1, HashCode64> || std::is_convertible_v<H1, HashCode32>);
190    static_assert(std::is_convertible_v<H2, HashCode64> || std::is_convertible_v<H2, HashCode32>);
191    return combineHash((n * 16777619) ^ m, args...);
192}
193
194struct Hasher
195{
196public:
197    Hasher() {}
198
199    /// Hash the given `value` and combine it into this hash state
200    template<typename T>
201    void hashValue(T const& value)
202    {
203        // TODO: Eventually, we should replace `getHashCode`
204        // with a "hash into" operation that takes the value
205        // and a `Hasher`.
206
207        m_hashCode = combineHash(m_hashCode, getHashCode(value));
208    }
209
210    /// Combine the given `hash` code into the hash state.
211    ///
212    /// Note: users should prefer to use `hashValue` or `hashObject`
213    /// when possible, as they may be able to ensure a higher-quality
214    /// hash result (e.g., by using more bits to represent the state
215    /// during hashing than are used for the final hash code).
216    ///
217    void addHash(HashCode hash) { m_hashCode = combineHash(m_hashCode, hash); }
218
219    HashCode getResult() const { return m_hashCode; }
220
221private:
222    HashCode m_hashCode = 0;
223};
224} // namespace Slang
225
226#endif