blob: 2811ac4bdd67fa666bd8b995da282eb1204d6aaa [file] [log] [blame]
/*
* 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.ast;
import com.google.gwt.dev.MinimalRebuildCache;
import com.google.gwt.dev.common.InliningMode;
import com.google.gwt.dev.jjs.Correlation.Literal;
import com.google.gwt.dev.jjs.InternalCompilerException;
import com.google.gwt.dev.jjs.SourceInfo;
import com.google.gwt.dev.jjs.SourceOrigin;
import com.google.gwt.dev.jjs.impl.GwtAstBuilder;
import com.google.gwt.dev.jjs.impl.JjsUtils;
import com.google.gwt.dev.jjs.impl.TypeCategory;
import com.google.gwt.dev.jjs.impl.codesplitter.FragmentPartitioningResult;
import com.google.gwt.dev.js.CoverageInstrumentor;
import com.google.gwt.dev.util.StringInterner;
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.CaseFormat;
import com.google.gwt.thirdparty.guava.common.base.Function;
import com.google.gwt.thirdparty.guava.common.base.Predicate;
import com.google.gwt.thirdparty.guava.common.collect.BiMap;
import com.google.gwt.thirdparty.guava.common.collect.Collections2;
import com.google.gwt.thirdparty.guava.common.collect.HashBiMap;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableList;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableMap;
import com.google.gwt.thirdparty.guava.common.collect.ImmutableSet;
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.Sets;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Root for the AST representing an entire Java program.
*/
public class JProgram extends JNode implements ArrayTypeCreator {
/**
* Encapsulates all information necessary to deal with native represented types in an
* generic fashion used throughout GWT. This can be extended later to deal with say, unboxed
* Integer if desired.
*/
public enum DispatchType {
// These this list can be extended by creating the appropriate fields/methods on Cast,
// as well as extending the TypeCategory enum and updating EqualityNormalizer.
// The order in which these native types appear is the inverse as the way they are
// checked by devirtualized method.
BOOLEAN(true),
DOUBLE(true),
STRING(true),
// non-native represented type values.
HAS_JAVA_VIRTUAL_DISPATCH(false), JAVA_ARRAY(false), JSO(false);
private final String castMapField;
private final TypeCategory typeCategory;
private final String className;
DispatchType(boolean nativeType) {
if (nativeType) {
// These field are initialized to methods that are by-convention
// The conventions are:
// Cast.[boxedTypeName]CastMap for cast map fields
// TypedCategory.TYPE_JAVA_LANG_[BoxedTypeName]
String methodName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name());
this.castMapField = "Cast." + methodName + "CastMap";
this.typeCategory = TypeCategory.valueOf("TYPE_JAVA_LANG_" + name());
String simpleClassName = CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name());
this.className = "java.lang." + simpleClassName;
} else {
this.castMapField = null;
this.typeCategory = null;
this.className = null;
}
}
public String getCastMapField() {
return castMapField;
}
public TypeCategory getTypeCategory() {
return typeCategory;
}
public String getClassName() {
return className;
}
}
private static final class TreeStatistics extends JVisitor {
private int nodeCount = 0;
public int getNodeCount() {
return nodeCount;
}
@Override
public boolean visit(JNode x, Context ctx) {
nodeCount++;
return true;
}
}
public static final Set<String> CODEGEN_TYPES_SET = Sets.newLinkedHashSet(Arrays.asList(
"com.google.gwt.lang.Array", "com.google.gwt.lang.Cast", "com.google.gwt.lang.Exceptions",
"com.google.gwt.lang.LongLib", "com.google.gwt.lang.Stats", "com.google.gwt.lang.Util",
"java.lang.Object"));
/*
* Types which are not referenced by any Java code, but are required to exist
* after Java optimizations have run in order to be used by backend
* code-generation. These classes and their members, are considered live
* by ControlFlowAnalysis, at all times. Immortal types always live in the
* initial fragment and their definitions are hoisted to appear before all
* other types. Only static methods and fields are allowed, and no clinits
* are run. Field initializers must be primitives, literals, or one of
* JSO.createObject() or JSO.createArray().
*
* Classes are inserted into the JsAST in the order they appear in the Set.
*/
public static final Set<String> IMMORTAL_CODEGEN_TYPES_SET = Sets.newLinkedHashSet(Arrays.asList(
"com.google.gwt.lang.CollapsedPropertyHolder",
"com.google.gwt.lang.Runtime",
"com.google.gwt.lang.ModuleUtils"));
public static final String JAVASCRIPTOBJECT = "com.google.gwt.core.client.JavaScriptObject";
public static final String CLASS_LITERAL_HOLDER = "com.google.gwt.lang.ClassLiteralHolder";
/**
* Types whose entire implementation is synthesized at compile time.
*/
public static final Set<String> SYNTHETIC_TYPE_NAMES = Sets.newHashSet(CLASS_LITERAL_HOLDER);
private static final Comparator<JArrayType> ARRAYTYPE_COMPARATOR =
new Comparator<JArrayType>() {
@Override
public int compare(JArrayType o1, JArrayType o2) {
int comp = o1.getDims() - o2.getDims();
if (comp != 0) {
return comp;
}
return o1.getName().compareTo(o2.getName());
}
};
private static final Map<String, JPrimitiveType> primitiveTypes = Maps.newHashMap();
@Deprecated
private static final Map<String, JPrimitiveType> primitiveTypesDeprecated = Maps.newHashMap();
static {
if (CoverageInstrumentor.isCoverageEnabled()) {
IMMORTAL_CODEGEN_TYPES_SET.add("com.google.gwt.lang.CoverageUtil");
}
CODEGEN_TYPES_SET.addAll(IMMORTAL_CODEGEN_TYPES_SET);
primitiveTypes.put(JPrimitiveType.BOOLEAN.getName(), JPrimitiveType.BOOLEAN);
primitiveTypes.put(JPrimitiveType.BYTE.getName(), JPrimitiveType.BYTE);
primitiveTypes.put(JPrimitiveType.CHAR.getName(), JPrimitiveType.CHAR);
primitiveTypes.put(JPrimitiveType.DOUBLE.getName(), JPrimitiveType.DOUBLE);
primitiveTypes.put(JPrimitiveType.FLOAT.getName(), JPrimitiveType.FLOAT);
primitiveTypes.put(JPrimitiveType.INT.getName(), JPrimitiveType.INT);
primitiveTypes.put(JPrimitiveType.LONG.getName(), JPrimitiveType.LONG);
primitiveTypes.put(JPrimitiveType.SHORT.getName(), JPrimitiveType.SHORT);
primitiveTypes.put(JPrimitiveType.VOID.getName(), JPrimitiveType.VOID);
primitiveTypesDeprecated.put(JPrimitiveType.BOOLEAN.getJsniSignatureName(),
JPrimitiveType.BOOLEAN);
primitiveTypesDeprecated.put(JPrimitiveType.BYTE.getJsniSignatureName(), JPrimitiveType.BYTE);
primitiveTypesDeprecated.put(JPrimitiveType.CHAR.getJsniSignatureName(), JPrimitiveType.CHAR);
primitiveTypesDeprecated.put(JPrimitiveType.DOUBLE.getJsniSignatureName(),
JPrimitiveType.DOUBLE);
primitiveTypesDeprecated.put(JPrimitiveType.FLOAT.getJsniSignatureName(), JPrimitiveType.FLOAT);
primitiveTypesDeprecated.put(JPrimitiveType.INT.getJsniSignatureName(), JPrimitiveType.INT);
primitiveTypesDeprecated.put(JPrimitiveType.LONG.getJsniSignatureName(), JPrimitiveType.LONG);
primitiveTypesDeprecated.put(JPrimitiveType.SHORT.getJsniSignatureName(), JPrimitiveType.SHORT);
primitiveTypesDeprecated.put(JPrimitiveType.VOID.getJsniSignatureName(), JPrimitiveType.VOID);
}
/**
* Helper to create an assignment, used to initialize fields, etc.
*/
public static JExpressionStatement createAssignmentStmt(SourceInfo info, JExpression lhs,
JExpression rhs) {
return createAssignment(info, lhs, rhs).makeStatement();
}
public static JBinaryOperation createAssignment(SourceInfo info, JExpression lhs,
JExpression rhs) {
return new JBinaryOperation(info, lhs.getType(), JBinaryOperator.ASG, lhs, rhs);
}
public static JLocal createLocal(SourceInfo info, String name, JType type, boolean isFinal,
JMethodBody enclosingMethodBody) {
assert (name != null);
assert (type != null);
assert (enclosingMethodBody != null);
JLocal x = new JLocal(info, name, type, isFinal);
enclosingMethodBody.addLocal(x);
return x;
}
public static List<JDeclaredType> deserializeTypes(ObjectInputStream stream) throws IOException,
ClassNotFoundException {
@SuppressWarnings("unchecked")
List<JDeclaredType> types = (List<JDeclaredType>) stream.readObject();
for (JDeclaredType type : types) {
type.readMembers(stream);
}
for (JDeclaredType type : types) {
type.readMethodBodies(stream);
}
return types;
}
public static String getFullName(JMethod method) {
return method.getEnclosingType().getName() + "." + method.getJsniSignature(false, true);
}
public static boolean isClinit(JMethod method) {
JDeclaredType enclosingType = method.getEnclosingType();
boolean isClinit = enclosingType != JClassType.NULL_CLASS &&
method == enclosingType.getClinitMethod();
assert !isClinit || method.getName().equals(GwtAstBuilder.CLINIT_METHOD_NAME);
return isClinit;
}
public static boolean isInit(JMethod method) {
JDeclaredType enclosingType = method.getEnclosingType();
if (method.isStatic()) {
// Hack, check the name.
return method.getName().equals(GwtAstBuilder.STATIC_INIT_METHOD_NAME);
}
boolean isInit = enclosingType != null && method == enclosingType.getInitMethod();
assert !isInit || method.getName().equals(GwtAstBuilder.INIT_NAME_METHOD_NAME);
return isInit;
}
public static void serializeTypes(List<JDeclaredType> types, ObjectOutputStream stream)
throws IOException {
stream.writeObject(types);
for (JDeclaredType type : types) {
type.writeMembers(stream);
}
for (JDeclaredType type : types) {
type.writeMethodBodies(stream);
}
}
public final List<JClassType> codeGenTypes = Lists.newArrayList();
public final List<JClassType> immortalCodeGenTypes = Lists.newArrayList();
public final JTypeOracle typeOracle;
/**
* Special serialization treatment.
*/
// TODO(stalcup): make this a set, or take special care to make updates unique when lazily loading
// in types. At the moment duplicates are accumulating.
private transient List<JDeclaredType> allTypes = Lists.newArrayList();
private final Map<JType, JArrayType> arrayTypes = Maps.newHashMap();
private Map<JReferenceType, JCastMap> castMaps;
private BiMap<JType, JField> classLiteralFieldsByType;
private final List<JMethod> entryMethods = Lists.newArrayList();
private final Map<String, JField> indexedFields = Maps.newHashMap();
private final Map<String, JMethod> indexedMethods = Maps.newHashMap();
/**
* An index of types, from type name to type instance.
*/
private final Map<String, JDeclaredType> indexedTypes = Maps.newHashMap();
/**
* The set of names of types (beyond the basic INDEX_TYPES_SET) whose instance should be indexed
* when seen.
*/
private final Set<String> typeNamesToIndex = buildInitialTypeNamesToIndex();
private final Map<JMethod, JMethod> instanceToStaticMap = Maps.newIdentityHashMap();
// wrap up .add here, and filter out forced source
private Set<String> referenceOnlyTypeNames = Sets.newHashSet();
/**
* Filled in by ReplaceRunAsync, once the numbers are known.
*/
private List<JRunAsync> runAsyncs = Lists.newArrayList();
private LinkedHashSet<JRunAsync> initialAsyncSequence = Sets.newLinkedHashSet();
private List<Integer> initialFragmentIdSequence = Lists.newArrayList();
private final Map<JMethod, JMethod> staticToInstanceMap = Maps.newIdentityHashMap();
private final Map<String, JDeclaredType> typeNameMap = Maps.newHashMap();
private Map<JField, JType> typesByClassLiteralField;
private JClassType typeClass;
private JClassType typeJavaLangObject;
private JArrayType typeJavaLangObjectArray;
private JClassType typeSpecialClassLiteralHolder;
private JClassType typeSpecialJavaScriptObject;
private JClassType typeString;
private FragmentPartitioningResult fragmentPartitioningResult;
private Map<JClassType, DispatchType> dispatchTypeByNativeType;
/**
* Add a pinned method.
*/
public void addPinnedMethod(JMethod method) {
method.setInliningMode(InliningMode.DO_NOT_INLINE);
method.disallowDevirtualization();
}
public JProgram(MinimalRebuildCache minimalRebuildCache) {
super(SourceOrigin.UNKNOWN);
typeOracle = new JTypeOracle(this, minimalRebuildCache);
}
public void addEntryMethod(JMethod entryPoint) {
assert !entryMethods.contains(entryPoint);
entryMethods.add(entryPoint);
}
/**
* Adds the given type name to the set of type names (beyond the basic INDEX_TYPES_SET) whose
* instance should be indexed when seen.
*/
public void addIndexedTypeName(String typeName) {
typeNamesToIndex.add(typeName);
}
public void addReferenceOnlyType(JDeclaredType type) {
referenceOnlyTypeNames.add(type.getName());
}
public void addType(JDeclaredType type) {
allTypes.add(type);
String name = type.getName();
putIntoTypeMap(name, type);
if (CODEGEN_TYPES_SET.contains(name)) {
codeGenTypes.add((JClassType) type);
}
if (IMMORTAL_CODEGEN_TYPES_SET.contains(name)) {
// Immortal types by definition won't run clinits.
type.setClinitTarget(null);
immortalCodeGenTypes.add((JClassType) type);
}
if (!typeNamesToIndex.contains(name)) {
return;
}
indexedTypes.put(type.getShortName(), type);
for (JMethod method : type.getMethods()) {
if (!method.isPrivate()) {
indexedMethods.put(JjsUtils.getIndexedName(method), method);
}
}
for (JField field : type.getFields()) {
indexedFields.put(JjsUtils.getIndexedName(field), field);
}
switch (name) {
case "java.lang.Object":
typeJavaLangObject = (JClassType) type;
typeJavaLangObjectArray = getOrCreateArrayType(type, 1);
break;
case "java.lang.String":
typeString = (JClassType) type;
break;
case "java.lang.Class":
typeClass = (JClassType) type;
break;
case JAVASCRIPTOBJECT:
typeSpecialJavaScriptObject = (JClassType) type;
break;
case CLASS_LITERAL_HOLDER:
typeSpecialClassLiteralHolder = (JClassType) type;
break;
}
}
public static boolean isRepresentedAsNative(final String className) {
return Iterables.any(Arrays.asList(DispatchType.values()), new Predicate<DispatchType>() {
@Override
public boolean apply(DispatchType dispatchType) {
return className.equals(dispatchType.getClassName());
}
});
}
public boolean isRepresentedAsNativeJsPrimitive(JType type) {
return getRepresentedAsNativeTypes().contains(type);
}
public Set<JClassType> getRepresentedAsNativeTypes() {
return getRepresentedAsNativeTypesDispatchMap().keySet();
}
public Map<JClassType, DispatchType> getRepresentedAsNativeTypesDispatchMap() {
if (dispatchTypeByNativeType == null) {
ImmutableMap.Builder<JClassType, DispatchType> builder =
new ImmutableMap.Builder<JClassType, DispatchType>();
for (DispatchType dispatchType : DispatchType.values()) {
if (dispatchType.getClassName() == null) {
continue;
}
JClassType classType = (JClassType) getFromTypeMap(dispatchType.getClassName());
assert classType != null : "Class " + dispatchType.getClassName() + " has not been loaded";
builder.put(classType, dispatchType);
}
dispatchTypeByNativeType = builder.build();
}
return dispatchTypeByNativeType;
}
public EnumSet<DispatchType> getDispatchType(JReferenceType type) {
if (!typeOracle.isInstantiatedType(type)) {
return EnumSet.noneOf(DispatchType.class);
}
// Object methods can be dispatched to all four possible classes.
if (type == getTypeJavaLangObject()) {
return EnumSet.allOf(DispatchType.class);
}
if (type.isArrayType()) {
// A variable of type Object[] could contain an instance of native JsType[], the latter
// is treated as a JSO for devirtualization purposes.
return EnumSet.of(DispatchType.JSO, DispatchType.JAVA_ARRAY);
}
EnumSet<DispatchType> dispatchSet = EnumSet.noneOf(DispatchType.class);
DispatchType dispatchType = getRepresentedAsNativeTypesDispatchMap().get(type);
if (dispatchType != null) {
dispatchSet = EnumSet.of(dispatchType);
} else if (typeOracle.isDualJsoInterface(type) || type.isJsNative()) {
// If it is an interface implemented both by JSOs and regular Java Objects; native JsTypes
// are considered JSOs for object method devirtualization.
dispatchSet = EnumSet.of(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH, DispatchType.JSO);
} else if (typeOracle.isSingleJsoImpl(type) || type.isJsoType()) {
// If it is either an interface implemented by JSOs or JavaScriptObject or one of its
// subclasses.
dispatchSet = EnumSet.of(DispatchType.JSO);
}
for (JDeclaredType potentialNativeDispatchType : getRepresentedAsNativeTypes()) {
if (potentialNativeDispatchType == type) {
continue;
}
if (typeOracle.isInstantiatedType(potentialNativeDispatchType)
&& typeOracle.isSuperClassOrInterface(potentialNativeDispatchType, type)) {
dispatchSet.add(getRepresentedAsNativeTypesDispatchMap().get(potentialNativeDispatchType));
dispatchSet.add(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH);
}
}
return dispatchSet;
}
/**
* Return the greatest lower bound of two types. That is, return the largest
* type that is a subtype of both inputs. If none exists return {@code thisType}.
*/
public JReferenceType strengthenType(JReferenceType thisType, JReferenceType thatType) {
if (thisType == thatType) {
return thisType;
}
if (thisType.isNullType() || thatType.isNullType()) {
return JReferenceType.NULL_TYPE;
}
if (!thisType.canBeNull() || !thatType.canBeNull()) {
JReferenceType thisTypeNonNull = thisType.strengthenToNonNull();
JReferenceType thatTypeNonNull = thatType.strengthenToNonNull();
// .strengthenToNonNull does not guarantee that the resulting type is non null (e.g. JSOs).
// If either is non-nullable, the result should be non-nullable, unless it is a type that
// can not be made non-nullable, like a JSO.
if (thisType != thisTypeNonNull || thatType != thatTypeNonNull) {
return strengthenType(thisTypeNonNull, thatTypeNonNull);
}
}
if (typeOracle.castSucceedsTrivially(thisType, thatType)) {
return thisType;
}
if (typeOracle.castSucceedsTrivially(thatType, thisType)) {
return thatType;
}
// This types are incompatible; ideally this code should not be reached, but there are two
// situations where this happens:
// 1 - unrelated interfaces;
// 2 - unsafe code.
// The original type is preserved in this case.
return thisType;
}
/**
* Return a minimal upper bound of a set of types. That is, a type
* that is a supertype of all the input types and is as close as possible to the
* input types.
* <p>
* NOTE: Ideally we would like to return the least upper bound but it does not exit as
* the Java type hierarchy is not really a lattice.
* <p>
* Hence, this function depends on the collection order. E.g.
* <p>
* {@code
* I O
* |\ / \
* | A B
* \ /
* \ /
* C
* }
* <p>
* where I is an interface an {O,A,B,C} are classes.
* <p>
* generalizeTypes({A,C}) could either be I or O.
* <p>
* In particular generalizeTypes({I,A,C}) = I and generalizeTypes({A,C,I}) = O.
*
*/
public JReferenceType generalizeTypes(Iterable<JReferenceType> types) {
Iterator<JReferenceType> it = types.iterator();
if (!it.hasNext()) {
return JReferenceType.NULL_TYPE;
}
JReferenceType curType = it.next();
while (it.hasNext()) {
curType = generalizeTypes(curType, it.next());
if (curType == typeJavaLangObject) {
break;
}
}
return curType;
}
/**
* Return the least upper bound of two types. That is, the "smallest" type that
* is a supertype of both types. In this lattice there the smallest element might no exist, there
* might be multiple minimal elements neither of which is smaller than the others. E.g.
* <p>
* {@code
* I J
* | \ /|
* | \ / |
* | x |
* | / \ |
* | / \ |
* A B
* }
* <p>
* where I and J are interfaces, A and B are classes and both A and B implement I and J. In this
* case both I and J are generalizing the types A and B.
*/
private JReferenceType generalizeTypes(JReferenceType thisType, JReferenceType thatType) {
if (!thisType.canBeNull() && !thatType.canBeNull()) {
// Nullability is an orthogonal property, so remove non_nullability and perform the
// generalization on the nullable types, and if both were NOT nullable then strengthen the
// result to NOT nullable.
//
// not_nullable(A) v not_nullable(B) = not_nullable(A v B)
JReferenceType nulllableGeneralizer =
generalizeTypes(thisType.weakenToNullable(), thatType.weakenToNullable());
return nulllableGeneralizer.strengthenToNonNull();
}
thisType = thisType.weakenToNullable();
thatType = thatType.weakenToNullable();
// From here on nullability does not need to be considered.
// Generalization for exact types is as follows.
// exact(A) v null = exact(A)
// A v null = A
if (thatType.isNullType()) {
return thisType;
}
// null v exact(A) = exact(A)
// null v A = A
if (thisType.isNullType()) {
return thatType;
}
// exact(A) v exact(A) = exact(A)
// A v A = A
if (thisType == thatType) {
return thisType;
}
// exact(A) v exact(B) = A v B
// A v exact(B) = A v B
// exact(A) v B = A v B
// A v B = A v B
return generalizeUnderlyingTypes(thisType.getUnderlyingType(), thatType.getUnderlyingType());
}
private JReferenceType generalizeUnderlyingTypes(
JReferenceType thisType, JReferenceType thatType) {
// We should not have any analysis properties from this point forward.
assert thisType == thisType.getUnderlyingType() && thatType == thatType.getUnderlyingType();
if (thisType == thatType) {
return thisType;
}
if (thisType instanceof JInterfaceType && thatType instanceof JInterfaceType) {
return generalizeInterfaces((JInterfaceType) thisType, (JInterfaceType) thatType);
}
if (thisType instanceof JArrayType && thatType instanceof JArrayType) {
return generalizeArrayTypes((JArrayType) thisType, (JArrayType) thatType);
}
if (thisType instanceof JClassType && thatType instanceof JClassType) {
return generalizeClasses((JClassType) thisType, (JClassType) thatType);
}
JInterfaceType interfaceType = thisType instanceof JInterfaceType ? (JInterfaceType) thisType :
(thatType instanceof JInterfaceType ? (JInterfaceType) thatType : null);
JReferenceType nonInterfaceType = interfaceType == thisType ? thatType : thisType;
// See if the class or the array is castable to the interface type.
if (interfaceType != null &&
typeOracle.castSucceedsTrivially(nonInterfaceType, interfaceType)) {
return interfaceType;
}
// unrelated: the best commonality is Object
return typeJavaLangObject;
}
private JReferenceType generalizeArrayTypes(JArrayType thisArrayType, JArrayType thatArrayType) {
assert thisArrayType != thatArrayType;
int thisDims = thisArrayType.getDims();
int thatDims = thatArrayType.getDims();
int minDims = Math.min(thisDims, thatDims);
/*
* At a bare minimum, any two arrays generalize to an Object array with
* one less dim than the lesser of the two; that is, int[][][][] and
* String[][][] generalize to Object[][]. If minDims is 1, then they
* just generalize to Object.
*/
JReferenceType minimalGeneralType = (minDims == 1) ? typeJavaLangObject :
getOrCreateArrayType(typeJavaLangObject, minDims - 1);
if (thisDims == thatDims) {
// Try to generalize by leaf types
JType thisLeafType = thisArrayType.getLeafType();
JType thatLeafType = thatArrayType.getLeafType();
if (!(thisLeafType instanceof JReferenceType) || !(thatLeafType instanceof JReferenceType)) {
return minimalGeneralType;
}
/*
* Both are reference types; the result is the generalization of the leaf types combined with
* the number of dims; that is, Foo[] and Bar[] generalize to X[] where X is the
* generalization of Foo and Bar.
*
* Never generalize arrays to arrays of {@link JAnalysisDecoratedType}. One of the reasons is
* that array initialization is not accounted for in {@link TypeTightener}.
*/
JReferenceType leafGeneralization = generalizeTypes(
(JReferenceType) thisLeafType, (JReferenceType) thatLeafType).getUnderlyingType();
return getOrCreateArrayType(leafGeneralization, thisDims);
}
// Different number of dims
if (typeOracle.castSucceedsTrivially(thatArrayType, thisArrayType)) {
return thisArrayType;
}
if (typeOracle.castSucceedsTrivially(thisArrayType, thatArrayType)) {
return thatArrayType;
}
// Totally unrelated
return minimalGeneralType;
}
private JReferenceType generalizeInterfaces(JInterfaceType thisInterface,
JInterfaceType thatInterface) {
if (typeOracle.castSucceedsTrivially(thisInterface, thatInterface)) {
return thatInterface;
}
if (typeOracle.castSucceedsTrivially(thatInterface, thisInterface)) {
return thisInterface;
}
// unrelated
return typeJavaLangObject;
}
private JReferenceType generalizeClasses(JClassType thisClass, JClassType thatClass) {
/*
* see how far each type is from object; walk the one who's farther up
* until they're even; then walk them up together until they meet (worst
* case at Object)
*/
int distance1 = countSuperTypes(thisClass);
int distance2 = countSuperTypes(thatClass);
for (; distance1 > distance2; --distance1) {
thisClass = thisClass.getSuperClass();
}
for (; distance1 < distance2; --distance2) {
thatClass = thatClass.getSuperClass();
}
while (thisClass != thatClass) {
thisClass = thisClass.getSuperClass();
thatClass = thatClass.getSuperClass();
}
return thisClass;
}
/**
* Returns a sorted list of array types, so the returned set can be iterated
* over without introducing nondeterminism.
*/
public List<JArrayType> getAllArrayTypes() {
List<JArrayType> result = Lists.newArrayList(arrayTypes.values());
Collections.sort(result, ARRAYTYPE_COMPARATOR);
return result;
}
/**
* Returns an expression that evaluates to an array class literal at runtime.
* <p>
* Note: This version can only be called after {@link
* com.google.gwt.dev.jjs.impl.ImplementClassLiteralsAsFields} has been run.
*/
public JExpression createArrayClassLiteralExpression(SourceInfo sourceInfo,
JClassLiteral leafTypeClassLiteral, int dimensions) {
JField leafTypeClassLiteralField = leafTypeClassLiteral.getField();
assert leafTypeClassLiteralField != null : "Array leaf type must have a class literal field; "
+ "either ImplementClassLiteralsAsField has not run yet or or there is an error computing"
+ "live class literals.";
return new JMethodCall(sourceInfo, null, getIndexedMethod(
RuntimeConstants.ARRAY_GET_CLASS_LITERAL_FOR_ARRAY),
new JFieldRef(sourceInfo, null, leafTypeClassLiteralField,
leafTypeClassLiteralField.getEnclosingType()), getLiteralInt(dimensions));
}
public Map<JReferenceType, JCastMap> getCastMap() {
return Collections.unmodifiableMap(castMaps);
}
public JCastMap getCastMap(JReferenceType referenceType) {
// ensure jsonCastableTypeMaps has been initialized
// it might not have been if the ImplementCastsAndTypeChecks has not been run
if (castMaps == null) {
initTypeInfo(null);
}
return castMaps.get(referenceType);
}
public JField getClassLiteralField(JType type) {
return classLiteralFieldsByType.get(
(type.isJsNative() || type.isJsoType()) ? getJavaScriptObject() : type);
}
public List<JDeclaredType> getDeclaredTypes() {
return allTypes;
}
public List<JMethod> getEntryMethods() {
return entryMethods;
}
public int getFragmentCount() {
// Initial fragment is the +1.
return runAsyncs.size() + 1;
}
public FragmentPartitioningResult getFragmentPartitioningResult() {
return fragmentPartitioningResult;
}
// TODO(stalcup): this is a blatant bug. there's no unambiguous way to convert from binary name to
// source name. JProgram needs to index types both ways.
public JDeclaredType getFromTypeMap(String qualifiedBinaryOrSourceName) {
String srcTypeName = qualifiedBinaryOrSourceName.replace('$', '.');
return typeNameMap.get(srcTypeName);
}
public JField getIndexedField(String string) {
JField field = indexedFields.get(string);
if (field == null) {
throw new InternalCompilerException("Unable to locate index field: " + string);
}
return field;
}
public Set<JField> getIndexedFields() {
return ImmutableSet.copyOf(indexedFields.values());
}
public JMethod getIndexedMethod(String string) {
JMethod method = indexedMethods.get(string);
if (method == null) {
throw new InternalCompilerException("Unable to locate index method: " + string);
}
return method;
}
public Set<JMethod> getIndexedMethods() {
return ImmutableSet.copyOf(indexedMethods.values());
}
public JMethod getIndexedMethodOrNull(String string) {
return indexedMethods.get(string);
}
public JDeclaredType getIndexedType(String string) {
JDeclaredType type = indexedTypes.get(string);
if (type == null) {
throw new InternalCompilerException("Unable to locate index type: " + string);
}
return type;
}
public Collection<JDeclaredType> getIndexedTypes() {
return Collections.unmodifiableCollection(indexedTypes.values());
}
public LinkedHashSet<JRunAsync> getInitialAsyncSequence() {
return initialAsyncSequence;
}
public List<Integer> getInitialFragmentIdSequence() {
return initialFragmentIdSequence;
}
public JClassType getJavaScriptObject() {
return typeSpecialJavaScriptObject;
}
public JLiteral getLiteral(Object value) {
return getLiteral(SourceOrigin.UNKNOWN, value);
}
public JLiteral getLiteral(SourceInfo info, Object value) {
if (value == null) {
return getLiteralNull();
}
if (value instanceof String) {
return getStringLiteral(info, (String) value);
}
if (value instanceof Integer) {
return getLiteralInt((Integer) value);
}
if (value instanceof Long) {
return getLiteralLong((Long) value);
}
if (value instanceof Character) {
return getLiteralChar((Character) value);
}
if (value instanceof Boolean) {
return getLiteralBoolean((Boolean) value);
}
if (value instanceof Double) {
return getLiteralDouble((Double) value);
}
if (value instanceof Float) {
return getLiteralFloat((Float) value);
}
throw new IllegalArgumentException("Unknown literal type for " + value);
}
public JBooleanLiteral getLiteralBoolean(boolean value) {
return JBooleanLiteral.get(value);
}
public JCharLiteral getLiteralChar(char value) {
return JCharLiteral.get(value);
}
public JDoubleLiteral getLiteralDouble(double d) {
return JDoubleLiteral.get(d);
}
public JFloatLiteral getLiteralFloat(double f) {
return JFloatLiteral.get(f);
}
public JIntLiteral getLiteralInt(int value) {
return JIntLiteral.get(value);
}
public JLongLiteral getLiteralLong(long value) {
return JLongLiteral.get(value);
}
public JNullLiteral getLiteralNull() {
return JNullLiteral.INSTANCE;
}
public JStringLiteral getStringLiteral(SourceInfo sourceInfo, String s) {
sourceInfo.addCorrelation(sourceInfo.getCorrelator().by(Literal.STRING));
return new JStringLiteral(sourceInfo, StringInterner.get().intern(s), typeString);
}
public List<JDeclaredType> getModuleDeclaredTypes() {
List<JDeclaredType> moduleDeclaredTypes = Lists.newArrayList();
for (JDeclaredType type : allTypes) {
if (isReferenceOnly(type)) {
continue;
}
moduleDeclaredTypes.add(type);
}
return moduleDeclaredTypes;
}
public int getNodeCount() {
Event countEvent = SpeedTracerLogger.start(CompilerEventType.OPTIMIZE, "phase", "countNodes");
TreeStatistics treeStats = new TreeStatistics();
treeStats.accept(this);
int numNodes = treeStats.getNodeCount();
countEvent.end();
return numNodes;
}
public JField getNullField() {
return JField.NULL_FIELD;
}
public JMethod getNullMethod() {
return JMethod.NULL_METHOD;
}
public List<JRunAsync> getRunAsyncs() {
return runAsyncs;
}
public int getCommonAncestorFragmentId(int thisFragmentId, int thatFragmentId) {
return fragmentPartitioningResult.getCommonAncestorFragmentId(thisFragmentId, thatFragmentId);
}
public Collection<JType> getSubclasses(JType type) {
return Collections2.transform(typeOracle.getSubClassNames(type.getName()),
new Function<String, JType>() {
@Override
public JType apply(String typeName) {
return getFromTypeMap(typeName);
}
}
);
}
public JMethod getStaticImpl(JMethod method) {
JMethod staticImpl = instanceToStaticMap.get(method);
assert staticImpl == null || staticImpl.getEnclosingType().getMethods().contains(staticImpl);
return staticImpl;
}
public JArrayType getTypeArray(JType elementType) {
JArrayType arrayType = arrayTypes.get(elementType);
if (arrayType == null) {
arrayType = new JArrayType(elementType);
arrayTypes.put(elementType, arrayType);
}
return arrayType;
}
// TODO(dankurka): Why does JProgram synthezise array types on the fly
// Look into refactoring JProgram to get rid of this responsibility
@Override
public JArrayType getOrCreateArrayType(JType leafType, int dimensions) {
assert dimensions > 0;
assert (!(leafType instanceof JArrayType));
JArrayType result = getTypeArray(leafType);
while (dimensions > 1) {
result = getTypeArray(result);
--dimensions;
}
return result;
}
public JType getTypeByClassLiteralField(JField field) {
return typesByClassLiteralField.get(field);
}
public JClassType getTypeClassLiteralHolder() {
return typeSpecialClassLiteralHolder;
}
/**
* Returns the JType corresponding to a JSNI type reference.
*/
public JType getTypeFromJsniRef(String className) {
int dim = 0;
while (className.endsWith("[]")) {
dim++;
className = className.substring(0, className.length() - 2);
}
JType type = primitiveTypes.get(className);
if (type == null) {
type = getFromTypeMap(className);
}
// TODO(deprecation): remove support for this.
if (type == null) {
type = primitiveTypesDeprecated.get(className);
}
if (type == null || dim == 0) {
return type;
} else {
return getOrCreateArrayType(type, dim);
}
}
public JClassType getTypeJavaLangClass() {
return typeClass;
}
public JClassType getTypeJavaLangObject() {
return typeJavaLangObject;
}
public JArrayType getTypeJavaLangObjectArray() {
return typeJavaLangObjectArray;
}
public JClassType getTypeJavaLangString() {
return typeString;
}
public Set<String> getTypeNamesToIndex() {
return typeNamesToIndex;
}
public JPrimitiveType getTypePrimitiveBoolean() {
return JPrimitiveType.BOOLEAN;
}
public JPrimitiveType getTypePrimitiveByte() {
return JPrimitiveType.BYTE;
}
public JPrimitiveType getTypePrimitiveChar() {
return JPrimitiveType.CHAR;
}
public JPrimitiveType getTypePrimitiveDouble() {
return JPrimitiveType.DOUBLE;
}
public JPrimitiveType getTypePrimitiveFloat() {
return JPrimitiveType.FLOAT;
}
public JPrimitiveType getTypePrimitiveInt() {
return JPrimitiveType.INT;
}
public JPrimitiveType getTypePrimitiveLong() {
return JPrimitiveType.LONG;
}
public JPrimitiveType getTypePrimitiveShort() {
return JPrimitiveType.SHORT;
}
public JPrimitiveType getTypeVoid() {
return JPrimitiveType.VOID;
}
public void initTypeInfo(Map<JReferenceType, JCastMap> castMapForType) {
castMaps = castMapForType;
if (castMaps == null) {
castMaps = Maps.newIdentityHashMap();
}
}
public boolean isUntypedArrayType(JType type) {
if (!type.isArrayType()) {
return false;
}
JArrayType arrayType = (JArrayType) type;
return arrayType.getLeafType().isJsNative();
}
public boolean isJavaLangString(JType type) {
assert type != null;
return type.getUnderlyingType() == typeString;
}
public boolean isJavaLangObject(JType type) {
assert type != null;
return type.getUnderlyingType() == typeJavaLangObject;
}
public boolean isReferenceOnly(JDeclaredType type) {
if (type != null) {
return referenceOnlyTypeNames.contains(type.getName());
}
return false;
}
public boolean isStaticImpl(JMethod method) {
return staticToInstanceMap.containsKey(method);
}
/**
* If the type is a JSO or an array of JSOs it returns cggcc.JavaScriptObject or an array of
* cggcc.JavaScriptObject respectively; otherwise returns {@code type}.
*/
public JType normalizeJsoType(JType type) {
type = type.getUnderlyingType();
if (type instanceof JArrayType) {
return getOrCreateArrayType(normalizeJsoType(((JArrayType) type).getLeafType()),
((JArrayType) type).getDims());
}
if (type.isJsoType()) {
return getJavaScriptObject();
}
return type;
}
public void putIntoTypeMap(String qualifiedBinaryName, JDeclaredType type) {
// Make it into a source type name.
String srcTypeName = qualifiedBinaryName.replace('$', '.');
typeNameMap.put(srcTypeName, type);
}
public void putStaticImpl(JMethod method, JMethod staticImpl) {
instanceToStaticMap.put(method, staticImpl);
staticToInstanceMap.put(staticImpl, method);
}
public void recordClassLiteralFields(Map<JType, JField> classLiteralFields) {
this.classLiteralFieldsByType = HashBiMap.create(classLiteralFields);
this.typesByClassLiteralField = classLiteralFieldsByType.inverse();
}
public void removeStaticImplMapping(JMethod staticImpl) {
JMethod instanceMethod = staticToInstanceMap.remove(staticImpl);
if (instanceMethod != null) {
instanceToStaticMap.remove(instanceMethod);
}
}
public void removeReferenceOnlyType(JDeclaredType type) {
referenceOnlyTypeNames.remove(type.getName());
}
public void setFragmentPartitioningResult(FragmentPartitioningResult result) {
fragmentPartitioningResult = result;
}
public void setInitialFragmentIdSequence(List<Integer> initialFragmentIdSequence) {
this.initialFragmentIdSequence = initialFragmentIdSequence;
}
public void setRunAsyncs(List<JRunAsync> runAsyncs) {
this.runAsyncs = ImmutableList.copyOf(runAsyncs);
}
public void setInitialAsyncSequence(LinkedHashSet<JRunAsync> initialAsyncSequence) {
assert this.initialAsyncSequence.isEmpty();
initialFragmentIdSequence = Lists.newArrayList();
// TODO(rluble): hack for now the initial fragments correspond to the initial runAsyncIds.
initialFragmentIdSequence.addAll(
Collections2.transform(initialAsyncSequence,
new Function<JRunAsync, Integer>() {
@Override
public Integer apply(JRunAsync runAsync) {
return runAsync.getRunAsyncId();
}
}));
this.initialAsyncSequence = initialAsyncSequence;
}
/**
* If {@code method} is a static impl method, returns the instance method
* that {@code method} is the implementation of. Otherwise, returns{@code null}.
*/
public JMethod instanceMethodForStaticImpl(JMethod method) {
return staticToInstanceMap.get(method);
}
@Override
public void traverse(JVisitor visitor, Context ctx) {
if (visitor.visit(this, ctx)) {
visitModuleTypes(visitor);
}
visitor.endVisit(this, ctx);
}
/**
* Builds the starter set of type names that should be indexed when seen during addType(). This
* set is a thread safe instance variable and external logic is free to modify it as further
* requirements are discovered.
*/
private static Set<String> buildInitialTypeNamesToIndex() {
Set<String> typeNamesToIndex = Sets.newHashSet();
typeNamesToIndex.addAll(ImmutableList.of("java.io.Serializable", "java.lang.Object",
"java.lang.String", "java.lang.Class", "java.lang.CharSequence", "java.lang.Cloneable",
"java.lang.Comparable", "java.lang.Enum", "java.lang.Iterable", "java.util.Iterator",
"java.lang.AssertionError", "java.lang.Boolean", "java.lang.Byte", "java.lang.Character",
"java.lang.Short", "java.lang.Integer", "java.lang.Long", "java.lang.Float",
"java.lang.Double", "java.lang.Throwable", "com.google.gwt.core.client.GWT",
JAVASCRIPTOBJECT, CLASS_LITERAL_HOLDER, "com.google.gwt.core.client.RunAsyncCallback",
"com.google.gwt.core.client.impl.AsyncFragmentLoader",
"com.google.gwt.core.client.impl.Impl",
"com.google.gwt.core.client.prefetch.RunAsyncCode"));
typeNamesToIndex.addAll(CODEGEN_TYPES_SET);
return typeNamesToIndex;
}
public void visitAllTypes(JVisitor visitor) {
visitor.accept(allTypes);
}
public void visitModuleTypes(JVisitor visitor) {
for (JDeclaredType type : allTypes) {
if (isReferenceOnly(type)) {
continue;
}
visitor.accept(type);
}
}
private int countSuperTypes(JClassType type) {
int count = 0;
while ((type = type.getSuperClass()) != null) {
++count;
}
return count;
}
/**
* See notes in {@link #writeObject(ObjectOutputStream)}.
*
* @see #writeObject(ObjectOutputStream)
*/
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
allTypes = deserializeTypes(stream);
stream.defaultReadObject();
}
/**
* Serializing the Java AST is a multi-step process to avoid blowing out the
* stack.
*
* <ol>
* <li>Write all declared types in a lightweight manner to establish object
* identity for types</li>
* <li>Write all fields; write all methods in a lightweight manner to
* establish object identity for methods</li>
* <li>Write all method bodies</li>
* <li>Write everything else, which will mostly refer to already-serialized
* objects.</li>
* <li>Write the bodies of the entry methods (unlike all other methods, these
* are not contained by any type.</li>
* </ol>
*
* The goal of this process to to avoid "running away" with the stack. Without
* special logic here, lots of things would reference types, method body code
* would reference both types and other methods, and really, really long
* recursion chains would result.
*/
private void writeObject(ObjectOutputStream stream) throws IOException {
serializeTypes(allTypes, stream);
stream.defaultWriteObject();
}
}