| 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_swar.h -- SWAR primitives for UTF-8 |
|
26
|
|
|
|
|
|
|
* ============================================================================= |
|
27
|
|
|
|
|
|
|
* |
|
28
|
|
|
|
|
|
|
* Counts UTF-8 codepoint boundaries in 8-byte words without SIMD instruction |
|
29
|
|
|
|
|
|
|
* set extension. Assumes well-formed UTF-8 input. |
|
30
|
|
|
|
|
|
|
* |
|
31
|
|
|
|
|
|
|
* |
|
32
|
|
|
|
|
|
|
* BACKGROUND |
|
33
|
|
|
|
|
|
|
* ---------- |
|
34
|
|
|
|
|
|
|
* |
|
35
|
|
|
|
|
|
|
* A UTF-8 byte falls into one of three classes: |
|
36
|
|
|
|
|
|
|
* |
|
37
|
|
|
|
|
|
|
* ASCII 0xxxxxxx (0x00-0x7F) -- starts a 1-byte sequence |
|
38
|
|
|
|
|
|
|
* Lead 11xxxxxx (0xC0-0xFF) -- starts a 2/3/4-byte sequence |
|
39
|
|
|
|
|
|
|
* Continuation 10xxxxxx (0x80-0xBF) -- interior byte, not a boundary |
|
40
|
|
|
|
|
|
|
* |
|
41
|
|
|
|
|
|
|
* Counting codepoints is equivalent to counting non-continuation bytes, |
|
42
|
|
|
|
|
|
|
* since every codepoint begins at exactly one ASCII or lead byte. |
|
43
|
|
|
|
|
|
|
* |
|
44
|
|
|
|
|
|
|
* |
|
45
|
|
|
|
|
|
|
* BYTE CLASS DETECTION |
|
46
|
|
|
|
|
|
|
* -------------------- |
|
47
|
|
|
|
|
|
|
* |
|
48
|
|
|
|
|
|
|
* Each mark function operates on an 8-byte word loaded with memcpy and |
|
49
|
|
|
|
|
|
|
* returns a word with bit 0 of each byte set iff the corresponding byte |
|
50
|
|
|
|
|
|
|
* belongs to the class. Each byte of the result is therefore 0 or 1. |
|
51
|
|
|
|
|
|
|
* |
|
52
|
|
|
|
|
|
|
* ASCII (bit 7 clear): |
|
53
|
|
|
|
|
|
|
* (~w & 0x8080...) >> 7 |
|
54
|
|
|
|
|
|
|
* |
|
55
|
|
|
|
|
|
|
* Continuation (10xxxxxx): |
|
56
|
|
|
|
|
|
|
* (w & ~(w << 1)) & 0x8080... |
|
57
|
|
|
|
|
|
|
* Bit 7 of each result byte is w[7] & ~w[6]. The shift crosses byte |
|
58
|
|
|
|
|
|
|
* boundaries, but the mask isolates bit 7, so each byte's result |
|
59
|
|
|
|
|
|
|
* depends only on its own bits 7 and 6. |
|
60
|
|
|
|
|
|
|
* |
|
61
|
|
|
|
|
|
|
* Non-continuation (ASCII or lead): |
|
62
|
|
|
|
|
|
|
* (~w | (w << 1)) & 0x8080... |
|
63
|
|
|
|
|
|
|
* Bit 7 of each result byte is ~w[7] | w[6] — true for ASCII |
|
64
|
|
|
|
|
|
|
* (bit 7 clear) and lead bytes (bit 6 set). Same masking as above. |
|
65
|
|
|
|
|
|
|
* |
|
66
|
|
|
|
|
|
|
* |
|
67
|
|
|
|
|
|
|
* HORIZONTAL SUMS |
|
68
|
|
|
|
|
|
|
* ---------------- |
|
69
|
|
|
|
|
|
|
* |
|
70
|
|
|
|
|
|
|
* Two reduction functions fold 8 byte lanes into a scalar count: |
|
71
|
|
|
|
|
|
|
* |
|
72
|
|
|
|
|
|
|
* utf8_swar_hsum_bits8(x) -- each byte is 0 or 1 (mark word). |
|
73
|
|
|
|
|
|
|
* utf8_swar_hsum_bytes8(x) -- each byte is 0..255 (accumulated marks). |
|
74
|
|
|
|
|
|
|
* |
|
75
|
|
|
|
|
|
|
* hsum_bits8 uses hardware popcount when available (UTF8_SWAR_POPCNT), |
|
76
|
|
|
|
|
|
|
* otherwise a multiply-fold sum. With popcount, independent calls on |
|
77
|
|
|
|
|
|
|
* separate words can execute in parallel. |
|
78
|
|
|
|
|
|
|
* |
|
79
|
|
|
|
|
|
|
* hsum_bytes8 widens adjacent bytes into 16-bit lanes, then folds with |
|
80
|
|
|
|
|
|
|
* a multiply. Used after accumulating mark words without per-word |
|
81
|
|
|
|
|
|
|
* reduction. |
|
82
|
|
|
|
|
|
|
* |
|
83
|
|
|
|
|
|
|
* |
|
84
|
|
|
|
|
|
|
* HARDWARE POPCOUNT DETECTION |
|
85
|
|
|
|
|
|
|
* --------------------------- |
|
86
|
|
|
|
|
|
|
* |
|
87
|
|
|
|
|
|
|
* UTF8_SWAR_POPCNT is defined when a hardware popcount instruction is |
|
88
|
|
|
|
|
|
|
* available: |
|
89
|
|
|
|
|
|
|
* |
|
90
|
|
|
|
|
|
|
* x86/x86-64 __POPCNT__ (-mpopcnt or -march=* implying it) |
|
91
|
|
|
|
|
|
|
* AArch64 __aarch64__ (cnt always available) |
|
92
|
|
|
|
|
|
|
* PowerPC __POPCNTD__ (popcntd, POWER5+ and later) |
|
93
|
|
|
|
|
|
|
* RISC-V __riscv_zbb (Zbb bit-manipulation extension) |
|
94
|
|
|
|
|
|
|
* MSVC __AVX__ (/arch:AVX or higher) |
|
95
|
|
|
|
|
|
|
* |
|
96
|
|
|
|
|
|
|
* __has_builtin(__builtin_popcountll) is intentionally NOT used: GCC |
|
97
|
|
|
|
|
|
|
* recognises the builtin on all targets but may emit a libgcc soft |
|
98
|
|
|
|
|
|
|
* call (__popcountdi2) when no hardware instruction is available. |
|
99
|
|
|
|
|
|
|
* |
|
100
|
|
|
|
|
|
|
*/ |
|
101
|
|
|
|
|
|
|
#ifndef UTF8_SWAR_H |
|
102
|
|
|
|
|
|
|
#define UTF8_SWAR_H |
|
103
|
|
|
|
|
|
|
#include |
|
104
|
|
|
|
|
|
|
#include |
|
105
|
|
|
|
|
|
|
#include |
|
106
|
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
#ifdef __cplusplus |
|
108
|
|
|
|
|
|
|
extern "C" { |
|
109
|
|
|
|
|
|
|
#endif |
|
110
|
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
#if defined(__POPCNT__) || \ |
|
112
|
|
|
|
|
|
|
defined(__aarch64__) || \ |
|
113
|
|
|
|
|
|
|
defined(__POPCNTD__) || \ |
|
114
|
|
|
|
|
|
|
defined(__riscv_zbb) || \ |
|
115
|
|
|
|
|
|
|
(defined(_MSC_VER) && defined(__AVX__)) |
|
116
|
|
|
|
|
|
|
# define UTF8_SWAR_POPCNT 1 |
|
117
|
|
|
|
|
|
|
#endif |
|
118
|
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
#if defined(_MSC_VER) && defined(UTF8_SWAR_POPCNT) |
|
120
|
|
|
|
|
|
|
# include |
|
121
|
|
|
|
|
|
|
#endif |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
/* |
|
124
|
|
|
|
|
|
|
* utf8_swar_hsum_bits8 -- sum the 8 byte lanes of a mark word. |
|
125
|
|
|
|
|
|
|
* Each byte of x must be 0 or 1. Returns the total count of 1-bytes. |
|
126
|
|
|
|
|
|
|
*/ |
|
127
|
|
|
|
|
|
|
#if defined(_MSC_VER) && defined(UTF8_SWAR_POPCNT) |
|
128
|
|
|
|
|
|
|
static inline size_t utf8_swar_hsum_bits8(uint64_t x) { |
|
129
|
|
|
|
|
|
|
return (size_t)__popcnt64(x); |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
#elif defined(UTF8_SWAR_POPCNT) |
|
132
|
|
|
|
|
|
|
static inline size_t utf8_swar_hsum_bits8(uint64_t x) { |
|
133
|
|
|
|
|
|
|
return (size_t)__builtin_popcountll(x); |
|
134
|
|
|
|
|
|
|
} |
|
135
|
|
|
|
|
|
|
#else |
|
136
|
0
|
|
|
|
|
|
static inline size_t utf8_swar_hsum_bits8(uint64_t x) { |
|
137
|
0
|
|
|
|
|
|
return (size_t)((x * UINT64_C(0x0101010101010101)) >> 56); |
|
138
|
|
|
|
|
|
|
} |
|
139
|
|
|
|
|
|
|
#endif |
|
140
|
|
|
|
|
|
|
|
|
141
|
|
|
|
|
|
|
/* |
|
142
|
|
|
|
|
|
|
* utf8_swar_hsum_bytes8 -- sum the 8 byte lanes of an accumulated word. |
|
143
|
|
|
|
|
|
|
* Each byte of x is in 0..255. Returns the total in [0, 2040]. |
|
144
|
|
|
|
|
|
|
*/ |
|
145
|
|
|
|
|
|
|
static inline size_t utf8_swar_hsum_bytes8(uint64_t x) { |
|
146
|
|
|
|
|
|
|
uint64_t pair_sum = (x & UINT64_C(0x00FF00FF00FF00FF)) |
|
147
|
|
|
|
|
|
|
+ ((x >> 8) & UINT64_C(0x00FF00FF00FF00FF)); |
|
148
|
|
|
|
|
|
|
return (size_t)((pair_sum * UINT64_C(0x0001000100010001)) >> 48); |
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
/* |
|
152
|
|
|
|
|
|
|
* has_ predicates -- high-bit mask per lane (0x80 = match, 0x00 = no match). |
|
153
|
|
|
|
|
|
|
* mark_ functions -- normalized to bit 0 per lane (0x01 = match, 0x00 = no match). |
|
154
|
|
|
|
|
|
|
*/ |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
// utf8_swar_has_zero8 -- test for zero bytes. |
|
157
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_has_zero8(uint64_t w) { |
|
158
|
|
|
|
|
|
|
return (w - UINT64_C(0x0101010101010101)) & ~w & UINT64_C(0x8080808080808080); |
|
159
|
|
|
|
|
|
|
} |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
// utf8_swar_has_byte8 -- test for bytes equal to byte. |
|
162
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_has_byte8(uint64_t w, uint8_t byte) { |
|
163
|
|
|
|
|
|
|
return utf8_swar_has_zero8(w ^ (UINT64_C(0x0101010101010101) * byte)); |
|
164
|
|
|
|
|
|
|
} |
|
165
|
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
// utf8_swar_has_newline8 -- test for '\n' bytes (0x0A). |
|
167
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_has_newline8(uint64_t w) { |
|
168
|
|
|
|
|
|
|
return utf8_swar_has_zero8(w ^ UINT64_C(0x0A0A0A0A0A0A0A0A)); |
|
169
|
|
|
|
|
|
|
} |
|
170
|
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
// utf8_swar_has_ascii8 -- test for ASCII bytes (0x00-0x7F). |
|
172
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_has_ascii8(uint64_t w) { |
|
173
|
|
|
|
|
|
|
return ~w & UINT64_C(0x8080808080808080); |
|
174
|
|
|
|
|
|
|
} |
|
175
|
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
// utf8_swar_has_continuations8 -- test for continuation bytes (0x80-0xBF). |
|
177
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_has_continuations8(uint64_t w) { |
|
178
|
|
|
|
|
|
|
return (w & ~(w << 1)) & UINT64_C(0x8080808080808080); |
|
179
|
|
|
|
|
|
|
} |
|
180
|
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
// utf8_swar_has_non_continuations8 -- test for non-continuation bytes (ASCII or lead). |
|
182
|
0
|
|
|
|
|
|
static inline uint64_t utf8_swar_has_non_continuations8(uint64_t w) { |
|
183
|
0
|
|
|
|
|
|
return (~w | (w << 1)) & UINT64_C(0x8080808080808080); |
|
184
|
|
|
|
|
|
|
} |
|
185
|
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
// utf8_swar_mark_zero8 -- mark zero bytes (0x01 per lane). |
|
187
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_mark_zero8(uint64_t w) { |
|
188
|
|
|
|
|
|
|
return utf8_swar_has_zero8(w) >> 7; |
|
189
|
|
|
|
|
|
|
} |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
// utf8_swar_mark_byte8 -- mark bytes equal to byte (0x01 per lane). |
|
192
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_mark_byte8(uint64_t w, uint8_t byte) { |
|
193
|
|
|
|
|
|
|
return utf8_swar_has_byte8(w, byte) >> 7; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
// utf8_swar_mark_newline8 -- mark '\n' bytes (0x01 per lane). |
|
197
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_mark_newline8(uint64_t w) { |
|
198
|
|
|
|
|
|
|
return utf8_swar_has_newline8(w) >> 7; |
|
199
|
|
|
|
|
|
|
} |
|
200
|
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
// utf8_swar_mark_ascii8 -- mark ASCII bytes (0x01 per lane). |
|
202
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_mark_ascii8(uint64_t w) { |
|
203
|
|
|
|
|
|
|
return utf8_swar_has_ascii8(w) >> 7; |
|
204
|
|
|
|
|
|
|
} |
|
205
|
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
// utf8_swar_mark_continuations8 -- mark continuation bytes (0x01 per lane). |
|
207
|
|
|
|
|
|
|
static inline uint64_t utf8_swar_mark_continuations8(uint64_t w) { |
|
208
|
|
|
|
|
|
|
return utf8_swar_has_continuations8(w) >> 7; |
|
209
|
|
|
|
|
|
|
} |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
// utf8_swar_mark_non_continuations8 -- mark non-continuation bytes (0x01 per lane). |
|
212
|
0
|
|
|
|
|
|
static inline uint64_t utf8_swar_mark_non_continuations8(uint64_t w) { |
|
213
|
0
|
|
|
|
|
|
return utf8_swar_has_non_continuations8(w) >> 7; |
|
214
|
|
|
|
|
|
|
} |
|
215
|
|
|
|
|
|
|
|
|
216
|
|
|
|
|
|
|
// utf8_swar_count_codepoints_1x8 -- count codepoints in one 8-byte block. |
|
217
|
0
|
|
|
|
|
|
static inline size_t utf8_swar_count_codepoints_1x8(const void *src) { |
|
218
|
|
|
|
|
|
|
uint64_t w; |
|
219
|
0
|
|
|
|
|
|
memcpy(&w, src, sizeof w); |
|
220
|
0
|
|
|
|
|
|
return utf8_swar_hsum_bits8(utf8_swar_mark_non_continuations8(w)); |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
/* |
|
224
|
|
|
|
|
|
|
* utf8_swar_count_codepoints_Nx32 -- count codepoints in n 32-byte blocks. |
|
225
|
|
|
|
|
|
|
* Each block is processed as four 8-byte words. Without hardware popcnt, |
|
226
|
|
|
|
|
|
|
* mark words are accumulated and folded every 63 blocks (63*4 = 252 < 255). |
|
227
|
|
|
|
|
|
|
*/ |
|
228
|
|
|
|
|
|
|
static inline size_t utf8_swar_count_codepoints_Nx32(const void *src, size_t n) { |
|
229
|
|
|
|
|
|
|
const uint8_t *bytes = (const uint8_t *)src; |
|
230
|
|
|
|
|
|
|
size_t count = 0; |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
#if defined(UTF8_SWAR_POPCNT) |
|
233
|
|
|
|
|
|
|
for (size_t i = 0; i < n; i++, bytes += 32) { |
|
234
|
|
|
|
|
|
|
uint64_t w0, w1, w2, w3; |
|
235
|
|
|
|
|
|
|
memcpy(&w0, bytes + 0, 8); |
|
236
|
|
|
|
|
|
|
memcpy(&w1, bytes + 8, 8); |
|
237
|
|
|
|
|
|
|
memcpy(&w2, bytes + 16, 8); |
|
238
|
|
|
|
|
|
|
memcpy(&w3, bytes + 24, 8); |
|
239
|
|
|
|
|
|
|
count += utf8_swar_hsum_bits8(utf8_swar_mark_non_continuations8(w0)) |
|
240
|
|
|
|
|
|
|
+ utf8_swar_hsum_bits8(utf8_swar_mark_non_continuations8(w1)) |
|
241
|
|
|
|
|
|
|
+ utf8_swar_hsum_bits8(utf8_swar_mark_non_continuations8(w2)) |
|
242
|
|
|
|
|
|
|
+ utf8_swar_hsum_bits8(utf8_swar_mark_non_continuations8(w3)); |
|
243
|
|
|
|
|
|
|
} |
|
244
|
|
|
|
|
|
|
#else |
|
245
|
|
|
|
|
|
|
while (n > 0) { |
|
246
|
|
|
|
|
|
|
size_t batch = n < 63 ? n : 63; |
|
247
|
|
|
|
|
|
|
n -= batch; |
|
248
|
|
|
|
|
|
|
uint64_t acc = 0; |
|
249
|
|
|
|
|
|
|
for (size_t i = 0; i < batch; i++, bytes += 32) { |
|
250
|
|
|
|
|
|
|
uint64_t w0, w1, w2, w3; |
|
251
|
|
|
|
|
|
|
memcpy(&w0, bytes + 0, 8); |
|
252
|
|
|
|
|
|
|
memcpy(&w1, bytes + 8, 8); |
|
253
|
|
|
|
|
|
|
memcpy(&w2, bytes + 16, 8); |
|
254
|
|
|
|
|
|
|
memcpy(&w3, bytes + 24, 8); |
|
255
|
|
|
|
|
|
|
acc += utf8_swar_mark_non_continuations8(w0) |
|
256
|
|
|
|
|
|
|
+ utf8_swar_mark_non_continuations8(w1) |
|
257
|
|
|
|
|
|
|
+ utf8_swar_mark_non_continuations8(w2) |
|
258
|
|
|
|
|
|
|
+ utf8_swar_mark_non_continuations8(w3); |
|
259
|
|
|
|
|
|
|
} |
|
260
|
|
|
|
|
|
|
count += utf8_swar_hsum_bytes8(acc); |
|
261
|
|
|
|
|
|
|
} |
|
262
|
|
|
|
|
|
|
#endif |
|
263
|
|
|
|
|
|
|
return count; |
|
264
|
|
|
|
|
|
|
} |
|
265
|
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
#ifdef __cplusplus |
|
267
|
|
|
|
|
|
|
} |
|
268
|
|
|
|
|
|
|
#endif |
|
269
|
|
|
|
|
|
|
#endif /* UTF8_SWAR_H */ |