|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | +# |
| 4 | +# Script that generates constants for computing the given CRC variant(s). |
| 5 | +# |
| 6 | +# Copyright 2025 Google LLC |
| 7 | +# |
| 8 | +# Author: Eric Biggers <[email protected]> |
| 9 | + |
| 10 | +import sys |
| 11 | + |
| 12 | +# XOR (add) an iterable of polynomials. |
| 13 | +def xor(iterable): |
| 14 | + res = 0 |
| 15 | + for val in iterable: |
| 16 | + res ^= val |
| 17 | + return res |
| 18 | + |
| 19 | +# Multiply two polynomials. |
| 20 | +def clmul(a, b): |
| 21 | + return xor(a << i for i in range(b.bit_length()) if (b & (1 << i)) != 0) |
| 22 | + |
| 23 | +# Polynomial division floor(a / b). |
| 24 | +def div(a, b): |
| 25 | + q = 0 |
| 26 | + while a.bit_length() >= b.bit_length(): |
| 27 | + q ^= 1 << (a.bit_length() - b.bit_length()) |
| 28 | + a ^= b << (a.bit_length() - b.bit_length()) |
| 29 | + return q |
| 30 | + |
| 31 | +# Reduce the polynomial 'a' modulo the polynomial 'b'. |
| 32 | +def reduce(a, b): |
| 33 | + return a ^ clmul(div(a, b), b) |
| 34 | + |
| 35 | +# Reflect the bits of a polynomial. |
| 36 | +def bitreflect(poly, num_bits): |
| 37 | + assert poly.bit_length() <= num_bits |
| 38 | + return xor(((poly >> i) & 1) << (num_bits - 1 - i) for i in range(num_bits)) |
| 39 | + |
| 40 | +# Format a polynomial as hex. Bit-reflect it if the CRC is lsb-first. |
| 41 | +def fmt_poly(variant, poly, num_bits): |
| 42 | + if variant.lsb: |
| 43 | + poly = bitreflect(poly, num_bits) |
| 44 | + return f'0x{poly:0{2*num_bits//8}x}' |
| 45 | + |
| 46 | +# Print a pair of 64-bit polynomial multipliers. They are always passed in the |
| 47 | +# order [HI64_TERMS, LO64_TERMS] but will be printed in the appropriate order. |
| 48 | +def print_mult_pair(variant, mults): |
| 49 | + mults = list(mults if variant.lsb else reversed(mults)) |
| 50 | + terms = ['HI64_TERMS', 'LO64_TERMS'] if variant.lsb else ['LO64_TERMS', 'HI64_TERMS'] |
| 51 | + for i in range(2): |
| 52 | + print(f'\t\t{fmt_poly(variant, mults[i]["val"], 64)},\t/* {terms[i]}: {mults[i]["desc"]} */') |
| 53 | + |
| 54 | +# Pretty-print a polynomial. |
| 55 | +def pprint_poly(prefix, poly): |
| 56 | + terms = [f'x^{i}' for i in reversed(range(poly.bit_length())) |
| 57 | + if (poly & (1 << i)) != 0] |
| 58 | + j = 0 |
| 59 | + while j < len(terms): |
| 60 | + s = prefix + terms[j] + (' +' if j < len(terms) - 1 else '') |
| 61 | + j += 1 |
| 62 | + while j < len(terms) and len(s) < 73: |
| 63 | + s += ' ' + terms[j] + (' +' if j < len(terms) - 1 else '') |
| 64 | + j += 1 |
| 65 | + print(s) |
| 66 | + prefix = ' * ' + (' ' * (len(prefix) - 3)) |
| 67 | + |
| 68 | +# Print a comment describing constants generated for the given CRC variant. |
| 69 | +def print_header(variant, what): |
| 70 | + print('/*') |
| 71 | + s = f'{"least" if variant.lsb else "most"}-significant-bit-first CRC-{variant.bits}' |
| 72 | + print(f' * {what} generated for {s} using') |
| 73 | + pprint_poly(' * G(x) = ', variant.G) |
| 74 | + print(' */') |
| 75 | + |
| 76 | +class CrcVariant: |
| 77 | + def __init__(self, bits, generator_poly, bit_order): |
| 78 | + self.bits = bits |
| 79 | + if bit_order not in ['lsb', 'msb']: |
| 80 | + raise ValueError('Invalid value for bit_order') |
| 81 | + self.lsb = bit_order == 'lsb' |
| 82 | + self.name = f'crc{bits}_{bit_order}_0x{generator_poly:0{(2*bits+7)//8}x}' |
| 83 | + if self.lsb: |
| 84 | + generator_poly = bitreflect(generator_poly, bits) |
| 85 | + self.G = generator_poly ^ (1 << bits) |
| 86 | + |
| 87 | +# Generate tables for CRC computation using the "slice-by-N" method. |
| 88 | +# N=1 corresponds to the traditional byte-at-a-time table. |
| 89 | +def gen_slicebyN_tables(variants, n): |
| 90 | + for v in variants: |
| 91 | + print('') |
| 92 | + print_header(v, f'Slice-by-{n} CRC table') |
| 93 | + print(f'static const u{v.bits} __maybe_unused {v.name}_table[{256*n}] = {{') |
| 94 | + s = '' |
| 95 | + for i in range(256 * n): |
| 96 | + # The i'th table entry is the CRC of the message consisting of byte |
| 97 | + # i % 256 followed by i // 256 zero bytes. |
| 98 | + poly = (bitreflect(i % 256, 8) if v.lsb else i % 256) << (v.bits + 8*(i//256)) |
| 99 | + next_entry = fmt_poly(v, reduce(poly, v.G), v.bits) + ',' |
| 100 | + if len(s + next_entry) > 71: |
| 101 | + print(f'\t{s}') |
| 102 | + s = '' |
| 103 | + s += (' ' if s else '') + next_entry |
| 104 | + if s: |
| 105 | + print(f'\t{s}') |
| 106 | + print('};') |
| 107 | + |
| 108 | +# Generate constants for carryless multiplication based CRC computation. |
| 109 | +def gen_x86_pclmul_consts(variants): |
| 110 | + # These are the distances, in bits, to generate folding constants for. |
| 111 | + FOLD_DISTANCES = [2048, 1024, 512, 256, 128] |
| 112 | + |
| 113 | + for v in variants: |
| 114 | + (G, n, lsb) = (v.G, v.bits, v.lsb) |
| 115 | + print('') |
| 116 | + print_header(v, 'CRC folding constants') |
| 117 | + print('static const struct {') |
| 118 | + if not lsb: |
| 119 | + print('\tu8 bswap_mask[16];') |
| 120 | + for i in FOLD_DISTANCES: |
| 121 | + print(f'\tu64 fold_across_{i}_bits_consts[2];') |
| 122 | + print('\tu8 shuf_table[48];') |
| 123 | + print('\tu64 barrett_reduction_consts[2];') |
| 124 | + print(f'}} {v.name}_consts ____cacheline_aligned __maybe_unused = {{') |
| 125 | + |
| 126 | + # Byte-reflection mask, needed for msb-first CRCs |
| 127 | + if not lsb: |
| 128 | + print('\t.bswap_mask = {' + ', '.join(str(i) for i in reversed(range(16))) + '},') |
| 129 | + |
| 130 | + # Fold constants for all distances down to 128 bits |
| 131 | + for i in FOLD_DISTANCES: |
| 132 | + print(f'\t.fold_across_{i}_bits_consts = {{') |
| 133 | + # Given 64x64 => 128 bit carryless multiplication instructions, two |
| 134 | + # 64-bit fold constants are needed per "fold distance" i: one for |
| 135 | + # HI64_TERMS that is basically x^(i+64) mod G and one for LO64_TERMS |
| 136 | + # that is basically x^i mod G. The exact values however undergo a |
| 137 | + # couple adjustments, described below. |
| 138 | + mults = [] |
| 139 | + for j in [64, 0]: |
| 140 | + pow_of_x = i + j |
| 141 | + if lsb: |
| 142 | + # Each 64x64 => 128 bit carryless multiplication instruction |
| 143 | + # actually generates a 127-bit product in physical bits 0 |
| 144 | + # through 126, which in the lsb-first case represent the |
| 145 | + # coefficients of x^1 through x^127, not x^0 through x^126. |
| 146 | + # Thus in the lsb-first case, each such instruction |
| 147 | + # implicitly adds an extra factor of x. The below removes a |
| 148 | + # factor of x from each constant to compensate for this. |
| 149 | + # For n < 64 the x could be removed from either the reduced |
| 150 | + # part or unreduced part, but for n == 64 the reduced part |
| 151 | + # is the only option. Just always use the reduced part. |
| 152 | + pow_of_x -= 1 |
| 153 | + # Make a factor of x^(64-n) be applied unreduced rather than |
| 154 | + # reduced, to cause the product to use only the x^(64-n) and |
| 155 | + # higher terms and always be zero in the lower terms. Usually |
| 156 | + # this makes no difference as it does not affect the product's |
| 157 | + # congruence class mod G and the constant remains 64-bit, but |
| 158 | + # part of the final reduction from 128 bits does rely on this |
| 159 | + # property when it reuses one of the constants. |
| 160 | + pow_of_x -= 64 - n |
| 161 | + mults.append({ 'val': reduce(1 << pow_of_x, G) << (64 - n), |
| 162 | + 'desc': f'(x^{pow_of_x} mod G) * x^{64-n}' }) |
| 163 | + print_mult_pair(v, mults) |
| 164 | + print('\t},') |
| 165 | + |
| 166 | + # Shuffle table for handling 1..15 bytes at end |
| 167 | + print('\t.shuf_table = {') |
| 168 | + print('\t\t' + (16*'-1, ').rstrip()) |
| 169 | + print('\t\t' + ''.join(f'{i:2}, ' for i in range(16)).rstrip()) |
| 170 | + print('\t\t' + (16*'-1, ').rstrip()) |
| 171 | + print('\t},') |
| 172 | + |
| 173 | + # Barrett reduction constants for reducing 128 bits to the final CRC |
| 174 | + print('\t.barrett_reduction_consts = {') |
| 175 | + mults = [] |
| 176 | + |
| 177 | + val = div(1 << (63+n), G) |
| 178 | + desc = f'floor(x^{63+n} / G)' |
| 179 | + if not lsb: |
| 180 | + val = (val << 1) - (1 << 64) |
| 181 | + desc = f'({desc} * x) - x^64' |
| 182 | + mults.append({ 'val': val, 'desc': desc }) |
| 183 | + |
| 184 | + val = G - (1 << n) |
| 185 | + desc = f'G - x^{n}' |
| 186 | + if lsb and n == 64: |
| 187 | + assert (val & 1) != 0 # The x^0 term should always be nonzero. |
| 188 | + val >>= 1 |
| 189 | + desc = f'({desc} - x^0) / x' |
| 190 | + else: |
| 191 | + pow_of_x = 64 - n - (1 if lsb else 0) |
| 192 | + val <<= pow_of_x |
| 193 | + desc = f'({desc}) * x^{pow_of_x}' |
| 194 | + mults.append({ 'val': val, 'desc': desc }) |
| 195 | + |
| 196 | + print_mult_pair(v, mults) |
| 197 | + print('\t},') |
| 198 | + |
| 199 | + print('};') |
| 200 | + |
| 201 | +def parse_crc_variants(vars_string): |
| 202 | + variants = [] |
| 203 | + for var_string in vars_string.split(','): |
| 204 | + bits, bit_order, generator_poly = var_string.split('_') |
| 205 | + assert bits.startswith('crc') |
| 206 | + bits = int(bits.removeprefix('crc')) |
| 207 | + assert generator_poly.startswith('0x') |
| 208 | + generator_poly = generator_poly.removeprefix('0x') |
| 209 | + assert len(generator_poly) % 2 == 0 |
| 210 | + generator_poly = int(generator_poly, 16) |
| 211 | + variants.append(CrcVariant(bits, generator_poly, bit_order)) |
| 212 | + return variants |
| 213 | + |
| 214 | +if len(sys.argv) != 3: |
| 215 | + sys.stderr.write(f'Usage: {sys.argv[0]} CONSTS_TYPE[,CONSTS_TYPE]... CRC_VARIANT[,CRC_VARIANT]...\n') |
| 216 | + sys.stderr.write(' CONSTS_TYPE can be sliceby[1-8] or x86_pclmul\n') |
| 217 | + sys.stderr.write(' CRC_VARIANT is crc${num_bits}_${bit_order}_${generator_poly_as_hex}\n') |
| 218 | + sys.stderr.write(' E.g. crc16_msb_0x8bb7 or crc32_lsb_0xedb88320\n') |
| 219 | + sys.stderr.write(' Polynomial must use the given bit_order and exclude x^{num_bits}\n') |
| 220 | + sys.exit(1) |
| 221 | + |
| 222 | +print('/* SPDX-License-Identifier: GPL-2.0-or-later */') |
| 223 | +print('/*') |
| 224 | +print(' * CRC constants generated by:') |
| 225 | +print(' *') |
| 226 | +print(f' *\t{sys.argv[0]} {" ".join(sys.argv[1:])}') |
| 227 | +print(' *') |
| 228 | +print(' * Do not edit manually.') |
| 229 | +print(' */') |
| 230 | +consts_types = sys.argv[1].split(',') |
| 231 | +variants = parse_crc_variants(sys.argv[2]) |
| 232 | +for consts_type in consts_types: |
| 233 | + if consts_type.startswith('sliceby'): |
| 234 | + gen_slicebyN_tables(variants, int(consts_type.removeprefix('sliceby'))) |
| 235 | + elif consts_type == 'x86_pclmul': |
| 236 | + gen_x86_pclmul_consts(variants) |
| 237 | + else: |
| 238 | + raise ValueError(f'Unknown consts_type: {consts_type}') |
0 commit comments