// File: /workspace/github_cpp_makefile_star2_O3/Alcaro[P]bridges/arlib/crc32.cpp
// Function: crc32_pclmul(arrayview<unsigned char>, unsigned int)
__attribute__((target("pclmul")))
static uint32_t crc32_pclmul(arrayview<uint8_t> data, uint32_t crc)
{
	// 0x104c11db7 is the usual polynomial; it's 0xedb88320 with bits reversed, and a 1 prefixed
	// 0x82608edb and 0x1db710641 also represent the same polynomial; they're the above two with or without a trailing 1 bit
	
	__m128i state = _mm_cvtsi32_si128(~crc);
	__m128i* ptr = (__m128i*)data.ptr();
	size_t len = data.size();
	
	__m128i fold_neg128 = _mm_set_epi32(0, crc32_poly_exp(-128-64), 0, crc32_poly_exp(-128));
	__m128i fold_8   = _mm_set_epi32(0, crc32_poly_exp(  8-64), 0, crc32_poly_exp(8));
	__m128i fold_16  = _mm_set_epi32(0, crc32_poly_exp( 16-64), 0, crc32_poly_exp(16));
	__m128i fold_32  = _mm_set_epi32(0, crc32_poly_exp( 32-64), 0, crc32_poly_exp(32));
	__m128i fold_64  = _mm_set_epi32(0, crc32_poly_exp( 64-64), 0, crc32_poly_exp(64));
	__m128i fold_128 = _mm_set_epi32(0, crc32_poly_exp(128-64), 0, crc32_poly_exp(128));
	__m128i fold_256 = _mm_set_epi32(0, crc32_poly_exp(256-64), 0, crc32_poly_exp(256));
	__m128i fold_384 = _mm_set_epi32(0, crc32_poly_exp(384-64), 0, crc32_poly_exp(384));
	__m128i fold_512 = _mm_set_epi32(0, crc32_poly_exp(512-64), 0, crc32_poly_exp(512));
	
	if (len >= 16+48+16)
	{
		__m128i state0 = state;
		__m128i state1 = _mm_setzero_si128();
		__m128i state2 = _mm_setzero_si128();
		__m128i state3 = _mm_setzero_si128();
		
		while (len >= 64+48+16)
		{
			// standard recommendation is process 64 bytes at the time, pclmul's latency is about 7x its execution time
			state0 = do_fold(_mm_xor_si128(state0, _mm_loadu_si128(ptr++)), fold_512);
			state1 = do_fold(_mm_xor_si128(state1, _mm_loadu_si128(ptr++)), fold_512);
			state2 = do_fold(_mm_xor_si128(state2, _mm_loadu_si128(ptr++)), fold_512);
			state3 = do_fold(_mm_xor_si128(state3, _mm_loadu_si128(ptr++)), fold_512);
			len -= 64;
		}
		
		state0 = do_fold(_mm_xor_si128(state0, _mm_loadu_si128(ptr++)), fold_384);
		state1 = do_fold(_mm_xor_si128(state1, _mm_loadu_si128(ptr++)), fold_256);
		state2 = do_fold(_mm_xor_si128(state2, _mm_loadu_si128(ptr++)), fold_128);
		
		state = _mm_xor_si128(_mm_xor_si128(state0, state1), _mm_xor_si128(state2, state3));
		len -= 48;
	}
	
	while (len >= 16+16)
	{
		state = do_fold(_mm_xor_si128(state, _mm_loadu_si128(ptr++)), fold_128);
		len -= 16;
	}
	
	
	const uint8_t * ptr8 = (uint8_t*)ptr;
	if (len & 8)
	{
		state = do_fold(_mm_xor_si128(state, _mm_loadl_epi64(ptr)), fold_64);
		ptr8 += 8;
	}
	if (len & 4)
	{
		state = do_fold(_mm_xor_si128(state, _mm_cvtsi32_si128(*(uint32_t*)ptr8)), fold_32);
		ptr8 += 4;
	}
	if (len & 2)
	{
		state = do_fold(_mm_xor_si128(state, _mm_cvtsi32_si128(*(uint16_t*)ptr8)), fold_16);
		ptr8 += 2;
	}
	if (len & 1)
	{
		state = do_fold(_mm_xor_si128(state, _mm_cvtsi32_si128(*(uint8_t*)ptr8)), fold_8);
		ptr8 += 1;
	}
	
	// alternative implementation, bigger and no meaningful perf difference
	//
	//__m128i folds[8] = {
	//	_mm_set_epi32(0, crc32_poly_exp(  0-64), 0, crc32_poly_exp(0)),
	//	_mm_set_epi32(0, crc32_poly_exp(  8-64), 0, crc32_poly_exp(8)),
	//	_mm_set_epi32(0, crc32_poly_exp( 16-64), 0, crc32_poly_exp(16)),
	//	_mm_set_epi32(0, crc32_poly_exp( 24-64), 0, crc32_poly_exp(24)),
	//	_mm_set_epi32(0, crc32_poly_exp( 32-64), 0, crc32_poly_exp(32)),
	//	_mm_set_epi32(0, crc32_poly_exp( 40-64), 0, crc32_poly_exp(40)),
	//	_mm_set_epi32(0, crc32_poly_exp( 48-64), 0, crc32_poly_exp(48)),
	//	_mm_set_epi32(0, crc32_poly_exp( 56-64), 0, crc32_poly_exp(56)),
	//};
	//
	//__attribute__((target("sse2")))
	//static forceinline __m128i load_sse2_small_highzero(const uint8_t * src, size_t len)
	//{
	//	static const uint8_t mask[32] = {
	//		0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
	//		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
	//	};
	//	return _mm_and_si128(load_sse2_small_highundef(src, len), _mm_loadu_si128((__m128i*)(mask+16-len)));
	//}
	//
	//state = do_fold(_mm_xor_si128(state, load_sse2_small_highzero(ptr8, len&7)), folds[len&7]);
	//ptr8 += len&7;
	
	if (len >= 16)
		state = _mm_xor_si128(state, _mm_loadu_si128((__m128i*)ptr8));
	else
		state = do_fold(state, fold_neg128); // inverse fold - kinda funky, but it works. Used only if input size is less than 16.
	
	// fold 128 bits into 64
	__m128i mask_low32 = _mm_set1_epi64x(0x00000000FFFFFFFF);
	state = _mm_xor_si128(_mm_bsrli_si128(state, 8), _mm_clmulepi64_si128(state, fold_64, 0x00));
	state = _mm_xor_si128(_mm_bsrli_si128(state, 4), _mm_clmulepi64_si128(_mm_and_si128(state, mask_low32), fold_32, 0x00));
	
	// Barrett reduction, using P(x) and µ from http://intel.ly/2ySEwL0, with bits backwards
	// latter magic constant is the polynomial with an extra 1, but I have no idea what the former is
	// it's called U_PRIME in some other implementations, but that doesn't help me
	__m128i magic = _mm_set_epi64x(0x01f7011641, 0x01db710641);
	__m128i magic2;
	magic2 = _mm_clmulepi64_si128(_mm_and_si128(state,  mask_low32), magic, 0x10);
	magic2 = _mm_clmulepi64_si128(_mm_and_si128(magic2, mask_low32), magic, 0x00);
	
	return ~_mm_cvtsi128_si32(_mm_bsrli_si128(_mm_xor_si128(state, magic2), 4));
}

// Assembly:
notl %edx
movd %edx, %xmm1
movl $0x1, %eax
movl $0xc0, %ecx
movl %eax, %edx
sarl $0x1f, %edx
andl $0xdb710641, %edx       # imm = 0xDB710641
addl %eax, %eax
xorl %edx, %eax
decl %ecx
jne 0x7f7f
movl $0x1, %ecx
movl $0x80, %edx
movl %ecx, %r8d
sarl $0x1f, %r8d
andl $0xdb710641, %r8d       # imm = 0xDB710641
addl %ecx, %ecx
xorl %r8d, %ecx
decl %edx
jne 0x7f9c
movd %eax, %xmm2
movd %ecx, %xmm0
punpcklqdq %xmm2, %xmm0    # xmm0 = xmm0[0],xmm2[0]
movl $0x1, %eax
movl $0x81, %ecx
movl %eax, %edx
andl $0x1, %edx
negl %edx
andl $0xedb88320, %edx       # imm = 0xEDB88320
shrl %eax
xorl %edx, %eax
decl %ecx
cmpl $0x1, %ecx
ja 0x7fc9
movd %eax, %xmm3
movaps 0x27374(%rip), %xmm2    # 0x2f360
movss %xmm3, %xmm2            # xmm2 = xmm3[0],xmm2[1,2,3]
movl $0x1, %eax
movl $0xc1, %ecx
movl %eax, %edx
andl $0x1, %edx
negl %edx
andl $0xedb88320, %edx       # imm = 0xEDB88320
shrl %eax
xorl %edx, %eax
decl %ecx
cmpl $0x1, %ecx
ja 0x7ffa
movl $0x1, %ecx
movl $0x101, %edx            # imm = 0x101
movl %ecx, %r8d
andl $0x1, %r8d
negl %r8d
andl $0xedb88320, %r8d       # imm = 0xEDB88320
shrl %ecx
xorl %r8d, %ecx
decl %edx
cmpl $0x1, %edx
ja 0x801c
movd %eax, %xmm4
movd %ecx, %xmm3
punpcklqdq %xmm4, %xmm3    # xmm3 = xmm3[0],xmm4[0]
movl $0x1, %eax
movl $0x141, %ecx            # imm = 0x141
movl %eax, %edx
andl $0x1, %edx
negl %edx
andl $0xedb88320, %edx       # imm = 0xEDB88320
shrl %eax
xorl %edx, %eax
decl %ecx
cmpl $0x1, %ecx
ja 0x804f
movl $0x1, %ecx
movl $0x181, %edx            # imm = 0x181
movl %ecx, %r8d
andl $0x1, %r8d
negl %r8d
andl $0xedb88320, %r8d       # imm = 0xEDB88320
shrl %ecx
xorl %r8d, %ecx
decl %edx
cmpl $0x1, %edx
ja 0x8071
movd %eax, %xmm5
movd %ecx, %xmm4
punpcklqdq %xmm5, %xmm4    # xmm4 = xmm4[0],xmm5[0]
movl $0x1, %eax
movl $0x1c1, %ecx            # imm = 0x1C1
movl %eax, %edx
andl $0x1, %edx
negl %edx
andl $0xedb88320, %edx       # imm = 0xEDB88320
shrl %eax
xorl %edx, %eax
decl %ecx
cmpl $0x1, %ecx
ja 0x80a4
movl $0x1, %ecx
movl $0x201, %edx            # imm = 0x201
movl %ecx, %r8d
andl $0x1, %r8d
negl %r8d
andl $0xedb88320, %r8d       # imm = 0xEDB88320
shrl %ecx
xorl %r8d, %ecx
decl %edx
cmpl $0x1, %edx
ja 0x80c6
cmpq $0x50, %rsi
jb 0x8244
pxor %xmm5, %xmm5
cmpq $0x80, %rsi
jb 0x81c8
movd %eax, %xmm5
movd %ecx, %xmm8
punpcklqdq %xmm5, %xmm8    # xmm8 = xmm8[0],xmm5[0]
pxor %xmm6, %xmm6
pxor %xmm7, %xmm7
pxor %xmm5, %xmm5
movdqu (%rdi), %xmm9
pxor %xmm1, %xmm9
movdqu 0x10(%rdi), %xmm10
pxor %xmm5, %xmm10
movdqu 0x20(%rdi), %xmm11
pxor %xmm7, %xmm11
movdqu 0x30(%rdi), %xmm12
pxor %xmm6, %xmm12
movdqa %xmm9, %xmm5
pclmulqdq $0x0, %xmm8, %xmm5
pclmulqdq $0x11, %xmm8, %xmm9
movdqa %xmm9, %xmm1
pxor %xmm5, %xmm1
movdqa %xmm10, %xmm6
pclmulqdq $0x0, %xmm8, %xmm6
pclmulqdq $0x11, %xmm8, %xmm10
movdqa %xmm10, %xmm5
pxor %xmm6, %xmm5
movdqa %xmm11, %xmm6
pclmulqdq $0x0, %xmm8, %xmm6
pclmulqdq $0x11, %xmm8, %xmm11
movdqa %xmm11, %xmm7
pxor %xmm6, %xmm7
addq $0x40, %rdi
movdqa %xmm12, %xmm9
pclmulqdq $0x0, %xmm8, %xmm9
pclmulqdq $0x11, %xmm8, %xmm12
movdqa %xmm12, %xmm6
pxor %xmm9, %xmm6
addq $-0x40, %rsi
cmpq $0x7f, %rsi
ja 0x8118
jmp 0x81d0
pxor %xmm7, %xmm7
pxor %xmm6, %xmm6
movdqu (%rdi), %xmm8
pxor %xmm1, %xmm8
movdqu 0x10(%rdi), %xmm9
pxor %xmm5, %xmm9
movdqu 0x20(%rdi), %xmm1
pxor %xmm7, %xmm1
movdqa %xmm8, %xmm5
pclmulqdq $0x0, %xmm4, %xmm5
pxor %xmm6, %xmm5
pclmulqdq $0x11, %xmm4, %xmm8
movdqa %xmm9, %xmm4
pclmulqdq $0x0, %xmm3, %xmm4
pxor %xmm8, %xmm4
pxor %xmm5, %xmm4
pclmulqdq $0x11, %xmm3, %xmm9
addq $0x30, %rdi
movdqa %xmm1, %xmm3
pclmulqdq $0x0, %xmm2, %xmm3
pxor %xmm9, %xmm3
pclmulqdq $0x11, %xmm2, %xmm1
pxor %xmm3, %xmm1
pxor %xmm4, %xmm1
addq $-0x30, %rsi
cmpq $0x20, %rsi
jb 0x827a
movdqu (%rdi), %xmm3
addq $0x10, %rdi
pxor %xmm1, %xmm3
movdqa %xmm3, %xmm1
pclmulqdq $0x0, %xmm2, %xmm1
pclmulqdq $0x11, %xmm2, %xmm3
pxor %xmm1, %xmm3
addq $-0x10, %rsi
movdqa %xmm3, %xmm1
cmpq $0x1f, %rsi
ja 0x824a
jmp 0x827e
movdqa %xmm1, %xmm3
testb $0x8, %sil
je 0x82b2
movq (%rdi), %xmm1
pxor %xmm3, %xmm1
movl $0xccaa009e, %eax       # imm = 0xCCAA009E
movq %rax, %xmm2
pclmulqdq $0x0, %xmm1, %xmm2
pclmulqdq $0x11, 0x270ca(%rip), %xmm1 # 0x2f370
pxor %xmm2, %xmm1
addq $0x8, %rdi
movdqa %xmm1, %xmm3
testb $0x4, %sil
je 0x82e2
movd (%rdi), %xmm1
pxor %xmm1, %xmm3
movl $0xb8bc6765, %eax       # imm = 0xB8BC6765
movq %rax, %xmm1
pclmulqdq $0x0, %xmm3, %xmm1
pclmulqdq $0x11, 0x270a6(%rip), %xmm3 # 0x2f380
pxor %xmm1, %xmm3
addq $0x4, %rdi
testb $0x2, %sil
je 0x8315
movzwl (%rdi), %eax
movd %eax, %xmm1
pxor %xmm1, %xmm3
movl $0x191b3141, %eax       # imm = 0x191B3141
movq %rax, %xmm1
pclmulqdq $0x0, %xmm3, %xmm1
pclmulqdq $0x11, 0x27083(%rip), %xmm3 # 0x2f390
pxor %xmm1, %xmm3
addq $0x2, %rdi
testb $0x1, %sil
je 0x8347
movzbl (%rdi), %eax
movd %eax, %xmm1
pxor %xmm1, %xmm3
movl $0x77073096, %eax       # imm = 0x77073096
movq %rax, %xmm1
pclmulqdq $0x0, %xmm3, %xmm1
pclmulqdq $0x11, 0x27060(%rip), %xmm3 # 0x2f3a0
pxor %xmm1, %xmm3
incq %rdi
cmpq $0x10, %rsi
jb 0x8357
movdqu (%rdi), %xmm0
pxor %xmm3, %xmm0
jmp 0x836f
movdqa %xmm3, %xmm1
pclmulqdq $0x0, %xmm0, %xmm1
pclmulqdq $0x11, %xmm0, %xmm3
pxor %xmm1, %xmm3
movdqa %xmm3, %xmm0
movdqa %xmm0, %xmm1
psrldq $0x8, %xmm1             # xmm1 = xmm1[8,9,10,11,12,13,14,15],zero,zero,zero,zero,zero,zero,zero,zero
movl $0xccaa009e, %eax       # imm = 0xCCAA009E
movq %rax, %xmm2
pclmulqdq $0x0, %xmm0, %xmm2
pxor %xmm1, %xmm2
pxor %xmm0, %xmm0
pxor %xmm1, %xmm1
movss %xmm2, %xmm1            # xmm1 = xmm2[0],xmm1[1,2,3]
psrldq $0x4, %xmm2             # xmm2 = xmm2[4,5,6,7,8,9,10,11,12,13,14,15],zero,zero,zero,zero
movl $0xb8bc6765, %eax       # imm = 0xB8BC6765
movq %rax, %xmm3
pclmulqdq $0x0, %xmm1, %xmm3
pxor %xmm2, %xmm3
xorps %xmm1, %xmm1
movss %xmm3, %xmm1            # xmm1 = xmm3[0],xmm1[1,2,3]
pclmulqdq $0x10, 0x26fee(%rip), %xmm1 # 0x2f3b0
movss %xmm1, %xmm0            # xmm0 = xmm1[0],xmm0[1,2,3]
movabsq $0x1db710641, %rax      # imm = 0x1DB710641
movq %rax, %xmm1
pclmulqdq $0x0, %xmm0, %xmm1
pxor %xmm3, %xmm1
pshufd $0x55, %xmm1, %xmm0     # xmm0 = xmm1[1,1,1,1]
movd %xmm0, %eax
notl %eax
retq
nop