| /* |
| * 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.SourceInfo; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperation; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperator; |
| import com.google.gwt.dev.jjs.ast.JBlock; |
| import com.google.gwt.dev.jjs.ast.JBooleanLiteral; |
| import com.google.gwt.dev.jjs.ast.JBreakStatement; |
| import com.google.gwt.dev.jjs.ast.JCaseStatement; |
| import com.google.gwt.dev.jjs.ast.JCastOperation; |
| import com.google.gwt.dev.jjs.ast.JCharLiteral; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JConditional; |
| import com.google.gwt.dev.jjs.ast.JContinueStatement; |
| import com.google.gwt.dev.jjs.ast.JDeclarationStatement; |
| import com.google.gwt.dev.jjs.ast.JDeclaredType; |
| import com.google.gwt.dev.jjs.ast.JDoStatement; |
| import com.google.gwt.dev.jjs.ast.JDoubleLiteral; |
| import com.google.gwt.dev.jjs.ast.JEnumField; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JExpressionStatement; |
| import com.google.gwt.dev.jjs.ast.JField; |
| import com.google.gwt.dev.jjs.ast.JFieldRef; |
| import com.google.gwt.dev.jjs.ast.JFloatLiteral; |
| import com.google.gwt.dev.jjs.ast.JForStatement; |
| import com.google.gwt.dev.jjs.ast.JIfStatement; |
| import com.google.gwt.dev.jjs.ast.JInstanceOf; |
| import com.google.gwt.dev.jjs.ast.JIntLiteral; |
| import com.google.gwt.dev.jjs.ast.JLiteral; |
| import com.google.gwt.dev.jjs.ast.JLocalRef; |
| import com.google.gwt.dev.jjs.ast.JLongLiteral; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JNewInstance; |
| 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.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| import com.google.gwt.dev.jjs.ast.JStatement; |
| import com.google.gwt.dev.jjs.ast.JStringLiteral; |
| import com.google.gwt.dev.jjs.ast.JSwitchStatement; |
| import com.google.gwt.dev.jjs.ast.JTryStatement; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JUnaryOperator; |
| import com.google.gwt.dev.jjs.ast.JValueLiteral; |
| import com.google.gwt.dev.jjs.ast.JVariableRef; |
| import com.google.gwt.dev.jjs.ast.JVisitor; |
| import com.google.gwt.dev.jjs.ast.JWhileStatement; |
| import com.google.gwt.dev.jjs.ast.RuntimeConstants; |
| import com.google.gwt.dev.jjs.ast.js.JMultiExpression; |
| import com.google.gwt.dev.util.Ieee754_64_Arithmetic; |
| import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger; |
| import com.google.gwt.dev.util.log.speedtracer.SpeedTracerLogger.Event; |
| import com.google.gwt.thirdparty.guava.common.annotations.VisibleForTesting; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap; |
| import com.google.gwt.thirdparty.guava.common.collect.Lists; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import java.lang.reflect.Method; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * Removes certain kinds of dead code, and simplifies certain expressions. This |
| * pass focuses on intraprocedural optimizations, that is, optimizations that |
| * can be performed inside of a single method body (and usually a single |
| * expression) rather than global transformations. Global optimizations done in |
| * other passes will feed into this, however. |
| */ |
| public class DeadCodeElimination { |
| |
| /** |
| * Eliminates dead or unreachable code when possible, and makes local |
| * simplifications like changing "<code>x || true</code>" to "<code>x</code>". |
| * |
| * This visitor should perform all of its optimizations in a single pass. |
| * Except in rare cases, running this pass multiple times should produce no |
| * changes after the first pass. The only currently known exception to this |
| * rule is in {@link #endVisit(JNewInstance, Context)}, where the target |
| * constructor may be non-empty at the beginning of DCE and become empty |
| * during the run, which potentially unlocks optimizations at call sites. |
| * |
| * TODO: leverage ignoring expression output more to remove intermediary |
| * operations in favor of pure side effects. |
| * |
| * TODO: move more simplifications into methods like |
| * {@link #cast(JExpression, SourceInfo, JType, JExpression) simplifyCast}, so |
| * that more simplifications can be made on a single pass through a tree. |
| */ |
| public class DeadCodeVisitor extends JChangeTrackingVisitor { |
| |
| public DeadCodeVisitor(OptimizerContext optimizerCtx) { |
| super(optimizerCtx); |
| } |
| |
| /** |
| * Expressions whose result does not matter. A parent node should add any |
| * children whose result does not matter to this set during the parent's |
| * <code>visit()</code> method. It should then remove those children during |
| * its own <code>endVisit()</code>. |
| * |
| * TODO: there's a latent bug here: some immutable nodes (such as literals) |
| * can be multiply referenced in the AST. In theory, one reference to that |
| * node could be put into this set while another reference actually contains |
| * a result that is needed. In practice this is okay at the moment since the |
| * existing uses of <code>ignoringExpressionOutput</code> are with mutable |
| * nodes. |
| */ |
| private final Set<JExpression> ignoringExpressionOutput = Sets.newHashSet(); |
| |
| /** |
| * Expressions being used as lvalues. |
| */ |
| private final Set<JExpression> lvalues = Sets.newHashSet(); |
| |
| private final Set<JBlock> switchBlocks = Sets.newHashSet(); |
| |
| private final JMethod isScriptMethod = program.getIndexedMethod(RuntimeConstants.GWT_IS_SCRIPT); |
| private final JMethod enumOrdinalMethod = |
| program.getIndexedMethod(RuntimeConstants.ENUM_ORDINAL); |
| private final JField enumOrdinalField = |
| program.getIndexedField(RuntimeConstants.ENUM_ORDINAL); |
| |
| /** |
| * Short circuit binary operations. |
| */ |
| @Override |
| public void endVisit(JBinaryOperation x, Context ctx) { |
| JBinaryOperator op = x.getOp(); |
| JExpression lhs = x.getLhs(); |
| JExpression rhs = x.getRhs(); |
| |
| // If either parameter is a multiexpression, restructure if possible. |
| if (isNonEmptyMultiExpression(lhs)) { |
| // Push the operation inside the multiexpression. This exposes other optimization |
| // opportunities for latter passes e.g: |
| // |
| // (a(), b(), 1) + 2 ==> (a(), b(), 1 + 2) ==> (a(), b(), 3) |
| // |
| // There is no need to consider the symmetric case as it requires that all expression in the |
| // rhs (except the last one) to be side effect free, in which case they will end up being |
| // removed anyway. |
| |
| List<JExpression> expressions = ((JMultiExpression) lhs).getExpressions(); |
| JMultiExpression result = new JMultiExpression(lhs.getSourceInfo(), |
| expressions.subList(0, expressions.size() - 1)); |
| result.addExpressions(new JBinaryOperation(x.getSourceInfo(), x.getType(), x.getOp(), |
| expressions.get(expressions.size() - 1), rhs)); |
| ctx.replaceMe(result); |
| return; |
| } |
| |
| if (isNonEmptyMultiExpression(rhs) && lhs instanceof JValueLiteral && |
| op != JBinaryOperator.AND && op != JBinaryOperator.OR) { |
| // Push the operation inside the multiexpression if the lhs is a value literal. |
| // This exposes other optimization opportunities for latter passes e.g: |
| // |
| // 2 + (a(), b(), 1) ==> (a(), b(), 2 + 1) ==> (a(), b(), 3) |
| // |
| // And exception must be made for || and &&, as these operations might not evaluate the |
| // rhs due to shortcutting. |
| List<JExpression> expressions = ((JMultiExpression) rhs).getExpressions(); |
| JMultiExpression result = new JMultiExpression(rhs.getSourceInfo(), |
| expressions.subList(0, expressions.size() - 1)); |
| result.addExpressions(new JBinaryOperation(x.getSourceInfo(), x.getType(), x.getOp(), |
| lhs, expressions.get(expressions.size() - 1))); |
| ctx.replaceMe(result); |
| return; |
| } |
| |
| if ((lhs instanceof JValueLiteral) && (rhs instanceof JValueLiteral)) { |
| if (evalOpOnLiterals(op, (JValueLiteral) lhs, (JValueLiteral) rhs, ctx)) { |
| return; |
| } |
| } |
| |
| switch (op) { |
| case AND: |
| maybeReplaceMe(x, Simplifier.simplifyAnd(x), ctx); |
| break; |
| case OR: |
| maybeReplaceMe(x, Simplifier.simplifyOr(x), ctx); |
| break; |
| case BIT_XOR: |
| simplifyXor(lhs, rhs, ctx); |
| break; |
| case EQ: |
| simplifyEq(lhs, rhs, ctx, false); |
| break; |
| case NEQ: |
| simplifyEq(lhs, rhs, ctx, true); |
| break; |
| case ADD: |
| simplifyAdd(lhs, rhs, ctx, x.getType()); |
| break; |
| case CONCAT: |
| evalConcat(x.getSourceInfo(), lhs, rhs, ctx); |
| break; |
| case SUB: |
| simplifySub(lhs, rhs, ctx, x.getType()); |
| break; |
| case MUL: |
| simplifyMul(lhs, rhs, ctx, x.getType()); |
| break; |
| case DIV: |
| simplifyDiv(lhs, rhs, ctx, x.getType()); |
| break; |
| case SHL: |
| case SHR: |
| case SHRU: |
| if (isLiteralZero(rhs)) { |
| ctx.replaceMe(lhs); |
| } |
| break; |
| default: |
| if (op.isAssignment()) { |
| lvalues.remove(lhs); |
| } |
| break; |
| } |
| } |
| |
| private boolean isNonEmptyMultiExpression(JExpression expression) { |
| return expression instanceof JMultiExpression && |
| !((JMultiExpression) expression).getExpressions().isEmpty(); |
| } |
| |
| /** |
| * Prune dead statements and empty blocks. |
| */ |
| @Override |
| public void endVisit(JBlock x, Context ctx) { |
| // Switch blocks require special optimization code |
| if (switchBlocks.contains(x)) { |
| return; |
| } |
| |
| /* |
| * Remove any dead statements after an abrupt change in code flow and |
| * promote safe statements within nested blocks to this block. |
| */ |
| for (int i = 0; i < x.getStatements().size(); i++) { |
| JStatement stmt = x.getStatements().get(i); |
| |
| if (stmt instanceof JBlock) { |
| /* |
| * Promote a sub-block's children to the current block, unless the |
| * sub-block contains local declarations as children. |
| */ |
| JBlock block = (JBlock) stmt; |
| if (canPromoteBlock(block)) { |
| x.removeStmt(i); |
| x.addStmts(i, block.getStatements()); |
| i--; |
| madeChanges(); |
| continue; |
| } |
| } |
| |
| if (stmt instanceof JExpressionStatement) { |
| JExpressionStatement stmtExpr = (JExpressionStatement) stmt; |
| if (stmtExpr.getExpr() instanceof JMultiExpression) { |
| // Promote a multi's expressions to the current block |
| x.removeStmt(i); |
| int start = i; |
| JMultiExpression multi = ((JMultiExpression) stmtExpr.getExpr()); |
| for (JExpression expr : multi.getExpressions()) { |
| x.addStmt(i++, expr.makeStatement()); |
| } |
| i = start - 1; |
| continue; |
| } |
| } |
| |
| if (stmt.unconditionalControlBreak()) { |
| // Abrupt change in flow, chop the remaining items from this block |
| for (int j = i + 1; j < x.getStatements().size();) { |
| x.removeStmt(j); |
| madeChanges(); |
| } |
| } |
| } |
| |
| if (ctx.canRemove() && x.getStatements().size() == 0) { |
| // Remove blocks with no effect |
| ctx.removeMe(); |
| } |
| } |
| |
| @Override |
| public void endVisit(JCastOperation x, Context ctx) { |
| maybeReplaceMe(x, Simplifier.simplifyCast(x), ctx); |
| } |
| |
| @Override |
| public void endVisit(JConditional x, Context ctx) { |
| maybeReplaceMe(x, Simplifier.simplifyConditional(x), ctx); |
| } |
| |
| @Override |
| public void endVisit(JDeclarationStatement x, Context ctx) { |
| JVariableRef variableRef = x.getVariableRef(); |
| lvalues.remove(variableRef); |
| } |
| |
| /** |
| * Convert do { } while (false); into a block. |
| */ |
| @Override |
| public void endVisit(JDoStatement x, Context ctx) { |
| JExpression expression = x.getTestExpr(); |
| if (expression instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) expression; |
| |
| // If false, replace do with do's body |
| if (!booleanLiteral.getValue()) { |
| if (JjsUtils.isEmptyBlock(x.getBody())) { |
| ctx.removeMe(); |
| } else { // Unless it contains break/continue statements |
| FindBreakContinueStatementsVisitor visitor = new FindBreakContinueStatementsVisitor(); |
| visitor.accept(x.getBody()); |
| if (!visitor.hasBreakContinueStatements()) { |
| ctx.replaceMe(x.getBody()); |
| } |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void endVisit(JExpressionStatement x, Context ctx) { |
| ignoringExpressionOutput.remove(x.getExpr()); |
| if (!x.getExpr().hasSideEffects()) { |
| removeMe(x, ctx); |
| } |
| } |
| |
| @Override |
| public void endVisit(JFieldRef x, Context ctx) { |
| |
| if (x.getField() == enumOrdinalField) { |
| maybeReplaceWithOrdinalValue(x.getInstance(), ctx); |
| return; |
| } |
| |
| JLiteral literal = tryGetConstant(x); |
| if (literal == null && !ignoringExpressionOutput.contains(x)) { |
| return; |
| } |
| /* |
| * At this point, either we have a constant replacement, or our value is |
| * irrelevant. We can inline the constant, if any, but we might also need |
| * to evaluate an instance and run a clinit. |
| */ |
| // We can inline the constant, but we might also need to evaluate an |
| // instance and run a clinit. |
| List<JExpression> exprs = Lists.newArrayList(); |
| |
| JExpression instance = x.getInstance(); |
| if (instance != null) { |
| exprs.add(instance); |
| } |
| |
| if (x.hasClinit() && !x.getField().isCompileTimeConstant()) { |
| // Access to compile time constants do not trigger class initialization (JLS 12.4.1). |
| exprs.add(createClinitCall(x.getSourceInfo(), x.getField().getEnclosingType())); |
| } |
| |
| if (literal != null) { |
| exprs.add(literal); |
| } |
| |
| JExpression replacement; |
| if (exprs.size() == 1) { |
| replacement = exprs.get(0); |
| } else { |
| replacement = new JMultiExpression(x.getSourceInfo(), exprs); |
| } |
| |
| ctx.replaceMe(this.accept(replacement)); |
| } |
| |
| /** |
| * Prune for (X; false; Y) statements, but make sure X is run. |
| */ |
| @Override |
| public void endVisit(JForStatement x, Context ctx) { |
| JExpression expression = x.getCondition(); |
| if (expression instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) expression; |
| |
| // If false, replace the for statement with its initializers |
| if (!booleanLiteral.getValue()) { |
| JBlock block = new JBlock(x.getSourceInfo()); |
| block.addStmts(x.getInitializers()); |
| replaceMe(block, ctx); |
| } |
| } |
| } |
| |
| /** |
| * Simplify if statements. |
| */ |
| @Override |
| public void endVisit(JIfStatement x, Context ctx) { |
| JType methodReturnType = getCurrentMethod() != null ? getCurrentMethod().getType() : null; |
| maybeReplaceMe(x, Simplifier.simplifyIfStatement(x, methodReturnType), ctx); |
| } |
| |
| /** |
| * Simplify JInstanceOf expression whose output is ignored. |
| */ |
| @Override |
| public void endVisit(JInstanceOf x, Context ctx) { |
| if (ignoringExpressionOutput.contains(x)) { |
| ctx.replaceMe(x.getExpr()); |
| ignoringExpressionOutput.remove(x); |
| return; |
| } |
| |
| if (!(x.getExpr().getType() instanceof JReferenceType)) { |
| return; |
| } |
| |
| AnalysisResult analysisResult = |
| staticallyEvaluateInstanceOf((JReferenceType) x.getExpr().getType(), x.getTestType()); |
| switch (analysisResult) { |
| case TRUE: |
| // replace with a simple null test: (expr != null). |
| ctx.replaceMe( |
| JjsUtils.createOptimizedNotNullComparison(program, x.getSourceInfo(), x.getExpr())); |
| break; |
| case FALSE: |
| // replace with a false literal |
| ctx.replaceMe(JjsUtils.createOptimizedMultiExpression(x.getExpr(), |
| program.getLiteralBoolean(false))); |
| break; |
| case UNKNOWN: |
| default: |
| } |
| } |
| |
| @Override |
| public void endVisit(JLocalRef x, Context ctx) { |
| JLiteral literal = tryGetConstant(x); |
| if (literal != null) { |
| assert (!x.hasSideEffects()); |
| ctx.replaceMe(literal); |
| } |
| } |
| |
| /** |
| * Resolve method calls that can be computed statically. |
| */ |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| // Restore ignored expressions. |
| JMethod target = x.getTarget(); |
| JExpression instance = x.getInstance(); |
| if (target.isStatic() && instance != null) { |
| ignoringExpressionOutput.remove(instance); |
| } |
| |
| // Normal optimizations. |
| JDeclaredType targetType = target.getEnclosingType(); |
| if (targetType == program.getTypeJavaLangString() || |
| (instance != null && |
| instance.getType().getUnderlyingType() == program.getTypeJavaLangString())) { |
| tryOptimizeStringCall(x, ctx, target); |
| } else if (JProgram.isClinit(target)) { |
| // Eliminate the call if the target is now empty. |
| if (!targetType.hasClinit()) { |
| ctx.replaceMe(program.getLiteralNull()); |
| } else if (targetType != targetType.getClinitTarget()) { |
| // Tighten the target. |
| ctx.replaceMe(createClinitCall(x.getSourceInfo(), targetType.getClinitTarget())); |
| } |
| } else if (target == isScriptMethod) { |
| // optimize out in draftCompiles that don't do inlining |
| ctx.replaceMe(JBooleanLiteral.TRUE); |
| } else if (target == enumOrdinalMethod) { |
| maybeReplaceWithOrdinalValue(instance, ctx); |
| } |
| } |
| |
| /** |
| * Remove any parts of JMultiExpression that have no side-effect. |
| */ |
| @Override |
| public void endVisit(JMultiExpression x, Context ctx) { |
| List<JExpression> exprs = x.getExpressions(); |
| if (exprs.size() > 0) { |
| if (ignoringExpressionOutput.contains(x)) { |
| // Remove all my children we previously added. |
| ignoringExpressionOutput.removeAll(exprs); |
| } else { |
| // Remove the non-final children we previously added. |
| List<JExpression> nonFinalChildren = exprs.subList(0, exprs.size() - 1); |
| ignoringExpressionOutput.removeAll(nonFinalChildren); |
| } |
| } |
| |
| HashSet<JDeclaredType> clinitsCalled = new HashSet<JDeclaredType>(); |
| for (int i = 0; i < numRemovableExpressions(x); ++i) { |
| JExpression expr = x.getExpression(i); |
| if (!expr.hasSideEffects()) { |
| x.removeExpression(i); |
| --i; |
| madeChanges(); |
| continue; |
| } |
| |
| // Remove nested JMultiExpressions |
| if (expr instanceof JMultiExpression) { |
| x.removeExpression(i); |
| x.addExpressions(i, ((JMultiExpression) expr).getExpressions()); |
| i--; |
| madeChanges(); |
| continue; |
| } |
| |
| // Remove redundant clinits |
| if (expr instanceof JMethodCall && JProgram.isClinit(((JMethodCall) expr).getTarget())) { |
| JDeclaredType enclosingType = ((JMethodCall) expr).getTarget().getEnclosingType(); |
| // If a clinit of enclosingType or a subclass of enclosingType has already been |
| // called as part of this JMultiExpression then this clinit call is noop at runtime |
| // and can be statically removed. |
| if (enclosingType.findSubtype(clinitsCalled) != null) { |
| x.removeExpression(i); |
| --i; |
| madeChanges(); |
| continue; |
| } else { |
| clinitsCalled.add(enclosingType); |
| } |
| } |
| } |
| |
| if (x.getNumberOfExpressions() == 1) { |
| maybeReplaceMe(x, x.getExpressions().get(0), ctx); |
| } |
| } |
| |
| @Override |
| public void endVisit(JNewInstance x, Context ctx) { |
| super.endVisit(x, ctx); |
| /* |
| * If the result of a new operation is ignored, we can remove it, provided |
| * it has no side effects. |
| */ |
| if (ignoringExpressionOutput.contains(x)) { |
| if (!x.getTarget().isEmpty()) { |
| return; |
| } |
| JMultiExpression multi = new JMultiExpression(x.getSourceInfo()); |
| multi.addExpressions(x.getArgs()); |
| // Determine whether a new Blah() in this method needs a clinit. |
| if (getCurrentMethod().getEnclosingType().checkClinitTo(x.getTarget().getEnclosingType())) { |
| multi.addExpressions( |
| createClinitCall(x.getSourceInfo(), x.getTarget().getEnclosingType())); |
| } |
| ignoringExpressionOutput.add(multi); |
| ctx.replaceMe(this.accept(multi)); |
| ignoringExpressionOutput.remove(multi); |
| } |
| } |
| |
| @Override |
| public void endVisit(JParameterRef x, Context ctx) { |
| JLiteral literal = tryGetConstant(x); |
| if (literal != null) { |
| assert (!x.hasSideEffects()); |
| ctx.replaceMe(literal); |
| } |
| } |
| |
| /** |
| * Replace post-inc/dec with pre-inc/dec if the result doesn't matter. |
| */ |
| @Override |
| public void endVisit(JPostfixOperation x, Context ctx) { |
| if (x.getOp().isModifying()) { |
| lvalues.remove(x.getArg()); |
| } |
| |
| if (isNonEmptyMultiExpression(x.getArg())) { |
| List<JExpression> expressions = ((JMultiExpression) x.getArg()).getExpressions(); |
| JMultiExpression result = new JMultiExpression(x.getArg().getSourceInfo(), |
| expressions.subList(0, expressions.size() - 1)); |
| result.addExpressions(new JPostfixOperation(x.getSourceInfo(), x.getOp(), |
| expressions.get(expressions.size() - 1))); |
| ctx.replaceMe(result); |
| return; |
| } |
| |
| if (ignoringExpressionOutput.contains(x)) { |
| JPrefixOperation newOp = new JPrefixOperation(x.getSourceInfo(), x.getOp(), x.getArg()); |
| ctx.replaceMe(newOp); |
| } |
| } |
| |
| /** |
| * Simplify the ! operator if possible. |
| */ |
| @Override |
| public void endVisit(JPrefixOperation x, Context ctx) { |
| if (x.getOp().isModifying()) { |
| lvalues.remove(x.getArg()); |
| } |
| |
| // If the argument is a multiexpression restructure it if possible. |
| if (isNonEmptyMultiExpression(x.getArg())) { |
| List<JExpression> expressions = ((JMultiExpression) x.getArg()).getExpressions(); |
| JMultiExpression result = new JMultiExpression(x.getArg().getSourceInfo(), |
| expressions.subList(0, expressions.size() - 1)); |
| result.addExpressions(new JPrefixOperation(x.getSourceInfo(), x.getOp(), |
| expressions.get(expressions.size() - 1))); |
| ctx.replaceMe(result); |
| return; |
| } |
| |
| if (x.getArg() instanceof JValueLiteral) { |
| if (evalOpOnLiteral(x.getOp(), (JValueLiteral) x.getArg(), ctx)) { |
| return; |
| } |
| } |
| |
| if (x.getOp() == JUnaryOperator.NOT) { |
| maybeReplaceMe(x, Simplifier.simplifyNot(x), ctx); |
| return; |
| } else if (x.getOp() == JUnaryOperator.NEG) { |
| maybeReplaceMe(x, simplifyNegate(x, x.getArg()), ctx); |
| } |
| } |
| |
| /** |
| * Optimize switch statements. |
| */ |
| @Override |
| public void endVisit(JSwitchStatement x, Context ctx) { |
| switchBlocks.remove(x.getBody()); |
| |
| // If it returns true, it was reduced to nothing |
| if (tryReduceSwitchWithConstantInput(x, ctx)) { |
| return; |
| } |
| |
| if (hasNoDefaultCase(x)) { |
| removeEmptyCases(x); |
| } |
| removeDoubleBreaks(x); |
| tryRemoveSwitch(x, ctx); |
| } |
| |
| /** |
| * 1) Remove catch blocks whose exception type is not instantiable. 2) Prune |
| * try statements with no body. 3) Hoist up try statements with no catches |
| * and an empty finally. |
| */ |
| @Override |
| public void endVisit(JTryStatement x, Context ctx) { |
| // 1) Remove catch blocks whose exception type is not instantiable. |
| List<JTryStatement.CatchClause> catchClauses = x.getCatchClauses(); |
| |
| Iterator<JTryStatement.CatchClause> itClauses = catchClauses.iterator(); |
| while (itClauses.hasNext()) { |
| JTryStatement.CatchClause clause = itClauses.next(); |
| // Go over the types in the multiexception and remove the ones that are not instantiable. |
| Iterator<JType> itTypes = clause.getTypes().iterator(); |
| while (itTypes.hasNext()) { |
| JReferenceType type = (JReferenceType) itTypes.next(); |
| if (!program.typeOracle.isInstantiatedType(type) || type.isNullType()) { |
| itTypes.remove(); |
| madeChanges(); |
| } |
| } |
| |
| // if all exception types are gone then remove whole clause. |
| if (clause.getTypes().isEmpty()) { |
| itClauses.remove(); |
| madeChanges(); |
| } |
| } |
| |
| // Compute properties regarding the state of this try statement |
| boolean noTry = JjsUtils.isEmptyBlock(x.getTryBlock()); |
| boolean noCatch = catchClauses.size() == 0; |
| boolean noFinally = JjsUtils.isEmptyBlock(x.getFinallyBlock()); |
| |
| if (noTry) { |
| // 2) Prune try statements with no body. |
| if (noFinally) { |
| // if there's no finally, prune the whole thing |
| removeMe(x, ctx); |
| } else { |
| // replace the try statement with just the contents of the finally |
| replaceMe(x.getFinallyBlock(), ctx); |
| } |
| } else if (noCatch && noFinally) { |
| // 3) Hoist up try statements with no catches and an empty finally. |
| // If there's no catch or finally, there's no point in this even being |
| // a try statement, replace myself with the try block |
| replaceMe(x.getTryBlock(), ctx); |
| } |
| } |
| |
| /** |
| * Prune while (false) statements. |
| */ |
| @Override |
| public void endVisit(JWhileStatement x, Context ctx) { |
| JExpression expression = x.getTestExpr(); |
| if (expression instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) expression; |
| |
| // If false, prune the while statement |
| if (!booleanLiteral.getValue()) { |
| removeMe(x, ctx); |
| } |
| } |
| } |
| |
| @Override |
| public boolean visit(JBinaryOperation x, Context ctx) { |
| if (x.getOp().isAssignment()) { |
| lvalues.add(x.getLhs()); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JClassType x, Context ctx) { |
| // We can't eliminate code from an external type |
| return !x.isExternal(); |
| } |
| |
| @Override |
| public boolean visit(JDeclarationStatement x, Context ctx) { |
| lvalues.add(x.getVariableRef()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JExpressionStatement x, Context ctx) { |
| ignoringExpressionOutput.add(x.getExpr()); |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JMethodCall x, Context ctx) { |
| JMethod target = x.getTarget(); |
| if (target.isStatic() && x.getInstance() != null) { |
| ignoringExpressionOutput.add(x.getInstance()); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JMultiExpression x, Context ctx) { |
| List<JExpression> exprs = x.getExpressions(); |
| if (exprs.size() > 0) { |
| if (ignoringExpressionOutput.contains(x)) { |
| // None of my children matter. |
| ignoringExpressionOutput.addAll(exprs); |
| } else { |
| // Only my final child matters. |
| List<JExpression> nonFinalChildren = exprs.subList(0, exprs.size() - 1); |
| ignoringExpressionOutput.addAll(nonFinalChildren); |
| } |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JPostfixOperation x, Context ctx) { |
| if (x.getOp().isModifying()) { |
| lvalues.add(x.getArg()); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JPrefixOperation x, Context ctx) { |
| if (x.getOp().isModifying()) { |
| lvalues.add(x.getArg()); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JSwitchStatement x, Context ctx) { |
| switchBlocks.add(x.getBody()); |
| return true; |
| } |
| |
| /** |
| * Returns true if a block can be merged into its parent block. This is true |
| * when the block contains no local declarations. |
| */ |
| private boolean canPromoteBlock(JBlock block) { |
| for (JStatement nestedStmt : block.getStatements()) { |
| if (nestedStmt instanceof JDeclarationStatement) { |
| JDeclarationStatement decl = (JDeclarationStatement) nestedStmt; |
| if (decl.getVariableRef() instanceof JLocalRef) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private JMethodCall createClinitCall(SourceInfo sourceInfo, JDeclaredType targetType) { |
| JMethod clinit = targetType.getClinitTarget().getClinitMethod(); |
| assert (JProgram.isClinit(clinit)); |
| return new JMethodCall(sourceInfo, null, clinit); |
| } |
| |
| private void evalConcat(SourceInfo info, JExpression lhs, JExpression rhs, Context ctx) { |
| if (lhs instanceof JValueLiteral && rhs instanceof JValueLiteral) { |
| Object lhsObj = ((JValueLiteral) lhs).getValueObj(); |
| Object rhsObj = ((JValueLiteral) rhs).getValueObj(); |
| ctx.replaceMe(program.getStringLiteral(info, String.valueOf(lhsObj) |
| + String.valueOf(rhsObj))); |
| } |
| } |
| |
| /** |
| * Evaluate <code>lhs == rhs</code>. |
| * |
| * @param lhs Any literal. |
| * @param rhs Any literal. |
| * @return Whether <code>lhs == rhs</code> will evaluate to |
| * <code>true</code> at run time; string literal comparisons use .equals() semantics. |
| */ |
| private boolean evalEq(JValueLiteral lhs, JValueLiteral rhs) { |
| if (isTypeNull(lhs)) { |
| return isTypeNull(rhs); |
| } |
| if (isTypeString(lhs)) { |
| return isTypeString(rhs) && |
| ((JStringLiteral) lhs).getValue().equals(((JStringLiteral) rhs).getValue()); |
| } |
| if (isTypeBoolean(lhs)) { |
| return toBoolean(lhs) == toBoolean(rhs); |
| } |
| if (isTypeFloatingPoint(lhs) || isTypeFloatingPoint(rhs)) { |
| return Ieee754_64_Arithmetic.eq(toDouble(lhs), toDouble(rhs)); |
| } |
| if (isTypeLong(lhs) || isTypeLong(rhs)) { |
| return toLong(lhs) == toLong(rhs); |
| } |
| return toInt(lhs) == toInt(rhs); |
| } |
| |
| /** |
| * Static evaluation of a unary operation on a literal. |
| * |
| * @return Whether a change was made |
| */ |
| private boolean evalOpOnLiteral(JUnaryOperator op, JValueLiteral exp, Context ctx) { |
| switch (op) { |
| case BIT_NOT: { |
| long value = toLong(exp); |
| long res = ~value; |
| if (isTypeLong(exp)) { |
| ctx.replaceMe(program.getLiteralLong(res)); |
| } else { |
| ctx.replaceMe(program.getLiteralInt((int) res)); |
| } |
| return true; |
| } |
| |
| case NEG: |
| if (isTypeLong(exp)) { |
| ctx.replaceMe(program.getLiteralLong(-toLong(exp))); |
| return true; |
| } |
| if (isTypeIntegral(exp)) { |
| ctx.replaceMe(program.getLiteralInt(-toInt(exp))); |
| return true; |
| } |
| if (isTypeDouble(exp)) { |
| ctx.replaceMe(program.getLiteralDouble(Ieee754_64_Arithmetic.neg(toDouble(exp)))); |
| return true; |
| } |
| if (isTypeFloat(exp)) { |
| ctx.replaceMe(program.getLiteralFloat(Ieee754_64_Arithmetic.neg(toDouble(exp)))); |
| return true; |
| } |
| return false; |
| |
| case NOT: { |
| JBooleanLiteral booleanLit = (JBooleanLiteral) exp; |
| ctx.replaceMe(program.getLiteralBoolean(!booleanLit.getValue())); |
| return true; |
| } |
| |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Static evaluation of a binary operation on two literals. |
| * |
| * @return Whether a change was made |
| */ |
| private boolean evalOpOnLiterals(JBinaryOperator op, JValueLiteral lhs, JValueLiteral rhs, |
| Context ctx) { |
| switch (op) { |
| case EQ: { |
| ctx.replaceMe(program.getLiteralBoolean(evalEq(lhs, rhs))); |
| return true; |
| } |
| |
| case NEQ: { |
| ctx.replaceMe(program.getLiteralBoolean(!evalEq(lhs, rhs))); |
| return true; |
| } |
| |
| case ADD: |
| case SUB: |
| case MUL: |
| case DIV: |
| case MOD: { |
| if (isTypeFloatingPoint(lhs) || isTypeFloatingPoint(rhs)) { |
| // do the op on doubles and cast back |
| double left = toDouble(lhs); |
| double right = toDouble(rhs); |
| double res; |
| switch (op) { |
| case ADD: |
| res = Ieee754_64_Arithmetic.add(left, right); |
| break; |
| case SUB: |
| res = Ieee754_64_Arithmetic.subtract(left, right); |
| break; |
| case MUL: |
| res = Ieee754_64_Arithmetic.multiply(left, right); |
| break; |
| case DIV: |
| res = Ieee754_64_Arithmetic.divide(left, right); |
| break; |
| case MOD: |
| res = Ieee754_64_Arithmetic.mod(left, right); |
| break; |
| default: |
| assert false; |
| return false; |
| } |
| if (isTypeDouble(lhs) || isTypeDouble(rhs)) { |
| ctx.replaceMe(program.getLiteralDouble(res)); |
| } else { |
| ctx.replaceMe(program.getLiteralFloat(res)); |
| } |
| return true; |
| } else if (isTypeIntegral(lhs) && isTypeIntegral(rhs)) { |
| // do the op on longs and cast to the correct |
| // result type at the end |
| long left = toLong(lhs); |
| long right = toLong(rhs); |
| |
| long res; |
| switch (op) { |
| case ADD: |
| res = left + right; |
| break; |
| case SUB: |
| res = left - right; |
| break; |
| case MUL: |
| res = left * right; |
| break; |
| case DIV: |
| if (right == 0) { |
| // Don't divide by zero |
| return false; |
| } |
| res = left / right; |
| break; |
| case MOD: |
| if (right == 0) { |
| // Don't divide by zero |
| return false; |
| } |
| res = left % right; |
| break; |
| default: |
| assert false; |
| return false; |
| } |
| if (isTypeLong(lhs) || isTypeLong(rhs)) { |
| ctx.replaceMe(program.getLiteralLong(res)); |
| } else { |
| ctx.replaceMe(program.getLiteralInt((int) res)); |
| } |
| return true; |
| } |
| return false; |
| } |
| case LT: |
| case LTE: |
| case GT: |
| case GTE: { |
| if (isTypeFloatingPoint(lhs) || isTypeFloatingPoint(rhs)) { |
| // operate on doubles |
| double left = toDouble(lhs); |
| double right = toDouble(rhs); |
| boolean res; |
| switch (op) { |
| case LT: |
| res = Ieee754_64_Arithmetic.lt(left, right); |
| break; |
| case LTE: |
| res = Ieee754_64_Arithmetic.le(left, right); |
| break; |
| case GT: |
| res = Ieee754_64_Arithmetic.gt(left, right); |
| break; |
| case GTE: |
| res = Ieee754_64_Arithmetic.ge(left, right); |
| break; |
| default: |
| assert false; |
| return false; |
| } |
| ctx.replaceMe(program.getLiteralBoolean(res)); |
| return true; |
| } else if (isTypeIntegral(lhs) && isTypeIntegral(rhs)) { |
| // operate on longs |
| long left = toLong(lhs); |
| long right = toLong(rhs); |
| boolean res; |
| switch (op) { |
| case LT: |
| res = left < right; |
| break; |
| case LTE: |
| res = left <= right; |
| break; |
| case GT: |
| res = left > right; |
| break; |
| case GTE: |
| res = left >= right; |
| break; |
| default: |
| assert false; |
| return false; |
| } |
| ctx.replaceMe(program.getLiteralBoolean(res)); |
| return true; |
| } |
| return false; |
| } |
| case BIT_AND: |
| case BIT_OR: |
| case BIT_XOR: |
| if (isTypeBoolean(lhs)) { |
| // TODO: maybe eval non-short-circuit boolean operators. |
| return false; |
| } else if (isTypeIntegral(lhs) && isTypeIntegral(rhs)) { |
| // operate on longs and then cast down |
| long left = toLong(lhs); |
| long right = toLong(rhs); |
| long res; |
| switch (op) { |
| case BIT_AND: |
| res = left & right; |
| break; |
| |
| case BIT_OR: |
| res = left | right; |
| break; |
| |
| case BIT_XOR: |
| res = left ^ right; |
| break; |
| |
| default: |
| assert false; |
| return false; |
| } |
| if (isTypeLong(lhs) || isTypeLong(rhs)) { |
| ctx.replaceMe(program.getLiteralLong(res)); |
| } else { |
| ctx.replaceMe(program.getLiteralInt((int) res)); |
| } |
| return true; |
| } |
| return false; |
| case SHL: |
| case SHR: |
| case SHRU: { |
| if (isTypeLong(lhs)) { |
| long left = toLong(lhs); |
| int right = toInt(rhs); |
| long res; |
| switch (op) { |
| case SHL: |
| res = left << right; |
| break; |
| |
| case SHR: |
| res = left >> right; |
| break; |
| |
| case SHRU: |
| res = left >>> right; |
| break; |
| |
| default: |
| assert false; |
| return false; |
| } |
| |
| ctx.replaceMe(program.getLiteralLong(res)); |
| return true; |
| } else if (isTypeIntegral(lhs) && isTypeIntegral(rhs)) { |
| int left = toInt(lhs); |
| int right = toInt(rhs); |
| int res; |
| switch (op) { |
| case SHL: |
| res = left << right; |
| break; |
| |
| case SHR: |
| res = left >> right; |
| break; |
| |
| case SHRU: |
| res = left >>> right; |
| break; |
| |
| default: |
| assert false; |
| return false; |
| } |
| |
| ctx.replaceMe(program.getLiteralInt(res)); |
| return true; |
| } |
| return false; |
| } |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * If the effect of <code>statement</code> is to immediately do a break, |
| * then return the {@link JBreakStatement} corresponding to that break. |
| */ |
| private JBreakStatement findUnconditionalBreak(JStatement statement) { |
| if (statement instanceof JBreakStatement) { |
| return (JBreakStatement) statement; |
| } else if (statement instanceof JBlock) { |
| JBlock block = (JBlock) statement; |
| List<JStatement> blockStmts = block.getStatements(); |
| if (blockStmts.size() > 0 && isUnconditionalBreak(blockStmts.get(0))) { |
| return (JBreakStatement) blockStmts.get(0); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Tries to removes cases and statements from switches whose expression is a |
| * constant value. |
| * |
| * @return true, if the switch was completely eliminated |
| */ |
| private boolean tryReduceSwitchWithConstantInput(JSwitchStatement s, Context ctx) { |
| if (!(s.getExpr() instanceof JValueLiteral)) { |
| // the input is not a constant |
| return false; |
| } |
| JValueLiteral targetValue = (JValueLiteral) s.getExpr(); |
| |
| // Find the matching case |
| JCaseStatement matchingCase = null; |
| for (JStatement subStatement : s.getBody().getStatements()) { |
| if (subStatement instanceof JCaseStatement) { |
| JCaseStatement caseStatement = (JCaseStatement) subStatement; |
| if (caseStatement.getExpr() == null) { |
| // speculatively put the default case into the matching case |
| matchingCase = caseStatement; |
| } else if (caseStatement.getExpr() instanceof JValueLiteral) { |
| JValueLiteral caseValue = (JValueLiteral) caseStatement.getExpr(); |
| if (caseValue.getValueObj().equals(targetValue.getValueObj())) { |
| matchingCase = caseStatement; |
| break; |
| } |
| } |
| } |
| } |
| |
| if (matchingCase == null) { |
| // the switch has no default and no matching cases |
| // the expression is a value literal, so it can go away completely |
| removeMe(s, ctx); |
| return true; |
| } |
| |
| Iterator<JStatement> it = s.getBody().getStatements().iterator(); |
| |
| // Remove things until we find the matching case |
| while (it.hasNext() && (it.next() != matchingCase)) { |
| it.remove(); |
| madeChanges(); |
| } |
| |
| // Until an unconditional control break, preserve everything that isn't a case |
| // or default. |
| while (it.hasNext()) { |
| JStatement statement = it.next(); |
| if (statement.unconditionalControlBreak()) { |
| break; |
| } else if (statement instanceof JCaseStatement) { |
| it.remove(); |
| } |
| } |
| |
| // having found a break or return (or reached the end), remove all remaining |
| while (it.hasNext()) { |
| it.next(); |
| it.remove(); |
| madeChanges(); |
| } |
| |
| return false; |
| } |
| |
| private boolean hasNoDefaultCase(JSwitchStatement x) { |
| JBlock body = x.getBody(); |
| boolean inDefault = false; |
| for (JStatement statement : body.getStatements()) { |
| if (statement instanceof JCaseStatement) { |
| JCaseStatement caseStmt = (JCaseStatement) statement; |
| if (caseStmt.getExpr() == null) { |
| inDefault = true; |
| } |
| } else if (isUnconditionalUnlabeledBreak(statement)) { |
| inDefault = false; |
| } else { |
| // We have some code to execute other than a break. |
| if (inDefault) { |
| // We have a default case with real code. |
| return false; |
| } |
| } |
| } |
| // We found no default case that wasn't empty. |
| return true; |
| } |
| |
| private boolean isLiteralNegativeOne(JExpression exp) { |
| if (!(exp instanceof JValueLiteral)) { |
| return false; |
| } |
| JValueLiteral lit = (JValueLiteral) exp; |
| if (isTypeIntegral(lit) && toLong(lit) == -1) { |
| return true; |
| } |
| if (isTypeFloatingPoint(lit) && toDouble(lit) == -1.0) { |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean isLiteralOne(JExpression exp) { |
| if (!(exp instanceof JValueLiteral)) { |
| return false; |
| } |
| JValueLiteral lit = (JValueLiteral) exp; |
| if (isTypeIntegral(lit) && toLong(lit) == 1) { |
| return true; |
| } |
| return isTypeFloatingPoint(lit) && toDouble(lit) == 1.0; |
| } |
| |
| private boolean isLiteralZero(JExpression exp) { |
| if (exp instanceof JValueLiteral) { |
| JValueLiteral lit = (JValueLiteral) exp; |
| if (toDouble(lit) == 0.0) { |
| // Using toDouble only is safe even for integer types. All types but |
| // long will keep full precision. Longs will lose precision, but |
| // it will not affect whether the resulting double is zero or not. |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean isTypeBoolean(JExpression lhs) { |
| return lhs.getType() == program.getTypePrimitiveBoolean(); |
| } |
| |
| private boolean isTypeDouble(JExpression exp) { |
| return isTypeDouble(exp.getType()); |
| } |
| |
| private boolean isTypeDouble(JType type) { |
| return type == program.getTypePrimitiveDouble(); |
| } |
| |
| private boolean isTypeFloat(JExpression exp) { |
| return isTypeFloat(exp.getType()); |
| } |
| |
| private boolean isTypeFloat(JType type) { |
| return type == program.getTypePrimitiveFloat(); |
| } |
| |
| /** |
| * Return whether the type of the expression is float or double. |
| */ |
| private boolean isTypeFloatingPoint(JExpression exp) { |
| return isTypeFloatingPoint(exp.getType()); |
| } |
| |
| private boolean isTypeFloatingPoint(JType type) { |
| return ((type == program.getTypePrimitiveDouble()) || (type == program |
| .getTypePrimitiveFloat())); |
| } |
| |
| /** |
| * Return whether the type of the expression is byte, char, short, int, or |
| * long. |
| */ |
| private boolean isTypeIntegral(JExpression exp) { |
| return isTypeIntegral(exp.getType()); |
| } |
| |
| private boolean isTypeIntegral(JType type) { |
| return ((type == program.getTypePrimitiveInt()) || (type == program.getTypePrimitiveLong()) |
| || (type == program.getTypePrimitiveChar()) || (type == program.getTypePrimitiveByte()) |
| || (type == program.getTypePrimitiveShort())); |
| } |
| |
| private boolean isTypeLong(JExpression exp) { |
| return isTypeLong(exp.getType()); |
| } |
| |
| private boolean isTypeLong(JType type) { |
| return type == program.getTypePrimitiveLong(); |
| } |
| |
| private boolean isTypeNull(JExpression exp) { |
| return isTypeNull(exp.getType()); |
| } |
| |
| private boolean isTypeNull(JType type) { |
| return type.isNullType(); |
| } |
| |
| private boolean isTypeString(JExpression exp) { |
| return program.isJavaLangString(exp.getType()); |
| } |
| |
| private boolean isUnconditionalBreak(JStatement statement) { |
| return findUnconditionalBreak(statement) != null; |
| } |
| |
| private boolean isUnconditionalUnlabeledBreak(JStatement statement) { |
| JBreakStatement breakStat = findUnconditionalBreak(statement); |
| return (breakStat != null) && (breakStat.getLabel() == null); |
| } |
| |
| private <T> T last(List<T> statements) { |
| return statements.get(statements.size() - 1); |
| } |
| |
| private Class<?> classObjectForType(JType type) { |
| return classObjectsByType.get(type); |
| } |
| |
| /** |
| * Replace me only if the updated expression is different. |
| */ |
| private void maybeReplaceMe(JExpression x, JExpression updated, Context ctx) { |
| if (updated != x) { |
| ctx.replaceMe(updated); |
| } |
| } |
| |
| private void maybeReplaceMe(JStatement x, JStatement updated, Context ctx) { |
| if (updated != x) { |
| replaceMe(updated, ctx); |
| } |
| } |
| |
| private void maybeReplaceWithOrdinalValue(JExpression instanceExpr, Context ctx) { |
| if (!(instanceExpr instanceof JFieldRef)) { |
| return; |
| } |
| |
| JFieldRef fieldRef = (JFieldRef) instanceExpr; |
| if (!(fieldRef.getField() instanceof JEnumField)) { |
| return; |
| } |
| |
| assert fieldRef.getInstance() == null; |
| JEnumField enumField = (JEnumField) fieldRef.getField(); |
| ctx.replaceMe(program.getLiteralInt(enumField.ordinal())); |
| } |
| |
| private int numRemovableExpressions(JMultiExpression x) { |
| if (ignoringExpressionOutput.contains(x)) { |
| // The result doesn't matter: all expressions can be removed. |
| return x.getNumberOfExpressions(); |
| } else { |
| // The last expression cannot be removed. |
| return x.getNumberOfExpressions() - 1; |
| } |
| } |
| |
| /** |
| * Removes any break statements that appear one after another. |
| */ |
| private void removeDoubleBreaks(JSwitchStatement x) { |
| JBlock body = x.getBody(); |
| boolean lastWasBreak = true; |
| for (int i = 0; i < body.getStatements().size(); ++i) { |
| JStatement statement = body.getStatements().get(i); |
| boolean isBreak = isUnconditionalBreak(statement); |
| if (isBreak && lastWasBreak) { |
| body.removeStmt(i--); |
| madeChanges(); |
| } |
| lastWasBreak = isBreak; |
| } |
| |
| // Remove a trailing break statement from a case block |
| if (body.getStatements().size() > 0 |
| && isUnconditionalUnlabeledBreak(last(body.getStatements()))) { |
| body.removeStmt(body.getStatements().size() - 1); |
| madeChanges(); |
| } |
| } |
| |
| /** |
| * A switch with no default case can have its empty cases pruned. |
| */ |
| private void removeEmptyCases(JSwitchStatement x) { |
| JBlock body = x.getBody(); |
| List<JStatement> noOpCaseStatements = new ArrayList<JStatement>(); |
| List<JStatement> potentialNoOpCaseStatements = new ArrayList<JStatement>(); |
| /* |
| * A case statement has no effect if there is no code between it and |
| * either an unconditional break or the end of the switch. |
| */ |
| for (JStatement statement : body.getStatements()) { |
| if (statement instanceof JCaseStatement) { |
| potentialNoOpCaseStatements.add(statement); |
| } else if (isUnconditionalUnlabeledBreak(statement)) { |
| // If we have any potential no-ops, they now become real no-ops. |
| noOpCaseStatements.addAll(potentialNoOpCaseStatements); |
| potentialNoOpCaseStatements.clear(); |
| } else { |
| // Any other kind of statement makes these case statements are useful. |
| potentialNoOpCaseStatements.clear(); |
| } |
| } |
| // None of the remaining case statements have any effect |
| noOpCaseStatements.addAll(potentialNoOpCaseStatements); |
| |
| if (noOpCaseStatements.size() > 0) { |
| for (JStatement statement : noOpCaseStatements) { |
| body.removeStmt(body.getStatements().indexOf(statement)); |
| madeChanges(); |
| } |
| } |
| } |
| |
| /** |
| * Call this instead of directly calling <code>ctx.removeMe()</code> for |
| * Statements. |
| */ |
| private void removeMe(JStatement stmt, Context ctx) { |
| if (ctx.canRemove()) { |
| ctx.removeMe(); |
| } else { |
| // empty block statement |
| ctx.replaceMe(new JBlock(stmt.getSourceInfo())); |
| } |
| } |
| |
| /** |
| * |
| * Call this instead of directly calling <code>ctx.replaceMe()</code> for |
| * Statements. |
| */ |
| private void replaceMe(JStatement stmt, Context ctx) { |
| stmt = this.accept(stmt, ctx.canRemove()); |
| if (stmt == null) { |
| ctx.removeMe(); |
| } else { |
| ctx.replaceMe(stmt); |
| } |
| } |
| |
| private boolean simplifyAdd(JExpression lhs, JExpression rhs, Context ctx, JType type) { |
| if (isLiteralZero(rhs)) { |
| ctx.replaceMe(Simplifier.cast(type, lhs)); |
| return true; |
| } |
| if (isLiteralZero(lhs)) { |
| ctx.replaceMe(Simplifier.cast(type, rhs)); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Simplify <code>exp == bool</code>, where <code>bool</code> is a boolean |
| * literal. |
| */ |
| private void simplifyBooleanEq(JExpression exp, boolean bool, Context ctx) { |
| if (bool) { |
| ctx.replaceMe(exp); |
| } else { |
| ctx.replaceMe(new JPrefixOperation(exp.getSourceInfo(), JUnaryOperator.NOT, exp)); |
| } |
| } |
| |
| /** |
| * Simplify <code>lhs == rhs</code>, where <code>lhs</code> and |
| * <code>rhs</code> are known to be boolean. If <code>negate</code> is |
| * <code>true</code>, then treat it as <code>lhs != rhs</code> instead of |
| * <code>lhs == rhs</code>. Assumes that the case where both sides are |
| * literals has already been checked. |
| */ |
| private void simplifyBooleanEq(JExpression lhs, JExpression rhs, Context ctx, boolean negate) { |
| if (lhs instanceof JBooleanLiteral) { |
| boolean left = ((JBooleanLiteral) lhs).getValue(); |
| simplifyBooleanEq(rhs, left ^ negate, ctx); |
| return; |
| } |
| if (rhs instanceof JBooleanLiteral) { |
| boolean right = ((JBooleanLiteral) rhs).getValue(); |
| simplifyBooleanEq(lhs, right ^ negate, ctx); |
| return; |
| } |
| } |
| |
| private boolean simplifyDiv(JExpression lhs, JExpression rhs, Context ctx, JType type) { |
| if (isLiteralOne(rhs)) { |
| ctx.replaceMe(Simplifier.cast(type, lhs)); |
| return true; |
| } |
| if (isLiteralNegativeOne(rhs)) { |
| ctx.replaceMe(simplifyNegate(Simplifier.cast(type, lhs))); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| private AnalysisResult staticallyEvaluateEq(JExpression lhs, JExpression rhs) { |
| JType lhsType = lhs.getType(); |
| JType rhsType = rhs.getType(); |
| if (lhsType.isNullType() && rhsType.isNullType()) { |
| return AnalysisResult.TRUE; |
| } |
| if (lhsType.isNullType() && !rhsType.canBeNull() || |
| rhsType.isNullType() && !lhsType.canBeNull()) { |
| return AnalysisResult.FALSE; |
| } |
| if (!lhsType.canBeSubclass() && !rhsType.canBeSubclass() && |
| !lhsType.canBeNull() && !rhsType.canBeNull() && lhsType instanceof JReferenceType && |
| lhsType.getUnderlyingType() != rhsType.getUnderlyingType()) { |
| // We can conclude that lhs != rhs via type compatibility. If both lhs and rhs are typed |
| // as exact and not null then if the underlying types are different the comparison clearly |
| // fails. |
| return AnalysisResult.FALSE; |
| } |
| return AnalysisResult.UNKNOWN; |
| } |
| |
| /** |
| * Tries to statically evaluate the instanceof operation. Returning TRUE if it can be determined |
| * statically that it is true when not null, FALSE if it can be determined that is false and |
| * UNKNOWN if the* result can not be determined statically. |
| */ |
| private AnalysisResult staticallyEvaluateInstanceOf(JReferenceType fromType, |
| JReferenceType toType) { |
| if (fromType.isNullType()) { |
| // null is never instanceof anything |
| return AnalysisResult.FALSE; |
| } else if (program.typeOracle.castSucceedsTrivially(fromType, toType)) { |
| return AnalysisResult.TRUE; |
| } else if (!program.typeOracle.isInstantiatedType(toType)) { |
| return AnalysisResult.FALSE; |
| } else if (program.typeOracle.castFailsTrivially(fromType, toType)) { |
| return AnalysisResult.FALSE; |
| } |
| return AnalysisResult.UNKNOWN; |
| } |
| |
| /** |
| * Simplify <code>lhs == rhs</code>. If <code>negate</code> is true, then |
| * it's actually static evaluation of <code>lhs != rhs</code>. |
| */ |
| private void simplifyEq(JExpression lhs, JExpression rhs, Context ctx, boolean negate) { |
| // simplify: null == null -> true |
| AnalysisResult analysisResult = staticallyEvaluateEq(lhs, rhs); |
| if (analysisResult != AnalysisResult.UNKNOWN) { |
| ctx.replaceMe(JjsUtils.createOptimizedMultiExpression( |
| lhs, rhs, program.getLiteralBoolean(negate ^ (analysisResult == AnalysisResult.TRUE)))); |
| return; |
| } |
| |
| if (isTypeBoolean(lhs) && isTypeBoolean(rhs)) { |
| simplifyBooleanEq(lhs, rhs, ctx, negate); |
| return; |
| } |
| } |
| |
| private boolean simplifyMul(JExpression lhs, JExpression rhs, Context ctx, JType type) { |
| if (isLiteralOne(rhs)) { |
| ctx.replaceMe(Simplifier.cast(type, lhs)); |
| return true; |
| } |
| if (isLiteralOne(lhs)) { |
| ctx.replaceMe(Simplifier.cast(type, rhs)); |
| return true; |
| } |
| if (isLiteralNegativeOne(rhs)) { |
| ctx.replaceMe(simplifyNegate(Simplifier.cast(type, lhs))); |
| return true; |
| } |
| if (isLiteralNegativeOne(lhs)) { |
| ctx.replaceMe(simplifyNegate(Simplifier.cast(type, rhs))); |
| return true; |
| } |
| if (isLiteralZero(rhs) && !lhs.hasSideEffects()) { |
| ctx.replaceMe(Simplifier.cast(type, rhs)); |
| return true; |
| } |
| if (isLiteralZero(lhs) && !rhs.hasSideEffects()) { |
| ctx.replaceMe(Simplifier.cast(type, lhs)); |
| return true; |
| } |
| return false; |
| } |
| |
| private JExpression simplifyNegate(JExpression exp) { |
| return simplifyNegate(null, exp); |
| } |
| |
| /** |
| * Simplify the expression <code>-exp</code>. |
| * |
| * @param original An expression equivalent to <code>-exp</code>. |
| * @param exp The expression to negate. |
| * |
| * @return A simplified expression equivalent to <code>- exp</code>. |
| */ |
| private JExpression simplifyNegate(JExpression original, JExpression exp) { |
| // - -x -> x |
| if (exp instanceof JPrefixOperation) { |
| JPrefixOperation prefarg = (JPrefixOperation) exp; |
| if (prefarg.getOp() == JUnaryOperator.NEG) { |
| return prefarg.getArg(); |
| } |
| } |
| |
| // no change |
| if (original != null) { |
| return original; |
| } |
| return new JPrefixOperation(exp.getSourceInfo(), JUnaryOperator.NEG, exp); |
| } |
| |
| private boolean simplifySub(JExpression lhs, JExpression rhs, Context ctx, JType type) { |
| if (isLiteralZero(rhs)) { |
| ctx.replaceMe(Simplifier.cast(type, lhs)); |
| return true; |
| } |
| if (isLiteralZero(lhs) && !isTypeFloatingPoint(type)) { |
| ctx.replaceMe(simplifyNegate(Simplifier.cast(type, rhs))); |
| return true; |
| } |
| return false; |
| } |
| |
| private void simplifyXor(JExpression lhs, JBooleanLiteral rhs, Context ctx) { |
| if (rhs.getValue()) { |
| ctx.replaceMe(new JPrefixOperation(lhs.getSourceInfo(), JUnaryOperator.NOT, lhs)); |
| } else { |
| ctx.replaceMe(lhs); |
| } |
| } |
| |
| /** |
| * Simplify XOR expressions. |
| * |
| * <pre> |
| * true ^ x -> !x |
| * false ^ x -> x |
| * y ^ true -> !y |
| * y ^ false -> y |
| * </pre> |
| */ |
| private void simplifyXor(JExpression lhs, JExpression rhs, Context ctx) { |
| if (lhs instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) lhs; |
| simplifyXor(rhs, booleanLiteral, ctx); |
| } else if (rhs instanceof JBooleanLiteral) { |
| JBooleanLiteral booleanLiteral = (JBooleanLiteral) rhs; |
| simplifyXor(lhs, booleanLiteral, ctx); |
| } |
| } |
| |
| private boolean toBoolean(JValueLiteral x) { |
| return ((JBooleanLiteral) x).getValue(); |
| } |
| |
| /** |
| * Cast a Java wrapper class (Integer, Double, Float, etc.) to a double. |
| */ |
| private double toDouble(JValueLiteral literal) { |
| Object valueObj = literal.getValueObj(); |
| if (valueObj instanceof Number) { |
| return ((Number) valueObj).doubleValue(); |
| } else { |
| return ((Character) valueObj).charValue(); |
| } |
| } |
| |
| /** |
| * Cast a Java wrapper class (Integer, Double, Float, etc.) to a long. |
| */ |
| private int toInt(JValueLiteral literal) { |
| Object valueObj = literal.getValueObj(); |
| if (valueObj instanceof Number) { |
| return ((Number) valueObj).intValue(); |
| } else { |
| return ((Character) valueObj).charValue(); |
| } |
| } |
| |
| /** |
| * Cast a Java wrapper class (Integer, Double, Float, etc.) to a long. |
| */ |
| private long toLong(JValueLiteral literal) { |
| Object valueObj = literal.getValueObj(); |
| if (valueObj instanceof Number) { |
| return ((Number) valueObj).longValue(); |
| } else { |
| return ((Character) valueObj).charValue(); |
| } |
| } |
| |
| private JLiteral tryGetConstant(JVariableRef x) { |
| if (!lvalues.contains(x)) { |
| JLiteral lit = x.getTarget().getConstInitializer(); |
| if (lit != null) { |
| /* |
| * Upcast the initializer so that the semantics of any arithmetic on |
| * this value is not changed. |
| */ |
| // TODO(spoon): use Simplifier.cast to shorten this |
| if (x.getType().isPrimitiveType() && (lit instanceof JValueLiteral)) { |
| JPrimitiveType xTypePrim = (JPrimitiveType) x.getType(); |
| lit = xTypePrim.coerce((JValueLiteral) lit); |
| } |
| return lit; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Replace String methods having literal args with the static result. |
| */ |
| private void tryOptimizeStringCall(JMethodCall x, Context ctx, JMethod method) { |
| |
| if (method.getType() == program.getTypeVoid()) { |
| return; |
| } |
| |
| if (method.getOriginalParamTypes().size() != method.getParams().size()) { |
| // One or more parameters were pruned, abort. |
| return; |
| } |
| |
| if (method.getName().endsWith("hashCode")) { |
| // This cannot be computed at compile time because our implementation |
| // differs from the JRE. |
| // TODO: actually, I think it DOES match now, so we can remove this? |
| return; |
| } |
| |
| boolean isStaticImplMethod = program.isStaticImpl(method); |
| JExpression instance = isStaticImplMethod ? x.getArgs().get(0) : x.getInstance(); |
| // drop the instance argument if the call was devirtualized as the compile time evaluation |
| // will use the real Java String class. |
| List<JExpression> arguments = isStaticImplMethod |
| ? x.getArgs().subList(1, x.getArgs().size()) |
| : x.getArgs(); |
| |
| // Handle toString specially to make sure it is a noop on non null string objects. |
| if (method.getName().endsWith("toString")) { |
| // replaces s.toString() with s |
| if (!instance.getType().canBeNull()) { |
| // Only replace when it known to be non null, otherwise it should follow the normal path |
| // and throw an NPE if null at runtime. |
| ctx.replaceMe(instance); |
| } |
| return; |
| } |
| |
| Object instanceLiteral = tryTranslateLiteral(instance, String.class); |
| method = isStaticImplMethod ? program.instanceMethodForStaticImpl(method) : method; |
| |
| if (instanceLiteral == null && !method.isStatic()) { |
| // Instance method on an instance that was not a literal. |
| return; |
| } |
| |
| // Extract constant values from arguments or bail out if not possible, and also extract the |
| // declared parameter types that will be used to get the correct override. |
| int numberOfParameters = method.getOriginalParamTypes().size(); |
| Object[] argumentConstantValues = new Object[numberOfParameters]; |
| Class<?>[] parametersClasses = new Class<?>[numberOfParameters]; |
| for (int i = 0; i < numberOfParameters; i++) { |
| parametersClasses[i] = classObjectForType(method.getOriginalParamTypes().get(i)); |
| argumentConstantValues[i] = tryTranslateLiteral(arguments.get(i), parametersClasses[i]); |
| if (parametersClasses[i] == null || argumentConstantValues[i] == null) { |
| // not a literal, cannot evaluate statically. |
| return; |
| } |
| } |
| |
| // Finally invoke the method statically. |
| JLiteral resultValue = staticallyInvokeStringMethod( |
| method.getName(), instanceLiteral, parametersClasses, argumentConstantValues); |
| if (resultValue != null) { |
| ctx.replaceMe(resultValue); |
| } |
| } |
| |
| private JLiteral staticallyInvokeStringMethod( |
| String methodName, Object instance, Class<?>[] parameterClasses, Object[] argumentValues) { |
| Method actualMethod = getMethod(methodName, String.class, parameterClasses); |
| if (actualMethod == null) { |
| // Convert all parameters types to Object to find a more generic applicable method. |
| Arrays.fill(parameterClasses, Object.class); |
| actualMethod = getMethod(methodName, String.class, parameterClasses); |
| } |
| if (actualMethod == null) { |
| return null; |
| } |
| |
| try { |
| return program.getLiteral(actualMethod.invoke(instance, argumentValues)); |
| } catch (Exception e) { |
| // couldn't evaluate statically or convert the result to a JLiteral |
| return null; |
| } |
| } |
| |
| private Method getMethod(String name, Class<?> enclosingClass, Class<?>[] parameterTypes) { |
| try { |
| return enclosingClass.getMethod(name, parameterTypes); |
| } catch (NoSuchMethodException e) { |
| return null; |
| } |
| } |
| |
| private void tryRemoveSwitch(JSwitchStatement x, Context ctx) { |
| JBlock body = x.getBody(); |
| if (body.getStatements().size() == 0) { |
| // Empty switch; just run the switch condition. |
| replaceMe(x.getExpr().makeStatement(), ctx); |
| } else if (body.getStatements().size() == 2) { |
| /* |
| * If there are only two statements, we know it's a case statement and |
| * something with an effect. |
| * |
| * TODO: make this more sophisticated; what we should really care about |
| * is how many case statements it contains, not how many statements: |
| * |
| * switch(i) { default: a(); b(); c(); } |
| * |
| * becomes { a(); b(); c(); } |
| * |
| * switch(i) { case 1: a(); b(); c(); } |
| * |
| * becomes if (i == 1) { a(); b(); c(); } |
| * |
| * switch(i) { case 1: a(); b(); break; default: c(); d(); } |
| * |
| * becomes if (i == 1) { a(); b(); } else { c(); d(); } |
| */ |
| JCaseStatement caseStatement = (JCaseStatement) body.getStatements().get(0); |
| JStatement statement = body.getStatements().get(1); |
| |
| FindBreakContinueStatementsVisitor visitor = new FindBreakContinueStatementsVisitor(); |
| visitor.accept(statement); |
| if (visitor.hasBreakContinueStatements()) { |
| // Cannot optimize. |
| return; |
| } |
| |
| if (caseStatement.getExpr() != null) { |
| // Create an if statement equivalent to the single-case switch. |
| JBinaryOperation compareOperation = |
| new JBinaryOperation(x.getSourceInfo(), program.getTypePrimitiveBoolean(), |
| JBinaryOperator.EQ, x.getExpr(), caseStatement.getExpr()); |
| JBlock block = new JBlock(x.getSourceInfo()); |
| block.addStmt(statement); |
| JIfStatement ifStatement = |
| new JIfStatement(x.getSourceInfo(), compareOperation, block, null); |
| replaceMe(ifStatement, ctx); |
| } else { |
| // All we have is a default case; convert to a JBlock. |
| JBlock block = new JBlock(x.getSourceInfo()); |
| block.addStmt(x.getExpr().makeStatement()); |
| block.addStmt(statement); |
| replaceMe(block, ctx); |
| } |
| } |
| } |
| |
| private Object tryTranslateLiteral(JExpression maybeLiteral, Class<?> type) { |
| if (!(maybeLiteral instanceof JValueLiteral)) { |
| return null; |
| } |
| // TODO: make this way better by a mile |
| if (type == boolean.class && maybeLiteral instanceof JBooleanLiteral) { |
| return Boolean.valueOf(((JBooleanLiteral) maybeLiteral).getValue()); |
| } |
| if (type == char.class && maybeLiteral instanceof JCharLiteral) { |
| return Character.valueOf(((JCharLiteral) maybeLiteral).getValue()); |
| } |
| if (type == double.class && maybeLiteral instanceof JFloatLiteral) { |
| return new Double(((JFloatLiteral) maybeLiteral).getValue()); |
| } |
| if (type == double.class && maybeLiteral instanceof JDoubleLiteral) { |
| return new Double(((JDoubleLiteral) maybeLiteral).getValue()); |
| } |
| if (type == int.class && maybeLiteral instanceof JIntLiteral) { |
| return Integer.valueOf(((JIntLiteral) maybeLiteral).getValue()); |
| } |
| if (type == long.class && maybeLiteral instanceof JLongLiteral) { |
| return Long.valueOf(((JLongLiteral) maybeLiteral).getValue()); |
| } |
| if (type == String.class && maybeLiteral instanceof JStringLiteral) { |
| return ((JStringLiteral) maybeLiteral).getValue(); |
| } |
| if (type == Object.class) { |
| // We already know it is a JValueLiteral instance |
| return ((JValueLiteral) maybeLiteral).getValueObj(); |
| } |
| return null; |
| } |
| } |
| |
| /** |
| * Examines code to find out whether it contains any break or continue |
| * statements. |
| * |
| * TODO: We could be more sophisticated with this. A nested while loop with an |
| * unlabeled break should not cause this visitor to return false. Nor should a |
| * labeled break break to another context. |
| */ |
| public static class FindBreakContinueStatementsVisitor extends JVisitor { |
| private boolean hasBreakContinueStatements = false; |
| |
| @Override |
| public void endVisit(JBreakStatement x, Context ctx) { |
| hasBreakContinueStatements = true; |
| } |
| |
| @Override |
| public void endVisit(JContinueStatement x, Context ctx) { |
| hasBreakContinueStatements = true; |
| } |
| |
| protected boolean hasBreakContinueStatements() { |
| return hasBreakContinueStatements; |
| } |
| } |
| |
| public static final String NAME = DeadCodeElimination.class.getSimpleName(); |
| |
| @VisibleForTesting |
| public static OptimizerStats exec(JProgram program) { |
| return new DeadCodeElimination(program).execImpl(Collections.singleton(program), |
| OptimizerContext.NULL_OPTIMIZATION_CONTEXT); |
| } |
| |
| // TODO(leafwang): Mark this as @VisibleForTesting eventually; this code path is also used |
| // by DataflowOptimizer for now. |
| public static OptimizerStats exec(JProgram program, JMethod method) { |
| return new DeadCodeElimination(program).execImpl(Collections.singleton(method), |
| OptimizerContext.NULL_OPTIMIZATION_CONTEXT); |
| } |
| |
| /** |
| * Apply DeadCodeElimination on the set of newly modified methods (obtained from the optimzer |
| * context). |
| */ |
| public static OptimizerStats exec(JProgram program, OptimizerContext optimizerCtx) { |
| Set<JMethod> affectedMethods = affectedMethods(optimizerCtx); |
| OptimizerStats stats = new DeadCodeElimination(program).execImpl(affectedMethods, optimizerCtx); |
| optimizerCtx.setLastStepFor(NAME, optimizerCtx.getOptimizationStep()); |
| optimizerCtx.incOptimizationStep(); |
| JavaAstVerifier.assertProgramIsConsistent(program); |
| return stats; |
| } |
| |
| /** |
| * Return the set of methods affected (because they are or callers of) by the modifications to the |
| * given set functions. |
| */ |
| private static Set<JMethod> affectedMethods(OptimizerContext optimizerCtx) { |
| Set<JMethod> modifiedMethods = |
| optimizerCtx.getModifiedMethodsSince(optimizerCtx.getLastStepFor(NAME)); |
| Set<JMethod> affectedMethods = Sets.newLinkedHashSet(); |
| affectedMethods.addAll(modifiedMethods); |
| affectedMethods.addAll(optimizerCtx.getCallers(modifiedMethods)); |
| affectedMethods.addAll(optimizerCtx.getMethodsByReferencedFields( |
| optimizerCtx.getModifiedFieldsSince(optimizerCtx.getLastStepFor(NAME)))); |
| return affectedMethods; |
| } |
| |
| private final JProgram program; |
| |
| private final Map<JType, Class<?>> classObjectsByType; |
| |
| public DeadCodeElimination(JProgram program) { |
| this.program = program; |
| classObjectsByType = new ImmutableMap.Builder() |
| .put(program.getTypeJavaLangObject(), Object.class) |
| .put(program.getTypeJavaLangString(), String.class) |
| .put(program.getTypePrimitiveBoolean(), boolean.class) |
| .put(program.getTypePrimitiveByte(), byte.class) |
| .put(program.getTypePrimitiveChar(), char.class) |
| .put(program.getTypePrimitiveDouble(), double.class) |
| .put(program.getTypePrimitiveFloat(), float.class) |
| .put(program.getTypePrimitiveInt(), int.class) |
| .put(program.getTypePrimitiveLong(), long.class) |
| .put(program.getTypePrimitiveShort(), short.class) |
| .build(); |
| } |
| |
| private OptimizerStats execImpl(Iterable<? extends JNode> nodes, OptimizerContext optimizerCtx) { |
| OptimizerStats stats = new OptimizerStats(NAME); |
| Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME); |
| |
| DeadCodeVisitor deadCodeVisitor = new DeadCodeVisitor(optimizerCtx); |
| for (JNode node : nodes) { |
| deadCodeVisitor.accept(node); |
| } |
| stats.recordModified(deadCodeVisitor.getNumMods()); |
| optimizeEvent.end("didChange", "" + stats.didChange()); |
| return stats; |
| } |
| |
| private enum AnalysisResult { TRUE, FALSE, UNKNOWN } |
| } |