Sourcemeta Core 0.0.0
Loading...
Searching...
No Matches
json_hash.h
1#ifndef SOURCEMETA_CORE_JSON_HASH_H_
2#define SOURCEMETA_CORE_JSON_HASH_H_
3
4#include <sourcemeta/core/numeric.h>
5
6#include <bit> // std::endian
7#include <cassert> // assert
8#include <cstddef> // std::size_t
9#include <cstring> // std::memcpy
10#include <functional> // std::reference_wrapper
11
12namespace sourcemeta::core {
13
16template <typename T> struct HashJSON {
17 using hash_type = std::uint64_t;
18
19 inline auto operator()(const T &value) const noexcept -> hash_type {
20 if constexpr (requires { value.get().fast_hash(); }) {
21 return value.get().fast_hash();
22 } else {
23 return value.fast_hash();
24 }
25 }
26
28 [[nodiscard]]
29 inline auto is_perfect(const hash_type) const noexcept -> bool {
30 return false;
31 }
32};
33
36template <typename T> struct PropertyHashJSON {
37 struct hash_type {
38 using type = sourcemeta::core::uint128_t;
39 type a{0};
40 type b{0};
41
42 auto operator==(const hash_type &) const noexcept -> bool = default;
43 };
44
46 [[nodiscard]]
47 constexpr auto perfect(const char *data,
48 const std::size_t size) const noexcept -> hash_type {
49 hash_type result;
50 assert(size > 0);
51 if consteval {
52 // A constant evaluation cannot memcpy through a reinterpret_cast, so pack
53 // the bytes arithmetically instead. This reproduces the runtime memcpy on
54 // a little-endian target, where the packed bytes read back as the same
55 // integer, which the static assertion enforces so the two can never
56 // silently diverge
57 static_assert(std::endian::native == std::endian::little);
58 for (std::size_t index = 0; index < size; index += 1) {
59 const auto byte{static_cast<typename hash_type::type>(
60 static_cast<unsigned char>(data[index]))};
61 const auto position{index + 1};
62 if (position < 16) {
63 result.a |= byte << static_cast<int>(8 * position);
64 } else {
65 result.b |= byte << static_cast<int>(8 * (position - 16));
66 }
67 }
68 } else {
69 std::memcpy(reinterpret_cast<char *>(&result) + 1, data, size);
70 }
71
72 return result;
73 }
74
75 // GCC does not optimise well across implicit type conversions such as
76 // std::string to std::string_view, so we provide separate overloads with
77 // duplicated logic instead of unifying on a single parameter type
78
79 constexpr auto operator()(const T &value) const noexcept -> hash_type {
80 const auto size{value.size()};
81 switch (size) {
82 case 0:
83 return {};
84 case 1:
85 return this->perfect(value.data(), 1);
86 case 2:
87 return this->perfect(value.data(), 2);
88 case 3:
89 return this->perfect(value.data(), 3);
90 case 4:
91 return this->perfect(value.data(), 4);
92 case 5:
93 return this->perfect(value.data(), 5);
94 case 6:
95 return this->perfect(value.data(), 6);
96 case 7:
97 return this->perfect(value.data(), 7);
98 case 8:
99 return this->perfect(value.data(), 8);
100 case 9:
101 return this->perfect(value.data(), 9);
102 case 10:
103 return this->perfect(value.data(), 10);
104 case 11:
105 return this->perfect(value.data(), 11);
106 case 12:
107 return this->perfect(value.data(), 12);
108 case 13:
109 return this->perfect(value.data(), 13);
110 case 14:
111 return this->perfect(value.data(), 14);
112 case 15:
113 return this->perfect(value.data(), 15);
114 case 16:
115 return this->perfect(value.data(), 16);
116 case 17:
117 return this->perfect(value.data(), 17);
118 case 18:
119 return this->perfect(value.data(), 18);
120 case 19:
121 return this->perfect(value.data(), 19);
122 case 20:
123 return this->perfect(value.data(), 20);
124 case 21:
125 return this->perfect(value.data(), 21);
126 case 22:
127 return this->perfect(value.data(), 22);
128 case 23:
129 return this->perfect(value.data(), 23);
130 case 24:
131 return this->perfect(value.data(), 24);
132 case 25:
133 return this->perfect(value.data(), 25);
134 case 26:
135 return this->perfect(value.data(), 26);
136 case 27:
137 return this->perfect(value.data(), 27);
138 case 28:
139 return this->perfect(value.data(), 28);
140 case 29:
141 return this->perfect(value.data(), 29);
142 case 30:
143 return this->perfect(value.data(), 30);
144 case 31:
145 return this->perfect(value.data(), 31);
146 default:
147 // This case is specifically designed to be constant with regards to
148 // string length, and to exploit the fact that most JSON objects don't
149 // have a lot of entries, so hash collision is not as common
150 auto hash = this->perfect(value.data(), 31);
151 hash.a |= 1 + (static_cast<std::uint64_t>(size) +
152 static_cast<typename hash_type::type>(value.front()) +
153 static_cast<typename hash_type::type>(value.back())) %
154 // Make sure the property hash can never exceed 8 bits
155 255;
156 return hash;
157 }
158 }
159
160 constexpr auto operator()(const char *data,
161 const std::size_t size) const noexcept
162 -> hash_type {
163 switch (size) {
164 case 0:
165 return {};
166 case 1:
167 return this->perfect(data, 1);
168 case 2:
169 return this->perfect(data, 2);
170 case 3:
171 return this->perfect(data, 3);
172 case 4:
173 return this->perfect(data, 4);
174 case 5:
175 return this->perfect(data, 5);
176 case 6:
177 return this->perfect(data, 6);
178 case 7:
179 return this->perfect(data, 7);
180 case 8:
181 return this->perfect(data, 8);
182 case 9:
183 return this->perfect(data, 9);
184 case 10:
185 return this->perfect(data, 10);
186 case 11:
187 return this->perfect(data, 11);
188 case 12:
189 return this->perfect(data, 12);
190 case 13:
191 return this->perfect(data, 13);
192 case 14:
193 return this->perfect(data, 14);
194 case 15:
195 return this->perfect(data, 15);
196 case 16:
197 return this->perfect(data, 16);
198 case 17:
199 return this->perfect(data, 17);
200 case 18:
201 return this->perfect(data, 18);
202 case 19:
203 return this->perfect(data, 19);
204 case 20:
205 return this->perfect(data, 20);
206 case 21:
207 return this->perfect(data, 21);
208 case 22:
209 return this->perfect(data, 22);
210 case 23:
211 return this->perfect(data, 23);
212 case 24:
213 return this->perfect(data, 24);
214 case 25:
215 return this->perfect(data, 25);
216 case 26:
217 return this->perfect(data, 26);
218 case 27:
219 return this->perfect(data, 27);
220 case 28:
221 return this->perfect(data, 28);
222 case 29:
223 return this->perfect(data, 29);
224 case 30:
225 return this->perfect(data, 30);
226 case 31:
227 return this->perfect(data, 31);
228 default:
229 // This case is specifically designed to be constant with regards to
230 // string length, and to exploit the fact that most JSON objects don't
231 // have a lot of entries, so hash collision is not as common
232 auto hash = this->perfect(data, 31);
233 hash.a |= 1 + (static_cast<std::uint64_t>(size) +
234 static_cast<typename hash_type::type>(data[0]) +
235 static_cast<typename hash_type::type>(data[size - 1])) %
236 // Make sure the property hash can never exceed 8 bits
237 255;
238 return hash;
239 }
240 }
241
243 [[nodiscard]]
244 constexpr auto is_perfect(const hash_type &hash) const noexcept -> bool {
245 // If there is anything written past the first byte,
246 // then it is a perfect hash
247 return (hash.a & 255) == 0;
248 }
249};
250
256template <typename T> struct EqualJSON {
257 inline auto operator()(const T &left, const T &right) const -> bool {
258 if constexpr (requires { left.get() == right.get(); }) {
259 return left.get() == right.get();
260 } else {
261 return left == right;
262 }
263 }
264};
265
266} // namespace sourcemeta::core
267
268#endif
constexpr auto perfect(const char *data, const std::size_t size) const noexcept -> hash_type
Compute a perfect hash from raw data.
Definition json_hash.h:47
auto is_perfect(const hash_type) const noexcept -> bool
Check whether the given hash is a perfect hash.
Definition json_hash.h:29
constexpr auto is_perfect(const hash_type &hash) const noexcept -> bool
Check whether the given hash is a perfect hash.
Definition json_hash.h:244
Definition json_hash.h:256
Definition json_hash.h:16
Definition json_hash.h:36
Definition numeric_uint128.h:25