VapourSynth-llvmexpr
Loading...
Searching...
No Matches
StructurizeCFGPass.cpp
Go to the documentation of this file.
1
19
22#include "BlockAnalysisPass.hpp"
23#include "StackSafetyPass.hpp"
24
26
27#include <algorithm>
28#include <cstdint>
29#include <deque>
30#include <limits>
31#include <stack>
32#include <vector>
33
34namespace analysis {
35
36namespace {
37
38using Bitset = analysis::dynamic_bitset::DynamicBitset;
39
40std::vector<int> compute_reachable(const std::vector<CFGBlock>& cfg) {
41 std::vector<int> reachable(cfg.size(), 0);
42 if (cfg.empty()) {
43 return reachable;
44 }
45 std::deque<int> q;
46 reachable[0] = 1;
47 q.push_back(0);
48 while (!q.empty()) {
49 int u = q.front();
50 q.pop_front();
51 for (int v : cfg[u].successors) {
52 if (v >= 0 && static_cast<size_t>(v) < cfg.size() &&
53 reachable[v] == 0) {
54 reachable[v] = 1;
55 q.push_back(v);
56 }
57 }
58 }
59 return reachable;
60}
61
62struct SCCDecomposition {
63 std::vector<int> comp; // node -> component id, -1 unreachable
64 std::vector<std::vector<int>> nodes; // component id -> nodes
65};
66
67SCCDecomposition compute_scc_kosaraju(const std::vector<CFGBlock>& cfg,
68 const std::vector<int>& reachable) {
69 int n = (int)cfg.size();
70 std::vector<std::vector<int>> g(n);
71 std::vector<std::vector<int>> gr(n);
72 for (int u = 0; u < n; ++u) {
73 if (reachable[u] == 0) {
74 continue;
75 }
76 for (int v : cfg[u].successors) {
77 if (v >= 0 && v < n && reachable[v] != 0) {
78 g[u].push_back(v);
79 gr[v].push_back(u);
80 }
81 }
82 }
83
84 std::vector<int> order;
85 order.reserve((size_t)n);
86 std::vector<int> vis(n, 0);
87 for (int i = 0; i < n; ++i) {
88 if (reachable[i] == 0 || vis[i] != 0) {
89 continue;
90 }
91 std::stack<std::pair<int, size_t>> st;
92 st.emplace(i, 0);
93 vis[i] = 1;
94 while (!st.empty()) {
95 auto& [u, idx] = st.top();
96 if (idx < g[u].size()) {
97 int v = g[u][idx++];
98 if (vis[v] == 0) {
99 vis[v] = 1;
100 st.emplace(v, 0);
101 }
102 } else {
103 order.push_back(u);
104 st.pop();
105 }
106 }
107 }
108
109 std::vector<int> comp(n, -1);
110 int comp_count = 0;
111 for (int k = (int)order.size() - 1; k >= 0; --k) {
112 int v0 = order[(size_t)k];
113 if (comp[v0] != -1) {
114 continue;
115 }
116 std::deque<int> q;
117 q.push_back(v0);
118 comp[v0] = comp_count;
119 while (!q.empty()) {
120 int v = q.front();
121 q.pop_front();
122 for (int p : gr[v]) {
123 if (comp[p] == -1) {
124 comp[p] = comp_count;
125 q.push_back(p);
126 }
127 }
128 }
129 comp_count++;
130 }
131
132 std::vector<std::vector<int>> nodes((size_t)comp_count);
133 for (int i = 0; i < n; ++i) {
134 if (reachable[i] == 0 || comp[i] < 0) {
135 continue;
136 }
137 nodes[(size_t)comp[i]].push_back(i);
138 }
139
140 return {.comp = std::move(comp), .nodes = std::move(nodes)};
141}
142
143bool scc_is_cyclic(const std::vector<CFGBlock>& cfg,
144 const std::vector<int>& reachable,
145 const std::vector<int>& scc_nodes) {
146 if (scc_nodes.empty()) {
147 return false;
148 }
149 if (scc_nodes.size() > 1) {
150 return true;
151 }
152 int u = scc_nodes[0];
153 if (reachable[u] == 0) {
154 return false;
155 }
156 return std::ranges::any_of(cfg[u].successors,
157 [u](int v) { return v == u; });
158}
159
160std::vector<int> scc_entry_nodes(const std::vector<CFGBlock>& cfg,
161 const std::vector<int>& reachable,
162 const SCCDecomposition& scc, int cid) {
163 std::vector<int> entry;
164 for (int v : scc.nodes[(size_t)cid]) {
165 if (reachable[v] == 0) {
166 continue;
167 }
168 for (int p : cfg[v].predecessors) {
169 if (p < 0 || static_cast<size_t>(p) >= cfg.size()) {
170 continue;
171 }
172 if (reachable[p] == 0) {
173 continue;
174 }
175 if (scc.comp[p] != cid) {
176 entry.push_back(v);
177 break;
178 }
179 }
180 }
181 std::ranges::sort(entry);
182 auto ret = std::ranges::unique(entry);
183 entry.erase(ret.begin(), ret.end());
184 return entry;
185}
186
187bool check_reducible(const std::vector<CFGBlock>& cfg,
188 const std::vector<int>& reachable) {
189 int n = (int)cfg.size();
190 if (n == 0) {
191 return true;
192 }
193
194 auto scc = compute_scc_kosaraju(cfg, reachable);
195 for (size_t cid = 0; cid < scc.nodes.size(); ++cid) {
196 const auto& nodes = scc.nodes[cid];
197 if (!scc_is_cyclic(cfg, reachable, nodes)) {
198 continue;
199 }
200 // Reducible iff the SCC has at most one entry node.
201 auto entry_nodes = scc_entry_nodes(cfg, reachable, scc, (int)cid);
202 if (entry_nodes.size() > 1) {
203 return false;
204 }
205 }
206 return true;
207}
208
209void rebuild_predecessors(std::vector<CFGBlock>& cfg) {
210 for (auto& b : cfg) {
211 b.predecessors.clear();
212 }
213 for (size_t u = 0; u < cfg.size(); ++u) {
214 for (int v : cfg[u].successors) {
215 if (v >= 0 && static_cast<size_t>(v) < cfg.size()) {
216 cfg[(size_t)v].predecessors.push_back((int)u);
217 }
218 }
219 }
220}
221
222bool node_split_make_reducible(std::vector<CFGBlock>& cfg,
223 std::vector<int>& origin_map,
224 size_t max_blocks) {
225 if (cfg.empty()) {
226 return false;
227 }
228
229 bool changed_any = false;
230 // Iterate because splitting one SCC can expose/alter others.
231 // Bound iterations to avoid pathological cases.
232 // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
233 for (int iter = 0; iter < 128; ++iter) {
234 rebuild_predecessors(cfg);
235 auto reachable = compute_reachable(cfg);
236 auto scc = compute_scc_kosaraju(cfg, reachable);
237
238 bool changed_this_iter = false;
239 for (size_t cid = 0; cid < scc.nodes.size(); ++cid) {
240 const auto& comp_nodes = scc.nodes[cid];
241 if (!scc_is_cyclic(cfg, reachable, comp_nodes)) {
242 continue;
243 }
244 auto entry_nodes = scc_entry_nodes(cfg, reachable, scc, (int)cid);
245 if (entry_nodes.size() <= 1) {
246 continue;
247 }
248
249 // Heuristic: keep the first entry node as the "primary" entry of
250 // the original SCC, and duplicate the SCC for each other entry.
251 int primary_entry = entry_nodes.front();
252 (void)primary_entry;
253
254 std::vector<uint8_t> in_component(cfg.size(), 0);
255 for (int v : comp_nodes) {
256 if (v >= 0 && static_cast<size_t>(v) < in_component.size()) {
257 in_component[(size_t)v] = 1;
258 }
259 }
260
261 int original_node_count = (int)cfg.size();
262
263 for (size_t ei = 1; ei < entry_nodes.size(); ++ei) {
264 int entry = entry_nodes[ei];
265
266 if (cfg.size() + comp_nodes.size() > max_blocks) {
267 return changed_any;
268 }
269
270 std::vector<int> clone_of((size_t)original_node_count, -1);
271
272 // Create clones for all nodes in this SCC.
273 for (int v : comp_nodes) {
274 CFGBlock nb = cfg[(size_t)v];
275 nb.successors.clear();
276 nb.predecessors.clear();
277
278 int new_idx = (int)cfg.size();
279 cfg.push_back(std::move(nb));
280 origin_map.push_back(origin_map[(size_t)v]);
281 if (v >= 0 && v < original_node_count) {
282 clone_of[(size_t)v] = new_idx;
283 }
284 }
285
286 // Wire clone successors (internal edges are remapped to clones,
287 // external edges keep their target).
288 for (int v : comp_nodes) {
289 int v_clone = clone_of[(size_t)v];
290 if (v_clone < 0) {
291 continue;
292 }
293 for (int s : cfg[(size_t)v].successors) {
294 if (s >= 0 && s < original_node_count &&
295 in_component[(size_t)s] != 0) {
296 cfg[(size_t)v_clone].successors.push_back(
297 clone_of[(size_t)s]);
298 } else {
299 cfg[(size_t)v_clone].successors.push_back(s);
300 }
301 }
302 }
303
304 // Redirect all external incoming edges to `entry` to the clone.
305 int entry_clone = clone_of[(size_t)entry];
306 for (int p = 0; p < original_node_count; ++p) {
307 if (reachable[(size_t)p] == 0) {
308 continue;
309 }
310 if (in_component[(size_t)p] != 0) {
311 continue; // keep SCC-internal edges pointing to original
312 }
313 std::ranges::replace(cfg[(size_t)p].successors, entry,
314 entry_clone);
315 }
316 }
317
318 changed_this_iter = true;
319 changed_any = true;
320 break; // CFG changed; restart SCC discovery
321 }
322
323 if (!changed_this_iter) {
324 break;
325 }
326 }
327 return changed_any;
328}
329
330bool tail_duplicate_trivial_joins(std::vector<CFGBlock>& cfg,
331 std::vector<int>& origin_map,
332 size_t max_blocks) {
333 if (cfg.empty()) {
334 return false;
335 }
336
337 rebuild_predecessors(cfg);
338 auto reachable = compute_reachable(cfg);
339 auto scc = compute_scc_kosaraju(cfg, reachable);
340
341 std::vector<uint8_t> cyclic(cfg.size(), 0);
342 for (const auto& comp_nodes : scc.nodes) {
343 if (!scc_is_cyclic(cfg, reachable, comp_nodes)) {
344 continue;
345 }
346 for (int v : comp_nodes) {
347 if (v >= 0 && static_cast<size_t>(v) < cyclic.size()) {
348 cyclic[(size_t)v] = 1;
349 }
350 }
351 }
352
353 bool changed = false;
354 int original_n = (int)cfg.size();
355
356 // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers)
357 constexpr int K_MAX_TRIVIAL_TOKENS = 6;
358
359 for (int c = 1; c < original_n; ++c) {
360 if (reachable[(size_t)c] == 0) {
361 continue;
362 }
363 if (cyclic[(size_t)c] != 0) {
364 continue;
365 }
366 // Do not duplicate terminal blocks.
367 if (cfg[(size_t)c].successors.empty()) {
368 continue;
369 }
370 if (cfg[(size_t)c].successors.size() > 1) {
371 continue;
372 }
373 if (cfg[(size_t)c].predecessors.size() < 2) {
374 continue;
375 }
376 int tok_count =
377 cfg[(size_t)c].end_token_idx - cfg[(size_t)c].start_token_idx;
378 if (tok_count > K_MAX_TRIVIAL_TOKENS) {
379 continue;
380 }
381
382 auto preds = cfg[(size_t)c].predecessors;
383 std::ranges::sort(preds);
384 auto ret = std::ranges::unique(preds);
385 preds.erase(ret.begin(), ret.end());
386
387 for (int p : preds) {
388 if (cfg.size() + 1 > max_blocks) {
389 rebuild_predecessors(cfg);
390 return changed;
391 }
392
393 CFGBlock clone = cfg[(size_t)c];
394 clone.predecessors.clear();
395
396 int clone_idx = (int)cfg.size();
397 cfg.push_back(std::move(clone));
398 origin_map.push_back(origin_map[(size_t)c]);
399
400 std::ranges::replace(cfg[(size_t)p].successors, c, clone_idx);
401 }
402
403 changed = true;
404 }
405
406 rebuild_predecessors(cfg);
407 return changed;
408}
409
410std::vector<Bitset> compute_dominators(const std::vector<CFGBlock>& cfg,
411 const std::vector<int>& reachable) {
412 int n = (int)cfg.size();
413 std::vector<Bitset> dom;
414 dom.reserve((size_t)n);
415 for (int i = 0; i < n; ++i) {
416 dom.emplace_back(n);
417 }
418 if (n == 0) {
419 return dom;
420 }
421
422 auto init = [&]() {
423 for (int i = 0; i < n; ++i) {
424 if (!reachable[i]) {
425 dom[i].resetAll();
426 dom[i].set(i);
427 } else {
428 dom[i].setAll();
429 }
430 }
431 dom[0].resetAll();
432 dom[0].set(0);
433 };
434
435 auto compute_for_node = [&](int i) -> Bitset {
436 Bitset new_dom(n);
437 new_dom.setAll();
438 if (cfg[i].predecessors.empty()) {
439 new_dom.resetAll();
440 } else {
441 for (int p : cfg[i].predecessors) {
442 if (!reachable[p]) {
443 continue;
444 }
445 new_dom.intersectWith(dom[p]);
446 }
447 }
448 new_dom.set(i);
449 return new_dom;
450 };
451
452 init();
453
454 bool changed = true;
455 while (changed) {
456 changed = false;
457 for (int i = 1; i < n; ++i) {
458 if (reachable[i] == 0) {
459 continue;
460 }
461 Bitset new_dom = compute_for_node(i);
462 if (!new_dom.equals(dom[i])) {
463 dom[i] = std::move(new_dom);
464 changed = true;
465 }
466 }
467 }
468 return dom;
469}
470
471// Post-dominators with a virtual exit node at index n.
472std::vector<Bitset> compute_postdominators(const std::vector<CFGBlock>& cfg,
473 const std::vector<int>& reachable) {
474 int n = (int)cfg.size();
475 int node_count = n + 1; // including virtual exit node
476
477 std::vector<std::vector<int>> succ((size_t)node_count);
478 std::vector<std::vector<int>> pred((size_t)node_count);
479
480 auto build_reverse_graph = [&]() {
481 for (int i = 0; i < n; ++i) {
482 if (!reachable[i]) {
483 continue;
484 }
485 for (int s : cfg[i].successors) {
486 if (s >= 0 && s < n && reachable[s]) {
487 succ[i].push_back(s);
488 pred[s].push_back(i);
489 }
490 }
491 if (succ[i].empty()) {
492 succ[i].push_back(n);
493 pred[n].push_back(i);
494 }
495 }
496 };
497
498 auto init = [&]() {
499 std::vector<Bitset> pdom;
500 pdom.reserve((size_t)node_count);
501 for (int i = 0; i < node_count; ++i) {
502 pdom.emplace_back(node_count);
503 }
504 for (int i = 0; i < node_count; ++i) {
505 pdom[i].setAll();
506 }
507 pdom[n].resetAll();
508 pdom[n].set(n);
509 return pdom;
510 };
511
512 build_reverse_graph();
513
514 std::vector<Bitset> pdom = init();
515
516 auto compute_for_node = [&](int i) -> Bitset {
517 Bitset new_pdom(node_count);
518 new_pdom.setAll();
519 for (int s : succ[i]) {
520 new_pdom.intersectWith(pdom[s]);
521 }
522 new_pdom.set(i);
523 return new_pdom;
524 };
525
526 bool changed = true;
527 while (changed) {
528 changed = false;
529 for (int i = 0; i < n; ++i) {
530 if (reachable[i] == 0) {
531 continue;
532 }
533 Bitset new_pdom = compute_for_node(i);
534 if (!new_pdom.equals(pdom[i])) {
535 pdom[i] = std::move(new_pdom);
536 changed = true;
537 }
538 }
539 }
540
541 return pdom;
542}
543
544int compute_ipdom_for_node(int node, const std::vector<Bitset>& pdom) {
545 // ipdom(node) is the closest strict post-dominator of node in the
546 // post-dominator tree.
547 int node_count = static_cast<int>(pdom.size());
548 // Collect strict post-dominators.
549 std::vector<int> strict;
550 strict.reserve((size_t)node_count);
551 for (int i = 0; i < node_count; ++i) {
552 if (i != node && pdom[node].test(i)) {
553 strict.push_back(i);
554 }
555 }
556 if (strict.empty()) {
557 return -1;
558 }
559
560 // Choose the closest strict post-dominator.
561 // For post-dominators, "A post-dominates B" iff A ∈ pdom[B].
562 // The immediate post-dominator of `node` is the strict post-dominator that
563 // does NOT post-dominate any other strict post-dominator of `node`.
564 int best = -1;
565 for (int c : strict) {
566 bool postdominates_other = false;
567 for (int other : strict) {
568 if (other == c) {
569 continue;
570 }
571 // If c post-dominates `other`, then c cannot be the immediate
572 // post-dominator of `node`
573 if (pdom[other].test(c)) {
574 postdominates_other = true;
575 break;
576 }
577 }
578 if (!postdominates_other) {
579 best = c;
580 break;
581 }
582 }
583 return best;
584}
585
586std::vector<int> compute_ipdom_vector(const std::vector<Bitset>& pdom,
587 int real_node_count,
588 const std::vector<int>& reachable) {
589 std::vector<int> ipdom(real_node_count, -1);
590 int exit_node = real_node_count;
591 for (int i = 0; i < real_node_count; ++i) {
592 if (reachable[i] == 0) {
593 continue;
594 }
595 int ip = compute_ipdom_for_node(i, pdom);
596 ipdom[i] = (ip == exit_node) ? -1 : ip;
597 }
598 return ipdom;
599}
600
601void compute_natural_loops(StructurizeCFGResult& result,
602 const std::vector<CFGBlock>& cfg,
603 const std::vector<int>& reachable,
604 const std::vector<Bitset>& dom) {
605 int n = (int)cfg.size();
606 for (int u = 0; u < n; ++u) {
607 if (reachable[u] == 0) {
608 continue;
609 }
610 for (int h : cfg[u].successors) {
611 if (h < 0 || h >= n || reachable[h] == 0) {
612 continue;
613 }
614 if (!dom[u].test(h)) {
615 continue;
616 }
617 auto& body = result.loop_body[h];
618 std::deque<int> work;
619 body.insert(h);
620 if (!body.contains(u)) {
621 body.insert(u);
622 work.push_back(u);
623 }
624 while (!work.empty()) {
625 int x = work.front();
626 work.pop_front();
627 for (int p : cfg[x].predecessors) {
628 if (reachable[p] == 0) {
629 continue;
630 }
631 if (!body.contains(p)) {
632 body.insert(p);
633 work.push_back(p);
634 }
635 }
636 }
637 }
638 }
639}
640
641void compute_loop_follow(StructurizeCFGResult& result) {
642 for (auto& [header, body] : result.loop_body) {
643 int f =
644 (header >= 0 && static_cast<size_t>(header) < result.ipdom.size())
645 ? result.ipdom[header]
646 : -1;
647 while (f != -1 && body.contains(f)) {
648 f = result.ipdom[f];
649 }
650 result.loop_follow[header] = f;
651 }
652}
653
654void compute_innermost_loop_header(StructurizeCFGResult& result,
655 const std::vector<int>& reachable) {
656 int n = (int)result.innermost_loop_header.size();
657 for (int b = 0; b < n; ++b) {
658 if (reachable[b] == 0) {
659 continue;
660 }
661 int best_header = -1;
662 size_t best_size = std::numeric_limits<size_t>::max();
663 for (const auto& [header, body] : result.loop_body) {
664 if (body.contains(b) && body.size() < best_size) {
665 best_size = body.size();
666 best_header = header;
667 }
668 }
669 result.innermost_loop_header[b] = best_header;
670 }
671}
672
673} // namespace
674
676StructurizeCFGPass::run(const std::vector<Token>& /*unused*/,
677 AnalysisManager& am) {
678 const auto& block_result = am.getResult<BlockAnalysisPass>();
679 const auto& cfg = block_result.cfg_blocks;
680 const auto& stack_safety = am.getResult<StackSafetyPass>();
681
682 Result result;
683
684 if (cfg.empty()) {
685 return result;
686 }
687
688 const auto reachable0 = compute_reachable(cfg);
689 const bool reducible0 = check_reducible(cfg, reachable0);
690
691 const std::vector<CFGBlock>* analysis_cfg = &cfg;
692
693 std::vector<CFGBlock> work_cfg = cfg;
694 std::vector<int> origin_map(cfg.size());
695 for (size_t i = 0; i < cfg.size(); ++i) {
696 origin_map[i] = static_cast<int>(i);
697 }
698
699 constexpr size_t MAX_BLOCKS = 256;
700 constexpr size_t MAX_BLOCKS_RATIO = 8;
701 size_t max_blocks =
702 std::max<size_t>(cfg.size() * MAX_BLOCKS_RATIO, MAX_BLOCKS);
703
704 bool changed =
705 tail_duplicate_trivial_joins(work_cfg, origin_map, max_blocks);
706 if (!reducible0) {
707 changed |= node_split_make_reducible(work_cfg, origin_map, max_blocks);
708 }
709
710 if (changed) {
711 result.structured_cfg_blocks = std::move(work_cfg);
712 result.structured_block_origin = std::move(origin_map);
713 analysis_cfg = &result.structured_cfg_blocks;
714
715 result.structured_stack_depth_in.resize(
716 result.structured_cfg_blocks.size(), -1);
717 for (size_t i = 0; i < result.structured_cfg_blocks.size(); ++i) {
718 int orig = result.structured_block_origin[i];
719 if (orig >= 0 && static_cast<size_t>(orig) <
720 stack_safety.stack_depth_in.size()) {
721 result.structured_stack_depth_in[i] =
722 stack_safety.stack_depth_in[(size_t)orig];
723 }
724 }
725 }
726
727 const auto reachable = compute_reachable(*analysis_cfg);
728 result.reducible = check_reducible(*analysis_cfg, reachable);
729 result.success = result.reducible;
730
731 result.ipdom.assign(analysis_cfg->size(), -1);
732 result.innermost_loop_header.assign(analysis_cfg->size(), -1);
733
734 const auto dom = compute_dominators(*analysis_cfg, reachable);
735 const auto pdom = compute_postdominators(*analysis_cfg, reachable);
736
737 int n = (int)analysis_cfg->size();
738 result.ipdom = compute_ipdom_vector(pdom, n, reachable);
739
740 compute_natural_loops(result, *analysis_cfg, reachable, dom);
741 compute_loop_follow(result);
742 compute_innermost_loop_header(result, reachable);
743
744 return result;
745}
746
747} // namespace analysis
Result run(const std::vector< Token > &tokens, AnalysisManager &am) override
std::vector< int > predecessors
std::vector< int > successors