Re-roll of r10435. Changes Class.getName() to return Class$S<seedId> when -XdisableClassMetaData is called to prevent breakage of some apps which expected the text after the '$' to be a legal identifier. Review at: http://gwt-code-reviews.appspot.com/1447821/ git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10462 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java b/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java index 368acec..97cd1d7 100644 --- a/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java +++ b/dev/core/src/com/google/gwt/core/ext/linker/SymbolData.java
@@ -108,6 +108,11 @@ int getQueryId(); /** + * Returns the seedId for types. + */ + int getSeedId(); + + /** * Returns the line number on which the symbol was originally declared or * <code>-1</code> if the line number is unknown. */
diff --git a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java index fc57185..78cfb0c 100644 --- a/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java +++ b/dev/core/src/com/google/gwt/core/ext/linker/impl/StandardSymbolData.java
@@ -32,15 +32,15 @@ public class StandardSymbolData implements SymbolData { public static StandardSymbolData forClass(String className, String uriString, - int lineNumber, int queryId, CastableTypeMap castableTypeMap) { + int lineNumber, int queryId, CastableTypeMap castableTypeMap, int seedId) { return new StandardSymbolData(className, null, null, uriString, lineNumber, - queryId, castableTypeMap); + queryId, castableTypeMap, seedId); } public static StandardSymbolData forMember(String className, String memberName, String methodSig, String uriString, int lineNumber) { return new StandardSymbolData(className, memberName, methodSig, uriString, - lineNumber, 0, null); + lineNumber, 0, null, -1); } public static String toUriString(String fileName) { @@ -63,11 +63,12 @@ private String sourceUri; private String symbolName; private int queryId; + private int seedId; private CastableTypeMap castableTypeMap; private StandardSymbolData(String className, String memberName, String methodSig, String sourceUri, int sourceLine, int queryId, - CastableTypeMap castableTypeMap) { + CastableTypeMap castableTypeMap, int seedId) { assert className != null && className.length() > 0 : "className"; assert memberName != null || methodSig == null : "methodSig without memberName"; assert sourceLine >= -1 : "sourceLine: " + sourceLine; @@ -79,6 +80,7 @@ this.sourceLine = sourceLine; this.queryId = queryId; this.castableTypeMap = castableTypeMap; + this.seedId = seedId; } public CastableTypeMap getCastableTypeMap() { @@ -107,6 +109,10 @@ return queryId; } + public int getSeedId() { + return seedId; + } + public int getSourceLine() { return sourceLine; } @@ -161,6 +167,7 @@ symbolName = in.readUTF(); queryId = in.readInt(); castableTypeMap = (CastableTypeMap) in.readObject(); + seedId = in.readInt(); } /** @@ -189,5 +196,6 @@ out.writeUTF(symbolName); out.writeInt(queryId); out.writeObject(castableTypeMap); + out.writeInt(seedId); } }
diff --git a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java index 47906e4..a32d252 100644 --- a/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java +++ b/dev/core/src/com/google/gwt/dev/cfg/StaticPropertyOracle.java
@@ -22,9 +22,6 @@ import com.google.gwt.core.ext.TreeLogger; import java.io.Serializable; -import java.util.List; -import java.util.Map; -import java.util.Set; import java.util.TreeSet; /** @@ -155,7 +152,7 @@ possibleValues.add(v); } return new DefaultSelectionProperty(value, prop.getFallback(), name, - possibleValues, (Map<String, List<Set<String>>>) prop.getFallbackValuesMap()); + possibleValues, prop.getFallbackValuesMap()); } }
diff --git a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java index ee1c256..ea413fb 100644 --- a/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java +++ b/dev/core/src/com/google/gwt/dev/javac/testing/impl/JavaResourceBase.java
@@ -302,11 +302,12 @@ StringBuilder code = new StringBuilder(); code.append("package java.lang;\n"); code.append("public class Object {\n"); + code.append(" private Class<?> ___clazz;"); code.append(" public boolean equals(Object that){return this == that;}"); code.append(" public int hashCode() { return 0; }\n"); code.append(" public String toString() { return \"Object\"; }\n"); code.append(" public Object clone() { return this; } "); - code.append(" public Class<?> getClass() { return Object.class; } "); + code.append(" public Class<?> getClass() { return ___clazz; } "); code.append("}\n"); return code; }
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 bac3201..67d86c7 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java +++ b/dev/core/src/com/google/gwt/dev/jjs/JavaToJavaScriptCompiler.java
@@ -99,6 +99,7 @@ import com.google.gwt.dev.jjs.impl.Pruner; import com.google.gwt.dev.jjs.impl.RecordRebinds; import com.google.gwt.dev.jjs.impl.RemoveEmptySuperCalls; +import com.google.gwt.dev.jjs.impl.ReplaceGetClassOverrides; import com.google.gwt.dev.jjs.impl.ReplaceRebinds; import com.google.gwt.dev.jjs.impl.ReplaceRunAsyncs; import com.google.gwt.dev.jjs.impl.ResolveRebinds; @@ -307,6 +308,8 @@ // (6) Perform further post-normalization optimizations // Prune everything Pruner.exec(jprogram, false); + // prune all Object.getClass() overrides and replace with inline field ref + ReplaceGetClassOverrides.exec(jprogram); // (7) Generate a JavaScript code DOM from the Java type declarations jprogram.typeOracle.recomputeAfterOptimizations(); @@ -335,7 +338,7 @@ /* * Creates new variables, must run before code splitter and namer. */ - JsStackEmulator.exec(jsProgram, propertyOracles); + JsStackEmulator.exec(jprogram, jsProgram, propertyOracles, jjsmap); /* * Work around Safari 5 bug by rewriting a >> b as ~~a >> b.
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 dfb9119..63351a5 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
@@ -64,6 +64,21 @@ "com.google.gwt.lang.CollapsedPropertyHolder", "com.google.gwt.lang.Exceptions", "com.google.gwt.lang.LongLib", "com.google.gwt.lang.Stats", "com.google.gwt.lang.Util")); + /* + * 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 = new LinkedHashSet<String>(Arrays.asList( + "com.google.gwt.lang.SeedUtil")); + public static final Set<String> INDEX_TYPES_SET = new LinkedHashSet<String>(Arrays.asList( "java.io.Serializable", "java.lang.Object", "java.lang.String", "java.lang.Class", "java.lang.CharSequence", "java.lang.Cloneable", "java.lang.Comparable", "java.lang.Enum", @@ -98,6 +113,7 @@ new HashMap<String, JPrimitiveType>(); static { + CODEGEN_TYPES_SET.addAll(IMMORTAL_CODEGEN_TYPES_SET); INDEX_TYPES_SET.addAll(CODEGEN_TYPES_SET); /* @@ -302,6 +318,7 @@ } public final List<JClassType> codeGenTypes = new ArrayList<JClassType>(); + public final List<JClassType> immortalCodeGenTypes = new ArrayList<JClassType>(); public final JTypeOracle typeOracle = new JTypeOracle(this); @@ -314,6 +331,8 @@ private IdentityHashMap<JReferenceType, JsCastMap> castMaps; + private Map<JType, JField> classLiteralFields; + /** * A factory to create correlations. */ @@ -391,6 +410,11 @@ if (CODEGEN_TYPES_SET.contains(name)) { codeGenTypes.add((JClassType) type); } + + if (IMMORTAL_CODEGEN_TYPES_SET.contains(name)) { + immortalCodeGenTypes.add((JClassType) type); + } + if (INDEX_TYPES_SET.contains(name)) { indexedTypes.put(type.getShortName(), type); for (JMethod method : type.getMethods()) { @@ -730,6 +754,10 @@ return castMaps.get(referenceType); } + public JField getClassLiteralField(JType type) { + return classLiteralFields.get(isJavaScriptObject(type) ? getJavaScriptObject() : type); + } + public String getClassLiteralName(JType type) { return type.getJavahSignatureName() + "_classLit"; } @@ -1011,6 +1039,10 @@ } } + public void recordClassLiteralFields(Map<JType, JField> classLiteralFields) { + this.classLiteralFields = classLiteralFields; + } + public void recordQueryIds(Map<JReferenceType, Integer> queryIdsByType, List<JReferenceType> typesByQueryId) { this.queryIdsByType = queryIdsByType;
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JSeedIdOf.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JSeedIdOf.java new file mode 100644 index 0000000..ab1ca95 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JSeedIdOf.java
@@ -0,0 +1,36 @@ +/* + * Copyright 2011 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.jjs.SourceInfo; + +/** + * An AST node whose evaluation results in the seedId of its node. + */ +public class JSeedIdOf extends JNameOf { + + public JSeedIdOf(SourceInfo info, JClassType stringType, HasName node) { + super(info, stringType, node); + } + + public void traverse(JVisitor visitor, Context ctx) { + if (visitor.visit(this, ctx)) { + // Intentionally not visiting referenced node + } + visitor.endVisit(this, ctx); + } + +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java index b9095eb..8850cb6 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JTypeOracle.java
@@ -565,6 +565,10 @@ return results; } + public Set<JReferenceType> getInstantiatedTypes() { + return instantiatedTypes; + } + public JMethod getPolyMethod(JClassType type, String signature) { return getOrCreatePolyMap(type).get(signature); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java index 885d8e7..8e17e97 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/ast/JVisitor.java
@@ -417,6 +417,10 @@ endVisit((JExpression) x, ctx); } + public void endVisit(JSeedIdOf x, Context ctx) { + endVisit((JNameOf) x, ctx); + } + public void endVisit(JsCastMap x, Context ctx) { endVisit((JsonArray) x, ctx); } @@ -741,6 +745,10 @@ return visit((JExpression) x, ctx); } + public boolean visit(JSeedIdOf x, Context ctx) { + return visit((JNameOf) x, ctx); + } + public boolean visit(JsCastMap x, Context ctx) { return visit((JsonArray) x, ctx); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java index 7730084..4c85a0a 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/CodeSplitter.java
@@ -24,6 +24,7 @@ import com.google.gwt.dev.jjs.ast.Context; import com.google.gwt.dev.jjs.ast.JArrayType; import com.google.gwt.dev.jjs.ast.JClassLiteral; +import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JExpression; import com.google.gwt.dev.jjs.ast.JField; @@ -454,7 +455,7 @@ cfa.setDependencyRecorder(dependencyRecorder); cfa.traverseEntryMethods(); traverseClassArray(jprogram, cfa); - + traverseImmortalTypes(jprogram, cfa); dependencyRecorder.endDependencyGraph(); return cfa; } @@ -580,6 +581,21 @@ } } + /** + * Any immortal codegen types must be part of the initial download. + */ + private static void traverseImmortalTypes(JProgram jprogram, + ControlFlowAnalyzer cfa) { + for (JClassType type : jprogram.immortalCodeGenTypes) { + cfa.traverseFromInstantiationOf(type); + for (JMethod method : type.getMethods()) { + if (!method.needsVtable()) { + cfa.traverseFrom(method); + } + } + } + } + private static <T> Set<T> union(Set<? extends T> set1, Set<? extends T> set2) { Set<T> union = new HashSet<T>(); union.addAll(set1);
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 36c534f..f9b3aaa 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
@@ -518,6 +518,16 @@ return false; } + private void maybeRescueClassLiteral(JReferenceType type) { + if (liveFieldsAndMethods.contains(getClassMethod) || liveFieldsAndMethods.contains(getClassField)) { + // getClass() already live so rescue class literal immediately + rescue(program.getClassLiteralField(type)); + } else { + // getClass() not live yet, so mark for later rescue + classLiteralsToBeRescuedIfGetClassIsLive.add(type); + } + } + /** * Subclasses of JavaScriptObject are never instantiated directly. They are * implicitly created when a JSNI method passes a reference to an existing @@ -574,6 +584,9 @@ maybeRescueJavaScriptObjectPassingIntoJava(method.getType()); } rescueOverridingMethods(method); + if (method == getClassMethod) { + rescueClassLiteralsIfGetClassIsLive(); + } return true; } } @@ -594,6 +607,8 @@ boolean doVisit = false; if (isInstantiated && !instantiatedTypes.contains(type)) { instantiatedTypes.add(type); + maybeRescueClassLiteral(type); + doVisit = true; } @@ -623,6 +638,10 @@ if (var != null) { if (liveFieldsAndMethods.add(var)) { membersToRescueIfTypeIsInstantiated.remove(var); + if (var == getClassField) { + rescueClassLiteralsIfGetClassIsLive(); + } + if (isStaticFieldInitializedToLiteral(var)) { /* * Rescue literal initializers when the field is rescued, not when @@ -746,6 +765,18 @@ } } + private void rescueClassLiteralsIfGetClassIsLive() { + if (classLiteralsToBeRescuedIfGetClassIsLive != null) { + // guard against re-entrant calls. This only needs to run once. + Set<JReferenceType> toRescue = classLiteralsToBeRescuedIfGetClassIsLive; + classLiteralsToBeRescuedIfGetClassIsLive = null; + + for (JReferenceType classLit : toRescue) { + maybeRescueClassLiteral(classLit); + } + } + } + /** * If the type is instantiable, rescue any of its virtual methods that a * previously seen method call could call. @@ -807,6 +838,13 @@ private final JMethod asyncFragmentOnLoad; private final JDeclaredType baseArrayType; + + /** + * Schrodinger set of classLiterals to be rescued if type is instantiated AND getClass() + * is live. + */ + private Set<JReferenceType> classLiteralsToBeRescuedIfGetClassIsLive = new HashSet<JReferenceType>(); + private DependencyRecorder dependencyRecorder; private Set<JField> fieldsWritten = new HashSet<JField>(); private Set<JReferenceType> instantiatedTypes = new HashSet<JReferenceType>(); @@ -827,6 +865,8 @@ */ private Map<JMethod, List<JMethod>> methodsThatOverrideMe; + private final JField getClassField; + private final JMethod getClassMethod; private final JProgram program; private Set<JReferenceType> referencedTypes = new HashSet<JReferenceType>(); private final RescueVisitor rescuer = new RescueVisitor(); @@ -851,6 +891,8 @@ new HashMap<JParameter, List<JExpression>>(cfa.argsToRescueIfParameterRead); } methodsThatOverrideMe = cfa.methodsThatOverrideMe; + getClassField = program.getIndexedField("Object.___clazz"); + getClassMethod = program.getIndexedMethod("Object.getClass"); } public ControlFlowAnalyzer(JProgram program) { @@ -858,6 +900,8 @@ asyncFragmentOnLoad = program.getIndexedMethod("AsyncFragmentLoader.onLoad"); runAsyncOnsuccess = program.getIndexedMethod("RunAsyncCallback.onSuccess"); baseArrayType = program.getIndexedType("Array"); + getClassField = program.getIndexedField("Object.___clazz"); + getClassMethod = program.getIndexedMethod("Object.getClass"); buildMethodsOverriding(); } @@ -964,6 +1008,15 @@ runAsync.traverseOnSuccess(rescuer); } + /** + * Traverse the fragments for all runAsyncs. + */ + public void traverseFromRunAsyncs() { + for (JRunAsync runAsync : program.getRunAsyncs()) { + traverseFromRunAsync(runAsync); + } + } + private void buildMethodsOverriding() { methodsThatOverrideMe = new HashMap<JMethod, List<JMethod>>(); for (JDeclaredType type : program.getDeclaredTypes()) { @@ -979,13 +1032,4 @@ } } } - - /** - * Traverse the fragments for all runAsyncs. - */ - private void traverseFromRunAsyncs() { - for (JRunAsync runAsync : program.getRunAsyncs()) { - traverseFromRunAsync(runAsync); - } - } }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java index a522b6e..8d75fc9 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/FragmentExtractor.java
@@ -31,15 +31,15 @@ import com.google.gwt.dev.js.ast.JsExpression; import com.google.gwt.dev.js.ast.JsFunction; import com.google.gwt.dev.js.ast.JsInvocation; +import com.google.gwt.dev.js.ast.JsModVisitor; import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsNameRef; -import com.google.gwt.dev.js.ast.JsNew; import com.google.gwt.dev.js.ast.JsNumberLiteral; -import com.google.gwt.dev.js.ast.JsObjectLiteral; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.js.ast.JsVars; import com.google.gwt.dev.js.ast.JsVars.JsVar; +import com.google.gwt.dev.js.ast.JsVisitable; import java.util.ArrayList; import java.util.Collections; @@ -238,21 +238,31 @@ * The type whose vtables can currently be installed. */ JClassType currentVtableType = null; + JClassType pendingVtableType = null; + JsExprStmt pendingDefineSeed = null; - // Since we haven't run yet. + + // Since we haven't run yet. assert jsprogram.getFragmentCount() == 1; + List<JsStatement> stats = jsprogram.getGlobalBlock().getStatements(); for (JsStatement stat : stats) { + boolean keepIt; JClassType vtableTypeAssigned = vtableTypeAssigned(stat); - if (vtableTypeAssigned != null && livenessPredicate.isLive(vtableTypeAssigned)) { - JsExprStmt result = - extractPrototypeSetup(livenessPredicate, alreadyLoadedPredicate, stat, - vtableTypeAssigned); - if (result != null) { + if (vtableTypeAssigned != null + && livenessPredicate.isLive(vtableTypeAssigned)) { + boolean[] anyCtorsSetup = new boolean[1]; + JsExprStmt result = maybeRemoveCtorsFromDefineSeedStmt(livenessPredicate, + alreadyLoadedPredicate, stat, anyCtorsSetup); + boolean anyWorkDone = anyCtorsSetup[0] + || !alreadyLoadedPredicate.isLive(vtableTypeAssigned); + if (anyWorkDone) { stat = result; keepIt = true; } else { + pendingDefineSeed = result; + pendingVtableType = vtableTypeAssigned; keepIt = false; } } else if (containsRemovableVars(stat)) { @@ -270,8 +280,11 @@ } JClassType vtableType = vtableTypeNeeded(stat); if (vtableType != null && vtableType != currentVtableType) { - extractedStats.add(vtableStatFor(vtableType)); - currentVtableType = vtableType; + assert pendingVtableType == vtableType; + extractedStats.add(pendingDefineSeed); + currentVtableType = pendingVtableType; + pendingDefineSeed = null; + pendingVtableType = null; } extractedStats.add(stat); } @@ -323,57 +336,6 @@ return false; } - /** - * Weird case: the seed function's liveness is associated with the type - * itself. However, individual constructors can have a liveness that is a - * subset of the type's liveness. We essentially have to break up the - * prototype chain according to exactly what's newly live. - */ - private JsExprStmt extractPrototypeSetup(final LivenessPredicate livenessPredicate, - final LivenessPredicate alreadyLoadedPredicate, JsStatement stat, - final JClassType vtableTypeAssigned) { - final boolean[] anyLiveCode = new boolean[1]; - Cloner c = new Cloner() { - @Override - public void endVisit(JsBinaryOperation x, JsContext ctx) { - JsExpression rhs = stack.pop(); - JsNameRef lhs = (JsNameRef) stack.pop(); - if (rhs instanceof JsNew || rhs instanceof JsObjectLiteral) { - // The super op is being assigned to the seed prototype. - if (alreadyLoadedPredicate.isLive(vtableTypeAssigned)) { - stack.push(lhs); - return; - } else { - anyLiveCode[0] = true; - } - } else if (lhs.getQualifier() == null) { - // The underscore is being assigned to. - assert "_".equals(lhs.getIdent()); - } else { - // A constructor function is being assigned to. - assert "prototype".equals(lhs.getIdent()); - JsNameRef ctorRef = (JsNameRef) lhs.getQualifier(); - JConstructor ctor = (JConstructor) map.nameToMethod(ctorRef.getName()); - assert ctor != null; - if (livenessPredicate.isLive(ctor) && !alreadyLoadedPredicate.isLive(ctor)) { - anyLiveCode[0] = true; - } else { - stack.push(rhs); - return; - } - } - - JsBinaryOperation toReturn = new JsBinaryOperation(x.getSourceInfo(), x.getOperator()); - toReturn.setArg2(rhs); - toReturn.setArg1(lhs); - stack.push(toReturn); - } - }; - c.accept(((JsExprStmt) stat).getExpression()); - JsExprStmt result = anyLiveCode[0] ? c.getExpression().makeStmt() : null; - return result; - } - private boolean isLive(JsStatement stat, LivenessPredicate livenessPredicate) { JClassType type = map.typeForStatement(stat); if (type != null) { @@ -420,6 +382,42 @@ } /** + * Weird case: the seed function's liveness is associated with the type + * itself. However, individual constructors can have a liveness that is a + * subset of the type's liveness. + */ + private JsExprStmt maybeRemoveCtorsFromDefineSeedStmt( + final LivenessPredicate livenessPredicate, + final LivenessPredicate alreadyLoadedPredicate, JsStatement stat, + final boolean[] anyCtorsSetup) { + Cloner c = new Cloner(); + c.accept(((JsExprStmt) stat).getExpression()); + JsExprStmt result = c.getExpression().makeStmt(); + new JsModVisitor() { + public void endVisit(JsNameRef x, JsContext ctx) { + JMethod maybeCtor = map.nameToMethod(x.getName()); + if (maybeCtor instanceof JConstructor) { + JConstructor ctor = (JConstructor) maybeCtor; + if (!livenessPredicate.isLive(ctor) + || alreadyLoadedPredicate.isLive(ctor)) { + ctx.removeMe(); + } else { + anyCtorsSetup[0] = true; + } + } + }; + + /** + * Overridden to allow insert/remove on the varargs portion. + */ + protected <T extends JsVisitable> void doAcceptList(List<T> collection) { + doAcceptWithInsertRemove(collection); + }; + }.accept(result); + return result; + } + + /** * Return the Java method corresponding to <code>stat</code>, or * <code>null</code> if there isn't one. It recognizes JavaScript of the form * <code>function foo(...) { ...}</code>, where <code>foo</code> is the name @@ -465,40 +463,30 @@ } /** - * Compute a statement that can be used to set up for installing instance - * methods into a vtable. It will be of the form - * <code>_ = foo.prototype</code>, where <code>foo</code> is the constructor - * function for <code>vtableType</code>. - */ - private JsStatement vtableStatFor(JClassType vtableType) { - JsNameRef prototypeField = - new JsNameRef(jsprogram.createSourceInfoSynthetic(FragmentExtractor.class), "prototype"); - JsExpression constructorRef; - SourceInfo sourceInfoVtableSetup = jsprogram.createSourceInfoSynthetic(FragmentExtractor.class); - if (vtableType == jprogram.getTypeJavaLangString()) { - // The methods of java.lang.String are put onto JavaScript's String - // prototype - SourceInfo sourceInfoConstructorRef = - jsprogram.createSourceInfoSynthetic(FragmentExtractor.class); - constructorRef = new JsNameRef(sourceInfoConstructorRef, "String"); - } else { - constructorRef = map.nameForType(vtableType).makeRef(sourceInfoVtableSetup); - } - prototypeField.setQualifier(constructorRef); - SourceInfo underlineSourceInfo = jsprogram.createSourceInfoSynthetic(FragmentExtractor.class); - return (new JsBinaryOperation(sourceInfoVtableSetup, JsBinaryOperator.ASG, jsprogram.getScope() - .declareName("_").makeRef(underlineSourceInfo), prototypeField)).makeStmt(); - } - - /** - * If <code>state</code> is of the form <code>_ = Foo.prototype = exp</code>, - * then return <code>Foo</code>. Otherwise return <code>null</code>. + * If <code>state</code> is of the form <code>_ = String.prototype</code>, + * then return <code>String</code>. If the form is + * <code>defineSeed(id, superId, cTM, ctor1, ctor2, ...)</code> return the type + * corresponding to that id. Otherwise return <code>null</code>. */ private JClassType vtableTypeAssigned(JsStatement stat) { if (!(stat instanceof JsExprStmt)) { return null; } JsExprStmt expr = (JsExprStmt) stat; + if (expr.getExpression() instanceof JsInvocation) { + // Handle a defineSeed call. + JsInvocation call = (JsInvocation) expr.getExpression(); + if (!(call.getQualifier() instanceof JsNameRef)) { + return null; + } + JsNameRef func = (JsNameRef) call.getQualifier(); + if (func.getName() != jsprogram.getIndexedFunction("SeedUtil.defineSeed").getName()) { + return null; + } + return map.typeForStatement(stat); + } + + // Handle String. if (!(expr.getExpression() instanceof JsBinaryOperation)) { return null; } @@ -510,30 +498,26 @@ return null; } JsNameRef lhs = (JsNameRef) binExpr.getArg1(); - if (lhs.getQualifier() != null) { + JsName underBar = jsprogram.getScope().findExistingName("_"); + assert underBar != null; + if (lhs.getName() != underBar) { return null; } - if (lhs.getName() == null) { - return null; - } - if (!lhs.getName().getShortIdent().equals("_")) { - return null; - } - if (!(binExpr.getArg2() instanceof JsBinaryOperation)) { - return null; - } - JsBinaryOperation binExprRhs = (JsBinaryOperation) binExpr.getArg2(); - if (binExprRhs.getOperator() != JsBinaryOperator.ASG) { - return null; - } - if (!(binExprRhs.getArg1() instanceof JsNameRef)) { - return null; - } - JsNameRef middleNameRef = (JsNameRef) binExprRhs.getArg1(); - if (!middleNameRef.getName().getShortIdent().equals("prototype")) { + if (!(binExpr.getArg2() instanceof JsNameRef)) { return null; } + JsNameRef rhsRef = (JsNameRef) binExpr.getArg2(); + if (!(rhsRef.getQualifier() instanceof JsNameRef)) { + return null; + } + if (!((JsNameRef) rhsRef.getQualifier()).getShortIdent().equals("String")) { + return null; + } + + if (!rhsRef.getName().getShortIdent().equals("prototype")) { + return null; + } return map.typeForStatement(stat); }
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java index 87a6aa7..3c4883f 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/GenerateJavaAST.java
@@ -514,14 +514,17 @@ // Just use JavaScriptObject's implementation for all subclasses. currentClass.getMethods().remove(2); } else { - tryFindUpRefs(method); - SourceInfo info = method.getSourceInfo(); - if (isScript(program) && currentClass == program.getIndexedType("Array")) { - // Special implementation: return this.arrayClass - implementMethod(method, new JFieldRef(info, new JThisRef(info, - (JClassType) currentClass), program.getIndexedField("Array.arrayClass"), - currentClass)); + if (currentClass == program.getIndexedType("Array")) { + /* + * Don't implement, fall through to Object.getClass(). Array emulation code + * in com.google.gwt.lang.Array invokes Array.getClass() and expects to get the + * class literal for the actual runtime type of the array (e.g. Foo[].class) and + * not Array.class. + */ + currentClass.getMethods().remove(2); } else { + tryFindUpRefs(method); + SourceInfo info = method.getSourceInfo(); implementMethod(method, new JClassLiteral(info.makeChild(), currentClass)); } }
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 8cfa63e..c599720 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
@@ -76,6 +76,7 @@ import com.google.gwt.dev.jjs.ast.JReboundEntryPoint; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; +import com.google.gwt.dev.jjs.ast.JSeedIdOf; import com.google.gwt.dev.jjs.ast.JStatement; import com.google.gwt.dev.jjs.ast.JSwitchStatement; import com.google.gwt.dev.jjs.ast.JThisRef; @@ -138,6 +139,7 @@ import com.google.gwt.dev.js.ast.JsReturn; import com.google.gwt.dev.js.ast.JsRootScope; import com.google.gwt.dev.js.ast.JsScope; +import com.google.gwt.dev.js.ast.JsSeedIdOf; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.js.ast.JsSwitch; import com.google.gwt.dev.js.ast.JsSwitchMember; @@ -388,7 +390,7 @@ * as var foo = function blah() {} and introduce a separate scope for * the function's name according to EcmaScript-262, but this would mess * up stack traces by allowing two inner scope function names to - * onfuscate to the same identifier, making function names no longer a + * obfuscate to the same identifier, making function names no longer a * 1:1 mapping to obfuscated symbols. Leaving them in global scope * causes no harm. */ @@ -405,6 +407,7 @@ Maps.put(indexedFunctions, x.getEnclosingType().getShortName() + "." + x.getName(), jsFunction); } + return true; } @@ -499,7 +502,8 @@ StandardSymbolData symbolData = StandardSymbolData.forClass(x.getName(), x.getSourceInfo().getFileName(), x - .getSourceInfo().getStartLine(), program.getQueryId(x), castableTypeMap); + .getSourceInfo().getStartLine(), program.getQueryId(x), castableTypeMap, + x instanceof JClassType || x instanceof JArrayType ? getSeedId(x) : -1); assert !symbolTable.containsKey(symbolData); symbolTable.put(symbolData, jsName); } @@ -568,6 +572,7 @@ JsName name = topScope.declareName("Q$" + longName, "Q$" + shortName); namesByQueryId.add(name); } + // TODO(cromwellian): see about moving this into an immortal type StringBuilder sb = new StringBuilder(); sb.append("function makeCastMap(a) {"); sb.append(" var result = {};"); @@ -607,12 +612,16 @@ private final JsName prototype = objectScope.declareName("prototype"); + { globalTemp.setObfuscatable(false); prototype.setObfuscatable(false); arrayLength.setObfuscatable(false); } + public GenerateJavaScriptVisitor() { + } + @Override public void endVisit(JAbsentArrayDimension x, Context ctx) { throw new InternalCompilerException("Should not get here."); @@ -719,6 +728,11 @@ return; } + if (program.immortalCodeGenTypes.contains(x)) { + // Handled in generateImmortalTypes + return; + } + alreadyRan.add(x); List<JsFunction> jsFuncs = popList(x.getMethods().size()); // methods @@ -738,6 +752,7 @@ // declare all methods into the global scope for (int i = 0; i < jsFuncs.size(); ++i) { JsFunction func = jsFuncs.get(i); + // don't add polymorphic JsFuncs, inline decl into vtable assignment if (func != null && !polymorphicJsFunctions.contains(func)) { globalStmts.add(func.makeStmt()); @@ -1260,7 +1275,7 @@ // Long lits must go at the top, they can be constant field initializers. generateLongLiterals(vars); - + generateImmortalTypes(vars); generateQueryIdConstants(vars); // Class objects, but only if there are any. @@ -1298,6 +1313,12 @@ } @Override + public void endVisit(JSeedIdOf x, Context ctx) { + JsName name = names.get(x.getNode()); + push(new JsSeedIdOf(x.getSourceInfo(), name, getSeedId((JReferenceType) x.getNode()))); + } + + @Override public void endVisit(JsCastMap x, Context ctx) { super.endVisit(x, ctx); JsArrayLiteral arrayLit = (JsArrayLiteral) pop(); @@ -1418,6 +1439,11 @@ return false; } + if (program.immortalCodeGenTypes.contains(x)) { + // Handled in generateImmortalTypes + return false; + } + // force super type to generate code first, this is required for prototype // chaining to work properly if (x.getSuperClass() != null && !alreadyRan.contains(x)) { @@ -1454,6 +1480,7 @@ for (int i = 0; i < entryMethods.size(); i++) { entryMethodToIndex.put(entryMethods.get(i), i); } + return true; } @@ -1647,27 +1674,20 @@ return new JsBinaryOperation(lhs.getSourceInfo(), JsBinaryOperator.COMMA, lhs, rhs); } - private void generateCastableTypeMap(JClassType x, List<JsStatement> globalStmts) { + private JsExpression generateCastableTypeMap(JClassType x) { JsCastMap castMap = program.getCastMap(x); if (castMap != null) { JField castableTypeMapField = program.getIndexedField("Object.castableTypeMap"); JsName castableTypeMapName = names.get(castableTypeMapField); if (castableTypeMapName == null) { // Was pruned; this compilation must have no dynamic casts. - return; + return new JsObjectLiteral(SourceOrigin.UNKNOWN); } - // Generate castableTypeMap for each type prototype - // _.castableTypeMap$ = ... - SourceInfo sourceInfo = jsProgram.createSourceInfoSynthetic(GenerateJavaScriptAST.class); - JsNameRef fieldRef = castableTypeMapName.makeRef(sourceInfo); - fieldRef.setQualifier(globalTemp.makeRef(sourceInfo)); accept(castMap); - JsExpression asg = createAssignment(fieldRef, (JsExpression) pop()); - JsExprStmt asgStmt = asg.makeStmt(); - globalStmts.add(asgStmt); - typeForStatMap.put(asgStmt, x); + return (JsExpression) pop(); } + return new JsObjectLiteral(SourceOrigin.UNKNOWN); } private void generateClassLiteral(JDeclarationStatement decl, JsVars vars) { @@ -1704,8 +1724,6 @@ // special: setup the identifying typeMarker field generateTypeMarker(globalStmts); } - - generateCastableTypeMap(x, globalStmts); } private void generateGwtOnLoad(List<JsFunction> entryFuncs, List<JsStatement> globalStmts) { @@ -1806,6 +1824,78 @@ errCall.getArguments().add(modName.makeRef(sourceInfo)); } + private void generateImmortalTypes(JsVars globals) { + List<JsStatement> globalStmts = jsProgram.getGlobalBlock().getStatements(); + List<JClassType> immortalTypes = new ArrayList<JClassType>( + program.immortalCodeGenTypes); + // visit in reverse order since insertions start at head + Collections.reverse(immortalTypes); + JMethod createObjMethod = program.getIndexedMethod("JavaScriptObject.createObject"); + JMethod createArrMethod = program.getIndexedMethod("JavaScriptObject.createArray"); + + for (JClassType x : immortalTypes) { + // should not be pruned + assert x.getMethods().size() > 0; + // insert all static methods + for (JMethod method : x.getMethods()) { + /* + * Skip virtual methods and constructors. Even in cases where there is no constructor + * defined, the compiler will synthesize a default constructor which invokes + * a synthensized $init() method. We must skip both of these inserted methods. + */ + if (method.needsVtable() || method instanceof JConstructor) { + continue; + } + if (JProgram.isClinit(method)) { + /** + * Emit empty clinits that will be pruned. If a type B extends A, then even if + * B and A have no fields to initialize, there will be a call inserted in B's clinit + * to invoke A's clinit. Likewise, if you have a static field initialized to + * JavaScriptObject.createObject(), the clinit() will include this initializer code, + * which we don't want. + */ + JsFunction func = new JsFunction(x.getSourceInfo(), topScope, + topScope.declareName(mangleNameForGlobal(method)), true); + func.setBody(new JsBlock(method.getBody().getSourceInfo())); + push(func); + } else { + accept(method); + } + // add after var declaration, but before everything else + JsFunction func = (JsFunction) pop(); + assert func.getName() != null; + globalStmts.add(1, func.makeStmt()); + } + + // insert fields into global var declaration + for (JField field : x.getFields()) { + assert field.isStatic() : "All fields on immortal types must be static."; + accept(field); + JsNode node = pop(); + assert node instanceof JsVar; + JsVar fieldVar = (JsVar) node; + JExpression init = field.getInitializer(); + if (init != null + && field.getLiteralInitializer() == null) { + // no literal, but it could be a JavaScriptObject + if (init.getType() == program.getJavaScriptObject()) { + assert init instanceof JMethodCall; + JMethod meth = ((JMethodCall) init).getTarget(); + // immortal types can only have non-primitive literal initializers of createArray,createObject + if (meth == createObjMethod) { + fieldVar.setInitExpr(new JsObjectLiteral(init.getSourceInfo())); + } else if (meth == createArrMethod) { + fieldVar.setInitExpr(new JsArrayLiteral(init.getSourceInfo())); + } else { + assert false : "Illegal initializer expression for immortal field " + field; + } + } + } + globals.add(fieldVar); + } + } + } + private void generateLongLiterals(JsVars vars) { for (Entry<Long, JsName> entry : longLits.entrySet()) { JsName jsName = entry.getValue(); @@ -1831,44 +1921,31 @@ private void generateSeedFuncAndPrototype(JClassType x, List<JsStatement> globalStmts) { SourceInfo sourceInfo = x.getSourceInfo(); if (x != program.getTypeJavaLangString()) { - JsName seedFuncName = names.get(x); - - // seed function - // function com_example_foo_Foo() { } - JsFunction seedFunc = new JsFunction(sourceInfo, topScope, seedFuncName, true); - seedFuncName.setStaticRef(seedFunc); - JsBlock body = new JsBlock(sourceInfo); - seedFunc.setBody(body); - JsExprStmt seedFuncStmt = seedFunc.makeStmt(); - globalStmts.add(seedFuncStmt); - typeForStatMap.put(seedFuncStmt, x); - - // Setup prototype chain. - // _ = Foo__V.prototype = FooSeed.prototype = new FooSuper(); - JsNameRef seedProtoRef = prototype.makeRef(sourceInfo); - seedProtoRef.setQualifier(seedFuncName.makeRef(sourceInfo)); - JsExpression protoObj; - if (x.getSuperClass() != null) { - JsNameRef superPrototypeRef = names.get(x.getSuperClass()).makeRef(sourceInfo); - JsNew newExpr = new JsNew(sourceInfo, superPrototypeRef); - protoObj = newExpr; - } else { - protoObj = new JsObjectLiteral(sourceInfo); - } - JsExpression protoAsg = createAssignment(seedProtoRef, protoObj); - + JsInvocation defineSeed = new JsInvocation(x.getSourceInfo()); + JsName seedNameRef = indexedFunctions.get( + "SeedUtil.defineSeed").getName(); + defineSeed.setQualifier(seedNameRef.makeRef(x.getSourceInfo())); + int newSeed = getSeedId(x); + assert newSeed > 0; + JClassType superClass = x.getSuperClass(); + int superSeed = (superClass == null) ? -1 : getSeedId(x.getSuperClass()); + // SeedUtil.defineSeed(queryId, superId, castableMap, constructors) + defineSeed.getArguments().add(new JsNumberLiteral(x.getSourceInfo(), + newSeed)); + defineSeed.getArguments().add(new JsNumberLiteral(x.getSourceInfo(), + superSeed)); + JsExpression castMap = generateCastableTypeMap(x); + defineSeed.getArguments().add(castMap); + // Chain assign the same prototype to every live constructor. for (JMethod method : x.getMethods()) { if (liveCtors.contains(method)) { - JsNameRef protoRef = prototype.makeRef(sourceInfo); - protoRef.setQualifier(names.get(method).makeRef(sourceInfo)); - protoAsg = createAssignment(protoRef, protoAsg); + defineSeed.getArguments().add(names.get(method).makeRef( + sourceInfo)); } } - // Finally, assign to the temp var for setup code. - JsExpression tmpAsg = createAssignment(globalTemp.makeRef(sourceInfo), protoAsg); - JsExprStmt tmpAsgStmt = tmpAsg.makeStmt(); + JsStatement tmpAsgStmt = defineSeed.makeStmt(); globalStmts.add(tmpAsgStmt); typeForStatMap.put(tmpAsgStmt, x); } else { @@ -1883,6 +1960,16 @@ JsExprStmt tmpAsgStmt = tmpAsg.makeStmt(); globalStmts.add(tmpAsgStmt); typeForStatMap.put(tmpAsgStmt, x); + JField castableTypeMapField = program.getIndexedField("Object.castableTypeMap"); + JsName castableTypeMapName = names.get(castableTypeMapField); + JsNameRef ctmRef = castableTypeMapName.makeRef(sourceInfo); + ctmRef.setQualifier(globalTemp.makeRef(sourceInfo)); + JsExpression castMapLit = generateCastableTypeMap(x); + JsExpression ctmAsg = createAssignment(ctmRef, + castMapLit); + JsExprStmt ctmAsgStmt = ctmAsg.makeStmt(); + globalStmts.add(ctmAsgStmt); + typeForStatMap.put(ctmAsgStmt, x); } } @@ -2185,6 +2272,7 @@ private final Map<JAbstractMethodBody, JsFunction> methodBodyMap = new IdentityHashMap<JAbstractMethodBody, JsFunction>(); private final Map<HasName, JsName> names = new IdentityHashMap<HasName, JsName>(); + private int nextSeedId = 1; private List<JsName> namesByQueryId; private JsFunction nullFunc; @@ -2199,6 +2287,11 @@ private final JProgram program; /** + * Map of class type to allocated seed id. + */ + private final Map<JReferenceType, Integer> seedIdMap = new HashMap<JReferenceType, Integer>(); + + /** * All of the fields in String and Array need special handling for interop. */ private final Map<JField, String> specialObfuscatedFields = new HashMap<JField, String>(); @@ -2280,8 +2373,8 @@ namesToIdents.put("expando", "eX"); namesToIdents.put("typeMarker", "tM"); namesToIdents.put("castableTypeMap", "cM"); + namesToIdents.put("___clazz", "cZ"); // Array magic field - namesToIdents.put("arrayClass", "aC"); namesToIdents.put("queryId", "qI"); List<JField> fields = new ArrayList<JField>(program.getTypeJavaLangObject().getFields()); @@ -2293,6 +2386,11 @@ specialObfuscatedFields.put(field, ident); } } + + // force java.lang.Object,java.lang.String + // to have seed ids 1,2 + getSeedId(program.getTypeJavaLangObject()); + getSeedId(program.getTypeJavaLangString()); } String getNameString(HasName hasName) { @@ -2300,6 +2398,20 @@ return s; } + /** + * Looks up or assigns a seed id for a type.. + */ + int getSeedId(JReferenceType type) { + Integer val = seedIdMap.get(type); + int seedId = val == null ? 0 : val; + + if (seedId == 0) { + seedId = nextSeedId++; + seedIdMap.put(type, seedId); + } + return seedId; + } + String mangleName(JField x) { String s = getNameString(x.getEnclosingType()) + '_' + getNameString(x); return s;
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 ae2e645..831aa25 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
@@ -2158,15 +2158,13 @@ assert ("getClass".equals(method.getName())); SourceInfo info = method.getSourceInfo(); if ("com.google.gwt.lang.Array".equals(type.getName())) { - // Special implementation: return this.arrayClass - JField arrayClassField = null; - for (JField field : type.getFields()) { - if ("arrayClass".equals(field.getName())) { - arrayClassField = field; - } - } - assert arrayClassField != null; - implementMethod(method, new JFieldRef(info, makeThisRef(info), arrayClassField, type)); + /* + * Don't implement, fall through to Object.getClass(). Array emulation code + * in com.google.gwt.lang.Array invokes Array.getClass() and expects to get the + * class literal for the actual runtime type of the array (e.g. Foo[].class) and + * not Array.class. + */ + type.getMethods().remove(2); } else { implementMethod(method, new JClassLiteral(info, type)); } @@ -2767,8 +2765,7 @@ * * TODO(zundel): something much more awesome? */ - private static final long AST_VERSION = 2; - + private static final long AST_VERSION = 3; private static final char[] _STRING = "_String".toCharArray(); private static final String ARRAY_LENGTH_FIELD = "length";
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java index f525c3a..d13c0fc 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ImplementClassLiteralsAsFields.java
@@ -23,7 +23,6 @@ import com.google.gwt.dev.jjs.ast.JClassLiteral; import com.google.gwt.dev.jjs.ast.JClassType; import com.google.gwt.dev.jjs.ast.JDeclarationStatement; -import com.google.gwt.dev.jjs.ast.JDeclaredType; import com.google.gwt.dev.jjs.ast.JEnumType; import com.google.gwt.dev.jjs.ast.JField; import com.google.gwt.dev.jjs.ast.JField.Disposition; @@ -34,10 +33,10 @@ import com.google.gwt.dev.jjs.ast.JMethodBody; import com.google.gwt.dev.jjs.ast.JMethodCall; import com.google.gwt.dev.jjs.ast.JModVisitor; -import com.google.gwt.dev.jjs.ast.JNameOf; import com.google.gwt.dev.jjs.ast.JNullLiteral; import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.ast.JSeedIdOf; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.js.JsniMethodRef; @@ -58,15 +57,20 @@ * Ordinarily, accessing one of these fields would trigger a clinit to run, but * we've special-cased class literal fields to evaluate as top-level code before * the application starts running to avoid the clinit. + * + * Class literal factory methods are responsible for installing references + * to themselves on the Object.clazz field of their JS runtime prototype + * since getClass() is no longer an overridden method. Prototypes can be + * looked up via 'seedId' from the global seedTable object, and so each + * class literal factory method is passed the seedId of its type. * <p> - * TODO(cromwellian): consider lazy-initialization to improve startup time. */ public class ImplementClassLiteralsAsFields { private class NormalizeVisitor extends JModVisitor { @Override public void endVisit(JClassLiteral x, Context ctx) { - JField field = resolveClassLiteralField(x); + JField field = resolveClassLiteralField(x.getRefType()); x.setField(field); } } @@ -120,8 +124,8 @@ * Class: * * <pre> - * Class.createForClass("java.lang.", "Object", /JNameOf/"java.lang.Object", null) - * Class.createForClass("java.lang.", "Exception", /JNameOf/"java.lang.Exception", Throwable.class) + * Class.createForClass("java.lang.", "Object", /JSeedIdOf/"java.lang.Object", null) + * Class.createForClass("java.lang.", "Exception", /JSeedIdOf/"java.lang.Exception", Throwable.class) * </pre> * * Interface: @@ -139,21 +143,21 @@ * Array: * * <pre> - * Class.createForArray("", "[I", /JNameOf/"com.google.gwt.lang.Array", int.class) - * Class.createForArray("[Lcom.example.", "Foo;", /JNameOf/"com.google.gwt.lang.Array", Foo.class) + * Class.createForArray("", "[I", /JSeedIdOf/"com.google.gwt.lang.Array", int.class) + * Class.createForArray("[Lcom.example.", "Foo;", /JSeedIdOf/"com.google.gwt.lang.Array", Foo.class) * </pre> * * Enum: * * <pre> - * Class.createForEnum("com.example.", "MyEnum", /JNameOf/"com.example.MyEnum", Enum.class, + * Class.createForEnum("com.example.", "MyEnum", /JSeedIdOf/"com.example.MyEnum", Enum.class, * public static MyEnum[] values(), public static MyEnum valueOf(String name)) * </pre> * * Enum subclass: * * <pre> - * Class.createForEnum("com.example.", "MyEnum$1", /JNameOf/"com.example.MyEnum$1", MyEnum.class, + * Class.createForEnum("com.example.", "MyEnum$1", /JSeedIdOf/"com.example.MyEnum$1", MyEnum.class, * null, null)) * </pre> */ @@ -182,15 +186,9 @@ JStringLiteral className = program.getLiteralString(info, getClassName(typeName)); call.addArgs(packageName, className); - if (type instanceof JArrayType) { - // There's only one seed function for all arrays - JDeclaredType arrayType = program.getIndexedType("Array"); - call.addArg(new JNameOf(info, program.getTypeJavaLangString(), arrayType)); - - } else if (type instanceof JClassType) { + if (type instanceof JArrayType || type instanceof JClassType) { // Add the name of the seed function for concrete types - call.addArg(new JNameOf(info, program.getTypeJavaLangString(), type)); - + call.addArg(new JSeedIdOf(info, program.getTypeJavaLangString(), type)); } else if (type instanceof JPrimitiveType) { // And give primitive types an illegal, though meaningful, value call.addArg(program.getLiteralString(info, " " + type.getJavahSignatureName())); @@ -256,7 +254,7 @@ private JClassLiteral createDependentClassLiteral(SourceInfo info, JType type) { JClassLiteral classLiteral = new JClassLiteral(info.makeChild(), type); - JField field = resolveClassLiteralField(classLiteral); + JField field = resolveClassLiteralField(classLiteral.getRefType()); classLiteral.setField(field); return classLiteral; } @@ -264,6 +262,7 @@ private void execImpl() { NormalizeVisitor visitor = new NormalizeVisitor(); visitor.accept(program); + program.recordClassLiteralFields(classLiteralFields); } private String getTypeName(JType type) { @@ -309,8 +308,7 @@ * } * </pre> */ - private JField resolveClassLiteralField(JClassLiteral classLiteral) { - JType type = classLiteral.getRefType(); + private JField resolveClassLiteralField(JType type) { type = normalizeJsoType(type); JField field = classLiteralFields.get(type); if (field == null) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java index 4375220..0594d53 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/Pruner.java
@@ -42,6 +42,7 @@ import com.google.gwt.dev.jjs.ast.JPrimitiveType; import com.google.gwt.dev.jjs.ast.JProgram; import com.google.gwt.dev.jjs.ast.JReferenceType; +import com.google.gwt.dev.jjs.ast.JSeedIdOf; import com.google.gwt.dev.jjs.ast.JType; import com.google.gwt.dev.jjs.ast.JVariable; import com.google.gwt.dev.jjs.ast.JVariableRef; @@ -205,6 +206,10 @@ } @Override + public void endVisit(JSeedIdOf x, Context ctx) { + } + + @Override public void endVisit(JsniFieldRef x, Context ctx) { if (isPruned(x.getField())) { String ident = x.getIdent(); @@ -599,13 +604,17 @@ ControlFlowAnalyzer livenessAnalyzer = new ControlFlowAnalyzer(program); livenessAnalyzer.setForPruning(); + + // SPECIAL: Immortal codegen types are never pruned + traverseTypes(livenessAnalyzer, program.immortalCodeGenTypes); + if (saveCodeGenTypes) { /* * SPECIAL: Some classes contain methods used by code generation later. * Unless those transforms have already been performed, we must rescue all * contained methods for later user. */ - traverseFromCodeGenTypes(livenessAnalyzer); + traverseTypes(livenessAnalyzer, program.codeGenTypes); } livenessAnalyzer.traverseEverything(); @@ -627,11 +636,11 @@ } /** - * Traverse from all methods in the program's code-gen types. See - * {@link JProgram#CODEGEN_TYPES_SET}. + * Traverse from all methods starting from a set of types. */ - private void traverseFromCodeGenTypes(ControlFlowAnalyzer livenessAnalyzer) { - for (JClassType type : program.codeGenTypes) { + private void traverseTypes(ControlFlowAnalyzer livenessAnalyzer, + List<JClassType> types) { + for (JClassType type : types) { livenessAnalyzer.traverseFromReferenceTo(type); for (JMethod method : type.getMethods()) { if (method instanceof JConstructor) {
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceGetClassOverrides.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceGetClassOverrides.java new file mode 100644 index 0000000..18152d0 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ReplaceGetClassOverrides.java
@@ -0,0 +1,71 @@ +/* + * Copyright 2011 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.JField; +import com.google.gwt.dev.jjs.ast.JFieldRef; +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.JProgram; + +/** + * Prune all overrides of Object.getClass() except when the enclosing class is JavaScriptObject + * (to permit getClass() devirtualization in JsoDevirtualizer to continue to work). + * Also Inline all method calls to Object.getClass() as Object.clazz. + */ +public class ReplaceGetClassOverrides { + public static void exec(JProgram program) { + new GetClassInlinerRemover(program).accept(program); + } + + private static class GetClassInlinerRemover extends JModVisitor { + + private JProgram program; + private JMethod getClassMethod; + private JField clazzField; + + public GetClassInlinerRemover(JProgram program) { + this.program = program; + getClassMethod = program.getIndexedMethod("Object.getClass"); + clazzField = program.getIndexedField("Object.___clazz"); + } + + public void endVisit(JMethod x, Context ctx) { + // don't prune JSO.getClass() + if (x.getEnclosingType() == program.getJavaScriptObject()) { + return; + } + if (x.getOverrides().contains(getClassMethod)) { + ctx.removeMe(); + } + } + + public void endVisit(JMethodCall x, Context ctx) { + // don't inline JSO.getClass() + if (x.getTarget().getEnclosingType() == program.getJavaScriptObject()) { + return; + } + // replace overridden getClass() with reference to Object.clazz field + if (x.getTarget() == getClassMethod || + x.getTarget().getOverrides().contains(getClassMethod)) { + ctx.replaceMe(new JFieldRef(x.getSourceInfo(), x.getInstance(), + clazzField, clazzField.getEnclosingType())); + } + } + } +}
diff --git a/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java index 829886f..691752a 100644 --- a/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java +++ b/dev/core/src/com/google/gwt/dev/jjs/impl/ToStringGenerationVisitor.java
@@ -76,6 +76,7 @@ import com.google.gwt.dev.jjs.ast.JReboundEntryPoint; import com.google.gwt.dev.jjs.ast.JReferenceType; import com.google.gwt.dev.jjs.ast.JReturnStatement; +import com.google.gwt.dev.jjs.ast.JSeedIdOf; import com.google.gwt.dev.jjs.ast.JStatement; import com.google.gwt.dev.jjs.ast.JStringLiteral; import com.google.gwt.dev.jjs.ast.JSwitchStatement; @@ -135,6 +136,7 @@ protected static final char[] CHARS_PROTECTED = "protected ".toCharArray(); protected static final char[] CHARS_PUBLIC = "public ".toCharArray(); protected static final char[] CHARS_RETURN = "return".toCharArray(); + protected static final char[] CHARS_SEEDIDOF = " JSeedIdOf ".toCharArray(); protected static final char[] CHARS_SLASHSTAR = "/*".toCharArray(); protected static final char[] CHARS_STARSLASH = "*/".toCharArray(); protected static final char[] CHARS_STATIC = "static ".toCharArray(); @@ -677,7 +679,7 @@ @Override public boolean visit(JNameOf x, Context ctx) { print(CHARS_SLASHSTAR); - print(CHARS_NAMEOF); + print(x instanceof JSeedIdOf ? CHARS_SEEDIDOF : CHARS_NAMEOF); print(CHARS_STARSLASH); printStringLiteral(x.getNode().getName()); return false;
diff --git a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java index 2a430a5..f123b0f 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java +++ b/dev/core/src/com/google/gwt/dev/js/JsSourceGenerationVisitorWithSizeBreakdown.java
@@ -26,6 +26,7 @@ import com.google.gwt.dev.js.ast.JsName; import com.google.gwt.dev.js.ast.JsProgram; import com.google.gwt.dev.js.ast.JsProgramFragment; +import com.google.gwt.dev.js.ast.JsSeedIdOf; import com.google.gwt.dev.js.ast.JsStatement; import com.google.gwt.dev.js.ast.JsVisitable; import com.google.gwt.dev.js.ast.JsVars.JsVar; @@ -82,6 +83,12 @@ } @Override + public boolean visit(JsSeedIdOf x, JsContext ctx) { + out.print(String.valueOf(x.getSeedId())); + return false; + } + + @Override protected <T extends JsVisitable> T doAccept(T node) { JsName newName = nameToBillTo(node); if (newName == null) {
diff --git a/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java b/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java index 82a6379..5df8898 100644 --- a/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java +++ b/dev/core/src/com/google/gwt/dev/js/JsStackEmulator.java
@@ -22,6 +22,9 @@ import com.google.gwt.dev.jjs.HasSourceInfo; import com.google.gwt.dev.jjs.InternalCompilerException; import com.google.gwt.dev.jjs.SourceInfo; +import com.google.gwt.dev.jjs.ast.JMethod; +import com.google.gwt.dev.jjs.ast.JProgram; +import com.google.gwt.dev.jjs.impl.JavaToJavaScriptMap; import com.google.gwt.dev.js.ast.JsArrayAccess; import com.google.gwt.dev.js.ast.JsArrayLiteral; import com.google.gwt.dev.js.ast.JsBinaryOperation; @@ -559,12 +562,22 @@ } /** - * Creates a visitor to instrument each JsFunction in the program. + * Creates a visitor to instrument each JsFunction in the jsProgram. */ private class InstrumentAllFunctions extends JsVisitor { + @Override public void endVisit(JsFunction x, JsContext ctx) { if (!x.getBody().getStatements().isEmpty()) { + JsName fnName = x.getName(); + JMethod method = jjsmap.nameToMethod(fnName); + /** + * Do not instrumental immortal types because they are potentially + * evaluated before anything else has been defined. + */ + if (method != null && jprogram.immortalCodeGenTypes.contains(method.getEnclosingType())) { + return; + } if (recordLineNumbers) { (new LocationVisitor(x)).accept(x.getBody()); } else { @@ -802,9 +815,11 @@ STRIP, NATIVE, EMULATED; } - public static void exec(JsProgram program, PropertyOracle[] propertyOracles) { + public static void exec(JProgram jprogram, JsProgram jsProgram, + PropertyOracle[] propertyOracles, + JavaToJavaScriptMap jjsmap) { if (getStackMode(propertyOracles) == StackMode.EMULATED) { - (new JsStackEmulator(program, propertyOracles)).execImpl(); + (new JsStackEmulator(jprogram, jsProgram, propertyOracles, jjsmap)).execImpl(); } } @@ -839,14 +854,20 @@ private JsFunction caughtFunction; private JsName lineNumbers; - private final JsProgram program; + private JProgram jprogram; + private final JsProgram jsProgram; + private JavaToJavaScriptMap jjsmap; private boolean recordFileNames; private boolean recordLineNumbers; private JsName stack; private JsName stackDepth; - private JsStackEmulator(JsProgram program, PropertyOracle[] propertyOracles) { - this.program = program; + private JsStackEmulator(JProgram jprogram, JsProgram jsProgram, + PropertyOracle[] propertyOracles, + JavaToJavaScriptMap jjsmap) { + this.jprogram = jprogram; + this.jsProgram = jsProgram; + this.jjsmap = jjsmap; assert propertyOracles.length > 0; PropertyOracle oracle = propertyOracles[0]; @@ -865,27 +886,27 @@ } private void execImpl() { - caughtFunction = program.getIndexedFunction("Exceptions.caught"); + caughtFunction = jsProgram.getIndexedFunction("Exceptions.caught"); if (caughtFunction == null) { // No exceptions caught? Weird, but possible. return; } initNames(); makeVars(); - (new ReplaceUnobfuscatableNames()).accept(program); - (new InstrumentAllFunctions()).accept(program); + (new ReplaceUnobfuscatableNames()).accept(jsProgram); + (new InstrumentAllFunctions()).accept(jsProgram); } private void initNames() { - stack = program.getScope().declareName("$JsStackEmulator_stack", "$stack"); - stackDepth = program.getScope().declareName("$JsStackEmulator_stackDepth", + stack = jsProgram.getScope().declareName("$JsStackEmulator_stack", "$stack"); + stackDepth = jsProgram.getScope().declareName("$JsStackEmulator_stackDepth", "$stackDepth"); - lineNumbers = program.getScope().declareName("$JsStackEmulator_location", + lineNumbers = jsProgram.getScope().declareName("$JsStackEmulator_location", "$location"); } private void makeVars() { - SourceInfo info = program.createSourceInfoSynthetic(getClass()); + SourceInfo info = jsProgram.createSourceInfoSynthetic(getClass()); JsVar stackVar = new JsVar(info, stack); stackVar.setInitExpr(new JsArrayLiteral(info)); JsVar stackDepthVar = new JsVar(info, stackDepth); @@ -894,12 +915,12 @@ lineNumbersVar.setInitExpr(new JsArrayLiteral(info)); JsVars vars; - JsStatement first = program.getGlobalBlock().getStatements().get(0); + JsStatement first = jsProgram.getGlobalBlock().getStatements().get(0); if (first instanceof JsVars) { vars = (JsVars) first; } else { vars = new JsVars(info); - program.getGlobalBlock().getStatements().add(0, vars); + jsProgram.getGlobalBlock().getStatements().add(0, vars); } vars.add(stackVar); vars.add(stackDepthVar);
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsSeedIdOf.java b/dev/core/src/com/google/gwt/dev/js/ast/JsSeedIdOf.java new file mode 100644 index 0000000..e7c1778 --- /dev/null +++ b/dev/core/src/com/google/gwt/dev/js/ast/JsSeedIdOf.java
@@ -0,0 +1,41 @@ +/* + * Copyright 2011 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.js.ast; + +import com.google.gwt.dev.jjs.SourceInfo; + +/** + * An AST node whose evaluation results in the numeric seed id of its type. + */ +public class JsSeedIdOf extends JsNameOf { + + private final int seedId; + + public JsSeedIdOf(SourceInfo info, JsName name, int seedId) { + super(info, name); + this.seedId = seedId; + } + + public int getSeedId() { + return seedId; + } + + public void traverse(JsVisitor visitor, JsContext ctx) { + if (visitor.visit(this, ctx)) { + } + visitor.endVisit(this, ctx); + } +}
diff --git a/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java b/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java index e64799f..142e462 100644 --- a/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java +++ b/dev/core/src/com/google/gwt/dev/js/ast/JsVisitor.java
@@ -219,6 +219,10 @@ public void endVisit(JsReturn x, JsContext ctx) { } + public void endVisit(JsSeedIdOf x, JsContext ctx) { + endVisit((JsNameOf) x, ctx); + } + public void endVisit(JsStringLiteral x, JsContext ctx) { } @@ -383,6 +387,10 @@ return true; } + public boolean visit(JsSeedIdOf x, JsContext ctx) { + return visit((JsNameOf) x, ctx); + } + public boolean visit(JsStringLiteral x, JsContext ctx) { return true; }
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 60c809f..3c36c55 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
@@ -173,7 +173,7 @@ public static Array initValues(Class<?> arrayClass, JavaScriptObject castableTypeMap, int queryId, Array array) { ExpandoWrapper.wrapArray(array); - array.arrayClass = arrayClass; + setClass(array, arrayClass); Util.setCastableTypeMap(array, castableTypeMap); array.queryId = queryId; return array; @@ -302,17 +302,15 @@ return array[index] = value; }-*/; + // violator pattern so that the field remains private + private static native void setClass(Object o, Class<?> clazz) /*-{ + o.@java.lang.Object::___clazz = clazz; + }-*/; + /* * Explicitly initialize all fields to JS false values; see comment in * ExpandoWrapper.initExpandos(). */ - - /** - * Holds the real type-specific Class object for a given array instance. The - * compiler produces a magic implementation of getClass() which returns this - * field directly. - */ - protected Class<?> arrayClass = null; /** * A representation of the necessary cast target for objects stored into this
diff --git a/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/SeedUtil.java b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/SeedUtil.java new file mode 100644 index 0000000..bd80104 --- /dev/null +++ b/dev/core/super/com/google/gwt/dev/jjs/intrinsic/com/google/gwt/lang/SeedUtil.java
@@ -0,0 +1,70 @@ +/* + * Copyright 2011 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.lang; + +import com.google.gwt.core.client.JavaScriptObject; + +/** + * Utility class for fetching prototype-seed functions for injection into JsAST. + */ +public class SeedUtil { + + /* + * Holds a map of seedId to anonymous Javascript functions (prototypes for class vtables). + */ + private static JavaScriptObject seedTable = JavaScriptObject.createObject(); + + /** + * If not already created, generates an anonymous function and assigns it a slot in the global + * seedTable. If superSeed is > -1, it creates an instance of the superSeed by invoking + * newSeed() and then assigns it as the prototype of the seed being defined. It also sets up the + * castableTypeMap, as well as any ctors which are passed in via Javascript varargs. Finally, if + * the class literal for this seed id was setup first, which can happen if they are in separate + * code-split fragments, the Class.createFor* methods will have created a placeholder seedTable + * entry containing the Class literal, and this will be copied from the placeholder location + * onto the current prototype. + */ + public static native JavaScriptObject defineSeed(int id, int superSeed, + JavaScriptObject castableTypeMap) /*-{ + var seed = @com.google.gwt.lang.SeedUtil::seedTable[id]; + if (seed && !seed.@java.lang.Object::___clazz) { + // not a placeholder entry setup by Class.setClassLiteral + _ = seed.prototype; + } else { + if (!seed) { + seed = @com.google.gwt.lang.SeedUtil::seedTable[id] = function() { + }; + } + _ = seed.prototype = (superSeed < 0) ? {} + : @com.google.gwt.lang.SeedUtil::newSeed(I)(superSeed); + _.@java.lang.Object::castableTypeMap = castableTypeMap; + } + for (var i = 3; i < arguments.length; ++i) { + arguments[i].prototype = _; + } + if (seed.@java.lang.Object::___clazz) { + _.@java.lang.Object::___clazz = seed.@java.lang.Object::___clazz; + seed.@java.lang.Object::___clazz = null; + } + }-*/; + + /** + * Lookup seed function by id and instantiate an object with it. + */ + public static native JavaScriptObject newSeed(int id) /*-{ + return new (@com.google.gwt.lang.SeedUtil::seedTable[id]); + }-*/; +}
diff --git a/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitterTest.java b/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitterTest.java index 187b660..d9cdc10 100644 --- a/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitterTest.java +++ b/dev/core/test/com/google/gwt/dev/jjs/impl/CodeSplitterTest.java
@@ -31,7 +31,5 @@ ControlFlowAnalyzer cfa = CodeSplitter.computeInitiallyLive(program); assertTrue(cfa.getInstantiatedTypes().contains(findType(program, "com.google.gwt.lang.Array"))); - assertTrue(cfa.getLiveFieldsAndMethods().contains( - findMethod(findType(program, "com.google.gwt.lang.Array"), "getClass"))); } }
diff --git a/user/src/com/google/gwt/rpc/linker/ClientOracleLinker.java b/user/src/com/google/gwt/rpc/linker/ClientOracleLinker.java index eb3c555..9bd5e36 100644 --- a/user/src/com/google/gwt/rpc/linker/ClientOracleLinker.java +++ b/user/src/com/google/gwt/rpc/linker/ClientOracleLinker.java
@@ -77,7 +77,8 @@ builder.add(symbolData.getSymbolName(), symbolData.getJsniIdent(), symbolData.getClassName(), symbolData.getMemberName(), symbolData.getQueryId(), - new CastableTypeDataImpl(castableTypeMapString)); + new CastableTypeDataImpl(castableTypeMapString), + symbolData.getSeedId()); } ByteArrayOutputStream out = new ByteArrayOutputStream();
diff --git a/user/src/com/google/gwt/rpc/server/WebModeClientOracle.java b/user/src/com/google/gwt/rpc/server/WebModeClientOracle.java index d91c0e3..4c8976b 100644 --- a/user/src/com/google/gwt/rpc/server/WebModeClientOracle.java +++ b/user/src/com/google/gwt/rpc/server/WebModeClientOracle.java
@@ -52,7 +52,8 @@ private WebModeClientOracle oracle = new WebModeClientOracle(); public void add(String jsIdent, String jsniIdent, String className, - String memberName, int queryId, CastableTypeData castableTypeData) { + String memberName, int queryId, CastableTypeData castableTypeData, + int seedId) { oracle.idents.add(jsIdent); ClassData data = oracle.getClassData(className); @@ -73,6 +74,9 @@ data.typeName = className; data.seedName = jsIdent; oracle.seedNamesToClassData.put(jsIdent, data); + // Class.getName() with metadata disabled is "Class$S<seedId>" + oracle.seedIdsToClassData.put("S" + seedId, data); + data.seedId = seedId; } else { if (jsniIdent.contains("(")) { jsniIdent = jsniIdent.substring(jsniIdent.indexOf("::") + 2, @@ -131,6 +135,7 @@ public String seedName; public List<String> serializableFields = Collections.emptyList(); public String typeName; + public int seedId; } /** @@ -139,7 +144,7 @@ * TODO: Use something other than Java serialization to store this type's * data. */ - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 2L; /** * Recreate a WebModeClientOracle based on the contents previously emitted by @@ -222,6 +227,7 @@ private final Set<String> idents = new HashSet<String>(); private final Map<String, ClassData> seedNamesToClassData = new HashMap<String, ClassData>(); + private final Map<String, ClassData> seedIdsToClassData = new HashMap<String, ClassData>(); private transient Map<Class<?>, Field[]> operableFieldMap = new IdentityHashMap<Class<?>, Field[]>(); @@ -381,6 +387,9 @@ seedName = seedName.substring(6); } ClassData data = seedNamesToClassData.get(seedName); + if (data == null) { + data = seedIdsToClassData.get(seedName); + } return data == null ? null : data.typeName; }
diff --git a/user/src/com/google/gwt/rpc/server/WebModePayloadSink.java b/user/src/com/google/gwt/rpc/server/WebModePayloadSink.java index b5716fd..7e9f431 100644 --- a/user/src/com/google/gwt/rpc/server/WebModePayloadSink.java +++ b/user/src/com/google/gwt/rpc/server/WebModePayloadSink.java
@@ -257,12 +257,14 @@ byte[] currentBackRef = begin(x); byte[] constructorFunction = constructorFunction(x); - String seedName = clientOracle.getSeedName(x.getTargetClass()); - if (seedName == null) { - throw new IncompatibleRemoteServiceException( - "The client cannot create type " + x.getTargetClass()); - } + String getSeedFunc = clientOracle.getMethodId("java.lang.Class", + "getSeedFunction", "Ljava/lang/Class;"); + String classLitId = clientOracle.getFieldId( + "com.google.gwt.lang.ClassLiteralHolder", + getJavahSignatureName(x.getTargetClass()) + "_classLit"); + assert classLitId != null : "No class literal for " + + x.getTargetClass().getName(); /* * If we need to maintain a backreference to the object, it's established @@ -270,7 +272,7 @@ * constructorFunction. This is done in case one of the fields should * require a reference to the object that is currently being constructed. */ - // constructorFunctionFoo(x = new Foo, field1, field2) + // constructorFunctionFoo(x = new (classLit.getSeedFunction()), field1, field2) push(constructorFunction); lparen(); if (hasBackRef) { @@ -278,7 +280,12 @@ eq(); } _new(); - push(seedName); + lparen(); + push(getSeedFunc); + lparen(); + push(classLitId); + rparen(); + rparen(); for (SetCommand setter : x.getSetters()) { comma(); accept(setter.getValue()); @@ -562,7 +569,7 @@ byte[] ident = getBytes("_0"); - // function foo(_0) {return initValues(classLid, castableTypeData, queryId, _0)} + // function foo(_0) {return initValues(classLit, castableTypeData, queryId, _0)} function(); push(functionName); lparen();
diff --git a/user/super/com/google/gwt/emul/java/lang/Class.java b/user/super/com/google/gwt/emul/java/lang/Class.java index d000d2d..b2413ec 100644 --- a/user/super/com/google/gwt/emul/java/lang/Class.java +++ b/user/super/com/google/gwt/emul/java/lang/Class.java
@@ -30,16 +30,21 @@ private static final int ARRAY = 0x00000004; private static final int ENUM = 0x00000008; + static native String asString(int number) /*-{ + // for primitives, the seedId isn't a number, but a string like ' Z' + return typeof(number) == 'number' ? "S" + (number < -1 ? -number : number): number; + }-*/; + /** * Create a Class object for an array. * * @skip */ static <T> Class<T> createForArray(String packageName, String className, - String seedName, Class<?> componentType) { + int seedId, Class<?> componentType) { // Initialize here to avoid method inliner Class<T> clazz = new Class<T>(); - setName(clazz, packageName, className, seedName); + setName(clazz, packageName, className, seedId != -1 ? -seedId : -1); clazz.modifiers = ARRAY; clazz.superclass = Object.class; clazz.componentType = componentType; @@ -52,10 +57,10 @@ * @skip */ static <T> Class<T> createForClass(String packageName, String className, - String seedName, Class<? super T> superclass) { + int seedId, Class<? super T> superclass) { // Initialize here to avoid method inliner Class<T> clazz = new Class<T>(); - setName(clazz, packageName, className, seedName); + setName(clazz, packageName, className, seedId); clazz.superclass = superclass; return clazz; } @@ -66,11 +71,11 @@ * @skip */ static <T> Class<T> createForEnum(String packageName, String className, - String seedName, Class<? super T> superclass, + int seedId, Class<? super T> superclass, JavaScriptObject enumConstantsFunc, JavaScriptObject enumValueOfFunc) { // Initialize here to avoid method inliner Class<T> clazz = new Class<T>(); - setName(clazz, packageName, className, seedName); + setName(clazz, packageName, className, seedId); clazz.modifiers = (enumConstantsFunc != null) ? ENUM : 0; clazz.superclass = clazz.enumSuperclass = superclass; clazz.enumConstantsFunc = enumConstantsFunc; @@ -86,7 +91,7 @@ static <T> Class<T> createForInterface(String packageName, String className) { // Initialize here to avoid method inliner Class<T> clazz = new Class<T>(); - setName(clazz, packageName, className, null); + setName(clazz, packageName, className, -1); clazz.modifiers = INTERFACE; return clazz; } @@ -97,21 +102,73 @@ * @skip */ static Class<?> createForPrimitive(String packageName, String className, - String seedName) { + int seedId) { // Initialize here to avoid method inliner Class<?> clazz = new Class<Object>(); - setName(clazz, packageName, className, seedName); + setName(clazz, packageName, className, seedId); clazz.modifiers = PRIMITIVE; return clazz; } + /** + * Used by {@link WebModePayloadSink} to create uninitialized instances. + */ + static native JavaScriptObject getSeedFunction(Class<?> clazz) /*-{ + var func = @com.google.gwt.lang.SeedUtil::seedTable[clazz.@java.lang.Class::seedId]; + clazz = null; // HACK: prevent pruning via inlining by using param as lvalue + return func; + }-*/; + static boolean isClassMetadataEnabled() { // This body may be replaced by the compiler return true; } + /** + * null or -1 implies lack of seed function / non-instantiable type + */ + static native boolean isInstantiable(int seedId) /*-{ + return seedId != null && typeof (seedId) == 'number' && seedId > -1; + }-*/; + + /** + * Install class literal into seed.prototype.clazz field such that + * Object.getClass() returning this.clazz returns the literal. Also stores + * seedId on class literal for looking up prototypes given a literal. This + * is used for deRPC at the moment, but may be used to implement + * Class.newInstance() in the future. + */ + static native void setClassLiteral(int seedId, Class<?> clazz) /*-{ + var proto; + clazz.@java.lang.Class::seedId = seedId; + // String is the exception to the usual vtable setup logic + if (seedId == 2) { + proto = String.prototype + } else { + if (seedId > -1) { + // Guarantees virtual method won't be pruned by using a JSNI ref + // This is required because deRPC needs to call it. + var seed = @java.lang.Class::getSeedFunction(Ljava/lang/Class;)(clazz); + // A class literal may be referenced prior to an async-loaded vtable setup + // For example, class literal lives in inital fragment, + // but type is instantiated in another fragment + if (seed) { + proto = seed.prototype; + } else { + // Leave a place holder for now to be filled in by __defineSeed__ later + seed = @com.google.gwt.lang.SeedUtil::seedTable[seedId] = function(){}; + seed.@java.lang.Object::___clazz = clazz; + return; + } + } else { + return; + } + } + proto.@java.lang.Object::___clazz = clazz; + }-*/; + static void setName(Class<?> clazz, String packageName, String className, - String seedName) { + int seedId) { if (clazz.isClassMetadataEnabled()) { clazz.typeName = packageName + className; } else { @@ -121,7 +178,11 @@ * during application start up, before class Integer has been initialized. */ clazz.typeName = "Class$" - + (seedName != null ? seedName : "" + clazz.hashCode()); + + (seedId != -1 ? asString(seedId) : asString(clazz.hashCode())); + } + + if (isInstantiable(seedId)) { + setClassLiteral(seedId, clazz); } } @@ -140,6 +201,8 @@ private String typeName; + private int seedId; + /** * Not publicly instantiable. *
diff --git a/user/super/com/google/gwt/emul/java/lang/Object.java b/user/super/com/google/gwt/emul/java/lang/Object.java index 3b8efff..63717d2 100644 --- a/user/super/com/google/gwt/emul/java/lang/Object.java +++ b/user/super/com/google/gwt/emul/java/lang/Object.java
@@ -26,6 +26,14 @@ public class Object { /** + * Holds class literal for subtypes of Object. + */ + // BUG: If this field name conflicts with a method param name, JDT will complain + // CHECKSTYLE_OFF + private transient Class<?> ___clazz; + // CHECKSTYLE_ON + + /** * Used by {@link com.google.gwt.core.client.impl.WeakMapping} in web mode * to store an expando containing a String -> Object mapping. * @@ -60,14 +68,11 @@ } /* - * Note: Unlike the real JRE, we don't spec this method as final because the - * compiler generates a polymorphic override on every other class which will - * return the correct class object. - * - * TODO(scottb, compiler magician): declare this final, but have the compiler fix it up. + * magic; Actual assignment to this field is done by Class.createFor() methods by injecting it + * into the prototype. */ public Class<? extends Object> getClass() { - return Object.class; + return ___clazz; } public int hashCode() {