Guest User

Untitled

a guest
Aug 18th, 2023
838
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 9.51 KB | None | 0 0
  1.  
  2. #include "latin1_to_utf8.h"
  3. #include <stdint.h>
  4. #include <stdio.h>
  5. #include <string.h>
  6. #include <x86intrin.h> // update if we need to support Windows.
  7.  
  8. size_t latin1_to_utf8(const char *buf, size_t len, char *utf8_output) {
  9.   const unsigned char *data = (const unsigned char *)(buf);
  10.   size_t pos = 0;
  11.   char *start = utf8_output;
  12.   while (pos < len) {
  13.     // try to convert the next block of 16 ASCII bytes
  14.     if (pos + 16 <=
  15.         len) { // if it is safe to read 16 more bytes, check that they are ascii
  16.       uint64_t v1;
  17.       memcpy(&v1, data + pos, sizeof(uint64_t));
  18.       uint64_t v2;
  19.       memcpy(&v2, data + pos + sizeof(uint64_t), sizeof(uint64_t));
  20.       uint64_t v =
  21.           v1 | v2; // We are only interested in these bits: 1000 1000 1000 1000,
  22.                    // so it makes sense to concatenate everything
  23.       if ((v & 0x8080808080808080) ==
  24.           0) { // if NONE of these are set, e.g. all of them are zero, then
  25.                // everything is ASCII
  26.         size_t final_pos = pos + 16;
  27.         while (pos < final_pos) {
  28.           *utf8_output++ = (char)(buf[pos]);
  29.           pos++;
  30.         }
  31.         continue;
  32.       }
  33.     }
  34.  
  35.     unsigned char byte = data[pos];
  36.     if ((byte & 0x80) == 0) { // if ASCII
  37.       // will generate one UTF-8 byte
  38.       *utf8_output++ = (char)(byte);
  39.       pos++;
  40.     } else {
  41.       // will generate two UTF-8 bytes
  42.       *utf8_output++ = (char)((byte >> 6) | 0b11000000);
  43.       *utf8_output++ = (char)((byte & 0b111111) | 0b10000000);
  44.       pos++;
  45.     }
  46.   }
  47.   return (size_t)(utf8_output - start);
  48. }
  49.  
  50. void printbinary(uint64_t n) {
  51.   for (size_t i = 0; i < 64; i++) {
  52.     if (n & 1)
  53.       printf("1");
  54.     else
  55.       printf("0");
  56.  
  57.     n >>= 1;
  58.   }
  59.   printf("\n");
  60. }
  61. void print8(const char *name, __m512i x) {
  62.   printf("%.32s : ", name);
  63.   uint8_t buffer[64];
  64.   _mm512_storeu_si512((__m512i *)buffer, x);
  65.   for (size_t i = 0; i < 64; i++) {
  66.     printf("%02x ", buffer[i]);
  67.   }
  68.   printf("\n");
  69. }
  70.  
  71. /*
  72. static inline size_t latin1_to_utf8_avx512_old_vec(__m256i input, size_t input_len, char *utf8_output) {
  73.   __m512i byteflip = _mm512_setr_epi64(0x0607040502030001, 0x0e0f0c0d0a0b0809,
  74.                                        0x0607040502030001, 0x0e0f0c0d0a0b0809,
  75.                                        0x0607040502030001, 0x0e0f0c0d0a0b0809,
  76.                                        0x0607040502030001, 0x0e0f0c0d0a0b0809);
  77.   __mmask32 nonascii = _mm256_movepi8_mask(input);
  78.   size_t output_size = input_len + (size_t)_popcnt32(nonascii);
  79.   uint64_t compmask = ~_pdep_u64(~nonascii, UINT64_C(0x5555555555555555));
  80.   __mmask32 sixth =
  81.       _mm256_cmpge_epu8_mask(input, _mm256_set1_epi8((char)192));
  82.   __m512i input16 = _mm512_cvtepu8_epi16(input);
  83.       input16 =_mm512_add_epi16(input16, _mm512_set1_epi16((short int)0xc200));
  84.   __m512i output16 =
  85.       _mm512_mask_add_epi16(input16, sixth, input16,
  86.                             _mm512_set1_epi16(0x00c0));
  87.   output16 = _mm512_shuffle_epi8(output16, byteflip);
  88.   __m512i output = _mm512_maskz_compress_epi8(compmask, output16);
  89.   __mmask64 write_mask = _bzhi_u64(~0ULL, (long long unsigned int)output_size);
  90.   _mm512_mask_storeu_epi8(utf8_output, write_mask, output);
  91.   return output_size;
  92. }
  93.  
  94. size_t latin1_to_utf8_avx512_old(const char *buf, size_t len, char *utf8_output) {
  95.   char *start = utf8_output;
  96.   size_t pos = 0;
  97.   // Note that
  98.   for (; pos + 32 <= len; pos += 32) {
  99.     __m256i input = _mm256_loadu_si256((__m256i *)(buf + pos));
  100.     utf8_output += latin1_to_utf8_avx512_old_vec(input, 32, utf8_output);
  101.   }
  102.   if (pos < len) {
  103.     __mmask32 load_mask = _bzhi_u32(~0U, (unsigned int)(len - pos));
  104.     __m256i input = _mm256_maskz_loadu_epi8(load_mask, (__m256i *)(buf + pos));
  105.     utf8_output += latin1_to_utf8_avx512_old_vec(input, len - pos, utf8_output);
  106.   }
  107.   return (size_t)(utf8_output - start);
  108. }
  109. */
  110.  
  111. static inline size_t latin1_to_utf8_avx512_vec(__m512i input, size_t input_len, char *utf8_output, int mask_output) {
  112.   __mmask64 nonascii = _mm512_movepi8_mask(input);
  113.   size_t output_size = input_len + (size_t)_popcnt64(nonascii);
  114.  
  115.   __mmask64 sixth =
  116.       _mm512_cmpge_epu8_mask(input, _mm512_set1_epi8(-64));
  117.  
  118.   const uint64_t alternate_bits = UINT64_C(0x5555555555555555);
  119.   uint64_t ascii = ~nonascii;
  120.   uint64_t maskA = ~_pdep_u64(ascii, alternate_bits);
  121.   uint64_t maskB = ~_pdep_u64(ascii>>32, alternate_bits);
  122.  
  123.  
  124.   __m512i outputA, outputB;
  125.   if (0) {
  126.     // expand strategy - slower than compress strategy
  127.     maskA = _pext_u64(maskA^alternate_bits, maskA);
  128.     maskB = _pext_u64(maskB^alternate_bits, maskB);
  129.    
  130.     uint64_t leading_maskA = _pdep_u64(sixth, maskA) >> 1;
  131.     uint64_t leading_maskB = _pdep_u64(sixth>>32, maskB) >> 1;
  132.     __m512i leadingA = _mm512_mask_blend_epi8(leading_maskA, _mm512_set1_epi8(-62), _mm512_set1_epi8(-61));
  133.     __m512i leadingB = _mm512_mask_blend_epi8(leading_maskB, _mm512_set1_epi8(-62), _mm512_set1_epi8(-61));
  134.    
  135.     input = _mm512_mask_add_epi8(input, sixth, input, _mm512_set1_epi8(-64));
  136.     outputA = _mm512_mask_expand_epi8(leadingA, maskA, input);
  137.     outputB = _mm512_mask_expand_epi8(leadingB, maskB, _mm512_castsi256_si512(_mm512_extracti64x4_epi64(input, 1)));
  138.    
  139.   } else {
  140.     // compress strategy
  141.     // interleave bytes from top and bottom halves (abcd...ABCD -> aAbBcCdD)
  142.     __m512i input_interleaved = _mm512_permutexvar_epi8(_mm512_set_epi32(
  143.       0x3f1f3e1e, 0x3d1d3c1c, 0x3b1b3a1a, 0x39193818,
  144.       0x37173616, 0x35153414, 0x33133212, 0x31113010,
  145.       0x2f0f2e0e, 0x2d0d2c0c, 0x2b0b2a0a, 0x29092808,
  146.       0x27072606, 0x25052404, 0x23032202, 0x21012000
  147.     ), input);
  148.    
  149.     // double size of each byte, and insert the leading byte
  150.     outputA = _mm512_shldi_epi16(input_interleaved, _mm512_set1_epi8(-62), 8);
  151.     outputA = _mm512_mask_add_epi16(outputA, (__mmask32)sixth, outputA, _mm512_set1_epi16(1 - 0x4000));
  152.    
  153.     __m512i leadingB = _mm512_mask_blend_epi16((__mmask32)(sixth>>32), _mm512_set1_epi16(0x00c2), _mm512_set1_epi16(0x40c3));
  154.     outputB = _mm512_ternarylogic_epi32(input_interleaved, leadingB, _mm512_set1_epi16((short)0xff00), (240 & 170) ^ 204); // (input_interleaved & 0xff00) ^ leadingB
  155.    
  156.     // prune redundant bytes
  157.     outputA = _mm512_maskz_compress_epi8(maskA, outputA);
  158.     outputB = _mm512_maskz_compress_epi8(maskB, outputB);
  159.   }
  160.  
  161.  
  162.   size_t output_sizeA = (size_t)_popcnt32((uint32_t)nonascii) + 32;
  163.   if(mask_output) {
  164.     if(input_len > 32) { // is the second half of the input vector used?
  165.       __mmask64 write_mask = _bzhi_u64(~0ULL, output_sizeA);
  166.       _mm512_mask_storeu_epi8(utf8_output, write_mask, outputA);
  167.       utf8_output += output_sizeA;
  168.       write_mask = _bzhi_u64(~0ULL, output_size - output_sizeA);
  169.       _mm512_mask_storeu_epi8(utf8_output, write_mask, outputB);
  170.     } else {
  171.       __mmask64 write_mask = _bzhi_u64(~0ULL, output_size);
  172.       _mm512_mask_storeu_epi8(utf8_output, write_mask, outputA);
  173.     }
  174.   } else {
  175.     _mm512_storeu_si512(utf8_output, outputA);
  176.     utf8_output += output_sizeA;
  177.     _mm512_storeu_si512(utf8_output, outputB);
  178.   }
  179.   return output_size;
  180. }
  181.  
  182. // if the likelihood of non-ASCII characters is low, it may make sense to add a branch for a faster routine
  183. static inline size_t latin1_to_utf8_avx512_branch(__m512i input, char *utf8_output) {
  184.   __mmask64 nonascii = _mm512_movepi8_mask(input);
  185.   size_t nonascii_count = (size_t)_popcnt64(nonascii);
  186.  
  187.   if(1) { // shortcut for no non-ASCII characters
  188.     if(nonascii_count > 0)
  189.       return latin1_to_utf8_avx512_vec(input, 64, utf8_output, 0);
  190.     _mm512_storeu_si512(utf8_output, input);
  191.     return 64;
  192.    
  193.   } else { // shortcut for up to 1 non-ASCII characters
  194.     if(nonascii_count > 1)
  195.       return latin1_to_utf8_avx512_vec(input, 64, utf8_output, 0);
  196.    
  197.     __mmask64 sixth =
  198.         _mm512_cmpge_epu8_mask(input, _mm512_set1_epi8(-64));
  199.     input = _mm512_mask_add_epi8(input, sixth, input, _mm512_set1_epi8(-64));
  200.    
  201.     // we're writing either 64 or 65 bytes; write the last byte to cater for the latter case (the earlier bytes will get overwritten)
  202.     //_mm512_storeu_si512(utf8_output + 1, input);
  203.     _mm_store_ss((float*)(utf8_output + 61), _mm_castsi128_ps(_mm512_extracti32x4_epi32(input, 3)));
  204.    
  205.     __m512i leading = _mm512_mask_blend_epi8(sixth, _mm512_set1_epi8(-62), _mm512_set1_epi8(-61));
  206.     input = _mm512_mask_expand_epi8(leading, ~nonascii, input);
  207.     _mm512_storeu_si512(utf8_output, input);
  208.    
  209.     return 64 + nonascii_count;
  210.   }
  211. }
  212.  
  213. size_t latin1_to_utf8_avx512(const char *buf, size_t len, char *utf8_output) {
  214.   char *start = utf8_output;
  215.   size_t pos = 0;
  216.   // if there's at least 128 bytes remaining, we don't need to mask the output
  217.   for (; pos + 128 <= len; pos += 64) {
  218.     __m512i input = _mm512_loadu_si512((__m512i *)(buf + pos));
  219.     //utf8_output += latin1_to_utf8_avx512_vec(input, 64, utf8_output, 0);
  220.     utf8_output += latin1_to_utf8_avx512_branch(input, utf8_output);
  221.   }
  222.   // in the last 128 bytes, the first 64 may require masking the output
  223.   if (pos + 64 <= len) {
  224.     __m512i input = _mm512_loadu_si512((__m512i *)(buf + pos));
  225.     utf8_output += latin1_to_utf8_avx512_vec(input, 64, utf8_output, 1);
  226.     pos += 64;
  227.   }
  228.   // with the last 64 bytes, the input also needs to be masked
  229.   if (pos < len) {
  230.     __mmask64 load_mask = _bzhi_u64(~0ULL, (unsigned int)(len - pos));
  231.     __m512i input = _mm512_maskz_loadu_epi8(load_mask, (__m512i *)(buf + pos));
  232.     utf8_output += latin1_to_utf8_avx512_vec(input, len - pos, utf8_output, 1);
  233.   }
  234.   return (size_t)(utf8_output - start);
  235. }
  236.  
  237.  
Advertisement
Add Comment
Please, Sign In to add comment