Coverage Report

Created: 2026-08-31 07:21

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/simdutf/src/icelake/icelake_base64.inl.cpp
Line
Count
Source
1
// file included directly
2
/**
3
 * References and further reading:
4
 *
5
 * Wojciech Muła, Daniel Lemire, Base64 encoding and decoding at almost the
6
 * speed of a memory copy, Software: Practice and Experience 50 (2), 2020.
7
 * https://arxiv.org/abs/1910.05109
8
 *
9
 * Wojciech Muła, Daniel Lemire, Faster Base64 Encoding and Decoding using AVX2
10
 * Instructions, ACM Transactions on the Web 12 (3), 2018.
11
 * https://arxiv.org/abs/1704.00605
12
 *
13
 * Simon Josefsson. 2006. The Base16, Base32, and Base64 Data Encodings.
14
 * https://tools.ietf.org/html/rfc4648. (2006). Internet Engineering Task Force,
15
 * Request for Comments: 4648.
16
 *
17
 * Alfred Klomp. 2014a. Fast Base64 encoding/decoding with SSE vectorization.
18
 * http://www.alfredklomp.com/programming/sse-base64/. (2014).
19
 *
20
 * Alfred Klomp. 2014b. Fast Base64 stream encoder/decoder in C99, with SIMD
21
 * acceleration. https://github.com/aklomp/base64. (2014).
22
 *
23
 * Hanson Char. 2014. A Fast and Correct Base 64 Codec. (2014).
24
 * https://aws.amazon.com/blogs/developer/a-fast-and-correct-base-64-codec/
25
 *
26
 * Nick Kopp. 2013. Base64 Encoding on a GPU.
27
 * https://www.codeproject.com/Articles/276993/Base-Encoding-on-a-GPU. (2013).
28
 */
29
30
struct block64 {
31
  __m512i chunks[1];
32
};
33
34
static inline size_t write_multi_lf_m256i(__m256i chunk, uint8_t *out,
35
                                          size_t output_len, size_t line_length,
36
0
                                          size_t &offset) {
37
0
  if (line_length >= 32) {
38
0
    if (offset + output_len > line_length) {
39
0
      __m512i expanded = _mm512_mask_expand_epi8(
40
0
          _mm512_set1_epi8('\n'), ~(1ULL << (line_length - offset)),
41
0
          _mm512_castsi256_si512(chunk));
42
0
      _mm512_mask_storeu_epi8(reinterpret_cast<__m512i *>(out),
43
0
                              (1ULL << (output_len + 1)) - 1, expanded);
44
0
      offset = output_len - (line_length - offset);
45
0
      return output_len + 1;
46
0
    } else {
47
0
      __mmask32 write_mask =
48
0
          output_len == 32 ? 0xffffffff : ((__mmask32)1 << output_len) - 1;
49
0
      _mm256_mask_storeu_epi8(reinterpret_cast<__m256i *>(out), write_mask,
50
0
                              chunk);
51
0
      offset += output_len;
52
0
      return output_len;
53
0
    }
54
0
  } else {
55
    // minimum line_length starts from 4
56
0
    static const uint64_t masks[28] = {
57
0
        0x2700000842108421, 0x2600001041041041, 0x2500000810204081,
58
0
        0x2400000101010101, 0x2300000008040201, 0x2300000040100401,
59
0
        0x2300000200400801, 0x2200000001001001, 0x2200000004002001,
60
0
        0x2200000010004001, 0x2200000040008001, 0x2200000100010001,
61
0
        0x2100000000020001, 0x2100000000040001, 0x2100000000080001,
62
0
        0x2100000000100001, 0x2100000000200001, 0x2100000000400001,
63
0
        0x2100000000800001, 0x2100000001000001, 0x2100000002000001,
64
0
        0x2100000004000001, 0x2100000008000001, 0x2100000010000001,
65
0
        0x2100000020000001, 0x2100000040000001, 0x2100000080000001,
66
0
        0x2100000100000001,
67
0
    };
68
0
    uint64_t mask = masks[line_length - 4];
69
0
    uint64_t max_width;
70
0
    uint64_t num_lf;
71
0
    if (output_len == 32) {
72
      // use pre-computed width to avoid integer division in main loop
73
0
      max_width = mask >> 56;
74
0
      mask = (mask << (line_length - offset)) & ((1ULL << max_width) - 1);
75
0
      num_lf = _mm_popcnt_u64(mask);
76
0
    } else {
77
0
      if (output_len <= line_length - offset) {
78
0
        num_lf = 0;
79
0
      } else {
80
0
        num_lf = 1 + (output_len - (line_length - offset) - 1) / line_length;
81
0
      }
82
0
      max_width = num_lf + output_len;
83
0
      mask = (mask << (line_length - offset)) & ((1ULL << max_width) - 1);
84
0
    }
85
0
    __mmask64 write_mask = (1ULL << (num_lf + output_len)) - 1;
86
0
    __m512i expanded =
87
0
        _mm512_mask_expand_epi8(_mm512_set1_epi8('\n'), ~((uint64_t)mask),
88
0
                                _mm512_castsi256_si512(chunk));
89
0
    _mm512_mask_storeu_epi8(reinterpret_cast<__m512i *>(out), write_mask,
90
0
                            expanded);
91
0
    offset = _lzcnt_u64(mask) - _lzcnt_u64(write_mask);
92
0
    return num_lf + output_len;
93
0
  }
94
0
}
95
96
template <bool base64_url, bool use_lines>
97
size_t encode_base64_impl(char *dst, const char *src, size_t srclen,
98
                          base64_options options,
99
0
                          size_t line_length = simdutf::default_line_length) {
100
0
  size_t offset = 0;
101
0
  if (line_length < 4) {
102
0
    line_length = 4; // We do not support line_length less than 4
103
0
  }
104
  // credit: Wojciech Muła
105
0
  const uint8_t *input = (const uint8_t *)src;
106
107
0
  uint8_t *out = (uint8_t *)dst;
108
0
  static const char *lookup_tbl =
109
0
      base64_url
110
0
          ? "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"
111
0
          : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
112
0
  const __m512i shuffle_input = _mm512_setr_epi32(
113
0
      0x01020001, 0x04050304, 0x07080607, 0x0a0b090a, 0x0d0e0c0d, 0x10110f10,
114
0
      0x13141213, 0x16171516, 0x191a1819, 0x1c1d1b1c, 0x1f201e1f, 0x22232122,
115
0
      0x25262425, 0x28292728, 0x2b2c2a2b, 0x2e2f2d2e);
116
0
  const __m512i lookup =
117
0
      _mm512_loadu_si512(reinterpret_cast<const __m512i *>(lookup_tbl));
118
0
  const __m512i multi_shifts = _mm512_set1_epi64(UINT64_C(0x3036242a1016040a));
119
0
  size_t size = srclen;
120
0
  __mmask64 input_mask = 0xffffffffffff; // (1 << 48) - 1
121
  // We want that input == end_input means that we must stop.
122
0
  const uint8_t *end_input = input + (size - (size % 48));
123
0
  while (input != end_input) {
124
0
    const __m512i v = _mm512_maskz_loadu_epi8(
125
0
        input_mask, reinterpret_cast<const __m512i *>(input));
126
0
    const __m512i in = _mm512_permutexvar_epi8(shuffle_input, v);
127
0
    const __m512i indices = _mm512_multishift_epi64_epi8(multi_shifts, in);
128
0
    const __m512i result = _mm512_permutexvar_epi8(indices, lookup);
129
0
    if (use_lines) {
130
0
      if (offset + 64 > line_length) {
131
0
        if (line_length >= 64) {
132
0
          __m512i expanded = _mm512_mask_expand_epi8(
133
0
              _mm512_set1_epi8('\n'), ~(1ULL << ((line_length - offset))),
134
0
              result);
135
0
          _mm512_storeu_si512(reinterpret_cast<__m512i *>(out), expanded);
136
0
          __m128i last_lane =
137
0
              _mm512_extracti32x4_epi32(result, 3); // Lane 3 (bytes 48-63)
138
0
          uint8_t last_byte =
139
0
              static_cast<uint8_t>(_mm_extract_epi8(last_lane, 15));
140
0
          out[64] = last_byte;
141
0
          out += 65;
142
0
          offset = 64 - (line_length - offset);
143
0
        } else { // slow path
144
0
          __m256i lo = _mm512_extracti64x4_epi64(result, 0);
145
0
          __m256i hi = _mm512_extracti64x4_epi64(result, 1);
146
0
          out += write_multi_lf_m256i(lo, out, 32, line_length, offset);
147
0
          out += write_multi_lf_m256i(hi, out, 32, line_length, offset);
148
0
        }
149
0
      } else {
150
0
        _mm512_storeu_si512(reinterpret_cast<__m512i *>(out), result);
151
0
        offset += 64;
152
0
        out += 64;
153
0
      }
154
0
    } else {
155
0
      _mm512_storeu_si512(reinterpret_cast<__m512i *>(out), result);
156
0
      out += 64;
157
0
    }
158
0
    input += 48;
159
0
  }
160
0
  size = size % 48;
161
162
0
  input_mask = ((__mmask64)1 << size) - 1;
163
0
  const __m512i v = _mm512_maskz_loadu_epi8(
164
0
      input_mask, reinterpret_cast<const __m512i *>(input));
165
0
  const __m512i in = _mm512_permutexvar_epi8(shuffle_input, v);
166
0
  const __m512i indices = _mm512_multishift_epi64_epi8(multi_shifts, in);
167
0
  bool padding_needed =
168
0
      (((options & base64_url) == 0) ^
169
0
       ((options & base64_reverse_padding) == base64_reverse_padding));
170
0
  size_t padding_amount = ((size % 3) > 0) ? (3 - (size % 3)) : 0;
171
0
  size_t output_len = ((size + 2) / 3) * 4;
172
0
  size_t non_padded_output_len = output_len - padding_amount;
173
0
  if (!padding_needed) {
174
0
    output_len = non_padded_output_len;
175
0
  }
176
  // If no output, we are done.
177
0
  if (output_len == 0) {
178
0
    return (size_t)(out - (uint8_t *)dst);
179
0
  }
180
0
  __mmask64 output_mask = 0xFFFFFFFFFFFFFFFF >> (64 - output_len);
181
0
  __m512i result = _mm512_mask_permutexvar_epi8(
182
0
      _mm512_set1_epi8('='), ((__mmask64)1 << non_padded_output_len) - 1,
183
0
      indices, lookup);
184
0
  if (use_lines) {
185
0
    if (offset + output_len > line_length) {
186
0
      if (line_length >= 64) {
187
0
        __m512i expanded = _mm512_mask_expand_epi8(
188
0
            _mm512_set1_epi8('\n'), ~(1ULL << ((line_length - offset))),
189
0
            result);
190
0
        if (output_len == 64) {
191
0
          _mm512_storeu_si512(reinterpret_cast<__m512i *>(out), expanded);
192
0
          out += 64;
193
0
          _mm512_mask_storeu_epi8(reinterpret_cast<__m512i *>(out - 63),
194
0
                                  1ULL << 63, result);
195
0
          out++;
196
0
        } else {
197
0
          output_mask = 0xFFFFFFFFFFFFFFFF >> (64 - output_len - 1);
198
0
          _mm512_mask_storeu_epi8(reinterpret_cast<__m512i *>(out), output_mask,
199
0
                                  expanded);
200
0
          out += output_len + 1;
201
0
        }
202
0
      } else {
203
0
        if (output_len > 32) {
204
0
          __m256i lo = _mm512_extracti64x4_epi64(result, 0);
205
0
          __m256i hi = _mm512_extracti64x4_epi64(result, 1);
206
0
          out += write_multi_lf_m256i(lo, out, 32, line_length, offset);
207
0
          out += write_multi_lf_m256i(hi, out, output_len - 32, line_length,
208
0
                                      offset);
209
0
        } else {
210
0
          __m256i lo = _mm512_extracti64x4_epi64(result, 0);
211
0
          out += write_multi_lf_m256i(lo, out, output_len, line_length, offset);
212
0
        }
213
0
      }
214
0
    } else {
215
0
      _mm512_mask_storeu_epi8(reinterpret_cast<__m512i *>(out), output_mask,
216
0
                              result);
217
0
      out += output_len;
218
0
    }
219
0
  } else {
220
0
    _mm512_mask_storeu_epi8(reinterpret_cast<__m512i *>(out), output_mask,
221
0
                            result);
222
0
    out += output_len;
223
0
  }
224
0
  return (size_t)(out - (uint8_t *)dst);
225
0
}
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::encode_base64_impl<true, false>(char*, char const*, unsigned long, simdutf::base64_options, unsigned long)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::encode_base64_impl<false, false>(char*, char const*, unsigned long, simdutf::base64_options, unsigned long)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::encode_base64_impl<true, true>(char*, char const*, unsigned long, simdutf::base64_options, unsigned long)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::encode_base64_impl<false, true>(char*, char const*, unsigned long, simdutf::base64_options, unsigned long)
226
227
template <bool base64_url>
228
size_t encode_base64(char *dst, const char *src, size_t srclen,
229
0
                     base64_options options) {
230
0
  return encode_base64_impl<base64_url, false>(dst, src, srclen, options);
231
0
}
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::encode_base64<true>(char*, char const*, unsigned long, simdutf::base64_options)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::encode_base64<false>(char*, char const*, unsigned long, simdutf::base64_options)
232
233
template <bool base64_url, bool ignore_garbage, bool default_or_url>
234
static inline uint64_t to_base64_mask(block64 *b, uint64_t *error,
235
0
                                      uint64_t input_mask = UINT64_MAX) {
236
0
  __m512i input = b->chunks[0];
237
0
  const __m512i ascii_space_tbl = _mm512_set_epi8(
238
0
      0, 0, 13, 12, 0, 10, 9, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 13, 12, 0, 10,
239
0
      9, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 13, 12, 0, 10, 9, 0, 0, 0, 0, 0, 0,
240
0
      0, 0, 32, 0, 0, 13, 12, 0, 10, 9, 0, 0, 0, 0, 0, 0, 0, 0, 32);
241
0
  __m512i lookup0;
242
0
  if (default_or_url) {
243
0
    lookup0 = _mm512_set_epi8(
244
0
        -128, -128, -128, -128, -128, -128, 61, 60, 59, 58, 57, 56, 55, 54, 53,
245
0
        52, 63, -128, 62, -128, 62, -128, -128, -128, -128, -128, -128, -128,
246
0
        -128, -128, -128, -1, -128, -128, -128, -128, -128, -128, -128, -128,
247
0
        -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -1, -128,
248
0
        -128, -1, -1, -128, -128, -128, -128, -128, -128, -128, -128, -1);
249
0
  } else if (base64_url) {
250
0
    lookup0 = _mm512_set_epi8(
251
0
        -128, -128, -128, -128, -128, -128, 61, 60, 59, 58, 57, 56, 55, 54, 53,
252
0
        52, -128, -128, 62, -128, -128, -128, -128, -128, -128, -128, -128,
253
0
        -128, -128, -128, -128, -1, -128, -128, -128, -128, -128, -128, -128,
254
0
        -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -1,
255
0
        -128, -128, -1, -1, -128, -128, -128, -128, -128, -128, -128, -128, -1);
256
0
  } else {
257
0
    lookup0 = _mm512_set_epi8(
258
0
        -128, -128, -128, -128, -128, -128, 61, 60, 59, 58, 57, 56, 55, 54, 53,
259
0
        52, 63, -128, -128, -128, 62, -128, -128, -128, -128, -128, -128, -128,
260
0
        -128, -128, -128, -1, -128, -128, -128, -128, -128, -128, -128, -128,
261
0
        -128, -128, -128, -128, -128, -128, -128, -128, -128, -128, -1, -128,
262
0
        -128, -1, -1, -128, -128, -128, -128, -128, -128, -128, -128, -128);
263
0
  }
264
0
  __m512i lookup1;
265
0
  if (default_or_url) {
266
0
    lookup1 = _mm512_set_epi8(
267
0
        -128, -128, -128, -128, -128, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42,
268
0
        41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, -128,
269
0
        63, -128, -128, -128, -128, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15,
270
0
        14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -128);
271
0
  } else if (base64_url) {
272
0
    lookup1 = _mm512_set_epi8(
273
0
        -128, -128, -128, -128, -128, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42,
274
0
        41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, -128,
275
0
        63, -128, -128, -128, -128, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16, 15,
276
0
        14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -128);
277
0
  } else {
278
0
    lookup1 = _mm512_set_epi8(
279
0
        -128, -128, -128, -128, -128, 51, 50, 49, 48, 47, 46, 45, 44, 43, 42,
280
0
        41, 40, 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, 29, 28, 27, 26, -128,
281
0
        -128, -128, -128, -128, -128, 25, 24, 23, 22, 21, 20, 19, 18, 17, 16,
282
0
        15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -128);
283
0
  }
284
285
0
  const __m512i translated = _mm512_permutex2var_epi8(lookup0, input, lookup1);
286
0
  const __m512i combined = _mm512_or_si512(translated, input);
287
0
  const __mmask64 mask = _mm512_movepi8_mask(combined) & input_mask;
288
0
  if (!ignore_garbage && mask) {
289
0
    const __mmask64 spaces =
290
0
        _mm512_cmpeq_epi8_mask(_mm512_shuffle_epi8(ascii_space_tbl, input),
291
0
                               input) &
292
0
        input_mask;
293
0
    *error = (mask ^ spaces);
294
0
  }
295
0
  b->chunks[0] = translated;
296
297
0
  return mask | (~input_mask);
298
0
}
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::to_base64_mask<false, true, true>(simdutf::icelake::(anonymous namespace)::block64*, unsigned long*, unsigned long)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::to_base64_mask<false, false, true>(simdutf::icelake::(anonymous namespace)::block64*, unsigned long*, unsigned long)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::to_base64_mask<true, true, false>(simdutf::icelake::(anonymous namespace)::block64*, unsigned long*, unsigned long)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::to_base64_mask<true, false, false>(simdutf::icelake::(anonymous namespace)::block64*, unsigned long*, unsigned long)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::to_base64_mask<false, true, false>(simdutf::icelake::(anonymous namespace)::block64*, unsigned long*, unsigned long)
Unexecuted instantiation: simdutf.cpp:unsigned long simdutf::icelake::(anonymous namespace)::to_base64_mask<false, false, false>(simdutf::icelake::(anonymous namespace)::block64*, unsigned long*, unsigned long)
299
300
0
static inline void copy_block(block64 *b, char *output) {
301
0
  _mm512_storeu_si512(reinterpret_cast<__m512i *>(output), b->chunks[0]);
302
0
}
303
304
0
static inline uint64_t compress_block(block64 *b, uint64_t mask, char *output) {
305
0
  uint64_t nmask = ~mask;
306
0
  __m512i c = _mm512_maskz_compress_epi8(nmask, b->chunks[0]);
307
0
  _mm512_storeu_si512(reinterpret_cast<__m512i *>(output), c);
308
0
  return _mm_popcnt_u64(nmask);
309
0
}
310
311
// The caller of this function is responsible to ensure that there are 64 bytes
312
// available from reading at src. The data is read into a block64 structure.
313
0
static inline void load_block(block64 *b, const char *src) {
314
0
  b->chunks[0] = _mm512_loadu_si512(reinterpret_cast<const __m512i *>(src));
315
0
}
316
317
static inline void load_block_partial(block64 *b, const char *src,
318
0
                                      __mmask64 input_mask) {
319
0
  b->chunks[0] = _mm512_maskz_loadu_epi8(
320
0
      input_mask, reinterpret_cast<const __m512i *>(src));
321
0
}
322
323
// The caller of this function is responsible to ensure that there are 128 bytes
324
// available from reading at src. The data is read into a block64 structure.
325
0
static inline void load_block(block64 *b, const char16_t *src) {
326
0
  __m512i m1 = _mm512_loadu_si512(reinterpret_cast<const __m512i *>(src));
327
0
  __m512i m2 = _mm512_loadu_si512(reinterpret_cast<const __m512i *>(src + 32));
328
0
  __m512i p = _mm512_packus_epi16(m1, m2);
329
0
  b->chunks[0] =
330
0
      _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), p);
331
0
}
332
333
static inline void load_block_partial(block64 *b, const char16_t *src,
334
0
                                      __mmask64 input_mask) {
335
0
  __m512i m1 = _mm512_maskz_loadu_epi16((__mmask32)input_mask,
336
0
                                        reinterpret_cast<const __m512i *>(src));
337
0
  __m512i m2 =
338
0
      _mm512_maskz_loadu_epi16((__mmask32)(input_mask >> 32),
339
0
                               reinterpret_cast<const __m512i *>(src + 32));
340
0
  __m512i p = _mm512_packus_epi16(m1, m2);
341
0
  b->chunks[0] =
342
0
      _mm512_permutexvar_epi64(_mm512_setr_epi64(0, 2, 4, 6, 1, 3, 5, 7), p);
343
0
}
344
345
0
static inline void base64_decode(char *out, __m512i str) {
346
0
  const __m512i merge_ab_and_bc =
347
0
      _mm512_maddubs_epi16(str, _mm512_set1_epi32(0x01400140));
348
0
  const __m512i merged =
349
0
      _mm512_madd_epi16(merge_ab_and_bc, _mm512_set1_epi32(0x00011000));
350
0
  const __m512i pack = _mm512_set_epi8(
351
0
      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 61, 62, 56, 57, 58,
352
0
      52, 53, 54, 48, 49, 50, 44, 45, 46, 40, 41, 42, 36, 37, 38, 32, 33, 34,
353
0
      28, 29, 30, 24, 25, 26, 20, 21, 22, 16, 17, 18, 12, 13, 14, 8, 9, 10, 4,
354
0
      5, 6, 0, 1, 2);
355
0
  const __m512i shuffled = _mm512_permutexvar_epi8(pack, merged);
356
0
  _mm512_mask_storeu_epi8(
357
0
      (__m512i *)out, 0xffffffffffff,
358
0
      shuffled); // mask would be 0xffffffffffff since we write 48 bytes.
359
0
}
360
// decode 64 bytes and output 48 bytes
361
0
static inline void base64_decode_block(char *out, const char *src) {
362
0
  base64_decode(out,
363
0
                _mm512_loadu_si512(reinterpret_cast<const __m512i *>(src)));
364
0
}
365
0
static inline void base64_decode_block(char *out, block64 *b) {
366
0
  base64_decode(out, b->chunks[0]);
367
0
}
368
369
template <bool base64_url, bool ignore_garbage, bool default_or_url,
370
          typename chartype>
371
full_result
372
compress_decode_base64(char *dst, const chartype *src, size_t srclen,
373
                       base64_options options,
374
0
                       last_chunk_handling_options last_chunk_options) {
375
0
  (void)options;
376
0
  const uint8_t *to_base64 =
377
0
      default_or_url ? tables::base64::to_base64_default_or_url_value
378
0
                     : (base64_url ? tables::base64::to_base64_url_value
379
0
                                   : tables::base64::to_base64_value);
380
0
  auto ri = simdutf::scalar::base64::find_end(src, srclen, options);
381
0
  size_t equallocation = ri.equallocation;
382
0
  size_t padding_characters = ri.equalsigns;
383
0
  srclen = ri.srclen;
384
0
  size_t full_input_length = ri.full_input_length;
385
0
  if (srclen == 0) {
386
0
    if (!ignore_garbage && padding_characters > 0) {
387
0
      return {INVALID_BASE64_CHARACTER, equallocation, 0, true};
388
0
    }
389
0
    return {SUCCESS, full_input_length, 0};
390
0
  }
391
0
  const chartype *const srcinit = src;
392
0
  const char *const dstinit = dst;
393
0
  const chartype *const srcend = src + srclen;
394
395
  // figure out why block_size == 2 is sometimes best???
396
0
  constexpr size_t block_size = 6;
397
0
  char buffer[block_size * 64];
398
0
  char *bufferptr = buffer;
399
0
  if (srclen >= 64) {
400
0
    const chartype *const srcend64 = src + srclen - 64;
401
0
    while (src <= srcend64) {
402
0
      block64 b;
403
0
      load_block(&b, src);
404
0
      src += 64;
405
0
      uint64_t error = 0;
406
0
      uint64_t badcharmask =
407
0
          to_base64_mask<base64_url, ignore_garbage, default_or_url>(&b,
408
0
                                                                     &error);
409
0
      if (!ignore_garbage && error) {
410
0
        src -= 64;
411
0
        size_t error_offset = _tzcnt_u64(error);
412
0
        return {error_code::INVALID_BASE64_CHARACTER,
413
0
                size_t(src - srcinit + error_offset), size_t(dst - dstinit)};
414
0
      }
415
0
      if (badcharmask != 0) {
416
        // optimization opportunity: check for simple masks like those made of
417
        // continuous 1s followed by continuous 0s. And masks containing a
418
        // single bad character.
419
0
        bufferptr += compress_block(&b, badcharmask, bufferptr);
420
0
      } else if (bufferptr != buffer) {
421
0
        copy_block(&b, bufferptr);
422
0
        bufferptr += 64;
423
0
      } else {
424
0
        base64_decode_block(dst, &b);
425
0
        dst += 48;
426
0
      }
427
0
      if (bufferptr >= (block_size - 1) * 64 + buffer) {
428
0
        for (size_t i = 0; i < (block_size - 1); i++) {
429
0
          base64_decode_block(dst, buffer + i * 64);
430
0
          dst += 48;
431
0
        }
432
0
        std::memcpy(buffer, buffer + (block_size - 1) * 64,
433
0
                    64); // 64 might be too much
434
0
        bufferptr -= (block_size - 1) * 64;
435
0
      }
436
0
    }
437
0
  }
438
439
0
  int last_block_len = (int)(srcend - src);
440
0
  if (last_block_len != 0) {
441
0
    __mmask64 input_mask = ((__mmask64)1 << last_block_len) - 1;
442
0
    block64 b;
443
0
    load_block_partial(&b, src, input_mask);
444
0
    uint64_t error = 0;
445
0
    uint64_t badcharmask =
446
0
        to_base64_mask<base64_url, ignore_garbage, default_or_url>(&b, &error,
447
0
                                                                   input_mask);
448
0
    if (!ignore_garbage && error) {
449
0
      size_t error_offset = _tzcnt_u64(error);
450
0
      return {error_code::INVALID_BASE64_CHARACTER,
451
0
              size_t(src - srcinit + error_offset), size_t(dst - dstinit)};
452
0
    }
453
0
    src += last_block_len;
454
0
    bufferptr += compress_block(&b, badcharmask, bufferptr);
455
0
  }
456
457
0
  char *buffer_start = buffer;
458
0
  for (; buffer_start + 64 <= bufferptr; buffer_start += 64) {
459
0
    base64_decode_block(dst, buffer_start);
460
0
    dst += 48;
461
0
  }
462
0
  if ((bufferptr - buffer_start) != 0) {
463
    // For efficiency reasons, we end up reproducing much of the code
464
    // in base64_tail_decode_impl. Better engineering would be to
465
    // refactor the code so that we can call it without a performance hit.
466
0
    size_t rem = (bufferptr - buffer_start);
467
0
    int idx = rem % 4;
468
0
    __mmask64 mask = ((__mmask64)1 << rem) - 1;
469
0
    __m512i input = _mm512_maskz_loadu_epi8(mask, buffer_start);
470
0
    size_t output_len = (rem / 4) * 3;
471
0
    __mmask64 output_mask = mask >> (rem - output_len);
472
0
    const __m512i merge_ab_and_bc =
473
0
        _mm512_maddubs_epi16(input, _mm512_set1_epi32(0x01400140));
474
0
    const __m512i merged =
475
0
        _mm512_madd_epi16(merge_ab_and_bc, _mm512_set1_epi32(0x00011000));
476
0
    const __m512i pack = _mm512_set_epi8(
477
0
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 60, 61, 62, 56, 57, 58,
478
0
        52, 53, 54, 48, 49, 50, 44, 45, 46, 40, 41, 42, 36, 37, 38, 32, 33, 34,
479
0
        28, 29, 30, 24, 25, 26, 20, 21, 22, 16, 17, 18, 12, 13, 14, 8, 9, 10, 4,
480
0
        5, 6, 0, 1, 2);
481
0
    const __m512i shuffled = _mm512_permutexvar_epi8(pack, merged);
482
    // We never should have that the number of base64 characters + the
483
    // number of padding characters is more than 4.
484
0
    if (!ignore_garbage && (idx + padding_characters > 4)) {
485
0
      return {INVALID_BASE64_CHARACTER, equallocation, size_t(dst - dstinit),
486
0
              true};
487
0
    }
488
    // The idea here is that in loose mode,
489
    // if there is padding at all, it must be used
490
    // to form 4-wise chunk. However, in loose mode,
491
    // we do accept no padding at all.
492
0
    if (!ignore_garbage &&
493
0
        last_chunk_options == last_chunk_handling_options::loose &&
494
0
        (idx >= 2) && padding_characters > 0 &&
495
0
        ((idx + padding_characters) & 3) != 0) {
496
0
      return {INVALID_BASE64_CHARACTER, equallocation, size_t(dst - dstinit),
497
0
              true};
498
0
    } else
499
      // The idea here is that in strict mode, we do not want to accept
500
      // incomplete base64 chunks. So if the chunk was otherwise valid, we
501
      // return BASE64_INPUT_REMAINDER.
502
0
      if (!ignore_garbage &&
503
0
          last_chunk_options == last_chunk_handling_options::strict &&
504
0
          (idx >= 2) && ((idx + padding_characters) & 3) != 0) {
505
        // The partial chunk was at src - idx
506
0
        if (output_len > 0) {
507
0
          _mm512_mask_storeu_epi8((__m512i *)dst, output_mask, shuffled);
508
0
          dst += output_len;
509
0
        }
510
0
        return {BASE64_INPUT_REMAINDER, equallocation, size_t(dst - dstinit),
511
0
                true};
512
0
      } else
513
        // If there is a partial chunk with insufficient padding, with
514
        // stop_before_partial, we need to just ignore it. In "only full" mode,
515
        // skip the minute there are padding characters.
516
0
        if ((last_chunk_options ==
517
0
                 last_chunk_handling_options::stop_before_partial &&
518
0
             (padding_characters + idx < 4) && (idx != 0) &&
519
0
             (idx >= 2 || padding_characters == 0)) ||
520
0
            (last_chunk_options ==
521
0
                 last_chunk_handling_options::only_full_chunks &&
522
0
             (idx >= 2 || padding_characters == 0))) {
523
0
          if (output_len > 0) {
524
0
            _mm512_mask_storeu_epi8((__m512i *)dst, output_mask, shuffled);
525
0
            dst += output_len;
526
0
          }
527
          // we need to rewind src to before the partial chunk
528
0
          size_t characters_to_skip = idx;
529
0
          while (characters_to_skip > 0) {
530
0
            src--;
531
0
            auto c = *src;
532
0
            uint8_t code = to_base64[uint8_t(c)];
533
0
            if (simdutf::scalar::base64::is_eight_byte(c) && code <= 63) {
534
0
              characters_to_skip--;
535
0
            }
536
0
          }
537
          // And then we need to skip ignored characters
538
          // See https://github.com/simdutf/simdutf/issues/824
539
0
          while (src > srcinit) {
540
0
            auto c = *(src - 1);
541
0
            uint8_t code = to_base64[uint8_t(c)];
542
0
            if (simdutf::scalar::base64::is_eight_byte(c) && code <= 63) {
543
0
              break;
544
0
            }
545
0
            src--;
546
0
          }
547
0
          return {SUCCESS, size_t(src - srcinit), size_t(dst - dstinit)};
548
0
        } else {
549
0
          if (idx == 2) {
550
0
            if (!ignore_garbage &&
551
0
                last_chunk_options == last_chunk_handling_options::strict) {
552
0
              uint32_t triple = (uint32_t(bufferptr[-2]) << 3 * 6) +
553
0
                                (uint32_t(bufferptr[-1]) << 2 * 6);
554
0
              if (triple & 0xffff) {
555
0
                if (output_len > 0) {
556
0
                  _mm512_mask_storeu_epi8((__m512i *)dst, output_mask,
557
0
                                          shuffled);
558
0
                  dst += output_len;
559
0
                }
560
0
                return {BASE64_EXTRA_BITS, size_t(src - srcinit),
561
0
                        size_t(dst - dstinit)};
562
0
              }
563
0
            }
564
0
            output_mask = (output_mask << 1) | 1;
565
0
            output_len += 1;
566
0
            _mm512_mask_storeu_epi8((__m512i *)dst, output_mask, shuffled);
567
0
            dst += output_len;
568
0
          } else if (idx == 3) {
569
0
            if (!ignore_garbage &&
570
0
                last_chunk_options == last_chunk_handling_options::strict) {
571
0
              uint32_t triple = (uint32_t(bufferptr[-3]) << 3 * 6) +
572
0
                                (uint32_t(bufferptr[-2]) << 2 * 6) +
573
0
                                (uint32_t(bufferptr[-1]) << 1 * 6);
574
0
              if (triple & 0xff) {
575
0
                if (output_len > 0) {
576
0
                  _mm512_mask_storeu_epi8((__m512i *)dst, output_mask,
577
0
                                          shuffled);
578
0
                  dst += output_len;
579
0
                }
580
0
                return {BASE64_EXTRA_BITS, size_t(src - srcinit),
581
0
                        size_t(dst - dstinit)};
582
0
              }
583
0
            }
584
0
            output_mask = (output_mask << 2) | 3;
585
0
            output_len += 2;
586
0
            _mm512_mask_storeu_epi8((__m512i *)dst, output_mask, shuffled);
587
0
            dst += output_len;
588
0
          } else if (!ignore_garbage && idx == 1 &&
589
0
                     (!is_partial(last_chunk_options) ||
590
0
                      (is_partial(last_chunk_options) &&
591
0
                       padding_characters > 0))) {
592
0
            if (output_len > 0) {
593
0
              _mm512_mask_storeu_epi8((__m512i *)dst, output_mask, shuffled);
594
0
              dst += output_len;
595
0
            }
596
0
            return {BASE64_INPUT_REMAINDER, size_t(src - srcinit),
597
0
                    size_t(dst - dstinit)};
598
0
          } else if (!ignore_garbage && idx == 0 && padding_characters > 0) {
599
0
            if (output_len > 0) {
600
0
              _mm512_mask_storeu_epi8((__m512i *)dst, output_mask, shuffled);
601
0
              dst += output_len;
602
0
            }
603
0
            return {INVALID_BASE64_CHARACTER, equallocation,
604
0
                    size_t(dst - dstinit), true};
605
0
          } else {
606
0
            if (output_len > 0) {
607
0
              _mm512_mask_storeu_epi8((__m512i *)dst, output_mask, shuffled);
608
0
              dst += output_len;
609
0
            }
610
0
          }
611
0
        }
612
0
    if (!ignore_garbage && !is_partial(last_chunk_options) &&
613
0
        padding_characters > 0) {
614
0
      size_t output_count = size_t(dst - dstinit);
615
0
      if ((output_count % 3 == 0) ||
616
0
          ((output_count % 3) + 1 + padding_characters != 4)) {
617
0
        return {INVALID_BASE64_CHARACTER, equallocation, output_count, true};
618
0
      }
619
0
    }
620
0
    return {SUCCESS, full_input_length, size_t(dst - dstinit)};
621
0
  }
622
623
0
  if (!ignore_garbage && padding_characters > 0) {
624
0
    if ((size_t(dst - dstinit) % 3 == 0) ||
625
0
        ((size_t(dst - dstinit) % 3) + 1 + padding_characters != 4)) {
626
0
      return {INVALID_BASE64_CHARACTER, equallocation, size_t(dst - dstinit),
627
0
              true};
628
0
    }
629
0
  }
630
0
  return {SUCCESS, srclen, size_t(dst - dstinit)};
631
0
}
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<false, true, true, char>(char*, char const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<false, false, true, char>(char*, char const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<true, true, false, char>(char*, char const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<true, false, false, char>(char*, char const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<false, true, false, char>(char*, char const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<false, false, false, char>(char*, char const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<false, true, true, char16_t>(char*, char16_t const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<false, false, true, char16_t>(char*, char16_t const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<true, true, false, char16_t>(char*, char16_t const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<true, false, false, char16_t>(char*, char16_t const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<false, true, false, char16_t>(char*, char16_t const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
Unexecuted instantiation: simdutf.cpp:simdutf::full_result simdutf::icelake::(anonymous namespace)::compress_decode_base64<false, false, false, char16_t>(char*, char16_t const*, unsigned long, simdutf::base64_options, simdutf::last_chunk_handling_options)
632
633
simdutf_warn_unused size_t icelake_binary_length_from_base64(const char *input,
634
0
                                                             size_t length) {
635
0
  size_t count = 0;
636
0
  const char *ptr = input;
637
0
  const char *end = input + length;
638
639
0
  __m512i spaces = _mm512_set1_epi8(0x20);
640
0
  while (ptr + 64 <= end) {
641
0
    __m512i data = _mm512_loadu_si512(reinterpret_cast<const __m512i *>(ptr));
642
0
    uint64_t mask = _mm512_cmpgt_epi8_mask(data, spaces);
643
0
    count += count_ones(mask);
644
0
    ptr += 64;
645
0
  }
646
647
0
  if (ptr < end) {
648
0
    size_t len = end - ptr;
649
0
    __mmask64 input_mask = ((__mmask64)1 << len) - 1;
650
0
    __m512i data = _mm512_maskz_loadu_epi8(
651
0
        input_mask, reinterpret_cast<const __m512i *>(ptr));
652
0
    uint64_t mask = _mm512_cmpgt_epi8_mask(data, spaces);
653
0
    count += count_ones(mask);
654
0
    ptr += len;
655
0
  }
656
657
0
  size_t padding = 0;
658
0
  size_t pos = length;
659
0
  while (pos > 0 && padding < 2) {
660
0
    char c = input[--pos];
661
0
    if (c == '=') {
662
0
      padding++;
663
0
    } else if (c > ' ') {
664
0
      break;
665
0
    }
666
0
  }
667
0
  return ((count - padding) * 3) / 4;
668
0
}
669
670
simdutf_warn_unused size_t
671
0
icelake_binary_length_from_base64(const char16_t *input, size_t length) {
672
0
  size_t count = 0;
673
0
  const char16_t *ptr = input;
674
0
  const char16_t *end = input + length;
675
676
0
  __m512i spaces = _mm512_set1_epi16(0x20);
677
0
  while (ptr + 32 <= end) {
678
0
    __m512i data = _mm512_loadu_si512(reinterpret_cast<const __m512i *>(ptr));
679
0
    __mmask32 mask = _mm512_cmpgt_epi16_mask(data, spaces);
680
0
    count += _mm_popcnt_u32(mask);
681
0
    ptr += 32;
682
0
  }
683
684
0
  if (ptr < end) {
685
0
    size_t len = end - ptr;
686
0
    __mmask32 input_mask = ((__mmask32)1 << len) - 1;
687
0
    __m512i data = _mm512_maskz_loadu_epi16(
688
0
        input_mask, reinterpret_cast<const __m512i *>(ptr));
689
0
    uint32_t mask = _mm512_cmpgt_epi16_mask(data, spaces);
690
0
    count += _mm_popcnt_u32(mask);
691
0
    ptr += len;
692
0
  }
693
694
0
  size_t padding = 0;
695
0
  size_t pos = length;
696
0
  while (pos > 0 && padding < 2) {
697
0
    char16_t c = input[--pos];
698
0
    if (c == '=') {
699
0
      padding++;
700
0
    } else if (c > ' ') {
701
0
      break;
702
0
    }
703
0
  }
704
0
  return ((count - padding) * 3) / 4;
705
0
}