-
Notifications
You must be signed in to change notification settings - Fork 135
Expand file tree
/
Copy pathstringzilla.h
More file actions
774 lines (688 loc) · 37.3 KB
/
Copy pathstringzilla.h
File metadata and controls
774 lines (688 loc) · 37.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
/**
* @brief StringZilla is a collection of advanced string algorithms, designed to be used in Big Data applications.
* It is generally faster than LibC, and has a broader & cleaner interface for safer @b length-bounded strings.
* On modern CPUs it uses AVX2, AVX-512, NEON, SVE, SVE2, WebAssembly, RISC-V, LoongArch & Power @b SIMD,
* with a SWAR fallback for older CPUs.
* On @b CUDA-capable GPUs it also provides C++ kernels for bulk processing.
*
* @file include/stringzilla/stringzilla.h
* @author Ash Vardanian
*
* @see StringZilla docs: https://github.com/ashvardanian/StringZilla/blob/main/README.md
* @see LibC string docs: https://pubs.opengroup.org/onlinepubs/009695399/basedefs/string.h.html
*
* @section Introduction
*
* StringZilla is multi-language project designed for high-throughput string processing, differentiating
* the low-level "embeddable" mostly-C core implementation, containing:
*
* - `compare.h` - byte-level comparison functions.
* - `memory.h` - copying, moving, and filling raw memory.
* - `hash.h` - hash functions and checksum algorithms.
* - `cipher.h` - AES-256 encryption in counter and Galois/counter modes.
* - `find.h` - searching for substrings and byte sets.
* - `sort.h` - single-threaded sorting algorithms.
* - `intersect.h` - intersections of unordered string sets.
* - `small_string.h` - "Small String Optimization" in C 99.
* - `stringzilla.h` - umbrella header for the core C API.
* - `stringzilla.hpp` - umbrella header for the core C++ API.
*
* It also provides many higher-level parallel algorithms, implemented in C++ with Fork Union and CUDA, also exposed
* via the stable C 99 ABI, but requiring C++17 and CUDA 17 compilers to build the shared @b StringZillas libraries:
*
* - `similarities.{hpp,cuh}` - similarity measures, like Levenshtein, Needleman-Wunsch, & Smith-Waterman scores.
* - `fingerprints.{hpp,cuh}` - feature extraction for TF-IDF and other Machine Learning algorithms.
*
* The core implementations of those algorithms are mostly structured as callable structure templates, as opposed to
* template functions to simplify specialized overloads and reusing the state between invocations.
*
* @section Compilation Settings
*
* Consider overriding the following macros to customize the library:
*
* - `SZ_DEBUG=0` - whether to enable debug assertions and logging.
* - `SZ_AVOID_LIBC=0` - whether to avoid including the standard C library headers.
* - `SZ_DYNAMIC_DISPATCH=0` - whether to use runtime dispatching of the most advanced SIMD backend.
* - `SZ_USE_MISALIGNED_LOADS=0` - whether to use misaligned loads on platforms that support them.
*
* Performance tuning:
*
* - `SZ_SWAR_THRESHOLD=24` - threshold for switching to SWAR backend over serial byte-level for-loops.
* - `SZ_CACHE_LINE_WIDTH=64` - cache-line width that affects the execution of some algorithms.
* - `SZ_CACHE_SIZE=1048576` - the combined size of L1d and L2 caches in bytes, affecting temporal loads.
*
* Different generations of CPUs and SIMD capabilities can be enabled or disabled with the following macros:
*
* - `SZ_USE_WESTMERE=?` - whether to use SSE4.2 & AES-NI instructions on x86_64.
* - `SZ_USE_GOLDMONT=?` - whether to use SHA-NI instructions on x86_64.
* - `SZ_USE_HASWELL=?` - whether to use AVX2 instructions on x86_64.
* - `SZ_USE_SKYLAKE=?` - whether to use AVX-512 instructions on x86_64.
* - `SZ_USE_ICELAKE=?` - whether to use AVX-512 VBMI & wider AES instructions on x86_64.
* - `SZ_USE_NEON=?` - whether to use NEON instructions on ARM.
* - `SZ_USE_NEONAES=?` - whether to use NEON AES instructions on ARM.
* - `SZ_USE_NEONSHA=?` - whether to use NEON SHA-2 instructions on ARM.
* - `SZ_USE_SVE=?` - whether to use SVE instructions on ARM.
* - `SZ_USE_SVE2=?` - whether to use SVE2 instructions on ARM.
* - `SZ_USE_SVE2AES=?` - whether to use SVE2 AES instructions on ARM.
* - `SZ_USE_V128=?` - whether to use WebAssembly SIMD128 instructions.
* - `SZ_USE_V128RELAXED=?` - whether to use WebAssembly relaxed-SIMD instructions.
* - `SZ_USE_RVV=?` - whether to use RISC-V Vector (RVV 1.0) instructions.
* - `SZ_USE_LASX=?` - whether to use LoongArch LASX instructions.
* - `SZ_USE_POWERVSX=?` - whether to use IBM Power VSX instructions.
* - `SZ_USE_CUDA=?` - whether to use minimal CUDA capabilities on Nvidia GPUs.
* - `SZ_USE_KEPLER=?` - whether to use Kepler-level instructions on Nvidia GPUs.
* - `SZ_USE_HOPPER=?` - whether to use Hopper-level instructions on Nvidia GPUs.
*/
#ifndef STRINGZILLA_H_
#define STRINGZILLA_H_
#define STRINGZILLA_H_VERSION_MAJOR 5
#define STRINGZILLA_H_VERSION_MINOR 1
#define STRINGZILLA_H_VERSION_PATCH 2
#include "stringzilla/types.h" // `sz_size_t`, `sz_bool_t`, `sz_ordering_t`
#include "stringzilla/compare.h" // `sz_equal`, `sz_order`
#include "stringzilla/memory.h" // `sz_copy`, `sz_move`, `sz_fill`
#include "stringzilla/hash.h" // `sz_bytesum`, `sz_hash`, `sz_state_init`, `sz_state_stream`, `sz_state_fold`
#include "stringzilla/cipher.h" // `sz_aes256_key_init`, `sz_aes256_ctr_xor`, `sz_aes256_gcm_encrypt`
#include "stringzilla/find.h" // `sz_find`, `sz_find_byteset`, `sz_rfind`
#include "stringzilla/sort.h" // `sz_sequence_argsort`, `sz_pgrams_sort`
#include "stringzilla/intersect.h" // `sz_sequence_intersect`
#include "stringzilla/utf8_runes.h" // `sz_utf8_count`, `sz_utf8_seek`, `sz_utf8_decode`
#include "stringzilla/utf8_tokens.h" // `sz_utf8_newlines`, `sz_utf8_whitespaces`, `sz_utf8_delimiters`
#include "stringzilla/utf8_wordbreaks.h" // `sz_utf8_wordbreaks`, `sz_rune_word_break_property`
#include "stringzilla/utf8_graphemes.h" // `sz_utf8_graphemes`
#include "stringzilla/utf8_sentences.h" // `sz_utf8_sentences`
#include "stringzilla/utf8_linebreaks.h" // `sz_utf8_linebreaks`
#include "stringzilla/utf8_uncased_fold.h" // `sz_utf8_uncased_fold`
#include "stringzilla/utf8_uncased.h" // `sz_utf8_uncased_search`, `sz_utf8_uncased_order`
#include "stringzilla/utf8_norm.h" // `sz_utf8_norm`, `sz_utf8_find_denormalized`
#include "stringzilla/small_string.h" // `sz_string_t`, `sz_string_init`, `sz_string_free`
/* Inferring target OS: Windows, MacOS, or Linux */
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) || defined(__CYGWIN__)
#define SZ_IS_WINDOWS_ 1
#elif defined(__APPLE__) && defined(__MACH__)
#define SZ_IS_APPLE_ 1
#elif defined(__linux__)
#define SZ_IS_LINUX_ 1
#elif defined(__FreeBSD__)
#define SZ_IS_FREEBSD_ 1
#endif
/* On Apple Silicon, `mrs` is not allowed in user-space, so we need to use the `sysctl` API */
#if defined(SZ_IS_APPLE_)
#include <sys/sysctl.h>
#endif
/* On 64-bit RISC-V we probe HWCAP via the auxiliary vector and vector sub-extensions via the
* Linux `riscv_hwprobe` syscall (FreeBSD lacks it and uses `elf_aux_info` for base RVV only). */
#if defined(__riscv) && (__riscv_xlen == 64) && !SZ_AVOID_LIBC
#if defined(SZ_IS_LINUX_)
#include <sys/auxv.h> // `getauxval`, `AT_HWCAP`
#include <sys/syscall.h> // `SYS_riscv_hwprobe`
#include <unistd.h> // `syscall`
#elif defined(SZ_IS_FREEBSD_)
#include <sys/auxv.h> // `elf_aux_info`, `AT_HWCAP`
#endif
#endif
/* On LoongArch and IBM POWER the SIMD extensions are likewise reported through the auxiliary vector. */
#if (defined(__loongarch__) || defined(__powerpc64__) || defined(__powerpc__)) && !SZ_AVOID_LIBC
#if defined(SZ_IS_LINUX_)
#include <sys/auxv.h> // `getauxval`, `AT_HWCAP`, `AT_HWCAP2`
#elif defined(SZ_IS_FREEBSD_)
#include <sys/auxv.h> // `elf_aux_info`, `AT_HWCAP`, `AT_HWCAP2`
#endif
#endif
/* Detect POSIX extensions availability for signal handling.
* POSIX extensions provide `sigaction`, `sigjmp_buf`, and `sigsetjmp` for safe signal handling.
* These are needed on Linux ARM for safely testing `mrs` instruction availability.
* `_POSIX_VERSION` only exists after `<unistd.h>` - without this include the check below was always
* false, the `mrs` path compiled out, and Linux-Arm detection silently degraded to NEON-only. */
#if defined(SZ_IS_LINUX_) && !SZ_AVOID_LIBC
#include <unistd.h>
#endif
#if defined(SZ_IS_LINUX_) && !SZ_AVOID_LIBC && defined(_POSIX_VERSION)
#include <setjmp.h>
#include <signal.h>
#define SZ_HAS_POSIX_EXTENSIONS_ 1
#else
#define SZ_HAS_POSIX_EXTENSIONS_ 0
#endif
/* On Windows ARM, we use IsProcessorFeaturePresent API for capability detection */
#if defined(SZ_IS_WINDOWS_) && SZ_IS_64BIT_ARM_
#define NOMINMAX
#include <windows.h>
#endif
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Internal helper function to convert SIMD capabilities to an array of string pointers.
* @param caps The capabilities bitfield.
* @param strings Output array to store string pointers (should have more than `SZ_CAPABILITIES_COUNT` slots).
* @param max_count Maximum number of strings to output.
* @return Number of capability strings written to the array.
* @sa sz_capabilities_to_string_implementation_, sz_capabilities
*/
SZ_HELPER_AUTO sz_size_t sz_capabilities_to_strings_implementation_(sz_capability_t caps, char const **strings,
sz_size_t max_count) {
// Mapping each flag to its string literal.
struct {
sz_capability_t flag;
char const *name;
} capability_map[] = {
//
{sz_cap_serial_k, "serial"},
{sz_cap_parallel_k, "parallel"},
//
{sz_cap_westmere_k, "westmere"},
{sz_cap_goldmont_k, "goldmont"},
{sz_cap_haswell_k, "haswell"},
{sz_cap_skylake_k, "skylake"},
{sz_cap_icelake_k, "icelake"},
//
{sz_cap_neon_k, "neon"},
{sz_cap_neonaes_k, "neonaes"},
{sz_cap_neonsha_k, "neonsha"},
{sz_cap_sve_k, "sve"},
{sz_cap_sve2_k, "sve2"},
{sz_cap_sve2aes_k, "sve2aes"},
//
{sz_cap_v128_k, "v128"},
{sz_cap_v128relaxed_k, "v128relaxed"},
{sz_cap_rvv_k, "rvv"},
{sz_cap_rvvcrypto_k, "rvvcrypto"},
{sz_cap_lasx_k, "lasx"},
{sz_cap_powervsx_k, "powervsx"},
//
{sz_cap_cuda_k, "cuda"},
{sz_cap_kepler_k, "kepler"},
{sz_cap_hopper_k, "hopper"},
};
int const capabilities_count = sizeof(capability_map) / sizeof(capability_map[0]);
// Iterate over each capability flag.
sz_size_t capability_count = 0;
for (int capability_index = 0; capability_index < capabilities_count && capability_count < max_count;
capability_index++)
if (caps & capability_map[capability_index].flag)
strings[capability_count++] = capability_map[capability_index].name;
return capability_count;
}
SZ_HELPER_AUTO sz_bool_t sz_equal_null_terminated_serial(char const *a, char const *b) {
if (!a || !b) return sz_false_k;
for (; *a && *b; a++, b++)
if (*a != *b) return sz_false_k;
// Both strings must terminate together; a one-sided check would let "neonaes" match the prefix "neon".
return (*a == '\0' && *b == '\0') ? sz_true_k : sz_false_k;
}
/**
* @brief Internal helper to map a capability name to its flag.
* @param name Capability name, e.g. "serial", "neon", "sve2aes".
* @return `sz_caps_none_k` if unknown name, or a valid capability flag.
*/
SZ_HELPER_AUTO sz_capability_t sz_capability_from_string_implementation_(char const *name) {
// CPU + execution model
if (sz_equal_null_terminated_serial(name, "serial") == sz_true_k) return sz_cap_serial_k;
if (sz_equal_null_terminated_serial(name, "parallel") == sz_true_k) return sz_cap_parallel_k;
// x86
if (sz_equal_null_terminated_serial(name, "westmere") == sz_true_k) return sz_cap_westmere_k;
if (sz_equal_null_terminated_serial(name, "goldmont") == sz_true_k) return sz_cap_goldmont_k;
if (sz_equal_null_terminated_serial(name, "haswell") == sz_true_k) return sz_cap_haswell_k;
if (sz_equal_null_terminated_serial(name, "skylake") == sz_true_k) return sz_cap_skylake_k;
if (sz_equal_null_terminated_serial(name, "icelake") == sz_true_k) return sz_cap_icelake_k;
// WebAssembly, RISC-V, LoongArch, Power
if (sz_equal_null_terminated_serial(name, "v128") == sz_true_k) return sz_cap_v128_k;
if (sz_equal_null_terminated_serial(name, "v128relaxed") == sz_true_k) return sz_cap_v128relaxed_k;
if (sz_equal_null_terminated_serial(name, "rvv") == sz_true_k) return sz_cap_rvv_k;
if (sz_equal_null_terminated_serial(name, "rvvcrypto") == sz_true_k) return sz_cap_rvvcrypto_k;
if (sz_equal_null_terminated_serial(name, "lasx") == sz_true_k) return sz_cap_lasx_k;
if (sz_equal_null_terminated_serial(name, "powervsx") == sz_true_k) return sz_cap_powervsx_k;
// Arm
if (sz_equal_null_terminated_serial(name, "neon") == sz_true_k) return sz_cap_neon_k;
if (sz_equal_null_terminated_serial(name, "sve") == sz_true_k) return sz_cap_sve_k;
if (sz_equal_null_terminated_serial(name, "sve2") == sz_true_k) return sz_cap_sve2_k;
if (sz_equal_null_terminated_serial(name, "neonaes") == sz_true_k) return sz_cap_neonaes_k;
if (sz_equal_null_terminated_serial(name, "neonsha") == sz_true_k) return sz_cap_neonsha_k;
if (sz_equal_null_terminated_serial(name, "sve2aes") == sz_true_k) return sz_cap_sve2aes_k;
// GPU
if (sz_equal_null_terminated_serial(name, "cuda") == sz_true_k) return sz_cap_cuda_k;
if (sz_equal_null_terminated_serial(name, "kepler") == sz_true_k) return sz_cap_kepler_k;
if (sz_equal_null_terminated_serial(name, "hopper") == sz_true_k) return sz_cap_hopper_k;
// Any
if (sz_equal_null_terminated_serial(name, "any") == sz_true_k) return sz_cap_any_k;
return sz_caps_none_k;
}
/**
* @brief Internal helper function to convert SIMD capabilities to a string.
* @sa sz_capabilities_to_string, sz_capabilities
*/
SZ_HELPER_AUTO sz_cptr_t sz_capabilities_to_string_implementation_(sz_capability_t caps) {
static char buffer[256];
char *p = buffer;
char *const end = buffer + sizeof(buffer);
// Use the new function to get capability strings
char const *cap_strings[SZ_CAPABILITIES_COUNT];
sz_size_t cap_count = sz_capabilities_to_strings_implementation_(caps, cap_strings, SZ_CAPABILITIES_COUNT);
// Build the comma-separated string
for (sz_size_t capability_index = 0; capability_index < cap_count; capability_index++) {
if (capability_index > 0) {
// Add separator if this is not the first capability.
char const sep[2] = {',', '\0'};
char const *s = sep;
while (*s && p < end - 1) *p++ = *s++;
}
// Append the capability name character by character.
char const *s = cap_strings[capability_index];
while (*s && p < end - 1) *p++ = *s++;
}
// Null-terminate the string.
*p = '\0';
return buffer;
}
/* The runtime detectors below report the FULL hardware capability set, independent of which `SZ_USE_*`
* tiers this build compiled in: `sz_capabilities` ANDs their result with the compile-time mask anyway,
* and the executable instructions involved are unconditionally safe - `cpuid` is baseline x86-64 with
* `xgetbv` behind the OSXSAVE check, and the Arm `mrs` reads sit behind a SIGILL-guarded probe with the
* `ID_AA64ZFR0_EL1` encoding enabled by the `target("+sve")` pragma that already wraps the whole
* detector. Keeping detection unconditional lets build-system probes (`probes/run_capabilities.c`)
* compile a serial-only translation unit and still learn what this machine runs, so the build can
* intersect it with what the toolchain compiles before any kernel is built.
*/
SZ_API_COMPTIME sz_capability_t sz_capabilities_comptime_implementation_(void) {
return (sz_capability_t)( //
(sz_cap_neon_k * SZ_USE_NEON) | //
(sz_cap_neonaes_k * SZ_USE_NEONAES) | //
(sz_cap_neonsha_k * SZ_USE_NEONSHA) | //
(sz_cap_sve_k * SZ_USE_SVE) | //
(sz_cap_sve2_k * SZ_USE_SVE2) | //
(sz_cap_sve2aes_k * SZ_USE_SVE2AES) | //
(sz_cap_westmere_k * SZ_USE_WESTMERE) | //
(sz_cap_goldmont_k * SZ_USE_GOLDMONT) | //
(sz_cap_haswell_k * SZ_USE_HASWELL) | //
(sz_cap_skylake_k * SZ_USE_SKYLAKE) | //
(sz_cap_icelake_k * SZ_USE_ICELAKE) | //
(sz_cap_v128_k * SZ_USE_V128) | //
(sz_cap_v128relaxed_k * SZ_USE_V128RELAXED) | //
(sz_cap_rvv_k * SZ_USE_RVV) | //
(sz_cap_rvvcrypto_k * SZ_USE_RVVCRYPTO) | //
(sz_cap_lasx_k * SZ_USE_LASX) | //
(sz_cap_powervsx_k * SZ_USE_POWERVSX) | //
(sz_cap_serial_k));
}
#if SZ_IS_64BIT_ARM_
/* Compiling the next section one may get: selected processor does not support system register name 'id_aa64zfr0_el1'.
* Suppressing assembler errors is very complicated, so when dealing with older ARM CPUs it's simpler to compile this
* function targeting newer ones.
*/
#if defined(__clang__)
#pragma clang attribute push(__attribute__((target("+sve"))), apply_to = function)
#elif defined(__GNUC__)
#pragma GCC push_options
#pragma GCC target("+sve")
#endif
#if SZ_HAS_POSIX_EXTENSIONS_
/** @brief SIGILL handler for `mrs` instruction testing on Linux ARM */
static sigjmp_buf sz_mrs_test_jump_buffer_;
static void sz_mrs_test_sigill_handler_(int sig) {
sz_unused_(sig);
siglongjmp(sz_mrs_test_jump_buffer_, 1);
}
#endif
/**
* @brief Function to determine the SIMD capabilities of the current 64-bit Arm machine at @b runtime.
* @return A bitmask of the SIMD capabilities represented as a `sz_capability_t` enum value.
*/
SZ_API_COMPTIME sz_capability_t sz_capabilities_implementation_arm_(void) {
// https://github.com/ashvardanian/SimSIMD/blob/28e536083602f85ad0c59456782c8864463ffb0e/include/simsimd/simsimd.h#L434
// for documentation on how we detect capabilities across different ARM platforms.
#if defined(SZ_IS_APPLE_)
// On Apple Silicon, `mrs` is not allowed in user-space, so we need to use the `sysctl` API.
uint32_t supports_neon = 0;
uint32_t supports_neonaes = 0;
uint32_t supports_neonsha = 0;
size_t size = sizeof(supports_neon);
if (sysctlbyname("hw.optional.neon", &supports_neon, &size, NULL, 0) != 0) supports_neon = 0;
if (sysctlbyname("hw.optional.arm.FEAT_AES", &supports_neonaes, &size, NULL, 0) != 0) supports_neonaes = 0;
if (sysctlbyname("hw.optional.arm.FEAT_SHA256", &supports_neonsha, &size, NULL, 0) != 0) supports_neonsha = 0;
return (sz_capability_t)( //
(sz_cap_neon_k * (supports_neon)) | //
(sz_cap_neonaes_k * (supports_neonaes)) | //
(sz_cap_neonsha_k * (supports_neonsha)) | //
(sz_cap_serial_k));
#elif defined(SZ_IS_LINUX_)
// Depending on the environment, reading system registers may cause SIGILL.
// One option to avoid the crash is to use `getauxval(AT_HWCAP)` and `getauxval(AT_HWCAP2)`,
// Linux APIs, but those aren't as informative as reading the registers directly.
// So before reading the ID registers, we set up a signal handler to catch SIGILL
// and probe one of the registers, reverting back to the old signal handler afterwards.
//
// This issue was originally observed in SimSIMD: https://github.com/ashvardanian/SimSIMD/issues/279
#if SZ_HAS_POSIX_EXTENSIONS_
struct sigaction action_new, action_old;
action_new.sa_handler = sz_mrs_test_sigill_handler_;
sigemptyset(&action_new.sa_mask);
action_new.sa_flags = 0;
int mrs_works = 0;
if (sigaction(SIGILL, &action_new, &action_old) == 0) {
if (sigsetjmp(sz_mrs_test_jump_buffer_, 1) == 0) {
unsigned long midr_value;
__asm__ __volatile__("mrs %0, MIDR_EL1" : "=r"(midr_value));
mrs_works = 1;
}
sigaction(SIGILL, &action_old, NULL);
}
// Early exit if `mrs` doesn't work - return conservative NEON-only capabilities
if (!mrs_works) return (sz_capability_t)(sz_cap_neon_k | sz_cap_serial_k);
#else // SZ_HAS_POSIX_EXTENSIONS_
// Without POSIX signal handlers, fall back to conservative NEON capabilities.
return (sz_capability_t)(sz_cap_neon_k | sz_cap_serial_k);
#endif // SZ_HAS_POSIX_EXTENSIONS_
// Read CPUID registers directly
unsigned long id_aa64isar0_el1 = 0, id_aa64isar1_el1 = 0, id_aa64pfr0_el1 = 0, id_aa64zfr0_el1 = 0;
unsigned supports_neon = 0, supports_neonaes = 0, supports_neonsha = 0, supports_sve = 0, supports_sve2 = 0,
supports_sve2aes = 0;
sz_unused_(id_aa64isar0_el1);
sz_unused_(id_aa64isar1_el1);
sz_unused_(id_aa64pfr0_el1);
sz_unused_(id_aa64zfr0_el1);
// Now let's unpack the status flags from ID_AA64ISAR0_EL1
// https://developer.arm.com/documentation/ddi0601/2024-03/AArch64-Registers/ID-AA64ISAR0-EL1--AArch64-Instruction-Set-Attribute-Register-0?lang=en
__asm__ __volatile__("mrs %0, ID_AA64ISAR0_EL1" : "=r"(id_aa64isar0_el1));
// Now let's unpack the status flags from ID_AA64ISAR1_EL1
// https://developer.arm.com/documentation/ddi0601/2024-03/AArch64-Registers/ID-AA64ISAR1-EL1--AArch64-Instruction-Set-Attribute-Register-1?lang=en
__asm__ __volatile__("mrs %0, ID_AA64ISAR1_EL1" : "=r"(id_aa64isar1_el1));
// Now let's unpack the status flags from ID_AA64PFR0_EL1
// https://developer.arm.com/documentation/ddi0601/2024-03/AArch64-Registers/ID-AA64PFR0-EL1--AArch64-Processor-Feature-Register-0?lang=en
__asm__ __volatile__("mrs %0, ID_AA64PFR0_EL1" : "=r"(id_aa64pfr0_el1));
// AdvSIMD, bits [23:20] of ID_AA64PFR0_EL1 can be used to check for `fp16` support
// - 0b0000: integers, single, double precision arithmetic
// - 0b0001: includes support for half-precision floating-point arithmetic
// - 0b1111: NEON is not supported?!
// That's a really weird way to encode lack of NEON support, but it's important to
// check in case we are running on R-profile CPUs.
supports_neon = ((id_aa64pfr0_el1 >> 20) & 0xF) != 0xF;
// AES, bits [7:4] of ID_AA64ISAR0_EL1
supports_neonaes = ((id_aa64isar0_el1 >> 4) & 0xF) >= 1;
// SHA2, bits [15:12] of ID_AA64ISAR0_EL1
supports_neonsha = ((id_aa64isar0_el1 >> 12) & 0xF) >= 1;
// SVE, bits [35:32] of ID_AA64PFR0_EL1
supports_sve = ((id_aa64pfr0_el1 >> 32) & 0xF) >= 1;
// Now let's unpack the status flags from ID_AA64ZFR0_EL1
// https://developer.arm.com/documentation/ddi0601/2024-03/AArch64-Registers/ID-AA64ZFR0-EL1--SVE-Feature-ID-Register-0?lang=en
if (supports_sve) __asm__ __volatile__("mrs %0, ID_AA64ZFR0_EL1" : "=r"(id_aa64zfr0_el1));
// SVEver, bits [3:0] can be used to check for capability levels:
// - 0b0000: SVE is implemented
// - 0b0001: SVE2 is implemented
// - 0b0010: SVE2.1 is implemented
// This value must match the existing indicator obtained from ID_AA64PFR0_EL1:
supports_sve2 = ((id_aa64zfr0_el1) & 0xF) >= 1;
supports_sve2aes = ((id_aa64zfr0_el1 >> 4) & 0xF) >= 1;
return (sz_capability_t)( //
(sz_cap_neon_k * (supports_neon)) | //
(sz_cap_neonaes_k * (supports_neonaes)) | //
(sz_cap_neonsha_k * (supports_neonsha)) | //
(sz_cap_sve_k * (supports_sve)) | //
(sz_cap_sve2_k * (supports_sve2)) | //
(sz_cap_sve2aes_k * (supports_sve2aes)) | //
(sz_cap_serial_k));
#elif defined(SZ_IS_WINDOWS_)
// On Windows ARM, use the `IsProcessorFeaturePresent` API for capability detection.
// https://learn.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-isprocessorfeaturepresent
unsigned supports_neon = IsProcessorFeaturePresent(PF_ARM_V8_INSTRUCTIONS_AVAILABLE);
unsigned supports_crypto = IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE);
return (sz_capability_t)( //
(sz_cap_neon_k * (supports_neon)) | //
(sz_cap_neonaes_k * (supports_crypto)) | //
(sz_cap_neonsha_k * (supports_crypto)) | //
(sz_cap_serial_k));
#else // Unknown platform
// Conservative fallback for unknown platforms: NEON is mandatory in ARMv8-A (ARM64)
return (sz_capability_t)(sz_cap_neon_k | sz_cap_serial_k);
#endif
}
#if defined(__clang__)
#pragma clang attribute pop
#elif defined(__GNUC__)
#pragma GCC pop_options
#endif
#endif // SZ_IS_64BIT_ARM_
#if SZ_IS_64BIT_X86_
SZ_API_COMPTIME sz_capability_t sz_capabilities_implementation_x86_(void) {
/// The states of 4 registers populated for a specific "cpuid" assembly call
union four_registers_t {
int array[4];
struct separate_t {
unsigned eax, ebx, ecx, edx;
} named;
} info0, info1, info7;
#if defined(_MSC_VER) && !defined(__clang__)
__cpuidex(info0.array, 0, 0);
__cpuidex(info1.array, 1, 0);
__cpuidex(info7.array, 7, 0);
#else
__asm__ __volatile__( //
"cpuid"
: "=a"(info0.named.eax), "=b"(info0.named.ebx), "=c"(info0.named.ecx), "=d"(info0.named.edx)
: "a"(0), "c"(0));
__asm__ __volatile__( //
"cpuid"
: "=a"(info1.named.eax), "=b"(info1.named.ebx), "=c"(info1.named.ecx), "=d"(info1.named.edx)
: "a"(1), "c"(0));
__asm__ __volatile__( //
"cpuid"
: "=a"(info7.named.eax), "=b"(info7.named.ebx), "=c"(info7.named.ecx), "=d"(info7.named.edx)
: "a"(7), "c"(0));
#endif
// Querying a leaf above the highest supported one returns the highest leaf's data, not zeros, so on
// early x86-64 parts (max basic leaf below 7) the "leaf 7" registers would hold unrelated bits. The
// AVX family is already masked by the XGETBV/OSXSAVE chain below, but SHA-NI is read from leaf 7
// unmasked - so zero the whole leaf when it does not exist rather than trust garbage.
if (info0.named.eax < 7) info7.named.eax = info7.named.ebx = info7.named.ecx = info7.named.edx = 0;
// Gate AVX/AVX-512 on OS-enabled extended state (XGETBV)
unsigned has_osxsave = (info1.named.ecx & (1u << 27)) != 0; // OSXSAVE
unsigned has_avx = (info1.named.ecx & (1u << 28)) != 0; // AVX
unsigned long long xcr0 = 0;
if (has_osxsave) {
#if defined(_MSC_VER) && !defined(__clang__)
xcr0 = _xgetbv(0);
#else
unsigned eax, edx;
__asm__ __volatile__(".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c"(0)); // xgetbv
xcr0 = ((unsigned long long)edx << 32) | eax;
#endif
}
unsigned os_avx_enabled = has_osxsave && has_avx && ((xcr0 & 0x6u) == 0x6u); // XMM+YMM
unsigned os_avx512_enabled = os_avx_enabled && ((xcr0 & 0xE0u) == 0xE0u); // OPMASK+ZMM
// Check for AVX2/AVX-512 (Function ID 7), masked by OS state
// https://github.com/llvm/llvm-project/blob/50598f0ff44f3a4e75706f8c53f3380fe7faa896/clang/lib/Headers/cpuid.h#L148
unsigned supports_avx2 = os_avx_enabled && ((info7.named.ebx & 0x00000020u) != 0);
unsigned supports_avx512f = os_avx512_enabled && ((info7.named.ebx & 0x00010000u) != 0);
unsigned supports_avx512bw = os_avx512_enabled && ((info7.named.ebx & 0x40000000u) != 0);
unsigned supports_avx512vl = os_avx512_enabled && ((info7.named.ebx & 0x80000000u) != 0);
unsigned supports_avx512vbmi = os_avx512_enabled && ((info7.named.ecx & 0x00000002u) != 0);
unsigned supports_avx512vbmi2 = os_avx512_enabled && ((info7.named.ecx & 0x00000040u) != 0);
unsigned supports_vaes = os_avx512_enabled && ((info7.named.ecx & 0x00000200u) != 0);
// Check for SSE4.2, AES-NI, and SHA-NI (Function ID 1 and 7)
unsigned supports_sse42 = ((info1.named.ecx & 0x00100000u) != 0);
unsigned supports_aesni = ((info1.named.ecx & 0x02000000u) != 0);
unsigned supports_shani = ((info7.named.ebx & 0x20000000u) != 0); // SHA-NI bit 29 in EBX from CPUID(7,0)
return (sz_capability_t)( //
(sz_cap_westmere_k * (supports_sse42 && supports_aesni)) | //
(sz_cap_goldmont_k * (supports_shani)) | //
(sz_cap_haswell_k * (supports_avx2)) | //
(sz_cap_skylake_k * (supports_avx512f && supports_avx512vl && supports_avx512bw)) | //
(sz_cap_icelake_k * (supports_avx512vbmi && supports_avx512vbmi2 && supports_vaes)) | //
(sz_cap_serial_k));
}
#endif // SZ_IS_64BIT_X86_
#if defined(__riscv) && (__riscv_xlen == 64)
/**
* @brief Function to determine the SIMD capabilities of the current 64-bit RISC-V machine at @b runtime.
* @return A bitmask of the SIMD capabilities represented as a `sz_capability_t` enum value.
*/
SZ_HELPER_AUTO sz_capability_t sz_capabilities_implementation_riscv_(void) {
#if defined(SZ_IS_LINUX_) && !SZ_AVOID_LIBC
// The base "V" extension is reported through the auxiliary vector, but the individual
// vector sub-extensions (vector crypto, bf16, …) are only exposed through the
// `riscv_hwprobe(2)` syscall (number 258), introduced in Linux 6.4.
unsigned long hwcap = getauxval(AT_HWCAP);
sz_capability_t caps = sz_cap_serial_k;
// HWCAP bit 21 (`COMPAT_HWCAP_ISA_V`, i.e. `1UL << ('V' - 'A')`) marks RVV 1.0.
if (hwcap & (1UL << 21)) {
caps = (sz_capability_t)(caps | sz_cap_rvv_k);
// `riscv_hwprobe(2)`: fill an array of {key, value} pairs. We query a single key,
// `RISCV_HWPROBE_KEY_IMA_EXT_0` (= 4), whose value carries the extension bitmask.
// Constants confirmed against `/usr/riscv64-linux-gnu/include/asm/hwprobe.h`:
// RISCV_HWPROBE_KEY_IMA_EXT_0 == 4
// RISCV_HWPROBE_EXT_ZVKNED == (1 << 21) // Zvkned (AES)
// RISCV_HWPROBE_EXT_ZVKNHB == (1 << 23) // Zvknhb (SHA-256/512)
struct {
long long key;
unsigned long long value;
} pairs[1];
pairs[0].key = 4; // RISCV_HWPROBE_KEY_IMA_EXT_0
pairs[0].value = 0;
// `long syscall(SYS_riscv_hwprobe, pairs, pair_count, cpu_count, cpus, flags)`.
if (syscall(258, pairs, (unsigned long)1, (unsigned long)0, (void *)0, (unsigned long)0) == 0) {
unsigned long long const has_zvkned = pairs[0].value & (1ULL << 21); // RISCV_HWPROBE_EXT_ZVKNED
unsigned long long const has_zvknhb = pairs[0].value & (1ULL << 23); // RISCV_HWPROBE_EXT_ZVKNHB
if (has_zvkned && has_zvknhb) caps = (sz_capability_t)(caps | sz_cap_rvvcrypto_k);
}
}
return caps;
#elif defined(SZ_IS_FREEBSD_) && !SZ_AVOID_LIBC
// FreeBSD exposes HWCAP through `elf_aux_info`, but lacks the Linux `riscv_hwprobe`
// syscall, so the vector crypto sub-extensions stay compile-time only here.
unsigned long hwcap = 0;
elf_aux_info(AT_HWCAP, &hwcap, sizeof(hwcap));
sz_capability_t caps = sz_cap_serial_k;
if (hwcap & (1UL << 21)) caps = (sz_capability_t)(caps | sz_cap_rvv_k);
return caps;
#else
// Without a portable runtime probe, mirror the compile-time capabilities.
return sz_capabilities_comptime_implementation_();
#endif
}
#endif // defined(__riscv) && (__riscv_xlen == 64)
#if defined(__loongarch__)
/**
* @brief Function to determine the SIMD capabilities of the current LoongArch machine at @b runtime.
* @return A bitmask of the SIMD capabilities represented as a `sz_capability_t` enum value.
*/
SZ_HELPER_AUTO sz_capability_t sz_capabilities_implementation_loongarch_(void) {
#if defined(SZ_IS_LINUX_) && !SZ_AVOID_LIBC
// The SIMD extensions are reported through the auxiliary vector, matching `asm/hwcap.h`:
// HWCAP_LOONGARCH_LSX == (1 << 4) // 128-bit SIMD
// HWCAP_LOONGARCH_LASX == (1 << 5) // 256-bit SIMD, implies LSX
unsigned long hwcap = getauxval(AT_HWCAP);
return (sz_capability_t)((sz_cap_lasx_k * ((hwcap & (1UL << 5)) != 0)) | sz_cap_serial_k);
#else
// Without a portable runtime probe, mirror the compile-time capabilities.
return sz_capabilities_comptime_implementation_();
#endif
}
#endif // defined(__loongarch__)
#if defined(__powerpc64__) || defined(__powerpc__)
/**
* @brief Function to determine the SIMD capabilities of the current IBM POWER machine at @b runtime.
* @return A bitmask of the SIMD capabilities represented as a `sz_capability_t` enum value.
*/
SZ_HELPER_AUTO sz_capability_t sz_capabilities_implementation_power_(void) {
#if (defined(SZ_IS_LINUX_) || defined(SZ_IS_FREEBSD_)) && !SZ_AVOID_LIBC
// The `powervsx` kernels target POWER9 (`-mcpu=power9 -mvsx`), so both facts are required,
// matching the constants in `arch/powerpc/include/uapi/asm/cputable.h`:
// PPC_FEATURE_HAS_VSX == 0x00000080 // in AT_HWCAP
// PPC_FEATURE2_ARCH_3_00 == 0x00800000 // in AT_HWCAP2, the POWER9 ISA level
unsigned long hwcap = 0, hwcap2 = 0;
#if defined(SZ_IS_LINUX_)
hwcap = getauxval(AT_HWCAP);
hwcap2 = getauxval(AT_HWCAP2);
#else
elf_aux_info(AT_HWCAP, &hwcap, sizeof(hwcap));
elf_aux_info(AT_HWCAP2, &hwcap2, sizeof(hwcap2));
#endif
unsigned const supports_powervsx = ((hwcap & 0x00000080UL) != 0) && ((hwcap2 & 0x00800000UL) != 0);
return (sz_capability_t)((sz_cap_powervsx_k * supports_powervsx) | sz_cap_serial_k);
#else
// Without a portable runtime probe, mirror the compile-time capabilities.
return sz_capabilities_comptime_implementation_();
#endif
}
#endif // defined(__powerpc64__) || defined(__powerpc__)
/**
* @brief Whether `sz_capabilities_runtime_implementation_` performs real hardware introspection on this
* platform, or merely mirrors the compile-time mask because no portable probe exists.
*
* This is the header-owned source of truth the build systems infer from - the run probe
* (`probes/run_capabilities.c`) reports "no answer" when it is 0, and a compile probe
* (`probes/runtime_detection.c`) lets cross builds ask the same question without executing anything -
* so neither CMake nor `build.rs` hard-codes platform lists that could drift from the detectors here.
* WebAssembly stays 0 by nature: a module carrying unsupported SIMD opcodes fails validation at
* instantiation, so not even load-time masking is possible there.
*/
#if SZ_IS_64BIT_X86_ || SZ_IS_64BIT_ARM_
#define SZ_CAPABILITIES_RUNTIME_DETECTABLE_ (1)
#elif defined(__riscv) && (__riscv_xlen == 64) && (defined(SZ_IS_LINUX_) || defined(SZ_IS_FREEBSD_)) && !SZ_AVOID_LIBC
#define SZ_CAPABILITIES_RUNTIME_DETECTABLE_ (1)
#elif defined(__loongarch__) && defined(SZ_IS_LINUX_) && !SZ_AVOID_LIBC
#define SZ_CAPABILITIES_RUNTIME_DETECTABLE_ (1)
#elif (defined(__powerpc64__) || defined(__powerpc__)) && (defined(SZ_IS_LINUX_) || defined(SZ_IS_FREEBSD_)) && \
!SZ_AVOID_LIBC
#define SZ_CAPABILITIES_RUNTIME_DETECTABLE_ (1)
#else
#define SZ_CAPABILITIES_RUNTIME_DETECTABLE_ (0)
#endif
/**
* @brief Function to determine the SIMD capabilities of the current CPU at @b runtime.
* @return A bitmask of the SIMD capabilities represented as a `sz_capability_t` enum value.
* @note Excludes parallel-processing & GPGPU capabilities, which are detected separately in StringZillas.
*/
SZ_API_COMPTIME sz_capability_t sz_capabilities_runtime_implementation_(void) {
#if !SZ_CAPABILITIES_RUNTIME_DETECTABLE_
// WebAssembly and OS-less exotic targets expose their SIMD support at compile time only,
// so runtime capabilities mirror compile-time ones.
return sz_capabilities_comptime_implementation_();
#elif SZ_IS_64BIT_X86_
return sz_capabilities_implementation_x86_();
#elif SZ_IS_64BIT_ARM_
return sz_capabilities_implementation_arm_();
#elif defined(__riscv) && (__riscv_xlen == 64)
return sz_capabilities_implementation_riscv_();
#elif defined(__loongarch__)
return sz_capabilities_implementation_loongarch_();
#elif defined(__powerpc64__) || defined(__powerpc__)
return sz_capabilities_implementation_power_();
#else
return sz_capabilities_comptime_implementation_();
#endif
}
#if SZ_DYNAMIC_DISPATCH
SZ_API_RUNTIME int sz_dynamic_dispatch(void);
SZ_API_RUNTIME int sz_version_major(void);
SZ_API_RUNTIME int sz_version_minor(void);
SZ_API_RUNTIME int sz_version_patch(void);
SZ_API_RUNTIME sz_capability_t sz_capabilities_comptime(void);
SZ_API_RUNTIME sz_capability_t sz_capabilities_runtime(void);
SZ_API_RUNTIME sz_capability_t sz_capabilities(void);
SZ_API_RUNTIME sz_cptr_t sz_capabilities_to_string(sz_capability_t caps);
SZ_API_RUNTIME void sz_dispatch_table_init(void);
SZ_API_RUNTIME void sz_dispatch_table_update(sz_capability_t caps);
#else
// These public entry points are `SZ_API_RUNTIME` so they export as external symbols when this header is
// compiled into the amalgamation TU with `SZ_EXPORT` (compile-time dispatch as a linkable library);
// for plain header-only inclusion `SZ_API_RUNTIME` is `inline static`, same as the rest of the API.
SZ_API_RUNTIME int sz_dynamic_dispatch(void) { return 0; }
SZ_API_RUNTIME int sz_version_major(void) { return STRINGZILLA_H_VERSION_MAJOR; }
SZ_API_RUNTIME int sz_version_minor(void) { return STRINGZILLA_H_VERSION_MINOR; }
SZ_API_RUNTIME int sz_version_patch(void) { return STRINGZILLA_H_VERSION_PATCH; }
SZ_API_RUNTIME sz_capability_t sz_capabilities_comptime(void) { return sz_capabilities_comptime_implementation_(); }
SZ_API_RUNTIME sz_capability_t sz_capabilities_runtime(void) { return sz_capabilities_runtime_implementation_(); }
SZ_API_RUNTIME sz_capability_t sz_capabilities(void) {
return (sz_capability_t)(sz_capabilities_comptime_implementation_() & sz_capabilities_runtime_implementation_());
}
SZ_API_RUNTIME sz_cptr_t sz_capabilities_to_string(sz_capability_t caps) {
return sz_capabilities_to_string_implementation_(caps);
}
SZ_API_RUNTIME void sz_dispatch_table_init(void) {}
SZ_API_RUNTIME void sz_dispatch_table_update(sz_capability_t caps) { sz_unused_(caps); } // No-op in non-dynamic builds
#endif
#ifdef __cplusplus
}
#endif // __cplusplus
#endif // STRINGZILLA_H_