Skip to content

Commit 560f076

Browse files
authored
CFG: dead-code analysis (#1708)
* refactor(cfg): clean up id usage * feat(cfg): dead code detection for loops * refactor(cfg): remove draft code * refactor(cfg): remove unused property * refactor(cfg): update dead code visitor to new if api
1 parent be528b8 commit 560f076

File tree

13 files changed

+345
-117
lines changed

13 files changed

+345
-117
lines changed

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

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/* currently this does not do work on function definitions */
2+
import type { ControlFlowInformation } from './control-flow-graph';
3+
import { CfgEdgeType } from './control-flow-graph';
4+
import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
5+
import { Ternary } from '../util/logic';
6+
import type { CfgPassInfo } from './cfg-simplification';
7+
import { SemanticCfgGuidedVisitor } from './semantic-cfg-guided-visitor';
8+
import type { DataflowGraphVertexFunctionCall } from '../dataflow/graph/vertex';
9+
import type { FunctionArgument } from '../dataflow/graph/graph';
10+
import { resolveIdToValue } from '../dataflow/environments/resolve-by-name';
11+
import { log } from '../util/log';
12+
import { EmptyArgument } from '../r-bridge/lang-4.x/ast/model/nodes/r-function-call';
13+
14+
type CachedValues = Map<NodeId, Ternary>;
15+
16+
class CfgConditionalDeadCodeRemoval extends SemanticCfgGuidedVisitor {
17+
private readonly cachedConditions: CachedValues = new Map();
18+
19+
private getValue(id: NodeId): Ternary {
20+
const has = this.cachedConditions.get(id);
21+
if(has) {
22+
return has;
23+
}
24+
this.visitNode(id);
25+
return this.cachedConditions.get(id) ?? Ternary.Maybe;
26+
}
27+
28+
private unableToCalculateValue(id: NodeId): void {
29+
this.cachedConditions.set(id, Ternary.Maybe);
30+
}
31+
32+
private storeDefiniteValue(id: NodeId, value: boolean): void {
33+
this.cachedConditions.set(id, value ? Ternary.Always : Ternary.Never);
34+
}
35+
36+
protected override startVisitor(): void {
37+
for(const [from, targets] of this.config.controlFlow.graph.edges()) {
38+
for(const [target, edge] of targets) {
39+
if(edge.label === CfgEdgeType.Cd) {
40+
const og = this.getValue(edge.caused);
41+
42+
if(og === Ternary.Always && edge.when === 'FALSE') {
43+
this.config.controlFlow.graph.removeEdge(from, target);
44+
} else if(og === Ternary.Never && edge.when === 'TRUE') {
45+
this.config.controlFlow.graph.removeEdge(from, target);
46+
}
47+
}
48+
}
49+
}
50+
}
51+
52+
private handleValuesFor(id: NodeId, valueId: NodeId): void {
53+
const values = resolveIdToValue(valueId, { graph: this.config.dataflow, full: true, idMap: this.config.normalizedAst.idMap });
54+
if(values === undefined || values.length !== 1) {
55+
this.unableToCalculateValue(id);
56+
return;
57+
}
58+
/* we should translate this to truthy later */
59+
this.storeDefiniteValue(id, Boolean(values[0]));
60+
}
61+
62+
private handleWithCondition(data: { call: DataflowGraphVertexFunctionCall, condition?: FunctionArgument | NodeId }) {
63+
const id = data.call.id;
64+
if(data.condition === undefined || data.condition === EmptyArgument) {
65+
this.unableToCalculateValue(id);
66+
return;
67+
}
68+
this.handleValuesFor(id, typeof data.condition === 'object' ? data.condition.nodeId : data.condition);
69+
}
70+
71+
protected onIfThenElseCall(data: { call: DataflowGraphVertexFunctionCall, condition?: NodeId }) {
72+
this.handleWithCondition(data);
73+
}
74+
75+
protected onWhileLoopCall(data: { call: DataflowGraphVertexFunctionCall, condition: FunctionArgument }) {
76+
this.handleWithCondition(data);
77+
}
78+
}
79+
80+
81+
/** Breaks unsatisfiable control dependencies */
82+
export function cfgAnalyzeDeadCode(cfg: ControlFlowInformation, info: CfgPassInfo): ControlFlowInformation {
83+
if(!info.ast || !info.dfg) {
84+
log.warn('cfgAnalyzeDeadCode called without ast or dfg, skipping dead code analysis');
85+
return cfg;
86+
}
87+
const visitor = new CfgConditionalDeadCodeRemoval({
88+
controlFlow: cfg,
89+
normalizedAst: info.ast,
90+
dataflow: info.dfg,
91+
defaultVisitingOrder: 'forward'
92+
});
93+
visitor.start();
94+
return cfg;
95+
}
Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
11
import type { ControlFlowInformation } from './control-flow-graph';
2-
import { visitCfgInOrder } from './simple-visitor';
3-
import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
42
import { convertCfgToBasicBlocks } from './cfg-to-basic-blocks';
3+
import type { NormalizedAst } from '../r-bridge/lang-4.x/ast/model/processing/decorate';
4+
import type { DataflowGraph } from '../dataflow/graph/graph';
5+
import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
6+
import { visitCfgInOrder } from './simple-visitor';
7+
import { cfgAnalyzeDeadCode } from './cfg-dead-code';
58

6-
export type CfgSimplificationPass = (cfg: ControlFlowInformation) => ControlFlowInformation;
9+
export interface CfgPassInfo {
10+
ast?: NormalizedAst,
11+
dfg?: DataflowGraph
12+
}
13+
export type CfgSimplificationPass = (cfg: ControlFlowInformation, info: CfgPassInfo) => ControlFlowInformation;
714

815
const CfgSimplificationPasses = {
9-
'unique-cf-sets': uniqueControlFlowSets,
10-
'remove-dead-code': cfgRemoveDeadCode,
11-
'to-basic-blocks': toBasicBlocks
16+
'unique-cf-sets': uniqueControlFlowSets,
17+
'analyze-dead-code': cfgAnalyzeDeadCode,
18+
'remove-dead-code': cfgRemoveDeadCode,
19+
'to-basic-blocks': toBasicBlocks
1220
} as const satisfies Record<string, CfgSimplificationPass>;
1321

1422
export type CfgSimplificationPassName = keyof typeof CfgSimplificationPasses;
@@ -25,28 +33,20 @@ export const DefaultCfgSimplificationOrder = [
2533
*/
2634
export function simplifyControlFlowInformation(
2735
cfg: ControlFlowInformation,
36+
info: CfgPassInfo,
2837
passes: readonly CfgSimplificationPassName[] = DefaultCfgSimplificationOrder
2938
): ControlFlowInformation {
3039
for(const pass of passes) {
3140
const passFn = CfgSimplificationPasses[pass];
32-
cfg = passFn(cfg);
41+
cfg = passFn(cfg, info);
3342
}
3443
return cfg;
3544
}
3645

37-
function uniqueControlFlowSets(cfg: ControlFlowInformation): ControlFlowInformation {
38-
return {
39-
returns: [...new Set(cfg.returns)],
40-
entryPoints: [...new Set(cfg.entryPoints)],
41-
exitPoints: [...new Set(cfg.exitPoints)],
42-
breaks: [...new Set(cfg.breaks)],
43-
nexts: [...new Set(cfg.nexts)],
44-
graph: cfg.graph
45-
};
46-
}
47-
48-
/* currently this does not do work on function definitions */
49-
function cfgRemoveDeadCode(cfg: ControlFlowInformation): ControlFlowInformation {
46+
/**
47+
* removes dead vertices and edges from the control flow graph.
48+
*/
49+
function cfgRemoveDeadCode(cfg: ControlFlowInformation, _info?: CfgPassInfo): ControlFlowInformation {
5050
// remove every root level node and accompanying vertices that can not be reached from the entry points
5151
const reachable = new Set<NodeId>();
5252
visitCfgInOrder(cfg.graph, cfg.entryPoints, node => {
@@ -60,6 +60,17 @@ function cfgRemoveDeadCode(cfg: ControlFlowInformation): ControlFlowInformation
6060
return cfg;
6161
}
6262

63-
function toBasicBlocks(cfg: ControlFlowInformation): ControlFlowInformation {
63+
function uniqueControlFlowSets(cfg: ControlFlowInformation, _info?: CfgPassInfo): ControlFlowInformation {
64+
return {
65+
returns: [...new Set(cfg.returns)],
66+
entryPoints: [...new Set(cfg.entryPoints)],
67+
exitPoints: [...new Set(cfg.exitPoints)],
68+
breaks: [...new Set(cfg.breaks)],
69+
nexts: [...new Set(cfg.nexts)],
70+
graph: cfg.graph
71+
};
72+
}
73+
74+
function toBasicBlocks(cfg: ControlFlowInformation, _info?: CfgPassInfo): ControlFlowInformation {
6475
return convertCfgToBasicBlocks(cfg);
6576
}

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ interface CfgFlowDependencyEdge extends MergeableRecord {
107107
label: CfgEdgeType.Fd
108108
}
109109

110-
interface CfgControlDependencyEdge extends MergeableRecord {
110+
export interface CfgControlDependencyEdge extends MergeableRecord {
111111
label: CfgEdgeType.Cd
112112
/** the id which caused the control dependency */
113113
caused: NodeId,
@@ -205,7 +205,8 @@ export interface ReadOnlyControlFlowGraph {
205205
* If you want to prohibit modification, please refer to the {@link ReadOnlyControlFlowGraph} interface.
206206
*/
207207
export class ControlFlowGraph<Vertex extends CfgSimpleVertex = CfgSimpleVertex> implements ReadOnlyControlFlowGraph {
208-
private readonly rootVertices: Set<NodeId> = new Set<NodeId>();
208+
private readonly rootVertices: Set<NodeId> = new Set<NodeId>();
209+
/** Nesting-Independent vertex information, mapping the id to the vertex */
209210
private readonly vertexInformation: Map<NodeId, Vertex> = new Map<NodeId, Vertex>();
210211
/** the basic block children map contains a mapping of ids to all vertices that are nested in basic blocks, mapping them to the Id of the block they appear in */
211212
private readonly bbChildren: Map<NodeId, NodeId> = new Map<NodeId, NodeId>();

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import type { CfgExpressionVertex, CfgStatementVertex, ControlFlowInformation } from './control-flow-graph';
22
import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
33

4-
import type { DataflowInformation } from '../dataflow/info';
54
import type {
65
DataflowGraphVertexArgument, DataflowGraphVertexFunctionCall, DataflowGraphVertexFunctionDefinition,
76
DataflowGraphVertexUse,
@@ -11,10 +10,11 @@ import { VertexType
1110
import type { BasicCfgGuidedVisitorConfiguration } from './basic-cfg-guided-visitor';
1211
import { BasicCfgGuidedVisitor } from './basic-cfg-guided-visitor';
1312
import { assertUnreachable } from '../util/assert';
13+
import type { DataflowGraph } from '../dataflow/graph/graph';
1414

1515
export interface DataflowCfgGuidedVisitorConfiguration<
1616
Cfg extends ControlFlowInformation = ControlFlowInformation,
17-
Dfg extends DataflowInformation = DataflowInformation
17+
Dfg extends DataflowGraph = DataflowGraph
1818
> extends BasicCfgGuidedVisitorConfiguration<Cfg> {
1919
readonly dataflow: Dfg;
2020
}
@@ -26,15 +26,15 @@ export interface DataflowCfgGuidedVisitorConfiguration<
2626
*/
2727
export class DataflowAwareCfgGuidedVisitor<
2828
Cfg extends ControlFlowInformation = ControlFlowInformation,
29-
Dfg extends DataflowInformation = DataflowInformation,
29+
Dfg extends DataflowGraph = DataflowGraph,
3030
Config extends DataflowCfgGuidedVisitorConfiguration<Cfg, Dfg> = DataflowCfgGuidedVisitorConfiguration<Cfg, Dfg>
3131
> extends BasicCfgGuidedVisitor<Cfg, Config> {
3232

3333
/**
3434
* Get the dataflow graph vertex for the given id
3535
*/
3636
protected getDataflowGraph(id: NodeId): DataflowGraphVertexArgument | undefined {
37-
return this.config.dataflow.graph.getVertex(id);
37+
return this.config.dataflow.getVertex(id);
3838
}
3939

4040

0 commit comments

Comments
 (0)