#ifdef LZMA_CRC_X86_CLMUL_H
# error crc_x86_clmul.h was included twice.
#endif
#define LZMA_CRC_X86_CLMUL_H
#if BUILDING_CRC_CLMUL != 32 && BUILDING_CRC_CLMUL != 64
# error BUILDING_CRC_CLMUL is undefined or has an invalid value
#endif
#include <immintrin.h>
#if defined(_MSC_VER)
# include <intrin.h>
#elif defined(HAVE_CPUID_H)
# include <cpuid.h>
#endif
#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__)
# define crc_attr_target \
__attribute__((__target__("ssse3,sse4.1,pclmul")))
#else
# define crc_attr_target
#endif
#if defined(__i386__) || defined(_M_IX86)
# define my_set_low64(a) _mm_set_epi64x(0, (a))
#else
# define my_set_low64(a) _mm_cvtsi64_si128(a)
#endif
alignas(64)
static uint8_t vmasks[64] = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
};
crc_attr_target
static inline __m128i
my_load128(const uint8_t *p)
{
return _mm_loadu_si128((const __m128i *)p);
}
crc_attr_target
static inline __m128i
keep_high_bytes(__m128i v, size_t count)
{
return _mm_and_si128(my_load128((vmasks + count)), v);
}
crc_attr_target
static inline __m128i
shift_left(__m128i v, size_t amount)
{
return _mm_shuffle_epi8(v, my_load128((vmasks + 32 - amount)));
}
crc_attr_target
static inline __m128i
shift_right(__m128i v, size_t amount)
{
return _mm_shuffle_epi8(v, my_load128((vmasks + 32 + amount)));
}
crc_attr_target
static inline __m128i
fold(__m128i v, __m128i k)
{
__m128i a = _mm_clmulepi64_si128(v, k, 0x00);
__m128i b = _mm_clmulepi64_si128(v, k, 0x11);
return _mm_xor_si128(a, b);
}
crc_attr_target
static inline __m128i
fold_xor(__m128i v, __m128i k, const uint8_t *buf)
{
return _mm_xor_si128(my_load128(buf), fold(v, k));
}
#if BUILDING_CRC_CLMUL == 32
crc_attr_target
static uint32_t
crc32_arch_optimized(const uint8_t *buf, size_t size, uint32_t crc)
#else
crc_attr_target
static uint64_t
crc64_arch_optimized(const uint8_t *buf, size_t size, uint64_t crc)
#endif
{
if (size == 0)
return crc;
#if BUILDING_CRC_CLMUL == 32
const __m128i fold512 = _mm_set_epi64x(0x1d9513d7, 0x8f352d95);
const __m128i fold128 = _mm_set_epi64x(0xccaa009e, 0xae689191);
const __m128i mu_p = _mm_set_epi64x(
(int64_t)0xb4e5b025f7011641, 0x1db710640);
#else
const __m128i fold512 = _mm_set_epi64x(
(int64_t)0x081f6054a7842df4, (int64_t)0x6ae3efbb9dd441f3);
const __m128i fold128 = _mm_set_epi64x(
(int64_t)0xdabe95afc7875f40, (int64_t)0xe05dd497ca393ae4);
const __m128i mu_p = _mm_set_epi64x(
(int64_t)0x9c3e466c172963d5, (int64_t)0x92d8af2baf0e1e84);
#endif
__m128i v0, v1, v2, v3;
crc = ~crc;
if (size < 8) {
uint64_t x = crc;
size_t i = 0;
if (size & 4) {
x ^= read32le(buf);
buf += 4;
i = 32;
}
if (size & 2) {
x ^= (uint64_t)read16le(buf) << i;
buf += 2;
i += 16;
}
if (size & 1)
x ^= (uint64_t)*buf << i;
v0 = my_set_low64((int64_t)x);
v0 = shift_left(v0, 8 - size);
} else if (size < 16) {
v0 = my_set_low64((int64_t)(crc ^ read64le(buf)));
size -= 8;
if (size > 0) {
const size_t padding = 8 - size;
uint64_t high = read64le(buf + size) >> (padding * 8);
#if defined(__i386__) || defined(_M_IX86)
v0 = _mm_insert_epi32(v0, (int32_t)high, 2);
v0 = _mm_insert_epi32(v0, (int32_t)(high >> 32), 3);
#else
v0 = _mm_insert_epi64(v0, (int64_t)high, 1);
#endif
v0 = shift_left(v0, padding);
v1 = _mm_srli_si128(v0, 8);
v0 = _mm_clmulepi64_si128(v0, fold128, 0x10);
v0 = _mm_xor_si128(v0, v1);
}
} else {
v0 = my_set_low64((int64_t)crc);
v0 = _mm_xor_si128(v0, my_load128(buf));
buf += 16;
size -= 16;
if (size >= 48) {
v1 = my_load128(buf);
v2 = my_load128(buf + 16);
v3 = my_load128(buf + 32);
buf += 48;
size -= 48;
while (size >= 64) {
v0 = fold_xor(v0, fold512, buf);
v1 = fold_xor(v1, fold512, buf + 16);
v2 = fold_xor(v2, fold512, buf + 32);
v3 = fold_xor(v3, fold512, buf + 48);
buf += 64;
size -= 64;
}
v0 = _mm_xor_si128(v1, fold(v0, fold128));
v0 = _mm_xor_si128(v2, fold(v0, fold128));
v0 = _mm_xor_si128(v3, fold(v0, fold128));
}
while (size >= 16) {
v0 = fold_xor(v0, fold128, buf);
buf += 16;
size -= 16;
}
if (size > 0) {
v1 = my_load128(buf + size - 16);
v1 = keep_high_bytes(v1, size);
v1 = _mm_or_si128(v1, shift_right(v0, size));
v0 = shift_left(v0, 16 - size);
v0 = _mm_xor_si128(v1, fold(v0, fold128));
}
v1 = _mm_srli_si128(v0, 8);
v0 = _mm_clmulepi64_si128(v0, fold128, 0x10);
v0 = _mm_xor_si128(v0, v1);
}
#if BUILDING_CRC_CLMUL == 32
v1 = _mm_clmulepi64_si128(v0, mu_p, 0x10); v1 = _mm_clmulepi64_si128(v1, mu_p, 0x00); v0 = _mm_xor_si128(v0, v1);
return ~(uint32_t)_mm_extract_epi32(v0, 2);
#else
v1 = _mm_clmulepi64_si128(v0, mu_p, 0x10); v2 = _mm_slli_si128(v1, 8);
v1 = _mm_clmulepi64_si128(v1, mu_p, 0x00); v0 = _mm_xor_si128(v0, v2);
v0 = _mm_xor_si128(v0, v1);
#if defined(__i386__) || defined(_M_IX86)
return ~(((uint64_t)(uint32_t)_mm_extract_epi32(v0, 3) << 32) |
(uint64_t)(uint32_t)_mm_extract_epi32(v0, 2));
#else
return ~(uint64_t)_mm_extract_epi64(v0, 1);
#endif
#endif
}
#if defined(CRC32_GENERIC) || defined(CRC64_GENERIC)
static inline bool
is_arch_extension_supported(void)
{
int success = 1;
uint32_t r[4];
#if defined(_MSC_VER)
__cpuid((int *)r, 1);
#elif defined(HAVE_CPUID_H)
success = __get_cpuid(1, &r[0], &r[1], &r[2], &r[3]);
#else
__asm__("cpuid\n\t"
: "=a"(r[0]), "=b"(r[1]), "=c"(r[2]), "=d"(r[3])
: "a"(1), "c"(0));
#endif
const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19);
return success && (r[2] & ecx_mask) == ecx_mask;
}
#endif