Coverage Report

Created: 2026-08-13 06:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}