// File: /workspace/github_cpp_makefile_star2_O2/WojciechMula[P]utf16utf8/src/sse.cpp
// Function: sse_convert_valid_utf8_to_utf16_lemire(unsigned char const*, unsigned long, unsigned short*)
size_t sse_convert_valid_utf8_to_utf16_lemire(const uint8_t* input, size_t size, uint16_t* output) {
  size_t pos = 0;
  uint16_t *start = output;
  while (pos + 16 <= size) {
    //////////////////////////////
    // Could go faster if we grab large
    // blocks of data, larger than
    // 16 bytes?
    //////////////////////////////
    const __m128i in = _mm_loadu_si128((__m128i *)(input + pos));
    const uint16_t non_ascii_chars = uint16_t(_mm_movemask_epi8(in));
    // ASCII is likely common in many cases, we want a fast path.
    if(non_ascii_chars == 0) {
        // could use _mm256_cvtepu8_epi16/_mm_cvtepu8_epi16 (SSE4.1)
        const __m128i out1 = _mm_unpacklo_epi8(in, _mm_setzero_si128());// order of parameter determines endianness
        _mm_storeu_si128((__m128i*)output, out1);
        output += 8;
        const __m128i out2 = _mm_unpackhi_epi8(in, _mm_setzero_si128());
        _mm_storeu_si128((__m128i*)output, out2);
        output += 8;
        pos += 16;
        continue;
    }
    // 
    // Otherwise, we use an approach where we try to process up to 12 input bytes.
    // Why 12 input bytes and not 16? Because we are concerned with the size of the lookup tables.
    // Also 12 is nicely divisible by two and three.
    //
    //const __m128i maxtwobyte = _mm_set1_epi8(uint8_t(0xdf));
    //uint16_t istwobytes = uint16_t(_mm_movemask_epi8(_mm_cmpeq_epi8(_mm_max_epu8(maxtwobyte, in), maxtwobyte)));
    // The most significant 4 bits (high nibble) can be used to classify the bytes, as
    // to whether they start a new code point.
    const __m128i hn = _mm_set1_epi8(uint8_t(0xF));
    const __m128i in_high_nibbles = _mm_and_si128(hn, _mm_srli_epi16(in, 4));
    // We could remove the UTF8 headers with one instruction:
    //const __m128i masks = _mm_shuffle_epi8(_mm_set_epi8(0xf0, 0xe0, 0xc0, 0xc0, 0x80, 0x80, 0x80, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0), in_high_nibbles);
    const uint16_t start_of_code_point = uint16_t(_mm_movemask_epi8(_mm_shuffle_epi8(_mm_set_epi8(0xff, 0xff, 0xff, 0xff, 0x0, 0x0, 0x0, 0x0, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff), in_high_nibbles)));
    /////////////
    // ASCII characters are the start and the end of a code point 
    // and anything immediately before the start of a code point 
    // must be the end of a start of code point.
    ///////////
    const uint16_t end_of_code_point = uint16_t((start_of_code_point >> 1) | (! non_ascii_chars));
    const uint16_t twelvebits_mask = 0xFFF;
    const uint16_t twelvebits_end_of_code_point = end_of_code_point&twelvebits_mask;
    const uint8_t idx = utf8bigindex[twelvebits_end_of_code_point][0];
    const uint8_t consumed = utf8bigindex[twelvebits_end_of_code_point][1];
    //
    // Let us first try to see if we are in the easy two-byte scenario
    //
    if(idx < 64) {
        // this is a relatively easy scenario
        // we process SIX (6) input code-words. The max length in bytes of six code words
        // spanning between 1 and 2 bytes each is 12 bytes.
        // On processors where pdep/pext is fast, we might be able to use a small lookup table.
        const __m128i sh = _mm_loadu_si128((const __m128i *)shufutf8[idx]);
        const __m128i perm = _mm_shuffle_epi8(in, sh);
        const __m128i ascii = _mm_and_si128(perm,_mm_set1_epi16(0x7f));
        const __m128i highbyte = _mm_and_si128(perm,_mm_set1_epi16(0x1f00));
        const __m128i composed = _mm_or_si128(ascii,_mm_srli_epi16(highbyte,2));
        _mm_storeu_si128((__m128i*)output, composed);
        output += 6;
        pos += consumed;
    } else if (idx < 145) {
        const __m128i sh = _mm_loadu_si128((const __m128i *)shufutf8[idx]);
        const __m128i perm = _mm_shuffle_epi8(in, sh);
        const __m128i ascii = _mm_and_si128(perm,_mm_set1_epi32(0x7f)); // 7 or 6 bits
        const __m128i middlebyte = _mm_and_si128(perm,_mm_set1_epi32(0x3f00)); // 5 or 6 bits
        const __m128i middlebyte_shifted = _mm_srli_epi32(middlebyte,2);
        const __m128i highbyte = _mm_and_si128(perm,_mm_set1_epi32(0x0f0000)); // 4 bits
        const __m128i highbyte_shifted = _mm_srli_epi32(highbyte,4);
        const __m128i composed = _mm_or_si128(_mm_or_si128(ascii,middlebyte_shifted),highbyte_shifted);
        const __m128i composed_repacked = _mm_packus_epi32(composed,composed);
        _mm_storeu_si128((__m128i*)output,  composed_repacked);
        output += 4;
        pos += consumed;
    } else if(idx < 209) {
        const __m128i sh = _mm_loadu_si128((const __m128i *)shufutf8[idx]);
        const __m128i perm = _mm_shuffle_epi8(in, sh);
        const __m128i ascii = _mm_and_si128(perm,_mm_set1_epi32(0x7f)); 
        const __m128i middlebyte = _mm_and_si128(perm,_mm_set1_epi32(0x3f00));
        const __m128i middlebyte_shifted = _mm_srli_epi32(middlebyte,2);
        __m128i middlehighbyte = _mm_and_si128(perm,_mm_set1_epi32(0x3f0000));//_mm_xor_si128(_mm_and_si128(perm,_mm_set1_epi32(0x3f0000)), );
        // correct for spurious high bit
        const __m128i correct = _mm_srli_epi32(_mm_and_si128(perm,_mm_set1_epi32(0x400000)),1);
        middlehighbyte = _mm_xor_si128(correct, middlehighbyte);
        const __m128i middlehighbyte_shifted = _mm_srli_epi32(middlehighbyte,4);
        const __m128i highbyte = _mm_and_si128(perm,_mm_set1_epi32(0x07000000));
        const __m128i highbyte_shifted = _mm_srli_epi32(highbyte,6);
        const __m128i composed = _mm_or_si128(_mm_or_si128(ascii,middlebyte_shifted),_mm_or_si128(highbyte_shifted,middlehighbyte_shifted));
        const __m128i composedminus = _mm_sub_epi32(composed,_mm_set1_epi32(0x10000));
        const __m128i lowtenbits = _mm_and_si128(composedminus,_mm_set1_epi32(0x3ff));
        const __m128i hightenbits = _mm_srli_epi32(composedminus,10);
        const __m128i lowtenbitsadd = _mm_add_epi32(lowtenbits,_mm_set1_epi32(0xDC00));
        const __m128i hightenbitsadd = _mm_add_epi32(hightenbits,_mm_set1_epi32(0xD800));
        const __m128i lowtenbitsaddshifted = _mm_slli_epi32(lowtenbitsadd,16);
        const __m128i surrogates = _mm_or_si128(hightenbitsadd,lowtenbitsaddshifted);
        uint32_t basic_buffer[4];
        _mm_storeu_si128((__m128i*)basic_buffer,  composed);
        uint32_t surrogate_buffer[4];
        _mm_storeu_si128((__m128i*)surrogate_buffer,  surrogates);
        for(size_t i = 0; i < 3; i++) {
            if(basic_buffer[i]<65536) {
                *output = uint16_t(basic_buffer[i]);
                output++;
            } else {
                output[0] = uint16_t(surrogate_buffer[i] &0xFFFF);
                output[1] = uint16_t(surrogate_buffer[i] >> 16);
                output += 2;
            }
        }
        pos += consumed;        
    } else {
        // here we know that there is an error???
    }
  }
  size_t len = strlen_utf8_to_utf16_with_length(input + pos, size - pos);
  utf8_to_utf16_with_length(input + pos, size - pos, output);
  output += len;
  return output - start;
}

// Assembly:
pushq %rbp
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
subq $0x38, %rsp
vpbroadcastb 0x469f(%rip), %xmm1 # 0x7022
vpmovsxbd 0x965c(%rip), %xmm2 # 0xbfe8
vpbroadcastq 0x962b(%rip), %xmm3 # 0xbfc0
vpbroadcastq 0x962a(%rip), %xmm4 # 0xbfc8
vpbroadcastq 0x95d9(%rip), %xmm5 # 0xbf80
vpbroadcastq 0x95f0(%rip), %xmm6 # 0xbfa0
vpbroadcastq 0x95ff(%rip), %xmm7 # 0xbfb8
vpbroadcastq 0x95c6(%rip), %xmm8 # 0xbf88
vpbroadcastq 0x95c5(%rip), %xmm9 # 0xbf90
vpbroadcastq 0x95c4(%rip), %xmm10 # 0xbf98
vpbroadcastd 0x9603(%rip), %xmm11 # 0xbfe0
vpbroadcastd 0x95fe(%rip), %xmm12 # 0xbfe4
vpbroadcastq 0x95b9(%rip), %xmm13 # 0xbfa8
vpbroadcastq 0x95b8(%rip), %xmm14 # 0xbfb0
movq %rdx, %r12
movq %rsi, %r14
vpxord %xmm17, %xmm17, %xmm17
movl $0xc01, %eax            # imm = 0xC01
leaq 0x6830(%rip), %rdx      # 0x9240
leaq 0x8829(%rip), %rsi      # 0xb240
xorl %ecx, %ecx
pushq $0x2
popq %r8
pushq $0x4
popq %r9
movq %r12, 0x18(%rsp)
leaq 0x10(%rcx), %r10
leaq (%rdi,%rcx), %r13
cmpq %r14, %r10
ja 0x2bda
vmovdqu (%r13), %xmm15
vpmovmskb %xmm15, %r11d
testl %r11d, %r11d
je 0x2b43
vpsrlw $0x4, %xmm15, %xmm16
vpandq %xmm1, %xmm16, %xmm16
vpshufb %xmm16, %xmm2, %xmm0
vpmovmskb %xmm0, %r11d
bextrl %eax, %r11d, %ebx
movzbl (%rdx,%rbx,2), %r11d
cmpq $0x3f, %r11
jbe 0x2b69
cmpb $-0x70, %r11b
jbe 0x2b95
cmpb $-0x30, %r11b
ja 0x2a2e
shll $0x4, %r11d
movzbl 0x1(%rdx,%rbx,2), %r10d
vpshufb (%r11,%rsi), %xmm15, %xmm0
xorl %r11d, %r11d
vpsrld $0x1, %xmm0, %xmm16
vpsrld $0x2, %xmm0, %xmm15
vpsrld $0x6, %xmm0, %xmm18
vpandq %xmm8, %xmm16, %xmm16
vpand %xmm5, %xmm15, %xmm15
vpandq %xmm10, %xmm18, %xmm18
vpternlogq $0x78, %xmm9, %xmm0, %xmm16
vpternlogq $0xf8, %xmm6, %xmm0, %xmm15
vpsrld $0x4, %xmm16, %xmm16
vpternlogq $0xfe, %xmm18, %xmm16, %xmm15
vpaddd %xmm11, %xmm15, %xmm0
vpslld $0x10, %xmm15, %xmm16
vmovdqa %xmm15, (%rsp)
vpsrld $0xa, %xmm0, %xmm0
vpandq %xmm13, %xmm16, %xmm16
vpaddd %xmm0, %xmm12, %xmm0
vpternlogq $0xfe, %xmm14, %xmm0, %xmm16
vmovdqa64 %xmm16, 0x20(%rsp)
cmpq $0x3, %r11
je 0x2bd2
movl (%rsp,%r11,4), %ebx
movq %r8, %r15
cmpl $0x10000, %ebx          # imm = 0x10000
jb 0x2b33
movl 0x20(%rsp,%r11,4), %ebx
movq %r9, %r15
movl %ebx, %ebp
shrl $0x10, %ebp
movw %bp, 0x2(%r12)
addq %r12, %r15
movw %bx, (%r12)
incq %r11
movq %r15, %r12
jmp 0x2b07
vpmovzxbw %xmm15, %xmm16  # xmm16 = xmm15[0],zero,xmm15[1],zero,xmm15[2],zero,xmm15[3],zero,xmm15[4],zero,xmm15[5],zero,xmm15[6],zero,xmm15[7],zero
vpunpckhbw %xmm17, %xmm15, %xmm15 # xmm15 = xmm15[8],xmm17[8],xmm15[9],xmm17[9],xmm15[10],xmm17[10],xmm15[11],xmm17[11],xmm15[12],xmm17[12],xmm15[13],xmm17[13],xmm15[14],xmm17[14],xmm15[15],xmm17[15]
movq %r10, %rcx
vmovdqu64 %xmm16, (%r12)
vmovdqu %xmm15, 0x10(%r12)
addq $0x20, %r12
jmp 0x2a26
shll $0x4, %r11d
movzbl 0x1(%rdx,%rbx,2), %r10d
vpshufb (%r11,%rsi), %xmm15, %xmm0
vpsrlw $0x2, %xmm0, %xmm15
vpand %xmm3, %xmm15, %xmm15
vpternlogq $0xf8, %xmm4, %xmm0, %xmm15
vmovdqu %xmm15, (%r12)
addq $0xc, %r12
jmp 0x2bd2
shll $0x4, %r11d
movzbl 0x1(%rdx,%rbx,2), %r10d
vpshufb (%r11,%rsi), %xmm15, %xmm0
vpsrld $0x2, %xmm0, %xmm15
vpsrld $0x4, %xmm0, %xmm16
vpand %xmm5, %xmm15, %xmm15
vpternlogq $0xf8, %xmm6, %xmm0, %xmm15
vpternlogq $0xf8, %xmm7, %xmm16, %xmm15
vpackusdw %xmm15, %xmm15, %xmm0
vmovdqu %xmm0, (%r12)
addq $0x8, %r12
addq %r10, %rcx
jmp 0x2a26
subq %rcx, %r14
xorl %edi, %edi
movq %rsp, %r15
pushq $0x1
popq %rbx
xorl %ebp, %ebp
movq %rdi, (%rsp)
cmpq %r14, %rdi
jae 0x2c1c
addq %r13, %rdi
movq %r15, %rsi
callq 0x2cdd
movq (%rsp), %rdi
xorl %ecx, %ecx
cmpl $0x110000, %eax         # imm = 0x110000
setb %cl
addl %ecx, %ecx
cmpl $0x10000, %eax          # imm = 0x10000
cmovbq %rbx, %rcx
addq %rcx, %rbp
incq %rdi
jmp 0x2be7
xorl %edi, %edi
pushq $0x1
popq %rbx
xorl %r15d, %r15d
movq %rdi, (%rsp)
cmpq %r14, %rdi
jae 0x2cc2
addq %r13, %rdi
movq %rsp, %rsi
callq 0x2cdd
testq %r12, %r12
je 0x2c9f
xorl %edx, %edx
cmpl $0x110000, %eax         # imm = 0x110000
movl %r15d, %ecx
leaq (%r12,%rcx,2), %rcx
setb %dl
addl %edx, %edx
cmpl $0x10000, %eax          # imm = 0x10000
cmovbl %ebx, %edx
cmpl $0x2, %edx
je 0x2c73
cmpl $0x1, %edx
jne 0x2c9b
movw %ax, (%rcx)
testl %eax, %eax
je 0x2c9f
addq $0x2, %rcx
jmp 0x2c9b
leal 0x3ff0000(%rax), %edx
shrl $0xa, %edx
addl $0xffffd800, %edx       # imm = 0xFFFFD800
movw %dx, (%rcx)
movl %eax, %edx
andl $0x3ff, %edx            # imm = 0x3FF
orl $0xdc00, %edx           # imm = 0xDC00
movw %dx, 0x2(%rcx)
addq $0x4, %rcx
andw $0x0, (%rcx)
movq (%rsp), %rdi
xorl %ecx, %ecx
cmpl $0x110000, %eax         # imm = 0x110000
setb %cl
addl %ecx, %ecx
cmpl $0x10000, %eax          # imm = 0x10000
cmovbl %ebx, %ecx
addl %ecx, %r15d
incq %rdi
jmp 0x2c24
leaq (%r12,%rbp,2), %rax
subq 0x18(%rsp), %rax
sarq %rax
addq $0x38, %rsp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
retq