| //===-- StructuralHash.cpp - IR Hashing -------------------------*- 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 | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/IR/StructuralHash.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" | |
| using namespace llvm; | |
| namespace { | |
| // Basic hashing mechanism to detect structural change to the IR, used to verify | |
| // pass return status consistency with actual change. In addition to being used | |
| // by the MergeFunctions pass. | |
| class StructuralHashImpl { | |
| stable_hash Hash = 4; | |
| bool DetailedHash; | |
| // 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. | |
| static constexpr stable_hash BlockHeaderHash = 45798; | |
| static constexpr stable_hash FunctionHeaderHash = 0x62642d6b6b2d6b72; | |
| static constexpr stable_hash GlobalHeaderHash = 23456; | |
| /// IgnoreOp is a function that returns true if the operand should be ignored. | |
| IgnoreOperandFunc IgnoreOp = nullptr; | |
| /// Whether name-based hashes of globals with local linkage are qualified | |
| /// with the source file name of their module. | |
| bool SourceQualifyLocalGlobals; | |
| /// Lazily computed hash of the source file name. An instance only hashes | |
| /// values from a single module, so one cached value suffices; 0 means the | |
| /// source file name is empty and hashes are left unqualified. | |
| std::optional<stable_hash> SourceNameHash; | |
| /// A mapping from instruction indices to instruction pointers. | |
| /// The index represents the position of an instruction based on the order in | |
| /// which it is first encountered. | |
| std::unique_ptr<IndexInstrMap> IndexInstruction = nullptr; | |
| /// A mapping from pairs of instruction indices and operand indices | |
| /// to the hashes of the operands. | |
| std::unique_ptr<IndexOperandHashMapType> IndexOperandHashMap = nullptr; | |
| /// Assign a unique ID to each Value in the order they are first seen. | |
| DenseMap<const Value *, int> ValueToId; | |
| static stable_hash hashType(Type *ValueType) { | |
| SmallVector<stable_hash> Hashes; | |
| Hashes.emplace_back(ValueType->getTypeID()); | |
| if (ValueType->isIntegerTy()) | |
| Hashes.emplace_back(ValueType->getIntegerBitWidth()); | |
| return stable_hash_combine(Hashes); | |
| } | |
| public: | |
| StructuralHashImpl() = delete; | |
| explicit StructuralHashImpl(bool DetailedHash, | |
| IgnoreOperandFunc IgnoreOp = nullptr, | |
| bool SourceQualifyLocalGlobals = false) | |
| : DetailedHash(DetailedHash), IgnoreOp(IgnoreOp), | |
| SourceQualifyLocalGlobals(SourceQualifyLocalGlobals) { | |
| if (IgnoreOp) { | |
| IndexInstruction = std::make_unique<IndexInstrMap>(); | |
| IndexOperandHashMap = std::make_unique<IndexOperandHashMapType>(); | |
| } | |
| } | |
| static stable_hash hashAPInt(const APInt &I) { | |
| SmallVector<stable_hash> Hashes; | |
| Hashes.emplace_back(I.getBitWidth()); | |
| auto RawVals = ArrayRef<uint64_t>(I.getRawData(), I.getNumWords()); | |
| Hashes.append(RawVals.begin(), RawVals.end()); | |
| return stable_hash_combine(Hashes); | |
| } | |
| static stable_hash hashAPFloat(const APFloat &F) { | |
| return hashAPInt(F.bitcastToAPInt()); | |
| } | |
| stable_hash hashGlobalName(const GlobalVariable &GVar) { | |
| stable_hash GlobalHash = hashGlobalValue(&GVar); | |
| if (!GlobalHash || !SourceQualifyLocalGlobals || !GVar.hasLocalLinkage()) | |
| return GlobalHash; | |
| if (!SourceNameHash) { | |
| StringRef SourceName = GVar.getParent()->getSourceFileName(); | |
| SourceNameHash = | |
| SourceName.empty() ? stable_hash(0) : stable_hash_name(SourceName); | |
| } | |
| if (!*SourceNameHash) | |
| return GlobalHash; | |
| return stable_hash_combine(*SourceNameHash, GlobalHash); | |
| } | |
| stable_hash hashGlobalVariable(const GlobalVariable &GVar) { | |
| if (!GVar.hasInitializer()) | |
| return hashGlobalName(GVar); | |
| // Hash the contents of a string. | |
| if (GVar.getName().starts_with(".str")) { | |
| auto *C = GVar.getInitializer(); | |
| if (const auto *Seq = dyn_cast<ConstantDataSequential>(C)) | |
| if (Seq->isString()) | |
| return stable_hash_name(Seq->getAsString()); | |
| } | |
| // Hash structural contents of Objective-C metadata in specific sections. | |
| // This can be extended to other metadata if needed. | |
| static constexpr const char *SectionNames[] = { | |
| "__cfstring", "__cstring", "__objc_classrefs", | |
| "__objc_methname", "__objc_selrefs", | |
| }; | |
| if (GVar.hasSection()) { | |
| StringRef SectionName = GVar.getSection(); | |
| for (const char *Name : SectionNames) | |
| if (SectionName.contains(Name)) | |
| return hashConstant(GVar.getInitializer()); | |
| } | |
| return hashGlobalName(GVar); | |
| } | |
| static stable_hash hashGlobalValue(const GlobalValue *GV) { | |
| if (!GV->hasName()) | |
| return 0; | |
| return stable_hash_name(GV->getName()); | |
| } | |
| // Compute a hash for a Constant. This function is logically similar to | |
| // FunctionComparator::cmpConstants() in FunctionComparator.cpp, but here | |
| // we're interested in computing a hash rather than comparing two Constants. | |
| // Some of the logic is simplified, e.g, we don't expand GEPOperator. | |
| stable_hash hashConstant(const Constant *C) { | |
| SmallVector<stable_hash> Hashes; | |
| Type *Ty = C->getType(); | |
| Hashes.emplace_back(hashType(Ty)); | |
| if (C->isNullValue()) { | |
| Hashes.emplace_back(static_cast<stable_hash>('N')); | |
| return stable_hash_combine(Hashes); | |
| } | |
| if (auto *GVar = dyn_cast<GlobalVariable>(C)) { | |
| Hashes.emplace_back(hashGlobalVariable(*GVar)); | |
| return stable_hash_combine(Hashes); | |
| } | |
| if (auto *G = dyn_cast<GlobalValue>(C)) { | |
| Hashes.emplace_back(hashGlobalValue(G)); | |
| return stable_hash_combine(Hashes); | |
| } | |
| if (const auto *Seq = dyn_cast<ConstantDataSequential>(C)) { | |
| if (Seq->isString()) { | |
| Hashes.emplace_back(stable_hash_name(Seq->getAsString())); | |
| return stable_hash_combine(Hashes); | |
| } | |
| } | |
| switch (C->getValueID()) { | |
| case Value::ConstantIntVal: { | |
| const APInt &Int = cast<ConstantInt>(C)->getValue(); | |
| Hashes.emplace_back(hashAPInt(Int)); | |
| return stable_hash_combine(Hashes); | |
| } | |
| case Value::ConstantFPVal: { | |
| const APFloat &APF = cast<ConstantFP>(C)->getValueAPF(); | |
| Hashes.emplace_back(hashAPFloat(APF)); | |
| return stable_hash_combine(Hashes); | |
| } | |
| case Value::ConstantArrayVal: | |
| case Value::ConstantStructVal: | |
| case Value::ConstantVectorVal: | |
| case Value::ConstantExprVal: { | |
| for (const auto &Op : C->operands()) { | |
| auto H = hashConstant(cast<Constant>(Op)); | |
| Hashes.emplace_back(H); | |
| } | |
| return stable_hash_combine(Hashes); | |
| } | |
| case Value::BlockAddressVal: { | |
| const BlockAddress *BA = cast<BlockAddress>(C); | |
| auto H = hashGlobalValue(BA->getFunction()); | |
| Hashes.emplace_back(H); | |
| return stable_hash_combine(Hashes); | |
| } | |
| case Value::DSOLocalEquivalentVal: { | |
| const auto *Equiv = cast<DSOLocalEquivalent>(C); | |
| auto H = hashGlobalValue(Equiv->getGlobalValue()); | |
| Hashes.emplace_back(H); | |
| return stable_hash_combine(Hashes); | |
| } | |
| default: | |
| // Skip other types of constants for simplicity. | |
| return stable_hash_combine(Hashes); | |
| } | |
| } | |
| stable_hash hashValue(Value *V) { | |
| // Check constant and return its hash. | |
| Constant *C = dyn_cast<Constant>(V); | |
| if (C) | |
| return hashConstant(C); | |
| // Hash argument number. | |
| SmallVector<stable_hash> Hashes; | |
| if (Argument *Arg = dyn_cast<Argument>(V)) | |
| Hashes.emplace_back(Arg->getArgNo()); | |
| // Get an index (an insertion order) for the non-constant value. | |
| auto [It, WasInserted] = ValueToId.try_emplace(V, ValueToId.size()); | |
| Hashes.emplace_back(It->second); | |
| return stable_hash_combine(Hashes); | |
| } | |
| stable_hash hashOperand(Value *Operand) { | |
| SmallVector<stable_hash> Hashes; | |
| Hashes.emplace_back(hashType(Operand->getType())); | |
| Hashes.emplace_back(hashValue(Operand)); | |
| return stable_hash_combine(Hashes); | |
| } | |
| stable_hash hashInstruction(const Instruction &Inst) { | |
| SmallVector<stable_hash> Hashes; | |
| Hashes.emplace_back(Inst.getOpcode()); | |
| if (!DetailedHash) | |
| return stable_hash_combine(Hashes); | |
| Hashes.emplace_back(hashType(Inst.getType())); | |
| // 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()); | |
| unsigned InstIdx = 0; | |
| if (IndexInstruction) { | |
| InstIdx = IndexInstruction->size(); | |
| IndexInstruction->try_emplace(InstIdx, const_cast<Instruction *>(&Inst)); | |
| } | |
| for (const auto [OpndIdx, Op] : enumerate(Inst.operands())) { | |
| auto OpndHash = hashOperand(Op); | |
| if (IgnoreOp && IgnoreOp(&Inst, OpndIdx)) { | |
| assert(IndexOperandHashMap); | |
| IndexOperandHashMap->try_emplace({InstIdx, OpndIdx}, OpndHash); | |
| } else | |
| Hashes.emplace_back(OpndHash); | |
| } | |
| return stable_hash_combine(Hashes); | |
| } | |
| // A function hash is calculated by considering only the number of arguments | |
| // and whether a function is varargs, the order of basic blocks (given by the | |
| // successors of each basic block in depth first order), and the order of | |
| // opcodes of each instruction within each of these basic blocks. This mirrors | |
| // the strategy FunctionComparator::compare() uses to compare functions by | |
| // walking the BBs in depth first order and comparing each instruction in | |
| // sequence. Because this hash currently does not look at the operands, it is | |
| // insensitive to things such as the target of calls and the constants used in | |
| // the function, which makes it useful when possibly merging functions which | |
| // are the same modulo constants and call targets. | |
| // | |
| // Note that different users of StructuralHash will want different behavior | |
| // out of it (i.e., MergeFunctions will want something different from PM | |
| // expensive checks for pass modification status). When modifying this | |
| // function, most changes should be gated behind an option and enabled | |
| // selectively. | |
| void update(const Function &F) { | |
| // Declarations don't affect analyses. | |
| if (F.isDeclaration()) | |
| return; | |
| SmallVector<stable_hash> Hashes; | |
| Hashes.emplace_back(Hash); | |
| Hashes.emplace_back(FunctionHeaderHash); | |
| Hashes.emplace_back(F.isVarArg()); | |
| Hashes.emplace_back(F.arg_size()); | |
| SmallVector<const BasicBlock *, 8> BBs; | |
| SmallPtrSet<const BasicBlock *, 16> VisitedBBs; | |
| // Walk the blocks in the same order as | |
| // FunctionComparator::cmpBasicBlocks(), accumulating the hash of the | |
| // function "structure." (BB and opcode sequence) | |
| BBs.push_back(&F.getEntryBlock()); | |
| VisitedBBs.insert(BBs[0]); | |
| while (!BBs.empty()) { | |
| const BasicBlock *BB = BBs.pop_back_val(); | |
| Hashes.emplace_back(BlockHeaderHash); | |
| for (auto &Inst : *BB) | |
| Hashes.emplace_back(hashInstruction(Inst)); | |
| for (const BasicBlock *Succ : successors(BB)) | |
| if (VisitedBBs.insert(Succ).second) | |
| BBs.push_back(Succ); | |
| } | |
| // Update the combined hash in place. | |
| Hash = stable_hash_combine(Hashes); | |
| } | |
| void update(const GlobalVariable &GV) { | |
| // Declarations and used/compiler.used don't affect analyses. | |
| // Since there are several `llvm.*` metadata, like `llvm.embedded.object`, | |
| // we ignore anything with the `.llvm` prefix | |
| if (GV.isDeclaration() || GV.getName().starts_with("llvm.")) | |
| return; | |
| SmallVector<stable_hash> Hashes; | |
| Hashes.emplace_back(Hash); | |
| Hashes.emplace_back(GlobalHeaderHash); | |
| Hashes.emplace_back(GV.getValueType()->getTypeID()); | |
| // Update the combined hash in place. | |
| Hash = stable_hash_combine(Hashes); | |
| } | |
| void update(const Module &M) { | |
| for (const GlobalVariable &GV : M.globals()) | |
| update(GV); | |
| for (const Function &F : M) | |
| update(F); | |
| } | |
| uint64_t getHash() const { return Hash; } | |
| std::unique_ptr<IndexInstrMap> getIndexInstrMap() { | |
| return std::move(IndexInstruction); | |
| } | |
| std::unique_ptr<IndexOperandHashMapType> getIndexPairOpndHashMap() { | |
| return std::move(IndexOperandHashMap); | |
| } | |
| }; | |
| } // namespace | |
| stable_hash llvm::StructuralHash(const Function &F, bool DetailedHash) { | |
| StructuralHashImpl H(DetailedHash); | |
| H.update(F); | |
| return H.getHash(); | |
| } | |
| stable_hash llvm::StructuralHash(const GlobalVariable &GVar, | |
| bool SourceQualifyLocalGlobals) { | |
| StructuralHashImpl H(/*DetailedHash=*/false, /*IgnoreOp=*/nullptr, | |
| SourceQualifyLocalGlobals); | |
| return H.hashGlobalVariable(GVar); | |
| } | |
| stable_hash llvm::StructuralHash(const Module &M, bool DetailedHash) { | |
| StructuralHashImpl H(DetailedHash); | |
| H.update(M); | |
| return H.getHash(); | |
| } | |
| FunctionHashInfo | |
| llvm::StructuralHashWithDifferences(const Function &F, | |
| IgnoreOperandFunc IgnoreOp, | |
| bool SourceQualifyLocalGlobals) { | |
| StructuralHashImpl H(/*DetailedHash=*/true, IgnoreOp, | |
| SourceQualifyLocalGlobals); | |
| H.update(F); | |
| return FunctionHashInfo(H.getHash(), H.getIndexInstrMap(), | |
| H.getIndexPairOpndHashMap()); | |
| } |