[llvm] [IR] Include semantic fields in detailed structural hashes (PR #200488)

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


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

>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 1/2] [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, "");

>From aab79fec220b48d3cb46b70321aa03aadb093371 Mon Sep 17 00:00:00 2001
From: "Yaxun (Sam) Liu" <yaxun.liu at amd.com>
Date: Fri, 29 May 2026 14:54:53 -0400
Subject: [PATCH 2/2] [IR] Include semantic fields in detailed structural
 hashes

Detailed structural hashes are used to detect IR changes. Include semantic
fields that are not fully represented by opcode, type, and operand hashing.

This covers instruction flags, memory operation properties, call properties,
and function attributes.
---
 llvm/include/llvm/IR/FMF.h               |   1 +
 llvm/lib/IR/StructuralHash.cpp           |  55 +++++++
 llvm/unittests/IR/StructuralHashTest.cpp | 179 +++++++++++++++++++++++
 3 files changed, 235 insertions(+)

diff --git a/llvm/include/llvm/IR/FMF.h b/llvm/include/llvm/IR/FMF.h
index e55e64f3905af..9dcf7e01169e6 100644
--- a/llvm/include/llvm/IR/FMF.h
+++ b/llvm/include/llvm/IR/FMF.h
@@ -56,6 +56,7 @@ class FastMathFlags {
   bool any() const { return Flags != 0; }
   bool none() const { return Flags == 0; }
   bool all() const { return Flags == AllFlagsMask; }
+  unsigned getRawFlags() const { return Flags; }
 
   void clear() { Flags = 0; }
   void set() { Flags = AllFlagsMask; }
diff --git a/llvm/lib/IR/StructuralHash.cpp b/llvm/lib/IR/StructuralHash.cpp
index 0ae2543f2b065..d17f51fd618b2 100644
--- a/llvm/lib/IR/StructuralHash.cpp
+++ b/llvm/lib/IR/StructuralHash.cpp
@@ -7,12 +7,15 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/IR/StructuralHash.h"
+#include "llvm/ADT/SmallString.h"
 #include "llvm/IR/Function.h"
 #include "llvm/IR/GlobalVariable.h"
 #include "llvm/IR/InstrTypes.h"
 #include "llvm/IR/Instructions.h"
 #include "llvm/IR/IntrinsicInst.h"
 #include "llvm/IR/Module.h"
+#include "llvm/IR/Operator.h"
+#include "llvm/Support/raw_ostream.h"
 
 using namespace llvm;
 
@@ -81,6 +84,13 @@ class StructuralHashImpl {
     return hashAPInt(F.bitcastToAPInt());
   }
 
+  static stable_hash hashAttributeList(AttributeList Attrs) {
+    SmallString<128> AttrString;
+    raw_svector_ostream OS(AttrString);
+    Attrs.print(OS);
+    return stable_hash_name(AttrString);
+  }
+
   static stable_hash hashGlobalVariable(const GlobalVariable &GVar) {
     if (!GVar.hasInitializer())
       return hashGlobalValue(&GVar);
@@ -219,12 +229,53 @@ class StructuralHashImpl {
       return stable_hash_combine(Hashes);
 
     Hashes.emplace_back(hashType(Inst.getType()));
+    Hashes.emplace_back(Inst.getRawSubclassOptionalData());
+    if (const auto *FPO = dyn_cast<FPMathOperator>(&Inst))
+      Hashes.emplace_back(FPO->getFastMathFlags().getRawFlags());
 
     // Handle additional properties of specific instructions that cause
     // semantic differences in the IR.
     if (const auto *ComparisonInstruction = dyn_cast<CmpInst>(&Inst))
       Hashes.emplace_back(ComparisonInstruction->getPredicate());
 
+    if (const auto *LI = dyn_cast<LoadInst>(&Inst)) {
+      Hashes.emplace_back(LI->isVolatile());
+      Hashes.emplace_back(LI->getAlign().value());
+      Hashes.emplace_back(static_cast<unsigned>(LI->getOrdering()));
+      Hashes.emplace_back(LI->getSyncScopeID());
+    }
+    if (const auto *SI = dyn_cast<StoreInst>(&Inst)) {
+      Hashes.emplace_back(SI->isVolatile());
+      Hashes.emplace_back(SI->getAlign().value());
+      Hashes.emplace_back(static_cast<unsigned>(SI->getOrdering()));
+      Hashes.emplace_back(SI->getSyncScopeID());
+    }
+    if (const auto *FI = dyn_cast<FenceInst>(&Inst)) {
+      Hashes.emplace_back(static_cast<unsigned>(FI->getOrdering()));
+      Hashes.emplace_back(FI->getSyncScopeID());
+    }
+    if (const auto *CXI = dyn_cast<AtomicCmpXchgInst>(&Inst)) {
+      Hashes.emplace_back(CXI->isVolatile());
+      Hashes.emplace_back(CXI->isWeak());
+      Hashes.emplace_back(CXI->getAlign().value());
+      Hashes.emplace_back(static_cast<unsigned>(CXI->getSuccessOrdering()));
+      Hashes.emplace_back(static_cast<unsigned>(CXI->getFailureOrdering()));
+      Hashes.emplace_back(CXI->getSyncScopeID());
+    }
+    if (const auto *RMWI = dyn_cast<AtomicRMWInst>(&Inst)) {
+      Hashes.emplace_back(RMWI->isVolatile());
+      Hashes.emplace_back(RMWI->getOperation());
+      Hashes.emplace_back(RMWI->getAlign().value());
+      Hashes.emplace_back(static_cast<unsigned>(RMWI->getOrdering()));
+      Hashes.emplace_back(RMWI->getSyncScopeID());
+    }
+    if (const auto *CB = dyn_cast<CallBase>(&Inst)) {
+      Hashes.emplace_back(CB->getCallingConv());
+      Hashes.emplace_back(hashAttributeList(CB->getAttributes()));
+      if (const auto *CI = dyn_cast<CallInst>(CB))
+        Hashes.emplace_back(CI->getTailCallKind());
+    }
+
     unsigned InstIdx = 0;
     if (IndexInstruction) {
       InstIdx = IndexInstruction->size();
@@ -273,6 +324,10 @@ class StructuralHashImpl {
 
     Hashes.emplace_back(F.isVarArg());
     Hashes.emplace_back(F.arg_size());
+    if (DetailedHash) {
+      Hashes.emplace_back(F.getCallingConv());
+      Hashes.emplace_back(hashAttributeList(F.getAttributes()));
+    }
 
     SmallVector<const BasicBlock *, 8> BBs;
     SmallPtrSet<const BasicBlock *, 16> VisitedBBs;
diff --git a/llvm/unittests/IR/StructuralHashTest.cpp b/llvm/unittests/IR/StructuralHashTest.cpp
index 2f8988697e7c5..054b0a59cde90 100644
--- a/llvm/unittests/IR/StructuralHashTest.cpp
+++ b/llvm/unittests/IR/StructuralHashTest.cpp
@@ -203,6 +203,185 @@ TEST(StructuralHashTest, ComparisonInstructionPredicate) {
   EXPECT_NE(StructuralHash(*M1, true), StructuralHash(*M2, true));
 }
 
+TEST(StructuralHashTest, InstructionFlags) {
+  LLVMContext Ctx;
+
+  auto FunctionHash = [&](const char *IR) {
+    return StructuralHash(*parseIR(Ctx, IR)->getFunction("f"),
+                          /*DetailedHash=*/true);
+  };
+
+  EXPECT_NE(FunctionHash("define i32 @f(i32 %a, i32 %b) {\n"
+                         "  %r = add i32 %a, %b\n"
+                         "  ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("define i32 @f(i32 %a, i32 %b) {\n"
+                         "  %r = add nsw i32 %a, %b\n"
+                         "  ret i32 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define i32 @f(i32 %a, i32 %b) {\n"
+                         "  %r = udiv i32 %a, %b\n"
+                         "  ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("define i32 @f(i32 %a, i32 %b) {\n"
+                         "  %r = udiv exact i32 %a, %b\n"
+                         "  ret i32 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define i1 @f(i32 %a, i32 %b) {\n"
+                         "  %r = icmp slt i32 %a, %b\n"
+                         "  ret i1 %r\n"
+                         "}\n"),
+            FunctionHash("define i1 @f(i32 %a, i32 %b) {\n"
+                         "  %r = icmp samesign slt i32 %a, %b\n"
+                         "  ret i1 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define float @f(float %a, float %b) {\n"
+                         "  %r = fadd float %a, %b\n"
+                         "  ret float %r\n"
+                         "}\n"),
+            FunctionHash("define float @f(float %a, float %b) {\n"
+                         "  %r = fadd fast float %a, %b\n"
+                         "  ret float %r\n"
+                         "}\n"));
+}
+
+TEST(StructuralHashTest, MemoryInstructionProperties) {
+  LLVMContext Ctx;
+
+  auto FunctionHash = [&](const char *IR) {
+    return StructuralHash(*parseIR(Ctx, IR)->getFunction("f"),
+                          /*DetailedHash=*/true);
+  };
+
+  EXPECT_NE(FunctionHash("define i32 @f(ptr %p) {\n"
+                         " %r = load i32, ptr %p, align 4\n"
+                         " ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("define i32 @f(ptr %p) {\n"
+                         " %r = load i32, ptr %p, align 8\n"
+                         " ret i32 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define i32 @f(ptr %p) {\n"
+                         " %r = load i32, ptr %p\n"
+                         " ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("define i32 @f(ptr %p) {\n"
+                         " %r = load volatile i32, ptr %p\n"
+                         " ret i32 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define i32 @f(ptr %p) {\n"
+                         " %r = load atomic i32, ptr %p monotonic, align 4\n"
+                         " ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("define i32 @f(ptr %p) {\n"
+                         " %r = load atomic i32, ptr %p acquire, align 4\n"
+                         " ret i32 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define void @f(ptr %p, i32 %v) {\n"
+                         " store i32 %v, ptr %p\n"
+                         " ret void\n"
+                         "}\n"),
+            FunctionHash("define void @f(ptr %p, i32 %v) {\n"
+                         " store volatile i32 %v, ptr %p\n"
+                         " ret void\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define void @f(ptr %p, i32 %v) {\n"
+                         " store atomic i32 %v, ptr %p monotonic, align 4\n"
+                         " ret void\n"
+                         "}\n"),
+            FunctionHash("define void @f(ptr %p, i32 %v) {\n"
+                         " store atomic i32 %v, ptr %p release, align 4\n"
+                         " ret void\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define void @f() {\n"
+                         " fence acquire\n"
+                         " ret void\n"
+                         "}\n"),
+            FunctionHash("define void @f() {\n"
+                         " fence seq_cst\n"
+                         " ret void\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define void @f(ptr %p, i32 %old, i32 %new) {\n"
+                         " %r = cmpxchg ptr %p, i32 %old, i32 %new "
+                         "monotonic monotonic\n"
+                         " ret void\n"
+                         "}\n"),
+            FunctionHash("define void @f(ptr %p, i32 %old, i32 %new) {\n"
+                         " %r = cmpxchg weak ptr %p, i32 %old, i32 %new "
+                         "monotonic monotonic\n"
+                         " ret void\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define void @f(ptr %p, i32 %v) {\n"
+                         " %r = atomicrmw add ptr %p, i32 %v monotonic\n"
+                         " ret void\n"
+                         "}\n"),
+            FunctionHash("define void @f(ptr %p, i32 %v) {\n"
+                         " %r = atomicrmw sub ptr %p, i32 %v monotonic\n"
+                         " ret void\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define i32 @f(ptr %p) {\n"
+                         " %r = load atomic i32, ptr %p monotonic, align 4\n"
+                         " ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("define i32 @f(ptr %p) {\n"
+                         " %r = load atomic i32, ptr %p "
+                         "syncscope(\"singlethread\") monotonic, align 4\n"
+                         " ret i32 %r\n"
+                         "}\n"));
+}
+
+TEST(StructuralHashTest, CallAndFunctionAttributes) {
+  LLVMContext Ctx;
+
+  auto FunctionHash = [&](const char *IR) {
+    return StructuralHash(*parseIR(Ctx, IR)->getFunction("f"),
+                          /*DetailedHash=*/true);
+  };
+
+  EXPECT_NE(FunctionHash("declare i32 @callee(i32)\n"
+                         "define i32 @f(i32 %v) {\n"
+                         " %r = call i32 @callee(i32 %v)\n"
+                         " ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("declare i32 @callee(i32)\n"
+                         "define i32 @f(i32 %v) {\n"
+                         " %r = tail call i32 @callee(i32 %v)\n"
+                         " ret i32 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define i32 @f(i32 %v) {\n"
+                         " ret i32 %v\n"
+                         "}\n"),
+            FunctionHash("define i32 @f(i32 %v) nounwind {\n"
+                         " ret i32 %v\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("declare i32 @callee(i32)\n"
+                         "define i32 @f(i32 %v) {\n"
+                         " %r = call i32 @callee(i32 %v)\n"
+                         " ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("declare i32 @callee(i32)\n"
+                         "define i32 @f(i32 %v) {\n"
+                         " %r = call i32 @callee(i32 noundef %v)\n"
+                         " ret i32 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("declare i32 @callee(i32)\n"
+                         "define i32 @f(i32 %v) {\n"
+                         " %r = call i32 @callee(i32 %v)\n"
+                         " ret i32 %r\n"
+                         "}\n"),
+            FunctionHash("declare fastcc i32 @callee(i32)\n"
+                         "define i32 @f(i32 %v) {\n"
+                         " %r = call fastcc i32 @callee(i32 %v)\n"
+                         " ret i32 %r\n"
+                         "}\n"));
+  EXPECT_NE(FunctionHash("define i32 @f(i32 %v) {\n"
+                         " ret i32 %v\n"
+                         "}\n"),
+            FunctionHash("define fastcc i32 @f(i32 %v) {\n"
+                         " ret i32 %v\n"
+                         "}\n"));
+}
+
 TEST(StructuralHashTest, IntrinsicInstruction) {
   LLVMContext Ctx;
   std::unique_ptr<Module> M1 =



More information about the llvm-commits mailing list