com.google.gwt.dev.jjs.impl.CompoundAssignmentNormalizer Maven / Gradle / Ivy
/*
* 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.SourceInfo;
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.JMethodBody;
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.JType;
import com.google.gwt.dev.jjs.ast.JUnaryOperator;
import com.google.gwt.dev.jjs.ast.js.JMultiExpression;
/**
*
* 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
* shouldBreakUp()
methods.
*
*
*
* 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.
*
*/
public abstract class CompoundAssignmentNormalizer {
/**
* Breaks apart certain complex assignments.
*/
private class BreakupAssignOpsVisitor extends JModVisitorWithTemporaryVariableCreation {
/**
* 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.addExpressions(asg);
// Update me with the temp
return cloner.cloneExpression(tempRef);
}
}
@Override
protected String newTemporaryLocalName(SourceInfo info, JType type, JMethodBody methodBody) {
return CompoundAssignmentNormalizer.this.newTemporaryLocalName(info, type, methodBody);
}
@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.isEmpty()) {
// just use the split assignment expression
ctx.replaceMe(asg);
} else {
// add the assignment as the last item in the multi
multiExpr.addExpressions(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.addExpressions(asg);
// x += 1
asg = createAsgOpFromUnary(newArg, op);
// Break the resulting asg op before adding to multi.
multi.addExpressions(accept(asg));
// t
tempRef = new JLocalRef(x.getSourceInfo(), tempLocal);
multi.addExpressions(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);
}
// Name to assign to temporaries. All temporaries are created with the same name, which is
// not a problem as they are referred to by reference.
// {@link GenerateJavaScriptAst.FixNameClashesVisitor} will resolve into unique names when
// needed.
private static final String TEMP_LOCAL_NAME = "$tmp";
/**
* Gets a new temporary local variable name in {@code methodBody}. Locals might have duplicate
* names as they are always referred to by reference.
* {@link GenerateJavaScriptAST} will attempt coalesce variables of same name.
*
* Subclasses might decide on different approaches to naming local temporaries.
*/
protected String newTemporaryLocalName(SourceInfo info, JType type, JMethodBody methodBody) {
return TEMP_LOCAL_NAME;
}
/**
* Decide what expression to return when breaking up a compound assignment of
* the form lhs op= rhs
. By default the lhs
is
* returned.
*/
protected JExpression expressionToReturn(JExpression lhs) {
return lhs;
}
/**
* Decide what expression to return when breaking up a compound assignment of
* the form lhs op= rhs
. The breakup creates an expression of the
* form lhs = lhs op rhs
, 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);
}