| //===- SampleProfileInference.cpp - Adjust sample profiles in the IR ------===// | |
| // | |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. | |
| // See https://llvm.org/LICENSE.txt for license information. | |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception | |
| // | |
| //===----------------------------------------------------------------------===// | |
| // | |
| // This file implements a profile inference algorithm. Given an incomplete and | |
| // possibly imprecise block counts, the algorithm reconstructs realistic block | |
| // and edge counts that satisfy flow conservation rules, while minimally modify | |
| // input block counts. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/Transforms/Utils/SampleProfileInference.h" | |
| #include "llvm/ADT/BitVector.h" | |
| #include "llvm/Support/CommandLine.h" | |
| #include "llvm/Support/Debug.h" | |
| #include <queue> | |
| #include <set> | |
| #include <stack> | |
| #include <unordered_set> | |
| using namespace llvm; | |
| #define DEBUG_TYPE "sample-profile-inference" | |
| namespace { | |
| static cl::opt<bool> SampleProfileEvenFlowDistribution( | |
| "sample-profile-even-flow-distribution", cl::init(true), cl::Hidden, | |
| cl::desc("Try to evenly distribute flow when there are multiple equally " | |
| "likely options.")); | |
| static cl::opt<bool> SampleProfileRebalanceUnknown( | |
| "sample-profile-rebalance-unknown", cl::init(true), cl::Hidden, | |
| cl::desc("Evenly re-distribute flow among unknown subgraphs.")); | |
| static cl::opt<bool> SampleProfileJoinIslands( | |
| "sample-profile-join-islands", cl::init(true), cl::Hidden, | |
| cl::desc("Join isolated components having positive flow.")); | |
| static cl::opt<unsigned> SampleProfileProfiCostBlockInc( | |
| "sample-profile-profi-cost-block-inc", cl::init(10), cl::Hidden, | |
| cl::desc("The cost of increasing a block's count by one.")); | |
| static cl::opt<unsigned> SampleProfileProfiCostBlockDec( | |
| "sample-profile-profi-cost-block-dec", cl::init(20), cl::Hidden, | |
| cl::desc("The cost of decreasing a block's count by one.")); | |
| static cl::opt<unsigned> SampleProfileProfiCostBlockEntryInc( | |
| "sample-profile-profi-cost-block-entry-inc", cl::init(40), cl::Hidden, | |
| cl::desc("The cost of increasing the entry block's count by one.")); | |
| static cl::opt<unsigned> SampleProfileProfiCostBlockEntryDec( | |
| "sample-profile-profi-cost-block-entry-dec", cl::init(10), cl::Hidden, | |
| cl::desc("The cost of decreasing the entry block's count by one.")); | |
| static cl::opt<unsigned> SampleProfileProfiCostBlockZeroInc( | |
| "sample-profile-profi-cost-block-zero-inc", cl::init(11), cl::Hidden, | |
| cl::desc("The cost of increasing a count of zero-weight block by one.")); | |
| static cl::opt<unsigned> SampleProfileProfiCostBlockUnknownInc( | |
| "sample-profile-profi-cost-block-unknown-inc", cl::init(0), cl::Hidden, | |
| cl::desc("The cost of increasing an unknown block's count by one.")); | |
| /// A value indicating an infinite flow/capacity/weight of a block/edge. | |
| /// Not using numeric_limits<int64_t>::max(), as the values can be summed up | |
| /// during the execution. | |
| static constexpr int64_t INF = ((int64_t)1) << 50; | |
| /// The minimum-cost maximum flow algorithm. | |
| /// | |
| /// The algorithm finds the maximum flow of minimum cost on a given (directed) | |
| /// network using a modified version of the classical Moore-Bellman-Ford | |
| /// approach. The algorithm applies a number of augmentation iterations in which | |
| /// flow is sent along paths of positive capacity from the source to the sink. | |
| /// The worst-case time complexity of the implementation is O(v(f)*m*n), where | |
| /// where m is the number of edges, n is the number of vertices, and v(f) is the | |
| /// value of the maximum flow. However, the observed running time on typical | |
| /// instances is sub-quadratic, that is, o(n^2). | |
| /// | |
| /// The input is a set of edges with specified costs and capacities, and a pair | |
| /// of nodes (source and sink). The output is the flow along each edge of the | |
| /// minimum total cost respecting the given edge capacities. | |
| class MinCostMaxFlow { | |
| public: | |
| MinCostMaxFlow(const ProfiParams &Params) : Params(Params) {} | |
| // Initialize algorithm's data structures for a network of a given size. | |
| void initialize(uint64_t NodeCount, uint64_t SourceNode, uint64_t SinkNode) { | |
| Source = SourceNode; | |
| Target = SinkNode; | |
| Nodes = std::vector<Node>(NodeCount); | |
| Edges = std::vector<std::vector<Edge>>(NodeCount, std::vector<Edge>()); | |
| if (Params.EvenFlowDistribution) | |
| AugmentingEdges = | |
| std::vector<std::vector<Edge *>>(NodeCount, std::vector<Edge *>()); | |
| } | |
| // Run the algorithm. | |
| int64_t run() { | |
| LLVM_DEBUG(dbgs() << "Starting profi for " << Nodes.size() << " nodes\n"); | |
| // Iteratively find an augmentation path/dag in the network and send the | |
| // flow along its edges | |
| size_t AugmentationIters = applyFlowAugmentation(); | |
| // Compute the total flow and its cost | |
| int64_t TotalCost = 0; | |
| int64_t TotalFlow = 0; | |
| for (uint64_t Src = 0; Src < Nodes.size(); Src++) { | |
| for (auto &Edge : Edges[Src]) { | |
| if (Edge.Flow > 0) { | |
| TotalCost += Edge.Cost * Edge.Flow; | |
| if (Src == Source) | |
| TotalFlow += Edge.Flow; | |
| } | |
| } | |
| } | |
| LLVM_DEBUG(dbgs() << "Completed profi after " << AugmentationIters | |
| << " iterations with " << TotalFlow << " total flow" | |
| << " of " << TotalCost << " cost\n"); | |
| (void)TotalFlow; | |
| (void)AugmentationIters; | |
| return TotalCost; | |
| } | |
| /// Adding an edge to the network with a specified capacity and a cost. | |
| /// Multiple edges between a pair of nodes are allowed but self-edges | |
| /// are not supported. | |
| void addEdge(uint64_t Src, uint64_t Dst, int64_t Capacity, int64_t Cost) { | |
| assert(Capacity > 0 && "adding an edge of zero capacity"); | |
| assert(Src != Dst && "loop edge are not supported"); | |
| Edge SrcEdge; | |
| SrcEdge.Dst = Dst; | |
| SrcEdge.Cost = Cost; | |
| SrcEdge.Capacity = Capacity; | |
| SrcEdge.Flow = 0; | |
| SrcEdge.RevEdgeIndex = Edges[Dst].size(); | |
| Edge DstEdge; | |
| DstEdge.Dst = Src; | |
| DstEdge.Cost = -Cost; | |
| DstEdge.Capacity = 0; | |
| DstEdge.Flow = 0; | |
| DstEdge.RevEdgeIndex = Edges[Src].size(); | |
| Edges[Src].push_back(SrcEdge); | |
| Edges[Dst].push_back(DstEdge); | |
| } | |
| /// Adding an edge to the network of infinite capacity and a given cost. | |
| void addEdge(uint64_t Src, uint64_t Dst, int64_t Cost) { | |
| addEdge(Src, Dst, INF, Cost); | |
| } | |
| /// Get the total flow from a given source node. | |
| /// Returns a list of pairs (target node, amount of flow to the target). | |
| std::vector<std::pair<uint64_t, int64_t>> getFlow(uint64_t Src) const { | |
| std::vector<std::pair<uint64_t, int64_t>> Flow; | |
| for (const auto &Edge : Edges[Src]) { | |
| if (Edge.Flow > 0) | |
| Flow.push_back(std::make_pair(Edge.Dst, Edge.Flow)); | |
| } | |
| return Flow; | |
| } | |
| /// Get the total flow between a pair of nodes. | |
| int64_t getFlow(uint64_t Src, uint64_t Dst) const { | |
| int64_t Flow = 0; | |
| for (const auto &Edge : Edges[Src]) { | |
| if (Edge.Dst == Dst) { | |
| Flow += Edge.Flow; | |
| } | |
| } | |
| return Flow; | |
| } | |
| private: | |
| /// Iteratively find an augmentation path/dag in the network and send the | |
| /// flow along its edges. The method returns the number of applied iterations. | |
| size_t applyFlowAugmentation() { | |
| size_t AugmentationIters = 0; | |
| while (findAugmentingPath()) { | |
| uint64_t PathCapacity = computeAugmentingPathCapacity(); | |
| while (PathCapacity > 0) { | |
| bool Progress = false; | |
| if (Params.EvenFlowDistribution) { | |
| // Identify node/edge candidates for augmentation | |
| identifyShortestEdges(PathCapacity); | |
| // Find an augmenting DAG | |
| auto AugmentingOrder = findAugmentingDAG(); | |
| // Apply the DAG augmentation | |
| Progress = augmentFlowAlongDAG(AugmentingOrder); | |
| PathCapacity = computeAugmentingPathCapacity(); | |
| } | |
| if (!Progress) { | |
| augmentFlowAlongPath(PathCapacity); | |
| PathCapacity = 0; | |
| } | |
| AugmentationIters++; | |
| } | |
| } | |
| return AugmentationIters; | |
| } | |
| /// Compute the capacity of the cannonical augmenting path. If the path is | |
| /// saturated (that is, no flow can be sent along the path), then return 0. | |
| uint64_t computeAugmentingPathCapacity() { | |
| uint64_t PathCapacity = INF; | |
| uint64_t Now = Target; | |
| while (Now != Source) { | |
| uint64_t Pred = Nodes[Now].ParentNode; | |
| auto &Edge = Edges[Pred][Nodes[Now].ParentEdgeIndex]; | |
| assert(Edge.Capacity >= Edge.Flow && "incorrect edge flow"); | |
| uint64_t EdgeCapacity = uint64_t(Edge.Capacity - Edge.Flow); | |
| PathCapacity = std::min(PathCapacity, EdgeCapacity); | |
| Now = Pred; | |
| } | |
| return PathCapacity; | |
| } | |
| /// Check for existence of an augmenting path with a positive capacity. | |
| bool findAugmentingPath() { | |
| // Initialize data structures | |
| for (auto &Node : Nodes) { | |
| Node.Distance = INF; | |
| Node.ParentNode = uint64_t(-1); | |
| Node.ParentEdgeIndex = uint64_t(-1); | |
| Node.Taken = false; | |
| } | |
| std::queue<uint64_t> Queue; | |
| Queue.push(Source); | |
| Nodes[Source].Distance = 0; | |
| Nodes[Source].Taken = true; | |
| while (!Queue.empty()) { | |
| uint64_t Src = Queue.front(); | |
| Queue.pop(); | |
| Nodes[Src].Taken = false; | |
| // Although the residual network contains edges with negative costs | |
| // (in particular, backward edges), it can be shown that there are no | |
| // negative-weight cycles and the following two invariants are maintained: | |
| // (i) Dist[Source, V] >= 0 and (ii) Dist[V, Target] >= 0 for all nodes V, | |
| // where Dist is the length of the shortest path between two nodes. This | |
| // allows to prune the search-space of the path-finding algorithm using | |
| // the following early-stop criteria: | |
| // -- If we find a path with zero-distance from Source to Target, stop the | |
| // search, as the path is the shortest since Dist[Source, Target] >= 0; | |
| // -- If we have Dist[Source, V] > Dist[Source, Target], then do not | |
| // process node V, as it is guaranteed _not_ to be on a shortest path | |
| // from Source to Target; it follows from inequalities | |
| // Dist[Source, Target] >= Dist[Source, V] + Dist[V, Target] | |
| // >= Dist[Source, V] | |
| if (!Params.EvenFlowDistribution && Nodes[Target].Distance == 0) | |
| break; | |
| if (Nodes[Src].Distance > Nodes[Target].Distance) | |
| continue; | |
| // Process adjacent edges | |
| for (uint64_t EdgeIdx = 0; EdgeIdx < Edges[Src].size(); EdgeIdx++) { | |
| auto &Edge = Edges[Src][EdgeIdx]; | |
| if (Edge.Flow < Edge.Capacity) { | |
| uint64_t Dst = Edge.Dst; | |
| int64_t NewDistance = Nodes[Src].Distance + Edge.Cost; | |
| if (Nodes[Dst].Distance > NewDistance) { | |
| // Update the distance and the parent node/edge | |
| Nodes[Dst].Distance = NewDistance; | |
| Nodes[Dst].ParentNode = Src; | |
| Nodes[Dst].ParentEdgeIndex = EdgeIdx; | |
| // Add the node to the queue, if it is not there yet | |
| if (!Nodes[Dst].Taken) { | |
| Queue.push(Dst); | |
| Nodes[Dst].Taken = true; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| return Nodes[Target].Distance != INF; | |
| } | |
| /// Update the current flow along the augmenting path. | |
| void augmentFlowAlongPath(uint64_t PathCapacity) { | |
| assert(PathCapacity > 0 && "found an incorrect augmenting path"); | |
| uint64_t Now = Target; | |
| while (Now != Source) { | |
| uint64_t Pred = Nodes[Now].ParentNode; | |
| auto &Edge = Edges[Pred][Nodes[Now].ParentEdgeIndex]; | |
| auto &RevEdge = Edges[Now][Edge.RevEdgeIndex]; | |
| Edge.Flow += PathCapacity; | |
| RevEdge.Flow -= PathCapacity; | |
| Now = Pred; | |
| } | |
| } | |
| /// Find an Augmenting DAG order using a modified version of DFS in which we | |
| /// can visit a node multiple times. In the DFS search, when scanning each | |
| /// edge out of a node, continue search at Edge.Dst endpoint if it has not | |
| /// been discovered yet and its NumCalls < MaxDfsCalls. The algorithm | |
| /// runs in O(MaxDfsCalls * |Edges| + |Nodes|) time. | |
| /// It returns an Augmenting Order (Taken nodes in decreasing Finish time) | |
| /// that starts with Source and ends with Target. | |
| std::vector<uint64_t> findAugmentingDAG() { | |
| // We use a stack based implemenation of DFS to avoid recursion. | |
| // Defining DFS data structures: | |
| // A pair (NodeIdx, EdgeIdx) at the top of the Stack denotes that | |
| // - we are currently visiting Nodes[NodeIdx] and | |
| // - the next edge to scan is Edges[NodeIdx][EdgeIdx] | |
| typedef std::pair<uint64_t, uint64_t> StackItemType; | |
| std::stack<StackItemType> Stack; | |
| std::vector<uint64_t> AugmentingOrder; | |
| // Phase 0: Initialize Node attributes and Time for DFS run | |
| for (auto &Node : Nodes) { | |
| Node.Discovery = 0; | |
| Node.Finish = 0; | |
| Node.NumCalls = 0; | |
| Node.Taken = false; | |
| } | |
| uint64_t Time = 0; | |
| // Mark Target as Taken | |
| // Taken attribute will be propagated backwards from Target towards Source | |
| Nodes[Target].Taken = true; | |
| // Phase 1: Start DFS traversal from Source | |
| Stack.emplace(Source, 0); | |
| Nodes[Source].Discovery = ++Time; | |
| while (!Stack.empty()) { | |
| auto NodeIdx = Stack.top().first; | |
| auto EdgeIdx = Stack.top().second; | |
| // If we haven't scanned all edges out of NodeIdx, continue scanning | |
| if (EdgeIdx < Edges[NodeIdx].size()) { | |
| auto &Edge = Edges[NodeIdx][EdgeIdx]; | |
| auto &Dst = Nodes[Edge.Dst]; | |
| Stack.top().second++; | |
| if (Edge.OnShortestPath) { | |
| // If we haven't seen Edge.Dst so far, continue DFS search there | |
| if (Dst.Discovery == 0 && Dst.NumCalls < MaxDfsCalls) { | |
| Dst.Discovery = ++Time; | |
| Stack.emplace(Edge.Dst, 0); | |
| Dst.NumCalls++; | |
| } else if (Dst.Taken && Dst.Finish != 0) { | |
| // Else, if Edge.Dst already have a path to Target, so that NodeIdx | |
| Nodes[NodeIdx].Taken = true; | |
| } | |
| } | |
| } else { | |
| // If we are done scanning all edge out of NodeIdx | |
| Stack.pop(); | |
| // If we haven't found a path from NodeIdx to Target, forget about it | |
| if (!Nodes[NodeIdx].Taken) { | |
| Nodes[NodeIdx].Discovery = 0; | |
| } else { | |
| // If we have found a path from NodeIdx to Target, then finish NodeIdx | |
| // and propagate Taken flag to DFS parent unless at the Source | |
| Nodes[NodeIdx].Finish = ++Time; | |
| // NodeIdx == Source if and only if the stack is empty | |
| if (NodeIdx != Source) { | |
| assert(!Stack.empty() && "empty stack while running dfs"); | |
| Nodes[Stack.top().first].Taken = true; | |
| } | |
| AugmentingOrder.push_back(NodeIdx); | |
| } | |
| } | |
| } | |
| // Nodes are collected decreasing Finish time, so the order is reversed | |
| std::reverse(AugmentingOrder.begin(), AugmentingOrder.end()); | |
| // Phase 2: Extract all forward (DAG) edges and fill in AugmentingEdges | |
| for (size_t Src : AugmentingOrder) { | |
| AugmentingEdges[Src].clear(); | |
| for (auto &Edge : Edges[Src]) { | |
| uint64_t Dst = Edge.Dst; | |
| if (Edge.OnShortestPath && Nodes[Src].Taken && Nodes[Dst].Taken && | |
| Nodes[Dst].Finish < Nodes[Src].Finish) { | |
| AugmentingEdges[Src].push_back(&Edge); | |
| } | |
| } | |
| assert((Src == Target || !AugmentingEdges[Src].empty()) && | |
| "incorrectly constructed augmenting edges"); | |
| } | |
| return AugmentingOrder; | |
| } | |
| /// Update the current flow along the given (acyclic) subgraph specified by | |
| /// the vertex order, AugmentingOrder. The objective is to send as much flow | |
| /// as possible while evenly distributing flow among successors of each node. | |
| /// After the update at least one edge is saturated. | |
| bool augmentFlowAlongDAG(const std::vector<uint64_t> &AugmentingOrder) { | |
| // Phase 0: Initialization | |
| for (uint64_t Src : AugmentingOrder) { | |
| Nodes[Src].FracFlow = 0; | |
| Nodes[Src].IntFlow = 0; | |
| for (auto &Edge : AugmentingEdges[Src]) { | |
| Edge->AugmentedFlow = 0; | |
| } | |
| } | |
| // Phase 1: Send a unit of fractional flow along the DAG | |
| uint64_t MaxFlowAmount = INF; | |
| Nodes[Source].FracFlow = 1.0; | |
| for (uint64_t Src : AugmentingOrder) { | |
| assert((Src == Target || Nodes[Src].FracFlow > 0.0) && | |
| "incorrectly computed fractional flow"); | |
| // Distribute flow evenly among successors of Src | |
| uint64_t Degree = AugmentingEdges[Src].size(); | |
| for (auto &Edge : AugmentingEdges[Src]) { | |
| double EdgeFlow = Nodes[Src].FracFlow / Degree; | |
| Nodes[Edge->Dst].FracFlow += EdgeFlow; | |
| if (Edge->Capacity == INF) | |
| continue; | |
| uint64_t MaxIntFlow = double(Edge->Capacity - Edge->Flow) / EdgeFlow; | |
| MaxFlowAmount = std::min(MaxFlowAmount, MaxIntFlow); | |
| } | |
| } | |
| // Stop early if we cannot send any (integral) flow from Source to Target | |
| if (MaxFlowAmount == 0) | |
| return false; | |
| // Phase 2: Send an integral flow of MaxFlowAmount | |
| Nodes[Source].IntFlow = MaxFlowAmount; | |
| for (uint64_t Src : AugmentingOrder) { | |
| if (Src == Target) | |
| break; | |
| // Distribute flow evenly among successors of Src, rounding up to make | |
| // sure all flow is sent | |
| uint64_t Degree = AugmentingEdges[Src].size(); | |
| // We are guaranteeed that Node[Src].IntFlow <= SuccFlow * Degree | |
| uint64_t SuccFlow = (Nodes[Src].IntFlow + Degree - 1) / Degree; | |
| for (auto &Edge : AugmentingEdges[Src]) { | |
| uint64_t Dst = Edge->Dst; | |
| uint64_t EdgeFlow = std::min(Nodes[Src].IntFlow, SuccFlow); | |
| EdgeFlow = std::min(EdgeFlow, uint64_t(Edge->Capacity - Edge->Flow)); | |
| Nodes[Dst].IntFlow += EdgeFlow; | |
| Nodes[Src].IntFlow -= EdgeFlow; | |
| Edge->AugmentedFlow += EdgeFlow; | |
| } | |
| } | |
| assert(Nodes[Target].IntFlow <= MaxFlowAmount); | |
| Nodes[Target].IntFlow = 0; | |
| // Phase 3: Send excess flow back traversing the nodes backwards. | |
| // Because of rounding, not all flow can be sent along the edges of Src. | |
| // Hence, sending the remaining flow back to maintain flow conservation | |
| for (size_t Idx = AugmentingOrder.size() - 1; Idx > 0; Idx--) { | |
| uint64_t Src = AugmentingOrder[Idx - 1]; | |
| // Try to send excess flow back along each edge. | |
| // Make sure we only send back flow we just augmented (AugmentedFlow). | |
| for (auto &Edge : AugmentingEdges[Src]) { | |
| uint64_t Dst = Edge->Dst; | |
| if (Nodes[Dst].IntFlow == 0) | |
| continue; | |
| uint64_t EdgeFlow = std::min(Nodes[Dst].IntFlow, Edge->AugmentedFlow); | |
| Nodes[Dst].IntFlow -= EdgeFlow; | |
| Nodes[Src].IntFlow += EdgeFlow; | |
| Edge->AugmentedFlow -= EdgeFlow; | |
| } | |
| } | |
| // Phase 4: Update flow values along all edges | |
| bool HasSaturatedEdges = false; | |
| for (uint64_t Src : AugmentingOrder) { | |
| // Verify that we have sent all the excess flow from the node | |
| assert(Src == Source || Nodes[Src].IntFlow == 0); | |
| for (auto &Edge : AugmentingEdges[Src]) { | |
| assert(uint64_t(Edge->Capacity - Edge->Flow) >= Edge->AugmentedFlow); | |
| // Update flow values along the edge and its reverse copy | |
| auto &RevEdge = Edges[Edge->Dst][Edge->RevEdgeIndex]; | |
| Edge->Flow += Edge->AugmentedFlow; | |
| RevEdge.Flow -= Edge->AugmentedFlow; | |
| if (Edge->Capacity == Edge->Flow && Edge->AugmentedFlow > 0) | |
| HasSaturatedEdges = true; | |
| } | |
| } | |
| // The augmentation is successful iff at least one edge becomes saturated | |
| return HasSaturatedEdges; | |
| } | |
| /// Identify candidate (shortest) edges for augmentation. | |
| void identifyShortestEdges(uint64_t PathCapacity) { | |
| assert(PathCapacity > 0 && "found an incorrect augmenting DAG"); | |
| // To make sure the augmentation DAG contains only edges with large residual | |
| // capacity, we prune all edges whose capacity is below a fraction of | |
| // the capacity of the augmented path. | |
| // (All edges of the path itself are always in the DAG) | |
| uint64_t MinCapacity = std::max(PathCapacity / 2, uint64_t(1)); | |
| // Decide which edges are on a shortest path from Source to Target | |
| for (size_t Src = 0; Src < Nodes.size(); Src++) { | |
| // An edge cannot be augmenting if the endpoint has large distance | |
| if (Nodes[Src].Distance > Nodes[Target].Distance) | |
| continue; | |
| for (auto &Edge : Edges[Src]) { | |
| uint64_t Dst = Edge.Dst; | |
| Edge.OnShortestPath = | |
| Src != Target && Dst != Source && | |
| Nodes[Dst].Distance <= Nodes[Target].Distance && | |
| Nodes[Dst].Distance == Nodes[Src].Distance + Edge.Cost && | |
| Edge.Capacity > Edge.Flow && | |
| uint64_t(Edge.Capacity - Edge.Flow) >= MinCapacity; | |
| } | |
| } | |
| } | |
| /// Maximum number of DFS iterations for DAG finding. | |
| static constexpr uint64_t MaxDfsCalls = 10; | |
| /// A node in a flow network. | |
| struct Node { | |
| /// The cost of the cheapest path from the source to the current node. | |
| int64_t Distance; | |
| /// The node preceding the current one in the path. | |
| uint64_t ParentNode; | |
| /// The index of the edge between ParentNode and the current node. | |
| uint64_t ParentEdgeIndex; | |
| /// An indicator of whether the current node is in a queue. | |
| bool Taken; | |
| /// Data fields utilized in DAG-augmentation: | |
| /// Fractional flow. | |
| double FracFlow; | |
| /// Integral flow. | |
| uint64_t IntFlow; | |
| /// Discovery time. | |
| uint64_t Discovery; | |
| /// Finish time. | |
| uint64_t Finish; | |
| /// NumCalls. | |
| uint64_t NumCalls; | |
| }; | |
| /// An edge in a flow network. | |
| struct Edge { | |
| /// The cost of the edge. | |
| int64_t Cost; | |
| /// The capacity of the edge. | |
| int64_t Capacity; | |
| /// The current flow on the edge. | |
| int64_t Flow; | |
| /// The destination node of the edge. | |
| uint64_t Dst; | |
| /// The index of the reverse edge between Dst and the current node. | |
| uint64_t RevEdgeIndex; | |
| /// Data fields utilized in DAG-augmentation: | |
| /// Whether the edge is currently on a shortest path from Source to Target. | |
| bool OnShortestPath; | |
| /// Extra flow along the edge. | |
| uint64_t AugmentedFlow; | |
| }; | |
| /// The set of network nodes. | |
| std::vector<Node> Nodes; | |
| /// The set of network edges. | |
| std::vector<std::vector<Edge>> Edges; | |
| /// Source node of the flow. | |
| uint64_t Source; | |
| /// Target (sink) node of the flow. | |
| uint64_t Target; | |
| /// Augmenting edges. | |
| std::vector<std::vector<Edge *>> AugmentingEdges; | |
| /// Params for flow computation. | |
| const ProfiParams &Params; | |
| }; | |
| /// A post-processing adjustment of the control flow. It applies two steps by | |
| /// rerouting some flow and making it more realistic: | |
| /// | |
| /// - First, it removes all isolated components ("islands") with a positive flow | |
| /// that are unreachable from the entry block. For every such component, we | |
| /// find the shortest from the entry to an exit passing through the component, | |
| /// and increase the flow by one unit along the path. | |
| /// | |
| /// - Second, it identifies all "unknown subgraphs" consisting of basic blocks | |
| /// with no sampled counts. Then it rebalnces the flow that goes through such | |
| /// a subgraph so that each branch is taken with probability 50%. | |
| /// An unknown subgraph is such that for every two nodes u and v: | |
| /// - u dominates v and u is not unknown; | |
| /// - v post-dominates u; and | |
| /// - all inner-nodes of all (u,v)-paths are unknown. | |
| /// | |
| class FlowAdjuster { | |
| public: | |
| FlowAdjuster(const ProfiParams &Params, FlowFunction &Func) | |
| : Params(Params), Func(Func) {} | |
| /// Apply the post-processing. | |
| void run() { | |
| if (Params.JoinIslands) { | |
| // Adjust the flow to get rid of isolated components | |
| joinIsolatedComponents(); | |
| } | |
| if (Params.RebalanceUnknown) { | |
| // Rebalance the flow inside unknown subgraphs | |
| rebalanceUnknownSubgraphs(); | |
| } | |
| } | |
| private: | |
| void joinIsolatedComponents() { | |
| // Find blocks that are reachable from the source | |
| auto Visited = BitVector(NumBlocks(), false); | |
| findReachable(Func.Entry, Visited); | |
| // Iterate over all non-reachable blocks and adjust their weights | |
| for (uint64_t I = 0; I < NumBlocks(); I++) { | |
| auto &Block = Func.Blocks[I]; | |
| if (Block.Flow > 0 && !Visited[I]) { | |
| // Find a path from the entry to an exit passing through the block I | |
| auto Path = findShortestPath(I); | |
| // Increase the flow along the path | |
| assert(Path.size() > 0 && Path[0]->Source == Func.Entry && | |
| "incorrectly computed path adjusting control flow"); | |
| Func.Blocks[Func.Entry].Flow += 1; | |
| for (auto &Jump : Path) { | |
| Jump->Flow += 1; | |
| Func.Blocks[Jump->Target].Flow += 1; | |
| // Update reachability | |
| findReachable(Jump->Target, Visited); | |
| } | |
| } | |
| } | |
| } | |
| /// Run BFS from a given block along the jumps with a positive flow and mark | |
| /// all reachable blocks. | |
| void findReachable(uint64_t Src, BitVector &Visited) { | |
| if (Visited[Src]) | |
| return; | |
| std::queue<uint64_t> Queue; | |
| Queue.push(Src); | |
| Visited[Src] = true; | |
| while (!Queue.empty()) { | |
| Src = Queue.front(); | |
| Queue.pop(); | |
| for (auto *Jump : Func.Blocks[Src].SuccJumps) { | |
| uint64_t Dst = Jump->Target; | |
| if (Jump->Flow > 0 && !Visited[Dst]) { | |
| Queue.push(Dst); | |
| Visited[Dst] = true; | |
| } | |
| } | |
| } | |
| } | |
| /// Find the shortest path from the entry block to an exit block passing | |
| /// through a given block. | |
| std::vector<FlowJump *> findShortestPath(uint64_t BlockIdx) { | |
| // A path from the entry block to BlockIdx | |
| auto ForwardPath = findShortestPath(Func.Entry, BlockIdx); | |
| // A path from BlockIdx to an exit block | |
| auto BackwardPath = findShortestPath(BlockIdx, AnyExitBlock); | |
| // Concatenate the two paths | |
| std::vector<FlowJump *> Result; | |
| llvm::append_range(Result, ForwardPath); | |
| llvm::append_range(Result, BackwardPath); | |
| return Result; | |
| } | |
| /// Apply the Dijkstra algorithm to find the shortest path from a given | |
| /// Source to a given Target block. | |
| /// If Target == -1, then the path ends at an exit block. | |
| std::vector<FlowJump *> findShortestPath(uint64_t Source, uint64_t Target) { | |
| // Quit early, if possible | |
| if (Source == Target) | |
| return std::vector<FlowJump *>(); | |
| if (Func.Blocks[Source].isExit() && Target == AnyExitBlock) | |
| return std::vector<FlowJump *>(); | |
| // Initialize data structures | |
| auto Distance = std::vector<int64_t>(NumBlocks(), INF); | |
| auto Parent = std::vector<FlowJump *>(NumBlocks(), nullptr); | |
| Distance[Source] = 0; | |
| std::set<std::pair<uint64_t, uint64_t>> Queue; | |
| Queue.insert(std::make_pair(Distance[Source], Source)); | |
| // Run the Dijkstra algorithm | |
| while (!Queue.empty()) { | |
| uint64_t Src = Queue.begin()->second; | |
| Queue.erase(Queue.begin()); | |
| // If we found a solution, quit early | |
| if (Src == Target || | |
| (Func.Blocks[Src].isExit() && Target == AnyExitBlock)) | |
| break; | |
| for (auto *Jump : Func.Blocks[Src].SuccJumps) { | |
| uint64_t Dst = Jump->Target; | |
| int64_t JumpDist = jumpDistance(Jump); | |
| if (Distance[Dst] > Distance[Src] + JumpDist) { | |
| Queue.erase(std::make_pair(Distance[Dst], Dst)); | |
| Distance[Dst] = Distance[Src] + JumpDist; | |
| Parent[Dst] = Jump; | |
| Queue.insert(std::make_pair(Distance[Dst], Dst)); | |
| } | |
| } | |
| } | |
| // If Target is not provided, find the closest exit block | |
| if (Target == AnyExitBlock) { | |
| for (uint64_t I = 0; I < NumBlocks(); I++) { | |
| if (Func.Blocks[I].isExit() && Parent[I] != nullptr) { | |
| if (Target == AnyExitBlock || Distance[Target] > Distance[I]) { | |
| Target = I; | |
| } | |
| } | |
| } | |
| } | |
| assert(Parent[Target] != nullptr && "a path does not exist"); | |
| // Extract the constructed path | |
| std::vector<FlowJump *> Result; | |
| uint64_t Now = Target; | |
| while (Now != Source) { | |
| assert(Now == Parent[Now]->Target && "incorrect parent jump"); | |
| Result.push_back(Parent[Now]); | |
| Now = Parent[Now]->Source; | |
| } | |
| // Reverse the path, since it is extracted from Target to Source | |
| std::reverse(Result.begin(), Result.end()); | |
| return Result; | |
| } | |
| /// A distance of a path for a given jump. | |
| /// In order to incite the path to use blocks/jumps with large positive flow, | |
| /// and avoid changing branch probability of outgoing edges drastically, | |
| /// set the jump distance so as: | |
| /// - to minimize the number of unlikely jumps used and subject to that, | |
| /// - to minimize the number of Flow == 0 jumps used and subject to that, | |
| /// - minimizes total multiplicative Flow increase for the remaining edges. | |
| /// To capture this objective with integer distances, we round off fractional | |
| /// parts to a multiple of 1 / BaseDistance. | |
| int64_t jumpDistance(FlowJump *Jump) const { | |
| if (Jump->IsUnlikely) | |
| return Params.CostUnlikely; | |
| uint64_t BaseDistance = | |
| std::max(FlowAdjuster::MinBaseDistance, | |
| std::min(Func.Blocks[Func.Entry].Flow, | |
| Params.CostUnlikely / (2 * (NumBlocks() + 1)))); | |
| if (Jump->Flow > 0) | |
| return BaseDistance + BaseDistance / Jump->Flow; | |
| return 2 * BaseDistance * (NumBlocks() + 1); | |
| }; | |
| uint64_t NumBlocks() const { return Func.Blocks.size(); } | |
| /// Rebalance unknown subgraphs so that the flow is split evenly across the | |
| /// outgoing branches of every block of the subgraph. The method iterates over | |
| /// blocks with known weight and identifies unknown subgraphs rooted at the | |
| /// blocks. Then it verifies if flow rebalancing is feasible and applies it. | |
| void rebalanceUnknownSubgraphs() { | |
| // Try to find unknown subgraphs from each block | |
| for (const FlowBlock &SrcBlock : Func.Blocks) { | |
| // Verify if rebalancing rooted at SrcBlock is feasible | |
| if (!canRebalanceAtRoot(&SrcBlock)) | |
| continue; | |
| // Find an unknown subgraphs starting at SrcBlock. Along the way, | |
| // fill in known destinations and intermediate unknown blocks. | |
| std::vector<FlowBlock *> UnknownBlocks; | |
| std::vector<FlowBlock *> KnownDstBlocks; | |
| findUnknownSubgraph(&SrcBlock, KnownDstBlocks, UnknownBlocks); | |
| // Verify if rebalancing of the subgraph is feasible. If the search is | |
| // successful, find the unique destination block (which can be null) | |
| FlowBlock *DstBlock = nullptr; | |
| if (!canRebalanceSubgraph(&SrcBlock, KnownDstBlocks, UnknownBlocks, | |
| DstBlock)) | |
| continue; | |
| // We cannot rebalance subgraphs containing cycles among unknown blocks | |
| if (!isAcyclicSubgraph(&SrcBlock, DstBlock, UnknownBlocks)) | |
| continue; | |
| // Rebalance the flow | |
| rebalanceUnknownSubgraph(&SrcBlock, DstBlock, UnknownBlocks); | |
| } | |
| } | |
| /// Verify if rebalancing rooted at a given block is possible. | |
| bool canRebalanceAtRoot(const FlowBlock *SrcBlock) { | |
| // Do not attempt to find unknown subgraphs from an unknown or a | |
| // zero-flow block | |
| if (SrcBlock->HasUnknownWeight || SrcBlock->Flow == 0) | |
| return false; | |
| // Do not attempt to process subgraphs from a block w/o unknown sucessors | |
| bool HasUnknownSuccs = false; | |
| for (auto *Jump : SrcBlock->SuccJumps) { | |
| if (Func.Blocks[Jump->Target].HasUnknownWeight) { | |
| HasUnknownSuccs = true; | |
| break; | |
| } | |
| } | |
| if (!HasUnknownSuccs) | |
| return false; | |
| return true; | |
| } | |
| /// Find an unknown subgraph starting at block SrcBlock. The method sets | |
| /// identified destinations, KnownDstBlocks, and intermediate UnknownBlocks. | |
| void findUnknownSubgraph(const FlowBlock *SrcBlock, | |
| std::vector<FlowBlock *> &KnownDstBlocks, | |
| std::vector<FlowBlock *> &UnknownBlocks) { | |
| // Run BFS from SrcBlock and make sure all paths are going through unknown | |
| // blocks and end at a known DstBlock | |
| auto Visited = BitVector(NumBlocks(), false); | |
| std::queue<uint64_t> Queue; | |
| Queue.push(SrcBlock->Index); | |
| Visited[SrcBlock->Index] = true; | |
| while (!Queue.empty()) { | |
| auto &Block = Func.Blocks[Queue.front()]; | |
| Queue.pop(); | |
| // Process blocks reachable from Block | |
| for (auto *Jump : Block.SuccJumps) { | |
| // If Jump can be ignored, skip it | |
| if (ignoreJump(SrcBlock, nullptr, Jump)) | |
| continue; | |
| uint64_t Dst = Jump->Target; | |
| // If Dst has been visited, skip Jump | |
| if (Visited[Dst]) | |
| continue; | |
| // Process block Dst | |
| Visited[Dst] = true; | |
| if (!Func.Blocks[Dst].HasUnknownWeight) { | |
| KnownDstBlocks.push_back(&Func.Blocks[Dst]); | |
| } else { | |
| Queue.push(Dst); | |
| UnknownBlocks.push_back(&Func.Blocks[Dst]); | |
| } | |
| } | |
| } | |
| } | |
| /// Verify if rebalancing of the subgraph is feasible. If the checks are | |
| /// successful, set the unique destination block, DstBlock (can be null). | |
| bool canRebalanceSubgraph(const FlowBlock *SrcBlock, | |
| const std::vector<FlowBlock *> &KnownDstBlocks, | |
| const std::vector<FlowBlock *> &UnknownBlocks, | |
| FlowBlock *&DstBlock) { | |
| // If the list of unknown blocks is empty, we don't need rebalancing | |
| if (UnknownBlocks.empty()) | |
| return false; | |
| // If there are multiple known sinks, we can't rebalance | |
| if (KnownDstBlocks.size() > 1) | |
| return false; | |
| DstBlock = KnownDstBlocks.empty() ? nullptr : KnownDstBlocks.front(); | |
| // Verify sinks of the subgraph | |
| for (auto *Block : UnknownBlocks) { | |
| if (Block->SuccJumps.empty()) { | |
| // If there are multiple (known and unknown) sinks, we can't rebalance | |
| if (DstBlock != nullptr) | |
| return false; | |
| continue; | |
| } | |
| size_t NumIgnoredJumps = 0; | |
| for (auto *Jump : Block->SuccJumps) { | |
| if (ignoreJump(SrcBlock, DstBlock, Jump)) | |
| NumIgnoredJumps++; | |
| } | |
| // If there is a non-sink block in UnknownBlocks with all jumps ignored, | |
| // then we can't rebalance | |
| if (NumIgnoredJumps == Block->SuccJumps.size()) | |
| return false; | |
| } | |
| return true; | |
| } | |
| /// Decide whether the Jump is ignored while processing an unknown subgraphs | |
| /// rooted at basic block SrcBlock with the destination block, DstBlock. | |
| bool ignoreJump(const FlowBlock *SrcBlock, const FlowBlock *DstBlock, | |
| const FlowJump *Jump) { | |
| // Ignore unlikely jumps with zero flow | |
| if (Jump->IsUnlikely && Jump->Flow == 0) | |
| return true; | |
| auto JumpSource = &Func.Blocks[Jump->Source]; | |
| auto JumpTarget = &Func.Blocks[Jump->Target]; | |
| // Do not ignore jumps coming into DstBlock | |
| if (DstBlock != nullptr && JumpTarget == DstBlock) | |
| return false; | |
| // Ignore jumps out of SrcBlock to known blocks | |
| if (!JumpTarget->HasUnknownWeight && JumpSource == SrcBlock) | |
| return true; | |
| // Ignore jumps to known blocks with zero flow | |
| if (!JumpTarget->HasUnknownWeight && JumpTarget->Flow == 0) | |
| return true; | |
| return false; | |
| } | |
| /// Verify if the given unknown subgraph is acyclic, and if yes, reorder | |
| /// UnknownBlocks in the topological order (so that all jumps are "forward"). | |
| bool isAcyclicSubgraph(const FlowBlock *SrcBlock, const FlowBlock *DstBlock, | |
| std::vector<FlowBlock *> &UnknownBlocks) { | |
| // Extract local in-degrees in the considered subgraph | |
| auto LocalInDegree = std::vector<uint64_t>(NumBlocks(), 0); | |
| auto fillInDegree = [&](const FlowBlock *Block) { | |
| for (auto *Jump : Block->SuccJumps) { | |
| if (ignoreJump(SrcBlock, DstBlock, Jump)) | |
| continue; | |
| LocalInDegree[Jump->Target]++; | |
| } | |
| }; | |
| fillInDegree(SrcBlock); | |
| for (auto *Block : UnknownBlocks) { | |
| fillInDegree(Block); | |
| } | |
| // A loop containing SrcBlock | |
| if (LocalInDegree[SrcBlock->Index] > 0) | |
| return false; | |
| std::vector<FlowBlock *> AcyclicOrder; | |
| std::queue<uint64_t> Queue; | |
| Queue.push(SrcBlock->Index); | |
| while (!Queue.empty()) { | |
| FlowBlock *Block = &Func.Blocks[Queue.front()]; | |
| Queue.pop(); | |
| // Stop propagation once we reach DstBlock, if any | |
| if (DstBlock != nullptr && Block == DstBlock) | |
| break; | |
| // Keep an acyclic order of unknown blocks | |
| if (Block->HasUnknownWeight && Block != SrcBlock) | |
| AcyclicOrder.push_back(Block); | |
| // Add to the queue all successors with zero local in-degree | |
| for (auto *Jump : Block->SuccJumps) { | |
| if (ignoreJump(SrcBlock, DstBlock, Jump)) | |
| continue; | |
| uint64_t Dst = Jump->Target; | |
| LocalInDegree[Dst]--; | |
| if (LocalInDegree[Dst] == 0) { | |
| Queue.push(Dst); | |
| } | |
| } | |
| } | |
| // If there is a cycle in the subgraph, AcyclicOrder contains only a subset | |
| // of all blocks | |
| if (UnknownBlocks.size() != AcyclicOrder.size()) | |
| return false; | |
| UnknownBlocks = std::move(AcyclicOrder); | |
| return true; | |
| } | |
| /// Rebalance a given subgraph rooted at SrcBlock, ending at DstBlock and | |
| /// having UnknownBlocks intermediate blocks. | |
| void rebalanceUnknownSubgraph(const FlowBlock *SrcBlock, | |
| const FlowBlock *DstBlock, | |
| const std::vector<FlowBlock *> &UnknownBlocks) { | |
| assert(SrcBlock->Flow > 0 && "zero-flow block in unknown subgraph"); | |
| // Ditribute flow from the source block | |
| uint64_t BlockFlow = 0; | |
| // SrcBlock's flow is the sum of outgoing flows along non-ignored jumps | |
| for (auto *Jump : SrcBlock->SuccJumps) { | |
| if (ignoreJump(SrcBlock, DstBlock, Jump)) | |
| continue; | |
| BlockFlow += Jump->Flow; | |
| } | |
| rebalanceBlock(SrcBlock, DstBlock, SrcBlock, BlockFlow); | |
| // Ditribute flow from the remaining blocks | |
| for (auto *Block : UnknownBlocks) { | |
| assert(Block->HasUnknownWeight && "incorrect unknown subgraph"); | |
| uint64_t BlockFlow = 0; | |
| // Block's flow is the sum of incoming flows | |
| for (auto *Jump : Block->PredJumps) { | |
| BlockFlow += Jump->Flow; | |
| } | |
| Block->Flow = BlockFlow; | |
| rebalanceBlock(SrcBlock, DstBlock, Block, BlockFlow); | |
| } | |
| } | |
| /// Redistribute flow for a block in a subgraph rooted at SrcBlock, | |
| /// and ending at DstBlock. | |
| void rebalanceBlock(const FlowBlock *SrcBlock, const FlowBlock *DstBlock, | |
| const FlowBlock *Block, uint64_t BlockFlow) { | |
| // Process all successor jumps and update corresponding flow values | |
| size_t BlockDegree = 0; | |
| for (auto *Jump : Block->SuccJumps) { | |
| if (ignoreJump(SrcBlock, DstBlock, Jump)) | |
| continue; | |
| BlockDegree++; | |
| } | |
| // If all successor jumps of the block are ignored, skip it | |
| if (DstBlock == nullptr && BlockDegree == 0) | |
| return; | |
| assert(BlockDegree > 0 && "all outgoing jumps are ignored"); | |
| // Each of the Block's successors gets the following amount of flow. | |
| // Rounding the value up so that all flow is propagated | |
| uint64_t SuccFlow = (BlockFlow + BlockDegree - 1) / BlockDegree; | |
| for (auto *Jump : Block->SuccJumps) { | |
| if (ignoreJump(SrcBlock, DstBlock, Jump)) | |
| continue; | |
| uint64_t Flow = std::min(SuccFlow, BlockFlow); | |
| Jump->Flow = Flow; | |
| BlockFlow -= Flow; | |
| } | |
| assert(BlockFlow == 0 && "not all flow is propagated"); | |
| } | |
| /// A constant indicating an arbitrary exit block of a function. | |
| static constexpr uint64_t AnyExitBlock = uint64_t(-1); | |
| /// Minimum BaseDistance for the jump distance values in island joining. | |
| static constexpr uint64_t MinBaseDistance = 10000; | |
| /// Params for flow computation. | |
| const ProfiParams &Params; | |
| /// The function. | |
| FlowFunction &Func; | |
| }; | |
| std::pair<int64_t, int64_t> assignBlockCosts(const ProfiParams &Params, | |
| const FlowBlock &Block); | |
| std::pair<int64_t, int64_t> assignJumpCosts(const ProfiParams &Params, | |
| const FlowJump &Jump); | |
| /// Initializing flow network for a given function. | |
| /// | |
| /// Every block is split into two nodes that are responsible for (i) an | |
| /// incoming flow, (ii) an outgoing flow; they penalize an increase or a | |
| /// reduction of the block weight. | |
| void initializeNetwork(const ProfiParams &Params, MinCostMaxFlow &Network, | |
| FlowFunction &Func) { | |
| uint64_t NumBlocks = Func.Blocks.size(); | |
| assert(NumBlocks > 1 && "Too few blocks in a function"); | |
| uint64_t NumJumps = Func.Jumps.size(); | |
| assert(NumJumps > 0 && "Too few jumps in a function"); | |
| // Introducing dummy source/sink pairs to allow flow circulation. | |
| // The nodes corresponding to blocks of the function have indices in | |
| // the range [0 .. 2 * NumBlocks); the dummy sources/sinks are indexed by the | |
| // next four values. | |
| uint64_t S = 2 * NumBlocks; | |
| uint64_t T = S + 1; | |
| uint64_t S1 = S + 2; | |
| uint64_t T1 = S + 3; | |
| Network.initialize(2 * NumBlocks + 4, S1, T1); | |
| // Initialize nodes of the flow network | |
| for (uint64_t B = 0; B < NumBlocks; B++) { | |
| auto &Block = Func.Blocks[B]; | |
| // Split every block into two auxiliary nodes to allow | |
| // increase/reduction of the block count. | |
| uint64_t Bin = 2 * B; | |
| uint64_t Bout = 2 * B + 1; | |
| // Edges from S and to T | |
| if (Block.isEntry()) { | |
| Network.addEdge(S, Bin, 0); | |
| } else if (Block.isExit()) { | |
| Network.addEdge(Bout, T, 0); | |
| } | |
| // Assign costs for increasing/decreasing the block counts | |
| auto [AuxCostInc, AuxCostDec] = assignBlockCosts(Params, Block); | |
| // Add the corresponding edges to the network | |
| Network.addEdge(Bin, Bout, AuxCostInc); | |
| if (Block.Weight > 0) { | |
| Network.addEdge(Bout, Bin, Block.Weight, AuxCostDec); | |
| Network.addEdge(S1, Bout, Block.Weight, 0); | |
| Network.addEdge(Bin, T1, Block.Weight, 0); | |
| } | |
| } | |
| // Initialize edges of the flow network | |
| for (uint64_t J = 0; J < NumJumps; J++) { | |
| auto &Jump = Func.Jumps[J]; | |
| // Get the endpoints corresponding to the jump | |
| uint64_t Jin = 2 * Jump.Source + 1; | |
| uint64_t Jout = 2 * Jump.Target; | |
| // Assign costs for increasing/decreasing the jump counts | |
| auto [AuxCostInc, AuxCostDec] = assignJumpCosts(Params, Jump); | |
| // Add the corresponding edges to the network | |
| Network.addEdge(Jin, Jout, AuxCostInc); | |
| if (Jump.Weight > 0) { | |
| Network.addEdge(Jout, Jin, Jump.Weight, AuxCostDec); | |
| Network.addEdge(S1, Jout, Jump.Weight, 0); | |
| Network.addEdge(Jin, T1, Jump.Weight, 0); | |
| } | |
| } | |
| // Make sure we have a valid flow circulation | |
| Network.addEdge(T, S, 0); | |
| } | |
| /// Assign costs for increasing/decreasing the block counts. | |
| std::pair<int64_t, int64_t> assignBlockCosts(const ProfiParams &Params, | |
| const FlowBlock &Block) { | |
| // Modifying the weight of an unlikely block is expensive | |
| if (Block.IsUnlikely) | |
| return std::make_pair(Params.CostUnlikely, Params.CostUnlikely); | |
| // Assign default values for the costs | |
| int64_t CostInc = Params.CostBlockInc; | |
| int64_t CostDec = Params.CostBlockDec; | |
| // Update the costs depending on the block metadata | |
| if (Block.HasUnknownWeight) { | |
| CostInc = Params.CostBlockUnknownInc; | |
| CostDec = 0; | |
| } else { | |
| // Increasing the count for "cold" blocks with zero initial count is more | |
| // expensive than for "hot" ones | |
| if (Block.Weight == 0) | |
| CostInc = Params.CostBlockZeroInc; | |
| // Modifying the count of the entry block is expensive | |
| if (Block.isEntry()) { | |
| CostInc = Params.CostBlockEntryInc; | |
| CostDec = Params.CostBlockEntryDec; | |
| } | |
| } | |
| return std::make_pair(CostInc, CostDec); | |
| } | |
| /// Assign costs for increasing/decreasing the jump counts. | |
| std::pair<int64_t, int64_t> assignJumpCosts(const ProfiParams &Params, | |
| const FlowJump &Jump) { | |
| // Modifying the weight of an unlikely jump is expensive | |
| if (Jump.IsUnlikely) | |
| return std::make_pair(Params.CostUnlikely, Params.CostUnlikely); | |
| // Assign default values for the costs | |
| int64_t CostInc = Params.CostJumpInc; | |
| int64_t CostDec = Params.CostJumpDec; | |
| // Update the costs depending on the block metadata | |
| if (Jump.Source + 1 == Jump.Target) { | |
| // Adjusting the fall-through branch | |
| CostInc = Params.CostJumpFTInc; | |
| CostDec = Params.CostJumpFTDec; | |
| } | |
| if (Jump.HasUnknownWeight) { | |
| // The cost is different for fall-through and non-fall-through branches | |
| if (Jump.Source + 1 == Jump.Target) | |
| CostInc = Params.CostJumpUnknownFTInc; | |
| else | |
| CostInc = Params.CostJumpUnknownInc; | |
| CostDec = 0; | |
| } | |
| return std::make_pair(CostInc, CostDec); | |
| } | |
| /// Extract resulting block and edge counts from the flow network. | |
| void extractWeights(const ProfiParams &Params, MinCostMaxFlow &Network, | |
| FlowFunction &Func) { | |
| uint64_t NumBlocks = Func.Blocks.size(); | |
| uint64_t NumJumps = Func.Jumps.size(); | |
| // Extract resulting jump counts | |
| for (uint64_t J = 0; J < NumJumps; J++) { | |
| auto &Jump = Func.Jumps[J]; | |
| uint64_t SrcOut = 2 * Jump.Source + 1; | |
| uint64_t DstIn = 2 * Jump.Target; | |
| int64_t Flow = 0; | |
| int64_t AuxFlow = Network.getFlow(SrcOut, DstIn); | |
| if (Jump.Source != Jump.Target) | |
| Flow = int64_t(Jump.Weight) + AuxFlow; | |
| else | |
| Flow = int64_t(Jump.Weight) + (AuxFlow > 0 ? AuxFlow : 0); | |
| Jump.Flow = Flow; | |
| assert(Flow >= 0 && "negative jump flow"); | |
| } | |
| // Extract resulting block counts | |
| auto InFlow = std::vector<uint64_t>(NumBlocks, 0); | |
| auto OutFlow = std::vector<uint64_t>(NumBlocks, 0); | |
| for (auto &Jump : Func.Jumps) { | |
| InFlow[Jump.Target] += Jump.Flow; | |
| OutFlow[Jump.Source] += Jump.Flow; | |
| } | |
| for (uint64_t B = 0; B < NumBlocks; B++) { | |
| auto &Block = Func.Blocks[B]; | |
| Block.Flow = std::max(OutFlow[B], InFlow[B]); | |
| } | |
| } | |
| #ifndef NDEBUG | |
| /// Verify that the provided block/jump weights are as expected. | |
| void verifyInput(const FlowFunction &Func) { | |
| // Verify entry and exit blocks | |
| assert(Func.Entry == 0 && Func.Blocks[0].isEntry()); | |
| size_t NumExitBlocks = 0; | |
| for (size_t I = 1; I < Func.Blocks.size(); I++) { | |
| assert(!Func.Blocks[I].isEntry() && "multiple entry blocks"); | |
| if (Func.Blocks[I].isExit()) | |
| NumExitBlocks++; | |
| } | |
| assert(NumExitBlocks > 0 && "cannot find exit blocks"); | |
| // Verify that there are no parallel edges | |
| for (auto &Block : Func.Blocks) { | |
| std::unordered_set<uint64_t> UniqueSuccs; | |
| for (auto &Jump : Block.SuccJumps) { | |
| auto It = UniqueSuccs.insert(Jump->Target); | |
| assert(It.second && "input CFG contains parallel edges"); | |
| } | |
| } | |
| // Verify CFG jumps | |
| for (auto &Block : Func.Blocks) { | |
| assert((!Block.isEntry() || !Block.isExit()) && | |
| "a block cannot be an entry and an exit"); | |
| } | |
| // Verify input block weights | |
| for (auto &Block : Func.Blocks) { | |
| assert((!Block.HasUnknownWeight || Block.Weight == 0 || Block.isEntry()) && | |
| "non-zero weight of a block w/o weight except for an entry"); | |
| } | |
| // Verify input jump weights | |
| for (auto &Jump : Func.Jumps) { | |
| assert((!Jump.HasUnknownWeight || Jump.Weight == 0) && | |
| "non-zero weight of a jump w/o weight"); | |
| } | |
| } | |
| /// Verify that the computed flow values satisfy flow conservation rules. | |
| void verifyOutput(const FlowFunction &Func) { | |
| const uint64_t NumBlocks = Func.Blocks.size(); | |
| auto InFlow = std::vector<uint64_t>(NumBlocks, 0); | |
| auto OutFlow = std::vector<uint64_t>(NumBlocks, 0); | |
| for (const auto &Jump : Func.Jumps) { | |
| InFlow[Jump.Target] += Jump.Flow; | |
| OutFlow[Jump.Source] += Jump.Flow; | |
| } | |
| uint64_t TotalInFlow = 0; | |
| uint64_t TotalOutFlow = 0; | |
| for (uint64_t I = 0; I < NumBlocks; I++) { | |
| auto &Block = Func.Blocks[I]; | |
| if (Block.isEntry()) { | |
| TotalInFlow += Block.Flow; | |
| assert(Block.Flow == OutFlow[I] && "incorrectly computed control flow"); | |
| } else if (Block.isExit()) { | |
| TotalOutFlow += Block.Flow; | |
| assert(Block.Flow == InFlow[I] && "incorrectly computed control flow"); | |
| } else { | |
| assert(Block.Flow == OutFlow[I] && "incorrectly computed control flow"); | |
| assert(Block.Flow == InFlow[I] && "incorrectly computed control flow"); | |
| } | |
| } | |
| assert(TotalInFlow == TotalOutFlow && "incorrectly computed control flow"); | |
| // Verify that there are no isolated flow components | |
| // One could modify FlowFunction to hold edges indexed by the sources, which | |
| // will avoid a creation of the object | |
| auto PositiveFlowEdges = std::vector<std::vector<uint64_t>>(NumBlocks); | |
| for (const auto &Jump : Func.Jumps) { | |
| if (Jump.Flow > 0) { | |
| PositiveFlowEdges[Jump.Source].push_back(Jump.Target); | |
| } | |
| } | |
| // Run BFS from the source along edges with positive flow | |
| std::queue<uint64_t> Queue; | |
| auto Visited = BitVector(NumBlocks, false); | |
| Queue.push(Func.Entry); | |
| Visited[Func.Entry] = true; | |
| while (!Queue.empty()) { | |
| uint64_t Src = Queue.front(); | |
| Queue.pop(); | |
| for (uint64_t Dst : PositiveFlowEdges[Src]) { | |
| if (!Visited[Dst]) { | |
| Queue.push(Dst); | |
| Visited[Dst] = true; | |
| } | |
| } | |
| } | |
| // Verify that every block that has a positive flow is reached from the source | |
| // along edges with a positive flow | |
| for (uint64_t I = 0; I < NumBlocks; I++) { | |
| auto &Block = Func.Blocks[I]; | |
| assert((Visited[I] || Block.Flow == 0) && "an isolated flow component"); | |
| } | |
| } | |
| #endif | |
| } // end of anonymous namespace | |
| /// Apply the profile inference algorithm for a given function and provided | |
| /// profi options | |
| void llvm::applyFlowInference(const ProfiParams &Params, FlowFunction &Func) { | |
| // Check if the function has samples and assign initial flow values | |
| bool HasSamples = false; | |
| for (FlowBlock &Block : Func.Blocks) { | |
| if (Block.Weight > 0) | |
| HasSamples = true; | |
| Block.Flow = Block.Weight; | |
| } | |
| for (FlowJump &Jump : Func.Jumps) { | |
| if (Jump.Weight > 0) | |
| HasSamples = true; | |
| Jump.Flow = Jump.Weight; | |
| } | |
| // Quit early for functions with a single block or ones w/o samples | |
| if (Func.Blocks.size() <= 1 || !HasSamples) | |
| return; | |
| #ifndef NDEBUG | |
| // Verify the input data | |
| verifyInput(Func); | |
| #endif | |
| // Create and apply an inference network model | |
| auto InferenceNetwork = MinCostMaxFlow(Params); | |
| initializeNetwork(Params, InferenceNetwork, Func); | |
| InferenceNetwork.run(); | |
| // Extract flow values for every block and every edge | |
| extractWeights(Params, InferenceNetwork, Func); | |
| // Post-processing adjustments to the flow | |
| auto Adjuster = FlowAdjuster(Params, Func); | |
| Adjuster.run(); | |
| #ifndef NDEBUG | |
| // Verify the result | |
| verifyOutput(Func); | |
| #endif | |
| } | |
| /// Apply the profile inference algorithm for a given flow function | |
| void llvm::applyFlowInference(FlowFunction &Func) { | |
| ProfiParams Params; | |
| // Set the params from the command-line flags. | |
| Params.EvenFlowDistribution = SampleProfileEvenFlowDistribution; | |
| Params.RebalanceUnknown = SampleProfileRebalanceUnknown; | |
| Params.JoinIslands = SampleProfileJoinIslands; | |
| Params.CostBlockInc = SampleProfileProfiCostBlockInc; | |
| Params.CostBlockDec = SampleProfileProfiCostBlockDec; | |
| Params.CostBlockEntryInc = SampleProfileProfiCostBlockEntryInc; | |
| Params.CostBlockEntryDec = SampleProfileProfiCostBlockEntryDec; | |
| Params.CostBlockZeroInc = SampleProfileProfiCostBlockZeroInc; | |
| Params.CostBlockUnknownInc = SampleProfileProfiCostBlockUnknownInc; | |
| applyFlowInference(Params, Func); | |
| } |