@specs-feup/clava
Version:
A C/C++ source-to-source compiler written in Typescript
231 lines (204 loc) • 7.02 kB
text/typescript
import cytoscape from "@specs-feup/lara/api/libs/cytoscape-3.26.0.js";
import { Case, Expression, If, Statement, Switch } from "../../Joinpoints.js";
import ControlFlowGraph from "../graphs/ControlFlowGraph.js";
import CfgNodeData from "../graphs/cfg/CfgNodeData.js";
import CfgNodeType from "../graphs/cfg/CfgNodeType.js";
import LivenessUtils from "./LivenessUtils.js";
export default class LivenessAnalyser {
/**
* A Cytoscape graph representing the CFG
*/
private cfg: cytoscape.Core;
/**
* Maps each CFG node ID to the corresponding def set
*/
private defs: Map<string, Set<string>>;
/**
* Maps each CFG node ID to the corresponding use set
*/
private uses: Map<string, Set<string>>;
/**
* Maps each CFG node ID to the corresponding LiveIn set
*/
private liveIn: Map<string, Set<string>>;
/**
* Maps each CFG node ID to the corresponding LiveOut set
*/
private liveOut: Map<string, Set<string>>;
/**
* Creates a new instance of the LivenessAnalyser class
* @param cfg - The control flow graph. Can be either a Cytoscape graph or a ControlFlowGraph object.
*/
constructor(cfg: ControlFlowGraph | cytoscape.Core) {
this.cfg = this.validateCfg(cfg);
this.defs = new Map();
this.uses = new Map();
this.liveIn = new Map();
this.liveOut = new Map();
}
/**
* Checks if the given control flow graph is a Cytoscape graph or a ControlFlowGraph object.
* Additionally, verifies if each instruction list node contains only one statement.
* @param cfg - The control flow graph to be validated
*/
private validateCfg(cfg: ControlFlowGraph | cytoscape.Core): cytoscape.Core {
if (cfg instanceof ControlFlowGraph) cfg = cfg.graph;
else if (!LivenessUtils.isCytoscapeGraph(cfg))
throw new Error(
"'cfg' must be a Cytoscape graph or a ControlFlowGraph object."
);
for (const node of cfg.nodes()) {
const nodeData: CfgNodeData = node.data() as CfgNodeData;
if (nodeData.type === CfgNodeType.INST_LIST && nodeData.stmts.length > 1)
throw new Error(
"Each instruction list node of the control flow graph must contain only one statement."
);
}
return cfg;
}
/**
* Computes the def, use, live in and live out sets of each CFG node
* @returns An array that contains the def, use, live in and live out of each CFG node.
*/
analyse(): [
Map<string, Set<string>>,
Map<string, Set<string>>,
Map<string, Set<string>>,
Map<string, Set<string>>,
] {
this.computeDefs();
this.computeUses();
this.computeLiveInOut();
return [this.defs, this.uses, this.liveIn, this.liveOut];
}
/**
*
* @param $jp -
* @returns The def set for the given joinpoint
*/
private computeDef($jp: Statement | Expression | undefined): Set<string> {
if ($jp === undefined) {
throw new Error("Joinpoint is undefined");
}
const declaredVars = LivenessUtils.getVarDeclsWithInit($jp);
const assignedVars = LivenessUtils.getAssignedVars($jp);
return LivenessUtils.unionSets(declaredVars, assignedVars);
}
/**
*
* @param $jp -
* @returns The use set for the given joinpoint
*/
private computeUse($jp: Statement | Expression | undefined): Set<string> {
if ($jp === undefined) {
throw new Error("Joinpoint is undefined");
}
return LivenessUtils.getVarRefs($jp);
}
/**
* Computes the def set of each CFG node according to its type
*/
private computeDefs() {
for (const node of this.cfg.nodes()) {
const nodeData: CfgNodeData = node.data() as CfgNodeData;
const $nodeStmt = nodeData.nodeStmt;
const nodeType = nodeData.type;
let def: Set<string>;
switch (nodeType) {
case CfgNodeType.START:
case CfgNodeType.END:
case CfgNodeType.SCOPE:
case CfgNodeType.THEN:
case CfgNodeType.ELSE:
case CfgNodeType.LOOP:
case CfgNodeType.SWITCH:
case CfgNodeType.CASE:
def = new Set<string>();
break;
case CfgNodeType.IF:
def = this.computeDef(($nodeStmt as If | undefined)?.cond);
break;
default:
def = this.computeDef($nodeStmt);
}
this.defs.set(node.id(), def);
}
}
/**
* Computes the use set of each CFG node according to its type
*/
private computeUses() {
for (const node of this.cfg.nodes()) {
const nodeData: CfgNodeData = node.data() as CfgNodeData;
const $nodeStmt = nodeData.nodeStmt;
const nodeType = nodeData.type;
let use: Set<string>;
switch (nodeType) {
case CfgNodeType.START:
case CfgNodeType.END:
case CfgNodeType.SCOPE:
case CfgNodeType.THEN:
case CfgNodeType.ELSE:
case CfgNodeType.LOOP:
use = new Set<string>();
break;
case CfgNodeType.IF:
use = this.computeUse(($nodeStmt as If | undefined)?.cond);
break;
case CfgNodeType.SWITCH:
use = this.computeUse(($nodeStmt as Switch | undefined)?.condition);
break;
case CfgNodeType.CASE: {
const $switchCondition = (
($nodeStmt as Case | undefined)?.getAncestor("switch") as Switch
).condition;
use = ($nodeStmt as Case | undefined)?.isDefault
? new Set<string>()
: this.computeUse($switchCondition);
break;
}
default:
use = this.computeUse($nodeStmt);
}
this.uses.set(node.id(), use);
}
}
/**
* Computes the LiveIn and LiveOut set of each CFG node
*/
private computeLiveInOut() {
for (const node of this.cfg.nodes()) {
this.liveIn.set(node.id(), new Set());
this.liveOut.set(node.id(), new Set());
}
let liveChanged;
do {
liveChanged = false;
for (const node of this.cfg.nodes()) {
const def = this.defs.get(node.id());
const use = this.uses.get(node.id());
// Save current liveIn and liveOut
const oldLiveIn = this.liveIn.get(node.id());
const oldLiveOut = this.liveOut.get(node.id());
// Compute and save new liveIn
const diff = LivenessUtils.differenceSets(oldLiveOut, def);
const newLiveIn = LivenessUtils.unionSets(use, diff);
this.liveIn.set(node.id(), newLiveIn);
// Compute and save new liveOut
let newLiveOut = new Set<string>();
for (const child of LivenessUtils.getChildren(node)) {
const childId = child.id();
const childLiveIn = this.liveIn.get(childId);
newLiveOut = LivenessUtils.unionSets(newLiveOut, childLiveIn);
}
this.liveOut.set(node.id(), newLiveOut);
// Update liveChanged
if (
!LivenessUtils.isSameSet(oldLiveIn, newLiveIn) ||
!LivenessUtils.isSameSet(oldLiveOut, newLiveOut)
)
liveChanged = true;
}
} while (liveChanged);
}
}