68#define DEBUG_TYPE "amdgpu-split-module"
74 "amdgpu-module-splitting-max-depth",
76 "maximum search depth. 0 forces a greedy approach. "
77 "warning: the algorithm is up to O(2^N), where N is the max depth."),
80static cl::opt<float> LargeFnFactor(
83 "when max depth is reached and we can no longer branch out, this "
84 "value determines if a function is worth merging into an already "
85 "existing partition to reduce code duplication. This is a factor "
86 "of the ideal partition size, e.g. 2.0 means we consider the "
87 "function for merging if its cost (including its callees) is 2x the "
88 "size of an ideal partition."));
90static cl::opt<float> LargeFnOverlapForMerge(
92 cl::desc(
"when a function is considered for merging into a partition that "
93 "already contains some of its callees, do the merge if at least "
94 "n% of the code it can reach is already present inside the "
95 "partition; e.g. 0.7 means only merge >70%"));
98 "amdgpu-module-splitting-no-externalize-globals",
cl::Hidden,
99 cl::desc(
"disables externalization of global variable with local linkage; "
100 "may cause globals to be duplicated which increases binary size"));
103 "amdgpu-module-splitting-no-externalize-address-taken",
cl::Hidden,
105 "disables externalization of functions whose addresses are taken"));
108 ModuleDotCfgOutput(
"amdgpu-module-splitting-print-module-dotcfg",
110 cl::desc(
"output file to write out the dotgraph "
111 "representation of the input module"));
114 "amdgpu-module-splitting-print-partition-summaries",
cl::Hidden,
115 cl::desc(
"output file to write out a summary of "
116 "the partitions created for each module"));
120 UseLockFile(
"amdgpu-module-splitting-serial-execution",
cl::Hidden,
121 cl::desc(
"use a lock file so only one process in the system "
122 "can run this pass at once. useful to avoid mangled "
123 "debug output in multithreaded environments."));
126 DebugProposalSearch(
"amdgpu-module-splitting-debug-proposal-search",
128 cl::desc(
"print all proposals received and whether "
129 "they were rejected or accepted"));
132struct SplitModuleTimer : NamedRegionTimer {
133 SplitModuleTimer(StringRef Name, StringRef
Desc)
134 : NamedRegionTimer(Name,
Desc,
DEBUG_TYPE,
"AMDGPU Module Splitting",
143using FunctionsCostMap = DenseMap<const Function *, CostType>;
145static constexpr unsigned InvalidPID = -1;
150static auto formatRatioOf(CostType Num, CostType Dem) {
151 CostType DemOr1 = Dem ? Dem : 1;
152 return format(
"%0.2f", (
static_cast<double>(Num) / DemOr1) * 100);
163static bool isNonCopyable(
const Function &
F) {
164 return F.hasExternalLinkage() || !
F.isDefinitionExact() ||
175 FunctionsCostMap &CostMap) {
176 SplitModuleTimer SMT(
"calculateFunctionCosts",
"cost analysis");
178 LLVM_DEBUG(
dbgs() <<
"[cost analysis] calculating function costs\n");
179 CostType ModuleCost = 0;
180 [[maybe_unused]] CostType KernelCost = 0;
183 if (Fn.isDeclaration())
187 const auto &
TTI = GetTTI(Fn);
188 for (
const auto &BB : Fn) {
189 for (
const auto &
I : BB) {
194 CostType CostVal = Cost.isValid()
197 assert((FnCost + CostVal) >= FnCost &&
"Overflow!");
204 CostMap[&Fn] = FnCost;
205 assert((ModuleCost + FnCost) >= ModuleCost &&
"Overflow!");
206 ModuleCost += FnCost;
209 KernelCost += FnCost;
217 const CostType FnCost = ModuleCost - KernelCost;
218 dbgs() <<
" - total module cost is " << ModuleCost <<
". kernels cost "
219 <<
"" << KernelCost <<
" ("
220 <<
format(
"%0.2f", (
float(KernelCost) / ModuleCost) * 100)
221 <<
"% of the module), functions cost " << FnCost <<
" ("
222 <<
format(
"%0.2f", (
float(FnCost) / ModuleCost) * 100)
223 <<
"% of the module)\n";
230static bool canBeIndirectlyCalled(
const Function &
F) {
233 return !
F.hasLocalLinkage() ||
234 F.hasAddressTaken(
nullptr,
262 enum class EdgeKind :
uint8_t {
281 : Src(Src), Dst(Dst), Kind(Kind) {}
288 using EdgesVec = SmallVector<const Edge *, 0>;
290 using nodes_iterator =
const Node *
const *;
292 SplitGraph(
const Module &M,
const FunctionsCostMap &CostMap,
294 : M(M), CostMap(CostMap), ModuleCost(ModuleCost) {}
296 void buildGraph(CallGraph &CG);
299 bool verifyGraph()
const;
302 bool empty()
const {
return Nodes.empty(); }
304 const Node &
getNode(
unsigned ID)
const {
return *Nodes[ID]; }
306 unsigned getNumNodes()
const {
return Nodes.size(); }
307 BitVector createNodesBitVector()
const {
return BitVector(Nodes.size()); }
309 const Module &getModule()
const {
return M; }
311 CostType getModuleCost()
const {
return ModuleCost; }
316 CostType calculateCost(
const BitVector &BV)
const;
321 Node &
getNode(DenseMap<const GlobalValue *, Node *> &Cache,
322 const GlobalValue &GV);
325 const Edge &createEdge(
Node &Src,
Node &Dst, EdgeKind EK);
328 const FunctionsCostMap &CostMap;
334 SpecificBumpPtrAllocator<Node> NodesPool;
340 std::is_trivially_destructible_v<Edge>,
341 "Edge must be trivially destructible to use the BumpPtrAllocator");
357class SplitGraph::Node {
358 friend class SplitGraph;
361 Node(
unsigned ID,
const GlobalValue &GV, CostType IndividualCost,
363 : ID(ID), GV(GV), IndividualCost(IndividualCost),
364 IsNonCopyable(IsNonCopyable), IsEntryFnCC(
false), IsGraphEntry(
false) {
371 unsigned getID()
const {
return ID; }
377 CostType getIndividualCost()
const {
return IndividualCost; }
379 bool isNonCopyable()
const {
return IsNonCopyable; }
380 bool isEntryFunctionCC()
const {
return IsEntryFnCC; }
386 bool isGraphEntryPoint()
const {
return IsGraphEntry; }
388 StringRef
getName()
const {
return GV.getName(); }
390 bool hasAnyIncomingEdges()
const {
return IncomingEdges.size(); }
391 bool hasAnyIncomingEdgesOfKind(EdgeKind EK)
const {
392 return any_of(IncomingEdges, [&](
const auto *
E) {
return E->Kind == EK; });
395 bool hasAnyOutgoingEdges()
const {
return OutgoingEdges.size(); }
396 bool hasAnyOutgoingEdgesOfKind(EdgeKind EK)
const {
397 return any_of(OutgoingEdges, [&](
const auto *
E) {
return E->Kind == EK; });
401 return IncomingEdges;
405 return OutgoingEdges;
408 bool shouldFollowIndirectCalls()
const {
return isEntryFunctionCC(); }
415 void visitAllDependencies(std::function<
void(
const Node &)> Visitor)
const;
424 void getDependencies(BitVector &BV)
const {
425 visitAllDependencies([&](
const Node &
N) { BV.set(
N.getID()); });
429 void markAsGraphEntry() { IsGraphEntry =
true; }
432 const GlobalValue &GV;
433 CostType IndividualCost;
434 bool IsNonCopyable : 1;
435 bool IsEntryFnCC : 1;
436 bool IsGraphEntry : 1;
440 EdgesVec IncomingEdges;
441 EdgesVec OutgoingEdges;
444void SplitGraph::Node::visitAllDependencies(
445 std::function<
void(
const Node &)> Visitor)
const {
446 const bool FollowIndirect = shouldFollowIndirectCalls();
449 DenseSet<const Node *> Seen;
450 SmallVector<const Node *, 8> WorkList({
this});
451 while (!WorkList.empty()) {
452 const Node *CurN = WorkList.pop_back_val();
453 if (
auto [It, Inserted] = Seen.insert(CurN); !Inserted)
458 for (
const Edge *
E : CurN->outgoing_edges()) {
459 if (!FollowIndirect &&
E->Kind == EdgeKind::IndirectCall)
461 WorkList.push_back(
E->Dst);
473static bool handleCalleesMD(
const Instruction &
I,
474 SetVector<Function *> &Callees) {
475 auto *MD =
I.getMetadata(LLVMContext::MD_callees);
479 for (
const auto &Op : MD->operands()) {
480 Function *Callee = mdconst::extract_or_null<Function>(Op);
483 Callees.insert(Callee);
489void SplitGraph::buildGraph(CallGraph &CG) {
490 SplitModuleTimer SMT(
"buildGraph",
"graph construction");
493 <<
"[build graph] constructing graph representation of the input\n");
501 DenseMap<const GlobalValue *, Node *> Cache;
502 SmallVector<const Function *> FnsWithIndirectCalls, IndirectlyCallableFns;
503 for (
const Function &Fn : M) {
504 if (Fn.isDeclaration())
508 SetVector<const Function *> DirectCallees;
509 bool CallsExternal =
false;
510 for (
auto &CGEntry : *CG[&Fn]) {
511 auto *CGNode = CGEntry.second;
512 if (
auto *Callee = CGNode->getFunction()) {
513 if (!Callee->isDeclaration())
514 DirectCallees.insert(Callee);
515 }
else if (CGNode == CG.getCallsExternalNode())
516 CallsExternal =
true;
522 LLVM_DEBUG(dbgs() <<
" [!] callgraph is incomplete for ";
523 Fn.printAsOperand(dbgs());
524 dbgs() <<
" - analyzing function\n");
526 SetVector<Function *> KnownCallees;
527 bool HasUnknownIndirectCall =
false;
530 const auto *CB = dyn_cast<CallBase>(&Inst);
531 if (!CB || CB->getCalledFunction())
536 if (CB->isInlineAsm()) {
537 LLVM_DEBUG(dbgs() <<
" found inline assembly\n");
541 if (handleCalleesMD(Inst, KnownCallees))
545 KnownCallees.clear();
549 HasUnknownIndirectCall =
true;
553 if (HasUnknownIndirectCall) {
554 LLVM_DEBUG(dbgs() <<
" indirect call found\n");
555 FnsWithIndirectCalls.push_back(&Fn);
556 }
else if (!KnownCallees.empty())
557 DirectCallees.insert_range(KnownCallees);
561 for (
const auto *Callee : DirectCallees)
562 createEdge(
N,
getNode(Cache, *Callee), EdgeKind::DirectCall);
564 if (canBeIndirectlyCalled(Fn))
565 IndirectlyCallableFns.push_back(&Fn);
569 for (
const Function *Fn : FnsWithIndirectCalls) {
570 for (
const Function *Candidate : IndirectlyCallableFns) {
573 createEdge(Src, Dst, EdgeKind::IndirectCall);
578 SmallVector<Node *, 16> CandidateEntryPoints;
579 BitVector NodesReachableByKernels = createNodesBitVector();
580 for (
Node *
N : Nodes) {
582 if (
N->isEntryFunctionCC()) {
583 N->markAsGraphEntry();
584 N->getDependencies(NodesReachableByKernels);
585 }
else if (!
N->hasAnyIncomingEdgesOfKind(EdgeKind::DirectCall))
586 CandidateEntryPoints.push_back(
N);
589 for (
Node *
N : CandidateEntryPoints) {
595 if (!NodesReachableByKernels.test(
N->getID()))
596 N->markAsGraphEntry();
605bool SplitGraph::verifyGraph()
const {
606 unsigned ExpectedID = 0;
608 DenseSet<const Node *> SeenNodes;
609 DenseSet<const Function *> SeenFunctionNodes;
610 for (
const Node *
N : Nodes) {
611 if (
N->getID() != (ExpectedID++)) {
612 errs() <<
"Node IDs are incorrect!\n";
616 if (!SeenNodes.insert(
N).second) {
617 errs() <<
"Node seen more than once!\n";
622 errs() <<
"getNode doesn't return the right node\n";
626 for (
const Edge *
E :
N->IncomingEdges) {
627 if (!
E->Src || !
E->Dst || (
E->Dst !=
N) ||
628 (
find(
E->Src->OutgoingEdges,
E) ==
E->Src->OutgoingEdges.end())) {
629 errs() <<
"ill-formed incoming edges\n";
634 for (
const Edge *
E :
N->OutgoingEdges) {
635 if (!
E->Src || !
E->Dst || (
E->Src !=
N) ||
636 (
find(
E->Dst->IncomingEdges,
E) ==
E->Dst->IncomingEdges.end())) {
637 errs() <<
"ill-formed outgoing edges\n";
642 const Function &Fn =
N->getFunction();
643 if (AMDGPU::isEntryFunctionCC(Fn.getCallingConv())) {
644 if (
N->hasAnyIncomingEdges()) {
645 errs() <<
"Kernels cannot have incoming edges\n";
650 if (Fn.isDeclaration()) {
651 errs() <<
"declarations shouldn't have nodes!\n";
655 auto [It, Inserted] = SeenFunctionNodes.insert(&Fn);
657 errs() <<
"one function has multiple nodes!\n";
662 if (ExpectedID != Nodes.size()) {
663 errs() <<
"Node IDs out of sync!\n";
667 if (createNodesBitVector().size() != getNumNodes()) {
668 errs() <<
"nodes bit vector doesn't have the right size!\n";
673 BitVector BV = createNodesBitVector();
675 if (
N->isGraphEntryPoint())
676 N->getDependencies(BV);
680 for (
const auto &Fn : M) {
681 if (!Fn.isDeclaration()) {
682 if (!SeenFunctionNodes.contains(&Fn)) {
683 errs() <<
"Fn has no associated node in the graph!\n";
690 errs() <<
"not all nodes are reachable through the graph's entry points!\n";
698CostType SplitGraph::calculateCost(
const BitVector &BV)
const {
700 for (
unsigned NodeID : BV.set_bits())
701 Cost +=
getNode(NodeID).getIndividualCost();
706SplitGraph::getNode(DenseMap<const GlobalValue *, Node *> &Cache,
707 const GlobalValue &GV) {
708 auto &
N = Cache[&GV];
713 bool NonCopyable =
false;
714 if (
const Function *Fn = dyn_cast<Function>(&GV)) {
715 NonCopyable = isNonCopyable(*Fn);
716 Cost = CostMap.at(Fn);
718 N =
new (NodesPool.Allocate())
Node(Nodes.size(), GV, Cost, NonCopyable);
724const SplitGraph::Edge &SplitGraph::createEdge(
Node &Src,
Node &Dst,
726 const Edge *
E =
new (EdgesPool.Allocate<Edge>(1))
Edge(&Src, &Dst, EK);
727 Src.OutgoingEdges.push_back(
E);
728 Dst.IncomingEdges.push_back(
E);
747 SplitProposal(
const SplitGraph &SG,
unsigned MaxPartitions) : SG(&SG) {
748 Partitions.resize(MaxPartitions, {0, SG.createNodesBitVector()});
751 void setName(StringRef NewName) { Name = NewName; }
752 StringRef
getName()
const {
return Name; }
754 const BitVector &operator[](
unsigned PID)
const {
755 return Partitions[PID].second;
758 void add(
unsigned PID,
const BitVector &BV) {
759 Partitions[PID].second |= BV;
763 void print(raw_ostream &OS)
const;
768 unsigned findCheapestPartition()
const;
771 void calculateScores();
774 void verifyCompleteness()
const;
786 double getCodeSizeScore()
const {
return CodeSizeScore; }
800 double getBottleneckScore()
const {
return BottleneckScore; }
803 void updateScore(
unsigned PID) {
805 for (
auto &[PCost, Nodes] : Partitions) {
807 PCost = SG->calculateCost(Nodes);
813 double CodeSizeScore = 0.0;
815 double BottleneckScore = 0.0;
817 CostType TotalCost = 0;
819 const SplitGraph *SG =
nullptr;
822 std::vector<std::pair<CostType, BitVector>> Partitions;
825void SplitProposal::print(raw_ostream &OS)
const {
828 OS <<
"[proposal] " << Name <<
", total cost:" << TotalCost
829 <<
", code size score:" << format(
"%0.3f", CodeSizeScore)
830 <<
", bottleneck score:" << format(
"%0.3f", BottleneckScore) <<
'\n';
831 for (
const auto &[PID, Part] : enumerate(Partitions)) {
832 const auto &[Cost, NodeIDs] = Part;
833 OS <<
" - P" << PID <<
" nodes:" << NodeIDs.count() <<
" cost: " << Cost
834 <<
'|' << formatRatioOf(Cost, SG->getModuleCost()) <<
"%\n";
838unsigned SplitProposal::findCheapestPartition()
const {
839 assert(!Partitions.empty());
840 CostType CurCost = std::numeric_limits<CostType>::max();
841 unsigned CurPID = InvalidPID;
842 for (
const auto &[Idx, Part] : enumerate(Partitions)) {
843 if (Part.first <= CurCost) {
845 CurCost = Part.first;
848 assert(CurPID != InvalidPID);
852void SplitProposal::calculateScores() {
853 if (Partitions.empty())
857 CostType LargestPCost = 0;
858 for (
auto &[PCost, Nodes] : Partitions) {
859 if (PCost > LargestPCost)
860 LargestPCost = PCost;
863 CostType ModuleCost = SG->getModuleCost();
864 CodeSizeScore = double(TotalCost) / ModuleCost;
865 assert(CodeSizeScore >= 0.0);
867 BottleneckScore = double(LargestPCost) / ModuleCost;
869 CodeSizeScore = std::ceil(CodeSizeScore * 100.0) / 100.0;
870 BottleneckScore = std::ceil(BottleneckScore * 100.0) / 100.0;
874void SplitProposal::verifyCompleteness()
const {
875 if (Partitions.empty())
878 BitVector Result = Partitions[0].second;
879 for (
const auto &
P : drop_begin(Partitions))
881 assert(Result.all() &&
"some nodes are missing from this proposal!");
900class RecursiveSearchSplitting {
902 using SubmitProposalFn = function_ref<void(SplitProposal)>;
904 RecursiveSearchSplitting(
const SplitGraph &SG,
unsigned NumParts,
905 SubmitProposalFn SubmitProposal);
910 struct WorkListEntry {
911 WorkListEntry(
const BitVector &BV) : Cluster(BV) {}
913 unsigned NumNonEntryNodes = 0;
914 CostType TotalCost = 0;
915 CostType CostExcludingGraphEntryPoints = 0;
922 void setupWorkList();
933 void pickPartition(
unsigned Depth,
unsigned Idx, SplitProposal SP);
940 std::pair<unsigned, CostType>
941 findMostSimilarPartition(
const WorkListEntry &Entry,
const SplitProposal &SP);
943 const SplitGraph &SG;
945 SubmitProposalFn SubmitProposal;
949 CostType LargeClusterThreshold = 0;
950 unsigned NumProposalsSubmitted = 0;
951 SmallVector<WorkListEntry> WorkList;
954RecursiveSearchSplitting::RecursiveSearchSplitting(
955 const SplitGraph &SG,
unsigned NumParts, SubmitProposalFn SubmitProposal)
956 : SG(SG), NumParts(NumParts), SubmitProposal(SubmitProposal) {
961 report_fatal_error(
"[amdgpu-split-module] search depth of " +
962 Twine(MaxDepth) +
" is too high!");
963 LargeClusterThreshold =
964 (LargeFnFactor != 0.0)
965 ? CostType(((SG.getModuleCost() / NumParts) * LargeFnFactor))
966 : std::numeric_limits<CostType>::max();
967 LLVM_DEBUG(dbgs() <<
"[recursive search] large cluster threshold set at "
968 << LargeClusterThreshold <<
"\n");
971void RecursiveSearchSplitting::run() {
973 SplitModuleTimer SMT(
"recursive_search_prepare",
"preparing worklist");
978 SplitModuleTimer SMT(
"recursive_search_pick",
"partitioning");
979 SplitProposal SP(SG, NumParts);
980 pickPartition(0, 0, std::move(SP));
984void RecursiveSearchSplitting::setupWorkList() {
992 EquivalenceClasses<unsigned> NodeEC;
993 for (
const SplitGraph::Node *
N : SG.nodes()) {
994 if (!
N->isGraphEntryPoint())
997 NodeEC.insert(
N->getID());
998 N->visitAllDependencies([&](
const SplitGraph::Node &Dep) {
999 if (&Dep !=
N && Dep.isNonCopyable())
1000 NodeEC.unionSets(
N->getID(), Dep.getID());
1004 for (
const auto &
Node : NodeEC) {
1005 if (!
Node->isLeader())
1008 BitVector Cluster = SG.createNodesBitVector();
1009 for (
unsigned M : NodeEC.members(*
Node)) {
1010 const SplitGraph::Node &
N = SG.getNode(M);
1011 if (
N.isGraphEntryPoint())
1012 N.getDependencies(Cluster);
1014 WorkList.emplace_back(std::move(Cluster));
1018 for (WorkListEntry &Entry : WorkList) {
1019 for (
unsigned NodeID : Entry.Cluster.set_bits()) {
1020 const SplitGraph::Node &
N = SG.getNode(NodeID);
1021 const CostType Cost =
N.getIndividualCost();
1023 Entry.TotalCost += Cost;
1024 if (!
N.isGraphEntryPoint()) {
1025 Entry.CostExcludingGraphEntryPoints += Cost;
1026 ++Entry.NumNonEntryNodes;
1031 stable_sort(WorkList, [](
const WorkListEntry &
A,
const WorkListEntry &
B) {
1032 if (
A.TotalCost !=
B.TotalCost)
1033 return A.TotalCost >
B.TotalCost;
1035 if (
A.CostExcludingGraphEntryPoints !=
B.CostExcludingGraphEntryPoints)
1036 return A.CostExcludingGraphEntryPoints >
B.CostExcludingGraphEntryPoints;
1038 if (
A.NumNonEntryNodes !=
B.NumNonEntryNodes)
1039 return A.NumNonEntryNodes >
B.NumNonEntryNodes;
1041 return A.Cluster.count() >
B.Cluster.count();
1045 dbgs() <<
"[recursive search] worklist:\n";
1046 for (
const auto &[Idx, Entry] : enumerate(WorkList)) {
1047 dbgs() <<
" - [" << Idx <<
"]: ";
1048 for (
unsigned NodeID : Entry.Cluster.set_bits())
1049 dbgs() << NodeID <<
" ";
1050 dbgs() <<
"(total_cost:" << Entry.TotalCost
1051 <<
", cost_excl_entries:" << Entry.CostExcludingGraphEntryPoints
1057void RecursiveSearchSplitting::pickPartition(
unsigned Depth,
unsigned Idx,
1059 while (Idx < WorkList.size()) {
1062 const WorkListEntry &Entry = WorkList[Idx];
1063 const BitVector &Cluster = Entry.Cluster;
1067 const unsigned CheapestPID = SP.findCheapestPartition();
1068 assert(CheapestPID != InvalidPID);
1072 const auto [MostSimilarPID, SimilarDepsCost] =
1073 findMostSimilarPartition(Entry, SP);
1077 unsigned SinglePIDToTry = InvalidPID;
1078 if (MostSimilarPID == InvalidPID)
1079 SinglePIDToTry = CheapestPID;
1080 else if (MostSimilarPID == CheapestPID)
1081 SinglePIDToTry = CheapestPID;
1082 else if (Depth >= MaxDepth) {
1085 if (Entry.CostExcludingGraphEntryPoints > LargeClusterThreshold) {
1087 assert(SimilarDepsCost && Entry.CostExcludingGraphEntryPoints);
1088 const double Ratio =
static_cast<double>(SimilarDepsCost) /
1089 Entry.CostExcludingGraphEntryPoints;
1090 assert(Ratio >= 0.0 && Ratio <= 1.0);
1091 if (Ratio > LargeFnOverlapForMerge) {
1096 SinglePIDToTry = MostSimilarPID;
1099 SinglePIDToTry = CheapestPID;
1106 if (SinglePIDToTry != InvalidPID) {
1107 LLVM_DEBUG(dbgs() << Idx <<
"=P" << SinglePIDToTry <<
' ');
1109 SP.add(SinglePIDToTry, Cluster);
1114 assert(MostSimilarPID != InvalidPID);
1123 SplitProposal BranchSP = SP;
1125 <<
" [lb] " << Idx <<
"=P" << CheapestPID <<
"? ");
1126 BranchSP.add(CheapestPID, Cluster);
1127 pickPartition(Depth + 1, Idx + 1, std::move(BranchSP));
1132 SplitProposal BranchSP = SP;
1134 <<
" [ms] " << Idx <<
"=P" << MostSimilarPID <<
"? ");
1135 BranchSP.add(MostSimilarPID, Cluster);
1136 pickPartition(Depth + 1, Idx + 1, std::move(BranchSP));
1144 assert(Idx == WorkList.size());
1145 assert(NumProposalsSubmitted <= (2u << MaxDepth) &&
1146 "Search got out of bounds?");
1147 SP.setName(
"recursive_search (depth=" + std::to_string(Depth) +
") #" +
1148 std::to_string(NumProposalsSubmitted++));
1150 SubmitProposal(std::move(SP));
1153std::pair<unsigned, CostType>
1154RecursiveSearchSplitting::findMostSimilarPartition(
const WorkListEntry &Entry,
1155 const SplitProposal &SP) {
1156 if (!Entry.NumNonEntryNodes)
1157 return {InvalidPID, 0};
1162 unsigned ChosenPID = InvalidPID;
1163 CostType ChosenCost = 0;
1164 for (
unsigned PID = 0; PID < NumParts; ++PID) {
1165 BitVector BV = SP[PID];
1166 BV &= Entry.Cluster;
1171 const CostType Cost = SG.calculateCost(BV);
1173 if (ChosenPID == InvalidPID || ChosenCost < Cost ||
1174 (ChosenCost == Cost && PID > ChosenPID)) {
1180 return {ChosenPID, ChosenCost};
1187const SplitGraph::Node *mapEdgeToDst(
const SplitGraph::Edge *
E) {
1191using SplitGraphEdgeDstIterator =
1192 mapped_iterator<SplitGraph::edges_iterator,
decltype(&mapEdgeToDst)>;
1207 return {
Ref->outgoing_edges().begin(), mapEdgeToDst};
1210 return {
Ref->outgoing_edges().end(), mapEdgeToDst};
1214 return G.nodes().begin();
1217 return G.nodes().end();
1225 return SG.getModule().getName().str();
1229 return N->getName().str();
1233 const SplitGraph &SG) {
1235 if (
N->isEntryFunctionCC())
1236 Result +=
"entry-fn-cc ";
1237 if (
N->isNonCopyable())
1238 Result +=
"non-copyable ";
1239 Result +=
"cost:" + std::to_string(
N->getIndividualCost());
1244 const SplitGraph &SG) {
1245 return N->hasAnyIncomingEdges() ?
"" :
"color=\"red\"";
1249 SplitGraphEdgeDstIterator EI,
1250 const SplitGraph &SG) {
1252 switch ((*EI.getCurrent())->Kind) {
1253 case SplitGraph::EdgeKind::DirectCall:
1255 case SplitGraph::EdgeKind::IndirectCall:
1256 return "style=\"dashed\"";
1271static bool needsConservativeImport(
const GlobalValue *GV) {
1272 if (
const auto *Var = dyn_cast<GlobalVariable>(GV))
1273 return Var->hasLocalLinkage();
1274 if (
const auto *GA = dyn_cast<GlobalAlias>(GV))
1275 return GA->hasLocalLinkage();
1281static void printPartitionSummary(raw_ostream &OS,
unsigned N,
const Module &M,
1282 unsigned PartCost,
unsigned ModuleCost) {
1283 OS <<
"*** Partition P" <<
N <<
" ***\n";
1285 for (
const auto &Fn : M) {
1286 if (!Fn.isDeclaration())
1287 OS <<
" - [function] " << Fn.getName() <<
"\n";
1290 for (
const auto &GV :
M.globals()) {
1291 if (GV.hasInitializer())
1292 OS <<
" - [global] " << GV.getName() <<
"\n";
1295 OS <<
"Partition contains " << formatRatioOf(PartCost, ModuleCost)
1296 <<
"% of the source\n";
1299static void evaluateProposal(SplitProposal &Best, SplitProposal New) {
1300 SplitModuleTimer SMT(
"proposal_evaluation",
"proposal ranking algorithm");
1303 New.verifyCompleteness();
1304 if (DebugProposalSearch)
1308 const double CurBScore = Best.getBottleneckScore();
1309 const double CurCSScore = Best.getCodeSizeScore();
1310 const double NewBScore =
New.getBottleneckScore();
1311 const double NewCSScore =
New.getCodeSizeScore();
1323 bool IsBest =
false;
1324 if (NewBScore < CurBScore)
1326 else if (NewBScore == CurBScore)
1327 IsBest = (NewCSScore < CurCSScore);
1330 Best = std::move(New);
1334 dbgs() <<
"[search] new best proposal!\n";
1336 dbgs() <<
"[search] discarding - not profitable\n";
1341static std::unique_ptr<Module> cloneAll(
const Module &M) {
1343 return CloneModule(M, VMap, [&](
const GlobalValue *GV) {
return true; });
1347static void writeDOTGraph(
const SplitGraph &SG) {
1348 if (ModuleDotCfgOutput.empty())
1352 raw_fd_ostream OS(ModuleDotCfgOutput, EC);
1354 errs() <<
"[" DEBUG_TYPE "]: cannot open '" << ModuleDotCfgOutput
1355 <<
"' - DOTGraph will not be printed\n";
1358 SG.getModule().getName());
1361static void splitAMDGPUModule(
1363 function_ref<
void(std::unique_ptr<Module> MPart)> ModuleCallback) {
1383 if (!NoExternalizeOnAddrTaken) {
1384 for (
auto &Fn : M) {
1385 if (Fn.hasLocalLinkage() && Fn.hasAddressTaken()) {
1387 dbgs() <<
" because its address is taken\n");
1395 if (!NoExternalizeGlobals) {
1396 for (
auto &GV :
M.globals()) {
1397 if (GV.hasLocalLinkage())
1398 LLVM_DEBUG(
dbgs() <<
"[externalize] GV " << GV.getName() <<
'\n');
1403 for (
auto &GA :
M.aliases()) {
1404 if (GA.hasLocalLinkage()) {
1405 LLVM_DEBUG(
dbgs() <<
"[externalize] alias " << GA.getName() <<
'\n');
1412 FunctionsCostMap FnCosts;
1413 const CostType ModuleCost = calculateFunctionCosts(GetTTI, M, FnCosts);
1417 SplitGraph SG(M, FnCosts, ModuleCost);
1423 <<
"[!] no nodes in graph, input is empty - no splitting possible\n");
1424 ModuleCallback(cloneAll(M));
1429 dbgs() <<
"[graph] nodes:\n";
1430 for (
const SplitGraph::Node *
N : SG.nodes()) {
1431 dbgs() <<
" - [" <<
N->getID() <<
"]: " <<
N->getName() <<
" "
1432 << (
N->isGraphEntryPoint() ?
"(entry)" :
"") <<
" "
1433 << (
N->isNonCopyable() ?
"(noncopyable)" :
"") <<
"\n";
1441 std::optional<SplitProposal> Proposal;
1442 const auto EvaluateProposal = [&](SplitProposal
SP) {
1443 SP.calculateScores();
1445 Proposal = std::move(SP);
1447 evaluateProposal(*Proposal, std::move(SP));
1452 RecursiveSearchSplitting(SG, NumParts, EvaluateProposal).run();
1453 LLVM_DEBUG(
if (Proposal)
dbgs() <<
"[search done] selected proposal: "
1454 << Proposal->getName() <<
"\n";);
1457 LLVM_DEBUG(
dbgs() <<
"[!] no proposal made, no splitting possible!\n");
1458 ModuleCallback(cloneAll(M));
1464 std::optional<raw_fd_ostream> SummariesOS;
1465 if (!PartitionSummariesOutput.empty()) {
1467 SummariesOS.emplace(PartitionSummariesOutput, EC);
1469 errs() <<
"[" DEBUG_TYPE "]: cannot open '" << PartitionSummariesOutput
1470 <<
"' - Partition summaries will not be printed\n";
1475 bool ImportAllGVs =
true;
1477 for (
unsigned PID = 0; PID < NumParts; ++PID) {
1478 SplitModuleTimer SMT2(
"modules_creation",
1479 "creating modules for each partition");
1482 DenseSet<const Function *> FnsInPart;
1483 for (
unsigned NodeID : (*Proposal)[PID].set_bits())
1484 FnsInPart.insert(&SG.getNode(NodeID).getFunction());
1487 if (FnsInPart.empty()) {
1489 <<
" is empty, not creating module\n");
1494 CostType PartCost = 0;
1495 std::unique_ptr<Module> MPart(
1498 if (
const auto *Fn = dyn_cast<Function>(GV)) {
1499 if (FnsInPart.contains(Fn)) {
1500 PartCost += SG.getCost(*Fn);
1507 if (
const auto *GA = dyn_cast<GlobalAlias>(GV)) {
1508 if (
const auto *Fn = dyn_cast<Function>(GA->getAliaseeObject()))
1509 return FnsInPart.contains(Fn);
1513 return ImportAllGVs || needsConservativeImport(GV);
1516 ImportAllGVs =
false;
1520 if (needsConservativeImport(&GV) && GV.use_empty())
1521 GV.eraseFromParent();
1525 printPartitionSummary(*SummariesOS, PID, *MPart, PartCost, ModuleCost);
1528 printPartitionSummary(
dbgs(), PID, *MPart, PartCost, ModuleCost));
1530 ModuleCallback(std::move(MPart));
1537 SplitModuleTimer SMT(
1538 "total",
"total pass runtime (incl. potentially waiting for lockfile)");
1561 dbgs() <<
"[amdgpu-split-module] unable to acquire lockfile, debug "
1562 "output may be mangled by other processes\n");
1563 }
else if (!Owned) {
1572 <<
"[amdgpu-split-module] unable to acquire lockfile, debug "
1573 "output may be mangled by other processes\n");
1579 splitAMDGPUModule(TTIGetter, M, N, ModuleCallback);
1587 splitAMDGPUModule(TTIGetter, M, N, ModuleCallback);
assert(UImm &&(UImm !=~static_cast< T >(0)) &&"Invalid immediate!")
static msgpack::DocNode getNode(msgpack::DocNode DN, msgpack::Type Type, MCValue Val)
function_ref< const TargetTransformInfo *(Function &)> GetTTIFn
Unify divergent function exit nodes
This file defines the BumpPtrAllocator interface.
static void print(raw_ostream &Out, object::Archive::Kind Kind, T Val)
Expand Atomic instructions
static GCRegistry::Add< ErlangGC > A("erlang", "erlang-compatible garbage collector")
static GCRegistry::Add< CoreCLRGC > E("coreclr", "CoreCLR-compatible GC")
static GCRegistry::Add< OcamlGC > B("ocaml", "ocaml 3.10-compatible GC")
This file provides interfaces used to build and manipulate a call graph, which is a very useful tool ...
#define LLVM_DUMP_METHOD
Mark debug helper function definitions like dump() that should not be stripped from debug builds.
static InstructionCost getCost(Instruction &Inst, TTI::TargetCostKind CostKind, TargetTransformInfo &TTI)
Generic implementation of equivalence classes through the use Tarjan's efficient union-find algorithm...
This file defines the little GraphTraits<X> template class that should be specialized by classes that...
Module.h This file contains the declarations for the Module class.
Machine Check Debug Module
FunctionAnalysisManager FAM
ModuleAnalysisManager MAM
This header defines classes/functions to handle pass execution timing information with interfaces for...
static StringRef getName(Value *V)
std::pair< BasicBlock *, BasicBlock * > Edge
This file defines the SmallVector class.
static const BasicSubtargetSubTypeKV * find(StringRef S, ArrayRef< BasicSubtargetSubTypeKV > A)
Find KV in array using binary search.
static Function * getFunction(FunctionType *Ty, const Twine &Name, Module *M)
PreservedAnalyses run(Module &M, ModuleAnalysisManager &MAM)
Lightweight error class with error context and mandatory checking.
static InstructionCost getMax()
Class that manages the creation of a lock file to aid implicit coordination between different process...
std::error_code unsafeUnlock() override
Remove the lock file.
WaitForUnlockResult waitForUnlockFor(std::chrono::seconds MaxSeconds) override
For a shared lock, wait until the owner releases the lock.
Expected< bool > tryLock() override
Tries to acquire the lock without blocking.
A Module instance is used to store all the information related to an LLVM module.
A set of analyses that are preserved following a run of a transformation pass.
static PreservedAnalyses none()
Convenience factory function for the empty preserved set.
SmallString - A SmallString is just a SmallVector with methods and accessors that make it work better...
StringRef str() const
Explicit conversion to StringRef.
Analysis pass providing the TargetTransformInfo.
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
LLVM_READNONE constexpr bool isEntryFunctionCC(CallingConv::ID CC)
template class LLVM_TEMPLATE_ABI opt< bool >
template class LLVM_TEMPLATE_ABI opt< unsigned >
initializer< Ty > init(const Ty &Val)
template class LLVM_TEMPLATE_ABI opt< std::string >
LLVM_ABI void system_temp_directory(bool erasedOnReboot, SmallVectorImpl< char > &result)
Get the typical temporary directory for the system, e.g., "/var/tmp" or "C:/TEMP".
LLVM_ABI void append(SmallVectorImpl< char > &path, const Twine &a, const Twine &b="", const Twine &c="", const Twine &d="")
Append to path.
This is an optimization pass for GlobalISel generic memory operations.
decltype(auto) dyn_cast(const From &Val)
dyn_cast<X> - Return the argument parameter cast to the specified type.
iterator_range< early_inc_iterator_impl< detail::IterOfRange< RangeT > > > make_early_inc_range(RangeT &&Range)
Make a range that does early increment to allow mutation of the underlying range without disrupting i...
InnerAnalysisManagerProxy< FunctionAnalysisManager, Module > FunctionAnalysisManagerModuleProxy
Provide the FunctionAnalysisManager to Module proxy.
LLVM_ABI bool TimePassesIsEnabled
If the user specifies the -time-passes argument on an LLVM tool command line then the value of this b...
raw_ostream & WriteGraph(raw_ostream &O, const GraphType &G, bool ShortNames=false, const Twine &Title="")
bool any_of(R &&range, UnaryPredicate P)
Provide wrappers to std::any_of which take ranges instead of having to pass begin/end explicitly.
LLVM_ABI raw_ostream & dbgs()
dbgs() - This returns a reference to a raw_ostream for debugging messages.
LLVM_ABI void externalizeGlobal(GlobalValue &GV)
If GV has local linkage, promote it to external + hidden visibility so it can be referenced across mo...
class LLVM_GSL_OWNER SmallVector
Forward declaration of SmallVector so that calculateSmallVectorDefaultInlinedElements can reference s...
format_object< Ts... > format(const char *Fmt, const Ts &... Vals)
These are helper functions used to produce formatted output.
@ Success
The lock was released successfully.
@ OwnerDied
Owner died while holding the lock.
@ Timeout
Reached timeout while waiting for the owner to release the lock.
LLVM_ABI raw_fd_ostream & errs()
This returns a reference to a raw_ostream for standard error.
iterator_range(Container &&) -> iterator_range< llvm::detail::IterOfRange< Container > >
@ Ref
The access may reference the value stored in memory.
ValueMap< const Value *, WeakTrackingVH > ValueToValueMapTy
decltype(auto) cast(const From &Val)
cast<X> - Return the argument parameter cast to the specified type.
AnalysisManager< Function > FunctionAnalysisManager
Convenience typedef for the Function analysis manager.
BumpPtrAllocatorImpl<> BumpPtrAllocator
The standard BumpPtrAllocator which just uses the default template parameters.
LLVM_ABI std::unique_ptr< Module > CloneModule(const Module &M)
Return an exact copy of the specified module.
void consumeError(Error Err)
Consume a Error without doing anything.
AnalysisManager< Module > ModuleAnalysisManager
Convenience typedef for the Module analysis manager.
static std::string getEdgeAttributes(const SplitGraph::Node *N, SplitGraphEdgeDstIterator EI, const SplitGraph &SG)
static std::string getGraphName(const SplitGraph &SG)
DOTGraphTraits(bool IsSimple=false)
static std::string getNodeAttributes(const SplitGraph::Node *N, const SplitGraph &SG)
static std::string getNodeDescription(const SplitGraph::Node *N, const SplitGraph &SG)
std::string getNodeLabel(const SplitGraph::Node *N, const SplitGraph &SG)
DefaultDOTGraphTraits(bool simple=false)
const SplitGraph::Edge * EdgeRef
static NodeRef getEntryNode(NodeRef N)
SplitGraph::nodes_iterator nodes_iterator
SplitGraph::edges_iterator ChildEdgeIteratorType
SplitGraphEdgeDstIterator ChildIteratorType
static nodes_iterator nodes_end(const SplitGraph &G)
static ChildIteratorType child_begin(NodeRef Ref)
static nodes_iterator nodes_begin(const SplitGraph &G)
const SplitGraph::Node * NodeRef
static ChildIteratorType child_end(NodeRef Ref)