/* * Copyright 2008 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.google.gwt.dev.jjs.impl; import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JArrayRef; import com.google.gwt.dev.jjs.ast.JBinaryOperation; import com.google.gwt.dev.jjs.ast.JBinaryOperator; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JFieldRef; import com.google.gwt.dev.jjs.ast.JIntLiteral; import com.google.gwt.dev.jjs.ast.JLocal; import com.google.gwt.dev.jjs.ast.JLocalRef; import com.google.gwt.dev.jjs.ast.JLongLiteral; import com.google.gwt.dev.jjs.ast.JModVisitor; import com.google.gwt.dev.jjs.ast.JNode; import com.google.gwt.dev.jjs.ast.JParameterRef; import com.google.gwt.dev.jjs.ast.JPostfixOperation; import com.google.gwt.dev.jjs.ast.JPrefixOperation; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JThisRef; import com.google.gwt.dev.jjs.ast.JUnaryOperator; import com.google.gwt.dev.jjs.ast.js.JMultiExpression; /** * <p> * Replace problematic compound assignments with a sequence of simpler * operations, all of which are either simple assignments or are non-assigning * operations. When doing so, be careful that side effects happen exactly once * and that the order of any side effects is preserved. The choice of which * assignments to replace is made in subclasses; they must override the three * <code>shouldBreakUp()</code> methods. * </p> * * <p> * Note that because AST nodes are mutable, they cannot be reused in different * parts of the same tree. Instead, the node must be cloned before each * insertion into a tree other than the first. * </p> * * <p> * If the <code>reuseTemps</code> constructor parameter is set to * <code>true</code>, then temps of the correct type will be reused once they * become available. To determine when a temp can be reused, the current * implementation uses a notion of "temp usage scopes". Every time a temporary * variable is allocated, it is recorded in the current temp usage scope. Once * the current temp usage scope is exited, all of its temps become available for * use for other purposes. * </p> */ public abstract class CompoundAssignmentNormalizer { /** * Breaks apart certain complex assignments. */ private class BreakupAssignOpsVisitor extends TempLocalVisitor { /** * Replaces side effects in lvalue. */ private class ReplaceSideEffectsInLvalue extends JModVisitor { private final JMultiExpression multi; ReplaceSideEffectsInLvalue(JMultiExpression multi) { this.multi = multi; } public JMultiExpression getMultiExpr() { return multi; } @Override public boolean visit(JArrayRef x, Context ctx) { JExpression newInstance = possiblyReplace(x.getInstance()); JExpression newIndexExpr = possiblyReplace(x.getIndexExpr()); if (newInstance != x.getInstance() || newIndexExpr != x.getIndexExpr()) { JArrayRef newExpr = new JArrayRef(x.getSourceInfo(), newInstance, newIndexExpr); ctx.replaceMe(newExpr); } return false; } @Override public boolean visit(JFieldRef x, Context ctx) { if (x.getInstance() != null) { JExpression newInstance = possiblyReplace(x.getInstance()); if (newInstance != x.getInstance()) { JFieldRef newExpr = new JFieldRef(x.getSourceInfo(), newInstance, x.getField(), x.getEnclosingType()); ctx.replaceMe(newExpr); } } return false; } @Override public boolean visit(JLocalRef x, Context ctx) { return false; } @Override public boolean visit(JParameterRef x, Context ctx) { return false; } @Override public boolean visit(JThisRef x, Context ctx) { return false; } private JExpression possiblyReplace(JExpression x) { if (!x.hasSideEffects()) { return x; } // Create a temp local JLocal tempLocal = createTempLocal(x.getSourceInfo(), x.getType()); // Create an assignment for this temp and add it to multi. JLocalRef tempRef = new JLocalRef(x.getSourceInfo(), tempLocal); JBinaryOperation asg = new JBinaryOperation(x.getSourceInfo(), x.getType(), JBinaryOperator.ASG, tempRef, x); multi.exprs.add(asg); // Update me with the temp return cloner.cloneExpression(tempRef); } } @Override public void endVisit(JBinaryOperation x, Context ctx) { JBinaryOperator op = x.getOp(); if (op.getNonAssignmentOf() == null) { return; } if (!shouldBreakUp(x)) { return; } /* * Convert to an assignment and binary operation. Since the left hand size * must be computed twice, we have to replace any left-hand side * expressions that could have side effects with temporaries, so that they * are only run once. */ ReplaceSideEffectsInLvalue replacer = new ReplaceSideEffectsInLvalue(new JMultiExpression(x.getSourceInfo())); JExpression newLhs = replacer.accept(x.getLhs()); JExpression operation = new JBinaryOperation(x.getSourceInfo(), newLhs.getType(), op.getNonAssignmentOf(), newLhs, x.getRhs()); operation = modifyResultOperation((JBinaryOperation) operation); // newLhs is cloned below because it was used in operation JBinaryOperation asg = new JBinaryOperation(x.getSourceInfo(), newLhs.getType(), JBinaryOperator.ASG, cloner .cloneExpression(newLhs), operation); JMultiExpression multiExpr = replacer.getMultiExpr(); if (multiExpr.exprs.isEmpty()) { // just use the split assignment expression ctx.replaceMe(asg); } else { // add the assignment as the last item in the multi multiExpr.exprs.add(asg); ctx.replaceMe(multiExpr); } } @Override public void endVisit(JPostfixOperation x, Context ctx) { JUnaryOperator op = x.getOp(); if (!op.isModifying()) { return; } if (!shouldBreakUp(x)) { return; } // Convert into a comma operation, such as: // (t = x, x += 1, t) // First, replace the arg with a non-side-effect causing one. JMultiExpression multi = new JMultiExpression(x.getSourceInfo()); ReplaceSideEffectsInLvalue replacer = new ReplaceSideEffectsInLvalue(multi); JExpression newArg = replacer.accept(x.getArg()); JExpression expressionReturn = expressionToReturn(newArg); // Now generate the appropriate expressions. JLocal tempLocal = createTempLocal(x.getSourceInfo(), expressionReturn.getType()); // t = x JLocalRef tempRef = new JLocalRef(x.getSourceInfo(), tempLocal); JBinaryOperation asg = new JBinaryOperation(x.getSourceInfo(), x.getType(), JBinaryOperator.ASG, tempRef, expressionReturn); multi.exprs.add(asg); // x += 1 asg = createAsgOpFromUnary(newArg, op); // Break the resulting asg op before adding to multi. multi.exprs.add(accept(asg)); // t tempRef = new JLocalRef(x.getSourceInfo(), tempLocal); multi.exprs.add(tempRef); ctx.replaceMe(multi); } @Override public void endVisit(JPrefixOperation x, Context ctx) { JUnaryOperator op = x.getOp(); if (!op.isModifying()) { return; } if (!shouldBreakUp(x)) { return; } // Convert into the equivalent binary assignment operation, such as: // x += 1 JBinaryOperation asg = createAsgOpFromUnary(x.getArg(), op); // Visit the result to break it up even more. ctx.replaceMe(accept(asg)); } private JBinaryOperation createAsgOpFromUnary(JExpression arg, JUnaryOperator op) { JBinaryOperator newOp; if (op == JUnaryOperator.INC) { newOp = JBinaryOperator.ASG_ADD; } else if (op == JUnaryOperator.DEC) { newOp = JBinaryOperator.ASG_SUB; } else { throw new InternalCompilerException("Unexpected modifying unary operator: " + String.valueOf(op.getSymbol())); } JExpression one; if (arg.getType() == JPrimitiveType.LONG) { // use an explicit long, so that LongEmulationNormalizer does not get // confused one = JLongLiteral.get(1); } else { // int is safe to add to all other types one = JIntLiteral.get(1); } // arg is cloned below because the caller is allowed to use it somewhere JBinaryOperation asg = new JBinaryOperation(arg.getSourceInfo(), arg.getType(), newOp, cloner .cloneExpression(arg), one); return asg; } } private final CloneExpressionVisitor cloner = new CloneExpressionVisitor(); public void accept(JNode node) { BreakupAssignOpsVisitor breaker = new BreakupAssignOpsVisitor(); breaker.accept(node); } /** * Decide what expression to return when breaking up a compound assignment of * the form <code>lhs op= rhs</code>. By default the <code>lhs</code> is * returned. */ protected JExpression expressionToReturn(JExpression lhs) { return lhs; } /** * Decide what expression to return when breaking up a compound assignment of * the form <code>lhs op= rhs</code>. The breakup creates an expression of the * form <code>lhs = lhs op rhs</code>, and the right hand side of the newly * created expression is passed to this method. */ protected JExpression modifyResultOperation(JBinaryOperation op) { return op; } protected abstract boolean shouldBreakUp(JBinaryOperation x); protected abstract boolean shouldBreakUp(JPostfixOperation x); protected abstract boolean shouldBreakUp(JPrefixOperation x); }