[llvm] [IR][NFC] Expose structural hash details (PR #200480)

Yaxun Liu via llvm-commits llvm-commits at lists.llvm.org
Wed Jul 29 09:15:47 PDT 2026


https://github.com/yxsamliu updated https://github.com/llvm/llvm-project/pull/200480

>From c6d8a425ce46ea3fb73136df87118b740b298a38 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Fri, 29 May 2026 14:39:07 -0400
Subject: [PATCH] [IR][NFC] Expose structural hash details

StructuralHash already computes block and function hashes while walking a
function. Expose those pieces so other users can reuse the same
change-detection logic instead of rebuilding a parallel hash walk.

This is NFC for existing users. The existing StructuralHash entry points
keep returning the same hashes.

This is intended for tools such as `-print-changed`, which need the
function hash for a quick skip check and block hashes to decide which
blocks changed.
---
 llvm/include/llvm/IR/StructuralHash.h    | 17 +++++++++++
 llvm/lib/IR/StructuralHash.cpp           | 39 +++++++++++++++++++++---
 llvm/unittests/IR/StructuralHashTest.cpp | 27 ++++++++++++++++
 3 files changed, 78 insertions(+), 5 deletions(-)

diff --git a/llvm/include/llvm/IR/StructuralHash.h b/llvm/include/llvm/IR/StructuralHash.h
index fc4b97ee2d41e..41ab733c596cc 100644
--- a/llvm/include/llvm/IR/StructuralHash.h
+++ b/llvm/include/llvm/IR/StructuralHash.h
@@ -15,6 +15,7 @@
 #define LLVM_IR_STRUCTURALHASH_H
 
 #include "llvm/ADT/MapVector.h"
+#include "llvm/ADT/SmallVector.h"
 #include "llvm/ADT/StableHashing.h"
 #include "llvm/IR/Instruction.h"
 #include "llvm/Support/Compiler.h"
@@ -22,9 +23,20 @@
 
 namespace llvm {
 
+class BasicBlock;
 class Function;
 class Module;
 
+struct BasicBlockStructuralHashInfo {
+  const BasicBlock *BB = nullptr;
+  stable_hash BlockHash = 0;
+};
+
+struct FunctionStructuralHashInfo {
+  stable_hash FunctionHash = 0;
+  SmallVector<BasicBlockStructuralHashInfo, 0> Blocks;
+};
+
 /// Returns a hash of the function \p F.
 /// \param F The function to hash.
 /// \param DetailedHash Whether or not to encode additional information in the
@@ -33,6 +45,11 @@ class Module;
 LLVM_ABI stable_hash StructuralHash(const Function &F,
                                     bool DetailedHash = false);
 
+/// Returns structural hash details for \p F, including the function hash and
+/// block hashes computed while building it.
+LLVM_ABI FunctionStructuralHashInfo
+StructuralHashWithDetails(const Function &F, bool DetailedHash = false);
+
 /// Returns a hash of the global variable \p G.
 LLVM_ABI stable_hash StructuralHash(const GlobalVariable &G);
 
diff --git a/llvm/lib/IR/StructuralHash.cpp b/llvm/lib/IR/StructuralHash.cpp
index 1c617c100c7dc..0ae2543f2b065 100644
--- a/llvm/lib/IR/StructuralHash.cpp
+++ b/llvm/lib/IR/StructuralHash.cpp
@@ -26,6 +26,7 @@ class StructuralHashImpl {
   stable_hash Hash = 4;
 
   bool DetailedHash;
+  bool CollectDetails;
 
   // This random value acts as a block header, as otherwise the partition of
   // opcodes into BBs wouldn't affect the hash, only the order of the opcodes.
@@ -42,6 +43,7 @@ class StructuralHashImpl {
   /// A mapping from pairs of instruction indices and operand indices
   /// to the hashes of the operands.
   std::unique_ptr<IndexOperandHashMapType> IndexOperandHashMap = nullptr;
+  FunctionStructuralHashInfo Details;
 
   /// Assign a unique ID to each Value in the order they are first seen.
   DenseMap<const Value *, int> ValueToId;
@@ -57,8 +59,10 @@ class StructuralHashImpl {
 public:
   StructuralHashImpl() = delete;
   explicit StructuralHashImpl(bool DetailedHash,
-                              IgnoreOperandFunc IgnoreOp = nullptr)
-      : DetailedHash(DetailedHash), IgnoreOp(IgnoreOp) {
+                              IgnoreOperandFunc IgnoreOp = nullptr,
+                              bool CollectDetails = false)
+      : DetailedHash(DetailedHash), CollectDetails(CollectDetails),
+        IgnoreOp(IgnoreOp) {
     if (IgnoreOp) {
       IndexInstruction = std::make_unique<IndexInstrMap>();
       IndexOperandHashMap = std::make_unique<IndexOperandHashMapType>();
@@ -257,8 +261,11 @@ class StructuralHashImpl {
   // selectively.
   void update(const Function &F) {
     // Declarations don't affect analyses.
-    if (F.isDeclaration())
+    if (F.isDeclaration()) {
+      if (CollectDetails)
+        Details.FunctionHash = Hash;
       return;
+    }
 
     SmallVector<stable_hash> Hashes;
     Hashes.emplace_back(Hash);
@@ -279,8 +286,18 @@ class StructuralHashImpl {
       const BasicBlock *BB = BBs.pop_back_val();
 
       Hashes.emplace_back(BlockHeaderHash);
-      for (auto &Inst : *BB)
-        Hashes.emplace_back(hashInstruction(Inst));
+      SmallVector<stable_hash> BlockHashes;
+      if (CollectDetails)
+        BlockHashes.emplace_back(BlockHeaderHash);
+
+      for (auto &Inst : *BB) {
+        stable_hash InstHash = hashInstruction(Inst);
+        Hashes.emplace_back(InstHash);
+        if (CollectDetails)
+          BlockHashes.emplace_back(InstHash);
+      }
+      if (CollectDetails)
+        Details.Blocks.push_back({BB, stable_hash_combine(BlockHashes)});
 
       for (const BasicBlock *Succ : successors(BB))
         if (VisitedBBs.insert(Succ).second)
@@ -289,6 +306,8 @@ class StructuralHashImpl {
 
     // Update the combined hash in place.
     Hash = stable_hash_combine(Hashes);
+    if (CollectDetails)
+      Details.FunctionHash = Hash;
   }
 
   void update(const GlobalVariable &GV) {
@@ -315,6 +334,8 @@ class StructuralHashImpl {
 
   uint64_t getHash() const { return Hash; }
 
+  FunctionStructuralHashInfo getDetails() { return std::move(Details); }
+
   std::unique_ptr<IndexInstrMap> getIndexInstrMap() {
     return std::move(IndexInstruction);
   }
@@ -332,6 +353,14 @@ stable_hash llvm::StructuralHash(const Function &F, bool DetailedHash) {
   return H.getHash();
 }
 
+FunctionStructuralHashInfo llvm::StructuralHashWithDetails(const Function &F,
+                                                           bool DetailedHash) {
+  StructuralHashImpl H(DetailedHash, /*IgnoreOp=*/nullptr,
+                       /*CollectDetails=*/true);
+  H.update(F);
+  return H.getDetails();
+}
+
 stable_hash llvm::StructuralHash(const GlobalVariable &GVar) {
   return StructuralHashImpl::hashGlobalVariable(GVar);
 }
diff --git a/llvm/unittests/IR/StructuralHashTest.cpp b/llvm/unittests/IR/StructuralHashTest.cpp
index 81c17120a1f6f..2f8988697e7c5 100644
--- a/llvm/unittests/IR/StructuralHashTest.cpp
+++ b/llvm/unittests/IR/StructuralHashTest.cpp
@@ -70,6 +70,33 @@ TEST(StructuralHashTest, BasicFunction) {
             StructuralHash(*M->getFunction("h")));
 }
 
+TEST(StructuralHashTest, FunctionHashDetails) {
+  LLVMContext Ctx;
+  std::unique_ptr<Module> M = parseIR(Ctx, "define i32 @f(i32 %x) {\n"
+                                           "entry:\n"
+                                           "  %a = add i32 %x, 1\n"
+                                           "  ret i32 %a\n"
+                                           "}\n");
+  Function &F = *M->getFunction("f");
+
+  FunctionStructuralHashInfo Info =
+      StructuralHashWithDetails(F, /*DetailedHash=*/true);
+  EXPECT_EQ(StructuralHash(F, /*DetailedHash=*/true), Info.FunctionHash);
+  ASSERT_THAT(Info.Blocks, SizeIs(1));
+  EXPECT_EQ(&F.getEntryBlock(), Info.Blocks[0].BB);
+  EXPECT_NE(0u, Info.Blocks[0].BlockHash);
+}
+
+TEST(StructuralHashTest, FunctionHashDetailsForDeclaration) {
+  LLVMContext Ctx;
+  std::unique_ptr<Module> M = parseIR(Ctx, "declare void @f()\n");
+  Function &F = *M->getFunction("f");
+
+  FunctionStructuralHashInfo Info = StructuralHashWithDetails(F);
+  EXPECT_EQ(StructuralHash(F), Info.FunctionHash);
+  EXPECT_THAT(Info.Blocks, SizeIs(0));
+}
+
 TEST(StructuralHashTest, Declaration) {
   LLVMContext Ctx;
   std::unique_ptr<Module> M0 = parseIR(Ctx, "");



More information about the llvm-commits mailing list