Skip to content

Commit 94d2eba

Browse files
authored
Doc: the new CFG (#1631)
* doc(cfg): up to the discussion of vertex types * doc(cfg): basic blocks how to * doc(cfg): basic traversal, sructure * doc(cfg): basic expl for the syntax aware visitor * doc(dfg): improve general documentation * perf(cfg): minor improvements to manipulation functions * doc(dfg): origin function * feat(cfg): semantic visitor draft * feat(cfg): semantic visitor
1 parent c81942e commit 94d2eba

26 files changed

+3701
-329
lines changed

src/cli/repl/commands/repl-cfg.ts

+44-20
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { fileProtocol, requestFromInput } from '../../../r-bridge/retriever';
55
import { cfgToMermaid, cfgToMermaidUrl } from '../../../util/mermaid/cfg';
66
import type { KnownParser } from '../../../r-bridge/parser';
77
import { ColorEffect, Colors, FontStyles } from '../../../util/text/ansi';
8+
import type { ControlFlowInformation } from '../../../control-flow/control-flow-graph';
9+
import type { NormalizedAst } from '../../../r-bridge/lang-4.x/ast/model/processing/decorate';
10+
import type { CfgSimplificationPassName } from '../../../control-flow/cfg-simplification';
11+
import { DefaultCfgSimplificationOrder } from '../../../control-flow/cfg-simplification';
812

913
async function controlflow(parser: KnownParser, remainingLine: string) {
1014
return await createDataflowPipeline(parser, {
@@ -20,40 +24,60 @@ function formatInfo(out: ReplOutput, type: string): string {
2024
return out.formatter.format(`Copied ${type} to clipboard.`, { color: Colors.White, effect: ColorEffect.Foreground, style: FontStyles.Italic });
2125
}
2226

27+
28+
async function produceAndPrintCfg(shell: KnownParser, remainingLine: string, output: ReplOutput, simplifications: readonly CfgSimplificationPassName[], cfgConverter: (cfg: ControlFlowInformation, ast: NormalizedAst) => string) {
29+
const result = await controlflow(shell, handleString(remainingLine));
30+
31+
const cfg = extractCFG(result.normalize, result.dataflow.graph, [...DefaultCfgSimplificationOrder, ...simplifications]);
32+
const mermaid = cfgConverter(cfg, result.normalize);
33+
output.stdout(mermaid);
34+
try {
35+
const clipboard = await import('clipboardy');
36+
clipboard.default.writeSync(mermaid);
37+
output.stdout(formatInfo(output, 'mermaid code'));
38+
} catch{ /* do nothing this is a service thing */
39+
}
40+
}
41+
2342
export const controlflowCommand: ReplCommand = {
2443
description: `Get mermaid code for the control-flow graph of R code, start with '${fileProtocol}' to indicate a file`,
2544
usageExample: ':controlflow',
2645
aliases: [ 'cfg', 'cf' ],
2746
script: false,
2847
fn: async(output, shell, remainingLine) => {
29-
const result = await controlflow(shell, handleString(remainingLine));
30-
31-
const cfg = extractCFG(result.normalize, result.dataflow.graph);
32-
const mermaid = cfgToMermaid(cfg, result.normalize);
33-
output.stdout(mermaid);
34-
try {
35-
const clipboard = await import('clipboardy');
36-
clipboard.default.writeSync(mermaid);
37-
output.stdout(formatInfo(output, 'mermaid code'));
38-
} catch{ /* do nothing this is a service thing */ }
48+
await produceAndPrintCfg(shell, remainingLine, output, [], cfgToMermaid);
3949
}
4050
};
4151

52+
4253
export const controlflowStarCommand: ReplCommand = {
4354
description: 'Returns the URL to mermaid.live',
4455
usageExample: ':controlflow*',
4556
aliases: [ 'cfg*', 'cf*' ],
4657
script: false,
4758
fn: async(output, shell, remainingLine) => {
48-
const result = await controlflow(shell, handleString(remainingLine));
49-
50-
const cfg = extractCFG(result.normalize, result.dataflow.graph);
51-
const mermaid = cfgToMermaidUrl(cfg, result.normalize);
52-
output.stdout(mermaid);
53-
try {
54-
const clipboard = await import('clipboardy');
55-
clipboard.default.writeSync(mermaid);
56-
output.stdout(formatInfo(output, 'mermaid url'));
57-
} catch{ /* do nothing this is a service thing */ }
59+
await produceAndPrintCfg(shell, remainingLine, output, [], cfgToMermaidUrl);
60+
}
61+
};
62+
63+
64+
export const controlflowBBCommand: ReplCommand = {
65+
description: `Get mermaid code for the control-flow graph with basic blocks, start with '${fileProtocol}' to indicate a file`,
66+
usageExample: ':controlflowbb',
67+
aliases: [ 'cfgb', 'cfb' ],
68+
script: false,
69+
fn: async(output, shell, remainingLine) => {
70+
await produceAndPrintCfg(shell, remainingLine, output, ['to-basic-blocks'], cfgToMermaid);
71+
}
72+
};
73+
74+
75+
export const controlflowBBStarCommand: ReplCommand = {
76+
description: 'Returns the URL to mermaid.live',
77+
usageExample: ':controlflowbb*',
78+
aliases: [ 'cfgb*', 'cfb*' ],
79+
script: false,
80+
fn: async(output, shell, remainingLine) => {
81+
await produceAndPrintCfg(shell, remainingLine, output, ['to-basic-blocks' ], cfgToMermaidUrl);
5882
}
5983
};

src/cli/repl/commands/repl-commands.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
dataflowSimplifiedCommand,
1313
dataflowStarCommand
1414
} from './repl-dataflow';
15-
import { controlflowCommand, controlflowStarCommand } from './repl-cfg';
15+
import { controlflowBBCommand, controlflowBBStarCommand, controlflowCommand, controlflowStarCommand } from './repl-cfg';
1616
import type { OutputFormatter } from '../../../util/text/ansi';
1717
import { italic , bold } from '../../../util/text/ansi';
1818
import { splitAtEscapeSensitive } from '../../../util/text/args';
@@ -93,6 +93,8 @@ const _commands: Record<string, ReplCommand> = {
9393
'dataflowsimple*': dataflowSimpleStarCommand,
9494
'controlflow': controlflowCommand,
9595
'controlflow*': controlflowStarCommand,
96+
'controlflowbb': controlflowBBCommand,
97+
'controlflowbb*': controlflowBBStarCommand,
9698
'lineage': lineageCommand,
9799
'query': queryCommand,
98100
'query*': queryStarCommand

src/cli/repl/commands/repl-dataflow.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ function formatInfo(out: ReplOutput, type: string, timing: number): string {
2323
}
2424

2525
export const dataflowCommand: ReplCommand = {
26-
description: `Get mermaid code for the dataflow graph of R code, start with '${fileProtocol}' to indicate a file`,
26+
description: `Get mermaid code for the dataflow graph, start with '${fileProtocol}' to indicate a file`,
2727
usageExample: ':dataflow',
2828
aliases: [ 'd', 'df' ],
2929
script: false,
@@ -58,7 +58,7 @@ export const dataflowStarCommand: ReplCommand = {
5858

5959

6060
export const dataflowSimplifiedCommand: ReplCommand = {
61-
description: `Get simplified mermaid code for the dataflow graph of R code, start with '${fileProtocol}' to indicate a file`,
61+
description: `Get mermaid code for the simplified dataflow graph, start with '${fileProtocol}' to indicate a file`,
6262
usageExample: ':dataflowsimple',
6363
aliases: [ 'ds', 'dfs' ],
6464
script: false,

src/control-flow/cfg-properties.ts

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { setMinus } from '../util/collections/set';
44
import { log } from '../util/log';
55
import { visitCfgInOrder, visitCfgInReverseOrder } from './simple-visitor';
66

7+
/**
8+
* The collection of properties that can be checked on a control flow graph.
9+
*/
710
const CfgProperties = {
811
'single-entry-and-exit': checkSingleEntryAndExit,
912
'has-entry-and-exit': hasEntryAndExit,

src/control-flow/cfg-simplification.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export type CfgSimplificationPassName = keyof typeof CfgSimplificationPasses;
1515

1616
export const DefaultCfgSimplificationOrder = [
1717
'unique-cf-sets',
18-
'remove-dead-code'
18+
// 'remove-dead-code' // way too expensive for conventional use!
1919
// basic blocks must be requested
2020
] as const satisfies CfgSimplificationPassName[];
2121

src/control-flow/cfg-to-basic-blocks.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ function singleOutgoingFd(outgoing: ReadonlyMap<NodeId, CfgEdge> | undefined): N
1717
}
1818

1919
/**
20-
* Take a control flow graph without any basic blocks and convert it to a graph with basic blocks.
20+
* Take a control flow information of a graph without any basic blocks and convert it to a graph with basic blocks.
2121
*/
2222
export function convertCfgToBasicBlocks(cfInfo: ControlFlowInformation): ControlFlowInformation {
2323
const newCfg = wrapEveryVertexInBasicBlock(cfInfo.graph);

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

+30-13
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,18 @@ export function edgeTypeToString(type: CfgEdgeType): string {
3333
}
3434
}
3535

36+
/**
37+
* A plain vertex in the {@link ControlFlowGraph}.
38+
* Please use {@link CfgSimpleVertex} to refer to all potential vertex types within the graph.
39+
*/
3640
interface CfgBaseVertex extends MergeableRecord {
41+
/** the type of the vertex */
3742
type: CfgVertexType,
43+
/** the id of the vertex, for non-blocks this should directly relate to the AST node */
3844
id: NodeId,
45+
/** child nodes attached to this one */
3946
children?: NodeId[],
47+
/** if the vertex calls a function, this links all targets of this call */
4048
callTargets?: Set<NodeId>,
4149
}
4250

@@ -55,18 +63,19 @@ export interface CfgExpressionVertex extends CfgWithMarker {
5563
type: CfgVertexType.Expression
5664
}
5765

58-
export interface CfgMidMarkerVertex extends CfgBaseVertex {
66+
export interface CfgWithRoot extends CfgBaseVertex {
67+
/** the vertex for which this is a marker */
68+
root: NodeId
69+
}
70+
71+
export interface CfgMidMarkerVertex extends CfgWithRoot {
5972
type: CfgVertexType.MidMarker
6073
// describing the separation performed by this marker
6174
kind: string
62-
/** the vertex for which this is a mid-marker */
63-
root: NodeId
6475
}
6576

66-
export interface CfgEndMarkerVertex extends CfgBaseVertex {
77+
export interface CfgEndMarkerVertex extends CfgWithRoot {
6778
type: CfgVertexType.EndMarker
68-
/** the vertex for which this is an end-marker */
69-
root: NodeId,
7079
}
7180

7281
export interface CfgBasicBlockVertex extends CfgBaseVertex {
@@ -101,6 +110,7 @@ interface CfgControlDependencyEdge extends MergeableRecord {
101110
label: CfgEdgeType.Cd
102111
/** the id which caused the control dependency */
103112
caused: NodeId,
113+
/** is the control dependency satisfied with a true condition or is it negated (e.g., else-branch)? */
104114
when: typeof RTrue | typeof RFalse
105115
}
106116

@@ -141,26 +151,28 @@ export class ControlFlowGraph<Vertex extends CfgSimpleVertex = CfgSimpleVertex>
141151
addVertex(vertex: Vertex, rootVertex = true): this {
142152
if(this.vertexInformation.has(vertex.id)) {
143153
throw new Error(`Node with id ${vertex.id} already exists`);
144-
} else if(vertex.type === CfgVertexType.Block && vertex.elems.some(e => this.bbChildren.has(e.id) || this.rootVertices.has(e.id))) {
145-
throw new Error(`Vertex ${vertex.id} contains vertices that are already part of the graph`);
146-
}
147-
this.vertexInformation.set(vertex.id, vertex);
148-
if(vertex.type === CfgVertexType.Block) {
154+
} else if(vertex.type === CfgVertexType.Block) {
155+
if(vertex.elems.some(e => this.bbChildren.has(e.id) || this.rootVertices.has(e.id))) {
156+
throw new Error(`Vertex ${vertex.id} contains vertices that are already part of the graph`);
157+
}
149158
for(const elem of vertex.elems) {
150159
this.bbChildren.set(elem.id, vertex.id);
151160
}
152161
}
162+
this.vertexInformation.set(vertex.id, vertex);
163+
153164
if(rootVertex) {
154165
this.rootVertices.add(vertex.id);
155166
}
156167
return this;
157168
}
158169

159170
addEdge(from: NodeId, to: NodeId, edge: CfgEdge): this {
171+
const edgesFrom = this.edgeInformation.get(from) ?? new Map<NodeId, CfgEdge>();
160172
if(!this.edgeInformation.has(from)) {
161-
this.edgeInformation.set(from, new Map<NodeId, CfgEdge>());
173+
this.edgeInformation.set(from, edgesFrom);
162174
}
163-
this.edgeInformation.get(from)?.set(to, edge);
175+
edgesFrom.set(to, edge);
164176
return this;
165177
}
166178

@@ -319,14 +331,19 @@ export class ControlFlowGraph<Vertex extends CfgSimpleVertex = CfgSimpleVertex>
319331
}
320332
}
321333

334+
/** Summarizes the control information of a program */
322335
export interface ControlFlowInformation<Vertex extends CfgSimpleVertex = CfgSimpleVertex> extends MergeableRecord {
336+
/** all active 'return'(-like) unconditional jumps */
323337
returns: NodeId[],
338+
/** all active 'break'(-like) unconditional jumps */
324339
breaks: NodeId[],
340+
/** all active 'next'(-like) unconditional jumps */
325341
nexts: NodeId[],
326342
/** intended to construct a hammock graph, with 0 exit points representing a block that should not be part of the CFG (like a comment) */
327343
entryPoints: NodeId[],
328344
/** See {@link ControlFlowInformation#entryPoints|entryPoints} */
329345
exitPoints: NodeId[],
346+
/** the control flow graph summarizing the flow information */
330347
graph: ControlFlowGraph<Vertex>
331348
}
332349

+71-14
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,94 @@
1-
import type { ControlFlowInformation } from './control-flow-graph';
1+
import type { CfgExpressionVertex, CfgStatementVertex, ControlFlowInformation } from './control-flow-graph';
22
import type { NodeId } from '../r-bridge/lang-4.x/ast/model/processing/node-id';
3-
import type {
4-
NormalizedAst
5-
} from '../r-bridge/lang-4.x/ast/model/processing/decorate';
3+
64
import type { DataflowInformation } from '../dataflow/info';
7-
import type { SyntaxCfgGuidedVisitorConfiguration } from './syntax-cfg-guided-visitor';
8-
import { SyntaxGuidedCfgGuidedVisitor } from './syntax-cfg-guided-visitor';
9-
import type { DataflowGraphVertexArgument } from '../dataflow/graph/vertex';
5+
import type {
6+
DataflowGraphVertexArgument, DataflowGraphVertexFunctionCall, DataflowGraphVertexFunctionDefinition,
7+
DataflowGraphVertexUse,
8+
DataflowGraphVertexValue, DataflowGraphVertexVariableDefinition } from '../dataflow/graph/vertex';
9+
import { VertexType
10+
} from '../dataflow/graph/vertex';
11+
import type { BasicCfgGuidedVisitorConfiguration } from './basic-cfg-guided-visitor';
12+
import { BasicCfgGuidedVisitor } from './basic-cfg-guided-visitor';
13+
import { assertUnreachable } from '../util/assert';
1014

1115
export interface DataflowCfgGuidedVisitorConfiguration<
1216
Cfg extends ControlFlowInformation = ControlFlowInformation,
13-
Ast extends NormalizedAst = NormalizedAst,
1417
Dfg extends DataflowInformation = DataflowInformation
15-
> extends SyntaxCfgGuidedVisitorConfiguration<Cfg, Ast> {
18+
> extends BasicCfgGuidedVisitorConfiguration<Cfg> {
1619
readonly dataflow: Dfg;
1720
}
1821

1922
/**
20-
* This visitor extends on the {@link SyntaxGuidedCfgGuidedVisitor} by dispatching visitors based on the AST type of the node.
23+
* This visitor extends on the {@link BasicCfgGuidedVisitor} by dispatching visitors based on the dataflow graph.
2124
*
2225
* Use {@link BasicCfgGuidedVisitor#start} to start the traversal.
2326
*/
2427
export class DataflowAwareCfgGuidedVisitor<
2528
Cfg extends ControlFlowInformation = ControlFlowInformation,
26-
Ast extends NormalizedAst = NormalizedAst,
2729
Dfg extends DataflowInformation = DataflowInformation,
28-
Config extends DataflowCfgGuidedVisitorConfiguration<Cfg, Ast, Dfg> = DataflowCfgGuidedVisitorConfiguration<Cfg, Ast, Dfg>
29-
> extends SyntaxGuidedCfgGuidedVisitor<Cfg, Ast, Config> {
30+
Config extends DataflowCfgGuidedVisitorConfiguration<Cfg, Dfg> = DataflowCfgGuidedVisitorConfiguration<Cfg, Dfg>
31+
> extends BasicCfgGuidedVisitor<Cfg, Config> {
3032

3133
/**
32-
* Get the normalized AST node for the given id or fail if it does not exist.
34+
* Get the dataflow graph vertex for the given id
3335
*/
3436
protected getDataflowGraph(id: NodeId): DataflowGraphVertexArgument | undefined {
3537
return this.config.dataflow.graph.getVertex(id);
3638
}
39+
40+
41+
protected override onStatementNode(node: CfgStatementVertex): void {
42+
super.onStatementNode(node);
43+
this.onExprOrStmtNode(node);
44+
}
45+
46+
protected override onExpressionNode(node: CfgExpressionVertex): void {
47+
super.onExpressionNode(node);
48+
this.onExprOrStmtNode(node);
49+
}
50+
51+
private onExprOrStmtNode(node: CfgStatementVertex | CfgExpressionVertex): void {
52+
const dfgVertex = this.getDataflowGraph(node.id);
53+
if(!dfgVertex) {
54+
return;
55+
}
56+
57+
const tag = dfgVertex.tag;
58+
switch(tag) {
59+
case VertexType.Use:
60+
this.visitVariableUse(dfgVertex);
61+
break;
62+
case VertexType.VariableDefinition:
63+
this.visitVariableDefinition(dfgVertex);
64+
break;
65+
case VertexType.FunctionDefinition:
66+
this.visitFunctionDefinition(dfgVertex);
67+
break;
68+
case VertexType.FunctionCall:
69+
this.visitFunctionCall(dfgVertex);
70+
break;
71+
case VertexType.Value:
72+
this.visitValue(dfgVertex);
73+
break;
74+
default:
75+
assertUnreachable(tag);
76+
}
77+
}
78+
79+
protected visitValue(_val: DataflowGraphVertexValue): void {
80+
}
81+
82+
protected visitVariableUse(_use: DataflowGraphVertexUse): void {
83+
}
84+
85+
protected visitVariableDefinition(_def: DataflowGraphVertexVariableDefinition): void {
86+
}
87+
88+
protected visitFunctionDefinition(_def: DataflowGraphVertexFunctionDefinition): void {
89+
}
90+
91+
protected visitFunctionCall(_call: DataflowGraphVertexFunctionCall): void {
92+
}
93+
3794
}

0 commit comments

Comments
 (0)