// File: /workspace/github_cpp_makefile_star2_O3/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
movq %rsi, %r14
movq %rdi, %r15
cmpq $0x10, %rsi
jae 0x2b98
xorl %r11d, %r11d
movq %rdx, %r13
subq %r11, %r14
movq $0x0, (%rsp)
jne 0x2ddb
xorl %ebp, %ebp
jmp 0x2ed6
vpbroadcastb 0x5481(%rip), %xmm1 # 0x8022
vpmovsxbd 0xa44e(%rip), %xmm2 # 0xcff8
vpbroadcastq 0xa42d(%rip), %xmm3 # 0xcfe0
vpbroadcastq 0xa42c(%rip), %xmm4 # 0xcfe8
vpbroadcastq 0xa3db(%rip), %xmm5 # 0xcfa0
vpbroadcastq 0xa3f2(%rip), %xmm6 # 0xcfc0
vpbroadcastq 0xa401(%rip), %xmm7 # 0xcfd8
vpbroadcastq 0xa3c7(%rip), %xmm17 # 0xcfa8
vpbroadcastq 0xa3c6(%rip), %xmm9 # 0xcfb0
vpbroadcastq 0xa3c5(%rip), %xmm10 # 0xcfb8
vpbroadcastd 0xa3f4(%rip), %xmm11 # 0xcff0
vpbroadcastd 0xa3ef(%rip), %xmm12 # 0xcff4
vpbroadcastq 0xa3ba(%rip), %xmm13 # 0xcfc8
vpbroadcastq 0xa3b9(%rip), %xmm14 # 0xcfd0
movl $0x10, %edi
vpxor %xmm0, %xmm0, %xmm0
leaq 0x7619(%rip), %rax      # 0xa240
leaq 0x9612(%rip), %rcx      # 0xc240
xorl %r11d, %r11d
movq %rdx, %rsi
vmovdqu (%r15,%r11), %xmm15
vpmovmskb %xmm15, %r8d
testl %r8d, %r8d
je 0x2c97
vpsrlw $0x4, %xmm15, %xmm16
vpandq %xmm1, %xmm16, %xmm16
vpshufb %xmm16, %xmm2, %xmm8
vpmovmskb %xmm8, %edi
andl $0x1ffe, %edi           # imm = 0x1FFE
movzbl (%rdi,%rax), %r8d
movzbl 0x1(%rdi,%rax), %edi
cmpq $0x3f, %r8
ja 0x2cb2
shll $0x4, %r8d
vpshufb (%r8,%rcx), %xmm15, %xmm8
vpsrlw $0x2, %xmm8, %xmm15
vpand %xmm3, %xmm15, %xmm15
vpternlogq $0xf8, %xmm4, %xmm8, %xmm15
vmovdqu %xmm15, (%rsi)
addq $0xc, %rsi
jmp 0x2cee
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 %xmm0, %xmm15, %xmm15 # xmm15 = xmm15[8],xmm0[8],xmm15[9],xmm0[9],xmm15[10],xmm0[10],xmm15[11],xmm0[11],xmm15[12],xmm0[12],xmm15[13],xmm0[13],xmm15[14],xmm0[14],xmm15[15],xmm0[15]
vmovdqu64 %xmm16, (%rsi)
vmovdqu %xmm15, 0x10(%rsi)
addq $0x20, %rsi
jmp 0x2cf4
cmpb $-0x70, %r8b
ja 0x2d0f
shll $0x4, %r8d
vpshufb (%r8,%rcx), %xmm15, %xmm8
vpsrld $0x2, %xmm8, %xmm15
vpsrld $0x4, %xmm8, %xmm16
vpand %xmm5, %xmm15, %xmm15
vpternlogq $0xf8, %xmm6, %xmm8, %xmm15
vpternlogq $0xf8, %xmm7, %xmm16, %xmm15
vpackusdw %xmm15, %xmm15, %xmm8
vmovdqu %xmm8, (%rsi)
addq $0x8, %rsi
addq %rdi, %r11
movq %r11, %rdi
movq %rsi, %r13
movq %rdi, %r11
addq $0x10, %rdi
movq %r13, %rsi
cmpq %r14, %rdi
jbe 0x2c34
jmp 0x2b80
cmpb $-0x30, %r8b
ja 0x2cf1
shll $0x4, %r8d
vpshufb (%r8,%rcx), %xmm15, %xmm8
xorl %r8d, %r8d
vpsrld $0x1, %xmm8, %xmm16
vpsrld $0x2, %xmm8, %xmm15
vpsrld $0x6, %xmm8, %xmm18
vpandq %xmm17, %xmm16, %xmm16
vpand %xmm5, %xmm15, %xmm15
vpandq %xmm10, %xmm18, %xmm18
vpternlogq $0x78, %xmm9, %xmm8, %xmm16
vpternlogq $0xf8, %xmm6, %xmm8, %xmm15
vpsrld $0x4, %xmm16, %xmm16
vpternlogq $0xfe, %xmm18, %xmm16, %xmm15
vpaddd %xmm11, %xmm15, %xmm8
vpslld $0x10, %xmm15, %xmm16
vmovdqa %xmm15, (%rsp)
vpsrld $0xa, %xmm8, %xmm8
vpandq %xmm13, %xmm16, %xmm16
vpaddd %xmm12, %xmm8, %xmm8
vpternlogq $0xfe, %xmm14, %xmm8, %xmm16
vmovdqa64 %xmm16, 0x20(%rsp)
movl (%rsp,%r8,4), %r9d
movl $0x2, %r13d
cmpl $0x10000, %r9d          # imm = 0x10000
jb 0x2dbd
movl 0x20(%rsp,%r8,4), %r9d
movl $0x4, %r13d
movl %r9d, %r10d
shrl $0x10, %r10d
movw %r10w, 0x2(%rsi)
addq %rsi, %r13
incq %r8
movw %r9w, (%rsi)
movq %r13, %rsi
cmpq $0x3, %r8
jne 0x2d93
addq %rdi, %r11
movq %r11, %rdi
jmp 0x2cf7
addq %r11, %r15
movl $0x1, %ebx
movq %rdx, 0x18(%rsp)
xorl %ebp, %ebp
movq %rsp, %r12
xorl %edi, %edi
addq %r15, %rdi
movq %r12, %rsi
callq 0x2ef0
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
movq %rdi, (%rsp)
cmpq %r14, %rdi
jb 0x2def
movl $0x1, %r12d
movq %rsp, %rax
movq $0x0, (%rax)
xorl %ebx, %ebx
xorl %edi, %edi
addq %r15, %rdi
movq %rsp, %rsi
callq 0x2ef0
testq %r13, %r13
je 0x2ea6
xorl %edx, %edx
cmpl $0x110000, %eax         # imm = 0x110000
movl %ebx, %ecx
leaq (%r13,%rcx,2), %rcx
setb %dl
addl %edx, %edx
cmpl $0x10000, %eax          # imm = 0x10000
cmovbl %r12d, %edx
cmpl $0x2, %edx
je 0x2e79
cmpl $0x1, %edx
jne 0x2ea1
movw %ax, (%rcx)
testl %eax, %eax
je 0x2ea6
addq $0x2, %rcx
jmp 0x2ea1
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
movw $0x0, (%rcx)
movq (%rsp), %rdi
xorl %ecx, %ecx
cmpl $0x110000, %eax         # imm = 0x110000
setb %cl
addl %ecx, %ecx
cmpl $0x10000, %eax          # imm = 0x10000
cmovbl %r12d, %ecx
addl %ecx, %ebx
incq %rdi
movq %rdi, (%rsp)
cmpq %r14, %rdi
jb 0x2e36
movq 0x18(%rsp), %rdx
leaq (%r13,%rbp,2), %rax
subq %rdx, %rax
sarq %rax
addq $0x38, %rsp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
retq