]> git.ipfire.org Git - thirdparty/xz.git/commitdiff
liblzma: Create crc_clmul.c.
authorJia Tan <jiat0218@gmail.com>
Sat, 14 Oct 2023 04:17:57 +0000 (12:17 +0800)
committerJia Tan <jiat0218@gmail.com>
Wed, 18 Oct 2023 15:54:36 +0000 (23:54 +0800)
Both crc32_clmul() and crc64_clmul() are now exported from
crc32_clmul.c as lzma_crc32_clmul() and lzma_crc64_clmul(). This
ensures that is_clmul_supported() (now lzma_is_clmul_supported()) is
not duplicated between crc32_fast.c and crc64_fast.c.

Also, it encapsulates the complexity of the CLMUL implementations into a
single file and reduces the complexity of crc32_fast.c and crc64_fast.c.
Before, CLMUL code was present in crc32_fast.c, crc64_fast.c, and
crc_common.h.

During the conversion, various cleanups were applied to code (thanks to
Lasse Collin) including:

- Require using semicolons with MASK_/L/H/LH macros.
- Variable typing and const handling improvements.
- Improvements to comments.
- Fixes to the pragmas used.
- Removed unneeded variables.
- Whitespace improvements.
- Fixed CRC_USE_GENERIC_FOR_SMALL_INPUTS handling.
- Silenced warnings and removed the need for some #pragmas

CMakeLists.txt
configure.ac
src/liblzma/check/Makefile.inc
src/liblzma/check/crc32_fast.c
src/liblzma/check/crc64_fast.c
src/liblzma/check/crc_clmul.c [new file with mode: 0644]
src/liblzma/check/crc_common.h

index ecbb7b2d1b77923fe086387c63412eb7d006e8eb..6e3cd6f88ed3d1bfcfa2327172fdf097eea34278 100644 (file)
@@ -845,7 +845,11 @@ if(HAVE_IMMINTRIN_H)
             int main(void) { return 0; }
     "
     HAVE_USABLE_CLMUL)
-    tuklib_add_definition_if(liblzma HAVE_USABLE_CLMUL)
+
+    if(HAVE_USABLE_CLMUL)
+        target_sources(liblzma PRIVATE src/liblzma/check/crc_clmul.c)
+        target_compile_definitions(liblzma PRIVATE HAVE_USABLE_CLMUL)
+    endif()
 endif()
 
 # Support -fvisiblity=hidden when building shared liblzma.
index 4d298430fc4f89d15a03329c2328cfd43a7612e5..19dbf1d74aad97df47ab407ded43fefe840fd8a0 100644 (file)
@@ -1035,11 +1035,13 @@ __m128i my_clmul(__m128i a)
                        [Define to 1 if _mm_set_epi64x and
                        _mm_clmulepi64_si128 are usable.
                        See configure.ac for details.])
-               AC_MSG_RESULT([yes])
+               enable_clmul_crc=yes
        ], [
-               AC_MSG_RESULT([no])
+               enable_clmul_crc=no
        ])
+       AC_MSG_RESULT([$enable_clmul_crc])
 ])
+AM_CONDITIONAL([COND_CRC_CLMUL], [test "x$enable_clmul_crc" = xyes])
 
 # Check for sandbox support. If one is found, set enable_sandbox=found.
 AS_CASE([$enable_sandbox],
index 1b01635655ef28c74ff0fae0b71935852a7fb3c4..7780d20d7dd95d336d9791ebf40a2b87b761e5c8 100644 (file)
@@ -26,6 +26,9 @@ if COND_ASM_X86
 liblzma_la_SOURCES += check/crc32_x86.S
 else
 liblzma_la_SOURCES += check/crc32_fast.c
+if COND_CRC_CLMUL
+liblzma_la_SOURCES += check/crc_clmul.c
+endif
 endif
 endif
 endif
index 3ac9b6d70b42acf03d49737c12b9ba5327488f7b..8849a47609adc8283fe5c002432a3d5e9303a840 100644 (file)
 #include "check.h"
 #include "crc_common.h"
 
+#ifdef CRC_GENERIC
+
 ///////////////////
 // Generic CRC32 //
 ///////////////////
-#ifdef CRC_GENERIC
-
 
 static uint32_t
 crc32_generic(const uint8_t *buf, size_t size, uint32_t crc)
@@ -99,118 +99,6 @@ crc32_generic(const uint8_t *buf, size_t size, uint32_t crc)
 }
 #endif
 
-
-/////////////////////
-// x86 CLMUL CRC32 //
-/////////////////////
-
-#ifdef CRC_CLMUL
-
-#include <immintrin.h>
-
-
-/*
-// These functions were used to generate the constants
-// at the top of crc32_clmul().
-static uint64_t
-calc_lo(uint64_t p, uint64_t a, int n)
-{
-       uint64_t b = 0; int i;
-       for (i = 0; i < n; i++) {
-               b = b >> 1 | (a & 1) << (n - 1);
-               a = (a >> 1) ^ ((0 - (a & 1)) & p);
-       }
-       return b;
-}
-
-// same as ~crc(&a, sizeof(a), ~0)
-static uint64_t
-calc_hi(uint64_t p, uint64_t a, int n)
-{
-       int i;
-       for (i = 0; i < n; i++)
-               a = (a >> 1) ^ ((0 - (a & 1)) & p);
-       return a;
-}
-*/
-
-
-// MSVC (VS2015 - VS2022) produces bad 32-bit x86 code from the CLMUL CRC
-// code when optimizations are enabled (release build). According to the bug
-// report, the ebx register is corrupted and the calculated result is wrong.
-// Trying to workaround the problem with "__asm mov ebx, ebx" didn't help.
-// The following pragma works and performance is still good. x86-64 builds
-// aren't affected by this problem.
-//
-// NOTE: Another pragma after the function restores the optimizations.
-// If the #if condition here is updated, the other one must be updated too.
-#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) \
-               && defined(_M_IX86)
-#      pragma optimize("g", off)
-#endif
-
-// EDG-based compilers (Intel's classic compiler and compiler for E2K) can
-// define __GNUC__ but the attribute must not be used with them.
-// The new Clang-based ICX needs the attribute.
-//
-// NOTE: Build systems check for this too, keep them in sync with this.
-#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__)
-__attribute__((__target__("ssse3,sse4.1,pclmul")))
-#endif
-static uint32_t
-crc32_clmul(const uint8_t *buf, size_t size, uint32_t crc)
-{
-       // The prototypes of the intrinsics use signed types while most of
-       // the values are treated as unsigned here. These warnings in this
-       // function have been checked and found to be harmless so silence them.
-#if TUKLIB_GNUC_REQ(4, 6) || defined(__clang__)
-#      pragma GCC diagnostic push
-#      pragma GCC diagnostic ignored "-Wsign-conversion"
-#      pragma GCC diagnostic ignored "-Wconversion"
-#endif
-
-#ifndef CRC_USE_GENERIC_FOR_SMALL_INPUTS
-       // The code assumes that there is at least one byte of input.
-       if (size == 0)
-               return crc;
-#endif
-
-       // uint32_t poly = 0xedb88320;
-       uint64_t p = 0x1db710640; // p << 1
-       uint64_t mu = 0x1f7011641; // calc_lo(p, p, 32) << 1 | 1
-       uint64_t k5 = 0x163cd6124; // calc_hi(p, p, 32) << 1
-       uint64_t k4 = 0x0ccaa009e; // calc_hi(p, p, 64) << 1
-       uint64_t k3 = 0x1751997d0; // calc_hi(p, p, 128) << 1
-
-       __m128i vfold4 = _mm_set_epi64x(mu, p);
-       __m128i vfold8 = _mm_set_epi64x(0, k5);
-       __m128i vfold16 = _mm_set_epi64x(k4, k3);
-
-       __m128i v0, v1, v2;
-
-       crc_simd_body(buf,  size, &v0, &v1, vfold16, _mm_cvtsi32_si128(~crc));
-
-       v1 = _mm_xor_si128(
-                       _mm_clmulepi64_si128(v0, vfold16, 0x10), v1); // xxx0
-       v2 = _mm_shuffle_epi32(v1, 0xe7); // 0xx0
-       v0 = _mm_slli_epi64(v1, 32);  // [0]
-       v0 = _mm_clmulepi64_si128(v0, vfold8, 0x00);
-       v0 = _mm_xor_si128(v0, v2);   // [1] [2]
-       v2 = _mm_clmulepi64_si128(v0, vfold4, 0x10);
-       v2 = _mm_clmulepi64_si128(v2, vfold4, 0x00);
-       v0 = _mm_xor_si128(v0, v2);   // [2]
-       return ~_mm_extract_epi32(v0, 2);
-
-#if TUKLIB_GNUC_REQ(4, 6) || defined(__clang__)
-#      pragma GCC diagnostic pop
-#endif
-}
-#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) \
-               && defined(_M_IX86)
-#      pragma optimize("", on)
-#endif
-#endif
-
 #if defined(CRC_GENERIC) && defined(CRC_CLMUL)
 typedef uint32_t (*crc32_func_type)(
                const uint8_t *buf, size_t size, uint32_t crc);
@@ -226,7 +114,7 @@ typedef uint32_t (*crc32_func_type)(
 static crc32_func_type
 crc32_resolve(void)
 {
-       return is_clmul_supported() ? &crc32_clmul : &crc32_generic;
+       return lzma_is_clmul_supported() ? &lzma_crc32_clmul : &crc32_generic;
 }
 
 #if defined(HAVE_FUNC_ATTRIBUTE_IFUNC) && defined(__clang__)
@@ -305,7 +193,7 @@ lzma_crc32(const uint8_t *buf, size_t size, uint32_t crc)
        return crc32_func(buf, size, crc);
 
 #elif defined(CRC_CLMUL)
-       return crc32_clmul(buf, size, crc);
+       return lzma_crc32_clmul(buf, size, crc);
 
 #else
        return crc32_generic(buf, size, crc);
index 79b662a4c25eb6a6f0ff2c5d79a7ff8fd29be0b3..07f4f5769931a523b157cf7ccebad064ac951269 100644 (file)
 #include "check.h"
 #include "crc_common.h"
 
+#ifdef CRC_GENERIC
+
 /////////////////////////////////
 // Generic slice-by-four CRC64 //
 /////////////////////////////////
 
-#ifdef CRC_GENERIC
-
-
 #ifdef WORDS_BIGENDIAN
 #      define A1(x) ((x) >> 56)
 #else
@@ -93,125 +92,6 @@ crc64_generic(const uint8_t *buf, size_t size, uint64_t crc)
 }
 #endif
 
-
-/////////////////////
-// x86 CLMUL CRC64 //
-/////////////////////
-
-#ifdef CRC_CLMUL
-
-#include <immintrin.h>
-
-
-/*
-// These functions were used to generate the constants
-// at the top of crc64_clmul().
-static uint64_t
-calc_lo(uint64_t poly)
-{
-       uint64_t a = poly;
-       uint64_t b = 0;
-
-       for (unsigned i = 0; i < 64; ++i) {
-               b = (b >> 1) | (a << 63);
-               a = (a >> 1) ^ (a & 1 ? poly : 0);
-       }
-
-       return b;
-}
-
-static uint64_t
-calc_hi(uint64_t poly, uint64_t a)
-{
-       for (unsigned i = 0; i < 64; ++i)
-               a = (a >> 1) ^ (a & 1 ? poly : 0);
-
-       return a;
-}
-*/
-
-
-// MSVC (VS2015 - VS2022) produces bad 32-bit x86 code from the CLMUL CRC
-// code when optimizations are enabled (release build). According to the bug
-// report, the ebx register is corrupted and the calculated result is wrong.
-// Trying to workaround the problem with "__asm mov ebx, ebx" didn't help.
-// The following pragma works and performance is still good. x86-64 builds
-// aren't affected by this problem.
-//
-// NOTE: Another pragma after the function restores the optimizations.
-// If the #if condition here is updated, the other one must be updated too.
-#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) \
-               && defined(_M_IX86)
-#      pragma optimize("g", off)
-#endif
-
-// EDG-based compilers (Intel's classic compiler and compiler for E2K) can
-// define __GNUC__ but the attribute must not be used with them.
-// The new Clang-based ICX needs the attribute.
-//
-// NOTE: Build systems check for this too, keep them in sync with this.
-#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__)
-__attribute__((__target__("ssse3,sse4.1,pclmul")))
-#endif
-static uint64_t
-crc64_clmul(const uint8_t *buf, size_t size, uint64_t crc)
-{
-       // The prototypes of the intrinsics use signed types while most of
-       // the values are treated as unsigned here. These warnings in this
-       // function have been checked and found to be harmless so silence them.
-#if TUKLIB_GNUC_REQ(4, 6) || defined(__clang__)
-#      pragma GCC diagnostic push
-#      pragma GCC diagnostic ignored "-Wsign-conversion"
-#      pragma GCC diagnostic ignored "-Wconversion"
-#endif
-
-#ifndef CRC_USE_GENERIC_FOR_SMALL_INPUTS
-       // The code assumes that there is at least one byte of input.
-       if (size == 0)
-               return crc;
-#endif
-
-       // const uint64_t poly = 0xc96c5795d7870f42; // CRC polynomial
-       const uint64_t p  = 0x92d8af2baf0e1e85; // (poly << 1) | 1
-       const uint64_t mu = 0x9c3e466c172963d5; // (calc_lo(poly) << 1) | 1
-       const uint64_t k2 = 0xdabe95afc7875f40; // calc_hi(poly, 1)
-       const uint64_t k1 = 0xe05dd497ca393ae4; // calc_hi(poly, k2)
-
-       const __m128i vfold8 = _mm_set_epi64x(p, mu);
-       const __m128i vfold16 = _mm_set_epi64x(k2, k1);
-
-       __m128i v0, v1, v2;
-
-#if defined(__i386__) || defined(_M_IX86)
-       crc_simd_body(buf,  size, &v0, &v1, vfold16, _mm_set_epi64x(0, ~crc));
-#else
-       // GCC and Clang would produce good code with _mm_set_epi64x
-       // but MSVC needs _mm_cvtsi64_si128 on x86-64.
-       crc_simd_body(buf,  size, &v0, &v1, vfold16, _mm_cvtsi64_si128(~crc));
-#endif
-
-       v1 = _mm_xor_si128(_mm_clmulepi64_si128(v0, vfold16, 0x10), v1);
-       v0 = _mm_clmulepi64_si128(v1, vfold8, 0x00);
-       v2 = _mm_clmulepi64_si128(v0, vfold8, 0x10);
-       v0 = _mm_xor_si128(_mm_xor_si128(v1, _mm_slli_si128(v0, 8)), v2);
-
-#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
-
-#if TUKLIB_GNUC_REQ(4, 6) || defined(__clang__)
-#      pragma GCC diagnostic pop
-#endif
-}
-#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) \
-               && defined(_M_IX86)
-#      pragma optimize("", on)
-#endif
-#endif
-
 #if defined(CRC_GENERIC) && defined(CRC_CLMUL)
 typedef uint64_t (*crc64_func_type)(
                const uint8_t *buf, size_t size, uint64_t crc);
@@ -227,7 +107,7 @@ typedef uint64_t (*crc64_func_type)(
 static crc64_func_type
 crc64_resolve(void)
 {
-       return is_clmul_supported() ? &crc64_clmul : &crc64_generic;
+       return lzma_is_clmul_supported() ? &lzma_crc64_clmul : &crc64_generic;
 }
 
 #if defined(HAVE_FUNC_ATTRIBUTE_IFUNC) && defined(__clang__)
@@ -322,7 +202,7 @@ lzma_crc64(const uint8_t *buf, size_t size, uint64_t crc)
        //
        // FIXME: Lookup table isn't currently omitted on 32-bit x86,
        // see crc64_table.c.
-       return crc64_clmul(buf, size, crc);
+       return lzma_crc64_clmul(buf, size, crc);
 
 #else
        return crc64_generic(buf, size, crc);
diff --git a/src/liblzma/check/crc_clmul.c b/src/liblzma/check/crc_clmul.c
new file mode 100644 (file)
index 0000000..f53fcc9
--- /dev/null
@@ -0,0 +1,414 @@
+///////////////////////////////////////////////////////////////////////////////
+//
+/// \file       crc_clmul.c
+/// \brief      CRC32 and CRC64 implementations using CLMUL instructions.
+///
+/// lzma_crc32_clmul() and lzma_crc64_clmul() use 32/64-bit x86
+/// SSSE3, SSE4.1, and CLMUL instructions. This is compatible with
+/// Elbrus 2000 (E2K) too.
+///
+/// They were derived from
+/// https://www.researchgate.net/publication/263424619_Fast_CRC_computation
+/// and the public domain code from https://github.com/rawrunprotected/crc
+/// (URLs were checked on 2023-10-14).
+///
+/// FIXME: Builds for 32-bit x86 use the assembly .S files by default
+/// unless configured with --disable-assembler. Even then the lookup table
+/// isn't omitted in crc64_table.c since it doesn't know that assembly
+/// code has been disabled.
+//
+//  Authors:    Ilya Kurdyukov
+//              Hans Jansen
+//              Lasse Collin
+//              Jia Tan
+//
+//
+//  This file has been put into the public domain.
+//  You can do whatever you want with this file.
+//
+///////////////////////////////////////////////////////////////////////////////
+
+#include "common.h"
+#include "crc_common.h"
+#include <immintrin.h>
+
+
+#define MASK_L(in, mask, r) r = _mm_shuffle_epi8(in, mask)
+
+#define MASK_H(in, mask, r) \
+       r = _mm_shuffle_epi8(in, _mm_xor_si128(mask, vsign))
+
+#define MASK_LH(in, mask, low, high) \
+       MASK_L(in, mask, low); \
+       MASK_H(in, mask, high)
+
+
+// MSVC (VS2015 - VS2022) produces bad 32-bit x86 code from the CLMUL CRC
+// code when optimizations are enabled (release build). According to the bug
+// report, the ebx register is corrupted and the calculated result is wrong.
+// Trying to workaround the problem with "__asm mov ebx, ebx" didn't help.
+// The following pragma works and performance is still good. x86-64 builds
+// aren't affected by this problem.
+//
+// NOTE: Another pragma after lzma_crc64_clmul() restores the optimizations.
+// If the #if condition here is updated, the other one must be updated too.
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) \
+               && defined(_M_IX86)
+#      pragma optimize("g", off)
+#endif
+
+
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__)
+__attribute__((__target__("ssse3,sse4.1,pclmul")))
+#endif
+#if lzma_has_attribute(__no_sanitize_address__)
+__attribute__((__no_sanitize_address__))
+#endif
+static inline void
+crc_simd_body(const uint8_t *buf, const size_t size, __m128i *v0, __m128i *v1,
+               const __m128i vfold16, const __m128i initial_crc)
+{
+       // Create a vector with 8-bit values 0 to 15. This is used to
+       // construct control masks for _mm_blendv_epi8 and _mm_shuffle_epi8.
+       const __m128i vramp = _mm_setr_epi32(
+                       0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c);
+
+       // This is used to inverse the control mask of _mm_shuffle_epi8
+       // so that bytes that wouldn't be picked with the original mask
+       // will be picked and vice versa.
+       const __m128i vsign = _mm_set1_epi8(-0x80);
+
+       // Memory addresses A to D and the distances between them:
+       //
+       //     A           B     C         D
+       //     [skip_start][size][skip_end]
+       //     [     size2      ]
+       //
+       // A and D are 16-byte aligned. B and C are 1-byte aligned.
+       // skip_start and skip_end are 0-15 bytes. size is at least 1 byte.
+       //
+       // A = aligned_buf will initially point to this address.
+       // B = The address pointed by the caller-supplied buf.
+       // C = buf + size == aligned_buf + size2
+       // D = buf + size + skip_end == aligned_buf + size2 + skip_end
+       const size_t skip_start = (size_t)((uintptr_t)buf & 15);
+       const size_t skip_end = (size_t)((0U - (uintptr_t)(buf + size)) & 15);
+       const __m128i *aligned_buf = (const __m128i *)(
+                       (uintptr_t)buf & ~(uintptr_t)15);
+
+       // If size2 <= 16 then the whole input fits into a single 16-byte
+       // vector. If size2 > 16 then at least two 16-byte vectors must
+       // be processed. If size2 > 16 && size <= 16 then there is only
+       // one 16-byte vector's worth of input but it is unaligned in memory.
+       //
+       // NOTE: There is no integer overflow here if the arguments
+       // are valid. If this overflowed, buf + size would too.
+       const size_t size2 = skip_start + size;
+
+       // Masks to be used with _mm_blendv_epi8 and _mm_shuffle_epi8:
+       // The first skip_start or skip_end bytes in the vectors will have
+       // the high bit (0x80) set. _mm_blendv_epi8 and _mm_shuffle_epi8
+       // will produce zeros for these positions. (Bitwise-xor of these
+       // masks with vsign will produce the opposite behavior.)
+       const __m128i mask_start
+                       = _mm_sub_epi8(vramp, _mm_set1_epi8((char)skip_start));
+       const __m128i mask_end
+                       = _mm_sub_epi8(vramp, _mm_set1_epi8((char)skip_end));
+
+       // Get the first 1-16 bytes into data0. If loading less than 16
+       // bytes, the bytes are loaded to the high bits of the vector and
+       // the least significant positions are filled with zeros.
+       const __m128i data0 = _mm_blendv_epi8(_mm_load_si128(aligned_buf),
+                       _mm_setzero_si128(), mask_start);
+       aligned_buf++;
+
+       __m128i v2, v3;
+
+#ifndef CRC_USE_GENERIC_FOR_SMALL_INPUTS
+       if (size <= 16) {
+               // Right-shift initial_crc by 1-16 bytes based on "size"
+               // and store the result in v1 (high bytes) and v0 (low bytes).
+               //
+               // NOTE: The highest 8 bytes of initial_crc are zeros so
+               // v1 will be filled with zeros if size >= 8. The highest
+               // 8 bytes of v1 will always become zeros.
+               //
+               // [      v1      ][      v0      ]
+               //  [ initial_crc  ]                  size == 1
+               //   [ initial_crc  ]                 size == 2
+               //                [ initial_crc  ]    size == 15
+               //                 [ initial_crc  ]   size == 16 (all in v0)
+               const __m128i mask_low = _mm_add_epi8(
+                               vramp, _mm_set1_epi8((char)(size - 16)));
+               MASK_LH(initial_crc, mask_low, *v0, *v1);
+
+               if (size2 <= 16) {
+                       // There are 1-16 bytes of input and it is all
+                       // in data0. Copy the input bytes to v3. If there
+                       // are fewer than 16 bytes, the low bytes in v3
+                       // will be filled with zeros. That is, the input
+                       // bytes are stored to the same position as
+                       // (part of) initial_crc is in v0.
+                       MASK_L(data0, mask_end, v3);
+               } else {
+                       // There are 2-16 bytes of input but not all bytes
+                       // are in data0.
+                       const __m128i data1 = _mm_load_si128(aligned_buf);
+
+                       // Collect the 2-16 input bytes from data0 and data1
+                       // to v2 and v3, and bitwise-xor them with the
+                       // low bits of initial_crc in v0. Note that the
+                       // the second xor is below this else-block as it
+                       // is shared with the other branch.
+                       MASK_H(data0, mask_end, v2);
+                       MASK_L(data1, mask_end, v3);
+                       *v0 = _mm_xor_si128(*v0, v2);
+               }
+
+               *v0 = _mm_xor_si128(*v0, v3);
+               *v1 = _mm_alignr_epi8(*v1, *v0, 8);
+       } else
+#endif
+       {
+               // There is more than 16 bytes of input.
+               const __m128i data1 = _mm_load_si128(aligned_buf);
+               const __m128i *end = (const __m128i*)(
+                               (const char *)aligned_buf - 16 + size2);
+               aligned_buf++;
+
+               MASK_LH(initial_crc, mask_start, *v0, *v1);
+               *v0 = _mm_xor_si128(*v0, data0);
+               *v1 = _mm_xor_si128(*v1, data1);
+
+               while (aligned_buf < end) {
+                       *v1 = _mm_xor_si128(*v1, _mm_clmulepi64_si128(
+                                       *v0, vfold16, 0x00));
+                       *v0 = _mm_xor_si128(*v1, _mm_clmulepi64_si128(
+                                       *v0, vfold16, 0x11));
+                       *v1 = _mm_load_si128(aligned_buf++);
+               }
+
+               if (aligned_buf != end) {
+                       MASK_H(*v0, mask_end, v2);
+                       MASK_L(*v0, mask_end, *v0);
+                       MASK_L(*v1, mask_end, v3);
+                       *v1 = _mm_or_si128(v2, v3);
+               }
+
+               *v1 = _mm_xor_si128(*v1, _mm_clmulepi64_si128(
+                               *v0, vfold16, 0x00));
+               *v0 = _mm_xor_si128(*v1, _mm_clmulepi64_si128(
+                               *v0, vfold16, 0x11));
+               *v1 = _mm_srli_si128(*v0, 8);
+       }
+}
+
+
+/////////////////////
+// x86 CLMUL CRC32 //
+/////////////////////
+
+/*
+// These functions were used to generate the constants
+// at the top of lzma_crc32_clmul().
+static uint64_t
+calc_lo(uint64_t p, uint64_t a, int n)
+{
+       uint64_t b = 0; int i;
+       for (i = 0; i < n; i++) {
+               b = b >> 1 | (a & 1) << (n - 1);
+               a = (a >> 1) ^ ((0 - (a & 1)) & p);
+       }
+       return b;
+}
+
+// same as ~crc(&a, sizeof(a), ~0)
+static uint64_t
+calc_hi(uint64_t p, uint64_t a, int n)
+{
+       int i;
+       for (i = 0; i < n; i++)
+               a = (a >> 1) ^ ((0 - (a & 1)) & p);
+       return a;
+}
+*/
+
+#ifdef HAVE_CHECK_CRC32
+
+// EDG-based compilers (Intel's classic compiler and compiler for E2K) can
+// define __GNUC__ but the attribute must not be used with them.
+// The new Clang-based ICX needs the attribute.
+//
+// NOTE: Build systems check for this too, keep them in sync with this.
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__)
+__attribute__((__target__("ssse3,sse4.1,pclmul")))
+#endif
+extern uint32_t
+lzma_crc32_clmul(const uint8_t *buf, size_t size, uint32_t crc)
+{
+#ifndef CRC_USE_GENERIC_FOR_SMALL_INPUTS
+       // The code assumes that there is at least one byte of input.
+       if (size == 0)
+               return crc;
+#endif
+
+       // uint32_t poly = 0xedb88320;
+       const int64_t p = 0x1db710640; // p << 1
+       const int64_t mu = 0x1f7011641; // calc_lo(p, p, 32) << 1 | 1
+       const int64_t k5 = 0x163cd6124; // calc_hi(p, p, 32) << 1
+       const int64_t k4 = 0x0ccaa009e; // calc_hi(p, p, 64) << 1
+       const int64_t k3 = 0x1751997d0; // calc_hi(p, p, 128) << 1
+
+       const __m128i vfold4 = _mm_set_epi64x(mu, p);
+       const __m128i vfold8 = _mm_set_epi64x(0, k5);
+       const __m128i vfold16 = _mm_set_epi64x(k4, k3);
+
+       __m128i v0, v1, v2;
+
+       crc_simd_body(buf,  size, &v0, &v1, vfold16,
+                       _mm_cvtsi32_si128((int32_t)~crc));
+
+       v1 = _mm_xor_si128(
+                       _mm_clmulepi64_si128(v0, vfold16, 0x10), v1); // xxx0
+       v2 = _mm_shuffle_epi32(v1, 0xe7); // 0xx0
+       v0 = _mm_slli_epi64(v1, 32);  // [0]
+       v0 = _mm_clmulepi64_si128(v0, vfold8, 0x00);
+       v0 = _mm_xor_si128(v0, v2);   // [1] [2]
+       v2 = _mm_clmulepi64_si128(v0, vfold4, 0x10);
+       v2 = _mm_clmulepi64_si128(v2, vfold4, 0x00);
+       v0 = _mm_xor_si128(v0, v2);   // [2]
+       return ~(uint32_t)_mm_extract_epi32(v0, 2);
+}
+#endif // HAVE_CHECK_CRC32
+
+
+/////////////////////
+// x86 CLMUL CRC64 //
+/////////////////////
+
+/*
+// These functions were used to generate the constants
+// at the top of lzma_crc64_clmul().
+static uint64_t
+calc_lo(uint64_t poly)
+{
+       uint64_t a = poly;
+       uint64_t b = 0;
+
+       for (unsigned i = 0; i < 64; ++i) {
+               b = (b >> 1) | (a << 63);
+               a = (a >> 1) ^ (a & 1 ? poly : 0);
+       }
+
+       return b;
+}
+
+static uint64_t
+calc_hi(uint64_t poly, uint64_t a)
+{
+       for (unsigned i = 0; i < 64; ++i)
+               a = (a >> 1) ^ (a & 1 ? poly : 0);
+
+       return a;
+}
+*/
+
+#ifdef HAVE_CHECK_CRC64
+
+#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__)
+__attribute__((__target__("ssse3,sse4.1,pclmul")))
+#endif
+extern uint64_t
+lzma_crc64_clmul(const uint8_t *buf, size_t size, uint64_t crc)
+{
+#ifndef CRC_USE_GENERIC_FOR_SMALL_INPUTS
+       // The code assumes that there is at least one byte of input.
+       if (size == 0)
+               return crc;
+#endif
+
+       // const uint64_t poly = 0xc96c5795d7870f42; // CRC polynomial
+       const uint64_t p  = 0x92d8af2baf0e1e85; // (poly << 1) | 1
+       const uint64_t mu = 0x9c3e466c172963d5; // (calc_lo(poly) << 1) | 1
+       const uint64_t k2 = 0xdabe95afc7875f40; // calc_hi(poly, 1)
+       const uint64_t k1 = 0xe05dd497ca393ae4; // calc_hi(poly, k2)
+
+       const __m128i vfold8 = _mm_set_epi64x((int64_t)p, (int64_t)mu);
+       const __m128i vfold16 = _mm_set_epi64x((int64_t)k2, (int64_t)k1);
+
+       __m128i v0, v1, v2;
+
+#if defined(__i386__) || defined(_M_IX86)
+       crc_simd_body(buf,  size, &v0, &v1, vfold16,
+                       _mm_set_epi64x(0, (int64_t)~crc));
+#else
+       // GCC and Clang would produce good code with _mm_set_epi64x
+       // but MSVC needs _mm_cvtsi64_si128 on x86-64.
+       crc_simd_body(buf,  size, &v0, &v1, vfold16,
+                       _mm_cvtsi64_si128((int64_t)~crc));
+#endif
+
+       v1 = _mm_xor_si128(_mm_clmulepi64_si128(v0, vfold16, 0x10), v1);
+       v0 = _mm_clmulepi64_si128(v1, vfold8, 0x00);
+       v2 = _mm_clmulepi64_si128(v0, vfold8, 0x10);
+       v0 = _mm_xor_si128(_mm_xor_si128(v1, _mm_slli_si128(v0, 8)), v2);
+
+#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 // HAVE_CHECK_CRC64
+
+
+#if defined(_MSC_VER) && !defined(__INTEL_COMPILER) && !defined(__clang__) \
+               && defined(_M_IX86)
+#      pragma optimize("", on)
+#endif
+
+
+////////////////////////
+// Detect CPU support //
+////////////////////////
+
+extern bool
+lzma_is_clmul_supported(void)
+{
+       int success = 1;
+       uint32_t r[4]; // eax, ebx, ecx, edx
+
+#if defined(_MSC_VER)
+       // This needs <intrin.h> with MSVC. ICC has it as a built-in
+       // on all platforms.
+       __cpuid(r, 1);
+#elif defined(HAVE_CPUID_H)
+       // Compared to just using __asm__ to run CPUID, this also checks
+       // that CPUID is supported and saves and restores ebx as that is
+       // needed with GCC < 5 with position-independent code (PIC).
+       success = __get_cpuid(1, &r[0], &r[1], &r[2], &r[3]);
+#else
+       // Just a fallback that shouldn't be needed.
+       __asm__("cpuid\n\t"
+                       : "=a"(r[0]), "=b"(r[1]), "=c"(r[2]), "=d"(r[3])
+                       : "a"(1), "c"(0));
+#endif
+
+       // Returns true if these are supported:
+       // CLMUL (bit 1 in ecx)
+       // SSSE3 (bit 9 in ecx)
+       // SSE4.1 (bit 19 in ecx)
+       const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19);
+       return success && (r[2] & ecx_mask) == ecx_mask;
+
+       // Alternative methods that weren't used:
+       //   - ICC's _may_i_use_cpu_feature: the other methods should work too.
+       //   - GCC >= 6 / Clang / ICX __builtin_cpu_supports("pclmul")
+       //
+       // CPUID decding is needed with MSVC anyway and older GCC. This keeps
+       // the feature checks in the build system simpler too. The nice thing
+       // about __builtin_cpu_supports would be that it generates very short
+       // code as is it only reads a variable set at startup but a few bytes
+       // doesn't matter here.
+}
index 55fdd55e6eaee8423549458d36920577d0c40b2e..b0176adcccf714274117ef25a2b86427e0f7a90b 100644 (file)
@@ -6,6 +6,7 @@
 //  Authors:    Lasse Collin
 //              Ilya Kurdyukov
 //              Hans Jansen
+//              Jia Tan
 //
 //  This file has been put into the public domain.
 //  You can do whatever you want with this file.
 #      endif
 #endif
 
-////////////////////////
-// Detect CPU support //
-////////////////////////
+/// Detect at runtime if the CPU supports the x86 CLMUL instruction when
+/// both the generic and CLMUL implementations are built.
+extern bool lzma_is_clmul_supported(void);
 
-#if defined(CRC_GENERIC) && defined(CRC_CLMUL)
-static inline bool
-is_clmul_supported(void)
-{
-       int success = 1;
-       uint32_t r[4]; // eax, ebx, ecx, edx
+/// CRC32 implemented with the x86 CLMUL instruction.
+extern uint32_t lzma_crc32_clmul(const uint8_t *buf, size_t size,
+               uint32_t crc);
 
-#if defined(_MSC_VER)
-       // This needs <intrin.h> with MSVC. ICC has it as a built-in
-       // on all platforms.
-       __cpuid(r, 1);
-#elif defined(HAVE_CPUID_H)
-       // Compared to just using __asm__ to run CPUID, this also checks
-       // that CPUID is supported and saves and restores ebx as that is
-       // needed with GCC < 5 with position-independent code (PIC).
-       success = __get_cpuid(1, &r[0], &r[1], &r[2], &r[3]);
-#else
-       // Just a fallback that shouldn't be needed.
-       __asm__("cpuid\n\t"
-                       : "=a"(r[0]), "=b"(r[1]), "=c"(r[2]), "=d"(r[3])
-                       : "a"(1), "c"(0));
-#endif
-
-       // Returns true if these are supported:
-       // CLMUL (bit 1 in ecx)
-       // SSSE3 (bit 9 in ecx)
-       // SSE4.1 (bit 19 in ecx)
-       const uint32_t ecx_mask = (1 << 1) | (1 << 9) | (1 << 19);
-       return success && (r[2] & ecx_mask) == ecx_mask;
-
-       // Alternative methods that weren't used:
-       //   - ICC's _may_i_use_cpu_feature: the other methods should work too.
-       //   - GCC >= 6 / Clang / ICX __builtin_cpu_supports("pclmul")
-       //
-       // CPUID decding is needed with MSVC anyway and older GCC. This keeps
-       // the feature checks in the build system simpler too. The nice thing
-       // about __builtin_cpu_supports would be that it generates very short
-       // code as is it only reads a variable set at startup but a few bytes
-       // doesn't matter here.
-}
-#endif
-
-
-#define MASK_L(in, mask, r) r = _mm_shuffle_epi8(in, mask);
-#define MASK_H(in, mask, r) \
-       r = _mm_shuffle_epi8(in, _mm_xor_si128(mask, vsign));
-#define MASK_LH(in, mask, low, high) \
-       MASK_L(in, mask, low) MASK_H(in, mask, high)
-
-#ifdef CRC_CLMUL
-
-#include <immintrin.h>
-
-
-#if (defined(__GNUC__) || defined(__clang__)) && !defined(__EDG__)
-__attribute__((__target__("ssse3,sse4.1,pclmul")))
-#endif
-#if lzma_has_attribute(__no_sanitize_address__)
-__attribute__((__no_sanitize_address__))
-#endif
-static inline void
-crc_simd_body(const uint8_t *buf, size_t size, __m128i *v0, __m128i *v1,
-               __m128i vfold16, __m128i crc2vec)
-{
-#if TUKLIB_GNUC_REQ(4, 6) || defined(__clang__)
-#      pragma GCC diagnostic push
-#      pragma GCC diagnostic ignored "-Wsign-conversion"
-#endif
-       // Memory addresses A to D and the distances between them:
-       //
-       //     A           B     C         D
-       //     [skip_start][size][skip_end]
-       //     [     size2      ]
-       //
-       // A and D are 16-byte aligned. B and C are 1-byte aligned.
-       // skip_start and skip_end are 0-15 bytes. size is at least 1 byte.
-       //
-       // A = aligned_buf will initially point to this address.
-       // B = The address pointed by the caller-supplied buf.
-       // C = buf + size == aligned_buf + size2
-       // D = buf + size + skip_end == aligned_buf + size2 + skip_end
-       uintptr_t skip_start = (uintptr_t)buf & 15;
-       uintptr_t skip_end = -(uintptr_t)(buf + size) & 15;
-
-       // Create a vector with 8-bit values 0 to 15.
-       // This is used to construct control masks
-       // for _mm_blendv_epi8 and _mm_shuffle_epi8.
-       __m128i vramp = _mm_setr_epi32(
-                       0x03020100, 0x07060504, 0x0b0a0908, 0x0f0e0d0c);
-
-       // This is used to inverse the control mask of _mm_shuffle_epi8
-       // so that bytes that wouldn't be picked with the original mask
-       // will be picked and vice versa.
-       __m128i vsign = _mm_set1_epi8(-0x80);
-
-       // Masks to be used with _mm_blendv_epi8 and _mm_shuffle_epi8
-       // The first skip_start or skip_end bytes in the vectors will hav
-       // the high bit (0x80) set. _mm_blendv_epi8 and _mm_shuffle_epi
-       // will produce zeros for these positions. (Bitwise-xor of thes
-       // masks with vsign will produce the opposite behavior.)
-       __m128i mask_start = _mm_sub_epi8(vramp, _mm_set1_epi8(skip_start));
-       __m128i mask_end = _mm_sub_epi8(vramp, _mm_set1_epi8(skip_end));
-
-       // If size2 <= 16 then the whole input fits into a single 16-byte
-       // vector. If size2 > 16 then at least two 16-byte vectors must
-       // be processed. If size2 > 16 && size <= 16 then there is only
-       // one 16-byte vector's worth of input but it is unaligned in memory.
-       //
-       // NOTE: There is no integer overflow here if the arguments
-       // are valid. If this overflowed, buf + size would too.
-       uintptr_t size2 = skip_start + size;
-       const __m128i *aligned_buf = (const __m128i*)((uintptr_t)buf & -16);
-       __m128i v2, v3, vcrc, data0;
-
-       vcrc = crc2vec;
-
-       // Get the first 1-16 bytes into data0. If loading less than 16
-       // bytes, the bytes are loaded to the high bits of the vector and
-       // the least significant positions are filled with zeros.
-       data0 = _mm_load_si128(aligned_buf);
-       data0 = _mm_blendv_epi8(data0, _mm_setzero_si128(), mask_start);
-       aligned_buf++;
-       if (size2 <= 16) {
-               //  There are 1-16 bytes of input and it is all
-               //  in data0. Copy the input bytes to v3. If there
-               //  are fewer than 16 bytes, the low bytes in v3
-               //  will be filled with zeros. That is, the input
-               //  bytes are stored to the same position as
-               //  (part of) initial_crc is in v0.
-               __m128i mask_low = _mm_add_epi8(
-                               vramp, _mm_set1_epi8(size - 16));
-               MASK_LH(vcrc, mask_low, *v0, *v1)
-               MASK_L(data0, mask_end, v3)
-               *v0 = _mm_xor_si128(*v0, v3);
-               *v1 = _mm_alignr_epi8(*v1, *v0, 8);
-       } else {
-               __m128i data1 = _mm_load_si128(aligned_buf);
-               if (size <= 16) {
-                       //  Collect the 2-16 input bytes from data0 and data1
-                       //  to v2 and v3, and bitwise-xor them with the
-                       //  low bits of initial_crc in v0. Note that the
-                       //  the second xor is below this else-block as it
-                       //  is shared with the other branch.
-                       __m128i mask_low = _mm_add_epi8(
-                                       vramp, _mm_set1_epi8(size - 16));
-                       MASK_LH(vcrc, mask_low, *v0, *v1);
-                       MASK_H(data0, mask_end, v2)
-                       MASK_L(data1, mask_end, v3)
-                       *v0 = _mm_xor_si128(*v0, v2);
-                       *v0 = _mm_xor_si128(*v0, v3);
-
-                       *v1 = _mm_alignr_epi8(*v1, *v0, 8);
-               } else {
-                       const __m128i *end = (const __m128i*)(
-                                       (char*)aligned_buf++ - 16 + size2);
-                       MASK_LH(vcrc, mask_start, *v0, *v1)
-                       *v0 = _mm_xor_si128(*v0, data0);
-                       *v1 = _mm_xor_si128(*v1, data1);
-                       while (aligned_buf < end) {
-                                *v1 = _mm_xor_si128(*v1, _mm_clmulepi64_si128(*v0, vfold16, 0x00)); \
-                               *v0 = _mm_xor_si128(*v1, _mm_clmulepi64_si128(*v0, vfold16, 0x11));
-                                *v1 = _mm_load_si128(aligned_buf++);
-                        }
-
-                       if (aligned_buf != end) {
-                               MASK_H(*v0, mask_end, v2)
-                               MASK_L(*v0, mask_end, *v0)
-                               MASK_L(*v1, mask_end, v3)
-                               *v1 = _mm_or_si128(v2, v3);
-                       }
-                       *v1 = _mm_xor_si128(*v1, _mm_clmulepi64_si128(*v0, vfold16, 0x00));
-                       *v0 = _mm_xor_si128(*v1, _mm_clmulepi64_si128(*v0, vfold16, 0x11));
-
-                       *v1 = _mm_srli_si128(*v0, 8);
-               }
-       }
-}
-#endif
+/// CRC64 implemented with the x86 CLMUL instruction.
+extern uint64_t lzma_crc64_clmul(const uint8_t *buf, size_t size,
+               uint64_t crc);