/* * 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.SourceOrigin; import com.google.gwt.dev.jjs.ast.CanBeAbstract; import com.google.gwt.dev.jjs.ast.CanBeStatic; import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JArrayRef; import com.google.gwt.dev.jjs.ast.JBinaryOperation; import com.google.gwt.dev.jjs.ast.JBinaryOperator; import com.google.gwt.dev.jjs.ast.JCastOperation; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JConditional; 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.JInstanceOf; import com.google.gwt.dev.jjs.ast.JInterfaceType; import com.google.gwt.dev.jjs.ast.JLocal; 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.JParameter; import com.google.gwt.dev.jjs.ast.JPermutationDependentValue; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; import com.google.gwt.dev.jjs.ast.JRunAsync; import com.google.gwt.dev.jjs.ast.JTryStatement; 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.JVisitor; import com.google.gwt.dev.jjs.ast.js.JsniFieldRef; import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; 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.base.Predicate; import com.google.gwt.thirdparty.guava.common.collect.FluentIterable; import com.google.gwt.thirdparty.guava.common.collect.HashMultimap; import com.google.gwt.thirdparty.guava.common.collect.Iterables; import com.google.gwt.thirdparty.guava.common.collect.Lists; import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.gwt.thirdparty.guava.common.collect.Multimap; import com.google.gwt.thirdparty.guava.common.collect.Sets; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Stack; /** * The purpose of this pass is to record "type flow" information and then use * the information to infer places where "tighter" (that is, more specific) * types can be inferred for locals, fields, parameters, and method return * types. We also optimize dynamic casts and instanceof operations. * * Examples: * * This declaration of variable foo: * * <pre> * final List foo = new ArrayList(); * </pre> * * can be tightened from List to ArrayList because no type other than ArrayList * can ever be assigned to foo. * * The return value of the method bar: * * <pre> * Collection bar() { * return new LinkedHashSet; * } * </pre> * * can be tightened from Collection to LinkedHashSet since it * will never return any other type. * * By working in conjunction with {@link MethodCallTightener}, Type tightening * can eliminate generating run-time dispatch code for polymorphic methods. * * Type flow occurs automatically in most JExpressions. But locals, fields, * parameters, and method return types serve as "way points" where type * information is fixed based on the declared type. Type tightening can be done * by analyzing the types "flowing" into each way point, and then updating the * declared type of the way point to be a more specific type than it had before. * * Oddly, it's quite possible to tighten a variable to the Null type, which * means either the variable was never assigned, or it was only ever assigned * null. This is great for two reasons: * * 1) Once a variable has been tightened to null, it will no longer impact the * variables that depend on it. * * 2) It creates some very interesting opportunities to optimize later, since we * know statically that the value of the variable is always null. * * Open issue: we don't handle recursion where a method passes (some of) its own * args to itself or returns its own call result. With our naive analysis, we * can't figure out that tightening might occur. * * Type flow is not supported for primitive types, only reference types. */ public class TypeTightener { /** * Replaces dangling null references with dummy calls. */ public class FixDanglingRefsVisitor extends JChangeTrackingVisitor { public FixDanglingRefsVisitor(OptimizerContext optimizerCtx) { super(optimizerCtx); } @Override public void endVisit(JFieldRef x, Context ctx) { JExpression instance = x.getInstance(); JField field = x.getField(); if (field.isStatic() && instance != null) { // this doesn't really belong here, but while we're here let's remove // non-side-effect qualifiers to statics if (!instance.hasSideEffects()) { JFieldRef fieldRef = new JFieldRef(x.getSourceInfo(), null, field, x.getEnclosingType()); ctx.replaceMe(fieldRef); } } else if (isNullReference(field, instance) && field != program.getNullField()) { // Change any dereference of null to use the null field ctx.replaceMe(Pruner.transformToNullFieldRef(x, program)); } } @Override public void endVisit(JMethodCall x, Context ctx) { JExpression instance = x.getInstance(); JMethod method = x.getTarget(); boolean isStaticImpl = program.isStaticImpl(method); if (method.isStatic() && !isStaticImpl && instance != null) { // TODO: move to DeadCodeElimination. // this doesn't really belong here, but while we're here let's remove // non-side-effect qualifiers to statics if (!instance.hasSideEffects()) { JMethodCall newCall = new JMethodCall(x.getSourceInfo(), null, x.getTarget()); newCall.addArgs(x.getArgs()); ctx.replaceMe(newCall); } } else if (isNullReference(method, instance)) { ctx.replaceMe(Pruner.transformToNullMethodCall(x, program)); } else if (isStaticImpl && method.getParams().size() > 0 && method.getParams().get(0).isThis() && x.getArgs().size() > 0 && x.getArgs().get(0).getType().isNullType()) { // bind null instance calls to the null method for static impls ctx.replaceMe(Pruner.transformToNullMethodCall(x, program)); } } @Override public void endVisit(JNewInstance x, Context ctx) { // Do not visit. } } /* * TODO(later): handle recursion, self-assignment, arrays, method tightening * on invocations from within JSNI blocks */ /** * Record "type flow" information. Variables receive type flow via assignment. * As a special case, Parameters also receive type flow based on the types of * arguments used when calling the containing method (think of this as a kind * of assignment). Method return types receive type flow from their contained * return statements, plus the return type of any methods that * override/implement them. * * Note that we only have to run this pass ONCE to record the relationships, * because type tightening never changes any relationships, only the types of * the things related. In my original implementation, I had naively mapped * nodes onto sets of JReferenceType directly, which meant I had to rerun this * visitor each time. */ private class RecordVisitor extends JVisitor { private JMethod currentMethod; private Predicate<JField> canUninitializedValueBeObserved; /** * The call trace invoked by arguments in a method call. It is used to record * {@code callersByFieldRefArg} and {@code callersByMethodCallArg}. * For example, fun1(fun2(fun3(), fun4()), fun5()); The stack would be ... * fun1 -> fun2 -> fun3; (pop fun3, push fun4) * fun1 -> fun2 -> fun4; (pop fun4) * fun1 -> fun2; (pop fun2, push fun5) * fun1 -> fun5; (pop fun5) * fun1; */ private Stack<JMethod> nestedCallTrace = new Stack<JMethod>(); @Override public void endVisit(JBinaryOperation x, Context ctx) { if (x.isAssignment() && (x.getType() instanceof JReferenceType)) { JExpression lhs = x.getLhs(); if (lhs instanceof JVariableRef) { addAssignment(((JVariableRef) lhs).getTarget(), x.getOp() == JBinaryOperator.ASG ? x.getRhs() : x); } else { assert lhs instanceof JArrayRef; } } } @Override public void endVisit(JClassType x, Context ctx) { if (program.typeOracle.isInstantiatedType(x)) { for (JClassType cur = x; cur != null; cur = cur.getSuperClass()) { addImplementor(cur, x); addInterfacesImplementorRecursive(cur, x); } } } @Override public void endVisit(JDeclarationStatement x, Context ctx) { JExpression initializer = x.getInitializer(); if (initializer != null) { addAssignment(x.getVariableRef().getTarget(), initializer); } } @Override public void endVisit(JField x, Context ctx) { if (!x.hasInitializer() || canUninitializedValueBeObserved.apply(x)) { addAssignment(x, x.getType().getDefaultValue()); } currentMethod = null; } @Override public void endVisit(JFieldRef x, Context ctx) { if (!nestedCallTrace.empty()) { calledMethodsByFieldRefArg.put(x.getField(), nestedCallTrace.peek()); } } @Override public void endVisit(JMethod x, Context ctx) { currentMethod = null; } @Override public void endVisit(JMethodCall x, Context ctx) { // All of the params in the target method are considered to be assigned by // the arguments from the caller Iterator<JExpression> argIt = x.getArgs().iterator(); List<JParameter> params = x.getTarget().getParams(); for (JParameter param : params) { JExpression arg = argIt.next(); if (param.getType() instanceof JReferenceType) { addAssignment(param, arg); } } nestedCallTrace.pop(); if (!nestedCallTrace.empty()) { calledMethodsByMethodCallArg.put(x.getTarget(), nestedCallTrace.peek()); } } @Override public void endVisit(JReturnStatement x, Context ctx) { if (currentMethod.getType() instanceof JReferenceType) { addReturn(currentMethod, x.getExpr()); } } @Override public void endVisit(JsniFieldRef x, Context ctx) { if (x.isLvalue()) { // If this happens in JSNI, we can't make any type-tightening // assumptions. Fake an assignment-to-self to prevent tightening. addAssignment(x.getTarget(), x); } } @Override public void endVisit(JsniMethodRef x, Context ctx) { // If this happens in JSNI, we can't make any type-tightening assumptions // Fake an assignment-to-self on all args to prevent tightening JMethod method = x.getTarget(); for (JParameter param : method.getParams()) { addAssignment(param, param.makeRef(SourceOrigin.UNKNOWN)); } } @Override public void endVisit(JTryStatement x, Context ctx) { // Never tighten args to catch blocks // Fake an assignment-to-self to prevent tightening for (JTryStatement.CatchClause clause : x.getCatchClauses()) { addAssignment(clause.getArg().getTarget(), clause.getArg()); } } /** * Merge param call args across overriders/implementors. We can't tighten a * param type in an overriding method if the declaring method is looser. */ @Override public boolean visit(JMethod x, Context ctx) { currentMethod = x; if (x.canBePolymorphic()) { /* * Add an assignment to each parameter from that same parameter in every * method this method overrides. */ Collection<JMethod> overriddenMethods = x.getOverriddenMethods(); if (overriddenMethods.isEmpty()) { return true; } for (int j = 0, c = x.getParams().size(); j < c; ++j) { JParameter param = x.getParams().get(j); for (JMethod baseMethod : overriddenMethods) { JParameter baseParam = baseMethod.getParams().get(j); add(param, baseParam, paramUpRefs); } } } return true; } @Override public boolean visit(JMethodCall x, Context ctx) { nestedCallTrace.push(x.getTarget()); return true; } public void record(JProgram program) { canUninitializedValueBeObserved = ComputePotentiallyObservableUninitializedValues .analyze(program); accept(program); } private void addAssignment(JVariable target, JExpression rhs) { add(target, rhs, assignments); } private void addImplementor(JReferenceType target, JClassType implementor) { add(target, implementor, implementors); } private void addInterfacesImplementorRecursive(JDeclaredType target, JClassType implementor) { for (JInterfaceType implment : target.getImplements()) { addImplementor(implment, implementor); addInterfacesImplementorRecursive(implment, implementor); } } private void addReturn(JMethod target, JExpression expr) { add(target, expr, returns); } } /** * Wherever possible, use the type flow information recorded by RecordVisitor * to change the declared type of a field, local, parameter, or method to a * more specific type. * * Also optimize dynamic casts and instanceof operations where possible. */ public class TightenTypesVisitor extends JChangeTrackingVisitor { public TightenTypesVisitor(OptimizerContext optimizerCtx) { super(optimizerCtx); } /** * Tries to determine a specific concrete type for the cast, then either * removes the cast, or tightens the cast to a narrower type. * * If static analysis determines that a cast is not possible, swap in a cast * to a null type. This will later be normalized into throwing an * Exception. * * @see ImplementCastsAndTypeChecks */ @Override public void endVisit(JCastOperation x, Context ctx) { JType argumentType = x.getExpr().getType(); if (!(x.getCastType() instanceof JReferenceType) || !(argumentType instanceof JReferenceType)) { return; } JReferenceType toType = getSingleConcreteType(x.getCastType()); if (toType == null) { toType = (JReferenceType) x.getCastType(); } JReferenceType fromType = getSingleConcreteType(argumentType); if (fromType == null) { fromType = (JReferenceType) argumentType; } if (program.typeOracle.castSucceedsTrivially(fromType, toType)) { // remove the cast operation ctx.replaceMe(x.getExpr()); return; } if ((!program.typeOracle.isInstantiatedType(toType) || program.typeOracle.castFailsTrivially(fromType, toType)) && toType != JReferenceType.NULL_TYPE) { // replace with a placeholder cast to NULL, unless it's already a cast to NULL ctx.replaceMe(new JCastOperation(x.getSourceInfo(), JReferenceType.NULL_TYPE, x.getExpr())); return; } // If possible, try to use a narrower cast JReferenceType tighterType = getSingleConcreteType(toType); if (tighterType != null && tighterType != toType) { ctx.replaceMe(new JCastOperation(x.getSourceInfo(), tighterType, x.getExpr())); } } @Override public void endVisit(JConditional x, Context ctx) { if (!(x.getType() instanceof JReferenceType)) { return; } JReferenceType type = (JReferenceType) x.getType(); JReferenceType resultType = strongerType(type, (JReferenceType) x.getThenExpr().getType(), (JReferenceType) x.getElseExpr().getType()); if (type != resultType) { x.setType(resultType); madeChanges(); } } @Override public void exit(JField x, Context ctx) { if (program.codeGenTypes.contains(x.getEnclosingType()) || x.canBeReferencedExternally() || x.canBeImplementedExternally()) { // We cannot tighten this field as we don't see all references or the initial value. return; } if (!x.isVolatile()) { tighten(x); } } @Override public void endVisit(JInstanceOf x, Context ctx) { JType argType = x.getExpr().getType(); if (!(argType instanceof JReferenceType)) { // TODO: is this even possible? Replace with assert maybe. return; } JReferenceType concreteType = getSingleConcreteType(x.getTestType()); // If possible, try to use a narrower cast if (concreteType != null) { ctx.replaceMe( new JInstanceOf(x.getSourceInfo(), concreteType.getUnderlyingType(), x.getExpr())); } } @Override public void endVisit(JLocal x, Context ctx) { tighten(x); } /** * Tighten based on return types and overrides. */ @Override public void exit(JMethod x, Context ctx) { if (program.codeGenTypes.contains(x.getEnclosingType())) { return; } if (!(x.getType() instanceof JReferenceType)) { return; } JReferenceType returnType = (JReferenceType) x.getType(); if (returnType.isNullType()) { return; } // tighten based on non-instantiability if (!program.typeOracle.isInstantiatedType(returnType)) { x.setType(JReferenceType.NULL_TYPE); madeChanges(); return; } JReferenceType concreteType = getSingleConcreteType(returnType); if (concreteType != null) { x.setType(concreteType); madeChanges(); } /* * The only information that we can infer about native methods is if they * are declared to return a leaf type. */ if (x.isJsniMethod() || x.canBeImplementedExternally()) { return; } Iterable<JReferenceType> returnTypes = Iterables.concat( JjsUtils.getExpressionTypes(returns.get(x)), JjsUtils.getExpressionTypes(x.getOverridingMethods())); JReferenceType strengthenedType = strongerType(returnType, returnTypes); if (returnType != strengthenedType) { x.setType(strengthenedType); madeChanges(); } } /** * Tighten the target method from the abstract base method to the final * implementation. */ @Override public void endVisit(JMethodCall x, Context ctx) { if (!x.canBePolymorphic() || x.isVolatile()) { return; } JMethod target = x.getTarget(); JMethod concreteMethod = getSingleConcreteMethodOverride(target); assert concreteMethod != target; if (concreteMethod != null) { assert !x.isStaticDispatchOnly(); JMethodCall newCall = new JMethodCall(x.getSourceInfo(), x.getInstance(), concreteMethod); newCall.addArgs(x.getArgs()); newCall.setCannotBePolymorphic(); ctx.replaceMe(newCall); } } @Override public void endVisit(JParameter x, Context ctx) { JMethod currentMethod = getCurrentMethod(); if (program.codeGenTypes.contains(currentMethod.getEnclosingType()) // We cannot tighten this parameter as we don't know all callers. || currentMethod.canBeReferencedExternally() // And do not tighten JsVararg parameters because the implementation of the JsVarargs // calling convention creates an array of the parameter type hence requires that it is // preserved. || x.isVarargs() && currentMethod.isJsMethodVarargs()) { return; } tighten(x); } @Override public void endVisit(JPermutationDependentValue x, Context ctx) { throw new IllegalStateException("AST should not contain permutation dependent values at " + "this point but contains " + x); } @Override public boolean visit(JRunAsync x, Context ctx) { // JRunAsync's onSuccessCall is not normally traversed but should be here. x.traverseOnSuccess(this); return true; } /** * Find a replacement method. If the original method is abstract, this will * return the leaf, final implementation of the method. If the method is * already concrete, but enclosed by an abstract type, the overriding method * from the leaf concrete type will be returned. If the method is static, * return <code>null</code> no matter what. */ private JMethod getSingleConcreteMethodOverride(JMethod method) { assert method.canBePolymorphic(); if (getSingleConcreteType(method.getEnclosingType()) != null) { return getSingleConcrete(method.getOverridingMethods()); } return null; } @Override public boolean visit(JClassType x, Context ctx) { // don't mess with classes used in code gen if (program.codeGenTypes.contains(x)) { return false; } return true; } @Override public boolean enter(JMethod x, Context ctx) { /* * Explicitly NOT visiting native methods since we can't infer further * type information. */ return !x.isJsniMethod(); } /** * Given an abstract type, return the single concrete implementation of that * type. */ private JReferenceType getSingleConcreteType(JType type) { if (!(type instanceof JReferenceType) || type.canBeImplementedExternally()) { return null; } JReferenceType refType = (JReferenceType) type; if (refType.isAbstract()) { JReferenceType singleConcrete = getSingleConcrete(implementors.get(refType.getUnderlyingType())); assert (singleConcrete == null || program.typeOracle.isInstantiatedType(singleConcrete)); if (singleConcrete == null) { return null; } singleConcrete = singleConcrete.strengthenToExact(); return refType.canBeNull() ? singleConcrete : singleConcrete.strengthenToNonNull(); } return null; } /** * Tighten based on assignment, and for parameters, callArgs as well. */ private void tighten(JVariable x) { if (!(x.getType() instanceof JReferenceType)) { return; } JReferenceType varType = (JReferenceType) x.getType(); if (varType.isNullType()) { return; } // tighten based on non-instantiability if (!program.typeOracle.isInstantiatedType(varType)) { x.setType(JReferenceType.NULL_TYPE); madeChanges(); return; } // tighten based on leaf types JReferenceType leafType = getSingleConcreteType(varType); if (leafType != null) { x.setType(leafType); madeChanges(); return; } // tighten based on assignment Collection<JReferenceType> assignmentTypes = getAssignmentsIfValid(x); if (assignmentTypes == null) { return; } JReferenceType strengthenedType = strongerType(varType, Iterables.concat(assignmentTypes, JjsUtils.getExpressionTypes(paramUpRefs.get(x)))); if (varType != strengthenedType) { x.setType(strengthenedType); madeChanges(); } } private Collection<JReferenceType> getAssignmentsIfValid(JVariable variable) { Collection<JExpression> assignedExpressions = assignments.get(variable); if (assignedExpressions == null) { return Collections.emptyList(); } Collection<JReferenceType> assignedTypes = Lists.newArrayList(); for (JExpression expression : assignedExpressions) { JType expressionType = expression.getType(); if (!(expressionType instanceof JReferenceType)) { // In some case there will be types that are not JReferenceType; and it is not safe in // such a case to replace the type of the lhs. Those cases only arise by AST manipulation, // see {@link ImplementCastsAndTypeChecks} and {@link Class#createForClass}. return null; } assignedTypes.add((JReferenceType) expressionType); } return assignedTypes; } } private static final String NAME = TypeTightener.class.getSimpleName(); public static OptimizerStats exec(JProgram program, OptimizerContext optimizerCtx) { Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME); OptimizerStats stats = new TypeTightener(program).execImpl(optimizerCtx); optimizerCtx.incOptimizationStep(); optimizeEvent.end("didChange", "" + stats.didChange()); return stats; } @VisibleForTesting static OptimizerStats exec(JProgram program) { return exec(program, new FullOptimizerContext(program)); } private static <T, V> void add(T key, V value, Map<T, Collection<V>> map) { Collection<V> list = map.get(key); if (list == null) { list = Sets.newLinkedHashSet(); map.put(key, list); } list.add(value); } /** * Find exactly one concrete element in a collection. If there are * none or more than one concrete element, return <code>null</code>. */ private static <T extends CanBeAbstract> T getSingleConcrete(Collection<T> collection) { // No collection, then no concrete version if (collection == null) { return null; } Iterator<T> concreteIterator = FluentIterable.from(collection).filter( new Predicate<T>() { @Override public boolean apply(T element) { return !element.isAbstract(); } }).iterator(); if (!concreteIterator.hasNext()) { return null; } T firstConcrete = concreteIterator.next(); if (concreteIterator.hasNext()) { // multiple concrete elements. return null; } return firstConcrete; } /** * For each program Variable (includes fields, locals and parameters) tracks the set * of expressions that are assigned to them. Assignments include parameter instantiations. * */ private final Map<JVariable, Collection<JExpression>> assignments = Maps.newIdentityHashMap(); /** * For each type tracks all classes the extend or implement it. */ private final Map<JReferenceType, Collection<JClassType>> implementors = Maps.newIdentityHashMap(); /** * For each parameter P (in method M) tracks the set of parameters that share its position in all * the methods that are overridden by M. */ private final Map<JParameter, Collection<JParameter>> paramUpRefs = Maps.newIdentityHashMap(); /** * For each method tracks the set of all expressions that are returned. */ private final Map<JMethod, Collection<JExpression>> returns = Maps.newIdentityHashMap(); /** * For each method call, record the method calls and field references in its arguments. * When the callee methods or the referenced fields in the arguments are modified, * it would be possible for the target method to be type tightened. */ private final Multimap<JMethod, JMethod> calledMethodsByMethodCallArg = HashMultimap.create(); private final Multimap<JField, JMethod> calledMethodsByFieldRefArg = HashMultimap.create(); private final JProgram program; private TypeTightener(JProgram program) { this.program = program; } private OptimizerStats execImpl(OptimizerContext optimizerCtx) { OptimizerStats stats = new OptimizerStats(NAME); RecordVisitor recorder = new RecordVisitor(); recorder.record(program); /* * We must iterate multiple times because each way point we tighten creates * more opportunities to do additional tightening for the things that depend * on it. * * TODO(zundel): See if we can remove this loop, or otherwise run to less * than completion if we compile with an option for less than 100% optimized * output. */ int lastStep = optimizerCtx.getLastStepFor(NAME); /* * Set the last step to the step at which TypeTightener does the first iteration. Since the * RecordVisitor is run only once, the information in {@code assignments} etc. is not updated. * So it is still possible for the type tightened methods/fields to be type tightened for the * next time. */ optimizerCtx.setLastStepFor(NAME, optimizerCtx.getOptimizationStep()); while (true) { TightenTypesVisitor tightener = new TightenTypesVisitor(optimizerCtx); Set<JMethod> affectedMethods = computeAffectedMethods(optimizerCtx, lastStep); Set<JField> affectedFields = computeAffectedFields(optimizerCtx, lastStep); optimizerCtx.traverse(tightener, affectedFields); optimizerCtx.traverse(tightener, affectedMethods); stats.recordModified(tightener.getNumMods()); lastStep = optimizerCtx.getOptimizationStep(); optimizerCtx.incOptimizationStep(); if (!tightener.didChange()) { break; } } if (stats.didChange()) { FixDanglingRefsVisitor fixer = new FixDanglingRefsVisitor(optimizerCtx); fixer.accept(program); optimizerCtx.incOptimizationStep(); JavaAstVerifier.assertProgramIsConsistent(program); } return stats; } private Set<JMethod> computeAffectedMethods(OptimizerContext optimizerCtx, int lastStep) { Set<JMethod> modifiedMethods = optimizerCtx.getModifiedMethodsSince(lastStep); Set<JField> modifiedFields = optimizerCtx.getModifiedFieldsSince(lastStep); Set<JMethod> affectedMethods = Sets.newLinkedHashSet(); // If the return type or parameters' types of a method are changed, its caller methods should be // reanalyzed. affectedMethods.addAll(optimizerCtx.getCallers(modifiedMethods)); // If a method is modified, its callee should be reanalyzed. affectedMethods.addAll(optimizerCtx.getCallees(modifiedMethods)); // The removed callee methods (one or more method calls to it are removed) should be reanalyzed. affectedMethods.addAll(optimizerCtx.getRemovedCalleeMethodsSince(lastStep)); // If a method's return type is changed, the called method whose argument calls the method // should be reanalyzed. for (JMethod method : modifiedMethods) { affectedMethods.addAll(calledMethodsByMethodCallArg.get(method)); } // If a method's return type or parameters' types are changed, its overriders and overridden // methods should be reanalyzed. The overridden methods and overriders from typeOracle may have // been pruned, so we have to check if they are in the AST. for (JMethod method : modifiedMethods) { affectedMethods.addAll(method.getOverriddenMethods()); affectedMethods.addAll(method.getOverridingMethods()); } // If a field is changed, the methods that reference to it should be reanalyzed. affectedMethods.addAll(optimizerCtx.getMethodsByReferencedFields(modifiedFields)); // If a field is changed, the caller methods which call it through argument should be // reanalyzed. for (JField field : modifiedFields) { affectedMethods.addAll(calledMethodsByFieldRefArg.get(field)); } // All the methods that are modified by other optimizer should be reanalyzed. affectedMethods.addAll(modifiedMethods); return affectedMethods; } private Set<JField> computeAffectedFields(OptimizerContext optimizerCtx, int lastStep) { Set<JMethod> modifiedMethods = optimizerCtx.getModifiedMethodsSince(lastStep); Set<JField> modifiedFields = optimizerCtx.getModifiedFieldsSince(lastStep); Set<JField> affectedFields = Sets.newLinkedHashSet(); affectedFields.addAll(modifiedFields); affectedFields.addAll(optimizerCtx.getReferencedFieldsByMethods(modifiedMethods)); return affectedFields; } private boolean isNullReference(CanBeStatic member, JExpression instance) { return !member.isStatic() && instance.getType().isNullType(); } /** * Computes type ^ (V assignedTypes). */ private JReferenceType strongerType(JReferenceType type, JReferenceType... assignedTypes) { return strongerType(type, Arrays.asList(assignedTypes)); } /** * Computes type ^ (V assignedTypes). */ private JReferenceType strongerType(JReferenceType type, Iterable<JReferenceType> assignedTypes) { return program.strengthenType(type, program.generalizeTypes(assignedTypes)); } }