diff --git a/llvm/include/llvm/ADT/StableHashing.h b/llvm/include/llvm/ADT/StableHashing.h index 7852199f8b0a00..b220a0ed1f9131 100644 --- a/llvm/include/llvm/ADT/StableHashing.h +++ b/llvm/include/llvm/ADT/StableHashing.h @@ -53,6 +53,12 @@ inline stable_hash stable_hash_combine(stable_hash A, stable_hash B, // Removes suffixes introduced by LLVM from the name to enhance stability and // maintain closeness to the original name across different builds. inline StringRef get_stable_name(StringRef Name) { + // Return the part after ".content." that represents contents. + auto [P0, S0] = Name.rsplit(".content."); + if (!S0.empty()) + return S0; + + // Ignore these suffixes. auto [P1, S1] = Name.rsplit(".llvm."); auto [P2, S2] = P1.rsplit(".__uniq."); return P2; diff --git a/llvm/include/llvm/CodeGen/MachineOutliner.h b/llvm/include/llvm/CodeGen/MachineOutliner.h index eaba6c9b18f2bb..fbb958ccf6488e 100644 --- a/llvm/include/llvm/CodeGen/MachineOutliner.h +++ b/llvm/include/llvm/CodeGen/MachineOutliner.h @@ -18,6 +18,7 @@ #include "llvm/CodeGen/LiveRegUnits.h" #include "llvm/CodeGen/MachineFunction.h" #include "llvm/CodeGen/MachineRegisterInfo.h" +#include "llvm/CodeGen/MachineStableHash.h" #include namespace llvm { @@ -234,11 +235,11 @@ struct OutlinedFunction { unsigned FrameConstructionID = 0; /// Return the number of candidates for this \p OutlinedFunction. - unsigned getOccurrenceCount() const { return Candidates.size(); } + virtual unsigned getOccurrenceCount() const { return Candidates.size(); } /// Return the number of bytes it would take to outline this /// function. - unsigned getOutliningCost() const { + virtual unsigned getOutliningCost() const { unsigned CallOverhead = 0; for (const Candidate &C : Candidates) CallOverhead += C.getCallOverhead(); @@ -272,7 +273,42 @@ struct OutlinedFunction { } OutlinedFunction() = delete; + virtual ~OutlinedFunction() = default; }; + +/// The information necessary to create an outlined function that is matched +/// globally. +struct GlobalOutlinedFunction : public OutlinedFunction { + explicit GlobalOutlinedFunction(std::unique_ptr OF, + unsigned GlobalOccurrenceCount) + : OutlinedFunction(*OF), GlobalOccurrenceCount(GlobalOccurrenceCount) {} + + unsigned GlobalOccurrenceCount; + + /// Return the number of times that appear globally. + /// Global outlining candidate is uniquely created per each match, but this + /// might be erased out when it's overlapped with the previous outlining + /// instance. + unsigned getOccurrenceCount() const override { + assert(Candidates.size() <= 1); + return Candidates.empty() ? 0 : GlobalOccurrenceCount; + } + + /// Return the outlining cost using the global occurrence count + /// with the same cost as the first (unique) candidate. + unsigned getOutliningCost() const override { + assert(Candidates.size() <= 1); + unsigned CallOverhead = + Candidates.empty() + ? 0 + : Candidates[0].getCallOverhead() * getOccurrenceCount(); + return CallOverhead + SequenceSize + FrameOverhead; + } + + GlobalOutlinedFunction() = delete; + ~GlobalOutlinedFunction() = default; +}; + } // namespace outliner } // namespace llvm diff --git a/llvm/lib/CGData/CodeGenData.cpp b/llvm/lib/CGData/CodeGenData.cpp index 9dd4b1674e094a..55d2504231c744 100644 --- a/llvm/lib/CGData/CodeGenData.cpp +++ b/llvm/lib/CGData/CodeGenData.cpp @@ -24,6 +24,13 @@ using namespace llvm; using namespace cgdata; +cl::opt + CodeGenDataGenerate("codegen-data-generate", cl::init(false), cl::Hidden, + cl::desc("Emit CodeGen Data into custom sections")); +cl::opt + CodeGenDataUsePath("codegen-data-use-path", cl::init(""), cl::Hidden, + cl::desc("File path to where .cgdata file is read")); + static std::string getCGDataErrString(cgdata_error Err, const std::string &ErrMsg = "") { std::string Msg; @@ -132,7 +139,24 @@ CodeGenData &CodeGenData::getInstance() { std::call_once(CodeGenData::OnceFlag, []() { Instance = std::unique_ptr(new CodeGenData()); - // TODO: Initialize writer or reader mode for the client optimization. + if (CodeGenDataGenerate) + Instance->EmitCGData = true; + else if (!CodeGenDataUsePath.empty()) { + // Initialize the global CGData if the input file name is given. + // We do not error-out when failing to parse the input file. + // Instead, just emit an warning message and fall back as if no CGData + // were available. + auto FS = vfs::getRealFileSystem(); + auto ReaderOrErr = CodeGenDataReader::create(CodeGenDataUsePath, *FS); + if (Error E = ReaderOrErr.takeError()) { + warn(std::move(E), CodeGenDataUsePath); + return; + } + // Publish each CGData based on the data type in the header. + auto Reader = ReaderOrErr->get(); + if (Reader->hasOutlinedHashTree()) + Instance->publishOutlinedHashTree(Reader->releaseOutlinedHashTree()); + } }); return *(Instance.get()); } diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt index f1607f85c5b319..3e75737185c3ee 100644 --- a/llvm/lib/CodeGen/CMakeLists.txt +++ b/llvm/lib/CodeGen/CMakeLists.txt @@ -267,6 +267,7 @@ add_llvm_component_library(LLVMCodeGen Analysis BitReader BitWriter + CGData CodeGenTypes Core MC diff --git a/llvm/lib/CodeGen/MachineOutliner.cpp b/llvm/lib/CodeGen/MachineOutliner.cpp index 42f410c277179b..7736df2def77bc 100644 --- a/llvm/lib/CodeGen/MachineOutliner.cpp +++ b/llvm/lib/CodeGen/MachineOutliner.cpp @@ -59,7 +59,9 @@ #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/Twine.h" +#include "llvm/Analysis/ModuleSummaryAnalysis.h" #include "llvm/Analysis/OptimizationRemarkEmitter.h" +#include "llvm/CGData/CodeGenDataReader.h" #include "llvm/CodeGen/LivePhysRegs.h" #include "llvm/CodeGen/MachineModuleInfo.h" #include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h" @@ -75,6 +77,7 @@ #include "llvm/Support/Debug.h" #include "llvm/Support/SuffixTree.h" #include "llvm/Support/raw_ostream.h" +#include "llvm/Transforms/Utils/ModuleUtils.h" #include #include #include @@ -98,6 +101,10 @@ STATISTIC(NumInvisible, "Invisible instructions skipped during mapping"); STATISTIC(UnsignedVecSize, "Total number of instructions mapped and saved to mapping vector"); +STATISTIC(StableHashAttempts, + "Count of hashing attempts made for outlined functions"); +STATISTIC(StableHashDropped, + "Count of unsuccessful hashing attempts for outlined functions"); // Set to true if the user wants the outliner to run on linkonceodr linkage // functions. This is false by default because the linker can dedupe linkonceodr @@ -128,6 +135,19 @@ static cl::opt OutlinerLeafDescendants( "tree as candidates for outlining (if false, only leaf children " "are considered)")); +static cl::opt + DisableGlobalOutlining("disable-global-outlining", cl::Hidden, + cl::desc("Disable global outlining only by ignoring " + "the codegen data generation or use"), + cl::init(false)); + +static cl::opt AppendContentHashToOutlinedName( + "append-content-hash-outlined-name", cl::Hidden, + cl::desc("This appends the content hash to the globally outlined function " + "name. It's beneficial for enhancing the precision of the stable " + "hash and for ordering the outlined functions."), + cl::init(true)); + namespace { /// Maps \p MachineInstrs to unsigned integers and stores the mappings. @@ -421,11 +441,29 @@ struct MachineOutliner : public ModulePass { /// Set when the pass is constructed in TargetPassConfig. bool RunOnAllFunctions = true; + /// This is a compact representation of hash sequences of outlined functions. + /// It is used when OutlinerMode = CGDataMode::Write. + /// The resulting hash tree will be emitted into __llvm_outlined section + /// which will be dead-stripped not going to the final binary. + /// A post-process using llvm-cgdata, lld, or ThinLTO can merge them into + /// a global oulined hash tree for the subsequent codegen. + std::unique_ptr LocalHashTree; + + /// The mode of the outliner. + /// When is's CGDataMode::None, candidates are populated with the suffix tree + /// within a module and outlined. + /// When it's CGDataMode::Write, in addition to CGDataMode::None, the hash + /// sequences of outlined functions are published into LocalHashTree. + /// When it's CGDataMode::Read, candidates are populated with the global + /// outlined hash tree that has been built by the previous codegen. + CGDataMode OutlinerMode = CGDataMode::None; + StringRef getPassName() const override { return "Machine Outliner"; } void getAnalysisUsage(AnalysisUsage &AU) const override { AU.addRequired(); AU.addPreserved(); + AU.addRequired(); AU.setPreservesAll(); ModulePass::getAnalysisUsage(AU); } @@ -460,6 +498,16 @@ struct MachineOutliner : public ModulePass { findCandidates(InstructionMapper &Mapper, std::vector> &FunctionList); + /// Find all repeated substrings that match in the global outlined hash + /// tree built from the previous codegen. + /// + /// \param Mapper Contains outlining mapping information. + /// \param[out] FunctionList Filled with a list of \p OutlinedFunctions + /// each type of candidate. + void findGlobalCandidates( + InstructionMapper &Mapper, + std::vector> &FunctionList); + /// Replace the sequences of instructions represented by \p OutlinedFunctions /// with calls to functions. /// @@ -476,6 +524,17 @@ struct MachineOutliner : public ModulePass { InstructionMapper &Mapper, unsigned Name); + /// Compute and publish the stable hash sequence of instructions in the + /// outlined function, \p MF. The parameter \p CandSize represents the number + /// of candidates that have identical instruction sequences to \p MF. + void computeAndPublishHashSequence(MachineFunction &MF, unsigned CandSize); + + /// Initialize the outliner mode. + void initializeOutlinerMode(const Module &M); + + /// Emit the outlined hash tree into __llvm_outline section. + void emitOutlinedHashTree(Module &M); + /// Calls 'doOutline()' 1 + OutlinerReruns times. bool runOnModule(Module &M) override; @@ -585,6 +644,111 @@ void MachineOutliner::emitOutlinedFunctionRemark(OutlinedFunction &OF) { MORE.emit(R); } +struct MatchedEntry { + size_t StartIdx; + size_t Length; + size_t Count; +}; + +static const HashNode *followHashNode(stable_hash StableHash, + const HashNode *Current) { + auto I = Current->Successors.find(StableHash); + return (I == Current->Successors.end()) ? nullptr : I->second.get(); +} + +// Find all matches in the global outlined hash tree. +// It's quadratic complexity in theory, but it's nearly linear in practice +// since the length of outlined sequences are small within a block. +static std::vector getMatchedEntries(InstructionMapper &Mapper) { + auto &InstrList = Mapper.InstrList; + auto &UnsignedVec = Mapper.UnsignedVec; + + std::vector MatchedEntries; + std::vector Sequence; + auto Size = UnsignedVec.size(); + + // Get the global outlined hash tree built from the previous run. + assert(cgdata::hasOutlinedHashTree()); + const auto *RootNode = cgdata::getOutlinedHashTree()->getRoot(); + for (size_t I = 0; I < Size; ++I) { + // skip the invalid mapping that represents a large negative value. + if (UnsignedVec[I] >= Size) + continue; + const MachineInstr &MI = *InstrList[I]; + // skip debug instructions as we did for the outlined function. + if (MI.isDebugInstr()) + continue; + // skip the empty hash value. + stable_hash StableHashI = stableHashValue(MI); + if (!StableHashI) + continue; + Sequence.clear(); + Sequence.push_back(StableHashI); + + const HashNode *LastNode = followHashNode(StableHashI, RootNode); + if (!LastNode) + continue; + + size_t J = I + 1; + for (; J < Size; ++J) { + // break on the invalid mapping that represents a large negative value. + if (UnsignedVec[J] >= Size) + break; + // ignore debug instructions as we did for the outlined function. + const MachineInstr &MJ = *InstrList[J]; + if (MJ.isDebugInstr()) + continue; + // break on the empty hash value. + stable_hash StableHashJ = stableHashValue(MJ); + if (!StableHashJ) + break; + LastNode = followHashNode(StableHashJ, LastNode); + if (!LastNode) + break; + + // Even with a match ending with a terminal, we continue finding + // matches to populate all candidates. + Sequence.push_back(StableHashJ); + auto Count = LastNode->Terminals; + if (Count) + MatchedEntries.push_back({I, J - I + 1, *Count}); + } + } + + return MatchedEntries; +} + +void MachineOutliner::findGlobalCandidates( + InstructionMapper &Mapper, + std::vector> &FunctionList) { + FunctionList.clear(); + auto &InstrList = Mapper.InstrList; + auto &MBBFlagsMap = Mapper.MBBFlagsMap; + + std::vector CandidatesForRepeatedSeq; + for (auto &ME : getMatchedEntries(Mapper)) { + CandidatesForRepeatedSeq.clear(); + MachineBasicBlock::iterator StartIt = InstrList[ME.StartIdx]; + MachineBasicBlock::iterator EndIt = InstrList[ME.StartIdx + ME.Length - 1]; + MachineBasicBlock *MBB = StartIt->getParent(); + CandidatesForRepeatedSeq.emplace_back(ME.StartIdx, ME.Length, StartIt, + EndIt, MBB, FunctionList.size(), + MBBFlagsMap[MBB]); + const TargetInstrInfo *TII = + MBB->getParent()->getSubtarget().getInstrInfo(); + unsigned MinRepeats = 1; + std::optional> OF = + TII->getOutliningCandidateInfo(*MMI, CandidatesForRepeatedSeq, + MinRepeats); + if (!OF.has_value() || OF.value()->Candidates.empty()) + continue; + // We create a global candidate each match. + assert(OF.value()->Candidates.size() == MinRepeats); + FunctionList.emplace_back(std::make_unique( + std::move(OF.value()), ME.Count)); + } +} + void MachineOutliner::findCandidates( InstructionMapper &Mapper, std::vector> &FunctionList) { @@ -695,6 +859,39 @@ void MachineOutliner::findCandidates( } } +void MachineOutliner::computeAndPublishHashSequence(MachineFunction &MF, + unsigned CandSize) { + // Compute the hash sequence for the outlined function. + SmallVector OutlinedHashSequence; + for (auto &MBB : MF) { + for (auto &NewMI : MBB) { + stable_hash Hash = stableHashValue(NewMI); + if (!Hash) { + OutlinedHashSequence.clear(); + break; + } + OutlinedHashSequence.push_back(Hash); + } + } + + // Append a unique name based on the non-empty hash sequence. + if (AppendContentHashToOutlinedName && !OutlinedHashSequence.empty()) { + auto CombinedHash = stable_hash_combine(OutlinedHashSequence); + auto NewName = + MF.getName().str() + ".content." + std::to_string(CombinedHash); + MF.getFunction().setName(NewName); + } + + // Publish the non-empty hash sequence to the local hash tree. + if (OutlinerMode == CGDataMode::Write) { + StableHashAttempts++; + if (!OutlinedHashSequence.empty()) + LocalHashTree->insert({OutlinedHashSequence, CandSize}); + else + StableHashDropped++; + } +} + MachineFunction *MachineOutliner::createOutlinedFunction( Module &M, OutlinedFunction &OF, InstructionMapper &Mapper, unsigned Name) { @@ -770,6 +967,9 @@ MachineFunction *MachineOutliner::createOutlinedFunction( } } + if (OutlinerMode != CGDataMode::None) + computeAndPublishHashSequence(MF, OF.Candidates.size()); + // Set normal properties for a late MachineFunction. MF.getProperties().reset(MachineFunctionProperties::Property::IsSSA); MF.getProperties().set(MachineFunctionProperties::Property::NoPHIs); @@ -1134,12 +1334,65 @@ void MachineOutliner::emitInstrCountChangedRemark( } } +void MachineOutliner::initializeOutlinerMode(const Module &M) { + if (DisableGlobalOutlining) + return; + + if (auto *IndexWrapperPass = + getAnalysisIfAvailable()) { + auto *TheIndex = IndexWrapperPass->getIndex(); + // (Full)LTO module does not have functions added to the index. + // In this case, we run the outliner without using codegen data as usual. + if (TheIndex && !TheIndex->hasExportedFunctions(M)) + return; + } + + // When codegen data write is enabled, we want to write the local outlined + // hash tree to the custom section, `__llvm_outline`. + // When the outlined hash tree is available from the previous codegen data, + // we want to read it to optimistically create global outlining candidates. + if (cgdata::emitCGData()) { + OutlinerMode = CGDataMode::Write; + // Create a local outlined hash tree to be published. + LocalHashTree.reset(new OutlinedHashTree()); + // We don't need to read the outlined hash tree from the previous codegen + } else if (cgdata::hasOutlinedHashTree()) + OutlinerMode = CGDataMode::Read; +} + +void MachineOutliner::emitOutlinedHashTree(Module &M) { + assert(LocalHashTree); + if (!LocalHashTree->empty()) { + LLVM_DEBUG({ + dbgs() << "Emit outlined hash tree. Size: " << LocalHashTree->size() + << "\n"; + }); + SmallVector Buf; + raw_svector_ostream OS(Buf); + + OutlinedHashTreeRecord HTR(std::move(LocalHashTree)); + HTR.serialize(OS); + + llvm::StringRef Data(Buf.data(), Buf.size()); + std::unique_ptr Buffer = + MemoryBuffer::getMemBuffer(Data, "in-memory outlined hash tree", false); + + Triple TT(M.getTargetTriple()); + embedBufferInModule( + M, *Buffer.get(), + getCodeGenDataSectionName(CG_outline, TT.getObjectFormat())); + } +} + bool MachineOutliner::runOnModule(Module &M) { // Check if there's anything in the module. If it's empty, then there's // nothing to outline. if (M.empty()) return false; + // Initialize the outliner mode. + initializeOutlinerMode(M); + MMI = &getAnalysis().getMMI(); // Number to append to the current outlined function. @@ -1161,6 +1414,9 @@ bool MachineOutliner::runOnModule(Module &M) { } } + if (OutlinerMode == CGDataMode::Write) + emitOutlinedHashTree(M); + return true; } @@ -1189,7 +1445,10 @@ bool MachineOutliner::doOutline(Module &M, unsigned &OutlinedFunctionNum) { std::vector> FunctionList; // Find all of the outlining candidates. - findCandidates(Mapper, FunctionList); + if (OutlinerMode == CGDataMode::Read) + findGlobalCandidates(Mapper, FunctionList); + else + findCandidates(Mapper, FunctionList); // If we've requested size remarks, then collect the MI counts of every // function before outlining, and the MI counts after outlining. diff --git a/llvm/test/CodeGen/AArch64/O3-pipeline.ll b/llvm/test/CodeGen/AArch64/O3-pipeline.ll index 3465b717261cf5..66ce960462c63d 100644 --- a/llvm/test/CodeGen/AArch64/O3-pipeline.ll +++ b/llvm/test/CodeGen/AArch64/O3-pipeline.ll @@ -16,6 +16,7 @@ ; CHECK-NEXT: Machine Branch Probability Analysis ; CHECK-NEXT: Default Regalloc Eviction Advisor ; CHECK-NEXT: Default Regalloc Priority Advisor +; CHECK-NEXT: Module summary info ; CHECK-NEXT: ModulePass Manager ; CHECK-NEXT: Pre-ISel Intrinsic Lowering ; CHECK-NEXT: FunctionPass Manager diff --git a/llvm/test/CodeGen/AArch64/cgdata-global-hash.ll b/llvm/test/CodeGen/AArch64/cgdata-global-hash.ll new file mode 100644 index 00000000000000..c425eda56f5d5b --- /dev/null +++ b/llvm/test/CodeGen/AArch64/cgdata-global-hash.ll @@ -0,0 +1,40 @@ +; This test verifies the stable hash values for different global variables +; that have distinct names. +; We generate two different cgdata files from nearly identical outline instances, +; with the only difference being the last call target globals, @g vs @h. + +; RUN: split-file %s %t + +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %t/local-g.ll -o %t/local-g.o +; RUN: llvm-cgdata --merge %t/local-g.o -o %t/local-g.cgdata +; RUN: llvm-cgdata --convert %t/local-g.cgdata -o %t/local-g.cgtext +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %t/local-h.ll -o %t/local-h.o +; RUN: llvm-cgdata --merge %t/local-h.o -o %t/local-h.cgdata +; RUN: llvm-cgdata --convert %t/local-h.cgdata -o %t/local-h.cgtext + +; We compare the trees which are only different at the terminal node's hash value. +; Here we simply count the different lines that have `Hash` string. +; RUN: not diff %t/local-g.cgtext %t/local-h.cgtext 2>&1 | grep Hash | wc -l | FileCheck %s +; CHECK: 2 + +;--- local-g.ll +declare i32 @g(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @g(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @g(i32 20, i32 1, i32 2); + ret i32 %1 +} + +;--- local-h.ll +declare i32 @h(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @h(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @h(i32 20, i32 1, i32 2); + ret i32 %1 +} diff --git a/llvm/test/CodeGen/AArch64/cgdata-outlined-name.ll b/llvm/test/CodeGen/AArch64/cgdata-outlined-name.ll new file mode 100644 index 00000000000000..69f1ecd6515e7e --- /dev/null +++ b/llvm/test/CodeGen/AArch64/cgdata-outlined-name.ll @@ -0,0 +1,41 @@ +; This test verifies the globally outlined function name has the content hash. + +; RUN: split-file %s %t + +; Check if the outlined function name has the content hash depending the flag. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -append-content-hash-outlined-name=false -filetype=obj %t/local-two.ll -o %t_write_base +; RUN: llvm-objdump -d %t_write_base | FileCheck %s --check-prefix=BASE +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -append-content-hash-outlined-name=true -filetype=obj %t/local-two.ll -o %t_write_suffix +; RUN: llvm-objdump -d %t_write_suffix | FileCheck %s --check-prefix=SUFFIX +; BASE-NOT: _OUTLINED_FUNCTION_{{.*}}.content.{{[0-9]+}} +; SUFFIX: _OUTLINED_FUNCTION_{{.*}}.content.{{[0-9]+}} + +; Generate the cgdata file from each case and show they are identical. +; RUN: llvm-cgdata --merge %t_write_base -o %t_cgdata_base +; RUN: llvm-cgdata --merge %t_write_suffix -o %t_cgdata_suffix +; RUN: diff %t_cgdata_base %t_cgdata_suffix + +; Read the cgdata in the machine outliner for optimistically outlining in local-one.ll. +; Check if the outlined function has the content hash depending the flag. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata_base -append-content-hash-outlined-name=false -filetype=obj %t/local-one.ll -o %t_read_base +; RUN: llvm-objdump -d %t_read_base | FileCheck %s --check-prefix=BASE +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata_suffix -append-content-hash-outlined-name=true -filetype=obj %t/local-one.ll -o %t_read_suffix +; RUN: llvm-objdump -d %t_read_suffix | FileCheck %s --check-prefix=SUFFIX + +;--- local-two.ll +declare i32 @g(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @g(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @g(i32 20, i32 1, i32 2); + ret i32 %1 +} + +;--- local-one.ll +declare i32 @g(i32, i32, i32) +define i32 @f3() minsize { + %1 = call i32 @g(i32 30, i32 1, i32 2); + ret i32 %1 +} diff --git a/llvm/test/CodeGen/AArch64/cgdata-read-double-outline.ll b/llvm/test/CodeGen/AArch64/cgdata-read-double-outline.ll new file mode 100644 index 00000000000000..6e027308c17068 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/cgdata-read-double-outline.ll @@ -0,0 +1,57 @@ +; This test demonstrates how identical instruction sequences are handled during global outlining. +; Currently, we do not attempt to share an outlined function for identical sequences. +; Instead, each instruction sequence that matches against the global outlined hash tree +; is outlined into its own unique function. + +; RUN: split-file %s %t + +; First, we generate the cgdata file from a local outline instance present in local-two.ll. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %t/local-two.ll -o %t_write +; RUN: llvm-cgdata --merge %t_write -o %t_cgdata +; RUN: llvm-cgdata --show %t_cgdata | FileCheck %s --check-prefix=SHOW + +; SHOW: Outlined hash tree: +; SHOW-NEXT: Total Node Count: 4 +; SHOW-NEXT: Terminal Node Count: 1 +; SHOW-NEXT: Depth: 3 + +; Now, we read the cgdata for local-two-another.ll and proceed to optimistically outline +; each instruction sequence that matches against the global outlined hash tree. +; Since each matching sequence is considered a candidate, we expect to generate two +; unique outlined functions. These functions, although unique, will be identical in code, +; and thus, will be folded by the linker. + +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata -filetype=obj %t/local-two-another.ll -o %t_read +; RUN: llvm-objdump -d %t_read | FileCheck %s + +; CHECK: _OUTLINED_FUNCTION_{{.*}}: +; CHECK-NEXT: mov +; CHECK-NEXT: mov +; CHECK-NEXT: b + +; CHECK: _OUTLINED_FUNCTION_{{.*}}: +; CHECK-NEXT: mov +; CHECK-NEXT: mov +; CHECK-NEXT: b + +;--- local-two.ll +declare i32 @g(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @g(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @g(i32 20, i32 1, i32 2); + ret i32 %1 +} + +;--- local-two-another.ll +declare i32 @g(i32, i32, i32) +define i32 @f3() minsize { + %1 = call i32 @g(i32 30, i32 1, i32 2); + ret i32 %1 +} +define i32 @f4() minsize { + %1 = call i32 @g(i32 40, i32 1, i32 2); + ret i32 %1 +} diff --git a/llvm/test/CodeGen/AArch64/cgdata-read-lto-outline.ll b/llvm/test/CodeGen/AArch64/cgdata-read-lto-outline.ll new file mode 100644 index 00000000000000..f1a5d1a0ccc7f0 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/cgdata-read-lto-outline.ll @@ -0,0 +1,96 @@ +; This test is similar to cgdata-read-double-outline.ll, but it is executed with LTO (Link Time Optimization). +; It demonstrates how identical instruction sequences are handled during global outlining. +; Currently, we do not attempt to reuse an outlined function for identical sequences. +; Instead, each instruction sequence that appears in the global outlined hash tree +; is outlined into its own unique function. + +; RUN: split-file %s %t + +; We first create the cgdata file from a local outline instance in local-two.ll +; RUN: opt -module-summary %t/local-two.ll -o %t/write.bc +; RUN: llvm-lto2 run %t/write.bc -o %t/write \ +; RUN: -r %t/write.bc,_f1,px -r %t/write.bc,_f2,px -r %t/write.bc,_g,p \ +; RUN: -codegen-data-generate=true +; RUN: llvm-cgdata --merge %t/write.1 -o %t_cgdata +; RUN: llvm-cgdata --show %t_cgdata | FileCheck %s --check-prefix=SHOW + +; SHOW: Outlined hash tree: +; SHOW-NEXT: Total Node Count: 4 +; SHOW-NEXT: Terminal Node Count: 1 +; SHOW-NEXT: Depth: 3 + +; Now, we execute either ThinLTO or LTO by reading the cgdata for local-two-another.ll. +; With ThinLTO, similar to the no-LTO scenario shown in cgdata-read-double-outline.ll, +; it optimistically outlines each instruction sequence that matches against +; the global outlined hash tree. Since each matching sequence is considered a candidate, +; we expect to generate two unique outlined functions that will be folded +; by the linker at a later stage. +; However, with LTO, we do not utilize the cgdata, but instead fall back to the default +; outliner mode. This results in a single outlined function that is +; shared across two call-sites. + +; Run ThinLTO +; RUN: opt -module-summary %t/local-two-another.ll -o %t/thinlto.bc +; RUN: llvm-lto2 run %t/thinlto.bc -o %t/thinlto \ +; RUN: -r %t/thinlto.bc,_f3,px -r %t/thinlto.bc,_f4,px -r %t/thinlto.bc,_g,p \ +; RUN: -codegen-data-use-path=%t_cgdata +; RUN: llvm-objdump -d %t/thinlto.1 | FileCheck %s + +; CHECK: _OUTLINED_FUNCTION_{{.*}}: +; CHECK-NEXT: mov +; CHECK-NEXT: mov +; CHECK-NEXT: b +; CHECK: _OUTLINED_FUNCTION_{{.*}}: +; CHECK-NEXT: mov +; CHECK-NEXT: mov +; CHECK-NEXT: b + +; Run ThinLTO while disabling the global outliner. +; We have a single outlined case with the default outliner. +; RUN: llvm-lto2 run %t/thinlto.bc -o %t/thinlto-disable \ +; RUN: -r %t/thinlto.bc,_f3,px -r %t/thinlto.bc,_f4,px -r %t/thinlto.bc,_g,p \ +; RUN: -enable-machine-outliner \ +; RUN: -codegen-data-use-path=%t_cgdata \ +; RUN: -disable-global-outlining +; RUN: llvm-objdump -d %t/thinlto-disable.1 | FileCheck %s --check-prefix=DISABLE + +; DISABLE: _OUTLINED_FUNCTION_{{.*}}: +; DISABLE-NEXT: mov +; DISABLE-NEXT: mov +; DISABLE-NEXT: b +; DISABLE-NOT: _OUTLINED_FUNCTION_{{.*}}: + +; Run LTO, which effectively disables the global outliner. +; RUN: opt %t/local-two-another.ll -o %t/lto.bc +; RUN: llvm-lto2 run %t/lto.bc -o %t/lto \ +; RUN: -r %t/lto.bc,_f3,px -r %t/lto.bc,_f4,px -r %t/lto.bc,_g,p \ +; RUN: -enable-machine-outliner \ +; RUN: -codegen-data-use-path=%t_cgdata +; RUN: llvm-objdump -d %t/lto.0 | FileCheck %s --check-prefix=DISABLE + +;--- local-two.ll +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" +target triple = "arm64-apple-darwin" +declare i32 @g(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @g(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @g(i32 20, i32 1, i32 2); + ret i32 %1 +} + +;--- local-two-another.ll +target datalayout = "e-m:o-i64:64-i128:128-n32:64-S128" +target triple = "arm64-apple-darwin" + +declare i32 @g(i32, i32, i32) +define i32 @f3() minsize { + %1 = call i32 @g(i32 30, i32 1, i32 2); + ret i32 %1 +} +define i32 @f4() minsize { + %1 = call i32 @g(i32 40, i32 1, i32 2); + ret i32 %1 +} diff --git a/llvm/test/CodeGen/AArch64/cgdata-read-priority.ll b/llvm/test/CodeGen/AArch64/cgdata-read-priority.ll new file mode 100644 index 00000000000000..affeea8c71acd3 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/cgdata-read-priority.ll @@ -0,0 +1,68 @@ +; This test verifies whether we can outline a singleton instance (i.e., an instance that does not repeat) +; using codegen data that has been read from a previous codegen run. +; When multiple matches occur, we prioritize the candidates using the global frequency. + +; RUN: split-file %s %t + +; First, we generate the cgdata file from local outline instances present in write1.ll and write2.ll +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %t/write1.ll -o %t_write1 +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %t/write2.ll -o %t_write2 +; RUN: llvm-cgdata --merge %t_write1 %t_write2 -o %t_cgdata +; RUN: llvm-cgdata --show %t_cgdata | FileCheck %s --check-prefix=SHOW + +; SHOW: Outlined hash tree: +; SHOW-NEXT: Total Node Count: 8 +; SHOW-NEXT: Terminal Node Count: 2 +; SHOW-NEXT: Depth: 4 + +; Now, we read the cgdata in the machine outliner, enabling us to optimistically +; outline a singleton instance in read.ll that matches against the cgdata. +; There are two matches -- (1) (mov #1, mov #2, mov #3, b) and (2) (mov #2, mov #3, b). +; Even though sequence (1) is longer than sequence (2), the latter is outlined because it occurs more frequently in the outlined hash tree. + +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata -filetype=obj %t/read.ll -o %t_read +; RUN: llvm-objdump -d %t_read | FileCheck %s + +; CHECK: _OUTLINED_FUNCTION +; CHECK-NEXT: mov +; CHECK-NEXT: mov +; CHECK-NEXT: b + +;--- write1.ll +; The sequence (mov #2, mov #3, b) are repeated 4 times. +declare i32 @g(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @g(i32 10, i32 50, i32 2, i32 3); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @g(i32 20, i32 60, i32 2, i32 3); + ret i32 %1 +} +define i32 @f3() minsize { + %1 = call i32 @g(i32 30, i32 70, i32 2, i32 3); + ret i32 %1 +} +define i32 @f4() minsize { + %1 = call i32 @g(i32 40, i32 80, i32 2, i32 3); + ret i32 %1 +} + +;--- write2.ll +; The sequence (mov #1, mov #2, mov #3, b) are repeated 2 times. +declare i32 @g(i32, i32, i32) +define i32 @f6() minsize { + %1 = call i32 @g(i32 10, i32 1, i32 2, i32 3); + ret i32 %1 +} +define i32 @f7() minsize { + %1 = call i32 @g(i32 20, i32 1, i32 2, i32 3); + ret i32 %1 +} + +;--- read.ll +declare i32 @g(i32, i32, i32) +define i32 @f3() minsize { + %1 = call i32 @g(i32 30, i32 1, i32 2, i32 3); + ret i32 %1 +} diff --git a/llvm/test/CodeGen/AArch64/cgdata-read-single-outline-suffix.ll b/llvm/test/CodeGen/AArch64/cgdata-read-single-outline-suffix.ll new file mode 100644 index 00000000000000..11bee02c0be5ca --- /dev/null +++ b/llvm/test/CodeGen/AArch64/cgdata-read-single-outline-suffix.ll @@ -0,0 +1,100 @@ +; This test checks if a singleton instance (an instance that appears only once) can be outlined +; using codegen data from a previous codegen run. +; Unlike cgdata-read-single-outline.ll, this test also examines various suffixes that LLVM appends to names. +; Specifically, we aim to disregard the suffixes `.llvm.{number}` and `.__uniq.{number}` during the matching of call targets in hash computations. +; This approach helps in accurately identifying the original call target, especially when an LTO build may append additional suffixes for uniqueness. +; Conversely, we only consider the number from the suffix `.content.{number}`. +; This matching strategy is crucial for recursively finding outlining candidates when multiple outliner runs are enabled. + +; RUN: split-file %s %t + +; First, we generate the cgdata file from a local outline instance present in local-two.ll. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %t/local-two.ll -o %t_write +; RUN: llvm-cgdata --merge %t_write -o %t_cgdata +; RUN: llvm-cgdata --show %t_cgdata | FileCheck %s --check-prefix=SHOW + +; SHOW: Outlined hash tree: +; SHOW-NEXT: Total Node Count: 4 +; SHOW-NEXT: Terminal Node Count: 1 +; SHOW-NEXT: Depth: 3 + +; Now, we read the cgdata in the machine outliner, enabling us to optimistically +; outline a singleton instance in local-one.ll that matches against the cgdata. +; We outline instances while disregarding the suffixes `.llvm.{number}` or `.__uniq.{number}` in names. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata -filetype=obj %t/local-one-ignore-suffix-1.ll -o %t_read_ignore_1 +; RUN: llvm-objdump -d %t_read_ignore_1 | FileCheck %s +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata -filetype=obj %t/local-one-ignore-suffix-2.ll -o %t_read_ignore_2 +; RUN: llvm-objdump -d %t_read_ignore_2 | FileCheck %s + +; CHECK: _OUTLINED_FUNCTION +; CHECK-NEXT: mov +; CHECK-NEXT: mov +; CHECK-NEXT: b + +; We don't ignore `.invalid.{number}`. So no outlining occurs. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata -filetype=obj %t/local-one-no-ignore-suffix.ll -o %t_read_no_ignore +; RUN: llvm-objdump -d %t_read_no_ignore | FileCheck %s --check-prefix=NOOUTLINE + +; NOOUTLINE-NOT: _OUTLINED_FUNCTION + +;--- local-two.ll +declare i32 @g(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @g(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @g(i32 20, i32 1, i32 2); + ret i32 %1 +} + +;--- local-one-ignore-suffix-1.ll +declare i32 @g.llvm.123(i32, i32, i32) +define i32 @f3() minsize { + %1 = call i32 @g.llvm.123(i32 30, i32 1, i32 2); + ret i32 %1 +} + +;--- local-one-ignore-suffix-2.ll +declare i32 @g.__uniq.456(i32, i32, i32) +define i32 @f4() minsize { + %1 = call i32 @g.__uniq.456(i32 30, i32 1, i32 2); + ret i32 %1 +} + +;--- local-one-no-ignore-suffix.ll +declare i32 @g.invalid.789(i32, i32, i32) +define i32 @f5() minsize { + %1 = call i32 @g.invalid.789(i32 30, i32 1, i32 2); + ret i32 %1 +} + +; Similarly, we outline functions that have already been processed in previous outliner runs. +; Assuming `-machine-outliner-reruns` is locally enabled, we might already have `OUTLINED_FUNCTION*` instances. +; First, we generate the cgdata file from a local outline instance found in local-two-content.ll. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %t/local-two-content.ll -o %t_write_content +; RUN: llvm-cgdata --merge %t_write_content -o %t_cgdata_content +; RUN: llvm-cgdata --show %t_cgdata_content | FileCheck %s --check-prefix=SHOW + +; Despite the target function names being different -- `OUTLINED_FUNCTION_0.content.123` vs. `OUTLINED_FUNCTION_1.content.123`, +; We compute the same hash based on the suffix `.content.{number}`, and optimistically outline them. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata_content -filetype=obj %t/local-one-content.ll -o %t_read_content +; RUN: llvm-objdump -d %t_read_content | FileCheck %s + +;--- local-two-content.ll +declare i32 @OUTLINED_FUNCTION_0.content.123(i32, i32, i32) +define i32 @f6() minsize { + %1 = call i32 @OUTLINED_FUNCTION_0.content.123(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f7() minsize { + %1 = call i32 @OUTLINED_FUNCTION_0.content.123(i32 20, i32 1, i32 2); + ret i32 %1 +} + +;--- local-one-content.ll +declare i32 @OUTLINED_FUNCTION_1.content.123(i32, i32, i32) +define i32 @f8() minsize { + %1 = call i32 @OUTLINED_FUNCTION_1.content.123(i32 30, i32 1, i32 2); + ret i32 %1 +} diff --git a/llvm/test/CodeGen/AArch64/cgdata-read-single-outline.ll b/llvm/test/CodeGen/AArch64/cgdata-read-single-outline.ll new file mode 100644 index 00000000000000..7725648a6bc3d5 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/cgdata-read-single-outline.ll @@ -0,0 +1,42 @@ +; This test verifies whether we can outline a singleton instance (i.e., an instance that does not repeat) +; using codegen data that has been read from a previous codegen run. + +; RUN: split-file %s %t + +; First, we generate the cgdata file from a local outline instance present in local-two.ll. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %t/local-two.ll -o %t_write +; RUN: llvm-cgdata --merge %t_write -o %t_cgdata +; RUN: llvm-cgdata --show %t_cgdata | FileCheck %s --check-prefix=SHOW + +; SHOW: Outlined hash tree: +; SHOW-NEXT: Total Node Count: 4 +; SHOW-NEXT: Terminal Node Count: 1 +; SHOW-NEXT: Depth: 3 + +; Now, we read the cgdata in the machine outliner, enabling us to optimistically +; outline a singleton instance in local-one.ll that matches against the cgdata. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-use-path=%t_cgdata -filetype=obj %t/local-one.ll -o %t_read +; RUN: llvm-objdump -d %t_read | FileCheck %s + +; CHECK: _OUTLINED_FUNCTION +; CHECK-NEXT: mov +; CHECK-NEXT: mov +; CHECK-NEXT: b + +;--- local-two.ll +declare i32 @g(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @g(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @g(i32 20, i32 1, i32 2); + ret i32 %1 +} + +;--- local-one.ll +declare i32 @g(i32, i32, i32) +define i32 @f3() minsize { + %1 = call i32 @g(i32 30, i32 1, i32 2); + ret i32 %1 +} diff --git a/llvm/test/CodeGen/AArch64/cgdata-write-outline.ll b/llvm/test/CodeGen/AArch64/cgdata-write-outline.ll new file mode 100644 index 00000000000000..09ad499190ee37 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/cgdata-write-outline.ll @@ -0,0 +1,51 @@ +; This test verifies whether an outlined function is encoded into the __llvm_outline section +; when the -codegen-data-generate flag is used. + +; Verify whether an outlined function is always created, but only encoded into the section when the flag is used. +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=true -filetype=obj %s -o %t_save +; RUN: llvm-objdump -d %t_save | FileCheck %s +; RUN: llvm-objdump -h %t_save | FileCheck %s --check-prefix=SECTNAME +; RUN: llc -mtriple=arm64-apple-darwin -enable-machine-outliner -codegen-data-generate=false -filetype=obj %s -o %t_nosave +; RUN: llvm-objdump -d %t_nosave | FileCheck %s +; RUN: llvm-objdump -h %t_nosave | FileCheck %s --check-prefix=NOSECTNAME + +; CHECK: _OUTLINED_FUNCTION +; CHECK-NEXT: mov +; CHECK-NEXT: mov +; CHECK-NEXT: b +; SECTNAME: __llvm_outline +; NOSECTNAME-NOT: __llvm_outline + +; Verify the content of cgdata after it has been processed with llvm-cgdata. +; RUN: llvm-cgdata --merge %t_save -o %t_cgdata +; RUN: llvm-cgdata --convert %t_cgdata | FileCheck %s --check-prefix=TREE + +; TREE: :outlined_hash_tree +; TREE: --- +; TREE-NEXT: 0: +; TREE-NEXT: Hash: 0x0 +; TREE-NEXT: Terminals: 0 +; TREE-NEXT: SuccessorIds: [ 1 ] +; TREE-NEXT: 1: +; TREE-NEXT: Hash: {{.}} +; TREE-NEXT: Terminals: 0 +; TREE-NEXT: SuccessorIds: [ 2 ] +; TREE-NEXT: 2: +; TREE-NEXT: Hash: {{.}} +; TREE-NEXT: Terminals: 0 +; TREE-NEXT: SuccessorIds: [ 3 ] +; TREE-NEXT: 3: +; TREE-NEXT: Hash: {{.}} +; TREE-NEXT: Terminals: 2 +; TREE-NEXT: SuccessorIds: [ ] +; TREE-NEXT: ... + +declare i32 @g(i32, i32, i32) +define i32 @f1() minsize { + %1 = call i32 @g(i32 10, i32 1, i32 2); + ret i32 %1 +} +define i32 @f2() minsize { + %1 = call i32 @g(i32 20, i32 1, i32 2); + ret i32 %1 +} diff --git a/llvm/test/CodeGen/RISCV/O3-pipeline.ll b/llvm/test/CodeGen/RISCV/O3-pipeline.ll index 44c270fdc3c257..7749f0db0c54d3 100644 --- a/llvm/test/CodeGen/RISCV/O3-pipeline.ll +++ b/llvm/test/CodeGen/RISCV/O3-pipeline.ll @@ -20,6 +20,7 @@ ; CHECK-NEXT: Machine Branch Probability Analysis ; CHECK-NEXT: Default Regalloc Eviction Advisor ; CHECK-NEXT: Default Regalloc Priority Advisor +; CHECK-NEXT: Module summary info ; CHECK-NEXT: ModulePass Manager ; CHECK-NEXT: Pre-ISel Intrinsic Lowering ; CHECK-NEXT: FunctionPass Manager diff --git a/llvm/unittests/MIR/MachineStableHashTest.cpp b/llvm/unittests/MIR/MachineStableHashTest.cpp index c6b99123d4bd2a..1d888c2ec3e72b 100644 --- a/llvm/unittests/MIR/MachineStableHashTest.cpp +++ b/llvm/unittests/MIR/MachineStableHashTest.cpp @@ -141,3 +141,73 @@ body: | // Do not ignore `.invalid.{number}`. EXPECT_NE(stableHashValue(*MF1), stableHashValue(*MF4)); } + +TEST_F(MachineStableHashTest, ContentName) { + auto TM = createTargetMachine(("aarch64--"), "", ""); + if (!TM) + GTEST_SKIP(); + StringRef MIRString = R"MIR( +--- | + define void @f1() { ret void } + define void @f2() { ret void } + define void @f3() { ret void } + define void @f4() { ret void } + declare void @goo() + declare void @goo.content.123() + declare void @zoo.content.123() + declare void @goo.content.456() +... +--- +name: f1 +alignment: 16 +tracksRegLiveness: true +frameInfo: + maxAlignment: 16 +machineFunctionInfo: {} +body: | + bb.0: + liveins: $lr + BL @goo + RET undef $lr +... +--- +name: f2 +body: | + bb.0: + liveins: $lr + BL @goo.content.123 + RET undef $lr +... +--- +name: f3 +body: | + bb.0: + liveins: $lr + BL @zoo.content.123 + RET undef $lr +... +--- +name: f4 +body: | + bb.0: + liveins: $lr + BL @goo.content.456 + RET undef $lr +... +)MIR"; + MachineModuleInfo MMI(TM.get()); + M = parseMIR(*TM, MIRString, MMI); + ASSERT_TRUE(M); + auto *MF1 = MMI.getMachineFunction(*M->getFunction("f1")); + auto *MF2 = MMI.getMachineFunction(*M->getFunction("f2")); + auto *MF3 = MMI.getMachineFunction(*M->getFunction("f3")); + auto *MF4 = MMI.getMachineFunction(*M->getFunction("f4")); + + // Do not ignore `.content.{number}`. + EXPECT_NE(stableHashValue(*MF1), stableHashValue(*MF2)); + EXPECT_EQ(stableHashValue(*MF2), stableHashValue(*MF3)) + << "Expect the same hash for the same suffix, `.content.{number}`"; + // Different suffixes should result in different hashes. + EXPECT_NE(stableHashValue(*MF2), stableHashValue(*MF4)); + EXPECT_NE(stableHashValue(*MF3), stableHashValue(*MF4)); +}