// File: BU-DiSC[P]BF-Shared-Hashing/hash/Crc32.cpp
// Function: crc32_combine(unsigned int, unsigned int, unsigned long)
uint32_t crc32_combine(uint32_t crcA, uint32_t crcB, size_t lengthB)
{
  // based on Mark Adler's crc_combine from
  // https://github.com/madler/pigz/blob/master/pigz.c

  // main idea:
  // - if you have two equally-sized blocks A and B,
  //   then you can create a block C = A ^ B
  //   which has the property crc(C) = crc(A) ^ crc(B)
  // - if you append length(B) zeros to A and call it A' (think of it as AAAA000)
  //   and   prepend length(A) zeros to B and call it B' (think of it as 0000BBB)
  //   then exists a C' = A' ^ B'
  // - remember: if you XOR someting with zero, it remains unchanged: X ^ 0 = X
  // - that means C' = A concat B so that crc(A concat B) = crc(C') = crc(A') ^ crc(B')
  // - the trick is to compute crc(A') based on crc(A)
  //                       and crc(B') based on crc(B)
  // - since B' starts with many zeros, the crc of those initial zeros is still zero
  // - that means crc(B') = crc(B)
  // - unfortunately the trailing zeros of A' change the crc, so usually crc(A') != crc(A)
  // - the following code is a fast algorithm to compute crc(A')
  // - starting with crc(A) and appending length(B) zeros, needing just log2(length(B)) iterations
  // - the details are explained by the original author at
  //   https://stackoverflow.com/questions/23122312/crc-calculation-of-a-mostly-static-data-stream/23126768
  //
  // notes:
  // - I squeezed everything into one function to keep global namespace clean (original code two helper functions)
  // - most original comments are still in place, I added comments where these helper functions where made inline code
  // - performance-wise there isn't any differenze to the original zlib/pigz code

  // degenerated case
  if (lengthB == 0)
    return crcA;

  /// CRC32 => 32 bits
  const uint32_t CrcBits = 32;

  uint32_t odd [CrcBits]; // odd-power-of-two  zeros operator
  uint32_t even[CrcBits]; // even-power-of-two zeros operator

  // put operator for one zero bit in odd
  odd[0] = Polynomial;    // CRC-32 polynomial
  for (int i = 1; i < CrcBits; i++)
    odd[i] = 1 << (i - 1);

  // put operator for two zero bits in even
  // same as gf2_matrix_square(even, odd);
  for (int i = 0; i < CrcBits; i++)
  {
    uint32_t vec = odd[i];
    even[i] = 0;
    for (int j = 0; vec != 0; j++, vec >>= 1)
      if (vec & 1)
        even[i] ^= odd[j];
  }
  // put operator for four zero bits in odd
  // same as gf2_matrix_square(odd, even);
  for (int i = 0; i < CrcBits; i++)
  {
    uint32_t vec = even[i];
    odd[i] = 0;
    for (int j = 0; vec != 0; j++, vec >>= 1)
      if (vec & 1)
        odd[i] ^= even[j];
  }

  // the following loop becomes much shorter if I keep swapping even and odd
  uint32_t* a = even;
  uint32_t* b = odd;
  // apply secondLength zeros to firstCrc32
  for (; lengthB > 0; lengthB >>= 1)
  {
    // same as gf2_matrix_square(a, b);
    for (int i = 0; i < CrcBits; i++)
    {
      uint32_t vec = b[i];
      a[i] = 0;
      for (int j = 0; vec != 0; j++, vec >>= 1)
        if (vec & 1)
          a[i] ^= b[j];
    }

    // apply zeros operator for this bit
    if (lengthB & 1)
    {
      // same as firstCrc32 = gf2_matrix_times(a, firstCrc32);
      uint32_t sum = 0;
      for (int i = 0; crcA != 0; i++, crcA >>= 1)
        if (crcA & 1)
          sum ^= a[i];
      crcA = sum;
    }

    // switch even and odd
    uint32_t* t = a; a = b; b = t;
  }

  // return combined crc
  return crcA ^ crcB;
}

// Assembly:
subq $0xf8, %rsp
movl %edi, 0xf0(%rsp)
movl %esi, 0xec(%rsp)
movq %rdx, 0xe0(%rsp)
cmpq $0x0, 0xe0(%rsp)
jne 0x60eeb
movl 0xf0(%rsp), %eax
movl %eax, 0xf4(%rsp)
jmp 0x611b7
movl $0x20, 0xdc(%rsp)
movl $0xedb88320, 0x50(%rsp) # imm = 0xEDB88320
movl $0x1, -0x34(%rsp)
cmpl $0x20, -0x34(%rsp)
jae 0x60f33
movl -0x34(%rsp), %ecx
subl $0x1, %ecx
movl $0x1, %eax
shll %cl, %eax
movl %eax, %ecx
movslq -0x34(%rsp), %rax
movl %ecx, 0x50(%rsp,%rax,4)
movl -0x34(%rsp), %eax
addl $0x1, %eax
movl %eax, -0x34(%rsp)
jmp 0x60f06
movl $0x0, -0x38(%rsp)
cmpl $0x20, -0x38(%rsp)
jae 0x60fb5
movslq -0x38(%rsp), %rax
movl 0x50(%rsp,%rax,4), %eax
movl %eax, -0x3c(%rsp)
movslq -0x38(%rsp), %rax
movl $0x0, -0x30(%rsp,%rax,4)
movl $0x0, -0x40(%rsp)
cmpl $0x0, -0x3c(%rsp)
je 0x60fa6
movl -0x3c(%rsp), %eax
andl $0x1, %eax
cmpl $0x0, %eax
je 0x60f8d
movslq -0x40(%rsp), %rax
movl 0x50(%rsp,%rax,4), %ecx
movslq -0x38(%rsp), %rax
xorl -0x30(%rsp,%rax,4), %ecx
movl %ecx, -0x30(%rsp,%rax,4)
jmp 0x60f8f
movl -0x40(%rsp), %eax
addl $0x1, %eax
movl %eax, -0x40(%rsp)
movl -0x3c(%rsp), %eax
shrl %eax
movl %eax, -0x3c(%rsp)
jmp 0x60f64
jmp 0x60fa8
movl -0x38(%rsp), %eax
addl $0x1, %eax
movl %eax, -0x38(%rsp)
jmp 0x60f3b
movl $0x0, -0x44(%rsp)
cmpl $0x20, -0x44(%rsp)
jae 0x61037
movslq -0x44(%rsp), %rax
movl -0x30(%rsp,%rax,4), %eax
movl %eax, -0x48(%rsp)
movslq -0x44(%rsp), %rax
movl $0x0, 0x50(%rsp,%rax,4)
movl $0x0, -0x4c(%rsp)
cmpl $0x0, -0x48(%rsp)
je 0x61028
movl -0x48(%rsp), %eax
andl $0x1, %eax
cmpl $0x0, %eax
je 0x6100f
movslq -0x4c(%rsp), %rax
movl -0x30(%rsp,%rax,4), %ecx
movslq -0x44(%rsp), %rax
xorl 0x50(%rsp,%rax,4), %ecx
movl %ecx, 0x50(%rsp,%rax,4)
jmp 0x61011
movl -0x4c(%rsp), %eax
addl $0x1, %eax
movl %eax, -0x4c(%rsp)
movl -0x48(%rsp), %eax
shrl %eax
movl %eax, -0x48(%rsp)
jmp 0x60fe6
jmp 0x6102a
movl -0x44(%rsp), %eax
addl $0x1, %eax
movl %eax, -0x44(%rsp)
jmp 0x60fbd
leaq -0x30(%rsp), %rax
movq %rax, -0x58(%rsp)
leaq 0x50(%rsp), %rax
movq %rax, -0x60(%rsp)
cmpq $0x0, 0xe0(%rsp)
jbe 0x611a2
movl $0x0, -0x64(%rsp)
cmpl $0x20, -0x64(%rsp)
jae 0x610f2
movq -0x60(%rsp), %rax
movslq -0x64(%rsp), %rcx
movl (%rax,%rcx,4), %eax
movl %eax, -0x68(%rsp)
movq -0x58(%rsp), %rax
movslq -0x64(%rsp), %rcx
movl $0x0, (%rax,%rcx,4)
movl $0x0, -0x6c(%rsp)
cmpl $0x0, -0x68(%rsp)
je 0x610e0
movl -0x68(%rsp), %eax
andl $0x1, %eax
cmpl $0x0, %eax
je 0x610c7
movq -0x60(%rsp), %rax
movslq -0x6c(%rsp), %rcx
movl (%rax,%rcx,4), %edx
movq -0x58(%rsp), %rax
movslq -0x64(%rsp), %rcx
xorl (%rax,%rcx,4), %edx
movl %edx, (%rax,%rcx,4)
jmp 0x610c9
movl -0x6c(%rsp), %eax
addl $0x1, %eax
movl %eax, -0x6c(%rsp)
movl -0x68(%rsp), %eax
shrl %eax
movl %eax, -0x68(%rsp)
jmp 0x61097
jmp 0x610e2
movl -0x64(%rsp), %eax
addl $0x1, %eax
movl %eax, -0x64(%rsp)
jmp 0x61062
movq 0xe0(%rsp), %rax
andq $0x1, %rax
cmpq $0x0, %rax
je 0x6116c
movl $0x0, -0x70(%rsp)
movl $0x0, -0x74(%rsp)
cmpl $0x0, 0xf0(%rsp)
je 0x61161
movl 0xf0(%rsp), %eax
andl $0x1, %eax
cmpl $0x0, %eax
je 0x61142
movq -0x58(%rsp), %rax
movslq -0x74(%rsp), %rcx
movl (%rax,%rcx,4), %eax
xorl -0x70(%rsp), %eax
movl %eax, -0x70(%rsp)
jmp 0x61144
movl -0x74(%rsp), %eax
addl $0x1, %eax
movl %eax, -0x74(%rsp)
movl 0xf0(%rsp), %eax
shrl %eax
movl %eax, 0xf0(%rsp)
jmp 0x61114
movl -0x70(%rsp), %eax
movl %eax, 0xf0(%rsp)
movq -0x58(%rsp), %rax
movq %rax, -0x80(%rsp)
movq -0x60(%rsp), %rax
movq %rax, -0x58(%rsp)
movq -0x80(%rsp), %rax
movq %rax, -0x60(%rsp)
movq 0xe0(%rsp), %rax
shrq %rax
movq %rax, 0xe0(%rsp)
jmp 0x6104b
movl 0xf0(%rsp), %eax
xorl 0xec(%rsp), %eax
movl %eax, 0xf4(%rsp)
movl 0xf4(%rsp), %eax
addq $0xf8, %rsp
retq
nopw %cs:(%rax,%rax)