Skip to content

Commit 99123ad

Browse files
committed
perf: avoid multiple cfg reverses
1 parent af154a9 commit 99123ad

File tree

4 files changed

+23
-12
lines changed

4 files changed

+23
-12
lines changed

src/control-flow/basic-cfg-guided-visitor.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
, CfgVertexType } from './control-flow-graph';
77
import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
88
import { assertUnreachable } from '../util/assert';
9+
import { invertCfg } from './invert-cfg';
910

1011
export interface BasicCfgGuidedVisitorConfiguration<
1112
ControlFlow extends ControlFlowInformation = ControlFlowInformation,
@@ -49,10 +50,14 @@ export class BasicCfgGuidedVisitor<
4950

5051
protected startVisitor(start: readonly NodeId[]): void {
5152
const graph = this.config.controlFlow.graph;
52-
const getNext = this.config.defaultVisitingOrder === 'forward' ?
53-
(node: NodeId) => graph.ingoingEdges(node)?.keys().toArray().toReversed() :
54-
(node: NodeId) => graph.outgoingEdges(node)?.keys().toArray();
55-
const stack = [...start];
53+
let getNext: (node: NodeId) => MapIterator<NodeId> | NodeId[] | undefined;
54+
if(this.config.defaultVisitingOrder === 'forward') {
55+
const inverseGraph = invertCfg(graph);
56+
getNext = (node: NodeId) => inverseGraph.outgoingEdges(node)?.keys().toArray().reverse();
57+
} else {
58+
getNext = (node: NodeId) => graph.outgoingEdges(node)?.keys();
59+
}
60+
const stack = Array.from(start);
5661
while(stack.length > 0) {
5762
const current = stack.pop() as NodeId;
5863

src/control-flow/cfg-dead-code.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* currently this does not do work on function definitions */
2-
import type { ControlFlowInformation } from './control-flow-graph';
2+
import type { ControlFlowGraph, ControlFlowInformation } from './control-flow-graph';
33
import { CfgEdgeType } from './control-flow-graph';
44
import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
55
import { Ternary } from '../util/logic';
@@ -13,6 +13,7 @@ import { EmptyArgument } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-c
1313
import { valueSetGuard } from '../dataflow/eval/values/general';
1414
import { isValue } from '../dataflow/eval/values/r-value';
1515
import { visitCfgInOrder } from './simple-visitor';
16+
import { invertCfg } from './invert-cfg';
1617

1718
type CachedValues<Val> = Map<NodeId, Val>;
1819

@@ -21,6 +22,7 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor {
2122
private readonly cachedConditions: CachedValues<Ternary> = new Map();
2223
private readonly cachedStatements: CachedValues<boolean> = new Map();
2324
private readonly inTry: Set<NodeId> = new Set<NodeId>();
25+
private invertedCfg: ControlFlowGraph | undefined;
2426

2527
private getValue(id: NodeId): Ternary {
2628
const has = this.cachedConditions.get(id);
@@ -52,6 +54,7 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor {
5254
}
5355

5456
protected override startVisitor(): void {
57+
this.invertedCfg = invertCfg(this.config.controlFlow.graph);
5558
for(const [from, targets] of this.config.controlFlow.graph.edges()) {
5659
for(const [target, edge] of targets) {
5760
if(edge.label === CfgEdgeType.Cd) {
@@ -65,7 +68,7 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor {
6568
} else if(edge.label === CfgEdgeType.Fd && this.isUnconditionalJump(target)) {
6669
// for each unconditional jump, we find the corresponding end/exit nodes and remove any flow edges
6770
for(const end of this.getCfgVertex(target)?.end as NodeId[] ?? []) {
68-
for(const [target, edge] of this.config.controlFlow.graph.ingoingEdges(end) ?? []) {
71+
for(const [target, edge] of this.invertedCfg.outgoingEdges(end) ?? []) {
6972
if(edge.label === CfgEdgeType.Fd) {
7073
this.config.controlFlow.graph.removeEdge(target, end);
7174
}
@@ -152,7 +155,7 @@ class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor {
152155
}
153156
this.inTry.add(n);
154157
return false;
155-
});
158+
}, this.invertedCfg);
156159
}
157160
}
158161

@@ -168,7 +171,7 @@ export function cfgAnalyzeDeadCode(cfg: ControlFlowInformation, info: CfgPassInf
168171
normalizedAst: info.ast,
169172
dfg: info.dfg,
170173
ctx: info.ctx,
171-
defaultVisitingOrder: 'forward',
174+
defaultVisitingOrder: 'backward'
172175
});
173176
visitor.start();
174177
return cfg;

src/control-flow/control-flow-graph.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -280,8 +280,9 @@ export class ControlFlowGraph<Vertex extends CfgSimpleVertex = CfgSimpleVertex>
280280
ingoingEdges(id: NodeId): ReadonlyMap<NodeId, CfgEdge> | undefined {
281281
const edges = new Map<NodeId, CfgEdge>();
282282
for(const [source, outgoing] of this.edgeInformation.entries()) {
283-
if(outgoing.has(id)) {
284-
edges.set(source, outgoing.get(id) as CfgEdge);
283+
const o = outgoing.get(id);
284+
if(o) {
285+
edges.set(source, o);
285286
}
286287
}
287288
return edges;

src/control-flow/simple-visitor.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ export function visitCfgInReverseOrder(
4949
* @param graph - The control flow graph.
5050
* @param startNodes - The nodes to start the traversal from.
5151
* @param visitor - The visitor function to call for each node, if you return true the traversal from this node will be stopped.
52+
* @param invertedCfg - Optionally provide an inverted control flow graph, if not provided the function will create one by inverting the given graph, which can be expensive for large graphs.
5253
*
5354
* This function is of type {@link SimpleCfgVisitor}.
5455
* @see {@link visitCfgInReverseOrder} for a traversal in reversed order
@@ -57,12 +58,13 @@ export function visitCfgInOrder(
5758
graph: ControlFlowGraph,
5859
startNodes: readonly NodeId[],
5960
// eslint-disable-next-line @typescript-eslint/no-invalid-void-type -- void is used to indicate that the return value is ignored/we never stop
60-
visitor: (node: NodeId) => boolean | void
61+
visitor: (node: NodeId) => boolean | void,
62+
invertedCfg?: ControlFlowGraph
6163
): Set<NodeId> {
6264
const visited = new Set<NodeId>();
6365
let queue = startNodes.slice();
6466
const hasBb = graph.mayHaveBasicBlocks();
65-
const g = invertCfg(graph);
67+
const g = invertedCfg ?? invertCfg(graph);
6668
while(queue.length > 0) {
6769
const current = queue.shift() as NodeId;
6870
if(visited.has(current)) {

0 commit comments

Comments
 (0)