File Coverage

src/hexsimd.c
Criterion Covered Total %
statement 130 213 61.0
branch 41 98 41.8
condition n/a
subroutine n/a
pod n/a
total 171 311 54.9


line stmt bran cond sub pod time code
1             // hexsimd.c
2             // SIMD-accelerated hex<->bin with runtime dispatch.
3             // Build modes controlled by Makefile (UNIVERSAL vs NATIVE).
4             // make -f make-dist.mk demo
5              
6             #include "hexsimd.h"
7             #include // getenv
8             #include // strcasecmp (or use strcmp if you prefer exact case)
9             #include // for memcpy
10             #include
11             #include
12              
13             #if defined(_MSC_VER)
14             #include
15             #else
16             #include
17             #endif
18              
19             /* decide once. at compile time. whether AVX512 code exists in this file */
20             #ifdef HEXSIMD_ENABLE_AVX512
21             # define HEXSIMD_HAVE_AVX512 1
22             #else
23             # define HEXSIMD_HAVE_AVX512 0
24             #endif
25              
26             // -------------------------
27             // CPUID / XGETBV helpers
28             // -------------------------
29 8           static void cpuid_x86(unsigned leaf, unsigned subleaf, unsigned regs[4]) {
30             #if defined(_MSC_VER)
31             int cpuInfo[4];
32             __cpuidex(cpuInfo, (int)leaf, (int)subleaf);
33             regs[0]=(unsigned)cpuInfo[0]; regs[1]=(unsigned)cpuInfo[1];
34             regs[2]=(unsigned)cpuInfo[2]; regs[3]=(unsigned)cpuInfo[3];
35             #else
36             unsigned a,b,c,d;
37 8           __asm__ volatile("cpuid" : "=a"(a), "=b"(b), "=c"(c), "=d"(d)
38             : "a"(leaf), "c"(subleaf));
39 8           regs[0]=a; regs[1]=b; regs[2]=c; regs[3]=d;
40             #endif
41 8           }
42              
43 4           static unsigned long long xgetbv_x86(unsigned idx) {
44             #if defined(_MSC_VER)
45             return _xgetbv(idx);
46             #else
47             unsigned eax, edx;
48 4           __asm__ volatile (".byte 0x0f, 0x01, 0xd0" : "=a"(eax), "=d"(edx) : "c"(idx));
49 4           return ((unsigned long long)edx << 32) | eax;
50             #endif
51             }
52              
53             typedef struct {
54             int sse2, avx, avx2, avx512bw, avx512vl;
55             } isa_t;
56              
57 4           static isa_t detect_isa_runtime(void) {
58 4           isa_t f = {0};
59 4           unsigned r[4] = {0};
60 4           cpuid_x86(1,0,r);
61 4           int osxsave = (r[2] & (1u<<27)) != 0;
62 4           f.sse2 = (r[3] & (1u<<26)) != 0;
63              
64 4 50         if (osxsave) {
65 4           unsigned long long xcr0 = xgetbv_x86(0);
66 4           int os_avx = ((xcr0 & 0x6) == 0x6);
67 4 50         if (os_avx && (r[2] & (1u<<28))) f.avx = 1;
    50          
68              
69 4           cpuid_x86(7,0,r);
70 4 50         if (f.avx) f.avx2 = (r[1] & (1u<<5)) != 0;
71              
72 4           int os_avx512 = ((xcr0 & 0xE0) == 0xE0);
73 4 50         if (os_avx512) {
74 0           f.avx512bw = (r[1] & (1u<<30)) != 0;
75 0           f.avx512vl = (r[1] & (1u<<31)) != 0;
76             }
77             }
78 4           return f;
79             }
80              
81             // -------------------------
82             // Scalar reference
83             // -------------------------
84 28           static inline int nibble_from_ascii(unsigned char c, unsigned char* out) {
85 28 50         if (c >= '0' && c <= '9') { *out = (unsigned char)(c - '0'); return 1; }
    100          
86 8           unsigned char u = (unsigned char)(c & ~0x20u);
87 8 50         if (u >= 'A' && u <= 'F') { *out = (unsigned char)(10 + (u - 'A')); return 1; }
    100          
88 2           return 0;
89             }
90              
91             __attribute__((target("default")))
92 4           static ptrdiff_t hex_to_bytes_scalar_impl(const char* src, size_t len, uint8_t* dst, bool strict) {
93 4 50         if (len & 1) return -1;
94 4           size_t o=0;
95 17 100         for (size_t i=0;i
96 14           unsigned char hi=0, lo=0;
97 14           int v1 = nibble_from_ascii((unsigned char)src[i], &hi);
98 14           int v2 = nibble_from_ascii((unsigned char)src[i+1], &lo);
99 14 50         if (strict && (!v1 || !v2)) return -1;
    100          
    50          
100 13 50         if (!v1) hi = 0;
101 13 50         if (!v2) lo = 0;
102 13           dst[o++] = (uint8_t)((hi<<4)|lo);
103             }
104 3           return (ptrdiff_t)o;
105             }
106              
107             __attribute__((target("default")))
108 0           static ptrdiff_t bytes_to_hex_scalar_impl(const uint8_t* src, size_t len, char* dst) {
109             static const char HEX[16] = "0123456789ABCDEF";
110 0 0         for (size_t i=0;i
111 0           uint8_t b = src[i];
112 0           dst[2*i+0] = HEX[b>>4];
113 0           dst[2*i+1] = HEX[b & 0x0F];
114             }
115 0           return (ptrdiff_t)(2*len);
116             }
117              
118             // Names for debugging
119             static const char *g_hex2bin_name = "scalar";
120             static const char *g_bin2hex_name = "scalar";
121              
122             // -------------------------
123             // SSE2 (128b) — enabled per function via target pragma
124             // -------------------------
125             #if defined(__GNUC__) || defined(__clang__)
126             #pragma GCC push_options
127             #pragma GCC target("sse2")
128             #endif
129             #if (defined(__SSE2__) || defined(__GNUC__) || defined(__clang__) || defined(_M_X64) || defined(_M_IX86))
130             #include
131              
132 0           static inline __m128i sse2_toNib(__m128i x, __m128i *valid, int want_valid) {
133 0           const __m128i c0 = _mm_set1_epi8('0'), c9p1=_mm_set1_epi8('9'+1);
134 0           const __m128i cA = _mm_set1_epi8('A'), cFp1=_mm_set1_epi8('F'+1);
135 0           const __m128i casebit=_mm_set1_epi8(0x20), ten=_mm_set1_epi8(10);
136              
137 0           __m128i upper = _mm_andnot_si128(casebit, x);
138 0           __m128i ge0 = _mm_cmpeq_epi8(_mm_max_epu8(x,c0), x);
139 0           __m128i lt10= _mm_cmpeq_epi8(_mm_min_epu8(x,c9p1), x);
140 0           __m128i isd = _mm_and_si128(ge0, lt10);
141              
142 0           __m128i geA = _mm_cmpeq_epi8(_mm_max_epu8(upper,cA), upper);
143 0           __m128i ltG = _mm_cmpeq_epi8(_mm_min_epu8(upper,cFp1), upper);
144 0           __m128i isa = _mm_and_si128(geA, ltG);
145              
146 0           __m128i dval = _mm_sub_epi8(x, c0);
147 0           __m128i lval = _mm_add_epi8(_mm_sub_epi8(upper, cA), ten);
148 0           __m128i nib = _mm_or_si128(_mm_and_si128(isd, dval),
149             _mm_andnot_si128(isd, lval));
150 0           nib = _mm_and_si128(nib, _mm_set1_epi8(0x0F));
151              
152 0 0         if (want_valid && valid) *valid = _mm_or_si128(isd, isa);
    0          
153 0           return nib;
154             }
155              
156              
157 0           static inline __m128i sse2_pack_pairs(__m128i n) {
158 0           __m128i even = _mm_and_si128(n, _mm_set1_epi16(0x00FF));
159 0           __m128i odd = _mm_and_si128(_mm_srli_epi16(n,8), _mm_set1_epi16(0x00FF));
160 0           __m128i w16 = _mm_or_si128(_mm_slli_epi16(even,4), odd);
161 0           return _mm_packus_epi16(w16, _mm_setzero_si128());
162             }
163              
164             __attribute__((target("sse2")))
165 0           static ptrdiff_t hex_to_bytes_sse2_impl(const char* src, size_t len, uint8_t* dst, bool strict) {
166 0 0         if (len & 1) return -1;
167 0           size_t i=0,o=0;
168 0 0         for (; i+16<=len; i+=16, o+=8) {
169 0           __m128i x = _mm_loadu_si128((const __m128i*)(src+i));
170             __m128i valid;
171 0           __m128i n = sse2_toNib(x, &valid, strict);
172 0 0         if (strict) {
173 0           __m128i all = _mm_cmpeq_epi8(valid, _mm_set1_epi8((char)0xFF));
174 0 0         if ((unsigned)_mm_movemask_epi8(all) != 0xFFFFu) return -1;
175             }
176 0           __m128i out = sse2_pack_pairs(n);
177 0           _mm_storel_epi64((__m128i*)(dst+o), out);
178             }
179 0 0         if (i
180 0           ptrdiff_t t = hex_to_bytes_scalar_impl(src+i, len-i, dst+o, strict);
181 0 0         if (t<0) return -1;
182 0           o += (size_t)t;
183             }
184 0           return (ptrdiff_t)o;
185             }
186              
187             // bytes -> hex (SSE2, arithmetic map; no SSSE3 needed)
188             __attribute__((target("sse2")))
189 0           static ptrdiff_t bytes_to_hex_sse2_impl(const uint8_t* src, size_t len, char* dst) {
190 0           size_t i=0, o=0;
191 0           const __m128i mask0F = _mm_set1_epi8(0x0F);
192 0           const __m128i add30 = _mm_set1_epi8(0x30);
193 0           const __m128i add7 = _mm_set1_epi8(0x07);
194 0           const __m128i nine = _mm_set1_epi8(9);
195              
196 0 0         for (; i+16<=len; i+=16, o+=32) {
197 0           __m128i b = _mm_loadu_si128((const __m128i*)(src+i));
198 0           __m128i lo = _mm_and_si128(b, mask0F);
199 0           __m128i hi = _mm_and_si128(_mm_srli_epi16(b,4), mask0F);
200              
201             // map nibble -> ASCII '0'..'9','A'..'F' = nib + 0x30 + (nib>9?0x07:0)
202 0           __m128i lo_gt9 = _mm_cmpgt_epi8(lo, nine);
203 0           __m128i hi_gt9 = _mm_cmpgt_epi8(hi, nine);
204              
205 0           lo = _mm_add_epi8(lo, add30);
206 0           hi = _mm_add_epi8(hi, add30);
207 0           lo = _mm_add_epi8(lo, _mm_and_si128(lo_gt9, add7));
208 0           hi = _mm_add_epi8(hi, _mm_and_si128(hi_gt9, add7));
209              
210             // interleave [hi0 lo0 hi1 lo1 ...]
211 0           __m128i lo_i = _mm_unpacklo_epi8(hi, lo);
212 0           __m128i hi_i = _mm_unpackhi_epi8(hi, lo);
213              
214 0           _mm_storeu_si128((__m128i*)(dst+o+0), lo_i);
215 0           _mm_storeu_si128((__m128i*)(dst+o+16), hi_i);
216             }
217             // tail
218 0 0         for (; i
219 0           uint8_t b = src[i];
220 0           uint8_t h = (b>>4), l = (b&0x0F);
221 0 0         dst[o+0] = (char)(h + 0x30 + (h>9 ? 0x07 : 0));
222 0 0         dst[o+1] = (char)(l + 0x30 + (l>9 ? 0x07 : 0));
223             }
224 0           return (ptrdiff_t)o;
225             }
226             #endif
227             #if defined(__GNUC__) || defined(__clang__)
228             #pragma GCC pop_options
229             #endif
230              
231             // -------------------------
232             // AVX2 (256b)
233             // -------------------------
234             #if defined(__GNUC__) || defined(__clang__)
235             #pragma GCC push_options
236             #pragma GCC target("avx2")
237             #endif
238             #if defined(__AVX2__) || (defined(__GNUC__) || defined(__clang__))
239 5           static ptrdiff_t hex_to_bytes_avx2_impl(const char* src, size_t len, uint8_t* dst, bool strict) {
240 5 50         if (len & 1) return -1;
241 5           size_t i=0,o=0;
242 10           const __m256i c0=_mm256_set1_epi8('0'), c9p1=_mm256_set1_epi8('9'+1);
243 10           const __m256i cA=_mm256_set1_epi8('A'), cFp1=_mm256_set1_epi8('F'+1);
244 10           const __m256i casebit=_mm256_set1_epi8(0x20), ten=_mm256_set1_epi8(10);
245 5           const __m256i mask0F=_mm256_set1_epi8(0x0F);
246 5           const __m256i pack016=_mm256_setr_epi8(
247             16, 1, 16, 1, 16, 1, 16, 1,
248             16, 1, 16, 1, 16, 1, 16, 1,
249             16, 1, 16, 1, 16, 1, 16, 1,
250             16, 1, 16, 1, 16, 1, 16, 1
251             );
252 5           const __m256i all_ff=_mm256_set1_epi32(-1);
253 32773 100         for (; i+32<=len; i+=32, o+=16) {
254 65536           __m256i x = _mm256_loadu_si256((const __m256i*)(src+i));
255 32768           __m256i upper = _mm256_andnot_si256(casebit, x);
256 163840           __m256i isd = _mm256_and_si256(
257             _mm256_cmpeq_epi8(_mm256_max_epu8(x,c0), x),
258             _mm256_cmpeq_epi8(_mm256_min_epu8(x,c9p1), x));
259 131072           __m256i isa = _mm256_and_si256(
260             _mm256_cmpeq_epi8(_mm256_max_epu8(upper,cA), upper),
261             _mm256_cmpeq_epi8(_mm256_min_epu8(upper,cFp1), upper));
262 32768 50         if (strict) {
263 32768           __m256i valid = _mm256_or_si256(isd, isa);
264 32768 50         if (!_mm256_testc_si256(valid, all_ff)) return -1;
265             }
266 32768           __m256i dval = _mm256_sub_epi8(x,c0);
267 65536           __m256i lval = _mm256_add_epi8(_mm256_sub_epi8(upper,cA), ten);
268 98304           __m256i nib = _mm256_or_si256(_mm256_and_si256(isd,dval),
269             _mm256_andnot_si256(isd,lval));
270 32768           nib = _mm256_and_si256(nib, mask0F);
271              
272 32768           __m256i pairs = _mm256_maddubs_epi16(nib, pack016);
273 32768           __m128i lo16 = _mm256_castsi256_si128(pairs);
274 32768           __m128i hi16 = _mm256_extracti128_si256(pairs,1);
275 32768           __m128i out8 = _mm_packus_epi16(lo16, hi16);
276 32768           _mm_storeu_si128((__m128i*)(dst+o), out8);
277             }
278 5 100         if (i
279 4           ptrdiff_t t = hex_to_bytes_scalar_impl(src+i, len-i, dst+o, strict);
280 4 100         if (t<0) return -1;
281 3           o += (size_t)t;
282             }
283 4           return (ptrdiff_t)o;
284             }
285              
286 5           static ptrdiff_t bytes_to_hex_avx2_impl(const uint8_t* src, size_t len, char* dst) {
287 5           size_t i=0, o=0;
288 5           const __m128i mask0F = _mm_set1_epi8(0x0F);
289 5           const __m128i add30 = _mm_set1_epi8(0x30);
290 5           const __m128i add7 = _mm_set1_epi8(0x07);
291 5           const __m128i nine = _mm_set1_epi8(9);
292              
293 16389 100         for (; i + 32 <= len; i += 32, o += 64) {
294 32768           __m256i b256 = _mm256_loadu_si256((const __m256i*)(src + i));
295              
296             // lane 0 (lower 128)
297 16384           __m128i b0 = _mm256_castsi256_si128(b256);
298 16384           __m128i lo0 = _mm_and_si128(b0, mask0F);
299 32768           __m128i hi0 = _mm_and_si128(_mm_srli_epi16(b0, 4), mask0F);
300              
301 16384           __m128i lo0_gt9 = _mm_cmpgt_epi8(lo0, nine);
302 16384           __m128i hi0_gt9 = _mm_cmpgt_epi8(hi0, nine);
303              
304 16384           lo0 = _mm_add_epi8(lo0, add30);
305 16384           hi0 = _mm_add_epi8(hi0, add30);
306 32768           lo0 = _mm_add_epi8(lo0, _mm_and_si128(lo0_gt9, add7));
307 32768           hi0 = _mm_add_epi8(hi0, _mm_and_si128(hi0_gt9, add7));
308              
309 16384           __m128i out0_lo = _mm_unpacklo_epi8(hi0, lo0); // pairs 0..7 of lane0
310 16384           __m128i out0_hi = _mm_unpackhi_epi8(hi0, lo0); // pairs 8..15 of lane0
311              
312 16384           _mm_storeu_si128((__m128i*)(dst + o + 0), out0_lo);
313 16384           _mm_storeu_si128((__m128i*)(dst + o + 16), out0_hi);
314              
315             // lane 1 (upper 128)
316 16384           __m128i b1 = _mm256_extracti128_si256(b256, 1);
317 16384           __m128i lo1 = _mm_and_si128(b1, mask0F);
318 32768           __m128i hi1 = _mm_and_si128(_mm_srli_epi16(b1, 4), mask0F);
319              
320 16384           __m128i lo1_gt9 = _mm_cmpgt_epi8(lo1, nine);
321 16384           __m128i hi1_gt9 = _mm_cmpgt_epi8(hi1, nine);
322              
323 16384           lo1 = _mm_add_epi8(lo1, add30);
324 16384           hi1 = _mm_add_epi8(hi1, add30);
325 32768           lo1 = _mm_add_epi8(lo1, _mm_and_si128(lo1_gt9, add7));
326 32768           hi1 = _mm_add_epi8(hi1, _mm_and_si128(hi1_gt9, add7));
327              
328 16384           __m128i out1_lo = _mm_unpacklo_epi8(hi1, lo1); // pairs 0..7 of lane1
329 16384           __m128i out1_hi = _mm_unpackhi_epi8(hi1, lo1); // pairs 8..15 of lane1
330              
331 16384           _mm_storeu_si128((__m128i*)(dst + o + 32), out1_lo);
332 16384           _mm_storeu_si128((__m128i*)(dst + o + 48), out1_hi);
333             }
334              
335             // tail
336 21 100         for (; i < len; ++i, o += 2) {
337 16           uint8_t b = src[i];
338 16           uint8_t h = b >> 4, l = b & 0x0F;
339 16 100         dst[o+0] = (char)(h + 0x30 + (h > 9 ? 0x07 : 0));
340 16 100         dst[o+1] = (char)(l + 0x30 + (l > 9 ? 0x07 : 0));
341             }
342 5           return (ptrdiff_t)o;
343             }
344              
345             #endif
346             #if defined(__GNUC__) || defined(__clang__)
347             #pragma GCC pop_options
348             #endif
349              
350             // -------------------------
351             // AVX-512BW+VL helpers (MUST be compiled with avx512bw,avx512vl)
352             // -------------------------
353             #if defined(__GNUC__) || defined(__clang__)
354             #pragma GCC push_options
355             #pragma GCC target("avx512bw,avx512vl")
356             #endif
357              
358             static inline __m512i pack_pairs_avx512_pack(__m512i nib) {
359             // Pair adjacent nibbles from bytes into 16-bit words, then pack to bytes.
360             const __m512i maskFF = _mm512_set1_epi16(0x00FF);
361             __m512i even = _mm512_and_si512(nib, maskFF); // low byte = even nibble
362             __m512i odd = _mm512_and_si512(_mm512_srli_epi16(nib, 8), maskFF); // low byte = odd nibble
363             __m512i hi = _mm512_slli_epi16(even, 4);
364             __m512i w16 = _mm512_or_si512(hi, odd); // 32x u16 words
365             return _mm512_packus_epi16(w16, _mm512_setzero_si512()); // low 32 bytes are outputs
366             }
367              
368             /* Only build AVX512 version if
369             * - the compiler is already compiling with AVX512BW+VL,
370             * - or the build system explicitly asked for it.
371             */
372              
373             #if defined(__GNUC__) || defined(__clang__)
374             #pragma GCC pop_options
375             #endif
376              
377             //#if (defined(__AVX512BW__) && defined(__AVX512VL__)) || defined(HEXSIMD_ENABLE_AVX512)
378             #if HEXSIMD_HAVE_AVX512
379             // -------------------------
380             // AVX-512BW+VL (512b math, 128/256/512 lanes)
381             // -------------------------
382             #if defined(__GNUC__) || defined(__clang__)
383             #pragma GCC push_options
384             #pragma GCC target("avx512bw,avx512vl")
385             #endif
386              
387              
388             static ptrdiff_t hex_to_bytes_avx512_impl(const char* src, size_t len, uint8_t* dst, bool strict) {
389             if (len & 1) return -1;
390             size_t i = 0, o = 0;
391             const size_t CH = 64; // 64 ASCII -> 32 bytes
392              
393             const __m512i c0 = _mm512_set1_epi8('0');
394             const __m512i c9 = _mm512_set1_epi8('9');
395             const __m512i cA = _mm512_set1_epi8('A');
396             const __m512i cF = _mm512_set1_epi8('F');
397             const __m512i casebit = _mm512_set1_epi8(0x20);
398             const __m512i ten = _mm512_set1_epi8(10);
399              
400             // Main 64-char chunks
401             for (; i + CH <= len; i += CH, o += 32) {
402             __m512i x = _mm512_loadu_si512((const void*)(src + i));
403             __m512i upper = _mm512_andnot_si512(casebit, x);
404              
405             __mmask64 is_digit = _mm512_cmp_epu8_mask(x, c0, _MM_CMPINT_GE)
406             & _mm512_cmp_epu8_mask(x, c9, _MM_CMPINT_LE);
407             __mmask64 is_alpha = _mm512_cmp_epu8_mask(upper, cA, _MM_CMPINT_GE)
408             & _mm512_cmp_epu8_mask(upper, cF, _MM_CMPINT_LE);
409              
410             if (strict && (is_digit | is_alpha) != ~(__mmask64)0) return -1;
411              
412             __m512i dval = _mm512_sub_epi8(x, c0);
413             __m512i lval = _mm512_add_epi8(_mm512_sub_epi8(upper, cA), ten);
414             __m512i nib = _mm512_mask_blend_epi8(is_digit, lval, dval);
415             nib = _mm512_and_si512(nib, _mm512_set1_epi8(0x0F));
416              
417             // (even<<4) | odd in 16-bit lanes
418             const __m512i maskFF = _mm512_set1_epi16(0x00FF);
419             __m512i even = _mm512_and_si512(nib, maskFF);
420             __m512i odd = _mm512_and_si512(_mm512_srli_epi16(nib, 8), maskFF);
421             __m512i w16 = _mm512_or_si512(_mm512_slli_epi16(even, 4), odd);
422              
423             // Pack each 128-bit lane to bytes and store its lower 8 bytes.
424             // This yields 4 * 8 = 32 output bytes in proper order.
425             for (int lane = 0; lane < 4; ++lane) {
426             __m128i w16_lane = _mm512_extracti32x4_epi32(w16, lane);
427             __m128i packed128 = _mm_packus_epi16(w16_lane, _mm_setzero_si128()); // 16B: low 8B are valid
428             _mm_storel_epi64((__m128i*)(dst + o + lane*8), packed128);
429             }
430             //o += 32;
431              
432             }
433              
434             // Tail (0..63 chars) — process in 16-char chunks via SSE2, then scalar.
435             size_t rem = len - i;
436             if (rem) {
437             const char* p = src + i;
438            
439             while (rem >= 16) {
440             __m128i x128 = _mm_loadu_si128((const __m128i*)p);
441             __m128i valid128;
442             __m128i n128 = sse2_toNib(x128, &valid128, /*want_valid=*/strict);
443             if (strict) {
444             __m128i all = _mm_cmpeq_epi8(valid128, _mm_set1_epi8((char)0xFF));
445             if ((unsigned)_mm_movemask_epi8(all) != 0xFFFFu) return -1;
446             }
447             __m128i out128 = sse2_pack_pairs(n128);
448             _mm_storel_epi64((__m128i*)(dst + o), out128); // 8 output bytes
449             p += 16;
450             o += 8;
451             rem -= 16;
452             }
453              
454             if (rem) {
455             ptrdiff_t t = hex_to_bytes_scalar_impl(p, rem, dst + o, strict);
456             if (t < 0) return -1;
457             o += (size_t)t;
458             }
459             }
460             return (ptrdiff_t)o;
461              
462             }
463              
464              
465             static ptrdiff_t bytes_to_hex_avx512_impl(const uint8_t* src, size_t len, char* dst) {
466             size_t i=0, o=0;
467             const __m128i mask0F = _mm_set1_epi8(0x0F);
468             const __m128i add30 = _mm_set1_epi8(0x30);
469             const __m128i add7 = _mm_set1_epi8(0x07);
470             const __m128i nine = _mm_set1_epi8(9);
471              
472             for (; i + 64 <= len; i += 64, o += 128) {
473             __m512i b512 = _mm512_loadu_si512((const void*)(src + i));
474              
475             // process 4 lanes: idx = 0..3
476             for (int lane = 0; lane < 4; ++lane) {
477             __m128i b = _mm512_extracti32x4_epi32(b512, lane);
478             __m128i lo = _mm_and_si128(b, mask0F);
479             __m128i hi = _mm_and_si128(_mm_srli_epi16(b, 4), mask0F);
480              
481             __m128i lo_gt9 = _mm_cmpgt_epi8(lo, nine);
482             __m128i hi_gt9 = _mm_cmpgt_epi8(hi, nine);
483              
484             lo = _mm_add_epi8(lo, add30);
485             hi = _mm_add_epi8(hi, add30);
486             lo = _mm_add_epi8(lo, _mm_and_si128(lo_gt9, add7));
487             hi = _mm_add_epi8(hi, _mm_and_si128(hi_gt9, add7));
488              
489             __m128i out_lo = _mm_unpacklo_epi8(hi, lo); // pairs 0..7 of this lane
490             __m128i out_hi = _mm_unpackhi_epi8(hi, lo); // pairs 8..15 of this lane
491              
492             // each lane yields 32 chars
493             _mm_storeu_si128((__m128i*)(dst + o + lane*32 + 0), out_lo);
494             _mm_storeu_si128((__m128i*)(dst + o + lane*32 + 16), out_hi);
495             }
496             }
497              
498             // tail (<=63 bytes): scalar is fine
499             for (; i < len; ++i, o += 2) {
500             uint8_t b = src[i];
501             uint8_t h = b >> 4, l = b & 0x0F;
502             dst[o+0] = (char)(h + 0x30 + (h > 9 ? 0x07 : 0));
503             dst[o+1] = (char)(l + 0x30 + (l > 9 ? 0x07 : 0));
504             }
505             return (ptrdiff_t)o;
506             }
507              
508             #if defined(__GNUC__) || defined(__clang__)
509             #pragma GCC pop_options
510             #endif
511              
512             #endif // HEXSIMD_HAVE_AVX512
513              
514              
515             // -------------------------
516             // Dispatcher
517             // -------------------------
518             typedef ptrdiff_t (*hex2bin_fn)(const char*, size_t, uint8_t*, bool);
519             typedef ptrdiff_t (*bin2hex_fn)(const uint8_t*, size_t, char*);
520              
521             static hex2bin_fn g_hex2bin = NULL;
522             static bin2hex_fn g_bin2hex = NULL;
523              
524 14           static void pick_impls(void) {
525 18 100         if (g_hex2bin) return;
526              
527 4           isa_t f = detect_isa_runtime();
528              
529             // Defaults
530 4           g_hex2bin = hex_to_bytes_scalar_impl;
531 4           g_bin2hex = bytes_to_hex_scalar_impl;
532 4           g_hex2bin_name = "scalar";
533 4           g_bin2hex_name = "scalar";
534              
535 4           const char* force = getenv("HEXSIMD_FORCE");
536 4 50         if (force && *force) {
    0          
537             // normalize desired path
538 0 0         if (!strcasecmp(force, "scalar")) {
539 0           return; // already set
540             }
541 0 0         if (!strcasecmp(force, "sse2") && f.sse2) {
    0          
542             extern ptrdiff_t hex_to_bytes_sse2_impl(const char*, size_t, uint8_t*, bool);
543             extern ptrdiff_t bytes_to_hex_sse2_impl(const uint8_t*, size_t, char*);
544 0           g_hex2bin = hex_to_bytes_sse2_impl; g_bin2hex = bytes_to_hex_sse2_impl;
545 0           g_hex2bin_name = "sse2"; g_bin2hex_name = "sse2"; return;
546             }
547 0 0         if (!strcasecmp(force, "avx2") && f.avx2) {
    0          
548             extern ptrdiff_t hex_to_bytes_avx2_impl(const char*, size_t, uint8_t*, bool);
549             extern ptrdiff_t bytes_to_hex_avx2_impl(const uint8_t*, size_t, char*);
550 0           g_hex2bin = hex_to_bytes_avx2_impl; g_bin2hex = bytes_to_hex_avx2_impl;
551 0           g_hex2bin_name = "avx2"; g_bin2hex_name = "avx2"; return;
552             }
553             #if HEXSIMD_HAVE_AVX512
554             if (!strcasecmp(force, "avx512") && f.avx512bw && f.avx512vl) {
555             extern ptrdiff_t hex_to_bytes_avx512_impl(const char*, size_t, uint8_t*, bool);
556             extern ptrdiff_t bytes_to_hex_avx512_impl(const uint8_t*, size_t, char*);
557             g_hex2bin = hex_to_bytes_avx512_impl; g_bin2hex = bytes_to_hex_avx512_impl;
558             g_hex2bin_name = "avx512bw"; g_bin2hex_name = "avx512bw"; return;
559             }
560             #endif // HEXSIMD_HAVE_AVX512
561             // If forced path isn’t compiled in or supported, we’ll fall through
562             // to auto-select below.
563             }
564              
565             #if HEXSIMD_HAVE_AVX512
566             if (f.avx512bw && f.avx512vl) {
567             extern ptrdiff_t hex_to_bytes_avx512_impl(const char*, size_t, uint8_t*, bool);
568             extern ptrdiff_t bytes_to_hex_avx512_impl(const uint8_t*, size_t, char*);
569             g_hex2bin = hex_to_bytes_avx512_impl; g_bin2hex = bytes_to_hex_avx512_impl;
570             g_hex2bin_name = "avx512bw"; g_bin2hex_name = "avx512bw"; return;
571             }
572             #endif // HEXSIMD_HAVE_AVX512
573 4 50         if (f.avx2) {
574             extern ptrdiff_t hex_to_bytes_avx2_impl(const char*, size_t, uint8_t*, bool);
575             extern ptrdiff_t bytes_to_hex_avx2_impl(const uint8_t*, size_t, char*);
576 4           g_hex2bin = hex_to_bytes_avx2_impl; g_bin2hex = bytes_to_hex_avx2_impl;
577 4           g_hex2bin_name = "avx2"; g_bin2hex_name = "avx2"; return;
578             }
579 0 0         if (f.sse2) {
580             extern ptrdiff_t hex_to_bytes_sse2_impl(const char*, size_t, uint8_t*, bool);
581             extern ptrdiff_t bytes_to_hex_sse2_impl(const uint8_t*, size_t, char*);
582 0           g_hex2bin = hex_to_bytes_sse2_impl; g_bin2hex = bytes_to_hex_sse2_impl;
583 0           g_hex2bin_name = "sse2"; g_bin2hex_name = "sse2"; return;
584             }
585             }
586              
587              
588 5           ptrdiff_t hex_to_bytes(const char *src, size_t len, uint8_t *dst, bool strict) {
589 5           pick_impls();
590 5           return g_hex2bin(src, len, dst, strict);
591             }
592 5           ptrdiff_t bytes_to_hex(const uint8_t *src, size_t len, char *dst) {
593 5           pick_impls();
594 5           return g_bin2hex(src, len, dst);
595             }
596              
597 2           const char* hexsimd_hex2bin_impl_name(void) { pick_impls(); return g_hex2bin_name; }
598 2           const char* hexsimd_bin2hex_impl_name(void) { pick_impls(); return g_bin2hex_name; }
599             //
600             // -------------------------
601             // Optional micro-test
602             // -------------------------
603             #ifdef TEST_HEX
604             extern const char* hexsimd_hex2bin_impl_name(void);
605              
606             static void dump_features(void){
607             isa_t f = detect_isa_runtime();
608             printf("ISA: sse2=%d avx=%d avx2=%d avx512bw=%d avx512vl=%d\n",
609             f.sse2, f.avx, f.avx2, f.avx512bw, f.avx512vl);
610             }
611              
612              
613             int main(void){
614             dump_features();
615             char *hx = "32D45FA2883337F16CAF523264E538D1AD89BD2924B67693AF1A7BCE7C6041AC96528A702C1FCAB51F75B14B6A5F20B1BAAFD93E9AC30769247EB6FAF408087F38E4BFB318CFA3A38FBA7206081ECEB9E7C4BC25201A14D5BCC6A6590B96A4738C9BCE941C541D688C8195550F6EF9CEEDD06353FB7A033AF63B40701632049C";
616             size_t BIN_LEN = strlen(hx)+1;
617             uint8_t *bin = malloc(BIN_LEN);
618             char *back = malloc( (BIN_LEN * 2) +1);
619             memset(back, 0x5A, BIN_LEN * 2);
620            
621             //ptrdiff_t n = hex_to_bytes(hx, strlen(hx), bin, true);
622             ptrdiff_t n = hex_to_bytes(hx, BIN_LEN-1, bin, true);
623             if (n < 0) { puts("parse failed"); return 1; }
624             ptrdiff_t m = bytes_to_hex(bin, (size_t)n, back);
625             back[m] = 0;
626             puts(hexsimd_hex2bin_impl_name());
627              
628             int match = strcmp(hx,back);
629              
630             if (match == 0 ){
631             puts(back);
632             } else {
633             printf("match: %d\n", match);
634             printf("Source: %s\n",hx);
635             printf(" Dest: %s\n",back);
636             printf("Error! Src and Dest do not match\n");
637             // use scalar to convert back to hex and compare, as we know that works
638             char *scalar_compare = malloc( (BIN_LEN * 2) +1);
639             memset(scalar_compare, 0x5A, BIN_LEN * 2);
640             ptrdiff_t t = bytes_to_hex_scalar_impl(bin, (size_t)n,scalar_compare);
641             scalar_compare[t] = 0;
642             match = strcmp(hx,scalar_compare);
643             if (match == 0 ){
644             printf("Hex2Bin OK\n");
645             } else {
646             printf("Hex2Bin NOT OK\n");
647             printf("hx: %s\n", hx);
648             printf("sc: %s\n", scalar_compare);
649             }
650             return 1;
651             }
652              
653             return 0;
654             }
655              
656             #endif