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