xref: /llvm-project/mlir/lib/Conversion/PDLToPDLInterp/RootOrdering.cpp (revision 9eb8e7b176e9fc38c8df86bd927663c6409ac262)
16df7cc7fSStanislav Funiak //===- RootOrdering.cpp - Optimal root ordering ---------------------------===//
26df7cc7fSStanislav Funiak //
36df7cc7fSStanislav Funiak // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
46df7cc7fSStanislav Funiak // See https://llvm.org/LICENSE.txt for license information.
56df7cc7fSStanislav Funiak // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
66df7cc7fSStanislav Funiak //
76df7cc7fSStanislav Funiak //===----------------------------------------------------------------------===//
86df7cc7fSStanislav Funiak //
96df7cc7fSStanislav Funiak // An implementation of Edmonds' optimal branching algorithm. This is a
106df7cc7fSStanislav Funiak // directed analogue of the minimum spanning tree problem for a given root.
116df7cc7fSStanislav Funiak //
126df7cc7fSStanislav Funiak //===----------------------------------------------------------------------===//
136df7cc7fSStanislav Funiak 
146df7cc7fSStanislav Funiak #include "RootOrdering.h"
156df7cc7fSStanislav Funiak 
166df7cc7fSStanislav Funiak #include "llvm/ADT/DenseMap.h"
176df7cc7fSStanislav Funiak #include "llvm/ADT/DenseSet.h"
186df7cc7fSStanislav Funiak #include "llvm/ADT/SmallVector.h"
196df7cc7fSStanislav Funiak #include <queue>
206df7cc7fSStanislav Funiak #include <utility>
216df7cc7fSStanislav Funiak 
226df7cc7fSStanislav Funiak using namespace mlir;
236df7cc7fSStanislav Funiak using namespace mlir::pdl_to_pdl_interp;
246df7cc7fSStanislav Funiak 
256df7cc7fSStanislav Funiak /// Returns the cycle implied by the specified parent relation, starting at the
266df7cc7fSStanislav Funiak /// given node.
getCycle(const DenseMap<Value,Value> & parents,Value rep)276df7cc7fSStanislav Funiak static SmallVector<Value> getCycle(const DenseMap<Value, Value> &parents,
286df7cc7fSStanislav Funiak                                    Value rep) {
296df7cc7fSStanislav Funiak   SmallVector<Value> cycle;
306df7cc7fSStanislav Funiak   Value node = rep;
316df7cc7fSStanislav Funiak   do {
326df7cc7fSStanislav Funiak     cycle.push_back(node);
336df7cc7fSStanislav Funiak     node = parents.lookup(node);
346df7cc7fSStanislav Funiak     assert(node && "got an empty value in the cycle");
356df7cc7fSStanislav Funiak   } while (node != rep);
366df7cc7fSStanislav Funiak   return cycle;
376df7cc7fSStanislav Funiak }
386df7cc7fSStanislav Funiak 
396df7cc7fSStanislav Funiak /// Contracts the specified cycle in the given graph in-place.
406df7cc7fSStanislav Funiak /// The parentsCost map specifies, for each node in the cycle, the lowest cost
416df7cc7fSStanislav Funiak /// among the edges entering that node. Then, the nodes in the cycle C are
426df7cc7fSStanislav Funiak /// replaced with a single node v_C (the first node in the cycle). All edges
436df7cc7fSStanislav Funiak /// (u, v) entering the cycle, v \in C, are replaced with a single edge
446df7cc7fSStanislav Funiak /// (u, v_C) with an appropriately chosen cost, and the selected node v is
456df7cc7fSStanislav Funiak /// marked in the output map actualTarget[u]. All edges (u, v) leaving the
466df7cc7fSStanislav Funiak /// cycle, u \in C, are replaced with a single edge (v_C, v), and the selected
476df7cc7fSStanislav Funiak /// node u is marked in the ouptut map actualSource[v].
contract(RootOrderingGraph & graph,ArrayRef<Value> cycle,const DenseMap<Value,unsigned> & parentDepths,DenseMap<Value,Value> & actualSource,DenseMap<Value,Value> & actualTarget)486df7cc7fSStanislav Funiak static void contract(RootOrderingGraph &graph, ArrayRef<Value> cycle,
49*9eb8e7b1SStanislav Funiak                      const DenseMap<Value, unsigned> &parentDepths,
506df7cc7fSStanislav Funiak                      DenseMap<Value, Value> &actualSource,
516df7cc7fSStanislav Funiak                      DenseMap<Value, Value> &actualTarget) {
526df7cc7fSStanislav Funiak   Value rep = cycle.front();
536df7cc7fSStanislav Funiak   DenseSet<Value> cycleSet(cycle.begin(), cycle.end());
546df7cc7fSStanislav Funiak 
556df7cc7fSStanislav Funiak   // Now, contract the cycle, marking the actual sources and targets.
56*9eb8e7b1SStanislav Funiak   DenseMap<Value, RootOrderingEntry> repEntries;
576df7cc7fSStanislav Funiak   for (auto outer = graph.begin(), e = graph.end(); outer != e; ++outer) {
586df7cc7fSStanislav Funiak     Value target = outer->first;
596df7cc7fSStanislav Funiak     if (cycleSet.contains(target)) {
606df7cc7fSStanislav Funiak       // Target in the cycle => edges incoming to the cycle or within the cycle.
61*9eb8e7b1SStanislav Funiak       unsigned parentDepth = parentDepths.lookup(target);
626df7cc7fSStanislav Funiak       for (const auto &inner : outer->second) {
636df7cc7fSStanislav Funiak         Value source = inner.first;
646df7cc7fSStanislav Funiak         // Ignore edges within the cycle.
656df7cc7fSStanislav Funiak         if (cycleSet.contains(source))
666df7cc7fSStanislav Funiak           continue;
676df7cc7fSStanislav Funiak 
686df7cc7fSStanislav Funiak         // Edge incoming to the cycle.
696df7cc7fSStanislav Funiak         std::pair<unsigned, unsigned> cost = inner.second.cost;
70*9eb8e7b1SStanislav Funiak         assert(parentDepth <= cost.first && "invalid parent depth");
716df7cc7fSStanislav Funiak 
726df7cc7fSStanislav Funiak         // Subtract the cost of the parent within the cycle from the cost of
736df7cc7fSStanislav Funiak         // the edge incoming to the cycle. This update ensures that the cost
746df7cc7fSStanislav Funiak         // of the minimum-weight spanning arborescence of the entire graph is
756df7cc7fSStanislav Funiak         // the cost of arborescence for the contracted graph plus the cost of
766df7cc7fSStanislav Funiak         // the cycle, no matter which edge in the cycle we choose to drop.
77*9eb8e7b1SStanislav Funiak         cost.first -= parentDepth;
78*9eb8e7b1SStanislav Funiak         auto it = repEntries.find(source);
79*9eb8e7b1SStanislav Funiak         if (it == repEntries.end() || it->second.cost > cost) {
806df7cc7fSStanislav Funiak           actualTarget[source] = target;
816df7cc7fSStanislav Funiak           // Do not bother populating the connector (the connector is only
826df7cc7fSStanislav Funiak           // relevant for the final traversal, not for the optimal branching).
83*9eb8e7b1SStanislav Funiak           repEntries[source].cost = cost;
846df7cc7fSStanislav Funiak         }
856df7cc7fSStanislav Funiak       }
866df7cc7fSStanislav Funiak       // Erase the node in the cycle.
876df7cc7fSStanislav Funiak       graph.erase(outer);
886df7cc7fSStanislav Funiak     } else {
896df7cc7fSStanislav Funiak       // Target not in cycle => edges going away from or unrelated to the cycle.
90*9eb8e7b1SStanislav Funiak       DenseMap<Value, RootOrderingEntry> &entries = outer->second;
916df7cc7fSStanislav Funiak       Value bestSource;
926df7cc7fSStanislav Funiak       std::pair<unsigned, unsigned> bestCost;
93*9eb8e7b1SStanislav Funiak       auto inner = entries.begin(), innerE = entries.end();
9402b6fb21SMehdi Amini       while (inner != innerE) {
956df7cc7fSStanislav Funiak         Value source = inner->first;
966df7cc7fSStanislav Funiak         if (cycleSet.contains(source)) {
976df7cc7fSStanislav Funiak           // Going-away edge => get its cost and erase it.
986df7cc7fSStanislav Funiak           if (!bestSource || bestCost > inner->second.cost) {
996df7cc7fSStanislav Funiak             bestSource = source;
1006df7cc7fSStanislav Funiak             bestCost = inner->second.cost;
1016df7cc7fSStanislav Funiak           }
102*9eb8e7b1SStanislav Funiak           entries.erase(inner++);
1036df7cc7fSStanislav Funiak         } else {
1046df7cc7fSStanislav Funiak           ++inner;
1056df7cc7fSStanislav Funiak         }
1066df7cc7fSStanislav Funiak       }
1076df7cc7fSStanislav Funiak 
1086df7cc7fSStanislav Funiak       // There were going-away edges, contract them.
1096df7cc7fSStanislav Funiak       if (bestSource) {
110*9eb8e7b1SStanislav Funiak         entries[rep].cost = bestCost;
1116df7cc7fSStanislav Funiak         actualSource[target] = bestSource;
1126df7cc7fSStanislav Funiak       }
1136df7cc7fSStanislav Funiak     }
1146df7cc7fSStanislav Funiak   }
1156df7cc7fSStanislav Funiak 
1166df7cc7fSStanislav Funiak   // Store the edges to the representative.
117*9eb8e7b1SStanislav Funiak   graph[rep] = std::move(repEntries);
1186df7cc7fSStanislav Funiak }
1196df7cc7fSStanislav Funiak 
OptimalBranching(RootOrderingGraph graph,Value root)1206df7cc7fSStanislav Funiak OptimalBranching::OptimalBranching(RootOrderingGraph graph, Value root)
1216df7cc7fSStanislav Funiak     : graph(std::move(graph)), root(root) {}
1226df7cc7fSStanislav Funiak 
solve()1236df7cc7fSStanislav Funiak unsigned OptimalBranching::solve() {
1246df7cc7fSStanislav Funiak   // Initialize the parents and total cost.
1256df7cc7fSStanislav Funiak   parents.clear();
1266df7cc7fSStanislav Funiak   parents[root] = Value();
1276df7cc7fSStanislav Funiak   unsigned totalCost = 0;
1286df7cc7fSStanislav Funiak 
1296df7cc7fSStanislav Funiak   // A map that stores the cost of the optimal local choice for each node
1306df7cc7fSStanislav Funiak   // in a directed cycle. This map is cleared every time we seed the search.
131*9eb8e7b1SStanislav Funiak   DenseMap<Value, unsigned> parentDepths;
132*9eb8e7b1SStanislav Funiak   parentDepths.reserve(graph.size());
1336df7cc7fSStanislav Funiak 
1346df7cc7fSStanislav Funiak   // Determine if the optimal local choice results in an acyclic graph. This is
1356df7cc7fSStanislav Funiak   // done by computing the optimal local choice and traversing up the computed
1366df7cc7fSStanislav Funiak   // parents. On success, `parents` will contain the parent of each node.
1376df7cc7fSStanislav Funiak   for (const auto &outer : graph) {
1386df7cc7fSStanislav Funiak     Value node = outer.first;
1396df7cc7fSStanislav Funiak     if (parents.count(node)) // already visited
1406df7cc7fSStanislav Funiak       continue;
1416df7cc7fSStanislav Funiak 
1426df7cc7fSStanislav Funiak     // Follow the trail of best sources until we reach an already visited node.
1436df7cc7fSStanislav Funiak     // The code will assert if we cannot reach an already visited node, i.e.,
1446df7cc7fSStanislav Funiak     // the graph is not strongly connected.
145*9eb8e7b1SStanislav Funiak     parentDepths.clear();
1466df7cc7fSStanislav Funiak     do {
1476df7cc7fSStanislav Funiak       auto it = graph.find(node);
1486df7cc7fSStanislav Funiak       assert(it != graph.end() && "the graph is not strongly connected");
1496df7cc7fSStanislav Funiak 
150*9eb8e7b1SStanislav Funiak       // Find the best local parent, taking into account both the depth and the
151*9eb8e7b1SStanislav Funiak       // tie breaking rules.
1526df7cc7fSStanislav Funiak       Value &bestSource = parents[node];
153*9eb8e7b1SStanislav Funiak       std::pair<unsigned, unsigned> bestCost;
1546df7cc7fSStanislav Funiak       for (const auto &inner : it->second) {
155*9eb8e7b1SStanislav Funiak         const RootOrderingEntry &entry = inner.second;
156*9eb8e7b1SStanislav Funiak         if (!bestSource /* initial */ || bestCost > entry.cost) {
1576df7cc7fSStanislav Funiak           bestSource = inner.first;
158*9eb8e7b1SStanislav Funiak           bestCost = entry.cost;
1596df7cc7fSStanislav Funiak         }
1606df7cc7fSStanislav Funiak       }
1616df7cc7fSStanislav Funiak       assert(bestSource && "the graph is not strongly connected");
162*9eb8e7b1SStanislav Funiak       parentDepths[node] = bestCost.first;
1636df7cc7fSStanislav Funiak       node = bestSource;
164*9eb8e7b1SStanislav Funiak       totalCost += bestCost.first;
1656df7cc7fSStanislav Funiak     } while (!parents.count(node));
1666df7cc7fSStanislav Funiak 
1676df7cc7fSStanislav Funiak     // If we reached a non-root node, we have a cycle.
168*9eb8e7b1SStanislav Funiak     if (parentDepths.count(node)) {
1696df7cc7fSStanislav Funiak       // Determine the cycle starting at the representative node.
1706df7cc7fSStanislav Funiak       SmallVector<Value> cycle = getCycle(parents, node);
1716df7cc7fSStanislav Funiak 
1726df7cc7fSStanislav Funiak       // The following maps disambiguate the source / target of the edges
1736df7cc7fSStanislav Funiak       // going out of / into the cycle.
1746df7cc7fSStanislav Funiak       DenseMap<Value, Value> actualSource, actualTarget;
1756df7cc7fSStanislav Funiak 
1766df7cc7fSStanislav Funiak       // Contract the cycle and recurse.
177*9eb8e7b1SStanislav Funiak       contract(graph, cycle, parentDepths, actualSource, actualTarget);
1786df7cc7fSStanislav Funiak       totalCost = solve();
1796df7cc7fSStanislav Funiak 
1806df7cc7fSStanislav Funiak       // Redirect the going-away edges.
1816df7cc7fSStanislav Funiak       for (auto &p : parents)
1826df7cc7fSStanislav Funiak         if (p.second == node)
1836df7cc7fSStanislav Funiak           // The parent is the node representating the cycle; replace it
1846df7cc7fSStanislav Funiak           // with the actual (best) source in the cycle.
1856df7cc7fSStanislav Funiak           p.second = actualSource.lookup(p.first);
1866df7cc7fSStanislav Funiak 
1876df7cc7fSStanislav Funiak       // Redirect the unique incoming edge and copy the cycle.
1886df7cc7fSStanislav Funiak       Value parent = parents.lookup(node);
1896df7cc7fSStanislav Funiak       Value entry = actualTarget.lookup(parent);
1906df7cc7fSStanislav Funiak       cycle.push_back(node); // complete the cycle
1916df7cc7fSStanislav Funiak       for (size_t i = 0, e = cycle.size() - 1; i < e; ++i) {
192*9eb8e7b1SStanislav Funiak         totalCost += parentDepths.lookup(cycle[i]);
1936df7cc7fSStanislav Funiak         if (cycle[i] == entry)
1946df7cc7fSStanislav Funiak           parents[cycle[i]] = parent; // break the cycle
1956df7cc7fSStanislav Funiak         else
1966df7cc7fSStanislav Funiak           parents[cycle[i]] = cycle[i + 1];
1976df7cc7fSStanislav Funiak       }
1986df7cc7fSStanislav Funiak 
1996df7cc7fSStanislav Funiak       // `parents` has a complete solution.
2006df7cc7fSStanislav Funiak       break;
2016df7cc7fSStanislav Funiak     }
2026df7cc7fSStanislav Funiak   }
2036df7cc7fSStanislav Funiak 
2046df7cc7fSStanislav Funiak   return totalCost;
2056df7cc7fSStanislav Funiak }
2066df7cc7fSStanislav Funiak 
2076df7cc7fSStanislav Funiak OptimalBranching::EdgeList
preOrderTraversal(ArrayRef<Value> nodes) const2086df7cc7fSStanislav Funiak OptimalBranching::preOrderTraversal(ArrayRef<Value> nodes) const {
2096df7cc7fSStanislav Funiak   // Invert the parent mapping.
2106df7cc7fSStanislav Funiak   DenseMap<Value, std::vector<Value>> children;
2116df7cc7fSStanislav Funiak   for (Value node : nodes) {
2126df7cc7fSStanislav Funiak     if (node != root) {
2136df7cc7fSStanislav Funiak       Value parent = parents.lookup(node);
2146df7cc7fSStanislav Funiak       assert(parent && "invalid parent");
2156df7cc7fSStanislav Funiak       children[parent].push_back(node);
2166df7cc7fSStanislav Funiak     }
2176df7cc7fSStanislav Funiak   }
2186df7cc7fSStanislav Funiak 
2196df7cc7fSStanislav Funiak   // The result which simultaneously acts as a queue.
2206df7cc7fSStanislav Funiak   EdgeList result;
2216df7cc7fSStanislav Funiak   result.reserve(nodes.size());
2226df7cc7fSStanislav Funiak   result.emplace_back(root, Value());
2236df7cc7fSStanislav Funiak 
2246df7cc7fSStanislav Funiak   // Perform a BFS, pushing into the queue.
2256df7cc7fSStanislav Funiak   for (size_t i = 0; i < result.size(); ++i) {
2266df7cc7fSStanislav Funiak     Value node = result[i].first;
2276df7cc7fSStanislav Funiak     for (Value child : children[node])
2286df7cc7fSStanislav Funiak       result.emplace_back(child, node);
2296df7cc7fSStanislav Funiak   }
2306df7cc7fSStanislav Funiak 
2316df7cc7fSStanislav Funiak   return result;
2326df7cc7fSStanislav Funiak }
233