usearch 2.8.16 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/.github/ISSUE_TEMPLATE/bug_report.yml +1 -1
  2. package/.github/workflows/prerelease.yml +33 -7
  3. package/.github/workflows/release.yml +25 -26
  4. package/.gitmodules +4 -1
  5. package/CITATION.cff +1 -1
  6. package/CMakeLists.txt +6 -8
  7. package/CONTRIBUTING.md +20 -1
  8. package/Cargo.toml +1 -1
  9. package/README.md +7 -6
  10. package/VERSION +1 -1
  11. package/conanfile.py +1 -1
  12. package/cpp/test.cpp +9 -2
  13. package/include/usearch/index.hpp +11 -11
  14. package/include/usearch/index_plugins.hpp +2 -1
  15. package/javascript/usearch.js +1 -1
  16. package/package.json +1 -1
  17. package/pyproject.toml +2 -2
  18. package/setup.py +10 -4
  19. package/simsimd/.releaserc +3 -2
  20. package/simsimd/Cargo.lock +708 -0
  21. package/simsimd/Cargo.toml +36 -0
  22. package/simsimd/README.md +37 -1
  23. package/simsimd/build.rs +20 -0
  24. package/simsimd/include/simsimd/simsimd.h +4 -4
  25. package/simsimd/include/simsimd/spatial.h +1 -1
  26. package/simsimd/include/simsimd/types.h +5 -1
  27. package/simsimd/package.json +1 -1
  28. package/simsimd/rust/benches/cosine.rs +27 -0
  29. package/simsimd/rust/benches/native.rs +27 -0
  30. package/simsimd/rust/benches/sqeuclidean.rs +27 -0
  31. package/simsimd/rust/lib.c +68 -0
  32. package/simsimd/rust/lib.rs +232 -0
  33. package/sqlite/README.md +178 -0
  34. package/sqlite/lib.cpp +325 -0
  35. package/stringzilla/CONTRIBUTING.md +429 -0
  36. package/stringzilla/Cargo.lock +25 -0
  37. package/stringzilla/Cargo.toml +26 -0
  38. package/stringzilla/LICENSE +201 -0
  39. package/stringzilla/README.md +1281 -0
  40. package/stringzilla/VERSION +1 -0
  41. package/stringzilla/assets/cover-strinzilla.jpeg +0 -0
  42. package/stringzilla/assets/meme-stringzilla-v2.jpeg +0 -0
  43. package/stringzilla/assets/meme-stringzilla-v3.jpeg +0 -0
  44. package/stringzilla/binding.gyp +16 -0
  45. package/stringzilla/build.rs +14 -0
  46. package/stringzilla/c/lib.c +304 -0
  47. package/stringzilla/cli/README.md +48 -0
  48. package/stringzilla/cli/split.py +72 -0
  49. package/stringzilla/cli/wc.py +37 -0
  50. package/stringzilla/include/stringzilla/experimental.h +724 -0
  51. package/stringzilla/include/stringzilla/module.modulemap +9 -0
  52. package/stringzilla/include/stringzilla/stringzilla.h +5240 -0
  53. package/stringzilla/include/stringzilla/stringzilla.hpp +3830 -0
  54. package/stringzilla/javascript/lib.c +125 -0
  55. package/stringzilla/javascript/stringzilla.js +22 -0
  56. package/stringzilla/package-ci.json +10 -0
  57. package/stringzilla/package.json +32 -0
  58. package/stringzilla/rust/lib.rs +362 -0
  59. package/stringzilla/swift/StringProtocol+StringZilla.swift +280 -0
  60. package/stringzilla/swift/Test.swift +60 -0
  61. package/wasmer.toml +1 -1
  62. package/simsimd/bench.js +0 -51
@@ -0,0 +1,724 @@
1
+ /**
2
+ * @brief Experimental kernels for StringZilla.
3
+ * @file experimental.h
4
+ * @author Ash Vardanian
5
+ */
6
+ #ifndef STRINGZILLA_EXPERIMENTAL_H_
7
+ #define STRINGZILLA_EXPERIMENTAL_H_
8
+
9
+ #include "stringzilla.h"
10
+
11
+ #ifdef __cplusplus
12
+ extern "C" {
13
+ #endif
14
+
15
+ /**
16
+ * @brief Bitap algo for exact matching of patterns up to @b 8-bytes long.
17
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
18
+ */
19
+ SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_8bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
20
+ sz_size_t n_length) {
21
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
22
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
23
+ sz_u8_t const *h_end = h_unsigned + h_length;
24
+
25
+ // Here is our baseline:
26
+ //
27
+ // sz_u8_t running_match = 0xFF;
28
+ // sz_u8_t character_position_masks[256];
29
+ // for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFF; }
30
+ // for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
31
+ // for (sz_size_t i = 0; i < h_length; ++i) {
32
+ // running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
33
+ // if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
34
+ // }
35
+ //
36
+ // On very short patterns, however, every tiny condition may have a huge affect on performance.
37
+ // 1. Let's replace byte-level intialization of `character_position_masks` with 64-bit ops.
38
+ // 2. Let's combine the first `n_length - 1` passes of the last loop into the previous loop.
39
+ typedef sz_u8_t offset_mask_t;
40
+
41
+ // Initialize the possible offset masks.
42
+ // Even using 8-byte `wide_masks` words, this would require 64 iterations to populate 256 bytes.
43
+ union {
44
+ offset_mask_t masks[256];
45
+ sz_u64_t wide_masks[sizeof(offset_mask_t) * 256 / sizeof(sz_u64_t)];
46
+ } character_positions;
47
+ for (sz_size_t i = 0; i != sizeof(offset_mask_t) * 256 / sizeof(sz_u64_t); ++i) {
48
+ character_positions.wide_masks[i] = 0xFFFFFFFFFFFFFFFFull;
49
+ }
50
+
51
+ // Populate the mask with possible positions for each character.
52
+ for (sz_size_t i = 0; i != n_length; ++i) { character_positions.masks[n_unsigned[i]] &= ~((offset_mask_t)1 << i); }
53
+
54
+ // The "running match" for the serial algorithm should be at least as wide as the `offset_mask_t`.
55
+ // But on modern systems larger integers may work better.
56
+ offset_mask_t running_match = 0, final_match = 1;
57
+ running_match = ~(running_match ^ running_match); //< Initialize with all-ones
58
+ final_match <<= n_length - 1;
59
+
60
+ for (; (h_unsigned != h_end) + ((running_match & final_match) != 0) == 2; ++h_unsigned) {
61
+ running_match = (running_match << 1) | character_positions.masks[h_unsigned[0]];
62
+ }
63
+ return ((running_match & final_match) == 0) ? (sz_cptr_t)(h_unsigned - n_length) : NULL;
64
+ }
65
+
66
+ /**
67
+ * @brief Bitap algorithm for exact matching of patterns up to @b 8-bytes long in @b reverse order.
68
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
69
+ */
70
+ SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_8bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
71
+ sz_size_t n_length) {
72
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
73
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
74
+ sz_u8_t running_match = 0xFF;
75
+ sz_u8_t character_position_masks[256];
76
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFF; }
77
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
78
+ for (sz_size_t i = 0; i < h_length; ++i) {
79
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
80
+ if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
81
+ }
82
+
83
+ return NULL;
84
+ }
85
+
86
+ /**
87
+ * @brief Bitap algo for exact matching of patterns up to @b 16-bytes long.
88
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
89
+ */
90
+ SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_16bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
91
+ sz_size_t n_length) {
92
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
93
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
94
+ sz_u16_t running_match = 0xFFFF;
95
+ sz_u16_t character_position_masks[256];
96
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFF; }
97
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
98
+ for (sz_size_t i = 0; i < h_length; ++i) {
99
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
100
+ if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
101
+ }
102
+
103
+ return NULL;
104
+ }
105
+
106
+ /**
107
+ * @brief Bitap algorithm for exact matching of patterns up to @b 16-bytes long in @b reverse order.
108
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
109
+ */
110
+ SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_16bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
111
+ sz_size_t n_length) {
112
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
113
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
114
+ sz_u16_t running_match = 0xFFFF;
115
+ sz_u16_t character_position_masks[256];
116
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFF; }
117
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
118
+ for (sz_size_t i = 0; i < h_length; ++i) {
119
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
120
+ if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
121
+ }
122
+
123
+ return NULL;
124
+ }
125
+
126
+ /**
127
+ * @brief Bitap algo for exact matching of patterns up to @b 32-bytes long.
128
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
129
+ */
130
+ SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_32bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
131
+ sz_size_t n_length) {
132
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
133
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
134
+ sz_u32_t running_match = 0xFFFFFFFF;
135
+ sz_u32_t character_position_masks[256];
136
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFF; }
137
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1u << i); }
138
+ for (sz_size_t i = 0; i < h_length; ++i) {
139
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
140
+ if ((running_match & (1u << (n_length - 1))) == 0) { return h + i - n_length + 1; }
141
+ }
142
+
143
+ return NULL;
144
+ }
145
+
146
+ /**
147
+ * @brief Bitap algorithm for exact matching of patterns up to @b 32-bytes long in @b reverse order.
148
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
149
+ */
150
+ SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_32bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
151
+ sz_size_t n_length) {
152
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
153
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
154
+ sz_u32_t running_match = 0xFFFFFFFF;
155
+ sz_u32_t character_position_masks[256];
156
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFF; }
157
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1u << i); }
158
+ for (sz_size_t i = 0; i < h_length; ++i) {
159
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
160
+ if ((running_match & (1u << (n_length - 1))) == 0) { return h + h_length - i - 1; }
161
+ }
162
+
163
+ return NULL;
164
+ }
165
+
166
+ /**
167
+ * @brief Bitap algo for exact matching of patterns up to @b 64-bytes long.
168
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
169
+ */
170
+ SZ_INTERNAL sz_cptr_t _sz_find_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
171
+ sz_size_t n_length) {
172
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
173
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
174
+ sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
175
+ sz_u64_t character_position_masks[256];
176
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
177
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1ull << i); }
178
+ for (sz_size_t i = 0; i < h_length; ++i) {
179
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
180
+ if ((running_match & (1ull << (n_length - 1))) == 0) { return h + i - n_length + 1; }
181
+ }
182
+
183
+ return NULL;
184
+ }
185
+
186
+ /**
187
+ * @brief Bitap algorithm for exact matching of patterns up to @b 64-bytes long in @b reverse order.
188
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
189
+ */
190
+ SZ_INTERNAL sz_cptr_t _sz_rfind_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
191
+ sz_size_t n_length) {
192
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
193
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
194
+ sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
195
+ sz_u64_t character_position_masks[256];
196
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
197
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1ull << i); }
198
+ for (sz_size_t i = 0; i < h_length; ++i) {
199
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
200
+ if ((running_match & (1ull << (n_length - 1))) == 0) { return h + h_length - i - 1; }
201
+ }
202
+
203
+ return NULL;
204
+ }
205
+
206
+ /**
207
+ * @brief Bitap algo for approximate matching of patterns up to @b 64-bytes long.
208
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
209
+ */
210
+ SZ_INTERNAL sz_cptr_t _sz_find_bounded_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
211
+ sz_size_t n_length) {
212
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
213
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
214
+ sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
215
+ sz_u64_t character_position_masks[256];
216
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
217
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[i]] &= ~(1ull << i); }
218
+ for (sz_size_t i = 0; i < h_length; ++i) {
219
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[i]];
220
+ if ((running_match & (1ull << (n_length - 1))) == 0) { return h + i - n_length + 1; }
221
+ }
222
+
223
+ return NULL;
224
+ }
225
+
226
+ /**
227
+ * @brief Bitap algorithm for approximate matching of patterns up to @b 64-bytes long in @b reverse order.
228
+ * https://en.wikipedia.org/wiki/Bitap_algorithm
229
+ */
230
+ SZ_INTERNAL sz_cptr_t _sz_find_bounded_last_bitap_upto_64bytes_serial(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n,
231
+ sz_size_t n_length) {
232
+ sz_u8_t const *h_unsigned = (sz_u8_t const *)h;
233
+ sz_u8_t const *n_unsigned = (sz_u8_t const *)n;
234
+ sz_u64_t running_match = 0xFFFFFFFFFFFFFFFFull;
235
+ sz_u64_t character_position_masks[256];
236
+ for (sz_size_t i = 0; i != 256; ++i) { character_position_masks[i] = 0xFFFFFFFFFFFFFFFFull; }
237
+ for (sz_size_t i = 0; i < n_length; ++i) { character_position_masks[n_unsigned[n_length - i - 1]] &= ~(1ull << i); }
238
+ for (sz_size_t i = 0; i < h_length; ++i) {
239
+ running_match = (running_match << 1) | character_position_masks[h_unsigned[h_length - i - 1]];
240
+ if ((running_match & (1ull << (n_length - 1))) == 0) { return h + h_length - i - 1; }
241
+ }
242
+
243
+ return NULL;
244
+ }
245
+
246
+ #if SZ_USE_AVX512
247
+
248
+ SZ_PUBLIC sz_size_t sz_edit_distance_avx512( //
249
+ sz_cptr_t const a, sz_size_t const a_length, //
250
+ sz_cptr_t const b, sz_size_t const b_length, //
251
+ sz_size_t const bound, sz_memory_allocator_t *alloc) {
252
+
253
+ sz_u512_vec_t a_vec, b_vec, previous_vec, current_vec, permutation_vec;
254
+ sz_u512_vec_t cost_deletion_vec, cost_insertion_vec, cost_substitution_vec;
255
+ sz_size_t min_distance;
256
+
257
+ b_vec.zmm = _mm512_maskz_loadu_epi8(_sz_u64_mask_until(b_length), b);
258
+ previous_vec.zmm = _mm512_set_epi8(63, 62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, //
259
+ 47, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, //
260
+ 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, //
261
+ 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0);
262
+
263
+ // Shifting bytes across the whole ZMM register is quite complicated, so let's use a permutation for that.
264
+ permutation_vec.zmm = _mm512_set_epi8(62, 61, 60, 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, 49, 48, 47, //
265
+ 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, //
266
+ 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15, //
267
+ 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 63);
268
+
269
+ for (sz_size_t idx_a = 0; idx_a != a_length; ++idx_a) {
270
+ min_distance = bound - 1;
271
+
272
+ a_vec.zmm = _mm512_set1_epi8(a[idx_a]);
273
+ // We first start by computing the cost of deletions and substitutions
274
+ // for (sz_size_t idx_b = 0; idx_b != b_length; ++idx_b) {
275
+ // sz_u8_t cost_deletion = previous_vec.u8s[idx_b + 1] + 1;
276
+ // sz_u8_t cost_substitution = previous_vec.u8s[idx_b] + (a[idx_a] != b[idx_b]);
277
+ // current_vec.u8s[idx_b + 1] = sz_min_of_two(cost_deletion, cost_substitution);
278
+ // }
279
+ cost_deletion_vec.zmm = _mm512_add_epi8(previous_vec.zmm, _mm512_set1_epi8(1));
280
+ cost_substitution_vec.zmm =
281
+ _mm512_mask_set1_epi8(_mm512_setzero_si512(), _mm512_cmpneq_epi8_mask(a_vec.zmm, b_vec.zmm), 0x01);
282
+ cost_substitution_vec.zmm = _mm512_add_epi8(previous_vec.zmm, cost_substitution_vec.zmm);
283
+ cost_substitution_vec.zmm = _mm512_permutexvar_epi8(permutation_vec.zmm, cost_substitution_vec.zmm);
284
+ current_vec.zmm = _mm512_min_epu8(cost_deletion_vec.zmm, cost_substitution_vec.zmm);
285
+ current_vec.u8s[0] = idx_a + 1;
286
+
287
+ // Now we need to compute the inclusive prefix sums using the minimum operator
288
+ // In one line:
289
+ // current_vec.u8s[idx_b + 1] = sz_min_of_two(current_vec.u8s[idx_b + 1], current_vec.u8s[idx_b] + 1)
290
+ //
291
+ // Unrolling this:
292
+ // current_vec.u8s[0 + 1] = sz_min_of_two(current_vec.u8s[0 + 1], current_vec.u8s[0] + 1)
293
+ // current_vec.u8s[1 + 1] = sz_min_of_two(current_vec.u8s[1 + 1], current_vec.u8s[1] + 1)
294
+ // current_vec.u8s[2 + 1] = sz_min_of_two(current_vec.u8s[2 + 1], current_vec.u8s[2] + 1)
295
+ // current_vec.u8s[3 + 1] = sz_min_of_two(current_vec.u8s[3 + 1], current_vec.u8s[3] + 1)
296
+ //
297
+ // Alternatively, using a tree-like reduction in log2 steps:
298
+ // - 6 cycles of reductions shifting by 1, 2, 4, 8, 16, 32, 64 bytes;
299
+ // - with each cycle containing at least one shift, min, add, blend.
300
+ //
301
+ // Which adds meaningless complexity without any performance gains.
302
+ for (sz_size_t idx_b = 0; idx_b != b_length; ++idx_b) {
303
+ sz_u8_t cost_insertion = current_vec.u8s[idx_b] + 1;
304
+ current_vec.u8s[idx_b + 1] = sz_min_of_two(current_vec.u8s[idx_b + 1], cost_insertion);
305
+ }
306
+
307
+ // Swap previous_distances and current_distances pointers
308
+ sz_u512_vec_t temp_vec;
309
+ temp_vec.zmm = previous_vec.zmm;
310
+ previous_vec.zmm = current_vec.zmm;
311
+ current_vec.zmm = temp_vec.zmm;
312
+ }
313
+
314
+ return previous_vec.u8s[b_length] < bound ? previous_vec.u8s[b_length] : bound;
315
+ }
316
+
317
+ sz_u512_vec_t sz_inclusive_min(sz_i32_t previous, sz_error_cost_t gap, sz_u512_vec_t base_vec) {
318
+
319
+ sz_u512_vec_t gap_vec, gap_double_vec, gap_quad_vec, gap_octa_vec;
320
+ gap_vec.zmm = _mm512_set1_epi32(gap);
321
+ gap_double_vec.zmm = _mm512_set1_epi32(2 * gap);
322
+ gap_quad_vec.zmm = _mm512_set1_epi32(4 * gap);
323
+ gap_octa_vec.zmm = _mm512_set1_epi32(8 * gap);
324
+
325
+ // __mmask16 mask_skip_one = 0xFFFF - 1;
326
+ // __mmask16 mask_skip_two = 0xFFFF - 3;
327
+ // __mmask16 mask_skip_four = 0xFFF0;
328
+ // __mmask16 mask_skip_eight = 0xFF00;
329
+ __mmask16 mask_skip_one = 0x7FFF;
330
+ __mmask16 mask_skip_two = 0x3FFF;
331
+ __mmask16 mask_skip_four = 0x0FFF;
332
+ __mmask16 mask_skip_eight = 0x00FF;
333
+ sz_u512_vec_t shift_by_one_vec, shift_by_two_vec, shift_by_four_vec, shift_by_eight_vec;
334
+ shift_by_one_vec.zmm = _mm512_set_epi32(14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0);
335
+ shift_by_two_vec.zmm = _mm512_set_epi32(13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0);
336
+ shift_by_four_vec.zmm = _mm512_set_epi32(11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0);
337
+ shift_by_eight_vec.zmm = _mm512_set_epi32(7, 6, 5, 4, 3, 2, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0);
338
+
339
+ sz_u512_vec_t shifted_vec;
340
+ sz_u512_vec_t new_vec = base_vec;
341
+ shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_one_vec.zmm, new_vec.zmm);
342
+ shifted_vec.i32s[0] = previous;
343
+ shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_vec.zmm);
344
+ new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_one, new_vec.zmm, shifted_vec.zmm);
345
+ sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
346
+
347
+ shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_two_vec.zmm, new_vec.zmm);
348
+ shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_double_vec.zmm);
349
+ new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_two, new_vec.zmm, shifted_vec.zmm);
350
+ sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
351
+
352
+ shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_four_vec.zmm, new_vec.zmm);
353
+ shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_quad_vec.zmm);
354
+ new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_four, new_vec.zmm, shifted_vec.zmm);
355
+ sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
356
+
357
+ shifted_vec.zmm = _mm512_permutexvar_epi32(shift_by_eight_vec.zmm, new_vec.zmm);
358
+ shifted_vec.zmm = _mm512_add_epi32(shifted_vec.zmm, gap_octa_vec.zmm);
359
+ new_vec.zmm = _mm512_mask_max_epi32(new_vec.zmm, mask_skip_eight, new_vec.zmm, shifted_vec.zmm);
360
+
361
+ sz_assert(new_vec.i32s[0] == max(previous + gap, base_vec.i32s[0]));
362
+ for (sz_size_t i = 1; i < 16; i++) sz_assert(new_vec.i32s[i] == max(new_vec.i32s[i - 1] + gap, new_vec.i32s[i]));
363
+
364
+ return new_vec;
365
+ }
366
+
367
+ #endif // SZ_USE_AVX512
368
+
369
+ #if SZ_USE_ARM_NEON
370
+
371
+ SZ_INTERNAL void interleave_uint32x4_to_uint64x2(uint32x4_t in_low, uint32x4_t in_high, uint64x2_t *out_first_second,
372
+ uint64x2_t *out_third_fourth) {
373
+ // Interleave elements
374
+ uint32x4x2_t interleaved = vzipq_u32(in_low, in_high);
375
+
376
+ // The results are now in two uint32x4_t vectors, which we need to cast to uint64x2_t
377
+ *out_first_second = vreinterpretq_u64_u32(interleaved.val[0]);
378
+ *out_third_fourth = vreinterpretq_u64_u32(interleaved.val[1]);
379
+ }
380
+
381
+ /* Arm NEON has several very relevant extensions for 32-bit FMA we can use for rolling hashes:
382
+ * * vmlaq_u32 - vector "fused-multiply-add"
383
+ * * vmlaq_n_u32 - vector-scalar "fused-multiply-add"
384
+ * * vmlsq_u32 - vector "fused-multiply-subtract"
385
+ * * vmlsq_n_u32 - vector-scalar "fused-multiply-subtract"
386
+ * Other basic intrinsics worth remembering:
387
+ * * vbslq_u32 - bitwise select to avoid branching
388
+ * * vld1q_dup_u32 - broadcast a 32-bit word into all 4 lanes of a 128-bit register
389
+ */
390
+
391
+ SZ_PUBLIC void sz_hashes_neon_naive(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step, //
392
+ sz_hash_callback_t callback, void *callback_handle) {
393
+
394
+ if (length < window_length || !window_length) return;
395
+ if (length < 2 * window_length) {
396
+ sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
397
+ return;
398
+ }
399
+
400
+ // Using NEON, we can perform 4 integer multiplications and additions within one register.
401
+ // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
402
+ sz_u8_t const *text = (sz_u8_t const *)start;
403
+ sz_u8_t const *text_end = text + length;
404
+
405
+ // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
406
+ sz_u32_t prime_power_low = 1, prime_power_high = 1;
407
+ for (sz_size_t i = 0; i + 1 < window_length; ++i)
408
+ prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
409
+ prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
410
+
411
+ sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
412
+ uint8_t high_shift = 77u;
413
+ uint32_t prime = SZ_U32_MAX_PRIME;
414
+
415
+ sz_u128_vec_t chars_outgoing_vec, chars_incoming_vec, chars_outgoing_shifted_vec, chars_incoming_shifted_vec;
416
+ // Let's skip the first window, as we are going to compute it in the loop.
417
+ sz_size_t cycles = 0;
418
+ sz_size_t step_mask = step - 1;
419
+ sz_u32_t one = 1;
420
+
421
+ // In every iteration we process 4 consecutive sliding windows.
422
+ // Once each of them computes separate values, we step forward (W-1) times,
423
+ // computing all interleaving values. That way the byte spilled from the second
424
+ // hash, can be added to the first one. That way we minimize the number of separate loads.
425
+ for (; text + window_length * 4 + (window_length - 1) <= text_end; text += window_length * 4) {
426
+ hash_low_vec.u32x4 = vld1q_dup_u32(&one);
427
+ hash_high_vec.u32x4 = vld1q_dup_u32(&one);
428
+ for (sz_size_t i = 0; i != window_length; ++i) {
429
+ chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
430
+ chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 1 + i);
431
+ chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 2 + i);
432
+ chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 3 + i);
433
+ chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
434
+
435
+ // Append new data.
436
+ hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
437
+ hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
438
+ hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
439
+ vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
440
+ hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
441
+ vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
442
+ }
443
+
444
+ if ((cycles & step_mask) == 0) {
445
+ interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
446
+ &hash_mix23_vec.u64x2);
447
+ callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
448
+ callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
449
+ callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
450
+ callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
451
+ }
452
+ ++cycles;
453
+
454
+ for (sz_size_t i = 0; i + 1 != window_length; ++i, ++cycles) {
455
+ // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
456
+ // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
457
+ chars_outgoing_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
458
+ chars_outgoing_vec.u32s[1] = chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 1 + i);
459
+ chars_outgoing_vec.u32s[2] = chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 2 + i);
460
+ chars_outgoing_vec.u32s[3] = chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 3 + i);
461
+ chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 4 + i);
462
+ chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
463
+ chars_outgoing_shifted_vec.u8x16 = vaddq_u8(chars_outgoing_vec.u8x16, vld1q_dup_u8(&high_shift));
464
+
465
+ // Drop old data.
466
+ hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_vec.u32x4, prime_power_low);
467
+ hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_shifted_vec.u32x4, prime_power_high);
468
+
469
+ // Append new data.
470
+ hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
471
+ hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
472
+ hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
473
+ vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
474
+ hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
475
+ vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
476
+ // Mix and call the user if needed
477
+ if ((cycles & step_mask) == 0) {
478
+ interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
479
+ &hash_mix23_vec.u64x2);
480
+ callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
481
+ callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
482
+ callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
483
+ callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
484
+ }
485
+ }
486
+ }
487
+ }
488
+
489
+ SZ_PUBLIC void sz_hashes_neon_reusing_loads(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step,
490
+ sz_hash_callback_t callback, void *callback_handle) {
491
+
492
+ if (length < window_length || !window_length) return;
493
+ if (length < 2 * window_length) {
494
+ sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
495
+ return;
496
+ }
497
+
498
+ // Using NEON, we can perform 4 integer multiplications and additions within one register.
499
+ // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
500
+ sz_u8_t const *text = (sz_u8_t const *)start;
501
+ sz_u8_t const *text_end = text + length;
502
+
503
+ // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
504
+ sz_u32_t prime_power_low = 1, prime_power_high = 1;
505
+ for (sz_size_t i = 0; i + 1 < window_length; ++i)
506
+ prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
507
+ prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
508
+
509
+ sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
510
+ uint8_t high_shift = 77u;
511
+ uint32_t prime = SZ_U32_MAX_PRIME;
512
+
513
+ sz_u128_vec_t chars_outgoing_vec, chars_incoming_vec, chars_outgoing_shifted_vec, chars_incoming_shifted_vec;
514
+ // Let's skip the first window, as we are going to compute it in the loop.
515
+ sz_size_t cycles = 0;
516
+ sz_size_t const step_mask = step - 1;
517
+ sz_u32_t const one = 1;
518
+
519
+ // In every iteration we process 4 consecutive sliding windows.
520
+ // Once each of them computes separate values, we step forward (W-1) times,
521
+ // computing all interleaving values. That way the byte spilled from the second
522
+ // hash, can be added to the first one. That way we minimize the number of separate loads.
523
+ for (; text + window_length * 4 + (window_length - 1) <= text_end; text += window_length * 4) {
524
+ hash_low_vec.u32x4 = vld1q_dup_u32(&one);
525
+ hash_high_vec.u32x4 = vld1q_dup_u32(&one);
526
+ for (sz_size_t i = 0; i != window_length; ++i) {
527
+ chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
528
+ chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 1 + i);
529
+ chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 2 + i);
530
+ chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 3 + i);
531
+ chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
532
+
533
+ // Append new data.
534
+ hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
535
+ hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
536
+ hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
537
+ vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
538
+ hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
539
+ vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
540
+ }
541
+
542
+ if ((cycles & step_mask) == 0) {
543
+ interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
544
+ &hash_mix23_vec.u64x2);
545
+ callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
546
+ callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
547
+ callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
548
+ callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
549
+ }
550
+ ++cycles;
551
+
552
+ for (sz_size_t i = 0; i + 1 != window_length; ++i, ++cycles) {
553
+ // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
554
+ // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
555
+ // Vectorizing these loads is a huge opportunity for performance optimizations, but naive prefetching
556
+ // into the register just makes things worse.
557
+ chars_outgoing_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
558
+ chars_outgoing_vec.u32s[1] = chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 1 + i);
559
+ chars_outgoing_vec.u32s[2] = chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 2 + i);
560
+ chars_outgoing_vec.u32s[3] = chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 3 + i);
561
+ chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 4 + i);
562
+ chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
563
+ chars_outgoing_shifted_vec.u8x16 = vaddq_u8(chars_outgoing_vec.u8x16, vld1q_dup_u8(&high_shift));
564
+
565
+ // Drop old data.
566
+ hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_vec.u32x4, prime_power_low);
567
+ hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_shifted_vec.u32x4, prime_power_high);
568
+
569
+ // Append new data.
570
+ hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
571
+ hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
572
+ hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
573
+ vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
574
+ hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
575
+ vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
576
+
577
+ // Mix and call the user if needed
578
+ if ((cycles & step_mask) == 0) {
579
+ interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
580
+ &hash_mix23_vec.u64x2);
581
+ callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
582
+ callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
583
+ callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
584
+ callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
585
+ }
586
+ }
587
+ }
588
+ }
589
+
590
+ SZ_PUBLIC void sz_hashes_neon_readahead(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step,
591
+ sz_hash_callback_t callback, void *callback_handle) {
592
+
593
+ if (length < window_length || !window_length) return;
594
+ if (length < 2 * window_length) {
595
+ sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
596
+ return;
597
+ }
598
+
599
+ // Using NEON, we can perform 4 integer multiplications and additions within one register.
600
+ // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
601
+ sz_u8_t const *text = (sz_u8_t const *)start;
602
+ sz_u8_t const *text_end = text + length;
603
+
604
+ // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
605
+ sz_u32_t prime_power_low = 1, prime_power_high = 1;
606
+ for (sz_size_t i = 0; i + 1 < window_length; ++i)
607
+ prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
608
+ prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
609
+
610
+ sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
611
+ uint8_t high_shift = 77u;
612
+ uint32_t prime = SZ_U32_MAX_PRIME;
613
+
614
+ /// Primary buffers containing four upcasted characters as uint32_t values.
615
+ sz_u128_vec_t chars_outgoing_low_vec, chars_incoming_low_vec;
616
+ sz_u128_vec_t chars_outgoing_high_vec, chars_incoming_high_vec;
617
+ // Let's skip the first window, as we are going to compute it in the loop.
618
+ sz_size_t cycles = 0;
619
+ sz_size_t const step_mask = step - 1;
620
+ sz_u32_t const one = 1;
621
+
622
+ // In every iteration we process 4 consecutive sliding windows.
623
+ // Once each of them computes separate values, we step forward (W-1) times,
624
+ // computing all interleaving values. That way the byte spilled from the second
625
+ // hash, can be added to the first one. That way we minimize the number of separate loads.
626
+ sz_size_t read_ahead_length = window_length - 1 + 16; // TODO: Instead of +16 round up to 16 multiple
627
+ for (; text + window_length * 4 + read_ahead_length <= text_end; text += window_length * 4) {
628
+ hash_low_vec.u32x4 = vld1q_dup_u32(&one);
629
+ hash_high_vec.u32x4 = vld1q_dup_u32(&one);
630
+
631
+ for (sz_size_t i = 0; i < window_length;) {
632
+ sz_u128_vec_t chars_readahead_vec[4];
633
+ chars_readahead_vec[0].u8x16 = vld1q_u8(text + window_length * 0 + i);
634
+ chars_readahead_vec[1].u8x16 = vld1q_u8(text + window_length * 1 + i);
635
+ chars_readahead_vec[2].u8x16 = vld1q_u8(text + window_length * 2 + i);
636
+ chars_readahead_vec[3].u8x16 = vld1q_u8(text + window_length * 3 + i);
637
+
638
+ for (; i != window_length; ++i) {
639
+ chars_incoming_low_vec.u32s[0] = chars_readahead_vec[0].u8x16[i];
640
+ chars_incoming_low_vec.u32s[1] = chars_readahead_vec[1].u8x16[i];
641
+ chars_incoming_low_vec.u32s[2] = chars_readahead_vec[2].u8x16[i];
642
+ chars_incoming_low_vec.u32s[3] = chars_readahead_vec[3].u8x16[i];
643
+ chars_incoming_high_vec.u8x16 = vaddq_u8(chars_incoming_low_vec.u8x16, vld1q_dup_u8(&high_shift));
644
+
645
+ // Append new data.
646
+ hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_low_vec.u32x4, hash_low_vec.u32x4, 31u);
647
+ hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_high_vec.u32x4, hash_high_vec.u32x4, 257u);
648
+ hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
649
+ vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
650
+ hash_high_vec.u32x4 =
651
+ vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
652
+ vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
653
+ }
654
+ }
655
+
656
+ if ((cycles & step_mask) == 0) {
657
+ interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
658
+ &hash_mix23_vec.u64x2);
659
+ callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
660
+ callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
661
+ callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
662
+ callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
663
+ }
664
+ ++cycles;
665
+
666
+ for (sz_size_t i = 0; i + 1 < window_length; ++i, ++cycles) {
667
+ // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
668
+ // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
669
+ sz_u128_vec_t chars_readahead_vec[5];
670
+ chars_readahead_vec[0].u8x16 = vld1q_u8(text + window_length * 0 + i);
671
+ chars_readahead_vec[1].u8x16 = vld1q_u8(text + window_length * 1 + i);
672
+ chars_readahead_vec[2].u8x16 = vld1q_u8(text + window_length * 2 + i);
673
+ chars_readahead_vec[3].u8x16 = vld1q_u8(text + window_length * 3 + i);
674
+ chars_readahead_vec[4].u8x16 = vld1q_u8(text + window_length * 4 + i);
675
+
676
+ for (; i + 1 < window_length; ++i) {
677
+ // Transpose
678
+ chars_outgoing_low_vec.u32s[0] = chars_readahead_vec[0].u8x16[i];
679
+ chars_outgoing_low_vec.u32s[1] = chars_incoming_low_vec.u32s[0] = chars_readahead_vec[1].u8x16[i];
680
+ chars_outgoing_low_vec.u32s[2] = chars_incoming_low_vec.u32s[1] = chars_readahead_vec[2].u8x16[i];
681
+ chars_outgoing_low_vec.u32s[3] = chars_incoming_low_vec.u32s[2] = chars_readahead_vec[3].u8x16[i];
682
+ chars_incoming_low_vec.u32s[3] = chars_readahead_vec[4].u8x16[i];
683
+
684
+ chars_outgoing_high_vec.u8x16 = vaddq_u8(chars_outgoing_low_vec.u8x16, vld1q_dup_u8(&high_shift));
685
+ chars_incoming_high_vec.u8x16 = vaddq_u8(chars_incoming_low_vec.u8x16, vld1q_dup_u8(&high_shift));
686
+
687
+ // Drop old data.
688
+ hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_low_vec.u32x4, prime_power_low);
689
+ hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_high_vec.u32x4, prime_power_high);
690
+
691
+ // Append new data.
692
+ hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_low_vec.u32x4, hash_low_vec.u32x4, 31u);
693
+ hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_high_vec.u32x4, hash_high_vec.u32x4, 257u);
694
+ hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
695
+ vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
696
+ hash_high_vec.u32x4 =
697
+ vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
698
+ vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
699
+
700
+ // Mix and call the user if needed
701
+ if ((cycles & step_mask) == 0) {
702
+ interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
703
+ &hash_mix23_vec.u64x2);
704
+ callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0],
705
+ callback_handle);
706
+ callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1],
707
+ callback_handle);
708
+ callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0],
709
+ callback_handle);
710
+ callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1],
711
+ callback_handle);
712
+ }
713
+ }
714
+ }
715
+ }
716
+ }
717
+
718
+ #endif // SZ_USE_ARM_NEON
719
+
720
+ #ifdef __cplusplus
721
+ } // extern "C"
722
+ #endif
723
+
724
+ #endif // STRINGZILLA_EXPERIMENTAL_H_