xref: /src/contrib/llvm-project/lld/ELF/CallGraphSort.cpp (revision 5f757f3ff9144b609b3c433dfd370cc6bdc191ad)
120d35e67SDimitry Andric //===- CallGraphSort.cpp --------------------------------------------------===//
220d35e67SDimitry Andric //
3f1e1c239SDimitry Andric // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4f1e1c239SDimitry Andric // See https://llvm.org/LICENSE.txt for license information.
5f1e1c239SDimitry Andric // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
620d35e67SDimitry Andric //
720d35e67SDimitry Andric //===----------------------------------------------------------------------===//
820d35e67SDimitry Andric ///
9b1c73532SDimitry Andric /// The file is responsible for sorting sections using LLVM call graph profile
10b1c73532SDimitry Andric /// data by placing frequently executed code sections together. The goal of the
11b1c73532SDimitry Andric /// placement is to improve the runtime performance of the final executable by
12b1c73532SDimitry Andric /// arranging code sections so that i-TLB misses and i-cache misses are reduced.
13b1c73532SDimitry Andric ///
14b1c73532SDimitry Andric /// The algorithm first builds a call graph based on the profile data and then
15b1c73532SDimitry Andric /// iteratively merges "chains" (ordered lists) of input sections which will be
16b1c73532SDimitry Andric /// laid out as a unit. There are two implementations for deciding how to
17b1c73532SDimitry Andric /// merge a pair of chains:
18b1c73532SDimitry Andric ///  - a simpler one, referred to as Call-Chain Clustering (C^3), that follows
19b1c73532SDimitry Andric ///    "Optimizing Function Placement for Large-Scale Data-Center Applications"
2020d35e67SDimitry Andric /// https://research.fb.com/wp-content/uploads/2017/01/cgo2017-hfsort-final1.pdf
21b1c73532SDimitry Andric /// - a more advanced one, referred to as Cache-Directed-Sort (CDSort), which
22b1c73532SDimitry Andric ///   typically produces layouts with higher locality, and hence, yields fewer
23b1c73532SDimitry Andric ///   instruction cache misses on large binaries.
2420d35e67SDimitry Andric //===----------------------------------------------------------------------===//
2520d35e67SDimitry Andric 
2620d35e67SDimitry Andric #include "CallGraphSort.h"
27145449b1SDimitry Andric #include "InputFiles.h"
28145449b1SDimitry Andric #include "InputSection.h"
2920d35e67SDimitry Andric #include "Symbols.h"
30145449b1SDimitry Andric #include "llvm/Support/FileSystem.h"
31b1c73532SDimitry Andric #include "llvm/Transforms/Utils/CodeLayout.h"
3220d35e67SDimitry Andric 
33d2bd9e70SDimitry Andric #include <numeric>
34d2bd9e70SDimitry Andric 
3520d35e67SDimitry Andric using namespace llvm;
36cfca06d7SDimitry Andric using namespace lld;
37cfca06d7SDimitry Andric using namespace lld::elf;
3820d35e67SDimitry Andric 
3920d35e67SDimitry Andric namespace {
4020d35e67SDimitry Andric struct Edge {
41f1e1c239SDimitry Andric   int from;
42f1e1c239SDimitry Andric   uint64_t weight;
4320d35e67SDimitry Andric };
4420d35e67SDimitry Andric 
4520d35e67SDimitry Andric struct Cluster {
Cluster__anon8806e05c0111::Cluster46d2bd9e70SDimitry Andric   Cluster(int sec, size_t s) : next(sec), prev(sec), size(s) {}
4720d35e67SDimitry Andric 
getDensity__anon8806e05c0111::Cluster4820d35e67SDimitry Andric   double getDensity() const {
49f1e1c239SDimitry Andric     if (size == 0)
5020d35e67SDimitry Andric       return 0;
51f1e1c239SDimitry Andric     return double(weight) / double(size);
5220d35e67SDimitry Andric   }
5320d35e67SDimitry Andric 
54d2bd9e70SDimitry Andric   int next;
55d2bd9e70SDimitry Andric   int prev;
56b60736ecSDimitry Andric   uint64_t size;
57f1e1c239SDimitry Andric   uint64_t weight = 0;
58f1e1c239SDimitry Andric   uint64_t initialWeight = 0;
59f1e1c239SDimitry Andric   Edge bestPred = {-1, 0};
6020d35e67SDimitry Andric };
6120d35e67SDimitry Andric 
62b1c73532SDimitry Andric /// Implementation of the Call-Chain Clustering (C^3). The goal of this
63b1c73532SDimitry Andric /// algorithm is to improve runtime performance of the executable by arranging
64b1c73532SDimitry Andric /// code sections such that page table and i-cache misses are minimized.
65b1c73532SDimitry Andric ///
66b1c73532SDimitry Andric /// Definitions:
67b1c73532SDimitry Andric /// * Cluster
68b1c73532SDimitry Andric ///   * An ordered list of input sections which are laid out as a unit. At the
69b1c73532SDimitry Andric ///     beginning of the algorithm each input section has its own cluster and
70b1c73532SDimitry Andric ///     the weight of the cluster is the sum of the weight of all incoming
71b1c73532SDimitry Andric ///     edges.
72b1c73532SDimitry Andric /// * Call-Chain Clustering (C³) Heuristic
73b1c73532SDimitry Andric ///   * Defines when and how clusters are combined. Pick the highest weighted
74b1c73532SDimitry Andric ///     input section then add it to its most likely predecessor if it wouldn't
75b1c73532SDimitry Andric ///     penalize it too much.
76b1c73532SDimitry Andric /// * Density
77b1c73532SDimitry Andric ///   * The weight of the cluster divided by the size of the cluster. This is a
78b1c73532SDimitry Andric ///     proxy for the amount of execution time spent per byte of the cluster.
79b1c73532SDimitry Andric ///
80b1c73532SDimitry Andric /// It does so given a call graph profile by the following:
81b1c73532SDimitry Andric /// * Build a weighted call graph from the call graph profile
82b1c73532SDimitry Andric /// * Sort input sections by weight
83b1c73532SDimitry Andric /// * For each input section starting with the highest weight
84b1c73532SDimitry Andric ///   * Find its most likely predecessor cluster
85b1c73532SDimitry Andric ///   * Check if the combined cluster would be too large, or would have too low
86b1c73532SDimitry Andric ///     a density.
87b1c73532SDimitry Andric ///   * If not, then combine the clusters.
88b1c73532SDimitry Andric /// * Sort non-empty clusters by density
8920d35e67SDimitry Andric class CallGraphSort {
9020d35e67SDimitry Andric public:
9120d35e67SDimitry Andric   CallGraphSort();
9220d35e67SDimitry Andric 
9320d35e67SDimitry Andric   DenseMap<const InputSectionBase *, int> run();
9420d35e67SDimitry Andric 
9520d35e67SDimitry Andric private:
96f1e1c239SDimitry Andric   std::vector<Cluster> clusters;
97f1e1c239SDimitry Andric   std::vector<const InputSectionBase *> sections;
9820d35e67SDimitry Andric };
9920d35e67SDimitry Andric 
100706b4fc4SDimitry Andric // Maximum amount the combined cluster density can be worse than the original
10120d35e67SDimitry Andric // cluster to consider merging.
10220d35e67SDimitry Andric constexpr int MAX_DENSITY_DEGRADATION = 8;
10320d35e67SDimitry Andric 
10420d35e67SDimitry Andric // Maximum cluster size in bytes.
10520d35e67SDimitry Andric constexpr uint64_t MAX_CLUSTER_SIZE = 1024 * 1024;
10620d35e67SDimitry Andric } // end anonymous namespace
10720d35e67SDimitry Andric 
108f1e1c239SDimitry Andric using SectionPair =
109f1e1c239SDimitry Andric     std::pair<const InputSectionBase *, const InputSectionBase *>;
110e2fd426bSDimitry Andric 
11120d35e67SDimitry Andric // Take the edge list in Config->CallGraphProfile, resolve symbol names to
11220d35e67SDimitry Andric // Symbols, and generate a graph between InputSections with the provided
11320d35e67SDimitry Andric // weights.
CallGraphSort()11420d35e67SDimitry Andric CallGraphSort::CallGraphSort() {
115f1e1c239SDimitry Andric   MapVector<SectionPair, uint64_t> &profile = config->callGraphProfile;
116f1e1c239SDimitry Andric   DenseMap<const InputSectionBase *, int> secToCluster;
11720d35e67SDimitry Andric 
118f1e1c239SDimitry Andric   auto getOrCreateNode = [&](const InputSectionBase *isec) -> int {
119d2bd9e70SDimitry Andric     auto res = secToCluster.try_emplace(isec, clusters.size());
120f1e1c239SDimitry Andric     if (res.second) {
121f1e1c239SDimitry Andric       sections.push_back(isec);
122f1e1c239SDimitry Andric       clusters.emplace_back(clusters.size(), isec->getSize());
12320d35e67SDimitry Andric     }
124f1e1c239SDimitry Andric     return res.first->second;
12520d35e67SDimitry Andric   };
12620d35e67SDimitry Andric 
12720d35e67SDimitry Andric   // Create the graph.
128f1e1c239SDimitry Andric   for (std::pair<SectionPair, uint64_t> &c : profile) {
12977fc4c14SDimitry Andric     const auto *fromSB = cast<InputSectionBase>(c.first.first);
13077fc4c14SDimitry Andric     const auto *toSB = cast<InputSectionBase>(c.first.second);
131f1e1c239SDimitry Andric     uint64_t weight = c.second;
13220d35e67SDimitry Andric 
13320d35e67SDimitry Andric     // Ignore edges between input sections belonging to different output
13420d35e67SDimitry Andric     // sections.  This is done because otherwise we would end up with clusters
13520d35e67SDimitry Andric     // containing input sections that can't actually be placed adjacently in the
13620d35e67SDimitry Andric     // output.  This messes with the cluster size and density calculations.  We
13720d35e67SDimitry Andric     // would also end up moving input sections in other output sections without
13820d35e67SDimitry Andric     // moving them closer to what calls them.
139f1e1c239SDimitry Andric     if (fromSB->getOutputSection() != toSB->getOutputSection())
14020d35e67SDimitry Andric       continue;
14120d35e67SDimitry Andric 
142f1e1c239SDimitry Andric     int from = getOrCreateNode(fromSB);
143f1e1c239SDimitry Andric     int to = getOrCreateNode(toSB);
14420d35e67SDimitry Andric 
145f1e1c239SDimitry Andric     clusters[to].weight += weight;
14620d35e67SDimitry Andric 
147f1e1c239SDimitry Andric     if (from == to)
14820d35e67SDimitry Andric       continue;
14920d35e67SDimitry Andric 
150e2fd426bSDimitry Andric     // Remember the best edge.
151f1e1c239SDimitry Andric     Cluster &toC = clusters[to];
152f1e1c239SDimitry Andric     if (toC.bestPred.from == -1 || toC.bestPred.weight < weight) {
153f1e1c239SDimitry Andric       toC.bestPred.from = from;
154f1e1c239SDimitry Andric       toC.bestPred.weight = weight;
155e2fd426bSDimitry Andric     }
15620d35e67SDimitry Andric   }
157f1e1c239SDimitry Andric   for (Cluster &c : clusters)
158f1e1c239SDimitry Andric     c.initialWeight = c.weight;
15920d35e67SDimitry Andric }
16020d35e67SDimitry Andric 
16120d35e67SDimitry Andric // It's bad to merge clusters which would degrade the density too much.
isNewDensityBad(Cluster & a,Cluster & b)162f1e1c239SDimitry Andric static bool isNewDensityBad(Cluster &a, Cluster &b) {
163f1e1c239SDimitry Andric   double newDensity = double(a.weight + b.weight) / double(a.size + b.size);
164f1e1c239SDimitry Andric   return newDensity < a.getDensity() / MAX_DENSITY_DEGRADATION;
16520d35e67SDimitry Andric }
16620d35e67SDimitry Andric 
167d2bd9e70SDimitry Andric // Find the leader of V's belonged cluster (represented as an equivalence
168d2bd9e70SDimitry Andric // class). We apply union-find path-halving technique (simple to implement) in
169d2bd9e70SDimitry Andric // the meantime as it decreases depths and the time complexity.
getLeader(int * leaders,int v)170e3b55780SDimitry Andric static int getLeader(int *leaders, int v) {
171d2bd9e70SDimitry Andric   while (leaders[v] != v) {
172d2bd9e70SDimitry Andric     leaders[v] = leaders[leaders[v]];
173d2bd9e70SDimitry Andric     v = leaders[v];
174d2bd9e70SDimitry Andric   }
175d2bd9e70SDimitry Andric   return v;
176d2bd9e70SDimitry Andric }
177d2bd9e70SDimitry Andric 
mergeClusters(std::vector<Cluster> & cs,Cluster & into,int intoIdx,Cluster & from,int fromIdx)178d2bd9e70SDimitry Andric static void mergeClusters(std::vector<Cluster> &cs, Cluster &into, int intoIdx,
179d2bd9e70SDimitry Andric                           Cluster &from, int fromIdx) {
180d2bd9e70SDimitry Andric   int tail1 = into.prev, tail2 = from.prev;
181d2bd9e70SDimitry Andric   into.prev = tail2;
182d2bd9e70SDimitry Andric   cs[tail2].next = intoIdx;
183d2bd9e70SDimitry Andric   from.prev = tail1;
184d2bd9e70SDimitry Andric   cs[tail1].next = fromIdx;
185f1e1c239SDimitry Andric   into.size += from.size;
186f1e1c239SDimitry Andric   into.weight += from.weight;
187f1e1c239SDimitry Andric   from.size = 0;
188f1e1c239SDimitry Andric   from.weight = 0;
18920d35e67SDimitry Andric }
19020d35e67SDimitry Andric 
19120d35e67SDimitry Andric // Group InputSections into clusters using the Call-Chain Clustering heuristic
19220d35e67SDimitry Andric // then sort the clusters by density.
run()193d2bd9e70SDimitry Andric DenseMap<const InputSectionBase *, int> CallGraphSort::run() {
194d2bd9e70SDimitry Andric   std::vector<int> sorted(clusters.size());
195e3b55780SDimitry Andric   std::unique_ptr<int[]> leaders(new int[clusters.size()]);
19620d35e67SDimitry Andric 
197e3b55780SDimitry Andric   std::iota(leaders.get(), leaders.get() + clusters.size(), 0);
198d2bd9e70SDimitry Andric   std::iota(sorted.begin(), sorted.end(), 0);
199d2bd9e70SDimitry Andric   llvm::stable_sort(sorted, [&](int a, int b) {
200f1e1c239SDimitry Andric     return clusters[a].getDensity() > clusters[b].getDensity();
20120d35e67SDimitry Andric   });
20220d35e67SDimitry Andric 
203d2bd9e70SDimitry Andric   for (int l : sorted) {
204d2bd9e70SDimitry Andric     // The cluster index is the same as the index of its leader here because
205d2bd9e70SDimitry Andric     // clusters[L] has not been merged into another cluster yet.
206d2bd9e70SDimitry Andric     Cluster &c = clusters[l];
20720d35e67SDimitry Andric 
208e2fd426bSDimitry Andric     // Don't consider merging if the edge is unlikely.
209f1e1c239SDimitry Andric     if (c.bestPred.from == -1 || c.bestPred.weight * 10 <= c.initialWeight)
21020d35e67SDimitry Andric       continue;
21120d35e67SDimitry Andric 
212e3b55780SDimitry Andric     int predL = getLeader(leaders.get(), c.bestPred.from);
213d2bd9e70SDimitry Andric     if (l == predL)
21420d35e67SDimitry Andric       continue;
21520d35e67SDimitry Andric 
216d2bd9e70SDimitry Andric     Cluster *predC = &clusters[predL];
217f1e1c239SDimitry Andric     if (c.size + predC->size > MAX_CLUSTER_SIZE)
21820d35e67SDimitry Andric       continue;
21920d35e67SDimitry Andric 
220f1e1c239SDimitry Andric     if (isNewDensityBad(*predC, c))
22120d35e67SDimitry Andric       continue;
22220d35e67SDimitry Andric 
223d2bd9e70SDimitry Andric     leaders[l] = predL;
224d2bd9e70SDimitry Andric     mergeClusters(clusters, *predC, predL, c, l);
22520d35e67SDimitry Andric   }
22620d35e67SDimitry Andric 
227d2bd9e70SDimitry Andric   // Sort remaining non-empty clusters by density.
228d2bd9e70SDimitry Andric   sorted.clear();
229d2bd9e70SDimitry Andric   for (int i = 0, e = (int)clusters.size(); i != e; ++i)
230d2bd9e70SDimitry Andric     if (clusters[i].size > 0)
231d2bd9e70SDimitry Andric       sorted.push_back(i);
232d2bd9e70SDimitry Andric   llvm::stable_sort(sorted, [&](int a, int b) {
233d2bd9e70SDimitry Andric     return clusters[a].getDensity() > clusters[b].getDensity();
23420d35e67SDimitry Andric   });
23520d35e67SDimitry Andric 
236f1e1c239SDimitry Andric   DenseMap<const InputSectionBase *, int> orderMap;
237d2bd9e70SDimitry Andric   int curOrder = 1;
238b60736ecSDimitry Andric   for (int leader : sorted) {
239d2bd9e70SDimitry Andric     for (int i = leader;;) {
240d2bd9e70SDimitry Andric       orderMap[sections[i]] = curOrder++;
241d2bd9e70SDimitry Andric       i = clusters[i].next;
242d2bd9e70SDimitry Andric       if (i == leader)
243d2bd9e70SDimitry Andric         break;
244d2bd9e70SDimitry Andric     }
245b60736ecSDimitry Andric   }
246f1e1c239SDimitry Andric   if (!config->printSymbolOrder.empty()) {
247f1e1c239SDimitry Andric     std::error_code ec;
248d2bd9e70SDimitry Andric     raw_fd_ostream os(config->printSymbolOrder, ec, sys::fs::OF_None);
249f1e1c239SDimitry Andric     if (ec) {
250f1e1c239SDimitry Andric       error("cannot open " + config->printSymbolOrder + ": " + ec.message());
251f1e1c239SDimitry Andric       return orderMap;
252f1e1c239SDimitry Andric     }
253f1e1c239SDimitry Andric 
254f1e1c239SDimitry Andric     // Print the symbols ordered by C3, in the order of increasing curOrder
255f1e1c239SDimitry Andric     // Instead of sorting all the orderMap, just repeat the loops above.
256d2bd9e70SDimitry Andric     for (int leader : sorted)
257d2bd9e70SDimitry Andric       for (int i = leader;;) {
258f1e1c239SDimitry Andric         // Search all the symbols in the file of the section
259f1e1c239SDimitry Andric         // and find out a Defined symbol with name that is within the section.
260d2bd9e70SDimitry Andric         for (Symbol *sym : sections[i]->file->getSymbols())
261f1e1c239SDimitry Andric           if (!sym->isSection()) // Filter out section-type symbols here.
262f1e1c239SDimitry Andric             if (auto *d = dyn_cast<Defined>(sym))
263d2bd9e70SDimitry Andric               if (sections[i] == d->section)
264f1e1c239SDimitry Andric                 os << sym->getName() << "\n";
265d2bd9e70SDimitry Andric         i = clusters[i].next;
266d2bd9e70SDimitry Andric         if (i == leader)
267d2bd9e70SDimitry Andric           break;
268d2bd9e70SDimitry Andric       }
269f1e1c239SDimitry Andric   }
270f1e1c239SDimitry Andric 
271f1e1c239SDimitry Andric   return orderMap;
27220d35e67SDimitry Andric }
27320d35e67SDimitry Andric 
274b1c73532SDimitry Andric // Sort sections by the profile data using the Cache-Directed Sort algorithm.
275b1c73532SDimitry Andric // The placement is done by optimizing the locality by co-locating frequently
276b1c73532SDimitry Andric // executed code sections together.
computeCacheDirectedSortOrder()277b1c73532SDimitry Andric DenseMap<const InputSectionBase *, int> elf::computeCacheDirectedSortOrder() {
278b1c73532SDimitry Andric   SmallVector<uint64_t, 0> funcSizes;
279b1c73532SDimitry Andric   SmallVector<uint64_t, 0> funcCounts;
280b1c73532SDimitry Andric   SmallVector<codelayout::EdgeCount, 0> callCounts;
281b1c73532SDimitry Andric   SmallVector<uint64_t, 0> callOffsets;
282b1c73532SDimitry Andric   SmallVector<const InputSectionBase *, 0> sections;
283b1c73532SDimitry Andric   DenseMap<const InputSectionBase *, size_t> secToTargetId;
284b1c73532SDimitry Andric 
285b1c73532SDimitry Andric   auto getOrCreateNode = [&](const InputSectionBase *inSec) -> size_t {
286b1c73532SDimitry Andric     auto res = secToTargetId.try_emplace(inSec, sections.size());
287b1c73532SDimitry Andric     if (res.second) {
288b1c73532SDimitry Andric       // inSec does not appear before in the graph.
289b1c73532SDimitry Andric       sections.push_back(inSec);
290b1c73532SDimitry Andric       funcSizes.push_back(inSec->getSize());
291b1c73532SDimitry Andric       funcCounts.push_back(0);
292b1c73532SDimitry Andric     }
293b1c73532SDimitry Andric     return res.first->second;
294b1c73532SDimitry Andric   };
295b1c73532SDimitry Andric 
296b1c73532SDimitry Andric   // Create the graph.
297b1c73532SDimitry Andric   for (std::pair<SectionPair, uint64_t> &c : config->callGraphProfile) {
298b1c73532SDimitry Andric     const InputSectionBase *fromSB = cast<InputSectionBase>(c.first.first);
299b1c73532SDimitry Andric     const InputSectionBase *toSB = cast<InputSectionBase>(c.first.second);
300b1c73532SDimitry Andric     // Ignore edges between input sections belonging to different sections.
301b1c73532SDimitry Andric     if (fromSB->getOutputSection() != toSB->getOutputSection())
302b1c73532SDimitry Andric       continue;
303b1c73532SDimitry Andric 
304b1c73532SDimitry Andric     uint64_t weight = c.second;
305b1c73532SDimitry Andric     // Ignore edges with zero weight.
306b1c73532SDimitry Andric     if (weight == 0)
307b1c73532SDimitry Andric       continue;
308b1c73532SDimitry Andric 
309b1c73532SDimitry Andric     size_t from = getOrCreateNode(fromSB);
310b1c73532SDimitry Andric     size_t to = getOrCreateNode(toSB);
311b1c73532SDimitry Andric     // Ignore self-edges (recursive calls).
312b1c73532SDimitry Andric     if (from == to)
313b1c73532SDimitry Andric       continue;
314b1c73532SDimitry Andric 
315b1c73532SDimitry Andric     callCounts.push_back({from, to, weight});
316b1c73532SDimitry Andric     // Assume that the jump is at the middle of the input section. The profile
317b1c73532SDimitry Andric     // data does not contain jump offsets.
318b1c73532SDimitry Andric     callOffsets.push_back((funcSizes[from] + 1) / 2);
319b1c73532SDimitry Andric     funcCounts[to] += weight;
320b1c73532SDimitry Andric   }
321b1c73532SDimitry Andric 
322b1c73532SDimitry Andric   // Run the layout algorithm.
323b1c73532SDimitry Andric   std::vector<uint64_t> sortedSections = codelayout::computeCacheDirectedLayout(
324b1c73532SDimitry Andric       funcSizes, funcCounts, callCounts, callOffsets);
325b1c73532SDimitry Andric 
326b1c73532SDimitry Andric   // Create the final order.
327b1c73532SDimitry Andric   DenseMap<const InputSectionBase *, int> orderMap;
328b1c73532SDimitry Andric   int curOrder = 1;
329b1c73532SDimitry Andric   for (uint64_t secIdx : sortedSections)
330b1c73532SDimitry Andric     orderMap[sections[secIdx]] = curOrder++;
331b1c73532SDimitry Andric 
332b1c73532SDimitry Andric   return orderMap;
333b1c73532SDimitry Andric }
334b1c73532SDimitry Andric 
335c0981da4SDimitry Andric // Sort sections by the profile data provided by --callgraph-profile-file.
33620d35e67SDimitry Andric //
33720d35e67SDimitry Andric // This first builds a call graph based on the profile data then merges sections
338b1c73532SDimitry Andric // according either to the C³ or Cache-Directed-Sort ordering algorithm.
computeCallGraphProfileOrder()339cfca06d7SDimitry Andric DenseMap<const InputSectionBase *, int> elf::computeCallGraphProfileOrder() {
340b1c73532SDimitry Andric   if (config->callGraphProfileSort == CGProfileSortKind::Cdsort)
341b1c73532SDimitry Andric     return computeCacheDirectedSortOrder();
34220d35e67SDimitry Andric   return CallGraphSort().run();
34320d35e67SDimitry Andric }
344