LLVM 24.0.0git
AMDGPUSplitModule.cpp
Go to the documentation of this file.
1//===- AMDGPUSplitModule.cpp ----------------------------------------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8//
9/// \file Implements a module splitting algorithm designed to support the
10/// FullLTO --lto-partitions option for parallel codegen.
11///
12/// The role of this module splitting pass is the same as
13/// lib/Transforms/Utils/SplitModule.cpp: load-balance the module's functions
14/// across a set of N partitions to allow for parallel codegen.
15///
16/// The similarities mostly end here, as this pass achieves load-balancing in a
17/// more elaborate fashion which is targeted towards AMDGPU modules. It can take
18/// advantage of the structure of AMDGPU modules (which are mostly
19/// self-contained) to allow for more efficient splitting without affecting
20/// codegen negatively, or causing innaccurate resource usage analysis.
21///
22/// High-level pass overview:
23/// - SplitGraph & associated classes
24/// - Graph representation of the module and of the dependencies that
25/// matter for splitting.
26/// - RecursiveSearchSplitting
27/// - Core splitting algorithm.
28/// - SplitProposal
29/// - Represents a suggested solution for splitting the input module. These
30/// solutions can be scored to determine the best one when multiple
31/// solutions are available.
32/// - Driver/pass "run" function glues everything together.
33
34#include "AMDGPUSplitModule.h"
40#include "llvm/ADT/StringRef.h"
43#include "llvm/IR/Function.h"
44#include "llvm/IR/GlobalAlias.h"
46#include "llvm/IR/Instruction.h"
47#include "llvm/IR/Module.h"
49#include "llvm/IR/Value.h"
53#include "llvm/Support/Debug.h"
55#include "llvm/Support/Path.h"
56#include "llvm/Support/Timer.h"
59#include <cassert>
60#include <cmath>
61#include <utility>
62
63#ifndef NDEBUG
65#endif
66
67#define DEBUG_TYPE "amdgpu-split-module"
68
69namespace llvm {
70namespace {
71
72struct SplitModuleTimer : NamedRegionTimer {
73 SplitModuleTimer(StringRef Name, StringRef Desc)
74 : NamedRegionTimer(Name, Desc, DEBUG_TYPE, "AMDGPU Module Splitting",
76};
77
78//===----------------------------------------------------------------------===//
79// Utils
80//===----------------------------------------------------------------------===//
81
82using CostType = InstructionCost::CostType;
83using FunctionsCostMap = DenseMap<const Function *, CostType>;
84using GetTTIFn = function_ref<const TargetTransformInfo &(Function &)>;
85static constexpr unsigned InvalidPID = -1;
86
87/// \param Num numerator
88/// \param Dem denominator
89/// \returns a printable object to print (Num/Dem) using "%0.2f".
90static auto formatRatioOf(CostType Num, CostType Dem) {
91 CostType DemOr1 = Dem ? Dem : 1;
92 return format("%0.2f", (static_cast<double>(Num) / DemOr1) * 100);
93}
94
95/// Checks whether a given function is non-copyable.
96///
97/// Non-copyable functions cannot be cloned into multiple partitions, and only
98/// one copy of the function can be present across all partitions.
99///
100/// Kernel functions and external functions fall into this category. If we were
101/// to clone them, we would end up with multiple symbol definitions and a very
102/// unhappy linker.
103static bool isNonCopyable(const Function &F) {
104 return F.hasExternalLinkage() || !F.isDefinitionExact() ||
105 AMDGPU::isEntryFunctionCC(F.getCallingConv());
106}
107
108/// Cost analysis function. Calculates the cost of each function in \p M
109///
110/// \param GetTTI Abstract getter for TargetTransformInfo.
111/// \param M Module to analyze.
112/// \param CostMap[out] Resulting Function -> Cost map.
113/// \return The module's total cost.
114static CostType calculateFunctionCosts(GetTTIFn GetTTI, Module &M,
115 FunctionsCostMap &CostMap) {
116 SplitModuleTimer SMT("calculateFunctionCosts", "cost analysis");
117
118 LLVM_DEBUG(dbgs() << "[cost analysis] calculating function costs\n");
119 CostType ModuleCost = 0;
120 [[maybe_unused]] CostType KernelCost = 0;
121
122 for (auto &Fn : M) {
123 if (Fn.isDeclaration())
124 continue;
125
126 CostType FnCost = 0;
127 const auto &TTI = GetTTI(Fn);
128 for (const auto &BB : Fn) {
129 for (const auto &I : BB) {
130 auto Cost =
133 // Assume expensive if we can't tell the cost of an instruction.
134 CostType CostVal = Cost.isValid()
135 ? Cost.getValue()
137 assert((FnCost + CostVal) >= FnCost && "Overflow!");
138 FnCost += CostVal;
139 }
140 }
141
142 assert(FnCost != 0);
143
144 CostMap[&Fn] = FnCost;
145 assert((ModuleCost + FnCost) >= ModuleCost && "Overflow!");
146 ModuleCost += FnCost;
147
148 if (AMDGPU::isEntryFunctionCC(Fn.getCallingConv()))
149 KernelCost += FnCost;
150 }
151
152 if (CostMap.empty())
153 return 0;
154
155 assert(ModuleCost);
156 LLVM_DEBUG({
157 const CostType FnCost = ModuleCost - KernelCost;
158 dbgs() << " - total module cost is " << ModuleCost << ". kernels cost "
159 << "" << KernelCost << " ("
160 << format("%0.2f", (float(KernelCost) / ModuleCost) * 100)
161 << "% of the module), functions cost " << FnCost << " ("
162 << format("%0.2f", (float(FnCost) / ModuleCost) * 100)
163 << "% of the module)\n";
164 });
165
166 return ModuleCost;
167}
168
169/// \return true if \p F can be indirectly called
170static bool canBeIndirectlyCalled(const Function &F) {
171 if (F.isDeclaration() || AMDGPU::isEntryFunctionCC(F.getCallingConv()))
172 return false;
173 return !F.hasLocalLinkage() ||
174 F.hasAddressTaken(/*PutOffender=*/nullptr,
175 /*IgnoreCallbackUses=*/false,
176 /*IgnoreAssumeLikeCalls=*/true,
177 /*IgnoreLLVMUsed=*/true,
178 /*IgnoreARCAttachedCall=*/false,
179 /*IgnoreCastedDirectCall=*/true);
180}
181
182//===----------------------------------------------------------------------===//
183// Graph-based Module Representation
184//===----------------------------------------------------------------------===//
185
186/// AMDGPUSplitModule's view of the source Module, as a graph of all components
187/// that can be split into different modules.
188///
189/// The most trivial instance of this graph is just the CallGraph of the module,
190/// but it is not guaranteed that the graph is strictly equal to the CG. It
191/// currently always is but it's designed in a way that would eventually allow
192/// us to create abstract nodes, or nodes for different entities such as global
193/// variables or any other meaningful constraint we must consider.
194///
195/// The graph is only mutable by this class, and is generally not modified
196/// after \ref SplitGraph::buildGraph runs. No consumers of the graph can
197/// mutate it.
198class SplitGraph {
199public:
200 class Node;
201
202 enum class EdgeKind : uint8_t {
203 /// The nodes are related through a direct call. This is a "strong" edge as
204 /// it means the Src will directly reference the Dst.
206 /// The nodes are related through an indirect call.
207 /// This is a "weaker" edge and is only considered when traversing the graph
208 /// starting from a kernel. We need this edge for resource usage analysis.
209 ///
210 /// The reason why we have this edge in the first place is due to how
211 /// AMDGPUResourceUsageAnalysis works. In the presence of an indirect call,
212 /// the resource usage of the kernel containing the indirect call is the
213 /// max resource usage of all functions that can be indirectly called.
215 };
216
217 /// An edge between two nodes. Edges are directional, and tagged with a
218 /// "kind".
219 struct Edge {
220 Edge(Node *Src, Node *Dst, EdgeKind Kind)
221 : Src(Src), Dst(Dst), Kind(Kind) {}
222
223 Node *Src; ///< Source
224 Node *Dst; ///< Destination
225 EdgeKind Kind;
226 };
227
228 using EdgesVec = SmallVector<const Edge *, 0>;
229 using edges_iterator = EdgesVec::const_iterator;
230 using nodes_iterator = const Node *const *;
231
232 SplitGraph(const Module &M, const FunctionsCostMap &CostMap,
233 CostType ModuleCost)
234 : M(M), CostMap(CostMap), ModuleCost(ModuleCost) {}
235
236 void buildGraph(CallGraph &CG);
237
238#ifndef NDEBUG
239 bool verifyGraph() const;
240#endif
241
242 bool empty() const { return Nodes.empty(); }
243 iterator_range<nodes_iterator> nodes() const { return Nodes; }
244 const Node &getNode(unsigned ID) const { return *Nodes[ID]; }
245
246 unsigned getNumNodes() const { return Nodes.size(); }
247 BitVector createNodesBitVector() const { return BitVector(Nodes.size()); }
248
249 const Module &getModule() const { return M; }
250
251 CostType getModuleCost() const { return ModuleCost; }
252 CostType getCost(const Function &F) const { return CostMap.at(&F); }
253
254 /// \returns the aggregated cost of all nodes in \p BV (bits set to 1 = node
255 /// IDs).
256 CostType calculateCost(const BitVector &BV) const;
257
258private:
259 /// Retrieves the node for \p GV in \p Cache, or creates a new node for it and
260 /// updates \p Cache.
261 Node &getNode(DenseMap<const GlobalValue *, Node *> &Cache,
262 const GlobalValue &GV);
263
264 // Create a new edge between two nodes and add it to both nodes.
265 const Edge &createEdge(Node &Src, Node &Dst, EdgeKind EK);
266
267 const Module &M;
268 const FunctionsCostMap &CostMap;
269 CostType ModuleCost;
270
271 // Final list of nodes with stable ordering.
273
274 SpecificBumpPtrAllocator<Node> NodesPool;
275
276 // Edges are trivially destructible objects, so as a small optimization we
277 // use a BumpPtrAllocator which avoids destructor calls but also makes
278 // allocation faster.
279 static_assert(
280 std::is_trivially_destructible_v<Edge>,
281 "Edge must be trivially destructible to use the BumpPtrAllocator");
282 BumpPtrAllocator EdgesPool;
283};
284
285/// Nodes in the SplitGraph contain both incoming, and outgoing edges.
286/// Incoming edges have this node as their Dst, and Outgoing ones have this node
287/// as their Src.
288///
289/// Edge objects are shared by both nodes in Src/Dst. They provide immediate
290/// feedback on how two nodes are related, and in which direction they are
291/// related, which is valuable information to make splitting decisions.
292///
293/// Nodes are fundamentally abstract, and any consumers of the graph should
294/// treat them as such. While a node will be a function most of the time, we
295/// could also create nodes for any other reason. In the future, we could have
296/// single nodes for multiple functions, or nodes for GVs, etc.
297class SplitGraph::Node {
298 friend class SplitGraph;
299
300public:
301 Node(unsigned ID, const GlobalValue &GV, CostType IndividualCost,
302 bool IsNonCopyable)
303 : ID(ID), GV(GV), IndividualCost(IndividualCost),
304 IsNonCopyable(IsNonCopyable), IsEntryFnCC(false), IsGraphEntry(false) {
305 if (auto *Fn = dyn_cast<Function>(&GV))
306 IsEntryFnCC = AMDGPU::isEntryFunctionCC(Fn->getCallingConv());
307 }
308
309 /// An 0-indexed ID for the node. The maximum ID (exclusive) is the number of
310 /// nodes in the graph. This ID can be used as an index in a BitVector.
311 unsigned getID() const { return ID; }
312
313 const Function &getFunction() const { return cast<Function>(GV); }
314
315 /// \returns the cost to import this component into a given module, not
316 /// accounting for any dependencies that may need to be imported as well.
317 CostType getIndividualCost() const { return IndividualCost; }
318
319 bool isNonCopyable() const { return IsNonCopyable; }
320 bool isEntryFunctionCC() const { return IsEntryFnCC; }
321
322 /// \returns whether this is an entry point in the graph. Entry points are
323 /// defined as follows: if you take all entry points in the graph, and iterate
324 /// their dependencies, you are guaranteed to visit all nodes in the graph at
325 /// least once.
326 bool isGraphEntryPoint() const { return IsGraphEntry; }
327
328 StringRef getName() const { return GV.getName(); }
329
330 bool hasAnyIncomingEdges() const { return IncomingEdges.size(); }
331 bool hasAnyIncomingEdgesOfKind(EdgeKind EK) const {
332 return any_of(IncomingEdges, [&](const auto *E) { return E->Kind == EK; });
333 }
334
335 iterator_range<edges_iterator> outgoing_edges() const {
336 return OutgoingEdges;
337 }
338
339 bool shouldFollowIndirectCalls() const { return isEntryFunctionCC(); }
340
341 /// Visit all children of this node in a recursive fashion. Also visits Self.
342 /// If \ref shouldFollowIndirectCalls returns false, then this only follows
343 /// DirectCall edges.
344 ///
345 /// \param Visitor Visitor Function.
346 void visitAllDependencies(std::function<void(const Node &)> Visitor) const;
347
348 /// Adds the depedencies of this node in \p BV by setting the bit
349 /// corresponding to each node.
350 ///
351 /// Implemented using \ref visitAllDependencies, hence it follows the same
352 /// rules regarding dependencies traversal.
353 ///
354 /// \param[out] BV The bitvector where the bits should be set.
355 void getDependencies(BitVector &BV) const {
356 visitAllDependencies([&](const Node &N) { BV.set(N.getID()); });
357 }
358
359private:
360 void markAsGraphEntry() { IsGraphEntry = true; }
361
362 unsigned ID;
363 const GlobalValue &GV;
364 CostType IndividualCost;
365 bool IsNonCopyable : 1;
366 bool IsEntryFnCC : 1;
367 bool IsGraphEntry : 1;
368
369 // TODO: Use a single sorted vector (with all incoming/outgoing edges grouped
370 // together)
371 EdgesVec IncomingEdges;
372 EdgesVec OutgoingEdges;
373};
374
375void SplitGraph::Node::visitAllDependencies(
376 std::function<void(const Node &)> Visitor) const {
377 const bool FollowIndirect = shouldFollowIndirectCalls();
378 // FIXME: If this can access SplitGraph in the future, use a BitVector
379 // instead.
380 DenseSet<const Node *> Seen;
381 SmallVector<const Node *, 8> WorkList({this});
382 while (!WorkList.empty()) {
383 const Node *CurN = WorkList.pop_back_val();
384 if (auto [It, Inserted] = Seen.insert(CurN); !Inserted)
385 continue;
386
387 Visitor(*CurN);
388
389 for (const Edge *E : CurN->outgoing_edges()) {
390 if (!FollowIndirect && E->Kind == EdgeKind::IndirectCall)
391 continue;
392 WorkList.push_back(E->Dst);
393 }
394 }
395}
396
397/// Checks if \p I has MD_callees and if it does, parse it and put the function
398/// in \p Callees.
399///
400/// \returns true if there was metadata and it was parsed correctly. false if
401/// there was no MD or if it contained unknown entries and parsing failed.
402/// If this returns false, \p Callees will contain incomplete information
403/// and must not be used.
404static bool handleCalleesMD(const Instruction &I,
405 SetVector<Function *> &Callees) {
406 auto *MD = I.getMetadata(LLVMContext::MD_callees);
407 if (!MD)
408 return false;
409
410 for (const auto &Op : MD->operands()) {
411 Function *Callee = mdconst::extract_or_null<Function>(Op);
412 if (!Callee)
413 return false;
414 Callees.insert(Callee);
415 }
416
417 return true;
418}
419
420void SplitGraph::buildGraph(CallGraph &CG) {
421 SplitModuleTimer SMT("buildGraph", "graph construction");
423 dbgs()
424 << "[build graph] constructing graph representation of the input\n");
425
426 // FIXME(?): Is the callgraph really worth using if we have to iterate the
427 // function again whenever it fails to give us enough information?
428
429 // We build the graph by just iterating all functions in the module and
430 // working on their direct callees. At the end, all nodes should be linked
431 // together as expected.
432 DenseMap<const GlobalValue *, Node *> Cache;
433 SmallVector<const Function *> FnsWithIndirectCalls, IndirectlyCallableFns;
434 for (const Function &Fn : M) {
435 if (Fn.isDeclaration())
436 continue;
437
438 // Look at direct callees and create the necessary edges in the graph.
439 SetVector<const Function *> DirectCallees;
440 bool CallsExternal = false;
441 for (auto &CGEntry : *CG[&Fn]) {
442 auto *CGNode = CGEntry.second;
443 if (auto *Callee = CGNode->getFunction()) {
444 if (!Callee->isDeclaration())
445 DirectCallees.insert(Callee);
446 } else if (CGNode == CG.getCallsExternalNode())
447 CallsExternal = true;
448 }
449
450 // Keep track of this function if it contains an indirect call and/or if it
451 // can be indirectly called.
452 if (CallsExternal) {
453 LLVM_DEBUG(dbgs() << " [!] callgraph is incomplete for ";
454 Fn.printAsOperand(dbgs());
455 dbgs() << " - analyzing function\n");
456
457 SetVector<Function *> KnownCallees;
458 bool HasUnknownIndirectCall = false;
459 for (const auto &Inst : instructions(Fn)) {
460 // look at all calls without a direct callee.
461 const auto *CB = dyn_cast<CallBase>(&Inst);
462 if (!CB || CB->getCalledFunction())
463 continue;
464
465 // inline assembly can be ignored, unless InlineAsmIsIndirectCall is
466 // true.
467 if (CB->isInlineAsm()) {
468 LLVM_DEBUG(dbgs() << " found inline assembly\n");
469 continue;
470 }
471
472 if (handleCalleesMD(Inst, KnownCallees))
473 continue;
474 // If we failed to parse any !callees MD, or some was missing,
475 // the entire KnownCallees list is now unreliable.
476 KnownCallees.clear();
477
478 // Everything else is handled conservatively. If we fall into the
479 // conservative case don't bother analyzing further.
480 HasUnknownIndirectCall = true;
481 break;
482 }
483
484 if (HasUnknownIndirectCall) {
485 LLVM_DEBUG(dbgs() << " indirect call found\n");
486 FnsWithIndirectCalls.push_back(&Fn);
487 } else if (!KnownCallees.empty())
488 DirectCallees.insert_range(KnownCallees);
489 }
490
491 Node &N = getNode(Cache, Fn);
492 for (const auto *Callee : DirectCallees)
493 createEdge(N, getNode(Cache, *Callee), EdgeKind::DirectCall);
494
495 if (canBeIndirectlyCalled(Fn))
496 IndirectlyCallableFns.push_back(&Fn);
497 }
498
499 // Post-process functions with indirect calls.
500 for (const Function *Fn : FnsWithIndirectCalls) {
501 for (const Function *Candidate : IndirectlyCallableFns) {
502 Node &Src = getNode(Cache, *Fn);
503 Node &Dst = getNode(Cache, *Candidate);
504 createEdge(Src, Dst, EdgeKind::IndirectCall);
505 }
506 }
507
508 // Now, find all entry points.
509 SmallVector<Node *, 16> CandidateEntryPoints;
510 BitVector NodesReachableByKernels = createNodesBitVector();
511 for (Node *N : Nodes) {
512 // Functions with an Entry CC are always graph entry points too.
513 if (N->isEntryFunctionCC()) {
514 N->markAsGraphEntry();
515 N->getDependencies(NodesReachableByKernels);
516 } else if (!N->hasAnyIncomingEdgesOfKind(EdgeKind::DirectCall))
517 CandidateEntryPoints.push_back(N);
518 }
519
520 BitVector ReachableNodes = NodesReachableByKernels;
521 for (Node *N : CandidateEntryPoints) {
522 // This can be another entry point if it's not reachable by a kernel
523 // TODO: We could sort all of the possible new entries in a stable order
524 // (e.g. by cost), then consume them one by one until
525 // NodesReachableByKernels is all 1s. It'd allow us to avoid
526 // considering some nodes as non-entries in some specific cases.
527 if (!NodesReachableByKernels.test(N->getID())) {
528 N->markAsGraphEntry();
529 N->getDependencies(ReachableNodes);
530 }
531 }
532
533 // A function in a call cycle always has an incoming DirectCall edge, so it
534 // is never a candidate above. If no entry point reaches a cycle, one of its
535 // functions must become an entry point. Visit the unreached nodes in reverse
536 // post-order of the DirectCall edges, so that callers come before callees.
537 // This way, a function that an unreached cycle calls does not become an
538 // entry point of its own.
539 SmallVector<Node *> PostOrder;
540 BitVector Visited = ReachableNodes;
541 SmallVector<std::pair<Node *, edges_iterator>> Stack;
542 for (Node *Root : Nodes) {
543 if (Visited.test(Root->getID()))
544 continue;
545 Visited.set(Root->getID());
546 Stack.emplace_back(Root, Root->outgoing_edges().begin());
547 while (!Stack.empty()) {
548 auto &[N, It] = Stack.back();
549 if (It == N->outgoing_edges().end()) {
550 PostOrder.push_back(N);
551 Stack.pop_back();
552 continue;
553 }
554 const Edge *E = *It++;
555 if (E->Kind != EdgeKind::DirectCall || Visited.test(E->Dst->getID()))
556 continue;
557 Visited.set(E->Dst->getID());
558 Stack.emplace_back(E->Dst, E->Dst->outgoing_edges().begin());
559 }
560 }
561
562 for (Node *N : reverse(PostOrder)) {
563 if (ReachableNodes.test(N->getID()))
564 continue;
565 N->markAsGraphEntry();
566 N->getDependencies(ReachableNodes);
567 }
568
569#ifndef NDEBUG
570 assert(verifyGraph());
571#endif
572}
573
574#ifndef NDEBUG
575bool SplitGraph::verifyGraph() const {
576 unsigned ExpectedID = 0;
577 // Exceptionally using a set here in case IDs are messed up.
578 DenseSet<const Node *> SeenNodes;
579 DenseSet<const Function *> SeenFunctionNodes;
580 for (const Node *N : Nodes) {
581 if (N->getID() != (ExpectedID++)) {
582 errs() << "Node IDs are incorrect!\n";
583 return false;
584 }
585
586 if (!SeenNodes.insert(N).second) {
587 errs() << "Node seen more than once!\n";
588 return false;
589 }
590
591 if (&getNode(N->getID()) != N) {
592 errs() << "getNode doesn't return the right node\n";
593 return false;
594 }
595
596 for (const Edge *E : N->IncomingEdges) {
597 if (!E->Src || !E->Dst || (E->Dst != N) ||
598 (find(E->Src->OutgoingEdges, E) == E->Src->OutgoingEdges.end())) {
599 errs() << "ill-formed incoming edges\n";
600 return false;
601 }
602 }
603
604 for (const Edge *E : N->OutgoingEdges) {
605 if (!E->Src || !E->Dst || (E->Src != N) ||
606 (find(E->Dst->IncomingEdges, E) == E->Dst->IncomingEdges.end())) {
607 errs() << "ill-formed outgoing edges\n";
608 return false;
609 }
610 }
611
612 const Function &Fn = N->getFunction();
613 if (AMDGPU::isEntryFunctionCC(Fn.getCallingConv())) {
614 if (N->hasAnyIncomingEdges()) {
615 errs() << "Kernels cannot have incoming edges\n";
616 return false;
617 }
618 }
619
620 if (Fn.isDeclaration()) {
621 errs() << "declarations shouldn't have nodes!\n";
622 return false;
623 }
624
625 auto [It, Inserted] = SeenFunctionNodes.insert(&Fn);
626 if (!Inserted) {
627 errs() << "one function has multiple nodes!\n";
628 return false;
629 }
630 }
631
632 if (ExpectedID != Nodes.size()) {
633 errs() << "Node IDs out of sync!\n";
634 return false;
635 }
636
637 if (createNodesBitVector().size() != getNumNodes()) {
638 errs() << "nodes bit vector doesn't have the right size!\n";
639 return false;
640 }
641
642 // Check we respect the promise of Node::isKernel
643 BitVector BV = createNodesBitVector();
644 for (const Node *N : nodes()) {
645 if (N->isGraphEntryPoint())
646 N->getDependencies(BV);
647 }
648
649 // Ensure each function in the module has an associated node.
650 for (const auto &Fn : M) {
651 if (!Fn.isDeclaration()) {
652 if (!SeenFunctionNodes.contains(&Fn)) {
653 errs() << "Fn has no associated node in the graph!\n";
654 return false;
655 }
656 }
657 }
658
659 if (!BV.all()) {
660 errs() << "not all nodes are reachable through the graph's entry points!\n";
661 return false;
662 }
663
664 return true;
665}
666#endif
667
668CostType SplitGraph::calculateCost(const BitVector &BV) const {
669 CostType Cost = 0;
670 for (unsigned NodeID : BV.set_bits())
671 Cost += getNode(NodeID).getIndividualCost();
672 return Cost;
673}
674
675SplitGraph::Node &
676SplitGraph::getNode(DenseMap<const GlobalValue *, Node *> &Cache,
677 const GlobalValue &GV) {
678 auto &N = Cache[&GV];
679 if (N)
680 return *N;
681
682 CostType Cost = 0;
683 bool NonCopyable = false;
684 if (const Function *Fn = dyn_cast<Function>(&GV)) {
685 NonCopyable = isNonCopyable(*Fn);
686 Cost = CostMap.at(Fn);
687 }
688 N = new (NodesPool.Allocate()) Node(Nodes.size(), GV, Cost, NonCopyable);
689 Nodes.push_back(N);
690 assert(&getNode(N->getID()) == N);
691 return *N;
692}
693
694const SplitGraph::Edge &SplitGraph::createEdge(Node &Src, Node &Dst,
695 EdgeKind EK) {
696 const Edge *E = new (EdgesPool.Allocate<Edge>(1)) Edge(&Src, &Dst, EK);
697 Src.OutgoingEdges.push_back(E);
698 Dst.IncomingEdges.push_back(E);
699 return *E;
700}
701
702//===----------------------------------------------------------------------===//
703// Split Proposals
704//===----------------------------------------------------------------------===//
705
706/// Represents a module splitting proposal.
707///
708/// Proposals are made of N BitVectors, one for each partition, where each bit
709/// set indicates that the node is present and should be copied inside that
710/// partition.
711///
712/// Proposals have several metrics attached so they can be compared/sorted,
713/// which the driver to try multiple strategies resultings in multiple proposals
714/// and choose the best one out of them.
715class SplitProposal {
716public:
717 SplitProposal(const SplitGraph &SG, unsigned MaxPartitions) : SG(&SG) {
718 Partitions.resize(MaxPartitions, {0, SG.createNodesBitVector()});
719 }
720
721 void setName(StringRef NewName) { Name = NewName; }
722 StringRef getName() const { return Name; }
723
724 const BitVector &operator[](unsigned PID) const {
725 return Partitions[PID].second;
726 }
727
728 void add(unsigned PID, const BitVector &BV) {
729 Partitions[PID].second |= BV;
730 updateScore(PID);
731 }
732
733 void print(raw_ostream &OS) const;
734 LLVM_DUMP_METHOD void dump() const { print(dbgs()); }
735
736 // Find the cheapest partition (lowest cost). In case of ties, always returns
737 // the highest partition number.
738 unsigned findCheapestPartition() const;
739
740 /// Calculate the CodeSize and Bottleneck scores.
741 void calculateScores();
742
743#ifndef NDEBUG
744 void verifyCompleteness() const;
745#endif
746
747 /// Only available after \ref calculateScores is called.
748 ///
749 /// A positive number indicating the % of code duplication that this proposal
750 /// creates. e.g. 0.2 means this proposal adds roughly 20% code size by
751 /// duplicating some functions across partitions.
752 ///
753 /// Value is always rounded up to 3 decimal places.
754 ///
755 /// A perfect score would be 0.0, and anything approaching 1.0 is very bad.
756 double getCodeSizeScore() const { return CodeSizeScore; }
757
758 /// Only available after \ref calculateScores is called.
759 ///
760 /// A number between [0, 1] which indicates how big of a bottleneck is
761 /// expected from the largest partition.
762 ///
763 /// A score of 1.0 means the biggest partition is as big as the source module,
764 /// so build time will be equal to or greater than the build time of the
765 /// initial input.
766 ///
767 /// Value is always rounded up to 3 decimal places.
768 ///
769 /// This is one of the metrics used to estimate this proposal's build time.
770 double getBottleneckScore() const { return BottleneckScore; }
771
772private:
773 void updateScore(unsigned PID) {
774 assert(SG);
775 for (auto &[PCost, Nodes] : Partitions) {
776 TotalCost -= PCost;
777 PCost = SG->calculateCost(Nodes);
778 TotalCost += PCost;
779 }
780 }
781
782 /// \see getCodeSizeScore
783 double CodeSizeScore = 0.0;
784 /// \see getBottleneckScore
785 double BottleneckScore = 0.0;
786 /// Aggregated cost of all partitions
787 CostType TotalCost = 0;
788
789 const SplitGraph *SG = nullptr;
790 std::string Name;
791
792 std::vector<std::pair<CostType, BitVector>> Partitions;
793};
794
795void SplitProposal::print(raw_ostream &OS) const {
796 assert(SG);
797
798 OS << "[proposal] " << Name << ", total cost:" << TotalCost
799 << ", code size score:" << format("%0.3f", CodeSizeScore)
800 << ", bottleneck score:" << format("%0.3f", BottleneckScore) << '\n';
801 for (const auto &[PID, Part] : enumerate(Partitions)) {
802 const auto &[Cost, NodeIDs] = Part;
803 OS << " - P" << PID << " nodes:" << NodeIDs.count() << " cost: " << Cost
804 << '|' << formatRatioOf(Cost, SG->getModuleCost()) << "%\n";
805 }
806}
807
808unsigned SplitProposal::findCheapestPartition() const {
809 assert(!Partitions.empty());
810 CostType CurCost = std::numeric_limits<CostType>::max();
811 unsigned CurPID = InvalidPID;
812 for (const auto &[Idx, Part] : enumerate(Partitions)) {
813 if (Part.first <= CurCost) {
814 CurPID = Idx;
815 CurCost = Part.first;
816 }
817 }
818 assert(CurPID != InvalidPID);
819 return CurPID;
820}
821
822void SplitProposal::calculateScores() {
823 if (Partitions.empty())
824 return;
825
826 assert(SG);
827 CostType LargestPCost = 0;
828 for (auto &[PCost, Nodes] : Partitions) {
829 if (PCost > LargestPCost)
830 LargestPCost = PCost;
831 }
832
833 CostType ModuleCost = SG->getModuleCost();
834 CodeSizeScore = double(TotalCost) / ModuleCost;
835 assert(CodeSizeScore >= 0.0);
836
837 BottleneckScore = double(LargestPCost) / ModuleCost;
838
839 CodeSizeScore = std::ceil(CodeSizeScore * 100.0) / 100.0;
840 BottleneckScore = std::ceil(BottleneckScore * 100.0) / 100.0;
841}
842
843#ifndef NDEBUG
844void SplitProposal::verifyCompleteness() const {
845 if (Partitions.empty())
846 return;
847
848 BitVector Result = Partitions[0].second;
849 for (const auto &P : drop_begin(Partitions))
850 Result |= P.second;
851 assert(Result.all() && "some nodes are missing from this proposal!");
852}
853#endif
854
855//===-- RecursiveSearchStrategy -------------------------------------------===//
856
857/// Partitioning algorithm.
858///
859/// This is a recursive search algorithm that can explore multiple possiblities.
860///
861/// When a cluster of nodes can go into more than one partition, and we haven't
862/// reached maximum search depth, we recurse and explore both options and their
863/// consequences. Both branches will yield a proposal, and the driver will grade
864/// both and choose the best one.
865///
866/// If max depth is reached, we will use some heuristics to make a choice. Most
867/// of the time we will just use the least-pressured (cheapest) partition, but
868/// if a cluster is particularly big and there is a good amount of overlap with
869/// an existing partition, we will choose that partition instead.
870class RecursiveSearchSplitting {
871public:
872 using SubmitProposalFn = function_ref<void(SplitProposal)>;
873
874 RecursiveSearchSplitting(const AMDGPUOptions &CLOpts, const SplitGraph &SG,
875 unsigned NumParts, SubmitProposalFn SubmitProposal);
876
877 void run();
878
879private:
880 struct WorkListEntry {
881 WorkListEntry(const BitVector &BV) : Cluster(BV) {}
882
883 unsigned NumNonEntryNodes = 0;
884 CostType TotalCost = 0;
885 CostType CostExcludingGraphEntryPoints = 0;
886 BitVector Cluster;
887 };
888
889 /// Collects all graph entry points's clusters and sort them so the most
890 /// expensive clusters are viewed first. This will merge clusters together if
891 /// they share a non-copyable dependency.
892 void setupWorkList();
893
894 /// Recursive function that assigns the worklist item at \p Idx into a
895 /// partition of \p SP.
896 ///
897 /// \p Depth is the current search depth. When this value is equal to
898 /// the maximum search depth, we can no longer recurse.
899 ///
900 /// This function only recurses if there is more than one possible assignment,
901 /// otherwise it is iterative to avoid creating a call stack that is as big as
902 /// \ref WorkList.
903 void pickPartition(unsigned Depth, unsigned Idx, SplitProposal SP);
904
905 /// \return A pair: first element is the PID of the partition that has the
906 /// most similarities with \p Entry, or \ref InvalidPID if no partition was
907 /// found with at least one element in common. The second element is the
908 /// aggregated cost of all dependencies in common between \p Entry and that
909 /// partition.
910 std::pair<unsigned, CostType>
911 findMostSimilarPartition(const WorkListEntry &Entry, const SplitProposal &SP);
912
913 const AMDGPUOptions &CLOpts;
914 const SplitGraph &SG;
915 unsigned NumParts;
916 SubmitProposalFn SubmitProposal;
917
918 // A Cluster is considered large when its cost, excluding entry points,
919 // exceeds this value.
920 CostType LargeClusterThreshold = 0;
921 unsigned NumProposalsSubmitted = 0;
922 SmallVector<WorkListEntry> WorkList;
923};
924
925RecursiveSearchSplitting::RecursiveSearchSplitting(
926 const AMDGPUOptions &CLOpts, const SplitGraph &SG, unsigned NumParts,
927 SubmitProposalFn SubmitProposal)
928 : CLOpts(CLOpts), SG(SG), NumParts(NumParts),
929 SubmitProposal(SubmitProposal) {
930 // arbitrary max value as a safeguard. Anything above 10 will already be
931 // slow, this is just a max value to prevent extreme resource exhaustion or
932 // unbounded run time.
933 if (CLOpts.module_splitting_max_depth > 16)
934 report_fatal_error("[amdgpu-split-module] search depth of " +
935 Twine(CLOpts.module_splitting_max_depth) +
936 " is too high!");
937 float LargeFnFactor = CLOpts.module_splitting_large_threshold;
938 LargeClusterThreshold =
939 (LargeFnFactor != 0.0)
940 ? CostType(((SG.getModuleCost() / NumParts) * LargeFnFactor))
941 : std::numeric_limits<CostType>::max();
942 LLVM_DEBUG(dbgs() << "[recursive search] large cluster threshold set at "
943 << LargeClusterThreshold << "\n");
944}
945
946void RecursiveSearchSplitting::run() {
947 {
948 SplitModuleTimer SMT("recursive_search_prepare", "preparing worklist");
949 setupWorkList();
950 }
951
952 {
953 SplitModuleTimer SMT("recursive_search_pick", "partitioning");
954 SplitProposal SP(SG, NumParts);
955 pickPartition(/*BranchDepth=*/0, /*Idx=*/0, std::move(SP));
956 }
957}
958
959void RecursiveSearchSplitting::setupWorkList() {
960 // e.g. if A and B are two worklist item, and they both call a non copyable
961 // dependency C, this does:
962 // A=C
963 // B=C
964 // => NodeEC will create a single group (A, B, C) and we create a new
965 // WorkList entry for that group.
966
967 EquivalenceClasses<unsigned> NodeEC;
968 for (const SplitGraph::Node *N : SG.nodes()) {
969 if (!N->isGraphEntryPoint())
970 continue;
971
972 NodeEC.insert(N->getID());
973 N->visitAllDependencies([&](const SplitGraph::Node &Dep) {
974 if (&Dep != N && Dep.isNonCopyable())
975 NodeEC.unionSets(N->getID(), Dep.getID());
976 });
977 }
978
979 for (const auto &Node : NodeEC) {
980 if (!Node->isLeader())
981 continue;
982
983 BitVector Cluster = SG.createNodesBitVector();
984 for (unsigned M : NodeEC.members(*Node)) {
985 const SplitGraph::Node &N = SG.getNode(M);
986 if (N.isGraphEntryPoint())
987 N.getDependencies(Cluster);
988 }
989 WorkList.emplace_back(std::move(Cluster));
990 }
991
992 // Calculate costs and other useful information.
993 for (WorkListEntry &Entry : WorkList) {
994 for (unsigned NodeID : Entry.Cluster.set_bits()) {
995 const SplitGraph::Node &N = SG.getNode(NodeID);
996 const CostType Cost = N.getIndividualCost();
997
998 Entry.TotalCost += Cost;
999 if (!N.isGraphEntryPoint()) {
1000 Entry.CostExcludingGraphEntryPoints += Cost;
1001 ++Entry.NumNonEntryNodes;
1002 }
1003 }
1004 }
1005
1006 stable_sort(WorkList, [](const WorkListEntry &A, const WorkListEntry &B) {
1007 if (A.TotalCost != B.TotalCost)
1008 return A.TotalCost > B.TotalCost;
1009
1010 if (A.CostExcludingGraphEntryPoints != B.CostExcludingGraphEntryPoints)
1011 return A.CostExcludingGraphEntryPoints > B.CostExcludingGraphEntryPoints;
1012
1013 if (A.NumNonEntryNodes != B.NumNonEntryNodes)
1014 return A.NumNonEntryNodes > B.NumNonEntryNodes;
1015
1016 return A.Cluster.count() > B.Cluster.count();
1017 });
1018
1019 LLVM_DEBUG({
1020 dbgs() << "[recursive search] worklist:\n";
1021 for (const auto &[Idx, Entry] : enumerate(WorkList)) {
1022 dbgs() << " - [" << Idx << "]: ";
1023 for (unsigned NodeID : Entry.Cluster.set_bits())
1024 dbgs() << NodeID << " ";
1025 dbgs() << "(total_cost:" << Entry.TotalCost
1026 << ", cost_excl_entries:" << Entry.CostExcludingGraphEntryPoints
1027 << ")\n";
1028 }
1029 });
1030}
1031
1032void RecursiveSearchSplitting::pickPartition(unsigned Depth, unsigned Idx,
1033 SplitProposal SP) {
1034 while (Idx < WorkList.size()) {
1035 // Step 1: Determine candidate PIDs.
1036 //
1037 const WorkListEntry &Entry = WorkList[Idx];
1038 const BitVector &Cluster = Entry.Cluster;
1039
1040 // Default option is to do load-balancing, AKA assign to least pressured
1041 // partition.
1042 const unsigned CheapestPID = SP.findCheapestPartition();
1043 assert(CheapestPID != InvalidPID);
1044
1045 // Explore assigning to the kernel that contains the most dependencies in
1046 // common.
1047 const auto [MostSimilarPID, SimilarDepsCost] =
1048 findMostSimilarPartition(Entry, SP);
1049
1050 // We can chose to explore only one path if we only have one valid path, or
1051 // if we reached maximum search depth and can no longer branch out.
1052 unsigned SinglePIDToTry = InvalidPID;
1053 if (MostSimilarPID == InvalidPID) // no similar PID found
1054 SinglePIDToTry = CheapestPID;
1055 else if (MostSimilarPID == CheapestPID) // both landed on the same PID
1056 SinglePIDToTry = CheapestPID;
1057 else if (Depth >= CLOpts.module_splitting_max_depth) {
1058 // We have to choose one path. Use a heuristic to guess which one will be
1059 // more appropriate.
1060 if (Entry.CostExcludingGraphEntryPoints > LargeClusterThreshold) {
1061 // Check if the amount of code in common makes it worth it.
1062 assert(SimilarDepsCost && Entry.CostExcludingGraphEntryPoints);
1063 const double Ratio = static_cast<double>(SimilarDepsCost) /
1064 Entry.CostExcludingGraphEntryPoints;
1065 assert(Ratio >= 0.0 && Ratio <= 1.0);
1066 if (Ratio > CLOpts.module_splitting_merge_threshold) {
1067 // For debug, just print "L", so we'll see "L3=P3" for instance, which
1068 // will mean we reached max depth and chose P3 based on this
1069 // heuristic.
1070 LLVM_DEBUG(dbgs() << 'L');
1071 SinglePIDToTry = MostSimilarPID;
1072 }
1073 } else
1074 SinglePIDToTry = CheapestPID;
1075 }
1076
1077 // Step 2: Explore candidates.
1078
1079 // When we only explore one possible path, and thus branch depth doesn't
1080 // increase, do not recurse, iterate instead.
1081 if (SinglePIDToTry != InvalidPID) {
1082 LLVM_DEBUG(dbgs() << Idx << "=P" << SinglePIDToTry << ' ');
1083 // Only one path to explore, don't clone SP, don't increase depth.
1084 SP.add(SinglePIDToTry, Cluster);
1085 ++Idx;
1086 continue;
1087 }
1088
1089 assert(MostSimilarPID != InvalidPID);
1090
1091 // We explore multiple paths: recurse at increased depth, then stop this
1092 // function.
1093
1094 LLVM_DEBUG(dbgs() << '\n');
1095
1096 // lb = load balancing = put in cheapest partition
1097 {
1098 SplitProposal BranchSP = SP;
1099 LLVM_DEBUG(dbgs().indent(Depth)
1100 << " [lb] " << Idx << "=P" << CheapestPID << "? ");
1101 BranchSP.add(CheapestPID, Cluster);
1102 pickPartition(Depth + 1, Idx + 1, std::move(BranchSP));
1103 }
1104
1105 // ms = most similar = put in partition with the most in common
1106 {
1107 SplitProposal BranchSP = SP;
1108 LLVM_DEBUG(dbgs().indent(Depth)
1109 << " [ms] " << Idx << "=P" << MostSimilarPID << "? ");
1110 BranchSP.add(MostSimilarPID, Cluster);
1111 pickPartition(Depth + 1, Idx + 1, std::move(BranchSP));
1112 }
1113
1114 return;
1115 }
1116
1117 // Step 3: If we assigned all WorkList items, submit the proposal.
1118
1119 assert(Idx == WorkList.size());
1120 assert(NumProposalsSubmitted <= (2u << CLOpts.module_splitting_max_depth) &&
1121 "Search got out of bounds?");
1122 SP.setName("recursive_search (depth=" + std::to_string(Depth) + ") #" +
1123 std::to_string(NumProposalsSubmitted++));
1124 LLVM_DEBUG(dbgs() << '\n');
1125 SubmitProposal(std::move(SP));
1126}
1127
1128std::pair<unsigned, CostType>
1129RecursiveSearchSplitting::findMostSimilarPartition(const WorkListEntry &Entry,
1130 const SplitProposal &SP) {
1131 if (!Entry.NumNonEntryNodes)
1132 return {InvalidPID, 0};
1133
1134 // We take the partition that is the most similar using Cost as a metric.
1135 // So we take the set of nodes in common, compute their aggregated cost, and
1136 // pick the partition with the highest cost in common.
1137 unsigned ChosenPID = InvalidPID;
1138 CostType ChosenCost = 0;
1139 for (unsigned PID = 0; PID < NumParts; ++PID) {
1140 BitVector BV = SP[PID];
1141 BV &= Entry.Cluster; // FIXME: & doesn't work between BVs?!
1142
1143 if (BV.none())
1144 continue;
1145
1146 const CostType Cost = SG.calculateCost(BV);
1147
1148 if (ChosenPID == InvalidPID || ChosenCost < Cost ||
1149 (ChosenCost == Cost && PID > ChosenPID)) {
1150 ChosenPID = PID;
1151 ChosenCost = Cost;
1152 }
1153 }
1154
1155 return {ChosenPID, ChosenCost};
1156}
1157
1158//===----------------------------------------------------------------------===//
1159// DOTGraph Printing Support
1160//===----------------------------------------------------------------------===//
1161
1162const SplitGraph::Node *mapEdgeToDst(const SplitGraph::Edge *E) {
1163 return E->Dst;
1164}
1165
1166using SplitGraphEdgeDstIterator =
1167 mapped_iterator<SplitGraph::edges_iterator, decltype(&mapEdgeToDst)>;
1168
1169} // namespace
1170
1171template <> struct GraphTraits<SplitGraph> {
1172 using NodeRef = const SplitGraph::Node *;
1173 using nodes_iterator = SplitGraph::nodes_iterator;
1174 using ChildIteratorType = SplitGraphEdgeDstIterator;
1175
1176 using EdgeRef = const SplitGraph::Edge *;
1177 using ChildEdgeIteratorType = SplitGraph::edges_iterator;
1178
1179 static NodeRef getEntryNode(NodeRef N) { return N; }
1180
1182 return {Ref->outgoing_edges().begin(), mapEdgeToDst};
1183 }
1185 return {Ref->outgoing_edges().end(), mapEdgeToDst};
1186 }
1187
1188 static nodes_iterator nodes_begin(const SplitGraph &G) {
1189 return G.nodes().begin();
1190 }
1191 static nodes_iterator nodes_end(const SplitGraph &G) {
1192 return G.nodes().end();
1193 }
1194};
1195
1196template <> struct DOTGraphTraits<SplitGraph> : public DefaultDOTGraphTraits {
1197 DOTGraphTraits(bool IsSimple = false) : DefaultDOTGraphTraits(IsSimple) {}
1198
1199 static std::string getGraphName(const SplitGraph &SG) {
1200 return SG.getModule().getName().str();
1201 }
1202
1203 std::string getNodeLabel(const SplitGraph::Node *N, const SplitGraph &SG) {
1204 return N->getName().str();
1205 }
1206
1207 static std::string getNodeDescription(const SplitGraph::Node *N,
1208 const SplitGraph &SG) {
1209 std::string Result;
1210 if (N->isEntryFunctionCC())
1211 Result += "entry-fn-cc ";
1212 if (N->isNonCopyable())
1213 Result += "non-copyable ";
1214 Result += "cost:" + std::to_string(N->getIndividualCost());
1215 return Result;
1216 }
1217
1218 static std::string getNodeAttributes(const SplitGraph::Node *N,
1219 const SplitGraph &SG) {
1220 return N->hasAnyIncomingEdges() ? "" : "color=\"red\"";
1221 }
1222
1223 static std::string getEdgeAttributes(const SplitGraph::Node *N,
1224 SplitGraphEdgeDstIterator EI,
1225 const SplitGraph &SG) {
1226
1227 switch ((*EI.getCurrent())->Kind) {
1228 case SplitGraph::EdgeKind::DirectCall:
1229 return "";
1230 case SplitGraph::EdgeKind::IndirectCall:
1231 return "style=\"dashed\"";
1232 }
1233 llvm_unreachable("Unknown SplitGraph::EdgeKind enum");
1234 }
1235};
1236
1237//===----------------------------------------------------------------------===//
1238// Driver
1239//===----------------------------------------------------------------------===//
1240
1241namespace {
1242
1243// If we didn't externalize GVs, then local GVs need to be conservatively
1244// imported into every module (including their initializers), and then cleaned
1245// up afterwards.
1246static bool needsConservativeImport(const GlobalValue *GV) {
1247 if (const auto *Var = dyn_cast<GlobalVariable>(GV))
1248 return Var->hasLocalLinkage();
1249 if (const auto *GA = dyn_cast<GlobalAlias>(GV))
1250 return GA->hasLocalLinkage();
1251 return false;
1252}
1253
1254/// Prints a summary of the partition \p N, represented by module \p M, to \p
1255/// OS.
1256static void printPartitionSummary(raw_ostream &OS, unsigned N, const Module &M,
1257 unsigned PartCost, unsigned ModuleCost) {
1258 OS << "*** Partition P" << N << " ***\n";
1259
1260 for (const auto &Fn : M) {
1261 if (!Fn.isDeclaration())
1262 OS << " - [function] " << Fn.getName() << "\n";
1263 }
1264
1265 for (const auto &GV : M.globals()) {
1266 if (GV.hasInitializer())
1267 OS << " - [global] " << GV.getName() << "\n";
1268 }
1269
1270 OS << "Partition contains " << formatRatioOf(PartCost, ModuleCost)
1271 << "% of the source\n";
1272}
1273
1274static void evaluateProposal(const AMDGPUOptions &CLOpts, SplitProposal &Best,
1275 SplitProposal New) {
1276 SplitModuleTimer SMT("proposal_evaluation", "proposal ranking algorithm");
1277
1278 LLVM_DEBUG({
1279 New.verifyCompleteness();
1280 if (CLOpts.module_splitting_debug_proposal_search)
1281 New.print(dbgs());
1282 });
1283
1284 const double CurBScore = Best.getBottleneckScore();
1285 const double CurCSScore = Best.getCodeSizeScore();
1286 const double NewBScore = New.getBottleneckScore();
1287 const double NewCSScore = New.getCodeSizeScore();
1288
1289 // TODO: Improve this
1290 // We can probably lower the precision of the comparison at first
1291 // e.g. if we have
1292 // - (Current): BScore: 0.489 CSCore 1.105
1293 // - (New): BScore: 0.475 CSCore 1.305
1294 // Currently we'd choose the new one because the bottleneck score is
1295 // lower, but the new one duplicates more code. It may be worth it to
1296 // discard the new proposal as the impact on build time is negligible.
1297
1298 // Compare them
1299 bool IsBest = false;
1300 if (NewBScore < CurBScore)
1301 IsBest = true;
1302 else if (NewBScore == CurBScore)
1303 IsBest = (NewCSScore < CurCSScore); // Use code size as tie breaker.
1304
1305 if (IsBest)
1306 Best = std::move(New);
1307
1308 LLVM_DEBUG(if (CLOpts.module_splitting_debug_proposal_search) {
1309 if (IsBest)
1310 dbgs() << "[search] new best proposal!\n";
1311 else
1312 dbgs() << "[search] discarding - not profitable\n";
1313 });
1314}
1315
1316/// Trivial helper to create an identical copy of \p M.
1317static std::unique_ptr<Module> cloneAll(const Module &M) {
1318 ValueToValueMapTy VMap;
1319 return CloneModule(M, VMap, [&](const GlobalValue *GV) { return true; });
1320}
1321
1322/// Writes \p SG as a DOTGraph to \ref ModuleDotCfgDir if requested.
1323static void writeDOTGraph(const AMDGPUOptions &CLOpts, const SplitGraph &SG) {
1324 StringRef ModuleDotCfgOutput = CLOpts.module_splitting_print_module_dotcfg;
1325 if (ModuleDotCfgOutput.empty())
1326 return;
1327
1328 std::error_code EC;
1329 raw_fd_ostream OS(ModuleDotCfgOutput, EC);
1330 if (EC) {
1331 errs() << "[" DEBUG_TYPE "]: cannot open '" << ModuleDotCfgOutput
1332 << "' - DOTGraph will not be printed\n";
1333 }
1334 WriteGraph(OS, SG, /*ShortName=*/false,
1335 /*Title=*/SG.getModule().getName());
1336}
1337
1338static void splitAMDGPUModule(
1339 const AMDGPUOptions &CLOpts, GetTTIFn GetTTI, Module &M, unsigned NumParts,
1340 function_ref<void(std::unique_ptr<Module> MPart)> ModuleCallback) {
1341 CallGraph CG(M);
1342
1343 // Externalize functions whose address are taken.
1344 //
1345 // This is needed because partitioning is purely based on calls, but sometimes
1346 // a kernel/function may just look at the address of another local function
1347 // and not do anything (no calls). After partitioning, that local function may
1348 // end up in a different module (so it's just a declaration in the module
1349 // where its address is taken), which emits a "undefined hidden symbol" linker
1350 // error.
1351 //
1352 // Additionally, it guides partitioning to not duplicate this function if it's
1353 // called directly at some point.
1354 //
1355 // TODO: Could we be smarter about this ? This makes all functions whose
1356 // addresses are taken non-copyable. We should probably model this type of
1357 // constraint in the graph and use it to guide splitting, instead of
1358 // externalizing like this. Maybe non-copyable should really mean "keep one
1359 // visible copy, then internalize all other copies" for some functions?
1360 if (CLOpts.module_splitting_externalize_address_taken) {
1361 for (auto &Fn : M) {
1362 if (Fn.hasLocalLinkage() && Fn.hasAddressTaken()) {
1363 LLVM_DEBUG(dbgs() << "[externalize] "; Fn.printAsOperand(dbgs());
1364 dbgs() << " because its address is taken\n");
1365 Fn.externalize();
1366 }
1367 }
1368 }
1369
1370 // Externalize local GVs, which avoids duplicating their initializers, which
1371 // in turns helps keep code size in check.
1372 if (CLOpts.module_splitting_externalize_globals) {
1373 for (auto &GV : M.globals()) {
1374 if (GV.hasLocalLinkage())
1375 LLVM_DEBUG(dbgs() << "[externalize] GV " << GV.getName() << '\n');
1376 GV.externalize();
1377 }
1378 }
1379
1380 for (auto &GA : M.aliases()) {
1381 if (GA.hasLocalLinkage()) {
1382 LLVM_DEBUG(dbgs() << "[externalize] alias " << GA.getName() << '\n');
1383 GA.externalize();
1384 }
1385 }
1386
1387 // Start by calculating the cost of every function in the module, as well as
1388 // the module's overall cost.
1389 FunctionsCostMap FnCosts;
1390 const CostType ModuleCost = calculateFunctionCosts(GetTTI, M, FnCosts);
1391
1392 // Build the SplitGraph, which represents the module's functions and models
1393 // their dependencies accurately.
1394 SplitGraph SG(M, FnCosts, ModuleCost);
1395 SG.buildGraph(CG);
1396
1397 if (SG.empty()) {
1398 LLVM_DEBUG(
1399 dbgs()
1400 << "[!] no nodes in graph, input is empty - no splitting possible\n");
1401 ModuleCallback(cloneAll(M));
1402 return;
1403 }
1404
1405 LLVM_DEBUG({
1406 dbgs() << "[graph] nodes:\n";
1407 for (const SplitGraph::Node *N : SG.nodes()) {
1408 dbgs() << " - [" << N->getID() << "]: " << N->getName() << " "
1409 << (N->isGraphEntryPoint() ? "(entry)" : "") << " "
1410 << (N->isNonCopyable() ? "(noncopyable)" : "") << "\n";
1411 }
1412 });
1413
1414 writeDOTGraph(CLOpts, SG);
1415
1416 LLVM_DEBUG(dbgs() << "[search] testing splitting strategies\n");
1417
1418 std::optional<SplitProposal> Proposal;
1419 const auto EvaluateProposal = [&](SplitProposal SP) {
1420 SP.calculateScores();
1421 if (!Proposal)
1422 Proposal = std::move(SP);
1423 else
1424 evaluateProposal(CLOpts, *Proposal, std::move(SP));
1425 };
1426
1427 // TODO: It would be very easy to create new strategies by just adding a base
1428 // class to RecursiveSearchSplitting and abstracting it away.
1429 RecursiveSearchSplitting(CLOpts, SG, NumParts, EvaluateProposal).run();
1430 LLVM_DEBUG(if (Proposal) dbgs() << "[search done] selected proposal: "
1431 << Proposal->getName() << "\n";);
1432
1433 if (!Proposal) {
1434 LLVM_DEBUG(dbgs() << "[!] no proposal made, no splitting possible!\n");
1435 ModuleCallback(cloneAll(M));
1436 return;
1437 }
1438
1439 LLVM_DEBUG(Proposal->print(dbgs()););
1440
1441 std::optional<raw_fd_ostream> SummariesOS;
1442 StringRef PartitionSummariesOutput =
1443 CLOpts.module_splitting_print_partition_summaries;
1444 if (!PartitionSummariesOutput.empty()) {
1445 std::error_code EC;
1446 SummariesOS.emplace(PartitionSummariesOutput, EC);
1447 if (EC)
1448 errs() << "[" DEBUG_TYPE "]: cannot open '" << PartitionSummariesOutput
1449 << "' - Partition summaries will not be printed\n";
1450 }
1451
1452 // One module will import all GlobalValues that are not Functions
1453 // and are not subject to conservative import.
1454 bool ImportAllGVs = true;
1455
1456 for (unsigned PID = 0; PID < NumParts; ++PID) {
1457 SplitModuleTimer SMT2("modules_creation",
1458 "creating modules for each partition");
1459 LLVM_DEBUG(dbgs() << "[split] creating new modules\n");
1460
1461 DenseSet<const Function *> FnsInPart;
1462 for (unsigned NodeID : (*Proposal)[PID].set_bits())
1463 FnsInPart.insert(&SG.getNode(NodeID).getFunction());
1464
1465 // Don't create empty modules.
1466 if (FnsInPart.empty()) {
1467 LLVM_DEBUG(dbgs() << "[split] P" << PID
1468 << " is empty, not creating module\n");
1469 continue;
1470 }
1471
1472 ValueToValueMapTy VMap;
1473 CostType PartCost = 0;
1474 std::unique_ptr<Module> MPart(
1475 CloneModule(M, VMap, [&](const GlobalValue *GV) {
1476 // Functions go in their assigned partition.
1477 if (const auto *Fn = dyn_cast<Function>(GV)) {
1478 if (FnsInPart.contains(Fn)) {
1479 PartCost += SG.getCost(*Fn);
1480 return true;
1481 }
1482 return false;
1483 }
1484
1485 // Aliases should not be separated from their underlying object.
1486 if (const auto *GA = dyn_cast<GlobalAlias>(GV)) {
1487 if (const auto *Fn = dyn_cast<Function>(GA->getAliaseeObject()))
1488 return FnsInPart.contains(Fn);
1489 }
1490
1491 // Everything else goes in the first non-empty module we create.
1492 return ImportAllGVs || needsConservativeImport(GV);
1493 }));
1494
1495 ImportAllGVs = false;
1496
1497 // Clean-up conservatively imported GVs without any users.
1498 for (auto &GV : make_early_inc_range(MPart->global_values())) {
1499 if (needsConservativeImport(&GV) && GV.use_empty())
1500 GV.eraseFromParent();
1501 }
1502
1503 if (SummariesOS)
1504 printPartitionSummary(*SummariesOS, PID, *MPart, PartCost, ModuleCost);
1505
1506 LLVM_DEBUG(
1507 printPartitionSummary(dbgs(), PID, *MPart, PartCost, ModuleCost));
1508
1509 ModuleCallback(std::move(MPart));
1510 }
1511}
1512} // namespace
1513
1516 SplitModuleTimer SMT(
1517 "total", "total pass runtime (incl. potentially waiting for lockfile)");
1518
1520 MAM.getResult<FunctionAnalysisManagerModuleProxy>(M).getManager();
1521 const auto TTIGetter = [&FAM](Function &F) -> const TargetTransformInfo & {
1522 return FAM.getResult<TargetIRAnalysis>(F);
1523 };
1524
1525 const AMDGPUOptions &CLOpts =
1526 M.getContext().getLibraryOptions().get<AMDGPUOptions>();
1527 bool Done = false;
1528#ifndef NDEBUG
1529 if (CLOpts.module_splitting_serial_execution) {
1530 SmallString<128> LockFilePath;
1531 sys::path::system_temp_directory(/*ErasedOnReboot=*/true, LockFilePath);
1532 sys::path::append(LockFilePath, "amdgpu-split-module-debug");
1533 LLVM_DEBUG(dbgs() << DEBUG_TYPE " using lockfile '" << LockFilePath
1534 << "'\n");
1535
1536 while (true) {
1537 llvm::LockFileManager Lock(LockFilePath.str());
1538 bool Owned;
1539 if (Error Err = Lock.tryLock().moveInto(Owned)) {
1540 consumeError(std::move(Err));
1541 LLVM_DEBUG(
1542 dbgs() << "[amdgpu-split-module] unable to acquire lockfile, debug "
1543 "output may be mangled by other processes\n");
1544 } else if (!Owned) {
1545 switch (Lock.waitForUnlockFor(std::chrono::seconds(90))) {
1547 break;
1549 continue; // try again to get the lock.
1551 LLVM_DEBUG(
1552 dbgs()
1553 << "[amdgpu-split-module] unable to acquire lockfile, debug "
1554 "output may be mangled by other processes\n");
1555 Lock.unsafeUnlock();
1556 break; // give up
1557 }
1558 }
1559
1560 splitAMDGPUModule(CLOpts, TTIGetter, M, N, ModuleCallback);
1561 Done = true;
1562 break;
1563 }
1564 }
1565#endif
1566
1567 if (!Done)
1568 splitAMDGPUModule(CLOpts, TTIGetter, M, N, ModuleCallback);
1569
1570 // We can change linkage/visibilities in the input, consider that nothing is
1571 // preserved just to be safe. This pass runs last anyway.
1572 return PreservedAnalyses::none();
1573}
1574} // namespace llvm
unsigned NumParts
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.
Definition Compiler.h:690
static InstructionCost getCost(const AnalysisOptions &Opts, Instruction &Inst, TTI::TargetCostKind CostKind, TargetTransformInfo &TTI)
Definition CostModel.cpp:33
decltype(auto) dyn_cast(const From &Val)
dyn_cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:643
Generic implementation of equivalence classes through the use Tarjan's efficient union-find algorithm...
#define DEBUG_TYPE
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.
#define F(x, y, z)
Definition MD5.cpp:54
#define I(x, y, z)
Definition MD5.cpp:57
#define G(x, y, z)
Definition MD5.cpp:55
Machine Check Debug Module
#define P(N)
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.
This file contains some functions that are useful when dealing with strings.
#define LLVM_DEBUG(...)
Definition Debug.h:119
static const BasicSubtargetSubTypeKV * find(StringRef S, ArrayRef< BasicSubtargetSubTypeKV > A)
Find KV in array using binary search.
This pass exposes codegen information to IR-level passes.
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.
Definition Error.h:159
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.
Definition Module.h:68
A set of analyses that are preserved following a run of a transformation pass.
Definition Analysis.h:112
static PreservedAnalyses none()
Convenience factory function for the empty preserved set.
Definition Analysis.h:115
SmallString - A SmallString is just a SmallVector with methods and accessors that make it work better...
Definition SmallString.h:28
StringRef str() const
Explicit conversion to StringRef.
Analysis pass providing the TargetTransformInfo.
This pass provides access to the codegen interfaces that are needed for IR-level transformations.
@ TCK_CodeSize
Instruction code size.
@ TCC_Expensive
The cost of a 'div' instruction on x86.
LLVM_ABI InstructionCost getInstructionCost(const User *U, ArrayRef< const Value * > Operands, TargetCostKind CostKind) const
Estimate the cost of a given IR user when lowered.
#define llvm_unreachable(msg)
Marks that the current location is not supposed to be reachable.
LLVM_READNONE constexpr bool isEntryFunctionCC(CallingConv::ID CC)
constexpr MCPhysReg SP
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.
Definition Path.cpp:467
This is an optimization pass for GlobalISel generic memory operations.
@ Done
Definition Threading.h:60
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...
Definition STLExtras.h:649
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="")
Op::Description Desc
bool any_of(R &&range, UnaryPredicate P)
Provide wrappers to std::any_of which take ranges instead of having to pass begin/end explicitly.
Definition STLExtras.h:1762
LLVM_ABI raw_ostream & dbgs()
dbgs() - This returns a reference to a raw_ostream for debugging messages.
Definition Debug.cpp:209
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.
Definition Format.h:102
@ 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.
Definition ModRef.h:32
TargetTransformInfo TTI
ValueMap< const Value *, WeakTrackingVH > ValueToValueMapTy
decltype(auto) cast(const From &Val)
cast<X> - Return the argument parameter cast to the specified type.
Definition Casting.h:559
AnalysisManager< Function > FunctionAnalysisManager
Convenience typedef for the Function analysis manager.
BumpPtrAllocatorImpl<> BumpPtrAllocator
The standard BumpPtrAllocator which just uses the default template parameters.
Definition Allocator.h:391
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.
Definition Error.h:1106
AnalysisManager< Module > ModuleAnalysisManager
Convenience typedef for the Module analysis manager.
Definition MIRParser.h:39
#define N
static std::string getEdgeAttributes(const SplitGraph::Node *N, SplitGraphEdgeDstIterator EI, const SplitGraph &SG)
static std::string getGraphName(const SplitGraph &SG)
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)
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)
static ChildIteratorType child_end(NodeRef Ref)