xref: /aosp_15_r20/external/perfetto/src/trace_processor/perfetto_sql/intrinsics/functions/dominator_tree.cc (revision 6dbdd20afdafa5e3ca9b8809fa73465d530080dc)
1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "src/trace_processor/perfetto_sql/intrinsics/functions/dominator_tree.h"
18 
19 #include <algorithm>
20 #include <cstddef>
21 #include <cstdint>
22 #include <memory>
23 #include <optional>
24 #include <string>
25 #include <utility>
26 #include <vector>
27 
28 #include "perfetto/trace_processor/basic_types.h"
29 #include "src/trace_processor/perfetto_sql/intrinsics/functions/tables_py.h"
30 #include "src/trace_processor/sqlite/bindings/sqlite_aggregate_function.h"
31 #include "src/trace_processor/sqlite/bindings/sqlite_result.h"
32 
33 namespace perfetto::trace_processor {
34 namespace tables {
35 DominatorTreeTable::~DominatorTreeTable() = default;
36 }  // namespace tables
37 
38 namespace {
39 
40 class Forest;
41 
42 // Represents a node in the graph which the dominator tree is being computed on.
43 struct Node {
44   uint32_t id;
operator ==perfetto::trace_processor::__anon7a6808a70111::Node45   bool operator==(const Node& v) const { return id == v.id; }
46 };
47 
48 // Represents the "number" (i.e. index) of a node in the spanning tree computed
49 // by a DFS on the graph.
50 struct TreeNumber {
51   uint32_t i;
operator <perfetto::trace_processor::__anon7a6808a70111::TreeNumber52   bool operator<(const TreeNumber& o) const { return i < o.i; }
53 };
54 
55 // Helper class containing the "global state" used by the Lengauer-Tarjan
56 // algorithm.
57 class Graph {
58  public:
59   // Lengauer-Tarjan Dominators: Step 1.
60   void RunDfs(Node root_node);
61 
62   // Lengauer-Tarjan Dominators: Step 2 and 3.
63   void ComputeSemiDominatorAndPartialDominator(Forest&);
64 
65   // Lengauer-Tarjan Dominators: Step 4.
66   void ComputeDominators();
67 
AddEdge(Node source,Node dest)68   void AddEdge(Node source, Node dest) {
69     state_by_node_.resize(std::max<size_t>(
70         state_by_node_.size(), std::max(source.id + 1, dest.id + 1)));
71     GetStateForNode(source).successors.push_back(dest);
72     GetStateForNode(dest).predecessors.push_back(source);
73   }
74 
75   // Converts the dominator tree to a table.
ToTable(tables::DominatorTreeTable * table,Node root_node)76   void ToTable(tables::DominatorTreeTable* table, Node root_node) {
77     for (uint32_t i = 0; i < node_count_in_tree(); ++i) {
78       Node v = GetNodeForTreeNumber(TreeNumber{i});
79       NodeState& v_state = GetStateForNode(v);
80       tables::DominatorTreeTable::Row r;
81       r.node_id = v.id;
82       r.dominator_node_id = v == root_node
83                                 ? std::nullopt
84                                 : std::make_optional(v_state.dominator.id);
85       table->Insert(r);
86     }
87   }
88 
89   // Returns the TreeNumber for a given Node.
GetSemiDominator(Node v) const90   TreeNumber GetSemiDominator(Node v) const {
91     // Note: if you happen to see this check failing, it's likely a problem that
92     // the graph has nodes which are not reachable from the root node.
93     return *GetStateForNode(v).semi_dominator;
94   }
95 
96   // Returns the number of nodes in the tree (== the number of nodes in
97   // the graph.)
node_count_in_tree() const98   uint32_t node_count_in_tree() const {
99     return static_cast<uint32_t>(node_by_tree_number_.size());
100   }
101 
102   // Returns the "range" of the ids of the range (i.e. max(node id) + 1).
103   //
104   // This is useful for creating vectors which are indexed by node id.
node_id_range() const105   uint32_t node_id_range() const {
106     return static_cast<uint32_t>(state_by_node_.size());
107   }
108 
109  private:
110   // Struct containing the state needed for each node.
111   struct NodeState {
112     std::vector<Node> successors;
113     std::vector<Node> predecessors;
114     std::optional<TreeNumber> tree_parent;
115     std::vector<Node> self_as_semi_dominator;
116     std::optional<TreeNumber> semi_dominator;
117     Node dominator{0};
118   };
119 
GetStateForNode(Node v) const120   const NodeState& GetStateForNode(Node v) const {
121     return state_by_node_[v.id];
122   }
GetStateForNode(Node v)123   NodeState& GetStateForNode(Node v) { return state_by_node_[v.id]; }
GetNodeForTreeNumber(TreeNumber d)124   Node& GetNodeForTreeNumber(TreeNumber d) { return node_by_tree_number_[d.i]; }
125 
126   std::vector<NodeState> state_by_node_;
127   std::vector<Node> node_by_tree_number_;
128 };
129 
130 // Implementation of the "union-find" like helper data structure used by the
131 // Lengauer-Tarjan algorithm.
132 //
133 // This corresponds to the "Link" and "Eval" functions in the paper.
134 class Forest {
135  public:
Forest(uint32_t vertices_count)136   explicit Forest(uint32_t vertices_count) : state_by_node_(vertices_count) {
137     for (uint32_t i = 0; i < vertices_count; ++i) {
138       state_by_node_[i].min_semi_dominator_until_ancestor = Node{i};
139     }
140   }
141 
142   // Corresponds to the "Link" function in the paper.
Link(Node ancestor,Node descendant)143   void Link(Node ancestor, Node descendant) {
144     std::optional<Node>& a = state_by_node_[descendant.id].ancestor;
145     PERFETTO_DCHECK(!a);
146     a = ancestor;
147   }
148 
149   // Corresponds to the "Eval" function in the paper.
GetMinSemiDominatorToAncestor(Node vertex,const Graph & graph)150   Node GetMinSemiDominatorToAncestor(Node vertex, const Graph& graph) {
151     NodeState& state = GetStateForNode(vertex);
152     if (!state.ancestor) {
153       return vertex;
154     }
155     Compress(vertex, graph);
156     return state.min_semi_dominator_until_ancestor;
157   }
158 
159  private:
160   struct NodeState {
161     std::optional<Node> ancestor;
162     Node min_semi_dominator_until_ancestor;
163   };
164 
165   // Implements the O(log(n)) path-compression algorithm in the paper: note that
166   // we use stack-based recursion to avoid stack-overflows with very large heap
167   // graphs.
Compress(Node vertex,const Graph & graph)168   void Compress(Node vertex, const Graph& graph) {
169     struct CompressState {
170       Node current;
171       bool recurse_done;
172     };
173     std::vector<CompressState> states{CompressState{vertex, false}};
174     while (!states.empty()) {
175       CompressState& s = states.back();
176       NodeState& state = GetStateForNode(s.current);
177       PERFETTO_CHECK(state.ancestor);
178       NodeState& ancestor_state = GetStateForNode(*state.ancestor);
179       if (s.recurse_done) {
180         states.pop_back();
181         Node ancestor_min = ancestor_state.min_semi_dominator_until_ancestor;
182         Node self_min = state.min_semi_dominator_until_ancestor;
183         if (graph.GetSemiDominator(ancestor_min) <
184             graph.GetSemiDominator(self_min)) {
185           state.min_semi_dominator_until_ancestor = ancestor_min;
186         }
187         state.ancestor = ancestor_state.ancestor;
188       } else {
189         s.recurse_done = true;
190         if (auto grand_ancestor = ancestor_state.ancestor; grand_ancestor) {
191           states.push_back(CompressState{*state.ancestor, false});
192         } else {
193           states.pop_back();
194         }
195       }
196     }
197   }
198 
GetStateForNode(Node v)199   NodeState& GetStateForNode(Node v) { return state_by_node_[v.id]; }
200 
201   std::vector<NodeState> state_by_node_;
202 };
203 
204 // Lengauer-Tarjan Dominators: Step 1.
RunDfs(Node root)205 void Graph::RunDfs(Node root) {
206   struct StackState {
207     Node node;
208     std::optional<TreeNumber> parent;
209   };
210 
211   std::vector<StackState> stack{{root, std::nullopt}};
212   while (!stack.empty()) {
213     StackState stack_state = stack.back();
214     stack.pop_back();
215 
216     NodeState& s = GetStateForNode(stack_state.node);
217     if (s.semi_dominator) {
218       continue;
219     }
220 
221     TreeNumber tree_number{static_cast<uint32_t>(node_by_tree_number_.size())};
222     s.tree_parent = stack_state.parent;
223     s.semi_dominator = tree_number;
224     node_by_tree_number_.push_back(stack_state.node);
225 
226     for (auto it = s.successors.rbegin(); it != s.successors.rend(); ++it) {
227       stack.emplace_back(StackState{*it, tree_number});
228     }
229   }
230 }
231 
232 // Lengauer-Tarjan Dominators: Step 2 & 3.
ComputeSemiDominatorAndPartialDominator(Forest & forest)233 void Graph::ComputeSemiDominatorAndPartialDominator(Forest& forest) {
234   // Note the >0 is *intentional* as we do *not* want to process the root.
235   for (uint32_t i = node_count_in_tree() - 1; i > 0; --i) {
236     Node w = GetNodeForTreeNumber(TreeNumber{i});
237     NodeState& w_state = GetStateForNode(w);
238     for (Node v : w_state.predecessors) {
239       Node u = forest.GetMinSemiDominatorToAncestor(v, *this);
240       w_state.semi_dominator =
241           std::min(*w_state.semi_dominator, GetSemiDominator(u));
242     }
243     NodeState& semi_dominator_state =
244         GetStateForNode(GetNodeForTreeNumber(*w_state.semi_dominator));
245     semi_dominator_state.self_as_semi_dominator.push_back(w);
246     PERFETTO_CHECK(w_state.tree_parent);
247 
248     Node w_parent = GetNodeForTreeNumber(*w_state.tree_parent);
249     forest.Link(w_parent, w);
250 
251     NodeState& w_parent_state = GetStateForNode(w_parent);
252     for (Node v : w_parent_state.self_as_semi_dominator) {
253       Node u = forest.GetMinSemiDominatorToAncestor(v, *this);
254       NodeState& v_state = GetStateForNode(v);
255       v_state.dominator =
256           GetSemiDominator(u) < v_state.semi_dominator ? u : w_parent;
257     }
258     w_parent_state.self_as_semi_dominator.clear();
259   }
260 }
261 
262 // Lengauer-Tarjan Dominators: Step 4.
ComputeDominators()263 void Graph::ComputeDominators() {
264   // Starting from 1 is intentional as we don't want to process the root node.
265   for (uint32_t i = 1; i < node_count_in_tree(); ++i) {
266     Node w = GetNodeForTreeNumber(TreeNumber{i});
267     NodeState& w_state = GetStateForNode(w);
268     Node semi_dominator = GetNodeForTreeNumber(*w_state.semi_dominator);
269     if (w_state.dominator == semi_dominator) {
270       continue;
271     }
272     w_state.dominator = GetStateForNode(w_state.dominator).dominator;
273   }
274 }
275 
276 struct AggCtx : SqliteAggregateContext<AggCtx> {
277   Graph graph;
278   std::optional<uint32_t> start_id;
279 };
280 
281 }  // namespace
282 
Step(sqlite3_context * ctx,int argc,sqlite3_value ** argv)283 void DominatorTree::Step(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
284   if (argc != kArgCount) {
285     return sqlite::result::Error(
286         ctx, "dominator_tree: incorrect number of arguments");
287   }
288   auto& agg_ctx = AggCtx::GetOrCreateContextForStep(ctx);
289   auto source = static_cast<uint32_t>(sqlite3_value_int64(argv[0]));
290   auto dest = static_cast<uint32_t>(sqlite3_value_int64(argv[1]));
291   agg_ctx.graph.AddEdge(Node{source}, Node{dest});
292   if (PERFETTO_UNLIKELY(!agg_ctx.start_id)) {
293     agg_ctx.start_id = static_cast<uint32_t>(sqlite3_value_int64(argv[2]));
294   }
295 }
296 
Final(sqlite3_context * ctx)297 void DominatorTree::Final(sqlite3_context* ctx) {
298   auto raw_agg_ctx = AggCtx::GetContextOrNullForFinal(ctx);
299   auto table = std::make_unique<tables::DominatorTreeTable>(GetUserData(ctx));
300   if (auto* agg_ctx = raw_agg_ctx.get(); agg_ctx) {
301     Node start_node{static_cast<uint32_t>(*agg_ctx->start_id)};
302     Graph& graph = agg_ctx->graph;
303     if (start_node.id >= graph.node_id_range()) {
304       return sqlite::result::Error(
305           ctx, "dominator_tree: root node is not in the graph");
306     }
307     Forest forest(graph.node_id_range());
308 
309     // Execute the Lengauer-Tarjan Dominators algorithm to compute the dominator
310     // tree.
311     graph.RunDfs(start_node);
312     if (graph.node_count_in_tree() <= 1) {
313       return sqlite::result::Error(
314           ctx,
315           "dominator_tree: non empty graph must contain root and another node");
316     }
317     graph.ComputeSemiDominatorAndPartialDominator(forest);
318     graph.ComputeDominators();
319     graph.ToTable(table.get(), start_node);
320   }
321   // Take the computed dominator tree and convert it to a table.
322   return sqlite::result::RawPointer(
323       ctx, table.release(), "TABLE",
324       [](void* ptr) { delete static_cast<tables::DominatorTreeTable*>(ptr); });
325 }
326 
327 }  // namespace perfetto::trace_processor
328