• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 { ArkIfStmt, ArkReturnStmt, ArkThrowStmt } from '../core/base/Stmt';
17import { Trap } from '../core/base/Trap';
18import { BasicBlock } from '../core/graph/BasicBlock';
19import { Cfg } from '../core/graph/Cfg';
20
21export enum CodeBlockType {
22    NORMAL,
23    IF,
24    ELSE,
25    BREAK,
26    CONTINUE,
27    DO,
28    DO_WHILE,
29    WHILE,
30    FOR,
31    COMPOUND_END,
32    TRY,
33    CATCH,
34    FINALLY,
35}
36
37export type TraversalCallback = (block: BasicBlock | undefined, type: CodeBlockType) => void;
38
39export class AbstractFlowGraph {
40    private nodes: AbstractNode[] = [];
41    private entry: AbstractNode;
42    private block2NodeMap: Map<BasicBlock, AbstractNode>;
43    private structOf: Map<AbstractNode, Region> = new Map();
44    private structTypes: Map<Region, RegionType> = new Map();
45    private structBlocks: Map<AbstractNode, Set<AbstractNode>> = new Map();
46    private loopMap: Map<AbstractNode, NaturalLoopRegion> = new Map();
47
48    constructor(cfg: Cfg, traps?: Trap[]) {
49        this.block2NodeMap = new Map<BasicBlock, AbstractNode>();
50        for (const bb of cfg.getBlocks()) {
51            let an = new AbstractNode();
52            an.setBlock(bb);
53            this.block2NodeMap.set(bb, an);
54        }
55
56        for (const bb of cfg.getBlocks()) {
57            let an = this.block2NodeMap.get(bb)!;
58            for (const succ of bb.getSuccessors()) {
59                an.addSucc(this.block2NodeMap.get(succ)!);
60            }
61            for (const pred of bb.getPredecessors()) {
62                an.addPred(this.block2NodeMap.get(pred)!);
63            }
64        }
65
66        let trapRegions = this.buildTrap(traps);
67        this.searchTrapFinallyNodes(trapRegions);
68        this.trapsStructuralAnalysis(trapRegions);
69
70        this.entry = this.block2NodeMap.get(cfg.getStartingBlock()!)!;
71        this.entry = this.structuralAnalysis(this.entry);
72    }
73
74    public getEntry(): AbstractNode {
75        return this.entry;
76    }
77
78    public getForIncBlock(block: BasicBlock): BasicBlock {
79        let node = this.block2NodeMap.get(block)!;
80        let loop = this.loopMap.get(node)! as ForLoopRegion;
81        return loop.inc.getBlock()!;
82    }
83
84    public preOrder(node: AbstractNode, callback: TraversalCallback, visitor: Set<AbstractNode> = new Set()): void {
85        visitor.add(node);
86        node.traversal(callback, CodeBlockType.NORMAL);
87        for (const succ of node.getSucc()) {
88            if (!visitor.has(succ)) {
89                this.preOrder(succ, callback, visitor);
90            }
91        }
92    }
93
94    private structuralAnalysis(entry: AbstractNode, scope?: Set<AbstractNode>): AbstractNode {
95        let preds = entry.getPred();
96        let entryBak = entry;
97        this.nodes = this.dfsPostOrder(entry, scope);
98        this.entry = entry;
99        this.buildCyclicStructural();
100
101        // acyclic structural
102        let postMax = this.nodes.length;
103        let change = true;
104        while (postMax > 1 && change) {
105            change = false;
106            for (let i = 0; i < postMax; i++) {
107                let node = this.nodes[i];
108                let nset = new Set<AbstractNode>();
109                let rtype = this.identifyRegionType(node, nset, scope);
110                if (!rtype) {
111                    continue;
112                }
113
114                let p = this.reduce(rtype, nset);
115                if (!p) {
116                    continue;
117                }
118
119                scope?.add(p);
120                if (nset.has(entry)) {
121                    entry = p;
122                }
123                this.nodes = this.dfsPostOrder(entry, scope);
124                change = postMax !== this.nodes.length;
125                postMax = this.nodes.length;
126            }
127        }
128
129        for (const pred of preds) {
130            pred.replaceSucc(entryBak, entry);
131            entry.addPred(pred);
132        }
133
134        return entry;
135    }
136
137    private dfsPostOrder(
138        node: AbstractNode,
139        scope?: Set<AbstractNode>,
140        visitor: Set<AbstractNode> = new Set(),
141        postOrder: AbstractNode[] = []
142    ): AbstractNode[] {
143        visitor.add(node);
144        for (const succ of node.getSucc()) {
145            if (visitor.has(succ)) {
146                continue;
147            }
148
149            if (scope && !scope.has(succ)) {
150                continue;
151            }
152
153            this.dfsPostOrder(succ, scope, visitor, postOrder);
154        }
155        postOrder.push(node);
156        return postOrder;
157    }
158
159    private buildCyclicStructural(): void {
160        for (const loop of this.prepareBuildLoops()) {
161            let nset = new Set<AbstractNode | Region>();
162            for (const n of loop) {
163                if (this.structOf.has(n)) {
164                    nset.add(this.structOf.get(n)!);
165                } else {
166                    nset.add(n);
167                }
168            }
169            let rtype = this.cyclicRegionType(nset);
170            let region = this.createRegion(rtype, nset)! as NaturalLoopRegion;
171            region.revise();
172            this.structTypes.set(region, rtype);
173            let blocks = new Set<AbstractNode>();
174            for (const s of nset) {
175                this.handleRegion(s, region, blocks);
176            }
177            this.structBlocks.set(region, blocks);
178            this.loopMap.set(region.header, region);
179        }
180    }
181
182    private handleRegion(s: AbstractNode | Region, region: NaturalLoopRegion, blocks: Set<AbstractNode>): void {
183        if (!this.structOf.has(s)) {
184            this.structOf.set(s, region);
185        }
186        if (this.structBlocks.has(s as Region)) {
187            for (const b of this.structBlocks.get(s as Region)!) {
188                blocks.add(b);
189            }
190        } else {
191            blocks.add(s);
192        }
193    }
194
195    private prepareBuildLoops(): Set<AbstractNode>[] {
196        let dom = this.buildDominator();
197        let loops: Set<AbstractNode>[] = [];
198        for (const header of this.nodes) {
199            let innermost: Set<AbstractNode> | undefined;
200            let longest: number = 0;
201
202            let backEdges = this.getBackEdges(dom, header);
203            if (backEdges.size === 0) {
204                continue;
205            }
206
207            if (this.isSelfLoopNode(header)) {
208                loops.push(new Set<AbstractNode>([header]));
209            }
210
211            for (const start of backEdges) {
212                let loop = this.naturalLoop(start, header);
213                if (!innermost || loop.size > longest) {
214                    innermost = loop;
215                    longest = loop.size;
216                }
217            }
218            loops.push(innermost!);
219        }
220        loops.sort((a, b) => a.size - b.size);
221        return loops;
222    }
223
224    private buildDominator(): Map<AbstractNode, Set<AbstractNode>> {
225        let domin = new Map<AbstractNode, Set<AbstractNode>>();
226        domin.set(this.entry, new Set<AbstractNode>([this.entry]));
227        for (const node of this.nodes) {
228            if (node !== this.entry) {
229                domin.set(node, new Set<AbstractNode>(this.nodes));
230            }
231        }
232
233        let change = true;
234        while (change) {
235            change = false;
236            for (const node of this.nodes) {
237                if (node === this.entry) {
238                    continue;
239                }
240                let t = new Set<AbstractNode>(domin.get(node)!);
241                for (const p of node.getPred()) {
242                    t = this.setIntersect(t, domin.get(p)!);
243                }
244                t.add(node);
245                if (!this.isSetEqual(t, domin.get(node)!)) {
246                    change = true;
247                    domin.set(node, t);
248                }
249            }
250        }
251
252        return domin;
253    }
254
255    private getBackEdges(dom: Map<AbstractNode, Set<AbstractNode>>, header: AbstractNode): Set<AbstractNode> {
256        let backEdges = new Set<AbstractNode>();
257        for (const n of header.getPred()) {
258            // h dom n && n -> h
259            if (dom.get(n)?.has(header)) {
260                backEdges.add(n);
261            }
262        }
263        return backEdges;
264    }
265
266    private naturalLoop(backEdgeStart: AbstractNode, backEdgeEnd: AbstractNode): Set<AbstractNode> {
267        let stack: AbstractNode[] = [];
268        let loop = new Set<AbstractNode>([backEdgeEnd, backEdgeStart]);
269
270        stack.push(backEdgeStart);
271
272        while (stack.length > 0) {
273            let m = stack.shift()!;
274            for (const pred of m.getPred()) {
275                if (loop.has(pred)) {
276                    continue;
277                }
278                loop.add(pred);
279                stack.push(pred);
280            }
281        }
282
283        return loop;
284    }
285
286    private isSelfLoopNode(node: AbstractNode): boolean {
287        let inSucc = false;
288        let inPred = false;
289
290        for (const pred of node.getPred()) {
291            if (pred === node) {
292                inPred = true;
293            }
294        }
295
296        for (const succ of node.getSucc()) {
297            if (succ === node) {
298                inSucc = true;
299            }
300        }
301
302        return inSucc && inPred;
303    }
304
305    private isForLoopIncNode(node: AbstractNode): boolean {
306        for (const loop of this.loopMap.values()) {
307            if (loop.getType() === RegionType.FOR_LOOP_REGION) {
308                if (node === (loop as ForLoopRegion).inc) {
309                    return true;
310                }
311            }
312        }
313        return false;
314    }
315
316    private isValidInBlocks(node: AbstractNode, scope?: Set<AbstractNode>): boolean {
317        if (this.isForLoopIncNode(node) || node.hasIfStmt()) {
318            return false;
319        }
320        if (scope && !scope.has(node)) {
321            return false;
322        }
323
324        return true;
325    }
326
327    private isIfRegion(node: AbstractNode, nodeSet: Set<AbstractNode>): boolean {
328        nodeSet.clear();
329        if (node.getSucc().length !== 2) {
330            return false;
331        }
332        let m = node.getSucc()[0];
333        let n = node.getSucc()[1];
334        if (m.getSucc().length === 1 && m.getSucc()[0] === n) {
335            nodeSet.add(node).add(m);
336            return true;
337        }
338
339        return false;
340    }
341
342    private isIfExitRegion(node: AbstractNode, nodeSet: Set<AbstractNode>): boolean {
343        nodeSet.clear();
344        if (node.getSucc().length !== 2) {
345            return false;
346        }
347        let m = node.getSucc()[0];
348        if (m.hasReturnStmt()) {
349            nodeSet.add(node).add(m);
350            return true;
351        }
352
353        return false;
354    }
355
356    private isIfElseRegion(node: AbstractNode, nodeSet: Set<AbstractNode>): boolean {
357        nodeSet.clear();
358        if (node.getSucc().length !== 2) {
359            return false;
360        }
361        let m = node.getSucc()[0];
362        let n = node.getSucc()[1];
363        if (
364            (m.getSucc().length === 1 &&
365                n.getSucc().length === 1 &&
366                m.getPred().length === 1 &&
367                n.getPred().length === 1 &&
368                m.getSucc()[0] === n.getSucc()[0]) ||
369            (m.getSucc().length === 0 && n.getSucc().length === 0)
370        ) {
371            nodeSet.add(node).add(m).add(n);
372            return true;
373        }
374
375        return false;
376    }
377
378    private isBlockRegion(node: AbstractNode, nodeSet: Set<AbstractNode>, scope?: Set<AbstractNode>): boolean {
379        let n = node;
380        let p = true;
381        let s = n.getSucc().length === 1;
382        nodeSet.clear();
383
384        let blocks = [];
385        while (p && s && !nodeSet.has(n) && this.isValidInBlocks(n, scope)) {
386            nodeSet.add(n);
387            blocks.push(n);
388            n = n.getSucc()[0];
389            p = n.getPred().length === 1;
390            s = n.getSucc().length === 1;
391        }
392
393        if (p && this.isValidInBlocks(n, scope)) {
394            if (!nodeSet.has(n)) {
395                blocks.push(n);
396            }
397            nodeSet.add(n);
398        }
399
400        n = node;
401        p = n.getPred().length === 1;
402        s = true;
403        while (p && s && this.isValidInBlocks(n, scope)) {
404            if (!nodeSet.has(n)) {
405                blocks.unshift(n);
406            }
407            nodeSet.add(n);
408            n = n.getPred()[0];
409            if (nodeSet.has(n)) {
410                break;
411            }
412            p = n.getPred().length === 1;
413            s = n.getSucc().length === 1;
414        }
415
416        if (s && this.isValidInBlocks(n, scope)) {
417            if (!nodeSet.has(n)) {
418                blocks.unshift(n);
419            }
420            nodeSet.add(n);
421        }
422
423        nodeSet.clear();
424        for (const n of blocks) {
425            nodeSet.add(n);
426        }
427
428        if (nodeSet.size >= 2) {
429            return true;
430        }
431        return false;
432    }
433
434    private isIfBreakRegion(node: AbstractNode, nodeSet: Set<AbstractNode>, loop: NaturalLoopRegion): boolean {
435        let m = node.getSucc()[0];
436        nodeSet.clear();
437        if (this.isExitLoop(m, this.structBlocks.get(loop)!)) {
438            nodeSet.add(node);
439            return true;
440        }
441
442        if (m.getSucc().length === 1 && this.isExitLoop(m.getSucc()[0], this.structBlocks.get(loop)!)) {
443            nodeSet.add(node).add(m);
444            return true;
445        }
446
447        return false;
448    }
449
450    private isIfContinueRegion(node: AbstractNode, nodeSet: Set<AbstractNode>, loop: NaturalLoopRegion): boolean {
451        nodeSet.clear();
452        let m = node.getSucc()[0];
453        let n = node.getSucc()[1];
454        if (loop.control.has(m)) {
455            nodeSet.add(node);
456            return true;
457        }
458
459        if (m.getSucc().length === 1 && loop.control.has(m.getSucc()[0]) && !loop.control.has(n) && !this.isIfElseRegion(node, nodeSet)) {
460            nodeSet.add(node).add(m);
461            return true;
462        }
463        return false;
464    }
465
466    private isWhileRegion(node: AbstractNode, nodeSet: Set<AbstractNode>, loop: NaturalLoopRegion): boolean {
467        nodeSet.clear();
468        let m = node.getSucc()[0];
469        if (loop.header === node && m.getSucc().length === 1 && m.getPred().length === 1 && m.getSucc()[0] === node) {
470            nodeSet.add(node).add(m);
471            return true;
472        }
473        return false;
474    }
475
476    private isForRegion(node: AbstractNode, nodeSet: Set<AbstractNode>, loop: NaturalLoopRegion): boolean {
477        nodeSet.clear();
478        if (loop.header === node && loop.getType() === RegionType.FOR_LOOP_REGION) {
479            let forLoop = loop as ForLoopRegion;
480            let blocks = node.getSucc()[0];
481            if (forLoop.inc.getPred().length === 1 && forLoop.inc.getPred()[0] === blocks && blocks.getSucc().length === 1) {
482                nodeSet.add(node).add(forLoop.inc).add(blocks);
483                return true;
484            }
485        }
486        return false;
487    }
488
489    private isDoWhileRegion(node: AbstractNode, nodeSet: Set<AbstractNode>, loop: NaturalLoopRegion): boolean {
490        nodeSet.clear();
491        if (loop.back === node && loop.getType() === RegionType.DO_WHILE_LOOP_REGION) {
492            let blocks = node.getPred()[0];
493            if (blocks.getSucc().length === 1 && blocks.getSucc()[0] === node && node.getSucc()[0] === blocks) {
494                nodeSet.add(blocks).add(node);
495                return true;
496            }
497        }
498        return false;
499    }
500
501    private identifyRegionType(node: AbstractNode, nodeSet: Set<AbstractNode>, scope?: Set<AbstractNode>): RegionType | undefined {
502        if (this.isBlockRegion(node, nodeSet, scope)) {
503            return RegionType.BLOCK_REGION;
504        }
505
506        let inLoop = false;
507        let region = this.structOf.get(node);
508        if (region && LOOP_TYPES.has(region?.getType())) {
509            inLoop = true;
510        }
511
512        if (new Set(node.getPred()).has(node) && new Set(node.getSucc()).has(node)) {
513            nodeSet.add(node);
514            if (inLoop) {
515                return region?.getType();
516            }
517            return RegionType.SELF_LOOP_REGION;
518        }
519
520        if (node.getSucc().length !== 2) {
521            return undefined;
522        }
523
524        if (inLoop) {
525            let loop = region as NaturalLoopRegion;
526            if (!loop.control.has(node)) {
527                if (this.isIfBreakRegion(node, nodeSet, loop)) {
528                    return RegionType.IF_THEN_BREAK_REGION;
529                }
530
531                if (this.isIfContinueRegion(node, nodeSet, loop)) {
532                    return RegionType.IF_THEN_CONTINUE_REGION;
533                }
534            }
535            if (this.isWhileRegion(node, nodeSet, loop)) {
536                return RegionType.WHILE_LOOP_REGION;
537            }
538
539            if (this.isForRegion(node, nodeSet, loop)) {
540                return RegionType.FOR_LOOP_REGION;
541            }
542
543            if (this.isDoWhileRegion(node, nodeSet, loop)) {
544                return RegionType.DO_WHILE_LOOP_REGION;
545            }
546        }
547
548        // check for if
549        if (this.isIfExitRegion(node, nodeSet)) {
550            return RegionType.IF_THEN_EXIT_REGION;
551        }
552        if (this.isIfRegion(node, nodeSet)) {
553            return RegionType.IF_REGION;
554        }
555
556        // check for an if else
557        if (this.isIfElseRegion(node, nodeSet)) {
558            return RegionType.IF_ELSE_REGION;
559        }
560
561        return undefined;
562    }
563
564    private cyclicRegionType(nodeSet: Set<AbstractNode>): RegionType {
565        let nodes = Array.from(nodeSet);
566        let header = nodes[0];
567        if (nodeSet.size === 1) {
568            let tail = nodes[0].getBlock()?.getTail();
569            if (tail instanceof ArkIfStmt) {
570                return RegionType.DO_WHILE_LOOP_REGION;
571            }
572            return RegionType.WHILE_LOOP_REGION;
573        }
574
575        let back = nodes[1];
576        // exit loop from back
577        if (!this.hasExitLoopSucc(header, nodeSet) && this.hasExitLoopSucc(back, nodeSet)) {
578            return RegionType.DO_WHILE_LOOP_REGION;
579        }
580
581        if (this.hasExitLoopSucc(header, nodeSet) && this.hasExitLoopSucc(back, nodeSet)) {
582            // header true exit loop --> exit is break
583            if (!nodeSet.has(header.getSucc()[0])) {
584                return RegionType.DO_WHILE_LOOP_REGION;
585            }
586        }
587
588        // for
589        if (back.getSucc().length === 1 && back.getBlock()?.getStmts()?.length === 1) {
590            let isForLoop = true;
591            for (const pred of header.getPred()) {
592                if (nodeSet.has(pred) && pred !== back) {
593                    isForLoop = false;
594                }
595            }
596            if (isForLoop) {
597                return RegionType.FOR_LOOP_REGION;
598            }
599        }
600
601        return RegionType.WHILE_LOOP_REGION;
602    }
603
604    private hasExitLoopSucc(node: AbstractNode, nodeSet: Set<AbstractNode>): boolean {
605        for (const succ of node.getSucc()) {
606            if (!nodeSet.has(succ)) {
607                return true;
608            }
609        }
610
611        return false;
612    }
613
614    private isExitLoop(node: AbstractNode | Region, nodeSet: Set<AbstractNode>): boolean {
615        if (this.structBlocks.has(node)) {
616            for (const n of this.structBlocks.get(node)!) {
617                if (!nodeSet.has(n)) {
618                    return true;
619                }
620            }
621        } else {
622            if (!nodeSet.has(node)) {
623                return true;
624            }
625        }
626
627        return false;
628    }
629
630    private createRegion(rtype: RegionType, nodeSet: Set<AbstractNode>): Region | undefined {
631        let node: Region | undefined;
632        if (rtype === RegionType.BLOCK_REGION) {
633            node = new BlockRegion(nodeSet);
634        } else if (rtype === RegionType.IF_ELSE_REGION) {
635            node = new IfElseRegion(nodeSet);
636        } else if (rtype === RegionType.IF_REGION) {
637            node = new IfRegion(nodeSet);
638        } else if (rtype === RegionType.IF_THEN_EXIT_REGION) {
639            node = new IfExitRegion(nodeSet);
640        } else if (rtype === RegionType.IF_THEN_BREAK_REGION) {
641            node = new IfBreakRegion(nodeSet);
642        } else if (rtype === RegionType.IF_THEN_CONTINUE_REGION) {
643            node = new IfContinueRegion(nodeSet);
644        } else if (rtype === RegionType.SELF_LOOP_REGION) {
645            node = new SelfLoopRegion(nodeSet);
646        } else if (rtype === RegionType.WHILE_LOOP_REGION) {
647            let whileLoop = new WhileLoopRegion(nodeSet);
648            this.loopMap.set(whileLoop.header, whileLoop);
649            node = whileLoop;
650        } else if (rtype === RegionType.FOR_LOOP_REGION) {
651            let forLoop = new ForLoopRegion(nodeSet);
652            this.loopMap.set(forLoop.header, forLoop);
653            node = forLoop;
654        } else if (rtype === RegionType.DO_WHILE_LOOP_REGION) {
655            let doWhileLoop = new DoWhileLoopRegion(nodeSet);
656            this.loopMap.set(doWhileLoop.header, doWhileLoop);
657            node = doWhileLoop;
658        } else if (rtype === RegionType.TRY_CATCH_REGION || rtype === RegionType.TRY_FINALLY_REGION || rtype === RegionType.TRY_CATCH_FINALLY_REGION) {
659            node = new TrapRegion(nodeSet, rtype);
660        }
661
662        return node;
663    }
664
665    private reduce(rtype: RegionType, nodeSet: Set<AbstractNode>): Region | undefined {
666        let region = this.createRegion(rtype, nodeSet);
667        region?.replace();
668        if (region === undefined) {
669            return undefined;
670        }
671        this.structTypes.set(region, rtype);
672        let blocks = new Set<AbstractNode>();
673        for (const s of nodeSet) {
674            this.structOf.set(s, region);
675            if (this.structBlocks.has(s as Region)) {
676                for (const b of this.structBlocks.get(s as Region)!) {
677                    blocks.add(b);
678                }
679            } else {
680                blocks.add(s);
681            }
682        }
683        this.structBlocks.set(region, blocks);
684        return region;
685    }
686
687    private setIntersect(a: Set<AbstractNode>, b: Set<AbstractNode>): Set<AbstractNode> {
688        let r = new Set<AbstractNode>();
689        if (!b) {
690            return r;
691        }
692        for (const n of b) {
693            if (a.has(n)) {
694                r.add(n);
695            }
696        }
697
698        return r;
699    }
700
701    private isSetEqual(a: Set<AbstractNode>, b: Set<AbstractNode>): boolean {
702        if (a.size !== b.size) {
703            return false;
704        }
705
706        return this.setIntersect(a, b).size === a.size;
707    }
708
709    private buildTrap(traps?: Trap[]): NaturalTrapRegion[] {
710        if (!traps) {
711            return [];
712        }
713        traps.sort((a, b) => a.getTryBlocks().length + a.getCatchBlocks().length - (b.getTryBlocks().length + b.getCatchBlocks().length));
714
715        let trapRegions: NaturalTrapRegion[] = [];
716
717        for (const trap of traps) {
718            let region = new NaturalTrapRegion(trap, this.block2NodeMap);
719            let findTrapRegion = this.getNaturalTrapRegion(region);
720
721            if (!findTrapRegion) {
722                for (const n of region.getNodes()) {
723                    this.structOf.set(n, region);
724                }
725                trapRegions.push(region);
726                continue;
727            }
728            if (findTrapRegion.type === RegionType.TRY_FINALLY_REGION) {
729                findTrapRegion.trySet = region.trySet;
730                findTrapRegion.catchSet = region.catchSet;
731                region = findTrapRegion;
732            } else {
733                findTrapRegion.finallySet = region.finallySet;
734                region = findTrapRegion;
735            }
736
737            for (const n of region.getNodes()) {
738                this.structOf.set(n, region);
739            }
740            region.type = RegionType.TRY_CATCH_FINALLY_REGION;
741        }
742
743        this.structOf.clear();
744
745        return trapRegions;
746    }
747
748    private searchTrapFinallyNodes(trapRegions: NaturalTrapRegion[]): void {
749        // search finally
750        for (const region of trapRegions) {
751            if (region.type === RegionType.TRY_CATCH_REGION) {
752                continue;
753            }
754
755            this.bfs(region);
756        }
757    }
758
759    private bfs(region: NaturalTrapRegion): void {
760        let finallyNodes = new Set<AbstractNode>();
761        let count = (region as NaturalTrapRegion).finallySet!.size;
762        let queue = [region.getSucc()[0]];
763        while (queue.length > 0 && finallyNodes.size < count) {
764            let node = queue[0];
765            queue.splice(0, 1);
766            finallyNodes.add(node);
767            (region as NaturalTrapRegion).identifyFinallySet.add(node);
768            for (const succ of node.getSucc()) {
769                if (!finallyNodes.has(succ)) {
770                    queue.push(succ);
771                }
772            }
773        }
774    }
775
776    private getNaturalTrapRegion(trap: NaturalTrapRegion): NaturalTrapRegion | undefined {
777        let findTrap = this.findNaturalTrapRegion(trap.trySet);
778        if (findTrap) {
779            return findTrap;
780        }
781        if (trap.catchSet) {
782            findTrap = this.findNaturalTrapRegion(trap.catchSet);
783        }
784
785        if (findTrap) {
786            return findTrap;
787        }
788
789        if (trap.finallySet) {
790            findTrap = this.findNaturalTrapRegion(trap.finallySet);
791        }
792
793        return findTrap;
794    }
795
796    private findNaturalTrapRegion(nodes: Set<AbstractNode>): NaturalTrapRegion | undefined {
797        let findTrap: NaturalTrapRegion | undefined;
798        for (const node of nodes) {
799            if (!this.structOf.has(node)) {
800                return undefined;
801            }
802            if (!findTrap) {
803                findTrap = this.structOf.get(node)! as NaturalTrapRegion;
804                continue;
805            }
806            if (findTrap !== this.structOf.get(node)) {
807                return undefined;
808            }
809        }
810        return findTrap;
811    }
812
813    private trapsStructuralAnalysis(trapRegions: NaturalTrapRegion[]): void {
814        trapRegions.sort((a, b) => a.size() - b.size());
815
816        for (const trap of trapRegions) {
817            let tryNode = this.trapsSubStructuralAnalysis(trap.trySet)!;
818            let catchNode: AbstractNode | undefined = this.trapsSubStructuralAnalysis(trap.catchSet);
819            let finnallyNode: AbstractNode | undefined = this.trapsSubStructuralAnalysis(trap.identifyFinallySet);
820
821            if (catchNode === undefined) {
822                this.reduce(RegionType.TRY_FINALLY_REGION, new Set([tryNode, finnallyNode!]));
823            } else if (finnallyNode === undefined) {
824                this.reduce(RegionType.TRY_CATCH_REGION, new Set([tryNode, catchNode!]));
825            } else {
826                this.reduce(RegionType.TRY_CATCH_FINALLY_REGION, new Set([tryNode, catchNode!, finnallyNode!]));
827            }
828        }
829    }
830
831    private trapsSubStructuralAnalysis(nodes?: Set<AbstractNode>): AbstractNode | undefined {
832        if (!nodes) {
833            return undefined;
834        }
835        let entry = Array.from(nodes)[0];
836        if (nodes.size <= 1) {
837            return entry;
838        }
839
840        for (const node of nodes) {
841            if (this.structOf.has(node)) {
842                nodes.add(this.structOf.get(node)!);
843            }
844        }
845
846        return this.structuralAnalysis(entry, nodes);
847    }
848}
849
850enum RegionType {
851    ABSTRACT_NODE,
852    TRY_NODE,
853    CATCH_NODE,
854    FINALLY_NODE,
855    /* Sequence of blocks.  */
856    BLOCK_REGION,
857    IF_REGION,
858    IF_ELSE_REGION,
859    IF_THEN_EXIT_REGION,
860    IF_THEN_BREAK_REGION,
861    IF_THEN_CONTINUE_REGION,
862    SELF_LOOP_REGION,
863    NATURAL_LOOP_REGION,
864    WHILE_LOOP_REGION,
865    DO_WHILE_LOOP_REGION,
866    FOR_LOOP_REGION,
867    CASE_REGION,
868    SWITCH_REGION,
869    TRY_CATCH_REGION,
870    TRY_FINALLY_REGION,
871    TRY_CATCH_FINALLY_REGION,
872}
873
874const LOOP_TYPES = new Set<RegionType>([
875    RegionType.SELF_LOOP_REGION,
876    RegionType.NATURAL_LOOP_REGION,
877    RegionType.WHILE_LOOP_REGION,
878    RegionType.FOR_LOOP_REGION,
879    RegionType.DO_WHILE_LOOP_REGION,
880]);
881
882class AbstractNode {
883    type: RegionType;
884    private predNodes: AbstractNode[] = [];
885    private succNodes: AbstractNode[] = [];
886    private bb: BasicBlock | undefined;
887
888    constructor() {
889        this.type = RegionType.ABSTRACT_NODE;
890    }
891
892    public traversal(callback: TraversalCallback, type: CodeBlockType): void {
893        callback(this.bb, type);
894    }
895
896    public getType(): RegionType {
897        return this.type;
898    }
899
900    public getSucc(): AbstractNode[] {
901        return this.succNodes;
902    }
903
904    public addSucc(node: AbstractNode): void {
905        this.succNodes.push(node);
906    }
907
908    public replaceSucc(src: AbstractNode, dst: AbstractNode): void {
909        for (let i = 0; i < this.succNodes.length; i++) {
910            if (this.succNodes[i] === src) {
911                this.succNodes[i] = dst;
912                break;
913            }
914        }
915    }
916
917    public removeSucc(src: AbstractNode): void {
918        for (let i = 0; i < this.predNodes.length; i++) {
919            if (this.succNodes[i] === src) {
920                this.succNodes.splice(i, 1);
921                break;
922            }
923        }
924    }
925
926    public getPred(): AbstractNode[] {
927        return this.predNodes;
928    }
929
930    public addPred(block: AbstractNode): void {
931        let set = new Set(this.predNodes);
932        if (set.has(block)) {
933            return;
934        }
935        this.predNodes.push(block);
936    }
937
938    public replacePred(src: AbstractNode, dst: AbstractNode): void {
939        for (let i = 0; i < this.predNodes.length; i++) {
940            if (this.predNodes[i] === src) {
941                this.predNodes[i] = dst;
942                break;
943            }
944        }
945    }
946
947    public removePred(src: AbstractNode): void {
948        for (let i = 0; i < this.predNodes.length; i++) {
949            if (this.predNodes[i] === src) {
950                this.predNodes.splice(i, 1);
951                break;
952            }
953        }
954    }
955
956    public setBlock(bb: BasicBlock): void {
957        this.bb = bb;
958    }
959
960    public getBlock(): BasicBlock | undefined {
961        return this.bb;
962    }
963
964    public hasIfStmt(): boolean {
965        if (!this.bb) {
966            return false;
967        }
968
969        for (let stmt of this.bb.getStmts()) {
970            if (stmt instanceof ArkIfStmt) {
971                return true;
972            }
973        }
974        return false;
975    }
976
977    public hasReturnStmt(): boolean {
978        if (!this.bb) {
979            return false;
980        }
981        for (let stmt of this.bb.getStmts()) {
982            if (stmt instanceof ArkReturnStmt) {
983                return true;
984            }
985        }
986        return false;
987    }
988}
989
990abstract class Region extends AbstractNode {
991    nset: Set<AbstractNode>;
992    constructor(nset: Set<AbstractNode>, type: RegionType) {
993        super();
994        this.nset = nset;
995        this.type = type;
996    }
997
998    public getBlock(): BasicBlock | undefined {
999        if (this.nset.size === 0) {
1000            return undefined;
1001        }
1002
1003        return Array.from(this.nset)[0].getBlock();
1004    }
1005
1006    public abstract replace(): void;
1007}
1008
1009class BlockRegion extends Region {
1010    blocks: AbstractNode[];
1011    constructor(nset: Set<AbstractNode>) {
1012        super(nset, RegionType.BLOCK_REGION);
1013        this.blocks = Array.from(nset);
1014    }
1015
1016    public replace(): void {
1017        for (let pred of this.blocks[0].getPred()) {
1018            pred.replaceSucc(this.blocks[0], this);
1019            this.addPred(pred);
1020        }
1021
1022        for (let succ of this.blocks[this.blocks.length - 1].getSucc()) {
1023            succ.replacePred(this.blocks[this.blocks.length - 1], this);
1024            this.addSucc(succ);
1025        }
1026    }
1027
1028    public traversal(callback: TraversalCallback): void {
1029        for (const node of this.blocks) {
1030            node.traversal(callback, CodeBlockType.NORMAL);
1031        }
1032    }
1033}
1034
1035abstract class NaturalLoopRegion extends Region {
1036    header: AbstractNode;
1037    back: AbstractNode;
1038    control: Set<AbstractNode>;
1039
1040    constructor(nset: Set<AbstractNode>, type: RegionType = RegionType.NATURAL_LOOP_REGION) {
1041        super(nset, type);
1042        let nodes = Array.from(nset);
1043        this.header = nodes[0];
1044        if (nset.size > 1) {
1045            this.back = nodes[1];
1046        } else {
1047            this.back = nodes[0];
1048        }
1049        this.control = new Set([this.header]);
1050    }
1051
1052    public replace(): void {
1053        for (let pred of this.header.getPred()) {
1054            if (!this.nset.has(pred)) {
1055                pred.replaceSucc(this.header, this);
1056                this.addPred(pred);
1057            }
1058        }
1059
1060        let succNodes = new Set<AbstractNode>();
1061        for (let node of this.nset) {
1062            for (let succ of node.getSucc()) {
1063                if (!this.nset.has(succ)) {
1064                    succNodes.add(succ);
1065                }
1066            }
1067        }
1068
1069        if (succNodes.size === 0) {
1070            return;
1071        }
1072
1073        let pred = Array.from(succNodes)[0];
1074        let replaced = false;
1075        for (let succ of pred.getPred()) {
1076            if (this.nset.has(succ)) {
1077                if (!replaced) {
1078                    pred.replacePred(succ, this);
1079                    this.addSucc(pred);
1080                    replaced = true;
1081                } else {
1082                    pred.removePred(succ);
1083                }
1084            }
1085        }
1086    }
1087
1088    public revise(): void {
1089        // add node to loop sets
1090        for (const node of this.nset) {
1091            for (const succ of node.getSucc()) {
1092                if (!this.nset.has(succ) && succ !== this.getExitNode() && succ.getSucc().length === 1 && succ.getSucc()[0] === this.getExitNode()) {
1093                    this.nset.add(succ);
1094                }
1095            }
1096        }
1097    }
1098
1099    abstract getExitNode(): AbstractNode;
1100}
1101
1102class SelfLoopRegion extends NaturalLoopRegion {
1103    constructor(nset: Set<AbstractNode>) {
1104        super(nset, RegionType.SELF_LOOP_REGION);
1105        this.back = this.header;
1106    }
1107
1108    public replace(): void {
1109        for (let pred of this.header.getPred()) {
1110            if (pred !== this.header) {
1111                pred.replaceSucc(this.header, this);
1112                this.addPred(pred);
1113            }
1114        }
1115
1116        for (let succ of this.header.getSucc()) {
1117            if (succ !== this.header) {
1118                succ.replacePred(this.header, this);
1119                this.addSucc(succ);
1120            }
1121        }
1122    }
1123
1124    getExitNode(): AbstractNode {
1125        return this.header.getSucc()[1];
1126    }
1127}
1128
1129class WhileLoopRegion extends NaturalLoopRegion {
1130    constructor(nset: Set<AbstractNode>) {
1131        super(nset, RegionType.WHILE_LOOP_REGION);
1132    }
1133
1134    public traversal(callback: TraversalCallback): void {
1135        this.header.traversal(callback, CodeBlockType.WHILE);
1136        if (this.header !== this.back) {
1137            this.back.traversal(callback, CodeBlockType.NORMAL);
1138        }
1139        callback(undefined, CodeBlockType.COMPOUND_END);
1140    }
1141
1142    getExitNode(): AbstractNode {
1143        return this.header.getSucc()[1];
1144    }
1145}
1146
1147class DoWhileLoopRegion extends NaturalLoopRegion {
1148    constructor(nset: Set<AbstractNode>) {
1149        super(nset, RegionType.DO_WHILE_LOOP_REGION);
1150        this.control.clear();
1151        this.control.add(this.back);
1152    }
1153
1154    public traversal(callback: TraversalCallback): void {
1155        callback(undefined, CodeBlockType.DO);
1156        if (this.header !== this.back) {
1157            this.header.traversal(callback, CodeBlockType.NORMAL);
1158        }
1159        this.back.traversal(callback, CodeBlockType.DO_WHILE);
1160    }
1161
1162    getExitNode(): AbstractNode {
1163        return this.back.getSucc()[1];
1164    }
1165}
1166
1167class ForLoopRegion extends NaturalLoopRegion {
1168    inc: AbstractNode;
1169
1170    constructor(nset: Set<AbstractNode>) {
1171        super(nset, RegionType.FOR_LOOP_REGION);
1172        this.inc = this.back;
1173        this.control.add(this.inc);
1174    }
1175
1176    public traversal(callback: TraversalCallback): void {
1177        this.header.traversal(callback, CodeBlockType.FOR);
1178        for (const node of this.nset) {
1179            if (node !== this.header && node !== this.inc) {
1180                node.traversal(callback, CodeBlockType.NORMAL);
1181            }
1182        }
1183        callback(undefined, CodeBlockType.COMPOUND_END);
1184    }
1185
1186    getExitNode(): AbstractNode {
1187        return this.header.getSucc()[1];
1188    }
1189}
1190
1191class IfRegion extends Region {
1192    contition: AbstractNode;
1193    then: AbstractNode;
1194
1195    constructor(nset: Set<AbstractNode>) {
1196        super(nset, RegionType.IF_REGION);
1197        let nodes = Array.from(nset);
1198        this.contition = nodes[0];
1199        this.then = nodes[1];
1200    }
1201
1202    public replace(): void {
1203        this.replaceContitionPred();
1204
1205        for (let succ of this.then.getSucc()) {
1206            if (succ !== this.then) {
1207                succ.replacePred(this.then, this);
1208                succ.removePred(this.contition);
1209                this.addSucc(succ);
1210            }
1211        }
1212    }
1213
1214    public traversal(callback: TraversalCallback): void {
1215        this.contition.traversal(callback, CodeBlockType.IF);
1216        this.then.traversal(callback, CodeBlockType.NORMAL);
1217        callback(undefined, CodeBlockType.COMPOUND_END);
1218    }
1219
1220    protected replaceContitionPred(): void {
1221        for (let pred of this.contition.getPred()) {
1222            if (pred !== this.contition) {
1223                pred.replaceSucc(this.contition, this);
1224                this.addPred(pred);
1225            }
1226        }
1227    }
1228}
1229
1230class IfExitRegion extends IfRegion {
1231    constructor(nset: Set<AbstractNode>) {
1232        super(nset);
1233        this.type = RegionType.IF_THEN_EXIT_REGION;
1234    }
1235
1236    public replace(): void {
1237        this.replaceContitionPred();
1238
1239        let succ = this.contition.getSucc()[1];
1240        succ.replacePred(this.contition, this);
1241        this.addSucc(succ);
1242    }
1243}
1244
1245class IfBreakRegion extends IfRegion {
1246    constructor(nset: Set<AbstractNode>) {
1247        super(nset);
1248        this.type = RegionType.IF_THEN_BREAK_REGION;
1249    }
1250
1251    public replace(): void {
1252        this.replaceContitionPred();
1253
1254        let succ = this.contition.getSucc()[1];
1255        succ.replacePred(this.contition, this);
1256        this.addSucc(succ);
1257
1258        if (this.then) {
1259            succ = this.then.getSucc()[0];
1260            succ.removePred(this.then);
1261        } else {
1262            succ = this.contition.getSucc()[0];
1263            succ.removePred(this.contition);
1264        }
1265    }
1266
1267    public traversal(callback: TraversalCallback): void {
1268        this.contition.traversal(callback, CodeBlockType.IF);
1269        this.then?.traversal(callback, CodeBlockType.NORMAL);
1270        callback(undefined, CodeBlockType.BREAK);
1271        callback(undefined, CodeBlockType.COMPOUND_END);
1272    }
1273}
1274
1275class IfContinueRegion extends IfBreakRegion {
1276    constructor(nset: Set<AbstractNode>) {
1277        super(nset);
1278        this.type = RegionType.IF_THEN_CONTINUE_REGION;
1279    }
1280
1281    public traversal(callback: TraversalCallback): void {
1282        this.contition.traversal(callback, CodeBlockType.IF);
1283        this.then?.traversal(callback, CodeBlockType.NORMAL);
1284        callback(undefined, CodeBlockType.CONTINUE);
1285        callback(undefined, CodeBlockType.COMPOUND_END);
1286    }
1287}
1288
1289class IfElseRegion extends Region {
1290    contition: AbstractNode;
1291    then: AbstractNode;
1292    else: AbstractNode;
1293
1294    constructor(nset: Set<AbstractNode>) {
1295        super(nset, RegionType.IF_ELSE_REGION);
1296        let nodes = Array.from(nset);
1297        this.contition = nodes[0];
1298        this.then = nodes[1];
1299        this.else = nodes[2];
1300    }
1301
1302    public replace(): void {
1303        for (let pred of this.contition.getPred()) {
1304            if (pred !== this.contition) {
1305                pred.replaceSucc(this.contition, this);
1306                this.addPred(pred);
1307            }
1308        }
1309
1310        for (let succ of this.then.getSucc()) {
1311            if (succ !== this.then) {
1312                succ.replacePred(this.then, this);
1313                succ.removePred(this.else);
1314                this.addSucc(succ);
1315            }
1316        }
1317    }
1318
1319    public traversal(callback: TraversalCallback): void {
1320        this.contition.traversal(callback, CodeBlockType.IF);
1321        this.then.traversal(callback, CodeBlockType.NORMAL);
1322        callback(undefined, CodeBlockType.ELSE);
1323        this.else.traversal(callback, CodeBlockType.NORMAL);
1324        callback(undefined, CodeBlockType.COMPOUND_END);
1325    }
1326}
1327
1328class TrapRegion extends Region {
1329    tryNode: AbstractNode;
1330    catchNode?: AbstractNode;
1331    finallyNode?: AbstractNode;
1332
1333    constructor(nset: Set<AbstractNode>, type: RegionType) {
1334        super(nset, type);
1335        let nodes = Array.from(nset);
1336
1337        this.tryNode = nodes[0];
1338        if (type === RegionType.TRY_CATCH_REGION) {
1339            this.catchNode = nodes[1];
1340        } else if (type === RegionType.TRY_FINALLY_REGION) {
1341            this.finallyNode = nodes[1];
1342        } else {
1343            this.catchNode = nodes[1];
1344            this.finallyNode = nodes[2];
1345        }
1346    }
1347
1348    public replace(): void {
1349        for (let pred of this.tryNode.getPred()) {
1350            if (pred !== this.tryNode) {
1351                pred.replaceSucc(this.tryNode, this);
1352                this.addPred(pred);
1353            }
1354        }
1355
1356        if (this.finallyNode) {
1357            for (let succ of this.finallyNode.getSucc()) {
1358                if (succ !== this.finallyNode) {
1359                    succ.replacePred(this.finallyNode, this);
1360                    this.addSucc(succ);
1361                }
1362            }
1363        } else {
1364            for (let succ of this.tryNode.getSucc()) {
1365                if (succ !== this.tryNode) {
1366                    succ.replacePred(this.tryNode, this);
1367                    this.addSucc(succ);
1368                }
1369            }
1370        }
1371    }
1372
1373    public traversal(callback: TraversalCallback): void {
1374        callback(undefined, CodeBlockType.TRY);
1375        this.tryNode.traversal(callback, CodeBlockType.NORMAL);
1376        if (this.catchNode) {
1377            callback(this.catchNode.getBlock(), CodeBlockType.CATCH);
1378            this.catchNode?.traversal(callback, CodeBlockType.NORMAL);
1379        }
1380        if (this.finallyNode) {
1381            callback(undefined, CodeBlockType.FINALLY);
1382            this.finallyNode?.traversal(callback, CodeBlockType.NORMAL);
1383        }
1384        callback(undefined, CodeBlockType.COMPOUND_END);
1385    }
1386}
1387
1388class NaturalTrapRegion extends Region {
1389    trySet: Set<AbstractNode>;
1390    catchSet?: Set<AbstractNode>;
1391    finallySet?: Set<AbstractNode>;
1392    identifyFinallySet: Set<AbstractNode>;
1393
1394    constructor(trap: Trap, block2NodeMap: Map<BasicBlock, AbstractNode>) {
1395        super(new Set<AbstractNode>(), RegionType.TRY_CATCH_FINALLY_REGION);
1396        this.trySet = new Set<AbstractNode>();
1397        this.catchSet = new Set<AbstractNode>();
1398        this.identifyFinallySet = new Set<AbstractNode>();
1399
1400        for (const block of trap.getTryBlocks()) {
1401            this.trySet.add(block2NodeMap.get(block)!);
1402        }
1403
1404        for (const block of trap.getCatchBlocks()) {
1405            this.catchSet.add(block2NodeMap.get(block)!);
1406        }
1407
1408        if (this.isFinallyNode(Array.from(this.catchSet!)[this.catchSet.size - 1])!) {
1409            this.type = RegionType.TRY_FINALLY_REGION;
1410            this.finallySet = this.catchSet;
1411            this.catchSet = undefined;
1412        } else {
1413            this.type = RegionType.TRY_CATCH_REGION;
1414        }
1415    }
1416
1417    private isFinallyNode(node: AbstractNode): boolean {
1418        let block = node.getBlock();
1419        if (!block) {
1420            return false;
1421        }
1422
1423        let stmtLen = block.getStmts().length;
1424        if (stmtLen < 1) {
1425            return false;
1426        }
1427
1428        let stmtLast = block.getStmts()[stmtLen - 1];
1429        return stmtLast instanceof ArkThrowStmt;
1430    }
1431
1432    public size(): number {
1433        let size = this.trySet.size;
1434        if (this.catchSet) {
1435            size += this.catchSet.size;
1436        }
1437        if (this.finallySet) {
1438            size += this.finallySet.size;
1439        }
1440        return size;
1441    }
1442
1443    public replace(): void {}
1444
1445    public getNodes(): AbstractNode[] {
1446        let nodes = Array.from(this.trySet);
1447
1448        if (this.catchSet) {
1449            nodes.push(...this.catchSet);
1450        }
1451
1452        if (this.finallySet) {
1453            nodes.push(...this.finallySet);
1454        }
1455        return nodes;
1456    }
1457
1458    public getSucc(): AbstractNode[] {
1459        let succ = new Set<AbstractNode>();
1460
1461        for (const node of this.trySet) {
1462            for (const s of node.getSucc()) {
1463                if (!this.trySet.has(s)) {
1464                    succ.add(s);
1465                }
1466            }
1467        }
1468        return Array.from(succ);
1469    }
1470}
1471