1/* 2 * Copyright (c) 2024-2025 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16import { BaseNode } from './BaseExplicitGraph'; 17import { NodeID, GraphTraits } from './GraphTraits'; 18 19type NodeSet = Set<NodeID>; 20type NodeStack = NodeID[]; 21type Node2RepSCCInfoMap = Map<NodeID, NodeSCCInfo>; 22type Node2NodeMap = Map<NodeID, NodeID>; 23 24/** 25 * Basic SCC info for a single node 26 */ 27class NodeSCCInfo { 28 private _rep: NodeID; 29 private _subNodes: NodeSet; 30 31 constructor() { 32 this._rep = Number.MAX_SAFE_INTEGER; 33 this._subNodes = new Set(); 34 } 35 36 get rep(): NodeID { 37 return this._rep; 38 } 39 40 set rep(n: NodeID) { 41 this._rep = n; 42 } 43 44 addSubNodes(n: NodeID): void { 45 this._subNodes.add(n); 46 } 47 48 get subNodes(): NodeSet { 49 return this._subNodes; 50 } 51} 52 53/** 54 * Detect strongly connected components in a directed graph 55 * A topological graph is an extra product from this algorithm 56 * Advanced Nuutila’s algorithm which come from the following paper: 57 * Wave Propagation and Deep Propagation for pointer Analysis 58 * CGO 2009 59 */ 60export class SCCDetection<Graph extends GraphTraits<BaseNode>> { 61 // graph G = (V, E) 62 private _G: Graph; 63 // counter 64 private _I: number; 65 // map of V to {1, . . . , |V |} ∪ ⊥, associates the 66 // nodes in V to the order in which they are visited by 67 // Nuutila’s algorithm. Initially, D(v) = ⊥. 68 private _D: Node2NodeMap; 69 // map of V to V , associates each node in a cycle to 70 // the representative of that cycle. Initially R(v) = v. 71 private _R: Node2RepSCCInfoMap; 72 // stack of V, holds the nodes that are in a cycle, but 73 // have not yet been inserted into C. Initially empty 74 private _S: NodeStack; 75 // stack of V , holds the nodes of V that are represenatives 76 // of strongly connected components. T keeps the 77 // nodes in topological order, that is, the top node has no 78 // predecessors. Initially empty 79 private _T: NodeStack; 80 private repNodes: NodeSet; 81 private visitedNodes: Set<NodeID>; 82 private inSCCNodes: Set<NodeID>; 83 84 constructor(GT: Graph) { 85 this._G = GT; 86 this._I = 0; 87 this._D = new Map<NodeID, NodeID>(); 88 this._S = new Array<NodeID>(); 89 this._T = new Array<NodeID>(); 90 this.repNodes = new Set<NodeID>(); 91 this._R = new Map<number, NodeSCCInfo>(); 92 this.visitedNodes = new Set(); 93 this.inSCCNodes = new Set(); 94 } 95 96 private isVisited(n: NodeID): boolean { 97 return this.visitedNodes.has(n); 98 } 99 100 private inSCC(n: NodeID): boolean { 101 return this.inSCCNodes.has(n); 102 } 103 104 private setVisited(n: NodeID): void { 105 this.visitedNodes.add(n); 106 } 107 108 private setInSCC(n: NodeID): void { 109 this.inSCCNodes.add(n); 110 } 111 112 private setRep(n: NodeID, r: NodeID): void { 113 let sccIn = this._R.get(n); 114 if (!sccIn) { 115 sccIn = new NodeSCCInfo(); 116 this._R.set(n, sccIn); 117 } 118 sccIn.rep = r; 119 120 let rInfo = this._R.get(r); 121 if (!rInfo) { 122 rInfo = new NodeSCCInfo(); 123 this._R.set(r, rInfo); 124 } 125 rInfo.addSubNodes(n); 126 if (n !== r) { 127 sccIn.subNodes.clear(); 128 this.repNodes.add(r); 129 } 130 } 131 132 private getRep(n: NodeID): NodeID { 133 let info = this._R.get(n); 134 if (!info) { 135 info = new NodeSCCInfo(); 136 this._R.set(n, info); 137 } 138 return info.rep; 139 } 140 141 private getNode(id: NodeID): BaseNode { 142 let n = this._G.getNode(id); 143 if (!n) { 144 throw new Error('Node is not found'); 145 } 146 return n; 147 } 148 149 private visit(v: NodeID): void { 150 this._I += 1; 151 this._D.set(v, this._I); 152 this.setRep(v, v); 153 this.setVisited(v); 154 155 let node = this.getNode(v); 156 node.getOutgoingEdges().forEach(e => { 157 let w: NodeID = e.getDstID(); 158 if (!this.isVisited(w)) { 159 this.visit(w); 160 } 161 162 if (!this.inSCC(w)) { 163 let repV = this.getRep(v); 164 let repW = this.getRep(w); 165 if (!this._D.has(repV) || !this._D.has(repW)) { 166 throw new Error('Error happening in SCC detection'); 167 } 168 let rep = this._D.get(repV)! < this._D.get(repW)! ? repV : repW; 169 this.setRep(v, rep); 170 } 171 }); 172 173 if (this.getRep(v) === v) { 174 this.setInSCC(v); 175 while (this._S.length > 0) { 176 let w = this._S[this._S.length - 1]!; 177 if (this._D.get(w)! <= this._D.get(v)!) { 178 break; 179 } else { 180 this._S.pop(); 181 this.setInSCC(w); 182 this.setRep(w, v); 183 } 184 } 185 this._T.push(v); 186 } else { 187 this._S.push(v); 188 } 189 } 190 191 private clear(): void { 192 this._R.clear(); 193 this._I = 0; 194 this._D.clear(); 195 this.repNodes.clear(); 196 this._S.length = 0; 197 this._T.length = 0; 198 this.inSCCNodes.clear(); 199 this.visitedNodes.clear(); 200 } 201 202 /** 203 * Get the rep node 204 * If not found return itself 205 */ 206 public getRepNode(n: NodeID): NodeID { 207 const it = this._R.get(n); 208 if (!it) { 209 throw new Error('scc rep not found'); 210 } 211 const rep = it.rep; 212 return rep !== Number.MAX_SAFE_INTEGER ? rep : n; 213 } 214 215 /** 216 * Start to detect and collapse SCC 217 */ 218 public find(): void { 219 this.clear(); 220 let nodeIt = this._G.nodesItor(); 221 for (let node of nodeIt) { 222 const nodeId: NodeID = node.getID(); 223 if (!this.isVisited(nodeId) && !this._D.has(nodeId)) { 224 this.visit(nodeId); 225 } 226 } 227 } 228 229 public getTopoAndCollapsedNodeStack(): NodeStack { 230 return this._T; 231 } 232 233 public getNode2SCCInfoMap(): Node2RepSCCInfoMap { 234 return this._R; 235 } 236 237 // whether the node is in a cycle 238 public nodeIsInCycle(n: NodeID): boolean { 239 const rep = this.getRepNode(n); 240 const subNodesCount = this.getSubNodes(rep).size; 241 // multi-node cycle 242 if (subNodesCount > 1) { 243 return true; 244 } 245 // self-cycle: a call a 246 let repNode = this._G.getNode(rep)!; 247 for (const e of repNode?.getOutgoingEdges()) { 248 if (e.getDstID() === rep) { 249 return true; 250 } 251 } 252 return false; 253 } 254 255 public getMySCCNodes(n: NodeID): NodeSet { 256 const rep = this.getRepNode(n); 257 return this.getSubNodes(rep); 258 } 259 260 // get all subnodes in one scc 261 public getSubNodes(n: NodeID): NodeSet { 262 const it = this._R.get(n); 263 if (!it) { 264 throw new Error('sccInfo not found for a node'); 265 } 266 let sub = it.subNodes; 267 if (sub.size === 0) { 268 sub.add(n); 269 } 270 271 return sub; 272 } 273 274 // get all representative nodes 275 public getRepNodes(): NodeSet { 276 return this.repNodes; 277 } 278} 279