[llvm] [HashRecognize] Introduce new analysis (PR #139120)

Piotr Fusik via llvm-commits llvm-commits at lists.llvm.org
Tue May 13 06:26:05 PDT 2025


================
@@ -0,0 +1,682 @@
+//===- HashRecognize.h ------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// The HashRecognize analysis recognizes unoptimized polynomial hash functions
+// with operations over a Galois field of characteristic 2, also called binary
+// fields, or GF(2^n): this class of hash functions can be optimized using a
+// lookup-table-driven implementation, or with target-specific instructions.
+// Examples:
+//
+//  1. Cyclic redundancy check (CRC), which is a polynomial division in GF(2).
+//  2. Rabin fingerprint, a component of the Rabin-Karp algorithm, which is a
+//     rolling hash polynomial division in GF(2).
+//  3. Rijndael MixColumns, a step in AES computation, which is a polynomial
+//     multiplication in GF(2^3).
+//  4. GHASH, the authentication mechanism in AES Galois/Counter Mode (GCM),
+//     which is a polynomial evaluation in GF(2^128).
+//
+// All of them use an irreducible generating polynomial of degree m,
+//
+//    c_m * x^m + c_(m-1) * x^(m-1) + ... + c_0 * x^0
+//
+// where each coefficient c is can take values in GF(2^n), where 2^n is termed
+// the order of the Galois field. For GF(2), each coefficient can take values
+// either 0 or 1, and the polynomial is simply represented by m+1 bits,
+// corresponding to the coefficients. The different variants of CRC are named by
+// degree of generating polynomial used: so CRC-32 would use a polynomial of
+// degree 32.
+//
+// The reason algorithms on GF(2^n) can be optimized with a lookup-table is the
+// following: in such fields, polynomial addition and subtraction are identical
+// and equivalent to XOR, polynomial multiplication is an AND, and polynomial
+// division is identity: the XOR and AND operations in unoptimized
+// implmentations are performed bit-wise, and can be optimized to be performed
+// chunk-wise, by interleaving copies of the generating polynomial, and storing
+// the pre-computed values in a table.
+//
+// A generating polynomial of m bits always has the MSB set, so we usually
+// omit it. An example of a 16-bit polynomial is the CRC-16-CCITT polynomial:
+//
+//   (x^16) + x^12 + x^5 + 1 = (1) 0001 0000 0010 0001 = 0x1021
+//
+// Transmissions are either in big-endian or little-endian form, and hash
+// algorithms are written according to this. For example, IEEE 802 and RS-232
+// specify little-endian transmission.
+//
+//===----------------------------------------------------------------------===//
+//
+// At the moment, we only recognize the CRC algorithm.
+// Documentation on CRC32 from the kernel:
+// https://www.kernel.org/doc/Documentation/crc32.txt
+//
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Analysis/HashRecognize.h"
+#include "llvm/ADT/APInt.h"
+#include "llvm/ADT/SmallSet.h"
+#include "llvm/Analysis/LoopAnalysisManager.h"
+#include "llvm/Analysis/LoopInfo.h"
+#include "llvm/Analysis/ScalarEvolution.h"
+#include "llvm/Analysis/ScalarEvolutionPatternMatch.h"
+#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/IR/PatternMatch.h"
+#include "llvm/Support/KnownBits.h"
+
+using namespace llvm;
+
+#define DEBUG_TYPE "hash-recognize"
+
+// KnownBits for a PHI node. There are at most two PHI nodes, corresponding to
+// the Simple Recurrence and Conditional Recurrence. The IndVar PHI is not
+// relevant.
+using KnownPhiMap = SmallDenseMap<const PHINode *, KnownBits, 2>;
+
+// A pair of a PHI node along with its incoming value from within a loop.
+using PhiStepPair = std::pair<const PHINode *, const Instruction *>;
+
+/// A much simpler version of ValueTracking, in that it computes KnownBits of
+/// values, except that it computes the evolution of KnownBits in a loop with a
+/// given trip count, and predication is specialized for a significant-bit
+/// check.
+class ValueEvolution {
+  unsigned TripCount;
+  bool ByteOrderSwapped;
+  APInt GenPoly;
+  StringRef ErrStr;
+  unsigned AtIteration;
+
+  KnownBits computeBinOp(const BinaryOperator *I, const KnownPhiMap &KnownPhis);
+  KnownBits computeInstr(const Instruction *I, const KnownPhiMap &KnownPhis);
+  KnownBits compute(const Value *V, const KnownPhiMap &KnownPhis);
+
+public:
+  ValueEvolution(unsigned TripCount, bool ByteOrderSwapped);
+
+  // In case ValueEvolution encounters an error, these are meant to be used for
+  // a precise error message.
+  bool hasError() const;
+  StringRef getError() const;
+
+  // Given a list of PHI nodes along with their incoming value from within the
+  // loop, and the trip-count of the loop, computeEvolutions
+  // computes the KnownBits of each of the PHI nodes on the final iteration.
+  std::optional<KnownPhiMap>
+  computeEvolutions(ArrayRef<PhiStepPair> PhiEvolutions);
+};
+
+ValueEvolution::ValueEvolution(unsigned TripCount, bool ByteOrderSwapped)
+    : TripCount(TripCount), ByteOrderSwapped(ByteOrderSwapped) {}
+
+bool ValueEvolution::hasError() const { return !ErrStr.empty(); }
+StringRef ValueEvolution::getError() const { return ErrStr; }
+
+/// Compute the KnownBits of BinaryOperator \p I.
+KnownBits ValueEvolution::computeBinOp(const BinaryOperator *I,
+                                       const KnownPhiMap &KnownPhis) {
+  unsigned BitWidth = I->getType()->getScalarSizeInBits();
+
+  KnownBits KnownL(compute(I->getOperand(0), KnownPhis));
+  KnownBits KnownR(compute(I->getOperand(1), KnownPhis));
+
+  switch (I->getOpcode()) {
+  case Instruction::BinaryOps::And:
+    return KnownL & KnownR;
+  case Instruction::BinaryOps::Or:
+    return KnownL | KnownR;
+  case Instruction::BinaryOps::Xor:
+    return KnownL ^ KnownR;
+  case Instruction::BinaryOps::Shl: {
+    auto *OBO = cast<OverflowingBinaryOperator>(I);
+    return KnownBits::shl(KnownL, KnownR, OBO->hasNoUnsignedWrap(),
+                          OBO->hasNoSignedWrap());
+  }
+  case Instruction::BinaryOps::LShr:
+    return KnownBits::lshr(KnownL, KnownR);
+  case Instruction::BinaryOps::AShr:
+    return KnownBits::ashr(KnownL, KnownR);
+  case Instruction::BinaryOps::Add: {
+    auto *OBO = cast<OverflowingBinaryOperator>(I);
+    return KnownBits::add(KnownL, KnownR, OBO->hasNoUnsignedWrap(),
+                          OBO->hasNoSignedWrap());
+  }
+  case Instruction::BinaryOps::Sub: {
+    auto *OBO = cast<OverflowingBinaryOperator>(I);
+    return KnownBits::sub(KnownL, KnownR, OBO->hasNoUnsignedWrap(),
+                          OBO->hasNoSignedWrap());
+  }
+  case Instruction::BinaryOps::Mul: {
+    Value *Op0 = I->getOperand(0);
+    Value *Op1 = I->getOperand(1);
+    bool SelfMultiply = Op0 == Op1;
+    if (SelfMultiply)
+      SelfMultiply &= isGuaranteedNotToBeUndef(Op0);
----------------
pfusik wrote:

```suggestion
    bool SelfMultiply = Op0 == Op1 && isGuaranteedNotToBeUndef(Op0);
```

https://github.com/llvm/llvm-project/pull/139120


More information about the llvm-commits mailing list