@specs-feup/clava
Version:
A C/C++ source-to-source compiler written in Typescript
272 lines • 12.6 kB
JavaScript
import { debug } from "@specs-feup/lara/api/lara/core/LaraCore.js";
import { BinaryOp, Call, Case, DeclStmt, EmptyStmt, ExprStmt, LabelStmt, MemberCall, ReturnStmt, Scope, Statement, TernaryOp, UnaryOp, Vardecl, } from "../../Joinpoints.js";
import ClavaJoinPoints from "../ClavaJoinPoints.js";
import DecomposeResult from "./DecomposeResult.js";
/**
* Decomposes complex statements into several simpler ones.
*/
export default class StatementDecomposer {
tempPrefix;
startIndex;
constructor(tempPrefix = "decomp_", startIndex = 0) {
this.tempPrefix = tempPrefix;
this.startIndex = startIndex;
}
newTempVarname() {
const varName = `${this.tempPrefix}${this.startIndex}`;
this.startIndex++;
return varName;
}
/**
* Some Joinpoints might generate invalid code under certain conditions. This method checks
* for those cases and adds an empty statement to the AST to fix them.
*
* @param $jp - Joinpoint to be scrutinized
*/
ensureValidNode($jp) {
if (!($jp instanceof Statement) || $jp instanceof EmptyStmt) {
const parentStmt = $jp.getAncestor("statement");
if (parentStmt === undefined)
return;
this.ensureValidNode(parentStmt);
return;
}
// If preceeding statement is a CaseStmt or a LabelStmt it might generate invalid code if a declaration is inserted
// Add empty statement after the label to avoid this situation
const $leftStmt = $jp.leftJp;
if ($leftStmt === undefined ||
(!($leftStmt instanceof Case) && !($leftStmt instanceof LabelStmt)))
return;
debug(`StatementDecomposer: statement just before label, inserting empty statement after as a precaution`);
$leftStmt.insertAfter(ClavaJoinPoints.emptyStmt());
}
/**
* If the given statement can be decomposed in two or more statements, replaces the statement with the decomposition.
*
* @param $stmt - A statement that will be decomposed.
*/
decomposeAndReplace($stmt) {
const stmts = this.decompose($stmt);
// No statements to replace
if (stmts.length === 0) {
return;
}
// Replace stmt with array of statements
$stmt.replaceWith(stmts);
}
/**
* @param $stmt - A statement that will be decomposed.
* @returns An array with the new statements, or an empty array if no decomposition could be made
*/
decompose($stmt) {
try {
return this.decomposeStmt($stmt);
}
catch (e) {
console.log(`StatementDecomposer: ${String(e)}`);
return [];
}
}
decomposeStmt($stmt) {
// Unsupported
if ($stmt instanceof Scope || $stmt.joinPointType === "statement") {
debug(`StatementDecomposer: skipping scope or generic statement join point`);
return [];
}
this.ensureValidNode($stmt);
if ($stmt instanceof ExprStmt) {
return this.decomposeExprStmt($stmt);
}
if ($stmt instanceof ReturnStmt) {
return this.decomposeReturnStmt($stmt);
}
if ($stmt instanceof DeclStmt) {
return this.decomposeDeclStmt($stmt);
}
debug(`StatementDecomposer: not implemented for statement of type ${$stmt.joinPointType}`);
return [];
}
decomposeExprStmt($stmt) {
// Statement represents an expression
const $expr = $stmt.expr;
const { precedingStmts, succeedingStmts } = this.decomposeExpr($expr);
return [...precedingStmts, ...succeedingStmts];
}
decomposeReturnStmt($stmt) {
// Return may contain an expression
const $expr = $stmt.returnExpr;
if ($expr === undefined) {
return [];
}
const { precedingStmts, $resultExpr } = this.decomposeExpr($expr);
const $newReturnStmt = ClavaJoinPoints.returnStmt($resultExpr);
return [...precedingStmts, $newReturnStmt];
}
decomposeDeclStmt($stmt) {
// declStmt can have one or more declarations
const $decls = $stmt.decls;
return $decls.flatMap(($decl) => this.decomposeDecl($decl));
}
decomposeDecl($decl) {
if (!($decl instanceof Vardecl)) {
debug(`StatementDecomposer.decomposeDeclStmt: not implemented for decl of type ${$decl.joinPointType}`);
return [ClavaJoinPoints.declStmt($decl)];
}
// If vardecl has init, decompose expression
if ($decl.hasInit) {
const decomposeResult = this.decomposeExpr($decl.init);
//expr = newStmts.concat(decomposeResult.stmts);
$decl.init = decomposeResult.$resultExpr;
return [
...decomposeResult.precedingStmts,
ClavaJoinPoints.declStmt($decl),
...decomposeResult.succeedingStmts,
];
}
return [ClavaJoinPoints.declStmt($decl)];
}
decomposeExpr($expr) {
this.ensureValidNode($expr);
if ($expr instanceof BinaryOp) {
return this.decomposeBinaryOp($expr);
}
if ($expr instanceof UnaryOp) {
return this.decomposeUnaryOp($expr);
}
if ($expr instanceof TernaryOp) {
return this.decomposeTernaryOp($expr);
}
if ($expr instanceof Call) {
return this.decomposeCall($expr);
}
if ($expr.numChildren === 0) {
return new DecomposeResult([], $expr);
}
debug(`StatementDecomposer: decomposition not implemented for type ${$expr.joinPointType}. Returning '${$expr.code}'as is`);
return new DecomposeResult([], $expr);
}
decomposeCall($call) {
const argResults = $call.args.map(($arg) => this.decomposeExpr($arg));
const precedingStmts = argResults.flatMap((res) => res.precedingStmts);
const succeedingStmts = argResults.flatMap((res) => res.succeedingStmts);
const newArgs = argResults.map((res) => res.$resultExpr);
const $newCall = this.copyCall($call, newArgs);
//const $newCall = ClavaJoinPoints.call($call.function, ...newArgs);
// Desugaring type, to avoid possible problems of code generation of more complex types
// E.g. for vector.size(), currently is generating code without the qualifier
const desugaredReturnType = $newCall.type.desugarAll;
// If call is inside an exprStmt, and exprStmt is inside a scope, just convert new call to statement
// The scope test is to avoid wrong code in situations such as loop headers
if ($call.parent !== undefined &&
$call.parent instanceof ExprStmt &&
$call.parent.parent !== undefined &&
$call.parent.parent instanceof Scope) {
// Using .exprStmt() to ensure a new statement is created.
// .stmt might not create a new statement, and interfere with detaching the previous stmt
const newStmts = [...precedingStmts, ClavaJoinPoints.exprStmt($newCall)];
return new DecomposeResult(newStmts, $newCall, []);
}
// Otherwise, create a variable to store the result of the call
// and return the variable as the value expression
const tempVarname = this.newTempVarname();
const tempVarDecl = ClavaJoinPoints.varDeclNoInit(tempVarname, desugaredReturnType);
const tempVarAssign = ClavaJoinPoints.assign(ClavaJoinPoints.varRef(tempVarDecl), $newCall);
return new DecomposeResult([...precedingStmts, tempVarDecl.stmt, tempVarAssign.stmt], ClavaJoinPoints.varRef(tempVarDecl), succeedingStmts);
}
copyCall($call, newArgs) {
// Instance method
if ($call instanceof MemberCall) {
// Copy node
const $newCall = $call.copy();
// Update args
// TODO: use a kind of .setArgs that replaces all
for (let i = 0; i < newArgs.length; i++) {
$newCall.setArg(i, newArgs[i]);
}
return $newCall;
}
// Default
return ClavaJoinPoints.call($call.function, ...newArgs);
}
decomposeBinaryOp($binaryOp) {
if ($binaryOp.isAssignment) {
return this.decomposeAssignment($binaryOp);
}
// Apply decompose to both sides
const leftResult = this.decomposeExpr($binaryOp.left);
const rightResult = this.decomposeExpr($binaryOp.right);
// Create operation with result of decomposition
const $newExpr = ClavaJoinPoints.binaryOp($binaryOp.kind, leftResult.$resultExpr, rightResult.$resultExpr, $binaryOp.type);
// Create declaration statement with result to new temporary variable
const tempVarname = this.newTempVarname();
const tempVarDecl = ClavaJoinPoints.varDeclNoInit(tempVarname, $binaryOp.type);
const tempVarAssign = ClavaJoinPoints.assign(ClavaJoinPoints.varRef(tempVarDecl), $newExpr);
const precedingStmts = [
...leftResult.precedingStmts,
...rightResult.precedingStmts,
tempVarDecl.stmt,
tempVarAssign.stmt,
];
const succeedingStmts = [
...leftResult.succeedingStmts,
...rightResult.succeedingStmts,
];
return new DecomposeResult(precedingStmts, ClavaJoinPoints.varRef(tempVarDecl), succeedingStmts);
}
decomposeAssignment($assign) {
// Get statements of right hand-side
const rightResult = this.decomposeExpr($assign.right);
const $newAssign = $assign.operator === "="
? ClavaJoinPoints.assign($assign.left, rightResult.$resultExpr)
: ClavaJoinPoints.compoundAssign($assign.operator, $assign.left, rightResult.$resultExpr);
const $assignExpr = ClavaJoinPoints.exprStmt($newAssign);
return new DecomposeResult([...rightResult.precedingStmts, $assignExpr], $assign.left, rightResult.succeedingStmts);
}
decomposeTernaryOp($ternaryOp) {
const condResult = this.decomposeExpr($ternaryOp.cond);
const trueResult = this.decomposeExpr($ternaryOp.trueExpr);
const falseResult = this.decomposeExpr($ternaryOp.falseExpr);
const tempVarname = this.newTempVarname();
const tempVarDecl = ClavaJoinPoints.varDeclNoInit(tempVarname, $ternaryOp.type);
// assign the value of the new temp variable with an if-else statement
// to maintain the semantics of only evaluating the expression that
// falls on the right side of the ternary.
// we do not want side-effects to be executed without regard to the branch
// taken
const $thenBody = ClavaJoinPoints.scope(...condResult.succeedingStmts, ...trueResult.precedingStmts, ClavaJoinPoints.assign(ClavaJoinPoints.varRef(tempVarDecl), trueResult.$resultExpr), ...trueResult.succeedingStmts);
const $elseBody = ClavaJoinPoints.scope(...condResult.succeedingStmts, ...falseResult.precedingStmts, ClavaJoinPoints.assign(ClavaJoinPoints.varRef(tempVarDecl), falseResult.$resultExpr), ...falseResult.succeedingStmts);
const $ifStmt = ClavaJoinPoints.ifStmt(condResult.$resultExpr, $thenBody, $elseBody);
const precedingStmts = [
tempVarDecl.stmt,
...condResult.precedingStmts,
$ifStmt,
];
return new DecomposeResult(precedingStmts, ClavaJoinPoints.varRef(tempVarDecl));
}
decomposeUnaryOp($unaryOp) {
const kind = $unaryOp.kind;
// only decompose increment / decrement operations, separating the change
// from the result of the change
if (kind !== "post_dec" &&
kind !== "post_inc" &&
kind !== "pre_dec" &&
kind !== "pre_inc") {
return new DecomposeResult([], $unaryOp, []);
}
switch (kind) {
case "post_dec":
case "post_inc": {
const $innerExpr = $unaryOp.operand.copy();
const succeedingStmts = [ClavaJoinPoints.exprStmt($unaryOp)];
return new DecomposeResult([], $innerExpr, succeedingStmts);
}
case "pre_dec":
case "pre_inc": {
const $innerExpr = $unaryOp.operand.copy();
const precedingStmts = [ClavaJoinPoints.exprStmt($unaryOp)];
return new DecomposeResult(precedingStmts, $innerExpr, []);
}
}
}
}
//# sourceMappingURL=StatementDecomposer.js.map