x86: new optimized CRC functions, with VPCLMULQDQ support
Subsystems (2)
cover x86: new optimized CRC functions, with VPCLMULQDQ support
This patchset is also available in git via:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git crc-x86-v1
This patchset applies on top of my other recent CRC patchsets
https://lore.kernel.org/r/[email protected]/ and
https://lore.kernel.org/r/[email protected]/ . Consider it a preview for what may be coming next, as my priority is getting those two other patchsets merged first.
This patchset adds a new assembly macro that expands into the body of a CRC function for x86 for the specified number of bits, bit order, vector length, and AVX level. There's also a new script that generates the constants needed by this function, given a CRC generator polynomial.
This approach allows easily wiring up an x86-optimized implementation of any variant of CRC-8, CRC-16, CRC-32, or CRC-64, including full support for VPCLMULQDQ. On long messages the resulting functions are up to 4x faster than the existing PCLMULQDQ optimized functions when they exist, or up to 29x faster than the existing table-based functions.
This patchset starts by wiring up the new macro for crc32_le, crc_t10dif, and crc32_be. Later I'd also like to wire up crc64_be and crc64_rocksoft, once the design of the library functions for those has been fixed to be like what I'm doing for crc32* and crc_t10dif.
A similar approach of sharing code between CRC variants, and vector lengths when applicable, should work for other architectures. The CRC constant generation script should be mostly reusable.
Eric Biggers (6):
x86: move zmm exclusion list into CPU feature flag scripts/crc: add gen-crc-consts.py
x86/crc: add "template" for [V]PCLMULQDQ based CRC functions x86/crc32: implement crc32_le using new template x86/crc-t10dif: implement crc_t10dif using new template x86/crc32: implement crc32_be using new template
arch/x86/Kconfig | 2 +- arch/x86/crypto/aesni-intel_glue.c | 22 +- arch/x86/include/asm/cpufeatures.h | 1 + arch/x86/kernel/cpu/intel.c | 22 + arch/x86/lib/Makefile | 2 +- arch/x86/lib/crc-pclmul-consts.h | 148 ++++++ arch/x86/lib/crc-pclmul-template-glue.h | 84 ++++ arch/x86/lib/crc-pclmul-template.S | 588 ++++++++++++++++++++++++ arch/x86/lib/crc-t10dif-glue.c | 22 +- arch/x86/lib/crc16-msb-pclmul.S | 6 + arch/x86/lib/crc32-glue.c | 38 +- arch/x86/lib/crc32-pclmul.S | 220 +-------- arch/x86/lib/crct10dif-pcl-asm_64.S | 332 ------------- scripts/crc/gen-crc-consts.py | 207 +++++++++ 14 files changed, 1087 insertions(+), 607 deletions(-) create mode 100644 arch/x86/lib/crc-pclmul-consts.h create mode 100644 arch/x86/lib/crc-pclmul-template-glue.h create mode 100644 arch/x86/lib/crc-pclmul-template.S create mode 100644 arch/x86/lib/crc16-msb-pclmul.S delete mode 100644 arch/x86/lib/crct10dif-pcl-asm_64.S create mode 100755 scripts/crc/gen-crc-consts.py
Signature · 1 line
2.47.0
PATCH 1/6 x86: move zmm exclusion list into CPU feature flag
From: Eric Biggers <[email protected]>
Lift zmm_exclusion_list in aesni-intel_glue.c into the x86 CPU setup code, and add a new x86 CPU feature flag X86_FEATURE_PREFER_YMM that is set when the CPU is on this list.
This allows other code in arch/x86/, such as the CRC library code, to apply the same exclusion list when deciding whether to execute 256-bit or 512-bit optimized functions.
Note that full AVX512 support including zmm registers is still exposed to userspace and is still supported for in-kernel use. This flag just indicates whether in-kernel code should prefer to use ymm registers.
--- arch/x86/crypto/aesni-intel_glue.c | 22 +--------------------- arch/x86/include/asm/cpufeatures.h | 1 + arch/x86/kernel/cpu/intel.c | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 21 deletions(-)
arch/x86/crypto/aesni-intel_glue.c +1 -21
@@ -1534,30 +1534,10 @@ DEFINE_GCM_ALGS(vaes_avx10_256, FLAG_AVX10_256,DEFINE_GCM_ALGS (vaes_avx10_512,FLAG_AVX10_512 ,"generic-gcm-vaes-avx10_512" ,"rfc4106-gcm-vaes-avx10_512" ,AES_GCM_KEY_AVX10_SIZE ,800 );#endif /* CONFIG_AS_VAES && CONFIG_AS_VPCLMULQDQ */ -/* -* This is a list of CPU models that are known to suffer from downclocking when -* zmm registers (512-bit vectors) are used. On these CPUs, the AES mode -* implementations with zmm registers won't be used by default. Implementations -* with ymm registers (256-bit vectors) will be used by default instead. -*/ -static const struct x86_cpu_id zmm_exclusion_list[] = { -X86_MATCH_VFM (INTEL_SKYLAKE_X ,0 ), -X86_MATCH_VFM (INTEL_ICELAKE_X ,0 ), -X86_MATCH_VFM (INTEL_ICELAKE_D ,0 ), -X86_MATCH_VFM (INTEL_ICELAKE ,0 ), -X86_MATCH_VFM (INTEL_ICELAKE_L ,0 ), -X86_MATCH_VFM (INTEL_ICELAKE_NNPI ,0 ), -X86_MATCH_VFM (INTEL_TIGERLAKE_L ,0 ), -X86_MATCH_VFM (INTEL_TIGERLAKE ,0 ), -/* Allow Rocket Lake and later, and Sapphire Rapids and later. */ -/* Also allow AMD CPUs (starting with Zen 4, the first with AVX-512). */ - {}, -}; -static int __init register_avx_algs (void ) {int err;if (!boot_cpu_has (X86_FEATURE_AVX ))
@@ -1598,11 +1578,11 @@ static int __init register_avx_algs(void)ARRAY_SIZE (aes_gcm_algs_vaes_avx10_256), aes_gcm_simdalgs_vaes_avx10_256);if (err)return err; -if (x86_match_cpu (zmm_exclusion_list)) { +if (boot_cpu_has (X86_FEATURE_PREFER_YMM )) {int i; aes_xts_alg_vaes_avx10_512.base.cra_priority =1 ;for (i =0 ; i <ARRAY_SIZE (aes_gcm_algs_vaes_avx10_512); i++) aes_gcm_algs_vaes_avx10_512[i].base.cra_priority =1 ;
arch/x86/include/asm/cpufeatures.h +1 -0
@@ -477,10 +477,11 @@#define X86_FEATURE_CLEAR_BHB_HW (21*32+ 3)/* BHI_DIS_S HW control enabled */ #define X86_FEATURE_CLEAR_BHB_LOOP_ON_VMEXIT (21*32+ 4)/* Clear branch history at vmexit using SW loop */ #define X86_FEATURE_AMD_FAST_CPPC (21*32 + 5)/* Fast CPPC */ #define X86_FEATURE_AMD_HETEROGENEOUS_CORES (21*32 + 6)/* Heterogeneous Core Topology */ #define X86_FEATURE_AMD_WORKLOAD_CLASS (21*32 + 7)/* Workload Classification */ +#define X86_FEATURE_PREFER_YMM (21*32 + 8)/* Avoid zmm registers due to downclocking */ /* * BUG word(s) */ #define X86_BUG (x) (NCAPINTS*32 + (x))
arch/x86/kernel/cpu/intel.c +22 -0
@@ -519,10 +519,29 @@ static void init_intel_misc_features(struct cpuinfo_x86 *c) msr =this_cpu_read (msr_misc_features_shadow);wrmsrl (MSR_MISC_FEATURES_ENABLES , msr); } +/* +* This is a list of Intel CPUs that are known to suffer from downclocking when +* zmm registers (512-bit vectors) are used. On these CPUs, when the kernel +* executes SIMD-optimized code such as cryptography functions or CRCs, it +* should prefer 256-bit (ymm) code to 512-bit (zmm) code. +*/ +static const struct x86_cpu_id zmm_exclusion_list[] = { +X86_MATCH_VFM (INTEL_SKYLAKE_X ,0 ), +X86_MATCH_VFM (INTEL_ICELAKE_X ,0 ), +X86_MATCH_VFM (INTEL_ICELAKE_D ,0 ), +X86_MATCH_VFM (INTEL_ICELAKE ,0 ), +X86_MATCH_VFM (INTEL_ICELAKE_L ,0 ), +X86_MATCH_VFM (INTEL_ICELAKE_NNPI ,0 ), +X86_MATCH_VFM (INTEL_TIGERLAKE_L ,0 ), +X86_MATCH_VFM (INTEL_TIGERLAKE ,0 ), +/* Allow Rocket Lake and later, and Sapphire Rapids and later. */ + {}, +}; +static void init_intel (struct cpuinfo_x86 *c) {early_init_intel (c);intel_workarounds (c);
@@ -602,10 +621,13 @@ static void init_intel(struct cpuinfo_x86 *c)set_cpu_cap (c,X86_FEATURE_P4 );if (c->x86 ==6 )set_cpu_cap (c,X86_FEATURE_P3 );#endif +if (x86_match_cpu (zmm_exclusion_list)) +set_cpu_cap (c,X86_FEATURE_PREFER_YMM ); +/* Work around errata */ srat_detect_node (c);init_ia32_feat_ctl (c);
Signature · 1 line
2.47.0
Re: [PATCH 1/6] x86: move zmm exclusion list into CPU feature flag
Quoted 14 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Lift
zmm_exclusion_listinaesni-intel_glue.cinto the x86 CPU setup code, and add a new x86 CPU feature flagX86_FEATURE_PREFER_YMMthat is set when the CPU is on this list.This allows other code in
arch/x86/, such as the CRC library code, to apply the same exclusion list when deciding whether to execute 256-bit or 512-bit optimized functions.Note that full AVX512 support including zmm registers is still exposed to userspace and is still supported for in-kernel use. This flag just indicates whether in-kernel code should prefer to use ymm registers.
Quoted 6 lines
Signed-off-by Eric Biggers <[email protected]>--- arch/x86/crypto/aesni-intel_glue.c | 22 +--------------------- arch/x86/include/asm/cpufeatures.h | 1 + arch/x86/kernel/cpu/intel.c | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 21 deletions(-)
I suppose you'd like to carry this in the crypto tree?
Quoted 6 lines
+/*
+ * This is a list of Intel CPUs that are known to suffer from downclocking when
+ * zmm registers (512-bit vectors) are used. On these CPUs, when the kernel
+ * executes SIMD-optimized code such as cryptography functions or CRCs, it
+ * should prefer 256-bit (ymm) code to 512-bit (zmm) code.
+ */
One speling nit, could you please do:
s/ymm/YMM
s/zmm/ZMM
... to make it consistent with how the rest of the x86 code is capitalizing the names of FPU vector register classes. Just like we are capitalizing CPU and CRC properly ;-)
Thanks,
Ingo
Re: [PATCH 1/6] x86: move zmm exclusion list into CPU feature flag
Quoted 24 lines · Ingo Molnar
Quoted 14 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Lift
zmm_exclusion_listinaesni-intel_glue.cinto the x86 CPU setup code, and add a new x86 CPU feature flagX86_FEATURE_PREFER_YMMthat is set when the CPU is on this list.This allows other code in
arch/x86/, such as the CRC library code, to apply the same exclusion list when deciding whether to execute 256-bit or 512-bit optimized functions.Note that full AVX512 support including zmm registers is still exposed to userspace and is still supported for in-kernel use. This flag just indicates whether in-kernel code should prefer to use ymm registers.
Quoted 6 lines
Signed-off-by Eric Biggers <[email protected]>--- arch/x86/crypto/aesni-intel_glue.c | 22 +--------------------- arch/x86/include/asm/cpufeatures.h | 1 + arch/x86/kernel/cpu/intel.c | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 21 deletions(-)
Quoted 3 lines
Acked-by Ingo Molnar <[email protected]>I suppose you'd like to carry this in the crypto tree?
I am planning to carry CRC-related patches myself
(https://lore.kernel.org/lkml/[email protected]/).
Quoted 8 lines
Quoted 7 lines
+/*
+ * This is a list of Intel CPUs that are known to suffer from downclocking when
+ * zmm registers (512-bit vectors) are used. On these CPUs, when the kernel
+ * executes SIMD-optimized code such as cryptography functions or CRCs, it
+ * should prefer 256-bit (ymm) code to 512-bit (zmm) code.
+ */
Quoted 9 lines
One speling nit, could you please do:
s/ymm/YMM
s/zmm/ZMM... to make it consistent with how the rest of the x86 code is capitalizing the names of FPU vector register classes. Just like we are capitalizing CPU and CRC properly ;-)
Will do, thanks.
- Eric
Re: [PATCH 1/6] x86: move zmm exclusion list into CPU feature flag
Quoted 25 lines · Eric Biggers
Quoted 24 lines · Ingo Molnar
Quoted 20 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Lift
zmm_exclusion_listinaesni-intel_glue.cinto the x86 CPU setup code, and add a new x86 CPU feature flagX86_FEATURE_PREFER_YMMthat is set when the CPU is on this list.This allows other code in
arch/x86/, such as the CRC library code, to apply the same exclusion list when deciding whether to execute 256-bit or 512-bit optimized functions.Note that full AVX512 support including zmm registers is still exposed to userspace and is still supported for in-kernel use. This flag just indicates whether in-kernel code should prefer to use ymm registers.
Signed-off-by Eric Biggers <[email protected]>--- arch/x86/crypto/aesni-intel_glue.c | 22 +--------------------- arch/x86/include/asm/cpufeatures.h | 1 + arch/x86/kernel/cpu/intel.c | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+), 21 deletions(-)
Quoted 6 lines
Quoted 3 lines
Acked-by Ingo Molnar <[email protected]>I suppose you'd like to carry this in the crypto tree?
I am planning to carry CRC-related patches myself
(https://lore.kernel.org/lkml/[email protected]/).
Sounds good!
Quoted 14 lines
Quoted 14 lines
Quoted 8 lines
+/*
+ * This is a list of Intel CPUs that are known to suffer from downclocking when
+ * zmm registers (512-bit vectors) are used. On these CPUs, when the kernel
+ * executes SIMD-optimized code such as cryptography functions or CRCs, it
+ * should prefer 256-bit (ymm) code to 512-bit (zmm) code.
+ */One speling nit, could you please do:
s/ymm/YMM
s/zmm/ZMM
Quoted 6 lines
Quoted 4 lines
... to make it consistent with how the rest of the x86 code is capitalizing the names of FPU vector register classes. Just like we are capitalizing CPU and CRC properly ;-)
Will do, thanks.
Thank you!
Ingo
PATCH 2/6 scripts/crc: add gen-crc-consts.py
In reply to Eric BiggersFrom: Eric Biggers <[email protected]>
Add a Python script that generates constants for computing the given CRC variant(s) using x86's pclmulqdq or vpclmulqdq instructions.
It can also generate the traditional byte-at-a-time tables.
Only small changes should be needed for this script to also work to generate the constants needed for CRC computation on other architectures with a carryless multiplication instruction, such as arm64.
--- scripts/crc/gen-crc-consts.py | 207 ++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100755 scripts/crc/gen-crc-consts.py
scripts/crc/gen-crc-consts.py new file +207 -0
@@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Script that generates constants for computing the given CRC variant(s). +# +# Copyright 2024 Google LLC +# +# Author: Eric Biggers <[email protected]> + +import sys + +# XOR (add) an iterable of polynomials. +def xor(iterable): + res = 0 + for val in iterable: + res ^= val + return res + +# Multiply two polynomials. +def clmul(a, b): + return xor(a << i for i in range(b.bit_length()) if (b & (1 << i)) != 0) + +# Polynomial division floor(a / b). +def div(a, b): + q = 0 + while a.bit_length() >= b.bit_length(): + q ^= 1 << (a.bit_length() - b.bit_length()) + a ^= b << (a.bit_length() - b.bit_length()) + return q + +# Reduce the polynomial 'a' modulo the polynomial 'b'. +def reduce(a, b): + return a ^ clmul(div(a, b), b) + +# Pretty-print a polynomial. +def pprint_poly(prefix, poly): + terms = ['1' if i == 0 else 'x' if i == 1 else f'x^{i}' + for i in reversed(range(poly.bit_length())) + if (poly & (1 << i)) != 0] + j = 0 + while j < len(terms): + s = prefix + terms[j] + (' +' if j < len(terms) - 1 else '') + j += 1 + while j < len(terms) and len(s) < 72: + s += ' ' + terms[j] + (' +' if j < len(terms) - 1 else '') + j += 1 + print(s) + prefix = ' * ' + (' ' * (len(prefix) - 3)) + +# Reverse the bits of a polynomial. +def bitreverse(poly, num_bits): + return xor(1 << (num_bits - 1 - i) for i in range(num_bits) + if (poly & (1 << i)) != 0) + +# Format a polynomial as hex. Bit-reflect it if the CRC is LSB-first. +def fmt_poly(variant, poly, num_bits): + if variant.lsb: + poly = bitreverse(poly, num_bits) + return f'0x{poly:0{2*num_bits//8}x}' + +# Print a comment describing constants generated for the given CRC variant. +def print_header(variant, what): + print('/*') + s = f'{"least" if variant.lsb else "most"}-significant-bit-first CRC-{variant.bits}' + print(f' * {what} generated for {s} using') + pprint_poly(' * G(x) = ', variant.G) + print(' */') + +# Print a polynomial as hex, but drop a term if needed to keep it in 64 bits. +def print_poly_truncate65thbit(variant, poly, num_bits, desc): + if num_bits > 64: + assert num_bits == 65 + if variant.lsb: + assert (poly & 1) != 0 + poly >>= 1 + desc += ' - 1' + else: + poly ^= 1 << 64 + desc += ' - x^64' + num_bits = 64 + print(f'\t\t{fmt_poly(variant, poly, num_bits)},\t/* {desc} */') + +class CrcVariant: + def __init__(self, bits, generator_poly, bit_order): + self.bits = bits + if bit_order not in ['lsb', 'msb']: + raise ValueError('Invalid value for bit_order') + self.lsb = bit_order == 'lsb' + self.name = f'crc{bits}_{bit_order}_0x{generator_poly:0{(2*bits+7)//8}x}' + if self.lsb: + generator_poly = bitreverse(generator_poly, bits) + self.G = generator_poly ^ (1 << bits) + +# Generate tables for byte-at-a-time CRC computation. +def gen_sliceby1_tables(variants): + for v in variants: + print('') + print_header(v, 'CRC table') + print(f'static const u{v.bits} __maybe_unused {v.name}_table[256] = {{') + s = '' + for i in range(256): + remainder = (bitreverse(i, 8) if v.lsb else i) << (v.bits - 8) + for _ in range(8): + remainder <<= 1 + if (remainder & (1 << v.bits)) != 0: + remainder ^= v.G + next_entry = fmt_poly(v, remainder, v.bits) + ',' + if len(s + next_entry) > 71: + print(f'\t{s}') + s = '' + s += (' ' if s else '') + next_entry + if s: + print(f'\t{s}') + print('};') + +# Generate constants for carryless multiplication based CRC computation. +def gen_x86_pclmul_consts(variants): + # These are the distances, in bits, to generate folding constants for. + FOLD_DISTANCES = [2048, 1024, 512, 256, 128] + + for v in variants: + print('') + print_header(v, 'CRC folding constants') + print('static const struct {') + if not v.lsb: + print('\tu8 bswap_mask[16];') + for i in FOLD_DISTANCES: + print(f'\tu64 fold_across_{i}_bits_consts[2];') + print('\tu8 shuf_table[48];') + print('\tu64 barrett_reduction_consts[2];') + if v.lsb and v.bits < 64: + print('\tu64 extract_crc_mask[2];') + print(f'}} {v.name}_consts __cacheline_aligned __maybe_unused = {{') + + # Byte-reflection mask, needed for MSB CRCs + if not v.lsb: + print('\t.bswap_mask = {' + ', '.join(str(i) for i in reversed(range(16))) + '},') + + # Fold constants for all distances down to 128 bits + k = v.bits - 65 if v.lsb else 0 + for i in FOLD_DISTANCES: + print(f'\t.fold_across_{i}_bits_consts = {{') + for j in [64, 0] if v.lsb else [0, 64]: + const = reduce(1<<(i+j+k), v.G) + pow_desc = f'{i}{"+" if j >= 0 else "-"}{abs(j)}' + if k != 0: + pow_desc += f'{"+" if k >= 0 else "-"}{abs(k)}' + print(f'\t\t{fmt_poly(v, const, v.bits)},\t/* x^({pow_desc}) mod G(x) */') + print('\t},') + + # Shuffle table for handling 1..15 bytes at end + print('\t.shuf_table = {') + print('\t\t' + (16*'-1, ').rstrip()) + print('\t\t' + ''.join(f'{i:2}, ' for i in range(16)).rstrip()) + print('\t\t' + (16*'-1, ').rstrip()) + print('\t},') + + # Barrett reduction constants for reducing 128 bits to the final CRC + m = 63 if v.lsb else 64 + print('\t.barrett_reduction_consts = {') + print_poly_truncate65thbit(v, div(1<<(m+v.bits), v.G), m+1, + f'floor(x^{m+v.bits} / G(x))') + print_poly_truncate65thbit(v, v.G, v.bits+1, 'G(x)') + print('\t},') + if v.lsb and v.bits < 64: + print(f'\t.extract_crc_mask = {{0, 0x{(1<<(v.bits))-1:x}}},') + + print('};') + +def parse_crc_variants(vars_string): + variants = [] + for var_string in vars_string.split(','): + bits, bit_order, generator_poly = var_string.split('_') + assert bits.startswith('crc') + bits = int(bits.removeprefix('crc')) + assert generator_poly.startswith('0x') + generator_poly = generator_poly.removeprefix('0x') + assert len(generator_poly) % 2 == 0 + generator_poly = int(generator_poly, 16) + variants.append(CrcVariant(bits, generator_poly, bit_order)) + return variants + +if len(sys.argv) != 3: + sys.stderr.write(f'Usage: {sys.argv[0]} CONSTS_TYPE[,CONSTS_TYPE]... CRC_VARIANT[,CRC_VARIANT]...\n') + sys.stderr.write(' CONSTS_TYPE can be sliceby1 or x86_pclmul\n') + sys.stderr.write(' CRC_VARIANT is crc${num_bits}_${bit_order}_${generator_poly_as_hex}\n') + sys.stderr.write(' E.g. crc16_msb_0x8bb7 or crc32_lsb_0xedb88320\n') + sys.stderr.write(' Polynomial must use the given bit_order and exclude x^{num_bits}\n') + sys.exit(1) + +print('/* SPDX-License-Identifier: GPL-2.0-or-later */') +print('/*') +print(' * CRC constants generated by:') +print(' *') +print(f' *\t{sys.argv[0]} {" ".join(sys.argv[1:])}') +print(' *') +print(' * Do not edit manually.') +print(' */') +consts_types = sys.argv[1].split(',') +variants = parse_crc_variants(sys.argv[2]) +for consts_type in consts_types: + if consts_type == 'sliceby1': + gen_sliceby1_tables(variants) + elif consts_type == 'x86_pclmul': + gen_x86_pclmul_consts(variants) + else: + raise ValueError(f'Unknown consts_type: {consts_type}')
Signature · 1 line
2.47.0
Re: [PATCH 2/6] scripts/crc: add gen-crc-consts.py
Quoted 6 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Add a Python script that generates constants for computing the given CRC variant(s) using x86's pclmulqdq or vpclmulqdq instructions.
There is nothing x86 specific about this, right? Except perhaps the choice of fold distances?
Quoted 199 lines
It can also generate the traditional byte-at-a-time tables.
Only small changes should be needed for this script to also work to generate the constants needed for CRC computation on other architectures with a carryless multiplication instruction, such as arm64.
Signed-off-by Eric Biggers <[email protected]>--- scripts/crc/gen-crc-consts.py | 207 ++++++++++++++++++++++++++++++++++ 1 file changed, 207 insertions(+) create mode 100755 scripts/crc/gen-crc-consts.pyscripts/crc/gen-crc-consts.py new file +181
-0@@ -0,0 +1,207 @@ +#!/usr/bin/env python3 +# SPDX-License-Identifier: GPL-2.0-or-later +# +# Script that generates constants for computing the given CRC variant(s). +# +# Copyright 2024 Google LLC +# +# Author: Eric Biggers <[email protected]> + +import sys + +# XOR (add) an iterable of polynomials. +def xor(iterable): + res = 0 + for val in iterable: + res ^= val + return res + +# Multiply two polynomials. +def clmul(a, b): + return xor(a << i for i in range(b.bit_length()) if (b & (1 << i)) != 0) + +# Polynomial division floor(a / b). +def div(a, b): + q = 0 + while a.bit_length() >= b.bit_length(): + q ^= 1 << (a.bit_length() - b.bit_length()) + a ^= b << (a.bit_length() - b.bit_length()) + return q + +# Reduce the polynomial 'a' modulo the polynomial 'b'. +def reduce(a, b): + return a ^ clmul(div(a, b), b) + +# Pretty-print a polynomial. +def pprint_poly(prefix, poly): + terms = ['1' if i == 0 else 'x' if i == 1 else f'x^{i}' + for i in reversed(range(poly.bit_length())) + if (poly & (1 << i)) != 0] + j = 0 + while j < len(terms): + s = prefix + terms[j] + (' +' if j < len(terms) - 1 else '') + j += 1 + while j < len(terms) and len(s) < 72: + s += ' ' + terms[j] + (' +' if j < len(terms) - 1 else '') + j += 1 + print(s) + prefix = ' * ' + (' ' * (len(prefix) - 3)) + +# Reverse the bits of a polynomial. +def bitreverse(poly, num_bits): + return xor(1 << (num_bits - 1 - i) for i in range(num_bits) + if (poly & (1 << i)) != 0) + +# Format a polynomial as hex. Bit-reflect it if the CRC is LSB-first. +def fmt_poly(variant, poly, num_bits): + if variant.lsb: + poly = bitreverse(poly, num_bits) + return f'0x{poly:0{2*num_bits//8}x}' + +# Print a comment describing constants generated for the given CRC variant. +def print_header(variant, what): + print('/*') + s = f'{"least" if variant.lsb else "most"}-significant-bit-first CRC-{variant.bits}' + print(f' * {what} generated for {s} using') + pprint_poly(' * G(x) = ', variant.G) + print(' */') + +# Print a polynomial as hex, but drop a term if needed to keep it in 64 bits. +def print_poly_truncate65thbit(variant, poly, num_bits, desc): + if num_bits > 64: + assert num_bits == 65 + if variant.lsb: + assert (poly & 1) != 0 + poly >>= 1 + desc += ' - 1' + else: + poly ^= 1 << 64 + desc += ' - x^64' + num_bits = 64 + print(f'\t\t{fmt_poly(variant, poly, num_bits)},\t/* {desc} */') + +class CrcVariant: + def __init__(self, bits, generator_poly, bit_order): + self.bits = bits + if bit_order not in ['lsb', 'msb']: + raise ValueError('Invalid value for bit_order') + self.lsb = bit_order == 'lsb' + self.name = f'crc{bits}_{bit_order}_0x{generator_poly:0{(2*bits+7)//8}x}' + if self.lsb: + generator_poly = bitreverse(generator_poly, bits) + self.G = generator_poly ^ (1 << bits) + +# Generate tables for byte-at-a-time CRC computation. +def gen_sliceby1_tables(variants): + for v in variants: + print('') + print_header(v, 'CRC table') + print(f'static const u{v.bits} __maybe_unused {v.name}_table[256] = {{') + s = '' + for i in range(256): + remainder = (bitreverse(i, 8) if v.lsb else i) << (v.bits - 8) + for _ in range(8): + remainder <<= 1 + if (remainder & (1 << v.bits)) != 0: + remainder ^= v.G + next_entry = fmt_poly(v, remainder, v.bits) + ',' + if len(s + next_entry) > 71: + print(f'\t{s}') + s = '' + s += (' ' if s else '') + next_entry + if s: + print(f'\t{s}') + print('};') + +# Generate constants for carryless multiplication based CRC computation. +def gen_x86_pclmul_consts(variants): + # These are the distances, in bits, to generate folding constants for. + FOLD_DISTANCES = [2048, 1024, 512, 256, 128] + + for v in variants: + print('') + print_header(v, 'CRC folding constants') + print('static const struct {') + if not v.lsb: + print('\tu8 bswap_mask[16];') + for i in FOLD_DISTANCES: + print(f'\tu64 fold_across_{i}_bits_consts[2];') + print('\tu8 shuf_table[48];') + print('\tu64 barrett_reduction_consts[2];') + if v.lsb and v.bits < 64: + print('\tu64 extract_crc_mask[2];') + print(f'}} {v.name}_consts __cacheline_aligned __maybe_unused = {{') + + # Byte-reflection mask, needed for MSB CRCs + if not v.lsb: + print('\t.bswap_mask = {' + ', '.join(str(i) for i in reversed(range(16))) + '},') + + # Fold constants for all distances down to 128 bits + k = v.bits - 65 if v.lsb else 0 + for i in FOLD_DISTANCES: + print(f'\t.fold_across_{i}_bits_consts = {{') + for j in [64, 0] if v.lsb else [0, 64]: + const = reduce(1<<(i+j+k), v.G) + pow_desc = f'{i}{"+" if j >= 0 else "-"}{abs(j)}' + if k != 0: + pow_desc += f'{"+" if k >= 0 else "-"}{abs(k)}' + print(f'\t\t{fmt_poly(v, const, v.bits)},\t/* x^({pow_desc}) mod G(x) */') + print('\t},') + + # Shuffle table for handling 1..15 bytes at end + print('\t.shuf_table = {') + print('\t\t' + (16*'-1, ').rstrip()) + print('\t\t' + ''.join(f'{i:2}, ' for i in range(16)).rstrip()) + print('\t\t' + (16*'-1, ').rstrip()) + print('\t},') + + # Barrett reduction constants for reducing 128 bits to the final CRC + m = 63 if v.lsb else 64 + print('\t.barrett_reduction_consts = {') + print_poly_truncate65thbit(v, div(1<<(m+v.bits), v.G), m+1, + f'floor(x^{m+v.bits} / G(x))') + print_poly_truncate65thbit(v, v.G, v.bits+1, 'G(x)') + print('\t},') + if v.lsb and v.bits < 64: + print(f'\t.extract_crc_mask = {{0, 0x{(1<<(v.bits))-1:x}}},') + + print('};') + +def parse_crc_variants(vars_string): + variants = [] + for var_string in vars_string.split(','): + bits, bit_order, generator_poly = var_string.split('_') + assert bits.startswith('crc') + bits = int(bits.removeprefix('crc')) + assert generator_poly.startswith('0x') + generator_poly = generator_poly.removeprefix('0x') + assert len(generator_poly) % 2 == 0 + generator_poly = int(generator_poly, 16) + variants.append(CrcVariant(bits, generator_poly, bit_order)) + return variants
scripts/crc/gen-crc-consts.py line 191
Quoted 10 lines
scripts/crc/gen-crc-consts.py new file +10
-0+ +if len(sys.argv) != 3: + sys.stderr.write(f'Usage: {sys.argv[0]} CONSTS_TYPE[,CONSTS_TYPE]... CRC_VARIANT[,CRC_VARIANT]...\n') + sys.stderr.write(' CONSTS_TYPE can be sliceby1 or x86_pclmul\n') + sys.stderr.write(' CRC_VARIANT is crc${num_bits}_${bit_order}_${generator_poly_as_hex}\n') + sys.stderr.write(' E.g. crc16_msb_0x8bb7 or crc32_lsb_0xedb88320\n') + sys.stderr.write(' Polynomial must use the given bit_order and exclude x^{num_bits}\n') + sys.exit(1) + +print('/* SPDX-License-Identifier: GPL-2.0-or-later */')
Does it make sense to add a GPL header into a generated file?
Quoted 19 lines
scripts/crc/gen-crc-consts.py new file +16
-1+print('/*') +print(' * CRC constants generated by:') +print(' *') +print(f' *\t{sys.argv[0]} {" ".join(sys.argv[1:])}') +print(' *') +print(' * Do not edit manually.') +print(' */') +consts_types = sys.argv[1].split(',') +variants = parse_crc_variants(sys.argv[2]) +for consts_type in consts_types: + if consts_type == 'sliceby1': + gen_sliceby1_tables(variants) + elif consts_type == 'x86_pclmul': + gen_x86_pclmul_consts(variants) + else: + raise ValueError(f'Unknown consts_type: {consts_type}') --2.47.0
Re: [PATCH 2/6] scripts/crc: add gen-crc-consts.py
Quoted 10 lines · Ard Biesheuvel
Quoted 6 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Add a Python script that generates constants for computing the given CRC variant(s) using x86's pclmulqdq or vpclmulqdq instructions.
There is nothing x86 specific about this, right? Except perhaps the choice of fold distances?
Yes, and maybe other architectures will want something different for bswap_mask and shuf_table depending on exactly what instructions they have. But it should be straightforward to add an option to generate another arch's variant.
Quoted 3 lines
Quoted 1 line
+print('/* SPDX-License-Identifier: GPL-2.0-or-later */')
Does it make sense to add a GPL header into a generated file?
Since I'm checking in the generated file, I figured it would run up against the policy that every source file must have a license.
We could generate the file during every build, but I don't really want to deal with complaints about Python not being installed or Python being too old, or to put the performance of the script on the critical path for almost everyone building a kernel for x86. (Note that Documentation/process/changes.rst currently lists Python as "optional" for building the kernel, not required.)
- Eric
Re: [PATCH 2/6] scripts/crc: add gen-crc-consts.py
Quoted 21 lines · Eric Biggers
Quoted 10 lines · Ard Biesheuvel
Quoted 6 lines · Eric Biggers
From: Eric Biggers <[email protected]>
Add a Python script that generates constants for computing the given CRC variant(s) using x86's pclmulqdq or vpclmulqdq instructions.
There is nothing x86 specific about this, right? Except perhaps the choice of fold distances?
Yes, and maybe other architectures will want something different for
bswap_maskandshuf_tabledepending on exactly what instructions they have. But it should be straightforward to add an option to generate another arch's variant.Quoted 3 lines
Quoted 1 line
+print('/* SPDX-License-Identifier: GPL-2.0-or-later */')
Does it make sense to add a GPL header into a generated file?
Quoted 9 lines
Since I'm checking in the generated file, I figured it would run up against the policy that every source file must have a license.
We could generate the file during every build, but I don't really want to deal with complaints about Python not being installed or Python being too old, or to put the performance of the script on the critical path for almost everyone building a kernel for x86. (Note that
Documentation/process/changes.rstcurrently lists Python as "optional" for building the kernel, not required.)
Fair enough.
PATCH 3/6 x86/crc: add "template" for [V]PCLMULQDQ based CRC functions
In reply to Eric BiggersFrom: Eric Biggers <[email protected]>
The Linux kernel implements many variants of CRC, such as crc16, crct10dif, crc32_le, crc32c, crc32_be, crc64_rocksoft, and crc64_be. On x86, except for crc32c which has special x86 scalar instructions, the fastest way to compute any of these CRCs is to use the SIMD carryless multiplication instructions PCLMULQDQ or VPCLMULQDQ. Depending on the available CPU features this can mean PCLMULQDQ+SSE4.1, VPCLMULQDQ+AVX2, VPCLMULQDQ+AVX10/256, or VPCLMULQDQ+AVX10/512 (or the AVX512 equivalents to AVX10/*). This results in a total of 20+ CRC implementations being potentially needed to properly optimize all CRCs that someone cares about for x86. Besides crc32c, currently only crc32_le and crct10dif are actually optimized for x86, and they only use PCLMULQDQ, which means they can be 2-4x slower than what is possible with VPCLMULQDQ.
Fortunately, at a high level the code that is needed for any [V]PCLMULQDQ based CRC implementation is mostly the same. Therefore, this patch introduces an assembly macro that expands into the body of a [V]PCLMULQDQ based CRC function for a given number of bits (8, 16, 32, or 64), bit order (LSB or MSB-first), vector length, and AVX level.
The function expects to be passed a constants table, specific to the polynomial desired, that was generated by the script previously added. When two CRC variants share the same number of bits and bit order, the same functions can be reused, with only the constants table differing.
A new C header crc-pclmul-template-glue.h is also added to make it easy to integrate the new assembly code using a static call.
The result is that it becomes straightforward to wire up a fully optimized implementation of any CRC-8, CRC-16, CRC-32, or CRC-64 for x86 (except for crc32c, which usually does a bit better with its specialized instructions). Later patches will wire up specific CRC variants.
Although this new template allows easily generating many functions, care was taken to still keep the binary size fairly low. Each generated function is only ~520 bytes for LSB CRCs or ~660 bytes for MSB CRCs.
Note that a similar approach should also work for other architectures that have carryless multiplication instructions, such as arm64.
--- arch/x86/lib/crc-pclmul-template-glue.h | 84 ++++ arch/x86/lib/crc-pclmul-template.S | 588 ++++++++++++++++++++++++ 2 files changed, 672 insertions(+) create mode 100644 arch/x86/lib/crc-pclmul-template-glue.h create mode 100644 arch/x86/lib/crc-pclmul-template.S
arch/x86/lib/crc-pclmul-template-glue.h new file +84 -0
@@ -0,0 +1,84 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* +* Macros for accessing the [V]PCLMULQDQ-based CRC functions that are +* instantiated by crc-pclmul-template.S +* +* Copyright 2024 Google LLC +* +* Author: Eric Biggers <[email protected]> +*/ +#ifndef _CRC_PCLMUL_TEMPLATE_GLUE_H +#define _CRC_PCLMUL_TEMPLATE_GLUE_H + +#include <asm/cpufeatures.h> +#include <crypto/internal/simd.h> +#include <linux/static_call.h> + +#define DECLARE_CRC_PCLMUL_FUNCS (prefix, crc_t) \ +crc_t prefix##_pclmul_sse(crc_t crc, const u8 *p, size_t len, \ + const void *consts_ptr); \ +crc_t prefix##_vpclmul_avx2(crc_t crc, const u8 *p, size_t len, \ + const void *consts_ptr); \ +crc_t prefix##_vpclmul_avx10_256(crc_t crc, const u8 *p, size_t len, \ + const void *consts_ptr); \ +crc_t prefix##_vpclmul_avx10_512(crc_t crc, const u8 *p, size_t len, \ + const void *consts_ptr); \ + \ +DEFINE_STATIC_CALL(prefix##_pclmul, prefix##_pclmul_sse) + +#define INIT_CRC_PCLMUL (prefix) \ +do { \ + if (IS_ENABLED(CONFIG_AS_VPCLMULQDQ) && \ + boot_cpu_has(X86_FEATURE_VPCLMULQDQ) && \ + boot_cpu_has(X86_FEATURE_AVX2) && \ + cpu_has_xfeatures(XFEATURE_MASK_YMM, NULL)) { \ + if (boot_cpu_has(X86_FEATURE_AVX512BW) && \ + boot_cpu_has(X86_FEATURE_AVX512VL) && \ + cpu_has_xfeatures(XFEATURE_MASK_AVX512, NULL)) { \ + if (boot_cpu_has(X86_FEATURE_PREFER_YMM)) \ + static_call_update(prefix##_pclmul, \ + prefix##_vpclmul_avx10_256); \ + else \ + static_call_update(prefix##_pclmul, \ + prefix##_vpclmul_avx10_512); \ + } else { \ + static_call_update(prefix##_pclmul, \ + prefix##_vpclmul_avx2); \ + } \ + } \ +} while (0) + +/* +* Call a [V]PCLMULQDQ optimized CRC function if SIMD is usable and the CPU has +* PCLMULQDQ support, and the length is not very small. +* +* The SIMD functions require len >= 16. However, if the fallback +* implementation uses slice-by-8 instead of slice-by-1 (which makes it much +* faster, assuming the larger tables stay in dcache...), then roughly len >= 64 +* is needed for the overhead of the kernel_fpu_{begin,end}() to be worth it. +* +* (64 is just a rough estimate. The exact breakeven point varies by factors +* such as the CPU model; how many FPU sections are executed before returning to +* userspace, considering that only one XSAVE + XRSTOR pair is executed no +* matter how many FPU sections there are; whether the userspace thread used ymm +* or zmm registers which makes the XSAVE + XRSTOR more expensive; and whether +* the thread is a kernel thread, which never needs the XSAVE + XRSTOR.) +*/ +#define CRC_PCLMUL (crc, p, len, prefix, consts, \ + have_pclmulqdq, is_fallback_sliced) \ +do { \ + if ((len) >= ((is_fallback_sliced) ? 64 : 16) && \ + static_branch_likely(&(have_pclmulqdq)) && \ + crypto_simd_usable()) { \ + const void *consts_ptr; \ + \ + consts_ptr = (consts).fold_across_128_bits_consts; \ + kernel_fpu_begin(); \ + crc = static_call(prefix##_pclmul)((crc), (p), (len), \ + consts_ptr); \ + kernel_fpu_end(); \ + return crc; \ + } \ +} while (0) + +#endif /* _CRC_PCLMUL_TEMPLATE_GLUE_H */
arch/x86/lib/crc-pclmul-template.S new file +588 -0
588 lines not on this page · raw message
Signature · 1 line
2.47.0
PATCH 4/6 x86/crc32: implement crc32_le using new template
In reply to Eric BiggersFrom: Eric Biggers <[email protected]>
Instantiate crc-pclmul-template.S for crc32_le, and delete the original PCLMULQDQ optimized implementation. This has the following advantages:
- Less CRC-variant-specific code.
- Full VPCLMULQDQ support, improving performance on newer CPUs.
- A faster final reduction from 128 bits.
- Support for lengths not a multiple of 16 bytes, improving performance for such lengths.
- Support for misaligned buffers, improving performance in such cases.
Benchmark results on AMD Ryzen 9 9950X (Zen 5) using crc_kunit:
Length Before After ------ ------ ----- 1 274 MB/s 302 MB/s 16 3004 MB/s 2576 MB/s 64 3077 MB/s 7406 MB/s 127 2791 MB/s 9768 MB/s 128 8366 MB/s 12878 MB/s 200 10010 MB/s 15320 MB/s 256 16018 MB/s 22664 MB/s 511 11809 MB/s 27646 MB/s 512 18473 MB/s 48241 MB/s 1024 20002 MB/s 63912 MB/s 3173 20907 MB/s 78547 MB/s 4096 21291 MB/s 82808 MB/s 16384 21304 MB/s 83350 MB/s
The above results compare the state directly before this patch to the state after this patch. Note that performance was also improved earlier by eliminating the indirect call associated with the crypto API.
--- arch/x86/lib/crc-pclmul-consts.h | 55 ++++++++ arch/x86/lib/crc32-glue.c | 34 ++--- arch/x86/lib/crc32-pclmul.S | 219 +------------------------------ 3 files changed, 68 insertions(+), 240 deletions(-) create mode 100644 arch/x86/lib/crc-pclmul-consts.h
arch/x86/lib/crc-pclmul-consts.h new file +55 -0
@@ -0,0 +1,55 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* +* CRC constants generated by: +* +* ./scripts/crc/gen-crc-consts.py x86_pclmul crc32_lsb_0xedb88320 +* +* Do not edit manually. +*/ + +/* +* CRC folding constants generated for least-significant-bit-first CRC-32 using +* G(x) = x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + +* x^5 + x^4 + x^2 + x + 1 +*/ +static const struct { +u64 fold_across_2048_bits_consts[2 ]; +u64 fold_across_1024_bits_consts[2 ]; +u64 fold_across_512_bits_consts[2 ]; +u64 fold_across_256_bits_consts[2 ]; +u64 fold_across_128_bits_consts[2 ]; +u8 shuf_table[48 ]; +u64 barrett_reduction_consts[2 ]; +u64 extract_crc_mask[2 ]; +} crc32_lsb_0xedb88320_consts __cacheline_aligned __maybe_unused = { + .fold_across_2048_bits_consts = { +0xce3371cb ,/* x^(2048+64-33) mod G(x) */ +0xe95c1271 ,/* x^(2048+0-33) mod G(x) */ + }, + .fold_across_1024_bits_consts = { +0x33fff533 ,/* x^(1024+64-33) mod G(x) */ +0x910eeec1 ,/* x^(1024+0-33) mod G(x) */ + }, + .fold_across_512_bits_consts = { +0x8f352d95 ,/* x^(512+64-33) mod G(x) */ +0x1d9513d7 ,/* x^(512+0-33) mod G(x) */ + }, + .fold_across_256_bits_consts = { +0xf1da05aa ,/* x^(256+64-33) mod G(x) */ +0x81256527 ,/* x^(256+0-33) mod G(x) */ + }, + .fold_across_128_bits_consts = { +0xae689191 ,/* x^(128+64-33) mod G(x) */ +0xccaa009e ,/* x^(128+0-33) mod G(x) */ + }, + .shuf_table = { +-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 , +0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 , +-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 , + }, + .barrett_reduction_consts = { +0xb4e5b025f7011641 ,/* floor(x^95 / G(x)) */ +0x1db710641 ,/* G(x) */ + }, + .extract_crc_mask = {0 ,0xffffffff }, +};
arch/x86/lib/crc32-glue.c +9 -25
@@ -11,41 +11,23 @@#include <asm/simd.h> #include <crypto/internal/simd.h> #include <linux/crc32.h> #include <linux/linkage.h> #include <linux/module.h> - -/* minimum size of buffer for crc32_pclmul_le_16 */ -#define CRC32_PCLMUL_MIN_LEN 64 +#include "crc-pclmul-consts.h" +#include "crc-pclmul-template-glue.h" static DEFINE_STATIC_KEY_FALSE (have_crc32);static DEFINE_STATIC_KEY_FALSE (have_pclmulqdq); -u32 crc32_pclmul_le_16 (u32 crc,const u8 *buffer,size_t len); +DECLARE_CRC_PCLMUL_FUNCS (crc32_lsb, u32);u32 crc32_le_arch (u32 crc,const u8 *p,size_t len) { -if (len >=CRC32_PCLMUL_MIN_LEN +15 && -static_branch_likely (&have_pclmulqdq) &&crypto_simd_usable ()) { -size_t n = -(uintptr_t )p &15 ; - -/* align p to 16-byte boundary */ -if (n) { - crc =crc32_le_base (crc, p, n); - p += n; - len -= n; - } - n =round_down (len,16 ); -kernel_fpu_begin (); - crc =crc32_pclmul_le_16 (crc, p, n); -kernel_fpu_end (); - p += n; - len -= n; - } -if (len) - crc =crc32_le_base (crc, p, len); -return crc; +CRC_PCLMUL (crc, p, len, crc32_lsb, crc32_lsb_0xedb88320_consts, + have_pclmulqdq,IS_ENABLED (CONFIG_CRC32_SLICEBY8 )); +return crc32_le_base (crc, p, len); }EXPORT_SYMBOL (crc32_le_arch);#ifdef CONFIG_X86_64 #define CRC32_INST "crc32q %1, %q0"
@@ -95,12 +77,14 @@ EXPORT_SYMBOL(crc32_be_arch);static int __init crc32_x86_init (void ) {if (boot_cpu_has (X86_FEATURE_XMM4_2 ))static_branch_enable (&have_crc32); -if (boot_cpu_has (X86_FEATURE_PCLMULQDQ )) +if (boot_cpu_has (X86_FEATURE_PCLMULQDQ )) {static_branch_enable (&have_pclmulqdq); +INIT_CRC_PCLMUL (crc32_lsb); + }return 0 ; }arch_initcall (crc32_x86_init); static void __exit crc32_x86_exit(void)
arch/x86/lib/crc32-pclmul.S +4 -215
221 lines not on this page · raw message
Signature · 1 line
2.47.0
PATCH 5/6 x86/crc-t10dif: implement crc_t10dif using new template
In reply to Eric BiggersFrom: Eric Biggers <[email protected]>
Instantiate crc-pclmul-template.S for crc_t10dif and delete the original PCLMULQDQ optimized implementation. This has the following advantages:
- Less CRC-variant-specific code.
- Full VPCLMULQDQ support, improving performance on newer CPUs.
- A faster final reduction from 128 bits.
- Support for i386.
Benchmark results on AMD Ryzen 9 9950X (Zen 5) using crc_kunit:
Length Before After ------ ------ ----- 1 435 MB/s 509 MB/s 16 1878 MB/s 1852 MB/s 64 4336 MB/s 6123 MB/s 127 5394 MB/s 8431 MB/s 128 5590 MB/s 10886 MB/s 200 5948 MB/s 13371 MB/s 256 15813 MB/s 19428 MB/s 511 14193 MB/s 25177 MB/s 512 18136 MB/s 35188 MB/s 1024 19878 MB/s 62569 MB/s 3173 21009 MB/s 77916 MB/s 4096 21249 MB/s 82295 MB/s 16384 21645 MB/s 89350 MB/s
The above results compare the state directly before this patch to the state after this patch. Note that performance was also improved earlier by eliminating the indirect call associated with the crypto API.
--- arch/x86/Kconfig | 2 +- arch/x86/lib/Makefile | 2 +- arch/x86/lib/crc-pclmul-consts.h | 48 +++- arch/x86/lib/crc-t10dif-glue.c | 22 +- arch/x86/lib/crc16-msb-pclmul.S | 6 + arch/x86/lib/crct10dif-pcl-asm_64.S | 332 ---------------------------- 6 files changed, 65 insertions(+), 347 deletions(-) create mode 100644 arch/x86/lib/crc16-msb-pclmul.S delete mode 100644 arch/x86/lib/crct10dif-pcl-asm_64.S
arch/x86/Kconfig +1 -1
@@ -75,11 +75,11 @@ config X86 select ARCH_HAS_CACHE_LINE_SIZE select ARCH_HAS_CPU_CACHE_INVALIDATE_MEMREGION select ARCH_HAS_CPU_FINALIZE_INIT select ARCH_HAS_CPU_PASID if IOMMU_SVA select ARCH_HAS_CRC32 - select ARCH_HAS_CRC_T10DIF if X86_64 + select ARCH_HAS_CRC_T10DIF select ARCH_HAS_CURRENT_STACK_POINTER select ARCH_HAS_DEBUG_VIRTUAL select ARCH_HAS_DEBUG_VM_PGTABLE if !X86_PAE select ARCH_HAS_DEVMEM_IS_ALLOWED select ARCH_HAS_DMA_OPS if GART_IOMMU || XEN
arch/x86/lib/Makefile +1 -1
@@ -41,11 +41,11 @@ lib-$(CONFIG_MITIGATION_RETPOLINE) += retpoline.o obj-$(CONFIG_CRC32_ARCH) += crc32-x86.o crc32-x86-y := crc32-glue.o crc32-pclmul.o crc32-x86-$(CONFIG_64BIT) += crc32c-3way.o obj-$(CONFIG_CRC_T10DIF_ARCH) += crc-t10dif-x86.o -crc-t10dif-x86-y := crc-t10dif-glue.o crct10dif-pcl-asm_64.o +crc-t10dif-x86-y := crc-t10dif-glue.o crc16-msb-pclmul.o obj-y += msr.o msr-reg.o msr-reg-export.o hweight.o obj-y += iomem.o ifeq ($(CONFIG_X86_32),y)
arch/x86/lib/crc-pclmul-consts.h +47 -1
61 lines not on this page · raw message
arch/x86/lib/crc-t10dif-glue.c +10 -12
@@ -1,39 +1,37 @@// SPDX-License-Identifier: GPL-2.0-or-later /* -* CRC-T10DIF using PCLMULQDQ instructions +* CRC-T10DIF using [V]PCLMULQDQ instructions * * Copyright 2024 Google LLC */ #include <asm/cpufeatures.h> #include <asm/simd.h> -#include <crypto/internal/simd.h> #include <linux/crc-t10dif.h> #include <linux/module.h> +#include "crc-pclmul-consts.h" +#include "crc-pclmul-template-glue.h" static DEFINE_STATIC_KEY_FALSE (have_pclmulqdq); -asmlinkage u16 crc_t10dif_pcl(u16 init_crc, const u8 *buf, size_t len); +DECLARE_CRC_PCLMUL_FUNCS (crc16_msb, u16);u16 crc_t10dif_arch (u16 crc,const u8 *p,size_t len) { -if (len >=16 && -static_key_enabled (&have_pclmulqdq) &&crypto_simd_usable ()) { -kernel_fpu_begin (); - crc =crc_t10dif_pcl (crc, p, len); -kernel_fpu_end (); -return crc; - } +CRC_PCLMUL (crc, p, len, crc16_msb, crc16_msb_0x8bb7_consts, + have_pclmulqdq, false);return crc_t10dif_generic (crc, p, len); }EXPORT_SYMBOL (crc_t10dif_arch);static int __init crc_t10dif_x86_init (void ) { -if (boot_cpu_has (X86_FEATURE_PCLMULQDQ )) +if (boot_cpu_has (X86_FEATURE_PCLMULQDQ )) {static_branch_enable (&have_pclmulqdq); +INIT_CRC_PCLMUL (crc16_msb); + }return 0 ; }arch_initcall (crc_t10dif_x86_init); static void __exit crc_t10dif_x86_exit(void)
@@ -45,7 +43,7 @@ bool crc_t10dif_is_optimized(void) {return static_key_enabled (&have_pclmulqdq); }EXPORT_SYMBOL (crc_t10dif_is_optimized); -MODULE_DESCRIPTION ("CRC-T10DIF using PCLMULQDQ instructions" ); +MODULE_DESCRIPTION ("CRC-T10DIF using [V]PCLMULQDQ instructions" );MODULE_LICENSE ("GPL" );
arch/x86/lib/crc16-msb-pclmul.S new file +6 -0
@@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +// Copyright 2024 Google LLC + +#include "crc-pclmul-template.S" + +DEFINE_CRC_PCLMUL_FUNCS(crc16_msb, /* bits= */ 16, /* lsb= */ 0)
arch/x86/lib/crct10dif-pcl-asm_64.S deleted +0 -332
332 lines not on this page · raw message
Signature · 1 line
2.47.0
PATCH 6/6 x86/crc32: implement crc32_be using new template
In reply to Eric BiggersFrom: Eric Biggers <[email protected]>
crc32_be was previously unoptimized on x86. Optimize it using the new template. This improves performance by over 25x in some cases.
Benchmark results on AMD Ryzen 9 9950X (Zen 5) using crc_kunit:
Length Before After ------ ------ ----- 1 389 MB/s 325 MB/s 16 2845 MB/s 2911 MB/s 64 3012 MB/s 6513 MB/s 127 2567 MB/s 9057 MB/s 128 3048 MB/s 11589 MB/s 200 3070 MB/s 14042 MB/s 256 3067 MB/s 20454 MB/s 511 2938 MB/s 26245 MB/s 512 3081 MB/s 36926 MB/s 1024 3090 MB/s 61914 MB/s 3173 3065 MB/s 76201 MB/s 4096 3084 MB/s 82547 MB/s 16384 3084 MB/s 89333 MB/s
--- arch/x86/lib/crc-pclmul-consts.h | 49 +++++++++++++++++++++++++++++++- arch/x86/lib/crc32-glue.c | 4 +++ arch/x86/lib/crc32-pclmul.S | 1 + 3 files changed, 53 insertions(+), 1 deletion(-)
arch/x86/lib/crc-pclmul-consts.h +48 -1
@@ -1,10 +1,10 @@/* SPDX-License-Identifier: GPL-2.0-or-later */ /* * CRC constants generated by: * -* ./scripts/crc/gen-crc-consts.py x86_pclmul crc16_msb_0x8bb7,crc32_lsb_0xedb88320 +* ./scripts/crc/gen-crc-consts.py x86_pclmul crc16_msb_0x8bb7,crc32_lsb_0xedb88320,crc32_msb_0x04c11db7 * * Do not edit manually. */ /*
@@ -97,5 +97,52 @@ static const struct { 0xb4e5b025f7011641, /* floor(x^95 / G(x)) */ 0x1db710641, /* G(x) */ }, .extract_crc_mask = {0, 0xffffffff}, }; + +/* +* CRC folding constants generated for most-significant-bit-first CRC-32 using +* G(x) = x^32 + x^26 + x^23 + x^22 + x^16 + x^12 + x^11 + x^10 + x^8 + x^7 + +* x^5 + x^4 + x^2 + x + 1 +*/ +static const struct { +u8 bswap_mask[16 ]; +u64 fold_across_2048_bits_consts[2 ]; +u64 fold_across_1024_bits_consts[2 ]; +u64 fold_across_512_bits_consts[2 ]; +u64 fold_across_256_bits_consts[2 ]; +u64 fold_across_128_bits_consts[2 ]; +u8 shuf_table[48 ]; +u64 barrett_reduction_consts[2 ]; +} crc32_msb_0x04c11db7_consts __cacheline_aligned __maybe_unused = { + .bswap_mask = {15 ,14 ,13 ,12 ,11 ,10 ,9 ,8 ,7 ,6 ,5 ,4 ,3 ,2 ,1 ,0 }, + .fold_across_2048_bits_consts = { +0x88fe2237 ,/* x^(2048+0) mod G(x) */ +0xcbcf3bcb ,/* x^(2048+64) mod G(x) */ + }, + .fold_across_1024_bits_consts = { +0x567fddeb ,/* x^(1024+0) mod G(x) */ +0x10bd4d7c ,/* x^(1024+64) mod G(x) */ + }, + .fold_across_512_bits_consts = { +0xe6228b11 ,/* x^(512+0) mod G(x) */ +0x8833794c ,/* x^(512+64) mod G(x) */ + }, + .fold_across_256_bits_consts = { +0x75be46b7 ,/* x^(256+0) mod G(x) */ +0x569700e5 ,/* x^(256+64) mod G(x) */ + }, + .fold_across_128_bits_consts = { +0xe8a45605 ,/* x^(128+0) mod G(x) */ +0xc5b9cd4c ,/* x^(128+64) mod G(x) */ + }, + .shuf_table = { +-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 , +0 ,1 ,2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10 ,11 ,12 ,13 ,14 ,15 , +-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 ,-1 , + }, + .barrett_reduction_consts = { +0x04d101df481b4e5a ,/* floor(x^96 / G(x)) - x^64 */ +0x104c11db7 ,/* G(x) */ + }, +};
arch/x86/lib/crc32-glue.c +4 -0
@@ -18,10 +18,11 @@static DEFINE_STATIC_KEY_FALSE (have_crc32);static DEFINE_STATIC_KEY_FALSE (have_pclmulqdq);DECLARE_CRC_PCLMUL_FUNCS (crc32_lsb, u32); +DECLARE_CRC_PCLMUL_FUNCS (crc32_msb, u32);u32 crc32_le_arch (u32 crc,const u8 *p,size_t len) {CRC_PCLMUL (crc, p, len, crc32_lsb, crc32_lsb_0xedb88320_consts, have_pclmulqdq,IS_ENABLED (CONFIG_CRC32_SLICEBY8 ));
@@ -69,10 +70,12 @@ u32 crc32c_le_arch(u32 crc, const u8 *p, size_t len) }EXPORT_SYMBOL (crc32c_le_arch);u32 crc32_be_arch (u32 crc,const u8 *p,size_t len) { +CRC_PCLMUL (crc, p, len, crc32_msb, crc32_msb_0x04c11db7_consts, + have_pclmulqdq,IS_ENABLED (CONFIG_CRC32_SLICEBY8 ));return crc32_be_base (crc, p, len); }EXPORT_SYMBOL (crc32_be_arch);static int __init crc32_x86_init (void )
@@ -80,10 +83,11 @@ static int __init crc32_x86_init(void)if (boot_cpu_has (X86_FEATURE_XMM4_2 ))static_branch_enable (&have_crc32);if (boot_cpu_has (X86_FEATURE_PCLMULQDQ )) {static_branch_enable (&have_pclmulqdq);INIT_CRC_PCLMUL (crc32_lsb); +INIT_CRC_PCLMUL (crc32_msb); }return 0 ; }arch_initcall (crc32_x86_init);
arch/x86/lib/crc32-pclmul.S +1 -0
@@ -2,5 +2,6 @@ // Copyright 2024 Google LLC #include "crc-pclmul-template.S" DEFINE_CRC_PCLMUL_FUNCS(crc32_lsb, /* bits= */ 32, /* lsb= */ 1) +DEFINE_CRC_PCLMUL_FUNCS(crc32_msb, /* bits= */ 32, /* lsb= */ 0)
Signature · 1 line
2.47.0
Quoted 31 lines · Eric Biggers
This patchset is also available in git via:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git crc-x86-v1
This patchset applies on top of my other recent CRC patchsets
https://lore.kernel.org/r/[email protected]/ and
https://lore.kernel.org/r/[email protected]/ . Consider it a preview for what may be coming next, as my priority is getting those two other patchsets merged first.This patchset adds a new assembly macro that expands into the body of a CRC function for x86 for the specified number of bits, bit order, vector length, and AVX level. There's also a new script that generates the constants needed by this function, given a CRC generator polynomial.
This approach allows easily wiring up an x86-optimized implementation of any variant of CRC-8, CRC-16, CRC-32, or CRC-64, including full support for VPCLMULQDQ. On long messages the resulting functions are up to 4x faster than the existing PCLMULQDQ optimized functions when they exist, or up to 29x faster than the existing table-based functions.
This patchset starts by wiring up the new macro for
crc32_le,crc_t10dif, andcrc32_be. Later I'd also like to wire upcrc64_beandcrc64_rocksoft, once the design of the library functions for those has been fixed to be like what I'm doing for crc32* andcrc_t10dif.A similar approach of sharing code between CRC variants, and vector lengths when applicable, should work for other architectures. The CRC constant generation script should be mostly reusable.
Quoted 8 lines
Eric Biggers (6):
x86: move zmm exclusion list into CPU feature flagscripts/crc: addgen-crc-consts.py
x86/crc: add "template" for [V]PCLMULQDQ based CRC functions x86/crc32: implementcrc32_leusing new template x86/crc-t10dif: implementcrc_t10difusing new template x86/crc32: implementcrc32_beusing new template
Good stuff!
Would indeed be nice to get CRC-64 implemented this way as well, so we can use it on both x86 and arm64.
Quoted 41 lines · Ard Biesheuvel
Quoted 31 lines · Eric Biggers
This patchset is also available in git via:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/ebiggers/linux.git crc-x86-v1
This patchset applies on top of my other recent CRC patchsets
https://lore.kernel.org/r/[email protected]/ and
https://lore.kernel.org/r/[email protected]/ . Consider it a preview for what may be coming next, as my priority is getting those two other patchsets merged first.This patchset adds a new assembly macro that expands into the body of a CRC function for x86 for the specified number of bits, bit order, vector length, and AVX level. There's also a new script that generates the constants needed by this function, given a CRC generator polynomial.
This approach allows easily wiring up an x86-optimized implementation of any variant of CRC-8, CRC-16, CRC-32, or CRC-64, including full support for VPCLMULQDQ. On long messages the resulting functions are up to 4x faster than the existing PCLMULQDQ optimized functions when they exist, or up to 29x faster than the existing table-based functions.
This patchset starts by wiring up the new macro for
crc32_le,crc_t10dif, andcrc32_be. Later I'd also like to wire upcrc64_beandcrc64_rocksoft, once the design of the library functions for those has been fixed to be like what I'm doing for crc32* andcrc_t10dif.A similar approach of sharing code between CRC variants, and vector lengths when applicable, should work for other architectures. The CRC constant generation script should be mostly reusable.
Quoted 8 lines
Eric Biggers (6):
x86: move zmm exclusion list into CPU feature flagscripts/crc: addgen-crc-consts.py
x86/crc: add "template" for [V]PCLMULQDQ based CRC functions x86/crc32: implementcrc32_leusing new template x86/crc-t10dif: implementcrc_t10difusing new template x86/crc32: implementcrc32_beusing new template
Quoted 6 lines
Good stuff!
Acked-by Ard Biesheuvel <[email protected]>Would indeed be nice to get CRC-64 implemented this way as well, so we can use it on both x86 and arm64.
Thanks! The template actually supports CRC-64 already (both LSB and MSB-first variants) and I've tested it in userspace. I just haven't wired it up to the kernel's CRC-64 functions yet.
- Eric