File Coverage

utf8_simd.h
Criterion Covered Total %
statement 0 28 0.0
branch 0 6 0.0
condition n/a
subroutine n/a
pod n/a
total 0 34 0.0


line stmt bran cond sub pod time code
1             /*
2             * Copyright (c) 2026 Christian Hansen
3             *
4             *
5             * Permission is hereby granted, free of charge, to any person obtaining a copy
6             * of this software and associated documentation files (the "Software"), to deal
7             * in the Software without restriction, including without limitation the rights
8             * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9             * copies of the Software, and to permit persons to whom the Software is
10             * furnished to do so, subject to the following conditions:
11             *
12             * The above copyright notice and this permission notice shall be included in all
13             * copies or substantial portions of the Software.
14             *
15             * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16             * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17             * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18             * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19             * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20             * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21             * SOFTWARE.
22             */
23              
24             /*
25             * utf8_simd.h -- SIMD primitives for UTF-8
26             * =========================================
27             *
28             * Provides utf8_simd_count_codepoints_Nx32 when a supported SIMD
29             * instruction set is available at compile time. The header may be
30             * included unconditionally; callers check UTF8_SIMD_AVAILABLE before
31             * calling any function.
32             *
33             * Counts non-continuation bytes (signed > -65) in SIMD lanes.
34             * Comparison results (0xFF / 0x00) accumulate in a byte-wide vector;
35             * a periodic flush widens to 64-bit before overflow.
36             *
37             * Batch limits per flush:
38             * AVX2 — 255 blocks (one 32-byte load, max 1 per byte per block)
39             * SSE2 — 127 blocks (two 16-byte loads added, max 2 per byte per block)
40             * NEON — 127 blocks (same)
41             */
42             #ifndef UTF8_SIMD_H
43             #define UTF8_SIMD_H
44             #include
45             #include
46              
47             #if defined(__AVX2__)
48             # define UTF8_SIMD_HAS_AVX2 1
49             # include
50             #elif defined(__SSE2__) || defined(_M_X64) || (defined(_M_IX86_FP) && (_M_IX86_FP >= 2))
51             # define UTF8_SIMD_HAS_SSE2 1
52             # include
53             #elif defined(__aarch64__)
54             # define UTF8_SIMD_HAS_NEON 1
55             # include
56             #endif
57              
58             #if defined(UTF8_SIMD_HAS_AVX2) || defined(UTF8_SIMD_HAS_SSE2) || defined(UTF8_SIMD_HAS_NEON)
59             # define UTF8_SIMD_AVAILABLE 1
60             #endif
61              
62             #ifdef UTF8_SIMD_AVAILABLE
63              
64             #ifdef __cplusplus
65             extern "C" {
66             #endif
67              
68             /*
69             * utf8_simd_count_codepoints_Nx32 -- count codepoints in n 32-byte blocks.
70             *
71             * src MUST point to well-formed UTF-8. No validation is performed.
72             * Returns the number of codepoints in src[0..n*32).
73             */
74 0           static inline size_t utf8_simd_count_codepoints_Nx32(const void *src, size_t n) {
75 0           const uint8_t *bytes = (const uint8_t *)src;
76              
77             #if defined(UTF8_SIMD_HAS_AVX2)
78             const __m256i threshold = _mm256_set1_epi8(-65);
79             const __m256i zero = _mm256_setzero_si256();
80             __m256i acc64 = zero;
81              
82             while (n > 0) {
83             size_t batch = n < 255 ? n : 255;
84             n -= batch;
85             __m256i acc8 = zero;
86             for (; batch >= 4; batch -= 4) {
87             __m256i v0 = _mm256_loadu_si256((const __m256i *)bytes);
88             __m256i v1 = _mm256_loadu_si256((const __m256i *)(bytes + 32));
89             __m256i v2 = _mm256_loadu_si256((const __m256i *)(bytes + 64));
90             __m256i v3 = _mm256_loadu_si256((const __m256i *)(bytes + 96));
91             acc8 = _mm256_sub_epi8(acc8, _mm256_cmpgt_epi8(v0, threshold));
92             acc8 = _mm256_sub_epi8(acc8, _mm256_cmpgt_epi8(v1, threshold));
93             acc8 = _mm256_sub_epi8(acc8, _mm256_cmpgt_epi8(v2, threshold));
94             acc8 = _mm256_sub_epi8(acc8, _mm256_cmpgt_epi8(v3, threshold));
95             bytes += 128;
96             }
97             for (; batch > 0; batch--) {
98             __m256i v = _mm256_loadu_si256((const __m256i *)bytes);
99             acc8 = _mm256_sub_epi8(acc8, _mm256_cmpgt_epi8(v, threshold));
100             bytes += 32;
101             }
102             acc64 = _mm256_add_epi64(acc64, _mm256_sad_epu8(acc8, zero));
103             }
104              
105             __m128i lo = _mm256_castsi256_si128(acc64);
106             __m128i hi = _mm256_extracti128_si256(acc64, 1);
107             __m128i sum = _mm_add_epi64(lo, hi);
108             sum = _mm_add_epi64(sum, _mm_srli_si128(sum, 8));
109             return (size_t)_mm_cvtsi128_si64(sum);
110              
111             #elif defined(UTF8_SIMD_HAS_SSE2)
112 0           const __m128i threshold = _mm_set1_epi8(-65);
113 0           const __m128i zero = _mm_setzero_si128();
114 0           __m128i acc64 = zero;
115              
116 0 0         while (n > 0) {
117 0           size_t batch = n < 127 ? n : 127;
118 0           n -= batch;
119 0           __m128i acc8 = zero;
120 0 0         for (; batch >= 2; batch -= 2) {
121 0           __m128i a0 = _mm_loadu_si128((const __m128i *)bytes);
122 0           __m128i a1 = _mm_loadu_si128((const __m128i *)(bytes + 16));
123 0           __m128i b0 = _mm_loadu_si128((const __m128i *)(bytes + 32));
124 0           __m128i b1 = _mm_loadu_si128((const __m128i *)(bytes + 48));
125 0           __m128i ca = _mm_add_epi8(_mm_cmpgt_epi8(a0, threshold),
126             _mm_cmpgt_epi8(a1, threshold));
127 0           __m128i cb = _mm_add_epi8(_mm_cmpgt_epi8(b0, threshold),
128             _mm_cmpgt_epi8(b1, threshold));
129 0           acc8 = _mm_sub_epi8(acc8, ca);
130 0           acc8 = _mm_sub_epi8(acc8, cb);
131 0           bytes += 64;
132             }
133 0 0         for (; batch > 0; batch--) {
134 0           __m128i v0 = _mm_loadu_si128((const __m128i *)bytes);
135 0           __m128i v1 = _mm_loadu_si128((const __m128i *)(bytes + 16));
136 0           __m128i c = _mm_add_epi8(_mm_cmpgt_epi8(v0, threshold),
137             _mm_cmpgt_epi8(v1, threshold));
138 0           acc8 = _mm_sub_epi8(acc8, c);
139 0           bytes += 32;
140             }
141 0           acc64 = _mm_add_epi64(acc64, _mm_sad_epu8(acc8, zero));
142             }
143              
144 0           acc64 = _mm_add_epi64(acc64, _mm_srli_si128(acc64, 8));
145 0           return (size_t)_mm_cvtsi128_si64(acc64);
146              
147             #elif defined(UTF8_SIMD_HAS_NEON)
148             const int8x16_t threshold = vdupq_n_s8(-65);
149             uint64_t count = 0;
150              
151             while (n > 0) {
152             size_t batch = n < 127 ? n : 127;
153             n -= batch;
154             uint8x16_t acc8 = vdupq_n_u8(0);
155             for (; batch >= 4; batch -= 4) {
156             int8x16_t a0 = vld1q_s8((const int8_t *)bytes);
157             int8x16_t a1 = vld1q_s8((const int8_t *)(bytes + 16));
158             int8x16_t b0 = vld1q_s8((const int8_t *)(bytes + 32));
159             int8x16_t b1 = vld1q_s8((const int8_t *)(bytes + 48));
160             int8x16_t c0 = vld1q_s8((const int8_t *)(bytes + 64));
161             int8x16_t c1 = vld1q_s8((const int8_t *)(bytes + 80));
162             int8x16_t d0 = vld1q_s8((const int8_t *)(bytes + 96));
163             int8x16_t d1 = vld1q_s8((const int8_t *)(bytes + 112));
164             acc8 = vsubq_u8(acc8, vcgtq_s8(a0, threshold));
165             acc8 = vsubq_u8(acc8, vcgtq_s8(a1, threshold));
166             acc8 = vsubq_u8(acc8, vcgtq_s8(b0, threshold));
167             acc8 = vsubq_u8(acc8, vcgtq_s8(b1, threshold));
168             acc8 = vsubq_u8(acc8, vcgtq_s8(c0, threshold));
169             acc8 = vsubq_u8(acc8, vcgtq_s8(c1, threshold));
170             acc8 = vsubq_u8(acc8, vcgtq_s8(d0, threshold));
171             acc8 = vsubq_u8(acc8, vcgtq_s8(d1, threshold));
172             bytes += 128;
173             }
174             for (; batch > 0; batch--) {
175             int8x16_t v0 = vld1q_s8((const int8_t *)bytes);
176             int8x16_t v1 = vld1q_s8((const int8_t *)(bytes + 16));
177             acc8 = vsubq_u8(acc8, vcgtq_s8(v0, threshold));
178             acc8 = vsubq_u8(acc8, vcgtq_s8(v1, threshold));
179             bytes += 32;
180             }
181             count += vaddlvq_u8(acc8);
182             }
183              
184             return (size_t)count;
185             #endif
186             }
187              
188             #ifdef __cplusplus
189             }
190             #endif
191             #endif /* UTF8_SIMD_AVAILABLE */
192             #endif /* UTF8_SIMD_H */