diff --git a/ClavaLaraApi/src-java/pt/up/fe/specs/clava/weaver/LaraApiResource.java b/ClavaLaraApi/src-java/pt/up/fe/specs/clava/weaver/LaraApiResource.java index 86e9c25ef..be9320556 100644 --- a/ClavaLaraApi/src-java/pt/up/fe/specs/clava/weaver/LaraApiResource.java +++ b/ClavaLaraApi/src-java/pt/up/fe/specs/clava/weaver/LaraApiResource.java @@ -102,9 +102,11 @@ public enum LaraApiResource implements LaraResourceProvider { CFG_CASE_DATA("graphs/cfg/nodedata/CaseData.js"), CFG_DATA_FACTORY("graphs/cfg/nodedata/DataFactory.js"), + CFG_GOTO_DATA("graphs/cfg/nodedata/GotoData.js"), CFG_HEADER_DATA("graphs/cfg/nodedata/HeaderData.js"), CFG_IF_DATA("graphs/cfg/nodedata/IfData.js"), CFG_INST_LIST_NODE_DATA("graphs/cfg/nodedata/InstListNodeData.js"), + CFG_LABEL_DATA("graphs/cfg/nodedata/LabelData.js"), CFG_LOOP_DATA("graphs/cfg/nodedata/LoopData.js"), CFG_SCOPE_NODE_DATA("graphs/cfg/nodedata/ScopeNodeData.js"), CFG_SWITCH_DATA("graphs/cfg/nodedata/SwitchData.js"), diff --git a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/ControlFlowGraph.js b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/ControlFlowGraph.js index 18bbfa4f1..3f4b853a2 100644 --- a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/ControlFlowGraph.js +++ b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/ControlFlowGraph.js @@ -25,8 +25,20 @@ class ControlFlowGraph extends Graph { this.#endNode = endNode; } - static build($jp, deterministicIds = false, splitInstList = false) { - const builderResult = new CfgBuilder($jp, deterministicIds, splitInstList).build(); + /** + * Builds the control flow graph + * + * @param {joinpoint} $jp + * @param {boolean} [deterministicIds = false] If true, uses deterministic ids for the graph ids (e.g. id_0, id_1...). Otherwise, uses $jp.astId whenever possible + * @param {Object} [options = {}] An object containing configuration options for the cfg + * @param {boolean} [options.splitInstList = false] If true, statements of each instruction list must be split + * @param {boolean} [options.removeGotoNodes = false] If true, the nodes that correspond to goto statements will be excluded from the resulting graph + * @param {boolean} [options.removeLabelNodes = false] If true, the nodes that correspond to label statements will be excluded from the resulting graph + * + * @returns {ControlFlowGraph} a new instance of the ControlFlowGraph class + */ + static build($jp, deterministicIds = false, options = {}) { + const builderResult = new CfgBuilder($jp, deterministicIds, options).build(); return new ControlFlowGraph(...builderResult); } diff --git a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgBuilder.js b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgBuilder.js index d2534622b..004db79f7 100644 --- a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgBuilder.js +++ b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgBuilder.js @@ -62,20 +62,35 @@ class CfgBuilder { #nextNodes; /** - * Indicates whether an instruction list should be split + * {boolean} If true, each instruction list should be split */ #splitInstList; + /** + * {boolean} If true, the goto nodes should be excluded from the graph + */ + #removeGotoNodes; + + /** + * {boolean} If true, the label nodes should be excluded from the graph + */ + #removeLabelNodes; + /** * Creates a new instance of the CfgBuilder class * @param {joinpoint} $jp - * @param {boolean} [splitInstList = false] If true, statements of each instruction list must be split * @param {boolean} [deterministicIds = false] If true, uses deterministic ids for the graph ids (e.g. id_0, id_1...). Otherwise, uses $jp.astId whenever possible - */ - constructor($jp, deterministicIds = false, splitInstList = false) { + * @param {Object} [options = {}] An object containing configuration options for the cfg + * @param {boolean} [options.splitInstList = false] If true, statements of each instruction list must be split + * @param {boolean} [options.removeGotoNodes = false] If true, the nodes that correspond to goto statements will be excluded from the resulting graph + * @param {boolean} [options.removeLabelNodes = false] If true, the nodes that correspond to label statements will be excluded from the resulting graph + */ + constructor($jp, deterministicIds = false, options = {}) { this.#jp = $jp; - this.#splitInstList = splitInstList; this.#deterministicIds = deterministicIds; + this.#splitInstList = options.splitInstList || false; + this.#removeGotoNodes = options.removeGotoNodes || false; + this.#removeLabelNodes = options.removeLabelNodes || false; this.#currentId = 0; this.#dataFactory = new DataFactory(this.#jp); this.#graph = Graphs.newGraph(); @@ -278,18 +293,9 @@ class CfgBuilder { */ #connectBreakNode(node) { const $breakStmt = node.data().nodeStmt; - const $loop = $breakStmt.ancestor("loop"); - const $switch = $breakStmt.ancestor("switch"); - const loopDepth = $loop !== undefined ? $loop.depth : -1; - const switchDepth = $switch !== undefined ? $switch.depth : -1; - let afterNode = undefined; - - if (loopDepth > switchDepth) - // Statement is used to terminate a loop - afterNode = this.#nextNodes.nextExecutedNode($loop); - // Statement is used to exit a switch block - else afterNode = this.#nextNodes.nextExecutedNode($switch); + const $enclosingStmt = $breakStmt.enclosingStmt; + const afterNode = this.#nextNodes.nextExecutedNode($enclosingStmt); this.#addEdge(node, afterNode, CfgEdgeType.UNCONDITIONAL); } @@ -424,6 +430,28 @@ class CfgBuilder { this.#addEdge(node, afterNode, CfgEdgeType.UNCONDITIONAL); } + /** + * @param {Cytoscape.node} node node whose type is "GOTO" + */ + #connectGotoNode(node) { + const $gotoStmt = node.data().nodeStmt; + const labelName = $gotoStmt.label.name; + const $labelStmt = Query.searchFromInclusive(this.#jp, "labelStmt", {decl: decl => decl.name == labelName}).first(); + + const afterNode = this.#nodes.get($labelStmt.astId); + this.#addEdge(node, afterNode, CfgEdgeType.UNCONDITIONAL); + } + + /** + * @param {Cytoscape.node} node node whose type is "LABEL" + */ + #connectLabelNode(node) { + const $labelStmt = node.data().nodeStmt; + + const afterNode = this.#nextNodes.nextExecutedNode($labelStmt); + this.#addEdge(node, afterNode, CfgEdgeType.UNCONDITIONAL); + } + /** * Connects a node associated with a statement that is an instance of a "return" statement. * @param {Cytoscape.node} node node whose type is "RETURN" @@ -510,6 +538,12 @@ class CfgBuilder { case CfgNodeType.INST_LIST: this.#connectInstListNode(node); break; + case CfgNodeType.GOTO: + this.#connectGotoNode(node); + break; + case CfgNodeType.LABEL: + this.#connectLabelNode(node); + break; case CfgNodeType.RETURN: this.#connectReturnNode(node); break; @@ -585,6 +619,38 @@ class CfgBuilder { ); } + // Remove label nodes + if (this.#removeLabelNodes) { + for (const node of this.#graph.nodes()) { + // Only nodes whose type is "LABEL" + if (node.data().type !== CfgNodeType.LABEL) + continue; + + Graphs.removeNode( + this.#graph, + node, + (incoming, outgoing) => new CfgEdge(incoming.data().type) + ); + this.#nodes.delete(node.data().nodeStmt.astId); + } + } + + // Remove goto nodes + if (this.#removeGotoNodes) { + for (const node of this.#graph.nodes()) { + // Only nodes whose type is "GOTO" + if (node.data().type !== CfgNodeType.GOTO) + continue; + + Graphs.removeNode( + this.#graph, + node, + (incoming, outgoing) => new CfgEdge(incoming.data().type) + ); + this.#nodes.delete(node.data().nodeStmt.astId); + } + } + // Remove nodes that have no incoming edge and are not start for (const node of this.#graph.nodes()) { // Only nodes that are not start diff --git a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgNodeType.js b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgNodeType.js index 7a123657a..f42b70596 100644 --- a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgNodeType.js +++ b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgNodeType.js @@ -18,6 +18,8 @@ class CfgNodeType { static CONTINUE = new CfgNodeType("CONTINUE"); static SWITCH = new CfgNodeType("SWITCH"); static CASE = new CfgNodeType("CASE"); + static GOTO = new CfgNodeType("GOTO"); + static LABEL = new CfgNodeType("LABEL"); static RETURN = new CfgNodeType("RETURN"); // To add: WHILE, DOWHILE? diff --git a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgUtils.js b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgUtils.js index e2ea50a92..4ae0ed0a0 100644 --- a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgUtils.js +++ b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/CfgUtils.js @@ -42,11 +42,21 @@ class CfgUtils { return CfgNodeType.SWITCH; } - //Case stmt + // Case stmt if ($stmt.instanceOf("case")) { return CfgNodeType.CASE; } + // Goto stmt + if ($stmt.instanceOf("gotoStmt")) { + return CfgNodeType.GOTO; + } + + // Label stmt + if ($stmt.instanceOf("labelStmt")) { + return CfgNodeType.LABEL; + } + // Return stmt if ($stmt.instanceOf("returnStmt")) { return CfgNodeType.RETURN; diff --git a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/CaseData.js b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/CaseData.js index 871306cb6..7f82c0313 100644 --- a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/CaseData.js +++ b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/CaseData.js @@ -16,9 +16,7 @@ class CaseData extends CfgNodeData { } toString() { - if (this.case.isDefault) - return "default"; - return "case " + this.case.values.map(value => value.code).join(" || "); + return this.case.code; } isBranch() { diff --git a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/DataFactory.js b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/DataFactory.js index a6f267e26..e392119a4 100644 --- a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/DataFactory.js +++ b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/DataFactory.js @@ -7,6 +7,8 @@ laraImport("clava.graphs.cfg.nodedata.HeaderData"); laraImport("clava.graphs.cfg.nodedata.IfData"); laraImport("clava.graphs.cfg.nodedata.SwitchData"); laraImport("clava.graphs.cfg.nodedata.CaseData"); +laraImport("clava.graphs.cfg.nodedata.GotoData"); +laraImport("clava.graphs.cfg.nodedata.LabelData"); laraImport("clava.graphs.cfg.nodedata.ReturnData"); class DataFactory { @@ -38,6 +40,10 @@ class DataFactory { return new SwitchData($stmt, id); case CfgNodeType.CASE: return new CaseData($stmt, id); + case CfgNodeType.GOTO: + return new GotoData($stmt, id); + case CfgNodeType.LABEL: + return new LabelData($stmt, id); case CfgNodeType.RETURN: return new ReturnData($stmt, id); default: diff --git a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/GotoData.js b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/GotoData.js new file mode 100644 index 000000000..80fc671c1 --- /dev/null +++ b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/GotoData.js @@ -0,0 +1,17 @@ +laraImport("clava.graphs.cfg.CfgNodeData"); +laraImport("clava.graphs.cfg.CfgNodeType"); + +class GotoData extends CfgNodeData { + //#stmt + + constructor($stmt, id) { + super(CfgNodeType.GOTO, $stmt, id); + + //this.#stmt = $stmt + } + + toString() { + return this.nodeStmt.code; + } + +} diff --git a/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/LabelData.js b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/LabelData.js new file mode 100644 index 000000000..bab15963c --- /dev/null +++ b/ClavaLaraApi/src-lara-clava/clava/clava/graphs/cfg/nodedata/LabelData.js @@ -0,0 +1,17 @@ +laraImport("clava.graphs.cfg.CfgNodeData"); +laraImport("clava.graphs.cfg.CfgNodeType"); + +class LabelData extends CfgNodeData { + //#stmt + + constructor($stmt, id) { + super(CfgNodeType.LABEL, $stmt, id); + + //this.#stmt = $stmt + } + + toString() { + return this.nodeStmt.code; + } + +}