Commit graph

6 commits

Author SHA1 Message Date
volodya
95fa7ea003
utils/graph: answer subgraph_between_nodes reachability in one pass (#6662)
subgraph_between_nodes() copied the whole graph and then ran a fresh
networkx.has_path() search for every (candidate successor, frontier node)
pair, so its worst case was O(candidate_edges * frontier_nodes * (V + E)).
Negative queries are the expensive ones: a successor that cannot reach any
frontier node forces a full traversal of everything reachable from it, once
per frontier node.

RegionIdentifier._find_initial_loop_nodes() calls this for every loop it
recovers, so a loop head whose successors lead into a large region that only
returns to the head (which the function's own "remove all incoming edges of
the source" step makes unable to reach any latch) makes loop recovery
quadratic. On a 1.1k-node AArch64 CFG built to have that shape, decompilation
spends 40.5s of 52.5s inside 608,847 has_path() calls.

Replace the repeated searches with a single reverse multi-source BFS from the
frontier that stops at the source; membership in the resulting set answers
every reachability question the forward walk asks. Stopping the reverse walk
at the source is exactly equivalent to deleting all incoming edges of the
source, so the graph copy is no longer needed either. Also peel dead leaves
with a degree worklist instead of rescanning all nodes after each removal.

Results are unchanged, including node/edge insertion order and edge
attributes: 6,000 randomized fixed-seed comparisons (3,000 graphs x both
include_frontier modes) against the previous implementation are identical, as
is the decompiler output on the CFG above (52.5s -> 12.5s end to end, 0
has_path() calls).

One behaviour change is deliberate: frontier is now turned into a set before
the "source not in graph or any(node not in graph for node in frontier)"
check rather than after it. In the old order an iterator argument was consumed
by that check, so the subsequent set(frontier) was empty and the function
silently sliced with no frontier at all. Every in-tree caller passes a list or
a set, so no in-tree behaviour changes.
2026-07-22 01:41:36 -07:00
Kevin Phoenix
f939c5b88c
Enable ruff isort rule (#6452)
* Enable ruff isort rule

* [pre-commit.ci] auto fixes from pre-commit.com hooks

for more information, see https://pre-commit.ci

---------

Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
2026-06-02 14:48:07 -07:00
Matt Borgerson
2b88867a23 AIL: Update ailment imports 2025-05-28 16:00:13 -07:00
Matt Borgerson
3f0d72c199 GraphUtils: Test quasi-topological-sort panic mode with different node types 2025-04-09 00:42:05 -07:00
Kevin Phoenix
ee464d0338
Adopt from __future__ import annotations (#4790) 2024-08-28 18:31:43 -07:00
W4terf1re
17d5607d47
Fix bug for DFS when getting dominators (#4414)
* Update graph.py

fix bug for DFS

* add testcase for Dominators

* Update test_graph.py
2024-01-30 03:37:58 -07:00