// File: /workspace/github_cpp_makefile_star2_O2/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:
pushq %rbp
pushq %r15
pushq %r14
pushq %r13
pushq %r12
pushq %rbx
subq $0xd8, %rsp
movq %rsi, %r14
movq %rdi, %rbx
notl %edx
movd %edx, %xmm0
movdqa %xmm0, 0x20(%rsp)
movl $0xffffff40, %edi       # imm = 0xFFFFFF40
callq 0x6943
movl %eax, %ebp
pushq $-0x80
popq %rdi
callq 0x6943
movd %ebp, %xmm0
movdqa %xmm0, 0x80(%rsp)
movd %eax, %xmm0
movdqa %xmm0, 0xc0(%rsp)
pushq $-0x38
popq %rdi
callq 0x6943
movl %eax, %ebp
pushq $0x8
popq %rdi
callq 0x6943
movd %ebp, %xmm0
movdqa %xmm0, 0x70(%rsp)
movd %eax, %xmm0
movdqa %xmm0, 0xa0(%rsp)
pushq $-0x30
popq %rdi
callq 0x6943
movl %eax, %ebp
pushq $0x10
popq %rdi
callq 0x6943
movd %ebp, %xmm0
movdqa %xmm0, 0x60(%rsp)
movd %eax, %xmm0
movdqa %xmm0, 0x90(%rsp)
pushq $-0x20
popq %rdi
callq 0x6943
movl %eax, %ebp
pushq $0x20
popq %rdi
callq 0x6943
movd %ebp, %xmm0
movdqa %xmm0, 0x50(%rsp)
movd %eax, %xmm0
movdqa %xmm0, 0xb0(%rsp)
xorl %edi, %edi
callq 0x6943
movl %eax, %ebp
pushq $0x40
popq %rdi
callq 0x6943
movd %ebp, %xmm0
movdqa %xmm0, 0x40(%rsp)
movd %eax, %xmm0
movdqa %xmm0, 0x30(%rsp)
movl $0x80, %edi
callq 0x6943
movd %eax, %xmm0
punpcklqdq 0x30(%rsp), %xmm0 # xmm0 = xmm0[0],mem[0]
movdqa %xmm0, 0x10(%rsp)
movl $0xc0, %edi
callq 0x6943
movl %eax, 0xc(%rsp)
movl $0x100, %edi            # imm = 0x100
callq 0x6943
movl %eax, %r15d
movl $0x140, %edi            # imm = 0x140
callq 0x6943
movl %eax, %r12d
movl $0x180, %edi            # imm = 0x180
callq 0x6943
movl %eax, %r13d
movl $0x1c0, %edi            # imm = 0x1C0
callq 0x6943
movl %eax, %ebp
movl $0x200, %edi            # imm = 0x200
callq 0x6943
cmpq $0x50, %r14
jb 0x670d
movd 0xc(%rsp), %xmm1
movd %r15d, %xmm0
punpcklqdq %xmm1, %xmm0    # xmm0 = xmm0[0],xmm1[0]
movd %r12d, %xmm2
movd %r13d, %xmm1
punpcklqdq %xmm2, %xmm1    # xmm1 = xmm1[0],xmm2[0]
movd %ebp, %xmm2
movd %eax, %xmm3
punpcklqdq %xmm2, %xmm3    # xmm3 = xmm3[0],xmm2[0]
addq $0x30, %rbx
pxor %xmm6, %xmm6
pxor %xmm5, %xmm5
pxor %xmm2, %xmm2
movdqa 0x20(%rsp), %xmm10
movdqu -0x30(%rbx), %xmm4
pxor %xmm10, %xmm4
cmpq $0x80, %r14
jb 0x671c
movdqa %xmm4, %xmm7
pclmulqdq $0x0, %xmm3, %xmm7
pclmulqdq $0x11, %xmm3, %xmm4
pxor %xmm7, %xmm4
movdqu -0x20(%rbx), %xmm7
movdqu -0x10(%rbx), %xmm8
movdqu (%rbx), %xmm9
pxor %xmm6, %xmm7
movdqa %xmm7, %xmm6
pclmulqdq $0x0, %xmm3, %xmm6
pclmulqdq $0x11, %xmm3, %xmm7
pxor %xmm6, %xmm7
pxor %xmm5, %xmm8
movdqa %xmm8, %xmm5
pclmulqdq $0x0, %xmm3, %xmm5
pclmulqdq $0x11, %xmm3, %xmm8
pxor %xmm5, %xmm8
pxor %xmm2, %xmm9
movdqa %xmm9, %xmm2
pclmulqdq $0x0, %xmm3, %xmm2
pclmulqdq $0x11, %xmm3, %xmm9
pxor %xmm2, %xmm9
addq $-0x40, %r14
addq $0x40, %rbx
movdqa %xmm4, %xmm10
movdqa %xmm7, %xmm6
movdqa %xmm8, %xmm5
movdqa %xmm9, %xmm2
jmp 0x6662
movdqa 0x20(%rsp), %xmm7
movdqa 0x10(%rsp), %xmm8
jmp 0x6783
movdqa %xmm4, %xmm3
pclmulqdq $0x0, %xmm1, %xmm3
pclmulqdq $0x11, %xmm1, %xmm4
pxor %xmm3, %xmm4
movdqu -0x20(%rbx), %xmm1
movdqu -0x10(%rbx), %xmm7
pxor %xmm6, %xmm1
movdqa %xmm1, %xmm3
pclmulqdq $0x0, %xmm0, %xmm3
pclmulqdq $0x11, %xmm0, %xmm1
pxor %xmm3, %xmm1
pxor %xmm4, %xmm1
pxor %xmm5, %xmm7
movdqa %xmm7, %xmm0
movdqa 0x10(%rsp), %xmm8
pclmulqdq $0x0, %xmm8, %xmm0
pclmulqdq $0x11, %xmm8, %xmm7
pxor %xmm0, %xmm7
pxor %xmm2, %xmm7
pxor %xmm1, %xmm7
addq $-0x30, %r14
movdqa 0xc0(%rsp), %xmm2
punpcklqdq 0x80(%rsp), %xmm2 # xmm2 = xmm2[0],mem[0]
movdqa 0xa0(%rsp), %xmm5
punpcklqdq 0x70(%rsp), %xmm5 # xmm5 = xmm5[0],mem[0]
movdqa 0x90(%rsp), %xmm6
punpcklqdq 0x60(%rsp), %xmm6 # xmm6 = xmm6[0],mem[0]
movdqa 0xb0(%rsp), %xmm3
punpcklqdq 0x50(%rsp), %xmm3 # xmm3 = xmm3[0],mem[0]
movdqa 0x30(%rsp), %xmm4
punpcklqdq 0x40(%rsp), %xmm4 # xmm4 = xmm4[0],mem[0]
cmpq $0x20, %r14
jb 0x6800
movdqu (%rbx), %xmm0
addq $0x10, %rbx
pxor %xmm7, %xmm0
movdqa %xmm0, %xmm1
pclmulqdq $0x0, %xmm8, %xmm1
pclmulqdq $0x11, %xmm8, %xmm0
pxor %xmm1, %xmm0
addq $-0x10, %r14
movdqa %xmm0, %xmm7
jmp 0x67ce
testb $0x8, %r14b
je 0x682a
movq (%rbx), %xmm0
pxor %xmm7, %xmm0
movdqa %xmm0, %xmm1
pclmulqdq $0x0, %xmm4, %xmm1
pclmulqdq $0x11, %xmm4, %xmm0
pxor %xmm1, %xmm0
addq $0x8, %rbx
movdqa %xmm0, %xmm7
testb $0x4, %r14b
je 0x6850
movd (%rbx), %xmm0
pxor %xmm0, %xmm7
movdqa %xmm7, %xmm0
pclmulqdq $0x0, %xmm3, %xmm0
pclmulqdq $0x11, %xmm3, %xmm7
pxor %xmm0, %xmm7
addq $0x4, %rbx
testb $0x2, %r14b
je 0x6879
movzwl (%rbx), %eax
movd %eax, %xmm0
pxor %xmm0, %xmm7
movdqa %xmm7, %xmm0
pclmulqdq $0x0, %xmm6, %xmm0
pclmulqdq $0x11, %xmm6, %xmm7
pxor %xmm0, %xmm7
addq $0x2, %rbx
testb $0x1, %r14b
je 0x68a1
movzbl (%rbx), %eax
movd %eax, %xmm0
pxor %xmm0, %xmm7
movdqa %xmm7, %xmm0
pclmulqdq $0x0, %xmm5, %xmm0
pclmulqdq $0x11, %xmm5, %xmm7
pxor %xmm0, %xmm7
incq %rbx
cmpq $0x10, %r14
jb 0x68b1
movdqu (%rbx), %xmm0
pxor %xmm7, %xmm0
jmp 0x68c9
movdqa %xmm7, %xmm0
pclmulqdq $0x0, %xmm2, %xmm0
pclmulqdq $0x11, %xmm2, %xmm7
pxor %xmm0, %xmm7
movdqa %xmm7, %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
pclmulqdq $0x0, %xmm0, %xmm4
pxor %xmm1, %xmm4
pxor %xmm0, %xmm0
pxor %xmm1, %xmm1
movss %xmm4, %xmm1            # xmm1 = xmm4[0],xmm1[1,2,3]
psrldq $0x4, %xmm4             # xmm4 = xmm4[4,5,6,7,8,9,10,11,12,13,14,15],zero,zero,zero,zero
pclmulqdq $0x0, %xmm3, %xmm1
pxor %xmm4, %xmm1
pxor %xmm2, %xmm2
movss %xmm1, %xmm2            # xmm2 = xmm1[0],xmm2[1,2,3]
pclmulqdq $0x10, 0x21a57(%rip), %xmm2 # 0x28360
movss %xmm2, %xmm0            # xmm0 = xmm2[0],xmm0[1,2,3]
movabsq $0x1db710641, %rax      # imm = 0x1DB710641
movq %rax, %xmm2
pclmulqdq $0x0, %xmm0, %xmm2
pxor %xmm1, %xmm2
pshufd $0x55, %xmm2, %xmm0     # xmm0 = xmm2[1,1,1,1]
movd %xmm0, %eax
notl %eax
addq $0xd8, %rsp
popq %rbx
popq %r12
popq %r13
popq %r14
popq %r15
popq %rbp
retq