diff --git a/llvm/include/llvm/CodeGen/MachineOutliner.h b/llvm/include/llvm/CodeGen/MachineOutliner.h --- a/llvm/include/llvm/CodeGen/MachineOutliner.h +++ b/llvm/include/llvm/CodeGen/MachineOutliner.h @@ -18,7 +18,9 @@ #include "llvm/CodeGen/LivePhysRegs.h" #include "llvm/CodeGen/LiveRegUnits.h" #include "llvm/CodeGen/MachineFunction.h" +#include "llvm/CodeGen/MachineInstr.h" #include "llvm/CodeGen/MachineRegisterInfo.h" +#include "llvm/CodeGen/StableHashing.h" #include "llvm/CodeGen/TargetRegisterInfo.h" namespace llvm { @@ -180,6 +182,12 @@ /// Target-defined identifier for constructing a frame for this function. unsigned FrameConstructionID = 0; + /// The sequence of stable_hash'es for a Candidate in Candidates. + /// StableHashSequence is empty if computing hashes is disabled or if + /// one of the MachineOperands in one of the MachineInstrs in the Candidates + /// is not able have a stable_hash computed. + std::vector StableHashSequence; + /// Return the number of candidates for this \p OutlinedFunction. unsigned getOccurrenceCount() const { return Candidates.size(); } diff --git a/llvm/include/llvm/CodeGen/MachineStableHash.h b/llvm/include/llvm/CodeGen/MachineStableHash.h --- a/llvm/include/llvm/CodeGen/MachineStableHash.h +++ b/llvm/include/llvm/CodeGen/MachineStableHash.h @@ -14,6 +14,7 @@ #ifndef LLVM_CODEGEN_MACHINESTABLEHASH_H #define LLVM_CODEGEN_MACHINESTABLEHASH_H +#include "llvm/CodeGen/MachineBasicBlock.h" #include "llvm/CodeGen/StableHashing.h" namespace llvm { @@ -21,10 +22,14 @@ class MachineOperand; stable_hash stableHashValue(const MachineOperand &MO); + stable_hash stableHashValue(const MachineInstr &MI, bool HashVRegs = false, bool HashConstantPoolIndices = false, bool HashMemOperands = false); +std::vector +stableHashMachineInstrs(MachineBasicBlock::iterator &Begin, + const MachineBasicBlock::iterator &End); } // namespace llvm #endif diff --git a/llvm/include/llvm/CodeGen/StableHashTree.h b/llvm/include/llvm/CodeGen/StableHashTree.h new file mode 100644 --- /dev/null +++ b/llvm/include/llvm/CodeGen/StableHashTree.h @@ -0,0 +1,94 @@ +//===-- StableHashTree.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 +// +//===----------------------------------------------------------------------===// +/// +/// \file +/// Contains a stable hash tree implementation based on llvm::stable_hash. +/// Primarily used or Global Machine Outlining but is reusable. +/// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_STABLEHASHTREE_H +#define LLVM_STABLEHASHTREE_H + +#include +#include +#include + +#include "llvm/CodeGen/StableHashing.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/raw_ostream.h" + +namespace llvm { + +// A node in the hash tree might be terminal, i.e. it represents the end +// of an stable instruction hash sequence that was outlined in some module. +// Each node may have several successor nodes that can be reached via +// different stable instruction hashes. +// +// Data is the Hash for the current node +// IsTerminal is true if this node is the last node in a hash sequence +struct HashNode { + stable_hash Data = 0LL; + bool IsTerminal{false}; + std::unordered_map> Successors; +}; + +class HashTree { +public: + /// Walks every edge and vertex in the HashTree and calls CallbackEdge for the + /// edges and CallbackVertex for the vertices with the stable_hash for the + /// source and the stable_hash of the sink for the edge. Using walkEdges it + /// should be possible to traverse the HashTree and serialize it, compute its + /// depth, compute the number of vertices, etc. + void walkGraph( + std::function CallbackEdge, + std::function CallbackVertex) const; + + /// Walks the edges of a HashTree using walkGraph. + void walkEdges( + std::function Callback) const; + + // Walks the vertices of a HashTree using walkGraph. + void walkVertices(std::function Callback) const; + + /// Uses HashTree::walkEdges to print the edges of the hash tree. + /// If a DebugMap is provided, then it will be used to provide richer output. + void dump(raw_ostream &OS = llvm::errs(), + std::unordered_map DebugMap = {}) const; + + /// Builds a HashTree from a JSON file. Same format as getJsonMap. + llvm::Error readHashTreeFromFile(StringRef Filename); + + /// Writes a JSON file representing the current HashTree. + llvm::Error writeHashTreeToFile(StringRef Filename) const; + + /// When building a hash tree, insert sequences of stable instruction hashes. + void insertIntoHashTree( + const std::vector> &StableHashSequences); + + // When using a hash tree, starting from the root, check whether a sequence + // of stable instruction hashes ends up at a terminal node. + bool findInHashTree(const std::vector &StableHashSequence) const; + +private: + // The hash tree is a compact representation of the set of all outlined + // instruction sequences across all modules. + // This is not a suffix tree, but just represents a set of instruction + // sequences, allowing for efficient walking of instruction sequence + // prefices for matching purposes. + // We build the tree by inserting stable instruction sequences during the + // first first ThinLTO codegen round, and we use the tree by following and + // finding stable instruction hashes during the second ThinLTO codegen round. + HashNode HashTreeImpl; + + void insertIntoHashTree(const std::vector &StableHashSequence); +}; + +} // namespace llvm + +#endif diff --git a/llvm/lib/CodeGen/CMakeLists.txt b/llvm/lib/CodeGen/CMakeLists.txt --- a/llvm/lib/CodeGen/CMakeLists.txt +++ b/llvm/lib/CodeGen/CMakeLists.txt @@ -93,6 +93,7 @@ MachineOptimizationRemarkEmitter.cpp MachineOutliner.cpp MachinePassManager.cpp + StableHashTree.cpp MachinePipeliner.cpp MachinePostDominators.cpp MachineRegionInfo.cpp diff --git a/llvm/lib/CodeGen/MachineOutliner.cpp b/llvm/lib/CodeGen/MachineOutliner.cpp --- a/llvm/lib/CodeGen/MachineOutliner.cpp +++ b/llvm/lib/CodeGen/MachineOutliner.cpp @@ -59,9 +59,13 @@ #include "llvm/ADT/SmallSet.h" #include "llvm/ADT/Statistic.h" #include "llvm/ADT/Twine.h" +#include "llvm/CodeGen/MachineInstr.h" #include "llvm/CodeGen/MachineModuleInfo.h" #include "llvm/CodeGen/MachineOptimizationRemarkEmitter.h" +#include "llvm/CodeGen/MachineStableHash.h" #include "llvm/CodeGen/Passes.h" +#include "llvm/CodeGen/StableHashTree.h" +#include "llvm/CodeGen/StableHashing.h" #include "llvm/CodeGen/TargetInstrInfo.h" #include "llvm/CodeGen/TargetSubtargetInfo.h" #include "llvm/IR/DIBuilder.h" @@ -70,10 +74,13 @@ #include "llvm/InitializePasses.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/Debug.h" +#include "llvm/Support/Error.h" #include "llvm/Support/SuffixTree.h" #include "llvm/Support/raw_ostream.h" +#include #include #include +#include #include #define DEBUG_TYPE "machine-outliner" @@ -103,6 +110,23 @@ cl::desc( "Number of times to rerun the outliner after the initial outline")); +static cl::opt OutlinerHashTreeMode( + "outliner-hash-tree-mode", cl::init(""), cl::Hidden, + cl::desc("Outliner Hash Tree mode < none | write | read >. Anything but " + "'read' or 'write' mode will disable all functionality and this " + "is the default. In write mode, the outliner will collect hashes " + "of the candidate sequences in a HashTree and write the tree to " + "disk. In read mode, the outliner will read a provided HashTree " + "from disk and use the tree to aid in bumping up down the " + "threshold for consideration of a candidate when it is present in " + "the HashTree loaded from disk.")); + +static cl::opt OutlinerHashTreeFilename( + "outliner-hash-tree-filename", cl::init("OutlinerHashTree.out"), + cl::Hidden, + cl::desc("Outliner Hash Tree file name written to or read from when using " + "-outliner-hash-tree-mode.")); + namespace { /// Maps \p MachineInstrs to unsigned integers and stores the mappings. @@ -350,6 +374,9 @@ /// Set when the pass is constructed in TargetPassConfig. bool RunOnAllFunctions = true; + /// stable-hash of the outlined instruction sequence. + HashTree OutlinerHashTree; + StringRef getPassName() const override { return "Machine Outliner"; } void getAnalysisUsage(AnalysisUsage &AU) const override { @@ -361,6 +388,11 @@ MachineOutliner() : ModulePass(ID) { initializeMachineOutlinerPass(*PassRegistry::getPassRegistry()); + + if (StringRef(OutlinerHashTreeMode).lower() == "read") + if (auto Err = + OutlinerHashTree.readHashTreeFromFile(OutlinerHashTreeFilename)) + consumeError(std::move(Err)); } /// Remark output explaining that not outlining a set of candidates would be @@ -566,26 +598,74 @@ } } + auto GetOutlinedFunction = + [](std::vector &CandidatesForRepeatedSeq, + std::vector &StableHashSequence) { + // Arbitrarily choose a TII from the first candidate. + // FIXME: Should getOutliningCandidateInfo move to TargetMachine? + const TargetInstrInfo *TII = CandidatesForRepeatedSeq[0] + .getMF() + ->getSubtarget() + .getInstrInfo(); + OutlinedFunction OF = + TII->getOutliningCandidateInfo(CandidatesForRepeatedSeq); + OF.StableHashSequence = StableHashSequence; + return OF; + }; + + std::vector StableHashSequence; + // If we are not using Serialzied HashTrees then dont bother computing a + // StableHashSequence. + if (CandidatesForRepeatedSeq.size() && + (StringRef(OutlinerHashTreeMode).lower() == "write" || + StringRef(OutlinerHashTreeMode).lower() == "read")) { + auto &C = CandidatesForRepeatedSeq.front(); + auto CandidateBegin = C.front(); + const auto CandidateEnd = std::next(C.back()); + + StableHashSequence = + stableHashMachineInstrs(CandidateBegin, CandidateEnd); + } + + const bool IsCandidateInHashTree = + !StableHashSequence.empty() && + OutlinerHashTree.findInHashTree(StableHashSequence); + + // If the candidate is present in the HashTree then it likely occured at + // twice in some other module so consider it for outlining. Otherwise go + // with the standard 2 or more occorences for benefit heuristic. + const size_t CandidateRepeatLowerThreshold = 2; + + const size_t CandidatesForRepeatedSize = CandidatesForRepeatedSeq.size(); + if (IsCandidateInHashTree && CandidatesForRepeatedSeq.size() == + (CandidateRepeatLowerThreshold - 1)) { + CandidatesForRepeatedSeq.push_back(CandidatesForRepeatedSeq.front()); + } + // We've found something we might want to outline. // Create an OutlinedFunction to store it and check if it'd be beneficial // to outline. - if (CandidatesForRepeatedSeq.size() < 2) + if (CandidatesForRepeatedSeq.size() < CandidateRepeatLowerThreshold) continue; - // Arbitrarily choose a TII from the first candidate. - // FIXME: Should getOutliningCandidateInfo move to TargetMachine? - const TargetInstrInfo *TII = - CandidatesForRepeatedSeq[0].getMF()->getSubtarget().getInstrInfo(); - - OutlinedFunction OF = - TII->getOutliningCandidateInfo(CandidatesForRepeatedSeq); + OutlinedFunction OF = GetOutlinedFunction(CandidatesForRepeatedSeq, + StableHashSequence); // If we deleted too many candidates, then there's nothing worth outlining. // FIXME: This should take target-specified instruction sizes into account. - if (OF.Candidates.size() < 2) + if (OF.Candidates.size() < CandidateRepeatLowerThreshold) continue; + // NOTE: This is a not so nice way to trick the backend outliner code to + // play nice. I really want some feedback here because currently the + // threshold for candidate counts in the backends is 2. But in a case where + // We have information that an external module could have an additional + // candidate this breaks down. + if (CandidatesForRepeatedSize == OF.Candidates.size() - 1) + OF.Candidates.pop_back(); + // Is it better to outline this candidate than not? + if (!IsCandidateInHashTree) if (OF.getBenefit() < 1) { emitNotOutliningCheaperRemark(StringLen, CandidatesForRepeatedSeq, OF); continue; @@ -744,6 +824,8 @@ return LHS.getBenefit() > RHS.getBenefit(); }); + std::vector> ModuleStableHashSequences; + // Walk over each function, outlining them as we go along. Functions are // outlined greedily, based off the sort above. for (OutlinedFunction &OF : FunctionList) { @@ -756,10 +838,20 @@ [](unsigned I) { return (I == static_cast(-1)); }); }); + const bool IsCandidateInHashTree = + !OF.StableHashSequence.empty() && + OutlinerHashTree.findInHashTree(OF.StableHashSequence); + + // If we made it unbeneficial to outline this function, skip it. + if (!IsCandidateInHashTree) if (OF.getBenefit() < 1) continue; + if (OF.StableHashSequence.size()) { + ModuleStableHashSequences.push_back(OF.StableHashSequence); + } + // It's beneficial. Create the function and outline its sequence's // occurrences. OF.MF = createOutlinedFunction(M, OF, Mapper, OutlinedFunctionNum); @@ -851,6 +943,13 @@ } } + // If we are not in HashTree write mode, then we do not want to modify + // the current state of the hash tree at this time. + if (StringRef(OutlinerHashTreeMode).lower() == "write" && + ModuleStableHashSequences.size()) { + OutlinerHashTree.insertIntoHashTree(ModuleStableHashSequences); + } + LLVM_DEBUG(dbgs() << "OutlinedSomething = " << OutlinedSomething << "\n";); return OutlinedSomething; } @@ -977,12 +1076,36 @@ } } +static std::unordered_map +getStableHashDebugStrings(MachineModuleInfo &MMI, Module &M) { + std::unordered_map StableHashMIStrings; + for (auto &F : M) { + const MachineFunction &MF = MMI.getOrCreateMachineFunction(F); + for (const auto &BB : MF) { + for (const auto &MI : BB) { + std::string MIStr; + raw_string_ostream OS(MIStr); + MI.print(OS, true, false, false, false); + StableHashMIStrings[stableHashValue(MI)] = MIStr; + } + } + } + + return StableHashMIStrings; +} + 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; + std::unordered_map StableHashMIStrings; + LLVM_DEBUG({ + StableHashMIStrings = getStableHashDebugStrings( + getAnalysis().getMMI(), M); + }); + // Number to append to the current outlined function. unsigned OutlinedFunctionNum = 0; @@ -1002,6 +1125,17 @@ } } + LLVM_DEBUG({ + llvm::dbgs() << "Dump Outliner Hash Tree:\n"; + OutlinerHashTree.dump(llvm::dbgs(), StableHashMIStrings); + }); + + if (StringRef(OutlinerHashTreeMode).lower() == "write") { + if (auto Err = + OutlinerHashTree.writeHashTreeToFile(OutlinerHashTreeFilename)) + consumeError(std::move(Err)); + } + return true; } diff --git a/llvm/lib/CodeGen/MachineStableHash.cpp b/llvm/lib/CodeGen/MachineStableHash.cpp --- a/llvm/lib/CodeGen/MachineStableHash.cpp +++ b/llvm/lib/CodeGen/MachineStableHash.cpp @@ -192,3 +192,17 @@ return stable_hash_combine_range(HashComponents.begin(), HashComponents.end()); } + +std::vector +llvm::stableHashMachineInstrs(MachineBasicBlock::iterator &Begin, + const MachineBasicBlock::iterator &End) { + std::vector Sequence; + for (auto I = Begin; I != End; I++) { + const MachineInstr &MI = *I; + stable_hash Hash = stableHashValue(MI); + if (!Hash) + return {}; + Sequence.push_back(Hash); + } + return Sequence; +} diff --git a/llvm/lib/CodeGen/StableHashTree.cpp b/llvm/lib/CodeGen/StableHashTree.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/CodeGen/StableHashTree.cpp @@ -0,0 +1,241 @@ +//===---- StableHashTree.cpp ------------------------------------*- 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/CodeGen/StableHashTree.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/Statistic.h" +#include "llvm/CodeGen/MachineOperand.h" +#include "llvm/CodeGen/StableHashing.h" +#include "llvm/Support/Debug.h" +#include "llvm/Support/Error.h" +#include "llvm/Support/ErrorHandling.h" +#include "llvm/Support/JSON.h" +#include "llvm/Support/MemoryBuffer.h" +#include "llvm/Support/VirtualFileSystem.h" +#include "llvm/Support/raw_ostream.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#define DEBUG_TYPE "stable-hash-tree" + +using namespace llvm; + +namespace llvm { + +void HashTree::walkGraph( + std::function CallbackEdge, + std::function CallbackVertex) const { + std::stack Stack; + Stack.push(&HashTreeImpl); + + while (!Stack.empty()) { + const auto *Current = Stack.top(); + Stack.pop(); + CallbackVertex(Current); + for (const auto &P : Current->Successors) { + CallbackEdge(Current, P.second.get()); + Stack.push(P.second.get()); + } + } +} + +void HashTree::walkVertices( + std::function Callback) const { + walkGraph([](const HashNode *A, const HashNode *B) {}, Callback); +} + +void HashTree::walkEdges( + std::function Callback) const { + walkGraph(Callback, [](const HashNode *A) {}); +} + +void HashTree::dump( + llvm::raw_ostream &OS, + std::unordered_map DebugMap) const { + + std::unordered_map NodeMap; + + walkVertices([&NodeMap](const HashNode *Current) { + size_t Index = NodeMap.size(); + NodeMap[Current] = Index; + assert(Index = NodeMap.size() + 1 && + "Expected size of ModeMap to increment by 1"); + }); + + bool IsFirstEntry = true; + OS << "{"; + for (const auto &Entry : NodeMap) { + if (!IsFirstEntry) + OS << ","; + OS << "\n"; + IsFirstEntry = false; + OS << " \"" << Entry.second << "\" : {\n"; + OS << " \"hash\" : \""; + OS.raw_ostream::write_hex(Entry.first->Data); + OS << "\",\n"; + + OS << " \"isTerminal\" : " + << "\"" << (Entry.first->IsTerminal ? "true" : "false") << "\",\n"; + + // For debugging we want to provide a string representation of the hashing + // source, such as a MachineInstr dump, etc. Not intended for production. + auto MII = DebugMap.find(Entry.first->Data); + if (MII != DebugMap.end()) + OS << " \"source\" : \"" << MII->second << "\",\n"; + + OS << " \"neighbors\" : ["; + + bool IsFirst = true; + for (const auto &Adj : Entry.first->Successors) { + if (!IsFirst) + OS << ","; + IsFirst = false; + OS << " \""; + OS << NodeMap[Adj.second.get()]; + OS << "\" "; + } + + OS << "]\n }"; + } + OS << "\n}\n"; + OS.flush(); +} + +llvm::Error HashTree::writeHashTreeToFile(StringRef Filename) const { + std::error_code EC; + llvm::raw_fd_ostream OS(Filename, EC, llvm::sys::fs::OF_Text); + if (EC) + return llvm::createStringError(EC, "Unable to open JSON HashTree"); + dump(OS); + OS.flush(); + return llvm::Error::success(); +} + +llvm::Error HashTree::readHashTreeFromFile(StringRef Filename) { + llvm::SmallString<256> Filepath(Filename); + auto FileOrError = llvm::vfs::getRealFileSystem()->getBufferForFile(Filepath); + if (!FileOrError) + return llvm::errorCodeToError(FileOrError.getError()); + + auto Json = llvm::json::parse(FileOrError.get()->getBuffer()); + if (!Json) + return Json.takeError(); + + const json::Object *JO = Json.get().getAsObject(); + if (!JO) + return llvm::createStringError(std::error_code(), "Bad Json"); + + std::unordered_map JsonMap; + for (const auto &E : *JO) + JsonMap[std::stoul(E.first.str())] = &E.second; + + assert(JsonMap.find(0x0) != JsonMap.end() && "Expected a root HashTree node"); + + // We have a JsonMap and a NodeMap. We walk the JSON form of the HashTree + // using the JsonMap by using the stack of JSON IDs. As we walk we used the + // IDs to get the currwent JSON Node and the current HashNode. + std::unordered_map NodeMap; + std::stack Stack; + Stack.push(0); + NodeMap[0] = &HashTreeImpl; + + while (!Stack.empty()) { + unsigned Current = Stack.top(); + Stack.pop(); + + HashNode *CurrentSubtree = NodeMap[Current]; + const auto *CurrentJson = JsonMap[Current]->getAsObject(); + + std::vector Neighbors; + llvm::transform(*CurrentJson->get("neighbors")->getAsArray(), + std::back_inserter(Neighbors), + [](const llvm::json::Value &S) { + return std::stoull(S.getAsString()->str()); + }); + + stable_hash Hash = std::stoull( + CurrentJson->get("hash")->getAsString()->str(), nullptr, 16); + CurrentSubtree->Data = Hash; + + std::string IsTerminalStr = + StringRef(CurrentJson->get("isTerminal")->getAsString()->str()).lower(); + CurrentSubtree->IsTerminal = + IsTerminalStr == "true" || IsTerminalStr == "on"; + + for (auto N : Neighbors) { + auto I = JsonMap.find(N); + if (I == JsonMap.end()) + return llvm::createStringError(std::error_code(), + "Missing neighbor in JSON"); + + std::unique_ptr Neighbor = std::make_unique(); + HashNode *NeighborPtr = Neighbor.get(); + stable_hash StableHash = std::stoull( + I->second->getAsObject()->get("hash")->getAsString()->str(), nullptr, + 16); + CurrentSubtree->Successors.emplace(StableHash, std::move(Neighbor)); + NodeMap[N] = NeighborPtr; + + Stack.push(I->first); + } + } + + return llvm::Error::success(); +} + +void HashTree::insertIntoHashTree( + const std::vector &StableHashSequence) { + HashNode *Current = &HashTreeImpl; + for (stable_hash StableHash : StableHashSequence) { + auto I = Current->Successors.find(StableHash); + if (I == Current->Successors.end()) { + std::unique_ptr Next = std::make_unique(); + HashNode *NextPtr = Next.get(); + NextPtr->Data = StableHash; + Current->Successors.emplace(StableHash, std::move(Next)); + Current = NextPtr; + continue; + } + Current = I->second.get(); + } + Current->IsTerminal = true; +} + +bool HashTree::findInHashTree( + const std::vector &StableHashSequence) const { + const HashNode *Current = &HashTreeImpl; + for (stable_hash StableHash : StableHashSequence) { + const auto I = Current->Successors.find(StableHash); + if (I == Current->Successors.end()) + return false; + Current = I->second.get(); + } + // return Current->Successors.empty(); + return Current->IsTerminal; +} + +void HashTree::insertIntoHashTree( + const std::vector> &StableHashSequences) { + for (const auto &StableHashSequence : StableHashSequences) + insertIntoHashTree(StableHashSequence); +} + +} // namespace llvm diff --git a/llvm/test/CodeGen/AArch64/machine-outliner-serialize-hashtree.mir b/llvm/test/CodeGen/AArch64/machine-outliner-serialize-hashtree.mir new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/AArch64/machine-outliner-serialize-hashtree.mir @@ -0,0 +1,58 @@ + +# This is a check to make sure the external module we are sourcing our tree from +# outlines the a Candidate Sequence. +# RUN: llc -mtriple=aarch64--- -run-pass=machine-outliner -verify-machineinstrs \ +# RUN: %S/../Inputs/machine-outliner-serialize-hashtree-external.mir -o - | \ +# RUN: FileCheck --check-prefix=CHECK-EXTERNAL %s + +# This is a check to make sure the current file which has only a single +# Candidate Sequence (which matches out external module) will outline the +# Candidate based on the serialized hash tree even though it only exists in the +# module one time. +# RUN: llc -mtriple=aarch64--- -run-pass=machine-outliner --outliner-hash-tree-mode write \ +# RUN: -outliner-hash-tree-filename %t1.hashtree \ +# RUN: -verify-machineinstrs %S/../Inputs/machine-outliner-serialize-hashtree-external.mir && \ +# RUN: llc -mtriple=aarch64--- -run-pass=machine-outliner --outliner-hash-tree-mode read \ +# RUN: -outliner-hash-tree-filename %t1.hashtree \ +# RUN: -verify-machineinstrs %s -o - | FileCheck --check-prefix=CHECK-HASHTREE %s + +# This is a check to ensure that without use of the serialized hash tree that we +# are behaving as expected: one candidate in a module means no outlining. +# RUN: llc -mtriple=aarch64--- -run-pass=machine-outliner \ +# RUN: -verify-machineinstrs %s -o - | FileCheck --check-prefix=CHECK-DEFAULT %s + +# CHECK-EXTERNAL: OUTLINED_FUNCTION_ +# CHECK-HASHTREE: OUTLINED_FUNCTION_ +# CHECK-DEFAULT-NOT: OUTLINED_FUNCTION_ + +--- | + + @x = common global i32 0, align 4 + + define void @bar(i32 %a) #0 { + ret void + } + + attributes #0 = { noinline noredzone } +... +--- +name: bar +tracksRegLiveness: true +body: | + bb.0: + liveins: $w0, $lr, $w8 + $sp = frame-setup SUBXri $sp, 32, 0 + $fp = frame-setup ADDXri $sp, 16, 0 + bb.2: + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w8 = ORRWri $wzr, 0 + RET undef $lr + +... +--- diff --git a/llvm/test/CodeGen/Inputs/machine-outliner-serialize-hashtree-external.mir b/llvm/test/CodeGen/Inputs/machine-outliner-serialize-hashtree-external.mir new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/Inputs/machine-outliner-serialize-hashtree-external.mir @@ -0,0 +1,38 @@ +# RUN: llc -mtriple=aarch64--- -run-pass=prologepilog -run-pass=machine-outliner -verify-machineinstrs -frame-pointer=non-leaf %s -o - | FileCheck %s +--- | + + @x = common global i32 0, align 4 + + define void @bar(i32 %a) #0 { + ret void + } + + attributes #0 = { noinline noredzone } +... +--- +name: bar +tracksRegLiveness: true +body: | + bb.0: + liveins: $w0, $lr, $w8 + $sp = frame-setup SUBXri $sp, 32, 0 + $fp = frame-setup ADDXri $sp, 16, 0 + bb.2: + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w8 = ORRWri $wzr, 0 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w15 = ORRWri $wzr, 1 + $w8 = ORRWri $wzr, 1 + RET undef $lr + +... +---