/src/glaze/include/glaze/util/compare.hpp
Line | Count | Source |
1 | | // Glaze Library |
2 | | // For the license information refer to glaze.hpp |
3 | | |
4 | | #pragma once |
5 | | |
6 | | #include <array> |
7 | | #include <bit> |
8 | | #include <cstdint> |
9 | | #include <cstring> |
10 | | #include <string_view> |
11 | | #include <type_traits> |
12 | | |
13 | | #include "glaze/util/inline.hpp" |
14 | | |
15 | | namespace glz |
16 | | { |
17 | | namespace detail |
18 | | { |
19 | | // Lowercases 8 packed bytes at once. Only 'A'..'Z' are changed. |
20 | | // The high-bit mask keeps per-byte additions from carrying into neighbors |
21 | | // and excludes non-ASCII bytes from classification. |
22 | | inline constexpr uint64_t ascii_tolower_u64(const uint64_t v) noexcept |
23 | 0 | { |
24 | 0 | constexpr uint64_t ones = 0x0101010101010101ull; |
25 | 0 | constexpr uint64_t high = 0x8080808080808080ull; |
26 | 0 | const uint64_t seven = v & ~high; |
27 | 0 | const uint64_t ge_A = seven + (0x80 - 'A') * ones; |
28 | 0 | const uint64_t gt_Z = seven + (0x80 - ('Z' + 1)) * ones; |
29 | 0 | const uint64_t is_upper = ge_A & ~gt_Z & ~v & high; |
30 | 0 | return v | (is_upper >> 2); |
31 | 0 | } |
32 | | } |
33 | | |
34 | | template <class Char> |
35 | | inline bool compare(const Char* lhs, const Char* rhs, uint64_t count) noexcept |
36 | 0 | { |
37 | 0 | if (count > 7) { |
38 | 0 | uint64_t v[2]; |
39 | 0 | while (count > 8) { |
40 | 0 | std::memcpy(v, lhs, 8); |
41 | 0 | std::memcpy(v + 1, rhs, 8); |
42 | 0 | if (v[0] != v[1]) { |
43 | 0 | return false; |
44 | 0 | } |
45 | 0 | count -= 8; |
46 | 0 | lhs += 8; |
47 | 0 | rhs += 8; |
48 | 0 | } |
49 | 0 |
|
50 | 0 | const auto shift = 8 - count; |
51 | 0 | lhs -= shift; |
52 | 0 | rhs -= shift; |
53 | 0 |
|
54 | 0 | std::memcpy(v, lhs, 8); |
55 | 0 | std::memcpy(v + 1, rhs, 8); |
56 | 0 | return v[0] == v[1]; |
57 | 0 | } |
58 | 0 |
|
59 | 0 | { |
60 | 0 | constexpr uint64_t n{sizeof(uint32_t)}; |
61 | 0 | if (count >= n) { |
62 | 0 | uint32_t v[2]; |
63 | 0 | std::memcpy(v, lhs, n); |
64 | 0 | std::memcpy(v + 1, rhs, n); |
65 | 0 | if (v[0] != v[1]) { |
66 | 0 | return false; |
67 | 0 | } |
68 | 0 | count -= n; |
69 | 0 | lhs += n; |
70 | 0 | rhs += n; |
71 | 0 | } |
72 | 0 | } |
73 | 0 | { |
74 | 0 | constexpr uint64_t n{sizeof(uint16_t)}; |
75 | 0 | if (count >= n) { |
76 | 0 | uint16_t v[2]; |
77 | 0 | std::memcpy(v, lhs, n); |
78 | 0 | std::memcpy(v + 1, rhs, n); |
79 | 0 | if (v[0] != v[1]) { |
80 | 0 | return false; |
81 | 0 | } |
82 | 0 | count -= n; |
83 | 0 | lhs += n; |
84 | 0 | rhs += n; |
85 | 0 | } |
86 | 0 | } |
87 | 0 | if (count && *lhs != *rhs) { |
88 | 0 | return false; |
89 | 0 | } |
90 | 0 | return true; |
91 | 0 | } |
92 | | |
93 | | // IMPORTANT: |
94 | | // This comparison function produces less binary than `compare` above and is very fast. |
95 | | // However, if our count is less than 8, we must be able to access the previous [8 - count] bytes. |
96 | | template <class Char> |
97 | | inline bool internal_compare(const Char* lhs, const Char* rhs, uint64_t count) noexcept |
98 | | { |
99 | | uint64_t v[2]; |
100 | | while (count > 8) { |
101 | | std::memcpy(v, lhs, 8); |
102 | | std::memcpy(v + 1, rhs, 8); |
103 | | if (v[0] != v[1]) { |
104 | | return false; |
105 | | } |
106 | | count -= 8; |
107 | | lhs += 8; |
108 | | rhs += 8; |
109 | | } |
110 | | |
111 | | const auto shift = 8 - count; |
112 | | lhs -= shift; |
113 | | rhs -= shift; |
114 | | |
115 | | std::memcpy(v, lhs, 8); |
116 | | std::memcpy(v + 1, rhs, 8); |
117 | | return v[0] == v[1]; |
118 | | } |
119 | | |
120 | | template <uint64_t Count, class Char> |
121 | | GLZ_ALWAYS_INLINE bool compare(const Char* lhs, const Char* rhs) noexcept |
122 | | { |
123 | | if constexpr (Count > 8) { |
124 | | return 0 == std::memcmp(lhs, rhs, Count); |
125 | | // return internal_compare(lhs, rhs, Count); |
126 | | } |
127 | | else if constexpr (Count == 8) { |
128 | | uint64_t l, r; |
129 | | std::memcpy(&l, lhs, 8); |
130 | | std::memcpy(&r, rhs, 8); |
131 | | return l == r; |
132 | | } |
133 | | else if constexpr (Count == 7) { |
134 | | uint32_t l, r; |
135 | | std::memcpy(&l, lhs, 4); |
136 | | std::memcpy(&r, rhs, 4); |
137 | | uint32_t l2, r2; |
138 | | std::memcpy(&l2, lhs + 3, 4); |
139 | | std::memcpy(&r2, rhs + 3, 4); |
140 | | return (l == r) & (l2 == r2); |
141 | | } |
142 | | else if constexpr (Count == 6) { |
143 | | uint32_t l, r; |
144 | | std::memcpy(&l, lhs, 4); |
145 | | std::memcpy(&r, rhs, 4); |
146 | | uint16_t l2, r2; |
147 | | std::memcpy(&l2, lhs + 4, 2); |
148 | | std::memcpy(&r2, rhs + 4, 2); |
149 | | return (l == r) & (l2 == r2); |
150 | | } |
151 | | else if constexpr (Count == 5) { |
152 | | uint32_t l, r; |
153 | | std::memcpy(&l, lhs, 4); |
154 | | std::memcpy(&r, rhs, 4); |
155 | | return (l == r) & (lhs[4] == rhs[4]); |
156 | | } |
157 | | else if constexpr (Count == 4) { |
158 | | uint32_t l, r; |
159 | | std::memcpy(&l, lhs, 4); |
160 | | std::memcpy(&r, rhs, 4); |
161 | | return l == r; |
162 | | } |
163 | | else if constexpr (Count == 3) { |
164 | | uint16_t l, r; |
165 | | std::memcpy(&l, lhs, 2); |
166 | | std::memcpy(&r, rhs, 2); |
167 | | return (l == r) & (lhs[2] == rhs[2]); |
168 | | } |
169 | | else if constexpr (Count == 2) { |
170 | | uint16_t l, r; |
171 | | std::memcpy(&l, lhs, 2); |
172 | | std::memcpy(&r, rhs, 2); |
173 | | return l == r; |
174 | | } |
175 | | else if constexpr (Count == 1) { |
176 | | return *lhs == *rhs; |
177 | | } |
178 | | else if constexpr (Count == 0) { |
179 | | return true; |
180 | | } |
181 | | } |
182 | | |
183 | | template <size_t N> |
184 | | consteval auto bytes_to_unsigned_type() noexcept |
185 | | { |
186 | | if constexpr (N == 1) { |
187 | | return uint8_t{}; |
188 | | } |
189 | | else if constexpr (N == 2) { |
190 | | return uint16_t{}; |
191 | | } |
192 | | else if constexpr (N == 4) { |
193 | | return uint32_t{}; |
194 | | } |
195 | | else if constexpr (N == 8) { |
196 | | return uint64_t{}; |
197 | | } |
198 | | else { |
199 | | return; |
200 | | } |
201 | | } |
202 | | |
203 | | template <size_t N> |
204 | | using unsigned_bytes_t = std::decay_t<decltype(bytes_to_unsigned_type<N>())>; |
205 | | |
206 | | template <const std::string_view& Str, size_t N> |
207 | | requires(N <= 8) |
208 | | consteval auto pack() |
209 | | { |
210 | | using T = unsigned_bytes_t<N>; |
211 | | T v{}; |
212 | | for (size_t i = 0; i < N; ++i) { |
213 | | v |= (static_cast<T>(uint8_t(Str[i])) << ((i % 8) * 8)); |
214 | | } |
215 | | return v; |
216 | | } |
217 | | |
218 | | template <const std::string_view& Str, size_t N> |
219 | | requires(N > 8) |
220 | | consteval auto pack() |
221 | | { |
222 | | constexpr auto chunks = N / 8; |
223 | | std::array<uint64_t, ((chunks > 0) ? chunks + 1 : 1)> v{}; |
224 | | for (size_t i = 0; i < N; ++i) { |
225 | | const auto chunk = i / 8; |
226 | | v[chunk] |= (static_cast<uint64_t>(uint8_t(Str[i])) << ((i % 8) * 8)); |
227 | | } |
228 | | return v; |
229 | | } |
230 | | |
231 | | template <const std::string_view& Str, size_t N> |
232 | | requires(N <= 8) |
233 | | consteval auto pack_buffered() |
234 | | { |
235 | | using T = unsigned_bytes_t<N>; |
236 | | T v{}; |
237 | | for (size_t i = 0; i < Str.size(); ++i) { |
238 | | v |= (static_cast<T>(uint8_t(Str[i])) << ((i % 8) * 8)); |
239 | | } |
240 | | return v; |
241 | | } |
242 | | |
243 | | template <const std::string_view& Str, size_t N = Str.size()> |
244 | | GLZ_ALWAYS_INLINE bool comparitor(const auto* other) noexcept |
245 | | { |
246 | | // pack() builds values in little-endian order (byte 0 in LSB position). |
247 | | // On big-endian systems, memcpy produces native (big-endian) values, |
248 | | // so we need to byteswap to match the packed representation. |
249 | | if constexpr (N == 8) { |
250 | | static constexpr auto packed = pack<Str, 8>(); |
251 | | uint64_t in; |
252 | | std::memcpy(&in, other, 8); |
253 | | if constexpr (std::endian::native == std::endian::big) { |
254 | | in = std::byteswap(in); |
255 | | } |
256 | | return (in == packed); |
257 | | } |
258 | | else if constexpr (N == 7) { |
259 | | static constexpr auto packed = pack_buffered<Str, 8>(); |
260 | | uint64_t in{}; |
261 | | std::memcpy(&in, other, 7); |
262 | | if constexpr (std::endian::native == std::endian::big) { |
263 | | in = std::byteswap(in); |
264 | | } |
265 | | return (in == packed); |
266 | | } |
267 | | else if constexpr (N == 6) { |
268 | | static constexpr auto packed = pack_buffered<Str, 8>(); |
269 | | uint64_t in{}; |
270 | | std::memcpy(&in, other, 6); |
271 | | if constexpr (std::endian::native == std::endian::big) { |
272 | | in = std::byteswap(in); |
273 | | } |
274 | | return (in == packed); |
275 | | } |
276 | | else if constexpr (N == 5) { |
277 | | static constexpr auto packed = pack<Str, 4>(); |
278 | | uint32_t in; |
279 | | std::memcpy(&in, other, 4); |
280 | | if constexpr (std::endian::native == std::endian::big) { |
281 | | in = std::byteswap(in); |
282 | | } |
283 | | return (in == packed) & (Str[4] == other[4]); |
284 | | } |
285 | | else if constexpr (N == 4) { |
286 | | static constexpr auto packed = pack<Str, 4>(); |
287 | | uint32_t in; |
288 | | std::memcpy(&in, other, 4); |
289 | | if constexpr (std::endian::native == std::endian::big) { |
290 | | in = std::byteswap(in); |
291 | | } |
292 | | return (in == packed); |
293 | | } |
294 | | else if constexpr (N == 3) { |
295 | | static constexpr auto packed = pack<Str, 2>(); |
296 | | uint16_t in; |
297 | | std::memcpy(&in, other, 2); |
298 | | if constexpr (std::endian::native == std::endian::big) { |
299 | | in = std::byteswap(in); |
300 | | } |
301 | | return (in == packed) & (Str[2] == other[2]); |
302 | | } |
303 | | else if constexpr (N == 2) { |
304 | | static constexpr auto packed = pack<Str, 2>(); |
305 | | uint16_t in; |
306 | | std::memcpy(&in, other, 2); |
307 | | if constexpr (std::endian::native == std::endian::big) { |
308 | | in = std::byteswap(in); |
309 | | } |
310 | | return (in == packed); |
311 | | } |
312 | | else if constexpr (N == 1) { |
313 | | return Str[0] == other[0]; |
314 | | } |
315 | | else if constexpr (N == 0) { |
316 | | return true; |
317 | | } |
318 | | else { |
319 | | // Clang and GCC optimize this extremely well for constexpr std::string_view |
320 | | // Packing data can create more binary on GCC |
321 | | // The other cases probably aren't needed as compiler explorer shows them optimized equally well as memcmp |
322 | | return 0 == std::memcmp(Str.data(), other, N); |
323 | | } |
324 | | } |
325 | | |
326 | | // ASCII case-insensitive equality, checks sizes |
327 | | inline constexpr bool striequal(const std::string_view lhs, const std::string_view rhs) noexcept |
328 | 0 | { |
329 | 0 | if (lhs.size() != rhs.size()) { |
330 | 0 | return false; |
331 | 0 | } |
332 | 0 |
|
333 | 0 | if consteval { |
334 | 0 | for (size_t i = 0; i < lhs.size(); ++i) { |
335 | 0 | const auto lower = [](char c) { return (c >= 'A' && c <= 'Z') ? char(c + 32) : c; }; |
336 | 0 | if (lower(lhs[i]) != lower(rhs[i])) { |
337 | 0 | return false; |
338 | 0 | } |
339 | 0 | } |
340 | 0 | return true; |
341 | 0 | } |
342 | 0 | else { |
343 | 0 | const char* l = lhs.data(); |
344 | 0 | const char* r = rhs.data(); |
345 | 0 | uint64_t count = lhs.size(); |
346 | 0 |
|
347 | 0 | for (; count >= 8; l += 8, r += 8, count -= 8) { |
348 | 0 | uint64_t a, b; |
349 | 0 | std::memcpy(&a, l, 8); |
350 | 0 | std::memcpy(&b, r, 8); |
351 | 0 | if (glz::detail::ascii_tolower_u64(a) != glz::detail::ascii_tolower_u64(b)) { |
352 | 0 | return false; |
353 | 0 | } |
354 | 0 | } |
355 | 0 |
|
356 | 0 | if (count) { |
357 | 0 | // Zero padding is safe, both sides pad identically and 0 is not 'A'..'Z' |
358 | 0 | uint64_t a{}, b{}; |
359 | 0 | std::memcpy(&a, l, count); |
360 | 0 | std::memcpy(&b, r, count); |
361 | 0 | return glz::detail::ascii_tolower_u64(a) == glz::detail::ascii_tolower_u64(b); |
362 | 0 | } |
363 | 0 | return true; |
364 | 0 | } |
365 | 0 | } |
366 | | |
367 | | // compare_sv checks sizes |
368 | | inline constexpr bool compare_sv(const std::string_view lhs, const std::string_view rhs) noexcept |
369 | 0 | { |
370 | 0 | if consteval { |
371 | 0 | return lhs == rhs; |
372 | 0 | } |
373 | 0 | else { |
374 | 0 | return (lhs.size() == rhs.size()) && compare(lhs.data(), rhs.data(), lhs.size()); |
375 | 0 | } |
376 | 0 | } |
377 | | |
378 | | template <const std::string_view& lhs> |
379 | | inline constexpr bool compare_sv(const std::string_view rhs) noexcept |
380 | | { |
381 | | if consteval { |
382 | | return lhs == rhs; |
383 | | } |
384 | | else { |
385 | | constexpr auto N = lhs.size(); |
386 | | return (N == rhs.size()) && compare<N>(lhs.data(), rhs.data()); |
387 | | } |
388 | | } |
389 | | } |