// File: /workspace/github_cpp_makefile_star2_O1/WojciechMula[P]utf16utf8/src/sse.cpp
// Function: sse_convert_utf16_to_utf8(unsigned short const*, unsigned long, unsigned char*)
size_t sse_convert_utf16_to_utf8(const uint16_t* input, size_t size, uint8_t* output) {
    const uint16_t* end = input + (size & ~0x7); // round down size to 8
    uint8_t* start = output;
    while (input != end) {
        const __m128i in = _mm_loadu_si128((__m128i*)input);

        // 0. test if there are any surrogates
        const __m128i msn = _mm_and_si128(in, _mm_set1_epi16(int16_t(0xf800)));
        const __m128i hi_surrogates = _mm_cmpeq_epi16(msn, _mm_set1_epi16(int16_t(0xd800))); // higher byte: 0b1101_10xx
        const __m128i lo_surrogates = _mm_cmpeq_epi16(msn, _mm_set1_epi16(int16_t(0xdc00))); // higher byte: 0b1101_11xx
        const __m128i any_surrogate = _mm_or_si128(lo_surrogates, hi_surrogates);
        const uint16_t surrogates_mask = uint16_t(_mm_movemask_epi8(any_surrogate));
        if (surrogates_mask) {
            // for now only scalar fallback   
            auto save_utf8 = [&output](uint32_t value) {
                auto save_bytes = [&output](int byte) { *output++ = uint8_t(byte); };
                encode_utf8(value, save_bytes);
            };

            bool malformed = false;
            int consumed = 8;
            auto on_error = [&consumed, &malformed](const uint16_t* data,
                                                    const uint16_t* current,
                                                    utf16::Error error)
            {
                const auto error_pos = (current - data);
                if (error == utf16::Error::missing_low_surrogate and error_pos == 7)
                    consumed = 7; // hi surrogate at the and of 8-byte block, would reprocess it again
                else
                    malformed = true;
            };

            utf16::decode(input, 8, save_utf8, on_error);
            if (malformed)
                return output - start;
            else
                input += consumed;

            continue;
        }

        // if no surrogates: we for sure process whole register
        input += 8;

        // 0. determine how many bytes each 16-bit value produces
        //      1 byte  =     (in < 0x0080)
        //      2 bytes = not (in < 0x0080) and (in < 0x0800)
        //      3 bytes = not (in < 0x0800)
        const __m128i lt0080 = _mm_cmpeq_epi16(_mm_setzero_si128(),
                                               _mm_and_si128(in, _mm_set1_epi16((int16_t)0xff80)));
        const __m128i lt0800 = _mm_cmpeq_epi16(_mm_setzero_si128(),
                                               _mm_and_si128(in, _mm_set1_epi16((int16_t)0xf800)));

        // a. store lt0080 and lt0800 as bitmask, interleaving bits from both vectors
        const __m128i t0 = _mm_blendv_epi8(lt0080, lt0800, _mm_set1_epi16((int16_t)0xff00));
        const uint16_t patterns = uint16_t(_mm_movemask_epi8(t0));

        if (patterns == 0xffff) { // condition: (in < 0x0080)
            // Fast path: only ASCII values

            // [0000|0000|0ccc|dddd] => [0ccc|dddd]
            const __m128i lt0 = _mm_packus_epi16(in, in);
            uint64_t tmp = _mm_cvtsi128_si64(lt0);
            memcpy(output, &tmp, 8);
            output += 8;
        }
        else if ((patterns & 0xaaaa) == 0xaaaa) { // condition: (in < 0x0800)
            // Fast path: values are in range 0x0000 ... 0x07ff
            // UTF16 codeword is valid and is expanded to either 1 or 2 bytes of UTF8

            // a. for values 00 .. 7f we have transformation (two UTF16 bytes -> one UTF8 byte):
            //    [0000|0000|0ccc|dddd] => [0ccc|dddd]
            // b. for value  0080 .. 07ff we have (two UTF16 bytes -> two UTF8 bytes)
            //    [0000|0bbb|cccc|dddd] => [110b|bbcc|10cc|dddd]

            // tmp      = [0g0h|0i0j|0k0l|0m0n]
            // pattern  =           [gkhl|imjn]
            const uint16_t tmp     = patterns & 0x5555;
            const uint8_t  pattern = uint8_t(tmp | (tmp >> 7));

            // [0000|0000|0ccc|dddd]
            const __m128i utf8_1byte = in;

            __m128i byte0;
            __m128i byte1;
            __m128i word0;

            byte0 = _mm_and_si128(in, _mm_set1_epi16(0x003f));                  // [0000|0000|00cc|dddd]
            byte1 = _mm_slli_epi16(in, 2);                                      // [000b|bbcc|xxxx|xxxx]
            byte1 = _mm_and_si128(byte1, _mm_set1_epi16((int16_t)0x1f00));      // [000b|bbcc|0000|0000]

            word0 = _mm_or_si128(byte0, byte1);                                 // [000b|bbcc|00cc|dddd]

            // update UTF8 markers:
            __m128i utf8_2bytes;
            utf8_2bytes = _mm_or_si128(word0, _mm_set1_epi16((int16_t)0xc080)); // [110b|bbcc|10cc|dddd]

            // keep in 16-bits proper UTF8 variants
            const __m128i utf8_t0 = _mm_blendv_epi8(utf8_2bytes, utf8_1byte, lt0080);

            // compress zeros from 1-byte words
            const __m128i lookup = _mm_loadu_si128((const __m128i*)compress_16bit_lookup[pattern]);
            const __m128i utf8   = _mm_shuffle_epi8(utf8_t0, lookup);

            _mm_storeu_si128((__m128i*)output, utf8);
            output += compress_16bit_length[pattern];
        } else {
            // input in range 0x0000 .. 0xffff

            // 1. prepare UTF8 bytes

            // output 1 UTF8 byte  : [0000|0000|0ccc|dddd] => [0ccc|dddd]
            // output 2 UTF8 bytes : [0000|0bbb|cccc|dddd] => [110b|bbcc], [10cc|dddd]
            // output 3 UTF8 bytes : [aaaa|bbbb|cccc|dddd] => [1110|aaaa], [10bb|bbcc], [10cc|dddd]

            // a. 1 byte code equals to input
            __m128i word0_1 = in;

            // a. build 2 byte codes
            __m128i tmp;
            __m128i byte0_2;
            __m128i byte1_2;
            __m128i word0_2;


                                                                        // [0000|0bbb|cccc|dddd]
            byte0_2 = _mm_and_si128(in, _mm_set1_epi16(0x003f));        // [0000|0000|00cc|dddd]

            tmp     = _mm_slli_epi32(in, 2);                            // [000b|bbcc|ccdd|dd00] // reused later
            byte1_2 = _mm_and_si128(tmp, _mm_set1_epi16(0x1f00));       // [000b|bbcc|0000|0000]

            const __m128i loct0 = _mm_set1_epi16((int16_t)0xc080);
            word0_2 = _mm_or_si128(byte0_2, byte1_2);                   // [000b|bbcc|00cc|dddd]
            word0_2 = _mm_or_si128(word0_2, loct0);                     // [110b|bbcc|10cc|dddd]

            // b. build 3 byte codes
            __m128i byte0_3;
            __m128i byte1_3;
            __m128i byte2_3;
            __m128i word0_3;
            __m128i word1_3;

            byte0_3 = byte0_2; // reuse from 2-byte case                // [0000|0000|00cc|dddd]

            byte1_3 = _mm_and_si128(tmp, _mm_set1_epi16(0x3f00));       // [00bb|bbcc|0000|0000]

            const __m128i t1 = _mm_set1_epi16((int16_t)0x8080);
            word0_3 = _mm_or_si128(byte0_3, byte1_3);                   // [00bb|bbcc|00cc|dddd]
            word0_3 = _mm_or_si128(word0_3, t1);                        // [10bb|bbcc|10cc|dddd]

            byte2_3 = _mm_srli_epi16(in, 12);
            byte2_3 = _mm_or_si128(byte2_3, _mm_set1_epi16(0x00e0));    // [0000|0000|1110|aaaa]
            word1_3 = byte2_3;

            word0_1 = _mm_and_si128(lt0080, word0_1);

            const __m128i m2 = _mm_andnot_si128(lt0080, lt0800);
            word0_2 = _mm_and_si128(m2, word0_2);

            word0_3 = _mm_andnot_si128(lt0800, word0_3);
            word1_3 = _mm_andnot_si128(lt0800, word1_3);

            // 2. expand 2-byte codes into 4-byte codes, possible dwords:
            //    - [0000|0000|0000|0000|0000|0000|0ccc|dddd]
            //    - [0000|0000|0000|0000|110b|bbcc|10cc|dddd]
            //    - [0000|0000|1110|aaaa|10bb|bbcc|10cc|dddd]
            
            __m128i word0;
            __m128i word1;
            word0 = nonstd::_mm_or_si128(word0_1, word0_2, word0_3);
            word1 = word1_3;

            __m128i dword_lo = _mm_unpacklo_epi16(word0, word1);
            __m128i dword_hi = _mm_unpackhi_epi16(word0, word1);

            // 3. compress bytes
            // a. compress lo dwords
            {
                const uint8_t pattern = uint8_t(patterns & 0x00ff);
                const __m128i lookup  = _mm_loadu_si128((const __m128i*)compress_32bit_lookup[pattern]);
                const __m128i utf8    = _mm_shuffle_epi8(dword_lo, lookup);

                _mm_storeu_si128((__m128i*)output, utf8);
                output += compress_32bit_length[pattern];
            }

            // b. compress hi dwords
            {
                const uint8_t pattern = uint8_t(patterns >> 8);
                const __m128i lookup  = _mm_loadu_si128((const __m128i*)compress_32bit_lookup[pattern]);
                const __m128i utf8    = _mm_shuffle_epi8(dword_hi, lookup);

                _mm_storeu_si128((__m128i*)output, utf8);
                output += compress_32bit_length[pattern];
            }
        }
    }

    // process tail
    return output - start;
}

// Assembly:
pushq %rbp
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
andq $-0x8, %rsi
movq %rdx, %r10
je 0x2852
leaq (%rdi,%rsi,2), %rcx
vpbroadcastw 0x5b00(%rip), %xmm0 # 0x8010
vpbroadcastw 0x5af9(%rip), %xmm1 # 0x8012
vpbroadcastw 0x5af2(%rip), %xmm2 # 0x8014
vpxor %xmm3, %xmm3, %xmm3
movw $0xaaaa, %si            # imm = 0xAAAA
vpbroadcastq 0xaa1d(%rip), %xmm4 # 0xcf50
vpbroadcastq 0xaa1c(%rip), %xmm5 # 0xcf58
vpbroadcastw 0x5ad1(%rip), %xmm6 # 0x8016
leaq 0x6af4(%rip), %r9       # 0x9040
vpbroadcastq 0xaa0b(%rip), %xmm7 # 0xcf60
vpbroadcastq 0xaa0a(%rip), %xmm8 # 0xcf68
vpbroadcastq 0xaa09(%rip), %xmm9 # 0xcf70
vpbroadcastq 0xaa08(%rip), %xmm10 # 0xcf78
leaq 0x6bc9(%rip), %r11      # 0x9140
leaq 0x7bc2(%rip), %rbx      # 0xa140
movq %rdx, %r10
vmovdqu (%rdi), %xmm11
vpand %xmm0, %xmm11, %xmm12
vpcmpeqw %xmm1, %xmm12, %xmm13
vpmovmskb %xmm13, %ebp
testl %ebp, %ebp
je 0x2711
xorl %r14d, %r14d
xorl %r12d, %r12d
cmpq $0x10, %r12
je 0x26d6
movzwl (%rdi,%r12), %r13d
leaq 0x2(%r12), %r15
leal 0x2000(%r13), %ebp
cmpw $0xf7ff, %bp            # imm = 0xF7FF
ja 0x25d1
cmpw $0x7f, %r13w
ja 0x25de
movb %r13b, (%r10)
incq %r10
jmp 0x263f
cmpw $0xdbff, %r13w          # imm = 0xDBFF
jbe 0x2605
movb $0x1, %r14b
jmp 0x263f
movl %r13d, %ebp
cmpw $0x7ff, %r13w           # imm = 0x7FF
ja 0x2613
shrl $0x6, %ebp
orb $-0x40, %bpl
movb %bpl, (%r10)
andb $0x3f, %r13b
orb $-0x80, %r13b
movb %r13b, 0x1(%r10)
addq $0x2, %r10
jmp 0x263f
movb $0x1, %bpl
cmpq $0x10, %r15
jne 0x2652
movb $0x1, %r14b
jmp 0x2641
shrl $0xc, %ebp
orb $-0x20, %bpl
movb %bpl, (%r10)
movl %r13d, %ebp
shrl $0x6, %ebp
andb $0x3f, %bpl
orb $-0x80, %bpl
movb %bpl, 0x1(%r10)
andb $0x3f, %r13b
orb $-0x80, %r13b
movb %r13b, 0x2(%r10)
addq $0x3, %r10
xorl %ebp, %ebp
movq %r15, %r12
testb %bpl, %bpl
je 0x25a0
jmp 0x26d6
movzwl (%rdi,%r15), %r15d
leal 0x2000(%r15), %ebp
cmpw $0xfc00, %bp            # imm = 0xFC00
jae 0x266a
movb $0x1, %r14b
jmp 0x26c8
movl %r15d, %ebp
andl $0x3ff, %ebp            # imm = 0x3FF
shll $0xa, %r13d
andl $0xffc00, %r13d         # imm = 0xFFC00
leal (%r13,%rbp), %r8d
leal 0x10000(%r13,%rbp), %ebp
movl %ebp, %r13d
shrl $0x12, %r13d
orb $-0x10, %r13b
movb %r13b, (%r10)
shrl $0xc, %ebp
andb $0x3f, %bpl
orb $-0x80, %bpl
movb %bpl, 0x1(%r10)
shrl $0x6, %r8d
andb $0x3f, %r8b
orb $-0x80, %r8b
movb %r8b, 0x2(%r10)
andb $0x3f, %r15b
orb $-0x80, %r15b
movb %r15b, 0x3(%r10)
addq $0x4, %r10
addq $0x4, %r12
xorl %ebp, %ebp
movq %r12, %r15
jmp 0x2641
movq %r10, %r8
subq %rdx, %r8
movzbl %r14b, %r14d
movl %r14d, %r15d
andl $0x1, %r15d
movl $0x2, %ebp
subl %r15d, %ebp
movl %r14d, %r15d
notb %r15b
movzbl %r15b, %r15d
andl $0x1, %r15d
shll $0x4, %r15d
addq %r15, %rdi
testb $0x1, %r14b
cmovneq %r8, %rax
jmp 0x2843
addq $0x10, %rdi
vpcmpltuw %xmm2, %xmm11, %k1
vpmovm2w %k1, %xmm14
vpcmpeqw %xmm3, %xmm12, %xmm12
kmovd %esi, %k2
vpblendmb %xmm12, %xmm14, %xmm13 {%k2}
vpmovmskb %xmm13, %r14d
cmpl $0xffff, %r14d          # imm = 0xFFFF
je 0x27a3
movl %r14d, %r8d
notl %r8d
vpand %xmm4, %xmm11, %xmm13
testl $0xaaaa, %r8d           # imm = 0xAAAA
jne 0x27b6
movl %r14d, %r8d
shrl $0x7, %r8d
andl $0xaa, %r8d
vpsllw $0x2, %xmm11, %xmm12
vpand %xmm5, %xmm12, %xmm12
vpternlogq $0xfe, %xmm13, %xmm6, %xmm12
vmovdqu16 %xmm11, %xmm12 {%k1}
andl $0x55, %r14d
orl %r8d, %r14d
movl %r14d, %r8d
shll $0x4, %r8d
leaq 0x58b5(%rip), %r15      # 0x8040
vpshufb (%r8,%r15), %xmm12, %xmm11
vmovdqu %xmm11, (%r10)
movzbl (%r14,%r9), %r8d
addq %r8, %r10
jmp 0x2841
vpackuswb %xmm11, %xmm11, %xmm11
vmovq %xmm11, (%r10)
addq $0x8, %r10
jmp 0x2841
vpslld $0x2, %xmm11, %xmm15
vmovdqa64 %xmm13, %xmm16
vpternlogq $0xf8, %xmm5, %xmm15, %xmm16
vpternlogq $0xf8, %xmm7, %xmm15, %xmm13
vpsrlw $0xc, %xmm11, %xmm15
vpternlogq $0x32, %xmm8, %xmm14, %xmm16
vpternlogq $0x32, %xmm9, %xmm12, %xmm13
vpternlogq $0x32, %xmm10, %xmm12, %xmm15
vpternlogq $0xf8, %xmm14, %xmm11, %xmm13
vpternlogq $0xf8, %xmm12, %xmm16, %xmm13
vpunpcklwd %xmm15, %xmm13, %xmm11 # xmm11 = xmm13[0],xmm15[0],xmm13[1],xmm15[1],xmm13[2],xmm15[2],xmm13[3],xmm15[3]
vpunpckhwd %xmm15, %xmm13, %xmm12 # xmm12 = xmm13[4],xmm15[4],xmm13[5],xmm15[5],xmm13[6],xmm15[6],xmm13[7],xmm15[7]
movzbl %r14b, %r8d
movl %r8d, %r15d
shll $0x4, %r15d
vpshufb (%r15,%r11), %xmm11, %xmm11
vmovdqu %xmm11, (%r10)
movzbl (%r8,%rbx), %r8d
leaq (%r10,%r8), %r15
shrl $0x8, %r14d
movl %r14d, %r12d
shll $0x4, %r12d
vpshufb (%r12,%r11), %xmm12, %xmm11
vmovdqu %xmm11, (%r10,%r8)
movzbl (%r14,%rbx), %r10d
addq %r15, %r10
xorl %ebp, %ebp
testb $0x1, %bpl
jne 0x2858
cmpq %rcx, %rdi
jne 0x2581
subq %rdx, %r10
movq %r10, %rax
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
retq