| /* |
| * 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.Context; |
| import com.google.gwt.dev.jjs.ast.JArrayRef; |
| 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.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.JGwtCreate; |
| 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.JLocalRef; |
| import com.google.gwt.dev.jjs.ast.JMethod; |
| import com.google.gwt.dev.jjs.ast.JMethodCall; |
| import com.google.gwt.dev.jjs.ast.JModVisitor; |
| import com.google.gwt.dev.jjs.ast.JNewInstance; |
| import com.google.gwt.dev.jjs.ast.JNullLiteral; |
| import com.google.gwt.dev.jjs.ast.JNullType; |
| import com.google.gwt.dev.jjs.ast.JParameter; |
| import com.google.gwt.dev.jjs.ast.JParameterRef; |
| 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.JTryStatement; |
| import com.google.gwt.dev.jjs.ast.JType; |
| import com.google.gwt.dev.jjs.ast.JTypeOracle; |
| 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 java.util.ArrayList; |
| import java.util.HashSet; |
| import java.util.IdentityHashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| /** |
| * 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 JModVisitor { |
| |
| @Override |
| public void endVisit(JFieldRef x, Context ctx) { |
| JExpression instance = x.getInstance(); |
| boolean isStatic = x.getField().isStatic(); |
| if (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, x.getField(), x.getEnclosingType()); |
| ctx.replaceMe(fieldRef); |
| } |
| } else if (!isStatic && instance.getType() == typeNull |
| && x.getField() != 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 isStatic = method.isStatic(); |
| boolean isStaticImpl = program.isStaticImpl(method); |
| if (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 (!isStatic && instance.getType() == typeNull) { |
| 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() == typeNull) { |
| // 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. |
| */ |
| public class RecordVisitor extends JVisitor { |
| private JMethod currentMethod; |
| |
| @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.getRhs()); |
| } 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.getLiteralInitializer() != null) { |
| // TODO: do I still need this? |
| addAssignment(x, x.getLiteralInitializer()); |
| } |
| currentMethod = null; |
| } |
| |
| @Override |
| public void endVisit(JMethod x, Context ctx) { |
| if (program.typeOracle.isInstantiatedType(x.getEnclosingType())) { |
| for (JMethod method : program.typeOracle.getAllOverrides(x)) { |
| addOverrider(method, x); |
| } |
| } |
| 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 (int i = 0; i < params.size(); ++i) { |
| JParameter param = params.get(i); |
| JExpression arg = argIt.next(); |
| if (param.getType() instanceof JReferenceType) { |
| addAssignment(param, arg); |
| } |
| } |
| } |
| |
| @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, new JParameterRef(SourceOrigin.UNKNOWN, param)); |
| } |
| } |
| |
| @Override |
| public void endVisit(JTryStatement x, Context ctx) { |
| // Never tighten args to catch blocks |
| // Fake an assignment-to-self to prevent tightening |
| for (JLocalRef arg : x.getCatchArgs()) { |
| addAssignment(arg.getTarget(), arg); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| Set<JMethod> overrides = program.typeOracle.getAllOverrides(x); |
| if (overrides.isEmpty()) { |
| return true; |
| } |
| for (int j = 0, c = x.getParams().size(); j < c; ++j) { |
| JParameter param = x.getParams().get(j); |
| Set<JParameter> set = paramUpRefs.get(param); |
| if (set == null) { |
| set = new HashSet<JParameter>(); |
| paramUpRefs.put(param, set); |
| } |
| for (JMethod baseMethod : overrides) { |
| JParameter baseParam = baseMethod.getParams().get(j); |
| set.add(baseParam); |
| } |
| } |
| } |
| return true; |
| } |
| |
| 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 addOverrider(JMethod target, JMethod overrider) { |
| add(target, overrider, overriders); |
| } |
| |
| 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 JModVisitor { |
| |
| /** |
| * 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 CastNormalizer |
| */ |
| @Override |
| public void endVisit(JCastOperation x, Context ctx) { |
| JType argType = x.getExpr().getType(); |
| if (!(x.getCastType() instanceof JReferenceType) || !(argType instanceof JReferenceType)) { |
| return; |
| } |
| |
| JReferenceType toType = getSingleConcreteType(x.getCastType()); |
| if (toType == null) { |
| toType = (JReferenceType) x.getCastType(); |
| } |
| JReferenceType fromType = getSingleConcreteType(argType); |
| if (fromType == null) { |
| fromType = (JReferenceType) argType; |
| } |
| |
| boolean triviallyTrue = false; |
| boolean triviallyFalse = false; |
| |
| JTypeOracle typeOracle = program.typeOracle; |
| if (typeOracle.canTriviallyCast(fromType, toType)) { |
| triviallyTrue = true; |
| } else if (!typeOracle.isInstantiatedType(toType)) { |
| triviallyFalse = true; |
| } else if (!typeOracle.canTheoreticallyCast(fromType, toType)) { |
| triviallyFalse = true; |
| } |
| |
| if (triviallyTrue) { |
| // remove the cast operation |
| ctx.replaceMe(x.getExpr()); |
| } else if (triviallyFalse && toType != program.getTypeNull()) { |
| // replace with a placeholder cast to NULL, unless it's already a cast to NULL |
| JCastOperation newOp = |
| new JCastOperation(x.getSourceInfo(), program.getTypeNull(), x.getExpr()); |
| ctx.replaceMe(newOp); |
| } else { |
| // If possible, try to use a narrower cast |
| JReferenceType tighterType = getSingleConcreteType(toType); |
| |
| if (tighterType != null && tighterType != toType) { |
| JCastOperation newOp = new JCastOperation(x.getSourceInfo(), tighterType, x.getExpr()); |
| ctx.replaceMe(newOp); |
| } |
| } |
| } |
| |
| @Override |
| public void endVisit(JConditional x, Context ctx) { |
| if (x.getType() instanceof JReferenceType) { |
| JReferenceType newType = |
| program.generalizeTypes((JReferenceType) x.getThenExpr().getType(), (JReferenceType) x |
| .getElseExpr().getType()); |
| if (newType != x.getType()) { |
| x.setType(newType); |
| madeChanges(); |
| } |
| } |
| } |
| |
| @Override |
| public void endVisit(JField x, Context ctx) { |
| if (!x.isVolatile()) { |
| tighten(x); |
| } |
| } |
| |
| @Override |
| public void endVisit(JGwtCreate x, Context ctx) { |
| List<JReferenceType> typeList = new ArrayList<JReferenceType>(); |
| for (JExpression expr : x.getInstantiationExpressions()) { |
| JReferenceType type = (JReferenceType) expr.getType(); |
| typeList.add(type); |
| } |
| |
| JReferenceType resultType = program.generalizeTypes(typeList); |
| if (x.getType() != resultType) { |
| x.setType(resultType); |
| madeChanges(); |
| } |
| } |
| |
| @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 toType = getSingleConcreteType(x.getTestType()); |
| if (toType == null) { |
| toType = x.getTestType(); |
| } |
| JReferenceType fromType = getSingleConcreteType(argType); |
| if (fromType == null) { |
| fromType = (JReferenceType) argType; |
| } |
| |
| boolean triviallyTrue = false; |
| boolean triviallyFalse = false; |
| |
| JTypeOracle typeOracle = program.typeOracle; |
| if (fromType == program.getTypeNull()) { |
| // null is never instanceof anything |
| triviallyFalse = true; |
| } else if (typeOracle.canTriviallyCast(fromType, toType)) { |
| triviallyTrue = true; |
| } else if (!typeOracle.isInstantiatedType(toType)) { |
| triviallyFalse = true; |
| } else if (!typeOracle.canTheoreticallyCast(fromType, toType)) { |
| triviallyFalse = true; |
| } |
| |
| if (triviallyTrue) { |
| // replace with a simple null test |
| JNullLiteral nullLit = program.getLiteralNull(); |
| JBinaryOperation neq = |
| new JBinaryOperation(x.getSourceInfo(), program.getTypePrimitiveBoolean(), |
| JBinaryOperator.NEQ, x.getExpr(), nullLit); |
| ctx.replaceMe(neq); |
| } else if (triviallyFalse) { |
| // replace with a false literal |
| ctx.replaceMe(program.getLiteralBoolean(false)); |
| } else { |
| // If possible, try to use a narrower cast |
| JReferenceType concreteType = getSingleConcreteType(toType); |
| if (concreteType != null) { |
| JInstanceOf newOp = new JInstanceOf(x.getSourceInfo(), concreteType, x.getExpr()); |
| ctx.replaceMe(newOp); |
| } |
| } |
| } |
| |
| @Override |
| public void endVisit(JLocal x, Context ctx) { |
| tighten(x); |
| } |
| |
| /** |
| * Tighten based on return types and overrides. |
| */ |
| @Override |
| public void endVisit(JMethod x, Context ctx) { |
| if (!(x.getType() instanceof JReferenceType)) { |
| return; |
| } |
| JReferenceType refType = (JReferenceType) x.getType(); |
| |
| if (refType == typeNull) { |
| return; |
| } |
| |
| // tighten based on non-instantiability |
| if (!program.typeOracle.isInstantiatedType(refType)) { |
| x.setType(typeNull); |
| madeChanges(); |
| return; |
| } |
| |
| JReferenceType concreteType = getSingleConcreteType(x.getType()); |
| 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.isNative()) { |
| return; |
| } |
| |
| // tighten based on both returned types and possible overrides |
| List<JReferenceType> typeList = new ArrayList<JReferenceType>(); |
| |
| Set<JExpression> myReturns = returns.get(x); |
| if (myReturns != null) { |
| for (JExpression expr : myReturns) { |
| typeList.add((JReferenceType) expr.getType()); |
| } |
| } |
| Set<JMethod> myOverriders = overriders.get(x); |
| if (myOverriders != null) { |
| for (JMethod method : myOverriders) { |
| typeList.add((JReferenceType) method.getType()); |
| } |
| } |
| |
| JReferenceType resultType; |
| if (typeList.isEmpty()) { |
| // The method returns nothing |
| resultType = typeNull; |
| } else { |
| resultType = program.generalizeTypes(typeList); |
| } |
| resultType = program.strongerType(refType, resultType); |
| if (refType != resultType) { |
| x.setType(resultType); |
| madeChanges(); |
| } |
| } |
| |
| /** |
| * Tighten the target method from the abstract base method to the final |
| * implementation. |
| */ |
| @Override |
| public void endVisit(JMethodCall x, Context ctx) { |
| if (x.isVolatile()) { |
| return; |
| } |
| JMethod target = x.getTarget(); |
| JMethod concreteMethod = getSingleConcreteMethod(target); |
| if (concreteMethod != null) { |
| JMethodCall newCall = new JMethodCall(x.getSourceInfo(), x.getInstance(), concreteMethod); |
| newCall.addArgs(x.getArgs()); |
| ctx.replaceMe(newCall); |
| target = concreteMethod; |
| x = newCall; |
| } |
| |
| /* |
| * Mark a call as non-polymorphic if the targeted method is the only |
| * possible dispatch, given the qualifying instance type. |
| */ |
| if (x.canBePolymorphic() && !target.isAbstract()) { |
| JExpression instance = x.getInstance(); |
| assert (instance != null); |
| JReferenceType instanceType = (JReferenceType) instance.getType(); |
| Set<JMethod> myOverriders = overriders.get(target); |
| if (myOverriders != null) { |
| for (JMethod override : myOverriders) { |
| JReferenceType overrideType = override.getEnclosingType(); |
| if (program.typeOracle.canTheoreticallyCast(instanceType, overrideType)) { |
| // This call is truly polymorphic. |
| // TODO: composite types! :) |
| return; |
| } |
| } |
| // The instance type is incompatible with all overrides. |
| } |
| x.setCannotBePolymorphic(); |
| madeChanges(); |
| } |
| } |
| |
| @Override |
| public void endVisit(JParameter x, Context ctx) { |
| tighten(x); |
| } |
| |
| @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 visit(JMethod x, Context ctx) { |
| /* |
| * Explicitly NOT visiting native methods since we can't infer further |
| * type information. |
| */ |
| return !x.isNative(); |
| } |
| |
| /** |
| * 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 getSingleConcreteMethod(JMethod method) { |
| if (!method.canBePolymorphic()) { |
| return null; |
| } |
| if (getSingleConcreteType(method.getEnclosingType()) != null) { |
| return getSingleConcrete(method, overriders); |
| } else { |
| return null; |
| } |
| } |
| |
| /** |
| * Given an abstract type, return the single concrete implementation of that |
| * type. |
| */ |
| private JReferenceType getSingleConcreteType(JType type) { |
| if (type instanceof JReferenceType) { |
| JReferenceType refType = (JReferenceType) type; |
| if (refType.isAbstract()) { |
| JClassType singleConcrete = getSingleConcrete(refType.getUnderlyingType(), implementors); |
| assert (singleConcrete == null || program.typeOracle.isInstantiatedType(singleConcrete)); |
| if (singleConcrete == null) { |
| return null; |
| } |
| return refType.canBeNull() ? singleConcrete : singleConcrete.getNonNull(); |
| } |
| } |
| return null; |
| } |
| |
| private JArrayType nullifyArrayType(JArrayType arrayType) { |
| JType elementType = arrayType.getElementType(); |
| if (elementType instanceof JReferenceType) { |
| JReferenceType refType = (JReferenceType) elementType; |
| if (!program.typeOracle.isInstantiatedType(refType)) { |
| return program.getTypeArray(JNullType.INSTANCE); |
| } else if (elementType instanceof JArrayType) { |
| JArrayType newElementType = nullifyArrayType((JArrayType) elementType); |
| return program.getTypeArray(newElementType.getLeafType(), newElementType.getDims() + 1); |
| } |
| } |
| return arrayType; |
| } |
| |
| /** |
| * Tighten based on assignment, and for parameters, callArgs as well. |
| */ |
| private void tighten(JVariable x) { |
| if (!(x.getType() instanceof JReferenceType)) { |
| return; |
| } |
| JReferenceType refType = (JReferenceType) x.getType(); |
| |
| if (refType == typeNull) { |
| return; |
| } |
| |
| // tighten based on non-instantiability |
| if (!program.typeOracle.isInstantiatedType(refType)) { |
| x.setType(typeNull); |
| madeChanges(); |
| return; |
| } |
| |
| if (refType instanceof JArrayType) { |
| JArrayType arrayType = (JArrayType) refType; |
| refType = nullifyArrayType(arrayType); |
| } |
| |
| // tighten based on leaf types |
| JReferenceType leafType = getSingleConcreteType(refType); |
| if (leafType != null) { |
| x.setType(leafType); |
| madeChanges(); |
| return; |
| } |
| |
| // tighten based on assignment |
| List<JReferenceType> typeList = new ArrayList<JReferenceType>(); |
| |
| /* |
| * For fields without an initializer, add a null assignment, because the |
| * field might be accessed before initialized. Technically even a field |
| * with an initializer might be accessed before initialization, but |
| * presumably that is not the programmer's intent, so the compiler cheats |
| * and assumes the initial null will not be seen. |
| */ |
| if ((x instanceof JField) && !x.hasInitializer()) { |
| typeList.add(typeNull); |
| } |
| |
| Set<JExpression> myAssignments = assignments.get(x); |
| if (myAssignments != null) { |
| for (JExpression expr : myAssignments) { |
| JType type = expr.getType(); |
| if (!(type instanceof JReferenceType)) { |
| return; // something fishy is going on, just abort |
| } |
| typeList.add((JReferenceType) type); |
| } |
| } |
| |
| if (x instanceof JParameter) { |
| Set<JParameter> myParams = paramUpRefs.get(x); |
| if (myParams != null) { |
| for (JParameter param : myParams) { |
| typeList.add((JReferenceType) param.getType()); |
| } |
| } |
| } |
| |
| JReferenceType resultType; |
| if (!typeList.isEmpty()) { |
| resultType = program.generalizeTypes(typeList); |
| resultType = program.strongerType(refType, resultType); |
| } else { |
| if (x instanceof JParameter) { |
| /* |
| * There is no need to tighten unused parameters, because they will be |
| * pruned. |
| */ |
| resultType = refType; |
| } else { |
| resultType = typeNull; |
| } |
| } |
| |
| if (x.getType() != resultType) { |
| x.setType(resultType); |
| madeChanges(); |
| } |
| } |
| } |
| |
| private static final String NAME = TypeTightener.class.getSimpleName(); |
| |
| public static OptimizerStats exec(JProgram program) { |
| Event optimizeEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "optimizer", NAME); |
| OptimizerStats stats = new TypeTightener(program).execImpl(); |
| optimizeEvent.end("didChange", "" + stats.didChange()); |
| return stats; |
| } |
| |
| private static <T, V> void add(T target, V value, Map<T, Set<V>> map) { |
| Set<V> set = map.get(target); |
| if (set == null) { |
| set = new HashSet<V>(); |
| map.put(target, set); |
| } |
| set.add(value); |
| } |
| |
| /** |
| * Find exactly one concrete element for a key in a Map of Sets. If there are |
| * none or more than one concrete element, return <code>null</code>. |
| */ |
| private static <B, T extends CanBeAbstract> T getSingleConcrete(B x, Map<? super B, Set<T>> map) { |
| |
| Set<T> set = map.get(x); |
| // No set, then no concrete version |
| if (set == null) { |
| return null; |
| } |
| |
| T toReturn = null; |
| for (T elt : set) { |
| if (elt.isAbstract()) { |
| continue; |
| } |
| |
| // If we already have previously seen a concrete element, fail |
| if (toReturn != null) { |
| return null; |
| } else { |
| toReturn = elt; |
| } |
| } |
| |
| return toReturn; |
| } |
| |
| private final Map<JVariable, Set<JExpression>> assignments = |
| new IdentityHashMap<JVariable, Set<JExpression>>(); |
| private final Map<JReferenceType, Set<JClassType>> implementors = |
| new IdentityHashMap<JReferenceType, Set<JClassType>>(); |
| private final Map<JMethod, Set<JMethod>> overriders = |
| new IdentityHashMap<JMethod, Set<JMethod>>(); |
| private final Map<JParameter, Set<JParameter>> paramUpRefs = |
| new IdentityHashMap<JParameter, Set<JParameter>>(); |
| private final JProgram program; |
| private final Map<JMethod, Set<JExpression>> returns = |
| new IdentityHashMap<JMethod, Set<JExpression>>(); |
| private final JNullType typeNull; |
| |
| private TypeTightener(JProgram program) { |
| this.program = program; |
| typeNull = program.getTypeNull(); |
| } |
| |
| private OptimizerStats execImpl() { |
| OptimizerStats stats = new OptimizerStats(NAME); |
| RecordVisitor recorder = new RecordVisitor(); |
| recorder.accept(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. |
| */ |
| while (true) { |
| TightenTypesVisitor tightener = new TightenTypesVisitor(); |
| tightener.accept(program); |
| stats.recordModified(tightener.getNumMods()); |
| if (!tightener.didChange()) { |
| break; |
| } |
| |
| FixDanglingRefsVisitor fixer = new FixDanglingRefsVisitor(); |
| fixer.accept(program); |
| } |
| return stats; |
| } |
| } |