@specs-feup/clava
Version:
A C/C++ source-to-source compiler written in Typescript
644 lines (583 loc) • 17.5 kB
text/typescript
import Query from "@specs-feup/lara/api/weaver/Query.js";
import {
ArrayAccess,
BinaryOp,
Expression,
FunctionJp,
Joinpoint,
Loop,
MemberAccess,
Param,
Scope,
Statement,
UnaryOp,
Vardecl,
Varref,
} from "../../Joinpoints.js";
import Logger from "../../lara/code/Logger.js";
import ClavaJoinPoints from "../ClavaJoinPoints.js";
const interfaces: Record<string, string[]> = {};
const locals: Record<string, string[]> = {};
const defCounters = ["const", "temp", "op", "mux", "ne"];
const CM = '\\"';
const NL = "\\n";
let logger: Logger;
/**
* Source code assumptions:
* - Arrays have predetermined size
* - No pointers
*/
export default class TraceInstrumentation {
static instrument(funName: string): void {
//Get function root
let root;
const filename = funName + ".dot";
logger = new Logger(undefined, filename);
for (const elem of Query.search(FunctionJp).chain()) {
if ((elem["function"] as FunctionJp).name == funName)
root = elem["function"];
}
if (root == undefined) {
console.log("Function " + funName + " not found, terminating...");
return;
}
//Get scope and interface
let scope;
for (let i = 0; i < root.children.length; i++) {
const child = root.children[i];
if (child instanceof Scope) {
scope = child;
}
if (child instanceof Param) {
registerInterface(child);
}
}
if (scope == undefined) {
console.log("Function " + funName + " has no scope, terminating...");
return;
}
const children = scope.children;
//Get global vars as interfaces
for (const elem of Query.search(Vardecl)) {
if (elem.isGlobal) registerInterface(elem);
}
//Get local vars
for (const elem of Query.search(FunctionJp, { name: funName }).search(
Vardecl
)) {
registerLocal(elem);
}
//Begin graph and create counters
const firstOp = children[0];
logger.text("Digraph G {\\n").log(firstOp, true);
createSeparator(firstOp);
createDefaultCounters(firstOp);
for (const entry in locals) {
declareLocalCounter(firstOp, entry);
}
for (const entry in interfaces) {
declareInterfaceCounter(firstOp, entry);
}
//Load all interfaces
for (const inter in interfaces) {
initializeInterface(firstOp, inter);
}
createSeparator(firstOp);
//Go through each statement and generate nodes and edges
explore(children);
//Close graph
logger.text("}").log(children[scope.children.length - 1], true);
}
}
function explore(children: Joinpoint[]): void {
for (let i = 0; i < children.length; i++) {
const child = children[i];
if (child instanceof Statement) {
if (child.children.length == 1) {
handleStatement(child.children[0]);
}
if (child.children.length > 1) {
if (child.children[0] instanceof Vardecl) {
for (let j = 0; j < child.children.length; j++)
handleStatement(child.children[j]);
}
}
}
if (child instanceof Loop) {
explore(child.children[3].children.slice());
}
}
}
//--------------------
// Counters
//--------------------
function createDefaultCounters(node: Joinpoint): void {
for (let i = 0; i < defCounters.length; i++)
node.insertBefore(
ClavaJoinPoints.stmtLiteral("int n_" + defCounters[i] + " = 0;")
);
}
function declareInterfaceCounter(node: Joinpoint, name: string): void {
const info = interfaces[name];
const init = info.length == 1 ? " = 0;" : " = {0};";
node.insertBefore(
ClavaJoinPoints.stmtLiteral("int " + getCounterOfVar(name, info) + init)
);
}
function declareLocalCounter(node: Joinpoint, name: string): void {
const info = locals[name];
const init = info.length == 1 ? " = 0;" : " = {0};";
node.insertBefore(
ClavaJoinPoints.stmtLiteral("int " + getCounterOfVar(name, info) + init)
);
}
function splitMulti(str: string, tokens: string[]): string[] {
const tempChar = tokens[0];
for (let i = 1; i < tokens.length; i++) {
str = str.split(tokens[i]).join(tempChar);
}
const strParts = str.split(tempChar);
const newStr = [];
for (let i = 0; i < strParts.length; i++) {
if (strParts[i] != "") newStr.push(strParts[i]);
}
return newStr;
}
function filterCommonKeywords(
value: string,
index: number,
arr: string[]
): boolean {
const common = ["const", "static", "unsigned"];
return !common.includes(value);
}
function registerInterface(param: Param): void {
let tokens = splitMulti(param.code, [" ", "]", "["]);
if (tokens.indexOf("=") != -1) tokens = tokens.slice(0, tokens.indexOf("="));
tokens = tokens.filter(filterCommonKeywords);
const interName = tokens[1];
tokens.splice(1, 1);
if (interfaces[interName] != undefined) return;
interfaces[interName] = tokens;
}
function registerLocal(local: Vardecl): void {
let tokens = splitMulti(local.code, [" ", "]", "["]);
if (tokens.indexOf("=") != -1) tokens = tokens.slice(0, tokens.indexOf("="));
tokens = tokens.filter(filterCommonKeywords);
const locName = tokens[1];
tokens.splice(1, 1);
if (interfaces[locName] != undefined) return;
if (locals[locName] != undefined) return;
locals[locName] = tokens;
}
function getCounterOfVar(name: string, info: string[]): string {
let str = "n_" + name;
for (let i = 1; i < info.length; i++) str += "[" + info[i] + "]";
return str;
}
function isLocal(varName: string): boolean {
return locals[varName] != undefined;
}
function isInterface(varName: string): boolean {
return interfaces[varName] != undefined;
}
//--------------------
// Create nodes
//--------------------
function refCounter(name: string): Varref {
name = "n_" + name;
return ClavaJoinPoints.varRef(name, ClavaJoinPoints.builtinType("int"));
}
function refArrayCounter(name: string, indexes: string[]): Statement {
name = "n_" + name;
for (let i = 0; i < indexes.length; i++) name += "[" + indexes[i] + "]";
return ClavaJoinPoints.stmtLiteral(name);
}
function refAnyExpr(code: string): Statement {
return ClavaJoinPoints.stmtLiteral(code);
}
function incrementCounter(
node: Joinpoint,
variable: string,
indexes?: string[]
): void {
if (indexes != undefined) {
let access = "";
for (let i = 0; i < indexes.length; i++) {
access += "[" + indexes[i] + "]";
}
node.insertBefore(
ClavaJoinPoints.stmtLiteral("n_" + variable + access + "++;")
);
} else {
node.insertBefore(ClavaJoinPoints.stmtLiteral("n_" + variable + "++;"));
}
}
function storeVar(node: Joinpoint, variable: string): void {
incrementCounter(node, variable);
const att1 = "var";
let att2 = "";
let att3 = "";
if (isLocal(variable)) {
att2 = "loc";
att3 = locals[variable][0];
}
if (isInterface(variable)) {
att2 = "inte";
att3 = interfaces[variable][0];
}
logger
.text(CM + variable + "_")
.int(refCounter(variable))
.text(
CM +
" [label=" +
CM +
variable +
CM +
", att1=" +
att1 +
", att2=" +
att2 +
", att3=" +
att3 +
"];" +
NL
)
.log(node, true);
}
function storeArray(
node: Joinpoint,
variable: string,
indexes: string[]
): void {
incrementCounter(node, variable, indexes);
const att1 = "var";
let att2 = "";
let att3 = "";
if (isLocal(variable)) {
att2 = "loc";
att3 = locals[variable][0];
}
if (isInterface(variable)) {
att2 = "inte";
att3 = interfaces[variable][0];
}
logger.text(CM + variable);
for (let i = 0; i < indexes.length; i++) {
logger.text("[").int(refAnyExpr(indexes[i])).text("]");
}
logger
.text("_")
.int(refArrayCounter(variable, indexes))
.text("_l" + CM);
logger.text(" [label=" + CM + variable);
for (let i = 0; i < indexes.length; i++) {
logger.text("[").int(refAnyExpr(indexes[i])).text("]");
}
logger
.text(
CM + ", att1=" + att1 + ", att2=" + att2 + ", att3=" + att3 + "];" + NL
)
.log(node, true);
}
function createOp(node: Joinpoint, op: string): void {
incrementCounter(node, "op");
logger
.text(CM + "op")
.int(refCounter("op"))
.text(CM + " [label=" + CM + op + CM + ", att1=op];" + NL)
.log(node, true);
}
function createMux(node: Joinpoint): void {
//TODO: include OP attribute?
incrementCounter(node, "mux");
logger
.text(CM + "mux")
.int(refCounter("mux"))
.text(CM + " [label=" + CM + "mux")
.int(refCounter("mux"))
.text(CM + ", att1=mux];" + NL)
.log(node, true);
}
function createConst(node: Joinpoint, num: string): void {
incrementCounter(node, "const");
logger
.text(CM + "const")
.int(refCounter("const"))
.text(CM + " [label=" + CM + parseInt(num) + CM + ", att1=const];" + NL)
.log(node, true);
}
function createTemp(node: Joinpoint, type: string): void {
incrementCounter(node, "temp");
logger
.text(CM + "temp")
.int(refCounter("temp"))
.text(CM + " [label=" + CM + "temp")
.int(refCounter("temp"))
.text(CM + ", att1=var, att2=loc, att3=" + type + "];" + NL)
.log(node, true);
}
function createSeparator(node: Joinpoint): void {
const separator = "//---------------------";
node.insertBefore(ClavaJoinPoints.stmtLiteral(separator));
}
//--------------------
// Create edges
//--------------------
function createEdge(
node: Joinpoint,
source: string[],
dest: string[],
pos?: string,
offset?: string
): void {
incrementCounter(node, "ne");
logger.text(CM);
if (source.length == 1) {
if (defCounters.includes(source[0])) {
if (source[0] == "temp" && offset != undefined)
logger.text(source[0]).int(refAnyExpr("n_temp" + offset));
else logger.text(source[0]).int(refCounter(source[0]));
} else logger.text(source[0]).text("_").int(refCounter(source[0]));
} else {
logger.text(source[0]);
for (let i = 1; i < source.length; i++)
logger.text("[").int(refAnyExpr(source[i])).text("]");
logger
.text("_")
.int(refArrayCounter(source[0], source.slice(1)))
.text("_l");
}
logger.text(CM + " -> " + CM);
if (dest.length == 1) {
if (defCounters.includes(dest[0])) {
if (dest[0] == "temp" && offset != undefined)
logger.text(dest[0]).int(refAnyExpr("n_temp" + offset));
else logger.text(dest[0]).int(refCounter(dest[0]));
} else logger.text(dest[0]).text("_").int(refCounter(dest[0]));
} else {
logger.text(dest[0]);
for (let i = 1; i < dest.length; i++)
logger.text("[").int(refAnyExpr(dest[i])).text("]");
logger
.text("_")
.int(refArrayCounter(dest[0], dest.slice(1)))
.text("_l");
}
logger
.text(CM + " [label=" + CM)
.int(refCounter("ne"))
.text(CM + ", ord=" + CM)
.int(refCounter("ne"));
if (pos != undefined) logger.text(CM + ", pos=" + CM + pos);
logger.text(CM + "];" + NL).log(node, true);
}
//--------------------
// Handle statements
//--------------------
function handleStatement(node: Joinpoint): void {
createSeparator(node);
if (node instanceof Vardecl) handleVardecl(node);
if (node instanceof BinaryOp) handleAssign(node);
if (node instanceof UnaryOp) handleUnaryOp(node);
createSeparator(node);
}
function handleVardecl(node: Vardecl): void {
//Not contemplating arrays, TODO if necessary
if (node.children.length != 1) return;
const info = getInfo(node.children[0]);
storeVar(node, node.name);
createEdge(node, info, [node.name]);
}
function handleAssign(node: BinaryOp): void {
//Build rhs node(s)
let rhsInfo = getInfo(node.right);
//Build lhs node
const lhsInfo = getInfo(node.left);
//If assignment, load extra node and build op
if (node.kind.includes("_")) {
const opKind = mapOperation(node.kind);
createOp(node, opKind);
if (rhsInfo[0] == "temp" && lhsInfo[0] == "temp")
createEdge(node, rhsInfo, ["op"], "r", "-1");
else createEdge(node, rhsInfo, ["op"], "r");
createEdge(node, lhsInfo, ["op"], "l");
rhsInfo = ["op"];
}
if (node.left instanceof ArrayAccess) {
storeArray(node, lhsInfo[0], lhsInfo.slice(1));
}
if (node.left instanceof Varref) {
storeVar(node, lhsInfo[0]);
}
//Create assignment edge
createEdge(node, rhsInfo, lhsInfo);
}
function handleUnaryOp(node: UnaryOp): string[] | void {
if (node.kind.includes("pre") || node.kind.includes("post")) {
createOp(node, mapOperation(node.kind));
const info = handleVarref(node.children[0] as Varref);
createConst(node, "1");
createEdge(node, ["const"], ["op"], "r");
createEdge(node, info, ["op"], "l");
storeVar(node, info[0]);
createEdge(node, ["op"], info);
} else return ["const"];
}
function handleVarref(node: Varref): string[] {
return [node.name];
}
function handleArrayAccess(node: ArrayAccess): string[] {
const info = [(node.arrayVar as Varref | MemberAccess).name];
for (let i = 0; i < node.subscript.length; i++)
info.push(node.subscript[i].code);
return info;
}
function handleBinaryOp(node: BinaryOp): string[] {
//Build rhs
const rhsInfo = getInfo(node.right);
//Build lhs
const lhsInfo = getInfo(node.left);
//Build op
const opKind = mapOperation(node.kind);
createOp(node, opKind);
if (rhsInfo[0] == "temp" && lhsInfo[0] == "temp")
createEdge(node, rhsInfo, ["op"], "r", "-1");
else createEdge(node, rhsInfo, ["op"], "r");
createEdge(node, lhsInfo, ["op"], "l");
//Build temp
createTemp(node, "float");
createEdge(node, ["op"], ["temp"]);
return ["temp"];
}
function getInfo(node: Joinpoint): string[] {
let info = ["null"];
if (node instanceof Varref) info = handleVarref(node);
if (node instanceof BinaryOp) info = handleBinaryOp(node);
if (node instanceof ArrayAccess) info = handleArrayAccess(node);
if (node instanceof Expression) info = handleExpression(node);
return info;
}
function handleExpression(node: Joinpoint): string[] {
if (node.children.length == 3 && node.children[0] instanceof Expression) {
//Build comparison
const cmpInfo = getInfo(node.children[0].children[0]);
//Build true value
const trueInfo = getInfo(node.children[1]);
//Build false value
const falseInfo = getInfo(node.children[2]);
//Build multiplexer
createMux(node);
createEdge(node, cmpInfo, ["mux"], "sel");
createEdge(node, trueInfo, ["mux"], "t");
createEdge(node, falseInfo, ["mux"], "f");
return ["mux"];
}
if (node.children.length == 1) return getInfo(node.children[0]);
if (node.children.length == 0) {
createConst(node, node.code);
return ["const"];
}
return ["null"];
}
//--------------------
// Utils
//--------------------
function mapOperation(op: string): string {
switch (op) {
case "mul":
return "*";
case "div":
return "/";
case "rem":
return "%";
case "add":
return "+";
case "sub":
return "-";
case "shl":
return "<<";
case "shr":
return ">>";
case "cmp":
return "cmp";
case "lt":
return "<";
case "gt":
return ">";
case "le":
return "<=";
case "ge":
return ">=";
case "eq":
return "==";
case "ne":
return "!=";
case "and":
return "&";
case "xor":
return "^";
case "or":
return "|";
case "l_and":
return "&&";
case "l_or":
return "||";
case "assign":
return "=";
case "mul_assign":
return "*";
case "rem_assign":
return "%";
case "add_assign":
return "+";
case "sub_assign":
return "-";
case "shl_assign":
return "<<";
case "shr_assign":
return ">>";
case "and_assign":
return "&&";
case "xor_assign":
return "^";
case "or_assign":
return "||";
case "post_int":
return "+";
case "post_dec":
return "-";
case "pre_inc":
return "+";
case "pre_dec":
return "-";
}
return op;
}
function initializeInterface(node: Joinpoint, variable: string): void {
let level = 0;
let stmt = "";
const info = [];
const indexes = ["_i", "_j", "_k", "_l", "_m", "_n"];
for (let i = 1; i < interfaces[variable].length; i++) {
stmt +=
"for (int " +
indexes[level] +
" = 0; " +
indexes[level] +
" < " +
interfaces[variable][i] +
"; " +
indexes[level] +
"++) {\n";
info.push(indexes[level]);
level++;
}
node.insertBefore(ClavaJoinPoints.stmtLiteral(stmt));
if (level != 0) storeArray(node, variable, info);
else storeVar(node, variable);
stmt = "";
for (let i = 0; i < level; i++) stmt += "}\n";
node.insertBefore(ClavaJoinPoints.stmtLiteral(stmt));
}