Double and Boolean implemented as unboxed raw types. Change-Id: I3a11e91be6ceb0d72b727a43c28581ebef6d70e3
diff --git a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java index e250f9d..b507c64 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -113,6 +113,7 @@ import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.StringTypeMapper; import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeMapper; import com.google.gwt.dev.jjs.impl.ResolveRuntimeTypeReferences.TypeOrder; +import com.google.gwt.dev.jjs.impl.RewriteConstructorCallsForUnboxedTypes; import com.google.gwt.dev.jjs.impl.SameParameterValueOptimizer; import com.google.gwt.dev.jjs.impl.SourceInfoCorrelator; import com.google.gwt.dev.jjs.impl.TypeCoercionNormalizer; @@ -325,6 +326,9 @@ // TODO(stalcup): hide metrics gathering in a callback or subclass logger.log(TreeLogger.INFO, "Compiling permutation " + permutationId + "..."); + // Rewrite calls to from boxed constructor types to specialized unboxed methods + RewriteConstructorCallsForUnboxedTypes.exec(jprogram); + // (2) Transform unresolved Java AST to resolved Java AST ResolvePermutationDependentValues .exec(jprogram, properties, permutation.getPropertyAndBindingInfos());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java index c5d5a17..2135760 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JProgram.java
@@ -22,6 +22,7 @@ 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.TypeCategory; import com.google.gwt.dev.jjs.impl.codesplitter.FragmentPartitioningResult; import com.google.gwt.dev.util.StringInterner; import com.google.gwt.dev.util.log.speedtracer.CompilerEventType; @@ -32,6 +33,7 @@ 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.Lists; import com.google.gwt.thirdparty.guava.common.collect.Maps; import com.google.gwt.thirdparty.guava.common.collect.Sets; @@ -43,6 +45,7 @@ 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; @@ -55,6 +58,68 @@ 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. + STRING(true), + DOUBLE(true), + BOOLEAN(true), + + // non-native represented type values. + HAS_JAVA_VIRTUAL_DISPATCH(false), JAVA_ARRAY(false), JSO(false); + + private final String instanceOfMethod; + private final String castMapField; + private final TypeCategory typeCategory; + private final String dynamicCastMethod; + + DispatchType(boolean nativeType) { + if (nativeType) { + // These field are initialized to methods that are by-convention + // The conventions are: + // Cast.isJava[BoxedTypeName] for instanceof checks + // Cast.[boxedTypeName]CastMap for cast map fields + // Cast.dynamicCastTo[BoxedTypeName] for cast checks + // TypedCategory.TYPE_JAVA_LANG_[BoxedTypeName] + // If Cast or TypeCategory is edited, update this constructor + this.instanceOfMethod = "Cast.isJava" + camelCase(name()); + this.castMapField = "Cast." + name().toLowerCase() + "CastMap"; + this.dynamicCastMethod = "Cast.dynamicCastTo" + camelCase(name()); + this.typeCategory = TypeCategory.valueOf("TYPE_JAVA_LANG_" + name()); + } else { + instanceOfMethod = null; + castMapField = null; + typeCategory = null; + dynamicCastMethod = null; + } + } + + private static String camelCase(String name) { + return Character.toUpperCase(name.charAt(0)) + name.toLowerCase().substring(1); + } + + public String getInstanceOfMethod() { + return instanceOfMethod; + } + + public String getCastMapField() { + return castMapField; + } + + public TypeCategory getTypeCategory() { + return typeCategory; + } + + public String getDynamicCastMethod() { + return dynamicCastMethod; + } + } + + /** * Returns whether a class is a synthetic Prototype class generated by APT or user. */ public boolean isJsTypePrototype(JDeclaredType classType) { @@ -288,8 +353,14 @@ private JClassType typeString; + private JClassType typeDouble; + + private JClassType typeBoolean; + private FragmentPartitioningResult fragmentPartitioningResult; + private Map<JClassType, DispatchType> nativeType2DispatchType; + /** * Add a pinned method. */ @@ -353,6 +424,12 @@ case "java.lang.String": typeString = (JClassType) type; break; + case "java.lang.Double": + typeDouble = (JClassType) type; + break; + case "java.lang.Boolean": + typeBoolean = (JClassType) type; + break; case "java.lang.Class": typeClass = (JClassType) type; break; @@ -365,6 +442,59 @@ } } + public boolean isRepresentedAsNativeJsPrimitive(JType type) { + return getRepresentedAsNativeTypes().contains(type); + } + + public Set<JClassType> getRepresentedAsNativeTypes() { + return getRepresentedAsNativeTypesDispatchMap().keySet(); + } + + public Map<JClassType, DispatchType> getRepresentedAsNativeTypesDispatchMap() { + if (nativeType2DispatchType == null) { + // these are returned in the reverse order in which they are checked + // because devirtualizer constructs the nested expressions from the inside out. + nativeType2DispatchType = ImmutableMap.of( + typeBoolean, DispatchType.BOOLEAN, + typeDouble, DispatchType.DOUBLE, + typeString, DispatchType.STRING); + } + return nativeType2DispatchType; + } + + 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); + } + + EnumSet<DispatchType> dispatchSet = EnumSet.noneOf(DispatchType.class); + DispatchType dispatchType = getRepresentedAsNativeTypesDispatchMap().get(type); + if (dispatchType != null) { + dispatchSet = EnumSet.of(dispatchType); + } else if (typeOracle.isDualJsoInterface(type)) { + // If it is an interface implemented both by JSOs and regular Java Objects; + 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 (typeOracle.isInstantiatedType(potentialNativeDispatchType) && + typeOracle.castSucceedsTrivially(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}.
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java index 4dc76f8..b263b4c 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ControlFlowAnalyzer.java
@@ -413,7 +413,6 @@ } rescueArgumentsIfParametersCanBeRead(call); - return false; } @@ -514,7 +513,8 @@ private boolean canBeInstantiatedInJavaScript(JType type) { // Technically, JsType/JsFunction are also instantiatable in JavaScript but we don't track // them using similar to JSO as if we do that then after cast normalization, they got pruned. - if (program.typeOracle.canBeJavaScriptObject(type) || program.isJavaLangString(type)) { + if (program.typeOracle.canBeJavaScriptObject(type) + || program.isRepresentedAsNativeJsPrimitive(type)) { return true; }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java index 3da8a7e..acf104c 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Devirtualizer.java
@@ -31,6 +31,7 @@ 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.JProgram.DispatchType; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; import com.google.gwt.dev.jjs.ast.JType; @@ -41,6 +42,8 @@ import com.google.gwt.thirdparty.guava.common.collect.Lists; import com.google.gwt.thirdparty.guava.common.collect.Maps; +import java.util.EnumMap; +import java.util.EnumSet; import java.util.List; import java.util.Map; @@ -109,16 +112,16 @@ } JType instanceType = x.getInstance().getType().getUnderlyingType(); - // If the instance can't possibly be a JSO, String or an interface implemented by String, do - // not devirtualize. + // If the instance can't possibly be a JSO, String, Number, or an interface implemented by + // either, do not devirtualize. if (instanceType != program.getTypeJavaLangObject() && !program.typeOracle.canBeJavaScriptObject(instanceType) // not a string - && instanceType != program.getTypeJavaLangString() + && !(program.isRepresentedAsNativeJsPrimitive(instanceType)) // not an array && !(instanceType instanceof JArrayType) // not an interface of String, e.g. CharSequence or Comparable - && !program.getTypeJavaLangString().getImplements().contains(instanceType) + && !isSuperOfRepresentedAsNativeType(instanceType) // it is a super.m() call and the superclass is not a JSO. (this case is NOT reached if // MakeCallsStatic was called). || x.isStaticDispatchOnly() @@ -203,12 +206,21 @@ || program.typeOracle.isSingleJsoImpl(targetType) || program.typeOracle.isDualJsoInterface(targetType) || targetType == program.getTypeJavaLangObject() - || targetType == program.getTypeJavaLangString() - || program.getTypeJavaLangString().getImplements().contains(targetType)) { + || isSuperOfRepresentedAsNativeType(targetType)) { return true; } return false; } + + private boolean isSuperOfRepresentedAsNativeType(JType targetType) { + for (JClassType type : program.getRepresentedAsNativeTypes()) { + if (program.typeOracle.isInstantiatedType(type) && + program.typeOracle.castSucceedsTrivially(type, targetType)) { + return true; + } + } + return false; + } } public static void exec(JProgram program) { @@ -236,11 +248,6 @@ private final JMethod hasJavaObjectVirtualDispatch; /** - * Contains the Cast.isJavaString method. - */ - private final JMethod isJavaStringMethod; - - /** * Contains the Cast.instanceofArray method. */ private final JMethod isJavaArray; @@ -297,7 +304,7 @@ private Devirtualizer(JProgram program) { this.program = program; - this.isJavaStringMethod = program.getIndexedMethod("Cast.isJavaString"); + this.hasJavaObjectVirtualDispatch = program.getIndexedMethod("Cast.hasJavaObjectVirtualDispatch"); this.isJavaArray = program.getIndexedMethod("Cast.isJavaArray"); @@ -325,6 +332,7 @@ if (target == null) { return null; } + for (JMethod overridingMethod : target.getMethods()) { if (JTypeOracle.methodsDoMatch(method, overridingMethod)) { return overridingMethod; @@ -385,13 +393,6 @@ return dispatchCall; } - // Byte mask used by {@link ::getÓrCreateDevirtualMethod} to determine possible dispatches of a - // method. - private static final byte STRING = 0x01; - private static final byte HAS_JAVA_VIRTUAL_DISPATCH = 0x02; - private static final byte JAVA_ARRAY = 0x04; - private static final byte JSO = 0x08; - /** * Create a conditional method to discriminate between static and virtual * dispatch. @@ -413,42 +414,27 @@ ///////////////////////////////////////////////////////////////// // 1. Determine which types of object are target of this dispatch ///////////////////////////////////////////////////////////////// - byte possibleTargetTypes = 0x0; JReferenceType enclosingType = method.getEnclosingType(); - if (enclosingType == program.getTypeJavaLangObject()) { - // Object methods can be dispatched to all four possible classes. - possibleTargetTypes = STRING | HAS_JAVA_VIRTUAL_DISPATCH | JAVA_ARRAY | JSO; - } else if (enclosingType == program.getTypeJavaLangString()) { - // String is final and can not be extended. - possibleTargetTypes |= STRING; - } - - if (program.typeOracle.isDualJsoInterface(enclosingType)) { - // If it is an interface implemented both by JSOs and regular Java Objects; - possibleTargetTypes = HAS_JAVA_VIRTUAL_DISPATCH | JSO; - } else if (program.typeOracle.isSingleJsoImpl(enclosingType) || enclosingType.isJsoType()) { - // If it is either an interface implemented by JSOs or JavaScriptObject or one of its - // subclasses. - possibleTargetTypes = JSO; - } - - if (program.getTypeJavaLangString().getImplements().contains(enclosingType)) { - // If it is an interface implemented by String. - possibleTargetTypes |= (byte) (STRING | HAS_JAVA_VIRTUAL_DISPATCH); - } + EnumSet<DispatchType> possibleTargetTypes = program.getDispatchType( + enclosingType.getUnderlyingType()); ///////////////////////////////////////////////////////////////// // 2. Compute the dispatch to method for each relevant case. ///////////////////////////////////////////////////////////////// - Map<Byte, JMethod> dispatchToMethodByTargetType = Maps.newTreeMap(); - if ((possibleTargetTypes & STRING) != 0) { - JMethod overridingMethod = findOverridingMethod(method, program.getTypeJavaLangString()); - assert overridingMethod != null : method.getEnclosingType().getName() + "::" + - method.getName() + " not overridden by String"; - dispatchToMethodByTargetType.put(STRING, - staticImplCreator.getOrCreateStaticImpl(program, overridingMethod)); + EnumMap<DispatchType, JMethod> dispatchToMethodByTargetType = new EnumMap<>(DispatchType.class); + for (Map.Entry<JClassType, DispatchType> nativeRepresentedType : + program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { + // skip non-instantiated boxed types, which have been pruned from the AST. + if (program.typeOracle.isInstantiatedType(nativeRepresentedType.getKey())) { + maybeCreateDispatchFor(method, nativeRepresentedType.getValue(), possibleTargetTypes, + dispatchToMethodByTargetType, nativeRepresentedType.getKey()); + } } - if ((possibleTargetTypes & JSO) != 0) { + + maybeCreateDispatchFor(method, DispatchType.JAVA_ARRAY, possibleTargetTypes, + dispatchToMethodByTargetType, program.getTypeJavaLangObject()); + + if (possibleTargetTypes.contains(DispatchType.JSO)) { JMethod overridingMethod = findOverridingMethod(method, program.typeOracle.getSingleJsoImpl(enclosingType)); if (overridingMethod == null && enclosingType == program.getTypeJavaLangObject()) { @@ -456,19 +442,12 @@ } assert overridingMethod != null : method.getEnclosingType().getName() + "::" + method.getName() + " not overridden by JavaScriptObject"; - dispatchToMethodByTargetType.put(JSO, + dispatchToMethodByTargetType.put(DispatchType.JSO, staticImplCreator.getOrCreateStaticImpl(program, overridingMethod)); } - if ((possibleTargetTypes & JAVA_ARRAY) != 0) { - // Arrays only implement Object methods as the Clonable interface is not supported in GWT. - JMethod overridingMethod = findOverridingMethod(method, program.getTypeJavaLangObject()); - assert overridingMethod != null : method.getEnclosingType().getName() + "::" + - method.getName() + " not overridden by Object"; - dispatchToMethodByTargetType.put(JAVA_ARRAY, - staticImplCreator.getOrCreateStaticImpl(program, overridingMethod)); - } - if ((possibleTargetTypes & HAS_JAVA_VIRTUAL_DISPATCH) != 0) { - dispatchToMethodByTargetType.put(HAS_JAVA_VIRTUAL_DISPATCH, method); + + if (possibleTargetTypes.contains(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH)) { + dispatchToMethodByTargetType.put(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH, method); } ///////////////////////////////////////////////////////////////// @@ -483,17 +462,27 @@ JClassType devirtualMethodEnclosingClass = null; if (method.getEnclosingType() instanceof JClassType) { devirtualMethodEnclosingClass = (JClassType) method.getEnclosingType(); - } else if (dispatchToMethodByTargetType.get(STRING) != null) { - // Methods from interfaces implemented by String end up in String. - devirtualMethodEnclosingClass = program.getTypeJavaLangString(); - } else if (dispatchToMethodByTargetType.get(JSO) != null) { - // This is an interface method implemented by a JSO, place in the JSO class. - devirtualMethodEnclosingClass = (JClassType) - dispatchToMethodByTargetType.get(JSO).getEnclosingType(); - } else { - // It is an interface implemented by String or arrays, place it in Object. - devirtualMethodEnclosingClass = program.getTypeJavaLangObject(); + } else { + for (Map.Entry<JClassType, DispatchType> nativeRepresentedType : + program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { + if (dispatchToMethodByTargetType.containsKey(nativeRepresentedType.getValue())) { + devirtualMethodEnclosingClass = nativeRepresentedType.getKey(); + break; + } + } } + + if (devirtualMethodEnclosingClass == null) { + if (dispatchToMethodByTargetType.get(DispatchType.JSO) != null) { + // This is an interface method implemented by a JSO, place in the JSO class. + devirtualMethodEnclosingClass = (JClassType) + dispatchToMethodByTargetType.get(DispatchType.JSO).getEnclosingType(); + } else { + // It is an interface implemented by String or arrays, place it in Object. + devirtualMethodEnclosingClass = program.getTypeJavaLangObject(); + } + } + // Devirtualization of external methods stays external and devirtualization of internal methods // stays internal. assert program.isReferenceOnly(devirtualMethodEnclosingClass) @@ -521,13 +510,13 @@ // Construct back to fort. Last is JSO. JExpression dispatchExpression = - maybeCreateDispatch(dispatchToMethodByTargetType.get(JSO), devirtualMethod); + maybeCreateDispatch(dispatchToMethodByTargetType.get(DispatchType.JSO), devirtualMethod); // Dispatch to array dispatchExpression = constructMinimalCondition( isJavaArray, new JParameterRef(thisParam.getSourceInfo(), thisParam), - maybeCreateDispatch(dispatchToMethodByTargetType.get(JAVA_ARRAY), devirtualMethod), + maybeCreateDispatch(dispatchToMethodByTargetType.get(DispatchType.JAVA_ARRAY), devirtualMethod), dispatchExpression); // Dispatch to regular object @@ -535,15 +524,19 @@ hasJavaObjectVirtualDispatch, new JParameterRef(thisParam.getSourceInfo(), thisParam), maybeCreateDispatch( - dispatchToMethodByTargetType.get(HAS_JAVA_VIRTUAL_DISPATCH), devirtualMethod), + dispatchToMethodByTargetType.get(DispatchType.HAS_JAVA_VIRTUAL_DISPATCH), devirtualMethod), dispatchExpression); - // Dispatch to regular string - dispatchExpression = constructMinimalCondition( - isJavaStringMethod, - new JParameterRef(thisParam.getSourceInfo(), thisParam), - maybeCreateDispatch(dispatchToMethodByTargetType.get(STRING), devirtualMethod), - dispatchExpression); + // Dispatch to regular string, double, boolean + for (Map.Entry<JClassType, DispatchType> nativeRepresentedType + : program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { + DispatchType dispatchType = nativeRepresentedType.getValue(); + dispatchExpression = constructMinimalCondition( + program.getIndexedMethod(dispatchType.getInstanceOfMethod()), + new JParameterRef(thisParam.getSourceInfo(), thisParam), + maybeCreateDispatch(dispatchToMethodByTargetType.get(dispatchType), devirtualMethod), + dispatchExpression); + } // return dispatchConditional; JReturnStatement returnStatement = new JReturnStatement(sourceInfo, dispatchExpression); @@ -553,4 +546,16 @@ return devirtualMethod; } + + private void maybeCreateDispatchFor(JMethod method, DispatchType target, + EnumSet<DispatchType> possibleTargetTypes, + EnumMap<DispatchType, JMethod> dispatchToMethodByTargetType, JClassType targetDevirtualType) { + if (possibleTargetTypes.contains(target)) { + JMethod overridingMethod = findOverridingMethod(method, targetDevirtualType); + assert overridingMethod != null : method.getEnclosingType().getName() + "::" + + method.getName() + " not overridden by " + targetDevirtualType.getSimpleName(); + dispatchToMethodByTargetType.put(target, + staticImplCreator.getOrCreateStaticImpl(program, overridingMethod)); + } + } }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java b/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java index d9714e3..30e0bcb 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/EqualityNormalizer.java
@@ -24,9 +24,12 @@ import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JModVisitor; import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JProgram.DispatchType; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JType; +import java.util.Map; + /** * <p> * Rewrite Java <code>==</code> so that it will execute correctly in JavaScript. @@ -40,16 +43,16 @@ * <code>undefined</code> as a valid translation of a Java <code>null</code>. * </p> * <p> - * However, whenever something that may be a String is compared to something - * that may not be a <code>String</code>, use <code>===</code>. A Java object + * However, whenever something that may be an unboxed type is compared to something + * that may not be a unboxed type, use <code>===</code>. A Java object * compared to a string should always yield false, but that's not true when * comparing in JavaScript using <code>==</code>. The cases where * <code>===</code> must be used are: * </p> * <ul> - * <li>One or both sides have unknown <code>String</code> status.</li> - * <li>One side is definitely <code>String</code> and one side is definitely ! - * <code>String</code>. <br/> + * <li>One or both sides have unknown <i>unboxed type</i> status.</li> + * <li>One side is definitely an <i>unboxed type</i> and one side is definitely ! + * <i>unboxed type</i>. <br/> * TODO: This case could be optimized as * <code>(a == null) & (b == null)</code>.</li> * </ul> @@ -58,6 +61,8 @@ * <code>null</code> vs. <code>undefined</code> if it's possible for one side to * be <code>null</code> and the other to be <code>undefined</code>. * </p> + * <p> An "unboxed type" is a String, Boolean, or Double that is represented as a naked raw + * JS type. */ public class EqualityNormalizer { @@ -81,12 +86,12 @@ return; } - StringStatus lhsStatus = getStringStatus((JReferenceType) lhsType); - StringStatus rhsStatus = getStringStatus((JReferenceType) rhsType); - int strat = COMPARISON_STRAT[lhsStatus.getIndex()][rhsStatus.getIndex()]; + UnboxedTypeStatus lhsStatus = getUnboxedTypeStatus((JReferenceType) lhsType); + UnboxedTypeStatus rhsStatus = getUnboxedTypeStatus((JReferenceType) rhsType); + int comparisonStrategy = COMPARISON_TABLE[lhsStatus.getIndex()][rhsStatus.getIndex()]; - switch (strat) { - case STRAT_TRIPLE: { + switch (comparisonStrategy) { + case T: { if (canBeNull(lhs) && canBeNull(rhs)) { /* * If it's possible for one side to be null and the other side @@ -102,11 +107,11 @@ break; } - case STRAT_DOUBLE: { + case D: { boolean lhsNullLit = lhs == program.getLiteralNull(); boolean rhsNullLit = rhs == program.getLiteralNull(); - if ((lhsNullLit && rhsStatus == StringStatus.NOTSTRING) - || (rhsNullLit && lhsStatus == StringStatus.NOTSTRING)) { + if ((lhsNullLit && rhsStatus == UnboxedTypeStatus.NOT_UNBOXEDTYPE) + || (rhsNullLit && lhsStatus == UnboxedTypeStatus.NOT_UNBOXEDTYPE)) { /* * If either side is a null literal and the other is non-String, * replace with a null-check. @@ -139,21 +144,33 @@ } } - private StringStatus getStringStatus(JReferenceType type) { - JClassType stringType = program.getTypeJavaLangString(); + private UnboxedTypeStatus getUnboxedTypeStatus(JReferenceType type) { if (type.isNullType()) { - return StringStatus.NULL; - } else if (program.typeOracle.castSucceedsTrivially(type, stringType)) { - return StringStatus.STRING; - } else if (program.typeOracle.castFailsTrivially(type, stringType)) { - return StringStatus.NOTSTRING; + return UnboxedTypeStatus.NULL; } else { - return StringStatus.UNKNOWN; + for (Map.Entry<JClassType, DispatchType> nativeDispatchType : + program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { + if (program.typeOracle.castSucceedsTrivially(type, nativeDispatchType.getKey())) { + switch (nativeDispatchType.getValue()) { + case DOUBLE: + return UnboxedTypeStatus.DOUBLE; + case BOOLEAN: + return UnboxedTypeStatus.BOOLEAN; + case STRING: + return UnboxedTypeStatus.STRING; + default: + throw new IllegalStateException("Shouldn't happen"); + } + } else if (!program.typeOracle.castFailsTrivially(type, nativeDispatchType.getKey())) { + return UnboxedTypeStatus.UNKNOWN; + } + } + return UnboxedTypeStatus.NOT_UNBOXEDTYPE; } } private JExpression maskUndefined(JExpression lhs) { - assert ((JReferenceType) lhs.getType()).canBeNull(); + assert lhs.getType().canBeNull(); JMethod maskMethod = program.getIndexedMethod("Cast.maskUndefined"); JMethodCall lhsCall = new JMethodCall(lhs.getSourceInfo(), null, maskMethod, lhs.getType()); @@ -166,12 +183,12 @@ * Represents what we know about an operand type in terms of its type and * <code>null</code> status. */ - private enum StringStatus { - NOTSTRING(2), NULL(3), STRING(1), UNKNOWN(0); + private enum UnboxedTypeStatus { + NOT_UNBOXEDTYPE(4), NULL(5), DOUBLE(3), BOOLEAN(2) ,STRING(1), UNKNOWN(0); private int index; - StringStatus(int index) { + UnboxedTypeStatus(int index) { this.index = index; } @@ -181,32 +198,41 @@ } /** - * A map of the combinations where each comparison strategy should be used. - */ - private static int[][] COMPARISON_STRAT = { - // ..U..S.!S..N - {1, 1, 1, 0,}, // UNKNOWN - {1, 0, 1, 0,}, // STRING - {1, 1, 0, 0,}, // NOTSTRING - {0, 0, 0, 0,}, // NULL - }; - - /** * The comparison strategy of using ==. + * Mnemonic: D = double eq */ - private static final int STRAT_DOUBLE = 0; + private static final int D = 0; /** * The comparison strategy of using ===. + * Mnemonic: T = triple eq */ - private static final int STRAT_TRIPLE = 1; + private static final int T = 1; + + /** + * A map of the combinations where each comparison strategy should be used. + */ + private static int[][] COMPARISON_TABLE = { + // any type compared to unknown uses triple eq + {T, T, T, T, T, D,}, // UNKNOWN + // string type uses D only against String and null + {T, D, T, T, T, D,}, // STRING + // double type uses D only against Double and null + {T, T, D, T, T, D,}, // DOUBLE + // boolean type uses D only against Boolean and null + {T, T, T, D, T, D,}, // BOOLEAN + // non-unboxed type uses D only against other non-unboxed types and null + {T, T, T, T, D, D,}, // NOT_UNBOXEDTYPE + // null vs null is safe everywhere + {D, D, D, D, D, D,}, // NULL + }; public static void exec(JProgram program) { new EqualityNormalizer(program).execImpl(); } private static boolean canBeNull(JExpression x) { - return ((JReferenceType) x.getType()).canBeNull(); + return x.getType().canBeNull(); } private final JProgram program;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java index f208011..ac591c3 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaScriptAST.java
@@ -81,6 +81,7 @@ import com.google.gwt.dev.jjs.ast.JPrefixOperation; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JProgram.DispatchType; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; import com.google.gwt.dev.jjs.ast.JStatement; @@ -2348,8 +2349,12 @@ // special: setup a "toString" alias for java.lang.Object.toString() generateToStringAlias(x, globalStmts); - // Set up the artificial castmap for string. - setupStringCastMap(program.getTypeJavaLangString(), globalStmts); + // Set up the artificial castmap for string, double, and boolean + for (Map.Entry<JClassType, DispatchType> nativeRepresentedType : + program.getRepresentedAsNativeTypesDispatchMap().entrySet()) { + setupCastMapForUnboxedType(nativeRepresentedType.getKey(), globalStmts, + nativeRepresentedType.getValue().getCastMapField()); + } // Perform necessary polyfills. globalStmts.add(constructInvocation(x.getSourceInfo(), @@ -2661,11 +2666,12 @@ } /* - * Sets up the catmap for String. + * Sets up the castmap for type X */ - private void setupStringCastMap(JClassType x, List<JsStatement> globalStmts) { - // Cast.stringCastMap = /* String cast map */ { ..:1, ..:1} - JField castableTypeMapField = program.getIndexedField("Cast.stringCastMap"); + private void setupCastMapForUnboxedType(JClassType x, List<JsStatement> globalStmts, + String castMapField) { + // Cast.[castMapName] = /* cast map */ { ..:1, ..:1} + JField castableTypeMapField = program.getIndexedField(castMapField); JsName castableTypeMapName = names.get(castableTypeMapField); JsNameRef ctmRef = castableTypeMapName.makeRef(x.getSourceInfo());
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java index 91c89df..1768df2 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GwtAstBuilder.java
@@ -3283,24 +3283,6 @@ } } - if (ctor.getEnclosingType() == javaLangString) { - /* - * MAGIC: java.lang.String is implemented as a JavaScript String - * primitive with a modified prototype. This requires funky handling of - * constructor calls. We find a method named _String() whose signature - * matches the requested constructor - * - * TODO(scottb): consider moving this to a later pass. - */ - MethodBinding staticBinding = - targetBinding.getExactMethod(_STRING, b.parameters, curCud.scope); - assert staticBinding.isStatic(); - JMethod staticMethod = typeMap.get(staticBinding); - JMethodCall newCall = new JMethodCall(info, null, staticMethod); - newCall.addArgs(call.getArgs()); - call = newCall; - } - push(call); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementCastsAndTypeChecks.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementCastsAndTypeChecks.java index 8e38b09..71cb6c9 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementCastsAndTypeChecks.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementCastsAndTypeChecks.java
@@ -29,6 +29,7 @@ import com.google.gwt.dev.jjs.ast.JModVisitor; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JProgram.DispatchType; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JRuntimeTypeReference; import com.google.gwt.dev.jjs.ast.JType; @@ -226,6 +227,8 @@ assert EnumSet.of(TypeCategory.TYPE_JSO, TypeCategory.TYPE_JAVA_OBJECT_OR_JSO, TypeCategory.TYPE_JAVA_LANG_OBJECT, TypeCategory.TYPE_JAVA_LANG_STRING, + TypeCategory.TYPE_JAVA_LANG_DOUBLE, + TypeCategory.TYPE_JAVA_LANG_BOOLEAN, TypeCategory.TYPE_JAVA_OBJECT, TypeCategory.TYPE_JS_PROTOTYPE, TypeCategory.TYPE_JS_FUNCTION).contains(typeCategory); @@ -300,12 +303,17 @@ this.instanceOfMethodsByTargetTypeCategory.put( TypeCategory.TYPE_JSO, program.getIndexedMethod("Cast.instanceOfJso")); this.instanceOfMethodsByTargetTypeCategory.put( - TypeCategory.TYPE_JAVA_LANG_STRING, program.getIndexedMethod("Cast.isJavaString")); - this.instanceOfMethodsByTargetTypeCategory.put( TypeCategory.TYPE_JS_PROTOTYPE, program.getIndexedMethod("Cast.instanceOfJsPrototype")); this.instanceOfMethodsByTargetTypeCategory.put( TypeCategory.TYPE_JS_FUNCTION, program.getIndexedMethod("Cast.instanceOfJsFunction")); + for (DispatchType nativeDispatchType : program.getRepresentedAsNativeTypesDispatchMap().values()) { + this.instanceOfMethodsByTargetTypeCategory.put( + nativeDispatchType.getTypeCategory(), + program.getIndexedMethod(nativeDispatchType.getInstanceOfMethod()) + ); + } + // Populate the necessary dynamicCast methods. this.dynamicCastMethodsByTargetTypeCategory.put( TypeCategory.TYPE_JAVA_OBJECT, program.getIndexedMethod("Cast.dynamicCast")); @@ -315,12 +323,19 @@ TypeCategory.TYPE_JAVA_OBJECT_OR_JSO, program.getIndexedMethod("Cast.dynamicCastAllowJso")); this.dynamicCastMethodsByTargetTypeCategory.put( TypeCategory.TYPE_JSO, program.getIndexedMethod("Cast.dynamicCastJso")); - this.dynamicCastMethodsByTargetTypeCategory.put( - TypeCategory.TYPE_JAVA_LANG_STRING, program.getIndexedMethod("Cast.dynamicCastToString")); + this.dynamicCastMethodsByTargetTypeCategory.put( TypeCategory.TYPE_JS_PROTOTYPE, program.getIndexedMethod("Cast.dynamicCastWithPrototype")); this.dynamicCastMethodsByTargetTypeCategory.put( TypeCategory.TYPE_JS_FUNCTION, program.getIndexedMethod("Cast.dynamicCastToJsFunction")); + + for (DispatchType nativeDispatchType : + program.getRepresentedAsNativeTypesDispatchMap().values()) { + this.dynamicCastMethodsByTargetTypeCategory.put( + nativeDispatchType.getTypeCategory(), + program.getIndexedMethod(nativeDispatchType.getDynamicCastMethod()) + ); + } } private void execImpl() {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/RewriteConstructorCallsForUnboxedTypes.java b/dev/core/src/com/google/gwt/dev/jjs/impl/RewriteConstructorCallsForUnboxedTypes.java new file mode 100644 index 0000000..4717a41 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/RewriteConstructorCallsForUnboxedTypes.java
@@ -0,0 +1,107 @@ +/* + * Copyright 2014 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.ast.Context; +import com.google.gwt.dev.jjs.ast.JConstructor; +import com.google.gwt.dev.jjs.ast.JDeclaredType; +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.JProgram; +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.HashMap; +import java.util.Map; + +/** + * Rewrite instantiations of Boolean, Double, and String to use static helper methods which return + * unboxed versions. + * + */ +public class RewriteConstructorCallsForUnboxedTypes extends JModVisitor { + + public static final String NATIVE_TYPE_CREATEMETHOD_PREFIX = "$create"; + private JProgram program; + private Map<String, JMethod> jsniSig2Method = new HashMap<>(); + + public RewriteConstructorCallsForUnboxedTypes(JProgram program) { + this.program = program; + for (JDeclaredType unboxedType : program.getRepresentedAsNativeTypes()) { + for (JMethod method : unboxedType.getMethods()) { + if (method.getName().startsWith(NATIVE_TYPE_CREATEMETHOD_PREFIX)) { + jsniSig2Method.put(method.getJsniSignature(false, false), method); + } + } + } + } + + @Override + public void endVisit(JNewInstance x, Context ctx) { + JMethod createMethod; + JConstructor ctor = x.getTarget(); + + if (!program.isRepresentedAsNativeJsPrimitive(ctor.getEnclosingType())) { + return; + } + + // map BoxedType(args) -> BoxedType.$createBoxedType(args) + createMethod = jsniSig2Method.get(NATIVE_TYPE_CREATEMETHOD_PREFIX + + ctor.getJsniSignature(false, false)); + assert createMethod != null; + + JMethodCall createCall = new JMethodCall(x.getSourceInfo(), null, createMethod); + createCall.addArgs(x.getArgs()); + ctx.replaceMe(createCall); + } + + @Override + public void endVisit(JsniMethodRef x, Context ctx) { + if (x.getTarget().isConstructor() + && program.isRepresentedAsNativeJsPrimitive(x.getTarget().getEnclosingType())) { + JConstructor ctor = (JConstructor) x.getTarget(); + // map BoxedType(args) -> BoxedType.$createBoxedType(args) + JMethod createMethod = jsniSig2Method.get(NATIVE_TYPE_CREATEMETHOD_PREFIX + + ctor.getJsniSignature(false, false)); + assert createMethod != null; + + JsniMethodRef newJsniMethodRef = new JsniMethodRef(x.getSourceInfo(), + x.getIdent(), createMethod, program.getJavaScriptObject()); + ctx.replaceMe(newJsniMethodRef); + } + } + + private static final String NAME = RewriteConstructorCallsForUnboxedTypes.class + .getSimpleName(); + + private OptimizerStats execImpl() { + OptimizerStats stats = new OptimizerStats(NAME); + accept(program); + return stats; + } + + public static OptimizerStats exec(JProgram program) { + Event optimizeEvent = SpeedTracerLogger + .start(CompilerEventType.OPTIMIZE, "optimizer", NAME); + OptimizerStats stats = new RewriteConstructorCallsForUnboxedTypes(program).execImpl(); + optimizeEvent.end("didChange", "" + stats.didChange()); + return stats; + } +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java index 5d25699..b7f73f0 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/TypeCategory.java
@@ -25,14 +25,17 @@ * * These are used in Cast checking and array implementation. */ -public enum TypeCategory { +public enum TypeCategory { /* Make sure this list is kept in sync with the one in Array.java */ TYPE_JAVA_OBJECT, TYPE_JAVA_OBJECT_OR_JSO, TYPE_JSO, TYPE_JAVA_LANG_OBJECT, + // If the next three are renamed, please update JProgram.DispatchType's constructor TYPE_JAVA_LANG_STRING, + TYPE_JAVA_LANG_DOUBLE, + TYPE_JAVA_LANG_BOOLEAN, TYPE_JS_PROTOTYPE, TYPE_JS_FUNCTION, TYPE_PRIMITIVE_LONG, @@ -57,8 +60,8 @@ type = type.getUnderlyingType(); if (type == program.getTypeJavaLangObject()) { return TypeCategory.TYPE_JAVA_LANG_OBJECT; - } else if (type == program.getTypeJavaLangString()) { - return TypeCategory.TYPE_JAVA_LANG_STRING; + } else if (program.getRepresentedAsNativeTypesDispatchMap().containsKey(type)) { + return program.getRepresentedAsNativeTypesDispatchMap().get(type).getTypeCategory(); } else if (program.typeOracle.isEffectivelyJavaScriptObject(type)) { return TypeCategory.TYPE_JSO; } else if (program.typeOracle.isDualJsoInterface(type)
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java index 5ddff12..989da65 100644 --- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java +++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Array.java
@@ -34,11 +34,13 @@ private static final int TYPE_JSO = 2; private static final int TYPE_JAVA_LANG_OBJECT = 3; private static final int TYPE_JAVA_LANG_STRING = 4; - private static final int TYPE_JS_PROTOTYPE = 5; - private static final int TYPE_JS_FUNCTION = 6; - private static final int TYPE_PRIMITIVE_LONG = 7; - private static final int TYPE_PRIMITIVE_NUMBER = 8; - private static final int TYPE_PRIMITIVE_BOOLEAN = 9; + private static final int TYPE_JAVA_LANG_DOUBLE = 5; + private static final int TYPE_JAVA_LANG_BOOLEAN = 6; + private static final int TYPE_JS_PROTOTYPE = 7; + private static final int TYPE_JS_FUNCTION = 8; + private static final int TYPE_PRIMITIVE_LONG = 9; + private static final int TYPE_PRIMITIVE_NUMBER = 10; + private static final int TYPE_PRIMITIVE_BOOLEAN = 11; public static <T> T[] stampJavaTypeInfo(Object array, T[] referenceType) { initValues(referenceType.getClass(), Util.getCastableTypeMap(referenceType), @@ -157,6 +159,10 @@ switch (Array.getElementTypeCategory(array)) { case TYPE_JAVA_LANG_STRING: return Cast.isJavaString(value); + case TYPE_JAVA_LANG_DOUBLE: + return Cast.isJavaDouble(value); + case TYPE_JAVA_LANG_BOOLEAN: + return Cast.isJavaBoolean(value); case TYPE_JAVA_OBJECT: return Cast.canCast(value, Array.getElementTypeId(array)); case TYPE_JSO:
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Cast.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Cast.java index b41383e..122fa90 100644 --- a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Cast.java +++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/Cast.java
@@ -39,13 +39,23 @@ * NOTE: it is important that the field is left uninitialized so that Cast does not * require a clinit. */ + // NOTE: if any of these three are edited, update JProgram.DispatchType's constructor private static JavaScriptObject stringCastMap; + // the next two are implemented exactly as the former + private static JavaScriptObject doubleCastMap; + private static JavaScriptObject booleanCastMap; + @HasNoSideEffects static native boolean canCast(Object src, JavaScriptObject dstId) /*-{ return @com.google.gwt.lang.Cast::isJavaString(*)(src) && !!@com.google.gwt.lang.Cast::stringCastMap[dstId] || - src.@java.lang.Object::castableTypeMap && !!src.@java.lang.Object::castableTypeMap[dstId]; + src.@java.lang.Object::castableTypeMap && !!src.@java.lang.Object::castableTypeMap[dstId] || + @com.google.gwt.lang.Cast::isJavaDouble(*)(src) && + !!@com.google.gwt.lang.Cast::doubleCastMap[dstId] || + // this occurs last because it is much rarer and less likely to be in hot code + @com.google.gwt.lang.Cast::isJavaBoolean(*)(src) && + !!@com.google.gwt.lang.Cast::booleanCastMap[dstId]; }-*/; @HasNoSideEffects @@ -65,11 +75,22 @@ return src; } + // NOTE: if any of these three are edited, update JProgram.DispatchType's constructor static Object dynamicCastToString(Object src) { checkType(src == null || isJavaString(src)); return src; } + static Object dynamicCastToDouble(Object src) { + checkType(src == null || isJavaDouble(src)); + return src; + } + + static Object dynamicCastToBoolean(Object src) { + checkType(src == null || isJavaBoolean(src)); + return src; + } + /** * Allow a dynamic cast to an object, always succeeding if it's a JSO. */ @@ -145,7 +166,7 @@ /** * Uses the not operator to perform a null-check; do NOT use on anything that - * could be a String. + * could be a String, 'unboxed' Double, or 'unboxed' Boolean. */ static native boolean isNotNull(Object src) /*-{ // Coerce to boolean. @@ -154,7 +175,7 @@ /** * Uses the not operator to perform a null-check; do NOT use on anything that - * could be a String. + * could be a String, 'unboxed' Double, or 'unboxed' Boolean. */ static native boolean isNull(Object src) /*-{ return !src; @@ -257,6 +278,7 @@ return o; } + // NOTE: if any of these three are edited, update JProgram.DispatchType's constructor /** * Returns whether the Object is a Java String. * @@ -268,6 +290,26 @@ }-*/; /** + * Returns whether the Object is a Java Double. + * + * Java Numbers are translated to JavaScript numbers. + */ + @HasNoSideEffects + static native boolean isJavaDouble(Object src) /*-{ + return typeof(src) === "number"; + }-*/; + + /** + * Returns whether the Object is a Java Boolean. (*) + * + * Java Booleans are translated to JavaScript booleans. + */ + @HasNoSideEffects + static native boolean isJavaBoolean(Object src) /*-{ + return typeof(src) === "boolean"; + }-*/; + + /** * Returns true if Object can dispatch instance methods and does not need a compiler * provided trampoline. *
diff --git a/dev/core/test/com/google/gwt/core/ext/linker/SourceMapTest.java b/dev/core/test/com/google/gwt/core/ext/linker/SourceMapTest.java index c820112..5fd62f3 100644 --- a/dev/core/test/com/google/gwt/core/ext/linker/SourceMapTest.java +++ b/dev/core/test/com/google/gwt/core/ext/linker/SourceMapTest.java
@@ -385,7 +385,7 @@ return true; } // only $init and $clinit will match the below if - if (relaxName.startsWith("$")) { + if (relaxName.startsWith("$") && !relaxName.startsWith("$create")) { return strictName.equals(relaxName.substring(1)); } return false;
diff --git a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java index 613af57..9e5713a 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java +++ b/dev/core/test/com/google/gwt/dev/jjs/JavaAstConstructor.java
@@ -82,17 +82,23 @@ "import com.google.gwt.core.client.JavaScriptObject;", "public final class Cast {", " private static JavaScriptObject stringCastMap;", + " private static JavaScriptObject doubleCastMap;", + " private static JavaScriptObject booleanCastMap;", " public static native String charToString(char x) /*-{ }-*/;", " public static Object dynamicCast(Object src, int dstId) { return src;}", " public static Object dynamicCastAllowJso(Object src, int dstId) { return src;}", " public static Object dynamicCastJso(Object src) { return src;}", " public static Object dynamicCastToString(Object src) { return src;}", + " public static Object dynamicCastToDouble(Object src) { return src;}", + " public static Object dynamicCastToBoolean(Object src) { return src;}", " public static Object dynamicCastWithPrototype(Object src, int dstId, String jsType) { return src;}", " public static Object dynamicCastToJsFunction(Object src) { return src; }", " public static boolean instanceOf(Object src, int dstId) { return false;}", " public static boolean hasJavaObjectVirtualDispatch(Object o) { return true; }", " public static boolean isJavaArray(Object o) { return false; }", " public static boolean isJavaString(Object o) { return true; }", + " public static boolean isJavaDouble(Object o) { return true; }", + " public static boolean isJavaBoolean(Object o) { return true; }", " public static boolean isJavaScriptObject(Object o) { return true; }", " public static boolean instanceOfOrJso(Object src, int dst) { return false;}", " public static boolean instanceOfJso(Object src) { return false;}",
diff --git a/user/super/com/google/gwt/emul/java/lang/Boolean.java b/user/super/com/google/gwt/emul/java/lang/Boolean.java index 0a57b30..9d4a1a4 100644 --- a/user/super/com/google/gwt/emul/java/lang/Boolean.java +++ b/user/super/com/google/gwt/emul/java/lang/Boolean.java
@@ -70,37 +70,55 @@ return valueOf(parseBoolean(s)); } - private final transient boolean value; - public Boolean(boolean value) { - this.value = value; + /* + * Call to $createBoolean(value) must be here so that the method is referenced and not pruned + * before new Boolean(value) is replaced by $createBoolean(value) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createBoolean(value); } public Boolean(String s) { - this(parseBoolean(s)); + /* + * Call to $createBoolean(value) must be here so that the method is referenced and not pruned + * before new Boolean(value) is replaced by $createBoolean(value) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createBoolean(s); } - public boolean booleanValue() { - return value; - } + public native boolean booleanValue() /*-{ + return @javaemul.internal.InternalPreconditions::checkNotNull(Ljava/lang/Object;)(this); + }-*/; @Override public int compareTo(Boolean b) { - return compare(value, b.value); - } + return compare(booleanValue(), b.booleanValue()); + }; @Override - public boolean equals(Object o) { - return (o instanceof Boolean) && (((Boolean) o).value == value); - } + public native boolean equals(Object o) /*-{ + return this === o; + }-*/; @Override public int hashCode() { - return hashCode(value); + return hashCode(booleanValue()); } @Override public String toString() { - return toString(value); + return toString(booleanValue()); } + + // CHECKSTYLE_OFF: Utility Methods for unboxed Boolean. + static native Boolean $createBoolean(boolean x) /*-{ + return x; + }-*/; + + static Boolean $createBoolean(String x) { + return $createBoolean(Boolean.parseBoolean(x)); + } + // CHECKSTYLE_ON: End utility methods }
diff --git a/user/super/com/google/gwt/emul/java/lang/Double.java b/user/super/com/google/gwt/emul/java/lang/Double.java index f75e195..2151657 100644 --- a/user/super/com/google/gwt/emul/java/lang/Double.java +++ b/user/super/com/google/gwt/emul/java/lang/Double.java
@@ -76,16 +76,20 @@ // 2^-1022 (smallest double non-denorm) private static final double POWER_MINUS_1022 = 2.2250738585072014E-308; - private static final double[] powers = { - POWER_512, POWER_256, POWER_128, POWER_64, POWER_32, POWER_16, POWER_8, - POWER_4, POWER_2, POWER_1 - }; - private static final double[] invPowers = { - POWER_MINUS_512, POWER_MINUS_256, POWER_MINUS_128, POWER_MINUS_64, - POWER_MINUS_32, POWER_MINUS_16, POWER_MINUS_8, POWER_MINUS_4, POWER_MINUS_2, - POWER_MINUS_1 - }; + + private static class PowersTable { + private static final double[] powers = { + POWER_512, POWER_256, POWER_128, POWER_64, POWER_32, POWER_16, POWER_8, + POWER_4, POWER_2, POWER_1 + }; + + private static final double[] invPowers = { + POWER_MINUS_512, POWER_MINUS_256, POWER_MINUS_128, POWER_MINUS_64, + POWER_MINUS_32, POWER_MINUS_16, POWER_MINUS_8, POWER_MINUS_4, POWER_MINUS_2, + POWER_MINUS_1 + }; + } public static int compare(double x, double y) { if (x < y) { @@ -141,8 +145,8 @@ if (value < 1.0) { int bit = 512; for (int i = 0; i < 10; i++, bit >>= 1) { - if (value < invPowers[i] && exp - bit >= -1023) { - value *= powers[i]; + if (value < PowersTable.invPowers[i] && exp - bit >= -1023) { + value *= PowersTable.powers[i]; exp -= bit; } } @@ -154,8 +158,8 @@ } else if (value >= 2.0) { int bit = 512; for (int i = 0; i < 10; i++, bit >>= 1) { - if (value >= powers[i]) { - value *= invPowers[i]; + if (value >= PowersTable.powers[i]) { + value *= PowersTable.invPowers[i]; exp += bit; } } @@ -237,7 +241,7 @@ int bit = 512; for (int i = 0; i < 10; i++, bit >>= 1) { if (exp >= bit) { - d *= powers[i]; + d *= PowersTable.powers[i]; exp -= bit; } } @@ -246,7 +250,7 @@ int bit = 512; for (int i = 0; i < 10; i++, bit >>= 1) { if (exp <= -bit) { - d *= invPowers[i]; + d *= PowersTable.invPowers[i]; exp += bit; } } @@ -271,39 +275,47 @@ return new Double(s); } - private final transient double value; - public Double(double value) { - this.value = value; + /* + * Call to $createDouble(value) must be here so that the method is referenced and not + * pruned before new Double(value) is replaced by $createDouble(value) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createDouble(value); } public Double(String s) { - value = parseDouble(s); + /* + * Call to $createDouble(value) must be here so that the method is referenced and not + * pruned before new Double(value) is replaced by $createDouble(value) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createDouble(s); } @Override public byte byteValue() { - return (byte) value; + return (byte) doubleValue(); } @Override public int compareTo(Double b) { - return compare(this.value, b.value); + return compare(doubleValue(), b.doubleValue()); } @Override - public double doubleValue() { - return value; - } + public native double doubleValue() /*-{ + return @javaemul.internal.InternalPreconditions::checkNotNull(Ljava/lang/Object;)(this); + }-*/; @Override - public boolean equals(Object o) { - return (o instanceof Double) && (((Double) o).value == value); - } + public native boolean equals(Object o) /*-{ + return this === o; + }-*/; @Override public float floatValue() { - return (float) value; + return (float) doubleValue(); } /** @@ -317,34 +329,44 @@ */ @Override public int hashCode() { - return hashCode(value); + return hashCode(doubleValue()); } @Override public int intValue() { - return (int) value; + return (int) doubleValue(); } public boolean isInfinite() { - return isInfinite(value); + return isInfinite(doubleValue()); } public boolean isNaN() { - return isNaN(value); + return isNaN(doubleValue()); } @Override public long longValue() { - return (long) value; + return (long) doubleValue(); } @Override public short shortValue() { - return (short) value; + return (short) doubleValue(); } @Override public String toString() { - return toString(value); + return toString(doubleValue()); } + + // CHECKSTYLE_OFF: Utility Methods for unboxed Double. + static native Double $createDouble(double x) /*-{ + return x; + }-*/; + + static Double $createDouble(String s) { + return $createDouble(Double.parseDouble(s)); + } + // CHECKSTYLE_ON: End utility methods }
diff --git a/user/super/com/google/gwt/emul/java/lang/String.java b/user/super/com/google/gwt/emul/java/lang/String.java index cbbe5b9..75cf4ab 100644 --- a/user/super/com/google/gwt/emul/java/lang/String.java +++ b/user/super/com/google/gwt/emul/java/lang/String.java
@@ -188,104 +188,7 @@ return replaceStr; } - /** - * @skip - */ - static String _String() { - return ""; - } - - /** - * @skip - */ - static String _String(byte[] bytes) { - return _String(bytes, 0, bytes.length); - } - - /** - * @skip - */ - static String _String(byte[] bytes, int ofs, int len) { - return _String(bytes, ofs, len, EmulatedCharset.UTF_8); - } - - /** - * @skip - */ - static String _String(byte[] bytes, int ofs, int len, String charsetName) - throws UnsupportedEncodingException { - return _String(bytes, ofs, len, getCharset(charsetName)); - } - - /** - * @skip - */ - static String _String(byte[] bytes, int ofs, int len, Charset charset) { - return valueOf(((EmulatedCharset) charset).decodeString(bytes, ofs, len)); - } - - /** - * @skip - */ - static String _String(byte[] bytes, String charsetName) - throws UnsupportedEncodingException { - return _String(bytes, 0, bytes.length, charsetName); - } - - /** - * @skip - */ - static String _String(byte[] bytes, Charset charset) - throws UnsupportedEncodingException { - return _String(bytes, 0, bytes.length, charset.name()); - } - - /** - * @skip - */ - static String _String(char value[]) { - return valueOf(value); - } - - /** - * @skip - */ - static String _String(char value[], int offset, int count) { - return valueOf(value, offset, count); - } - - /** - * @skip - */ - static String _String(int[] codePoints, int offset, int count) { - char[] chars = new char[count * 2]; - int charIdx = 0; - while (count-- > 0) { - charIdx += Character.toChars(codePoints[offset++], chars, charIdx); - } - return valueOf(chars, 0, charIdx); - } - - /** - * @skip - */ - static String _String(String other) { - return other; - } - - /** - * @skip - */ - static String _String(StringBuffer sb) { - return valueOf(sb); - } - - /** - * @skip - */ - static String _String(StringBuilder sb) { - return valueOf(sb); - } + // CHECKSTYLE_ON @@ -316,71 +219,123 @@ } public String() { - // magic delegation to _String - _String(); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(); } public String(byte[] bytes) { - // magic delegation to _String - _String(bytes); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(bytes); } public String(byte[] bytes, int ofs, int len) { - // magic delegation to _String - _String(bytes, ofs, len); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(bytes, ofs, len); } public String(byte[] bytes, int ofs, int len, String charsetName) throws UnsupportedEncodingException { - // magic delegation to _String - _String(bytes, ofs, len, charsetName); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(bytes, ofs, len, charsetName); } public String(byte[] bytes, int ofs, int len, Charset charset) { - // magic delegation to _String - _String(bytes, ofs, len, charset); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(bytes, ofs, len, charset); } public String(byte[] bytes, String charsetName) throws UnsupportedEncodingException { - // magic delegation to _String - _String(bytes, charsetName); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(bytes, charsetName); } public String(byte[] bytes, Charset charset) throws UnsupportedEncodingException { - // magic delegation to _String - _String(bytes, charset); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(bytes, charset); } public String(char value[]) { - // magic delegation to _String - _String(value); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(value); } public String(char value[], int offset, int count) { - // magic delegation to _String - _String(value, offset, count); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(value, offset, count); } public String(int codePoints[], int offset, int count) { - // magic delegation to _String - _String(codePoints, offset, count); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(codePoints, offset, count); } public String(String other) { - // magic delegation to _String - _String(other); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(other); } public String(StringBuffer sb) { - // magic delegation to _String - _String(sb); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(sb); } public String(StringBuilder sb) { - // magic delegation to _String - _String(sb); + /* + * Call to $createString(args) must be here so that the method is referenced and not + * pruned before new String(args) is replaced by $createString(args) by + * RewriteConstructorCallsForUnboxedTypes. + */ + $createString(sb); } @Override @@ -793,4 +748,67 @@ } return start > 0 || end < length ? substring(start, end) : this; } + + // CHECKSTYLE_OFF: Utility Methods for unboxed String. + + static String $createString() { + return ""; + } + + static String $createString(byte[] bytes) { + return $createString(bytes, 0, bytes.length); + } + + static String $createString(byte[] bytes, int ofs, int len) { + return $createString(bytes, ofs, len, EmulatedCharset.UTF_8); + } + + static String $createString(byte[] bytes, int ofs, int len, String charsetName) + throws UnsupportedEncodingException { + return $createString(bytes, ofs, len, String.getCharset(charsetName)); + } + + static String $createString(byte[] bytes, int ofs, int len, Charset charset) { + return String.valueOf(((EmulatedCharset) charset).decodeString(bytes, ofs, len)); + } + + static String $createString(byte[] bytes, String charsetName) + throws UnsupportedEncodingException { + return $createString(bytes, 0, bytes.length, charsetName); + } + + static String $createString(byte[] bytes, Charset charset) + throws UnsupportedEncodingException { + return $createString(bytes, 0, bytes.length, charset.name()); + } + + static String $createString(char value[]) { + return String.valueOf(value); + } + + static String $createString(char value[], int offset, int count) { + return String.valueOf(value, offset, count); + } + + static String $createString(int[] codePoints, int offset, int count) { + char[] chars = new char[count * 2]; + int charIdx = 0; + while (count-- > 0) { + charIdx += Character.toChars(codePoints[offset++], chars, charIdx); + } + return String.valueOf(chars, 0, charIdx); + } + + static String $createString(String other) { + return other; + } + + static String $createString(StringBuffer sb) { + return String.valueOf(sb); + } + + static String $createString(StringBuilder sb) { + return String.valueOf(sb); + } + // CHECKSTYLE_ON: end utility methods }
diff --git a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java index 8996704..427345b 100644 --- a/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java +++ b/user/test/com/google/gwt/dev/jjs/test/CompilerTest.java
@@ -363,6 +363,42 @@ byte[] bytes = new byte[10]; bytes[0] = (byte) '1'; assertEquals(49, bytes[0]); + + Object[] disguisedApple = apple; + expectArrayStoreException(disguisedApple, "Hello"); + expectArrayStoreException(disguisedApple, true); + expectArrayStoreException(disguisedApple, 42.0); + } + + private void expectArrayStoreException(Object[] array, Object o) { + try { + array[0] = o; + fail("Expected ArrayStoreException"); + } catch (ArrayStoreException e) { + } + } + + public void testUnboxedArrays() { + Double[] doubleArray = new Double[1]; + doubleArray[0] = 42.0; + + expectArrayStoreException(doubleArray, true); + expectArrayStoreException(doubleArray, "Hello"); + expectArrayStoreException(doubleArray, new Integer(23)); + + Boolean[] booleanArray = new Boolean[1]; + booleanArray[0] = true; + + expectArrayStoreException(booleanArray, 42.0); + expectArrayStoreException(booleanArray, "Hello"); + expectArrayStoreException(booleanArray, new Integer(23)); + + String[] stringArray = new String[1]; + stringArray[0] = "Hello"; + + expectArrayStoreException(stringArray, 42.0); + expectArrayStoreException(stringArray, true); + expectArrayStoreException(stringArray, new Integer(23)); } /**
diff --git a/user/test/com/google/gwt/emultest/java/lang/BooleanTest.java b/user/test/com/google/gwt/emultest/java/lang/BooleanTest.java index 4b00b5c..7975d66 100644 --- a/user/test/com/google/gwt/emultest/java/lang/BooleanTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/BooleanTest.java
@@ -78,4 +78,32 @@ assertFalse(Boolean.valueOf(false4)); } + public void testNPE() { + Boolean b = Math.random() < 0 ? Boolean.TRUE : null; + try { + assertEquals(null, b.booleanValue()); + fail("Should have thrown exception"); + } catch (Exception e) { + } + + try { + boolean bb = b; + fail("Should have thrown exception" + bb); + } catch (Exception e) { + } + } + + public void testEqualityNormalizer() { + Boolean b = false; + if (b != null) { + assertEquals(b.booleanValue(), false); + } else { + fail("false should not evaluate to null"); + } + Object s = ""; + assertTrue(b != s); + + Object d = 0.0; + assertTrue(b != d); + } }
diff --git a/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java b/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java index 9da22bf..e18693d 100644 --- a/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java +++ b/user/test/com/google/gwt/emultest/java/lang/DoubleTest.java
@@ -90,6 +90,35 @@ assertTrue(Double.compare(500.0, 500.0) == 0); } + public void testNPE() { + Double d = Math.random() < 0 ? 42.0 : null; + try { + assertEquals(null, d.doubleValue()); + fail("Should have thrown exception"); + } catch (Exception e) { + } + + try { + double dd = d; + fail("Should have thrown exception" + dd); + } catch (Exception e) { + } + } + + public void testEqualityNormalizer() { + Double d = 0.0; + if (d != null) { + assertEquals(d.doubleValue(), 0.0); + } else { + fail("0.0 should not evaluate to null"); + } + Object s = "0.0"; + assertTrue(d != s); + + Object b = Boolean.FALSE; + assertTrue(b != s); + } + public void testCompareTo() { Double zero = new Double(0.0); Double three = new Double(3.0);