usearch 2.9.2 → 2.10.1

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 (172) hide show
  1. package/README.md +39 -38
  2. package/javascript/{types → dist/cjs}/usearch.d.ts +90 -115
  3. package/javascript/dist/cjs/usearch.js +531 -0
  4. package/javascript/dist/esm/usearch.d.ts +212 -0
  5. package/javascript/{usearch.js → dist/esm/usearch.js} +199 -153
  6. package/package.json +25 -23
  7. package/prebuilds/darwin-arm64+x64/usearch.node +0 -0
  8. package/prebuilds/linux-arm64/usearch.node +0 -0
  9. package/prebuilds/linux-x64/usearch.node +0 -0
  10. package/prebuilds/win32-ia32/usearch.node +0 -0
  11. package/prebuilds/win32-x64/usearch.node +0 -0
  12. package/.clang-format +0 -10
  13. package/.clang-tidy +0 -60
  14. package/.cmake-format.py +0 -19
  15. package/.dockerignore +0 -93
  16. package/.gitattributes +0 -9
  17. package/.github/ISSUE_TEMPLATE/bug_report.yml +0 -90
  18. package/.github/ISSUE_TEMPLATE/config.yml +0 -5
  19. package/.github/ISSUE_TEMPLATE/feature_request.yml +0 -64
  20. package/.github/workflows/Dockerfile.libs +0 -36
  21. package/.github/workflows/prerelease.yml +0 -331
  22. package/.github/workflows/release.yml +0 -722
  23. package/.github/workflows/update_version.sh +0 -15
  24. package/.gitmodules +0 -9
  25. package/.releaserc +0 -90
  26. package/CITATION.cff +0 -11
  27. package/CMakeLists.txt +0 -324
  28. package/CODE_OF_CONDUCT.md +0 -128
  29. package/CONTRIBUTING.md +0 -361
  30. package/Cargo.toml +0 -19
  31. package/Dockerfile +0 -16
  32. package/VERSION +0 -1
  33. package/assets/unum.png +0 -0
  34. package/assets/usearch-approaches-transparent.png +0 -0
  35. package/assets/usearch-approaches-white.png +0 -0
  36. package/assets/usearch-neighbor-types.png +0 -0
  37. package/binding.gyp +0 -87
  38. package/build.gradle +0 -204
  39. package/build.rs +0 -18
  40. package/c/CMakeLists.txt +0 -25
  41. package/c/README.md +0 -34
  42. package/c/lib.cpp +0 -358
  43. package/c/test.c +0 -382
  44. package/c/usearch.h +0 -311
  45. package/cmake/clipp.cmake +0 -7
  46. package/cmake/config.cmake.in +0 -15
  47. package/cmake/emscripten.cmake +0 -8
  48. package/cmake/jemalloc.cmake +0 -62
  49. package/cmake/pkg-config.pc.in +0 -4
  50. package/cmake/usearchConfigVersion.cmake.in +0 -20
  51. package/conanfile.py +0 -26
  52. package/cpp/CMakeLists.txt +0 -14
  53. package/cpp/README.md +0 -161
  54. package/cpp/bench.cpp +0 -618
  55. package/cpp/test.cpp +0 -323
  56. package/fp16/CMakeLists.txt +0 -176
  57. package/fp16/LICENSE +0 -11
  58. package/fp16/README.md +0 -20
  59. package/fp16/bench/alt-element.cc +0 -89
  60. package/fp16/bench/from-alt-array.cc +0 -175
  61. package/fp16/bench/from-ieee-array.cc +0 -399
  62. package/fp16/bench/ieee-element.cc +0 -272
  63. package/fp16/bench/to-alt-array.cc +0 -91
  64. package/fp16/bench/to-ieee-array.cc +0 -303
  65. package/fp16/cmake/DownloadGoogleBenchmark.cmake +0 -15
  66. package/fp16/cmake/DownloadGoogleTest.cmake +0 -15
  67. package/fp16/cmake/DownloadPSimd.cmake +0 -15
  68. package/fp16/configure.py +0 -65
  69. package/fp16/confu.yaml +0 -8
  70. package/fp16/include/fp16/__init__.py +0 -0
  71. package/fp16/include/fp16/avx.py +0 -50
  72. package/fp16/include/fp16/avx2.py +0 -53
  73. package/fp16/include/fp16/bitcasts.h +0 -92
  74. package/fp16/include/fp16/fp16.h +0 -451
  75. package/fp16/include/fp16/psimd.h +0 -131
  76. package/fp16/include/fp16.h +0 -11
  77. package/fp16/jni/Android.mk +0 -6
  78. package/fp16/jni/Application.mk +0 -4
  79. package/fp16/test/alt-from-fp32-value.cc +0 -498
  80. package/fp16/test/alt-to-fp32-bits.cc +0 -263
  81. package/fp16/test/alt-to-fp32-psimd.cc +0 -145
  82. package/fp16/test/alt-to-fp32-value.cc +0 -348
  83. package/fp16/test/alt-to-fp32x2-psimd.cc +0 -245
  84. package/fp16/test/bitcasts.cc +0 -56
  85. package/fp16/test/ieee-from-fp32-value.cc +0 -523
  86. package/fp16/test/ieee-to-fp32-bits.cc +0 -333
  87. package/fp16/test/ieee-to-fp32-psimd.cc +0 -312
  88. package/fp16/test/ieee-to-fp32-value.cc +0 -420
  89. package/fp16/test/ieee-to-fp32x2-psimd.cc +0 -541
  90. package/fp16/test/peachpy/alt-xmm-to-fp32-xmm-avx.cc +0 -96
  91. package/fp16/test/peachpy/alt-xmm-to-fp32-ymm-avx2.cc +0 -96
  92. package/fp16/test/peachpy/stubs.py +0 -38
  93. package/fp16/test/tables.cc +0 -530
  94. package/fp16/test/tables.h +0 -13
  95. package/fp16/third-party/THHalf.h +0 -92
  96. package/fp16/third-party/eigen-half.h +0 -170
  97. package/fp16/third-party/float16-compressor.h +0 -80
  98. package/fp16/third-party/half.hpp +0 -2916
  99. package/fp16/third-party/npy-halffloat.h +0 -328
  100. package/include/usearch/index.hpp +0 -3836
  101. package/include/usearch/index_dense.hpp +0 -2004
  102. package/include/usearch/index_plugins.hpp +0 -2213
  103. package/javascript/lib.cpp +0 -341
  104. package/javascript/types/usearch.d.ts.map +0 -1
  105. package/javascript/usearch.test.js +0 -74
  106. package/package-ci.json +0 -10
  107. package/pyproject.toml +0 -51
  108. package/setup.py +0 -201
  109. package/simsimd/.releaserc +0 -86
  110. package/simsimd/CONTRIBUTING.md +0 -88
  111. package/simsimd/Cargo.lock +0 -709
  112. package/simsimd/Cargo.toml +0 -44
  113. package/simsimd/LICENSE +0 -201
  114. package/simsimd/README.md +0 -317
  115. package/simsimd/binding.gyp +0 -36
  116. package/simsimd/build.rs +0 -20
  117. package/simsimd/include/simsimd/binary.h +0 -245
  118. package/simsimd/include/simsimd/complex.h +0 -174
  119. package/simsimd/include/simsimd/probability.h +0 -548
  120. package/simsimd/include/simsimd/simsimd.h +0 -465
  121. package/simsimd/include/simsimd/spatial.h +0 -1267
  122. package/simsimd/include/simsimd/types.h +0 -208
  123. package/simsimd/javascript/bench.js +0 -127
  124. package/simsimd/javascript/fallback.ts +0 -193
  125. package/simsimd/javascript/lib.c +0 -102
  126. package/simsimd/javascript/node-gyp-build.d.ts +0 -1
  127. package/simsimd/javascript/simsimd.ts +0 -124
  128. package/simsimd/javascript/test.mjs +0 -153
  129. package/simsimd/javascript/tsconfig-base.json +0 -25
  130. package/simsimd/javascript/tsconfig-cjs.json +0 -8
  131. package/simsimd/javascript/tsconfig-esm.json +0 -8
  132. package/simsimd/package-lock.json +0 -1688
  133. package/simsimd/package.json +0 -61
  134. package/simsimd/rust/benches/cosine.rs +0 -27
  135. package/simsimd/rust/benches/native.rs +0 -30
  136. package/simsimd/rust/benches/sqeuclidean.rs +0 -27
  137. package/simsimd/rust/lib.c +0 -45
  138. package/simsimd/rust/lib.rs +0 -550
  139. package/sqlite/README.md +0 -178
  140. package/sqlite/lib.cpp +0 -325
  141. package/stringzilla/CONTRIBUTING.md +0 -446
  142. package/stringzilla/Cargo.lock +0 -25
  143. package/stringzilla/Cargo.toml +0 -26
  144. package/stringzilla/LICENSE +0 -201
  145. package/stringzilla/README.md +0 -1422
  146. package/stringzilla/VERSION +0 -1
  147. package/stringzilla/assets/cover-strinzilla.jpeg +0 -0
  148. package/stringzilla/assets/meme-stringzilla-v2.jpeg +0 -0
  149. package/stringzilla/assets/meme-stringzilla-v3.jpeg +0 -0
  150. package/stringzilla/binding.gyp +0 -16
  151. package/stringzilla/build.rs +0 -87
  152. package/stringzilla/c/lib.c +0 -337
  153. package/stringzilla/cli/README.md +0 -65
  154. package/stringzilla/cli/__init__.py +0 -0
  155. package/stringzilla/cli/split.py +0 -105
  156. package/stringzilla/cli/wc.py +0 -117
  157. package/stringzilla/include/stringzilla/experimental.h +0 -845
  158. package/stringzilla/include/stringzilla/module.modulemap +0 -9
  159. package/stringzilla/include/stringzilla/stringzilla.h +0 -5370
  160. package/stringzilla/include/stringzilla/stringzilla.hpp +0 -3838
  161. package/stringzilla/javascript/lib.c +0 -125
  162. package/stringzilla/javascript/stringzilla.js +0 -22
  163. package/stringzilla/package-ci.json +0 -10
  164. package/stringzilla/package.json +0 -32
  165. package/stringzilla/rust/lib.rs +0 -873
  166. package/stringzilla/swift/StringProtocol+StringZilla.swift +0 -280
  167. package/stringzilla/swift/Test.swift +0 -60
  168. package/tsconfig.json +0 -21
  169. package/wasmer.toml +0 -12
  170. package/winlibinstaller.bat +0 -42
  171. /package/{simsimd/javascript/dist-package-cjs.json → javascript/dist/cjs/package.json} +0 -0
  172. /package/{simsimd/javascript/dist-package-esm.json → javascript/dist/esm/package.json} +0 -0
@@ -1,845 +0,0 @@
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_PUBLIC sz_cptr_t sz_find_neon_too_smart(sz_cptr_t h, sz_size_t h_length, sz_cptr_t n, sz_size_t n_length) {
372
-
373
- // This almost never fires, but it's better to be safe than sorry.
374
- if (h_length < n_length || !n_length) return SZ_NULL_CHAR;
375
- if (n_length == 1) return sz_find_byte_neon(h, h_length, n);
376
-
377
- // Scan through the string.
378
- // Assuming how tiny the Arm NEON registers are, we should avoid internal branches at all costs.
379
- // That's why, for smaller needles, we use different loops.
380
- if (n_length == 2) {
381
- // This is a common case. Aside from ASCII bigrams, it's also the most common case for UTF-16,
382
- // or any UTF8 content in Cyrillic, Greek, Armenian, Hebrew, Arabic, Coptic, Syriac, Thaana,
383
- // N'Ko writing system of West-African nations, and, of course, Latin scripts.
384
- // Dealing with 16-bit values, we can check 15 possible offsets in a single loop iteration.
385
- // For that we are going to keep 2 registers populated with haystack data.
386
- // First - bigrams at even offsets - 0, 2, 4, 6, 8, 10, 12, 14.
387
- // Second - bigrams at odd offsets - 1, 3, 5, 7, 9, 11, 13. One less than the first one.
388
- sz_u64_t matches;
389
- sz_u128_vec_t h_even_vec, h_odd_vec, n_vec, interleave_mask_vec, matches_vec;
390
- // Broadcast needle characters into SIMD registers.
391
- n_vec.u16x8 = vdupq_n_u16(sz_u16_load(n).u16);
392
- interleave_mask_vec.u16x8 = vdupq_n_u16(0x00FFu);
393
- for (; h_length >= 16; h += 15, h_length -= 15) {
394
- h_even_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
395
- h_odd_vec.u8x16 = vextq_u8(h_even_vec.u8x16, /* can be any noise: */ h_even_vec.u8x16, 1);
396
- // We can now compare both 16-bit arrays with the needle.
397
- // The result of each comparison will also be 16 bits long.
398
- // Then - we blend!
399
- // For odd offsets we are gonna take the bottom 8 bits, and for even - the top ones!
400
- matches_vec.u8x16 =
401
- vbslq_u8(interleave_mask_vec.u8x16, vreinterpretq_u8_u16(vceqq_u16(h_even_vec.u16x8, n_vec.u16x8)),
402
- vreinterpretq_u8_u16(vceqq_u16(h_odd_vec.u16x8, n_vec.u16x8)));
403
- matches = vreinterpretq_u8_u4(matches_vec.u8x16);
404
- if (matches) return h + sz_u64_ctz(matches) / 4;
405
- }
406
- }
407
- else if (n_length == 3) {
408
- // Comparing 24-bit values is a bumer. Being lazy, I went with a simple design.
409
- // Instead of keeping one register per haystack offset, I keep a register per needle character.
410
- sz_u64_t matches;
411
- sz_u128_vec_t h_vec, n_first_vec, n_second_vec, n_third_vec, matches_vec;
412
- // Broadcast needle characters into SIMD registers.
413
- n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[0]);
414
- n_second_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[1]);
415
- n_third_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[2]);
416
- for (; h_length >= 16; h += 14, h_length -= 14) {
417
- h_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
418
- // Let's compare the first character.
419
- matches_vec.u8x16 = vceqq_u8(h_vec.u8x16, n_first_vec.u8x16);
420
- // Let's compare the second one, shift the equality indicators left by 8 bits, and blend.
421
- matches_vec.u8x16 =
422
- vandq_u8(matches_vec.u8x16, vextq_u8(vceqq_u8(h_vec.u8x16, n_second_vec.u8x16), vdupq_n_u8(0), 1));
423
- // Let's compare the third one, shift the equality indicators left by 16 bits, and blend.
424
- matches_vec.u8x16 =
425
- vandq_u8(matches_vec.u8x16, vextq_u8(vceqq_u8(h_vec.u8x16, n_third_vec.u8x16), vdupq_n_u8(0), 2));
426
- // Now reduce bytes to nibbles, and check for matches.
427
- matches = vreinterpretq_u8_u4(matches_vec.u8x16);
428
- if (matches) return h + sz_u64_ctz(matches) / 4;
429
- }
430
- }
431
- else if (n_length == 4) {
432
- // This is a common case not only for ASCII 4-grams, but also UTF-32 content,
433
- // emojis, Chinese, and many other east-Asian languages.
434
- // Dealing with 32-bit values, we can analyze 13 offsets at once.
435
- sz_u128_vec_t h_first_vec, h_second_vec, h_third_vec, h_fourth_vec, interleave_2mask_vec, interleave_4mask_vec,
436
- n_vec, matches_vec;
437
- sz_u64_t matches;
438
- // Broadcast needle characters into SIMD registers.
439
- n_vec.u32x4 = vdupq_n_u32(sz_u32_load(n).u32);
440
- interleave_2mask_vec.u16x8 = vdupq_n_u16(0x00FFu);
441
- interleave_4mask_vec.u32x4 = vdupq_n_u32(0x0000FFFFu);
442
- for (; h_length >= 16; h += 13, h_length -= 13) {
443
- h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)h);
444
- h_second_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 1);
445
- h_third_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 2);
446
- h_fourth_vec.u8x16 = vextq_u8(h_first_vec.u8x16, /* can be any noise: */ h_first_vec.u8x16, 3);
447
- // We can now compare all four arrays of 32-bit values with the needle.
448
- // The result of each comparison will also be 32 bits long.
449
- // Then - we blend!
450
- matches_vec.u8x16 = vbslq_u8(
451
- interleave_4mask_vec.u8x16,
452
- vbslq_u8(interleave_2mask_vec.u8x16, vreinterpretq_u8_u32(vceqq_u32(h_first_vec.u32x4, n_vec.u32x4)),
453
- vreinterpretq_u8_u32(vceqq_u32(h_second_vec.u32x4, n_vec.u32x4))),
454
- vbslq_u8(interleave_2mask_vec.u8x16, vreinterpretq_u8_u32(vceqq_u32(h_third_vec.u32x4, n_vec.u32x4)),
455
- vreinterpretq_u8_u32(vceqq_u32(h_fourth_vec.u32x4, n_vec.u32x4))));
456
- matches = vreinterpretq_u8_u4(matches_vec.u8x16);
457
- if (matches) return h + sz_u64_ctz(matches) / 4;
458
- }
459
- }
460
- else {
461
- // Pick the parts of the needle that are worth comparing.
462
- sz_size_t offset_first, offset_mid, offset_last;
463
- _sz_locate_needle_anomalies(n, n_length, &offset_first, &offset_mid, &offset_last);
464
- // Broadcast those characters into SIMD registers.
465
- sz_u64_t matches;
466
- sz_u128_vec_t h_first_vec, h_mid_vec, h_last_vec, n_first_vec, n_mid_vec, n_last_vec, matches_vec;
467
- n_first_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_first]);
468
- n_mid_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_mid]);
469
- n_last_vec.u8x16 = vld1q_dup_u8((sz_u8_t const *)&n[offset_last]);
470
- // Walk through the string.
471
- for (; h_length >= n_length + 16; h += 16, h_length -= 16) {
472
- h_first_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_first));
473
- h_mid_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_mid));
474
- h_last_vec.u8x16 = vld1q_u8((sz_u8_t const *)(h + offset_last));
475
- matches_vec.u8x16 = vandq_u8( //
476
- vandq_u8( //
477
- vceqq_u8(h_first_vec.u8x16, n_first_vec.u8x16), //
478
- vceqq_u8(h_mid_vec.u8x16, n_mid_vec.u8x16)),
479
- vceqq_u8(h_last_vec.u8x16, n_last_vec.u8x16));
480
- matches = vreinterpretq_u8_u4(matches_vec.u8x16);
481
- while (matches) {
482
- int potential_offset = sz_u64_ctz(matches) / 4;
483
- if (sz_equal(h + potential_offset, n, n_length)) return h + potential_offset;
484
- matches &= matches - 1;
485
- }
486
- }
487
- }
488
-
489
- return sz_find_serial(h, h_length, n, n_length);
490
- }
491
-
492
- SZ_INTERNAL void interleave_uint32x4_to_uint64x2(uint32x4_t in_low, uint32x4_t in_high, uint64x2_t *out_first_second,
493
- uint64x2_t *out_third_fourth) {
494
- // Interleave elements
495
- uint32x4x2_t interleaved = vzipq_u32(in_low, in_high);
496
-
497
- // The results are now in two uint32x4_t vectors, which we need to cast to uint64x2_t
498
- *out_first_second = vreinterpretq_u64_u32(interleaved.val[0]);
499
- *out_third_fourth = vreinterpretq_u64_u32(interleaved.val[1]);
500
- }
501
-
502
- /* Arm NEON has several very relevant extensions for 32-bit FMA we can use for rolling hashes:
503
- * * vmlaq_u32 - vector "fused-multiply-add"
504
- * * vmlaq_n_u32 - vector-scalar "fused-multiply-add"
505
- * * vmlsq_u32 - vector "fused-multiply-subtract"
506
- * * vmlsq_n_u32 - vector-scalar "fused-multiply-subtract"
507
- * Other basic intrinsics worth remembering:
508
- * * vbslq_u32 - bitwise select to avoid branching
509
- * * vld1q_dup_u32 - broadcast a 32-bit word into all 4 lanes of a 128-bit register
510
- */
511
-
512
- SZ_PUBLIC void sz_hashes_neon_naive(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step, //
513
- sz_hash_callback_t callback, void *callback_handle) {
514
-
515
- if (length < window_length || !window_length) return;
516
- if (length < 2 * window_length) {
517
- sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
518
- return;
519
- }
520
-
521
- // Using NEON, we can perform 4 integer multiplications and additions within one register.
522
- // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
523
- sz_u8_t const *text = (sz_u8_t const *)start;
524
- sz_u8_t const *text_end = text + length;
525
-
526
- // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
527
- sz_u32_t prime_power_low = 1, prime_power_high = 1;
528
- for (sz_size_t i = 0; i + 1 < window_length; ++i)
529
- prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
530
- prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
531
-
532
- sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
533
- uint8_t high_shift = 77u;
534
- uint32_t prime = SZ_U32_MAX_PRIME;
535
-
536
- sz_u128_vec_t chars_outgoing_vec, chars_incoming_vec, chars_outgoing_shifted_vec, chars_incoming_shifted_vec;
537
- // Let's skip the first window, as we are going to compute it in the loop.
538
- sz_size_t cycles = 0;
539
- sz_size_t step_mask = step - 1;
540
- sz_u32_t one = 1;
541
-
542
- // In every iteration we process 4 consecutive sliding windows.
543
- // Once each of them computes separate values, we step forward (W-1) times,
544
- // computing all interleaving values. That way the byte spilled from the second
545
- // hash, can be added to the first one. That way we minimize the number of separate loads.
546
- for (; text + window_length * 4 + (window_length - 1) <= text_end; text += window_length * 4) {
547
- hash_low_vec.u32x4 = vld1q_dup_u32(&one);
548
- hash_high_vec.u32x4 = vld1q_dup_u32(&one);
549
- for (sz_size_t i = 0; i != window_length; ++i) {
550
- chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
551
- chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 1 + i);
552
- chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 2 + i);
553
- chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 3 + i);
554
- chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
555
-
556
- // Append new data.
557
- hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
558
- hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
559
- hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
560
- vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
561
- hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
562
- vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
563
- }
564
-
565
- if ((cycles & step_mask) == 0) {
566
- interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
567
- &hash_mix23_vec.u64x2);
568
- callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
569
- callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
570
- callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
571
- callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
572
- }
573
- ++cycles;
574
-
575
- for (sz_size_t i = 0; i + 1 != window_length; ++i, ++cycles) {
576
- // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
577
- // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
578
- chars_outgoing_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
579
- chars_outgoing_vec.u32s[1] = chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 1 + i);
580
- chars_outgoing_vec.u32s[2] = chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 2 + i);
581
- chars_outgoing_vec.u32s[3] = chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 3 + i);
582
- chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 4 + i);
583
- chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
584
- chars_outgoing_shifted_vec.u8x16 = vaddq_u8(chars_outgoing_vec.u8x16, vld1q_dup_u8(&high_shift));
585
-
586
- // Drop old data.
587
- hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_vec.u32x4, prime_power_low);
588
- hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_shifted_vec.u32x4, prime_power_high);
589
-
590
- // Append new data.
591
- hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
592
- hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
593
- hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
594
- vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
595
- hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
596
- vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
597
- // Mix and call the user if needed
598
- if ((cycles & step_mask) == 0) {
599
- interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
600
- &hash_mix23_vec.u64x2);
601
- callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
602
- callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
603
- callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
604
- callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
605
- }
606
- }
607
- }
608
- }
609
-
610
- 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,
611
- sz_hash_callback_t callback, void *callback_handle) {
612
-
613
- if (length < window_length || !window_length) return;
614
- if (length < 2 * window_length) {
615
- sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
616
- return;
617
- }
618
-
619
- // Using NEON, we can perform 4 integer multiplications and additions within one register.
620
- // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
621
- sz_u8_t const *text = (sz_u8_t const *)start;
622
- sz_u8_t const *text_end = text + length;
623
-
624
- // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
625
- sz_u32_t prime_power_low = 1, prime_power_high = 1;
626
- for (sz_size_t i = 0; i + 1 < window_length; ++i)
627
- prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
628
- prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
629
-
630
- sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
631
- uint8_t high_shift = 77u;
632
- uint32_t prime = SZ_U32_MAX_PRIME;
633
-
634
- sz_u128_vec_t chars_outgoing_vec, chars_incoming_vec, chars_outgoing_shifted_vec, chars_incoming_shifted_vec;
635
- // Let's skip the first window, as we are going to compute it in the loop.
636
- sz_size_t cycles = 0;
637
- sz_size_t const step_mask = step - 1;
638
- sz_u32_t const one = 1;
639
-
640
- // In every iteration we process 4 consecutive sliding windows.
641
- // Once each of them computes separate values, we step forward (W-1) times,
642
- // computing all interleaving values. That way the byte spilled from the second
643
- // hash, can be added to the first one. That way we minimize the number of separate loads.
644
- for (; text + window_length * 4 + (window_length - 1) <= text_end; text += window_length * 4) {
645
- hash_low_vec.u32x4 = vld1q_dup_u32(&one);
646
- hash_high_vec.u32x4 = vld1q_dup_u32(&one);
647
- for (sz_size_t i = 0; i != window_length; ++i) {
648
- chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
649
- chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 1 + i);
650
- chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 2 + i);
651
- chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 3 + i);
652
- chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
653
-
654
- // Append new data.
655
- hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
656
- hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
657
- hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
658
- vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
659
- hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
660
- vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
661
- }
662
-
663
- if ((cycles & step_mask) == 0) {
664
- interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
665
- &hash_mix23_vec.u64x2);
666
- callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
667
- callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
668
- callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
669
- callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
670
- }
671
- ++cycles;
672
-
673
- for (sz_size_t i = 0; i + 1 != window_length; ++i, ++cycles) {
674
- // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
675
- // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
676
- // Vectorizing these loads is a huge opportunity for performance optimizations, but naive prefetching
677
- // into the register just makes things worse.
678
- chars_outgoing_vec.u32s[0] = *(uint8_t const *)(text + window_length * 0 + i);
679
- chars_outgoing_vec.u32s[1] = chars_incoming_vec.u32s[0] = *(uint8_t const *)(text + window_length * 1 + i);
680
- chars_outgoing_vec.u32s[2] = chars_incoming_vec.u32s[1] = *(uint8_t const *)(text + window_length * 2 + i);
681
- chars_outgoing_vec.u32s[3] = chars_incoming_vec.u32s[2] = *(uint8_t const *)(text + window_length * 3 + i);
682
- chars_incoming_vec.u32s[3] = *(uint8_t const *)(text + window_length * 4 + i);
683
- chars_incoming_shifted_vec.u8x16 = vaddq_u8(chars_incoming_vec.u8x16, vld1q_dup_u8(&high_shift));
684
- chars_outgoing_shifted_vec.u8x16 = vaddq_u8(chars_outgoing_vec.u8x16, vld1q_dup_u8(&high_shift));
685
-
686
- // Drop old data.
687
- hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_vec.u32x4, prime_power_low);
688
- hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_shifted_vec.u32x4, prime_power_high);
689
-
690
- // Append new data.
691
- hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_vec.u32x4, hash_low_vec.u32x4, 31u);
692
- hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_shifted_vec.u32x4, hash_high_vec.u32x4, 257u);
693
- hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
694
- vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
695
- hash_high_vec.u32x4 = vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
696
- vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
697
-
698
- // Mix and call the user if needed
699
- if ((cycles & step_mask) == 0) {
700
- interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
701
- &hash_mix23_vec.u64x2);
702
- callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
703
- callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
704
- callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
705
- callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
706
- }
707
- }
708
- }
709
- }
710
-
711
- SZ_PUBLIC void sz_hashes_neon_readahead(sz_cptr_t start, sz_size_t length, sz_size_t window_length, sz_size_t step,
712
- sz_hash_callback_t callback, void *callback_handle) {
713
-
714
- if (length < window_length || !window_length) return;
715
- if (length < 2 * window_length) {
716
- sz_hashes_serial(start, length, window_length, step, callback, callback_handle);
717
- return;
718
- }
719
-
720
- // Using NEON, we can perform 4 integer multiplications and additions within one register.
721
- // So let's slice the entire string into 4 overlapping windows, to slide over them in parallel.
722
- sz_u8_t const *text = (sz_u8_t const *)start;
723
- sz_u8_t const *text_end = text + length;
724
-
725
- // Prepare the `prime ^ window_length` values, that we are going to use for modulo arithmetic.
726
- sz_u32_t prime_power_low = 1, prime_power_high = 1;
727
- for (sz_size_t i = 0; i + 1 < window_length; ++i)
728
- prime_power_low = (prime_power_low * 31ull) % SZ_U32_MAX_PRIME,
729
- prime_power_high = (prime_power_high * 257ull) % SZ_U32_MAX_PRIME;
730
-
731
- sz_u128_vec_t hash_low_vec, hash_high_vec, hash_mix01_vec, hash_mix23_vec;
732
- uint8_t high_shift = 77u;
733
- uint32_t prime = SZ_U32_MAX_PRIME;
734
-
735
- /// Primary buffers containing four upcasted characters as uint32_t values.
736
- sz_u128_vec_t chars_outgoing_low_vec, chars_incoming_low_vec;
737
- sz_u128_vec_t chars_outgoing_high_vec, chars_incoming_high_vec;
738
- // Let's skip the first window, as we are going to compute it in the loop.
739
- sz_size_t cycles = 0;
740
- sz_size_t const step_mask = step - 1;
741
- sz_u32_t const one = 1;
742
-
743
- // In every iteration we process 4 consecutive sliding windows.
744
- // Once each of them computes separate values, we step forward (W-1) times,
745
- // computing all interleaving values. That way the byte spilled from the second
746
- // hash, can be added to the first one. That way we minimize the number of separate loads.
747
- sz_size_t read_ahead_length = window_length - 1 + 16; // TODO: Instead of +16 round up to 16 multiple
748
- for (; text + window_length * 4 + read_ahead_length <= text_end; text += window_length * 4) {
749
- hash_low_vec.u32x4 = vld1q_dup_u32(&one);
750
- hash_high_vec.u32x4 = vld1q_dup_u32(&one);
751
-
752
- for (sz_size_t i = 0; i < window_length;) {
753
- sz_u128_vec_t chars_readahead_vec[4];
754
- chars_readahead_vec[0].u8x16 = vld1q_u8(text + window_length * 0 + i);
755
- chars_readahead_vec[1].u8x16 = vld1q_u8(text + window_length * 1 + i);
756
- chars_readahead_vec[2].u8x16 = vld1q_u8(text + window_length * 2 + i);
757
- chars_readahead_vec[3].u8x16 = vld1q_u8(text + window_length * 3 + i);
758
-
759
- for (; i != window_length; ++i) {
760
- chars_incoming_low_vec.u32s[0] = chars_readahead_vec[0].u8x16[i];
761
- chars_incoming_low_vec.u32s[1] = chars_readahead_vec[1].u8x16[i];
762
- chars_incoming_low_vec.u32s[2] = chars_readahead_vec[2].u8x16[i];
763
- chars_incoming_low_vec.u32s[3] = chars_readahead_vec[3].u8x16[i];
764
- chars_incoming_high_vec.u8x16 = vaddq_u8(chars_incoming_low_vec.u8x16, vld1q_dup_u8(&high_shift));
765
-
766
- // Append new data.
767
- hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_low_vec.u32x4, hash_low_vec.u32x4, 31u);
768
- hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_high_vec.u32x4, hash_high_vec.u32x4, 257u);
769
- hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
770
- vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
771
- hash_high_vec.u32x4 =
772
- vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
773
- vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
774
- }
775
- }
776
-
777
- if ((cycles & step_mask) == 0) {
778
- interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
779
- &hash_mix23_vec.u64x2);
780
- callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0], callback_handle);
781
- callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1], callback_handle);
782
- callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0], callback_handle);
783
- callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1], callback_handle);
784
- }
785
- ++cycles;
786
-
787
- for (sz_size_t i = 0; i + 1 < window_length; ++i, ++cycles) {
788
- // Now, to compute 4 hashes per iteration, instead of loading 8 separate bytes (4 incoming and 4 outgoing)
789
- // we can limit ourselves to only 5 values, 3 of which will be reused for both append and erase operations.
790
- sz_u128_vec_t chars_readahead_vec[5];
791
- chars_readahead_vec[0].u8x16 = vld1q_u8(text + window_length * 0 + i);
792
- chars_readahead_vec[1].u8x16 = vld1q_u8(text + window_length * 1 + i);
793
- chars_readahead_vec[2].u8x16 = vld1q_u8(text + window_length * 2 + i);
794
- chars_readahead_vec[3].u8x16 = vld1q_u8(text + window_length * 3 + i);
795
- chars_readahead_vec[4].u8x16 = vld1q_u8(text + window_length * 4 + i);
796
-
797
- for (; i + 1 < window_length; ++i) {
798
- // Transpose
799
- chars_outgoing_low_vec.u32s[0] = chars_readahead_vec[0].u8x16[i];
800
- chars_outgoing_low_vec.u32s[1] = chars_incoming_low_vec.u32s[0] = chars_readahead_vec[1].u8x16[i];
801
- chars_outgoing_low_vec.u32s[2] = chars_incoming_low_vec.u32s[1] = chars_readahead_vec[2].u8x16[i];
802
- chars_outgoing_low_vec.u32s[3] = chars_incoming_low_vec.u32s[2] = chars_readahead_vec[3].u8x16[i];
803
- chars_incoming_low_vec.u32s[3] = chars_readahead_vec[4].u8x16[i];
804
-
805
- chars_outgoing_high_vec.u8x16 = vaddq_u8(chars_outgoing_low_vec.u8x16, vld1q_dup_u8(&high_shift));
806
- chars_incoming_high_vec.u8x16 = vaddq_u8(chars_incoming_low_vec.u8x16, vld1q_dup_u8(&high_shift));
807
-
808
- // Drop old data.
809
- hash_low_vec.u32x4 = vmlsq_n_u32(hash_low_vec.u32x4, chars_outgoing_low_vec.u32x4, prime_power_low);
810
- hash_high_vec.u32x4 = vmlsq_n_u32(hash_high_vec.u32x4, chars_outgoing_high_vec.u32x4, prime_power_high);
811
-
812
- // Append new data.
813
- hash_low_vec.u32x4 = vmlaq_n_u32(chars_incoming_low_vec.u32x4, hash_low_vec.u32x4, 31u);
814
- hash_high_vec.u32x4 = vmlaq_n_u32(chars_incoming_high_vec.u32x4, hash_high_vec.u32x4, 257u);
815
- hash_low_vec.u32x4 = vbslq_u32(hash_low_vec.u32x4, vsubq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)),
816
- vcgtq_u32(hash_low_vec.u32x4, vld1q_dup_u32(&prime)));
817
- hash_high_vec.u32x4 =
818
- vbslq_u32(hash_high_vec.u32x4, vsubq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)),
819
- vcgtq_u32(hash_high_vec.u32x4, vld1q_dup_u32(&prime)));
820
-
821
- // Mix and call the user if needed
822
- if ((cycles & step_mask) == 0) {
823
- interleave_uint32x4_to_uint64x2(hash_low_vec.u32x4, hash_high_vec.u32x4, &hash_mix01_vec.u64x2,
824
- &hash_mix23_vec.u64x2);
825
- callback((sz_cptr_t)(text + window_length * 0), window_length, hash_mix01_vec.u64s[0],
826
- callback_handle);
827
- callback((sz_cptr_t)(text + window_length * 1), window_length, hash_mix01_vec.u64s[1],
828
- callback_handle);
829
- callback((sz_cptr_t)(text + window_length * 2), window_length, hash_mix23_vec.u64s[0],
830
- callback_handle);
831
- callback((sz_cptr_t)(text + window_length * 3), window_length, hash_mix23_vec.u64s[1],
832
- callback_handle);
833
- }
834
- }
835
- }
836
- }
837
- }
838
-
839
- #endif // SZ_USE_ARM_NEON
840
-
841
- #ifdef __cplusplus
842
- } // extern "C"
843
- #endif
844
-
845
- #endif // STRINGZILLA_EXPERIMENTAL_H_