xref: /aosp_15_r20/external/stg/doc/scc.md (revision 9e3b08ae94a55201065475453d799e8b1378bea6)
1*9e3b08aeSAndroid Build Coastguard Worker# SCC finder core algorithm
2*9e3b08aeSAndroid Build Coastguard Worker
3*9e3b08aeSAndroid Build Coastguard WorkerWhen comparing types recursively, we explore a graph whose nodes are pairs of
4*9e3b08aeSAndroid Build Coastguard Workertypes. Nodes can be visited more than once, and there can even be cycles.
5*9e3b08aeSAndroid Build Coastguard Worker
6*9e3b08aeSAndroid Build Coastguard WorkerThe standard tool for exploring directed graphs is Depth First Search. Dealing
7*9e3b08aeSAndroid Build Coastguard Workerwith cycles in such a graph requires the determination of its Strongly-Connected
8*9e3b08aeSAndroid Build Coastguard WorkerComponents.
9*9e3b08aeSAndroid Build Coastguard Worker
10*9e3b08aeSAndroid Build Coastguard WorkerDFS code for graph traversal is trivial. SCC determination is not and we would
11*9e3b08aeSAndroid Build Coastguard Workerlike it to be provided by a reliable and efficient library component.
12*9e3b08aeSAndroid Build Coastguard Worker
13*9e3b08aeSAndroid Build Coastguard Worker## Core algorithm
14*9e3b08aeSAndroid Build Coastguard Worker
15*9e3b08aeSAndroid Build Coastguard WorkerThere are three commonly-studied asymptotically-optimal approaches to
16*9e3b08aeSAndroid Build Coastguard Workerdetermining the Strongly-Connected Components of a directed graph. Each of these
17*9e3b08aeSAndroid Build Coastguard Workeradmits various optimisations and specialisations for different purposes.
18*9e3b08aeSAndroid Build Coastguard Worker
19*9e3b08aeSAndroid Build Coastguard Worker*   [Kosaraju's algorithm](https://en.wikipedia.org/wiki/Kosaraju%27s_algorithm)
20*9e3b08aeSAndroid Build Coastguard Worker*   [Tarjan's algorithm](https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm)
21*9e3b08aeSAndroid Build Coastguard Worker*   [The path-based algorithm](https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm)
22*9e3b08aeSAndroid Build Coastguard Worker
23*9e3b08aeSAndroid Build Coastguard WorkerKosaraju's algorithm is unsuited to DFS-generated graphs (such as type
24*9e3b08aeSAndroid Build Coastguard Workercomparison graphs) as it requires both forwards and reverse edges to be known
25*9e3b08aeSAndroid Build Coastguard Workerahead of time.
26*9e3b08aeSAndroid Build Coastguard Worker
27*9e3b08aeSAndroid Build Coastguard WorkerTarjan's algorithm can be massaged into the form where it can be separated into
28*9e3b08aeSAndroid Build Coastguard Workera plain DFS traversal and SCC-specific pieces but the resulting code is a bit
29*9e3b08aeSAndroid Build Coastguard Workermessy and responsibility for SCC state management is rather scattered.
30*9e3b08aeSAndroid Build Coastguard Worker
31*9e3b08aeSAndroid Build Coastguard WorkerThe path-based algorithm is the best fit and can be put in a form where the DFS
32*9e3b08aeSAndroid Build Coastguard Workertraversal and SCC state management are cleanly separated. The concept of "open"
33*9e3b08aeSAndroid Build Coastguard Workernodes carries directly over to the implementation used here and SCC state
34*9e3b08aeSAndroid Build Coastguard Workermanagement occurs in two well-defined places.
35*9e3b08aeSAndroid Build Coastguard Worker
36*9e3b08aeSAndroid Build Coastguard Worker*   node visit starts; repeat visits to open nodes are detected
37*9e3b08aeSAndroid Build Coastguard Worker*   node visit end; completed SCCs are detected
38*9e3b08aeSAndroid Build Coastguard Worker
39*9e3b08aeSAndroid Build Coastguard WorkerThe remainder of this document discusses the finer details of what the finder
40*9e3b08aeSAndroid Build Coastguard WorkerAPI should look like.
41*9e3b08aeSAndroid Build Coastguard Worker
42*9e3b08aeSAndroid Build Coastguard Worker## Choice of primitives
43*9e3b08aeSAndroid Build Coastguard Worker
44*9e3b08aeSAndroid Build Coastguard WorkerThe choice of primitives should be determined by the interface priorities:
45*9e3b08aeSAndroid Build Coastguard Worker
46*9e3b08aeSAndroid Build Coastguard Worker*   simple - hard to misuse
47*9e3b08aeSAndroid Build Coastguard Worker*   efficient - does not preclude optimal implementation
48*9e3b08aeSAndroid Build Coastguard Worker*   powerful - no need to code around deficiencies
49*9e3b08aeSAndroid Build Coastguard Worker
50*9e3b08aeSAndroid Build Coastguard WorkerRoughly speaking, the SCC finder "opens" and "closes" nodes and the user code
51*9e3b08aeSAndroid Build Coastguard Workerwill try to open and close nodes at the beginning and end of each node visit.
52*9e3b08aeSAndroid Build Coastguard Worker
53*9e3b08aeSAndroid Build Coastguard Worker### Close
54*9e3b08aeSAndroid Build Coastguard Worker
55*9e3b08aeSAndroid Build Coastguard WorkerThe logic to follow when the SCC finder reports a complete SCC (nodes closed) is
56*9e3b08aeSAndroid Build Coastguard Workersimple: the user code must ensure it considers each node as definitively visited
57*9e3b08aeSAndroid Build Coastguard Workerfrom this point and avoid asking the finder to consider it again. This condition
58*9e3b08aeSAndroid Build Coastguard Workermeans the SCC does not have to independently track "ever visited" status that
59*9e3b08aeSAndroid Build Coastguard Workermay be duplicated elsewhere.
60*9e3b08aeSAndroid Build Coastguard Worker
61*9e3b08aeSAndroid Build Coastguard Worker### Open
62*9e3b08aeSAndroid Build Coastguard Worker
63*9e3b08aeSAndroid Build Coastguard WorkerWhen reaching a node via DFS, the user of the SCC finder has to perform a 3-way
64*9e3b08aeSAndroid Build Coastguard Workerclassification:
65*9e3b08aeSAndroid Build Coastguard Worker
66*9e3b08aeSAndroid Build Coastguard Worker1.  never visited before - the node should immediately transition to open and
67*9e3b08aeSAndroid Build Coastguard Worker    known to the SCC finder
68*9e3b08aeSAndroid Build Coastguard Worker2.  open - the link just followed would create a cycle and the SCC finder
69*9e3b08aeSAndroid Build Coastguard Worker    algorithm needs to do some state maintenance; the user code must not
70*9e3b08aeSAndroid Build Coastguard Worker    recursively process the node
71*9e3b08aeSAndroid Build Coastguard Worker3.  closed - the link just followed reaches a node already fully processed and
72*9e3b08aeSAndroid Build Coastguard Worker    assigned to a SCC; the user code must not recursively process the node
73*9e3b08aeSAndroid Build Coastguard Worker
74*9e3b08aeSAndroid Build Coastguard WorkerThere are at least 3 different ways of structuring program logic to distinguish
75*9e3b08aeSAndroid Build Coastguard Workerthese paths.
76*9e3b08aeSAndroid Build Coastguard Worker
77*9e3b08aeSAndroid Build Coastguard Worker#### Populate user visited state on open
78*9e3b08aeSAndroid Build Coastguard Worker
79*9e3b08aeSAndroid Build Coastguard WorkerNode lifecycle:
80*9e3b08aeSAndroid Build Coastguard Worker
81*9e3b08aeSAndroid Build Coastguard Worker1.  unvisited + not open
82*9e3b08aeSAndroid Build Coastguard Worker2.  visited + open
83*9e3b08aeSAndroid Build Coastguard Worker3.  visited + not open
84*9e3b08aeSAndroid Build Coastguard Worker
85*9e3b08aeSAndroid Build Coastguard WorkerIf a node has never been visited, it can be unconditionally opened. If it has
86*9e3b08aeSAndroid Build Coastguard Workerbeen visited, we must still check if it's open. This is a bit odd in the context
87*9e3b08aeSAndroid Build Coastguard Workerof the SCC algorithm as `really_open` and `is_open` become separate operations
88*9e3b08aeSAndroid Build Coastguard Worker(-simplicity). It's possible that a user could know, for other reasons, whether
89*9e3b08aeSAndroid Build Coastguard Workera visited node is open or not and then omitting to call `is_open` could upset
90*9e3b08aeSAndroid Build Coastguard Workerthe SCC finder (which needs to update its internal state in the already-open
91*9e3b08aeSAndroid Build Coastguard Workercase). This risk could be eliminated by duplicating the state update logic in
92*9e3b08aeSAndroid Build Coastguard Workerboth methods (-efficiency).
93*9e3b08aeSAndroid Build Coastguard Worker
94*9e3b08aeSAndroid Build Coastguard Worker```c++
95*9e3b08aeSAndroid Build Coastguard Workerif (visited) {
96*9e3b08aeSAndroid Build Coastguard Worker  if (is_open(node)) {
97*9e3b08aeSAndroid Build Coastguard Worker    // cycle-breaking back link
98*9e3b08aeSAndroid Build Coastguard Worker    return;
99*9e3b08aeSAndroid Build Coastguard Worker  } else {
100*9e3b08aeSAndroid Build Coastguard Worker    // work-saving link to shared node
101*9e3b08aeSAndroid Build Coastguard Worker    return;
102*9e3b08aeSAndroid Build Coastguard Worker  }
103*9e3b08aeSAndroid Build Coastguard Worker}
104*9e3b08aeSAndroid Build Coastguard Workermark_visited();
105*9e3b08aeSAndroid Build Coastguard Workertoken = really_open(node);
106*9e3b08aeSAndroid Build Coastguard Worker...
107*9e3b08aeSAndroid Build Coastguard Worker// do work
108*9e3b08aeSAndroid Build Coastguard Worker...
109*9e3b08aeSAndroid Build Coastguard Workernodes = close(token);
110*9e3b08aeSAndroid Build Coastguard Workerif (!nodes.empty()) {
111*9e3b08aeSAndroid Build Coastguard Worker  ...
112*9e3b08aeSAndroid Build Coastguard Worker}
113*9e3b08aeSAndroid Build Coastguard Worker```
114*9e3b08aeSAndroid Build Coastguard Worker
115*9e3b08aeSAndroid Build Coastguard Worker#### Check SCC open / closed first
116*9e3b08aeSAndroid Build Coastguard Worker
117*9e3b08aeSAndroid Build Coastguard WorkerNode lifecycle:
118*9e3b08aeSAndroid Build Coastguard Worker
119*9e3b08aeSAndroid Build Coastguard Worker1.  not open + unvisited (never visited)
120*9e3b08aeSAndroid Build Coastguard Worker2.  open (being visited)
121*9e3b08aeSAndroid Build Coastguard Worker3.  not open + visited (closed)
122*9e3b08aeSAndroid Build Coastguard Worker
123*9e3b08aeSAndroid Build Coastguard WorkerThis scheme also requires separate `is_open` and `really_open` operations as
124*9e3b08aeSAndroid Build Coastguard Workernodes musn't be reopened (-simplicity, -efficiency). It does allow the user to
125*9e3b08aeSAndroid Build Coastguard Workermark nodes as visited any time between open and close (-simplicity, +power).
126*9e3b08aeSAndroid Build Coastguard Worker
127*9e3b08aeSAndroid Build Coastguard Worker```c++
128*9e3b08aeSAndroid Build Coastguard Workerif (is_open(node)) {
129*9e3b08aeSAndroid Build Coastguard Worker  // cycle-breaking back link
130*9e3b08aeSAndroid Build Coastguard Worker  return;
131*9e3b08aeSAndroid Build Coastguard Worker}
132*9e3b08aeSAndroid Build Coastguard Workerif (visited) {
133*9e3b08aeSAndroid Build Coastguard Worker  // work-saving link to shared node
134*9e3b08aeSAndroid Build Coastguard Worker  return;
135*9e3b08aeSAndroid Build Coastguard Worker}
136*9e3b08aeSAndroid Build Coastguard Workertoken = really_open(node);
137*9e3b08aeSAndroid Build Coastguard Worker...
138*9e3b08aeSAndroid Build Coastguard Worker// do work
139*9e3b08aeSAndroid Build Coastguard Worker...
140*9e3b08aeSAndroid Build Coastguard Workernodes = close(token);
141*9e3b08aeSAndroid Build Coastguard Workerif (!nodes.empty()) {
142*9e3b08aeSAndroid Build Coastguard Worker  // last chance to call mark_visited
143*9e3b08aeSAndroid Build Coastguard Worker}
144*9e3b08aeSAndroid Build Coastguard Worker```
145*9e3b08aeSAndroid Build Coastguard Worker
146*9e3b08aeSAndroid Build Coastguard Worker#### Test user visited state before open, populate on close
147*9e3b08aeSAndroid Build Coastguard Worker
148*9e3b08aeSAndroid Build Coastguard WorkerNOTE: This is the currently implemented approach.
149*9e3b08aeSAndroid Build Coastguard Worker
150*9e3b08aeSAndroid Build Coastguard WorkerNode lifecycle:
151*9e3b08aeSAndroid Build Coastguard Worker
152*9e3b08aeSAndroid Build Coastguard Worker1.  unvisited + not open
153*9e3b08aeSAndroid Build Coastguard Worker2.  unvisited + open
154*9e3b08aeSAndroid Build Coastguard Worker3.  visited + not open
155*9e3b08aeSAndroid Build Coastguard Worker
156*9e3b08aeSAndroid Build Coastguard WorkerThis is the purest form of the algorithm with the `open` and `close` operations
157*9e3b08aeSAndroid Build Coastguard Workerclearly bracketing "real" work. `really_open` and `is_open` operations are
158*9e3b08aeSAndroid Build Coastguard Workerfolded together into `open` which will fail to open an already open node
159*9e3b08aeSAndroid Build Coastguard Worker(+simplicity, +efficiency).
160*9e3b08aeSAndroid Build Coastguard Worker
161*9e3b08aeSAndroid Build Coastguard WorkerThe user value store cannot be something that gets initialised and updated
162*9e3b08aeSAndroid Build Coastguard Workerbetween open and close or it will also need to duplicate the open/closed state
163*9e3b08aeSAndroid Build Coastguard Workerand this correspondence will need to be maintained as an invariant (-power).
164*9e3b08aeSAndroid Build Coastguard Worker
165*9e3b08aeSAndroid Build Coastguard Worker```c++
166*9e3b08aeSAndroid Build Coastguard Workerif (visited) {
167*9e3b08aeSAndroid Build Coastguard Worker  // work-saving link to shared node
168*9e3b08aeSAndroid Build Coastguard Worker  return;
169*9e3b08aeSAndroid Build Coastguard Worker}
170*9e3b08aeSAndroid Build Coastguard Workertoken = open(node);
171*9e3b08aeSAndroid Build Coastguard Workerif (!token) {
172*9e3b08aeSAndroid Build Coastguard Worker  // cycle-breaking back link
173*9e3b08aeSAndroid Build Coastguard Worker  return;
174*9e3b08aeSAndroid Build Coastguard Worker}
175*9e3b08aeSAndroid Build Coastguard Worker...
176*9e3b08aeSAndroid Build Coastguard Worker// do work
177*9e3b08aeSAndroid Build Coastguard Worker...
178*9e3b08aeSAndroid Build Coastguard Workernodes = close(token.value());
179*9e3b08aeSAndroid Build Coastguard Workerif (!nodes.empty()) {
180*9e3b08aeSAndroid Build Coastguard Worker  ...
181*9e3b08aeSAndroid Build Coastguard Worker  mark_visited();
182*9e3b08aeSAndroid Build Coastguard Worker}
183*9e3b08aeSAndroid Build Coastguard Worker```
184*9e3b08aeSAndroid Build Coastguard Worker
185*9e3b08aeSAndroid Build Coastguard WorkerEvaluating equality can be made to fit nicely into this category, as can using
186*9e3b08aeSAndroid Build Coastguard Workerequality along with the return stack to build a diff tree with stubs for
187*9e3b08aeSAndroid Build Coastguard Workersharing- and cycle-breaking links.
188*9e3b08aeSAndroid Build Coastguard Worker
189*9e3b08aeSAndroid Build Coastguard WorkerHowever, building a graph (say a copy of the traversal, or a diff graph)
190*9e3b08aeSAndroid Build Coastguard Workerrequires open node state to be squirrelled away somewhere.
191*9e3b08aeSAndroid Build Coastguard Worker
192*9e3b08aeSAndroid Build Coastguard Worker##### Enhancement
193*9e3b08aeSAndroid Build Coastguard Worker
194*9e3b08aeSAndroid Build Coastguard WorkerThe SCC finder data structure can be made to carry values associated with open
195*9e3b08aeSAndroid Build Coastguard Workernodes and hand them to the user on failure-to-open and closure. This allows us
196*9e3b08aeSAndroid Build Coastguard Workerto retain purity and regain the ability to maintain simple state for open nodes
197*9e3b08aeSAndroid Build Coastguard Workerseparately from that for closed nodes, at the expense of a slightly
198*9e3b08aeSAndroid Build Coastguard Workerheavier-touch interface (+power).
199*9e3b08aeSAndroid Build Coastguard Worker
200*9e3b08aeSAndroid Build Coastguard WorkerIn the simplest case, we'd want nothing stored at all (beyond the node identity)
201*9e3b08aeSAndroid Build Coastguard Workerand actually supplying a second empty type would be an annoyance and an
202*9e3b08aeSAndroid Build Coastguard Workerinefficiency (-simplicity, -power, -efficiency)). So the best thing to supply is
203*9e3b08aeSAndroid Build Coastguard Workerthe user's container's `value_type` and associated `value_compare` comparator.
204*9e3b08aeSAndroid Build Coastguard Worker
205*9e3b08aeSAndroid Build Coastguard WorkerHowever, in this variation, it's painful to set up the SCC structures for
206*9e3b08aeSAndroid Build Coastguard Workerefficient `open` as nodes need to exist in a map or set, independently of any
207*9e3b08aeSAndroid Build Coastguard Workerpayload. The approach could be revisited if there's a solution to this.
208*9e3b08aeSAndroid Build Coastguard Worker
209*9e3b08aeSAndroid Build Coastguard Worker```c++
210*9e3b08aeSAndroid Build Coastguard Workerif (visited) {
211*9e3b08aeSAndroid Build Coastguard Worker  // work-saving link to shared node
212*9e3b08aeSAndroid Build Coastguard Worker  return;
213*9e3b08aeSAndroid Build Coastguard Worker}
214*9e3b08aeSAndroid Build Coastguard Worker[&node_state, token] = open(node_state);
215*9e3b08aeSAndroid Build Coastguard Workerif (!token) {
216*9e3b08aeSAndroid Build Coastguard Worker  // cycle-breaking back link
217*9e3b08aeSAndroid Build Coastguard Worker  return;
218*9e3b08aeSAndroid Build Coastguard Worker}
219*9e3b08aeSAndroid Build Coastguard Worker...
220*9e3b08aeSAndroid Build Coastguard Worker// do work, update node_state if you like
221*9e3b08aeSAndroid Build Coastguard Worker...
222*9e3b08aeSAndroid Build Coastguard Workernode_states = close(token.value())
223*9e3b08aeSAndroid Build Coastguard Workerif (!node_states.empty()) {
224*9e3b08aeSAndroid Build Coastguard Worker  ...
225*9e3b08aeSAndroid Build Coastguard Worker  mark_visited();
226*9e3b08aeSAndroid Build Coastguard Worker}
227*9e3b08aeSAndroid Build Coastguard Worker```
228