|
31 | 31 | // Hashing `XXH3_64bits(&value, sizeof(value))` is only well-defined when the |
32 | 32 | // struct has no uninitialized bytes -- otherwise implicit padding between |
33 | 33 | // members poisons the hash with stack garbage and produces non-deterministic |
34 | | -// results. The helpers in this file convert "no padding bytes" from an |
35 | | -// invariant the author has to remember into one the compiler proves on every |
36 | | -// build, by requiring the call site to enumerate every non-static data member |
37 | | -// of the struct. |
38 | | -// |
39 | | -// All checks in this header are driven by the *types* in the member-pointer |
40 | | -// pack (via sizeof(Ms)), never by the runtime pointer values. The pointer |
41 | | -// values themselves are unused -- they exist solely so the call site reads |
42 | | -// naturally as a list of `&T::field` and the compiler can deduce `Ms...`. |
| 34 | +// results. hashStructByMemory() converts "no padding bytes" from an invariant |
| 35 | +// the author has to remember into one the compiler proves on every build, by |
| 36 | +// requiring the call site to enumerate every non-static data member of the |
| 37 | +// struct as non-type template parameters. |
43 | 38 |
|
44 | 39 | namespace dxvk { |
45 | 40 |
|
46 | | - // Returns true iff T has no implicit padding bytes, given that the supplied |
47 | | - // member-pointer pack enumerates every non-static data member of T. |
48 | | - // |
49 | | - // Intended for use inside a static_assert at the point T is declared. |
50 | | - // The pack does not need to be in declaration order; only the sum of member |
51 | | - // sizes is compared against sizeof(T). |
52 | | - // |
53 | | - // Example: |
54 | | - // struct Foo { uint64_t a; uint32_t b; uint32_t c; }; |
55 | | - // static_assert(hasNoImplicitPadding<Foo>(&Foo::a, &Foo::b, &Foo::c), |
56 | | - // "Foo has padding bytes"); |
57 | | - template <typename T, typename... Ms> |
58 | | - constexpr bool hasNoImplicitPadding(Ms T::*...) { |
59 | | - return sizeof(T) == (size_t{0} + ... + sizeof(Ms)); |
60 | | - } |
| 41 | +namespace hashStructHelpers { |
61 | 42 |
|
62 | | - // Hashes a single T as a contiguous byte range via XXH3, with compile-time |
63 | | - // safety checks: |
64 | | - // * T is standard-layout (well-defined memory layout) |
65 | | - // * T is trivially copyable (memcpy semantics) |
66 | | - // * sizeof(T) equals the sum of the member sizes (no implicit padding, |
67 | | - // given the supplied member-pointer pack enumerates every non-static |
68 | | - // data member of T) |
69 | | - // |
70 | | - // Any violation produces a localized compile error pointing at the call. |
71 | | - // |
72 | | - // The caller is still responsible for value-initializing the object |
73 | | - // (e.g. `Foo data{};`) so that any field forgotten in the data-fill code |
74 | | - // is zero rather than stack garbage. |
75 | | - template <typename T, typename... Ms> |
76 | | - XXH64_hash_t hashStructByMemory(const T& data, Ms T::*...) { |
77 | | - static_assert(std::is_standard_layout_v<T>, |
78 | | - "Type must be standard-layout to hash as a memory range."); |
79 | | - static_assert(std::is_trivially_copyable_v<T>, |
80 | | - "Type must be trivially copyable to hash as a memory range."); |
81 | | - static_assert(sizeof(T) == (size_t{0} + ... + sizeof(Ms)), |
82 | | - "sizeof(T) does not match the sum of the listed member " |
83 | | - "sizes. Either a non-static data member of T was omitted " |
84 | | - "from this call, or T contains implicit padding bytes " |
85 | | - "(reorder fields by descending alignment, or add explicit " |
86 | | - "padding members)."); |
87 | | - return XXH3_64bits(&data, sizeof(T)); |
88 | | - } |
| 43 | + template <auto MemberPtr> |
| 44 | + struct memberDataSize; |
89 | 45 |
|
90 | | - // Hashes `count` contiguous T values as a single byte range via XXH3, with |
91 | | - // the same compile-time safety checks as hashStructByMemory. Useful when |
92 | | - // accumulating trivially-copyable structs into a vector and hashing them |
93 | | - // all at once. |
94 | | - template <typename T, typename... Ms> |
95 | | - XXH64_hash_t hashStructArrayByMemory(const T* data, size_t count, Ms T::*...) { |
| 46 | + template <typename T, typename M, M T::* Ptr> |
| 47 | + struct memberDataSize<Ptr> { |
| 48 | + static constexpr size_t value = sizeof(M); |
| 49 | + }; |
| 50 | + |
| 51 | + // Compile-time checks only. Instantiating this type triggers static_asserts |
| 52 | + // but emits no runtime code when referenced from static_assert. |
| 53 | + template <typename T, auto... MemberPtrs> |
| 54 | + struct hashStructByMemoryChecks { |
96 | 55 | static_assert(std::is_standard_layout_v<T>, |
97 | 56 | "Type must be standard-layout to hash as a memory range."); |
98 | 57 | static_assert(std::is_trivially_copyable_v<T>, |
99 | 58 | "Type must be trivially copyable to hash as a memory range."); |
100 | | - static_assert(sizeof(T) == (size_t{0} + ... + sizeof(Ms)), |
| 59 | + static_assert(sizeof(T) == (size_t{0} + ... + memberDataSize<MemberPtrs>::value), |
101 | 60 | "sizeof(T) does not match the sum of the listed member " |
102 | 61 | "sizes. Either a non-static data member of T was omitted " |
103 | 62 | "from this call, or T contains implicit padding bytes " |
104 | 63 | "(reorder fields by descending alignment, or add explicit " |
105 | 64 | "padding members)."); |
106 | | - return XXH3_64bits(data, count * sizeof(T)); |
107 | | - } |
| 65 | + static constexpr bool value = true; |
| 66 | + }; |
| 67 | + |
| 68 | +} // namespace hashStructHelpers |
108 | 69 |
|
| 70 | +// Hashes a single T as a contiguous byte range via XXH3, with compile-time |
| 71 | +// safety checks: |
| 72 | +// * T is standard-layout (well-defined memory layout) |
| 73 | +// * T is trivially copyable (memcpy semantics) |
| 74 | +// * sizeof(T) equals the sum of the member sizes (no implicit padding, |
| 75 | +// given the supplied member-pointer pack enumerates every non-static |
| 76 | +// data member of T) |
| 77 | +// |
| 78 | +// Any violation produces a localized compile error pointing at the call. |
| 79 | +// |
| 80 | +// The caller is still responsible for value-initializing the object |
| 81 | +// (e.g. `Foo data{};`) so that any field forgotten in the data-fill code |
| 82 | +// is zero rather than stack garbage. |
| 83 | +// |
| 84 | +// Example: |
| 85 | +// return hashStructByMemory<Foo, &Foo::a, &Foo::b, &Foo::c>(data); |
| 86 | +template <typename T, auto... MemberPtrs> |
| 87 | +inline XXH64_hash_t hashStructByMemory(const T& data) { |
| 88 | + static_assert(hashStructHelpers::hashStructByMemoryChecks<T, MemberPtrs...>::value); |
| 89 | + return XXH3_64bits(&data, sizeof(T)); |
109 | 90 | } |
| 91 | + |
| 92 | +// Hashes `count` contiguous T values as a single byte range via XXH3, with |
| 93 | +// the same compile-time safety checks as hashStructByMemory(). Useful when |
| 94 | +// accumulating trivially-copyable structs into a vector and hashing them |
| 95 | +// all at once. |
| 96 | +// |
| 97 | +// Example: |
| 98 | +// return hashStructArrayByMemory<Foo, &Foo::a, &Foo::b, &Foo::c>(data, count); |
| 99 | +template <typename T, auto... MemberPtrs> |
| 100 | +inline XXH64_hash_t hashStructArrayByMemory(const T* data, size_t count) { |
| 101 | + static_assert(hashStructHelpers::hashStructByMemoryChecks<T, MemberPtrs...>::value); |
| 102 | + return XXH3_64bits(data, count * sizeof(T)); |
| 103 | +} |
| 104 | + |
| 105 | +} // namespace dxvk |
0 commit comments