Skip to content

Commit

Permalink
feat(ir): DOT dumper tests; simplify CFG construction
Browse files Browse the repository at this point in the history
  • Loading branch information
byakuren-hijiri committed Jul 29, 2024
1 parent c5269a9 commit d3e11e6
Show file tree
Hide file tree
Showing 56 changed files with 1,075 additions and 541 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,5 +134,7 @@ docs
# Generated test artifacts
test/contracts/*.config.json
test/contracts/*.json
test/contracts/*.dot
test/contracts/*.actual.out
!test/contracts/*.cfg.json
!test/contracts/*.cfg.dot
3 changes: 2 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,8 @@
"привет",
"PUSHREF",
"PUSHSLICE",
"SETINDEXVARQ"
"SETINDEXVARQ",
"Idxes"
],
"flagWords": [],
"ignorePaths": [
Expand Down
4 changes: 2 additions & 2 deletions src/internals/ir.ts
Original file line number Diff line number Diff line change
Expand Up @@ -600,7 +600,7 @@ export class CFG {
* @param astStore The store containing the AST nodes.
* @param callback The function to apply to each node.
*/
forEachNode(
public forEachNode(
astStore: TactASTStore,
callback: (stmt: AstStatement, cfgNode: Node) => void,
) {
Expand All @@ -620,7 +620,7 @@ export class CFG {
* Iterates over all edges in a CFG, applying a callback to each edge.
* @param callback The function to apply to each edge.
*/
forEachEdge(callback: (cfgEdge: Edge) => void) {
public forEachEdge(callback: (cfgEdge: Edge) => void) {
this.edges.forEach((cfgEdge) => {
callback(cfgEdge);
});
Expand Down
131 changes: 44 additions & 87 deletions src/internals/tactIRBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -567,104 +567,61 @@ export class TactIRBuilder {
* @param statements The array of AstStatement objects.
* @param nodes An optional array of Node objects to which new nodes will be added.
* @param edges An optional array of Edge objects to which new edges will be added.
* @param parentNodeIdx An optional NodeIdx representing the index of the node from which control flow enters the current sequence of statements.
* @param lastNodeIdxes An optional NodeIdx representing the index of the node from which control flow enters the current sequence of statements.
* @returns A tuple containing the arrays of Node and Edge objects representing the CFG derived from the statements.
*/
processStatements(
statements: AstStatement[],
nodes: Node[] = [],
edges: Edge[] = [],
parentNodeIdx?: NodeIdx,
lastNodeIdxes: NodeIdx[] = [],
): [Node[], Edge[]] {
let lastNodeIdx: NodeIdx | undefined = parentNodeIdx;

statements.forEach((stmt, index) => {
statements.forEach((stmt, _index) => {
const newNode = new Node(stmt.id, this.getNodeKind(stmt));
nodes.push(newNode);

// For the first node, if there's a parent node, connect this node to the parent
if (index === 0 && parentNodeIdx !== undefined) {
const edgeToParent = new Edge(parentNodeIdx, newNode.idx);
edges.push(edgeToParent);
nodes
.find((node) => node.idx === parentNodeIdx)
?.dstEdges.add(edgeToParent.idx);
newNode.srcEdges.add(edgeToParent.idx);
} else if (lastNodeIdx !== undefined) {
// Connect this node to the last node if it's not the first or has a specific parent node
const newEdge = new Edge(lastNodeIdx, newNode.idx);
edges.push(newEdge);
nodes
.find((node) => node.idx === lastNodeIdx)
?.dstEdges.add(newEdge.idx);
newNode.srcEdges.add(newEdge.idx);
// If there's a parent node, connect this node to the parent
if (lastNodeIdxes !== undefined) {
lastNodeIdxes.forEach((idx) => {
const src = this.getParent(nodes, idx);
edges.push(this.addEdge(src, newNode));
});
}

// Update the lastNodeIdx to the current node's index
lastNodeIdx = newNode.idx;

if (
stmt.kind == "statement_let" ||
stmt.kind == "statement_expression" ||
stmt.kind == "statement_assign" ||
stmt.kind == "statement_augmentedassign"
) {
// Logic for linear flow statements
// Update the lastNodeIdx to the current node's index
lastNodeIdxes = [newNode.idx];
} else if (stmt.kind === "statement_condition") {
// Branching logic for trueStatements
const [trueNodes, trueEdges] = this.processStatements(
stmt.trueStatements,
nodes,
edges,
newNode.idx,
[newNode.idx],
);
nodes = trueNodes;
edges = trueEdges;

const trueEndNode = trueNodes[trueNodes.length - 1];

if (stmt.falseStatements) {
if (stmt.falseStatements !== null && stmt.falseStatements.length > 0) {
// Branching logic for falseStatements
const [falseNodes, falseEdges] = this.processStatements(
stmt.falseStatements,
nodes,
edges,
newNode.idx,
[newNode.idx],
);
nodes = falseNodes;
edges = falseEdges;

const falseEndNode = falseNodes[falseNodes.length - 1];

const nextStmt = statements[index + 1];
if (nextStmt) {
const nextNode = new Node(nextStmt.id, this.getNodeKind(nextStmt));
nodes.push(nextNode);

const edgeToNextFromTrue = new Edge(trueEndNode.idx, nextNode.idx);
edges.push(edgeToNextFromTrue);
trueEndNode.dstEdges.add(edgeToNextFromTrue.idx);
nextNode.srcEdges.add(edgeToNextFromTrue.idx);

const edgeToNextFromFalse = new Edge(
falseEndNode.idx,
nextNode.idx,
);
edges.push(edgeToNextFromFalse);
falseEndNode.dstEdges.add(edgeToNextFromFalse.idx);
nextNode.srcEdges.add(edgeToNextFromFalse.idx);
}
lastNodeIdxes = [trueEndNode.idx, falseEndNode.idx];
} else {
const nextStmt = statements[index + 1];
if (nextStmt) {
const nextNode = new Node(nextStmt.id, this.getNodeKind(nextStmt));
nodes.push(nextNode);

const edgeToNextFromTrue = new Edge(trueEndNode.idx, nextNode.idx);
edges.push(edgeToNextFromTrue);
trueEndNode.dstEdges.add(edgeToNextFromTrue.idx);
nextNode.srcEdges.add(edgeToNextFromTrue.idx);
}
// Connect the end of the true branch to the next statement
lastNodeIdxes = [trueEndNode.idx];
}
} else if (
stmt.kind == "statement_while" ||
Expand All @@ -678,47 +635,47 @@ export class TactIRBuilder {
// Process the statements within the loop body.
const [loopNodes, loopEdges] = this.processStatements(
stmt.statements,
[],
[],
newNode.idx, // Pass the loop condition node as the parent node to link back to.
nodes,
edges,
[newNode.idx],
);

// Concatenate the loop nodes and edges with the main lists.
nodes = nodes.concat(loopNodes);
edges = edges.concat(loopEdges);
nodes = loopNodes;
edges = loopEdges;

// Create an edge from the last node in the loop back to the condition to represent the loop's cycle.
if (loopNodes.length > 0) {
const backEdge = new Edge(
loopNodes[loopNodes.length - 1].idx,
newNode.idx,
);
edges.push(backEdge);
loopNodes[loopNodes.length - 1].dstEdges.add(backEdge.idx);
newNode.srcEdges.add(backEdge.idx);
const lastNode = loopNodes[loopNodes.length - 1];
edges.push(this.addEdge(lastNode, newNode));
}

const nextStmt = statements[index + 1];
if (nextStmt) {
const nextNode = new Node(nextStmt.id, this.getNodeKind(nextStmt));
nodes.push(nextNode);
const exitEdge = new Edge(newNode.idx, nextNode.idx);
edges.push(exitEdge);
newNode.dstEdges.add(exitEdge.idx);
nextNode.srcEdges.add(exitEdge.idx);
}

lastNodeIdx = undefined;
// Connect condition with the statement after loop.
lastNodeIdxes = [newNode.idx];
} else if (stmt.kind === "statement_return") {
// No need to connect return statements to subsequent nodes
lastNodeIdx = undefined; // This effectively ends the current flow
lastNodeIdxes = [];
} else {
throw InternalException.make("Unsupported statement", { node: stmt });
}
});

return [nodes, edges];
}

private getParent(nodes: Node[], idx: NodeIdx): Node {
const node = nodes.find((node) => node.idx === idx);
if (node === undefined) {
throw InternalException.make(
`Cannot find node with index=${idx}. Available nodes: ${nodes.map((n) => n.idx)}`,
);
}
return node;
}

private addEdge(src: Node, dst: Node): Edge {
const edge = new Edge(src.idx, dst.idx);
src.dstEdges.add(edge.idx);
dst.srcEdges.add(edge.idx);
return edge;
}
}

class TactConfigManager {
Expand Down
15 changes: 15 additions & 0 deletions test/contracts/conditional-1.cfg.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
digraph "conditional-1" {
node [shape=box];
subgraph "cluster_test" {
label="test";
"test_141" [label="let a: Int = 20"];
"test_142" [label="if (a > 42)"];
"test_144" [label="a = 23"];
"test_146" [label="let b: Int = a + 1"];
"test_148" [label="return b"];
"test_141" -> "test_142";
"test_142" -> "test_144";
"test_144" -> "test_146";
"test_146" -> "test_148";
}
}
64 changes: 25 additions & 39 deletions test/contracts/conditional-1.cfg.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,86 +6,72 @@
"cfg": {
"nodes": [
{
"id": 150,
"id": 141,
"stmtID": 1531,
"srcEdges": [],
"dstEdges": [
152
143
]
},
{
"id": 151,
"id": 142,
"stmtID": 1538,
"srcEdges": [
152
143
],
"dstEdges": [
154,
158
145
]
},
{
"id": 153,
"id": 144,
"stmtID": 1537,
"srcEdges": [
154
145
],
"dstEdges": [
156
147
]
},
{
"id": 155,
"id": 146,
"stmtID": 1544,
"srcEdges": [
156
],
"dstEdges": []
},
{
"id": 157,
"stmtID": 1544,
"srcEdges": [
158
147
],
"dstEdges": [
160
149
]
},
{
"id": 159,
"id": 148,
"stmtID": 1546,
"srcEdges": [
160
149
],
"dstEdges": []
}
],
"edges": [
{
"id": 152,
"src": 150,
"dst": 151
},
{
"id": 154,
"src": 151,
"dst": 153
"id": 143,
"src": 141,
"dst": 142
},
{
"id": 156,
"src": 153,
"dst": 155
"id": 145,
"src": 142,
"dst": 144
},
{
"id": 158,
"src": 151,
"dst": 157
"id": 147,
"src": 144,
"dst": 146
},
{
"id": 160,
"src": 157,
"dst": 159
"id": 149,
"src": 146,
"dst": 148
}
]
}
Expand Down
1 change: 0 additions & 1 deletion test/contracts/conditional-1.tact
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,3 @@ fun test(): Int {
let b: Int = a + 1;
return b;
}

16 changes: 16 additions & 0 deletions test/contracts/conditional-2.cfg.dot
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
digraph "conditional-2" {
node [shape=box];
subgraph "cluster_test" {
label="test";
"test_141" [label="let a: Int = 20"];
"test_142" [label="if (a > 42)"];
"test_144" [label="a = 23"];
"test_146" [label="a = 22"];
"test_148" [label="return a"];
"test_141" -> "test_142";
"test_142" -> "test_144";
"test_142" -> "test_146";
"test_144" -> "test_148";
"test_146" -> "test_148";
}
}
Loading

0 comments on commit d3e11e6

Please sign in to comment.