| /* |
| * 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.CanBeStatic; |
| import com.google.gwt.dev.jjs.ast.Context; |
| import com.google.gwt.dev.jjs.ast.HasEnclosingType; |
| import com.google.gwt.dev.jjs.ast.HasName; |
| import com.google.gwt.dev.jjs.ast.JArrayType; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperation; |
| import com.google.gwt.dev.jjs.ast.JBinaryOperator; |
| import com.google.gwt.dev.jjs.ast.JClassType; |
| import com.google.gwt.dev.jjs.ast.JConstructor; |
| import com.google.gwt.dev.jjs.ast.JDeclarationStatement; |
| import com.google.gwt.dev.jjs.ast.JDeclaredType; |
| import com.google.gwt.dev.jjs.ast.JExpression; |
| import com.google.gwt.dev.jjs.ast.JField; |
| import com.google.gwt.dev.jjs.ast.JFieldRef; |
| import com.google.gwt.dev.jjs.ast.JLocal; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodBody; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JNameOf; |
| import com.google.gwt.dev.jjs.ast.JNewInstance; |
| import com.google.gwt.dev.jjs.ast.JNode; |
| import com.google.gwt.dev.jjs.ast.JParameter; |
| import com.google.gwt.dev.jjs.ast.JProgram; |
| import com.google.gwt.dev.jjs.ast.JReferenceType; |
| import com.google.gwt.dev.jjs.ast.JRuntimeTypeReference; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JVariable; |
| import com.google.gwt.dev.jjs.ast.JVariableRef; |
| import com.google.gwt.dev.jjs.ast.js.JMultiExpression; |
| import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; |
| import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; |
| import com.google.gwt.dev.util.collect.Stack; |
| 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.base.Predicate; |
| import com.google.gwt.thirdparty.guava.common.base.Predicates; |
| import com.google.gwt.thirdparty.guava.common.collect.ArrayListMultimap; |
| import com.google.gwt.thirdparty.guava.common.collect.ImmutableList; |
| import com.google.gwt.thirdparty.guava.common.collect.Iterables; |
| import com.google.gwt.thirdparty.guava.common.collect.ListMultimap; |
| import com.google.gwt.thirdparty.guava.common.collect.Sets; |
| |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Set; |
| |
| /** |
| * Remove globally unreferenced classes, interfaces, methods, parameters, and |
| * fields from the AST. This algorithm is based on having known "entry points" |
| * into the application which serve as the root(s) from which reachability is |
| * determined and everything else is rescued. Pruner determines reachability at |
| * a global level based on method calls and new operations; it does not perform |
| * any local code flow analysis. But, a local code flow optimization pass that |
| * can eliminate method calls would allow Pruner to prune additional nodes. |
| * |
| * Note: references to pruned types may still exist in the tree after this pass |
| * runs, however, it should only be in contexts that do not rely on any code |
| * generation for the pruned type. For example, it's legal to have a variable of |
| * a pruned type, or to try to cast to a pruned type. These will cause natural |
| * failures at run time; or later optimizations might be able to hard-code |
| * failures at compile time. |
| * |
| * Note: this class is limited to pruning parameters of static methods only. |
| */ |
| public class Pruner { |
| /** |
| * Remove assignments to pruned fields, locals and params. Nullify the return |
| * type of methods declared to return a globally uninstantiable type. Replace |
| * references to pruned variables and methods by references to the null field |
| * and null method, assignments to pruned variables, and nullify the type of |
| * variable whose type is a pruned type. |
| */ |
| private class CleanupRefsVisitor extends JModVisitorWithTemporaryVariableCreation { |
| private final Stack<JExpression> lValues = new Stack<JExpression>(); |
| private final ListMultimap<JMethod, JParameter> priorParametersByMethod; |
| private final Set<? extends JNode> referencedNonTypes; |
| { |
| // Initialize a sentinel value to avoid having to check for empty stack. |
| lValues.push(null); |
| } |
| |
| public CleanupRefsVisitor(Set<? extends JNode> referencedNodes, |
| ListMultimap<JMethod, JParameter> priorParametersByMethod, |
| OptimizerContext optimizerCtx) { |
| super(optimizerCtx); |
| this.referencedNonTypes = referencedNodes; |
| this.priorParametersByMethod = priorParametersByMethod; |
| } |
| |
| @Override |
| public void endVisit(JBinaryOperation x, Context ctx) { |
| if (x.getOp() != JBinaryOperator.ASG) { |
| return; |
| } |
| // The LHS of assignments may have been pruned. |
| lValues.pop(); |
| JExpression lhs = x.getLhs(); |
| if (!(lhs instanceof JVariableRef)) { |
| return; |
| } |
| |
| JVariableRef variableRef = (JVariableRef) lhs; |
| if (isVariablePruned(variableRef.getTarget())) { |
| // TODO: better null tracking; we might be missing some NPEs here. |
| JExpression replacement = |
| makeReplacementForAssignment(x.getSourceInfo(), variableRef, x.getRhs()); |
| ctx.replaceMe(replacement); |
| } |
| } |
| |
| @Override |
| public void endVisit(JDeclarationStatement x, Context ctx) { |
| super.endVisit(x, ctx); |
| lValues.pop(); |
| // The variable may have been pruned. |
| if (isVariablePruned(x.getVariableRef().getTarget())) { |
| JExpression replacement = |
| makeReplacementForAssignment(x.getSourceInfo(), x.getVariableRef(), x.getInitializer()); |
| ctx.replaceMe(replacement.makeStatement()); |
| } |
| } |
| |
| @Override |
| public void endVisit(JFieldRef x, Context ctx) { |
| // Handle l-values at a higher level. |
| if (lValues.peek() == x) { |
| return; |
| } |
| |
| if (isPruned(x.getField())) { |
| // The field is gone; replace x by a null field reference. |
| JFieldRef fieldRef = transformToNullFieldRef(x, program); |
| ctx.replaceMe(fieldRef); |
| } |
| } |
| |
| @Override |
| public void exit(JMethod x, Context ctx) { |
| JType type = x.getType(); |
| if (type instanceof JReferenceType && |
| !program.typeOracle.isInstantiatedType((JReferenceType) type)) { |
| x.setType(JReferenceType.NULL_TYPE); |
| } |
| Predicate<JMethod> isPruned = new Predicate<JMethod>() { |
| @Override |
| public boolean apply(JMethod method) { |
| return isPruned(method); |
| } |
| }; |
| Iterables.removeIf(x.getOverriddenMethods(), isPruned); |
| Iterables.removeIf(x.getOverridingMethods(), isPruned); |
| } |
| |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| JMethod method = x.getTarget(); |
| |
| // Is the method pruned entirely? |
| if (isPruned(method)) { |
| /* |
| * We assert that method must be non-static, otherwise it would have |
| * been rescued. |
| */ |
| ctx.replaceMe(transformToNullMethodCall(x, program)); |
| return; |
| } |
| |
| maybeReplaceForPrunedParameters(x, ctx); |
| } |
| |
| @Override |
| public void endVisit(JNameOf x, Context ctx) { |
| HasName node = x.getNode(); |
| boolean pruned; |
| if (node instanceof JField) { |
| pruned = isPruned((JField) node); |
| } else if (node instanceof JMethod) { |
| pruned = isPruned((JMethod) node); |
| } else if (node instanceof JReferenceType) { |
| pruned = !program.typeOracle.isInstantiatedType((JReferenceType) node); |
| } else { |
| throw new InternalCompilerException("Unhandled JNameOf node: " + node); |
| } |
| |
| if (pruned) { |
| ctx.replaceMe(program.getLiteralNull()); |
| } |
| } |
| |
| @Override |
| public void endVisit(JNewInstance x, Context ctx) { |
| maybeReplaceForPrunedParameters(x, ctx); |
| } |
| |
| @Override |
| public void endVisit(JRuntimeTypeReference x, Context ctx) { |
| if (!program.typeOracle.isInstantiatedType(x.getReferredType())) { |
| ctx.replaceMe(program.getLiteralNull()); |
| } |
| } |
| |
| @Override |
| public void endVisit(JsniFieldRef x, Context ctx) { |
| if (isPruned(x.getField())) { |
| String ident = x.getIdent(); |
| JField nullField = program.getNullField(); |
| JsniFieldRef nullFieldRef = |
| new JsniFieldRef(x.getSourceInfo(), ident, nullField, x.getEnclosingType(), x |
| .isLvalue()); |
| ctx.replaceMe(nullFieldRef); |
| } |
| } |
| |
| @Override |
| public void endVisit(JsniMethodRef x, Context ctx) { |
| // Redirect JSNI refs to uninstantiable types to the null method. |
| if (isPruned(x.getTarget())) { |
| String ident = x.getIdent(); |
| JMethod nullMethod = program.getNullMethod(); |
| JsniMethodRef nullMethodRef = |
| new JsniMethodRef(x.getSourceInfo(), ident, nullMethod, program.getJavaScriptObject()); |
| ctx.replaceMe(nullMethodRef); |
| } |
| } |
| |
| @Override |
| public void exit(JVariable x, Context ctx) { |
| JType type = x.getType(); |
| if (type instanceof JReferenceType && |
| !program.typeOracle.isInstantiatedType((JReferenceType) type)) { |
| x.setType(JReferenceType.NULL_TYPE); |
| madeChanges(); |
| } |
| } |
| |
| @Override |
| public boolean visit(JBinaryOperation x, Context ctx) { |
| if (x.getOp() == JBinaryOperator.ASG) { |
| lValues.push(x.getLhs()); |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JDeclarationStatement x, Context ctx) { |
| super.visit(x, ctx); |
| lValues.push(x.getVariableRef()); |
| return true; |
| } |
| |
| private <T extends HasEnclosingType & CanBeStatic> boolean isPruned(T node) { |
| if (!referencedNonTypes.contains(node)) { |
| return true; |
| } |
| JReferenceType enclosingType = node.getEnclosingType(); |
| return !node.isStatic() && enclosingType != null |
| && !program.typeOracle.isInstantiatedType(enclosingType); |
| } |
| |
| private boolean isVariablePruned(JVariable variable) { |
| if (variable instanceof JField) { |
| return isPruned((JField) variable); |
| } |
| return !referencedNonTypes.contains(variable); |
| } |
| |
| private JExpression makeReplacementForAssignment(SourceInfo info, JVariableRef variableRef, |
| JExpression rhs) { |
| // Replace with a multi, which may wind up empty. |
| JMultiExpression multi = new JMultiExpression(info); |
| |
| // If the lhs is a field ref, evaluate it first. |
| if (variableRef instanceof JFieldRef) { |
| JFieldRef fieldRef = (JFieldRef) variableRef; |
| JExpression instance = fieldRef.getInstance(); |
| if (instance != null) { |
| multi.addExpressions(instance); |
| } |
| } |
| |
| // If there is an rhs, evaluate it second. |
| if (rhs != null) { |
| multi.addExpressions(rhs); |
| } |
| if (multi.getNumberOfExpressions() == 1) { |
| return multi.getExpression(0); |
| } else { |
| return multi; |
| } |
| } |
| |
| // Arguments for pruned parameters will be pushed right into a multiexpression that will be |
| // evaluated with the next arg, e.g. m(arg1, (prunnedArg2, prunnedArg3, arg4)). |
| private void maybeReplaceForPrunedParameters(JMethodCall x, Context ctx) { |
| if (!priorParametersByMethod.containsKey(x.getTarget())) { |
| // No parameter was pruned. |
| return; |
| } |
| |
| JMethodCall replacementCall = x.cloneWithoutParameters(); |
| |
| assert !x.getTarget().canBePolymorphic(); |
| List<JParameter> originalParams = priorParametersByMethod.get(x.getTarget()); |
| |
| // The method and the call agree in the number of parameters. |
| assert originalParams.size() == x.getArgs().size(); |
| |
| // Traverse the call arguments left to right. |
| SourceInfo sourceInfo = x.getSourceInfo(); |
| JMultiExpression unevaluatedArgumentsForPrunedParameters = |
| new JMultiExpression(sourceInfo); |
| List<JExpression> args = x.getArgs(); |
| for (int currentArgumentIndex = 0; currentArgumentIndex < args.size(); |
| ++currentArgumentIndex) { |
| JExpression arg = args.get(currentArgumentIndex); |
| |
| // If the parameter was not pruned . |
| if (referencedNonTypes.contains(originalParams.get(currentArgumentIndex))) { |
| // Add the current argument to the list of unevaluated arguments and pass the multi |
| // expression to the call. |
| unevaluatedArgumentsForPrunedParameters.addExpressions(arg); |
| replacementCall.addArg(unevaluatedArgumentsForPrunedParameters); |
| // Reset the accumulating multi expression. |
| unevaluatedArgumentsForPrunedParameters = new JMultiExpression(sourceInfo); |
| } else if (arg.hasSideEffects()) { |
| // If the argument was pruned and has sideffects accumulate it; otherwise discard. |
| unevaluatedArgumentsForPrunedParameters.addExpressions(arg); |
| } |
| } |
| |
| if (unevaluatedArgumentsForPrunedParameters.isEmpty()) { |
| // We are done, all (side effectful) parameters have been evaluated. |
| ctx.replaceMe(replacementCall); |
| return; |
| } |
| |
| // If the last few parameters where pruned, we need to evaluate the (side effectful) arguments |
| // for those parameters. |
| if (replacementCall.getArgs().isEmpty()) { |
| // All parameters have been pruned, replace by (prunedArg1, ..., prunedArgn, m()). |
| unevaluatedArgumentsForPrunedParameters.addExpressions(replacementCall); |
| ctx.replaceMe(unevaluatedArgumentsForPrunedParameters); |
| return; |
| } |
| // Some parameters have been pruned from the end, replace by |
| // m(arg1,..., (lastArg = lastUnprunedArg, remainingArgs, lastArg)) |
| JExpression lastArg = Iterables.getLast(replacementCall.getArgs()); |
| JLocal tempVar = |
| createTempLocal(sourceInfo, Iterables.getLast( |
| Iterables.filter(originalParams, Predicates.in(referencedNonTypes))).getType(), "lastArg"); |
| unevaluatedArgumentsForPrunedParameters.addExpressions(0, JProgram.createAssignment( |
| lastArg.getSourceInfo(), tempVar.makeRef(sourceInfo), lastArg)); |
| unevaluatedArgumentsForPrunedParameters.addExpressions(tempVar.makeRef(sourceInfo)); |
| replacementCall.setArg(replacementCall.getArgs().size() - 1, unevaluatedArgumentsForPrunedParameters); |
| ctx.replaceMe(replacementCall); |
| } |
| } |
| |
| /** |
| * Remove any unreferenced classes and interfaces from JProgram. Remove any |
| * unreferenced methods and fields from their containing classes. |
| */ |
| private class PruneVisitor extends JChangeTrackingVisitor { |
| private final ListMultimap<JMethod, JParameter> priorParametersByMethod = |
| ArrayListMultimap.create(); |
| private final Set<? extends JNode> referencedNonTypes; |
| private final Set<? extends JReferenceType> referencedTypes; |
| |
| public PruneVisitor(Set<? extends JReferenceType> referencedTypes, |
| Set<? extends JNode> referencedNodes, OptimizerContext optimizerCtx) { |
| super(optimizerCtx); |
| this.referencedTypes = referencedTypes; |
| this.referencedNonTypes = referencedNodes; |
| } |
| |
| public ListMultimap<JMethod, JParameter> getPriorParametersByMethod() { |
| return priorParametersByMethod; |
| } |
| |
| @Override |
| public boolean visit(JDeclaredType type, Context ctx) { |
| assert referencedTypes.contains(type); |
| Predicate<JNode> notReferenced = Predicates.not(Predicates.in(referencedNonTypes)); |
| removeFields(notReferenced, type); |
| removeMethods(notReferenced, type); |
| |
| for (JMethod method : type.getMethods()) { |
| accept(method); |
| } |
| |
| return false; |
| } |
| |
| @Override |
| public boolean enter(JMethod x, Context ctx) { |
| if (!x.canBePolymorphic()) { |
| /* |
| * Don't prune parameters on unreferenced methods. The methods might not |
| * be reachable through the current method traversal routines, but might |
| * be used or checked elsewhere. |
| * |
| * Basically, if we never actually checked if the method parameters were |
| * used or not, don't prune them. Doing so would leave a number of |
| * dangling JParameterRefs that blow up in later optimizations. |
| */ |
| if (!referencedNonTypes.contains(x)) { |
| return true; |
| } |
| |
| /* |
| * We cannot prune parameters from staticImpls that still have a live |
| * instance method, because doing so would screw up any subsequent |
| * devirtualizations. If the instance method has been pruned, then it's |
| * okay. Also, it's okay on the final pass (saveCodeTypes == false) |
| * since no more devirtualizations will occur. |
| * |
| * TODO: prune params; MakeCallsStatic smarter to account for it. |
| */ |
| JMethod instanceMethod = program.instanceMethodForStaticImpl(x); |
| // Unless the instance method has already been pruned, of course. |
| if (saveCodeGenTypes && instanceMethod != null && |
| referencedNonTypes.contains(instanceMethod)) { |
| // instance method is still live |
| return true; |
| } |
| |
| List<JParameter> originalParameters = ImmutableList.copyOf(x.getParams()); |
| |
| for (int i = 0; i < x.getParams().size(); ++i) { |
| JParameter param = x.getParams().get(i); |
| if (!referencedNonTypes.contains(param)) { |
| x.removeParam(i); |
| madeChanges(); |
| --i; |
| } |
| } |
| |
| if (x.getParams().size() != originalParameters.size()) { |
| // Parameters were pruned. record the original parameters for the cleanup pass. |
| priorParametersByMethod.putAll(x, originalParameters); |
| } |
| } |
| |
| return true; |
| } |
| |
| @Override |
| public boolean visit(JMethodBody x, Context ctx) { |
| for (int i = 0; i < x.getLocals().size(); ++i) { |
| if (!referencedNonTypes.contains(x.getLocals().get(i))) { |
| x.removeLocal(i--); |
| madeChanges(); |
| } |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean visit(JProgram program, Context ctx) { |
| for (Iterator<JDeclaredType> it = program.getDeclaredTypes().iterator(); it.hasNext();) { |
| JDeclaredType type = it.next(); |
| if (referencedTypes.contains(type)) { |
| accept(type); |
| } else { |
| prunedMethods.addAll(type.getMethods()); |
| methodsWereRemoved(type.getMethods()); |
| fieldsWereRemoved(type.getFields()); |
| it.remove(); |
| madeChanges(); |
| } |
| } |
| return false; |
| } |
| |
| private void removeFields(Predicate<JNode> shouldRemove, JDeclaredType type) { |
| for (int i = 0; i < type.getFields().size(); ++i) { |
| JField field = type.getFields().get(i); |
| if (!shouldRemove.apply(field)) { |
| continue; |
| } |
| wasRemoved(field); |
| type.removeField(i); |
| madeChanges(); |
| --i; |
| } |
| } |
| |
| private void removeMethods(Predicate<JNode> shouldRemove, JDeclaredType type) { |
| // Skip method 0 which is clinit and is assumed to exist. |
| assert type.getMethods().get(0) == type.getClinitMethod(); |
| for (int i = 1; i < type.getMethods().size(); ++i) { |
| JMethod method = type.getMethods().get(i); |
| if (!shouldRemove.apply(method)) { |
| continue; |
| } |
| prunedMethods.add(method); |
| wasRemoved(method); |
| type.removeMethod(i); |
| program.removeStaticImplMapping(method); |
| madeChanges(); |
| --i; |
| } |
| } |
| } |
| |
| private static final String NAME = Pruner.class.getSimpleName(); |
| |
| public static OptimizerStats exec(JProgram program, boolean noSpecialTypes, |
| OptimizerContext optimizerCtx) { |
| Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME); |
| OptimizerStats stats = new Pruner(program, noSpecialTypes).execImpl(optimizerCtx); |
| optimizeEvent.end("didChange", "" + stats.didChange()); |
| return stats; |
| } |
| |
| public static OptimizerStats exec(JProgram program, boolean noSpecialTypes) { |
| return exec(program, noSpecialTypes, OptimizerContext.NULL_OPTIMIZATION_CONTEXT); |
| } |
| |
| /** |
| * Transform a reference to a pruned instance field into a reference to the |
| * null field, which will be used to replace <code>x</code>. |
| */ |
| public static JFieldRef transformToNullFieldRef(JFieldRef x, JProgram program) { |
| JExpression instance = x.getInstance(); |
| |
| /* |
| * We assert that field must be non-static if it's an rvalue, otherwise it |
| * would have been rescued. |
| */ |
| // assert !x.getField().isStatic(); |
| /* |
| * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with |
| * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to |
| * never be rescured. This in turn causes invalid references to static |
| * methods, which violates otherwise good assumptions about compiler |
| * operation. |
| * |
| * TODO: Remove this when ControlFlowAnalyzer doesn't special-case |
| * CLH.clinit(). |
| */ |
| if (x.getField().isStatic() && instance == null) { |
| instance = program.getLiteralNull(); |
| } |
| |
| assert instance != null; |
| if (!instance.hasSideEffects()) { |
| instance = program.getLiteralNull(); |
| } |
| |
| JFieldRef fieldRef = |
| new JFieldRef(x.getSourceInfo(), instance, program.getNullField(), x.getEnclosingType(), |
| primitiveTypeOrNullTypeOrArray(program, x.getType())); |
| return fieldRef; |
| } |
| |
| /** |
| * Transform a call to a pruned instance method (or static impl) into a call |
| * to the null method, which will be used to replace <code>x</code>. |
| */ |
| public static JMethodCall transformToNullMethodCall(JMethodCall x, JProgram program) { |
| JExpression instance = x.getInstance(); |
| List<JExpression> args = x.getArgs(); |
| if (program.isStaticImpl(x.getTarget())) { |
| instance = args.get(0); |
| args = args.subList(1, args.size()); |
| } else { |
| /* |
| * We assert that method must be non-static, otherwise it would have been |
| * rescued. |
| */ |
| // assert !x.getTarget().isStatic(); |
| /* |
| * HACK HACK HACK: ControlFlowAnalyzer has special hacks for dealing with |
| * ClassLiterals, which causes the body of ClassLiteralHolder's clinit to |
| * never be rescured. This in turn causes invalid references to static |
| * methods, which violates otherwise good assumptions about compiler |
| * operation. |
| * |
| * TODO: Remove this when ControlFlowAnalyzer doesn't special-case |
| * CLH.clinit(). |
| */ |
| if (x.getTarget().isStatic() && instance == null) { |
| instance = program.getLiteralNull(); |
| } |
| } |
| assert (instance != null); |
| if (!instance.hasSideEffects()) { |
| instance = program.getLiteralNull(); |
| } |
| |
| JMethodCall newCall = new JMethodCall(x.getSourceInfo(), instance, program.getNullMethod()); |
| newCall.overrideReturnType(primitiveTypeOrNullTypeOrArray(program, x.getType())); |
| // Retain the original arguments, they will be evaluated for side effects. |
| for (JExpression arg : args) { |
| if (arg.hasSideEffects()) { |
| newCall.addArg(arg); |
| } |
| } |
| return newCall; |
| } |
| |
| /** |
| * Return the smallest type that is is a subtype of the argument. |
| */ |
| static JType primitiveTypeOrNullTypeOrArray(JProgram program, JType type) { |
| if (type instanceof JArrayType) { |
| JType leafType = primitiveTypeOrNullTypeOrArray(program, ((JArrayType) type).getLeafType()); |
| return program.getOrCreateArrayType(leafType, ((JArrayType) type).getDims()); |
| } |
| if (type.isPrimitiveType()) { |
| return type; |
| } |
| return JReferenceType.NULL_TYPE; |
| } |
| |
| private final JProgram program; |
| |
| private final boolean saveCodeGenTypes; |
| |
| private final Set<JMethod> prunedMethods = Sets.newLinkedHashSet(); |
| |
| private Pruner(JProgram program, boolean saveCodeGenTypes) { |
| this.program = program; |
| this.saveCodeGenTypes = saveCodeGenTypes; |
| } |
| |
| private OptimizerStats execImpl(OptimizerContext optimizerCtx) { |
| OptimizerStats stats = new OptimizerStats(NAME); |
| |
| ControlFlowAnalyzer livenessAnalyzer = new ControlFlowAnalyzer(program); |
| livenessAnalyzer.setForPruning(); |
| |
| // SPECIAL: Immortal codegen types are never pruned |
| traverseTypes(livenessAnalyzer, program.immortalCodeGenTypes); |
| |
| if (saveCodeGenTypes) { |
| /* |
| * SPECIAL: Some classes contain methods used by code generation later. |
| * Unless those transforms have already been performed, we must rescue all |
| * contained methods for later user. |
| */ |
| traverseTypes(livenessAnalyzer, program.codeGenTypes); |
| } |
| livenessAnalyzer.traverseEverything(); |
| |
| program.typeOracle.setInstantiatedTypes(livenessAnalyzer.getInstantiatedTypes()); |
| |
| PruneVisitor pruner = |
| new PruneVisitor(livenessAnalyzer.getReferencedTypes(), livenessAnalyzer |
| .getLiveFieldsAndMethods(), optimizerCtx); |
| pruner.accept(program); |
| stats.recordModified(pruner.getNumMods()); |
| |
| if (!pruner.didChange()) { |
| return stats; |
| } |
| CleanupRefsVisitor cleaner = |
| new CleanupRefsVisitor(livenessAnalyzer.getLiveFieldsAndMethods(), pruner |
| .getPriorParametersByMethod(), optimizerCtx); |
| cleaner.accept(program.getDeclaredTypes()); |
| optimizerCtx.incOptimizationStep(); |
| optimizerCtx.syncDeletedSubCallGraphsSince(optimizerCtx.getLastStepFor(NAME) + 1, |
| prunedMethods); |
| JavaAstVerifier.assertProgramIsConsistent(program); |
| return stats; |
| } |
| |
| /** |
| * Traverse from all methods starting from a set of types. |
| */ |
| private void traverseTypes(ControlFlowAnalyzer livenessAnalyzer, |
| List<JClassType> types) { |
| for (JClassType type : types) { |
| livenessAnalyzer.traverseFromReferenceTo(type); |
| for (JMethod method : type.getMethods()) { |
| if (method instanceof JConstructor) { |
| livenessAnalyzer.traverseFromInstantiationOf(type); |
| } |
| livenessAnalyzer.traverseFrom(method); |
| } |
| } |
| } |
| } |